pymammotion 0.1.9__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

pymammotion/__init__.py CHANGED
@@ -10,21 +10,13 @@ import os
10
10
 
11
11
  # works outside HA on its own
12
12
  from pymammotion.bluetooth.ble import LubaBLE
13
- from pymammotion.http.http import LubaHTTP, connect_http
13
+ from pymammotion.http.http import MammotionHTTP, connect_http
14
14
 
15
15
  # TODO make a working device that will work outside HA too.
16
16
  from pymammotion.mammotion.devices import MammotionBaseBLEDevice
17
17
  from pymammotion.mqtt import MammotionMQTT
18
18
 
19
-
20
-
21
- __all__ = [
22
- 'LubaBLE',
23
- 'LubaHTTP',
24
- 'connect_http',
25
- 'MammotionBaseBLEDevice',
26
- 'MammotionMQTT'
27
- ]
19
+ __all__ = ["LubaBLE", "MammotionHTTP", "connect_http", "MammotionBaseBLEDevice", "MammotionMQTT"]
28
20
 
29
21
  logger = logging.getLogger(__name__)
30
22
 
@@ -391,8 +391,6 @@ class CloudIOTGateway:
391
391
 
392
392
  def check_or_refresh_session(self):
393
393
  """Check or refresh the session."""
394
- if self.load_saved_params() is False:
395
- return False
396
394
  config = Config(
397
395
  app_key=self._app_key,
398
396
  app_secret=self._app_secret,
@@ -436,7 +434,7 @@ class CloudIOTGateway:
436
434
  # Carica la stringa JSON in un dizionario
437
435
  json.loads(response_body_str)
438
436
 
439
- def list_binding_by_account(self):
437
+ def list_binding_by_account(self) -> ListingDevByAccountResponse:
440
438
  """List bindings by account."""
441
439
  config = Config(
442
440
  app_key=self._app_key,
@@ -476,6 +474,7 @@ class CloudIOTGateway:
476
474
  raise Exception("Error in creating session: " + response_body_dict["msg"])
477
475
 
478
476
  self._listing_dev_by_account_response = ListingDevByAccountResponse.from_dict(response_body_dict)
477
+ return self._listing_dev_by_account_response
479
478
 
480
479
  def send_cloud_command(self, iot_id: str, command: bytes) -> str:
481
480
  """Send a cloud command to the specified IoT device."""
@@ -6,7 +6,6 @@ from mashumaro.mixins.orjson import DataClassORJSONMixin
6
6
 
7
7
  @dataclass
8
8
  class Device(DataClassORJSONMixin):
9
- productModel: str
10
9
  gmtModified: int
11
10
  netType: str
12
11
  nickName: str
@@ -26,6 +25,7 @@ class Device(DataClassORJSONMixin):
26
25
  status: int
27
26
  productImage: Optional[str] = None
28
27
  categoryImage: Optional[str] = None
28
+ productModel: Optional[str] = None
29
29
 
30
30
 
31
31
  @dataclass
@@ -0,0 +1,8 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class Credentials:
6
+ email: str
7
+ password: str
8
+ account_id: str
@@ -1,4 +1,5 @@
1
1
  """MowingDevice class to wrap around the betterproto dataclasses."""
2
+
2
3
  import math
3
4
  from dataclasses import dataclass
4
5
 
@@ -15,7 +16,7 @@ from pymammotion.proto.mctrl_driver import MctlDriver
15
16
  from pymammotion.proto.mctrl_nav import MctlNav
16
17
  from pymammotion.proto.mctrl_ota import MctlOta
17
18
  from pymammotion.proto.mctrl_pept import MctlPept
18
- from pymammotion.proto.mctrl_sys import MctlSys, MowToAppInfoT, SystemUpdateBufMsg, ReportInfoData
19
+ from pymammotion.proto.mctrl_sys import MctlSys, MowToAppInfoT, ReportInfoData, SystemUpdateBufMsg
19
20
  from pymammotion.utility.map import CoordinateConverter
20
21
 
21
22
 
@@ -99,12 +100,12 @@ class MowingDevice:
99
100
  if index == 0:
100
101
  self.location.position_type = location.pos_type
101
102
  self.location.orientation = location.real_toward / 10000
102
- self.location.device = coordinate_converter.enu_to_lla(parse_double(location.real_pos_y, 4.0), parse_double(location.real_pos_x, 4.0))
103
+ self.location.device = coordinate_converter.enu_to_lla(
104
+ parse_double(location.real_pos_y, 4.0), parse_double(location.real_pos_x, 4.0)
105
+ )
103
106
 
104
107
  self.report_data = self.report_data.from_dict(toapp_report_data.to_dict(casing=betterproto.Casing.SNAKE))
105
108
 
106
-
107
-
108
109
  def mow_info(self, toapp_mow_info: MowToAppInfoT):
109
110
  pass
110
111
 
@@ -3,7 +3,7 @@ from dataclasses import dataclass
3
3
 
4
4
  @dataclass
5
5
  class DeviceLimits:
6
- cutter_height_min: int
7
- cutter_height_max: int
6
+ blade_height_min: int
7
+ blade_height_max: int
8
8
  working_speed_min: float
9
9
  working_speed_max: float
@@ -34,7 +34,7 @@ class Location:
34
34
  RTK: Point
35
35
  dock: Dock
36
36
  position_type: int
37
- orientation: int # 360 degree rotation +-
37
+ orientation: int # 360 degree rotation +-
38
38
 
39
39
  def __init__(self):
40
40
  self.device = Point()
@@ -39,7 +39,8 @@ class MowOrder(IntEnum):
39
39
 
40
40
  class BypassStrategy(IntEnum):
41
41
  """Matches up with ultra_wave."""
42
+
42
43
  direct_touch = 0
43
44
  slow_touch = 1
44
45
  less_touch = 2
45
- no_touch = 3 # luba 2 yuka only or possibly value of 10
46
+ no_touch = 3 # luba 2 yuka only or possibly value of 10
@@ -1,4 +1,5 @@
1
- from dataclasses import dataclass, field, asdict
1
+ from dataclasses import asdict, dataclass, field
2
+
2
3
 
3
4
  @dataclass
4
5
  class ConnectData:
@@ -9,11 +10,12 @@ class ConnectData:
9
10
  @classmethod
10
11
  def from_dict(cls, data: dict):
11
12
  return cls(
12
- connect_type=data.get('connect_type', 0),
13
- ble_rssi=data.get('ble_rssi', 0),
14
- wifi_rssi=data.get('wifi_rssi', 0)
13
+ connect_type=data.get("connect_type", 0),
14
+ ble_rssi=data.get("ble_rssi", 0),
15
+ wifi_rssi=data.get("wifi_rssi", 0),
15
16
  )
16
17
 
18
+
17
19
  @dataclass
18
20
  class DeviceData:
19
21
  sys_status: int = 0
@@ -26,14 +28,15 @@ class DeviceData:
26
28
  @classmethod
27
29
  def from_dict(cls, data: dict):
28
30
  return cls(
29
- sys_status=data.get('sys_status', 0),
30
- charge_state=data.get('charge_state', 0),
31
- battery_val=data.get('battery_val', 0),
32
- sensor_status=data.get('sensor_status', 0),
33
- last_status=data.get('last_status', 0),
34
- sys_time_stamp=data.get('sys_time_stamp', "")
31
+ sys_status=data.get("sys_status", 0),
32
+ charge_state=data.get("charge_state", 0),
33
+ battery_val=data.get("battery_val", 0),
34
+ sensor_status=data.get("sensor_status", 0),
35
+ last_status=data.get("last_status", 0),
36
+ sys_time_stamp=data.get("sys_time_stamp", ""),
35
37
  )
36
38
 
39
+
37
40
  @dataclass
38
41
  class RTKData:
39
42
  status: int = 0
@@ -45,13 +48,14 @@ class RTKData:
45
48
  @classmethod
46
49
  def from_dict(cls, data: dict):
47
50
  return cls(
48
- status=data.get('status', 0),
49
- pos_level=data.get('pos_level', 0),
50
- gps_stars=data.get('gps_stars', 0),
51
- dis_status=data.get('dis_status', ""),
52
- co_view_stars=data.get('co_view_stars', 0)
51
+ status=data.get("status", 0),
52
+ pos_level=data.get("pos_level", 0),
53
+ gps_stars=data.get("gps_stars", 0),
54
+ dis_status=data.get("dis_status", ""),
55
+ co_view_stars=data.get("co_view_stars", 0),
53
56
  )
54
57
 
58
+
55
59
  @dataclass
56
60
  class LocationData:
57
61
  real_pos_x: int = 0
@@ -63,13 +67,14 @@ class LocationData:
63
67
  @classmethod
64
68
  def from_dict(cls, data: dict):
65
69
  return cls(
66
- real_pos_x=data.get('real_pos_x', 0),
67
- real_pos_y=data.get('real_pos_y', 0),
68
- real_toward=data.get('real_toward', 0),
69
- pos_type=data.get('pos_type', 0),
70
- bol_hash=data.get('bol_hash', "")
70
+ real_pos_x=data.get("real_pos_x", 0),
71
+ real_pos_y=data.get("real_pos_y", 0),
72
+ real_toward=data.get("real_toward", 0),
73
+ pos_type=data.get("pos_type", 0),
74
+ bol_hash=data.get("bol_hash", ""),
71
75
  )
72
76
 
77
+
73
78
  @dataclass
74
79
  class WorkData:
75
80
  path: int = 0
@@ -96,28 +101,29 @@ class WorkData:
96
101
  @classmethod
97
102
  def from_dict(cls, data: dict):
98
103
  return cls(
99
- path=data.get('path', 0),
100
- path_hash=data.get('path_hash', ""),
101
- progress=data.get('progress', 0),
102
- area=data.get('area', 0),
103
- bp_info=data.get('bp_info', 0),
104
- bp_hash=data.get('bp_hash', ""),
105
- bp_pos_x=data.get('bp_pos_x', 0),
106
- bp_pos_y=data.get('bp_pos_y', 0),
107
- real_path_num=data.get('real_path_num', ""),
108
- path_pos_x=data.get('path_pos_x', 0),
109
- path_pos_y=data.get('path_pos_y', 0),
110
- ub_zone_hash=data.get('ub_zone_hash', ""),
111
- ub_path_hash=data.get('ub_path_hash', ""),
112
- init_cfg_hash=data.get('init_cfg_hash', ""),
113
- ub_ecode_hash=data.get('ub_ecode_hash', ""),
114
- nav_run_mode=data.get('nav_run_mode', 0),
115
- test_mode_status=data.get('test_mode_status', 0),
116
- man_run_speed=data.get('man_run_speed', 0),
117
- nav_edit_status=data.get('nav_edit_status', 0),
118
- knife_height=data.get('knife_height', 0)
104
+ path=data.get("path", 0),
105
+ path_hash=data.get("path_hash", ""),
106
+ progress=data.get("progress", 0),
107
+ area=data.get("area", 0),
108
+ bp_info=data.get("bp_info", 0),
109
+ bp_hash=data.get("bp_hash", ""),
110
+ bp_pos_x=data.get("bp_pos_x", 0),
111
+ bp_pos_y=data.get("bp_pos_y", 0),
112
+ real_path_num=data.get("real_path_num", ""),
113
+ path_pos_x=data.get("path_pos_x", 0),
114
+ path_pos_y=data.get("path_pos_y", 0),
115
+ ub_zone_hash=data.get("ub_zone_hash", ""),
116
+ ub_path_hash=data.get("ub_path_hash", ""),
117
+ init_cfg_hash=data.get("init_cfg_hash", ""),
118
+ ub_ecode_hash=data.get("ub_ecode_hash", ""),
119
+ nav_run_mode=data.get("nav_run_mode", 0),
120
+ test_mode_status=data.get("test_mode_status", 0),
121
+ man_run_speed=data.get("man_run_speed", 0),
122
+ nav_edit_status=data.get("nav_edit_status", 0),
123
+ knife_height=data.get("knife_height", 0),
119
124
  )
120
125
 
126
+
121
127
  @dataclass
122
128
  class ReportData:
123
129
  connect: ConnectData = field(default_factory=ConnectData)
@@ -126,16 +132,15 @@ class ReportData:
126
132
  locations: list[LocationData] = field(default_factory=list)
127
133
  work: WorkData = field(default_factory=WorkData)
128
134
 
129
-
130
135
  def from_dict(self, data: dict):
131
136
  locations = self.locations
132
- if data.get('locations') is not None:
133
- locations=[LocationData.from_dict(loc) for loc in data.get('locations', [])]
137
+ if data.get("locations") is not None:
138
+ locations = [LocationData.from_dict(loc) for loc in data.get("locations", [])]
134
139
 
135
140
  return ReportData(
136
- connect=ConnectData.from_dict(data.get('connect', asdict(self.connect))),
137
- dev=DeviceData.from_dict(data.get('dev', asdict(self.dev))),
138
- rtk=RTKData.from_dict(data.get('rtk', asdict(self.rtk))),
141
+ connect=ConnectData.from_dict(data.get("connect", asdict(self.connect))),
142
+ dev=DeviceData.from_dict(data.get("dev", asdict(self.dev))),
143
+ rtk=RTKData.from_dict(data.get("rtk", asdict(self.rtk))),
139
144
  locations=locations,
140
- work=WorkData.from_dict(data.get('work', asdict(self.work)))
141
- )
145
+ work=WorkData.from_dict(data.get("work", asdict(self.work))),
146
+ )
@@ -1,6 +1,6 @@
1
1
  from base64 import b64decode
2
2
  from dataclasses import dataclass
3
- from typing import Any, Literal, Union
3
+ from typing import Any, Literal, Optional, Union
4
4
 
5
5
  from google.protobuf import json_format
6
6
  from mashumaro.mixins.orjson import DataClassORJSONMixin
@@ -67,6 +67,17 @@ class GeneralParams(DataClassORJSONMixin):
67
67
  tenantInstanceId: str
68
68
  value: Any
69
69
 
70
+ # Campi opzionali
71
+ checkFailedData: Optional[dict] = None
72
+ _tenantId: Optional[str] = None
73
+ generateTime: Optional[int] = None
74
+ JMSXDeliveryCount: Optional[int] = None
75
+ qos: Optional[int] = None
76
+ requestId: Optional[str] = None
77
+ _categoryKey: Optional[str] = None
78
+ deviceType: Optional[str] = None
79
+ _traceId: Optional[str] = None
80
+
70
81
 
71
82
  @dataclass
72
83
  class DeviceProtobufMsgEventParams(GeneralParams):
@@ -88,3 +99,23 @@ class ThingEventMessage(DataClassORJSONMixin):
88
99
  id: str
89
100
  params: Union[DeviceProtobufMsgEventParams, DeviceWarningEventParams]
90
101
  version: Literal["1.0"]
102
+
103
+ @classmethod
104
+ def from_dicts(cls, payload: dict) -> "ThingEventMessage":
105
+ """Deserializza il payload JSON in un'istanza di ThingEventMessage."""
106
+ method = payload.get("method")
107
+ event_id = payload.get("id")
108
+ params_dict = payload.get("params", {})
109
+ version = payload.get("version")
110
+
111
+ # Determina quale classe usare per i parametri
112
+ identifier = params_dict.get("identifier")
113
+ if identifier == "device_protobuf_msg_event":
114
+ params_obj = DeviceProtobufMsgEventParams(**params_dict)
115
+ elif identifier == "device_warning_event":
116
+ params_obj = DeviceWarningEventParams(**params_dict)
117
+ else:
118
+ raise ValueError(f"Unknown identifier: {identifier}")
119
+
120
+ # Crea e restituisce l'istanza di ThingEventMessage
121
+ return cls(method=method, id=event_id, params=params_obj, version=version)
pymammotion/http/http.py CHANGED
@@ -1,10 +1,12 @@
1
1
  from dataclasses import dataclass
2
- from typing import Generic, Literal, TypeVar
2
+ from typing import Generic, Literal, Optional, TypeVar
3
3
 
4
4
  from aiohttp import ClientSession
5
+ from aiohttp.hdrs import AUTHORIZATION
5
6
  from mashumaro import DataClassDictMixin
6
7
  from mashumaro.mixins.orjson import DataClassORJSONMixin
7
8
 
9
+ from pymammotion.aliyun.dataclass.connect_response import Device
8
10
  from pymammotion.const import (
9
11
  MAMMOTION_CLIENT_ID,
10
12
  MAMMOTION_CLIENT_SECRET,
@@ -25,7 +27,7 @@ class Response(DataClassDictMixin, Generic[DataT]):
25
27
  class LoginResponseUserInformation(DataClassORJSONMixin):
26
28
  areaCode: str
27
29
  domainAbbreviation: str
28
- email: str
30
+ email: Optional[str]
29
31
  userId: str
30
32
  userAccount: str
31
33
  authType: str
@@ -44,11 +46,11 @@ class LoginResponseData(DataClassORJSONMixin):
44
46
  jti: str
45
47
 
46
48
 
47
- class LubaHTTP:
48
- def __init__(self, session: ClientSession, login: LoginResponseData):
49
- self._session = session
50
- self._session.headers["Authorization"] = f"Bearer {login.access_token}"
51
- self._login = login
49
+ class MammotionHTTP:
50
+ def __init__(self, login: LoginResponseData):
51
+ self._headers = dict()
52
+ self._headers["Authorization"] = f"Bearer {login.access_token}"
53
+ self.login = login
52
54
 
53
55
  @classmethod
54
56
  async def login(cls, session: ClientSession, username: str, password: str) -> Response[LoginResponseData]:
@@ -63,14 +65,13 @@ class LubaHTTP:
63
65
  ),
64
66
  ) as resp:
