La copia del bridge dentro l'app va tenuta allineata: contiene la correzione del boost, dell'assenza, di ACS off e del riscaldamento forzato, che la libreria accetta solo con un argomento. Il pacchetto Sailfish non cambia (il bridge non entra nell'RPM), quindi non serve ricostruire l'RPM.
196 lines
6.4 KiB
Markdown
196 lines
6.4 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` | minuti, 15–240 (default 60) | boost ACS una tantum |
|
||
| `hot_water_heating_on` / `hot_water_heating_off` | minuti, 15–1440 (default 60) | riscaldamento ACS |
|
||
| `away_mode_on` / `away_mode_off` | giorni, 1–60 (default 7) | modalità assenza |
|
||
| `night_mode_1h` | — | night mode per un'ora |
|
||
| `force_heating_on` / `force_heating_off` | minuti, 15–240 (default 60) | 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": "..."}`.
|