Skip to content

Commit

Permalink
Added price cap support (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
dan-r committed Jan 20, 2024
1 parent dff1362 commit 7bff0af
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 5 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ 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
* Enable Price Cap - Whether price cap is applied
* 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
* Preconditioning - Change pre-conditioning time. 0 is off
* Price Cap - Maximum charge price
* Time
* Target Time - Change the target time
* Buttons
Expand All @@ -84,7 +86,8 @@ The coordinators are listed with their refresh intervals below. Relevant coordin
* Switches: Max charge, pause charge
* Inputs: Target time, target percentage and preconditioning (If car connected)
* OhmeAccountInfoCoordinator (1m refresh)
* Switches: Lock buttons, require approval and sleep when inactive
* Switches: Lock buttons, require approval, sleep when inactive and enable price cap
* Inputs: Price cap
* OhmeAdvancedSettingsCoordinator (1m refresh)
* Sensors: CT reading sensor
* Binary Sensors: Charger online
Expand Down
12 changes: 12 additions & 0 deletions custom_components/ohme/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,18 @@ async def async_apply_session_rule(self, max_price=None, target_time=None, targe
result = await self._put_request(f"/v1/chargeSessions/{self._serial}/rule?enableMaxPrice={max_price}&targetTs={target_ts}&enablePreconditioning={pre_condition}&toPercent={target_percent}&preconditionLengthMins={pre_condition_length}")
return bool(result)

async def async_change_price_cap(self, enabled=None, cap=None):
"""Change price cap settings."""
settings = await self._get_request("/v1/users/me/settings")
if enabled is not None:
settings['chargeSettings'][0]['enabled'] = enabled

if cap is not None:
settings['chargeSettings'][0]['value'] = cap

result = await self._put_request("/v1/users/me/settings", data=settings)
return bool(result)

async def async_get_schedule(self):
"""Get the first schedule."""
schedules = await self._get_request("/v1/chargeRules")
Expand Down
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.4.0"
INTEGRATION_VERSION = "0.4.1"
CONFIG_VERSION = 1
ENTITY_TYPES = ["sensor", "binary_sensor", "switch", "button", "number", "time"]

Expand Down
62 changes: 60 additions & 2 deletions custom_components/ohme/number.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from __future__ import annotations
import asyncio
from homeassistant.components.number import NumberEntity, NumberDeviceClass
from homeassistant.components.number.const import NumberMode
from homeassistant.const import UnitOfTime
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.core import callback, HomeAssistant
from .const import DOMAIN, DATA_CLIENT, DATA_COORDINATORS, COORDINATOR_CHARGESESSIONS, COORDINATOR_SCHEDULES
from .const import DOMAIN, DATA_CLIENT, DATA_COORDINATORS, COORDINATOR_ACCOUNTINFO, COORDINATOR_CHARGESESSIONS, COORDINATOR_SCHEDULES
from .utils import session_in_progress


Expand All @@ -21,7 +22,8 @@ async def async_setup_entry(
numbers = [TargetPercentNumber(
coordinators[COORDINATOR_CHARGESESSIONS], coordinators[COORDINATOR_SCHEDULES], hass, client),
PreconditioningNumber(
coordinators[COORDINATOR_CHARGESESSIONS], coordinators[COORDINATOR_SCHEDULES], hass, client)]
coordinators[COORDINATOR_CHARGESESSIONS], coordinators[COORDINATOR_SCHEDULES], hass, client),
PriceCapNumber(coordinators[COORDINATOR_ACCOUNTINFO], hass, client)]

async_add_entities(numbers, update_before_add=True)

Expand Down Expand Up @@ -187,3 +189,59 @@ def _handle_coordinator_update(self) -> None:
@property
def native_value(self):
return self._state


class PriceCapNumber(NumberEntity):
_attr_name = "Price Cap"
_attr_native_unit_of_measurement = "p"
_attr_device_class = NumberDeviceClass.MONETARY
_attr_mode = NumberMode.BOX
_attr_native_step = 0.1
_attr_native_min_value = 1
_attr_native_max_value = 100

def __init__(self, coordinator, hass: HomeAssistant, client):
self.coordinator = coordinator
self._client = client
self._state = None
self.entity_id = generate_entity_id(
"number.{}", "ohme_price_cap", hass=hass)

self._attr_device_info = client.get_device_info()

async def async_added_to_hass(self) -> None:
"""When entity is added to hass."""
await super().async_added_to_hass()
self.async_on_remove(
self.coordinator.async_add_listener(
self._handle_coordinator_update, None
)
)

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

async def async_set_native_value(self, value: float) -> None:
"""Update the current value."""
await self._client.async_change_price_cap(cap=value)

await asyncio.sleep(1)
await self.coordinator.async_refresh()

@property
def icon(self):
"""Icon of the sensor."""
return "mdi:cash"

@callback
def _handle_coordinator_update(self) -> None:
"""Get value from data returned from API by coordinator"""
if self.coordinator.data is not None:
self._state = self.coordinator.data["userSettings"]["chargeSettings"][0]["value"]
self.async_write_ha_state()

@property
def native_value(self):
return self._state
54 changes: 53 additions & 1 deletion custom_components/ohme/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ async def async_setup_entry(
client = hass.data[DOMAIN][DATA_CLIENT]

switches = [OhmePauseChargeSwitch(coordinator, hass, client),
OhmeMaxChargeSwitch(coordinator, hass, client)]
OhmeMaxChargeSwitch(coordinator, hass, client),
OhmePriceCapSwitch(accountinfo_coordinator, hass, client)]

if client.is_capable("buttonsLockable"):
switches.append(
Expand Down Expand Up @@ -223,3 +224,54 @@ async def async_turn_off(self):

await asyncio.sleep(1)
await self.coordinator.async_refresh()


class OhmePriceCapSwitch(CoordinatorEntity[OhmeAccountInfoCoordinator], SwitchEntity):
"""Switch for enabling price cap."""
_attr_name = "Enable Price Cap"

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

self._client = client

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

self._attr_device_info = client.get_device_info()

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

@property
def icon(self):
"""Icon of the switch."""
return f"mdi:car-speed-limiter"

@callback
def _handle_coordinator_update(self) -> None:
"""Determine configuration value."""
if self.coordinator.data is None:
self._attr_is_on = None
else:
self._attr_is_on = bool(self.coordinator.data["userSettings"]["chargeSettings"][0]["enabled"])

self._last_updated = utcnow()

self.async_write_ha_state()

async def async_turn_on(self):
"""Turn on the switch."""
await self._client.async_change_price_cap(enabled=True)

await asyncio.sleep(1)
await self.coordinator.async_refresh()

async def async_turn_off(self):
"""Turn off the switch."""
await self._client.async_change_price_cap(enabled=False)

await asyncio.sleep(1)
await self.coordinator.async_refresh()

0 comments on commit 7bff0af

Please sign in to comment.