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.
- {python_roborock-7.4.2.dist-info → python_roborock-7.5.0.dist-info}/METADATA +1 -1
- {python_roborock-7.4.2.dist-info → python_roborock-7.5.0.dist-info}/RECORD +30 -30
- roborock/broadcast_protocol.py +2 -2
- roborock/cli.py +11 -11
- roborock/data/b01_q7/b01_q7_code_mappings.py +5 -1
- roborock/data/b01_q7/b01_q7_containers.py +2 -2
- roborock/data/code_mappings.py +1 -1
- roborock/data/containers.py +5 -5
- roborock/data/v1/v1_code_mappings.py +1 -1
- roborock/device_features.py +1 -1
- roborock/devices/rpc/b01_q7_channel.py +1 -1
- roborock/devices/traits/__init__.py +1 -1
- roborock/devices/traits/b01/q7/map_content.py +4 -0
- roborock/devices/traits/v1/clean_summary.py +1 -1
- roborock/devices/traits/v1/rooms.py +1 -1
- roborock/devices/transport/local_channel.py +1 -1
- roborock/map/b01_grid_layers.py +1 -1
- roborock/map/b01_map_parser.py +266 -16
- roborock/map/b01_q10_map_parser.py +2 -2
- roborock/map/proto/b01_scmap.proto +82 -1
- roborock/map/proto/b01_scmap_pb2.py +29 -5
- roborock/mqtt/roborock_session.py +1 -1
- roborock/protocol.py +3 -3
- roborock/protocols/a01_protocol.py +5 -3
- roborock/protocols/b01_q7_protocol.py +4 -2
- roborock/testing/v1_simulator.py +2 -2
- roborock/util.py +1 -1
- {python_roborock-7.4.2.dist-info → python_roborock-7.5.0.dist-info}/WHEEL +0 -0
- {python_roborock-7.4.2.dist-info → python_roborock-7.5.0.dist-info}/entry_points.txt +0 -0
- {python_roborock-7.4.2.dist-info → python_roborock-7.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 7.
|
|
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=
|
|
2
|
+
roborock/broadcast_protocol.py,sha256=HY3towvw_JyeAQxhMwD7XyG0Gvd3pn-Ik7BYb5iP6M4,4047
|
|
3
3
|
roborock/callbacks.py,sha256=w2yPXtWFH7zfoFsLPYoCTbfwbE9p1ivA7iDcw20lJQI,4300
|
|
4
|
-
roborock/cli.py,sha256=
|
|
4
|
+
roborock/cli.py,sha256=gbe444a9Hq-AU16a5ov3fmoX7mBcuxa7JN4YW43-P2M,63321
|
|
5
5
|
roborock/const.py,sha256=fWpgUPrCgLnaXmncgjCLZET-t7wHWQlpyzV8qHtTjb4,2860
|
|
6
|
-
roborock/device_features.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
17
|
-
roborock/data/containers.py,sha256=
|
|
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=
|
|
23
|
-
roborock/data/b01_q7/b01_q7_containers.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
103
|
-
roborock/map/b01_map_parser.py,sha256=
|
|
104
|
-
roborock/map/b01_q10_map_parser.py,sha256=
|
|
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=
|
|
111
|
-
roborock/map/proto/b01_scmap_pb2.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
128
|
-
python_roborock-7.
|
|
129
|
-
python_roborock-7.
|
|
130
|
-
python_roborock-7.
|
|
131
|
-
python_roborock-7.
|
|
132
|
-
python_roborock-7.
|
|
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,,
|
roborock/broadcast_protocol.py
CHANGED
|
@@ -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
|
|
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
|
|
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(
|
|
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 = (
|
|
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
|
roborock/data/code_mappings.py
CHANGED
|
@@ -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:
|
roborock/data/containers.py
CHANGED
|
@@ -52,7 +52,7 @@ def _attr_repr(obj: Any) -> str:
|
|
|
52
52
|
continue
|
|
53
53
|
try:
|
|
54
54
|
v = getattr(obj, k)
|
|
55
|
-
except
|
|
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=
|
|
383
|
-
devices: list[HomeDataDevice] = field(default_factory=
|
|
384
|
-
received_devices: list[HomeDataDevice] = field(default_factory=
|
|
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):
|
roborock/device_features.py
CHANGED
|
@@ -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):
|
|
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
|
|
@@ -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 []
|
roborock/map/b01_grid_layers.py
CHANGED
|
@@ -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)):
|
roborock/map/b01_map_parser.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
115
|
-
"""
|
|
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:
|
|
120
|
-
# - 128:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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(
|
|
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
|
|
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\"\
|
|
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['
|
|
45
|
-
_globals['
|
|
46
|
-
_globals['
|
|
47
|
-
_globals['
|
|
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(
|
|
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":
|
|
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(
|
|
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(
|
|
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
|
roborock/testing/v1_simulator.py
CHANGED
|
@@ -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
|
File without changes
|
|
File without changes
|
|
File without changes
|