diff --git a/README.md b/README.md index 315471d..ec78a6c 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,8 @@ Browse to `http://localhost:8000` The DCC++ EX connector exposes an Arduino board running DCC++ EX Command Station, connected via serial port, to the network, allowing commands to be sent via a -TCP socket. +TCP socket. A response generated by the DCC++ EX board is sent to all connected clients, +providing synchronization between multiple clients (eg. multiple JMRI instances). Its use is not needed when running DCC++ EX from a [WiFi](https://dcc-ex.com/get-started/wifi-setup.html) capable board (like when using an ESP8266 module or a [Mega+WiFi board](https://dcc-ex.com/advanced-setup/supported-microcontrollers/wifi-mega.html)). @@ -112,7 +113,7 @@ Settings may need to be customized based on your setup. ```bash $ cd daemons $ podman build -t dcc/net-to-serial . -$ podman run -d -p 2560:2560 dcc/net-to-serial +$ podman run --group-add keep-groups --device /dev/ttyACM0 -p 2560:2560 dcc/net-to-serial ``` ### Manual setup diff --git a/arduino/CommandStation-EX b/arduino/CommandStation-EX index aca9c9c..7311f2c 160000 --- a/arduino/CommandStation-EX +++ b/arduino/CommandStation-EX @@ -1 +1 @@ -Subproject commit aca9c9c941185a57bf1dd7d4a0c68ef199248083 +Subproject commit 7311f2ce64b66c0703859e786717df57d7cc1eb7 diff --git a/arduino/arduino-cli b/arduino/arduino-cli index 76251df..940c945 160000 --- a/arduino/arduino-cli +++ b/arduino/arduino-cli @@ -1 +1 @@ -Subproject commit 76251df9241a7e09108bbc681d7455a024bccd13 +Subproject commit 940c94573b1f446c2aa7f2011f123550e068d9e4 diff --git a/arduino/dcc-ex.github.io b/arduino/dcc-ex.github.io index d1e5c92..f74f30d 160000 --- a/arduino/dcc-ex.github.io +++ b/arduino/dcc-ex.github.io @@ -1 +1 @@ -Subproject commit d1e5c92c7bf575e46dcc3916c832c93222a86785 +Subproject commit f74f30debc2688769ae2d5b50330a6aab3a8a985 diff --git a/daemons/Dockerfile b/daemons/Dockerfile index cea2369..e7817c7 100644 --- a/daemons/Dockerfile +++ b/daemons/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10-alpine +FROM python:3.11-alpine RUN mkdir /opt/dcc && pip -q install pyserial ADD net-to-serial.py config.ini /opt/dcc diff --git a/daemons/README.md b/daemons/README.md new file mode 100644 index 0000000..e8dbf61 --- /dev/null +++ b/daemons/README.md @@ -0,0 +1,3 @@ +## DCC++ EX connector + +See [README.md](../README.md) diff --git a/daemons/config.ini b/daemons/config.ini index f470991..bff0dcd 100644 --- a/daemons/config.ini +++ b/daemons/config.ini @@ -2,6 +2,7 @@ LogLevel = debug ListeningIP = 0.0.0.0 ListeningPort = 2560 +MaxClients = 10 [Serial] # UNO diff --git a/daemons/net-to-serial.py b/daemons/net-to-serial.py index fda59cd..cb24d91 100755 --- a/daemons/net-to-serial.py +++ b/daemons/net-to-serial.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 import re -import time import logging import serial import asyncio @@ -10,11 +9,15 @@ from pathlib import Path class SerialDaemon: + connected_clients = set() + def __init__(self, config): self.ser = serial.Serial( config["Serial"]["Port"], - timeout=int(config["Serial"]["Timeout"])/1000) + timeout=int(config["Serial"]["Timeout"]) / 1000, + ) self.ser.baudrate = config["Serial"]["Baudrate"] + self.max_clients = int(config["Daemon"]["MaxClients"]) def __del__(self): try: @@ -43,19 +46,32 @@ class SerialDaemon: async def handle_echo(self, reader, writer): """Process a request from socket and return the response""" - while 1: # keep connection to client open - data = await reader.read(100) - if not data: # client has disconnected - break + logging.info( + "Clients already connected: {} (max: {})".format( + len(self.connected_clients), + self.max_clients, + ) + ) - addr = writer.get_extra_info('peername') - logging.info("Received {} from {}".format(data, addr[0])) - - self.__write_serial(data) - response = self.__read_serial() - writer.write(response) - await writer.drain() - logging.info("Sent: {}".format(response)) + addr = writer.get_extra_info("peername")[0] + if len(self.connected_clients) < self.max_clients: + self.connected_clients.add(writer) + while True: # keep connection to client open + data = await reader.read(100) + if not data: # client has disconnected + break + logging.info("Received {} from {}".format(data, addr)) + self.__write_serial(data) + response = self.__read_serial() + for client in self.connected_clients: + client.write(response) + await client.drain() + logging.info("Sent: {}".format(response)) + self.connected_clients.remove(writer) + else: + logging.warning( + "TooManyClients: client {} disconnected".format(addr) + ) writer.close() await writer.wait_closed() @@ -68,33 +84,37 @@ class SerialDaemon: while "DCC-EX" not in line: line = self.__read_serial().decode() board = re.findall(r"", line)[0] - return(board) + return board async def main(): config = configparser.ConfigParser() config.read( - Path(__file__).resolve().parent / "config.ini") # mimick os.path.join + Path(__file__).resolve().parent / "config.ini" + ) # mimick os.path.join logging.basicConfig(level=config["Daemon"]["LogLevel"].upper()) sd = SerialDaemon(config) server = await asyncio.start_server( sd.handle_echo, config["Daemon"]["ListeningIP"], - config["Daemon"]["ListeningPort"]) + config["Daemon"]["ListeningPort"], + ) addr = server.sockets[0].getsockname() - logging.warning("Serving on {} port {}".format(addr[0], addr[1])) - logging.warning( + logging.info("Serving on {} port {}".format(addr[0], addr[1])) + logging.info( "Proxying to {} (Baudrate: {}, Timeout: {})".format( config["Serial"]["Port"], config["Serial"]["Baudrate"], - config["Serial"]["Timeout"])) - logging.warning("Initializing board") - logging.warning("Board {} ready".format( - await sd.return_board())) + config["Serial"]["Timeout"], + ) + ) + logging.info("Initializing board") + logging.info("Board {} ready".format(await sd.return_board())) async with server: await server.serve_forever() + if __name__ == "__main__": asyncio.run(main()) diff --git a/daemons/simulator/CommandStation-EX-uno-7311f2c.elf b/daemons/simulator/CommandStation-EX-uno-7311f2c.elf new file mode 100755 index 0000000..473475c Binary files /dev/null and b/daemons/simulator/CommandStation-EX-uno-7311f2c.elf differ diff --git a/daemons/simulator/CommandStation-EX-uno-c47dd10.elf b/daemons/simulator/CommandStation-EX-uno-c47dd10.elf deleted file mode 100755 index 7e500c2..0000000 Binary files a/daemons/simulator/CommandStation-EX-uno-c47dd10.elf and /dev/null differ diff --git a/ram/portal/templates/includes/search.html b/ram/portal/templates/includes/search.html index 1b09ce9..b94ba3c 100644 --- a/ram/portal/templates/includes/search.html +++ b/ram/portal/templates/includes/search.html @@ -1,6 +1,6 @@
- +