Radio Connection
MeshDash supports three physical connection modes. The connection.py module manages the full lifecycle: initial connect, packet streaming, reconnection on loss, and graceful shutdown.
Serial (USB) — Recommended
The most reliable and lowest-latency method. Plug your radio directly into the device running MeshDash over USB.
Finding Your Port
# List all serial devices
ls /dev/tty*
# More targeted — show only ACM and USB serial
ls /dev/ttyACM* /dev/ttyUSB* 2>/dev/null
# Watch for the device appearing when you plug in
dmesg | tail -20
/dev/ttyACM0 — T-Beam, Heltec, most modern Meshtastic devices/dev/ttyUSB0 — older adapter chipsCOM3 etc./dev/ttyACM0 or /dev/ttyS3Permissions
# Add your user to the dialout group (logout/login required after)
sudo usermod -aG dialout $USER
# Verify membership
groups | grep dialout
WSL2 USB Passthrough (usbipd)
WSL2 does not automatically share USB devices. Install usbipd on Windows then attach your radio:
# In Windows PowerShell (as Administrator)
winget install usbipd
# List all USB devices
usbipd list
# Bind and attach the radio (replace 1-3 with your BUSID)
usbipd bind --busid 1-3
usbipd attach --wsl --busid 1-3
# In WSL, verify
ls /dev/ttyACM*
Config
MESHTASTIC_CONNECTION_TYPE=SERIAL
MESHTASTIC_SERIAL_PORT=/dev/ttyACM0
TCP (WiFi / LAN)
Connects over the local network to a radio with WiFi enabled (e.g. T-Beam in AP or STA mode).
Finding the Radio's IP
- Check your router's DHCP client table
- Use the Meshtastic mobile app — it shows the IP in device settings
- Scan:
nmap -sn 192.168.1.0/24
Config
MESHTASTIC_CONNECTION_TYPE=TCP
MESHTASTIC_HOST=192.168.1.50
MESHTASTIC_PORT=4403
Bluetooth (BLE)
Uses the Meshtastic Python SDK's BLE interface. Requires compatible Bluetooth hardware on the MeshDash host.
Finding the MAC Address
sudo bluetoothctl
scan on
# Wait ~10 seconds
devices
# Note the MAC: e.g. AA:BB:CC:DD:EE:FF
quit
Config
MESHTASTIC_CONNECTION_TYPE=BLE
MESHTASTIC_BLE_MAC=AA:BB:CC:DD:EE:FF
MQTT — Network Observer Mode
Connects to the Meshtastic MQTT servers (mqtt.meshtastic.org) or a self-hosted broker. No physical radio required — observe the wider mesh, read messages, and view telemetry from any node broadcasting to MQTT. Useful for regional monitoring without radio hardware.
How MQTT Integration Works
The MQTT manager uses paho-mqtt for transport and meshtastic protobufs to decode/encode ServiceEnvelope payloads. Incoming messages are decoded from protobuf, translated to MeshDash-compatible packet dicts, and placed on the slot's packet queue — the existing packet-processing worker consumes them identically to Serial/TCP packets.
- Subscribe topics:
msh/{REGION}/2/e/{CHANNEL}/# - Publish topics:
msh/{REGION}/2/e/{CHANNEL}/{GATEWAY_ID} - Region
#falls back toEU_868(public broker rejects fully-wildcard subscriptions) - Echo suppression: Packets from the manager's own node (matched by
MQTT_NODE_ID) are silently dropped to prevent echo loops
MQTT Presets
meshtastic_publicmqtt.meshtastic.org, Port: 1883, TLS: Nomeshtastic_public_tlsmqtt.meshtastic.org, Port: 8883, TLS: YesCredentials for the public broker: meshdev / large4cats.
MQTT Encryption (AES-CTR)
Inbound: Encrypted packets on the 2/e/ topic are decrypted with the default Meshtastic PSK (single byte 0x01 padded to 16 bytes). If the default key fails and user-supplied PSKs are available (via set_channel_psk()), those are tried next.
Outbound: All messages published to 2/e/ are encrypted using AES-CTR with the same nonce scheme the Meshtastic firmware uses: nonce[0..7] = packet_id as little-endian uint64, nonce[8..15] = from_node_num as little-endian uint64. Channel PSK resolved: caller-supplied → stored PSKs → default PSK.
Firehose Filters
Config flags to drop noisy packet types:
MQTT_DROP_TELEMETRYMQTT_DROP_POSITIONMQTT_DROP_NODEINFOMQTT_DROP_NEIGHBORMQTT_DROP_ROUTINGMQTT_DROP_ENCRYPTEDObserver Mode vs Identity Mode
MQTT provides no myInfo burst. The manager synthesises a virtual local node from MQTT_NODE_ID (optional). If set, NodeInfo packets from that node are promoted to local and you can send to the mesh. If unset, the slot operates in observer mode — receive only.
Config
MESHTASTIC_CONNECTION_TYPE=MQTT
MQTT_BROKER=mqtt.meshtastic.org
MQTT_PORT=1883
MQTT_USERNAME=meshdev
MQTT_PASSWORD=large4cats
MQTT_TLS=false
MQTT_REGION=EU_868
MQTT_CHANNEL=# # # = all channels
MQTT_NODE_ID=!aabbccdd # optional — observer mode if unset
MeshCore — Alternative Protocol Support
Connects to MeshCore companion radios (serial, TCP, or BLE) using the meshcore Python library (≥2.3). MeshCore is a different protocol than Meshtastic — it uses a binary companion protocol with its own framing, event types, and commands. MeshDash translates MeshCore events into the standard packet pipeline.
Event Translation
MeshCore events are translated to MeshDash packet types:
| MeshCore EventType | MeshDash Packet Type | Notes |
|---|---|---|
SELF_INFO | Node Info + Position | Own node identity on appstart |
ADVERTISEMENT | Node Info + Position | Another node discovered |
NEW_CONTACT | Node Info + Position | Same as advertisement |
CONTACT_MSG_RECV | Message (DM) | toId = local node |
CHANNEL_MSG_RECV | Message (broadcast) | toId = ^all |
ACK | Ack (ROUTING_APP) | DM delivery confirmation |
TELEMETRY_RESPONSE / STATUS_RESPONSE | Telemetry | Battery, voltage, uptime, noise floor |
BATTERY | Telemetry | Lightweight battery event |
PATH_UPDATE | Traceroute | Routing path discovery |
Node ID Convention
MeshCore identifies nodes by a 6-byte public key prefix (12 hex chars), e.g. a1b2c3d4e5f6. MeshDash node IDs use the same !hexstring convention: !a1b2c3d4e5f6. This is longer than Meshtastic's 8-char IDs, but MeshDash treats node IDs as opaque strings throughout.
RX_LOG Correlation
MeshCore fires RX_LOG_DATA events separately from message events. The manager implements a 500ms correlation window:
CHANNEL_MSG_RECVarrives → sleep 500ms- Check
_pending_rx_logsfor entries matching{channel_idx}:{timestamp}:{text[:40]} - Attach best SNR/RSSI to the message packet
ACK Tracking
When sendText() sends a DM, the meshcore library returns an expected_ack code stored in _pending_acks (capped at 500, 5-minute TTL). When an ACK event fires, the matched packet_id is emitted as a ROUTING_APP packet so the messages DB can update status to DELIVERED.
Session Startup Sequence
- Create
MeshCoreinstance via transport factory - Subscribe all event handlers
start_auto_message_fetching()send_appstart()→ triggersSELF_INFO_refresh_contacts()→ populate contacts cacheget_channel(0..7)→ populate channel info (stops after 2 consecutive errors)get_self_telemetry()→ battery/uptime appear immediatelyget_device_info()→ firmware version, radio paramssend_advert(flood=False)→ announce presence on mesh
Config
MESHTASTIC_CONNECTION_TYPE=MESHCORE
MESHCORE_TRANSPORT=serial # serial, tcp, or ble
MESHCORE_SERIAL_PORT=/dev/ttyUSB0
MESHCORE_BAUD=115200
MESHCORE_HOST=192.168.1.100 # if transport=tcp
MESHCORE_PORT=4000 # if transport=tcp
MESHCORE_BLE_MAC= # if transport=ble
MESHCORE_BLE_PIN= # if transport=ble
MESHCORE_LABEL=My MeshCore Node
Web Serial (Browser-Direct USB)
A fourth connection mode — WEBSERIAL — lets the browser hold the serial port directly, bypassing the server's serial connection entirely. This is useful when MeshDash runs on a remote server but you want to connect a radio physically attached to your local PC.
Set MESHTASTIC_CONNECTION_TYPE=WEBSERIAL in the config (or select it in Settings). After restart, the server will not open any serial port. A WEB SERIAL button appears in the topbar — click it to connect the browser to the radio via the Web Serial API.
See the full Web Serial Connection guide for the complete setup walkthrough, baud rate selection, wakeup frame, and disconnect procedure.
Connection State Machine & Reconnection
All six connection types share the same state machine and reconnect logic. See Connection Manager for the full state transition diagram (IDLE → CONNECTING → DEGRADED → CONNECTED with RECONNECTING and DISCONNECTED branches), two-layer health checks, 3-strike system, exponential backoff with jitter, and is_ready event.
Which Connection Type Should I Use?
Are you connecting a radio physically attached to this server?
│
├─ YES ── What radio type?
│ ├─ Meshtastic radio (T-Beam, Heltec, etc.) → SERIAL
│ │ Most reliable. USB data cable required. Use dialout group.
│ │ Auto-detects port. Config: MESHTASTIC_CONNECTION_TYPE=SERIAL
│ │
│ └─ MeshCore radio → MESHCORE (serial)
│ Alternative protocol. Config: MESHCORE_TRANSPORT=serial
│
├─ Is radio on the local network with WiFi?
│ └─ TCP → connects to radio's IP:4403
│ Assign static IP or DHCP reservation.
│ TCP keepalive probes detect dead radios.
│ Config: MESHTASTIC_CONNECTION_TYPE=TCP MESHTASTIC_HOST=192.168.1.50
│
├─ Bluetooth radio nearby? (least stable, experimental)
│ ├─ Meshtastic BLE → BLE
│ │ Requires server Bluetooth adapter. Pair from CLI.
│ │ Config: MESHTASTIC_CONNECTION_TYPE=BLE MESHTASTIC_BLE_MAC=AA:BB:CC:DD:EE:FF
│ └─ MeshCore BLE → MESHCORE (ble)
│
├─ No physical radio at all? Just want to observe the mesh?
│ └─ MQTT → connects to mqtt.meshtastic.org
│ Observe without radio hardware. Receive only by default.
│ Set MQTT_NODE_ID for send capability.
│ Config: MESHTASTIC_CONNECTION_TYPE=MQTT
│
└─ Radio is near your browser, not the server?
└─ WEBSERIAL → browser holds the USB connection
Chrome/Edge only. HTTPS required. Server parks idle.
Config: MESHTASTIC_CONNECTION_TYPE=WEBSERIAL
The /api/status endpoint always reflects the current connection state:
GET /api/status
{
"api_status": "online",
"connection_status": "Connected",
"is_system_ready": true,
"local_node_info": { ... },
"last_error": null,
"public_mode": false
}
is_system_ready is true only when the radio connection is fully established and the background sync is complete. Always check this before sending messages.
Connection History
Every connection state change (Connected, Disconnected, Reconnecting, etc.) is logged to the connection_log table and exposed via:
GET /api/system/connection_history?limit=60
Returns timestamped entries with a numeric value field (0.9 = connected, 0.5 = transitioning, 0.1 = disconnected) suitable for time-series charting.
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
Permission denied /dev/ttyACM0 | Not in dialout group | sudo usermod -aG dialout $USER then re-login |
Device or resource busy | Another process has the port open | Close Meshtastic app / Python Flasher. fuser /dev/ttyACM0 to identify. |
No /dev/ttyACM* visible | Charge-only cable, wrong driver | Use a data USB cable. Check dmesg | tail when plugging in. |
| TCP connection refused | Wrong IP or radio WiFi disabled | Verify IP with nmap, confirm radio is in WiFi mode. |
| Keeps reconnecting every ~30 s | Serial cable quality / Pi USB power | Use a powered USB hub, shorter cable, or TCP mode. |
| BLE not found | Radio out of range or BLE off in firmware | Check radio Bluetooth settings, reduce distance to <2 m for pairing. |