Skip to content

Commit e3a0eab

Browse files
authored
Add additional test data devices types for Saros 10, PearlS Lite, and Q5 Max+. (#807)
* feat: add support for Saros 10 (A147) device and update test snapshots * test: add property-based testing for device features using paired mock data * refactor: ensure deterministic test data loading by sorting file globs and remove redundant snapshot comment * refactor: optimize device lookup in mock data by caching parsed device objects * feat: add support for Roborock Q5 Max+ and update test snapshots * chore: update supported features documentation, device definitions, and CLI model mapping
1 parent 11eacfa commit e3a0eab

13 files changed

Lines changed: 3120 additions & 238 deletions

SUPPORTED_FEATURES.md

Lines changed: 194 additions & 193 deletions
Large diffs are not rendered by default.

device_info.yaml

Lines changed: 660 additions & 22 deletions
Large diffs are not rendered by default.

roborock/cli.py

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -878,12 +878,12 @@ def _parse_diagnostic_file(diagnostic_path: Path) -> dict[str, dict[str, Any]]:
878878
device_features = traits_data.get("device_features", {})
879879

880880
# newFeatureInfo is the integer
881-
new_feature_info = device_features.get("newFeatureInfo")
881+
new_feature_info = device_features.get("newFeatureInfo", device_data.get("featureSet"))
882882
if new_feature_info is not None:
883-
current_product_data["new_feature_info"] = new_feature_info
883+
current_product_data["new_feature_info"] = int(new_feature_info)
884884

885885
# newFeatureInfoStr is the hex string
886-
new_feature_info_str = device_features.get("newFeatureInfoStr")
886+
new_feature_info_str = device_data.get("newFeatureSet")
887887
if new_feature_info_str:
888888
current_product_data["new_feature_info_str"] = new_feature_info_str
889889

@@ -1048,29 +1048,67 @@ def update_docs(data_file: str, output_file: str):
10481048
data_path = Path(data_file)
10491049
output_path = Path(output_file)
10501050

1051-
if not data_path.exists():
1052-
click.echo(f"Error: Data file not found at '{data_path}'", err=True)
1053-
return
1054-
1055-
click.echo(f"Loading data from {data_path}...")
1056-
with open(data_path, encoding="utf-8") as f:
1057-
product_data_from_yaml = yaml.safe_load(f)
1058-
1059-
if not product_data_from_yaml:
1060-
click.echo("No data found in YAML file. Exiting.", err=True)
1051+
product_data_from_yaml = {}
1052+
if data_path.exists():
1053+
click.echo(f"Loading data from {data_path}...")
1054+
with open(data_path, encoding="utf-8") as f:
1055+
product_data_from_yaml = yaml.safe_load(f) or {}
1056+
else:
1057+
click.echo(f"Data file not found at '{data_path}', will try testdata.")
1058+
1059+
combined_product_data = {}
1060+
1061+
testdata_path = Path("tests/testdata")
1062+
if testdata_path.exists():
1063+
products_by_id = {}
1064+
for f in testdata_path.glob("home_data_product_*.json"):
1065+
with open(f, encoding="utf-8") as file:
1066+
data = json.load(file)
1067+
products_by_id[data.get("id")] = data
1068+
1069+
loaded_testdata = False
1070+
for f in testdata_path.glob("home_data_device_*.json"):
1071+
with open(f, encoding="utf-8") as file:
1072+
device_data = json.load(file)
1073+
product_id = device_data["productId"]
1074+
if product_id in products_by_id:
1075+
product_data = products_by_id[product_id]
1076+
model = product_data["model"]
1077+
short_model = model.split(".")[-1]
1078+
product_nickname = SHORT_MODEL_TO_ENUM.get(short_model)
1079+
feature_set = device_data.get("featureSet", "0")
1080+
new_feature_set = device_data.get("newFeatureSet", "0")
1081+
combined_product_data[model] = {
1082+
"product_nickname": product_nickname.name if product_nickname else "Unknown",
1083+
"protocol_version": device_data["pv"],
1084+
"new_feature_info": int(feature_set),
1085+
"new_feature_info_str": new_feature_set,
1086+
"feature_info": [],
1087+
}
1088+
print("Loaded %s", product_id)
1089+
loaded_testdata = True
1090+
if loaded_testdata:
1091+
click.echo("Loaded mock testdata from local repository.")
1092+
1093+
if product_data_from_yaml:
1094+
for model, data in product_data_from_yaml.items():
1095+
combined_product_data[model] = data
1096+
1097+
if not combined_product_data:
1098+
click.echo("No data found from YAML or testdata. Exiting.", err=True)
10611099
return
10621100

10631101
product_features_map = {}
10641102
all_feature_names = set()
10651103

1066-
# Process the raw data from YAML to build the feature map
1067-
for model, data in product_data_from_yaml.items():
1104+
# Process the combined data to build the feature map
1105+
for model, data in combined_product_data.items():
10681106
# Reconstruct the DeviceFeatures object from the raw data in the YAML file
10691107
device_features = DeviceFeatures.from_feature_flags(
1070-
new_feature_info=data.get("new_feature_info"),
1071-
new_feature_info_str=data.get("new_feature_info_str"),
1072-
feature_info=data.get("feature_info"),
1073-
product_nickname=data.get("product_nickname"),
1108+
new_feature_info=data.get("new_feature_info", 0),
1109+
new_feature_info_str=data.get("new_feature_info_str", ""),
1110+
feature_info=data.get("feature_info", []),
1111+
product_nickname=data.get("product_nickname", ""),
10741112
)
10751113
features_dict = asdict(device_features)
10761114

0 commit comments

Comments
 (0)