python-roborock 2.51.0__tar.gz → 2.52.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_roborock-2.51.0 → python_roborock-2.52.0}/PKG-INFO +1 -1
- {python_roborock-2.51.0 → python_roborock-2.52.0}/pyproject.toml +1 -1
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/cli.py +11 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/device_manager.py +4 -4
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/traits/v1/__init__.py +11 -4
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/traits/v1/common.py +2 -2
- python_roborock-2.52.0/roborock/devices/traits/v1/rooms.py +93 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/LICENSE +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/README.md +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/__init__.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/api.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/b01_containers.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/callbacks.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/clean_modes.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/cloud_api.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/code_mappings.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/command_cache.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/const.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/containers.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/device_features.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/README.md +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/__init__.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/a01_channel.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/b01_channel.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/cache.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/channel.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/device.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/local_channel.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/mqtt_channel.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/traits/a01/__init__.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/v1_channel.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/v1_rpc_channel.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/exceptions.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/map/map_parser.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/mqtt/session.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/protocol.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/protocols/b01_protocol.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/py.typed +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/roborock_future.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/roborock_message.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/roborock_typing.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/util.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/version_1_apis/roborock_client_v1.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
- {python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/web_api.py +0 -0
|
@@ -475,6 +475,16 @@ async def reset_consumable(ctx, device_id: str, consumable: str):
|
|
|
475
475
|
click.echo(f"Reset {consumable} for device {device_id}")
|
|
476
476
|
|
|
477
477
|
|
|
478
|
+
@session.command()
|
|
479
|
+
@click.option("--device_id", required=True)
|
|
480
|
+
@click.pass_context
|
|
481
|
+
@async_command
|
|
482
|
+
async def rooms(ctx, device_id: str):
|
|
483
|
+
"""Get device room mapping info."""
|
|
484
|
+
context: RoborockContext = ctx.obj
|
|
485
|
+
await _display_v1_trait(context, device_id, lambda v1: v1.rooms)
|
|
486
|
+
|
|
487
|
+
|
|
478
488
|
@click.command()
|
|
479
489
|
@click.option("--device_id", required=True)
|
|
480
490
|
@click.option("--cmd", required=True)
|
|
@@ -719,6 +729,7 @@ cli.add_command(set_volume)
|
|
|
719
729
|
cli.add_command(maps)
|
|
720
730
|
cli.add_command(consumables)
|
|
721
731
|
cli.add_command(reset_consumable)
|
|
732
|
+
cli.add_command(rooms)
|
|
722
733
|
|
|
723
734
|
|
|
724
735
|
def main():
|
|
@@ -35,7 +35,7 @@ __all__ = [
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
HomeDataApi = Callable[[], Awaitable[HomeData]]
|
|
38
|
-
DeviceCreator = Callable[[HomeDataDevice, HomeDataProduct], RoborockDevice]
|
|
38
|
+
DeviceCreator = Callable[[HomeData, HomeDataDevice, HomeDataProduct], RoborockDevice]
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class DeviceVersion(enum.StrEnum):
|
|
@@ -84,7 +84,7 @@ class DeviceManager:
|
|
|
84
84
|
for duid, (device, product) in device_products.items():
|
|
85
85
|
if duid in self._devices:
|
|
86
86
|
continue
|
|
87
|
-
new_device = self._device_creator(device, product)
|
|
87
|
+
new_device = self._device_creator(home_data, device, product)
|
|
88
88
|
await new_device.connect()
|
|
89
89
|
new_devices[duid] = new_device
|
|
90
90
|
|
|
@@ -143,13 +143,13 @@ async def create_device_manager(
|
|
|
143
143
|
mqtt_params = create_mqtt_params(user_data.rriot)
|
|
144
144
|
mqtt_session = await create_lazy_mqtt_session(mqtt_params)
|
|
145
145
|
|
|
146
|
-
def device_creator(device: HomeDataDevice, product: HomeDataProduct) -> RoborockDevice:
|
|
146
|
+
def device_creator(home_data: HomeData, device: HomeDataDevice, product: HomeDataProduct) -> RoborockDevice:
|
|
147
147
|
channel: Channel
|
|
148
148
|
trait: Trait
|
|
149
149
|
match device.pv:
|
|
150
150
|
case DeviceVersion.V1:
|
|
151
151
|
channel = create_v1_channel(user_data, mqtt_params, mqtt_session, device, cache)
|
|
152
|
-
trait = v1.create(product, channel.rpc_channel, channel.mqtt_rpc_channel)
|
|
152
|
+
trait = v1.create(product, home_data, channel.rpc_channel, channel.mqtt_rpc_channel)
|
|
153
153
|
case DeviceVersion.A01:
|
|
154
154
|
channel = create_mqtt_channel(user_data, mqtt_params, mqtt_session, device)
|
|
155
155
|
trait = a01.create(product, channel)
|
|
@@ -12,6 +12,7 @@ from .common import V1TraitMixin
|
|
|
12
12
|
from .consumeable import ConsumableTrait
|
|
13
13
|
from .do_not_disturb import DoNotDisturbTrait
|
|
14
14
|
from .maps import MapsTrait
|
|
15
|
+
from .rooms import RoomsTrait
|
|
15
16
|
from .status import StatusTrait
|
|
16
17
|
from .volume import SoundVolumeTrait
|
|
17
18
|
|
|
@@ -41,14 +42,18 @@ class PropertiesApi(Trait):
|
|
|
41
42
|
dnd: DoNotDisturbTrait
|
|
42
43
|
clean_summary: CleanSummaryTrait
|
|
43
44
|
sound_volume: SoundVolumeTrait
|
|
45
|
+
rooms: RoomsTrait
|
|
44
46
|
maps: MapsTrait
|
|
45
47
|
consumables: ConsumableTrait
|
|
46
48
|
|
|
47
49
|
# In the future optional fields can be added below based on supported features
|
|
48
50
|
|
|
49
|
-
def __init__(
|
|
50
|
-
|
|
51
|
+
def __init__(
|
|
52
|
+
self, product: HomeDataProduct, home_data: HomeData, rpc_channel: V1RpcChannel, mqtt_rpc_channel: V1RpcChannel
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Initialize the V1TraitProps."""
|
|
51
55
|
self.status = StatusTrait(product)
|
|
56
|
+
self.rooms = RoomsTrait(home_data)
|
|
52
57
|
self.maps = MapsTrait(self.status)
|
|
53
58
|
|
|
54
59
|
# This is a hack to allow setting the rpc_channel on all traits. This is
|
|
@@ -67,6 +72,8 @@ class PropertiesApi(Trait):
|
|
|
67
72
|
trait._rpc_channel = rpc_channel
|
|
68
73
|
|
|
69
74
|
|
|
70
|
-
def create(
|
|
75
|
+
def create(
|
|
76
|
+
product: HomeDataProduct, home_data: HomeData, rpc_channel: V1RpcChannel, mqtt_rpc_channel: V1RpcChannel
|
|
77
|
+
) -> PropertiesApi:
|
|
71
78
|
"""Create traits for V1 devices."""
|
|
72
|
-
return PropertiesApi(product, rpc_channel, mqtt_rpc_channel)
|
|
79
|
+
return PropertiesApi(product, home_data, rpc_channel, mqtt_rpc_channel)
|
|
@@ -38,7 +38,7 @@ class V1TraitMixin(ABC):
|
|
|
38
38
|
command: ClassVar[RoborockCommand]
|
|
39
39
|
|
|
40
40
|
@classmethod
|
|
41
|
-
def _parse_type_response(cls, response: V1ResponseData) ->
|
|
41
|
+
def _parse_type_response(cls, response: V1ResponseData) -> RoborockBase:
|
|
42
42
|
"""Parse the response from the device into a a RoborockBase.
|
|
43
43
|
|
|
44
44
|
Subclasses should override this method to implement custom parsing
|
|
@@ -53,7 +53,7 @@ class V1TraitMixin(ABC):
|
|
|
53
53
|
raise ValueError(f"Unexpected {cls} response format: {response!r}")
|
|
54
54
|
return cls.from_dict(response)
|
|
55
55
|
|
|
56
|
-
def _parse_response(self, response: V1ResponseData) ->
|
|
56
|
+
def _parse_response(self, response: V1ResponseData) -> RoborockBase:
|
|
57
57
|
"""Parse the response from the device into a a RoborockBase.
|
|
58
58
|
|
|
59
59
|
This is used by subclasses that want to override the class
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Trait for managing room mappings on Roborock devices."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from roborock.containers import HomeData, RoborockBase, RoomMapping
|
|
7
|
+
from roborock.devices.traits.v1 import common
|
|
8
|
+
from roborock.roborock_typing import RoborockCommand
|
|
9
|
+
|
|
10
|
+
_LOGGER = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
_DEFAULT_NAME = "Unknown"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class NamedRoomMapping(RoomMapping):
|
|
17
|
+
"""Dataclass representing a mapping of a room segment to a name.
|
|
18
|
+
|
|
19
|
+
The name information is not provided by the device directly, but is provided
|
|
20
|
+
from the HomeData based on the iot_id from the room.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
name: str
|
|
24
|
+
"""The human-readable name of the room, if available."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class Rooms(RoborockBase):
|
|
29
|
+
"""Dataclass representing a collection of room mappings."""
|
|
30
|
+
|
|
31
|
+
rooms: list[NamedRoomMapping] | None = None
|
|
32
|
+
"""List of room mappings."""
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def room_map(self) -> dict[int, NamedRoomMapping]:
|
|
36
|
+
"""Returns a mapping of segment_id to NamedRoomMapping."""
|
|
37
|
+
if self.rooms is None:
|
|
38
|
+
return {}
|
|
39
|
+
return {room.segment_id: room for room in self.rooms}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class RoomsTrait(Rooms, common.V1TraitMixin):
|
|
43
|
+
"""Trait for managing the room mappings of Roborock devices."""
|
|
44
|
+
|
|
45
|
+
command = RoborockCommand.GET_ROOM_MAPPING
|
|
46
|
+
|
|
47
|
+
def __init__(self, home_data: HomeData) -> None:
|
|
48
|
+
"""Initialize the RoomsTrait."""
|
|
49
|
+
super().__init__()
|
|
50
|
+
self._home_data = home_data
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def _iot_id_room_name_map(self) -> dict[str, str]:
|
|
54
|
+
"""Returns a dictionary of Room IOT IDs to room names."""
|
|
55
|
+
return {str(room.id): room.name for room in self._home_data.rooms or ()}
|
|
56
|
+
|
|
57
|
+
def _parse_response(self, response: common.V1ResponseData) -> Rooms:
|
|
58
|
+
"""Parse the response from the device into a list of NamedRoomMapping."""
|
|
59
|
+
if not isinstance(response, list):
|
|
60
|
+
raise ValueError(f"Unexpected RoomsTrait response format: {response!r}")
|
|
61
|
+
name_map = self._iot_id_room_name_map
|
|
62
|
+
segment_pairs = _extract_segment_pairs(response)
|
|
63
|
+
return Rooms(
|
|
64
|
+
rooms=[
|
|
65
|
+
NamedRoomMapping(segment_id=segment_id, iot_id=iot_id, name=name_map.get(iot_id, _DEFAULT_NAME))
|
|
66
|
+
for segment_id, iot_id in segment_pairs
|
|
67
|
+
]
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _extract_segment_pairs(response: list) -> list[tuple[int, str]]:
|
|
72
|
+
"""Extract segment_id and iot_id pairs from the response.
|
|
73
|
+
|
|
74
|
+
The response format can be either a flat list of [segment_id, iot_id] or a
|
|
75
|
+
list of lists, where each inner list is a pair of [segment_id, iot_id]. This
|
|
76
|
+
function normalizes the response into a list of (segment_id, iot_id) tuples
|
|
77
|
+
|
|
78
|
+
NOTE: We currently only partial samples of the room mapping formats, so
|
|
79
|
+
improving test coverage with samples from a real device with this format
|
|
80
|
+
would be helpful.
|
|
81
|
+
"""
|
|
82
|
+
if len(response) == 2 and not isinstance(response[0], list):
|
|
83
|
+
segment_id, iot_id = response[0], response[1]
|
|
84
|
+
return [(segment_id, iot_id)]
|
|
85
|
+
|
|
86
|
+
segment_pairs: list[tuple[int, str]] = []
|
|
87
|
+
for part in response:
|
|
88
|
+
if not isinstance(part, list) or len(part) < 2:
|
|
89
|
+
_LOGGER.warning("Unexpected room mapping entry format: %r", part)
|
|
90
|
+
continue
|
|
91
|
+
segment_id, iot_id = part[0], part[1]
|
|
92
|
+
segment_pairs.append((segment_id, iot_id))
|
|
93
|
+
return segment_pairs
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/traits/v1/clean_summary.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/devices/traits/v1/do_not_disturb.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/version_1_apis/roborock_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-2.51.0 → python_roborock-2.52.0}/roborock/version_a01_apis/roborock_client_a01.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|