diff --git a/README.md b/README.md index 07f51a9..219094b 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ This integration exposes the following entities: * Car Charging - On when a car is connected and drawing power * Pending Approval - On when a car is connected and waiting for approval * Charge Slot Active - On when a charge slot is in progress according to the Ohme-generated charge plan -* Sensors (Charge power) - **These are only available during a charge session** +* Sensors (Charge power) - **Only available during a charge session** * Power Draw (Watts) - Power draw of connected car * Current Draw (Amps) - Current draw of connected car * Voltage (Volts) - Voltage reading @@ -54,6 +54,9 @@ 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 +* Inputs - **Only available during a charge session** + * Number: Target Percentage - Change the target percentage of the ongoing charge + * Time: Target Time - Change the time target for the current charge * Buttons * Approve Charge - Approves a charge when 'Pending Approval' is on @@ -67,6 +70,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 * OhmeAccountInfoCoordinator (1m refresh) * Switches: Lock buttons, require approval and sleep when inactive * OhmeAdvancedSettingsCoordinator (1m refresh) diff --git a/custom_components/ohme/__init__.py b/custom_components/ohme/__init__.py index 9961d15..4891161 100644 --- a/custom_components/ohme/__init__.py +++ b/custom_components/ohme/__init__.py @@ -44,7 +44,7 @@ async def async_setup_entry(hass, entry): hass.data[DOMAIN][DATA_COORDINATORS] = coordinators # Create tasks for each entity type - entity_types = ["sensor", "binary_sensor", "switch", "button", "number"] + entity_types = ["sensor", "binary_sensor", "switch", "button", "number", "time"] for entity_type in entity_types: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, entity_type) diff --git a/custom_components/ohme/number.py b/custom_components/ohme/number.py new file mode 100644 index 0000000..e740856 --- /dev/null +++ b/custom_components/ohme/number.py @@ -0,0 +1,74 @@ +from __future__ import annotations +import asyncio +from homeassistant.components.number import NumberEntity, NumberDeviceClass +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_ACCOUNTINFO + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: config_entries.ConfigEntry, + async_add_entities +): + """Setup switches and configure coordinator.""" + coordinators = hass.data[DOMAIN][DATA_COORDINATORS] + + coordinator = coordinators[COORDINATOR_CHARGESESSIONS] + client = hass.data[DOMAIN][DATA_CLIENT] + + numbers = [TargetPercentNumber(coordinator, hass, client)] + + async_add_entities(numbers, update_before_add=True) + + +class TargetPercentNumber(NumberEntity): + """Target percentage sensor.""" + _attr_name = "Target Percentage" + _attr_device_class = NumberDeviceClass.BATTERY + _attr_suggested_display_precision = 0 + + def __init__(self, coordinator, hass: HomeAssistant, client): + self.coordinator = coordinator + + self._client = client + + self._state = 0 + self._last_updated = None + self._attributes = {} + + self.entity_id = generate_entity_id( + "number.{}", "ohme_target_percent", 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("target_percent") + + async def async_set_native_value(self, value: float) -> None: + """Update the current value.""" + await self._client.async_apply_charge_rule(target_percent=int(value)) + + await asyncio.sleep(1) + await self.coordinator.async_refresh() + + @property + def icon(self): + """Icon of the sensor.""" + return "mdi:battery-heart" + + @property + def native_value(self): + """Get value from data returned from API by coordinator""" + if self.coordinator.data and self.coordinator.data['appliedRule']: + target = round( + self.coordinator.data['appliedRule']['targetPercent']) + + if target == 0: + return self._state + + self._state = target + return self._state + return None diff --git a/custom_components/ohme/time.py b/custom_components/ohme/time.py new file mode 100644 index 0000000..4577428 --- /dev/null +++ b/custom_components/ohme/time.py @@ -0,0 +1,74 @@ +from __future__ import annotations +import asyncio +import logging +from homeassistant.components.time import TimeEntity +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_ACCOUNTINFO +from datetime import time as dt_time + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: config_entries.ConfigEntry, + async_add_entities +): + """Setup switches and configure coordinator.""" + coordinators = hass.data[DOMAIN][DATA_COORDINATORS] + + coordinator = coordinators[COORDINATOR_CHARGESESSIONS] + client = hass.data[DOMAIN][DATA_CLIENT] + + numbers = [TargetTime(coordinator, hass, client)] + + async_add_entities(numbers, update_before_add=True) + + +class TargetTime(TimeEntity): + """Target time sensor.""" + _attr_name = "Target Time" + + def __init__(self, coordinator, hass: HomeAssistant, client): + self.coordinator = coordinator + + self._client = client + + self._state = 0 + self._last_updated = None + self._attributes = {} + + self.entity_id = generate_entity_id( + "number.{}", "ohme_target_time", 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("target_time") + + async def async_set_value(self, value: dt_time) -> None: + """Update the current value.""" + await self._client.async_apply_charge_rule(target_time=(int(value.hour), int(value.minute))) + + await asyncio.sleep(1) + await self.coordinator.async_refresh() + + @property + def icon(self): + """Icon of the sensor.""" + return "mdi:alarm-check" + + @property + def native_value(self): + """Get value from data returned from API by coordinator""" + if self.coordinator.data and self.coordinator.data['appliedRule']: + target = self.coordinator.data['appliedRule']['targetTime'] + return dt_time( + hour=target // 3600, + minute=(target % 3600) // 60, + second=0 + ) + return None