Skip to content

Commit 83b2d3e

Browse files
committed
Add diagnostic information to the device
This encapsulated diagnostic information entirely within the device since the device has all of the local information such as traits, device information, product information, etc. The motivation is to facilitate local testing rather than testing via Home Assistant and dealing with mocks and it could be helpful for other users of the library as well.
1 parent b34abde commit 83b2d3e

File tree

6 files changed

+1580
-8
lines changed

6 files changed

+1580
-8
lines changed

roborock/devices/device.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
import logging
88
from abc import ABC
9-
from collections.abc import Callable
9+
from collections.abc import Callable, Mapping
10+
from typing import Any, TypeVar, cast
1011

1112
from roborock.data import HomeDataDevice, HomeDataProduct
1213
from roborock.roborock_message import RoborockMessage
@@ -113,3 +114,41 @@ async def close(self) -> None:
113114
def _on_message(self, message: RoborockMessage) -> None:
114115
"""Handle incoming messages from the device."""
115116
_LOGGER.debug("Received message from device: %s", message)
117+
118+
def diagnostic_data(self) -> dict[str, Any]:
119+
"""Return diagnostics information about the device."""
120+
extra: dict[str, Any]= {}
121+
if self.v1_properties:
122+
extra["traits"] = _redact_data(self.v1_properties.as_dict())
123+
return {
124+
"device": _redact_data(self.device_info.as_dict()),
125+
"product": _redact_data(self.product.as_dict()),
126+
**extra,
127+
}
128+
129+
130+
T = TypeVar("T")
131+
132+
REDACT_KEYS = {"duid", "localKey", "mac", "bssid"}
133+
REDACTED = "**REDACTED**"
134+
135+
136+
def _redact_data(data: T) -> T | dict[str, Any] | list[Any]:
137+
"""Redact sensitive data in a dict."""
138+
if not isinstance(data, (Mapping, list)):
139+
return data
140+
141+
if isinstance(data, list):
142+
return cast(T, [_redact_data(item) for item in data])
143+
144+
redacted = {**data}
145+
146+
for key, value in redacted.items():
147+
if key in REDACT_KEYS:
148+
redacted[key] = REDACTED
149+
elif isinstance(value, dict):
150+
redacted[key] = _redact_data(value)
151+
elif isinstance(value, list):
152+
redacted[key] = [_redact_data(item) for item in value]
153+
154+
return redacted

roborock/devices/traits/v1/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from dataclasses import dataclass, field, fields
3535
from typing import Any, get_args
3636

37-
from roborock.data.containers import HomeData, HomeDataProduct
37+
from roborock.data.containers import HomeData, HomeDataProduct, RoborockBase
3838
from roborock.data.v1.v1_code_mappings import RoborockDockTypeCode
3939
from roborock.devices.cache import Cache
4040
from roborock.devices.traits import Trait
@@ -247,6 +247,16 @@ async def _set_cached_trait_data(self, name: str, value: Any) -> None:
247247
_LOGGER.debug("Updating cached trait data: %s", cache_data.trait_data)
248248
await self._cache.set(cache_data)
249249

250+
def as_dict(self) -> dict[str, Any]:
251+
"""Return the trait data as a dictionary."""
252+
result: dict[str, Any] = {}
253+
for item in fields(self):
254+
trait = getattr(self, item.name, None)
255+
if trait is None or not isinstance(trait, RoborockBase):
256+
continue
257+
result[item.name] = trait.as_dict()
258+
return result
259+
250260

251261
def create(
252262
device_uid: str,

roborock/devices/traits/v1/device_features.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from dataclasses import fields
12
from typing import Self
23

34
from roborock.data import AppInitStatus, RoborockProductNickname
@@ -16,6 +17,10 @@ def __init__(self, product_nickname: RoborockProductNickname, cache: Cache) -> N
1617
"""Initialize MapContentTrait."""
1718
self._nickname = product_nickname
1819
self._cache = cache
20+
# All fields of DeviceFeatures are required. Initialize them to False
21+
# so we have some known state.
22+
for field in fields(self):
23+
setattr(self, field.name, False)
1924

2025
async def refresh(self) -> Self:
2126
"""Refresh the contents of this trait.

roborock/devices/traits/v1/network_info.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def __init__(self, device_uid: str, cache: Cache) -> None:
2929
"""Initialize the trait."""
3030
self._device_uid = device_uid
3131
self._cache = cache
32+
self.ip = ""
3233

3334
async def refresh(self) -> Self:
3435
"""Refresh the network info from the cache."""

0 commit comments

Comments
 (0)