pymammotion 0.5.27__py3-none-any.whl → 0.5.44__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.

Potentially problematic release.


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

Files changed (56) hide show
  1. pymammotion/__init__.py +3 -3
  2. pymammotion/aliyun/client.py +3 -0
  3. pymammotion/aliyun/cloud_gateway.py +117 -19
  4. pymammotion/aliyun/model/dev_by_account_response.py +198 -20
  5. pymammotion/const.py +3 -0
  6. pymammotion/data/model/device.py +1 -0
  7. pymammotion/data/model/device_config.py +1 -1
  8. pymammotion/data/model/enums.py +5 -3
  9. pymammotion/data/model/generate_route_information.py +2 -2
  10. pymammotion/data/model/hash_list.py +113 -33
  11. pymammotion/data/model/region_data.py +4 -4
  12. pymammotion/data/{state_manager.py → mower_state_manager.py} +17 -7
  13. pymammotion/data/mqtt/event.py +47 -22
  14. pymammotion/data/mqtt/mammotion_properties.py +257 -0
  15. pymammotion/data/mqtt/properties.py +32 -29
  16. pymammotion/data/mqtt/status.py +17 -16
  17. pymammotion/homeassistant/__init__.py +3 -0
  18. pymammotion/homeassistant/mower_api.py +446 -0
  19. pymammotion/homeassistant/rtk_api.py +54 -0
  20. pymammotion/http/http.py +431 -18
  21. pymammotion/http/model/http.py +82 -2
  22. pymammotion/http/model/response_factory.py +10 -4
  23. pymammotion/mammotion/commands/mammotion_command.py +20 -0
  24. pymammotion/mammotion/commands/messages/navigation.py +10 -6
  25. pymammotion/mammotion/commands/messages/system.py +0 -14
  26. pymammotion/mammotion/devices/__init__.py +27 -3
  27. pymammotion/mammotion/devices/base.py +22 -146
  28. pymammotion/mammotion/devices/mammotion.py +367 -206
  29. pymammotion/mammotion/devices/mammotion_bluetooth.py +8 -5
  30. pymammotion/mammotion/devices/mammotion_cloud.py +47 -83
  31. pymammotion/mammotion/devices/mammotion_mower_ble.py +49 -0
  32. pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
  33. pymammotion/mammotion/devices/managers/managers.py +81 -0
  34. pymammotion/mammotion/devices/mower_device.py +121 -0
  35. pymammotion/mammotion/devices/mower_manager.py +107 -0
  36. pymammotion/mammotion/devices/rtk_ble.py +89 -0
  37. pymammotion/mammotion/devices/rtk_cloud.py +113 -0
  38. pymammotion/mammotion/devices/rtk_device.py +50 -0
  39. pymammotion/mammotion/devices/rtk_manager.py +122 -0
  40. pymammotion/mqtt/__init__.py +2 -1
  41. pymammotion/mqtt/aliyun_mqtt.py +232 -0
  42. pymammotion/mqtt/mammotion_mqtt.py +174 -192
  43. pymammotion/mqtt/mqtt_models.py +66 -0
  44. pymammotion/proto/__init__.py +2 -2
  45. pymammotion/proto/mctrl_nav.proto +2 -2
  46. pymammotion/proto/mctrl_nav_pb2.py +1 -1
  47. pymammotion/proto/mctrl_nav_pb2.pyi +4 -4
  48. pymammotion/proto/mctrl_sys.proto +1 -1
  49. pymammotion/utility/datatype_converter.py +13 -12
  50. pymammotion/utility/device_type.py +88 -3
  51. pymammotion/utility/mur_mur_hash.py +132 -87
  52. {pymammotion-0.5.27.dist-info → pymammotion-0.5.44.dist-info}/METADATA +25 -30
  53. {pymammotion-0.5.27.dist-info → pymammotion-0.5.44.dist-info}/RECORD +61 -47
  54. {pymammotion-0.5.27.dist-info → pymammotion-0.5.44.dist-info}/WHEEL +1 -1
  55. pymammotion/http/_init_.py +0 -0
  56. {pymammotion-0.5.27.dist-info → pymammotion-0.5.44.dist-info/licenses}/LICENSE +0 -0
@@ -7,19 +7,20 @@ T = TypeVar("T")
7
7
 
8
8
  def deserialize_data(value, target_type):
