Skip to content

alteriom packages

github-actions[bot] edited this page Sep 7, 2026 · 1 revision

Alteriom Packages

Alteriom provides production-ready, type-safe packages for painlessMesh that handle common IoT communication patterns. These packages eliminate the need for manual JSON handling and provide compile-time type safety.

Available Packages

SensorPackage (Type 200)

For broadcasting environmental sensor data across the mesh network.

#include "examples/alteriom/alteriom_sensor_package.hpp"

using namespace alteriom;

SensorPackage sensor;
sensor.temperature = 23.5;      // °C
sensor.humidity = 65.0;         // %
sensor.pressure = 1013.25;      // hPa
sensor.sensorId = 1001;         // Unique sensor identifier
sensor.timestamp = mesh.getNodeTime();
sensor.batteryLevel = 85;       // %

// Send to all nodes
mesh.sendPackage(&sensor);

Fields:

  • temperature (double) - Temperature in Celsius
  • humidity (double) - Relative humidity percentage
  • pressure (double) - Atmospheric pressure in hPa
  • sensorId (uint32_t) - Unique sensor identifier
  • timestamp (uint32_t) - Measurement timestamp
  • batteryLevel (uint8_t) - Battery level percentage

CommandPackage (Type 400)

For sending control commands to specific devices in the mesh.

CommandPackage cmd;
cmd.dest = targetNodeId;        // Target node ID
cmd.command = 1;                // Command type (LED_CONTROL)
cmd.targetDevice = 100;         // Device ID (LED strip)
cmd.parameters = "{\"brightness\":75,\"color\":\"blue\"}";
cmd.commandId = generateCommandId();

// Send to specific node
mesh.sendPackage(&cmd);

Fields:

  • command (uint8_t) - Command type identifier
  • targetDevice (uint32_t) - Target device ID
  • commandId (uint32_t) - Unique command identifier for tracking
  • parameters (TSTRING) - JSON parameters for the command

StatusPackage (Type 202)

For broadcasting device health and operational status.

StatusPackage status;
status.deviceStatus = 1;        // OPERATIONAL
status.uptime = millis() / 1000;
status.freeMemory = ESP.getFreeHeap();
status.wifiStrength = WiFi.RSSI();
status.firmwareVersion = "2.0.0";

// Broadcast to all nodes
mesh.sendPackage(&status);

Fields:

  • deviceStatus (uint8_t) - Device status flags
  • uptime (uint32_t) - Device uptime in seconds
  • freeMemory (uint16_t) - Available memory in bytes
  • wifiStrength (uint8_t) - WiFi signal strength
  • firmwareVersion (TSTRING) - Firmware version string

Package Handlers

Register handlers to process incoming packages:

void setup() {
    mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
    
    // Register package handlers
    mesh.onPackage(200, handleSensorData);
    mesh.onPackage(400, handleCommand);
    mesh.onPackage(202, handleStatus);
}

void handleSensorData(protocol::Variant& variant) {
    SensorPackage sensor = variant.to<SensorPackage>();
    
    Serial.printf("Sensor %u: T=%.1f°C, H=%.1f%%, P=%.1f hPa\n",
                 sensor.sensorId, sensor.temperature, 
                 sensor.humidity, sensor.pressure);
    
    // Process sensor data
    if (sensor.temperature > 30.0) {
        triggerCooling();
    }
    
    if (sensor.batteryLevel < 20) {
        Serial.printf("Low battery warning: Node %u, Sensor %u\n", 
                     sensor.from, sensor.sensorId);
    }
}

void handleCommand(protocol::Variant& variant) {
    CommandPackage cmd = variant.to<CommandPackage>();
    
    // Check if command is for this node
    if (cmd.dest != mesh.getNodeId()) {
        return;
    }
    
    Serial.printf("Command %u for device %u: %u\n",
                 cmd.commandId, cmd.targetDevice, cmd.command);
    
    // Execute command
    executeDeviceCommand(cmd);
    
    // Send acknowledgment
    sendCommandAcknowledgment(cmd);
}

