Add diagnostics to Roborock (#94099)
* Add diagnostics * Update homeassistant/components/roborock/models.py Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * adds snapshot --------- Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>pull/94158/head
parent
e6fcc6b73c
commit
49388eab3a
|
@ -0,0 +1,38 @@
|
||||||
|
"""Support for the Airzone diagnostics."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.components.diagnostics.util import async_redact_data
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_UNIQUE_ID
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import RoborockDataUpdateCoordinator
|
||||||
|
|
||||||
|
TO_REDACT_CONFIG = ["token", "sn", "rruid", CONF_UNIQUE_ID, "username", "uid"]
|
||||||
|
|
||||||
|
TO_REDACT_COORD = ["duid", "localKey", "mac", "bssid"]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_get_config_entry_diagnostics(
|
||||||
|
hass: HomeAssistant, config_entry: ConfigEntry
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Return diagnostics for a config entry."""
|
||||||
|
coordinators: dict[str, RoborockDataUpdateCoordinator] = hass.data[DOMAIN][
|
||||||
|
config_entry.entry_id
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"config_entry": async_redact_data(config_entry.data, TO_REDACT_CONFIG),
|
||||||
|
"coordinators": {
|
||||||
|
f"**REDACTED-{i}**": {
|
||||||
|
"roborock_device_info": async_redact_data(
|
||||||
|
coordinator.roborock_device_info.as_dict(), TO_REDACT_COORD
|
||||||
|
),
|
||||||
|
"api": coordinator.api.diagnostic_data,
|
||||||
|
}
|
||||||
|
for i, coordinator in enumerate(coordinators.values())
|
||||||
|
},
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
"""Roborock Models."""
|
"""Roborock Models."""
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from roborock.containers import HomeDataDevice, HomeDataProduct, NetworkInfo
|
from roborock.containers import HomeDataDevice, HomeDataProduct, NetworkInfo
|
||||||
from roborock.roborock_typing import DeviceProp
|
from roborock.roborock_typing import DeviceProp
|
||||||
|
@ -13,3 +14,12 @@ class RoborockHassDeviceInfo:
|
||||||
network_info: NetworkInfo
|
network_info: NetworkInfo
|
||||||
product: HomeDataProduct
|
product: HomeDataProduct
|
||||||
props: DeviceProp
|
props: DeviceProp
|
||||||
|
|
||||||
|
def as_dict(self) -> dict[str, dict[str, Any]]:
|
||||||
|
"""Turn RoborockHassDeviceInfo into a dictionary."""
|
||||||
|
return {
|
||||||
|
"device": self.device.as_dict(),
|
||||||
|
"network_info": self.network_info.as_dict(),
|
||||||
|
"product": self.product.as_dict(),
|
||||||
|
"props": self.props.as_dict(),
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ from homeassistant.const import CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from .mock_data import BASE_URL, HOME_DATA, PROP, USER_DATA, USER_EMAIL
|
from .mock_data import BASE_URL, HOME_DATA, NETWORK_INFO, PROP, USER_DATA, USER_EMAIL
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
@ -54,7 +54,8 @@ async def setup_entry(
|
||||||
"homeassistant.components.roborock.RoborockApiClient.get_home_data",
|
"homeassistant.components.roborock.RoborockApiClient.get_home_data",
|
||||||
return_value=HOME_DATA,
|
return_value=HOME_DATA,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.roborock.RoborockMqttClient.get_networking"
|
"homeassistant.components.roborock.RoborockMqttClient.get_networking",
|
||||||
|
return_value=NETWORK_INFO,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.roborock.coordinator.RoborockLocalClient.get_prop",
|
"homeassistant.components.roborock.coordinator.RoborockLocalClient.get_prop",
|
||||||
return_value=PROP,
|
return_value=PROP,
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
"""Mock data for Roborock tests."""
|
"""Mock data for Roborock tests."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
from roborock.containers import (
|
from roborock.containers import (
|
||||||
CleanRecord,
|
CleanRecord,
|
||||||
CleanSummary,
|
CleanSummary,
|
||||||
|
@ -320,6 +322,8 @@ DND_TIMER = DnDTimer.from_dict(
|
||||||
"enabled": 1,
|
"enabled": 1,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
DND_TIMER.start_time = datetime.datetime(year=2023, month=6, day=1, hour=22)
|
||||||
|
DND_TIMER.end_time = datetime.datetime(year=2023, month=6, day=2, hour=7)
|
||||||
|
|
||||||
STATUS = S7Status.from_dict(
|
STATUS = S7Status.from_dict(
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,303 @@
|
||||||
|
# serializer version: 1
|
||||||
|
# name: test_diagnostics
|
||||||
|
dict({
|
||||||
|
'config_entry': dict({
|
||||||
|
'base_url': 'https://usiot.roborock.com',
|
||||||
|
'user_data': dict({
|
||||||
|
'avatarurl': 'https://files.roborock.com/iottest/default_avatar.png',
|
||||||
|
'country': 'US',
|
||||||
|
'countrycode': '1',
|
||||||
|
'nickname': 'user_nickname',
|
||||||
|
'region': 'us',
|
||||||
|
'rriot': dict({
|
||||||
|
'h': 'abc123',
|
||||||
|
'k': 'abc123',
|
||||||
|
'r': dict({
|
||||||
|
'a': 'https://api-us.roborock.com',
|
||||||
|
'l': 'https://wood-us.roborock.com',
|
||||||
|
'm': 'ssl://mqtt-us-2.roborock.com:8883',
|
||||||
|
'r': 'US',
|
||||||
|
}),
|
||||||
|
's': 'abc123',
|
||||||
|
'u': 'abc123',
|
||||||
|
}),
|
||||||
|
'rruid': '**REDACTED**',
|
||||||
|
'token': '**REDACTED**',
|
||||||
|
'tokentype': '',
|
||||||
|
'tuyaDeviceState': 2,
|
||||||
|
'uid': '**REDACTED**',
|
||||||
|
}),
|
||||||
|
'username': '**REDACTED**',
|
||||||
|
}),
|
||||||
|
'coordinators': dict({
|
||||||
|
'**REDACTED-0**': dict({
|
||||||
|
'api': dict({
|
||||||
|
}),
|
||||||
|
'roborock_device_info': 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': '**REDACTED**',
|
||||||
|
'extra': '{"RRPhotoPrivacyVersion": "1"}',
|
||||||
|
'featureSet': '2234201184108543',
|
||||||
|
'fv': '02.56.02',
|
||||||
|
'iconUrl': '',
|
||||||
|
'localKey': '**REDACTED**',
|
||||||
|
'name': 'Roborock S7 MaxV',
|
||||||
|
'newFeatureSet': '0000000000002041',
|
||||||
|
'online': True,
|
||||||
|
'productId': 'abc123',
|
||||||
|
'pv': '1.0',
|
||||||
|
'roomId': 2362003,
|
||||||
|
'share': False,
|
||||||
|
'silentOtaSwitch': True,
|
||||||
|
'sn': 'abc123',
|
||||||
|
'timeZoneId': 'America/Los_Angeles',
|
||||||
|
'tuyaMigrated': False,
|
||||||
|
}),
|
||||||
|
'network_info': dict({
|
||||||
|
'bssid': '**REDACTED**',
|
||||||
|
'ip': '123.232.12.1',
|
||||||
|
'mac': '**REDACTED**',
|
||||||
|
'rssi': 90,
|
||||||
|
'ssid': 'wifi',
|
||||||
|
}),
|
||||||
|
'product': dict({
|
||||||
|
'capability': 0,
|
||||||
|
'category': 'robot.vacuum.cleaner',
|
||||||
|
'code': 'a27',
|
||||||
|
'id': 'abc123',
|
||||||
|
'model': 'roborock.vacuum.a27',
|
||||||
|
'name': 'Roborock S7 MaxV',
|
||||||
|
'schema': list([
|
||||||
|
dict({
|
||||||
|
'code': 'rpc_request',
|
||||||
|
'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': '错误代码',
|
||||||
|
'type': 'ENUM',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'code': 'state',
|
||||||
|
'id': '121',
|
||||||
|
'mode': 'ro',
|
||||||
|
'name': '设备状态',
|
||||||
|
'type': 'ENUM',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'code': 'battery',
|
||||||
|
'id': '122',
|
||||||
|
'mode': 'ro',
|
||||||
|
'name': '设备电量',
|
||||||
|
'type': 'ENUM',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'code': 'fan_power',
|
||||||
|
'id': '123',
|
||||||
|
'mode': 'rw',
|
||||||
|
'name': '清扫模式',
|
||||||
|
'type': 'ENUM',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'code': 'water_box_mode',
|
||||||
|
'id': '124',
|
||||||
|
'mode': 'rw',
|
||||||
|
'name': '拖地模式',
|
||||||
|
'type': 'ENUM',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'code': 'main_brush_life',
|
||||||
|
'id': '125',
|
||||||
|
'mode': 'rw',
|
||||||
|
'name': '主刷寿命',
|
||||||
|
'type': 'VALUE',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'code': 'side_brush_life',
|
||||||
|
'id': '126',
|
||||||
|
'mode': 'rw',
|
||||||
|
'name': '边刷寿命',
|
||||||
|
'type': 'VALUE',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'code': 'filter_life',
|
||||||
|
'id': '127',
|
||||||
|
'mode': 'rw',
|
||||||
|
'name': '滤网寿命',
|
||||||
|
'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',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'props': dict({
|
||||||
|
'cleanSummary': dict({
|
||||||
|
'cleanArea': 1159182500,
|
||||||
|
'cleanCount': 31,
|
||||||
|
'cleanTime': 74382,
|
||||||
|
'dustCollectionCount': 25,
|
||||||
|
'records': list([
|
||||||
|
1672543330,
|
||||||
|
1672458041,
|
||||||
|
]),
|
||||||
|
'squareMeterCleanArea': 1159.2,
|
||||||
|
}),
|
||||||
|
'consumable': dict({
|
||||||
|
'cleaningBrushWorkTimes': 65,
|
||||||
|
'dustCollectionWorkTimes': 25,
|
||||||
|
'filterElementWorkTime': 0,
|
||||||
|
'filterTimeLeft': 465618,
|
||||||
|
'filterWorkTime': 74382,
|
||||||
|
'mainBrushTimeLeft': 1005618,
|
||||||
|
'mainBrushWorkTime': 74382,
|
||||||
|
'sensorDirtyTime': 74382,
|
||||||
|
'sensorTimeLeft': 33618,
|
||||||
|
'sideBrushTimeLeft': 645618,
|
||||||
|
'sideBrushWorkTime': 74382,
|
||||||
|
'strainerWorkTimes': 65,
|
||||||
|
}),
|
||||||
|
'dndTimer': dict({
|
||||||
|
'enabled': 1,
|
||||||
|
'endHour': 7,
|
||||||
|
'endMinute': 0,
|
||||||
|
'endTime': '2023-06-02T07:00:00',
|
||||||
|
'startHour': 22,
|
||||||
|
'startMinute': 0,
|
||||||
|
'startTime': '2023-06-01T22:00:00',
|
||||||
|
}),
|
||||||
|
'lastCleanRecord': dict({
|
||||||
|
'area': 20965000,
|
||||||
|
'avoidCount': 19,
|
||||||
|
'begin': 1672543330,
|
||||||
|
'cleanType': 3,
|
||||||
|
'complete': 1,
|
||||||
|
'duration': 1176,
|
||||||
|
'dustCollectionStatus': 1,
|
||||||
|
'end': 1672544638,
|
||||||
|
'error': 0,
|
||||||
|
'finishReason': 56,
|
||||||
|
'mapFlag': 0,
|
||||||
|
'squareMeterArea': 21.0,
|
||||||
|
'startType': 2,
|
||||||
|
'washCount': 2,
|
||||||
|
}),
|
||||||
|
'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,
|
||||||
|
'squareMeterCleanArea': 21.0,
|
||||||
|
'state': 8,
|
||||||
|
'switchMapMode': 0,
|
||||||
|
'unsaveMapFlag': 0,
|
||||||
|
'unsaveMapReason': 0,
|
||||||
|
'washPhase': 0,
|
||||||
|
'washReady': 0,
|
||||||
|
'waterBoxCarriageStatus': 1,
|
||||||
|
'waterBoxMode': 203,
|
||||||
|
'waterBoxStatus': 1,
|
||||||
|
'waterShortageStatus': 0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
# ---
|
|
@ -0,0 +1,23 @@
|
||||||
|
"""Tests for the diagnostics data provided by the Roborock integration."""
|
||||||
|
|
||||||
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
from tests.components.diagnostics import get_diagnostics_for_config_entry
|
||||||
|
from tests.typing import ClientSessionGenerator
|
||||||
|
|
||||||
|
|
||||||
|
async def test_diagnostics(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_client: ClientSessionGenerator,
|
||||||
|
bypass_api_fixture,
|
||||||
|
setup_entry: MockConfigEntry,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
) -> None:
|
||||||
|
"""Test diagnostics for config entry."""
|
||||||
|
result = await get_diagnostics_for_config_entry(hass, hass_client, setup_entry)
|
||||||
|
|
||||||
|
assert isinstance(result, dict)
|
||||||
|
assert result == snapshot
|
Loading…
Reference in New Issue