First smart lights :)
This commit is contained in:
137
apps/motiontracker.py
Normal file
137
apps/motiontracker.py
Normal file
@@ -0,0 +1,137 @@
|
||||
import appdaemon.plugins.hass.hassapi as hass
|
||||
from ad_toolbox.smartobject import SmartObject
|
||||
from ad_toolbox.eventhandler import EventHandler
|
||||
from ad_toolbox.expressionparser import ParsingException
|
||||
import time
|
||||
|
||||
class MotionTracker(SmartObject):
|
||||
|
||||
MAX_TIME = 120
|
||||
|
||||
def initialize(self):
|
||||
super().initialize()
|
||||
|
||||
if self.dataset == None:
|
||||
self.dataset = { 'areas_movement_time' : dict(), 'last_area_with_movement' : 'Unknown', 'areas_door_close_time' : dict() }
|
||||
else:
|
||||
# clean obsolete keys
|
||||
self.dataset['areas_movement_time'] = {area : self.dataset['areas_movement_time'][area] for area in self.args['areas'] if area in self.dataset['areas_movement_time']}
|
||||
self.dataset['areas_door_close_time'] = {area : self.dataset['areas_door_close_time'][area] for area in self.args['areas'] if area in self.dataset['areas_door_close_time']}
|
||||
|
||||
self.input_sensors = dict()
|
||||
self.output_last_motion_sensors = dict()
|
||||
self.output_last_motion_time_sensors = dict()
|
||||
self.output_door_close_time_sensors = dict()
|
||||
self.update_cb_handle = None
|
||||
|
||||
if "areas" in self.args:
|
||||
current_time = time.time()
|
||||
for area in self.args['areas']:
|
||||
update_on_both_front = False
|
||||
self.output_last_motion_sensors[area] = self.create_entity(f"sensor.{area}_last_motion")
|
||||
self.output_last_motion_time_sensors[area] = self.create_entity(f"sensor.{area}_last_motion_time")
|
||||
if isinstance(self.args['areas'][area], dict):
|
||||
sensor_entities = self.args['areas'][area]['motion_sensors']
|
||||
try: update_on_both_front = self.args['areas'][area]['update_on_both_front']
|
||||
except KeyError: pass
|
||||
|
||||
if 'door_sensor' in self.args['areas'][area]:
|
||||
self.output_door_close_time_sensors[area] = self.create_entity(f"{area}_sensor.door_close_time")
|
||||
|
||||
if not area in self.dataset['areas_door_close_time']:
|
||||
self.dataset['areas_door_close_time'][area] = 0
|
||||
|
||||
if not self.output_door_close_time_sensors[area].exists():
|
||||
self.output_door_close_time_sensors[area].set_state(state = self.dataset['areas_door_close_time'][area],attributes = {'unit_of_measurement' : "s"})
|
||||
|
||||
self.listen_state(self.on_door_close,self.args['areas'][area]['door_sensor'],new = 'off', old = 'on',area = area)
|
||||
else:
|
||||
sensor_entities = self.args['areas'][area]
|
||||
|
||||
if isinstance(sensor_entities, list):
|
||||
for entity in sensor_entities:
|
||||
self.register_motion_sensor(area,entity,update_on_both_front)
|
||||
else:
|
||||
self.register_motion_sensor(area,sensor_entities,update_on_both_front)
|
||||
|
||||
if not area in self.dataset['areas_movement_time']:
|
||||
self.dataset['areas_movement_time'][area] = 0
|
||||
|
||||
if not self.output_last_motion_time_sensors[area].exists():
|
||||
self.output_last_motion_time_sensors[area].set_state(state = self.dataset['areas_movement_time'][area],attributes = {'unit_of_measurement' : "s"})
|
||||
|
||||
self.update_area_sensor(area,current_time)
|
||||
|
||||
if "clear_areas_events" in self.args:
|
||||
self.event_handlers = list()
|
||||
for entry in self.args["clear_areas_events"]:
|
||||
yaml_block = self.args["clear_areas_events"][entry]
|
||||
|
||||
try: self.event_handlers.append(EventHandler(self,yaml_block['events_to_listen'],self.on_clear_areas_event,entry))
|
||||
except ParsingException as e:
|
||||
self.log_error(str(e))
|
||||
continue
|
||||
|
||||
self.update_areas_data()
|
||||
|
||||
def on_clear_areas_event(self, event_name, event_data,entry):
|
||||
for area in self.args["clear_areas_events"][entry]['areas_to_clear']:
|
||||
self.log(f"{area} movement data reseted by {event_name} event")
|
||||
self.dataset['areas_movement_time'][area] = 0
|
||||
self.output_last_motion_time_sensors[area].set_state(state = 0)
|
||||
|
||||
self.update_areas_data()
|
||||
|
||||
def register_motion_sensor(self,area,sensor_entity,update_on_both_front):
|
||||
self.log(f"Registering sensor {sensor_entity} for area {area}")
|
||||
if sensor_entity not in self.input_sensors:
|
||||
self.input_sensors[sensor_entity] = area
|
||||
self.listen_state(self.on_motion_detected,sensor_entity,old = "off", new = "on", area = area)
|
||||
if update_on_both_front:
|
||||
self.listen_state(self.on_motion_detected,sensor_entity,old = "on", new = "off", area = area)
|
||||
else:
|
||||
self.log_error(f"{sensor_entity} is already registered for area {self.input_sensors[sensor_entity]}")
|
||||
|
||||
def update_area_sensor(self,area,current_time):
|
||||
time_elapsed = min((current_time - self.dataset['areas_movement_time'][area]) / 60,self.MAX_TIME)
|
||||
assert time_elapsed != None
|
||||
self.output_last_motion_sensors[area].set_state(state = int(time_elapsed),attributes = {'unit_of_measurement' : "min"})
|
||||
|
||||
def is_excluded_from_last_area_with_movement(self,area):
|
||||
if "areas_excluded_from_last_area_with_movement" in self.args:
|
||||
return area in self.args["areas_excluded_from_last_area_with_movement"]
|
||||
|
||||
return False
|
||||
|
||||
def is_area_initialized(self,area):
|
||||
return area in self.output_last_motion_sensors #might need a dedicated boolean at some point
|
||||
|
||||
def update_areas_data(self,*args):
|
||||
current_time = time.time()
|
||||
#we don't want update_areas_data_to_fork if it's called directly from on_state_change
|
||||
if self.update_cb_handle and self.timer_running(self.update_cb_handle):
|
||||
self.cancel_timer(self.update_cb_handle)
|
||||
self.update_cb_handle = None
|
||||
|
||||
#I want to start updating the oldest
|
||||
#if not, the order might not be respected for a fraction of seconds.
|
||||
#For example if A = 5 and B = 6 and add 2 to the both of them starting by A
|
||||
#A will be bigger than B (A = 7 and B = 6) for a very short time, and might trigger some automation
|
||||
for area in sorted(self.dataset['areas_movement_time'], key = lambda area: self.dataset['areas_movement_time'][area]):
|
||||
if (self.is_area_initialized(area)): self.update_area_sensor(area,current_time)
|
||||
|
||||
self.set_state("sensor.last_motion",state = self.dataset['last_area_with_movement'])
|
||||
self.update_cb_handle = self.run_in(self.update_areas_data,30)
|
||||
|
||||
def on_motion_detected(self, entity, attribute, old, new, kwargs):
|
||||
current_time = time.time()
|
||||
self.dataset['areas_movement_time'][kwargs['area']] = current_time
|
||||
self.output_last_motion_time_sensors[kwargs['area']].set_state(state = current_time)
|
||||
|
||||
if not self.is_excluded_from_last_area_with_movement(kwargs['area']):
|
||||
self.dataset['last_area_with_movement'] = kwargs['area']
|
||||
|
||||
self.update_areas_data()
|
||||
|
||||
def on_door_close(self, entity, attribute, old, new, kwargs):
|
||||
self.output_door_close_time_sensors[kwargs['area']].set_state(state = time.time())
|
||||
Reference in New Issue
Block a user