This commit is contained in:
Lynn Ochs 2021-02-27 19:01:39 +01:00
parent 5212cd45d6
commit e48f0f6338

154
door.py
View File

@ -1,3 +1,4 @@
#!/usr/bin/python
import os, serial, socket, subprocess, select, datetime, sys import os, serial, socket, subprocess, select, datetime, sys
import argparse import argparse
@ -14,7 +15,7 @@ config = parser.parse_args()
# Log data to stdout and the log file # Log data to stdout and the log file
def log(*args): def log(*args):
data = "%s %s" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), " ".join(i for i in args)) data = "%s %s" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), " ".join(str(i) for i in args))
lgf.write(data + "\n") lgf.write(data + "\n")
lgf.flush() lgf.flush()
print(data) print(data)
@ -59,10 +60,23 @@ def read_valid_tokens():
global config global config
try: try:
valid = [ s.strip() for s in open(config.valid_tokens, "r").readlines() ] log("Loading tokens")
valid = {}
lines =[ s.strip() for s in open(config.valid_tokens, "r").readlines() ]
for l in lines:
l = l.split(' ', 1)
if len(l) > 1:
if l[0] in valid:
log("Warning: Overwriting token %s" % (l[0],))
log("Got token: %s (%s)" % (l[0], l[1]))
valid[l[0]] = l[1]
else:
log("Got unnamed token: %s" % (l[0],))
valid[l[0]] = None
except: except:
valid = [] valid = {}
log("No valid tokens") log("Error reading token file")
raise
# Opens the log file for writing # Opens the log file for writing
def open_logfile(): def open_logfile():
@ -73,43 +87,51 @@ def open_logfile():
# Actions # Actions
IDLE, CLOSE, OPEN_THEN_CLOSE, OPEN, CLOSE_THEN_OPEN = range(5) IDLE, CLOSE, OPEN_THEN_CLOSE, OPEN, CLOSE_THEN_OPEN = range(5)
state_names = {
OPEN: "open",
CLOSE: "close"
}
action_names = {
IDLE: "idling",
OPEN: "waiting for open door",
CLOSE: "waiting for closed door",
OPEN_THEN_CLOSE: "waiting for open door, then closing again",
CLOSE_THEN_OPEN: "waiting for closed door, then opening again"
}
# Current door state # Current door state
state = None state = None
state_pos = 0
# Current target action # Current target action
action = CLOSE action = CLOSE
# Start time of the current action # Start time of the current action
start_time = datetime.datetime.now() start_time = None
# How often the action was repeated # How often the action was repeated
repeats = 0 repeats = 0
# Read the state of the door from the serial port # Read the state of the door from the serial port
def poll_door_state(): def poll_door_state():
global state global state
global state_pos
global serial_port global serial_port
serial_port.reset_input_buffer() serial_port.reset_input_buffer()
serial_port.write(b"R") serial_port.write(b"R")
data = serial_port.readline().strip().split() data = serial_port.readline().strip().split()
if data[0] == b'pos:': if data[0] == b'pos:':
data = int(data[1]) data = int(data[1])
state_pos = data
changed = False changed = False
if data < 80 and door_state != OPEN: if data < 80 and state != OPEN:
door_state = OPEN state = OPEN
changed = True changed = True
elif data > 100 and door_state != CLOSE: elif data > 100 and state != CLOSE:
door_state = CLOSE state = CLOSE
changed = True changed = True
else: return state
print(data)
return door_state
# Check the door state and send commands through a state machine # Check the door state and send commands through a state machine
def handle_door_state(): def handle_door_state():
global action, target, serial_port, start_time, repeats global action, target, serial_port, start_time, repeats
state_names = {
OPEN: "open",
CLOSE: "close"
}
# Commands associated with each target state # Commands associated with each target state
target_state_cmd = { target_state_cmd = {
OPEN: b'O', OPEN: b'O',
@ -129,7 +151,7 @@ def handle_door_state():
poll_door_state() poll_door_state()
# Idle + change = key? # Idle + change = key?
if target == IDLE: if action == IDLE:
if state != old_state: if state != old_state:
log("Door changed unexpectedly:", state_names[state]) log("Door changed unexpectedly:", state_names[state])
return return
@ -142,6 +164,10 @@ def handle_door_state():
CLOSE_THEN_OPEN: (CLOSE, OPEN, OPEN ) CLOSE_THEN_OPEN: (CLOSE, OPEN, OPEN )
} }
if start_time == None:
start_time = datetime.datetime.now()
serial_port.write(target_state_cmd[actions[action][0]])
# Target state reached # Target state reached
if state == actions[action][0]: if state == actions[action][0]:
# Select next action, reset start time and repetitions # Select next action, reset start time and repetitions
@ -154,13 +180,13 @@ def handle_door_state():
return return
# Execution time # Execution time
t = (datetime.datetime.now() - start_time) t = (datetime.datetime.now() - start_time).total_seconds()
if t >= config.state_timeout: if t >= config.state_timeout:
# Timeout -> switch to timeout action # Timeout -> switch to timeout action
action = actions[action][2] action = actions[action][2]
start_time = datetime.datetime.now() start_time = None
repeats = 0 repeats = 0
log("Timeout. Switching to", action) log("Timeout. Switching to", action_names[action])
elif t >= (1 + repeats) * config.repeat_time: elif t >= (1 + repeats) * config.repeat_time:
# Repeat every couple of seconds # Repeat every couple of seconds
repeats += 1 repeats += 1
@ -169,13 +195,19 @@ def handle_door_state():
def open_door(): def open_door():
global action global action
global start_time
log("Opening the door") log("Opening the door")
action = OPEN action = OPEN
start_time = None
handle_door_state()
def close_door(): def close_door():
global action global action
global start_time
log("Closing the door") log("Closing the door")
action = CLOSE action = CLOSE
start_time = None
handle_door_state()
def toggle_door_state(): def toggle_door_state():
global state, action global state, action
@ -184,17 +216,22 @@ def toggle_door_state():
else: else:
close_door() close_door()
def handle_nfc_token(): def handle_nfc_token(token = None):
global valid global valid
token = nfc_fifo.readline() if not token:
token = nfc_fifo.readline()
if token == "": if token == "":
open_nfc_fifo() open_nfc_fifo()
token = token.strip() token = token.strip()
if token in valid: if token in valid:
log("Valid token:", token) name = valid[token]
if name:
name = "%s (%s)" % (token, name)
else:
name = token
log("Valid token: %s" % name)
toggle_door_state() toggle_door_state()
else: else:
log("Invalid token:", token) log("Invalid token:", token)
@ -205,18 +242,69 @@ open_nfc_fifo()
open_serial_port() open_serial_port()
open_control_socket() open_control_socket()
class LineBuffer(object):
def __init__(self, f, handler):
self.data = b''
self.f = f
self.handler = handler
def update(self):
data = self.f.recv(1024)
if not data:
return False
self.data += data
d = self.data.split(b'\n')
d, self.data = d[:-1], d[-1]
for i in d:
self.handler(self.f, i)
return True
def handle_cmd(comm, data):
cmd = data.decode('utf8').split()
cmd, args = cmd[0], cmd[1:]
log("Got command:", data)
send = lambda x: comm.send(x.encode('utf8'))
if cmd == 'fake':
log("Faking token", args[0])
send("Handling token\n")
handle_nfc_token(args[0])
elif cmd == 'open':
log("Control socket opening door")
send("Opening door")
open_door()
elif cmd == 'close':
log("Control socket closing door")
send("Closing door")
close_door()
elif cmd == 'rld':
log("Reloading tokens")
send("Reloading tokens")
read_valid_tokens()
elif cmd == 'stat':
send("Door status is %s, position is %d. Current action: %s (%g seconds ago)\n" % (
state_names.get(state, "None"),
state_pos,
action_names[action],
(datetime.datetime.now() - start_time).total_seconds()))
buffers = {}
while True: while True:
readable = select.select([ nfc_fifo, control_socket ] + comm_channels, [], [], 0.5)[0] readable = select.select([ nfc_fifo, control_socket ] + comm_channels, [], [], 1)[0]
for c in readable: for c in readable:
if c == nfc_fifo: if c == nfc_fifo:
handle_nfc_token() handle_nfc_token()
elif c == control_socket: elif c == control_socket:
comm_channels.append(control_socket.accept()[0]) log("Got connection")
sock = control_socket.accept()[0]
buffers[sock] = LineBuffer(sock, handle_cmd)
comm_channels += [sock]
else: else:
data = c.recv(1024) if not buffers[c].update():
if len(data) == 0: log("Lost connection")
del buffers[c]
comm_channels.remove(c) comm_channels.remove(c)
else: handle_door_state()
print("Got comms data: ", str(c.recv(1024)))
c.sendall(b':)\n')