Appearance
Local contacts, presence, and away message
Devices combine routing state, identity, and RF metrics into a contacts-oriented view for the UI. Separately, each node may store a short away message in NVM that other members of the same channel can request over the mesh.
Wire status: See Payloads for normative
payloadTypedispatch.AWAY_MSG_REQ/AWAY_MSG_RSPmessage shapes are design targets until promoted.
Contacts list (runtime + optional NVM pins)
The NeighborTable and on-wire path state (see Traceroute & link metrics and Embedded storage) are required for the mesh. In C++, the documented RAM mirror is lowmesh::runtime::Neighbor. A contacts list is an optional aggregation used by apps: one row per known peer (typically keyed by nodeId32), merging:
| Field | Source |
|---|---|
| Node id | nodeId32 (4 bytes LE), routing key |
| Name | shortName / longName from NODE_ADVERT or NODE_INFO_RSP — Node identity |
| Battery | Prefer batteryPercent from NODE_INFO_RSP when available; else show batteryMv (absolute terminal mV, diagnostic) or unknown |
| Hardware type | hwBoardId, radioProfileId, and capability bits from NODE_ADVERT / NODE_INFO_RSP |
| Signal strength | Last direct RF: lastRssiDbm, lastSnrDb on lowmesh::runtime::Neighbor. For multi-hop paths, show per-hop metrics from the last TRACEROUTE_RSP or bottleneck score from cached trace data |
| Hop count | Hop count toward that destination from the last trace or policy estimate |
| Hop list | Copy of the ordered nodeId32 path from the last successful TRACEROUTE_RSP (or empty if unknown); optional ring buffer of recent traces per peer |
| Status (reachability) | Derived: e.g. online if lastSeenMs is within a freshness window and a route exists; stale if heard recently but route uncertain; offline if no recent frames and no valid route. Implementations SHOULD document timers. |
Identity collision: If two contacts (or a new advert vs an existing row) share nodeId32 but differ in pkEd, mark identityCollision and do not silently merge them. Follow collision handling.
Host / companion structs: The merge row above is the protocol-facing view; the canonical merged row type is lowmesh::runtime::Contact in Embedded storage — same field names, referenced with the lowmesh::runtime:: prefix in code.
Local-only extras (not a wire requirement): starred/favorite flag, user notes, sort order, and “pinned” contacts MAY be stored in NVM as a small overlay (pinnedMask + notes table) — same privacy rules as other local prefs.
Capacity, eviction, and hop-distance policy
The contacts table is bounded:
| Setting | Description |
|---|---|
N_maxContacts | Maximum rows in the contacts aggregate (RAM and/or NVM). When full, new peers require eviction of an existing row. |
Eviction order (recommended):
- Unpinned rows before pinned/starred.
- Among candidates, oldest
lastSeenMsor worstpathScoreDbfirst (implementation-defined but SHOULD be stable). - Optional hop-distance filter: if
maxHopsKeepis set to a non-zero value (user / policy setting), the implementation SHOULD prefer dropping contacts whosehopCounttoward that peer is greater thanmaxHopsKeep— i.e. distant contacts are discarded first when space is needed. This keeps the list relevant for local and reachable nodes without unbounded growth from far-away routes.
If maxHopsKeep = 0, do not apply hop-based preference; eviction uses only recency, score, and pin state under N_maxContacts.
N_maxContacts and maxHopsKeep are device-local settings (NVM); see Embedded storage.
Away message (NVM on each device)
Each device MAY store:
| Field | Constraint |
|---|---|
awayUtf8 | UTF-8 text, max 128 bytes stored locally (see Text encoding) |
awayLen | 0–128; 0 means “cleared” |
presenceFlags | Optional 1 byte: HERE, AWAY, BUSY, DND — used for UI and echoed in responses |
The away message is not included in every NODE_ADVERT (airtime); it is fetched on demand with AWAY_MSG_REQ / AWAY_MSG_RSP (see Payloads).
Remote query (AWAY_MSG_REQ / AWAY_MSG_RSP)
Semantics:
- Unicast to
dstId= target node (normal mesh routing). The target SHOULD reply withAWAY_MSG_RSPwhen idle; battery-powered nodes MAY defer or rate-limit replies. - Privacy: On the default community channel, anyone with
defaultCommunityPsk32can decrypt away strings. On named/DM channels, only holders of that key see payloads — still treat as sensitive if it contains location or schedule details. - Rate limiting: Implementations SHOULD limit how often a node answers unsolicited
AWAY_MSG_REQfrom the samesrcId(e.g. minimum interval per peer).
AWAY_MSG_REQ (0x08) — body
| Field | Size | Description |
|---|---|---|
reqFlags | 1 | bit 0: request batteryMv in response; bit 1: request batteryPercent in response; other bits reserved 0 |
reserved | 1 | 0 |
AWAY_MSG_RSP (0x09) — body
| Field | Size | Description |
|---|---|---|
reqFlagsEcho | 1 | Echo of request flags |
presence | 1 | 0=unknown, 1=here, 2=away, 3=busy, 4=do not disturb |
batteryMv | 2 | uint16 LE; MUST be absolute millivolts at the battery terminals (same semantics as NODE_INFO_RSP); 0xFFFF = not provided |
batteryPercent | 1 | uint8 0–100; 0xFF = unknown/not provided |
awayLen | 1 | 0–128 |
awayUtf8 | awayLen | May be empty |
Note: Full telemetry (GPS, uptime, etc.) remains on NODE_INFO_REQ / NODE_INFO_RSP — away query is intentionally small.
Relationship to other payloads
NODE_ADVERT: lightweight periodic hello; no away text.NODE_INFO_RSP: richer device record; may overlap battery/hardware with contact row — contacts UI may refresh from either source.- Bulletin board: durable public posts on infrastructure — different from personal away text on the handset.
See Embedded storage for reference structs (lowmesh::nvm::AwayMessage, lowmesh::runtime::Contact).