Home Assistant Unofficial Reference 2024.12.1
image_processing.py
Go to the documentation of this file.
1 """Component that will help set the OpenALPR cloud for ALPR processing."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from base64 import b64encode
7 from http import HTTPStatus
8 import logging
9 
10 import aiohttp
11 import voluptuous as vol
12 
14  ATTR_CONFIDENCE,
15  CONF_CONFIDENCE,
16  PLATFORM_SCHEMA as IMAGE_PROCESSING_PLATFORM_SCHEMA,
17  ImageProcessingDeviceClass,
18  ImageProcessingEntity,
19 )
20 from homeassistant.const import (
21  ATTR_ENTITY_ID,
22  CONF_API_KEY,
23  CONF_ENTITY_ID,
24  CONF_NAME,
25  CONF_REGION,
26  CONF_SOURCE,
27 )
28 from homeassistant.core import HomeAssistant, callback, split_entity_id
29 from homeassistant.helpers.aiohttp_client import async_get_clientsession
31 from homeassistant.helpers.entity_platform import AddEntitiesCallback
32 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
33 from homeassistant.util.async_ import run_callback_threadsafe
34 
35 _LOGGER = logging.getLogger(__name__)
36 
37 ATTR_PLATE = "plate"
38 ATTR_PLATES = "plates"
39 ATTR_VEHICLES = "vehicles"
40 
41 EVENT_FOUND_PLATE = "image_processing.found_plate"
42 
43 OPENALPR_API_URL = "https://api.openalpr.com/v1/recognize"
44 
45 OPENALPR_REGIONS = [
46  "au",
47  "auwide",
48  "br",
49  "eu",
50  "fr",
51  "gb",
52  "kr",
53  "kr2",
54  "mx",
55  "sg",
56  "us",
57  "vn2",
58 ]
59 
60 PLATFORM_SCHEMA = IMAGE_PROCESSING_PLATFORM_SCHEMA.extend(
61  {
62  vol.Required(CONF_API_KEY): cv.string,
63  vol.Required(CONF_REGION): vol.All(vol.Lower, vol.In(OPENALPR_REGIONS)),
64  }
65 )
66 
67 
69  hass: HomeAssistant,
70  config: ConfigType,
71  async_add_entities: AddEntitiesCallback,
72  discovery_info: DiscoveryInfoType | None = None,
73 ) -> None:
74  """Set up the OpenALPR cloud API platform."""
75  confidence = config[CONF_CONFIDENCE]
76  params = {
77  "secret_key": config[CONF_API_KEY],
78  "tasks": "plate",
79  "return_image": 0,
80  "country": config[CONF_REGION],
81  }
82 
85  camera[CONF_ENTITY_ID], params, confidence, camera.get(CONF_NAME)
86  )
87  for camera in config[CONF_SOURCE]
88  )
89 
90 
92  """Base entity class for ALPR image processing."""
93 
94  _attr_device_class = ImageProcessingDeviceClass.ALPR
95 
96  def __init__(self) -> None:
97  """Initialize base ALPR entity."""
98  self.platesplates: dict[str, float] = {}
99  self.vehiclesvehicles = 0
100 
101  @property
102  def state(self):
103  """Return the state of the entity."""
104  confidence = 0
105  plate = None
106 
107  # search high plate
108  for i_pl, i_co in self.platesplates.items():
109  if i_co > confidence:
110  confidence = i_co
111  plate = i_pl
112  return plate
113 
114  @property
116  """Return device specific state attributes."""
117  return {ATTR_PLATES: self.platesplates, ATTR_VEHICLES: self.vehiclesvehicles}
118 
119  def process_plates(self, plates: dict[str, float], vehicles: int) -> None:
120  """Send event with new plates and store data."""
121  run_callback_threadsafe(
122  self.hasshass.loop, self.async_process_platesasync_process_plates, plates, vehicles
123  ).result()
124 
125  @callback
126  def async_process_plates(self, plates: dict[str, float], vehicles: int) -> None:
127  """Send event with new plates and store data.
128 
129  Plates are a dict in follow format:
130  { '<plate>': confidence }
131  This method must be run in the event loop.
132  """
133  plates = {
134  plate: confidence
135  for plate, confidence in plates.items()
136  if self.confidenceconfidence is None or confidence >= self.confidenceconfidence
137  }
138  new_plates = set(plates) - set(self.platesplates)
139 
140  # Send events
141  for i_plate in new_plates:
142  self.hasshass.bus.async_fire(
143  EVENT_FOUND_PLATE,
144  {
145  ATTR_PLATE: i_plate,
146  ATTR_ENTITY_ID: self.entity_identity_id,
147  ATTR_CONFIDENCE: plates.get(i_plate),
148  },
149  )
150 
151  # Update entity store
152  self.platesplates = plates
153  self.vehiclesvehicles = vehicles
154 
155 
157  """Representation of an OpenALPR cloud entity."""
158 
159  def __init__(self, camera_entity, params, confidence, name=None):
160  """Initialize OpenALPR cloud API."""
161  super().__init__()
162 
163  self._params_params = params
164  self._camera_camera = camera_entity
165  self._confidence_confidence = confidence
166 
167  if name:
168  self._name_name = name
169  else:
170  self._name_name = f"OpenAlpr {split_entity_id(camera_entity)[1]}"
171 
172  @property
173  def confidence(self):
174  """Return minimum confidence for send events."""
175  return self._confidence_confidence
176 
177  @property
178  def camera_entity(self):
179  """Return camera entity id from process pictures."""
180  return self._camera_camera
181 
182  @property
183  def name(self):
184  """Return the name of the entity."""
185  return self._name_name
186 
187  async def async_process_image(self, image):
188  """Process image.
189 
190  This method is a coroutine.
191  """
192  websession = async_get_clientsession(self.hasshass)
193  params = self._params_params.copy()
194 
195  body = {"image_bytes": str(b64encode(image), "utf-8")}
196 
197  try:
198  async with asyncio.timeout(self.timeouttimeout):
199  request = await websession.post(
200  OPENALPR_API_URL, params=params, data=body
201  )
202 
203  data = await request.json()
204 
205  if request.status != HTTPStatus.OK:
206  _LOGGER.error("Error %d -> %s", request.status, data.get("error"))
207  return
208 
209  except (TimeoutError, aiohttp.ClientError):
210  _LOGGER.error("Timeout for OpenALPR API")
211  return
212 
213  # Processing API data
214  vehicles = 0
215  result = {}
216 
217  for row in data["plate"]["results"]:
218  vehicles += 1
219 
220  for p_data in row["candidates"]:
221  try:
222  result.update({p_data["plate"]: float(p_data["confidence"])})
223  except ValueError:
224  continue
225 
226  self.async_process_platesasync_process_plates(result, vehicles)
def __init__(self, camera_entity, params, confidence, name=None)
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=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)