forked from home-assistant/core
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add sighthound integration (home-assistant#28824)
* Add component files * Add test state * Adds person detected event * Update CODEOWNERS * Updates requirements * remove unused datetime * Bump sighthound version * Update CODEOWNERS * Update CODEOWNERS * Create requirements_test_all.txt * Address reviewer comments * Add test for bad_api_key
- Loading branch information
1 parent
73a5582
commit c71ae09
Showing
8 changed files
with
234 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""The sighthound integration.""" |
120 changes: 120 additions & 0 deletions
120
homeassistant/components/sighthound/image_processing.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
"""Person detection using Sighthound cloud service.""" | ||
import logging | ||
|
||
import simplehound.core as hound | ||
import voluptuous as vol | ||
|
||
from homeassistant.components.image_processing import ( | ||
CONF_ENTITY_ID, | ||
CONF_NAME, | ||
CONF_SOURCE, | ||
PLATFORM_SCHEMA, | ||
ImageProcessingEntity, | ||
) | ||
from homeassistant.const import ATTR_ENTITY_ID, CONF_API_KEY | ||
from homeassistant.core import split_entity_id | ||
import homeassistant.helpers.config_validation as cv | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
EVENT_PERSON_DETECTED = "sighthound.person_detected" | ||
|
||
ATTR_BOUNDING_BOX = "bounding_box" | ||
ATTR_PEOPLE = "people" | ||
CONF_ACCOUNT_TYPE = "account_type" | ||
DEV = "dev" | ||
PROD = "prod" | ||
|
||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( | ||
{ | ||
vol.Required(CONF_API_KEY): cv.string, | ||
vol.Optional(CONF_ACCOUNT_TYPE, default=DEV): vol.In([DEV, PROD]), | ||
} | ||
) | ||
|
||
|
||
def setup_platform(hass, config, add_entities, discovery_info=None): | ||
"""Set up the platform.""" | ||
# Validate credentials by processing image. | ||
api_key = config[CONF_API_KEY] | ||
account_type = config[CONF_ACCOUNT_TYPE] | ||
api = hound.cloud(api_key, account_type) | ||
try: | ||
api.detect(b"Test") | ||
except hound.SimplehoundException as exc: | ||
_LOGGER.error("Sighthound error %s setup aborted", exc) | ||
return | ||
|
||
entities = [] | ||
for camera in config[CONF_SOURCE]: | ||
sighthound = SighthoundEntity( | ||
api, camera[CONF_ENTITY_ID], camera.get(CONF_NAME) | ||
) | ||
entities.append(sighthound) | ||
add_entities(entities) | ||
|
||
|
||
class SighthoundEntity(ImageProcessingEntity): | ||
"""Create a sighthound entity.""" | ||
|
||
def __init__(self, api, camera_entity, name): | ||
"""Init.""" | ||
self._api = api | ||
self._camera = camera_entity | ||
if name: | ||
self._name = name | ||
else: | ||
camera_name = split_entity_id(camera_entity)[1] | ||
self._name = f"sighthound_{camera_name}" | ||
self._state = None | ||
self._image_width = None | ||
self._image_height = None | ||
|
||
def process_image(self, image): | ||
"""Process an image.""" | ||
detections = self._api.detect(image) | ||
people = hound.get_people(detections) | ||
self._state = len(people) | ||
|
||
metadata = hound.get_metadata(detections) | ||
self._image_width = metadata["image_width"] | ||
self._image_height = metadata["image_height"] | ||
for person in people: | ||
self.fire_person_detected_event(person) | ||
|
||
def fire_person_detected_event(self, person): | ||
"""Send event with detected total_persons.""" | ||
self.hass.bus.fire( | ||
EVENT_PERSON_DETECTED, | ||
{ | ||
ATTR_ENTITY_ID: self.entity_id, | ||
ATTR_BOUNDING_BOX: hound.bbox_to_tf_style( | ||
person["boundingBox"], self._image_width, self._image_height | ||
), | ||
}, | ||
) | ||
|
||
@property | ||
def camera_entity(self): | ||
"""Return camera entity id from process pictures.""" | ||
return self._camera | ||
|
||
@property | ||
def name(self): | ||
"""Return the name of the sensor.""" | ||
return self._name | ||
|
||
@property | ||
def should_poll(self): | ||
"""Return the polling state.""" | ||
return False | ||
|
||
@property | ||
def state(self): | ||
"""Return the state of the entity.""" | ||
return self._state | ||
|
||
@property | ||
def unit_of_measurement(self): | ||
"""Return the unit of measurement.""" | ||
return ATTR_PEOPLE |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"domain": "sighthound", | ||
"name": "Sighthound", | ||
"documentation": "https://www.home-assistant.io/integrations/sighthound", | ||
"requirements": [ | ||
"simplehound==0.3" | ||
], | ||
"dependencies": [], | ||
"codeowners": [ | ||
"@robmarkcole" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""Tests for the Sighthound integration.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
"""Tests for the Sighthound integration.""" | ||
from unittest.mock import patch | ||
|
||
import pytest | ||
import simplehound.core as hound | ||
|
||
import homeassistant.components.image_processing as ip | ||
import homeassistant.components.sighthound.image_processing as sh | ||
from homeassistant.const import ATTR_ENTITY_ID, CONF_API_KEY | ||
from homeassistant.core import callback | ||
from homeassistant.setup import async_setup_component | ||
|
||
VALID_CONFIG = { | ||
ip.DOMAIN: { | ||
"platform": "sighthound", | ||
CONF_API_KEY: "abc123", | ||
ip.CONF_SOURCE: {ip.CONF_ENTITY_ID: "camera.demo_camera"}, | ||
}, | ||
"camera": {"platform": "demo"}, | ||
} | ||
|
||
VALID_ENTITY_ID = "image_processing.sighthound_demo_camera" | ||
|
||
MOCK_DETECTIONS = { | ||
"image": {"width": 960, "height": 480, "orientation": 1}, | ||
"objects": [ | ||
{ | ||
"type": "person", | ||
"boundingBox": {"x": 227, "y": 133, "height": 245, "width": 125}, | ||
}, | ||
{ | ||
"type": "person", | ||
"boundingBox": {"x": 833, "y": 137, "height": 268, "width": 93}, | ||
}, | ||
], | ||
"requestId": "545cec700eac4d389743e2266264e84b", | ||
} | ||
|
||
|
||
@pytest.fixture | ||
def mock_detections(): | ||
"""Return a mock detection.""" | ||
with patch( | ||
"simplehound.core.cloud.detect", return_value=MOCK_DETECTIONS | ||
) as detection: | ||
yield detection | ||
|
||
|
||
@pytest.fixture | ||
def mock_image(): | ||
"""Return a mock camera image.""" | ||
with patch( | ||
"homeassistant.components.demo.camera.DemoCamera.camera_image", | ||
return_value=b"Test", | ||
) as image: | ||
yield image | ||
|
||
|
||
async def test_bad_api_key(hass, caplog): | ||
"""Catch bad api key.""" | ||
with patch("simplehound.core.cloud.detect", side_effect=hound.SimplehoundException): | ||
await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG) | ||
assert "Sighthound error" in caplog.text | ||
assert not hass.states.get(VALID_ENTITY_ID) | ||
|
||
|
||
async def test_setup_platform(hass, mock_detections): | ||
"""Set up platform with one entity.""" | ||
await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG) | ||
assert hass.states.get(VALID_ENTITY_ID) | ||
|
||
|
||
async def test_process_image(hass, mock_image, mock_detections): | ||
"""Process an image.""" | ||
await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG) | ||
assert hass.states.get(VALID_ENTITY_ID) | ||
|
||
person_events = [] | ||
|
||
@callback | ||
def capture_person_event(event): | ||
"""Mock event.""" | ||
person_events.append(event) | ||
|
||
hass.bus.async_listen(sh.EVENT_PERSON_DETECTED, capture_person_event) | ||
|
||
data = {ATTR_ENTITY_ID: VALID_ENTITY_ID} | ||
await hass.services.async_call(ip.DOMAIN, ip.SERVICE_SCAN, service_data=data) | ||
await hass.async_block_till_done() | ||
|
||
state = hass.states.get(VALID_ENTITY_ID) | ||
assert state.state == "2" | ||
assert len(person_events) == 2 |