Skip to content

Commit 85e3e68

Browse files
authored
v.1.0.6
- Refactor sensor value conversion to retain Accumulated values across restarts. #62 - Change resetting daily sensors at last second of the day instead of first second of the day. # - More detailed log for #64
2 parents 81d038d + ee51b4c commit 85e3e68

File tree

3 files changed

+63
-15
lines changed

3 files changed

+63
-15
lines changed

custom_components/sigen/calculated_sensor.py

+61-13
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848
# "sensor.sigen_plant_accumulated_grid_import_energy",
4949
# "sensor.sigen_plant_accumulated_pv_energy",
5050
# "sigen_plant_accumulated_battery_charge_energy",
51-
"sigen_plant_accumulated_pv_energy"
51+
"sensor.sigen_plant_accumulated_pv_energy",
52+
# "sensor.sigen_plant_daily_pv_energy",
5253
]
5354

5455

@@ -344,8 +345,12 @@ def calculate_plant_consumed_power(
344345
# Sanity check
345346
if consumed_power < 0:
346347
_LOGGER.warning(
347-
"[CS][Plant Consumed] Calculated power is negative: %s kW",
348+
"[CS][Plant Consumed] Calculated power is negative: %s kW = %s + %s - %s - %s",
348349
consumed_power,
350+
pv_power,
351+
grid_import,
352+
grid_export,
353+
battery_power
349354
)
350355
# Keep the negative value as it might be valid in some scenarios
351356

@@ -487,6 +492,10 @@ def __init__(
487492
source_entity_id: str = "",
488493
pv_string_idx: Optional[int] = None,
489494
) -> None:
495+
# Initialize state variables
496+
self._state: Decimal | None = None
497+
self._last_valid_state: Decimal | None = None
498+
490499
"""Initialize the integration sensor."""
491500
# Call SigenergyEntity's __init__ first
492501
super().__init__(
@@ -512,10 +521,6 @@ def __init__(
512521
self.log_this_entity = False
513522
self._last_coordinator_update = None
514523

515-
# Initialize state variables
516-
self._state: Decimal | None = None
517-
self._last_valid_state: Decimal | None = None
518-
519524
# Time tracking variables
520525
self._max_sub_interval = (
521526
None # disable time based integration
@@ -579,14 +584,27 @@ def _update_integral(self, area: Decimal) -> None:
579584
self.entity_id, area, area_scaled
580585
)
581586

582-
self._last_valid_state = self._state
587+
# Only update last_valid_state if we have a valid calculation
588+
if self._state is not None and isinstance(self._state, Decimal):
589+
# We only want to save positive values
590+
if self._state >= Decimal('0'):
591+
self._last_valid_state = self._state
592+
if self.log_this_entity:
593+
_LOGGER.debug(
594+
"[%s] _update_integral - Updated _last_valid_state: %s (state_class: %s)",
595+
self.entity_id,
596+
self._last_valid_state,
597+
self.state_class
598+
)
583599

584600
def _setup_midnight_reset(self) -> None:
585601
"""Schedule reset at midnight."""
586602
now = dt_util.now()
587-
midnight = (now + timedelta(days=1)).replace(
588-
hour=0, minute=0, second=0, microsecond=0
589-
)
603+
# Calculate last second of the current day (23:59:59)
604+
midnight = now.replace(hour=23, minute=59, second=59, microsecond=0)
605+
# If we're already past midnight, use tomorrow's date
606+
if now.hour >= 23 and now.minute >= 59 and now.second >= 59:
607+
midnight = (now + timedelta(days=1)).replace(hour=23, minute=59, second=59, microsecond=0)
590608

591609
@callback
592610
def _handle_midnight(current_time):
@@ -636,7 +654,8 @@ async def async_added_to_hass(self) -> None:
636654
init_value_dec = safe_decimal(init_value)
637655
if init_value_dec is not None:
638656
restore_value = init_value_dec
639-
_LOGGER.debug("Saving initial value for %s: %s", self.entity_id, restore_value)
657+
if self.log_this_entity:
658+
_LOGGER.info("Saving initial value for %s: %s", self.entity_id, restore_value)
640659
restored_from_config = True # Mark that we restored from config
641660
else:
642661
_LOGGER.warning("Could not convert init_value '%s' to Decimal for %s", init_value, self.entity_id)
@@ -664,7 +683,21 @@ async def async_added_to_hass(self) -> None:
664683
STATE_UNKNOWN,
665684
STATE_UNAVAILABLE,
666685
):
667-
restore_value = last_state.state
686+
if self.unit_of_measurement == "MWh":
687+
restore_value = str(Decimal(last_state.state) * 1000)
688+
else:
689+
restore_value = str(Decimal(last_state.state) * 1)
690+
if self.log_this_entity:
691+
if self.unit_of_measurement == last_state.attributes["unit_of_measurement"]:
692+
_LOGGER.debug("Both are %s", self.unit_of_measurement)
693+
else:
694+
_LOGGER.debug("Self is %s and last is %s", self.unit_of_measurement, last_state.attributes["unit_of_measurement"])
695+
696+
else:
697+
_LOGGER.debug(
698+
"No valid last state available for %s, using default value",
699+
self.entity_id,
700+
)
668701

669702
if restore_value is not None: # Check if restore_value is not None before trying to use it
670703
try:
@@ -677,8 +710,20 @@ async def async_added_to_hass(self) -> None:
677710
self._last_integration_time = dt_util.utcnow()
678711
else:
679712
_LOGGER.warning("Could not convert restore value '%s' to Decimal for %s", restore_value, self.entity_id)
713+
# Try to use last_valid_state if available as fallback
714+
if self._last_valid_state is not None:
715+
self._state = self._last_valid_state
716+
_LOGGER.debug("Falling back to last valid state for %s: %s", self.entity_id, self._last_valid_state)
680717
except (ValueError, TypeError, InvalidOperation) as e:
681718
_LOGGER.warning("Could not restore state for %s from value '%s': %s", self.entity_id, restore_value, e)
719+
# Try to use last_valid_state if available as fallback
720+
if self._last_valid_state is not None:
721+
self._state = self._last_valid_state
722+
_LOGGER.debug("Falling back to last valid state for %s: %s", self.entity_id, self._last_valid_state)
723+
elif self._last_valid_state is not None:
724+
# If no restore value but we have a last valid state, use that
725+
self._state = self._last_valid_state
726+
_LOGGER.debug("Using last valid state for %s: %s", self.entity_id, self._last_valid_state)
682727
else:
683728
_LOGGER.debug("No restore value available for %s, state remains uninitialized.", self.entity_id)
684729

@@ -778,6 +823,9 @@ def _integrate_on_state_change(
778823
self, old_state: State | None, new_state: State | None
779824
) -> None:
780825
"""Perform integration based on state change."""
826+
if self.log_this_entity:
827+
_LOGGER.debug("[_integrate_on_state_change] Starting for %s with old_state: %s, new_state: %s",
828+
self.entity_id, old_state, new_state)
781829
if new_state is None:
782830
return
783831

@@ -805,7 +853,7 @@ def _integrate_on_state_change(
805853
)
806854

807855
# Update the integral
808-
self._update_integral(area) # Logging is now inside _update_integral
856+
self._update_integral(area)
809857

810858
# Write state
811859
if self.log_this_entity:

custom_components/sigen/config_flow.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1181,7 +1181,7 @@ def generate_accumulated_energy_schema(data_source: Dict):
11811181
if val is None or val == "unknown":
11821182
current_value = 0.0
11831183
else:
1184-
current_value = val * 1000 # Convert to kWh
1184+
current_value = val #* 1000 # Convert to kWh
11851185
try:
11861186
_LOGGER.debug("Current value for %s: %s", sensor, current_value)
11871187
schema[vol.Required(sensor, default=data_source.get(sensor, current_value))] = vol.All(

custom_components/sigen/manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@
2121
"loggers": ["custom_components.sigen"],
2222
"quality_scale": "custom",
2323
"requirements": ["pymodbus>=3.0.0"],
24-
"version": "1.0.5"
24+
"version": "1.0.6"
2525
}

0 commit comments

Comments
 (0)