void handleStatus(protocol::Variant& variant) {
    StatusPackage status = variant.to<StatusPackage>();
    
    Serial.printf("Node %u status: %s, uptime %u seconds\n",
                 status.from, 
                 status.deviceStatus == 1 ? "OK" : "ERROR",
                 status.uptime);
    
    // Monitor network health
    if (status.freeMemory < 10000) {
        Serial.printf("Low memory warning: Node %u has %u bytes\n",
                     status.from, status.freeMemory);
    }
}

Type Safety Benefits

Compile-Time Validation

// ✅ Correct - compiler validates types
sensor.temperature = 25.5;
sensor.sensorId = 1001;

// ❌ Compiler error - type mismatch
sensor.temperature = "invalid";
sensor.sensorId = "not a number";

IDE Support

Modern IDEs provide autocompletion and type hints:

sensor.| // IDE shows: temperature, humidity, pressure, sensorId, etc.

Automatic Serialization

No manual JSON handling required:

// Automatic conversion to/from JSON
mesh.sendPackage(&sensor);  // Automatically serializes

// In handler
SensorPackage received = variant.to<SensorPackage>();  // Automatically deserializes

Advanced Usage Patterns

Command Types

Define application-specific command constants:

enum DeviceCommands {
    LED_CONTROL = 1,
    SERVO_POSITION = 2,
    RELAY_SWITCH = 3,
    DISPLAY_UPDATE = 4,
    SENSOR_CALIBRATION = 5
};

void executeDeviceCommand(const CommandPackage& cmd) {
    switch(cmd.command) {
        case LED_CONTROL:
            handleLEDCommand(cmd);
            break;
        case SERVO_POSITION:
            handleServoCommand(cmd);
            break;
        case RELAY_SWITCH:
            handleRelayCommand(cmd);
            break;
        // ... other commands
    }
}

JSON Parameters

Use structured parameters for complex commands:

void handleLEDCommand(const CommandPackage& cmd) {
    // Parse JSON parameters
    DynamicJsonDocument doc(256);
    deserializeJson(doc, cmd.parameters);
    
    int brightness = doc["brightness"];
    String color = doc["color"];
    int duration = doc["duration"];
    
    // Execute LED control
    setLEDColor(color);
    setLEDBrightness(brightness);
    
    if (duration > 0) {
        scheduleAutoOff(duration);
    }
}

Status Flags

Use bit flags for detailed device status:

enum StatusFlags {
    STATUS_OK = 0x01,
    STATUS_LOW_MEMORY = 0x02,
    STATUS_WEAK_SIGNAL = 0x04,
    STATUS_SENSOR_ERROR = 0x08,
    STATUS_BATTERY_LOW = 0x10
};

void updateDeviceStatus() {
    StatusPackage status;
    status.from = mesh.getNodeId();
    status.deviceStatus = STATUS_OK;
    
    if (ESP.getFreeHeap() < 10000) {
        status.deviceStatus |= STATUS_LOW_MEMORY;
    }
    
    if (WiFi.RSSI() < -80) {
        status.deviceStatus |= STATUS_WEAK_SIGNAL;
    }
    
    if (batteryVoltage < 3.2) {
        status.deviceStatus |= STATUS_BATTERY_LOW;
    }
    
    status.uptime = millis() / 1000;
    status.freeMemory = ESP.getFreeHeap();
    status.wifiStrength = WiFi.RSSI();
    status.firmwareVersion = FIRMWARE_VERSION;
    
    mesh.sendPackage(&status);
}

Command Acknowledgments

Implement reliable command delivery:

void sendCommandAcknowledgment(const CommandPackage& originalCmd) {
    CommandPackage ack;
    ack.dest = originalCmd.from;
    ack.command = 255;  // ACK command
    ack.targetDevice = originalCmd.targetDevice;
    ack.commandId = originalCmd.commandId;
    ack.parameters = "OK";
    
    mesh.sendPackage(&ack);
}

// In command sender
std::map<uint32_t, uint32_t> pendingCommands;  // commandId -> timestamp

void trackCommand(const CommandPackage& cmd) {
    pendingCommands[cmd.commandId] = millis();
}

void checkCommandTimeouts() {
    uint32_t now = millis();
    
    for (auto it = pendingCommands.begin(); it != pendingCommands.end();) {
        if (now - it->second > 5000) {  // 5 second timeout
            Serial.printf("Command %u timed out\n", it->first);
            it = pendingCommands.erase(it);
        } else {
            ++it;
        }
    }
}

