pymammotion 0.4.0a2__py3-none-any.whl → 0.5.51__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 (133) hide show
  1. pymammotion/__init__.py +5 -4
  2. pymammotion/aliyun/client.py +235 -0
  3. pymammotion/aliyun/cloud_gateway.py +312 -64
  4. pymammotion/aliyun/model/aep_response.py +1 -2
  5. pymammotion/aliyun/model/dev_by_account_response.py +170 -23
  6. pymammotion/aliyun/model/login_by_oauth_response.py +2 -3
  7. pymammotion/aliyun/model/regions_response.py +3 -3
  8. pymammotion/aliyun/model/session_by_authcode_response.py +2 -2
  9. pymammotion/aliyun/model/thing_response.py +12 -0
  10. pymammotion/aliyun/regions.py +62 -0
  11. pymammotion/aliyun/tea/core.py +297 -0
  12. pymammotion/bluetooth/ble.py +7 -9
  13. pymammotion/bluetooth/ble_message.py +10 -14
  14. pymammotion/const.py +3 -0
  15. pymammotion/data/model/__init__.py +1 -2
  16. pymammotion/data/model/device.py +95 -27
  17. pymammotion/data/model/device_config.py +4 -4
  18. pymammotion/data/model/device_info.py +35 -0
  19. pymammotion/data/model/device_limits.py +10 -10
  20. pymammotion/data/model/enums.py +12 -2
  21. pymammotion/data/model/errors.py +12 -0
  22. pymammotion/data/model/events.py +14 -0
  23. pymammotion/data/model/generate_geojson.py +521 -0
  24. pymammotion/data/model/generate_route_information.py +2 -2
  25. pymammotion/data/model/hash_list.py +370 -57
  26. pymammotion/data/model/location.py +4 -4
  27. pymammotion/data/model/mowing_modes.py +17 -1
  28. pymammotion/data/model/raw_data.py +2 -10
  29. pymammotion/data/model/region_data.py +10 -11
  30. pymammotion/data/model/report_info.py +31 -5
  31. pymammotion/data/model/work.py +27 -0
  32. pymammotion/data/mower_state_manager.py +316 -0
  33. pymammotion/data/mqtt/event.py +73 -28
  34. pymammotion/data/mqtt/mammotion_properties.py +257 -0
  35. pymammotion/data/mqtt/properties.py +93 -78
  36. pymammotion/data/mqtt/status.py +18 -17
  37. pymammotion/event/event.py +27 -6
  38. pymammotion/homeassistant/__init__.py +3 -0
  39. pymammotion/homeassistant/mower_api.py +484 -0
  40. pymammotion/homeassistant/rtk_api.py +54 -0
  41. pymammotion/http/encryption.py +5 -6
  42. pymammotion/http/http.py +574 -28
  43. pymammotion/http/model/__init__.py +0 -0
  44. pymammotion/{aliyun/model/stream_subscription_response.py → http/model/camera_stream.py} +14 -2
  45. pymammotion/http/model/http.py +129 -4
  46. pymammotion/http/model/response_factory.py +61 -0
  47. pymammotion/http/model/rtk.py +16 -0
  48. pymammotion/mammotion/commands/abstract_message.py +7 -5
  49. pymammotion/mammotion/commands/mammotion_command.py +30 -1
  50. pymammotion/mammotion/commands/messages/basestation.py +43 -0
  51. pymammotion/mammotion/commands/messages/driver.py +61 -29
  52. pymammotion/mammotion/commands/messages/media.py +68 -15
  53. pymammotion/mammotion/commands/messages/navigation.py +61 -25
  54. pymammotion/mammotion/commands/messages/network.py +17 -23
  55. pymammotion/mammotion/commands/messages/ota.py +18 -18
  56. pymammotion/mammotion/commands/messages/system.py +32 -49
  57. pymammotion/mammotion/commands/messages/video.py +15 -16
  58. pymammotion/mammotion/devices/__init__.py +27 -3
  59. pymammotion/mammotion/devices/base.py +40 -131
  60. pymammotion/mammotion/devices/mammotion.py +436 -201
  61. pymammotion/mammotion/devices/mammotion_bluetooth.py +57 -47
  62. pymammotion/mammotion/devices/mammotion_cloud.py +134 -105
  63. pymammotion/mammotion/devices/mammotion_mower_ble.py +49 -0
  64. pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
  65. pymammotion/mammotion/devices/managers/managers.py +81 -0
  66. pymammotion/mammotion/devices/mower_device.py +124 -0
  67. pymammotion/mammotion/devices/mower_manager.py +107 -0
  68. pymammotion/mammotion/devices/rtk_ble.py +89 -0
  69. pymammotion/mammotion/devices/rtk_cloud.py +113 -0
  70. pymammotion/mammotion/devices/rtk_device.py +50 -0
  71. pymammotion/mammotion/devices/rtk_manager.py +122 -0
  72. pymammotion/mqtt/__init__.py +2 -1
  73. pymammotion/mqtt/aliyun_mqtt.py +232 -0
  74. pymammotion/mqtt/linkkit/__init__.py +5 -0
  75. pymammotion/mqtt/linkkit/h2client.py +585 -0
  76. pymammotion/mqtt/linkkit/linkkit.py +3023 -0
  77. pymammotion/mqtt/mammotion_mqtt.py +176 -169
  78. pymammotion/mqtt/mqtt_models.py +66 -0
  79. pymammotion/proto/__init__.py +4839 -4
  80. pymammotion/proto/basestation.proto +8 -0
  81. pymammotion/proto/basestation_pb2.py +11 -9
  82. pymammotion/proto/basestation_pb2.pyi +16 -2
  83. pymammotion/proto/dev_net.proto +79 -55
  84. pymammotion/proto/dev_net_pb2.py +60 -56
  85. pymammotion/proto/dev_net_pb2.pyi +49 -6
  86. pymammotion/proto/luba_msg.proto +2 -1
  87. pymammotion/proto/luba_msg_pb2.py +6 -6
  88. pymammotion/proto/luba_msg_pb2.pyi +1 -0
  89. pymammotion/proto/luba_mul.proto +62 -1
  90. pymammotion/proto/luba_mul_pb2.py +38 -22
  91. pymammotion/proto/luba_mul_pb2.pyi +94 -7
  92. pymammotion/proto/mctrl_driver.proto +44 -4
  93. pymammotion/proto/mctrl_driver_pb2.py +26 -14
  94. pymammotion/proto/mctrl_driver_pb2.pyi +66 -11
  95. pymammotion/proto/mctrl_nav.proto +93 -52
  96. pymammotion/proto/mctrl_nav_pb2.py +75 -67
  97. pymammotion/proto/mctrl_nav_pb2.pyi +142 -56
  98. pymammotion/proto/mctrl_ota.proto +40 -2
  99. pymammotion/proto/mctrl_ota_pb2.py +23 -13
  100. pymammotion/proto/mctrl_ota_pb2.pyi +67 -4
  101. pymammotion/proto/mctrl_pept.proto +8 -3
  102. pymammotion/proto/mctrl_pept_pb2.py +8 -6
  103. pymammotion/proto/mctrl_pept_pb2.pyi +14 -6
  104. pymammotion/proto/mctrl_sys.proto +325 -86
  105. pymammotion/proto/mctrl_sys_pb2.py +162 -98
  106. pymammotion/proto/mctrl_sys_pb2.pyi +451 -25
  107. pymammotion/proto/message_pool.py +3 -0
  108. pymammotion/proto/py.typed +0 -0
  109. pymammotion/utility/constant/device_constant.py +29 -5
  110. pymammotion/utility/datatype_converter.py +13 -12
  111. pymammotion/utility/device_config.py +522 -130
  112. pymammotion/utility/device_type.py +218 -21
  113. pymammotion/utility/map.py +238 -51
  114. pymammotion/utility/mur_mur_hash.py +159 -0
  115. {pymammotion-0.4.0a2.dist-info → pymammotion-0.5.51.dist-info}/METADATA +26 -31
  116. pymammotion-0.5.51.dist-info/RECORD +152 -0
  117. {pymammotion-0.4.0a2.dist-info → pymammotion-0.5.51.dist-info}/WHEEL +1 -1
  118. pymammotion/aliyun/cloud_service.py +0 -65
  119. pymammotion/data/model/plan.py +0 -58
  120. pymammotion/data/state_manager.py +0 -129
  121. pymammotion/proto/basestation.py +0 -59
  122. pymammotion/proto/common.py +0 -12
  123. pymammotion/proto/dev_net.py +0 -381
  124. pymammotion/proto/luba_msg.py +0 -81
  125. pymammotion/proto/luba_mul.py +0 -76
  126. pymammotion/proto/mctrl_driver.py +0 -100
  127. pymammotion/proto/mctrl_nav.py +0 -664
  128. pymammotion/proto/mctrl_ota.py +0 -48
  129. pymammotion/proto/mctrl_pept.py +0 -41
  130. pymammotion/proto/mctrl_sys.py +0 -574
  131. pymammotion-0.4.0a2.dist-info/RECORD +0 -131
  132. /pymammotion/http/{_init_.py → __init__.py} +0 -0
  133. {pymammotion-0.4.0a2.dist-info → pymammotion-0.5.51.dist-info/licenses}/LICENSE +0 -0
