From 37c3e2e77f1e981da483c56c02c8a32b82a2bbdc Mon Sep 17 00:00:00 2001 From: schizza Date: Mon, 10 Mar 2025 08:42:17 +0100 Subject: [PATCH] Adds sensors for WSLink and improves data handling Includes new sensor connection status constants and mappings Updates WeatherSensor to handle null data values Switches temperature unit to Celsius for WSLink sensors Refines heat and chill index calculations with unit conversions --- custom_components/sws12500/__init__.py | 4 ++ custom_components/sws12500/const.py | 29 ++++++++++-- custom_components/sws12500/sensor.py | 16 +++++-- custom_components/sws12500/sensors_weather.py | 45 ++++++++++--------- custom_components/sws12500/sensors_wslink.py | 43 +++++++++--------- custom_components/sws12500/utils.py | 36 ++++++++++++--- 6 files changed, 116 insertions(+), 57 deletions(-) diff --git a/custom_components/sws12500/__init__.py b/custom_components/sws12500/__init__.py index 558d134..f7f1f45 100644 --- a/custom_components/sws12500/__init__.py +++ b/custom_components/sws12500/__init__.py @@ -97,6 +97,10 @@ class WeatherDataUpdateCoordinator(DataUpdateCoordinator): self.hass, DOMAIN, f"sensor.{t_key}", key="name", category="entity" ) for t_key in sensors + if await translations( + self.hass, DOMAIN, f"sensor.{t_key}", key="name", category="entity" + ) + is not None ] human_readable = "\n".join(translate_sensors) diff --git a/custom_components/sws12500/const.py b/custom_components/sws12500/const.py index 9a60f76..19e06d6 100644 --- a/custom_components/sws12500/const.py +++ b/custom_components/sws12500/const.py @@ -60,6 +60,7 @@ BARO_PRESSURE: Final = "baro_pressure" OUTSIDE_TEMP: Final = "outside_temp" DEW_POINT: Final = "dew_point" OUTSIDE_HUMIDITY: Final = "outside_humidity" +OUTSIDE_CONNECTION: Final = "outside_connection" WIND_SPEED: Final = "wind_speed" WIND_GUST: Final = "wind_gust" WIND_DIR: Final = "wind_dir" @@ -72,10 +73,13 @@ INDOOR_HUMIDITY: Final = "indoor_humidity" UV: Final = "uv" CH2_TEMP: Final = "ch2_temp" CH2_HUMIDITY: Final = "ch2_humidity" +CH2_CONNECTION: Final = "ch2_connection" CH3_TEMP: Final = "ch3_temp" CH3_HUMIDITY: Final = "ch3_humidity" +CH3_CONNECTION: Final = "ch3_connection" CH4_TEMP: Final = "ch4_temp" CH4_HUMIDITY: Final = "ch4_humidity" +CH4_CONNECTION: Final = "ch4_connection" HEAT_INDEX: Final = "heat_index" CHILL_INDEX: Final = "chill_index" @@ -105,19 +109,37 @@ REMAP_ITEMS: dict = { REMAP_WSLINK_ITEMS: dict = { "intem": INDOOR_TEMP, "inhum": INDOOR_HUMIDITY, - "t1temp": OUTSIDE_TEMP, + "t1tem": OUTSIDE_TEMP, "t1hum": OUTSIDE_HUMIDITY, "t1dew": DEW_POINT, "t1wdir": WIND_DIR, "t1ws": WIND_SPEED, - "t1wg": WIND_GUST, + "t1wgust": WIND_GUST, "t1rainra": RAIN, "t1raindy": DAILY_RAIN, "t1solrad": SOLAR_RADIATION, "rbar": BARO_PRESSURE, - "uvi": UV + "t1uvi": UV, + "t234c1tem": CH2_TEMP, + "t234c1hum": CH2_HUMIDITY, + "t1cn": OUTSIDE_CONNECTION, + "t234c1cn": CH2_CONNECTION, + "t234c2cn": CH3_CONNECTION, + "t1chill": CHILL_INDEX, + "t1heat": HEAT_INDEX, } +# TODO: Add more sensors +# +# 'inbat' indoor battery level (1 normal, 0 low) +# 't1rainhr' hourly rain rate in mm +# 't1rainwy' weekly rain rate in mm +# 't1rainmth': monthly rain rate in mm +# 't1rainyr': yearly rain rate in mm +# 't1bat': outdoor battery level (1 normal, 0 low) +# 't234c1bat': CH2 battery level (1 normal, 0 low) CH2 in integration is CH1 in WSLink + + DISABLED_BY_DEFAULT: Final = [ CH2_TEMP, CH2_HUMIDITY, @@ -167,4 +189,3 @@ AZIMUT: list[UnitOfDir] = [ UnitOfDir.NNW, UnitOfDir.N, ] - diff --git a/custom_components/sws12500/sensor.py b/custom_components/sws12500/sensor.py index fc6888d..a0275a2 100644 --- a/custom_components/sws12500/sensor.py +++ b/custom_components/sws12500/sensor.py @@ -111,16 +111,26 @@ class WeatherSensor( def native_value(self) -> str | int | float | None: """Return value of entity.""" + _wslink = self.coordinator.config.options.get(WSLINK) + if self.coordinator.data and (WIND_AZIMUT in self.entity_description.key): return self.entity_description.value_fn(self.coordinator.data.get(WIND_DIR)) - if self.coordinator.data and (HEAT_INDEX in self.entity_description.key): + if ( + self.coordinator.data + and (HEAT_INDEX in self.entity_description.key) + and not _wslink + ): return self.entity_description.value_fn(heat_index(self.coordinator.data)) - if self.coordinator.data and (CHILL_INDEX in self.entity_description.key): + if ( + self.coordinator.data + and (CHILL_INDEX in self.entity_description.key) + and not _wslink + ): return self.entity_description.value_fn(chill_index(self.coordinator.data)) - return self.entity_description.value_fn(self._data) + return None if self._data == "" else self.entity_description.value_fn(self._data) @property def suggested_entity_id(self) -> str: diff --git a/custom_components/sws12500/sensors_weather.py b/custom_components/sws12500/sensors_weather.py index 9912633..a61750f 100644 --- a/custom_components/sws12500/sensors_weather.py +++ b/custom_components/sws12500/sensors_weather.py @@ -51,7 +51,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=lambda data: cast("float", data), ), WeatherSensorEntityDescription( key=INDOOR_HUMIDITY, @@ -64,12 +64,12 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( ), WeatherSensorEntityDescription( key=OUTSIDE_TEMP, - native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, state_class=SensorStateClass.MEASUREMENT, icon="mdi:thermometer", device_class=SensorDeviceClass.TEMPERATURE, translation_key=OUTSIDE_TEMP, - value_fn=lambda data: cast(float, data), + value_fn=lambda data: cast("float", data), ), WeatherSensorEntityDescription( key=OUTSIDE_HUMIDITY, @@ -78,7 +78,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=lambda data: cast("int", data), ), WeatherSensorEntityDescription( key=DEW_POINT, @@ -87,7 +87,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=lambda data: cast("float", data), ), WeatherSensorEntityDescription( key=BARO_PRESSURE, @@ -97,7 +97,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=lambda data: cast("float", data), ), WeatherSensorEntityDescription( key=WIND_SPEED, @@ -107,7 +107,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=lambda data: cast("int", data), ), WeatherSensorEntityDescription( key=WIND_GUST, @@ -117,7 +117,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=lambda data: cast("float", data), ), WeatherSensorEntityDescription( key=WIND_DIR, @@ -126,12 +126,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=lambda data: cast("int", data), ), WeatherSensorEntityDescription( key=WIND_AZIMUT, icon="mdi:sign-direction", - value_fn=lambda data: cast(str, wind_dir_to_text(data)), + value_fn=lambda data: cast("str", wind_dir_to_text(data)), device_class=SensorDeviceClass.ENUM, options=list(UnitOfDir), translation_key=WIND_AZIMUT, @@ -145,7 +145,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=lambda data: cast("float", data), ), WeatherSensorEntityDescription( key=DAILY_RAIN, @@ -156,7 +156,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=lambda data: cast("float", data), ), WeatherSensorEntityDescription( key=SOLAR_RADIATION, @@ -165,7 +165,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=lambda data: cast("float", data), ), WeatherSensorEntityDescription( key=UV, @@ -174,7 +174,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=lambda data: cast("float", data), ), WeatherSensorEntityDescription( key=CH2_TEMP, @@ -184,7 +184,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=lambda data: cast("float", data), ), WeatherSensorEntityDescription( key=CH2_HUMIDITY, @@ -193,7 +193,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=lambda data: cast("int", data), ), WeatherSensorEntityDescription( key=CH3_TEMP, @@ -203,7 +203,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=lambda data: cast("float", data), ), WeatherSensorEntityDescription( key=CH3_HUMIDITY, @@ -212,7 +212,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=lambda data: cast("int", data), ), WeatherSensorEntityDescription( key=CH4_TEMP, @@ -222,7 +222,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=lambda data: cast("float", data), ), WeatherSensorEntityDescription( key=CH4_HUMIDITY, @@ -231,7 +231,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=lambda data: cast("int", data), ), WeatherSensorEntityDescription( key=HEAT_INDEX, @@ -239,9 +239,10 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.TEMPERATURE, suggested_unit_of_measurement=UnitOfTemperature.CELSIUS, + suggested_display_precision=2, icon="mdi:weather-sunny", translation_key=HEAT_INDEX, - value_fn=lambda data: cast(int, data), + value_fn=lambda data: cast("int", data), ), WeatherSensorEntityDescription( key=CHILL_INDEX, @@ -252,6 +253,6 @@ 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=lambda data: cast("int", data), ), ) diff --git a/custom_components/sws12500/sensors_wslink.py b/custom_components/sws12500/sensors_wslink.py index 880ec53..f006e05 100644 --- a/custom_components/sws12500/sensors_wslink.py +++ b/custom_components/sws12500/sensors_wslink.py @@ -176,25 +176,25 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( translation_key=UV, value_fn=lambda data: cast("float", data), ), - # WeatherSensorEntityDescription( - # key=CH2_TEMP, - # native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT, - # state_class=SensorStateClass.MEASUREMENT, - # device_class=SensorDeviceClass.TEMPERATURE, - # suggested_unit_of_measurement=UnitOfTemperature.CELSIUS, - # icon="mdi:weather-sunny", - # translation_key=CH2_TEMP, - # value_fn=lambda data: cast(float, data), - # ), - # WeatherSensorEntityDescription( - # key=CH2_HUMIDITY, - # native_unit_of_measurement=PERCENTAGE, - # state_class=SensorStateClass.MEASUREMENT, - # device_class=SensorDeviceClass.HUMIDITY, - # icon="mdi:weather-sunny", - # translation_key=CH2_HUMIDITY, - # value_fn=lambda data: cast(int, data), - # ), + WeatherSensorEntityDescription( + key=CH2_TEMP, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.TEMPERATURE, + suggested_unit_of_measurement=UnitOfTemperature.CELSIUS, + icon="mdi:weather-sunny", + translation_key=CH2_TEMP, + value_fn=lambda data: cast("float", data), + ), + WeatherSensorEntityDescription( + key=CH2_HUMIDITY, + native_unit_of_measurement=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.HUMIDITY, + icon="mdi:weather-sunny", + translation_key=CH2_HUMIDITY, + value_fn=lambda data: cast("int", data), + ), # WeatherSensorEntityDescription( # key=CH3_TEMP, # native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT, @@ -235,17 +235,18 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( # ), WeatherSensorEntityDescription( key=HEAT_INDEX, - native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.TEMPERATURE, suggested_unit_of_measurement=UnitOfTemperature.CELSIUS, + suggested_display_precision=2, icon="mdi:weather-sunny", translation_key=HEAT_INDEX, value_fn=lambda data: cast("int", data), ), WeatherSensorEntityDescription( key=CHILL_INDEX, - native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.TEMPERATURE, suggested_unit_of_measurement=UnitOfTemperature.CELSIUS, diff --git a/custom_components/sws12500/utils.py b/custom_components/sws12500/utils.py index cbf4d41..d92671c 100644 --- a/custom_components/sws12500/utils.py +++ b/custom_components/sws12500/utils.py @@ -131,9 +131,7 @@ def remap_wslink_items(entities): def loaded_sensors(config_entry: ConfigEntry) -> list | None: """Get loaded sensors.""" - return ( - config_entry.options.get(SENSORS_TO_LOAD) or [] - ) + return config_entry.options.get(SENSORS_TO_LOAD) or [] def check_disabled( @@ -176,13 +174,30 @@ def wind_dir_to_text(deg: float) -> UnitOfDir | None: return None -def heat_index(data: Any) -> UnitOfTemperature: - """Calculate heat index from temperature.""" +def fahrenheit_to_celsius(fahrenheit: float) -> float: + """Convert Fahrenheit to Celsius.""" + return (fahrenheit - 32) * 5.0 / 9.0 + + +def celsius_to_fahrenheit(celsius: float) -> float: + """Convert Celsius to Fahrenheit.""" + return celsius * 9.0 / 5.0 + 32 + + +def heat_index(data: Any, convert: bool = False) -> UnitOfTemperature: + """Calculate heat index from temperature. + + data: dict with temperature and humidity + convert: bool, convert recieved data from Celsius to Fahrenheit + """ temp = float(data[OUTSIDE_TEMP]) rh = float(data[OUTSIDE_HUMIDITY]) adjustment = None + if convert: + temp = celsius_to_fahrenheit(temp) + simple = 0.5 * (temp + 61.0 + ((temp - 68.0) * 1.2) + (rh * 0.094)) if ((simple + temp) / 2) > 80: full_index = ( @@ -207,12 +222,19 @@ def heat_index(data: Any) -> UnitOfTemperature: return simple -def chill_index(data: Any) -> UnitOfTemperature: - """Calculate wind chill index.""" +def chill_index(data: Any, convert: bool = False) -> UnitOfTemperature: + """Calculate wind chill index from temperature and wind speed. + + data: dict with temperature and wind speed + convert: bool, convert recieved data from Celsius to Fahrenheit + """ temp = float(data[OUTSIDE_TEMP]) wind = float(data[WIND_SPEED]) + if convert: + temp = celsius_to_fahrenheit(temp) + return ( round( (