Skip to main content

v0.36.0 Release Notes

EnerOS v0.36.0

Release Date: 2026-05-02 Codename: IoT Git Tag: v0.36.0 Support Status: Stable Total Crates: 84 (6 new) Test Cases: 10700+ (700 new)

Overview

EnerOS v0.36.0 “IoT” is an IoT ubiquitous access-focused release, extending EnerOS’s device access capabilities from traditional IEC 61850/SCADA to the IoT protocol system, achieving ubiquitous access to all elements of “source-grid-load-storage.” This release enables EnerOS to access millions of IoT devices, supporting plug-and-play access for new devices such as smart distribution grids, distributed renewable energy, user-side energy storage, and EV charging piles.

The core design philosophy of the IoT release is “protocol-agnostic + device shadow” — upper-layer applications do not directly interface with various heterogeneous communication protocols, but interact through a unified device model and device shadows. The device shadow, as a digital proxy of the physical device in the kernel, decouples the device’s online status from the application’s access needs: when the device is offline, applications can still read the last known state from the shadow; when the device comes online, the shadow automatically syncs and appends data from the offline period.

This release introduces five core capabilities: MQTT Protocol Support, OPC UA Protocol Support, Device Shadow Engine, Batch Device Management, and Device Access Gateway. All capabilities are implemented through five new crates: eneros-iot, eneros-iot-mqtt, eneros-iot-opcua, eneros-iot-shadow, and eneros-iot-batch.

Key Metrics

MetricValueDescription
MQTT concurrent connections100KSingle node
OPC UA device access50KSingle node
Device shadow update latency2msPer device
Batch config deployment10K devices/secParallel
New Crates6IoT-related
New tests700+Includes 150 end-to-end

New Features

1. MQTT Protocol Support

Adds the eneros-iot-mqtt crate, providing a high-performance MQTT 5.0 broker and client, supporting million-scale concurrent device access. MQTT is the de facto standard in the IoT field, widely used in smart meters, charging piles, distributed solar, and other devices.

MQTT Broker Configuration

use eneros_iot_mqtt::{MqttBroker, BrokerConfig, AuthConfig};

let broker = MqttBroker::new(BrokerConfig {
    listen: "0.0.0.0:1883".parse()?,
    listen_tls: Some("0.0.0.0:8883".parse()?),
    max_connections: 100_000,
    max_packet_size: 256 * 1024,
    auth: AuthConfig::Certificate {
        ca: "/etc/eneros/ca.pem",
        require_client_cert: true,
    },
    session_persistence: true,
    retained_messages: 100_000,
}).await?;

// Start broker
broker.start().await?;

Device Access and Data Flow

use eneros_iot_mqtt::{DeviceClient, TopicPattern};

// Device side: publish telemetry data
let client = DeviceClient::connect("mqtts://broker.eneros.io:8883").await?;
client.publish(
    TopicPattern::telemetry("meter-001"),
    json!({
        "voltage": 220.3,
        "current": 15.2,
        "power": 3.35,
        "timestamp": now_iso8601(),
    }),
    QoS::AtLeastOnce,
).await?;

// Platform side: subscribe to device data stream
let mut stream = broker.subscribe(TopicPattern::telemetry("+")).await?;
while let Some(msg) = stream.next().await {
    let device_id = msg.topic.extract_device_id()?;
    let telemetry: Telemetry = serde_json::from_slice(&msg.payload)?;
    // Auto-write to timeseries engine
    timeseries.write(device_id, telemetry).await?;
}

MQTT Topic Design

Topic PatternPurposeQoSExample
devices/{id}/telemetryTelemetry data reporting1devices/meter-001/telemetry
devices/{id}/commandCommand delivery2devices/meter-001/command
devices/{id}/eventEvent reporting1devices/meter-001/event
devices/{id}/shadowShadow update1devices/meter-001/shadow
devices/{id}/statusOnline status1devices/meter-001/status
broadcast/{topic}Broadcast message0broadcast/ota-notice

Performance Benchmarks

MetricValueDescription
Concurrent connections100KSingle node
Message throughput500K/secQoS 1
Message latency P993msSame node
Memory usage1.2 GB100K connections
TLS handshake8msCertificate authentication

