Connection Manager
connection.py contains the MeshtasticConnectionManager — the core class responsible for opening, monitoring, and recovering the radio connection throughout the life of the MeshDash process. Every radio slot gets its own independent manager instance.
Timing Constants
All timing values are class-level constants and can be understood as the "personality" of the connection loop:
HEALTH_CHECK_INTERVALPROBE_TIMEOUTMAX_STRIKESRECONNECT_COOLDOWNBASE_BACKOFFMAX_BACKOFFBACKOFF_JITTERINTERFACE_INIT_TIMEOUTDISCONNECTED_POLL_INTERVALTCP_KEEPALIVE_IDLETCP_KEEPALIVE_INTERVALTCP_KEEPALIVE_COUNTSCHEDULER_CONNECT_TIMEOUT and SCHEDULER_RW_TIMEOUT values in .mesh-dash_config are passed into the connection manager at runtime and override the class-level timeouts for initial connect and read/write operations respectively.Connection Loop Lifecycle
The connect_loop() coroutine runs indefinitely as a background asyncio task. Its state machine:
IDLE / DISCONNECTED
│
▼ attempt connect
[Interface Init] ← timeout: INTERFACE_INIT_TIMEOUT (25s)
│ success │ failure
▼ ▼
CONNECTED BACKOFF → retry
│
▼ every HEALTH_CHECK_INTERVAL (12s)
[Health Probe] ← timeout: PROBE_TIMEOUT (8s)
│ OK │ failed
▼ ▼
(loop) strike++ ← MAX_STRIKES = 2
│
│ strikes >= MAX_STRIKES
▼
RECONNECT_COOLDOWN (3s)
│
▼
[Close interface]
│
▼
DISCONNECTED → back to top
The Strike System
Health probes check that the radio is still responding. A single missed probe adds a "strike". Only after MAX_STRIKES (2) consecutive failures does the manager declare the connection dead and begin the reconnect sequence. This prevents a single slow response from causing unnecessary disconnects.
Strikes reset to 0 on a successful probe. A successful probe also resets the _last_successful_probe timestamp used for staleness detection.
is_ready Event
connection_manager.is_ready is an asyncio.Event. It is:
- Set when the interface initialises, callbacks are registered, and the background sync completes
- Cleared immediately when a disconnect is detected
Always check is_ready.is_set() before calling sendText() or any other radio operation. The POST /api/messages endpoint returns 503 if is_ready is not set.
sendText
await connection_manager.sendText(text, destinationId, channelIndex, wantAck)
Thread-safe via an asyncio.Lock (_send_lock). Will return None and log an error if the interface is None or is_ready is not set. The mesh packet object is returned on success — its .id is the packet ID used for ACK tracking.
Config Hot-Reload
The connection loop watches the .mesh-dash_config file's modification time (_config_mtime). If the file changes while the loop is running, the new connection parameters are loaded without restarting the process. This enables changing the serial port or TCP host via the Settings UI without a full restart for some scenarios.
PubSub Topics
The manager subscribes to and publishes Meshtastic PubSub topics:
meshtastic.receivemeshtastic.sentmeshtastic.connection.establishedon_connection() → sets local node info + starts background node sync.meshtastic.connection.loston_connection() → broadcasts link-down SSE event.meshtastic.node.updatedmeshState.nodes.Multi-Slot Isolation
When multiple radio slots are active, each has its own MeshtasticConnectionManager instance with its own interface reference stored in _interface_ref[0]. The receive callback is wrapped in a filter that checks interface is iface_ref[0] before firing — this prevents a packet from slot A accidentally being processed by slot B's handler. See Slot Management.
Graceful Shutdown and Restart
When MeshDash receives a shutdown signal (SIGTERM from systemd, or POST /api/system/restart), the lifespan shutdown handler:
- Waits up to 5 seconds for the packet queue to drain (all buffered packets written to DB)
- Calls
await connection_manager.shutdown()with an 8-second timeout - The shutdown method closes the Meshtastic interface cleanly (un-subscribes PubSub callbacks, closes serial/TCP/BLE connection)
- Cancels all remaining background tasks
- Exits the process
The disconnect_for_restart(settle_seconds=3.0) method is used specifically before updates — it disconnects the radio and waits 3 seconds for the hardware to settle before the process is replaced. This prevents the ESP32 from being caught mid-transmission when the process exits.
WebSerial Mode
When MESHTASTIC_CONNECTION_TYPE=WEBSERIAL, the connection manager does not run a connect_loop(). Instead, is_ready is set by the POST /api/webserial/status endpoint when the browser connects. The manager's sendText() method still works — outbound messages are queued for the browser to collect from the send endpoint.