python-roborock 7.4.2__py3-none-any.whl → 7.5.0__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.4.2
3
+ Version: 7.5.0
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/
@@ -1,26 +1,26 @@
1
1
  roborock/__init__.py,sha256=jpp3MEmevRvoJ5tq2waCDGH5VfJk--Lv1DmrwEdDyEQ,375
2
- roborock/broadcast_protocol.py,sha256=fjNQW7NdBKRzSy6126TfCSoYGpT2Esj9Jd-t7tXFc_E,4015
2
+ roborock/broadcast_protocol.py,sha256=HY3towvw_JyeAQxhMwD7XyG0Gvd3pn-Ik7BYb5iP6M4,4047
3
3
  roborock/callbacks.py,sha256=w2yPXtWFH7zfoFsLPYoCTbfwbE9p1ivA7iDcw20lJQI,4300
4
- roborock/cli.py,sha256=0xTv7rfXSKU19bc2b2SILuFifTVYDcAj-FSQDSOgi1k,63308
4
+ roborock/cli.py,sha256=gbe444a9Hq-AU16a5ov3fmoX7mBcuxa7JN4YW43-P2M,63321
5
5
  roborock/const.py,sha256=fWpgUPrCgLnaXmncgjCLZET-t7wHWQlpyzV8qHtTjb4,2860
6
- roborock/device_features.py,sha256=EbMvw4vz0HvN23x9stj5CfNu45HE1tqg4HyygAzkU5Q,46753
6
+ roborock/device_features.py,sha256=RZTE6WovDYPHD9B6Iv_1pEeKaTmqb993Qfo8lCW-9xA,46737
7
7
  roborock/diagnostics.py,sha256=OVfPCjBVidAlQtFz-WRlgCWOBIaXlO-73xQloXJaLwI,5107
8
8
  roborock/exceptions.py,sha256=t2YvfSRMymZPtdPV238itjxVJTaTq5-uBL_EM5FTlfg,3159
9
- roborock/protocol.py,sha256=eNUL6UGmtSdfsGM1mlYjmorukGGudV45iKXUA7a-Qa8,20193
9
+ roborock/protocol.py,sha256=a0uwfn1uX1nVfRFK2QQTw2tHft9fCHBElLgzaEIhpW4,20179
10
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
- roborock/util.py,sha256=mjw329ujLjoWPPAr9mG0VZstaVL06dmVvNnYACB-Pxk,1650
13
+ roborock/util.py,sha256=4zibjn2MgPrLCiha66LUXZj907LOXUtNzugbbb0NIlo,1634
14
14
  roborock/web_api.py,sha256=ypW9e11U0jNXnyIQO9UN0DnIdz4c6AxtJlUJmjuEl-o,40514
15
15
  roborock/data/__init__.py,sha256=jSZHOrd-LfVN-TdBkjNW334ef99rGBz4-hPT_7KvoLs,278
16
- roborock/data/code_mappings.py,sha256=SGLJvHSdibUsXqCbQcqCGKm3x09JqhRKLoCgMCN1_s0,9769
17
- roborock/data/containers.py,sha256=ISfVcuDYFqMUiYa0V7NaMDH6snjYjJqOg6oKp-URghg,17900
16
+ roborock/data/code_mappings.py,sha256=j-ZZRtQQdIMJQAgEwDAxWKreNhD6U36oIhBKr_nLbis,9783
17
+ roborock/data/containers.py,sha256=ePz1tmhNY1QpgiVlyc8j6PH5kjU6fkZYtBByCFj2wzU,17896
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
21
21
  roborock/data/b01_q7/__init__.py,sha256=D-Cd8eB0njf1PQPM6Exl_9A0AhpzIjrL0hapGOCEkkA,69
22
- roborock/data/b01_q7/b01_q7_code_mappings.py,sha256=aOJqNFrJWqRSvgruVIpuaB6tP7gwvr-sSGHVsCOJAHk,11072
23
- roborock/data/b01_q7/b01_q7_containers.py,sha256=jNEHoNEdq276XJZGGUmtSyJ81cSIzJwem-O_NOgmfdQ,10270
22
+ roborock/data/b01_q7/b01_q7_code_mappings.py,sha256=S2KiBIpkUniEnw0hSSLeLWHZMZvq4Ld6RXzbaXO43Zk,11274
23
+ roborock/data/b01_q7/b01_q7_containers.py,sha256=h01GNZM-M4OAMBO3aqE0LHgvmsrirssMmjR3YWdZMqo,10421
24
24
  roborock/data/dyad/__init__.py,sha256=Ksx7Ri1alsAYAyILOpj9iNbCC_6sF7xvETFRnTO4rdM,65
25
25
  roborock/data/dyad/dyad_code_mappings.py,sha256=PGzEpkdc1jsR5RLRnjVnI2Y87O_HFAFulwOjjjbT4B0,2872
26
26
  roborock/data/dyad/dyad_containers.py,sha256=klJD2wV-2McjAoSN8Bthuyeid59L7M8nzSvc3MtmqOI,434
@@ -29,7 +29,7 @@ roborock/data/mower/mower_code_mappings.py,sha256=T3NJEjgkYv3svIt7-gqFgrnNmVeBOk
29
29
  roborock/data/mower/mower_containers.py,sha256=PYNVhbGCafkchenBlBmG_fUnIC-9WfxF8gNB0C4_9Yo,4106
30
30
  roborock/data/v1/__init__.py,sha256=Lx_D6baZrCMxp57D0NcdiA8rssJ5NnInZwUwrT79XOI,91
31
31
  roborock/data/v1/v1_clean_modes.py,sha256=ZUhMqrM9MwnXFl3btZj9_LEAOmhNIl80krW-AiuWouk,13866
32
- roborock/data/v1/v1_code_mappings.py,sha256=_2ycHU9VH86SayZXPLXY1Ej6bJxRvOT_GDIdHsNUR04,12934
32
+ roborock/data/v1/v1_code_mappings.py,sha256=hToQbd38mM3Dw8ONLyuDa2scqmNs0gNgOyqVgm-Gg7Y,12948
33
33
  roborock/data/v1/v1_containers.py,sha256=UBScIG6VKziEAC0FaQRHnfrGWDY06nITY8NkFJEzhiE,17256
34
34
  roborock/data/zeo/__init__.py,sha256=VxG5qG4M24DGOq2YAaxownWp6bEfj9DEOgOMHmTpcqY,63
35
35
  roborock/data/zeo/zeo_code_mappings.py,sha256=mfm2xoI8RllPZfyZE0HmUhUhLECwRD0llkMNWfaEHvw,8057
@@ -43,9 +43,9 @@ roborock/devices/file_cache.py,sha256=-G2Bm4ra8K_DvSBjntv5hGaRDB-VzSoKWcI0Y_kK6j
43
43
  roborock/devices/rpc/__init__.py,sha256=MDtZovvHi3F4Wbgyzu6gyjAjkZ6xYGU1LRQmtiIapuU,561
44
44
  roborock/devices/rpc/a01_channel.py,sha256=R33NQ3hK6pWjPzF2kaWVv93r9RrTWskg6_YWi3Oiiq4,3886
45
45
  roborock/devices/rpc/b01_q10_channel.py,sha256=nT7fsTtti37alm68CHzId8Wy3GMd6npiSNB8Gjegy3I,3494
46
- roborock/devices/rpc/b01_q7_channel.py,sha256=sE3U5r8RlSqSCXxSdd_a_4nPkOSkIPFvCTwxKMtcOnU,8738
46
+ roborock/devices/rpc/b01_q7_channel.py,sha256=yHpuXr-_QG1NNWPV13fJzZGMsfHWBRHSEHz4I2xn8Xs,8754
47
47
  roborock/devices/rpc/v1_channel.py,sha256=xCQ9YqgTFteU-ptc9-H0w6HtZK3OyH8GBS4QNsxi18w,24365
48
- roborock/devices/traits/__init__.py,sha256=YaL9qoxG6IBG0XXDP0HMTvoc0Jkmq7AF6iH2oYWNtcg,765
48
+ roborock/devices/traits/__init__.py,sha256=wUiMPJXm4oVULQy2zjPkChrb4cWy2L1kUqzU79oZ-vQ,779
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
@@ -70,10 +70,10 @@ roborock/devices/traits/b01/q10/volume.py,sha256=UnT0yYDQntxYwnpv4_NVychm0FnGAYR
70
70
  roborock/devices/traits/b01/q7/__init__.py,sha256=wHe2uncF49MlfPwyIgs0xbwnQo8q0eDEyHTh33iHq80,8549
71
71
  roborock/devices/traits/b01/q7/clean_summary.py,sha256=M43iJVeYjrkBV6ZxXdHtn4Rd5Mx-KAK7WLU5fABiN8I,2742
72
72
  roborock/devices/traits/b01/q7/map.py,sha256=NfPQAEwNCUsb0SYGMxmGeq4q4rEHqDJK0zTD5cE-ESs,1299
