From aae0e41ef6bb0d76df32770c74968771eadbf72c Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 29 Dec 2025 19:34:03 -0800 Subject: [PATCH 1/4] chore: Add additional Home data to diagnostics Add home API data including products devices, rooms, etc and increase the number of redacted fields. Add a device manager snapshot test to verify which fields are redacted. --- roborock/devices/device_manager.py | 17 +- roborock/diagnostics.py | 10 + .../__snapshots__/test_device_manager.ambr | 367 ++++++++++++++++++ tests/devices/test_device_manager.py | 5 +- 4 files changed, 392 insertions(+), 7 deletions(-) create mode 100644 tests/devices/__snapshots__/test_device_manager.ambr diff --git a/roborock/devices/device_manager.py b/roborock/devices/device_manager.py index 9773923d..02d15dc4 100644 --- a/roborock/devices/device_manager.py +++ b/roborock/devices/device_manager.py @@ -4,7 +4,7 @@ import enum import logging from collections.abc import Callable, Mapping -from dataclasses import dataclass +from dataclasses import dataclass, asdict from typing import Any import aiohttp @@ -16,7 +16,7 @@ UserData, ) from roborock.devices.device import DeviceReadyCallback, RoborockDevice -from roborock.diagnostics import Diagnostics +from roborock.diagnostics import Diagnostics, redact_device_data from roborock.exceptions import RoborockException from roborock.map.map_parser import MapParserConfig from roborock.mqtt.roborock_session import create_lazy_mqtt_session @@ -76,6 +76,7 @@ def __init__( self._devices: dict[str, RoborockDevice] = {} self._mqtt_session = mqtt_session self._diagnostics = diagnostics + self._home_data: HomeData | None = None async def discover_devices(self, prefer_cache: bool = True) -> list[RoborockDevice]: """Discover all devices for the logged-in user.""" @@ -91,9 +92,9 @@ async def discover_devices(self, prefer_cache: bool = True) -> list[RoborockDevi raise _LOGGER.debug("Failed to fetch home data, using cached data: %s", ex) await self._cache.set(cache_data) - home_data = cache_data.home_data + self._home_data = cache_data.home_data - device_products = home_data.device_products + device_products = self._home_data.device_products _LOGGER.debug("Discovered %d devices", len(device_products)) # These are connected serially to avoid overwhelming the MQTT broker @@ -106,7 +107,7 @@ async def discover_devices(self, prefer_cache: bool = True) -> list[RoborockDevi if duid in self._devices: continue try: - new_device = self._device_creator(home_data, device, product) + new_device = self._device_creator(self._home_data, device, product) except UnsupportedDeviceError: _LOGGER.info("Skipping unsupported device %s %s", product.summary_info(), device.summary_info()) unsupported_devices_counter.increment(device.pv or "unknown") @@ -136,7 +137,11 @@ async def close(self) -> None: def diagnostic_data(self) -> Mapping[str, Any]: """Return diagnostics information about the device manager.""" - return self._diagnostics.as_dict() + return { + **self._diagnostics.as_dict(), + "home_data": redact_device_data(asdict(self._home_data or {})), + "devices": [device.diagnostic_data() for device in self._devices.values()], + } @dataclass diff --git a/roborock/diagnostics.py b/roborock/diagnostics.py index b69d3ada..4ed4e656 100644 --- a/roborock/diagnostics.py +++ b/roborock/diagnostics.py @@ -101,6 +101,16 @@ def reset(self) -> None: "imageContent", "mapData", "rawApiResponse", + # Home data + "id", + "name", + "productId", + "ipAddress", + "mac", + "wifiName", + "schema", # Large + "lat", + "long", } DEVICE_UID = "duid" REDACTED = "**REDACTED**" diff --git a/tests/devices/__snapshots__/test_device_manager.ambr b/tests/devices/__snapshots__/test_device_manager.ambr new file mode 100644 index 00000000..4473c875 --- /dev/null +++ b/tests/devices/__snapshots__/test_device_manager.ambr @@ -0,0 +1,367 @@ +# serializer version: 1 +# name: test_diagnostics_collection + dict({ + 'devices': list([ + dict({ + 'device': dict({ + 'activeTime': 1672364449, + 'deviceStatus': dict({ + '120': 0, + '121': 8, + '122': 100, + '123': 102, + '124': 203, + '125': 94, + '126': 90, + '127': 87, + '128': 0, + '133': 1, + }), + 'duid': '******bc123', + 'extra': '{"RRPhotoPrivacyVersion": "1"}', + 'featureSet': '2234201184108543', + 'fv': '02.56.02', + 'iconUrl': 'no_url', + 'localKey': '**REDACTED**', + 'name': '**REDACTED**', + 'newFeatureSet': '0000000000002041', + 'online': True, + 'productId': '**REDACTED**', + 'pv': '1.0', + 'roomId': 2362003, + 'share': False, + 'silentOtaSwitch': True, + 'sn': '**REDACTED**', + 'timeZoneId': 'America/Los_Angeles', + 'tuyaMigrated': False, + }), + 'product': dict({ + 'capability': 0, + 'category': 'robot.vacuum.cleaner', + 'code': 'a27', + 'id': '**REDACTED**', + 'model': 'roborock.vacuum.a27', + 'name': '**REDACTED**', + 'schema': '**REDACTED**', + }), + 'traits': dict({ + 'device_features': dict({ + 'isActivateVideoChargingAndStandbySupported': False, + 'isAnalysisSupported': True, + 'isAnyStateTransitGotoSupported': False, + 'isAutoCollection2Supported': False, + 'isAutoDeliveryFieldInGlobalStatusSupported': False, + 'isAutoTearDownMopSupported': False, + 'isAvoidCollisionModeSupported': False, + 'isAvoidCollisionSupported': False, + 'isBackChargeAutoWashSupported': False, + 'isBackWashNewSmartSupported': False, + 'isCarefulSlowMopSupported': False, + 'isCarpetCustomCleanSupported': False, + 'isCarpetDeepCleanSupported': False, + 'isCarpetLongHairedExSupported': False, + 'isCarpetLongHairedSupported': False, + 'isCarpetPressureUseOriginParasSupported': False, + 'isCarpetShapeTypeSupported': False, + 'isCarpetShowOnMap': False, + 'isCarpetSupported': False, + 'isCes2022Supported': False, + 'isCleanCountSettingSupported': False, + 'isCleanDirectStatusSupported': False, + 'isCleanEfficiencySupported': False, + 'isCleanFluidDeliverySupported': False, + 'isCleanHistoryTimeLineSupported': False, + 'isCleanRouteDeepSlowPlusSupported': False, + 'isCleanRouteFastModeSupported': False, + 'isCleanRouteSettingSupported': True, + 'isCleanThenMopModeSupported': False, + 'isCleanTimeLineSupported': False, + 'isCollectDustCountShowSupported': False, + 'isCollectDustModeSupported': True, + 'isCornerCleanModeSupported': False, + 'isCornerMopStretchSupported': False, + 'isCtmWithRepeatSupported': False, + 'isCurrentMapRestoreEnabled': True, + 'isCustomCleanModeCountSupported': False, + 'isCustomModeSupported': True, + 'isCustomWaterBoxDistanceSupported': True, + 'isCustomizedCleanSupported': True, + 'isDetectWireCarpetSupported': False, + 'isDirtyObjectDetectSupported': False, + 'isDirtyReplenishCleanSupported': False, + 'isDryIntervalTimerSupported': False, + 'isDssBelievable': False, + 'isDualBandWiFiSupported': False, + 'isDustCollectionSettingSupported': False, + 'isDynamicallyAddCleanZonesSupported': False, + 'isDynamicallySkipCleanZoneSupported': False, + 'isEggDanceModeSupported': False, + 'isEggModeSupportedFromNewFeatures': False, + 'isExactCustomModeSupported': False, + 'isExhibitionFunctionSupported': False, + 'isFloorDirCleanAnyTimeSupported': False, + 'isFlowLedSettingSupported': False, + 'isFollowLowObsSupported': False, + 'isFullDuplesSwitchSupported': False, + 'isFwFilterObstacleSupported': False, + 'isGapDeepCleanSupported': False, + 'isGotoPureCleanPathSupported': False, + 'isHotWashTowelSupported': False, + 'isIdentifyRoomSupported': False, + 'isIgnoreUnknownMapObjectSupported': False, + 'isLdsLiftingSupported': False, + 'isLedStatusSwitchSupported': True, + 'isLeftWaterDrainSupported': False, + 'isLowAreaAccessSupported': False, + 'isMainBrushUpDownSupportedFromStr': False, + 'isMapBeautifyInternalDebugSupported': False, + 'isMapCarpetAddSupport': False, + 'isMapEraserSupported': False, + 'isMatterSupported': False, + 'isMaxPlusModeSupported': True, + 'isMaxZoneOpenedSupported': False, + 'isMechanicalArmModeSupported': False, + 'isMidwayBackToDockSupported': False, + 'isMinBattery15ToCleanTaskSupported': False, + 'isMopForbiddenSupported': True, + 'isMopPathSupported': False, + 'isMopShakeModuleSupported': True, + 'isMopShakeWaterMaxSupported': False, + 'isMultiFloorSupported': True, + 'isMultiMapSegmentTimerSupported': True, + 'isNewAiRecognitionSupported': False, + 'isNewDataForCleanHistory': False, + 'isNewDataForCleanHistoryDetail': False, + 'isNewEndpointSupported': False, + 'isNewRemoteViewSupported': True, + 'isNoNeedCarpetPressSetSupported': False, + 'isNonePureCleanMopWithMaxPlus': False, + 'isObjectDetectCheckSupported': False, + 'isOfflineMapSupported': False, + 'isOptimizeBatterySupported': False, + 'isOrderCleanSupported': True, + 'isOverSeaCtmSupported': False, + 'isPetSnapshotSupported': False, + 'isPetSuppliesDeepCleanSupported': False, + 'isProgramModeSupported': False, + 'isPumpingWaterSupported': False, + 'isPureCleanMopSupported': True, + 'isReSegmentSupported': True, + 'isRecordAllowed': False, + 'isRemoteSupported': True, + 'isRightBrushStretchSupported': False, + 'isRoomNameSupported': True, + 'isRpcRetrySupported': False, + 'isRubberBrushCarpetSupported': False, + 'isSetChildSupported': False, + 'isSettingCarpetFirstSupported': False, + 'isShakeMopSetSupported': False, + 'isShouldShowArmOverLoadSupported': False, + 'isShowCleanFinishReasonSupported': True, + 'isShowGeneralObstacleSupported': False, + 'isShowObstaclePhotoSupported': False, + 'isSideBrushLiftCarpetSupported': False, + 'isSmallSideMopSupported': False, + 'isSmartCleanModeSetSupported': False, + 'isSoakAndWashSupported': False, + 'isSoftCleanModeSupported': False, + 'isSrMapSupported': False, + 'isSuperDeepWashSupported': False, + 'isSupportApiAppStopGraspSupported': False, + 'isSupportBackupMap': True, + 'isSupportCleanEstimate': False, + 'isSupportCliffZone': False, + 'isSupportCustomCarpet': False, + 'isSupportCustomDnd': False, + 'isSupportCustomDoorSill': False, + 'isSupportCustomModeInCleaning': False, + 'isSupportFetchTimerSummary': True, + 'isSupportFloorDirection': False, + 'isSupportFloorEdit': False, + 'isSupportFurniture': False, + 'isSupportGetParticularStatusSupported': False, + 'isSupportIncrementalMap': False, + 'isSupportMainBrushUpDownSupported': False, + 'isSupportMopBackPwmSet': False, + 'isSupportQuickMapBuilder': True, + 'isSupportRemoteControlInCall': False, + 'isSupportRoomTag': False, + 'isSupportSetSwitchMapMode': False, + 'isSupportSetVolumeInCall': False, + 'isSupportSideBrushUpDownSupported': False, + 'isSupportSmartDoorSill': False, + 'isSupportSmartGlobalCleanWithCustomMode': False, + 'isSupportSmartScene': False, + 'isSupportStuckZone': False, + 'isSupportVoiceControlDebug': False, + 'isSupportWaterMode': True, + 'isSupportedDownloadTestVoice': False, + 'isSupportedDrying': False, + 'isSupportedValleyElectricity': False, + 'isSyncServerNameSupported': False, + 'isThreeDMappingInnerTestSupported': False, + 'isTidyupZonesSupported': False, + 'isTwoGearsNoCollisionSupported': False, + 'isTwoKeyRealTimeVideoSupported': False, + 'isTwoKeyRtvInChargingSupported': False, + 'isTypeIdentifySupported': False, + 'isUnsaveMapReasonSupported': True, + 'isUvcSterilizeSupported': False, + 'isVideoMonitorSupported': False, + 'isVideoPatrolSupported': False, + 'isVideoSettingSupported': False, + 'isVoiceControlLedSupported': False, + 'isVoiceControlSupported': False, + 'isWashThenChargeCmdSupported': False, + 'isWaterLeakCheckSupported': False, + 'isWaterSlideModeSupported': False, + 'isWaterUpDownDrainSupported': False, + '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, + }), + }), + }), + ]), + 'discover_devices': 1, + 'fetch_home_data': 1, + 'home_data': dict({ + 'devices': list([ + dict({ + 'active_time': 1672364449, + 'attribute': None, + 'cid': None, + 'create_time': None, + 'device_status': dict({ + '120': 0, + '121': 8, + '122': 100, + '123': 102, + '124': 203, + '125': 94, + '126': 90, + '127': 87, + '128': 0, + '133': 1, + }), + 'duid': '******bc123', + 'extra': '{"RRPhotoPrivacyVersion": "1"}', + 'f': None, + 'feature_set': '2234201184108543', + 'fv': '02.56.02', + 'icon_url': 'no_url', + 'lat': '**REDACTED**', + 'local_key': 'key123key123key1', + 'lon': None, + 'name': '**REDACTED**', + 'new_feature_set': '0000000000002041', + 'online': True, + 'product_id': 'product-id-s7-maxv', + 'pv': '1.0', + 'room_id': 2362003, + 'runtime_env': None, + 'setting': None, + 'share': False, + 'share_expired_time': None, + 'share_time': None, + 'share_type': None, + 'silent_ota_switch': True, + 'sn': '**REDACTED**', + 'time_zone_id': 'America/Los_Angeles', + 'tuya_migrated': False, + 'tuya_uuid': None, + }), + ]), + 'geo_name': None, + 'id': '**REDACTED**', + 'lat': '**REDACTED**', + 'lon': None, + 'name': '**REDACTED**', + 'products': list([ + dict({ + 'attribute': None, + 'capability': 0, + 'category': , + 'code': 'a27', + 'icon_url': None, + 'id': '**REDACTED**', + 'model': 'roborock.vacuum.a27', + 'name': '**REDACTED**', + 'schema': '**REDACTED**', + }), + ]), + 'received_devices': list([ + ]), + 'rooms': list([ + dict({ + 'id': '**REDACTED**', + 'name': '**REDACTED**', + }), + dict({ + 'id': '**REDACTED**', + 'name': '**REDACTED**', + }), + dict({ + 'id': '**REDACTED**', + 'name': '**REDACTED**', + }), + ]), + }), + 'supported_devices': dict({ + '1.0': 1, + }), + }) +# --- diff --git a/tests/devices/test_device_manager.py b/tests/devices/test_device_manager.py index c4758480..75fab503 100644 --- a/tests/devices/test_device_manager.py +++ b/tests/devices/test_device_manager.py @@ -6,6 +6,7 @@ from typing import Any from unittest.mock import AsyncMock, Mock, patch +import syrupy import pytest from roborock.data import HomeData, UserData @@ -349,7 +350,7 @@ async def test_start_connect_unexpected_error(home_data: HomeData, channel_failu await create_device_manager(USER_PARAMS) -async def test_diagnostics_collection(home_data: HomeData) -> None: +async def test_diagnostics_collection(home_data: HomeData, snapshot: syrupy.SnapshotAssertion) -> None: """Test that diagnostics are collected correctly in the DeviceManager.""" device_manager = await create_device_manager(USER_PARAMS) devices = await device_manager.get_devices() @@ -360,6 +361,8 @@ async def test_diagnostics_collection(home_data: HomeData) -> None: assert diagnostics.get("discover_devices") == 1 assert diagnostics.get("fetch_home_data") == 1 + assert snapshot == diagnostics + await device_manager.close() From 8f8f6a0e7caa4fd420c96b37ed01176dc0393859 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 29 Dec 2025 22:55:15 -0800 Subject: [PATCH 2/4] chore: Update device snapshots and lint errors --- roborock/devices/device_manager.py | 4 +- .../devices/__snapshots__/test_v1_device.ambr | 524 +----------------- tests/devices/test_device_manager.py | 2 +- 3 files changed, 23 insertions(+), 507 deletions(-) diff --git a/roborock/devices/device_manager.py b/roborock/devices/device_manager.py index 02d15dc4..54467d2d 100644 --- a/roborock/devices/device_manager.py +++ b/roborock/devices/device_manager.py @@ -4,7 +4,7 @@ import enum import logging from collections.abc import Callable, Mapping -from dataclasses import dataclass, asdict +from dataclasses import asdict, dataclass from typing import Any import aiohttp @@ -139,7 +139,7 @@ def diagnostic_data(self) -> Mapping[str, Any]: """Return diagnostics information about the device manager.""" return { **self._diagnostics.as_dict(), - "home_data": redact_device_data(asdict(self._home_data or {})), + "home_data": redact_device_data(asdict(self._home_data) if self._home_data else {}), "devices": [device.diagnostic_data() for device in self._devices.values()], } diff --git a/tests/devices/__snapshots__/test_v1_device.ambr b/tests/devices/__snapshots__/test_v1_device.ambr index 0a6fde4c..c40a9cb6 100644 --- a/tests/devices/__snapshots__/test_v1_device.ambr +++ b/tests/devices/__snapshots__/test_v1_device.ambr @@ -24,10 +24,10 @@ 'fv': '02.56.02', 'iconUrl': 'no_url', 'localKey': '**REDACTED**', - 'name': 'Roborock S7 MaxV', + 'name': '**REDACTED**', 'newFeatureSet': '0000000000002041', 'online': True, - 'productId': 'product-id-s7-maxv', + 'productId': '**REDACTED**', 'pv': '1.0', 'roomId': 2362003, 'share': False, @@ -40,131 +40,10 @@ 'capability': 0, 'category': 'robot.vacuum.cleaner', 'code': 'a27', - 'id': 'product-id-s7-maxv', + 'id': '**REDACTED**', 'model': 'roborock.vacuum.a27', - 'name': 'Roborock S7 MaxV', - 'schema': list([ - dict({ - 'code': 'rpc_request_code', - 'id': '101', - 'mode': 'rw', - 'name': 'rpc_request', - 'type': 'RAW', - }), - dict({ - 'code': 'rpc_response', - 'id': '102', - 'mode': 'rw', - 'name': 'rpc_response', - 'type': 'RAW', - }), - dict({ - 'code': 'error_code', - 'id': '120', - 'mode': 'ro', - 'name': '错误代码', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'state', - 'id': '121', - 'mode': 'ro', - 'name': '设备状态', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'battery', - 'id': '122', - 'mode': 'ro', - 'name': '设备电量', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'fan_power', - 'id': '123', - 'mode': 'rw', - 'name': '清扫模式', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'water_box_mode', - 'id': '124', - 'mode': 'rw', - 'name': '拖地模式', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'main_brush_life', - 'id': '125', - 'mode': 'rw', - 'name': '主刷寿命', - 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', - 'type': 'VALUE', - }), - dict({ - 'code': 'side_brush_life', - 'id': '126', - 'mode': 'rw', - 'name': '边刷寿命', - 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', - 'type': 'VALUE', - }), - dict({ - 'code': 'filter_life', - 'id': '127', - 'mode': 'rw', - 'name': '滤网寿命', - 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', - 'type': 'VALUE', - }), - dict({ - 'code': 'additional_props', - 'id': '128', - 'mode': 'ro', - 'name': '额外状态', - 'type': 'RAW', - }), - dict({ - 'code': 'task_complete', - 'id': '130', - 'mode': 'ro', - 'name': '完成事件', - 'type': 'RAW', - }), - dict({ - 'code': 'task_cancel_low_power', - 'id': '131', - 'mode': 'ro', - 'name': '电量不足任务取消', - 'type': 'RAW', - }), - dict({ - 'code': 'task_cancel_in_motion', - 'id': '132', - 'mode': 'ro', - 'name': '运动中任务取消', - 'type': 'RAW', - }), - dict({ - 'code': 'charge_status', - 'id': '133', - 'mode': 'ro', - 'name': '充电状态', - 'type': 'RAW', - }), - dict({ - 'code': 'drying_status', - 'id': '134', - 'mode': 'ro', - 'name': '烘干状态', - 'type': 'RAW', - }), - ]), + 'name': '**REDACTED**', + 'schema': '**REDACTED**', }), 'traits': dict({ 'clean_summary': dict({ @@ -450,10 +329,10 @@ 'fv': '02.56.02', 'iconUrl': 'no_url', 'localKey': '**REDACTED**', - 'name': 'Roborock S7 MaxV', + 'name': '**REDACTED**', 'newFeatureSet': '0000000000002041', 'online': True, - 'productId': 'product-id-s7-maxv', + 'productId': '**REDACTED**', 'pv': '1.0', 'roomId': 2362003, 'share': False, @@ -466,131 +345,10 @@ 'capability': 0, 'category': 'robot.vacuum.cleaner', 'code': 'a27', - 'id': 'product-id-s7-maxv', + 'id': '**REDACTED**', 'model': 'roborock.vacuum.a27', - 'name': 'Roborock S7 MaxV', - 'schema': list([ - dict({ - 'code': 'rpc_request_code', - 'id': '101', - 'mode': 'rw', - 'name': 'rpc_request', - 'type': 'RAW', - }), - dict({ - 'code': 'rpc_response', - 'id': '102', - 'mode': 'rw', - 'name': 'rpc_response', - 'type': 'RAW', - }), - dict({ - 'code': 'error_code', - 'id': '120', - 'mode': 'ro', - 'name': '错误代码', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'state', - 'id': '121', - 'mode': 'ro', - 'name': '设备状态', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'battery', - 'id': '122', - 'mode': 'ro', - 'name': '设备电量', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'fan_power', - 'id': '123', - 'mode': 'rw', - 'name': '清扫模式', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'water_box_mode', - 'id': '124', - 'mode': 'rw', - 'name': '拖地模式', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'main_brush_life', - 'id': '125', - 'mode': 'rw', - 'name': '主刷寿命', - 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', - 'type': 'VALUE', - }), - dict({ - 'code': 'side_brush_life', - 'id': '126', - 'mode': 'rw', - 'name': '边刷寿命', - 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', - 'type': 'VALUE', - }), - dict({ - 'code': 'filter_life', - 'id': '127', - 'mode': 'rw', - 'name': '滤网寿命', - 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', - 'type': 'VALUE', - }), - dict({ - 'code': 'additional_props', - 'id': '128', - 'mode': 'ro', - 'name': '额外状态', - 'type': 'RAW', - }), - dict({ - 'code': 'task_complete', - 'id': '130', - 'mode': 'ro', - 'name': '完成事件', - 'type': 'RAW', - }), - dict({ - 'code': 'task_cancel_low_power', - 'id': '131', - 'mode': 'ro', - 'name': '电量不足任务取消', - 'type': 'RAW', - }), - dict({ - 'code': 'task_cancel_in_motion', - 'id': '132', - 'mode': 'ro', - 'name': '运动中任务取消', - 'type': 'RAW', - }), - dict({ - 'code': 'charge_status', - 'id': '133', - 'mode': 'ro', - 'name': '充电状态', - 'type': 'RAW', - }), - dict({ - 'code': 'drying_status', - 'id': '134', - 'mode': 'ro', - 'name': '烘干状态', - 'type': 'RAW', - }), - ]), + 'name': '**REDACTED**', + 'schema': '**REDACTED**', }), 'traits': dict({ 'device_features': dict({ @@ -856,10 +614,10 @@ 'fv': '02.56.02', 'iconUrl': 'no_url', 'localKey': '**REDACTED**', - 'name': 'Roborock S7 MaxV', + 'name': '**REDACTED**', 'newFeatureSet': '0000000000002041', 'online': True, - 'productId': 'product-id-s7-maxv', + 'productId': '**REDACTED**', 'pv': '1.0', 'roomId': 2362003, 'share': False, @@ -872,131 +630,10 @@ 'capability': 0, 'category': 'robot.vacuum.cleaner', 'code': 'a27', - 'id': 'product-id-s7-maxv', + 'id': '**REDACTED**', 'model': 'roborock.vacuum.a27', - 'name': 'Roborock S7 MaxV', - 'schema': list([ - dict({ - 'code': 'rpc_request_code', - 'id': '101', - 'mode': 'rw', - 'name': 'rpc_request', - 'type': 'RAW', - }), - dict({ - 'code': 'rpc_response', - 'id': '102', - 'mode': 'rw', - 'name': 'rpc_response', - 'type': 'RAW', - }), - dict({ - 'code': 'error_code', - 'id': '120', - 'mode': 'ro', - 'name': '错误代码', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'state', - 'id': '121', - 'mode': 'ro', - 'name': '设备状态', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'battery', - 'id': '122', - 'mode': 'ro', - 'name': '设备电量', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'fan_power', - 'id': '123', - 'mode': 'rw', - 'name': '清扫模式', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'water_box_mode', - 'id': '124', - 'mode': 'rw', - 'name': '拖地模式', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'main_brush_life', - 'id': '125', - 'mode': 'rw', - 'name': '主刷寿命', - 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', - 'type': 'VALUE', - }), - dict({ - 'code': 'side_brush_life', - 'id': '126', - 'mode': 'rw', - 'name': '边刷寿命', - 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', - 'type': 'VALUE', - }), - dict({ - 'code': 'filter_life', - 'id': '127', - 'mode': 'rw', - 'name': '滤网寿命', - 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', - 'type': 'VALUE', - }), - dict({ - 'code': 'additional_props', - 'id': '128', - 'mode': 'ro', - 'name': '额外状态', - 'type': 'RAW', - }), - dict({ - 'code': 'task_complete', - 'id': '130', - 'mode': 'ro', - 'name': '完成事件', - 'type': 'RAW', - }), - dict({ - 'code': 'task_cancel_low_power', - 'id': '131', - 'mode': 'ro', - 'name': '电量不足任务取消', - 'type': 'RAW', - }), - dict({ - 'code': 'task_cancel_in_motion', - 'id': '132', - 'mode': 'ro', - 'name': '运动中任务取消', - 'type': 'RAW', - }), - dict({ - 'code': 'charge_status', - 'id': '133', - 'mode': 'ro', - 'name': '充电状态', - 'type': 'RAW', - }), - dict({ - 'code': 'drying_status', - 'id': '134', - 'mode': 'ro', - 'name': '烘干状态', - 'type': 'RAW', - }), - ]), + 'name': '**REDACTED**', + 'schema': '**REDACTED**', }), 'traits': dict({ 'device_features': dict({ @@ -1233,10 +870,10 @@ 'fv': '02.56.02', 'iconUrl': 'no_url', 'localKey': '**REDACTED**', - 'name': 'Roborock S7 MaxV', + 'name': '**REDACTED**', 'newFeatureSet': '0000000000002041', 'online': True, - 'productId': 'product-id-s7-maxv', + 'productId': '**REDACTED**', 'pv': '1.0', 'roomId': 2362003, 'share': False, @@ -1249,131 +886,10 @@ 'capability': 0, 'category': 'robot.vacuum.cleaner', 'code': 'a27', - 'id': 'product-id-s7-maxv', + 'id': '**REDACTED**', 'model': 'roborock.vacuum.a27', - 'name': 'Roborock S7 MaxV', - 'schema': list([ - dict({ - 'code': 'rpc_request_code', - 'id': '101', - 'mode': 'rw', - 'name': 'rpc_request', - 'type': 'RAW', - }), - dict({ - 'code': 'rpc_response', - 'id': '102', - 'mode': 'rw', - 'name': 'rpc_response', - 'type': 'RAW', - }), - dict({ - 'code': 'error_code', - 'id': '120', - 'mode': 'ro', - 'name': '错误代码', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'state', - 'id': '121', - 'mode': 'ro', - 'name': '设备状态', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'battery', - 'id': '122', - 'mode': 'ro', - 'name': '设备电量', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'fan_power', - 'id': '123', - 'mode': 'rw', - 'name': '清扫模式', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'water_box_mode', - 'id': '124', - 'mode': 'rw', - 'name': '拖地模式', - 'property': '{"range": []}', - 'type': 'ENUM', - }), - dict({ - 'code': 'main_brush_life', - 'id': '125', - 'mode': 'rw', - 'name': '主刷寿命', - 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', - 'type': 'VALUE', - }), - dict({ - 'code': 'side_brush_life', - 'id': '126', - 'mode': 'rw', - 'name': '边刷寿命', - 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', - 'type': 'VALUE', - }), - dict({ - 'code': 'filter_life', - 'id': '127', - 'mode': 'rw', - 'name': '滤网寿命', - 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', - 'type': 'VALUE', - }), - dict({ - 'code': 'additional_props', - 'id': '128', - 'mode': 'ro', - 'name': '额外状态', - 'type': 'RAW', - }), - dict({ - 'code': 'task_complete', - 'id': '130', - 'mode': 'ro', - 'name': '完成事件', - 'type': 'RAW', - }), - dict({ - 'code': 'task_cancel_low_power', - 'id': '131', - 'mode': 'ro', - 'name': '电量不足任务取消', - 'type': 'RAW', - }), - dict({ - 'code': 'task_cancel_in_motion', - 'id': '132', - 'mode': 'ro', - 'name': '运动中任务取消', - 'type': 'RAW', - }), - dict({ - 'code': 'charge_status', - 'id': '133', - 'mode': 'ro', - 'name': '充电状态', - 'type': 'RAW', - }), - dict({ - 'code': 'drying_status', - 'id': '134', - 'mode': 'ro', - 'name': '烘干状态', - 'type': 'RAW', - }), - ]), + 'name': '**REDACTED**', + 'schema': '**REDACTED**', }), 'traits': dict({ 'device_features': dict({ diff --git a/tests/devices/test_device_manager.py b/tests/devices/test_device_manager.py index 75fab503..b348b42a 100644 --- a/tests/devices/test_device_manager.py +++ b/tests/devices/test_device_manager.py @@ -6,8 +6,8 @@ from typing import Any from unittest.mock import AsyncMock, Mock, patch -import syrupy import pytest +import syrupy from roborock.data import HomeData, UserData from roborock.devices.cache import InMemoryCache From 5b8cc1c1c4b77e1b6293514e2dd228b0a3dd6317 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 30 Dec 2025 07:57:55 -0800 Subject: [PATCH 3/4] chore: Improve redaction logic to support more complex paths --- roborock/devices/device_manager.py | 2 +- roborock/diagnostics.py | 24 +- .../__snapshots__/test_device_manager.ambr | 304 +++++++++++++++++- tests/devices/test_device_manager.py | 14 +- 4 files changed, 321 insertions(+), 23 deletions(-) diff --git a/roborock/devices/device_manager.py b/roborock/devices/device_manager.py index 54467d2d..fb91ec00 100644 --- a/roborock/devices/device_manager.py +++ b/roborock/devices/device_manager.py @@ -138,9 +138,9 @@ async def close(self) -> None: def diagnostic_data(self) -> Mapping[str, Any]: """Return diagnostics information about the device manager.""" return { - **self._diagnostics.as_dict(), "home_data": redact_device_data(asdict(self._home_data) if self._home_data else {}), "devices": [device.diagnostic_data() for device in self._devices.values()], + "diagnostics": self._diagnostics.as_dict(), } diff --git a/roborock/diagnostics.py b/roborock/diagnostics.py index 4ed4e656..f5c0b8f5 100644 --- a/roborock/diagnostics.py +++ b/roborock/diagnostics.py @@ -102,39 +102,49 @@ def reset(self) -> None: "mapData", "rawApiResponse", # Home data - "id", + "id", # We want to redact home_data.id but keep some other ids, see below "name", "productId", "ipAddress", "mac", "wifiName", - "schema", # Large "lat", "long", } +KEEP_KEYS = { + # Product information no unique per user + "product.id", + "schema.id", + # Room ids are likely unique per user, but don't seem too sensitive and are + # useful for debugging + "rooms.id", +} DEVICE_UID = "duid" REDACTED = "**REDACTED**" -def redact_device_data(data: T) -> T | dict[str, Any]: +def redact_device_data(data: T, path: str = "") -> T | dict[str, Any]: """Redact sensitive data in a dict.""" if not isinstance(data, (Mapping, list)): return data if isinstance(data, list): - return cast(T, [redact_device_data(item) for item in data]) + return cast(T, [redact_device_data(item, path) for item in data]) redacted = {**data} for key, value in redacted.items(): - if key in REDACT_KEYS: + curr_path = f"{path}.{key}" if path else key + if key in KEEP_KEYS or curr_path in KEEP_KEYS: + continue + if key in REDACT_KEYS or curr_path in REDACT_KEYS: redacted[key] = REDACTED elif key == DEVICE_UID and isinstance(value, str): redacted[key] = redact_device_uid(value) elif isinstance(value, dict): - redacted[key] = redact_device_data(value) + redacted[key] = redact_device_data(value, curr_path) elif isinstance(value, list): - redacted[key] = [redact_device_data(item) for item in value] + redacted[key] = [redact_device_data(item, curr_path) for item in value] return redacted diff --git a/tests/devices/__snapshots__/test_device_manager.ambr b/tests/devices/__snapshots__/test_device_manager.ambr index 4473c875..b46534a1 100644 --- a/tests/devices/__snapshots__/test_device_manager.ambr +++ b/tests/devices/__snapshots__/test_device_manager.ambr @@ -42,7 +42,128 @@ 'id': '**REDACTED**', 'model': 'roborock.vacuum.a27', 'name': '**REDACTED**', - 'schema': '**REDACTED**', + 'schema': list([ + dict({ + 'code': 'rpc_request_code', + 'id': '101', + 'mode': 'rw', + 'name': '**REDACTED**', + 'type': 'RAW', + }), + dict({ + 'code': 'rpc_response', + 'id': '102', + 'mode': 'rw', + 'name': '**REDACTED**', + 'type': 'RAW', + }), + dict({ + 'code': 'error_code', + 'id': '120', + 'mode': 'ro', + 'name': '**REDACTED**', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'state', + 'id': '121', + 'mode': 'ro', + 'name': '**REDACTED**', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'battery', + 'id': '122', + 'mode': 'ro', + 'name': '**REDACTED**', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'fan_power', + 'id': '123', + 'mode': 'rw', + 'name': '**REDACTED**', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'water_box_mode', + 'id': '124', + 'mode': 'rw', + 'name': '**REDACTED**', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'main_brush_life', + 'id': '125', + 'mode': 'rw', + 'name': '**REDACTED**', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'side_brush_life', + 'id': '126', + 'mode': 'rw', + 'name': '**REDACTED**', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'filter_life', + 'id': '127', + 'mode': 'rw', + 'name': '**REDACTED**', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'additional_props', + 'id': '128', + 'mode': 'ro', + 'name': '**REDACTED**', + 'type': 'RAW', + }), + dict({ + 'code': 'task_complete', + 'id': '130', + 'mode': 'ro', + 'name': '**REDACTED**', + 'type': 'RAW', + }), + dict({ + 'code': 'task_cancel_low_power', + 'id': '131', + 'mode': 'ro', + 'name': '**REDACTED**', + 'type': 'RAW', + }), + dict({ + 'code': 'task_cancel_in_motion', + 'id': '132', + 'mode': 'ro', + 'name': '**REDACTED**', + 'type': 'RAW', + }), + dict({ + 'code': 'charge_status', + 'id': '133', + 'mode': 'ro', + 'name': '**REDACTED**', + 'type': 'RAW', + }), + dict({ + 'code': 'drying_status', + 'id': '134', + 'mode': 'ro', + 'name': '**REDACTED**', + 'type': 'RAW', + }), + ]), }), 'traits': dict({ 'device_features': dict({ @@ -276,8 +397,13 @@ }), }), ]), - 'discover_devices': 1, - 'fetch_home_data': 1, + 'diagnostics': dict({ + 'discover_devices': 1, + 'fetch_home_data': 1, + 'supported_devices': dict({ + '1.0': 1, + }), + }), 'home_data': dict({ 'devices': list([ dict({ @@ -340,28 +466,186 @@ 'id': '**REDACTED**', 'model': 'roborock.vacuum.a27', 'name': '**REDACTED**', - 'schema': '**REDACTED**', + 'schema': list([ + dict({ + 'code': 'rpc_request_code', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'rw', + 'name': '**REDACTED**', + 'product_property': None, + 'property': None, + 'type': 'RAW', + }), + dict({ + 'code': 'rpc_response', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'rw', + 'name': '**REDACTED**', + 'product_property': None, + 'property': None, + 'type': 'RAW', + }), + dict({ + 'code': 'error_code', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'state', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'battery', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'fan_power', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'rw', + 'name': '**REDACTED**', + 'product_property': None, + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'water_box_mode', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'rw', + 'name': '**REDACTED**', + 'product_property': None, + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'main_brush_life', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'rw', + 'name': '**REDACTED**', + 'product_property': None, + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'side_brush_life', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'rw', + 'name': '**REDACTED**', + 'product_property': None, + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'filter_life', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'rw', + 'name': '**REDACTED**', + 'product_property': None, + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'additional_props', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': None, + 'type': 'RAW', + }), + dict({ + 'code': 'task_complete', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': None, + 'type': 'RAW', + }), + dict({ + 'code': 'task_cancel_low_power', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': None, + 'type': 'RAW', + }), + dict({ + 'code': 'task_cancel_in_motion', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': None, + 'type': 'RAW', + }), + dict({ + 'code': 'charge_status', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': None, + 'type': 'RAW', + }), + dict({ + 'code': 'drying_status', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': None, + 'type': 'RAW', + }), + ]), }), ]), 'received_devices': list([ ]), 'rooms': list([ dict({ - 'id': '**REDACTED**', + 'id': 2362048, 'name': '**REDACTED**', }), dict({ - 'id': '**REDACTED**', + 'id': 2362044, 'name': '**REDACTED**', }), dict({ - 'id': '**REDACTED**', + 'id': 2362041, 'name': '**REDACTED**', }), ]), }), - 'supported_devices': dict({ - '1.0': 1, - }), }) # --- diff --git a/tests/devices/test_device_manager.py b/tests/devices/test_device_manager.py index b348b42a..397ae2c5 100644 --- a/tests/devices/test_device_manager.py +++ b/tests/devices/test_device_manager.py @@ -358,8 +358,10 @@ async def test_diagnostics_collection(home_data: HomeData, snapshot: syrupy.Snap diagnostics = device_manager.diagnostic_data() assert diagnostics is not None - assert diagnostics.get("discover_devices") == 1 - assert diagnostics.get("fetch_home_data") == 1 + diagnostics_data = diagnostics.get("diagnostics") + assert diagnostics_data + assert diagnostics_data.get("discover_devices") == 1 + assert diagnostics_data.get("fetch_home_data") == 1 assert snapshot == diagnostics @@ -413,6 +415,8 @@ async def test_unsupported_protocol_version() -> None: assert [device.duid for device in devices] == ["device-uid-1"] # Verify diagnostics - data = device_manager.diagnostic_data() - assert data.get("supported_devices") == {"1.0": 1} - assert data.get("unsupported_devices") == {"unknown-pv": 1} + diagnostics = device_manager.diagnostic_data() + diagnostics_data = diagnostics.get("diagnostics") + assert diagnostics_data + assert diagnostics_data.get("supported_devices") == {"1.0": 1} + assert diagnostics_data.get("unsupported_devices") == {"unknown-pv": 1} From 751a2df349eec5f7d8bf38baefdf7057dd57f701 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 30 Dec 2025 08:04:49 -0800 Subject: [PATCH 4/4] chore: Fix schema redaction --- roborock/devices/device.py | 14 +- roborock/diagnostics.py | 3 +- .../__snapshots__/test_device_manager.ambr | 34 +- .../devices/__snapshots__/test_v1_device.ambr | 500 +++++++++++++++++- 4 files changed, 519 insertions(+), 32 deletions(-) diff --git a/roborock/devices/device.py b/roborock/devices/device.py index d7908c74..509c7030 100644 --- a/roborock/devices/device.py +++ b/roborock/devices/device.py @@ -226,9 +226,11 @@ def diagnostic_data(self) -> dict[str, Any]: """Return diagnostics information about the device.""" extra: dict[str, Any] = {} if self.v1_properties: - extra["traits"] = redact_device_data(self.v1_properties.as_dict()) - return { - "device": redact_device_data(self.device_info.as_dict()), - "product": redact_device_data(self.product.as_dict()), - **extra, - } + extra["traits"] = self.v1_properties.as_dict() + return redact_device_data( + { + "device": self.device_info.as_dict(), + "product": self.product.as_dict(), + **extra, + } + ) diff --git a/roborock/diagnostics.py b/roborock/diagnostics.py index f5c0b8f5..fb7ee199 100644 --- a/roborock/diagnostics.py +++ b/roborock/diagnostics.py @@ -114,7 +114,8 @@ def reset(self) -> None: KEEP_KEYS = { # Product information no unique per user "product.id", - "schema.id", + "product.schema.id", + "product.schema.name", # Room ids are likely unique per user, but don't seem too sensitive and are # useful for debugging "rooms.id", diff --git a/tests/devices/__snapshots__/test_device_manager.ambr b/tests/devices/__snapshots__/test_device_manager.ambr index b46534a1..591f1a06 100644 --- a/tests/devices/__snapshots__/test_device_manager.ambr +++ b/tests/devices/__snapshots__/test_device_manager.ambr @@ -39,7 +39,7 @@ 'capability': 0, 'category': 'robot.vacuum.cleaner', 'code': 'a27', - 'id': '**REDACTED**', + 'id': 'product-id-s7-maxv', 'model': 'roborock.vacuum.a27', 'name': '**REDACTED**', 'schema': list([ @@ -47,21 +47,21 @@ 'code': 'rpc_request_code', 'id': '101', 'mode': 'rw', - 'name': '**REDACTED**', + 'name': 'rpc_request', 'type': 'RAW', }), dict({ 'code': 'rpc_response', 'id': '102', 'mode': 'rw', - 'name': '**REDACTED**', + 'name': 'rpc_response', 'type': 'RAW', }), dict({ 'code': 'error_code', 'id': '120', 'mode': 'ro', - 'name': '**REDACTED**', + 'name': '错误代码', 'property': '{"range": []}', 'type': 'ENUM', }), @@ -69,7 +69,7 @@ 'code': 'state', 'id': '121', 'mode': 'ro', - 'name': '**REDACTED**', + 'name': '设备状态', 'property': '{"range": []}', 'type': 'ENUM', }), @@ -77,7 +77,7 @@ 'code': 'battery', 'id': '122', 'mode': 'ro', - 'name': '**REDACTED**', + 'name': '设备电量', 'property': '{"range": []}', 'type': 'ENUM', }), @@ -85,7 +85,7 @@ 'code': 'fan_power', 'id': '123', 'mode': 'rw', - 'name': '**REDACTED**', + 'name': '清扫模式', 'property': '{"range": []}', 'type': 'ENUM', }), @@ -93,7 +93,7 @@ 'code': 'water_box_mode', 'id': '124', 'mode': 'rw', - 'name': '**REDACTED**', + 'name': '拖地模式', 'property': '{"range": []}', 'type': 'ENUM', }), @@ -101,7 +101,7 @@ 'code': 'main_brush_life', 'id': '125', 'mode': 'rw', - 'name': '**REDACTED**', + 'name': '主刷寿命', 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', 'type': 'VALUE', }), @@ -109,7 +109,7 @@ 'code': 'side_brush_life', 'id': '126', 'mode': 'rw', - 'name': '**REDACTED**', + 'name': '边刷寿命', 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', 'type': 'VALUE', }), @@ -117,7 +117,7 @@ 'code': 'filter_life', 'id': '127', 'mode': 'rw', - 'name': '**REDACTED**', + 'name': '滤网寿命', 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', 'type': 'VALUE', }), @@ -125,42 +125,42 @@ 'code': 'additional_props', 'id': '128', 'mode': 'ro', - 'name': '**REDACTED**', + 'name': '额外状态', 'type': 'RAW', }), dict({ 'code': 'task_complete', 'id': '130', 'mode': 'ro', - 'name': '**REDACTED**', + 'name': '完成事件', 'type': 'RAW', }), dict({ 'code': 'task_cancel_low_power', 'id': '131', 'mode': 'ro', - 'name': '**REDACTED**', + 'name': '电量不足任务取消', 'type': 'RAW', }), dict({ 'code': 'task_cancel_in_motion', 'id': '132', 'mode': 'ro', - 'name': '**REDACTED**', + 'name': '运动中任务取消', 'type': 'RAW', }), dict({ 'code': 'charge_status', 'id': '133', 'mode': 'ro', - 'name': '**REDACTED**', + 'name': '充电状态', 'type': 'RAW', }), dict({ 'code': 'drying_status', 'id': '134', 'mode': 'ro', - 'name': '**REDACTED**', + 'name': '烘干状态', 'type': 'RAW', }), ]), diff --git a/tests/devices/__snapshots__/test_v1_device.ambr b/tests/devices/__snapshots__/test_v1_device.ambr index c40a9cb6..94f0c9ec 100644 --- a/tests/devices/__snapshots__/test_v1_device.ambr +++ b/tests/devices/__snapshots__/test_v1_device.ambr @@ -40,10 +40,131 @@ 'capability': 0, 'category': 'robot.vacuum.cleaner', 'code': 'a27', - 'id': '**REDACTED**', + 'id': 'product-id-s7-maxv', 'model': 'roborock.vacuum.a27', 'name': '**REDACTED**', - 'schema': '**REDACTED**', + 'schema': list([ + dict({ + 'code': 'rpc_request_code', + 'id': '101', + 'mode': 'rw', + 'name': 'rpc_request', + 'type': 'RAW', + }), + dict({ + 'code': 'rpc_response', + 'id': '102', + 'mode': 'rw', + 'name': 'rpc_response', + 'type': 'RAW', + }), + dict({ + 'code': 'error_code', + 'id': '120', + 'mode': 'ro', + 'name': '错误代码', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'state', + 'id': '121', + 'mode': 'ro', + 'name': '设备状态', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'battery', + 'id': '122', + 'mode': 'ro', + 'name': '设备电量', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'fan_power', + 'id': '123', + 'mode': 'rw', + 'name': '清扫模式', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'water_box_mode', + 'id': '124', + 'mode': 'rw', + 'name': '拖地模式', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'main_brush_life', + 'id': '125', + 'mode': 'rw', + 'name': '主刷寿命', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'side_brush_life', + 'id': '126', + 'mode': 'rw', + 'name': '边刷寿命', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'filter_life', + 'id': '127', + 'mode': 'rw', + 'name': '滤网寿命', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'additional_props', + 'id': '128', + 'mode': 'ro', + 'name': '额外状态', + 'type': 'RAW', + }), + dict({ + 'code': 'task_complete', + 'id': '130', + 'mode': 'ro', + 'name': '完成事件', + 'type': 'RAW', + }), + dict({ + 'code': 'task_cancel_low_power', + 'id': '131', + 'mode': 'ro', + 'name': '电量不足任务取消', + 'type': 'RAW', + }), + dict({ + 'code': 'task_cancel_in_motion', + 'id': '132', + 'mode': 'ro', + 'name': '运动中任务取消', + 'type': 'RAW', + }), + dict({ + 'code': 'charge_status', + 'id': '133', + 'mode': 'ro', + 'name': '充电状态', + 'type': 'RAW', + }), + dict({ + 'code': 'drying_status', + 'id': '134', + 'mode': 'ro', + 'name': '烘干状态', + 'type': 'RAW', + }), + ]), }), 'traits': dict({ 'clean_summary': dict({ @@ -345,10 +466,131 @@ 'capability': 0, 'category': 'robot.vacuum.cleaner', 'code': 'a27', - 'id': '**REDACTED**', + 'id': 'product-id-s7-maxv', 'model': 'roborock.vacuum.a27', 'name': '**REDACTED**', - 'schema': '**REDACTED**', + 'schema': list([ + dict({ + 'code': 'rpc_request_code', + 'id': '101', + 'mode': 'rw', + 'name': 'rpc_request', + 'type': 'RAW', + }), + dict({ + 'code': 'rpc_response', + 'id': '102', + 'mode': 'rw', + 'name': 'rpc_response', + 'type': 'RAW', + }), + dict({ + 'code': 'error_code', + 'id': '120', + 'mode': 'ro', + 'name': '错误代码', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'state', + 'id': '121', + 'mode': 'ro', + 'name': '设备状态', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'battery', + 'id': '122', + 'mode': 'ro', + 'name': '设备电量', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'fan_power', + 'id': '123', + 'mode': 'rw', + 'name': '清扫模式', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'water_box_mode', + 'id': '124', + 'mode': 'rw', + 'name': '拖地模式', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'main_brush_life', + 'id': '125', + 'mode': 'rw', + 'name': '主刷寿命', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'side_brush_life', + 'id': '126', + 'mode': 'rw', + 'name': '边刷寿命', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'filter_life', + 'id': '127', + 'mode': 'rw', + 'name': '滤网寿命', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'additional_props', + 'id': '128', + 'mode': 'ro', + 'name': '额外状态', + 'type': 'RAW', + }), + dict({ + 'code': 'task_complete', + 'id': '130', + 'mode': 'ro', + 'name': '完成事件', + 'type': 'RAW', + }), + dict({ + 'code': 'task_cancel_low_power', + 'id': '131', + 'mode': 'ro', + 'name': '电量不足任务取消', + 'type': 'RAW', + }), + dict({ + 'code': 'task_cancel_in_motion', + 'id': '132', + 'mode': 'ro', + 'name': '运动中任务取消', + 'type': 'RAW', + }), + dict({ + 'code': 'charge_status', + 'id': '133', + 'mode': 'ro', + 'name': '充电状态', + 'type': 'RAW', + }), + dict({ + 'code': 'drying_status', + 'id': '134', + 'mode': 'ro', + 'name': '烘干状态', + 'type': 'RAW', + }), + ]), }), 'traits': dict({ 'device_features': dict({ @@ -630,10 +872,131 @@ 'capability': 0, 'category': 'robot.vacuum.cleaner', 'code': 'a27', - 'id': '**REDACTED**', + 'id': 'product-id-s7-maxv', 'model': 'roborock.vacuum.a27', 'name': '**REDACTED**', - 'schema': '**REDACTED**', + 'schema': list([ + dict({ + 'code': 'rpc_request_code', + 'id': '101', + 'mode': 'rw', + 'name': 'rpc_request', + 'type': 'RAW', + }), + dict({ + 'code': 'rpc_response', + 'id': '102', + 'mode': 'rw', + 'name': 'rpc_response', + 'type': 'RAW', + }), + dict({ + 'code': 'error_code', + 'id': '120', + 'mode': 'ro', + 'name': '错误代码', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'state', + 'id': '121', + 'mode': 'ro', + 'name': '设备状态', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'battery', + 'id': '122', + 'mode': 'ro', + 'name': '设备电量', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'fan_power', + 'id': '123', + 'mode': 'rw', + 'name': '清扫模式', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'water_box_mode', + 'id': '124', + 'mode': 'rw', + 'name': '拖地模式', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'main_brush_life', + 'id': '125', + 'mode': 'rw', + 'name': '主刷寿命', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'side_brush_life', + 'id': '126', + 'mode': 'rw', + 'name': '边刷寿命', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'filter_life', + 'id': '127', + 'mode': 'rw', + 'name': '滤网寿命', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'additional_props', + 'id': '128', + 'mode': 'ro', + 'name': '额外状态', + 'type': 'RAW', + }), + dict({ + 'code': 'task_complete', + 'id': '130', + 'mode': 'ro', + 'name': '完成事件', + 'type': 'RAW', + }), + dict({ + 'code': 'task_cancel_low_power', + 'id': '131', + 'mode': 'ro', + 'name': '电量不足任务取消', + 'type': 'RAW', + }), + dict({ + 'code': 'task_cancel_in_motion', + 'id': '132', + 'mode': 'ro', + 'name': '运动中任务取消', + 'type': 'RAW', + }), + dict({ + 'code': 'charge_status', + 'id': '133', + 'mode': 'ro', + 'name': '充电状态', + 'type': 'RAW', + }), + dict({ + 'code': 'drying_status', + 'id': '134', + 'mode': 'ro', + 'name': '烘干状态', + 'type': 'RAW', + }), + ]), }), 'traits': dict({ 'device_features': dict({ @@ -886,10 +1249,131 @@ 'capability': 0, 'category': 'robot.vacuum.cleaner', 'code': 'a27', - 'id': '**REDACTED**', + 'id': 'product-id-s7-maxv', 'model': 'roborock.vacuum.a27', 'name': '**REDACTED**', - 'schema': '**REDACTED**', + 'schema': list([ + dict({ + 'code': 'rpc_request_code', + 'id': '101', + 'mode': 'rw', + 'name': 'rpc_request', + 'type': 'RAW', + }), + dict({ + 'code': 'rpc_response', + 'id': '102', + 'mode': 'rw', + 'name': 'rpc_response', + 'type': 'RAW', + }), + dict({ + 'code': 'error_code', + 'id': '120', + 'mode': 'ro', + 'name': '错误代码', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'state', + 'id': '121', + 'mode': 'ro', + 'name': '设备状态', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'battery', + 'id': '122', + 'mode': 'ro', + 'name': '设备电量', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'fan_power', + 'id': '123', + 'mode': 'rw', + 'name': '清扫模式', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'water_box_mode', + 'id': '124', + 'mode': 'rw', + 'name': '拖地模式', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'main_brush_life', + 'id': '125', + 'mode': 'rw', + 'name': '主刷寿命', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'side_brush_life', + 'id': '126', + 'mode': 'rw', + 'name': '边刷寿命', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'filter_life', + 'id': '127', + 'mode': 'rw', + 'name': '滤网寿命', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'additional_props', + 'id': '128', + 'mode': 'ro', + 'name': '额外状态', + 'type': 'RAW', + }), + dict({ + 'code': 'task_complete', + 'id': '130', + 'mode': 'ro', + 'name': '完成事件', + 'type': 'RAW', + }), + dict({ + 'code': 'task_cancel_low_power', + 'id': '131', + 'mode': 'ro', + 'name': '电量不足任务取消', + 'type': 'RAW', + }), + dict({ + 'code': 'task_cancel_in_motion', + 'id': '132', + 'mode': 'ro', + 'name': '运动中任务取消', + 'type': 'RAW', + }), + dict({ + 'code': 'charge_status', + 'id': '133', + 'mode': 'ro', + 'name': '充电状态', + 'type': 'RAW', + }), + dict({ + 'code': 'drying_status', + 'id': '134', + 'mode': 'ro', + 'name': '烘干状态', + 'type': 'RAW', + }), + ]), }), 'traits': dict({ 'device_features': dict({