VE6SLP / Projects

Manage a node. Upload a command over LoRa.

Your companion radio can open Aspen's Management console without WiFi on the node. A computer with a separate KISS gateway can add a Lua file over the same mesh, without reflashing or replacing your other command files.

Choose the connection you have

Devices and endpoints
TaskSender and transportDestination
Run !hello meshMeshCore app with a companion radio; ordinary encrypted DMBot identity
Read/change settingsNative companion radio; app repeater/admin console or meshcliAspen Management identity
Upload a file from a computerPython client owning a sender key; independent raw-KISS radio gatewayAspen Management identity
Manage PineAuthenticated Pine-Relay/repeater consolePine-Relay; use the Pine guide for its commands and limits
Manage a host locallyPrivate owner socketBirch host or Willow host endpoint
  • Use the Management public key from your existing setup/contact. Management advertises a repeater-type CLI endpoint for administration; forwarding remains with the repeater role.
  • Match the complete PHY: frequency, bandwidth, spreading factor and coding rate. Keep production radios on their existing profile and use a separate test radio for another PHY.
  • Obtain authorization: use the existing Management password or an already authorized full sender public key. Public channels and radio proximity carry no administrative authority.

Open the Management console over RF

In an app that exposes the native repeater administrator console, select the Management contact, log in and send status, then bot status. App menu names vary; if yours lacks this console, use meshcli with a companion radio:

meshcli -s /dev/serial/by-id/YOUR_COMPANION

Or connect to a native companion TCP endpoint with meshcli -t COMPANION_HOST -p COMPANION_PORT. Raw KISS uses a separate endpoint. In the interactive console, replace the key and password placeholders locally:

add_contact MANAGEMENT_PUBLIC_KEY64 2 mast
reset_path mast
login mast ADMIN_PASSWORD
cmd mast status
wmt8
cmd mast "bot status"
wmt8
cmd mast "source status"
wmt8

For an already authorized full companion key, use login mast "". cmd sends without requesting a delivery ACK. Read the native encrypted CLI reply with wmt8, or sync_msgs for replies already queued. Keep console history containing a password private.

Example · Companion console → Management · login and inspection
  1. Operator · companion consolelogin mast ADMIN_PASSWORD
  2. Management · native login responseAuthenticated session accepted
  3. Operatorcmd mast "source status"
    wmt8
  4. Management · source status replyGeneration, selected/previous source slots, upload progress and live outcome

The status fields depend on the node. A missing reply leaves the outcome uncertain; check the session and route before retrying.

Native login, telemetry and ordinary bot owner controls

Login uses native encrypted ANON_REQ with timestamp/password, or an authorized full key and empty password. Subsequent commands and replies use native encrypted CLI TXT_MSG.

After login, req_status mast reads device counters and req_telemetry mast reads permitted Cayenne LPP sensor measurements. Unsupported board values are omitted from telemetry. telemetry status instead describes the optional metrics publisher.

Use bot !admin for supported owner controls from a trusted full-key identity. Use Management to change credentials, upload programs or transfer stored data. Bot access and runtime reference · Native RF framing and access.

Install one Lua command from a file over LoRa

Use a separate MeshCore KISS gateway radio reachable over TCP, the source checkout and a Python environment with the admin client dependencies. The gateway transmits the client's encrypted packets to the target Aspen.

Need a gateway? The WiFi modem setup prepares a separate radio with the KISS endpoint this route needs.

Use an operator-managed 32-byte Ed25519 sender seed file and a password file, both private regular files with mode 0600. The password file contains only the password, with no final newline. The client can use a dedicated identity or an existing operator-managed identity. Keep app and radio private keys in their original stores. Omit the password file when this client's full public key is already authorized.

Create a new standalone client identity, offline

Create the key once in an existing private directory outside the source checkout. Replace the path below. The example creates the file exclusively with mode 0600, prints the public key and leaves node authorization unchanged:

