From 8b0cc2d25e66c5fa18c0ccd563abd152277aa22b Mon Sep 17 00:00:00 2001 From: schizza Date: Thu, 9 May 2024 13:26:01 +0200 Subject: [PATCH] Add Heat Index * Heat index sensor * Heat index computation is added --- custom_components/sws12500/const.py | 1 + custom_components/sws12500/sensor.py | 18 +++++- .../sws12500/translations/cs.json | 1 + .../sws12500/translations/en.json | 1 + custom_components/sws12500/utils.py | 62 +++++++++++++++++-- 5 files changed, 77 insertions(+), 6 deletions(-) diff --git a/custom_components/sws12500/const.py b/custom_components/sws12500/const.py index 793d178..5551e8c 100644 --- a/custom_components/sws12500/const.py +++ b/custom_components/sws12500/const.py @@ -69,6 +69,7 @@ INDOOR_HUMIDITY: Final = "indoor_humidity" UV: Final = "uv" CH2_TEMP: Final = "ch2_temp" CH2_HUMIDITY: Final = "ch2_humidity" +HEAT_INDEX: Final = "heat_index" REMAP_ITEMS: dict = { diff --git a/custom_components/sws12500/sensor.py b/custom_components/sws12500/sensor.py index 72fc409..ebf1750 100644 --- a/custom_components/sws12500/sensor.py +++ b/custom_components/sws12500/sensor.py @@ -37,6 +37,7 @@ from .const import ( DAILY_RAIN, DEW_POINT, DOMAIN, + HEAT_INDEX, INDOOR_HUMIDITY, INDOOR_TEMP, OUTSIDE_HUMIDITY, @@ -51,7 +52,7 @@ from .const import ( WIND_SPEED, UnitOfDir, ) -from .utils import wind_dir_to_text +from .utils import wind_dir_to_text, heat_index _LOGGER = logging.getLogger(__name__) @@ -215,6 +216,16 @@ SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = ( translation_key=CH2_HUMIDITY, value_fn=lambda data: cast(int, data), ), + WeatherSensorEntityDescription( + key=HEAT_INDEX, + 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=HEAT_INDEX, + value_fn=lambda data: cast(int, data), + ), ) @@ -234,6 +245,8 @@ async def async_setup_entry( if sensors_to_load := config_entry.options.get(SENSORS_TO_LOAD): if WIND_DIR in sensors_to_load: sensors_to_load.append(WIND_AZIMUT) + if (WIND_SPEED in sensors_to_load) and (OUTSIDE_TEMP in sensors_to_load): + sensors_to_load.append(HEAT_INDEX) sensors = [ WeatherSensor(hass, description, coordinator) for description in SENSOR_TYPES @@ -293,6 +306,9 @@ class WeatherSensor( 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): + return self.entity_description.value_fn(heat_index(self.coordinator.data)) + return self.entity_description.value_fn(self._data) @property diff --git a/custom_components/sws12500/translations/cs.json b/custom_components/sws12500/translations/cs.json index 52d9a21..f9ba0ad 100644 --- a/custom_components/sws12500/translations/cs.json +++ b/custom_components/sws12500/translations/cs.json @@ -76,6 +76,7 @@ "solar_radiation": { "name": "Sluneční osvit" }, "ch2_temp": { "name": "Teplota senzoru 2" }, "ch2_humidity": { "name": "Vlhkost sensoru 2" }, + "heat_index": { "name": "Tepelný index" }, "wind_azimut": { "name": "Azimut", "state": { diff --git a/custom_components/sws12500/translations/en.json b/custom_components/sws12500/translations/en.json index 2ea26ab..362b175 100644 --- a/custom_components/sws12500/translations/en.json +++ b/custom_components/sws12500/translations/en.json @@ -77,6 +77,7 @@ "solar_radiation": { "name": "Solar irradiance" }, "ch2_temp": { "name": "Channel 2 temperature" }, "ch2_humidity": { "name": "Channel 2 humidity" }, + "heat_index": { "name": "Apparent temperature" }, "wind_azimut": { "name": "Bearing", "state": { diff --git a/custom_components/sws12500/utils.py b/custom_components/sws12500/utils.py index 568400c..5d927fd 100644 --- a/custom_components/sws12500/utils.py +++ b/custom_components/sws12500/utils.py @@ -6,18 +6,31 @@ from homeassistant.components import persistent_notification from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.translation import async_get_translations +from homeassistant.const import UnitOfTemperature +from typing import Any +import math +import numpy as np -from .const import AZIMUT, DEV_DBG, REMAP_ITEMS, SENSORS_TO_LOAD, UnitOfDir +from .const import ( + AZIMUT, + DEV_DBG, + REMAP_ITEMS, + SENSORS_TO_LOAD, + UnitOfDir, + OUTSIDE_TEMP, + OUTSIDE_HUMIDITY, +) _LOGGER = logging.getLogger(__name__) + async def translations( hass: HomeAssistant, translation_domain: str, translation_key: str, *, key: str = "message", - category: str = "notify" + category: str = "notify", ) -> str: """Get translated keys for domain.""" @@ -31,6 +44,7 @@ async def translations( if localize_key in _translations: return _translations[localize_key] + async def translated_notification( hass: HomeAssistant, translation_domain: str, @@ -39,13 +53,15 @@ async def translated_notification( notification_id: str | None = None, *, key: str = "message", - category: str = "notify" + category: str = "notify", ) -> str: """Translate notification.""" localize_key = f"component.{translation_domain}.{category}.{translation_key}.{key}" - localize_title = f"component.{translation_domain}.{category}.{translation_key}.title" + localize_title = ( + f"component.{translation_domain}.{category}.{translation_key}.title" + ) language = hass.config.language @@ -97,10 +113,16 @@ def remap_items(entities): return items + def loaded_sensors(config_entry: ConfigEntry) -> list | None: """Get loaded sensors.""" - return config_entry.options.get(SENSORS_TO_LOAD) if config_entry.options.get(SENSORS_TO_LOAD) else [] + return ( + config_entry.options.get(SENSORS_TO_LOAD) + if config_entry.options.get(SENSORS_TO_LOAD) + else [] + ) + def check_disabled( hass: HomeAssistant, items, config_entry: ConfigEntry @@ -129,6 +151,7 @@ def check_disabled( return missing_sensors if entityFound else None + def wind_dir_to_text(deg: float) -> UnitOfDir | None: """Return wind direction in text representation. @@ -141,3 +164,32 @@ def wind_dir_to_text(deg: float) -> UnitOfDir | None: return None +def heat_index(data: Any) -> UnitOfTemperature: + """Calculate heat index from temperature.""" + + temp = float(data[OUTSIDE_TEMP]) + rh = float(data[OUTSIDE_HUMIDITY]) + adjustment = None + + simple = 0.5 * (temp + 61.0 + ((temp - 68.0) * 1.2) + (rh * 0.094)) + if ((simple + temp) / 2) > 80: + full_index = ( + -42.379 + + 2.04901523 * temp + + 10.14333127 * rh + - 0.22475541 * temp * rh + - 0.00683783 * temp * temp + - 0.05481717 * rh * rh + + 0.00122874 * temp * temp * rh + + 0.00085282 * temp * rh * rh + - 0.00000199 * temp * temp * rh * rh + ) + if rh < 13 and (temp in np.arange(80, 112, 0.1)): + adjustment = ((13 - rh) / 4) * math.sqrt((17 - abs(temp - 95)) / 17) + + if rh > 80 and (temp in np.arange(80, 87, 0.1)): + adjustment = ((rh - 85) / 10) * ((87 - temp) / 5) + + return full_index + adjustment if adjustment else full_index + + return simple