python-roborock 4.5.0__tar.gz → 4.7.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-4.5.0 → python_roborock-4.7.0}/PKG-INFO +1 -1
- {python_roborock-4.5.0 → python_roborock-4.7.0}/pyproject.toml +2 -2
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/cli.py +208 -56
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/zeo/zeo_code_mappings.py +2 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/device_features.py +14 -1
- {python_roborock-4.5.0 → python_roborock-4.7.0}/.gitignore +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/LICENSE +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/README.md +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/callbacks.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/const.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/b01_q10/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/b01_q7/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/code_mappings.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/containers.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/dyad/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/dyad/dyad_code_mappings.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/dyad/dyad_containers.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/v1/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/v1/v1_clean_modes.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/v1/v1_code_mappings.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/v1/v1_containers.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/zeo/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/zeo/zeo_containers.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/README.md +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/cache.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/device.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/device_manager.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/file_cache.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/rpc/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/rpc/a01_channel.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/rpc/b01_q10_channel.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/rpc/b01_q7_channel.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/rpc/v1_channel.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/a01/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/b01/q10/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/b01/q10/command.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/b01/q7/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/child_lock.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/command.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/common.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/device_features.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/flow_led_status.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/home.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/led_status.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/map_content.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/network_info.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/rooms.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/routines.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/transport/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/transport/channel.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/transport/local_channel.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/transport/mqtt_channel.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/diagnostics.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/exceptions.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/map/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/map/map_parser.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/mqtt/health_manager.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/mqtt/session.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/protocol.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/protocols/__init__.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/protocols/b01_q10_protocol.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/protocols/b01_q7_protocol.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/py.typed +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/roborock_message.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/roborock_typing.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/util.py +0 -0
- {python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/web_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.7.0
|
|
4
4
|
Summary: A package to control Roborock vacuums.
|
|
5
5
|
Project-URL: Repository, https://github.com/humbertogontijo/python-roborock
|
|
6
6
|
Project-URL: Documentation, https://python-roborock.readthedocs.io/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-roborock"
|
|
3
|
-
version = "4.
|
|
3
|
+
version = "4.7.0"
|
|
4
4
|
description = "A package to control Roborock vacuums."
|
|
5
5
|
authors = [{ name = "humbertogontijo", email = "humbertogontijo@users.noreply.github.com" }, {name="Lash-L"}, {name="allenporter"}]
|
|
6
6
|
requires-python = ">=3.11, <4"
|
|
@@ -44,7 +44,7 @@ dev = [
|
|
|
44
44
|
"pytest",
|
|
45
45
|
"pre-commit>=3.5,<5.0",
|
|
46
46
|
"mypy",
|
|
47
|
-
"ruff==0.14.
|
|
47
|
+
"ruff==0.14.11",
|
|
48
48
|
"codespell",
|
|
49
49
|
"pyshark>=0.6,<0.7",
|
|
50
50
|
"aioresponses>=0.7.7,<0.8",
|
|
@@ -44,6 +44,7 @@ from pyshark.packet.packet import Packet # type: ignore
|
|
|
44
44
|
from roborock import RoborockCommand
|
|
45
45
|
from roborock.data import RoborockBase, UserData
|
|
46
46
|
from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP
|
|
47
|
+
from roborock.data.code_mappings import SHORT_MODEL_TO_ENUM
|
|
47
48
|
from roborock.device_features import DeviceFeatures
|
|
48
49
|
from roborock.devices.cache import Cache, CacheData
|
|
49
50
|
from roborock.devices.device import RoborockDevice
|
|
@@ -299,6 +300,12 @@ def cli(ctx, debug: int):
|
|
|
299
300
|
|
|
300
301
|
@click.command()
|
|
301
302
|
@click.option("--email", required=True)
|
|
303
|
+
@click.option(
|
|
304
|
+
"--reauth",
|
|
305
|
+
is_flag=True,
|
|
306
|
+
default=False,
|
|
307
|
+
help="Re-authenticate even if cached credentials exist.",
|
|
308
|
+
)
|
|
302
309
|
@click.option(
|
|
303
310
|
"--password",
|
|
304
311
|
required=False,
|
|
@@ -306,15 +313,16 @@ def cli(ctx, debug: int):
|
|
|
306
313
|
)
|
|
307
314
|
@click.pass_context
|
|
308
315
|
@async_command
|
|
309
|
-
async def login(ctx, email, password):
|
|
316
|
+
async def login(ctx, email, password, reauth):
|
|
310
317
|
"""Login to Roborock account."""
|
|
311
318
|
context: RoborockContext = ctx.obj
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
319
|
+
if not reauth:
|
|
320
|
+
try:
|
|
321
|
+
context.validate()
|
|
322
|
+
_LOGGER.info("Already logged in")
|
|
323
|
+
return
|
|
324
|
+
except RoborockException:
|
|
325
|
+
pass
|
|
318
326
|
client = RoborockApiClient(email)
|
|
319
327
|
if password is not None:
|
|
320
328
|
user_data = await client.pass_login(password)
|
|
@@ -834,59 +842,203 @@ async def parser(_, local_key, device_ip, file):
|
|
|
834
842
|
)
|
|
835
843
|
|
|
836
844
|
|
|
845
|
+
def _parse_diagnostic_file(diagnostic_path: Path) -> dict[str, dict[str, Any]]:
|
|
846
|
+
"""Parse device info from a Home Assistant diagnostic file.
|
|
847
|
+
|
|
848
|
+
Args:
|
|
849
|
+
diagnostic_path: Path to the diagnostic JSON file.
|
|
850
|
+
|
|
851
|
+
Returns:
|
|
852
|
+
A dictionary mapping model names to device info dictionaries.
|
|
853
|
+
"""
|
|
854
|
+
with open(diagnostic_path, encoding="utf-8") as f:
|
|
855
|
+
diagnostic_data = json.load(f)
|
|
856
|
+
|
|
857
|
+
all_products_data: dict[str, dict[str, Any]] = {}
|
|
858
|
+
|
|
859
|
+
# Navigate to coordinators in the diagnostic data
|
|
860
|
+
coordinators = diagnostic_data.get("data", {}).get("coordinators", {})
|
|
861
|
+
if not coordinators:
|
|
862
|
+
return all_products_data
|
|
863
|
+
|
|
864
|
+
for coordinator_data in coordinators.values():
|
|
865
|
+
device_data = coordinator_data.get("device", {})
|
|
866
|
+
product_data = coordinator_data.get("product", {})
|
|
867
|
+
|
|
868
|
+
model = product_data.get("model")
|
|
869
|
+
if not model or model in all_products_data:
|
|
870
|
+
continue
|
|
871
|
+
# Derive product nickname from model
|
|
872
|
+
short_model = model.split(".")[-1]
|
|
873
|
+
product_nickname = SHORT_MODEL_TO_ENUM.get(short_model)
|
|
874
|
+
|
|
875
|
+
current_product_data: dict[str, Any] = {
|
|
876
|
+
"protocol_version": device_data.get("pv"),
|
|
877
|
+
"product_nickname": product_nickname.name if product_nickname else "Unknown",
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
# Get feature info from the device_features trait (preferred location)
|
|
881
|
+
traits_data = coordinator_data.get("traits", {})
|
|
882
|
+
device_features = traits_data.get("device_features", {})
|
|
883
|
+
|
|
884
|
+
# newFeatureInfo is the integer
|
|
885
|
+
new_feature_info = device_features.get("newFeatureInfo")
|
|
886
|
+
if new_feature_info is not None:
|
|
887
|
+
current_product_data["new_feature_info"] = new_feature_info
|
|
888
|
+
|
|
889
|
+
# newFeatureInfoStr is the hex string
|
|
890
|
+
new_feature_info_str = device_features.get("newFeatureInfoStr")
|
|
891
|
+
if new_feature_info_str:
|
|
892
|
+
current_product_data["new_feature_info_str"] = new_feature_info_str
|
|
893
|
+
|
|
894
|
+
# featureInfo is the list of feature codes
|
|
895
|
+
feature_info = device_features.get("featureInfo")
|
|
896
|
+
if feature_info:
|
|
897
|
+
current_product_data["feature_info"] = feature_info
|
|
898
|
+
|
|
899
|
+
# Build product dict from diagnostic product data
|
|
900
|
+
if product_data:
|
|
901
|
+
# Convert to the format expected by device_info.yaml
|
|
902
|
+
product_dict: dict[str, Any] = {}
|
|
903
|
+
for key in ["id", "name", "model", "category", "capability", "schema"]:
|
|
904
|
+
if key in product_data:
|
|
905
|
+
product_dict[key] = product_data[key]
|
|
906
|
+
if product_dict:
|
|
907
|
+
current_product_data["product"] = product_dict
|
|
908
|
+
|
|
909
|
+
all_products_data[model] = current_product_data
|
|
910
|
+
|
|
911
|
+
return all_products_data
|
|
912
|
+
|
|
913
|
+
|
|
837
914
|
@click.command()
|
|
915
|
+
@click.option(
|
|
916
|
+
"--record",
|
|
917
|
+
is_flag=True,
|
|
918
|
+
default=False,
|
|
919
|
+
help="Save new device info entries to the YAML file.",
|
|
920
|
+
)
|
|
921
|
+
@click.option(
|
|
922
|
+
"--device-info-file",
|
|
923
|
+
default="device_info.yaml",
|
|
924
|
+
help="Path to the YAML file with device and product data.",
|
|
925
|
+
)
|
|
926
|
+
@click.option(
|
|
927
|
+
"--diagnostic-file",
|
|
928
|
+
default=None,
|
|
929
|
+
help="Path to a Home Assistant diagnostic JSON file to parse instead of connecting to devices.",
|
|
930
|
+
)
|
|
838
931
|
@click.pass_context
|
|
839
932
|
@async_command
|
|
840
|
-
async def get_device_info(ctx: click.Context):
|
|
933
|
+
async def get_device_info(ctx: click.Context, record: bool, device_info_file: str, diagnostic_file: str | None):
|
|
841
934
|
"""
|
|
842
935
|
Connects to devices and prints their feature information in YAML format.
|
|
936
|
+
|
|
937
|
+
Can also parse device info from a Home Assistant diagnostic file using --diagnostic-file.
|
|
843
938
|
"""
|
|
844
|
-
click.echo("Discovering devices...")
|
|
845
939
|
context: RoborockContext = ctx.obj
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
if
|
|
850
|
-
|
|
851
|
-
|
|
940
|
+
device_info_path = Path(device_info_file)
|
|
941
|
+
existing_device_info: dict[str, Any] = {}
|
|
942
|
+
|
|
943
|
+
# Load existing device info if recording
|
|
944
|
+
if record:
|
|
945
|
+
click.echo(f"Using device info file: {device_info_path.resolve()}")
|
|
946
|
+
if device_info_path.exists():
|
|
947
|
+
with open(device_info_path, encoding="utf-8") as f:
|
|
948
|
+
data = yaml.safe_load(f)
|
|
949
|
+
if isinstance(data, dict):
|
|
950
|
+
existing_device_info = data
|
|
951
|
+
|
|
952
|
+
# Parse from diagnostic file if provided
|
|
953
|
+
if diagnostic_file:
|
|
954
|
+
diagnostic_path = Path(diagnostic_file)
|
|
955
|
+
if not diagnostic_path.exists():
|
|
956
|
+
click.echo(f"Diagnostic file not found: {diagnostic_path}", err=True)
|
|
957
|
+
return
|
|
958
|
+
|
|
959
|
+
click.echo(f"Parsing diagnostic file: {diagnostic_path.resolve()}")
|
|
960
|
+
all_products_data = _parse_diagnostic_file(diagnostic_path)
|
|
961
|
+
|
|
962
|
+
if not all_products_data:
|
|
963
|
+
click.echo("No device data found in diagnostic file.")
|
|
964
|
+
return
|
|
965
|
+
|
|
966
|
+
click.echo(f"Found {len(all_products_data)} device(s) in diagnostic file.")
|
|
852
967
|
|
|
853
|
-
|
|
968
|
+
else:
|
|
969
|
+
click.echo("Discovering devices...")
|
|
854
970
|
|
|
855
|
-
|
|
971
|
+
if record:
|
|
972
|
+
connection_cache = await context.get_devices()
|
|
973
|
+
home_data = connection_cache.cache_data.home_data if connection_cache.cache_data else None
|
|
974
|
+
if home_data is None:
|
|
975
|
+
click.echo("Home data not available.", err=True)
|
|
976
|
+
return
|
|
856
977
|
|
|
857
|
-
|
|
858
|
-
|
|
978
|
+
device_connection_manager = await context.get_device_manager()
|
|
979
|
+
device_manager = await device_connection_manager.ensure_device_manager()
|
|
980
|
+
devices = await device_manager.get_devices()
|
|
981
|
+
if not devices:
|
|
982
|
+
click.echo("No devices found.")
|
|
983
|
+
return
|
|
859
984
|
|
|
860
|
-
|
|
861
|
-
click.echo(f" - Skipping duplicate model {device.product.model}")
|
|
862
|
-
continue
|
|
985
|
+
click.echo(f"Found {len(devices)} devices. Fetching data...")
|
|
863
986
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
)
|
|
873
|
-
except Exception as e:
|
|
874
|
-
click.echo(f" - Error processing device {device.name}: {e}", err=True)
|
|
987
|
+
all_products_data = {}
|
|
988
|
+
|
|
989
|
+
for device in devices:
|
|
990
|
+
click.echo(f" - Processing {device.name} ({device.duid})")
|
|
991
|
+
|
|
992
|
+
model = device.product.model
|
|
993
|
+
if model in all_products_data:
|
|
994
|
+
click.echo(f" - Skipping duplicate model {model}")
|
|
875
995
|
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
|
-
)
|
|
884
996
|
|
|
885
|
-
|
|
997
|
+
current_product_data = {
|
|
998
|
+
"protocol_version": device.device_info.pv,
|
|
999
|
+
"product_nickname": device.product.product_nickname.name
|
|
1000
|
+
if device.product.product_nickname
|
|
1001
|
+
else "Unknown",
|
|
1002
|
+
}
|
|
1003
|
+
if device.v1_properties is not None:
|
|
1004
|
+
try:
|
|
1005
|
+
result: list[dict[str, Any]] = await device.v1_properties.command.send(
|
|
1006
|
+
RoborockCommand.APP_GET_INIT_STATUS
|
|
1007
|
+
)
|
|
1008
|
+
except Exception as e:
|
|
1009
|
+
click.echo(f" - Error processing device {device.name}: {e}", err=True)
|
|
1010
|
+
continue
|
|
1011
|
+
init_status_result = result[0] if result else {}
|
|
1012
|
+
current_product_data.update(
|
|
1013
|
+
{
|
|
1014
|
+
"new_feature_info": init_status_result.get("new_feature_info"),
|
|
1015
|
+
"new_feature_info_str": init_status_result.get("new_feature_info_str"),
|
|
1016
|
+
"feature_info": init_status_result.get("feature_info"),
|
|
1017
|
+
}
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
product_data = device.product.as_dict()
|
|
1021
|
+
if product_data:
|
|
1022
|
+
current_product_data["product"] = product_data
|
|
1023
|
+
|
|
1024
|
+
all_products_data[model] = current_product_data
|
|
1025
|
+
|
|
1026
|
+
if record:
|
|
1027
|
+
if not all_products_data:
|
|
1028
|
+
click.echo("No device info updates needed.")
|
|
1029
|
+
return
|
|
1030
|
+
updated_device_info = {**existing_device_info, **all_products_data}
|
|
1031
|
+
device_info_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1032
|
+
ordered_data = dict(sorted(updated_device_info.items(), key=lambda item: item[0]))
|
|
1033
|
+
with open(device_info_path, "w", encoding="utf-8") as f:
|
|
1034
|
+
yaml.safe_dump(ordered_data, f, sort_keys=False)
|
|
1035
|
+
click.echo(f"Updated {device_info_path}.")
|
|
1036
|
+
click.echo("\n--- Device Info Updates ---\n")
|
|
1037
|
+
click.echo(yaml.safe_dump(all_products_data, sort_keys=False))
|
|
1038
|
+
return
|
|
886
1039
|
|
|
887
1040
|
if all_products_data:
|
|
888
1041
|
click.echo("\n--- Device Information (copy to your YAML file) ---\n")
|
|
889
|
-
# Use yaml.dump to print in a clean, copy-paste friendly format
|
|
890
1042
|
click.echo(yaml.dump(all_products_data, sort_keys=False))
|
|
891
1043
|
|
|
892
1044
|
|
|
@@ -919,19 +1071,19 @@ def update_docs(data_file: str, output_file: str):
|
|
|
919
1071
|
for model, data in product_data_from_yaml.items():
|
|
920
1072
|
# Reconstruct the DeviceFeatures object from the raw data in the YAML file
|
|
921
1073
|
device_features = DeviceFeatures.from_feature_flags(
|
|
922
|
-
new_feature_info=data.get("
|
|
923
|
-
new_feature_info_str=data.get("
|
|
924
|
-
feature_info=data.get("
|
|
925
|
-
product_nickname=data.get("
|
|
1074
|
+
new_feature_info=data.get("new_feature_info"),
|
|
1075
|
+
new_feature_info_str=data.get("new_feature_info_str"),
|
|
1076
|
+
feature_info=data.get("feature_info"),
|
|
1077
|
+
product_nickname=data.get("product_nickname"),
|
|
926
1078
|
)
|
|
927
1079
|
features_dict = asdict(device_features)
|
|
928
1080
|
|
|
929
1081
|
# This dictionary will hold the final data for the markdown table row
|
|
930
1082
|
current_product_data = {
|
|
931
|
-
"
|
|
932
|
-
"
|
|
933
|
-
"
|
|
934
|
-
"
|
|
1083
|
+
"product_nickname": data.get("product_nickname", ""),
|
|
1084
|
+
"protocol_version": data.get("protocol_version", ""),
|
|
1085
|
+
"new_feature_info": data.get("new_feature_info", ""),
|
|
1086
|
+
"new_feature_info_str": data.get("new_feature_info_str", ""),
|
|
935
1087
|
}
|
|
936
1088
|
|
|
937
1089
|
# Populate features from the calculated DeviceFeatures object
|
|
@@ -940,7 +1092,7 @@ def update_docs(data_file: str, output_file: str):
|
|
|
940
1092
|
if is_supported:
|
|
941
1093
|
current_product_data[feature] = "X"
|
|
942
1094
|
|
|
943
|
-
supported_codes = data.get("
|
|
1095
|
+
supported_codes = data.get("feature_info", [])
|
|
944
1096
|
if isinstance(supported_codes, list):
|
|
945
1097
|
for code in supported_codes:
|
|
946
1098
|
feature_name = str(code)
|
|
@@ -954,10 +1106,10 @@ def update_docs(data_file: str, output_file: str):
|
|
|
954
1106
|
"""Writes the data into a markdown table (products as columns)."""
|
|
955
1107
|
sorted_products = sorted(product_features.keys())
|
|
956
1108
|
special_rows = [
|
|
957
|
-
"
|
|
958
|
-
"
|
|
959
|
-
"
|
|
960
|
-
"
|
|
1109
|
+
"product_nickname",
|
|
1110
|
+
"protocol_version",
|
|
1111
|
+
"new_feature_info",
|
|
1112
|
+
"new_feature_info_str",
|
|
961
1113
|
]
|
|
962
1114
|
# Regular features are the remaining keys, sorted alphabetically
|
|
963
1115
|
# We filter out the special rows to avoid duplicating them.
|
|
@@ -554,6 +554,11 @@ class DeviceFeatures(RoborockBase):
|
|
|
554
554
|
metadata={"product_features": [ProductFeatures.MOP_SHAKE_MODULE, ProductFeatures.MOP_SPIN_MODULE]}
|
|
555
555
|
)
|
|
556
556
|
|
|
557
|
+
# Raw feature info values from get_init_status for diagnostics
|
|
558
|
+
new_feature_info: int = field(default=0, repr=False)
|
|
559
|
+
new_feature_info_str: str = field(default="", repr=False)
|
|
560
|
+
feature_info: list[int] = field(default_factory=list, repr=False)
|
|
561
|
+
|
|
557
562
|
@classmethod
|
|
558
563
|
def from_feature_flags(
|
|
559
564
|
cls,
|
|
@@ -571,9 +576,17 @@ class DeviceFeatures(RoborockBase):
|
|
|
571
576
|
# RobotNewFeatures = new_feature_info
|
|
572
577
|
# newFeatureInfoStr = new_feature_info_str
|
|
573
578
|
# feature_info =robotFeatures
|
|
574
|
-
kwargs: dict[str, Any] = {
|
|
579
|
+
kwargs: dict[str, Any] = {
|
|
580
|
+
# Store raw feature info for diagnostics
|
|
581
|
+
"new_feature_info": new_feature_info,
|
|
582
|
+
"new_feature_info_str": new_feature_info_str,
|
|
583
|
+
"feature_info": feature_info,
|
|
584
|
+
}
|
|
575
585
|
|
|
576
586
|
for f in fields(cls):
|
|
587
|
+
# Skip raw feature info fields (already set above)
|
|
588
|
+
if f.name in ("new_feature_info", "new_feature_info_str", "feature_info"):
|
|
589
|
+
continue
|
|
577
590
|
# Default all features to False.
|
|
578
591
|
kwargs[f.name] = False
|
|
579
592
|
if not f.metadata:
|
|
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-4.5.0 → python_roborock-4.7.0}/roborock/data/b01_q10/b01_q10_code_mappings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/data/b01_q7/b01_q7_code_mappings.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
|
|
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-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/device_features.py
RENAMED
|
File without changes
|
{python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/do_not_disturb.py
RENAMED
|
File without changes
|
{python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/dust_collection_mode.py
RENAMED
|
File without changes
|
{python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/flow_led_status.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
|
{python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/smart_wash_params.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-4.5.0 → python_roborock-4.7.0}/roborock/devices/traits/v1/wash_towel_mode.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|