Repository navigation
FUTURE_PROPOSALS
Document Type: Feature Proposals & Roadmap
Status: 📋 Proposed - Awaiting Review & Implementation
Target: Phase 3 and beyond
This document outlines proposed enhancements for painlessMesh beyond the completed Phase 1 (v1.6.x) and Phase 2 (v1.7.0) implementations. These features represent the next evolution of OTA distribution and status monitoring capabilities.
Completed Features: See OTA_STATUS_ENHANCEMENTS.md for implemented options
Implementation Details: See IMPLEMENTATION_HISTORY.md for Phase 1-2 technical details
- Phase 3 Overview
- OTA Enhancement Proposals
- Status Monitoring Proposals
- Implementation Priority
- Timeline Estimates
Goal: Advanced features for enterprise and large-scale deployments
Key Themes:
- Production Safety - Zero-downtime updates with automatic rollback
- Massive Scale - Support for 100+ node meshes
- Real-time Monitoring - Sub-second telemetry and proactive alerting
- User Experience - Web-based dashboards and management interfaces
Prerequisites:
- ✅ Phase 1 complete (v1.6.x)
- ✅ Phase 2 complete (v1.7.0)
- ✅ Stable mesh infrastructure
- ✅ Testing framework established
Status: 📋 Proposed
Priority: ⭐⭐⭐⭐⭐ High
Complexity: ⭐⭐⭐⭐ High
Timeline: 3-4 months
Deploy firmware in controlled waves (canary → early adopters → all) with health monitoring between phases and automatic rollback on failures.
1. Phased Rollout Strategy:
- Wave 1 (Canary): 5% of nodes - Test on small subset
- Wave 2 (Early Adopters): 20% of nodes - Expand if Wave 1 succeeds
- Wave 3 (General Availability): 75% of nodes - Majority deployment
- Wave 4 (Laggards): 100% of nodes - Complete rollout
2. Health Monitoring:
- Track node uptime after update
- Monitor memory usage, reboot loops, error rates
- Compare health metrics pre/post update
- Configurable health thresholds
3. Automatic Rollback:
- Trigger rollback if health check fails
- Revert to previous firmware version
- Preserve critical node functionality
- Alert administrators of failures
4. Manual Controls:
- Pause rollout between waves
- Skip waves for emergency deployments
- Manually approve wave progression
- Emergency rollback button
class ProgressiveOTA {
private:
painlessMesh& mesh;
std::vector<float> wavePercentages; // e.g., {0.05, 0.20, 0.75, 1.0}
std::function<bool(uint32_t nodeId)> healthCheck;
uint32_t currentWave = 0;
std::set<uint32_t> updatedNodes;
std::set<uint32_t> failedNodes;
public:
ProgressiveOTA(painlessMesh& mesh) : mesh(mesh) {
wavePercentages = {0.05, 0.20, 0.75, 1.0}; // Default waves
}
void setPhases(std::vector<float> phases) { wavePercentages = phases; }
void setHealthCheck(std::function<bool(uint32_t)> check) { healthCheck = check; }
void begin(TSTRING role, TSTRING hardware, TSTRING md5) {
// Start Wave 1 (canary)
deployWave(0, role, hardware, md5);
}
void deployWave(uint32_t wave, TSTRING role, TSTRING hardware, TSTRING md5) {
auto nodes = mesh.getNodeList();
size_t targetCount = nodes.size() * wavePercentages[wave];
// Select nodes for this wave
std::vector<uint32_t> waveNodes = selectNodesForWave(nodes, targetCount);
// Deploy to selected nodes
for (auto nodeId : waveNodes) {
mesh.sendSingle(nodeId, createOTAAnnounce(role, hardware, md5));
}
// Schedule health check
scheduleHealthCheck(wave, waveNodes);
}
void scheduleHealthCheck(uint32_t wave, std::vector<uint32_t> nodes) {
mesh.addTask(
TASK_MINUTE * 5, // Wait 5 minutes after deployment
TASK_ONCE,
[this, wave, nodes]() {
bool allHealthy = true;
for (auto nodeId : nodes) {
if (!healthCheck(nodeId)) {
failedNodes.insert(nodeId);
allHealthy = false;
}
}
if (allHealthy && wave < wavePercentages.size() - 1) {
// Proceed to next wave
deployWave(wave + 1, role, hardware, md5);
} else if (!allHealthy) {
// Rollback entire deployment
rollbackDeployment();
}
}
);
}
void rollbackDeployment() {
// Trigger rollback to previous firmware
for (auto nodeId : updatedNodes) {
mesh.sendSingle(nodeId, createRollbackCommand());
}
Log(ERROR, "Progressive OTA rollback triggered!\n");
}
};ProgressiveOTA ota(mesh);
// Configure waves (5%, 20%, 100%)
ota.setPhases({0.05, 0.20, 1.0});
// Define health check
ota.setHealthCheck([&](uint32_t nodeId) -> bool {
// Check if node is responding
if (!mesh.isConnected(nodeId)) return false;
// Check node hasn't rebooted multiple times
if (getRebootCount(nodeId) > 2) return false;
// Check memory is healthy
if (getFreeMemory(nodeId) < 10000) return false;
return true;
});
// Start progressive rollout
ota.begin("sensor", "ESP32", firmwareMD5);
// Manually approve next wave (optional)
// ota.approveNextWave();
// Emergency rollback (optional)
// ota.rollbackAll();- Zero-downtime updates - Never update all nodes simultaneously
- Early failure detection - Catch issues before mesh-wide deployment
- Automatic rollback - Minimize impact of bad firmware
- Production-safe - Tested approach used by major platforms
- Complexity - Requires state management, health monitoring
- Time - Slower updates (hours vs minutes)
- Node selection - How to choose canary nodes?
- Rollback mechanism - Requires dual-boot or firmware storage
- Critical infrastructure requiring high availability
- Production deployments with zero-downtime requirements
- Organizations with risk-averse policies
- Meshes with diverse hardware/configurations
Status: 📋 Proposed
Priority: ⭐⭐⭐ Medium
Complexity: ⭐⭐⭐⭐⭐ Very High
Timeline: 4-6 months
Viral propagation where updated nodes become distribution sources, enabling exponential scaling for very large meshes (100+ nodes).
1. Viral Propagation:
- Updated nodes store firmware in flash
- Updated nodes become OTA senders
- Exponential distribution speed: 1→2→4→8→16...
- Reduces load on original sender
2. Firmware Storage:
- Requires +200-500KB flash per node
- ESP32 preferred (larger flash capacity)
- ESP8266 possible with external flash
- Automatic cleanup after mesh-wide update
3. Smart Routing:
- Nodes prefer downloading from nearest neighbor
- Load balancing across multiple sources
- Avoids redundant transmissions
- Network topology awareness
4. Chunk Coordination:
- Nodes track which chunks they have
- Request missing chunks from any source
- Parallel chunk downloads
- BitTorrent-like swarm behavior
class P2POTA {
private:
painlessMesh& mesh;
bool isSeedNode = false;
bool hasCompleteFirmware = false;
std::vector<bool> chunkBitmap; // Track which chunks we have
std::set<uint32_t> activeSources; // Nodes offering firmware
public:
void handleOTAAnnounce(const OTAAnnounce& announce) {
if (hasMatchingFirmware(announce.md5)) {
// We already have this firmware, become a source
isSeedNode = true;
advertiseFirmware(announce);
} else {
// We need this firmware, start downloading
findSources(announce);
startDownload(announce);
}
}
void findSources(const OTAAnnounce& announce) {
// Query neighbors for firmware availability
auto neighbors = mesh.getNodeList();
for (auto nodeId : neighbors) {
sendSourceQuery(nodeId, announce.md5);
}
}
void handleSourceAdvertisement(uint32_t sourceId, TSTRING md5) {
activeSources.insert(sourceId);
// Prefer nearby sources (lower hop count)
if (getHopCount(sourceId) < 3) {
// Download from this source
requestChunksFrom(sourceId);
}
}
void requestChunksFrom(uint32_t sourceId) {
// Find missing chunks
for (size_t i = 0; i < chunkBitmap.size(); i++) {
if (!chunkBitmap[i]) {
sendChunkRequest(sourceId, i);
break; // One at a time to avoid congestion
}
}
}
void handleChunkReceived(uint32_t chunkId, TSTRING data) {
// Store chunk
writeChunkToFlash(chunkId, data);
chunkBitmap[chunkId] = true;
// Check if we have all chunks
if (std::all_of(chunkBitmap.begin(), chunkBitmap.end(), [](bool b) { return b; })) {
hasCompleteFirmware = true;
isSeedNode = true; // We can now serve others
// Reboot into new firmware after serving others for a bit
scheduleReboot();
} else {
// Request next missing chunk
requestNextChunk();
}
}
};Update Speed (150 chunks, 10KB/chunk = 1.5MB firmware):
| Mesh Size | Unicast | Broadcast | P2P | P2P Speedup |
|---|---|---|---|---|
| 10 nodes | 60s | 20s | 30s | Similar to broadcast |
| 50 nodes | 300s | 30s | 40s | 1.3x faster |
| 100 nodes | 600s | 40s | 50s | 12x faster |
| 500 nodes | 3000s | 60s | 70s | 40x faster |
Why P2P is faster at scale:
- Broadcast limited by single sender's bandwidth
- P2P leverages multiple senders simultaneously
- Exponential growth: Each updated node helps others
1. Memory Requirements:
- ESP8266: ~80KB RAM, ~1-4MB flash - Challenging
- ESP32: ~320KB RAM, ~4-16MB flash - Feasible
2. Complexity:
- Chunk coordination across many nodes
- Preventing transmission loops
- Handling partial updates
- Flash wear concerns
3. Network Congestion:
- Many simultaneous transmissions
- Need traffic shaping/throttling
- QoS considerations
4. Security:
- Firmware integrity verification
- Preventing malicious nodes from distributing bad firmware
- MD5 alone may not be sufficient
- Very large deployments (100+ nodes)
- ESP32-based meshes with sufficient flash
- Scenarios where update speed is critical
- Meshes with good interconnectivity
Status: 📋 Proposed
Priority: ⭐⭐⭐ Medium
Complexity: ⭐⭐⭐ Medium
Timeline: 2-3 months
Standardized MQTT interface for triggering and managing OTA operations, enabling cloud-based firmware management and integration with external OTA tools.
1. MQTT Command Interface:
- Trigger OTA via MQTT publish
- Query OTA status via MQTT
- Cancel/pause OTA operations
- Rollback commands
2. Cloud Integration:
- Store firmware in cloud storage (S3, Azure Blob)
- Trigger updates from cloud dashboard
- Track update progress in real-time
- Integration with CI/CD pipelines
3. Firmware Distribution:
- Download firmware from URL
- Stream firmware chunks over MQTT
- Cache firmware locally
- Automatic cleanup
4. OTA Lifecycle Management:
- Schedule updates (e.g., 2 AM on weekends)
- Maintenance windows
- Update approvals/gate keeping
- Audit logging
class MqttOTA {
private:
painlessMesh& mesh;
PubSubClient& mqttClient;
String commandTopic = "mesh/ota/command";
String statusTopic = "mesh/ota/status";
public:
void begin() {
mqttClient.subscribe(commandTopic.c_str());
mqttClient.setCallback([this](char* topic, byte* payload, unsigned int length) {
handleMqttMessage(topic, payload, length);
});
}
void handleMqttMessage(char* topic, byte* payload, unsigned int length) {
DynamicJsonDocument doc(1024);
deserializeJson(doc, payload, length);
String command = doc["command"];
if (command == "start_ota") {
startOTA(doc);
} else if (command == "cancel_ota") {
cancelOTA();
} else if (command == "query_status") {
publishStatus();
} else if (command == "rollback") {
rollbackFirmware();
}
}
void startOTA(JsonDocument& doc) {
String role = doc["role"];
String hardware = doc["hardware"];
String md5 = doc["md5"];
String firmwareUrl = doc["url"];
// Download firmware from URL
downloadFirmware(firmwareUrl, [&](bool success) {
if (success) {
// Start OTA distribution
mesh.offerOTA(role, hardware, md5, getChunkCount(), false, true, true);
// Publish status
publishStatus("started", 0);
} else {
publishStatus("failed", 0);
}
});
}
void publishStatus(String status, uint8_t progress) {
DynamicJsonDocument doc(512);
doc["status"] = status;
doc["progress"] = progress;
doc["timestamp"] = millis();
doc["updated_nodes"] = getUpdatedNodeCount();
doc["total_nodes"] = mesh.getNodeList().size();
String payload;
serializeJson(doc, payload);
mqttClient.publish(statusTopic.c_str(), payload.c_str());
}
};Commands (Subscribe):
mesh/ota/command
Command Format:
{
"command": "start_ota",
"role": "sensor",
"hardware": "ESP32",
"md5": "abc123...",
"url": "https://storage.example.com/firmware/sensor-v2.0.bin",
"options": {
"broadcast": true,
"compressed": true,
"progressive": false
}
}Status (Publish):
mesh/ota/status
Status Format:
{
"status": "in_progress",
"progress": 45,
"timestamp": 1234567890,
"updated_nodes": 15,
"total_nodes": 50,
"estimated_completion": 120
}- Cloud-based OTA management
- Integration with existing MQTT infrastructure
- Automated CI/CD pipeline integration
- Centralized logging and monitoring
- Organizations with MQTT infrastructure
- Cloud-managed deployments
- CI/CD integrated workflows
- Remote management scenarios
Status: 📋 Proposed
Priority: ⭐⭐⭐⭐ Medium-High
Complexity: ⭐⭐⭐ Medium
Timeline: 2-3 months
Query-based status collection with centralized aggregation at root node, providing on-demand mesh-wide status via RESTful API or MQTT.
1. Centralized Status Aggregation:
- Root node collects status from all nodes
- On-demand vs periodic collection
- Status caching with TTL
- Historical data retention (optional)
2. RESTful API:
- HTTP endpoints on root node
- JSON response format
- Query individual or all nodes
- Filter by node ID, role, health
3. Status Queries:
-
/status- All nodes -
/status/{nodeId}- Specific node -
/health- Health summary -
/topology- Mesh structure
4. Advanced Filtering:
- Query by node role
- Filter by health status
- Sort by metrics (memory, uptime)
- Pagination for large meshes
class StatusService {
private:
painlessMesh& mesh;
AsyncWebServer server(80);
std::map<uint32_t, NodeStatus> statusCache;
uint32_t cacheTTL = 60000; // 60 seconds
public:
void begin() {
// Set up HTTP endpoints
server.on("/status", HTTP_GET, [this](AsyncWebServerRequest *request) {
handleAllStatus(request);
});
server.on("/status/*", HTTP_GET, [this](AsyncWebServerRequest *request) {
handleNodeStatus(request);
});
server.on("/health", HTTP_GET, [this](AsyncWebServerRequest *request) {
handleHealthSummary(request);
});
server.begin();
}
void handleAllStatus(AsyncWebServerRequest *request) {
// Check cache freshness
if (isCacheStale()) {
refreshCache();
}
// Build JSON response
DynamicJsonDocument doc(8192);
JsonArray nodes = doc.createNestedArray("nodes");
for (auto& [nodeId, status] : statusCache) {
JsonObject node = nodes.createNestedObject();
node["nodeId"] = nodeId;
node["uptime"] = status.uptime;
node["freeMemory"] = status.freeMemory;
node["health"] = calculateHealth(status);
}
String response;
serializeJson(doc, response);
request->send(200, "application/json", response);
}
void refreshCache() {
// Request status from all nodes
auto nodes = mesh.getNodeList();
for (auto nodeId : nodes) {
requestStatus(nodeId);
}
}
void requestStatus(uint32_t nodeId) {
// Send status request message
StatusRequest req;
req.to = nodeId;
mesh.sendPackage(&req);
}
void handleStatusResponse(const StatusResponse& response) {
// Update cache
statusCache[response.from] = {
.uptime = response.uptime,
.freeMemory = response.freeMemory,
.health = response.health,
.timestamp = millis()
};
}
};Query All Nodes:
curl http://mesh-root.local/statusResponse:
{
"nodes": [
{
"nodeId": 123456,
"uptime": 3600,
"freeMemory": 45000,
"health": "healthy",
"lastSeen": 1234567890
},
{
"nodeId": 789012,
"uptime": 1800,
"freeMemory": 38000,
"health": "warning",
"lastSeen": 1234567885
}
],
"timestamp": 1234567890,
"totalNodes": 2
}Query Specific Node:
curl http://mesh-root.local/status/123456- On-demand queries (no periodic overhead)
- RESTful API familiar to developers
- Easy integration with dashboards
- Centralized status management
- Applications needing centralized monitoring
- Dashboard integrations
- Health check systems
- Administrative tools
Status: 📋 Proposed
Priority: ⭐⭐⭐⭐⭐ High
Complexity: ⭐⭐⭐⭐ High
Timeline: 3-4 months
Continuous low-bandwidth telemetry with delta encoding, anomaly detection, and proactive alerting for real-time critical monitoring.
1. Delta Encoding:
- Only transmit changed values
- 80-90% bandwidth reduction
- Configurable thresholds
- Periodic full snapshots
2. Anomaly Detection:
- Statistical anomaly detection
- Threshold-based alerting
- Pattern recognition
- Predictive alerts
3. Real-time Streaming:
- Sub-second latency
- Continuous updates
- Minimal overhead
- Buffering for offline nodes
4. Proactive Alerting:
- Automatic alert generation
- Alert severity levels
- Alert aggregation
- Integration with notification systems
class TelemetryStream {
private:
painlessMesh& mesh;
std::map<uint32_t, TelemetryState> lastState;
uint32_t streamInterval = 1000; // 1 second
public:
void begin() {
mesh.addTask(
TASK_MILLISECOND * streamInterval,
TASK_FOREVER,
[this]() { collectAndStreamTelemetry(); }
);
}
void collectAndStreamTelemetry() {
TelemetryState current = collectCurrentState();
// Calculate deltas
TelemetryDelta delta = calculateDelta(lastState[mesh.getNodeId()], current);
// Only transmit if significant changes
if (delta.hasSignificantChanges()) {
streamDelta(delta);
}
// Check for anomalies
if (detectAnomaly(current)) {
generateAlert(current);
}
lastState[mesh.getNodeId()] = current;
}
TelemetryDelta calculateDelta(const TelemetryState& prev, const TelemetryState& current) {
TelemetryDelta delta;
// Memory change
int memoryDiff = (int)current.freeMemory - (int)prev.freeMemory;
if (abs(memoryDiff) > 1000) { // 1KB threshold
delta.memoryDelta = memoryDiff;
}
// Message rate change
uint32_t messageRateDiff = current.messageRate - prev.messageRate;
if (messageRateDiff > 10) { // 10 msg/s threshold
delta.messageRateDelta = messageRateDiff;
}
return delta;
}
bool detectAnomaly(const TelemetryState& current) {
// Statistical anomaly detection
if (current.freeMemory < 10000) return true; // Low memory
if (current.messageRate > 1000) return true; // Message flood
if (current.rebootCount > 3) return true; // Reboot loop
return false;
}
void generateAlert(const TelemetryState& state) {
Alert alert;
alert.severity = calculateSeverity(state);
alert.message = generateAlertMessage(state);
alert.timestamp = millis();
// Send alert via high-priority channel
mesh.sendBroadcast(alert.toJson(), true); // High priority
}
};Full State (200 bytes):
{
"nodeId": 123456,
"uptime": 3600,
"freeMemory": 45000,
"messageRate": 50,
"connections": 3,
...
}Delta Update (30 bytes):
{
"nodeId": 123456,
"Δmem": -1500,
"Δrate": +5
}Bandwidth Savings: 85%
- Real-time monitoring (<1s latency)
- 80-90% bandwidth reduction via delta encoding
- Proactive anomaly detection
- Scalable to 100+ nodes
- Real-time critical monitoring
- Large-scale deployments
- Predictive maintenance
- High-frequency data collection
Status: 📋 Proposed
Priority: ⭐⭐⭐ Medium
Complexity: ⭐⭐⭐⭐⭐ Very High
Timeline: 4-6 months
Complete web-based monitoring solution with embedded web server, real-time visualization, interactive topology display, and management interface.
1. Web-Based UI:
- Responsive web interface
- Real-time updates via WebSockets
- Interactive topology visualization
- Mobile-friendly design
2. Visualization:
- Network topology graph
- Memory usage charts
- Message rate graphs
- Historical trends
3. Management Interface:
- Trigger OTA updates
- View/clear alerts
- Configure settings
- Node diagnostics
4. Embedded Web Server:
- Runs on root node
- Serves static assets
- WebSocket for real-time data
- RESTful API backend
Frontend:
- HTML5/CSS3/JavaScript
- Chart.js for graphs
- D3.js for topology visualization
- WebSockets for real-time updates
Backend:
- AsyncWebServer (ESP32)
- WebSocket protocol
- JSON REST API
- SPIFFS for assets
- User-friendly visual interface
- No external dependencies
- Runs entirely on mesh
- Real-time monitoring
- High complexity (full web app)
- Large assets (~200KB)
- ESP32 required (ESP8266 insufficient)
- Performance considerations
- User-facing applications
- Local network management
- Visual monitoring needs
- Educational/demo purposes
-
Progressive Rollout OTA (1B) ⭐⭐⭐⭐⭐
- Production-critical feature
- High demand from enterprise users
- Timeline: 3-4 months
-
Telemetry Stream (2C) ⭐⭐⭐⭐⭐
- Real-time monitoring need
- Scalability improvement
- Timeline: 3-4 months
-
Status Service (2B) ⭐⭐⭐⭐
- Centralized monitoring
- RESTful API integration
- Timeline: 2-3 months
-
MQTT OTA (1D) ⭐⭐⭐
- Cloud integration
- CI/CD workflows
- Timeline: 2-3 months
-
Peer-to-Peer OTA (1C) ⭐⭐⭐
- Very large mesh support
- High complexity
- Timeline: 4-6 months
-
Health Dashboard (2D) ⭐⭐⭐
- Visual monitoring
- Educational value
- Timeline: 4-6 months
- Progressive Rollout OTA (1B)
- Telemetry Stream (2C)
- Status Service (2B)
- MQTT OTA (1D)
- Peer-to-Peer OTA (1C)
- Health Dashboard (2D)
Total Timeline: 12-18 months for all features
Interested in implementing these proposals?
- Review this document and OTA_STATUS_ENHANCEMENTS.md
- Open a GitHub issue to discuss implementation approach
- Create design document with detailed specification
- Submit proof-of-concept PR for feedback
- Implement tests and documentation
- Submit final PR for integration
Contact:
- GitHub Issues: https://github.com/Alteriom/painlessMesh/issues
- Discussions: https://github.com/Alteriom/painlessMesh/discussions
Document Version: 1.0
Last Updated: October 2025
Status: Living document - updated as proposals evolve