diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 55f7adc860b..27cee778655 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -296,7 +296,8 @@ def _async_register_base_station( ) -> None: """Register a new bridge.""" device_registry = dr.async_get(hass) - device_registry.async_get_or_create( + + base_station = device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={(DOMAIN, str(system.system_id))}, manufacturer="SimpliSafe", @@ -304,6 +305,20 @@ def _async_register_base_station( name=system.address, ) + # Check for an old system ID format and remove it: + if old_base_station := device_registry.async_get_device( + {(DOMAIN, system.system_id)} # type: ignore[arg-type] + ): + # Update the new base station with any properties the user might have configured + # on the old base station: + device_registry.async_update_device( + base_station.id, + area_id=old_base_station.area_id, + disabled_by=old_base_station.disabled_by, + name_by_user=old_base_station.name_by_user, + ) + device_registry.async_remove_device(old_base_station.id) + @callback def _async_standardize_config_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: diff --git a/tests/components/simplisafe/conftest.py b/tests/components/simplisafe/conftest.py index 165f71cde04..4b8686d7a7f 100644 --- a/tests/components/simplisafe/conftest.py +++ b/tests/components/simplisafe/conftest.py @@ -15,7 +15,7 @@ from tests.common import MockConfigEntry, load_fixture CODE = "12345" PASSWORD = "password" -SYSTEM_ID = "system_123" +SYSTEM_ID = 12345 @pytest.fixture(name="api") @@ -79,7 +79,8 @@ def data_settings_fixture(): @pytest.fixture(name="data_subscription", scope="package") def data_subscription_fixture(): """Define subscription data.""" - return json.loads(load_fixture("subscription_data.json", "simplisafe")) + data = json.loads(load_fixture("subscription_data.json", "simplisafe")) + return {SYSTEM_ID: data} @pytest.fixture(name="reauth_config") diff --git a/tests/components/simplisafe/fixtures/subscription_data.json b/tests/components/simplisafe/fixtures/subscription_data.json index ad83f7a29a2..f0f6f84b17b 100644 --- a/tests/components/simplisafe/fixtures/subscription_data.json +++ b/tests/components/simplisafe/fixtures/subscription_data.json @@ -1,335 +1,333 @@ { - "system_123": { + "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, - "sid": "system_123", - "sStatus": 20, - "activated": 1445034752, - "planSku": "SSEDSM2", - "planName": "Interactive Monitoring", - "price": 24.99, - "currency": "USD", + "lStatus": 10, + "account": "1234ABCD", + "street1": "1234 Main Street", + "street2": "", + "locationName": "", + "city": "Atlantis", + "county": "SEA", + "state": "UW", + "zip": "12345", "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 + "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 }, - "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": [ + "temperature": 67, + "exitDelayRemaining": 60, + "cameras": [ { - "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": { + "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", - "fwDownloadVersion": "", - "fwDownloadPercentage": 0, - "recovered": false, - "recoveredFromVersion": "", - "_id": "1234567890", - "initErrors": [], - "speedTestTokenCreated": 1600235629 + "region": "us-east-1", + "enableWifiAnalyticsLib": false, + "ivLicense": "" }, - "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" + "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" }, - "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 - } + "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 } diff --git a/tests/components/simplisafe/test_diagnostics.py b/tests/components/simplisafe/test_diagnostics.py index f7a88fe0d06..30e52021f6f 100644 --- a/tests/components/simplisafe/test_diagnostics.py +++ b/tests/components/simplisafe/test_diagnostics.py @@ -21,7 +21,7 @@ async def test_entry_diagnostics(hass, config_entry, hass_client, setup_simplisa "disabled_by": None, }, "subscription_data": { - "system_123": { + "12345": { "uid": REDACTED, "sid": REDACTED, "sStatus": 20, diff --git a/tests/components/simplisafe/test_init.py b/tests/components/simplisafe/test_init.py new file mode 100644 index 00000000000..6b81bbe7943 --- /dev/null +++ b/tests/components/simplisafe/test_init.py @@ -0,0 +1,40 @@ +"""Define tests for SimpliSafe setup.""" +from unittest.mock import patch + +from homeassistant.components.simplisafe import DOMAIN +from homeassistant.helpers import device_registry as dr +from homeassistant.setup import async_setup_component + + +async def test_base_station_migration(hass, api, config, config_entry): + """Test that errors are shown when duplicates are added.""" + old_identifers = (DOMAIN, 12345) + new_identifiers = (DOMAIN, "12345") + + device_registry = dr.async_get(hass) + device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + identifiers={old_identifers}, + manufacturer="SimpliSafe", + name="old", + ) + + with patch( + "homeassistant.components.simplisafe.config_flow.API.async_from_auth", + return_value=api, + ), patch( + "homeassistant.components.simplisafe.API.async_from_auth", + return_value=api, + ), patch( + "homeassistant.components.simplisafe.API.async_from_refresh_token", + return_value=api, + ), patch( + "homeassistant.components.simplisafe.SimpliSafe._async_start_websocket_loop" + ), patch( + "homeassistant.components.simplisafe.PLATFORMS", [] + ): + assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() + + assert device_registry.async_get_device(identifiers={old_identifers}) is None + assert device_registry.async_get_device(identifiers={new_identifiers}) is not None