Browse Source

Sum the lifetime counter for REST plugs in all-time energy stats (#3232) (#3233)

Rojo 2 days ago
parent
commit
ef029ad9eb
2 changed files with 52 additions and 2 deletions
  1. 11 2
      backend/app/api/routes/archives.py
  2. 41 0
      backend/tests/unit/test_energy_snapshots.py

+ 11 - 2
backend/app/api/routes/archives.py

@@ -1448,9 +1448,18 @@ async def _sum_live_plug_totals(db: AsyncSession) -> float:
             if mqtt_data and mqtt_data.energy is not None:
             if mqtt_data and mqtt_data.energy is not None:
                 total += mqtt_data.energy
                 total += mqtt_data.energy
         elif plug.plug_type == "rest":
         elif plug.plug_type == "rest":
+            # A REST device that exposes only a lifetime counter (Shelly
+            # ``aenergy.total`` via ``rest_energy_total_path``) returns no
+            # ``today`` key, so read the lifetime total first, like the
+            # Tasmota/HA branches above. ``today`` stays as the fallback for
+            # devices that only have a daily counter.
             energy = await rest_smart_plug_service.get_energy(plug)
             energy = await rest_smart_plug_service.get_energy(plug)
-            if energy and energy.get("today") is not None:
-                total += energy["today"]
+            if energy:
+                value = energy.get("total")
+                if value is None:
+                    value = energy.get("today")
+                if value is not None:
+                    total += value
     return total
     return total
 
 
 
 

+ 41 - 0
backend/tests/unit/test_energy_snapshots.py

@@ -197,3 +197,44 @@ class TestPerPrintRestartResilience:
         reloaded = result.scalar_one()
         reloaded = result.scalar_one()
         assert reloaded.energy_kwh == pytest.approx(3.4)
         assert reloaded.energy_kwh == pytest.approx(3.4)
         assert reloaded.energy_cost == pytest.approx(1.02)
         assert reloaded.energy_cost == pytest.approx(1.02)
+
+
+class TestSumLivePlugTotalsRest:
+    """#3232 — all-time total-consumption mode read ``today`` for REST plugs.
+
+    A REST device that exposes only a lifetime counter (Shelly ``aenergy.total``
+    via ``rest_energy_total_path``) has no ``today`` key, so the unfiltered
+    Statistics total stayed at 0 while the date-filtered path worked.
+    """
+
+    @pytest.mark.asyncio
+    async def test_rest_plug_lifetime_total_is_summed(self, db_session, smart_plug_factory, monkeypatch):
+        from backend.app.api.routes.archives import _sum_live_plug_totals
+        from backend.app.services import rest_smart_plug
+
+        await smart_plug_factory(name="Shelly", plug_type="rest")
+
+        async def fake_get_energy(_plug):
+            return {"power": 11.0, "total": 5.802}
+
+        monkeypatch.setattr(rest_smart_plug.rest_smart_plug_service, "get_energy", fake_get_energy)
+
+        total = await _sum_live_plug_totals(db_session)
+
+        assert total == pytest.approx(5.802)
+
+    @pytest.mark.asyncio
+    async def test_rest_plug_falls_back_to_today_without_lifetime(self, db_session, smart_plug_factory, monkeypatch):
+        from backend.app.api.routes.archives import _sum_live_plug_totals
+        from backend.app.services import rest_smart_plug
+
+        await smart_plug_factory(name="DailyOnly", plug_type="rest")
+
+        async def fake_get_energy(_plug):
+            return {"power": 11.0, "today": 0.4}
+
+        monkeypatch.setattr(rest_smart_plug.rest_smart_plug_service, "get_energy", fake_get_energy)
+
+        total = await _sum_live_plug_totals(db_session)
+
+        assert total == pytest.approx(0.4)