Rust parsing library for the ZCS Azzurro Portal API.
Retrieve solar production, battery state, and energy consumption/import/export data from ZCS-compatible inverters and storage systems.
This crate handles JSON serialization/deserialization only — HTTP transport is left to the caller.
- Historic data — time-series values (power, energy, battery SoC, …) for any 24-hour window
- Realtime data — latest snapshot values per device
- Device alarms — active alarm codes and last-update timestamp per device
[dependencies]
azzurro = "0.1"
serde_json = "1" # for serializationuse azzurro::{HistoricRequest, RealtimeRequest, DeviceAlarmRequest};
// Historic: query a 24-hour window
let req = HistoricRequest::new(
vec!["ZA1ES111H1C029".into(), "ZE1ES330M3E696".into()],
"2021-09-15T00:00:00.000Z",
"2021-09-15T23:59:59.059Z",
);
let body = serde_json::to_string(&req)?;
// Realtime: latest snapshot
let req = RealtimeRequest::new(vec!["ZA1ES111H1C029".into()]);
let body = serde_json::to_string(&req)?;
// Device alarms
let req = DeviceAlarmRequest::new(vec!["ZA1ES122JAH761".into()]);
let body = serde_json::to_string(&req)?;POST https://third.zcsazzurroportal.com:19003/
Content-Type: application/json
Authorization: Zcs <token>
use azzurro::HistoricResponse;
let resp: HistoricResponse = serde_json::from_str(&response_body)?;
for device_map in &resp.historic_data.params.value {
for (thing_key, data) in device_map {
if let Some(ts) = &data.ts {
println!("{}: {} samples", thing_key, ts.len());
}
if let Some(gen) = &data.power_generating {
println!(" powerGenerating: {:?}", gen);
}
}
}Time-series arrays (one value per sample, aligned with ts). Max query window: 24 hours.
| Field | Unit | Description |
|---|---|---|
ts |
— | ISO 8601 timestamps |
batterySoC |
% | Battery state of charge |
batteryCycletime |
— | Battery cycle count |
batterySoC2 |
% | Second battery SoC (dual-battery) |
batteryCycletime2 |
— | Second battery cycle count |
powerGenerating |
W | PV generation power |
powerConsuming |
W | Load consumption power |
powerCharging |
W | Battery charging power |
powerDischarging |
W | Battery discharging power |
powerExporting |
W | Grid export power |
powerImporting |
W | Grid import power |
powerAutoconsuming |
W | Self-consumed power |
powerDC |
W | DC power |
currentDC |
A | DC current |
voltageDC |
V | DC voltage |
temperature |
°C | Inverter temperature |
energyGenerating |
kWh | PV energy generated (period) |
energyConsuming |
kWh | Energy consumed (period) |
energyCharging |
kWh | Energy into battery (period) |
energyDischarging |
kWh | Energy from battery (period) |
energyExporting |
kWh | Energy exported (period) |
energyImporting |
kWh | Energy imported (period) |
energyAutoconsuming |
kWh | Self-consumed energy (period) |
energyGeneratingTotal |
kWh | Lifetime PV energy |
energyConsumingTotal |
kWh | Lifetime consumption |
energyChargingTotal |
kWh | Lifetime battery in |
energyDischargingTotal |
kWh | Lifetime battery out |
energyExportingTotal |
kWh | Lifetime grid export |
energyImportingTotal |
kWh | Lifetime grid import |
Same fields as historicData (scalar values, not arrays), plus:
| Field | Description |
|---|---|
lastUpdate |
ISO 8601 timestamp of last packet |
thingFind |
ISO 8601 timestamp of first-ever packet |
energyAutoconsumingTotal |
kWh lifetime self-consumed |
| Field | Description |
|---|---|
deviceAlarm |
List of active alarm IDs ([] = no alarms, absent = no data) |
lastUpdate |
ISO 8601 timestamp of last packet |
All requests default to RequiredValues::All ("*"), which asks the API to return every field the device supports. To request a subset, set required_values directly:
use azzurro::{RealtimeRequest, RequiredValues};
let mut req = RealtimeRequest::new(vec!["ZA1ES111H1C029".into()]);
req.realtime_data.params.required_values =
RequiredValues::Fields(vec!["powerGenerating".into(), "lastUpdate".into()]);Not every field is returned for every device type. The API only includes fields that are compatible with the hardware. Missing fields deserialize to None.
MIT