core1-mqtt #1
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "core1-mqtt"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Add the struct contract between core 0 (domain) and core 1 (the upcoming network bridge). No JSON crosses cores — events and commands are typed C structs over two pico_util SPSC queues, leaving the SIO FIFO free for the flash_safe_execute() lockout handshake. The largest message, scan_complete, is 9 bytes here vs ~4.5 KB as JSON. This commit is purely additive: the existing USB-CDC JSON emit/parse path is untouched and still works (removed in a later commit). Core 0 now ALSO: - forwards every emitted state event to core 1 as an ic_event_t, and - drains an inbound ic_cmd_t queue, dispatching through the SAME domain entry points the JSON/CLI use (ic_dispatch_cmd). Details: - intercore.h/.c: ic_event_t (11 variants) / ic_cmd_t (9 variants) + two queues. Event queue depth 512 so a full synchronous resync burst (~386 events incl. all mappings) never drops. Provision msg is not carried (core 1 synthesises it from the state enum) to keep the event element ~32 B. NB queue_init() calloc's the rings once at boot (~16 KB); a permanent allocation, not runtime alloc/free. - serial_protocol.c: tee in serial_poll (emit_event JSON to USB + push struct), inbound command drain, levt_to_ic converter, ic_dispatch_cmd. emit_state_dali() also pushes — it is the only path a config-only set_dali (which produces no dirty-poll event) reaches core 1. - button_fsm_emit.c / serial_protocol_provision.c: push button / provision events alongside their existing printf. - intercore_init() runs in serial_protocol_init(), before the main loop. Builds clean for RP2040 (text 217 KB, bss 39 KB), no warnings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Bring up core 1 as a stub network bridge and make the single hardware watchdog tolerant of a network outage while still catching a real core-1 wedge. - core1.c/.h: multicore_launch_core1_with_stack() with an explicit static 8 KB stack. core1_entry() calls flash_safe_execute_core_init() first (registers core 1 as the flash lockout victim — the one app-side action needed for multicore flash safety; pico-vfs already wraps the writes), then loops beating a heartbeat and draining/discarding the event queue (the net stack replaces the discard later). core1_launch() blocks (bounded 1 s) until that init is done, so a subsequent core-0 littlefs write is safe. - main.c: launch core 1 after intercore_init(); replace the two unconditional watchdog_update() calls with wd_feed(), which feeds only while core 1's heartbeat is fresh (< 2 s) or within a 15 s post-launch grace window. A network outage keeps core 1 looping/beating so the chip never reboots; only a genuine wedge stops the beat and lets the 5 s watchdog fire. BSS +8 KB (the core-1 stack); builds clean for RP2040 (text 213 KB, bss 47 KB). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Get the board onto the network. This is the netif half of the original "commit 4"; the TCP socket state machine + DNS move to the coreMQTT commit, where the connection manager that drives them lives. - net/wizchip_glue.c: SPI0 + CS/RST/INT glue and the ioLibrary callbacks. The cris callback disables interrupts around each W5100S access so a register/burst transfer can't be interrupted mid-transaction by core 1's flash-lockout (SIO) IRQ — which would park core 1 with CS asserted while core 0 writes flash. Each access is microseconds, so it only briefly defers a flash write, never blocks it. - net/netif.c: non-blocking bring-up/DHCP/link/recovery state machine. * reliability #9: getVER()==0x51 sanity; full RSTn reset + wizchip_init re-init with exponential backoff on repeated failure (SPI desync leaves the chip unrecoverable at the socket level). * reliability #10: CW_GET_PHYLINK — parks in LINK_WAIT with the cable out instead of churning. * RTR=200 ms / RCR=3 so a dead peer surfaces in ~0.8 s (tunable). * stable per-board locally-administered MAC from the flash uid. * DHCP lease + renewal (default); static addressing supported (the /config.json net settings are wired in a later commit). - core1.c: netif_init(NULL) + netif_tick() each loop; alive log now reports link/ready. (Still discards events — MQTT publish replaces that.) - CMake: build ioLibrary (wizchip_conf/socket/w5100s/dhcp) with _WIZCHIP_= W5100S, link hardware_spi + pico_unique_id; -w on the vendored sources so our -Wall -Wextra output stays clean. Builds clean for RP2040 (text 223 KB, bss 48 KB), zero warnings in our code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Stand up the broker connection on core 1 and make it self-healing. Treats the connection as disposable: any fault tears down to a known state and rebuilds. - net/mqtt_app.c: a 5-state connection manager (OFFLINE -> TCP_CONNECTING -> MQTT_CONNECTING -> ONLINE -> BACKOFF) over a single non-blocking W5100S socket. Implements the socket-level reliability requirements: #1 SF_IO_NONBLOCK + getSn_SR() state machine; every op has a wall-clock timeout (TCP connect 5 s) so connect() can't wedge the loop. #2 explicit SOCK_CLOSE_WAIT -> disconnect()+close() recycle. #3 act on Sn_IR (CON / DISCON / TIMEOUT) then clear via setSn_IR(). #5 setSn_KPALVTR (6 * 5 s = 30 s) once ESTABLISHED. coreMQTT on top via a TransportInterface (SOCK_BUSY -> 0, error -> -1): #6 MQTT_ProcessLoop tracks PINGRESP natively; a keepalive timeout returns an error and we reconnect. #7 Last Will <prefix>/availability = "offline" (retained) at CONNECT. #8 exponential backoff (1 -> 30 s) with integer-LCG jitter at the TCP+MQTT layer; faults close the TCP socket abruptly so the LWT fires and HA sees us offline at once. Connected/message hooks are exposed for the translation layer; on connect we publish availability=online. MQTT_Connect timeout (1200 ms) stays under the core-1 heartbeat-staleness threshold. - core_mqtt_config.h: state-array/buffer sizing; coreMQTT logging routed to the '#'-prefixed CDC log (errors/warnings only). - Broker addressed by IP (recommended fixed-LAN setup). Compile defaults are placeholders (0.0.0.0:1883, prefix "lighting", no creds); real values wired from /config.json later. Hostname/DNS is a noted follow-up. - core1.c: mqtt_app_init/tick; alive log now reports mqtt state. Builds clean for RP2040 (text 242 KB, bss 50 KB), zero warnings in our code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Port mqtt_bridge.py's auto-discovery to core 1. On each MQTT (re)connect the full retained entity set is (re)published so HA repopulates from scratch. Published INCREMENTALLY — one entity per disc_step(), a bounded batch per tick, gated on free socket-TX space (TX_HEADROOM). Blasting ~600 retained configs at once would overrun the 2 KB socket TX buffer and freeze the core-1 heartbeat; the tx-free check means a ~650 B publish never meets a full buffer, so it flows at network speed and never blocks. Payloads are built one at a time into a single reused 1.5 KB buffer (no full-set-in-RAM). Entities (shapes mirror mqtt_bridge.py exactly — topics, device blocks, availability arrays, value templates, patterns): - 24x7 switch-plate event + binding-text entities (+ binding state seeds) - 32 relay switches (gated on system + gateway availability) - 64 ballast lights per DALI slave (+ per-ballast availability seeded offline; scan_complete brings present ones online), availability_mode "all" over system + gateway + per-ballast - 16 group lights + 16 members-text entities per slave (+ state seeds) - per gateway: 3 diagnostic sensors, 4 commission buttons, 1 target number (+ target_state / status seeds) - gateway/relay-board reachability availability seeded online Enablers: - intercore: a core0->core1 config snapshot (slave lists) filled in main() before core1 launch — discovery needs to know the slaves. - mqtt_app: getSn_TX_FSR accessor for discovery flow control. - ha_bridge.c: the discovery cursor + builders + connected hook. Controller-config and per-ballast config-number entities are published lazily on first state_cfg / state_dali (next commit, with the event translation). Builds clean for RP2040 (text 250 KB, bss 52 KB), zero warnings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Consolidate the four persisted files to two. config.json now also holds the modbus baud + DE mode and the MQTT + network settings; mappings.json is untouched (kept separate for its tolerant, frequently-rewritten format). - config.c: parse with jsmn (malloc/float-free; JSMN_STATIC so config.c and ha_bridge.c each get a private copy). Flat keys (mqtt_host, net_ip, ...) for simple, robust, human-editable JSON. One-time migration imports /modbus_baud.txt + /modbus_config.txt then deletes them. - modbus.c: read baud + DE mode from g_config (set by config_load before modbus_init); modbus_set_baud / set_modbus_de_mode now write g_config + config_save instead of their own files. Removed the now-dead text-file parse helpers. (The one called-out core-0 domain edit.) - intercore snapshot extended with the resolved broker IP/port/creds/prefix and the net settings; main() fills it (parsing the broker dotted-quad to an IP) before launching core 1. - core1: build netif_config_t + mqtt_config_t from the snapshot — the bridge now connects to the configured broker instead of the placeholder default. - CLI: `mqtt set host=.. port=.. user=.. pass=.. prefix=..` / `mqtt show`, `net set dhcp=on|off ip=.. mask=.. gw=.. dns=..` / `net show`. Changes save to config.json and apply on next boot (snapshot is read at core-1 launch). Compile defaults are placeholders only (broker unset, prefix "lighting", DHCP); no secrets committed. Builds clean for RP2040 (text 273 KB, bss 52 KB). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>The batch wipe-and-replace transaction existed only for bridge/sync_bindings.py, which is now obsolete — Home Assistant (with the mapper tool) owns binding via the per-binding set_mapping / del_mapping path that core 1 exposes. - delete bridge/sync_bindings.py - remove the replace_mappings_begin/add/commit/abort JSON handlers and diff_emit_cb from serial_protocol.c - remove the mapping_txn_* backend + the s_txn_scratch table from mapping.c/.h (frees ~3.8 KB BSS) Builds clean for RP2040 (text 272 KB, bss 48 KB). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>USB CDC is now a human text CLI only — all JSON emit/parse is gone. JSON lives solely at core 1's MQTT edge. The state emitters still feed core 1 (typed structs); they just no longer print JSON to USB. - emit_state_dali/relay/group/cfg, emit_config_mapping, serial_provision_poll: converted from "printf JSON + push struct" to push-only. - removed emit_event (the levt->JSON formatter); serial_poll forwards events via levt_to_ic only. - removed handle_json_line (inbound JSON command parser) — handle_line ignores any stray '{' line; the text CLI is the only USB command surface. - removed serial_tx_ack + the dead inbound provision JSON handlers (provisioning is driven via MQTT -> ic_dispatch_cmd) and the prov_json helpers; serial_tx_hello is now a human text log. - dali_xlsx_sync.py (pure text CLI) and manual screen/picocom are unaffected; `mqtt set`/`net set` configure the broker/network over the same CLI. - README: on-board (no Python bridge) topology, RP2040/W5100S pinout, build submodules, and a protocol-section banner noting the inter-core struct contract + text-only USB. mqtt_bridge.py kept as the behavioural reference. Final RP2040 footprint: text 262 KB / 2 MB flash (13%); 48 KB static SRAM + ~50 KB heap (inter-core queues) + 8 KB core-1 stack of 264 KB — ~150 KB free. Builds clean, zero warnings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>The MQTT bridge now runs on-board on core 1, so the external Python host and its leftovers are gone. Also enable CLI echo by default and bring the docs in line with the actual firmware. Removed: - bridge/ — mqtt_bridge.py + env/requirements (replaced by core 1; the rotating controller.log* and .env secrets went with the directory). - docs/{core1-mqtt-plan,issue-raw-cmd-async,state-sync-review}.md — design notes for completed work; git history preserves them. - src/bindings.txt, src/lights.png — unreferenced scratch data committed into src/. - mapping_serialise_all() — dead since get_mappings switched to incremental per-binding emission. - The stray JSON `button` printf to USB in button_fsm_emit.c — a leftover from the old USB-JSON protocol; the typed core-1 event is the only path. Changed: - CLI echo + backspace editing now default ON (serial_protocol.c): the CLI is a human-only interface now that machine clients are gone. - `help` now lists the `mqtt` / `net` config commands (were dispatched but undocumented). - README.md: rewritten against the code — text-CLI-only USB, real MQTT topic structure / discovery / availability from ha_bridge.c, key=value CLI grammar, /mappings.json + consolidated /config.json, editable DALI group membership. Removed the Python-bridge run/Docker sections and all references to removed commands. - CLAUDE.md: documented the on-board core-1 architecture, the flash-lockout launch order, the four hardware-only bugs, the ioLibrary recv() #173 build-time patch, the writev publish coalescing, and the echo default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>