Skip to content

Commit

Permalink
Fix bug with session energy usage sensor, put accumulative energy usa…
Browse files Browse the repository at this point in the history
…ge sensor behind an option, fix charge slots updating mid-charge, fix charge end logic (#60)

* Fix none issue with session energy usage

* Put accumulative energy usage sensor behind an option

* Removing charge slot hashing

* Fix charge end logic

* Version bump for breaking changes
  • Loading branch information
dan-r authored Feb 15, 2024
1 parent 13b60d0 commit 7980117
Show file tree
Hide file tree
Showing 7 changed files with 38 additions and 45 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ This integration exposes the following entities:
* Next Charge Slot End - The next time your car will stop charging according to the Ohme-generated charge plan
* Sensors (Other)
* CT Reading (Amps) - Reading from attached CT clamp
* Accumulative Energy Usage (kWh) - Total energy used by the charger
* Session Energy Usage (kWh) - Energy used in the current session
* Accumulative Energy Usage (kWh) - Total energy used by the charger (If enabled in options)
* 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
* Switches (Settings) - **Only options available to your charger model will show**
* Lock Buttons - Locks buttons on charger
Expand All @@ -84,6 +84,7 @@ This integration exposes the following entities:
## Options
Some options can be set from the 'Configure' menu in Home Assistant:
* 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.
* Enable accumulative energy usage sensor - Enable the sensor showing an all-time incrementing energy usage counter. This causes issues with some accounts.


## Coordinators
Expand Down
27 changes: 21 additions & 6 deletions custom_components/ohme/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
from homeassistant import core
from .const import *
from .utils import get_option
from .api_client import OhmeApiClient
from .coordinator import OhmeChargeSessionsCoordinator, OhmeStatisticsCoordinator, OhmeAccountInfoCoordinator, OhmeAdvancedSettingsCoordinator, OhmeChargeSchedulesCoordinator
from homeassistant.exceptions import ConfigEntryNotReady
Expand All @@ -25,12 +26,9 @@ async def async_setup_dependencies(hass, entry):

async def async_update_listener(hass, entry):
"""Handle options flow credentials update."""
# Re-instantiate the API client
await async_setup_dependencies(hass, entry)

# Refresh all coordinators for good measure
for coordinator in hass.data[DOMAIN][DATA_COORDINATORS]:
await coordinator.async_refresh()

# Reload this instance
await hass.config_entries.async_reload(entry.entry_id)


async def async_setup_entry(hass, entry):
Expand All @@ -53,7 +51,24 @@ async def async_setup_entry(hass, entry):
OhmeAdvancedSettingsCoordinator
]

coordinators_skipped = []

# Skip statistics coordinator if we don't need it
if not get_option(hass, "enable_accumulative_energy"):
coordinators_skipped.append(OhmeStatisticsCoordinator)

for coordinator in coordinators:
# If we should skip this coordinator
skip = False
for skipped in coordinators_skipped:
if isinstance(coordinator, skipped):
skip = True
break

if skip:
_LOGGER.debug(f"Skipping initial load of {coordinator.__class__.__name__}")
continue

