1 """Adds support for generic hygrostat units."""
3 from __future__
import annotations
6 from collections.abc
import Callable, Mapping
7 from datetime
import datetime, timedelta
9 from typing
import TYPE_CHECKING, Any, cast
15 PLATFORM_SCHEMA
as HUMIDIFIER_PLATFORM_SCHEMA,
17 HumidifierDeviceClass,
19 HumidifierEntityFeature,
27 EVENT_HOMEASSISTANT_START,
36 DOMAIN
as HOMEASSISTANT_DOMAIN,
38 EventStateChangedData,
39 EventStateReportedData,
48 async_track_state_change_event,
49 async_track_state_report_event,
50 async_track_time_interval,
73 _LOGGER = logging.getLogger(__name__)
75 ATTR_SAVED_HUMIDITY =
"saved_humidity"
77 PLATFORM_SCHEMA = HUMIDIFIER_PLATFORM_SCHEMA.extend(HYGROSTAT_SCHEMA.schema)
83 async_add_entities: AddEntitiesCallback,
84 discovery_info: DiscoveryInfoType |
None =
None,
86 """Set up the generic hygrostat platform."""
88 config = discovery_info
90 hass, config, config.get(CONF_UNIQUE_ID), async_add_entities
96 config_entry: ConfigEntry,
97 async_add_entities: AddEntitiesCallback,
99 """Initialize config entry."""
103 config_entry.options,
104 config_entry.entry_id,
112 return cast(timedelta, cv.time_period(value))
117 config: Mapping[str, Any],
118 unique_id: str |
None,
119 async_add_entities: AddEntitiesCallback,
121 name: str = config[CONF_NAME]
122 switch_entity_id: str = config[CONF_HUMIDIFIER]
123 sensor_entity_id: str = config[CONF_SENSOR]
124 min_humidity: float |
None = config.get(CONF_MIN_HUMIDITY)
125 max_humidity: float |
None = config.get(CONF_MAX_HUMIDITY)
126 target_humidity: float |
None = config.get(CONF_TARGET_HUMIDITY)
127 device_class: HumidifierDeviceClass |
None = config.get(CONF_DEVICE_CLASS)
129 config.get(CONF_MIN_DUR)
132 config.get(CONF_STALE_DURATION)
134 dry_tolerance: float = config[CONF_DRY_TOLERANCE]
135 wet_tolerance: float = config[CONF_WET_TOLERANCE]
137 initial_state: bool |
None = config.get(CONF_INITIAL_STATE)
138 away_humidity: int |
None = config.get(CONF_AWAY_HUMIDITY)
139 away_fixed: bool |
None = config.get(CONF_AWAY_FIXED)
159 sensor_stale_duration,
167 """Representation of a Generic Hygrostat device."""
169 _attr_should_poll =
False
175 switch_entity_id: str,
176 sensor_entity_id: str,
177 min_humidity: float |
None,
178 max_humidity: float |
None,
179 target_humidity: float |
None,
180 device_class: HumidifierDeviceClass |
None,
181 min_cycle_duration: timedelta |
None,
182 dry_tolerance: float,
183 wet_tolerance: float,
184 keep_alive: timedelta |
None,
185 initial_state: bool |
None,
186 away_humidity: int |
None,
187 away_fixed: bool |
None,
188 sensor_stale_duration: timedelta |
None,
189 unique_id: str |
None,
191 """Initialize the hygrostat."""
199 self.
_device_class_device_class = device_class
or HumidifierDeviceClass.HUMIDIFIER
213 self._attr_supported_features |= HumidifierEntityFeature.MODES
223 """Run when entity about to be added."""
248 async
def _async_startup(event: Event |
None) ->
None:
249 """Init on startup."""
251 if sensor_state
is None or sensor_state.state
in (
256 "The sensor state is %s, initialization is delayed",
257 sensor_state.state
if sensor_state
is not None else "None",
263 self.
hasshass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _async_startup)
266 if old_state.attributes.get(ATTR_MODE) == MODE_AWAY:
270 if old_state.attributes.get(ATTR_HUMIDITY):
272 if old_state.attributes.get(ATTR_SAVED_HUMIDITY):
274 old_state.attributes[ATTR_SAVED_HUMIDITY]
277 self.
_state_state = old_state.state == STATE_ON
279 if self.
_device_class_device_class == HumidifierDeviceClass.HUMIDIFIER:
284 "No previously saved humidity, setting to %s", self.
_target_humidity_target_humidity
286 if self.
_state_state
is None:
289 await _async_startup(
None)
292 """Run when entity will be removed from hass."""
299 """Return True if entity is available."""
304 """Return the optional state attributes."""
311 """Return the name of the hygrostat."""
312 return self.
_name_name
316 """Return true if the hygrostat is on."""
321 """Return the measured humidity."""
326 """Return the humidity we try to reach."""
331 """Return the current mode."""
340 """Return a list of available modes."""
342 return [MODE_NORMAL, MODE_AWAY]
347 """Return the device class of the humidifier."""
351 """Turn hygrostat on."""
359 """Turn hygrostat off."""
368 """Set new target humidity."""
383 """Return the minimum humidity."""
388 return super().min_humidity
392 """Return the maximum humidity."""
397 return super().max_humidity
400 self, event: Event[EventStateChangedData] | Event[EventStateReportedData]
402 """Handle ambient humidity changes."""
403 new_state = event.data[
"new_state"]
404 if new_state
is None:
410 """Update state based on humidity sensor."""
427 """Handle sensor stale event."""
431 "Sensor has not been updated for %s",
432 now - state.last_reported
if now
and state
else "---",
434 _LOGGER.warning(
"Sensor is stalled, call the emergency stop")
439 """Handle humidifier switch state changes."""
444 """Handle humidifier switch state changes."""
445 if new_state
is None:
448 if new_state.state == STATE_ON:
449 if self.
_device_class_device_class == HumidifierDeviceClass.DEHUMIDIFIER:
452 self.
_attr_action_attr_action = HumidifierAction.HUMIDIFYING
459 """Update hygrostat with latest state from sensor."""
462 except ValueError
as ex:
464 _LOGGER.warning(
"Unable to update from sensor: %s", ex)
467 _LOGGER.debug(
"Unable to update from sensor: %s", ex)
473 self, time: datetime |
None =
None, force: bool =
False
475 """Check if we need to turn humidifying on or off."""
477 if not self.
_active_active
and None not in (
485 "Obtained current and target humidity. "
486 "Generic hygrostat active. %s, %s"
495 if not force
and time
is None:
502 current_state = STATE_ON
504 current_state = STATE_OFF
505 long_enough = condition.state(
516 dry_tolerance: float = 0
517 wet_tolerance: float = 0
529 self.
_device_class_device_class == HumidifierDeviceClass.HUMIDIFIER
and too_wet
531 self.
_device_class_device_class == HumidifierDeviceClass.DEHUMIDIFIER
and too_dry
533 _LOGGER.debug(
"Turning off humidifier %s", self.
_switch_entity_id_switch_entity_id)
535 elif time
is not None:
539 self.
_device_class_device_class == HumidifierDeviceClass.HUMIDIFIER
and too_dry
540 )
or (self.
_device_class_device_class == HumidifierDeviceClass.DEHUMIDIFIER
and too_wet):
541 _LOGGER.debug(
"Turning on humidifier %s", self.
_switch_entity_id_switch_entity_id)
543 elif time
is not None:
549 """If the toggleable device is currently active."""
553 """Turn humidifier toggleable device on."""
555 await self.
hasshass.services.async_call(HOMEASSISTANT_DOMAIN, SERVICE_TURN_ON, data)
558 """Turn humidifier toggleable device off."""
560 await self.
hasshass.services.async_call(
561 HOMEASSISTANT_DOMAIN, SERVICE_TURN_OFF, data
567 This method must be run in the event loop and returns a coroutine.
571 if mode == MODE_AWAY
and not self.
_is_away_is_away:
580 elif mode == MODE_NORMAL
and self.
_is_away_is_away:
float|None current_humidity(self)
None async_will_remove_from_hass(self)
None _async_switch_event(self, Event[EventStateChangedData] event)
None async_added_to_hass(self)
None async_turn_on(self, **Any kwargs)
None _async_device_turn_on(self)
None _async_device_turn_off(self)
None __init__(self, HomeAssistant hass, str name, str switch_entity_id, str sensor_entity_id, float|None min_humidity, float|None max_humidity, float|None target_humidity, HumidifierDeviceClass|None device_class, timedelta|None min_cycle_duration, float dry_tolerance, float wet_tolerance, timedelta|None keep_alive, bool|None initial_state, int|None away_humidity, bool|None away_fixed, timedelta|None sensor_stale_duration, str|None unique_id)
bool _is_device_active(self)
None async_turn_off(self, **Any kwargs)
float|None target_humidity(self)
None _async_sensor_not_responding(self, datetime|None now=None)
None _async_operate(self, datetime|None time=None, bool force=False)
None async_set_mode(self, str mode)
None _async_update_humidity(self, str humidity)
None _async_sensor_event(self, Event[EventStateChangedData]|Event[EventStateReportedData] event)
None _async_sensor_update(self, State new_state)
list[str]|None available_modes(self)
None async_set_humidity(self, int humidity)
None _async_switch_changed(self, State|None new_state)
dict[str, Any]|None extra_state_attributes(self)
HumidifierDeviceClass device_class(self)
None async_write_ha_state(self)
None async_on_remove(self, CALLBACK_TYPE func)
State|None async_get_last_state(self)
timedelta|None _time_period_or_none(Any value)
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
None _async_setup_config(HomeAssistant hass, Mapping[str, Any] config, str|None unique_id, AddEntitiesCallback async_add_entities)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
dr.DeviceInfo|None async_device_info_to_link_from_entity(HomeAssistant hass, str entity_id_or_uuid)
CALLBACK_TYPE async_track_state_report_event(HomeAssistant hass, str|Iterable[str] entity_ids, Callable[[Event[EventStateReportedData]], Any] action, HassJobType|None job_type=None)
CALLBACK_TYPE async_track_state_change_event(HomeAssistant hass, str|Iterable[str] entity_ids, Callable[[Event[EventStateChangedData]], Any] action, HassJobType|None job_type=None)
CALLBACK_TYPE async_track_time_interval(HomeAssistant hass, Callable[[datetime], Coroutine[Any, Any, None]|None] action, timedelta interval, *str|None name=None, bool|None cancel_on_shutdown=None)