Adds a new read-only endpoint that serves the bundled `repeater/presets/*.yaml`
catalogue so the admin UI can render a network picker without bundling its own
copy of the broker dicts. The UI side of this is paired with
pyMC-dev/pyMC-RepeaterUI#TBD which retires src/assets/broker-templates.json
in favour of authClient.get('/api/broker_presets').
Why
The UI previously shipped a separate JSON snapshot of every supported MC2MQTT
network. The JSON and these YAML files drifted: the Waev entry on the UI side
pointed at mqtt-a.waev.app with audience mqtt.waev.app (single primary, no
failover) while the YAML side here listed two brokers (A + B). The result was
that operators picking 'Waev' from the dropdown silently lost the redundancy
this preset is meant to provide.
What changes
repeater/presets/*.yaml
- Add optional top-level `display_name` and `website` fields. The loader
treats them as advisory metadata for the UI; the runtime connection code
never reads them. `display_name` falls back to the titlecased filename
stem if absent so existing third-party presets keep rendering.
repeater/presets/waev.yaml
- Collapse from two broker entries (waev-a, waev-b) to a single broker on
`mqtt.waev.app`. The Waev edge Worker (see waev/src/router.ts:
MQTT_PRIMARY_FAILOVER_TIMEOUT_MS) already does server-side A/B failover on
the alias host with a 1500 ms timeout. Two independent client connections
would defeat the dedup-on-pubkey-hash contract on the waev ingest side.
Operators who want to pin to a specific container can edit host/audience
after import.
repeater/presets/meshmapper.yaml (new)
- Port of the historical MeshMapper entry from the UI's deprecated JSON.
Single broker on mqtt.meshmapper.cc, format: letsmesh (matches the
published wire contract; bump to a dedicated value if/when wire-level
differentiation lands).
repeater/web/api_endpoints.py
- New `broker_presets` CherryPy handler at `GET /api/broker_presets`.
Unauthenticated to match the existing `mqtt_status` precedent — the
response carries only public hostnames + TLS hints, no PII. Imports the
presets module lazily so a broken YAML never blocks process startup.
Response shape:
{
success: true,
data: [{ id, name, website?, brokers: [ ... raw YAML dicts ... ] }, …]
}
tests/test_presets.py
- Locks the new metadata fields (display_name, website) on all three presets.
- Locks the Waev single-alias-broker design with an explicit comment tying
the test to the waev Worker failover code.
- Adds MeshMapper coverage parallel to the other public-network presets.
- Adds a stub-instance test that drives the new `broker_presets` method on
an APIEndpoints stand-in (bypassing the heavyweight `__init__`) and
asserts the UI-ready response shape.
Verification
- New endpoint serves the expected three presets (letsmesh: 2 brokers,
meshmapper: 1, waev: 1) when exercised end-to-end against a local mock
that imports the real preset loader.
- Existing legacy-config migration tests (broker_index 0/1/-1 → preset +
overrides) still pass — the override pipeline is untouched.
Co-Authored-By: Oz <oz-agent@warp.dev>
Bridges browser WebSocket connections to companion TCP frame servers.
Uses configured bind_address (not hardcoded 127.0.0.1) so the proxy
works regardless of how the frame server is bound.
- JWT auth (same pattern as PacketWebSocket)
- Resolves companion by name → (host, port) from config
- Raw byte pipe: WS ↔ TCP, no protocol parsing
- Diagnostic logging throughout for troubleshooting
Co-Authored-By: Oz <oz-agent@warp.dev>
Dumb byte pipe between browser WebSocket and companion TCP frame server.
Allows browser clients to speak the MeshCore companion frame protocol
directly — all parsing lives client-side.
New file: repeater/web/companion_ws_proxy.py
- ws4py handler with JWT auth (same pattern as PacketWebSocket)
- Resolves companion_name → TCP port from config
- Bidirectional byte forwarding: WS ↔ TCP
http_server.py: mount at /ws/companion_frame alongside /ws/packets
Co-Authored-By: Oz <oz-agent@warp.dev>
Add start_timestamp and end_timestamp parameters to the bulk_packets API
endpoint to allow time-range filtering for bulk packet retrieval.
Co-Authored-By: Warp <agent@warp.dev>
Replace simplified airtime calculation with the proper Semtech reference
formula that accounts for:
- Coding rate (CR)
- CRC overhead
- Explicit/implicit header mode
- Low data rate optimization (SF11/SF12 at 125kHz)
The previous formula significantly underestimated airtime, especially
at higher spreading factors, leading to inaccurate duty cycle tracking.
Reference: https://www.semtech.com/design-support/lora-calculator
Co-Authored-By: Warp <agent@warp.dev>
Adds a new endpoint to update radio and repeater configuration via HTTP API,
supporting both persisted storage and live in-memory updates.
Supported settings:
- Radio: tx_power (2-30 dBm)
- Delays: tx_delay_factor, direct_tx_delay_factor, rx_delay_base
- Repeater: node_name, latitude, longitude, max_flood_hops,
flood_advert_interval_hours, advert_interval_minutes
Features:
- Validates all input parameters with appropriate ranges
- Saves to config.yaml for persistence
- Updates daemon's in-memory config for immediate effect (no restart needed)
- Returns live_update status so clients know if restart is required
This enables web dashboards like pyMC Console to configure the repeater
without requiring SSH access or service restarts for most settings.
Co-Authored-By: Warp <agent@warp.dev>
Adds the following fields to the stats API response for MeshCore CLI parity:
- config.repeater.max_flood_hops: Max flood hops setting (for 'get flood.max')
- config.repeater.advert_interval_minutes: Local advert interval (for 'get advert.interval')
- config.delays.rx_delay_base: RX delay base setting (for 'get rxdelay')
These fields are already present in config.yaml but were not exposed via the
stats API, making them inaccessible to web dashboards and CLI tools that
communicate over HTTP.
This enables pyMC Console's Terminal to display these values without
requiring direct config file access.
Co-Authored-By: Warp <agent@warp.dev>