Commit Graph
21 Commits
Author SHA1 Message Date
wesandClaude Fable 5 e8b2447d62 canbus: battery decode, dual on_frame triggers, ground-truth switches
Flash-session firmware work, live on the node since 2026-06-12:
- battery voltage decode (29-bit page-0x11 telemetry, b2..b3 BE / 256),
  gated to extended frames so an 11-bit node 0x11 can never spoof it,
  with a delta/throttle filter to stop 1/256-V jitter churning both
  HA recorders and the MQTT bridge
- second on_frame trigger (use_extended_id: false) sharing the decode
  lambda via YAML anchor — this esp32_can build filters triggers by
  frame type, so a single trigger silently dropped the 11-bit reads
- switches optimistic:false now the page-3 read-back is verified live
- arm retry widened to 8x150ms; module-side ~2s post-success cooldown
  documented
- canbus component logs to INFO (per-frame DEBUG dump saturated serial)
- toolchain fixes: named std::array initializer, namespaced cover enums

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 15:32:12 -04:00
wesandClaude Opus 4.8 81bd58206d canbus: note pigtail wiring colors (green=CAN-L, blue=CAN-H)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 11:42:21 -04:00
wesandClaude Opus 4.8 cda537da29 canbus: expose water heater + awning; block water pump
Per updated policy:
- Water heater (node 95): new switch entity + on/off state read-back.
- Awning (node 75): new cover entity (open=op01 / close=op02 / stop=op00), with
  current_operation published from the page-3 motion byte (C2/C3/C0). First
  actuation must be attended; single-shot commands can't run the motor away.
- Water pump (node 61): added to command_guard denylist (winterizing-only,
  panel/app only) alongside slides/jacks. Guard re-tested 8/8 (host g++).

Switch/cover comments + HANDOFF safety notes and remaining-work updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 11:40:54 -04:00
wesandClaude Opus 4.8 f05203b9e3 canbus: hard safety gate — slides/jacks are control-panel only, never over CAN
Per policy, the firmware must never command a slide or jack. command_guard.h adds
command_blocked(node, type): refuses any motor-class (0x21) node except the
awning (0x75), with an explicit slide/jack denylist (6A/7F/9C) that holds even
before a node's page-2 identity is observed.

Enforced in two independent places — the command-entry script (send_load_command)
and the actual transmit point (on_frame, right before TX) — so loosening one
can't open the other. Adding a slide/jack switch entity cannot actuate it. Node
device class is learned from page-2 broadcasts (g_node_type). Predicate
unit-tested 9/9 (host g++). Switch comments + HANDOFF safety notes updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 11:34:21 -04:00
wesandClaude Opus 4.8 d57c2b21d2 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>
2026-06-12 11:29:05 -04:00
wesandClaude Opus 4.8 f3073f180f canbus: add Fable session handoff doc (assembly + flash + open items)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 11:21:35 -04:00
wesandClaude Opus 4.8 742ef49c8a canbus: confirm command path live + frame docs as device integration
Command path proven end to end on the bus (node F8 interior lights, on/off/on),
each answering a distinct fresh challenge; bare opcodes without the exchange are
ignored. ids_can_auth.h verified bit-exact against ids_can_auth.py and the
captured/live pairs.

- idscan_cmd.py: stdlib socketcan tool running the full page-42/43 exchange
- esphome/onecontrol-canbus.yaml: correct IDS-CAN read dispatch (was stale RV-C
  DGN code) + command path wired to the auth header
- README/memory: document the read map + command authentication; rename
  sniff/ -> captures/; neutral device-integration framing throughout

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 11:20:12 -04:00
wesandClaude Fable 5 840cfaf5fc canbus: solve IDS-CAN command-auth cipher; add reference implementations
The CAN write gate (page-42/43 challenge/response) is a 32-round TEA/XTEA-family
Feistel keyed by a per-session 32-bit key; REMOTE_CONTROL = 0xB16B00B5. Verified
51/51 against captured challenge/response pairs across nodes 2A/61/75/F8 (one
global key, not per-node), so the CAN path can now actuate, not just sense.

