|
15 | 15 | from pyrate_limiter import Duration, Limiter, Rate |
16 | 16 |
|
17 | 17 | from roborock import HomeDataSchedule |
18 | | -from roborock.data import HomeData, HomeDataRoom, HomeDataScene, ProductResponse, RRiot, UserData |
| 18 | +from roborock.data import FirmwareInfo, HomeData, HomeDataRoom, HomeDataScene, ProductResponse, RRiot, UserData |
19 | 19 | from roborock.exceptions import ( |
20 | 20 | RoborockAccountDoesNotExist, |
21 | 21 | RoborockException, |
@@ -608,6 +608,81 @@ async def get_scenes(self, user_data: UserData, device_id: str) -> list[HomeData |
608 | 608 | else: |
609 | 609 | raise RoborockException("scene_response result was an unexpected type") |
610 | 610 |
|
| 611 | + async def get_firmware_info(self, user_data: UserData, device_id: str) -> FirmwareInfo: |
| 612 | + """Get firmware/OTA info for a device (latest version + updatable flag). |
| 613 | +
|
| 614 | + Uses the rriot API host (``user_data.rriot.r.a``, e.g. ``api-eu.roborock.com``) |
| 615 | + with Hawk-signed authentication, identical to the home-data endpoints. The |
| 616 | + IoT host (``usiot.roborock.com``) returns an empty body for this path. |
| 617 | + """ |
| 618 | + rriot = user_data.rriot |
| 619 | + if rriot is None: |
| 620 | + raise RoborockException("rriot is none") |
| 621 | + if rriot.r.a is None: |
| 622 | + raise RoborockException("Missing field 'a' in rriot reference") |
| 623 | + path = f"/ota/firmware/{device_id}/updatev2" |
| 624 | + params = {"lang": "en"} |
| 625 | + firmware_request = PreparedRequest( |
| 626 | + rriot.r.a, |
| 627 | + self.session, |
| 628 | + { |
| 629 | + "Authorization": _get_hawk_authentication(rriot, path, params=params), |
| 630 | + }, |
| 631 | + ) |
| 632 | + firmware_response = await firmware_request.request("get", path, params=params) |
| 633 | + if not firmware_response or not firmware_response.get("success"): |
| 634 | + raise RoborockException(firmware_response) |
| 635 | + return FirmwareInfo.from_dict(firmware_response.get("result") or {}) |
| 636 | + |
| 637 | + async def start_firmware_update(self, user_data: UserData, device_id: str) -> None: |
| 638 | + """Trigger the (irreversible) firmware update for a device. |
| 639 | +
|
| 640 | + Empty-body POST to the rriot API host with Hawk auth. The device then |
| 641 | + downloads and flashes the latest firmware reported by |
| 642 | + :meth:`get_firmware_info`. |
| 643 | + """ |
| 644 | + rriot = user_data.rriot |
| 645 | + if rriot is None: |
| 646 | + raise RoborockException("rriot is none") |
| 647 | + if rriot.r.a is None: |
| 648 | + raise RoborockException("Missing field 'a' in rriot reference") |
| 649 | + path = f"/ota/device/{device_id}/upgrade" |
| 650 | + upgrade_request = PreparedRequest( |
| 651 | + rriot.r.a, |
| 652 | + self.session, |
| 653 | + { |
| 654 | + "Authorization": _get_hawk_authentication(rriot, path), |
| 655 | + }, |
| 656 | + ) |
| 657 | + upgrade_response = await upgrade_request.request("post", path) |
| 658 | + if not upgrade_response or not upgrade_response.get("success"): |
| 659 | + raise RoborockException(upgrade_response) |
| 660 | + |
| 661 | + async def set_silent_ota(self, user_data: UserData, device_id: str, enabled: bool) -> None: |
| 662 | + """Enable/disable automatic (silent) firmware updates for a device. |
| 663 | +
|
| 664 | + Form-encoded PUT to the rriot API host with Hawk auth (the form field is |
| 665 | + part of the signed MAC). The current value is reported as |
| 666 | + ``silent_ota_switch`` on the home-data device. |
| 667 | + """ |
| 668 | + rriot = user_data.rriot |
| 669 | + if rriot is None: |
| 670 | + raise RoborockException("rriot is none") |
| 671 | + if rriot.r.a is None: |
| 672 | + raise RoborockException("Missing field 'a' in rriot reference") |
| 673 | + path = f"/user/devices/{device_id}" |
| 674 | + formdata = {"silentOtaSwitch": "true" if enabled else "false"} |
| 675 | + silent_ota_request = PreparedRequest( |
| 676 | + rriot.r.a, |
| 677 | + self.session, |
| 678 | + { |
| 679 | + "Authorization": _get_hawk_authentication(rriot, path, formdata=formdata), |
| 680 | + }, |
| 681 | + ) |
| 682 | + silent_ota_response = await silent_ota_request.request("put", path, data=formdata) |
| 683 | + if not silent_ota_response or not silent_ota_response.get("success"): |
| 684 | + raise RoborockException(silent_ota_response) |
| 685 | + |
611 | 686 | async def execute_scene(self, user_data: UserData, scene_id: int) -> None: |
612 | 687 | rriot = user_data.rriot |
613 | 688 | if rriot is None: |
|
0 commit comments