2. OPC UA Protocol Support

Adds the eneros-iot-opcua crate, providing OPC UA client and server for accessing industrial control systems (DCS/PLC/RTU). OPC UA is the mainstream protocol in industrial automation, supporting information models and security mechanisms.

OPC UA Client

use eneros_iot_opcua::{OpcUaClient, ClientConfig, SecurityPolicy};

let client = OpcUaClient::new(ClientConfig {
    endpoint: "opc.tcp://10.20.30.40:4840",
    security: SecurityPolicy::Basic256Sha256,
    auth: AuthMode::UserName {
        username: "eneros",
        password: "********",
    },
    subscription_interval: Duration::milliseconds(100),
}).await?;

// Connect and browse address space
let namespace = client.get_namespace("urn:PLC:Substation1").await?;
let nodes = client.browse(&namespace.root()).await?;

// Subscribe to node data changes
let subscription = client.subscribe(vec![
    "ns=2;s=Bus1.Voltage",
    "ns=2;s=Line3.Current",
    "ns=2;s=CircuitBreaker42.Status",
]).await?;

let mut stream = subscription.data_changes();
while let Some(change) = stream.next().await {
    println!("Node {} changed: {:?}", change.node_id, change.value);
    timeseries.write(change.node_id, change.value).await?;
}

OPC UA Information Model Mapping

OPC UA ConceptEnerOS ConceptMapping Method
NodeDevice/measurement pointAuto-registration
ObjectDevice instanceMaps to Equipment
VariableTelemetry dataMaps to TimeseriesPoint
MethodControl commandMaps to Command
EventEvent alarmMaps to Event

3. Device Shadow Engine

Adds the eneros-iot-shadow crate, maintaining a digital shadow for each device, containing desired state, reported state, and last known state.

Shadow Data Structure

use eneros_iot_shadow::{DeviceShadow, ShadowState};

let shadow = shadow_store.get_or_create("meter-001").await?;

// Device reports state
shadow.update_reported(json!({
    "voltage": 220.3,
    "current": 15.2,
    "power_factor": 0.95,
    "status": "online",
})).await?;

// Application sets desired state
shadow.update_desired(json!({
    "switch": "on",
    "brightness": 80,
})).await?;

// Get current shadow state
let state: ShadowState = shadow.state().await?;
println!("Desired state: {:?}", state.desired);
println!("Reported state: {:?}", state.reported);
println!("Delta: {:?}", state.delta);  // Difference between desired and reported

Shadow Update Flow

// Auto-sync delta after device comes online
shadow.on_device_online(|device_id| {
    let delta = shadow.delta().await?;
    if !delta.is_empty() {
        // Deliver desired state to device
        mqtt_client.publish(
            TopicPattern::command(device_id),
            delta,
            QoS::AtLeastOnce,
        ).await?;
    }
}).await?;

// Preserve last state when device goes offline
shadow.on_device_offline(|device_id| {
    shadow.freeze_last_known().await?;
}).await?;

Shadow Versioning and Conflicts

FieldTypeDescription
versionu64Monotonically increasing version number
timestampDateTimeLast update time
desiredJSONDesired state
reportedJSONReported state
last_knownJSONLast known state
metadataMapPer-field update time

4. Batch Device Management

Adds the eneros-iot-batch crate, supporting batch registration, configuration, upgrade, and control for large-scale devices.

Batch Registration

use eneros_iot_batch::{BatchManager, BatchOperation};

let manager = BatchManager::new(&ctx);

// Batch register devices (CSV import)
let job = manager.batch_register()
    .source("devices.csv")
    .template(DeviceTemplate::new("smart-meter-v3"))
    .parallelism(32)
    .on_progress(|p| println!("Registration progress: {:.1}%", p.percentage))
    .execute().await?;

println!("Success: {}, Failure: {}", job.success, job.failure);

Batch Configuration Deployment

