Skip to content

Commit

Permalink
Initial implementation of switches for pause/max charge (#1)
Browse files Browse the repository at this point in the history
* Add pause charge switch

* Add max charge switch

* Change update interval
  • Loading branch information
dan-r committed Dec 26, 2023
1 parent cf726c4 commit b2e273b
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 2 deletions.
3 changes: 3 additions & 0 deletions custom_components/ohme/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ async def async_setup_entry(hass, entry):
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "binary_sensor")
)
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "switch")
)

hass.data[DOMAIN][DATA_COORDINATOR] = OhmeUpdateCoordinator(hass=hass)
await hass.data[DOMAIN][DATA_COORDINATOR].async_config_entry_first_refresh()
Expand Down
60 changes: 59 additions & 1 deletion custom_components/ohme/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def __init__(self, email, password):

self._device_info = None
self._token = None
self._serial = ""
self._session = aiohttp.ClientSession()

async def async_refresh_session(self):
Expand All @@ -37,6 +38,63 @@ async def async_refresh_session(self):
self._token = resp_json['idToken']
return True

async def _post_request(self, url, skip_json=False, data=None, is_retry=False):
"""Try to make a POST request
If we get a non 200 response, refresh auth token and try again"""
async with self._session.post(
url,
data=data,
headers={"Authorization": "Firebase %s" % self._token}
) as resp:
if resp.status != 200 and not is_retry:
await self.async_refresh_session()
return await self._post_request(url, skip_json=skip_json, data=data, is_retry=True)
elif resp.status != 200:
return False

if skip_json:
return await resp.text()

resp_json = await resp.json()
return resp_json

async def _put_request(self, url, data=None, is_retry=False):
"""Try to make a PUT request
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}
) as resp:
if resp.status != 200 and not is_retry:
await self.async_refresh_session()
return await self._put_request(url, data=data, is_retry=True)
elif resp.status != 200:
return False

return True

async def async_pause_charge(self):
"""Pause an ongoing charge"""
result = await self._post_request(f"https://api.ohme.io/v1/chargeSessions/{self._serial}/stop", skip_json=True)
return bool(result)

async def async_resume_charge(self):
"""Resume a paused charge"""
result = await self._post_request(f"https://api.ohme.io/v1/chargeSessions/{self._serial}/resume", skip_json=True)
return bool(result)

async def async_max_charge(self):
"""Enable max charge"""
result = await self._put_request(f"https://api.ohme.io/v1/chargeSessions/{self._serial}/rule?maxCharge=true")
return bool(result)

async def async_stop_max_charge(self):
"""Stop max charge.
This is more complicated than starting one as we need to give more parameters."""
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_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 Down Expand Up @@ -78,7 +136,7 @@ async def async_update_device_info(self, is_retry=False):
sw_version=device['firmwareVersionLabel'],
serial_number=device['id']
)

self._serial = device['id']
self._device_info = info

def get_device_info(self):
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 @@ -20,7 +20,7 @@ def __init__(self, hass):
hass,
_LOGGER,
name="Ohme Charger",
update_interval=timedelta(seconds=60),
update_interval=timedelta(seconds=30),
)
self._client = hass.data[DOMAIN][DATA_CLIENT]

Expand Down
146 changes: 146 additions & 0 deletions custom_components/ohme/switch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
from __future__ import annotations
import logging

from homeassistant.core import callback, HomeAssistant
from homeassistant.helpers.entity import generate_entity_id

from homeassistant.helpers.update_coordinator import (
CoordinatorEntity
)
from homeassistant.components.switch import SwitchEntity
from homeassistant.util.dt import (utcnow)

from .const import DOMAIN, DATA_CLIENT, DATA_COORDINATOR
from .coordinator import OhmeUpdateCoordinator

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(
hass: HomeAssistant,
config_entry: config_entries.ConfigEntry,
async_add_entities
):
"""Setup switches and configure coordinator."""
coordinator = hass.data[DOMAIN][DATA_COORDINATOR]
client = hass.data[DOMAIN][DATA_CLIENT]

buttons = [OhmePauseCharge(coordinator, hass, client),
OhmeMaxCharge(coordinator, hass, client)]

async_add_entities(buttons, update_before_add=True)


class OhmePauseCharge(CoordinatorEntity[OhmeUpdateCoordinator], SwitchEntity):
"""Switch for pausing a charge."""
_attr_name = "Ohme Pause Charge"

def __init__(self, coordinator, hass: HomeAssistant, client):
super().__init__(coordinator=coordinator)

self._client = client

self._state = False
self._last_updated = None
self._attributes = {}

self.entity_id = generate_entity_id(
"switch.{}", "ohme_pause_charge", hass=hass)

self._attr_device_info = client.get_device_info()

@property
def unique_id(self):
"""The unique ID of the switch."""
return self.entity_id

@property
def icon(self):
"""Icon of the switch."""
return "mdi:pause"

@callback
def _handle_coordinator_update(self) -> None:
"""Determine if charge is paused.
We handle this differently to the sensors as the state of this switch
is changed 'optimistically' to stop the switch flicking back then forth."""
if self.coordinator.data is None:
self._attr_is_on = False
else:
self._attr_is_on = bool(self.coordinator.data["mode"] == "STOPPED")

self.async_write_ha_state()

async def async_turn_on(self):
"""Turn on the switch."""
await self._client.async_pause_charge()

self._attr_is_on = True
self._last_updated = utcnow()
self.async_write_ha_state()

await self.coordinator.async_refresh()

async def async_turn_off(self):
"""Turn off the switch."""
await self._client.async_resume_charge()

self._attr_is_on = False
self._last_updated = utcnow()
self.async_write_ha_state()

await self.coordinator.async_refresh()

class OhmeMaxCharge(CoordinatorEntity[OhmeUpdateCoordinator], SwitchEntity):
"""Switch for pausing a charge."""
_attr_name = "Ohme Max Charge"

def __init__(self, coordinator, hass: HomeAssistant, client):
super().__init__(coordinator=coordinator)

self._client = client

self._state = False
self._last_updated = None
self._attributes = {}

self.entity_id = generate_entity_id(
"switch.{}", "ohme_max_charge", hass=hass)

self._attr_device_info = client.get_device_info()

@property
def unique_id(self):
"""The unique ID of the switch."""
return self.entity_id

@property
def icon(self):
"""Icon of the switch."""
return "mdi:battery-arrow-up"

@callback
def _handle_coordinator_update(self) -> None:
"""Determine if we are max charging."""
if self.coordinator.data is None:
self._attr_is_on = False
else:
self._attr_is_on = bool(self.coordinator.data["mode"] == "MAX_CHARGE")

self.async_write_ha_state()

async def async_turn_on(self):
"""Turn on the switch."""
await self._client.async_max_charge()

self._attr_is_on = True
self._last_updated = utcnow()
self.async_write_ha_state()

async def async_turn_off(self):
"""Turn off the switch."""
await self._client.async_stop_max_charge()

self._attr_is_on = False
self._last_updated = utcnow()
self.async_write_ha_state()

0 comments on commit b2e273b

Please sign in to comment.