Home Presence Appdaemon App

Hi guys, just want to share an Appdaemon app I have been working on and testing. This is to compliment the wonderful work done by @andrewjfreyer Bluetooth home monitor system. Also thanks to @PianSom for setting up the idea and support when putting this together.

How to setup the monitor (not presence) system is seen here, and what this app does is simply to make it easy to integrate it into HA. This is based of Appdaemon, so it will only profit those that use it. This app does the following

  • Generates sensors for the following
    • Sensors of the Confidence levels for each device based on each location. So if you have 3 presence systems, each known device will have 3 confidence sensors with the names sensor.<device name>_location
    • Binary Sensors for each device. So no matter the number of location sensors you have, only one is generated and this is a presence sensor. The sensor entity_id will be binary_sensor.<device name>_home_state. So if one has an entry in the known_static_address as xx:xx:xx:xx:xx:xx odianosen's iphone it will generate binary_sensor.odianosens_iphone_home_state
    • Binary sensors for when everyone is in binary_sensor.everyone_home and when everyone is out binary_sensor.everyone_not_home. These sensors are set to ON or OFF when all declared users in the apps.yaml file users_sensors are in or out. If some are in and some out, both will be OFF. This is handy for other automation rules which I use alot.
  • If a device is seen to be below the configured minimum confidence minimum_confidence level across all locations which defaults to 90, a configurable not_home_timeout is ran before declaring the user device is not home in HA using the binary sensor generated for that device.
  • When one of the declared gateway_sensors in the apps.yaml is opened, based on who is in the house it will send a scan instruction to the monitor system.
  • Before sending the scan instruction, it first checks for if the system is busy scanning. This is done rudimentary for now by simply waiting out a configurable timer scan_timeout for messages being received. I have requested that @andrewjfreyer add a scan complete mqtt message so I can use that. But for now, this one works

When developing this app, 4 main things were my target:

  1. Ease of use: The user should only setup the monitor system/s, and no matter the number of locations involved, it should be up and running without virtually any or minimal extra work needed. The idea of editing the configuration.yaml file for sensors, automation and input_boolean as in the example to use this great system was almost a put off for me. And once one’s system grows, it exponentially takes more work to setup and debug :persevere:.
  2. Scalability: No matter the number of users or gateways or monitor systems in place, whether its small like mine which is 3, 1 & 2 respectively or you have 30, 10 and 20 respectively (if possible), it should take virtually the same amount of work to be up and running when using this app :smirk:
  3. Speed: Thanks to an idea by @benjimatt and following @andrewjfreyer example, I found out it was possible to speed up detection time within the app. So the app instructs the system to carryout an arrival or departure scans based on if someone enters or leaves the house and if everyone home or not. This made possible without need of forcing the monitor system to scan more frequently and thereby reducing impact on WiFi and other wireless equipment :relieved:
  4. Lastly and most especially Reliability: It was important false positives/negatives are eliminated in the way the system runs. So I tried to build in some little time based buffers here and there :grimacing:

To maximise the app, it will be advisable to setup the system in the home as follows:

  • Use Appdaemon (of course :roll_eyes:)

  • Try make use of the Appdaemon MQTT plugin found here. Though @aimc and @ReneTode still working on making it easier to integrate custom plugins, but for the mean time this has to be placed in plugin folder of your setup. If not wanting to use the plugin, its still very much possible but well I will need someone to make it work

  • Have a single main sensor, which runs as monitor.sh in a location that users stay more often as in @andrewjfreyer xample setup. If having more than 1 sensor, have the rest run as monitor.sh -t so they only scan on trigger. The main one triggers the rest and and the app does that also when need be

  • In the main sensor, have good spacing between scans, not only to avoid unnecessarily flooding your environment with scans but also allowing the app to take over scans intermittently. I have mine set at 120 secs throughout for now

  • Have sensors at the entrances into the home which I termed gateways, whether it be doors or garages. Windows also for those that use it :wink:

Do let me know what you think, and hope its useful to you guys. If there are better ways to improve this, do kindly let me know. I am a coder as a hubbist not as a job (as I do something else during the day lols). So experienced guys, just be kind :slight_smile:

Regards

Sample apps.yaml

home_presence_app:
  module: home_presence_app
  class: HomePresenceApp
  plugin: HASS
  hass_namespace: hass
  presence_topic: presence
  minimum_confidence: 100
  not_home_timeout: 60
  home_gateway_sensors:
    - binary_sensor.main_door
  scan_timeout: 20
  users_sensors:
    - binary_sensor.odianosens_iphone_home_state
    - binary_sensor.nkirukas_iphone_home_state

home_presence_app.py

import appdaemon.plugins.mqtt.mqttapi as mqtt
import json
import shelve
import os, sys
dirname, filename = os.path.split(os.path.abspath(sys.argv[0]))

 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 = os.path.join(os.path.dirname(__file__),'home_presence_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.depart_check_time = self.args.get('depart_check_time', 30)
        self.home_state_entities = dict() #used to store or map different confidence sensors based on location to devices 
        self.all_users_sensors = self.args['users_sensors'] #used to determine if anyone at home or not

        self.monitor_entity = '{}.monitor_state'.format(self.presence_topic) #used to check if the network monitor is busy 
        if not self.entity_exists(self.monitor_entity):
            self.set_app_state(self.monitor_entity, state = 'idle', attributes = {}) #set it to idle initially

        self.monitor_handlers = dict() #used to store different handlers
        self.monitor_handlers[self.monitor_entity] = None

        everyone_not_home_state = 'binary_sensor.everyone_not_home_state'
        everyone_home_state = 'binary_sensor.everyone_home_state'
        self.gateway_timer = None #run only a single timer at a time, to avoid sending multiple messages to the monitor

        if not self.entity_exists(everyone_not_home_state, namespace = self.hass_namespace): #check if the sensor exist and if not create it
            self.log('Creating Binary Sensor for Everyone Not Home State', level='INFO')
            topic =  'homeassistant/binary_sensor/everyone_not_home_state/config'
            payload = {"name": "Everyone Not Home State", "device_class" : "presence", 
                        "state_topic": "homeassistant/binary_sensor/everyone_not_home_state/state"}
            self.mqtt_send(topic, json.dumps(payload)) #send to homeassistant to create binary sensor sensor for home state

        if not self.entity_exists(everyone_home_state, namespace = self.hass_namespace): #check if the sensor exist and if not create it
            self.log('Creating Binary Sensor for Everyone Home State', level='INFO')
            topic =  'homeassistant/binary_sensor/everyone_home_state/config'
            payload = {"name": "Everyone Home State", "device_class" : "presence", 
                        "state_topic": "homeassistant/binary_sensor/everyone_home_state/state"}
            self.mqtt_send(topic, json.dumps(payload)) #send to homeassistant to create binary sensor sensor for home state

        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

        '''setup home gateway sensors'''
        for gateway_sensor in self.args['home_gateway_sensors']:
            '''it is assumed when the sensor is "on" it is opened'''
            self.listen_state(self.gateway_opened, gateway_sensor, new = 'on', namespace = self.hass_namespace)
        
    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 

        if topic.split('/')[-1] == 'start': #meaning a scan is starting 
            if self.get_state(self.monitor_entity) != 'scanning':
                '''since it idle, just set it to scanning to the scan number to 1 being the first'''
                self.set_app_state(self.monitor_entity, state = 'scanning', attributes = {'scan_type' : topic.split('/')[2], 'scan_num': 1})
            else: #meaing it was already set to 'scanning' already, so just update the number
                scan_num = self.get_state(self.monitor_entity, attribute = 'scan_num')
                if scan_num == None: #happens if AppD was to restart
                    scan_num = 0
                scan_num = scan_num + 1
                self.set_app_state(self.monitor_entity, attributes = {'scan_num': scan_num}) #update the scan number in the event of different scan systems in place

            #self.log('__function__, __line__, Scan Number is {} and Monitor State is {}'.format(self.get_state(self.monitor_entity, attribute = 'scan_num'), self.get_state(self.monitor_entity)))
                
        elif topic.split('/')[-1] == 'end': #meaning a scan just ended
            scan_num = self.get_state(self.monitor_entity, attribute = 'scan_num')
            if scan_num == None: #happens if AppD was to restart
                scan_num = 0
            scan_num = scan_num - 1
            if scan_num <= 0: # a <0 will happen if there is a restart and a message is missed in the process
                self.set_app_state(self.monitor_entity, state = 'idle', attributes = {'scan_type' : topic.split('/')[2], 'scan_num': 0}) #set the monitor state to idle since no messages being sent
            else:
                self.set_app_state(self.monitor_entity, attributes = {'scan_num': scan_num}) #update the scan number in the event of different scan systems are in place

            #self.log('__function__, __line__, Scan Number is {} and Monitor State is {}'.format(self.get_state(self.monitor_entity, attribute = 'scan_num'), self.get_state(self.monitor_entity)))

        if 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_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:
                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)

            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.send_mqtt_message, 1, 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)
        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

                if user_sensor in self.all_users_sensors: #check if everyone home
                    '''since at least someone home, set to off the everyone not home state'''
                    appdaemon_entity = '{}.everyone_not_home_state'.format(self.presence_topic)
                    topic = "homeassistant/binary_sensor/everyone_not_home_state/state"
                    payload = 'OFF'
                    self.mqtt_send(topic, payload) #send to homeassistant to update sensor that user home
                    self.set_app_state(appdaemon_entity, state = False) #not needed but one may as well for other apps

                    self.run_in(self.check_home_state, 2, check_state = 'is_home')

            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

            if user_sensor in self.all_users_sensors: #check if everyone not home
                '''since at least someone not home, set to off the everyone home state'''
                appdaemon_entity = '{}.everyone_home_state'.format(self.presence_topic)
                topic = "homeassistant/binary_sensor/everyone_home_state/state"
                payload = 'OFF'
                self.mqtt_send(topic, payload) #send to homeassistant to update sensor that user home
                self.set_app_state(appdaemon_entity, state = False) #not needed but one may as well for other apps

                self.run_in(self.check_home_state, 2, check_state = 'not_home')

    def send_mqtt_message(self, kwargs):
        topic = kwargs['topic']
        payload = kwargs['payload']
        if not kwargs.get('scan', False): #meaning its not for scanning, but for like sensor updating 
            self.mqtt_send(topic, payload) #send to broker
        else:
            self.gateway_timer = None #meaning no more gateway based timer is running

            if self.get_state(self.monitor_entity) == 'idle': #meaning its not busy
                self.mqtt_send(topic, payload) #send to scan for departure of anyone
            else: #meaning it is busy so re-run timer for it to get idle before sending the message to start scan
                self.gateway_timer = self.run_in(self.send_mqtt_message, self.depart_check_time, topic = topic, payload = payload, scan = True) 

    def gateway_opened(self, entity, attribute, old, new, kwargs):
        '''one of the gateways was opened and so needs to check what happened'''
        everyone_not_home_state = 'binary_sensor.everyone_not_home_state'
        everyone_home_state = 'binary_sensor.everyone_home_state'

        if self.gateway_timer != None: #meaning a timer is running already
            self.cancel_timer(self.gateway_timer)
            self.gateway_timer = None

        if self.get_state(everyone_not_home_state, namespace = self.hass_namespace) == 'on': #meaning no one at home
            topic = '{}/scan/Arrive'.format(self.presence_topic)
            payload = ''
            '''used to listen for when the monitor is free, and then send the message'''

            if self.get_state(self.monitor_entity) == 'idle': #meaning its not busy
                self.mqtt_send(topic, payload) #send to scan for arrival of anyone
            else:
                '''meaning it is busy so wait for it to get idle before sending the message'''
                if self.monitor_handlers.get('Arrive Scan', None) == None: #meaning its not listening already
                    self.monitor_handlers['Arrive Scan'] = self.listen_state(self.monitor_changed_state, self.monitor_entity, 
                                new = 'idle', scan = 'Arrive Scan', topic = topic, payload = payload)

        elif self.get_state(everyone_home_state, namespace = self.hass_namespace) == 'on': #meaning everyone at home
            topic ='{}/scan/Depart'.format(self.presence_topic)
            payload = ''
            self.gateway_timer = self.run_in(self.send_mqtt_message, self.depart_check_time, topic = topic, payload = payload, scan = True) #send to scan for departure of anyone
        else:
            topic = '{}/scan/Arrive'.format(self.presence_topic)
            payload = ''
            if self.get_state(self.monitor_entity) == 'idle': #meaning its not busy
                self.mqtt_send(topic, payload) #send to scan for arrival of anyone
            else:
                '''used to listen for when the monitor is free, and then send the message'''
                if self.monitor_handlers.get('Arrive Scan', None) == None: #meaning its not listening already
                    self.monitor_handlers['Arrive Scan'] = self.listen_state(self.monitor_changed_state, self.monitor_entity, 
                                new = 'idle', scan = 'Arrive Scan', topic = topic, payload = payload)

            topic ='{}/scan/Depart'.format(self.presence_topic)
            payload = ''
            self.gateway_timer = self.run_in(self.send_mqtt_message, self.depart_check_time, topic = topic, payload = payload, scan = True) #send to scan for departure of anyone

    def check_home_state(self, kwargs):
        check_state = kwargs['check_state']
        if check_state == 'is_home':
            ''' now run to check if everyone is home since a user is home'''
            user_res = list(map(lambda x: self.get_state(x, namespace = self.hass_namespace), self.all_users_sensors))
            user_res = [i for i in user_res if i != 'unknown'] # remove unknown vales from list
            user_res = [i for i in user_res if i != None] # remove None vales from list

            if all(list(map(lambda x: x == 'on', user_res))): #meaning every one is home
                appdaemon_entity = '{}.everyone_home_state'.format(self.presence_topic)
                topic = "homeassistant/binary_sensor/everyone_home_state/state"
                payload = 'ON'
                self.mqtt_send(topic, payload) #send to homeassistant to update sensor that user home
                self.set_app_state(appdaemon_entity, state = True) #not needed but one may as well for other apps
                
        elif check_state == 'not_home':
            ''' now run to check if everyone is not home since a user is not home'''
            user_res = list(map(lambda x: self.get_state(x, namespace = self.hass_namespace), self.all_users_sensors))
            user_res = [i for i in user_res if i != 'unknown'] # remove unknown vales from list
            user_res = [i for i in user_res if i != None] # remove None vales from list

            if all(list(map(lambda x: x == 'off', user_res))): #meaning no one is home
                appdaemon_entity = '{}.everyone_not_home_state'.format(self.presence_topic)
                topic = "homeassistant/binary_sensor/everyone_not_home_state/state"
                payload = 'ON'
                self.mqtt_send(topic, payload) #send to homeassistant to update sensor that user home
                self.set_app_state(appdaemon_entity, state = True) #not needed but one may as well for other apps

    def monitor_changed_state(self, entity, attribute, old, new, kwargs):
        topic = kwargs['topic']
        payload = kwargs['payload']
        scan = kwargs['scan']
        self.mqtt_send(topic, payload) #send to broker
        self.cancel_listen_state(self.monitor_handlers[scan])
        self.monitor_handlers[scan] = None