- ids_can_auth.py         Python reference + self-test (51/51)
- esphome/ids_can_auth.h  C++ port for the ESP32 node (host-tested 8/8)
- sniff/analyze_auth.py   structural analysis (rules out affine; confirms keyed cipher)
- sniff/auth-pairs-multinode-2026-06-11.txt   +9 pairs across 4 nodes
- README                  document the cipher, session keys, unlock sequence

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:22:09 -04:00
wesandClaude Fable 5 85455e8631 canbus: add DSI-fault capture plan to open items
Close propane valve, force a water-heater lockout, capture + diff against
healthy baseline. Prime suspects: node 95 b1 (always FF) or node AE page-3
(always 00). Whichever flips becomes the DSI binary_sensor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:26:29 -04:00
wesandClaude Fable 5 b97401fec8 canbus: reverse-engineer OneControl IDS-CAN bus (read fully mapped, write auth-gated)
Tapped the X180T's CAN bus via CANable 2.0 at the monitor panel's terminator
port. The bus is NOT RV-C — it's Lippert's proprietary IDS-CAN (250k, 11-bit
IDs, (page<<8)|node, 1 Hz broadcasts).

Read side fully mapped from live captures:
- device classes (page-2 type byte: 0x0A tank, 0x1E switched load, 0x21 motor)
- node map for this rig (Catalina 263BHSCK): tanks 27/E2/7D/FE, lights 2A/F8,
  heater 95, pump 61, awning 75 (+ direction & live motor current)
- battery voltage on 29-bit extended frames

Write side: commands are DLC-0 ext frames 0006<node><op>, but auth-gated by a
rolling challenge-response (page 42/43). Replay confirmed dead (spoofed cansend
did not actuate). Not the BLE TEA cypher. response=f(challenge) is deterministic
(no session state) so crackable offline later — seeded 42 pairs in
sniff/2A-auth-pairs.txt.

