Skip to content

PHASE1_GUIDE

github-actions[bot] edited this page Oct 24, 2025 · 2 revisions

Phase 1 OTA Features Guide

Overview

Phase 1 implements two key enhancements to painlessMesh:

  1. Option 1E: Compressed OTA Transfer - 40-60% bandwidth reduction
  2. Option 2A: Enhanced StatusPackage - Comprehensive health monitoring

These features provide immediate value with minimal complexity and risk.

Benefits

Compressed OTA (Option 1E)

  • ✅ 40-60% bandwidth reduction - Less network congestion
  • ✅ Faster updates - Reduced transfer time
  • ✅ Lower energy consumption - Less radio time
  • ✅ Universal benefit - Works with all distribution methods
  • ✅ Backward compatible - Works with uncompressed nodes

Enhanced Status (Option 2A)

  • ✅ Comprehensive monitoring - Device health + mesh stats + performance
  • ✅ Proactive alerting - Detect issues before they become critical
  • ✅ Standardized format - Consistent across all Alteriom nodes
  • ✅ Easy integration - Ready for dashboards and monitoring tools
  • ✅ Minimal overhead - ~500 bytes per status report

Implementation

1. Compressed OTA Transfer

Sender Side (OTA Distribution Node)

#include "painlessMesh.h"

#define PAINLESSMESH_ENABLE_OTA

painlessMesh mesh;

void setup() {
  // Initialize mesh...
  
  // Setup OTA sender with callback to provide firmware data
  mesh.initOTASend(firmwareCallback, OTA_PART_SIZE);
  
  // Announce firmware with compression enabled
  String md5 = "abc123def456";  // MD5 hash of firmware
  size_t parts = 100;           // Number of firmware chunks
  
  mesh.offerOTA(
    "sensor",    // Role
    "ESP32",     // Hardware
    md5,         // MD5 hash
    parts,       // Number of parts
    false,       // Not forced
    false,       // Not broadcast (Phase 2)
    true         // COMPRESSED! ← Phase 1 feature
  );
}

size_t firmwareCallback(painlessmesh::plugin::ota::DataRequest req, char* buffer) {
  // Load firmware chunk into buffer
  // Return size of data loaded
  return loadFirmwareChunk(req.partNo, buffer);
}

Receiver Side (Nodes Being Updated)

#include "painlessMesh.h"

#define PAINLESSMESH_ENABLE_OTA

painlessMesh mesh;

void setup() {
  // Initialize mesh...
  
  // Setup OTA receiver - automatically handles compression
  mesh.initOTAReceive("sensor", progressCallback);
  
  // That's it! Compression is handled automatically
}

void progressCallback(int current, int total) {
  Serial.printf("OTA Progress: %d/%d (%.1f%%)\n", 
                current, total, 100.0 * current / total);
}

Key Points:

  • Compression is transparent to receiver nodes
  • Nodes automatically decompress during flash write
  • MD5 verification uses original (uncompressed) firmware hash
  • Backward compatible - compressed nodes can receive uncompressed OTA

2. Enhanced Status Package

Creating and Sending Enhanced Status

#include "alteriom_sensor_package.hpp"
#include "painlessMesh.h"

painlessMesh mesh;

void sendEnhancedStatus() {
  alteriom::EnhancedStatusPackage status;
  
  // Device Health
  status.deviceStatus = 0x01;                          // Online
  status.uptime = millis() / 1000;                     // Seconds
  status.freeMemory = ESP.getFreeHeap() / 1024;        // KB
  status.wifiStrength = map(WiFi.RSSI(), -100, -50, 0, 100);
  status.firmwareVersion = "1.0.0";
  status.firmwareMD5 = "abc123def456";
  
  // Mesh Statistics
  status.nodeCount = mesh.getNodeList().size();
  status.connectionCount = mesh.connectionCount();
  status.messagesReceived = getTotalMessagesReceived();
  status.messagesSent = getTotalMessagesSent();
  status.messagesDropped = getTotalMessagesDropped();
  
  // Performance Metrics
  status.avgLatency = calculateAverageLatency();       // ms
  status.packetLossRate = calculatePacketLoss();       // 0-100%
  status.throughput = calculateThroughput();           // bytes/sec
  
  // Alerts
  status.alertFlags = 0;
  if (status.freeMemory < 32) status.alertFlags |= 0x01;  // Low memory
  if (status.wifiStrength < 30) status.alertFlags |= 0x02; // Weak signal
  status.lastError = (status.alertFlags == 0) ? "" : "See alerts";
  
  // Send as broadcast
  String msg;
  protocol::Variant(&status).printTo(msg);
  mesh.sendBroadcast(msg);
}

Receiving and Processing Enhanced Status

void receivedCallback(uint32_t from, String& msg) {
  DynamicJsonDocument doc(2048);
  deserializeJson(doc, msg);
  
  int msgType = doc["type"];
  
  if (msgType == 203) {  // Enhanced Status
    protocol::Variant variant(msg);
    auto status = variant.to<alteriom::EnhancedStatusPackage>();
    
    Serial.printf("Status from %u: v%s\n", from, status.firmwareVersion.c_str());
    Serial.printf("  Uptime: %ds, Memory: %dKB, Nodes: %d\n",
                  status.uptime, status.freeMemory, status.nodeCount);
    Serial.printf("  Messages: %d RX, %d TX, %d dropped\n",
                  status.messagesReceived, status.messagesSent, 
                  status.messagesDropped);
    Serial.printf("  Performance: %dms latency, %d%% loss, %d B/s\n",
                  status.avgLatency, status.packetLossRate, status.throughput);
    
    if (status.alertFlags) {
      Serial.printf("  ALERTS: 0x%02X - %s\n", 
                    status.alertFlags, status.lastError.c_str());
    }
  }
}

