replx 1.4__tar.gz → 1.6__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.6}/PKG-INFO +1 -1
- {replx-1.4 → replx-1.6}/replx/__init__.py +1 -1
- {replx-1.4 → replx-1.6}/replx/cli/agent/client/core.py +7 -4
- {replx-1.4 → replx-1.6}/replx/cli/agent/server/connection_manager.py +9 -2
- {replx-1.4 → replx-1.6}/replx/cli/agent/server/handlers/exec.py +8 -5
- {replx-1.4 → replx-1.6}/replx/cli/agent/server/handlers/session.py +3 -6
- {replx-1.4 → replx-1.6}/replx/cli/commands/exec.py +3 -5
- {replx-1.4 → replx-1.6}/replx/cli/commands/utility.py +13 -13
- {replx-1.4 → replx-1.6/replx.egg-info}/PKG-INFO +1 -1
- replx-1.6/test/test_termio.py +70 -0
- replx-1.4/test/test_termio.py +0 -62
- {replx-1.4 → replx-1.6}/LICENSE +0 -0
- {replx-1.4 → replx-1.6}/README.md +0 -0
- {replx-1.4 → replx-1.6}/pyproject.toml +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/__init__.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/agent/__init__.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/agent/client/__init__.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/agent/client/session.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/agent/protocol.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/agent/server/__init__.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/agent/server/__main__.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/agent/server/command_dispatcher.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/agent/server/core.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/agent/server/handlers/__init__.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/agent/server/handlers/filesystem.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/agent/server/handlers/repl.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/agent/server/handlers/transfer.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/agent/server/session_manager.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/app.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/commands/__init__.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/commands/device.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/commands/file.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/commands/firmware.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/commands/package.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/config.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/connection.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/helpers/__init__.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/helpers/compiler.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/helpers/environment.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/helpers/output.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/helpers/registry.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/helpers/scanner.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/helpers/store.py +0 -0
- {replx-1.4 → replx-1.6}/replx/cli/helpers/updater.py +0 -0
- {replx-1.4 → replx-1.6}/replx/commands.py +0 -0
- {replx-1.4 → replx-1.6}/replx/protocol/__init__.py +0 -0
- {replx-1.4 → replx-1.6}/replx/protocol/repl.py +0 -0
- {replx-1.4 → replx-1.6}/replx/protocol/storage.py +0 -0
- {replx-1.4 → replx-1.6}/replx/terminal.py +0 -0
- {replx-1.4 → replx-1.6}/replx/tests/__init__.py +0 -0
- {replx-1.4 → replx-1.6}/replx/tests/test_agent_asyncio.py +0 -0
- {replx-1.4 → replx-1.6}/replx/tests/test_agent_port_canonicalization.py +0 -0
- {replx-1.4 → replx-1.6}/replx/tests/test_agent_thread_pool.py +0 -0
- {replx-1.4 → replx-1.6}/replx/tests/test_compiler_arch.py +0 -0
- {replx-1.4 → replx-1.6}/replx/tests/test_connection_info_lookup.py +0 -0
- {replx-1.4 → replx-1.6}/replx/tests/test_device_info_esp_multi_core.py +0 -0
- {replx-1.4 → replx-1.6}/replx/tests/test_disconnect_cleanup.py +0 -0
- {replx-1.4 → replx-1.6}/replx/tests/test_lock_cleanup.py +0 -0
- {replx-1.4 → replx-1.6}/replx/tests/test_pkg_local_version.py +0 -0
- {replx-1.4 → replx-1.6}/replx/tests/test_pkg_search_scope_filter.py +0 -0
- {replx-1.4 → replx-1.6}/replx/tests/test_repl_reader_task.py +0 -0
- {replx-1.4 → replx-1.6}/replx/tests/test_session_disconnect_releases_shared_port.py +0 -0
- {replx-1.4 → replx-1.6}/replx/tests/test_session_id_fallback.py +0 -0
- {replx-1.4 → replx-1.6}/replx/tests/test_shutdown_status_message.py +0 -0
- {replx-1.4 → replx-1.6}/replx/tests/test_windows_com_port_normalization.py +0 -0
- {replx-1.4 → replx-1.6}/replx/transport/__init__.py +0 -0
- {replx-1.4 → replx-1.6}/replx/transport/base.py +0 -0
- {replx-1.4 → replx-1.6}/replx/transport/serial.py +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/_thread.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/aioble/__init__.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/array.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/asyncio/__init__.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/binascii.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/bluetooth.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/builtins.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/cmath.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/collections.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/cryptolib.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/deflate.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/errno.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/framebuf.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/gc.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/hashlib.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/heapq.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/io.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/json.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/lwip.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/machine.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/math.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/micropython.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/mip/__init__.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/network.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/ntptime.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/os.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/platform.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/random.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/re.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/requests/__init__.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/select.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/socket.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/ssl.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/struct.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/sys.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/time.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/tls.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/uasyncio.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/uctypes.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/urequests.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm/vfs.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/binascii.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/errno.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/hashlib.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/io.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/json.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/machine.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/math.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/micropython.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/network.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/os.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/select.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/socket.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/ssl.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/struct.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/sys.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/time.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/ubinascii.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/ucryptolib.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/uerrno.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/uhashlib.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/uio.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/ujson.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/umachine.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/uos.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/uselect.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/usocket.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/ussl.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/ustruct.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/utime.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/core/ESP32/aioespnow.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/core/ESP32/esp.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/core/ESP32/esp32.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/core/ESP32/espnow.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/core/MIMXRT1062DVJ6A/mimxrt.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/typehints/core/RP2350/rp2.pyi +0 -0
- {replx-1.4 → replx-1.6}/replx/utils/__init__.py +0 -0
- {replx-1.4 → replx-1.6}/replx/utils/constants.py +0 -0
- {replx-1.4 → replx-1.6}/replx/utils/device_info.py +0 -0
- {replx-1.4 → replx-1.6}/replx/utils/exceptions.py +0 -0
- {replx-1.4 → replx-1.6}/replx.egg-info/SOURCES.txt +0 -0
- {replx-1.4 → replx-1.6}/replx.egg-info/dependency_links.txt +0 -0
- {replx-1.4 → replx-1.6}/replx.egg-info/entry_points.txt +0 -0
- {replx-1.4 → replx-1.6}/replx.egg-info/requires.txt +0 -0
- {replx-1.4 → replx-1.6}/replx.egg-info/top_level.txt +0 -0
- {replx-1.4 → replx-1.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: replx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6
|
|
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
|
|
|
@@ -455,6 +455,10 @@ class ConnectionManager:
|
|
|
455
455
|
|
|
456
456
|
conn = self._connections.pop(key)
|
|
457
457
|
|
|
458
|
+
# Remember whether a detached (user) script was running before clearing the flag.
|
|
459
|
+
# If so, the device is executing paste-mode code on its UART, NOT in raw REPL.
|
|
460
|
+
# Sending CTRL_B in that state would inject \x02 into the running script's stdin.
|
|
461
|
+
was_detached = conn.is_detached()
|
|
458
462
|
conn.stop_detached()
|
|
459
463
|
|
|
460
464
|
if conn.repl.active:
|
|
@@ -469,8 +473,11 @@ class ConnectionManager:
|
|
|
469
473
|
transport = conn.repl_protocol.transport
|
|
470
474
|
if transport:
|
|
471
475
|
try:
|
|
472
|
-
|
|
473
|
-
|
|
476
|
+
if not was_detached:
|
|
477
|
+
# Only send CTRL_B to leave raw REPL when no user script
|
|
478
|
+
# was running; otherwise the byte contaminates the script stdin.
|
|
479
|
+
transport.write(CTRL_B)
|
|
480
|
+
time.sleep(0.1 if sys.platform == 'win32' else 0.05)
|
|
474
481
|
except Exception:
|
|
475
482
|
pass
|
|
476
483
|
transport.close()
|
|
@@ -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()
|
|
@@ -344,13 +344,11 @@ By default, runs a file from your computer. Use -d to run from device.
|
|
|
344
344
|
if stream_type == "stderr":
|
|
345
345
|
stderr_buffer.extend(data)
|
|
346
346
|
else:
|
|
347
|
-
# Normalize line endings for all platforms
|
|
348
347
|
if not IS_WINDOWS:
|
|
349
|
-
|
|
350
|
-
data = data.replace(b'\r
|
|
351
|
-
data = data.replace(b'\
|
|
348
|
+
data = data.replace(b'\r\n', b'\n') # CRLF -> LF
|
|
349
|
+
data = data.replace(b'\r', b'\n') # lone CR -> LF
|
|
350
|
+
data = data.replace(b'\n', b'\r\n') # LF -> CRLF
|
|
352
351
|
else:
|
|
353
|
-
# Windows: just remove CR (terminal handles LF)
|
|
354
352
|
data = data.replace(b'\r', b'')
|
|
355
353
|
|
|
356
354
|
if data:
|
|
@@ -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.6
|
|
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,70 @@
|
|
|
1
|
+
from termio import ReplSerial
|
|
2
|
+
from utools import rand, clamp, intervalChecker
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def new_token() -> str:
|
|
7
|
+
return f"{rand(4):08x}" # 예: "a3f2c1b4"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
with ReplSerial(timeout=0) as ser:
|
|
11
|
+
token = new_token()
|
|
12
|
+
authed = False
|
|
13
|
+
every_sess = intervalChecker(1000) # 미인증: 1초마다 SESSION 재전송
|
|
14
|
+
heartbeat = intervalChecker(5000) # 인증 후: 5초마다 ALIVE
|
|
15
|
+
|
|
16
|
+
# 최초 토큰 전송
|
|
17
|
+
ser.write(f"SESSION={token}\r\n".encode())
|
|
18
|
+
|
|
19
|
+
while True:
|
|
20
|
+
# timeout=0이므로 \r\n이 두 pump 사이클에 걸려 도착할 수 있음 → b"\n" 사용
|
|
21
|
+
line = ser.read_until(b"\n", max_size=64)
|
|
22
|
+
if line and line.endswith(b"\n"):
|
|
23
|
+
cmd = line.decode("utf-8", "replace").strip()
|
|
24
|
+
|
|
25
|
+
# 인증 (항상 허용)
|
|
26
|
+
if cmd.startswith("AUTH "):
|
|
27
|
+
if cmd[5:] == token:
|
|
28
|
+
authed = True
|
|
29
|
+
ser.write(b"OK authenticated\r\n")
|
|
30
|
+
else:
|
|
31
|
+
authed = False
|
|
32
|
+
ser.write(b"ERR wrong token\r\n")
|
|
33
|
+
|
|
34
|
+
# 미인증 요청 거부
|
|
35
|
+
elif not authed:
|
|
36
|
+
ser.write(b"ERR not authenticated\r\n")
|
|
37
|
+
|
|
38
|
+
# 인증 후 커맨드
|
|
39
|
+
elif cmd.startswith("SET_PWM:"):
|
|
40
|
+
try:
|
|
41
|
+
duty = clamp(int(cmd[8:]), 0, 100)
|
|
42
|
+
# pwm.duty(int(duty * 655.35)) # 0–100% → 0–65535
|
|
43
|
+
ser.write(f"OK pwm={duty}%\r\n".encode())
|
|
44
|
+
except (ValueError, TypeError):
|
|
45
|
+
ser.write(b"ERR bad value\r\n")
|
|
46
|
+
|
|
47
|
+
elif cmd.startswith("SET_FREQ:"):
|
|
48
|
+
try:
|
|
49
|
+
freq = clamp(int(cmd[9:]), 1, 20_000)
|
|
50
|
+
# pwm.freq(freq)
|
|
51
|
+
ser.write(f"OK freq={freq}Hz\r\n".encode())
|
|
52
|
+
except (ValueError, TypeError):
|
|
53
|
+
ser.write(b"ERR bad value\r\n")
|
|
54
|
+
|
|
55
|
+
elif cmd == "RESET_SESSION":
|
|
56
|
+
token = new_token()
|
|
57
|
+
authed = False
|
|
58
|
+
every_sess = intervalChecker(1000)
|
|
59
|
+
ser.write(f"SESSION={token}\r\n".encode())
|
|
60
|
+
|
|
61
|
+
else:
|
|
62
|
+
ser.write(b"ERR unknown cmd\r\n")
|
|
63
|
+
|
|
64
|
+
# 주기적 전송 (커맨드 수신 여부와 무관)
|
|
65
|
+
if not authed and every_sess():
|
|
66
|
+
ser.write(f"SESSION={token}\r\n".encode())
|
|
67
|
+
elif authed and heartbeat():
|
|
68
|
+
ser.write(b"ALIVE\r\n")
|
|
69
|
+
|
|
70
|
+
time.sleep_ms(10)
|
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.6}/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
|