65
67
  data = await resp.json()
66
- print(data)
67
68
  # TODO catch errors from mismatch user / password
68
69
  # Assuming the data format matches the expected structure
69
70
  login_response_data = LoginResponseData.from_dict(data["data"])
70
71
  return Response(data=login_response_data, code=data["code"], msg=data["msg"])
71
72
 
72
73
 
73
- async def connect_http(username: str, password: str) -> LubaHTTP:
74
+ async def connect_http(username: str, password: str) -> MammotionHTTP:
74
75
  async with ClientSession(MAMMOTION_DOMAIN) as session:
75
- login_response = await LubaHTTP.login(session, username, password)
76
- return LubaHTTP(session, login_response.data)
76
+ login_response = await MammotionHTTP.login(session, username, password)
77
+ return MammotionHTTP(login_response.data)
@@ -4,14 +4,11 @@ from pymammotion.mammotion.commands.messages.ota import MessageOta
4
4
  from pymammotion.mammotion.commands.messages.system import MessageSystem
5
5
  from pymammotion.mammotion.commands.messages.video import MessageVideo
6
6
  from pymammotion.proto import dev_net_pb2, luba_msg_pb2
7
- from pymammotion.utility.device_type import DeviceType
8
7
 
9
8
 
10
9
  class MammotionCommand(MessageSystem, MessageNavigation, MessageNetwork, MessageOta, MessageVideo):
