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

Commit c07c8a5

Browse files
authored
Frequency configuration and data fix (#101)
* Added debug logging to EnergyUsageSensor * Remove deprecated acculumative energy sensor * Remove outdated hardware version numbers * Configurable refresh intervals * Bump version * Messy debug logging for EnergyUsageSensor * Change source of energy sensor and amend logic * Fix None comparison
1 parent 66bd621 commit c07c8a5

File tree

8 files changed

+63
-134
lines changed

8 files changed

+63
-134
lines changed

README.md

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ If you find any bugs or would like to request a feature, please open an issue.
88

99
## Tested Hardware
1010
This integration has been tested with the following hardware:
11-
* Ohme Home Pro [v1.32]
12-
* Ohme Home [v1.32]
13-
* Ohme Go [v1.32]
14-
* Ohme ePod [v2.12]
11+
* Ohme Home Pro
12+
* Ohme Home
13+
* Ohme Go
14+
* Ohme ePod
1515

1616
## External Software
1717
The 'Charge Slot Active' binary sensor mimics the `planned_dispatches` and `completed_dispatches` attributes from the [Octopus Energy](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy) integration, so should support external software which reads this such as [predbat](https://springfall2008.github.io/batpred/devices/#ohme).
@@ -57,7 +57,6 @@ This integration exposes the following entities:
5757
* Sensors (Other)
5858
* CT Reading (Amps) - Reading from attached CT clamp
5959
* Energy Usage (kWh) - Energy used in the current/last session. *This is supported by the energy dashboard.*
60-
* Accumulative Energy Usage (kWh) - Deprecated - Total energy used by the charger (If enabled in options)
6160
* Battery State of Charge (%) - If your car is API connected this is read from the car, if not it is how much charge Ohme thinks it has added
6261
* Switches (Settings) - **Only options available to your charger model will show**
6362
* Lock Buttons - Locks buttons on charger
@@ -82,7 +81,7 @@ This integration exposes the following entities:
8281
Some options can be set from the 'Configure' menu in Home Assistant:
8382
* Never update an ongoing session - Override the default behaviour of the target time, percentage and preconditioning inputs and only ever update the schedule, not the current session. This was added as changing the current session can cause issues for customers on Intelligent Octopus Go.
8483
* Don't collapse charge slots - By default, adjacent slots are merged into one. This option shows every slot, as shown in the Ohme app.
85-
* Enable accumulative energy usage sensor - Enable the sensor showing an all-time incrementing energy usage counter. This causes issues with some accounts.
84+
* Refresh Intervals - The refresh interval for the four coordinators listed below can be configured manually. The default times also serve as minimums, as to be respectful to Ohme, but you can choose to fetch data less frequently.
8685

8786

8887
## Coordinators
@@ -102,7 +101,5 @@ The coordinators are listed with their refresh intervals below. Relevant coordin
102101
* OhmeAdvancedSettingsCoordinator (1m refresh)
103102
* Sensors: CT reading sensor
104103
* Binary Sensors: Charger online
105-
* OhmeStatisticsCoordinator (30m refresh)
106-
* Sensors: Accumulative energy usage
107104
* OhmeChargeSchedulesCoordinator (10m refresh)
108105
* Inputs: Target time, target percentage and preconditioning (If car disconnected)

custom_components/ohme/__init__.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from .const import *
44
from .utils import get_option
55
from .api_client import OhmeApiClient
6-
from .coordinator import OhmeChargeSessionsCoordinator, OhmeStatisticsCoordinator, OhmeAccountInfoCoordinator, OhmeAdvancedSettingsCoordinator, OhmeChargeSchedulesCoordinator
6+
from .coordinator import OhmeChargeSessionsCoordinator, OhmeAccountInfoCoordinator, OhmeAdvancedSettingsCoordinator, OhmeChargeSchedulesCoordinator
77
from homeassistant.exceptions import ConfigEntryNotReady
88

99
_LOGGER = logging.getLogger(__name__)
@@ -40,35 +40,16 @@ async def async_setup_entry(hass, entry):
4040
coordinators = [
4141
OhmeChargeSessionsCoordinator(hass=hass), # COORDINATOR_CHARGESESSIONS
4242
OhmeAccountInfoCoordinator(hass=hass), # COORDINATOR_ACCOUNTINFO
43-
OhmeStatisticsCoordinator(hass=hass), # COORDINATOR_STATISTICS
4443
OhmeAdvancedSettingsCoordinator(hass=hass), # COORDINATOR_ADVANCED
4544
OhmeChargeSchedulesCoordinator(hass=hass) # COORDINATOR_SCHEDULES
4645
]
4746

4847
# We can function without these so setup can continue
4948
coordinators_optional = [
50-
OhmeStatisticsCoordinator,
5149
OhmeAdvancedSettingsCoordinator
5250
]
5351

54-
coordinators_skipped = []
55-
56-
# Skip statistics coordinator if we don't need it
57-
if not get_option(hass, "enable_accumulative_energy"):
58-
coordinators_skipped.append(OhmeStatisticsCoordinator)
59-
6052
for coordinator in coordinators:
61-
# If we should skip this coordinator
62-
skip = False
63-
for skipped in coordinators_skipped:
64-
if isinstance(coordinator, skipped):
65-
skip = True
66-
break
67-
68-
if skip:
69-
_LOGGER.debug(f"Skipping initial load of {coordinator.__class__.__name__}")
70-
continue
71-
7253
# Catch failures if this is an 'optional' coordinator
7354
try:
7455
await coordinator.async_config_entry_first_refresh()

custom_components/ohme/api_client.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,6 @@ async def async_refresh_session(self):
9595

9696
# Internal methods
9797

98-
def _last_second_of_month_timestamp(self):
99-
"""Get the last second of this month."""
100-
dt = datetime.today()
101-
dt = dt.replace(day=1) + timedelta(days=32)
102-
dt = dt.replace(day=1, hour=0, minute=0, second=0,
103-
microsecond=0) - timedelta(seconds=1)
104-
return int(dt.timestamp()*1e3)
105-
10698
async def _handle_api_error(self, url, resp):
10799
"""Raise an exception if API response failed."""
108100
if resp.status != 200:
@@ -335,13 +327,6 @@ async def async_update_device_info(self, is_retry=False):
335327

336328
return True
337329

338-
async def async_get_charge_statistics(self):
339-
"""Get charge statistics. Currently this is just for all time (well, Jan 2019)."""
340-
end_ts = self._last_second_of_month_timestamp()
341-
resp = await self._get_request(f"/v1/chargeSessions/summary/users/{self._user_id}?&startTs={self._provision_date}&endTs={end_ts}&granularity=MONTH")
342-
343-
return resp['totalStats']
344-
345330
async def async_get_advanced_settings(self):
346331
"""Get advanced settings (mainly for CT clamp reading)"""
347332
resp = await self._get_request(f"/v1/chargeDevices/{self._serial}/advancedSettings")

custom_components/ohme/config_flow.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import voluptuous as vol
22
from homeassistant.config_entries import (ConfigFlow, OptionsFlow)
3-
from .const import DOMAIN, CONFIG_VERSION
3+
from .const import DOMAIN, CONFIG_VERSION, DEFAULT_INTERVAL_CHARGESESSIONS, DEFAULT_INTERVAL_ACCOUNTINFO, DEFAULT_INTERVAL_ADVANCED, DEFAULT_INTERVAL_SCHEDULES
44
from .api_client import OhmeApiClient
55

66

@@ -91,7 +91,16 @@ async def async_step_init(self, options):
9191
"never_collapse_slots", default=self._config_entry.options.get("never_collapse_slots", False)
9292
) : bool,
9393
vol.Required(
94-
"enable_accumulative_energy", default=self._config_entry.options.get("enable_accumulative_energy", False)
95-
) : bool
94+
"interval_chargesessions", default=self._config_entry.options.get("interval_chargesessions", DEFAULT_INTERVAL_CHARGESESSIONS)
95+
) : vol.All(vol.Coerce(float), vol.Clamp(min=DEFAULT_INTERVAL_CHARGESESSIONS)),
96+
vol.Required(
97+
"interval_accountinfo", default=self._config_entry.options.get("interval_accountinfo", DEFAULT_INTERVAL_ACCOUNTINFO)
98+
) : vol.All(vol.Coerce(float), vol.Clamp(min=DEFAULT_INTERVAL_ACCOUNTINFO)),
99+
vol.Required(
100+
"interval_advanced", default=self._config_entry.options.get("interval_advanced", DEFAULT_INTERVAL_ADVANCED)
101+
) : vol.All(vol.Coerce(float), vol.Clamp(min=DEFAULT_INTERVAL_ADVANCED)),
102+
vol.Required(
103+
"interval_schedules", default=self._config_entry.options.get("interval_schedules", DEFAULT_INTERVAL_SCHEDULES)
104+
) : vol.All(vol.Coerce(float), vol.Clamp(min=DEFAULT_INTERVAL_SCHEDULES))
96105
}), errors=errors
97106
)