@@ -1,14 +1,13 @@
1
1
  from bleak import BleakClient, BleakScanner, BLEDevice
2
2
  from bleak.backends.characteristic import BleakGATTCharacteristic
3
3
 
4
- from pymammotion.bluetooth.const import (
5
- SERVICE_CHANGED_CHARACTERISTIC,
6
- UUID_NOTIFICATION_CHARACTERISTIC,
7
- )
4
+ from pymammotion.bluetooth.const import SERVICE_CHANGED_CHARACTERISTIC, UUID_NOTIFICATION_CHARACTERISTIC
8
5
  from pymammotion.event.event import BleNotificationEvent
9
6
 
10
7
 
11
8
  class MammotionBLE:
9
+ """Class for basic ble connections to mowers."""
10
+
12
11
  client: BleakClient
13
12
 
14
13
  def __init__(self, bleEvt: BleNotificationEvent) -> None:
@@ -37,12 +36,10 @@ class MammotionBLE:
37
36
  return await self.connect()
38
37
 
39
38
  async def connect(self) -> bool:
40
- if self.client is not None:
41
- return await self.client.connect()
39
+ return await self.client.connect() if self.client is not None else False
42
40
 
43
41
  async def disconnect(self) -> bool:
44
- if self.client is not None:
45
- return await self.client.disconnect()
42
+ return await self.client.disconnect() if self.client is not None else False
46
43
 