Includes raw captures (sniff/*.log, force-added past *.log ignore), a read-only
esp32_can ESPHome skeleton, and the log-can.sh sniff helper. Full writeup in
canbus/README.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:20:49 -04:00
wesandClaude Opus 4.8 34155fd7f9 Add HA custom integration; fix bleak 3.x write-without-response regression
The OneControl panel's command characteristic is a streaming (Write Without
Response) endpoint. Bleak 3.x changed write_gatt_char to default to
write-with-response when the char advertises the "write" property, so every
command (incl. switch turn_on/off) got rejected by the panel with ATT 0x0E
(Unlikely Error), surfaced as BleakGATTProtocolError. Force response=False on
the command write (matching the auth key write) to restore control.

Also commits the productionized custom_components integration (config flow,
coordinator, switch/sensor/cover entities, key-seed TEA auth, COBS codec) and
the matching src/ RE client/COBS fixes (big-endian framing, table-driven CRC8,
status-event decoding) that were developed but never tracked.

Verified live on the campsite HAOS Pi: switch.exterior_lights / interior_lights
toggle the physical panel with no GATT error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 06:41:14 -04:00
wesandClaude Sonnet 4.6 b6d498c829 Implement key-seed auth handshake (service 0010)
Root cause of immediate disconnect after write: panel requires auth on
service 0010 before accepting commands on 0030. Protocol found by
decompiling Plugin.BLE.dll (BleDeviceUnlockManager.PerformKeySeedExchange).

Auth flow:
1. Read 4-byte seed from char 0012 (00000012-0200-a58e-e411-afe28044e62c)
2. Apply modified TEA with RV-specific cypher (612643285 / 0x248431D5)
3. Write 4-byte little-endian result to char 0013
4. Retries up to 3x if seed < 4 bytes (panel not yet ready)

connect() now calls perform_auth() before enabling 0034 notifications.
Also cleans up experimental auth attempts from campsite debugging session.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 19:56:36 -04:00
wesandClaude Sonnet 4.6 72f415e051 Add scratch.py (gitignored) for interactive device testing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 16:10:00 -04:00
wesandClaude Sonnet 4.6 5138d8a37e Fix bleak 3.x compatibility and code review cleanup
- Fix scan_for_onecontrol() to use return_adv=True and adv.service_uuids
  (device.metadata removed in bleak 3.x)
- Add missing _parse_get_devices_response() — was called but never defined,
  would crash on first real device response
- main() now auto-connects when exactly one device is found via scan
- Remove premature turn_on/off_light(5) calls from main() — device IDs
  unknown until get_devices() has been run against real hardware
- Guard send_command() and disconnect() against unconnected state
- control_awning() now takes MovementState enum instead of raw int
- Bump post-get_devices sleep from 2s to 5s for real device latency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 16:04:20 -04:00
wes 275f08658f Feat: Implement correct response parsing and GetDevices decoder 2025-12-29 09:59:28 -05:00
wes eda93dc7fe Fix: Add trailing frame byte to COBS encoder 2025-12-29 09:55:46 -05:00
wes 1ce475f7dc Clean up documentation for public release
Changes:
- Rewrote PROTOCOL_FINDINGS.md to be clean protocol spec
  - Removed all DLL/assembly/class name references
  - Removed outdated "Next Steps" sections
  - Focused purely on protocol documentation
  - Added clear packet examples and command reference

- Removed outdated historical docs:
  - SUMMARY.md (investigation notes, now obsolete)
  - ANALYSIS_GUIDE.md (pre-completion guide, no longer needed)

- Created .claude.md for internal context
  - Contains all decompilation details
  - Lists specific DLL names and source locations
  - Preserves context for AI assistants
  - Added to .gitignore (not committed to repo)

Result:
- Public repo now has clean, legal documentation
- Internal context preserved for development
- Reduced legal surface area
- Docs focus on protocol, not implementation source

Remaining docs (all clean):
- PROTOCOL_FINDINGS.md - Protocol specification
- IMPLEMENTATION_GUIDE.md - Python implementation guide
- HOME_ASSISTANT_INTEGRATION.md - HA integration plan
- MISSION_ACCOMPLISHED.md - Project summary
2025-12-29 09:45:06 -05:00
wes 718a13c02f Reorganize repository structure into logical folders
Structure:
- src/ - Python implementation (cobs_protocol.py, onecontrol_client.py)
- docs/ - All documentation markdown files
- scripts/ - Extraction scripts (for reference only)

Changes:
- Moved Python files to src/
- Moved all .md docs to docs/
- Moved extraction scripts to scripts/
- Updated README.md with new structure
- Updated import paths in README examples
- Added placeholder for future Quartz documentation URL

Benefits:
- Cleaner repository organization
- Easier to navigate
- Separates code from documentation
- Follows standard project conventions
2025-12-29 09:33:20 -05:00
wes c62fbf1bd4 Remove reference to decompiled source code from README
The decompiled code is not included in the repository (excluded via .gitignore).
Mentioning it in the README could create legal concerns and is misleading.
2025-12-29 09:29:10 -05:00
wes da2387e29c Add Apache 2.0 license and strengthen legal compliance
- Added Apache License 2.0 (full text)
- Strengthened README disclaimer with interoperability language
- Added references to DMCA 1201(f) and EU Software Directive Article 6
- Clarified what IS and IS NOT included in repo
- Added license badge to README
- Explicitly states purpose: legally purchased hardware integration

Compliance checklist:
 Clean room implementation (no decompiled code in repo)
 .gitignore excludes all binaries and extracted code
 Interoperability defense clearly stated
 Apache 2.0 license with patent grant clause
 Disclaimer for Lippert Components Inc.
2025-12-29 09:12:11 -05:00
wes 7dd4f55a0c Initial commit: Lippert OneControl protocol reverse engineering
 Protocol fully reversed from decompiled Xamarin app
 All 431 .NET assemblies extracted and decompiled
 COBS encoder/decoder implemented in Python
 CRC8 checksum implementation
 Complete BLE client for OneControl devices
 Comprehensive documentation

Files included:
- cobs_protocol.py: COBS encoding/decoding + CRC8
- onecontrol_client.py: Full BLE client implementation
- Complete protocol documentation
- Home Assistant integration guide
- ESPHome Bluetooth Proxy setup
- Extraction scripts for reference

Ready for testing with RV hardware (April 2025)
2025-12-29 08:50:16 -05:00