custom_components/ohme/const.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Component constants"""
22
DOMAIN = "ohme"
33
USER_AGENT = "dan-r-homeassistant-ohme"
4-
INTEGRATION_VERSION = "1.0.0"
4+
INTEGRATION_VERSION = "1.0.1"
55
CONFIG_VERSION = 1
66
ENTITY_TYPES = ["sensor", "binary_sensor", "switch", "button", "number", "time"]
77

@@ -12,6 +12,10 @@
1212

1313
COORDINATOR_CHARGESESSIONS = 0
1414
COORDINATOR_ACCOUNTINFO = 1
15-
COORDINATOR_STATISTICS = 2
16-
COORDINATOR_ADVANCED = 3
17-
COORDINATOR_SCHEDULES = 4
15+
COORDINATOR_ADVANCED = 2
16+
COORDINATOR_SCHEDULES = 3
17+
18+
DEFAULT_INTERVAL_CHARGESESSIONS = 0.5
19+
DEFAULT_INTERVAL_ACCOUNTINFO = 1
20+
DEFAULT_INTERVAL_ADVANCED = 1
21+
DEFAULT_INTERVAL_SCHEDULES = 10

custom_components/ohme/coordinator.py

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
UpdateFailed
77
)
88

9-
from .const import DOMAIN, DATA_CLIENT
9+
from .const import DOMAIN, DATA_CLIENT, DEFAULT_INTERVAL_CHARGESESSIONS, DEFAULT_INTERVAL_ACCOUNTINFO, DEFAULT_INTERVAL_ADVANCED, DEFAULT_INTERVAL_SCHEDULES
10+
from .utils import get_option
1011

1112
_LOGGER = logging.getLogger(__name__)
1213

@@ -20,7 +21,9 @@ def __init__(self, hass):
2021
hass,
2122
_LOGGER,
2223
name="Ohme Charge Sessions",
23-
update_interval=timedelta(seconds=30),
24+
update_interval=timedelta(minutes=
25+
get_option(hass, "interval_chargesessions", DEFAULT_INTERVAL_CHARGESESSIONS)
26+
),
2427
)
2528
self._client = hass.data[DOMAIN][DATA_CLIENT]
2629

@@ -42,7 +45,9 @@ def __init__(self, hass):
4245
hass,
4346
_LOGGER,
4447
name="Ohme Account Info",
45-
update_interval=timedelta(minutes=1),
48+
update_interval=timedelta(minutes=
49+
get_option(hass, "interval_accountinfo", DEFAULT_INTERVAL_ACCOUNTINFO)
50+
),
4651
)
4752
self._client = hass.data[DOMAIN][DATA_CLIENT]
4853

@@ -55,28 +60,6 @@ async def _async_update_data(self):
5560
raise UpdateFailed("Error communicating with API")
5661

5762

58-
class OhmeStatisticsCoordinator(DataUpdateCoordinator):
59-
"""Coordinator to update statistics from API periodically.
60-
(But less so than the others)"""
61-
62-
def __init__(self, hass):
63-
"""Initialise coordinator."""
64-
super().__init__(
65-
hass,
66-
_LOGGER,
67-
name="Ohme Charger Statistics",
68-
update_interval=timedelta(minutes=30),
69-
)
70-
self._client = hass.data[DOMAIN][DATA_CLIENT]
71-
72-
async def _async_update_data(self):
73-
"""Fetch data from API endpoint."""
74-
try:
75-
return await self._client.async_get_charge_statistics()
76-
77-
except BaseException:
78-
raise UpdateFailed("Error communicating with API")
79-
8063
class OhmeAdvancedSettingsCoordinator(DataUpdateCoordinator):
8164
"""Coordinator to pull CT clamp reading."""
8265

@@ -86,7 +69,9 @@ def __init__(self, hass):
8669
hass,
8770
_LOGGER,
8871
name="Ohme Advanced Settings",
89-
update_interval=timedelta(minutes=1),
72+
update_interval=timedelta(minutes=
73+
get_option(hass, "interval_advanced", DEFAULT_INTERVAL_ADVANCED)
74+
),
9075
)
9176
self._client = hass.data[DOMAIN][DATA_CLIENT]
9277

@@ -98,6 +83,7 @@ async def _async_update_data(self):
9883
except BaseException:
9984
raise UpdateFailed("Error communicating with API")
10085

86+
10187
class OhmeChargeSchedulesCoordinator(DataUpdateCoordinator):
10288
"""Coordinator to pull charge schedules."""
10389

@@ -107,7 +93,9 @@ def __init__(self, hass):
10793
hass,
10894
_LOGGER,
10995
name="Ohme Charge Schedules",
110-
update_interval=timedelta(minutes=10),
96+
update_interval=timedelta(minutes=
97+
get_option(hass, "interval_schedules", DEFAULT_INTERVAL_SCHEDULES)
98+
),
11199
)
112100
self._client = hass.data[DOMAIN][DATA_CLIENT]
113101

@@ -118,4 +106,3 @@ async def _async_update_data(self):
118106

119107
except BaseException:
120108
raise UpdateFailed("Error communicating with API")
121-

custom_components/ohme/sensor.py

Lines changed: 16 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
from homeassistant.core import HomeAssistant, callback
1313
from homeassistant.helpers.entity import generate_entity_id
1414
from homeassistant.util.dt import (utcnow)
15-
from .const import DOMAIN, DATA_CLIENT, DATA_COORDINATORS, DATA_SLOTS, COORDINATOR_CHARGESESSIONS, COORDINATOR_STATISTICS, COORDINATOR_ADVANCED
16-
from .coordinator import OhmeChargeSessionsCoordinator, OhmeStatisticsCoordinator, OhmeAdvancedSettingsCoordinator
15+
from .const import DOMAIN, DATA_CLIENT, DATA_COORDINATORS, DATA_SLOTS, COORDINATOR_CHARGESESSIONS, COORDINATOR_ADVANCED
16+
from .coordinator import OhmeChargeSessionsCoordinator, OhmeAdvancedSettingsCoordinator
1717
from .utils import next_slot, get_option, slot_list, slot_list_str
1818

1919
_LOGGER = logging.getLogger(__name__)
@@ -28,7 +28,6 @@ async def async_setup_entry(
2828
coordinators = hass.data[DOMAIN][DATA_COORDINATORS]
2929

3030
coordinator = coordinators[COORDINATOR_CHARGESESSIONS]
31-
stats_coordinator = coordinators[COORDINATOR_STATISTICS]
3231
adv_coordinator = coordinators[COORDINATOR_ADVANCED]
3332

3433
sensors = [PowerDrawSensor(coordinator, hass, client),
@@ -41,9 +40,6 @@ async def async_setup_entry(
4140
SlotListSensor(coordinator, hass, client),
4241
BatterySOCSensor(coordinator, hass, client)]
4342

44-
if get_option(hass, "enable_accumulative_energy"):
45-
sensors.append(AccumulativeEnergyUsageSensor(stats_coordinator, hass, client))
46-
4743
async_add_entities(sensors, update_before_add=True)
4844

4945

@@ -213,52 +209,6 @@ def native_value(self):
213209
return self.coordinator.data['clampAmps']
214210

215211

216-
class AccumulativeEnergyUsageSensor(CoordinatorEntity[OhmeStatisticsCoordinator], SensorEntity):
217-
"""Sensor for total energy usage."""
218-
_attr_name = "Accumulative Energy Usage"
219-
_attr_native_unit_of_measurement = UnitOfEnergy.WATT_HOUR
220-
_attr_suggested_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR
221-
_attr_suggested_display_precision = 1
222-
_attr_device_class = SensorDeviceClass.ENERGY
223-
_attr_state_class = SensorStateClass.TOTAL
224-
225-
def __init__(
226-
self,
227-
coordinator: OhmeStatisticsCoordinator,
228-
hass: HomeAssistant,
229-
client):
230-
super().__init__(coordinator=coordinator)
231-
232-
self._state = None
233-
self._attributes = {}
234-
self._last_updated = None
235-
self._client = client
236-
237-
self.entity_id = generate_entity_id(
238-
"sensor.{}", "ohme_accumulative_energy", hass=hass)
239-
240-
self._attr_device_info = hass.data[DOMAIN][DATA_CLIENT].get_device_info(
241-
)
242-
243-
@property
244-
def unique_id(self) -> str:
245-
"""Return the unique ID of the sensor."""
246-
return self._client.get_unique_id("accumulative_energy")
247-
248-
@property
249-
def icon(self):
250-
"""Icon of the sensor."""
251-
return "mdi:lightning-bolt"
252-
253-
@property
254-
def native_value(self):
255-
"""Get value from data returned from API by coordinator"""
256-
if self.coordinator.data and self.coordinator.data['energyChargedTotalWh']:
257-
return self.coordinator.data['energyChargedTotalWh']
258-
259-
return None
260-
261-
262212
class EnergyUsageSensor(CoordinatorEntity[OhmeChargeSessionsCoordinator], SensorEntity):
263213
"""Sensor for total energy usage."""
264214
_attr_name = "Energy"
@@ -290,13 +240,25 @@ def __init__(
290240
def _handle_coordinator_update(self) -> None:
291241
# Ensure we have data, then ensure value is going up and above 0
292242
if self.coordinator.data and self.coordinator.data['batterySoc']:
293-
new_state = self.coordinator.data['batterySoc']['wh']
243+
new_state = 0
244+
try:
245+
new_state = self.coordinator.data['chargeGraph']['now']['y']
246+
except BaseException:
247+
_LOGGER.debug("EnergyUsageSensor: ChargeGraph reading failed, falling back to batterySoc")
248+
new_state = self.coordinator.data['batterySoc']['wh']
294249

295250
# Let the state reset to 0, but not drop otherwise
296251
if not new_state or new_state <= 0:
252+
_LOGGER.debug("EnergyUsageSensor: Resetting Wh reading to 0")
297253
self._state = 0
298254
else:
299-
self._state = max(0, self._state or 0, new_state)
255+
# Allow a significant (90%+) drop, even if we dont hit exactly 0
256+
if self._state and self._state > 0 and new_state > 0 and (new_state / self._state) < 0.1:
257+
self._state = new_state
258+
else:
259+
self._state = max(0, self._state or 0, new_state)
260+
261+
_LOGGER.debug("EnergyUsageSensor: New state is %s", self._state)
300262

301263
self.async_write_ha_state()
302264

custom_components/ohme/translations/en.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,16 @@
2424
"password": "Password",
2525
"never_session_specific": "Never update an ongoing session",
2626
"never_collapse_slots": "Don't collapse charge slots",
27-
"enable_accumulative_energy": "Enable accumulative energy sensor"
27+
"interval_chargesessions": "Charge sessions refresh rate (minutes)",
28+
"interval_accountinfo": "Account info refresh rate (minutes)",
29+
"interval_advanced": "Advanced settings refresh rate (minutes)",
30+
"interval_schedules": "Schedules refresh rate (minutes)"
2831
},
2932
"data_description": {
3033
"password": "If you are not changing your credentials, leave the password field empty.",
3134
"never_session_specific": "When adjusting charge percentage, charge target or preconditioning settings, the schedule will always be updated even if a charge session is in progress.",
32-
"never_collapse_slots": "By default, adjacent slots are merged into one. This option shows every slot, as shown in the Ohme app."
35+
"never_collapse_slots": "By default, adjacent slots are merged into one. This option shows every slot, as shown in the Ohme app.",
36+
"interval_schedules": "Details on which entities are updated by each coordinator are in the README."
3337
}
3438
}
3539
},

0 commit comments

Comments
 (0)