I've been working on this outdoor sensor alarm system for a while now

So the image is of a dashboard for a system I’ve been working on for a while. Basically my dad has a bunch of outdoor sensors that triggers when someone comes on the prosperity. There is 6 sensors for this. It seems like it is overkill but my sister has ties with very bad people, and she isn’t a good person. Also a year or 2 ago some people did try to break into our property. They left quickly.

Anyways, the driveway sensors he uses is eMACROS

It should be noted he got this prior to me getting the home assistant stuff, but even still. There isn’t a long distance sensor for outside and solar and within this price that directly works with home assistant.

They are good enough at their job, but we have multiple hubs. Their hubs are horrible, it doesn’t hook up to 3rd party things, and so on. I’ve been working on and still am to some point on getting it where it 100% works. But basically I got a RTL-SDR Blog V3 R860 hooked to the home assistant green

From there I basically worked on hooking them up.
I made a guide on it and this part is true even today but I found I had to do even more testing. It isn’t perfect but what I did find when doing this is each sensor is unique and this lets me put a intendent trigger per.
https://www.reddit.com/r/homeassistant/comments/1qkjgwz/guide_how_to_hook_up_emacros_driveway_sensors_to/

Anyways, now due to this I can do 2 things.

  1. The alerts over google home speakers
  2. Automations

When given sensors trigger the outside lights turn on 100% for 15 minutes. This is a straight forward motion then trigger thing.

But the sound was/is a bit of a nightmare.

During testing I found there is no way to group all the Google Home speakers and ungroup with code. I tried this to prevent the popcorn thing but ya…

I’m still modifying things and if you find things then let me know. Because I’m doing this on my own and it is extremely complex ya…

But with the sound the rules are simple.

Global timer is used to prevent multiple sounds from playing ontop of each other. Global sound can be edited by the user in the Driveway Alarm dashbaord

Sensor timers are used for when the global timer is active, so sensors don’t spam the system. Basically, if the sensor timer is idle, then it adds the sensor to the list and start the timer. If the sensor triggers again during that time, it doesn’t itslef to the list

The system should respect the mute decitions made in the dashbaord.

The system should adjust the sound after saving it per speaker before playing. This allowing the alarm to be heard but sound to be restored. Sound is set on the dashbaord.

How it works or should work is like this

Trigger happens, it starts the sensor and global cooldown timer and does sound. If another sensor triggers in the time for the global cooldown then it gets added to a text list to play right after, if it triggers multiple times then it ignores it which is why every sensor has their own cooldown. Then if another triggers it gets added.

When the global cooldown gets done it looks at the next thing on the list and pushes that alert out, start the global cooldown, and repeat until the list has nothing on it. If a given sensor is muted on all speakers then it just bypasses that and moves to the next thing on the list so there is no wait.

For this it required a bit of code. So ya… Again I’m still working on things since it isn’t 100% like I need it. And any help would be welcomed. Note I can’t share a lot of the code because the system freaks out. I think it is too much. I will put it in the comments if I can

The announcer.
THIS is the main thing.

alias: Driveway Announcer (Choose Logic)
description: >
  Summary: When the outside sensors trigger it plays over the Google Home
  speakers a MP3 file alerting which triggered.

  Rules: 

  1. Global timer is used to prevent multiple sounds from playing ontop of each
  other. Global sound can be edited by the user in the Driveway Alarm dashbaord 

  2. Sensor timers are used for when the global timer is active, so sensors
  don't spam the system. Basically, if the sensor timer is idle, then it adds
  the sensor to the list and start the timer. If the sensor triggers again
  during that time, it doesn't itslef to the list 

  4. The system should respect the mute decitions made in the dashbaord. 

  5. The system should adjust the sound after saving it per speaker before
  playing. This allowing the alarm to be heard but sound to be restored. Sound
  is set on the dashbaord.
triggers:
  - trigger: state
    entity_id:
      - binary_sensor.front_door
      - binary_sensor.driveway_alarm
      - binary_sensor.east_side_yard
      - binary_sensor.side_yard_next_to_driveway
      - binary_sensor.crystal_s_driveway
      - binary_sensor.pine_grove
      - binary_sensor.front_door_to_outside
      - binary_sensor.wyze_cam_front_porch_camera_motion
    to: "on"
    id: input_trigger
  - trigger: state
    entity_id:
      - input_button.test_mom_s_off_alarm
      - input_button.test_kitchen_alarm
      - input_button.test_middle_room_alert
      - input_button.test_master_bedroom_alarm
      - input_button.test_my_bedroom_alert
      - input_button.test_kids_room_alarm
    id: input_trigger
  - trigger: event
    event_type: timer.finished
    event_data:
      entity_id: timer.driveway_global_cooldown
    id: timer_done
