def __call__(self, data: Any) -> str | list[str]: """Validate the passed selection.""" include_entities = self.config.get("include_entities") exclude_entities = self.config.get("exclude_entities") def validate(e_or_u: str) -> str: e_or_u = cv.entity_id_or_uuid(e_or_u) if not valid_entity_id(e_or_u): return e_or_u if allowed_domains := cv.ensure_list(self.config.get("domain")): domain = split_entity_id(e_or_u)[0] if domain not in allowed_domains: raise vol.Invalid( f"Entity {e_or_u} belongs to domain {domain}, " f"expected {allowed_domains}") if include_entities: vol.In(include_entities)(e_or_u) if exclude_entities: vol.NotIn(exclude_entities)(e_or_u) return e_or_u
cv.temperature_unit, CONF_UNIT_SYSTEM: cv.unit_system, CONF_TIME_ZONE: cv.time_zone, vol.Optional(CONF_WHITELIST_EXTERNAL_DIRS): # pylint: disable=no-value-for-parameter vol.All(cv.ensure_list, [vol.IsDir()]), vol.Optional(CONF_PACKAGES, default={}): PACKAGES_CONFIG_SCHEMA, vol.Optional(CONF_AUTH_PROVIDERS): vol.All(cv.ensure_list, [ auth_providers.AUTH_PROVIDER_SCHEMA.extend({ CONF_TYPE: vol.NotIn(['insecure_example'], 'The insecure_example auth provider' ' is for testing only.') }) ], _no_duplicate_auth_provider), vol.Optional(CONF_AUTH_MFA_MODULES): vol.All(cv.ensure_list, [ auth_mfa_modules.MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({ CONF_TYPE: vol.NotIn(['insecure_example'], 'The insecure_example mfa module' ' is for testing only.') }) ], _no_duplicate_auth_mfa_module), }) def get_default_config_dir() -> str:
PY_XIAOMI_GATEWAY = "xiaomi_gw" TIME_TILL_UNAVAILABLE = timedelta(minutes=150) SERVICE_PLAY_RINGTONE = 'play_ringtone' SERVICE_STOP_RINGTONE = 'stop_ringtone' SERVICE_ADD_DEVICE = 'add_device' SERVICE_REMOVE_DEVICE = 'remove_device' GW_MAC = vol.All(cv.string, lambda value: value.replace(':', '').lower(), vol.Length(min=12, max=12)) SERVICE_SCHEMA_PLAY_RINGTONE = vol.Schema({ vol.Required(ATTR_RINGTONE_ID): vol.All(vol.Coerce(int), vol.NotIn([9, 14, 15, 16, 17, 18, 19])), vol.Optional(ATTR_RINGTONE_VOL): vol.All(vol.Coerce(int), vol.Clamp(min=0, max=100)) }) SERVICE_SCHEMA_REMOVE_DEVICE = vol.Schema({ vol.Required(ATTR_DEVICE_ID): vol.All(cv.string, vol.Length(min=14, max=14)) }) GATEWAY_CONFIG = vol.Schema({ vol.Optional(CONF_MAC, default=None): vol.Any(GW_MAC, None), vol.Optional(CONF_KEY): vol.All(cv.string, vol.Length(min=16, max=16)), vol.Optional(CONF_HOST):
CONF_ELEVATION: vol.Coerce(int), vol.Optional(CONF_TEMPERATURE_UNIT): cv.temperature_unit, CONF_UNIT_SYSTEM: cv.unit_system, CONF_TIME_ZONE: cv.time_zone, vol.Optional(CONF_WHITELIST_EXTERNAL_DIRS): # pylint: disable=no-value-for-parameter vol.All(cv.ensure_list, [vol.IsDir()]), vol.Optional(CONF_PACKAGES, default={}): PACKAGES_CONFIG_SCHEMA, vol.Optional(CONF_AUTH_PROVIDERS): vol.All( cv.ensure_list, [ auth_providers.AUTH_PROVIDER_SCHEMA.extend( { CONF_TYPE: vol.NotIn( ["insecure_example"], "The insecure_example auth provider" " is for testing only.", ) } ) ], _no_duplicate_auth_provider, ), vol.Optional(CONF_AUTH_MFA_MODULES): vol.All( cv.ensure_list, [ auth_mfa_modules.MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend( { CONF_TYPE: vol.NotIn( ["insecure_example"], "The insecure_example mfa module" " is for testing only.",
if self.is_off: return "OFF" return str(self.value) TEMP_SCHEMA = vol.Schema( vol.All( vol.Any(float, int, Off, vol.All(str, lambda v: v.upper(), "OFF")), lambda v: Temp(v), # pylint: disable=unnecessary-lambda )) CONFIG_SCHEMA = vol.Schema( { vol.Optional("delta", default=0): vol.All(TEMP_SCHEMA, vol.NotIn([Temp(OFF)])), vol.Optional("min_temp", default=None): vol.Any( vol.All(TEMP_SCHEMA, vol.NotIn([Temp(OFF)])), None, ), vol.Optional("max_temp", default=None): vol.Any( vol.All(TEMP_SCHEMA, vol.NotIn([Temp(OFF)])), None, ), vol.Optional("off_temp", default=OFF): TEMP_SCHEMA, vol.Optional("supports_opmodes", default=True): bool, vol.Optional("opmode_heat", default="heat"):
class ThermostatActor(ActorBase): """A thermostat to be controlled by Schedy.""" name = "thermostat" config_schema_dict = { **ActorBase.config_schema_dict, vol.Optional("delta", default=0): vol.All(TEMP_SCHEMA, vol.NotIn([Temp(OFF)])), vol.Optional("min_temp", default=None): vol.Any(vol.All(TEMP_SCHEMA, vol.NotIn([Temp(OFF)])), None), vol.Optional("max_temp", default=None): vol.Any(vol.All(TEMP_SCHEMA, vol.NotIn([Temp(OFF)])), None), vol.Optional("off_temp", default=OFF): TEMP_SCHEMA, vol.Optional("supports_hvac_modes", default=True): bool, vol.Optional("hvac_mode_on", default="heat"): str, vol.Optional("hvac_mode_off", default="off"): str, } expression_helpers = ActorBase.expression_helpers + [ ThermostatExpressionHelper ] stats_param_types = [TempDeltaParameter] def __init__(self, *args: T.Any, **kwargs: T.Any) -> None: super().__init__(*args, **kwargs) self._current_temp = None # type: T.Optional[Temp] def check_config_plausibility(self, state: dict) -> None: """Is called during initialization to warn the user about some possible common configuration mistakes.""" if not state: self.log("Thermostat couldn't be found.", level="WARNING") return required_attrs = ["temperature"] if self.cfg["supports_hvac_modes"]: required_attrs.append("state") for attr in required_attrs: if attr not in state: self.log( "Thermostat has no attribute named {!r}. Available are {!r}. " "Please check your config!".format(attr, list(state.keys())), level="WARNING", ) temp_attrs = ["temperature", "current_temperature"] for attr in temp_attrs: value = state.get(attr) try: value = float(value) # type: ignore except (TypeError, ValueError): self.log( "The value {!r} of attribute {!r} is no valid temperature." .format(value, attr), level="WARNING", ) allowed_hvac_modes = state.get("hvac_modes") if not self.cfg["supports_hvac_modes"]: if allowed_hvac_modes: self.log( "HVAC mode support has been disabled, but the modes {!r} seem to " "be supported. Maybe disabling it was a mistake?".format( allowed_hvac_modes), level="WARNING", ) return if not allowed_hvac_modes: self.log( "Attributes for thermostat contain no 'hvac_modes', Consider " "disabling HVAC mode support.", level="WARNING", ) return for hvac_mode in (self.cfg["hvac_mode_on"], self.cfg["hvac_mode_off"]): if hvac_mode not in allowed_hvac_modes: self.log( "Thermostat doesn't seem to support the " "HVAC mode {}, supported modes are: {}. " "Please check your config!".format(hvac_mode, allowed_hvac_modes), level="WARNING", ) @property def current_temp(self) -> T.Optional[Temp]: """Returns the current temperature as measured by the thermostat.""" return self._current_temp @staticmethod def deserialize_value(value: str) -> Temp: """Deserializes by calling validate_value().""" return ThermostatActor.validate_value(value) def do_send(self) -> None: """Sends self._wanted_value to the thermostat.""" target_temp = self._wanted_value if target_temp.is_off: hvac_mode = self.cfg["hvac_mode_off"] temp = None else: hvac_mode = self.cfg["hvac_mode_on"] temp = target_temp if not self.cfg["supports_hvac_modes"]: hvac_mode = None self.log( "Setting temperature = {!r}, HVAC mode = {!r}.".format( "<unset>" if temp is None else temp, "<unset>" if hvac_mode is None else hvac_mode, ), level="DEBUG", prefix=common.LOG_PREFIX_OUTGOING, ) if hvac_mode is not None: self.app.call_service("climate/set_hvac_mode", entity_id=self.entity_id, hvac_mode=hvac_mode) if temp is not None: self.app.call_service( "climate/set_temperature", entity_id=self.entity_id, temperature=temp.value, ) def filter_set_value(self, value: Temp) -> T.Optional[Temp]: """Preprocesses the given target temperature for setting on this thermostat. This algorithm will try best to achieve the closest possible temperature supported by this particular thermostat. The return value is either the temperature to set or None, if nothing has to be sent.""" if value.is_off: value = self.cfg["off_temp"] if not value.is_off: value = value + self.cfg["delta"] if isinstance(self.cfg["min_temp"], Temp) and value < self.cfg["min_temp"]: value = self.cfg["min_temp"] elif (isinstance(self.cfg["max_temp"], Temp) and value > self.cfg["max_temp"]): value = self.cfg["max_temp"] elif not self.cfg["supports_hvac_modes"]: self.log( "Not turning off because it doesn't support HVAC modes.", level="WARNING", ) self.log( "Consider defining an off_temp in the actor " "configuration for these cases.", level="WARNING", ) return None return value def notify_state_changed(self, attrs: dict) -> T.Any: """Is called when the thermostat's state changes. This method fetches both the current and target temperature from the thermostat and reacts accordingly.""" _target_temp = None # type: T.Optional[TempValueType] if self.cfg["supports_hvac_modes"]: hvac_mode = attrs.get("state") self.log( "Attribute 'state' is {}.".format(repr(hvac_mode)), level="DEBUG", prefix=common.LOG_PREFIX_INCOMING, ) if hvac_mode == self.cfg["hvac_mode_off"]: _target_temp = OFF elif hvac_mode != self.cfg["hvac_mode_on"]: self.log( "Unknown HVAC mode {!r}, ignoring thermostat.".format( hvac_mode), level="ERROR", ) return None else: hvac_mode = None if _target_temp is None: _target_temp = attrs.get("temperature") self.log( "Attribute 'temperature' is {}.".format(repr(_target_temp)), level="DEBUG", prefix=common.LOG_PREFIX_INCOMING, ) try: target_temp = Temp(_target_temp) except ValueError: self.log("Invalid target temperature, ignoring thermostat.", level="ERROR") return None _current_temp = attrs.get("current_temperature") self.log( "Attribute 'current_temperature' is {}.".format( repr(_current_temp)), level="DEBUG", prefix=common.LOG_PREFIX_INCOMING, ) if _current_temp is not None: try: current_temp = Temp(_current_temp) # type: T.Optional[Temp] except ValueError: self.log( "Invalid current temperature {!r}, not updating it.". format(_current_temp), level="ERROR", ) else: if current_temp != self._current_temp: self._current_temp = current_temp self.events.trigger("current_temp_changed", self, current_temp) return target_temp @staticmethod def serialize_value(value: Temp) -> str: """Wrapper around Temp.serialize().""" if not isinstance(value, Temp): raise ValueError("can only serialize Temp objects, not {}".format( repr(value))) return value.serialize() @staticmethod def validate_value(value: T.Any) -> Temp: """Ensures the given value is a valid temperature.""" return Temp(value)
lambda v: TEMP_EXPRESSION_MODULE_SCHEMA(v or {}), }) ########## THERMOSTATS THERMOSTAT_SCHEMA = vol.Schema( vol.All( lambda v: v or {}, { "friendly_name": str, vol.Optional("delta", default=0): vol.Any(float, int), vol.Optional("min_temp", default=None): vol.Any( vol.All(TEMP_SCHEMA, vol.NotIn([expr.Temp(expr.OFF)])), None, ), vol.Optional("max_temp", default=None): vol.Any( vol.All(TEMP_SCHEMA, vol.NotIn([expr.Temp(expr.OFF)])), None, ), vol.Optional("off_temp", default=expr.OFF): TEMP_SCHEMA, vol.Optional("set_temp_retries", default=10): vol.All(int, vol.Range(min=-1)), vol.Optional("set_temp_retry_interval", default=30): vol.All(int, vol.Range(min=1)), vol.Optional("supports_opmodes", default=True): bool,