mpytool 2.0.0__py3-none-any.whl → 2.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mpytool/conn.py CHANGED
@@ -63,6 +63,23 @@ class Conn():
63
63
  return self._read_available()
64
64
  return None
65
65
 
66
+ def read_bytes(self, count, timeout=1):
67
+ """Read exactly count bytes from device"""
68
+ start_time = _time.time()
69
+ while len(self._buffer) < count:
70
+ if self._read_to_buffer(wait_timeout=0.001):
71
+ start_time = _time.time()
72
+ if timeout is not None and start_time + timeout < _time.time():
73
+ if self._buffer:
74
+ raise Timeout(
75
+ f"During timeout received: {bytes(self._buffer)}")
76
+ raise Timeout("No data received")
77
+ data = bytes(self._buffer[:count])
78
+ del self._buffer[:count]
79
+ if self._log:
80
+ self._log.debug("rd: %s", data)
81
+ return data
82
+
66
83
  def write(self, data):
67
84
  """Write data to device"""
68
85
  if self._log:
@@ -98,3 +115,15 @@ class Conn():
98
115
  """Read single line"""
99
116
  line = self.read_until(b'\n', timeout)
100
117
  return line.strip(b'\r')
118
+
119
+ def hard_reset(self):
120
+ """Hardware reset (only available on serial connections)"""
121
+ raise NotImplementedError("Hardware reset not available on this connection")
122
+
123
+ def reset_to_bootloader(self):
124
+ """Reset into bootloader mode (only available on serial connections)"""
125
+ raise NotImplementedError("Reset to bootloader not available on this connection")
126
+
127
+ def reconnect(self, timeout=None):
128
+ """Reconnect after device reset (only available on serial connections)"""
129
+ raise NotImplementedError("Reconnect not available on this connection")
mpytool/conn_serial.py CHANGED
@@ -1,12 +1,16 @@
1
1
  """MicroPython tool: serial connector"""
2
2
 
3
+ import time as _time
3
4
  import serial as _serial
4
5
  import mpytool.conn as _conn
5
6
 
6
7
 
7
8
  class ConnSerial(_conn.Conn):
9
+ RECONNECT_TIMEOUT = 5 # seconds
10
+
8
11
  def __init__(self, log=None, **serial_config):
9
12
  super().__init__(log)
13
+ self._serial_config = serial_config
10
14
  try:
11
15
  self._serial = _serial.Serial(**serial_config)
12
16
  except _serial.serialutil.SerialException as err:
@@ -32,3 +36,76 @@ class ConnSerial(_conn.Conn):
32
36
  def _write_raw(self, data):
33
37
  """Write data to serial port"""
34
38
  return self._serial.write(data)
39
+
40
+ def _is_usb_cdc(self):
41
+ """Check if this is a USB-CDC port (native USB on ESP32-S/C)"""
42
+ port = self._serial.port or ''
43
+ return 'ACM' in port or 'usbmodem' in port
44
+
45
+ def hard_reset(self):
46
+ """Hardware reset using DTR/RTS signals"""
47
+ self._serial.setDTR(False) # GPIO0 high (normal boot)
48
+ self._serial.setRTS(True) # Assert reset
49
+ _time.sleep(0.1)
50
+ self._serial.setRTS(False) # Release reset
51
+
52
+ def reconnect(self, timeout=None):
53
+ """Close and reopen serial port (for USB-CDC reconnect after reset)"""
54
+ if timeout is None:
55
+ timeout = self.RECONNECT_TIMEOUT
56
+ port = self._serial_config.get('port', 'unknown')
57
+ if self._serial:
58
+ try:
59
+ self._serial.close()
60
+ except (OSError, _serial.serialutil.SerialException):
61
+ pass # Port may already be gone
62
+ self._serial = None
63
+ start = _time.time()
64
+ while _time.time() - start < timeout:
65
+ try:
66
+ self._serial = _serial.Serial(**self._serial_config)
67
+ del self._buffer[:] # Clear any stale data
68
+ _time.sleep(0.5) # Wait for device to stabilize
69
+ # Verify connection is stable
70
+ _ = self._serial.in_waiting
71
+ return True
72
+ except (_serial.serialutil.SerialException, OSError):
73
+ if self._serial:
74
+ try:
75
+ self._serial.close()
76
+ except (OSError, _serial.serialutil.SerialException):
77
+ pass
78
+ self._serial = None
79
+ _time.sleep(0.1)
80
+ raise _conn.ConnError(f"Could not reconnect to {port} within {timeout}s")
81
+
82
+ def reset_to_bootloader(self):
83
+ """Reset into bootloader mode (auto-detects USB-CDC vs USB-UART)"""
84
+ if self._is_usb_cdc():
85
+ self._reset_to_bootloader_usb_jtag()
86
+ else:
87
+ self._reset_to_bootloader_classic()
88
+
89
+ def _reset_to_bootloader_usb_jtag(self):
90
+ """Bootloader reset for USB-JTAG-Serial (esptool sequence)"""
91
+ self._serial.setRTS(False)
92
+ self._serial.setDTR(False)
93
+ _time.sleep(0.1)
94
+ self._serial.setDTR(True)
95
+ self._serial.setRTS(True)
96
+ _time.sleep(0.1)
97
+ self._serial.setDTR(False)
98
+ self._serial.setRTS(True)
99
+ _time.sleep(0.1)
100
+ self._serial.setDTR(False)
101
+ self._serial.setRTS(False)
102
+
103
+ def _reset_to_bootloader_classic(self):
104
+ """Bootloader reset for USB-UART (classic DTR/RTS circuit)"""
105
+ self._serial.setDTR(False) # GPIO0 high
106
+ self._serial.setRTS(True) # Assert reset
107
+ _time.sleep(0.1)
108
+ self._serial.setDTR(True) # GPIO0 low (bootloader)
109
+ self._serial.setRTS(False) # Release reset
110
+ _time.sleep(0.05)
111
+ self._serial.setDTR(False) # GPIO0 high