47
44
  async def notification_handler(self, _characteristic: BleakGATTCharacteristic, data: bytearray) -> None:
48
45
  """Simple notification handler which prints the data received."""
@@ -60,5 +57,6 @@ class MammotionBLE:
60
57
  await self.client.start_notify(UUID_NOTIFICATION_CHARACTERISTIC, self.notification_handler)
61
58
  await self.client.start_notify(SERVICE_CHANGED_CHARACTERISTIC, self.service_changed_handler)
62
59
 
63
- def getClient(self):
60
+ def get_client(self):
61
+ """Returns the ble client."""
64
62
  return self.client
@@ -1,12 +1,11 @@
1
+ from asyncio import sleep
2
+ from io import BytesIO
1
3
  import itertools
2
4
  import json
3
5
  import logging
4
6
  import queue
5
7
  import sys
6
8
  import time
7
- from asyncio import sleep
8
- from io import BytesIO
9
- from typing import Union
10
9
 
11
10
  from bleak import BleakClient
12
11
  from jsonic.serializable import serialize
@@ -17,10 +16,7 @@ from pymammotion.bluetooth.data.framectrldata import FrameCtrlData
17
16
  from pymammotion.bluetooth.data.notifydata import BlufiNotifyData
18
17
  from pymammotion.bluetooth.model.atomic_integer import AtomicInteger