EDIT: 06-2019
The latest version of the code is available on Github
Please it is based on AD => 4.0

9 Likes

Hey @Odianosen25 - can I suggest that you post a link to you work on Andrew’s thread, and in the AppDaemon section?

This deserves a wider audience!

2 Likes

@Odianosen25 Absolutely agree with @PianSom. Great work!

Hello Bro,

Thanks for the suggestion, and I will look at doing that. Honestly not sure how to approach it, but will post it in the thread now.

@andrewjfreyer, so glad to get your remarks on this :blush:. Nothing better than the creator acknowledging your work. Thanks.

Regards

1 Like

This is great. I had started something similar but have not had time to debug and finish!

Any reason you don’t choose mqtt device_tracker over binary sensor?

Thanks for the response @kylerw.

That’s a cool idea, but unfortunately one is limited to supported devices that can be discovered by HA over MQTT. The list can be found Here.

Regards

Thanks, didn’t realize it wasn’t available yet.

I’ve added a feature request: Add device_tracker to MQTT discovery

Thanks for putting in the feature request.

I will edit the app to make it optional for those that want to use device tracker for viewing in HA. It will just be simply Home or Away. So if in the apps.yaml file device_tracker is set to true, it will also send an mqtt message to a certain topic fed off the device name generated.

So in my example above, if I set device_tracker to True, it will send Home or Away to the topic presence/odianosens_iphone. It is up to the user, to set it all up in the configuration.yaml file.