python3 - /absolute/private/rf-client.seed <<'PY'
import os, secrets, sys
from cryptography.hazmat.primitives.asymmetric import ed25519
from cryptography.hazmat.primitives import serialization
seed = secrets.token_bytes(32)
fd = os.open(sys.argv[1], os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
with os.fdopen(fd, "wb") as output:
    output.write(seed)
    output.flush()
    os.fsync(output.fileno())
public = ed25519.Ed25519PrivateKey.from_private_bytes(seed).public_key()
print("Client public key:", public.public_bytes(
    serialization.Encoding.Raw, serialization.PublicFormat.Raw).hex())
PY

Use the existing Management password, or ask its authorized operator to grant this exact public key. Reuse this seed for later uploads and retain its adjacent .clock timestamp file; the directory must be writable. Use one active client per seed so concurrent clients do not compete for replay state. Keep both files private and out of commits.

Keep the gateway network private. KISS and native companion TCP are unencrypted network services; use a trusted LAN or protected tunnel. Give --gateway the raw KISS TCP endpoint, usually port 8001. RF login and CLI packets sent by this client are encrypted.

  1. Read capabilities and save the current set

    Run from the source repository root. Substitute your own gateway, full Management key and protected file paths in every invocation:

    python3 tools/admin.py \
      --gateway KISS_GATEWAY_HOST --port 8001 --target MANAGEMENT_PUBLIC_KEY64 \
      --seed-file /absolute/private/rf-client.seed \
      --password-file /absolute/private/node-password \
      command 'source api sources'
    python3 tools/admin.py \
      --gateway KISS_GATEWAY_HOST --port 8001 --target MANAGEMENT_PUBLIC_KEY64 \
      --seed-file /absolute/private/rf-client.seed \
      --password-file /absolute/private/node-password \
      source download saved-source.lua
    chmod 600 saved-source.lua

    Expected: the capability reply includes format=meshcore-sources/1 and commit-base=sha256. The download verifies full readback and writes a new file; chmod makes that backup private. Keep it out of public commits. With the same options, source list lists names and source export NAME NEW_FILE exports one installed file.

  2. Save and test a greeting

    Download hello.lua or save the following as UTF-8 with LF newlines and a final newline:

    function hello(name)
      return "Hello " .. name
    end
    command("hello", "name:string:32", "Greet a name",
            "hello", "public", "!hello mesh")

    Test the greeting locally before using airtime. Choose a source name not used by an unrelated file. Duplicate command names reject the candidate.

  3. Add the named file and wait for activation

    python3 tools/admin.py \
      --gateway KISS_GATEWAY_HOST --port 8001 --target MANAGEMENT_PUBLIC_KEY64 \
      --seed-file /absolute/private/rf-client.seed \
      --password-file /absolute/private/node-password \
      source install greeting hello.lua
    python3 tools/admin.py \
      --gateway KISS_GATEWAY_HOST --port 8001 --target MANAGEMENT_PUBLIC_KEY64 \
      --seed-file /absolute/private/rf-client.seed \
      --password-file /absolute/private/node-password source status

    Expected: Durable chunk N/TOTAL, acceptance of verification, then source durably saved and active. The client verifies the selected hash. The file named greeting changes; other files remain. A concurrent source update rejects the conditional commit and preserves that update.

    A disabled bot reports source saved; bot disabled; loads on next enabled boot. Inspect its channel policy before enabling/restarting. If activation is still pending when the client times out, inspect source status and source hash instead of repeating the install.

  4. Switch to the bot conversation

    From a separate companion on the same PHY, advertise your companion so the bot learns its contact, then open the bot's ordinary private DM:

    Example · Companion → Bot DM · greeting active
    1. You!hello mesh
    2. BotHello mesh
    3. You!help hello

    Generated help describes the argument and example. Send chat commands to the bot's contact. Use a separate companion radio when checking replies over RF.

What installation keeps and replaces

A named installation updates one file while keeping other installed files and bundled commands. The complete encoded Lua set shares the 4096-byte limit and eight-command registry; wrapper bytes also count. Whole-set source replace and package install replace that runtime's entire command set. Choose the LAN or RF upload route above to match your connection.

Installed programs return after restart. Installation keeps role identities, settings, scoped KV and notes; it cancels that runtime's pending work. Code rollback keeps data changes and cannot recall packets already sent. Do not format, provision or erase a node to add a command.

Named files and recovery · Complete source RF guide.

Understand the transfer and its replies

The client logs in to Management, admits an upload, sends numbered 48-byte chunks and starts verification. Each durable chunk reply confirms readback and saved progress. Commit acceptance is only the start: read status until activation finishes, then check the hash.

Example Management conversation: all three chunks, activation and hash

This is a whole-set wire-format illustration. These are the exact bytes of the downloadable 144-byte greeting alone. Sending this raw commit replaces the whole Lua set. Use source install greeting hello.lua for named-file installation that preserves other files; its composed bundle has a different size and hash.

Login first as above. In meshcli, each outgoing source line is the argument to cmd mast "…"; read each reply with wmt8. For actual file installation, let the Python client manage these exchanges.

Example · Authenticated Management · exact whole-file fixture
  1. Uploader · admit exact filesource begin aff977000622bb04 144 aff977000622bb048a971afd190e1c4bfd4ec5f00447d62311e2185e8cecbcf5
  2. ManagementACK aff977000622bb04 next=0
  3. Uploader · chunk 0source chunk aff977000622bb04 0 66756e6374696f6e2068656c6c6f286e616d65290a202072657475726e202248656c6c6f2022202e2e206e616d650a65
  4. Management · durable progressACK aff977000622bb04 next=1
  5. Uploader · chunk 1source chunk aff977000622bb04 1 6e640a636f6d6d616e64282268656c6c6f222c20226e616d653a737472696e673a3332222c202247726565742061206e
  6. Management · durable progressACK aff977000622bb04 next=2
  7. Uploader · chunk 2source chunk aff977000622bb04 2 616d65222c0a20202020202020202268656c6c6f222c20227075626c6963222c20222168656c6c6f206d65736822290a
  8. Management · durable progressACK aff977000622bb04 next=3
  9. Uploadersource commit aff977000622bb04
  10. ManagementAccepted verification; source status reports durable activation outcome
  11. Uploader · poll until terminal outcomesource status
  12. Management · terminal outcome excerptsource durably saved and active
  13. Uploader · check selected filesource hash
  14. Management · hash excerptSHA256 aff977000622bb048a971afd190e1c4bfd4ec5f00447d62311e2185e8cecbcf5

Status also includes the node's current generation, slots and progress; the hash reply ends with gen=N.

Resume, retries and airtime

Matching source begin ID16 SIZE SHA256 resumes saved progress after a disconnect/restart. Sequential chunks are 48 source bytes except the final one. Repeating an identical chunk is safe; a gap or conflicting duplicate fails. The upload loop makes at most three identical chunk attempts on timeout, then polls activation up to 20 times with 0.5-second pauses plus RF round-trip time.

The RF transport can also retransmit a command twice with the same timestamp/text so Management returns its cached outcome for the same administrative attempt. --no-command-retry disables those transport retransmissions; the bounded identical-chunk upload retries still apply.

A 4096-byte source needs 86 chunks and their replies. Named source installation accepts Lua source files; ordinary bot DMs, compressed files, bytecode and native multipart messages are invalid inputs here. A durable chunk ACK and final activation status confirm upload progress and installation.

Exact upload grammar, source journal and failure behavior.

Admin commands you can use over LoRa

After Management login, begin with read-only checks. In meshcli, use cmd mast "COMMAND" and wmt8. In the Python client, use the RF connection options above followed by command 'COMMAND'. Consult help and help TOPIC PAGE on your actual build before changing anything.

Management tasks and consequences
TaskRead firstChange / consequence
Health and diagnosticsstatus, ver, board, stats help, jobRead-only; job reports deferred-control outcome
Roles and namesroles, roles list 2, bot status, role help, bot keyrole name ROLE TEXT persists without replacing a key; roles MASK and bot on/off save boot selection; apply restarts
Shared PHYget radio, get freq, get txradio HZ BW_HZ SF CR DBM changes all roles after the old-PHY reply; tempradio SECONDS HZ BW_HZ SF CR DBM later restores the durable profile
Advertsget autoadvert, role config botset autoadvert off/on; role advert ROLE zerohop/flood deliberately queues a transmission, not confirmed reception
Sensors and native statusCompanion req_status mast, req_telemetry mastBoard measurements and permissions determine available telemetry; no publisher setup is needed to request native telemetry
Bot workbot stats, bot admission, bot log, bot limits, bot diagnosticsbot cancel cancels bot work; already admitted effects may have committed
Channel policybot aliases, bot membership SLOT, bot access CONTEXT list OFFSET, bot thread CONTEXT list OFFSETJoining resets defaults to 63 and clears overrides. Disable/restart before creating addressed-only membership; set default 12 before enabling
Source lifecyclesource api sources, source status, source hash, source metadataNamed Python install retains other files; source retry, source rollback, source remove, source cancel have different recovery effects
Management permissionsget acl, password, key management pendingsetperm FULL_KEY64 3 grants admin; 0 removes the saved entry. trust FULL_KEY/trust none controls additional singleton trust; native role ACLs are separate
WiFiwifi status, get wifi.enabled, get wifi.ipSave credentials over encrypted RF only; wifi apply reconnects; disabling WiFi keeps RF Management running
Native servicesbot https status, telemetry endpoint status, mqtt status, cloudroom statusBuild-dependent; use service guides for destinations, grants and protected credential uploads
Restartstatus, jobapply or reboot restarts the device; read running state before retrying an uncertain restart

Command catalog and service guides · Exact syntax, units and limits · Safe addressed-only channel walkthrough.

Permissions, secrets and operations that can disconnect you

Role bits are repeater=1, room=2, companion=4, observer=8; bot selection is separate. roles MASK saves that selection. Management remains available with optional roles disabled. Shared PHY changes affect every role and host using the modem; keep a matching recovery route and follow local spectrum rules.

Use protected-file operations for passwords, private identities, WiFi credentials, service tokens and scoped backups. Do not place secrets in public posts, command-line examples or diagnostic captures. Password, identity and WiFi secret setters require encrypted Management RF; LAN HTTP exposes passwords/session traffic and is for trusted networks only.

Key rotation deliberately changes identity and requires restart; identity-bound bot data stays with the previous key. Repeater, Room and Management each use their own permissions. A compiled authority remains after removing an independent saved ACL entry.

ACL and session behavior · Password changes · Identity imports.

Recover without erasing the node

Management does not answer
Check the destination key, native admin login, route and complete PHY. Read queued replies with sync_msgs. Verify whether your client expects native companion TCP or raw KISS.
A chunk reply is lost
A matching source begin reports saved progress; only repeat the identical numbered chunk. The current source remains selected during upload.
Commit, install or restart is uncertain
Read source status, source hash, status and the affected saved/live settings before any retry.
A candidate is rejected
Inspect the error; the old source remains selected. Fix the size, hash, command collision or initialization failure locally before a new explicit attempt.
The durable selection cannot run
Inspect bot readiness and source status, then choose source retry, source rollback or source remove. Removal restores bundled handlers and removes the installed set; rollback restores code and help while preserving stored data.
Example · Management recovery · commit reply lost
  1. Operator · inspect before another commitsource status
  2. Management · terminal outcome excerptsource durably saved and active
  3. Operatorsource hash
  4. ManagementSelected hash and generation; compare with the actual uploaded candidate

A matching active selection confirms installation despite the lost commit reply.

Management remains available when the bot is disabled or custom source is quarantined. Use it to repair the selected program without formatting flash or replacing identities.

More command tutorials · Node administration overview · Source RF walkthrough.