Skip to content

Commit 8a02b28

Browse files
committed
Update mserial.py
1 parent bbade78 commit 8a02b28

1 file changed

Lines changed: 68 additions & 15 deletions

File tree

uc2rest/mserial.py

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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

573626
if __name__ == "__main__":
574627
# Usage example

0 commit comments

Comments
 (0)