73
- roborock/devices/traits/b01/q7/map_content.py,sha256=M0anKPY82kN3wZZAu-vVWqpq_Cw-FA5YJ6QsMef9YPo,4722
73
+ roborock/devices/traits/b01/q7/map_content.py,sha256=O4UMnmX0-K-jxhOaknUZpWJSwxUuaTd2_D9_AP-elGY,4920
74
74
  roborock/devices/traits/v1/__init__.py,sha256=gzp5b2aJCugHF_9jqEZG7ycsv_xCMZa8ymhtm3Nqofg,16986
75
75
  roborock/devices/traits/v1/child_lock.py,sha256=NeaPqcEoBRXvEzKZGonkyINkwCBOK3xa4zSWvJW66kA,1161
76
- roborock/devices/traits/v1/clean_summary.py,sha256=2uDl3Tg6H9INf7HTEPPat_PBgodoWeX_ko5MJgWggn8,4625
76
+ roborock/devices/traits/v1/clean_summary.py,sha256=gwAwJMuJewrdSxmYEaD5mywYrqfz62__NB7sL23u98A,4641
77
77
  roborock/devices/traits/v1/command.py,sha256=2yNyCF_EDPkLE_vK6IN5VB8ZVypwrrwKTByCdQzJs3c,1494
78
78
  roborock/devices/traits/v1/common.py,sha256=fNjoFr5pYXkdhehj61Hyd7ImuF7qdQ-TOlEuwqD1Rnk,6610
79
79
  roborock/devices/traits/v1/consumeable.py,sha256=8r028GfP7LVLSXth42Bbq65MvuuDGjKWruRmFc5yoyU,2536
@@ -87,7 +87,7 @@ roborock/devices/traits/v1/map_content.py,sha256=w_34H6ItiYm98QFOmEDCP0AvTBWAFVu
87
87
  roborock/devices/traits/v1/maps.py,sha256=JdOjJCeJ6iAH85pNHKHbVGlD-zOrha9h6XAh7IDeQr8,3468
88
88
  roborock/devices/traits/v1/network_info.py,sha256=CX9-ye2Zb_8lAWPGvegXWSOguxMY2K0xO-2OmWmE67o,2291
89
89
  roborock/devices/traits/v1/obstacle_photos.py,sha256=Aul1sOxJ6AxLWqI5AbeHHSzxfUPzQfQDCPIvZ3oXb_4,4179
90
- roborock/devices/traits/v1/rooms.py,sha256=PKNEvzL_Pk5pxGpiMC-XgGpgWVRjivMw_yv7vpGlz3s,6221
90
+ roborock/devices/traits/v1/rooms.py,sha256=SEAs3sHZxDmiiSWc9Korm838zu8ub_04CsmJVlT4F48,6237
91
91
  roborock/devices/traits/v1/routines.py,sha256=BfktlpnyDjD2DB3zBDS5BnmjZPE38aHa-wjMontPnik,931
92
92
  roborock/devices/traits/v1/smart_wash_params.py,sha256=6K3XrF5SRS39BWaMjjtGJgKX3lZ24jPsb6Nia4a9GVM,634
93
93
  roborock/devices/traits/v1/status.py,sha256=HPEUnpCbOxoZ6O_Dz3C3mJpYiIiq9vs8zvrzc78oxAw,5078
@@ -96,27 +96,27 @@ roborock/devices/traits/v1/volume.py,sha256=etRIYE0ynZZLEo2ez7Dm7_JbijmElrdjpn59
96
96
  roborock/devices/traits/v1/wash_towel_mode.py,sha256=bSXtaJ0ObDlXsJYuCHWCN3J0GyHEibPsDvOqBCxQW3Q,2039
97
97
  roborock/devices/transport/__init__.py,sha256=6_vMJ7BFAX_I56-Y0MT3TKhOXT4Qv5q_eT5dZLaUNqU,288
98
98
  roborock/devices/transport/channel.py,sha256=W9WR2tZRQSxr6C4Lh0kzPqHpoo4qc6006KtoG6_vdOk,981
99
- roborock/devices/transport/local_channel.py,sha256=kU49v6v3wsT6MQeN4wN-XkTxr-zByAihKZVh6xADjog,11611
99
+ roborock/devices/transport/local_channel.py,sha256=xnCzhIMa6V01Okaoa5KlUFw7k0colFd14itfwlYyxl0,11627
100
100
  roborock/devices/transport/mqtt_channel.py,sha256=dpL1mZIoI4Glk3hfUO9JweWFHG1tZWLrWbt9f4RbvhQ,4789
101
101
  roborock/map/__init__.py,sha256=L3WHujuxUHCTTPaOElkJsTN_FkYhw01drfR-W4blgFc,146
102
- roborock/map/b01_grid_layers.py,sha256=VluAmKEIBTsvzGB5pUrr8iYFiyEx5siyDwBKft1C2vA,12290
103
- roborock/map/b01_map_parser.py,sha256=tTeeWH4fA4Pv4DvqN7PBQs92DuWTaD2rvD44Mo_t3OY,4433
104
- roborock/map/b01_q10_map_parser.py,sha256=EQMx2ZNVjHKsmKr6vR8JS7o86IZu85dNg3bKi5S0ZYw,29985
102
+ roborock/map/b01_grid_layers.py,sha256=b87MsGXslqLR_5H_4z4aj1wQqYgeTuULWqt4_txOF-M,12303
103
+ roborock/map/b01_map_parser.py,sha256=y4Qlgiwre3bn5_UpL6R-ytjg8kQqwkbvhIYFOtpAM3s,14814
104
+ roborock/map/b01_q10_map_parser.py,sha256=Pt-goedXV60hg-mwHOgOF0f4TQBQmQXe6otpWaa99ps,29996
105
105
  roborock/map/b01_q10_overlays.py,sha256=JmriUbT4-oe6NiE94VttMplFc1HHQrAGNQIiZHj5QKk,6640
106
106
  roborock/map/b01_q10_render.py,sha256=b7YUBr53WXxOJhXbfb5IVmgi82iGcC0lJ_j9Ef3kV7M,13502
107
107
  roborock/map/map_parser.py,sha256=EcJcz9yld_wu7d5QrmxR2BTfJ801F6SaEgJkbIIap4w,5111
108
108
  roborock/map/room_colors.py,sha256=5Q85oR_7xsUyzHYGOJPuzsZ6LQSCpJwfxfWql1v6T1E,2152
109
109
  roborock/map/proto/__init__.py,sha256=XcH_s-E67_hcQNwqSkdtY7DgYE0mRBBJwctzsf8zng4,60
110
- roborock/map/proto/b01_scmap.proto,sha256=ux1OnI_-LSDOlTujCCUeOJrTsVCvjLAUKfyddu-rH5U,1929
111
- roborock/map/proto/b01_scmap_pb2.py,sha256=KxHxnxSvEjIAZJY5QO0pH5rY7uYxKlup8hyt30jFAtw,3767
110
+ roborock/map/proto/b01_scmap.proto,sha256=lQtOruroUfrDK9EJ99cnGTi7avEK3CQ1haLIopDrS_A,3725
111
+ roborock/map/proto/b01_scmap_pb2.py,sha256=HI7xxxpvV0cFl80B7T7RN74C3cP8YNqfukL_FW324ss,7493
112
112
  roborock/mqtt/__init__.py,sha256=Lplt5aS3lCJW09KX34tf2r6NHU8penM-R07FI56Z5CE,415
113
113
  roborock/mqtt/health_manager.py,sha256=i_3vbslHMebVd0VNBWalPaEnZi8gcRI3IcKPt2wlQF8,2208
114
- roborock/mqtt/roborock_session.py,sha256=BBNvMAWXDRx5ih3HrwG8H5cOFqaAaoa_zvE2OuSJ65c,21011
114
+ roborock/mqtt/roborock_session.py,sha256=ir7HXs4gtoineiwlc6ixyAueBTCSPtNU8zPGgY9hGOE,21027
115
115
  roborock/mqtt/session.py,sha256=eVW1o6PziXaRyNXviPgC_kIVuqhPSrloAWuP2RYj-uo,4096
116
116
  roborock/protocols/__init__.py,sha256=1ems3I1_KUQoMf2Hf6ZYDKFhu5Gnsc0PbfD-dkQJ7vA,82
117
- roborock/protocols/a01_protocol.py,sha256=LpFsrZ9epwuTNsPprYGL0KgQD37DfvXfFrNuc_x-ieU,2454
117
+ roborock/protocols/a01_protocol.py,sha256=nyUAn39CXH0xLqcRcr7JiORt2zFNgxP4F4ClVc3dvB4,2501
118
118
  roborock/protocols/b01_q10_protocol.py,sha256=Ac3_VHJ2DAwBiXFMPYOHExEV0prxS9Ju0djiC8nKtiI,4935
119
- roborock/protocols/b01_q7_protocol.py,sha256=o8repRrxhxknzspsg1K27ds643rs87ilWXbj8RJtOCs,4619
119
+ roborock/protocols/b01_q7_protocol.py,sha256=2dk6nbDOhb0O-Q_UPKeZHjzdazbS-MA3PIqpXvqRqoo,4657
120
120
  roborock/protocols/v1_protocol.py,sha256=ntFQoGn1IF7v_BwjiGow478SJHBSKr6jq6M2s01OdaI,12918
121
121
  roborock/testing/__init__.py,sha256=rji4a-JBIWC_I6SUTG9j4r2bGxfhYUoopdDAzYQ1tmA,5086
