pymammotion 0.2.41__py3-none-any.whl → 0.2.43__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.
Files changed (27) hide show
  1. pymammotion/aliyun/cloud_gateway.py +90 -7
  2. pymammotion/aliyun/{dataclass → model}/dev_by_account_response.py +1 -1
  3. pymammotion/aliyun/model/stream_subscription_response.py +19 -0
  4. pymammotion/bluetooth/ble_message.py +9 -7
  5. pymammotion/data/model/device.py +12 -1
  6. pymammotion/data/model/device_config.py +1 -1
  7. pymammotion/data/model/rapid_state.py +3 -1
  8. pymammotion/data/model/report_info.py +9 -9
  9. pymammotion/data/state_manager.py +7 -1
  10. pymammotion/http/http.py +17 -0
  11. pymammotion/http/model/http.py +4 -0
  12. pymammotion/mammotion/commands/messages/network.py +15 -15
  13. pymammotion/mammotion/commands/messages/system.py +50 -19
  14. pymammotion/mammotion/devices/base.py +5 -1
  15. pymammotion/mammotion/devices/mammotion.py +31 -25
  16. pymammotion/mammotion/devices/mammotion_bluetooth.py +5 -0
  17. pymammotion/mammotion/devices/mammotion_cloud.py +37 -8
  18. pymammotion/mqtt/mammotion_mqtt.py +12 -0
  19. {pymammotion-0.2.41.dist-info → pymammotion-0.2.43.dist-info}/METADATA +1 -1
  20. {pymammotion-0.2.41.dist-info → pymammotion-0.2.43.dist-info}/RECORD +27 -26
  21. /pymammotion/aliyun/{dataclass → model}/aep_response.py +0 -0
  22. /pymammotion/aliyun/{dataclass → model}/connect_response.py +0 -0
  23. /pymammotion/aliyun/{dataclass → model}/login_by_oauth_response.py +0 -0
  24. /pymammotion/aliyun/{dataclass → model}/regions_response.py +0 -0
  25. /pymammotion/aliyun/{dataclass → model}/session_by_authcode_response.py +0 -0
  26. {pymammotion-0.2.41.dist-info → pymammotion-0.2.43.dist-info}/LICENSE +0 -0
  27. {pymammotion-0.2.41.dist-info → pymammotion-0.2.43.dist-info}/WHEEL +0 -0
@@ -17,14 +17,14 @@ from alibabacloud_iot_api_gateway.models import CommonParams, Config, IoTApiRequ
17
17
  from alibabacloud_tea_util.client import Client as UtilClient
18
18
  from alibabacloud_tea_util.models import RuntimeOptions
19
19
 
