Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions roborock/devices/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
)
15 changes: 10 additions & 5 deletions roborock/devices/device_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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."""
Expand All @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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 {
"home_data": redact_device_data(self._home_data.as_dict()) if self._home_data else None,
"devices": [device.diagnostic_data() for device in self._devices.values()],
"diagnostics": self._diagnostics.as_dict(),
}


@dataclass
Expand Down
30 changes: 25 additions & 5 deletions roborock/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,30 +101,50 @@ def reset(self) -> None:
"imageContent",
"mapData",
"rawApiResponse",
# Home data
"id", # We want to redact home_data.id but keep some other ids, see below
"name",
"productId",
"ipAddress",
"wifiName",
"lat",
"long",
}
KEEP_KEYS = {
# Product information not unique per user
"product.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",
}
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

Expand Down
Loading