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
|
- binary_sensor.living_room_presence_presence
|
||||||
|
|
||||||
motion_intrusion_increment: 0.2
|
motion_intrusion_increment: 0.2
|
||||||
|
opening_intrusion_increment: 0.9
|
||||||
intrusion_cooldown_step: 0.1
|
intrusion_cooldown_step: 0.1
|
||||||
intrusion_cooldown_interval: 60
|
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
|
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:
|
notifications:
|
||||||
home_alarm_armed_notification:
|
alarm_armed_notification:
|
||||||
recipients:
|
recipients:
|
||||||
all:
|
all:
|
||||||
services:
|
services:
|
||||||
- notify/mobile_app_iphone_de_pierre
|
- notify/mobile_app_iphone_de_pierre
|
||||||
#- notify/mobile_app_mae
|
- notify/mobile_app_mae
|
||||||
conditions: binary_sensor.home_alarm_armed
|
conditions: binary_sensor.home_alarm_armed
|
||||||
|
|
||||||
message:
|
message:
|
||||||
@@ -48,13 +49,13 @@ home_alarm_notifications:
|
|||||||
data:
|
data:
|
||||||
tag: home_alarm_notification
|
tag: home_alarm_notification
|
||||||
|
|
||||||
home_alarm_windows_open_notification:
|
windows_open_notification:
|
||||||
recipients:
|
recipients:
|
||||||
all:
|
all:
|
||||||
services:
|
services:
|
||||||
- notify/mobile_app_iphone_de_pierre
|
- notify/mobile_app_iphone_de_pierre
|
||||||
#- notify/mobile_app_mae
|
- 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
|
conditions: binary_sensor.home_alarm_armed and sensor.ad_number_of_windows_open > 0 and binary_sensor.home_alarm_just_armed
|
||||||
|
|
||||||
message:
|
message:
|
||||||
title: Alarme
|
title: Alarme
|
||||||
@@ -64,13 +65,13 @@ home_alarm_notifications:
|
|||||||
push:
|
push:
|
||||||
interruption-level: critical
|
interruption-level: critical
|
||||||
|
|
||||||
home_alarm_doors_open_notification:
|
doors_open_notification:
|
||||||
recipients:
|
recipients:
|
||||||
all:
|
all:
|
||||||
services:
|
services:
|
||||||
- notify/mobile_app_iphone_de_pierre
|
- notify/mobile_app_iphone_de_pierre
|
||||||
#- notify/mobile_app_mae
|
- 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
|
conditions: binary_sensor.home_alarm_armed and sensor.ad_number_of_doors_open > 0 and binary_sensor.home_alarm_just_armed
|
||||||
|
|
||||||
message:
|
message:
|
||||||
title: Alarme
|
title: Alarme
|
||||||
@@ -80,12 +81,42 @@ home_alarm_notifications:
|
|||||||
push:
|
push:
|
||||||
interruption-level: critical
|
interruption-level: critical
|
||||||
|
|
||||||
home_alarm_unarmed_notification:
|
intrusion_detected_notification:
|
||||||
recipients:
|
recipients:
|
||||||
all:
|
all:
|
||||||
services:
|
services:
|
||||||
- notify/mobile_app_iphone_de_pierre
|
- 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
|
conditions: not binary_sensor.home_alarm_armed
|
||||||
|
|
||||||
message:
|
message:
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ from datetime import datetime
|
|||||||
# motion_intrusion_increment: <float> # optional, default 0.1
|
# motion_intrusion_increment: <float> # optional, default 0.1
|
||||||
# Intrusion level increment applied per motion detection.
|
# 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
|
# intrusion_cooldown_step: <float> # optional, default 0.1
|
||||||
# Value subtracted from the intrusion level on each cooldown tick.
|
# Value subtracted from the intrusion level on each cooldown tick.
|
||||||
#
|
#
|
||||||
@@ -50,15 +53,20 @@ from datetime import datetime
|
|||||||
# off = alarm is disarmed
|
# off = alarm is disarmed
|
||||||
# Attributes: sensors_ignored_count
|
# 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
|
# sensor.<app_name>_intrusion_level
|
||||||
# Numeric intrusion level.
|
# Numeric intrusion level.
|
||||||
# 0 means no intrusion.
|
# 0 means no intrusion.
|
||||||
# Each opening adds +1.
|
# Each opening adds +opening_intrusion_increment.
|
||||||
# Each motion adds +motion_intrusion_increment.
|
# Each motion adds +motion_intrusion_increment.
|
||||||
# Cooldown lowers the level over time but never below the integer count
|
# 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).
|
# of opening intrusions (e.g. 1.5 can cool down to 1, not below).
|
||||||
# Attributes: breach_list, breach_count, opening_intrusions,
|
# 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)
|
# binary_sensor.<app_name>_breach_<local_name> (one per opening sensor)
|
||||||
# on = breach detected on this sensor during the current arm cycle
|
# on = breach detected on this sensor during the current arm cycle
|
||||||
@@ -84,6 +92,8 @@ from datetime import datetime
|
|||||||
|
|
||||||
class HomeAlarm(SmartObject):
|
class HomeAlarm(SmartObject):
|
||||||
|
|
||||||
|
EVENT_LOG_MAX_SIZE = 30
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Initialisation
|
# Initialisation
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@@ -97,11 +107,17 @@ class HomeAlarm(SmartObject):
|
|||||||
self._last_intrusion_source = None
|
self._last_intrusion_source = None
|
||||||
self.intrusion_level = 0.0
|
self.intrusion_level = 0.0
|
||||||
self.opening_intrusions_count = 0
|
self.opening_intrusions_count = 0
|
||||||
|
self.event_log = []
|
||||||
|
self.just_armed_off_timer = None
|
||||||
|
|
||||||
self.motion_intrusion_increment = self._read_positive_float(
|
self.motion_intrusion_increment = self._read_positive_float(
|
||||||
'motion_intrusion_increment',
|
'motion_intrusion_increment',
|
||||||
0.1,
|
0.1,
|
||||||
)
|
)
|
||||||
|
self.opening_intrusion_increment = self._read_positive_float(
|
||||||
|
'opening_intrusion_increment',
|
||||||
|
1.0,
|
||||||
|
)
|
||||||
self.intrusion_cooldown_step = self._read_positive_float(
|
self.intrusion_cooldown_step = self._read_positive_float(
|
||||||
'intrusion_cooldown_step',
|
'intrusion_cooldown_step',
|
||||||
0.1,
|
0.1,
|
||||||
@@ -143,6 +159,13 @@ class HomeAlarm(SmartObject):
|
|||||||
attributes={'sensors_ignored_count': 0},
|
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
|
# Intrusion level sensor
|
||||||
self.intrusion_sensor = self.create_entity(
|
self.intrusion_sensor = self.create_entity(
|
||||||
f"sensor.{self.name}_intrusion_level",
|
f"sensor.{self.name}_intrusion_level",
|
||||||
@@ -155,9 +178,8 @@ class HomeAlarm(SmartObject):
|
|||||||
'opening_intrusions': 0,
|
'opening_intrusions': 0,
|
||||||
'last_intrusion_time': None,
|
'last_intrusion_time': None,
|
||||||
'last_intrusion_source': None,
|
'last_intrusion_source': None,
|
||||||
'motion_intrusion_increment': self.motion_intrusion_increment,
|
'event_log': [],
|
||||||
'intrusion_cooldown_step': self.intrusion_cooldown_step,
|
'event_log_markdown': '',
|
||||||
'intrusion_cooldown_interval': self.intrusion_cooldown_interval,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -199,8 +221,16 @@ class HomeAlarm(SmartObject):
|
|||||||
|
|
||||||
def on_alarm_armed(self):
|
def on_alarm_armed(self):
|
||||||
self.log_info("Alarm ARMED")
|
self.log_info("Alarm ARMED")
|
||||||
|
self.event_log = []
|
||||||
|
self._push_event("Alarm armed")
|
||||||
self.alarm_active = True
|
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
|
# Snapshot sensors that are already open — they will be ignored
|
||||||
self.sensors_open_at_activation = {
|
self.sensors_open_at_activation = {
|
||||||
s for s in self.opening_sensors if self.get_state(s) == 'on'
|
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):
|
def on_alarm_disarmed(self):
|
||||||
self.log_info("Alarm DISARMED")
|
self.log_info("Alarm DISARMED")
|
||||||
|
self._push_event("Alarm disarmed")
|
||||||
self.alarm_active = False
|
self.alarm_active = False
|
||||||
self.sensors_open_at_activation = set()
|
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(
|
self.armed_sensor.set_state(
|
||||||
state='off',
|
state='off',
|
||||||
attributes={
|
attributes={
|
||||||
'sensors_ignored_count': 0,
|
'sensors_ignored_count': 0,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
# Intentionally keep breach sensors and intrusion sensor as-is
|
self._update_intrusion_sensor()
|
||||||
# so detections remain visible until manually reset.
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Opening sensor state changes
|
# Opening sensor state changes
|
||||||
@@ -257,8 +295,9 @@ class HomeAlarm(SmartObject):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.log_info(f"INTRUSION DETECTED via {entity}")
|
self.log_info(f"INTRUSION DETECTED via {entity}")
|
||||||
|
self._push_event(f"Opening intrusion: {entity}")
|
||||||
self.breach_sensors[entity].set_state(state='on')
|
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):
|
def on_motion_sensor_change(self, entity, attribute, old, new, kwargs):
|
||||||
if not self.alarm_active:
|
if not self.alarm_active:
|
||||||
@@ -268,9 +307,13 @@ class HomeAlarm(SmartObject):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.log_info(f"MOTION DETECTED via {entity}")
|
self.log_info(f"MOTION DETECTED via {entity}")
|
||||||
|
self._push_event(f"Motion detected: {entity}")
|
||||||
self._add_intrusion(self.motion_intrusion_increment, entity)
|
self._add_intrusion(self.motion_intrusion_increment, entity)
|
||||||
|
|
||||||
def on_intrusion_cooldown(self, kwargs):
|
def on_intrusion_cooldown(self, kwargs):
|
||||||
|
if not self.alarm_active:
|
||||||
|
return
|
||||||
|
|
||||||
floor = float(self.opening_intrusions_count)
|
floor = float(self.opening_intrusions_count)
|
||||||
if self.intrusion_level <= floor:
|
if self.intrusion_level <= floor:
|
||||||
if self.intrusion_level != floor:
|
if self.intrusion_level != floor:
|
||||||
@@ -287,12 +330,17 @@ class HomeAlarm(SmartObject):
|
|||||||
self.intrusion_level = new_value
|
self.intrusion_level = new_value
|
||||||
self._update_intrusion_sensor()
|
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
|
# Reset button
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
def on_reset_button(self, entity, attribute, old, new, kwargs):
|
def on_reset_button(self, entity, attribute, old, new, kwargs):
|
||||||
self.log_info("Breach sensors reset")
|
self.log_info("Breach sensors reset")
|
||||||
|
self._push_event("Alarm reset")
|
||||||
for handle in self.breach_sensors.values():
|
for handle in self.breach_sensors.values():
|
||||||
handle.set_state(state='off')
|
handle.set_state(state='off')
|
||||||
self.intrusion_level = 0.0
|
self.intrusion_level = 0.0
|
||||||
@@ -363,8 +411,15 @@ class HomeAlarm(SmartObject):
|
|||||||
|
|
||||||
self._last_breach_time = datetime.now().isoformat()
|
self._last_breach_time = datetime.now().isoformat()
|
||||||
self._last_intrusion_source = source
|
self._last_intrusion_source = source
|
||||||
|
#self._push_event(f"Intrusion level +{increment} from {source} -> {self.intrusion_level}")
|
||||||
self._update_intrusion_sensor()
|
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):
|
def _update_intrusion_sensor(self):
|
||||||
"""Recompute and push the intrusion level sensor state."""
|
"""Recompute and push the intrusion level sensor state."""
|
||||||
active = [
|
active = [
|
||||||
@@ -385,8 +440,7 @@ class HomeAlarm(SmartObject):
|
|||||||
'intrusion_floor': floor,
|
'intrusion_floor': floor,
|
||||||
'last_intrusion_time': self._last_breach_time,
|
'last_intrusion_time': self._last_breach_time,
|
||||||
'last_intrusion_source': self._last_intrusion_source,
|
'last_intrusion_source': self._last_intrusion_source,
|
||||||
'motion_intrusion_increment': self.motion_intrusion_increment,
|
'event_log': self.event_log,
|
||||||
'intrusion_cooldown_step': self.intrusion_cooldown_step,
|
'event_log_markdown': "\n".join(f"- {line}" for line in self.event_log),
|
||||||
'intrusion_cooldown_interval': self.intrusion_cooldown_interval,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user