11
10
  """MQTT commands for Luba."""
12
11
 
13
-
14
-
15
12
  def __init__(self, device_name: str) -> None:
16
13
  self._device_name = device_name
17
14
  self._product_key = ""
@@ -26,7 +23,6 @@ class MammotionCommand(MessageSystem, MessageNavigation, MessageNetwork, Message
26
23
  def set_device_product_key(self, product_key: str) -> None:
27
24
  self._product_key = product_key
28
25
 
29
-
30
26
  """BLE commands for Luba."""
31
27
 
32
28
  def send_todev_ble_sync(self, sync_type: int) -> bytes:
@@ -34,7 +34,10 @@ logger = logging.getLogger(__name__)
34
34
  class MessageNavigation(AbstractMessage, ABC):
35
35
  def get_msg_device(self, msg_type: MsgCmdType, msg_device: MsgDevice) -> MsgDevice:
36
36
  """Changes the rcver name if it's not a luba1."""
37
- if not DeviceType.is_luba1(self.get_device_name(), self.get_device_product_key()) and msg_type == MsgCmdType.MSG_CMD_TYPE_NAV:
37
+ if (
38
+ not DeviceType.is_luba1(self.get_device_name(), self.get_device_product_key())
39
+ and msg_type == MsgCmdType.MSG_CMD_TYPE_NAV
40
+ ):
38
41
  return MsgDevice.DEV_NAVIGATION
39
42
  return msg_device
40
43
 
@@ -3,15 +3,17 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ import base64
6
7
  import codecs
7
8
  import json
8
9
  import logging
9
10
  from abc import abstractmethod
10
11
  from enum import Enum
11
- from typing import Any, cast
12
+ from typing import Any, Callable, Optional, cast
12
13
  from uuid import UUID
13
14
 
14
15
  import betterproto
16
+ from aiohttp import ClientSession
15
17
  from bleak import BleakClient
16
18
  from bleak.backends.device import BLEDevice
17
19
  from bleak.backends.service import BleakGATTCharacteristic, BleakGATTServiceCollection
@@ -23,11 +25,15 @@ from bleak_retry_connector import (
23
25
  establish_connection,
24
26
  )
25
27
 
28
+ from pymammotion.aliyun.cloud_gateway import CloudIOTGateway
26
29
  from pymammotion.bluetooth import BleMessage
30
+ from pymammotion.const import MAMMOTION_DOMAIN
27
31
  from pymammotion.data.model import RegionData
32
+ from pymammotion.data.model.account import Credentials
28
33
  from pymammotion.data.model.device import MowingDevice
29
34
  from pymammotion.data.mqtt.event import ThingEventMessage
30
35
  from pymammotion.data.state_manager import StateManager
36
+ from pymammotion.http.http import MammotionHTTP, connect_http
31
37
  from pymammotion.mammotion.commands.mammotion_command import MammotionCommand
32
38
  from pymammotion.mqtt import MammotionMQTT
33
39
  from pymammotion.proto.luba_msg import LubaMsg
@@ -71,7 +77,7 @@ _LOGGER = logging.getLogger(__name__)
71
77
  def slashescape(err):
72
78
  """Escape a slash character."""
73
79
  # print err, dir(err), err.start, err.end, err.object[:err.start]
74
- thebyte = err.object[err.start: err.end]
80
+ thebyte = err.object[err.start : err.end]
75
81
  repl = "\\x" + hex(ord(thebyte))[2:]
76
82
  return (repl, err.end)
77
83
 
@@ -106,6 +112,12 @@ async def _handle_retry(fut: asyncio.Future[None], func, command: bytes) -> None
106
112
  await func(command)
107
113
 
108
114
 
115
+ async def _handle_retry_cloud(fut: asyncio.Future[None], func, iotId: str, command: bytes) -> None:
116
+ """Handle a retry."""
117
+ if not fut.done():
118
+ func(iotId, command)
119
+
120
+
109
121
  class ConnectionPreference(Enum):
110
122
  """Enum for connection preference."""
111
123
 
@@ -118,20 +130,83 @@ class MammotionDevice:
118
130
  """Represents a Mammotion device."""
119
131
 
120
132
  _ble_device: MammotionBaseBLEDevice | None = None
133
+ _cloud_device: MammotionBaseCloudDevice | None = None
134
+ _devices_list = []
121
135
 
122
136
  def __init__(
123
- self,
124
- ble_device: BLEDevice,
125
- preference: ConnectionPreference = ConnectionPreference.EITHER,
137
+ self,
138
+ ble_device: BLEDevice,
139
+ cloud_credentials: Credentials | None = None,
140
+ preference: ConnectionPreference = ConnectionPreference.EITHER,
126
141
  ) -> None:
127
142
  """Initialize MammotionDevice."""
128
143
  if ble_device:
129
144
  self._ble_device = MammotionBaseBLEDevice(ble_device)
130
145
  self._preference = preference
131
146
 
147
+ if cloud_credentials:
148
+ self.initiate_cloud_connection(cloud_credentials.account_id or cloud_credentials.email, cloud_credentials.password)
149
+
150
+ async def initiate_cloud_connection(self, account: str, password: str):
151
+ cloud_client = await self.login(account, password)
152
+
153
+ _mammotion_mqtt = MammotionMQTT(region_id=cloud_client._region.data.regionId,
154
+ product_key=cloud_client._aep_response.data.productKey,
155
+ device_name=cloud_client._aep_response.data.deviceName,
156
+ device_secret=cloud_client._aep_response.data.deviceSecret,
157
+ iot_token=cloud_client._session_by_authcode_response.data.iotToken,
158
+ client_id=cloud_client._client_id)
159
+
160
+ _mammotion_mqtt._cloud_client = cloud_client
161
+ _mammotion_mqtt.connect_async()
162
+
163
+ self._devices_list = []
164
+ for device in cloud_client._listing_dev_by_account_response.data.data:
165
+ if (device.deviceName.startswith(("Luba-", "Yuka-"))):
166
+ dev = MammotionBaseCloudDevice(
167
+ mqtt_client=_mammotion_mqtt,
168
+ iot_id=device.iotId,
169
+ device_name=device.deviceName,
170
+ nick_name=device.nickName
171
+ )
172
+ self._devices_list.append(dev)
173
+
174
+ @staticmethod
175
+ async def login(account: str, password: str) -> CloudIOTGateway:
176
+ """Login to mammotion cloud."""
177
+ cloud_client = CloudIOTGateway()
178
+ async with ClientSession(MAMMOTION_DOMAIN) as session:
179
+ mammotion_http = await connect_http(account, password)
180
+ country_code = mammotion_http.login.userInformation.domainAbbreviation
181
+ _LOGGER.debug("CountryCode: " + country_code)
182
+ _LOGGER.debug("AuthCode: " + mammotion_http.login.authorization_code)
183
+ cloud_client.get_region(country_code, mammotion_http.login.authorization_code)
184
+ await cloud_client.connect()
185
+ await cloud_client.login_by_oauth(country_code, mammotion_http.login.authorization_code)
186
+ cloud_client.aep_handle()
187
+ cloud_client.session_by_auth_code()
188
+
189
+ cloud_client.list_binding_by_account()
190
+ return cloud_client
191
+
192
+
132
193
  async def send_command(self, key: str):
133
194
  """Send a command to the device."""
134
- return await self._ble_device.command(key)
195
+ if self._preference is ConnectionPreference.BLUETOOTH:
196
+ return await self._ble_device.command(key)
197
+ if self._preference is ConnectionPreference.WIFI:
198
+ return await self._cloud_device.command(key)
199
+ # TODO work with both with EITHER
200
+
201
+ async def send_command_with_args(self, key, kwargs):
202
+ """Send a command with args to the device."""
203
+ if self._preference is ConnectionPreference.BLUETOOTH:
204
+ return await self._ble_device.command(key, **kwargs)
205
+ if self._preference is ConnectionPreference.WIFI:
206
+ return await self._cloud_device.command(key, **kwargs)
207
+ # TODO work with both with EITHER
208
+
209
+
135
210
 
136
211
 
137
212
  def has_field(message: betterproto.Message) -> bool:
@@ -318,8 +393,10 @@ class MammotionBaseDevice:
318
393
 
319
394
  await self._send_command_with_args("get_hash_response", total_frame=1, current_frame=1)
320
395
 
321
- await self._send_command_with_args("get_area_name_list",
322
- device_id=self.luba_msg.device.net.toapp_wifi_iot_status.devicename)
396
+ await self._send_command_with_args(
397
+ "get_area_name_list", device_id=self.luba_msg.device.net.toapp_wifi_iot_status.devicename
398
+ )
399
+
323
400
  # sub_cmd 3 is job hashes??
324
401
  # sub_cmd 4 is dump location (yuka)
325
402
  # jobs list
@@ -545,7 +622,6 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
545
622
  new_msg = LubaMsg().parse(data)
546
623
  if betterproto.serialized_on_wire(new_msg.net):
547
624
  if new_msg.net.todev_ble_sync != 0 or has_field(new_msg.net.toapp_wifi_iot_status):
548
-
549
625
  if has_field(new_msg.net.toapp_wifi_iot_status) and self._commands.get_device_product_key() == "":
550
626
  self._commands.set_device_product_key(new_msg.net.toapp_wifi_iot_status.productkey)
551
627
 
@@ -713,58 +789,149 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
713
789
  """Base class for Mammotion Cloud devices."""
714
790
 
715
791
  def __init__(
716
- self,
717
- mqtt_client: MammotionMQTT,
718
- iot_id: str,
719
- device_name: str,
720
- nick_name: str,
721
- **kwargs: Any,
792
+ self,
793
+ mqtt_client: MammotionMQTT,
794
+ iot_id: str,
795
+ device_name: str,
796
+ nick_name: str,
797
+ **kwargs: Any,
722
798
  ) -> None:
723
799
  """Initialize MammotionBaseCloudDevice."""
724
800
  super().__init__()
801
+ self._ble_sync_task = None
802
+ self.is_ready = False
725
803
  self._mqtt_client = mqtt_client
726
804
  self.iot_id = iot_id
727
805
  self.nick_name = nick_name
728
806
  self._command_futures = {}
729
807
  self._commands: MammotionCommand = MammotionCommand(device_name)
730
- self.loop = asyncio.get_event_loop()
808
+ self.currentID = ""
809
+ self.on_ready_callback: Optional[Callable[[], None]] = None
810
+ self._operation_lock = asyncio.Lock()
811
+
812
+ self._mqtt_client.on_connected = self.on_connected
813
+ self._mqtt_client.on_disconnected = self.on_disconnected
814
+ self._mqtt_client.on_message = self._on_mqtt_message
815
+ self._mqtt_client.on_ready = self.on_ready
816
+ if self._mqtt_client.is_connected:
817
+ self._ble_sync()
818
+ self.run_periodic_sync_task()
819
+
820
+ # temporary for testing only
821
+ # self._start_sync_task = self.loop.call_later(30, lambda: asyncio.ensure_future(self.start_sync(0)))
822
+
823
+ def on_ready(self):
824
+ """Callback for when MQTT is subscribed to events."""
825
+ if self.on_ready_callback:
826
+ self.on_ready_callback()
827
+
828
+ def on_connected(self):
829
+ """Callback for when MQTT connects."""
830
+ self._ble_sync()
831
+ self.run_periodic_sync_task()
832
+
833
+ def on_disconnected(self):
834
+ """Callback for when MQTT disconnects."""
731
835
 
732
- def _on_mqtt_message(self, topic: str, payload: str) -> None:
836
+ def _ble_sync(self):
837
+ command_bytes = self._commands.send_todev_ble_sync(3)
838
+ self._mqtt_client.get_cloud_client().send_cloud_command(self.iot_id, command_bytes)
839
+
840
+ async def run_periodic_sync_task(self) -> None:
841
+ """Send ble sync to robot."""
842
+ try:
843
+ self._ble_sync()
844
+ finally:
845
+ self.schedule_ble_sync()
846
+
847
+ def schedule_ble_sync(self):
848
+ """Periodically sync to keep connection alive."""
849
+ if self._mqtt_client is not None and self._mqtt_client.is_connected:
850
+ self._ble_sync_task = self.loop.call_later(
851
+ 160, lambda: asyncio.ensure_future(self.run_periodic_sync_task())
852
+ )
853
+
854
+ def _on_mqtt_message(self, topic: str, payload: str, iot_id: str) -> None:
733
855
  """Handle incoming MQTT messages."""
734
856
  _LOGGER.debug("MQTT message received on topic %s: %s", topic, payload)
735
- payload = json.loads(payload)
736
- message_id = self._extract_message_id(payload)
737
- if message_id and message_id in self._command_futures:
738
- self._parse_mqtt_response(topic=topic, payload=payload)
739
- future = self._command_futures.pop(message_id)
740
- if not future.done():
741
- future.set_result(payload)
857
+
858
+ json_str = json.dumps(payload)
859
+ payload = json.loads(json_str)
860
+
861
+ self._handle_mqtt_message(topic, payload)
742
862
 
743
863
  async def _send_command(self, key: str, retry: int | None = None) -> bytes | None:
744
864
  """Send command to device via MQTT and read response."""
745
- future = self.loop.create_future()
746
- command_bytes = getattr(self._commands, key)()
747
- message_id = self._mqtt_client.get_cloud_client().send_cloud_command(self.iot_id, command_bytes)
748
- if message_id != "":
749
- self._command_futures[message_id] = future
865
+ if self._operation_lock.locked():
866
+ _LOGGER.debug("%s: Operation already in progress, waiting for it to complete;", self.nick_name)
867
+
868
+ async with self._operation_lock:
750
869
  try:
751
- return await asyncio.wait_for(future, timeout=TIMEOUT_CLOUD_RESPONSE)
752
- except asyncio.TimeoutError:
753
- _LOGGER.error("Command '%s' timed out", key)
754
- return None
870
+ command_bytes = getattr(self._commands, key)()
871
+ return await self._send_command_locked(key, command_bytes)
872
+ except Exception as ex:
873
+ _LOGGER.exception("%s: error in sending command - %s", self.nick_name, ex)
874
+ raise
875
+
876
+ async def _send_command_locked(self, key: str, command: bytes) -> bytes:
877
+ """Send command to device and read response."""
878
+ try:
879
+ return await self._execute_command_locked(key, command)
880
+ except Exception as ex:
881
+ # Disconnect so we can reset state and try again
882
+ await asyncio.sleep(DBUS_ERROR_BACKOFF_TIME)
883
+ _LOGGER.debug(
884
+ "%s: error in _send_command_locked: %s",
885
+ self.nick_name,
886
+ ex,
887
+ )
888
+ raise
889
+
890
+ async def _execute_command_locked(self, key: str, command: bytes) -> bytes:
891
+ """Execute command and read response."""
892
+ assert self._mqtt_client is not None
893
+ self._notify_future = self.loop.create_future()
894
+ self._key = key
895
+ _LOGGER.debug("%s: Sending command: %s", self.nick_name, key)
896
+ self._mqtt_client.get_cloud_client().send_cloud_command(self.iot_id, command)
897
+
898
+ retry_handle = self.loop.call_at(
899
+ self.loop.time() + 20,
900
+ lambda: asyncio.ensure_future(
901
+ _handle_retry_cloud(
902
+ self._notify_future, self._mqtt_client.get_cloud_client().send_cloud_command, self.iot_id, command
903
+ )
904
+ ),
905
+ )
906
+ timeout = 20
907
+ timeout_handle = self.loop.call_at(self.loop.time() + timeout, _handle_timeout, self._notify_future)
908
+ timeout_expired = False
909
+ try:
910
+ notify_msg = await self._notify_future
911
+ except asyncio.TimeoutError:
912
+ timeout_expired = True
913
+ raise
914
+ finally:
915
+ if not timeout_expired:
916
+ timeout_handle.cancel()
917
+ retry_handle.cancel()
918
+ self._notify_future = None
919
+
920
+ _LOGGER.debug("%s: Message received", self.nick_name)
921
+
922
+ return notify_msg
755
923
 
756
924
  async def _send_command_with_args(self, key: str, **kwargs: any) -> bytes | None:
757
925
  """Send command with arguments to device via MQTT and read response."""
758
- future = self.loop.create_future()
759
- command_bytes = getattr(self._commands, key)(**kwargs)
760
- message_id = self._mqtt_client.get_cloud_client().send_cloud_command(self.iot_id, command_bytes)
761
- if message_id != "":
762
- self._command_futures[message_id] = future
926
+ if self._operation_lock.locked():
927
+ _LOGGER.debug("%s: Operation already in progress, waiting for it to complete;", self.nick_name)
928
+ async with self._operation_lock:
763
929
  try:
764
- return await asyncio.wait_for(future, timeout=TIMEOUT_CLOUD_RESPONSE)
765
- except asyncio.TimeoutError:
766
- _LOGGER.error("Command '%s' timed out", key)
767
- return None
930
+ command_bytes = getattr(self._commands, key)(**kwargs)
931
+ return await self._send_command_locked(key, command_bytes)
932
+ except Exception as ex:
933
+ _LOGGER.exception("%s: error in sending command - %s", self.nick_name, ex)
934
+ raise
768
935
 
769
936
  def _extract_message_id(self, payload: dict) -> str:
770
937
  """Extract the message ID from the payload."""
@@ -782,10 +949,21 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
782
949
  def _parse_mqtt_response(self, topic: str, payload: dict) -> None:
783
950
  """Parse the MQTT response."""
784
951
  if topic.endswith("/app/down/thing/events"):
785
- event = ThingEventMessage(**payload)
952
+ _LOGGER.debug("Thing event received")
953
+ event = ThingEventMessage.from_dicts(payload)
786
954
  params = event.params
787
955
  if params.identifier == "device_protobuf_msg_event":
788
- self._update_raw_data(cast(bytes, params.value.content))
956
+ _LOGGER.debug("Protobuf event")
957
+ binary_data = base64.b64decode(params.value.get("content", ""))
958
+ self._update_raw_data(cast(bytes, binary_data))
959
+ new_msg = LubaMsg().parse(cast(bytes, binary_data))
960
+ if self._notify_future and not self._notify_future.done():
961
+ self._notify_future.set_result(new_msg)
962
+ asyncio.run(self._state_manager.notification(new_msg))
963
+
964
+ def _handle_mqtt_message(self, topic: str, payload: dict) -> None:
965
+ """Async handler for incoming MQTT messages."""
966
+ self._parse_mqtt_response(topic=topic, payload=payload)
789
967
 
790
968
  async def _disconnect(self):
791
969
  """Disconnect the MQTT client."""
@@ -8,7 +8,7 @@ from logging import getLogger
8
8
  from typing import Callable, Optional, cast
9
9
 
10
10
  from linkkit.linkkit import LinkKit
11
- from paho.mqtt.client import Client, MQTTMessage, MQTTv311, connack_string
11
+ from paho.mqtt.client import MQTTMessage
12
12
 
13
13
  from pymammotion.aliyun.cloud_gateway import CloudIOTGateway
14
14
  from pymammotion.data.mqtt.event import ThingEventMessage
@@ -22,8 +22,6 @@ logger = getLogger(__name__)
22
22
  class MammotionMQTT:
23
23
  """MQTT client for pymammotion."""
24
24
 
25
- _cloud_client = None
26
-
27
25
  def __init__(
28
26
  self,
29
27
  region_id: str,
@@ -35,8 +33,11 @@ class MammotionMQTT:
35
33
  ):
36
34
  """Create instance of MammotionMQTT."""
37
35
  super().__init__()
38
-
36
+ self._cloud_client = None
37
+ self.is_connected = False
38
+ self.is_ready = False
39
39
  self.on_connected: Optional[Callable[[], None]] = None
40
+ self.on_ready: Optional[Callable[[], None]] = None
40
41
  self.on_error: Optional[Callable[[str], None]] = None
41
42
  self.on_disconnected: Optional[Callable[[], None]] = None
42
43
  self.on_message: Optional[Callable[[str, str, str], None]] = None
@@ -73,41 +74,19 @@ class MammotionMQTT:
73
74
  self._linkkit_client.on_disconnect = self._on_disconnect
74
75
  self._linkkit_client.on_thing_enable = self._thing_on_thing_enable
75
76
  self._linkkit_client.on_topic_message = self._thing_on_topic_message
76
- # self._mqtt_host = "public.itls.eu-central-1.aliyuncs.com"
77
77
  self._mqtt_host = f"{self._product_key}.iot-as-mqtt.{region_id}.aliyuncs.com"
78
78
 
79
- self._client = Client(
80
- client_id=self._mqtt_client_id,
81
- protocol=MQTTv311,
82
- )
83
- self._client.on_message = self._on_message
84
- self._client.on_connect = self._on_connect
85
- self._client.on_disconnect = self._on_disconnect
86
- self._client.username_pw_set(self._mqtt_username, self._mqtt_password)
87
- self._client.enable_logger(logger.getChild("paho"))
88
-
89
- # region Connection handling
90
- def connect(self):
91
- """Connect to MQTT Server."""
92
- logger.info("Connecting...")
93
- self._client.connect(host=self._mqtt_host)
94
- self._client.loop_forever()
95
-
96
79
  def connect_async(self):
97
80
  """Connect async to MQTT Server."""
98
81
  logger.info("Connecting...")
99
82
  self._linkkit_client.thing_setup()
100
83
  self._linkkit_client.connect_async()
101
- # self._client.connect_async(host=self._mqtt_host)
102
- # self._client.loop_start()
103
84
  self._linkkit_client.start_worker_loop()
104
85
 
105
86
  def disconnect(self):
106
87
  """Disconnect from MQTT Server."""
107
88
  logger.info("Disconnecting...")
108
89
  self._linkkit_client.disconnect()
109
- self._client.disconnect()
110
- self._client.loop_stop()
111
90
 
112
91
  def _thing_on_thing_enable(self, user_data):
113
92
  """Is called when Thing is enabled."""
@@ -139,6 +118,9 @@ class MammotionMQTT:
139
118
  ),
