Compare commits
7 Commits
e86312437c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 580fd11aca | |||
|
|
d6331a105c | ||
| 2f3db9272b | |||
| 713cd6e9a1 | |||
| 9e72c62481 | |||
|
|
eee9327e15 | ||
|
|
5f7acf38f0 |
2
Pipfile
2
Pipfile
@@ -16,5 +16,5 @@ python_version = "3"
|
|||||||
[scripts]
|
[scripts]
|
||||||
web = "python web.py"
|
web = "python web.py"
|
||||||
cli = "python cli.py"
|
cli = "python cli.py"
|
||||||
build = "pyinstaller --clean led-cli.spec"
|
build = "pyinstaller --clean --onefile --name led-cli --paths lib cli.py"
|
||||||
install = "pipenv install"
|
install = "pipenv install"
|
||||||
|
|||||||
42
README.md
42
README.md
@@ -1,8 +1,40 @@
|
|||||||
# led-tool
|
# led-tool
|
||||||
- `-s, --show`: Display current settings from device
|
|
||||||
- `--no-download`: Don't download settings first (use empty settings)
|
|
||||||
**Default behavior:** Downloads settings and prints them. If any edit flags are provided, settings are modified and uploaded automatically (unless `--no-upload` is specified).
|
|
||||||
|
|
||||||
## Device Connection
|
CLI helpers for MicroPython LED devices: **`settings.json`** download/upload via **mpremote**, resets, follow/tail, recursive uploads, optional firmware flash, and **led-driver**-oriented flags (presets, transport, Wi-Fi fields).
|
||||||
|
|
||||||
The tools use an integrated mpremote transport to communicate with MicroPython devices. Make sure your device is connected and accessible via the specified serial port.## LicenseSee LICENSE file for details.
|
Connection is always via **`-p` / `--port`** (default `/dev/ttyACM0`). There is **no** separate “server IP” flag; the Pi or PC address is configured on the device in **`settings.json`** when using Wi-Fi mode.
|
||||||
|
|
||||||
|
## Common flags
|
||||||
|
|
||||||
|
| Flag | Purpose |
|
||||||
|
|------|--------|
|
||||||
|
| `-p`, `--port` | Serial device (default `/dev/ttyACM0`) |
|
||||||
|
| `-s`, `--show` | Print current settings from the device |
|
||||||
|
| `-n`, `--name` | Device **name** |
|
||||||
|
| `--reset-device-name` | Set **name** to firmware default (`led-` + STA MAC hex, same as `Settings.set_defaults` on led-driver) |
|
||||||
|
| `--id` | Numeric device id (ESP-NOW, 0–255) |
|
||||||
|
| `--pin` | LED GPIO (`led_pin`) |
|
||||||
|
| `-b`, `--brightness` | Brightness 0–255 |
|
||||||
|
| `-l`, `--leds` | LED count (`num_leds`) |
|
||||||
|
| `-d`, `--debug` | Debug 0 or 1 |
|
||||||
|
| `-o`, `--order` | LED colour order (`rgb`, `grb`, …) |
|
||||||
|
| `--preset` / `--pattern` | Create or replace a named preset in **led-driver** `presets.json` |
|
||||||
|
| `--default` | Startup preset name |
|
||||||
|
| `--transport` | `espnow` or `wifi` (`transport_type` on device) |
|
||||||
|
| `--ssid`, `--wifi-password`, `--wifi-channel` | Wi-Fi / channel fields for the driver |
|
||||||
|
| `-r`, `--reset` | Reset the device |
|
||||||
|
| `-f`, `--follow` | Follow serial output (optional timeout seconds) |
|
||||||
|
| `--pause` | Sleep N seconds (for chained actions) |
|
||||||
|
| `-u`, `--upload` | Recursive upload: `-u SRC [DEST]` |
|
||||||
|
| `--src`, `--lib` | Upload `src/` or a tree to `/lib` |
|
||||||
|
| `-e` | Erase device code (keeps `settings.json`) |
|
||||||
|
| `--rm` | Remove a path on the device |
|
||||||
|
| `--flash` | Flash a firmware binary (uses **esptool** on the host) |
|
||||||
|
|
||||||
|
**Default behaviour:** the tool always downloads `settings.json`, prints the merged view, and uploads again **only** when you pass setting edits (`--name`, `--leds`, …), **`--preset`**, or the relevant upload/flash/erase actions in order.
|
||||||
|
|
||||||
|
Run **`python cli.py -h`** for the full epilog and argument list.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See **LICENSE** in this directory.
|
||||||
|
|||||||
181
cli.py
181
cli.py
@@ -11,11 +11,12 @@ import argparse
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import shutil
|
||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional
|
||||||
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import os
|
import os
|
||||||
from device import copy_file, DeviceConnection
|
from device import copy_file, DeviceConnection, firmware_default_device_name
|
||||||
|
|
||||||
|
|
||||||
def resolve_flash_binary(path: str) -> Optional[str]:
|
def resolve_flash_binary(path: str) -> Optional[str]:
|
||||||
@@ -146,15 +147,15 @@ def print_settings(settings: Dict[str, Any]) -> None:
|
|||||||
_FLAGS_WITH_VALUE = frozenset({
|
_FLAGS_WITH_VALUE = frozenset({
|
||||||
'-p', '--port', '-n', '--name', '--pin', '-b', '--brightness',
|
'-p', '--port', '-n', '--name', '--pin', '-b', '--brightness',
|
||||||
'-l', '--leds', '-d', '-debug', '--debug', '-o', '--order',
|
'-l', '--leds', '-d', '-debug', '--debug', '-o', '--order',
|
||||||
'--preset', '--pattern', '--default', '--transport', '--server-ip', '--ssid',
|
'--preset', '--pattern', '--default', '--transport', '--ssid',
|
||||||
'--wifi-password', '--wifi-channel',
|
'--wifi-password', '--wifi-channel', '--src', '--lib', '--patterns', '--paterns',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
def _get_ordered_actions(argv: List[str]) -> List[tuple]:
|
def _get_ordered_actions(argv: List[str]) -> List[tuple]:
|
||||||
"""
|
"""
|
||||||
Scan argv and return list of (action_name, value) in order of appearance.
|
Scan argv and return list of (action_name, value) in order of appearance.
|
||||||
Actions: flash, pause, reset, upload, erase_all, rm, follow.
|
Actions: flash, pause, reset, upload, ls, erase_all, rm, follow.
|
||||||
"""
|
"""
|
||||||
actions = []
|
actions = []
|
||||||
i = 1
|
i = 1
|
||||||
@@ -201,22 +202,47 @@ def _get_ordered_actions(argv: List[str]) -> List[tuple]:
|
|||||||
i += 2
|
i += 2
|
||||||
else:
|
else:
|
||||||
i += 1
|
i += 1
|
||||||
# Use empty string as remote_dir to map to root on device
|
# Upload source tree excluding patterns/ (handled by --patterns).
|
||||||
actions.append(('upload', [local_dir, ""]))
|
actions.append(('upload_src_no_patterns', local_dir))
|
||||||
continue
|
continue
|
||||||
if arg == '--lib':
|
if arg in ('--patterns', '--paterns'):
|
||||||
# Upload local DIR to /lib on device
|
# Upload local patterns DIR (default: ./src/patterns) to /patterns.
|
||||||
if i + 1 < len(argv):
|
local_dir = os.path.join("src", "patterns")
|
||||||
|
if i + 1 < len(argv) and not argv[i + 1].startswith('-'):
|
||||||
local_dir = argv[i + 1]
|
local_dir = argv[i + 1]
|
||||||
actions.append(('upload', [local_dir, "lib"]))
|
|
||||||
i += 2
|
i += 2
|
||||||
else:
|
else:
|
||||||
raise ValueError("--lib requires a directory argument")
|
i += 1
|
||||||
|
actions.append(('upload', [local_dir, "patterns"]))
|
||||||
|
continue
|
||||||
|
if arg == '--all':
|
||||||
|
actions.append(('upload_src_no_patterns', "src"))
|
||||||
|
actions.append(('upload', [os.path.join("src", "patterns"), "patterns"]))
|
||||||
|
actions.append(('upload', ["lib", "lib"]))
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
if arg == '--lib':
|
||||||
|
# Upload local DIR (default: ./lib) to /lib on device
|
||||||
|
local_dir = "lib"
|
||||||
|
if i + 1 < len(argv) and not argv[i + 1].startswith('-'):
|
||||||
|
local_dir = argv[i + 1]
|
||||||
|
i += 2
|
||||||
|
else:
|
||||||
|
i += 1
|
||||||
|
actions.append(('upload', [local_dir, "lib"]))
|
||||||
|
continue
|
||||||
|
if arg == '--ls':
|
||||||
|
actions.append(('ls', None))
|
||||||
|
i += 1
|
||||||
continue
|
continue
|
||||||
if arg == '-e':
|
if arg == '-e':
|
||||||
actions.append(('erase_all', None))
|
actions.append(('erase_all', None))
|
||||||
i += 1
|
i += 1
|
||||||
continue
|
continue
|
||||||
|
if arg == '--erase':
|
||||||
|
actions.append(('erase_all', None))
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
if arg == '--rm':
|
if arg == '--rm':
|
||||||
if i + 1 < len(argv):
|
if i + 1 < len(argv):
|
||||||
actions.append(('rm', argv[i + 1]))
|
actions.append(('rm', argv[i + 1]))
|
||||||
@@ -283,6 +309,9 @@ Examples:
|
|||||||
|
|
||||||
# Set name, num_leds, default pattern, and upload
|
# Set name, num_leds, default pattern, and upload
|
||||||
%(prog)s --name "MyStrip" -l 60 --default rainbow
|
%(prog)s --name "MyStrip" -l 60 --default rainbow
|
||||||
|
|
||||||
|
# Reset logical device name to firmware default (STA MAC based)
|
||||||
|
%(prog)s --reset-device-name
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -297,6 +326,12 @@ Examples:
|
|||||||
help="Device name"
|
help="Device name"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--reset-device-name",
|
||||||
|
action="store_true",
|
||||||
|
help="Set name to firmware default (led-<STA MAC hex>, same as fresh settings.json on led-driver)",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--id",
|
"--id",
|
||||||
dest="device_id",
|
dest="device_id",
|
||||||
@@ -369,12 +404,6 @@ Examples:
|
|||||||
help="led-driver transport_type",
|
help="led-driver transport_type",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--server-ip",
|
|
||||||
metavar="IP",
|
|
||||||
help="led-driver server_ip (Pi TCP host in wifi mode)",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--ssid",
|
"--ssid",
|
||||||
help="led-driver ssid (Wi-Fi network in wifi mode)",
|
help="led-driver ssid (Wi-Fi network in wifi mode)",
|
||||||
@@ -435,17 +464,40 @@ Examples:
|
|||||||
nargs="?",
|
nargs="?",
|
||||||
const="src",
|
const="src",
|
||||||
metavar="DIR",
|
metavar="DIR",
|
||||||
help="Upload DIR recursively to device root (:/, no leading directory). If DIR is omitted, uses local ./src."
|
help="Upload DIR recursively to device root (:/, no leading directory), excluding patterns/. If DIR is omitted, uses local ./src."
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--lib",
|
"--lib",
|
||||||
|
nargs="?",
|
||||||
|
const="lib",
|
||||||
metavar="DIR",
|
metavar="DIR",
|
||||||
help="Upload DIR recursively to /lib on device"
|
help="Upload DIR recursively to /lib on device. If DIR is omitted, uses local ./lib."
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-e",
|
"--all",
|
||||||
|
action="store_true",
|
||||||
|
help="Upload ./src (excluding patterns), ./src/patterns, and ./lib."
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--patterns", "--paterns",
|
||||||
|
dest="patterns_dir",
|
||||||
|
nargs="?",
|
||||||
|
const=os.path.join("src", "patterns"),
|
||||||
|
metavar="DIR",
|
||||||
|
help="Upload DIR recursively to /patterns on device. If DIR is omitted, uses local ./src/patterns."
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--ls",
|
||||||
|
action="store_true",
|
||||||
|
help="List files on the device root (:/)"
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-e", "--erase",
|
||||||
dest="erase_all",
|
dest="erase_all",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Erase all code on the device (delete all files except settings.json)"
|
help="Erase all code on the device (delete all files except settings.json)"
|
||||||
@@ -532,6 +584,50 @@ Examples:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error uploading directory: {e}", file=sys.stderr)
|
print(f"Error uploading directory: {e}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
elif action_name == 'upload_src_no_patterns':
|
||||||
|
src_dir = value
|
||||||
|
if not os.path.exists(src_dir):
|
||||||
|
print(f"Error: Directory does not exist: {src_dir}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if not os.path.isdir(src_dir):
|
||||||
|
print(f"Error: Not a directory: {src_dir}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
try:
|
||||||
|
with tempfile.TemporaryDirectory() as temp_src:
|
||||||
|
for entry in sorted(os.listdir(src_dir)):
|
||||||
|
if entry == "patterns":
|
||||||
|
continue
|
||||||
|
src_entry = os.path.join(src_dir, entry)
|
||||||
|
dst_entry = os.path.join(temp_src, entry)
|
||||||
|
if os.path.isdir(src_entry):
|
||||||
|
shutil.copytree(src_entry, dst_entry)
|
||||||
|
else:
|
||||||
|
shutil.copy2(src_entry, dst_entry)
|
||||||
|
print(
|
||||||
|
f"Uploading {src_dir} (excluding patterns/) to device root on {port}...",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
conn = DeviceConnection(port)
|
||||||
|
files_copied, dirs_created = conn.upload_directory(temp_src, "")
|
||||||
|
print(
|
||||||
|
f"Upload complete: {files_copied} files, {dirs_created} directories created.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error uploading src (excluding patterns): {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
elif action_name == 'ls':
|
||||||
|
try:
|
||||||
|
print(f"Listing files on device {port}...", file=sys.stderr)
|
||||||
|
conn = DeviceConnection(port)
|
||||||
|
items = conn.list_files('')
|
||||||
|
for name, is_dir, size in items:
|
||||||
|
marker = "d" if is_dir else "-"
|
||||||
|
print(f"{marker} {size:>8} {name}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error listing files: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
elif action_name == 'erase_all':
|
elif action_name == 'erase_all':
|
||||||
try:
|
try:
|
||||||
@@ -575,11 +671,35 @@ Examples:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
return # follow blocks; when interrupted we're done
|
return # follow blocks; when interrupted we're done
|
||||||
|
|
||||||
|
default_name_from_device: Optional[str] = None
|
||||||
|
if args.reset_device_name:
|
||||||
|
if args.name is not None:
|
||||||
|
print(
|
||||||
|
"Error: use either --name or --reset-device-name, not both.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
try:
|
||||||
|
print(
|
||||||
|
f"Reading firmware default device name (STA MAC) on {port}...",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
default_name_from_device = firmware_default_device_name(port)
|
||||||
|
print(
|
||||||
|
f"Default name will be {default_name_from_device!r}.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# Collect all edit parameters
|
# Collect all edit parameters
|
||||||
edits: Dict[str, Any] = {}
|
edits: Dict[str, Any] = {}
|
||||||
|
|
||||||
if args.name is not None:
|
if args.name is not None:
|
||||||
edits["name"] = args.name
|
edits["name"] = args.name
|
||||||
|
elif default_name_from_device is not None:
|
||||||
|
edits["name"] = default_name_from_device
|
||||||
|
|
||||||
if args.pin is not None:
|
if args.pin is not None:
|
||||||
edits["led_pin"] = args.pin
|
edits["led_pin"] = args.pin
|
||||||
@@ -602,9 +722,6 @@ Examples:
|
|||||||
if args.transport is not None:
|
if args.transport is not None:
|
||||||
edits["transport_type"] = args.transport
|
edits["transport_type"] = args.transport
|
||||||
|
|
||||||
if args.server_ip is not None:
|
|
||||||
edits["server_ip"] = args.server_ip
|
|
||||||
|
|
||||||
if args.ssid is not None:
|
if args.ssid is not None:
|
||||||
edits["ssid"] = args.ssid
|
edits["ssid"] = args.ssid
|
||||||
|
|
||||||
@@ -636,10 +753,18 @@ Examples:
|
|||||||
if not edits and args.preset is None:
|
if not edits and args.preset is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
# 2. Edit: apply edits to downloaded settings
|
# 2. Edit: only apply/upload settings when values actually change
|
||||||
if edits:
|
changed_edits: Dict[str, Any] = {}
|
||||||
print(f"Applying {len(edits)} edit(s)...", file=sys.stderr)
|
for key, value in edits.items():
|
||||||
settings.update(edits)
|
if settings.get(key) != value:
|
||||||
|
changed_edits[key] = value
|
||||||
|
|
||||||
|
if edits and not changed_edits:
|
||||||
|
print("No settings changes detected; skipping settings upload.", file=sys.stderr)
|
||||||
|
|
||||||
|
if changed_edits:
|
||||||
|
print(f"Applying {len(changed_edits)} setting change(s)...", file=sys.stderr)
|
||||||
|
settings.update(changed_edits)
|
||||||
|
|
||||||
print_settings(settings)
|
print_settings(settings)
|
||||||
|
|
||||||
@@ -670,7 +795,7 @@ Examples:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# 3b. Settings upload (resets device)
|
# 3b. Settings upload (resets device)
|
||||||
if edits:
|
if changed_edits:
|
||||||
try:
|
try:
|
||||||
print(f"\nUploading settings to {args.port}...", file=sys.stderr)
|
print(f"\nUploading settings to {args.port}...", file=sys.stderr)
|
||||||
upload_settings(args.port, settings)
|
upload_settings(args.port, settings)
|
||||||
|
|||||||
72
device.py
72
device.py
@@ -23,12 +23,15 @@ if lib_path not in sys.path and os.path.exists(lib_path):
|
|||||||
sys.path.insert(0, lib_path)
|
sys.path.insert(0, lib_path)
|
||||||
|
|
||||||
from mpremote.transport_serial import SerialTransport
|
from mpremote.transport_serial import SerialTransport
|
||||||
from mpremote.transport import TransportError
|
from mpremote.transport import TransportError, TransportExecError
|
||||||
|
|
||||||
|
|
||||||
class DeviceConnection:
|
class DeviceConnection:
|
||||||
"""Wrapper for device communication."""
|
"""Wrapper for device communication."""
|
||||||
|
|
||||||
|
#: Feed interval during uploads (led-driver uses WDT(timeout=10000) ms).
|
||||||
|
WDT_FEED_INTERVAL_SEC = 5.0
|
||||||
|
|
||||||
def __init__(self, device):
|
def __init__(self, device):
|
||||||
"""Connect to a device."""
|
"""Connect to a device."""
|
||||||
self.device = device
|
self.device = device
|
||||||
@@ -54,6 +57,38 @@ class DeviceConnection:
|
|||||||
pass
|
pass
|
||||||
self.transport = None
|
self.transport = None
|
||||||
|
|
||||||
|
def _feed_wdt(self) -> None:
|
||||||
|
"""Best-effort feed of the ESP task WDT between chunked FS writes."""
|
||||||
|
if self.transport is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.transport.exec(
|
||||||
|
"try:\n import machine\n machine.WDT(timeout=10000).feed()\nexcept Exception:\n pass\n"
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _make_wdt_upload_progress_callback(self):
|
||||||
|
"""Progress hook for Transport.fs_writefile: feed WDT every WDT_FEED_INTERVAL_SEC."""
|
||||||
|
last_feed = [time.monotonic()]
|
||||||
|
|
||||||
|
def progress(written: int, total: int) -> None:
|
||||||
|
now = time.monotonic()
|
||||||
|
if now - last_feed[0] >= self.WDT_FEED_INTERVAL_SEC:
|
||||||
|
self._feed_wdt()
|
||||||
|
last_feed[0] = now
|
||||||
|
|
||||||
|
return progress
|
||||||
|
|
||||||
|
def _fs_writefile_with_wdt(self, remote_path: str, data: bytes) -> None:
|
||||||
|
"""Write file to device with periodic WDT feeds during long transfers."""
|
||||||
|
self._feed_wdt()
|
||||||
|
self.transport.fs_writefile(
|
||||||
|
remote_path,
|
||||||
|
data,
|
||||||
|
progress_callback=self._make_wdt_upload_progress_callback(),
|
||||||
|
)
|
||||||
|
|
||||||
def copy_from_device(self, remote_path, local_path):
|
def copy_from_device(self, remote_path, local_path):
|
||||||
"""Copy a file from device to local filesystem."""
|
"""Copy a file from device to local filesystem."""
|
||||||
self.connect()
|
self.connect()
|
||||||
@@ -70,7 +105,7 @@ class DeviceConnection:
|
|||||||
try:
|
try:
|
||||||
with open(local_path, 'rb') as f:
|
with open(local_path, 'rb') as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
self.transport.fs_writefile(remote_path, data)
|
self._fs_writefile_with_wdt(remote_path, data)
|
||||||
finally:
|
finally:
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
@@ -133,7 +168,7 @@ class DeviceConnection:
|
|||||||
print(f"Uploading: {remote_file}", file=sys.stderr)
|
print(f"Uploading: {remote_file}", file=sys.stderr)
|
||||||
with open(local_file, 'rb') as f:
|
with open(local_file, 'rb') as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
self.transport.fs_writefile(remote_file, data)
|
self._fs_writefile_with_wdt(remote_file, data)
|
||||||
files_copied += 1
|
files_copied += 1
|
||||||
|
|
||||||
return files_copied, dirs_created
|
return files_copied, dirs_created
|
||||||
@@ -235,6 +270,37 @@ class DeviceConnection:
|
|||||||
raise TransportError(f"Failed to follow output: {e}") from e
|
raise TransportError(f"Failed to follow output: {e}") from e
|
||||||
|
|
||||||
|
|
||||||
|
def firmware_default_device_name(device: str) -> str:
|
||||||
|
"""
|
||||||
|
Default logical device name on led-driver firmware: led-<sta_mac_hex>,
|
||||||
|
matching Settings.set_defaults() in led-driver/src/settings.py.
|
||||||
|
"""
|
||||||
|
conn = DeviceConnection(device)
|
||||||
|
conn.connect()
|
||||||
|
try:
|
||||||
|
code = (
|
||||||
|
"import network, ubinascii\n"
|
||||||
|
"sta = network.WLAN(network.STA_IF)\n"
|
||||||
|
"sta.active(True)\n"
|
||||||
|
"mac = ubinascii.hexlify(sta.config('mac')).decode().lower()\n"
|
||||||
|
"print('led-' + mac)\n"
|
||||||
|
)
|
||||||
|
out = conn.transport.exec(code)
|
||||||
|
except TransportExecError as e:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Could not read STA MAC from device (is this MicroPython with network?): "
|
||||||
|
+ (e.error_output or str(e)).strip()
|
||||||
|
) from e
|
||||||
|
finally:
|
||||||
|
conn.disconnect()
|
||||||
|
text = out.decode("utf-8", errors="replace").strip()
|
||||||
|
for line in reversed(text.splitlines()):
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith("led-"):
|
||||||
|
return line
|
||||||
|
raise RuntimeError("Device did not return a led-<mac> default name")
|
||||||
|
|
||||||
|
|
||||||
def copy_file(from_device, device, remote_path, local_path):
|
def copy_file(from_device, device, remote_path, local_path):
|
||||||
"""
|
"""
|
||||||
Copy a file to/from device.
|
Copy a file to/from device.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ pipenv install "$@"
|
|||||||
if [ ! -f "dist/led-cli" ] || [ "cli.py" -nt "dist/led-cli" ] || [ "device.py" -nt "dist/led-cli" ]; then
|
if [ ! -f "dist/led-cli" ] || [ "cli.py" -nt "dist/led-cli" ] || [ "device.py" -nt "dist/led-cli" ]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "Building binary..."
|
echo "Building binary..."
|
||||||
pipenv run pyinstaller --clean led-cli.spec
|
pipenv run pyinstaller --clean --onefile --name led-cli --paths lib cli.py
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Ensure ~/.local/bin exists
|
# Ensure ~/.local/bin exists
|
||||||
|
|||||||
Reference in New Issue
Block a user