0.5.0: riordino come l'app ufficiale (home, Dati, Impianto)
- 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
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
#!/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.")
|
||||
Reference in New Issue
Block a user