ソースを参照

fix: surface a clear warning when a device has no energy-flow rule configured (#26)

Issue #21's original report was "no sensors, no warnings" on a device whose
/energy/flow/v1 capture returns code=70132 "Energy flow rule not exists".
That error fell into the same silent DEBUG-level except clause as a merely
unsupported/404 endpoint, so a portal-side setup gap looked identical to a
bug in this integration. It now logs a WARNING naming the actual cause.
Bob Ehlers 1 ヶ月 前
親
コミット
dde9b05b95

+ 12 - 0
CHANGELOG.md

@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ---
 
+## [2.6.1] - 2026-09-15
+
+### Fixed
+- **A device with no energy-flow rule configured on the portal produced no
+  sensors and no warning**, which was indistinguishable from a bug in this
+  integration. The portal's `code=70132 "Energy flow rule not exists"`
+  response fell into the same silent DEBUG-level handling as a merely
+  unsupported/404 endpoint. That specific error now logs a clear WARNING
+  explaining the fix is a portal-side setup step (configuring an
+  energy-flow rule for the device on solar.siseli.com), not something a
+  future field mapping can resolve. Reported in #21.
+
 ## [2.6.0] - 2026-09-15
 
 ### Added

+ 33 - 5
custom_components/solar_of_things/api.py

@@ -120,6 +120,18 @@ class AuthenticationError(Exception):
     """Raised when login credentials are rejected by the server."""
 
 
+class EnergyFlowRuleNotConfiguredError(RuntimeError):
+    """Raised when the portal returns code 70132 ("Energy flow rule not exists").
+
+    This means the device's *account* has no energy-flow rule configured on
+    the portal side — a per-device setup step on solar.siseli.com, not
+    something this integration can fix by mapping a different field. Reported
+    in issue #21: without this, the fallback failing this way looked
+    identical to "no data returned", so a device in this state got no
+    sensors and no warning either.
+    """
+
+
 # ──────────────────────────────────────────────────────────────────────────────
 # Signing helpers  (reverse-engineered from portal umi.js)
 # ──────────────────────────────────────────────────────────────────────────────
@@ -729,6 +741,19 @@ class SolarOfThingsAPI:
             except TokenExpiredError:
                 # Must reach the coordinator so it can start the re-auth flow.
                 raise
+            except EnergyFlowRuleNotConfiguredError as err:
+                # A portal-side setup gap, not something a field mapping can
+                # fix — but "no sensors, no warnings" (issue #21) looked
+                # identical to a bug, so this needs to be loud by default.
+                _LOGGER.warning(
+                    "SolarOfThings device %s: no energy-flow rule is configured "
+                    "for this device in the Siseli portal (%s). Realtime sensors "
+                    "will stay unavailable until an energy-flow rule is set up "
+                    "for it there, or its historical time-series endpoint starts "
+                    "returning data instead.",
+                    device_id,
+                    err,
+                )
             except Exception as err:
                 _LOGGER.debug(
                     "SolarOfThings device %s: energy-flow fallback unavailable: %s",
@@ -854,11 +879,14 @@ class SolarOfThingsAPI:
         """
         data = self._get(API_ENERGY_FLOW, {"deviceId": device_id, "dataSource": 1})
 
-        if data.get("code") not in (0, None, "0"):
-            raise RuntimeError(
-                f"Energy-flow error code={data.get('code')} "
-                f"message={data.get('message') or data.get('msg')}"
-            )
+        code = data.get("code")
+        if code not in (0, None, "0"):
+            message = data.get("message") or data.get("msg")
+            if code in (70132, "70132"):
+                raise EnergyFlowRuleNotConfiguredError(
+                    f"Energy-flow error code={code} message={message}"
+                )
+            raise RuntimeError(f"Energy-flow error code={code} message={message}")
 
         payload = data.get("data") or {}
         state = payload.get("deviceAttributeState") or {}

+ 1 - 1
custom_components/solar_of_things/manifest.json

@@ -11,5 +11,5 @@
   "requirements": [
     "pycryptodome>=3.9.0"
   ],
-  "version": "2.6.0"
+  "version": "2.6.1"
 }

+ 19 - 0
tests/test_energy_flow.py

@@ -16,6 +16,7 @@ from __future__ import annotations
 import pytest
 
 from custom_components.solar_of_things.api import (
+    EnergyFlowRuleNotConfiguredError,
     SolarOfThingsAPI,
     TokenExpiredError,
     has_realtime_values,
@@ -366,6 +367,24 @@ def test_token_expiry_propagates_for_reauth(api_factory) -> None:
         api.fetch_latest_data("device-6")
 
 
+def test_energy_flow_rule_not_configured_logs_a_warning(api_factory, caplog) -> None:
+    """Issue #21: this failure mode looked identical to "no data" — no
+    sensors AND no warning — because it fell into the same silent DEBUG-level
+    except clause as an unsupported/404 endpoint. It must be loud by default,
+    since the fix (configuring an energy-flow rule on the portal) is on the
+    user/installer's side, not something a future field mapping can resolve.
+    """
+    api, _ = api_factory(
+        {}, flow_error=EnergyFlowRuleNotConfiguredError("code=70132 message=Energy flow rule not exists")
+    )
+    with caplog.at_level("WARNING"):
+        result = api.fetch_latest_data("device-8")
+
+    assert isinstance(result, dict)  # still degrades quietly, not a raised error
+    assert any(record.levelname == "WARNING" for record in caplog.records)
+    assert "energy-flow rule" in caplog.text.lower()
+
+
 def test_unknown_firmware_logs_field_names_for_reporting(api_factory, caplog) -> None:
     api, _ = api_factory({}, flow_fields={"someBrandNewKey": 5, "another": 7})
     api.fetch_latest_data("device-7")