Skip to content
Open
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,
}
)
17 changes: 11 additions & 6 deletions roborock/devices/device_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import enum
import logging
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from dataclasses import asdict, dataclass
from typing import Any

import aiohttp
Expand All @@ -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(asdict(self._home_data) if self._home_data else {}),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is using our as_dict() functionality better?

"devices": [device.diagnostic_data() for device in self._devices.values()],
"diagnostics": self._diagnostics.as_dict(),
}


@dataclass
Expand Down
31 changes: 26 additions & 5 deletions roborock/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,30 +101,51 @@ 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",
"mac",
"wifiName",
"lat",
"long",
}
KEEP_KEYS = {
# Product information no 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