pymammotion 0.2.22__py3-none-any.whl → 0.2.24__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.
@@ -5,7 +5,7 @@ from dataclasses import dataclass
5
5
 
6
6
  import betterproto
7
7
 
8
- from pymammotion.data.model import HashList
8
+ from pymammotion.data.model import HashList, RapidState
9
9
  from pymammotion.data.model.device_config import DeviceLimits
10
10
  from pymammotion.data.model.location import Location
11
11
  from pymammotion.data.model.report_info import ReportData
@@ -16,14 +16,13 @@ from pymammotion.proto.mctrl_driver import MctlDriver
16
16
  from pymammotion.proto.mctrl_nav import MctlNav
17
17
  from pymammotion.proto.mctrl_ota import MctlOta
18
18
  from pymammotion.proto.mctrl_pept import MctlPept
19
- from pymammotion.proto.mctrl_sys import MctlSys, MowToAppInfoT, ReportInfoData, SystemUpdateBufMsg
19
+ from pymammotion.proto.mctrl_sys import MctlSys, MowToAppInfoT, ReportInfoData, SystemUpdateBufMsg, \
20
+ SystemRapidStateTunnelMsg
21
+ from pymammotion.utility.constant import WorkMode
22
+ from pymammotion.utility.conversions import parse_double
20
23
  from pymammotion.utility.map import CoordinateConverter
21
24
 
22
25
 
23
- def parse_double(val: float, d: float):
24
- return val / math.pow(10.0, d)
25
-
26
-
27
26
  @dataclass
28
27
  class MowingDevice:
29
28
  """Wraps the betterproto dataclasses, so we can bypass the groups for keeping all data."""
@@ -31,15 +30,17 @@ class MowingDevice:
31
30
  device: LubaMsg
32
31
  map: HashList
33
32
  location: Location
33
+ mowing_state: RapidState
34
34
 
35
35
  def __init__(self):
36
36
  self.device = LubaMsg()
37
- self.map = HashList(area={}, path={}, obstacle={})
37
+ self.map = HashList(area={}, path={}, obstacle={}, hashlist=[])
38
38
  self.location = Location()
39
39
  self.report_data = ReportData()
40
40
  self.err_code_list = []
41
41
  self.err_code_list_time = []
42
42
  self.limits = DeviceLimits(30, 70, 0.2, 0.6)
43
+ self.mowing_state = RapidState()
43
44
 
44
45
  @classmethod
45
46
  def from_raw(cls, raw: dict) -> "MowingDevice":
@@ -97,15 +98,22 @@ class MowingDevice:
97
98
  def update_report_data(self, toapp_report_data: ReportInfoData):
98
99
  coordinate_converter = CoordinateConverter(self.location.RTK.latitude, self.location.RTK.longitude)
99
100
  for index, location in enumerate(toapp_report_data.locations):
100
- if index == 0:
101
+ if index == 0 and location.real_pos_y != 0:
101
102
  self.location.position_type = location.pos_type
102
103
  self.location.orientation = location.real_toward / 10000
103
104
  self.location.device = coordinate_converter.enu_to_lla(
104
105
  parse_double(location.real_pos_y, 4.0), parse_double(location.real_pos_x, 4.0)
105
106
  )
107
+ if location.zone_hash:
108
+ self.location.work_zone = location.zone_hash if self.report_data.dev.sys_status == WorkMode.MODE_WORKING else 0
109
+
110
+
106
111
 
107
112
  self.report_data = self.report_data.from_dict(toapp_report_data.to_dict(casing=betterproto.Casing.SNAKE))
108
113
 
114
+ def run_state_update(self, rapid_state: SystemRapidStateTunnelMsg):
115
+ self.mowing_state = RapidState().from_raw(rapid_state.rapid_state_data)
116
+
109
117
  def mow_info(self, toapp_mow_info: MowToAppInfoT):
110
118
  pass
111
119
 
@@ -1,8 +1,15 @@
1
1
  from dataclasses import dataclass
2
+ from enum import IntEnum
2
3
 
3
4
  from pymammotion.proto.mctrl_nav import NavGetCommDataAck
4
5
 
5
6
 
7
+ class PathType(IntEnum):
8
+ """Path types for common data."""
9
+ AREA = 0
10
+ OBSTACLE = 1
11
+ PATH = 2
12
+
6
13
  @dataclass
7
14
  class FrameList:
8
15
  total_frame: int
@@ -13,23 +20,56 @@ class FrameList:
13
20
  class HashList:
14
21
  """stores our map data.
15
22
  [hashID, FrameList].
23
+ hashlist for all our hashIDs for verification
16
24
  """
17
25
 
18
26
  area: dict # type 0
19
27
  path: dict # type 2
20
28
  obstacle: dict # type 1
29
+ hashlist: list[int]
30
+
31
+ def set_hashlist(self, hashlist: list[int]):
32
+ self.hashlist = hashlist
33
+ self.area = {hash_id: frames for hash_id, frames in self.area.items() if hash_id in hashlist}
34
+ self.path = {hash_id: frames for hash_id, frames in self.path.items() if hash_id in hashlist}
35
+ self.obstacle = {hash_id: frames for hash_id, frames in self.obstacle.items() if hash_id in hashlist}
36
+
37
+
38
+ def missing_frame(self, hash_data: NavGetCommDataAck) -> list[int]:
39
+ if hash_data.type == PathType.AREA:
40
+ return self._find_missing_frames(self.area.get(hash_data.hash))
41
+
42
+ if hash_data.type == PathType.OBSTACLE:
43
+ return self._find_missing_frames(self.obstacle.get(hash_data.hash))
44
+
45
+ if hash_data.type == PathType.PATH:
46
+ return self._find_missing_frames(self.path.get(hash_data.hash))
47
+
48
+
21
49
 
