This code integrates with dweet and freeboard to produce a dashboard display of which occupants of a house are probably at home, based on the presence or absence of their phones' MAC addresses on the local network.
An important distinction to meke is between current and marked presence. For instance, with a 15 minute grace period set through the config file, users marked as currently present at any time within the past 15 minutes will be marked present, even if they're not currently present right now. In other words, current presence is known for certain at the time that a line of code is run, whereas marked presence is 'probably accurate' and takes into account a longer period of time.
The program also reads a config file in YAML format, stored by default at /etc/homepresenced/homepresenced.yaml
. An example config file is shown below.
from time import sleep import subprocess import datetime import yaml import dweepy CONFIG_FILE_PATH = '/etc/homepresenced/homepresenced.yaml' class Occupant: def __init__(self, name, mac): self.name = name self.mac = mac self.lastPresent = None def registerPresence(self): self.lastPresent = datetime.datetime.now() with open(CONFIG_FILE_PATH) as f: # load configuration into conf dictionary conf = yaml.safe_load(f) # replace yaml-style occupant listings with instances of Occupant object for i, occupant in enumerate(conf['occupants']): conf['occupants'][i] = Occupant(occupant['name'], occupant['mac']) try: while True: occupancy_report = {} # data sent to dweepy about occupant presence # perform network scan. this takes time so it's more efficient to do it once per loop, rather than once per occupant per loop arp_scan = str(subprocess.check_output("sudo arp-scan -l", shell=True)) for occupant in conf['occupants']: if occupant.mac in arp_scan: occupant.registerPresence() # register occupant as CURRENTLY present if mac is in scan # should the occupant be MARKED as present (taking into account the 'grace period')? presence = datetime.datetime.now() - occupant.lastPresent < datetime.timedelta(minutes=conf['presence_grace_period']) if occupant.lastPresent else None # format time of last CURRENT presence. if not set (==None) then use "never" lastPresent = occupant.lastPresent.strftime(conf['time_format']) if occupant.lastPresent else "never" print(f"Occupant {occupant.name}'s device is {'PRESENT' if presence else 'ABSENT' if presence == False else 'UNSEEN'} (last present: {lastPresent})") # add to dictionary that will be sent to dweepy occupancy_report[occupant.name] = { 'presence': presence, 'lastPresent': lastPresent } dweepy.dweet_for(conf['dweet_thing'], occupancy_report) # catch keyboard ctrl+c interrupt signals and exit cleanly except KeyboardInterrupt: exit()
presence_grace_period: 15 dweet_thing: identifier_for_dweet_io time_format: "%Y/%m/%d at %HL%M:%S" occupants: - name: User One mac: 11:aa:11:aa:11:aa - name: User Two mac: 22:bb:22:bb:22:bb - name: User Three mac: 33:cc:33:cc:33:9cc