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 / Colorado Mesh / 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) |
MeshCore: Trace Route and Ping trace
Trace Route (node detail) and Ping trace (Repeaters panel) use the firmware tracePath flow. Remote nodes often answer only when they have your node in their contact list. Heard-only or one-way peers may produce no response until the client times out. See troubleshooting.md.
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 Packet logger (mqtt:publishMeshcorePacketLog) publishes RX packet summaries to {topicPrefix}/meshcore/packets using meshcoretomqtt-shaped JSON; implemented in MeshcoreMqttAdapter.publishPacketLog (see letsmesh-mqtt-auth.md § Packet logger). Debug logging is sampled to suppress repeated decode failures and noise (traceroute, empty-type JSON).
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). 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, Colorado Mesh, 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 (wss). JWT auth; see Authentication below. Optional Packet logger publishes to meshcore/packets. See letsmesh-mqtt-auth.md. |
| Ripple Networks | mqtt.ripplenetworks.com.au |
8883 | TLS; preset fills default shared credentials and insecure TLS for self-signed / non–public CA chains |
| Colorado Mesh | meshcore_mqtt.coloradomesh.org |
8883 / 443 | TLS; JWT auth with custom audience mapping (coloradomesh in token generation). Same topic prefix meshcore. |
| 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.
Log filtering
MQTT log messages are prefixed for easy filtering: [Meshtastic MQTT] in mqtt-manager.ts and [MeshCore MQTT] in meshcore-mqtt-adapter.ts. The Log panel filter and analyzer patterns recognize these tags.
Maintenance
When MeshCore firmware/SDK defines official MQTT topics and payloads, replace or extend MeshcoreMqttAdapter and update this document.
MeshCore MQTT Authentication
LetsMesh JWT authentication
LetsMesh uses WebSocket (wss) with JWT authentication. The implementation matches meshcore-mqtt-broker:
- MQTT username:
v1_<64-hex public key>(uppercase hex) - MQTT password: A token from
@michaelhart/meshcore-decodercreateAuthTokenwith: publicKey: 64-character hex public keyiat: Issued-at timestampexp: Expiration timestamp- JWT
aud(audience): The regional broker hostname (same as the Server field)
The JWT audience must match the regional broker hostname (mqtt-us-v1.letsmesh.net or mqtt-eu-v1.letsmesh.net).
Signing uses cached private key material from either a Radio-tab MeshCore JSON import or automatic persistence after a successful MeshCore radio session (same storage shape as import).
Configuration
Import a MeshCore config JSON file (Radio tab) when you need credentials before connecting a radio, or to replace missing data; otherwise connecting the MeshCore radio first fills the same cache. The implementation is in letsMeshJwt.ts.
Packet logger (optional)
The optional Packet logger publishes RX packet summaries to meshcore/packets under the topic prefix using meshcoretomqtt-shaped JSON. See letsmesh-mqtt-auth.md for details.