Skip to content

Proposal: opt-in mobile client-RX coverage (crowdsourced RF reach) + /api/nodes/resolve #1727

@efiten

Description

@efiten

Summary

I'd like to contribute an opt-in, crowdsourced RF-coverage feature, plus a small node-name lookup endpoint. The whole thing is gated behind one config flag and is disabled by default, so it has zero impact on existing deployments unless an operator turns it on. A clean branch off master is ready (efiten:feat/client-rx-coverage-pr) and I can open a PR — opening this issue first to check the design is welcome, since the feature has a companion-app dependency worth discussing up front.

What it adds

1. Mobile client-RX coverage. A roaming MeshCore companion radio — driven by an open-source Android PWA, corescope-rx (GPLv3) — reports which nodes it heard directly, tagged with the phone's GPS and the packet's SNR/RSSI. CoreScope ingests these into a new client_receptions table and renders:

  • per-node hex coverage as an overlay on the existing Reach page, and
  • a standalone Coverage dashboard (#/rx-coverage) with a top-mobile-observers leaderboard.

2. GET /api/nodes/resolve?prefix=<hex> — a tiny read-only lookup that resolves a pubkey prefix to {name, pubkey, ambiguous}. The companion app uses it to show friendly node names; it's generally useful and not coverage-specific.

How it works

companion ──BLE 0x88 (snr+rssi+raw)──▶ corescope-rx PWA ──▶ MQTT meshcore/client/{pubkey}/packets
                                                                      │
                                          ingestor (gated) ──▶ client_receptions (GPS + SNR + heard-key)
                                                                      │
              server: pure-Go hex grid ──▶ GeoJSON ──▶ Reach hex overlay + Coverage dashboard
  • Direct-only capture: records only what the companion heard itself and directly — a 0-hop advert's pubkey, or path[last] (the last forwarder) for FLOOD routes; a ≥2-byte path-hash is required. Upstream hops are discarded.
  • Pure Go, no new deps: hexbins are a pointy-top grid over Web Mercator (cmd/server/hexgrid.go) computed at query time — CGO_ENABLED=0 / modernc.org/sqlite friendly. The frontend draws with the existing Leaflet.
  • Trust: the companion pubkey is the identity; an EMQX ACL binds each client to publish only to its own meshcore/client/{pubkey}/packets topic (soft attribution).
  • The MQTT payload contract is documented in docs/client-rx-coverage.md.

Opt-in — default OFF

Everything is behind one flag, off by default:

"clientRxCoverage": { "enabled": false }

When disabled (the default): the ingestor writes no client_receptions; the three coverage endpoints return a clean 404; the frontend hides the Coverage nav link, the #/rx-coverage route, and the Reach-page toggle. /api/nodes/resolve is always available (it isn't coverage-specific).

Why opt-in / default-off

A coverage feature only makes sense for operators who want to invite users to run the companion app and contribute GPS-tagged receptions — it's a privacy/storage choice. Default-off means existing deployments are completely unaffected, and there's no crowdsourced GPS data stored unless the operator explicitly enables it.

Status

  • Built and running on a fork deployment; this is the clean upstream extraction (no fork-specific analytics/customizations).
  • Server + ingestor build and test green (new opt-in gate tests in both: off → no rows / 404, on → works); JS unit + Playwright e2e tests included (the e2e skips when coverage is disabled, so it's CI-safe under the default).

Would you accept a PR for this? Branch is ready and I'll open the PR linked to this issue. Happy to adjust the design (e.g. the data model, the companion-app boundary, or how the routes are documented in the OpenAPI spec).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions