Skip to content

Commit

Permalink
Added preconditioning input
Browse files Browse the repository at this point in the history
  • Loading branch information
dan-r committed Jan 17, 2024
1 parent a348ac1 commit 6a52f57
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 7 deletions.
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,19 @@ This integration exposes the following entities:
* Max Charge - Forces the connected car to charge regardless of set schedule
* Pause Charge - Pauses an ongoing charge
* 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
* Number
* Target Percentage - Change the target battery percentage
* Preconditioning - Change pre-conditioning time. 0 is off
* Time
* Target Time - Change the target time
* Buttons
* Approve Charge - Approves a charge when 'Pending Approval' is on

## Options
Some options can be set from the 'Configure' menu in Home Assistant:
* Never update an ongoing session - Override the default behaviour of the target time, percentage and preconditioning inputs and only ever update the schedule, not the current session. This was added as changing the current session can cause issues for customers on Intelligent Octopus Go.


## Coordinators
Updates are made to entity states by polling the Ohme API. This is handled by 'coordinators' defined to Home Assistant, which refresh at a set interval or when externally triggered.

Expand All @@ -74,7 +82,7 @@ The coordinators are listed with their refresh intervals below. Relevant coordin
* Buttons: Approve Charge
* Sensors: Power, current, voltage and next slot (start & end)
* Switches: Max charge, pause charge
* Inputs: Target time and target percentage (If car connected)
* Inputs: Target time, target percentage and preconditioning (If car connected)
* OhmeAccountInfoCoordinator (1m refresh)
* Switches: Lock buttons, require approval and sleep when inactive
* OhmeAdvancedSettingsCoordinator (1m refresh)
Expand All @@ -83,4 +91,4 @@ The coordinators are listed with their refresh intervals below. Relevant coordin
* OhmeStatisticsCoordinator (30m refresh)
* Sensors: Accumulative energy usage
* OhmeChargeSchedulesCoordinator (10m refresh)
* Inputs: Target time and target percentage (If car disconnected)
* Inputs: Target time, target percentage and preconditioning (If car disconnected)
8 changes: 7 additions & 1 deletion custom_components/ohme/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ async def async_get_schedule(self):

return schedules[0] if len(schedules) > 0 else None

async def async_update_schedule(self, target_percent=None, target_time=None):
async def async_update_schedule(self, target_percent=None, target_time=None, pre_condition=None, pre_condition_length=None):
"""Update the first listed schedule."""
rule = await self.async_get_schedule()

Expand All @@ -247,6 +247,12 @@ async def async_update_schedule(self, target_percent=None, target_time=None):
if target_time is not None:
rule['targetTime'] = (target_time[0] * 3600) + (target_time[1] * 60)

# Update pre-conditioning if provided
if pre_condition is not None:
rule['preconditioningEnabled'] = pre_condition
if pre_condition_length is not None:
rule['preconditionLengthMins'] = pre_condition_length

await self._put_request(f"/v1/chargeRules/{rule['id']}", data=rule)
return True

Expand Down
98 changes: 96 additions & 2 deletions custom_components/ohme/number.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from __future__ import annotations
import asyncio
from homeassistant.components.number import NumberEntity, NumberDeviceClass
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 .utils import session_in_progress


async def async_setup_entry(
hass: HomeAssistant,
config_entry: config_entries.ConfigEntry,
Expand All @@ -17,6 +19,8 @@ async def async_setup_entry(
client = hass.data[DOMAIN][DATA_CLIENT]

numbers = [TargetPercentNumber(
coordinators[COORDINATOR_CHARGESESSIONS], coordinators[COORDINATOR_SCHEDULES], hass, client),
PreconditioningNumber(
coordinators[COORDINATOR_CHARGESESSIONS], coordinators[COORDINATOR_SCHEDULES], hass, client)]

async_add_entities(numbers, update_before_add=True)
Expand Down Expand Up @@ -56,7 +60,7 @@ async def async_added_to_hass(self) -> None:
self._handle_coordinator_update, None
)
)

@property
def unique_id(self):
"""The unique ID of the switch."""
Expand Down Expand Up @@ -84,7 +88,8 @@ def _handle_coordinator_update(self) -> None:
"""Get value from data returned from API by coordinator"""
# Set with the same logic as reading
if session_in_progress(self.hass, self.coordinator.data):
target = round(self.coordinator.data['appliedRule']['targetPercent'])
target = round(
self.coordinator.data['appliedRule']['targetPercent'])
elif self.coordinator_schedules.data:
target = round(self.coordinator_schedules.data['targetPercent'])

Expand All @@ -93,3 +98,92 @@ def _handle_coordinator_update(self) -> None:
@property
def native_value(self):
return self._state


class PreconditioningNumber(NumberEntity):
"""Preconditioning sensor."""
_attr_name = "Preconditioning"
_attr_device_class = NumberDeviceClass.DURATION
_attr_native_unit_of_measurement = UnitOfTime.MINUTES
_attr_native_min_value = 0
_attr_native_step = 5
_attr_native_max_value = 60

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

self._client = client

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

self.entity_id = generate_entity_id(
"number.{}", "ohme_preconditioning", 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
)
)
self.async_on_remove(
self.coordinator_schedules.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("preconditioning")

async def async_set_native_value(self, value: float) -> None:
"""Update the current value."""
# If session in progress, update this session, if not update the first schedule
if session_in_progress(self.hass, self.coordinator.data):
if value == 0:
await self._client.async_apply_session_rule(pre_condition=False)
else:
await self._client.async_apply_session_rule(pre_condition=True, pre_condition_length=int(value))
await asyncio.sleep(1)
await self.coordinator.async_refresh()
else:
if value == 0:
await self._client.async_update_schedule(pre_condition=False)
else:
await self._client.async_update_schedule(pre_condition=True, pre_condition_length=int(value))
await asyncio.sleep(1)
await self.coordinator_schedules.async_refresh()

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

@callback
def _handle_coordinator_update(self) -> None:
"""Get value from data returned from API by coordinator"""
precondition = None
# Set with the same logic as reading
if session_in_progress(self.hass, self.coordinator.data):
enabled = self.coordinator.data['appliedRule'].get(
'preconditioningEnabled', False)
precondition = 0 if not enabled else self.coordinator.data['appliedRule'].get(
'preconditionLengthMins', None)
elif self.coordinator_schedules.data:
enabled = self.coordinator_schedules.data.get(
'preconditioningEnabled', False)
precondition = 0 if not enabled else self.coordinator_schedules.data.get(
'preconditionLengthMins', None)

self._state = precondition

@property
def native_value(self):
return self._state

0 comments on commit 6a52f57

Please sign in to comment.