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