20
- from pymammotion.aliyun.dataclass.aep_response import AepResponse
21
- from pymammotion.aliyun.dataclass.connect_response import ConnectResponse
22
- from pymammotion.aliyun.dataclass.dev_by_account_response import (
20
+ from pymammotion.aliyun.model.aep_response import AepResponse
21
+ from pymammotion.aliyun.model.connect_response import ConnectResponse
22
+ from pymammotion.aliyun.model.dev_by_account_response import (
23
23
  ListingDevByAccountResponse,
24
24
  )
25
- from pymammotion.aliyun.dataclass.login_by_oauth_response import LoginByOAuthResponse
26
- from pymammotion.aliyun.dataclass.regions_response import RegionResponse
27
- from pymammotion.aliyun.dataclass.session_by_authcode_response import (
25
+ from pymammotion.aliyun.model.login_by_oauth_response import LoginByOAuthResponse
26
+ from pymammotion.aliyun.model.regions_response import RegionResponse
27
+ from pymammotion.aliyun.model.session_by_authcode_response import (
28
28
  SessionByAuthCodeResponse,
29
29
  )
30
30
  from pymammotion.const import ALIYUN_DOMAIN, APP_KEY, APP_SECRET, APP_VERSION
@@ -436,6 +436,49 @@ class CloudIOTGateway:
436
436
 
437
437
  return response.body
438
438
 
439
+ def sign_out(self) -> None:
440
+ config = Config(
441
+ app_key=self._app_key,
442
+ app_secret=self._app_secret,
443
+ domain=self._region_response.data.apiGatewayEndpoint,
444
+ )
445
+ client = Client(config)
446
+
447
+ # build request
448
+ request = CommonParams(api_ver="1.0.4", language="en-US")
449
+ body = IoTApiRequest(
450
+ id=str(uuid.uuid4()),
451
+ params={
452
+ "request": {
453
+ "refreshToken": self._session_by_authcode_response.data.refreshToken,
454
+ "identityId": self._session_by_authcode_response.data.identityId,
455
+ }
456
+ },
457
+ request=request,
458
+ version="1.0",
459
+ )
460
+
461
+ # send request
462
+ # possibly need to do this ourselves
463
+ response = client.do_request(
464
+ "/iotx/account/invalidSession",
465
+ "https",
466
+ "POST",
467
+ None,
468
+ body,
469
+ RuntimeOptions(),
470
+ )
471
+ logger.debug(response.status_message)
472
+ logger.debug(response.headers)
473
+ logger.debug(response.status_code)
474
+ logger.debug(response.body)
475
+
476
+ # Decode the response body
477
+ response_body_str = response.body.decode("utf-8")
478
+
479
+ # Load the JSON string into a dictionary
480
+ response_body_dict = json.loads(response_body_str)
481
+
439
482
  def check_or_refresh_session(self):
440
483
  """Check or refresh the session."""
441
484
  logger.debug("Try to refresh token")
@@ -542,6 +585,47 @@ class CloudIOTGateway:
542
585
  self._devices_by_account_response = ListingDevByAccountResponse.from_dict(response_body_dict)
543
586
  return self._devices_by_account_response
544
587
 
588
+ def list_binding_by_dev(self, iot_id: str):
589
+ config = Config(
590
+ app_key=self._app_key,
591
+ app_secret=self._app_secret,
592
+ domain=self._region_response.data.apiGatewayEndpoint,
593
+ )
594
+
595
+ client = Client(config)
596
+
597
+ # build request
598
+ request = CommonParams(
599
+ api_ver="1.0.8",
600
+ language="en-US",
601
+ iot_token=self._session_by_authcode_response.data.iotToken,
602
+ )
603
+ body = IoTApiRequest(
604
+ id=str(uuid.uuid4()),
605
+ params={"pageSize": 100, "pageNo": 1, "iotId": iot_id},
606
+ request=request,
607
+ version="1.0",
608
+ )
609
+
610
+ # send request
611
+ response = client.do_request("/uc/listBindingByDev", "https", "POST", None, body, RuntimeOptions())
612
+ logger.debug(response.status_message)
613
+ logger.debug(response.headers)
614
+ logger.debug(response.status_code)
615
+ logger.debug(response.body)
616
+
617
+ # Decode the response body
618
+ response_body_str = response.body.decode("utf-8")
619
+
620
+ # Load the JSON string into a dictionary
621
+ response_body_dict = json.loads(response_body_str)
622
+
623
+ if int(response_body_dict.get("code")) != 200:
624
+ raise Exception("Error in creating session: " + response_body_dict["msg"])
625
+
626
+ self._devices_by_account_response = ListingDevByAccountResponse.from_dict(response_body_dict)
627
+ return self._devices_by_account_response
628
+
545
629
  def send_cloud_command(self, iot_id: str, command: bytes) -> str:
546
630
  """Send a cloud command to the specified IoT device."""
547
631
 
@@ -587,7 +671,6 @@ class CloudIOTGateway:
587
671
  version="1.0",
588
672
  )
589
673
  logger.debug(self.converter.printBase64Binary(command))
590
-
591
674
  # send request
592
675
  response = client.do_request("/thing/service/invoke", "https", "POST", None, body, RuntimeOptions())
593
676
  logger.debug(response.status_message)
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from typing import List, Optional
2
+ from typing import Optional
3
3
 
4
4
  from mashumaro.config import BaseConfig
5
5
  from mashumaro.mixins.orjson import DataClassORJSONMixin
@@ -0,0 +1,19 @@
1
+ from dataclasses import dataclass
2
+ from typing import List
3
+
4
+ from mashumaro.mixins.orjson import DataClassORJSONMixin
5
+
6
+
7
+ @dataclass
8
+ class Camera(DataClassORJSONMixin):
9
+ cameraId: int
10
+ token: str
11
+
12
+
13
+ @dataclass
14
+ class StreamSubscriptionResponse(DataClassORJSONMixin):
15
+ appid: str
16
+ cameras: List[Camera]
17
+ channelName: str
18
+ token: str
19
+ uid: int
@@ -16,7 +16,6 @@ from pymammotion.bluetooth.data.convert import parse_custom_data
16
16
  from pymammotion.bluetooth.data.framectrldata import FrameCtrlData
17
17
  from pymammotion.bluetooth.data.notifydata import BlufiNotifyData
18
18
  from pymammotion.data.model.execute_boarder import ExecuteBorder
19
- from pymammotion.mammotion.commands.messages.navigation import MessageNavigation
20
19
  from pymammotion.proto import (
21
20
  dev_net_pb2,
22
21
  luba_msg_pb2,
@@ -49,7 +48,6 @@ class BleMessage:
49
48
  mReadSequence: iter
50
49
  mAck: queue
51
50
  notification: BlufiNotifyData
52
- messageNavigation: MessageNavigation = MessageNavigation()
53
51
 
54
52
  def __init__(self, client: BleakClient) -> None:
55
53
  self.client = client
@@ -123,9 +121,9 @@ class BleMessage:
123
121
 
124
122
  async def requestDeviceStatus(self) -> None:
125
123
  request = False
126
- type = self.messageNavigation.getTypeValue(0, 5)
124
+ type = self.getTypeValue(0, 5)
127
125
  try:
128
- request = await self.messageNavigation.post(BleMessage.mEncrypted, BleMessage.mChecksum, False, type, None)
126
+ request = await self.post(BleMessage.mEncrypted, BleMessage.mChecksum, False, type, None)
129
127
  # _LOGGER.debug(request)
130
128
  except Exception as err:
131
129
  # Log.w(TAG, "post requestDeviceStatus interrupted")
@@ -137,9 +135,9 @@ class BleMessage:
137
135
 
138
136
  async def requestDeviceVersion(self) -> None:
139
137
  request = False
140
- type = self.messageNavigation.getTypeValue(0, 7)
138
+ type = self.getTypeValue(0, 7)
141
139
  try:
142
- request = await self.messageNavigation.post(BleMessage.mEncrypted, BleMessage.mChecksum, False, type, None)
140
+ request = await self.post(BleMessage.mEncrypted, BleMessage.mChecksum, False, type, None)
143
141
  # _LOGGER.debug(request)
144
142
  except Exception as err:
145
143
  # Log.w(TAG, "post requestDeviceStatus interrupted")
@@ -147,7 +145,7 @@ class BleMessage:
147
145
  _LOGGER.error(err)
148
146
 
149
147
  async def sendBorderPackage(self, executeBorder: ExecuteBorder) -> None:
150
- await self.messageNavigation.post_custom_data(serialize(executeBorder))
148
+ await self.post_custom_data(serialize(executeBorder))
151
149
 
152
150
  async def gatt_write(self, data: bytes) -> None:
153
151
  await self.client.write_gatt_char(UUID_WRITE_CHARACTERISTIC, data, True)
@@ -340,6 +338,10 @@ class BleMessage:
340
338
  # onPostCustomDataResult(status, data)
341
339
  except Exception as err:
342
340
  _LOGGER.debug(err)
341
+ # we might be constantly connected and in a bad state
342
+ self.mSendSequence = itertools.count()
343
+ self.mReadSequence = itertools.count()
344
+ await self.client.disconnect()
343
345
 
344
346
  async def post(
345
347
  self,
@@ -109,10 +109,21 @@ class MowingDevice(DataClassORJSONMixin):
109
109
  location.zone_hash if self.report_data.dev.sys_status == WorkMode.MODE_WORKING else 0
110
110
  )
111
111
 
112
- self.report_data = self.report_data.from_dict(toapp_report_data.to_dict(casing=betterproto.Casing.SNAKE))
112
+ self.report_data.update(toapp_report_data.to_dict(casing=betterproto.Casing.SNAKE))
113
+
113
114
 
114
115
  def run_state_update(self, rapid_state: SystemRapidStateTunnelMsg) -> None:
116
+ coordinate_converter = CoordinateConverter(self.location.RTK.latitude, self.location.RTK.longitude)
115
117
  self.mowing_state = RapidState().from_raw(rapid_state.rapid_state_data)
118
+ self.location.position_type = self.mowing_state.pos_type
119
+ self.location.orientation = self.mowing_state.toward / 10000
120
+ self.location.device = coordinate_converter.enu_to_lla(
121
+ parse_double(self.mowing_state.pos_y, 4.0), parse_double(self.mowing_state.pos_x, 4.0)
122
+ )
123
+ if self.mowing_state.zone_hash:
124
+ self.location.work_zone = (
125
+ self.mowing_state.zone_hash if self.report_data.dev.sys_status == WorkMode.MODE_WORKING else 0
126
+ )
116
127
 
117
128
  def mow_info(self, toapp_mow_info: MowToAppInfoT) -> None:
118
129
  pass
@@ -8,7 +8,7 @@ from pymammotion.utility.device_type import DeviceType
8
8
  @dataclass
9
9
  class DeviceLimits(DataClassORJSONMixin):
10
10
  blade_height_min: int = 30
11
- blade_height_max: int = 70
11
+ blade_height_max: int = 100
12
12
  working_speed_min: float = 0.2
13
13
  working_speed_max: float = 1.2
14
14
  working_path_min: int = 15
@@ -1,6 +1,8 @@
1
1
  from dataclasses import dataclass
2
2
  from enum import Enum
3
3
 
4
+ from mashumaro.mixins.orjson import DataClassORJSONMixin
5
+
4
6
  from pymammotion.utility.conversions import parse_double
5
7
 
6
8
 
@@ -11,7 +13,7 @@ class RTKStatus(Enum):
11
13
 
12
14
 
13
15
  @dataclass
14
- class RapidState:
16
+ class RapidState(DataClassORJSONMixin):
15
17
  pos_x: float = 0
16
18
  pos_y: float = 0
17
19
  rtk_status: RTKStatus = RTKStatus.NONE
@@ -63,22 +63,22 @@ class WorkData(DataClassORJSONMixin):
63
63
 
64
64
 
65
65
  @dataclass
66
- class ReportData:
66
+ class ReportData(DataClassORJSONMixin):
67
67
  connect: ConnectData = field(default_factory=ConnectData)
68
68
  dev: DeviceData = field(default_factory=DeviceData)
69
69
  rtk: RTKData = field(default_factory=RTKData)
70
70
  locations: list[LocationData] = field(default_factory=list)
71
71
  work: WorkData = field(default_factory=WorkData)
72
72
 
73
- def from_dict(self, data: dict):
73
+ def update(self, data: dict):
74
74
  locations = self.locations
75
75
  if data.get("locations") is not None:
76
76
  locations = [LocationData.from_dict(loc) for loc in data.get("locations", [])]
77
77
 
78
- return ReportData(
79
- connect=ConnectData.from_dict(data.get("connect", asdict(self.connect))),
80
- dev=DeviceData.from_dict(data.get("dev", asdict(self.dev))),
81
- rtk=RTKData.from_dict(data.get("rtk", asdict(self.rtk))),
82
- locations=locations,
83
- work=WorkData.from_dict(data.get("work", asdict(self.work))),
84
- )
78
+
79
+ self.connect=ConnectData.from_dict(data.get("connect", asdict(self.connect)))
80
+ self.dev=DeviceData.from_dict(data.get("dev", asdict(self.dev)))
81
+ self.rtk=RTKData.from_dict(data.get("rtk", asdict(self.rtk)))
82
+ self.locations=locations
83
+ self.work=WorkData.from_dict(data.get("work", asdict(self.work)))
84
+
@@ -1,5 +1,6 @@
1
1
  """Manage state from notifications into MowingDevice."""
2
2
 
3
+ from datetime import datetime
3
4
  from typing import Any, Awaitable, Callable, Optional
4
5
 
5
6
  import betterproto
@@ -8,12 +9,14 @@ from pymammotion.data.model.device import MowingDevice
8
9
  from pymammotion.data.model.hash_list import AreaHashNameList
9
10
  from pymammotion.proto.luba_msg import LubaMsg
10
11
  from pymammotion.proto.mctrl_nav import AppGetAllAreaHashName, NavGetCommDataAck, NavGetHashListAck
12
+ from pymammotion.utility.constant import WorkMode
11
13
 
12
14
 
13
15
  class StateManager:
14
16
  """Manage state."""
15
17
 
16
18
  _device: MowingDevice
19
+ last_updated_at: datetime = datetime.now()
17
20
 
18
21
  def __init__(self, device: MowingDevice) -> None:
19
22
  self._device = device
@@ -21,6 +24,7 @@ class StateManager:
21
24
  self.get_commondata_ack_callback: Optional[Callable[[NavGetCommDataAck], Awaitable[None]]] = None
22
25
  self.on_notification_callback: Optional[Callable[[], Awaitable[None]]] = None
23
26
  self.queue_command_callback: Optional[Callable[[str, dict[str, Any]], Awaitable[bytes]]] = None
27
+ self.last_updated_at = datetime.now()
24
28
 
25
29
  def get_device(self) -> MowingDevice:
26
30
  """Get device."""
@@ -33,6 +37,7 @@ class StateManager:
33
37
  async def notification(self, message: LubaMsg) -> None:
34
38
  """Handle protobuf notifications."""
35
39
  res = betterproto.which_one_of(message, "LubaSubMsg")
40
+ self.last_updated_at = datetime.now()
36
41
 
37
42
  match res[0]:
38
43
  case "nav":
@@ -78,7 +83,8 @@ class StateManager:
78
83
  case "toapp_report_data":
79
84
  self._device.update_report_data(sys_msg[1])
80
85
  if self.queue_command_callback:
81
- await self.queue_command_callback("get_report_cfg", stop=True)
86
+ if self._device.sys.toapp_report_data.dev.sys_status != WorkMode.MODE_WORKING:
87
+ await self.queue_command_callback("get_report_cfg_stop")
82
88
  case "mow_to_app_info":
83
89
  self._device.mow_info(sys_msg[1])
84
90
  case "system_tard_state_tunnel":
pymammotion/http/http.py CHANGED
@@ -3,6 +3,7 @@ from typing import cast
3
3
 
4
4
  from aiohttp import ClientSession
5
5
 
6
+ from pymammotion.aliyun.model.stream_subscription_response import StreamSubscriptionResponse
6
7
  from pymammotion.const import (
7
8
  MAMMOTION_API_DOMAIN,
8
9
  MAMMOTION_CLIENT_ID,
@@ -43,6 +44,22 @@ class MammotionHTTP:
43
44
  data = await resp.json()
44
45
  response = Response.from_dict(data)
45
46
 
47
+ async def get_stream_subscription(self, iot_id: str) -> Response[StreamSubscriptionResponse]:
48
+ """Get agora.io data for view camera stream"""
49
+ async with ClientSession(MAMMOTION_API_DOMAIN) as session:
50
+ async with session.post(
51
+ "/device-server/v1/stream/subscription",
52
+ json={"deviceId": iot_id},
53
+ headers={
54
+ "Authorization": f"{self._headers.get('Authorization', "")}",
55
+ "Content-Type": "application/json",
56
+ },
57
+ ) as resp:
58
+ data = await resp.json()
59
+ # TODO catch errors from mismatch like token expire etc
60
+ # Assuming the data format matches the expected structure
61
+ return Response[StreamSubscriptionResponse].from_dict(data)
62
+
46
63
  @classmethod
47
64
  async def login(cls, session: ClientSession, username: str, password: str) -> Response[LoginResponseData]:
48
65
  async with session.post(
@@ -2,6 +2,7 @@ from dataclasses import dataclass
2
2
  from typing import Generic, Literal, Optional, TypeVar
3
3
 
4
4
  from mashumaro import DataClassDictMixin
5
+ from mashumaro.config import BaseConfig
5
6
  from mashumaro.mixins.orjson import DataClassORJSONMixin
6
7
 
7
8
  DataT = TypeVar("DataT")
@@ -51,6 +52,9 @@ class Response(DataClassDictMixin, Generic[DataT]):
51
52
  msg: str
52
53
  data: DataT | None = None
53
54
 
55
+ class Config(BaseConfig):
56
+ omit_default = True
57
+
54
58
 
55
59
  @dataclass
56
60
  class LoginResponseUserInformation(DataClassORJSONMixin):
@@ -1,5 +1,5 @@
1
1
  # === sendOrderMsg_Net ===
2
-
2
+ from pymammotion import logger
3
3
  from pymammotion.mammotion.commands.messages.navigation import MessageNavigation
4
4
  from pymammotion.proto import dev_net_pb2, luba_msg_pb2
5
5
 
@@ -34,12 +34,12 @@ class MessageNetwork:
34
34
 
35
35
  def get_4g_module_info(self) -> bytes:
36
36
  build = dev_net_pb2.DevNet(todev_get_mnet_cfg_req=dev_net_pb2.DevNet().todev_get_mnet_cfg_req)
37
- print("Send command -- Get device 4G network module information")
37
+ logger.debug("Send command -- Get device 4G network module information")
38
38
  return self.send_order_msg_net(build)
39
39
 
40
40
  def get_4g_info(self) -> bytes:
41
41
  build = dev_net_pb2.DevNet(todev_mnet_info_req=dev_net_pb2.DevNet().todev_mnet_info_req)
42
- print("Send command -- Get device 4G network information")
42
+ logger.debug("Send command -- Get device 4G network information")
43
43
  return self.send_order_msg_net(build)
44
44
 
45
45
  def set_zmq_enable(self) -> bytes:
@@ -50,12 +50,12 @@ class MessageNetwork:
50
50
  tx_zmq_url="tcp://0.0.0.0:5555",
51
51
  )
52
52
  )
53
- print("Send command -- Set vision ZMQ to enable")
53
+ logger.debug("Send command -- Set vision ZMQ to enable")
54
54
  return self.send_order_msg_net(build)
55
55
 
56
56
  def set_iot_setting(self, iot_control_type: dev_net_pb2.iot_conctrl_type) -> bytes:
57
57
  build = dev_net_pb2.DevNet(todev_set_iot_offline_req=iot_control_type)
58
- print("Send command -- Device re-online")
58
+ logger.debug("Send command -- Device re-online")
59
59
  return self.send_order_msg_net(build)
60
60
 
61
61
  def set_device_log_upload(
@@ -75,7 +75,7 @@ class MessageNetwork:
75
75
  num=number,
76
76
  type=type,
77
77
  )
78
- print(
78
+ logger.debug(
79
79
  f"Send log====Feedback====Command======requestID:{request_id} operation:{operation} serverIp:{server_ip} type:{type}"
80
80
  )
81
81
  return self.send_order_msg_net(dev_net_pb2.DevNet(todev_ble_sync=1, todev_uploadfile_req=build))
@@ -98,7 +98,7 @@ class MessageNetwork:
98
98
  num=number,
99
99
  type=type,
100
100
  )
101
- print(
101
+ logger.debug(
102
102
  f"Send log====Feedback====Command======requestID:{request_id} operation:{operation} serverIp:{server_ip} type:{type}"
103
103
  )
104
104
  return self.send_order_msg_net(dev_net_pb2.DevNet(todev_ble_sync=1, todev_uploadfile_req=build))
@@ -126,7 +126,7 @@ class MessageNetwork:
126
126
 
127
127
  def get_device_network_info(self) -> bytes:
128
128
  build = dev_net_pb2.DevNet(todev_networkinfo_req=dev_net_pb2.GetNetworkInfoReq(req_ids=1))
129
- print("Send command - get device network information")
129
+ logger.debug("Send command - get device network information")
130
130
  return self.send_order_msg_net(build)
131
131
 
132
132
  def set_device_4g_enable_status(self, new_4g_status: bool) -> bytes:
@@ -141,7 +141,7 @@ class MessageNetwork:
141
141
  ),
142
142
  )
143
143
 
144
- print(f"Send command - set 4G (on/off status). newWifiStatus={new_4g_status}")
144
+ logger.debug(f"Send command - set 4G (on/off status). newWifiStatus={new_4g_status}")
145
145
  return self.send_order_msg_net(build)
146
146
 
147
147
  def set_device_wifi_enable_status(self, new_wifi_status: bool) -> bytes:
@@ -149,11 +149,11 @@ class MessageNetwork:
149
149
  todev_ble_sync=1,
150
150
  todev_Wifi_Configuration=dev_net_pb2.DrvWifiSet(configParam=4, wifi_enable=new_wifi_status),
151
151
  )
