python-roborock 7.3.0__py3-none-any.whl → 7.4.1__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-roborock
3
- Version: 7.3.0
3
+ Version: 7.4.1
4
4
  Summary: A package to control Roborock vacuums.
5
5
  Project-URL: Repository, https://github.com/python-roborock/python-roborock
6
6
  Project-URL: Documentation, https://python-roborock.readthedocs.io/
@@ -11,10 +11,10 @@ roborock/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  roborock/roborock_message.py,sha256=VWsytnm7nQqOMBj7oYbiqPPqwE9qClDSI03CFuHXsds,11822
12
12
  roborock/roborock_typing.py,sha256=xppavdf57-Cu6elj3MP403uT75Tjw5Zs6bLGPLQmZf4,15483
13
13
  roborock/util.py,sha256=mjw329ujLjoWPPAr9mG0VZstaVL06dmVvNnYACB-Pxk,1650
14
- roborock/web_api.py,sha256=M_A4PiRK5cwkoT3oS8fvglOU97s1jpWKuuCxeeCiF_E,37507
14
+ roborock/web_api.py,sha256=ypW9e11U0jNXnyIQO9UN0DnIdz4c6AxtJlUJmjuEl-o,40514
15
15
  roborock/data/__init__.py,sha256=jSZHOrd-LfVN-TdBkjNW334ef99rGBz4-hPT_7KvoLs,278
16
16
  roborock/data/code_mappings.py,sha256=SGLJvHSdibUsXqCbQcqCGKm3x09JqhRKLoCgMCN1_s0,9769
17
- roborock/data/containers.py,sha256=r46o2Xf6gfpw2SHBZnkVbfJIYYbpgTPYWDmAHWdgL8E,17228
17
+ roborock/data/containers.py,sha256=ISfVcuDYFqMUiYa0V7NaMDH6snjYjJqOg6oKp-URghg,17900
18
18
  roborock/data/b01_q10/__init__.py,sha256=E4lwJcz4AI1z92FcYOJ-cY3fcfHSoXqvYVgfA-TgaRk,71
19
19
  roborock/data/b01_q10/b01_q10_code_mappings.py,sha256=UBFkxCWLYCGQatENmNwR0owkXrcB8AosnD-hICfS2AY,15203
20
20
  roborock/data/b01_q10/b01_q10_containers.py,sha256=HE1qAhYKDTz35oRgekYItNfD1iWTi7605-wpETcDzxU,11823
@@ -49,7 +49,7 @@ roborock/devices/traits/__init__.py,sha256=YaL9qoxG6IBG0XXDP0HMTvoc0Jkmq7AF6iH2o
49
49
  roborock/devices/traits/common.py,sha256=n3fHK4i4bkTU1QL7skMStJiUPIDlpN37z0T9mPQYoOY,4588
50
50
  roborock/devices/traits/traits_mixin.py,sha256=7t15l_Ty1cN-3-fKNtRH1XR6-mfadPnTBCQRZP4rAtc,2092
51
51
  roborock/devices/traits/a01/__init__.py,sha256=rRrdXgSsolyNC6MOCyYIfW8oaUmzt-p_uppuhzfYYIU,18375
52
- roborock/devices/traits/a01/device_feature.py,sha256=6--fV8kRv4nxWIaQlxHt0XFyovhqk6dGeVm-E1n5y_U,13514
52
+ roborock/devices/traits/a01/device_feature.py,sha256=tO7u1dvnwerj9IVq9je-p7tzyEqTQR7i59DEj57eHZk,14117
53
53
  roborock/devices/traits/b01/__init__.py,sha256=pGN6wEocE6Fr2BTasG8Z_75crD6OdTwpju59Vw7eTDk,202
54
54
  roborock/devices/traits/b01/q10/__init__.py,sha256=BpeCO7vrM11ISy8JZwBiSkHsTs5-vp68c9YwnWKEIDk,6747
55
55
  roborock/devices/traits/b01/q10/button_light.py,sha256=_CCWSjFe_iZnnSXgBHOju31VUmwG1KKd_jGkmSpLew4,994
