1 """Support for MQTT binary sensors."""
3 from __future__
import annotations
5 from datetime
import datetime, timedelta
9 import voluptuous
as vol
13 DEVICE_CLASSES_SCHEMA,
37 from .
import subscription
38 from .config
import MQTT_RO_SCHEMA
39 from .const
import CONF_STATE_TOPIC, PAYLOAD_NONE
40 from .entity
import MqttAvailabilityMixin, MqttEntity, async_setup_entity_entry_helper
41 from .models
import MqttValueTemplate, ReceiveMessage
42 from .schemas
import MQTT_ENTITY_COMMON_SCHEMA
44 _LOGGER = logging.getLogger(__name__)
48 DEFAULT_NAME =
"MQTT Binary sensor"
49 CONF_OFF_DELAY =
"off_delay"
50 DEFAULT_PAYLOAD_OFF =
"OFF"
51 DEFAULT_PAYLOAD_ON =
"ON"
52 DEFAULT_FORCE_UPDATE =
False
53 CONF_EXPIRE_AFTER =
"expire_after"
55 PLATFORM_SCHEMA_MODERN = MQTT_RO_SCHEMA.extend(
57 vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA,
None),
58 vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int,
59 vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
60 vol.Optional(CONF_NAME): vol.Any(cv.string,
None),
61 vol.Optional(CONF_OFF_DELAY): cv.positive_int,
62 vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
63 vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
65 ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema)
67 DISCOVERY_SCHEMA = PLATFORM_SCHEMA_MODERN.extend({}, extra=vol.REMOVE_EXTRA)
72 config_entry: ConfigEntry,
73 async_add_entities: AddEntitiesCallback,
75 """Set up MQTT binary sensor through YAML and through MQTT discovery."""
83 PLATFORM_SCHEMA_MODERN,
88 """Representation a binary sensor that is updated by MQTT."""
90 _default_name = DEFAULT_NAME
91 _delay_listener: CALLBACK_TYPE |
None =
None
92 _entity_id_format = binary_sensor.ENTITY_ID_FORMAT
94 _expire_after: int |
None
95 _expiration_trigger: CALLBACK_TYPE |
None =
None
98 """Restore state for entities with expire_after set."""
102 and last_state.state
not in [STATE_UNKNOWN, STATE_UNAVAILABLE]
107 expiration_at: datetime = last_state.last_changed +
timedelta(
110 remain_seconds = (expiration_at - dt_util.utcnow()).total_seconds()
112 if remain_seconds <= 0:
114 _LOGGER.debug(
"Skip state recovery after reload for %s", self.
entity_identity_id)
120 self.
hasshasshass, remain_seconds, self._value_is_expired
124 "State recovered after reload for %s, remaining time before"
132 """Remove exprire triggers."""
135 _LOGGER.debug(
"Clean up expire after trigger for %s", self.
entity_identity_id)
139 await MqttEntity.async_will_remove_from_hass(self)
143 """Return the config schema."""
144 return DISCOVERY_SCHEMA
147 """(Re)Setup the entity."""
159 ).async_render_with_possible_json_value
162 def _off_delay_listener(self, now: datetime) ->
None:
163 """Switch device off after a delay."""
168 def _state_message_received(self, msg: ReceiveMessage) ->
None:
169 """Handle a new received MQTT state message."""
187 if not payload.strip():
190 "Empty template output for entity: %s with state topic: %s."
191 " Payload: '%s', with value template '%s'"
194 self.
_config_config[CONF_STATE_TOPIC],
200 if payload == self.
_config_config[CONF_PAYLOAD_ON]:
202 elif payload == self.
_config_config[CONF_PAYLOAD_OFF]:
204 elif payload == PAYLOAD_NONE:
208 if self.
_config_config.
get(CONF_VALUE_TEMPLATE)
is not None:
210 f
", template output: '{payload!s}', with value template"
211 f
" '{self._config.get(CONF_VALUE_TEMPLATE)!s}'"
215 "No matching payload found for entity: %s with state topic: %s."
219 self.
_config_config[CONF_STATE_TOPIC],
229 off_delay: int |
None = self.
_config_config.
get(CONF_OFF_DELAY)
230 if self.
_attr_is_on_attr_is_on
and off_delay
is not None:
232 self.
hasshasshass, off_delay, self._off_delay_listener
237 """(Re)Subscribe to topics."""
239 CONF_STATE_TOPIC, self._state_message_received, {
"_attr_is_on",
"_expired"}
243 """(Re)Subscribe to topics."""
244 subscription.async_subscribe_topics_internal(self.
hasshasshass, self.
_sub_state_sub_state)
247 def _value_is_expired(self, *_: Any) ->
None:
248 """Triggered when value is expired."""
256 """Return true if the device is available and value has not expired."""
258 return MqttAvailabilityMixin.available.fget(self)
and (
None mqtt_async_added_to_hass(self)
None _setup_from_config(self, ConfigType config)
None _prepare_subscribe_topics(self)
VolSchemaType config_schema()
bool add_subscription(self, str state_topic_config_key, Callable[[ReceiveMessage], None] msg_callback, set[str]|None tracked_attributes, bool disable_encoding=False)
None _subscribe_topics(self)
None async_will_remove_from_hass(self)
None async_write_ha_state(self)
State|None async_get_last_state(self)
web.Response get(self, web.Request request, str config_key)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
None async_setup_entity_entry_helper(HomeAssistant hass, ConfigEntry entry, type[MqttEntity]|None entity_class, str domain, AddEntitiesCallback async_add_entities, VolSchemaType discovery_schema, VolSchemaType platform_schema_modern, dict[str, type[MqttEntity]]|None schema_class_mapping=None)
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)