122
122
  roborock/testing/a01_simulator.py,sha256=M4R0ATgebIva6Jl0BPKfwdGqdZjmjGZHlozAVVteJ5I,10477
@@ -124,9 +124,9 @@ roborock/testing/b01_q10_simulator.py,sha256=7k2poUIDI0akTw0rEcCpTUZq0gjr8z3kfTk
124
124
  roborock/testing/channel.py,sha256=vjulT5MHBe8lAq6XITUqcxJVcCILWx7ZEcw5imTx0Tk,7243
125
125
  roborock/testing/cloud.py,sha256=L2L0KlZDYaMuoV19KUQUoq5EAUDeNQ1lJ8Ew5YaOgjo,11946
126
126
  roborock/testing/simulator.py,sha256=g8ZUWVSjgAzuRRxWOCeOq-7deodyEItwUpqEw9RzCmQ,4347
127
- roborock/testing/v1_simulator.py,sha256=TQ2PsRoP_4kJvVn-Tm532ByHuaCKltsVjvTa2Ju4PAo,14407
128
- python_roborock-7.4.2.dist-info/METADATA,sha256=2EEnCpez1BS4kd3YAaXGO1fxAZ4y6tBwQ54lzvnB4UI,5278
129
- python_roborock-7.4.2.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
130
- python_roborock-7.4.2.dist-info/entry_points.txt,sha256=EvC1nMqi9ZXKgZnqlNXA33v_3nzgPNjnM0mzWlrehnY,47
131
- python_roborock-7.4.2.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
132
- python_roborock-7.4.2.dist-info/RECORD,,
127
+ roborock/testing/v1_simulator.py,sha256=_LX_vsBMcB2Uu2QDt8QnQymgtVtbnwD5Xa7tmhiPAnY,14439
128
+ python_roborock-7.5.0.dist-info/METADATA,sha256=UJyDy8lpgf3x32vXVYE46C2rlW0PSY3cTtToPhv_Jyk,5278
129
+ python_roborock-7.5.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
130
+ python_roborock-7.5.0.dist-info/entry_points.txt,sha256=EvC1nMqi9ZXKgZnqlNXA33v_3nzgPNjnM0mzWlrehnY,47
131
+ python_roborock-7.5.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
132
+ python_roborock-7.5.0.dist-info/RECORD,,
@@ -4,7 +4,7 @@ import json
4
4
  import logging
5
5
  from asyncio import BaseTransport, Lock
6
6
 
