pymammotion 0.0.40__py3-none-any.whl → 0.0.41__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (38) hide show
  1. pymammotion/__init__.py +4 -2
  2. pymammotion/aliyun/__init__.py +1 -0
  3. pymammotion/aliyun/cloud_gateway.py +74 -95
  4. pymammotion/aliyun/tmp_constant.py +2 -6
  5. pymammotion/bluetooth/ble.py +4 -12
  6. pymammotion/bluetooth/ble_message.py +12 -36
  7. pymammotion/bluetooth/data/convert.py +1 -3
  8. pymammotion/bluetooth/data/notifydata.py +0 -1
  9. pymammotion/data/model/device.py +62 -3
  10. pymammotion/data/model/hash_list.py +34 -14
  11. pymammotion/data/model/location.py +40 -0
  12. pymammotion/data/model/rapid_state.py +1 -5
  13. pymammotion/data/state_manager.py +84 -0
  14. pymammotion/event/event.py +18 -3
  15. pymammotion/http/http.py +2 -6
  16. pymammotion/mammotion/commands/mammotion_command.py +1 -3
  17. pymammotion/mammotion/commands/messages/driver.py +7 -21
  18. pymammotion/mammotion/commands/messages/media.py +4 -9
  19. pymammotion/mammotion/commands/messages/navigation.py +42 -107
  20. pymammotion/mammotion/commands/messages/network.py +10 -30
  21. pymammotion/mammotion/commands/messages/system.py +11 -26
  22. pymammotion/mammotion/commands/messages/video.py +1 -3
  23. pymammotion/mammotion/control/joystick.py +9 -33
  24. pymammotion/mammotion/devices/__init__.py +5 -1
  25. pymammotion/mammotion/devices/{luba.py → mammotion.py} +299 -110
  26. pymammotion/mqtt/__init__.py +5 -0
  27. pymammotion/mqtt/{mqtt.py → mammotion_mqtt.py} +46 -50
  28. pymammotion/utility/constant/device_constant.py +14 -0
  29. pymammotion/utility/datatype_converter.py +52 -9
  30. pymammotion/utility/device_type.py +129 -20
  31. pymammotion/utility/periodic.py +65 -0
  32. pymammotion/utility/rocker_util.py +63 -4
  33. {pymammotion-0.0.40.dist-info → pymammotion-0.0.41.dist-info}/METADATA +10 -4
  34. {pymammotion-0.0.40.dist-info → pymammotion-0.0.41.dist-info}/RECORD +36 -34
  35. {pymammotion-0.0.40.dist-info → pymammotion-0.0.41.dist-info}/WHEEL +1 -1
  36. pymammotion/luba/_init_.py +0 -0
  37. pymammotion/luba/base.py +0 -52
  38. {pymammotion-0.0.40.dist-info → pymammotion-0.0.41.dist-info}/LICENSE +0 -0
@@ -2,6 +2,8 @@
2
2
 
3
3
  from dataclasses import dataclass
4
4
 
5
+ from pymammotion.data.model import HashList
6
+ from pymammotion.data.model.location import Location
5
7
  from pymammotion.proto.dev_net import DevNet
6
8
  from pymammotion.proto.luba_msg import LubaMsg
7
9
  from pymammotion.proto.luba_mul import SocMul
@@ -9,19 +11,76 @@ from pymammotion.proto.mctrl_driver import MctlDriver
9
11
  from pymammotion.proto.mctrl_nav import MctlNav
10
12
  from pymammotion.proto.mctrl_ota import MctlOta
11
13
  from pymammotion.proto.mctrl_pept import MctlPept
12
- from pymammotion.proto.mctrl_sys import MctlSys
14
+ from pymammotion.proto.mctrl_sys import MctlSys, SystemUpdateBufMsg
13
15
 
14
16
 
15
17
  @dataclass
16
18
  class MowingDevice:
17
- """Wraps the betterproto dataclasses so we can bypass the groups for keeping all data."""
19
+ """Wraps the betterproto dataclasses, so we can bypass the groups for keeping all data."""
18
20
 
19
21
  device: LubaMsg
22
+ map: HashList
23
+ location: Location
24
+
25
+ def __init__(self):
26
+ self.device = LubaMsg()
27
+ self.map = HashList(area={}, path={}, obstacle={})
28
+ self.location = Location()
29
+ self.err_code_list = []
30
+ self.err_code_list_time = []
20
31
 