152
- print(f"szNetwork: Send command - set network (on/off status). newWifiStatus={new_wifi_status}")
152
+ logger.debug(f"szNetwork: Send command - set network (on/off status). newWifiStatus={new_wifi_status}")
153
153
  return self.send_order_msg_net(build)
154
154
 
155
155
  def wifi_connectinfo_update(self, device_name: str, is_binary: bool) -> bytes:
156
- print(
156
+ logger.debug(
157
157
  f"Send command - get Wifi connection information.wifiConnectinfoUpdate().deviceName={device_name}.isBinary={is_binary}"
158
158
  )
159
159
  if is_binary:
@@ -161,7 +161,7 @@ class MessageNetwork:
161
161
  todev_ble_sync=1,
162
162
  todev_WifiMsgUpload=dev_net_pb2.DrvWifiUpload(wifi_msg_upload=1),
163
163
  )
164
- print("Send command - get Wifi connection information")
164
+ logger.debug("Send command - get Wifi connection information")
165
165
  return self.send_order_msg_net(build)
166
166
  self.wifi_connectinfo_update2()
167
167
 
@@ -171,10 +171,10 @@ class MessageNetwork:
171
171
  # 68, hash_map)) # ToDo: Fix this
172
172
 
173
173
  def get_record_wifi_list(self, is_binary: bool) -> bytes:
