import os 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 from .door.constants import state_names class DoorControlSocket(util.Loggable): class LineBuffer(util.Loggable): def __init__(self, f, handler): super().__init__("socket", "DoorControlSocket") self.data = b'' self.f = f self.handler = handler def update(self): data = self.f.recv(1024) print(repr(data)) if not data: self._logger().info("Socket closed") return False self.data += data d = self.data.split(b'\n') d, self.data = d[:-1], d[-1] for i in d: self._logger().debug(f"Handling {repr(i)}") try: self.handler(self.f, i) except: self._logger().exception("Error while handling command") return True def __init__(self, config, control, bell, nfc): super().__init__("socket") self._config = config self._control = control self._bell = bell self._nfc = nfc self._fifo = self._open_control_socket() self._stop = True self._mutex = RLock() self._cond = Condition(self._mutex) def _open_control_socket(self) -> socket.socket: """(Re-)creates and opens the control socket. Config must have a control_socket member.""" path = self._config.control_socket self._logger().debug(f"Opening control socket {path}") if os.path.exists(path): os.unlink(path) s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.bind(path) s.listen(5) return s def _run(self): buffers = {} sockets = [] with self._cond: while not self._stop: if not self._fifo: self._fifo = self._open_control_socket() if not self._fifo: time.sleep(5) continue readable, _, _ = select.select( [self._fifo] + sockets, [], [], 1) for fd in readable: if fd == self._fifo: self._logger().debug("Got connection") sock = fd.accept()[0] buffers[sock] = DoorControlSocket.LineBuffer(sock, self.handle_cmd) sockets += [sock] else: if not buffers[fd].update(): del buffers[fd] sockets.remove(fd) def handle_cmd(self, comm, data): cmd = data.decode('utf8').split() try: cmd, args = cmd[0], cmd[1:] except: return self._logger().debug(f"Got command: {data}") send = lambda x: comm.send(x.encode('utf8')) if cmd == 'fake': self._logger().debug(f"Faking token {args[0]}") send("Handling token\n") self._nfc.handle_token(args[0]) elif cmd == 'reset': self._logger().info("Resetting") send("Resetting MCU") self._control._comms.cmd_restart() elif cmd == 'open': if len(args) > 0: self._logger().info(f"Control socket opening door for {args[0]}") send("Opening door") self._control.open() else: send("Missing login") elif cmd == 'close': if len(args) > 0: self._logger().info(f"Control socket closing door for {args[0]}") send("Closing door") self._control.close() else: send("Missing login") elif cmd == 'bell_open': send("Opening front door") self._bell._protocol.open_door() elif cmd == 'bell_light': send("Turning the light on") self._bell._protocol.send_light_msg() elif cmd == 'bell_perma_light': self._bell._protocol.doing_light ^= True send("Turning the light on permanently: {self._bell._protocol.doing_light}") elif cmd == 'bell_raw': if (len(args) % 4) != 0: send(f"Invalid number of bytes: {len(args)}") return data = bytes.fromhex(" ".join(args)) data = [ data[i:i+4] for i in range(0, len(data), 4) ] send(f"Sending {repr(data)}") self._bell._protocol.alternate_msgs(data) elif cmd == 'rld': self._logger().debug("Reloading tokens") send("Reloading tokens") self._nfc._read_valid_tokens() elif cmd == 'stat': send(f"Door status is {self._control.state.str()}, position is {self._control.position()}. Current action: {self._control.target.str()}\n") def start(self): with self._mutex: if self._stop: self._stop = False self._task = Thread(target=self._run, daemon=True) self._task.start() def stop(self): with self._cond: if not self._stop: self._stop = True self._cond.notify() self._task.join()