Refactor home alarm notifications and enhance intrusion detection logic
This commit is contained in:
@@ -20,6 +20,7 @@ home_alarm:
|
||||
- binary_sensor.living_room_presence_presence
|
||||
|
||||
motion_intrusion_increment: 0.2
|
||||
opening_intrusion_increment: 0.9
|
||||
intrusion_cooldown_step: 0.1
|
||||
intrusion_cooldown_interval: 60
|
||||
|
||||
@@ -33,12 +34,12 @@ home_alarm_notifications:
|
||||
priority: 100 # default priority app is 50, since the notification_manager doesn't create any sensor but is based on sensor created by many app, it's important it's created last
|
||||
|
||||
notifications:
|
||||
home_alarm_armed_notification:
|
||||
alarm_armed_notification:
|
||||
recipients:
|
||||
all:
|
||||
services:
|
||||
- notify/mobile_app_iphone_de_pierre
|
||||
#- notify/mobile_app_mae
|
||||
- notify/mobile_app_mae
|
||||
conditions: binary_sensor.home_alarm_armed
|
||||
|
||||
message:
|
||||
@@ -48,13 +49,13 @@ home_alarm_notifications:
|
||||
data:
|
||||
tag: home_alarm_notification
|
||||
|
||||
home_alarm_windows_open_notification:
|
||||
windows_open_notification:
|
||||
recipients:
|
||||
all:
|
||||
services:
|
||||
- notify/mobile_app_iphone_de_pierre
|
||||
#- notify/mobile_app_mae
|
||||
conditions: binary_sensor.home_alarm_armed and sensor.ad_number_of_windows_open > 0 and sensor.home_alarm_intrusion_level == 0
|
||||
- notify/mobile_app_mae
|
||||
conditions: binary_sensor.home_alarm_armed and sensor.ad_number_of_windows_open > 0 and binary_sensor.home_alarm_just_armed
|
||||
|
||||
message:
|
||||
title: Alarme
|
||||
@@ -64,13 +65,13 @@ home_alarm_notifications:
|
||||
push:
|
||||
interruption-level: critical
|
||||
|
||||
home_alarm_doors_open_notification:
|
||||
doors_open_notification:
|
||||
recipients:
|
||||
all:
|
||||
services:
|
||||
- notify/mobile_app_iphone_de_pierre
|
||||
#- notify/mobile_app_mae
|
||||
conditions: binary_sensor.home_alarm_armed and sensor.ad_number_of_doors_open > 0 and sensor.home_alarm_intrusion_level == 0
|
||||
- notify/mobile_app_mae
|
||||
conditions: binary_sensor.home_alarm_armed and sensor.ad_number_of_doors_open > 0 and binary_sensor.home_alarm_just_armed
|
||||
|
||||
message:
|
||||
title: Alarme
|
||||
@@ -80,12 +81,42 @@ home_alarm_notifications:
|
||||
push:
|
||||
interruption-level: critical
|
||||
|
||||
home_alarm_unarmed_notification:
|
||||
intrusion_detected_notification:
|
||||
recipients:
|
||||
all:
|
||||
services:
|
||||
- notify/mobile_app_iphone_de_pierre
|
||||
#- notify/mobile_app_mae
|
||||
- notify/mobile_app_mae
|
||||
conditions: binary_sensor.home_alarm_armed and sensor.home_alarm_intrusion_level >= 1
|
||||
|
||||
message:
|
||||
title: Alarme
|
||||
content: Intrusion détectée
|
||||
data:
|
||||
tag: home_alarm_notification
|
||||
push:
|
||||
interruption-level: critical
|
||||
|
||||
intrusion_suspected_notification:
|
||||
recipients:
|
||||
all:
|
||||
services:
|
||||
- notify/mobile_app_iphone_de_pierre
|
||||
- notify/mobile_app_mae
|
||||
conditions: binary_sensor.home_alarm_armed and sensor.home_alarm_intrusion_level > 0 and sensor.home_alarm_intrusion_level < 1
|
||||
|
||||
message:
|
||||
title: Alarme
|
||||
content: Intrusion suspectée
|
||||
data:
|
||||
tag: home_alarm_notification
|
||||
|
||||
alarm_unarmed_notification:
|
||||
recipients:
|
||||
all:
|
||||
services:
|
||||
- notify/mobile_app_iphone_de_pierre
|
||||
- notify/mobile_app_mae
|
||||
conditions: not binary_sensor.home_alarm_armed
|
||||
|
||||
message:
|
||||
|
||||
@@ -33,6 +33,9 @@ from datetime import datetime
|
||||
# motion_intrusion_increment: <float> # optional, default 0.1
|
||||
# Intrusion level increment applied per motion detection.
|
||||
#
|
||||
# opening_intrusion_increment: <float> # optional, default 1.0
|
||||
# Intrusion level increment applied per opening detection.
|
||||
#
|
||||
# intrusion_cooldown_step: <float> # optional, default 0.1
|
||||
# Value subtracted from the intrusion level on each cooldown tick.
|
||||
#
|
||||
@@ -50,15 +53,20 @@ from datetime import datetime
|
||||
# off = alarm is disarmed
|
||||
# Attributes: sensors_ignored_count
|
||||
#
|
||||
# binary_sensor.<app_name>_just_armed
|
||||
# on = during the 1s window immediately after arming
|
||||
# off = otherwise
|
||||
#
|
||||
# sensor.<app_name>_intrusion_level
|
||||
# Numeric intrusion level.
|
||||
# 0 means no intrusion.
|
||||
# Each opening adds +1.
|
||||
# Each opening adds +opening_intrusion_increment.
|
||||
# Each motion adds +motion_intrusion_increment.
|
||||
# Cooldown lowers the level over time but never below the integer count
|
||||
# of opening intrusions (e.g. 1.5 can cool down to 1, not below).
|
||||
# Attributes: breach_list, breach_count, opening_intrusions,
|
||||
# last_intrusion_time, last_intrusion_source, cooldown config.
|
||||
# last_intrusion_time, last_intrusion_source, event_log,
|
||||
# event_log_markdown.
|
||||
#
|
||||
# binary_sensor.<app_name>_breach_<local_name> (one per opening sensor)
|
||||
# on = breach detected on this sensor during the current arm cycle
|
||||
@@ -84,6 +92,8 @@ from datetime import datetime
|
||||
|
||||
class HomeAlarm(SmartObject):
|
||||
|
||||
EVENT_LOG_MAX_SIZE = 30
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Initialisation
|
||||
# ------------------------------------------------------------------
|
||||
@@ -97,11 +107,17 @@ class HomeAlarm(SmartObject):
|
||||
self._last_intrusion_source = None
|
||||
self.intrusion_level = 0.0
|
||||
self.opening_intrusions_count = 0
|
||||
self.event_log = []
|
||||
self.just_armed_off_timer = None
|
||||
|
||||
self.motion_intrusion_increment = self._read_positive_float(
|
||||
'motion_intrusion_increment',
|
||||
0.1,
|
||||
)
|
||||
self.opening_intrusion_increment = self._read_positive_float(
|
||||
'opening_intrusion_increment',
|
||||
1.0,
|
||||
)
|
||||
self.intrusion_cooldown_step = self._read_positive_float(
|
||||
'intrusion_cooldown_step',
|
||||
0.1,
|
||||
@@ -143,6 +159,13 @@ class HomeAlarm(SmartObject):
|
||||
attributes={'sensors_ignored_count': 0},
|
||||
)
|
||||
|
||||
self.just_armed_sensor = self.create_entity(
|
||||
f"binary_sensor.{self.name}_just_armed",
|
||||
state='off',
|
||||
icon='mdi:timer-sand',
|
||||
friendly_name='Alarm Just Armed',
|
||||
)
|
||||
|
||||
# Intrusion level sensor
|
||||
self.intrusion_sensor = self.create_entity(
|
||||
f"sensor.{self.name}_intrusion_level",
|
||||
@@ -155,9 +178,8 @@ class HomeAlarm(SmartObject):
|
||||
'opening_intrusions': 0,
|
||||
'last_intrusion_time': None,
|
||||
'last_intrusion_source': None,
|
||||
'motion_intrusion_increment': self.motion_intrusion_increment,
|
||||
'intrusion_cooldown_step': self.intrusion_cooldown_step,
|
||||
'intrusion_cooldown_interval': self.intrusion_cooldown_interval,
|
||||
'event_log': [],
|
||||
'event_log_markdown': '',
|
||||
},
|
||||
)
|
||||
|
||||
@@ -199,8 +221,16 @@ class HomeAlarm(SmartObject):
|
||||
|
||||
def on_alarm_armed(self):
|
||||
self.log_info("Alarm ARMED")
|
||||
self.event_log = []
|
||||
self._push_event("Alarm armed")
|
||||
self.alarm_active = True
|
||||
|
||||
if self.just_armed_off_timer is not None:
|
||||
self.cancel_timer(self.just_armed_off_timer)
|
||||
self.just_armed_off_timer = None
|
||||
self.just_armed_sensor.set_state(state='on')
|
||||
self.just_armed_off_timer = self.run_in(self.on_just_armed_timeout, 1)
|
||||
|
||||
# Snapshot sensors that are already open — they will be ignored
|
||||
self.sensors_open_at_activation = {
|
||||
s for s in self.opening_sensors if self.get_state(s) == 'on'
|
||||
@@ -229,17 +259,25 @@ class HomeAlarm(SmartObject):
|
||||
|
||||
def on_alarm_disarmed(self):
|
||||
self.log_info("Alarm DISARMED")
|
||||
self._push_event("Alarm disarmed")
|
||||
self.alarm_active = False
|
||||
self.sensors_open_at_activation = set()
|
||||
|
||||
if self.just_armed_off_timer is not None:
|
||||
self.cancel_timer(self.just_armed_off_timer)
|
||||
self.just_armed_off_timer = None
|
||||
self.just_armed_sensor.set_state(state='off')
|
||||
|
||||
# Reset live intrusion level while keeping historical attributes/events.
|
||||
self.intrusion_level = 0.0
|
||||
|
||||
self.armed_sensor.set_state(
|
||||
state='off',
|
||||
attributes={
|
||||
'sensors_ignored_count': 0,
|
||||
},
|
||||
)
|
||||
# Intentionally keep breach sensors and intrusion sensor as-is
|
||||
# so detections remain visible until manually reset.
|
||||
self._update_intrusion_sensor()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Opening sensor state changes
|
||||
@@ -257,8 +295,9 @@ class HomeAlarm(SmartObject):
|
||||
return
|
||||
|
||||
self.log_info(f"INTRUSION DETECTED via {entity}")
|
||||
self._push_event(f"Opening intrusion: {entity}")
|
||||
self.breach_sensors[entity].set_state(state='on')
|
||||
self._add_intrusion(1.0, entity, count_as_opening_intrusion=True)
|
||||
self._add_intrusion(self.opening_intrusion_increment, entity, count_as_opening_intrusion=True)
|
||||
|
||||
def on_motion_sensor_change(self, entity, attribute, old, new, kwargs):
|
||||
if not self.alarm_active:
|
||||
@@ -268,9 +307,13 @@ class HomeAlarm(SmartObject):
|
||||
return
|
||||
|
||||
self.log_info(f"MOTION DETECTED via {entity}")
|
||||
self._push_event(f"Motion detected: {entity}")
|
||||
self._add_intrusion(self.motion_intrusion_increment, entity)
|
||||
|
||||
def on_intrusion_cooldown(self, kwargs):
|
||||
if not self.alarm_active:
|
||||
return
|
||||
|
||||
floor = float(self.opening_intrusions_count)
|
||||
if self.intrusion_level <= floor:
|
||||
if self.intrusion_level != floor:
|
||||
@@ -287,12 +330,17 @@ class HomeAlarm(SmartObject):
|
||||
self.intrusion_level = new_value
|
||||
self._update_intrusion_sensor()
|
||||
|
||||
def on_just_armed_timeout(self, kwargs):
|
||||
self.just_armed_off_timer = None
|
||||
self.just_armed_sensor.set_state(state='off')
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Reset button
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def on_reset_button(self, entity, attribute, old, new, kwargs):
|
||||
self.log_info("Breach sensors reset")
|
||||
self._push_event("Alarm reset")
|
||||
for handle in self.breach_sensors.values():
|
||||
handle.set_state(state='off')
|
||||
self.intrusion_level = 0.0
|
||||
@@ -363,8 +411,15 @@ class HomeAlarm(SmartObject):
|
||||
|
||||
self._last_breach_time = datetime.now().isoformat()
|
||||
self._last_intrusion_source = source
|
||||
#self._push_event(f"Intrusion level +{increment} from {source} -> {self.intrusion_level}")
|
||||
self._update_intrusion_sensor()
|
||||
|
||||
def _push_event(self, message):
|
||||
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
self.event_log.insert(0, f"{now} - {message}")
|
||||
if len(self.event_log) > self.EVENT_LOG_MAX_SIZE:
|
||||
self.event_log = self.event_log[:self.EVENT_LOG_MAX_SIZE]
|
||||
|
||||
def _update_intrusion_sensor(self):
|
||||
"""Recompute and push the intrusion level sensor state."""
|
||||
active = [
|
||||
@@ -385,8 +440,7 @@ class HomeAlarm(SmartObject):
|
||||
'intrusion_floor': floor,
|
||||
'last_intrusion_time': self._last_breach_time,
|
||||
'last_intrusion_source': self._last_intrusion_source,
|
||||
'motion_intrusion_increment': self.motion_intrusion_increment,
|
||||
'intrusion_cooldown_step': self.intrusion_cooldown_step,
|
||||
'intrusion_cooldown_interval': self.intrusion_cooldown_interval,
|
||||
'event_log': self.event_log,
|
||||
'event_log_markdown': "\n".join(f"- {line}" for line in self.event_log),
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user