Repository navigation
Multi_bridge_setup
Complete guide for deploying multiple simultaneous bridge nodes in painlessMesh
- Overview
- Architecture
- Use Cases
- Requirements
- Configuration
- API Reference
- Examples
- Testing
- Troubleshooting
- Advanced Topics
Multi-bridge coordination enables multiple bridge nodes to operate simultaneously in a painlessMesh network, providing:
- High Availability: Zero downtime during bridge failures
- Load Balancing: Distribute traffic across multiple Internet connections
- Geographic Distribution: Bridges in different physical locations
- Redundancy: Multiple paths to the Internet
- Traffic Shaping: Different message types to different bridges (planned)
| Feature | Status | Description |
|---|---|---|
| Multiple Simultaneous Bridges | ✅ Implemented | 2-5 bridges can operate concurrently |
| Bridge Priority System | ✅ Implemented | Priorities 1-10 for deterministic selection |
| Selection Strategies | ✅ Implemented | Priority-based, Round-robin, Best-signal |
| Automatic Coordination | ✅ Implemented | Bridges discover and coordinate automatically |
| Load Reporting | ✅ Implemented | Bridges report current load percentage |
| Failover Integration | ✅ Implemented | Works with bridge failover (Issue #64) |
Internet
|
+--------------+---------------+
| |
Router A Router B
| |
[Bridge 1] [Bridge 2]
Priority: 10 Priority: 5
Role: Primary Role: Secondary
| |
+-------+----------+-----------+
| |
[Node A] [Node B]
Regular Regular
Nodes Nodes
Bridge Nodes:
- Connect to both mesh (AP) and router (STA)
- Broadcast coordination messages (Type 613) every 30s
- Report priority, role, load, and peer bridges
- Act as Internet gateways for mesh
Regular Nodes:
- Connect to mesh only (STA mode)
- Receive bridge coordination messages
- Track available bridges and their priorities
- Select best bridge using configured strategy
Message Type: 613 (BRIDGE_COORDINATION)
Message Structure:
{
"type": 613,
"from": 123456,
"routing": 2,
"priority": 10,
"role": "primary",
"load": 45,
"timestamp": 1234567890,
"peerBridges": [789012, 345678]
}Broadcast Interval: 30 seconds
Timeout: 60 seconds (2 missed heartbeats)
Scenario: Critical IoT system that cannot tolerate Internet outages
Setup:
- Primary Bridge (Priority 10): Main Internet connection
- Secondary Bridge (Priority 5): Backup Internet connection
- Strategy: PRIORITY_BASED (default)
Behavior:
- All traffic uses primary bridge normally
- If primary fails, automatic switch to secondary
- Zero downtime, no manual intervention
Example:
// Primary Bridge
mesh.initAsBridge(MESH_SSID, MESH_PASSWORD,
"PrimaryRouter", "pass1",
&scheduler, 5555, 10);
// Secondary Bridge
mesh.initAsBridge(MESH_SSID, MESH_PASSWORD,
"BackupRouter", "pass2",
&scheduler, 5555, 5);Scenario: High-traffic mesh network with multiple Internet connections
Setup:
- Bridge 1 (Priority 7): Internet connection A
- Bridge 2 (Priority 7): Internet connection B
- Strategy: ROUND_ROBIN
Behavior:
- Messages distributed evenly across both bridges
- 50/50 traffic split
- Maximizes available bandwidth
Example:
// On regular nodes
mesh.setBridgeSelectionStrategy(painlessMesh::ROUND_ROBIN);
// Both bridges equal priority
mesh.initAsBridge(MESH_SSID, MESH_PASSWORD,
"RouterA", "pass1",
&scheduler, 5555, 7);Scenario: Large mesh network spanning multiple buildings
Setup:
- Building A: Bridge 1 (Priority 10) → Local Internet A
- Building B: Bridge 2 (Priority 10) → Local Internet B
- Strategy: BEST_SIGNAL
Behavior:
- Nodes use closest bridge (best signal strength)
- Automatic selection based on location
- Optimizes latency and reliability
Example:
// On regular nodes
mesh.setBridgeSelectionStrategy(painlessMesh::BEST_SIGNAL);
// Both bridges same priority, selected by signal
mesh.initAsBridge(MESH_SSID, MESH_PASSWORD,
"BuildingA_Router", "pass1",
&scheduler, 5555, 10);Bridge Nodes:
- ESP32 or ESP8266 with WiFi
- 2+ MB flash recommended
- Stable power supply (no battery)
Regular Nodes:
- ESP32 or ESP8266 with WiFi
- 1+ MB flash
- Battery power acceptable
Network:
- 1+ WiFi routers with Internet connection
- All devices must support 2.4 GHz WiFi
- Same WiFi channel recommended for all
Library Version:
- painlessMesh v1.8.1 or later
- ArduinoJson v6.x or v7.x
- TaskScheduler v3.x
Platform:
- Arduino IDE 1.8+ or PlatformIO
- ESP32 Arduino Core 2.x+ or ESP8266 Core 3.x+
Dependencies:
- Bridge Status Broadcast (Issue #63) ✅
- Bridge Failover (Issue #64) ✅
Multi-bridge mode must be enabled on all bridge nodes:
mesh.enableMultiBridge(true);Note: Regular nodes automatically detect multi-bridge mode and don't need explicit configuration.
When initializing a bridge, specify priority (1-10):
mesh.initAsBridge(MESH_SSID, MESH_PASSWORD,
ROUTER_SSID, ROUTER_PASSWORD,
&scheduler, MESH_PORT,
priority); // 1-10Priority Guidelines:
| Priority | Role | When to Use |
|---|---|---|
| 10 | Primary | Main bridge, best Internet connection |
| 8-9 | Primary-High | Co-primary for load sharing |
| 5-7 | Secondary | Backup bridge, hot standby |
| 2-4 | Tertiary | Last resort backup |
| 1 | Standby | Only if all others fail |
Role Assignment:
- Priority ≥ 8 → "primary"
- Priority ≥ 5 → "secondary"
- Priority < 5 → "standby"
Choose how regular nodes select bridges:
// Priority-Based (default) - Always use highest priority
mesh.setBridgeSelectionStrategy(painlessMesh::PRIORITY_BASED);
// Round-Robin - Distribute load evenly
mesh.setBridgeSelectionStrategy(painlessMesh::ROUND_ROBIN);
// Best Signal - Use bridge with best RSSI
mesh.setBridgeSelectionStrategy(painlessMesh::BEST_SIGNAL);Strategy Comparison:
| Strategy | Pros | Cons | Best For |
|---|---|---|---|
| PRIORITY_BASED | Predictable, deterministic | All traffic on one bridge | Production systems |
| ROUND_ROBIN | Even load distribution | May not respect priority | High-traffic systems |
| BEST_SIGNAL | Optimizes latency | Requires WiFi scanning | Large/mobile networks |
Limit number of tracked bridges:
mesh.setMaxBridges(3); // Default: 2, Max: 5Recommendations:
- Small networks (< 20 nodes): 2 bridges
- Medium networks (20-50 nodes): 3 bridges
- Large networks (50+ nodes): 4-5 bridges
void enableMultiBridge(bool enabled)Enable or disable multi-bridge coordination mode.
Parameters:
-
enabled- true to enable, false to disable
Example:
mesh.enableMultiBridge(true);Notes:
- Must be called on bridge nodes before
initAsBridge() - Regular nodes auto-detect multi-bridge mode
- Default: disabled (single-bridge mode)
void setBridgeSelectionStrategy(BridgeSelectionStrategy strategy)Set bridge selection algorithm for regular nodes.
Parameters:
-
strategy- One of:-
painlessMesh::PRIORITY_BASED- Use highest priority (default) -
painlessMesh::ROUND_ROBIN- Distribute evenly -
painlessMesh::BEST_SIGNAL- Use best RSSI
-
Example:
mesh.setBridgeSelectionStrategy(painlessMesh::ROUND_ROBIN);void setMaxBridges(uint8_t maxBridges)Set maximum number of concurrent bridges to track.
Parameters:
-
maxBridges- Maximum bridges (1-5)
Example:
mesh.setMaxBridges(3);Notes:
- Values < 1 clamped to 1
- Values > 5 clamped to 5
- Default: 2
void initAsBridge(TSTRING meshSSID, TSTRING meshPassword,
TSTRING routerSSID, TSTRING routerPassword,
Scheduler *baseScheduler, uint16_t port,
uint8_t priority)Initialize node as bridge with specified priority.
Parameters:
-
meshSSID- Mesh network name -
meshPassword- Mesh password -
routerSSID- Router SSID to connect to -
routerPassword- Router password -
baseScheduler- Task scheduler instance -
port- TCP port (default: 5555) -
priority- Bridge priority (1-10)
Example:
mesh.initAsBridge("MyMesh", "meshpass",
"MyRouter", "routerpass",
&scheduler, 5555, 10);Notes:
- Priority 10 = highest (primary bridge)
- Priority 1 = lowest (standby bridge)
- Auto-detects router channel
- Sets node as root automatically
std::vector<uint32_t> getActiveBridges()Get list of all currently active bridge node IDs.
Returns:
- Vector of bridge node IDs with Internet connection
Example:
auto bridges = mesh.getActiveBridges();
Serial.printf("Active bridges: %d\n", bridges.size());
for (auto bridgeId : bridges) {
Serial.printf(" - Bridge: %u\n", bridgeId);
}Notes:
- Only includes bridges with Internet connection
- Only includes healthy bridges (seen within 60s)
- Updates automatically from coordination messages
uint32_t getRecommendedBridge()Get best bridge node ID based on current strategy.
Returns:
- Bridge node ID, or 0 if no bridge available
Example:
uint32_t bridgeId = mesh.getRecommendedBridge();
if (bridgeId != 0) {
mesh.sendSingle(bridgeId, "Hello!");
} else {
Serial.println("No bridge available");
}Notes:
- Respects current selection strategy
- Returns 0 if no bridges available
- Thread-safe, can call frequently
void selectBridge(uint32_t bridgeNodeId)Manually select specific bridge for next transmission.
Parameters:
-
bridgeNodeId- Node ID of bridge to use
Example:
// Override strategy for critical message
mesh.selectBridge(primaryBridgeId);
mesh.sendSingle(primaryBridgeId, criticalData);Notes:
- One-time override of selection strategy
- Resets after one message
- Use sparingly for critical messages only
bool isMultiBridgeEnabled() constCheck if multi-bridge mode is enabled.
Returns:
- true if multi-bridge coordination active
Example:
if (mesh.isMultiBridgeEnabled()) {
Serial.println("Multi-bridge mode active");
}See complete examples in examples/multi_bridge/:
- primary_bridge.ino - Primary bridge configuration
- secondary_bridge.ino - Secondary bridge configuration
- regular_node.ino - Regular node with bridge awareness
#include "painlessMesh.h"
#define MESH_PREFIX "ProductionMesh"
#define MESH_PASSWORD "meshpass"
#define MESH_PORT 5555
#define ROUTER_SSID "PrimaryRouter"
#define ROUTER_PASSWORD "routerpass"
Scheduler userScheduler;
painlessMesh mesh;
// Monitor bridge status
Task taskBridgeMonitor(10000, TASK_FOREVER, [](){
Serial.println("\n=== Primary Bridge Status ===");
Serial.printf("Node ID: %u\n", mesh.getNodeId());
Serial.printf("Connected Nodes: %d\n", mesh.getNodeList().size());
auto activeBridges = mesh.getActiveBridges();
Serial.printf("Active Bridges: %d\n", activeBridges.size());
for (auto bridgeId : activeBridges) {
Serial.printf(" - Bridge: %u%s\n", bridgeId,
(bridgeId == mesh.getNodeId()) ? " (ME)" : "");
}
Serial.printf("Recommended Bridge: %u\n", mesh.getRecommendedBridge());
Serial.println("============================\n");
});
void setup() {
Serial.begin(115200);
delay(2000);
Serial.println("\n=== PRIMARY BRIDGE INITIALIZATION ===\n");
// Configure debugging
mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION);
// Enable multi-bridge coordination
mesh.enableMultiBridge(true);
// Set selection strategy (PRIORITY_BASED is default)
mesh.setBridgeSelectionStrategy(painlessMesh::PRIORITY_BASED);
// Initialize as primary bridge (priority 10)
mesh.initAsBridge(MESH_PREFIX, MESH_PASSWORD,
ROUTER_SSID, ROUTER_PASSWORD,
&userScheduler, MESH_PORT, 10);
// Setup callbacks
mesh.onReceive([](uint32_t from, String& msg) {
Serial.printf("Received from %u: %s\n", from, msg.c_str());
});
mesh.onNewConnection([](uint32_t nodeId) {
Serial.printf("New connection: %u\n", nodeId);
});
// Add monitoring task
userScheduler.addTask(taskBridgeMonitor);
taskBridgeMonitor.enable();
Serial.println("\n=== PRIMARY BRIDGE READY ===");
Serial.println("Priority: 10 (Primary)");
Serial.println("This bridge will handle all mesh traffic");
Serial.println("Secondary bridge will activate if this fails\n");
}
void loop() {
mesh.update();
}#include "painlessMesh.h"
#define MESH_PREFIX "ProductionMesh"
#define MESH_PASSWORD "meshpass"
#define MESH_PORT 5555
Scheduler userScheduler;
painlessMesh mesh;
// Send test messages periodically
Task taskSendMessage(5000, TASK_FOREVER, [](){
uint32_t bridgeId = mesh.getRecommendedBridge();
if (bridgeId != 0) {
String msg = "Hello from node " + String(mesh.getNodeId());
Serial.printf("Sending to bridge %u: %s\n", bridgeId, msg.c_str());
mesh.sendSingle(bridgeId, msg);
} else {
Serial.println("No bridge available - message queued");
}
});
// Monitor network status
Task taskNetworkStatus(15000, TASK_FOREVER, [](){
Serial.println("\n=== Network Status ===");
Serial.printf("Node ID: %u\n", mesh.getNodeId());
Serial.printf("Connected Nodes: %d\n", mesh.getNodeList().size());
auto activeBridges = mesh.getActiveBridges();
Serial.printf("Active Bridges: %d\n", activeBridges.size());
for (auto bridgeId : activeBridges) {
Serial.printf(" - Bridge: %u\n", bridgeId);
}
Serial.printf("Internet Available: %s\n",
mesh.hasInternetConnection() ? "YES" : "NO");
Serial.printf("Recommended Bridge: %u\n",
mesh.getRecommendedBridge());
Serial.println("=====================\n");
});
void setup() {
Serial.begin(115200);
delay(2000);
Serial.println("\n=== REGULAR NODE INITIALIZATION ===\n");
// Configure debugging
mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION);
// Initialize as regular node
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
// Setup callbacks
mesh.onReceive([](uint32_t from, String& msg) {
Serial.printf("Received from %u: %s\n", from, msg.c_str());
});
mesh.onBridgeStatusChanged([](uint32_t bridgeId, bool hasInternet) {
Serial.printf("Bridge %u: Internet %s\n",
bridgeId, hasInternet ? "ONLINE" : "OFFLINE");
});
mesh.onNewConnection([](uint32_t nodeId) {
Serial.printf("New connection: %u\n", nodeId);
});
// Add tasks
userScheduler.addTask(taskSendMessage);
userScheduler.addTask(taskNetworkStatus);
taskSendMessage.enable();
taskNetworkStatus.enable();
Serial.println("\n=== REGULAR NODE READY ===");
Serial.println("Automatically discovers bridges");
Serial.println("Uses configured selection strategy\n");
}
void loop() {
mesh.update();
}Objective: Verify two bridges coordinate and nodes prefer primary
Steps:
- Flash primary bridge (priority 10)
- Flash secondary bridge (priority 5)
- Flash 2-3 regular nodes
- Power on all devices
- Monitor serial output
Expected Results:
- ✅ Both bridges connect to routers
- ✅ Both bridges see each other in coordination messages
- ✅ Regular nodes discover both bridges
- ✅ Regular nodes prefer primary bridge (priority 10)
- ✅
getActiveBridges()returns 2 bridge IDs - ✅
getRecommendedBridge()returns primary bridge ID
Objective: Verify automatic failover to secondary bridge
Steps:
- Setup dual bridges and regular nodes (as above)
- Wait for stable operation (2+ minutes)
- Disconnect primary bridge power
- Monitor regular nodes for failover
Expected Results:
- ✅ Regular nodes detect primary bridge offline within 60s
- ✅
getActiveBridges()returns 1 bridge ID (secondary only) - ✅
getRecommendedBridge()returns secondary bridge ID - ✅ Messages continue flowing through secondary bridge
- ✅ No messages lost during transition
Objective: Verify even distribution across bridges
Steps:
- Setup dual bridges with equal priority (both 7)
- Flash regular node with ROUND_ROBIN strategy
- Send 10 messages from regular node
- Monitor which bridge receives each message
Expected Results:
- ✅ First message → Bridge 1
- ✅ Second message → Bridge 2
- ✅ Third message → Bridge 1
- ✅ Distribution is approximately 50/50
- ✅ Both bridges handle traffic
Objective: Verify selection based on WiFi signal strength
Steps:
- Setup dual bridges at different physical locations
- Flash regular node with BEST_SIGNAL strategy
- Move regular node between locations
- Monitor bridge selection
Expected Results:
- ✅ Node near Bridge 1 selects Bridge 1
- ✅ Node near Bridge 2 selects Bridge 2
- ✅ Selection changes as node moves
- ✅ Always uses bridge with best RSSI
Run comprehensive unit tests:
cd painlessMesh
cmake -G Ninja .
ninja
./bin/catch_pluginExpected Output:
===============================================================================
All tests passed (67 assertions in 4 test cases)
Test Coverage:
- ✅ BridgeCoordinationPackage serialization
- ✅ JSON round-trip integrity
- ✅ Empty peer list handling
- ✅ Maximum value edge cases
- ✅ Field preservation
Symptoms:
Active Bridges: 0
Internet Available: NO
Recommended Bridge: 0
Possible Causes:
- Bridges not powered on
- Router credentials incorrect
- Bridges failed to connect to router
- Mesh SSID/password mismatch
Solutions:
- Verify bridges are powered and running
- Check router credentials in bridge code
- Verify router SSID is correct
- Check serial output of bridges for connection status
- Ensure all devices use same MESH_PREFIX and MESH_PASSWORD
Diagnostic Commands:
// On bridge node
Serial.printf("Router connected: %s\n",
WiFi.status() == WL_CONNECTED ? "YES" : "NO");
Serial.printf("Router IP: %s\n",
WiFi.localIP().toString().c_str());
Serial.printf("Router RSSI: %d dBm\n", WiFi.RSSI());Symptoms:
Active Bridges: 1
Expected: 2
Coordination messages not appearing in logs
Possible Causes:
-
enableMultiBridge(true)not called on bridges - Bridges not connected to mesh
- Mesh network partitioned
- Coordination broadcast failing
Solutions:
- Verify
enableMultiBridge(true)called beforeinitAsBridge() - Check mesh connectivity between bridges
- Enable CONNECTION debug messages
- Verify bridges see each other in mesh node list
Diagnostic Commands:
// On bridge node
Serial.printf("Multi-bridge enabled: %s\n",
mesh.isMultiBridgeEnabled() ? "YES" : "NO");
auto nodes = mesh.getNodeList(false);
Serial.printf("Mesh nodes: %d\n", nodes.size());
for (auto node : nodes) {
Serial.printf(" - Node: %u\n", node);
}Symptoms:
Expected: Primary bridge (priority 10)
Actual: Secondary bridge (priority 5)
Possible Causes:
- Priority not set correctly
- Primary bridge offline
- Wrong selection strategy
- Primary bridge no Internet connection
Solutions:
- Verify priority values in
initAsBridge()calls - Check primary bridge is online and healthy
- Verify strategy is PRIORITY_BASED
- Check primary bridge has Internet connection
Diagnostic Commands:
// On regular node
auto bridges = mesh.getBridges();
for (auto& bridge : bridges) {
Serial.printf("Bridge %u: RSSI=%d, Internet=%s, Healthy=%s\n",
bridge.nodeId, bridge.routerRSSI,
bridge.internetConnected ? "YES" : "NO",
bridge.isHealthy() ? "YES" : "NO");
}Symptoms:
Primary bridge offline
Regular nodes not switching to secondary
Messages timing out
Possible Causes:
- Secondary bridge not available
- Bridge timeout not expired (60s)
- Secondary bridge no Internet connection
- Bridge status broadcasts disabled
Solutions:
- Verify secondary bridge is online and connected
- Wait full 60s timeout period
- Check secondary bridge Internet connection
- Verify
enableBridgeStatusBroadcast(true)called
Diagnostic Commands:
// Monitor bridge health
auto primary = mesh.getPrimaryBridge();
if (primary != nullptr) {
Serial.printf("Primary: %u (seen %u ms ago)\n",
primary->nodeId, millis() - primary->lastSeen);
} else {
Serial.println("No primary bridge available");
}Symptoms:
Free heap: < 20KB
Crashes or resets
Memory allocation failures
Possible Causes:
- Too many tracked bridges
- Large peer bridge lists
- Memory leak in application code
Solutions:
- Reduce
setMaxBridges()to 2-3 - Limit number of concurrent bridges
- Profile application memory usage
- Check for memory leaks in custom code
Diagnostic Commands:
Serial.printf("Free heap: %u bytes\n", ESP.getFreeHeap());
Serial.printf("Max bridges: %u\n", maxConcurrentBridges);
Serial.printf("Known bridges: %u\n", knownBridges.size());For very large or critical deployments:
// Bridge 1: Primary (highest priority)
mesh.initAsBridge(ssid, pass, router1, pass1, &sched, port, 10);
// Bridge 2: Secondary (medium priority)
mesh.initAsBridge(ssid, pass, router2, pass2, &sched, port, 7);
// Bridge 3: Tertiary (backup)
mesh.initAsBridge(ssid, pass, router3, pass3, &sched, port, 3);
// Set max tracked bridges
mesh.setMaxBridges(3);Considerations:
- Memory usage increases with more bridges
- Coordination overhead increases
- Recommended maximum: 5 bridges
- Most deployments work well with 2-3 bridges
For mesh networks spanning multiple buildings:
// Building A Bridge (equal priority)
mesh.initAsBridge(ssid, pass, "BuildingA_Router", pass1, &sched, port, 10);
// Building B Bridge (equal priority)
mesh.initAsBridge(ssid, pass, "BuildingB_Router", pass2, &sched, port, 10);
// Regular nodes use BEST_SIGNAL strategy
mesh.setBridgeSelectionStrategy(painlessMesh::BEST_SIGNAL);Benefits:
- Nodes automatically use closest bridge
- Optimizes latency and reliability
- Supports mobile nodes
Future enhancement for routing different traffic types:
// Coming in v1.8.2+
mesh.setBridgeSelectionStrategy(painlessMesh::TRAFFIC_TYPE);
mesh.routeTrafficType(ALARM_MESSAGE, primaryBridge);
mesh.routeTrafficType(SENSOR_DATA, secondaryBridge);Future enhancement for unequal load distribution:
// Coming in v1.8.2+
mesh.setBridgeWeight(bridge1, 70); // 70% of traffic
mesh.setBridgeWeight(bridge2, 30); // 30% of trafficMemory Optimization:
// Reduce max bridges if memory limited
mesh.setMaxBridges(2);
// Increase coordination interval to reduce overhead
// (requires code modification - not configurable via API)Network Optimization:
// Adjust bridge timeout for faster failover
mesh.setBridgeTimeout(30000); // 30 seconds instead of 60
// Adjust bridge status broadcast interval
mesh.setBridgeStatusInterval(15000); // 15 seconds instead of 30- Bridge Status Feature - Bridge status broadcasts
- Bridge Failover - Automatic failover system
- Bridge Architecture - Overall bridge design
- Examples - Complete working examples
- API Reference - Full API documentation
Resources:
- GitHub Issues: https://github.com/Alteriom/painlessMesh/issues
- Discussions: https://github.com/Alteriom/painlessMesh/discussions
- Examples:
examples/multi_bridge/ - Wiki: https://alteriom.github.io/painlessMesh/
Reporting Issues:
- Check troubleshooting section
- Enable debug messages:
mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION); - Capture serial output from all nodes
- Report with code snippets and logs
- ✅ Initial multi-bridge coordination implementation
- ✅ BridgeCoordinationPackage (Type 613)
- ✅ Three bridge selection strategies
- ✅ Bridge priority system (1-10)
- ✅ Complete API and examples
Document Version: 1.0
Last Updated: 2025-11-11
Status: Complete and Production-Ready