pymammotion 0.2.35__py3-none-any.whl → 0.2.36__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.
@@ -586,6 +586,7 @@ class CloudIOTGateway:
586
586
  request=request,
587
587
  version="1.0",
588
588
  )
589
+ logger.debug(self.converter.printBase64Binary(command))
589
590
 
590
591
  # send request
591
592
  response = client.do_request("/thing/service/invoke", "https", "POST", None, body, RuntimeOptions())
@@ -1,6 +1,6 @@
1
1
  """Manage state from notifications into MowingDevice."""
2
2
 
3
- from typing import Awaitable, Callable, Optional
3
+ from typing import Any, Awaitable, Callable, Optional
4
4
 
5
5
  import betterproto
6
6
 
@@ -19,6 +19,7 @@ class StateManager:
19
19
  self.gethash_ack_callback: Optional[Callable[[NavGetHashListAck], Awaitable[None]]] = None
20
20
  self.get_commondata_ack_callback: Optional[Callable[[NavGetCommDataAck], Awaitable[None]]] = None
21
21
  self.on_notification_callback: Optional[Callable[[], Awaitable[None]]] = None
22
+ self.queue_command_callback: Optional[Callable[[str, dict[str, Any]], Awaitable[bytes]]] = None
22
23
 
23
24
  def get_device(self) -> MowingDevice:
24
25
  """Get device."""
@@ -36,7 +37,7 @@ class StateManager:
36
37
  case "nav":
37
38
  await self._update_nav_data(message)
38
39
  case "sys":
39
- self._update_sys_data(message)
40
+ await self._update_sys_data(message)
40
41
  case "driver":
41
42
  self._update_driver_data(message)
42
43
  case "net":
@@ -66,7 +67,7 @@ class StateManager:
66
67
  hash_names: AppGetAllAreaHashName = nav_msg[1]
67
68
  self._device.map.area_name = hash_names.hashnames
68
69
 
69
- def _update_sys_data(self, message) -> None:
70
+ async def _update_sys_data(self, message) -> None:
70
71
  """Update system."""
71
72
  sys_msg = betterproto.which_one_of(message.sys, "SubSysMsg")
72
73
  match sys_msg[0]:
@@ -74,6 +75,8 @@ class StateManager:
74
75
  self._device.buffer(sys_msg[1])
75
76
  case "toapp_report_data":
76
77
  self._device.update_report_data(sys_msg[1])
78
+ if self.queue_command_callback:
79
+ await self.queue_command_callback("get_report_cfg", stop=True)
77
80
  case "mow_to_app_info":
78
81
  self._device.mow_info(sys_msg[1])
79
82
  case "system_tard_state_tunnel":
@@ -4,8 +4,20 @@ from abc import ABC
4
4
 
5
5
  from pymammotion.mammotion.commands.abstract_message import AbstractMessage
6
6
  from pymammotion.mammotion.commands.messages.navigation import MessageNavigation
7
- from pymammotion.proto import luba_msg_pb2, mctrl_sys, mctrl_sys_pb2
8
- from pymammotion.proto.mctrl_sys import RptInfoType
7
+ from pymammotion.proto.luba_msg import LubaMsg, MsgAttr, MsgCmdType, MsgDevice
8
+ from pymammotion.proto.mctrl_sys import (
9
+ DeviceProductTypeInfoT,
10
+ LoraCfgReq,
11
+ MctlSys,
12
+ MCtrlSimulationCmdData,
13
+ ReportInfoCfg,
14
+ RptAct,
15
+ RptInfoType,
16
+ SysCommCmd,
17
+ SysKnifeControl,
18
+ SysSetDateTime,
19
+ TimeCtrlLight,
20
+ )
9
21
  from pymammotion.utility.device_type import DeviceType
10
22
 
11
23
 
@@ -13,37 +25,35 @@ class MessageSystem(AbstractMessage, ABC):
13
25
  messageNavigation: MessageNavigation = MessageNavigation()
14
26
 
15
27
  def send_order_msg_sys(self, sys):
