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()