diff --git a/.coveragerc b/.coveragerc index 8d3aeb916d1..b33d9b15f99 100644 --- a/.coveragerc +++ b/.coveragerc @@ -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 diff --git a/CODEOWNERS b/CODEOWNERS index a335dc0bb19..4a5b6b0d56e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -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 diff --git a/homeassistant/components/zoneminder/__init__.py b/homeassistant/components/zoneminder/__init__.py index 92186b7a0b5..c631406b0e3 100644 --- a/homeassistant/components/zoneminder/__init__.py +++ b/homeassistant/components/zoneminder/__init__.py @@ -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 diff --git a/homeassistant/components/zoneminder/binary_sensor.py b/homeassistant/components/zoneminder/binary_sensor.py index 73f7ce2f4c9..73d6877ef2d 100644 --- a/homeassistant/components/zoneminder/binary_sensor.py +++ b/homeassistant/components/zoneminder/binary_sensor.py @@ -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): diff --git a/homeassistant/components/zoneminder/camera.py b/homeassistant/components/zoneminder/camera.py index c4ef1b14772..6144fe11226 100644 --- a/homeassistant/components/zoneminder/camera.py +++ b/homeassistant/components/zoneminder/camera.py @@ -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): diff --git a/homeassistant/components/zoneminder/common.py b/homeassistant/components/zoneminder/common.py deleted file mode 100644 index 9b5498b0cda..00000000000 --- a/homeassistant/components/zoneminder/common.py +++ /dev/null @@ -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 diff --git a/homeassistant/components/zoneminder/config_flow.py b/homeassistant/components/zoneminder/config_flow.py deleted file mode 100644 index 8b0b94107f3..00000000000 --- a/homeassistant/components/zoneminder/config_flow.py +++ /dev/null @@ -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) diff --git a/homeassistant/components/zoneminder/const.py b/homeassistant/components/zoneminder/const.py deleted file mode 100644 index ad890a1d4d6..00000000000 --- a/homeassistant/components/zoneminder/const.py +++ /dev/null @@ -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" diff --git a/homeassistant/components/zoneminder/manifest.json b/homeassistant/components/zoneminder/manifest.json index 13bec8c4d9a..b3a87510e5a 100644 --- a/homeassistant/components/zoneminder/manifest.json +++ b/homeassistant/components/zoneminder/manifest.json @@ -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"] } diff --git a/homeassistant/components/zoneminder/sensor.py b/homeassistant/components/zoneminder/sensor.py index 8605e842813..75531e79e13 100644 --- a/homeassistant/components/zoneminder/sensor.py +++ b/homeassistant/components/zoneminder/sensor.py @@ -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): diff --git a/homeassistant/components/zoneminder/services.yaml b/homeassistant/components/zoneminder/services.yaml index 52e8a3bf0bb..a6fb85b641d 100644 --- a/homeassistant/components/zoneminder/services.yaml +++ b/homeassistant/components/zoneminder/services.yaml @@ -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" diff --git a/homeassistant/components/zoneminder/strings.json b/homeassistant/components/zoneminder/strings.json deleted file mode 100644 index 2973e193b86..00000000000 --- a/homeassistant/components/zoneminder/strings.json +++ /dev/null @@ -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." } - } -} diff --git a/homeassistant/components/zoneminder/switch.py b/homeassistant/components/zoneminder/switch.py index d8d1cc78797..0428ddbf888 100644 --- a/homeassistant/components/zoneminder/switch.py +++ b/homeassistant/components/zoneminder/switch.py @@ -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.""" diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index c7a29e549f7..5ac224eba2f 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -220,6 +220,5 @@ FLOWS = [ "yeelight", "zerproc", "zha", - "zoneminder", "zwave" ] diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 94d287249b0..52b93e1865e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1099,6 +1099,3 @@ zigpy-znp==0.2.1 # homeassistant.components.zha zigpy==0.26.0 - -# homeassistant.components.zoneminder -zm-py==0.4.0 diff --git a/tests/components/zoneminder/__init__.py b/tests/components/zoneminder/__init__.py deleted file mode 100644 index 9ea5189a7b9..00000000000 --- a/tests/components/zoneminder/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the zoneminder component.""" diff --git a/tests/components/zoneminder/test_binary_sensor.py b/tests/components/zoneminder/test_binary_sensor.py deleted file mode 100644 index ee9883283e9..00000000000 --- a/tests/components/zoneminder/test_binary_sensor.py +++ /dev/null @@ -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" diff --git a/tests/components/zoneminder/test_camera.py b/tests/components/zoneminder/test_camera.py deleted file mode 100644 index 06f4c3554df..00000000000 --- a/tests/components/zoneminder/test_camera.py +++ /dev/null @@ -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" diff --git a/tests/components/zoneminder/test_config_flow.py b/tests/components/zoneminder/test_config_flow.py deleted file mode 100644 index d62183cd0f6..00000000000 --- a/tests/components/zoneminder/test_config_flow.py +++ /dev/null @@ -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 diff --git a/tests/components/zoneminder/test_init.py b/tests/components/zoneminder/test_init.py deleted file mode 100644 index 333106946bd..00000000000 --- a/tests/components/zoneminder/test_init.py +++ /dev/null @@ -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) diff --git a/tests/components/zoneminder/test_sensor.py b/tests/components/zoneminder/test_sensor.py deleted file mode 100644 index 0b9db899387..00000000000 --- a/tests/components/zoneminder/test_sensor.py +++ /dev/null @@ -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" diff --git a/tests/components/zoneminder/test_switch.py b/tests/components/zoneminder/test_switch.py deleted file mode 100644 index 3665b2fa17e..00000000000 --- a/tests/components/zoneminder/test_switch.py +++ /dev/null @@ -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