replx 1.4__tar.gz → 1.5__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.4/replx.egg-info → replx-1.5}/PKG-INFO +1 -1
- {replx-1.4 → replx-1.5}/replx/__init__.py +1 -1
- {replx-1.4 → replx-1.5}/replx/cli/agent/client/core.py +7 -4
- {replx-1.4 → replx-1.5}/replx/cli/agent/server/handlers/exec.py +8 -5
- {replx-1.4 → replx-1.5}/replx/cli/agent/server/handlers/session.py +3 -6
- {replx-1.4 → replx-1.5}/replx/cli/commands/utility.py +13 -13
- {replx-1.4 → replx-1.5/replx.egg-info}/PKG-INFO +1 -1
- replx-1.5/test/test_termio.py +42 -0
- replx-1.4/test/test_termio.py +0 -62
- {replx-1.4 → replx-1.5}/LICENSE +0 -0
- {replx-1.4 → replx-1.5}/README.md +0 -0
- {replx-1.4 → replx-1.5}/pyproject.toml +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/__init__.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/agent/__init__.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/agent/client/__init__.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/agent/client/session.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/agent/protocol.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/agent/server/__init__.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/agent/server/__main__.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/agent/server/command_dispatcher.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/agent/server/connection_manager.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/agent/server/core.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/agent/server/handlers/__init__.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/agent/server/handlers/filesystem.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/agent/server/handlers/repl.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/agent/server/handlers/transfer.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/agent/server/session_manager.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/app.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/commands/__init__.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/commands/device.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/commands/exec.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/commands/file.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/commands/firmware.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/commands/package.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/config.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/connection.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/helpers/__init__.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/helpers/compiler.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/helpers/environment.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/helpers/output.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/helpers/registry.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/helpers/scanner.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/helpers/store.py +0 -0
- {replx-1.4 → replx-1.5}/replx/cli/helpers/updater.py +0 -0
- {replx-1.4 → replx-1.5}/replx/commands.py +0 -0
- {replx-1.4 → replx-1.5}/replx/protocol/__init__.py +0 -0
- {replx-1.4 → replx-1.5}/replx/protocol/repl.py +0 -0
- {replx-1.4 → replx-1.5}/replx/protocol/storage.py +0 -0
- {replx-1.4 → replx-1.5}/replx/terminal.py +0 -0
- {replx-1.4 → replx-1.5}/replx/tests/__init__.py +0 -0
- {replx-1.4 → replx-1.5}/replx/tests/test_agent_asyncio.py +0 -0
- {replx-1.4 → replx-1.5}/replx/tests/test_agent_port_canonicalization.py +0 -0
- {replx-1.4 → replx-1.5}/replx/tests/test_agent_thread_pool.py +0 -0
- {replx-1.4 → replx-1.5}/replx/tests/test_compiler_arch.py +0 -0
- {replx-1.4 → replx-1.5}/replx/tests/test_connection_info_lookup.py +0 -0
- {replx-1.4 → replx-1.5}/replx/tests/test_device_info_esp_multi_core.py +0 -0
- {replx-1.4 → replx-1.5}/replx/tests/test_disconnect_cleanup.py +0 -0
- {replx-1.4 → replx-1.5}/replx/tests/test_lock_cleanup.py +0 -0
- {replx-1.4 → replx-1.5}/replx/tests/test_pkg_local_version.py +0 -0
- {replx-1.4 → replx-1.5}/replx/tests/test_pkg_search_scope_filter.py +0 -0
- {replx-1.4 → replx-1.5}/replx/tests/test_repl_reader_task.py +0 -0
- {replx-1.4 → replx-1.5}/replx/tests/test_session_disconnect_releases_shared_port.py +0 -0
- {replx-1.4 → replx-1.5}/replx/tests/test_session_id_fallback.py +0 -0
- {replx-1.4 → replx-1.5}/replx/tests/test_shutdown_status_message.py +0 -0
- {replx-1.4 → replx-1.5}/replx/tests/test_windows_com_port_normalization.py +0 -0
- {replx-1.4 → replx-1.5}/replx/transport/__init__.py +0 -0
- {replx-1.4 → replx-1.5}/replx/transport/base.py +0 -0
- {replx-1.4 → replx-1.5}/replx/transport/serial.py +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/_thread.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/aioble/__init__.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/array.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/asyncio/__init__.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/binascii.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/bluetooth.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/builtins.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/cmath.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/collections.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/cryptolib.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/deflate.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/errno.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/framebuf.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/gc.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/hashlib.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/heapq.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/io.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/json.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/lwip.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/machine.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/math.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/micropython.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/mip/__init__.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/network.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/ntptime.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/os.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/platform.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/random.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/re.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/requests/__init__.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/select.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/socket.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/ssl.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/struct.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/sys.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/time.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/tls.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/uasyncio.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/uctypes.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/urequests.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm/vfs.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/binascii.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/errno.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/hashlib.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/io.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/json.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/machine.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/math.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/micropython.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/network.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/os.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/select.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/socket.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/ssl.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/struct.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/sys.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/time.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/ubinascii.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/ucryptolib.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/uerrno.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/uhashlib.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/uio.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/ujson.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/umachine.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/uos.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/uselect.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/usocket.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/ussl.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/ustruct.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/utime.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/core/ESP32/aioespnow.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/core/ESP32/esp.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/core/ESP32/esp32.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/core/ESP32/espnow.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/core/MIMXRT1062DVJ6A/mimxrt.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/typehints/core/RP2350/rp2.pyi +0 -0
- {replx-1.4 → replx-1.5}/replx/utils/__init__.py +0 -0
- {replx-1.4 → replx-1.5}/replx/utils/constants.py +0 -0
- {replx-1.4 → replx-1.5}/replx/utils/device_info.py +0 -0
- {replx-1.4 → replx-1.5}/replx/utils/exceptions.py +0 -0
- {replx-1.4 → replx-1.5}/replx.egg-info/SOURCES.txt +0 -0
- {replx-1.4 → replx-1.5}/replx.egg-info/dependency_links.txt +0 -0
- {replx-1.4 → replx-1.5}/replx.egg-info/entry_points.txt +0 -0
- {replx-1.4 → replx-1.5}/replx.egg-info/requires.txt +0 -0
- {replx-1.4 → replx-1.5}/replx.egg-info/top_level.txt +0 -0
- {replx-1.4 → replx-1.5}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: replx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5
|
|
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@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -326,13 +326,14 @@ class AgentClient:
|
|
|
326
326
|
if port:
|
|
327
327
|
cmd.append(str(port))
|
|
328
328
|
|
|
329
|
+
proc = None
|
|
329
330
|
if background:
|
|
330
331
|
if sys.platform == 'win32':
|
|
331
332
|
startupinfo = subprocess.STARTUPINFO()
|
|
332
333
|
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
333
334
|
startupinfo.wShowWindow = 0
|
|
334
335
|
|
|
335
|
-
subprocess.Popen(
|
|
336
|
+
proc = subprocess.Popen(
|
|
336
337
|
cmd,
|
|
337
338
|
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS,
|
|
338
339
|
stdout=subprocess.DEVNULL,
|
|
@@ -340,7 +341,7 @@ class AgentClient:
|
|
|
340
341
|
startupinfo=startupinfo
|
|
341
342
|
)
|
|
342
343
|
else:
|
|
343
|
-
subprocess.Popen(
|
|
344
|
+
proc = subprocess.Popen(
|
|
344
345
|
cmd,
|
|
345
346
|
stdout=subprocess.DEVNULL,
|
|
346
347
|
stderr=subprocess.DEVNULL,
|
|
@@ -349,10 +350,12 @@ class AgentClient:
|
|
|
349
350
|
close_fds=True
|
|
350
351
|
)
|
|
351
352
|
else:
|
|
352
|
-
subprocess.Popen(cmd)
|
|
353
|
+
proc = subprocess.Popen(cmd)
|
|
353
354
|
|
|
354
|
-
for
|
|
355
|
+
for _ in range(100):
|
|
355
356
|
time.sleep(0.1)
|
|
357
|
+
if proc is not None and proc.poll() is not None:
|
|
358
|
+
raise RuntimeError(f"Failed to start agent (process exited with code {proc.returncode})")
|
|
356
359
|
if AgentClient.is_agent_running(port=port):
|
|
357
360
|
return True
|
|
358
361
|
|
|
@@ -143,11 +143,14 @@ class ExecCommandsMixin:
|
|
|
143
143
|
self.connection_manager.disconnect_all()
|
|
144
144
|
self.session_manager.clear_all_sessions()
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
# Defer actual cleanup so the response can be sent before the transport closes.
|
|
147
|
+
# cleanup() properly sets _stop_event, which stops the asyncio loop and
|
|
148
|
+
# releases the UDP port — preventing "Address already in use" on next start.
|
|
149
|
+
def _deferred_cleanup():
|
|
150
|
+
time.sleep(0.2)
|
|
151
|
+
self.cleanup()
|
|
152
|
+
|
|
153
|
+
threading.Thread(target=_deferred_cleanup, daemon=True).start()
|
|
151
154
|
|
|
152
155
|
return {"shutdown": True}
|
|
153
156
|
|
|
@@ -141,12 +141,9 @@ class SessionCommandsMixin:
|
|
|
141
141
|
if not self.session_manager.has_sessions():
|
|
142
142
|
def delayed_shutdown():
|
|
143
143
|
time.sleep(0.3)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
self.server_socket.close()
|
|
148
|
-
except Exception:
|
|
149
|
-
pass
|
|
144
|
+
# cleanup() properly sets _stop_event to stop the asyncio loop
|
|
145
|
+
# and releases the UDP port so a new agent can bind it immediately.
|
|
146
|
+
self.cleanup()
|
|
150
147
|
|
|
151
148
|
shutdown_thread = threading.Thread(target=delayed_shutdown, daemon=True)
|
|
152
149
|
shutdown_thread.start()
|
|
@@ -978,7 +978,8 @@ Format (erase) the filesystem on the connected device.
|
|
|
978
978
|
|
|
979
979
|
[bold cyan]After formatting:[/bold cyan]
|
|
980
980
|
• Device has empty filesystem
|
|
981
|
-
|
|
981
|
+
• Reinstall libraries: [green]replx pkg update core.all[/green]
|
|
982
|
+
• Install device libs: [green]replx pkg update device.all[/green]
|
|
982
983
|
• Or use [green]replx init[/green] instead (format + install)
|
|
983
984
|
|
|
984
985
|
[bold cyan]Related:[/bold cyan]
|
|
@@ -1088,8 +1089,6 @@ def init(
|
|
|
1088
1089
|
help_text = """\
|
|
1089
1090
|
Completely reset device: format filesystem and install all libraries.
|
|
1090
1091
|
|
|
1091
|
-
[bold yellow]⚠ WARNING: This deletes ALL files and reinstalls from scratch![/bold yellow]
|
|
1092
|
-
|
|
1093
1092
|
[bold cyan]Usage:[/bold cyan]
|
|
1094
1093
|
replx init
|
|
1095
1094
|
|
|
@@ -1114,13 +1113,15 @@ Completely reset device: format filesystem and install all libraries.
|
|
|
1114
1113
|
• Or set up workspace: [green]replx setup[/green]
|
|
1115
1114
|
|
|
1116
1115
|
[bold cyan]Equivalent to:[/bold cyan]
|
|
1117
|
-
replx pkg download
|
|
1118
|
-
replx format
|
|
1119
|
-
replx pkg update
|
|
1116
|
+
replx pkg download [dim]# Download libraries (if needed)[/dim]
|
|
1117
|
+
replx format [dim]# Erase device storage[/dim]
|
|
1118
|
+
replx pkg update core.all [dim]# Install all core libs[/dim]
|
|
1119
|
+
replx pkg update device.all [dim]# Install all device libs[/dim]
|
|
1120
1120
|
|
|
1121
1121
|
[bold cyan]Related:[/bold cyan]
|
|
1122
|
-
replx format
|
|
1123
|
-
replx pkg update
|
|
1122
|
+
replx format [dim]# Just erase (no install)[/dim]
|
|
1123
|
+
replx pkg update core.all [dim]# Just install core (no format)[/dim]
|
|
1124
|
+
replx pkg update device.all [dim]# Just install device (no format)[/dim]"""
|
|
1124
1125
|
OutputHelper.print_panel(help_text, border_style="dim")
|
|
1125
1126
|
console.print()
|
|
1126
1127
|
raise typer.Exit()
|
|
@@ -1270,11 +1271,11 @@ Completely reset device: format filesystem and install all libraries.
|
|
|
1270
1271
|
install_stats = {}
|
|
1271
1272
|
|
|
1272
1273
|
try:
|
|
1273
|
-
specs_to_install = ["core
|
|
1274
|
+
specs_to_install = ["core.all"]
|
|
1274
1275
|
|
|
1275
1276
|
dev_src = os.path.join(StoreManager.pkg_root(), "device", device_name_to_path(STATE.device), "src")
|
|
1276
1277
|
if os.path.isdir(dev_src):
|
|
1277
|
-
specs_to_install.append("device
|
|
1278
|
+
specs_to_install.append("device.all")
|
|
1278
1279
|
|
|
1279
1280
|
from rich.panel import Panel as RichPanel
|
|
1280
1281
|
initial_panel = RichPanel(
|
|
@@ -1290,11 +1291,10 @@ Completely reset device: format filesystem and install all libraries.
|
|
|
1290
1291
|
install_stats[spec_item] = result
|
|
1291
1292
|
|
|
1292
1293
|
summary_parts = []
|
|
1293
|
-
for spec_key in ["core
|
|
1294
|
+
for spec_key in ["core.all", "device.all"]:
|
|
1294
1295
|
if spec_key in install_stats:
|
|
1295
1296
|
stats = install_stats[spec_key]
|
|
1296
|
-
|
|
1297
|
-
summary_parts.append(f"🔧 {label}/ {stats['files']} file(s) {format_bytes(stats['bytes'])}")
|
|
1297
|
+
summary_parts.append(f"🔧 {spec_key} {stats['files']} file(s) {format_bytes(stats['bytes'])}")
|
|
1298
1298
|
|
|
1299
1299
|
summary_line = " ".join(summary_parts)
|
|
1300
1300
|
live.update(RichPanel(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: replx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5
|
|
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@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from termio import ReplSerial
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def discard_until_eol(ser, eol=b"\r\n", max_size=64):
|
|
6
|
+
"""너무 긴 줄로 잘린 뒤, 다음 개행(\r\n)이 나타날 때까지 남은 바이트를 버립니다."""
|
|
7
|
+
old_timeout = ser.timeout
|
|
8
|
+
ser.timeout = 0.15
|
|
9
|
+
try:
|
|
10
|
+
while True:
|
|
11
|
+
chunk = ser.read_until(eol, max_size=max_size)
|
|
12
|
+
if not chunk:
|
|
13
|
+
# 타임아웃: 버퍼에 남은 조각이 있으면 read()로 비운 뒤 재시도합니다.
|
|
14
|
+
n = ser.in_waiting
|
|
15
|
+
if n:
|
|
16
|
+
ser.read(n)
|
|
17
|
+
continue
|
|
18
|
+
return
|
|
19
|
+
if chunk.endswith(eol):
|
|
20
|
+
return
|
|
21
|
+
finally:
|
|
22
|
+
ser.timeout = old_timeout
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
with ReplSerial(timeout=0.1) as ser:
|
|
26
|
+
while True:
|
|
27
|
+
line = ser.read_until(b"\r\n", max_size=64)
|
|
28
|
+
if not line:
|
|
29
|
+
time.sleep_ms(10)
|
|
30
|
+
continue
|
|
31
|
+
|
|
32
|
+
if not line.endswith(b"\r\n"):
|
|
33
|
+
# max_size(64)로 잘려 나온 경우 → 나머지 조각을 버리고 다음 줄부터 다시 맞춥니다.
|
|
34
|
+
print("too long line -> discard")
|
|
35
|
+
discard_until_eol(ser, b"\r\n", max_size=64)
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
cmd = line[:-2].decode("utf-8", "replace")
|
|
39
|
+
print("cmd=", cmd)
|
|
40
|
+
ser.write(("OK:" + cmd + "\r\n").encode("utf-8"))
|
|
41
|
+
if cmd == "quit":
|
|
42
|
+
break
|
replx-1.4/test/test_termio.py
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
from termio import ReplSerial
|
|
2
|
-
import time
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def _drain_into(ser, rx_buf, per_loop_budget=128, read_chunk=32, max_rx_buf=512):
|
|
6
|
-
drained = 0
|
|
7
|
-
|
|
8
|
-
while True:
|
|
9
|
-
n = ser.in_waiting
|
|
10
|
-
if n <= 0:
|
|
11
|
-
break
|
|
12
|
-
if drained >= per_loop_budget:
|
|
13
|
-
break
|
|
14
|
-
|
|
15
|
-
# 한 번에 너무 크게 읽지 말고(파싱 지연/메모리), 일정 chunk로 끊어서 읽기
|
|
16
|
-
want = n
|
|
17
|
-
if want > read_chunk:
|
|
18
|
-
want = read_chunk
|
|
19
|
-
if want > (per_loop_budget - drained):
|
|
20
|
-
want = per_loop_budget - drained
|
|
21
|
-
|
|
22
|
-
chunk = ser.read(want) # timeout=0 이므로 즉시 반환
|
|
23
|
-
if not chunk:
|
|
24
|
-
break
|
|
25
|
-
rx_buf.extend(chunk)
|
|
26
|
-
drained += len(chunk)
|
|
27
|
-
|
|
28
|
-
# 상한 초과 시 오래된 데이터부터 버려 폭주 방지(실전에서는 정책을 명확히 정하세요)
|
|
29
|
-
if len(rx_buf) > max_rx_buf:
|
|
30
|
-
rx_buf = rx_buf[-max_rx_buf:]
|
|
31
|
-
|
|
32
|
-
return rx_buf, drained
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
with ReplSerial(timeout=0) as ser:
|
|
36
|
-
rx = bytearray()
|
|
37
|
-
last_tick = time.ticks_ms()
|
|
38
|
-
|
|
39
|
-
while True:
|
|
40
|
-
# (예시) 메인 루프 주기 작업: 200ms마다 tick
|
|
41
|
-
if time.ticks_diff(time.ticks_ms(), last_tick) >= 200:
|
|
42
|
-
last_tick = time.ticks_ms()
|
|
43
|
-
# print("tick") # 필요 시만 사용(로그가 수신을 가립니다)
|
|
44
|
-
|
|
45
|
-
# 1) 수신 버퍼에 쌓인 데이터를 "가능한 만큼" 꺼내 임시 버퍼(rx)에 담기
|
|
46
|
-
# (단, 이번 루프에서는 최대 per_loop_budget 바이트까지만 처리)
|
|
47
|
-
rx, drained = _drain_into(ser, rx, 128, 32, 512)
|
|
48
|
-
|
|
49
|
-
# 2) 완성된 프레임(\r)만 꺼내 처리
|
|
50
|
-
while True:
|
|
51
|
-
i = rx.find(b"\r")
|
|
52
|
-
if i < 0:
|
|
53
|
-
break
|
|
54
|
-
|
|
55
|
-
frame = bytes(rx[:i])
|
|
56
|
-
rx = rx[i + 1:]
|
|
57
|
-
|
|
58
|
-
text = frame.decode("utf-8", "replace")
|
|
59
|
-
print("[FRAME]", text)
|
|
60
|
-
|
|
61
|
-
# 3) 다른 일을 할 시간을 양보
|
|
62
|
-
time.sleep_ms(10)
|
{replx-1.4 → replx-1.5}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|