Repository navigation
BRIDGE_STATUS_FEATURE
The bridge status feature enables mesh nodes to monitor bridge Internet connectivity in real-time and respond intelligently to network conditions. This is critical for production deployments where data delivery reliability is essential.
In a typical mesh network with a bridge to the Internet, regular mesh nodes have no visibility into whether the bridge is actually connected to the Internet. This creates several problems:
- Data Loss: Nodes send data that fails silently when Internet is down
- No Failover: Nodes can't switch to backup bridges when primary fails
- Poor User Experience: No feedback about connectivity state
- Wasted Resources: Attempting to send data that can't be delivered
Bridge nodes now broadcast their connectivity status every 30 seconds (configurable). Regular nodes receive these broadcasts and can:
- Queue critical messages during outages
- Implement failover to backup bridges
- Provide user feedback about connectivity
- Make intelligent routing decisions
Bridge nodes automatically broadcast their status:
// Bridge nodes automatically broadcast every 30 seconds
// No code changes needed if using initAsBridge()
mesh.initAsBridge(MESH_SSID, MESH_PASSWORD,
ROUTER_SSID, ROUTER_PASSWORD,
&userScheduler, MESH_PORT);Status includes:
- Internet connectivity (
true/false) - Router signal strength (RSSI in dBm)
- Router WiFi channel
- Bridge uptime
- Gateway IP address
- Timestamp
Regular nodes can register a callback that fires when bridge status changes:
mesh.onBridgeStatusChanged([](uint32_t bridgeNodeId, bool hasInternet) {
if (hasInternet) {
Serial.println("✓ Internet available - sending queued data");
flushQueuedMessages();
} else {
Serial.println("⚠ Internet offline - queueing messages");
enableOfflineMode();
}
});When the callback fires:
- On first bridge status received
- When Internet connectivity changes (
true↔false) - When a new bridge appears or disappears (after 60s timeout)
Query bridge status programmatically:
// Check if any bridge has Internet
if (mesh.hasInternetConnection()) {
sendCriticalData();
}
// Get primary (best) bridge
auto primaryBridge = mesh.getPrimaryBridge();
if (primaryBridge) {
Serial.printf("Primary bridge: %u (RSSI: %d dBm)\n",
primaryBridge->nodeId,
primaryBridge->routerRSSI);
}
// Get all known bridges
auto bridges = mesh.getBridges();
for (const auto& bridge : bridges) {
Serial.printf("Bridge %u: %s (RSSI: %d dBm)\n",
bridge.nodeId,
bridge.internetConnected ? "Online" : "Offline",
bridge.routerRSSI);
}
// Check if this node is a bridge
if (mesh.isBridge()) {
Serial.println("This node is acting as a bridge");
}Customize the behavior:
// Change broadcast interval (default: 30000ms = 30 seconds)
mesh.setBridgeStatusInterval(60000); // 60 seconds
// Change bridge timeout (default: 60000ms = 60 seconds)
mesh.setBridgeTimeout(90000); // 90 seconds
// Disable broadcasting (bridge nodes only)
mesh.enableBridgeStatusBroadcast(false);The bridge status is transmitted as a broadcast package with type 610:
{
"type": 610,
"from": 123456789,
"routing": 2,
"timestamp": 12345678,
"internetConnected": true,
"routerRSSI": -45,
"routerChannel": 6,
"uptime": 3600000,
"gatewayIP": "192.168.1.1",
"message_type": 610
}| Field | Type | Description |
|---|---|---|
type |
uint16 | Package type (610 = BRIDGE_STATUS) |
from |
uint32 | Bridge node ID |
routing |
uint8 | Routing type (2 = BROADCAST) |
timestamp |
uint32 | Mesh network time when status was collected |
internetConnected |
bool | Is bridge connected to Internet? |
routerRSSI |
int8 | Router WiFi signal strength in dBm (-127 to 0) |
routerChannel |
uint8 | Router WiFi channel (1-13) |
uptime |
uint32 | Bridge uptime in milliseconds |
gatewayIP |
string | Router gateway IP address |
message_type |
uint16 | MQTT schema message type (610) |
The BridgeInfo class tracks the status of each bridge:
class BridgeInfo {
public:
uint32_t nodeId; // Bridge node ID
bool internetConnected; // Internet connectivity status
int8_t routerRSSI; // Router signal strength
uint8_t routerChannel; // Router WiFi channel
uint32_t lastSeen; // When last status was received (millis)
uint32_t uptime; // Bridge uptime
TSTRING gatewayIP; // Router gateway IP
uint32_t timestamp; // Timestamp from bridge
bool isHealthy(uint32_t timeoutMs = 60000) const;
};Health Checking: A bridge is considered healthy if a status update was received within the timeout period (default: 60 seconds).
The mesh automatically selects a "primary" bridge based on:
- Must be healthy (status received within 60 seconds)
-
Must have Internet (
internetConnected == true) - Best signal strength (highest RSSI)
auto primaryBridge = mesh.getPrimaryBridge();
if (primaryBridge) {
// Use this bridge for critical data
sendDataToBridge(primaryBridge->nodeId);
}Queue critical alarms during Internet outages:
std::vector<AlarmMessage> alarmQueue;
bool internetAvailable = false;
mesh.onBridgeStatusChanged([](uint32_t bridgeId, bool hasInternet) {
internetAvailable = hasInternet;
if (hasInternet) {
// Flush queued alarms
for (auto& alarm : alarmQueue) {
sendAlarmToCloud(alarm);
}
alarmQueue.clear();
}
});
void onCriticalAlarm(float oxygenLevel) {
AlarmMessage alarm = {
.type = ALARM_CRITICAL,
.value = oxygenLevel,
.timestamp = mesh.getNodeTime()
};
if (internetAvailable) {
sendAlarmToCloud(alarm);
} else {
alarmQueue.push_back(alarm);
Serial.println("⚠ CRITICAL: Alarm queued (Internet offline)");
}
}Switch to backup when primary fails:
mesh.onBridgeStatusChanged([](uint32_t bridgeId, bool hasInternet) {
if (!hasInternet) {
// Primary bridge lost Internet - try backup
auto bridges = mesh.getBridges();
for (const auto& bridge : bridges) {
if (bridge.nodeId != bridgeId &&
bridge.isHealthy() &&
bridge.internetConnected) {
Serial.printf("Failover to backup bridge %u\n", bridge.nodeId);
// Route critical data through backup
break;
}
}
}
});Batch data uploads when Internet is available:
std::vector<SensorReading> dataBuffer;
Task taskBufferData(5000, TASK_FOREVER, []() {
// Always collect data
dataBuffer.push_back(readSensors());
// Upload when Internet available and buffer is large enough
if (mesh.hasInternetConnection() && dataBuffer.size() >= 10) {
uploadBatch(dataBuffer);
dataBuffer.clear();
}
});Provide visual feedback about connectivity:
mesh.onBridgeStatusChanged([](uint32_t bridgeId, bool hasInternet) {
if (hasInternet) {
digitalWrite(LED_GREEN, HIGH);
digitalWrite(LED_RED, LOW);
display.println("✓ Connected");
} else {
digitalWrite(LED_GREEN, LOW);
digitalWrite(LED_RED, HIGH);
display.println("⚠ Offline");
}
});- Bridge nodes: 1 broadcast every 30 seconds (~200 bytes)
- Regular nodes: Process 1 message every 30 seconds per bridge
- Minimal impact: <1% of typical mesh traffic
- Bridge nodes: ~50 bytes for broadcast task
- Regular nodes: ~30 bytes per tracked bridge
- Example: 3 bridges = 90 bytes overhead
- Broadcasting: <1ms every 30 seconds (negligible)
- Processing: <5ms per status received (negligible)
- Tested with up to 10 bridges
- Recommended maximum: 5 bridges per mesh
- No performance degradation observed
// Standard reliability
mesh.setBridgeStatusInterval(30000); // 30 seconds
mesh.setBridgeTimeout(60000); // 60 seconds
// High reliability (critical systems)
mesh.setBridgeStatusInterval(15000); // 15 seconds
mesh.setBridgeTimeout(45000); // 45 seconds (3x interval)
// Low power (battery nodes)
mesh.setBridgeStatusInterval(60000); // 60 seconds
mesh.setBridgeTimeout(180000); // 180 seconds- Timeout should be 2-3x interval to avoid false timeouts
- Shorter intervals = faster failover but more network traffic
- Longer intervals = less traffic but slower failover detection
- Consider your application's criticality when choosing intervals
Symptoms: onBridgeStatusChanged() never fires
Possible Causes:
- Bridge not initialized with
initAsBridge() - Broadcasting disabled: Check
enableBridgeStatusBroadcast(true) - Network connectivity issues
- Callback not registered before
init()
Solutions:
// Ensure bridge is properly initialized
mesh.initAsBridge(...); // Not just mesh.init()
// Verify broadcasting is enabled
mesh.enableBridgeStatusBroadcast(true);
// Register callback AFTER mesh.init()
mesh.init(...);
mesh.onBridgeStatusChanged(&callback);
// Check debug logs
mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION | GENERAL);Symptoms: Callback fires repeatedly with same status
Possible Causes:
- Bridge Internet connection unstable
- Multiple bridges changing status
- Network congestion causing message delays
Solutions:
// Add debouncing
static uint32_t lastCallback = 0;
const uint32_t DEBOUNCE_MS = 5000;
mesh.onBridgeStatusChanged([](uint32_t bridgeId, bool hasInternet) {
if (millis() - lastCallback < DEBOUNCE_MS) return;
lastCallback = millis();
// Your code here
});
// Or track state manually
static bool lastState = false;
mesh.onBridgeStatusChanged([](uint32_t bridgeId, bool hasInternet) {
if (hasInternet != lastState) {
lastState = hasInternet;
// Only process actual changes
}
});Symptoms: bridge.isHealthy() returns false
Possible Causes:
- No status received in 60 seconds
- Bridge node crashed or rebooted
- Network partition
- Bridge stopped broadcasting
Solutions:
// Increase timeout for less reliable networks
mesh.setBridgeTimeout(120000); // 2 minutes
// Check bridge health before using
auto primary = mesh.getPrimaryBridge();
if (primary && primary->isHealthy()) {
// Safe to use
}
// Monitor bridge last seen times
auto bridges = mesh.getBridges();
for (const auto& bridge : bridges) {
uint32_t ageMs = millis() - bridge.lastSeen;
Serial.printf("Bridge %u: last seen %u ms ago\n",
bridge.nodeId, ageMs);
}See the following examples for complete implementations:
-
examples/bridge/bridge.ino
- Bridge node with automatic status broadcasting
- Minimal configuration required
-
examples/alteriomSensorNode/bridge_aware_sensor_node.ino
- Regular node with bridge status callback
- Message queueing during outages
- Critical alarm handling
- Fish farm monitoring simulation
void onBridgeStatusChanged(bridgeStatusChangedCallback_t callback);Parameters:
-
callback: Function with signaturevoid(uint32_t bridgeNodeId, bool internetAvailable)
Example:
mesh.onBridgeStatusChanged([](uint32_t bridgeId, bool hasInternet) {
Serial.printf("Bridge %u: Internet %s\n",
bridgeId, hasInternet ? "up" : "down");
});bool hasInternetConnection();Returns: true if at least one healthy bridge has Internet connectivity
Example:
if (mesh.hasInternetConnection()) {
sendData();
}std::vector<BridgeInfo> getBridges();Returns: Vector of all tracked bridges (healthy and unhealthy)
Example:
auto bridges = mesh.getBridges();
Serial.printf("Known bridges: %d\n", bridges.size());
for (const auto& bridge : bridges) {
Serial.printf(" - %u: %s\n", bridge.nodeId,
bridge.isHealthy() ? "Healthy" : "Timeout");
}BridgeInfo* getPrimaryBridge();Returns: Pointer to primary bridge, or nullptr if none suitable
Criteria: Healthy + Internet connected + Best RSSI
Example:
auto primary = mesh.getPrimaryBridge();
if (primary) {
Serial.printf("Primary: %u (RSSI: %d dBm)\n",
primary->nodeId, primary->routerRSSI);
} else {
Serial.println("No suitable bridge");
}bool isBridge();Returns: true if this node is configured as a bridge (root node)
Example:
if (mesh.isBridge()) {
// Bridge-specific logic
startStatusBroadcast();
}void setBridgeStatusInterval(uint32_t intervalMs);Parameters:
-
intervalMs: Broadcast interval in milliseconds (default: 30000)
Example:
mesh.setBridgeStatusInterval(60000); // Broadcast every 60 secondsvoid setBridgeTimeout(uint32_t timeoutMs);Parameters:
-
timeoutMs: Timeout threshold in milliseconds (default: 60000)
Recommendation: Set to 2-3x the broadcast interval
Example:
mesh.setBridgeTimeout(90000); // 90 second timeoutvoid enableBridgeStatusBroadcast(bool enabled);Parameters:
-
enabled:trueto enable broadcasting (default),falseto disable
Note: Only affects bridge nodes. Regular nodes always process received status.
Example:
// Disable broadcasting temporarily
mesh.enableBridgeStatusBroadcast(false);
// Re-enable later
mesh.enableBridgeStatusBroadcast(true);- ✅
initAsBridge()- Automatic channel detection - ✅
onReceive()- Normal message handling - ✅
onNewConnection()/onDroppedConnection()- Connection callbacks - ✅ OTA updates - Bridge status continues during updates
- ✅ Multiple bridges - Tracks all bridges independently
- ❌ Non-bridge nodes cannot broadcast bridge status (ignored)
- ❌ Mesh networks without bridges (no status to track)
This feature is fully backward compatible:
- Old bridge nodes: Work normally, just don't broadcast status
- Old regular nodes: Ignore type 610 packages
- Mixed networks: Old and new nodes coexist without issues
- No breaking changes: All existing APIs remain unchanged
Potential improvements for future versions:
- Bridge Load Balancing: Route based on bridge load, not just RSSI
- Historical Tracking: Track uptime and reliability over time
- Predictive Failover: Predict bridge failures before they occur
- Mesh-wide Health Score: Aggregate metric for entire mesh health
- Bridge Discovery: Active scanning for backup bridges
To contribute improvements:
- Follow the existing code style
- Add tests for new functionality
- Update documentation
- Test with multiple bridges
- Consider memory and performance impact
Same as painlessMesh library - GPL 3.0
- Feature requested by: @woodlist (fish farm monitoring use case)
- Implementation: GitHub Copilot + painlessMesh team
- Testing: Alteriom community
- BRIDGE_TO_INTERNET.md - Bridge setup guide
- Issue #63 - Feature request
- Issue #64 - Bridge failover (enabled by this feature)
- Issue #66 - Message queueing (enabled by this feature)