conditions:
  - condition: state
    entity_id: input_boolean.smoke_or_co_detected
    state: "off"
actions:
  - alias: Log Incoming Trigger
    action: logbook.log
    data:
      name: Driveway Announcer
      message: >-
        Triggered by: {{ trigger.id }} | Entity: {{ trigger.entity_id or 'None'
        }} | ID: {{ trigger_id }}
  - alias: "Global Timer Finished: Restore Volumes First"
    if:
      - condition: trigger
        id: timer_done
    then:
      - alias: Loop to Restore All Volumes
        repeat:
          for_each: "{{ speaker_list }}"
          sequence:
            - alias: Restore Volume from Storage
              action: media_player.volume_set
              target:
                entity_id: "{{ repeat.item.player }}"
              data:
                volume_level: "{{ states(repeat.item.vol_storage) | float(0.5) }}"
  - choose:
      - alias: "Option 1: Global Timer Idle -> Play Now"
        conditions:
          - condition: trigger
            id: input_trigger
          - condition: state
            entity_id: timer.driveway_global_cooldown
            state: idle
        sequence:
          - alias: Check if Individual Sensor is Cooling Down
            if:
              - condition: template
                value_template: "{{ is_individual_cooling and trigger_id != 'test' }}"
            then:
              - stop: Blocked by individual cooldown.
          - alias: Calculate Logic Variables
            variables:
              check_id: "{{ trigger_id }}"
              is_suppressed: >-
                {% set z1 = is_state('switch.unnamed_zone_zone', 'on') %} {% set
                z2 = is_state('switch.zone_2_zone', 'on') %}  {% set z3 =
                is_state('switch.zone_3_zone', 'on') %}  {% set z4 =
                is_state('switch.zone_4_zone', 'on') %}  {% if check_id ==
                'driveway' %} {{ z1 or z4 }}  {% elif check_id == 'front_door'
                %} {{ z2 }}  {% elif check_id == 'east_side' %} {{ z3 }}  {%
                elif check_id in ['west_side', 'crystal'] %} {{ z4 }}  {% else
                %} false  {% endif %}
              any_speaker_on: >-
                {% set ns = namespace(found=false) %} {% for s in speaker_list
                %}
                  {% if is_state(s.bool_prefix ~ check_id, 'on') %}
                    {% set ns.found = true %}
                  {% endif %}
                {% endfor %} {{ ns.found }}
              should_play: >-
                {{ (any_speaker_on or check_id == 'test') and (not is_suppressed
                or check_id == 'test') }}
          - alias: Start Individual Sensor Cooldown
            action: timer.start
            target:
              entity_id: "{{ individual_timer_entity }}"
            data:
              duration: "00:01:00"
          - alias: Start Global Cooldown Timer
            action: timer.start
            target:
              entity_id: timer.driveway_global_cooldown
            data:
              duration: |-
                {% if should_play %}
                  {{ states('input_text.driveway_global_cooldown') | int(30) }}
                {% else %}
                  00:00:00.1
                {% endif %}
          - alias: "Loop A: Save Volume & Play (No Delay)"
            repeat:
              for_each: "{{ speaker_list }}"
              sequence:
                - variables:
                    mute_check: "{{ repeat.item.bool_prefix ~ check_id }}"
                - if:
                    - condition: template
                      value_template: >-
                        {{ (is_state(mute_check, 'on') or check_id == 'test')
                        and (not is_suppressed or check_id == 'test') }}
                  then:
                    - alias: Save Current Volume
                      action: input_number.set_value
                      target:
                        entity_id: "{{ repeat.item.vol_storage }}"
                      data:
                        value: >-
                          {{ state_attr(repeat.item.player, 'volume_level') |
                          float(0.5) }}
                    - alias: Set Alert Volume
                      action: media_player.volume_set
                      target:
                        entity_id: "{{ repeat.item.player }}"
                      data:
                        volume_level: >-
                          {{ ([0, states(repeat.item.vol_target)|float(80),
                          100]|sort)[1] / 100 }}
                    - alias: Play MP3
                      action: media_player.play_media
                      target:
                        entity_id: "{{ repeat.item.player }}"
                      data:
                        announce: true
                        media:
                          media_content_id: "{{ mp3_map[check_id] }}"
                          media_content_type: audio/mpeg
                          metadata: {}
          - alias: Log Outcome
            action: logbook.log
            data:
              name: Driveway Announcer
              message: >-
                Processed direct trigger for {{ check_id }}. Result: {% if
                should_play %}Played Audio{% else %}Skipped (Muted/Suppressed){%
                endif %}. Reason: Global Timer was Idle.
      - alias: "Option 2: Global Timer Busy -> Add to Queue"
        conditions:
          - condition: trigger
            id: input_trigger
          - condition: state
            entity_id: timer.driveway_global_cooldown
            state: active
        sequence:
          - alias: Check if Individual Sensor is Cooling Down
            if:
              - condition: template
                value_template: "{{ is_individual_cooling and trigger_id != 'test' }}"
            then:
              - stop: Blocked by individual cooldown.
          - alias: Append Sensor ID to Queue Text
            action: input_text.set_value
            target:
              entity_id: input_text.driveway_queue
            data:
              value: "{{ states('input_text.driveway_queue') ~ trigger_id ~ '|' }}"
          - alias: Start Individual Sensor Cooldown
            action: timer.start
            target:
              entity_id: "{{ individual_timer_entity }}"
            data:
              duration: "00:01:00"
          - alias: Log Queue Addition
            action: logbook.log
            data:
              name: Driveway Announcer
              message: "Added {{ trigger_id }} to queue. Reason: Global Timer Busy."
      - alias: "Option 3: Global Timer Finished -> Process Queue"
        conditions:
          - condition: trigger
            id: timer_done
          - condition: template
            value_template: "{{ states('input_text.driveway_queue') | length > 0 }}"
        sequence:
          - alias: Extract Next Item from Queue
            variables:
              current_queue: "{{ states('input_text.driveway_queue') }}"
              queue_list: "{{ current_queue.split('|') }}"
              next_id: "{{ queue_list[0] }}"
              remaining: "{{ queue_list[1:] | join('|') }}"
          - alias: Remove Played Item from Queue
            action: input_text.set_value
            target:
              entity_id: input_text.driveway_queue
            data:
              value: "{{ remaining }}"
          - alias: Calculate Logic Variables
            variables:
              check_id: "{{ next_id }}"
              is_suppressed: >-
                {% set z1 = is_state('switch.unnamed_zone_zone', 'on') %} {% set
                z2 = is_state('switch.zone_2_zone', 'on') %}  {% set z3 =
                is_state('switch.zone_3_zone', 'on') %}  {% set z4 =
                is_state('switch.zone_4_zone', 'on') %}  {% if check_id ==
                'driveway' %} {{ z1 or z4 }}  {% elif check_id == 'front_door'
                %} {{ z2 }}  {% elif check_id == 'east_side' %} {{ z3 }}  {%
                elif check_id in ['west_side', 'crystal'] %} {{ z4 }}  {% else
                %} false  {% endif %}
              any_speaker_on: >-
                {% set ns = namespace(found=false) %} {% for s in speaker_list
                %}
                  {% if is_state(s.bool_prefix ~ check_id, 'on') %}
                    {% set ns.found = true %}
                  {% endif %}
                {% endfor %} {{ ns.found }}
              should_play: >-
                {{ (any_speaker_on or check_id == 'test') and (not is_suppressed
                or check_id == 'test') }}
          - alias: Start Global Cooldown Timer
            action: timer.start
            target:
              entity_id: timer.driveway_global_cooldown
            data:
              duration: |-
                {% if should_play %}
                  {{ states('input_text.driveway_global_cooldown') | int(30) }}
                {% else %}
                  00:00:00.1
                {% endif %}
          - alias: "Loop A: Save Volume & Play (No Delay)"
            repeat:
              for_each: "{{ speaker_list }}"
              sequence:
                - variables:
                    mute_check: "{{ repeat.item.bool_prefix ~ check_id }}"
                - if:
                    - condition: template
                      value_template: >-
                        {{ (is_state(mute_check, 'on') or check_id == 'test')
                        and (not is_suppressed or check_id == 'test') }}
                  then:
                    - alias: Save Current Volume
                      action: input_number.set_value
                      target:
                        entity_id: "{{ repeat.item.vol_storage }}"
                      data:
                        value: >-
                          {{ state_attr(repeat.item.player, 'volume_level') |
                          float(0.5) }}
                    - alias: Set Alert Volume
                      action: media_player.volume_set
                      target:
                        entity_id: "{{ repeat.item.player }}"
                      data:
                        volume_level: >-
                          {{ ([0, states(repeat.item.vol_target)|float(80),
                          100]|sort)[1] / 100 }}
                    - alias: Play MP3
                      action: media_player.play_media
                      target:
                        entity_id: "{{ repeat.item.player }}"
                      data:
                        announce: true
                        media:
                          media_content_id: "{{ mp3_map[check_id] }}"
                          media_content_type: audio/mpeg
                          metadata: {}
          - alias: Log Outcome
            action: logbook.log
            data:
              name: Driveway Announcer
              message: >-
                Processed queue item {{ check_id }}. Result: {% if should_play
                %}Played Audio{% else %}Skipped (Muted/Suppressed){% endif %}.
                Reason: Global Timer Finished.
