-
-
Notifications
You must be signed in to change notification settings - Fork 37.5k
Add Camera platform to Prosegur #76428
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ee34dd8
50e690a
82f4e3e
75a2d69
817f036
93fbaa7
16055c3
4bdd855
c535be0
874a34c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | ||
|
dgomes marked this conversation as resolved.
|
||
| 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, | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,3 +3,5 @@ | |
| DOMAIN = "prosegur" | ||
|
|
||
| CONF_COUNTRY = "country" | ||
|
|
||
| SERVICE_REQUEST_IMAGE = "request_image" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| request_image: | ||
| name: Request Camera image | ||
| description: Request a new image from a Prosegur Camera | ||
| target: | ||
| entity: | ||
| domain: camera | ||
|
dgomes marked this conversation as resolved.
|
||
| integration: prosegur | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The installation doesn't need to be an
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It actually does:
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Either mock the attributes of the installation explicitly or spec the installation according to the original target.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| 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()): | ||
|
dgomes marked this conversation as resolved.
|
||
| await hass.config_entries.async_setup(mock_config_entry.entry_id) | ||
| await hass.async_block_till_done() | ||
|
|
||
| return mock_config_entry | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need this? We have an update entity service
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The camera sends a still picture through an MMS service, so it's not advisable to update periodically and only use it seldomly (Prosegur will throttle your request heavily).
Prosegur Mobile App also requires this explicit request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some more explanation:
This service is therefore independent from update entity service which will periodically get the last camera capture available on Prosegur server.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The update entity service allows to request a single update (yes, it could be used periodically in an automation, but by itself it isn't periodically).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update_service -> request image from Prosegur server
request_image -> request Prosegur to refresh it's server stored image