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 diff --git a/README.md b/README.md index fcf29c0..3779ced 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 @@ -38,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 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 @@ -54,7 +58,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 @@ -66,15 +70,17 @@ 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 - * 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 + * Binary Sensors: Charger online * OhmeStatisticsCoordinator (30m refresh) * Sensors: Accumulative energy usage - +* OhmeChargeSchedulesCoordinator (10m refresh) + * Inputs: Target time and target percentage (If car disconnected) 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 a7d123e..cf50307 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) @@ -168,12 +170,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 +184,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 @@ -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 Online" + _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:web" + + @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/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" 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):