Skip to content

Commit

Permalink
Added charger online binary sensor (#31)
Browse files Browse the repository at this point in the history
* Require 3 downward state change readings rather than 2

* Updated action versions

* Moved schema to const

* Doc updates

* Added online sensor

* Revert "Moved schema to const"

This reverts commit 188b00a.

* Don't change unique ids

* Changed icons and docs

* Typo

* Rename charger online sensor

* Updated version
  • Loading branch information
dan-r authored Jan 3, 2024
1 parent 799c9f7 commit 3675b08
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 23 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)
6 changes: 3 additions & 3 deletions custom_components/ohme/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
58 changes: 52 additions & 6 deletions custom_components/ohme/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand All @@ -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)

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
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.2.8"
INTEGRATION_VERSION = "0.3.0"

DATA_CLIENT = "client"
DATA_COORDINATORS = "coordinators"
Expand Down
2 changes: 1 addition & 1 deletion custom_components/ohme/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion custom_components/ohme/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down

0 comments on commit 3675b08

Please sign in to comment.