BAPSicle/controllers/mattchbox_usb.py

145 lines
5.7 KiB
Python
Raw Normal View History

from helpers.the_terminator import Terminator
from typing import List, Optional
2021-04-08 21:21:28 +00:00
from multiprocessing import Queue, current_process
2021-02-14 23:58:32 +00:00
import serial
import time
from setproctitle import setproctitle
2021-04-08 21:21:28 +00:00
from helpers.logging_manager import LoggingManager
from helpers.state_manager import StateManager
from controllers.controller import Controller
2021-02-14 23:58:32 +00:00
2021-04-08 21:32:16 +00:00
2021-04-08 19:53:51 +00:00
class MattchBox(Controller):
ser: Optional[serial.Serial]
port: Optional[str]
next_port: Optional[str]
server_state: StateManager
logger: LoggingManager
2021-02-14 23:58:32 +00:00
2021-04-08 19:53:51 +00:00
def __init__(
self, player_to_q: List[Queue], player_from_q: Queue, state: StateManager
2021-04-08 19:53:51 +00:00
):
2021-02-14 23:58:32 +00:00
process_title = "BAPSicle - ControllerHandler"
2021-04-08 19:53:51 +00:00
setproctitle(process_title)
2021-04-18 20:17:41 +00:00
current_process().name = process_title
self.ser = None
2021-04-08 19:53:51 +00:00
self.logger = LoggingManager("ControllerMattchBox")
2021-04-08 19:53:51 +00:00
self.server_state = state # This is a copy, will not update :/
2021-04-08 21:21:28 +00:00
# This doesn't run, the callback function gets lost
# due to state being a copy in the multiprocessing process.
# self.server_state.add_callback(self._state_handler)
# Allow server config changes to trigger controller reload if required.
2021-04-08 19:53:51 +00:00
self.port = None
self.next_port = self.server_state.get()["serial_port"]
self.logger.log.info("Server config gives port as: {}".format(self.next_port))
2021-04-08 19:53:51 +00:00
self.player_from_q = player_from_q
self.player_to_q = player_to_q
2021-04-08 19:53:51 +00:00
self.handler()
# This doesn't run, the callback function gets lost in StateManager.
2021-04-08 19:53:51 +00:00
def _state_handler(self):
new_port = self.server_state.get()["serial_port"]
2021-04-08 19:53:51 +00:00
self.logger.log.info("Got server config update. New port: {}".format(new_port))
if new_port != self.port:
self.logger.log.info(
"Switching from port {} to {}".format(self.port, new_port)
)
# The serial port config has changed. Let's reload the serial.
self.port = None
self.next_port = new_port
def _disconnected(self):
# If we lose the controller, make sure to set channels live, so we tracklist.
for i in range(len(self.player_to_q)):
self.sendToPlayer(i, "SETLIVE:True")
self.server_state.update("ser_connected", False)
def connect(self, port: Optional[str]):
2021-04-08 19:53:51 +00:00
if port:
# connect to serial port
self.ser = serial.serial_for_url(port, do_not_open=True)
if self.ser:
self.ser.baudrate = 2400
2022-03-12 17:08:29 +00:00
self.ser.timeout = 0.1 # Speed up waiting for a byte.
try:
self.ser.open()
self.logger.log.info("Connected to serial port {}".format(port))
except (FileNotFoundError, serial.SerialException) as e:
self.logger.log.error(
"Could not open serial port {}:\n{}".format(port, e)
)
self._disconnected()
self.ser = None
2021-04-08 19:53:51 +00:00
else:
self.ser = None
2021-02-14 23:58:32 +00:00
2021-04-08 19:53:51 +00:00
def handler(self):
terminator = Terminator()
while not terminator.terminate:
2021-04-08 19:53:51 +00:00
if (
self.ser and self.ser.is_open and self.port
): # If self.port is changing (via state_handler), we should stop.
try:
if self.ser.in_waiting > 0:
line = int.from_bytes(
self.ser.read(1), "big"
) # Endianness doesn't matter for 1 byte.
self.logger.log.info("Received from controller: " + str(line))
if line == 255:
self.ser.write(b"\xff") # Send 255 back, this is a keepalive.
elif line in [51, 52, 53]:
# We've received a status update about fader live status, fader is down.
self.sendToPlayer(line - 51, "SETLIVE:False")
elif line in [61, 62, 63]:
# We've received a status update about fader live status, fader is up.
self.sendToPlayer(line - 61, "SETLIVE:True")
elif line in [1, 3, 5]:
self.sendToPlayer(int(line / 2), "PLAYPAUSE")
elif line in [2, 4, 6]:
self.sendToPlayer(int(line / 2) - 1, "STOP")
2021-04-08 21:32:16 +00:00
except Exception:
2021-05-02 17:12:38 +00:00
time.sleep(5)
self.connect(self.port)
2021-04-08 19:53:51 +00:00
finally:
time.sleep(0.01)
elif self.port:
# If there's still a port set, just wait a moment and see if it's been reconnected.
self._disconnected()
2021-04-08 19:53:51 +00:00
time.sleep(10)
self.connect(self.port)
else:
# We're not already connected, or a new port connection is to be made.
if self.ser:
self.ser.close()
self._disconnected()
2021-04-08 19:53:51 +00:00
2021-04-08 21:21:28 +00:00
if self.next_port is not None:
2021-04-08 19:53:51 +00:00
self.connect(self.next_port)
if self.ser and self.ser.is_open:
self.port = (
self.next_port
) # We connected successfully, make it stick.
self.server_state.update("ser_connected", True)
continue # skip the sleep.
time.sleep(10)
self.connect(None)
2021-04-08 19:53:51 +00:00
def sendToPlayer(self, channel: int, msg: str):
2021-09-11 15:49:08 +00:00
self.logger.log.info(
"Sending message to player channel {}: {}".format(channel, msg)
)
self.player_to_q[channel].put("CONTROLLER:" + msg)