Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions meshtastic/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,42 @@ def onConnected(interface):
# Must turn off encryption on primary channel
interface.getNode(args.dest, **getNode_kwargs).turnOffEncryptionOnPrimaryChannel()

if args.ls is not None:
closeNow = True
remote_dir = args.ls
depth = int(getattr(args, "ls_depth", 0) or 0)
node = interface.localNode
rows = node.listDir(remote_dir, depth=depth)
if rows is None:
meshtastic.util.our_exit("listDir failed", 1)
for path, sz in rows:
print(f"{sz}\t{path}")

if args.cp:
closeNow = True
src, dst = args.cp
import os
node = interface.localNode
# Direction: if src is an existing local file → upload; otherwise → download
if os.path.isfile(src):
print(f"Uploading {src} → {dst}")
def _upload_progress(sent, total):
pct = 100 * sent // total
bar = '#' * (pct // 5) + '.' * (20 - pct // 5)
print(f"\r [{bar}] {pct}%", end="", flush=True)
ok = node.uploadFile(src, dst, on_progress=_upload_progress)
print(f"\r {'OK' if ok else 'FAILED'}: {src} → {dst} ")
if not ok:
meshtastic.util.our_exit("Upload failed", 1)
else:
print(f"Downloading {src} → {dst}")
def _download_progress(received, _total):
print(f"\r {received} bytes received...", end="", flush=True)
ok = node.downloadFile(src, dst, on_progress=_download_progress)
print(f"\r {'OK' if ok else 'FAILED'}: {src} → {dst} ")
if not ok:
meshtastic.util.our_exit("Download failed", 1)

if args.reboot:
closeNow = True
waitForAckNak = True
Expand Down Expand Up @@ -1876,6 +1912,38 @@ def addLocalActionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPars
default=None
)

group.add_argument(
"--cp",
help=(
"Copy a file to or from the device via XModem. "
"Usage: --cp <src> <dst>. "
"If <src> is an existing local file it is uploaded to <dst> on the device. "
"Otherwise <src> is treated as a device path and downloaded to local <dst>. "
"Use /__ext__/ or /__int__/ prefixes to target external or internal flash."
),
nargs=2,
metavar=("SRC", "DST"),
)

group.add_argument(
"--ls",
help=(
"List files on the device under REMOTE_DIR via XMODEM MFLIST (requires matching firmware). "
"Output: size_bytes<TAB>path (one per line)."
),
nargs="?",
const="/",
default=None,
metavar="REMOTE_DIR",
)

group.add_argument(
"--ls-depth",
help="Max directory depth for --ls (0 = files in REMOTE_DIR only).",
type=int,
default=0,
)

return parser

def addRemoteActionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
Expand Down
Loading