1 """Support for Microsoft face recognition."""
3 from __future__
import annotations
6 from collections.abc
import Coroutine
12 from aiohttp.hdrs
import CONTENT_TYPE
13 import voluptuous
as vol
26 _LOGGER = logging.getLogger(__name__)
28 ATTR_CAMERA_ENTITY =
"camera_entity"
30 ATTR_PERSON =
"person"
32 CONF_AZURE_REGION =
"azure_region"
34 DATA_MICROSOFT_FACE =
"microsoft_face"
36 DOMAIN =
"microsoft_face"
38 FACE_API_URL =
"api.cognitive.microsoft.com/face/v1.0/{0}"
40 SERVICE_CREATE_GROUP =
"create_group"
41 SERVICE_CREATE_PERSON =
"create_person"
42 SERVICE_DELETE_GROUP =
"delete_group"
43 SERVICE_DELETE_PERSON =
"delete_person"
44 SERVICE_FACE_PERSON =
"face_person"
45 SERVICE_TRAIN_GROUP =
"train_group"
47 CONFIG_SCHEMA = vol.Schema(
51 vol.Required(CONF_API_KEY): cv.string,
52 vol.Optional(CONF_AZURE_REGION, default=
"westus"): cv.string,
53 vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
57 extra=vol.ALLOW_EXTRA,
60 SCHEMA_GROUP_SERVICE = vol.Schema({vol.Required(ATTR_NAME): cv.string})
62 SCHEMA_PERSON_SERVICE = SCHEMA_GROUP_SERVICE.extend(
63 {vol.Required(ATTR_GROUP): cv.slugify}
66 SCHEMA_FACE_SERVICE = vol.Schema(
68 vol.Required(ATTR_PERSON): cv.string,
69 vol.Required(ATTR_GROUP): cv.slugify,
70 vol.Required(ATTR_CAMERA_ENTITY): cv.entity_id,
74 SCHEMA_TRAIN_SERVICE = vol.Schema({vol.Required(ATTR_GROUP): cv.slugify})
77 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
78 """Set up Microsoft Face."""
79 component = EntityComponent[MicrosoftFaceGroupEntity](
80 logging.getLogger(__name__), DOMAIN, hass
82 entities: dict[str, MicrosoftFaceGroupEntity] = {}
85 config[DOMAIN].
get(CONF_AZURE_REGION),
86 config[DOMAIN].
get(CONF_API_KEY),
87 config[DOMAIN].
get(CONF_TIMEOUT),
94 await face.update_store()
95 except HomeAssistantError
as err:
96 _LOGGER.error(
"Can't load data from face api: %s", err)
99 hass.data[DATA_MICROSOFT_FACE] = face
101 async
def async_create_group(service: ServiceCall) ->
None:
102 """Create a new person group."""
103 name = service.data[ATTR_NAME]
107 await face.call_api(
"put", f
"persongroups/{g_id}", {
"name": name})
108 face.store[g_id] = {}
109 old_entity = entities.pop(g_id,
None)
111 await component.async_remove_entity(old_entity.entity_id)
114 await component.async_add_entities([entities[g_id]])
115 except HomeAssistantError
as err:
116 _LOGGER.error(
"Can't create group '%s' with error: %s", g_id, err)
118 hass.services.async_register(
119 DOMAIN, SERVICE_CREATE_GROUP, async_create_group, schema=SCHEMA_GROUP_SERVICE
122 async
def async_delete_group(service: ServiceCall) ->
None:
123 """Delete a person group."""
124 g_id =
slugify(service.data[ATTR_NAME])
127 await face.call_api(
"delete", f
"persongroups/{g_id}")
130 entity = entities.pop(g_id)
131 await component.async_remove_entity(entity.entity_id)
132 except HomeAssistantError
as err:
133 _LOGGER.error(
"Can't delete group '%s' with error: %s", g_id, err)
135 hass.services.async_register(
136 DOMAIN, SERVICE_DELETE_GROUP, async_delete_group, schema=SCHEMA_GROUP_SERVICE
139 async
def async_train_group(service: ServiceCall) ->
None:
140 """Train a person group."""
141 g_id = service.data[ATTR_GROUP]
144 await face.call_api(
"post", f
"persongroups/{g_id}/train")
145 except HomeAssistantError
as err:
146 _LOGGER.error(
"Can't train group '%s' with error: %s", g_id, err)
148 hass.services.async_register(
149 DOMAIN, SERVICE_TRAIN_GROUP, async_train_group, schema=SCHEMA_TRAIN_SERVICE
153 """Create a person in a group."""
154 name = service.data[ATTR_NAME]
155 g_id = service.data[ATTR_GROUP]
158 user_data = await face.call_api(
159 "post", f
"persongroups/{g_id}/persons", {
"name": name}
162 face.store[g_id][name] = user_data[
"personId"]
163 entities[g_id].async_write_ha_state()
164 except HomeAssistantError
as err:
165 _LOGGER.error(
"Can't create person '%s' with error: %s", name, err)
167 hass.services.async_register(
168 DOMAIN, SERVICE_CREATE_PERSON, async_create_person, schema=SCHEMA_PERSON_SERVICE
171 async
def async_delete_person(service: ServiceCall) ->
None:
172 """Delete a person in a group."""
173 name = service.data[ATTR_NAME]
174 g_id = service.data[ATTR_GROUP]
175 p_id = face.store[g_id].
get(name)
178 await face.call_api(
"delete", f
"persongroups/{g_id}/persons/{p_id}")
180 face.store[g_id].pop(name)
181 entities[g_id].async_write_ha_state()
182 except HomeAssistantError
as err:
183 _LOGGER.error(
"Can't delete person '%s' with error: %s", p_id, err)
185 hass.services.async_register(
186 DOMAIN, SERVICE_DELETE_PERSON, async_delete_person, schema=SCHEMA_PERSON_SERVICE
189 async
def async_face_person(service: ServiceCall) ->
None:
190 """Add a new face picture to a person."""
191 g_id = service.data[ATTR_GROUP]
192 p_id = face.store[g_id].
get(service.data[ATTR_PERSON])
194 camera_entity = service.data[ATTR_CAMERA_ENTITY]
197 image = await camera.async_get_image(hass, camera_entity)
201 f
"persongroups/{g_id}/persons/{p_id}/persistedFaces",
205 except HomeAssistantError
as err:
207 "Can't add an image of a person '%s' with error: %s", p_id, err
210 hass.services.async_register(
211 DOMAIN, SERVICE_FACE_PERSON, async_face_person, schema=SCHEMA_FACE_SERVICE
218 """Person-Group state/data Entity."""
220 _attr_should_poll =
False
223 """Initialize person/group entity."""
231 """Return the name of the entity."""
232 return self.
_name_name
236 """Return entity id."""
237 return f
"{DOMAIN}.{self._id}"
241 """Return the state of the entity."""
242 return len(self.
_api_api.store[self.
_id_id])
246 """Return device specific state attributes."""
251 """Microsoft Face api for Home Assistant."""
253 def __init__(self, hass, server_loc, api_key, timeout, component, entities):
254 """Initialize Microsoft Face api."""
259 self.
_server_url_server_url = f
"https://{server_loc}.{FACE_API_URL}"
261 self._component: EntityComponent[MicrosoftFaceGroupEntity] = component
266 """Store group/person data and IDs."""
270 """Load all group/person data into local store."""
271 groups = await self.
call_apicall_api(
"get",
"persongroups")
273 remove_tasks: list[Coroutine[Any, Any,
None]] = []
276 g_id = group[
"personGroupId"]
277 self.
_store_store[g_id] = {}
278 old_entity = self.
_entities_entities.pop(g_id,
None)
281 self._component.async_remove_entity(old_entity.entity_id)
285 self.
hasshass, self, g_id, group[
"name"]
287 new_entities.append(self.
_entities_entities[g_id])
289 persons = await self.
call_apicall_api(
"get", f
"persongroups/{g_id}/persons")
291 for person
in persons:
292 self.
_store_store[g_id][person[
"name"]] = person[
"personId"]
295 await asyncio.gather(*remove_tasks)
298 async
def call_api(self, method, function, data=None, binary=False, params=None):
299 """Make an api call."""
300 headers = {
"Ocp-Apim-Subscription-Key": self.
_api_key_api_key}
305 headers[CONTENT_TYPE] =
"application/octet-stream"
308 headers[CONTENT_TYPE] = CONTENT_TYPE_JSON
310 payload = json.dumps(data).encode()
315 async
with asyncio.timeout(self.
timeouttimeout):
316 response = await getattr(self.
websessionwebsession, method)(
317 url, data=payload, headers=headers, params=params
320 answer = await response.json()
322 _LOGGER.debug(
"Read from microsoft face api: %s", answer)
323 if response.status < 300:
327 "Error %d microsoft face api %s", response.status, response.url
331 except aiohttp.ClientError:
332 _LOGGER.warning(
"Can't connect to microsoft face api")
335 _LOGGER.warning(
"Timeout from microsoft face api %s", response.url)
def __init__(self, hass, api, g_id, name)
def extra_state_attributes(self)
def __init__(self, hass, server_loc, api_key, timeout, component, entities)
def call_api(self, method, function, data=None, binary=False, params=None)
web.Response get(self, web.Request request, str config_key)
bool async_setup(HomeAssistant hass, ConfigType config)
None async_create_person(HomeAssistant hass, str name, *str|None user_id=None, list[str]|None device_trackers=None)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)
str slugify(str|None text, *str separator="_")