Add support for homekit camera motion notification (#35994)
* Add support for homekit camera motion notification A motion sensor can now be linked to the cameras. * Increase coverage
This commit is contained in:
parent
15539536ad
commit
8cbee76929
6 changed files with 253 additions and 14 deletions
|
@ -9,8 +9,14 @@ import voluptuous as vol
|
||||||
from zeroconf import InterfaceChoice
|
from zeroconf import InterfaceChoice
|
||||||
|
|
||||||
from homeassistant.components import zeroconf
|
from homeassistant.components import zeroconf
|
||||||
from homeassistant.components.binary_sensor import DEVICE_CLASS_BATTERY_CHARGING
|
from homeassistant.components.binary_sensor import (
|
||||||
|
DEVICE_CLASS_BATTERY_CHARGING,
|
||||||
|
DEVICE_CLASS_MOTION,
|
||||||
|
DOMAIN as BINARY_SENSOR_DOMAIN,
|
||||||
|
)
|
||||||
|
from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN
|
||||||
from homeassistant.components.http import HomeAssistantView
|
from homeassistant.components.http import HomeAssistantView
|
||||||
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_BATTERY_CHARGING,
|
ATTR_BATTERY_CHARGING,
|
||||||
|
@ -58,6 +64,7 @@ from .const import (
|
||||||
CONF_FILTER,
|
CONF_FILTER,
|
||||||
CONF_LINKED_BATTERY_CHARGING_SENSOR,
|
CONF_LINKED_BATTERY_CHARGING_SENSOR,
|
||||||
CONF_LINKED_BATTERY_SENSOR,
|
CONF_LINKED_BATTERY_SENSOR,
|
||||||
|
CONF_LINKED_MOTION_SENSOR,
|
||||||
CONF_SAFE_MODE,
|
CONF_SAFE_MODE,
|
||||||
CONF_ZEROCONF_DEFAULT_INTERFACE,
|
CONF_ZEROCONF_DEFAULT_INTERFACE,
|
||||||
CONFIG_OPTIONS,
|
CONFIG_OPTIONS,
|
||||||
|
@ -522,8 +529,9 @@ class HomeKit:
|
||||||
|
|
||||||
device_lookup = ent_reg.async_get_device_class_lookup(
|
device_lookup = ent_reg.async_get_device_class_lookup(
|
||||||
{
|
{
|
||||||
("binary_sensor", DEVICE_CLASS_BATTERY_CHARGING),
|
(BINARY_SENSOR_DOMAIN, DEVICE_CLASS_BATTERY_CHARGING),
|
||||||
("sensor", DEVICE_CLASS_BATTERY),
|
(BINARY_SENSOR_DOMAIN, DEVICE_CLASS_MOTION),
|
||||||
|
(SENSOR_DOMAIN, DEVICE_CLASS_BATTERY),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -537,9 +545,7 @@ class HomeKit:
|
||||||
await self._async_set_device_info_attributes(
|
await self._async_set_device_info_attributes(
|
||||||
ent_reg_ent, dev_reg, state.entity_id
|
ent_reg_ent, dev_reg, state.entity_id
|
||||||
)
|
)
|
||||||
self._async_configure_linked_battery_sensors(
|
self._async_configure_linked_sensors(ent_reg_ent, device_lookup, state)
|
||||||
ent_reg_ent, device_lookup, state
|
|
||||||
)
|
|
||||||
|
|
||||||
bridged_states.append(state)
|
bridged_states.append(state)
|
||||||
|
|
||||||
|
@ -629,9 +635,7 @@ class HomeKit:
|
||||||
self.hass.add_job(self.driver.stop)
|
self.hass.add_job(self.driver.stop)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_configure_linked_battery_sensors(
|
def _async_configure_linked_sensors(self, ent_reg_ent, device_lookup, state):
|
||||||
self, ent_reg_ent, device_lookup, state
|
|
||||||
):
|
|
||||||
if (
|
if (
|
||||||
ent_reg_ent is None
|
ent_reg_ent is None
|
||||||
or ent_reg_ent.device_id is None
|
or ent_reg_ent.device_id is None
|
||||||
|
@ -644,7 +648,7 @@ class HomeKit:
|
||||||
if ATTR_BATTERY_CHARGING not in state.attributes:
|
if ATTR_BATTERY_CHARGING not in state.attributes:
|
||||||
battery_charging_binary_sensor_entity_id = device_lookup[
|
battery_charging_binary_sensor_entity_id = device_lookup[
|
||||||
ent_reg_ent.device_id
|
ent_reg_ent.device_id
|
||||||
].get(("binary_sensor", DEVICE_CLASS_BATTERY_CHARGING))
|
].get((BINARY_SENSOR_DOMAIN, DEVICE_CLASS_BATTERY_CHARGING))
|
||||||
if battery_charging_binary_sensor_entity_id:
|
if battery_charging_binary_sensor_entity_id:
|
||||||
self._config.setdefault(state.entity_id, {}).setdefault(
|
self._config.setdefault(state.entity_id, {}).setdefault(
|
||||||
CONF_LINKED_BATTERY_CHARGING_SENSOR,
|
CONF_LINKED_BATTERY_CHARGING_SENSOR,
|
||||||
|
@ -653,13 +657,22 @@ class HomeKit:
|
||||||
|
|
||||||
if ATTR_BATTERY_LEVEL not in state.attributes:
|
if ATTR_BATTERY_LEVEL not in state.attributes:
|
||||||
battery_sensor_entity_id = device_lookup[ent_reg_ent.device_id].get(
|
battery_sensor_entity_id = device_lookup[ent_reg_ent.device_id].get(
|
||||||
("sensor", DEVICE_CLASS_BATTERY)
|
(SENSOR_DOMAIN, DEVICE_CLASS_BATTERY)
|
||||||
)
|
)
|
||||||
if battery_sensor_entity_id:
|
if battery_sensor_entity_id:
|
||||||
self._config.setdefault(state.entity_id, {}).setdefault(
|
self._config.setdefault(state.entity_id, {}).setdefault(
|
||||||
CONF_LINKED_BATTERY_SENSOR, battery_sensor_entity_id
|
CONF_LINKED_BATTERY_SENSOR, battery_sensor_entity_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if state.entity_id.startswith(f"{CAMERA_DOMAIN}."):
|
||||||
|
motion_binary_sensor_entity_id = device_lookup[ent_reg_ent.device_id].get(
|
||||||
|
(BINARY_SENSOR_DOMAIN, DEVICE_CLASS_MOTION)
|
||||||
|
)
|
||||||
|
if motion_binary_sensor_entity_id:
|
||||||
|
self._config.setdefault(state.entity_id, {}).setdefault(
|
||||||
|
CONF_LINKED_MOTION_SENSOR, motion_binary_sensor_entity_id,
|
||||||
|
)
|
||||||
|
|
||||||
async def _async_set_device_info_attributes(self, ent_reg_ent, dev_reg, entity_id):
|
async def _async_set_device_info_attributes(self, ent_reg_ent, dev_reg, entity_id):
|
||||||
"""Set attributes that will be used for homekit device info."""
|
"""Set attributes that will be used for homekit device info."""
|
||||||
ent_cfg = self._config.setdefault(entity_id, {})
|
ent_cfg = self._config.setdefault(entity_id, {})
|
||||||
|
|
|
@ -41,6 +41,7 @@ CONF_FEATURE_LIST = "feature_list"
|
||||||
CONF_FILTER = "filter"
|
CONF_FILTER = "filter"
|
||||||
CONF_LINKED_BATTERY_SENSOR = "linked_battery_sensor"
|
CONF_LINKED_BATTERY_SENSOR = "linked_battery_sensor"
|
||||||
CONF_LINKED_BATTERY_CHARGING_SENSOR = "linked_battery_charging_sensor"
|
CONF_LINKED_BATTERY_CHARGING_SENSOR = "linked_battery_charging_sensor"
|
||||||
|
CONF_LINKED_MOTION_SENSOR = "linked_motion_sensor"
|
||||||
CONF_LOW_BATTERY_THRESHOLD = "low_battery_threshold"
|
CONF_LOW_BATTERY_THRESHOLD = "low_battery_threshold"
|
||||||
CONF_MAX_FPS = "max_fps"
|
CONF_MAX_FPS = "max_fps"
|
||||||
CONF_MAX_HEIGHT = "max_height"
|
CONF_MAX_HEIGHT = "max_height"
|
||||||
|
|
|
@ -13,16 +13,22 @@ from pyhap.camera import (
|
||||||
from pyhap.const import CATEGORY_CAMERA
|
from pyhap.const import CATEGORY_CAMERA
|
||||||
|
|
||||||
from homeassistant.components.ffmpeg import DATA_FFMPEG
|
from homeassistant.components.ffmpeg import DATA_FFMPEG
|
||||||
|
from homeassistant.const import STATE_ON
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.event import async_track_time_interval
|
from homeassistant.helpers.event import (
|
||||||
|
async_track_state_change,
|
||||||
|
async_track_time_interval,
|
||||||
|
)
|
||||||
from homeassistant.util import get_local_ip
|
from homeassistant.util import get_local_ip
|
||||||
|
|
||||||
from .accessories import TYPES, HomeAccessory
|
from .accessories import TYPES, HomeAccessory
|
||||||
from .const import (
|
from .const import (
|
||||||
|
CHAR_MOTION_DETECTED,
|
||||||
CHAR_STREAMING_STRATUS,
|
CHAR_STREAMING_STRATUS,
|
||||||
CONF_AUDIO_CODEC,
|
CONF_AUDIO_CODEC,
|
||||||
CONF_AUDIO_MAP,
|
CONF_AUDIO_MAP,
|
||||||
CONF_AUDIO_PACKET_SIZE,
|
CONF_AUDIO_PACKET_SIZE,
|
||||||
|
CONF_LINKED_MOTION_SENSOR,
|
||||||
CONF_MAX_FPS,
|
CONF_MAX_FPS,
|
||||||
CONF_MAX_HEIGHT,
|
CONF_MAX_HEIGHT,
|
||||||
CONF_MAX_WIDTH,
|
CONF_MAX_WIDTH,
|
||||||
|
@ -43,6 +49,7 @@ from .const import (
|
||||||
DEFAULT_VIDEO_MAP,
|
DEFAULT_VIDEO_MAP,
|
||||||
DEFAULT_VIDEO_PACKET_SIZE,
|
DEFAULT_VIDEO_PACKET_SIZE,
|
||||||
SERV_CAMERA_RTP_STREAM_MANAGEMENT,
|
SERV_CAMERA_RTP_STREAM_MANAGEMENT,
|
||||||
|
SERV_MOTION_SENSOR,
|
||||||
)
|
)
|
||||||
from .img_util import scale_jpeg_camera_image
|
from .img_util import scale_jpeg_camera_image
|
||||||
from .util import pid_is_alive
|
from .util import pid_is_alive
|
||||||
|
@ -178,6 +185,47 @@ class Camera(HomeAccessory, PyhapCamera):
|
||||||
category=CATEGORY_CAMERA,
|
category=CATEGORY_CAMERA,
|
||||||
options=options,
|
options=options,
|
||||||
)
|
)
|
||||||
|
self._char_motion_detected = None
|
||||||
|
self.linked_motion_sensor = self.config.get(CONF_LINKED_MOTION_SENSOR)
|
||||||
|
if not self.linked_motion_sensor:
|
||||||
|
return
|
||||||
|
state = self.hass.states.get(self.linked_motion_sensor)
|
||||||
|
if not state:
|
||||||
|
return
|
||||||
|
serv_motion = self.add_preload_service(SERV_MOTION_SENSOR)
|
||||||
|
self._char_motion_detected = serv_motion.configure_char(
|
||||||
|
CHAR_MOTION_DETECTED, value=False
|
||||||
|
)
|
||||||
|
self._async_update_motion_state(None, None, state)
|
||||||
|
|
||||||
|
async def run_handler(self):
|
||||||
|
"""Handle accessory driver started event.
|
||||||
|
|
||||||
|
Run inside the Home Assistant event loop.
|
||||||
|
"""
|
||||||
|
if self._char_motion_detected:
|
||||||
|
async_track_state_change(
|
||||||
|
self.hass, self.linked_motion_sensor, self._async_update_motion_state
|
||||||
|
)
|
||||||
|
|
||||||
|
await super().run_handler()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_update_motion_state(
|
||||||
|
self, entity_id=None, old_state=None, new_state=None
|
||||||
|
):
|
||||||
|
"""Handle link motion sensor state change to update HomeKit value."""
|
||||||
|
detected = new_state.state == STATE_ON
|
||||||
|
if self._char_motion_detected.value == detected:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._char_motion_detected.set_value(detected)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s: Set linked motion %s sensor to %d",
|
||||||
|
self.entity_id,
|
||||||
|
self.linked_motion_sensor,
|
||||||
|
detected,
|
||||||
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_state(self, new_state):
|
def async_update_state(self, new_state):
|
||||||
|
|
|
@ -11,7 +11,7 @@ import socket
|
||||||
import pyqrcode
|
import pyqrcode
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components import fan, media_player, sensor
|
from homeassistant.components import binary_sensor, fan, media_player, sensor
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_CODE,
|
ATTR_CODE,
|
||||||
ATTR_SUPPORTED_FEATURES,
|
ATTR_SUPPORTED_FEATURES,
|
||||||
|
@ -32,7 +32,9 @@ from .const import (
|
||||||
CONF_AUDIO_PACKET_SIZE,
|
CONF_AUDIO_PACKET_SIZE,
|
||||||
CONF_FEATURE,
|
CONF_FEATURE,
|
||||||
CONF_FEATURE_LIST,
|
CONF_FEATURE_LIST,
|
||||||
|
CONF_LINKED_BATTERY_CHARGING_SENSOR,
|
||||||
CONF_LINKED_BATTERY_SENSOR,
|
CONF_LINKED_BATTERY_SENSOR,
|
||||||
|
CONF_LINKED_MOTION_SENSOR,
|
||||||
CONF_LOW_BATTERY_THRESHOLD,
|
CONF_LOW_BATTERY_THRESHOLD,
|
||||||
CONF_MAX_FPS,
|
CONF_MAX_FPS,
|
||||||
CONF_MAX_HEIGHT,
|
CONF_MAX_HEIGHT,
|
||||||
|
@ -83,6 +85,9 @@ BASIC_INFO_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_NAME): cv.string,
|
vol.Optional(CONF_NAME): cv.string,
|
||||||
vol.Optional(CONF_LINKED_BATTERY_SENSOR): cv.entity_domain(sensor.DOMAIN),
|
vol.Optional(CONF_LINKED_BATTERY_SENSOR): cv.entity_domain(sensor.DOMAIN),
|
||||||
|
vol.Optional(CONF_LINKED_BATTERY_CHARGING_SENSOR): cv.entity_domain(
|
||||||
|
binary_sensor.DOMAIN
|
||||||
|
),
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
CONF_LOW_BATTERY_THRESHOLD, default=DEFAULT_LOW_BATTERY_THRESHOLD
|
CONF_LOW_BATTERY_THRESHOLD, default=DEFAULT_LOW_BATTERY_THRESHOLD
|
||||||
): cv.positive_int,
|
): cv.positive_int,
|
||||||
|
@ -115,6 +120,7 @@ CAMERA_SCHEMA = BASIC_INFO_SCHEMA.extend(
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
CONF_VIDEO_PACKET_SIZE, default=DEFAULT_VIDEO_PACKET_SIZE
|
CONF_VIDEO_PACKET_SIZE, default=DEFAULT_VIDEO_PACKET_SIZE
|
||||||
): cv.positive_int,
|
): cv.positive_int,
|
||||||
|
vol.Optional(CONF_LINKED_MOTION_SENSOR): cv.entity_domain(binary_sensor.DOMAIN),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,10 @@ import pytest
|
||||||
from zeroconf import InterfaceChoice
|
from zeroconf import InterfaceChoice
|
||||||
|
|
||||||
from homeassistant.components import zeroconf
|
from homeassistant.components import zeroconf
|
||||||
from homeassistant.components.binary_sensor import DEVICE_CLASS_BATTERY_CHARGING
|
from homeassistant.components.binary_sensor import (
|
||||||
|
DEVICE_CLASS_BATTERY_CHARGING,
|
||||||
|
DEVICE_CLASS_MOTION,
|
||||||
|
)
|
||||||
from homeassistant.components.homekit import (
|
from homeassistant.components.homekit import (
|
||||||
MAX_DEVICES,
|
MAX_DEVICES,
|
||||||
STATUS_READY,
|
STATUS_READY,
|
||||||
|
@ -1032,3 +1035,78 @@ async def test_homekit_ignored_missing_devices(
|
||||||
"linked_battery_sensor": "sensor.powerwall_battery",
|
"linked_battery_sensor": "sensor.powerwall_battery",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_homekit_finds_linked_motion_sensors(
|
||||||
|
hass, hk_driver, debounce_patcher, device_reg, entity_reg
|
||||||
|
):
|
||||||
|
"""Test HomeKit start method."""
|
||||||
|
entry = await async_init_integration(hass)
|
||||||
|
|
||||||
|
homekit = HomeKit(
|
||||||
|
hass,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
{},
|
||||||
|
{"camera.camera_demo": {}},
|
||||||
|
DEFAULT_SAFE_MODE,
|
||||||
|
advertise_ip=None,
|
||||||
|
interface_choice=None,
|
||||||
|
entry_id=entry.entry_id,
|
||||||
|
)
|
||||||
|
homekit.driver = hk_driver
|
||||||
|
homekit._filter = Mock(return_value=True)
|
||||||
|
homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge")
|
||||||
|
|
||||||
|
config_entry = MockConfigEntry(domain="test", data={})
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
device_entry = device_reg.async_get_or_create(
|
||||||
|
config_entry_id=config_entry.entry_id,
|
||||||
|
sw_version="0.16.0",
|
||||||
|
model="Camera Server",
|
||||||
|
manufacturer="Ubq",
|
||||||
|
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||||
|
)
|
||||||
|
|
||||||
|
binary_motion_sensor = entity_reg.async_get_or_create(
|
||||||
|
"binary_sensor",
|
||||||
|
"camera",
|
||||||
|
"motion_sensor",
|
||||||
|
device_id=device_entry.id,
|
||||||
|
device_class=DEVICE_CLASS_MOTION,
|
||||||
|
)
|
||||||
|
camera = entity_reg.async_get_or_create(
|
||||||
|
"camera", "camera", "demo", device_id=device_entry.id
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.states.async_set(
|
||||||
|
binary_motion_sensor.entity_id,
|
||||||
|
STATE_ON,
|
||||||
|
{ATTR_DEVICE_CLASS: DEVICE_CLASS_MOTION},
|
||||||
|
)
|
||||||
|
hass.states.async_set(camera.entity_id, STATE_ON)
|
||||||
|
|
||||||
|
def _mock_get_accessory(*args, **kwargs):
|
||||||
|
return [None, "acc", None]
|
||||||
|
|
||||||
|
with patch.object(homekit.bridge, "add_accessory"), patch(
|
||||||
|
f"{PATH_HOMEKIT}.show_setup_message"
|
||||||
|
), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch(
|
||||||
|
"pyhap.accessory_driver.AccessoryDriver.start"
|
||||||
|
):
|
||||||
|
await homekit.async_start()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
mock_get_acc.assert_called_with(
|
||||||
|
hass,
|
||||||
|
hk_driver,
|
||||||
|
ANY,
|
||||||
|
ANY,
|
||||||
|
{
|
||||||
|
"manufacturer": "Ubq",
|
||||||
|
"model": "Camera Server",
|
||||||
|
"sw_version": "0.16.0",
|
||||||
|
"linked_motion_sensor": "binary_sensor.camera_motion_sensor",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
|
@ -9,16 +9,21 @@ from homeassistant.components import camera, ffmpeg
|
||||||
from homeassistant.components.homekit.accessories import HomeBridge
|
from homeassistant.components.homekit.accessories import HomeBridge
|
||||||
from homeassistant.components.homekit.const import (
|
from homeassistant.components.homekit.const import (
|
||||||
AUDIO_CODEC_COPY,
|
AUDIO_CODEC_COPY,
|
||||||
|
CHAR_MOTION_DETECTED,
|
||||||
CONF_AUDIO_CODEC,
|
CONF_AUDIO_CODEC,
|
||||||
|
CONF_LINKED_MOTION_SENSOR,
|
||||||
CONF_STREAM_SOURCE,
|
CONF_STREAM_SOURCE,
|
||||||
CONF_SUPPORT_AUDIO,
|
CONF_SUPPORT_AUDIO,
|
||||||
CONF_VIDEO_CODEC,
|
CONF_VIDEO_CODEC,
|
||||||
|
DEVICE_CLASS_MOTION,
|
||||||
|
SERV_MOTION_SENSOR,
|
||||||
VIDEO_CODEC_COPY,
|
VIDEO_CODEC_COPY,
|
||||||
VIDEO_CODEC_H264_OMX,
|
VIDEO_CODEC_H264_OMX,
|
||||||
)
|
)
|
||||||
from homeassistant.components.homekit.img_util import TurboJPEGSingleton
|
from homeassistant.components.homekit.img_util import TurboJPEGSingleton
|
||||||
from homeassistant.components.homekit.type_cameras import Camera
|
from homeassistant.components.homekit.type_cameras import Camera
|
||||||
from homeassistant.components.homekit.type_switches import Switch
|
from homeassistant.components.homekit.type_switches import Switch
|
||||||
|
from homeassistant.const import ATTR_DEVICE_CLASS, STATE_OFF, STATE_ON
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
@ -492,3 +497,91 @@ async def test_camera_streaming_fails_after_starting_ffmpeg(hass, run_driver, ev
|
||||||
output=expected_output.format(**session_info),
|
output=expected_output.format(**session_info),
|
||||||
stdout_pipe=False,
|
stdout_pipe=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_camera_with_linked_motion_sensor(hass, run_driver, events):
|
||||||
|
"""Test a camera with a linked motion sensor can update."""
|
||||||
|
await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}})
|
||||||
|
await async_setup_component(
|
||||||
|
hass, camera.DOMAIN, {camera.DOMAIN: {"platform": "demo"}}
|
||||||
|
)
|
||||||
|
motion_entity_id = "binary_sensor.motion"
|
||||||
|
|
||||||
|
hass.states.async_set(
|
||||||
|
motion_entity_id, STATE_ON, {ATTR_DEVICE_CLASS: DEVICE_CLASS_MOTION}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
entity_id = "camera.demo_camera"
|
||||||
|
|
||||||
|
hass.states.async_set(entity_id, None)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
acc = Camera(
|
||||||
|
hass,
|
||||||
|
run_driver,
|
||||||
|
"Camera",
|
||||||
|
entity_id,
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
CONF_STREAM_SOURCE: "/dev/null",
|
||||||
|
CONF_SUPPORT_AUDIO: True,
|
||||||
|
CONF_VIDEO_CODEC: VIDEO_CODEC_H264_OMX,
|
||||||
|
CONF_AUDIO_CODEC: AUDIO_CODEC_COPY,
|
||||||
|
CONF_LINKED_MOTION_SENSOR: motion_entity_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
bridge = HomeBridge("hass", run_driver, "Test Bridge")
|
||||||
|
bridge.add_accessory(acc)
|
||||||
|
|
||||||
|
await acc.run_handler()
|
||||||
|
|
||||||
|
assert acc.aid == 2
|
||||||
|
assert acc.category == 17 # Camera
|
||||||
|
|
||||||
|
service = acc.get_service(SERV_MOTION_SENSOR)
|
||||||
|
assert service
|
||||||
|
char = service.get_characteristic(CHAR_MOTION_DETECTED)
|
||||||
|
assert char
|
||||||
|
|
||||||
|
assert char.value is True
|
||||||
|
|
||||||
|
hass.states.async_set(
|
||||||
|
motion_entity_id, STATE_OFF, {ATTR_DEVICE_CLASS: DEVICE_CLASS_MOTION}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert char.value is False
|
||||||
|
|
||||||
|
char.set_value(True)
|
||||||
|
hass.states.async_set(
|
||||||
|
motion_entity_id, STATE_ON, {ATTR_DEVICE_CLASS: DEVICE_CLASS_MOTION}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert char.value is True
|
||||||
|
|
||||||
|
|
||||||
|
async def test_camera_with_a_missing_linked_motion_sensor(hass, run_driver, events):
|
||||||
|
"""Test a camera with a configured linked motion sensor that is missing."""
|
||||||
|
await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}})
|
||||||
|
await async_setup_component(
|
||||||
|
hass, camera.DOMAIN, {camera.DOMAIN: {"platform": "demo"}}
|
||||||
|
)
|
||||||
|
motion_entity_id = "binary_sensor.motion"
|
||||||
|
entity_id = "camera.demo_camera"
|
||||||
|
hass.states.async_set(entity_id, None)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
acc = Camera(
|
||||||
|
hass,
|
||||||
|
run_driver,
|
||||||
|
"Camera",
|
||||||
|
entity_id,
|
||||||
|
2,
|
||||||
|
{CONF_LINKED_MOTION_SENSOR: motion_entity_id},
|
||||||
|
)
|
||||||
|
bridge = HomeBridge("hass", run_driver, "Test Bridge")
|
||||||
|
bridge.add_accessory(acc)
|
||||||
|
|
||||||
|
await acc.run_handler()
|
||||||
|
|
||||||
|
assert acc.aid == 2
|
||||||
|
assert acc.category == 17 # Camera
|
||||||
|
|
||||||
|
assert not acc.get_service(SERV_MOTION_SENSOR)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue