104 lines
5.9 KiB
Markdown
104 lines
5.9 KiB
Markdown
# Handoff — finish & flash the OneControl IDS-CAN node
|
||
|
||
**Goal:** complete and flash an ESP32 firmware that puts our RV's Lippert
|
||
OneControl system (tanks, lights, switches) into Home Assistant as native
|
||
entities, talking directly to the OneControl CAN network. This replaces the
|
||
slower Bluetooth integration in `../src/` + `../custom_components/`.
|
||
|
||
Read `README.md` first — it has the full message-format documentation, the node
|
||
map for this coach, and the wiring procedure. This file is just the current
|
||
state + what's left.
|
||
|
||
## Current status (2026-06-12)
|
||
|
||
- **Message format documented and confirmed against live captures.** The bus is
|
||
Lippert **IDS-CAN** (250 kbit/s, 11-bit IDs), not RV-C. Every tank, light,
|
||
pump, heater, the awning, and battery voltage is decoded — see the node map in
|
||
`README.md`.
|
||
- **Read path:** modules broadcast their state continuously, no authentication.
|
||
- **Command path:** each command is preceded by a short challenge/response
|
||
authentication exchange (the same one the OEM app uses). Implemented in
|
||
`ids_can_auth.py` (reference) and `esphome/ids_can_auth.h` (the firmware copy,
|
||
verified **bit-exact** against the Python and against captured/live values).
|
||
- **Proven working end to end.** `idscan_cmd.py` ran the full exchange over a USB
|
||
CAN adapter and operated the interior lights (node `F8`) on/off/on, each
|
||
answering a distinct fresh challenge, with the module's own status broadcast
|
||
confirming the result. A bare command with no exchange is ignored.
|
||
- **Firmware compiled successfully** last night (`esphome/.esphome/build/…`).
|
||
|
||
## Hardware (assembly-ready)
|
||
|
||
- ESP32 WROOM devboard (`esp32dev`) + Waveshare **SN65HVD230** transceiver
|
||
(3.3 V logic, onboard 120 Ω terminator → this node is the bus-END node).
|
||
- **Power:** buck converter dialed to **5 V**, tapped from the panel's 12 V
|
||
supply (common ground with the bus — good). Feed the ESP32 5 V pin.
|
||
- **Connection:** Molex Mini-Fit Jr. pigtail into the panel's CAN **data** port
|
||
(the one with the terminator), per `README.md` → "Physical connection".
|
||
⚠️ Meter the port first: data idles ~2.5 V, power reads ~12 V; 12 V on CAN-H/L
|
||
destroys the transceiver.
|
||
- **GPIO:** transceiver `CTX/D` ← ESP32 `GPIO5` (tx_pin), `CRX/R` → `GPIO4`
|
||
(rx_pin). Adjust `substitutions:` in the YAML if you wire differently.
|
||
|
||
## Remaining work (in order)
|
||
|
||
1. **`secrets.yaml`** — copy `esphome/secrets.yaml.example`, fill in WiFi, the
|
||
ESPHome `api_key`, and the fallback-AP password. It's git-ignored.
|
||
2. **First flash over USB:** `esphome run esphome/onecontrol-canbus.yaml` (pick
|
||
the serial port). OTA after that. Mirrors the gazebo-fan-proxy workflow.
|
||
3. **Confirm the read entities populate** in HA once it's on the bus: the four
|
||
tanks and the two light switches publish from the page-3 broadcasts. Watch the
|
||
DEBUG frame dump (`logger: level: DEBUG`) to confirm frames are decoded; drop
|
||
to INFO when happy.
|
||
4. **Finish the two open read items in the YAML lambda:**
|
||
- **Battery voltage** — rides a 29-bit telemetry frame (src `7D`/`AE`,
|
||
page `0x11`), bytes 2–3 big-endian / 256 = volts. Match that frame and
|
||
publish `battery_voltage`. (See README "29-bit extended frames".)
|
||
- Optionally add **water pump (`61`)** and **water heater (`95`)** — both are
|
||
ordinary switched loads, same decode + command path as the lights.
|
||
5. **Verify the command path** from HA: toggle Interior/Exterior Lights. The
|
||
`send_load_command` script + `on_frame` handler do the exchange and send the
|
||
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 /
|
||
OneControl BLE devices, if you want these in the home dashboard too.
|
||
|
||
## File map
|
||
|
||
| File | What it is |
|
||
|------|-----------|
|
||
| `esphome/onecontrol-canbus.yaml` | the ESP32 firmware (read dispatch + command path) — the thing to finish & flash |
|
||
| `esphome/ids_can_auth.h` | command-authentication response, used by the YAML lambda |
|
||
| `esphome/secrets.yaml.example` | template for the git-ignored secrets |
|
||
| `ids_can_auth.py` | Python reference for the same authentication + 51/51 self-test |
|
||
| `idscan_cmd.py` | desktop tool that proved the command path over a USB CAN adapter |
|
||
| `captures/` | raw bus logs + the challenge/response pairs + `analyze_auth.py` |
|
||
| `captures/log-can.sh` | bring up the USB CAN adapter and log frames |
|
||
| `README.md` | full message-format documentation + node map + wiring |
|
||
|
||
## Safety notes
|
||
|
||
- **Command path is lights-only by allowlist.** Movement nodes use the same
|
||
authentication but are untested from this node — don't add them until you can
|
||
watch the motor on the first actuation.
|
||
- The physical connection is fully reversible: unplug, re-seat the terminator.
|
||
- 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
|
||
|
||
> 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.
|