DoorControl/door_pi_control/nfc.py

128 lines
5.1 KiB
Python

import select
import socket
import time
from threading import Lock, RLock, Condition, Thread
from typing import Callable, Optional, IO, Container
from . import mqtt, util
from .util import timestamp
class DoorControlNfc(util.Loggable):
"""Validates tokens read from a socket and tells a DoorControl to toggle its target"""
def __init__(self, config, control, mqtt_client = None):
self._config = config
self._nfc = self._open_nfc_fifo()
self._control = control
self._stop = True
self._mutex = RLock()
self._cond = Condition(self._mutex)
self._fifo = None
self._mqttc = mqtt_client
self._read_valid_tokens()
self.last_invalid_token = mqtt.Value(self._mqttc,
"door/token/last_invalid", persistent=True)
def _open_nfc_fifo(self) -> IO:
"""Opens config.nfc_fifo as the FIFO through which detected tokens are passed in."""
self._logger().debug(f"Opening FIFO {self._config.nfc_fifo}")
return open(self._config.nfc_fifo, "rb")
def _read_valid_tokens(self) -> Container:
"""Refreshes all tokens from config.valid_tokens"""
with self._mutex:
valid = {}
try:
self._logger().info("Loading tokens")
lines =[ s.strip() for s in open(self._config.valid_tokens, "r").readlines() ]
for i, line in enumerate(lines):
l = line.split('|')
if len(l) == 5:
if not l[0].strip().startswith('#'):
token, name, organization, email, valid_thru = l
try:
if len(valid_thru.strip()) > 0:
valid_thru = datetime.date.fromisoformat(valid_thru)
else:
valid_thru = None
except Exception:
self._logger().error(f"Could not parse valid thru date for token {token} in line {i}")
valid_thru = None
self._logger().debug(f"Got token {token} associated with {name} <{email}> of {organization}, valid thru {valid_thru}")
if token in valid:
self._logger().warning(f"Overwriting token {token}")
valid[token] = {
'name': name,
'organization': organization,
'email': email,
'valid_thru': valid_thru
}
else:
self._logger().warning(f"Skipping line {i} ({line}) since it does not contain exactly 5 data field")
except Exception as e:
valid = {}
self._logger().error(f"Error reading token file. Exception: {e}")
self._tokens = valid
def _run(self):
with self._cond:
while not self._stop:
if not self._fifo:
self._read_valid_tokens()
self._fifo = self._open_nfc_fifo()
if not self._fifo:
time.sleep(5)
continue
self._cond.release()
readable, _, _ = select.select(
[self._fifo],
[], [], 1)
self._cond.acquire()
if self._fifo in readable:
token = self._fifo.readline().strip()
if token == "":
self._fifo = None
continue
self._logger().debug(f"Token from nfc_fifo: {token}")
self.handle_token(token)
def handle_token(self, token):
with self._mutex:
if token in self._tokens:
data = self._tokens[token]
if data['valid_thru'] is not None:
# if a valid thru date has been set we check if the token is still valid
authorized = datetime.date.today() <= data['valid_thru']
else:
# otherwise we don't need to check
authorized = True
if authorized:
self._logger().info(f"Valid token {token} of {data['name']}")
self._control.toggle()
else:
self._logger().warning(f"Token {token} of {data['name']} expired on {data['valid_thru']}")
else:
self._logger().warning(f"Invalid token: {token}")
self.last_invalid_token(f"{timestamp()};{token}")
def start(self):
with self._mutex:
if self._stop:
self._stop = False
self._task = Thread(target=self._run, daemon=True)
self._task.start()
print("Done")
def stop(self):
with self._cond:
if not self._stop:
self._stop = True
self._cond.notify()
self._task.join()