Skip to content

Commit 394de87

Browse files
authored
Merge branch 'main' into dynamic_status_trait
2 parents 0ed4902 + 0c4b90a commit 394de87

File tree

104 files changed

+5349
-5198
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+5349
-5198
lines changed

.pre-commit-config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ repos:
2626
rev: v2.2.6
2727
hooks:
2828
- id: codespell
29+
exclude: >
30+
(?x)^(
31+
.*\.ambr
32+
)$
2933
- repo: https://github.com/charliermarsh/ruff-pre-commit
3034
rev: v0.13.2
3135
hooks:

CHANGELOG.md

Lines changed: 345 additions & 0 deletions
Large diffs are not rendered by default.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "python-roborock"
3-
version = "3.19.0"
3+
version = "4.2.0"
44
description = "A package to control Roborock vacuums."
55
authors = [{ name = "humbertogontijo", email = "humbertogontijo@users.noreply.github.com" }, {name="Lash-L"}, {name="allenporter"}]
66
requires-python = ">=3.11, <4"
@@ -44,7 +44,7 @@ dev = [
4444
"pytest",
4545
"pre-commit>=3.5,<5.0",
4646
"mypy",
47-
"ruff==0.14.9",
47+
"ruff==0.14.10",
4848
"codespell",
4949
"pyshark>=0.6,<0.7",
5050
"aioresponses>=0.7.7,<0.8",

roborock/__init__.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,11 @@
88
from roborock.roborock_typing import *
99

1010
from . import (
11-
cloud_api,
1211
const,
1312
data,
1413
devices,
1514
exceptions,
1615
roborock_typing,
17-
version_1_apis,
18-
version_a01_apis,
1916
web_api,
2017
)
2118

roborock/api.py

Lines changed: 0 additions & 104 deletions
This file was deleted.

roborock/callbacks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def decoder_callback(
121121

122122
def wrapper(data: K) -> None:
123123
if not (messages := decoder(data)):
124-
logger.warning("Failed to decode message: %s", data)
124+
logger.debug("Failed to decode message: %s", data)
125125
return
126126
for message in messages:
127127
logger.debug("Decoded message: %s", message)

roborock/cli.py

Lines changed: 68 additions & 38 deletions
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, RoborockCommand
45-
from roborock.data import DeviceData, RoborockBase, UserData
44+
from roborock import RoborockCommand
45+
from roborock.data import RoborockBase, UserData
46+
from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP
4647
from roborock.device_features import DeviceFeatures
4748
from roborock.devices.cache import Cache, CacheData
4849
from roborock.devices.device import RoborockDevice
@@ -53,7 +54,6 @@
5354
from roborock.devices.traits.v1.map_content import MapContentTrait
5455
from roborock.exceptions import RoborockException, RoborockUnsupportedFeature
5556
from roborock.protocol import MessageParser
56-
from roborock.version_1_apis.roborock_mqtt_client_v1 import RoborockMqttClientV1
5757
from roborock.web_api import RoborockApiClient
5858

5959
_LOGGER = logging.getLogger(__name__)
@@ -91,7 +91,14 @@ def wrapper(*args, **kwargs):
9191
context: RoborockContext = ctx.obj
9292

9393
async def run():
94-
return await func(*args, **kwargs)
94+
try:
95+
await func(*args, **kwargs)
96+
except Exception as err:
97+
_LOGGER.exception("Uncaught exception in command")
98+
click.echo(f"Error: {err}", err=True)
99+
finally:
100+
if not context.is_session_mode():
101+
await context.cleanup()
95102

96103
if context.is_session_mode():
97104
# Session mode - run in the persistent loop
@@ -739,6 +746,21 @@ async def network_info(ctx, device_id: str):
739746
await _display_v1_trait(context, device_id, lambda v1: v1.network_info)
740747

741748

749+
def _parse_b01_q10_command(cmd: str) -> B01_Q10_DP:
750+
"""Parse B01_Q10 command from either enum name or value."""
751+
try:
752+
return B01_Q10_DP(int(cmd))
753+
except ValueError:
754+
try:
755+
return B01_Q10_DP.from_name(cmd)
756+
except ValueError:
757+
try:
758+
return B01_Q10_DP.from_value(cmd)
759+
except ValueError:
760+
pass
761+
raise RoborockException(f"Invalid command {cmd} for B01_Q10 device")
762+
763+
742764
@click.command()
743765
@click.option("--device_id", required=True)
744766
@click.option("--cmd", required=True)
@@ -749,12 +771,18 @@ async def command(ctx, cmd, device_id, params):
749771
context: RoborockContext = ctx.obj
750772
device_manager = await context.get_device_manager()
751773
device = await device_manager.get_device(device_id)
752-
if device.v1_properties is None:
753-
raise RoborockException(f"Device {device.name} does not support V1 protocol")
754-
command_trait: Trait = device.v1_properties.command
755-
result = await command_trait.send(cmd, json.loads(params) if params is not None else None)
756-
if result:
757-
click.echo(dump_json(result))
774+
if device.v1_properties is not None:
775+
command_trait: Trait = device.v1_properties.command
776+
result = await command_trait.send(cmd, json.loads(params) if params is not None else None)
777+
if result:
778+
click.echo(dump_json(result))
779+
elif device.b01_q10_properties is not None:
780+
cmd_value = _parse_b01_q10_command(cmd)
781+
command_trait: Trait = device.b01_q10_properties.command
782+
await command_trait.send(cmd_value, json.loads(params) if params is not None else None)
783+
click.echo("Command sent successfully; Enable debug logging (-d) to see responses.")
784+
# Q10 commands don't have a specific time to respond, so wait a bit and log
785+
await asyncio.sleep(5)
758786

759787

760788
@click.command()
@@ -815,44 +843,46 @@ async def get_device_info(ctx: click.Context):
815843
"""
816844
click.echo("Discovering devices...")
817845
context: RoborockContext = ctx.obj
818-
connection_cache = await context.get_devices()
819-
820-
home_data = connection_cache.cache_data.home_data
821-
822-
all_devices = home_data.get_all_devices()
823-
if not all_devices:
846+
device_connection_manager = await context.get_device_manager()
847+
device_manager = await device_connection_manager.ensure_device_manager()
848+
devices = await device_manager.get_devices()
849+
if not devices:
824850
click.echo("No devices found.")
825851
return
826852

827-
click.echo(f"Found {len(all_devices)} devices. Fetching data...")
853+
click.echo(f"Found {len(devices)} devices. Fetching data...")
828854

829855
all_products_data = {}
830856

831-
for device in all_devices:
857+
for device in devices:
832858
click.echo(f" - Processing {device.name} ({device.duid})")
833-
product_info = home_data.product_map[device.product_id]
834-
device_data = DeviceData(device, product_info.model)
835-
mqtt_client = RoborockMqttClientV1(connection_cache.user_data, device_data)
836859

837-
try:
838-
init_status_result = await mqtt_client.send_command(
839-
RoborockCommand.APP_GET_INIT_STATUS,
840-
)
841-
product_nickname = SHORT_MODEL_TO_ENUM.get(product_info.model.split(".")[-1]).name
842-
current_product_data = {
843-
"Protocol Version": device.pv,
844-
"Product Nickname": product_nickname,
845-
"New Feature Info": init_status_result.get("new_feature_info"),
846-
"New Feature Info Str": init_status_result.get("new_feature_info_str"),
847-
"Feature Info": init_status_result.get("feature_info"),
848-
}
860+
if device.product.model in all_products_data:
861+
click.echo(f" - Skipping duplicate model {device.product.model}")
862+
continue
849863

850-
all_products_data[product_info.model] = current_product_data
864+
current_product_data = {
865+
"Protocol Version": device.device_info.pv,
866+
"Product Nickname": device.product.product_nickname.name,
867+
}
868+
if device.v1_properties is not None:
869+
try:
870+
result: list[dict[str, Any]] = await device.v1_properties.command.send(
871+
RoborockCommand.APP_GET_INIT_STATUS
872+
)
873+
except Exception as e:
874+
click.echo(f" - Error processing device {device.name}: {e}", err=True)
875+
continue
876+
init_status_result = result[0] if result else {}
877+
current_product_data.update(
878+
{
879+
"New Feature Info": init_status_result.get("new_feature_info"),
880+
"New Feature Info Str": init_status_result.get("new_feature_info_str"),
881+
"Feature Info": init_status_result.get("feature_info"),
882+
}
883+
)
851884

852-
except Exception as e:
853-
click.echo(f" - Error processing device {device.name}: {e}", err=True)
854-
finally:
855-
await mqtt_client.async_release()
885+
all_products_data[device.product.model] = current_product_data
856886

857887
if all_products_data:
858888
click.echo("\n--- Device Information (copy to your YAML file) ---\n")

0 commit comments

Comments
 (0)