19
18
  from pymammotion.data.model.execute_boarder import ExecuteBorder
20
- from pymammotion.proto import (
21
- dev_net_pb2,
22
- )
23
- from pymammotion.proto.luba_msg import LubaMsg, MsgAttr, MsgCmdType, MsgDevice
19
+ from pymammotion.proto import DevNet, DrvDevInfoReq, LubaMsg, MsgAttr, MsgCmdType, MsgDevice
24
20
  from pymammotion.utility.constant.device_constant import bleOrderCmd
25
21
 
26
22
  _LOGGER = logging.getLogger(__name__)
@@ -320,7 +316,7 @@ class BleMessage:
320
316
  hash_map = {"ctrl": 1}
321
317
  await self.post_custom_data(self.get_json_string(bleOrderCmd.bleAlive, hash_map))
322
318
 
323
- def get_json_string(self, cmd: int, hash_map: dict[str, object]) -> str:
319
+ def get_json_string(self, cmd: int, hash_map: dict[str, int]) -> str:
324
320
  jSONObject = {}
325
321
  try:
326
322
  jSONObject["cmd"] = cmd
@@ -334,7 +330,7 @@ class BleMessage:
334
330
  print(e)
335
331
  return ""
336
332
 
337
- def clearNotification(self) -> None:
333
+ def clear_notification(self) -> None:
338
334
  self.notification = None
339
335
  self.notification = BlufiNotifyData()
340
336
 
@@ -344,14 +340,14 @@ class BleMessage:
344
340
  async def send_device_info(self) -> None:
345
341
  """Currently not called"""
346
342
  luba_msg = LubaMsg(
347
- msgtype=MsgCmdType.MSG_CMD_TYPE_ESP,
343
+ msgtype=MsgCmdType.ESP,
348
344
  sender=MsgDevice.DEV_MOBILEAPP,
349
345
  rcver=MsgDevice.DEV_COMM_ESP,
350
- msgattr=MsgAttr.MSG_ATTR_REQ,
346
+ msgattr=MsgAttr.REQ,
351
347
  seqs=1,
352
348
  version=1,
353
349
  subtype=1,
354
- net=dev_net_pb2.DevNet(todev_ble_sync=1, todev_devinfo_req=dev_net_pb2.DrvDevInfoReq()),
350
+ net=DevNet(todev_ble_sync=1, todev_devinfo_req=DrvDevInfoReq()),
355
351
  )
356
352
  byte_arr = luba_msg.SerializeToString()
357
353
  await self.post_custom_data_bytes(byte_arr)
@@ -403,7 +399,7 @@ class BleMessage:
403
399
  sequence = int(response[2]) # toInt
404
400
  current_sequence = self.mReadSequence.get() & 255
405
401
  if sequence == current_sequence:
406
- _LOGGER.debug(f"Received bluetooth data 1: {response.hex()}, object: {self}")
402
+ # _LOGGER.debug(f"Received bluetooth data 1: {response.hex()}, object: {self}")
407
403
  return 2
408
404
 
409
405
  # Compare with the second counter, mod 255
@@ -656,7 +652,7 @@ class BleMessage:
656
652
  return byteOS.getvalue()
657
653
 
658
654
  @staticmethod
