diff --git a/AUTHORS.md b/AUTHORS.md index bc1b256e..7e74f0d1 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -10,3 +10,4 @@ - Scott Newman (https://github.com/greencoder) - Scott Silence (https://github.com/ssilence5) - William Scanlon (https://github.com/w1ll1am23) +- Niccolo Zapponi (https://github.com/nzapponi) diff --git a/simplipy/api.py b/simplipy/api.py index 714d67e0..8a33b847 100644 --- a/simplipy/api.py +++ b/simplipy/api.py @@ -254,6 +254,7 @@ async def get_systems(self) -> Dict[str, System]: system = system_class( self.request, self._get_subscription_data, system_data["location"] ) + await system.update(include_system=False) systems[system_data["sid"]] = system diff --git a/simplipy/camera.py b/simplipy/camera.py new file mode 100644 index 00000000..20467d47 --- /dev/null +++ b/simplipy/camera.py @@ -0,0 +1,114 @@ +import logging +from urllib.parse import urlencode + +from simplipy.entity import Entity + +_LOGGER: logging.Logger = logging.getLogger(__name__) + +MEDIA_URL_BASE: str = "https://media.simplisafe.com/v1" +DEFAULT_VIDEO_WIDTH: int = 1280 +DEFAULT_AUDIO_ENCODING: str = "AAC" + +CAMERA_MODEL_CAMERA: str = "CAMERA" +CAMERA_MODEL_DOORBELL: str = "DOORBELL" +CAMERA_MODEL_UNKNOWN: str = "CAMERA_MODEL_UNKNOWN" + +MODEL_TO_TYPE = { + "SS001": CAMERA_MODEL_CAMERA, + "SS002": CAMERA_MODEL_DOORBELL, +} + + +class Camera(Entity): + """A SimpliCam.""" + + @property + def camera_settings(self) -> dict: + """Return the camera settings. + + :rtype: ``dict`` + """ + return self.entity_data["cameraSettings"] + + @property + def camera_type(self) -> str: + """Return the type of camera. + + :rtype: ``str`` + """ + + try: + return MODEL_TO_TYPE[self.entity_data["model"]] + except KeyError: + _LOGGER.error("Unknown camera type: %s", self.entity_data["model"]) + return CAMERA_MODEL_UNKNOWN + + @property + def name(self) -> str: + """Return the entity name. + + :rtype: ``str`` + """ + return self.entity_data["cameraSettings"]["cameraName"] + + @property + def serial(self) -> str: + """Return the entity's serial number. + + :rtype: ``str`` + """ + return self.entity_data["uuid"] + + @property + def shutter_open_when_away(self) -> bool: + """Return whether the privacy shutter is open when alarm system is armed in away mode. + + :rtype: ``bool`` + """ + return self.camera_settings["shutterAway"] == "open" + + @property + def shutter_open_when_home(self) -> bool: + """Return whether the privacy shutter is open when alarm system is armed in home mode. + + :rtype: ``bool`` + """ + return self.camera_settings["shutterHome"] == "open" + + @property + def shutter_open_when_off(self) -> bool: + """Return whether the privacy shutter is open when alarm system is off. + + :rtype: ``bool`` + """ + return self.camera_settings["shutterOff"] == "open" + + @property + def status(self) -> str: + """Return the camera status. + + :rtype: ``str`` + """ + return self.entity_data["status"] + + @property + def subscription_enabled(self) -> bool: + """Return the camera subscription status. + + :rtype: ``bool`` + """ + return self.entity_data["subscription"]["enabled"] + + def video_url( + self, + width: int = DEFAULT_VIDEO_WIDTH, + audio_encoding: str = DEFAULT_AUDIO_ENCODING, + **kwargs, + ) -> str: + """Return the camera video URL. + + :rtype: ``str`` + """ + url_params = {"x": width, "audioEncoding": audio_encoding, **kwargs} + + return f"{MEDIA_URL_BASE}/{self.serial}/flv?{urlencode(url_params)}" diff --git a/simplipy/system/__init__.py b/simplipy/system/__init__.py index ca02031c..1b832070 100644 --- a/simplipy/system/__init__.py +++ b/simplipy/system/__init__.py @@ -6,6 +6,7 @@ import logging from typing import Any, Callable, Coroutine, Dict, List, Optional, Set, Type, Union +from simplipy.camera import Camera from simplipy.entity import Entity, EntityTypes from simplipy.errors import PinError, SimplipyError from simplipy.lock import Lock @@ -197,6 +198,25 @@ def alarm_going_off(self) -> bool: """ return self._location_info["system"]["isAlarming"] + @property + def cameras(self) -> Dict[str, Camera]: + """Return list of cameras and doorbells. + + :rtype: ``Dict[str, :meth:`simplipy.camera.Camera`]`` + """ + + cameras_doorbells = [ + Camera( + self._request, + self._get_entities, + self.system_id, + EntityTypes.camera, + camera, + ) + for camera in self._location_info["system"]["cameras"] + ] + return {camera.serial: camera for camera in cameras_doorbells} + @property # type: ignore @guard_from_missing_data() def connection_type(self) -> str: diff --git a/tests/common.py b/tests/common.py index b077a442..6e0793a2 100644 --- a/tests/common.py +++ b/tests/common.py @@ -5,6 +5,9 @@ TEST_ACCESS_TOKEN = "abcde12345" TEST_ACCOUNT_ID = 12345 TEST_ADDRESS = "1234 Main Street" +TEST_CAMERA_ID = "1234567890" +TEST_CAMERA_ID_2 = "1234567891" +TEST_CAMERA_TYPE = "CAMERA" TEST_CLIENT_ID = "12345DEFG" TEST_EMAIL = "user@email.com" TEST_LOCK_ID = "987" diff --git a/tests/fixtures/subscriptions_response.json b/tests/fixtures/subscriptions_response.json index 02e306f7..f75f6b2e 100644 --- a/tests/fixtures/subscriptions_response.json +++ b/tests/fixtures/subscriptions_response.json @@ -1,192 +1,742 @@ { - "subscriptions": [ - { - "uid": 12345, - "sid": 12345, - "sStatus": 20, - "activated": 1445034752, - "planSku": "SSEDSM2", - "planName": "Interactive Monitoring", - "price": 24.99, - "currency": "USD", - "country": "US", - "expires": 1602887552, - "canceled": 0, - "extraTime": 0, - "creditCard": { - "lastFour": "", - "type": "", - "ppid": "ABCDE12345", - "uid": 12345 - }, - "time": 2628000, - "paymentProfileId": "ABCDE12345", - "features": { - "monitoring": true, - "alerts": true, - "online": true, - "hazard": true, - "video": true, - "cameras": 10, - "dispatch": true, - "proInstall": false, - "discount": 0, - "vipCS": false, - "medical": true, - "careVisit": false, - "storageDays": 30 - }, - "status": { - "hasBaseStation": true, - "isActive": true, - "monitoring": "Active" - }, - "subscriptionFeatures": { - "monitoredSensorsTypes": [ - "Entry", - "Motion", - "GlassBreak", - "Smoke", - "CO", - "Freeze", - "Water" - ], - "monitoredPanicConditions": [ - "Fire", - "Medical", - "Duress" - ], - "dispatchTypes": [ - "Police", - "Fire", - "Medical", - "Guard" - ], - "remoteControl": [ - "ArmDisarm", - "LockUnlock", - "ViewSettings", - "ConfigureSettings" - ], - "cameraFeatures": { - "liveView": true, - "maxRecordingCameras": 10, - "recordingStorageDays": 30, - "videoVerification": true - }, - "support": { - "level": "Basic", - "annualVisit": false, - "professionalInstall": false - }, - "cellCommunicationBackup": true, - "alertChannels": [ - "Push", - "SMS", - "Email" - ], - "alertTypes": [ - "Alarm", - "Error", - "Activity", - "Camera" - ], - "alarmModes": [ - "Alarm", - "SecretAlert", - "Disabled" - ], - "supportedIntegrations": [ - "GoogleAssistant", - "AmazonAlexa", - "AugustLock" - ], - "timeline": {} - }, - "dispatcher": "cops", - "dcid": 0, - "location": { - "sid": 12345, - "uid": 12345, - "lStatus": 10, - "account": "1234ABCD", - "street1": "1234 Main Street", - "street2": "", - "locationName": "", - "city": "Atlantis", - "county": "SEA", - "state": "UW", - "zip": "12345", - "country": "US", - "crossStreet": "River 1 and River 2", - "notes": "", - "residenceType": 2, - "numAdults": 2, - "numChildren": 0, - "locationOffset": -360, - "safeWord": "TRITON", - "signature": "Atlantis Citizen 1", - "timeZone": 2, - "primaryContacts": [ - { - "name": "John Doe", - "phone": "1234567890" - } - ], - "secondaryContacts": [ - { - "name": "Jane Doe", - "phone": "9876543210" - } - ], - "copsOptIn": false, - "certificateUri": "https://simplisafe.com/account2/12345/alarm-certificate/12345", - "nestStructureId": "", - "system": { - "serial": "1234ABCD", - "alarmState": "OFF", - "alarmStateTimestamp": 0, - "isAlarming": false, - "version": 3, - "capabilities": { - "setWifiOverCell": true, - "setDoorbellChimeVolume": true, - "outdoorBattCamera": true - }, - "temperature": 67, - "exitDelayRemaining": 60, - "cameras": [], - "connType": "wifi", - "stateUpdated": 1601502948, - "messages": [ - { - "_id": "xxxxxxxxxxxxxxxxxxxxxxxx", - "id": "xxxxxxxxxxxxxxxxxxxxxxxx", - "textTemplate": "Power Outage - Backup battery in use.", - "data": { - "time": "2020-02-16T03:20:28+00:00" - }, - "text": "Power Outage - Backup battery in use.", - "code": "2000", - "filters": [], - "link": "http://link.to.info", - "linkLabel": "More Info", - "expiration": 0, - "category": "error", - "timestamp": 1581823228 - } - ], - "powerOutage": false, - "lastPowerOutage": 1581991064, - "lastSuccessfulWifiTS": 1601424776, - "isOffline": false + "subscriptions": [ + { + "uid": 12345, + "sid": 12345, + "sStatus": 20, + "activated": 1445034752, + "planSku": "SSEDSM2", + "planName": "Interactive Monitoring", + "price": 24.99, + "currency": "USD", + "country": "US", + "expires": 1602887552, + "canceled": 0, + "extraTime": 0, + "creditCard": { + "lastFour": "", + "type": "", + "ppid": "ABCDE12345", + "uid": 12345 + }, + "time": 2628000, + "paymentProfileId": "ABCDE12345", + "features": { + "monitoring": true, + "alerts": true, + "online": true, + "hazard": true, + "video": true, + "cameras": 10, + "dispatch": true, + "proInstall": false, + "discount": 0, + "vipCS": false, + "medical": true, + "careVisit": false, + "storageDays": 30 + }, + "status": { + "hasBaseStation": true, + "isActive": true, + "monitoring": "Active" + }, + "subscriptionFeatures": { + "monitoredSensorsTypes": [ + "Entry", + "Motion", + "GlassBreak", + "Smoke", + "CO", + "Freeze", + "Water" + ], + "monitoredPanicConditions": [ + "Fire", + "Medical", + "Duress" + ], + "dispatchTypes": [ + "Police", + "Fire", + "Medical", + "Guard" + ], + "remoteControl": [ + "ArmDisarm", + "LockUnlock", + "ViewSettings", + "ConfigureSettings" + ], + "cameraFeatures": { + "liveView": true, + "maxRecordingCameras": 10, + "recordingStorageDays": 30, + "videoVerification": true + }, + "support": { + "level": "Basic", + "annualVisit": false, + "professionalInstall": false + }, + "cellCommunicationBackup": true, + "alertChannels": [ + "Push", + "SMS", + "Email" + ], + "alertTypes": [ + "Alarm", + "Error", + "Activity", + "Camera" + ], + "alarmModes": [ + "Alarm", + "SecretAlert", + "Disabled" + ], + "supportedIntegrations": [ + "GoogleAssistant", + "AmazonAlexa", + "AugustLock" + ], + "timeline": {} + }, + "dispatcher": "cops", + "dcid": 0, + "location": { + "sid": 12345, + "uid": 12345, + "lStatus": 10, + "account": "1234ABCD", + "street1": "1234 Main Street", + "street2": "", + "locationName": "", + "city": "Atlantis", + "county": "SEA", + "state": "UW", + "zip": "12345", + "country": "US", + "crossStreet": "River 1 and River 2", + "notes": "", + "residenceType": 2, + "numAdults": 2, + "numChildren": 0, + "locationOffset": -360, + "safeWord": "TRITON", + "signature": "Atlantis Citizen 1", + "timeZone": 2, + "primaryContacts": [ + { + "name": "John Doe", + "phone": "1234567890" + } + ], + "secondaryContacts": [ + { + "name": "Jane Doe", + "phone": "9876543210" + } + ], + "copsOptIn": false, + "certificateUri": "https://simplisafe.com/account2/12345/alarm-certificate/12345", + "nestStructureId": "", + "system": { + "serial": "1234ABCD", + "alarmState": "OFF", + "alarmStateTimestamp": 0, + "isAlarming": false, + "version": 3, + "capabilities": { + "setWifiOverCell": true, + "setDoorbellChimeVolume": true, + "outdoorBattCamera": true + }, + "temperature": 67, + "exitDelayRemaining": 60, + "cameras": [ + { + "staleSettingsTypes": [], + "upgradeWhitelisted": false, + "model": "SS001", + "uuid": "1234567890", + "uid": 12345, + "sid": 12345, + "cameraSettings": { + "cameraName": "Camera", + "pictureQuality": "720p", + "nightVision": "auto", + "statusLight": "off", + "micSensitivity": 100, + "micEnable": true, + "speakerVolume": 75, + "motionSensitivity": 0, + "shutterHome": "closedAlarmOnly", + "shutterAway": "open", + "shutterOff": "closedAlarmOnly", + "wifiSsid": "", + "canStream": false, + "canRecord": false, + "pirEnable": true, + "vaEnable": true, + "notificationsEnable": false, + "enableDoorbellNotification": true, + "doorbellChimeVolume": "off", + "privacyEnable": false, + "hdr": false, + "vaZoningEnable": false, + "vaZoningRows": 0, + "vaZoningCols": 0, + "vaZoningMask": [], + "maxDigitalZoom": 10, + "supportedResolutions": [ + "480p", + "720p" + ], + "admin": { + "IRLED": 0, + "pirSens": 0, + "statusLEDState": 1, + "lux": "lowLux", + "motionDetectionEnabled": false, + "motionThresholdZero": 0, + "motionThresholdOne": 10000, + "levelChangeDelayZero": 30, + "levelChangeDelayOne": 10, + "audioDetectionEnabled": false, + "audioChannelNum": 2, + "audioSampleRate": 16000, + "audioChunkBytes": 2048, + "audioSampleFormat": 3, + "audioSensitivity": 50, + "audioThreshold": 50, + "audioDirection": 0, + "bitRate": 284, + "longPress": 2000, + "kframe": 1, + "gopLength": 40, + "idr": 1, + "fps": 20, + "firmwareVersion": "2.6.1.107", + "netConfigVersion": "", + "camAgentVersion": "", + "lastLogin": 1600639997, + "lastLogout": 1600639944, + "pirSampleRateMs": 800, + "pirHysteresisHigh": 2, + "pirHysteresisLow": 10, + "pirFilterCoefficient": 1, + "logEnabled": true, + "logLevel": 3, + "logQDepth": 20, + "firmwareGroup": "public", + "irOpenThreshold": 445, + "irCloseThreshold": 840, + "irOpenDelay": 3, + "irCloseDelay": 3, + "irThreshold1x": 388, + "irThreshold2x": 335, + "irThreshold3x": 260, + "rssi": [ + [ + 1600935204, + -43 + ] + ], + "battery": [], + "dbm": 0, + "vmUse": 161592, + "resSet": 10540, + "uptime": 810043.74, + "wifiDisconnects": 1, + "wifiDriverReloads": 1, + "statsPeriod": 3600000, + "sarlaccDebugLogTypes": 0, + "odProcessingFps": 8, + "odObjectMinWidthPercent": 6, + "odObjectMinHeightPercent": 24, + "odEnableObjectDetection": true, + "odClassificationMask": 2, + "odClassificationConfidenceThreshold": 0.95, + "odEnableOverlay": false, + "odAnalyticsLib": 2, + "odSensitivity": 85, + "odEventObjectMask": 2, + "odLuxThreshold": 445, + "odLuxHysteresisHigh": 4, + "odLuxHysteresisLow": 4, + "odLuxSamplingFrequency": 30, + "odFGExtractorMode": 2, + "odVideoScaleFactor": 1, + "odSceneType": 1, + "odCameraView": 3, + "odCameraFOV": 2, + "odBackgroundLearnStationary": true, + "odBackgroundLearnStationarySpeed": 15, + "odClassifierQualityProfile": 1, + "odEnableVideoAnalyticsWhileStreaming": false, + "wlanMac": "XX:XX:XX:XX:XX:XX", + "region": "us-east-1", + "enableWifiAnalyticsLib": false, + "ivLicense": "" + }, + "pirLevel": "medium", + "odLevel": "medium" + }, + "__v": 0, + "cameraStatus": { + "firmwareVersion": "2.6.1.107", + "netConfigVersion": "", + "camAgentVersion": "", + "lastLogin": 1600639997, + "lastLogout": 1600639944, + "wlanMac": "XX:XX:XX:XX:XX:XX", + "fwDownloadVersion": "", + "fwDownloadPercentage": 0, + "recovered": false, + "recoveredFromVersion": "", + "_id": "1234567890", + "initErrors": [], + "speedTestTokenCreated": 1600235629 + }, + "supportedFeatures": { + "providers": { + "webrtc": "none", + "recording": "simplisafe", + "live": "simplisafe" + }, + "audioEncodings": [ + "speex" + ], + "resolutions": [ + "480p", + "720p" + ], + "_id": "1234567890", + "pir": true, + "videoAnalytics": false, + "privacyShutter": true, + "microphone": true, + "fullDuplexAudio": false, + "wired": true, + "networkSpeedTest": false, + "videoEncoding": "h264" + }, + "subscription": { + "enabled": true, + "freeTrialActive": false, + "freeTrialUsed": true, + "freeTrialEnds": 0, + "freeTrialExpires": 0, + "planSku": "SSVM1", + "price": 0, + "expires": 0, + "storageDays": 30, + "trialUsed": true, + "trialActive": false, + "trialExpires": 0 + }, + "status": "online" + }, + { + "staleSettingsTypes": [], + "upgradeWhitelisted": false, + "model": "SS002", + "uuid": "1234567892", + "uid": 12345, + "sid": 12345, + "cameraSettings": { + "cameraName": "Doorbell", + "pictureQuality": "720p", + "nightVision": "auto", + "statusLight": "off", + "micSensitivity": 100, + "micEnable": true, + "speakerVolume": 75, + "motionSensitivity": 0, + "shutterHome": "closedAlarmOnly", + "shutterAway": "open", + "shutterOff": "closedAlarmOnly", + "wifiSsid": "", + "canStream": false, + "canRecord": false, + "pirEnable": true, + "vaEnable": true, + "notificationsEnable": false, + "enableDoorbellNotification": true, + "doorbellChimeVolume": "off", + "privacyEnable": false, + "hdr": false, + "vaZoningEnable": false, + "vaZoningRows": 0, + "vaZoningCols": 0, + "vaZoningMask": [], + "maxDigitalZoom": 10, + "supportedResolutions": [ + "480p", + "720p" + ], + "admin": { + "IRLED": 0, + "pirSens": 0, + "statusLEDState": 1, + "lux": "lowLux", + "motionDetectionEnabled": false, + "motionThresholdZero": 0, + "motionThresholdOne": 10000, + "levelChangeDelayZero": 30, + "levelChangeDelayOne": 10, + "audioDetectionEnabled": false, + "audioChannelNum": 2, + "audioSampleRate": 16000, + "audioChunkBytes": 2048, + "audioSampleFormat": 3, + "audioSensitivity": 50, + "audioThreshold": 50, + "audioDirection": 0, + "bitRate": 284, + "longPress": 2000, + "kframe": 1, + "gopLength": 40, + "idr": 1, + "fps": 20, + "firmwareVersion": "2.6.1.107", + "netConfigVersion": "", + "camAgentVersion": "", + "lastLogin": 1600639997, + "lastLogout": 1600639944, + "pirSampleRateMs": 800, + "pirHysteresisHigh": 2, + "pirHysteresisLow": 10, + "pirFilterCoefficient": 1, + "logEnabled": true, + "logLevel": 3, + "logQDepth": 20, + "firmwareGroup": "public", + "irOpenThreshold": 445, + "irCloseThreshold": 840, + "irOpenDelay": 3, + "irCloseDelay": 3, + "irThreshold1x": 388, + "irThreshold2x": 335, + "irThreshold3x": 260, + "rssi": [ + [ + 1600935204, + -43 + ] + ], + "battery": [], + "dbm": 0, + "vmUse": 161592, + "resSet": 10540, + "uptime": 810043.74, + "wifiDisconnects": 1, + "wifiDriverReloads": 1, + "statsPeriod": 3600000, + "sarlaccDebugLogTypes": 0, + "odProcessingFps": 8, + "odObjectMinWidthPercent": 6, + "odObjectMinHeightPercent": 24, + "odEnableObjectDetection": true, + "odClassificationMask": 2, + "odClassificationConfidenceThreshold": 0.95, + "odEnableOverlay": false, + "odAnalyticsLib": 2, + "odSensitivity": 85, + "odEventObjectMask": 2, + "odLuxThreshold": 445, + "odLuxHysteresisHigh": 4, + "odLuxHysteresisLow": 4, + "odLuxSamplingFrequency": 30, + "odFGExtractorMode": 2, + "odVideoScaleFactor": 1, + "odSceneType": 1, + "odCameraView": 3, + "odCameraFOV": 2, + "odBackgroundLearnStationary": true, + "odBackgroundLearnStationarySpeed": 15, + "odClassifierQualityProfile": 1, + "odEnableVideoAnalyticsWhileStreaming": false, + "wlanMac": "XX:XX:XX:XX:XX:XX", + "region": "us-east-1", + "enableWifiAnalyticsLib": false, + "ivLicense": "" + }, + "pirLevel": "medium", + "odLevel": "medium" + }, + "__v": 0, + "cameraStatus": { + "firmwareVersion": "2.6.1.107", + "netConfigVersion": "", + "camAgentVersion": "", + "lastLogin": 1600639997, + "lastLogout": 1600639944, + "wlanMac": "XX:XX:XX:XX:XX:XX", + "fwDownloadVersion": "", + "fwDownloadPercentage": 0, + "recovered": false, + "recoveredFromVersion": "", + "_id": "1234567890", + "initErrors": [], + "speedTestTokenCreated": 1600235629 + }, + "supportedFeatures": { + "providers": { + "webrtc": "none", + "recording": "simplisafe", + "live": "simplisafe" + }, + "audioEncodings": [ + "speex" + ], + "resolutions": [ + "480p", + "720p" + ], + "_id": "1234567890", + "pir": true, + "videoAnalytics": false, + "privacyShutter": true, + "microphone": true, + "fullDuplexAudio": false, + "wired": true, + "networkSpeedTest": false, + "videoEncoding": "h264" + }, + "subscription": { + "enabled": true, + "freeTrialActive": false, + "freeTrialUsed": true, + "freeTrialEnds": 0, + "freeTrialExpires": 0, + "planSku": "SSVM1", + "price": 0, + "expires": 0, + "storageDays": 30, + "trialUsed": true, + "trialActive": false, + "trialExpires": 0 + }, + "status": "online" + }, + { + "staleSettingsTypes": [], + "upgradeWhitelisted": false, + "model": "SS003", + "uuid": "1234567891", + "uid": 12345, + "sid": 12345, + "cameraSettings": { + "cameraName": "Unknown Camera", + "pictureQuality": "720p", + "nightVision": "auto", + "statusLight": "off", + "micSensitivity": 100, + "micEnable": true, + "speakerVolume": 75, + "motionSensitivity": 0, + "shutterHome": "closedAlarmOnly", + "shutterAway": "open", + "shutterOff": "closedAlarmOnly", + "wifiSsid": "", + "canStream": false, + "canRecord": false, + "pirEnable": true, + "vaEnable": true, + "notificationsEnable": false, + "enableDoorbellNotification": true, + "doorbellChimeVolume": "off", + "privacyEnable": false, + "hdr": false, + "vaZoningEnable": false, + "vaZoningRows": 0, + "vaZoningCols": 0, + "vaZoningMask": [], + "maxDigitalZoom": 10, + "supportedResolutions": [ + "480p", + "720p" + ], + "admin": { + "IRLED": 0, + "pirSens": 0, + "statusLEDState": 1, + "lux": "lowLux", + "motionDetectionEnabled": false, + "motionThresholdZero": 0, + "motionThresholdOne": 10000, + "levelChangeDelayZero": 30, + "levelChangeDelayOne": 10, + "audioDetectionEnabled": false, + "audioChannelNum": 2, + "audioSampleRate": 16000, + "audioChunkBytes": 2048, + "audioSampleFormat": 3, + "audioSensitivity": 50, + "audioThreshold": 50, + "audioDirection": 0, + "bitRate": 284, + "longPress": 2000, + "kframe": 1, + "gopLength": 40, + "idr": 1, + "fps": 20, + "firmwareVersion": "2.6.1.107", + "netConfigVersion": "", + "camAgentVersion": "", + "lastLogin": 1600639997, + "lastLogout": 1600639944, + "pirSampleRateMs": 800, + "pirHysteresisHigh": 2, + "pirHysteresisLow": 10, + "pirFilterCoefficient": 1, + "logEnabled": true, + "logLevel": 3, + "logQDepth": 20, + "firmwareGroup": "public", + "irOpenThreshold": 445, + "irCloseThreshold": 840, + "irOpenDelay": 3, + "irCloseDelay": 3, + "irThreshold1x": 388, + "irThreshold2x": 335, + "irThreshold3x": 260, + "rssi": [ + [ + 1600935204, + -43 + ] + ], + "battery": [], + "dbm": 0, + "vmUse": 161592, + "resSet": 10540, + "uptime": 810043.74, + "wifiDisconnects": 1, + "wifiDriverReloads": 1, + "statsPeriod": 3600000, + "sarlaccDebugLogTypes": 0, + "odProcessingFps": 8, + "odObjectMinWidthPercent": 6, + "odObjectMinHeightPercent": 24, + "odEnableObjectDetection": true, + "odClassificationMask": 2, + "odClassificationConfidenceThreshold": 0.95, + "odEnableOverlay": false, + "odAnalyticsLib": 2, + "odSensitivity": 85, + "odEventObjectMask": 2, + "odLuxThreshold": 445, + "odLuxHysteresisHigh": 4, + "odLuxHysteresisLow": 4, + "odLuxSamplingFrequency": 30, + "odFGExtractorMode": 2, + "odVideoScaleFactor": 1, + "odSceneType": 1, + "odCameraView": 3, + "odCameraFOV": 2, + "odBackgroundLearnStationary": true, + "odBackgroundLearnStationarySpeed": 15, + "odClassifierQualityProfile": 1, + "odEnableVideoAnalyticsWhileStreaming": false, + "wlanMac": "XX:XX:XX:XX:XX:XX", + "region": "us-east-1", + "enableWifiAnalyticsLib": false, + "ivLicense": "" + }, + "pirLevel": "medium", + "odLevel": "medium" + }, + "__v": 0, + "cameraStatus": { + "firmwareVersion": "2.6.1.107", + "netConfigVersion": "", + "camAgentVersion": "", + "lastLogin": 1600639997, + "lastLogout": 1600639944, + "wlanMac": "XX:XX:XX:XX:XX:XX", + "fwDownloadVersion": "", + "fwDownloadPercentage": 0, + "recovered": false, + "recoveredFromVersion": "", + "_id": "1234567890", + "initErrors": [], + "speedTestTokenCreated": 1600235629 + }, + "supportedFeatures": { + "providers": { + "webrtc": "none", + "recording": "simplisafe", + "live": "simplisafe" + }, + "audioEncodings": [ + "speex" + ], + "resolutions": [ + "480p", + "720p" + ], + "_id": "1234567890", + "pir": true, + "videoAnalytics": false, + "privacyShutter": true, + "microphone": true, + "fullDuplexAudio": false, + "wired": true, + "networkSpeedTest": false, + "videoEncoding": "h264" + }, + "subscription": { + "enabled": true, + "freeTrialActive": false, + "freeTrialUsed": true, + "freeTrialEnds": 0, + "freeTrialExpires": 0, + "planSku": "SSVM1", + "price": 0, + "expires": 0, + "storageDays": 30, + "trialUsed": true, + "trialActive": false, + "trialExpires": 0 + }, + "status": "online" + } + ], + "connType": "wifi", + "stateUpdated": 1601502948, + "messages": [ + { + "_id": "xxxxxxxxxxxxxxxxxxxxxxxx", + "id": "xxxxxxxxxxxxxxxxxxxxxxxx", + "textTemplate": "Power Outage - Backup battery in use.", + "data": { + "time": "2020-02-16T03:20:28+00:00" + }, + "text": "Power Outage - Backup battery in use.", + "code": "2000", + "filters": [], + "link": "http://link.to.info", + "linkLabel": "More Info", + "expiration": 0, + "category": "error", + "timestamp": 1581823228 + } + ], + "powerOutage": false, + "lastPowerOutage": 1581991064, + "lastSuccessfulWifiTS": 1601424776, + "isOffline": false + } + }, + "pinUnlocked": true, + "billDate": 1602887552, + "billInterval": 2628000, + "pinUnlockedBy": "pin", + "autoActivation": null } - }, - "pinUnlocked": true, - "billDate": 1602887552, - "billInterval": 2628000, - "pinUnlockedBy": "pin", - "autoActivation": null - } - ] -} + ] +} \ No newline at end of file diff --git a/tests/system/test_v2.py b/tests/system/test_v2.py index bd672f86..dd13067b 100644 --- a/tests/system/test_v2.py +++ b/tests/system/test_v2.py @@ -98,6 +98,46 @@ async def test_get_systems(aresponses, v2_server, v2_subscriptions_response): assert simplisafe.access_token == TEST_ACCESS_TOKEN assert len(system.sensors) == 35 + +@pytest.mark.asyncio +async def test_get_systems_via_token(aresponses, v2_server, v2_subscriptions_response): + """Test the ability to get systems attached to a v2 account.""" + async with v2_server: + # Since this flow will call both three routes once more each (on top of + # what instantiation does) and aresponses deletes matches each time, + # we need to add additional routes: + v2_server.add( + "api.simplisafe.com", + "/v1/api/token", + "post", + aresponses.Response( + text=load_fixture("api_token_response.json"), status=200 + ), + ) + v2_server.add( + "api.simplisafe.com", + "/v1/api/authCheck", + "get", + aresponses.Response( + text=load_fixture("auth_check_response.json"), status=200 + ), + ) + v2_server.add( + "api.simplisafe.com", + f"/v1/users/{TEST_USER_ID}/subscriptions", + "get", + aresponses.Response(text=v2_subscriptions_response, status=200), + ) + v2_server.add( + "api.simplisafe.com", + f"/v1/subscriptions/{TEST_SUBSCRIPTION_ID}/settings", + "get", + aresponses.Response( + text=load_fixture("v2_settings_response.json"), status=200 + ), + ) + + async with aiohttp.ClientSession() as session: simplisafe = await API.login_via_token( TEST_REFRESH_TOKEN, client_id=TEST_CLIENT_ID, session=session ) diff --git a/tests/system/test_v3.py b/tests/system/test_v3.py index 38065f6d..0f6acdfa 100644 --- a/tests/system/test_v3.py +++ b/tests/system/test_v3.py @@ -185,6 +185,54 @@ async def test_get_systems( assert simplisafe.access_token == TEST_ACCESS_TOKEN assert len(system.sensors) == 22 + +@pytest.mark.asyncio +async def test_get_systems_via_token( + aresponses, v3_server, v3_subscriptions_response, v3_settings_response +): + """Test the ability to get systems attached to a v3 account when logging in via token.""" + async with v3_server: + # Since this flow will call both three routes once more each (on top of + # what instantiation does) and aresponses deletes matches each time, + # we need to add additional routes: + v3_server.add( + "api.simplisafe.com", + "/v1/api/token", + "post", + aresponses.Response( + text=load_fixture("api_token_response.json"), status=200 + ), + ) + v3_server.add( + "api.simplisafe.com", + "/v1/api/authCheck", + "get", + aresponses.Response( + text=load_fixture("auth_check_response.json"), status=200 + ), + ) + v3_server.add( + "api.simplisafe.com", + f"/v1/users/{TEST_USER_ID}/subscriptions", + "get", + aresponses.Response(text=v3_subscriptions_response, status=200), + ) + v3_server.add( + "api.simplisafe.com", + f"/v1/ss3/subscriptions/{TEST_SUBSCRIPTION_ID}/sensors", + "get", + aresponses.Response( + text=load_fixture("v3_sensors_response.json"), status=200 + ), + ) + v3_server.add( + "api.simplisafe.com", + f"/v1/ss3/subscriptions/{TEST_SUBSCRIPTION_ID}/settings/normal", + "get", + aresponses.Response(text=v3_settings_response, status=200), + ) + + async with aiohttp.ClientSession() as session: simplisafe = await API.login_via_token( TEST_REFRESH_TOKEN, client_id=TEST_CLIENT_ID, session=session ) diff --git a/tests/test_camera.py b/tests/test_camera.py new file mode 100644 index 00000000..b5518340 --- /dev/null +++ b/tests/test_camera.py @@ -0,0 +1,99 @@ +"""Define tests for the Camera objects.""" +import aiohttp +import pytest + +from simplipy import API +from simplipy.errors import InvalidCredentialsError + +from .common import ( + TEST_CAMERA_ID, + TEST_CAMERA_ID_2, + TEST_CAMERA_TYPE, + TEST_CLIENT_ID, + TEST_EMAIL, + TEST_PASSWORD, + TEST_SUBSCRIPTION_ID, + TEST_SYSTEM_ID, + TEST_USER_ID, + load_fixture, +) + + +@pytest.mark.asyncio +async def test_properties(aresponses, v3_server, v3_subscriptions_response): + """Test that camera properties are created properly.""" + async with v3_server: + v3_server.add( + "api.simplisafe.com", + f"/v1/users/{TEST_USER_ID}/subscriptions", + "get", + aresponses.Response(text=v3_subscriptions_response, status=200), + repeat=100, + ) + + async with aiohttp.ClientSession() as session: + simplisafe = await API.login_via_credentials( + TEST_EMAIL, TEST_PASSWORD, client_id=TEST_CLIENT_ID, session=session + ) + + systems = await simplisafe.get_systems() + + system = systems[TEST_SYSTEM_ID] + await system.update(include_settings=False, include_entities=False) + + camera = system.cameras[TEST_CAMERA_ID] + assert camera.name == "Camera" + assert camera.serial == TEST_CAMERA_ID + assert camera.camera_settings["cameraName"] == "Camera" + assert camera.status == "online" + assert camera.subscription_enabled + assert not camera.shutter_open_when_off + assert not camera.shutter_open_when_home + assert camera.shutter_open_when_away + assert camera.camera_type == TEST_CAMERA_TYPE + + error_camera = system.cameras[TEST_CAMERA_ID_2] + assert error_camera.camera_type == "CAMERA_MODEL_UNKNOWN" + + +@pytest.mark.asyncio +async def test_video_urls(aresponses, v3_server, v3_subscriptions_response): + """Test that camera video URL is configured properly.""" + async with v3_server: + v3_server.add( + "api.simplisafe.com", + f"/v1/users/{TEST_USER_ID}/subscriptions", + "get", + aresponses.Response(text=v3_subscriptions_response, status=200), + ) + + async with aiohttp.ClientSession() as session: + simplisafe = await API.login_via_credentials( + TEST_EMAIL, TEST_PASSWORD, client_id=TEST_CLIENT_ID, session=session + ) + + systems = await simplisafe.get_systems() + system = systems[TEST_SYSTEM_ID] + + camera = system.cameras[TEST_CAMERA_ID] + + assert ( + camera.video_url() + == f"https://media.simplisafe.com/v1/{TEST_CAMERA_ID}/flv?x=1280&audioEncoding=AAC" + ) + assert ( + camera.video_url(width=720) + == f"https://media.simplisafe.com/v1/{TEST_CAMERA_ID}/flv?x=720&audioEncoding=AAC" + ) + assert ( + camera.video_url(width=720, audio_encoding="OPUS") + == f"https://media.simplisafe.com/v1/{TEST_CAMERA_ID}/flv?x=720&audioEncoding=OPUS" + ) + assert ( + camera.video_url(audio_encoding="OPUS") + == f"https://media.simplisafe.com/v1/{TEST_CAMERA_ID}/flv?x=1280&audioEncoding=OPUS" + ) + assert ( + camera.video_url(additional_param="1") + == f"https://media.simplisafe.com/v1/{TEST_CAMERA_ID}/flv?x=1280&audioEncoding=AAC&additional_param=1" + )