Skip to content

Commit 0c9c328

Browse files
authored
Safeguard for calculated sensors. (#84)
* Added calculated current sensors * Added safeguard if sensors show up again.
1 parent 8b18d07 commit 0c9c328

2 files changed

Lines changed: 209 additions & 1 deletion

File tree

custom_components/enpal_webparser/tests/test_current_calculation.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,181 @@ def test_calculate_current_sensors_non_powersensor_group():
166166
# Should have only the original sensors (no calculation for non-PowerSensor group)
167167
assert len(result) == 2
168168
assert not any("Current Phase" in s["name"] for s in result)
169+
170+
171+
def test_calculate_current_sensors_already_provided():
172+
"""Test that calculation is skipped if current sensors already exist."""
173+
sensors = [
174+
# Power sensors
175+
{
176+
"name": "PowerSensor: Power AC Phase (A)",
177+
"value": "-61",
178+
"unit": "W",
179+
"group": "PowerSensor",
180+
"enabled": True,
181+
"enpal_last_update": "2025-10-31T09:57:14.635Z",
182+
},
183+
{
184+
"name": "PowerSensor: Power AC Phase (B)",
185+
"value": "-19",
186+
"unit": "W",
187+
"group": "PowerSensor",
188+
"enabled": True,
189+
"enpal_last_update": "2025-10-31T09:57:14.635Z",
190+
},
191+
{
192+
"name": "PowerSensor: Power AC Phase (C)",
193+
"value": "77",
194+
"unit": "W",
195+
"group": "PowerSensor",
196+
"enabled": True,
197+
"enpal_last_update": "2025-10-31T09:57:14.636Z",
198+
},
199+
# Voltage sensors
200+
{
201+
"name": "PowerSensor: Voltage Phase (A)",
202+
"value": "231.1",
203+
"unit": "V",
204+
"group": "PowerSensor",
205+
"enabled": True,
206+
"enpal_last_update": "2025-10-31T09:57:14.634Z",
207+
},
208+
{
209+
"name": "PowerSensor: Voltage Phase (B)",
210+
"value": "230.1",
211+
"unit": "V",
212+
"group": "PowerSensor",
213+
"enabled": True,
214+
"enpal_last_update": "2025-10-31T09:57:14.634Z",
215+
},
216+
{
217+
"name": "PowerSensor: Voltage Phase (C)",
218+
"value": "230.3",
219+
"unit": "V",
220+
"group": "PowerSensor",
221+
"enabled": True,
222+
"enpal_last_update": "2025-10-31T09:57:14.634Z",
223+
},
224+
# Current sensors ALREADY PROVIDED by Enpal
225+
{
226+
"name": "PowerSensor: Current Phase (A)",
227+
"value": "1.5",
228+
"unit": "A",
229+
"group": "PowerSensor",
230+
"enabled": True,
231+
"enpal_last_update": "2025-10-31T09:57:14.635Z",
232+
},
233+
{
234+
"name": "PowerSensor: Current Phase (B)",
235+
"value": "1.2",
236+
"unit": "A",
237+
"group": "PowerSensor",
238+
"enabled": True,
239+
"enpal_last_update": "2025-10-31T09:57:14.635Z",
240+
},
241+
{
242+
"name": "PowerSensor: Current Phase (C)",
243+
"value": "1.8",
244+
"unit": "A",
245+
"group": "PowerSensor",
246+
"enabled": True,
247+
"enpal_last_update": "2025-10-31T09:57:14.636Z",
248+
},
249+
]
250+
251+
result = add_calculated_current_sensors(sensors)
252+
253+
# Should have exactly 9 sensors (no duplicates added)
254+
assert len(result) == 9
255+
256+
# Find current sensors
257+
current_sensors = [s for s in result if "Current Phase" in s["name"]]
258+
assert len(current_sensors) == 3
259+
260+
# Verify these are the ORIGINAL sensors (not calculated)
261+
# The original values should be preserved (1.5, 1.2, 1.8 A)
262+
current_a = next(s for s in current_sensors if "Phase (A)" in s["name"])
263+
assert float(current_a["value"]) == 1.5
264+
265+
266+
def test_calculate_current_sensors_partial_provided():
267+
"""Test that calculation only adds missing current sensors."""
268+
sensors = [
269+
# Power and voltage for all phases
270+
{
271+
"name": "PowerSensor: Power AC Phase (A)",
272+
"value": "-61",
273+
"unit": "W",
274+
"group": "PowerSensor",
275+
"enabled": True,
276+
"enpal_last_update": "2025-10-31T09:57:14.635Z",
277+
},
278+
{
279+
"name": "PowerSensor: Voltage Phase (A)",
280+
"value": "231.1",
281+
"unit": "V",
282+
"group": "PowerSensor",
283+
"enabled": True,
284+
"enpal_last_update": "2025-10-31T09:57:14.634Z",
285+
},
286+
{
287+
"name": "PowerSensor: Power AC Phase (B)",
288+
"value": "-19",
289+
"unit": "W",
290+
"group": "PowerSensor",
291+
"enabled": True,
292+
"enpal_last_update": "2025-10-31T09:57:14.635Z",
293+
},
294+
{
295+
"name": "PowerSensor: Voltage Phase (B)",
296+
"value": "230.1",
297+
"unit": "V",
298+
"group": "PowerSensor",
299+
"enabled": True,
300+
"enpal_last_update": "2025-10-31T09:57:14.634Z",
301+
},
302+
{
303+
"name": "PowerSensor: Power AC Phase (C)",
304+
"value": "77",
305+
"unit": "W",
306+
"group": "PowerSensor",
307+
"enabled": True,
308+
"enpal_last_update": "2025-10-31T09:57:14.636Z",
309+
},
310+
{
311+
"name": "PowerSensor: Voltage Phase (C)",
312+
"value": "230.3",
313+
"unit": "V",
314+
"group": "PowerSensor",
315+
"enabled": True,
316+
"enpal_last_update": "2025-10-31T09:57:14.634Z",
317+
},
318+
# Only Phase A current sensor is provided
319+
{
320+
"name": "PowerSensor: Current Phase (A)",
321+
"value": "1.5",
322+
"unit": "A",
323+
"group": "PowerSensor",
324+
"enabled": True,
325+
"enpal_last_update": "2025-10-31T09:57:14.635Z",
326+
},
327+
]
328+
329+
result = add_calculated_current_sensors(sensors)
330+
331+
# Should have 7 original + 2 calculated (B and C) = 9 sensors
332+
assert len(result) == 9
333+
334+
# Find all current sensors
335+
current_sensors = [s for s in result if "Current Phase" in s["name"]]
336+
assert len(current_sensors) == 3
337+
338+
# Phase A should be the original (1.5 A)
339+
current_a = next(s for s in current_sensors if "Phase (A)" in s["name"])
340+
assert float(current_a["value"]) == 1.5
341+
342+
# Phase B and C should be calculated
343+
current_b = next(s for s in current_sensors if "Phase (B)" in s["name"])
344+
current_c = next(s for s in current_sensors if "Phase (C)" in s["name"])
345+
assert float(current_b["value"]) == pytest.approx(-0.08, abs=0.01)
346+
assert float(current_c["value"]) == pytest.approx(0.33, abs=0.01)

custom_components/enpal_webparser/utils.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,12 +328,38 @@ def add_calculated_current_sensors(sensors: List[Dict[str, Any]]) -> List[Dict[s
328328
Enpal boxes no longer provide Current.Phase.A/B/C sensors directly.
329329
This function calculates them using Ohm's law: I = P / U
330330
331+
If the current sensors are already provided by the Enpal box, this function
332+
does nothing to avoid duplicates.
333+
331334
Args:
332335
sensors: List of parsed sensors
333336
334337
Returns:
335-
Updated sensor list with calculated current sensors added
338+
Updated sensor list with calculated current sensors added (if missing)
336339
"""
340+
# First, check if current sensors already exist
341+
existing_current_sensors = set()
342+
for sensor in sensors:
343+
name = sensor.get("name", "")
344+
group = sensor.get("group", "")
345+
346+
if group == "PowerSensor":
347+
sensor_id = make_id(name)
348+
# Check for existing current sensors
349+
if "current_phase_a" in sensor_id:
350+
existing_current_sensors.add("a")
351+
elif "current_phase_b" in sensor_id:
352+
existing_current_sensors.add("b")
353+
elif "current_phase_c" in sensor_id:
354+
existing_current_sensors.add("c")
355+
356+
# If all current sensors already exist, skip calculation
357+
if len(existing_current_sensors) == 3:
358+
_LOGGER.debug(
359+
"[Enpal] PowerSensor current sensors already provided by Enpal box, skipping calculation"
360+
)
361+
return sensors
362+
337363
# Find power and voltage values for each phase
338364
power_sensors = {}
339365
voltage_sensors = {}
@@ -367,6 +393,10 @@ def add_calculated_current_sensors(sensors: List[Dict[str, Any]]) -> List[Dict[s
367393
# Calculate current for each phase where both power and voltage are available
368394
calculated_count = 0
369395
for phase in ["a", "b", "c"]:
396+
# Skip if this phase's current sensor already exists
397+
if phase in existing_current_sensors:
398+
continue
399+
370400
power_sensor = power_sensors.get(phase)
371401
voltage_sensor = voltage_sensors.get(phase)
372402

0 commit comments

Comments
 (0)