mode: parallel
max: 20
variables:
  sensor_map:
    binary_sensor.front_door: front_door
    binary_sensor.front_door_to_outside: front_door
    binary_sensor.wyze_cam_front_porch_camera_motion: front_door
    binary_sensor.driveway_alarm: driveway
    binary_sensor.east_side_yard: east_side
    binary_sensor.side_yard_next_to_driveway: west_side
    binary_sensor.crystal_s_driveway: crystal
    binary_sensor.pine_grove: pine_grove
  trigger_id: "{{ sensor_map.get(trigger.entity_id, 'test') }}"
  individual_timer_entity: timer.cooldown_{{ trigger_id }}
  is_individual_cooling: "{{ is_state(individual_timer_entity, 'active') }}"
  mp3_map:
    front_door: media-source://media_source/mp3/Front_door_Alarm.mp3
    driveway: media-source://media_source/mp3/Driveway.mp3
    east_side: media-source://media_source/mp3/East_yard.mp3
    west_side: media-source://media_source/mp3/West_side.mp3
    crystal: media-source://media_source/mp3/Crystal_driveway.mp3
    pine_grove: media-source://media_source/mp3/Pine.mp3
    test: media-source://media_source/mp3/Driveway.mp3
  speaker_list:
    - player: media_player.mom_s_office_mini
      bool_prefix: input_boolean.notify_mom_office_
      vol_storage: input_number.mom_s_office_volume
      vol_target: input_text.mom_s_office_alert_volume
    - player: media_player.game_room_speaker
      bool_prefix: input_boolean.notify_middle_room_
      vol_storage: input_number.middle_room_volume
      vol_target: input_text.middle_room_alert_volume
    - player: media_player.kitchen_display
      bool_prefix: input_boolean.notify_kitchen_
      vol_storage: input_number.kitchen_display_volume
      vol_target: input_text.kitchen_alert_volume
    - player: media_player.master_bedroom_speaker
      bool_prefix: input_boolean.notify_master_bedroom_
      vol_storage: input_number.master_bedroom_volume
      vol_target: input_text.master_bedroom_alert_volume
    - player: media_player.my_bedroom_mini
      bool_prefix: input_boolean.notify_my_bedroom_
      vol_storage: input_number.my_bedroom_mini_volume
      vol_target: input_text.my_bedroom_alert_volume
    - player: media_player.kid_speaker
      bool_prefix: input_boolean.notify_kids_room_
      vol_storage: input_number.kids_room_volume
      vol_target: input_text.kid_alert_volume

System wide mute logic

alias: Driveway - System Wide Mute Logic
description: >-
  Handles the GLOBAL mute button. Snapshots ALL rooms, turns ALL rooms off, and
  restores ALL rooms.
triggers:
  - trigger: state
    entity_id: input_button.mute_global
    id: button_press
  - trigger: event
    event_type: timer.finished
    event_data:
      entity_id: timer.global_mute
    id: timer_finished
actions:
  - variables:
      target_timer: timer.global_mute
      target_scene: scene.before_mute_global
      target_text: input_text.mute_text_global
      all_entities:
        - input_boolean.notify_mom_office
        - input_boolean.notify_kitchen
        - input_boolean.notify_middle_room
        - input_boolean.notify_my_bedroom
        - input_boolean.notify_master_bedroom
        - input_boolean.notify_kids_room
  - choose:
      - conditions:
          - condition: trigger
            id: button_press
        sequence:
          - choose:
              - conditions:
                  - condition: template
                    value_template: "{{ is_state(target_timer, 'active') }}"
                sequence:
                  - action: timer.cancel
                    target:
                      entity_id: "{{ target_timer }}"
                  - action: scene.turn_on
                    target:
                      entity_id: "{{ target_scene }}"
              - conditions:
                  - condition: template
                    value_template: "{{ is_state(target_timer, 'idle') }}"
                sequence:
                  - action: scene.create
                    data:
                      scene_id: "{{ target_scene.replace('scene.', '') }}"
                      snapshot_entities: "{{ all_entities }}"
                  - action: input_boolean.turn_off
                    target:
                      entity_id: "{{ all_entities }}"
                  - action: timer.start
                    target:
                      entity_id: "{{ target_timer }}"
                    data:
                      duration: "{{ (states(target_text) | float(0) * 60) | int }}"
      - conditions:
          - condition: trigger
            id: timer_finished
        sequence:
          - action: scene.turn_on
            target:
              entity_id: "{{ target_scene }}"
