Files
lippert-onecontrol/canbus/HANDOFF.md
T

5.9 KiB
Raw Blame History

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/RGPIO4 (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. 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.