I would like to forward data captured on one serial port to a multiclient TCP Server. In short, I need a serial to TCPIP bridge.
import sys
import socket
from threading import Thread
import serial
import serial.threaded
class SerialToNet(serial.threaded.Protocol):
"""serial->socket"""
def __init__(self):
self.sockets: list[socket.socket] = []
def __call__(self):
return self
def data_received(self, data):
"""Forward data from Serial to IP client Sockets"""
for socket in self.sockets:
socket.sendall(data)
class NetToSerial(Thread):
"""socket->serial"""
serial_worker: serial.threaded.ReaderThread
def __init__(self, client_socket):
Thread.__init__(self)
self._socket = client_socket
def run(self):
try:
while True:
data = self._socket.recv(1024)
serial_worker.write(data)
except (ConnectionAbortedError, ConnectionResetError):
print("NetToSerial client disconnection")
return
if __name__ == "__main__":
# Serial connection
SERIAL_COM_PORT = 'COM9'
try:
ser = serial.Serial(SERIAL_COM_PORT, 115200, timeout=2)
except serial.SerialException:
sys.exit(f"Serial port {SERIAL_COM_PORT} it not available")
serial_to_net = SerialToNet()
serial_worker = serial.threaded.ReaderThread(ser, serial_to_net)
serial_worker.start()
# TCP Server
# :todo Use socketserver.TCPServer
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('', 3490))
mythreads = []
try:
# Wait new IP clients
while True:
server_socket.listen()
print("Server: waiting TCP client connection")
(client_socket, _) = server_socket.accept()
# New client
net_to_serial_thread = NetToSerial(client_socket)
net_to_serial_thread.serial_worker = serial_worker
serial_to_net.sockets.append(client_socket)
net_to_serial_thread.start()
mythreads.append(net_to_serial_thread)
except KeyboardInterrupt:
pass
for t in mythreads:
t.join()
This implementation is quite working but I don't known how to update sockets
in SerialToNet
class when a TCP client disconnect.
CodePudding user response:
You need to implement some logic for when a network client disconnects.
You know a client has disconnected because you receive an empty response (b''
) from the socket. You're receiving data from network clients in NetToSerial
, here:
def run(self):
try:
while True:
data = self._socket.recv(1024)
serial_worker.write(data)
except (ConnectionAbortedError, ConnectionResetError):
print("NetToSerial client disconnection")
return
You need to check the value of data
, and if it's empty implement your disconnect logic:
- Close the associated socket.
- Exit the thread.
That might look like:
class NetToSerial(Thread):
"""socket->serial"""
serial_worker: serial.threaded.ReaderThread
def __init__(self, client_socket):
Thread.__init__(self)
self._socket = client_socket
def run(self):
try:
while True:
data = self._socket.recv(1024)
if not data:
break
serial_worker.write(data)
except (ConnectionAbortedError, ConnectionResetError):
print("NetToSerial client disconnection")
return
finally:
self._socket.close()
But that's only half the solution, because you're writing to this socket in your SerialToNet
class. You need to remove the socket from SerialToNet
sockets
array. You can have the class remove the socket in response to an exception when writing, like this:
class SerialToNet(serial.threaded.Protocol):
"""serial->socket"""
def __init__(self):
self.sockets: list[socket.socket] = []
def __call__(self):
return self
def data_received(self, data):
"""Forward data from Serial to IP client Sockets"""
for socket in self.sockets[:]:
try:
socket.sendall(data)
except OSError:
self.sockets.remove(socket)
Note that because it's not possible to remove an item from a list over which you're currently iterating, we are iterating over a copy of self.sockets
in the above code. This means we're free to remove sockets from self.sockets
from inside the loop.
With the above changes I believe your code will operate as you intend.
Not directly related to your question, but I'd like to make a comment about your code: as written, it allows multiple network clients to write to the serial port at the same time. That seems like a recipe for disaster and I cannot think of any situation in which that would make sense. You may want to reconsider that aspect of your code.