16
- luba_msg = luba_msg_pb2.LubaMsg(
17
- msgtype=luba_msg_pb2.MSG_CMD_TYPE_EMBED_SYS,
18
- sender=luba_msg_pb2.DEV_MOBILEAPP,
19
- rcver=luba_msg_pb2.DEV_MAINCTL,
28
+ luba_msg = LubaMsg(
29
+ msgtype=MsgCmdType.MSG_CMD_TYPE_EMBED_SYS,
30
+ sender=MsgDevice.DEV_MOBILEAPP,
31
+ rcver=MsgDevice.DEV_MAINCTL,
20
32
  sys=sys,
21
33
  )
22
34
 
23
35
  return luba_msg.SerializeToString()
24
36
 
25
37
  def reset_system(self):
26
- build = mctrl_sys_pb2.MctlSys(todev_reset_system=1)
38
+ build = MctlSys(todev_reset_system=1)
27
39
  print("Send command - send factory reset")
28
40
  return self.send_order_msg_sys(build)
29
41
 
30
42
  def set_blade_control(self, on_off: int):
31
- mctlsys = mctrl_sys_pb2.MctlSys()
32
- sys_knife_control = mctrl_sys_pb2.SysKnifeControl()
43
+ mctlsys = MctlSys()
44
+ sys_knife_control = SysKnifeControl()
33
45
  sys_knife_control.knife_status = on_off
34
- mctlsys.todev_knife_ctrl.CopyFrom(sys_knife_control)
46
+ mctlsys.todev_knife_ctrl = sys_knife_control
35
47
 
36
48
  return self.send_order_msg_sys(mctlsys)
37
49
 
38
50
  def get_device_product_model(self):
39
- return self.send_order_msg_sys(
40
- mctrl_sys_pb2.MctlSys(device_product_type_info=mctrl_sys_pb2.device_product_type_info_t())
41
- )
51
+ return self.send_order_msg_sys(MctlSys(device_product_type_info=DeviceProductTypeInfoT()))
42
52
 
43
53
  def read_and_set_sidelight(self, is_sidelight: bool, operate: int):
44
54
  """Read state of sidelight as well as set it."""
45
55
  if is_sidelight:
46
- build = mctrl_sys_pb2.TimeCtrlLight(
56
+ build = TimeCtrlLight(
47
57
  operate=operate,
48
58
  enable=0,
49
59
  action=0,
@@ -53,7 +63,7 @@ class MessageSystem(AbstractMessage, ABC):
53
63
  end_min=0,
54
64
  )
55
65
  else:
56
- build = mctrl_sys_pb2.TimeCtrlLight(
66
+ build = TimeCtrlLight(
57
67
  operate=operate,
58
68
  enable=1,
59
69
  action=0,
@@ -64,16 +74,16 @@ class MessageSystem(AbstractMessage, ABC):
64
74
  )
65
75
  print(f"Send read and write sidelight command is_sidelight:{
66
76
  is_sidelight}, operate:{operate}")
67
- build2 = mctrl_sys_pb2.MctlSys(todev_time_ctrl_light=build)
77
+ build2 = MctlSys(todev_time_ctrl_light=build)
68
78
  print(f"Send command - send read and write sidelight command is_sidelight:{
69
79
  is_sidelight}, operate:{operate}, timeCtrlLight:{build}")
70
80
  return self.send_order_msg_sys(build2)
71
81
 
72
82
  def test_tool_order_to_sys(self, sub_cmd: int, param_id: int, param_value: list[int]):
73
- build = mctrl_sys_pb2.mCtrlSimulationCmdData(sub_cmd=sub_cmd, param_id=param_id, param_value=param_value)
83
+ build = MCtrlSimulationCmdData(sub_cmd=sub_cmd, param_id=param_id, param_value=param_value)
74
84
  print(f"Send tool test command: subCmd={sub_cmd}, param_id:{
75
85
  param_id}, param_value={param_value}")
76
- build2 = mctrl_sys_pb2.MctlSys(simulation_cmd=build)
86
+ build2 = MctlSys(simulation_cmd=build)
77
87
  print(f"Send tool test command: subCmd={sub_cmd}, param_id:{
78
88
  param_id}, param_value={param_value}")
79
89
  return self.send_order_msg_sys(build2)
@@ -81,15 +91,13 @@ class MessageSystem(AbstractMessage, ABC):
81
91
  def read_and_set_rtk_paring_code(self, op: int, cgf: str):
82
92
  print(f"Send read and write base station configuration quality op:{
83
93
  op}, cgf:{cgf}")
84
- return self.send_order_msg_sys(
85
- mctrl_sys_pb2.MctlSys(todev_lora_cfg_req=mctrl_sys_pb2.LoraCfgReq(op=op, cfg=cgf))
86
- )
94
+ return self.send_order_msg_sys(MctlSys(todev_lora_cfg_req=LoraCfgReq(op=op, cfg=cgf)))
87
95
 
88
96
  def allpowerfull_rw(self, id: int, context: int, rw: int):
89
97
  if (id == 6 or id == 3 or id == 7) and DeviceType.is_luba_2(self.get_device_name()):
90
98
  self.messageNavigation.allpowerfull_rw_adapter_x3(id, context, rw)
91
99
  return
92
- build = mctrl_sys_pb2.MctlSys(bidire_comm_cmd=mctrl_sys_pb2.SysCommCmd(id=id, context=context, rw=rw))
100
+ build = MctlSys(bidire_comm_cmd=SysCommCmd(id=id, context=context, rw=rw))
93
101
  print(f"Send command - 9 general read and write command id={id}, context={context}, rw={rw}")
94
102
  if id == 5:
95
103
  # This logic doesnt make snese, but its what they had so..
@@ -98,7 +106,7 @@ class MessageSystem(AbstractMessage, ABC):
98
106
 
99
107
  # Commented out as not needed and too many refs to try fix up
100
108
  # def factory_test_order(self, test_id: int, test_duration: int, expect: str):
101
- # new_builder = mctrl_sys_pb2.mow_to_app_qctools_info_t.Builder()
109
+ # new_builder = mow_to_app_qctools_info_t.Builder()
102
110
  # print(f"Factory tool print, expect={expect}")
103
111
  # if not expect:
104
112
  # build = new_builder.set_type_value(
@@ -108,7 +116,7 @@ class MessageSystem(AbstractMessage, ABC):
108
116
  # json_array = json.loads(expect)
109
117
  # z2 = True
110
118
  # for i in range(len(json_array)):
111
- # new_builder2 = mctrl_sys_pb2.QCAppTestExcept.Builder()
119
+ # new_builder2 = QCAppTestExcept.Builder()
112
120
  # json_object = json_array[i]
113
121
  # if "except_type" in json_object:
114
122
  # string = json_object["except_type"]
@@ -116,7 +124,7 @@ class MessageSystem(AbstractMessage, ABC):
116
124
  # json_array2 = json_object["conditions"]
117
125
  # for i2 in range(len(json_array2)):
118
126
  # json_object2 = json_array2[i2]
119
- # new_builder3 = mctrl_sys_pb2.QCAppTestConditions.Builder()
127
+ # new_builder3 = QCAppTestConditions.Builder()
120
128
  # if "cond_type" in json_object2:
121
129
  # new_builder3.set_cond_type(
122
130
  # json_object2["cond_type"])
@@ -151,7 +159,7 @@ class MessageSystem(AbstractMessage, ABC):
151
159
  # test_id).set_time_of_duration(test_duration).build()
152
160
  # print(f"Factory tool print, mow_to_app_qctools_info_t={
153
161
  # build.except_count}, mow_to_app_qctools_info_t22={build.except_list}")
154
- # build2 = mctrl_sys_pb2.MctlSys(mow_to_app_qctools_info=build)
162
+ # build2 = MctlSys(mow_to_app_qctools_info=build)
155
163
  # print(f"Send command - factory tool test command testId={
156
164
  # test_id}, testDuration={test_duration}", "Factory tool print222", True)
157
165
  # return self.send_order_msg_sys(build2)
@@ -169,8 +177,8 @@ class MessageSystem(AbstractMessage, ABC):
169
177
  i9 = 1 if calendar.dst() else 0
170
178
  print(f"Print time zone, time zone={
171
179
  i8}, daylight saving time={i9} week={i4}")
172
- build = mctrl_sys.MctlSys(
173
- todev_data_time=mctrl_sys.SysSetDateTime(
180
+ build = MctlSys(
181
+ todev_data_time=SysSetDateTime(
174
182
  year=i,
175
183
  month=i2,
176
184
  date=i3,
@@ -191,21 +199,21 @@ class MessageSystem(AbstractMessage, ABC):
191
199
  return self.send_order_msg_sys(build)
192
200
 
193
201
  def get_device_version_info(self):
194
- return self.send_order_msg_sys(mctrl_sys_pb2.MctlSys(todev_get_dev_fw_info=1))
202
+ return self.send_order_msg_sys(MctlSys(todev_get_dev_fw_info=1))
195
203
 
196
204
  # === sendOrderMsg_Sys2 ===
197
205
 
198
206
  def request_iot_sys(
199
207
  self,
200
- rpt_act: mctrl_sys_pb2.rpt_act,
208
+ rpt_act: RptAct,
201
209
  rpt_info_type: list[RptInfoType | str] | None,
202
210
  timeout: int,
203
211
  period: int,
204
212
  no_change_period: int,
205
213
  count: int,
206
214
  ) -> bytes:
207
- build = mctrl_sys_pb2.MctlSys(
208
- todev_report_cfg=mctrl_sys_pb2.report_info_cfg(
215
+ build = MctlSys(
216
+ todev_report_cfg=ReportInfoCfg(
209
217
  act=rpt_act,
210
218
  sub=rpt_info_type,
211
219
  timeout=timeout,
@@ -218,9 +226,12 @@ class MessageSystem(AbstractMessage, ABC):
218
226
  build.todev_report_cfg.act} {build}")
219
227
  return self.send_order_msg_sys(build)
220
228
 
221
- def get_report_cfg(self, timeout: int = 10000, period: int = 1000, no_change_period: int = 2000):
222
- mctlsys = mctrl_sys_pb2.MctlSys(
223
- todev_report_cfg=mctrl_sys_pb2.report_info_cfg(
229
+ def get_report_cfg(
230
+ self, timeout: int = 10000, period: int = 1000, no_change_period: int = 1000, stop: bool = False
231
+ ):
232
+ mctlsys = MctlSys(
233
+ todev_report_cfg=ReportInfoCfg(
234
+ act=RptAct.RPT_STOP if stop else RptAct.RPT_START,
224
235
  timeout=timeout,
225
236
  period=period,
226
237
  no_change_period=no_change_period,
@@ -233,17 +244,18 @@ class MessageSystem(AbstractMessage, ABC):
233
244
  mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_DEV_LOCAL.value)
234
245
  mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_WORK.value)
235
246
  mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_DEV_STA.value)
247
+ mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_MAINTAIN.value)
236
248
  mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_VISION_POINT.value)
237
249
  mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_VIO.value)
238
250
  mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_VISION_STATISTIC.value)
239
251
 
240
- lubaMsg = luba_msg_pb2.LubaMsg()
241
- lubaMsg.msgtype = luba_msg_pb2.MSG_CMD_TYPE_EMBED_SYS
242
- lubaMsg.sender = luba_msg_pb2.DEV_MOBILEAPP
243
- lubaMsg.rcver = luba_msg_pb2.DEV_MAINCTL
244
- lubaMsg.msgattr = luba_msg_pb2.MSG_ATTR_REQ
252
+ lubaMsg = LubaMsg()
253
+ lubaMsg.msgtype = MsgCmdType.MSG_CMD_TYPE_EMBED_SYS
254
+ lubaMsg.sender = MsgDevice.DEV_MOBILEAPP
255
+ lubaMsg.rcver = MsgDevice.DEV_MAINCTL
256
+ lubaMsg.msgattr = MsgAttr.MSG_ATTR_REQ
245
257
  lubaMsg.seqs = 1
246
258
  lubaMsg.version = 1
247
259
  lubaMsg.subtype = 1
248
- lubaMsg.sys.CopyFrom(mctlsys)
260
+ lubaMsg.sys = mctlsys
249
261
  return lubaMsg.SerializeToString()
@@ -18,7 +18,7 @@ from pymammotion.utility.movement import get_percent, transform_both_speeds
18
18
  _LOGGER = logging.getLogger(__name__)
19
19
 
20
20
 
21
- def find_next_integer(lst: list[int], current_hash: float) -> int | None:
21
+ def find_next_integer(lst: list[int], current_hash: int) -> int | None:
22
22
  try:
23
23
  # Find the index of the current integer
24
24
  current_index = lst.index(current_hash)
@@ -54,6 +54,9 @@ class MammotionBaseDevice:
54
54
  def set_notification_callback(self, func: Callable[[], Awaitable[None]]) -> None:
55
55
  self._state_manager.on_notification_callback = func
56
56
 
57
+ def set_queue_callback(self, func: Callable[[str, dict[str, Any]], Awaitable[bytes]]) -> None:
58
+ self._state_manager.queue_command_callback = func
59
+
57
60
  async def datahash_response(self, hash_ack: NavGetHashListAck) -> None:
58
61
  """Handle datahash responses."""
59
62
  await self.queue_command("synchronize_hash_data", hash_num=hash_ack.data_couple[0])
@@ -37,7 +37,8 @@ class ConnectionPreference(Enum):
37
37
  class MammotionMixedDeviceManager:
38
38
  _ble_device: MammotionBaseBLEDevice | None = None
39
39
  _cloud_device: MammotionBaseCloudDevice | None = None
40
- _mowing_state: MowingDevice = MowingDevice()
40
+ _mowing_state: MowingDevice
41
+ preference: ConnectionPreference
41
42
 
42
43
  def __init__(
43
44
  self,
@@ -45,10 +46,13 @@ class MammotionMixedDeviceManager:
45
46
  cloud_device: Device | None = None,
46
47
  ble_device: BLEDevice | None = None,
47
48
  mqtt: MammotionCloud | None = None,
49
+ preference: ConnectionPreference = ConnectionPreference.BLUETOOTH,
48
50
  ) -> None:
49
51
  self.name = name
52
+ self._mowing_state = MowingDevice()
50
53
  self.add_ble(ble_device)
51
54
  self.add_cloud(cloud_device, mqtt)
55
+ self.preference = preference
52
56
 
53
57
  def mower_state(self):
54
58
  return self._mowing_state
@@ -101,19 +105,30 @@ class MammotionDevices:
101
105
  def get_device(self, mammotion_device_name: str) -> MammotionMixedDeviceManager:
102
106
  return self.devices.get(mammotion_device_name)
103
107
 
108
+ def remove_device(self, name) -> None:
109
+ device_for_removal = self.devices.pop(name)
110
+ if device_for_removal.has_cloud():
111
+ should_disconnect = {
112
+ device
113
+ for key, device in self.devices.items()
114
+ if device.cloud() is not None and device.cloud()._mqtt == device_for_removal.cloud()._mqtt
115
+ }
116
+ if len(should_disconnect) == 0:
117
+ device_for_removal.cloud()._mqtt.disconnect()
118
+
104
119
 
105
120
  async def create_devices(
106
121
  ble_device: BLEDevice,
107
122
  cloud_credentials: Credentials | None = None,
108
123
  preference: ConnectionPreference = ConnectionPreference.BLUETOOTH,
109
124
  ):
110
- mammotion = Mammotion(ble_device, preference)
125
+ mammotion = Mammotion()
126
+ mammotion.add_ble_device(ble_device, preference)
111
127
 
112
128
  if cloud_credentials and preference == ConnectionPreference.EITHER or preference == ConnectionPreference.WIFI:
113
- cloud_client = await Mammotion.login(
129
+ await mammotion.login_and_initiate_cloud(
114
130
  cloud_credentials.account_id or cloud_credentials.email, cloud_credentials.password
115
131
  )
116
- await mammotion.initiate_cloud_connection(cloud_client)
117
132
 
118
133
  return mammotion
119
134
 
@@ -123,26 +138,44 @@ class Mammotion:
123
138
  """Represents a Mammotion account and its devices."""
124
139
 
125
140
  devices = MammotionDevices()
126
- cloud_client: CloudIOTGateway | None = None
127
- mqtt: MammotionCloud | None = None
141
+ mqtt_list: dict[str, MammotionCloud] = dict()
128
142
 
129
- def __init__(
130
- self, ble_device: BLEDevice, preference: ConnectionPreference = ConnectionPreference.BLUETOOTH
131
- ) -> None:
143
+ _instance = None
144
+
145
+ def __new__(cls, *args, **kwargs):
146
+ if not cls._instance:
147
+ cls._instance = super().__new__(cls)
148
+ return cls._instance
149
+
150
+ def __init__(self) -> None:
132
151
  """Initialize MammotionDevice."""
133
- if ble_device:
134
- self.devices.add_device(MammotionMixedDeviceManager(name=ble_device.name, ble_device=ble_device))
152
+ self._login_lock = asyncio.Lock()
135
153
 
136
- if preference:
137
- self._preference = preference
154
+ def add_ble_device(self, ble_device: BLEDevice, preference: ConnectionPreference = ConnectionPreference.BLUETOOTH) -> None:
155
+ if ble_device:
156
+ self.devices.add_device(
157
+ MammotionMixedDeviceManager(name=ble_device.name, ble_device=ble_device, preference=preference)
158
+ )
138
159
 
139
- async def initiate_cloud_connection(self, cloud_client: CloudIOTGateway) -> None:
140
- if self.mqtt is not None:
141
- if self.mqtt.is_connected:
160
+ async def login_and_initiate_cloud(self, account, password, force: bool = False) -> None:
161
+ async with self._login_lock:
162
+ exists: MammotionCloud | None = self.mqtt_list.get(account)
163
+ if not exists or force:
164
+ cloud_client = await self.login(account, password)
165
+ else:
166
+ cloud_client = exists.cloud_client
167
+
168
+ await self.initiate_cloud_connection(account, cloud_client)
169
+
170
+ async def initiate_cloud_connection(self, account: str, cloud_client: CloudIOTGateway) -> None:
171
+ if self.mqtt_list.get(account) is not None:
172
+ if self.mqtt_list.get(account).is_connected:
173
+ # we might have removed a device so readd
174
+ self.add_cloud_devices(self.mqtt_list.get(account))
142
175
  return
143
176
 
144
177
  self.cloud_client = cloud_client
145
- self.mqtt = MammotionCloud(
178
+ self.mqtt_list[account] = MammotionCloud(
146
179
  MammotionMQTT(
147
180
  region_id=cloud_client.region_response.data.regionId,
148
181
  product_key=cloud_client.aep_response.data.productKey,
@@ -151,23 +184,30 @@ class Mammotion:
151
184
  iot_token=cloud_client.session_by_authcode_response.data.iotToken,
152
185
  client_id=cloud_client.client_id,
153
186
  cloud_client=cloud_client,
154
- )
187
+ ),
188
+ cloud_client,
155
189
  )
156
190
 
157
191
  loop = asyncio.get_running_loop()
158
- await loop.run_in_executor(None, self.mqtt.connect_async)
192
+ await loop.run_in_executor(None, self.mqtt_list[account].connect_async)
159
193
 
160
- for device in cloud_client.devices_by_account_response.data.data:
194
+ def add_cloud_devices(self, mqtt_client: MammotionCloud) -> None:
195
+ for device in mqtt_client.cloud_client.devices_by_account_response.data.data:
161
196
  mower_device = self.devices.get_device(device.deviceName)
162
197
  if device.deviceName.startswith(("Luba-", "Yuka-")) and mower_device is None:
163
198
  self.devices.add_device(
164
- MammotionMixedDeviceManager(name=device.deviceName, cloud_device=device, mqtt=self.mqtt)
199
+ MammotionMixedDeviceManager(
200
+ name=device.deviceName,
201
+ cloud_device=device,
202
+ mqtt=mqtt_client,
203
+ preference=ConnectionPreference.WIFI,
204
+ )
165
205
  )
166
206
  elif device.deviceName.startswith(("Luba-", "Yuka-")) and mower_device:
167
207
  if mower_device.cloud() is None:
168
- mower_device.add_cloud(cloud_device=device, mqtt=self.mqtt)
169
- else:
170
- device.replace_mqtt(self.mqtt)
208
+ mower_device.add_cloud(cloud_device=device, mqtt=mqtt_client)
209
+ elif mqtt_client != mower_device.cloud().mqtt:
210
+ mower_device.replace_mqtt(mqtt_client)
171
211
 
172
212
  def set_disconnect_strategy(self, disconnect: bool) -> None:
173
213
  for device_name, device in self.devices.devices:
@@ -175,8 +215,7 @@ class Mammotion:
175
215
  ble_device: MammotionBaseBLEDevice = device.ble()
176
216
  ble_device.set_disconnect_strategy(disconnect)
177
217
 
178
- @staticmethod
179
- async def login(account: str, password: str) -> CloudIOTGateway:
218
+ async def login(self, account: str, password: str) -> CloudIOTGateway:
180
219
  """Login to mammotion cloud."""
181
220
  cloud_client = CloudIOTGateway()
182
221
  async with ClientSession(MAMMOTION_DOMAIN) as session:
@@ -197,6 +236,9 @@ class Mammotion:
197
236
  await loop.run_in_executor(None, cloud_client.list_binding_by_account)
198
237
  return cloud_client
199
238
 
239
+ def remove_device(self, name: str) -> None:
240
+ self.devices.remove_device(name)
241
+
200
242
  def get_device_by_name(self, name: str) -> MammotionMixedDeviceManager:
201
243
  return self.devices.get_device(name)
202
244
 
@@ -204,9 +246,9 @@ class Mammotion:
204
246
  """Send a command to the device."""
205
247
  device = self.get_device_by_name(name)
206
248
  if device:
207
- if self._preference is ConnectionPreference.BLUETOOTH:
249
+ if device.preference is ConnectionPreference.BLUETOOTH:
208
250
  return await device.ble().command(key)
209
- if self._preference is ConnectionPreference.WIFI:
251
+ if device.preference is ConnectionPreference.WIFI:
210
252
  return await device.cloud().command(key)
211
253
  # TODO work with both with EITHER
212
254
 
@@ -214,27 +256,27 @@ class Mammotion:
214
256
  """Send a command with args to the device."""
215
257
  device = self.get_device_by_name(name)
216
258
  if device:
217
- if self._preference is ConnectionPreference.BLUETOOTH:
259
+ if device.preference is ConnectionPreference.BLUETOOTH:
218
260
  return await device.ble().command(key, **kwargs)
219
- if self._preference is ConnectionPreference.WIFI:
261
+ if device.preference is ConnectionPreference.WIFI:
220
262
  return await device.cloud().command(key, **kwargs)
221
263
  # TODO work with both with EITHER
222
264
 
223
265
  async def start_sync(self, name: str, retry: int):
224
266
  device = self.get_device_by_name(name)
225
267
  if device:
226
- if self._preference is ConnectionPreference.BLUETOOTH:
268
+ if device.preference is ConnectionPreference.BLUETOOTH:
227
269
  return await device.ble().start_sync(retry)
228
- if self._preference is ConnectionPreference.WIFI:
270
+ if device.preference is ConnectionPreference.WIFI:
229
271
  return await device.cloud().start_sync(retry)
230
272
  # TODO work with both with EITHER
231
273
 
232
274
  async def start_map_sync(self, name: str):
233
275
  device = self.get_device_by_name(name)
234
276
  if device:
235
- if self._preference is ConnectionPreference.BLUETOOTH:
277
+ if device.preference is ConnectionPreference.BLUETOOTH:
236
278
  return await device.ble().start_map_sync()
237
- if self._preference is ConnectionPreference.WIFI:
279
+ if device.preference is ConnectionPreference.WIFI:
238
280
  return await device.cloud().start_map_sync()
239
281
  # TODO work with both with EITHER
240
282
 
@@ -88,6 +88,7 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
88
88
  self._connect_lock = asyncio.Lock()
89
89
  self._operation_lock = asyncio.Lock()
90
90
  self._key: str | None = None
91
+ self.set_queue_callback(self.queue_command)
91
92
 
92
93
  def update_device(self, device: BLEDevice) -> None:
93
94
  """Update the BLE device."""
@@ -7,7 +7,7 @@ from typing import Any, Awaitable, Callable, Optional, cast
7
7
 
8
8
  import betterproto
9
9
 
10
- from pymammotion import MammotionMQTT
10
+ from pymammotion import CloudIOTGateway, MammotionMQTT
11
11
  from pymammotion.aliyun.dataclass.dev_by_account_response import Device
12
12
  from pymammotion.data.model.device import MowingDevice
13
13
  from pymammotion.data.mqtt.event import ThingEventMessage
@@ -24,7 +24,8 @@ _LOGGER = logging.getLogger(__name__)
24
24
  class MammotionCloud:
25
25
  """Per account MQTT cloud."""
26
26
 
27
- def __init__(self, mqtt_client: MammotionMQTT) -> None:
27
+ def __init__(self, mqtt_client: MammotionMQTT, cloud_client: CloudIOTGateway) -> None:
28
+ self.cloud_client = cloud_client
28
29
  self.loop = asyncio.get_event_loop()
29
30
  self._ble_sync_task = None
30
31
  self.is_ready = False
@@ -155,6 +156,7 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
155
156
  self.currentID = ""
156
157
  self._mqtt.mqtt_message_event.add_subscribers(self._parse_message_for_device)
157
158
  self._mqtt.on_ready_event.add_subscribers(self.on_ready)
159
+ self.set_queue_callback(self.queue_command)
158
160
 
159
161
  if self._mqtt.is_ready:
160
162
  self.run_periodic_sync_task()
@@ -244,3 +246,7 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
244
246
  if not fut.fut.cancelled():
245
247
  fut.resolve(cast(bytes, binary_data))
246
248
  await self._state_manager.notification(new_msg)
249
+
250
+ @property
251
+ def mqtt(self):
252
+ return self._mqtt
@@ -95,7 +95,6 @@ class DeviceType(Enum):
95
95
  elif DeviceType.LUBA.get_name() in substring2 or DeviceType.contain_luba_product_key(product_key):
96
96
  return DeviceType.LUBA
97
97
  else:
98
- print("unknown device type")
99
98
  return DeviceType.UNKNOWN
100
99
  except Exception:
101
100
  return DeviceType.UNKNOWN
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pymammotion
3
- Version: 0.2.35
3
+ Version: 0.2.36
4
4
  Summary:
5
5
  License: GNU-3.0
6
6
  Author: Michael Arthur
@@ -1,6 +1,6 @@
1
1
  pymammotion/__init__.py,sha256=jHCQrpJaG1jAoID9T4RT3g4JsZc0JpJqIcqjnA7cXd0,1605
2
2
  pymammotion/aliyun/__init__.py,sha256=T1lkX7TRYiL4nqYanG4l4MImV-SlavSbuooC-W-uUGw,29
3
- pymammotion/aliyun/cloud_gateway.py,sha256=Fktzd7ej9b02hr0Dl82_EgUkDCCzWqcp92_wd8D2vaw,22716
3
+ pymammotion/aliyun/cloud_gateway.py,sha256=X6Y9yyIXCwkPwgRqsBESUI5v6j-CKlJGJaEnMINyp8c,22780
4
4
  pymammotion/aliyun/cloud_service.py,sha256=px7dUKow5Z7VyebjYzuKkzkm77XbUXYiFiYO_2e-UQ0,2207
5
5
  pymammotion/aliyun/dataclass/aep_response.py,sha256=8f6GIP58ve8gd6AL3HBoXxsy0n2q4ygWvjELGnoOnVc,452
6
6
  pymammotion/aliyun/dataclass/connect_response.py,sha256=Yz-fEbDzgGPTo5Of2oAjmFkSv08T7ze80pQU4k-gKIU,824
@@ -38,7 +38,7 @@ pymammotion/data/mqtt/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdr
38
38
  pymammotion/data/mqtt/event.py,sha256=GKh4NdcaeeaG5pPktzy-eguFKK74Mfxpl0eIAkKU1Vc,4567
39
39
  pymammotion/data/mqtt/properties.py,sha256=HkBPghr26L9_b4QaOi1DtPgb0UoPIOGSe9wb3kgnM6Y,2815
40
40
  pymammotion/data/mqtt/status.py,sha256=zqnlo-MzejEQZszl0i0Wucoc3E76x6UtI9JLxoBnu54,1067
41
- pymammotion/data/state_manager.py,sha256=GpVQM78JTFNtjpym2ykFR-Ng7iMXRhfunyAbL-QpENs,3363
41
+ pymammotion/data/state_manager.py,sha256=ZIOB1Th7yDAgCJk17rd-kXjwD50GIqQMsNgf1iFldUs,3615
42
42
  pymammotion/event/__init__.py,sha256=mgATR6vPHACNQ-0zH5fi7NdzeTCDV1CZyaWPmtUusi8,115
43
43
  pymammotion/event/event.py,sha256=UzYnxV5DfvMDK3E06UvSzvzuBbaXOOUwO6xYt_zn9To,2034
44
44
  pymammotion/http/_init_.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -54,15 +54,15 @@ pymammotion/mammotion/commands/messages/media.py,sha256=ps0l06CXy5Ej--gTNCsyKttw
54
54
  pymammotion/mammotion/commands/messages/navigation.py,sha256=4rXBL-mViWc38K6x1w5O-GjwV8UWS5xZXkf4aHYjs8A,23684
55
55
  pymammotion/mammotion/commands/messages/network.py,sha256=gD7NKVKg8U2KNbPvgOxvTJXbznWdpdPQo9jBsQSx4OI,8027
56
56
  pymammotion/mammotion/commands/messages/ota.py,sha256=XkeuWBZtpYMMBze6r8UN7dJXbe2FxUNGNnjwBpXJKM0,1240
57
- pymammotion/mammotion/commands/messages/system.py,sha256=xm9Nj3wva9leVV1tyzfS_Hf53t-j7Nk8RBlFd00CQFM,10972
57
+ pymammotion/mammotion/commands/messages/system.py,sha256=4ijnn0VPHOIizgjqDGpeD3jVetEscnSDt2E4tIBwkoQ,10888
58
58
  pymammotion/mammotion/commands/messages/video.py,sha256=_8lJsU4sLm2CGnc7RDkueA0A51Ysui6x7SqFnhX8O2g,1007
59
59
  pymammotion/mammotion/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
60
  pymammotion/mammotion/control/joystick.py,sha256=QfBVxM_gxpWsZAGO90whtgxCI2tIZ3TTad9wHIPsU9s,5640
61
61
  pymammotion/mammotion/devices/__init__.py,sha256=f2qQFPgLGmV85W2hSlMUh5BYuht9o_Ar_JEAAMD4fsE,102
62
- pymammotion/mammotion/devices/base.py,sha256=zbPpGLP9-0dKZFDt2zgGWtuHwy1h2cXHlR8S2EnpTps,11348
63
- pymammotion/mammotion/devices/mammotion.py,sha256=mPD8ECsW5AwJdvzuVQBp9QYXjo3YhUxgtP-N8ptthmU,9726
64
- pymammotion/mammotion/devices/mammotion_bluetooth.py,sha256=3XUjhE2sb_2aZnJlevmwxd99zR_4qZOfaK86h6hKV5E,17303
65
- pymammotion/mammotion/devices/mammotion_cloud.py,sha256=W0gTdTvqofsL4QxjsVPHcBFYXTlgnzpLwIOAgR2yJA0,9914
62
+ pymammotion/mammotion/devices/base.py,sha256=5k82KHwW2JqZXW-MBYe8SkedZw_bQ2gqShX5RQd_Yio,11504
63
+ pymammotion/mammotion/devices/mammotion.py,sha256=uiKtzLHfVPyk-cdm5gFraI9nkrIX3ZkdZPE7xEyuoTQ,11597
64
+ pymammotion/mammotion/devices/mammotion_bluetooth.py,sha256=sgGeyQeAeA3lQodcalRYS4nDNAzjfFs9SddIB1kadvw,17355
65
+ pymammotion/mammotion/devices/mammotion_cloud.py,sha256=dk49VY9yHO3d-Nb17y-D4YIURs2FTLMzWHUYrxBYhtw,10116
66
66
  pymammotion/mqtt/__init__.py,sha256=Ocs5e-HLJvTuDpVXyECEsWIvwsUaxzj7lZ9mSYutNDY,105
67
67
  pymammotion/mqtt/mammotion_future.py,sha256=_OWqKOlUGl2yT1xOsXFQYpGd-1zQ63OxqXgy7KRQgYc,710
68
68
  pymammotion/mqtt/mammotion_mqtt.py,sha256=QZuqp2G5nywCFSwGNSayPI5JVL789yOxyr0UM0G7wzg,8295
@@ -112,12 +112,12 @@ pymammotion/utility/constant/__init__.py,sha256=tcY0LDeD-qDDHx2LKt55KOyv9ZI0UfCN
112
112
  pymammotion/utility/constant/device_constant.py,sha256=rAEK60F52VyJL31uLnq0Y60D-0VK5gVm59yi9kBfndM,7123
113
113
  pymammotion/utility/conversions.py,sha256=v3YICy0zZwwBBzrUZgabI7GRfiDBnkiAX2qdtk3NxOY,89
114
114
  pymammotion/utility/datatype_converter.py,sha256=SPM_HuaaD_XOawlqEnA8qlRRZXGba3WjA8kGOZgeBlQ,4284
115
- pymammotion/utility/device_type.py,sha256=6Mmv8oJoJ0DQrfGhRGt3rg20f_GLwIRw4NtpAdF6wcE,9478
115
+ pymammotion/utility/device_type.py,sha256=xOgfIhOkzgcAtoKtlhlB1q8FpiKe1rVVV5BvN7K7zYc,9433
116
116
  pymammotion/utility/map.py,sha256=GYscVMg2cX3IPlNpCBNHDW0S55yS1WGRf1iHnNZ7TfQ,2227
117
117
  pymammotion/utility/movement.py,sha256=N75oAoAgFydqoaOedYIxGUHmuTCtPzAOtb-d_29tpfI,615
118
118
  pymammotion/utility/periodic.py,sha256=MbeSb9cfhxzYmdT_RiE0dZe3H9IfbQW_zSqhmSX2RUc,3321
119
119
  pymammotion/utility/rocker_util.py,sha256=6tX7sS87qoQC_tsxbx3NLL-HgS08wtzXiZkhDiz7uo0,7179
120
- pymammotion-0.2.35.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
121
- pymammotion-0.2.35.dist-info/METADATA,sha256=_VXu85qe7SUTm01E2s-9VVwcoKOZc9cDOuiasv7lUuc,4052
122
- pymammotion-0.2.35.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
123
- pymammotion-0.2.35.dist-info/RECORD,,
120
+ pymammotion-0.2.36.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
121
+ pymammotion-0.2.36.dist-info/METADATA,sha256=oKsEdniFXVqz9dbL-obKVKoZWsKWlS0xDGjaob8n3Lc,4052
122
+ pymammotion-0.2.36.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
123
+ pymammotion-0.2.36.dist-info/RECORD,,