157 lines
5.4 KiB
Python
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()
|