2
\$\begingroup\$

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
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

PEP 8

Use 4-space indents. You are using 8-space indents.

Use snake case (register_presence, not registerPresence) for methods.

Reduce the number of blank lines, and only use one at a time. This has too much blank space.

Add if __name__ == '__main__': (this isn't PEP 8 but it's very common)

Other Style

Put the config file read and keyboard interrupt there in if __name__. Put all the other logic into a main_loop function (not a general rule, just something I'd recommend here).

Define what "current" and "marked" are in comments in the code. If you need to explain it here, you need to explain it in the code.

I'd recommend making a variable called occupants. Modifying conf['occupants'] is a little dubious, and it's also shorter.

Consider separating out checking whether the occupant is present, and acting on any changes. But for something this small, I think it's fine as-is.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.