9
9
  """Deserialize data into a specified target type.
10
-
10
+
11
11
  The function handles deserialization of basic types, lists, and unions. It
12
12
  recursively processes list elements and supports optional types by handling
13
13
  Union[T, None]. For custom types with a `from_dict` method, it calls this
14
14
  method for deserialization. If the target type is unknown or unsupported, it
15
15
  returns the value unchanged.
16
-
16
+
17
17
  Args:
18
18
  value: The data to be deserialized.
19
19
  target_type (type): The desired type into which the data should be deserialized.
20
-
20
+
21
21
  Returns:
22
22
  The deserialized data in the specified target type.
23
+
23
24
  """
24
25
  if value is None:
25
26
  return None
@@ -35,7 +36,12 @@ def deserialize_data(value, target_type):
35
36
  # Support Optional[T] = Union[T, None]
36
37
  non_none_types = [t for t in args if t is not type(None)]
37
38
  if len(non_none_types) == 1:
38
- return deserialize_data(value, non_none_types[0])
39
+ target = non_none_types[0]
40
+ # Handle Response[list[type]] case
41
+ if get_origin(target) is list and get_args(target):
42
+ item_type = get_args(target)[0]
43
+ return [deserialize_data(v, item_type) for v in value]
44
+ return deserialize_data(value, target)
39
45
 
40
46
  if hasattr(target_type, "from_dict"):
41
47
  return target_type.from_dict(value)
@@ -30,6 +30,26 @@ class MammotionCommand(
30
30
  return self.allpowerfull_rw_adapter_x3(rw_id, context, rw)
31
31
  return self.allpowerfull_rw(rw_id, context, rw)
32
32
 
33
+ def traverse_mode(self, context: int) -> bytes:
34
+ """Sets the traversal mode back to charger."""
35
+ # setReChargeMode
36
+ # 0 direct
37
+ # 1 follow the perimeter
38
+ return self.read_write_device(7, context, 1)
39
+
40
+ def turning_mode(self, context: int) -> bytes:
41
+ """Sets the traversal mode back to charger."""
42
+ # setTurnAroundMode
43
+ # 0 zero turn
44
+ # 1 multipoint turn
45
+ return self.read_write_device(6, context, 1)
46
+
47
+ def get_error_code(self) -> bytes:
48
+ return self.read_write_device(5, 2, 1)
49
+
50
+ def get_error_timestamp(self) -> bytes:
51
+ return self.read_write_device(5, 3, 1)
52
+
33
53
  def get_device_product_key(self) -> str:
34
54
  return self._product_key
35
55
 
@@ -33,7 +33,7 @@ logger = logging.getLogger(__name__)
33
33
 
34
34
 
35
35
  class MessageNavigation(AbstractMessage, ABC):
36
- def send_order_msg_nav(self, build) -> bytes:
36
+ def send_order_msg_nav(self, build: MctlNav) -> bytes:
37
37
  luba_msg = LubaMsg(
38
38
  msgtype=MsgCmdType.NAV,
39
39
  sender=MsgDevice.DEV_MOBILEAPP,
@@ -206,9 +206,9 @@ class MessageNavigation(AbstractMessage, ABC):
206
206
  reserved=plan_bean.reserved,
207
207
  weeks=plan_bean.weeks,
208
208
  start_date=plan_bean.start_date,
209
- trigger_type=plan_bean.job_type,
210
- day=plan_bean.interval_days,
211
- toward_included_angle=plan_bean.demond_angle,
209
+ trigger_type=plan_bean.trigger_type,
210
+ day=plan_bean.day,
211
+ toward_included_angle=plan_bean.toward_included_angle,
212
212
  toward_mode=0,
213
213
  )
214
214
  logger.debug(f"Send read job plan command planBean={plan_bean}")
@@ -262,7 +262,7 @@ class MessageNavigation(AbstractMessage, ABC):
262
262
  toapp_map_name_msg=NavMapNameMsg(
263
263
  hash=0,
264
264
  result=0,
265
- device_id=device_id, # iotId or ???
265
+ device_id=device_id, # iot_id
266
266
  rw=0,
267
267
  )
268
268
  )
@@ -390,7 +390,7 @@ class MessageNavigation(AbstractMessage, ABC):
390
390
  generate_route_information}")
391
391
  return self.send_order_msg_nav(MctlNav(bidire_reqconver_path=build))
392
392
 