22
50
  def update(self, hash_data: NavGetCommDataAck) -> bool:
23
51
  """Update the map data."""
24
- if hash_data.type == 0:
52
+ if hash_data.type == PathType.AREA:
25
53
  return self._add_hash_data(self.area, hash_data)
26
54
 
27
- if hash_data.type == 1:
55
+ if hash_data.type == PathType.OBSTACLE:
28
56
  return self._add_hash_data(self.obstacle, hash_data)
29
57
 
30
- if hash_data.type == 2:
58
+ if hash_data.type == PathType.PATH:
31
59
  return self._add_hash_data(self.path, hash_data)
32
60
 
61
+
62
+ @staticmethod
63
+ def _find_missing_frames(frame_list: FrameList) -> list[int]:
64
+ if frame_list.total_frame == len(frame_list.data):
65
+ return []
66
+ number_list = list(range(1, frame_list.total_frame+1))
67
+
68
+ current_frames = {frame.current_frame for frame in frame_list.data}
69
+ missing_numbers = [num for num in number_list if num not in current_frames]
70
+ return missing_numbers
71
+
72
+
33
73
  @staticmethod
34
74
  def _add_hash_data(hash_dict: dict, hash_data: NavGetCommDataAck) -> bool:
35
75
  if hash_dict.get(hash_data.hash) is None:
@@ -31,6 +31,7 @@ class Location:
31
31
  dock: Dock
32
32
  position_type: int = 0
33
33
  orientation: int = 0 # 360 degree rotation +-
34
+ work_zone: int = 0
34
35
 
35
36
  def __init__(self):
36
37
  self.device = Point()
@@ -1,6 +1,8 @@
1
1
  from dataclasses import dataclass
2
2
  from enum import Enum
3
3
 
4
+ from pymammotion.utility.conversions import parse_double
5
+
4
6
 
5
7
  class RTKStatus(Enum):
6
8
  NONE = 0
@@ -10,18 +12,18 @@ class RTKStatus(Enum):
10
12
 
11
13
  @dataclass
12
14
  class RapidState:
13
- pos_x: float
14
- pos_y: float
15
- rtk_status: RTKStatus
16
- toward: float
17
- satellites_total: int
18
- satellites_l2: int
19
- rtk_age: float
20
- lat_std: float
21
- lon_std: float
22
- pos_type: int
23
- zone_hash: int
24
- pos_level: int
15
+ pos_x: float = 0
16
+ pos_y: float = 0
17
+ rtk_status: RTKStatus = RTKStatus.NONE
18
+ toward: float = 0
19
+ satellites_total: int = 0
20
+ satellites_l2: int = 0
21
+ rtk_age: float = 0
22
+ lat_std: float = 0
23
+ lon_std: float = 0
24
+ pos_type: int = 0
25
+ zone_hash: int = 0
26
+ pos_level: int = 0
25
27
 
26
28
  @classmethod
27
29
  def from_raw(cls, raw: list[int]) -> "RapidState":
@@ -29,13 +31,13 @@ class RapidState:
29
31
  rtk_status=RTKStatus.FINE if raw[0] == 4 else RTKStatus.BAD if raw[0] in (1, 5) else RTKStatus.NONE,
30
32
  pos_level=raw[1],
31
33
  satellites_total=raw[2],
32
- rtk_age=raw[3] / 10000,
33
- lat_std=raw[4] / 10000,
34
- lon_std=raw[5] / 10000,
34
+ rtk_age=parse_double(raw[3], 4.0),
35
+ lat_std=parse_double(raw[4], 4.0),
36
+ lon_std=parse_double(raw[5], 4.0),
35
37
  satellites_l2=raw[6],
36
- pos_x=raw[7] / 10000,
37
- pos_y=raw[8] / 10000,
38
- toward=raw[9] / 10000,
38
+ pos_x=parse_double(raw[7], 4.0),
39
+ pos_y=parse_double(raw[8], 4.0),
40
+ toward=parse_double(raw[9], 4.0),
39
41
  pos_type=raw[10],
40
42
  zone_hash=raw[11],
41
43
  )
@@ -51,6 +51,16 @@ class DeviceConfigurationRequestValue(DataClassORJSONMixin):
51
51
  bizId: str
52
52
  params: str
53
53
 
54
+ @dataclass
55
+ class DeviceNotificationEventCode(DataClassORJSONMixin):
56
+ localTime: int
57
+ code: str
58
+
59
+ @dataclass
60
+ class DeviceNotificationEventValue(DataClassORJSONMixin):
61
+ data: DeviceNotificationEventCode
62
+
63
+
54
64
 
55
65
  @dataclass
56
66
  class GeneralParams(DataClassORJSONMixin):
@@ -90,6 +100,16 @@ class DeviceProtobufMsgEventParams(GeneralParams):
90
100
  type: Literal["info"]
91
101
  value: DeviceProtobufMsgEventValue
92
102
 
103
+ @dataclass
104
+ class DeviceNotificationEventParams(GeneralParams):
105
+ """Device notification event.
106
+
107
+ {'data': '{"localTime":1725159492000,"code":"1002"}'},
108
+ """
109
+
110
+ identifier: Literal["device_notification_event"]
111
+ type: Literal["info"]
112
+ value: DeviceNotificationEventValue
93
113
 
94
114
  @dataclass
95
115
  class DeviceWarningEventParams(GeneralParams):