Memory Optimization

Buffer Sizing

Packages automatically calculate required buffer sizes:

// Get exact buffer size needed
size_t bufferSize = sensor.jsonObjectSize();
DynamicJsonDocument doc(bufferSize);

// Or use static sizing for known maximums
StaticJsonDocument<256> doc;  // Sufficient for most SensorPackages

Memory Usage

Typical memory usage per package:

  • SensorPackage: ~150-200 bytes
  • CommandPackage: ~100-300 bytes (depending on parameters)
  • StatusPackage: ~200-250 bytes

Platform Compatibility

TSTRING Adaptation

Packages use TSTRING for cross-platform string compatibility:

#ifdef ARDUINO
typedef String TSTRING;        // Arduino String class
#else
typedef std::string TSTRING;   // Standard C++ string
#endif

ArduinoJson Versions

Compatible with both ArduinoJson v6 and v7:

#if ARDUINOJSON_VERSION_MAJOR >= 7
// v7 automatic sizing
#else
// v6 manual sizing with jsonObjectSize()
#endif

Testing Packages

Unit tests ensure package reliability:

#include "catch2/catch.hpp"
#include "examples/alteriom/alteriom_sensor_package.hpp"

SCENARIO("SensorPackage serialization works correctly") {
    GIVEN("A SensorPackage with test data") {
        auto pkg = SensorPackage();
        pkg.from = 12345;
        pkg.temperature = 25.5;
        pkg.humidity = 60.0;
        pkg.pressure = 1013.25;
        pkg.sensorId = 1001;
        pkg.timestamp = 1234567890;
        pkg.batteryLevel = 85;
        
        WHEN("Converting to and from Variant") {
            auto var = protocol::Variant(&pkg);
            auto pkg2 = var.to<SensorPackage>();
            
            THEN("All fields should match") {
                REQUIRE(pkg2.from == pkg.from);
                REQUIRE(pkg2.temperature == pkg.temperature);
                REQUIRE(pkg2.humidity == pkg.humidity);
                REQUIRE(pkg2.pressure == pkg.pressure);
                REQUIRE(pkg2.sensorId == pkg.sensorId);
                REQUIRE(pkg2.timestamp == pkg.timestamp);
                REQUIRE(pkg2.batteryLevel == pkg.batteryLevel);
            }
        }
    }
}

Creating Custom Packages

Extend the Alteriom package system with your own types:

class WeatherPackage : public painlessmesh::plugin::BroadcastPackage {
public:
    double windSpeed = 0.0;
    double windDirection = 0.0;
    double rainfall = 0.0;
    uint32_t stationId = 0;
    TSTRING location = "";
    
    WeatherPackage() : BroadcastPackage(206) {}  // Use an ID from the 206+ range
    
    WeatherPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
        windSpeed = jsonObj["windSpeed"];
        windDirection = jsonObj["windDirection"];
        rainfall = jsonObj["rainfall"];
        stationId = jsonObj["stationId"];
        location = jsonObj["location"].as<TSTRING>();
    }
    
    JsonObject addTo(JsonObject&& jsonObj) const {
        jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
        jsonObj["windSpeed"] = windSpeed;
        jsonObj["windDirection"] = windDirection;
        jsonObj["rainfall"] = rainfall;
        jsonObj["stationId"] = stationId;
        jsonObj["location"] = location;
        return jsonObj;
    }
    
#if ARDUINOJSON_VERSION_MAJOR < 7
    size_t jsonObjectSize() const {
        return JSON_OBJECT_SIZE(noJsonFields + 5) + location.length();
    }
#endif
};

Best Practices

1. Use Appropriate Package Types

  • SensorPackage: Environmental readings, measurements
  • CommandPackage: Device control, configuration changes
  • StatusPackage: Health monitoring, diagnostics
  • Custom packages: Domain-specific data structures

2. Design for Reliability

// Include timestamps for data freshness validation
sensor.timestamp = mesh.getNodeTime();

// Use unique IDs for command tracking
cmd.commandId = random(1000000, 9999999);

// Include version info for compatibility
status.firmwareVersion = "2.1.0";

3. Handle Errors Gracefully

void handleSensorData(protocol::Variant& variant) {
    try {
        SensorPackage sensor = variant.to<SensorPackage>();
        
        // Validate data ranges
        if (sensor.temperature < -50 || sensor.temperature > 100) {
            Serial.println("Invalid temperature reading");
            return;
        }
        
        processSensorData(sensor);
        
    } catch (const std::exception& e) {
        Serial.printf("Error processing sensor package: %s\n", e.what());
    }
}

4. Monitor Performance

void handleSensorData(protocol::Variant& variant) {
    uint32_t startTime = micros();
    
    SensorPackage sensor = variant.to<SensorPackage>();
    processSensorData(sensor);
    
    uint32_t processingTime = micros() - startTime;
    if (processingTime > 10000) {  // > 10ms
        Serial.printf("Slow sensor processing: %u µs\n", processingTime);
    }
}

Bridge Failover Packages

BridgeStatusPackage (Type 610)

For monitoring bridge health and Internet connectivity.

#include "examples/alteriom/alteriom_sensor_package.hpp"

using namespace alteriom;

BridgeStatusPackage bridgeStatus;
bridgeStatus.internetConnected = true;
bridgeStatus.routerRSSI = -45;         // dBm
bridgeStatus.routerChannel = 6;
bridgeStatus.uptime = millis();
bridgeStatus.gatewayIP = "192.168.1.1";
bridgeStatus.timestamp = mesh.getNodeTime();

// Broadcast to all nodes
mesh.sendPackage(&bridgeStatus);

Fields:

  • internetConnected (bool) - Bridge Internet connectivity status
  • routerRSSI (int8_t) - Router WiFi signal strength in dBm
  • routerChannel (uint8_t) - Router WiFi channel
  • uptime (uint32_t) - Bridge uptime in milliseconds
  • gatewayIP (TSTRING) - Router gateway IP address
  • timestamp (uint32_t) - Status check timestamp

Use Cases:

  • Bridge health monitoring
  • Internet connectivity tracking
  • Automatic failover detection
  • Network diagnostics

BridgeElectionPackage (Type 611)

For coordinating automatic bridge failover elections.

BridgeElectionPackage election;
election.routerRSSI = WiFi.RSSI();    // -127 to 0 dBm
election.routerChannel = WiFi.channel();
election.uptime = millis();
election.freeMemory = ESP.getFreeHeap();
election.timestamp = mesh.getNodeTime();
election.routerSSID = "MyRouter";

// Broadcast election candidacy
mesh.sendPackage(&election);

Fields:

  • routerRSSI (int8_t) - Router WiFi signal strength (-127 to 0 dBm)
  • routerChannel (uint8_t) - Router channel used if this candidate wins
  • uptime (uint32_t) - Node uptime in milliseconds
  • freeMemory (uint32_t) - Free memory in bytes
  • timestamp (uint32_t) - Election timestamp
  • routerSSID (TSTRING) - Router SSID for verification

Use Cases:

  • Automatic bridge election
  • Failover coordination
  • RSSI-based selection
  • Distributed consensus

BridgeTakeoverPackage (Type 612)

For announcing bridge role transitions.

BridgeTakeoverPackage takeover;
takeover.previousBridge = oldBridgeNodeId;  // 0 if none
takeover.reason = "Internet connectivity lost";
takeover.routerChannel = WiFi.channel();
takeover.timestamp = mesh.getNodeTime();

// Announce new bridge role
mesh.sendPackage(&takeover);

Fields:

  • previousBridge (uint32_t) - Previous bridge node ID (0 if none)
  • reason (TSTRING) - Takeover reason description
  • routerChannel (uint8_t) - Channel peers must follow during takeover
  • timestamp (uint32_t) - Takeover timestamp

Use Cases:

  • Bridge transition announcements
  • Failover notifications
  • Network event logging
  • Topology tracking

NTPTimeSyncPackage (Type 614)

For distributing NTP time synchronization from bridge to mesh.

NTPTimeSyncPackage ntpSync;
ntpSync.ntpTime = 1699564800;        // Unix timestamp
ntpSync.accuracy = 50;               // ±50ms uncertainty
ntpSync.source = "pool.ntp.org";
ntpSync.timestamp = mesh.getNodeTime();

