Skip to content

Commit

Permalink
Add irradiance mode option
Browse files Browse the repository at this point in the history
Fix scan interval broken by 2024.6.
  • Loading branch information
pnbruckner committed Aug 19, 2024
1 parent 68559de commit 8a3071e
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 21 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 2 additions & 0 deletions custom_components/illuminance/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
49 changes: 34 additions & 15 deletions custom_components/illuminance/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -77,6 +79,7 @@
DEFAULT_NAME,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
LUX_PER_WPSM,
MIN_SCAN_INTERVAL,
)

Expand Down Expand Up @@ -129,6 +132,7 @@ class Mode(Enum):

normal = auto()
simple = auto()
irradiance = auto()


MODES = list(Mode.__members__)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion custom_components/illuminance/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"mode": {
"options": {
"normal": "Normal",
"simple": "Simple"
"simple": "Simple",
"irradiance": "Irradiance"
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion custom_components/illuminance/translations/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"mode": {
"options": {
"normal": "Normale",
"simple": "Semplice"
"simple": "Semplice",
"irradiance": "Irradianza"
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion custom_components/illuminance/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"mode": {
"options": {
"normal": "Normaal",
"simple": "Eenvoudig"
"simple": "Eenvoudig",
"irradiance": "Bestraling"
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion custom_components/illuminance/translations/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"mode": {
"options": {
"normal": "Normalny",
"simple": "Prosty"
"simple": "Prosty",
"irradiance": "Natężenie promieniowania"
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion custom_components/illuminance/translations/sv.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"mode": {
"options": {
"normal": "Normal",
"simple": "Simpel"
"simple": "Simpel",
"irradiance": "Bestrålning"
}
}
},
Expand Down

0 comments on commit 8a3071e

Please sign in to comment.