659
- def calc_crc(initial: int, data: Union[bytes, bytearray]) -> int:
655
+ def calc_crc(initial: int, data: bytes | bytearray) -> int:
660
656
  """Calculate CRC value for given initial value and byte array.
661
657
 
662
658
  Args:
pymammotion/const.py CHANGED
@@ -8,3 +8,6 @@ MAMMOTION_DOMAIN = "https://id.mammotion.com"
8
8
  MAMMOTION_API_DOMAIN = "https://domestic.mammotion.com"
9
9
  MAMMOTION_CLIENT_ID = "MADKALUBAS"
10
10
  MAMMOTION_CLIENT_SECRET = "GshzGRZJjuMUgd2sYHM7"
11
+
12
+ MAMMOTION_OUATH2_CLIENT_ID = "GxebgSt8si6pKqR"
13
+ MAMMOTION_OUATH2_CLIENT_SECRET = "JP0508SRJFa0A90ADpzLINDBxMa4Vj"
@@ -2,8 +2,7 @@
2
2
 
3
3
  from .generate_route_information import GenerateRouteInformation
4
4
  from .hash_list import HashList
5
- from .plan import Plan
6
5
  from .rapid_state import RapidState, RTKStatus
7
6
  from .region_data import RegionData
8
7
 
9
- __all__ = ["GenerateRouteInformation", "HashList", "Plan", "RapidState", "RTKStatus", "RegionData"]
8
+ __all__ = ["GenerateRouteInformation", "HashList", "RapidState", "RTKStatus", "RegionData"]
@@ -1,23 +1,22 @@
1
1
  """MowingDevice class to wrap around the betterproto dataclasses."""
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import Optional
5
4
 
6
- import betterproto
5
+ import betterproto2
7
6
  from mashumaro.mixins.orjson import DataClassORJSONMixin
8
7
 
9
8
  from pymammotion.data.model import HashList, RapidState
10
- from pymammotion.data.model.device_info import MowerInfo
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
11
12
  from pymammotion.data.model.location import Location
12
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
13
16
  from pymammotion.data.mqtt.properties import ThingPropertiesMessage
14
- from pymammotion.http.model.http import ErrorInfo
15
- from pymammotion.proto.mctrl_sys import (
16
- MowToAppInfoT,
17
- ReportInfoData,
18
- SystemRapidStateTunnelMsg,
19
- SystemUpdateBufMsg,
20
- )
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
21
20
  from pymammotion.utility.constant import WorkMode
22
21
  from pymammotion.utility.conversions import parse_double
23
22
  from pymammotion.utility.map import CoordinateConverter
@@ -27,30 +26,42 @@ from pymammotion.utility.map import CoordinateConverter
27
26
  class MowingDevice(DataClassORJSONMixin):
28
27
  """Wraps the betterproto dataclasses, so we can bypass the groups for keeping all data."""
29
28
 
29
+ name: str = ""
30
+ online: bool = True
31
+ enabled: bool = True
32
+ update_check: CheckDeviceVersion = field(default_factory=CheckDeviceVersion)
30
33
  mower_state: MowerInfo = field(default_factory=MowerInfo)
31
34
  mqtt_properties: ThingPropertiesMessage | None = None
35
+ status_properties: ThingStatusMessage | None = None
36
+ device_event: ThingEventMessage | None = None
32
37
  map: HashList = field(default_factory=HashList)
38
+ work: CurrentTaskSettings = field(default_factory=CurrentTaskSettings)
33
39
  location: Location = field(default_factory=Location)
34
40
  mowing_state: RapidState = field(default_factory=RapidState)
35
41
  report_data: ReportData = field(default_factory=ReportData)
36
- err_code_list: list = field(default_factory=list)
37
- err_code_list_time: Optional[list] = field(default_factory=list)
38
- error_codes: dict[str, ErrorInfo] = field(default_factory=dict)
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)
39
46
 
40
47
  def buffer(self, buffer_list: SystemUpdateBufMsg) -> None:
41
48
  """Update the device based on which buffer we are reading from."""
42
49
  match buffer_list.update_buf_data[0]:
43
50
  case 1:
44
- # 4 speed
45
- self.location.RTK.latitude = parse_double(buffer_list.update_buf_data[5], 8.0)
46
- self.location.RTK.longitude = parse_double(buffer_list.update_buf_data[6], 8.0)
47
- self.location.dock.latitude = parse_double(buffer_list.update_buf_data[7], 4.0)
48
- self.location.dock.longitude = parse_double(buffer_list.update_buf_data[8], 4.0)
49
- self.location.dock.rotation = buffer_list.update_buf_data[3] + 180
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
+
50
61
  case 2:
51
- self.err_code_list.clear()
52
- self.err_code_list_time.clear()
53
- self.err_code_list.extend(
62
+ self.errors.err_code_list.clear()
63
+ self.errors.err_code_list_time.clear()
64
+ self.errors.err_code_list.extend(
54
65
  [
55
66
  buffer_list.update_buf_data[3],
56
67
  buffer_list.update_buf_data[5],
@@ -64,7 +75,7 @@ class MowingDevice(DataClassORJSONMixin):
64
75
  buffer_list.update_buf_data[21],
65
76
  ]
66
77
  )
67
- self.err_code_list_time.extend(
78
+ self.errors.err_code_list_time.extend(
68
79
  [
69
80
  buffer_list.update_buf_data[4],
70
81
  buffer_list.update_buf_data[6],
@@ -78,28 +89,48 @@ class MowingDevice(DataClassORJSONMixin):
78
89
  buffer_list.update_buf_data[22],
79
90
  ]
80
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_list = task_area_map
105
+ self.events.work_tasks_event.ids = task_area_ids
81
106
 
82
107
  def update_report_data(self, toapp_report_data: ReportInfoData) -> None:
108
+ """Set report data for the mower."""
83
109
  coordinate_converter = CoordinateConverter(self.location.RTK.latitude, self.location.RTK.longitude)
84
110
  for index, location in enumerate(toapp_report_data.locations):
85
111
  if index == 0 and location.real_pos_y != 0:
86
112
  self.location.position_type = location.pos_type
87
- self.location.orientation = location.real_toward / 10000
113
+ self.location.orientation = int(location.real_toward / 10000)
88
114
  self.location.device = coordinate_converter.enu_to_lla(
89
115
  parse_double(location.real_pos_y, 4.0), parse_double(location.real_pos_x, 4.0)
90
116
  )
117
+ self.map.invalidate_maps(location.bol_hash)
91
118
  if location.zone_hash:
92
119
  self.location.work_zone = (
93
120
  location.zone_hash if self.report_data.dev.sys_status == WorkMode.MODE_WORKING else 0
94
121
  )
95
122
 
96
- self.report_data.update(toapp_report_data.to_dict(casing=betterproto.Casing.SNAKE))
123
+ if toapp_report_data.fw_info:
124
+ self.update_device_firmwares(toapp_report_data.fw_info)
125
+
126
+ self.report_data.update(toapp_report_data.to_dict(casing=betterproto2.Casing.SNAKE))
97
127
 
98
128
  def run_state_update(self, rapid_state: SystemRapidStateTunnelMsg) -> None:
129
+ """Set lat long, work zone of RTK and robot."""
99
130
  coordinate_converter = CoordinateConverter(self.location.RTK.latitude, self.location.RTK.longitude)
100
131
  self.mowing_state = RapidState().from_raw(rapid_state.rapid_state_data)
101
132
  self.location.position_type = self.mowing_state.pos_type
102
- self.location.orientation = self.mowing_state.toward / 10000
133
+ self.location.orientation = int(self.mowing_state.toward / 10000)
103
134
  self.location.device = coordinate_converter.enu_to_lla(
104
135
  parse_double(self.mowing_state.pos_y, 4.0), parse_double(self.mowing_state.pos_x, 4.0)
105
136
  )
@@ -109,7 +140,44 @@ class MowingDevice(DataClassORJSONMixin):
109
140
  )
110
141
 
111
142
  def mow_info(self, toapp_mow_info: MowToAppInfoT) -> None:
112
- pass
143
+ """Set mow info."""
113
144
 
114
145
  def report_missing_data(self) -> None:
115
146
  """Report missing data so we can refetch it."""
147
+
148
+ def update_device_firmwares(self, fw_info: DeviceFwInfo) -> None:
149
+ """Set firmware versions on all parts of the robot or RTK."""
150
+ for mod in fw_info.mod:
151
+ match mod.type:
152
+ case 1:
153
+ self.device_firmwares.main_controller = mod.version
154
+ case 3:
155
+ self.device_firmwares.left_motor_driver = mod.version
156
+ case 4:
157
+ self.device_firmwares.right_motor_driver = mod.version
158
+ case 5:
159
+ self.device_firmwares.rtk_rover_station = mod.version
160
+ case 101:
161
+ # RTK main board
162
+ self.device_firmwares.main_controller = mod.version
163
+ case 102:
164
+ self.device_firmwares.rtk_version = mod.version
165
+ case 103:
166
+ self.device_firmwares.lora_version = mod.version
167
+
168
+
169
+ @dataclass
170
+ class RTKDevice(DataClassORJSONMixin):
171
+ name: str
172
+ iot_id: str
173
+ product_key: str
174
+ online: bool = True
175
+ lat: float = 0.0
176
+ lon: float = 0.0
177
+ lora: str = ""
178
+ wifi_rssi: int = 0
179
+ device_version: str = ""
180
+ lora_version: str = ""
181
+ wifi_sta_mac: str = ""
182
+ bt_mac: str = ""
183
+ update_check: CheckDeviceVersion = field(default_factory=CheckDeviceVersion)
@@ -30,23 +30,23 @@ class OperationSettings(DataClassORJSONMixin):
30
30
  obstacle_laps: int = 1
31
31
  mowing_laps: int = 1 # border laps
32
32
  start_progress: int = 0
33
- areas: list[int] = field(default_factory=list)
33
+ areas: set[int] = field(default_factory=set)
34
34
 
35
35
 
36
36
  def create_path_order(operation_mode: OperationSettings, device_name: str) -> str:
37
37
  # TODO add scheduling logic from getReserved() WorkSettingViewModel.java
38
- i2 = 0
39
38
  bArr = bytearray(8)
40
39
  bArr[0] = operation_mode.border_mode
41
40
  bArr[1] = operation_mode.obstacle_laps
42
41
  bArr[3] = int(operation_mode.start_progress)
43
42
  bArr[2] = 0
43
+ bArr[5] = 0
44
44
  if not DeviceType.is_luba1(device_name):
45
45
  bArr[4] = 0
46
- if DeviceType.is_yuka(device_name):
46
+ if DeviceType.is_yuka(device_name) and not DeviceType.is_yuka_mini(device_name):
47
47
  bArr[5] = calculate_yuka_mode(operation_mode)
48
48
  else:
49
- bArr[5] = 8 if DeviceType.is_luba_2(device_name) else 0
49
+ bArr[5] = 8 if DeviceType.is_luba_pro(device_name) else 0
50
50
 
51
51
  bArr[6] = int(operation_mode.collect_grass_frequency) if operation_mode.is_dump else 10
52
52
  if DeviceType.is_luba1(device_name):
@@ -14,12 +14,47 @@ class SideLight(DataClassORJSONMixin):
14
14
  action: int = 0
15
15
 
16
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
+
17
31
  @dataclass
18
32
  class MowerInfo(DataClassORJSONMixin):
19
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
20
39
  side_led: SideLight = field(default_factory=SideLight)
21
40
  collector_installation_status: bool = False
22
41
  model: str = ""
23
42
  swversion: str = ""
24
43
  product_key: str = ""
25
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 = ""
@@ -1,4 +1,4 @@
1
- from dataclasses import dataclass
1
+ from dataclasses import dataclass, field
2
2
 
3
3
 
4
4
  @dataclass
@@ -9,18 +9,18 @@ class RangeLimit:
9
9
 
10
10
  @dataclass
11
11
  class DeviceLimits:
12
- cutter_height: RangeLimit = RangeLimit(min=30, max=100)
13
- working_speed: RangeLimit = RangeLimit(min=0.2, max=1.2)
14
- working_path: RangeLimit = RangeLimit(min=15, max=35)
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
15
  work_area_num_max: int = 60
16
16
  display_image_type: int = 0
17
17
 
18
18
  def to_dict(self) -> dict:
19
19
  """Convert the device limits to a dictionary format."""
20
20
  return {
21
- "cutter_height": {"min": self.cutter_height.min, "max": self.cutter_height.max},
21
+ "blade_height": {"min": self.blade_height.min, "max": self.blade_height.max},
22
22
  "working_speed": {"min": self.working_speed.min, "max": self.working_speed.max},
23
- "working_path": {"min": self.working_path.min, "max": self.working_path.max},
23
+ "path_spacing": {"min": self.path_spacing.min, "max": self.path_spacing.max},
24
24
  "work_area_num_max": self.work_area_num_max,
25
25
  "display_image_type": self.display_image_type,
26
26
  }
@@ -29,9 +29,9 @@ class DeviceLimits:
29
29
  def from_dict(cls, data: dict) -> "DeviceLimits":
30
30
  """Create a DeviceLimits instance from a dictionary."""
31
31
  return cls(
32
- cutter_height=RangeLimit(min=data["cutter_height"]["min"], max=data["cutter_height"]["max"]),
32
+ blade_height=RangeLimit(min=data["blade_height"]["min"], max=data["blade_height"]["max"]),
33
33
  working_speed=RangeLimit(min=data["working_speed"]["min"], max=data["working_speed"]["max"]),
34
- working_path=RangeLimit(min=data["working_path"]["min"], max=data["working_path"]["max"]),
34
+ path_spacing=RangeLimit(min=data["path_spacing"]["min"], max=data["path_spacing"]["max"]),
35
35
  work_area_num_max=data["work_area_num_max"],
36
36
  display_image_type=data["display_image_type"],
37
37
  )
@@ -40,9 +40,9 @@ class DeviceLimits:
40
40
  """Validate that all ranges are logical (min <= max)."""
41
41
  return all(
42
42
  [
43
- self.cutter_height.min <= self.cutter_height.max,
43
+ self.blade_height.min <= self.blade_height.max,
44
44
  self.working_speed.min <= self.working_speed.max,
45
- self.working_path.min <= self.working_path.max,
45
+ self.path_spacing.min <= self.path_spacing.max,
46
46
  self.work_area_num_max > 0,
47
47
  self.display_image_type in (0, 1),
48
48
  ]
@@ -1,6 +1,16 @@
1
1
  from enum import Enum
2
2
 
3
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
+
4
14
  class PositionMode(Enum):
5
15
  FIX = 0
6
16
  SINGLE = 1
@@ -9,7 +19,7 @@ class PositionMode(Enum):
9
19
  UNKNOWN = 4
10
20
 
11
21
  @staticmethod
12
- def from_value(value: int):
22
+ def from_value(value: int) -> "PositionMode":
13
23
  if value == 0:
14
24
  return PositionMode.FIX
15
25
  elif value == 1:
@@ -42,7 +52,7 @@ class RTKStatus(Enum):
42
52
  UNKNOWN = 6
43
53
 
44
54
  @staticmethod
45
- def from_value(value: int):
55
+ def from_value(value: int) -> "RTKStatus":
46
56
  if value == 0:
47
57
  return RTKStatus.NONE
48
58
  elif value == 1 or value == 2:
@@ -0,0 +1,12 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ from mashumaro.mixins.orjson import DataClassORJSONMixin
4
+
5
+ from pymammotion.http.model.http import ErrorInfo
6
+
7
+
8
+ @dataclass
9
+ class DeviceErrors(DataClassORJSONMixin):
10
+ err_code_list: list[int] = field(default_factory=list)
11
+ err_code_list_time: list[int] = field(default_factory=list)
12
+ error_codes: dict[str, ErrorInfo] = field(default_factory=dict)
@@ -0,0 +1,14 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ from mashumaro.mixins.orjson import DataClassORJSONMixin
4
+
5
+
6
+ @dataclass
7
+ class WorkTaskEvent(DataClassORJSONMixin):
8
+ hash_area_map: dict[int, int] = field(default_factory=dict)
9
+ ids: list[int] = field(default_factory=list)
10
+
11
+
12
+ @dataclass
13
+ class Events(DataClassORJSONMixin):
14
+ work_tasks_event: WorkTaskEvent = field(default_factory=WorkTaskEvent)