#!/usr/bin/env python3
"""PM_K-1 audio chain - Stage 1b: Hi-Z instrument DI buffer + line/instrument select (SKiDL).

Run INSIDE the EDA container:
    cd hardware/eda && ./run.sh python3 ../eda/circuits/stage1b_select.py
Outputs ERC + a KiCad netlist at hardware/kicad/stage1b_select.net.

WHAT THIS STAGE DOES
  The same input jack feeds either the balanced LINE receiver (Stage 1) or a Hi-Z
  INSTRUMENT buffer (here). One DPDT relay (K1) does two jobs at once:
    pole 1  routes the jack TIP to the line receiver (default) OR the DI buffer
    pole 2  selects which OUTPUT (RX_OUT or DI_OUT) feeds the summing stage
  De-energized (relay OFF) = LINE = the common case (saves coil power; fail to line).
  The DI buffer = OPA1641 non-inverting, gain +12 dB (1+Rf/Rg), 1 Mohm input.

PINOUTS VERIFIED FROM DATASHEETS
  * OPA1641 (TI SBOS484D): 1=NC 2=-IN 3=+IN 4=V- 5=NC 6=OUT 7=V+ 8=NC. Supply +/-18V max.
  * TQ2SA (Panasonic TQ-SMD): 2 Form C. pole1 COM=3 (throws 2,4); pole2 COM=8 (throws 7,9)
    -- confirmed from the contact-resistance terminal pairs (2-3,3-4,7-8,8-9).
  RESOLVED from the Panasonic TQ-SMD connection diagram (single-side-stable, top view)
  + the contact-resistance terminal pairs (2-3,3-4,7-8,8-9):
  * coil = pins 1 & 10 (polarity irrelevant -- non-latching single coil, no diode).
  * pole1 COM=3, NC=4, NO=2 ; pole2 COM=8, NC=7, NO=9 ; pins 5,6 unused.
  NC/NO sense is also firmware-correctable: the relay is GPIO-driven, so if a physical
  unit reads opposite, invert SEL_LINST in firmware (no board change).
  Relay coil is driven by the SHARED ULN2003 (U14) -- represented here as net K1_DRV
  (sinks the coil) + SEL_LINST (the RP2350 GPIO). The ULN2003 is instantiated once in
  the power/control block, not per-relay.
"""
import os
from skidl import *

set_default_tool(KICAD9)

R = Part("Device", "R", dest=TEMPLATE, footprint="Resistor_SMD:R_0805_2012Metric")
C = Part("Device", "C", dest=TEMPLATE, footprint="Capacitor_SMD:C_0805_2012Metric")
D = Part("Device", "D", dest=TEMPLATE, footprint="Diode_SMD:D_SOD-323")

# ---- rails ----
p15, n15, gnd, p5 = Net("+15V"), Net("-15V"), Net("GND"), Net("+5V")
for n in (p15, n15, gnd, p5):
    n.drive = POWER

# ---- OPA1641 JFET Hi-Z buffer (pinout verified) ----
OPA = Part(name="OPA1641", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
    footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
    pins=[
        Pin(num=1, name="NC1", func=Pin.types.NOCONNECT),
        Pin(num=2, name="-IN", func=Pin.types.INPUT),
        Pin(num=3, name="+IN", func=Pin.types.INPUT),
        Pin(num=4, name="V-",  func=Pin.types.PWRIN),
        Pin(num=5, name="NC5", func=Pin.types.NOCONNECT),
        Pin(num=6, name="OUT", func=Pin.types.OUTPUT),
        Pin(num=7, name="V+",  func=Pin.types.PWRIN),
        Pin(num=8, name="NC8", func=Pin.types.NOCONNECT),
    ])
u = OPA(ref="U2")

# ---- TQ2SA DPDT select relay (contacts verified; NC/NO + coil pins flagged) ----
RLY = Part(name="TQ2SA-5V", tool=SKIDL, dest=TEMPLATE, ref_prefix="K",
    footprint="Relay_SMD:Relay_DPDT_Panasonic_TQ2-SA",   # footprint: confirm at layout
    pins=[
        Pin(num=1,  name="COIL_A", func=Pin.types.PASSIVE),
        Pin(num=10, name="COIL_B", func=Pin.types.PASSIVE),
        Pin(num=3,  name="P1_COM", func=Pin.types.PASSIVE),
        Pin(num=4,  name="P1_NC",  func=Pin.types.PASSIVE),   # de-energized = LINE
        Pin(num=2,  name="P1_NO",  func=Pin.types.PASSIVE),   # energized = INSTRUMENT
        Pin(num=8,  name="P2_COM", func=Pin.types.PASSIVE),
        Pin(num=7,  name="P2_NC",  func=Pin.types.PASSIVE),
        Pin(num=9,  name="P2_NO",  func=Pin.types.PASSIVE),
        Pin(num=5,  name="NC5",    func=Pin.types.NOCONNECT),
        Pin(num=6,  name="NC6",    func=Pin.types.NOCONNECT),
    ])
k1 = RLY(ref="K1")

# ---- nets (shared with Stage 1 / Stage 3 by name) ----
ain_hot    = Net("AIN_HOT")     # jack TIP
rx_hot_in  = Net("RX_HOT_IN")   # -> Stage 1 line-receiver hot protection (was AIN_HOT there)
di_in      = Net("DI_IN")       # -> this DI buffer input
rx_out     = Net("RX_OUT")      # <- Stage 1 line receiver output
di_out     = Net("DI_OUT")      # this DI buffer output
stage1_out = Net("STAGE1_OUT")  # selected -> Stage 3 summing
sel        = Net("SEL_LINST")   # RP2350 GPIO: low=LINE(default), high=INSTRUMENT
k1_drv     = Net("K1_DRV")      # shared ULN2003 output sinks the coil

# pole 1: route jack tip
k1["P1_COM"] += ain_hot
k1["P1_NC"]  += rx_hot_in       # default -> line receiver
k1["P1_NO"]  += di_in           # energized -> DI buffer
# pole 2: select output
k1["P2_COM"] += stage1_out
k1["P2_NC"]  += rx_out          # default -> line receiver output
k1["P2_NO"]  += di_out          # energized -> DI buffer output
# coil: +5V -- coil -- K1_DRV (ULN2003 sinks to gnd when SEL_LINST high)
k1["COIL_A"] += p5
k1["COIL_B"] += k1_drv

# ---- DI buffer input: DC-block, 1M bias, rail clamps ----
cblk = C(value="100nF", footprint="Capacitor_SMD:C_1206_3216Metric")  # film
rbias = R(value="1Meg")
dp, dn = D(value="1N4148WS"), D(value="1N4148WS")
node = Net("DI_NODE")
di_in += cblk[1]; cblk[2] += node
rbias[1] += node; rbias[2] += gnd
dp[1] += p15; dp[2] += node     # Device:D pin1=K, pin2=A -> high clamp (>+15)
dn[1] += node; dn[2] += n15     # low clamp (< -15)
u["+IN"] += node

# ---- non-inverting gain: Av = 1 + Rf/Rg = 1 + 3k/1k = 4 (+12 dB) ----
rf, rg = R(value="3k"), R(value="1k")
u["OUT"] += di_out
rf[1] += di_out; rf[2] += u["-IN"]
rg[1] += u["-IN"]; rg[2] += gnd

# ---- supplies + decoupling ----
u["V+"] += p15; u["V-"] += n15
for rail in (p15, n15):
    c = C(value="100nF"); rail += c[1]; c[2] += gnd

ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "stage1b_select.net"))
generate_netlist(file_=out)
print("Stage 1b netlist ->", out)