393
- def modify_generate_route_information(self, generate_route_information: GenerateRouteInformation) -> bytes:
393
+ def modify_route_information(self, generate_route_information: GenerateRouteInformation) -> bytes:
394
394
  logger.debug(f"Generate route data source: {generate_route_information}")
395
395
  build = NavReqCoverPath(
396
396
  pver=1,
@@ -432,7 +432,11 @@ class MessageNavigation(AbstractMessage, ABC):
432
432
  return self.send_order_msg_nav(build)
433
433
 
434
434
  def get_line_info_list(self, hash_list: list[int], transaction_id: int) -> bytes:
435
+ """Get route information (mow path) corresponding to the specified hash list based on time.
436
+ e.g transaction_id = int(time.time() * 1000)
437
+ """
435
438
  logger.debug(f"Sending==========Get route command: {hash_list}")
439
+
436
440
  build = MctlNav(
437
441
  app_request_cover_paths=AppRequestCoverPathsT(
438
442
  pver=1, hash_list=hash_list, transaction_id=transaction_id, sub_cmd=0
@@ -119,20 +119,6 @@ class MessageSystem(AbstractMessage, ABC):
119
119
  logger.debug(f"Send command - 9 general read and write command id={rw_id}, context={context}, rw={rw}")
120
120
  return self.send_order_msg_sys(build)
121
121
 
122
- def traverse_mode(self, context: int) -> bytes:
123
- """Sets the traversal mode back to charger."""
124
- # setReChargeMode
125
- # 0 direct
126
- # 1 follow the perimeter
127
- return self.allpowerfull_rw(7, context, 1)
128
-
129
- def turning_mode(self, context: int) -> bytes:
130
- """Sets the traversal mode back to charger."""
131
- # setTurnAroundMode
132
- # 0 zero turn
133
- # 1 multipoint turn
134
- return self.allpowerfull_rw(6, context, 1)
135
-
136
122
  # Commented out as not needed and too many refs to try fix up
137
123
  # def factory_test_order(self, test_id: int, test_duration: int, expect: str):
138
124
  # new_builder = mow_to_app_qctools_info_t.Builder()
@@ -1,5 +1,29 @@
1
- """mqtt init."""
1
+ """Mammotion devices module."""
2
2
 
3
- from .mammotion import MammotionBaseBLEDevice
3
+ from .mammotion import Mammotion, MammotionDeviceManager
4
+ from .mammotion_bluetooth import MammotionBaseBLEDevice
5
+ from .mammotion_cloud import MammotionBaseCloudDevice, MammotionCloud
6
+ from .mammotion_mower_ble import MammotionMowerBLEDevice
7
+ from .mammotion_mower_cloud import MammotionMowerCloudDevice
8
+ from .mower_device import MammotionMowerDevice
9
+ from .mower_manager import MammotionMowerDeviceManager
10
+ from .rtk_ble import MammotionRTKBLEDevice
11
+ from .rtk_cloud import MammotionRTKCloudDevice
12
+ from .rtk_device import MammotionRTKDevice
13
+ from .rtk_manager import MammotionRTKDeviceManager
4
14
 
5
- __all__ = ["MammotionBaseBLEDevice"]
15
+ __all__ = [
16
+ "Mammotion",
17
+ "MammotionDeviceManager",
18
+ "MammotionMowerDeviceManager",
19
+ "MammotionBaseBLEDevice",
20
+ "MammotionBaseCloudDevice",
21
+ "MammotionCloud",
22
+ "MammotionMowerBLEDevice",
23
+ "MammotionMowerCloudDevice",
24
+ "MammotionMowerDevice",
25
+ "MammotionRTKBLEDevice",
26
+ "MammotionRTKCloudDevice",
27
+ "MammotionRTKDevice",
28
+ "MammotionRTKDeviceManager",
29
+ ]
@@ -1,41 +1,23 @@
1
- from abc import abstractmethod
1
+ from abc import ABC, abstractmethod
2
2
  import asyncio
3
3
  import logging
4
- import time
5
4
  from typing import Any
6
5
 
7
6
  import betterproto2
8
7
 
9
8
  from pymammotion.aliyun.model.dev_by_account_response import Device
10
- from pymammotion.data.model import RegionData
11
9
  from pymammotion.data.model.device import MowingDevice
12
10
  from pymammotion.data.model.raw_data import RawMowerData
13
- from pymammotion.data.state_manager import StateManager
14
- from pymammotion.proto import LubaMsg, NavGetCommDataAck, NavGetHashListAck, NavPlanJobSet, SvgMessageAckT
15
- from pymammotion.utility.device_type import DeviceType
11
+ from pymammotion.data.mower_state_manager import MowerStateManager
12
+ from pymammotion.proto import LubaMsg
16
13
 
17
14
  _LOGGER = logging.getLogger(__name__)
18
15
 
19
16
 
20
- def find_next_integer(lst: list[int], current_hash: int) -> int | None:
21
- try:
22
- # Find the index of the current integer
23
- current_index = lst.index(current_hash)
24
-
25
- # Check if there is a next integer in the list
26
- if current_index + 1 < len(lst):
27
- return lst[current_index + 1]
28
- else:
29
- return None # Or raise an exception or handle it in some other way
30
- except ValueError:
31
- # Handle the case where current_int is not in the list
32
- return None # Or raise an exception or handle it in some other way
33
-
34
-
35
- class MammotionBaseDevice:
17
+ class MammotionBaseDevice(ABC):
36
18
  """Base class for Mammotion devices."""
37
19
 
38
- def __init__(self, state_manager: StateManager, cloud_device: Device) -> None:
20
+ def __init__(self, state_manager: MowerStateManager, cloud_device: Device) -> None:
39
21
  """Initialize MammotionBaseDevice."""
40
22
  self.loop = asyncio.get_event_loop()
41
23
  self._state_manager = state_manager
@@ -43,58 +25,6 @@ class MammotionBaseDevice:
43
25
  self._raw_mower_data: RawMowerData = RawMowerData()
44
26
  self._notify_future: asyncio.Future[bytes] | None = None
45
27
  self._cloud_device = cloud_device
46
- self.command_sent_time: float = time.time()
47
-
48
- async def datahash_response(self, hash_ack: NavGetHashListAck) -> None:
49
- """Handle datahash responses for root level hashs."""
50
- current_frame = hash_ack.current_frame
51
-
52
- missing_frames = self.mower.map.missing_root_hash_frame(hash_ack)
53
- if len(missing_frames) == 0:
54
- if len(self.mower.map.missing_hashlist(0)) > 0:
55
- data_hash = self.mower.map.missing_hashlist(hash_ack.sub_cmd).pop()
56
- await self.queue_command("synchronize_hash_data", hash_num=data_hash)
57
- return
58
-
59
- if current_frame != missing_frames[0] - 1:
60
- current_frame = missing_frames[0] - 1
61
- await self.queue_command("get_hash_response", total_frame=hash_ack.total_frame, current_frame=current_frame)
62
-
63
- async def commdata_response(self, common_data: NavGetCommDataAck | SvgMessageAckT) -> None:
64
- """Handle common data responses."""
65
- total_frame = common_data.total_frame
66
- current_frame = common_data.current_frame
67
-
68
- missing_frames = self.mower.map.missing_frame(common_data)
69
- if len(missing_frames) == 0:
70
- # get next in hash ack list
71
-
72
- data_hash = (
73
- self.mower.map.missing_hashlist(common_data.sub_cmd).pop()
74
- if len(self.mower.map.missing_hashlist(common_data.sub_cmd)) > 0
75
- else None
76
- )
77
- if data_hash is None:
78
- return
79
-
80
- await self.queue_command("synchronize_hash_data", hash_num=data_hash)
81
- else:
82
- if current_frame != missing_frames[0] - 1:
83
- current_frame = missing_frames[0] - 1
84
-
85
- region_data = RegionData()
86
- region_data.hash = common_data.data_hash if isinstance(common_data, SvgMessageAckT) else common_data.hash
87
- region_data.action = common_data.action if isinstance(common_data, NavGetCommDataAck) else None
88
- region_data.type = common_data.type
89
- region_data.sub_cmd = common_data.sub_cmd
90
- region_data.total_frame = total_frame
91
- region_data.current_frame = current_frame
92
- await self.queue_command("get_regional_data", regional_data=region_data)
93
-
94
- async def plan_callback(self, plan: NavPlanJobSet) -> None:
95
- if plan.plan_index < plan.total_plan_num - 1:
96
- index = plan.plan_index + 1
97
- await self.queue_command("read_plan", sub_cmd=2, plan_index=index)
98
28
 
99
29
  def _update_raw_data(self, data: bytes) -> None:
100
30
  """Update raw and model data from notifications."""
@@ -129,7 +59,7 @@ class MammotionBaseDevice:
129
59
  nav[nav_sub_msg[0]] = nav_sub_msg[1].to_dict(casing=betterproto2.Casing.SNAKE)
130
60
  self._raw_data["nav"] = nav
131
61
 
132
- def _update_sys_data(self, tmp_msg) -> None:
62
+ def _update_sys_data(self, tmp_msg: LubaMsg) -> None:
133
63
  """Update system data."""
134
64
  sys_sub_msg = betterproto2.which_one_of(tmp_msg.sys, "SubSysMsg")
135
65
  if sys_sub_msg[1] is None:
@@ -139,7 +69,7 @@ class MammotionBaseDevice:
139
69
  sys[sys_sub_msg[0]] = sys_sub_msg[1].to_dict(casing=betterproto2.Casing.SNAKE)
140
70
  self._raw_data["sys"] = sys
141
71
 
142
- def _update_driver_data(self, tmp_msg) -> None:
72
+ def _update_driver_data(self, tmp_msg: LubaMsg) -> None:
143
73
  """Update driver data."""
144
74
  drv_sub_msg = betterproto2.which_one_of(tmp_msg.driver, "SubDrvMsg")
145
75
  if drv_sub_msg[1] is None:
@@ -149,7 +79,7 @@ class MammotionBaseDevice:
149
79
  drv[drv_sub_msg[0]] = drv_sub_msg[1].to_dict(casing=betterproto2.Casing.SNAKE)
150
80
  self._raw_data["driver"] = drv
151
81
 
152
- def _update_net_data(self, tmp_msg) -> None:
82
+ def _update_net_data(self, tmp_msg: LubaMsg) -> None:
153
83
  """Update network data."""
154
84
  net_sub_msg = betterproto2.which_one_of(tmp_msg.net, "NetSubType")
155
85
  if net_sub_msg[1] is None:
@@ -162,7 +92,7 @@ class MammotionBaseDevice:
162
92
  net[net_sub_msg[0]] = net_sub_msg[1].to_dict(casing=betterproto2.Casing.SNAKE)
163
93
  self._raw_data["net"] = net
164
94
 
165
- def _update_mul_data(self, tmp_msg) -> None:
95
+ def _update_mul_data(self, tmp_msg: LubaMsg) -> None:
166
96
  """Update mul data."""
167
97
  mul_sub_msg = betterproto2.which_one_of(tmp_msg.mul, "SubMul")
168
98
  if mul_sub_msg[1] is None:
@@ -172,7 +102,7 @@ class MammotionBaseDevice:
172
102
  mul[mul_sub_msg[0]] = mul_sub_msg[1].to_dict(casing=betterproto2.Casing.SNAKE)
173
103
  self._raw_data["mul"] = mul
174
104
 
175
- def _update_ota_data(self, tmp_msg) -> None:
105
+ def _update_ota_data(self, tmp_msg: LubaMsg) -> None:
176
106
  """Update OTA data."""
177
107
  ota_sub_msg = betterproto2.which_one_of(tmp_msg.ota, "SubOtaMsg")
178
108
  if ota_sub_msg[1] is None:
@@ -193,81 +123,27 @@ class MammotionBaseDevice:
193
123
  return self._state_manager.get_device()
194
124
 
195
125
  @abstractmethod
196
- async def queue_command(self, key: str, **kwargs: any) -> bytes | None:
126
+ async def queue_command(self, key: str, **kwargs: Any) -> None:
197
127
  """Queue commands to mower."""
198
128
 
199
129
  @abstractmethod
200
- async def _ble_sync(self):
130
+ async def _ble_sync(self) -> None:
201
131
  """Send ble sync command every 3 seconds or sooner."""
202
132
 
203
133
  @abstractmethod
204
- def stop(self):
134
+ def stop(self) -> None:
205
135
  """Stop everything ready for destroying."""
206
136
 
207
- async def start_sync(self, retry: int) -> None:
208
- """Start synchronization with the device."""
209
- await self.queue_command("get_device_base_info")
210
- await self.queue_command("get_device_product_model")
211
- await self.queue_command("get_report_cfg")
212
- """RTK and dock location."""
213
- await self.queue_command("allpowerfull_rw", rw_id=5, context=1, rw=1)
214
- await self.async_read_settings()
215
-
216
- async def start_map_sync(self) -> None:
217
- """Start sync of map data."""
218
-
219
- self.mower.map.update_hash_lists(self.mower.map.hashlist)
220
-
221
- await self.queue_command("send_todev_ble_sync", sync_type=3)
222
-
223
- if self._cloud_device and len(self.mower.map.area_name) == 0 and not DeviceType.is_luba1(self.mower.name):
224
- await self.queue_command("get_area_name_list", device_id=self._cloud_device.iotId)
225
-
226
- if len(self.mower.map.plan) == 0 or list(self.mower.map.plan.values())[0].total_plan_num != len(
227
- self.mower.map.plan
228
- ):
229
- await self.queue_command("read_plan", sub_cmd=2, plan_index=0)
230
-
231
- for hash_id, frame in list(self.mower.map.area.items()):
232
- missing_frames = self.mower.map.find_missing_frames(frame)
233
- if len(missing_frames) > 0:
234
- del self.mower.map.area[hash_id]
235
-
236
- for hash_id, frame in list(self.mower.map.path.items()):
237
- missing_frames = self.mower.map.find_missing_frames(frame)
238
- if len(missing_frames) > 0:
239
- del self.mower.map.path[hash_id]
240
-
241
- for hash_id, frame in list(self.mower.map.obstacle.items()):
242
- missing_frames = self.mower.map.find_missing_frames(frame)
243
- if len(missing_frames) > 0:
244
- del self.mower.map.obstacle[hash_id]
245
-
246
- # don't know why but total frame on svg is wrong
247
- # for hash, frame in self.mower.map.svg.items():
248
- # missing_frames = self.mower.map.find_missing_frames(frame)
249
- # if len(missing_frames) > 0:
250
- # del self.mower.map.svg[hash]
251
-
252
- if len(self.mower.map.root_hash_lists) == 0 or len(self.mower.map.missing_hashlist()) > 0:
253
- await self.queue_command("get_all_boundary_hash_list", sub_cmd=0)
254
-
255
- # sub_cmd 3 is job hashes??
256
- # sub_cmd 4 is dump location (yuka)
257
- # jobs list
258
- #
259
- # await self.queue_command("get_all_boundary_hash_list", sub_cmd=3)
260
-
261
137
  async def async_read_settings(self) -> None:
262
138
  """Read settings from device."""
263
139
  # no cutting in rain nav_sys_param_cmd (id 3 context 1/0)
264
- await self.queue_command("allpowerfull_rw", rw_id=3, context=1, rw=0)
140
+ await self.queue_command("read_write_device", rw_id=3, context=1, rw=0)
265
141
  # ??
266
- await self.queue_command("allpowerfull_rw", rw_id=4, context=1, rw=0)
142
+ await self.queue_command("read_write_device", rw_id=4, context=1, rw=0)
267
143
  # turning mode nav_sys_param_cmd (id 6, context 1/0)
268
- await self.queue_command("allpowerfull_rw", rw_id=6, context=1, rw=0)
144
+ await self.queue_command("read_write_device", rw_id=6, context=1, rw=0)
269
145
  # traversal mode
270
- await self.queue_command("allpowerfull_rw", rw_id=7, context=1, rw=0)
146
+ await self.queue_command("read_write_device", rw_id=7, context=1, rw=0)
271
147
 
272
148
  await self.queue_command("read_and_set_sidelight", is_sidelight=True, operate=1)
273
149
 
@@ -275,13 +151,13 @@ class MammotionBaseDevice:
275
151
 
276
152
  async def async_get_errors(self) -> None:
277
153
  """Error codes."""
278
- await self.queue_command("allpowerfull_rw", rw_id=5, rw=1, context=2)
279
- await self.queue_command("allpowerfull_rw", rw_id=5, rw=1, context=3)
154
+ await self.queue_command("read_write_device", rw_id=5, rw=1, context=2)
155
+ await self.queue_command("read_write_device", rw_id=5, rw=1, context=3)
280
156
 
281
- async def command(self, key: str, **kwargs):
157
+ async def command(self, key: str, **kwargs: Any) -> None:
282
158
  """Send a command to the device."""
283
- return await self.queue_command(key, **kwargs)
159
+ await self.queue_command(key, **kwargs)
284
160
 
285
161
  @property
286
- def state_manager(self):
162
+ def state_manager(self) -> MowerStateManager:
287
163
  return self._state_manager