@@ -125,8 +125,8 @@ roborock/testing/channel.py,sha256=vjulT5MHBe8lAq6XITUqcxJVcCILWx7ZEcw5imTx0Tk,7
125
125
  roborock/testing/cloud.py,sha256=L2L0KlZDYaMuoV19KUQUoq5EAUDeNQ1lJ8Ew5YaOgjo,11946
126
126
  roborock/testing/simulator.py,sha256=g8ZUWVSjgAzuRRxWOCeOq-7deodyEItwUpqEw9RzCmQ,4347
127
127
  roborock/testing/v1_simulator.py,sha256=TQ2PsRoP_4kJvVn-Tm532ByHuaCKltsVjvTa2Ju4PAo,14407
128
- python_roborock-7.3.0.dist-info/METADATA,sha256=-UHI17_l7Ty6Txpqlc7NcpxYnkhKNsFsb3u4L3qxn7k,5278
129
- python_roborock-7.3.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
130
- python_roborock-7.3.0.dist-info/entry_points.txt,sha256=EvC1nMqi9ZXKgZnqlNXA33v_3nzgPNjnM0mzWlrehnY,47
131
- python_roborock-7.3.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
132
- python_roborock-7.3.0.dist-info/RECORD,,
128
+ python_roborock-7.4.1.dist-info/METADATA,sha256=rB0hTcmV3i3rR3cSxnW-Jy1Pzc9QsV9pFBsM2FZdaS4,5278
129
+ python_roborock-7.4.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
130
+ python_roborock-7.4.1.dist-info/entry_points.txt,sha256=EvC1nMqi9ZXKgZnqlNXA33v_3nzgPNjnM0mzWlrehnY,47
131
+ python_roborock-7.4.1.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
132
+ python_roborock-7.4.1.dist-info/RECORD,,
@@ -348,6 +348,24 @@ class HomeDataScene(RoborockBase):
348
348
  name: str
349
349
 
350
350
 
351
+ @dataclass
352
+ class FirmwareInfo(RoborockBase):
353
+ """Firmware/OTA info from the cloud (`ota/firmware/{duid}/updatev2`)."""
354
+
355
+ version: str | None = None
356
+ """Latest available firmware version."""
357
+ current_version: str | None = None
358
+ """Currently installed firmware version."""
359
+ updatable: bool | None = None
360
+ """Whether a newer firmware is available to install."""
361
+ desc: str | None = None
362
+ """Release notes / description."""
363
+ release_time: str | None = None
364
+ """Release date of the available firmware as an ISO-8601 date (``YYYY-MM-DD``)."""
365
+ force_update: bool | None = None
366
+ """Whether the update is mandatory (cannot be skipped)."""
367
+
368
+
351
369
  @dataclass
352
370
  class HomeDataSchedule(RoborockBase):
353
371
  id: int
@@ -143,6 +143,13 @@ _UNSUPPORTED_FEATURE_BITS: frozenset[str] = frozenset(
143
143
  }
144
144
  )
145
145
 
146
+ # Devices known to lack DEFAULT_SETTING (DP 225).
147
+ _UNSUPPORTED_DEFAULT_SETTING: frozenset[str] = frozenset(
148
+ {
149
+ "roborock.wm.a63", # H1
150
+ }
151
+ )
152
+
146
153
  # Series that support UV light (DP 228).