140
119
  )
141
120
 
121
+ if self.on_ready:
122
+ self.is_ready = True
123
+ self.on_ready()
142
124
  # self._linkkit_client.query_ota_firmware()
143
125
  # command = MammotionCommand(device_name="Luba")
144
126
  # self._cloud_client.send_cloud_command(command.get_report_cfg())
@@ -158,39 +140,18 @@ class MammotionMQTT:
158
140
 
159
141
  def _thing_on_connect(self, session_flag, rc, user_data):
160
142
  """Is called on thing connect."""
143
+ self.is_connected = True
144
+ if self.on_connected is not None:
145
+ self.on_connected()
161
146
  logger.debug("on_connect, session_flag:%d, rc:%d", session_flag, rc)
162
147
 
163
148
  # self._linkkit_client.subscribe_topic(f"/sys/{self._product_key}/{self._device_name}/#")
164
149
 
165
- def _on_connect(self, _client, _userdata, _flags: dict, rc: int):
166
- """Is called when on connect."""
167
- if rc == 0:
168
- logger.debug("Connected")
169
- self._client.subscribe(f"/sys/{self._product_key}/{self._device_name}/#")
170
- self._client.subscribe(f"/sys/{self._product_key}/{self._device_name}/app/down/account/bind_reply")
171
-
172
- self._client.publish(
173
- f"/sys/{self._product_key}/{self._device_name}/app/up/account/bind",
174
- json.dumps(
175
- {
176
- "id": "msgid1",
177
- "version": "1.0",
178
- "request": {"clientId": self._mqtt_username},
179
- "params": {"iotToken": self._iot_token},
180
- }
181
- ),
182
- )
183
-
184
- if self.on_connected:
185
- self.on_connected()
186
- else:
187
- logger.error("Could not connect %s", connack_string(rc))
188
- if self.on_error:
189
- self.on_error(connack_string(rc))
190
-
191
150
  def _on_disconnect(self, _client, _userdata, rc: int):
