|
1 | | -"""Checks energy site listener filtering against PR 310's SSE event shapes. |
| 1 | +"""Checks energy site listener filtering against PR 310/316's SSE event shapes. |
2 | 2 |
|
3 | 3 | Fixtures mirror the `liveStatusSchema`/`siteInfoSchema` from Teslemetry/api |
4 | 4 | PR 310: a flat envelope of `createdAt`, `site_id`, optional `isCache`, and |
5 | 5 | the full document under `live_status`/`site_info` (opaque, not a delta). |
| 6 | +`energy_totals` fixtures mirror PR 316's notification schema (renamed |
| 7 | +from `calendar_history_refreshed` to `energy_totals` post-merge): the |
| 8 | +uniform notification shape |
| 9 | +(`id`/`product_type`/`topic`/`url`/`createdAt`/`isCache`) plus a compact |
| 10 | +`totals` object - no `site_id` key, the site id rides `id` instead. |
6 | 11 | """ |
7 | 12 | from __future__ import annotations |
8 | 13 |
|
9 | 14 | from typing import Any |
10 | 15 |
|
| 16 | +from teslemetry_stream.const import EnergyHistoryTotals |
11 | 17 | from teslemetry_stream.stream import TeslemetryStream, recursive_match |
12 | 18 |
|
13 | 19 | SITE_A = "12345" |
|
55 | 61 | "live_status": {"battery_power": 0}, |
56 | 62 | } |
57 | 63 |
|
| 64 | +ENERGY_TOTALS_FIXTURE: dict[str, float | None] = { |
| 65 | + "solar_energy_exported": 12.3, |
| 66 | + "generator_energy_exported": None, |
| 67 | + "grid_energy_imported": 4.5, |
| 68 | + "grid_services_energy_imported": None, |
| 69 | + "grid_services_energy_exported": None, |
| 70 | + "grid_energy_exported_from_solar": None, |
| 71 | + "grid_energy_exported_from_generator": None, |
| 72 | + "grid_energy_exported_from_battery": None, |
| 73 | + "battery_energy_exported": 1.1, |
| 74 | + "battery_energy_imported_from_grid": None, |
| 75 | + "battery_energy_imported_from_solar": None, |
| 76 | + "battery_energy_imported_from_generator": None, |
| 77 | + "consumer_energy_imported_from_grid": None, |
| 78 | + "consumer_energy_imported_from_solar": None, |
| 79 | + "consumer_energy_imported_from_battery": None, |
| 80 | + "consumer_energy_imported_from_generator": None, |
| 81 | + "total_home_usage": 20.0, |
| 82 | + "total_battery_charge": None, |
| 83 | + "total_battery_discharge": None, |
| 84 | + "total_solar_generation": 12.3, |
| 85 | + "total_grid_energy_exported": None, |
| 86 | +} |
| 87 | + |
| 88 | +ENERGY_TOTALS_EVENT: dict[str, Any] = { |
| 89 | + "id": SITE_A, |
| 90 | + "product_type": "energy_site", |
| 91 | + "topic": "energy_totals", |
| 92 | + "url": f"/api/1/energy_sites/{SITE_A}/calendar_history?kind=energy&period=day", |
| 93 | + "createdAt": "2026-07-29T10:16:00.000Z", |
| 94 | + "isCache": False, |
| 95 | + "totals": ENERGY_TOTALS_FIXTURE, |
| 96 | +} |
| 97 | + |
| 98 | +OTHER_SITE_ENERGY_TOTALS: dict[str, Any] = { |
| 99 | + **ENERGY_TOTALS_EVENT, |
| 100 | + "id": SITE_B, |
| 101 | +} |
| 102 | + |
58 | 103 | CREDITS_EVENT: dict[str, Any] = { |
59 | 104 | "credits": {"type": "snapshot", "cost": 0, "name": "snapshot", "balance": 5, "quota": {}}, |
60 | 105 | "createdAt": "2026-07-28T10:16:00.000Z", |
@@ -139,6 +184,48 @@ def main() -> None: |
139 | 184 | ) |
140 | 185 | ) |
141 | 186 |
|
| 187 | + # listen_EnergyTotals receives the parsed totals dataclass. |
| 188 | + stream = make_stream() |
| 189 | + site = stream.get_energysite(SITE_A) |
| 190 | + totals_received: list[EnergyHistoryTotals] = [] |
| 191 | + site.listen_EnergyTotals(totals_received.append) |
| 192 | + dispatch(stream, ENERGY_TOTALS_EVENT) |
| 193 | + results.append( |
| 194 | + check( |
| 195 | + "listen_EnergyTotals parses the totals dict", |
| 196 | + totals_received == [EnergyHistoryTotals(**ENERGY_TOTALS_FIXTURE)], |
| 197 | + f"got {totals_received}", |
| 198 | + ) |
| 199 | + ) |
| 200 | + |
| 201 | + # An energy_totals event for another site is not delivered. |
| 202 | + stream = make_stream() |
| 203 | + site = stream.get_energysite(SITE_A) |
| 204 | + totals_received = [] |
| 205 | + site.listen_EnergyTotals(totals_received.append) |
| 206 | + dispatch(stream, OTHER_SITE_ENERGY_TOTALS) |
| 207 | + results.append( |
| 208 | + check( |
| 209 | + "a different site's energy_totals is filtered out", |
| 210 | + totals_received == [], |
| 211 | + f"got {totals_received}", |
| 212 | + ) |
| 213 | + ) |
| 214 | + |
| 215 | + # listen_LiveStatus does not receive energy_totals events. |
| 216 | + stream = make_stream() |
| 217 | + site = stream.get_energysite(SITE_A) |
| 218 | + received = [] |
| 219 | + site.listen_LiveStatus(received.append) |
| 220 | + dispatch(stream, ENERGY_TOTALS_EVENT) |
| 221 | + results.append( |
| 222 | + check( |
| 223 | + "listen_LiveStatus ignores energy_totals events", |
| 224 | + received == [], |
| 225 | + f"got {received}", |
| 226 | + ) |
| 227 | + ) |
| 228 | + |
142 | 229 | # Unrelated account-wide events (e.g. credits) are not delivered to energy listeners. |
143 | 230 | stream = make_stream() |
144 | 231 | site = stream.get_energysite(SITE_A) |
|
0 commit comments