Meshtastic vs MeshCore feature parity
This document summarizes which client features are Meshtastic-only, MeshCore-only, or shared, and whether gaps are app wiring, post-MQTT, or blocked by protocol.
See also CONTRIBUTING.md (dual-protocol architecture).
Capability flags
Shared UI gates use ProtocolCapabilities in src/renderer/lib/radio/BaseRadioProvider.ts. Prefer new gates there instead of protocol === 'meshcore' string checks.
Feature matrix
| Area | Meshtastic | MeshCore | Gap type |
|---|---|---|---|
| Transports | BLE, Serial, HTTP (@meshtastic/core) |
BLE, Web Serial, TCP bridge (5000) | Different stacks |
| Tab “Modules” / “Repeaters” | ModulePanel (protobuf modules) |
RepeatersPanel (trace, status, neighbors) |
Product split |
| MQTT broker UI | Full (with transport selection) | Same broker fields; transport protocol selected when connecting; MeshCore-only LetsMesh / Ripple / Custom presets fill known public brokers | Post-MQTT codec on broker path |
| MQTT wire format | ServiceEnvelope / MeshPacket (mqtt-manager.ts) |
JSON v1 chat on {topicPrefix}/meshcore/chat (non-LetsMesh / private brokers); LetsMesh: optional meshcoretomqtt-style packet JSON on {topicPrefix}/meshcore/packets (meshcore-mqtt-adapter.ts); chat parser in meshcoreMqttEnvelope.ts |
Adapter vs protobuf |
| Node list hops / MQTT columns | hops_away, via_mqtt from device |
Contact model; hops via outPathLen from device trace |
App (implemented) |
| RF diagnostics (LocalStats) | From protobuf | Not available | Blocked |
| Routing diagnostics (hop-based) | RoutingDiagnosticEngine with hop count |
Skipped when hasHopCount === false |
Blocked until hop metric exists |
| Neighbor UI | neighborInfo protobuf |
getNeighbours (repeaters) |
Different primitive |
| Radio config | Full protobuf (role, presets, WiFi, etc.) | setRadioParams, channels, advert name/position |
Blocked for Meshtastic-only admin |
| Position | Full GPS protobuf + request position | Advert lat/lon + setAdvertLatLong |
Partial |
| Waypoints | Supported | Not in protocol surface | Blocked |
| Favorites | nodes table |
meshcore_contacts.favorited + db:updateMeshcoreContactFavorited |
App (implemented) |
| Environment telemetry charts | Device telemetry module | Cayenne LPP via getTelemetry → environmentTelemetry |
App (implemented) |
| Chat transport badges / history | received_via on Meshtastic messages |
meshcore_messages.received_via (rf / mqtt / both) |
App (implemented) |
| Chat search | searchMessages |
searchMeshcoreMessages; UI search modal supports user: / channel: filters for cross-channel lookup |
Parallel DB tables |
Chat @[Display Name] tokens |
Same on-wire pattern for replies / reactions / path-style lines | Same | App — chat body renders tokens as inline labels (see below) |
| Repeater CLI | Not applicable | Per-repeater expandable CLI in RepeatersPanel; prefix-token correlation, retry, flood/auto routing toggle (RepeaterCommandService); Flood Advert and Sync Clock buttons moved to Radio panel (Device Actions section); auto flood advert scheduling available in App Settings (disabled / 12h / 24h) |
App (MeshCore-only) |
| Security / PKI admin | SecurityPanel when hasSecurityPanel |
Tab omitted — no Meshtastic-style PKI surface on MeshCore firmware | Blocked (protocol) |
| Contact groups | Built-in groups (GPS, RF+MQTT) via meshtasticContactGroupUtils; user-managed via ContactGroupsModal |
SQLite-backed groups + Nodes toolbar (useContactGroups, ContactGroupsModal); built-in Room filter |
App — protocol-neutral with Meshtastic built-ins |
| Log analyzer | LogPanel → Analyze (logAnalyzer.ts, protocol-aware) |
Same shared UI | App (implemented) |
Windows: MeshCore over BLE
Pair the radio in Settings → Bluetooth & devices before connecting from the app; WinRT is much more reliable with a bonded device. The client may retry once after transient GATT discovery failures, and canceling mid-connect should not surface a misleading long-running channel timeout. User-facing copy lives in the Connection tab on Windows; contributor details are in CONTRIBUTING.md (MeshCore internals, BLE) and README.md (MeshCore Transport Notes).
Linux: MeshCore over BLE
Linux uses Web Bluetooth in the renderer (not Noble). After you pick a device, the client reads bluetoothctl info <MAC>. If the radio is not paired in BlueZ, the UI asks for the PIN shown on the device and runs bluetooth-pair before resolving the pending Web Bluetooth requestDevice() selection. If a handshake times out, a single retry reuses the granted device via getDevices() so requestDevice() is not called again without a click. See development-environment.md and troubleshooting.md.
Chat mention tokens
Meshtastic and MeshCore use the literal form @[Display Name] in channel payloads for thread replies, emoji tapbacks, path / hop summaries, and inline references. The client may keep the raw string in storage when a reply parent cannot be matched; the Chat tab still parses these segments for display only: brackets are hidden and the name is shown as a compact inline label (ChatPayloadText in ChatPanel.tsx, parser in chatMentionSegments.ts). Threading / replyId behavior is unchanged — this is purely presentational.
MeshCore MQTT JSON envelope (v1)
Interim broker format until a binary/official MeshCore MQTT layout ships:
{
"v": 1,
"text": "message body",
"channelIdx": 0,
"senderName": "optional",
"senderNodeId": 305419896,
"timestamp": 1700000000000
}
Subscribes under {topicPrefix}/#. Outbound optional publish uses mqtt:publishMeshcore → {topicPrefix}/meshcore/chat (JSON same shape). LetsMesh public brokers do not use that path for MQTT-only chat without a radio; optional mqtt:publishMeshcorePacketLog → {topicPrefix}/meshcore/packets for Analyzer (see letsmesh-mqtt-auth.md § Packet logger).
Meshtastic MQTT network presets
In Meshtastic mode, ConnectionPanel.tsx shows MQTT :1883, Liam's, and Custom preset buttons. They populate MQTTSettings used by mqtt-manager.ts.
| Preset | Broker host | Port | Notes |
|---|---|---|---|
| MQTT :1883 | mqtt.meshtastic.org |
1883 | Plaintext; may be blocked on some networks |
| Liam's | mqtt.meshtastic.liamcottle.net |
1883 | Uplink-only (puts your node on Liam Cottle's map; no downlink). uplink / uplink credentials, no TLS. Useful when mqtt.meshtastic.org is unreachable |
| Custom | (user) | — | No automatic changes — use for private brokers |
Topic prefix defaults to msh/US/; users can edit fields after choosing a preset. Defined in meshtasticMqttTlsMigration.ts.
MeshCore MQTT network presets
In MeshCore mode only, ConnectionPanel.tsx shows LetsMesh, Ripple Networks, and Custom preset buttons. They populate the same MQTTSettings the main process uses for meshcore-mqtt-adapter.ts (with mqttTransportProtocol: 'meshcore').
| Preset | Broker host | Port | Notes |
|---|---|---|---|
| LetsMesh | mqtt-us-v1.letsmesh.net or mqtt-eu-v1.letsmesh.net |
443 | WebSocket (useWebSocket / wss). Auth matches meshcore-mqtt-broker: MQTT username v1_<64-hex public key> (uppercase); password is a token from @michaelhart/meshcore-decoder createAuthToken with publicKey, iat, exp, and JWT aud equal to the regional broker hostname (same as the Server field; aligns with common token tooling). Optional Packet logger publishes RX summaries to meshcore/packets under the topic prefix (meshcoretomqtt-shaped JSON). See docs/letsmesh-mqtt-auth.md. Implemented in letsMeshJwt.ts. Import MeshCore config JSON (Radio tab) so public_key and private_key are cached. |
| Ripple Networks | mqtt.ripplenetworks.com.au |
8883 | TLS; preset fills default shared credentials and insecure TLS for self-signed / non–public CA chains |
| Custom | (user) | — | No automatic changes — use for private brokers |
Topic prefix is set to meshcore for both public presets; users can still edit fields after choosing a preset.
Maintenance
When MeshCore firmware/SDK defines official MQTT topics and payloads, replace or extend MeshcoreMqttAdapter and update this document.