From 55488681b28eaf888f572e80884ac5a9f6da0163 Mon Sep 17 00:00:00 2001 From: sirmrak Date: Wed, 8 Jul 2026 14:26:08 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B5=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0?= =?UTF-8?q?=D0=B5=D0=BC=20=D1=81=D1=83=D1=89=D0=BD=D0=BE=D1=81=D1=82=D0=B8?= =?UTF-8?q?=20lux=20=D0=BF=D1=80=D0=B8=20=D0=BE=D1=82=D1=81=D1=83=D1=82?= =?UTF-8?q?=D1=81=D1=82=D0=B2=D0=B8=D0=B8=20=D0=B4=D0=B0=D1=82=D1=87=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BE=D1=81=D0=B2=D0=B5=D1=89=D0=B5=D0=BD=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- custom_components/motion_light/number.py | 335 ++++++++++++----------- 1 file changed, 171 insertions(+), 164 deletions(-) diff --git a/custom_components/motion_light/number.py b/custom_components/motion_light/number.py index 531dfc5..a06f932 100644 --- a/custom_components/motion_light/number.py +++ b/custom_components/motion_light/number.py @@ -1,165 +1,172 @@ -"""Number platform for Motion Light settings.""" -import logging -from homeassistant.components.number import NumberEntity -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import UnitOfTime -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity -from homeassistant.helpers.restore_state import RestoreEntity -from homeassistant.helpers.device_registry import DeviceInfo -from .const import ( - DOMAIN, - ENTITY_MOTION_FILTER, - ENTITY_OFF_DELAY, - ENTITY_LUX_THRESHOLD, - ENTITY_LUX_COOLDOWN, - ENTITY_MANUAL_IDLE_TIMEOUT, - ENTITY_MANUAL_OFF_COOLDOWN, - DEFAULT_MOTION_FILTER, - DEFAULT_OFF_DELAY, - DEFAULT_LUX_THRESHOLD, - DEFAULT_LUX_COOLDOWN, - DEFAULT_MANUAL_IDLE_TIMEOUT, - DEFAULT_MANUAL_OFF_COOLDOWN, -) - -_LOGGER = logging.getLogger(__name__) - - -class PrefixLoggerAdapter(logging.LoggerAdapter): - """Logger adapter that adds a prefix to log messages.""" - - def process(self, msg, kwargs): - """Add prefix to the log message.""" - return f"[{self.extra.get('name', 'Unknown')}] {msg}", kwargs - - -TRANSLATION_KEYS = { - ENTITY_MOTION_FILTER: "motion_light_motion_filter", - ENTITY_OFF_DELAY: "motion_light_off_delay", - ENTITY_LUX_THRESHOLD: "motion_light_lux_threshold", - ENTITY_LUX_COOLDOWN: "motion_light_lux_cooldown", - ENTITY_MANUAL_IDLE_TIMEOUT: "motion_light_manual_idle_timeout", - ENTITY_MANUAL_OFF_COOLDOWN: "motion_light_manual_off_cooldown", -} - -ICONS = { - ENTITY_MOTION_FILTER: "mdi:timer-outline", - ENTITY_OFF_DELAY: "mdi:timer-sand", - ENTITY_LUX_THRESHOLD: "mdi:brightness-5", - ENTITY_LUX_COOLDOWN: "mdi:timer-pause", - ENTITY_MANUAL_IDLE_TIMEOUT: "mdi:timer-alert", - ENTITY_MANUAL_OFF_COOLDOWN: "mdi:timer-off", -} - -UNITS = { - ENTITY_MOTION_FILTER: UnitOfTime.SECONDS, - ENTITY_OFF_DELAY: UnitOfTime.SECONDS, - ENTITY_LUX_THRESHOLD: "lx", - ENTITY_LUX_COOLDOWN: UnitOfTime.SECONDS, - ENTITY_MANUAL_IDLE_TIMEOUT: UnitOfTime.SECONDS, - ENTITY_MANUAL_OFF_COOLDOWN: UnitOfTime.SECONDS, -} - - -async def async_setup_entry( - hass: HomeAssistant, - entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Set up Motion Light number entities.""" - coordinator = hass.data[DOMAIN][entry.entry_id] - entities = [ - MotionLightNumber(coordinator, entry, ENTITY_MOTION_FILTER, 0, 10, 1, DEFAULT_MOTION_FILTER), - MotionLightNumber(coordinator, entry, ENTITY_OFF_DELAY, 0, 600, 5, DEFAULT_OFF_DELAY), - MotionLightNumber(coordinator, entry, ENTITY_LUX_THRESHOLD, 1, 1000, 10, DEFAULT_LUX_THRESHOLD), - MotionLightNumber(coordinator, entry, ENTITY_LUX_COOLDOWN, 0, 300, 5, DEFAULT_LUX_COOLDOWN), - MotionLightNumber(coordinator, entry, ENTITY_MANUAL_IDLE_TIMEOUT, 0, 3600, 60, DEFAULT_MANUAL_IDLE_TIMEOUT), - MotionLightNumber(coordinator, entry, ENTITY_MANUAL_OFF_COOLDOWN, 0, 300, 5, DEFAULT_MANUAL_OFF_COOLDOWN), - ] - async_add_entities(entities) - - -class MotionLightNumber(CoordinatorEntity, NumberEntity, RestoreEntity): - """Representation of a Motion Light setting as a number entity.""" - _attr_has_entity_name = True - - def __init__(self, coordinator, entry, entity_id, min_val, max_val, step, default): - """Initialize the number entity.""" - super().__init__(coordinator) - self.entry = entry - - self.logger = PrefixLoggerAdapter(_LOGGER, {"name": entry.title}) - - self._entity_suffix = entity_id - self._attr_unique_id = f"{entry.entry_id}_{entity_id}" - self._attr_translation_key = TRANSLATION_KEYS[entity_id] - self._attr_native_min_value = min_val - self._attr_native_max_value = max_val - self._attr_native_step = step - self._attr_native_unit_of_measurement = UNITS[entity_id] - self._attr_icon = ICONS[entity_id] - self._default_value = default - self._attr_native_value = default - self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, entry.entry_id)}, - name=entry.title, - manufacturer="Motion Light", - model="Switch Control Integration", - ) - - @property - def native_value(self) -> float: - """Return the current value.""" - return self._attr_native_value - - async def async_set_native_value(self, value: float) -> None: - """Update the current value.""" - self._attr_native_value = value - self.logger.info("Setting %s to %s", self._entity_suffix, value) - - if self._entity_suffix == ENTITY_MOTION_FILTER: - self.coordinator.set_motion_filter(value) - elif self._entity_suffix == ENTITY_OFF_DELAY: - self.coordinator.set_off_delay(value) - elif self._entity_suffix == ENTITY_LUX_THRESHOLD: - self.coordinator.set_lux_threshold(value) - elif self._entity_suffix == ENTITY_LUX_COOLDOWN: - self.coordinator.set_lux_cooldown(value) - elif self._entity_suffix == ENTITY_MANUAL_IDLE_TIMEOUT: - self.coordinator.set_manual_idle_timeout(value) - elif self._entity_suffix == ENTITY_MANUAL_OFF_COOLDOWN: - self.coordinator.set_manual_off_cooldown(value) - - self.async_write_ha_state() - - async def async_added_to_hass(self) -> None: - """Handle entity which will be added.""" - await super().async_added_to_hass() - last_state = await self.async_get_last_state() - if last_state is not None: - try: - value = float(last_state.state) - self._attr_native_value = value - self.logger.info("Restored %s: %s", self._entity_suffix, value) - - if self._entity_suffix == ENTITY_MOTION_FILTER: - self.coordinator.set_motion_filter(value) - elif self._entity_suffix == ENTITY_OFF_DELAY: - self.coordinator.set_off_delay(value) - elif self._entity_suffix == ENTITY_LUX_THRESHOLD: - self.coordinator.set_lux_threshold(value) - elif self._entity_suffix == ENTITY_LUX_COOLDOWN: - self.coordinator.set_lux_cooldown(value) - elif self._entity_suffix == ENTITY_MANUAL_IDLE_TIMEOUT: - self.coordinator.set_manual_idle_timeout(value) - elif self._entity_suffix == ENTITY_MANUAL_OFF_COOLDOWN: - self.coordinator.set_manual_off_cooldown(value) - except (ValueError, TypeError): - self.logger.warning("Could not restore %s, using default", self._entity_suffix) - self.async_write_ha_state() - - def _handle_coordinator_update(self) -> None: - """Handle updated data from the coordinator.""" +"""Number platform for Motion Light settings.""" +import logging +from homeassistant.components.number import NumberEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import UnitOfTime +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity +from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.helpers.device_registry import DeviceInfo +from .const import ( + DOMAIN, + ENTITY_MOTION_FILTER, + ENTITY_OFF_DELAY, + ENTITY_LUX_THRESHOLD, + ENTITY_LUX_COOLDOWN, + ENTITY_MANUAL_IDLE_TIMEOUT, + ENTITY_MANUAL_OFF_COOLDOWN, + DEFAULT_MOTION_FILTER, + DEFAULT_OFF_DELAY, + DEFAULT_LUX_THRESHOLD, + DEFAULT_LUX_COOLDOWN, + DEFAULT_MANUAL_IDLE_TIMEOUT, + DEFAULT_MANUAL_OFF_COOLDOWN, +) + +_LOGGER = logging.getLogger(__name__) + + +class PrefixLoggerAdapter(logging.LoggerAdapter): + """Logger adapter that adds a prefix to log messages.""" + + def process(self, msg, kwargs): + """Add prefix to the log message.""" + return f"[{self.extra.get('name', 'Unknown')}] {msg}", kwargs + + +TRANSLATION_KEYS = { + ENTITY_MOTION_FILTER: "motion_light_motion_filter", + ENTITY_OFF_DELAY: "motion_light_off_delay", + ENTITY_LUX_THRESHOLD: "motion_light_lux_threshold", + ENTITY_LUX_COOLDOWN: "motion_light_lux_cooldown", + ENTITY_MANUAL_IDLE_TIMEOUT: "motion_light_manual_idle_timeout", + ENTITY_MANUAL_OFF_COOLDOWN: "motion_light_manual_off_cooldown", +} + +ICONS = { + ENTITY_MOTION_FILTER: "mdi:timer-outline", + ENTITY_OFF_DELAY: "mdi:timer-sand", + ENTITY_LUX_THRESHOLD: "mdi:brightness-5", + ENTITY_LUX_COOLDOWN: "mdi:timer-pause", + ENTITY_MANUAL_IDLE_TIMEOUT: "mdi:timer-alert", + ENTITY_MANUAL_OFF_COOLDOWN: "mdi:timer-off", +} + +UNITS = { + ENTITY_MOTION_FILTER: UnitOfTime.SECONDS, + ENTITY_OFF_DELAY: UnitOfTime.SECONDS, + ENTITY_LUX_THRESHOLD: "lx", + ENTITY_LUX_COOLDOWN: UnitOfTime.SECONDS, + ENTITY_MANUAL_IDLE_TIMEOUT: UnitOfTime.SECONDS, + ENTITY_MANUAL_OFF_COOLDOWN: UnitOfTime.SECONDS, +} + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Motion Light number entities.""" + coordinator = hass.data[DOMAIN][entry.entry_id] + + lux_sensor = entry.options.get(CONF_LUX_SENSOR) or entry.data.get(CONF_LUX_SENSOR) + + entities = [ + MotionLightNumber(coordinator, entry, ENTITY_MOTION_FILTER, 0, 10, 1, DEFAULT_MOTION_FILTER), + MotionLightNumber(coordinator, entry, ENTITY_OFF_DELAY, 0, 600, 5, DEFAULT_OFF_DELAY), + MotionLightNumber(coordinator, entry, ENTITY_MANUAL_IDLE_TIMEOUT, 0, 3600, 30, DEFAULT_MANUAL_IDLE_TIMEOUT), + MotionLightNumber(coordinator, entry, ENTITY_MANUAL_OFF_COOLDOWN, 0, 300, 5, DEFAULT_MANUAL_OFF_COOLDOWN), + ] + + if lux_sensor: + entities.extend([ + MotionLightNumber(coordinator, entry, ENTITY_LUX_THRESHOLD, 1, 1000, 10, DEFAULT_LUX_THRESHOLD), + MotionLightNumber(coordinator, entry, ENTITY_LUX_COOLDOWN, 0, 300, 5, DEFAULT_LUX_COOLDOWN), + ]) + + async_add_entities(entities) + +class MotionLightNumber(CoordinatorEntity, NumberEntity, RestoreEntity): + """Representation of a Motion Light setting as a number entity.""" + _attr_has_entity_name = True + + def __init__(self, coordinator, entry, entity_id, min_val, max_val, step, default): + """Initialize the number entity.""" + super().__init__(coordinator) + self.entry = entry + + self.logger = PrefixLoggerAdapter(_LOGGER, {"name": entry.title}) + + self._entity_suffix = entity_id + self._attr_unique_id = f"{entry.entry_id}_{entity_id}" + self._attr_translation_key = TRANSLATION_KEYS[entity_id] + self._attr_native_min_value = min_val + self._attr_native_max_value = max_val + self._attr_native_step = step + self._attr_native_unit_of_measurement = UNITS[entity_id] + self._attr_icon = ICONS[entity_id] + self._default_value = default + self._attr_native_value = default + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, entry.entry_id)}, + name=entry.title, + manufacturer="Motion Light", + model="Switch Control Integration", + ) + + @property + def native_value(self) -> float: + """Return the current value.""" + return self._attr_native_value + + async def async_set_native_value(self, value: float) -> None: + """Update the current value.""" + self._attr_native_value = value + self.logger.info("Setting %s to %s", self._entity_suffix, value) + + if self._entity_suffix == ENTITY_MOTION_FILTER: + self.coordinator.set_motion_filter(value) + elif self._entity_suffix == ENTITY_OFF_DELAY: + self.coordinator.set_off_delay(value) + elif self._entity_suffix == ENTITY_LUX_THRESHOLD: + self.coordinator.set_lux_threshold(value) + elif self._entity_suffix == ENTITY_LUX_COOLDOWN: + self.coordinator.set_lux_cooldown(value) + elif self._entity_suffix == ENTITY_MANUAL_IDLE_TIMEOUT: + self.coordinator.set_manual_idle_timeout(value) + elif self._entity_suffix == ENTITY_MANUAL_OFF_COOLDOWN: + self.coordinator.set_manual_off_cooldown(value) + + self.async_write_ha_state() + + async def async_added_to_hass(self) -> None: + """Handle entity which will be added.""" + await super().async_added_to_hass() + last_state = await self.async_get_last_state() + if last_state is not None: + try: + value = float(last_state.state) + self._attr_native_value = value + self.logger.info("Restored %s: %s", self._entity_suffix, value) + + if self._entity_suffix == ENTITY_MOTION_FILTER: + self.coordinator.set_motion_filter(value) + elif self._entity_suffix == ENTITY_OFF_DELAY: + self.coordinator.set_off_delay(value) + elif self._entity_suffix == ENTITY_LUX_THRESHOLD: + self.coordinator.set_lux_threshold(value) + elif self._entity_suffix == ENTITY_LUX_COOLDOWN: + self.coordinator.set_lux_cooldown(value) + elif self._entity_suffix == ENTITY_MANUAL_IDLE_TIMEOUT: + self.coordinator.set_manual_idle_timeout(value) + elif self._entity_suffix == ENTITY_MANUAL_OFF_COOLDOWN: + self.coordinator.set_manual_off_cooldown(value) + except (ValueError, TypeError): + self.logger.warning("Could not restore %s, using default", self._entity_suffix) + self.async_write_ha_state() + + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" self.async_write_ha_state() \ No newline at end of file