@@ -129,6 +149,8 @@ class ThingEventMessage(DataClassORJSONMixin):
129
149
  params_obj = DeviceWarningEventParams(**params_dict)
130
150
  elif identifier == "device_config_req_event":
131
151
  params_obj = payload.get("params", '')
152
+ elif identifier == "device_notification_event":
153
+ params_obj = DeviceNotificationEventParams(**params_dict)
132
154
  else:
133
155
  raise ValueError(f"Unknown identifier: {identifier} {params_dict}")
134
156
 
@@ -53,9 +53,8 @@ class StateManager:
53
53
  nav_msg = betterproto.which_one_of(message.nav, "SubNavMsg")
54
54
  match nav_msg[0]:
55
55
  case "toapp_gethash_ack":
56
- self._device.map.obstacle = dict()
57
- self._device.map.area = dict()
58
- self._device.map.path = dict()
56
+ hashlist_ack: NavGetHashListAck = nav_msg[1]
57
+ self._device.map.set_hashlist(hashlist_ack.data_couple)
59
58
  await self.gethash_ack_callback(nav_msg[1])
60
59
  case "toapp_get_commondata_ack":
61
60
  common_data: NavGetCommDataAck = nav_msg[1]
@@ -73,6 +72,8 @@ class StateManager:
73
72
  self._device.update_report_data(sys_msg[1])
74
73
  case "mow_to_app_info":
75
74
  self._device.mow_info(sys_msg[1])
75
+ case "system_tard_state_tunnel":
76
+ self._device.run_state_update(sys_msg[1])
76
77
 
77
78
  def _update_driver_data(self, message):
78
79
  pass
@@ -30,20 +30,19 @@ class MessageSystem(AbstractMessage, ABC):
30
30
 
31
31
  def set_blade_control(self, on_off: int):
32
32
  mctlsys = mctrl_sys_pb2.MctlSys()
33
- sysKnifeControl = mctrl_sys_pb2.SysKnifeControl()
34
- sysKnifeControl.knife_status = on_off
35
- mctlsys.todev_knife_ctrl.CopyFrom(sysKnifeControl)
33
+ sys_knife_control = mctrl_sys_pb2.SysKnifeControl()
34
+ sys_knife_control.knife_status = on_off
35
+ mctlsys.todev_knife_ctrl.CopyFrom(sys_knife_control)
36
36
 
37
37
  return self.send_order_msg_sys(mctlsys)
38
38
 
39
39
  def get_device_product_model(self):
40
40
  return self.send_order_msg_sys(
41
- mctrl_sys_pb2.MctlSys(device_product_type_info=mctrl_sys_pb2.device_product_type_info_t()),
42
- 12,
43
- True,
41
+ mctrl_sys_pb2.MctlSys(device_product_type_info=mctrl_sys_pb2.device_product_type_info_t())
44
42
  )
45
43
 
46
44
  def read_and_set_sidelight(self, is_sidelight: bool, operate: int):
45
+ """Read state of sidelight as well as set it."""
47
46
  if is_sidelight:
48
47
  build = mctrl_sys_pb2.TimeCtrlLight(
49
48
  operate=operate,
@@ -80,7 +79,7 @@ class MessageSystem(AbstractMessage, ABC):
80
79
  param_id}, param_value={param_value}")
81
80
  return self.send_order_msg_sys(build2)
82
81
 
83
- def read_and_set_rt_k_paring_code(self, op: int, cgf: str):
82
+ def read_and_set_rtk_paring_code(self, op: int, cgf: str):
84
83
  print(f"Send read and write base station configuration quality op:{
85
84
  op}, cgf:{cgf}")
