Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Provides functionality to interact with image processing services."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from datetime import timedelta
7 from enum import StrEnum
8 import logging
9 from typing import Any, Final, TypedDict, final
10 
11 import voluptuous as vol
12 
13 from homeassistant.components.camera import async_get_image
14 from homeassistant.const import (
15  ATTR_ENTITY_ID,
16  ATTR_NAME,
17  CONF_ENTITY_ID,
18  CONF_NAME,
19  CONF_SOURCE,
20 )
21 from homeassistant.core import HomeAssistant, ServiceCall, callback
22 from homeassistant.exceptions import HomeAssistantError
24 from homeassistant.helpers.config_validation import make_entity_service_schema
25 from homeassistant.helpers.entity import Entity, EntityDescription
26 from homeassistant.helpers.entity_component import EntityComponent
27 from homeassistant.helpers.typing import ConfigType
28 
29 _LOGGER = logging.getLogger(__name__)
30 
31 DOMAIN = "image_processing"
32 SCAN_INTERVAL = timedelta(seconds=10)
33 
34 
36  """Device class for image processing entities."""
37 
38  # Automatic license plate recognition
39  ALPR = "alpr"
40 
41  # Face
42  FACE = "face"
43 
44  # OCR
45  OCR = "ocr"
46 
47 
48 SERVICE_SCAN = "scan"
49 
50 EVENT_DETECT_FACE = "image_processing.detect_face"
51 
52 ATTR_AGE = "age"
53 ATTR_CONFIDENCE: Final = "confidence"
54 ATTR_FACES = "faces"
55 ATTR_GENDER = "gender"
56 ATTR_GLASSES = "glasses"
57 ATTR_MOTION: Final = "motion"
58 ATTR_TOTAL_FACES = "total_faces"
59 
60 CONF_CONFIDENCE = "confidence"
61 
62 DEFAULT_TIMEOUT = 10
63 DEFAULT_CONFIDENCE = 80
64 
65 SOURCE_SCHEMA = vol.Schema(
66  {
67  vol.Required(CONF_ENTITY_ID): cv.entity_domain("camera"),
68  vol.Optional(CONF_NAME): cv.string,
69  }
70 )
71 
72 PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(
73  {
74  vol.Optional(CONF_SOURCE): vol.All(cv.ensure_list, [SOURCE_SCHEMA]),
75  vol.Optional(CONF_CONFIDENCE, default=DEFAULT_CONFIDENCE): vol.All(
76  vol.Coerce(float), vol.Range(min=0, max=100)
77  ),
78  }
79 )
80 PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE.extend(PLATFORM_SCHEMA.schema)
81 
82 
83 class FaceInformation(TypedDict, total=False):
84  """Face information."""
85 
86  confidence: float
87  name: str
88  age: float
89  gender: str
90  motion: str
91  glasses: str
92  entity_id: str
93 
94 
95 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
96  """Set up the image processing."""
97  component = EntityComponent[ImageProcessingEntity](
98  _LOGGER, DOMAIN, hass, SCAN_INTERVAL
99  )
100 
101  await component.async_setup(config)
102 
103  async def async_scan_service(service: ServiceCall) -> None:
104  """Service handler for scan."""
105  image_entities = await component.async_extract_from_service(service)
106 
107  update_tasks = []
108  for entity in image_entities:
109  entity.async_set_context(service.context)
110  update_tasks.append(asyncio.create_task(entity.async_update_ha_state(True)))
111 
112  if update_tasks:
113  await asyncio.wait(update_tasks)
114 
115  hass.services.async_register(
116  DOMAIN, SERVICE_SCAN, async_scan_service, schema=make_entity_service_schema({})
117  )
118 
119  return True
120 
121 
123  """A class that describes sensor entities."""
124 
125  device_class: ImageProcessingDeviceClass | None = None
126  camera_entity: str | None = None
127  confidence: float | None = None
128 
129 
131  """Base entity class for image processing."""
132 
133  entity_description: ImageProcessingEntityDescription
134  _attr_device_class: ImageProcessingDeviceClass | None
135  _attr_camera_entity: str | None
136  _attr_confidence: float | None
137  timeout = DEFAULT_TIMEOUT
138 
139  @property
140  def camera_entity(self) -> str | None:
141  """Return camera entity id from process pictures."""
142  if hasattr(self, "_attr_camera_entity"):
143  return self._attr_camera_entity
144  if hasattr(self, "entity_description"):
145  return self.entity_description.camera_entity
146  return None
147 
148  @property
149  def confidence(self) -> float | None:
150  """Return minimum confidence to do some things."""
151  if hasattr(self, "_attr_confidence"):
152  return self._attr_confidence
153  if hasattr(self, "entity_description"):
154  return self.entity_description.confidence
155  return None
156 
157  @property
158  def device_class(self) -> ImageProcessingDeviceClass | None:
159  """Return the class of this entity."""
160  if hasattr(self, "_attr_device_class"):
161  return self._attr_device_class
162  if hasattr(self, "entity_description"):
163  return self.entity_description.device_class
164  return None
165 
166  def process_image(self, image: bytes) -> None:
167  """Process image."""
168  raise NotImplementedError
169 
170  async def async_process_image(self, image: bytes) -> None:
171  """Process image."""
172  return await self.hasshass.async_add_executor_job(self.process_imageprocess_image, image)
173 
174  async def async_update(self) -> None:
175  """Update image and process it.
176 
177  This method is a coroutine.
178  """
179  if self.camera_entitycamera_entity is None:
180  _LOGGER.error(
181  "No camera entity id was set by the image processing entity",
182  )
183  return
184 
185  try:
186  image = await async_get_image(
187  self.hasshass, self.camera_entitycamera_entity, timeout=self.timeouttimeout
188  )
189  except HomeAssistantError as err:
190  _LOGGER.error("Error on receive image from entity: %s", err)
191  return
192 
193  # process image data
194  await self.async_process_imageasync_process_image(image.content)
195 
196 
198  """Base entity class for face image processing."""
199 
200  _attr_device_class = ImageProcessingDeviceClass.FACE
201 
202  def __init__(self) -> None:
203  """Initialize base face identify/verify entity."""
204  self.facesfaces: list[FaceInformation] = []
205  self.total_facestotal_faces = 0
206 
207  @property
208  def state(self) -> str | int | None:
209  """Return the state of the entity."""
210  confidence: float = 0
211  state = None
212 
213  # No confidence support
214  if not self.confidenceconfidence:
215  return self.total_facestotal_faces
216 
217  # Search high confidence
218  for face in self.facesfaces:
219  if ATTR_CONFIDENCE not in face:
220  continue
221 
222  if (f_co := face[ATTR_CONFIDENCE]) > confidence:
223  confidence = f_co
224  for attr in (ATTR_NAME, ATTR_MOTION):
225  if attr in face:
226  state = face[attr]
227  break
228 
229  return state
230 
231  @final
232  @property
233  def state_attributes(self) -> dict[str, Any]:
234  """Return device specific state attributes."""
235  return {ATTR_FACES: self.facesfaces, ATTR_TOTAL_FACES: self.total_facestotal_faces}
236 
237  def process_faces(self, faces: list[FaceInformation], total: int) -> None:
238  """Send event with detected faces and store data."""
239  self.hasshass.loop.call_soon_threadsafe(self.async_process_facesasync_process_faces, faces, total)
240 
241  @callback
242  def async_process_faces(self, faces: list[FaceInformation], total: int) -> None:
243  """Send event with detected faces and store data.
244 
245  known are a dict in follow format:
246  [
247  {
248  ATTR_CONFIDENCE: 80,
249  ATTR_NAME: 'Name',
250  ATTR_AGE: 12.0,
251  ATTR_GENDER: 'man',
252  ATTR_MOTION: 'smile',
253  ATTR_GLASSES: 'sunglasses'
254  },
255  ]
256 
257  This method must be run in the event loop.
258  """
259  # Send events
260  for face in faces:
261  if (
262  ATTR_CONFIDENCE in face
263  and self.confidenceconfidence
264  and face[ATTR_CONFIDENCE] < self.confidenceconfidence
265  ):
266  continue
267 
268  face.update({ATTR_ENTITY_ID: self.entity_identity_id})
269  self.hasshass.bus.async_fire(EVENT_DETECT_FACE, face)
270 
271  # Update entity store
272  self.facesfaces = faces
273  self.total_facestotal_faces = total
ImageProcessingDeviceClass|None device_class(self)
Definition: __init__.py:158
None process_faces(self, list[FaceInformation] faces, int total)
Definition: __init__.py:237
None async_process_faces(self, list[FaceInformation] faces, int total)
Definition: __init__.py:242
bytes|None async_get_image(HomeAssistant hass, str input_source, str output_format=IMAGE_JPEG, str|None extra_cmd=None, int|None width=None, int|None height=None)
Definition: __init__.py:121
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:95
VolSchemaType make_entity_service_schema(dict|None schema, *int extra=vol.PREVENT_EXTRA)