From fb7462b4c2c7368ca7ba12bde49347b99422e923 Mon Sep 17 00:00:00 2001 From: Brett Jia Date: Sat, 21 May 2022 23:32:14 -0400 Subject: [PATCH] arlo: various fixes and use correct api url for snapshots (#256) * remove Refresh, extend internal timeout, add new action to snapshots * lower timeout, properly listen for last image url * tweak arlo endpoint * promote to official release --- plugins/arlo/package-lock.json | 6 ++-- plugins/arlo/package.json | 2 +- .../arlo/src/arlo_plugin/arlo/arlo_async.py | 13 +++++--- .../src/arlo_plugin/arlo/eventstream_async.py | 2 +- plugins/arlo/src/arlo_plugin/camera.py | 32 ++++--------------- plugins/arlo/src/arlo_plugin/provider.py | 5 ++- 6 files changed, 23 insertions(+), 37 deletions(-) diff --git a/plugins/arlo/package-lock.json b/plugins/arlo/package-lock.json index 5d79d84ac..6be852582 100644 --- a/plugins/arlo/package-lock.json +++ b/plugins/arlo/package-lock.json @@ -1,19 +1,19 @@ { "name": "@scrypted/arlo", - "version": "0.2.9", + "version": "0.2.13", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/arlo", - "version": "0.2.9", + "version": "0.2.13", "devDependencies": { "@scrypted/sdk": "file:../../sdk" } }, "../../sdk": { "name": "@scrypted/sdk", - "version": "0.0.192", + "version": "0.0.196", "dev": true, "license": "ISC", "dependencies": { diff --git a/plugins/arlo/package.json b/plugins/arlo/package.json index 004341ead..58ea77457 100644 --- a/plugins/arlo/package.json +++ b/plugins/arlo/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/arlo", - "version": "0.2.9", + "version": "0.2.13", "description": "Arlo Plugin for Scrypted", "keywords": [ "scrypted", diff --git a/plugins/arlo/src/arlo_plugin/arlo/arlo_async.py b/plugins/arlo/src/arlo_plugin/arlo/arlo_async.py index d568d5fd9..c3063a1e9 100644 --- a/plugins/arlo/src/arlo_plugin/arlo/arlo_async.py +++ b/plugins/arlo/src/arlo_plugin/arlo/arlo_async.py @@ -94,6 +94,7 @@ class Arlo(object): def UseExistingAuth(self, user_id, headers): self.user_id = user_id self.request.session.headers.update(headers) + self.BASE_URL = 'myapi.arlo.com' def LoginMFA(self): self.request = Request() @@ -1580,7 +1581,7 @@ class Arlo(object): return await self.TriggerAndHandleEvent(basestation, resource, ["is"], trigger, callback) def StopStream(self, basestation, camera): - return self.request.post(f'https://{self.BASE_URL}/hmsweb/users/devices/stopStream', {"to":camera.get('parentId'),"from":self.user_id+"_web","resource":"cameras/"+camera. get('deviceId'),"action":"set","responseUrl":"", "publishResponse":True,"transId":self.genTransId(),"properties":{"activityState":"stopUserStream","cameraId":camera.get('deviceId')}}, headers={"xcloudId": camera.get('xCloudId')}) + return self.request.post(f'https://{self.BASE_URL}/hmsweb/users/devices/stopStream', {"to":camera.get('parentId'),"from":self.user_id+"_web","resource":"cameras/"+camera.get('deviceId'),"action":"set","responseUrl":"", "publishResponse":True,"transId":self.genTransId(),"properties":{"activityState":"stopUserStream","cameraId":camera.get('deviceId')}}, headers={"xcloudId": camera.get('xCloudId')}) # nonlocal variable hack for Python 2.x. class nl: @@ -1630,15 +1631,19 @@ class Arlo(object): resource = f"cameras/{camera.get('deviceId')}" def trigger(self): - self.request.post("https://my.arlo.com/hmsweb/users/devices/fullFrameSnapshot", {"to":camera.get("parentId"),"from":self.user_id+"_web","resource":"cameras/"+camera.get("deviceId"),"action":"set","publishResponse":True,"transId":self.genTransId(),"properties":{"activityState":"fullFrameSnapshot"}}, headers={"xcloudId":camera.get("xCloudId")}) + self.request.post(f"https://{self.BASE_URL}/hmsweb/users/devices/fullFrameSnapshot", {"to":camera.get("parentId"),"from":self.user_id+"_web","resource":"cameras/"+camera.get("deviceId"),"action":"set","publishResponse":True,"transId":self.genTransId(),"properties":{"activityState":"fullFrameSnapshot"}}, headers={"xcloudId":camera.get("xCloudId")}) def callback(self, event): - url = event.get("properties", {}).get("presignedFullFrameSnapshotUrl") + properties = event.get("properties", {}) + url = properties.get("presignedFullFrameSnapshotUrl") + if url: + return url + url = properties.get("presignedLastImageUrl") if url: return url return None - return await self.TriggerAndHandleEvent(basestation, resource, ["fullFrameSnapshotAvailable", "is"], trigger, callback) + return await self.TriggerAndHandleEvent(basestation, resource, ["fullFrameSnapshotAvailable", "lastImageSnapshotAvailable", "is"], trigger, callback) # def StartRecording(self, basestation, camera): # """ diff --git a/plugins/arlo/src/arlo_plugin/arlo/eventstream_async.py b/plugins/arlo/src/arlo_plugin/arlo/eventstream_async.py index 72eb1843e..af88c1cd2 100644 --- a/plugins/arlo/src/arlo_plugin/arlo/eventstream_async.py +++ b/plugins/arlo/src/arlo_plugin/arlo/eventstream_async.py @@ -32,7 +32,7 @@ from .logging import logger class EventStream: """This class provides a queue-based EventStream object.""" - def __init__(self, arlo, expire=15): + def __init__(self, arlo, expire=30): self.event_stream = None self.initializing = True self.connected = False diff --git a/plugins/arlo/src/arlo_plugin/camera.py b/plugins/arlo/src/arlo_plugin/camera.py index 7c22cd2e5..305119e95 100644 --- a/plugins/arlo/src/arlo_plugin/camera.py +++ b/plugins/arlo/src/arlo_plugin/camera.py @@ -2,11 +2,12 @@ import asyncio import scrypted_sdk from scrypted_sdk import ScryptedDeviceBase -from scrypted_sdk.types import Camera, VideoCamera, MotionSensor, Battery, Refresh, ScryptedMimeTypes +from scrypted_sdk.types import Camera, VideoCamera, MotionSensor, Battery, ScryptedMimeTypes from .logging import ScryptedDeviceLoggerMixin -class ArloCamera(ScryptedDeviceBase, Camera, VideoCamera, MotionSensor, Battery, Refresh, ScryptedDeviceLoggerMixin): +class ArloCamera(ScryptedDeviceBase, Camera, VideoCamera, MotionSensor, Battery, ScryptedDeviceLoggerMixin): + timeout = 30 nativeId = None arlo_device = None arlo_basestation = None @@ -22,7 +23,7 @@ class ArloCamera(ScryptedDeviceBase, Camera, VideoCamera, MotionSensor, Battery, self.provider = provider self.logger.setLevel(self.provider.get_current_log_level()) - self.update_device_details(arlo_device) + self._update_device_details(arlo_device) self.stop_motion_subscription = False self.start_motion_subscription() @@ -49,7 +50,7 @@ class ArloCamera(ScryptedDeviceBase, Camera, VideoCamera, MotionSensor, Battery, self.logger.info("Getting snapshot from prebuffer") return await real_device.getVideoStream({"refresh": False}) - pic_url = await asyncio.wait_for(self.provider.arlo.TriggerFullFrameSnapshot(self.arlo_basestation, self.arlo_device), timeout=30) + pic_url = await asyncio.wait_for(self.provider.arlo.TriggerFullFrameSnapshot(self.arlo_basestation, self.arlo_device), timeout=self.timeout) self.logger.debug(f"Got snapshot URL for at {pic_url}") if pic_url is None: @@ -78,29 +79,10 @@ class ArloCamera(ScryptedDeviceBase, Camera, VideoCamera, MotionSensor, Battery, async def getVideoStream(self, options=None): self.logger.info("Requesting stream") - rtsp_url = await asyncio.wait_for(self.provider.arlo.StartStream(self.arlo_basestation, self.arlo_device), timeout=30) + rtsp_url = await asyncio.wait_for(self.provider.arlo.StartStream(self.arlo_basestation, self.arlo_device), timeout=self.timeout) self.logger.debug(f"Got stream URL at {rtsp_url}") return await scrypted_sdk.mediaManager.createMediaObject(str.encode(rtsp_url), ScryptedMimeTypes.Url.value) - async def getRefreshFrequency(self): - return 60 - - async def refresh(self, refreshInterface, userInitiated): - self.logger.info(f"{refreshInterface} requested refresh" + userInitiated * " (user initiated)") - - devices = self.provider.arlo.GetDevices('camera') - arlo_device = None - for device in devices: - if device["deviceId"] == self.nativeId: - arlo_device = device - break - - if arlo_device is None: - raise Exception(f"Device {self.nativeId} not found in GetDevices call to Arlo") - - self.update_device_details(arlo_device) - self.arlo_device = arlo_device - - def update_device_details(self, arlo_device): + def _update_device_details(self, arlo_device): self.batteryLevel = arlo_device["properties"].get("batteryLevel") \ No newline at end of file diff --git a/plugins/arlo/src/arlo_plugin/provider.py b/plugins/arlo/src/arlo_plugin/provider.py index ad98c538e..233cf6068 100644 --- a/plugins/arlo/src/arlo_plugin/provider.py +++ b/plugins/arlo/src/arlo_plugin/provider.py @@ -93,13 +93,13 @@ class ArloProvider(ScryptedDeviceBase, Settings, DeviceProvider, DeviceDiscovery headers = self.arlo_auth_headers if headers: self._arlo.UseExistingAuth(self.arlo_user_id, json.loads(headers)) - self.logger.info(f"Initialized Arlo client for {self.arlo_username}, reusing stored auth headers") + self.logger.info(f"Initialized Arlo client, reusing stored auth headers") asyncio.get_event_loop().create_task(self.do_arlo_setup()) return self._arlo else: self._arlo_mfa_complete_auth = self._arlo.LoginMFA() - self.logger.info(f"Initialized Arlo client for {self.arlo_username}, waiting for MFA code") + self.logger.info(f"Initialized Arlo client, waiting for MFA code") return None except Exception as e: self.logger.error(f"Error initializing Arlo client: {type(e)} with message {str(e)}") @@ -219,7 +219,6 @@ class ArloProvider(ScryptedDeviceBase, Settings, DeviceProvider, DeviceDiscovery ScryptedInterface.VideoCamera.value, ScryptedInterface.Camera.value, ScryptedInterface.MotionSensor.value, - ScryptedInterface.Refresh.value, ], "type": ScryptedDeviceType.Camera.value, "providerNativeId": self.nativeId,