mode: single

The sync for the switches.

I had to make this because I wanted master controls on the dashboard.

alias: Driveway Smart Switch Sync
description: Cascades master switches to sub-switches and updates master status.
triggers:
  - trigger: state
    entity_id: input_boolean.driveway_system_master
    id: system_master
  - trigger: state
    entity_id:
      - input_boolean.notify_mom_office
      - input_boolean.notify_middle_room
      - input_boolean.notify_kitchen
      - input_boolean.notify_master_bedroom
      - input_boolean.notify_my_bedroom
      - input_boolean.notify_kids_room
    id: room_master
actions:
  - choose:
      - conditions:
          - condition: trigger
            id: system_master
        sequence:
          - action: >-
              input_boolean.turn_{{
              states('input_boolean.driveway_system_master') }}
            target:
              entity_id:
                - input_boolean.notify_mom_office
                - input_boolean.notify_middle_room
                - input_boolean.notify_kitchen
                - input_boolean.notify_master_bedroom
                - input_boolean.notify_my_bedroom
                - input_boolean.notify_kids_room
      - conditions:
          - condition: trigger
            id: room_master
        sequence:
          - variables:
              new_state: "{{ states(trigger.entity_id) }}"
          - action: input_boolean.turn_{{ new_state }}
            target:
              entity_id: >
                {{ trigger.entity_id }}_front_door, {{ trigger.entity_id
                }}_driveway, {{ trigger.entity_id }}_east_side, {{
                trigger.entity_id }}_west_side, {{ trigger.entity_id }}_crystal,
                {{ trigger.entity_id }}_pine_grove
mode: parallel

Sun glare rule

I made this because I thought sun was triggering one of the sensors and it turns it off during that time.

alias: "Solar Glare: East Side Mute & Restore"
description: >-
  Snapshots East Side alerts, turns them off during sun glare (Azimuth 100-135,
  Elevation < 30), and restores them afterwards.
triggers:
  - entity_id: sun.sun
    attribute: azimuth
    trigger: state
actions:
  - variables:
      is_glare_zone: |
        {{ 100 <= state_attr('sun.sun', 'azimuth') <= 135 and 
           0 <= state_attr('sun.sun', 'elevation') <= 30 }}
      target_entities:
        - input_boolean.notify_mom_office_east_side
        - input_boolean.notify_kitchen_east_side
        - input_boolean.notify_middle_room_east_side
        - input_boolean.notify_my_bedroom_east_side
        - input_boolean.notify_master_bedroom_east_side
        - input_boolean.notify_kids_room_east_side
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ is_glare_zone }}"
          - condition: template
            value_template: >-
              {{ states('scene.east_side_sun_snapshot') in ['unknown',
              'unavailable'] }}
        sequence:
          - data:
              scene_id: east_side_sun_snapshot
              snapshot_entities: "{{ target_entities }}"
            action: scene.create
          - target:
              entity_id: "{{ target_entities }}"
            action: input_boolean.turn_off
      - conditions:
          - condition: template
            value_template: "{{ not is_glare_zone }}"
          - condition: template
            value_template: >-
              {{ states('scene.east_side_sun_snapshot') not in ['unknown',
              'unavailable'] }}
        sequence:
          - target:
              entity_id: scene.east_side_sun_snapshot
            action: scene.turn_on

This looks very interesting, I’m quite interested in what you did here as I have a use case for outdoor security and my roommate hates cameras. Could you please wrap the configuration in your first post into a codeblock like you did in your second post? It makes it so much easier to read and makes copying out a one click event. Thanks.

Ya sorry about that. I just fixed it. I guess when I was trying to see how much code I can fit I might of messed that bit up.

Awesome thanks, going to pour through this on the weekend.