@@ -111,22 +111,26 @@ def openDevice(self, port=None, baud_rate=115200):
111111 self .serialdevice .close ()
112112 except : pass
113113
114-
114+ # Honour the baudrate passed in — previously tryToConnect used
115+ # self.baudrate, so any override from openDevice/reconnect was lost.
116+ if baud_rate :
117+ self .baudrate = baud_rate
118+
115119 try :
116- # check if port is string and if so within available ports
120+ # check if port is string and if so within available ports
117121 if type (port )== str :
118122 port = self .get_port_info (port )
119123 if port is None :
120124 raise ValueError ("Port not found" )
121-
125+
122126 for i in range (2 ): # not good, but sometimes it needs a second attempt
123- isUC2 = self .tryToConnect (port )
127+ isUC2 = self .tryToConnect (port , baudrate = self . baudrate )
124128 if isUC2 :
125129 break
126130 if not isUC2 :
127131 raise ValueError ('Wrong Firmware.' )
128132 else :
129- self ._logger .debug (f"Connected to { port .device } at { baud_rate } baud." )
133+ self ._logger .debug (f"Connected to { port .device } at { self . baudrate } baud." )
130134 ser = self .serialdevice
131135 self .is_connected = True
132136 self .manufacturer = "UC2"
@@ -135,7 +139,7 @@ def openDevice(self, port=None, baud_rate=115200):
135139 self ._logger .error ("[OpenDevice]: " + str (e ))
136140 ser = self .findCorrectSerialDevice ()
137141 if ser is None :
138- ser = MockSerial (port , baud_rate , timeout = .1 )
142+ ser = MockSerial (port , self . baudrate , timeout = .1 )
139143 self .is_connected = False
140144 ser .write_timeout = self .write_timeout
141145 if not ser .isOpen ():
@@ -197,7 +201,9 @@ def findCorrectSerialDevice(self):
197201 self .manufacturer = "UC2Mock"
198202 return None
199203
200- def tryToConnect (self , port ):
204+ def tryToConnect (self , port , baudrate = None ):
205+ if baudrate is not None :
206+ self .baudrate = baudrate
201207 try :
202208 self .serialdevice = serial .Serial (port .device , baudrate = self .baudrate , timeout = self .read_timeout , write_timeout = self .write_timeout )
203209 # close the device - similar to hard reset
@@ -301,8 +307,10 @@ def _process_commands(self):
301307 if self .serialdevice is None :
302308 self .is_connected = False # TODO: We have to indicate if the device is not connected or if it is just not ready yet - otherwise we will have a lot of "Failed to Send" messages in the beginning
303309 continue
304- else :
305- self .is_connected = True
310+ # Note: do NOT flip is_connected to True merely because the
311+ # serialdevice handle exists. The handle survives unplugs on
312+ # Linux until the next read, so we only consider ourselves
313+ # connected once a successful read happens below.
306314
307315 # if we just want to send but not even wait for a response
308316 with self .serialReadLock :
@@ -313,8 +321,20 @@ def _process_commands(self):
313321 if nFailedCommands > nFailedCommandsMax :
314322 raise Exception ("Failed to read the line in serial: " + str (mReadline ))
315323 line = mReadline .decode ('utf-8' ).strip ()
316- if self .DEBUG and line != "" :
324+ if line != "" :
325+ # Real bytes from the device — mark the link live
326+ # and reset the failure counter.
327+ self .is_connected = True
328+ nFailedCommands = 0
329+ if self .DEBUG and line != "" :
317330 self ._logger .debug ("[ProcessLines]:" + str (line ))
331+ except SerialException as e :
332+ # Hardware-level error (port disappeared, IO error).
333+ # Flip the flag immediately so callers see the truth.
334+ self .is_connected = False
335+ self ._logger .error ("SerialException in read loop: " + str (e ))
336+ nFailedCommands += 1
337+ line = ""
318338 except Exception as e :
319339 self ._logger .error ("Failed to read the line in serial: " + str (e ))
320340 nFailedCommands += 1
@@ -554,21 +574,54 @@ def closeSerial(self):
554574 def close (self ):
555575 self .closeSerial ()
556576
557- def reconnect (self , baudrate = None ):
558- self ._logger .debug ("Reconnecting to the serial device" )
577+ def reconnect (self , port = None , baudrate = None ):
578+ """Reconnect the serial link.
579+
580+ Either parameter may be omitted to keep the currently-configured value.
581+ Returns True on success, False otherwise.
582+ """
583+ self ._logger .debug (
584+ f"Reconnecting to the serial device (port={ port or self .serialport } , "
585+ f"baud={ baudrate or self .baudrate } )"
586+ )
559587 self .running = False
560588 if baudrate is not None :
561589 self .baudrate = baudrate
590+ if port is not None :
591+ self .serialport = port
562592 try :
563593 self .serialdevice .close ()
564594 except :
565595 pass
566- self .serialdevice = self .openDevice (port = self .serialport , baud_rate = self .baudrate )
567- if self .serialdevice :
568- self .serialport = self .serialdevice .port
596+ # Give the previous thread a moment to exit before we replace it.
597+ if self .thread is not None and self .thread .is_alive ():
598+ self .thread .join (timeout = 1.0 )
599+ self .serialdevice = self .openDevice (port = self .serialport , baud_rate = self .baudrate )
600+ if self .serialdevice :
601+ try :
602+ self .serialport = self .serialdevice .port
603+ except Exception :
604+ pass
569605 return True
570606 return False
571607
608+ def ping (self , timeout = 0.5 ):
609+ """Quick health check that asks the device for /state_get.
610+
611+ Returns True if the device responds within ``timeout`` seconds.
612+ Does NOT disturb the running command loop — uses sendMessage with
613+ nResponses=1 and a tight timeout. Intended for the strict variant of
614+ uc2_board_is_connected on the ImSwitch side.
615+ """
616+ if not self .is_connected or self .manufacturer == "UC2Mock" :
617+ return False
618+ try :
619+ resp = self .sendMessage ({"task" : "/state_get" }, nResponses = 1 , timeout = timeout )
620+ return isinstance (resp , list ) and len (resp ) > 0
621+ except Exception as e :
622+ self ._logger .debug (f"ping failed: { e } " )
623+ return False
624+
572625
573626if __name__ == "__main__" :
574627 # Usage example
0 commit comments