From 870cbff9213bf2bba15da1e096e5c45f52c141f8 Mon Sep 17 00:00:00 2001 From: Daniel Raper Date: Wed, 3 Jan 2024 09:56:51 +0000 Subject: [PATCH 01/11] Require 3 downward state change readings rather than 2 --- custom_components/ohme/binary_sensor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/ohme/binary_sensor.py b/custom_components/ohme/binary_sensor.py index a7d123e..263f45b 100644 --- a/custom_components/ohme/binary_sensor.py +++ b/custom_components/ohme/binary_sensor.py @@ -168,12 +168,12 @@ def _calculate_state(self) -> bool: self._trigger_count = 0 return True - # If state is going to change (downwards only for now), we want to see 2 consecutive readings of the state having + # If state is going to change (downwards only for now), we want to see 3 consecutive readings of the state having # changed before reporting it. if self._state != trigger_state: _LOGGER.debug("ChargingBinarySensor: Downwards state change, incrementing counter") self._trigger_count += 1 - if self._trigger_count > 1: + if self._trigger_count > 2: _LOGGER.debug("ChargingBinarySensor: Counter hit, publishing downward state change") self._trigger_count = 0 return trigger_state @@ -182,7 +182,7 @@ def _calculate_state(self) -> bool: _LOGGER.debug("ChargingBinarySensor: Returning existing state") - # State hasn't changed or we haven't seen 2 changed values - return existing state + # State hasn't changed or we haven't seen 3 changed values - return existing state return self._state @callback From 2d1e21a2b1948c639e1112f301814e7c92fb297b Mon Sep 17 00:00:00 2001 From: Daniel Raper Date: Wed, 3 Jan 2024 10:07:37 +0000 Subject: [PATCH 02/11] Updated action versions --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 55407ab..21331e9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ jobs: name: Validate runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v2" + - uses: "actions/checkout@v4" - uses: "home-assistant/actions/hassfest@master" - name: HACS Action uses: "hacs/action@main" @@ -23,11 +23,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: asdf_install - uses: asdf-vm/actions/install@v1 + uses: asdf-vm/actions/install@v3 - name: Install Python modules run: | pip install -r requirements.test.txt From 188b00a4fbada8e7cac37141781328f85b9306c5 Mon Sep 17 00:00:00 2001 From: Daniel Raper Date: Wed, 3 Jan 2024 10:12:02 +0000 Subject: [PATCH 03/11] Moved schema to const --- custom_components/ohme/config_flow.py | 9 ++------- custom_components/ohme/const.py | 8 +++++++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/custom_components/ohme/config_flow.py b/custom_components/ohme/config_flow.py index 1d7279b..384f46c 100644 --- a/custom_components/ohme/config_flow.py +++ b/custom_components/ohme/config_flow.py @@ -1,14 +1,9 @@ import voluptuous as vol from homeassistant.config_entries import (ConfigFlow, OptionsFlow) -from .const import DOMAIN +from .const import DOMAIN, CONFIG_SCHEMA from .api_client import OhmeApiClient -USER_SCHEMA = vol.Schema({ - vol.Required("email"): str, - vol.Required("password"): str -}) - class OhmeConfigFlow(ConfigFlow, domain=DOMAIN): """Config flow.""" @@ -28,5 +23,5 @@ async def async_step_user(self, info): ) return self.async_show_form( - step_id="user", data_schema=USER_SCHEMA, errors=errors + step_id="user", data_schema=CONFIG_SCHEMA, errors=errors ) diff --git a/custom_components/ohme/const.py b/custom_components/ohme/const.py index 8f83c0a..94ed1ef 100644 --- a/custom_components/ohme/const.py +++ b/custom_components/ohme/const.py @@ -1,7 +1,13 @@ """Component constants""" +import voluptuous as vol + DOMAIN = "ohme" USER_AGENT = "dan-r-homeassistant-ohme" INTEGRATION_VERSION = "0.2.8" +CONFIG_SCHEMA = vol.Schema({ + vol.Required("email"): str, + vol.Required("password"): str +}) DATA_CLIENT = "client" DATA_COORDINATORS = "coordinators" @@ -9,4 +15,4 @@ COORDINATOR_ACCOUNTINFO = 1 COORDINATOR_STATISTICS = 2 COORDINATOR_ADVANCED = 3 -COORDINATOR_SCHEDULES = 4 \ No newline at end of file +COORDINATOR_SCHEDULES = 4 From 6987484194e66c07f618e2666b054a70fb05ab38 Mon Sep 17 00:00:00 2001 From: Daniel Raper Date: Wed, 3 Jan 2024 10:13:49 +0000 Subject: [PATCH 04/11] Doc updates --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index fcf29c0..22ee495 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,10 @@ A basic integration for interacting with Ohme EV Chargers. This is an unofficial integration. I have no affiliation with Ohme besides owning one of their EV chargers. This integration does not currently support social login or accounts with multiple chargers. It has been tested with the following hardware: -* Ohme Home Pro [UK] -* Ohme Home/Go [UK] -* Ohme ePod [UK] +* Ohme Home Pro [v1.32] +* Ohme Home [v1.32] +* Ohme Go [v1.32] +* Ohme ePod [v2.12] If you find any bugs or would like to request a feature, please open an issue. @@ -27,7 +28,9 @@ This is the recommended installation method. ## Setup -From the Home Assistant Integrations page, search for an add the Ohme integration. If you created your Ohme account through a social login, you will need to 'reset your password' to use this integration. +From the Home Assistant Integrations page, search for and add the Ohme integration. + +If you created your Ohme account through a social login (Apple/Facebook/Google), you will need to set a password in the Ohme app or 'reset your password' to use this integration. ## Entities @@ -54,7 +57,7 @@ This integration exposes the following entities: * Switches (Charge state) - **These are only functional when a car is connected** * Max Charge - Forces the connected car to charge regardless of set schedule * Pause Charge - Pauses an ongoing charge -* Inputs - **If in a charge session, this will change the active charge. If disconnected, this will change your first schedule.** +* Inputs - **If in a charge session, these change the active charge. If disconnected, they change your first schedule.** * Number: Target Percentage - Change the target battery percentage * Time: Target Time - Change the target time * Buttons @@ -70,11 +73,12 @@ The coordinators are listed with their refresh intervals below. Relevant coordin * Buttons: Approve Charge * Sensors: Power, current, voltage and next slot (start & end) * Switches: Max charge, pause charge - * Inputs: Target time and target percentage + * Inputs: Target time and target percentage (If car connected) * OhmeAccountInfoCoordinator (1m refresh) * Switches: Lock buttons, require approval and sleep when inactive * OhmeAdvancedSettingsCoordinator (1m refresh) * Sensors: CT reading sensor * OhmeStatisticsCoordinator (30m refresh) * Sensors: Accumulative energy usage - +* OhmeChargeSchedulesCoordinator (10m refresh) + * Inputs: Target time and target percentage (If car disconnected) From a5d4796b4948a7146c444f17ef48e16dc96b11ad Mon Sep 17 00:00:00 2001 From: Daniel Raper Date: Wed, 3 Jan 2024 10:22:37 +0000 Subject: [PATCH 05/11] Added online sensor --- custom_components/ohme/api_client.py | 6 +-- custom_components/ohme/binary_sensor.py | 56 ++++++++++++++++++++++--- custom_components/ohme/coordinator.py | 2 +- custom_components/ohme/sensor.py | 2 +- 4 files changed, 56 insertions(+), 10 deletions(-) diff --git a/custom_components/ohme/api_client.py b/custom_components/ohme/api_client.py index 9ddd6df..00709a0 100644 --- a/custom_components/ohme/api_client.py +++ b/custom_components/ohme/api_client.py @@ -303,15 +303,15 @@ async def async_get_charge_statistics(self): return resp['totalStats'] - async def async_get_ct_reading(self): - """Get CT clamp reading.""" + async def async_get_advanced_settings(self): + """Get advanced settings (mainly for CT clamp reading)""" resp = await self._get_request(f"/v1/chargeDevices/{self._serial}/advancedSettings") # If we ever get a reading above 0, assume CT connected if resp['clampAmps'] and resp['clampAmps'] > 0: self._ct_connected = True - return resp['clampAmps'] + return resp # Exceptions diff --git a/custom_components/ohme/binary_sensor.py b/custom_components/ohme/binary_sensor.py index 263f45b..a1e0a97 100644 --- a/custom_components/ohme/binary_sensor.py +++ b/custom_components/ohme/binary_sensor.py @@ -9,8 +9,8 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import generate_entity_id from homeassistant.util.dt import (utcnow) -from .const import DOMAIN, DATA_COORDINATORS, COORDINATOR_CHARGESESSIONS, DATA_CLIENT -from .coordinator import OhmeChargeSessionsCoordinator +from .const import DOMAIN, DATA_COORDINATORS, COORDINATOR_CHARGESESSIONS, COORDINATOR_ADVANCED, DATA_CLIENT +from .coordinator import OhmeChargeSessionsCoordinator, OhmeAdvancedSettingsCoordinator from .utils import charge_graph_in_slot _LOGGER = logging.getLogger(__name__) @@ -23,11 +23,13 @@ async def async_setup_entry( """Setup sensors and configure coordinator.""" client = hass.data[DOMAIN][DATA_CLIENT] coordinator = hass.data[DOMAIN][DATA_COORDINATORS][COORDINATOR_CHARGESESSIONS] + coordinator_advanced = hass.data[DOMAIN][DATA_COORDINATORS][COORDINATOR_ADVANCED] sensors = [ConnectedBinarySensor(coordinator, hass, client), ChargingBinarySensor(coordinator, hass, client), PendingApprovalBinarySensor(coordinator, hass, client), - CurrentSlotBinarySensor(coordinator, hass, client)] + CurrentSlotBinarySensor(coordinator, hass, client), + ChargerOnlineBinarySensor(coordinator_advanced, hass, client)] async_add_entities(sensors, update_before_add=True) @@ -119,7 +121,7 @@ def icon(self): @property def unique_id(self) -> str: """Return the unique ID of the sensor.""" - return self._client.get_unique_id("ohme_car_charging") + return self._client.get_unique_id("car_charging") @property def is_on(self) -> bool: @@ -286,7 +288,7 @@ def icon(self): @property def unique_id(self) -> str: """Return the unique ID of the sensor.""" - return self._client.get_unique_id("ohme_slot_active") + return self._client.get_unique_id("slot_active") @property def is_on(self) -> bool: @@ -306,3 +308,47 @@ def _handle_coordinator_update(self) -> None: self._last_updated = utcnow() self.async_write_ha_state() + +class ChargerOnlineBinarySensor( + CoordinatorEntity[OhmeAdvancedSettingsCoordinator], + BinarySensorEntity): + """Binary sensor for if charger is online.""" + + _attr_name = "Charger Status" + _attr_device_class = BinarySensorDeviceClass.CONNECTIVITY + + def __init__( + self, + coordinator: OhmeAdvancedSettingsCoordinator, + hass: HomeAssistant, + client): + super().__init__(coordinator=coordinator) + + self._attributes = {} + self._last_updated = None + self._state = None + self._client = client + + self.entity_id = generate_entity_id( + "binary_sensor.{}", "ohme_charger_online", hass=hass) + + self._attr_device_info = hass.data[DOMAIN][DATA_CLIENT].get_device_info( + ) + + @property + def icon(self): + """Icon of the sensor.""" + return "mdi:calendar-check" + + @property + def unique_id(self) -> str: + """Return the unique ID of the sensor.""" + return self._client.get_unique_id("charger_online") + + @property + def is_on(self) -> bool: + if self.coordinator.data and self.coordinator.data["online"]: + return True + elif self.coordinator.data: + return False + return None diff --git a/custom_components/ohme/coordinator.py b/custom_components/ohme/coordinator.py index 9451859..253ebde 100644 --- a/custom_components/ohme/coordinator.py +++ b/custom_components/ohme/coordinator.py @@ -93,7 +93,7 @@ def __init__(self, hass): async def _async_update_data(self): """Fetch data from API endpoint.""" try: - return await self._client.async_get_ct_reading() + return await self._client.async_get_advanced_settings() except BaseException: raise UpdateFailed("Error communicating with API") diff --git a/custom_components/ohme/sensor.py b/custom_components/ohme/sensor.py index dd64c3b..faba6b8 100644 --- a/custom_components/ohme/sensor.py +++ b/custom_components/ohme/sensor.py @@ -202,7 +202,7 @@ def icon(self): @property def native_value(self): """Get value from data returned from API by coordinator""" - return self.coordinator.data + return self.coordinator.data['clampAmps'] class EnergyUsageSensor(CoordinatorEntity[OhmeStatisticsCoordinator], SensorEntity): From aa868458a99fdd5797969c19d4dcd34514195bfd Mon Sep 17 00:00:00 2001 From: Daniel Raper Date: Wed, 3 Jan 2024 10:22:46 +0000 Subject: [PATCH 06/11] Revert "Moved schema to const" This reverts commit 188b00a4fbada8e7cac37141781328f85b9306c5. --- custom_components/ohme/config_flow.py | 9 +++++++-- custom_components/ohme/const.py | 8 +------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/custom_components/ohme/config_flow.py b/custom_components/ohme/config_flow.py index 384f46c..1d7279b 100644 --- a/custom_components/ohme/config_flow.py +++ b/custom_components/ohme/config_flow.py @@ -1,9 +1,14 @@ import voluptuous as vol from homeassistant.config_entries import (ConfigFlow, OptionsFlow) -from .const import DOMAIN, CONFIG_SCHEMA +from .const import DOMAIN from .api_client import OhmeApiClient +USER_SCHEMA = vol.Schema({ + vol.Required("email"): str, + vol.Required("password"): str +}) + class OhmeConfigFlow(ConfigFlow, domain=DOMAIN): """Config flow.""" @@ -23,5 +28,5 @@ async def async_step_user(self, info): ) return self.async_show_form( - step_id="user", data_schema=CONFIG_SCHEMA, errors=errors + step_id="user", data_schema=USER_SCHEMA, errors=errors ) diff --git a/custom_components/ohme/const.py b/custom_components/ohme/const.py index 94ed1ef..8f83c0a 100644 --- a/custom_components/ohme/const.py +++ b/custom_components/ohme/const.py @@ -1,13 +1,7 @@ """Component constants""" -import voluptuous as vol - DOMAIN = "ohme" USER_AGENT = "dan-r-homeassistant-ohme" INTEGRATION_VERSION = "0.2.8" -CONFIG_SCHEMA = vol.Schema({ - vol.Required("email"): str, - vol.Required("password"): str -}) DATA_CLIENT = "client" DATA_COORDINATORS = "coordinators" @@ -15,4 +9,4 @@ COORDINATOR_ACCOUNTINFO = 1 COORDINATOR_STATISTICS = 2 COORDINATOR_ADVANCED = 3 -COORDINATOR_SCHEDULES = 4 +COORDINATOR_SCHEDULES = 4 \ No newline at end of file From 42963d373c0baf78dedbf9b56e4e064d3231a9e6 Mon Sep 17 00:00:00 2001 From: Daniel Raper Date: Wed, 3 Jan 2024 10:25:37 +0000 Subject: [PATCH 07/11] Don't change unique ids --- custom_components/ohme/binary_sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/ohme/binary_sensor.py b/custom_components/ohme/binary_sensor.py index a1e0a97..87643aa 100644 --- a/custom_components/ohme/binary_sensor.py +++ b/custom_components/ohme/binary_sensor.py @@ -121,7 +121,7 @@ def icon(self): @property def unique_id(self) -> str: """Return the unique ID of the sensor.""" - return self._client.get_unique_id("car_charging") + return self._client.get_unique_id("ohme_car_charging") @property def is_on(self) -> bool: @@ -288,7 +288,7 @@ def icon(self): @property def unique_id(self) -> str: """Return the unique ID of the sensor.""" - return self._client.get_unique_id("slot_active") + return self._client.get_unique_id("ohmr_slot_active") @property def is_on(self) -> bool: From 2f5d7beec7d536fac5112528e5ce8f2d23247d41 Mon Sep 17 00:00:00 2001 From: Daniel Raper Date: Wed, 3 Jan 2024 10:29:13 +0000 Subject: [PATCH 08/11] Changed icons and docs --- README.md | 4 +++- custom_components/ohme/binary_sensor.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 22ee495..8120f3f 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ This integration exposes the following entities: * Car Charging - On when a car is connected and drawing power * Pending Approval - On when a car is connected and waiting for approval * Charge Slot Active - On when a charge slot is in progress according to the Ohme-generated charge plan + * Charger Status - On if charger is online and connected to the internet * Sensors (Charge power) - **Only available during a charge session** * Power Draw (Watts) - Power draw of connected car * Current Draw (Amps) - Current draw of connected car @@ -69,7 +70,7 @@ Updates are made to entity states by polling the Ohme API. This is handled by 'c The coordinators are listed with their refresh intervals below. Relevant coordinators are also refreshed when using switches and buttons. * OhmeChargeSessionsCoordinator (30s refresh) - * Binary Sensors: All + * Binary Sensors: Car connected, car charging, pending approval and charge slot active * Buttons: Approve Charge * Sensors: Power, current, voltage and next slot (start & end) * Switches: Max charge, pause charge @@ -78,6 +79,7 @@ The coordinators are listed with their refresh intervals below. Relevant coordin * Switches: Lock buttons, require approval and sleep when inactive * OhmeAdvancedSettingsCoordinator (1m refresh) * Sensors: CT reading sensor + * Binary Sensors: Charger status * OhmeStatisticsCoordinator (30m refresh) * Sensors: Accumulative energy usage * OhmeChargeSchedulesCoordinator (10m refresh) diff --git a/custom_components/ohme/binary_sensor.py b/custom_components/ohme/binary_sensor.py index 87643aa..da3f1ff 100644 --- a/custom_components/ohme/binary_sensor.py +++ b/custom_components/ohme/binary_sensor.py @@ -338,7 +338,7 @@ def __init__( @property def icon(self): """Icon of the sensor.""" - return "mdi:calendar-check" + return "mdi:web" @property def unique_id(self) -> str: From 2c50629711a47f01f17bff2853fdacde3456387a Mon Sep 17 00:00:00 2001 From: Daniel Raper Date: Wed, 3 Jan 2024 10:30:21 +0000 Subject: [PATCH 09/11] Typo --- custom_components/ohme/binary_sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/ohme/binary_sensor.py b/custom_components/ohme/binary_sensor.py index da3f1ff..b5b4012 100644 --- a/custom_components/ohme/binary_sensor.py +++ b/custom_components/ohme/binary_sensor.py @@ -288,7 +288,7 @@ def icon(self): @property def unique_id(self) -> str: """Return the unique ID of the sensor.""" - return self._client.get_unique_id("ohmr_slot_active") + return self._client.get_unique_id("ohme_slot_active") @property def is_on(self) -> bool: From c7518bfc8038fb5aae46f3bd6228692eff6cbfd9 Mon Sep 17 00:00:00 2001 From: Daniel Raper Date: Wed, 3 Jan 2024 10:32:19 +0000 Subject: [PATCH 10/11] Rename charger online sensor --- README.md | 4 ++-- custom_components/ohme/binary_sensor.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8120f3f..3779ced 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ This integration exposes the following entities: * Car Charging - On when a car is connected and drawing power * Pending Approval - On when a car is connected and waiting for approval * Charge Slot Active - On when a charge slot is in progress according to the Ohme-generated charge plan - * Charger Status - On if charger is online and connected to the internet + * Charger Online - On if charger is online and connected to the internet * Sensors (Charge power) - **Only available during a charge session** * Power Draw (Watts) - Power draw of connected car * Current Draw (Amps) - Current draw of connected car @@ -79,7 +79,7 @@ The coordinators are listed with their refresh intervals below. Relevant coordin * Switches: Lock buttons, require approval and sleep when inactive * OhmeAdvancedSettingsCoordinator (1m refresh) * Sensors: CT reading sensor - * Binary Sensors: Charger status + * Binary Sensors: Charger online * OhmeStatisticsCoordinator (30m refresh) * Sensors: Accumulative energy usage * OhmeChargeSchedulesCoordinator (10m refresh) diff --git a/custom_components/ohme/binary_sensor.py b/custom_components/ohme/binary_sensor.py index b5b4012..cf50307 100644 --- a/custom_components/ohme/binary_sensor.py +++ b/custom_components/ohme/binary_sensor.py @@ -314,7 +314,7 @@ class ChargerOnlineBinarySensor( BinarySensorEntity): """Binary sensor for if charger is online.""" - _attr_name = "Charger Status" + _attr_name = "Charger Online" _attr_device_class = BinarySensorDeviceClass.CONNECTIVITY def __init__( From 797a0acb90f4f00cd00e0ffad834c884dcc15a9d Mon Sep 17 00:00:00 2001 From: Daniel Raper Date: Wed, 3 Jan 2024 10:34:16 +0000 Subject: [PATCH 11/11] Updated version --- custom_components/ohme/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/ohme/const.py b/custom_components/ohme/const.py index 8f83c0a..b98528b 100644 --- a/custom_components/ohme/const.py +++ b/custom_components/ohme/const.py @@ -1,7 +1,7 @@ """Component constants""" DOMAIN = "ohme" USER_AGENT = "dan-r-homeassistant-ohme" -INTEGRATION_VERSION = "0.2.8" +INTEGRATION_VERSION = "0.3.0" DATA_CLIENT = "client" DATA_COORDINATORS = "coordinators"