Files
lippert-onecontrol/canbus/HANDOFF.md
T
2026-06-12 11:42:21 -04:00

117 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
- **Pigtail wiring (as crimped):** green = CAN-L, blue = CAN-H.
- **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 23 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 and the
Water Heater. The `send_load_command` script + `on_frame` handler do the
exchange and send the opcode.
6. **Attended awning test (motor — watch it move).** The Awning `cover` is wired
(open/close/stop). On the first run, confirm: (a) which direction open/close
actually go, (b) whether one command runs to the travel limit or only moves
while commands stream. If it under-travels, change `send_load_command` to
stream the opcode (repeat until Stop) — only after watching it. The single-shot
default can't run the motor away.
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.
The DSI fault is already decoded and wired in (see below) — no capture needed.
## 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
- **SLIDES, JACKS, and the WATER PUMP ARE PANEL/APP ONLY — never over CAN. This
is a hard rule, do not weaken it.** `esphome/command_guard.h`
(`command_blocked()`) is the single source of truth: it refuses the slide/jack
nodes (6A/7F/9C, effective even before a node's identity is heard), the water
pump (61, winterizing-only), and — generally — any motor-class (0x21) node other
than the awning. It's enforced in two independent places (the command-entry
script and the actual transmit point), so loosening one does not open the other.
Wiring a switch for a blocked node cannot actuate it; the gate drops the command
before any frame goes out.
- **Exposed (controllable) loads:** exterior lights (2A), interior lights (F8),
water heater (95) as `switch`es, and the awning (75) as a `cover`. To expose
another *permitted* switched load, add its node to the layer-2 allowlist in
`send_load_command` and add a `switch` entity; never add a slide/jack/pump.
- **The awning is a motor — its first actuation must be attended** (see
remaining-work item 6). The wired commands are single-shot, which can't run the
motor away, but direction and latch-vs-stream behavior need a live check.
- 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 — DECODED (2026-06-12)
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).