canbus: decode the water-heater DSI fault + add fault binary_sensors
Forced a real lockout (captures/dsi-fault-*.log) and diffed vs the healthy baseline: - Water-heater DSI fault = node 95 page-3 b0 bit5 (0x20). Healthy 0x80/0x81, fault 0xA0. (Earlier suspects b1=FF and node AE were both wrong.) - Bus-wide "system fault present" = page-0 b0 bit0 (every node flips 02->03). Both wired into esphome/onecontrol-canbus.yaml as binary_sensor (device_class: problem). README + HANDOFF updated; DSI item closed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+11
-16
@@ -60,10 +60,11 @@ state + what's left.
|
|||||||
opcode. The node allowlist is **lights only** — keep movement nodes
|
opcode. The node allowlist is **lights only** — keep movement nodes
|
||||||
(awning/slides/jacks, type `0x21`) off the switch list until a careful
|
(awning/slides/jacks, type `0x21`) off the switch list until a careful
|
||||||
attended first test.
|
attended first test.
|
||||||
6. **DSI fault `binary_sensor`** — see the section below (pending a capture).
|
6. **(Optional) Surface at the campsite HA + bridge home** like the gazebo fans /
|
||||||
7. **(Optional) Surface at the campsite HA + bridge home** like the gazebo fans /
|
|
||||||
OneControl BLE devices, if you want these in the home dashboard too.
|
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 map
|
||||||
|
|
||||||
| File | What it is |
|
| 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
|
- One transceiver = one bus-end terminator. Never add a terminated node in the
|
||||||
middle of the bus (would make three terminators).
|
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
|
Forced a real lockout and captured it (`captures/dsi-fault-*.log`). Already wired
|
||||||
> every capture so far is of a healthy heater, so the exact byte is unknown.
|
into the YAML as two `binary_sensor`s; nothing left to do here.
|
||||||
>
|
|
||||||
> **Capture plan (this session):** close the propane valve, run the water heater
|
- **Water Heater DSI Fault** = node `95` page-3 `b0` bit5 (`0x20`). Healthy heater
|
||||||
> on gas until it locks out (DSI fault light on the panel), capture ~20 s, and
|
= `0x80` off / `0x81` running; lockout = `0xA0`.
|
||||||
> diff against a healthy baseline (`captures/baseline-2026-06-11_223823.log`).
|
- **OneControl System Fault** = page-0 `b0` bit0 (`0x01`), a bus-wide
|
||||||
> Prime suspects (both sit at a constant "all-clear" value today):
|
"fault exists somewhere" flag (read from node `95`'s page 0 in the lambda).
|
||||||
> - 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.
|
|
||||||
|
|||||||
+15
-10
@@ -187,18 +187,23 @@ the app uses to build its UI.
|
|||||||
`6A`/`7F`/`9C` (movement — slide?), and find the battery SoC / "4 green lights"
|
`6A`/`7F`/`9C` (movement — slide?), and find the battery SoC / "4 green lights"
|
||||||
source.
|
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
|
Forced a real lockout (propane valve closed, heater run on gas until it gave up)
|
||||||
far is of a *healthy* heater, so the fault encoding is unknown. **Plan:** close
|
and diffed against the healthy baseline. Two signals:
|
||||||
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.
|
|
||||||
|
|
||||||
Whichever flips → becomes a `binary_sensor` in the ESPHome node (the DSI fault
|
- **Water-heater DSI fault = node `95` page-3 `b0` bit5 (`0x20`).** Healthy heater
|
||||||
the Bluetooth app never exposed). Reset = reopen valve, re-light.
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -132,10 +132,21 @@ canbus:
|
|||||||
// switched loads
|
// switched loads
|
||||||
case 0xF8: id(interior_lights).publish_state(x[0] & 0x01); break;
|
case 0xF8: id(interior_lights).publish_state(x[0] & 0x01); break;
|
||||||
case 0x2A: id(exterior_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
|
// Battery voltage rides 29-bit telemetry frames (src 7D/AE, page
|
||||||
// 0x11), payload 00 2B 0D 4x ..; b2..b3 (BE) / 256 = volts.
|
// 0x11), payload 00 2B 0D 4x ..; b2..b3 (BE) / 256 = volts.
|
||||||
// TODO: match the exact source frame and publish battery_voltage.
|
// TODO: match the exact source frame and publish battery_voltage.
|
||||||
@@ -236,6 +247,19 @@ sensor:
|
|||||||
unit_of_measurement: "%"
|
unit_of_measurement: "%"
|
||||||
accuracy_decimals: 0
|
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).
|
# 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
|
# Each turn_on/off queues send_load_command for the load's node; the on_frame
|
||||||
|
|||||||
Reference in New Issue
Block a user