pymammotion 0.5.69__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. pymammotion/__init__.py +53 -0
  2. pymammotion/agora/__init__.py +0 -0
  3. pymammotion/agora/agora_api.py +755 -0
  4. pymammotion/agora/agora_rtc_capabilities.py +748 -0
  5. pymammotion/agora/agora_websockets.py +1175 -0
  6. pymammotion/aliyun/__init__.py +1 -0
  7. pymammotion/aliyun/client.py +235 -0
  8. pymammotion/aliyun/cloud_gateway.py +982 -0
  9. pymammotion/aliyun/model/aep_response.py +21 -0
  10. pymammotion/aliyun/model/connect_response.py +51 -0
  11. pymammotion/aliyun/model/dev_by_account_response.py +195 -0
  12. pymammotion/aliyun/model/login_by_oauth_response.py +64 -0
  13. pymammotion/aliyun/model/regions_response.py +29 -0
  14. pymammotion/aliyun/model/session_by_authcode_response.py +19 -0
  15. pymammotion/aliyun/model/thing_response.py +12 -0
  16. pymammotion/aliyun/regions.py +62 -0
  17. pymammotion/aliyun/tea/core.py +297 -0
  18. pymammotion/aliyun/tmp_constant.py +171 -0
  19. pymammotion/bluetooth/__init__.py +1 -0
  20. pymammotion/bluetooth/ble.py +62 -0
  21. pymammotion/bluetooth/ble_message.py +676 -0
  22. pymammotion/bluetooth/const.py +27 -0
  23. pymammotion/bluetooth/data/__init__.py +0 -0
  24. pymammotion/bluetooth/data/convert.py +25 -0
  25. pymammotion/bluetooth/data/framectrldata.py +40 -0
  26. pymammotion/bluetooth/data/notifydata.py +62 -0
  27. pymammotion/bluetooth/model/__init__.py +0 -0
  28. pymammotion/bluetooth/model/atomic_integer.py +54 -0
  29. pymammotion/const.py +13 -0
  30. pymammotion/data/__init__.py +0 -0
  31. pymammotion/data/model/__init__.py +8 -0
  32. pymammotion/data/model/account.py +8 -0
  33. pymammotion/data/model/device.py +192 -0
  34. pymammotion/data/model/device_config.py +72 -0
  35. pymammotion/data/model/device_info.py +60 -0
  36. pymammotion/data/model/device_limits.py +49 -0
  37. pymammotion/data/model/enums.py +77 -0
  38. pymammotion/data/model/errors.py +12 -0
  39. pymammotion/data/model/events.py +14 -0
  40. pymammotion/data/model/generate_geojson.py +565 -0
  41. pymammotion/data/model/generate_route_information.py +26 -0
  42. pymammotion/data/model/hash_list.py +475 -0
  43. pymammotion/data/model/location.py +36 -0
  44. pymammotion/data/model/mowing_modes.py +77 -0
  45. pymammotion/data/model/rapid_state.py +45 -0
  46. pymammotion/data/model/raw_data.py +215 -0
  47. pymammotion/data/model/region_data.py +102 -0
  48. pymammotion/data/model/report_info.py +182 -0
  49. pymammotion/data/model/work.py +27 -0
  50. pymammotion/data/mower_state_manager.py +369 -0
  51. pymammotion/data/mqtt/__init__.py +1 -0
  52. pymammotion/data/mqtt/event.py +227 -0
  53. pymammotion/data/mqtt/mammotion_properties.py +276 -0
  54. pymammotion/data/mqtt/properties.py +203 -0
  55. pymammotion/data/mqtt/status.py +57 -0
  56. pymammotion/event/__init__.py +6 -0
  57. pymammotion/event/event.py +96 -0
  58. pymammotion/homeassistant/__init__.py +3 -0
  59. pymammotion/homeassistant/mower_api.py +514 -0
  60. pymammotion/homeassistant/rtk_api.py +54 -0
  61. pymammotion/http/__init__.py +0 -0
  62. pymammotion/http/encryption.py +220 -0
  63. pymammotion/http/http.py +673 -0
  64. pymammotion/http/model/__init__.py +0 -0
  65. pymammotion/http/model/camera_stream.py +31 -0
  66. pymammotion/http/model/http.py +249 -0
  67. pymammotion/http/model/response_factory.py +61 -0
  68. pymammotion/http/model/rtk.py +16 -0
  69. pymammotion/mammotion/__init__.py +0 -0
  70. pymammotion/mammotion/commands/__init__.py +0 -0
  71. pymammotion/mammotion/commands/abstract_message.py +24 -0
  72. pymammotion/mammotion/commands/mammotion_command.py +81 -0
  73. pymammotion/mammotion/commands/messages/__init__.py +0 -0
  74. pymammotion/mammotion/commands/messages/basestation.py +43 -0
  75. pymammotion/mammotion/commands/messages/driver.py +122 -0
  76. pymammotion/mammotion/commands/messages/media.py +87 -0
  77. pymammotion/mammotion/commands/messages/navigation.py +564 -0
  78. pymammotion/mammotion/commands/messages/network.py +205 -0
  79. pymammotion/mammotion/commands/messages/ota.py +38 -0
  80. pymammotion/mammotion/commands/messages/system.py +330 -0
  81. pymammotion/mammotion/commands/messages/video.py +33 -0
  82. pymammotion/mammotion/control/__init__.py +0 -0
  83. pymammotion/mammotion/control/joystick.py +145 -0
  84. pymammotion/mammotion/devices/__init__.py +29 -0
  85. pymammotion/mammotion/devices/base.py +163 -0
  86. pymammotion/mammotion/devices/mammotion.py +571 -0
  87. pymammotion/mammotion/devices/mammotion_bluetooth.py +496 -0
  88. pymammotion/mammotion/devices/mammotion_cloud.py +355 -0
  89. pymammotion/mammotion/devices/mammotion_mower_ble.py +48 -0
  90. pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
  91. pymammotion/mammotion/devices/managers/managers.py +81 -0
  92. pymammotion/mammotion/devices/mower_device.py +120 -0
  93. pymammotion/mammotion/devices/mower_manager.py +107 -0
  94. pymammotion/mammotion/devices/rtk_ble.py +89 -0
  95. pymammotion/mammotion/devices/rtk_cloud.py +115 -0
  96. pymammotion/mammotion/devices/rtk_device.py +50 -0
  97. pymammotion/mammotion/devices/rtk_manager.py +125 -0
  98. pymammotion/mqtt/__init__.py +6 -0
  99. pymammotion/mqtt/aliyun_mqtt.py +237 -0
  100. pymammotion/mqtt/linkkit/__init__.py +5 -0
  101. pymammotion/mqtt/linkkit/h2client.py +585 -0
  102. pymammotion/mqtt/linkkit/linkkit.py +3025 -0
  103. pymammotion/mqtt/mammotion_future.py +26 -0
  104. pymammotion/mqtt/mammotion_mqtt.py +214 -0
  105. pymammotion/mqtt/mqtt_models.py +66 -0
  106. pymammotion/proto/__init__.py +4841 -0
  107. pymammotion/proto/basestation.proto +51 -0
  108. pymammotion/proto/basestation_pb2.py +35 -0
  109. pymammotion/proto/basestation_pb2.pyi +89 -0
  110. pymammotion/proto/common.proto +7 -0
  111. pymammotion/proto/common_pb2.py +25 -0
  112. pymammotion/proto/common_pb2.pyi +13 -0
  113. pymammotion/proto/dev_net.proto +321 -0
  114. pymammotion/proto/dev_net_pb2.py +111 -0
  115. pymammotion/proto/dev_net_pb2.pyi +515 -0
  116. pymammotion/proto/luba_msg.proto +76 -0
  117. pymammotion/proto/luba_msg_pb2.py +41 -0
  118. pymammotion/proto/luba_msg_pb2.pyi +97 -0
  119. pymammotion/proto/luba_mul.proto +129 -0
  120. pymammotion/proto/luba_mul_pb2.py +61 -0
  121. pymammotion/proto/luba_mul_pb2.pyi +178 -0
  122. pymammotion/proto/mctrl_driver.proto +107 -0
  123. pymammotion/proto/mctrl_driver_pb2.py +57 -0
  124. pymammotion/proto/mctrl_driver_pb2.pyi +167 -0
  125. pymammotion/proto/mctrl_nav.proto +591 -0
  126. pymammotion/proto/mctrl_nav_pb2.py +136 -0
  127. pymammotion/proto/mctrl_nav_pb2.pyi +1067 -0
  128. pymammotion/proto/mctrl_ota.proto +80 -0
  129. pymammotion/proto/mctrl_ota_pb2.py +45 -0
  130. pymammotion/proto/mctrl_ota_pb2.pyi +128 -0
  131. pymammotion/proto/mctrl_pept.proto +34 -0
  132. pymammotion/proto/mctrl_pept_pb2.py +33 -0
  133. pymammotion/proto/mctrl_pept_pb2.pyi +58 -0
  134. pymammotion/proto/mctrl_sys.proto +741 -0
  135. pymammotion/proto/mctrl_sys_pb2.py +206 -0
  136. pymammotion/proto/mctrl_sys_pb2.pyi +1213 -0
  137. pymammotion/proto/message_pool.py +3 -0
  138. pymammotion/proto/py.typed +0 -0
  139. pymammotion/py.typed +0 -0
  140. pymammotion/utility/constant/__init__.py +3 -0
  141. pymammotion/utility/constant/device_constant.py +315 -0
  142. pymammotion/utility/conversions.py +5 -0
  143. pymammotion/utility/datatype_converter.py +124 -0
  144. pymammotion/utility/device_config.py +755 -0
  145. pymammotion/utility/device_type.py +489 -0
  146. pymammotion/utility/map.py +259 -0
  147. pymammotion/utility/movement.py +18 -0
  148. pymammotion/utility/mur_mur_hash.py +159 -0
  149. pymammotion/utility/periodic.py +106 -0
  150. pymammotion/utility/rocker_util.py +194 -0
  151. pymammotion-0.5.69.dist-info/METADATA +93 -0
  152. pymammotion-0.5.69.dist-info/RECORD +154 -0
  153. pymammotion-0.5.69.dist-info/WHEEL +4 -0
  154. pymammotion-0.5.69.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,25 @@
