diff --git a/custom_components/sws12500/__init__.py b/custom_components/sws12500/__init__.py index 7c6a43f..1e1c487 100644 --- a/custom_components/sws12500/__init__.py +++ b/custom_components/sws12500/__init__.py @@ -36,11 +36,7 @@ from py_typecheck import checked, checked_or from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ( - ConfigEntryNotReady, - InvalidStateError, - PlatformNotReady, -) +from homeassistant.exceptions import ConfigEntryNotReady, InvalidStateError, PlatformNotReady from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ( @@ -215,9 +211,7 @@ class WeatherDataUpdateCoordinator(DataUpdateCoordinator): raise HTTPUnauthorized # Convert raw payload keys to our internal sensor keys (stable identifiers). - remaped_items: dict[str, str] = ( - remap_wslink_items(data) if _wslink else remap_items(data) - ) + remaped_items: dict[str, str] = remap_wslink_items(data) if _wslink else remap_items(data) # Auto-discovery: if payload contains keys that are not enabled/loaded yet, # add them to the option list and create entities dynamically. @@ -274,9 +268,7 @@ class WeatherDataUpdateCoordinator(DataUpdateCoordinator): # NOTE: Some linters prefer top-level imports. In this case the local import is # intentional and prevents "partially initialized module" errors. - from .sensor import ( # noqa: PLC0415 (local import is intentional) - add_new_sensors, - ) + from .sensor import add_new_sensors # noqa: PLC0415 (local import is intentional) add_new_sensors(self.hass, self.config, newly_discovered) @@ -294,9 +286,7 @@ class WeatherDataUpdateCoordinator(DataUpdateCoordinator): # to avoid additional background polling tasks. _windy_enabled = checked_or(self.config.options.get(WINDY_ENABLED), bool, False) - _pocasi_enabled = checked_or( - self.config.options.get(POCASI_CZ_ENABLED), bool, False - ) + _pocasi_enabled = checked_or(self.config.options.get(POCASI_CZ_ENABLED), bool, False) if _windy_enabled: await self.windy.push_data_to_windy(data, _wslink) @@ -342,38 +332,22 @@ def register_path( # Register webhooks in HomeAssistant with dispatcher try: - _default_route = hass.http.app.router.add_get( - DEFAULT_URL, routes.dispatch, name="_default_route" - ) - _wslink_post_route = hass.http.app.router.add_post( - WSLINK_URL, routes.dispatch, name="_wslink_post_route" - ) - _wslink_get_route = hass.http.app.router.add_get( - WSLINK_URL, routes.dispatch, name="_wslink_get_route" - ) - _health_route = hass.http.app.router.add_get( - HEALTH_URL, routes.dispatch, name="_health_route" - ) + _default_route = hass.http.app.router.add_get(DEFAULT_URL, routes.dispatch, name="_default_route") + _wslink_post_route = hass.http.app.router.add_post(WSLINK_URL, routes.dispatch, name="_wslink_post_route") + _wslink_get_route = hass.http.app.router.add_get(WSLINK_URL, routes.dispatch, name="_wslink_get_route") + _health_route = hass.http.app.router.add_get(HEALTH_URL, routes.dispatch, name="_health_route") # Save initialised routes hass_data["routes"] = routes except RuntimeError as Ex: - _LOGGER.critical( - "Routes cannot be added. Integration will not work as expected. %s", Ex - ) + _LOGGER.critical("Routes cannot be added. Integration will not work as expected. %s", Ex) raise ConfigEntryNotReady from Ex # Finally create internal route dispatcher with provided urls, while we have webhooks registered. - routes.add_route( - DEFAULT_URL, _default_route, coordinator.received_data, enabled=not _wslink - ) - routes.add_route( - WSLINK_URL, _wslink_post_route, coordinator.received_data, enabled=_wslink - ) - routes.add_route( - WSLINK_URL, _wslink_get_route, coordinator.received_data, enabled=_wslink - ) + routes.add_route(DEFAULT_URL, _default_route, coordinator.received_data, enabled=not _wslink) + routes.add_route(WSLINK_URL, _wslink_post_route, coordinator.received_data, enabled=_wslink) + routes.add_route(WSLINK_URL, _wslink_get_route, coordinator.received_data, enabled=_wslink) # Make health route `sticky` so it will not change upon updating options. routes.add_route( HEALTH_URL, @@ -449,9 +423,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if routes: _LOGGER.debug("We have routes registered, will try to switch dispatcher.") - routes.switch_route( - coordinator.received_data, DEFAULT_URL if not _wslink else WSLINK_URL - ) + routes.switch_route(coordinator.received_data, DEFAULT_URL if not _wslink else WSLINK_URL) routes.set_ingress_observer(coordinator_health.record_dispatch) coordinator_health.update_routing(routes) _LOGGER.debug("%s", routes.show_enabled()) @@ -487,14 +459,8 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry): """ if (hass_data := checked(hass.data.get(DOMAIN), dict[str, Any])) is not None: - if ( - entry_data := checked(hass_data.get(entry.entry_id), dict[str, Any]) - ) is not None: - if ( - old_options := checked( - entry_data.get(ENTRY_LAST_OPTIONS), dict[str, Any] - ) - ) is not None: + if (entry_data := checked(hass_data.get(entry.entry_id), dict[str, Any])) is not None: + if (old_options := checked(entry_data.get(ENTRY_LAST_OPTIONS), dict[str, Any])) is not None: new_options = dict(entry.options) changed_keys = { @@ -507,9 +473,7 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry): entry_data[ENTRY_LAST_OPTIONS] = new_options if changed_keys == {SENSORS_TO_LOAD}: - _LOGGER.debug( - "Options updated (%s); skipping reload.", SENSORS_TO_LOAD - ) + _LOGGER.debug("Options updated (%s); skipping reload.", SENSORS_TO_LOAD) return else: # No/invalid snapshot: store current options for next comparison. diff --git a/custom_components/sws12500/battery_sensors.py b/custom_components/sws12500/battery_sensors.py new file mode 100644 index 0000000..b17cb96 --- /dev/null +++ b/custom_components/sws12500/battery_sensors.py @@ -0,0 +1,53 @@ +"""Battery binary sensor entities.""" + +from __future__ import annotations + +from typing import Any + +from py_typecheck import checked_or + +from homeassistant.components.binary_sensor import BinarySensorEntity, BinarySensorEntityDescription +from homeassistant.helpers.update_coordinator import CoordinatorEntity + + +class BatteryBinarySensor( # pyright: ignore[reportIncompatibleVariableOverride] + CoordinatorEntity, BinarySensorEntity +): + """Represent a low-battery binary sensor. + + Station payload uses: + - ``0`` => low battery (binary sensor is ``on``) + - ``1`` => battery OK (binary sensor is ``off``) + """ + + _attr_has_entity_name = True + _attr_should_poll = False + + def __init__( + self, + coordinator: Any, + description: BinarySensorEntityDescription, + ) -> None: + """Initialize the battery binary sensor.""" + super().__init__(coordinator) + self.entity_description = description + self._attr_unique_id = f"{description.key}_battery" + + @property + def is_on(self) -> bool | None: # pyright: ignore[reportIncompatibleVariableOverride] + """Return low-battery state. + + ``True`` means low battery for ``BinarySensorDeviceClass.BATTERY``. + """ + data = checked_or(self.coordinator.data, dict[str, Any], {}) + raw: Any = data.get(self.entity_description.key) + + if raw is None or raw == "": + return None + + try: + value = int(raw) + except (TypeError, ValueError): + return None + + return value == 0 diff --git a/custom_components/sws12500/battery_sensors_def.py b/custom_components/sws12500/battery_sensors_def.py new file mode 100644 index 0000000..7292858 --- /dev/null +++ b/custom_components/sws12500/battery_sensors_def.py @@ -0,0 +1,24 @@ +"""Battery sensors.""" + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntityDescription, +) + +BATTERY_BINARY_SENSORS: tuple[BinarySensorEntityDescription, ...] = ( + BinarySensorEntityDescription( + key="outside_battery", + translation_key="outside_battery", + device_class=BinarySensorDeviceClass.BATTERY, + ), + BinarySensorEntityDescription( + key="indoor_battery", + translation_key="indoor_battery", + device_class=BinarySensorDeviceClass.BATTERY, + ), + BinarySensorEntityDescription( + key="ch2_battery", + translation_key="ch2_battery", + device_class=BinarySensorDeviceClass.BATTERY, + ), +) diff --git a/custom_components/sws12500/sensors_weather.py b/custom_components/sws12500/sensors_weather.py index 84de10e..9e74d23 100644 --- a/custom_components/sws12500/sensors_weather.py +++ b/custom_components/sws12500/sensors_weather.py @@ -1,7 +1,5 @@ """Sensor entities for the SWS12500 integration for old endpoint.""" -from typing import cast - from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass from homeassistant.const import ( DEGREE, @@ -41,7 +39,7 @@ from .const import ( UnitOfDir, ) from .sensors_common import WeatherSensorEntityDescription -from .utils import chill_index, heat_index, wind_dir_to_text +from .utils import chill_index, heat_index, to_float, to_int, wind_dir_to_text SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( WeatherSensorEntityDescription( @@ -51,7 +49,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( icon="mdi:thermometer", device_class=SensorDeviceClass.TEMPERATURE, translation_key=INDOOR_TEMP, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=INDOOR_HUMIDITY, @@ -60,7 +58,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( icon="mdi:thermometer", device_class=SensorDeviceClass.HUMIDITY, translation_key=INDOOR_HUMIDITY, - value_fn=lambda data: cast("int", data), + value_fn=to_int, ), WeatherSensorEntityDescription( key=OUTSIDE_TEMP, @@ -69,7 +67,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( icon="mdi:thermometer", device_class=SensorDeviceClass.TEMPERATURE, translation_key=OUTSIDE_TEMP, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=OUTSIDE_HUMIDITY, @@ -78,7 +76,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( icon="mdi:thermometer", device_class=SensorDeviceClass.HUMIDITY, translation_key=OUTSIDE_HUMIDITY, - value_fn=lambda data: cast("int", data), + value_fn=to_int, ), WeatherSensorEntityDescription( key=DEW_POINT, @@ -87,7 +85,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( icon="mdi:thermometer-lines", device_class=SensorDeviceClass.TEMPERATURE, translation_key=DEW_POINT, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=BARO_PRESSURE, @@ -97,7 +95,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE, suggested_unit_of_measurement=UnitOfPressure.HPA, translation_key=BARO_PRESSURE, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=WIND_SPEED, @@ -107,7 +105,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR, icon="mdi:weather-windy", translation_key=WIND_SPEED, - value_fn=lambda data: cast("int", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=WIND_GUST, @@ -117,7 +115,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR, icon="mdi:windsock", translation_key=WIND_GUST, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=WIND_DIR, @@ -127,15 +125,12 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( suggested_display_precision=None, icon="mdi:sign-direction", translation_key=WIND_DIR, - value_fn=lambda data: cast("int", data), + value_fn=to_int, ), WeatherSensorEntityDescription( key=WIND_AZIMUT, icon="mdi:sign-direction", - value_fn=lambda data: cast("str", wind_dir_to_text(data)), - value_from_data_fn=lambda data: cast( - "str", wind_dir_to_text(cast("float", data.get(WIND_DIR) or 0.0)) - ), + value_from_data_fn=lambda dir: wind_dir_to_text(dir.get(WIND_DIR, 0.0)), device_class=SensorDeviceClass.ENUM, options=[e.value for e in UnitOfDir], translation_key=WIND_AZIMUT, @@ -149,7 +144,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( suggested_display_precision=2, icon="mdi:weather-pouring", translation_key=RAIN, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=DAILY_RAIN, @@ -160,7 +155,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( suggested_display_precision=2, icon="mdi:weather-pouring", translation_key=DAILY_RAIN, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=SOLAR_RADIATION, @@ -169,7 +164,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.IRRADIANCE, icon="mdi:weather-sunny", translation_key=SOLAR_RADIATION, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=UV, @@ -178,7 +173,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( native_unit_of_measurement=UV_INDEX, icon="mdi:sunglasses", translation_key=UV, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=CH2_TEMP, @@ -188,7 +183,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfTemperature.CELSIUS, icon="mdi:weather-sunny", translation_key=CH2_TEMP, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=CH2_HUMIDITY, @@ -197,7 +192,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.HUMIDITY, icon="mdi:weather-sunny", translation_key=CH2_HUMIDITY, - value_fn=lambda data: cast("int", data), + value_fn=to_int, ), WeatherSensorEntityDescription( key=CH3_TEMP, @@ -207,7 +202,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfTemperature.CELSIUS, icon="mdi:weather-sunny", translation_key=CH3_TEMP, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=CH3_HUMIDITY, @@ -216,7 +211,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.HUMIDITY, icon="mdi:weather-sunny", translation_key=CH3_HUMIDITY, - value_fn=lambda data: cast("int", data), + value_fn=to_int, ), WeatherSensorEntityDescription( key=CH4_TEMP, @@ -226,7 +221,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfTemperature.CELSIUS, icon="mdi:weather-sunny", translation_key=CH4_TEMP, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=CH4_HUMIDITY, @@ -235,7 +230,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.HUMIDITY, icon="mdi:weather-sunny", translation_key=CH4_HUMIDITY, - value_fn=lambda data: cast("int", data), + value_fn=to_int, ), WeatherSensorEntityDescription( key=HEAT_INDEX, @@ -246,7 +241,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( suggested_display_precision=2, icon="mdi:weather-sunny", translation_key=HEAT_INDEX, - value_fn=lambda data: cast("int", data), + value_fn=to_int, value_from_data_fn=heat_index, ), WeatherSensorEntityDescription( @@ -258,7 +253,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( suggested_display_precision=2, icon="mdi:weather-sunny", translation_key=CHILL_INDEX, - value_fn=lambda data: cast("int", data), + value_fn=to_int, value_from_data_fn=chill_index, ), ) diff --git a/custom_components/sws12500/sensors_wslink.py b/custom_components/sws12500/sensors_wslink.py index d2051a3..73aeafe 100644 --- a/custom_components/sws12500/sensors_wslink.py +++ b/custom_components/sws12500/sensors_wslink.py @@ -1,7 +1,5 @@ """Sensor entities for the SWS12500 integration for old endpoint.""" -from typing import cast - from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass from homeassistant.const import ( DEGREE, @@ -64,7 +62,7 @@ from .const import ( UnitOfDir, ) from .sensors_common import WeatherSensorEntityDescription -from .utils import battery_level, wind_dir_to_text +from .utils import battery_level, to_float, to_int, wind_dir_to_text SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( WeatherSensorEntityDescription( @@ -74,7 +72,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( icon="mdi:thermometer", device_class=SensorDeviceClass.TEMPERATURE, translation_key=INDOOR_TEMP, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=INDOOR_HUMIDITY, @@ -83,7 +81,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( icon="mdi:thermometer", device_class=SensorDeviceClass.HUMIDITY, translation_key=INDOOR_HUMIDITY, - value_fn=lambda data: cast("int", data), + value_fn=to_int, ), WeatherSensorEntityDescription( key=OUTSIDE_TEMP, @@ -92,7 +90,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( icon="mdi:thermometer", device_class=SensorDeviceClass.TEMPERATURE, translation_key=OUTSIDE_TEMP, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=OUTSIDE_HUMIDITY, @@ -101,7 +99,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( icon="mdi:thermometer", device_class=SensorDeviceClass.HUMIDITY, translation_key=OUTSIDE_HUMIDITY, - value_fn=lambda data: cast("int", data), + value_fn=to_int, ), WeatherSensorEntityDescription( key=DEW_POINT, @@ -110,7 +108,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( icon="mdi:thermometer-lines", device_class=SensorDeviceClass.TEMPERATURE, translation_key=DEW_POINT, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=BARO_PRESSURE, @@ -120,7 +118,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE, suggested_unit_of_measurement=UnitOfPressure.HPA, translation_key=BARO_PRESSURE, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=WIND_SPEED, @@ -130,7 +128,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR, icon="mdi:weather-windy", translation_key=WIND_SPEED, - value_fn=lambda data: cast("int", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=WIND_GUST, @@ -140,7 +138,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR, icon="mdi:windsock", translation_key=WIND_GUST, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=WIND_DIR, @@ -150,15 +148,12 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_display_precision=None, icon="mdi:sign-direction", translation_key=WIND_DIR, - value_fn=lambda data: cast("int", data), + value_fn=to_int, ), WeatherSensorEntityDescription( key=WIND_AZIMUT, icon="mdi:sign-direction", - value_fn=lambda data: cast("str", wind_dir_to_text(data)), - value_from_data_fn=lambda data: cast( - "str", wind_dir_to_text(cast("float", data.get(WIND_DIR) or 0.0)) - ), + value_from_data_fn=lambda dir: wind_dir_to_text(dir.get(WIND_DIR, 0.0)), device_class=SensorDeviceClass.ENUM, options=[e.value for e in UnitOfDir], translation_key=WIND_AZIMUT, @@ -172,7 +167,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_display_precision=2, icon="mdi:weather-pouring", translation_key=RAIN, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=DAILY_RAIN, @@ -183,7 +178,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_display_precision=2, icon="mdi:weather-pouring", translation_key=DAILY_RAIN, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=HOURLY_RAIN, @@ -194,7 +189,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_display_precision=2, icon="mdi:weather-pouring", translation_key=HOURLY_RAIN, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=WEEKLY_RAIN, @@ -205,7 +200,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_display_precision=2, icon="mdi:weather-pouring", translation_key=WEEKLY_RAIN, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=MONTHLY_RAIN, @@ -216,7 +211,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_display_precision=2, icon="mdi:weather-pouring", translation_key=MONTHLY_RAIN, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=YEARLY_RAIN, @@ -227,7 +222,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_display_precision=2, icon="mdi:weather-pouring", translation_key=YEARLY_RAIN, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=SOLAR_RADIATION, @@ -236,7 +231,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.IRRADIANCE, icon="mdi:weather-sunny", translation_key=SOLAR_RADIATION, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=UV, @@ -245,7 +240,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( native_unit_of_measurement=UV_INDEX, icon="mdi:sunglasses", translation_key=UV, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=CH2_TEMP, @@ -255,7 +250,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfTemperature.CELSIUS, icon="mdi:weather-sunny", translation_key=CH2_TEMP, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=CH2_HUMIDITY, @@ -264,7 +259,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.HUMIDITY, icon="mdi:weather-sunny", translation_key=CH2_HUMIDITY, - value_fn=lambda data: cast("int", data), + value_fn=to_int, ), WeatherSensorEntityDescription( key=CH3_TEMP, @@ -274,7 +269,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfTemperature.CELSIUS, icon="mdi:weather-sunny", translation_key=CH3_TEMP, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=CH3_HUMIDITY, @@ -283,7 +278,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.HUMIDITY, icon="mdi:weather-sunny", translation_key=CH3_HUMIDITY, - value_fn=lambda data: cast("int", data), + value_fn=to_int, ), WeatherSensorEntityDescription( key=CH4_TEMP, @@ -293,7 +288,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfTemperature.CELSIUS, icon="mdi:weather-sunny", translation_key=CH4_TEMP, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=CH4_HUMIDITY, @@ -302,7 +297,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.HUMIDITY, icon="mdi:weather-sunny", translation_key=CH4_HUMIDITY, - value_fn=lambda data: cast("int", data), + value_fn=to_int, ), WeatherSensorEntityDescription( key=CH5_TEMP, @@ -312,7 +307,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfTemperature.CELSIUS, icon="mdi:weather-sunny", translation_key=CH5_TEMP, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=CH5_HUMIDITY, @@ -321,7 +316,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.HUMIDITY, icon="mdi:weather-sunny", translation_key=CH5_HUMIDITY, - value_fn=lambda data: cast("int", data), + value_fn=to_int, ), WeatherSensorEntityDescription( key=CH6_TEMP, @@ -331,7 +326,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfTemperature.CELSIUS, icon="mdi:weather-sunny", translation_key=CH6_TEMP, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=CH6_HUMIDITY, @@ -340,7 +335,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.HUMIDITY, icon="mdi:weather-sunny", translation_key=CH6_HUMIDITY, - value_fn=lambda data: cast("int", data), + value_fn=to_int, ), WeatherSensorEntityDescription( key=CH7_TEMP, @@ -350,7 +345,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfTemperature.CELSIUS, icon="mdi:weather-sunny", translation_key=CH7_TEMP, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=CH7_HUMIDITY, @@ -359,7 +354,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.HUMIDITY, icon="mdi:weather-sunny", translation_key=CH7_HUMIDITY, - value_fn=lambda data: cast("int", data), + value_fn=to_int, ), WeatherSensorEntityDescription( key=CH8_TEMP, @@ -369,7 +364,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfTemperature.CELSIUS, icon="mdi:weather-sunny", translation_key=CH8_TEMP, - value_fn=lambda data: cast("float", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=CH8_HUMIDITY, @@ -378,34 +373,28 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.HUMIDITY, icon="mdi:weather-sunny", translation_key=CH8_HUMIDITY, - value_fn=lambda data: cast("int", data), - ), - WeatherSensorEntityDescription( - key=HEAT_INDEX, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - device_class=SensorDeviceClass.ENUM, - value_fn=lambda data: data, + value_fn=to_int, ), WeatherSensorEntityDescription( key=CH3_BATTERY, translation_key=CH3_BATTERY, icon="mdi:battery-unknown", device_class=SensorDeviceClass.ENUM, - value_fn=lambda data: data, + value_fn=to_int, ), WeatherSensorEntityDescription( key=CH4_BATTERY, translation_key=CH4_BATTERY, icon="mdi:battery-unknown", device_class=SensorDeviceClass.ENUM, - value_fn=lambda data: data, + value_fn=to_int, ), WeatherSensorEntityDescription( key=CH5_BATTERY, translation_key=CH5_BATTERY, icon="mdi:battery-unknown", device_class=SensorDeviceClass.ENUM, - value_fn=lambda data: data, + value_fn=to_int, ), WeatherSensorEntityDescription( key=CH6_BATTERY, @@ -419,14 +408,14 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( translation_key=CH7_BATTERY, icon="mdi:battery-unknown", device_class=SensorDeviceClass.ENUM, - value_fn=lambda data: data, + value_fn=to_int, ), WeatherSensorEntityDescription( key=CH8_BATTERY, translation_key=CH8_BATTERY, icon="mdi:battery-unknown", device_class=SensorDeviceClass.ENUM, - value_fn=lambda data: data, + value_fn=to_int, ), WeatherSensorEntityDescription( key=HEAT_INDEX, @@ -437,7 +426,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_display_precision=2, icon="mdi:weather-sunny", translation_key=HEAT_INDEX, - value_fn=lambda data: cast("int", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=CHILL_INDEX, @@ -448,7 +437,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( suggested_display_precision=2, icon="mdi:weather-sunny", translation_key=CHILL_INDEX, - value_fn=lambda data: cast("int", data), + value_fn=to_float, ), WeatherSensorEntityDescription( key=OUTSIDE_BATTERY, @@ -473,12 +462,8 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( WeatherSensorEntityDescription( key=INDOOR_BATTERY, translation_key=INDOOR_BATTERY, - device_class=SensorDeviceClass.ENUM, - options=[e.value for e in UnitOfBat], - value_fn=None, - value_from_data_fn=lambda data: ( - battery_level(data.get(INDOOR_BATTERY, None)).value - ), + device_class=SensorDeviceClass.BATTERY, + value_fn=to_int, ), WeatherSensorEntityDescription( key=WBGT_TEMP, @@ -488,6 +473,6 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, suggested_display_precision=2, - value_fn=lambda data: cast("int", data), + value_fn=to_float, ), ) diff --git a/custom_components/sws12500/utils.py b/custom_components/sws12500/utils.py index 838374c..be52448 100644 --- a/custom_components/sws12500/utils.py +++ b/custom_components/sws12500/utils.py @@ -204,8 +204,10 @@ def wind_dir_to_text(deg: float) -> UnitOfDir | None: Returns UnitOfDir or None """ - if deg: - return AZIMUT[int(abs((float(deg) - 11.25) % 360) / 22.5)] + _deg = to_float(deg) + if _deg is not None: + _LOGGER.debug("wind_dir: %s", AZIMUT[int(abs((_deg - 11.25) % 360) / 22.5)]) + return AZIMUT[int(abs((_deg - 11.25) % 360) / 22.5)] return None @@ -263,11 +265,32 @@ def celsius_to_fahrenheit(celsius: float) -> float: return celsius * 9.0 / 5.0 + 32 -def _to_float(val: Any) -> float | None: +def to_int(val: Any) -> int | None: + """Convert int or string to int.""" + + if val is None: + return None + + if isinstance(val, str) and val.strip() == "": + return None + + try: + v = int(val) + except (TypeError, ValueError): + return None + else: + return v + + +def to_float(val: Any) -> float | None: """Convert int or string to float.""" - if not val: + if val is None: return None + + if isinstance(val, str) and val.strip() == "": + return None + try: v = float(val) except (TypeError, ValueError): @@ -284,14 +307,14 @@ def heat_index( data: dict with temperature and humidity convert: bool, convert recieved data from Celsius to Fahrenheit """ - if (temp := _to_float(data.get(OUTSIDE_TEMP))) is None: + if (temp := to_float(data.get(OUTSIDE_TEMP))) is None: _LOGGER.error( "We are missing/invalid OUTSIDE TEMP (%s), cannot calculate wind chill index.", temp, ) return None - if (rh := _to_float(data.get(OUTSIDE_HUMIDITY))) is None: + if (rh := to_float(data.get(OUTSIDE_HUMIDITY))) is None: _LOGGER.error( "We are missing/invalid OUTSIDE HUMIDITY (%s), cannot calculate wind chill index.", rh, @@ -335,8 +358,8 @@ def chill_index( data: dict with temperature and wind speed convert: bool, convert recieved data from Celsius to Fahrenheit """ - temp = _to_float(data.get(OUTSIDE_TEMP)) - wind = _to_float(data.get(WIND_SPEED)) + temp = to_float(data.get(OUTSIDE_TEMP)) + wind = to_float(data.get(WIND_SPEED)) if temp is None: _LOGGER.error(