7
- from construct import ( # type: ignore
7
+ from construct import ( # type: ignore[import-untyped]
8
8
  Bytes,
9
9
  Checksum,
10
10
  GreedyBytes,
@@ -63,7 +63,7 @@ class RoborockProtocol(asyncio.DatagramProtocol):
63
63
  parsed_message = BroadcastMessage(duid=json_payload["duid"], ip=json_payload["ip"], version=version)
64
64
  _LOGGER.debug(f"Received broadcast: {parsed_message}")
65
65
  self.devices_found.append(parsed_message)
66
- except Exception as e:
66
+ except Exception as e: # noqa: BLE001
67
67
  _LOGGER.warning(f"Failed to decode message: {data!r}. Error: {e}")
68
68
 
69
69
  async def discover(self) -> list[BroadcastMessage]:
roborock/cli.py CHANGED
@@ -22,6 +22,8 @@ roborock> status --device_id <device_id>
22
22
  ```
23
23
  """
24
24
 
25
+ # ruff: noqa: BLE001
26
+
25
27
  import asyncio
26
28
  import datetime
27
29
  import functools
@@ -38,9 +40,9 @@ try:
38
40
  import click
39
41
  import click_shell
40
42
  import yaml
41
- from pyshark import FileCapture # type: ignore
42
- from pyshark.capture.live_capture import LiveCapture, UnknownInterfaceException # type: ignore
43
- from pyshark.packet.packet import Packet # type: ignore
43
+ from pyshark import FileCapture # type: ignore[import-untyped]
44
+ from pyshark.capture.live_capture import LiveCapture, UnknownInterfaceException # type: ignore[import-untyped]
45
+ from pyshark.packet.packet import Packet # type: ignore[import-untyped]
44
46
  except ImportError as err:
45
47
  raise SystemExit(
46
48
  f"The 'roborock' command line tool requires extra dependencies that are not installed ({err.name}).\n"
@@ -307,7 +309,7 @@ class RoborockContext(Cache):
307
309
  @click.pass_context
308
310
  def cli(ctx, debug: int):
309
311
  logging_config: dict[str, Any] = {"level": logging.DEBUG if debug > 0 else logging.INFO}
310
- logging.basicConfig(**logging_config) # type: ignore
312
+ logging.basicConfig(**logging_config) # type: ignore[call-overload]
311
313
  ctx.obj = RoborockContext()
312
314
 
313
315
 
@@ -988,9 +990,8 @@ async def parser(_, local_key, device_ip, file):
988
990
  local_key,
989
991
  )
990
992
  print(f"Received request: {f}")
991
- except BaseException as e:
993
+ except Exception as e:
992
994
  print(e)
993
- pass
994
995
  elif packet.ip.src == device_ip:
995
996
  try:
996
997
  f, buffer["data"] = MessageParser.parse(
@@ -998,16 +999,15 @@ async def parser(_, local_key, device_ip, file):
998
999
  local_key,
999
1000
  )
1000
1001
  print(f"Received response: {f}")
1001
- except BaseException as e:
1002
+ except Exception as e:
1002
1003
  print(e)
1003
- pass
1004
1004
 
1005
1005
  try:
1006
1006
  await capture.packets_from_tshark(on_package, close_tshark=not file_provided)
1007
- except UnknownInterfaceException:
1007
+ except UnknownInterfaceException as err:
1008
1008
  raise RoborockException(
1009
1009
  "You need to run 'rvictl -s XXXXXXXX-XXXXXXXXXXXXXXXX' first, with an iPhone connected to usb port"
1010
- )
1010
+ ) from err
1011
1011
 
1012
1012
 
1013
1013
  def _parse_diagnostic_file(diagnostic_path: Path) -> dict[str, dict[str, Any]]:
@@ -1319,7 +1319,7 @@ def update_docs(data_file: str, output_file: str):
1319
1319
  ]
1320
1320
  # Regular features are the remaining keys, sorted alphabetically
1321
1321
  # We filter out the special rows to avoid duplicating them.
1322
- sorted_features = sorted(list(all_features - set(special_rows)))
1322
+ sorted_features = sorted(all_features - set(special_rows))
1323
1323
 
1324
1324
  header = ["Feature"] + sorted_products
1325
1325
 
@@ -302,7 +302,11 @@ class B01Fault(RoborockModeEnum):
302
302
  # charging.
303
303
  F_2101 = ("fault_2101", 2101)
304
304
  F_2102 = ("cleaning_complete", 2102) # Cleaning completed. Returning to the dock.
305
- F_2103 = ("fault_2103", 2103)
305
+ F_2103 = (
306
+ "cleaning_complete_docked",
307
+ 2103,
308
+ ) # Task finished and robot docked. Follows 2102 once docking and any auto-empty complete; persists
309
+ # while docked-idle. Not an error (hw-confirmed on a Q7 M5+).
306
310
  F_2104 = ("fault_2104", 2104)
307
311
  F_2105 = ("fault_2105", 2105)
308
312
  F_2108 = ("fault_2108", 2108)
@@ -137,11 +137,11 @@ class B01Props(RoborockBase):
137
137
  custom_type: int | None = None
138
138
  sound: int | None = None
139
139
  work_mode: WorkModeMapping | None = None
140
- station_act: int | None = None
140
+ station_act: int | None = None # dock activity; 3 while collecting dust, 0 idle. hw-confirmed (Q7 M5+)
141
141
  charge_state: int | None = None
142
142
  current_map_id: int | None = None
143
143
  map_num: int | None = None
144
- dust_action: int | None = None
144
+ dust_action: int | None = None # 1 while the dock is collecting dust; read-only (prop.set rejected, code 1)
145
145
  quiet_is_open: int | None = None
146
146
  quiet_begin_time: int | None = None
147
147
  quiet_end_time: int | None = None
@@ -33,7 +33,7 @@ class RoborockEnum(IntEnum):
33
33
  if warning not in completed_warnings:
34
34
  completed_warnings.add(warning)
35
35
  _LOGGER.warning(warning)
36
- return cls.unknown # type: ignore
36
+ return cls.unknown # type: ignore[attr-defined]
37
37
  default_value = next(item for item in cls)
38
38
  warning = f"Missing {cls.__name__} code: {key} - defaulting to {default_value}"
39
39
  if warning not in completed_warnings:
@@ -52,7 +52,7 @@ def _attr_repr(obj: Any) -> str:
52
52
  continue
53
53
  try:
54
54
  v = getattr(obj, k)
55
- except (RuntimeError, Exception):
55
+ except Exception: # noqa: BLE001
56
56
  continue
57
57
  if callable(v):
58
58
  continue
@@ -210,7 +210,7 @@ class Reference(RoborockBase):
210
210
  r: str | None = None
211
211
  a: str | None = None
212
212
  m: str | None = None
213
- l: str | None = None
213
+ l: str | None = None # noqa: E741
214
214
 
215
215
 
216
216
  @dataclass
@@ -379,9 +379,9 @@ class HomeDataSchedule(RoborockBase):
379
379
  class HomeData(RoborockBase):
380
380
  id: int
381
381
  name: str
382
- products: list[HomeDataProduct] = field(default_factory=lambda: [])
383
- devices: list[HomeDataDevice] = field(default_factory=lambda: [])
384
- received_devices: list[HomeDataDevice] = field(default_factory=lambda: [])
382
+ products: list[HomeDataProduct] = field(default_factory=list)
383
+ devices: list[HomeDataDevice] = field(default_factory=list)
384
+ received_devices: list[HomeDataDevice] = field(default_factory=list)
385
385
  lon: Any | None = None
386
386
  lat: Any | None = None
387
387
  geo_name: Any | None = None
@@ -151,7 +151,7 @@ class RoborockDssCodes(RoborockEnum):
151
151
  def _missing_(cls: type[Self], key) -> Self:
152
152
  # If the calculated value is not provided, then it should be viewed as okay.
153
153
  # As the math will sometimes result in you getting numbers that don't matter.
154
- return cls.okay # type: ignore
154
+ return cls.okay # type: ignore[attr-defined]
155
155
 
156
156
 
157
157
  class ClearWaterBoxStatus(RoborockDssCodes):
@@ -643,7 +643,7 @@ class DeviceFeatures(RoborockBase):
643
643
  elif (product_features := f.metadata.get("product_features")) is not None:
644
644
  if product_nickname is not None:
645
645
  available_features = PRODUCT_FEATURE_MAP.get(product_nickname, [])
646
- if any(feat in available_features for feat in product_features): # type: ignore
646
+ if any(feat in available_features for feat in product_features):
647
647
  kwargs[f.name] = True
648
648
 
649
649
  # The app combines runtime shake-mop, model shake/spin, and roller-mop
@@ -84,7 +84,7 @@ async def _send_command(
84
84
  return
85
85
  try:
86
86
  response = response_matcher(response_message)
87
- except Exception as ex:
87
+ except Exception as ex: # noqa: BLE001
88
88
  future.set_exception(ex)
89
89
  return
90
90
  if response is not None:
@@ -24,5 +24,5 @@ __all__ = [
24
24
  ]
25
25
 
26
26
 
27
- class Trait(ABC):
27
+ class Trait(ABC): # noqa: B024
28
28
  """Base class for all traits."""
@@ -6,6 +6,10 @@ This intentionally mirrors the v1 `MapContentTrait` contract:
6
6
  - fields `image_content`, `map_data`, and `raw_api_response` are then readable
7
7
 
8
8
  For B01/Q7 devices, the underlying raw map payload is retrieved via `MapTrait`.
9
+
10
+ Q7 devices additionally stream map frames unprompted while cleaning; those arrive
11
+ through `update_from_push()`, which keeps the same cached fields current and
12
+ notifies registered update listeners.
9
13
  """
10
14
 
11
15
  import asyncio
@@ -56,7 +56,7 @@ class CleanRecordConverter(common.V1TraitDataConverter):
56
56
  rec.square_meter_area or 0
57
57
  )
58
58
  return final_record
59
- except Exception:
59
+ except Exception: # noqa: BLE001
60
60
  # Return final record when an exception occurred
61
61
  return final_record
62
62
  # There are still a few unknown variables in this.
@@ -144,6 +144,6 @@ class RoomsTrait(Rooms, common.V1TraitMixin):
144
144
  rooms_by_id.update({room.iot_id: room for room in shared_rooms})
145
145
  return list(rooms_by_id.values())
146
146
  return await self._web_api.get_rooms()
147
- except Exception:
147
+ except Exception: # noqa: BLE001
148
148
  _LOGGER.debug("Failed to fetch rooms from web API", exc_info=True)
149
149
  return []
@@ -158,7 +158,7 @@ class LocalChannel(Channel):
158
158
  await self._ping()
159
159
  except asyncio.CancelledError:
160
160
  break
161
- except Exception:
161
+ except Exception: # noqa: BLE001
162
162
  self._logger.debug("Keep-alive ping failed", exc_info=True)
163
163
  # Retry next interval
164
164
 
@@ -198,7 +198,7 @@ def solve_calibration(
198
198
  min_sx, max_sx, min_sy, max_sy = min(sx), max(sx), min(sy), max(sy)
199
199
  if (max_sx - min_sx) >= w or (max_sy - min_sy) >= h:
200
200
  continue # path wider/taller than the map at this resolution
201
- pts = list(zip(sx, sy))
201
+ pts = list(zip(sx, sy, strict=True))
202
202
  # Slide so every point stays in-bounds: px = px_f + ox in [0, w), py = oy - py_f in [0, h).
203
203
  for ox in range(ceil(-min_sx), ceil(w - max_sx)):
204
204
  for oy in range(ceil(max_sy), ceil(h + min_sy)):
@@ -5,20 +5,36 @@ The inner SCMap blob is parsed with protobuf messages generated from
5
5
  """
6
6
 
7
7
  import io
8
+ import math
9
+ from collections import deque
8
10
  from dataclasses import dataclass
9
11
 
10
12
  from google.protobuf.message import DecodeError
11
13
  from PIL import Image
14
+ from vacuum_map_parser_base.config.color import ColorsPalette, SupportedColor
15
+ from vacuum_map_parser_base.config.drawable import Drawable
12
16
  from vacuum_map_parser_base.config.image_config import ImageConfig
13
- from vacuum_map_parser_base.map_data import ImageData, MapData
17
+ from vacuum_map_parser_base.map_data import Area, ImageData, MapData, Path, Point, Room
14
18
 
15
19
  from roborock.exceptions import RoborockException
16
20
  from roborock.map.proto.b01_scmap_pb2 import RobotMap # type: ignore[attr-defined]
17
21
 
18
- from .map_parser import ParsedMapData
22
+ from .map_parser import MapParserConfig, ParsedMapData, _create_image_generator
23
+ from .room_colors import adjacency_aware_room_colors
19
24
 
20
25
  _MAP_FILE_FORMAT = "PNG"
21
26
 
27
+ _FLOOR = 127
28
+ _WALL = 128
29
+
30
+ _B01_DRAWABLES = [
31
+ Drawable.CHARGER,
32
+ Drawable.NO_GO_AREAS,
33
+ Drawable.PATH,
34
+ Drawable.ROOM_NAMES,
35
+ Drawable.VACUUM_POSITION,
36
+ ]
37
+
22
38
 
23
39
  @dataclass
24
40
  class B01MapParserConfig:
@@ -40,7 +56,11 @@ class B01MapParser:
40
56
  size_x, size_y, grid = _extract_grid(parsed)
41
57
  room_names = _extract_room_names(parsed)
42
58
 
43
- image = _render_occupancy_image(grid, size_x=size_x, size_y=size_y, scale=self._config.map_scale)
59
+ room_pixels = _assign_room_pixels(parsed, grid, size_x=size_x, size_y=size_y)
60
+ carpet_pixels = _carpet_pixel_indices(parsed, grid, size_x=size_x, size_y=size_y)
61
+ image = _render_occupancy_image(
62
+ grid, room_pixels, carpet_pixels, size_x=size_x, size_y=size_y, scale=self._config.map_scale
63
+ )
44
64
 
45
65
  map_data = MapData()
46
66
  map_data.image = ImageData(
@@ -51,11 +71,30 @@ class B01MapParser:
51
71
  width=size_x,
52
72
  image_config=ImageConfig(scale=self._config.map_scale),
53
73
  data=image,
54
- img_transformation=lambda p: p,
74
+ # Overlay points are stored in the rendered image's top-down pixel
75
+ # space. ImageDimensions applies V1's bottom-up flip before drawing,
76
+ # so this adapter cancels it (same approach as the Q10 renderer).
77
+ img_transformation=lambda p: Point(p.x, size_y - p.y - 1, p.a),
55
78
  )
56
79
  if room_names:
57
80
  map_data.additional_parameters["room_names"] = room_names
58
81
 
82
+ projector = _WorldToPixel(parsed)
83
+ has_drawables = _place_poses(map_data, parsed, projector)
84
+ map_data.rooms = _extract_rooms(parsed, projector, room_names)
85
+ has_drawables = has_drawables or bool(map_data.rooms)
86
+ if carpet_pixels:
87
+ # Same contract as the Q10 parser: flat top-down grid indices.
88
+ map_data.carpet_map = {(size_y - 1 - index // size_x) * size_x + index % size_x for index in carpet_pixels}
89
+
90
+ if has_drawables:
91
+ generator = _create_image_generator(
92
+ MapParserConfig(map_scale=self._config.map_scale),
93
+ drawables=_B01_DRAWABLES,
94
+ )
95
+ generator.draw_map(map_data)
96
+ image = map_data.image.data
97
+
59
98
  image_bytes = io.BytesIO()
60
99
  image.save(image_bytes, format=_MAP_FILE_FORMAT)
61
100
 
@@ -101,6 +140,100 @@ def _extract_grid(parsed: RobotMap) -> tuple[int, int, bytes]:
101
140
  return size_x, size_y, map_data[:expected_len]
102
141
 
103
142
 
143
+ class _WorldToPixel:
144
+ """Project SCMap world coordinates (meters) into top-down image pixels."""
145
+
146
+ def __init__(self, parsed: RobotMap) -> None:
147
+ head = parsed.mapHead
148
+ self._min_x = head.minX
149
+ self._min_y = head.minY
150
+ self._max_x = head.maxX
151
+ self._max_y = head.maxY
152
+ self._resolution = head.resolution or 0.05
153
+ self._size_y = head.sizeY
154
+
155
+ def in_bounds(self, x: float, y: float) -> bool:
156
+ """Whether a world point lies inside the map (rejects placeholder poses)."""
157
+ return self._min_x <= x <= self._max_x and self._min_y <= y <= self._max_y
158
+
159
+ def to_pixel(self, x: float, y: float) -> tuple[float, float]:
160
+ """World meters to top-down image pixel coordinates."""
161
+ px = (x - self._min_x) / self._resolution
162
+ py = self._size_y - 1 - (y - self._min_y) / self._resolution
163
+ return px, py
164
+
165
+
166
+ def _place_poses(map_data: MapData, parsed: RobotMap, projector: _WorldToPixel) -> bool:
167
+ """Populate charger, robot position and path from the decoded SCMap."""
168
+ has_drawables = False
169
+
170
+ if parsed.HasField("chargeStation") and projector.in_bounds(parsed.chargeStation.x, parsed.chargeStation.y):
171
+ px, py = projector.to_pixel(parsed.chargeStation.x, parsed.chargeStation.y)
172
+ map_data.charger = Point(px, py, math.degrees(parsed.chargeStation.phi))
173
+ has_drawables = True
174
+
175
+ if parsed.HasField("currentPose") and projector.in_bounds(parsed.currentPose.x, parsed.currentPose.y):
176
+ px, py = projector.to_pixel(parsed.currentPose.x, parsed.currentPose.y)
177
+ map_data.vacuum_position = Point(px, py, math.degrees(parsed.currentPose.phi))
178
+ has_drawables = True
179
+ elif map_data.charger is not None:
180
+ # A saved map carries no live pose; show the robot at its dock.
181
+ map_data.vacuum_position = Point(map_data.charger.x, map_data.charger.y, map_data.charger.a)
182
+
183
+ areas = [
184
+ Area(*(coord for point in area.points for coord in projector.to_pixel(point.x, point.y)))
185
+ for area in parsed.areaInfo
186
+ if len(area.points) == 4
187
+ ]
188
+ if areas:
189
+ # areaInfo type semantics are not yet mapped per zone kind; render all
190
+ # restricted areas through the no-go drawable for now.
191
+ map_data.no_go_areas = areas
192
+ has_drawables = True
193
+
194
+ if parsed.HasField("historyPose"):
195
+ pixels = [
196
+ Point(*projector.to_pixel(point.x, point.y))
197
+ for point in parsed.historyPose.points
198
+ if projector.in_bounds(point.x, point.y)
199
+ ]
200
+ if pixels:
201
+ map_data.path = Path(len(pixels), 1, 0, [pixels])
202
+ has_drawables = True
203
+
204
+ return has_drawables
205
+
206
+
207
+ def _extract_rooms(parsed: RobotMap, projector: _WorldToPixel, room_names: dict[int, str]) -> dict[int, Room] | None:
208
+ """Build room bounding boxes (image-pixel space) from room outlines."""
209
+ rooms: dict[int, Room] = {}
210
+ label_positions = {
211
+ room.roomId: projector.to_pixel(room.roomNamePost.x, room.roomNamePost.y)
212
+ for room in parsed.roomDataInfo
213
+ if room.HasField("roomNamePost")
214
+ }
215
+ size_y = parsed.mapHead.sizeY
216
+ for outline in parsed.roomOutline:
217
+ if not outline.points:
218
+ continue
219
+ room_id = outline.roomId
220
+ # Outline points are top-down after the same vertical flip as the raster.
221
+ xs = [point.x for point in outline.points]
222
+ ys = [size_y - 1 - point.y for point in outline.points]
223
+ pos = label_positions.get(room_id)
224
+ rooms[room_id] = Room(
225
+ min(xs),
226
+ min(ys),
227
+ max(xs),
228
+ max(ys),
229
+ room_id,
230
+ room_names.get(room_id),
231
+ pos[0] if pos else None,
232
+ pos[1] if pos else None,
233
+ )
234
+ return rooms or None
235
+
236
+
104
237
  def _extract_room_names(parsed: RobotMap) -> dict[int, str]:
105
238
  # Expose room id/name mapping without inventing room geometry/polygons.
106
239
  room_names: dict[int, str] = {}
@@ -111,21 +244,138 @@ def _extract_room_names(parsed: RobotMap) -> dict[int, str]:
111
244
  return room_names
112
245
 
113
246
 
114
- def _render_occupancy_image(grid: bytes, *, size_x: int, size_y: int, scale: int) -> Image.Image:
115
- """Render the B01 occupancy grid into a simple image."""
247
+ def _assign_room_pixels(parsed: RobotMap, grid: bytes, *, size_x: int, size_y: int) -> bytearray:
248
+ """Assign a room id to each floor pixel by flood-filling from room labels.
249
+
250
+ The grid itself carries no room ids; room geometry arrives as boundary
251
+ pixel chains (``roomOutline``). Each room is filled from its label
252
+ position, bounded by walls and by any room's outline pixels, all in the
253
+ raw (bottom-up) grid space.
254
+ """
255
+ assignment = bytearray(len(grid))
256
+ outlines = {outline.roomId: outline for outline in parsed.roomOutline if outline.points}
257
+ if not outlines:
258
+ return assignment
259
+
260
+ barrier = {
261
+ point.y * size_x + point.x
262
+ for outline in outlines.values()
263
+ for point in outline.points
264
+ if point.x < size_x and point.y < size_y
265
+ }
266
+ floor_count = grid.count(_FLOOR)
267
+ # ponytail: leak guard — a gapped outline would flood the whole floor, so a
268
+ # fill larger than half of it is discarded instead of tracing outline gaps.
269
+ max_fill = floor_count // 2
270
+
271
+ head = parsed.mapHead
272
+ label_positions = {
273
+ room.roomId: (
274
+ int((room.roomNamePost.x - head.minX) / head.resolution),
275
+ int((room.roomNamePost.y - head.minY) / head.resolution),
276
+ )
277
+ for room in parsed.roomDataInfo
278
+ if room.HasField("roomNamePost")
279
+ }
280
+
281
+ for room_id, outline in outlines.items():
282
+ seed = label_positions.get(room_id)
283
+ if seed is None:
284
+ continue
285
+ col, row = seed
286
+ start = row * size_x + col
287
+ if not (0 <= col < size_x and 0 <= row < size_y) or grid[start] != _FLOOR:
288
+ continue
289
+ filled: list[int] = []
290
+ queue = deque([start])
291
+ seen = {start}
292
+ while queue and len(filled) <= max_fill:
293
+ index = queue.popleft()
294
+ filled.append(index)
295
+ for neighbor in (index - 1, index + 1, index - size_x, index + size_x):
296
+ if (
297
+ 0 <= neighbor < len(grid)
298
+ and neighbor not in seen
299
+ and grid[neighbor] == _FLOOR
300
+ and assignment[neighbor] == 0
301
+ and neighbor not in barrier
302
+ # Row-wrap guard for the horizontal neighbors.
303
+ and abs(neighbor % size_x - index % size_x) <= 1
304
+ ):
305
+ seen.add(neighbor)
306
+ queue.append(neighbor)
307
+ if len(filled) > max_fill:
308
+ continue
309
+ for index in filled:
310
+ assignment[index] = room_id
311
+ # Color the room's own boundary ring too where it sits on floor.
312
+ for point in outline.points:
313
+ index = point.y * size_x + point.x
314
+ if index < len(grid) and grid[index] == _FLOOR and assignment[index] == 0:
315
+ assignment[index] = room_id
316
+
317
+ return assignment
318
+
319
+
320
+ def _carpet_pixel_indices(parsed: RobotMap, grid: bytes, *, size_x: int, size_y: int) -> set[int]:
321
+ """Raw-grid indices of floor pixels covered by enabled carpets."""
322
+ head = parsed.mapHead
323
+ resolution = head.resolution or 0.05
324
+ indices: set[int] = set()
325
+ for carpet in parsed.carpetInfo:
326
+ if not carpet.points or (carpet.HasField("enabled") and not carpet.enabled):
327
+ continue
328
+ cols = [int((point.x - head.minX) / resolution) for point in carpet.points]
329
+ rows = [int((point.y - head.minY) / resolution) for point in carpet.points]
330
+ for row in range(max(min(rows), 0), min(max(rows), size_y - 1) + 1):
331
+ for col in range(max(min(cols), 0), min(max(cols), size_x - 1) + 1):
332
+ index = row * size_x + col
333
+ if grid[index] == _FLOOR:
334
+ indices.add(index)
335
+ return indices
336
+
337
+
338
+ def _render_occupancy_image(
339
+ grid: bytes, room_pixels: bytearray, carpet_pixels: set[int], *, size_x: int, size_y: int, scale: int
340
+ ) -> Image.Image:
341
+ """Render the B01 occupancy grid with per-room colors."""
342
+
343
+ colors = ColorsPalette()
344
+ room_colors = {
345
+ room_id: tuple(color[:3]) + (255,)
346
+ for room_id, color in adjacency_aware_room_colors(
347
+ room_pixels, size_x, colors, lambda value: value or None
348
+ ).items()
349
+ }
116
350
 
117
351
  # The observed occupancy grid contains only:
118
352
  # - 0: outside/unknown
119
- # - 127: wall/obstacle
120
- # - 128: floor/free
121
- table = bytearray(range(256))
122
- table[0] = 0
123
- table[127] = 180
124
- table[128] = 255
125
-
126
- mapped = grid.translate(bytes(table))
127
- img = Image.frombytes("L", (size_x, size_y), mapped)
128
- img = img.transpose(Image.Transpose.FLIP_TOP_BOTTOM).convert("RGB")
353
+ # - 127: floor/free
354
+ # - 128: wall/obstacle
355
+ # Same V1 palette roles as the Q10 renderer: transparent outside, grey
356
+ # walls/obstacles, MAP_INSIDE for floor not assigned to any room.
357
+ outside = (0, 0, 0, 0)
358
+ floor = tuple(colors.get_color(SupportedColor.MAP_INSIDE)[:3]) + (255,)
359
+ base_colors = {
360
+ 0: outside,
361
+ _FLOOR: floor,
362
+ _WALL: tuple(colors.get_color(SupportedColor.GREY_WALL)[:3]) + (255,),
363
+ }
364
+
365
+ rgba = bytearray()
366
+ for index, value in enumerate(grid):
367
+ if value == _FLOOR and (room_id := room_pixels[index]):
368
+ color = room_colors.get(room_id, floor)
369
+ else:
370
+ color = base_colors.get(value, floor)
371
+ if index in carpet_pixels and (index // size_x + index % size_x) % 2 == 0:
372
+ # Checkerboard stipple, like the V1 carpet texture.
373
+ color = tuple(min(channel + 60, 255) for channel in color[:3]) + (255,)
374
+ rgba.extend(color)
375
+
376
+ # RGBA so the shared V1 ImageGenerator can alpha-composite overlay glyphs.
377
+ img = Image.frombytes("RGBA", (size_x, size_y), bytes(rgba))
378
+ img = img.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
129
379
 
130
380
  if scale > 1:
131
381
  img = img.resize((size_x * scale, size_y * scale), resample=Image.Resampling.NEAREST)
@@ -341,7 +341,7 @@ def _drop_stray_leading_point(points: list[Q10Point]) -> list[Q10Point]:
341
341
  """
342
342
  if len(points) < 3:
343
343
  return points
344
- steps = [math.hypot(b.x - a.x, b.y - a.y) for a, b in zip(points, points[1:])]
344
+ steps = [math.hypot(b.x - a.x, b.y - a.y) for a, b in zip(points, points[1:], strict=False)]
345
345
  median_rest = statistics.median(steps[1:])
346
346
  if median_rest > 0 and steps[0] > _STRAY_POINT_STEP_RATIO * median_rest:
347
347
  return points[1:]
@@ -425,7 +425,7 @@ def _infer_layout(decoded: bytes, width: int) -> tuple[int, bytes, bytes]:
425
425
  up with the marker. Used as a fallback when the header carries no usable
426
426
  height.
427
427
  """
428
- for room_count in range(0, _MAX_ROOMS + 1):
428
+ for room_count in range(_MAX_ROOMS + 1):
429
429
  room_data_length = 2 + room_count * _ROOM_RECORD_LENGTH
430
430
  area = len(decoded) - room_data_length
431
431
  if area <= 0 or area % width:
@@ -1,6 +1,6 @@
1
1
  // Checked-in B01/Q7 SCMap schema for the generated runtime protobuf module.
2
2
  // Regenerate the checked-in Python module after edits with:
3
- // python -m grpc_tools.protoc -I./roborock/map/proto --python_out=./roborock/map/proto roborock/map/proto/b01_scmap.proto
3
+ // python -m grpc_tools.protoc -I. --python_out=. roborock/map/proto/b01_scmap.proto
4
4
  // The generated file `b01_scmap_pb2.py` is checked in for runtime use and should
5
5
  // not be edited by hand.
6
6
  syntax = "proto2";
@@ -47,6 +47,79 @@ message MapDataInfo {
47
47
  optional bytes mapData = 1;
48
48
  }
49
49
 
50
+ message MapInfo {
51
+ optional uint32 mapId = 1;
52
+ optional string mapName = 2;
53
+ }
54
+
55
+ message DevicePoseDataInfo {
56
+ optional uint32 update = 1;
57
+ optional float x = 2;
58
+ optional float y = 3;
59
+ }
60
+
61
+ message DeviceHistoryPoseInfo {
62
+ optional uint32 poseId = 1;
63
+ repeated DevicePoseDataInfo points = 2;
64
+ }
65
+
66
+ message DevicePoseInfo {
67
+ optional float x = 1;
68
+ optional float y = 2;
69
+ optional float phi = 3;
70
+ }
71
+
72
+ message DeviceCurrentPoseInfo {
73
+ optional uint32 poseId = 1;
74
+ optional uint32 update = 2;
75
+ optional float x = 3;
76
+ optional float y = 4;
77
+ optional float phi = 5;
78
+ }
79
+
80
+ message DeviceAreaDataInfo {
81
+ optional uint32 status = 1;
82
+ optional uint32 type = 2;
83
+ optional uint32 areaIndex = 3;
84
+ repeated DevicePointInfo points = 4;
85
+ }
86
+
87
+ message RoomMatrixInfo {
88
+ optional bytes matrix = 1;
89
+ }
90
+
91
+ message RoomOutlinePointInfo {
92
+ optional uint32 x = 1;
93
+ optional uint32 y = 2;
94
+ optional uint32 value = 3;
95
+ }
96
+
97
+ message RoomBorderPointInfo {
98
+ optional uint32 x = 1;
99
+ optional uint32 y = 2;
100
+ }
101
+
102
+ message RoomBorderInfo {
103
+ repeated RoomBorderPointInfo points = 1;
104
+ repeated uint32 roomIds = 2;
105
+ }
106
+
107
+ message RoomOutlineInfo {
108
+ optional uint32 roomId = 1;
109
+ repeated RoomOutlinePointInfo points = 2;
110
+ repeated RoomBorderInfo borders = 3;
111
+ }
112
+
113
+ message CarpetDataInfo {
114
+ optional uint32 carpetId = 1;
115
+ optional uint32 type = 2;
116
+ optional uint32 unknown3 = 3;
117
+ repeated DevicePointInfo points = 4;
118
+ optional uint32 enabled = 6;
119
+ optional uint32 unknown7 = 7;
120
+ optional uint32 unknown8 = 8;
121
+ }
122
+
50
123
  message RoomDataInfo {
51
124
  optional uint32 roomId = 1;
52
125
  optional string roomName = 2;
@@ -66,5 +139,13 @@ message RobotMap {
66
139
  optional MapExtInfo mapExtInfo = 2;
67
140
  optional MapHeadInfo mapHead = 3;
68
141
  optional MapDataInfo mapData = 4;
142
+ repeated MapInfo mapInfo = 5;
143
+ optional DeviceHistoryPoseInfo historyPose = 6;
144
+ optional DevicePoseInfo chargeStation = 7;
145
+ optional DeviceCurrentPoseInfo currentPose = 8;
146
+ repeated DeviceAreaDataInfo areaInfo = 9;
69
147
  repeated RoomDataInfo roomDataInfo = 12;
148
+ optional RoomMatrixInfo roomMatrix = 13;
149
+ repeated RoomOutlineInfo roomOutline = 14;
150
+ repeated CarpetDataInfo carpetInfo = 20;
70
151
  }
@@ -24,7 +24,7 @@ _sym_db = _symbol_database.Default()
24
24
 
25
25
 
26
26
 
27
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"roborock/map/proto/b01_scmap.proto\x12\tb01.scmap\"\'\n\x0f\x44\x65vicePointInfo\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\"]\n\x0fMapBoundaryInfo\x12\x0e\n\x06mapMd5\x18\x01 \x01(\t\x12\r\n\x05vMinX\x18\x02 \x01(\r\x12\r\n\x05vMaxX\x18\x03 \x01(\r\x12\r\n\x05vMinY\x18\x04 \x01(\r\x12\r\n\x05vMaxY\x18\x05 \x01(\r\"\xd9\x01\n\nMapExtInfo\x12\x15\n\rtaskBeginDate\x18\x01 \x01(\r\x12\x15\n\rmapUploadDate\x18\x02 \x01(\r\x12\x10\n\x08mapValid\x18\x03 \x01(\r\x12\x0e\n\x06radian\x18\x04 \x01(\r\x12\r\n\x05\x66orce\x18\x05 \x01(\r\x12\x11\n\tcleanPath\x18\x06 \x01(\r\x12/\n\x0b\x62oudaryInfo\x18\x07 \x01(\x0b\x32\x1a.b01.scmap.MapBoundaryInfo\x12\x12\n\nmapVersion\x18\x08 \x01(\r\x12\x14\n\x0cmapValueType\x18\t \x01(\r\"\x8a\x01\n\x0bMapHeadInfo\x12\x11\n\tmapHeadId\x18\x01 \x01(\r\x12\r\n\x05sizeX\x18\x02 \x01(\r\x12\r\n\x05sizeY\x18\x03 \x01(\r\x12\x0c\n\x04minX\x18\x04 \x01(\x02\x12\x0c\n\x04minY\x18\x05 \x01(\x02\x12\x0c\n\x04maxX\x18\x06 \x01(\x02\x12\x0c\n\x04maxY\x18\x07 \x01(\x02\x12\x12\n\nresolution\x18\x08 \x01(\x02\"\x1e\n\x0bMapDataInfo\x12\x0f\n\x07mapData\x18\x01 \x01(\x0c\"\x87\x02\n\x0cRoomDataInfo\x12\x0e\n\x06roomId\x18\x01 \x01(\r\x12\x10\n\x08roomName\x18\x02 \x01(\t\x12\x12\n\nroomTypeId\x18\x03 \x01(\r\x12\x12\n\nmeterialId\x18\x04 \x01(\r\x12\x12\n\ncleanState\x18\x05 \x01(\r\x12\x11\n\troomClean\x18\x06 \x01(\r\x12\x16\n\x0eroomCleanIndex\x18\x07 \x01(\r\x12\x30\n\x0croomNamePost\x18\x08 \x01(\x0b\x32\x1a.b01.scmap.DevicePointInfo\x12\x0f\n\x07\x63olorId\x18\n \x01(\r\x12\x17\n\x0f\x66loor_direction\x18\x0b \x01(\r\x12\x12\n\nglobal_seq\x18\x0c \x01(\r\"\xc7\x01\n\x08RobotMap\x12\x0f\n\x07mapType\x18\x01 \x01(\r\x12)\n\nmapExtInfo\x18\x02 \x01(\x0b\x32\x15.b01.scmap.MapExtInfo\x12\'\n\x07mapHead\x18\x03 \x01(\x0b\x32\x16.b01.scmap.MapHeadInfo\x12\'\n\x07mapData\x18\x04 \x01(\x0b\x32\x16.b01.scmap.MapDataInfo\x12-\n\x0croomDataInfo\x18\x0c \x03(\x0b\x32\x17.b01.scmap.RoomDataInfo')
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"roborock/map/proto/b01_scmap.proto\x12\tb01.scmap\"\'\n\x0f\x44\x65vicePointInfo\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\"]\n\x0fMapBoundaryInfo\x12\x0e\n\x06mapMd5\x18\x01 \x01(\t\x12\r\n\x05vMinX\x18\x02 \x01(\r\x12\r\n\x05vMaxX\x18\x03 \x01(\r\x12\r\n\x05vMinY\x18\x04 \x01(\r\x12\r\n\x05vMaxY\x18\x05 \x01(\r\"\xd9\x01\n\nMapExtInfo\x12\x15\n\rtaskBeginDate\x18\x01 \x01(\r\x12\x15\n\rmapUploadDate\x18\x02 \x01(\r\x12\x10\n\x08mapValid\x18\x03 \x01(\r\x12\x0e\n\x06radian\x18\x04 \x01(\r\x12\r\n\x05\x66orce\x18\x05 \x01(\r\x12\x11\n\tcleanPath\x18\x06 \x01(\r\x12/\n\x0b\x62oudaryInfo\x18\x07 \x01(\x0b\x32\x1a.b01.scmap.MapBoundaryInfo\x12\x12\n\nmapVersion\x18\x08 \x01(\r\x12\x14\n\x0cmapValueType\x18\t \x01(\r\"\x8a\x01\n\x0bMapHeadInfo\x12\x11\n\tmapHeadId\x18\x01 \x01(\r\x12\r\n\x05sizeX\x18\x02 \x01(\r\x12\r\n\x05sizeY\x18\x03 \x01(\r\x12\x0c\n\x04minX\x18\x04 \x01(\x02\x12\x0c\n\x04minY\x18\x05 \x01(\x02\x12\x0c\n\x04maxX\x18\x06 \x01(\x02\x12\x0c\n\x04maxY\x18\x07 \x01(\x02\x12\x12\n\nresolution\x18\x08 \x01(\x02\"\x1e\n\x0bMapDataInfo\x12\x0f\n\x07mapData\x18\x01 \x01(\x0c\")\n\x07MapInfo\x12\r\n\x05mapId\x18\x01 \x01(\r\x12\x0f\n\x07mapName\x18\x02 \x01(\t\":\n\x12\x44\x65vicePoseDataInfo\x12\x0e\n\x06update\x18\x01 \x01(\r\x12\t\n\x01x\x18\x02 \x01(\x02\x12\t\n\x01y\x18\x03 \x01(\x02\"V\n\x15\x44\x65viceHistoryPoseInfo\x12\x0e\n\x06poseId\x18\x01 \x01(\r\x12-\n\x06points\x18\x02 \x03(\x0b\x32\x1d.b01.scmap.DevicePoseDataInfo\"3\n\x0e\x44\x65vicePoseInfo\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\x12\x0b\n\x03phi\x18\x03 \x01(\x02\"Z\n\x15\x44\x65viceCurrentPoseInfo\x12\x0e\n\x06poseId\x18\x01 \x01(\r\x12\x0e\n\x06update\x18\x02 \x01(\r\x12\t\n\x01x\x18\x03 \x01(\x02\x12\t\n\x01y\x18\x04 \x01(\x02\x12\x0b\n\x03phi\x18\x05 \x01(\x02\"q\n\x12\x44\x65viceAreaDataInfo\x12\x0e\n\x06status\x18\x01 \x01(\r\x12\x0c\n\x04type\x18\x02 \x01(\r\x12\x11\n\tareaIndex\x18\x03 \x01(\r\x12*\n\x06points\x18\x04 \x03(\x0b\x32\x1a.b01.scmap.DevicePointInfo\" \n\x0eRoomMatrixInfo\x12\x0e\n\x06matrix\x18\x01 \x01(\x0c\";\n\x14RoomOutlinePointInfo\x12\t\n\x01x\x18\x01 \x01(\r\x12\t\n\x01y\x18\x02 \x01(\r\x12\r\n\x05value\x18\x03 \x01(\r\"+\n\x13RoomBorderPointInfo\x12\t\n\x01x\x18\x01 \x01(\r\x12\t\n\x01y\x18\x02 \x01(\r\"Q\n\x0eRoomBorderInfo\x12.\n\x06points\x18\x01 \x03(\x0b\x32\x1e.b01.scmap.RoomBorderPointInfo\x12\x0f\n\x07roomIds\x18\x02 \x03(\r\"~\n\x0fRoomOutlineInfo\x12\x0e\n\x06roomId\x18\x01 \x01(\r\x12/\n\x06points\x18\x02 \x03(\x0b\x32\x1f.b01.scmap.RoomOutlinePointInfo\x12*\n\x07\x62orders\x18\x03 \x03(\x0b\x32\x19.b01.scmap.RoomBorderInfo\"\xa3\x01\n\x0e\x43\x61rpetDataInfo\x12\x10\n\x08\x63\x61rpetId\x18\x01 \x01(\r\x12\x0c\n\x04type\x18\x02 \x01(\r\x12\x10\n\x08unknown3\x18\x03 \x01(\r\x12*\n\x06points\x18\x04 \x03(\x0b\x32\x1a.b01.scmap.DevicePointInfo\x12\x0f\n\x07\x65nabled\x18\x06 \x01(\r\x12\x10\n\x08unknown7\x18\x07 \x01(\r\x12\x10\n\x08unknown8\x18\x08 \x01(\r\"\x87\x02\n\x0cRoomDataInfo\x12\x0e\n\x06roomId\x18\x01 \x01(\r\x12\x10\n\x08roomName\x18\x02 \x01(\t\x12\x12\n\nroomTypeId\x18\x03 \x01(\r\x12\x12\n\nmeterialId\x18\x04 \x01(\r\x12\x12\n\ncleanState\x18\x05 \x01(\r\x12\x11\n\troomClean\x18\x06 \x01(\r\x12\x16\n\x0eroomCleanIndex\x18\x07 \x01(\r\x12\x30\n\x0croomNamePost\x18\x08 \x01(\x0b\x32\x1a.b01.scmap.DevicePointInfo\x12\x0f\n\x07\x63olorId\x18\n \x01(\r\x12\x17\n\x0f\x66loor_direction\x18\x0b \x01(\r\x12\x12\n\nglobal_seq\x18\x0c \x01(\r\"\xcc\x04\n\x08RobotMap\x12\x0f\n\x07mapType\x18\x01 \x01(\r\x12)\n\nmapExtInfo\x18\x02 \x01(\x0b\x32\x15.b01.scmap.MapExtInfo\x12\'\n\x07mapHead\x18\x03 \x01(\x0b\x32\x16.b01.scmap.MapHeadInfo\x12\'\n\x07mapData\x18\x04 \x01(\x0b\x32\x16.b01.scmap.MapDataInfo\x12#\n\x07mapInfo\x18\x05 \x03(\x0b\x32\x12.b01.scmap.MapInfo\x12\x35\n\x0bhistoryPose\x18\x06 \x01(\x0b\x32 .b01.scmap.DeviceHistoryPoseInfo\x12\x30\n\rchargeStation\x18\x07 \x01(\x0b\x32\x19.b01.scmap.DevicePoseInfo\x12\x35\n\x0b\x63urrentPose\x18\x08 \x01(\x0b\x32 .b01.scmap.DeviceCurrentPoseInfo\x12/\n\x08\x61reaInfo\x18\t \x03(\x0b\x32\x1d.b01.scmap.DeviceAreaDataInfo\x12-\n\x0croomDataInfo\x18\x0c \x03(\x0b\x32\x17.b01.scmap.RoomDataInfo\x12-\n\nroomMatrix\x18\r \x01(\x0b\x32\x19.b01.scmap.RoomMatrixInfo\x12/\n\x0broomOutline\x18\x0e \x03(\x0b\x32\x1a.b01.scmap.RoomOutlineInfo\x12-\n\ncarpetInfo\x18\x14 \x03(\x0b\x32\x19.b01.scmap.CarpetDataInfo')
28
28
 
29
29
  _globals = globals()
30
30
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -41,8 +41,32 @@ if not _descriptor._USE_C_DESCRIPTORS:
41
41
  _globals['_MAPHEADINFO']._serialized_end=544
42
42
  _globals['_MAPDATAINFO']._serialized_start=546
43
43
  _globals['_MAPDATAINFO']._serialized_end=576
44
- _globals['_ROOMDATAINFO']._serialized_start=579
45
- _globals['_ROOMDATAINFO']._serialized_end=842
46
- _globals['_ROBOTMAP']._serialized_start=845
47
- _globals['_ROBOTMAP']._serialized_end=1044
44
+ _globals['_MAPINFO']._serialized_start=578
45
+ _globals['_MAPINFO']._serialized_end=619
46
+ _globals['_DEVICEPOSEDATAINFO']._serialized_start=621
47
+ _globals['_DEVICEPOSEDATAINFO']._serialized_end=679
48
+ _globals['_DEVICEHISTORYPOSEINFO']._serialized_start=681
49
+ _globals['_DEVICEHISTORYPOSEINFO']._serialized_end=767
50
+ _globals['_DEVICEPOSEINFO']._serialized_start=769
51
+ _globals['_DEVICEPOSEINFO']._serialized_end=820
52
+ _globals['_DEVICECURRENTPOSEINFO']._serialized_start=822
53
+ _globals['_DEVICECURRENTPOSEINFO']._serialized_end=912
54
+ _globals['_DEVICEAREADATAINFO']._serialized_start=914
55
+ _globals['_DEVICEAREADATAINFO']._serialized_end=1027
56
+ _globals['_ROOMMATRIXINFO']._serialized_start=1029
57
+ _globals['_ROOMMATRIXINFO']._serialized_end=1061
58
+ _globals['_ROOMOUTLINEPOINTINFO']._serialized_start=1063
59
+ _globals['_ROOMOUTLINEPOINTINFO']._serialized_end=1122
60
+ _globals['_ROOMBORDERPOINTINFO']._serialized_start=1124
61
+ _globals['_ROOMBORDERPOINTINFO']._serialized_end=1167
62
+ _globals['_ROOMBORDERINFO']._serialized_start=1169
63
+ _globals['_ROOMBORDERINFO']._serialized_end=1250
64
+ _globals['_ROOMOUTLINEINFO']._serialized_start=1252
65
+ _globals['_ROOMOUTLINEINFO']._serialized_end=1378
66
+ _globals['_CARPETDATAINFO']._serialized_start=1381
67
+ _globals['_CARPETDATAINFO']._serialized_end=1544
68
+ _globals['_ROOMDATAINFO']._serialized_start=1547
69
+ _globals['_ROOMDATAINFO']._serialized_end=1810
70
+ _globals['_ROBOTMAP']._serialized_start=1813
71
+ _globals['_ROBOTMAP']._serialized_end=2401
48
72
  # @@protoc_insertion_point(module_scope)
@@ -165,7 +165,7 @@ class RoborockMqttSession(MqttSession):
165
165
  await self._connection_task
166
166
  except asyncio.CancelledError:
167
167
  _LOGGER.debug("MQTT connection task cancelled")
168
- except Exception:
168
+ except Exception: # noqa: BLE001
169
169
  # Exceptions are logged and handled in _run_connection.
170
170
  # There is a special case for exceptions on startup where we return
171
171
  # immediately. Otherwise, we let the reconnect loop retry with
roborock/protocol.py CHANGED
@@ -5,7 +5,7 @@ import logging
5
5
  from collections.abc import Callable
6
6
  from urllib.parse import urlparse
7
7
 
8
- from construct import ( # type: ignore
8
+ from construct import ( # type: ignore[import-untyped]
9
9
  Bytes,
10
10
  Checksum,
11
11
  ChecksumError,
@@ -65,7 +65,7 @@ class Utils:
65
65
  @staticmethod
66
66
  def encode_timestamp(_timestamp: int) -> bytes:
67
67
  hex_value = f"{_timestamp:x}".zfill(8)
68
- return "".join(list(map(lambda idx: hex_value[idx], [5, 6, 3, 7, 1, 2, 0, 4]))).encode()
68
+ return "".join([hex_value[idx] for idx in [5, 6, 3, 7, 1, 2, 0, 4]]).encode()
69
69
 
70
70
  @staticmethod
71
71
  def md5(data: bytes) -> bytes:
@@ -470,7 +470,7 @@ class _Parser:
470
470
  }
471
471
  )
472
472
  return self.con.build(
473
- {"messages": [message for message in messages], "remaining": b""},
473
+ {"messages": list(messages), "remaining": b""},
474
474
  local_key=local_key,
475
475
  prefixed=prefixed,
476
476
  connect_nonce=connect_nonce,
@@ -62,7 +62,7 @@ def decode_rpc_response(message: RoborockMessage) -> dict[int, Any]:
62
62
  try:
63
63
  unpadded = unpad(message.payload, AES.block_size)
64
64
  except ValueError as err:
65
- raise RoborockException(f"Unable to unpad A01 payload: {err}")
65
+ raise RoborockException(f"Unable to unpad A01 payload: {err}") from err
66
66
 
67
67
  try:
68
68
  payload = json.loads(unpadded.decode())
@@ -74,5 +74,7 @@ def decode_rpc_response(message: RoborockMessage) -> dict[int, Any]:
74
74
  raise RoborockException(f"Invalid A01 message format: 'dps' should be a dictionary for {message.payload!r}")
75
75
  try:
76
76
  return {int(key): value for key, value in datapoints.items()}
77
- except ValueError:
78
- raise RoborockException(f"Invalid A01 message format: 'dps' key should be an integer for {message.payload!r}")
77
+ except ValueError as err:
78
+ raise RoborockException(
79
+ f"Invalid A01 message format: 'dps' key should be an integer for {message.payload!r}"
80
+ ) from err
@@ -83,8 +83,10 @@ def decode_rpc_response(message: RoborockMessage) -> dict[int, Any]:
83
83
  raise RoborockException(f"Invalid B01 message format: 'dps' should be a dictionary for {message.payload!r}")
84
84
  try:
85
85
  return {int(key): value for key, value in datapoints.items()}
86
- except ValueError:
87
- raise RoborockException(f"Invalid B01 message format: 'dps' key should be an integer for {message.payload!r}")
86
+ except ValueError as err:
87
+ raise RoborockException(
88
+ f"Invalid B01 message format: 'dps' key should be an integer for {message.payload!r}"
89
+ ) from err
88
90
 
89
91
 
90
92
  @dataclass
@@ -346,7 +346,7 @@ class V1VacuumSimulator(RoborockDeviceSimulator):
346
346
  msg_id = inner["id"]
347
347
  method = inner["method"]
348
348
  params = inner.get("params", [])
349
- except Exception as e:
349
+ except Exception as e: # noqa: BLE001
350
350
  _LOGGER.debug("Failed to parse plaintext JSON RPC payload: %s", e, exc_info=True)
351
351
  return
352
352
 
@@ -358,7 +358,7 @@ class V1VacuumSimulator(RoborockDeviceSimulator):
358
358
  if handler:
359
359
  try:
360
360
  result = handler(params)
361
- except Exception as e:
361
+ except Exception as e: # noqa: BLE001
362
362
  error = str(e)
363
363
  _LOGGER.debug("Error executing command handler for %s: %s", method, e, exc_info=True)
364
364
  else:
roborock/util.py CHANGED
@@ -10,7 +10,7 @@ T = TypeVar("T")
10
10
 
11
11
 
12
12
  def unpack_list(value: list[T], size: int) -> list[T | None]:
13
- return (value + [None] * size)[:size] # type: ignore
13
+ return (value + [None] * size)[:size]
14
14
 
15
15
 
16
16
  class RoborockLoggerAdapter(logging.LoggerAdapter):