# Catch failures if this is an 'optional' coordinator
try:
await coordinator.async_config_entry_first_refresh()
Expand Down
3 changes: 3 additions & 0 deletions custom_components/ohme/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ async def async_step_init(self, options):
): str,
vol.Required(
"never_session_specific", default=self._config_entry.options.get("never_session_specific", False)
) : bool,
vol.Required(
"enable_accumulative_energy", default=self._config_entry.options.get("enable_accumulative_energy", False)
) : bool
}), errors=errors
)
2 changes: 1 addition & 1 deletion custom_components/ohme/const.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Component constants"""
DOMAIN = "ohme"
USER_AGENT = "dan-r-homeassistant-ohme"
INTEGRATION_VERSION = "0.6.1"
INTEGRATION_VERSION = "0.7.0"
CONFIG_VERSION = 1
ENTITY_TYPES = ["sensor", "binary_sensor", "switch", "button", "number", "time"]

Expand Down
27 changes: 5 additions & 22 deletions custom_components/ohme/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
SensorEntity
)
import json
import hashlib
import math
import logging
from homeassistant.helpers.update_coordinator import CoordinatorEntity
Expand All @@ -17,7 +16,7 @@
from homeassistant.util.dt import (utcnow)
from .const import DOMAIN, DATA_CLIENT, DATA_COORDINATORS, DATA_SLOTS, COORDINATOR_CHARGESESSIONS, COORDINATOR_STATISTICS, COORDINATOR_ADVANCED
from .coordinator import OhmeChargeSessionsCoordinator, OhmeStatisticsCoordinator, OhmeAdvancedSettingsCoordinator
from .utils import charge_graph_next_slot, charge_graph_slot_list
from .utils import charge_graph_next_slot, charge_graph_slot_list, get_option

_LOGGER = logging.getLogger(__name__)

Expand All @@ -39,11 +38,13 @@ async def async_setup_entry(
VoltageSensor(coordinator, hass, client),
CTSensor(adv_coordinator, hass, client),
EnergyUsageSensor(coordinator, hass, client),
AccumulativeEnergyUsageSensor(stats_coordinator, hass, client),
NextSlotEndSensor(coordinator, hass, client),
NextSlotStartSensor(coordinator, hass, client),
SlotListSensor(coordinator, hass, client),
BatterySOCSensor(coordinator, hass, client)]

if get_option(hass, "enable_accumulative_energy"):
sensors.append(AccumulativeEnergyUsageSensor(stats_coordinator, hass, client))

async_add_entities(sensors, update_before_add=True)

Expand Down Expand Up @@ -293,7 +294,7 @@ def _handle_coordinator_update(self) -> None:
new_state = self.coordinator.data['batterySoc']['wh']

# Let the state reset to 0, but not drop otherwise
if new_state <= 0:
if not new_state or new_state <= 0:
self._state = 0
else:
self._state = max(0, self._state or 0, new_state)
Expand Down Expand Up @@ -422,7 +423,6 @@ def _handle_coordinator_update(self) -> None:
class SlotListSensor(CoordinatorEntity[OhmeChargeSessionsCoordinator], SensorEntity):
"""Sensor for next smart charge slot end time."""
_attr_name = "Charge Slots"
_last_hash = None

def __init__(
self,
Expand Down Expand Up @@ -458,27 +458,13 @@ def native_value(self):
"""Return pre-calculated state."""
return self._state

def _hash_rule(self):
"""Generate a hashed representation of the current charge rule."""
serial = json.dumps(self.coordinator.data['appliedRule'], sort_keys=True)
sha1 = hashlib.sha1(serial.encode('utf-8')).hexdigest()
return sha1

@callback
def _handle_coordinator_update(self) -> None:
"""Get a list of charge slots."""
if self.coordinator.data is None or self.coordinator.data["mode"] == "DISCONNECTED" or self.coordinator.data["mode"] == "FINISHED_CHARGE":
self._state = None
self._last_hash = None
self._hass.data[DOMAIN][DATA_SLOTS] = []
else:
rule_hash = self._hash_rule()

# Rule has not changed, no point evaluating slots again
if rule_hash == self._last_hash:
_LOGGER.debug("Slot evaluation skipped - rule has not changed")
return

slots = charge_graph_slot_list(
self.coordinator.data['startTime'], self.coordinator.data['chargeGraph']['points'])

Expand All @@ -490,9 +476,6 @@ def _handle_coordinator_update(self) -> None:

# Make sure we return None/Unknown if the list is empty
self._state = None if self._state == "" else self._state

# Store hash of the last rule
self._last_hash = self._hash_rule()

self._last_updated = utcnow()
self.async_write_ha_state()
Expand Down
3 changes: 2 additions & 1 deletion custom_components/ohme/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"data": {
"email": "Email address",
"password": "Password",
"never_session_specific": "Never update an ongoing session"
"never_session_specific": "Never update an ongoing session",
"enable_accumulative_energy": "Enable accumulative energy sensor"
},
"data_description": {
"password": "If you are not changing your credentials, leave the password field empty.",
Expand Down
18 changes: 4 additions & 14 deletions custom_components/ohme/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,6 @@ def _sanitise_points(points):
return output


def _charge_finished(data):
"""Is the charge finished?"""
now = int(time())
data = [x['y'] for x in data if x["t"] > now]

if len(data) == 0 or min(data) == max(data):
return True
return False


def _next_slot(data, live=False, in_progress=False):
"""Get the next slot. live is whether or not we may start mid charge. Eg: For the next slot end sensor, we dont have the
start but still want the end of the in progress session, but for the slot list sensor we only want slots that have
Expand Down Expand Up @@ -117,7 +107,7 @@ def charge_graph_slot_list(charge_start, points, skip_format=False):
data = points if skip_format else _format_charge_graph(charge_start, points)

# Don't return any slots if charge is over
if _charge_finished(data):
if charge_graph_next_slot(charge_start, points)['end'] is None:
return []

data = _sanitise_points(data)
Expand Down Expand Up @@ -199,6 +189,6 @@ def session_in_progress(hass, data):
return True


def get_option(hass, option):
"""Return option value, default to False."""
return hass.data[DOMAIN][DATA_OPTIONS].get(option, None)
def get_option(hass, option, default=False):
"""Return option value, with settable default."""
return hass.data[DOMAIN][DATA_OPTIONS].get(option, default)

0 comments on commit 7980117

Please sign in to comment.