It will mean extra work for the user (which is what I wanted to avoid), but I see the need for simple viewing on the GUI instead of presence type of sensor.

Regards

Any plans to host on github or similar for easy updates/enhancements or do you see this as your “final” version?

Hello @kylerw,

Good point. I have just uploaded it to github as a pull request to @andrewjfreyer main branch here. This should make it easier for others to spot, that might be interested in using the system.

It is not definitely the final version, as I have a major upgrade I need to do to it, based on using different devices based on priorities per device to determine if a person is home or not. As its normal for one to have different Bluetooth devices and I believe it will reduce the probability of being mistakenly set as home/away.

I plan on using some form of Bayesian algorithm for that, but honestly figuring out the best way to approach it when still making it so user friendly is being a herculean task. And I got other projects I am working on, so not having so much time for this.

On the device_tracker side of things, I looked into it and for now the binary_sensor I think works fine as I am using the presence one. In HA, it comes up and either Home or Away which is what we need anyway, as the system doesn’t work outside the home environment. So for now, I wouldn’t be implementing the device_tracker side of things, unless your feature request is supported.

Thanks and regards

@Odianosen25 - I’ve stuck the mqtt plugin in the plugins folder, but the app can’t find the plugin and throws

ModuleNotFoundError: No module named 'appdaemon.plugins.mqtt'

