DoorControl/door_pi_control/socket.py

157 lines
5.4 KiB
Python

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(f"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) ]
self._logger().debug(f"Raw data: {data}")
send(f"Sending {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()