From 25822843b6fcadd2e03c07759131eddf38e21eff Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Sat, 15 Jul 2023 12:14:35 +0200 Subject: [PATCH] Major refactor and cleanup. --- custom_components/ubersolar/__init__.py | 3 +- custom_components/ubersolar/config_flow.py | 4 +- custom_components/ubersolar/coordinator.py | 5 +- custom_components/ubersolar/datetime.py | 12 +-- custom_components/ubersolar/entity.py | 11 ++- custom_components/ubersolar/manifest.json | 2 +- custom_components/ubersolar/select.py | 79 +++++++++---------- custom_components/ubersolar/sensor.py | 15 +--- custom_components/ubersolar/strings.json | 12 +-- custom_components/ubersolar/switch.py | 46 ++++++----- .../ubersolar/translations/en.json | 13 +-- 11 files changed, 93 insertions(+), 109 deletions(-) diff --git a/custom_components/ubersolar/__init__.py b/custom_components/ubersolar/__init__.py index aa6edb5..6f9e410 100644 --- a/custom_components/ubersolar/__init__.py +++ b/custom_components/ubersolar/__init__.py @@ -2,12 +2,13 @@ import logging +import ubersolar + from homeassistant.components import bluetooth from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ADDRESS, CONF_NAME, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady -import ubersolar from .const import CONF_RETRY_COUNT, DEFAULT_RETRY_COUNT, DOMAIN from .coordinator import UbersolarDataUpdateCoordinator diff --git a/custom_components/ubersolar/config_flow.py b/custom_components/ubersolar/config_flow.py index 0428b66..555821c 100644 --- a/custom_components/ubersolar/config_flow.py +++ b/custom_components/ubersolar/config_flow.py @@ -4,6 +4,7 @@ import logging from typing import Any +from ubersolar import UberSolarAdvertisement import voluptuous as vol from homeassistant.components.bluetooth import ( @@ -14,7 +15,6 @@ from homeassistant.const import CONF_ADDRESS from homeassistant.core import callback from homeassistant.data_entry_flow import AbortFlow, FlowResult -from ubersolar import UberSolarAdvertisement from .const import CONF_RETRY_COUNT, DEFAULT_RETRY_COUNT, DOMAIN @@ -45,7 +45,7 @@ def async_get_options_flow( """Get the options flow for this handler.""" return UbersolarOptionsFlowHandler(config_entry) - def __init__(self): + def __init__(self) -> None: """Initialize the config flow.""" self._discovered_adv: UberSolarAdvertisement | None = None self._discovered_advs: dict[str, UberSolarAdvertisement] = {} diff --git a/custom_components/ubersolar/coordinator.py b/custom_components/ubersolar/coordinator.py index 0657fa1..7b9e517 100644 --- a/custom_components/ubersolar/coordinator.py +++ b/custom_components/ubersolar/coordinator.py @@ -5,9 +5,10 @@ import logging from typing import TYPE_CHECKING +from ubersolar import UberSmart + from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -import ubersolar from .const import DOMAIN @@ -25,7 +26,7 @@ def __init__( hass: HomeAssistant, logger: logging.Logger, ble_device: BLEDevice, - device: ubersolar.UberSmart, + device: UberSmart, base_unique_id: str, device_name: str, ) -> None: diff --git a/custom_components/ubersolar/datetime.py b/custom_components/ubersolar/datetime.py index e4937cc..f63a75a 100644 --- a/custom_components/ubersolar/datetime.py +++ b/custom_components/ubersolar/datetime.py @@ -6,11 +6,10 @@ from homeassistant.components.datetime import DateTimeEntity, DateTimeEntityDescription from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_platform -from homeassistant.helpers.entity import EntityCategory from homeassistant.util import dt as dt_util -import ubersolar from .const import DOMAIN from .coordinator import UbersolarDataUpdateCoordinator @@ -23,7 +22,7 @@ DATETIME_TYPE = DateTimeEntityDescription( key="lluTime", name="Device Time", - entity_category=EntityCategory.DIAGNOSTIC, + entity_category=EntityCategory.CONFIG, ) @@ -35,15 +34,12 @@ async def async_setup_entry( """Set up UberSolar based on a config entry.""" coordinator: UbersolarDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - async_add_entities(UbersmartDateTime(coordinator)) + async_add_entities([UbersmartDateTime(coordinator)]) class UbersmartDateTime(UbersolarEntity, DateTimeEntity): """Representation of a UberSolar DateTimeEntity.""" - _device: ubersolar.UberSmart - _attr_has_entity_name = True - def __init__(self, coordinator: UbersolarDataUpdateCoordinator) -> None: """Initialize the UberSmart device.""" super().__init__(coordinator) @@ -53,7 +49,7 @@ def __init__(self, coordinator: UbersolarDataUpdateCoordinator) -> None: @property def native_value(self) -> datetime | None: """Return the value reported by the datetime.""" - return dt_util.parse_datetime(self.data["lluTime"]) + return dt_util.parse_datetime(f"{self.data['lluTime']}+02:00") async def async_set_value(self, value: datetime) -> None: """Change the date/time.""" diff --git a/custom_components/ubersolar/entity.py b/custom_components/ubersolar/entity.py index fff20e0..b5f44e8 100644 --- a/custom_components/ubersolar/entity.py +++ b/custom_components/ubersolar/entity.py @@ -4,11 +4,13 @@ import logging from typing import Any +from ubersolar import UberSmart + +from homeassistant.components import bluetooth from homeassistant.const import ATTR_CONNECTIONS from homeassistant.helpers import device_registry as dr from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from ubersolar import UberSmart from .const import MANUFACTURER, MODEL from .coordinator import UbersolarDataUpdateCoordinator @@ -50,3 +52,10 @@ def __init__(self, coordinator: UbersolarDataUpdateCoordinator) -> None: def data(self) -> dict[str, Any]: """Return coordinator data for this entity.""" return self.coordinator.device.status_data[self._address] + + @property + def available(self) -> bool: + """Return if entity is available.""" + return super().available and bluetooth.async_address_present( + self.hass, self._address, True + ) diff --git a/custom_components/ubersolar/manifest.json b/custom_components/ubersolar/manifest.json index 33c292b..92a9521 100644 --- a/custom_components/ubersolar/manifest.json +++ b/custom_components/ubersolar/manifest.json @@ -14,5 +14,5 @@ "issue_tracker": "https://github.com/RenierM26/ha-ubersolar/issues", "loggers": ["ubersolar"], "requirements": ["PyUbersolar==0.1.3"], - "version": "0.1.8" + "version": "0.1.9" } diff --git a/custom_components/ubersolar/select.py b/custom_components/ubersolar/select.py index aaab78e..5ecbce9 100644 --- a/custom_components/ubersolar/select.py +++ b/custom_components/ubersolar/select.py @@ -1,14 +1,14 @@ """Support for UberSolar.""" from __future__ import annotations +from dataclasses import dataclass import logging from homeassistant.components.select import SelectEntity, SelectEntityDescription from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import entity_platform -from homeassistant.helpers.entity import EntityCategory -import ubersolar from .const import DOMAIN from .coordinator import UbersolarDataUpdateCoordinator @@ -19,19 +19,28 @@ PARALLEL_UPDATES = 0 -SELECT_METHODS_LIST: dict[str, list] = { - "eSolenoidMode": ["set_solinoid_off", "set_solinoid_on", "set_solinoid_auto"], -} +@dataclass +class UbersmartSelectEntityDescriptionMixin: + """Mixin values for Ubersmart select entities.""" -SELECT_TYPES: dict[str, SelectEntityDescription] = { - "eSolenoidMode": SelectEntityDescription( - key="eSolenoidMode", - name="Solenoid Mode", - icon="mdi:electric-switch", - entity_category=EntityCategory.CONFIG, - options=[0, 1, 2], - ), -} + method: list + + +@dataclass +class UbersmartSelectEntityDescription( + SelectEntityDescription, UbersmartSelectEntityDescriptionMixin +): + """Describe a Ubersmart select entity.""" + + +SELECT_TYPE = UbersmartSelectEntityDescription( + key="eSolenoidMode", + name="Solenoid Mode", + icon="mdi:electric-switch", + entity_category=EntityCategory.CONFIG, + options=["Off", "On", "Auto"], + method=["set_solinoid_off", "set_solinoid_on", "set_solinoid_auto"], +) async def async_setup_entry( @@ -41,47 +50,37 @@ async def async_setup_entry( ) -> None: """Set up UberSolar based on a config entry.""" coordinator: UbersolarDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - entities = [ - UbersmartSelect( - coordinator, - selector, - ) - for selector in coordinator.device.status_data[coordinator.address] - if selector in SELECT_TYPES - ] - async_add_entities(entities) + async_add_entities([UbersmartSelect(coordinator)]) class UbersmartSelect(UbersolarEntity, SelectEntity): """Representation of a UberSolar Selector.""" - _device: ubersolar.UberSmart - _attr_has_entity_name = True - - def __init__( - self, coordinator: UbersolarDataUpdateCoordinator, selector: str - ) -> None: + def __init__(self, coordinator: UbersolarDataUpdateCoordinator) -> None: """Initialize the UberSmart device.""" super().__init__(coordinator) - self._selector = selector - self._attr_unique_id = f"{coordinator.base_unique_id}-{selector}" - self.entity_description = SELECT_TYPES[selector] - - @property - def current_option(self) -> str | None: - """Return the selected entity option to represent the entity state.""" - return self.data[self._selector] + self._selector = SELECT_TYPE.key + self._attr_unique_id = f"{coordinator.base_unique_id}-{SELECT_TYPE.key}" + self.entity_description = SELECT_TYPE + self._attr_current_option = SELECT_TYPE.options[self.data[self._selector]] async def async_select_option(self, option: str) -> None: """Change the selected option.""" - _LOGGER.info( + _LOGGER.debug( "Set %s value to %s for device %s", self._selector, option, self._address ) switch_method = getattr( - self._device, SELECT_METHODS_LIST[self._selector][option] + self._device, SELECT_TYPE.method[SELECT_TYPE.options.index(option)] ) await switch_method() + self._attr_current_option = option self.async_write_ha_state() + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self._attr_current_option = SELECT_TYPE.options[self.data[self._selector]] + super()._handle_coordinator_update() diff --git a/custom_components/ubersolar/sensor.py b/custom_components/ubersolar/sensor.py index de9a7ba..48c8715 100644 --- a/custom_components/ubersolar/sensor.py +++ b/custom_components/ubersolar/sensor.py @@ -12,13 +12,13 @@ from homeassistant.const import ( LIGHT_LUX, SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + EntityCategory, UnitOfElectricPotential, UnitOfTemperature, UnitOfTime, UnitOfVolume, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN @@ -43,7 +43,6 @@ native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, - entity_registry_enabled_default=True, ), "fManifoldTemperature": SensorEntityDescription( key="fManifoldTemperature", @@ -51,7 +50,6 @@ native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, - entity_registry_enabled_default=True, ), "fStoredWater": SensorEntityDescription( key="fStoredWater", @@ -59,13 +57,11 @@ native_unit_of_measurement=UnitOfVolume.LITERS, device_class=SensorDeviceClass.VOLUME, state_class=SensorStateClass.TOTAL, - entity_registry_enabled_default=True, ), "fSolenoidState": SensorEntityDescription( key="fSolenoidState", name="Solenoid State", state_class=SensorStateClass.MEASUREMENT, - entity_registry_enabled_default=True, entity_category=EntityCategory.DIAGNOSTIC, ), "lluTime": SensorEntityDescription( @@ -89,7 +85,6 @@ native_unit_of_measurement=LIGHT_LUX, device_class=SensorDeviceClass.ILLUMINANCE, state_class=SensorStateClass.MEASUREMENT, - entity_registry_enabled_default=False, ), "fPanelVoltage": SensorEntityDescription( key="fPanelVoltage", @@ -97,7 +92,6 @@ native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, - entity_registry_enabled_default=False, ), "fChipTemp": SensorEntityDescription( key="fChipTemp", @@ -105,13 +99,11 @@ native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, - entity_registry_enabled_default=False, ), "fWaterLevel": SensorEntityDescription( key="fWaterLevel", name="Water Level in Manifold", state_class=SensorStateClass.MEASUREMENT, - entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), "fTankSize": SensorEntityDescription( @@ -121,34 +113,29 @@ device_class=SensorDeviceClass.VOLUME, state_class=SensorStateClass.TOTAL, entity_category=EntityCategory.DIAGNOSTIC, - entity_registry_enabled_default=True, ), "bPanelFaultCode": SensorEntityDescription( key="bPanelFaultCode", name="SolarPanel Fault Code", state_class=SensorStateClass.MEASUREMENT, - entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), "bElementFaultCode": SensorEntityDescription( key="bElementFaultCode", name="Element Fault Code", state_class=SensorStateClass.MEASUREMENT, - entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), "bPumpFultCode": SensorEntityDescription( key="bPumpFultCode", name="Pump Fault Code", state_class=SensorStateClass.MEASUREMENT, - entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), "bSolenoidFaultCode": SensorEntityDescription( key="bSolenoidFaultCode", name="Solenoid Fault Code", state_class=SensorStateClass.MEASUREMENT, - entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), } diff --git a/custom_components/ubersolar/strings.json b/custom_components/ubersolar/strings.json index ab66694..31aa47f 100644 --- a/custom_components/ubersolar/strings.json +++ b/custom_components/ubersolar/strings.json @@ -9,21 +9,11 @@ }, "confirm": { "description": "Do you want to set up {name}?" - }, - "password": { - "description": "The {name} device requires a password", - "data": { - "password": "[%key:common::config_flow::data::password%]" - } } }, "error": {}, "abort": { - "already_configured_device": "[%key:common::config_flow::abort::already_configured_device%]", - "no_unconfigured_devices": "No unconfigured devices found.", - "unknown": "[%key:common::config_flow::error::unknown%]", - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "switchbot_unsupported_type": "Unsupported Switchbot Type." + "no_unconfigured_devices": "No unconfigured devices found." } }, "options": { diff --git a/custom_components/ubersolar/switch.py b/custom_components/ubersolar/switch.py index 5b5a8cf..747f8e6 100644 --- a/custom_components/ubersolar/switch.py +++ b/custom_components/ubersolar/switch.py @@ -1,15 +1,15 @@ """Support for UberSolar.""" from __future__ import annotations +from dataclasses import dataclass import logging from typing import Any from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import entity_platform -from homeassistant.helpers.entity import EntityCategory -import ubersolar from .const import DOMAIN from .coordinator import UbersolarDataUpdateCoordinator @@ -20,30 +20,41 @@ PARALLEL_UPDATES = 0 -SWITCH_METHODS_LIST: dict[str, list] = { - "bElementOn": ["turn_on_element", "turn_off_element"], - "bPumpOn": ["turn_on_pump", "turn_off_pump"], - "bHolidayMode": ["turn_on_holiday", "turn_off_holiday"], -} +@dataclass +class UbersmartSwitchEntityDescriptionMixin: + """Mixin values for Ubersmart switch entities.""" + + method: list + -SWITCH_TYPES: dict[str, SwitchEntityDescription] = { - "bElementOn": SwitchEntityDescription( +@dataclass +class UbersmartSwitchEntityDescription( + SwitchEntityDescription, UbersmartSwitchEntityDescriptionMixin +): + """Describe a Ubersmart switch entity.""" + + +SWITCH_TYPES: dict[str, UbersmartSwitchEntityDescription] = { + "bElementOn": UbersmartSwitchEntityDescription( key="bElementOn", name="Element", icon="mdi:heating-coil", entity_category=EntityCategory.CONFIG, + method=["turn_on_element", "turn_off_element"], ), - "bPumpOn": SwitchEntityDescription( + "bPumpOn": UbersmartSwitchEntityDescription( key="bPumpOn", name="Pump", icon="mdi:water-pump", entity_category=EntityCategory.CONFIG, + method=["turn_on_pump", "turn_off_pump"], ), - "bHolidayMode": SwitchEntityDescription( + "bHolidayMode": UbersmartSwitchEntityDescription( key="bHolidayMode", name="Holiday Mode", icon="mdi:beach", entity_category=EntityCategory.CONFIG, + method=["turn_on_holiday", "turn_off_holiday"], ), } @@ -70,8 +81,7 @@ async def async_setup_entry( class UbersmartSwitch(UbersolarEntity, SwitchEntity): """Representation of a UberSolar switch.""" - _device: ubersolar.UberSmart - _attr_has_entity_name = True + entity_description: UbersmartSwitchEntityDescription def __init__( self, coordinator: UbersolarDataUpdateCoordinator, switch: str @@ -81,13 +91,13 @@ def __init__( self._switch = switch self._attr_unique_id = f"{coordinator.base_unique_id}-{switch}" self.entity_description = SWITCH_TYPES[switch] - self._attr_is_on = self.data[self._switch] + self._attr_is_on = self.data[switch] async def async_turn_on(self, **kwargs: Any) -> None: """Turn device on.""" - _LOGGER.info("Turn %s on for device %s", self._switch, self._address) + _LOGGER.debug("Turn %s on for device %s", self._switch, self._address) - switch_method = getattr(self._device, SWITCH_METHODS_LIST[self._switch][0]) + switch_method = getattr(self._device, self.entity_description.method[0]) await switch_method() self._attr_is_on = True @@ -95,9 +105,9 @@ async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None: """Turn device off.""" - _LOGGER.info("Turn %s off for device %s", self._switch, self._address) + _LOGGER.debug("Turn %s off for device %s", self._switch, self._address) - switch_method = getattr(self._device, SWITCH_METHODS_LIST[self._switch][1]) + switch_method = getattr(self._device, self.entity_description.method[1]) await switch_method() self._attr_is_on = False diff --git a/custom_components/ubersolar/translations/en.json b/custom_components/ubersolar/translations/en.json index de742c3..8e4d83f 100644 --- a/custom_components/ubersolar/translations/en.json +++ b/custom_components/ubersolar/translations/en.json @@ -1,23 +1,14 @@ { "config": { "abort": { - "already_configured_device": "Device is already configured", - "cannot_connect": "Failed to connect", - "no_unconfigured_devices": "No unconfigured devices found.", - "switchbot_unsupported_type": "Unsupported Switchbot Type.", - "unknown": "Unexpected error" + "no_unconfigured_devices": "No unconfigured devices found." }, + "error": {}, "flow_title": "{name} ({address})", "step": { "confirm": { "description": "Do you want to set up {name}?" }, - "password": { - "data": { - "password": "Password" - }, - "description": "The {name} device requires a password" - }, "user": { "data": { "address": "Device address"