mirror of
https://github.com/daniviga/django-ram.git
synced 2025-08-04 05:07:50 +02:00
Net-to-serial broadcast messages to all clients and other cleanups (#20)
* Cleanup and maintenance * Net-to-serial broadcast messages to all clients This will make all clients to stay in sync with any operation occurring, like when having multiple JMRI instances * Update README and python version in containers
This commit is contained in:
@@ -96,7 +96,8 @@ Browse to `http://localhost:8000`
|
|||||||
|
|
||||||
The DCC++ EX connector exposes an Arduino board running DCC++ EX Command Station,
|
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
|
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
|
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)).
|
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
|
```bash
|
||||||
$ cd daemons
|
$ cd daemons
|
||||||
$ podman build -t dcc/net-to-serial .
|
$ 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
|
### Manual setup
|
||||||
|
Submodule arduino/CommandStation-EX updated: aca9c9c941...7311f2ce64
Submodule arduino/arduino-cli updated: 76251df924...940c94573b
Submodule arduino/dcc-ex.github.io updated: d1e5c92c7b...f74f30debc
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.10-alpine
|
FROM python:3.11-alpine
|
||||||
|
|
||||||
RUN mkdir /opt/dcc && pip -q install pyserial
|
RUN mkdir /opt/dcc && pip -q install pyserial
|
||||||
ADD net-to-serial.py config.ini /opt/dcc
|
ADD net-to-serial.py config.ini /opt/dcc
|
||||||
|
3
daemons/README.md
Normal file
3
daemons/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
## DCC++ EX connector
|
||||||
|
|
||||||
|
See [README.md](../README.md)
|
@@ -2,6 +2,7 @@
|
|||||||
LogLevel = debug
|
LogLevel = debug
|
||||||
ListeningIP = 0.0.0.0
|
ListeningIP = 0.0.0.0
|
||||||
ListeningPort = 2560
|
ListeningPort = 2560
|
||||||
|
MaxClients = 10
|
||||||
|
|
||||||
[Serial]
|
[Serial]
|
||||||
# UNO
|
# UNO
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import re
|
import re
|
||||||
import time
|
|
||||||
import logging
|
import logging
|
||||||
import serial
|
import serial
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -10,11 +9,15 @@ from pathlib import Path
|
|||||||
|
|
||||||
|
|
||||||
class SerialDaemon:
|
class SerialDaemon:
|
||||||
|
connected_clients = set()
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.ser = serial.Serial(
|
self.ser = serial.Serial(
|
||||||
config["Serial"]["Port"],
|
config["Serial"]["Port"],
|
||||||
timeout=int(config["Serial"]["Timeout"])/1000)
|
timeout=int(config["Serial"]["Timeout"]) / 1000,
|
||||||
|
)
|
||||||
self.ser.baudrate = config["Serial"]["Baudrate"]
|
self.ser.baudrate = config["Serial"]["Baudrate"]
|
||||||
|
self.max_clients = int(config["Daemon"]["MaxClients"])
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
try:
|
try:
|
||||||
@@ -43,19 +46,32 @@ class SerialDaemon:
|
|||||||
|
|
||||||
async def handle_echo(self, reader, writer):
|
async def handle_echo(self, reader, writer):
|
||||||
"""Process a request from socket and return the response"""
|
"""Process a request from socket and return the response"""
|
||||||
while 1: # keep connection to client open
|
logging.info(
|
||||||
data = await reader.read(100)
|
"Clients already connected: {} (max: {})".format(
|
||||||
if not data: # client has disconnected
|
len(self.connected_clients),
|
||||||
break
|
self.max_clients,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
addr = writer.get_extra_info('peername')
|
addr = writer.get_extra_info("peername")[0]
|
||||||
logging.info("Received {} from {}".format(data, addr[0]))
|
if len(self.connected_clients) < self.max_clients:
|
||||||
|
self.connected_clients.add(writer)
|
||||||
self.__write_serial(data)
|
while True: # keep connection to client open
|
||||||
response = self.__read_serial()
|
data = await reader.read(100)
|
||||||
writer.write(response)
|
if not data: # client has disconnected
|
||||||
await writer.drain()
|
break
|
||||||
logging.info("Sent: {}".format(response))
|
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()
|
writer.close()
|
||||||
await writer.wait_closed()
|
await writer.wait_closed()
|
||||||
@@ -68,33 +84,37 @@ class SerialDaemon:
|
|||||||
while "DCC-EX" not in line:
|
while "DCC-EX" not in line:
|
||||||
line = self.__read_serial().decode()
|
line = self.__read_serial().decode()
|
||||||
board = re.findall(r"<iDCC-EX.*>", line)[0]
|
board = re.findall(r"<iDCC-EX.*>", line)[0]
|
||||||
return(board)
|
return board
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read(
|
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())
|
logging.basicConfig(level=config["Daemon"]["LogLevel"].upper())
|
||||||
|
|
||||||
sd = SerialDaemon(config)
|
sd = SerialDaemon(config)
|
||||||
server = await asyncio.start_server(
|
server = await asyncio.start_server(
|
||||||
sd.handle_echo,
|
sd.handle_echo,
|
||||||
config["Daemon"]["ListeningIP"],
|
config["Daemon"]["ListeningIP"],
|
||||||
config["Daemon"]["ListeningPort"])
|
config["Daemon"]["ListeningPort"],
|
||||||
|
)
|
||||||
addr = server.sockets[0].getsockname()
|
addr = server.sockets[0].getsockname()
|
||||||
logging.warning("Serving on {} port {}".format(addr[0], addr[1]))
|
logging.info("Serving on {} port {}".format(addr[0], addr[1]))
|
||||||
logging.warning(
|
logging.info(
|
||||||
"Proxying to {} (Baudrate: {}, Timeout: {})".format(
|
"Proxying to {} (Baudrate: {}, Timeout: {})".format(
|
||||||
config["Serial"]["Port"],
|
config["Serial"]["Port"],
|
||||||
config["Serial"]["Baudrate"],
|
config["Serial"]["Baudrate"],
|
||||||
config["Serial"]["Timeout"]))
|
config["Serial"]["Timeout"],
|
||||||
logging.warning("Initializing board")
|
)
|
||||||
logging.warning("Board {} ready".format(
|
)
|
||||||
await sd.return_board()))
|
logging.info("Initializing board")
|
||||||
|
logging.info("Board {} ready".format(await sd.return_board()))
|
||||||
|
|
||||||
async with server:
|
async with server:
|
||||||
await server.serve_forever()
|
await server.serve_forever()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
BIN
daemons/simulator/CommandStation-EX-uno-7311f2c.elf
Executable file
BIN
daemons/simulator/CommandStation-EX-uno-7311f2c.elf
Executable file
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
<form class="d-flex needs-validation" action="{% url 'search' %}" method="post" novalidate>
|
<form class="d-flex needs-validation" action="{% url 'search' %}" method="post" novalidate>
|
||||||
<div class="input-group has-validation">
|
<div class="input-group has-validation">
|
||||||
<input class="form-control me-2" type="search" list="datalistOptions" placeholder="Search" aria-label="Search" name="search" id="searchValidation" required>
|
<input class="form-control" type="search" list="datalistOptions" placeholder="Search" aria-label="Search" name="search" id="searchValidation" required>
|
||||||
<datalist id="datalistOptions">
|
<datalist id="datalistOptions">
|
||||||
<option value="company: ">
|
<option value="company: ">
|
||||||
<option value="manufacturer: ">
|
<option value="manufacturer: ">
|
||||||
|
Reference in New Issue
Block a user