Improve sensor value parsing and add battery binary sensors.

- Introduce `to_int`/`to_float` helpers to safely handle None/blank payload values.
- Use the converters across weather/wslink sensor descriptions and simplify wind direction handling.
- Add low-battery binary sensor entities and definitions.
ecowitt_support
SchiZzA 2026-03-23 18:21:05 +01:00
parent 9d5fafa8d0
commit 159d465db5
No known key found for this signature in database
6 changed files with 190 additions and 146 deletions

View File

@ -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.

View File

@ -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

View File

@ -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,
),
)

View File

@ -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,
),
)

View File

@ -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,
),
)

View File

@ -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(