147
154
  _UV_LIGHT_SERIES: frozenset[str] = (
148
155
  _H1_LITE_SERIES # a90, a91, a237
@@ -242,6 +249,13 @@ def supports_feature_bits(model: str | None) -> bool:
242
249
  return model not in _UNSUPPORTED_FEATURE_BITS
243
250
 
244
251
 
252
+ def supports_default_setting(model: str | None) -> bool:
253
+ """H1 (a63) does not support DP 225 even though it has a softener compartment."""
254
+ if model is None:
255
+ return True # conservative: assume yes
256
+ return model not in _UNSUPPORTED_DEFAULT_SETTING
257
+
258
+
245
259
  def supports_remote_control(model: str | None) -> bool:
246
260
  """Remote control (DP 232) is supported on all overseas models."""
247
261
  if model is None:
@@ -309,6 +323,10 @@ def build_force_load_dp_list(model: str | None) -> list[RoborockZeoProtocol]:
309
323
  if not supports_feature_bits(model):
310
324
  base = [dp for dp in base if dp != RoborockZeoProtocol.FEATURE_BITS]
311
325
 
326
+ # ── Strip unsupported DEFAULT_SETTING ──
327
+ if not supports_default_setting(model):
328
+ base = [dp for dp in base if dp != RoborockZeoProtocol.DEFAULT_SETTING]
329
+
312
330
  return base
313
331
 
314
332
 
roborock/web_api.py CHANGED
@@ -15,7 +15,7 @@ from aiohttp import ContentTypeError, FormData
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,73 @@ class RoborockApiClient:
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
+ rriot = user_data.rriot
614
+ if rriot is None:
615
+ raise RoborockException("rriot is none")
616
+ if rriot.r.a is None:
617
+ raise RoborockException("Missing field 'a' in rriot reference")
618
+ path = f"/ota/firmware/{device_id}/updatev2"
619
+ params = {"lang": "en"}
620
+ firmware_request = PreparedRequest(
621
+ rriot.r.a,
622
+ self.session,
623
+ {
624
+ "Authorization": _get_hawk_authentication(rriot, path, params=params),
625
+ },
626
+ )
627
+ firmware_response = await firmware_request.request("get", path, params=params)
628
+ if not firmware_response or not firmware_response.get("success"):
629
+ raise RoborockException(firmware_response)
630
+ return FirmwareInfo.from_dict(firmware_response.get("result") or {})
631
+
632
+ async def start_firmware_update(self, user_data: UserData, device_id: str) -> None:
633
+ """Trigger the (irreversible) firmware update for a device.
634
+
635
+ The device downloads and flashes the latest firmware reported by
636
+ :meth:`get_firmware_info`.
637
+ """
638
+ rriot = user_data.rriot
639
+ if rriot is None:
640
+ raise RoborockException("rriot is none")
641
+ if rriot.r.a is None:
642
+ raise RoborockException("Missing field 'a' in rriot reference")
643
+ path = f"/ota/device/{device_id}/upgrade"
644
+ upgrade_request = PreparedRequest(
645
+ rriot.r.a,
646
+ self.session,
647
+ {
648
+ "Authorization": _get_hawk_authentication(rriot, path),
649
+ },
650
+ )
651
+ upgrade_response = await upgrade_request.request("post", path)
652
+ if not upgrade_response or not upgrade_response.get("success"):
653
+ raise RoborockException(upgrade_response)
654
+
655
+ async def set_silent_ota(self, user_data: UserData, device_id: str, enabled: bool) -> None:
656
+ """Enable/disable automatic (silent) firmware updates for a device.
657
+
658
+ The current value is reported as ``silent_ota_switch`` on the home-data device.
659
+ """
660
+ rriot = user_data.rriot
661
+ if rriot is None:
662
+ raise RoborockException("rriot is none")
663
+ if rriot.r.a is None:
664
+ raise RoborockException("Missing field 'a' in rriot reference")
665
+ path = f"/user/devices/{device_id}"
666
+ formdata = {"silentOtaSwitch": "true" if enabled else "false"}
667
+ silent_ota_request = PreparedRequest(
668
+ rriot.r.a,
669
+ self.session,
670
+ {
671
+ "Authorization": _get_hawk_authentication(rriot, path, formdata=formdata),
672
+ },
673
+ )
674
+ silent_ota_response = await silent_ota_request.request("put", path, data=formdata)
675
+ if not silent_ota_response or not silent_ota_response.get("success"):
676
+ raise RoborockException(silent_ota_response)
677
+
611
678
  async def execute_scene(self, user_data: UserData, scene_id: int) -> None:
612
679
  rriot = user_data.rriot
613
680
  if rriot is None: