@@ -64,6 +64,7 @@ def __init__(
6464 btr : Optional [str ] = None ,
6565 sleep_after_open : float = _SLEEP_AFTER_SERIAL_OPEN ,
6666 rtscts : bool = False ,
67+ timeout : float = 0.001 ,
6768 ** kwargs : Any ,
6869 ) -> None :
6970 """
@@ -82,7 +83,8 @@ def __init__(
8283 Time to wait in seconds after opening serial connection
8384 :param rtscts:
8485 turn hardware handshake (RTS/CTS) on and off
85-
86+ :param timeout:
87+ Timeout for the serial or usb device in seconds (default 0.001)
8688 :raise ValueError: if both ``bitrate`` and ``btr`` are set or the channel is invalid
8789 :raise CanInterfaceNotImplementedError: if the serial module is missing
8890 :raise CanInitializationError: if the underlying serial connection could not be established
@@ -98,7 +100,10 @@ def __init__(
98100
99101 with error_check (exception_type = CanInitializationError ):
100102 self .serialPortOrig = serial .serial_for_url (
101- channel , baudrate = ttyBaudrate , rtscts = rtscts
103+ channel ,
104+ baudrate = ttyBaudrate ,
105+ rtscts = rtscts ,
106+ timeout = timeout ,
102107 )
103108
104109 self ._buffer = bytearray ()
@@ -150,46 +155,34 @@ def _write(self, string: str) -> None:
150155 self .serialPortOrig .flush ()
151156
152157 def _read (self , timeout : Optional [float ]) -> Optional [str ]:
158+ _timeout = serial .Timeout (timeout )
153159
154160 with error_check ("Could not read from serial device" ):
155- # first read what is already in receive buffer
156- while self .serialPortOrig .in_waiting :
157- self ._buffer += self .serialPortOrig .read ()
158- # if we still don't have a complete message, do a blocking read
159- start = time .time ()
160- time_left = timeout
161- while not (
162- ord (self ._OK ) in self ._buffer or ord (self ._ERROR ) in self ._buffer
163- ):
164- self .serialPortOrig .timeout = time_left
165- byte = self .serialPortOrig .read ()
166- if byte :
167- self ._buffer += byte
168- # if timeout is None, try indefinitely
169- if timeout is None :
170- continue
171- # try next one only if there still is time, and with
172- # reduced timeout
173- else :
174- time_left = timeout - (time .time () - start )
175- if time_left > 0 :
176- continue
161+ while True :
162+ # Due to accessing `serialPortOrig.in_waiting` too often will reduce the performance.
163+ # We read the `serialPortOrig.in_waiting` only once here.
164+ in_waiting = self .serialPortOrig .in_waiting
165+ for _ in range (max (1 , in_waiting )):
166+ new_byte = self .serialPortOrig .read (size = 1 )
167+ if new_byte :
168+ self ._buffer .extend (new_byte )
177169 else :
178- return None
170+ break
171+
172+ if new_byte in (self ._ERROR , self ._OK ):
173+ string = self ._buffer .decode ()
174+ self ._buffer .clear ()
175+ return string
176+
177+ if _timeout .expired ():
178+ break
179179
180- # return first message
181- for i in range (len (self ._buffer )):
182- if self ._buffer [i ] == ord (self ._OK ) or self ._buffer [i ] == ord (self ._ERROR ):
183- string = self ._buffer [: i + 1 ].decode ()
184- del self ._buffer [: i + 1 ]
185- break
186- return string
180+ return None
187181
188182 def flush (self ) -> None :
189- del self ._buffer [:]
183+ self ._buffer . clear ()
190184 with error_check ("Could not flush" ):
191- while self .serialPortOrig .in_waiting :
192- self .serialPortOrig .read ()
185+ self .serialPortOrig .reset_input_buffer ()
193186
194187 def open (self ) -> None :
195188 self ._write ("O" )
@@ -204,7 +197,7 @@ def _recv_internal(
204197 canId = None
205198 remote = False
206199 extended = False
207- frame = []
200+ data = None
208201
209202 string = self ._read (timeout )
210203
@@ -215,14 +208,12 @@ def _recv_internal(
215208 canId = int (string [1 :9 ], 16 )
216209 dlc = int (string [9 ])
217210 extended = True
218- for i in range (0 , dlc ):
219- frame .append (int (string [10 + i * 2 : 12 + i * 2 ], 16 ))
211+ data = bytearray .fromhex (string [10 : 10 + dlc * 2 ])
220212 elif string [0 ] == "t" :
221213 # normal frame
222214 canId = int (string [1 :4 ], 16 )
223215 dlc = int (string [4 ])
224- for i in range (0 , dlc ):
225- frame .append (int (string [5 + i * 2 : 7 + i * 2 ], 16 ))
216+ data = bytearray .fromhex (string [5 : 5 + dlc * 2 ])
226217 elif string [0 ] == "r" :
227218 # remote frame
228219 canId = int (string [1 :4 ], 16 )
@@ -242,7 +233,7 @@ def _recv_internal(
242233 timestamp = time .time (), # Better than nothing...
243234 is_remote_frame = remote ,
244235 dlc = dlc ,
245- data = frame ,
236+ data = data ,
246237 )
247238 return msg , False
248239 return None , False
@@ -252,15 +243,15 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None:
252243 self .serialPortOrig .write_timeout = timeout
253244 if msg .is_remote_frame :
254245 if msg .is_extended_id :
255- sendStr = "R%08X%d" % ( msg .arbitration_id , msg .dlc )
246+ sendStr = f"R { msg .arbitration_id :08X } { msg .dlc :d } "
256247 else :
257- sendStr = "r%03X%d" % ( msg .arbitration_id , msg .dlc )
248+ sendStr = f"r { msg .arbitration_id :03X } { msg .dlc :d } "
258249 else :
259250 if msg .is_extended_id :
260- sendStr = "T%08X%d" % ( msg .arbitration_id , msg .dlc )
251+ sendStr = f"T { msg .arbitration_id :08X } { msg .dlc :d } "
261252 else :
262- sendStr = "t%03X%d" % ( msg .arbitration_id , msg .dlc )
263- sendStr += "" . join ([ "%02X" % b for b in msg .data ] )
253+ sendStr = f"t { msg .arbitration_id :03X } { msg .dlc :d } "
254+ sendStr += msg .data . hex (). upper ( )
264255 self ._write (sendStr )
265256
266257 def shutdown (self ) -> None :
@@ -295,29 +286,17 @@ def get_version(
295286 cmd = "V"
296287 self ._write (cmd )
297288
298- start = time .time ()
299- time_left = timeout
300- while True :
301- string = self ._read (time_left )
302-
303- if not string :
304- pass
305- elif string [0 ] == cmd and len (string ) == 6 :
306- # convert ASCII coded version
307- hw_version = int (string [1 :3 ])
308- sw_version = int (string [3 :5 ])
309- return hw_version , sw_version
310- # if timeout is None, try indefinitely
311- if timeout is None :
312- continue
313- # try next one only if there still is time, and with
314- # reduced timeout
315- else :
316- time_left = timeout - (time .time () - start )
317- if time_left > 0 :
318- continue
319- else :
320- return None , None
289+ string = self ._read (timeout )
290+
291+ if not string :
292+ pass
293+ elif string [0 ] == cmd and len (string ) == 6 :
294+ # convert ASCII coded version
295+ hw_version = int (string [1 :3 ])
296+ sw_version = int (string [3 :5 ])
297+ return hw_version , sw_version
298+
299+ return None , None
321300
322301 def get_serial_number (self , timeout : Optional [float ]) -> Optional [str ]:
323302 """Get serial number of the slcan interface.
@@ -331,24 +310,12 @@ def get_serial_number(self, timeout: Optional[float]) -> Optional[str]:
331310 cmd = "N"
332311 self ._write (cmd )
333312
334- start = time .time ()
335- time_left = timeout
336- while True :
337- string = self ._read (time_left )
338-
339- if not string :
340- pass
341- elif string [0 ] == cmd and len (string ) == 6 :
342- serial_number = string [1 :- 1 ]
343- return serial_number
344- # if timeout is None, try indefinitely
345- if timeout is None :
346- continue
347- # try next one only if there still is time, and with
348- # reduced timeout
349- else :
350- time_left = timeout - (time .time () - start )
351- if time_left > 0 :
352- continue
353- else :
354- return None
313+ string = self ._read (timeout )
314+
315+ if not string :
316+ pass
317+ elif string [0 ] == cmd and len (string ) == 6 :
318+ serial_number = string [1 :- 1 ]
319+ return serial_number
320+
321+ return None
0 commit comments