174
- print(f"getRecordWifiList().isBinary={is_binary}")
174
+ logger.debug(f"getRecordWifiList().isBinary={is_binary}")
175
175
  if is_binary:
176
176
  build = dev_net_pb2.DevNet(todev_ble_sync=1, todev_WifiListUpload=dev_net_pb2.DrvWifiList())
177
- print("Send command - get memorized WiFi list upload command")
177
+ logger.debug("Send command - get memorized WiFi list upload command")
178
178
  return self.send_order_msg_net(build)
179
179
  self.get_record_wifi_list2()
180
180
 
@@ -189,7 +189,7 @@ class MessageNetwork:
189
189
  todev_ble_sync=1,
190
190
  todev_Wifi_Configuration=dev_net_pb2.DrvWifiSet(configParam=status, Confssid=ssid),
191
191
  )
192
- print(
192
+ logger.debug(
193
193
  f"Send command - set network (disconnect, direct connect, forget, no operation reconnect) operation command (downlink ssid={ssid}, status={status})"
194
194
  )
195
195
  return self.send_order_msg_net(build)
@@ -2,6 +2,7 @@
2
2
  import datetime
3
3
  from abc import ABC
4
4
 
5
+ from pymammotion import logger
5
6
  from pymammotion.mammotion.commands.abstract_message import AbstractMessage
6
7
  from pymammotion.mammotion.commands.messages.navigation import MessageNavigation
7
8
  from pymammotion.proto.luba_msg import LubaMsg, MsgAttr, MsgCmdType, MsgDevice
@@ -36,7 +37,7 @@ class MessageSystem(AbstractMessage, ABC):
36
37
 
37
38
  def reset_system(self):
38
39
  build = MctlSys(todev_reset_system=1)
39
- print("Send command - send factory reset")
40
+ logger.debug("Send command - send factory reset")
40
41
  return self.send_order_msg_sys(build)
41
42
 
42
43
  def set_blade_control(self, on_off: int):
@@ -72,24 +73,24 @@ class MessageSystem(AbstractMessage, ABC):
72
73
  end_hour=0,
73
74
  end_min=0,
74
75
  )