86
85
  return self.send_order_msg_sys(
@@ -8,13 +8,12 @@ from pyjoystick.utils import PeriodicThread
8
8
 
9
9
  from pymammotion import MammotionBaseBLEDevice
10
10
  from pymammotion.event import BleNotificationEvent
11
- from pymammotion.utility.rocker_util import RockerControlUtil
11
+ from pymammotion.utility.movement import transform_both_speeds, get_percent
12
12
 
13
13
  bleNotificationEvt = BleNotificationEvent()
14
14
 
15
15
  nest_asyncio.apply()
16
16
 
17
-
18
17
  class JoystickControl:
19
18
  """Joystick class for controlling Luba with a joystick"""
20
19
 
@@ -54,7 +53,7 @@ class JoystickControl:
54
53
  return
55
54
  self.stopped = True
56
55
  self.stopped = False
57
- (linear_speed, angular_speed) = self.transform_both_speeds(
56
+ (linear_speed, angular_speed) = transform_both_speeds(
58
57
  self.linear_speed,
59
58
  self.angular_speed,
60
59
  self.linear_percent,
@@ -75,23 +74,6 @@ class JoystickControl:
75
74
  self.mngr.start()
76
75
  self.worker.start()
77
76
 
78
- def get_percent(self, percent: float):
79
- if percent <= 15.0:
80
- return 0.0
81
-
82
- return percent - 15.0
83
-
84
- @staticmethod
85
- def transform_both_speeds(linear: float, angular: float, linear_percent: float, angular_percent: float):
86
- transfrom3 = RockerControlUtil.getInstance().transfrom3(linear, linear_percent)
87
- transform4 = RockerControlUtil.getInstance().transfrom3(angular, angular_percent)
88
-
89
- if transfrom3 is not None and len(transfrom3) > 0:
90
- linear_speed = transfrom3[0] * 10
91
- angular_speed = int(transform4[1] * 4.5)
92
- print(linear_speed, angular_speed)
93
- return linear_speed, angular_speed
94
-
95
77
  def handle_key_received(self, key):
96
78
  # print(key, "-", key.keytype, "-", key.number, "-", key.value)
97
79
 
@@ -129,11 +111,13 @@ class JoystickControl:
129
111
  # linear_speed==-1000
130
112
  print("case 1")
131
113
  if key.value > 0:
114
+ """Backwards."""
132
115
  self.linear_speed = 270.0
133
- self.linear_percent = self.get_percent(abs(key.value * 100))
116
+ self.linear_percent = get_percent(abs(key.value * 100))
134
117
  else:
118
+ """Forwards."""
135
119
  self.linear_speed = 90.0
136
- self.linear_percent = self.get_percent(abs(key.value * 100))
120
+ self.linear_percent = get_percent(abs(key.value * 100))
137
121
 
138
122
  case 2: # right (left right)
139
123
  # take left right values and convert to angular movement
@@ -143,12 +127,12 @@ class JoystickControl:
143
127
  # angular_speed==450
144
128
  if key.value > 0:
145
129
  self.angular_speed = 0.0
146
- self.angular_percent = self.get_percent(abs(key.value * 100))
130
+ self.angular_percent = get_percent(abs(key.value * 100))
147
131
  else:
148
132
  # angle=180.0
149
133
  # linear_speed=0//angular_speed=-450
150
134
  self.angular_speed = 180.0
151
- self.angular_percent = self.get_percent(abs(key.value * 100))
135
+ self.angular_percent = get_percent(abs(key.value * 100))
152
136
 
153
137
  else:
154
138
  match key.number:
@@ -18,7 +18,6 @@ from uuid import UUID
18
18
 
19
19
  import betterproto
20
20
  from aiohttp import ClientSession
21
- from bleak import BleakClient
22
21
  from bleak.backends.device import BLEDevice
23
22
  from bleak.backends.service import BleakGATTCharacteristic, BleakGATTServiceCollection
24
23
  from bleak.exc import BleakDBusError
@@ -44,7 +43,7 @@ from pymammotion.mqtt import MammotionMQTT
44
43
  from pymammotion.mqtt.mammotion_future import MammotionFuture
45
44
  from pymammotion.proto.luba_msg import LubaMsg
46
45
  from pymammotion.proto.mctrl_nav import NavGetCommDataAck, NavGetHashListAck
47
- from pymammotion.utility.rocker_util import RockerControlUtil
46
+ from pymammotion.utility.movement import get_percent, transform_both_speeds
48
47
 
49
48
 
50
49
  class CharacteristicMissingError(Exception):
@@ -346,37 +345,31 @@ class MammotionBaseDevice:
346
345
  self._notify_future: asyncio.Future[bytes] | None = None
347
346
  self._cloud_device = cloud_device
348
347
 
349
- def set_notifiction_callback(self, func: Callable[[],Awaitable[None]]):
348
+ def set_notification_callback(self, func: Callable[[],Awaitable[None]]):
350
349
  self._state_manager.on_notification_callback = func
351
350
 
352
351
  async def datahash_response(self, hash_ack: NavGetHashListAck):
353
352
  """Handle datahash responses."""
354
- result_hash = 0
355
- while hash_ack.data_couple[0] != result_hash:
356
- data = await self.queue_command("synchronize_hash_data", hash_num=hash_ack.data_couple[0])
357
- msg = LubaMsg().parse(data)
358
- if betterproto.serialized_on_wire(msg.nav.toapp_get_commondata_ack):
359
- result_hash = msg.nav.toapp_get_commondata_ack.hash
353
+ await self.queue_command("synchronize_hash_data", hash_num=hash_ack.data_couple[0])
360
354
 
361
355
  async def commdata_response(self, common_data: NavGetCommDataAck):
362
356
  """Handle common data responses."""
363
357
  total_frame = common_data.total_frame
364
358
  current_frame = common_data.current_frame
365
359
 
366
- if total_frame == current_frame:
360
+ missing_frames = self._mower.map.missing_frame(common_data)
361
+ if len(missing_frames) == 0:
367
362
  # get next in hash ack list
368
363
 
369
364
  data_hash = find_next_integer(self._mower.nav.toapp_gethash_ack.data_couple, common_data.hash)
370
365
  if data_hash is None:
371
366
  return
372
- result_hash = 0
373
- while data_hash != result_hash:
374
- data = await self.queue_command("synchronize_hash_data", hash_num=data_hash)
375
- msg = LubaMsg().parse(data)
376
- if betterproto.serialized_on_wire(msg.nav.toapp_get_commondata_ack):
377
- result_hash = msg.nav.toapp_get_commondata_ack.hash
367
+
368
+ await self.queue_command("synchronize_hash_data", hash_num=data_hash)
378
369
  else:
379
- # check if we have the data already first
370
+ if current_frame != missing_frames[0]-1:
371
+ current_frame = missing_frames[0]-1
372
+
380
373
  region_data = RegionData()
381
374
  region_data.hash = common_data.hash
382
375
  region_data.action = common_data.action
@@ -499,28 +492,28 @@ class MammotionBaseDevice:
499
492
 
500
493
  async def start_sync(self, retry: int):
501
494
  """Start synchronization with the device."""
502
- await self._send_command("get_device_base_info", retry)
503
- await self._send_command("get_report_cfg", retry)
495
+ await self.queue_command("get_device_base_info")
496
+ await self.queue_command("get_device_product_model")
497
+ await self.queue_command("get_report_cfg")
504
498
  """RTK and dock location."""
505
- await self._send_command_with_args("allpowerfull_rw", id=5, rw=1, context=1)
506
- """Error codes."""
507
- await self._send_command_with_args("allpowerfull_rw", id=5, rw=1, context=2)
508
- await self._send_command_with_args("allpowerfull_rw", id=5, rw=1, context=3)
499
+ await self.queue_command("allpowerfull_rw", id=5, rw=1, context=1)
509
500
 
510
501
  async def start_map_sync(self):
511
502
  """Start sync of map data."""
512
- await self._send_command_with_args("read_plan", sub_cmd=2, plan_index=0)
503
+ await self.queue_command("read_plan", sub_cmd=2, plan_index=0)
513
504
 
514
- await self._send_command_with_args("get_all_boundary_hash_list", sub_cmd=0)
505
+ await self.queue_command("get_all_boundary_hash_list", sub_cmd=0)
515
506
 
516
- await self._send_command_with_args("get_hash_response", total_frame=1, current_frame=1)
507
+ await self.queue_command("get_hash_response", total_frame=1, current_frame=1)
517
508
 
509
+
510
+ # work out why this crashes sometimes for better proto
518
511
  if self._cloud_device:
519
- await self._send_command_with_args(
512
+ await self.queue_command(
520
513
  "get_area_name_list", device_id=self._cloud_device.deviceName
521
514
  )
522
515
  if has_field(self._mower.net.toapp_wifi_iot_status):
523
- await self._send_command_with_args(
516
+ await self.queue_command(
524
517
  "get_area_name_list", device_id=self._mower.net.toapp_wifi_iot_status.devicename
525
518
  )
526
519
 
@@ -529,32 +522,40 @@ class MammotionBaseDevice:
529
522
  # sub_cmd 4 is dump location (yuka)
530
523
  # jobs list
531
524
  # hash_list_result = await self._send_command_with_args("get_all_boundary_hash_list", sub_cmd=3)
525
+ async def async_get_errors(self):
526
+ """Error codes."""
527
+ await self.queue_command("allpowerfull_rw", id=5, rw=1, context=2)
528
+ await self.queue_command("allpowerfull_rw", id=5, rw=1, context=3)
532
529
 
533
- async def move_forward(self):
534
- linear_speed = 1.0
535
- angular_speed = 0.0
536
- transfrom3 = RockerControlUtil.getInstance().transfrom3(90, 1000)
537
- transform4 = RockerControlUtil.getInstance().transfrom3(0, 0)
538
530
 
539
- if transfrom3 is not None and len(transfrom3) > 0:
540
- linear_speed = transfrom3[0] * 10
541
- angular_speed = int(transform4[1] * 4.5)
542
- await self._send_command_with_args("send_movement", linear_speed=linear_speed, angular_speed=angular_speed)
531
+ async def move_forward(self, linear: float):
532
+ """Move forward. values 0.0 1.0."""
533
+ linear_percent = get_percent(abs(linear * 100))
534
+ (linear_speed, angular_speed) = transform_both_speeds(90.0, 0.0, linear_percent, 0.0)
535
+ await self.queue_command("send_movement", linear_speed=linear_speed, angular_speed=angular_speed)
543
536
 
544
- async def move_stop(self):
545
- linear_speed = 0.0
546
- angular_speed = 0.0
547
- transfrom3 = RockerControlUtil.getInstance().transfrom3(0, 0)
548
- transform4 = RockerControlUtil.getInstance().transfrom3(0, 0)
537
+ async def move_back(self, linear: float):
538
+ """Move back. values 0.0 1.0."""
539
+ linear_percent = get_percent(abs(linear * 100))
540
+ (linear_speed, angular_speed) = transform_both_speeds(270.0, 0.0, linear_percent, 0.0)
541
+ await self.queue_command("send_movement", linear_speed=linear_speed, angular_speed=angular_speed)
542
+
543
+ async def move_left(self, angulur: float):
544
+ """Move forward. values 0.0 1.0."""
545
+ angular_percent = get_percent(abs(angulur * 100))
546
+ (linear_speed, angular_speed) = transform_both_speeds(0.0, 0.0, 0.0, angular_percent)
547
+ await self.queue_command("send_movement", linear_speed=linear_speed, angular_speed=angular_speed)
548
+
549
+ async def move_right(self, angulur: float):
550
+ """Move back. values 0.0 1.0."""
551
+ angular_percent = get_percent(abs(angulur * 100))
552
+ (linear_speed, angular_speed) = transform_both_speeds(0.0, 180.0, 0.0, angular_percent)
553
+ await self.queue_command("send_movement", linear_speed=linear_speed, angular_speed=angular_speed)
549
554
 
550
- if transfrom3 is not None and len(transfrom3) > 0:
551
- linear_speed = transfrom3[0] * 10
552
- angular_speed = int(transform4[1] * 4.5)
553
- await self._send_command_with_args("send_movement", linear_speed=linear_speed, angular_speed=angular_speed)
554
555
 
555
556
  async def command(self, key: str, **kwargs):
556
557
  """Send a command to the device."""
557
- return await self._send_command_with_args(key, **kwargs)
558
+ return await self.queue_command(key, **kwargs)
558
559
 
559
560
 
560
561
  class MammotionBaseBLEDevice(MammotionBaseDevice):
@@ -806,6 +807,33 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
806
807
  _LOGGER.debug("%s: Sending command: %s", self.name, key)
807
808
  await self._message.post_custom_data_bytes(command)
808
809
 
810
+ timeout = 2
811
+ timeout_handle = self.loop.call_at(self.loop.time() + timeout, _handle_timeout, self._notify_future)
812
+ timeout_expired = False
813
+ try:
814
+ notify_msg = await self._notify_future
815
+ except asyncio.TimeoutError:
816
+ timeout_expired = True
817
+ notify_msg = b''
818
+ self._notify_future.set_result(notify_msg)
819
+ finally:
820
+ if not timeout_expired:
821
+ timeout_handle.cancel()
822
+ self._notify_future = None
823
+
824
+ _LOGGER.debug("%s: Notification received: %s", self.name, notify_msg.hex())
825
+ return notify_msg
826
+
827
+ async def _execute_command_locked_old(self, key: str, command: bytes) -> bytes:
828
+ """Execute command and read response."""
829
+ assert self._client is not None
830
+ assert self._read_char is not None
831
+ assert self._write_char is not None
832
+ self._notify_future = self.loop.create_future()
833
+ self._key = key
834
+ _LOGGER.debug("%s: Sending command: %s", self.name, key)
835
+ await self._message.post_custom_data_bytes(command)
836
+
809
837
  retry_handle = self.loop.call_at(
810
838
  self.loop.time() + 2,
811
839
  lambda: asyncio.ensure_future(
@@ -961,7 +989,6 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
961
989
  self._ble_sync_task = None
962
990
  self.is_ready = False
963
991
  self.command_queue = asyncio.Queue()
964
- self.processing_task = asyncio.create_task(self._process_queue())
965
992
  self._mqtt_client = mqtt_client
966
993
  self.iot_id = cloud_device.iotId
967
994
  self.device = cloud_device
@@ -971,7 +998,7 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
971
998
  self.currentID = ""
972
999
  self.on_ready_callback: Optional[Callable[[], Awaitable[None]]] = None
973
1000
  self._waiting_queue = deque()
974
- self._operation_lock = threading.Lock()
1001
+ self._operation_lock = asyncio.Lock()
975
1002
 
976
1003
  self._mqtt_client.on_connected = self.on_connected
977
1004
  self._mqtt_client.on_disconnected = self.on_disconnected
@@ -986,11 +1013,13 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
986
1013
 
987
1014
  async def on_ready(self):
988
1015
  """Callback for when MQTT is subscribed to events."""
989
- if self.on_ready_callback:
990
- await self.on_ready_callback()
1016
+ loop = asyncio.get_event_loop()
991
1017
 
1018
+ if self.on_ready_callback:
1019
+ self.loop.create_task(self.on_ready_callback)
992
1020
  await self._ble_sync()
993
1021
  await self.run_periodic_sync_task()
1022
+ loop.create_task(self._process_queue())
994
1023
 
995
1024
  async def on_connected(self):
996
1025
  """Callback for when MQTT connects."""
@@ -1008,7 +1037,8 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
1008
1037
  async def run_periodic_sync_task(self) -> None:
1009
1038
  """Send ble sync to robot."""
1010
1039
  try:
1011
- await self._ble_sync()
1040
+ if not self._operation_lock.locked():
1041
+ await self._ble_sync()
1012
1042
  finally:
1013
1043
  self.schedule_ble_sync()
1014
1044
 
@@ -1116,7 +1146,7 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
1116
1146
  try:
1117
1147
  notify_msg = await future.async_get(timeout)
1118
1148
  except asyncio.TimeoutError:
1119
- raise
1149
+ notify_msg = b''
1120
1150
 
1121
1151
  _LOGGER.debug("%s: Message received", self.device.nickName)
1122
1152
 
@@ -1189,6 +1219,3 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
1189
1219
  def _disconnect(self):
1190
1220
  """Disconnect the MQTT client."""
1191
1221
  self._mqtt_client.disconnect()
1192
-
1193
-
1194
-
@@ -123,7 +123,8 @@ class MammotionMQTT:
123
123
 
124
124
  if self.on_ready:
125
125
  self.is_ready = True
126
- asyncio.run_coroutine_threadsafe(self.on_ready(), self.loop).result()
126
+ future = asyncio.run_coroutine_threadsafe(self.on_ready(), self.loop)
127
+ asyncio.wrap_future(future, loop=self.loop)
127
128
  # self._linkkit_client.query_ota_firmware()
128
129
  # command = MammotionCommand(device_name="Luba")
129
130
  # self._cloud_client.send_cloud_command(command.get_report_cfg())
@@ -123,7 +123,7 @@ class SystemUpdateBuf:
123
123
  ZONE_STATE_LEN_INDEX = 1
124
124
 
125
125
 
126
- class SystemRapidStateTunnel:
126
+ class SystemRapidStateTunnelIndex(IntEnum):
127
127
  DIS_CAR_RTK_STARS_INDEX = 15
128
128
  DIS_RTK_STATUS_INDEX = 13
129
129
  L1_SATS_INDEX = 2
@@ -0,0 +1,5 @@
1
+ import math
2
+
3
+
4
+ def parse_double(val: float, d: float):
5
+ return val / math.pow(10.0, d)
@@ -0,0 +1,17 @@
1
+ from .rocker_util import RockerControlUtil
2
+
3
+
4
+ def transform_both_speeds(linear: float, angular: float, linear_percent: float, angular_percent: float):
5
+ transfrom3 = RockerControlUtil.getInstance().transfrom3(linear, linear_percent)
6
+ transform4 = RockerControlUtil.getInstance().transfrom3(angular, angular_percent)
7
+
8
+ if transfrom3 is not None and len(transfrom3) > 0:
9
+ linear_speed = transfrom3[0] * 10
10
+ angular_speed = int(transform4[1] * 4.5)
11
+ return linear_speed, angular_speed
12
+
13
+ def get_percent(percent: float):
14
+ if percent <= 15.0:
15
+ return 0.0
16
+
17
+ return percent - 15.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pymammotion
3
- Version: 0.2.22
3
+ Version: 0.2.24
4
4
  Summary:
5
5
  License: GNU-3.0
6
6
  Author: Michael Arthur
@@ -21,24 +21,24 @@ pymammotion/const.py,sha256=EEmZ1v4MqN2nPiNpS_mJQqaCkSNEa1EXUmtwZBUhXIg,329
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
23
  pymammotion/data/model/account.py,sha256=stWLDtWPw1vOPp3xNdX-jWziMmdCvww4IYl00UUGKuI,139
24
- pymammotion/data/model/device.py,sha256=jIroL0wEAbxnVQWzd4vKYTUvWwqmUDF4VPss6T7J1bs,10390
24
+ pymammotion/data/model/device.py,sha256=wDmmuCkWvuyoFhZxULCGeigMErmN5GX08NqYJrZbVhQ,10903
25
25
  pymammotion/data/model/device_config.py,sha256=E3rhLvUH4BuWEpBfylBYBEwn4G8u7c0QbKxWRElw3Sg,177
26
26
  pymammotion/data/model/enums.py,sha256=tD_vYsxstOV_lUkYF9uWkrjVOgAJPNnGevy_xmiu3WE,1558
27
27
  pymammotion/data/model/excute_boarder_params.py,sha256=kadSth4y-VXlXIZ6R-Ng-kDvBbM-3YRr8bmR86qR0U0,1355
28
28
  pymammotion/data/model/execute_boarder.py,sha256=oDb2h5tFtOQIa8OCNYaDugqCgCZBLjQRzQTNVcJVAGQ,1072
29
29
  pymammotion/data/model/generate_route_information.py,sha256=5w1MM1-gXGXb_AoEap_I5xTxAFbNSzXuqQFEkw4NmDs,4301
30
- pymammotion/data/model/hash_list.py,sha256=z4y0mzbW8LQ5nsaHbMlt1y6uS4suB4D_VljAyIEjFR0,1171
31
- pymammotion/data/model/location.py,sha256=qO3G0U_eWP9alswbZXTpmYImIcXJeteBVHX1cGZGbHg,729
30
+ pymammotion/data/model/hash_list.py,sha256=wHdM46r42_EF2OnXqBHNAyf-muDOibEB7NWVjc0gXYA,2683
31
+ pymammotion/data/model/location.py,sha256=mafcOPFcb6niTiiZdDWIh_TbnS76jA3Cqd5paKbnhgc,752
32
32
  pymammotion/data/model/mowing_modes.py,sha256=2GAF-xaHpv6CSGobYoNtfSi2if8_jW0nonCqN50SNx0,665
33
33
  pymammotion/data/model/plan.py,sha256=7JvqAo0a9Yg1Vtifd4J3Dx3StEppxrMOfmq2-877kYg,2891
34
- pymammotion/data/model/rapid_state.py,sha256=_e9M-65AbkvIqXyMYzLKBxbNvpso42qD8R-JSt66THY,986
34
+ pymammotion/data/model/rapid_state.py,sha256=BdJFD_DlhrVneg-PqEruqCoMty-CR7q_9Qna-hk1yd8,1171
35
35
  pymammotion/data/model/region_data.py,sha256=75xOTM1qeRbSROp53eIczw3yCmYM9DgMjMh8qE9xkKo,2880
36
36
  pymammotion/data/model/report_info.py,sha256=1Rj_yRZ9apWX296QHae5prdObDbs32bsQf9rzMz2KEQ,4458
37
37
  pymammotion/data/mqtt/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
38
- pymammotion/data/mqtt/event.py,sha256=QkuFQtSUfg8eHisJ-llu-b6cpAlTePaP0JbqL6xZcvk,3900
38
+ pymammotion/data/mqtt/event.py,sha256=0bxP7mt3unvoaPIJn5VDUXMuwTcakgw6bfuv64aaYAk,4522
39
39
  pymammotion/data/mqtt/properties.py,sha256=HkBPghr26L9_b4QaOi1DtPgb0UoPIOGSe9wb3kgnM6Y,2815
40
40
  pymammotion/data/mqtt/status.py,sha256=zqnlo-MzejEQZszl0i0Wucoc3E76x6UtI9JLxoBnu54,1067
41
- pymammotion/data/state_manager.py,sha256=jkedK5qAVDTGqIzlPNCmPpE-Pc1V4e-ZHW6Ds1ihJ50,3004
41
+ pymammotion/data/state_manager.py,sha256=T6slACOpbnhWVKmJD_XrBY9vrcCFc6q2l6jEf-g89NQ,3095
42
42
  pymammotion/event/__init__.py,sha256=mgATR6vPHACNQ-0zH5fi7NdzeTCDV1CZyaWPmtUusi8,115
43
43
  pymammotion/event/event.py,sha256=Fy5-I1p92AO_D67VW4eHQqA4pOt7MZsrP--tVfIVUz8,1820
44
44
  pymammotion/http/_init_.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -53,15 +53,15 @@ pymammotion/mammotion/commands/messages/media.py,sha256=ps0l06CXy5Ej--gTNCsyKttw
53
53
  pymammotion/mammotion/commands/messages/navigation.py,sha256=-qit4SrtMpnPr0qsub_HD0pzKMlYsyzspW5uf1Ym008,23217
54
54
  pymammotion/mammotion/commands/messages/network.py,sha256=1a0BIhaBhBuhqYaK5EUqLbDgfzjzsIGrXS32wMKtxOU,8316
55
55
  pymammotion/mammotion/commands/messages/ota.py,sha256=XkeuWBZtpYMMBze6r8UN7dJXbe2FxUNGNnjwBpXJKM0,1240
56
- pymammotion/mammotion/commands/messages/system.py,sha256=nQ-g-0ESDQ2YBB6JW7IF4Q1bmTbX_baA4dOw2P3GixI,10957
56
+ pymammotion/mammotion/commands/messages/system.py,sha256=m4Cc1zUcLgMFCu6zp5rMupwjDnMD-1PM6hqXpXuXRtw,10984
57
57
  pymammotion/mammotion/commands/messages/video.py,sha256=_8lJsU4sLm2CGnc7RDkueA0A51Ysui6x7SqFnhX8O2g,1007
58
58
  pymammotion/mammotion/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
- pymammotion/mammotion/control/joystick.py,sha256=EWV20MMzQuhbLlNlXbsyZKSEpeM7x1CQL7saU4Pn0-g,6165
59
+ pymammotion/mammotion/control/joystick.py,sha256=mresPubTlWCuLQBOFD9KTyYJz5BjZgqt52BaRodemhM,5557
60
60
  pymammotion/mammotion/devices/__init__.py,sha256=T72jt0ejtMjo1rPmn_FeMF3pmp0LLeRRpc9WcDKEYYY,126
61
- pymammotion/mammotion/devices/mammotion.py,sha256=eZxYo4GWomEaSQNAm_kapE64nyjADj72vwsXmIkdidI,48239
61
+ pymammotion/mammotion/devices/mammotion.py,sha256=a-5dbrc-qBc8NHFbwVzrj3eLxFnmlfr8Hyf36Pv1JG0,49404
62
62
  pymammotion/mqtt/__init__.py,sha256=Ocs5e-HLJvTuDpVXyECEsWIvwsUaxzj7lZ9mSYutNDY,105
63
63
  pymammotion/mqtt/mammotion_future.py,sha256=WKnHqeHiS2Ut-SaDBNOxqh1jDLeTiyLTsJ7PNUexrjk,687
64
- pymammotion/mqtt/mammotion_mqtt.py,sha256=7V0JW2N9dshUJAGlg6d6Y5LKWYoXvlQd0o-9l6idPNg,8071
64
+ pymammotion/mqtt/mammotion_mqtt.py,sha256=X012f5RvmZI52FlgchLSWbngvPnkv9M_gTYvJHakYaM,8127
65
65
  pymammotion/proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
66
  pymammotion/proto/basestation.proto,sha256=_x5gAz3FkZXS1jtq4GgZgaDCuRU-UV-7HTFdsfQ3zbo,1034
67
67
  pymammotion/proto/basestation.py,sha256=js64_N2xQYRxWPRdVNEapO0qe7vBlfYnjW5sE8hi7hw,2026
@@ -105,13 +105,15 @@ pymammotion/proto/mctrl_sys_pb2.py,sha256=DYemb514mlC7c27t-k1YqqBif0xxhLmnIWk8rX
105
105
  pymammotion/proto/mctrl_sys_pb2.pyi,sha256=Dj_1UM86kZ5MfcVyNC76Z0gKrfl5YFsVWP2b-bKoZvk,38912
106
106
  pymammotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
107
107
  pymammotion/utility/constant/__init__.py,sha256=tZz7szqIhrzNjfcLU3ysfINfg5VEBUAisd9AhP4mvT0,38
108
- pymammotion/utility/constant/device_constant.py,sha256=aM69DJJy-h-btLP9RJezld1zm1rGz4gZRdRSpArVtmc,7109
108
+ pymammotion/utility/constant/device_constant.py,sha256=rAEK60F52VyJL31uLnq0Y60D-0VK5gVm59yi9kBfndM,7123
109
+ pymammotion/utility/conversions.py,sha256=ioVTEGTmq2-H9GSQbHlm2D9mNAhOTbabuiTaKPElyXQ,88
109
110
  pymammotion/utility/datatype_converter.py,sha256=v6zym2Zu0upxQjR-xDqXwi3516zpntSlg7LP8tQF5K8,4216
110
111
  pymammotion/utility/device_type.py,sha256=KYawu2glZMVlPmxRbA4kVFujXz3miHp3rJiOWRVj-GI,8285
111
112
  pymammotion/utility/map.py,sha256=aoi-Luzuph02hKynTofMoq3mnPstanx75MDAVv49CuY,2211
113
+ pymammotion/utility/movement.py,sha256=JISPBWCOe4MqHbhmkewhV5aWykr1p6f01DzJfvOF-J8,613
112
114
  pymammotion/utility/periodic.py,sha256=9wJMfwXPlx6Mbp3Fws7LLTI34ZDKphH1bva_Ggyk32g,3281
113
115
  pymammotion/utility/rocker_util.py,sha256=syPL0QN4zMzHiTIkUKS7RXBBptjdbkfNlPddwUD5V3A,7171
114
- pymammotion-0.2.22.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
115
- pymammotion-0.2.22.dist-info/METADATA,sha256=7DY-GFnBPlE_Pr97Z4J25KjKSw6vIyVQ33lBdeY2Df0,3969
116
- pymammotion-0.2.22.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
117
- pymammotion-0.2.22.dist-info/RECORD,,
116
+ pymammotion-0.2.24.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
117
+ pymammotion-0.2.24.dist-info/METADATA,sha256=QvfMAbQeG90qBI9NAeUmgaIBG4DmCYUV4qWCrVeriMY,3969
118
+ pymammotion-0.2.24.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
119
+ pymammotion-0.2.24.dist-info/RECORD,,