mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 17:27:52 +00:00
Remove old openalpr component (#5406)
* Remove old openalpr component * update region support
This commit is contained in:
parent
d1ec422eab
commit
fe6a8f3367
@ -266,7 +266,6 @@ omit =
|
||||
homeassistant/components/notify/twitter.py
|
||||
homeassistant/components/notify/xmpp.py
|
||||
homeassistant/components/nuimo_controller.py
|
||||
homeassistant/components/openalpr.py
|
||||
homeassistant/components/remote/harmony.py
|
||||
homeassistant/components/scene/hunterdouglas_powerview.py
|
||||
homeassistant/components/sensor/amcrest.py
|
||||
|
@ -26,14 +26,18 @@ _LOGGER = logging.getLogger(__name__)
|
||||
OPENALPR_API_URL = "https://api.openalpr.com/v1/recognize"
|
||||
|
||||
OPENALPR_REGIONS = [
|
||||
'us',
|
||||
'eu',
|
||||
'au',
|
||||
'auwide',
|
||||
'br',
|
||||
'eu',
|
||||
'fr',
|
||||
'gb',
|
||||
'kr',
|
||||
'kr2',
|
||||
'mx',
|
||||
'sg',
|
||||
'us',
|
||||
'vn2'
|
||||
]
|
||||
|
||||
CONF_REGION = 'region'
|
||||
|
@ -31,16 +31,21 @@ ATTR_PLATES = 'plates'
|
||||
ATTR_VEHICLES = 'vehicles'
|
||||
|
||||
OPENALPR_REGIONS = [
|
||||
'us',
|
||||
'eu',
|
||||
'au',
|
||||
'auwide',
|
||||
'br',
|
||||
'eu',
|
||||
'fr',
|
||||
'gb',
|
||||
'kr',
|
||||
'kr2',
|
||||
'mx',
|
||||
'sg',
|
||||
'us',
|
||||
'vn2'
|
||||
]
|
||||
|
||||
|
||||
CONF_REGION = 'region'
|
||||
CONF_ALPR_BIN = 'alp_bin'
|
||||
|
||||
|
@ -1,472 +0,0 @@
|
||||
"""
|
||||
Component that will help set the openalpr for video streams.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/openalpr/
|
||||
"""
|
||||
from base64 import b64encode
|
||||
import logging
|
||||
import os
|
||||
from time import time
|
||||
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.const import (
|
||||
CONF_API_KEY, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, ATTR_ENTITY_ID,
|
||||
EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN)
|
||||
from homeassistant.components.ffmpeg import (
|
||||
get_binary, run_test, CONF_INPUT, CONF_EXTRA_ARGUMENTS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
|
||||
DOMAIN = 'openalpr'
|
||||
DEPENDENCIES = ['ffmpeg']
|
||||
REQUIREMENTS = [
|
||||
'https://github.com/pvizeli/cloudapi/releases/download/1.0.2/'
|
||||
'python-1.0.2.zip#openalpr_api==1.0.2',
|
||||
'ha-alpr==0.3']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SERVICE_SCAN = 'scan'
|
||||
SERVICE_RESTART = 'restart'
|
||||
|
||||
EVENT_FOUND = 'openalpr.found'
|
||||
|
||||
ATTR_PLATE = 'plate'
|
||||
|
||||
|
||||
ENGINE_LOCAL = 'local'
|
||||
ENGINE_CLOUD = 'cloud'
|
||||
|
||||
RENDER_IMAGE = 'image'
|
||||
RENDER_FFMPEG = 'ffmpeg'
|
||||
|
||||
OPENALPR_REGIONS = [
|
||||
'us',
|
||||
'eu',
|
||||
'au',
|
||||
'auwide',
|
||||
'gb',
|
||||
'kr',
|
||||
'mx',
|
||||
'sg',
|
||||
]
|
||||
|
||||
CONF_RENDER = 'render'
|
||||
CONF_ENGINE = 'engine'
|
||||
CONF_REGION = 'region'
|
||||
CONF_INTERVAL = 'interval'
|
||||
CONF_ENTITIES = 'entities'
|
||||
CONF_CONFIDENCE = 'confidence'
|
||||
CONF_ALPR_BINARY = 'alpr_binary'
|
||||
|
||||
DEFAULT_NAME = 'OpenAlpr'
|
||||
DEFAULT_ENGINE = ENGINE_LOCAL
|
||||
DEFAULT_RENDER = RENDER_FFMPEG
|
||||
DEFAULT_BINARY = 'alpr'
|
||||
DEFAULT_INTERVAL = 10
|
||||
DEFAULT_CONFIDENCE = 80.0
|
||||
|
||||
DEVICE_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_INPUT): cv.string,
|
||||
vol.Optional(CONF_INTERVAL, default=DEFAULT_INTERVAL): cv.positive_int,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_RENDER, default=DEFAULT_RENDER):
|
||||
vol.In([RENDER_IMAGE, RENDER_FFMPEG]),
|
||||
vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string,
|
||||
vol.Optional(CONF_USERNAME): cv.string,
|
||||
vol.Optional(CONF_PASSWORD): cv.string,
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_ENGINE): vol.In([ENGINE_LOCAL, ENGINE_CLOUD]),
|
||||
vol.Required(CONF_REGION): vol.In(OPENALPR_REGIONS),
|
||||
vol.Optional(CONF_CONFIDENCE, default=DEFAULT_CONFIDENCE):
|
||||
vol.Coerce(float),
|
||||
vol.Optional(CONF_API_KEY): cv.string,
|
||||
vol.Optional(CONF_ALPR_BINARY, default=DEFAULT_BINARY): cv.string,
|
||||
vol.Required(CONF_ENTITIES):
|
||||
vol.All(cv.ensure_list, [DEVICE_SCHEMA]),
|
||||
})
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
SERVICE_RESTART_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
})
|
||||
|
||||
SERVICE_SCAN_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
})
|
||||
|
||||
|
||||
def scan(hass, entity_id=None):
|
||||
"""Scan a image immediately."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
hass.services.call(DOMAIN, SERVICE_SCAN, data)
|
||||
|
||||
|
||||
def restart(hass, entity_id=None):
|
||||
"""Restart a ffmpeg process."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
hass.services.call(DOMAIN, SERVICE_RESTART, data)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Setup the OpenAlpr component."""
|
||||
engine = config[DOMAIN].get(CONF_ENGINE)
|
||||
region = config[DOMAIN].get(CONF_REGION)
|
||||
confidence = config[DOMAIN].get(CONF_CONFIDENCE)
|
||||
api_key = config[DOMAIN].get(CONF_API_KEY)
|
||||
binary = config[DOMAIN].get(CONF_ALPR_BINARY)
|
||||
use_render_fffmpeg = False
|
||||
|
||||
_LOGGER.warning("This platform is replaced by 'image_processing' and will "
|
||||
"be removed in a future version!")
|
||||
|
||||
component = EntityComponent(_LOGGER, DOMAIN, hass)
|
||||
openalpr_device = []
|
||||
|
||||
for device in config[DOMAIN].get(CONF_ENTITIES):
|
||||
input_source = device.get(CONF_INPUT)
|
||||
render = device.get(CONF_RENDER)
|
||||
|
||||
##
|
||||
# create api
|
||||
if engine == ENGINE_LOCAL:
|
||||
alpr_api = OpenalprApiLocal(
|
||||
confidence=confidence,
|
||||
region=region,
|
||||
binary=binary,
|
||||
)
|
||||
else:
|
||||
alpr_api = OpenalprApiCloud(
|
||||
confidence=confidence,
|
||||
region=region,
|
||||
api_key=api_key,
|
||||
)
|
||||
|
||||
##
|
||||
# Create Alpr device / render engine
|
||||
if render == RENDER_FFMPEG:
|
||||
use_render_fffmpeg = True
|
||||
if not run_test(hass, input_source):
|
||||
_LOGGER.error("'%s' is not valid ffmpeg input", input_source)
|
||||
continue
|
||||
|
||||
alpr_dev = OpenalprDeviceFFmpeg(
|
||||
name=device.get(CONF_NAME),
|
||||
interval=device.get(CONF_INTERVAL),
|
||||
api=alpr_api,
|
||||
input_source=input_source,
|
||||
extra_arguments=device.get(CONF_EXTRA_ARGUMENTS),
|
||||
)
|
||||
else:
|
||||
alpr_dev = OpenalprDeviceImage(
|
||||
name=device.get(CONF_NAME),
|
||||
interval=device.get(CONF_INTERVAL),
|
||||
api=alpr_api,
|
||||
input_source=input_source,
|
||||
username=device.get(CONF_USERNAME),
|
||||
password=device.get(CONF_PASSWORD),
|
||||
)
|
||||
|
||||
# register shutdown event
|
||||
openalpr_device.append(alpr_dev)
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, alpr_dev.shutdown)
|
||||
|
||||
component.add_entities(openalpr_device)
|
||||
|
||||
descriptions = load_yaml_config_file(
|
||||
os.path.join(os.path.dirname(__file__), 'services.yaml'))
|
||||
|
||||
def _handle_service_scan(service):
|
||||
"""Handle service for immediately scan."""
|
||||
device_list = component.extract_from_service(service)
|
||||
|
||||
for device in device_list:
|
||||
device.scan()
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_SCAN,
|
||||
_handle_service_scan,
|
||||
descriptions[DOMAIN][SERVICE_SCAN],
|
||||
schema=SERVICE_SCAN_SCHEMA)
|
||||
|
||||
# Add restart service only if a device use ffmpeg as render
|
||||
if not use_render_fffmpeg:
|
||||
return True
|
||||
|
||||
def _handle_service_restart(service):
|
||||
"""Handle service for restart ffmpeg process."""
|
||||
device_list = component.extract_from_service(service)
|
||||
|
||||
for device in device_list:
|
||||
device.restart()
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_RESTART,
|
||||
_handle_service_restart,
|
||||
descriptions[DOMAIN][SERVICE_RESTART],
|
||||
schema=SERVICE_RESTART_SCHEMA)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class OpenalprDevice(Entity):
|
||||
"""Represent a openalpr device object for processing stream/images."""
|
||||
|
||||
def __init__(self, name, interval, api):
|
||||
"""Init image processing."""
|
||||
self._name = name
|
||||
self._interval = interval
|
||||
self._api = api
|
||||
self._last = {}
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the entity."""
|
||||
confidence = 0
|
||||
plate = STATE_UNKNOWN
|
||||
|
||||
# search high plate
|
||||
for i_pl, i_co in self._last.items():
|
||||
if i_co > confidence:
|
||||
confidence = i_co
|
||||
plate = i_pl
|
||||
return plate
|
||||
|
||||
def shutdown(self, event):
|
||||
"""Close stream."""
|
||||
if hasattr(self._api, "shutdown"):
|
||||
self._api.shutdown(event)
|
||||
|
||||
def restart(self):
|
||||
"""Restart stream."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _process_image(self, image):
|
||||
"""Callback for processing image."""
|
||||
self._api.process_image(image, self._process_event)
|
||||
|
||||
def _process_event(self, plates):
|
||||
"""Send event with new plates."""
|
||||
state_change = False
|
||||
plates_set = set(plates)
|
||||
last_set = set(self._last)
|
||||
new_plates = plates_set - last_set
|
||||
|
||||
# send events
|
||||
for i_plate in new_plates:
|
||||
self.hass.bus.fire(EVENT_FOUND, {
|
||||
ATTR_PLATE: i_plate,
|
||||
ATTR_ENTITY_ID: self.entity_id
|
||||
})
|
||||
|
||||
# update entity store
|
||||
if last_set <= plates_set:
|
||||
state_change = True
|
||||
self._last = plates
|
||||
|
||||
# update HA state
|
||||
if state_change:
|
||||
self.update_ha_state()
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return device specific state attributes."""
|
||||
return {'plates': self._last}
|
||||
|
||||
def scan(self):
|
||||
"""Immediately scan a image."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the entity."""
|
||||
return self._name
|
||||
|
||||
|
||||
class OpenalprDeviceFFmpeg(OpenalprDevice):
|
||||
"""Represent a openalpr device object for processing stream/images."""
|
||||
|
||||
def __init__(self, name, interval, api, input_source,
|
||||
extra_arguments=None):
|
||||
"""Init image processing."""
|
||||
from haffmpeg import ImageStream, ImageSingle
|
||||
|
||||
super().__init__(name, interval, api)
|
||||
self._input_source = input_source
|
||||
self._extra_arguments = extra_arguments
|
||||
|
||||
if self._interval > 0:
|
||||
self._ffmpeg = ImageStream(get_binary(), self._process_image)
|
||||
else:
|
||||
self._ffmpeg = ImageSingle(get_binary())
|
||||
|
||||
self._start_ffmpeg()
|
||||
|
||||
def shutdown(self, event):
|
||||
"""Close ffmpeg stream."""
|
||||
if self._interval > 0:
|
||||
self._ffmpeg.close()
|
||||
|
||||
def restart(self):
|
||||
"""Restart ffmpeg stream."""
|
||||
if self._interval > 0:
|
||||
self._ffmpeg.close()
|
||||
self._start_ffmpeg()
|
||||
|
||||
def scan(self):
|
||||
"""Immediately scan a image."""
|
||||
from haffmpeg import IMAGE_PNG
|
||||
|
||||
# process single image
|
||||
if self._interval == 0:
|
||||
image = self._ffmpeg.get_image(
|
||||
self._input_source,
|
||||
output_format=IMAGE_PNG,
|
||||
extra_cmd=self._extra_arguments
|
||||
)
|
||||
return self._process_image(image)
|
||||
|
||||
# stream
|
||||
self._ffmpeg.push_image()
|
||||
|
||||
def _start_ffmpeg(self):
|
||||
"""Start a ffmpeg image stream."""
|
||||
from haffmpeg import IMAGE_PNG
|
||||
if self._interval == 0:
|
||||
return
|
||||
|
||||
self._ffmpeg.open_stream(
|
||||
input_source=self._input_source,
|
||||
interval=self._interval,
|
||||
output_format=IMAGE_PNG,
|
||||
extra_cmd=self._extra_arguments,
|
||||
)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return True if render is be 'image' or False if 'ffmpeg'."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
return self._interval == 0 or self._ffmpeg.is_running
|
||||
|
||||
|
||||
class OpenalprDeviceImage(OpenalprDevice):
|
||||
"""Represent a openalpr device object for processing stream/images."""
|
||||
|
||||
def __init__(self, name, interval, api, input_source,
|
||||
username=None, password=None):
|
||||
"""Init image processing."""
|
||||
super().__init__(name, interval, api)
|
||||
|
||||
self._next = time()
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._url = input_source
|
||||
|
||||
def restart(self):
|
||||
"""Fake restart with scan a picture."""
|
||||
self.scan()
|
||||
|
||||
def scan(self):
|
||||
"""Immediately scan a image."""
|
||||
# send request
|
||||
if self._username is not None and self._password is not None:
|
||||
req = requests.get(
|
||||
self._url, auth=(self._username, self._password), timeout=15)
|
||||
else:
|
||||
req = requests.get(self._url, timeout=15)
|
||||
|
||||
# process image
|
||||
image = req.content
|
||||
self._process_image(image)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return True if render is be 'image' or False if 'ffmpeg'."""
|
||||
return self._interval > 0
|
||||
|
||||
def update(self):
|
||||
"""Retrieve latest state."""
|
||||
if self._next > time():
|
||||
return
|
||||
self.scan()
|
||||
self._next = time() + self._interval
|
||||
|
||||
|
||||
class OpenalprApi(object):
|
||||
"""OpenAlpr api class."""
|
||||
|
||||
def __init__(self, region, confidence):
|
||||
"""Init basic api processing."""
|
||||
self._region = region
|
||||
self._confidence = confidence
|
||||
|
||||
def process_image(self, image, event_callback):
|
||||
"""Callback for processing image."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class OpenalprApiCloud(OpenalprApi):
|
||||
"""Use the cloud openalpr api to parse licences plate."""
|
||||
|
||||
def __init__(self, region, confidence, api_key):
|
||||
"""Init cloud api processing."""
|
||||
import openalpr_api
|
||||
|
||||
super().__init__(region=region, confidence=confidence)
|
||||
self._api = openalpr_api.DefaultApi()
|
||||
self._api_key = api_key
|
||||
|
||||
def process_image(self, image, event_callback):
|
||||
"""Callback for processing image."""
|
||||
result = self._api.recognize_post(
|
||||
self._api_key,
|
||||
'plate',
|
||||
image="",
|
||||
image_bytes=str(b64encode(image), 'utf-8'),
|
||||
country=self._region
|
||||
)
|
||||
|
||||
# process result
|
||||
f_plates = {}
|
||||
# pylint: disable=no-member
|
||||
for object_plate in result.plate.results:
|
||||
plate = object_plate.plate
|
||||
confidence = object_plate.confidence
|
||||
if confidence >= self._confidence:
|
||||
f_plates[plate] = confidence
|
||||
event_callback(f_plates)
|
||||
|
||||
|
||||
class OpenalprApiLocal(OpenalprApi):
|
||||
"""Use local openalpr library to parse licences plate."""
|
||||
|
||||
def __init__(self, region, confidence, binary):
|
||||
"""Init local api processing."""
|
||||
# pylint: disable=import-error
|
||||
from haalpr import HAAlpr
|
||||
|
||||
super().__init__(region=region, confidence=confidence)
|
||||
self._api = HAAlpr(binary=binary, country=region)
|
||||
|
||||
def process_image(self, image, event_callback):
|
||||
"""Callback for processing image."""
|
||||
result = self._api.recognize_byte(image)
|
||||
|
||||
# process result
|
||||
f_plates = {}
|
||||
for found in result:
|
||||
for plate, confidence in found.items():
|
||||
if confidence >= self._confidence:
|
||||
f_plates[plate] = confidence
|
||||
event_callback(f_plates)
|
@ -169,9 +169,6 @@ googlemaps==2.4.4
|
||||
# homeassistant.components.sensor.gpsd
|
||||
gps3==0.33.3
|
||||
|
||||
# homeassistant.components.openalpr
|
||||
ha-alpr==0.3
|
||||
|
||||
# homeassistant.components.ffmpeg
|
||||
ha-ffmpeg==0.15
|
||||
|
||||
@ -244,9 +241,6 @@ https://github.com/nkgilley/python-ecobee-api/archive/4856a704670c53afe1882178a8
|
||||
# homeassistant.components.notify.joaoapps_join
|
||||
https://github.com/nkgilley/python-join-api/archive/3e1e849f1af0b4080f551b62270c6d244d5fbcbd.zip#python-join-api==0.0.1
|
||||
|
||||
# homeassistant.components.openalpr
|
||||
https://github.com/pvizeli/cloudapi/releases/download/1.0.2/python-1.0.2.zip#openalpr_api==1.0.2
|
||||
|
||||
# homeassistant.components.switch.edimax
|
||||
https://github.com/rkabadi/pyedimax/archive/365301ce3ff26129a7910c501ead09ea625f3700.zip#pyedimax==0.1
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user