2021-03-07 22:19:44 +00:00
|
|
|
import paho.mqtt.client as mqtt
|
2021-03-21 14:53:33 +00:00
|
|
|
import socket
|
2021-03-14 13:18:59 +00:00
|
|
|
from pathlib import Path
|
2021-03-27 15:41:51 +00:00
|
|
|
import logging
|
2021-08-24 16:48:54 +00:00
|
|
|
from datetime import datetime
|
2021-03-07 22:19:44 +00:00
|
|
|
|
2022-02-02 20:39:52 +00:00
|
|
|
|
2021-03-08 20:38:02 +00:00
|
|
|
class DoorHandle:
|
2022-02-02 20:39:52 +00:00
|
|
|
def __init__(self, token_file, mqtt_host, mqtt_port=1883,
|
|
|
|
nfc_socket='/tmp/nfc.sock', logger=None):
|
2021-03-07 22:19:44 +00:00
|
|
|
self.state = None
|
|
|
|
self.encoder_position = None
|
|
|
|
|
2021-03-14 13:18:59 +00:00
|
|
|
if not Path(token_file).exists():
|
2022-02-02 20:39:52 +00:00
|
|
|
raise FileNotFoundError(
|
|
|
|
"File with door tokens could not be found at "
|
|
|
|
f"{Path(token_file).absolute()}"
|
|
|
|
)
|
2021-03-14 13:18:59 +00:00
|
|
|
|
|
|
|
self.token_file = token_file
|
|
|
|
self.last_invalid = {}
|
2021-03-07 22:19:44 +00:00
|
|
|
|
2021-03-27 15:41:51 +00:00
|
|
|
if logger:
|
|
|
|
self.logger = logger
|
|
|
|
else:
|
|
|
|
self.logger = logging
|
|
|
|
|
2021-03-21 14:53:33 +00:00
|
|
|
self.nfc_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
|
|
try:
|
|
|
|
self.nfc_sock.connect(nfc_socket)
|
2021-03-27 15:41:51 +00:00
|
|
|
self.logger.info(f"Connected to NFC socket at {nfc_socket}.")
|
2021-03-21 14:53:33 +00:00
|
|
|
except Exception as e:
|
2022-02-02 20:39:52 +00:00
|
|
|
print(f"Could not connect to NFC socket at {nfc_socket}. "
|
|
|
|
f"Exception: {e}")
|
2021-03-27 15:41:51 +00:00
|
|
|
self.nfc_sock = None
|
2021-03-21 14:53:33 +00:00
|
|
|
#raise
|
|
|
|
|
2022-02-02 20:39:52 +00:00
|
|
|
self.mqtt_client = mqtt.Client()
|
|
|
|
self.mqtt_client.on_connect = self.on_connect
|
|
|
|
self.mqtt_client.on_message = self.on_message
|
|
|
|
self.mqtt_client.connect_async(host=mqtt_host, port=mqtt_port)
|
|
|
|
self.mqtt_client.loop_start()
|
|
|
|
|
2021-03-07 22:19:44 +00:00
|
|
|
self.data_fields = ['name', 'organization', 'email', 'valid_thru']
|
|
|
|
|
2022-02-02 20:39:52 +00:00
|
|
|
|
|
|
|
pass
|
|
|
|
|
2021-03-07 22:19:44 +00:00
|
|
|
# The callback for when the client receives a CONNACK response from the server.
|
|
|
|
def on_connect(self, client, userdata, flags, rc):
|
2021-03-27 15:41:51 +00:00
|
|
|
self.logger.info("Connected to MQTT broker with result code " + str(rc))
|
2021-03-07 22:19:44 +00:00
|
|
|
|
|
|
|
# Subscribing in on_connect() means that if we lose the connection and
|
|
|
|
# reconnect then subscriptions will be renewed.
|
2022-02-02 20:39:52 +00:00
|
|
|
client.subscribe("door/#")
|
|
|
|
|
|
|
|
pass
|
2021-03-07 22:19:44 +00:00
|
|
|
|
|
|
|
# The callback for when a PUBLISH message is received from the server.
|
|
|
|
def on_message(self, client, userdata, msg):
|
2021-03-21 14:53:33 +00:00
|
|
|
#print(msg.topic + " " + str(msg.payload))
|
2021-03-07 22:19:44 +00:00
|
|
|
if msg.topic == 'door/state/value':
|
|
|
|
self.state = msg.payload.decode()
|
|
|
|
elif msg.topic == 'door/position/value':
|
|
|
|
self.encoder_position = int(msg.payload)
|
2021-03-14 13:18:59 +00:00
|
|
|
elif msg.topic == 'door/token/last_invalid':
|
|
|
|
timestamp, token = msg.payload.decode().split(";")
|
2021-08-24 16:48:54 +00:00
|
|
|
timestamp = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S')
|
2021-03-14 13:18:59 +00:00
|
|
|
self.last_invalid = {'timestamp': timestamp, 'token': token}
|
2021-03-07 22:19:44 +00:00
|
|
|
|
|
|
|
def get_tokens(self):
|
|
|
|
tokens = {}
|
|
|
|
with open(self.token_file) as f:
|
|
|
|
token_lines = f.readlines()
|
|
|
|
for line in token_lines[1:]:
|
|
|
|
data = line.split('|')
|
|
|
|
inactive = '#' in data[0]
|
|
|
|
token = data[0].strip().strip('#')
|
|
|
|
name = data[1].strip() if len(data) > 1 else None
|
|
|
|
organization = data[2].strip() if len(data) > 2 else None
|
|
|
|
email = data[3].strip() if len(data) > 3 else None
|
|
|
|
valid_thru = data[4].strip() if len(data) > 4 else None
|
|
|
|
|
|
|
|
tokens[token] = {'name': name,
|
|
|
|
'organization': organization,
|
|
|
|
'email': email,
|
|
|
|
'valid_thru': valid_thru,
|
|
|
|
'inactive': inactive}
|
|
|
|
return tokens
|
|
|
|
|
|
|
|
def store_tokens(self, tokens):
|
|
|
|
output = '# token | ' + ' | '.join(self.data_fields) + '\n'
|
|
|
|
|
|
|
|
for t, data in tokens.items():
|
|
|
|
output += '#' if data['inactive'] else ''
|
|
|
|
output += t
|
|
|
|
for key in self.data_fields:
|
|
|
|
output += '|'
|
|
|
|
output += data[key] if data[key] else ''
|
|
|
|
output += '\n'
|
2021-03-21 14:53:33 +00:00
|
|
|
# write new tokens to file and trigger reload
|
2021-03-07 22:19:44 +00:00
|
|
|
with open(self.token_file, 'w') as f:
|
|
|
|
f.write(output)
|
2021-03-27 15:41:51 +00:00
|
|
|
|
|
|
|
if self.nfc_sock is not None:
|
|
|
|
self.nfc_sock.send(b'rld\n')
|
2021-03-21 14:53:33 +00:00
|
|
|
|
2021-08-23 19:10:43 +00:00
|
|
|
def open_door(self, user=''):
|
2021-03-27 15:41:51 +00:00
|
|
|
if self.nfc_sock is not None:
|
2021-08-23 19:10:43 +00:00
|
|
|
self.nfc_sock.send(b'open ' + user.encode() + b'\n')
|
2021-03-27 15:41:51 +00:00
|
|
|
else:
|
2022-02-02 20:39:52 +00:00
|
|
|
raise RuntimeError("No connection to NFC socket. Cannot close door!")
|
2021-03-21 14:53:33 +00:00
|
|
|
|
2021-08-23 19:10:43 +00:00
|
|
|
def close_door(self, user=''):
|
2021-03-27 15:41:51 +00:00
|
|
|
if self.nfc_sock is not None:
|
2021-08-23 19:10:43 +00:00
|
|
|
self.nfc_sock.send(b'close ' + user.encode() + b'\n')
|
2021-03-27 15:41:51 +00:00
|
|
|
else:
|
2022-02-02 20:39:52 +00:00
|
|
|
raise RuntimeError("No connection to NFC socket. Cannot close door!")
|
2021-03-07 22:19:44 +00:00
|
|
|
|
|
|
|
def get_most_recent_token(self):
|
|
|
|
# read last invalid token from logfile
|
2021-03-14 13:18:59 +00:00
|
|
|
return self.last_invalid
|