Skip to content

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 payloadType dispatch. AWAY_MSG_REQ / AWAY_MSG_RSP message 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:

FieldSource
Node idnodeId32 (4 bytes LE), routing key
NameshortName / longName from NODE_ADVERT or NODE_INFO_RSPNode identity
BatteryPrefer batteryPercent from NODE_INFO_RSP when available; else show batteryMv (absolute terminal mV, diagnostic) or unknown
Hardware typehwBoardId, radioProfileId, and capability bits from NODE_ADVERT / NODE_INFO_RSP
Signal strengthLast 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 countHop count toward that destination from the last trace or policy estimate
Hop listCopy 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:

SettingDescription
N_maxContactsMaximum rows in the contacts aggregate (RAM and/or NVM). When full, new peers require eviction of an existing row.

Eviction order (recommended):

  1. Unpinned rows before pinned/starred.
  2. Among candidates, oldest lastSeenMs or worst pathScoreDb first (implementation-defined but SHOULD be stable).
  3. Optional hop-distance filter: if maxHopsKeep is set to a non-zero value (user / policy setting), the implementation SHOULD prefer dropping contacts whose hopCount toward that peer is greater than maxHopsKeep — 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:

FieldConstraint
awayUtf8UTF-8 text, max 128 bytes stored locally (see Text encoding)
awayLen0–128; 0 means “cleared”
presenceFlagsOptional 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 with AWAY_MSG_RSP when idle; battery-powered nodes MAY defer or rate-limit replies.
  • Privacy: On the default community channel, anyone with defaultCommunityPsk32 can 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_REQ from the same srcId (e.g. minimum interval per peer).

AWAY_MSG_REQ (0x08) — body

FieldSizeDescription
reqFlags1bit 0: request batteryMv in response; bit 1: request batteryPercent in response; other bits reserved 0
reserved10

AWAY_MSG_RSP (0x09) — body

FieldSizeDescription
reqFlagsEcho1Echo of request flags
presence10=unknown, 1=here, 2=away, 3=busy, 4=do not disturb
batteryMv2uint16 LE; MUST be absolute millivolts at the battery terminals (same semantics as NODE_INFO_RSP); 0xFFFF = not provided
batteryPercent1uint8 0–100; 0xFF = unknown/not provided
awayLen10–128
awayUtf8awayLenMay 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).

LowMeshOS — always open-source mesh protocol documentation