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.

Two identities, two conversations. Send ordinary ! commands to the bot. Log in to the separate Management contact for settings and file transfers. A bot DM, channel nickname or BLE pairing does not grant Management access.

This guide covers the current Aspen Management/source client. Check your node's help and capabilities first: the published firmware and current source are different snapshots. The conversations are labelled HTML examples, not screenshots or private message history.

Choose the connection you have

Devices and endpoints
TaskSender and transportDestination
Run !hello meshMeshCore app with a companion radio; ordinary encrypted DMBot identity, no admin login
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, not Pine-Bot; 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, not the bot key. Management advertises a repeater-type CLI endpoint but does not forward packets.
  • Match the complete PHY: frequency, bandwidth, spreading factor and coding rate. Keep existing identities/settings; do not retune production radios just to try the guide.
  • Obtain authorization: the existing Management password, or an already authorized full sender public key. Public channels and radio proximity do not authorize administration.

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. Use the admin console, not ordinary bot chat. App menu names vary; if yours does not expose 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. That port is not KISS. 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 does not request 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 (description, not literal CLI text)
  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; it is not a confirmed failure.

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. This uses existing MeshCore framing, not a new public bot/upload protocol.

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.

Trusted full-key bot !admin supports bounded owner controls, not credentials or bulk source/data transfers. Use Management for those operations. Bot authority and runtime reference · Native RF framing and access.

Install one Lua command from a file over LoRa

This file route uses a different sender setup from the console above. You need 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; it is not the target Aspen.

Need a gateway? Follow the WiFi modem setup on a different radio. You do not need to start a Go host just to use that modem's KISS port.

Use an existing 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 owns this sender identity; it need not be your mobile app's identity. Do not extract private keys from an app or radio. Omit the password file only 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. This refuses to overwrite an existing file, prints only a public key and does not change or authorize anything on the node:

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. Do not generate another seed for each upload. Retain the seed and the 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. --gateway does not connect to app BLE, native companion TCP or Aspen's web admin page. 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/hardware/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/hardware/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 \
      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/hardware/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/hardware/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 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 rather than overwriting 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. This uses the bot's key, not Management's. A reply reflected through the node's own Base is not reception by another radio.

Preserved state, whole-set replacement and limits

Named source installation reads the existing set, composes a candidate and commits against its original SHA-256. The complete encoded Lua set shares the 4096-byte limit and eight-command registry; wrapper bytes also count. Whole-set install and package-install replace that runtime's entire command set. A browser LAN upload is a web operation, not an automatic LoRa upload.

Durable source returns after restart. Installation retains role identities, settings, scoped KV and notes; it cancels that runtime's pending work. Reverting code does not undo data changes or 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

Wire-format illustration, not the named-file installer. 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 to preserve 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 generation, slots and progress; the hash reply ends with gen=N. Those values depend on the node. Excerpts omit them rather than inventing fixture values.

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. This is not a new administrative attempt after an unknown result. --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. Do not paste whole Lua into an ordinary bot DM, compress it, send bytecode or assume native multipart messages are a file protocol. A native packet ACK is neither a durable chunk ACK nor installation completion.

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 a mask, not a help page. Management remains available without optional roles. 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; it does not move identity-bound bot data. Repeater/Room permissions are not Management permissions. Removing a saved ACL entry does not remove independent compiled authority.

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. Do not confuse native companion TCP with 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 retrying. Do not blindly replay a mutation.
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/help, not 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

If the selection matches and is active, the lost commit reply does not require reinstalling.

Native Management remains available when the bot is disabled or custom source is quarantined. Source repair is not provisioning: do not format, erase flash or replace identities to install a command.

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