This repository contains the full software surface for running the Gradi blink proxy rig:
gradi-proxy-esp/: Arduino sketch for the XIAO ESP32S3 + DRV8833 + VCNL4040 hardware. The firmware streams proximity samples, blink detection metrics, and responds toSTART/STOPserial commands that drive the pump/valve sequence.server.js: Node.js bridge that opens the serial port, parses firmware output, controls blink sequences based on confidence thresholds, and broadcasts structured data to the frontend via WebSocket.public/: Browser dashboard that visualises proximity samples, blink events, and pump sequence state.
- Node.js 18+
- Arduino-flashed ESP32S3 running
gradi-proxy-esp/gradi-proxy-esp.ino
npm install
cp .env.example .env
# edit .env to match your serial port and preferred thresholds.env.example documents every configuration option; copy it and adjust values for your environment (serial path, baud rate, auto-control thresholds).
- Drive the DRV8833 motor supply (
VM) at 6.0 V so the valve receives its required rail; ensure ESP32 and driver share ground and add ≥470 µF bulk capacitance at the driver. - The firmware caps the pump PWM duty at 767/1023 (~75 %) which keeps the pump’s effective voltage at ≈4.5 V despite the higher supply. Adjusting
duty_runabove this limit has no effect—the clamp protects the hardware. - Default timing now spends 400 ms in PRECHARGE and 70 ms in PUFF so the pump reaches full duty before the valve opens while remaining within each 400 ms slot.
npm start
npm start -- --port /dev/gradi-esp-compress
# server logs: HTTP: http://localhost:3007Open http://localhost:3007 in a browser to load the dashboard. The UI auto-connects to /ws, streams proximity data, and mirrors controller state. Manual start/stop buttons emit JSON messages that route back through the Node controller, so manual and auto control share the same path.
Common CLI flags (pass after -- so npm forwards them):
--port <device>— override the serial device from.env(defaults to/dev/gradi-esp-compress, but any/dev/...path or bare device name works).--fullscreen— launch Chrome in kiosk mode on Windows/WSL, pointing at the dashboard.
Example:
npm start -- --port /dev/gradi-esp-compress
# shorthand with a bare positional arg (will show a warning but still works):
npm start /dev/gradi-esp-compressChrome must be installed in the default location (C:\Program Files\Google\Chrome\Application\chrome.exe or the x86 variant). Override the binary with CHROME_PATH if needed. The kiosk run uses a dedicated profile at C:\gradi-proxy-kiosk; change it with CHROME_PROFILE_DIR.
For engineering diagnostics, the legacy dashboard remains available at http://localhost:3007/debug/.
The firmware continuously emits STATUS lines with presence (state), proximity (prox), and blink confidence (confidence). The Node controller monitors these and:
- Auto-starts a sequence when confidence stays above
CONF_START_THRESHOLDwhile presence isPRESENCEand no run is active. - Re-arms after a run only when confidence drops below
CONF_REARM_THRESHOLDor the firmware reportsstate=IDLE(wearer left). - Auto-cancels during a run if confidence falls below
CONF_EXIT_THRESHOLDand proximity stays underPROX_EXIT_LEVELforLEAVE_HOLD_MS.
Thresholds live in environment variables (defaults in .env.example):
| Variable | Default | Description |
|---|---|---|
SERIAL_PORT |
/dev/gradi-esp-compress | Serial device path; leave unset to auto-pick |
BAUD |
115200 | Serial baud rate |
PORT |
3007 | HTTP/WebSocket port |
CONF_START_THRESHOLD |
0.95 | Confidence needed to auto-start |
CONF_REARM_THRESHOLD |
0.80 | Confidence level that re-arms auto-start |
CONF_EXIT_THRESHOLD |
0.40 | Combined with low proximity triggers auto-cancel |
PROX_EXIT_LEVEL |
5 | Proximity count considered “no wearer” |
LEAVE_HOLD_MS |
1000 | Dwell time before auto-cancelling |
START_DELAY_MS |
600 | Delay between auto-accept and issuing START |
Controller logs ([CTRL] …) appear in the Node terminal and are mirrored to the frontend as control-log messages so you can audit every START/STOP decision.
gradi-proxy-esp/gradi-proxy-esp.ino configures the VCNL4040 sensor (200 Hz sampling), runs the pump/valve state machine for 16 frames × 4 slots, tracks blink statistics (presence gating, EWMA baseline), and emits:
- Raw proximity lines (
t=<ms> | prox=<count>) STATUS …summaries with confidence, mean, sigma, etc.BLINK …events detailing each detected blinkSEQ START/END/CANCEL …messages for the pump sequence lifecycle
gradi-proxy/
├── gradi-proxy-esp/
├── public/
├── server.js
├── package.json
├── package-lock.json
├── .env.example
└── README.md
- No serial port found: set
SERIAL_PORTin.envto the correct device. On macOS this is usually/dev/tty.usbmodem*. - Auto-start never fires: inspect
STATUSlines (or UI stats) to confirm confidence reaches the threshold; lowerCONF_START_THRESHOLDif needed. - Sequences cancel unexpectedly: raise
LEAVE_HOLD_MSor adjustCONF_EXIT_THRESHOLD/PROX_EXIT_LEVELfor your sensor fit. - UI not updating: ensure the browser hits the same host/port as the Node server and that
/wsstays connected (check DevTools console).
With firmware streaming, the Node controller running, and the dashboard open, the system will trigger pump sequences automatically on confident blinks and stop safely when the wearer leaves. Adjust thresholds to match your hardware and environment.