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.
193 lines
4.2 KiB
193 lines
4.2 KiB
import os |
|
import sys |
|
import RPi.GPIO as GPIO |
|
import serial |
|
import sqlite3 |
|
import atexit |
|
import time |
|
import logging |
|
import pygame |
|
import threading |
|
import urllib |
|
|
|
def dict_factory(cursor, row): |
|
d = {} |
|
for idx, col in enumerate(cursor.description): |
|
d[col[0]] = row[idx] |
|
return d |
|
|
|
class DaemonApp(object): |
|
def __init__(self): |
|
self.stdin_path = '/dev/null' |
|
self.stdout_path = '/dev/null' |
|
self.stderr_path = '/dev/null' |
|
self.pidfile_path = '/var/run/door.pid' |
|
self.pidfile_timeout = 5 |
|
|
|
self.running = True |
|
self.log = logging.getLogger("daemon") |
|
|
|
def __halt(self): |
|
self.running = False |
|
|
|
def run(self): |
|
atexit.register(self.__halt) |
|
|
|
self.setup() |
|
while self.running: |
|
try: |
|
self.loop() |
|
except KeyboardInterrupt: |
|
break |
|
except Exception as ex: |
|
self.log.exception(ex) |
|
break |
|
|
|
self.stop() |
|
|
|
def setup(self): |
|
pass |
|
|
|
def stop(self): |
|
pass |
|
|
|
def loop(self): |
|
pass |
|
|
|
class App(DaemonApp): |
|
def __init__(self, serial, db_file): |
|
super(App, self).__init__(); |
|
|
|
self.serial = serial |
|
self.db_file = db_file |
|
|
|
self.recent = {} |
|
|
|
def setup(self): |
|
self.log.info("starting") |
|
|
|
GPIO.setwarnings(False) |
|
GPIO.setmode(GPIO.BCM) |
|
GPIO.setup(17, GPIO.OUT) |
|
# The 110 Door fails secure, |
|
# drive low to close the NO relay and unlock the door |
|
# leave HIGH otherwise. |
|
# The 108 Door fails secure, |
|
# but needs to be driven HIGH to close the normally open relay and unlock the door |
|
GPIO.output(17, GPIO.LOW) |
|
|
|
def loop(self): |
|
card = self.serial.readline() |
|
if not card: |
|
return |
|
|
|
card = card.strip() |
|
if len(card) != 10: |
|
return |
|
|
|
now = time.time() |
|
if card in self.recent: |
|
if now - self.recent[card] < 5.0: |
|
self.recent[card] = now |
|
return |
|
|
|
self.recent[card] = now |
|
|
|
self.handle_card_read(card) |
|
|
|
def stop(self): |
|
self.log.info("stopping") |
|
|
|
def handle_card_read(self, card): |
|
db = sqlite3.connect(self.db_file) |
|
db.row_factory = dict_factory |
|
baseurl = "http://my.protospace.ca/locks/door/108A/" |
|
|
|
self.unify_serial_numbers(db, card) |
|
|
|
# TODO flag duplicates |
|
cards = self.query_cards(db, card) |
|
if cards: |
|
card = cards.pop(0) |
|
|
|
# TODO merge duplicate values |
|
for temp in cards: |
|
pass |
|
|
|
self.update_timestamp(db, card['serial']) |
|
if not card['active']: |
|
self.log.warn("%s[%s] denied access" % (card["owner"], card['serial'])) |
|
urlaction = "DENIED" |
|
rfid = "%s" % card['serial'] |
|
|
|
else: |
|
self.log.info("%s[%s] entered the space" % (card["owner"], card['serial'])) |
|
urlaction = "ALLOWED" |
|
rfid = "%s" % card['serial'] |
|
|
|
threading.Thread(target=self.unlock_door, kwargs={"duration": 5.0}).start() |
|
|
|
else: |
|
self.log.info("Card read: %s" % (card,)) |
|
rfid = "%s" % (card,) |
|
urlaction = "NOT IN DB" |
|
|
|
url = baseurl + rfid + "/" + urlaction |
|
|
|
try: |
|
response = urllib.urlopen(url).read() |
|
self.log.info("Web log response was %s" % (response)) |
|
except: |
|
self.log.warn("A generic error occurred on the web call.") |
|
|
|
db.close() |
|
|
|
def unify_serial_numbers(self, db, card): |
|
q = "UPDATE cards SET serial='%s' WHERE serial='%s'" % (card, card[::-1]) |
|
db.execute(q) |
|
|
|
def update_scan_log(self, db, card): |
|
q = "INSERT INTO scan_logs" |
|
|
|
def update_timestamp(self, db, card): |
|
q = "UPDATE cards SET first_seen = datetime('now') WHERE serial = '%s' AND first_seen IS NULL" % (card) |
|
db.execute(q) |
|
|
|
q = "UPDATE cards SET last_seen = datetime('now') WHERE serial = '%s'" % (card) |
|
db.execute(q) |
|
|
|
db.commit() |
|
|
|
def unlock_door(self, duration): |
|
#108 Door needs HIGH to unlock |
|
#110 Door needs LOW to unlock |
|
GPIO.output(17, GPIO.HIGH) |
|
time.sleep(duration) |
|
GPIO.output(17, GPIO.LOW) |
|
|
|
def play_sound(self, path): |
|
if not (path and os.path.exists(path)): |
|
return |
|
|
|
if not pygame.mixer.music.get_busy(): |
|
try: |
|
pygame.mixer.music.load(path) |
|
pygame.mixer.music.play() |
|
except: |
|
self.log.warn("Failed to play sound '%s'" % (path)) |
|
|
|
def query_cards(self, db, card): |
|
cur = db.cursor() |
|
query = "SELECT * FROM cards WHERE serial='%s' ORDER BY id ASC" % card |
|
cur.execute(query) |
|
|
|
cards = [] |
|
|
|
row = cur.fetchone() |
|
while row: |
|
cards.append(row) |
|
row = cur.fetchone() |
|
|
|
self.log.debug(cards) |
|
|
|
return cards
|
|
|