DoorControl/door_pi_control/door/communication.py

145 lines
4.1 KiB
Python
Raw Normal View History

2022-11-06 13:30:11 +00:00
import os
import select
import serial
import time
from threading import Lock, RLock, Thread
from typing import IO, Dict, Callable, Union
from ..util import Loggable
class Communication(Loggable):
def __init__(self, port: Union[os.PathLike, IO]):
self._mutex: RLock = RLock()
self._write_mutex: Lock = Lock()
self._subscribers: Dict[str, Callable] = {}
self._read_pipe, self._write_pipe = os.pipe()
os.set_blocking(self._read_pipe, False)
self._read_pipe = os.fdopen(self._read_pipe, 'rb', buffering=0, )
self._write_pipe = os.fdopen(self._write_pipe, 'wb', buffering=0)
self._port_path = port
self._serial_port = None
self._open_serial_port()
self._stopped = True
self._task = None
def _forward(self) -> None:
data = self._read_pipe.read()
while True:
try:
return self._write(data)
except serial.serialutil.SerialException:
self._open_serial_port()
def _open_serial_port(self) -> None:
self._logger().debug("Opening port")
port = self._port_path
if type(port) == str:
i = 0
while i < 30 and not os.path.exists(port):
time.sleep(1)
i += 1
self._serial_port = serial.Serial(port, timeout=2)
else:
self._serial_port = port
def _process(self):
try:
line = self._read()
except serial.serialutil.SerialException:
line = None
if line == None:
self._logger().info("Empty line, reopening serial port")
self._open_serial_port()
return
if ':' not in line:
self._logger().debug(f"Line: {line}")
self._send_event("", line.strip())
return
try:
prefix, data = line.split(":", maxsplit=2)
if not prefix.startswith("pos"):
self._logger().debug(f"Line: {line}")
self._send_event(prefix, data.strip())
except:
pass
def _read(self) -> str:
data = self._serial_port.readline()
if data == "":
return None
return data.decode("ascii").strip()
def _run(self):
while True:
with self._mutex:
if self._stopped:
return
readable, _, _ = select.select(
[self._serial_port, self._read_pipe],
[], [], 0.1)
for r in readable:
if r == self._serial_port:
self._process()
elif r == self._read_pipe:
self._forward()
def _send_event(self, prefix, data):
with self._mutex:
if prefix in self._subscribers:
for sub in self._subscribers[prefix]:
sub(prefix, data)
def _write(self, data: bytes) -> None:
with self._write_mutex:
self._logger().debug(f"Sending {repr(data)}")
if self._serial_port:
self._serial_port.write(data)
def start(self):
with self._mutex:
self._logger().debug("Starting")
if self._task is None:
self._stopped = False
self._task = Thread(target=self._run, daemon=True)
self._task.start()
def stop(self) -> None:
self._mutex.acquire()
if self._task is not None:
self._stopped = True
self._mutex.release()
self._task.join()
self._mutex.acquire()
self._task = None
self._mutex.release()
def subscribe(self, prefix: str, callback: Callable):
with self._mutex:
if prefix not in self._subscribers:
self._subscribers[prefix] = []
self._subscribers[prefix].append(callback)
def write(self, data):
with self._write_mutex:
self._write_pipe.write(data)
def cmd_open(self):
self.write(b"O")
def cmd_close(self):
self.write(b"C")
def cmd_report(self):
self.write(b">r1\nR")
def cmd_restart(self):
self.write(b"S")