diff --git a/hardware/.gitkeep b/hardware/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/hardware/bom/.gitkeep b/hardware/bom/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/hardware/bom/merlin-bci-rev0.csv b/hardware/bom/merlin-bci-rev0.csv new file mode 100644 index 0000000..af5ff4f --- /dev/null +++ b/hardware/bom/merlin-bci-rev0.csv @@ -0,0 +1,126 @@ +RefDes,Qty,Value / MPN,Package,Description,Notes +U1,1,ADS1299IPAGR,TQFP-64,8-ch 24-bit delta-sigma EEG/ECG ADC,"TI; DVDD 1.8–3.6V, AVDD ±(2.5–5.25V)" +U2,1,ESP32-WROOM-32E (4MB),SMD module (18×25.5mm),"Dual-core 240MHz, WiFi+BT MCU","brainflow CUSTOM_BOARD via WiFi; or use devkit during firmware dev" +U3,1,MGJ2D121505SC,12-SMD SIP,"Isolated DC/DC 5V in → ±5V out, 2W","Murata; patient isolation barrier for AVDD/AVSS supply" +U4,1,REF3325AIDBZR,SOT-23-5,"2.5V precision ref/LDO, 50ppm/°C","TI; AVDD rail (patient side)" +U5,1,TPS60403DBVR,SOT-23-5,"Inverting charge pump −2.5V (AVSS)","TI; no inductor; in from isolated +5V" +U6,1,TLV1117-33DCYR,SOT-223,"3.3V 800mA LDO for DVDD + MCU","Input from isolated +5V rail" +Y1,1,2.048MHz ±50ppm,3225 4-pad SMD,"Crystal for ADS1299 CLK","Ties to CLK pin; CLKSEL → DVDD" +C_CLK1,1,12pF C0G 0402,0402,Crystal load cap X1,Match to crystal spec (typically 8–18pF) +C_CLK2,1,12pF C0G 0402,0402,Crystal load cap X2, +R_IN1,1,10kΩ 1% 0402,0402,IN1P input series protection, +R_IN2,1,10kΩ 1% 0402,0402,IN1N input series protection, +R_IN3,1,10kΩ 1% 0402,0402,IN2P input series protection, +R_IN4,1,10kΩ 1% 0402,0402,IN2N input series protection, +R_IN5,1,10kΩ 1% 0402,0402,IN3P input series protection, +R_IN6,1,10kΩ 1% 0402,0402,IN3N input series protection, +R_IN7,1,10kΩ 1% 0402,0402,IN4P input series protection, +R_IN8,1,10kΩ 1% 0402,0402,IN4N input series protection, +R_IN9,1,10kΩ 1% 0402,0402,IN5P input series protection, +R_IN10,1,10kΩ 1% 0402,0402,IN5N input series protection, +R_IN11,1,10kΩ 1% 0402,0402,IN6P input series protection, +R_IN12,1,10kΩ 1% 0402,0402,IN6N input series protection, +R_IN13,1,10kΩ 1% 0402,0402,IN7P input series protection, +R_IN14,1,10kΩ 1% 0402,0402,IN7N input series protection, +R_IN15,1,10kΩ 1% 0402,0402,IN8P input series protection, +R_IN16,1,10kΩ 1% 0402,0402,IN8N input series protection, +C_AC1,1,10µF X5R 0402,0402,"AC coupling / HPF ch1P (fc ≈ 1.6Hz @ 10kΩ)","Between R_IN and ADS1299 pin" +C_AC2,1,10µF X5R 0402,0402,AC coupling / HPF ch1N, +C_AC3,1,10µF X5R 0402,0402,AC coupling / HPF ch2P, +C_AC4,1,10µF X5R 0402,0402,AC coupling / HPF ch2N, +C_AC5,1,10µF X5R 0402,0402,AC coupling / HPF ch3P, +C_AC6,1,10µF X5R 0402,0402,AC coupling / HPF ch3N, +C_AC7,1,10µF X5R 0402,0402,AC coupling / HPF ch4P, +C_AC8,1,10µF X5R 0402,0402,AC coupling / HPF ch4N, +C_AC9,1,10µF X5R 0402,0402,AC coupling / HPF ch5P, +C_AC10,1,10µF X5R 0402,0402,AC coupling / HPF ch5N, +C_AC11,1,10µF X5R 0402,0402,AC coupling / HPF ch6P, +C_AC12,1,10µF X5R 0402,0402,AC coupling / HPF ch6N, +C_AC13,1,10µF X5R 0402,0402,AC coupling / HPF ch7P, +C_AC14,1,10µF X5R 0402,0402,AC coupling / HPF ch7N, +C_AC15,1,10µF X5R 0402,0402,AC coupling / HPF ch8P, +C_AC16,1,10µF X5R 0402,0402,AC coupling / HPF ch8N, +C_EMI1,1,4.7nF C0G 0402,0402,EMI shunt ch1P to AGND, +C_EMI2,1,4.7nF C0G 0402,0402,EMI shunt ch1N to AGND, +C_EMI3,1,4.7nF C0G 0402,0402,EMI shunt ch2P to AGND, +C_EMI4,1,4.7nF C0G 0402,0402,EMI shunt ch2N to AGND, +C_EMI5,1,4.7nF C0G 0402,0402,EMI shunt ch3P to AGND, +C_EMI6,1,4.7nF C0G 0402,0402,EMI shunt ch3N to AGND, +C_EMI7,1,4.7nF C0G 0402,0402,EMI shunt ch4P to AGND, +C_EMI8,1,4.7nF C0G 0402,0402,EMI shunt ch4N to AGND, +C_EMI9,1,4.7nF C0G 0402,0402,EMI shunt ch5P to AGND, +C_EMI10,1,4.7nF C0G 0402,0402,EMI shunt ch5N to AGND, +C_EMI11,1,4.7nF C0G 0402,0402,EMI shunt ch6P to AGND, +C_EMI12,1,4.7nF C0G 0402,0402,EMI shunt ch6N to AGND, +C_EMI13,1,4.7nF C0G 0402,0402,EMI shunt ch7P to AGND, +C_EMI14,1,4.7nF C0G 0402,0402,EMI shunt ch7N to AGND, +C_EMI15,1,4.7nF C0G 0402,0402,EMI shunt ch8P to AGND, +C_EMI16,1,4.7nF C0G 0402,0402,EMI shunt ch8N to AGND, +D1,1,BAV99,SOT-23,"Dual Schottky clamp ch1 (INP+INN to AVDD/AVSS)","1 BAV99 = 2 diodes; clamp one diff pair" +D2,1,BAV99,SOT-23,Dual Schottky clamp ch2, +D3,1,BAV99,SOT-23,Dual Schottky clamp ch3, +D4,1,BAV99,SOT-23,Dual Schottky clamp ch4, +D5,1,BAV99,SOT-23,Dual Schottky clamp ch5, +D6,1,BAV99,SOT-23,Dual Schottky clamp ch6, +D7,1,BAV99,SOT-23,Dual Schottky clamp ch7, +D8,1,BAV99,SOT-23,Dual Schottky clamp ch8, +R_BIAS1,1,820kΩ 1% 0402,0402,Input bias ch1P (BIASIN network to BIASOUT), +R_BIAS2,1,820kΩ 1% 0402,0402,Input bias ch1N, +R_BIAS3,1,820kΩ 1% 0402,0402,Input bias ch2P, +R_BIAS4,1,820kΩ 1% 0402,0402,Input bias ch2N, +R_BIAS5,1,820kΩ 1% 0402,0402,Input bias ch3P, +R_BIAS6,1,820kΩ 1% 0402,0402,Input bias ch3N, +R_BIAS7,1,820kΩ 1% 0402,0402,Input bias ch4P, +R_BIAS8,1,820kΩ 1% 0402,0402,Input bias ch4N, +R_BIAS9,1,820kΩ 1% 0402,0402,Input bias ch5P, +R_BIAS10,1,820kΩ 1% 0402,0402,Input bias ch5N, +R_BIAS11,1,820kΩ 1% 0402,0402,Input bias ch6P, +R_BIAS12,1,820kΩ 1% 0402,0402,Input bias ch6N, +R_BIAS13,1,820kΩ 1% 0402,0402,Input bias ch7P, +R_BIAS14,1,820kΩ 1% 0402,0402,Input bias ch7N, +R_BIAS15,1,820kΩ 1% 0402,0402,Input bias ch8P, +R_BIAS16,1,820kΩ 1% 0402,0402,Input bias ch8N, +R_DRL1,1,1MΩ 1% 0402,0402,DRL feedback resistor (RLDFB), +R_DRL2,1,680Ω 1% 0402,0402,DRL output protection, +C_DRL1,1,4.7nF C0G 0402,0402,DRL loop stability capacitor,In parallel with R_DRL1 +C_VREFP1,1,10µF X5R 6.3V 0805,0805,VREFP bulk bypass, +C_VREFP2,1,100nF C0G 0402,0402,VREFP HF bypass, +C_AVDD1,1,10µF X5R 10V 0805,0805,AVDD bulk bypass #1, +C_AVDD2,1,10µF X5R 10V 0805,0805,AVDD bulk bypass #2, +C_AVDD3,1,100nF C0G 0402,0402,AVDD HF bypass #1, +C_AVDD4,1,100nF C0G 0402,0402,AVDD HF bypass #2, +C_AVDD5,1,100nF C0G 0402,0402,AVDD HF bypass #3, +C_AVSS1,1,10µF X5R 10V 0805,0805,AVSS bulk bypass #1 (to AGND), +C_AVSS2,1,10µF X5R 10V 0805,0805,AVSS bulk bypass #2, +C_AVSS3,1,100nF C0G 0402,0402,AVSS HF bypass #1, +C_AVSS4,1,100nF C0G 0402,0402,AVSS HF bypass #2, +C_AVSS5,1,100nF C0G 0402,0402,AVSS HF bypass #3, +C_DVDD1,1,100nF C0G 0402,0402,DVDD bypass pin 1, +C_DVDD2,1,100nF C0G 0402,0402,DVDD bypass pin 2, +C_DVDD3,1,100nF C0G 0402,0402,DVDD bypass pin 3, +C_DVDD4,1,10µF X5R 0805,0805,DVDD bulk bypass, +R_SPI1,1,33Ω 0402,0402,SPI MOSI series termination,MCU side of trace +R_SPI2,1,33Ω 0402,0402,SPI MISO series termination, +R_SPI3,1,33Ω 0402,0402,SPI SCLK series termination, +R_SPI4,1,33Ω 0402,0402,SPI CS series termination, +R_DRDY,1,1kΩ 0402,0402,DRDY pull-up to DVDD, +C_DRDY,1,100pF C0G 0402,0402,DRDY glitch filter, +R_PU1,1,10kΩ 0402,0402,RESET pull-up to DVDD, +R_PU2,1,10kΩ 0402,0402,START pull-up to DVDD, +R_PU3,1,10kΩ 0402,0402,PWDN pull-up to DVDD, +R_PU4,1,10kΩ 0402,0402,CLKSEL tie to DVDD (ext crystal mode),"Can use 0Ω; 10kΩ keeps GPIO optional" +C_MCU1,1,100nF X5R 0402,0402,ESP32 VDDA bypass, +C_MCU2,1,100nF X5R 0402,0402,ESP32 3V3 bypass #1, +C_MCU3,1,100nF X5R 0402,0402,ESP32 3V3 bypass #2, +C_MCU4,1,100nF X5R 0402,0402,ESP32 3V3 bypass #3, +C_MCU5,1,10µF X5R 0805,0805,ESP32 3V3 bulk bypass, +C_ISO1,1,10µF X5R 10V 0805,0805,Isolated +5V rail bulk cap,After MGJ2D121505SC output +C_ISO2,1,100nF C0G 0402,0402,Isolated +5V HF bypass, +C_ISO3,1,10µF X5R 10V 0805,0805,Isolated −5V rail bulk cap, +C_ISO4,1,100nF C0G 0402,0402,Isolated −5V HF bypass, +J1,1,USB4085-GF-A,USB-C mid-mount SMD,USB-C connector to host,"GCT; data carried wirelessly (WiFi); USB only for firmware flash" +J2,1,3.5mm TRRS × 9,TH or panel mount,"Electrode jacks: 8 channels + DRL ref","Or Touchproof Ag/AgCl snap connector header" +J3,1,2×5 2.54mm header,TH,SWD/JTAG debug header for ESP32,Optional +J4,1,2×2 2.54mm header,TH,BOOT / EN jumpers for ESP32 flash mode, +FB1,1,600Ω @ 100MHz 0805,0805,Ferrite bead on DVDD supply,Isolate digital noise from analog rails +FB2,1,600Ω @ 100MHz 0805,0805,Ferrite bead on AGND bridge to DGND,Star-ground point; single-point bond diff --git a/hardware/cad/.gitkeep b/hardware/cad/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/hardware/cad/2d/.gitkeep b/hardware/cad/2d/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/hardware/cad/3d/.gitkeep b/hardware/cad/3d/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/hardware/cad/pcb/.gitkeep b/hardware/cad/pcb/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/hardware/cad/schematic/.gitkeep b/hardware/cad/schematic/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/hardware/cam/.gitkeep b/hardware/cam/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/hardware/cam/drill/.gitkeep b/hardware/cam/drill/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/hardware/cam/gerbers/.gitkeep b/hardware/cam/gerbers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/hardware/cam/pick-and-place/.gitkeep b/hardware/cam/pick-and-place/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/hardware/docs/.gitkeep b/hardware/docs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/hardware/parts/.gitkeep b/hardware/parts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/hardware/parts/datasheets/.gitkeep b/hardware/parts/datasheets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/hardware/parts/footprints/.gitkeep b/hardware/parts/footprints/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/hardware/parts/symbols/.gitkeep b/hardware/parts/symbols/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/hardware/sim/.gitkeep b/hardware/sim/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/merlin/actions/executor.py b/merlin/actions/executor.py index edf23d7..e43811b 100644 --- a/merlin/actions/executor.py +++ b/merlin/actions/executor.py @@ -32,6 +32,8 @@ _XDOTOOL_CLICK: dict[str, list[str]] = { "middle_click": ["xdotool", "click", "2"], "scroll_up": ["xdotool", "click", "4"], "scroll_down": ["xdotool", "click", "5"], + "scroll_left": ["xdotool", "click", "6"], + "scroll_right": ["xdotool", "click", "7"], } _XDOTOOL_KEY: dict[str, str] = { @@ -39,6 +41,9 @@ _XDOTOOL_KEY: dict[str, str] = { "key_escape": "Escape", "key_space": "space", "key_tab": "Tab", + "key_ctrl_z": "ctrl+z", + "key_ctrl_c": "ctrl+c", + "key_ctrl_v": "ctrl+v", } @@ -51,6 +56,10 @@ class ActionExecutor: else: self._backend = backend + @property + def backend(self) -> Backend: + return self._backend + def execute(self, action: str) -> None: """Execute a named action. Silently ignores unknown action names.""" try: @@ -86,11 +95,21 @@ class ActionExecutor: pyautogui.scroll(3) elif action == "scroll_down": pyautogui.scroll(-3) + elif action == "scroll_left": + pyautogui.hscroll(-3) + elif action == "scroll_right": + pyautogui.hscroll(3) elif action == "key_enter": pyautogui.press("enter") elif action == "key_escape": pyautogui.press("escape") elif action == "key_space": pyautogui.press("space") + elif action == "key_ctrl_z": + pyautogui.hotkey("ctrl", "z") + elif action == "key_ctrl_c": + pyautogui.hotkey("ctrl", "c") + elif action == "key_ctrl_v": + pyautogui.hotkey("ctrl", "v") else: logger.warning("pyautogui: unknown action %r", action) diff --git a/merlin/config.py b/merlin/config.py index 3994f51..a19c5be 100644 --- a/merlin/config.py +++ b/merlin/config.py @@ -25,6 +25,11 @@ DEFAULT_MAPPINGS: dict[str, str] = { "head_tilt_right": "scroll_down", "open_palm": "key_space", "pinch": "drag_start", + "fist": "key_ctrl_z", + "gaze_left": "scroll_left", + "gaze_right": "scroll_right", + "gaze_up": "scroll_up", + "gaze_down": "scroll_down", } diff --git a/merlin/daemon.py b/merlin/daemon.py index b1c9d81..fc112b6 100644 --- a/merlin/daemon.py +++ b/merlin/daemon.py @@ -82,6 +82,12 @@ def _camera_loop() -> None: if action: _executor.execute(action) + gaze = gaze_est.estimate(face) + if gaze.label != "center": + action = _mapper.map(f"gaze_{gaze.label}") + if action: + _executor.execute(action) + hands = hands_det.detect(rgb) if hands: gesture = hand_det.detect(hands[0].points) @@ -119,7 +125,7 @@ app.add_middleware( def status() -> dict[str, Any]: return { "running": _running, - "backend": _executor._backend, + "backend": _executor.backend, "config": { "camera_device": _config.camera_device, "blink_threshold": _config.blink_threshold,