# azzurro

Rust parsing library for the [ZCS Azzurro Portal](https://azzurroportal.com) 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.

## Features

- **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

## Installation

```toml
[dependencies]
azzurro = "0.1"
serde_json = "1"   # for serialization
```

## Usage

### Building a request

```rust
use 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)?;
```

### Sending the request (caller-provided HTTP)

```
POST https://third.zcsazzurroportal.com:19003/
Content-Type: application/json
Authorization: Zcs <token>
```

### Parsing a response

```rust
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);
        }
    }
}
```

## API coverage

### `historicData`

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               |

### `realtimeData`

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    |

### `deviceAlarm`

| Field         | Description                                               |
|---------------|-----------------------------------------------------------|
| `deviceAlarm` | List of active alarm IDs (`[]` = no alarms, absent = no data) |
| `lastUpdate`  | ISO 8601 timestamp of last packet                         |

## `RequiredValues`

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:

```rust
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()]);
```

## Device compatibility

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`.

## License

MIT
