1 """Support for Xiaomi Gateways."""
6 import voluptuous
as vol
7 from xiaomi_gateway
import AsyncXiaomiGatewayMulticast, XiaomiGateway
16 EVENT_HOMEASSISTANT_STOP,
28 DEFAULT_DISCOVERY_RETRY,
36 _LOGGER = logging.getLogger(__name__)
39 Platform.BINARY_SENSOR,
46 GATEWAY_PLATFORMS_NO_KEY = [Platform.BINARY_SENSOR, Platform.SENSOR]
48 ATTR_GW_MAC =
"gw_mac"
49 ATTR_RINGTONE_ID =
"ringtone_id"
50 ATTR_RINGTONE_VOL =
"ringtone_vol"
52 SERVICE_PLAY_RINGTONE =
"play_ringtone"
53 SERVICE_STOP_RINGTONE =
"stop_ringtone"
54 SERVICE_ADD_DEVICE =
"add_device"
55 SERVICE_REMOVE_DEVICE =
"remove_device"
57 SERVICE_SCHEMA_PLAY_RINGTONE = vol.Schema(
59 vol.Required(ATTR_RINGTONE_ID): vol.All(
60 vol.Coerce(int), vol.NotIn([9, 14, 15, 16, 17, 18, 19])
62 vol.Optional(ATTR_RINGTONE_VOL): vol.All(
63 vol.Coerce(int), vol.Clamp(min=0, max=100)
68 SERVICE_SCHEMA_REMOVE_DEVICE = vol.Schema(
69 {vol.Required(ATTR_DEVICE_ID): vol.All(cv.string, vol.Length(min=14, max=14))}
72 CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
75 def setup(hass: HomeAssistant, config: ConfigType) -> bool:
76 """Set up the Xiaomi component."""
78 def play_ringtone_service(call: ServiceCall) ->
None:
79 """Service to play ringtone through Gateway."""
80 ring_id = call.data.get(ATTR_RINGTONE_ID)
81 gateway: XiaomiGateway = call.data[ATTR_GW_MAC]
83 kwargs = {
"mid": ring_id}
85 if (ring_vol := call.data.get(ATTR_RINGTONE_VOL))
is not None:
86 kwargs[
"vol"] = ring_vol
88 gateway.write_to_hub(gateway.sid, **kwargs)
90 def stop_ringtone_service(call: ServiceCall) ->
None:
91 """Service to stop playing ringtone on Gateway."""
92 gateway: XiaomiGateway = call.data[ATTR_GW_MAC]
93 gateway.write_to_hub(gateway.sid, mid=10000)
95 def add_device_service(call: ServiceCall) ->
None:
96 """Service to add a new sub-device within the next 30 seconds."""
97 gateway: XiaomiGateway = call.data[ATTR_GW_MAC]
98 gateway.write_to_hub(gateway.sid, join_permission=
"yes")
99 persistent_notification.async_create(
102 "Join permission enabled for 30 seconds! "
103 "Please press the pairing button of the new device once."
105 title=
"Xiaomi Aqara Gateway",
108 def remove_device_service(call: ServiceCall) ->
None:
109 """Service to remove a sub-device from the gateway."""
110 device_id = call.data.get(ATTR_DEVICE_ID)
111 gateway: XiaomiGateway = call.data[ATTR_GW_MAC]
112 gateway.write_to_hub(gateway.sid, remove_device=device_id)
116 hass.services.register(
118 SERVICE_PLAY_RINGTONE,
119 play_ringtone_service,
123 hass.services.register(
124 DOMAIN, SERVICE_STOP_RINGTONE, stop_ringtone_service, schema=gateway_only_schema
127 hass.services.register(
128 DOMAIN, SERVICE_ADD_DEVICE, add_device_service, schema=gateway_only_schema
131 hass.services.register(
133 SERVICE_REMOVE_DEVICE,
134 remove_device_service,
142 """Set up the xiaomi aqara components from a config entry."""
143 hass.data.setdefault(DOMAIN, {})
144 setup_lock = hass.data[DOMAIN].setdefault(KEY_SETUP_LOCK, asyncio.Lock())
145 hass.data[DOMAIN].setdefault(GATEWAYS_KEY, {})
148 xiaomi_gateway = await hass.async_add_executor_job(
150 entry.data[CONF_HOST],
151 entry.data[CONF_SID],
152 entry.data[CONF_KEY],
153 DEFAULT_DISCOVERY_RETRY,
154 entry.data[CONF_INTERFACE],
155 entry.data[CONF_PORT],
156 entry.data[CONF_PROTOCOL],
158 hass.data[DOMAIN][GATEWAYS_KEY][entry.entry_id] = xiaomi_gateway
160 async
with setup_lock:
161 if LISTENER_KEY
not in hass.data[DOMAIN]:
162 multicast = AsyncXiaomiGatewayMulticast(
163 interface=entry.data[CONF_INTERFACE]
165 hass.data[DOMAIN][LISTENER_KEY] = multicast
168 await multicast.start_listen()
172 def stop_xiaomi(event):
173 """Stop Xiaomi Socket."""
174 _LOGGER.debug(
"Shutting down Xiaomi Gateway Listener")
175 multicast.stop_listen()
177 unsub = hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_xiaomi)
178 hass.data[DOMAIN][KEY_UNSUB_STOP] = unsub
180 multicast = hass.data[DOMAIN][LISTENER_KEY]
181 multicast.register_gateway(entry.data[CONF_HOST], xiaomi_gateway.multicast_callback)
183 "Gateway with host '%s' connected, listening for broadcasts",
184 entry.data[CONF_HOST],
187 assert entry.unique_id
188 device_registry = dr.async_get(hass)
189 device_registry.async_get_or_create(
190 config_entry_id=entry.entry_id,
191 identifiers={(DOMAIN, entry.unique_id)},
192 manufacturer=
"Xiaomi Aqara",
194 sw_version=entry.data[CONF_PROTOCOL],
197 if entry.data[CONF_KEY]
is not None:
198 platforms = GATEWAY_PLATFORMS
200 platforms = GATEWAY_PLATFORMS_NO_KEY
202 await hass.config_entries.async_forward_entry_setups(entry, platforms)
208 """Unload a config entry."""
209 if config_entry.data[CONF_KEY]
is not None:
210 platforms = GATEWAY_PLATFORMS
212 platforms = GATEWAY_PLATFORMS_NO_KEY
214 unload_ok = await hass.config_entries.async_unload_platforms(
215 config_entry, platforms
218 hass.data[DOMAIN][GATEWAYS_KEY].pop(config_entry.entry_id)
222 for entry
in hass.config_entries.async_entries(DOMAIN)
223 if entry.state == ConfigEntryState.LOADED
225 if len(loaded_entries) == 1:
227 unsub_stop = hass.data[DOMAIN].pop(KEY_UNSUB_STOP)
229 hass.data[DOMAIN].pop(GATEWAYS_KEY)
230 _LOGGER.debug(
"Shutting down Xiaomi Gateway Listener")
231 multicast = hass.data[DOMAIN].pop(LISTENER_KEY)
232 multicast.stop_listen()
238 """Extend a voluptuous schema with a gateway validator."""
241 """Convert sid to a gateway."""
242 sid =
str(sid).replace(
":",
"").lower()
244 for gateway
in hass.data[DOMAIN][GATEWAYS_KEY].values():
245 if gateway.sid == sid:
248 raise vol.Invalid(f
"Unknown gateway sid {sid}")
251 if (xiaomi_data := hass.data.get(DOMAIN))
is not None:
252 gateways =
list(xiaomi_data[GATEWAYS_KEY].values())
255 if len(gateways) == 1:
256 kwargs[
"default"] = gateways[0].sid
258 return schema.extend({vol.Required(ATTR_GW_MAC, **kwargs): gateway})
def _add_gateway_to_schema(hass, schema)
bool async_unload_entry(HomeAssistant hass, ConfigEntry config_entry)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
bool setup(HomeAssistant hass, ConfigType config)