Skip to content

Commit 967be80

Browse files
committed
Enhance UPS energy sensor to restore previous state and improve energy calculation logic
1 parent 237ca22 commit 967be80

File tree

1 file changed

+106
-21
lines changed
  • custom_components/unraid/sensors

1 file changed

+106
-21
lines changed

custom_components/unraid/sensors/ups.py

Lines changed: 106 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -218,10 +218,16 @@ def __init__(self, coordinator) -> None:
218218
self._last_power: float | None = None
219219
self._last_update: dt_util.datetime | None = None
220220
self._total_energy: float = 0.0
221+
self._initialized: bool = False
221222

222223
def _get_server_energy_usage(self, data: dict) -> float | None:
223224
"""Get cumulative server energy usage from UPS."""
224225
try:
226+
# Initialize from previous state if not done yet
227+
if not self._initialized:
228+
self._restore_state()
229+
self._initialized = True
230+
225231
ups_info = data.get("system_stats", {}).get("ups_info", {})
226232
nominal_power = self._validate_ups_metric(
227233
"NOMPOWER",
@@ -235,44 +241,123 @@ def _get_server_energy_usage(self, data: dict) -> float | None:
235241
current_power = self._calculate_power(nominal_power, load_percent)
236242
current_time = dt_util.now()
237243

238-
if current_power is not None and self._last_power is not None and self._last_update is not None:
239-
# Calculate time difference in hours
240-
time_diff = (current_time - self._last_update).total_seconds() / 3600.0
241-
242-
# Calculate average power during this period
243-
avg_power = (current_power + self._last_power) / 2.0
244-
245-
# Calculate energy consumed (kWh)
246-
energy_consumed = (avg_power * time_diff) / 1000.0 # Convert W*h to kWh
247-
248-
# Add to total energy
249-
self._total_energy += energy_consumed
244+
# Only calculate energy if we have valid current power
245+
if current_power is not None:
246+
# If we have previous data, calculate energy consumed since last update
247+
if self._last_power is not None and self._last_update is not None:
248+
# Calculate time difference in hours
249+
time_diff = (current_time - self._last_update).total_seconds() / 3600.0
250+
251+
# Only calculate if time difference is reasonable (minimum 10 seconds, maximum 2 hours)
252+
if 0.0028 <= time_diff <= 2.0: # 10 seconds to 2 hours
253+
# Calculate average power during this period
254+
avg_power = (current_power + self._last_power) / 2.0
255+
256+
# Calculate energy consumed (kWh)
257+
energy_consumed = (avg_power * time_diff) / 1000.0 # Convert W*h to kWh
258+
259+
# Add to total energy
260+
self._total_energy += energy_consumed
261+
262+
_LOGGER.debug(
263+
"UPS energy calculation: avg_power=%.2fW, time_diff=%.4fh, "
264+
"energy_consumed=%.6fkWh, total_energy=%.6fkWh",
265+
avg_power, time_diff, energy_consumed, self._total_energy
266+
)
267+
elif time_diff > 0:
268+
# For very small time differences, still accumulate but with minimal energy
269+
if time_diff < 0.0028: # Less than 10 seconds
270+
# Use minimum time difference to avoid division by zero but still accumulate
271+
min_time_diff = 0.0028 # 10 seconds
272+
avg_power = (current_power + self._last_power) / 2.0
273+
energy_consumed = (avg_power * min_time_diff) / 1000.0
274+
self._total_energy += energy_consumed
275+
276+
_LOGGER.debug(
277+
"UPS energy calculation (short interval): avg_power=%.2fW, "
278+
"actual_time_diff=%.6fh, used_time_diff=%.4fh, "
279+
"energy_consumed=%.6fkWh, total_energy=%.6fkWh",
280+
avg_power, time_diff, min_time_diff, energy_consumed, self._total_energy
281+
)
282+
else:
283+
_LOGGER.debug(
284+
"UPS energy: skipping calculation due to unusual time difference: %.4fh",
285+
time_diff
286+
)
287+
else:
288+
_LOGGER.debug(
289+
"UPS energy: skipping calculation due to negative time difference: %.6fh",
290+
time_diff
291+
)
250292

251-
# Update tracking variables
252-
self._last_power = current_power
253-
self._last_update = current_time
293+
# Update tracking variables
294+
self._last_power = current_power
295+
self._last_update = current_time
254296

255297
return round(self._total_energy, 3) if self._total_energy > 0 else 0.0
256298

257299
except (KeyError, TypeError) as err:
258300
_LOGGER.debug("Error getting server energy usage: %s", err)
259301
return None
260302

303+
def _restore_state(self) -> None:
304+
"""Restore previous energy state from Home Assistant registry."""
305+
try:
306+
if self.hass and hasattr(self.hass.states, 'get'):
307+
previous_state = self.hass.states.get(self.entity_id)
308+
if previous_state and previous_state.state not in ('unknown', 'unavailable'):
309+
try:
310+
self._total_energy = float(previous_state.state)
311+
_LOGGER.debug(
312+
"UPS energy sensor restored previous state: %.3f kWh",
313+
self._total_energy
314+
)
315+
except (ValueError, TypeError):
316+
_LOGGER.debug("Could not restore UPS energy state, starting from 0")
317+
self._total_energy = 0.0
318+
else:
319+
_LOGGER.debug("No previous UPS energy state found, starting from 0")
320+
self._total_energy = 0.0
321+
except Exception as err:
322+
_LOGGER.debug("Error restoring UPS energy state: %s", err)
323+
self._total_energy = 0.0
324+
261325
@property
262326
def extra_state_attributes(self) -> dict[str, Any]:
263327
"""Return additional state attributes."""
264328
try:
265329
ups_info = self.coordinator.data.get("system_stats", {}).get("ups_info", {})
266330

331+
# Base attributes with proper capitalization
267332
attrs = {
268-
"ups_model": ups_info.get("MODEL", "Unknown"),
269-
"rated_power": f"{ups_info.get('NOMPOWER', '0')}W",
270-
"current_power": f"{self._last_power or 0}W",
271-
"last_updated": dt_util.now().isoformat(),
272-
"energy_dashboard_ready": True,
273-
"measurement_type": "cumulative_energy",
333+
"UPS Model": ups_info.get("MODEL", "Unknown"),
334+
"Rated Power": f"{ups_info.get('NOMPOWER', '0')}W",
335+
"Current Power": f"{self._last_power or 0:.1f}W",
336+
"Last Updated": dt_util.now().isoformat(),
337+
"Energy Dashboard Ready": True,
274338
}
275339

340+
# Add useful UPS status information
341+
load_pct = ups_info.get("LOADPCT")
342+
if load_pct:
343+
attrs["Load"] = f"{load_pct}%"
344+
345+
battery_charge = ups_info.get("BCHARGE")
346+
if battery_charge:
347+
attrs["Battery"] = f"{battery_charge}%"
348+
349+
runtime = ups_info.get("TIMELEFT")
350+
if runtime:
351+
attrs["Runtime"] = f"{runtime} minutes"
352+
353+
status = ups_info.get("STATUS")
354+
if status:
355+
attrs["Status"] = status
356+
357+
line_voltage = ups_info.get("LINEV")
358+
if line_voltage:
359+
attrs["Line Voltage"] = f"{line_voltage}V"
360+
276361
return attrs
277362

278363
except (KeyError, TypeError) as err:

0 commit comments

Comments
 (0)