1212from .const import DOMAIN , DATA_COORDINATORS , COORDINATOR_CHARGESESSIONS , DATA_CLIENT
1313from .coordinator import OhmeChargeSessionsCoordinator
1414from .utils import charge_graph_in_slot
15- from time import time
1615
1716_LOGGER = logging .getLogger (__name__ )
1817
@@ -103,6 +102,9 @@ def __init__(
103102 self ._last_reading = None
104103 self ._last_reading_in_slot = False
105104
105+ # State variables for charge state detection
106+ self ._trigger_count = 0
107+
106108 self .entity_id = generate_entity_id (
107109 "binary_sensor.{}" , "ohme_car_charging" , hass = hass )
108110
@@ -129,6 +131,7 @@ def _calculate_state(self) -> bool:
129131
130132 # If no last reading or no batterySoc/power, fallback to power > 0
131133 if not self ._last_reading or not self ._last_reading ['batterySoc' ] or not self ._last_reading ['power' ]:
134+ _LOGGER .debug ("ChargingBinarySensor: No last reading, defaulting to power > 0" )
132135 return power > 0
133136
134137 # See if we are in a charge slot now and if we were for the last reading
@@ -146,23 +149,57 @@ def _calculate_state(self) -> bool:
146149 # This condition makes sure we get the charge state updated on the tick immediately after charge stop.
147150 lr_power = self ._last_reading ["power" ]["watt" ]
148151 if lr_in_charge_slot and not in_charge_slot and lr_power > 0 and power / lr_power < 0.6 :
152+ _LOGGER .debug ("ChargingBinarySensor: Power drop on state boundary, assuming not charging" )
153+ self ._trigger_count = 0
149154 return False
150155
151156 # Failing that, we use the watt hours field to check charge state:
152- # - If Wh has positive delta and a nonzero power reading, we are charging
153- # This isn't ideal - eg. quirk of MG ZS in #13, so need to revisit
157+ # - If Wh has positive delta
158+ # - We have a nonzero power reading
159+ # We are charging. Using the power reading isn't ideal - eg. quirk of MG ZS in #13, so need to revisit
154160 wh_delta = self .coordinator .data ['batterySoc' ]['wh' ] - self ._last_reading ['batterySoc' ]['wh' ]
155-
156- return wh_delta > 0 and power > 0
161+ trigger_state = wh_delta > 0 and power > 0
162+
163+ _LOGGER .debug (f"ChargingBinarySensor: Reading Wh delta of { wh_delta } and power of { power } w" )
164+
165+ # If state is going upwards, report straight away
166+ if trigger_state and not self ._state :
167+ _LOGGER .debug ("ChargingBinarySensor: Upwards state change, reporting immediately" )
168+ self ._trigger_count = 0
169+ return True
170+
171+ # If state is going to change (downwards only for now), we want to see 2 consecutive readings of the state having
172+ # changed before reporting it.
173+ if self ._state != trigger_state :
174+ _LOGGER .debug ("ChargingBinarySensor: Downwards state change, incrementing counter" )
175+ self ._trigger_count += 1
176+ if self ._trigger_count > 1 :
177+ _LOGGER .debug ("ChargingBinarySensor: Counter hit, publishing downward state change" )
178+ self ._trigger_count = 0
179+ return trigger_state
180+ else :
181+ self ._trigger_count = 0
182+
183+ _LOGGER .debug ("ChargingBinarySensor: Returning existing state" )
184+
185+ # State hasn't changed or we haven't seen 2 changed values - return existing state
186+ return self ._state
157187
158188 @callback
159189 def _handle_coordinator_update (self ) -> None :
160190 """Update data."""
191+ # Don't accept updates if 20s hasnt passed
192+ # State calculations use deltas that may be unreliable to check if requests are too often
193+ if self ._last_updated and (utcnow ().timestamp () - self ._last_updated .timestamp () < 20 ):
194+ _LOGGER .debug ("ChargingBinarySensor: State update too soon - suppressing" )
195+ return
196+
161197 # If we have power info and the car is plugged in, calculate state. Otherwise, false
162198 if self .coordinator .data and self .coordinator .data ["power" ] and self .coordinator .data ['mode' ] != "DISCONNECTED" :
163199 self ._state = self ._calculate_state ()
164200 else :
165201 self ._state = False
202+ _LOGGER .debug ("ChargingBinarySensor: No power data or car disconnected - reporting False" )
166203
167204 self ._last_reading = self .coordinator .data
168205 self ._last_updated = utcnow ()
0 commit comments