Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.

Commit 6a52f57

Browse files
committed
Added preconditioning input
1 parent a348ac1 commit 6a52f57

File tree

3 files changed

+115
-7
lines changed

3 files changed

+115
-7
lines changed

README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,19 @@ This integration exposes the following entities:
5959
* Max Charge - Forces the connected car to charge regardless of set schedule
6060
* Pause Charge - Pauses an ongoing charge
6161
* Inputs - **If in a charge session, these change the active charge. If disconnected, they change your first schedule.**
62-
* Number: Target Percentage - Change the target battery percentage
63-
* Time: Target Time - Change the target time
62+
* Number
63+
* Target Percentage - Change the target battery percentage
64+
* Preconditioning - Change pre-conditioning time. 0 is off
65+
* Time
66+
* Target Time - Change the target time
6467
* Buttons
6568
* Approve Charge - Approves a charge when 'Pending Approval' is on
6669

70+
## Options
71+
Some options can be set from the 'Configure' menu in Home Assistant:
72+
* 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.
73+
74+
6775
## Coordinators
6876
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.
6977

@@ -74,7 +82,7 @@ The coordinators are listed with their refresh intervals below. Relevant coordin
7482
* Buttons: Approve Charge
7583
* Sensors: Power, current, voltage and next slot (start & end)
7684
* Switches: Max charge, pause charge
77-
* Inputs: Target time and target percentage (If car connected)
85+
* Inputs: Target time, target percentage and preconditioning (If car connected)
7886
* OhmeAccountInfoCoordinator (1m refresh)
7987
* Switches: Lock buttons, require approval and sleep when inactive
8088
* OhmeAdvancedSettingsCoordinator (1m refresh)
@@ -83,4 +91,4 @@ The coordinators are listed with their refresh intervals below. Relevant coordin
8391
* OhmeStatisticsCoordinator (30m refresh)
8492
* Sensors: Accumulative energy usage
8593
* OhmeChargeSchedulesCoordinator (10m refresh)
86-
* Inputs: Target time and target percentage (If car disconnected)
94+
* Inputs: Target time, target percentage and preconditioning (If car disconnected)

custom_components/ohme/api_client.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ async def async_get_schedule(self):
233233

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

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

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

250+
# Update pre-conditioning if provided
251+
if pre_condition is not None:
252+
rule['preconditioningEnabled'] = pre_condition
253+
if pre_condition_length is not None:
254+
rule['preconditionLengthMins'] = pre_condition_length
255+
250256
await self._put_request(f"/v1/chargeRules/{rule['id']}", data=rule)
251257
return True
252258

custom_components/ohme/number.py

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from __future__ import annotations
22
import asyncio
33
from homeassistant.components.number import NumberEntity, NumberDeviceClass
4+
from homeassistant.const import UnitOfTime
45
from homeassistant.helpers.entity import generate_entity_id
56
from homeassistant.core import callback, HomeAssistant
67
from .const import DOMAIN, DATA_CLIENT, DATA_COORDINATORS, COORDINATOR_CHARGESESSIONS, COORDINATOR_SCHEDULES
78
from .utils import session_in_progress
89

10+
911
async def async_setup_entry(
1012
hass: HomeAssistant,
1113
config_entry: config_entries.ConfigEntry,
@@ -17,6 +19,8 @@ async def async_setup_entry(
1719
client = hass.data[DOMAIN][DATA_CLIENT]
1820

1921
numbers = [TargetPercentNumber(
22+
coordinators[COORDINATOR_CHARGESESSIONS], coordinators[COORDINATOR_SCHEDULES], hass, client),
23+
PreconditioningNumber(
2024
coordinators[COORDINATOR_CHARGESESSIONS], coordinators[COORDINATOR_SCHEDULES], hass, client)]
2125

2226
async_add_entities(numbers, update_before_add=True)
@@ -56,7 +60,7 @@ async def async_added_to_hass(self) -> None:
5660
self._handle_coordinator_update, None
5761
)
5862
)
59-
63+
6064
@property
6165
def unique_id(self):
6266
"""The unique ID of the switch."""
@@ -84,7 +88,8 @@ def _handle_coordinator_update(self) -> None:
8488
"""Get value from data returned from API by coordinator"""
8589
# Set with the same logic as reading
8690
if session_in_progress(self.hass, self.coordinator.data):
87-
target = round(self.coordinator.data['appliedRule']['targetPercent'])
91+
target = round(
92+
self.coordinator.data['appliedRule']['targetPercent'])
8893
elif self.coordinator_schedules.data:
8994
target = round(self.coordinator_schedules.data['targetPercent'])
9095