I am getting info in my log that the mqtt plugin seems to be running and is subscribed to #.

Any guidance?

How did you drop it in?

As you need to drop all 3 files within the folder mqtt.

Can I kindly see all the logs related to mqtt?

Regards

@Odianosen25

I created the files in my config directory (using docker) and the cp to the plugins folder

image

2018-09-01 14:53:04.602541 WARNING AppDaemon: ------------------------------------------------------------
2018-09-01 14:53:04.603116 WARNING AppDaemon: Unexpected error loading module: /conf/apps/home_presence/home_presence.py:
2018-09-01 14:53:04.603592 WARNING AppDaemon: ------------------------------------------------------------
2018-09-01 14:53:04.606549 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 2015, in check_app_updates
    self.read_app(mod["name"], mod["reload"])
  File "/usr/local/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 1802, in read_app
    self.modules[module_name] = importlib.import_module(module_name)
  File "/usr/local/lib/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/conf/apps/home_presence/home_presence.py", line 1, in <module>
    import appdaemon.plugins.mqtt.mqttapi as mqtt
ModuleNotFoundError: No module named 'appdaemon.plugins.mqtt'

2018-09-01 14:53:04.607083 WARNING AppDaemon: ------------------------------------------------------------

2018-09-01 14:53:01.849620 INFO AppDaemon: MQTT: Connected to Broker at URL 192.168.1.188:1883
2018-09-01 14:53:01.850990 INFO AppDaemon: MQTT: Subscribing to Topic: #
2018-09-01 14:53:01.853466 INFO AppDaemon: MQTT: Subscription to Topic # Sucessful
2018-09-01 14:53:01.854961 INFO AppDaemon: MQTT: Subscribing to Topic: location/#
2018-09-01 14:53:01.855669 INFO AppDaemon: MQTT: Subscription to Topic location/# Sucessful
2018-09-01 14:53:01.857508 INFO AppDaemon: MQTT: *** Sending Complete State: {'mqtt.none': {'state': 'None', 'attributes': {}}} ***
2018-09-01 14:53:01.858134 INFO AppDaemon: Got initial state from namespace mqtt
2018-09-01 14:53:01.858715 INFO AppDaemon: MQTT: MQTT Plugin initialization complete
2018-09-01 14:53:01.937995 INFO AppDaemon: MQTT: Message Received: Topic = smartthings/Basement Bedroom Window Sensor/contact, Payload = b'closed'
2018-09-01 14:53:01.939375 INFO AppDaemon: MQTT: Message Received: Topic = smartthings/Basement Bedroom Window Sensor/battery, Payload = b'93'
2018-09-01 14:53:01.940680 INFO AppDaemon: MQTT: Message Received: Topic = smartthings/Office Window Sensor/contact, Payload = b'closed'
2018-09-01 14:53:01.955182 INFO AppDaemon: MQTT: Message Received: Topic = smartthings/Office Window Sensor/battery, Payload = b'89'
2018-09-01 14:53:01.955428 INFO AppDaemon: Got initial state from namespace default
2018-09-01 14:53:01.956685 INFO AppDaemon: MQTT: Message Received: Topic = smartthings/TV Room Window Sensor/contact, Payload = b'closed'
2018-09-01 14:53:01.959027 INFO AppDaemon: MQTT: Message Received: Topic = smartthings/TV Room Window Sensor/battery, Payload = b'92'
2018-09-01 14:53:01.959830 INFO AppDaemon: MQTT: Message Received: Topic = smartthings/Toy Room Window Sensor/contact, Payload = b'closed'
2018-09-01 14:53:01.960667 INFO AppDaemon: MQTT: Message Received: Topic = smartthings/Toy Room Window Sensor/battery, Payload = b'81'

