ESP32 Node
AUS001 N1–N5
MQTT pub
8883 TLS
DNS
mqtt.equilibriumfan.com
β†’ 147.182.221.185
Droplet
Mosquitto
+ bridge.py
REST
Supabase
hvac_telemetry
bridge_health
Streamlit
Community Cloud
dashboard
NodeSiteLive FWLast SeenOTAStatus
Loading…
Loading issues…
GitHub
src/main.cpp
CI: ota_ci_check.py
push/tag
ota.equilibriumfan.com
nginx + Let's Encrypt
firmware binary
MQTT trigger
ESP32
esp_https_ota()
node-specific topic
no retain
ScriptWhat it doesStatus
session_init.pySession brief β€” fleet roster, live FW from Supabase, open GitHub issues. Run at session start.βœ“ synced
health_check.pyScheduled health report via GitHub Actions (11:15 UTC / 6:15 AM CDT). Rogue droplet cron at 12 PM CDT.⚠ sched conflict
ota_ci_check.pyCI guard: binary ≀ slot, struct ≀ 512B, stack β‰₯ 12288, watermark β‰₯ 2048.βœ“ committed
ota_host.pyLab OTA host for bench testing. Not the production nginx vhost.βœ“ committed
equisim_gauntlet.pyHIL simulation β€” scenarios 1–68.βœ“ committed
fleet_preflight_audit.pyPre-flight check before any OTA push.βœ“ committed
check_firmware_size.pyValidates binary size vs partition slot.βœ“ committed
simulate_ble_capture.pyBLE advertisement simulation for EquiSync RF learning.βœ“ committed
New Node Commissioning
  • Same session: USB, Streamlit, this dashboard
  • Banner first, then capture the BLE MAC
  • Fleet table follows fleet_topology.json via /topology
  • Bench 010 stays on hvac_telemetry_bench
  • Full procedure: docs/NODE_COMMISSIONING.md
Initial Flash (USB)
  • Physical USB-C to node
  • PlatformIO upload via Cursor / CLI
  • Sets FW_VERSION in NVS
  • Bench 010 still needs USB β†’ v4.5.16. N2 is on v4.5.16
EquiSync RF Learning
  • Hold GPIO35 at power-on β†’ learn mode
  • BLE advertisement from remote
  • Stored in NVS
  • simulate_ble_capture.py for bench test
OTA Update
  • Merge β†’ CI runs ota_ci_check.py
  • fleet_preflight_audit.py before trigger
  • Node-specific MQTT topic, no retain
  • Confirm via session_init
HIL Bench (010)
  • Site EQBNTV, table hvac_telemetry_bench
  • equisim_gauntlet.py β€” scenarios 1–68
  • Validate watermark β‰₯ 2048
  • USB flash needed β†’ v4.5.16
5-Party Ballot
  • Required: any threshold, pin, state-edge change
  • Open questions only β€” no A/B/C options
  • All 5 parties respond before merge
  • Label: needs-ballot
  • After merge: update AI.md + docs/TEN_COMMANDMENTS.md
Commit Rules
  • Every main.cpp commit β†’ bump FW_VERSION
  • kOffTroughRiseF = 1.2Β°F β€” Tier 3 to change
  • Node-specific OTA only, never fleet topic
  • static_assert(ota_status_t ≀ 512B)
  • otaWorker stack β‰₯ 12288
  • Post-ballot: update AI.md + TEN_COMMANDMENTS.md same session
Claude Web
Architecture Β· audit Β· review Β· diagrams
Gemini
Independent review Β· adversarial check
Claude Code
Code execution Β· CI Β· commits
Cursor
Active coding Β· file edits Β· droplet SSH
Steve
Final authority Β· site access Β· USB flash
πŸ””
Change health check notification time β€” rogue droplet cron fires at 12 PM CDT. Run crontab -l + systemctl list-timers, reschedule to 6 AM CDT (11:00 UTC). Set up email on the morning GitHub Actions brief.
HIGH
🌐
CNAME for dashboard.equilibriumfan.com β€” Pages project is live at equilibrium-dashboard-98s.pages.dev. Add a proxied CNAME dashboard β†’ equilibrium-dashboard-98s.pages.dev. The Wrangler token cannot write DNS.
HIGH
πŸ”¬
USB flash bench 010 β†’ v4.5.16 β€” HIL bench is behind the field image. USB flash only. Reads stay on hvac_telemetry_bench.
MED
⏳
Task D: fleet_baseline.json workflow β€” ballot pending β€” YAML written, not yet committed. Manual dispatch would update one existing node entry and commit. Waiting on a 5-party ballot before adding .github/workflows/.
MED
πŸ“‹
Update fleet_baseline.json β€” approved Austin baseline still lags the image running in the field. No hardcoded versions in static docs. The Task D workflow is the intended update path.
MED