Revert zoneminder config flow (#41395)

This commit is contained in:
Martin Hjelmare 2020-10-07 16:28:49 +02:00 committed by GitHub
parent 0f3489f5cb
commit e006e0ddb3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 124 additions and 1243 deletions

View file

@ -1053,6 +1053,7 @@ omit =
homeassistant/components/zhong_hong/climate.py
homeassistant/components/xbee/*
homeassistant/components/ziggo_mediabox_xl/media_player.py
homeassistant/components/zoneminder/*
homeassistant/components/supla/*
homeassistant/components/zwave/util.py

View file

@ -514,7 +514,7 @@ homeassistant/components/zerproc/* @emlove
homeassistant/components/zha/* @dmulcahey @adminiuga
homeassistant/components/zodiac/* @JulienTant
homeassistant/components/zone/* @home-assistant/core
homeassistant/components/zoneminder/* @rohankapoorcom @vangorra
homeassistant/components/zoneminder/* @rohankapoorcom
homeassistant/components/zwave/* @home-assistant/z-wave
# Individual files

View file

@ -2,169 +2,97 @@
import logging
import voluptuous as vol
from zoneminder.zm import ZoneMinder
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
import homeassistant.config_entries as config_entries
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_ID,
ATTR_NAME,
CONF_HOST,
CONF_PASSWORD,
CONF_PATH,
CONF_PLATFORM,
CONF_SOURCE,
CONF_SSL,
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv
from . import const
from .common import (
ClientAvailabilityResult,
async_test_client_availability,
create_client_from_config,
del_client_from_data,
get_client_from_data,
is_client_in_data,
set_client_to_data,
set_platform_configs,
)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
_LOGGER = logging.getLogger(__name__)
PLATFORM_DOMAINS = tuple(
[BINARY_SENSOR_DOMAIN, CAMERA_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN]
)
CONF_PATH_ZMS = "path_zms"
DEFAULT_PATH = "/zm/"
DEFAULT_PATH_ZMS = "/zm/cgi-bin/nph-zms"
DEFAULT_SSL = False
DEFAULT_TIMEOUT = 10
DEFAULT_VERIFY_SSL = True
DOMAIN = "zoneminder"
HOST_CONFIG_SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_PATH, default=const.DEFAULT_PATH): cv.string,
vol.Optional(const.CONF_PATH_ZMS, default=const.DEFAULT_PATH_ZMS): cv.string,
vol.Optional(CONF_SSL, default=const.DEFAULT_SSL): cv.boolean,
vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string,
vol.Optional(CONF_PATH_ZMS, default=DEFAULT_PATH_ZMS): cv.string,
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_VERIFY_SSL, default=const.DEFAULT_VERIFY_SSL): cv.boolean,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
}
)
CONFIG_SCHEMA = vol.All(
cv.deprecated(const.DOMAIN, invalidation_version="0.118"),
vol.Schema(
{const.DOMAIN: vol.All(cv.ensure_list, [HOST_CONFIG_SCHEMA])},
extra=vol.ALLOW_EXTRA,
),
CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.All(cv.ensure_list, [HOST_CONFIG_SCHEMA])}, extra=vol.ALLOW_EXTRA
)
SERVICE_SET_RUN_STATE = "set_run_state"
SET_RUN_STATE_SCHEMA = vol.Schema(
{vol.Required(ATTR_ID): cv.string, vol.Required(ATTR_NAME): cv.string}
)
async def async_setup(hass: HomeAssistant, base_config: dict):
def setup(hass, config):
"""Set up the ZoneMinder component."""
# Collect the platform specific configs. It's necessary to collect these configs
# here instead of the platform's setup_platform function because the invocation order
# of setup_platform and async_setup_entry is not consistent.
set_platform_configs(
hass,
SENSOR_DOMAIN,
[
platform_config
for platform_config in base_config.get(SENSOR_DOMAIN, [])
if platform_config[CONF_PLATFORM] == const.DOMAIN
],
)
set_platform_configs(
hass,
SWITCH_DOMAIN,
[
platform_config
for platform_config in base_config.get(SWITCH_DOMAIN, [])
if platform_config[CONF_PLATFORM] == const.DOMAIN
],
hass.data[DOMAIN] = {}
success = True
for conf in config[DOMAIN]:
protocol = "https" if conf[CONF_SSL] else "http"
host_name = conf[CONF_HOST]
server_origin = f"{protocol}://{host_name}"
zm_client = ZoneMinder(
server_origin,
conf.get(CONF_USERNAME),
conf.get(CONF_PASSWORD),
conf.get(CONF_PATH),
conf.get(CONF_PATH_ZMS),
conf.get(CONF_VERIFY_SSL),
)
hass.data[DOMAIN][host_name] = zm_client
success = zm_client.login() and success
def set_active_state(call):
"""Set the ZoneMinder run state to the given state name."""
zm_id = call.data[ATTR_ID]
state_name = call.data[ATTR_NAME]
if zm_id not in hass.data[DOMAIN]:
_LOGGER.error("Invalid ZoneMinder host provided: %s", zm_id)
if not hass.data[DOMAIN][zm_id].set_active_state(state_name):
_LOGGER.error(
"Unable to change ZoneMinder state. Host: %s, state: %s",
zm_id,
state_name,
)
hass.services.register(
DOMAIN, SERVICE_SET_RUN_STATE, set_active_state, schema=SET_RUN_STATE_SCHEMA
)
config = base_config.get(const.DOMAIN)
hass.async_create_task(
async_load_platform(hass, "binary_sensor", DOMAIN, {}, config)
)
if not config:
return True
for config_item in config:
hass.async_create_task(
hass.config_entries.flow.async_init(
const.DOMAIN,
context={CONF_SOURCE: config_entries.SOURCE_IMPORT},
data=config_item,
)
)
return True
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up Zoneminder config entry."""
zm_client = create_client_from_config(config_entry.data)
result = await async_test_client_availability(hass, zm_client)
if result != ClientAvailabilityResult.AVAILABLE:
raise ConfigEntryNotReady
set_client_to_data(hass, config_entry.unique_id, zm_client)
for platform_domain in PLATFORM_DOMAINS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(config_entry, platform_domain)
)
if not hass.services.has_service(const.DOMAIN, const.SERVICE_SET_RUN_STATE):
@callback
def set_active_state(call):
"""Set the ZoneMinder run state to the given state name."""
zm_id = call.data[ATTR_ID]
state_name = call.data[ATTR_NAME]
if not is_client_in_data(hass, zm_id):
_LOGGER.error("Invalid ZoneMinder host provided: %s", zm_id)
return
if not get_client_from_data(hass, zm_id).set_active_state(state_name):
_LOGGER.error(
"Unable to change ZoneMinder state. Host: %s, state: %s",
zm_id,
state_name,
)
hass.services.async_register(
const.DOMAIN,
const.SERVICE_SET_RUN_STATE,
set_active_state,
schema=SET_RUN_STATE_SCHEMA,
)
return True
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Unload Zoneminder config entry."""
for platform_domain in PLATFORM_DOMAINS:
hass.async_create_task(
hass.config_entries.async_forward_entry_unload(
config_entry, platform_domain
)
)
# If this is the last config to exist, remove the service too.
if len(hass.config_entries.async_entries(const.DOMAIN)) <= 1:
hass.services.async_remove(const.DOMAIN, const.SERVICE_SET_RUN_STATE)
del_client_from_data(hass, config_entry.unique_id)
return True
return success

View file

@ -1,43 +1,29 @@
"""Support for ZoneMinder binary sensors."""
from typing import Callable, List, Optional
from zoneminder.zm import ZoneMinder
from homeassistant.components.binary_sensor import (
DEVICE_CLASS_CONNECTIVITY,
BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
from .common import get_client_from_data
from . import DOMAIN as ZONEMINDER_DOMAIN
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: Callable[[List[Entity], Optional[bool]], None],
) -> None:
"""Set up the sensor config entry."""
zm_client = get_client_from_data(hass, config_entry.unique_id)
async_add_entities([ZMAvailabilitySensor(zm_client, config_entry)])
async def async_setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the ZoneMinder binary sensor platform."""
sensors = []
for host_name, zm_client in hass.data[ZONEMINDER_DOMAIN].items():
sensors.append(ZMAvailabilitySensor(host_name, zm_client))
add_entities(sensors)
return True
class ZMAvailabilitySensor(BinarySensorEntity):
"""Representation of the availability of ZoneMinder as a binary sensor."""
def __init__(self, client: ZoneMinder, config_entry: ConfigEntry):
def __init__(self, host_name, client):
"""Initialize availability sensor."""
self._state = None
self._name = config_entry.unique_id
self._name = host_name
self._client = client
self._config_entry = config_entry
@property
def unique_id(self) -> Optional[str]:
"""Return a unique ID."""
return f"{self._config_entry.unique_id}_availability"
@property
def name(self):

View file

@ -1,8 +1,5 @@
"""Support for ZoneMinder camera streaming."""
import logging
from typing import Callable, List, Optional
from zoneminder.monitor import Monitor
from homeassistant.components.mjpeg.camera import (
CONF_MJPEG_URL,
@ -10,12 +7,9 @@ from homeassistant.components.mjpeg.camera import (
MjpegCamera,
filter_urllib3_logging,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, CONF_VERIFY_SSL
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
from .common import get_client_from_data
from . import DOMAIN as ZONEMINDER_DOMAIN
_LOGGER = logging.getLogger(__name__)
@ -23,28 +17,23 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the ZoneMinder cameras."""
filter_urllib3_logging()
cameras = []
for zm_client in hass.data[ZONEMINDER_DOMAIN].values():
monitors = zm_client.get_monitors()
if not monitors:
_LOGGER.warning("Could not fetch monitors from ZoneMinder host: %s")
return
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: Callable[[List[Entity], Optional[bool]], None],
) -> None:
"""Set up the sensor config entry."""
zm_client = get_client_from_data(hass, config_entry.unique_id)
async_add_entities(
[
ZoneMinderCamera(monitor, zm_client.verify_ssl, config_entry)
for monitor in await hass.async_add_job(zm_client.get_monitors)
]
)
for monitor in monitors:
_LOGGER.info("Initializing camera %s", monitor.id)
cameras.append(ZoneMinderCamera(monitor, zm_client.verify_ssl))
add_entities(cameras)
class ZoneMinderCamera(MjpegCamera):
"""Representation of a ZoneMinder Monitor Stream."""
def __init__(self, monitor: Monitor, verify_ssl: bool, config_entry: ConfigEntry):
def __init__(self, monitor, verify_ssl):
"""Initialize as a subclass of MjpegCamera."""
device_info = {
CONF_NAME: monitor.name,
@ -56,12 +45,6 @@ class ZoneMinderCamera(MjpegCamera):
self._is_recording = None
self._is_available = None
self._monitor = monitor
self._config_entry = config_entry
@property
def unique_id(self) -> Optional[str]:
"""Return a unique ID."""
return f"{self._config_entry.unique_id}_{self._monitor.id}_camera"
@property
def should_poll(self):

View file

@ -1,110 +0,0 @@
"""Common code for the ZoneMinder component."""
from enum import Enum
from typing import List
import requests
from zoneminder.zm import ZoneMinder
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_PATH,
CONF_SSL,
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from homeassistant.core import HomeAssistant
from . import const
def prime_domain_data(hass: HomeAssistant) -> None:
"""Prime the data structures."""
hass.data.setdefault(const.DOMAIN, {})
def prime_platform_configs(hass: HomeAssistant, domain: str) -> None:
"""Prime the data structures."""
prime_domain_data(hass)
hass.data[const.DOMAIN].setdefault(const.PLATFORM_CONFIGS, {})
hass.data[const.DOMAIN][const.PLATFORM_CONFIGS].setdefault(domain, [])
def set_platform_configs(hass: HomeAssistant, domain: str, configs: List[dict]) -> None:
"""Set platform configs."""
prime_platform_configs(hass, domain)
hass.data[const.DOMAIN][const.PLATFORM_CONFIGS][domain] = configs
def get_platform_configs(hass: HomeAssistant, domain: str) -> List[dict]:
"""Get platform configs."""
prime_platform_configs(hass, domain)
return hass.data[const.DOMAIN][const.PLATFORM_CONFIGS][domain]
def prime_config_data(hass: HomeAssistant, unique_id: str) -> None:
"""Prime the data structures."""
prime_domain_data(hass)
hass.data[const.DOMAIN].setdefault(const.CONFIG_DATA, {})
hass.data[const.DOMAIN][const.CONFIG_DATA].setdefault(unique_id, {})
def set_client_to_data(hass: HomeAssistant, unique_id: str, client: ZoneMinder) -> None:
"""Put a ZoneMinder client in the Home Assistant data."""
prime_config_data(hass, unique_id)
hass.data[const.DOMAIN][const.CONFIG_DATA][unique_id][const.API_CLIENT] = client
def is_client_in_data(hass: HomeAssistant, unique_id: str) -> bool:
"""Check if ZoneMinder client is in the Home Assistant data."""
prime_config_data(hass, unique_id)
return const.API_CLIENT in hass.data[const.DOMAIN][const.CONFIG_DATA][unique_id]
def get_client_from_data(hass: HomeAssistant, unique_id: str) -> ZoneMinder:
"""Get a ZoneMinder client from the Home Assistant data."""
prime_config_data(hass, unique_id)
return hass.data[const.DOMAIN][const.CONFIG_DATA][unique_id][const.API_CLIENT]
def del_client_from_data(hass: HomeAssistant, unique_id: str) -> None:
"""Delete a ZoneMinder client from the Home Assistant data."""
prime_config_data(hass, unique_id)
del hass.data[const.DOMAIN][const.CONFIG_DATA][unique_id][const.API_CLIENT]
def create_client_from_config(conf: dict) -> ZoneMinder:
"""Create a new ZoneMinder client from a config."""
protocol = "https" if conf[CONF_SSL] else "http"
host_name = conf[CONF_HOST]
server_origin = f"{protocol}://{host_name}"
return ZoneMinder(
server_origin,
conf.get(CONF_USERNAME),
conf.get(CONF_PASSWORD),
conf.get(CONF_PATH),
conf.get(const.CONF_PATH_ZMS),
conf.get(CONF_VERIFY_SSL),
)
class ClientAvailabilityResult(Enum):
"""Client availability test result."""
AVAILABLE = "available"
ERROR_AUTH_FAIL = "invalid_auth"
ERROR_CONNECTION_ERROR = "cannot_connect"
async def async_test_client_availability(
hass: HomeAssistant, client: ZoneMinder
) -> ClientAvailabilityResult:
"""Test the availability of a ZoneMinder client."""
try:
if await hass.async_add_job(client.login):
return ClientAvailabilityResult.AVAILABLE
return ClientAvailabilityResult.ERROR_AUTH_FAIL
except requests.exceptions.ConnectionError:
return ClientAvailabilityResult.ERROR_CONNECTION_ERROR

View file

@ -1,99 +0,0 @@
"""ZoneMinder config flow."""
from urllib.parse import urlparse
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_PATH,
CONF_SOURCE,
CONF_SSL,
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from .common import (
ClientAvailabilityResult,
async_test_client_availability,
create_client_from_config,
)
from .const import (
CONF_PATH_ZMS,
DEFAULT_PATH,
DEFAULT_PATH_ZMS,
DEFAULT_SSL,
DEFAULT_VERIFY_SSL,
)
from .const import DOMAIN # pylint: disable=unused-import
class ZoneminderFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Flow handler for zoneminder integration."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
async def async_step_import(self, config: dict):
"""Handle a flow initialized by import."""
return await self.async_step_finish(
{**config, **{CONF_SOURCE: config_entries.SOURCE_IMPORT}}
)
async def async_step_user(self, user_input: dict = None):
"""Handle user step."""
user_input = user_input or {}
errors = {}
if user_input:
zm_client = create_client_from_config(user_input)
result = await async_test_client_availability(self.hass, zm_client)
if result == ClientAvailabilityResult.AVAILABLE:
return await self.async_step_finish(user_input)
errors["base"] = result.value
return self.async_show_form(
step_id=config_entries.SOURCE_USER,
data_schema=vol.Schema(
{
vol.Required(CONF_HOST, default=user_input.get(CONF_HOST)): str,
vol.Optional(
CONF_USERNAME, default=user_input.get(CONF_USERNAME, "")
): str,
vol.Optional(
CONF_PASSWORD, default=user_input.get(CONF_PASSWORD, "")
): str,
vol.Optional(
CONF_PATH, default=user_input.get(CONF_PATH, DEFAULT_PATH)
): str,
vol.Optional(
CONF_PATH_ZMS,
default=user_input.get(CONF_PATH_ZMS, DEFAULT_PATH_ZMS),
): str,
vol.Optional(
CONF_SSL, default=user_input.get(CONF_SSL, DEFAULT_SSL)
): bool,
vol.Optional(
CONF_VERIFY_SSL,
default=user_input.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL),
): bool,
}
),
errors=errors,
)
async def async_step_finish(self, config: dict):
"""Finish config flow."""
zm_client = create_client_from_config(config)
hostname = urlparse(zm_client.get_zms_url()).hostname
result = await async_test_client_availability(self.hass, zm_client)
if result != ClientAvailabilityResult.AVAILABLE:
return self.async_abort(reason=str(result.value))
await self.async_set_unique_id(hostname)
self._abort_if_unique_id_configured(config)
return self.async_create_entry(title=hostname, data=config)

View file

@ -1,14 +0,0 @@
"""Constants for zoneminder component."""
CONF_PATH_ZMS = "path_zms"
DEFAULT_PATH = "/zm/"
DEFAULT_PATH_ZMS = "/zm/cgi-bin/nph-zms"
DEFAULT_SSL = False
DEFAULT_VERIFY_SSL = True
DOMAIN = "zoneminder"
SERVICE_SET_RUN_STATE = "set_run_state"
PLATFORM_CONFIGS = "platform_configs"
CONFIG_DATA = "config_data"
API_CLIENT = "api_client"

View file

@ -1,8 +1,7 @@
{
"domain": "zoneminder",
"name": "ZoneMinder",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/zoneminder",
"requirements": ["zm-py==0.4.0"],
"codeowners": ["@rohankapoorcom", "@vangorra"]
"codeowners": ["@rohankapoorcom"]
}

View file

@ -1,19 +1,15 @@
"""Support for ZoneMinder sensors."""
import logging
from typing import Callable, List, Optional
import voluptuous as vol
from zoneminder.monitor import Monitor, TimePeriod
from zoneminder.zm import ZoneMinder
from zoneminder.monitor import TimePeriod
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, PLATFORM_SCHEMA
from homeassistant.config_entries import ConfigEntry
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from .common import get_client_from_data, get_platform_configs
from . import DOMAIN as ZONEMINDER_DOMAIN
_LOGGER = logging.getLogger(__name__)
@ -41,50 +37,35 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: Callable[[List[Entity], Optional[bool]], None],
) -> None:
"""Set up the sensor config entry."""
zm_client = get_client_from_data(hass, config_entry.unique_id)
monitors = await hass.async_add_job(zm_client.get_monitors)
if not monitors:
_LOGGER.warning("Did not fetch any monitors from ZoneMinder")
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the ZoneMinder sensor platform."""
include_archived = config.get(CONF_INCLUDE_ARCHIVED)
sensors = []
for monitor in monitors:
sensors.append(ZMSensorMonitors(monitor, config_entry))
for zm_client in hass.data[ZONEMINDER_DOMAIN].values():
monitors = zm_client.get_monitors()
if not monitors:
_LOGGER.warning("Could not fetch any monitors from ZoneMinder")
for config in get_platform_configs(hass, SENSOR_DOMAIN):
include_archived = config.get(CONF_INCLUDE_ARCHIVED)
for monitor in monitors:
sensors.append(ZMSensorMonitors(monitor))
for sensor in config[CONF_MONITORED_CONDITIONS]:
sensors.append(
ZMSensorEvents(monitor, include_archived, sensor, config_entry)
)
sensors.append(ZMSensorEvents(monitor, include_archived, sensor))
sensors.append(ZMSensorRunState(zm_client, config_entry))
async_add_entities(sensors, True)
sensors.append(ZMSensorRunState(zm_client))
add_entities(sensors)
class ZMSensorMonitors(Entity):
"""Get the status of each ZoneMinder monitor."""
def __init__(self, monitor: Monitor, config_entry: ConfigEntry):
def __init__(self, monitor):
"""Initialize monitor sensor."""
self._monitor = monitor
self._config_entry = config_entry
self._state = None
self._is_available = None
@property
def unique_id(self) -> Optional[str]:
"""Return a unique ID."""
return f"{self._config_entry.unique_id}_{self._monitor.id}_status"
@property
def name(self):
"""Return the name of the sensor."""
@ -113,26 +94,14 @@ class ZMSensorMonitors(Entity):
class ZMSensorEvents(Entity):
"""Get the number of events for each monitor."""
def __init__(
self,
monitor: Monitor,
include_archived: bool,
sensor_type: str,
config_entry: ConfigEntry,
):
def __init__(self, monitor, include_archived, sensor_type):
"""Initialize event sensor."""
self._monitor = monitor
self._include_archived = include_archived
self.time_period = TimePeriod.get_time_period(sensor_type)
self._config_entry = config_entry
self._state = None
@property
def unique_id(self) -> Optional[str]:
"""Return a unique ID."""
return f"{self._config_entry.unique_id}_{self._monitor.id}_{self.time_period.value}_{self._include_archived}_events"
@property
def name(self):
"""Return the name of the sensor."""
@ -156,17 +125,11 @@ class ZMSensorEvents(Entity):
class ZMSensorRunState(Entity):
"""Get the ZoneMinder run state."""
def __init__(self, client: ZoneMinder, config_entry: ConfigEntry):
def __init__(self, client):
"""Initialize run state sensor."""
self._state = None
self._is_available = None
self._client = client
self._config_entry = config_entry
@property
def unique_id(self) -> Optional[str]:
"""Return a unique ID."""
return f"{self._config_entry.unique_id}_runstate"
@property
def name(self):

View file

@ -1,9 +1,6 @@
set_run_state:
description: "Set the ZoneMinder run state"
description: Set the ZoneMinder run state
fields:
id:
description: "The host name or IP address of the ZoneMinder instance."
example: "10.10.0.2"
name:
description: "The string name of the ZoneMinder run state to set as active."
description: The string name of the ZoneMinder run state to set as active.
example: "Home"

View file

@ -1,28 +0,0 @@
{
"config": {
"flow_title": "ZoneMinder",
"step": {
"user": {
"title": "Add ZoneMinder Server.",
"data": {
"host": "Host and Port (ex 10.10.0.4:8010)",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]",
"path": "ZM Path",
"path_zms": "ZMS Path",
"ssl": "[%key:common::config_flow::data::ssl%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
}
}
},
"abort": {
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"error": {
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"create_entry": { "default": "ZoneMinder server added." }
}
}

View file

@ -1,61 +1,41 @@
"""Support for ZoneMinder switches."""
import logging
from typing import Callable, List, Optional
import voluptuous as vol
from zoneminder.monitor import Monitor, MonitorState
from zoneminder.monitor import MonitorState
from homeassistant.components.switch import (
DOMAIN as SWITCH_DOMAIN,
PLATFORM_SCHEMA,
SwitchEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity
from homeassistant.const import CONF_COMMAND_OFF, CONF_COMMAND_ON
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
from .common import get_client_from_data, get_platform_configs
from . import DOMAIN as ZONEMINDER_DOMAIN
_LOGGER = logging.getLogger(__name__)
MONITOR_STATES = {
MonitorState[name].value: MonitorState[name]
for name in dir(MonitorState)
if not name.startswith("_")
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_COMMAND_ON): vol.All(vol.In(MONITOR_STATES.keys())),
vol.Required(CONF_COMMAND_OFF): vol.All(vol.In(MONITOR_STATES.keys())),
vol.Required(CONF_COMMAND_ON): cv.string,
vol.Required(CONF_COMMAND_OFF): cv.string,
}
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: Callable[[List[Entity], Optional[bool]], None],
) -> None:
"""Set up the sensor config entry."""
zm_client = get_client_from_data(hass, config_entry.unique_id)
monitors = await hass.async_add_job(zm_client.get_monitors)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the ZoneMinder switch platform."""
if not monitors:
_LOGGER.warning("Could not fetch monitors from ZoneMinder")
return
on_state = MonitorState(config.get(CONF_COMMAND_ON))
off_state = MonitorState(config.get(CONF_COMMAND_OFF))
switches = []
for monitor in monitors:
for config in get_platform_configs(hass, SWITCH_DOMAIN):
on_state = MONITOR_STATES[config[CONF_COMMAND_ON]]
off_state = MONITOR_STATES[config[CONF_COMMAND_OFF]]
for zm_client in hass.data[ZONEMINDER_DOMAIN].values():
monitors = zm_client.get_monitors()
if not monitors:
_LOGGER.warning("Could not fetch monitors from ZoneMinder")
return
switches.append(
ZMSwitchMonitors(monitor, on_state, off_state, config_entry)
)
async_add_entities(switches, True)
for monitor in monitors:
switches.append(ZMSwitchMonitors(monitor, on_state, off_state))
add_entities(switches)
class ZMSwitchMonitors(SwitchEntity):
@ -63,25 +43,13 @@ class ZMSwitchMonitors(SwitchEntity):
icon = "mdi:record-rec"
def __init__(
self,
monitor: Monitor,
on_state: MonitorState,
off_state: MonitorState,
config_entry: ConfigEntry,
):
def __init__(self, monitor, on_state, off_state):
"""Initialize the switch."""
self._monitor = monitor
self._on_state = on_state
self._off_state = off_state
self._config_entry = config_entry
self._state = None
@property
def unique_id(self) -> Optional[str]:
"""Return a unique ID."""
return f"{self._config_entry.unique_id}_{self._monitor.id}_switch_{self._on_state.value}_{self._off_state.value}"
@property
def name(self):
"""Return the name of the switch."""

View file

@ -220,6 +220,5 @@ FLOWS = [
"yeelight",
"zerproc",
"zha",
"zoneminder",
"zwave"
]

View file

@ -1099,6 +1099,3 @@ zigpy-znp==0.2.1
# homeassistant.components.zha
zigpy==0.26.0
# homeassistant.components.zoneminder
zm-py==0.4.0

View file

@ -1 +0,0 @@
"""Tests for the zoneminder component."""

View file

@ -1,65 +0,0 @@
"""Binary sensor tests."""
from zoneminder.zm import ZoneMinder
from homeassistant.components.zoneminder import const
from homeassistant.config import async_process_ha_core_config
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_HOST,
CONF_PASSWORD,
CONF_PATH,
CONF_SSL,
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from homeassistant.core import DOMAIN as HASS_DOMAIN, HomeAssistant
from homeassistant.setup import async_setup_component
from tests.async_mock import MagicMock, patch
from tests.common import MockConfigEntry
async def test_async_setup_entry(hass: HomeAssistant) -> None:
"""Test setup of binary sensor entities."""
with patch(
"homeassistant.components.zoneminder.common.ZoneMinder", autospec=ZoneMinder
) as zoneminder_mock:
zm_client: ZoneMinder = MagicMock(spec=ZoneMinder)
zm_client.get_zms_url.return_value = "http://host1/path_zms1"
zm_client.login.return_value = True
zm_client.is_available = True
zoneminder_mock.return_value = zm_client
config_entry = MockConfigEntry(
domain=const.DOMAIN,
unique_id="host1",
data={
CONF_HOST: "host1",
CONF_USERNAME: "username1",
CONF_PASSWORD: "password1",
CONF_PATH: "path1",
const.CONF_PATH_ZMS: "path_zms1",
CONF_SSL: False,
CONF_VERIFY_SSL: True,
},
)
config_entry.add_to_hass(hass)
await async_process_ha_core_config(hass, {})
await async_setup_component(hass, HASS_DOMAIN, {})
await async_setup_component(hass, const.DOMAIN, {})
await hass.async_block_till_done()
await hass.services.async_call(
HASS_DOMAIN, "update_entity", {ATTR_ENTITY_ID: "binary_sensor.host1"}
)
await hass.async_block_till_done()
assert hass.states.get("binary_sensor.host1").state == "on"
zm_client.is_available = False
await hass.services.async_call(
HASS_DOMAIN, "update_entity", {ATTR_ENTITY_ID: "binary_sensor.host1"}
)
await hass.async_block_till_done()
assert hass.states.get("binary_sensor.host1").state == "off"

View file

@ -1,89 +0,0 @@
"""Binary sensor tests."""
from zoneminder.monitor import Monitor
from zoneminder.zm import ZoneMinder
from homeassistant.components.zoneminder import const
from homeassistant.config import async_process_ha_core_config
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_HOST,
CONF_PASSWORD,
CONF_PATH,
CONF_SSL,
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from homeassistant.core import DOMAIN as HASS_DOMAIN, HomeAssistant
from homeassistant.setup import async_setup_component
from tests.async_mock import MagicMock, patch
from tests.common import MockConfigEntry
async def test_async_setup_entry(hass: HomeAssistant) -> None:
"""Test setup of camera entities."""
with patch(
"homeassistant.components.zoneminder.common.ZoneMinder", autospec=ZoneMinder
) as zoneminder_mock:
monitor1 = MagicMock(spec=Monitor)
monitor1.name = "monitor1"
monitor1.mjpeg_image_url = "mjpeg_image_url1"
monitor1.still_image_url = "still_image_url1"
monitor1.is_recording = True
monitor1.is_available = True
monitor2 = MagicMock(spec=Monitor)
monitor2.name = "monitor2"
monitor2.mjpeg_image_url = "mjpeg_image_url2"
monitor2.still_image_url = "still_image_url2"
monitor2.is_recording = False
monitor2.is_available = False
zm_client: ZoneMinder = MagicMock(spec=ZoneMinder)
zm_client.get_zms_url.return_value = "http://host1/path_zms1"
zm_client.login.return_value = True
zm_client.get_monitors.return_value = [monitor1, monitor2]
zoneminder_mock.return_value = zm_client
config_entry = MockConfigEntry(
domain=const.DOMAIN,
unique_id="host1",
data={
CONF_HOST: "host1",
CONF_USERNAME: "username1",
CONF_PASSWORD: "password1",
CONF_PATH: "path1",
const.CONF_PATH_ZMS: "path_zms1",
CONF_SSL: False,
CONF_VERIFY_SSL: True,
},
)
config_entry.add_to_hass(hass)
await async_process_ha_core_config(hass, {})
await async_setup_component(hass, HASS_DOMAIN, {})
await async_setup_component(hass, const.DOMAIN, {})
await hass.async_block_till_done()
await hass.services.async_call(
HASS_DOMAIN, "update_entity", {ATTR_ENTITY_ID: "camera.monitor1"}
)
await hass.services.async_call(
HASS_DOMAIN, "update_entity", {ATTR_ENTITY_ID: "camera.monitor2"}
)
await hass.async_block_till_done()
assert hass.states.get("camera.monitor1").state == "recording"
assert hass.states.get("camera.monitor2").state == "unavailable"
monitor1.is_recording = False
monitor2.is_recording = True
await hass.services.async_call(
HASS_DOMAIN, "update_entity", {ATTR_ENTITY_ID: "camera.monitor1"}
)
await hass.services.async_call(
HASS_DOMAIN, "update_entity", {ATTR_ENTITY_ID: "camera.monitor2"}
)
await hass.async_block_till_done()
assert hass.states.get("camera.monitor1").state == "idle"
assert hass.states.get("camera.monitor2").state == "unavailable"

View file

@ -1,119 +0,0 @@
"""Config flow tests."""
import requests
from zoneminder.zm import ZoneMinder
from homeassistant import config_entries
from homeassistant.components.zoneminder import ClientAvailabilityResult, const
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_PATH,
CONF_SOURCE,
CONF_SSL,
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from homeassistant.core import HomeAssistant
from tests.async_mock import MagicMock, patch
async def test_import(hass: HomeAssistant) -> None:
"""Test import from configuration yaml."""
with patch(
"homeassistant.components.zoneminder.common.ZoneMinder", autospec=ZoneMinder
) as zoneminder_mock:
conf_data = {
CONF_HOST: "host1",
CONF_USERNAME: "username1",
CONF_PASSWORD: "password1",
CONF_PATH: "path1",
const.CONF_PATH_ZMS: "path_zms1",
CONF_SSL: False,
CONF_VERIFY_SSL: True,
}
zm_client: ZoneMinder = MagicMock(spec=ZoneMinder)
zm_client.get_zms_url.return_value = "http://host1/path_zms1"
zoneminder_mock.return_value = zm_client
zm_client.login.return_value = False
result = await hass.config_entries.flow.async_init(
const.DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=conf_data,
)
assert result
assert result["type"] == "abort"
assert result["reason"] == "invalid_auth"
zm_client.login.return_value = True
result = await hass.config_entries.flow.async_init(
const.DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=conf_data,
)
assert result
assert result["type"] == "create_entry"
assert result["data"] == {
**conf_data,
CONF_SOURCE: config_entries.SOURCE_IMPORT,
}
async def test_user(hass: HomeAssistant) -> None:
"""Test user initiated creation."""
with patch(
"homeassistant.components.zoneminder.common.ZoneMinder", autospec=ZoneMinder
) as zoneminder_mock:
conf_data = {
CONF_HOST: "host1",
CONF_USERNAME: "username1",
CONF_PASSWORD: "password1",
CONF_PATH: "path1",
const.CONF_PATH_ZMS: "path_zms1",
CONF_SSL: False,
CONF_VERIFY_SSL: True,
}
result = await hass.config_entries.flow.async_init(
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result
assert result["type"] == "form"
zm_client: ZoneMinder = MagicMock(spec=ZoneMinder)
zoneminder_mock.return_value = zm_client
zm_client.login.side_effect = requests.exceptions.ConnectionError()
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
conf_data,
)
assert result
assert result["type"] == "form"
assert result["errors"] == {
"base": ClientAvailabilityResult.ERROR_CONNECTION_ERROR.value
}
zm_client.login.side_effect = None
zm_client.login.return_value = False
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
conf_data,
)
assert result
assert result["type"] == "form"
assert result["errors"] == {
"base": ClientAvailabilityResult.ERROR_AUTH_FAIL.value
}
zm_client.login.return_value = True
zm_client.get_zms_url.return_value = "http://host1/path_zms1"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
conf_data,
)
assert result
assert result["type"] == "create_entry"
assert result["data"] == conf_data

View file

@ -1,122 +0,0 @@
"""Tests for init functions."""
from datetime import timedelta
from zoneminder.zm import ZoneMinder
from homeassistant import config_entries
from homeassistant.components.zoneminder import const
from homeassistant.components.zoneminder.common import is_client_in_data
from homeassistant.config_entries import (
ENTRY_STATE_LOADED,
ENTRY_STATE_NOT_LOADED,
ENTRY_STATE_SETUP_RETRY,
)
from homeassistant.const import (
ATTR_ID,
ATTR_NAME,
CONF_HOST,
CONF_PASSWORD,
CONF_PATH,
CONF_SOURCE,
CONF_SSL,
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from tests.async_mock import MagicMock, patch
from tests.common import async_fire_time_changed
async def test_no_yaml_config(hass: HomeAssistant) -> None:
"""Test empty yaml config."""
with patch(
"homeassistant.components.zoneminder.common.ZoneMinder", autospec=ZoneMinder
) as zoneminder_mock:
zm_client: ZoneMinder = MagicMock(spec=ZoneMinder)
zm_client.get_zms_url.return_value = "http://host1/path_zms1"
zm_client.login.return_value = True
zm_client.get_monitors.return_value = []
zoneminder_mock.return_value = zm_client
hass_config = {const.DOMAIN: []}
await async_setup_component(hass, const.DOMAIN, hass_config)
await hass.async_block_till_done()
assert not hass.services.has_service(const.DOMAIN, const.SERVICE_SET_RUN_STATE)
async def test_yaml_config_import(hass: HomeAssistant) -> None:
"""Test yaml config import."""
with patch(
"homeassistant.components.zoneminder.common.ZoneMinder", autospec=ZoneMinder
) as zoneminder_mock:
zm_client: ZoneMinder = MagicMock(spec=ZoneMinder)
zm_client.get_zms_url.return_value = "http://host1/path_zms1"
zm_client.login.return_value = True
zm_client.get_monitors.return_value = []
zoneminder_mock.return_value = zm_client
hass_config = {const.DOMAIN: [{CONF_HOST: "host1"}]}
await async_setup_component(hass, const.DOMAIN, hass_config)
await hass.async_block_till_done()
assert hass.services.has_service(const.DOMAIN, const.SERVICE_SET_RUN_STATE)
async def test_load_call_service_and_unload(hass: HomeAssistant) -> None:
"""Test config entry load/unload and calling of service."""
with patch(
"homeassistant.components.zoneminder.common.ZoneMinder", autospec=ZoneMinder
) as zoneminder_mock:
zm_client: ZoneMinder = MagicMock(spec=ZoneMinder)
zm_client.get_zms_url.return_value = "http://host1/path_zms1"
zm_client.login.side_effect = [True, True, False, True]
zm_client.get_monitors.return_value = []
zm_client.is_available.return_value = True
zoneminder_mock.return_value = zm_client
await hass.config_entries.flow.async_init(
const.DOMAIN,
context={CONF_SOURCE: config_entries.SOURCE_USER},
data={
CONF_HOST: "host1",
CONF_USERNAME: "username1",
CONF_PASSWORD: "password1",
CONF_PATH: "path1",
const.CONF_PATH_ZMS: "path_zms1",
CONF_SSL: False,
CONF_VERIFY_SSL: True,
},
)
await hass.async_block_till_done()
config_entry = next(iter(hass.config_entries.async_entries(const.DOMAIN)), None)
assert config_entry
assert config_entry.state == ENTRY_STATE_SETUP_RETRY
assert not is_client_in_data(hass, "host1")
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10))
await hass.async_block_till_done()
assert config_entry.state == ENTRY_STATE_LOADED
assert is_client_in_data(hass, "host1")
assert hass.services.has_service(const.DOMAIN, const.SERVICE_SET_RUN_STATE)
await hass.services.async_call(
const.DOMAIN,
const.SERVICE_SET_RUN_STATE,
{ATTR_ID: "host1", ATTR_NAME: "away"},
)
await hass.async_block_till_done()
zm_client.set_active_state.assert_called_with("away")
await config_entry.async_unload(hass)
await hass.async_block_till_done()
assert config_entry.state == ENTRY_STATE_NOT_LOADED
assert not is_client_in_data(hass, "host1")
assert not hass.services.has_service(const.DOMAIN, const.SERVICE_SET_RUN_STATE)

View file

@ -1,167 +0,0 @@
"""Binary sensor tests."""
from zoneminder.monitor import Monitor, MonitorState, TimePeriod
from zoneminder.zm import ZoneMinder
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.zoneminder import const
from homeassistant.components.zoneminder.sensor import CONF_INCLUDE_ARCHIVED
from homeassistant.config import async_process_ha_core_config
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_HOST,
CONF_MONITORED_CONDITIONS,
CONF_PASSWORD,
CONF_PATH,
CONF_PLATFORM,
CONF_SSL,
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from homeassistant.core import DOMAIN as HASS_DOMAIN, HomeAssistant
from homeassistant.setup import async_setup_component
from tests.async_mock import MagicMock, patch
from tests.common import MockConfigEntry
async def test_async_setup_entry(hass: HomeAssistant) -> None:
"""Test setup of sensor entities."""
def _get_events(monitor_id: int, time_period: TimePeriod, include_archived: bool):
enum_list = [name for name in dir(TimePeriod) if not name.startswith("_")]
tp_index = enum_list.index(time_period.name)
return (100 * monitor_id) + (tp_index * 10) + include_archived
def _monitor1_get_events(time_period: TimePeriod, include_archived: bool):
return _get_events(1, time_period, include_archived)
def _monitor2_get_events(time_period: TimePeriod, include_archived: bool):
return _get_events(2, time_period, include_archived)
with patch(
"homeassistant.components.zoneminder.common.ZoneMinder", autospec=ZoneMinder
) as zoneminder_mock:
monitor1 = MagicMock(spec=Monitor)
monitor1.name = "monitor1"
monitor1.mjpeg_image_url = "mjpeg_image_url1"
monitor1.still_image_url = "still_image_url1"
monitor1.is_recording = True
monitor1.is_available = True
monitor1.function = MonitorState.MONITOR
monitor1.get_events.side_effect = _monitor1_get_events
monitor2 = MagicMock(spec=Monitor)
monitor2.name = "monitor2"
monitor2.mjpeg_image_url = "mjpeg_image_url2"
monitor2.still_image_url = "still_image_url2"
monitor2.is_recording = False
monitor2.is_available = False
monitor2.function = MonitorState.MODECT
monitor2.get_events.side_effect = _monitor2_get_events
zm_client: ZoneMinder = MagicMock(spec=ZoneMinder)
zm_client.get_zms_url.return_value = "http://host1/path_zms1"
zm_client.login.return_value = True
zm_client.get_monitors.return_value = [monitor1, monitor2]
zoneminder_mock.return_value = zm_client
config_entry = MockConfigEntry(
domain=const.DOMAIN,
unique_id="host1",
data={
CONF_HOST: "host1",
CONF_USERNAME: "username1",
CONF_PASSWORD: "password1",
CONF_PATH: "path1",
const.CONF_PATH_ZMS: "path_zms1",
CONF_SSL: False,
CONF_VERIFY_SSL: True,
},
)
config_entry.add_to_hass(hass)
hass_config = {
HASS_DOMAIN: {},
SENSOR_DOMAIN: [
{
CONF_PLATFORM: const.DOMAIN,
CONF_INCLUDE_ARCHIVED: True,
CONF_MONITORED_CONDITIONS: ["all", "day"],
}
],
}
await async_process_ha_core_config(hass, hass_config[HASS_DOMAIN])
await async_setup_component(hass, HASS_DOMAIN, hass_config)
await async_setup_component(hass, SENSOR_DOMAIN, hass_config)
await hass.async_block_till_done()
await async_setup_component(hass, const.DOMAIN, hass_config)
await hass.async_block_till_done()
await hass.services.async_call(
HASS_DOMAIN, "update_entity", {ATTR_ENTITY_ID: "sensor.monitor1_status"}
)
await hass.services.async_call(
HASS_DOMAIN, "update_entity", {ATTR_ENTITY_ID: "sensor.monitor1_events"}
)
await hass.services.async_call(
HASS_DOMAIN,
"update_entity",
{ATTR_ENTITY_ID: "sensor.monitor1_events_last_day"},
)
await hass.services.async_call(
HASS_DOMAIN, "update_entity", {ATTR_ENTITY_ID: "sensor.monitor2_status"}
)
await hass.services.async_call(
HASS_DOMAIN, "update_entity", {ATTR_ENTITY_ID: "sensor.monitor2_events"}
)
await hass.services.async_call(
HASS_DOMAIN,
"update_entity",
{ATTR_ENTITY_ID: "sensor.monitor2_events_last_day"},
)
await hass.async_block_till_done()
assert (
hass.states.get("sensor.monitor1_status").state
== MonitorState.MONITOR.value
)
assert hass.states.get("sensor.monitor1_events").state == "101"
assert hass.states.get("sensor.monitor1_events_last_day").state == "111"
assert hass.states.get("sensor.monitor2_status").state == "unavailable"
assert hass.states.get("sensor.monitor2_events").state == "201"
assert hass.states.get("sensor.monitor2_events_last_day").state == "211"
monitor1.function = MonitorState.NONE
monitor2.function = MonitorState.NODECT
await hass.services.async_call(
HASS_DOMAIN, "update_entity", {ATTR_ENTITY_ID: "sensor.monitor1_status"}
)
await hass.services.async_call(
HASS_DOMAIN, "update_entity", {ATTR_ENTITY_ID: "sensor.monitor1_events"}
)
await hass.services.async_call(
HASS_DOMAIN,
"update_entity",
{ATTR_ENTITY_ID: "sensor.monitor1_events_last_day"},
)
await hass.services.async_call(
HASS_DOMAIN, "update_entity", {ATTR_ENTITY_ID: "sensor.monitor2_status"}
)
await hass.services.async_call(
HASS_DOMAIN, "update_entity", {ATTR_ENTITY_ID: "sensor.monitor2_events"}
)
await hass.services.async_call(
HASS_DOMAIN,
"update_entity",
{ATTR_ENTITY_ID: "sensor.monitor2_events_last_day"},
)
await hass.async_block_till_done()
assert (
hass.states.get("sensor.monitor1_status").state == MonitorState.NONE.value
)
assert hass.states.get("sensor.monitor1_events").state == "101"
assert hass.states.get("sensor.monitor1_events_last_day").state == "111"
assert hass.states.get("sensor.monitor2_status").state == "unavailable"
assert hass.states.get("sensor.monitor2_events").state == "201"
assert hass.states.get("sensor.monitor2_events_last_day").state == "211"

View file

@ -1,126 +0,0 @@
"""Binary sensor tests."""
from zoneminder.monitor import Monitor, MonitorState
from zoneminder.zm import ZoneMinder
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.zoneminder import const
from homeassistant.config import async_process_ha_core_config
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_COMMAND_OFF,
CONF_COMMAND_ON,
CONF_HOST,
CONF_PASSWORD,
CONF_PATH,
CONF_PLATFORM,
CONF_SSL,
CONF_USERNAME,
CONF_VERIFY_SSL,
STATE_OFF,
STATE_ON,
)
from homeassistant.core import DOMAIN as HASS_DOMAIN, HomeAssistant
from homeassistant.setup import async_setup_component
from tests.async_mock import MagicMock, patch
from tests.common import MockConfigEntry
async def test_async_setup_entry(hass: HomeAssistant) -> None:
"""Test setup of sensor entities."""
with patch(
"homeassistant.components.zoneminder.common.ZoneMinder", autospec=ZoneMinder
) as zoneminder_mock:
monitor1 = MagicMock(spec=Monitor)
monitor1.name = "monitor1"
monitor1.mjpeg_image_url = "mjpeg_image_url1"
monitor1.still_image_url = "still_image_url1"
monitor1.is_recording = True
monitor1.is_available = True
monitor1.function = MonitorState.MONITOR
monitor2 = MagicMock(spec=Monitor)
monitor2.name = "monitor2"
monitor2.mjpeg_image_url = "mjpeg_image_url2"
monitor2.still_image_url = "still_image_url2"
monitor2.is_recording = False
monitor2.is_available = False
monitor2.function = MonitorState.MODECT
zm_client: ZoneMinder = MagicMock(spec=ZoneMinder)
zm_client.get_zms_url.return_value = "http://host1/path_zms1"
zm_client.login.return_value = True
zm_client.get_monitors.return_value = [monitor1, monitor2]
zoneminder_mock.return_value = zm_client
config_entry = MockConfigEntry(
domain=const.DOMAIN,
unique_id="host1",
data={
CONF_HOST: "host1",
CONF_USERNAME: "username1",
CONF_PASSWORD: "password1",
CONF_PATH: "path1",
const.CONF_PATH_ZMS: "path_zms1",
CONF_SSL: False,
CONF_VERIFY_SSL: True,
},
)
config_entry.add_to_hass(hass)
hass_config = {
HASS_DOMAIN: {},
SWITCH_DOMAIN: [
{
CONF_PLATFORM: const.DOMAIN,
CONF_COMMAND_ON: MonitorState.MONITOR.value,
CONF_COMMAND_OFF: MonitorState.MODECT.value,
},
{
CONF_PLATFORM: const.DOMAIN,
CONF_COMMAND_ON: MonitorState.MODECT.value,
CONF_COMMAND_OFF: MonitorState.MONITOR.value,
},
],
}
await async_process_ha_core_config(hass, hass_config[HASS_DOMAIN])
await async_setup_component(hass, HASS_DOMAIN, hass_config)
await async_setup_component(hass, SWITCH_DOMAIN, hass_config)
await hass.async_block_till_done()
await async_setup_component(hass, const.DOMAIN, hass_config)
await hass.async_block_till_done()
await hass.services.async_call(
SWITCH_DOMAIN, "turn_on", {ATTR_ENTITY_ID: "switch.monitor1_state"}
)
await hass.services.async_call(
SWITCH_DOMAIN, "turn_off", {ATTR_ENTITY_ID: "switch.monitor1_state_2"}
)
await hass.async_block_till_done()
assert hass.states.get("switch.monitor1_state").state == STATE_ON
assert hass.states.get("switch.monitor1_state_2").state == STATE_OFF
await hass.services.async_call(
SWITCH_DOMAIN, "turn_off", {ATTR_ENTITY_ID: "switch.monitor1_state"}
)
await hass.services.async_call(
SWITCH_DOMAIN, "turn_on", {ATTR_ENTITY_ID: "switch.monitor1_state_2"}
)
await hass.async_block_till_done()
assert hass.states.get("switch.monitor1_state").state == STATE_OFF
assert hass.states.get("switch.monitor1_state_2").state == STATE_ON
monitor1.function = MonitorState.NONE
monitor2.function = MonitorState.NODECT
await hass.services.async_call(
HASS_DOMAIN, "update_entity", {ATTR_ENTITY_ID: "switch.monitor1_state"}
)
await hass.services.async_call(
HASS_DOMAIN, "update_entity", {ATTR_ENTITY_ID: "switch.monitor1_state_2"}
)
await hass.async_block_till_done()
assert hass.states.get("switch.monitor1_state").state == STATE_OFF
assert hass.states.get("switch.monitor1_state_2").state == STATE_OFF