mpytool 2.0.0__py3-none-any.whl → 2.2.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 +29 -0
- mpytool/conn_serial.py +92 -5
- mpytool/mpy.py +1063 -78
- mpytool/mpy_comm.py +140 -11
- mpytool/mpytool.py +1041 -393
- mpytool/terminal.py +1 -1
- mpytool/utils.py +6 -6
- mpytool-2.2.0.dist-info/METADATA +462 -0
- mpytool-2.2.0.dist-info/RECORD +16 -0
- {mpytool-2.0.0.dist-info → mpytool-2.2.0.dist-info}/WHEEL +1 -1
- mpytool-2.0.0.dist-info/METADATA +0 -233
- mpytool-2.0.0.dist-info/RECORD +0 -16
- {mpytool-2.0.0.dist-info → mpytool-2.2.0.dist-info}/entry_points.txt +0 -0
- {mpytool-2.0.0.dist-info → mpytool-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {mpytool-2.0.0.dist-info → mpytool-2.2.0.dist-info}/top_level.txt +0 -0
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 = 10 # 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:
|
|
@@ -24,11 +28,94 @@ class ConnSerial(_conn.Conn):
|
|
|
24
28
|
|
|
25
29
|
def _read_available(self):
|
|
26
30
|
"""Read available data from serial port"""
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
if self._serial is None:
|
|
32
|
+
raise _conn.ConnError("Not connected")
|
|
33
|
+
try:
|
|
34
|
+
in_waiting = self._serial.in_waiting
|
|
35
|
+
if in_waiting > 0:
|
|
36
|
+
return self._serial.read(in_waiting)
|
|
37
|
+
return None
|
|
38
|
+
except OSError as err:
|
|
39
|
+
raise _conn.ConnError(f"Connection lost: {err}") from err
|
|
31
40
|
|
|
32
41
|
def _write_raw(self, data):
|
|
33
42
|
"""Write data to serial port"""
|
|
34
|
-
|
|
43
|
+
if self._serial is None:
|
|
44
|
+
raise _conn.ConnError("Not connected")
|
|
45
|
+
try:
|
|
46
|
+
return self._serial.write(data)
|
|
47
|
+
except OSError as err:
|
|
48
|
+
raise _conn.ConnError(f"Connection lost: {err}") from err
|
|
49
|
+
|
|
50
|
+
def _is_usb_cdc(self):
|
|
51
|
+
"""Check if this is a USB-CDC port (native USB on ESP32-S/C)"""
|
|
52
|
+
port = self._serial.port or ''
|
|
53
|
+
return 'ACM' in port or 'usbmodem' in port
|
|
54
|
+
|
|
55
|
+
def hard_reset(self):
|
|
56
|
+
"""Hardware reset using DTR/RTS signals"""
|
|
57
|
+
self._serial.setDTR(False) # GPIO0 high (normal boot)
|
|
58
|
+
self._serial.setRTS(True) # Assert reset
|
|
59
|
+
_time.sleep(0.1)
|
|
60
|
+
self._serial.setRTS(False) # Release reset
|
|
61
|
+
|
|
62
|
+
def reconnect(self, timeout=None):
|
|
63
|
+
"""Close and reopen serial port (for USB-CDC reconnect after reset)"""
|
|
64
|
+
if timeout is None:
|
|
65
|
+
timeout = self.RECONNECT_TIMEOUT
|
|
66
|
+
port = self._serial_config.get('port', 'unknown')
|
|
67
|
+
if self._serial:
|
|
68
|
+
try:
|
|
69
|
+
self._serial.close()
|
|
70
|
+
except (OSError, _serial.serialutil.SerialException):
|
|
71
|
+
pass # Port may already be gone
|
|
72
|
+
self._serial = None
|
|
73
|
+
start = _time.time()
|
|
74
|
+
while _time.time() - start < timeout:
|
|
75
|
+
try:
|
|
76
|
+
self._serial = _serial.Serial(**self._serial_config)
|
|
77
|
+
del self._buffer[:] # Clear any stale data
|
|
78
|
+
_time.sleep(0.5) # Wait for device to stabilize
|
|
79
|
+
# Verify connection is stable
|
|
80
|
+
_ = self._serial.in_waiting
|
|
81
|
+
return True
|
|
82
|
+
except (_serial.serialutil.SerialException, OSError):
|
|
83
|
+
if self._serial:
|
|
84
|
+
try:
|
|
85
|
+
self._serial.close()
|
|
86
|
+
except (OSError, _serial.serialutil.SerialException):
|
|
87
|
+
pass
|
|
88
|
+
self._serial = None
|
|
89
|
+
_time.sleep(0.1)
|
|
90
|
+
raise _conn.ConnError(f"Could not reconnect to {port} within {timeout}s")
|
|
91
|
+
|
|
92
|
+
def reset_to_bootloader(self):
|
|
93
|
+
"""Reset into bootloader mode (auto-detects USB-CDC vs USB-UART)"""
|
|
94
|
+
if self._is_usb_cdc():
|
|
95
|
+
self._reset_to_bootloader_usb_jtag()
|
|
96
|
+
else:
|
|
97
|
+
self._reset_to_bootloader_classic()
|
|
98
|
+
|
|
99
|
+
def _reset_to_bootloader_usb_jtag(self):
|
|
100
|
+
"""Bootloader reset for USB-JTAG-Serial (esptool sequence)"""
|
|
101
|
+
self._serial.setRTS(False)
|
|
102
|
+
self._serial.setDTR(False)
|
|
103
|
+
_time.sleep(0.1)
|
|
104
|
+
self._serial.setDTR(True)
|
|
105
|
+
self._serial.setRTS(True)
|
|
106
|
+
_time.sleep(0.1)
|
|
107
|
+
self._serial.setDTR(False)
|
|
108
|
+
self._serial.setRTS(True)
|
|
109
|
+
_time.sleep(0.1)
|
|
110
|
+
self._serial.setDTR(False)
|
|
111
|
+
self._serial.setRTS(False)
|
|
112
|
+
|
|
113
|
+
def _reset_to_bootloader_classic(self):
|
|
114
|
+
"""Bootloader reset for USB-UART (classic DTR/RTS circuit)"""
|
|
115
|
+
self._serial.setDTR(False) # GPIO0 high
|
|
116
|
+
self._serial.setRTS(True) # Assert reset
|
|
117
|
+
_time.sleep(0.1)
|
|
118
|
+
self._serial.setDTR(True) # GPIO0 low (bootloader)
|
|
119
|
+
self._serial.setRTS(False) # Release reset
|
|
120
|
+
_time.sleep(0.05)
|
|
121
|
+
self._serial.setDTR(False) # GPIO0 high
|