Alert Flags

The alertFlags field uses bit flags for different alert conditions:

Bit Value Meaning
0 0x01 Low memory warning
1 0x02 Weak WiFi signal
2 0x04 Isolated node (no connections)
3 0x08 High packet loss rate
4 0x10 High message latency
5 0x20 Frequent disconnections
6 0x40 Custom application alert
7 0x80 Critical error condition

Package Type IDs

Type Package Name Description
200 SensorPackage Environmental sensor data
400 CommandPackage Device control commands
202 StatusPackage Basic status (original)
203 EnhancedStatusPackage Comprehensive status (Phase 1)

Integration with Metrics System

For production use, integrate with painlessMesh metrics:

#include "painlessmesh/metrics.hpp"

void sendEnhancedStatus() {
  alteriom::EnhancedStatusPackage status;
  
  // ... set device health fields ...
  
  // Integrate with mesh metrics (if enabled)
  #ifdef PAINLESSMESH_ENABLE_METRICS
  auto& metrics = mesh.getMetrics();
  status.messagesReceived = metrics.message_stats().messages_received;
  status.messagesSent = metrics.message_stats().messages_sent;
  status.avgLatency = metrics.message_stats().average_latency_ms();
  // ... other metrics ...
  #endif
  
  // ... send status ...
}

Example Projects

See the examples/alteriom/ directory for complete examples:

  • phase1_features.ino - Comprehensive Phase 1 demonstration
  • improved_sensor_node.ino - Enhanced sensor with validation

Performance Impact

Compressed OTA

  • Memory: +4-8KB RAM (decompression buffer)
  • CPU: Minimal decompression overhead
  • Bandwidth: 40-60% reduction (typical)
  • Update Time: 35-70s for 10 nodes (vs 60-120s uncompressed)

Enhanced Status

  • Memory: +500 bytes per status report
  • Network: ~1.5KB per status message
  • CPU: Negligible serialization overhead
  • Frequency: Configurable (recommend 30-60s interval)

Backward Compatibility

Both features are fully backward compatible:

Compressed OTA

  • Nodes without compression support can still receive uncompressed OTA
  • Mixed mesh with compressed and uncompressed nodes works correctly
  • Compression flag is optional in announce messages

Enhanced Status

  • Uses new type ID (203) to distinguish from basic status (202)
  • Nodes can send both basic and enhanced status
  • Receivers can handle both types simultaneously
  • Default values for all fields (safe to omit fields)

Migration Guide

From Uncompressed OTA

// Before (Phase 0)
mesh.offerOTA("sensor", "ESP32", md5, parts);

// After (Phase 1) - just add compression flag
mesh.offerOTA("sensor", "ESP32", md5, parts, false, false, true);
//                                            ^^^^^ ^^^^^ ^^^^
//                                            forced bcast compress

From Basic StatusPackage

// Before (basic status)
alteriom::StatusPackage status;
status.uptime = millis() / 1000;
status.freeMemory = ESP.getFreeHeap() / 1024;
// ... send ...

// After (enhanced status) - just add fields
alteriom::EnhancedStatusPackage status;
status.uptime = millis() / 1000;
status.freeMemory = ESP.getFreeHeap() / 1024;
status.nodeCount = mesh.getNodeList().size();  // New fields
status.messagesReceived = getTotalRx();        // New fields
// ... send ...

Testing

Run the test suite to verify Phase 1 features:

cd /path/to/painlessMesh
cmake -G Ninja .
ninja
./bin/catch_alteriom_packages

All tests should pass (80+ assertions).

Next Steps (Phase 2)

Phase 1 provides the foundation for Phase 2 features:

  • Broadcast OTA (Option 1A) - Single transmission to all nodes
  • MQTT Status Bridge (Option 2E) - Cloud integration
  • Grafana/InfluxDB dashboards - Professional monitoring

Troubleshooting

Compressed OTA Not Working

  1. Check that PAINLESSMESH_ENABLE_OTA is defined
  2. Verify compression flag is set to true
  3. Ensure sender has compression library available
  4. Check receiver has sufficient RAM for decompression buffer

Enhanced Status Not Received

  1. Verify type ID is 203 (not 202)
  2. Check receiver handles type 203 in callback
  3. Ensure sufficient buffer size (2048+ bytes for JSON parsing)
  4. Verify all string fields have valid values

Performance Issues

  1. Reduce status broadcast frequency (use 60s+ intervals)
  2. Check memory availability (need ~8KB free for OTA)
  3. Monitor packet loss rate in enhanced status
  4. Consider mesh topology (star vs mesh affects performance)

Support

For questions or issues:

  1. Check full proposal: docs/improvements/FEATURE_PROPOSALS.md
  2. Review examples: examples/alteriom/phase1_features.ino
  3. Open GitHub issue with logs and configuration

Phase 1 Status: ✅ Implemented and Tested
Expected Performance: 40-60% OTA bandwidth reduction + comprehensive status monitoring
Risk Level: Low (backward compatible, minimal changes)

Clone this wiki locally