196 lines
6.3 KiB
Markdown
196 lines
6.3 KiB
Markdown
# API del bridge
|
||
|
||
Servizio: `~/workspace/Carlo/Aira/aira_bridge.py` (Python, solo `pyairahome`).
|
||
Traduce HTTP/1.1 + JSON in gRPC verso il cloud Aira. Tutte le rotte tranne
|
||
`/health` richiedono:
|
||
|
||
```
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
Token: generato al primo avvio in `~/.hermes/profiles/carlo/aira_bridge.json`.
|
||
|
||
## GET /health
|
||
|
||
Nessuna autenticazione: serve a distinguere "server irraggiungibile" da
|
||
"token sbagliato".
|
||
|
||
```json
|
||
{"ok": true, "version": "0.4.0"}
|
||
```
|
||
|
||
## GET /summary
|
||
|
||
Valori pronti per la dashboard.
|
||
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"summary": {
|
||
"outdoor_temperature": 18.1,
|
||
"indoor_temperature": 22.5,
|
||
"dhw_current": 51.0,
|
||
"dhw_target": 55.0,
|
||
"operating_status": "OPERATING_STATUS_AUTO",
|
||
"away_mode": false,
|
||
"night_mode": false,
|
||
"manual_mode": false,
|
||
"force_heating": false,
|
||
"force_heating_remaining": "0s",
|
||
"inline_heater": false,
|
||
"hot_water_heating": true,
|
||
"configured_modes": "PUMP_MODE_STATE_HEATING",
|
||
"pump_active_state": "PUMP_ACTIVE_STATE_IDLE",
|
||
"outdoor_defrost": false,
|
||
"signature_lights": true,
|
||
"led_pattern": "LED_PATTERN_NORMAL",
|
||
"zones": 1,
|
||
"zone1_setpoint": 19.0,
|
||
"zone1_temperature": 23.0,
|
||
"zone1_mode": "PUMP_MODE_STATE_HEATING",
|
||
"zone1_cooling_setpoint": 22.0,
|
||
"zone2_setpoint": 0.0,
|
||
"zone2_temperature": 0.0,
|
||
"zone2_mode": "PUMP_MODE_STATE_IDLE",
|
||
"zone2_cooling_setpoint": 0.0,
|
||
"errors_count": 0,
|
||
"pump_updated": "2026-10-09 18:52:17.199340",
|
||
|
||
"device_id": "1f4b26",
|
||
"device_uuid": "1f4b26c4-1800-4b21-8ea2-236925a92aeb",
|
||
"version_platform": "6.10.0",
|
||
"version_connectivity": "3.30.1",
|
||
"version_climate_control": "3.9.12",
|
||
"version_outdoor_unit": "1.30.0",
|
||
"version_outdoor_eeprom": "0.114.0",
|
||
"connected_via": "Cloud",
|
||
"timezone": "Europe/Rome",
|
||
"thermostats_count": 1,
|
||
"thermostat_zone": 1,
|
||
"thermostat_article": "201884",
|
||
"thermostat_temperature": 22.8,
|
||
"thermostat_humidity": 73.3,
|
||
"thermostat_rssi": -79,
|
||
"thermostat_battery_low": false
|
||
}
|
||
}
|
||
```
|
||
|
||
Il blocco del dispositivo serve alle pagine "Impianto" e "Accessori" dell'app:
|
||
`device_id` sono le prime 6 cifre dell'UUID (come le mostra l'app ufficiale),
|
||
`connected_via` è la via di connessione, le `version_*` sono le versioni del
|
||
software e i campi `thermostat_*` descrivono il termostato a parete (assenti se
|
||
l'impianto non ne ha: in quel caso `thermostats_count` è 0 e gli altri sono
|
||
`null`). Il termostato manda i decimali come interi (228 = 22,8 °C): la
|
||
conversione la fa il bridge, non l'app.
|
||
|
||
## GET /stats
|
||
|
||
I numeri che Aira calcola nel cloud: sono gli stessi della scheda "Dati"
|
||
dell'app ufficiale (verificati sullo stesso periodo). Il bridge li tiene in
|
||
cache per **10 minuti** (`ttl`), perché sono quattro chiamate e cambiano
|
||
lentamente.
|
||
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"stats": {
|
||
"period_days": 30,
|
||
"currency": "EUR",
|
||
"month": {
|
||
"label": "2026-10",
|
||
"heat_kwh": 70.8,
|
||
"energy_kwh": 29.3,
|
||
"cop_heating": null,
|
||
"cop_dhw": 4.42
|
||
},
|
||
"cop_monthly": [
|
||
{"month": "2026-09", "heating_cop": null, "dhw_cop": 4.42, "avg_outdoor": 21.8}
|
||
],
|
||
"insights": [
|
||
{"date": "2026-10-01", "energy_kwh": 29.3, "heat_kwh": 70.8}
|
||
],
|
||
"savings": {"amount": 21.78, "co2_kg": 38.1, "show": true, "points": 31},
|
||
"smart_tariff": {"amount": 2.71, "hours": 180},
|
||
"monthly_savings": [{"month": "2026-09", "amount": 3.52}]
|
||
}
|
||
}
|
||
```
|
||
|
||
- `month`: l'ultimo periodo con dati — calore prodotto (`heat_kwh`), elettricità
|
||
usata (`energy_kwh`) ed efficienza (COP) di acqua calda e riscaldamento.
|
||
**`null` significa "nessun dato"**: su un impianto recente il riscaldamento può
|
||
non averne ancora (mentre l'acqua calda sì). `label` è il mese `AAAA-MM`.
|
||
- `cop_monthly` e `monthly_savings`: le serie mese per mese, per il dettaglio.
|
||
- `savings`: risparmio in bolletta sugli ultimi `period_days` giorni (30), CO₂
|
||
evitata e numero di punti usati dal calcolo; `show` è il permesso di Aira a
|
||
mostrare il dato.
|
||
- `smart_tariff`: il risparmio del mese mobile dovuto allo Smart Tariff Control.
|
||
- Questa rotta è di **sola lettura**: non invia nessun comando all'impianto.
|
||
|
||
## GET /state
|
||
|
||
Stato completo e grezzo dal cloud (42 campi: curve climatiche per zona,
|
||
versioni firmware, schedulazioni, errori). Per diagnostica.
|
||
|
||
## GET /commands
|
||
|
||
Elenco dei comandi ammessi con i loro parametri.
|
||
|
||
## POST /command
|
||
|
||
```json
|
||
{"id": "set_hot_water_target", "value": 55}
|
||
```
|
||
|
||
Risposta: `{"ok": true, "id": "...", "result": {...}}`.
|
||
|
||
### Comandi ammessi
|
||
|
||
| id | value | effetto |
|
||
|---|---|---|
|
||
| `ping` | — | verifica che l'impianto risponda |
|
||
| `set_hot_water_target` | 45–60 | temperatura ACS obiettivo |
|
||
| `set_heating_setpoint` | 5–30 | temperatura obiettivo della casa (zona, default 1) |
|
||
| `set_cooling_setpoint` | 5–30 | temperatura obiettivo del raffrescamento (zona, default 1) |
|
||
| `hot_water_boost_on` / `hot_water_boost_off` | — | boost ACS una tantum |
|
||
| `hot_water_heating_on` / `hot_water_heating_off` | — | riscaldamento ACS |
|
||
| `away_mode_on` / `away_mode_off` | — | modalità assenza |
|
||
| `night_mode_1h` | — | night mode per un'ora |
|
||
| `force_heating_on` / `force_heating_off` | — | riscaldamento forzato |
|
||
| `heating_on` / `heating_off` | — | funzione riscaldamento |
|
||
| `legionella_cycle` | — | ciclo antilegionella |
|
||
|
||
### Riscaldamento: come si invia il comando per zona
|
||
|
||
Il messaggio `ZoneTemperatures` contiene **sempre entrambe** le zone:
|
||
|
||
```json
|
||
{"id": "set_heating_setpoint", "value": 21.5, "zone": 1}
|
||
```
|
||
|
||
`zone` è opzionale (default 1) e accetta 1 o 2. La zona che non si sta
|
||
modificando viene rimandata con il **suo valore attuale**, letto dall'ultimo
|
||
stato disponibile: inviarla a zero la spegnerebbe. Per questo il bridge tiene
|
||
in memoria l'ultima lettura, e il comando va costruito con quel contesto.
|
||
|
||
Il campo `kind` distingue riscaldamento (`1`) da raffrescamento (`2`).
|
||
|
||
### Perché una whitelist
|
||
|
||
La libreria espone ~90 comandi, fra cui `FactoryReset`, `InstallFirmware`,
|
||
`RebootDevice`, `RotateCertificate`, `SetWifiCredentials`. Restano **fuori di
|
||
proposito**: un client mobile non deve poter riformattare l'impianto per un tap
|
||
sbagliato.
|
||
|
||
## Errori
|
||
|
||
| stato | significato |
|
||
|---|---|
|
||
| 400 | comando non ammesso, oppure valore fuori intervallo |
|
||
| 401 | token mancante o errato |
|
||
| 404 | rotta sconosciuta |
|
||
| 502 | il bridge non ha potuto parlare con il cloud (dettaglio in `error`) |
|
||
|
||
Formato: `{"ok": false, "error": "..."}`.
|