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).
| Template | Default TTL | Max TTL | Use case |
|---|---|---|---|
| Bulletin Board | 60 min | 24 hr | Public message board for local mesh |
| Signal Beacon | 15 min | 30 min | Ephemeral status: SOS, All Clear, Hazard |
| Quick Poll | 60 min | 24 hr | Multiple-choice vote, live tallies |
| Shared Checklist | 120 min | 24 hr | Collaborative check-off lists |
| Resource List | 120 min | 24 hr | Inventory and resource availability |
| Weather Station | 24 hr | 72 hr | Temperature, humidity, pressure readings |
| Sensor Node | 24 hr | 72 hr | Generic sensor with two data channels |
| Task Board | 120 min | 24 hr | Kanban-style task tracking across the mesh |
| Trail Conditions | 4 hr | 24 hr | Trail status: Clear, Muddy, Flooded, Blocked |
| Lost & Found | 24 hr | 72 hr | Lost/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:
- The service is stored locally in SQLite
- An immediate
SERVICE_ADVERTframe 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:
| Action | Code | Purpose |
|---|---|---|
listInstances | 0x0001 | List all active service instances on a peer |
getInstance | 0x0002 | Fetch full details of a specific instance |
interact | 0x0003 | Vote, check off an item, or perform a template action |
getSchema | 0x0004 | Fetch 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.