21
32
  @classmethod
22
33
  def from_raw(cls, raw: dict) -> "MowingDevice":
23
34
  """Take in raw data to hold in the betterproto dataclass."""
24
- return MowingDevice(device=LubaMsg(**raw))
35
+ mowing_device = MowingDevice()
36
+ mowing_device.device = LubaMsg(**raw)
37
+ return mowing_device
38
+
39
+ def update_raw(self, raw: dict) -> None:
40
+ """Update the raw LubaMsg data."""
41
+ self.device = LubaMsg(**raw)
42
+
43
+ def buffer(self, buffer_list: SystemUpdateBufMsg):
44
+ """Update the device based on which buffer we are reading from."""
45
+ match buffer_list.update_buf_data[0]:
46
+ case 1:
47
+ # 4 speed
48
+ self.location.RTK.latitude = buffer_list.update_buf_data[5]
49
+ self.location.RTK.longitude = buffer_list.update_buf_data[6]
50
+ self.location.dock.latitude = buffer_list.update_buf_data[7]
51
+ self.location.dock.longitude = buffer_list.update_buf_data[8]
52
+ self.location.dock.rotation = buffer_list.update_buf_data[3] + 180
53
+ case 2:
54
+ self.err_code_list.clear()
55
+ self.err_code_list_time.clear()
56
+ self.err_code_list.extend(
57
+ [
58
+ buffer_list.update_buf_data[3],
59
+ buffer_list.update_buf_data[5],
60
+ buffer_list.update_buf_data[7],
61
+ buffer_list.update_buf_data[9],
62
+ buffer_list.update_buf_data[11],
63
+ buffer_list.update_buf_data[13],
64
+ buffer_list.update_buf_data[15],
65
+ buffer_list.update_buf_data[17],
66
+ buffer_list.update_buf_data[19],
67
+ buffer_list.update_buf_data[21],
68
+ ]
69
+ )
70
+ self.err_code_list_time.extend(
71
+ [
72
+ buffer_list.update_buf_data[4],
73
+ buffer_list.update_buf_data[6],
74
+ buffer_list.update_buf_data[8],
75
+ buffer_list.update_buf_data[10],
76
+ buffer_list.update_buf_data[12],
77
+ buffer_list.update_buf_data[14],
78
+ buffer_list.update_buf_data[16],
79
+ buffer_list.update_buf_data[18],
80
+ buffer_list.update_buf_data[20],
81
+ buffer_list.update_buf_data[22],
82
+ ]
83
+ )
25
84
 
26
85
  @property
27
86
  def net(self):
@@ -1,17 +1,37 @@
1
- import typing
1
+ from dataclasses import dataclass
2
2
 
3
+ from pymammotion.proto.mctrl_nav import NavGetCommDataAck
3
4
 
5
+
6
+ @dataclass
7
+ class FrameList:
8
+ total_frame: int
9
+ data: list[NavGetCommDataAck]
10
+
11
+
12
+ @dataclass
4
13
  class HashList:
