You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
249 lines
6.3 KiB
249 lines
6.3 KiB
import os, sys |
|
import logging |
|
logging.basicConfig( |
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', |
|
level=logging.INFO) |
|
|
|
TEST = os.environ.get('TEST', False) |
|
DEBUG = os.environ.get('DEBUG', False) |
|
|
|
from multiprocessing import Process, Queue |
|
from queue import Empty |
|
import json |
|
import requests |
|
import time |
|
from signal import * |
|
import binascii |
|
|
|
if not TEST: |
|
import RPi.GPIO as GPIO |
|
from pn532pi import Pn532, Pn532Hsu, pn532 |
|
|
|
import secrets |
|
|
|
|
|
RELAY_PIN = 18 |
|
CARDS_FILE = 'card_data.json' |
|
OPEN_DURATION = 4 |
|
VALID_PACKAGES = [ |
|
'Maker', |
|
'Maker Plus', |
|
'Maker Pro', |
|
'Access to everything 24/7', |
|
] |
|
|
|
TEST_PIPE = '/tmp/airlock' |
|
try: |
|
os.remove(TEST_PIPE) |
|
except FileNotFoundError: |
|
pass |
|
|
|
API_MEMBERS = 'https://fabman.io/api/v1/members?limit=1000&embed=key&embed=activePackages&includeKeyToken=true' |
|
|
|
nfc = None |
|
|
|
def unlock_door(): |
|
logging.info('Unlocking door...') |
|
|
|
if not TEST: |
|
GPIO.output(RELAY_PIN, GPIO.HIGH) |
|
|
|
time.sleep(OPEN_DURATION) |
|
|
|
GPIO.output(RELAY_PIN, GPIO.LOW) |
|
|
|
logging.info('Done.') |
|
|
|
def lock_door_on_exit(*args): |
|
logging.info('Exiting, locking door...') |
|
|
|
if not TEST: |
|
GPIO.output(RELAY_PIN, GPIO.LOW) |
|
|
|
os._exit(0) |
|
|
|
def feed_watchdog(): |
|
if DEBUG or TEST: |
|
return |
|
|
|
with open('/dev/watchdog', 'w') as wdt: |
|
wdt.write('1') |
|
|
|
def init(): |
|
global nfc, cards |
|
|
|
if not TEST: |
|
GPIO.setwarnings(False) |
|
GPIO.setmode(GPIO.BCM) |
|
GPIO.setup(RELAY_PIN, GPIO.OUT) |
|
GPIO.output(RELAY_PIN, GPIO.LOW) |
|
logging.info('GPIO initialized') |
|
|
|
if TEST: |
|
os.mkfifo(TEST_PIPE) |
|
logging.info('Test pipe initialized') |
|
else: |
|
PN532_HSU = Pn532Hsu(Pn532Hsu.RPI_MINI_UART) |
|
nfc = Pn532(PN532_HSU) |
|
nfc.begin() |
|
nfc.SAMConfig() |
|
version = nfc.getFirmwareVersion() |
|
logging.info('NFC reader initialized, verion: %s', version) |
|
|
|
if not version: |
|
logging.error('Unable to communicate with reader, waiting 10s and exiting...') |
|
time.sleep(10) |
|
os._exit(0) |
|
|
|
|
|
for sig in (SIGABRT, SIGILL, SIGINT, SIGSEGV, SIGTERM): |
|
signal(sig, lock_door_on_exit) |
|
logging.info('Signals initialized') |
|
|
|
def reader_thread(card_data_queue): |
|
recent_scans = {} |
|
|
|
with open(CARDS_FILE, 'r') as f: |
|
cards = json.load(f) |
|
logging.info('Read {} cards from disk'.format(len(cards))) |
|
|
|
while True: |
|
try: |
|
cards = card_data_queue.get_nowait() |
|
except Empty: |
|
pass |
|
|
|
if TEST: |
|
with open(TEST_PIPE, 'r') as pipe: |
|
success, card = (True, pipe.readline()) |
|
else: |
|
nfc.SAMConfig() |
|
success, card = nfc.readPassiveTargetID(pn532.PN532_MIFARE_ISO14443A_106KBPS) |
|
|
|
if not TEST: |
|
try: |
|
# ensure we have communication with the reader |
|
if nfc.getFirmwareVersion(): |
|
feed_watchdog() |
|
else: |
|
raise |
|
except: |
|
logging.error('Problem communicating with NFC reader!') |
|
time.sleep(1) |
|
continue |
|
|
|
try: |
|
card = binascii.hexlify(card).decode().strip() |
|
except TypeError: |
|
card = card.strip() |
|
except: |
|
logging.info('Unable to decode card: %s', str(card)) |
|
continue |
|
|
|
if len(card) != 14: continue |
|
|
|
# debounce card scans |
|
now = time.time() |
|
if card in recent_scans: |
|
if now - recent_scans[card] < 5.0: |
|
logging.info('Debounce skipping card scan') |
|
time.sleep(1) |
|
continue |
|
recent_scans[card] = now |
|
|
|
logging.info('Read card: ' + card) |
|
|
|
if card in cards: |
|
logging.info('Card recognized') |
|
else: |
|
logging.info('Card not recognized, denying access') |
|
continue |
|
|
|
card_data = cards[card] |
|
|
|
logging.info('Card belongs to: %s', card_data['name']) |
|
|
|
if not any(package in card_data['packages'] for package in VALID_PACKAGES): |
|
logging.info('No valid packages found: %s', str(card_data['packages'])) |
|
continue |
|
|
|
logging.info('DOOR ACCESS GRANTED - Card: %s | Name: %s', card, card_data['name']) |
|
|
|
unlock_door() |
|
|
|
#try: |
|
# res = requests.post(API_SEEN(card), timeout=2) |
|
# res.raise_for_status() |
|
#except BaseException as e: |
|
# logging.error('Problem POSTing seen: {} - {}'.format(e.__class__.__name__, str(e))) |
|
# continue |
|
|
|
def get_cards(card_data_queue): |
|
headers = {'Authorization': 'Bearer ' + secrets.FABMAN_API_KEY} |
|
res = requests.get(API_MEMBERS, headers=headers, timeout=10) |
|
res.raise_for_status() |
|
res = res.json() |
|
|
|
members = res |
|
cards = {} |
|
|
|
logging.info('Got {} members from API'.format(str(len(res)))) |
|
|
|
for member in members: |
|
if member['state'] != 'active': |
|
continue |
|
|
|
packages = [] |
|
|
|
for member_packages in member['_embedded']['memberPackages']: |
|
package = member_packages['_embedded']['package'] |
|
|
|
if package['state'] != 'active': |
|
continue |
|
|
|
packages.append(package['name']) |
|
|
|
key = member['_embedded']['key'] |
|
|
|
if not key: |
|
continue |
|
|
|
if key['state'] != 'active': |
|
continue |
|
|
|
token = key['token'] |
|
name = '{} {} ({})'.format(member['firstName'], member['lastName'], member['memberNumber']) |
|
|
|
cards[token] = dict(name=name, packages=packages) |
|
|
|
|
|
logging.info('Processed {} cards'.format(len(cards))) |
|
|
|
card_data_queue.put(cards) |
|
|
|
logging.info('Writing data to file') |
|
with open(CARDS_FILE, 'w') as f: |
|
json.dump(cards, f, indent=4) |
|
|
|
def update_thread(card_data_queue): |
|
if not DEBUG: time.sleep(10) |
|
|
|
while True: |
|
logging.info('Updating cards...') |
|
|
|
try: |
|
get_cards(card_data_queue) |
|
except BaseException as e: |
|
logging.exception('Problem updating cards: {} - {}'.format(e.__class__.__name__, str(e))) |
|
|
|
time.sleep(300) |
|
|
|
|
|
if __name__ == '__main__': |
|
logging.info('Initializing...') |
|
init() |
|
|
|
card_data = Queue() |
|
|
|
Process(target=reader_thread, args=(card_data,)).start() |
|
Process(target=update_thread, args=(card_data,)).start()
|
|
|