- home: temperatura esterna e in casa/assente in alto, messaggio di stato, scheda impianto con temperatura stanza e acqua calda; comandi sotto - pagina Dati: calore prodotto, elettricita' usata, COP acqua calda e riscaldamento, risparmio, CO2 evitata, Smart Tariff Control (mese per mese) - pagina Impianto: stato, termostato a parete, versioni software, errori - bridge 0.4.0: rotta /stats (servizio statistiche del cloud) e blocco dispositivo in /summary (ID breve, versioni, termostato) - C++: parseStats, formatNumber, formatMoney, monthLabel (separatore e valuta secondo la lingua); 61/61 verifiche del nucleo - anteprime aggiornate (dati, impianto) in inglese e italiano
122 lines
4.6 KiB
Python
122 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Sonda di sola lettura sui dati statistici del cloud Aira (v2).
|
|
NON invia comandi all'impianto. Obiettivo: capire cosa si puo' mostrare in una
|
|
pagina "Dati" come quella dell'app ufficiale (calore prodotto, elettricita',
|
|
efficienza/COP, risparmi).
|
|
"""
|
|
import json
|
|
import os
|
|
import sys
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
|
|
CFG = Path(os.environ.get("AIRA_CONFIG_DIR", str(Path.home() / ".hermes/profiles/carlo")))
|
|
sys.path.insert(0, str(CFG))
|
|
|
|
from pyairahome import AiraHome
|
|
from pyairahome.device.heat_pump.statistics.v1 import service_pb2 as S
|
|
|
|
def section(t):
|
|
print("\n" + "=" * 72)
|
|
print(t)
|
|
print("=" * 72)
|
|
|
|
section("messaggi annidati e risposte")
|
|
for name in ("Uuid", "YearMonth", "LocalDateTime", "CopEntry", "GetCopResponse",
|
|
"MonthlySavings", "GetSavingsResponse", "RollingMonthOptimisation",
|
|
"GetOptimisationSavingsResponse", "InsightEntry", "GetHeatPumpInsightsResponse"):
|
|
cls = getattr(S, name, None)
|
|
if cls is None:
|
|
print(f"{name}: assente")
|
|
continue
|
|
try:
|
|
fs = cls.DESCRIPTOR.fields
|
|
print(f"{name}: " + ", ".join(
|
|
f"{f.name}(type={f.type}, msg={f.message_type and f.message_type.name}, "
|
|
f"enum={f.enum_type and f.enum_type.name})" for f in fs))
|
|
except Exception as exc: # noqa: BLE001
|
|
print(f"{name}: introspezione fallita ({type(exc).__name__}: {exc})")
|
|
|
|
# --- login ----------------------------------------------------------------- #
|
|
tokens = json.loads((CFG / "aira_tokens.json").read_text())
|
|
state = json.loads(Path("/tmp/aira_state.json").read_text())["state"]
|
|
hp = state["heat_pump_id"]["value"]
|
|
|
|
aira = AiraHome()
|
|
aira.cloud.login_with_tokens(tokens["id_token"], tokens["access_token"], tokens["refresh_token"])
|
|
stub = aira.cloud._stats_service_stub # noqa: SLF001
|
|
call = aira.cloud.call_service
|
|
print(f"\nheat_pump_id: {hp}")
|
|
|
|
now = datetime.now()
|
|
|
|
def show(label, method, req):
|
|
section(label)
|
|
try:
|
|
out = call(stub, method, req, timeout=25, raw=True)
|
|
print(str(out).replace("\n", " | ")[:1500] or "(risposta vuota)")
|
|
except Exception as exc: # noqa: BLE001
|
|
print(f"ERRORE {type(exc).__name__}: {str(exc)[:400]}")
|
|
|
|
# COP mensile, ultimi 6 mesi
|
|
req = S.GetCopRequest()
|
|
req.heat_pump_id.value = hp
|
|
req.granularity = S.COP_GRANULARITY_MONTHLY
|
|
start = now - timedelta(days=180)
|
|
req.start_date.year, req.start_date.month = start.year, start.month
|
|
req.end_date_exclusive.year, req.end_date_exclusive.month = now.year, now.month + 1 if now.month < 12 else 1
|
|
if now.month == 12:
|
|
req.end_date_exclusive.year = now.year + 1
|
|
show("GetCop mensile (6 mesi) -> efficienza", "GetCop", req)
|
|
|
|
# Risparmi: periodo 180 giorni
|
|
from google.protobuf.timestamp_pb2 import Timestamp
|
|
req = S.GetSavingsRequest()
|
|
req.heat_pump_id.value = hp
|
|
ts = Timestamp(); ts.FromDatetime(start); req.start_time.CopyFrom(ts)
|
|
te = Timestamp(); te.FromDatetime(now); req.end_time.CopyFrom(te)
|
|
show("GetSavings (180 giorni) -> risparmi", "GetSavings", req)
|
|
|
|
# Risparmi Smart Tariff Control (per nucleo familiare)
|
|
household = None
|
|
try:
|
|
devs = aira.cloud.get_devices()
|
|
print("\nget_devices ->", str(devs)[:400])
|
|
def find_hh(o):
|
|
if isinstance(o, dict):
|
|
for k, v in o.items():
|
|
if k == "household_id" and v:
|
|
return v if isinstance(v, str) else v.get("value")
|
|
r = find_hh(v)
|
|
if r:
|
|
return r
|
|
elif isinstance(o, list):
|
|
for v in o:
|
|
r = find_hh(v)
|
|
if r:
|
|
return r
|
|
return None
|
|
household = find_hh(devs)
|
|
print("household_id:", household)
|
|
except Exception as exc: # noqa: BLE001
|
|
print("get_devices fallita:", type(exc).__name__, str(exc)[:200])
|
|
|
|
if household:
|
|
req = S.GetOptimisationSavingsRequest()
|
|
req.household_id.value = household
|
|
show("GetOptimisationSavings -> risparmio Smart Tariff Control", "GetOptimisationSavings", req)
|
|
|
|
# Insight giornalieri, ultimi 7 giorni (LocalDateTime)
|
|
req = S.GetHeatPumpInsightsRequest()
|
|
req.heat_pump_id.value = hp
|
|
req.granularity = S.GRANULARITY_DAILY
|
|
s = now - timedelta(days=7)
|
|
for m in (req.start_time, req.end_time_exclusive):
|
|
pass
|
|
req.start_time.year, req.start_time.month, req.start_time.day = s.year, s.month, s.day
|
|
req.end_time_exclusive.year, req.end_time_exclusive.month, req.end_time_exclusive.day = now.year, now.month, now.day
|
|
show("GetHeatPumpInsights giornaliero (7 giorni)", "GetHeatPumpInsights", req)
|
|
|
|
aira.cloud.close()
|
|
print("\nsonda conclusa: nessun comando inviato all'impianto.")
|