// Broadcast NTP time to mesh
mesh.sendPackage(&ntpSync);

Fields:

  • ntpTime (uint32_t) - Unix timestamp from NTP server
  • accuracy (uint16_t) - Milliseconds uncertainty/precision
  • source (TSTRING) - NTP server source
  • timestamp (uint32_t) - Collection timestamp

Use Cases:

  • Time synchronization from Internet
  • RTC backup coordination
  • Mesh-wide time distribution
  • Offline timekeeping

BridgeCoordinationPackage (Type 613)

For coordinating multiple simultaneous bridges in advanced deployments.

BridgeCoordinationPackage coordination;
coordination.priority = 10;              // Highest priority
coordination.role = "primary";           // "primary", "secondary", "standby"
coordination.peerBridges.push_back(bridge2NodeId);
coordination.peerBridges.push_back(bridge3NodeId);
coordination.load = 35;                  // 35% load
coordination.timestamp = mesh.getNodeTime();

// Broadcast to coordinate with other bridges
mesh.sendPackage(&coordination);

Monitoring Callbacks

// Monitor bridge coordination (fires every ~30s per bridge)
mesh.onBridgeCoordination(
  [](const plugin::BridgeCoordinationPackage& pkg, uint32_t fromNode) {
    Serial.printf("Bridge %u: priority=%d, load=%d%%\n",
                  fromNode, pkg.priority, pkg.load);
  }
);

// Get notified when bridge state changes (new/updated/lost)
mesh.onBridgeCoordinationChanged(
  [](const plugin::BridgeCoordinationPackage& pkg, uint32_t fromNode,
     TSTRING changeType) {
    Serial.printf("Bridge %s: %u (role=%s)\n",
                  changeType.c_str(), fromNode, pkg.role.c_str());
  }
);

Fields:

  • priority (uint8_t) - Bridge priority (10=highest, 1=lowest)
  • role (TSTRING) - Current role: "primary", "secondary", "standby"
  • peerBridges (vector<uint32_t>) - List of known bridge node IDs
  • load (uint8_t) - Current load percentage (0-100)
  • timestamp (uint32_t) - Coordination timestamp

Use Cases:

  • Multiple simultaneous bridges (hot standby)
  • Load balancing across bridges
  • Geographic distribution
  • Automatic traffic shaping
  • Conflict resolution

MessageAckPackage (Type 630)

Internal package for per-message delivery confirmation (v2.0.0+). Sent automatically by the receiving node when an application message carries a msgId (i.e. the sender passed a delivery callback to sendSingle() or sendBroadcast()). Applications do not construct this package themselves — use the delivery callback API instead:

mesh.sendSingle(destId, msg,
                [](uint32_t nodeId, bool delivered, uint32_t latencyMs) {
  // delivered == true  → nodeId confirmed receipt (latencyMs round trip)
  // delivered == false → no acknowledgment within the timeout
});

Fields:

  • from (uint32_t) - Acknowledging node
  • dest (uint32_t) - Original sender the ACK is routed back to
  • msgId (uint32_t) - ID of the message being acknowledged

Wire notes:

  • Routed as a SINGLE package, so it traverses multiple hops
  • Delivery confirmation requires version 2.0 on every participating node.

Complete Package Type Reference

Type Class Purpose
200 SensorPackage Environmental sensor data
202 StatusPackage Device health monitoring
204 MetricsPackage Performance metrics
400 CommandPackage Device control
600 MeshNodeListPackage Node inventory
601 MeshTopologyPackage Network topology
602 MeshAlertPackage Network alerts
603 MeshBridgePackage Protocol bridging
604 EnhancedStatusPackage Mesh-wide status
605 HealthCheckPackage Proactive health monitoring
610 BridgeStatusPackage Bridge health monitoring
611 BridgeElectionPackage Failover election
612 BridgeTakeoverPackage Bridge transition and channel handoff
613 BridgeCoordinationPackage Multi-bridge coordination
614 NTPTimeSyncPackage NTP time distribution
620 GatewayDataPackage Internet-bound data routing
621 GatewayAckPackage Gateway delivery acknowledgment
622 GatewayHeartbeatPackage Gateway health monitoring
630 MessageAckPackage Per-message delivery acknowledgment

See also:

Clone this wiki locally