mesh-rs extensions
This document specifies the protocol and firmware extensions implemented by mesh-rs for developers building
compatible firmware or applications.
Two-player games are carried as readable chat lines that stock clients display as ordinary messages.
Transport
Frames are transmitted as plain-text DMs or room posts, not group-channel messages.
In a room, the 4-byte author prefix in the server's push identifies the player.
A message is treated as a game frame if it parses as a v1 frame, or if it begins with
~g<N> (for decimal N from 2 to 99) followed by a non-empty body.
Other text, including malformed ~g1 lines, remains chat.
A session is identified by the conversation key (the DM peer or room server) and gid.
Frame
~g1 <game> <gid> <verb> [args...]
-
A frame consists of at most 151 bytes of printable ASCII, with single spaces between tokens and no leading,
trailing or extra tokens.
Hexadecimal digits are lowercase.
-
game identifies the ruleset: t tic-tac-toe, b battleships,
k checkers, m mancala, n nine men's morris, g gomoku,
q quarto, c chess, or p pente.
-
gid is a 4-digit hex identifier chosen by the inviter so that it is unique within the
conversation.
Receivers use the identifier from the invitation to match subsequent frames to the session.
-
seq is an unsigned 16-bit decimal integer formatted without leading zeros; move frames carry a
non-zero sequence number.
#hash consists of # followed by 4 hex digits.
Payload blobs (move, echo, state, layout) are non-empty
strings of characters from [a-z0-9.-].
-
The state hash is the first 2 bytes of
SHA-256(gid[2] ‖ seq u16 ‖ canonical state bytes after the move), where gid[2] is
the 2-byte binary value of gid.
Each game's canonical state encoding is defined below and is hashed bit for bit.
| Verb |
Syntax |
Meaning |
| invite |
inv [w|b|f|s] |
f or s selects whether the inviter moves first or second; w or
b selects the inviter's colour in chess.
When omitted, the inviter moves first.
|
| accept |
ok |
Accepts a pending invitation. |
| decline |
no [f|v] |
f indicates that the session limit has been reached; v indicates an unknown
protocol version or game.
|
| bind |
go <8 hex> |
Used in rooms only; identifies the accepted opponent by author prefix. |
| move |
m <seq> <move>[/<echo>] #hash |
Applies a move at seq.
When present, echo repeats the opponent's move from seq−1 verbatim.
|
| repeat |
m! ... |
Re-transmits the sender's last move with only the verb changed. |
| snapshot |
st <seq> <state> #hash |
Carries the full game state after move seq. |
| sync |
sync <seq> |
Reports the sender's highest contiguous applied sequence number, or 0 before the first move. |
| resign |
rz <seq> |
Concedes the game for the sender.
Valid only during an active session.
|
| draw |
dr? / dr+ |
Offers (dr?) or accepts (dr+) a draw in checkers, morris or chess.
Any move cancels a standing draw offer.
|
| commit / reveal |
cm <32 hex> / rv <layout> <32 hex> |
Commits to or reveals a fleet layout in battleships. |
~g1 t 3fa2 inv f
~g1 t 3fa2 ok
~g1 t 3fa2 m 1 5 #2829
~g1 t 3fa2 m 2 1/5 #8b27
Lifecycle
Side A always moves first, and the invite argument selects the inviter's side.
The active turn is determined by the game state rather than sequence-number parity.
In a direct-message session, the game becomes active for the invitee when it sends ok, and for the
inviter when it receives either ok or a valid move at sequence 1 from the peer.
In a room, members track incoming invitations while below their session limit.
The inviter binds the session to the sender of the first ok by sending go with that
member's author prefix; that member becomes the second player and all other room members spectate.
Moves are accepted only from the player whose turn is indicated by the state.
An invitation for an unsupported game or protocol version is answered with no v.
When the session limit (6 sessions, or 4 on small-screen boards) is reached, a node answers DM invitations with
no f and ignores room invitations.
Incoming invitations never evict active sessions, and unanswered invitations expire locally after 7 days.
Each client detects game completion directly from the rules without a separate end frame.
Repair
Let held denote the receiver's local sequence number.
An incoming move with a sequence number at or below held is discarded.
A move at held + 1 is applied if the sender holds the active turn.
A move at held + 2 that includes an echo field first applies the echoed move at
held + 1, provided doing so transfers the turn to the sender, and then applies the move at
held + 2.
Any larger sequence gap, illegal move or state-hash mismatch triggers a sync <held> reply.
A participant replies to sync with an st snapshot only when the session is active,
its local sequence number exceeds the requested value, and it sent the most recent move.
A snapshot is accepted only when its state blob and hash are valid and its sequence number is at least
held.
Active sessions accept snapshots only while a sync request is pending, whereas pending room
sessions also accept snapshots to initialize their state.
Automatic sync requests and st responses are each rate-limited to one per 60 s per
session.
Receiving a move, resignation, draw frame, commitment or reveal for an unknown gid triggers at
most one sync 0 per gid every 300 s.
An unsolicited snapshot for an unknown gid is not applied without explicit user acceptance.
Per-game encodings
Snapshots encode the canonical state bytes as lowercase hex unless stated otherwise.
In canonical binary states, the turn byte is 0 for side A and 1 for side B; in snapshots, the trailing letter
a or b identifies the side to move.
Turns alternate strictly in every game except mancala.
A trit board packs five cells per byte, placing cell i in byte i / 5 at weight
3i mod 5 with values 0 for empty, 1 for side A and 2 for side B.
Battleships uses the cell values defined in its table entry below.
| Game |
Move |
Canonical state |
Rules to match |
Tic-tac-toe t |
Cell 1 to 9 in reading order. |
3 bytes encoding xmask | (omask << 9), where bit i corresponds to cell
i + 1.
The snapshot encodes this integer as 5 hex digits, most significant digit first.
|
Side A plays X. |
Mancala m |
Pit 1 to 6 on the mover's side, numbered in sowing order. |
15 bytes: side A pits 1 to 6, side A store, side B pits 1 to 6, side B store, and the turn byte.
The snapshot encodes the 14 seed counts as 2 hex digits each, followed by a or
b.
|
Kalah rules with 4 initial seeds per pit.
Sowing skips the opponent's store.
Landing the last seed in the mover's store grants another turn; landing it in an empty pit on the
mover's side opposite a non-empty pit captures both into the mover's store.
The game ends when the side to move has no seeds in its pits, and the opponent sweeps its remaining
seeds.
|
Checkers k |
Simple step 9-14 or jump chain 22x15x6 (up to 12 squares). |
13 bytes: side A, side B and king bitmasks as u32 values (where bit n − 1
represents square n), followed by the turn byte.
The snapshot encodes the three masks as 24 hex digits, followed by a or b.
|
Dark squares are numbered 1 to 32 starting from side A's back row.
Captures are mandatory, with free choice among available jumps.
A jump sequence continues while the same piece has another capture, and crowning terminates the turn.
Kings move and capture one square diagonally.
A player with no legal move loses.
|
Morris n |
point to place a piece, or from-to to move, suffixed with
x<point> when completing a mill.
|
8 bytes: side A and side B board masks (3 bytes each), a hands byte (side A in the high nibble, side B
in the low nibble, starting at 0x99), and the turn byte.
The snapshot encodes 12 hex digits for the masks, 2 hex digits for the hand counts, and a
or b.
|
Points 1 to 24 are numbered row by row: outer, middle and inner top; left side from outer to inner;
right side from inner to outer; and inner, middle and outer bottom.
There are 16 orthogonal mills and no diagonal mills.
Forming a double mill still captures only one piece.
A piece inside a mill is eligible for capture only when every opposing piece lies in a mill.
A player with 3 pieces on the board and none in hand flies to any empty point.
A player reduced to 2 pieces or left with no legal move loses.
|
Gomoku g |
Coordinate a1 to o15, mapped to zero-based cell index
(row − 1) · 15 + (column − a).
|
45-byte trit board, with the side to move derived from the stone counts. |
Played on a 15×15 board under free-style rules, where side A plays Black.
A line of five or more stones wins, and a full board without a line is a draw.
|
Quarto q |
At sequence 1, the initial handed piece 0 to f.
Subsequent moves are cell-piece (for cells a1 to d4), or bare
cell when the placement wins or fills the board.
|
12 bytes: an 80-bit board packing cell i at bit offset 5i as
0x10 | piece (or 0 when empty), the handed piece byte (0xFF when none), and
the turn byte.
The snapshot encodes 20 hex digits for the board, the handed piece digit or ., and
a or b.
|
Winning lines are the rows, columns and two main diagonals.
Completing a line of four pieces that share at least one attribute wins for the player who places the
fourth piece.
|
Chess c |
Four ICCF digits such as 5254, with a fifth digit for promotion (1 Q,
2 R, 3 B, 4 N).
Castling is encoded as the king's two-square move.
|
Occupancy mask as u64 (bit rank · 8 + file, both zero-based), piece nibbles
in square order packed low nibble first (1 to 6 for P, N, B, R, Q, K, plus
8 for Black), flags byte (bit 0 Black to move, bits 1 to 4 castling rights WK, WQ, BK,
BQ), en-passant file (0xFF when none), and halfmove clock as u8.
|
Side A plays White.
The en-passant file is recorded after every double pawn advance.
Any move touching a1, h1, e1, a8, h8
or e8 clears the corresponding castling rights.
Endings are evaluated in order: bare kings (draw), checkmate or stalemate, and the 100-halfmove rule
(draw).
Draw by repetition is claimed by mutual draw agreement.
|
Pente p |
Coordinate a1 to o15, as in gomoku. |
46 bytes: a 45-byte gomoku trit board followed by a status byte (bits 0 to 2 side A captured pairs,
bits 3 to 5 side B captured pairs, bit 6 side B to move).
|
Side A opens at h8.
Bracketing a contiguous pair of opposing stones along a line captures the pair, and a single move can
capture in multiple directions.
Placing a stone between two opposing stones does not self-capture.
Captures are resolved before checking for five in a row; capturing five pairs or forming an unbroken
line of five or more stones wins.
|
Battleships b |
[verdict]cell, where verdict is - (miss), x (hit),
or s2 to s5 (sunk ship of that length).
A verdict prefix is included whenever the opponent's preceding shot is awaiting a verdict.
|
42 bytes: a flags byte (bit 0 side A committed, bit 1 side B committed, bit 2 side B to move), the
pending shot cell index (0xFF when none), and the 20-byte trit boards for side A and side
B (0 unshot, 1 miss, 2 hit).
|
Played on a 10×10 grid with cells a1 to j10.
The fleet consists of ships of lengths 5, 4, 3, 3 and 2, encoded without separators in that order as
<cell><h|v> from each ship's lowest coordinate; ships are allowed to touch.
Once the session is active, each player sends cm containing the first 16 bytes of
SHA-256(16-byte salt ‖ layout), and both commitments are required before the first shot.
Reaching 17 claimed hits wins.
After the game ends, either player can send rv to reveal its layout and salt, which the
peer verifies against the commitment, fleet placement rules and all reported verdicts.
|
Virtual fish visit other nodes using the LFG1| direct-message protocol from LoRaGotchi DX, in
which fields are separated by literal ASCII pipe characters (|).
mesh-rs extends the COMMIT message with an optional trailing appearance field that stock hosts
ignore.
| Line |
Sender |
LFG1|REQ|tx|fid|stage|name |
owner |
LFG1|ACK|tx|fid, LFG1|BUSY|tx|fid |
host |
LFG1|COMMIT|tx|fid[|look] |
owner |
LFG1|ARRIVED|tx|fid |
host |
LFG1|RECALL|tx|fid |
owner |
LFG1|RETURN|tx|fid |
host |
-
Visit messages are transmitted as acknowledged plain-text DMs.
Any message beginning with
LFG1| is suppressed from normal chat display.
-
tx and fid are unsigned 32-bit decimal integers.
fid is a non-zero random identifier fixed for the lifetime of the fish, while tx
is generated fresh for each visit request.
Valid replies echo both identifiers and originate from the expected peer.
-
stage encodes the life stage as 0 (fry), 1 (young), 2
(adult) or 3 (elder).
name spans the remainder of the line and is at most 23 bytes.
-
look is an unsigned 16-bit decimal integer packing hue in bits 0 to 2, fin style in bits 3 to
4, body pattern in bits 5 to 6, and a round-body flag in bit 7.
When look is omitted, the appearance byte defaults to SHA-256(fid u32)[0].
Owner
After sending REQ, the owner waits up to 90 s for ACK or BUSY.
After ACK, the owner sends COMMIT and waits 120 s for ARRIVED.
It retries COMMIT once before abandoning the visit after another 120 s.
The 3600 s visit lease begins when the owner receives ARRIVED.
At expiry or on user recall, the owner sends RECALL.
The fish returns home when RETURN is received or after 120 s.
Host
A host accommodates between 1 and 3 visiting fish at a time, with at most one guest per owner.
A duplicate REQ for an active reservation is answered with ACK again.
The host replies with BUSY when its tank is full, the fish is already present, visitor hosting is
disabled, local time is between 20:00 and 06:00, or the node's clock is unset.
The host replies to every matching COMMIT with ARRIVED and starts its 3600 s lease on
receipt of the first matching COMMIT.
A reservation that receives no COMMIT expires after 300 s.
The host ends the visit and sends RETURN either on receipt of RECALL or when the
lease expires.
Fry
A visiting adult or elder fish leaves a fry in the host tank if the visit ends within 120 s of the lease
deadline.
The breeding seed is the first 4 bytes of SHA-256(host fid ‖ visitor fid ‖ tx), with each
identifier encoded as a 4-byte integer.
The fry inherits the host's hue, the visitor's fin and body shape, and the pattern encoded in bits 5 to 6 of
the first seed byte.
A node without a trusted clock requests the time from a designated contact so its adverts and messages carry
accurate timestamps.
A node serves as a tower when the feature is enabled and its own clock source is at app, network or GNSS tier.
Request and reply
A time request is transmitted as a DIRECT anonymous info request (ANON_REQ) with
subtype 0x05 along the contact's stored path, or zero-hop when no path is stored.
Multi-byte binary integers in this and other wire formats are little-endian unless stated otherwise.
| Message |
Layout |
| Request inner |
tag u32 | 0x05 | reply path.
Each request contains a fresh, unpredictable tag to resist replay of old replies, including after a
reboot.
|
| Reply body |
tag u32 | unix u32 | tier u8.
Replies shorter than 9 bytes are invalid.
|
Clock tiers in ascending priority order are 0 (unset), 1 (manual), 2 (restored), 3 (mesh), 4 (app), 5
(network) and 6 (GNSS).
Tower
The tower responds only to DIRECT requests while clock.tower is enabled and its clock
source is above mesh tier.
Time replies are rate-limited to 16 per 180 s, independently of other info replies.
A direct neighbour receives a zero-hop reply, while other requesters receive replies along the supplied reply
path.
Room login messages have the layout timestamp u32 | sync cursor u32 | password.
To distinguish time requests from logins, a room server treats a message as a time request only when its first
four bytes fall outside the login timestamp window (15 minutes before to 5 minutes after the server's clock).
Requester
Eligible contacts are favourites with bit 0x20 set in the contact flags byte.
The first request is sent 300 s after boot, and subsequent requests are sent every
clock.tower.interval (default 1800 s, minimum 300 s).
No requests are sent while the local clock source is at mesh tier or higher.
Candidates are queried in turn, starting with the most recently active contact.
A reply is accepted only from the last queried tower, with a matching request tag, before the next request is
sent, and with a reported tier above mesh.
The accepted timestamp is applied at mesh tier according to the tier priority order above.
CLI
| Command |
Effect |
get clock.tower, set clock.tower on|off |
Enables or disables serving time requests. |
get clock.tower.interval, set clock.tower.interval <secs> |
Gets or sets the query interval in seconds. |
contact.timesource <hex prefix> on|off |
Sets or clears flag bit 0x20 on the first matching contact. |
Repeaters advertise the public hashtag channels active around them so users can discover and join those
channels by name.
A public channel name begins with # followed by 1 to 29 bytes of UTF-8 containing no commas,
0x7F, or bytes at or below 0x20.
The prefix #$ is reserved.
The 16-byte channel key is SHA-256(name with #)[0..16].
A discovery request is an ANON_REQ info request with subtype 0x04, framed and routed
in the same manner as a clock-tower request.
The reply body has the layout tag u32 | unix u32 | names, where names is a
comma-separated list truncated on a channel-name boundary to at most 160 bytes; an empty list is valid.
Receivers stop parsing the list at the first NUL byte and ignore invalid or duplicate names.
Channel-discovery replies share the repeater's general info-reply budget of 4 replies per 180 s.
On the repeater CLI, channel advertise <name>, channel remove <name> and
channel list (which prints -none- when empty) manage the advertised list.
Up to 8 channel names are persisted in configuration storage.
To scan for channels, a client broadcasts a zero-hop node-discovery request for repeaters and, over the next
60 s, queries up to 4 newly heard repeaters every 5 s.
A region channel is a public channel bound to a geographic region whose messages always flood under that
region's transport scope.
This extension implements the proposal in
meshcore-dev/MeshCore#2908.
A region channel name begins with + followed by a valid region name of 1 to 29 bytes of ASCII
lowercase letters, digits and hyphens.
The channel key is SHA-256(name with +)[0..16], derived in the same way as a hashtag channel key,
while the flood scope is the region key SHA-256(# and the bare name)[0..16].
A channel is treated as a region channel only if its name is valid and its 16-byte key matches the derivation;
otherwise it is treated as an ordinary private channel.
The flood scope is re-derived from the name and key on every load and every channel write, so a client cannot
strip the scope without modifying the channel itself.
Messages sent on a region channel use its region scope in place of both the app's session scope and the node's
default scope.
Requests to change the scope of a region channel are ignored.
The leading characters +, # and $ are reserved and cannot be used to
start a new private channel name.
A repeater evaluates an ordered list of filter rules and a per-origin advert rate limiter before relaying
packets.
Operators can suppress traffic from a noisy channel, bot, region or upstream repeater while continuing to relay
other traffic.
Matching
Packets not addressed to the local node are filtered after duplicate suppression and before the standard hop,
region and loop checks.
Content predicates are evaluated only on group packets decrypted with a known channel key and a valid message
authentication code (MAC).
The first ": " substring separates the sender name from the message body, and the first NUL byte
terminates the text.
Filtering is enabled by default with an empty rule list.
The repeater supports up to 32 rules, 32 filter channels and 512 tracked advert origins.
Rules
A rule matches a packet only when every predicate configured on the rule matches.
Omitted predicates impose no restriction, and repeating a key in a rule definition replaces its earlier value.
| Key |
Value |
Matches |
type |
Comma-separated list: req response msg ack advert txt data anonreq path trace multipart control
raw, or any |
Matches the packet payload type, where txt and data denote group text and
group data packets.
|
route |
flood or direct |
Matches the routing mode; flood matches both plain and transport-scoped flood packets. |
hops, len |
Interval |
Matches the flood hop count (for flood packets only) or the payload length in bytes. |
snr |
Interval in dB, rounded to 0.25 |
Matches the received SNR truncated to 0.25 dB steps.
Packets without receive metadata do not match.
|
path |
[^]HEX>HEX…[$], 1 to 4 entries of 1 to 4 bytes |
Matches flood packets whose path contains the specified sequence of adjacent hop hashes, each compared
over min(entry length, hash size) bytes.
^ anchors the match to the first hop and $ to the last.
|
hsize |
Comma-separated list of 1 to 4 |
Matches the packet's path-hash width in bytes. |
chanhash |
2 hex digits |
Matches group packets whose first payload byte equals the specified byte. |
region |
Comma-separated list of lowercase region names, unscoped or * |
Matches flood packets by region scope; plain flood packets match unscoped, and
transport-flood packets match the region whose transport code matches.
|
chan |
Comma-separated list of filter channel names |
Matches group packets successfully decrypted with one of the named filter channels. |
sender, text |
Pattern, ≤31 and ≤63 bytes |
Matches the non-empty sender name or message body of a decrypted group text packet. |
signed |
meshos or none |
Matches decrypted group text packets with (meshos) or without (none) a
MeshOS v1 signature trailer.
|
prob |
1 to 100 |
Gates a matching rule so that its action applies with the given percentage probability. |
throttle |
1 to 65535 s |
Gates a matching rule with a cooldown window in seconds. |
action |
drop (default) or forward |
Selects the action applied when the rule matches and its gates are satisfied. |
Numeric intervals are written as a single value v or as a range [a,b],
(a,b), [a,b) or (a,b], where square brackets are inclusive, parentheses
are exclusive, and * denotes an unbounded endpoint.
When type is specified on a rule, the predicates chan, sender,
text, signed and chanhash are accepted only if the listed types include
the corresponding packet types.
A MeshOS v1 trailer immediately follows the NUL byte terminating the text and has the binary layout
'S' 0x01 | key id [8] | signature [64], followed by fewer than 16 zero padding bytes.
The 8-byte key identifier is the prefix of the signer's public key.
The signature covers "MeshOS signed channel v1" ‖ SHA-256(channel key)[0..16] ‖ ts u32 ‖ "name:
text"; repeaters check only the trailer structure and do not verify the Ed25519 signature because the
signer's full public key is not transmitted in the packet.
Rule actions
-
Enabled rules are evaluated in order.
The action of the first rule with matching predicates and satisfied gate conditions is applied.
drop discards the packet, while forward terminates rule evaluation and passes the
packet to the advert rate limiter and standard relay checks.
-
prob applies the rule's action to the specified percentage of matching packets, producing the
same decision for duplicate copies of a packet.
-
With
throttle, the first matching packet opens the cooldown window and continues to subsequent
rules.
Further matches within the window apply the rule's action without extending the window.
-
The advert limiter suppresses repeated flood adverts from the same origin for a configurable window of 0 to
720 hours.
The window starts when an advert from that origin is relayed.
When all 512 origin slots are in use, a new origin evicts the oldest tracked entry.
Patterns
Pattern predicates use tiny-regex-c syntax, supporting . ^ $ * + ?, character
classes [abc] and [^a-z], shorthand classes \d \D \w \W \s \S, and
backslash-escaped literals.
Matching is byte-oriented, case-sensitive and unanchored unless explicitly anchored with ^ or
$.
The wildcard . matches any byte except CR and LF.
A top-level | splits a pattern into at most 8 branches with anchors binding to their own branch,
whereas \| matches a literal ASCII pipe character.
A quantifier with no preceding atom or an anchor placed away from its branch boundary never matches.
Each branch compiles to at most 29 pattern atoms, and evaluation stops after 5000 steps per branch so complex
expressions cannot stall packet relaying.
Exceeding the step limit treats the branch as a non-match.
CLI
Arguments are separated by spaces, and double quotes group values that contain spaces.
Replies fit within 160 bytes, error replies begin with Err - , and multi-page listings end with
; next=N giving the start index for the next page.
| Command |
Reply |
filter |
on; rules N/32; chans N/32; ratelimit advert Hh; cache C/512; limiter L; aborted A |
filter on|off |
OK - filter on |
filter add k=v ... |
OK - rule N added |
filter list [start] |
on N/32: then <i><e|d><D|F><digest & 0xFFF as 3
hex> per rule
|
filter get <i> |
r<i> en|dis drop|forward, the set predicates in table order (throttle=S
pass=K), then hits=H air=MS |
filter enable|disable|del <i> |
OK - rule N enabled, disabled, deleted |
filter move <from> <to> |
OK - rule F moved to T |
filter clear |
OK - rules cleared (chans kept) |
filter chan [list [start]] |
<i>:<name>:<hash hex|nokey> entries, or no channels |
filter chan add <name> [psk hex] |
OK - chan NAME h=HH, plus (derived) without a key |
filter chan del <name> |
OK - chan NAME deleted |
filter ratelimit [advert <0..720>|clear] |
ratelimit advert Hh; cache C/512, OK - advert ratelimit Hh, OK - advert
cache cleared |
filter stats [reset|start] |
lim:L abort:A air:MS:P%; hits: i:n ..., OK - filter stats reset |
Filter channel names are 1 to 15 bytes long and exclude , = " and control bytes.
Keys for Public and #name are derived automatically when those channels are first
referenced in a rule.
Deleting a channel removes it from every rule, and a rule left with an empty channel list never matches.
Vendor companion frames provide diagnostics, file transfer and channel discovery using opcodes outside the
upstream range.
Commands (0xE0), responses (0xE0) and pushes (0xE1) share the binary
layout sub | payload, and chunk sequence numbers increment modulo 256.
| Sub |
Direction |
Payload |
Meaning |
| 0x01 |
to node |
text |
Executes a console command line; output follows as 0x07 frames. |
| 0x07, 0x14 |
from node |
text |
Carries a console output line or a spontaneous notice. |
| 0x02, 0x03 |
from node |
seq | bytes |
Streams raw bytes received from or sent to the GNSS receiver. |
| 0x04 |
from node |
1 | fmt | w u16 | h u16 | stride u16 | rot | len u32 |
Starts a screenshot stream, where fmt is 0 for MSB-first monochrome, 1 for big-endian
RGB565, or 2 for little-endian RGB565.
|
| 0x05, 0x06 |
from node |
seq | bytes, none |
Carries a screenshot data chunk or marks the end of the screenshot. |
| 0x08 |
to node |
kind | key[32] or slot | have u16 |
Requests a history page older than have rows, where kind is 0 for a DM peer
key or 1 for a channel slot.
|
| 0x09, 0x0A, 0x0B |
from node |
complete | rows u16 | len u32, seq | bytes, none |
Returns a history page header, a data chunk, or the end of the page; the reassembled payload is a CBOR
array of [ts, dir, sender?, text, delivery?, snr?] tuples.
|
| 0x0C, 0x10 |
to node |
path |
Lists a directory or deletes a path on the SD card. |
| 0x0D, 0x0E, 0x0F |
to node |
len u32 | crc32 | mtime u32 | path, seq | bytes, none |
Begins a file write, sends a file data chunk (with up to 3 chunks in flight), or aborts the transfer. |
| 0x11, 0x12, 0x13 |
from node |
dir | len u32 | mtime u32 | name, have u32, status |
Reports a directory entry, cumulative bytes accepted, or operation completion (status 0 indicates
success).
|
| 0x15, 0x16, 0x17 |
both |
none, enable, enable | persists |
Reads, sets or reports the public channel-discovery state. |
| 0x18, 0x19, 0x1A |
both |
none, name, none |
Starts a channel-discovery scan, reports a discovered channel name, or marks the scan complete. |
| 0x1B, 0x1C |
both |
name, status | slot |
Adds a public channel by name and reports the result (status 0 added, 1 table full, 2 invalid name). |
Console commands include screenshot, mem, key [long] <key> and
tap <x> <y> [long] for remote input.
Responses exceeding the 176-byte frame limit are fragmented for apps that send CMD_DEVICE_QUERY
with app_target_ver 15 or above.
Each fragment is sent as push 0x91 with the layout
frag_id u16 | index | count | orig_type | orig_len u16 | offset u16 | chunk (≤166 bytes).
Fragments of live pushes are sent consecutively.
For queued messages, one fragment is sent per CMD_SYNC_NEXT_MESSAGE request, which carries the
payload flags (bit 0 ack) | frag_id u16 | index.
The next fragment is sent after a matching acknowledgement; any other request resends the current fragment.
-
The twelfth byte of
RESP_CODE_BATT_AND_STORAGE indicates the power source: 0 unknown, 1
battery, 2 plugged in, or 3 charging.
-
For
CMD_GET_CHANNEL without an index byte, the node sends one channel-info frame per
configured slot.
Nodes expose standard Bluetooth GATT services alongside the Nordic UART service so a bonded phone can trigger
an audible alert or synchronize the clock without speaking the companion protocol.
-
The Immediate Alert service (
0x1802) exposes Alert Level (0x2A06) for writes
without response: 0 stops alerting, 1 sounds a mild alert (roughly one beep per
second), and 2 sounds a high alert (rapid beeps at full volume, overriding mute).
Alert writes are accepted only over an authenticated link, and an active alert stops 5 s after the most
recent write, on a write of 0, or on disconnect.
-
The Current Time service (
0x1805, characteristic 0x2A2B) reports the node's UTC
time, or all zero bytes when the clock is unset.
An authenticated write updates the node's clock at app tier.
The receiver reassembles MULTIPART packets carrying payload types other than ACK.
The remaining-parts counter decreases by one in each successive part until it reaches zero.
Up to 4 messages of at most 1024 bytes each are reassembled concurrently, and an out-of-order part resets the
partial message.
Stock repeaters do not relay MULTIPART packets, so multipart exchanges are limited to direct
neighbours.
| Command |
Effect |
set lbt none|cad|rssi [dBm] |
Configures listen-before-talk mode and, for RSSI mode, the threshold from −110 to −40 dBm. |
set cad.offset 0..10, set cad.auto on|off, set cad.probe
off|auto|10..3600, set cad.reset |
Tunes channel-activity-detection offset, automatic adaptation and probing interval, or resets CAD state. |
get config [start] |
Outputs every setting that differs from its default as a replayable CLI line, paged with
... next:N.
|
set repeat.unrestricted.hops <n> |
Stops relaying plain flood packets once their hop count exceeds n. |
set repeat.restrict.path.hash.size <n> |
Beyond the unrestricted hop limit, drops flood packets whose path-hash width is narrower than
n bytes.
|