diff --git a/canbus/HANDOFF.md b/canbus/HANDOFF.md index ef5b781..0a64237 100644 --- a/canbus/HANDOFF.md +++ b/canbus/HANDOFF.md @@ -60,10 +60,11 @@ state + what's left. opcode. The node allowlist is **lights only** — keep movement nodes (awning/slides/jacks, type `0x21`) off the switch list until a careful attended first test. -6. **DSI fault `binary_sensor`** — see the section below (pending a capture). -7. **(Optional) Surface at the campsite HA + bridge home** like the gazebo fans / +6. **(Optional) Surface at the campsite HA + bridge home** like the gazebo fans / OneControl BLE devices, if you want these in the home dashboard too. +The DSI fault is already decoded and wired in (see below) — no capture needed. + ## File map | File | What it is | @@ -86,18 +87,12 @@ state + what's left. - One transceiver = one bus-end terminator. Never add a terminated node in the middle of the bus (would make three terminators). -## DSI fault — PENDING a fault capture +## DSI fault — DECODED (2026-06-12) -> The water-heater DSI (gas ignition) fault is almost certainly on the bus, but -> every capture so far is of a healthy heater, so the exact byte is unknown. -> -> **Capture plan (this session):** close the propane valve, run the water heater -> on gas until it locks out (DSI fault light on the panel), capture ~20 s, and -> diff against a healthy baseline (`captures/baseline-2026-06-11_223823.log`). -> Prime suspects (both sit at a constant "all-clear" value today): -> - node `95` (heater) page-3 `b1` — always `0xFF`; expect a bit to drop on fault. -> - node `AE` (type 0x27, ?LP-gas/diagnostics) page-3 — always `0x00`; expect non-zero on fault. -> -> **TODO once decoded:** record the node/page/byte/bit here, then add a -> `binary_sensor` to the YAML (`device_class: problem`) that reads it — the DSI -> fault the Bluetooth app never exposed. +Forced a real lockout and captured it (`captures/dsi-fault-*.log`). Already wired +into the YAML as two `binary_sensor`s; nothing left to do here. + +- **Water Heater DSI Fault** = node `95` page-3 `b0` bit5 (`0x20`). Healthy heater + = `0x80` off / `0x81` running; lockout = `0xA0`. +- **OneControl System Fault** = page-0 `b0` bit0 (`0x01`), a bus-wide + "fault exists somewhere" flag (read from node `95`'s page 0 in the lambda). diff --git a/canbus/README.md b/canbus/README.md index f2da4ee..5bb0abb 100644 --- a/canbus/README.md +++ b/canbus/README.md @@ -187,18 +187,23 @@ the app uses to build its UI. `6A`/`7F`/`9C` (movement — slide?), and find the battery SoC / "4 green lights" source. -### Planned: capture the DSI fault (2026-06-12) +### DSI fault — decoded (2026-06-12, `captures/dsi-fault-*.log`) -The water-heater DSI fault is almost certainly on the bus, but every capture so -far is of a *healthy* heater, so the fault encoding is unknown. **Plan:** close -the propane tank valve, run the water heater on gas until it locks out (DSI fault -light on the panel), capture ~20 s, then diff against a healthy baseline. Prime -suspects (both sit at a constant "all-clear" value in current captures): -- **node `95` (heater) page-3 `b1`** — always `0xFF`; expect it to drop a bit on fault. -- **node `AE` (type 0x27, ?LP-gas/diagnostics) page-3** — always `0x00`; expect non-zero on fault. +Forced a real lockout (propane valve closed, heater run on gas until it gave up) +and diffed against the healthy baseline. Two signals: -Whichever flips → becomes a `binary_sensor` in the ESPHome node (the DSI fault -the Bluetooth app never exposed). Reset = reopen valve, re-light. +- **Water-heater DSI fault = node `95` page-3 `b0` bit5 (`0x20`).** Healthy heater + reads `0x80` (off) or `0x81` (running); during the lockout it read **`0xA0`** + (bit0 cleared, bit5 set) for every sample. Bit5 never appears healthy → it's + the gas-ignition lockout flag. (`b1` stays `0xFF` and `node AE` stays `0x00` — + the two earlier suspects were both wrong.) +- **Bus-wide "system fault present" = page-0 `b0` bit0 (`0x01`).** *Every* node's + page-0 `b0` flipped `0x02`→`0x03` during the fault, so any node carries a + generic "a fault exists somewhere" flag. + +Both are wired into the ESPHome node as `binary_sensor`s (`device_class: +problem`) — the DSI fault the Bluetooth app never exposed. Reset = reopen valve, +re-light. --- diff --git a/canbus/esphome/onecontrol-canbus.yaml b/canbus/esphome/onecontrol-canbus.yaml index 12d4398..9e49446 100644 --- a/canbus/esphome/onecontrol-canbus.yaml +++ b/canbus/esphome/onecontrol-canbus.yaml @@ -132,10 +132,21 @@ canbus: // switched loads case 0xF8: id(interior_lights).publish_state(x[0] & 0x01); break; case 0x2A: id(exterior_lights).publish_state(x[0] & 0x01); break; - // TODO: water pump (61), water heater (95) once exposed below. + // water heater (node 95): b0 bit0 = on, bit5 (0x20) = DSI/gas + // ignition fault/lockout (healthy: 0x80 off / 0x81 running; + // fault: 0xA0). Confirmed by a forced lockout 2026-06-12. + case 0x95: id(dsi_fault).publish_state(x[0] & 0x20); break; + // TODO: water pump (61) on/off once exposed below. } } + // page 0 b0 bit0 = a bus-wide "system fault present" flag (every + // node mirrors it; healthy 0x02, fault 0x03). Read it from one node + // (the heater) so we publish a single source, not 14 duplicates. + if (page == 0 && node == 0x95 && x.size() >= 1) { + id(system_fault).publish_state(x[0] & 0x01); + } + // Battery voltage rides 29-bit telemetry frames (src 7D/AE, page // 0x11), payload 00 2B 0D 4x ..; b2..b3 (BE) / 256 = volts. // TODO: match the exact source frame and publish battery_voltage. @@ -236,6 +247,19 @@ sensor: unit_of_measurement: "%" accuracy_decimals: 0 +# --------------------------------------------------------------------------- +# Fault indicators (published by the dispatcher above) +# --------------------------------------------------------------------------- +binary_sensor: + - platform: template + name: "Water Heater DSI Fault" + id: dsi_fault + device_class: problem # node 95 page-3 b0 bit5 — gas ignition lockout + - platform: template + name: "OneControl System Fault" + id: system_fault + device_class: problem # page-0 b0 bit0 — bus-wide "a fault exists" + # --------------------------------------------------------------------------- # Switches — authenticated command path (see send_load_command above). # Each turn_on/off queues send_load_command for the load's node; the on_frame