192
151
  """Is called on disconnect."""
193
152
  logger.info("Disconnected")
153
+ self.is_connected = False
154
+ self.is_ready = False
194
155
  logger.debug(rc)
195
156
  if self.on_disconnected:
196
157
  self.on_disconnected()
@@ -257,6 +257,7 @@ def device_mode(value) -> str:
257
257
 
258
258
  class PosType(IntEnum):
259
259
  """Position of the robot."""
260
+
260
261
  AREA_BORDER_ON = 7
261
262
  AREA_INSIDE = 1
262
263
  AREA_OUT = 0
@@ -268,4 +269,3 @@ class PosType(IntEnum):
268
269
  OBS_ON = 2
269
270
  TURN_AREA_INSIDE = 4
270
271
  VIRTUAL_INSIDE = 6
271
-
@@ -1,4 +1,5 @@
1
1
  import math
2
+
2
3
  import numpy as np
3
4
 
4
5
  from pymammotion.data.model.location import Point
@@ -8,11 +9,11 @@ class CoordinateConverter:
8
9
  def __init__(self, latitude_rad: float, longitude_rad: float):
9
10
  # Initialize constants
10
11
  self.WGS84A = 6378137.0
11
- self.f_ = 3.3528106647474805E-21
12
+ self.f_ = 3.3528106647474805e-21
12
13
  self.b_ = (1.0 - self.f_) * self.WGS84A