75
- print(f"Send read and write sidelight command is_sidelight:{
76
+ logger.debug(f"Send read and write sidelight command is_sidelight:{
76
77
  is_sidelight}, operate:{operate}")
77
78
  build2 = MctlSys(todev_time_ctrl_light=build)
78
- print(f"Send command - send read and write sidelight command is_sidelight:{
79
+ logger.debug(f"Send command - send read and write sidelight command is_sidelight:{
79
80
  is_sidelight}, operate:{operate}, timeCtrlLight:{build}")
80
81
  return self.send_order_msg_sys(build2)
81
82
 
82
83
  def test_tool_order_to_sys(self, sub_cmd: int, param_id: int, param_value: list[int]):
83
84
  build = MCtrlSimulationCmdData(sub_cmd=sub_cmd, param_id=param_id, param_value=param_value)
84
- print(f"Send tool test command: subCmd={sub_cmd}, param_id:{
85
+ logger.debug(f"Send tool test command: subCmd={sub_cmd}, param_id:{
85
86
  param_id}, param_value={param_value}")
86
87
  build2 = MctlSys(simulation_cmd=build)
87
- print(f"Send tool test command: subCmd={sub_cmd}, param_id:{
88
+ logger.debug(f"Send tool test command: subCmd={sub_cmd}, param_id:{
88
89
  param_id}, param_value={param_value}")
89
90
  return self.send_order_msg_sys(build2)
90
91
 
91
- def read_and_set_rtk_paring_code(self, op: int, cgf: str):
92
- print(f"Send read and write base station configuration quality op:{
92
+ def read_and_set_rtk_paring_code(self, op: int, cgf: str | None = None):
93
+ logger.debug(f"Send read and write base station configuration quality op:{
93
94
  op}, cgf:{cgf}")
94
95
  return self.send_order_msg_sys(MctlSys(todev_lora_cfg_req=LoraCfgReq(op=op, cfg=cgf)))
95
96
 
@@ -98,7 +99,7 @@ class MessageSystem(AbstractMessage, ABC):
98
99
  self.messageNavigation.allpowerfull_rw_adapter_x3(id, context, rw)
99
100
  return
100
101
  build = MctlSys(bidire_comm_cmd=SysCommCmd(id=id, context=context, rw=rw))
101
- print(f"Send command - 9 general read and write command id={id}, context={context}, rw={rw}")
102
+ logger.debug(f"Send command - 9 general read and write command id={id}, context={context}, rw={rw}")
102
103
  if id == 5:
103
104
  # This logic doesnt make snese, but its what they had so..
104
105
  return self.send_order_msg_sys(build)
@@ -107,7 +108,7 @@ class MessageSystem(AbstractMessage, ABC):
107
108
  # Commented out as not needed and too many refs to try fix up
108
109
  # def factory_test_order(self, test_id: int, test_duration: int, expect: str):
109
110
  # new_builder = mow_to_app_qctools_info_t.Builder()
110
- # print(f"Factory tool print, expect={expect}")
111
+ # logger.debug(f"Factory tool logger.debug, expect={expect}")
111
112
  # if not expect:
112
113
  # build = new_builder.set_type_value(
113
114
  # test_id).set_time_of_duration(test_duration).build()
@@ -157,11 +158,11 @@ class MessageSystem(AbstractMessage, ABC):
157
158
  # else:
158
159
  # build = new_builder.set_type_value(
159
160
  # test_id).set_time_of_duration(test_duration).build()
160
- # print(f"Factory tool print, mow_to_app_qctools_info_t={
161
+ # logger.debug(f"Factory tool logger.debug, mow_to_app_qctools_info_t={
161
162
  # build.except_count}, mow_to_app_qctools_info_t22={build.except_list}")
162
163
  # build2 = MctlSys(mow_to_app_qctools_info=build)
163
- # print(f"Send command - factory tool test command testId={
164
- # test_id}, testDuration={test_duration}", "Factory tool print222", True)
164
+ # logger.debug(f"Send command - factory tool test command testId={
165
+ # test_id}, testDuration={test_duration}", "Factory tool logger.debug222", True)
165
166
  # return self.send_order_msg_sys(build2)
166
167
 
167
168
  def send_sys_set_date_time(self):
@@ -175,7 +176,7 @@ class MessageSystem(AbstractMessage, ABC):
175
176
  i7 = calendar.second
176
177
  i8 = calendar.utcoffset().total_seconds() // 60 if calendar.utcoffset() else 0
177
178
  i9 = 1 if calendar.dst() else 0
178
- print(f"Print time zone, time zone={
179
+ logger.debug(f"Print time zone, time zone={
179
180
  i8}, daylight saving time={i9} week={i4}")
180
181
  build = MctlSys(
181
182
  todev_data_time=SysSetDateTime(
@@ -190,7 +191,7 @@ class MessageSystem(AbstractMessage, ABC):
190
191
  daylight=i9,
191
192
  )
192
193
  )
193
- print(
194
+ logger.debug(
194
195
  f"Send command - synchronize time zone={i8}, daylight saving time={i9} week={i4}, day:{
195
196
  i3}, month:{i2}, hours:{i5}, minutes:{i6}, seconds:{i7}, year={i}",
196
197
  "Time synchronization",
@@ -222,16 +223,46 @@ class MessageSystem(AbstractMessage, ABC):
222
223
  count=count,
223
224
  )
224
225
  )
225
- print(f"Send command==== IOT slim data Act {
226
+ logger.debug(f"Send command==== IOT slim data Act {
226
227
  build.todev_report_cfg.act} {build}")
227
228
  return self.send_order_msg_sys(build)
228
229
 
229
- def get_report_cfg(
230
- self, timeout: int = 10000, period: int = 1000, no_change_period: int = 2000, stop: bool = False
231
- ):
230
+ def get_report_cfg_stop(self, timeout: int = 10000, period: int = 1000, no_change_period: int = 1000):
232
231
  mctlsys = MctlSys(
233
232
  todev_report_cfg=ReportInfoCfg(
234
- act=RptAct.RPT_STOP if stop else RptAct.RPT_START,
233
+ act=RptAct.RPT_STOP,
234
+ timeout=timeout,
235
+ period=period,
236
+ no_change_period=no_change_period,
237
+ count=1,
238
+ )
239
+ )
240
+
241
+ mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_CONNECT)
242
+ mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_RTK)
243
+ mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_DEV_LOCAL)
244
+ mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_WORK)
245
+ mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_DEV_STA)
246
+ mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_MAINTAIN)
247
+ mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_VISION_POINT)
248
+ mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_VIO)
249
+ mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_VISION_STATISTIC)
250
+
251
+ lubaMsg = LubaMsg()
252
+ lubaMsg.msgtype = MsgCmdType.MSG_CMD_TYPE_EMBED_SYS
253
+ lubaMsg.sender = MsgDevice.DEV_MOBILEAPP
254
+ lubaMsg.rcver = MsgDevice.DEV_MAINCTL
255
+ lubaMsg.msgattr = MsgAttr.MSG_ATTR_REQ
256
+ lubaMsg.seqs = 1
257
+ lubaMsg.version = 1
258
+ lubaMsg.subtype = 1
259
+ lubaMsg.sys = mctlsys
260
+ return lubaMsg.SerializeToString()
261
+
262
+ def get_report_cfg(self, timeout: int = 10000, period: int = 1000, no_change_period: int = 2000):
263
+ mctlsys = MctlSys(
264
+ todev_report_cfg=ReportInfoCfg(
265
+ act=RptAct.RPT_START,
235
266
  timeout=timeout,
236
267
  period=period,
237
268
  no_change_period=no_change_period,
@@ -5,7 +5,7 @@ from typing import Any, Awaitable, Callable
5
5
 
6
6
  import betterproto
7
7
 
8
- from pymammotion.aliyun.dataclass.dev_by_account_response import Device
8
+ from pymammotion.aliyun.model.dev_by_account_response import Device
9
9
  from pymammotion.data.model import RegionData
10
10
  from pymammotion.data.model.device import MowingDevice
11
11
  from pymammotion.data.state_manager import StateManager
@@ -190,6 +190,10 @@ class MammotionBaseDevice:
190
190
  async def _ble_sync(self):
191
191
  """Send ble sync command every 3 seconds or sooner."""
192
192
 
193
+ @abstractmethod
194
+ def stop(self):
195
+ """Stop everything ready for destroying."""
196
+
193
197
  async def start_sync(self, retry: int) -> None:
194
198
  """Start synchronization with the device."""
195
199
  await self.queue_command("get_device_base_info")
@@ -8,12 +8,10 @@ from enum import Enum
8
8
  from functools import cache
9
9
  from typing import Any
10
10
 
11
- from aiohttp import ClientSession
12
11
  from bleak.backends.device import BLEDevice
13
12
 
14
13
  from pymammotion.aliyun.cloud_gateway import CloudIOTGateway
15
- from pymammotion.aliyun.dataclass.dev_by_account_response import Device
16
- from pymammotion.const import MAMMOTION_DOMAIN
14
+ from pymammotion.aliyun.model.dev_by_account_response import Device
17
15
  from pymammotion.data.model.account import Credentials
18
16
  from pymammotion.data.model.device import MowingDevice
19
17
  from pymammotion.http.http import connect_http
@@ -105,7 +103,7 @@ class MammotionDevices:
105
103
  def get_device(self, mammotion_device_name: str) -> MammotionMixedDeviceManager:
106
104
  return self.devices.get(mammotion_device_name)
107
105
 
108
- def remove_device(self, name) -> None:
106
+ async def remove_device(self, name) -> None:
109
107
  device_for_removal = self.devices.pop(name)
110
108
  if device_for_removal.has_cloud():
111
109
  should_disconnect = {
@@ -115,6 +113,11 @@ class MammotionDevices:
115
113
  }
116
114
  if len(should_disconnect) == 0:
117
115
  device_for_removal.cloud()._mqtt.disconnect()
116
+ await device_for_removal.cloud().stop()
117
+ if device_for_removal.has_ble():
118
+ await device_for_removal.ble().stop()
119
+
120
+ del device_for_removal
118
121
 
119
122
 
120
123
  async def create_devices(
@@ -164,10 +167,7 @@ class Mammotion:
164
167
  exists: MammotionCloud | None = self.mqtt_list.get(account)
165
168
  if not exists or force:
166
169
  cloud_client = await self.login(account, password)
167
- else:
168
- cloud_client = exists.cloud_client
169
-
170
- await self.initiate_cloud_connection(account, cloud_client)
170
+ await self.initiate_cloud_connection(account, cloud_client)
171
171
 
172
172
  async def initiate_cloud_connection(self, account: str, cloud_client: CloudIOTGateway) -> None:
173
173
  if self.mqtt_list.get(account) is not None:
@@ -176,7 +176,6 @@ class Mammotion:
176
176
  self.add_cloud_devices(self.mqtt_list.get(account))
177
177
  return
178
178
 
179
- self.cloud_client = cloud_client
180
179
  mammotion_cloud = MammotionCloud(
181
180
  MammotionMQTT(
182
181
  region_id=cloud_client.region_response.data.regionId,
@@ -222,23 +221,22 @@ class Mammotion:
222
221
  async def login(self, account: str, password: str) -> CloudIOTGateway:
223
222
  """Login to mammotion cloud."""
224
223
  cloud_client = CloudIOTGateway()
225
- async with ClientSession(MAMMOTION_DOMAIN) as session:
226
- mammotion_http = await connect_http(account, password)
227
- country_code = mammotion_http.login_info.userInformation.domainAbbreviation
228
- _LOGGER.debug("CountryCode: " + country_code)
229
- _LOGGER.debug("AuthCode: " + mammotion_http.login_info.authorization_code)
230
- loop = asyncio.get_running_loop()
231
- cloud_client.set_http(mammotion_http)
232
- await loop.run_in_executor(
233
- None, cloud_client.get_region, country_code, mammotion_http.login_info.authorization_code
234
- )
235
- await cloud_client.connect()
236
- await cloud_client.login_by_oauth(country_code, mammotion_http.login_info.authorization_code)
237
- await loop.run_in_executor(None, cloud_client.aep_handle)
238
- await loop.run_in_executor(None, cloud_client.session_by_auth_code)
224
+ mammotion_http = await connect_http(account, password)
225
+ country_code = mammotion_http.login_info.userInformation.domainAbbreviation
226
+ _LOGGER.debug("CountryCode: " + country_code)
227
+ _LOGGER.debug("AuthCode: " + mammotion_http.login_info.authorization_code)
228
+ cloud_client.set_http(mammotion_http)
229
+ loop = asyncio.get_running_loop()
230
+ await loop.run_in_executor(
231
+ None, cloud_client.get_region, country_code, mammotion_http.login_info.authorization_code
232
+ )
233
+ await cloud_client.connect()
234
+ await cloud_client.login_by_oauth(country_code, mammotion_http.login_info.authorization_code)
235
+ await loop.run_in_executor(None, cloud_client.aep_handle)
236
+ await loop.run_in_executor(None, cloud_client.session_by_auth_code)
239
237
 
240
- await loop.run_in_executor(None, cloud_client.list_binding_by_account)
241
- return cloud_client
238
+ await loop.run_in_executor(None, cloud_client.list_binding_by_account)
239
+ return cloud_client
242
240
 
243
241
  def remove_device(self, name: str) -> None:
244
242
  self.devices.remove_device(name)
@@ -284,6 +282,14 @@ class Mammotion:
284
282
  return await device.cloud().start_map_sync()
285
283
  # TODO work with both with EITHER
286
284
 
285
+ async def get_stream_subscription(self, name: str):
286
+ device = self.get_device_by_name(name)
287
+ if self._preference is ConnectionPreference.WIFI:
288
+ if device.has_cloud():
289
+ _stream_response = await self.cloud().cloud_client.get_stream_subscription(device.cloud().iot_id)
290
+ _LOGGER.debug(_stream_response)
291
+ return _stream_response
292
+
287
293
  def mower(self, name: str):
288
294
  device = self.get_device_by_name(name)
289
295
  if device:
@@ -112,6 +112,11 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
112
112
  130, lambda: asyncio.ensure_future(self.run_periodic_sync_task())
113
113
  )
114
114
 
115
+ async def stop(self) -> None:
116
+ """Stop all tasks and disconnect."""
117
+ self._ble_sync_task.cancel()
118
+ await self._client.disconnect()
119
+
115
120
  async def queue_command(self, key: str, **kwargs: Any) -> bytes | None:
116
121
  return await self._send_command_with_args(key, **kwargs)
117
122
 
@@ -2,13 +2,15 @@ import asyncio
2
2
  import base64
3
3
  import json
4
4
  import logging
5
+ from asyncio import TimerHandle
5
6
  from collections import deque
6
7
  from typing import Any, Awaitable, Callable, Optional, cast
7
8
 
8
9
  import betterproto
9
10
 
10
11
  from pymammotion import CloudIOTGateway, MammotionMQTT
11
- from pymammotion.aliyun.dataclass.dev_by_account_response import Device
12
+ from pymammotion.aliyun.cloud_gateway import DeviceOfflineException, SetupException
13
+ from pymammotion.aliyun.model.dev_by_account_response import Device
12
14
  from pymammotion.data.model.device import MowingDevice
13
15
  from pymammotion.data.mqtt.event import ThingEventMessage
14
16
  from pymammotion.event.event import DataEvent
@@ -27,12 +29,12 @@ class MammotionCloud:
27
29
  def __init__(self, mqtt_client: MammotionMQTT, cloud_client: CloudIOTGateway) -> None:
28
30
  self.cloud_client = cloud_client
29
31
  self.loop = asyncio.get_event_loop()
30
- self._ble_sync_task = None
31
32
  self.is_ready = False
32
33
  self.command_queue = asyncio.Queue()
33
34
  self._waiting_queue = deque()
34
35
  self.mqtt_message_event = DataEvent()
35
36
  self.on_ready_event = DataEvent()
37
+ self.on_disconnected_event = DataEvent()
36
38
  self._operation_lock = asyncio.Lock()
37
39
  self._mqtt_client = mqtt_client
38
40
  self._mqtt_client.on_connected = self.on_connected
@@ -65,6 +67,7 @@ class MammotionCloud:
65
67
 
66
68
  async def on_disconnected(self) -> None:
67
69
  """Callback for when MQTT disconnects."""
70
+ await self.on_disconnected_event.data_event(None)
68
71
 
69
72
  async def process_queue(self) -> None:
70
73
  while True:
@@ -145,6 +148,8 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
145
148
  def __init__(self, mqtt: MammotionCloud, cloud_device: Device, mowing_state: MowingDevice) -> None:
146
149
  """Initialize MammotionBaseCloudDevice."""
147
150
  super().__init__(mowing_state, cloud_device)
151
+ self._ble_sync_task: TimerHandle | None = None
152
+ self.stopped = False
148
153
  self.on_ready_callback: Optional[Callable[[], Awaitable[None]]] = None
149
154
  self.loop = asyncio.get_event_loop()
150
155
  self._mqtt = mqtt
@@ -156,6 +161,7 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
156
161
  self.currentID = ""
157
162
  self._mqtt.mqtt_message_event.add_subscribers(self._parse_message_for_device)
158
163
  self._mqtt.on_ready_event.add_subscribers(self.on_ready)
164
+ self._mqtt.on_disconnected_event.add_subscribers(self.on_disconnect)
159
165
  self.set_queue_callback(self.queue_command)
160
166
 
161
167
  if self._mqtt.is_ready:
@@ -163,10 +169,32 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
163
169
 
164
170
  async def on_ready(self) -> None:
165
171
  """Callback for when MQTT is subscribed to events."""
166
- await self._ble_sync()
167
- await self.run_periodic_sync_task()
168
- if self.on_ready_callback:
169
- await self.on_ready_callback()
172
+ if self.stopped:
173
+ return
174
+ try:
175
+ await self._ble_sync()
176
+ if self._ble_sync_task is None or self._ble_sync_task.cancelled():
177
+ await self.run_periodic_sync_task()
178
+ if self.on_ready_callback:
179
+ await self.on_ready_callback()
180
+ except DeviceOfflineException:
181
+ await self.stop()
182
+ except SetupException:
183
+ await self.stop()
184
+
185
+ async def on_disconnect(self) -> None:
186
+ if self._ble_sync_task:
187
+ self._ble_sync_task.cancel()
188
+ loop = asyncio.get_event_loop()
189
+ self._mqtt.disconnect()
190
+ await loop.run_in_executor(None, self._mqtt.cloud_client.sign_out)
191
+
192
+ async def stop(self) -> None:
193
+ """Stop all tasks and disconnect."""
194
+ if self._ble_sync_task:
195
+ self._ble_sync_task.cancel()
196
+ self._mqtt.on_ready_event.remove_subscribers(self.on_ready)
197
+ self.stopped = True
170
198
 
171
199
  async def _ble_sync(self) -> None:
172
200
  command_bytes = self._commands.send_todev_ble_sync(3)
@@ -176,10 +204,11 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
176
204
  async def run_periodic_sync_task(self) -> None:
177
205
  """Send ble sync to robot."""
178
206
  try:
179
- if not self._mqtt._operation_lock.locked():
207
+ if not self._mqtt._operation_lock.locked() or not self.stopped:
180
208
  await self._ble_sync()
181
209
  finally:
182
- self.schedule_ble_sync()
210
+ if not self.stopped:
211
+ self.schedule_ble_sync()
183
212
 
184
213
  def schedule_ble_sync(self) -> None:
185
214
  """Periodically sync to keep connection alive."""
@@ -91,6 +91,9 @@ class MammotionMQTT:
91
91
  def disconnect(self) -> None:
92
92
  """Disconnect from MQTT Server."""
93
93
  logger.info("Disconnecting...")
94
+
95
+
96
+
94
97
  self._linkkit_client.disconnect()
95
98
 
96
99
  def _thing_on_thing_enable(self, user_data) -> None:
@@ -105,6 +108,15 @@ class MammotionMQTT:
105
108
  self._linkkit_client.subscribe_topic(
106
109
  f"/sys/{self._product_key}/{self._device_name}/app/down/thing/event/property/post_reply"
107
110
  )
111
+ self._linkkit_client.subscribe_topic(
112
+ f"/sys/{self._product_key}/{self._device_name}/app/down/thing/wifi/status/notify"
113
+ )
114
+ self._linkkit_client.subscribe_topic(
115
+ f"/sys/{self._product_key}/{self._device_name}/app/down/thing/wifi/connect/event/notify"
116
+ )
117
+ self._linkkit_client.subscribe_topic(
118
+ f"/sys/{self._product_key}/{self._device_name}/app/down/_thing/event/notify"
119
+ )
108
120
  self._linkkit_client.subscribe_topic(f"/sys/{self._product_key}/{self._device_name}/app/down/thing/events")
109
121
  self._linkkit_client.subscribe_topic(f"/sys/{self._product_key}/{self._device_name}/app/down/thing/status")
110
122
  self._linkkit_client.subscribe_topic(f"/sys/{self._product_key}/{self._device_name}/app/down/thing/properties")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pymammotion
3
- Version: 0.2.41
3
+ Version: 0.2.43
4
4
  Summary:
5
5
  License: GNU-3.0
6
6
  Author: Michael Arthur
@@ -1,17 +1,18 @@
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=X6Y9yyIXCwkPwgRqsBESUI5v6j-CKlJGJaEnMINyp8c,22780
3
+ pymammotion/aliyun/cloud_gateway.py,sha256=vMNt5MquL-t7C0-yDAXQEYA37RR4cj2rdLfh5iQ131o,25561
4
4
  pymammotion/aliyun/cloud_service.py,sha256=px7dUKow5Z7VyebjYzuKkzkm77XbUXYiFiYO_2e-UQ0,2207
5
- pymammotion/aliyun/dataclass/aep_response.py,sha256=8f6GIP58ve8gd6AL3HBoXxsy0n2q4ygWvjELGnoOnVc,452
6
- pymammotion/aliyun/dataclass/connect_response.py,sha256=Yz-fEbDzgGPTo5Of2oAjmFkSv08T7ze80pQU4k-gKIU,824
7
- pymammotion/aliyun/dataclass/dev_by_account_response.py,sha256=gskum11h9HPf4lKjLJKVrsxRl5BHaHJP2TPrI09SUYs,1032
8
- pymammotion/aliyun/dataclass/login_by_oauth_response.py,sha256=IXSLZ6XnOliOnyXo5Bh0ErqFjA11puACh_9NH0sSJGQ,1262
9
- pymammotion/aliyun/dataclass/regions_response.py,sha256=TBBWKF9j5iWjX4YbvMBHNDOnr0cEhbdfgApgCyJZhVg,651
10
- pymammotion/aliyun/dataclass/session_by_authcode_response.py,sha256=qW0pnnRXfyEuMbb9ORc013sYqZwO8z4i_h_SfSaA_bw,419
5
+ pymammotion/aliyun/model/aep_response.py,sha256=8f6GIP58ve8gd6AL3HBoXxsy0n2q4ygWvjELGnoOnVc,452
6
+ pymammotion/aliyun/model/connect_response.py,sha256=Yz-fEbDzgGPTo5Of2oAjmFkSv08T7ze80pQU4k-gKIU,824
7
+ pymammotion/aliyun/model/dev_by_account_response.py,sha256=QZeopszFlGI7wltla-XDyGMbg5QSnb4Q1wTINkkaJyA,1026
8
+ pymammotion/aliyun/model/login_by_oauth_response.py,sha256=IXSLZ6XnOliOnyXo5Bh0ErqFjA11puACh_9NH0sSJGQ,1262
9
+ pymammotion/aliyun/model/regions_response.py,sha256=TBBWKF9j5iWjX4YbvMBHNDOnr0cEhbdfgApgCyJZhVg,651
10
+ pymammotion/aliyun/model/session_by_authcode_response.py,sha256=qW0pnnRXfyEuMbb9ORc013sYqZwO8z4i_h_SfSaA_bw,419
11
+ pymammotion/aliyun/model/stream_subscription_response.py,sha256=po765WASQDboVCosbPEfDHNlanBf-3WMrA6iV3ezooY,357
11
12
  pymammotion/aliyun/tmp_constant.py,sha256=M4Hq_lrGB3LZdX6R2XohRPFoK1NDnNV-pTJwJcJ9838,6650
12
13
  pymammotion/bluetooth/__init__.py,sha256=LAl8jqZ1fPh-3mLmViNQsP3s814C1vsocYUa6oSaXt0,36
13
14
  pymammotion/bluetooth/ble.py,sha256=YfkfEK3TLJ8BaidjAXfUVFv8reLCu6U_lYa3Bo0pddw,2449
14
- pymammotion/bluetooth/ble_message.py,sha256=m-RyxHncCWrYFoeNg_G9Co5afNADZuCm32jQaqaIFc8,16211
15
+ pymammotion/bluetooth/ble_message.py,sha256=jQ_vece7a5WH_SW3pC_sdk6_Ch84bgWrRw3BatEje0c,16188
15
16
  pymammotion/bluetooth/const.py,sha256=CCqyHsYbB0BAYjwdhXt_n6eWWxmhlUrAFjvVv57mbvE,1749
16
17
  pymammotion/bluetooth/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
18
  pymammotion/bluetooth/data/convert.py,sha256=6DMwvzVr9FWCoQFIKSI2poFXjISc_m6X59g8FlVO0-o,800
@@ -21,8 +22,8 @@ pymammotion/const.py,sha256=lWRxvTVdXnNHuxqvRkjO5ziK0Ic-fZMM6J2dbe5M6Nc,385
21
22
  pymammotion/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
23
  pymammotion/data/model/__init__.py,sha256=aSyroxYQQS-WMRi6WmWm2js4wLa9nmsi160gx9tts4o,323
23
24
  pymammotion/data/model/account.py,sha256=vJM-KTf2q6eBfVC-UlNHBSmJvqHiCawZ40vnuhXhaz8,140
24
- pymammotion/data/model/device.py,sha256=OGOVm3h-jTfL-hpCE07o2tLRk34i0rRO-hFjY8AHQRw,11472
25
- pymammotion/data/model/device_config.py,sha256=_DlpxqrybTfkPFxV-gPjNzIte0AKA_Oa_JRmDcVmLdk,2823
25
+ pymammotion/data/model/device.py,sha256=s7S6Nicuk3acR8jxeeqZlVTZXB94DjYzo7gYdwjleUs,12071
26
+ pymammotion/data/model/device_config.py,sha256=RkEbnkubJQ8nXPfZpuGPe_h9mGZGtxCxDSc8mY7UCT8,2824
26
27
  pymammotion/data/model/enums.py,sha256=EpKmO8yVUZyEnTY4yH0DMMVKYNQM42zpW1maUu0i3IE,1582
27
28
  pymammotion/data/model/excute_boarder_params.py,sha256=9CpUqrygcle1C_1hDW-riLmm4map4ZbE842NXjcomEI,1394
28
29
  pymammotion/data/model/execute_boarder.py,sha256=9rd_h4fbcsXxgnLOd2rO2hWyD1abnTGc47QTEpp8DD0,1103
@@ -31,19 +32,19 @@ pymammotion/data/model/hash_list.py,sha256=efUSelsj39IArxV_GEN7niM2fsVa1OL8ZjTox
31
32
  pymammotion/data/model/location.py,sha256=PwmITejfI4pm7PI4rzqSuuHetwle6IJr_CV95435s2M,871
32
33
  pymammotion/data/model/mowing_modes.py,sha256=5TrHSijUyPtIDWpNtgzx_vFQukRJWRz4gIrUaXggKPw,827
33
34
  pymammotion/data/model/plan.py,sha256=mcadkSL7fQXy0iJ0q786I3GEQY4i6kmQXfW6Ri69lcQ,2906
34
- pymammotion/data/model/rapid_state.py,sha256=BdJFD_DlhrVneg-PqEruqCoMty-CR7q_9Qna-hk1yd8,1171
35
+ pymammotion/data/model/rapid_state.py,sha256=mIdhAG_LZXpVcybxqTLgLXkNOmVmDTn04B9PGIDA8Ls,1251
35
36
  pymammotion/data/model/region_data.py,sha256=OTV15vRyn9JORXsQPjWMNF1ZujuNhsOKl25KeqwMObA,3007
36
- pymammotion/data/model/report_info.py,sha256=IRVpsuEHg8zTrFIqrKUFMTz_y0u5GuWIRqf1V8mb0Fk,2226
37
+ pymammotion/data/model/report_info.py,sha256=gY3L4UGEH37PS314e8d7DWuIczQxTIMfcf66xoVe0sY,2210
37
38
  pymammotion/data/mqtt/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
38
39
  pymammotion/data/mqtt/event.py,sha256=plt53w3kHulB_MfbkxK9j7AfdQ5ahVU2s4kiQMoLio4,4612
39
40
  pymammotion/data/mqtt/properties.py,sha256=HkBPghr26L9_b4QaOi1DtPgb0UoPIOGSe9wb3kgnM6Y,2815
40
41
  pymammotion/data/mqtt/status.py,sha256=zqnlo-MzejEQZszl0i0Wucoc3E76x6UtI9JLxoBnu54,1067
41
- pymammotion/data/state_manager.py,sha256=V9fcvC7CSzw0tT-x7Xmh_EbCXgA4z7JPJLLF8ILMNY4,3788
42
+ pymammotion/data/state_manager.py,sha256=sP-y5jUFpL19sRwuaaSZcdGCn8jNOJhLg5sEkOTJhEw,4104
42
43
  pymammotion/event/__init__.py,sha256=mgATR6vPHACNQ-0zH5fi7NdzeTCDV1CZyaWPmtUusi8,115
43
44
  pymammotion/event/event.py,sha256=UzYnxV5DfvMDK3E06UvSzvzuBbaXOOUwO6xYt_zn9To,2034
44
45
  pymammotion/http/_init_.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
- pymammotion/http/http.py,sha256=OZHjQY9ZLdlsKK6MDqqV79JIH9WYWJe3-ey-WWbe2z4,2642
46
- pymammotion/http/model/http.py,sha256=_aVrtZZyR4EdR6GcuqMP9vH8qBLN7nKu0U2OeraCcG0,1607
46
+ pymammotion/http/http.py,sha256=gJU8w3j8NL_m5YnxfbRxxm4y9SW0UfKr1iAaMvLsfuM,3562
47
+ pymammotion/http/model/http.py,sha256=1NJP4hkBulcnpbD7Rcwltd8Ry9lnZzQVFJN8Oo7MKeM,1706
47
48
  pymammotion/mammotion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
49
  pymammotion/mammotion/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
50
  pymammotion/mammotion/commands/abstract_message.py,sha256=nw6r7694yzl7iJKqRqhLmAPRjd_TL_Xo_-JXq2_a_ug,222
@@ -52,20 +53,20 @@ pymammotion/mammotion/commands/messages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
52
53
  pymammotion/mammotion/commands/messages/driver.py,sha256=ANIOEtjF23k7W_R_Yfgzxmc7p1KSBCF-l-tijUtw0Yg,3709
53
54
  pymammotion/mammotion/commands/messages/media.py,sha256=ps0l06CXy5Ej--gTNCsyKttwo7yHLVrJUpn-wNJYecs,1150
54
55
  pymammotion/mammotion/commands/messages/navigation.py,sha256=4rXBL-mViWc38K6x1w5O-GjwV8UWS5xZXkf4aHYjs8A,23684
55
- pymammotion/mammotion/commands/messages/network.py,sha256=gD7NKVKg8U2KNbPvgOxvTJXbznWdpdPQo9jBsQSx4OI,8027
56
+ pymammotion/mammotion/commands/messages/network.py,sha256=61msRJWyXdrO8FbI_rhrO2K8R1qkGVUj5BFzNwm7lwg,8155
56
57
  pymammotion/mammotion/commands/messages/ota.py,sha256=XkeuWBZtpYMMBze6r8UN7dJXbe2FxUNGNnjwBpXJKM0,1240
57
- pymammotion/mammotion/commands/messages/system.py,sha256=a1XSqWdDTWLBspvjN8otT46ovz3GQ8zv3TT-1FayFbo,10912
58
+ pymammotion/mammotion/commands/messages/system.py,sha256=XoDvnERAMXIj6oK6l-P7kVBmloaIOAf9flCq0Q6vryg,12399
58
59
  pymammotion/mammotion/commands/messages/video.py,sha256=_8lJsU4sLm2CGnc7RDkueA0A51Ysui6x7SqFnhX8O2g,1007
59
60
  pymammotion/mammotion/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
61
  pymammotion/mammotion/control/joystick.py,sha256=QfBVxM_gxpWsZAGO90whtgxCI2tIZ3TTad9wHIPsU9s,5640
61
62
  pymammotion/mammotion/devices/__init__.py,sha256=f2qQFPgLGmV85W2hSlMUh5BYuht9o_Ar_JEAAMD4fsE,102
62
- pymammotion/mammotion/devices/base.py,sha256=l1IKBMbUX8_IGQuEhLaFyNWmT3jBG5eai4_EvYLKKs0,11042
63
- pymammotion/mammotion/devices/mammotion.py,sha256=JnT5q5z9AxPI71WBu4kwdJwpYoWCJ_WkU315Cy1bPQM,11701
64
- pymammotion/mammotion/devices/mammotion_bluetooth.py,sha256=sgGeyQeAeA3lQodcalRYS4nDNAzjfFs9SddIB1kadvw,17355
65
- pymammotion/mammotion/devices/mammotion_cloud.py,sha256=dk49VY9yHO3d-Nb17y-D4YIURs2FTLMzWHUYrxBYhtw,10116
63
+ pymammotion/mammotion/devices/base.py,sha256=53of3422C6g0jiKX0X4uo-eRI1Du31NA4RWLdw2O5u8,11131
64
+ pymammotion/mammotion/devices/mammotion.py,sha256=od4crY_1hbBwlDt5St_IV_QT0jOT_tlKv_iplf_CTu4,11966
65
+ pymammotion/mammotion/devices/mammotion_bluetooth.py,sha256=Lu8-85fBa-rGzssARqiydfQbPuIeeTcV-1ROIoYBKKM,17512
66
+ pymammotion/mammotion/devices/mammotion_cloud.py,sha256=H_O3LrEOSlKm1JB8AoF5of7olyyiDhZAzD2cBXULUlo,11317
66
67
  pymammotion/mqtt/__init__.py,sha256=Ocs5e-HLJvTuDpVXyECEsWIvwsUaxzj7lZ9mSYutNDY,105
67
68
  pymammotion/mqtt/mammotion_future.py,sha256=_OWqKOlUGl2yT1xOsXFQYpGd-1zQ63OxqXgy7KRQgYc,710
68
- pymammotion/mqtt/mammotion_mqtt.py,sha256=QZuqp2G5nywCFSwGNSayPI5JVL789yOxyr0UM0G7wzg,8295
69
+ pymammotion/mqtt/mammotion_mqtt.py,sha256=-n_GryLoyl-ioQUQRnth4syToh8MwWOj-pt2p41J0xQ,8750
69
70
  pymammotion/proto/__init__.py,sha256=v3_P1LOsYRBN9qCAhaWrWMX7GWHrox9XD3hdZQxcPZM,190
70
71
  pymammotion/proto/basestation.proto,sha256=_x5gAz3FkZXS1jtq4GgZgaDCuRU-UV-7HTFdsfQ3zbo,1034
71
72
  pymammotion/proto/basestation.py,sha256=js64_N2xQYRxWPRdVNEapO0qe7vBlfYnjW5sE8hi7hw,2026
@@ -117,7 +118,7 @@ pymammotion/utility/map.py,sha256=GYscVMg2cX3IPlNpCBNHDW0S55yS1WGRf1iHnNZ7TfQ,22
117
118
  pymammotion/utility/movement.py,sha256=N75oAoAgFydqoaOedYIxGUHmuTCtPzAOtb-d_29tpfI,615
118
119
  pymammotion/utility/periodic.py,sha256=MbeSb9cfhxzYmdT_RiE0dZe3H9IfbQW_zSqhmSX2RUc,3321
119
120
  pymammotion/utility/rocker_util.py,sha256=6tX7sS87qoQC_tsxbx3NLL-HgS08wtzXiZkhDiz7uo0,7179
120
- pymammotion-0.2.41.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
121
- pymammotion-0.2.41.dist-info/METADATA,sha256=PvEZh8Kc-G_xRw2ui8RLdm8ISx1Rn3yLlmUCxuNhGT8,4052
122
- pymammotion-0.2.41.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
123
- pymammotion-0.2.41.dist-info/RECORD,,
121
+ pymammotion-0.2.43.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
122
+ pymammotion-0.2.43.dist-info/METADATA,sha256=EG9s9oLqTmk_dzxkCpAAYxrCw9XP29gH_2N3L9yL_jM,4052
123
+ pymammotion-0.2.43.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
124
+ pymammotion-0.2.43.dist-info/RECORD,,
File without changes