Skip to content

Commit

Permalink
Add switches for charger settings (#6)
Browse files Browse the repository at this point in the history
* Add settings switches

* Add capability check

* Doc updates

* Doc updates
  • Loading branch information
dan-r committed Dec 28, 2023
1 parent 6fad42c commit 44d796d
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 41 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ This is an unofficial integration. I have no affiliation with Ohme besides ownin

This has only be tested with an Ohme Home Pro and does not currently support social login or accounts with multiple chargers.

It's still very early in development but I plan to add more sensors and support for pausing/resuming charge.

## Entities
This integration exposes the following entities:

Expand All @@ -18,7 +16,11 @@ This integration exposes the following entities:
* Power Draw (Watts) - Power draw of connected car
* Accumulative Energy Usage (kWh) - Total energy used by the charger
* Next Smart Charge Slot - The next time your car will start charging according to the Ohme-generated charge plan
* Switches - These are only functional when a car is connected
* Switches (Settings) - Only options available to your charger model will show
* Lock Buttons - Locks buttons on charger
* Require Approval - Require approval to start a charge
* Sleep When Inactive - Charger screen & lights will automatically turn off
* 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

Expand All @@ -36,4 +38,4 @@ This is the recommended installation method.
3. Restart Home Assistant

## 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 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.
12 changes: 8 additions & 4 deletions custom_components/ohme/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from homeassistant import core
from .const import *
from .api_client import OhmeApiClient
from .coordinator import OhmeUpdateCoordinator, OhmeStatisticsUpdateCoordinator
from .coordinator import OhmeChargeSessionsCoordinator, OhmeStatisticsCoordinator, OhmeAccountInfoCoordinator


async def async_setup(hass: core.HomeAssistant, config: dict) -> bool:
Expand Down Expand Up @@ -31,13 +31,17 @@ async def async_setup_entry(hass, entry):

await async_setup_dependencies(hass, config)

hass.data[DOMAIN][DATA_COORDINATOR] = OhmeUpdateCoordinator(hass=hass)
await hass.data[DOMAIN][DATA_COORDINATOR].async_config_entry_first_refresh()
hass.data[DOMAIN][DATA_CHARGESESSIONS_COORDINATOR] = OhmeChargeSessionsCoordinator(hass=hass)
await hass.data[DOMAIN][DATA_CHARGESESSIONS_COORDINATOR].async_config_entry_first_refresh()

hass.data[DOMAIN][DATA_STATISTICS_COORDINATOR] = OhmeStatisticsUpdateCoordinator(
hass.data[DOMAIN][DATA_STATISTICS_COORDINATOR] = OhmeStatisticsCoordinator(
hass=hass)
await hass.data[DOMAIN][DATA_STATISTICS_COORDINATOR].async_config_entry_first_refresh()

hass.data[DOMAIN][DATA_ACCOUNTINFO_COORDINATOR] = OhmeAccountInfoCoordinator(
hass=hass)
await hass.data[DOMAIN][DATA_ACCOUNTINFO_COORDINATOR].async_config_entry_first_refresh()

# Create tasks for each entity type
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "sensor")
Expand Down
29 changes: 26 additions & 3 deletions custom_components/ohme/api_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import aiohttp
import logging
import json
from datetime import datetime, timedelta
from homeassistant.helpers.entity import DeviceInfo
from .const import DOMAIN
Expand All @@ -18,6 +19,7 @@ def __init__(self, email, password):
self._password = password

self._device_info = None
self._capabilities = {}
self._token = None
self._user_id = ""
self._serial = ""
Expand Down Expand Up @@ -63,8 +65,11 @@ async def _put_request(self, url, data=None, is_retry=False):
If we get a non 200 response, refresh auth token and try again"""
async with self._session.put(
url,
data=data,
headers={"Authorization": "Firebase %s" % self._token}
data=json.dumps(data),
headers={
"Authorization": "Firebase %s" % self._token,
"Content-Type": "application/json"
}
) as resp:
if resp.status != 200 and not is_retry:
await self.async_refresh_session()
Expand Down Expand Up @@ -110,6 +115,11 @@ async def async_stop_max_charge(self):
result = await self._put_request(f"https://api.ohme.io/v1/chargeSessions/{self._serial}/rule?enableMaxPrice=false&toPercent=80.0&inSeconds=43200")
return bool(result)

async def async_set_configuration_value(self, values):
"""Set a configuration value or values."""
result = await self._put_request(f"https://api.ohme.io/v1/chargeDevices/{self._serial}/appSettings", data=values)
return bool(result)

async def async_get_charge_sessions(self, is_retry=False):
"""Try to fetch charge sessions endpoint.
If we get a non 200 response, refresh auth token and try again"""
Expand All @@ -120,9 +130,17 @@ async def async_get_charge_sessions(self, is_retry=False):

return resp[0]

async def async_get_account_info(self):
resp = await self._get_request('https://api.ohme.io/v1/users/me/account')

if not resp:
return False

return resp

async def async_update_device_info(self, is_retry=False):
"""Update _device_info with our charger model."""
resp = await self._get_request('https://api.ohme.io/v1/users/me/account')
resp = await self.async_get_account_info()

if not resp:
return False
Expand All @@ -138,12 +156,17 @@ async def async_update_device_info(self, is_retry=False):
serial_number=device['id']
)

self._capabilities = device['modelCapabilities']
self._user_id = resp['user']['id']
self._serial = device['id']
self._device_info = info

return True

def is_capable(self, capability):
"""Return whether or not this model has a given capability."""
return bool(self._capabilities[capability])

def _last_second_of_month_timestamp(self):
"""Get the last second of this month."""
dt = datetime.today()
Expand Down
14 changes: 7 additions & 7 deletions custom_components/ohme/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import generate_entity_id
from .const import DOMAIN, DATA_COORDINATOR, DATA_CLIENT
from .coordinator import OhmeUpdateCoordinator
from .const import DOMAIN, DATA_CHARGESESSIONS_COORDINATOR, DATA_CLIENT
from .coordinator import OhmeChargeSessionsCoordinator


async def async_setup_entry(
Expand All @@ -19,7 +19,7 @@ async def async_setup_entry(
):
"""Setup sensors and configure coordinator."""
client = hass.data[DOMAIN][DATA_CLIENT]
coordinator = hass.data[DOMAIN][DATA_COORDINATOR]
coordinator = hass.data[DOMAIN][DATA_CHARGESESSIONS_COORDINATOR]

sensors = [ConnectedSensor(coordinator, hass, client),
ChargingSensor(coordinator, hass, client)]
Expand All @@ -28,7 +28,7 @@ async def async_setup_entry(


class ConnectedSensor(
CoordinatorEntity[OhmeUpdateCoordinator],
CoordinatorEntity[OhmeChargeSessionsCoordinator],
BinarySensorEntity):
"""Binary sensor for if car is plugged in."""

Expand All @@ -37,7 +37,7 @@ class ConnectedSensor(

def __init__(
self,
coordinator: OhmeUpdateCoordinator,
coordinator: OhmeChargeSessionsCoordinator,
hass: HomeAssistant,
client):
super().__init__(coordinator=coordinator)
Expand Down Expand Up @@ -74,7 +74,7 @@ def is_on(self) -> bool:


class ChargingSensor(
CoordinatorEntity[OhmeUpdateCoordinator],
CoordinatorEntity[OhmeChargeSessionsCoordinator],
BinarySensorEntity):
"""Binary sensor for if car is charging."""

Expand All @@ -83,7 +83,7 @@ class ChargingSensor(

def __init__(
self,
coordinator: OhmeUpdateCoordinator,
coordinator: OhmeChargeSessionsCoordinator,
hass: HomeAssistant,
client):
super().__init__(coordinator=coordinator)
Expand Down
3 changes: 2 additions & 1 deletion custom_components/ohme/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@

DOMAIN = "ohme"
DATA_CLIENT = "client"
DATA_COORDINATOR = "coordinator"
DATA_CHARGESESSIONS_COORDINATOR = "coordinator"
DATA_STATISTICS_COORDINATOR = "statistics_coordinator"
DATA_ACCOUNTINFO_COORDINATOR = "accountinfo_coordinator"
25 changes: 23 additions & 2 deletions custom_components/ohme/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
_LOGGER = logging.getLogger(__name__)


class OhmeUpdateCoordinator(DataUpdateCoordinator):
class OhmeChargeSessionsCoordinator(DataUpdateCoordinator):
"""Coordinator to pull from API periodically."""

def __init__(self, hass):
Expand All @@ -32,8 +32,29 @@ async def _async_update_data(self):
except BaseException:
raise UpdateFailed("Error communicating with API")

class OhmeAccountInfoCoordinator(DataUpdateCoordinator):
"""Coordinator to pull from API periodically."""

def __init__(self, hass):
"""Initialise coordinator."""
super().__init__(
hass,
_LOGGER,
name="Ohme Account Info",
update_interval=timedelta(minutes=1),
)
self._client = hass.data[DOMAIN][DATA_CLIENT]

async def _async_update_data(self):
"""Fetch data from API endpoint."""
try:
return await self._client.async_get_account_info()

except BaseException:
raise UpdateFailed("Error communicating with API")


class OhmeStatisticsUpdateCoordinator(DataUpdateCoordinator):
class OhmeStatisticsCoordinator(DataUpdateCoordinator):
"""Coordinator to update statistics from API periodically.
(But less so than OhmeUpdateCoordinator)"""

Expand Down
18 changes: 9 additions & 9 deletions custom_components/ohme/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,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_CLIENT, DATA_COORDINATOR, DATA_STATISTICS_COORDINATOR
from .coordinator import OhmeUpdateCoordinator, OhmeStatisticsUpdateCoordinator
from .const import DOMAIN, DATA_CLIENT, DATA_CHARGESESSIONS_COORDINATOR, DATA_STATISTICS_COORDINATOR
from .coordinator import OhmeChargeSessionsCoordinator, OhmeStatisticsCoordinator
from .utils import charge_graph_next_slot


Expand All @@ -22,7 +22,7 @@ async def async_setup_entry(
):
"""Setup sensors and configure coordinator."""
client = hass.data[DOMAIN][DATA_CLIENT]
coordinator = hass.data[DOMAIN][DATA_COORDINATOR]
coordinator = hass.data[DOMAIN][DATA_CHARGESESSIONS_COORDINATOR]
stats_coordinator = hass.data[DOMAIN][DATA_STATISTICS_COORDINATOR]

sensors = [PowerDrawSensor(coordinator, hass, client), EnergyUsageSensor(
Expand All @@ -31,15 +31,15 @@ async def async_setup_entry(
async_add_entities(sensors, update_before_add=True)


class PowerDrawSensor(CoordinatorEntity[OhmeUpdateCoordinator], SensorEntity):
class PowerDrawSensor(CoordinatorEntity[OhmeChargeSessionsCoordinator], SensorEntity):
"""Sensor for car power draw."""
_attr_name = "Current Power Draw"
_attr_native_unit_of_measurement = UnitOfPower.WATT
_attr_device_class = SensorDeviceClass.POWER

def __init__(
self,
coordinator: OhmeUpdateCoordinator,
coordinator: OhmeChargeSessionsCoordinator,
hass: HomeAssistant,
client):
super().__init__(coordinator=coordinator)
Expand Down Expand Up @@ -73,15 +73,15 @@ def native_value(self):
return 0


class EnergyUsageSensor(CoordinatorEntity[OhmeStatisticsUpdateCoordinator], SensorEntity):
class EnergyUsageSensor(CoordinatorEntity[OhmeStatisticsCoordinator], SensorEntity):
"""Sensor for total energy usage."""
_attr_name = "Accumulative Energy Usage"
_attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR
_attr_device_class = SensorDeviceClass.ENERGY

def __init__(
self,
coordinator: OhmeUpdateCoordinator,
coordinator: OhmeChargeSessionsCoordinator,
hass: HomeAssistant,
client):
super().__init__(coordinator=coordinator)
Expand Down Expand Up @@ -116,14 +116,14 @@ def native_value(self):
return None


class NextSlotSensor(CoordinatorEntity[OhmeStatisticsUpdateCoordinator], SensorEntity):
class NextSlotSensor(CoordinatorEntity[OhmeStatisticsCoordinator], SensorEntity):
"""Sensor for next smart charge slot."""
_attr_name = "Next Smart Charge Slot"
_attr_device_class = SensorDeviceClass.TIMESTAMP

def __init__(
self,
coordinator: OhmeUpdateCoordinator,
coordinator: OhmeChargeSessionsCoordinator,
hass: HomeAssistant,
client):
super().__init__(coordinator=coordinator)
Expand Down
Loading

0 comments on commit 44d796d

Please sign in to comment.