@@ -93,3 +98,92 @@ def _handle_coordinator_update(self) -> None:
9398
@property
9499
def native_value(self):
95100
return self._state
101+
102+
103+
class PreconditioningNumber(NumberEntity):
104+
"""Preconditioning sensor."""
105+
_attr_name = "Preconditioning"
106+
_attr_device_class = NumberDeviceClass.DURATION
107+
_attr_native_unit_of_measurement = UnitOfTime.MINUTES
108+
_attr_native_min_value = 0
109+
_attr_native_step = 5
110+
_attr_native_max_value = 60
111+
112+
def __init__(self, coordinator, coordinator_schedules, hass: HomeAssistant, client):
113+
self.coordinator = coordinator
114+
self.coordinator_schedules = coordinator_schedules
115+
116+
self._client = client
117+
118+
self._state = None
119+
self._last_updated = None
120+
self._attributes = {}
121+
122+
self.entity_id = generate_entity_id(
123+
"number.{}", "ohme_preconditioning", hass=hass)
124+
125+
self._attr_device_info = client.get_device_info()
126+
127+
async def async_added_to_hass(self) -> None:
128+
"""When entity is added to hass."""
129+
await super().async_added_to_hass()
130+
self.async_on_remove(
131+
self.coordinator.async_add_listener(
132+
self._handle_coordinator_update, None
133+
)
134+
)
135+
self.async_on_remove(
136+
self.coordinator_schedules.async_add_listener(
137+
self._handle_coordinator_update, None
138+
)
139+
)
140+
141+
@property
142+
def unique_id(self):
143+
"""The unique ID of the switch."""
144+
return self._client.get_unique_id("preconditioning")
145+
146+
async def async_set_native_value(self, value: float) -> None:
147+
"""Update the current value."""
148+
# If session in progress, update this session, if not update the first schedule
149+
if session_in_progress(self.hass, self.coordinator.data):
150+
if value == 0:
151+
await self._client.async_apply_session_rule(pre_condition=False)
152+
else:
153+
await self._client.async_apply_session_rule(pre_condition=True, pre_condition_length=int(value))
154+
await asyncio.sleep(1)
155+
await self.coordinator.async_refresh()
156+
else:
157+
if value == 0:
158+
await self._client.async_update_schedule(pre_condition=False)
159+
else:
160+
await self._client.async_update_schedule(pre_condition=True, pre_condition_length=int(value))
161+
await asyncio.sleep(1)
162+
await self.coordinator_schedules.async_refresh()
163+
164+
@property
165+
def icon(self):
166+
"""Icon of the sensor."""
167+
return "mdi:air-conditioner"
168+
169+
@callback
170+
def _handle_coordinator_update(self) -> None:
171+
"""Get value from data returned from API by coordinator"""
172+
precondition = None
173+
# Set with the same logic as reading
174+
if session_in_progress(self.hass, self.coordinator.data):
175+
enabled = self.coordinator.data['appliedRule'].get(
176+
'preconditioningEnabled', False)
177+
precondition = 0 if not enabled else self.coordinator.data['appliedRule'].get(
178+
'preconditionLengthMins', None)
179+
elif self.coordinator_schedules.data:
180+
enabled = self.coordinator_schedules.data.get(
181+
'preconditioningEnabled', False)
182+
precondition = 0 if not enabled else self.coordinator_schedules.data.get(
183+
'preconditionLengthMins', None)
184+
185+
self._state = precondition
186+
187+
@property
188+
def native_value(self):
189+
return self._state

0 commit comments

Comments
 (0)