diff --git a/doorcontrol/.gitignore b/doorcontrol/.gitignore new file mode 100644 index 0000000..922bbfb --- /dev/null +++ b/doorcontrol/.gitignore @@ -0,0 +1,113 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# Editor +*.swp +*.swo + +# DB +db.sqlite3 +migrations/ + +# media +media/* + +# Custom +settings.json diff --git a/doorcontrol/README.md b/doorcontrol/README.md new file mode 100644 index 0000000..3cd7704 --- /dev/null +++ b/doorcontrol/README.md @@ -0,0 +1,85 @@ +# Protospace lockout card updater + +Runs on the door controller and updates the auth server with member cards. + +## Setup + +### Cloning + +Clone to user `pi`'s home directory. If you'd like to place it elsewhere, adjust the supervisor config below. + +``` +$ cd +$ git clone https://gogs.tannercollin.com/tanner/pslockout +``` + +### Python + +``` +$ sudo apt install python3 python3-pip python3-virtualenv # for Debian +$ cd ~pi/pslockout/doorcontrol +$ virtualenv -p python3 env +$ . env/bin/activate +(env) $ pip install -r requirements.txt +(env) $ deactivate +``` + +### Supervisor + +Supervisor is used to keep the script always running. + +``` +$ sudo apt install supervisor +``` + +Create a file named /etc/supervisor/conf.d/pushcards.conf and add: + +``` +[program:pushcards] +user=pi +directory=/home/pi/pslockout/doorcontrol +command=source env/bin/activate && python pushcards.py +autostart=true +autorestart=true +stderr_logfile=/var/log/pushcards.log +stderr_logfile_maxbytes=1MB +stdout_logfile=/var/log/pushcards.log +stdout_logfile_maxbytes=1MB +``` + +### settings.json + +Copy the example and insert a lockout admin's auth token: + +``` +$ cd ~pi/pslockout/doorcontrol +$ cp settings.json.example settings.json +$ vim settings.json +``` + +You can find your auth token by requsting with `curl`: + +``` +$ curl -d username=tanner.collin -d password=supersecret http://tools-auth.protospace.ca/login/ + +{ + "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b" +} +``` + +### Launch + +Reload supervisor and start pushcards: + +``` +$ sudo supervisorctl reread +$ sudo supervisorctl update +``` + +## Theory of Operation + +Every ten seconds the script checks to see if the card database has changed. + +If it was modified, it reads the card numbers into a Python object. + +It then sends the object (modified or not) to the auth server which updates the cards for any users found in the system. diff --git a/doorcontrol/pushcards.py b/doorcontrol/pushcards.py new file mode 100644 index 0000000..7a8b973 --- /dev/null +++ b/doorcontrol/pushcards.py @@ -0,0 +1,54 @@ +import logging +logging.basicConfig( + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + level=logging.ERROR) + +import json +import os +import requests +import sqlite3 +import time + +CARD_DB_PATH = '/home/pi/production.sqlite3' +CARD_PUSH_DELAY = 10 +CARD_UPDATE_URL = 'https://tools-auth.protospace.ca/update-cards/' + +settings = json.load(open('settings.json')) +request_headers = {'Authorization': 'Token ' + settings['token']} +last_modified_time = None +card_data = {} + +while True: + try: + modified_time = os.path.getmtime(CARD_DB_PATH) + + logging.info('modified_time: {}, last_modified_time: {}'.format(modified_time, last_modified_time)) + + if modified_time != last_modified_time: + logging.info('Card database modified time is different, reading database...') + + conn = sqlite3.connect('file:' + CARD_DB_PATH + '?mode=ro', uri=True) + c = conn.cursor() + + for row in c.execute('select owner, group_concat(serial) from cards where active=1 group by owner'): + username = row[0].replace(' ', '.').lower() + logging.info('Read name: {}, username: {}, cards: {}'.format(row[0], username, row[1])) + card_data[username] = row[1] + + conn.close() + + last_modified_time = modified_time + + logging.info('Uploading card data to server...') + + r = requests.put(CARD_UPDATE_URL, headers=request_headers, data=card_data) + + if r.status_code == requests.codes.ok: + logging.info('Number of cards updated: {}'.format(r.json()['updated'])) + else: + r.raise_for_status() + + except BaseException as e: + logging.error(e) + + time.sleep(CARD_PUSH_DELAY) diff --git a/doorcontrol/requirements.txt b/doorcontrol/requirements.txt new file mode 100644 index 0000000..2979b0c --- /dev/null +++ b/doorcontrol/requirements.txt @@ -0,0 +1,5 @@ +certifi==2018.11.29 +chardet==3.0.4 +idna==2.7 +requests==2.20.1 +urllib3==1.24.1 diff --git a/doorcontrol/settings.json.example b/doorcontrol/settings.json.example new file mode 100644 index 0000000..723df03 --- /dev/null +++ b/doorcontrol/settings.json.example @@ -0,0 +1,3 @@ +{ + "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b" +}