13
14
  self.e2_ = (2.0 - self.f_) * self.f_
14
15
  self.e2m_ = (1.0 - self.f_) * (1.0 - self.f_)
15
- self.ep2_ = ((self.WGS84A ** 2) - (self.b_ ** 2)) / (self.b_ ** 2)
16
+ self.ep2_ = ((self.WGS84A**2) - (self.b_**2)) / (self.b_**2)
16
17
 
17
18
  # Initialize rotation matrix
18
19
  self.R_ = np.zeros((3, 3))
@@ -31,7 +32,7 @@ class CoordinateConverter:
31
32
  sin_lon = math.sin(lon_rad)
32
33
  cos_lon = math.cos(lon_rad)
33
34
 
34
- sqrt = self.WGS84A / math.sqrt(1.0 - (self.e2_ * (sin_lat ** 2)))
35
+ sqrt = self.WGS84A / math.sqrt(1.0 - (self.e2_ * (sin_lat**2)))
35
36
  d3 = sqrt * cos_lat
36
37
 
37
38
  self.x0_ = d3 * cos_lon
@@ -62,7 +63,10 @@ class CoordinateConverter:
62
63
  cos_lat = math.cos(atan2_lat)
63
64
 
64
65
  lon = math.atan2(d4, d3) * 180.0 / math.pi
