# Campsite presence / occupancy package. # # Occupancy truth: the gas water heater — always on while camping, always # turned off when leaving (propane). WiFi presence (MikroTik Chateau # device_trackers) is the second, internet-independent signal; until that # integration is set up the wifi sensor reads `unavailable` and everything # downstream treats it as "no veto" (failsafes key on GPS, dead-man stays # quiet). # # Deploy to Pi: /config/packages/campsite_presence.yaml # Repo copy: canbus/ha/campsite_presence.yaml # # ⚠️ FIRST DEPLOY: `template:` may be a new domain on this instance — full # core restart, then confirm the sensors exist (see dsi_fault_alert.yaml for # the reload_all trap). template: - binary_sensor: - name: "RV Occupied" unique_id: campsite_rv_occupied device_class: occupancy icon: mdi:campfire # unavailable/unknown heater (node down) -> sensor unavailable, not a # false "left": availability gates it. availability: >- {{ states('switch.onecontrol_can_water_heater') not in ['unavailable', 'unknown'] }} state: "{{ is_state('switch.onecontrol_can_water_heater', 'on') }}" - name: "Phones On Campsite WiFi" unique_id: campsite_phones_on_wifi device_class: presence icon: mdi:wifi-marker # MikroTik trackers (renamed to these ids after the integration is # added). While they don't exist the sensor is unavailable — that is # load-bearing: the dead-man automation below triggers on a sustained # 'off', which unavailable never satisfies. availability: >- {{ states('device_tracker.wes_phone_wifi') not in ['unavailable', 'unknown'] or states('device_tracker.lindsey_iphone_wifi') not in ['unavailable', 'unknown'] }} state: >- {{ is_state('device_tracker.wes_phone_wifi', 'home') or is_state('device_tracker.lindsey_iphone_wifi', 'home') }} automation: # --- Bridge both presence sensors home (discovery + state) ------------- - id: campsite_presence_mqtt_discovery alias: "Presence: Publish MQTT Discovery" triggers: - trigger: homeassistant event: start - trigger: event event_type: automation_reloaded - trigger: time_pattern hours: "/6" actions: - delay: "00:00:07" - action: mqtt.publish data: topic: "homeassistant/binary_sensor/campsite/rv_occupied/config" retain: true payload: >- {"name": "RV Occupied", "unique_id": "campsite_rv_occupied", "state_topic": "campsite/binary_sensor/rv_occupied/state", "payload_on": "ON", "payload_off": "OFF", "device_class": "occupancy", "availability_topic": "campsite/onecontrol/availability", "device": {"identifiers": ["campsite_onecontrol"]}} - action: mqtt.publish data: topic: "homeassistant/binary_sensor/campsite/phones_on_campsite_wifi/config" retain: true payload: >- {"name": "Phones On Campsite WiFi", "unique_id": "campsite_phones_on_wifi", "state_topic": "campsite/binary_sensor/phones_on_campsite_wifi/state", "payload_on": "ON", "payload_off": "OFF", "payload_available": "online", "payload_not_available": "offline", "availability_topic": "campsite/presence/wifi_availability", "device_class": "presence", "device": {"identifiers": ["campsite_onecontrol"]}} - delay: "00:00:02" - action: mqtt.publish data: topic: "campsite/binary_sensor/rv_occupied/state" retain: true payload: "{{ 'ON' if is_state('binary_sensor.rv_occupied', 'on') else 'OFF' }}" - action: mqtt.publish data: topic: "campsite/presence/wifi_availability" retain: true payload: >- {{ 'offline' if states('binary_sensor.phones_on_campsite_wifi') in ['unavailable', 'unknown'] else 'online' }} - action: mqtt.publish data: topic: "campsite/binary_sensor/phones_on_campsite_wifi/state" retain: true payload: "{{ 'ON' if is_state('binary_sensor.phones_on_campsite_wifi', 'on') else 'OFF' }}" - id: campsite_presence_forward_states alias: "Presence: Forward States" triggers: - trigger: state entity_id: - binary_sensor.rv_occupied - binary_sensor.phones_on_campsite_wifi actions: - action: mqtt.publish data: topic: "campsite/binary_sensor/{{ trigger.to_state.object_id }}/state" retain: true payload: "{{ 'ON' if trigger.to_state.state == 'on' else 'OFF' }}" - if: - condition: template value_template: "{{ trigger.to_state.object_id == 'phones_on_campsite_wifi' }}" then: - action: mqtt.publish data: topic: "campsite/presence/wifi_availability" retain: true payload: >- {{ 'offline' if trigger.to_state.state in ['unavailable', 'unknown'] else 'online' }} # --- Local dead-man notify (works with the WG tunnel down) ------------- # Heater on but neither phone seen on campsite WiFi for 3 h. Notify-only: # WiFi absence alone is not proof of departure (pool afternoon, dead # battery), so the auto-off lives home-side where GPS can confirm. - id: campsite_deadman_heater_notify alias: "Presence: Dead-man heater notify (local)" triggers: - trigger: state entity_id: binary_sensor.phones_on_campsite_wifi to: "off" for: "03:00:00" conditions: - condition: state entity_id: binary_sensor.rv_occupied state: "on" actions: - action: rest_command.discord_server_alerts data: message: >- <@321798967669030912> 🤨 The water heater has been on for 3+ hours with neither of your phones on the campsite WiFi. If you actually left, you forgot the shutdown ritual again — kill the heater from the dashboard. If you're at the pool, carry on. mode: single # --- Lot lights (Shelly) — PRE-STAGED, DISABLED until the relay exists -- # Enable + fix the entity id once the Shelly is installed in the shed # outlet box. Mirrors the camper exterior-lights sunset/sunrise pattern # (retry because nothing should be trusted fire-and-forget), gated on # occupancy so the lot doesn't light up for an empty site. - id: campsite_lot_lights_sunset alias: "Lot lights on at sunset (occupied only)" initial_state: false triggers: - trigger: sun event: sunset offset: 0 conditions: - condition: state entity_id: binary_sensor.rv_occupied state: "on" actions: - repeat: sequence: - action: switch.turn_on target: entity_id: switch.lot_lights - delay: "00:00:05" until: - condition: template value_template: >- {{ is_state('switch.lot_lights', 'on') or repeat.index >= 3 }} mode: single - id: campsite_lot_lights_sunrise alias: "Lot lights off at sunrise" initial_state: false triggers: - trigger: sun event: sunrise offset: 0 actions: - repeat: sequence: - action: switch.turn_off target: entity_id: switch.lot_lights - delay: "00:00:05" until: - condition: template value_template: >- {{ is_state('switch.lot_lights', 'off') or repeat.index >= 3 }} mode: single