Skip to content

Commit 8539fe4

Browse files
authored
feat: add a trait for device features (#534)
1 parent 8ae82d1 commit 8539fe4

File tree

7 files changed

+256
-10
lines changed

7 files changed

+256
-10
lines changed

pyproject.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ major_tags= ["refactor"]
8181

8282
[tool.ruff]
8383
lint.ignore = ["F403", "E741"]
84-
line-length = 120
8584
lint.select=["E", "F", "UP", "I"]
85+
line-length = 120
8686

8787
[tool.ruff.lint.per-file-ignores]
8888
"*/__init__.py" = ["F401"]
@@ -92,3 +92,9 @@ asyncio_mode = "auto"
9292
asyncio_default_fixture_loop_scope = "function"
9393
timeout = 30
9494
log_format = "%(asctime)s.%(msecs)03d %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
95+
96+
[tool.uv]
97+
dev-dependencies = [
98+
"pyyaml>=6.0.3",
99+
"pyshark>=0.6",
100+
]

roborock/cli.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@
4141
from pyshark.capture.live_capture import LiveCapture, UnknownInterfaceException # type: ignore
4242
from pyshark.packet.packet import Packet # type: ignore
4343

44-
from roborock import SHORT_MODEL_TO_ENUM, DeviceFeatures, RoborockCommand, RoborockException
44+
from roborock import SHORT_MODEL_TO_ENUM, RoborockCommand, RoborockException
4545
from roborock.containers import CombinedMapInfo, DeviceData, HomeData, NetworkInfo, RoborockBase, UserData
46+
from roborock.device_features import DeviceFeatures
4647
from roborock.devices.cache import Cache, CacheData
4748
from roborock.devices.device import RoborockDevice
4849
from roborock.devices.device_manager import DeviceManager, create_device_manager, create_home_data_api
@@ -541,6 +542,16 @@ async def rooms(ctx, device_id: str):
541542
await _display_v1_trait(context, device_id, lambda v1: v1.rooms)
542543

543544

545+
@session.command()
546+
@click.option("--device_id", required=True)
547+
@click.pass_context
548+
@async_command
549+
async def features(ctx, device_id: str):
550+
"""Get device room mapping info."""
551+
context: RoborockContext = ctx.obj
552+
await _display_v1_trait(context, device_id, lambda v1: v1.device_features)
553+
554+
544555
@session.command()
545556
@click.option("--device_id", required=True)
546557
@click.option("--refresh", is_flag=True, default=False, help="Refresh status before discovery.")
@@ -825,6 +836,7 @@ def write_markdown_table(product_features: dict[str, dict[str, any]], all_featur
825836
cli.add_command(reset_consumable)
826837
cli.add_command(rooms)
827838
cli.add_command(home)
839+
cli.add_command(features)
828840

829841

830842
def main():

roborock/containers.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@
9393
STRAINER_REPLACE_TIME,
9494
ROBOROCK_G20S_Ultra,
9595
)
96-
from .device_features import DeviceFeatures
9796
from .exceptions import RoborockException
9897

9998
_LOGGER = logging.getLogger(__name__)
@@ -280,6 +279,10 @@ class HomeDataProduct(RoborockBase):
280279
capability: int | None = None
281280
schema: list[HomeDataProductSchema] | None = None
282281

282+
@property
283+
def product_nickname(self) -> RoborockProductNickname:
284+
return SHORT_MODEL_TO_ENUM.get(self.model.split(".")[-1], RoborockProductNickname.PEARLPLUS)
285+
283286

284287
@dataclass
285288
class HomeDataDevice(RoborockBase):
@@ -863,7 +866,6 @@ class DeviceData(RoborockBase):
863866
device: HomeDataDevice
864867
model: str
865868
host: str | None = None
866-
device_features: DeviceFeatures | None = None
867869

868870
@property
869871
def product_nickname(self) -> RoborockProductNickname:

roborock/device_features.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import Any
66

77
from .code_mappings import RoborockProductNickname
8+
from .containers import RoborockBase
89

910

1011
class NewFeatureStrBit(IntEnum):
@@ -246,7 +247,7 @@ class ProductFeatures(StrEnum):
246247

247248

248249
@dataclass
249-
class DeviceFeatures:
250+
class DeviceFeatures(RoborockBase):
250251
"""Represents the features supported by a Roborock device."""
251252

252253
# Features from robot_new_features (lower 32 bits)

roborock/devices/traits/v1/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .clean_summary import CleanSummaryTrait
1313
from .common import V1TraitMixin
1414
from .consumeable import ConsumableTrait
15+
from .device_features import DeviceFeaturesTrait
1516
from .do_not_disturb import DoNotDisturbTrait
1617
from .home import HomeTrait
1718
from .map_content import MapContentTrait
@@ -33,6 +34,7 @@
3334
"MapContentTrait",
3435
"ConsumableTrait",
3536
"HomeTrait",
37+
"DeviceFeaturesTrait",
3638
]
3739

3840

@@ -53,6 +55,7 @@ class PropertiesApi(Trait):
5355
map_content: MapContentTrait
5456
consumables: ConsumableTrait
5557
home: HomeTrait
58+
device_features: DeviceFeaturesTrait
5659

5760
# In the future optional fields can be added below based on supported features
5861

@@ -72,6 +75,7 @@ def __init__(
7275
self.maps = MapsTrait(self.status)
7376
self.map_content = MapContentTrait(map_parser_config)
7477
self.home = HomeTrait(self.status, self.maps, self.rooms, cache)
78+
self.device_features = DeviceFeaturesTrait(product.product_nickname)
7579
# This is a hack to allow setting the rpc_channel on all traits. This is
7680
# used so we can preserve the dataclass behavior when the values in the
7781
# traits are updated, but still want to allow them to have a reference
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from roborock import AppInitStatus, RoborockProductNickname
2+
from roborock.device_features import DeviceFeatures
3+
from roborock.devices.traits.v1 import common
4+
from roborock.roborock_typing import RoborockCommand
5+
6+
7+
class DeviceFeaturesTrait(DeviceFeatures, common.V1TraitMixin):
8+
"""Trait for managing Do Not Disturb (DND) settings on Roborock devices."""
9+
10+
command = RoborockCommand.APP_GET_INIT_STATUS
11+
12+
def __init__(self, product_nickname: RoborockProductNickname) -> None:
13+
"""Initialize MapContentTrait."""
14+
self._nickname = product_nickname
15+
16+
def _parse_response(self, response: common.V1ResponseData) -> DeviceFeatures:
17+
"""Parse the response from the device into a MapContentTrait instance."""
18+
if not isinstance(response, list):
19+
raise ValueError(f"Unexpected AppInitStatus response format: {type(response)}")
20+
app_status = AppInitStatus.from_dict(response[0])
21+
return DeviceFeatures.from_feature_flags(
22+
new_feature_info=app_status.new_feature_info,
23+
new_feature_info_str=app_status.new_feature_info_str,
24+
feature_info=app_status.feature_info,
25+
product_nickname=self._nickname,
26+
)

0 commit comments

Comments
 (0)