65
- lat = math.atan2(d5 + self.ep2_ * self.b_ * (sin_lat ** 3), hypot - self.e2_ * self.WGS84A * (cos_lat ** 3)) * 180.0 / math.pi
66
+ lat = (
67
+ math.atan2(d5 + self.ep2_ * self.b_ * (sin_lat**3), hypot - self.e2_ * self.WGS84A * (cos_lat**3))
68
+ * 180.0
69
+ / math.pi
70
+ )
66
71
 
67
72
  return Point(latitude=lat, longitude=lon)
68
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pymammotion
3
- Version: 0.1.9
3
+ Version: 0.2.0
4
4
  Summary:
5
5
  License: GNU-3.0
6
6
  Author: Michael Arthur
@@ -1,10 +1,10 @@
1
- pymammotion/__init__.py,sha256=Hf_TaQAcSiPIwtxcGvznzCB0EOnJmrnZ9PQEqQhmitM,1530
1
+ pymammotion/__init__.py,sha256=kmnjdt3AEMejIz5JK7h1tTJj5ZriAgKwZBa3ScA4-Ao,1516
2
2
  pymammotion/aliyun/__init__.py,sha256=T1lkX7TRYiL4nqYanG4l4MImV-SlavSbuooC-W-uUGw,29
3
- pymammotion/aliyun/cloud_gateway.py,sha256=3j6y4yrN0Gf0T-jbultLqQeEEihdkjn3uSdUodQ30Dw,18295
3
+ pymammotion/aliyun/cloud_gateway.py,sha256=7cU-40s70H8q5D53Qxnul9FenzIgzXZZXN9BEEDKMoo,18308
4
4
  pymammotion/aliyun/cloud_service.py,sha256=YWcKuKK6iRWy5mTnBYgHxcCusiRGGzQt3spSf7dGDss,2183
5
5
  pymammotion/aliyun/dataclass/aep_response.py,sha256=EPuTU8uN0vkbPY_8MdBKAxASSBI9r021kODeOqrcdtw,353
6
6
  pymammotion/aliyun/dataclass/connect_response.py,sha256=Yz-fEbDzgGPTo5Of2oAjmFkSv08T7ze80pQU4k-gKIU,824
7
- pymammotion/aliyun/dataclass/dev_by_account_response.py,sha256=I3ZQEfF_223dw7yvvICYhQXYwo3_I6SJf5P9qlNbrhk,861
7
+ pymammotion/aliyun/dataclass/dev_by_account_response.py,sha256=qU29Jdpjvxv6mfp4tC3V6UCCkjBwdwttuGfV2C6ELo4,878
8
8
  pymammotion/aliyun/dataclass/login_by_oauth_response.py,sha256=6TQYAMyQ1jf_trsnTST007qlNXve3BqsvpV0Dwp7CFA,1245
9
9
  pymammotion/aliyun/dataclass/regions_response.py,sha256=0Kcly3OPMIEGK36O0OGFvZrHl6nJfnKzNeC4RPXvFhU,591
10
10
  pymammotion/aliyun/dataclass/session_by_authcode_response.py,sha256=wLGSX2nHkA7crmyYeE_dYly_lDtoYWAiIZjQ0C6m44o,358
@@ -20,36 +20,37 @@ pymammotion/bluetooth/data/notifydata.py,sha256=N1bphpueWUWbsWUcpZmMGt2CyCgLcKAF
20
20
  pymammotion/const.py,sha256=3plR6t5sFVhx3LoUbW5PE2gqIANoD-fSm-T0q8uIwIU,336
21
21
  pymammotion/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  pymammotion/data/model/__init__.py,sha256=d8FlIgCcWqoH3jJSpnm-IY-25RM-l2nbRwLtWjSHo74,222
23
- pymammotion/data/model/device.py,sha256=FHX1V_bzvpaGlAz6NXdxShNBYOAG-5g8ebnmj3jOqiA,10353
24
- pymammotion/data/model/device_config.py,sha256=6BcyVbMyw6vA1jANof8_mnG-jizGsF96pbKeHP4t8G4,179
23
+ pymammotion/data/model/account.py,sha256=rgYV61aTY2DlxLbY19Wa3Bp76KGos0_dnfRryvTXa80,118
24
+ pymammotion/data/model/device.py,sha256=jIroL0wEAbxnVQWzd4vKYTUvWwqmUDF4VPss6T7J1bs,10390
25
+ pymammotion/data/model/device_config.py,sha256=E3rhLvUH4BuWEpBfylBYBEwn4G8u7c0QbKxWRElw3Sg,177
25
26
  pymammotion/data/model/enums.py,sha256=tD_vYsxstOV_lUkYF9uWkrjVOgAJPNnGevy_xmiu3WE,1558
26
27
  pymammotion/data/model/excute_boarder_params.py,sha256=kadSth4y-VXlXIZ6R-Ng-kDvBbM-3YRr8bmR86qR0U0,1355
27
28
  pymammotion/data/model/execute_boarder.py,sha256=oDb2h5tFtOQIa8OCNYaDugqCgCZBLjQRzQTNVcJVAGQ,1072
28
29
  pymammotion/data/model/generate_route_information.py,sha256=5w1MM1-gXGXb_AoEap_I5xTxAFbNSzXuqQFEkw4NmDs,4301
29
30
  pymammotion/data/model/hash_list.py,sha256=z4y0mzbW8LQ5nsaHbMlt1y6uS4suB4D_VljAyIEjFR0,1171
30
- pymammotion/data/model/location.py,sha256=KEDVD5RgW7z5713EtWgdcseswK71u-7Kb__o58Dl_h8,783
31
- pymammotion/data/model/mowing_modes.py,sha256=121zhvouPKRbF7mynJIc7tJ0zO9bS-Nq4CFhFI7Nt0s,663
31
+ pymammotion/data/model/location.py,sha256=Fw6r4PFCjBmbUPsI6b0Swke6RgUlzoW7NXjR8EGjgzk,784
32
+ pymammotion/data/model/mowing_modes.py,sha256=2GAF-xaHpv6CSGobYoNtfSi2if8_jW0nonCqN50SNx0,665
32
33
  pymammotion/data/model/plan.py,sha256=7JvqAo0a9Yg1Vtifd4J3Dx3StEppxrMOfmq2-877kYg,2891
33
34
  pymammotion/data/model/rapid_state.py,sha256=_e9M-65AbkvIqXyMYzLKBxbNvpso42qD8R-JSt66THY,986
34
35
  pymammotion/data/model/region_data.py,sha256=75xOTM1qeRbSROp53eIczw3yCmYM9DgMjMh8qE9xkKo,2880
35
- pymammotion/data/model/report_info.py,sha256=n-bdKf_Np0N9meyEbpoVFrbjTnYFF4Q-3uJpiHtYo0M,4444
36
+ pymammotion/data/model/report_info.py,sha256=1Rj_yRZ9apWX296QHae5prdObDbs32bsQf9rzMz2KEQ,4458
36
37
  pymammotion/data/mqtt/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
37
- pymammotion/data/mqtt/event.py,sha256=kEilMLmwZG2nbokLw7X28KF3b-te8G-MfNARYm4LG6I,2170
38
+ pymammotion/data/mqtt/event.py,sha256=7hq-dl-HdKJB-TQ2kKfYfrebR_8qWE_lF2nQLlPyphU,3437
38
39
  pymammotion/data/mqtt/properties.py,sha256=HkBPghr26L9_b4QaOi1DtPgb0UoPIOGSe9wb3kgnM6Y,2815
