canbus: replace BLE integration with CAN node in HA + MQTT bridge home
The campsite HAOS Pi now runs the ESPHome CAN node natively ("OneControl
CAN", *.onecontrol_can_*); the BLE integration is fully torn out (config
entry, custom_components on the Pi, stale MQTT registry orphans) and the
camper dashboard rewritten — water-pump tile dropped on purpose (pump is
panel-only), awning + fault sensors added.
New canbus/ha/mqtt_bridge_onecontrol.yaml (deployed to the Pi as
packages/mqtt_bridge.yaml) bridges the CAN entities to the home broker
via MQTT Discovery, same pattern as the gazebo bridge. Kept the old
unique_ids so home entity ids and recorder history carried over; added
an availability topic and explicit command-topic allowlists (3 switches
+ awning — a future debug entity must not become remotely controllable
for free). Round-trip verified from home HA.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+67
-18
@@ -9,6 +9,32 @@ Read `README.md` first — it has the full message-format documentation, the nod
|
|||||||
map for this coach, and the wiring procedure. This file is just the current
|
map for this coach, and the wiring procedure. This file is just the current
|
||||||
state + what's left.
|
state + what's left.
|
||||||
|
|
||||||
|
## FLASHED & READ-VERIFIED (2026-06-12, afternoon)
|
||||||
|
|
||||||
|
The node is built, flashed over USB, on the bus, and on WiFi
|
||||||
|
(`192.168.69.18`, hostname `onecontrol-canbus`, adoptable via the API key in
|
||||||
|
`secrets.yaml`). **All 11 read entities verified live over the native API**:
|
||||||
|
battery 13.27 V, tanks fresh 0 / black 33 / grey1 33 / grey2 100 %, interior
|
||||||
|
lights on, exterior/heater off, both fault flags clear, awning idle.
|
||||||
|
|
||||||
|
What changed this session:
|
||||||
|
- `secrets.yaml` filled (WiFi + fallback AP from the shared gazebo proxy creds).
|
||||||
|
- **Battery voltage decode finished** (remaining-work item 4): matches the
|
||||||
|
page-`0x11` `00 2B …` telemetry frame, `b2..b3` BE / 256 — reads 13.27 V.
|
||||||
|
- **Toolchain fixes** (ESPHome moved since the last build; it no longer compiled):
|
||||||
|
the `g_node_type` array global's bare `{}` initializer was ambiguous (named the
|
||||||
|
type), and the awning cover enum is `COVER_OPERATION_OPENING/CLOSING` (no `IS_`).
|
||||||
|
- **Dual `on_frame` trigger** — this build filters `on_frame` by frame type, so a
|
||||||
|
single `use_extended_id: true` trigger only saw the 29-bit frames; the 11-bit
|
||||||
|
read broadcasts (tanks/lights/heater/awning) were silently dropped. Added a
|
||||||
|
second `use_extended_id: false` trigger sharing the same lambda via a YAML
|
||||||
|
anchor. (This was the exact risk the bus-config note called out.)
|
||||||
|
- **Logger** — raised the `canbus` component to INFO and silenced the per-frame
|
||||||
|
dump; at DEBUG it logged ~50 frames/s and saturated the 115200 serial link.
|
||||||
|
|
||||||
|
Still TODO (needs the operator in the loop — see "Remaining work" items 5–6):
|
||||||
|
command-path verification (toggle a light) and the attended awning test.
|
||||||
|
|
||||||
## Current status (2026-06-12)
|
## Current status (2026-06-12)
|
||||||
|
|
||||||
- **Message format documented and confirmed against live captures.** The bus is
|
- **Message format documented and confirmed against live captures.** The bus is
|
||||||
@@ -42,31 +68,54 @@ state + what's left.
|
|||||||
|
|
||||||
## Remaining work (in order)
|
## Remaining work (in order)
|
||||||
|
|
||||||
1. **`secrets.yaml`** — copy `esphome/secrets.yaml.example`, fill in WiFi, the
|
1. ~~**`secrets.yaml`**~~ — DONE. WiFi + fallback-AP creds filled from the shared
|
||||||
ESPHome `api_key`, and the fallback-AP password. It's git-ignored.
|
gazebo proxy; the `api_key` was already set.
|
||||||
2. **First flash over USB:** `esphome run esphome/onecontrol-canbus.yaml` (pick
|
2. ~~**First flash over USB**~~ — DONE (`/dev/ttyUSB0`). OTA from here on.
|
||||||
the serial port). OTA after that. Mirrors the gazebo-fan-proxy workflow.
|
Note: this laptop's user isn't in the `uucp` group, so the serial node needed
|
||||||
3. **Confirm the read entities populate** in HA once it's on the bus: the four
|
`chmod 666 /dev/ttyUSB0` (resets on replug) — irrelevant for OTA.
|
||||||
tanks and the two light switches publish from the page-3 broadcasts. Watch the
|
3. ~~**Confirm the read entities populate**~~ — DONE. All 11 verified live over the
|
||||||
DEBUG frame dump (`logger: level: DEBUG`) to confirm frames are decoded; drop
|
native API (see status block above). NB: ESPHome 2026.5 doesn't echo
|
||||||
to INFO when happy.
|
`publish_state` at DEBUG over serial, so a quiet serial log is normal — read the
|
||||||
4. **Finish the two open read items in the YAML lambda:**
|
states over the API, not the console.
|
||||||
- **Battery voltage** — rides a 29-bit telemetry frame (src `7D`/`AE`,
|
4. **Open read items in the YAML lambda:**
|
||||||
page `0x11`), bytes 2–3 big-endian / 256 = volts. Match that frame and
|
- ~~**Battery voltage**~~ — DONE; reads 13.27 V.
|
||||||
publish `battery_voltage`. (See README "29-bit extended frames".)
|
|
||||||
- Optionally add **water pump (`61`)** and **water heater (`95`)** — both are
|
- Optionally add **water pump (`61`)** and **water heater (`95`)** — both are
|
||||||
ordinary switched loads, same decode + command path as the lights.
|
ordinary switched loads, same decode + command path as the lights. (Heater is
|
||||||
5. **Verify the command path** from HA: toggle Interior/Exterior Lights and the
|
already exposed; pump stays command-blocked.)
|
||||||
Water Heater. The `send_load_command` script + `on_frame` handler do the
|
5. ~~**Verify the command path**~~ — DONE (Interior + Exterior lights, on & off,
|
||||||
exchange and send the opcode.
|
each a confirmed challenge→response→opcode exchange on the serial log, with the
|
||||||
|
read-back flipping to match). Switches are now `optimistic:false` so HA shows
|
||||||
|
ground truth. **Confirmed module behaviour:** after a successful session the
|
||||||
|
module enforces a **~2 s cooldown** — a second command to the same load sooner
|
||||||
|
gets no challenge and is dropped (opcode-independent, module-side, not a bug).
|
||||||
|
The arm retry was widened to 8×150 ms to absorb the odd dropped frame on the
|
||||||
|
busy bus. Heater is wired the same way but wasn't actuated (avoid cycling gas
|
||||||
|
ignition casually); it should behave identically.
|
||||||
6. **Attended awning test (motor — watch it move).** The Awning `cover` is wired
|
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
|
(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
|
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
|
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
|
stream the opcode (repeat until Stop) — only after watching it. The single-shot
|
||||||
default can't run the motor away.
|
default can't run the motor away.
|
||||||
7. **(Optional) Surface at the campsite HA + bridge home** like the gazebo fans /
|
7. ~~**Surface at the campsite HA**~~ — DONE (2026-06-12). Added to the campsite
|
||||||
OneControl BLE devices, if you want these in the home dashboard too.
|
HAOS Pi (`192.168.69.10`) as ESPHome config entry "OneControl CAN"; all 11
|
||||||
|
entities (`*.onecontrol_can_*`) live. The old BLE integration is **fully torn
|
||||||
|
out**: config entry deleted, `custom_components/lippert_onecontrol` removed
|
||||||
|
from the Pi, 11 stale `campsite_onecontrol_*` MQTT registry orphans purged
|
||||||
|
(home broker had no retained discovery topics — they were registry-only). The
|
||||||
|
camper dashboard (`lovelace.dashboard_camper`) was rewritten to the new
|
||||||
|
entities; the water-pump tile was dropped (pump is panel-only by design) and
|
||||||
|
awning + both fault sensors added.
|
||||||
|
8. ~~**Bridge home via MQTT**~~ — DONE (2026-06-12 evening). The Pi package
|
||||||
|
`/config/packages/mqtt_bridge.yaml` was rewritten against the CAN entities
|
||||||
|
(repo copy: `canbus/ha/mqtt_bridge_onecontrol.yaml`); it publishes MQTT
|
||||||
|
Discovery + state to cyrion's Mosquitto (192.168.88.69) and relays commands
|
||||||
|
back, same pattern as the gazebo bridge. Same `unique_id`s kept for surviving
|
||||||
|
loads, so home HA entity ids + history carried over (`*.campsite_onecontrol_*`);
|
||||||
|
water pump / cover_2 / cover_3 retained topics cleared (entities auto-removed
|
||||||
|
on home); awning + both fault sensors added, plus an availability topic
|
||||||
|
(`campsite/onecontrol/availability`) the old bridge lacked. Home Overview
|
||||||
|
"Camper" view updated to match. Command round-trip verified from home HA.
|
||||||
|
|
||||||
The DSI fault is already decoded and wired in (see below) — no capture needed.
|
The DSI fault is already decoded and wired in (see below) — no capture needed.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,364 @@
|
|||||||
|
# Campsite MQTT Bridge — publishes the OneControl CAN entities to home
|
||||||
|
# Mosquitto (cyrion, 192.168.88.69) via MQTT Discovery so they auto-appear
|
||||||
|
# on the home HA (192.168.88.40) as device "Campsite OneControl".
|
||||||
|
#
|
||||||
|
# Source side is the ESPHome IDS-CAN node ("OneControl CAN", entities
|
||||||
|
# *.onecontrol_can_*) — NOT the retired BLE integration.
|
||||||
|
# No water pump and no slide covers on purpose: those are panel-only
|
||||||
|
# (see canbus/HANDOFF.md safety notes).
|
||||||
|
#
|
||||||
|
# Deploy to Pi: /config/packages/mqtt_bridge.yaml
|
||||||
|
# Repo copy: canbus/ha/mqtt_bridge_onecontrol.yaml
|
||||||
|
|
||||||
|
automation:
|
||||||
|
# --- MQTT Discovery publish on startup / reload / every 6h ---
|
||||||
|
- id: campsite_mqtt_discovery
|
||||||
|
alias: "MQTT Bridge: Publish Discovery"
|
||||||
|
triggers:
|
||||||
|
- trigger: homeassistant
|
||||||
|
event: start
|
||||||
|
- trigger: event
|
||||||
|
event_type: automation_reloaded
|
||||||
|
- trigger: time_pattern
|
||||||
|
hours: "/6"
|
||||||
|
actions:
|
||||||
|
- delay: "00:00:05"
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "campsite/onecontrol/availability"
|
||||||
|
retain: true
|
||||||
|
payload: >-
|
||||||
|
{{ 'offline' if states('sensor.onecontrol_can_battery_voltage')
|
||||||
|
in ['unavailable', 'unknown'] else 'online' }}
|
||||||
|
# Switches
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "homeassistant/switch/campsite/gas_water_heater/config"
|
||||||
|
retain: true
|
||||||
|
payload: >-
|
||||||
|
{"name": "Water Heater",
|
||||||
|
"unique_id": "campsite_gas_water_heater",
|
||||||
|
"state_topic": "campsite/switch/water_heater/state",
|
||||||
|
"command_topic": "campsite/switch/water_heater/set",
|
||||||
|
"payload_on": "ON", "payload_off": "OFF",
|
||||||
|
"state_on": "ON", "state_off": "OFF",
|
||||||
|
"availability_topic": "campsite/onecontrol/availability",
|
||||||
|
"device": {"identifiers": ["campsite_onecontrol"],
|
||||||
|
"name": "Campsite OneControl",
|
||||||
|
"manufacturer": "Lippert",
|
||||||
|
"model": "Chateau 22QB (IDS-CAN)"}}
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "homeassistant/switch/campsite/exterior_lights/config"
|
||||||
|
retain: true
|
||||||
|
payload: >-
|
||||||
|
{"name": "Exterior Lights",
|
||||||
|
"unique_id": "campsite_exterior_lights",
|
||||||
|
"state_topic": "campsite/switch/exterior_lights/state",
|
||||||
|
"command_topic": "campsite/switch/exterior_lights/set",
|
||||||
|
"payload_on": "ON", "payload_off": "OFF",
|
||||||
|
"state_on": "ON", "state_off": "OFF",
|
||||||
|
"availability_topic": "campsite/onecontrol/availability",
|
||||||
|
"device": {"identifiers": ["campsite_onecontrol"]}}
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "homeassistant/switch/campsite/interior_lights/config"
|
||||||
|
retain: true
|
||||||
|
payload: >-
|
||||||
|
{"name": "Interior Lights",
|
||||||
|
"unique_id": "campsite_interior_lights",
|
||||||
|
"state_topic": "campsite/switch/interior_lights/state",
|
||||||
|
"command_topic": "campsite/switch/interior_lights/set",
|
||||||
|
"payload_on": "ON", "payload_off": "OFF",
|
||||||
|
"state_on": "ON", "state_off": "OFF",
|
||||||
|
"availability_topic": "campsite/onecontrol/availability",
|
||||||
|
"device": {"identifiers": ["campsite_onecontrol"]}}
|
||||||
|
# Sensors
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "homeassistant/sensor/campsite/battery_voltage/config"
|
||||||
|
retain: true
|
||||||
|
payload: >-
|
||||||
|
{"name": "Battery Voltage",
|
||||||
|
"unique_id": "campsite_battery_voltage",
|
||||||
|
"state_topic": "campsite/sensor/battery_voltage/state",
|
||||||
|
"unit_of_measurement": "V",
|
||||||
|
"device_class": "voltage",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"suggested_display_precision": 2,
|
||||||
|
"availability_topic": "campsite/onecontrol/availability",
|
||||||
|
"device": {"identifiers": ["campsite_onecontrol"]}}
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "homeassistant/sensor/campsite/fresh_water_tank/config"
|
||||||
|
retain: true
|
||||||
|
payload: >-
|
||||||
|
{"name": "Fresh Water Tank",
|
||||||
|
"unique_id": "campsite_fresh_water_tank",
|
||||||
|
"state_topic": "campsite/sensor/fresh_water_tank/state",
|
||||||
|
"unit_of_measurement": "%",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"icon": "mdi:water",
|
||||||
|
"availability_topic": "campsite/onecontrol/availability",
|
||||||
|
"device": {"identifiers": ["campsite_onecontrol"]}}
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "homeassistant/sensor/campsite/grey_tank_1/config"
|
||||||
|
retain: true
|
||||||
|
payload: >-
|
||||||
|
{"name": "Grey Tank 1",
|
||||||
|
"unique_id": "campsite_grey_tank_1",
|
||||||
|
"state_topic": "campsite/sensor/grey_tank_1/state",
|
||||||
|
"unit_of_measurement": "%",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"icon": "mdi:water-opacity",
|
||||||
|
"availability_topic": "campsite/onecontrol/availability",
|
||||||
|
"device": {"identifiers": ["campsite_onecontrol"]}}
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "homeassistant/sensor/campsite/grey_tank_2/config"
|
||||||
|
retain: true
|
||||||
|
payload: >-
|
||||||
|
{"name": "Grey Tank 2",
|
||||||
|
"unique_id": "campsite_grey_tank_2",
|
||||||
|
"state_topic": "campsite/sensor/grey_tank_2/state",
|
||||||
|
"unit_of_measurement": "%",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"icon": "mdi:water-opacity",
|
||||||
|
"availability_topic": "campsite/onecontrol/availability",
|
||||||
|
"device": {"identifiers": ["campsite_onecontrol"]}}
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "homeassistant/sensor/campsite/black_tank/config"
|
||||||
|
retain: true
|
||||||
|
payload: >-
|
||||||
|
{"name": "Black Tank",
|
||||||
|
"unique_id": "campsite_black_tank",
|
||||||
|
"state_topic": "campsite/sensor/black_tank/state",
|
||||||
|
"unit_of_measurement": "%",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"icon": "mdi:water-alert",
|
||||||
|
"availability_topic": "campsite/onecontrol/availability",
|
||||||
|
"device": {"identifiers": ["campsite_onecontrol"]}}
|
||||||
|
# Fault binary sensors
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "homeassistant/binary_sensor/campsite/water_heater_dsi_fault/config"
|
||||||
|
retain: true
|
||||||
|
payload: >-
|
||||||
|
{"name": "Water Heater DSI Fault",
|
||||||
|
"unique_id": "campsite_water_heater_dsi_fault",
|
||||||
|
"state_topic": "campsite/binary_sensor/water_heater_dsi_fault/state",
|
||||||
|
"payload_on": "ON", "payload_off": "OFF",
|
||||||
|
"device_class": "problem",
|
||||||
|
"availability_topic": "campsite/onecontrol/availability",
|
||||||
|
"device": {"identifiers": ["campsite_onecontrol"]}}
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "homeassistant/binary_sensor/campsite/onecontrol_system_fault/config"
|
||||||
|
retain: true
|
||||||
|
payload: >-
|
||||||
|
{"name": "System Fault",
|
||||||
|
"unique_id": "campsite_onecontrol_system_fault",
|
||||||
|
"state_topic": "campsite/binary_sensor/onecontrol_system_fault/state",
|
||||||
|
"payload_on": "ON", "payload_off": "OFF",
|
||||||
|
"device_class": "problem",
|
||||||
|
"availability_topic": "campsite/onecontrol/availability",
|
||||||
|
"device": {"identifiers": ["campsite_onecontrol"]}}
|
||||||
|
# Awning
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "homeassistant/cover/campsite/awning/config"
|
||||||
|
retain: true
|
||||||
|
payload: >-
|
||||||
|
{"name": "Awning",
|
||||||
|
"unique_id": "campsite_awning",
|
||||||
|
"state_topic": "campsite/cover/awning/state",
|
||||||
|
"command_topic": "campsite/cover/awning/set",
|
||||||
|
"device_class": "awning",
|
||||||
|
"availability_topic": "campsite/onecontrol/availability",
|
||||||
|
"device": {"identifiers": ["campsite_onecontrol"]}}
|
||||||
|
# Publish current states immediately after discovery
|
||||||
|
- delay: "00:00:02"
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "campsite/switch/water_heater/state"
|
||||||
|
retain: true
|
||||||
|
payload: "{{ 'ON' if is_state('switch.onecontrol_can_water_heater', 'on') else 'OFF' }}"
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "campsite/switch/exterior_lights/state"
|
||||||
|
retain: true
|
||||||
|
payload: "{{ 'ON' if is_state('switch.onecontrol_can_exterior_lights', 'on') else 'OFF' }}"
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "campsite/switch/interior_lights/state"
|
||||||
|
retain: true
|
||||||
|
payload: "{{ 'ON' if is_state('switch.onecontrol_can_interior_lights', 'on') else 'OFF' }}"
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "campsite/sensor/battery_voltage/state"
|
||||||
|
retain: true
|
||||||
|
payload: "{{ states('sensor.onecontrol_can_battery_voltage') }}"
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "campsite/sensor/fresh_water_tank/state"
|
||||||
|
retain: true
|
||||||
|
payload: "{{ states('sensor.onecontrol_can_fresh_water_tank') }}"
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "campsite/sensor/grey_tank_1/state"
|
||||||
|
retain: true
|
||||||
|
payload: "{{ states('sensor.onecontrol_can_grey_tank_1') }}"
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "campsite/sensor/grey_tank_2/state"
|
||||||
|
retain: true
|
||||||
|
payload: "{{ states('sensor.onecontrol_can_grey_tank_2') }}"
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "campsite/sensor/black_tank/state"
|
||||||
|
retain: true
|
||||||
|
payload: "{{ states('sensor.onecontrol_can_black_tank') }}"
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "campsite/binary_sensor/water_heater_dsi_fault/state"
|
||||||
|
retain: true
|
||||||
|
payload: "{{ 'ON' if is_state('binary_sensor.onecontrol_can_water_heater_dsi_fault', 'on') else 'OFF' }}"
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "campsite/binary_sensor/onecontrol_system_fault/state"
|
||||||
|
retain: true
|
||||||
|
payload: "{{ 'ON' if is_state('binary_sensor.onecontrol_can_onecontrol_system_fault', 'on') else 'OFF' }}"
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "campsite/cover/awning/state"
|
||||||
|
retain: true
|
||||||
|
payload: "{{ states('cover.onecontrol_can_awning') }}"
|
||||||
|
|
||||||
|
# --- Availability tracking (CAN node up/down) ---
|
||||||
|
- id: campsite_forward_availability
|
||||||
|
alias: "MQTT Bridge: Forward Availability"
|
||||||
|
triggers:
|
||||||
|
- trigger: state
|
||||||
|
entity_id: sensor.onecontrol_can_battery_voltage
|
||||||
|
to: "unavailable"
|
||||||
|
- trigger: state
|
||||||
|
entity_id: sensor.onecontrol_can_battery_voltage
|
||||||
|
from: "unavailable"
|
||||||
|
actions:
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "campsite/onecontrol/availability"
|
||||||
|
retain: true
|
||||||
|
payload: >-
|
||||||
|
{{ 'offline' if trigger.to_state.state == 'unavailable' else 'online' }}
|
||||||
|
|
||||||
|
# --- State change forwarding ---
|
||||||
|
- id: campsite_forward_switch_states
|
||||||
|
alias: "MQTT Bridge: Forward Switch States"
|
||||||
|
triggers:
|
||||||
|
- trigger: state
|
||||||
|
entity_id:
|
||||||
|
- switch.onecontrol_can_water_heater
|
||||||
|
- switch.onecontrol_can_exterior_lights
|
||||||
|
- switch.onecontrol_can_interior_lights
|
||||||
|
conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.to_state.state in ['on', 'off'] }}"
|
||||||
|
actions:
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "campsite/switch/{{ trigger.to_state.object_id | replace('onecontrol_can_', '') }}/state"
|
||||||
|
retain: true
|
||||||
|
payload: "{{ trigger.to_state.state | upper }}"
|
||||||
|
|
||||||
|
- id: campsite_forward_sensor_states
|
||||||
|
alias: "MQTT Bridge: Forward Sensor States"
|
||||||
|
triggers:
|
||||||
|
- trigger: state
|
||||||
|
entity_id:
|
||||||
|
- sensor.onecontrol_can_battery_voltage
|
||||||
|
- sensor.onecontrol_can_fresh_water_tank
|
||||||
|
- sensor.onecontrol_can_grey_tank_1
|
||||||
|
- sensor.onecontrol_can_grey_tank_2
|
||||||
|
- sensor.onecontrol_can_black_tank
|
||||||
|
conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.to_state.state not in ['unavailable', 'unknown'] }}"
|
||||||
|
actions:
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "campsite/sensor/{{ trigger.to_state.object_id | replace('onecontrol_can_', '') }}/state"
|
||||||
|
retain: true
|
||||||
|
payload: "{{ trigger.to_state.state }}"
|
||||||
|
|
||||||
|
- id: campsite_forward_binary_sensor_states
|
||||||
|
alias: "MQTT Bridge: Forward Fault States"
|
||||||
|
triggers:
|
||||||
|
- trigger: state
|
||||||
|
entity_id:
|
||||||
|
- binary_sensor.onecontrol_can_water_heater_dsi_fault
|
||||||
|
- binary_sensor.onecontrol_can_onecontrol_system_fault
|
||||||
|
conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.to_state.state in ['on', 'off'] }}"
|
||||||
|
actions:
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "campsite/binary_sensor/{{ trigger.to_state.object_id | replace('onecontrol_can_', '') }}/state"
|
||||||
|
retain: true
|
||||||
|
payload: "{{ trigger.to_state.state | upper }}"
|
||||||
|
|
||||||
|
- id: campsite_forward_cover_states
|
||||||
|
alias: "MQTT Bridge: Forward Cover States"
|
||||||
|
triggers:
|
||||||
|
- trigger: state
|
||||||
|
entity_id:
|
||||||
|
- cover.onecontrol_can_awning
|
||||||
|
conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.to_state.state not in ['unavailable', 'unknown'] }}"
|
||||||
|
actions:
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "campsite/cover/{{ trigger.to_state.object_id | replace('onecontrol_can_', '') }}/state"
|
||||||
|
retain: true
|
||||||
|
payload: "{{ trigger.to_state.state }}"
|
||||||
|
|
||||||
|
# --- Incoming command handling ---
|
||||||
|
# Explicit allowlists — the wildcard subscription must never widen the bridge
|
||||||
|
# beyond the 3 switches + awning it intends to expose. A future debug/test
|
||||||
|
# entity on the ESPHome node must NOT become remotely controllable for free
|
||||||
|
# (the firmware command_guard is the backstop, not the only gate).
|
||||||
|
- id: campsite_handle_switch_commands
|
||||||
|
alias: "MQTT Bridge: Handle Switch Commands"
|
||||||
|
triggers:
|
||||||
|
- trigger: mqtt
|
||||||
|
topic: "campsite/switch/+/set"
|
||||||
|
conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: >-
|
||||||
|
{{ trigger.topic.split('/')[2] in
|
||||||
|
['water_heater', 'exterior_lights', 'interior_lights'] }}
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.payload | lower in ['on', 'off'] }}"
|
||||||
|
actions:
|
||||||
|
- action: "switch.turn_{{ trigger.payload | lower }}"
|
||||||
|
target:
|
||||||
|
entity_id: "switch.onecontrol_can_{{ trigger.topic.split('/')[2] }}"
|
||||||
|
|
||||||
|
- id: campsite_handle_cover_commands
|
||||||
|
alias: "MQTT Bridge: Handle Cover Commands"
|
||||||
|
triggers:
|
||||||
|
- trigger: mqtt
|
||||||
|
topic: "campsite/cover/+/set"
|
||||||
|
conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.topic.split('/')[2] == 'awning' }}"
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.payload | lower in ['open', 'close', 'stop'] }}"
|
||||||
|
actions:
|
||||||
|
- action: "cover.{{ trigger.payload | lower }}_cover"
|
||||||
|
target:
|
||||||
|
entity_id: "cover.onecontrol_can_{{ trigger.topic.split('/')[2] }}"
|
||||||
Reference in New Issue
Block a user