Bump yalexs to 2.0.0 (#111706)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
A Björck 2024-03-10 09:11:10 +01:00 committed by GitHub
parent ffcbab1c20
commit e631224372
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 133 additions and 22 deletions

View File

@ -306,6 +306,13 @@ class AugustData(AugustSubscriberMixin):
exc_info=err, exc_info=err,
) )
async def refresh_camera_by_id(self, device_id: str) -> None:
"""Re-fetch doorbell/camera data from API."""
await self._async_update_device_detail(
self._doorbells_by_id[device_id],
self._api.async_get_doorbell_detail,
)
async def _async_refresh_device_detail_by_id(self, device_id: str) -> None: async def _async_refresh_device_detail_by_id(self, device_id: str) -> None:
if device_id in self._locks_by_id: if device_id in self._locks_by_id:
if self.activity_stream and self.activity_stream.pubnub.connected: if self.activity_stream and self.activity_stream.pubnub.connected:

View File

@ -2,9 +2,12 @@
from __future__ import annotations from __future__ import annotations
import logging
from aiohttp import ClientSession from aiohttp import ClientSession
from yalexs.activity import ActivityType from yalexs.activity import ActivityType
from yalexs.doorbell import Doorbell from yalexs.const import Brand
from yalexs.doorbell import ContentTokenExpired, Doorbell
from yalexs.util import update_doorbell_image_from_activity from yalexs.util import update_doorbell_image_from_activity
from homeassistant.components.camera import Camera from homeassistant.components.camera import Camera
@ -17,6 +20,8 @@ from . import AugustData
from .const import DEFAULT_NAME, DEFAULT_TIMEOUT, DOMAIN from .const import DEFAULT_NAME, DEFAULT_TIMEOUT, DOMAIN
from .entity import AugustEntityMixin from .entity import AugustEntityMixin
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
@ -48,6 +53,7 @@ class AugustCamera(AugustEntityMixin, Camera):
self._timeout = timeout self._timeout = timeout
self._session = session self._session = session
self._image_url = None self._image_url = None
self._content_token = None
self._image_content = None self._image_content = None
self._attr_unique_id = f"{self._device_id:s}_camera" self._attr_unique_id = f"{self._device_id:s}_camera"
self._attr_motion_detection_enabled = True self._attr_motion_detection_enabled = True
@ -63,6 +69,12 @@ class AugustCamera(AugustEntityMixin, Camera):
"""Return the camera model.""" """Return the camera model."""
return self._detail.model return self._detail.model
async def _async_update(self):
"""Update device."""
_LOGGER.debug("async_update called %s", self._detail.device_name)
await self._data.refresh_camera_by_id(self._device_id)
self._update_from_data()
@callback @callback
def _update_from_data(self) -> None: def _update_from_data(self) -> None:
"""Get the latest state of the sensor.""" """Get the latest state of the sensor."""
@ -70,7 +82,6 @@ class AugustCamera(AugustEntityMixin, Camera):
self._device_id, self._device_id,
{ActivityType.DOORBELL_MOTION, ActivityType.DOORBELL_IMAGE_CAPTURE}, {ActivityType.DOORBELL_MOTION, ActivityType.DOORBELL_IMAGE_CAPTURE},
) )
if doorbell_activity is not None: if doorbell_activity is not None:
update_doorbell_image_from_activity(self._detail, doorbell_activity) update_doorbell_image_from_activity(self._detail, doorbell_activity)
@ -82,7 +93,23 @@ class AugustCamera(AugustEntityMixin, Camera):
if self._image_url is not self._detail.image_url: if self._image_url is not self._detail.image_url:
self._image_url = self._detail.image_url self._image_url = self._detail.image_url
self._image_content = await self._detail.async_get_doorbell_image( self._content_token = self._detail.content_token or self._content_token
self._session, timeout=self._timeout _LOGGER.debug(
"calling doorbell async_get_doorbell_image, %s",
self._detail.device_name,
) )
try:
self._image_content = await self._detail.async_get_doorbell_image(
self._session, timeout=self._timeout
)
except ContentTokenExpired:
if self._data.brand == Brand.YALE_HOME:
_LOGGER.debug(
"Error fetching camera image, updating content-token from api to retry"
)
await self._async_update()
self._image_content = await self._detail.async_get_doorbell_image(
self._session, timeout=self._timeout
)
return self._image_content return self._image_content

View File

@ -28,5 +28,5 @@
"documentation": "https://www.home-assistant.io/integrations/august", "documentation": "https://www.home-assistant.io/integrations/august",
"iot_class": "cloud_push", "iot_class": "cloud_push",
"loggers": ["pubnub", "yalexs"], "loggers": ["pubnub", "yalexs"],
"requirements": ["yalexs==1.11.4", "yalexs-ble==2.4.2"] "requirements": ["yalexs==2.0.0", "yalexs-ble==2.4.2"]
} }

View File

@ -2893,7 +2893,7 @@ yalesmartalarmclient==0.3.9
yalexs-ble==2.4.2 yalexs-ble==2.4.2
# homeassistant.components.august # homeassistant.components.august
yalexs==1.11.4 yalexs==2.0.0
# homeassistant.components.yeelight # homeassistant.components.yeelight
yeelight==0.7.14 yeelight==0.7.14

