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.
- The alerts over google home speakers
- 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
