parent
d8ec4b30c8
commit
a619c3fe39
5 changed files with 260 additions and 0 deletions
@ -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 |
@ -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. |
@ -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) |
@ -0,0 +1,5 @@ |
||||
certifi==2018.11.29 |
||||
chardet==3.0.4 |
||||
idna==2.7 |
||||
requests==2.20.1 |
||||
urllib3==1.24.1 |
@ -0,0 +1,3 @@ |
||||
{ |
||||
"token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b" |
||||
} |
Loading…
Reference in new issue