View File

@ -2228,7 +2228,7 @@ yalesmartalarmclient==0.3.9
yalexs-ble==2.4.2 yalexs-ble==2.4.2
# homeassistant.components.august # homeassistant.components.august
yalexs==1.11.4 yalexs==2.0.0
# homeassistant.components.yeelight # homeassistant.components.yeelight
yeelight==0.7.14 yeelight==0.7.14

View File

@ -26,11 +26,12 @@ from yalexs.activity import (
LockOperationActivity, LockOperationActivity,
) )
from yalexs.authenticator import AuthenticationState from yalexs.authenticator import AuthenticationState
from yalexs.const import Brand
from yalexs.doorbell import Doorbell, DoorbellDetail from yalexs.doorbell import Doorbell, DoorbellDetail
from yalexs.lock import Lock, LockDetail from yalexs.lock import Lock, LockDetail
from yalexs.pubnub_async import AugustPubNub from yalexs.pubnub_async import AugustPubNub
from homeassistant.components.august.const import CONF_LOGIN_METHOD, DOMAIN from homeassistant.components.august.const import CONF_BRAND, CONF_LOGIN_METHOD, DOMAIN
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -38,13 +39,14 @@ from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry, load_fixture from tests.common import MockConfigEntry, load_fixture
def _mock_get_config(): def _mock_get_config(brand: Brand = Brand.AUGUST):
"""Return a default august config.""" """Return a default august config."""
return { return {
DOMAIN: { DOMAIN: {
CONF_LOGIN_METHOD: "email", CONF_LOGIN_METHOD: "email",
CONF_USERNAME: "mocked_username", CONF_USERNAME: "mocked_username",
CONF_PASSWORD: "mocked_password", CONF_PASSWORD: "mocked_password",
CONF_BRAND: brand,
} }
} }
@ -59,7 +61,7 @@ def _mock_authenticator(auth_state):
@patch("homeassistant.components.august.gateway.ApiAsync") @patch("homeassistant.components.august.gateway.ApiAsync")
@patch("homeassistant.components.august.gateway.AuthenticatorAsync.async_authenticate") @patch("homeassistant.components.august.gateway.AuthenticatorAsync.async_authenticate")
async def _mock_setup_august( async def _mock_setup_august(
hass, api_instance, pubnub_mock, authenticate_mock, api_mock hass, api_instance, pubnub_mock, authenticate_mock, api_mock, brand
): ):
"""Set up august integration.""" """Set up august integration."""
authenticate_mock.side_effect = MagicMock( authenticate_mock.side_effect = MagicMock(
@ -70,7 +72,7 @@ async def _mock_setup_august(
api_mock.return_value = api_instance api_mock.return_value = api_instance
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
data=_mock_get_config()[DOMAIN], data=_mock_get_config(brand)[DOMAIN],
options={}, options={},
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
@ -88,21 +90,26 @@ async def _create_august_with_devices(
api_call_side_effects: dict[str, Any] | None = None, api_call_side_effects: dict[str, Any] | None = None,
activities: list[Any] | None = None, activities: list[Any] | None = None,
pubnub: AugustPubNub | None = None, pubnub: AugustPubNub | None = None,
brand: Brand = Brand.AUGUST,
) -> ConfigEntry: ) -> ConfigEntry:
entry, _ = await _create_august_api_with_devices( entry, _ = await _create_august_api_with_devices(
hass, devices, api_call_side_effects, activities, pubnub hass, devices, api_call_side_effects, activities, pubnub, brand
) )
return entry return entry
async def _create_august_api_with_devices( # noqa: C901 async def _create_august_api_with_devices( # noqa: C901
hass, devices, api_call_side_effects=None, activities=None, pubnub=None hass,
devices,
api_call_side_effects=None,
activities=None,
pubnub=None,
brand=Brand.AUGUST,
): ):
if api_call_side_effects is None: if api_call_side_effects is None:
api_call_side_effects = {} api_call_side_effects = {}
if pubnub is None: if pubnub is None:
pubnub = AugustPubNub() pubnub = AugustPubNub()
device_data = {"doorbells": [], "locks": []} device_data = {"doorbells": [], "locks": []}
for device in devices: for device in devices:
if isinstance(device, LockDetail): if isinstance(device, LockDetail):
@ -111,7 +118,13 @@ async def _create_august_api_with_devices( # noqa: C901
) )
elif isinstance(device, DoorbellDetail): elif isinstance(device, DoorbellDetail):
device_data["doorbells"].append( device_data["doorbells"].append(
{"base": _mock_august_doorbell(device.device_id), "detail": device} {
"base": _mock_august_doorbell(
deviceid=device.device_id,
brand=device._data.get("brand", Brand.AUGUST),
),
"detail": device,
}
) )
else: else:
raise ValueError # noqa: TRY004 raise ValueError # noqa: TRY004
@ -182,7 +195,7 @@ async def _create_august_api_with_devices( # noqa: C901
) )
api_instance, entry = await _mock_setup_august_with_api_side_effects( api_instance, entry = await _mock_setup_august_with_api_side_effects(
hass, api_call_side_effects, pubnub hass, api_call_side_effects, pubnub, brand
) )
if device_data["locks"]: if device_data["locks"]:
@ -193,7 +206,9 @@ async def _create_august_api_with_devices( # noqa: C901
return entry, api_instance return entry, api_instance
async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects, pubnub): async def _mock_setup_august_with_api_side_effects(
hass, api_call_side_effects, pubnub, brand=Brand.AUGUST
):
api_instance = MagicMock(name="Api") api_instance = MagicMock(name="Api")
if api_call_side_effects["get_lock_detail"]: if api_call_side_effects["get_lock_detail"]:
@ -236,7 +251,9 @@ async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects,
api_instance.async_status_async = AsyncMock() api_instance.async_status_async = AsyncMock()
api_instance.async_get_user = AsyncMock(return_value={"UserID": "abc"}) api_instance.async_get_user = AsyncMock(return_value={"UserID": "abc"})
return api_instance, await _mock_setup_august(hass, api_instance, pubnub) return api_instance, await _mock_setup_august(
hass, api_instance, pubnub, brand=brand
)
def _mock_august_authentication(token_text, token_timestamp, state): def _mock_august_authentication(token_text, token_timestamp, state):
@ -253,13 +270,18 @@ def _mock_august_lock(lockid="mocklockid1", houseid="mockhouseid1"):
return Lock(lockid, _mock_august_lock_data(lockid=lockid, houseid=houseid)) return Lock(lockid, _mock_august_lock_data(lockid=lockid, houseid=houseid))
def _mock_august_doorbell(deviceid="mockdeviceid1", houseid="mockhouseid1"): def _mock_august_doorbell(
deviceid="mockdeviceid1", houseid="mockhouseid1", brand=Brand.AUGUST
):
return Doorbell( return Doorbell(
deviceid, _mock_august_doorbell_data(deviceid=deviceid, houseid=houseid) deviceid,
_mock_august_doorbell_data(deviceid=deviceid, houseid=houseid, brand=brand),
) )
def _mock_august_doorbell_data(deviceid="mockdeviceid1", houseid="mockhouseid1"): def _mock_august_doorbell_data(
deviceid="mockdeviceid1", houseid="mockhouseid1", brand=Brand.AUGUST
):
return { return {
"_id": deviceid, "_id": deviceid,
"DeviceID": deviceid, "DeviceID": deviceid,

View File

@ -3,6 +3,9 @@
from http import HTTPStatus from http import HTTPStatus
from unittest.mock import patch from unittest.mock import patch
from yalexs.const import Brand
from yalexs.doorbell import ContentTokenExpired
from homeassistant.const import STATE_IDLE from homeassistant.const import STATE_IDLE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -20,7 +23,7 @@ async def test_create_doorbell(
with patch.object( with patch.object(
doorbell_one, "async_get_doorbell_image", create=False, return_value="image" doorbell_one, "async_get_doorbell_image", create=False, return_value="image"
): ):
await _create_august_with_devices(hass, [doorbell_one]) await _create_august_with_devices(hass, [doorbell_one], brand=Brand.AUGUST)
camera_k98gidt45gul_name_camera = hass.states.get( camera_k98gidt45gul_name_camera = hass.states.get(
"camera.k98gidt45gul_name_camera" "camera.k98gidt45gul_name_camera"
@ -36,3 +39,55 @@ async def test_create_doorbell(
assert resp.status == HTTPStatus.OK assert resp.status == HTTPStatus.OK
body = await resp.text() body = await resp.text()
assert body == "image" assert body == "image"
async def test_doorbell_refresh_content_token_recover(
hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator
) -> None:
"""Test camera image content token expired."""
doorbell_two = await _mock_doorbell_from_fixture(hass, "get_doorbell.json")
with patch.object(
doorbell_two,
"async_get_doorbell_image",
create=False,
side_effect=[ContentTokenExpired, "image"],
):
await _create_august_with_devices(
hass,
[doorbell_two],
brand=Brand.YALE_HOME,
)
url = hass.states.get("camera.k98gidt45gul_name_camera").attributes[
"entity_picture"
]
client = await hass_client_no_auth()
resp = await client.get(url)
assert resp.status == HTTPStatus.OK
body = await resp.text()
assert body == "image"
async def test_doorbell_refresh_content_token_fail(
hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator
) -> None:
"""Test camera image content token expired."""
doorbell_two = await _mock_doorbell_from_fixture(hass, "get_doorbell.json")
with patch.object(
doorbell_two,
"async_get_doorbell_image",
create=False,
side_effect=ContentTokenExpired,
):
await _create_august_with_devices(
hass,
[doorbell_two],
brand=Brand.YALE_HOME,
)
url = hass.states.get("camera.k98gidt45gul_name_camera").attributes[
"entity_picture"
]
client = await hass_client_no_auth()
resp = await client.get(url)
assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR