22
33import fnmatch as _fnmatch
44import logging as _logging
5+ import socket as _socket
6+ import threading as _threading
57
68import serial as _serial
79import serial .tools .list_ports as _list_ports
@@ -35,6 +37,10 @@ class SerialProxy():
3537 def __init__ (self , config , log = None ):
3638 self ._log = log if log else _logging .Logger (self .__class__ .__name__ )
3739 self ._serial = None
40+ self ._reader_thread = None
41+ self ._reader_sock_r = None
42+ self ._reader_sock_w = None
43+ self ._reader_running = False
3844 self ._servers = []
3945 self ._serial_config = self ._init_serial_config (config ['serial' ])
4046 self ._match = self ._serial_config .pop ('match' , None )
@@ -103,6 +109,44 @@ def _port_matches(self, port_info, match):
103109 def __del__ (self ):
104110 self .close ()
105111
112+ def _start_reader_thread_if_needed (self ):
113+ """Start reader thread if serial port doesn't support fileno()"""
114+ try :
115+ self ._serial .fileno ()
116+ except OSError :
117+ self ._start_reader_thread ()
118+
119+ def _serial_reader_run (self ):
120+ """Reader thread: read from serial, forward to socketpair"""
121+ while self ._reader_running :
122+ try :
123+ data = self ._serial .read (size = max (1 , self ._serial .in_waiting ))
124+ if data :
125+ self ._reader_sock_w .sendall (data )
126+ except (OSError , _serial .SerialException ):
127+ break
128+
129+ def _start_reader_thread (self ):
130+ """Start reader thread with socketpair for select() compatibility"""
131+ self ._reader_sock_r , self ._reader_sock_w = _socket .socketpair ()
132+ self ._reader_running = True
133+ self ._reader_thread = _threading .Thread (
134+ target = self ._serial_reader_run , daemon = True )
135+ self ._reader_thread .start ()
136+ self ._log .debug ("Serial reader thread started" )
137+
138+ def _stop_reader_thread (self ):
139+ """Stop reader thread and close socketpair"""
140+ if self ._reader_thread is None :
141+ return
142+ self ._reader_running = False
143+ self ._reader_thread .join (timeout = 2 )
144+ self ._reader_sock_r .close ()
145+ self ._reader_sock_w .close ()
146+ self ._reader_thread = None
147+ self ._reader_sock_r = None
148+ self ._reader_sock_w = None
149+
106150 def connect (self ):
107151 """Connect to serial port"""
108152 if not self ._serial :
@@ -120,6 +164,7 @@ def connect(self):
120164 return False
121165 self ._log .info (
122166 "Serial %s connected" , self ._serial_config ['port' ])
167+ self ._start_reader_thread_if_needed ()
123168 return True
124169
125170 def has_connections (self ):
@@ -132,6 +177,7 @@ def has_connections(self):
132177 def disconnect (self ):
133178 """Disconnect serial port, but if there are no active connections"""
134179 if self ._serial and not self .has_connections ():
180+ self ._stop_reader_thread ()
135181 self ._serial .close ()
136182 self ._serial = None
137183 self ._log .info (
@@ -149,7 +195,10 @@ def read_sockets(self):
149195 for server in self ._servers :
150196 sockets += server .read_sockets ()
151197 if self ._serial :
152- sockets .append (self ._serial )
198+ if self ._reader_sock_r :
199+ sockets .append (self ._reader_sock_r )
200+ else :
201+ sockets .append (self ._serial )
153202 return sockets
154203
155204 def write_sockets (self ):
@@ -164,20 +213,31 @@ def send_to_connections(self, data):
164213 for server in self ._servers :
165214 server .send (data )
166215
216+ def _process_serial_data (self ):
217+ """Read and forward serial data to connections"""
218+ try :
219+ if self ._reader_sock_r :
220+ data = self ._reader_sock_r .recv (4096 )
221+ else :
222+ data = self ._serial .read (size = self ._serial .in_waiting )
223+ if data :
224+ self ._log .debug ("(%s): %s" , self ._serial_config ['port' ], data )
225+ self .send_to_connections (data )
226+ else :
227+ raise OSError ("Serial reader closed" )
228+ except (OSError , _serial .SerialException ) as err :
229+ self ._log .warning (err )
230+ for server in self ._servers :
231+ server .close_connections ()
232+ self .disconnect ()
233+
167234 def process_read (self , read_sockets ):
168235 """Process sockets with read event"""
169236 for server in self ._servers :
170237 server .process_read (read_sockets )
171- if self ._serial and self ._serial in read_sockets :
172- try :
173- data = self ._serial .read (size = self ._serial .in_waiting )
174- self ._log .debug ("(%s): %s" , self ._serial_config ['port' ], data )
175- self .send_to_connections (data )
176- except (OSError , _serial .SerialException ) as err :
177- self ._log .warning (err )
178- for server in self ._servers :
179- server .close_connections ()
180- self .disconnect ()
238+ serial_sock = self ._reader_sock_r or self ._serial
239+ if self ._serial and serial_sock in read_sockets :
240+ self ._process_serial_data ()
181241
182242 def process_write (self , write_sockets ):
183243 """Process sockets with write event"""
0 commit comments