For Agents

Reverse-chronological session log. Newest entries at top, grouped by date (## YYYY-MM-DD). Each bullet: one piece of work, short summary, wikilinks to docs touched. Updated by obsidian-documenter on every project doc write. Read by historian at bootstrap.

2026-09-02

  • Created the project. A repurposed industrial start/stop pushbutton station — ~6 × 12 cm black enclosure, gutted, ESP32 inside, green and red buttons retained, any function assignable to either. Design phase only: no code, no repo, nothing built. Buttons follow the industrial convention — green NO, red NC — chosen for failure behaviour, not aesthetics: a broken wire on an NO button reads as “never pressed” and is indistinguishable from a working idle button, while NC reads as permanently pressed. A stop that silently disables itself is the failure to design out — buttonbox
  • Power decided: mains/USB, always on. The battery design was worked out at length and then abandoned. The payoff is latency, not convenience — a board that stays associated drops press-to-action from ~500 ms (the tuned battery figure) to ~30 ms. Consequence for wiring: both buttons GPIO → contact → GND on internal pull-ups, zero external components; green reads HIGH at rest / LOW pressed, red reads LOW at rest / HIGH pressed and is inverted in one line of firmware. Red’s 73 µA continuous through the ~45 kΩ pull-up is design-breaking on a cell (7× the whole MCU sleep budget) and irrelevant on mainsWiring — final
  • Recorded the abandoned battery-era wiring because it is the correct answer and will be needed again. NC contact to 3.3 V with a 1 MΩ pull-DOWN, internal pulls disabled. It buys two things and the second is the non-obvious one: standby falls 73 µA → 3.3 µA (below the MCU’s own sleep floor), and it forces both pins to the same edge — because esp_sleep_enable_ext1_wakeup() takes ONE trigger level for the entire pin mask, not per-pin, so a wake-on-LOW and a wake-on-HIGH button cannot coexist in one ext1 config. Alternative is the ESP-IDF 5.x per-IO API (esp_sleep_enable_ext1_wakeup_io()) — one resistor vs an IDF version floor. General form already in A normally-closed switch bleeds current continuouslyThe Abandoned Battery Design — keep it, it is correct
  • ⚠️ Transport decided AND it deliberately breaks a house convention: WiFi → a new LAN-bound stdlib-Python listener, bearer-token authenticated (401 otherwise). tailnet-service-exposure-convention requires loopback-only binding behind a dedicated Tailscale node; an ESP32 cannot join the tailnet, so the listener must bind a real LAN address. The break is knowing and accepted, which makes the bearer token the entire security model. Rejected alternatives recorded, because “why not ESP-NOW” is the obvious future question:ESP-NOW to a USB dongle on telep-mainframe would have sidestepped both the tailnet problem and the 98.9 % 2.4 GHz congestion — it was shown to the user and declined, not missed — Transport — WiFi to a LAN-bound Python listener
  • Wire format decided, and it is the load-bearing architectural choice: the device has no opinion. It sends {"button": "...", "held_ms": N, "seq": N} on release and nothing else; all gesture semantics — short vs long vs double press, and toggle behaviour — live server-side in the daemon. Rationale: gesture meaning becomes editable config, so adding a long-press two months later is a Python dict edit, not a reflash of a device that has to be physically opened. Two free diagnostics fall out: seq gives lost-press detection, and held_ms catches a stuck or broken switch — a 45 000 ms “press” is a fault, not a press, which is exactly what a broken NC wire produces. Double-press does not tax every press: fire on release immediately, then linger ~350 ms (~0.004 mAh, irrelevant), and the daemon buffers 300 ms ONLY for buttons that actually have a double bindingWire Format — the device has no opinion
  • Latency budget recorded (naive ~2.6 s → tuned ~500 ms) even though it is moot on mains, because it applies to any battery ESP32 doing WiFi: boot 300 + AP scan ~1000 + associate 200 + DHCP ~500 + DNS 50 + TLS ~500 + POST 50. Tuned by caching BSSID and channel in RTC slow memory (survives deep sleep, and unlike NVS is not a flash write), a static DHCP lease, POSTing to a literal IP, and dropping TLS on a LAN-only path. ⚠️ Must keep a full-scan fallback — the vault has receipts for router reflash / channel / DFS churn (2026-08-26-router-reflash-mac-5ghz-dfs-and-bambu-offline, 2026-08-15-router-wifi-tuning-htmode-band-penetration) and a device that only tries its cached BSSID goes permanently offline after a router reboot — Latency Budget — naive vs tuned
  • Mechanical constraint drove a board purchase. The enclosure is so full of pushbutton contact blocks that a standard ESP32 devkit only fits flipped on its back with pins facing up — which is why 3× XIAO ESP32C3 (100.510.43, 0.6-inch pitch, 21 × 17.5 mm) are on the 2026-09-02 HESTORE cart. ⚠️ On order, NOT ownedON ORDER — the 2026-09-02 Cart as Submitted
  • Two items left explicitly UNRESOLVED. (1) Is the enclosure plastic or metal? It decides XIAO C3 (onboard antenna) vs a 32UE with a U.FL external antenna — and the 32UE is the scarce antenna-capable stock, only 5 exist. Go look. (2) Toggle state ownershipproposed but NOT confirmed: the daemon queries the real target and inverts what it actually finds, with a stored-boolean fallback for targets with no status endpoint, logging loudly when it falls back. Rationale is that a stored boolean desyncs the moment anything else changes the targetUnresolved
  • 🔴 Recorded a misread as a lesson. “Contactor” was initially read as a load-switching contactor, prompting a full mains-safety design (red hardwired NC in series with the coil, aux-contact sensing, relay in parallel with green). It was actually the contact blocks behind the pushbuttonsno mains, no load. The safe topology is kept for a future real-contactor project because the principle generalises: ⛔ red must stay hardwired in series with the coil so it physically breaks the circuit regardless of firmware — a software stop is not a stopThe “contactor” misread
  • Initialized activity log and project folder.