diff --git a/door.py b/door.py index 61a5256..62e4e92 100644 --- a/door.py +++ b/door.py @@ -1,3 +1,4 @@ +#!/usr/bin/python import os, serial, socket, subprocess, select, datetime, sys import argparse @@ -14,7 +15,7 @@ config = parser.parse_args() # Log data to stdout and the log file 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.flush() print(data) @@ -59,10 +60,23 @@ def read_valid_tokens(): global config 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: - valid = [] - log("No valid tokens") + valid = {} + log("Error reading token file") + raise # Opens the log file for writing def open_logfile(): @@ -73,43 +87,51 @@ def open_logfile(): # Actions 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 state = None +state_pos = 0 # Current target action action = CLOSE # Start time of the current action -start_time = datetime.datetime.now() +start_time = None # How often the action was repeated repeats = 0 # Read the state of the door from the serial port def poll_door_state(): global state + global state_pos global serial_port serial_port.reset_input_buffer() serial_port.write(b"R") data = serial_port.readline().strip().split() if data[0] == b'pos:': data = int(data[1]) + state_pos = data changed = False - if data < 80 and door_state != OPEN: - door_state = OPEN + if data < 80 and state != OPEN: + state = OPEN changed = True - elif data > 100 and door_state != CLOSE: - door_state = CLOSE + elif data > 100 and state != CLOSE: + state = CLOSE changed = True - else: - print(data) - return door_state + return state # Check the door state and send commands through a state machine def handle_door_state(): global action, target, serial_port, start_time, repeats - state_names = { - OPEN: "open", - CLOSE: "close" - } # Commands associated with each target state target_state_cmd = { OPEN: b'O', @@ -129,7 +151,7 @@ def handle_door_state(): poll_door_state() # Idle + change = key? - if target == IDLE: + if action == IDLE: if state != old_state: log("Door changed unexpectedly:", state_names[state]) return @@ -142,6 +164,10 @@ def handle_door_state(): 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 if state == actions[action][0]: # Select next action, reset start time and repetitions @@ -154,13 +180,13 @@ def handle_door_state(): return # Execution time - t = (datetime.datetime.now() - start_time) + t = (datetime.datetime.now() - start_time).total_seconds() if t >= config.state_timeout: # Timeout -> switch to timeout action action = actions[action][2] - start_time = datetime.datetime.now() + start_time = None repeats = 0 - log("Timeout. Switching to", action) + log("Timeout. Switching to", action_names[action]) elif t >= (1 + repeats) * config.repeat_time: # Repeat every couple of seconds repeats += 1 @@ -169,13 +195,19 @@ def handle_door_state(): def open_door(): global action + global start_time log("Opening the door") action = OPEN + start_time = None + handle_door_state() def close_door(): global action + global start_time log("Closing the door") action = CLOSE + start_time = None + handle_door_state() def toggle_door_state(): global state, action @@ -184,17 +216,22 @@ def toggle_door_state(): else: close_door() -def handle_nfc_token(): +def handle_nfc_token(token = None): global valid - token = nfc_fifo.readline() - - if token == "": - open_nfc_fifo() + if not token: + token = nfc_fifo.readline() + if token == "": + open_nfc_fifo() token = token.strip() 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() else: log("Invalid token:", token) @@ -205,18 +242,69 @@ open_nfc_fifo() open_serial_port() 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: - 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: if c == nfc_fifo: handle_nfc_token() 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: - data = c.recv(1024) - if len(data) == 0: + if not buffers[c].update(): + log("Lost connection") + del buffers[c] comm_channels.remove(c) - else: - print("Got comms data: ", str(c.recv(1024))) - c.sendall(b':)\n') + handle_door_state()