mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Add Camera platform to Prosegur (#76428)
* add camera to prosegur * add tests * address review * better tests * clean * clean * fix tests * leftover from merge * sorting missing * Update homeassistant/components/prosegur/services.yaml Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> --------- Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
parent
9be3f86a4c
commit
c8fc2dc440
@ -11,7 +11,7 @@ from homeassistant.helpers import aiohttp_client
|
||||
|
||||
from .const import CONF_COUNTRY, DOMAIN
|
||||
|
||||
PLATFORMS = [Platform.ALARM_CONTROL_PANEL]
|
||||
PLATFORMS = [Platform.ALARM_CONTROL_PANEL, Platform.CAMERA]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -15,6 +15,7 @@ from homeassistant.const import (
|
||||
STATE_ALARM_DISARMED,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import DOMAIN
|
||||
@ -59,6 +60,14 @@ class ProsegurAlarm(alarm.AlarmControlPanelEntity):
|
||||
self._attr_name = f"contract {self.contract}"
|
||||
self._attr_unique_id = self.contract
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
name="Prosegur Alarm",
|
||||
manufacturer="Prosegur",
|
||||
model="smart",
|
||||
identifiers={(DOMAIN, self.contract)},
|
||||
configuration_url="https://smart.prosegur.com",
|
||||
)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update alarm status."""
|
||||
|
||||
|
97
homeassistant/components/prosegur/camera.py
Normal file
97
homeassistant/components/prosegur/camera.py
Normal file
@ -0,0 +1,97 @@
|
||||
"""Support for Prosegur cameras."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from pyprosegur.auth import Auth
|
||||
from pyprosegur.exceptions import ProsegurException
|
||||
from pyprosegur.installation import Camera as InstallationCamera, Installation
|
||||
|
||||
from homeassistant.components.camera import Camera
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
AddEntitiesCallback,
|
||||
async_get_current_platform,
|
||||
)
|
||||
|
||||
from . import DOMAIN
|
||||
from .const import SERVICE_REQUEST_IMAGE
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up the Prosegur camera platform."""
|
||||
|
||||
platform = async_get_current_platform()
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_REQUEST_IMAGE,
|
||||
{},
|
||||
"async_request_image",
|
||||
)
|
||||
|
||||
_installation = await Installation.retrieve(hass.data[DOMAIN][entry.entry_id])
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
ProsegurCamera(_installation, camera, hass.data[DOMAIN][entry.entry_id])
|
||||
for camera in _installation.cameras
|
||||
],
|
||||
update_before_add=True,
|
||||
)
|
||||
|
||||
|
||||
class ProsegurCamera(Camera):
|
||||
"""Representation of a Smart Prosegur Camera."""
|
||||
|
||||
def __init__(
|
||||
self, installation: Installation, camera: InstallationCamera, auth: Auth
|
||||
) -> None:
|
||||
"""Initialize Prosegur Camera component."""
|
||||
Camera.__init__(self)
|
||||
|
||||
self._installation = installation
|
||||
self._camera = camera
|
||||
self._auth = auth
|
||||
self._attr_name = camera.description
|
||||
self._attr_unique_id = f"{self._installation.contract} {camera.id}"
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
name=self._camera.description,
|
||||
manufacturer="Prosegur",
|
||||
model="smart camera",
|
||||
identifiers={(DOMAIN, self._installation.contract)},
|
||||
configuration_url="https://smart.prosegur.com",
|
||||
)
|
||||
|
||||
async def async_camera_image(
|
||||
self, width: int | None = None, height: int | None = None
|
||||
) -> bytes | None:
|
||||
"""Return bytes of camera image."""
|
||||
|
||||
try:
|
||||
_LOGGER.debug("Get image for %s", self._camera.description)
|
||||
return await self._installation.get_image(self._auth, self._camera.id)
|
||||
|
||||
except ProsegurException as err:
|
||||
_LOGGER.error("Image %s doesn't exist: %s", self._camera.description, err)
|
||||
|
||||
return None
|
||||
|
||||
async def async_request_image(self):
|
||||
"""Request new image from the camera."""
|
||||
|
||||
try:
|
||||
_LOGGER.debug("Request image for %s", self._camera.description)
|
||||
await self._installation.request_image(self._auth, self._camera.id)
|
||||
|
||||
except ProsegurException as err:
|
||||
_LOGGER.error(
|
||||
"Could not request image from camera %s: %s",
|
||||
self._camera.description,
|
||||
err,
|
||||
)
|
@ -3,3 +3,5 @@
|
||||
DOMAIN = "prosegur"
|
||||
|
||||
CONF_COUNTRY = "country"
|
||||
|
||||
SERVICE_REQUEST_IMAGE = "request_image"
|
||||
|
29
homeassistant/components/prosegur/diagnostics.py
Normal file
29
homeassistant/components/prosegur/diagnostics.py
Normal file
@ -0,0 +1,29 @@
|
||||
"""Diagnostics support for Prosegur."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pyprosegur.installation import Installation
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
TO_REDACT = {"description", "latitude", "longitude", "contractId", "address"}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
|
||||
installation = await Installation.retrieve(hass.data[DOMAIN][entry.entry_id])
|
||||
|
||||
activity = await installation.activity(hass.data[DOMAIN][entry.entry_id])
|
||||
|
||||
return {
|
||||
"installation": async_redact_data(installation.data, TO_REDACT),
|
||||
"activity": activity,
|
||||
}
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/prosegur",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyprosegur"],
|
||||
"requirements": ["pyprosegur==0.0.5"]
|
||||
"requirements": ["pyprosegur==0.0.8"]
|
||||
}
|
||||
|
7
homeassistant/components/prosegur/services.yaml
Normal file
7
homeassistant/components/prosegur/services.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
request_image:
|
||||
name: Request Camera image
|
||||
description: Request a new image from a Prosegur Camera
|
||||
target:
|
||||
entity:
|
||||
domain: camera
|
||||
integration: prosegur
|
@ -1884,7 +1884,7 @@ pypoint==2.3.0
|
||||
pyprof2calltree==1.4.5
|
||||
|
||||
# homeassistant.components.prosegur
|
||||
pyprosegur==0.0.5
|
||||
pyprosegur==0.0.8
|
||||
|
||||
# homeassistant.components.prusalink
|
||||
pyprusalink==1.1.0
|
||||
|
@ -1364,7 +1364,7 @@ pypoint==2.3.0
|
||||
pyprof2calltree==1.4.5
|
||||
|
||||
# homeassistant.components.prosegur
|
||||
pyprosegur==0.0.5
|
||||
pyprosegur==0.0.8
|
||||
|
||||
# homeassistant.components.prusalink
|
||||
pyprusalink==1.1.0
|
||||
|
@ -1,27 +0,0 @@
|
||||
"""Common methods used across tests for Prosegur."""
|
||||
from homeassistant.components.prosegur import DOMAIN as PROSEGUR_DOMAIN
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
CONTRACT = "1234abcd"
|
||||
|
||||
|
||||
async def setup_platform(hass):
|
||||
"""Set up the Prosegur platform."""
|
||||
mock_entry = MockConfigEntry(
|
||||
domain=PROSEGUR_DOMAIN,
|
||||
data={
|
||||
"contract": "1234abcd",
|
||||
CONF_USERNAME: "user@email.com",
|
||||
CONF_PASSWORD: "password",
|
||||
"country": "PT",
|
||||
},
|
||||
)
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
assert await async_setup_component(hass, PROSEGUR_DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return mock_entry
|
58
tests/components/prosegur/conftest.py
Normal file
58
tests/components/prosegur/conftest.py
Normal file
@ -0,0 +1,58 @@
|
||||
"""Define test fixtures for Prosegur."""
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from pyprosegur.installation import Camera
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.prosegur import DOMAIN as PROSEGUR_DOMAIN
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
CONTRACT = "1234abcd"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry() -> MockConfigEntry:
|
||||
"""Return the default mocked config entry."""
|
||||
return MockConfigEntry(
|
||||
domain=PROSEGUR_DOMAIN,
|
||||
data={
|
||||
"contract": CONTRACT,
|
||||
CONF_USERNAME: "user@email.com",
|
||||
CONF_PASSWORD: "password",
|
||||
"country": "PT",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_install() -> AsyncMock:
|
||||
"""Return the mocked alarm install."""
|
||||
install = AsyncMock()
|
||||
install.contract = CONTRACT
|
||||
install.cameras = [Camera("1", "test_cam")]
|
||||
install.get_image = AsyncMock(return_value=b"ABC")
|
||||
install.request_image = AsyncMock()
|
||||
|
||||
install.data = {"contract": CONTRACT}
|
||||
install.activity = AsyncMock(return_value={"event": "armed"})
|
||||
|
||||
return install
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def init_integration(
|
||||
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_install: AsyncMock
|
||||
) -> MockConfigEntry:
|
||||
"""Set up the Prosegur integration for testing."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"pyprosegur.installation.Installation.retrieve", return_value=mock_install
|
||||
), patch("pyprosegur.auth.Auth.login", return_value=AsyncMock()):
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return mock_config_entry
|
@ -20,7 +20,7 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_component, entity_registry as er
|
||||
|
||||
from .common import CONTRACT, setup_platform
|
||||
from .conftest import CONTRACT
|
||||
|
||||
PROSEGUR_ALARM_ENTITY = f"alarm_control_panel.contract_{CONTRACT}"
|
||||
|
||||
@ -38,17 +38,17 @@ def mock_status(request):
|
||||
"""Mock the status of the alarm."""
|
||||
|
||||
install = AsyncMock()
|
||||
install.contract = "123"
|
||||
install.installationId = "1234abcd"
|
||||
install.contract = CONTRACT
|
||||
install.status = request.param
|
||||
|
||||
with patch("pyprosegur.installation.Installation.retrieve", return_value=install):
|
||||
yield
|
||||
|
||||
|
||||
async def test_entity_registry(hass: HomeAssistant, mock_auth, mock_status) -> None:
|
||||
async def test_entity_registry(
|
||||
hass: HomeAssistant, init_integration, mock_auth, mock_status
|
||||
) -> None:
|
||||
"""Tests that the devices are registered in the entity registry."""
|
||||
await setup_platform(hass)
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
entry = entity_registry.async_get(PROSEGUR_ALARM_ENTITY)
|
||||
@ -59,11 +59,13 @@ async def test_entity_registry(hass: HomeAssistant, mock_auth, mock_status) -> N
|
||||
|
||||
state = hass.states.get(PROSEGUR_ALARM_ENTITY)
|
||||
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "contract 1234abcd"
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == f"contract {CONTRACT}"
|
||||
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 3
|
||||
|
||||
|
||||
async def test_connection_error(hass: HomeAssistant, mock_auth) -> None:
|
||||
async def test_connection_error(
|
||||
hass: HomeAssistant, init_integration, mock_auth, mock_config_entry
|
||||
) -> None:
|
||||
"""Test the alarm control panel when connection can't be made to the cloud service."""
|
||||
|
||||
install = AsyncMock()
|
||||
@ -73,8 +75,6 @@ async def test_connection_error(hass: HomeAssistant, mock_auth) -> None:
|
||||
install.status = Status.ARMED
|
||||
|
||||
with patch("pyprosegur.installation.Installation.retrieve", return_value=install):
|
||||
await setup_platform(hass)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
with patch(
|
||||
@ -95,7 +95,7 @@ async def test_connection_error(hass: HomeAssistant, mock_auth) -> None:
|
||||
],
|
||||
)
|
||||
async def test_arm(
|
||||
hass: HomeAssistant, mock_auth, code, alarm_service, alarm_state
|
||||
hass: HomeAssistant, init_integration, mock_auth, code, alarm_service, alarm_state
|
||||
) -> None:
|
||||
"""Test the alarm control panel can be set to away."""
|
||||
|
||||
@ -106,8 +106,6 @@ async def test_arm(
|
||||
install.status = code
|
||||
|
||||
with patch("pyprosegur.installation.Installation.retrieve", return_value=install):
|
||||
await setup_platform(hass)
|
||||
|
||||
await hass.services.async_call(
|
||||
ALARM_DOMAIN,
|
||||
alarm_service,
|
||||
|
69
tests/components/prosegur/test_camera.py
Normal file
69
tests/components/prosegur/test_camera.py
Normal file
@ -0,0 +1,69 @@
|
||||
"""The camera tests for the prosegur platform."""
|
||||
import logging
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from pyprosegur.exceptions import ProsegurException
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import camera
|
||||
from homeassistant.components.camera import Image
|
||||
from homeassistant.components.prosegur.const import DOMAIN
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
|
||||
async def test_camera(hass, init_integration):
|
||||
"""Test prosegur get_image."""
|
||||
|
||||
image = await camera.async_get_image(hass, "camera.test_cam")
|
||||
|
||||
assert image == Image(content_type="image/jpeg", content=b"ABC")
|
||||
|
||||
|
||||
async def test_camera_fail(hass, init_integration, mock_install, caplog):
|
||||
"""Test prosegur get_image fails."""
|
||||
|
||||
mock_install.get_image = AsyncMock(
|
||||
return_value=b"ABC", side_effect=ProsegurException()
|
||||
)
|
||||
|
||||
with caplog.at_level(logging.ERROR, logger="homeassistant.components.prosegur"):
|
||||
try:
|
||||
await camera.async_get_image(hass, "camera.test_cam")
|
||||
except HomeAssistantError as exc:
|
||||
assert str(exc) == "Unable to get image"
|
||||
else:
|
||||
assert pytest.fail()
|
||||
|
||||
assert "Image test_cam doesn't exist" in caplog.text
|
||||
|
||||
|
||||
async def test_request_image(hass, init_integration, mock_install):
|
||||
"""Test the camera request image service."""
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
"request_image",
|
||||
{ATTR_ENTITY_ID: "camera.test_cam"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_install.request_image.called
|
||||
|
||||
|
||||
async def test_request_image_fail(hass, init_integration, mock_install, caplog):
|
||||
"""Test the camera request image service fails."""
|
||||
|
||||
mock_install.request_image = AsyncMock(side_effect=ProsegurException())
|
||||
|
||||
with caplog.at_level(logging.ERROR, logger="homeassistant.components.prosegur"):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
"request_image",
|
||||
{ATTR_ENTITY_ID: "camera.test_cam"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_install.request_image.called
|
||||
|
||||
assert "Could not request image from camera test_cam" in caplog.text
|
21
tests/components/prosegur/test_diagnostics.py
Normal file
21
tests/components/prosegur/test_diagnostics.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""Test Prosegur diagnostics."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from tests.components.diagnostics import get_diagnostics_for_config_entry
|
||||
|
||||
|
||||
async def test_diagnostics(hass, hass_client, init_integration, mock_install):
|
||||
"""Test generating diagnostics for a config entry."""
|
||||
|
||||
with patch(
|
||||
"pyprosegur.installation.Installation.retrieve", return_value=mock_install
|
||||
):
|
||||
diag = await get_diagnostics_for_config_entry(
|
||||
hass, hass_client, init_integration
|
||||
)
|
||||
|
||||
assert diag == {
|
||||
"installation": {"contract": "1234abcd"},
|
||||
"activity": {"event": "armed"},
|
||||
}
|
@ -1,12 +1,10 @@
|
||||
"""Tests prosegur setup."""
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.prosegur import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
|
||||
|
||||
@ -17,59 +15,28 @@ from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
ConnectionError,
|
||||
],
|
||||
)
|
||||
async def test_setup_entry_fail_retrieve(hass: HomeAssistant, error) -> None:
|
||||
async def test_setup_entry_fail_retrieve(
|
||||
hass: HomeAssistant, mock_config_entry, error
|
||||
) -> None:
|
||||
"""Test loading the Prosegur entry."""
|
||||
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
"username": "test-username",
|
||||
"password": "test-password",
|
||||
"country": "PT",
|
||||
"contract": "xpto",
|
||||
},
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"pyprosegur.auth.Auth.login",
|
||||
side_effect=error,
|
||||
):
|
||||
assert not await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
assert not await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def test_unload_entry(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
hass: HomeAssistant,
|
||||
init_integration,
|
||||
mock_config_entry,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
) -> None:
|
||||
"""Test unloading the Prosegur entry."""
|
||||
|
||||
aioclient_mock.post(
|
||||
"https://smart.prosegur.com/smart-server/ws/access/login",
|
||||
json={"data": {"token": "123456789"}},
|
||||
)
|
||||
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
"username": "test-username",
|
||||
"password": "test-password",
|
||||
"country": "PT",
|
||||
"contract": "xpto",
|
||||
},
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
install = MagicMock()
|
||||
install.contract = "123"
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.prosegur.config_flow.Installation.retrieve",
|
||||
return_value=install,
|
||||
):
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
assert await hass.config_entries.async_unload(mock_config_entry.entry_id)
|
||||
|
Loading…
x
Reference in New Issue
Block a user