Question: Can we reliably detect when GCM is disconnected before it reconnects?
Short Answer: No, not reliably with current Meshtastic protocol over serial UART.
Current State: We can detect reconnection perfectly (via rebooted_tag), but disconnection has no reliable signal.
Location: lib/meshtastic-arduino_src/mt_protocol.cpp:789-792
// Sent every 60 seconds
if(now >= (last_heartbeat_at + HEARTBEAT_INTERVAL_MS)){
mt_send_heartbeat(); // Tag 7 (heartbeat_tag)
last_heartbeat_at = now;
}Purpose: REQUIRED - GCM firmware will close the connection if no messages received for 15 minutes Direction: ONE-WAY (GCD → GCM only) GCM Response: NONE - The GCM does not acknowledge heartbeats Protocol Design: Heartbeat is a "dummy" message with no response expected
From mt_protocol.cpp comments (lines 24-26):
"Otherwise the connection is closed, and packets will no longer be received We will send a ping every 60 seconds, which is what the web client does" Reference: Meshtastic.js serialConnection.ts
From mesh.pb.h comments:
"A heartbeat message is sent to the node from the client to keep the connection alive. This is currently only needed to keep serial connections alive, but can be used by any PhoneAPI."
Heartbeat structure:
typedef struct _meshtastic_Heartbeat {
char dummy_field; // Size: 0 bytes - literally empty!
} meshtastic_Heartbeat;Why it's needed:
- GCM firmware expects at least one message every 15 minutes
- Without heartbeat, GCM will assume connection is dead and close it
- 60-second interval provides safety margin (15min / 15 = 4min, using 1min)
- This is a firmware requirement, not optional
Conclusion:
- Heartbeat is MANDATORY - DO NOT REMOVE
- It's a KEEPALIVE for the GCM, not a PING/PONG for disconnect detection
- No response expected or provided by GCM firmware
From test output analysis, during normal operation GCM sends:
| Tag | Name | Frequency | Trigger |
|---|---|---|---|
| 2 | packet_tag | Variable | Mesh network traffic (from other nodes) |
| 4 | node_info_tag | Frequent | Neighbor discovery/updates |
| 11 | queueStatus_tag | Periodic | Queue status updates |
| 15 | fileInfo_tag | Bursts | File transfer related |
Key Observations:
- No packets are guaranteed - If mesh is quiet, no tag 2 packets
- Tag 4 (node_info) frequency depends on neighbors - Could stop if no neighbors
- Tag 11 (queueStatus) appears periodic - But frequency is unknown and may depend on queue activity
- No packet is a direct response to heartbeat - Heartbeat is fire-and-forget
Hardware: ESP32 UART2 (HardwareSerial) Protocol: 8N1 at 115200 baud (MT_DEV_BAUD_RATE)
UART Limitations:
- No hardware disconnect detection (unlike USB)
- No DTR/RTS flow control configured
- RX buffer will simply stop receiving data
- No error signaling when remote device powers off
Potential UART-level detection:
- Framing errors (if disconnected mid-transmission)
- RX buffer timeout (if we knew expected packet interval)
Based on analysis, likely failure modes:
If timeout was set too aggressively (e.g., 30 seconds):
- Mesh network could be legitimately quiet for longer periods
- No guaranteed periodic packets from GCM
- False positives: declaring GCM disconnected when it's just idle
If timeout was set conservatively (e.g., 120+ seconds):
- Defeats the purpose - user sees stale data for 2+ minutes
- By the time disconnect is detected, GCM may have already reconnected
If monitoring a specific packet type (e.g., tag 11 queueStatus):
- Frequency may vary based on queue activity
- May not be sent if nothing is queued
- Unreliable timing
If expecting GCM to respond to heartbeat:
- Protocol doesn't specify a response
- GCM firmware doesn't send one
- Will never work without GCM firmware modification
FromRadio tag: 4 (every ~1-2 seconds based on grouping)
FromRadio tag: 2 (variable - depends on mesh traffic)
FromRadio tag: 11 (periodic - timing unknown)
ESP-NOW Telemetry from GCI (every ~1 second)
Key Finding: Even during "stable" operation, packet arrival is irregular. There are gaps in the FromRadio messages.
FromRadio tag: 8 ← DISCONNECT DETECTED HERE (GCM rebooted)
FromRadio tag: 3 ← GCM identity
FromRadio tag: 4 (37 times - all nodes)
FromRadio tag: 10 (8 times - all channels)
FromRadio tag: 5 (10 times - all configs)
FromRadio tag: 9 (13 times - all module configs)
FromRadio tag: 7 ← Handshake complete
FromRadio tag: 11 ← Normal operation resumes
FromRadio tag: 2 ← Mesh packets resume
Key Finding: rebooted_tag (tag 8) is a perfect reconnection indicator. It's sent immediately when GCM boots up.
- ✗ Meshtastic protocol does not define heartbeat response
- ✗ GCM firmware cannot be modified (upstream Meshtastic)
- ✗ UART has no hardware disconnect detection
- ✗ Packet arrival timing is non-deterministic
- ✓ Can monitor any/all incoming packets
- ✓ Can set reasonable timeout thresholds
- ✓ Can distinguish between "disconnected" and "idle"
- ✓ Can provide UI feedback about connection state
Reliability: Medium Complexity: Low False Positive Rate: Low to Medium
Method:
- Track timestamp of last received packet (ANY tag)
- If no packet received in 90-120 seconds, assume disconnected
- Update UI to show "Connection uncertain" or similar
Pros:
- Simple to implement
- No protocol changes needed
- Low false positive rate
Cons:
- 90-120 second delay before detection
- Still possible false positives in very quiet mesh
- User sees stale node ID for extended period
Implementation:
// In mt_protocol.cpp handle_packet()
static uint32_t last_packet_time = 0;
bool handle_packet(uint32_t now, size_t payload_len) {
last_packet_time = now; // Update on ANY packet
// ... existing code ...
}
// In meshtastic_task.cpp
#define GCM_TIMEOUT_MS 120000 // 2 minutes
static bool gcm_connection_uncertain = false;
if ((millis() - last_packet_time) > GCM_TIMEOUT_MS) {
if (!gcm_connection_uncertain) {
gcm_connection_uncertain = true;
Serial.println("*** GCM CONNECTION UNCERTAIN ***");
// Optional: UI update, but don't clear node ID yet
}
}Reliability: Perfect (for what it does) Complexity: Zero (already implemented) False Positive Rate: Zero
Method:
- Accept that disconnect detection is unreliable
- Use
rebooted_tagto detect reconnection (already working) - Clear UI state on reconnection, not on disconnect
Pros:
- Already fully implemented and tested
- Zero false positives
- Zero false negatives
- No additional complexity
- Matches actual protocol capabilities
Cons:
- Cannot detect disconnect proactively
- User doesn't know GCM is disconnected until reconnection
- Stale data may be displayed while disconnected
Current Behavior (already working):
1. GCM disconnected → No indication (no reliable signal)
2. GCM reconnects → rebooted_tag received
3. GCD responds → Clears node ID, sends wake notification
4. Handshake completes → Node ID restored, system operational
Reliability: High (if implemented) Complexity: High False Positive Rate: Very Low
Method:
- Periodically request node info from GCM
- Monitor if response is received
- If no response in 5-10 seconds, retry
- After 3 failed attempts, declare disconnected
Pros:
- Fast disconnect detection (15-30 seconds)
- Low false positive rate
- Active verification
Cons:
- Requires finding a GCM command that guarantees response
- Adds extra protocol traffic
- More complex state machine
- May still fail if GCM is busy/overloaded
Potential Implementation:
- Use
mt_request_node_report()periodically - Monitor if callback is invoked
- Problem: Callback may not be invoked if GCM is processing other things
Reliability: High (for user experience) Complexity: Low False Positive Rate: N/A (no binary decision)
Method:
- Show "connection quality" based on packet recency
- Green: Packet received < 30 seconds ago
- Yellow: Packet received 30-90 seconds ago
- Gray: Packet received > 90 seconds ago
- Don't make binary connected/disconnected decision
Pros:
- Honest about connection uncertainty
- No false positives/negatives (just gradual indication)
- Helps user understand system state
- Simple to implement
Cons:
- Doesn't provide binary state for automation
- Still shows stale data while disconnected
- Doesn't address the root issue
Rationale:
- Current system is working correctly - Reconnection detection via
rebooted_tagis perfect - Protocol limitations are fundamental - Meshtastic protocol doesn't support reliable disconnect detection over serial
- Risk of false positives - Any timeout-based approach will have edge cases
- Complexity vs. benefit - Adding disconnect detection adds complexity for minimal practical benefit
What we have now:
- ✅ Crash fixed (NULL pointer checks)
- ✅ Reconnection detected immediately (rebooted_tag)
- ✅ State properly reset on reconnection
- ✅ Full handshake completes automatically
- ✅ System is stable and reliable
What disconnect detection would add:
- Knowing GCM is disconnected ~60-120 seconds sooner
- Clearing stale node ID slightly earlier
- More complex code with potential for bugs
If you want to provide user feedback about connection quality:
- Add a "last packet" timestamp to UI
- Show connection quality indicator
- Don't make binary connected/disconnected determination
- Simple, honest, no false positives
Example UI additions:
GCM Node ID: !89a857a9
Last Seen: 5 seconds ago [●] (green)
Last Seen: 45 seconds ago [●] (yellow)
Last Seen: 2 minutes ago [●] (gray)
Bottom Line: The current system with reconnection-only detection is the correct design given the Meshtastic protocol's capabilities over serial UART.
Why previous attempt likely failed:
- Attempted to solve a problem that the protocol doesn't provide tools for
- Timeout-based detection is inherently unreliable with non-deterministic packet timing
- False positives likely occurred, making the feature unreliable
Why not try again:
- Fundamental protocol limitations haven't changed
- Risk of introducing bugs and complexity
- Marginal benefit (knowing disconnect 1-2 minutes sooner)
- Current reconnection detection is perfect
When to reconsider:
- If Meshtastic protocol adds heartbeat acknowledgment
- If switching from serial to WiFi/BLE (which have disconnect signaling)
- If user reports specific issues with current behavior
- If we can modify GCM firmware to send periodic keepalives
Interesting observation: ESP-NOW does have disconnect detection because:
- GCI sends heartbeat every 10 seconds
- GCD monitors heartbeat arrival
- Reliable because it's point-to-point
From espnow_handler.cpp:368:
case ESPNOW_MSG_HEARTBEAT: {
// GCI actively sends heartbeats
// We can reliably detect if they stop
}This works because:
- Known peer (one GCI)
- Controlled timing (10 second interval)
- Direct connection (not mesh-routed)
- Protocol designed for it
Contrast with Meshtastic:
- Unknown number of mesh nodes
- Unpredictable timing
- Mesh-routed (multi-hop)
- Protocol not designed for client disconnect detection over serial
Recommendation: Keep the current system. It's working correctly given the protocol's design.