Skip to content

fix(api): add rriotid, categoryId, category code and agreements to /api/v5/product - #207

Merged
Lash-L merged 2 commits into
Python-roborock:mainfrom
SmarthomeElektroniker:fix/product-response-fields
Oct 30, 2026
Merged

Lash-L merged 2 commits into
Python-roborock:mainfrom
SmarthomeElektroniker:fix/product-response-fields

Conversation

@SmarthomeElektroniker

Copy link
Copy Markdown
Contributor

Problem

In the Android app (4.60.06), opening a device failed with error 21020 "device initialization failed". Once that was fixed, the device plugin never got as far as requesting the map. The app's local Room database (rr-app-database) showed four gaps in what /api/v5/product delivers:

App DB Cause in the server response
rr_product.rriotid = '' field rriotid missing
product ↔ category link empty field categoryId missing
rr_category.code = '' field code missing in the category object
rr_product.agreements = 'null' field agreements missing

Details from the app's serializers and mappers:

  • rriotid: The API model Product reads the IoT product id under the JSON name rriotid. The DB entity ProductEntity calls the same field productId, stored in the column rriotid. The lookup select * from rr_product where uid=? and rriotid=? therefore found nothing. That gave ProductEntity.DEFAULT and error 21020. The real cloud /api/v5/product response carries rriotid. The value is the same id that home data puts in devices[].productId.
  • category.code: The mapper ProductAndCategory → Product sets Product.code from CategoryEntity.code. PluginSDKModule.getConstants() then passes it to the device plugin as deviceCategory. With an empty code, the app did not enter its device layer at all: no get_status or get_room_mapping was sent to the robot until this was set.
  • agreements: PluginSDKModule.getProductAgreements returned null. The plugin's privacy check never continued to fetchDeviceLocation(), so the map was never requested. An empty list means "no product-specific agreement required".

The second commit fixes a field-name typo. Product$$serializer knows picurl, cardpicurl, mediumCardpicurl, resetwifipicurl, pluginPicUrl and pictures. The server set cardPicUrl (capital P), which the app ignores. The cloud also uses cardpicurl.

Fix

routes/api/product_service.py:

  • product entry: "rriotid": <IoT product id>, "categoryId": <category id>, "agreements": []
  • category entry: "code": <category name> (e.g. robot.vacuum.cleaner)
  • cardPicUrl → cardpicurl

The numeric id is unchanged. It has to stay numeric: putting the string id there leaves the product table empty.

Tests

tests/test_product_response.py

  • the product carries rriotid, categoryId and agreements: []
  • the category carries code
  • rriotid matches the home data device's productId
  • the picture fields use the lower-case JSON names

Full suite: passing.

Live check (Oct 2026): verified together with the other fixes of this series (all based on current main) against the Android app 4.60.06 and a real Q7 Max (roborock.vacuum.a38). The device page opened without error 21020, the map and room names loaded. The product entry carried no productId, code or scope, and the app did not need them.

Notes

  • endpoint_rules.py still contains older copies of _normalize_devices, _build_web_state and _build_product_response. Nothing calls them; the live code is in routes/user/devices/service.py and routes/api/product_service.py. I left them alone, but they are easy to patch by mistake. For example, adding device["model"] there has no effect.
  • pluginPicUrl is still set to the plain icon URL. In the cloud it is either null or a JSON string such as {"pluginPic": "...", "pluginPicWithDock": "..."}. I did not change that here.

🤖 Generated with Claude Code

Immanuel and others added 2 commits October 25, 2026 08:00
…pi/v5/product

Opening a device in the Android app failed with error 21020 ("device
initialization failed") and the device plugin never reached the map. The app
database showed why: rr_product.rriotid was empty, rr_category.code was empty
and rr_product.agreements was null.

- rriotid: the app's API model (Product) reads the IoT product id under the
  JSON name "rriotid" (the DB entity calls the same field productId). The
  product lookup "select ... where rriotid=?" found nothing without it. The
  cloud /api/v5/product response carries this field.
- categoryId: links the product to its category entry.
- category.code: CategoryEntity.code is mapped into Product.code, which the
  device plugin receives as deviceCategory via PluginSDKModule.getConstants().
- agreements: PluginSDKModule.getProductAgreements returned null and the
  plugin's privacy check never continued to fetchDeviceLocation(). An empty
  list means "no product specific agreement required".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Android app's Product serializer knows picurl, cardpicurl,
mediumCardpicurl, resetwifipicurl, pluginPicUrl and pictures. "cardPicUrl"
(capital P) is not one of them, so the card picture was never set; the cloud
response uses the lower-case name as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Lash-L

Lash-L commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

Looks good, TY

@Lash-L
Lash-L merged commit 5003f21 into Python-roborock:main Oct 30, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants