mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 09:17:53 +00:00
Add agent_dvr integration (#32711)
* initial * add missing fixture * fix mocks * fix mocks 2 * update coverage * fix broken sync between agent and integration * Update homeassistant/components/agent_dvr/camera.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/agent_dvr/camera.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/agent_dvr/camera.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/agent_dvr/camera.py Co-authored-by: J. Nick Koston <nick@koston.org> * updates for review * add back in should poll again * revert motion detection enabled flag in state attributes * Update homeassistant/components/agent_dvr/camera.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/agent_dvr/camera.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/agent_dvr/camera.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/agent_dvr/camera.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/agent_dvr/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/agent_dvr/camera.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/agent_dvr/camera.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/agent_dvr/camera.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * add is_streaming * fix is_streaming bug, remove mp4 stream * cleanup Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
3a0d5126ae
commit
1be41b9de8
@ -16,6 +16,10 @@ omit =
|
||||
homeassistant/components/adguard/switch.py
|
||||
homeassistant/components/ads/*
|
||||
homeassistant/components/aftership/sensor.py
|
||||
homeassistant/components/agent_dvr/__init__.py
|
||||
homeassistant/components/agent_dvr/camera.py
|
||||
homeassistant/components/agent_dvr/const.py
|
||||
homeassistant/components/agent_dvr/helpers.py
|
||||
homeassistant/components/airly/__init__.py
|
||||
homeassistant/components/airly/air_quality.py
|
||||
homeassistant/components/airly/sensor.py
|
||||
|
@ -15,6 +15,7 @@ homeassistant/scripts/check_config.py @kellerza
|
||||
# Integrations
|
||||
homeassistant/components/abode/* @shred86
|
||||
homeassistant/components/adguard/* @frenck
|
||||
homeassistant/components/agent_dvr/* @ispysoftware
|
||||
homeassistant/components/airly/* @bieniu
|
||||
homeassistant/components/airvisual/* @bachya
|
||||
homeassistant/components/alarmdecoder/* @ajschmidt8
|
||||
|
82
homeassistant/components/agent_dvr/__init__.py
Normal file
82
homeassistant/components/agent_dvr/__init__.py
Normal file
@ -0,0 +1,82 @@
|
||||
"""Support for Agent."""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from agent import AgentError
|
||||
from agent.a import Agent
|
||||
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import CONNECTION, DOMAIN as AGENT_DOMAIN, SERVER_URL
|
||||
|
||||
ATTRIBUTION = "ispyconnect.com"
|
||||
DEFAULT_BRAND = "Agent DVR by ispyconnect.com"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
FORWARDS = ["camera"]
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Old way to set up integrations."""
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry):
|
||||
"""Set up the Agent component."""
|
||||
hass.data.setdefault(AGENT_DOMAIN, {})
|
||||
|
||||
server_origin = config_entry.data[SERVER_URL]
|
||||
|
||||
agent_client = Agent(server_origin, async_get_clientsession(hass))
|
||||
try:
|
||||
await agent_client.update()
|
||||
except AgentError:
|
||||
await agent_client.close()
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
if not agent_client.is_available:
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
await agent_client.get_devices()
|
||||
|
||||
hass.data[AGENT_DOMAIN][config_entry.entry_id] = {CONNECTION: agent_client}
|
||||
|
||||
device_registry = await dr.async_get_registry(hass)
|
||||
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
identifiers={(AGENT_DOMAIN, agent_client.unique)},
|
||||
manufacturer="iSpyConnect",
|
||||
name=f"Agent {agent_client.name}",
|
||||
model="Agent DVR",
|
||||
sw_version=agent_client.version,
|
||||
)
|
||||
|
||||
for forward in FORWARDS:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(config_entry, forward)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass, config_entry):
|
||||
"""Unload a config entry."""
|
||||
unload_ok = all(
|
||||
await asyncio.gather(
|
||||
*[
|
||||
hass.config_entries.async_forward_entry_unload(config_entry, forward)
|
||||
for forward in FORWARDS
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
await hass.data[AGENT_DOMAIN][config_entry.entry_id][CONNECTION].close()
|
||||
|
||||
if unload_ok:
|
||||
hass.data[AGENT_DOMAIN].pop(config_entry.entry_id)
|
||||
|
||||
return unload_ok
|
215
homeassistant/components/agent_dvr/camera.py
Normal file
215
homeassistant/components/agent_dvr/camera.py
Normal file
@ -0,0 +1,215 @@
|
||||
"""Support for Agent camera streaming."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from agent import AgentError
|
||||
|
||||
from homeassistant.components.camera import SUPPORT_ON_OFF
|
||||
from homeassistant.components.mjpeg.camera import (
|
||||
CONF_MJPEG_URL,
|
||||
CONF_STILL_IMAGE_URL,
|
||||
MjpegCamera,
|
||||
filter_urllib3_logging,
|
||||
)
|
||||
from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME
|
||||
from homeassistant.helpers import entity_platform
|
||||
|
||||
from .const import (
|
||||
ATTRIBUTION,
|
||||
CAMERA_SCAN_INTERVAL_SECS,
|
||||
CONNECTION,
|
||||
DOMAIN as AGENT_DOMAIN,
|
||||
)
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=CAMERA_SCAN_INTERVAL_SECS)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_DEV_EN_ALT = "enable_alerts"
|
||||
_DEV_DS_ALT = "disable_alerts"
|
||||
_DEV_EN_REC = "start_recording"
|
||||
_DEV_DS_REC = "stop_recording"
|
||||
_DEV_SNAP = "snapshot"
|
||||
|
||||
CAMERA_SERVICES = {
|
||||
_DEV_EN_ALT: "async_enable_alerts",
|
||||
_DEV_DS_ALT: "async_disable_alerts",
|
||||
_DEV_EN_REC: "async_start_recording",
|
||||
_DEV_DS_REC: "async_stop_recording",
|
||||
_DEV_SNAP: "async_snapshot",
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass, config_entry, async_add_entities, discovery_info=None
|
||||
):
|
||||
"""Set up the Agent cameras."""
|
||||
filter_urllib3_logging()
|
||||
cameras = []
|
||||
|
||||
server = hass.data[AGENT_DOMAIN][config_entry.entry_id][CONNECTION]
|
||||
if not server.devices:
|
||||
_LOGGER.warning("Could not fetch cameras from Agent server")
|
||||
return
|
||||
|
||||
for device in server.devices:
|
||||
if device.typeID == 2:
|
||||
camera = AgentCamera(device)
|
||||
cameras.append(camera)
|
||||
|
||||
async_add_entities(cameras)
|
||||
|
||||
platform = entity_platform.current_platform.get()
|
||||
for service, method in CAMERA_SERVICES.items():
|
||||
platform.async_register_entity_service(service, {}, method)
|
||||
|
||||
|
||||
class AgentCamera(MjpegCamera):
|
||||
"""Representation of an Agent Device Stream."""
|
||||
|
||||
def __init__(self, device):
|
||||
"""Initialize as a subclass of MjpegCamera."""
|
||||
self._servername = device.client.name
|
||||
self.server_url = device.client._server_url
|
||||
|
||||
device_info = {
|
||||
CONF_NAME: device.name,
|
||||
CONF_MJPEG_URL: f"{self.server_url}{device.mjpeg_image_url}&size=640x480",
|
||||
CONF_STILL_IMAGE_URL: f"{self.server_url}{device.still_image_url}&size=640x480",
|
||||
}
|
||||
self.device = device
|
||||
self._removed = False
|
||||
self._name = f"{self._servername} {device.name}"
|
||||
self._unique_id = f"{device._client.unique}_{device.typeID}_{device.id}"
|
||||
super().__init__(device_info)
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device info for adding the entity to the agent object."""
|
||||
return {
|
||||
"identifiers": {(AGENT_DOMAIN, self._unique_id)},
|
||||
"name": self._name,
|
||||
"manufacturer": "Agent",
|
||||
"model": "Camera",
|
||||
"sw_version": self.device.client.version,
|
||||
}
|
||||
|
||||
async def async_update(self):
|
||||
"""Update our state from the Agent API."""
|
||||
try:
|
||||
await self.device.update()
|
||||
if self._removed:
|
||||
_LOGGER.debug("%s reacquired", self._name)
|
||||
self._removed = False
|
||||
except AgentError:
|
||||
if self.device.client.is_available: # server still available - camera error
|
||||
if not self._removed:
|
||||
_LOGGER.error("%s lost", self._name)
|
||||
self._removed = True
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the Agent DVR camera state attributes."""
|
||||
return {
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
"editable": False,
|
||||
"enabled": self.is_on,
|
||||
"connected": self.connected,
|
||||
"detected": self.is_detected,
|
||||
"alerted": self.is_alerted,
|
||||
"has_ptz": self.device.has_ptz,
|
||||
"alerts_enabled": self.device.alerts_active,
|
||||
}
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Update the state periodically."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def is_recording(self) -> bool:
|
||||
"""Return whether the monitor is recording."""
|
||||
return self.device.recording
|
||||
|
||||
@property
|
||||
def is_alerted(self) -> bool:
|
||||
"""Return whether the monitor has alerted."""
|
||||
return self.device.alerted
|
||||
|
||||
@property
|
||||
def is_detected(self) -> bool:
|
||||
"""Return whether the monitor has alerted."""
|
||||
return self.device.detected
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return self.device.client.is_available
|
||||
|
||||
@property
|
||||
def connected(self) -> bool:
|
||||
"""Return True if entity is connected."""
|
||||
return self.device.connected
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Return supported features."""
|
||||
return SUPPORT_ON_OFF
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if on."""
|
||||
return self.device.online
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use in the frontend, if any."""
|
||||
if self.is_on:
|
||||
return "mdi:camcorder"
|
||||
return "mdi:camcorder-off"
|
||||
|
||||
@property
|
||||
def motion_detection_enabled(self):
|
||||
"""Return the camera motion detection status."""
|
||||
return self.device.detector_active
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique identifier for this agent object."""
|
||||
return self._unique_id
|
||||
|
||||
async def async_enable_alerts(self):
|
||||
"""Enable alerts."""
|
||||
await self.device.alerts_on()
|
||||
|
||||
async def async_disable_alerts(self):
|
||||
"""Disable alerts."""
|
||||
await self.device.alerts_off()
|
||||
|
||||
async def async_enable_motion_detection(self):
|
||||
"""Enable motion detection."""
|
||||
await self.device.detector_on()
|
||||
|
||||
async def async_disable_motion_detection(self):
|
||||
"""Disable motion detection."""
|
||||
await self.device.detector_off()
|
||||
|
||||
async def async_start_recording(self):
|
||||
"""Start recording."""
|
||||
await self.device.record()
|
||||
|
||||
async def async_stop_recording(self):
|
||||
"""Stop recording."""
|
||||
await self.device.record_stop()
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Enable the camera."""
|
||||
await self.device.enable()
|
||||
|
||||
async def async_snapshot(self):
|
||||
"""Take a snapshot."""
|
||||
await self.device.snapshot()
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Disable the camera."""
|
||||
await self.device.disable()
|
81
homeassistant/components/agent_dvr/config_flow.py
Normal file
81
homeassistant/components/agent_dvr/config_flow.py
Normal file
@ -0,0 +1,81 @@
|
||||
"""Config flow to configure Agent devices."""
|
||||
import logging
|
||||
|
||||
from agent import AgentConnectionError, AgentError
|
||||
from agent.a import Agent
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN, SERVER_URL # pylint:disable=unused-import
|
||||
from .helpers import generate_url
|
||||
|
||||
DEFAULT_PORT = 8090
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AgentFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle an Agent config flow."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the Agent config flow."""
|
||||
self.device_config = {}
|
||||
|
||||
async def async_step_user(self, info=None):
|
||||
"""Handle an Agent config flow."""
|
||||
errors = {}
|
||||
|
||||
if info is not None:
|
||||
host = info[CONF_HOST]
|
||||
port = info[CONF_PORT]
|
||||
|
||||
server_origin = generate_url(host, port)
|
||||
agent_client = Agent(server_origin, async_get_clientsession(self.hass))
|
||||
|
||||
try:
|
||||
await agent_client.update()
|
||||
except AgentConnectionError:
|
||||
pass
|
||||
except AgentError:
|
||||
pass
|
||||
|
||||
await agent_client.close()
|
||||
|
||||
if agent_client.is_available:
|
||||
await self.async_set_unique_id(agent_client.unique)
|
||||
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={
|
||||
CONF_HOST: info[CONF_HOST],
|
||||
CONF_PORT: info[CONF_PORT],
|
||||
SERVER_URL: server_origin,
|
||||
}
|
||||
)
|
||||
|
||||
self.device_config = {
|
||||
CONF_HOST: host,
|
||||
CONF_PORT: port,
|
||||
SERVER_URL: server_origin,
|
||||
}
|
||||
|
||||
return await self._create_entry(agent_client.name)
|
||||
|
||||
errors["base"] = "device_unavailable"
|
||||
|
||||
data = {
|
||||
vol.Required(CONF_HOST): str,
|
||||
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
|
||||
}
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
description_placeholders=self.device_config,
|
||||
data_schema=vol.Schema(data),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def _create_entry(self, server_name):
|
||||
"""Create entry for device."""
|
||||
return self.async_create_entry(title=server_name, data=self.device_config)
|
11
homeassistant/components/agent_dvr/const.py
Normal file
11
homeassistant/components/agent_dvr/const.py
Normal file
@ -0,0 +1,11 @@
|
||||
"""Constants for agent_dvr component."""
|
||||
DOMAIN = "agent_dvr"
|
||||
SERVERS = "servers"
|
||||
DEVICES = "devices"
|
||||
ENTITIES = "entities"
|
||||
CAMERA_SCAN_INTERVAL_SECS = 5
|
||||
SERVICE_UPDATE = "update"
|
||||
SIGNAL_UPDATE_AGENT = "agent_update"
|
||||
ATTRIBUTION = "Data provided by ispyconnect.com"
|
||||
SERVER_URL = "server_url"
|
||||
CONNECTION = "connection"
|
13
homeassistant/components/agent_dvr/helpers.py
Normal file
13
homeassistant/components/agent_dvr/helpers.py
Normal file
@ -0,0 +1,13 @@
|
||||
"""Helpers for Agent DVR component."""
|
||||
|
||||
|
||||
def generate_url(host, port) -> str:
|
||||
"""Create a URL from the host and port."""
|
||||
server_origin = host
|
||||
if "://" not in host:
|
||||
server_origin = f"http://{host}"
|
||||
|
||||
if server_origin[-1] == "/":
|
||||
server_origin = server_origin[:-1]
|
||||
|
||||
return f"{server_origin}:{port}/"
|
8
homeassistant/components/agent_dvr/manifest.json
Normal file
8
homeassistant/components/agent_dvr/manifest.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"domain": "agent_dvr",
|
||||
"name": "Agent DVR",
|
||||
"documentation": "https://www.home-assistant.io/integrations/agent_dvr/",
|
||||
"requirements": ["agent-py==0.0.20"],
|
||||
"config_flow": true,
|
||||
"codeowners": ["@ispysoftware"]
|
||||
}
|
34
homeassistant/components/agent_dvr/services.yaml
Normal file
34
homeassistant/components/agent_dvr/services.yaml
Normal file
@ -0,0 +1,34 @@
|
||||
start_recording:
|
||||
description: Enable continuous recording.
|
||||
fields:
|
||||
entity_id:
|
||||
description: "Name(s) of the entity to start recording."
|
||||
example: "camera.camera_1"
|
||||
|
||||
stop_recording:
|
||||
description: Disable continuous recording.
|
||||
fields:
|
||||
entity_id:
|
||||
description: "Name(s) of the entity to stop recording."
|
||||
example: "camera.camera_1"
|
||||
|
||||
enable_alerts:
|
||||
description: Enable alerts
|
||||
fields:
|
||||
entity_id:
|
||||
description: "Name(s) of the entity to enable alerts."
|
||||
example: "camera.camera_1"
|
||||
|
||||
disable_alerts:
|
||||
description: Disable alerts
|
||||
fields:
|
||||
entity_id:
|
||||
description: "Name(s) of the entity to disable alerts."
|
||||
example: "camera.camera_1"
|
||||
|
||||
snapshot:
|
||||
description: Take a photo
|
||||
fields:
|
||||
entity_id:
|
||||
description: "Name(s) of the entity to take a snapshot."
|
||||
example: "camera.camera_1"
|
21
homeassistant/components/agent_dvr/strings.json
Normal file
21
homeassistant/components/agent_dvr/strings.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"title": "Agent DVR",
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Set up Agent DVR",
|
||||
"data": {
|
||||
"host": "Host",
|
||||
"port": "Port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "Device is already configured"
|
||||
},
|
||||
"error": {
|
||||
"already_in_progress": "Config flow for device is already in progress.",
|
||||
"device_unavailable": "Device is not available"
|
||||
}
|
||||
}
|
||||
}
|
21
homeassistant/components/agent_dvr/translations/en.json
Normal file
21
homeassistant/components/agent_dvr/translations/en.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"title": "Agent DVR",
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Set up Agent DVR",
|
||||
"data": {
|
||||
"host": "Host",
|
||||
"port": "Port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "Device is already configured"
|
||||
},
|
||||
"error": {
|
||||
"already_in_progress": "Config flow for device is already in progress.",
|
||||
"device_unavailable": "Device is not available"
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ To update, run python3 -m script.hassfest
|
||||
FLOWS = [
|
||||
"abode",
|
||||
"adguard",
|
||||
"agent_dvr",
|
||||
"airly",
|
||||
"airvisual",
|
||||
"almond",
|
||||
|
@ -128,6 +128,9 @@ adguardhome==0.4.2
|
||||
# homeassistant.components.frontier_silicon
|
||||
afsapi==0.0.4
|
||||
|
||||
# homeassistant.components.agent_dvr
|
||||
agent-py==0.0.20
|
||||
|
||||
# homeassistant.components.geonetnz_quakes
|
||||
aio_geojson_geonetnz_quakes==0.12
|
||||
|
||||
|
@ -38,6 +38,9 @@ adb-shell==0.1.3
|
||||
# homeassistant.components.adguard
|
||||
adguardhome==0.4.2
|
||||
|
||||
# homeassistant.components.agent_dvr
|
||||
agent-py==0.0.20
|
||||
|
||||
# homeassistant.components.geonetnz_quakes
|
||||
aio_geojson_geonetnz_quakes==0.12
|
||||
|
||||
|
42
tests/components/agent_dvr/__init__.py
Normal file
42
tests/components/agent_dvr/__init__.py
Normal file
@ -0,0 +1,42 @@
|
||||
"""Tests for the agent_dvr component."""
|
||||
|
||||
from homeassistant.components.agent_dvr.const import DOMAIN, SERVER_URL
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry, load_fixture
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
|
||||
|
||||
async def init_integration(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, skip_setup: bool = False,
|
||||
) -> MockConfigEntry:
|
||||
"""Set up the Agent DVR integration in Home Assistant."""
|
||||
|
||||
aioclient_mock.get(
|
||||
"http://example.local:8090/command.cgi?cmd=getStatus",
|
||||
text=load_fixture("agent_dvr/status.json"),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
aioclient_mock.get(
|
||||
"http://example.local:8090/command.cgi?cmd=getObjects",
|
||||
text=load_fixture("agent_dvr/objects.json"),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id="c0715bba-c2d0-48ef-9e3e-bc81c9ea4447",
|
||||
data={
|
||||
CONF_HOST: "example.local",
|
||||
CONF_PORT: 8090,
|
||||
SERVER_URL: "http://example.local:8090/",
|
||||
},
|
||||
)
|
||||
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
if not skip_setup:
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return entry
|
90
tests/components/agent_dvr/test_config_flow.py
Normal file
90
tests/components/agent_dvr/test_config_flow.py
Normal file
@ -0,0 +1,90 @@
|
||||
"""Tests for the Agent DVR config flow."""
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components.agent_dvr import config_flow
|
||||
from homeassistant.components.agent_dvr.const import SERVER_URL
|
||||
from homeassistant.config_entries import SOURCE_USER
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import init_integration
|
||||
|
||||
from tests.common import load_fixture
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
|
||||
|
||||
async def test_show_user_form(hass: HomeAssistant) -> None:
|
||||
"""Test that the user set up form is served."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
config_flow.DOMAIN, context={"source": SOURCE_USER},
|
||||
)
|
||||
|
||||
assert result["step_id"] == "user"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
|
||||
|
||||
async def test_user_device_exists_abort(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
"""Test we abort flow if Agent device already configured."""
|
||||
await init_integration(hass, aioclient_mock)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
config_flow.DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
data={CONF_HOST: "example.local", CONF_PORT: 8090},
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
|
||||
|
||||
async def test_connection_error(hass: HomeAssistant, aioclient_mock) -> None:
|
||||
"""Test we show user form on Agent connection error."""
|
||||
|
||||
aioclient_mock.get("http://example.local:8090/command.cgi?cmd=getStatus", text="")
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
config_flow.DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
data={CONF_HOST: "example.local", CONF_PORT: 8090},
|
||||
)
|
||||
|
||||
assert result["errors"] == {"base": "device_unavailable"}
|
||||
assert result["step_id"] == "user"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
|
||||
|
||||
async def test_full_user_flow_implementation(
|
||||
hass: HomeAssistant, aioclient_mock
|
||||
) -> None:
|
||||
"""Test the full manual user flow from start to finish."""
|
||||
aioclient_mock.get(
|
||||
"http://example.local:8090/command.cgi?cmd=getStatus",
|
||||
text=load_fixture("agent_dvr/status.json"),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
|
||||
aioclient_mock.get(
|
||||
"http://example.local:8090/command.cgi?cmd=getObjects",
|
||||
text=load_fixture("agent_dvr/objects.json"),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
config_flow.DOMAIN, context={"source": SOURCE_USER},
|
||||
)
|
||||
|
||||
assert result["step_id"] == "user"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={CONF_HOST: "example.local", CONF_PORT: 8090}
|
||||
)
|
||||
|
||||
assert result["data"][CONF_HOST] == "example.local"
|
||||
assert result["data"][CONF_PORT] == 8090
|
||||
assert result["data"][SERVER_URL] == "http://example.local:8090/"
|
||||
assert result["title"] == "DESKTOP"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
|
||||
entries = hass.config_entries.async_entries(config_flow.DOMAIN)
|
||||
assert entries[0].unique_id == "c0715bba-c2d0-48ef-9e3e-bc81c9ea4447"
|
1
tests/fixtures/agent_dvr/objects.json
vendored
Normal file
1
tests/fixtures/agent_dvr/objects.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"settings":{"canUpdate":true,"supportsPlugins":true,"isArmed":true,"background":"255,255,255"},"directories":[{"ID":0,"dir":"D:\\Projects\\agent-service\\AgentService\\Media\\WebServerRoot\\Media\\"}],"locations":[],"objectList": [],"profiles": [{"name":"Home","active":true,"id":0},{"name":"Away","active":false,"id":1},{"name":"Night","active":false,"id":2}],"views":[{"name":"0","mode":"column","objects":[],"maxWidth":1266,"maxHeight":1222,"backColor":"#222222","id":1,"typeID":2,"focused":false},{"name":"1","mode":"grid","objects":[]},{"name":"2","mode":"grid","objects":[]},{"name":"3","mode":"grid","objects":[]},{"name":"4","mode":"grid","objects":[]},{"name":"5","mode":"grid","objects":[]},{"name":"6","mode":"grid","objects":[]},{"name":"7","mode":"grid","objects":[]},{"name":"8","mode":"grid","objects":[]}],"rtmpStreaming":false}
|
10
tests/fixtures/agent_dvr/status.json
vendored
Normal file
10
tests/fixtures/agent_dvr/status.json
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"armed": false,
|
||||
"devices": 4,
|
||||
"active": 4,
|
||||
"recording": 0,
|
||||
"remoteAccess": true,
|
||||
"unique": "c0715bba-c2d0-48ef-9e3e-bc81c9ea4447",
|
||||
"name": "DESKTOP",
|
||||
"version": "2.6.1.0"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user