diff --git a/roborock/devices/device.py b/roborock/devices/device.py index c822f323..d86b3f38 100644 --- a/roborock/devices/device.py +++ b/roborock/devices/device.py @@ -143,8 +143,15 @@ async def connect(self) -> None: """Connect to the device using the appropriate protocol channel.""" if self._unsub: raise ValueError("Already connected to the device") - self._unsub = await self._channel.subscribe(self._on_message) + unsub = await self._channel.subscribe(self._on_message) _LOGGER.info("Connected to V1 device %s", self.name) + if self.v1_properties is not None: + try: + await self.v1_properties.discover_features() + except RoborockException: + unsub() + raise + self._unsub = unsub async def close(self) -> None: """Close all connections to the device.""" diff --git a/tests/devices/__snapshots__/test_v1_device.ambr b/tests/devices/__snapshots__/test_v1_device.ambr index 9dcdf1e9..dedcddca 100644 --- a/tests/devices/__snapshots__/test_v1_device.ambr +++ b/tests/devices/__snapshots__/test_v1_device.ambr @@ -1,6 +1,6 @@ # serializer version: 1 # name: test_device_trait_command_parsing[clean_summary] - CleanSummaryTrait(clean_area=24258125000, clean_count=296, clean_time=1442559, command=, dust_collection_count=None, last_clean_record=CleanRecord(area=[1756848207, 1754930385, 1753203976, 1752183435, 1747427370, 1746204046, 1745601543, 1744387080, 1743528522, 1742489154, 1741022299, 1740433682, 1739902516, 1738875106, 1738864366, 1738620067, 1736873889, 1736197544, 1736121269, 1734458038], avoid_count=None, begin=1442559, begin_datetime=datetime.datetime(1970, 1, 17, 16, 42, 39, tzinfo=datetime.timezone.utc), clean_type=None, complete=None, duration=296, dust_collection_status=None, end=24258125000, end_datetime=datetime.datetime(2738, 9, 17, 8, 3, 20, tzinfo=datetime.timezone.utc), error=None, finish_reason=None, map_flag=None, start_type=None, wash_count=None), last_clean_t=None, records=[1756848207, 1754930385, 1753203976, 1752183435, 1747427370, 1746204046, 1745601543, 1744387080, 1743528522, 1742489154, 1741022299, 1740433682, 1739902516, 1738875106, 1738864366, 1738620067, 1736873889, 1736197544, 1736121269, 1734458038], square_meter_clean_area=24258.1) + CleanSummaryTrait(clean_area=24258125000, clean_count=296, clean_time=1442559, command=, dust_collection_count=None, last_clean_record=CleanRecord(area=81122500, avoid_count=None, begin=1738864366, begin_datetime=datetime.datetime(2025, 2, 6, 17, 52, 46, tzinfo=datetime.timezone.utc), clean_type=None, complete=None, duration=4358, dust_collection_status=None, end=1738868964, end_datetime=datetime.datetime(2025, 2, 6, 19, 9, 24, tzinfo=datetime.timezone.utc), error=None, finish_reason=None, map_flag=None, square_meter_area=81.1, start_type=None, wash_count=None), last_clean_t=None, records=[1756848207, 1754930385, 1753203976, 1752183435, 1747427370, 1746204046, 1745601543, 1744387080, 1743528522, 1742489154, 1741022299, 1740433682, 1739902516, 1738875106, 1738864366, 1738620067, 1736873889, 1736197544, 1736121269, 1734458038], square_meter_clean_area=24258.1) # --- # name: test_device_trait_command_parsing[clean_summary].1 dict({ @@ -196,7 +196,7 @@ }), 'device_features': dict({ 'isActivateVideoChargingAndStandbySupported': False, - 'isAnalysisSupported': False, + 'isAnalysisSupported': True, 'isAnyStateTransitGotoSupported': False, 'isAutoCollection2Supported': False, 'isAutoDeliveryFieldInGlobalStatusSupported': False, @@ -222,19 +222,19 @@ 'isCleanHistoryTimeLineSupported': False, 'isCleanRouteDeepSlowPlusSupported': False, 'isCleanRouteFastModeSupported': False, - 'isCleanRouteSettingSupported': False, + 'isCleanRouteSettingSupported': True, 'isCleanThenMopModeSupported': False, 'isCleanTimeLineSupported': False, 'isCollectDustCountShowSupported': False, - 'isCollectDustModeSupported': False, + 'isCollectDustModeSupported': True, 'isCornerCleanModeSupported': False, 'isCornerMopStretchSupported': False, 'isCtmWithRepeatSupported': False, - 'isCurrentMapRestoreEnabled': False, + 'isCurrentMapRestoreEnabled': True, 'isCustomCleanModeCountSupported': False, - 'isCustomModeSupported': False, - 'isCustomWaterBoxDistanceSupported': False, - 'isCustomizedCleanSupported': False, + 'isCustomModeSupported': True, + 'isCustomWaterBoxDistanceSupported': True, + 'isCustomizedCleanSupported': True, 'isDetectWireCarpetSupported': False, 'isDirtyObjectDetectSupported': False, 'isDirtyReplenishCleanSupported': False, @@ -259,7 +259,7 @@ 'isIdentifyRoomSupported': False, 'isIgnoreUnknownMapObjectSupported': False, 'isLdsLiftingSupported': False, - 'isLedStatusSwitchSupported': False, + 'isLedStatusSwitchSupported': True, 'isLeftWaterDrainSupported': False, 'isLowAreaAccessSupported': False, 'isMainBrushUpDownSupportedFromStr': False, @@ -267,46 +267,46 @@ 'isMapCarpetAddSupport': False, 'isMapEraserSupported': False, 'isMatterSupported': False, - 'isMaxPlusModeSupported': False, + 'isMaxPlusModeSupported': True, 'isMaxZoneOpenedSupported': False, 'isMechanicalArmModeSupported': False, 'isMidwayBackToDockSupported': False, 'isMinBattery15ToCleanTaskSupported': False, - 'isMopForbiddenSupported': False, + 'isMopForbiddenSupported': True, 'isMopPathSupported': False, - 'isMopShakeModuleSupported': False, + 'isMopShakeModuleSupported': True, 'isMopShakeWaterMaxSupported': False, - 'isMultiFloorSupported': False, - 'isMultiMapSegmentTimerSupported': False, + 'isMultiFloorSupported': True, + 'isMultiMapSegmentTimerSupported': True, 'isNewAiRecognitionSupported': False, 'isNewDataForCleanHistory': False, 'isNewDataForCleanHistoryDetail': False, 'isNewEndpointSupported': False, - 'isNewRemoteViewSupported': False, + 'isNewRemoteViewSupported': True, 'isNoNeedCarpetPressSetSupported': False, 'isNonePureCleanMopWithMaxPlus': False, 'isObjectDetectCheckSupported': False, 'isOfflineMapSupported': False, 'isOptimizeBatterySupported': False, - 'isOrderCleanSupported': False, + 'isOrderCleanSupported': True, 'isOverSeaCtmSupported': False, 'isPetSnapshotSupported': False, 'isPetSuppliesDeepCleanSupported': False, 'isProgramModeSupported': False, 'isPumpingWaterSupported': False, - 'isPureCleanMopSupported': False, - 'isReSegmentSupported': False, + 'isPureCleanMopSupported': True, + 'isReSegmentSupported': True, 'isRecordAllowed': False, - 'isRemoteSupported': False, + 'isRemoteSupported': True, 'isRightBrushStretchSupported': False, - 'isRoomNameSupported': False, + 'isRoomNameSupported': True, 'isRpcRetrySupported': False, 'isRubberBrushCarpetSupported': False, 'isSetChildSupported': False, 'isSettingCarpetFirstSupported': False, 'isShakeMopSetSupported': False, 'isShouldShowArmOverLoadSupported': False, - 'isShowCleanFinishReasonSupported': False, + 'isShowCleanFinishReasonSupported': True, 'isShowGeneralObstacleSupported': False, 'isShowObstaclePhotoSupported': False, 'isSideBrushLiftCarpetSupported': False, @@ -317,14 +317,14 @@ 'isSrMapSupported': False, 'isSuperDeepWashSupported': False, 'isSupportApiAppStopGraspSupported': False, - 'isSupportBackupMap': False, + 'isSupportBackupMap': True, 'isSupportCleanEstimate': False, 'isSupportCliffZone': False, 'isSupportCustomCarpet': False, 'isSupportCustomDnd': False, 'isSupportCustomDoorSill': False, 'isSupportCustomModeInCleaning': False, - 'isSupportFetchTimerSummary': False, + 'isSupportFetchTimerSummary': True, 'isSupportFloorDirection': False, 'isSupportFloorEdit': False, 'isSupportFurniture': False, @@ -332,7 +332,7 @@ 'isSupportIncrementalMap': False, 'isSupportMainBrushUpDownSupported': False, 'isSupportMopBackPwmSet': False, - 'isSupportQuickMapBuilder': False, + 'isSupportQuickMapBuilder': True, 'isSupportRemoteControlInCall': False, 'isSupportRoomTag': False, 'isSupportSetSwitchMapMode': False, @@ -343,7 +343,7 @@ 'isSupportSmartScene': False, 'isSupportStuckZone': False, 'isSupportVoiceControlDebug': False, - 'isSupportWaterMode': False, + 'isSupportWaterMode': True, 'isSupportedDownloadTestVoice': False, 'isSupportedDrying': False, 'isSupportedValleyElectricity': False, @@ -354,7 +354,7 @@ 'isTwoKeyRealTimeVideoSupported': False, 'isTwoKeyRtvInChargingSupported': False, 'isTypeIdentifySupported': False, - 'isUnsaveMapReasonSupported': False, + 'isUnsaveMapReasonSupported': True, 'isUvcSterilizeSupported': False, 'isVideoMonitorSupported': False, 'isVideoPatrolSupported': False, @@ -368,9 +368,60 @@ 'isWifiManageSupported': False, 'isWorkdayHolidaySupported': False, }), + 'led_status': dict({ + 'status': 0, + }), 'network_info': dict({ 'ip': '**REDACTED**', }), + 'status': dict({ + 'adbumperStatus': list([ + 0, + 0, + 0, + ]), + 'autoDustCollection': 1, + 'avoidCount': 19, + 'backType': -1, + 'battery': 100, + 'cameraStatus': 3457, + 'chargeStatus': 1, + 'cleanArea': 20965000, + 'cleanTime': 1176, + 'collisionAvoidStatus': 1, + 'debugMode': 0, + 'dndEnabled': 0, + 'dockErrorStatus': 0, + 'dockType': 3, + 'dustCollectionStatus': 0, + 'errorCode': 0, + 'fanPower': 102, + 'homeSecEnablePassword': 0, + 'homeSecStatus': 0, + 'inCleaning': 0, + 'inFreshState': 1, + 'inReturning': 0, + 'isExploring': 0, + 'isLocating': 0, + 'labStatus': 1, + 'lockStatus': 0, + 'mapPresent': 1, + 'mapStatus': 3, + 'mopForbiddenEnable': 1, + 'mopMode': 300, + 'msgSeq': 458, + 'msgVer': 2, + 'state': 8, + 'switchMapMode': 0, + 'unsaveMapFlag': 0, + 'unsaveMapReason': 0, + 'washPhase': 0, + 'washReady': 0, + 'waterBoxCarriageStatus': 1, + 'waterBoxMode': 203, + 'waterBoxStatus': 1, + 'waterShortageStatus': 0, + }), }), }) # --- @@ -544,7 +595,7 @@ 'traits': dict({ 'device_features': dict({ 'isActivateVideoChargingAndStandbySupported': False, - 'isAnalysisSupported': False, + 'isAnalysisSupported': True, 'isAnyStateTransitGotoSupported': False, 'isAutoCollection2Supported': False, 'isAutoDeliveryFieldInGlobalStatusSupported': False, @@ -570,19 +621,19 @@ 'isCleanHistoryTimeLineSupported': False, 'isCleanRouteDeepSlowPlusSupported': False, 'isCleanRouteFastModeSupported': False, - 'isCleanRouteSettingSupported': False, + 'isCleanRouteSettingSupported': True, 'isCleanThenMopModeSupported': False, 'isCleanTimeLineSupported': False, 'isCollectDustCountShowSupported': False, - 'isCollectDustModeSupported': False, + 'isCollectDustModeSupported': True, 'isCornerCleanModeSupported': False, 'isCornerMopStretchSupported': False, 'isCtmWithRepeatSupported': False, - 'isCurrentMapRestoreEnabled': False, + 'isCurrentMapRestoreEnabled': True, 'isCustomCleanModeCountSupported': False, - 'isCustomModeSupported': False, - 'isCustomWaterBoxDistanceSupported': False, - 'isCustomizedCleanSupported': False, + 'isCustomModeSupported': True, + 'isCustomWaterBoxDistanceSupported': True, + 'isCustomizedCleanSupported': True, 'isDetectWireCarpetSupported': False, 'isDirtyObjectDetectSupported': False, 'isDirtyReplenishCleanSupported': False, @@ -607,7 +658,7 @@ 'isIdentifyRoomSupported': False, 'isIgnoreUnknownMapObjectSupported': False, 'isLdsLiftingSupported': False, - 'isLedStatusSwitchSupported': False, + 'isLedStatusSwitchSupported': True, 'isLeftWaterDrainSupported': False, 'isLowAreaAccessSupported': False, 'isMainBrushUpDownSupportedFromStr': False, @@ -615,46 +666,46 @@ 'isMapCarpetAddSupport': False, 'isMapEraserSupported': False, 'isMatterSupported': False, - 'isMaxPlusModeSupported': False, + 'isMaxPlusModeSupported': True, 'isMaxZoneOpenedSupported': False, 'isMechanicalArmModeSupported': False, 'isMidwayBackToDockSupported': False, 'isMinBattery15ToCleanTaskSupported': False, - 'isMopForbiddenSupported': False, + 'isMopForbiddenSupported': True, 'isMopPathSupported': False, - 'isMopShakeModuleSupported': False, + 'isMopShakeModuleSupported': True, 'isMopShakeWaterMaxSupported': False, - 'isMultiFloorSupported': False, - 'isMultiMapSegmentTimerSupported': False, + 'isMultiFloorSupported': True, + 'isMultiMapSegmentTimerSupported': True, 'isNewAiRecognitionSupported': False, 'isNewDataForCleanHistory': False, 'isNewDataForCleanHistoryDetail': False, 'isNewEndpointSupported': False, - 'isNewRemoteViewSupported': False, + 'isNewRemoteViewSupported': True, 'isNoNeedCarpetPressSetSupported': False, 'isNonePureCleanMopWithMaxPlus': False, 'isObjectDetectCheckSupported': False, 'isOfflineMapSupported': False, 'isOptimizeBatterySupported': False, - 'isOrderCleanSupported': False, + 'isOrderCleanSupported': True, 'isOverSeaCtmSupported': False, 'isPetSnapshotSupported': False, 'isPetSuppliesDeepCleanSupported': False, 'isProgramModeSupported': False, 'isPumpingWaterSupported': False, - 'isPureCleanMopSupported': False, - 'isReSegmentSupported': False, + 'isPureCleanMopSupported': True, + 'isReSegmentSupported': True, 'isRecordAllowed': False, - 'isRemoteSupported': False, + 'isRemoteSupported': True, 'isRightBrushStretchSupported': False, - 'isRoomNameSupported': False, + 'isRoomNameSupported': True, 'isRpcRetrySupported': False, 'isRubberBrushCarpetSupported': False, 'isSetChildSupported': False, 'isSettingCarpetFirstSupported': False, 'isShakeMopSetSupported': False, 'isShouldShowArmOverLoadSupported': False, - 'isShowCleanFinishReasonSupported': False, + 'isShowCleanFinishReasonSupported': True, 'isShowGeneralObstacleSupported': False, 'isShowObstaclePhotoSupported': False, 'isSideBrushLiftCarpetSupported': False, @@ -665,14 +716,14 @@ 'isSrMapSupported': False, 'isSuperDeepWashSupported': False, 'isSupportApiAppStopGraspSupported': False, - 'isSupportBackupMap': False, + 'isSupportBackupMap': True, 'isSupportCleanEstimate': False, 'isSupportCliffZone': False, 'isSupportCustomCarpet': False, 'isSupportCustomDnd': False, 'isSupportCustomDoorSill': False, 'isSupportCustomModeInCleaning': False, - 'isSupportFetchTimerSummary': False, + 'isSupportFetchTimerSummary': True, 'isSupportFloorDirection': False, 'isSupportFloorEdit': False, 'isSupportFurniture': False, @@ -680,7 +731,7 @@ 'isSupportIncrementalMap': False, 'isSupportMainBrushUpDownSupported': False, 'isSupportMopBackPwmSet': False, - 'isSupportQuickMapBuilder': False, + 'isSupportQuickMapBuilder': True, 'isSupportRemoteControlInCall': False, 'isSupportRoomTag': False, 'isSupportSetSwitchMapMode': False, @@ -691,7 +742,7 @@ 'isSupportSmartScene': False, 'isSupportStuckZone': False, 'isSupportVoiceControlDebug': False, - 'isSupportWaterMode': False, + 'isSupportWaterMode': True, 'isSupportedDownloadTestVoice': False, 'isSupportedDrying': False, 'isSupportedValleyElectricity': False, @@ -702,7 +753,7 @@ 'isTwoKeyRealTimeVideoSupported': False, 'isTwoKeyRtvInChargingSupported': False, 'isTypeIdentifySupported': False, - 'isUnsaveMapReasonSupported': False, + 'isUnsaveMapReasonSupported': True, 'isUvcSterilizeSupported': False, 'isVideoMonitorSupported': False, 'isVideoPatrolSupported': False, @@ -723,9 +774,60 @@ 'startHour': 22, 'startMinute': 0, }), + 'led_status': dict({ + 'status': 0, + }), 'network_info': dict({ 'ip': '**REDACTED**', }), + 'status': dict({ + 'adbumperStatus': list([ + 0, + 0, + 0, + ]), + 'autoDustCollection': 1, + 'avoidCount': 19, + 'backType': -1, + 'battery': 100, + 'cameraStatus': 3457, + 'chargeStatus': 1, + 'cleanArea': 20965000, + 'cleanTime': 1176, + 'collisionAvoidStatus': 1, + 'debugMode': 0, + 'dndEnabled': 0, + 'dockErrorStatus': 0, + 'dockType': 3, + 'dustCollectionStatus': 0, + 'errorCode': 0, + 'fanPower': 102, + 'homeSecEnablePassword': 0, + 'homeSecStatus': 0, + 'inCleaning': 0, + 'inFreshState': 1, + 'inReturning': 0, + 'isExploring': 0, + 'isLocating': 0, + 'labStatus': 1, + 'lockStatus': 0, + 'mapPresent': 1, + 'mapStatus': 3, + 'mopForbiddenEnable': 1, + 'mopMode': 300, + 'msgSeq': 458, + 'msgVer': 2, + 'state': 8, + 'switchMapMode': 0, + 'unsaveMapFlag': 0, + 'unsaveMapReason': 0, + 'washPhase': 0, + 'washReady': 0, + 'waterBoxCarriageStatus': 1, + 'waterBoxMode': 203, + 'waterBoxStatus': 1, + 'waterShortageStatus': 0, + }), }), }) # --- @@ -899,7 +1001,7 @@ 'traits': dict({ 'device_features': dict({ 'isActivateVideoChargingAndStandbySupported': False, - 'isAnalysisSupported': False, + 'isAnalysisSupported': True, 'isAnyStateTransitGotoSupported': False, 'isAutoCollection2Supported': False, 'isAutoDeliveryFieldInGlobalStatusSupported': False, @@ -925,19 +1027,19 @@ 'isCleanHistoryTimeLineSupported': False, 'isCleanRouteDeepSlowPlusSupported': False, 'isCleanRouteFastModeSupported': False, - 'isCleanRouteSettingSupported': False, + 'isCleanRouteSettingSupported': True, 'isCleanThenMopModeSupported': False, 'isCleanTimeLineSupported': False, 'isCollectDustCountShowSupported': False, - 'isCollectDustModeSupported': False, + 'isCollectDustModeSupported': True, 'isCornerCleanModeSupported': False, 'isCornerMopStretchSupported': False, 'isCtmWithRepeatSupported': False, - 'isCurrentMapRestoreEnabled': False, + 'isCurrentMapRestoreEnabled': True, 'isCustomCleanModeCountSupported': False, - 'isCustomModeSupported': False, - 'isCustomWaterBoxDistanceSupported': False, - 'isCustomizedCleanSupported': False, + 'isCustomModeSupported': True, + 'isCustomWaterBoxDistanceSupported': True, + 'isCustomizedCleanSupported': True, 'isDetectWireCarpetSupported': False, 'isDirtyObjectDetectSupported': False, 'isDirtyReplenishCleanSupported': False, @@ -962,7 +1064,7 @@ 'isIdentifyRoomSupported': False, 'isIgnoreUnknownMapObjectSupported': False, 'isLdsLiftingSupported': False, - 'isLedStatusSwitchSupported': False, + 'isLedStatusSwitchSupported': True, 'isLeftWaterDrainSupported': False, 'isLowAreaAccessSupported': False, 'isMainBrushUpDownSupportedFromStr': False, @@ -970,46 +1072,46 @@ 'isMapCarpetAddSupport': False, 'isMapEraserSupported': False, 'isMatterSupported': False, - 'isMaxPlusModeSupported': False, + 'isMaxPlusModeSupported': True, 'isMaxZoneOpenedSupported': False, 'isMechanicalArmModeSupported': False, 'isMidwayBackToDockSupported': False, 'isMinBattery15ToCleanTaskSupported': False, - 'isMopForbiddenSupported': False, + 'isMopForbiddenSupported': True, 'isMopPathSupported': False, - 'isMopShakeModuleSupported': False, + 'isMopShakeModuleSupported': True, 'isMopShakeWaterMaxSupported': False, - 'isMultiFloorSupported': False, - 'isMultiMapSegmentTimerSupported': False, + 'isMultiFloorSupported': True, + 'isMultiMapSegmentTimerSupported': True, 'isNewAiRecognitionSupported': False, 'isNewDataForCleanHistory': False, 'isNewDataForCleanHistoryDetail': False, 'isNewEndpointSupported': False, - 'isNewRemoteViewSupported': False, + 'isNewRemoteViewSupported': True, 'isNoNeedCarpetPressSetSupported': False, 'isNonePureCleanMopWithMaxPlus': False, 'isObjectDetectCheckSupported': False, 'isOfflineMapSupported': False, 'isOptimizeBatterySupported': False, - 'isOrderCleanSupported': False, + 'isOrderCleanSupported': True, 'isOverSeaCtmSupported': False, 'isPetSnapshotSupported': False, 'isPetSuppliesDeepCleanSupported': False, 'isProgramModeSupported': False, 'isPumpingWaterSupported': False, - 'isPureCleanMopSupported': False, - 'isReSegmentSupported': False, + 'isPureCleanMopSupported': True, + 'isReSegmentSupported': True, 'isRecordAllowed': False, - 'isRemoteSupported': False, + 'isRemoteSupported': True, 'isRightBrushStretchSupported': False, - 'isRoomNameSupported': False, + 'isRoomNameSupported': True, 'isRpcRetrySupported': False, 'isRubberBrushCarpetSupported': False, 'isSetChildSupported': False, 'isSettingCarpetFirstSupported': False, 'isShakeMopSetSupported': False, 'isShouldShowArmOverLoadSupported': False, - 'isShowCleanFinishReasonSupported': False, + 'isShowCleanFinishReasonSupported': True, 'isShowGeneralObstacleSupported': False, 'isShowObstaclePhotoSupported': False, 'isSideBrushLiftCarpetSupported': False, @@ -1020,14 +1122,14 @@ 'isSrMapSupported': False, 'isSuperDeepWashSupported': False, 'isSupportApiAppStopGraspSupported': False, - 'isSupportBackupMap': False, + 'isSupportBackupMap': True, 'isSupportCleanEstimate': False, 'isSupportCliffZone': False, 'isSupportCustomCarpet': False, 'isSupportCustomDnd': False, 'isSupportCustomDoorSill': False, 'isSupportCustomModeInCleaning': False, - 'isSupportFetchTimerSummary': False, + 'isSupportFetchTimerSummary': True, 'isSupportFloorDirection': False, 'isSupportFloorEdit': False, 'isSupportFurniture': False, @@ -1035,7 +1137,7 @@ 'isSupportIncrementalMap': False, 'isSupportMainBrushUpDownSupported': False, 'isSupportMopBackPwmSet': False, - 'isSupportQuickMapBuilder': False, + 'isSupportQuickMapBuilder': True, 'isSupportRemoteControlInCall': False, 'isSupportRoomTag': False, 'isSupportSetSwitchMapMode': False, @@ -1046,7 +1148,7 @@ 'isSupportSmartScene': False, 'isSupportStuckZone': False, 'isSupportVoiceControlDebug': False, - 'isSupportWaterMode': False, + 'isSupportWaterMode': True, 'isSupportedDownloadTestVoice': False, 'isSupportedDrying': False, 'isSupportedValleyElectricity': False, @@ -1057,7 +1159,7 @@ 'isTwoKeyRealTimeVideoSupported': False, 'isTwoKeyRtvInChargingSupported': False, 'isTypeIdentifySupported': False, - 'isUnsaveMapReasonSupported': False, + 'isUnsaveMapReasonSupported': True, 'isUvcSterilizeSupported': False, 'isVideoMonitorSupported': False, 'isVideoPatrolSupported': False, @@ -1071,6 +1173,9 @@ 'isWifiManageSupported': False, 'isWorkdayHolidaySupported': False, }), + 'led_status': dict({ + 'status': 0, + }), 'network_info': dict({ 'ip': '**REDACTED**', }), @@ -1273,7 +1378,7 @@ 'traits': dict({ 'device_features': dict({ 'isActivateVideoChargingAndStandbySupported': False, - 'isAnalysisSupported': False, + 'isAnalysisSupported': True, 'isAnyStateTransitGotoSupported': False, 'isAutoCollection2Supported': False, 'isAutoDeliveryFieldInGlobalStatusSupported': False, @@ -1299,19 +1404,19 @@ 'isCleanHistoryTimeLineSupported': False, 'isCleanRouteDeepSlowPlusSupported': False, 'isCleanRouteFastModeSupported': False, - 'isCleanRouteSettingSupported': False, + 'isCleanRouteSettingSupported': True, 'isCleanThenMopModeSupported': False, 'isCleanTimeLineSupported': False, 'isCollectDustCountShowSupported': False, - 'isCollectDustModeSupported': False, + 'isCollectDustModeSupported': True, 'isCornerCleanModeSupported': False, 'isCornerMopStretchSupported': False, 'isCtmWithRepeatSupported': False, - 'isCurrentMapRestoreEnabled': False, + 'isCurrentMapRestoreEnabled': True, 'isCustomCleanModeCountSupported': False, - 'isCustomModeSupported': False, - 'isCustomWaterBoxDistanceSupported': False, - 'isCustomizedCleanSupported': False, + 'isCustomModeSupported': True, + 'isCustomWaterBoxDistanceSupported': True, + 'isCustomizedCleanSupported': True, 'isDetectWireCarpetSupported': False, 'isDirtyObjectDetectSupported': False, 'isDirtyReplenishCleanSupported': False, @@ -1336,7 +1441,7 @@ 'isIdentifyRoomSupported': False, 'isIgnoreUnknownMapObjectSupported': False, 'isLdsLiftingSupported': False, - 'isLedStatusSwitchSupported': False, + 'isLedStatusSwitchSupported': True, 'isLeftWaterDrainSupported': False, 'isLowAreaAccessSupported': False, 'isMainBrushUpDownSupportedFromStr': False, @@ -1344,46 +1449,46 @@ 'isMapCarpetAddSupport': False, 'isMapEraserSupported': False, 'isMatterSupported': False, - 'isMaxPlusModeSupported': False, + 'isMaxPlusModeSupported': True, 'isMaxZoneOpenedSupported': False, 'isMechanicalArmModeSupported': False, 'isMidwayBackToDockSupported': False, 'isMinBattery15ToCleanTaskSupported': False, - 'isMopForbiddenSupported': False, + 'isMopForbiddenSupported': True, 'isMopPathSupported': False, - 'isMopShakeModuleSupported': False, + 'isMopShakeModuleSupported': True, 'isMopShakeWaterMaxSupported': False, - 'isMultiFloorSupported': False, - 'isMultiMapSegmentTimerSupported': False, + 'isMultiFloorSupported': True, + 'isMultiMapSegmentTimerSupported': True, 'isNewAiRecognitionSupported': False, 'isNewDataForCleanHistory': False, 'isNewDataForCleanHistoryDetail': False, 'isNewEndpointSupported': False, - 'isNewRemoteViewSupported': False, + 'isNewRemoteViewSupported': True, 'isNoNeedCarpetPressSetSupported': False, 'isNonePureCleanMopWithMaxPlus': False, 'isObjectDetectCheckSupported': False, 'isOfflineMapSupported': False, 'isOptimizeBatterySupported': False, - 'isOrderCleanSupported': False, + 'isOrderCleanSupported': True, 'isOverSeaCtmSupported': False, 'isPetSnapshotSupported': False, 'isPetSuppliesDeepCleanSupported': False, 'isProgramModeSupported': False, 'isPumpingWaterSupported': False, - 'isPureCleanMopSupported': False, - 'isReSegmentSupported': False, + 'isPureCleanMopSupported': True, + 'isReSegmentSupported': True, 'isRecordAllowed': False, - 'isRemoteSupported': False, + 'isRemoteSupported': True, 'isRightBrushStretchSupported': False, - 'isRoomNameSupported': False, + 'isRoomNameSupported': True, 'isRpcRetrySupported': False, 'isRubberBrushCarpetSupported': False, 'isSetChildSupported': False, 'isSettingCarpetFirstSupported': False, 'isShakeMopSetSupported': False, 'isShouldShowArmOverLoadSupported': False, - 'isShowCleanFinishReasonSupported': False, + 'isShowCleanFinishReasonSupported': True, 'isShowGeneralObstacleSupported': False, 'isShowObstaclePhotoSupported': False, 'isSideBrushLiftCarpetSupported': False, @@ -1394,14 +1499,14 @@ 'isSrMapSupported': False, 'isSuperDeepWashSupported': False, 'isSupportApiAppStopGraspSupported': False, - 'isSupportBackupMap': False, + 'isSupportBackupMap': True, 'isSupportCleanEstimate': False, 'isSupportCliffZone': False, 'isSupportCustomCarpet': False, 'isSupportCustomDnd': False, 'isSupportCustomDoorSill': False, 'isSupportCustomModeInCleaning': False, - 'isSupportFetchTimerSummary': False, + 'isSupportFetchTimerSummary': True, 'isSupportFloorDirection': False, 'isSupportFloorEdit': False, 'isSupportFurniture': False, @@ -1409,7 +1514,7 @@ 'isSupportIncrementalMap': False, 'isSupportMainBrushUpDownSupported': False, 'isSupportMopBackPwmSet': False, - 'isSupportQuickMapBuilder': False, + 'isSupportQuickMapBuilder': True, 'isSupportRemoteControlInCall': False, 'isSupportRoomTag': False, 'isSupportSetSwitchMapMode': False, @@ -1420,7 +1525,7 @@ 'isSupportSmartScene': False, 'isSupportStuckZone': False, 'isSupportVoiceControlDebug': False, - 'isSupportWaterMode': False, + 'isSupportWaterMode': True, 'isSupportedDownloadTestVoice': False, 'isSupportedDrying': False, 'isSupportedValleyElectricity': False, @@ -1431,7 +1536,7 @@ 'isTwoKeyRealTimeVideoSupported': False, 'isTwoKeyRtvInChargingSupported': False, 'isTypeIdentifySupported': False, - 'isUnsaveMapReasonSupported': False, + 'isUnsaveMapReasonSupported': True, 'isUvcSterilizeSupported': False, 'isVideoMonitorSupported': False, 'isVideoPatrolSupported': False, @@ -1445,12 +1550,63 @@ 'isWifiManageSupported': False, 'isWorkdayHolidaySupported': False, }), + 'led_status': dict({ + 'status': 0, + }), 'network_info': dict({ 'ip': '**REDACTED**', }), 'sound_volume': dict({ 'volume': 90, }), + 'status': dict({ + 'adbumperStatus': list([ + 0, + 0, + 0, + ]), + 'autoDustCollection': 1, + 'avoidCount': 19, + 'backType': -1, + 'battery': 100, + 'cameraStatus': 3457, + 'chargeStatus': 1, + 'cleanArea': 20965000, + 'cleanTime': 1176, + 'collisionAvoidStatus': 1, + 'debugMode': 0, + 'dndEnabled': 0, + 'dockErrorStatus': 0, + 'dockType': 3, + 'dustCollectionStatus': 0, + 'errorCode': 0, + 'fanPower': 102, + 'homeSecEnablePassword': 0, + 'homeSecStatus': 0, + 'inCleaning': 0, + 'inFreshState': 1, + 'inReturning': 0, + 'isExploring': 0, + 'isLocating': 0, + 'labStatus': 1, + 'lockStatus': 0, + 'mapPresent': 1, + 'mapStatus': 3, + 'mopForbiddenEnable': 1, + 'mopMode': 300, + 'msgSeq': 458, + 'msgVer': 2, + 'state': 8, + 'switchMapMode': 0, + 'unsaveMapFlag': 0, + 'unsaveMapReason': 0, + 'washPhase': 0, + 'washReady': 0, + 'waterBoxCarriageStatus': 1, + 'waterBoxMode': 203, + 'waterBoxStatus': 1, + 'waterShortageStatus': 0, + }), }), }) # --- diff --git a/tests/devices/test_device_manager.py b/tests/devices/test_device_manager.py index fa1f29d0..3b0584c6 100644 --- a/tests/devices/test_device_manager.py +++ b/tests/devices/test_device_manager.py @@ -26,13 +26,31 @@ def setup_mqtt_session() -> Generator[Mock, None, None]: yield mock_create_session +@pytest.fixture(autouse=True, name="mock_rpc_channel") +def rpc_channel_fixture() -> AsyncMock: + """Fixture to set up the channel for tests.""" + return AsyncMock() + + +@pytest.fixture(autouse=True) +async def discover_features_fixture( + mock_rpc_channel: AsyncMock, +) -> None: + """Fixture to handle device feature discovery.""" + mock_rpc_channel.send_command.side_effect = [ + [mock_data.APP_GET_INIT_STATUS], + mock_data.STATUS, + ] + + @pytest.fixture(autouse=True) -def channel_fixture() -> Generator[Mock, None, None]: +def channel_fixture(mock_rpc_channel: AsyncMock) -> Generator[Mock, None, None]: """Fixture to set up the local session for the tests.""" with patch("roborock.devices.device_manager.create_v1_channel") as mock_channel: mock_unsub = Mock() mock_channel.return_value.subscribe = AsyncMock() mock_channel.return_value.subscribe.return_value = mock_unsub + mock_channel.return_value.rpc_channel = mock_rpc_channel yield mock_channel @@ -48,11 +66,12 @@ def mock_sleep() -> Generator[None, None, None]: @pytest.fixture(name="channel_failure") -def channel_failure_fixture() -> Generator[Mock, None, None]: +def channel_failure_fixture(mock_rpc_channel: AsyncMock) -> Generator[Mock, None, None]: """Fixture that makes channel subscribe fail.""" with patch("roborock.devices.device_manager.create_v1_channel") as mock_channel: mock_channel.return_value.subscribe = AsyncMock(side_effect=RoborockException("Connection failed")) mock_channel.return_value.is_connected = False + mock_channel.return_value.rpc_channel = mock_rpc_channel yield mock_channel diff --git a/tests/devices/test_v1_device.py b/tests/devices/test_v1_device.py index be3ca79e..e33c93b2 100644 --- a/tests/devices/test_v1_device.py +++ b/tests/devices/test_v1_device.py @@ -2,6 +2,7 @@ import pathlib from collections.abc import Callable +from typing import Any from unittest.mock import AsyncMock, Mock import pytest @@ -68,7 +69,7 @@ def device_fixture(channel: AsyncMock, rpc_channel: AsyncMock, mqtt_rpc_channel: ) -async def test_device_connection(device: RoborockDevice, channel: AsyncMock) -> None: +async def test_device_connection(device: RoborockDevice, channel: AsyncMock, setup_rpc_channel: AsyncMock) -> None: """Test the Device connection setup.""" unsub = Mock() @@ -111,30 +112,46 @@ async def test_connection_status( assert device.is_local_connected is local_connected +@pytest.fixture(name="payloads") +def payloads_fixture() -> list[pathlib.Path]: + """Fixture to provide the payload for the tests.""" + return [] + + @pytest.fixture(name="setup_rpc_channel") -def setup_rpc_channel_fixture(rpc_channel: AsyncMock, payload: pathlib.Path) -> AsyncMock: +def setup_rpc_channel_fixture(rpc_channel: AsyncMock, payloads: list[pathlib.Path]) -> AsyncMock: """Fixture to set up the RPC channel for the tests.""" - # The values other than the payload are arbitrary - message = RoborockMessage( - protocol=RoborockMessageProtocol.GENERAL_RESPONSE, - payload=payload.read_bytes(), - seq=12750, - version=b"1.0", - random=97431, - timestamp=1652547161, - ) - response_message = decode_rpc_response(message) - rpc_channel.send_command.return_value = response_message.data + # Device discovery calls + side_effects: list[dict[str, Any] | list[Any] | int] = [ + [mock_data.APP_GET_INIT_STATUS], + mock_data.STATUS, + ] + + # Subsequent calls return the data payloads setup by the test. + for payload in payloads: + # The values other than the payload are arbitrary + message = RoborockMessage( + protocol=RoborockMessageProtocol.GENERAL_RESPONSE, + payload=payload.read_bytes(), + seq=12750, + version=b"1.0", + random=97431, + timestamp=1652547161, + ) + response_message = decode_rpc_response(message) + side_effects.append(response_message.data) + + rpc_channel.send_command.side_effect = side_effects return rpc_channel @pytest.mark.parametrize( - ("payload", "property_method"), + ("payloads", "property_method"), [ - (TESTDATA / "get_status.json", lambda x: x.status), - (TESTDATA / "get_dnd.json", lambda x: x.dnd), - (TESTDATA / "get_clean_summary.json", lambda x: x.clean_summary), - (TESTDATA / "get_volume.json", lambda x: x.sound_volume), + ([TESTDATA / "get_status.json"], lambda x: x.status), + ([TESTDATA / "get_dnd.json"], lambda x: x.dnd), + ([TESTDATA / "get_clean_summary.json", TESTDATA / "get_last_clean_record.json"], lambda x: x.clean_summary), + ([TESTDATA / "get_volume.json"], lambda x: x.sound_volume), ], ids=[ "status", @@ -148,9 +165,10 @@ async def test_device_trait_command_parsing( setup_rpc_channel: AsyncMock, snapshot: SnapshotAssertion, property_method: Callable[..., V1TraitMixin], - payload: str, ) -> None: """Test the device trait command.""" + await device.connect() + trait = property_method(device.v1_properties) assert trait assert isinstance(trait, V1TraitMixin) diff --git a/tests/devices/traits/v1/fixtures.py b/tests/devices/traits/v1/fixtures.py index c6db2ce0..249e5fb3 100644 --- a/tests/devices/traits/v1/fixtures.py +++ b/tests/devices/traits/v1/fixtures.py @@ -91,11 +91,7 @@ async def discover_features_fixture( mock_rpc_channel: AsyncMock, dock_type_code: RoborockDockTypeCode | None, ) -> None: - """Fixture to set up the clean summary for tests. - - The CleanRecordTrait depends on the CleanSummaryTrait, so we need to - prepare that first. - """ + """Fixture to handle device feature discovery.""" assert device.v1_properties mock_rpc_channel.send_command.side_effect = [ [mock_data.APP_GET_INIT_STATUS], @@ -104,6 +100,7 @@ async def discover_features_fixture( "dock_type": dock_type_code, }, ] - await device.v1_properties.discover_features() + # Connecting triggers device discovery + await device.connect() assert device.v1_properties.status.dock_type == dock_type_code mock_rpc_channel.send_command.reset_mock()