39
40
  pymammotion/data/mqtt/status.py,sha256=zqnlo-MzejEQZszl0i0Wucoc3E76x6UtI9JLxoBnu54,1067
40
41
  pymammotion/data/state_manager.py,sha256=ItihtkmJCeIdXOYD3YRBp241cP29oMABbSISyC5A7tw,2805
41
42
  pymammotion/event/__init__.py,sha256=mgATR6vPHACNQ-0zH5fi7NdzeTCDV1CZyaWPmtUusi8,115
42
43
  pymammotion/event/event.py,sha256=Fy5-I1p92AO_D67VW4eHQqA4pOt7MZsrP--tVfIVUz8,1820
43
44
  pymammotion/http/_init_.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
- pymammotion/http/http.py,sha256=t9jO1U3Kjy30TzHkzQViv5BtmCKnNfyxrurAvXSghok,2284
45
+ pymammotion/http/http.py,sha256=VoAuDTciiueo8JRjqoZHf8wa0cfvIsFizOp8ISVHRs4,2361
45
46
  pymammotion/mammotion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
47
  pymammotion/mammotion/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
48
  pymammotion/mammotion/commands/abstract_message.py,sha256=nw6r7694yzl7iJKqRqhLmAPRjd_TL_Xo_-JXq2_a_ug,222
48
- pymammotion/mammotion/commands/mammotion_command.py,sha256=5WwD-V0XBkaYIRhxe-kVTTNpeA0XMYZLvmU_pj9rgdc,1548
49
+ pymammotion/mammotion/commands/mammotion_command.py,sha256=ppeWOBO6ifwyajpatIq4RLLpo82-XiU-mVtL-Q9W4Qc,1490
49
50
  pymammotion/mammotion/commands/messages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
51
  pymammotion/mammotion/commands/messages/driver.py,sha256=IFtmp9gJ3p3U2xtTgWVpKBMbnVnUht6Ioih17po6Hr4,3783
51
52
  pymammotion/mammotion/commands/messages/media.py,sha256=ps0l06CXy5Ej--gTNCsyKttwo7yHLVrJUpn-wNJYecs,1150
52
- pymammotion/mammotion/commands/messages/navigation.py,sha256=c7C6Dru-Xwtq-epzS9vKsy_6QlQ7ozKq9QDhFn2IRHU,23181
53
+ pymammotion/mammotion/commands/messages/navigation.py,sha256=-qit4SrtMpnPr0qsub_HD0pzKMlYsyzspW5uf1Ym008,23217
53
54
  pymammotion/mammotion/commands/messages/network.py,sha256=1a0BIhaBhBuhqYaK5EUqLbDgfzjzsIGrXS32wMKtxOU,8316
54
55
  pymammotion/mammotion/commands/messages/ota.py,sha256=XkeuWBZtpYMMBze6r8UN7dJXbe2FxUNGNnjwBpXJKM0,1240
55
56
  pymammotion/mammotion/commands/messages/system.py,sha256=nQ-g-0ESDQ2YBB6JW7IF4Q1bmTbX_baA4dOw2P3GixI,10957
@@ -57,9 +58,9 @@ pymammotion/mammotion/commands/messages/video.py,sha256=_8lJsU4sLm2CGnc7RDkueA0A
57
58
  pymammotion/mammotion/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
59
  pymammotion/mammotion/control/joystick.py,sha256=EWV20MMzQuhbLlNlXbsyZKSEpeM7x1CQL7saU4Pn0-g,6165
59
60
  pymammotion/mammotion/devices/__init__.py,sha256=T72jt0ejtMjo1rPmn_FeMF3pmp0LLeRRpc9WcDKEYYY,126
60
- pymammotion/mammotion/devices/mammotion.py,sha256=Dhn7KJxqCiCGbLh5NXJjAjx8Usg3NlD4QykseXK9tpI,31135
61
+ pymammotion/mammotion/devices/mammotion.py,sha256=HooHk3GFCwIdZ79Fb4isMR2u_B1qCWpziSzC2nuTpFU,38641
61
62
  pymammotion/mqtt/__init__.py,sha256=Ocs5e-HLJvTuDpVXyECEsWIvwsUaxzj7lZ9mSYutNDY,105
62
- pymammotion/mqtt/mammotion_mqtt.py,sha256=EBnUwqah-mZNcAn5Hq2sV3NKXueIYWNhYPiaXwaXdHg,9084
63
+ pymammotion/mqtt/mammotion_mqtt.py,sha256=VU0c8X1DrZSZDLCVAsCaEbLX2zcyrUbEAYaPzOXALNo,7523
63
64
  pymammotion/proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
64
65
  pymammotion/proto/basestation.proto,sha256=_x5gAz3FkZXS1jtq4GgZgaDCuRU-UV-7HTFdsfQ3zbo,1034
65
66
  pymammotion/proto/basestation.py,sha256=js64_N2xQYRxWPRdVNEapO0qe7vBlfYnjW5sE8hi7hw,2026
@@ -103,13 +104,13 @@ pymammotion/proto/mctrl_sys_pb2.py,sha256=DYemb514mlC7c27t-k1YqqBif0xxhLmnIWk8rX
103
104
  pymammotion/proto/mctrl_sys_pb2.pyi,sha256=BrivyEP8s7nnuSQkezTbnse9A4J1ad5o6yFcufELLVU,38910
104
105
  pymammotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
105
106
  pymammotion/utility/constant/__init__.py,sha256=tZz7szqIhrzNjfcLU3ysfINfg5VEBUAisd9AhP4mvT0,38
106
- pymammotion/utility/constant/device_constant.py,sha256=Ebmf1S9Of4s_SNp8yC7Vsbh35IniAeY-DYV4lRY5Kmw,7109
107
+ pymammotion/utility/constant/device_constant.py,sha256=aM69DJJy-h-btLP9RJezld1zm1rGz4gZRdRSpArVtmc,7109
107
108
  pymammotion/utility/datatype_converter.py,sha256=v6zym2Zu0upxQjR-xDqXwi3516zpntSlg7LP8tQF5K8,4216
108
109
  pymammotion/utility/device_type.py,sha256=KYawu2glZMVlPmxRbA4kVFujXz3miHp3rJiOWRVj-GI,8285
109
- pymammotion/utility/map.py,sha256=GNDcA-YBIglRJjvQylNdJVefoVzlH16mFSxnojK_B_A,2175
110
+ pymammotion/utility/map.py,sha256=aoi-Luzuph02hKynTofMoq3mnPstanx75MDAVv49CuY,2211
110
111
  pymammotion/utility/periodic.py,sha256=9wJMfwXPlx6Mbp3Fws7LLTI34ZDKphH1bva_Ggyk32g,3281
111
112
  pymammotion/utility/rocker_util.py,sha256=syPL0QN4zMzHiTIkUKS7RXBBptjdbkfNlPddwUD5V3A,7171
112
- pymammotion-0.1.9.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
113
- pymammotion-0.1.9.dist-info/METADATA,sha256=HhR28E9a2dkTRoICxUxGJcQBZ4exibBI0UW2ud3ppIU,3922
114
- pymammotion-0.1.9.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
115
- pymammotion-0.1.9.dist-info/RECORD,,
113
+ pymammotion-0.2.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
114
+ pymammotion-0.2.0.dist-info/METADATA,sha256=Rhb3BsXkrf-fHqvTLQkqnRS1ldAsccbkc6ZhsuZpLR4,3922
115
+ pymammotion-0.2.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
116
+ pymammotion-0.2.0.dist-info/RECORD,,