Mesh Services: Publish Anything on the Mesh

What if you could publish a poll, a weather report, or a trail conditions update directly on the mesh — and anyone nearby could discover and interact with it, with no internet, no server, no accounts? That is what Mesh Services does.

The problem

Mesh radios are good at two things: messages and telemetry. But there is a whole category of use case that neither covers. You are at a trailhead and want to post that the creek crossing is flooded. You are at a group campsite and want to run a quick vote on tomorrow’s route. You are monitoring a weather station and want other hikers to see your readings.

These are not messages. They are services — structured data with a lifespan, published to the mesh for anyone to discover. No conversation required.

Templates

Rather than a freeform “create anything” system, we ship 10 purpose-built templates. Each one defines the schema (what fields exist), the interaction model (how peers engage), and TTL bounds (how long it lives).

TemplateDefault TTLMax TTLUse case
Bulletin Board60 min24 hrPublic message board for local mesh
Signal Beacon15 min30 minEphemeral status: SOS, All Clear, Hazard
Quick Poll60 min24 hrMultiple-choice vote, live tallies
Shared Checklist120 min24 hrCollaborative check-off lists
Resource List120 min24 hrInventory and resource availability
Weather Station24 hr72 hrTemperature, humidity, pressure readings
Sensor Node24 hr72 hrGeneric sensor with two data channels
Task Board120 min24 hrKanban-style task tracking across the mesh
Trail Conditions4 hr24 hrTrail status: Clear, Muddy, Flooded, Blocked
Lost & Found24 hr72 hrLost/Found/Claimed item tracking

Templates are not arbitrary — each one maps to a real scenario people actually encounter in the field. The TTL bounds reflect this: a signal beacon (SOS, hazard warning) should be short-lived and urgent. A weather station reading is useful for days.

How TTL works

Every service gets an absolute expiry timestamp computed at publish time:

expiresAt = now + selectedTtl

This is stored in SQLite. It survives app restarts, device sleep, even reboots. A 60-minute service created at 2:00 PM expires at 3:00 PM, period. No pausing, no extending, no resetting.

When a peer discovers your service 20 minutes later, they see “40 min remaining” — computed fresh from the absolute timestamp, not from the original TTL. Late joiners always get accurate information.

When the TTL expires, the service is simply omitted from future advertisements. There is no explicit “un-advertise” message. Peers’ cached entries age out naturally within an hour.

The wire format problem

LoRa has a 237-byte MTU. After SIP and MRRP headers, that leaves 195 bytes of payload. That is not a lot of room for a structured service with a title, description, fields, and interaction data.

The solution: a compact binary schema codec. Every template’s schema — its fields, types, and actions — is serialized into a binary format that fits within the payload budget. Field types are encoded as single bytes. Strings are length-prefixed. Options lists use counted arrays.

Remote peers cache schemas after the first fetch (64 entries, 1 hour TTL). Subsequent interactions skip the schema request entirely, saving precious airtime.

Discovery

When you publish a service, two things happen:

  1. The service is stored locally in SQLite
  2. An immediate SERVICE_ADVERT frame is broadcast on the mesh

After that, the MRRP advertisement engine periodically re-broadcasts your active services. Each advertisement can carry descriptors for up to 8 services. If you have more than 8 active, they rotate across broadcast cycles.

Nearby peers receive the advertisement and see your service appear in Mesh Explorer — grouped by type, showing how many peers are offering each kind of service. They can tap to view details, and the interaction happens via MRRP request-response with live delivery tracking.

Interactions are ephemeral

This is a deliberate design choice. Poll votes, checklist check-offs, and other interaction state lives in memory only. If the publisher’s app restarts, vote tallies reset to zero.

Why? Because the alternative — persisting interaction state for potentially dozens of remote peers across an unreliable mesh — adds enormous complexity for marginal benefit. A poll that runs for 60 minutes is useful during those 60 minutes. The votes matter in that moment. If the app crashes and the poll resets, you create a new one. The mesh is ephemeral by nature.

Each peer gets one vote per poll. Voting again changes your selection (no ballot stuffing). Checklists allow any peer to toggle any item. It is collaborative, informal, and designed for small groups where trust is implicit.

The four MRRP actions

All mesh services share a single MRRP service ID (0x00000010). The handler demultiplexes by instance ID and one of four action codes:

ActionCodePurpose
listInstances0x0001List all active service instances on a peer
getInstance0x0002Fetch full details of a specific instance
interact0x0003Vote, check off an item, or perform a template action
getSchema0x0004Fetch the service’s field/action schema

These are request-response pairs over MRRP. The delivery tracker in the UI shows real-time phase updates: preparing, sending, delivered, or failed. If the mesh is congested and the airtime budget is exhausted, the request waits until budget refills.

Limits

A few hard limits keep the system honest:

  • 50 services per device. When the limit is reached, oldest expired services are evicted first, then stopped, then active. LRU eviction, 60-second periodic cleanup.
  • 8 descriptors per advertisement. More than 8 active services rotate across broadcast cycles.
  • 1024 bytes per 60 seconds airtime budget. Service advertisements respect the same rate limiter as all SIP traffic. The mesh comes first.
  • No editing. Once published, a service’s title and config are frozen. Want to change something? Stop it and create a new one. This keeps the protocol simple — no update propagation, no version conflicts, no stale caches.

No cloud, no accounts, no tracking

Services are stored in a local SQLite database. There is no cloud sync, no backup, no server component. Advertisements travel over LoRa radio and are visible to any SocialMesh user in range. Nothing leaves the local mesh.

This is intentional. Mesh services are a local, ephemeral, peer-to-peer tool. They exist for the people physically present on the mesh. When the TTL expires, they are gone. That is the point.

What comes next

The current implementation covers the full lifecycle: creation wizard, template-specific forms, TTL management, MRRP-based discovery and interaction, delivery tracking, and the My Services management screen. The Mesh Explorer shows peer services grouped by type.

Future work includes audience scoping (private services visible only to specific peers), richer interaction models for sensor templates, and potentially service forwarding where intermediate nodes can relay service data deeper into the mesh.

But the foundation is solid: 10 templates, binary schemas, absolute TTL, ephemeral interactions, and zero internet. Publish anything on the mesh.