replx 1.22.2__tar.gz → 1.30.3__tar.gz
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.
- {replx-1.22.2/replx.egg-info → replx-1.30.3}/PKG-INFO +13 -7
- {replx-1.22.2 → replx-1.30.3}/README.md +6 -1
- {replx-1.22.2 → replx-1.30.3}/pyproject.toml +7 -8
- {replx-1.22.2 → replx-1.30.3}/replx/__init__.py +2 -2
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/__init__.py +10 -3
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/client/core.py +39 -12
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/protocol.py +9 -14
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/server/connection_manager.py +105 -49
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/server/core.py +130 -52
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/server/handlers/exec.py +60 -30
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/server/handlers/filesystem.py +2 -1
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/server/handlers/repl.py +2 -0
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/server/handlers/session.py +11 -17
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/server/handlers/transfer.py +52 -65
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/server/handlers/uart.py +4 -2
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/server/session_manager.py +48 -5
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/server/udp.py +20 -12
- {replx-1.22.2 → replx-1.30.3}/replx/cli/app.py +7 -6
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/_common.py +52 -8
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/adc.py +1185 -1166
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/ble.py +32 -51
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/device.py +54 -112
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/exec.py +9 -11
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/file.py +28 -28
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/firmware.py +132 -6
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/gpio.py +111 -27
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/i2c.py +43 -34
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/i2s.py +489 -462
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/mip.py +11 -3
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/package.py +65 -4
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/pwm.py +1010 -1007
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/spi.py +718 -714
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/uart.py +388 -121
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/utility.py +45 -5
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/wifi.py +88 -79
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/ws2812.py +237 -222
- {replx-1.22.2 → replx-1.30.3}/replx/cli/config.py +0 -24
- {replx-1.22.2 → replx-1.30.3}/replx/cli/connection.py +1 -15
- {replx-1.22.2 → replx-1.30.3}/replx/cli/helpers/compiler.py +10 -2
- replx-1.30.3/replx/cli/helpers/scanner.py +308 -0
- {replx-1.22.2 → replx-1.30.3}/replx/commands.py +3 -2
- {replx-1.22.2 → replx-1.30.3}/replx/protocol/repl.py +155 -110
- {replx-1.22.2 → replx-1.30.3}/replx/protocol/storage.py +182 -128
- {replx-1.22.2 → replx-1.30.3}/replx/terminal.py +72 -10
- replx-1.30.3/replx/transport/__init__.py +32 -0
- {replx-1.22.2 → replx-1.30.3}/replx/transport/serial.py +55 -23
- {replx-1.22.2 → replx-1.30.3}/replx/utils/constants.py +14 -0
- {replx-1.22.2 → replx-1.30.3}/replx/utils/device_info.py +13 -3
- {replx-1.22.2 → replx-1.30.3/replx.egg-info}/PKG-INFO +13 -7
- replx-1.30.3/replx.egg-info/requires.txt +7 -0
- replx-1.22.2/replx/cli/helpers/scanner.py +0 -256
- replx-1.22.2/replx/transport/__init__.py +0 -15
- replx-1.22.2/replx.egg-info/requires.txt +0 -6
- {replx-1.22.2 → replx-1.30.3}/LICENSE +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/__main__.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/cli/__init__.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/client/__init__.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/client/session.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/server/__init__.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/server/__main__.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/server/command_dispatcher.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/server/handlers/__init__.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/server/handlers/i2c.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/cli/agent/server/handlers/spi.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/cli/commands/__init__.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/cli/helpers/__init__.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/cli/helpers/environment.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/cli/helpers/output.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/cli/helpers/registry.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/cli/helpers/store.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/cli/helpers/updater.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/protocol/__init__.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/_thread.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/aioble/__init__.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/aioble/l2cap.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/array.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/asyncio/__init__.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/binascii.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/bluetooth.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/builtins.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/cmath.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/collections.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/cryptolib.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/deflate.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/errno.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/framebuf.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/gc.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/hashlib.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/heapq.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/io.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/json.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/lwip.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/machine.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/math.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/micropython.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/mip/__init__.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/network.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/ntptime.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/os.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/platform.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/random.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/re.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/requests/__init__.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/select.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/socket.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/ssl.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/struct.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/sys.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/time.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/tls.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/uasyncio.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/uctypes.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/ulab.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/urequests.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm/vfs.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/binascii.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/digi/__init__.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/digi/ble.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/errno.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/hashlib.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/io.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/json.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/machine.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/math.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/micropython.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/network.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/os.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/select.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/socket.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/ssl.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/struct.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/sys.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/time.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/ubinascii.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/ucryptolib.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/uerrno.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/uhashlib.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/uio.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/ujson.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/umachine.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/uos.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/uselect.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/usocket.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/ussl.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/ustruct.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/utime.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/xbee/__init__.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/xbee/modem_status.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/comm_separate/EFR32MG/xbee/relay.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/core/ESP32/aioespnow.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/core/ESP32/esp.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/core/ESP32/esp32.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/core/ESP32/espnow.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/core/MIMXRT1062DVJ6A/mimxrt.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/typehints/core/RP2350/rp2.pyi +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/utils/__init__.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx/utils/exceptions.py +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx.egg-info/SOURCES.txt +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx.egg-info/dependency_links.txt +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx.egg-info/entry_points.txt +0 -0
- {replx-1.22.2 → replx-1.30.3}/replx.egg-info/top_level.txt +0 -0
- {replx-1.22.2 → replx-1.30.3}/setup.cfg +0 -0
- {replx-1.22.2 → replx-1.30.3}/test/test_hardware_commands.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: replx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.30.3
|
|
4
4
|
Summary: replx is a fast, modern MicroPython CLI: turbo REPL, robust file sync (put/get), project install, mpy-cross integration, and smart port discovery.
|
|
5
5
|
Author-email: "chanmin.park" <devcamp@planxlab.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
|
-
Project-URL: Homepage, https://
|
|
7
|
+
Project-URL: Homepage, https://planxlab.com
|
|
8
8
|
Project-URL: Repository, https://github.com/PlanXLab/replx
|
|
9
9
|
Project-URL: Issues, https://github.com/PlanXLab/replx/issues
|
|
10
10
|
Keywords: micropython,repl,serial,pyserial,typer,mpy-cross,deploy,gpio,i2c,spi,uart,pwm,adc,i2s,ws2812
|
|
@@ -17,12 +17,13 @@ Classifier: Topic :: System :: Hardware :: Universal Serial Bus (USB)
|
|
|
17
17
|
Requires-Python: >=3.10
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
License-File: LICENSE
|
|
20
|
-
Requires-Dist: click>=8.
|
|
21
|
-
Requires-Dist: typer>=0.27.
|
|
22
|
-
Requires-Dist: rich>=
|
|
20
|
+
Requires-Dist: click>=8.5.0
|
|
21
|
+
Requires-Dist: typer>=0.27.3
|
|
22
|
+
Requires-Dist: rich>=15.0.0
|
|
23
23
|
Requires-Dist: pyserial>=3.5
|
|
24
|
-
Requires-Dist: mpy-cross>=1.
|
|
24
|
+
Requires-Dist: mpy-cross>=1.29.0
|
|
25
25
|
Requires-Dist: psutil>=7.2.2
|
|
26
|
+
Requires-Dist: esptool<6,>=5.6.0
|
|
26
27
|
Dynamic: license-file
|
|
27
28
|
|
|
28
29
|
# replx
|
|
@@ -33,6 +34,8 @@ Dynamic: license-file
|
|
|
33
34
|
|
|
34
35
|
`replx` is a CLI tool for MicroPython development. It uses a single local agent process to manage multiple CLI sessions and multiple boards in a consistent workflow.
|
|
35
36
|
|
|
37
|
+
It runs on Windows, macOS, and Linux and targets standard MicroPython 1.29 or newer, with a separate source-based legacy compatibility path for Digi XBee3 Zigbee. Hardware commands are platform-dependent; see the detailed manual's board compatibility table. Actual board testing has been completed on Raspberry Pi Pico 2 W and Waveshare ESP32-P4-WIFI6; Digi XBee3 compatibility still requires physical board validation.
|
|
38
|
+
|
|
36
39
|
## What replx provides
|
|
37
40
|
|
|
38
41
|
- Shared connection management across terminal sessions
|
|
@@ -59,6 +62,7 @@ pip install replx
|
|
|
59
62
|
- `whoami`: Show the current foreground device.
|
|
60
63
|
- `disconnect`: Close a device connection.
|
|
61
64
|
- `shutdown`: Stop the agent and clear active sessions.
|
|
65
|
+
- `theme`: Show or customize UI panel colors and box style.
|
|
62
66
|
|
|
63
67
|
### Execution and interaction
|
|
64
68
|
|
|
@@ -77,7 +81,7 @@ pip install replx
|
|
|
77
81
|
- `mv`: Move or rename files or directories on device.
|
|
78
82
|
- `rm`: Remove files or directories on device.
|
|
79
83
|
- `mkdir`: Create directories on device.
|
|
80
|
-
- `touch`: Create
|
|
84
|
+
- `touch`: Create a file if missing; preserve existing contents.
|
|
81
85
|
|
|
82
86
|
### Device management
|
|
83
87
|
|
|
@@ -101,6 +105,8 @@ pip install replx
|
|
|
101
105
|
- `uart`: Open, write, read, and monitor UART.
|
|
102
106
|
- `spi`: Open, write, read, and transfer SPI data.
|
|
103
107
|
- `i2c`: Scan, read, write, and dump I2C devices.
|
|
108
|
+
- `i2s`: Play I2S tones and capture or monitor an I2S microphone.
|
|
109
|
+
- `ws2812`: Write pixels and regions on WS2812 LED matrices.
|
|
104
110
|
|
|
105
111
|
### Connectivity
|
|
106
112
|
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
`replx` is a CLI tool for MicroPython development. It uses a single local agent process to manage multiple CLI sessions and multiple boards in a consistent workflow.
|
|
8
8
|
|
|
9
|
+
It runs on Windows, macOS, and Linux and targets standard MicroPython 1.29 or newer, with a separate source-based legacy compatibility path for Digi XBee3 Zigbee. Hardware commands are platform-dependent; see the detailed manual's board compatibility table. Actual board testing has been completed on Raspberry Pi Pico 2 W and Waveshare ESP32-P4-WIFI6; Digi XBee3 compatibility still requires physical board validation.
|
|
10
|
+
|
|
9
11
|
## What replx provides
|
|
10
12
|
|
|
11
13
|
- Shared connection management across terminal sessions
|
|
@@ -32,6 +34,7 @@ pip install replx
|
|
|
32
34
|
- `whoami`: Show the current foreground device.
|
|
33
35
|
- `disconnect`: Close a device connection.
|
|
34
36
|
- `shutdown`: Stop the agent and clear active sessions.
|
|
37
|
+
- `theme`: Show or customize UI panel colors and box style.
|
|
35
38
|
|
|
36
39
|
### Execution and interaction
|
|
37
40
|
|
|
@@ -50,7 +53,7 @@ pip install replx
|
|
|
50
53
|
- `mv`: Move or rename files or directories on device.
|
|
51
54
|
- `rm`: Remove files or directories on device.
|
|
52
55
|
- `mkdir`: Create directories on device.
|
|
53
|
-
- `touch`: Create
|
|
56
|
+
- `touch`: Create a file if missing; preserve existing contents.
|
|
54
57
|
|
|
55
58
|
### Device management
|
|
56
59
|
|
|
@@ -74,6 +77,8 @@ pip install replx
|
|
|
74
77
|
- `uart`: Open, write, read, and monitor UART.
|
|
75
78
|
- `spi`: Open, write, read, and transfer SPI data.
|
|
76
79
|
- `i2c`: Scan, read, write, and dump I2C devices.
|
|
80
|
+
- `i2s`: Play I2S tones and capture or monitor an I2S microphone.
|
|
81
|
+
- `ws2812`: Write pixels and regions on WS2812 LED matrices.
|
|
77
82
|
|
|
78
83
|
### Connectivity
|
|
79
84
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools>=
|
|
2
|
+
requires = ["setuptools>=77.0.3"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
@@ -23,18 +23,17 @@ classifiers = [
|
|
|
23
23
|
]
|
|
24
24
|
|
|
25
25
|
dependencies = [
|
|
26
|
-
"click>=8.
|
|
27
|
-
"typer>=0.27.
|
|
28
|
-
"rich>=
|
|
26
|
+
"click>=8.5.0",
|
|
27
|
+
"typer>=0.27.3",
|
|
28
|
+
"rich>=15.0.0",
|
|
29
29
|
"pyserial>=3.5",
|
|
30
|
-
"mpy-cross>=1.
|
|
30
|
+
"mpy-cross>=1.29.0",
|
|
31
31
|
"psutil>=7.2.2",
|
|
32
|
+
"esptool>=5.6.0,<6",
|
|
32
33
|
]
|
|
33
34
|
|
|
34
|
-
[project.optional-dependencies]
|
|
35
|
-
|
|
36
35
|
[project.urls]
|
|
37
|
-
Homepage = "https://
|
|
36
|
+
Homepage = "https://planxlab.com"
|
|
38
37
|
Repository = "https://github.com/PlanXLab/replx"
|
|
39
38
|
Issues = "https://github.com/PlanXLab/replx/issues"
|
|
40
39
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
__all__ = ["__version__", "get_version", "__description__"]
|
|
2
|
-
__version__ = "1.
|
|
2
|
+
__version__ = "1.30.3"
|
|
3
3
|
__description__ = "Fast, modern MicroPython CLI with REPL, file sync, install, and smart port detection."
|
|
4
4
|
__author__ = "PlanX Lab Development Team"
|
|
5
5
|
|
|
@@ -8,4 +8,4 @@ def get_version() -> str:
|
|
|
8
8
|
from importlib.metadata import version as _v # Python 3.10+
|
|
9
9
|
return _v("replx")
|
|
10
10
|
except Exception:
|
|
11
|
-
return __version__
|
|
11
|
+
return __version__
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
|
+
from importlib import import_module
|
|
3
|
+
|
|
2
4
|
from .protocol import AgentProtocol
|
|
3
|
-
from .client import AgentClient, get_session_id, get_cached_session_id, clear_session_cache
|
|
4
5
|
|
|
5
6
|
__all__ = [
|
|
6
7
|
'AgentProtocol',
|
|
@@ -14,8 +15,14 @@ __all__ = [
|
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def __getattr__(name):
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
if name in {
|
|
19
|
+
'AgentClient',
|
|
20
|
+
'get_session_id',
|
|
21
|
+
'get_cached_session_id',
|
|
22
|
+
'clear_session_cache',
|
|
23
|
+
}:
|
|
24
|
+
return getattr(import_module('.client', __name__), name)
|
|
25
|
+
|
|
19
26
|
if name == 'AgentServer':
|
|
20
27
|
from .server import AgentServer
|
|
21
28
|
|
|
@@ -26,14 +26,14 @@ class AgentClient:
|
|
|
26
26
|
|
|
27
27
|
self._ppid = get_cached_session_id()
|
|
28
28
|
self._atexit_registered = False
|
|
29
|
+
# Set only when Ctrl+C interrupts a request the agent may still be
|
|
30
|
+
# executing. A normal CLI exit must keep the agent's board connection.
|
|
31
|
+
self._release_on_exit = False
|
|
29
32
|
|
|
30
33
|
def connect(self):
|
|
31
34
|
if not self.sock:
|
|
32
35
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
33
36
|
self.sock.settimeout(self.TIMEOUT)
|
|
34
|
-
# M7: ensure the agent releases the device port if the CLI process
|
|
35
|
-
# exits abnormally (Ctrl-C, unhandled exception, parent terminated)
|
|
36
|
-
# without its normal teardown path running.
|
|
37
37
|
if not self._atexit_registered and self.device_port:
|
|
38
38
|
try:
|
|
39
39
|
atexit.register(self._atexit_release)
|
|
@@ -43,9 +43,8 @@ class AgentClient:
|
|
|
43
43
|
|
|
44
44
|
def _atexit_release(self) -> None:
|
|
45
45
|
try:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
self._release_device_port_safe()
|
|
46
|
+
self._release_on_exit = False
|
|
47
|
+
self.disconnect()
|
|
49
48
|
except Exception:
|
|
50
49
|
pass
|
|
51
50
|
|
|
@@ -92,8 +91,22 @@ class AgentClient:
|
|
|
92
91
|
except Exception:
|
|
93
92
|
pass
|
|
94
93
|
|
|
94
|
+
@staticmethod
|
|
95
|
+
def _print_notifications(response: Dict[str, Any]) -> None:
|
|
96
|
+
for notification in response.get('notifications', ()):
|
|
97
|
+
if notification:
|
|
98
|
+
print(f"replx: {notification}", file=sys.stderr)
|
|
99
|
+
|
|
95
100
|
def send_command(self, command: str, timeout: float = None, max_retries: int = None,
|
|
96
101
|
wait_on_busy: float = 30.0, **args) -> Dict[str, Any]:
|
|
102
|
+
try:
|
|
103
|
+
return self._send_command(command, timeout, max_retries, wait_on_busy, **args)
|
|
104
|
+
except KeyboardInterrupt:
|
|
105
|
+
self._release_on_exit = True
|
|
106
|
+
raise
|
|
107
|
+
|
|
108
|
+
def _send_command(self, command: str, timeout: float = None, max_retries: int = None,
|
|
109
|
+
wait_on_busy: float = 30.0, **args) -> Dict[str, Any]:
|
|
97
110
|
if not self.sock:
|
|
98
111
|
self.connect()
|
|
99
112
|
|
|
@@ -161,6 +174,7 @@ class AgentClient:
|
|
|
161
174
|
if not response:
|
|
162
175
|
raise RuntimeError("No response from agent")
|
|
163
176
|
|
|
177
|
+
self._print_notifications(response)
|
|
164
178
|
error = response.get('error', '')
|
|
165
179
|
|
|
166
180
|
# Retry only for transient "Another command" busy — not REPL/detached.
|
|
@@ -178,7 +192,8 @@ class AgentClient:
|
|
|
178
192
|
output_callback: Callable[[bytes, str], None] = None,
|
|
179
193
|
input_provider: Callable[[], Optional[bytes]] = None,
|
|
180
194
|
stop_check: Callable[[], bool] = None,
|
|
181
|
-
ctrl_c_grace_s: float = 3.0
|
|
195
|
+
ctrl_c_grace_s: float = 3.0,
|
|
196
|
+
reset: bool = True) -> Dict[str, Any]:
|
|
182
197
|
if not self.sock:
|
|
183
198
|
self.connect()
|
|
184
199
|
|
|
@@ -191,7 +206,8 @@ class AgentClient:
|
|
|
191
206
|
port=self.device_port,
|
|
192
207
|
script_path=script_path,
|
|
193
208
|
script_content=script_content,
|
|
194
|
-
echo=echo
|
|
209
|
+
echo=echo,
|
|
210
|
+
reset=reset,
|
|
195
211
|
)
|
|
196
212
|
seq = request['seq']
|
|
197
213
|
request_data = AgentProtocol.encode_message(request)
|
|
@@ -324,9 +340,10 @@ class AgentClient:
|
|
|
324
340
|
|
|
325
341
|
# Check for stream reception timeout (connection loss detection)
|
|
326
342
|
if now - last_stream_time > stream_timeout:
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
343
|
+
try:
|
|
344
|
+
self.send_command(Cmd.RUN_STOP, timeout=1.0)
|
|
345
|
+
except Exception:
|
|
346
|
+
pass
|
|
330
347
|
raise RuntimeError("Connection lost - no data from board for {}s".format(stream_timeout))
|
|
331
348
|
|
|
332
349
|
if now - last_input_time >= input_interval:
|
|
@@ -394,6 +411,15 @@ class AgentClient:
|
|
|
394
411
|
def send_command_streaming(self, command: str, timeout: float = None,
|
|
395
412
|
progress_callback: Callable[[Dict[str, Any]], None] = None,
|
|
396
413
|
**args) -> Dict[str, Any]:
|
|
414
|
+
try:
|
|
415
|
+
return self._send_command_streaming(command, timeout, progress_callback, **args)
|
|
416
|
+
except KeyboardInterrupt:
|
|
417
|
+
self._release_on_exit = True
|
|
418
|
+
raise
|
|
419
|
+
|
|
420
|
+
def _send_command_streaming(self, command: str, timeout: float = None,
|
|
421
|
+
progress_callback: Callable[[Dict[str, Any]], None] = None,
|
|
422
|
+
**args) -> Dict[str, Any]:
|
|
397
423
|
if not self.sock:
|
|
398
424
|
self.connect()
|
|
399
425
|
|
|
@@ -458,6 +484,7 @@ class AgentClient:
|
|
|
458
484
|
if not response:
|
|
459
485
|
raise RuntimeError("No response from agent (timeout)")
|
|
460
486
|
|
|
487
|
+
self._print_notifications(response)
|
|
461
488
|
if response.get('error'):
|
|
462
489
|
raise RuntimeError(response['error'])
|
|
463
490
|
|
|
@@ -519,7 +546,7 @@ class AgentClient:
|
|
|
519
546
|
|
|
520
547
|
proc = subprocess.Popen(
|
|
521
548
|
cmd,
|
|
522
|
-
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.
|
|
549
|
+
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.CREATE_NO_WINDOW,
|
|
523
550
|
stdout=subprocess.DEVNULL,
|
|
524
551
|
stderr=stderr_file if stderr_file else subprocess.DEVNULL,
|
|
525
552
|
startupinfo=startupinfo
|
|
@@ -9,6 +9,7 @@ from replx.utils.constants import MAX_PAYLOAD_SIZE
|
|
|
9
9
|
|
|
10
10
|
_MAGIC = b'RPLX'
|
|
11
11
|
_VERSION = 1
|
|
12
|
+
_HEADER = struct.Struct('!4sBI')
|
|
12
13
|
|
|
13
14
|
_seq_lock = threading.Lock()
|
|
14
15
|
_seq_counter = int(time.time() * 1000000) & 0xFFFFFFFE # even start
|
|
@@ -24,38 +25,32 @@ def _next_seq() -> int:
|
|
|
24
25
|
class AgentProtocol:
|
|
25
26
|
@staticmethod
|
|
26
27
|
def encode_message(msg: Dict[str, Any]) -> bytes:
|
|
27
|
-
payload = json.dumps(msg).encode('utf-8')
|
|
28
|
+
payload = json.dumps(msg, separators=(',', ':')).encode('utf-8')
|
|
28
29
|
|
|
29
30
|
if len(payload) > MAX_PAYLOAD_SIZE:
|
|
30
31
|
raise ValueError(f"Message too large: {len(payload)} bytes")
|
|
31
32
|
|
|
32
|
-
header = (
|
|
33
|
-
_MAGIC +
|
|
34
|
-
struct.pack('!B', _VERSION) +
|
|
35
|
-
struct.pack('!I', len(payload))
|
|
36
|
-
)
|
|
33
|
+
header = _HEADER.pack(_MAGIC, _VERSION, len(payload))
|
|
37
34
|
|
|
38
35
|
return header + payload
|
|
39
36
|
|
|
40
37
|
@staticmethod
|
|
41
38
|
def decode_message(data: bytes) -> Optional[Dict[str, Any]]:
|
|
42
|
-
if len(data) <
|
|
39
|
+
if len(data) < _HEADER.size:
|
|
43
40
|
return None
|
|
44
41
|
|
|
45
|
-
|
|
42
|
+
magic, version, length = _HEADER.unpack_from(data)
|
|
43
|
+
if magic != _MAGIC:
|
|
46
44
|
return None
|
|
47
45
|
|
|
48
|
-
version = struct.unpack('!B', data[4:5])[0]
|
|
49
46
|
if version != _VERSION:
|
|
50
47
|
return None
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if len(data) < 9 + length:
|
|
49
|
+
payload_end = _HEADER.size + length
|
|
50
|
+
if len(data) < payload_end:
|
|
55
51
|
return None
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
return json.loads(payload.decode('utf-8'))
|
|
53
|
+
return json.loads(data[_HEADER.size:payload_end])
|
|
59
54
|
|
|
60
55
|
@staticmethod
|
|
61
56
|
def create_request(command: str, seq: int = None, ppid: int = None, port: str = None, **args) -> Dict[str, Any]:
|
|
@@ -2,14 +2,18 @@ import re
|
|
|
2
2
|
import sys
|
|
3
3
|
import time
|
|
4
4
|
import threading
|
|
5
|
+
from collections import deque
|
|
5
6
|
from concurrent.futures import Future as ConcurrentFuture
|
|
6
7
|
from dataclasses import dataclass, field
|
|
7
|
-
from typing import Optional, Dict, Any, List, Tuple
|
|
8
|
+
from typing import Optional, Dict, Any, Deque, List, Tuple
|
|
8
9
|
|
|
9
10
|
from replx.protocol import ReplProtocol, create_storage
|
|
10
11
|
from replx.utils import parse_device_banner, canon_port as _shared_canon_port
|
|
11
|
-
from replx.utils.constants import
|
|
12
|
+
from replx.utils.constants import (
|
|
13
|
+
CTRL_B, CTRL_C, CONNECT_DETECT_ROUNDS, CONNECT_DETECT_RETRY_DELAY,
|
|
14
|
+
)
|
|
12
15
|
from replx.utils.exceptions import TransportError
|
|
16
|
+
from replx.utils.device_info import get_root_fs_for_core
|
|
13
17
|
|
|
14
18
|
|
|
15
19
|
def _detect_device_info(transport, core: str, device: str = None) -> Tuple[str, str, str, str]:
|
|
@@ -58,7 +62,7 @@ class InteractiveSessionState:
|
|
|
58
62
|
seq: int = 0
|
|
59
63
|
client_addr: Optional[tuple] = None
|
|
60
64
|
echo: bool = True
|
|
61
|
-
input_queue:
|
|
65
|
+
input_queue: Deque[bytes] = field(default_factory=deque)
|
|
62
66
|
stop_requested: bool = False
|
|
63
67
|
thread: Optional[threading.Thread] = None
|
|
64
68
|
error: Optional[str] = None
|
|
@@ -71,7 +75,7 @@ class InteractiveSessionState:
|
|
|
71
75
|
self.seq = seq
|
|
72
76
|
self.client_addr = client_addr
|
|
73
77
|
self.echo = echo
|
|
74
|
-
self.input_queue
|
|
78
|
+
self.input_queue.clear()
|
|
75
79
|
self.stop_requested = False
|
|
76
80
|
self.error = None
|
|
77
81
|
|
|
@@ -82,7 +86,7 @@ class InteractiveSessionState:
|
|
|
82
86
|
self.ppid = None
|
|
83
87
|
self.seq = 0
|
|
84
88
|
self.client_addr = None
|
|
85
|
-
self.input_queue
|
|
89
|
+
self.input_queue.clear()
|
|
86
90
|
self.stop_requested = False
|
|
87
91
|
thread_to_join = self.thread
|
|
88
92
|
self.thread = None
|
|
@@ -104,14 +108,14 @@ class ReplSessionState:
|
|
|
104
108
|
ppid: Optional[int] = None
|
|
105
109
|
reader_future: Optional[ConcurrentFuture] = None
|
|
106
110
|
reader_thread: Optional[threading.Thread] = None
|
|
107
|
-
output_buffer:
|
|
111
|
+
output_buffer: bytearray = field(default_factory=bytearray)
|
|
108
112
|
buffer_lock: threading.Lock = field(default_factory=threading.Lock)
|
|
109
113
|
|
|
110
114
|
def start(self, ppid: int):
|
|
111
115
|
self.active = True
|
|
112
116
|
self.ppid = ppid
|
|
113
117
|
with self.buffer_lock:
|
|
114
|
-
self.output_buffer
|
|
118
|
+
self.output_buffer.clear()
|
|
115
119
|
|
|
116
120
|
def stop(self):
|
|
117
121
|
self.active = False
|
|
@@ -123,7 +127,7 @@ class ReplSessionState:
|
|
|
123
127
|
self.reader_thread.join(timeout=1)
|
|
124
128
|
self.reader_thread = None
|
|
125
129
|
with self.buffer_lock:
|
|
126
|
-
self.output_buffer
|
|
130
|
+
self.output_buffer.clear()
|
|
127
131
|
|
|
128
132
|
def is_owner(self, ppid: int) -> bool:
|
|
129
133
|
if ppid is None:
|
|
@@ -134,13 +138,13 @@ class ReplSessionState:
|
|
|
134
138
|
|
|
135
139
|
def append_output(self, data: bytes):
|
|
136
140
|
with self.buffer_lock:
|
|
137
|
-
self.output_buffer
|
|
141
|
+
self.output_buffer.extend(data)
|
|
138
142
|
|
|
139
143
|
def read_output(self) -> bytes:
|
|
140
144
|
with self.buffer_lock:
|
|
141
145
|
data = self.output_buffer
|
|
142
|
-
self.output_buffer =
|
|
143
|
-
|
|
146
|
+
self.output_buffer = bytearray()
|
|
147
|
+
return bytes(data)
|
|
144
148
|
|
|
145
149
|
@dataclass
|
|
146
150
|
class BoardConnection:
|
|
@@ -236,12 +240,26 @@ class BoardConnection:
|
|
|
236
240
|
self.last_command_time = time.time()
|
|
237
241
|
return True
|
|
238
242
|
|
|
239
|
-
def
|
|
243
|
+
def acquire_for_heartbeat(self) -> bool:
|
|
244
|
+
with self._busy_lock:
|
|
245
|
+
if self.busy or self.detached_running or self.repl.active or self.interactive.active:
|
|
246
|
+
return False
|
|
247
|
+
self.busy = True
|
|
248
|
+
self.busy_session = self.HEARTBEAT_SESSION_ID
|
|
249
|
+
self.busy_command = 'heartbeat'
|
|
250
|
+
self.busy_client = None
|
|
251
|
+
return True
|
|
252
|
+
|
|
253
|
+
def release(self, *, reset_liveness: bool = True, update_last_command_time: bool = True):
|
|
240
254
|
with self._busy_lock:
|
|
241
255
|
self.busy = False
|
|
242
256
|
self.busy_session = None
|
|
243
257
|
self.busy_command = None
|
|
244
258
|
self.busy_client = None
|
|
259
|
+
if update_last_command_time:
|
|
260
|
+
self.last_command_time = time.time()
|
|
261
|
+
if reset_liveness:
|
|
262
|
+
self._liveness_fail_count = 0
|
|
245
263
|
|
|
246
264
|
def set_busy_state(self, busy: bool, command: str = None, session: int = None,
|
|
247
265
|
client_addr: tuple = None) -> None:
|
|
@@ -292,6 +310,7 @@ class ConnectionManager:
|
|
|
292
310
|
# Per-port events used to serialize concurrent create_serial_connection
|
|
293
311
|
# calls for the same port (prevents double serial-open race).
|
|
294
312
|
self._port_creating: Dict[str, threading.Event] = {}
|
|
313
|
+
self._scan_lock = threading.Lock()
|
|
295
314
|
self._default_port: Optional[str] = None
|
|
296
315
|
|
|
297
316
|
@property
|
|
@@ -355,54 +374,89 @@ class ConnectionManager:
|
|
|
355
374
|
with self._connections_lock:
|
|
356
375
|
return len(self._connections)
|
|
357
376
|
|
|
377
|
+
@property
|
|
378
|
+
def scan_lock(self) -> threading.Lock:
|
|
379
|
+
return self._scan_lock
|
|
380
|
+
|
|
381
|
+
def reserve_probe_port(self, port: str) -> Optional[Tuple[str, threading.Event]]:
|
|
382
|
+
port_key = self._canon_port(port)
|
|
383
|
+
if not port_key:
|
|
384
|
+
return None
|
|
385
|
+
with self._connections_lock:
|
|
386
|
+
if port_key in self._connections or port_key in self._port_creating:
|
|
387
|
+
return None
|
|
388
|
+
event = threading.Event()
|
|
389
|
+
self._port_creating[port_key] = event
|
|
390
|
+
return port_key, event
|
|
391
|
+
|
|
392
|
+
def release_probe_port(self, reservation: Tuple[str, threading.Event]) -> None:
|
|
393
|
+
port_key, event = reservation
|
|
394
|
+
with self._connections_lock:
|
|
395
|
+
if self._port_creating.get(port_key) is event:
|
|
396
|
+
self._port_creating.pop(port_key, None)
|
|
397
|
+
event.set()
|
|
398
|
+
|
|
358
399
|
def create_serial_connection(
|
|
359
400
|
self,
|
|
360
401
|
port: str,
|
|
361
402
|
core: str = "RP2350",
|
|
362
403
|
device: str = None,
|
|
363
|
-
baudrate: int =
|
|
404
|
+
baudrate: Optional[int] = None
|
|
364
405
|
) -> Tuple[BoardConnection, Optional[str]]:
|
|
365
406
|
original_port = str(port).strip() if port is not None else ""
|
|
366
407
|
port_key = self._canon_port(original_port)
|
|
367
408
|
|
|
368
|
-
# --- Phase 1:
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
conn = self._connections[port_key]
|
|
372
|
-
if conn.is_connected():
|
|
373
|
-
return conn, None
|
|
374
|
-
# If another thread is already opening this port, get its event so
|
|
375
|
-
# we can wait for it instead of opening the same serial port twice.
|
|
376
|
-
if port_key in self._port_creating:
|
|
377
|
-
wait_event = self._port_creating[port_key]
|
|
378
|
-
our_event = None
|
|
379
|
-
else:
|
|
380
|
-
our_event = threading.Event()
|
|
381
|
-
self._port_creating[port_key] = our_event
|
|
382
|
-
wait_event = None
|
|
383
|
-
|
|
384
|
-
if wait_event is not None:
|
|
385
|
-
# Another thread is opening this port; wait then return its result.
|
|
386
|
-
wait_event.wait(timeout=30)
|
|
409
|
+
# --- Phase 1: serialize opens and scan probes for this port ---
|
|
410
|
+
deadline = time.monotonic() + 30.0
|
|
411
|
+
while True:
|
|
387
412
|
with self._connections_lock:
|
|
388
413
|
conn = self._connections.get(port_key)
|
|
389
414
|
if conn and conn.is_connected():
|
|
390
415
|
return conn, None
|
|
391
|
-
|
|
416
|
+
wait_event = self._port_creating.get(port_key)
|
|
417
|
+
if wait_event is None:
|
|
418
|
+
our_event = threading.Event()
|
|
419
|
+
self._port_creating[port_key] = our_event
|
|
420
|
+
break
|
|
421
|
+
|
|
422
|
+
remaining = deadline - time.monotonic()
|
|
423
|
+
if remaining <= 0 or not wait_event.wait(timeout=remaining):
|
|
424
|
+
return None, "Connection creation timed out (concurrent attempt)"
|
|
392
425
|
|
|
393
426
|
# --- Phase 2: we are the creator ---
|
|
394
427
|
try:
|
|
395
|
-
from replx.transport import create_transport
|
|
396
|
-
|
|
397
|
-
|
|
428
|
+
from replx.transport import create_transport, repl_baud_candidates
|
|
429
|
+
|
|
430
|
+
rates = [baudrate] if baudrate else repl_baud_candidates()
|
|
431
|
+
transport = create_transport(f"serial:{original_port}", baudrate=rates[0])
|
|
432
|
+
|
|
433
|
+
# A board that was just reset (e.g. by esptool or a USB-UART bridge's
|
|
434
|
+
# DTR/RTS lines) may still be booting, so a failed sweep is retried.
|
|
435
|
+
version = "?"
|
|
436
|
+
current_rate = rates[0]
|
|
437
|
+
for attempt in range(CONNECT_DETECT_ROUNDS):
|
|
438
|
+
if attempt:
|
|
439
|
+
time.sleep(CONNECT_DETECT_RETRY_DELAY)
|
|
440
|
+
for rate in rates:
|
|
441
|
+
if rate != current_rate:
|
|
442
|
+
transport.set_baudrate(rate)
|
|
443
|
+
current_rate = rate
|
|
444
|
+
version, detected_core, detected_device, manufacturer = _detect_device_info(
|
|
445
|
+
transport, core, device
|
|
446
|
+
)
|
|
447
|
+
if version != "?":
|
|
448
|
+
break
|
|
449
|
+
if version != "?":
|
|
450
|
+
break
|
|
451
|
+
else:
|
|
452
|
+
# No rate produced a recognisable REPL; keep the primary rate as before.
|
|
453
|
+
if current_rate != rates[0]:
|
|
454
|
+
transport.set_baudrate(rates[0])
|
|
398
455
|
|
|
399
|
-
|
|
400
|
-
|
|
456
|
+
device_root_fs = get_root_fs_for_core(detected_core)
|
|
457
|
+
repl_protocol = ReplProtocol(
|
|
458
|
+
transport, core=detected_core, device_root_fs=device_root_fs
|
|
401
459
|
)
|
|
402
|
-
|
|
403
|
-
repl_protocol = ReplProtocol(transport)
|
|
404
|
-
|
|
405
|
-
device_root_fs = "/"
|
|
406
460
|
if version != "?":
|
|
407
461
|
try:
|
|
408
462
|
result = repl_protocol.exec("import os; print(os.getcwd())")
|
|
@@ -412,14 +466,16 @@ class ConnectionManager:
|
|
|
412
466
|
cwd = result.strip() if result else "/"
|
|
413
467
|
else:
|
|
414
468
|
cwd = "/"
|
|
415
|
-
if cwd.startswith('/flash'):
|
|
469
|
+
if cwd == '/flash' or cwd.startswith('/flash/'):
|
|
416
470
|
device_root_fs = '/flash/'
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
471
|
+
except Exception as exc:
|
|
472
|
+
print(
|
|
473
|
+
f"Could not query filesystem root on {original_port}; "
|
|
474
|
+
f"using {device_root_fs} from the {detected_core} profile: {exc}",
|
|
475
|
+
file=sys.stderr,
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
repl_protocol.device_root_fs = device_root_fs
|
|
423
479
|
|
|
424
480
|
file_system = create_storage(
|
|
425
481
|
repl_protocol,
|