1 Like

Apologies for the late response, but the files shouldn’t be in the config directory but in the python site-packages library directory (at least I think that’s what its called). Now if you using docker, that could be an issue as I don’t know how you can access it. I use pip3 install and I have it in /usr/local/lib/python3.5/dist-packages/appdaemon/plugins.

When I considered using docker and since there is an issue now with custom_plugins folder, I had asked the docker developer to allow for access to the site-packages plugins folder, which I don’t think he has done yet. Without access to that one, for now it will be difficult to use to plugin in docker.

Though I think a few ppl have gotten around this via the use of docker compose to like create another image, out of the original or something. But I know not how to do this I am afriad.

Regards

Hmmm… That’s essentially where mine is at - I created in the config folder but copied to the appdaemon/plugins folder manually.

So you confirming the mqtt plugin folder is in /usr/local/lib/python3.6/site-packages/appdaemon/plugins/? As the picture you gave showed /usr/scr/app/appdaemon/plugins/mqtt

Regards

The docker container installed appdeamom into /use/src/app

Which is basically a pointer to the path you use/reference…

Hmm, maybe not. Let me dig a bit into this.

@Odianosen25 so that did it.

No an error with app_config ["settings"] do you have that app/config you can share?

Hello @kylerw, oh glad you fixed it, can you kindly let me in how you did, as I would like to know in the event I want to use docker.

Well app_config['settings'] is just a way I store all my configurations for my system. I have a separate app (Yaml file really), where I have all my home settings in. So I access a single database for everything appdaemon stores.

What is needed is just a directory to a database it can use. In your case you could try /usr/src/app/database. I will edit the app, so it creates the database locally within where the app file is stored.

Regards