Skip to content

Commit 8d7e5d3

Browse files
authored
Implement smart fallback for cumulative energy sensor selection and add unit tests (#89)
1 parent fdfeca6 commit 8d7e5d3

2 files changed

Lines changed: 401 additions & 7 deletions

File tree

custom_components/enpal_webparser/sensor.py

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,49 @@ async def async_update_data():
118118
entities.append(build_sensor_entity(sensor_dict, coordinator))
119119

120120

121-
entities.append(CumulativeEnergySensor(hass, coordinator, "Inverter Power DC Total (Huawei)", interval))
121+
# Create cumulative energy sensor with smart fallback for different inverter types
122+
123+
source_sensor = None
124+
available_sensor_names = [s.get("name", "") for s in coordinator.data]
125+
126+
# Priority 1: Try Huawei-specific sensor first
127+
if "Inverter: Power DC Total (Huawei)" in available_sensor_names:
128+
source_sensor = "Inverter: Power DC Total (Huawei)"
129+
_LOGGER.info("[Enpal] Using Huawei-specific DC power sensor: %s", source_sensor)
130+
131+
# Priority 2: Try other manufacturer-specific sensors (SMA, Fronius, etc.)
132+
# Look for pattern: "Inverter: Power DC Total (<Manufacturer>)"
133+
if not source_sensor:
134+
for name in available_sensor_names:
135+
name_id = make_id(name)
136+
# Match manufacturer-specific format but exclude Calculated
137+
if (name_id.startswith("inverter_power_dc_total_") and
138+
name_id != "inverter_power_dc_total_calculated" and
139+
name_id != "inverter_power_dc_total"):
140+
source_sensor = name
141+
_LOGGER.info("[Enpal] Found manufacturer-specific DC power sensor: %s", name)
142+
break
143+
144+
# Priority 3: Try generic "Inverter: Power DC Total"
145+
if not source_sensor and "Inverter: Power DC Total" in available_sensor_names:
146+
source_sensor = "Inverter: Power DC Total"
147+
_LOGGER.info("[Enpal] Using generic DC power sensor: %s", source_sensor)
148+
149+
# Priority 4: Last resort - use calculated sensor
150+
if not source_sensor and "Inverter: Power DC Total Calculated" in available_sensor_names:
151+
source_sensor = "Inverter: Power DC Total Calculated"
152+
_LOGGER.warning("[Enpal] Using calculated DC power sensor (least accurate): %s", source_sensor)
153+
154+
# Final fallback: If nothing found, use Huawei as default (will trigger warning)
155+
if not source_sensor:
156+
source_sensor = "Inverter: Power DC Total (Huawei)"
157+
_LOGGER.warning(
158+
"[Enpal] No DC power sensor found. Available power sensors: %s. Using fallback: %s",
159+
", ".join([make_id(n) for n in available_sensor_names if "power" in make_id(n).lower()]),
160+
source_sensor
161+
)
162+
163+
entities.append(CumulativeEnergySensor(hass, coordinator, [source_sensor], interval))
122164
entities.append(DailyResetFromEntitySensor(hass, "sensor.inverter_energy_produced_total_dc"))
123165

124166
if entry.options.get("use_wallbox_addon", False):
@@ -165,7 +207,7 @@ async def async_wallbox_update():
165207

166208

167209
class CumulativeEnergySensor(SensorEntity, RestoreEntity):
168-
def __init__(self, hass: HomeAssistant, coordinator: DataUpdateCoordinator, sensor_name: str, interval_seconds: int):
210+
def __init__(self, hass: HomeAssistant, coordinator: DataUpdateCoordinator, sensor_names: list[str], interval_seconds: int):
169211
self.hass = hass
170212
self._attr_name = str("Inverter: Energy produced total (DC)")
171213
self._attr_unique_id = str("cumulative_energy_produced_dc_kwh")
@@ -174,7 +216,8 @@ def __init__(self, hass: HomeAssistant, coordinator: DataUpdateCoordinator, sens
174216
self._attr_native_unit_of_measurement = "kWh"
175217
self._attr_icon = "mdi:solar-power"
176218
self._coordinator = coordinator
177-
self._source_uid = make_id(sensor_name)
219+
self._source_candidates = [make_id(name) for name in sensor_names]
220+
self._active_source_uid = None # Will be determined from available sensors
178221
self._interval_hours = interval_seconds / 3600
179222
self._state = self.hass.data[DOMAIN]["cumulative_energy_state"]
180223
self._value = None
@@ -187,7 +230,8 @@ def native_value(self) -> StateType:
187230
@property
188231
def extra_state_attributes(self) -> dict:
189232
return {
190-
"last_updated": self._last_updated
233+
"last_updated": self._last_updated,
234+
"source_sensor": self._active_source_uid if self._active_source_uid else "Not determined"
191235
}
192236

193237
async def async_added_to_hass(self):
@@ -204,17 +248,34 @@ async def async_added_to_hass(self):
204248
self._coordinator.async_add_listener(self._handle_coordinator_update)
205249

206250
def _handle_coordinator_update(self):
251+
# If we haven't determined the active source yet, find the first available one
252+
if self._active_source_uid is None:
253+
available_sensors = {make_id(s["name"]) for s in self._coordinator.data}
254+
for candidate in self._source_candidates:
255+
if candidate in available_sensors:
256+
self._active_source_uid = candidate
257+
_LOGGER.info("[Enpal] Using DC power sensor: %s", candidate)
258+
break
259+
260+
if self._active_source_uid is None:
261+
_LOGGER.warning(
262+
"[Enpal] No suitable DC power sensor found. Tried: %s",
263+
", ".join(self._source_candidates)
264+
)
265+
self.async_write_ha_state()
266+
return
267+
268+
# Now process the update with the active source
207269
for sensor in self._coordinator.data:
208-
_LOGGER.debug("[Enpal] Checking Sensor: %s (%s)", sensor["name"], make_id(sensor["name"]))
209-
if make_id(sensor["name"]) == self._source_uid:
270+
if make_id(sensor["name"]) == self._active_source_uid:
210271
try:
211272
power_watt = float(sensor["value"])
212273
energy_kwh = power_watt * self._interval_hours / 1000
213274
if self._value is None:
214275
self._value = 0.0
215276
self._value += energy_kwh
216277
self._last_updated = datetime.now().isoformat()
217-
_LOGGER.debug("[Enpal] +%.5f kWh -> Total: %.3f", energy_kwh, self._state["value"])
278+
_LOGGER.debug("[Enpal] +%.5f kWh -> Total: %.3f kWh", energy_kwh, self._value)
218279
except Exception as e:
219280
_LOGGER.warning("[Enpal] Error in energy calculation: %s", e)
220281
break

0 commit comments

Comments
 (0)