Hello @PianSom,
When I wrote this app, I hadn’t realised that @andrewjfreyer had two versions of the presence system. The presence.sh
and monitor.sh
. Over the weekend, I migrated to the monitor.sh
, and due to how different its message structure is, it broke the app above. I have rewritten the app to function with the monitor.sh
version, and it is below.
I must say one very good thing about the monitor.sh
version is that its easier to anticipate a few things. Like in the config, if I name a particular MAC, its easy to tell what sensors will be generated for it, instead of seeking out device name. Like if I write in known_static_addresses
xx:xx:xx:xx:xx:xx Odianosen's Iphone
, I can easily expect sensors called sensor.odianosens_iphone_living_room
for confidence (that is if my presence system is in the Living Room) and binary_sensor.odianosens_iphone_home_state
for if the device is in the house.
Based on the above, automation can then be built in anticipation of these. Think about those running stuffs like AirBnB or if one was to have guests around and need some special automation for them in anticipation. I have also made the system a bit more reliant on the data generated in HA, instead of basing it on what is stored in AppD.
Lastly I do understand that some might prefer to be able to use this app, without needing to bring in the mqtt plugin as you mentioned earlier. If that is the case, there is a way to still achieve it using events. But as I don’t use it, working on it will be a well difficult and maintaining it will be a drag. I can assist if you need it, and as usual just holla.
import appdaemon.plugins.mqtt.mqttapi as mqtt
import json
import shelve
class HomePresenceApp(mqtt.Mqtt):
def initialize(self):
self.set_namespace('mqtt')
self.hass_namespace = self.args.get('hass_namespace', 'default')
self.presence_topic = self.args.get('presence_topic', 'presence')
self.db_file = self.args['database']
self.listen_event(self.presence_message, 'MQTT')
self.not_home_timers = dict()
self.timeout = self.args.get('not_home_timeout', 120) #time interval before declaring not home
self.minimum_conf = self.args.get('minimum_confidence', 90)
self.home_state_entities = dict()
with shelve.open(self.db_file) as db: #check sensors
remove_sensors = []
try:
sensors = db[self.presence_topic]
except:
sensors = {}
if isinstance(sensors, str):
sensors = json.loads(db[self.presence_topic])
for conf_ha_sensor, user_state_entity in sensors.items():
'''confirm the entity still in HA if not remove it'''
if self.entity_exists(conf_ha_sensor, namespace = self.hass_namespace):
self.listen_state(self.confidence_updated, conf_ha_sensor, user_state_entity = user_state_entity, namespace = self.hass_namespace)
else:
remove_sensors.append(conf_ha_sensor)
if user_state_entity not in self.home_state_entities:
self.home_state_entities[user_state_entity ] = list()
if conf_ha_sensor not in remove_sensors and self.get_state(conf_ha_sensor, namespace = self.hass_namespace) != 'unknown': #meaning its not been scheduled to be removed
self.home_state_entities[user_state_entity].append(conf_ha_sensor)
if remove_sensors != []:
for sensor in remove_sensors:
sensors.pop(sensor)
db[self.presence_topic] = json.dumps(sensors) #reload new data
def presence_message(self, event_name, data, kwargs):
topic = data['topic']
if topic.split('/')[0] != self.presence_topic: #only interested in the presence topics
return
elif topic.split('/')[1] != 'owner':
return
payload = json.loads(data['payload'])
if payload.get('status', None) != None: #meaning its a message on the presence system
location = topic.split('/')[2].replace('_',' ').title()
self.log('The Presence System in the {} is {}'.format(location, payload.get('status').title()))
if payload.get('type', None) != 'Known Static MAC' or payload.get('name', None) == 'Unknown Name': #confirm its for a known MAC address
return
location = topic.split('/')[2].replace('_',' ').title()
location_Id = location.replace(' ', '_').lower()
device_name = payload['name']
user_name = payload['name'].lower().replace('’', '').replace(' ', '_').replace("'", "")
mac_address = topic.split('/')[3]
confidence = int(payload['confidence'])
user_conf_entity = '{}_{}'.format(user_name, location_Id)
conf_ha_sensor = 'sensor.{}'.format(user_conf_entity)
user_state_entity = '{}_home_state'.format(user_name)
user_sensor = 'binary_sensor.{}'.format(user_state_entity)
if user_state_entity not in self.not_home_timers:
self.not_home_timers[user_state_entity] = None #used to store the handle for the timer
appdaemon_entity = '{}.{}'.format(self.presence_topic, user_state_entity)
if not self.entity_exists(conf_ha_sensor, namespace = self.hass_namespace): #meaning it doesn't exist
self.log('Creating sensor {!r} for Confidence'.format(conf_ha_sensor), level='INFO')
topic = 'homeassistant/sensor/{}/{}/config'.format(self.presence_topic, user_conf_entity)
state_topic = "homeassistant/sensor/{}/{}/state".format(self.presence_topic, user_conf_entity)
payload = {"name": "{} {}".format(device_name, location), "state_topic": state_topic}
self.mqtt_send(topic, json.dumps(payload)) #send to homeassistant to create sensor for confidence
'''create user home state sensor'''
if not self.entity_exists(user_sensor, namespace = self.hass_namespace): #meaning it doesn't exist.
# it could be assumed it doesn't exist anyway
self.log('Creating sensor {!r} for Home State'.format(user_sensor), level='INFO')
topic = 'homeassistant/binary_sensor/{}/config'.format(user_state_entity)
payload = {"name": "{} Home State".format(device_name), "device_class" : "presence",
"state_topic": "homeassistant/binary_sensor/{}/state".format(user_state_entity)}
self.mqtt_send(topic, json.dumps(payload)) #send to homeassistant to create binary sensor sensor for home state
'''create app states for mapping user sensors to confidence to store and can be picked up by other apps if needed'''
if not self.entity_exists(appdaemon_entity):
self.set_app_state(appdaemon_entity, state = 'Initializing', attributes = {'Confidence' : confidence})
else:
user_attributes = self.get_state(appdaemon_entity, attribute = 'all')['attributes']
user_attributes['Confidence'] = confidence
self.set_app_state(appdaemon_entity, state = 'Updated', attributes = user_attributes)
self.listen_state(self.confidence_updated, conf_ha_sensor, user_state_entity = user_state_entity, namespace = self.hass_namespace)
if user_state_entity not in self.home_state_entities:
self.home_state_entities[user_state_entity] = list()
if conf_ha_sensor not in self.home_state_entities[user_state_entity]: #not really needed, but noting wrong in being extra careful
self.home_state_entities[user_state_entity].append(conf_ha_sensor)
self.run_in(self.update_sensor, 3, topic = state_topic, payload = confidence) #use delay so HA has time to setup sensor first before updating
with shelve.open(self.db_file) as db: #store sensors
try:
sensors = json.loads(db[self.presence_topic])
except:
sensors = {}
sensors[conf_ha_sensor] = user_state_entity
db[self.presence_topic] = json.dumps(sensors)
else:
if user_state_entity not in self.home_state_entities:
self.home_state_entities[user_state_entity] = list()
if conf_ha_sensor not in self.home_state_entities[user_state_entity]:
self.home_state_entities[user_state_entity].append(conf_ha_sensor)
self.listen_state(self.confidence_updated, conf_ha_sensor, user_state_entity = user_state_entity, namespace = self.hass_namespace)
with shelve.open(self.db_file) as db: #store sensors
try:
sensors = json.loads(db[self.presence_topic])
except:
sensors = {}
sensors[conf_ha_sensor] = user_state_entity
db[self.presence_topic] = json.dumps(sensors)
sensor_reading = self.get_state(conf_ha_sensor, namespace = self.hass_namespace)
if sensor_reading == 'unknown': #this will happen if HA was to restart
sensor_reading = 0
if int(sensor_reading) != confidence:
topic = "homeassistant/sensor/{}/{}/state".format(self.presence_topic, user_conf_entity)
payload = confidence
self.mqtt_send(topic, payload) #send to homeassistant to update sensor for confidence
if not self.entity_exists(appdaemon_entity): #in the event AppD restarts and not HA
self.set_app_state(appdaemon_entity, state = 'Initializing', attributes = {'Confidence' : confidence})
user_attributes = self.get_state(appdaemon_entity, attribute = 'all')['attributes']
user_attributes['Confidence'] = confidence
self.set_app_state(appdaemon_entity, state = 'Updated', attributes = user_attributes)
def confidence_updated(self, entity, attribute, old, new, kwargs):
user_state_entity = kwargs['user_state_entity']
user_sensor = 'binary_sensor.' + user_state_entity
appdaemon_entity = '{}.{}'.format(self.presence_topic, user_state_entity)
user_conf_sensors = self.home_state_entities.get(user_state_entity, None)
#self.log('__function__, __line__, user_conf_sensors: {}'.format(user_conf_sensors))
if user_conf_sensors != None:
sensor_res = list(map(lambda x: self.get_state(x, namespace = self.hass_namespace), user_conf_sensors))
sensor_res = [i for i in sensor_res if i != 'unknown'] # remove unknown vales from list
if sensor_res != [] and any(list(map(lambda x: int(x) >= self.minimum_conf, sensor_res))): #meaning at least one of them states is greater than the minimum so device definitely home
if self.not_home_timers[user_state_entity] != None: #cancel timer if running
self.cancel_timer(self.not_home_timers[user_state_entity])
self.not_home_timers[user_state_entity] = None
topic = "homeassistant/binary_sensor/{}/state".format(user_state_entity)
payload = 'ON'
self.mqtt_send(topic, payload) #send to homeassistant to update sensor that user home
self.set_app_state(appdaemon_entity, state = 'Home') #not needed but one may as well for other apps
else:
if self.not_home_timers[user_state_entity] == None and self.get_state(user_sensor, namespace = self.hass_namespace) != 'off': #run the timer
self.not_home_timers[user_state_entity] = self.run_in(self.not_home_func, self.timeout, user_state_entity = user_state_entity)
def not_home_func(self, kwargs):
user_state_entity = kwargs['user_state_entity']
user_sensor = 'binary_sensor.' + user_state_entity
appdaemon_entity = '{}.{}'.format(self.presence_topic, user_state_entity)
user_conf_sensors = self.home_state_entities[user_state_entity]
sensor_res = list(map(lambda x: self.get_state(x, namespace = self.hass_namespace), user_conf_sensors))
sensor_res = [i for i in sensor_res if i != 'unknown'] # remove unknown vales from list
if all(list(map(lambda x: int(x) < self.minimum_conf, sensor_res))): #still confirm for the last time
topic = "homeassistant/binary_sensor/{}/state".format(user_state_entity)
payload = 'OFF'
self.mqtt_send(topic, payload) #send to homeassistant to update sensor that user home
self.set_app_state(appdaemon_entity, state = 'Not Home') #not needed but one may as well for other apps
def update_sensor(self, kwargs):
topic = kwargs['topic']
payload = kwargs['payload']
self.mqtt_send(topic, payload) #send to homeassistant to update sensor for confidence