// Batch configure 10,000 devices
let rollout = manager.batch_config()
    .target(DeviceFilter::model("smart-meter-v3"))
    .config(ConfigPatch {
        collection_interval: Duration::seconds(15),
        reporting_endpoint: "mqtts://broker.eneros.io:8883",
        firmware_version: "3.2.1",
    })
    .strategy(RolloutStrategy::Wave {
        wave_size: 500,
        wave_interval: Duration::minutes(5),
        failure_threshold: 0.02,  // Pause at 2% failure rate
    })
    .execute().await?;

Batch Operation Types

OperationDescriptionConcurrencyDuration (10K devices)
RegisterBatch register devices325 minutes
ConfigureBatch deploy config1610 minutes
UpgradeBatch OTA upgrade830 minutes
ControlBatch control commands6430 seconds
QueryBatch status query12810 seconds

5. Device Access Gateway

Adds the eneros-iot crate (IoT core), providing a unified device access gateway, supporting multi-protocol coexistence and protocol conversion.

Multi-protocol Access

use eneros_iot::{DeviceGateway, ProtocolAdapter};

let gateway = DeviceGateway::new(&ctx);

// Register protocol adapters
gateway.register_adapter(ProtocolAdapter::mqtt(mqtt_config))?;
gateway.register_adapter(ProtocolAdapter::opcua(opcua_config))?;
gateway.register_adapter(ProtocolAdapter::modbus(modbus_config))?;
gateway.register_adapter(ProtocolAdapter::iec104(iec104_config))?;

// Unified device model
gateway.on_device_data(|data| {
    // Regardless of which protocol it comes from, unified to EnerOS device model
    let device_id = data.device_id;
    let telemetry = data.to_telemetry();
    timeseries.write(device_id, telemetry).await?;
    shadow_store.update_reported(device_id, data.payload).await?;
}).await?;

gateway.start().await?;

Protocol Adapter Comparison

ProtocolApplicable DeviceData ModelSecurity Mechanism
MQTTSmart meters, charging pilesTopic + JSONTLS + certificate
OPC UAPLC, DCSInformation modelSecurity policy
ModbusRTU, sensorsRegister mappingNetwork segment isolation
IEC 104Traditional RTUTelemetry/signalingApplication layer authentication
CoAPLow-power devicesRESTfulDTLS

Device Discovery

// Auto-discover devices in LAN
let discovered = gateway.discover()
    .protocol(Protocol::Mqtt)
    .subnet("10.20.30.0/24")
    .timeout(Duration::seconds(10))
    .execute().await?;

for device in &discovered {
    println!("Discovered device: {} ({:?}) @ {}",
        device.id, device.protocol, device.address);
    // Auto-register to device registry
    gateway.auto_register(device).await?;
}

Improvements

  • Timeseries Engine: Added IoT data-specific write path, small batch write throughput improved 3x
  • Topology Engine: Supports automatic construction of low-voltage distribution network topology from IoT devices
  • Agent Runtime: Added IoTAgent type, specifically for device interaction
  • Security Gateway: IoT device access enforces device certificate authentication
  • Observability: IoT device metrics integrated into central monitoring

Bug Fixes

  • Fixed eneros-iot-mqtt broker crash when large numbers of clients connect simultaneously (#3603)
  • Fixed eneros-iot-opcua subscriptions not auto-recovering after server restart (#3610)
  • Fixed eneros-iot-shadow version number conflicts during concurrent updates (#3616)
  • Fixed eneros-iot-batch not rolling back when batch operations fail midway (#3622)
  • Fixed eneros-iot data loss due to resource competition with concurrent multi-protocol (#3628)

Breaking Changes

  • DeviceShadow::update: Split into update_reported and update_desired
  • DeviceGateway::register_adapter: Returns Result<()>, conflicts must be handled
  • BatchManager: All methods changed to async

Upgrade Guide

  1. Update the eneros dependency in Cargo.toml to 0.36.0
  2. Run eneros iot init to initialize the IoT gateway
  3. Configure MQTT/OPC UA protocol parameters in eneros.toml
  4. Run eneros iot import devices.csv to batch import devices

Acknowledgments

Thanks to the 35 contributors who submitted 520+ commits, and to the IoT protocol community for compatibility testing.