Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.

Commit 8956d41

Browse files
authored
Tweaked charge state detection and added user-agent (#28)
* Added user-agent to all HTTP requests * Added more detail to auth_error * Improve charging detection logic * Added fix to target time when pending approval
1 parent 616a93e commit 8956d41

File tree

5 files changed

+55
-14
lines changed

5 files changed

+55
-14
lines changed

custom_components/ohme/api_client.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from time import time
55
from datetime import datetime, timedelta
66
from homeassistant.helpers.entity import DeviceInfo
7-
from .const import DOMAIN
7+
from .const import DOMAIN, USER_AGENT, INTEGRATION_VERSION
88
from .utils import time_next_occurs
99

1010
_LOGGER = logging.getLogger(__name__)
@@ -111,7 +111,8 @@ def _get_headers(self):
111111
"""Get auth and content-type headers"""
112112
return {
113113
"Authorization": "Firebase %s" % self._token,
114-
"Content-Type": "application/json"
114+
"Content-Type": "application/json",
115+
"User-Agent": f"{USER_AGENT}/{INTEGRATION_VERSION}"
115116
}
116117

117118
async def _post_request(self, url, skip_json=False, data=None):

custom_components/ohme/binary_sensor.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from .const import DOMAIN, DATA_COORDINATORS, COORDINATOR_CHARGESESSIONS, DATA_CLIENT
1313
from .coordinator import OhmeChargeSessionsCoordinator
1414
from .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()

custom_components/ohme/const.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""Component constants"""
2-
32
DOMAIN = "ohme"
3+
USER_AGENT = "dan-r-homeassistant-ohme"
4+
INTEGRATION_VERSION = "0.2.7"
5+
46
DATA_CLIENT = "client"
57
DATA_COORDINATORS = "coordinators"
68
COORDINATOR_CHARGESESSIONS = 0

custom_components/ohme/time.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def __init__(self, coordinator, hass: HomeAssistant, client):
3535

3636
self._client = client
3737

38-
self._state = 0
38+
self._state = None
3939
self._last_updated = None
4040
self._attributes = {}
4141

@@ -64,11 +64,12 @@ def icon(self):
6464
@property
6565
def native_value(self):
6666
"""Get value from data returned from API by coordinator"""
67-
if self.coordinator.data and self.coordinator.data['appliedRule']:
67+
# Make sure we're not pending approval, as this sets the target time to now
68+
if self.coordinator.data and self.coordinator.data['appliedRule'] and self.coordinator.data['mode'] != "PENDING_APPROVAL":
6869
target = self.coordinator.data['appliedRule']['targetTime']
69-
return dt_time(
70+
self._state = dt_time(
7071
hour=target // 3600,
7172
minute=(target % 3600) // 60,
7273
second=0
7374
)
74-
return None
75+
return self._state

custom_components/ohme/translations/en.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
}
1212
},
1313
"error": {
14-
"auth_error": "Invalid credentials provided."
14+
"auth_error": "Invalid credentials provided. Please ensure you are using an Ohme account and not a social account (eg. Google)."
1515
},
1616
"abort": {}
1717
},
@@ -27,7 +27,7 @@
2727
}
2828
},
2929
"error": {
30-
"auth_error": "Invalid credentials provided."
30+
"auth_error": "Invalid credentials provided. Please ensure you are using an Ohme account and not a social account (eg. Google)."
3131
},
3232
"abort": {}
3333
},

0 commit comments

Comments
 (0)