5
- def __init__(self):
6
- self.pver: int = 0
7
- self.subCmd: int = 0
8
- self.totalFrame: int = 0
9
- self.currentFrame: int = 0
10
- self.dataHash: int = 0
11
- self.path: typing.List[int] = []
12
-
13
- def __str__(self) -> str:
14
- return (
15
- f"HashBean{{pver={self.pver}, subCmd={self.subCmd}, totalFrame={self.totalFrame}, "
16
- + f"currentFrame={self.currentFrame}, dataHash={self.dataHash}, path={self.path}}}"
17
- )
14
+ """stores our map data.
15
+ [hashID, FrameList].
16
+ """
17
+
18
+ area: dict # type 0
19
+ path: dict # type 2
20
+ obstacle: dict # type 1
21
+
22
+ def update(self, hash_data: NavGetCommDataAck):
23
+ """Update the map data."""
24
+ if hash_data.type == 0:
25
+ if self.area.get(hash_data.hash) is None:
26
+ self.area[hash_data.hash] = FrameList(total_frame=hash_data.total_frame, data=[hash_data])
27
+ self.area[hash_data.hash].data.append(hash_data)
28
+
29
+ if hash_data.type == 1:
30
+ if self.obstacle.get(hash_data.hash) is None:
31
+ self.obstacle[hash_data.hash] = FrameList(total_frame=hash_data.total_frame, data=[hash_data])
32
+ self.obstacle[hash_data.hash].data.append(hash_data)
33
+
34
+ if hash_data.type == 2:
35
+ if self.path.get(hash_data.hash) is None:
36
+ self.path[hash_data.hash] = FrameList(total_frame=hash_data.total_frame, data=[hash_data])
37
+ self.path[hash_data.hash].data.append(hash_data)
@@ -0,0 +1,40 @@
1
+ """Contains RTK models for robot location and RTK positions."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass
7
+ class Point:
8
+ """Returns a lat long."""
9
+
10
+ latitude: float
11
+ longitude: float
12
+
13
+ def __init__(self):
14
+ self.latitude = 0
15
+ self.longitude = 0
16
+
17
+
18
+ @dataclass
19
+ class Dock(Point):
20
+ """Stores robot dock position."""
21
+
22
+ rotation: int
23
+
24
+ def __init__(self):
25
+ super().__init__()
26
+ self.rotation = 0
27
+
28
+
29
+ @dataclass
30
+ class Location:
31
+ """Stores/retrieves RTK GPS data."""
32
+
33
+ device: Point
34
+ RTK: Point
35
+ dock: Dock
36
+
37
+ def __init__(self):
38
+ self.device = Point()
39
+ self.RTK = Point()
40
+ self.dock = Dock()
@@ -26,11 +26,7 @@ class RapidState:
26
26
  @classmethod
27
27
  def from_raw(cls, raw: list[int]) -> "RapidState":
28
28
  return RapidState(
29
- rtk_status=RTKStatus.FINE
30
- if raw[0] == 4
31
- else RTKStatus.BAD
32
- if raw[0] in (1, 5)
33
- else RTKStatus.NONE,
29
+ rtk_status=RTKStatus.FINE if raw[0] == 4 else RTKStatus.BAD if raw[0] in (1, 5) else RTKStatus.NONE,
34
30
  pos_level=raw[1],
35
31
  satellites_total=raw[2],
36
32
  rtk_age=raw[3] / 10000,
@@ -0,0 +1,84 @@
1
+ """Manage state from notifications into MowingDevice."""
2
+
3
+ import betterproto
4
+
5
+ from pymammotion.data.model.device import MowingDevice
6
+ from pymammotion.event.event import DataEvent
7
+ from pymammotion.proto.luba_msg import LubaMsg
8
+
9
+
10
+ class StateManager:
11
+ """Manage state."""
12
+
13
+ _device: MowingDevice
14
+ gethash_ack_callback: DataEvent
15
+ get_commondata_ack_callback: DataEvent
16
+
17
+ def __init__(self, device: MowingDevice):
18
+ self._device = device
19
+ self.gethash_ack_callback = DataEvent()
20
+ self.get_commondata_ack_callback = DataEvent()
21
+
22
+ def get_device(self) -> MowingDevice:
23
+ """Get device."""
24
+ return self._device
25
+
26
+ def set_device(self, device: MowingDevice):
27
+ """Set device."""
28
+ self._device = device
29
+
30
+ async def notification(self, message: LubaMsg):
31
+ """Handle protobuf notifications."""
32
+ res = betterproto.which_one_of(message, "LubaSubMsg")
33
+
34
+ match res[0]:
35
+ case "nav":
36
+ await self._update_nav_data(message)
37
+ case "sys":
38
+ self._update_sys_data(message)
39
+ case "driver":
40
+ self._update_driver_data(message)
41
+ case "net":
42
+ self._update_net_data(message)
43
+ case "mul":
44
+ self._update_mul_data(message)
45
+ case "ota":
46
+ self._update_ota_data(message)
47
+
48
+ async def _update_nav_data(self, message):
49
+ """Update nav data."""
50
+ nav_msg = betterproto.which_one_of(message.nav, "SubNavMsg")
51
+ match nav_msg[0]:
52
+ case "toapp_gethash_ack":
53
+ # call callback to handle this data
54
+ print(nav_msg[1])
55
+ await self.gethash_ack_callback.data_event(nav_msg[1])
56
+ case "toapp_get_commondata_ack":
57
+ # callback to additional method
58
+ print(nav_msg[1])
59
+ self._device.map.update(nav_msg[1])
60
+ await self.get_commondata_ack_callback.data_event(nav_msg[1])
61
+
62
+ def _update_sys_data(self, message):
63
+ """Update system."""
64
+ sys_msg = betterproto.which_one_of(message.sys, "SubSysMsg")
65
+ match sys_msg[0]:
66
+ case "system_update_buf":
67
+ # call callback to handle this data
68
+ print(sys_msg[1])
69
+ self._device.buffer(sys_msg[1])
70
+ case "toapp_report_data":
71
+ # data for sensors
72
+ pass
73
+
74
+ def _update_driver_data(self, message):
75
+ pass
76
+
77
+ def _update_net_data(self, message):
78
+ pass
79
+
80
+ def _update_mul_data(self, message):
81
+ pass
82
+
83
+ def _update_ota_data(self, message):
84
+ pass
@@ -14,9 +14,7 @@ class Event:
14
14
  return self
15
15
 
16
16
  async def __call__(self, *args, **kwargs):
17
- await asyncio.gather(
18
- *[handler(*args, **kwargs) for handler in self.__eventhandlers]
19
- )
17
+ await asyncio.gather(*[handler(*args, **kwargs) for handler in self.__eventhandlers])
20
18
 
21
19
 
22
20
  class MoveEvent:
@@ -48,3 +46,20 @@ class BleNotificationEvent:
48
46
 
49
47
  def RemoveSubscribersForBleNotificationEvent(self, objMethod):
50
48
  self.OnBleNotification -= objMethod
49
+
50
+
51
+ class DataEvent:
52
+ """Callbacks for data events."""
53
+
54
+ def __init__(self):
55
+ self.on_data_event = Event()
56
+
57
+ async def data_event(self, data):
58
+ # This function will be executed when data is received.
59
+ await self.on_data_event(data)
60
+
61
+ def add_subscribers(self, obj_method):
62
+ self.on_data_event += obj_method
63
+
64
+ def remove_subscribers(self, obj_method):
65
+ self.on_data_event -= obj_method
pymammotion/http/http.py CHANGED
@@ -51,9 +51,7 @@ class LubaHTTP:
51
51
  self._login = login
52
52
 
53
53
  @classmethod
54
- async def login(
55
- cls, session: ClientSession, username: str, password: str
56
- ) -> Response[LoginResponseData]:
54
+ async def login(cls, session: ClientSession, username: str, password: str) -> Response[LoginResponseData]:
57
55
  async with session.post(
58
56
  "/user-server/v1/user/oauth/token",
59
57
  params=dict(
@@ -69,9 +67,7 @@ class LubaHTTP:
69
67
  # TODO catch errors from mismatch user / password
70
68
  # Assuming the data format matches the expected structure
71
69
  login_response_data = LoginResponseData.from_dict(data["data"])
72
- return Response(
73
- data=login_response_data, code=data["code"], msg=data["msg"]
74
- )
70
+ return Response(data=login_response_data, code=data["code"], msg=data["msg"])
75
71
 
76
72
 
77
73
  async def connect_http(username: str, password: str) -> LubaHTTP:
@@ -6,9 +6,7 @@ from pymammotion.mammotion.commands.messages.video import MessageVideo
6
6
  from pymammotion.proto import dev_net_pb2, luba_msg_pb2
7
7
 
8
8
 
9
- class MammotionCommand(
10
- MessageSystem, MessageNavigation, MessageNetwork, MessageOta, MessageVideo
11
- ):
9
+ class MammotionCommand(MessageSystem, MessageNavigation, MessageNetwork, MessageOta, MessageVideo):
12
10
  """MQTT commands for Luba."""
13
11
 
14
12
  def __init__(self, device_name: str) -> None:
@@ -28,49 +28,35 @@ class MessageDriver(AbstractMessage, ABC):
28
28
 
29
29
  def set_blade_height(self, height: int):
30
30
  logger.debug(f"Send knife height height={height}")
31
- build = mctrl_driver_pb2.MctlDriver(
32
- todev_knife_height_set=mctrl_driver_pb2.DrvKnifeHeight(knifeHeight=height)
33
- )
31
+ build = mctrl_driver_pb2.MctlDriver(todev_knife_height_set=mctrl_driver_pb2.DrvKnifeHeight(knifeHeight=height))
34
32
  logger.debug(f"Send command--Knife motor height setting height={height}")
35
33
  return self.send_order_msg_driver(build)
36
34
 
37
35
  def set_speed(self, speed: float):
38
36
  logger.debug(f"{self.get_device_name()} set speed, {speed}")
39
- build = mctrl_driver_pb2.MctlDriver(
40
- bidire_speed_read_set=mctrl_driver_pb2.DrvSrSpeed(speed=speed, rw=1)
41
- )
37
+ build = mctrl_driver_pb2.MctlDriver(bidire_speed_read_set=mctrl_driver_pb2.DrvSrSpeed(speed=speed, rw=1))
42
38
  logger.debug(f"Send command--Speed setting speed={speed}")
43
39
  return self.send_order_msg_driver(build)
44
40
 
45
41
  def syn_nav_star_point_data(self, sat_system: int):
46
42
  build = mctrl_driver_pb2.MctlDriver(
47
- rtk_sys_mask_query=mctrl_driver_pb2.rtk_sys_mask_query_t(
48
- sat_system=sat_system
49
- )
50
- )
51
- logger.debug(
52
- f"Send command--Navigation satellite frequency point synchronization={sat_system}"
43
+ rtk_sys_mask_query=mctrl_driver_pb2.rtk_sys_mask_query_t(sat_system=sat_system)
53
44
  )
45
+ logger.debug(f"Send command--Navigation satellite frequency point synchronization={sat_system}")
54
46
  return self.send_order_msg_driver(build)
55
47
 
56
48
  def set_nav_star_point(self, cmd_req: str):
57
49
  build = mctrl_driver_pb2.MctlDriver(
58
- rtk_cfg_req=mctrl_driver_pb2.rtk_cfg_req_t(
59
- cmd_req=cmd_req, cmd_length=len(cmd_req) - 1
60
- )
61
- )
62
- logger.debug(
63
- f"Send command--Navigation satellite frequency point setting={cmd_req}"
50
+ rtk_cfg_req=mctrl_driver_pb2.rtk_cfg_req_t(cmd_req=cmd_req, cmd_length=len(cmd_req) - 1)
64
51
  )
52
+ logger.debug(f"Send command--Navigation satellite frequency point setting={cmd_req}")
65
53
  logger.debug(
66
54
  f"Navigation satellite setting, Send command--Navigation satellite frequency point setting={cmd_req}"
67
55
  )
68
56
  return self.send_order_msg_driver(build)
69
57
 
70
58
  def get_speed(self):
71
- build = mctrl_driver_pb2.MctlDriver(
72
- bidire_speed_read_set=mctrl_driver_pb2.DrvSrSpeed(rw=0)
73
- )
59
+ build = mctrl_driver_pb2.MctlDriver(bidire_speed_read_set=mctrl_driver_pb2.DrvSrSpeed(rw=0))
74
60
  logger.debug("Send command--Get speed value")
75
61
  return self.send_order_msg_driver(build)
76
62
 
@@ -4,6 +4,7 @@ from pymammotion.proto.luba_mul import MUL_LANGUAGE
4
4
 
5
5
 
6
6
  class MessageMedia:
7
+ @staticmethod
7
8
  def send_order_msg_media(self, mul):
8
9
  luba_msg = luba_msg_pb2.LubaMsg(
9
10
  msgtype=luba_msg_pb2.MSG_CMD_TYPE_MUL,
@@ -19,18 +20,12 @@ class MessageMedia:
19
20
  return luba_msg.SerializeToString()
20
21
 
21
22
  def set_car_volume(self, volume: int):
22
- return self.send_order_msg_media(
23
- luba_mul_pb2.SocMul(set_audio=luba_mul_pb2.MulSetAudio(at_switch=volume))
24
- )
23
+ return self.send_order_msg_media(luba_mul_pb2.SocMul(set_audio=luba_mul_pb2.MulSetAudio(at_switch=volume)))
25
24
 
26
25
  def set_car_voice_language(self, language_type: MUL_LANGUAGE | str | None):
27
26
  return self.send_order_msg_media(
28
- luba_mul_pb2.SocMul(
29
- set_audio=luba_mul_pb2.MulSetAudio(au_language=language_type)
30
- )
27
+ luba_mul_pb2.SocMul(set_audio=luba_mul_pb2.MulSetAudio(au_language=language_type))
31
28
  )
32
29
 
33
30
  def set_car_wiper(self, round_num: int):
34
- return self.send_order_msg_media(
35
- luba_mul_pb2.SocMul(set_wiper=luba_mul_pb2.MulSetWiper(round=round_num))
36
- )
31
+ return self.send_order_msg_media(luba_mul_pb2.SocMul(set_wiper=luba_mul_pb2.MulSetWiper(round=round_num)))