diff --git a/custom_components/webastoconnect/__init__.py b/custom_components/webastoconnect/__init__.py index e7466a7..a12c9e4 100644 --- a/custom_components/webastoconnect/__init__.py +++ b/custom_components/webastoconnect/__init__.py @@ -23,8 +23,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if result: await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - # await async_setup_services(hass) - return result @@ -51,3 +49,19 @@ async def _async_setup(hass: HomeAssistant, entry: ConfigEntry) -> bool: await coordinator.async_config_entry_first_refresh() return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + for platform in PLATFORMS: + await hass.config_entries.async_forward_entry_unload(entry, platform) + + hass.data[DOMAIN].pop(entry.entry_id) + + return True + + +async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Reload config entry.""" + await async_unload_entry(hass, entry) + await async_setup_entry(hass, entry) diff --git a/custom_components/webastoconnect/base.py b/custom_components/webastoconnect/base.py index f66bd26..f39f5b6 100644 --- a/custom_components/webastoconnect/base.py +++ b/custom_components/webastoconnect/base.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from homeassistant.components.binary_sensor import BinarySensorEntityDescription +from homeassistant.components.number import NumberEntityDescription from homeassistant.components.sensor import SensorEntityDescription from homeassistant.components.switch import SwitchEntityDescription @@ -45,3 +46,13 @@ class WebastoConnectSwitchEntityDescription( command_fn: Callable[[WebastoConnect], None] = None type_fn: Callable[[WebastoConnect], None] = None name_fn: Callable[[WebastoConnect], None] = None + + +@dataclass +class WebastoConnectNumberEntityDescription( + NumberEntityDescription, WebastoConnectBaseEntityDescriptionMixin +): + """Describes a Webasto number.""" + + set_fn: Callable[[WebastoConnect], None] = None + unit_fn: Callable[["WebastoConnect"], None] = None diff --git a/custom_components/webastoconnect/const.py b/custom_components/webastoconnect/const.py index 58a8cd3..4737e6c 100644 --- a/custom_components/webastoconnect/const.py +++ b/custom_components/webastoconnect/const.py @@ -14,7 +14,7 @@ DOMAIN = "webastoconnect" -PLATFORMS = ["binary_sensor", "switch", "sensor", "device_tracker"] +PLATFORMS = ["binary_sensor", "switch", "sensor", "device_tracker", "number"] NEW_DATA = "webasto_signal" diff --git a/custom_components/webastoconnect/number.py b/custom_components/webastoconnect/number.py new file mode 100644 index 0000000..5110bc8 --- /dev/null +++ b/custom_components/webastoconnect/number.py @@ -0,0 +1,124 @@ +"""Support for number entities in Webasto Connect.""" + +import logging +from typing import Any, cast + +from homeassistant.components import number +from homeassistant.components.number import NumberEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory +from homeassistant.core import callback +from homeassistant.helpers.update_coordinator import CoordinatorEntity +from homeassistant.util import slugify as util_slugify + +from .api import WebastoConnectUpdateCoordinator +from .base import WebastoConnectNumberEntityDescription +from .const import ATTR_COORDINATOR, DOMAIN + +LOGGER = logging.getLogger(__name__) + +NUMBERS = [ + WebastoConnectNumberEntityDescription( + key="low_voltage_cutoff", + name="Low Voltage Cutoff", + entity_category=EntityCategory.CONFIG, + value_fn=lambda webasto: webasto.low_voltage_cutoff, + set_fn=lambda webasto, state: webasto.set_low_voltage_cutoff(state), + native_min_value=0, + native_max_value=30, + native_step=0.1, + native_unit_of_measurement="V", + entity_registry_enabled_default=False, + icon="mdi:battery-off", + ), + WebastoConnectNumberEntityDescription( + key="ext_temp_comp", + name="Temperature Compensation", + entity_category=EntityCategory.CONFIG, + value_fn=lambda webasto: webasto.temperature_compensation, + set_fn=lambda webasto, state: webasto.set_temperature_compensation(state), + native_min_value=-10, + native_max_value=10, + native_step=0.5, + unit_fn=lambda webasto: webasto.temperature_unit, + entity_registry_enabled_default=False, + icon="mdi:thermometer-alert", + ), +] + + +async def async_setup_entry(hass, entry: ConfigEntry, async_add_devices): + """Setup switch.""" + numbers_list = [] + + coordinator = hass.data[DOMAIN][entry.entry_id][ATTR_COORDINATOR] + + for number in NUMBERS: + entity = WebastoConnectNumber(number, coordinator) + LOGGER.debug( + "Adding number '%s' with entity_id '%s'", number.name, entity.entity_id + ) + numbers_list.append(entity) + + async_add_devices(numbers_list) + + +class WebastoConnectNumber(CoordinatorEntity, NumberEntity): + """Representation of a Webasto Connect number.""" + + def __init__( + self, + description: WebastoConnectNumberEntityDescription, + coordinator: WebastoConnectUpdateCoordinator, + ) -> None: + """Initialize a Webasto Connect number.""" + super().__init__(coordinator) + + self.entity_description = description + self._config = coordinator.entry + self.coordinator = coordinator + self._hass = coordinator.hass + + self._attr_name = self.entity_description.name + self._attr_unique_id = util_slugify( + f"{self._attr_name}_{self._config.entry_id}" + ) + self._attr_should_poll = False + self._attr_device_info = { + "identifiers": {(DOMAIN, self.coordinator.cloud.device_id)}, + "name": self.name, + "model": "ThermoConnect", + "manufacturer": "Webasto", + } + + if not isinstance(description.unit_fn, type(None)): + self._attr_native_unit_of_measurement = description.unit_fn( + coordinator.cloud + ) + + self.entity_id = number.ENTITY_ID_FORMAT.format( + util_slugify(f"{self.coordinator.cloud.name} {self._attr_name}") + ) + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self.async_write_ha_state() + + @property + def native_value(self) -> float | None: + """Get the native value.""" + LOGGER.debug( + "Native value for '%s' is '%s'", + self.entity_id, + self.entity_description.value_fn(self.coordinator.cloud), + ) + return cast(float, self.entity_description.value_fn(self.coordinator.cloud)) + + async def async_set_native_value(self, value: float) -> None: + """Set new value.""" + LOGGER.debug("Setting '%s' to '%s'", self.entity_id, value) + await self._hass.async_add_executor_job( + self.entity_description.set_fn, self.coordinator.cloud, value + ) + await self.coordinator.async_refresh() diff --git a/custom_components/webastoconnect/pywebasto/__init__.py b/custom_components/webastoconnect/pywebasto/__init__.py index 9b5934c..8523c83 100644 --- a/custom_components/webastoconnect/pywebasto/__init__.py +++ b/custom_components/webastoconnect/pywebasto/__init__.py @@ -1,5 +1,6 @@ """Module for interfacing with Webasto Connect.""" +from datetime import datetime import json import threading from typing import Any @@ -301,6 +302,12 @@ def output_name(self) -> str: """Get the main output name.""" return self._mainOutput["name"] + @property + def subscription_expiration(self) -> datetime: + """Get subscription expiration.""" + expiration = self._dev_data["subscription"]["expiration"] + return datetime.fromtimestamp(expiration) + def __get_value(self, group: str, key: str) -> Any: """Get a value from the settings dict.""" for g in self._settings["settings_tab"]: diff --git a/custom_components/webastoconnect/sensor.py b/custom_components/webastoconnect/sensor.py index d21d203..6128c5f 100644 --- a/custom_components/webastoconnect/sensor.py +++ b/custom_components/webastoconnect/sensor.py @@ -1,8 +1,8 @@ """Sensors for Webasto Connect.""" import logging -from typing import cast +import homeassistant.util.dt as dt_util from homeassistant.components import sensor from homeassistant.components.sensor import ( SensorDeviceClass, @@ -42,6 +42,16 @@ value_fn=lambda webasto: webasto.voltage, icon="mdi:car-battery", ), + WebastoConnectSensorEntityDescription( + key="subscription_expiration", + name="Subscription Expiration", + entity_category=EntityCategory.DIAGNOSTIC, + state_class=None, + device_class=None, + entity_registry_enabled_default=False, + value_fn=lambda webasto: webasto.subscription_expiration.strftime("%d-%m-%Y"), + icon="mdi:calendar-end", + ), ] diff --git a/custom_components/webastoconnect/switch.py b/custom_components/webastoconnect/switch.py index 3343bf5..18c7248 100644 --- a/custom_components/webastoconnect/switch.py +++ b/custom_components/webastoconnect/switch.py @@ -17,7 +17,7 @@ LOGGER = logging.getLogger(__name__) -BINARY_SENSORS = [ +SWITCHES = [ WebastoConnectSwitchEntityDescription( key="main_output", name="Output", @@ -44,10 +44,10 @@ async def async_setup_entry(hass, entry: ConfigEntry, async_add_devices): coordinator = hass.data[DOMAIN][entry.entry_id][ATTR_COORDINATOR] - for b_s in BINARY_SENSORS: - entity = WebastoConnectSwitch(b_s, coordinator) + for sw in SWITCHES: + entity = WebastoConnectSwitch(sw, coordinator) LOGGER.debug( - "Adding switch '%s' with entity_id '%s'", b_s.name, entity.entity_id + "Adding switch '%s' with entity_id '%s'", sw.name, entity.entity_id ) switches.append(entity)