|
|
|
@ -1,33 +1,28 @@ |
|
|
|
|
import logging |
|
|
|
|
import threading |
|
|
|
|
import requests |
|
|
|
|
import time |
|
|
|
|
from functools import partial |
|
|
|
|
import os |
|
|
|
|
import requests |
|
|
|
|
from hashlib import scrypt |
|
|
|
|
from json.decoder import JSONDecodeError |
|
|
|
|
|
|
|
|
|
try: |
|
|
|
|
from hashlib import scrypt |
|
|
|
|
UID_HASH = partial(scrypt, n=16384, r=8, p=1) |
|
|
|
|
except ImportError: |
|
|
|
|
# Python 3.5 needs pip install scrypt |
|
|
|
|
from scrypt import hash as scrypt |
|
|
|
|
UID_HASH = partial(scrypt, N=16384, r=8, p=1) |
|
|
|
|
|
|
|
|
|
from .wiegand import Decoder |
|
|
|
|
from wiegand import Decoder |
|
|
|
|
|
|
|
|
|
logging.basicConfig( |
|
|
|
|
level=logging.DEBUG, |
|
|
|
|
format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s', |
|
|
|
|
datefmt='%m-%d %H:%M' |
|
|
|
|
format="%(asctime)s %(name)-12s %(levelname)-8s %(message)s", |
|
|
|
|
datefmt="%m-%d %H:%M" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def hash_uid(uid: str, salt: str) -> str: |
|
|
|
|
return UID_HASH(bytes.fromhex(uid), salt=salt.encode()).hex() |
|
|
|
|
|
|
|
|
|
return scrypt(bytes.fromhex(uid), |
|
|
|
|
salt=salt.encode(), |
|
|
|
|
n=16384, |
|
|
|
|
r=8, |
|
|
|
|
p=1).hex() |
|
|
|
|
|
|
|
|
|
class Main: |
|
|
|
|
|
|
|
|
|
class DoorController: |
|
|
|
|
def __init__(self, |
|
|
|
|
door, |
|
|
|
|
api_allowed, |
|
|
|
@ -50,7 +45,8 @@ class Main: |
|
|
|
|
self.sync_cards() |
|
|
|
|
logging.info("Running") |
|
|
|
|
self.wiegand = Decoder(self.wiegand_callback) |
|
|
|
|
self.notify_thread = threading.Thread(target=self.listen_notification, daemon=True) |
|
|
|
|
self.notify_thread = threading.Thread(target=self.listen_notification, |
|
|
|
|
daemon=True) |
|
|
|
|
self.notify_thread.start() |
|
|
|
|
self.auto_sync_loop() |
|
|
|
|
|
|
|
|
@ -59,9 +55,9 @@ class Main: |
|
|
|
|
try: |
|
|
|
|
r = self.session.get(self.api_allowed, timeout=15) |
|
|
|
|
allowed_uids = r.json()["allowed_uids"] |
|
|
|
|
except JSONDecodeError as e: |
|
|
|
|
except JSONDecodeError: |
|
|
|
|
logging.exception("Failed to decode allowed uids json") |
|
|
|
|
except (requests.Timeout, requests.ConnectionError) as e: |
|
|
|
|
except (requests.Timeout, requests.ConnectionError): |
|
|
|
|
logging.exception("Connection timeout/error in sync cards") |
|
|
|
|
except Exception: |
|
|
|
|
logging.exception("Some other exception") |
|
|
|
@ -72,7 +68,6 @@ class Main: |
|
|
|
|
self.uids = uids |
|
|
|
|
|
|
|
|
|
def wiegand_callback(self, bits, value): |
|
|
|
|
#print("bits", bits, "value", value) |
|
|
|
|
uid_h = hash_uid(value, self.uid_hash) |
|
|
|
|
logging.debug("hash %s", uid_h) |
|
|
|
|
if uid_h in self.uids: |
|
|
|
@ -80,7 +75,8 @@ class Main: |
|
|
|
|
self.wiegand.open_door() |
|
|
|
|
success = True |
|
|
|
|
else: |
|
|
|
|
logging.info("Access card not in allow list, hash trail %s", uid_h[-10:]) |
|
|
|
|
logging.info("Access card not in allow list, hash trail %s", |
|
|
|
|
uid_h[-10:]) |
|
|
|
|
success = False |
|
|
|
|
data = { |
|
|
|
|
"uid": value, |
|
|
|
@ -92,7 +88,7 @@ class Main: |
|
|
|
|
data["success"] = success |
|
|
|
|
try: |
|
|
|
|
requests.post(self.api_swipe, data=data, timeout=15) |
|
|
|
|
except (requests.Timeout, requests.ConnectionError) as e: |
|
|
|
|
except (requests.Timeout, requests.ConnectionError): |
|
|
|
|
logging.exception("Connection timeout/error in post swipes") |
|
|
|
|
except Exception: |
|
|
|
|
logging.exception("Some other exception") |
|
|
|
@ -101,8 +97,8 @@ class Main: |
|
|
|
|
while 1: |
|
|
|
|
try: |
|
|
|
|
r = self.session.get(self.api_longpoll, |
|
|
|
|
headers={"Connection": "close"}, |
|
|
|
|
timeout=60, stream=True) |
|
|
|
|
headers={"Connection": "close"}, |
|
|
|
|
timeout=60, stream=True) |
|
|
|
|
for line in r.iter_lines(1, decode_unicode=True): |
|
|
|
|
if not line.strip(): |
|
|
|
|
continue |
|
|
|
@ -112,19 +108,28 @@ class Main: |
|
|
|
|
self.wiegand.open_door() |
|
|
|
|
self.force_sync_now.set() |
|
|
|
|
except (requests.Timeout, |
|
|
|
|
requests.ConnectionError) as e: |
|
|
|
|
requests.ConnectionError): |
|
|
|
|
logging.debug("notification timeout") |
|
|
|
|
except Exception: |
|
|
|
|
logging.exception("Some other exception") |
|
|
|
|
time.sleep(0.1) |
|
|
|
|
|
|
|
|
|
def auto_sync_loop(self): |
|
|
|
|
|
|
|
|
|
while 1: |
|
|
|
|
while True: |
|
|
|
|
try: |
|
|
|
|
self.force_sync_now.wait(60*10) # == 10min |
|
|
|
|
self.force_sync_now.clear() |
|
|
|
|
self.sync_cards() |
|
|
|
|
except KeyboardInterrupt as e: |
|
|
|
|
except KeyboardInterrupt: |
|
|
|
|
self.wiegand.cancel() |
|
|
|
|
break |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
DoorController( |
|
|
|
|
os.environ["KDOORPI_DOOR"], |
|
|
|
|
os.environ["KDOORPI_API_ALLOWED"], |
|
|
|
|
os.environ["KDOORPI_API_LONGPOLL"], |
|
|
|
|
os.environ["KDOORPI_API_SWIPE"], |
|
|
|
|
os.environ["KDOORPI_API_KEY"], |
|
|
|
|
os.environ["KDOORPI_UID_SALT"]) |