1
+ from google.protobuf.message import DecodeError
2
+
3
+ from pymammotion.proto import luba_msg_pb2
4
+
5
+
6
+ def parse_custom_data(data: bytes):
7
+ """Convert data into protobuf message."""
8
+ luba_msg = luba_msg_pb2.LubaMsg()
9
+ try:
10
+ luba_msg.ParseFromString(data)
11
+ return luba_msg
12
+
13
+ except DecodeError as err:
14
+ print(err)
15
+
16
+
17
+ def store_sys_data(sys) -> None:
18
+ if sys.HasField("systemTardStateTunnel"):
19
+ tard_state_data_list = sys.systemTardStateTunnel.tard_state_data
20
+ longValue8 = tard_state_data_list[0]
21
+ longValue9 = tard_state_data_list[1]
22
+ print("Device status report,deviceState:", longValue8, ",deviceName:", "Luba...")
23
+ chargeStateTemp = longValue9
24
+ longValue10 = tard_state_data_list[6]
25
+ longValue11 = tard_state_data_list[7]
@@ -0,0 +1,40 @@
1
+ class FrameCtrlData:
2
+ FRAME_CTRL_POSITION_CHECKSUM = 1
3
+ FRAME_CTRL_POSITION_DATA_DIRECTION = 2
4
+ FRAME_CTRL_POSITION_ENCRYPTED = 0
5
+ FRAME_CTRL_POSITION_FRAG = 4
6
+ FRAME_CTRL_POSITION_REQUIRE_ACK = 3
7
+ mValue = 0
8
+
9
+ def __init__(self, frameCtrlValue) -> None:
10
+ self.mValue = frameCtrlValue
11
+
12
+ def check(self, position):
13
+ return ((self.mValue >> position) & 1) == 1
14
+
15
+ def isEncrypted(self):
16
+ return self.check(0)
17
+
18
+ def isChecksum(self):
19
+ return self.check(1)
20
+
21
+ def isAckRequirement(self):
22
+ return self.check(3)
23
+
24
+ def hasFrag(self):
25
+ return self.check(4)
26
+
27
+ @staticmethod
28
+ def getFrameCTRLValue(encrypted, checksum, direction, requireAck, frag):
29
+ frame = 0
30
+ if encrypted:
31
+ frame = 0 | 1
32
+ if checksum:
33
+ frame |= 2
34
+ if direction == 1:
35
+ frame |= 4
36
+ if requireAck:
37
+ frame |= 8
38
+ if frag:
39
+ return frame | 16
40
+ return frame
@@ -0,0 +1,62 @@
1
+ from io import BytesIO
2
+
3
+ """Notify data object"""
4
+
5
+
6
+ class BlufiNotifyData:
7
+ """generated source for class BlufiNotifyData"""
8
+
9
+ def __init__(self) -> None:
10
+ self.mDataOS = BytesIO()
11
+ self.mFrameCtrlValue = 0
12
+ self.mPkgType = 0
13
+ self.mSubType = 0
14
+ self.mTypeValue = 0
15
+
16
+ def getType(self):
17
+ """Generated source for method getType"""
18
+ return self.mTypeValue
19
+
20
+ # JADX INFO: Access modifiers changed from: package-private
21
+ def setType(self, i) -> None:
22
+ """Generated source for method setType"""
23
+ self.mTypeValue = i
24
+
25
+ # JADX INFO: Access modifiers changed from: package-private
26
+ def getPkgType(self):
27
+ """Generated source for method getPkgType"""
28
+ return self.mPkgType
29
+
30
+ # JADX INFO: Access modifiers changed from: package-private
31
+ def setPkgType(self, i) -> None:
32
+ """Generated source for method setPkgType"""
33
+ self.mPkgType = i
34
+
35
+ # JADX INFO: Access modifiers changed from: package-private
36
+ def getSubType(self):
37
+ """Generated source for method getSubType"""
38
+ return self.mSubType
39
+
40
+ # JADX INFO: Access modifiers changed from: package-private
41
+ def setSubType(self, i) -> None:
42
+ """Generated source for method setSubType"""
43
+ self.mSubType = i
44
+
45
+ def getFrameCtrl(self):
46
+ """Generated source for method getFrameCtrl"""
47
+ return self.mFrameCtrlValue
48
+
49
+ # JADX INFO: Access modifiers changed from: package-private
50
+ def setFrameCtrl(self, i) -> None:
51
+ """Generated source for method setFrameCtrl"""
52
+ self.mFrameCtrlValue = i
53
+
54
+ # JADX INFO: Access modifiers changed from: package-private
55
+ def addData(self, bArr, i) -> None:
56
+ """Generated source for method addData"""
57
+ self.mDataOS.write(bArr[i:])
58
+
59
+ # JADX INFO: Access modifiers changed from: package-private
60
+ def getDataArray(self):
61
+ """Generated source for method getDataArray"""
62
+ return self.mDataOS.getvalue()
File without changes
@@ -0,0 +1,54 @@
1
+ from threading import Lock
2
+
3
+
4
+ class AtomicInteger:
5
+ """Thread-safe atomic integer implementation."""
6
+
7
+ def __init__(self, initial_value: int = 0) -> None:
8
+ """Initialize atomic integer with given value."""
9
+ self._value = initial_value
10
+ self._lock = Lock()
11
+
12
+ def get(self) -> int:
13
+ """Get the current value."""
14
+ with self._lock:
15
+ return self._value
16
+
17
+ def set(self, value: int) -> None:
18
+ """Set a new value."""
19
+ with self._lock:
20
+ self._value = value
21
+
22
+ def increment_and_get(self) -> int:
23
+ """Increment the value and return the new value."""
24
+ with self._lock:
25
+ self._value += 1
26
+ return self._value
27
+
28
+ def decrement_and_get(self) -> int:
29
+ """Decrement the value and return the new value."""
30
+ with self._lock:
31
+ self._value -= 1
32
+ return self._value
33
+
34
+ def add_and_get(self, delta: int) -> int:
35
+ """Add delta to value and return the new value."""
36
+ with self._lock:
37
+ self._value += delta
38
+ return self._value
39
+
40
+ def compare_and_set(self, expect: int, update: int) -> bool:
41
+ """Compare value with expected and set to update if they match."""
42
+ with self._lock:
43
+ if self._value == expect:
44
+ self._value = update
45
+ return True
46
+ return False
47
+
48
+ def __str__(self) -> str:
49
+ """Returns string representation of the atomic integer."""
50
+ return str(self.get())
51
+
52
+ def __repr__(self) -> str:
53
+ """Detailed string representation of the atomic integer."""
54
+ return f"AtomicInteger({self.get()})"
pymammotion/const.py ADDED
@@ -0,0 +1,13 @@
1
+ """App key and secret as taken from the mammotion android app."""
2
+
3
+ APP_KEY = "34231230"
4
+ APP_SECRET = "1ba85698bb10e19c6437413b61ba3445"
5
+ APP_VERSION = "1.11.130"
6
+ ALIYUN_DOMAIN = "api.link.aliyun.com"
7
+ MAMMOTION_DOMAIN = "https://id.mammotion.com"
8
+ MAMMOTION_API_DOMAIN = "https://domestic.mammotion.com"
9
+ MAMMOTION_CLIENT_ID = "MADKALUBAS"
10
+ MAMMOTION_CLIENT_SECRET = "GshzGRZJjuMUgd2sYHM7"
11
+
12
+ MAMMOTION_OUATH2_CLIENT_ID = "GxebgSt8si6pKqR"
13
+ MAMMOTION_OUATH2_CLIENT_SECRET = "JP0508SRJFa0A90ADpzLINDBxMa4Vj"
File without changes
@@ -0,0 +1,8 @@
1
+ """data models"""
2
+
3
+ from .generate_route_information import GenerateRouteInformation
4
+ from .hash_list import HashList
5
+ from .rapid_state import RapidState, RTKStatus
6
+ from .region_data import RegionData
7
+
8
+ __all__ = ["GenerateRouteInformation", "HashList", "RapidState", "RTKStatus", "RegionData"]
@@ -0,0 +1,8 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class Credentials:
6
+ email: str = None
7
+ password: str = None
8
+ account_id: str = None
@@ -0,0 +1,192 @@
1
+ """MowingDevice class to wrap around the betterproto dataclasses."""
2
+
3
+ from dataclasses import dataclass, field
4
+
5
+ import betterproto2
6
+ from mashumaro.mixins.orjson import DataClassORJSONMixin
7
+
8
+ from pymammotion.data.model import HashList, RapidState
9
+ from pymammotion.data.model.device_info import DeviceFirmwares, DeviceNonWorkingHours, MowerInfo
10
+ from pymammotion.data.model.errors import DeviceErrors
11
+ from pymammotion.data.model.events import Events
12
+ from pymammotion.data.model.location import Location
13
+ from pymammotion.data.model.report_info import ReportData
14
+ from pymammotion.data.model.work import CurrentTaskSettings
15
+ from pymammotion.data.mqtt.event import ThingEventMessage
16
+ from pymammotion.data.mqtt.properties import ThingPropertiesMessage
17
+ from pymammotion.data.mqtt.status import ThingStatusMessage
18
+ from pymammotion.http.model.http import CheckDeviceVersion
19
+ from pymammotion.proto import DeviceFwInfo, MowToAppInfoT, ReportInfoData, SystemRapidStateTunnelMsg, SystemUpdateBufMsg
20
+ from pymammotion.utility.constant import WorkMode
21
+ from pymammotion.utility.conversions import parse_double
22
+ from pymammotion.utility.map import CoordinateConverter
23
+
24
+
25
+ @dataclass
26
+ class MowingDevice(DataClassORJSONMixin):
27
+ """Wraps the betterproto dataclasses, so we can bypass the groups for keeping all data."""
28
+
29
+ name: str = ""
30
+ online: bool = True
31
+ enabled: bool = True
32
+ update_check: CheckDeviceVersion = field(default_factory=CheckDeviceVersion)
33
+ mower_state: MowerInfo = field(default_factory=MowerInfo)
34
+ mqtt_properties: ThingPropertiesMessage | None = None
35
+ status_properties: ThingStatusMessage | None = None
36
+ device_event: ThingEventMessage | None = None
37
+ map: HashList = field(default_factory=HashList)
38
+ work: CurrentTaskSettings = field(default_factory=CurrentTaskSettings)
39
+ location: Location = field(default_factory=Location)
40
+ mowing_state: RapidState = field(default_factory=RapidState)
41
+ report_data: ReportData = field(default_factory=ReportData)
42
+ device_firmwares: DeviceFirmwares = field(default_factory=DeviceFirmwares)
43
+ errors: DeviceErrors = field(default_factory=DeviceErrors)
44
+ non_work_hours: DeviceNonWorkingHours = field(default_factory=DeviceNonWorkingHours)
45
+ events: Events = field(default_factory=Events)
46
+
47
+ def buffer(self, buffer_list: SystemUpdateBufMsg) -> None:
48
+ """Update the device based on which buffer we are reading from."""
49
+ match buffer_list.update_buf_data[0]:
50
+ case 1:
51
+ # 4 speed?
52
+ if buffer_list.update_buf_data[5] != 0:
53
+ self.location.RTK.latitude = parse_double(buffer_list.update_buf_data[5], 8.0)
54
+ self.location.RTK.longitude = parse_double(buffer_list.update_buf_data[6], 8.0)
55
+ if buffer_list.update_buf_data[7] != 0:
56
+ # latitude Y longitude X
57
+ self.location.dock.longitude = parse_double(buffer_list.update_buf_data[7], 4.0)
58
+ self.location.dock.latitude = parse_double(buffer_list.update_buf_data[8], 4.0)
59
+ self.location.dock.rotation = buffer_list.update_buf_data[3]
60
+
61
+ case 2:
62
+ self.errors.err_code_list.clear()
63
+ self.errors.err_code_list_time.clear()
64
+ self.errors.err_code_list.extend(
65
+ [
66
+ buffer_list.update_buf_data[3],
67
+ buffer_list.update_buf_data[5],
68
+ buffer_list.update_buf_data[7],
69
+ buffer_list.update_buf_data[9],
70
+ buffer_list.update_buf_data[11],
71
+ buffer_list.update_buf_data[13],
72
+ buffer_list.update_buf_data[15],
73
+ buffer_list.update_buf_data[17],
74
+ buffer_list.update_buf_data[19],
75
+ buffer_list.update_buf_data[21],
76
+ ]
77
+ )
78
+ self.errors.err_code_list_time.extend(
79
+ [
80
+ buffer_list.update_buf_data[4],
81
+ buffer_list.update_buf_data[6],
82
+ buffer_list.update_buf_data[8],
83
+ buffer_list.update_buf_data[10],
84
+ buffer_list.update_buf_data[12],
85
+ buffer_list.update_buf_data[14],
86
+ buffer_list.update_buf_data[16],
87
+ buffer_list.update_buf_data[18],
88
+ buffer_list.update_buf_data[20],
89
+ buffer_list.update_buf_data[22],
90
+ ]
91
+ )
92
+ case 3:
93
+ # task state event
94
+ task_area_map: dict[int, int] = {}
95
+ task_area_ids = []
96
+
97
+ for i in range(3, len(buffer_list.update_buf_data), 2):
98
+ area_id = buffer_list.update_buf_data[i]
99
+
100
+ if area_id != 0:
101
+ area_value = int(buffer_list.update_buf_data[i + 1])
102
+ task_area_map[area_id] = area_value
103
+ task_area_ids.append(area_id)
104
+ self.events.work_tasks_event.hash_area_map = task_area_map
105
+ self.events.work_tasks_event.ids = task_area_ids
106
+
107
+ def update_report_data(self, toapp_report_data: ReportInfoData) -> None:
108
+ """Set report data for the mower."""
109
+ coordinate_converter = CoordinateConverter(self.location.RTK.latitude, self.location.RTK.longitude)
110
+ for index, location in enumerate(toapp_report_data.locations):
111
+ if index == 0 and location.real_pos_y != 0:
112
+ self.location.position_type = location.pos_type
113
+ self.location.orientation = int(location.real_toward / 10000)
114
+ self.location.device = coordinate_converter.enu_to_lla(
115
+ parse_double(location.real_pos_y, 4.0), parse_double(location.real_pos_x, 4.0)
116
+ )
117
+ self.map.invalidate_maps(location.bol_hash)
118
+ if location.zone_hash:
119
+ self.location.work_zone = (
120
+ location.zone_hash if self.report_data.dev.sys_status == WorkMode.MODE_WORKING else 0
121
+ )
122
+
123
+ if toapp_report_data.fw_info:
124
+ self.update_device_firmwares(toapp_report_data.fw_info)
125
+
126
+ if (
127
+ toapp_report_data.work
128
+ and (toapp_report_data.work.area >> 16) == 0
129
+ and toapp_report_data.work.path_hash == 0
130
+ ):
131
+ self.work.zone_hashs = []
132
+ self.map.current_mow_path = {}
133
+ self.map.generated_mow_path_geojson = {}
134
+
135
+ self.report_data.update(toapp_report_data.to_dict(casing=betterproto2.Casing.SNAKE))
136
+
137
+ def run_state_update(self, rapid_state: SystemRapidStateTunnelMsg) -> None:
138
+ """Set lat long, work zone of RTK and robot."""
139
+ coordinate_converter = CoordinateConverter(self.location.RTK.latitude, self.location.RTK.longitude)
140
+ self.mowing_state = RapidState().from_raw(rapid_state.rapid_state_data)
141
+ self.location.position_type = self.mowing_state.pos_type
142
+ self.location.orientation = int(self.mowing_state.toward / 10000)
143
+ self.location.device = coordinate_converter.enu_to_lla(
144
+ parse_double(self.mowing_state.pos_y, 4.0), parse_double(self.mowing_state.pos_x, 4.0)
145
+ )
146
+ if self.mowing_state.zone_hash:
147
+ self.location.work_zone = (
148
+ self.mowing_state.zone_hash if self.report_data.dev.sys_status == WorkMode.MODE_WORKING else 0
149
+ )
150
+
151
+ def mow_info(self, toapp_mow_info: MowToAppInfoT) -> None:
152
+ """Set mow info."""
153
+
154
+ def report_missing_data(self) -> None:
155
+ """Report missing data so we can refetch it."""
156
+
157
+ def update_device_firmwares(self, fw_info: DeviceFwInfo) -> None:
158
+ """Set firmware versions on all parts of the robot or RTK."""
159
+ for mod in fw_info.mod:
160
+ match mod.type:
161
+ case 1:
162
+ self.device_firmwares.main_controller = mod.version
163
+ case 3:
164
+ self.device_firmwares.left_motor_driver = mod.version
165
+ case 4:
166
+ self.device_firmwares.right_motor_driver = mod.version
167
+ case 5:
168
+ self.device_firmwares.rtk_rover_station = mod.version
169
+ case 101:
170
+ # RTK main board
171
+ self.device_firmwares.main_controller = mod.version
172
+ case 102:
173
+ self.device_firmwares.rtk_version = mod.version
174
+ case 103:
175
+ self.device_firmwares.lora_version = mod.version
176
+
177
+
178
+ @dataclass
179
+ class RTKDevice(DataClassORJSONMixin):
180
+ name: str
181
+ iot_id: str
182
+ product_key: str
183
+ online: bool = True
184
+ lat: float = 0.0
185
+ lon: float = 0.0
186
+ lora: str = ""
187
+ wifi_rssi: int = 0
188
+ device_version: str = ""
189
+ lora_version: str = ""
190
+ wifi_sta_mac: str = ""
191
+ bt_mac: str = ""
192
+ update_check: CheckDeviceVersion = field(default_factory=CheckDeviceVersion)
@@ -0,0 +1,72 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ from mashumaro.mixins.orjson import DataClassORJSONMixin
4
+
5
+ from pymammotion.utility.device_type import DeviceType
6
+
7
+
8
+ @dataclass
9
+ class OperationSettings(DataClassORJSONMixin):
10
+ """Operation settings for a device."""
11
+
12
+ is_mow: bool = True
13
+ is_dump: bool = True
14
+ is_edge: bool = False
15
+ collect_grass_frequency: int = 10
16
+ job_mode: int = 4 # taskMode
17
+ job_version: int = 0
18
+ job_id: int = 0
19
+ speed: float = 0.3
20
+ ultra_wave: int = 2 # touch no touch etc
21
+ channel_mode: int = 0 # grid or border first
22
+ channel_width: int = 25
23
+ rain_tactics: int = 0
24
+ blade_height: int = 0
25
+ path_order: str = ""
26
+ toward: int = 0 # is just angle
27
+ toward_included_angle: int = 90
28
+ toward_mode: int = 0 # angle type relative etc
29
+ border_mode: int = 0
30
+ obstacle_laps: int = 1
31
+ mowing_laps: int = 1 # border laps
32
+ start_progress: int = 0
33
+ areas: set[int] = field(default_factory=set)
34
+
35
+
36
+ def create_path_order(operation_mode: OperationSettings, device_name: str) -> str:
37
+ # TODO add scheduling logic from getReserved() WorkSettingViewModel.java
38
+ bArr = bytearray(8)
39
+ bArr[0] = operation_mode.border_mode
40
+ bArr[1] = operation_mode.obstacle_laps
41
+ bArr[3] = int(operation_mode.start_progress)
42
+ bArr[2] = 0
43
+ bArr[5] = 0
44
+ if not DeviceType.is_luba1(device_name):
45
+ bArr[4] = 0
46
+ if DeviceType.is_yuka(device_name) and not DeviceType.is_yuka_mini(device_name):
47
+ bArr[5] = calculate_yuka_mode(operation_mode)
48
+ else:
49
+ bArr[5] = 8 if DeviceType.is_luba_pro(device_name) else 0
50
+
51
+ bArr[6] = int(operation_mode.collect_grass_frequency) if operation_mode.is_dump else 10
52
+ if DeviceType.is_luba1(device_name):
53
+ bArr[4] = operation_mode.toward_mode
54
+ return bArr.decode()
55
+
56
+
57
+ def calculate_yuka_mode(operation_mode: OperationSettings) -> int:
58
+ if operation_mode.is_mow and operation_mode.is_dump and operation_mode.is_edge:
59
+ return 14
60
+ if operation_mode.is_mow and operation_mode.is_dump and not operation_mode.is_edge:
61
+ return 12
62
+ if operation_mode.is_mow and not operation_mode.is_dump and operation_mode.is_edge:
63
+ return 10
64
+ if operation_mode.is_mow and not operation_mode.is_dump and not operation_mode.is_edge:
65
+ return 8
66
+ if not operation_mode.is_mow and operation_mode.is_dump and operation_mode.is_edge:
67
+ return 6
68
+ if not operation_mode.is_mow and not operation_mode.is_dump and operation_mode.is_edge:
69
+ return 2
70
+ if not operation_mode.is_mow and operation_mode.is_dump and not operation_mode.is_edge:
71
+ return 4
72
+ return 0
@@ -0,0 +1,60 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ from mashumaro.mixins.orjson import DataClassORJSONMixin
4
+
5
+
6
+ @dataclass
7
+ class SideLight(DataClassORJSONMixin):
8
+ operate: int = 0
9
+ enable: int = 0
10
+ start_hour: int = 0
11
+ start_min: int = 0
12
+ end_hour: int = 0
13
+ end_min: int = 0
14
+ action: int = 0
15
+
16
+
17
+ @dataclass
18
+ class DeviceNonWorkingHours(DataClassORJSONMixin):
19
+ sub_cmd: int = 0
20
+ start_time: str = ""
21
+ end_time: str = ""
22
+
23
+
24
+ @dataclass
25
+ class LampInfo(DataClassORJSONMixin):
26
+ lamp_bright: int = 0
27
+ manual_light: bool = False
28
+ night_light: bool = False
29
+
30
+
31
+ @dataclass
32
+ class MowerInfo(DataClassORJSONMixin):
33
+ blade_status: bool = False
34
+ rain_detection: bool = False
35
+ traversal_mode: int = 0
36
+ turning_mode: int = 0
37
+ blade_mode: int = 0
38
+ blade_rpm: int = 0
39
+ side_led: SideLight = field(default_factory=SideLight)
40
+ collector_installation_status: bool = False
41
+ model: str = ""
42
+ swversion: str = ""
43
+ product_key: str = ""
44
+ model_id: str = ""
45
+ sub_model_id: str = ""
46
+ ble_mac: str = ""
47
+ wifi_mac: str = ""
48
+ lamp_info: LampInfo = field(default_factory=LampInfo)
49
+
50
+
51
+ @dataclass
52
+ class DeviceFirmwares(DataClassORJSONMixin):
53
+ device_version: str = ""
54
+ left_motor_driver: str = ""
55
+ lora_version: str = ""
56
+ main_controller: str = ""
57
+ model_name: str = ""
58
+ right_motor_driver: str = ""
59
+ rtk_rover_station: str = ""
60
+ rtk_version: str = ""
@@ -0,0 +1,49 @@
1
+ from dataclasses import dataclass, field
2
+
3
+
4
+ @dataclass
5
+ class RangeLimit:
6
+ min: float
7
+ max: float
8
+
9
+
10
+ @dataclass
11
+ class DeviceLimits:
12
+ blade_height: RangeLimit = field(default_factory=RangeLimit)
13
+ working_speed: RangeLimit = field(default_factory=RangeLimit)
14
+ path_spacing: RangeLimit = field(default_factory=RangeLimit)
15
+ work_area_num_max: int = 60
16
+ display_image_type: int = 0
17
+
18
+ def to_dict(self) -> dict:
19
+ """Convert the device limits to a dictionary format."""
20
+ return {
21
+ "blade_height": {"min": self.blade_height.min, "max": self.blade_height.max},
22
+ "working_speed": {"min": self.working_speed.min, "max": self.working_speed.max},
23
+ "path_spacing": {"min": self.path_spacing.min, "max": self.path_spacing.max},
24
+ "work_area_num_max": self.work_area_num_max,
25
+ "display_image_type": self.display_image_type,
26
+ }
27
+
28
+ @classmethod
29
+ def from_dict(cls, data: dict) -> "DeviceLimits":
30
+ """Create a DeviceLimits instance from a dictionary."""
31
+ return cls(
32
+ blade_height=RangeLimit(min=data["blade_height"]["min"], max=data["blade_height"]["max"]),
33
+ working_speed=RangeLimit(min=data["working_speed"]["min"], max=data["working_speed"]["max"]),
34
+ path_spacing=RangeLimit(min=data["path_spacing"]["min"], max=data["path_spacing"]["max"]),
35
+ work_area_num_max=data["work_area_num_max"],
36
+ display_image_type=data["display_image_type"],
37
+ )
38
+
39
+ def validate(self) -> bool:
40
+ """Validate that all ranges are logical (min <= max)."""
41
+ return all(
42
+ [
43
+ self.blade_height.min <= self.blade_height.max,
44
+ self.working_speed.min <= self.working_speed.max,
45
+ self.path_spacing.min <= self.path_spacing.max,
46
+ self.work_area_num_max > 0,
47
+ self.display_image_type in (0, 1),
48
+ ]
49
+ )
@@ -0,0 +1,77 @@
1
+ from enum import Enum
2
+
3
+
4
+ class ConnectionPreference(Enum):
5
+ """Enum for connection preference."""
6
+
7
+ ANY = 0
8
+ WIFI = 1
9
+ BLUETOOTH = 2
10
+ PREFER_WIFI = 3
11
+ PREFER_BLUETOOTH = 4
12
+
13
+
14
+ class PositionMode(Enum):
15
+ FIX = 0
16
+ SINGLE = 1
17
+ FLOAT = 2
18
+ NONE = 3
19
+ UNKNOWN = 4
20
+
21
+ @staticmethod
22
+ def from_value(value: int) -> "PositionMode":
23
+ if value == 0:
24
+ return PositionMode.FIX
25
+ elif value == 1:
26
+ return PositionMode.SINGLE
27
+ elif value == 2:
28
+ return PositionMode.FLOAT
29
+ elif value == 3:
30
+ return PositionMode.NONE
31
+ else:
32
+ return PositionMode.UNKNOWN
33
+
34
+ def __str__(self) -> str:
35
+ if self == PositionMode.FIX:
36
+ return "Fix"
37
+ elif self == PositionMode.SINGLE:
38
+ return "Single"
39
+ elif self == PositionMode.FLOAT:
40
+ return "Float"
41
+ elif self == PositionMode.NONE:
42
+ return "None"
43
+ else:
44
+ return "-"
45
+
46
+
47
+ class RTKStatus(Enum):
48
+ NONE = 0
49
+ SINGLE = 1
50
+ FIX = 4
51
+ FLOAT = 5
52
+ UNKNOWN = 6
53
+
54
+ @staticmethod
55
+ def from_value(value: int) -> "RTKStatus":
56
+ if value == 0:
57
+ return RTKStatus.NONE
58
+ elif value == 1 or value == 2:
59
+ return RTKStatus.SINGLE
60
+ elif value == 4:
61
+ return RTKStatus.FIX
62
+ elif value == 5:
63
+ return RTKStatus.FLOAT
64
+ else:
65
+ return RTKStatus.UNKNOWN
66
+
67
+ def __str__(self) -> str:
68
+ if self == RTKStatus.NONE:
69
+ return "None"
70
+ elif self == RTKStatus.SINGLE:
71
+ return "Single"
72
+ elif self == RTKStatus.FIX:
73
+ return "Fix"
74
+ elif self == RTKStatus.FLOAT:
75
+ return "Float"
76
+ else:
77
+ return "Unknown"