botslab360 0.4.1__tar.gz → 0.4.2__tar.gz

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.

Potentially problematic release.


This version of botslab360 might be problematic. Click here for more details.

Files changed (33) hide show
  1. {botslab360-0.4.1/src/botslab360.egg-info → botslab360-0.4.2}/PKG-INFO +11 -3
  2. {botslab360-0.4.1 → botslab360-0.4.2}/README.md +10 -2
  3. {botslab360-0.4.1 → botslab360-0.4.2}/pyproject.toml +1 -1
  4. {botslab360-0.4.1 → botslab360-0.4.2}/src/botslab360/__init__.py +2 -0
  5. {botslab360-0.4.1 → botslab360-0.4.2}/src/botslab360/models.py +10 -0
  6. {botslab360-0.4.1 → botslab360-0.4.2}/src/botslab360/rooms.py +10 -0
  7. {botslab360-0.4.1 → botslab360-0.4.2/src/botslab360.egg-info}/PKG-INFO +11 -3
  8. {botslab360-0.4.1 → botslab360-0.4.2}/tests/test_rooms.py +50 -3
  9. {botslab360-0.4.1 → botslab360-0.4.2}/ATTRIBUTION.md +0 -0
  10. {botslab360-0.4.1 → botslab360-0.4.2}/LICENSE +0 -0
  11. {botslab360-0.4.1 → botslab360-0.4.2}/setup.cfg +0 -0
  12. {botslab360-0.4.1 → botslab360-0.4.2}/src/botslab360/auth.py +0 -0
  13. {botslab360-0.4.1 → botslab360-0.4.2}/src/botslab360/client.py +0 -0
  14. {botslab360-0.4.1 → botslab360-0.4.2}/src/botslab360/commands.py +0 -0
  15. {botslab360-0.4.1 → botslab360-0.4.2}/src/botslab360/exceptions.py +0 -0
  16. {botslab360-0.4.1 → botslab360-0.4.2}/src/botslab360/protocol.py +0 -0
  17. {botslab360-0.4.1 → botslab360-0.4.2}/src/botslab360/quc.py +0 -0
  18. {botslab360-0.4.1 → botslab360-0.4.2}/src/botslab360.egg-info/SOURCES.txt +0 -0
  19. {botslab360-0.4.1 → botslab360-0.4.2}/src/botslab360.egg-info/dependency_links.txt +0 -0
  20. {botslab360-0.4.1 → botslab360-0.4.2}/src/botslab360.egg-info/requires.txt +0 -0
  21. {botslab360-0.4.1 → botslab360-0.4.2}/src/botslab360.egg-info/top_level.txt +0 -0
  22. {botslab360-0.4.1 → botslab360-0.4.2}/tests/test_android_360_diagnostic.py +0 -0
  23. {botslab360-0.4.1 → botslab360-0.4.2}/tests/test_auth.py +0 -0
  24. {botslab360-0.4.1 → botslab360-0.4.2}/tests/test_commands.py +0 -0
  25. {botslab360-0.4.1 → botslab360-0.4.2}/tests/test_compare_android_quc.py +0 -0
  26. {botslab360-0.4.1 → botslab360-0.4.2}/tests/test_credentials_auth_example.py +0 -0
  27. {botslab360-0.4.1 → botslab360-0.4.2}/tests/test_devices.py +0 -0
  28. {botslab360-0.4.1 → botslab360-0.4.2}/tests/test_protocol.py +0 -0
  29. {botslab360-0.4.1 → botslab360-0.4.2}/tests/test_public_robot360_auth_diagnostic.py +0 -0
  30. {botslab360-0.4.1 → botslab360-0.4.2}/tests/test_public_room_api_diagnostic.py +0 -0
  31. {botslab360-0.4.1 → botslab360-0.4.2}/tests/test_quc.py +0 -0
  32. {botslab360-0.4.1 → botslab360-0.4.2}/tests/test_room_diagnostic.py +0 -0
  33. {botslab360-0.4.1 → botslab360-0.4.2}/tests/test_status.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: botslab360
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: Async Python client for Botslab/360 robot vacuums
5
5
  License-Expression: MIT
6
6
  Project-URL: Source, https://github.com/placix/python-botslab360
@@ -217,12 +217,13 @@ map coordinate system (integer millimetres, original point order). Missing or
217
217
  malformed polygons are exposed as `None`; raw MapInfo data is not exposed.
218
218
 
219
219
  The Android room-attribute UI defines one or two cleaning passes, four suction
220
- modes, and three mopping water levels. Optional settings can override the
221
- current attributes for selected rooms in one cleaning request:
220
+ modes, three mopping water levels, and three cleaning modes. Optional settings
221
+ can override the current attributes for selected rooms in one cleaning request:
222
222
 
223
223
  ```python
224
224
  from botslab360 import (
225
225
  RoomCleaningSettings,
226
+ RoomCleaningMode,
226
227
  RoomFanMode,
227
228
  RoomWaterLevel,
228
229
  )
@@ -232,6 +233,7 @@ await client.clean_rooms(
232
233
  [1],
233
234
  room_settings={
234
235
  1: RoomCleaningSettings(
236
+ mode=RoomCleaningMode.SWEEP,
235
237
  clean_times=2,
236
238
  fan_mode=RoomFanMode.STRONG,
237
239
  water_pump=RoomWaterLevel.MEDIUM,
@@ -248,6 +250,12 @@ Water levels are `1` (low), `2` (medium), and `3` (high). An existing vendor
248
250
  `waterPump=0` value is preserved when not overridden, but `0` is not exposed as
249
251
  an "off" choice because that meaning has not been confirmed.
250
252
 
253
+ Cleaning modes are `1` (sweep and mop), `2` (sweep), and `3` (mop). Mode `2`
254
+ has been verified on a real robot; the assignments for modes `1` and `3` remain
255
+ provisional pending real-device verification. `Room.mode` preserves unknown
256
+ integer values reported by future vendor firmware, while outgoing overrides are
257
+ limited to the currently supported values.
258
+
251
259
  ## Status information
252
260
 
253
261
  Depending on the robot model, status information may include:
@@ -197,12 +197,13 @@ map coordinate system (integer millimetres, original point order). Missing or
197
197
  malformed polygons are exposed as `None`; raw MapInfo data is not exposed.
198
198
 
199
199
  The Android room-attribute UI defines one or two cleaning passes, four suction
200
- modes, and three mopping water levels. Optional settings can override the
201
- current attributes for selected rooms in one cleaning request:
200
+ modes, three mopping water levels, and three cleaning modes. Optional settings
201
+ can override the current attributes for selected rooms in one cleaning request:
202
202
 
203
203
  ```python
204
204
  from botslab360 import (
205
205
  RoomCleaningSettings,
206
+ RoomCleaningMode,
206
207
  RoomFanMode,
207
208
  RoomWaterLevel,
208
209
  )
@@ -212,6 +213,7 @@ await client.clean_rooms(
212
213
  [1],
213
214
  room_settings={
214
215
  1: RoomCleaningSettings(
216
+ mode=RoomCleaningMode.SWEEP,
215
217
  clean_times=2,
216
218
  fan_mode=RoomFanMode.STRONG,
217
219
  water_pump=RoomWaterLevel.MEDIUM,
@@ -228,6 +230,12 @@ Water levels are `1` (low), `2` (medium), and `3` (high). An existing vendor
228
230
  `waterPump=0` value is preserved when not overridden, but `0` is not exposed as
229
231
  an "off" choice because that meaning has not been confirmed.
230
232
 
233
+ Cleaning modes are `1` (sweep and mop), `2` (sweep), and `3` (mop). Mode `2`
234
+ has been verified on a real robot; the assignments for modes `1` and `3` remain
235
+ provisional pending real-device verification. `Room.mode` preserves unknown
236
+ integer values reported by future vendor firmware, while outgoing overrides are
237
+ limited to the currently supported values.
238
+
231
239
  ## Status information
232
240
 
233
241
  Depending on the robot model, status information may include:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "botslab360"
7
- version = "0.4.1"
7
+ version = "0.4.2"
8
8
  description = "Async Python client for Botslab/360 robot vacuums"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -19,6 +19,7 @@ from .models import (
19
19
  QihooCredentials,
20
20
  RobotStatus,
21
21
  Room,
22
+ RoomCleaningMode,
22
23
  RoomCleaningSettings,
23
24
  RoomFanMode,
24
25
  RoomWaterLevel,
@@ -42,6 +43,7 @@ __all__ = [
42
43
  "QucAuthenticationError",
43
44
  "RobotStatus",
44
45
  "Room",
46
+ "RoomCleaningMode",
45
47
  "RoomCleaningSettings",
46
48
  "RoomFanMode",
47
49
  "RoomWaterLevel",
@@ -111,6 +111,7 @@ class Room:
111
111
  fan_mode: str | None
112
112
  water_pump: int | None
113
113
  vertices: tuple[tuple[int, int], ...] | None = None
114
+ mode: int | None = None
114
115
 
115
116
 
116
117
  class RoomFanMode(str, Enum):
@@ -130,6 +131,14 @@ class RoomWaterLevel(IntEnum):
130
131
  HIGH = 3
131
132
 
132
133
 
134
+ class RoomCleaningMode(IntEnum):
135
+ """Cleaning modes supported by the Android room-attribute UI."""
136
+
137
+ SWEEP_AND_MOP = 1
138
+ SWEEP = 2
139
+ MOP = 3
140
+
141
+
133
142
  ROOM_CLEAN_TIMES = (1, 2)
134
143
 
135
144
 
@@ -140,6 +149,7 @@ class RoomCleaningSettings:
140
149
  clean_times: int | None = None
141
150
  fan_mode: str | None = None
142
151
  water_pump: int | None = None
152
+ mode: int | None = None
143
153
 
144
154
 
145
155
  @dataclass(frozen=True, slots=True)
@@ -18,6 +18,7 @@ from .exceptions import ApiError
18
18
  from .models import (
19
19
  ROOM_CLEAN_TIMES,
20
20
  Room,
21
+ RoomCleaningMode,
21
22
  RoomCleaningSettings,
22
23
  RoomFanMode,
23
24
  RoomWaterLevel,
@@ -73,6 +74,7 @@ class RoomMap:
73
74
 
74
75
  _ROOM_FAN_MODE_VALUES = frozenset(mode.value for mode in RoomFanMode)
75
76
  _ROOM_WATER_LEVEL_VALUES = frozenset(level.value for level in RoomWaterLevel)
77
+ _ROOM_CLEANING_MODE_VALUES = frozenset(mode.value for mode in RoomCleaningMode)
76
78
 
77
79
 
78
80
  def validate_room_cleaning_settings(settings: object) -> RoomCleaningSettings:
@@ -99,6 +101,11 @@ def validate_room_cleaning_settings(settings: object) -> RoomCleaningSettings:
99
101
  raise TypeError("water_pump must be an integer")
100
102
  if settings.water_pump not in _ROOM_WATER_LEVEL_VALUES:
101
103
  raise ValueError("water_pump must be 1, 2, or 3")
104
+ if settings.mode is not None:
105
+ if isinstance(settings.mode, bool) or not isinstance(settings.mode, int):
106
+ raise TypeError("mode must be an integer")
107
+ if settings.mode not in _ROOM_CLEANING_MODE_VALUES:
108
+ raise ValueError("mode must be 1, 2, or 3")
102
109
  return settings
103
110
 
104
111
 
@@ -273,6 +280,7 @@ def parse_room_map(map_info: object) -> RoomMap:
273
280
  fan_mode=_optional_str(area.get("windMode")),
274
281
  water_pump=_optional_int(area.get("waterPump")),
275
282
  vertices=_room_vertices(area.get("vertexs")),
283
+ mode=_optional_int(area.get("mode")),
276
284
  )
277
285
  )
278
286
  return RoomMap(
@@ -299,6 +307,8 @@ def _apply_room_settings(
299
307
  area["windMode"] = settings.fan_mode
300
308
  if settings.water_pump is not None:
301
309
  area["waterPump"] = settings.water_pump
310
+ if settings.mode is not None:
311
+ area["mode"] = settings.mode
302
312
 
303
313
 
304
314
  def prepare_area_setting(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: botslab360
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: Async Python client for Botslab/360 robot vacuums
5
5
  License-Expression: MIT
6
6
  Project-URL: Source, https://github.com/placix/python-botslab360
@@ -217,12 +217,13 @@ map coordinate system (integer millimetres, original point order). Missing or
217
217
  malformed polygons are exposed as `None`; raw MapInfo data is not exposed.
218
218
 
219
219
  The Android room-attribute UI defines one or two cleaning passes, four suction
220
- modes, and three mopping water levels. Optional settings can override the
221
- current attributes for selected rooms in one cleaning request:
220
+ modes, three mopping water levels, and three cleaning modes. Optional settings
221
+ can override the current attributes for selected rooms in one cleaning request:
222
222
 
223
223
  ```python
224
224
  from botslab360 import (
225
225
  RoomCleaningSettings,
226
+ RoomCleaningMode,
226
227
  RoomFanMode,
227
228
  RoomWaterLevel,
228
229
  )
@@ -232,6 +233,7 @@ await client.clean_rooms(
232
233
  [1],
233
234
  room_settings={
234
235
  1: RoomCleaningSettings(
236
+ mode=RoomCleaningMode.SWEEP,
235
237
  clean_times=2,
236
238
  fan_mode=RoomFanMode.STRONG,
237
239
  water_pump=RoomWaterLevel.MEDIUM,
@@ -248,6 +250,12 @@ Water levels are `1` (low), `2` (medium), and `3` (high). An existing vendor
248
250
  `waterPump=0` value is preserved when not overridden, but `0` is not exposed as
249
251
  an "off" choice because that meaning has not been confirmed.
250
252
 
253
+ Cleaning modes are `1` (sweep and mop), `2` (sweep), and `3` (mop). Mode `2`
254
+ has been verified on a real robot; the assignments for modes `1` and `3` remain
255
+ provisional pending real-device verification. `Room.mode` preserves unknown
256
+ integer values reported by future vendor firmware, while outgoing overrides are
257
+ limited to the currently supported values.
258
+
251
259
  ## Status information
252
260
 
253
261
  Depending on the robot model, status information may include:
@@ -17,6 +17,7 @@ from botslab360 import (
17
17
  Device,
18
18
  InvalidSessionError,
19
19
  Room,
20
+ RoomCleaningMode,
20
21
  RoomCleaningSettings,
21
22
  RoomFanMode,
22
23
  RoomWaterLevel,
@@ -56,7 +57,7 @@ def smart_area() -> dict:
56
57
  "forbidType": "none",
57
58
  "id": room_id,
58
59
  "material": 0,
59
- "mode": "normal",
60
+ "mode": 2,
60
61
  "name": encoded_name(f"Room {room_id}"),
61
62
  "radius": 0,
62
63
  "relativeRoom": -1,
@@ -143,6 +144,7 @@ def test_map_info_parses_ten_public_rooms() -> None:
143
144
  clean_times=2,
144
145
  fan_mode="max",
145
146
  water_pump=2,
147
+ mode=2,
146
148
  )
147
149
  assert parsed.rooms[9].room_type is None
148
150
  assert parsed.map_id == 1
@@ -213,6 +215,22 @@ def test_room_model_remains_compatible_without_vertices() -> None:
213
215
  room = Room(1, "Bad", None, 2, "max", 0)
214
216
 
215
217
  assert room.vertices is None
218
+ assert room.mode is None
219
+
220
+
221
+ @pytest.mark.parametrize("mode", [1, 2, 3, 99])
222
+ def test_room_mode_parsing_preserves_supported_and_unknown_values(mode: int) -> None:
223
+ payload = map_info()
224
+ payload["smartArea"]["value"][1]["mode"] = mode
225
+
226
+ assert parse_room_map(payload).rooms[1].mode == mode
227
+
228
+
229
+ def test_room_mode_parsing_ignores_malformed_values() -> None:
230
+ payload = map_info()
231
+ payload["smartArea"]["value"][1]["mode"] = "future-mode"
232
+
233
+ assert parse_room_map(payload).rooms[1].mode is None
216
234
 
217
235
 
218
236
  def test_public_room_setting_choices_match_android_values() -> None:
@@ -224,6 +242,19 @@ def test_public_room_setting_choices_match_android_values() -> None:
224
242
  "max",
225
243
  ]
226
244
  assert [level.value for level in RoomWaterLevel] == [1, 2, 3]
245
+ assert [mode.value for mode in RoomCleaningMode] == [1, 2, 3]
246
+
247
+
248
+ @pytest.mark.parametrize("mode", [1, 2, 3])
249
+ def test_prepare_area_setting_accepts_supported_cleaning_modes(mode: int) -> None:
250
+ room_map = parse_room_map(map_info())
251
+
252
+ result = json.loads(
253
+ prepare_area_setting(room_map, [1], {1: RoomCleaningSettings(mode=mode)})
254
+ )
255
+
256
+ assert result["value"][1]["mode"] == mode
257
+ assert result["value"][0] == room_map.area_setting["value"][0]
227
258
 
228
259
 
229
260
  @pytest.mark.parametrize(
@@ -232,13 +263,20 @@ def test_public_room_setting_choices_match_android_values() -> None:
232
263
  (RoomCleaningSettings(clean_times=1), {"cleanTimes": 1}),
233
264
  (RoomCleaningSettings(fan_mode="quiet"), {"windMode": "quiet"}),
234
265
  (RoomCleaningSettings(water_pump=3), {"waterPump": 3}),
266
+ (RoomCleaningSettings(mode=2), {"mode": 2}),
235
267
  (
236
268
  RoomCleaningSettings(
237
269
  clean_times=1,
238
270
  fan_mode=RoomFanMode.AUTO,
239
271
  water_pump=RoomWaterLevel.LOW,
272
+ mode=RoomCleaningMode.SWEEP_AND_MOP,
240
273
  ),
241
- {"cleanTimes": 1, "windMode": "auto", "waterPump": 1},
274
+ {
275
+ "cleanTimes": 1,
276
+ "windMode": "auto",
277
+ "waterPump": 1,
278
+ "mode": 1,
279
+ },
242
280
  ),
243
281
  ],
244
282
  )
@@ -300,6 +338,10 @@ def test_prepare_area_setting_supports_different_selected_room_settings() -> Non
300
338
  (RoomCleaningSettings(water_pump=4), ValueError),
301
339
  (RoomCleaningSettings(water_pump=True), TypeError),
302
340
  (RoomCleaningSettings(water_pump="1"), TypeError), # type: ignore[arg-type]
341
+ (RoomCleaningSettings(mode=0), ValueError),
342
+ (RoomCleaningSettings(mode=4), ValueError),
343
+ (RoomCleaningSettings(mode=True), TypeError),
344
+ (RoomCleaningSettings(mode="2"), TypeError), # type: ignore[arg-type]
303
345
  (object(), TypeError),
304
346
  ],
305
347
  )
@@ -477,13 +519,15 @@ def test_clean_rooms_sends_per_room_overrides_through_public_api(
477
519
  (RoomCleaningSettings(clean_times=1), {"cleanTimes": 1}),
478
520
  (RoomCleaningSettings(fan_mode="quiet"), {"windMode": "quiet"}),
479
521
  (RoomCleaningSettings(water_pump=3), {"waterPump": 3}),
522
+ (RoomCleaningSettings(mode=2), {"mode": 2}),
480
523
  (
481
524
  RoomCleaningSettings(
482
525
  clean_times=1,
483
526
  fan_mode="strong",
484
527
  water_pump=3,
528
+ mode=3,
485
529
  ),
486
- {"cleanTimes": 1, "windMode": "strong", "waterPump": 3},
530
+ {"cleanTimes": 1, "windMode": "strong", "waterPump": 3, "mode": 3},
487
531
  ),
488
532
  ],
489
533
  )
@@ -603,6 +647,9 @@ def test_clean_rooms_preserves_vendor_water_pump_zero(monkeypatch) -> None:
603
647
  ([1], {1: RoomCleaningSettings(water_pump=0)}, ValueError),
604
648
  ([1], {1: RoomCleaningSettings(water_pump=4)}, ValueError),
605
649
  ([1], {1: RoomCleaningSettings(water_pump=True)}, TypeError),
650
+ ([1], {1: RoomCleaningSettings(mode=0)}, ValueError),
651
+ ([1], {1: RoomCleaningSettings(mode=4)}, ValueError),
652
+ ([1], {1: RoomCleaningSettings(mode=True)}, TypeError),
606
653
  ([1], {1: object()}, TypeError),
607
654
  ([1], {6: RoomCleaningSettings(clean_times=1)}, ValueError),
608
655
  ([1], {True: RoomCleaningSettings(clean_times=1)}, TypeError),
File without changes
File without changes
File without changes
File without changes