From 8a3071e1a4ff141532bf85e07b649c0a7dc6a9e9 Mon Sep 17 00:00:00 2001 From: Phil Bruckner Date: Mon, 19 Aug 2024 10:56:51 -0500 Subject: [PATCH] Add irradiance mode option Fix scan interval broken by 2024.6. --- README.md | 2 +- custom_components/illuminance/const.py | 2 + custom_components/illuminance/sensor.py | 49 +++++++++++++------ .../illuminance/translations/en.json | 3 +- .../illuminance/translations/it.json | 3 +- .../illuminance/translations/nl.json | 3 +- .../illuminance/translations/pl.json | 3 +- .../illuminance/translations/sv.json | 3 +- 8 files changed, 47 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 84ddfd9..16ddddd 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ Key | Optional | Description `unique_id` | no | Unique identifier for sensor. This allows any of the remaining options to be changed without looking like a new sensor. (Only required for YAML-based configuration.) `entity_id` | yes | Entity ID of another entity that indicates current weather conditions or cloud coverage percentage `fallback` | yes | Illuminance divisor to use when weather data is not available. Must be in the range of 1 (clear) through 10 (dark.) Default is 10 if `entity_id` is used, or 1 if not. -`mode` | yes | Mode of operation. Choices are `normal` (default) which uses sun elevation, and `simple` which uses time of day. +`mode` | yes | Mode of operation. Choices are `normal` (default) which uses sun elevation, `simple` which uses time of day and `irradiance` which is the same as `normal`, except the value is expressed as irradiance in Watts/M². `name` | yes | Name of the sensor. Default is `Illuminance`. `scan_interval` | yes | Update interval. Minimum is 30 seconds. Default is 5 minutes. diff --git a/custom_components/illuminance/const.py b/custom_components/illuminance/const.py index 7f9834b..08a8927 100644 --- a/custom_components/illuminance/const.py +++ b/custom_components/illuminance/const.py @@ -8,5 +8,7 @@ DEFAULT_SCAN_INTERVAL_MIN = 5 DEFAULT_SCAN_INTERVAL = timedelta(minutes=DEFAULT_SCAN_INTERVAL_MIN) DEFAULT_FALLBACK = 10 +# Lux per Watts/M² +LUX_PER_WPSM = 120 CONF_FALLBACK = "fallback" diff --git a/custom_components/illuminance/sensor.py b/custom_components/illuminance/sensor.py index 1c0b5c6..63fd492 100644 --- a/custom_components/illuminance/sensor.py +++ b/custom_components/illuminance/sensor.py @@ -9,6 +9,7 @@ from dataclasses import dataclass from datetime import date, datetime, timedelta from enum import Enum, IntEnum, auto +from functools import cached_property import logging from math import asin, cos, exp, radians, sin import re @@ -54,6 +55,7 @@ LIGHT_LUX, STATE_UNAVAILABLE, STATE_UNKNOWN, + UnitOfIrradiance, ) from homeassistant.core import Event, HomeAssistant, State, callback import homeassistant.helpers.config_validation as cv @@ -77,6 +79,7 @@ DEFAULT_NAME, DEFAULT_SCAN_INTERVAL, DOMAIN, + LUX_PER_WPSM, MIN_SCAN_INTERVAL, ) @@ -129,6 +132,7 @@ class Mode(Enum): normal = auto() simple = auto() + irradiance = auto() MODES = list(Mode.__members__) @@ -172,14 +176,23 @@ def _sensor( float, config.get(CONF_FALLBACK, DEFAULT_FALLBACK if weather_entity else 1) ) + if (mode := Mode.__getitem__(cast(str, config[CONF_MODE]))) is Mode.irradiance: + device_class = SensorDeviceClass.IRRADIANCE + native_unit_of_measurement = UnitOfIrradiance.WATTS_PER_SQUARE_METER + suggested_display_precision = 1 + else: + device_class = SensorDeviceClass.ILLUMINANCE + native_unit_of_measurement = LIGHT_LUX + suggested_display_precision = 0 entity_description = IlluminanceSensorEntityDescription( key=DOMAIN, - device_class=SensorDeviceClass.ILLUMINANCE, + device_class=device_class, name=cast(str, config[CONF_NAME]), - native_unit_of_measurement=LIGHT_LUX, + native_unit_of_measurement=native_unit_of_measurement, state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=suggested_display_precision, weather_entity=weather_entity, - mode=Mode.__getitem__(cast(str, config[CONF_MODE])), + mode=mode, fallback=fallback, unique_id=unique_id, scan_interval=scan_interval, @@ -270,17 +283,17 @@ def __init__(self, entity_description: IlluminanceSensorEntityDescription) -> No else: self._attr_unique_id = cast(str, entity_description.name) - @property + @cached_property def weather_entity(self) -> str | None: """Input weather entity ID.""" return self.entity_description.weather_entity - @property + @cached_property def mode(self) -> Mode: """Illuminance calculation mode.""" return cast(Mode, self.entity_description.mode) - @property + @cached_property def fallback(self) -> float: """Fallback illuminance divisor.""" return cast(float, self.entity_description.fallback) @@ -295,8 +308,10 @@ def add_to_platform_start( """Start adding an entity to a platform.""" # This method is called before first call to async_update. - if self.entity_description.scan_interval: - platform.scan_interval = self.entity_description.scan_interval + if (scan_interval := self.entity_description.scan_interval) is not None: + platform.scan_interval = scan_interval + if hasattr(platform, "scan_interval_seconds"): + platform.scan_interval_seconds = scan_interval.total_seconds() super().add_to_platform_start(hass, platform, parallel_updates) # Now that parent method has been called, self.hass has been initialized. @@ -338,22 +353,26 @@ async def async_update(self) -> None: return try: - illuminance = self._calculate_illuminance( + value = self._calculate_illuminance( dt_util.now().replace(microsecond=0) ) except AbortUpdate: return - # Calculate final illuminance. + if self.mode is Mode.irradiance: + value /= LUX_PER_WPSM - self._attr_native_value = round(illuminance / self._sk) + # Calculate final value. + + self._attr_native_value = value / self._sk + display_precision = self._sensor_option_display_precision or 0 _LOGGER.debug( - "%s: Updating %s -> %i / %0.1f = %i", + "%s: Updating %s -> %s / %0.2f = %s", self.name, self._cond_desc, - round(illuminance), + f"{value:0.{display_precision}f}", self._sk, - self._attr_native_value, + f"{self._attr_native_value:0.{display_precision}f}", ) def _get_divisor_from_weather_data(self, entity_state: State | None) -> None: @@ -455,7 +474,7 @@ def _get_mappings(self, attribution: str | None, domain: str) -> None: def _calculate_illuminance(self, now: datetime) -> Num: """Calculate sunny illuminance.""" - if self.mode is Mode.normal: + if self.mode is not Mode.simple: return _illumiance(cast(Num, self._astral_event("solar_elevation", now))) sun_factor = self._sun_factor(now) diff --git a/custom_components/illuminance/translations/en.json b/custom_components/illuminance/translations/en.json index 222629f..27e6802 100644 --- a/custom_components/illuminance/translations/en.json +++ b/custom_components/illuminance/translations/en.json @@ -38,7 +38,8 @@ "mode": { "options": { "normal": "Normal", - "simple": "Simple" + "simple": "Simple", + "irradiance": "Irradiance" } } }, diff --git a/custom_components/illuminance/translations/it.json b/custom_components/illuminance/translations/it.json index 1ab7445..15fd6a3 100644 --- a/custom_components/illuminance/translations/it.json +++ b/custom_components/illuminance/translations/it.json @@ -36,7 +36,8 @@ "mode": { "options": { "normal": "Normale", - "simple": "Semplice" + "simple": "Semplice", + "irradiance": "Irradianza" } } }, diff --git a/custom_components/illuminance/translations/nl.json b/custom_components/illuminance/translations/nl.json index 8268bf5..315ba7a 100644 --- a/custom_components/illuminance/translations/nl.json +++ b/custom_components/illuminance/translations/nl.json @@ -36,7 +36,8 @@ "mode": { "options": { "normal": "Normaal", - "simple": "Eenvoudig" + "simple": "Eenvoudig", + "irradiance": "Bestraling" } } }, diff --git a/custom_components/illuminance/translations/pl.json b/custom_components/illuminance/translations/pl.json index 1b9fc0a..13af831 100644 --- a/custom_components/illuminance/translations/pl.json +++ b/custom_components/illuminance/translations/pl.json @@ -36,7 +36,8 @@ "mode": { "options": { "normal": "Normalny", - "simple": "Prosty" + "simple": "Prosty", + "irradiance": "Natężenie promieniowania" } } }, diff --git a/custom_components/illuminance/translations/sv.json b/custom_components/illuminance/translations/sv.json index e316e4d..264d354 100644 --- a/custom_components/illuminance/translations/sv.json +++ b/custom_components/illuminance/translations/sv.json @@ -36,7 +36,8 @@ "mode": { "options": { "normal": "Normal", - "simple": "Simpel" + "simple": "Simpel", + "irradiance": "Bestrålning" } } },