diff --git a/custom_components/sws12500/__init__.py b/custom_components/sws12500/__init__.py index fc9aa31..ef89a2b 100644 --- a/custom_components/sws12500/__init__.py +++ b/custom_components/sws12500/__init__.py @@ -8,15 +8,22 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import InvalidStateError, PlatformNotReady -from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import API_ID, API_KEY, DEFAULT_URL, DEV_DBG, DOMAIN, WINDY_ENABLED -from .utils import anonymize, check_disabled, remap_items +from .const import ( + API_ID, + API_KEY, + DEFAULT_URL, + DEV_DBG, + DOMAIN, + SENSORS_TO_LOAD, + WINDY_ENABLED, +) +from .utils import anonymize, check_disabled, remap_items, update_options from .windy_func import WindyPush _LOGGER = logging.getLogger(__name__) -PLATFORMS = [Platform.SENSOR] +PLATFORMS: list[Platform] = [Platform.SENSOR] class IncorrectDataError(InvalidStateError): @@ -57,7 +64,11 @@ class WeatherDataUpdateCoordinator(DataUpdateCoordinator): remaped_items = remap_items(data) - await check_disabled(self.hass, remaped_items, self.config_entry.options.get(DEV_DBG)) + if sensors := check_disabled(self.hass, remaped_items, self.config): + await update_options( + self.hass, self.config_entry, SENSORS_TO_LOAD, sensors + ) + # await self.hass.config_entries.async_reload(self.config.entry_id) self.async_set_updated_data(remaped_items) @@ -77,7 +88,10 @@ def register_path( "GET", url_path, coordinator.recieved_data ) except RuntimeError as Ex: # pylint: disable=(broad-except) - if "Added route will never be executed, method GET is already registered" in Ex.args: + if ( + "Added route will never be executed, method GET is already registered" + in Ex.args + ): _LOGGER.info("Handler to URL (%s) already registred", url_path) return True @@ -90,38 +104,22 @@ def register_path( ) return True + def unregister_path(hass: HomeAssistant): """Unregister path to handle incoming data.""" _LOGGER.error( - "Unable to delete webhook from API! Restart HA before adding integration!" + """Unable to delete webhook from API! Restart HA before adding integration! + If this error is raised while adding sensors or reloading configuration, you can ignore this error + """ ) -class Weather(WeatherDataUpdateCoordinator): - """Weather class.""" - - def __init__(self, hass: HomeAssistant, config) -> None: - """Init class.""" - self.hass = hass - super().__init__(hass, config) - - async def setup_update_listener(self, hass: HomeAssistant, entry: ConfigEntry): - """Update setup listener.""" - await hass.config_entries.async_reload(entry.entry_id) - - _LOGGER.info("Settings updated") - - async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up the config entry for my device.""" coordinator = WeatherDataUpdateCoordinator(hass, entry) - hass.data.setdefault(DOMAIN, {}) - - hass.data[DOMAIN][entry.entry_id] = coordinator - - weather = Weather(hass, entry) + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator if not register_path(hass, DEFAULT_URL, coordinator): _LOGGER.error("Fatal: path not registered!") @@ -129,10 +127,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - entry.async_on_unload(entry.add_update_listener(weather.setup_update_listener)) + entry.async_on_unload(entry.add_update_listener(update_listener)) return True +async def update_listener(hass: HomeAssistant, entry: ConfigEntry): + """Update setup listener.""" + await hass.config_entries.async_reload(entry.entry_id) + + _LOGGER.info("Settings updated") + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" @@ -145,10 +149,13 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return _ok -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up the component. +# async def async_setup(hass: HomeAssistant, entry: ConfigEntry) -> bool: +# """Set up the component. - This component can only be configured through the Integrations UI. - """ - hass.data.setdefault(DOMAIN, {}) - return True +# This component can only be configured through the Integrations UI. +# """ +# hass.data.setdefault(DOMAIN, {}) + +# await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + +# return True diff --git a/custom_components/sws12500/config_flow.py b/custom_components/sws12500/config_flow.py index 3923ad6..964fdd8 100644 --- a/custom_components/sws12500/config_flow.py +++ b/custom_components/sws12500/config_flow.py @@ -13,6 +13,7 @@ from .const import ( DEV_DBG, DOMAIN, INVALID_CREDENTIALS, + SENSORS_TO_LOAD, WINDY_API_KEY, WINDY_ENABLED, WINDY_LOGGER_ENABLED, @@ -46,6 +47,10 @@ class ConfigOptionsFlowHandler(config_entries.OptionsFlow): WINDY_LOGGER_ENABLED: self.config_entry.options.get(WINDY_LOGGER_ENABLED) if isinstance(self.config_entry.options.get(WINDY_LOGGER_ENABLED), bool) else False, } + self.sensors: dict[str, Any] = { + SENSORS_TO_LOAD: self.config_entry.options.get(SENSORS_TO_LOAD) if isinstance(self.config_entry.options.get(SENSORS_TO_LOAD), list) else [] + } + self.user_data_schema = { vol.Required(API_ID, default=self.user_data[API_ID] or ""): str, vol.Required(API_KEY, default=self.user_data[API_KEY] or ""): str, @@ -85,17 +90,12 @@ class ConfigOptionsFlowHandler(config_entries.OptionsFlow): elif user_input[API_KEY] == user_input[API_ID]: errors["base"] = "valid_credentials_match" else: - # retain Windy options - data: dict = {} - data[WINDY_API_KEY] = self.config_entry.options.get(WINDY_API_KEY) - data[WINDY_ENABLED] = self.config_entry.options.get(WINDY_ENABLED) - data[WINDY_LOGGER_ENABLED] = self.config_entry.options.get( - WINDY_LOGGER_ENABLED - ) - # retain windy data user_input.update(self.windy_data) + #retain sensors + user_input.update(self.sensors) + return self.async_create_entry(title=DOMAIN, data=user_input) self.user_data = user_input @@ -133,6 +133,9 @@ class ConfigOptionsFlowHandler(config_entries.OptionsFlow): # retain user_data user_input.update(self.user_data) + #retain senors + user_input.update(self.sensors) + return self.async_create_entry(title=DOMAIN, data=user_input) diff --git a/custom_components/sws12500/const.py b/custom_components/sws12500/const.py index b38c8f3..e8b0c55 100644 --- a/custom_components/sws12500/const.py +++ b/custom_components/sws12500/const.py @@ -11,6 +11,8 @@ ICON = "mdi:weather" API_KEY = "API_KEY" API_ID = "API_ID" +SENSORS_TO_LOAD: Final = "sensors_to_load" + DEV_DBG: Final = "dev_debug_checkbox" WINDY_API_KEY = "WINDY_API_KEY" @@ -85,7 +87,5 @@ REMAP_ITEMS: dict = { "soilmoisture": CH2_HUMIDITY, } -DISABLED_BY_DEFAULT: Final = [ - CH2_TEMP, - CH2_HUMIDITY -] +DISABLED_BY_DEFAULT: Final = [CH2_TEMP, CH2_HUMIDITY] + diff --git a/custom_components/sws12500/sensor.py b/custom_components/sws12500/sensor.py index e696faf..0c439bd 100644 --- a/custom_components/sws12500/sensor.py +++ b/custom_components/sws12500/sensor.py @@ -25,7 +25,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.device_registry import DeviceEntryType -from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity import DeviceInfo, generate_entity_id from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -42,6 +42,7 @@ from .const import ( OUTSIDE_HUMIDITY, OUTSIDE_TEMP, RAIN, + SENSORS_TO_LOAD, SOLAR_RADIATION, UV, WIND_DIR, @@ -177,6 +178,7 @@ SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = ( ), WeatherSensorEntityDescription( key=UV, + name=UV, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UV_INDEX, icon="mdi:sunglasses", @@ -191,7 +193,6 @@ SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfTemperature.CELSIUS, icon="mdi:weather-sunny", translation_key=CH2_TEMP, - entity_registry_visible_default=False, value_fn=lambda data: cast(float, data), ), WeatherSensorEntityDescription( @@ -201,7 +202,6 @@ SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.HUMIDITY, icon="mdi:weather-sunny", translation_key=CH2_HUMIDITY, - entity_registry_visible_default=False, value_fn=lambda data: cast(int, data), ), ) @@ -215,10 +215,18 @@ async def async_setup_entry( """Set up Weather Station sensors.""" coordinator: WeatherDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] - sensors = [] - for description in SENSOR_TYPES: - sensors.append(WeatherSensor(hass, description, coordinator)) - async_add_entities(sensors) + + sensors_to_load: list = [] + sensors: list = [] + + # Check if we have some sensors to load. + if sensors_to_load := config_entry.options.get(SENSORS_TO_LOAD): + sensors = [ + WeatherSensor(hass, description, coordinator) + for description in SENSOR_TYPES + if description.key in sensors_to_load + ] + async_add_entities(sensors) class WeatherSensor( @@ -226,7 +234,6 @@ class WeatherSensor( ): """Implementation of Weather Sensor entity.""" - entity_description: WeatherSensorEntityDescription _attr_has_entity_name = True _attr_should_poll = False @@ -244,20 +251,18 @@ class WeatherSensor( self._attr_unique_id = description.key self._data = None - async def async_added_to_hass(self) -> None: - """Handle disabled entities that has previous data.""" + # async def async_added_to_hass(self) -> None: + # """Handle listeners to reloaded sensors.""" - await super().async_added_to_hass() + # await super().async_added_to_hass() - self.coordinator.async_add_listener(self._handle_coordinator_update) + # self.coordinator.async_add_listener(self._handle_coordinator_update) - prev_state_data = await self.async_get_last_sensor_data() - prev_state = await self.async_get_last_state() - if not prev_state: - return - self._data = prev_state_data.native_value - if not self.entity_registry_visible_default: - self.entity_registry_visible_default = True + # # prev_state_data = await self.async_get_last_sensor_data() + # # prev_state = await self.async_get_last_state() + # # if not prev_state: + # # return + # # self._data = prev_state_data.native_value @callback def _handle_coordinator_update(self) -> None: @@ -272,9 +277,14 @@ class WeatherSensor( return self.entity_description.value_fn(self._data) @property - def state_class(self) -> str: - """Return stateClass.""" - return str(self.entity_description.state_class) + def suggested_entity_id(self) -> str: + """Return name.""" + return generate_entity_id("sensor.{}", self.entity_description.key) + + # @property + # def translation_key(self) -> str: + # """"Returns translation key.""" + # return self.entity_description.translation_key @property def device_info(self) -> DeviceInfo: diff --git a/custom_components/sws12500/utils.py b/custom_components/sws12500/utils.py index 274d574..aad89c0 100644 --- a/custom_components/sws12500/utils.py +++ b/custom_components/sws12500/utils.py @@ -3,16 +3,14 @@ import logging from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er -from .const import DISABLED_BY_DEFAULT, DOMAIN, REMAP_ITEMS +from .const import DEV_DBG, REMAP_ITEMS, SENSORS_TO_LOAD _LOGGER = logging.getLogger(__name__) -def update_options( +async def update_options( hass: HomeAssistant, entry: ConfigEntry, update_key, update_value ) -> None: """Update config.options entry.""" @@ -23,7 +21,7 @@ def update_options( conf[update_key] = update_value - hass.config_entries.async_update_entry(entry, options=conf) + return hass.config_entries.async_update_entry(entry, options=conf) def anonymize(data): @@ -47,37 +45,26 @@ def remap_items(entities): return items -async def check_disabled(hass: HomeAssistant, items, log: bool = False): - """Check if we have data for disabed sensors. +def check_disabled(hass: HomeAssistant, items, config_entry: ConfigEntry) -> list | None: + """Check if we have data for unloaded sensors. - If so, then enable senosor. + If so, then add sensor to load queue. - Returns True if sensor found else False + Returns list of found sensors or None """ - _ER = er.async_get(hass) - - eid: str = None + log: bool = config_entry.options.get(DEV_DBG) entityFound: bool = False + loaded_sensors: list = config_entry.options.get(SENSORS_TO_LOAD) if config_entry.options.get(SENSORS_TO_LOAD) else [] - for disabled in DISABLED_BY_DEFAULT: + for item in items: if log: - _LOGGER.info("Checking %s", disabled) - - if disabled in items: - eid = _ER.async_get_entity_id(Platform.SENSOR, DOMAIN, disabled) - is_disabled = _ER.entities[eid].hidden + _LOGGER.info("Checking %s", item) + if item not in loaded_sensors: + loaded_sensors.append(item) + entityFound = True if log: - _LOGGER.info("Found sensor %s", eid) + _LOGGER.info("Add sensor (%s) to loading queue", item) - if is_disabled: - if log: - _LOGGER.info("Sensor %s is hidden. Making visible", eid) - _ER.async_update_entity(eid, hidden_by=None) - entityFound = True - - elif not is_disabled and log: - _LOGGER.info("Sensor %s is visible.", eid) - - return entityFound + return loaded_sensors if entityFound else None