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.
- pymammotion/__init__.py +53 -0
- pymammotion/agora/__init__.py +0 -0
- pymammotion/agora/agora_api.py +755 -0
- pymammotion/agora/agora_rtc_capabilities.py +748 -0
- pymammotion/agora/agora_websockets.py +1175 -0
- pymammotion/aliyun/__init__.py +1 -0
- pymammotion/aliyun/client.py +235 -0
- pymammotion/aliyun/cloud_gateway.py +982 -0
- pymammotion/aliyun/model/aep_response.py +21 -0
- pymammotion/aliyun/model/connect_response.py +51 -0
- pymammotion/aliyun/model/dev_by_account_response.py +195 -0
- pymammotion/aliyun/model/login_by_oauth_response.py +64 -0
- pymammotion/aliyun/model/regions_response.py +29 -0
- pymammotion/aliyun/model/session_by_authcode_response.py +19 -0
- pymammotion/aliyun/model/thing_response.py +12 -0
- pymammotion/aliyun/regions.py +62 -0
- pymammotion/aliyun/tea/core.py +297 -0
- pymammotion/aliyun/tmp_constant.py +171 -0
- pymammotion/bluetooth/__init__.py +1 -0
- pymammotion/bluetooth/ble.py +62 -0
- pymammotion/bluetooth/ble_message.py +676 -0
- pymammotion/bluetooth/const.py +27 -0
- pymammotion/bluetooth/data/__init__.py +0 -0
- pymammotion/bluetooth/data/convert.py +25 -0
- pymammotion/bluetooth/data/framectrldata.py +40 -0
- pymammotion/bluetooth/data/notifydata.py +62 -0
- pymammotion/bluetooth/model/__init__.py +0 -0
- pymammotion/bluetooth/model/atomic_integer.py +54 -0
- pymammotion/const.py +13 -0
- pymammotion/data/__init__.py +0 -0
- pymammotion/data/model/__init__.py +8 -0
- pymammotion/data/model/account.py +8 -0
- pymammotion/data/model/device.py +192 -0
- pymammotion/data/model/device_config.py +72 -0
- pymammotion/data/model/device_info.py +60 -0
- pymammotion/data/model/device_limits.py +49 -0
- pymammotion/data/model/enums.py +77 -0
- pymammotion/data/model/errors.py +12 -0
- pymammotion/data/model/events.py +14 -0
- pymammotion/data/model/generate_geojson.py +565 -0
- pymammotion/data/model/generate_route_information.py +26 -0
- pymammotion/data/model/hash_list.py +475 -0
- pymammotion/data/model/location.py +36 -0
- pymammotion/data/model/mowing_modes.py +77 -0
- pymammotion/data/model/rapid_state.py +45 -0
- pymammotion/data/model/raw_data.py +215 -0
- pymammotion/data/model/region_data.py +102 -0
- pymammotion/data/model/report_info.py +182 -0
- pymammotion/data/model/work.py +27 -0
- pymammotion/data/mower_state_manager.py +369 -0
- pymammotion/data/mqtt/__init__.py +1 -0
- pymammotion/data/mqtt/event.py +227 -0
- pymammotion/data/mqtt/mammotion_properties.py +276 -0
- pymammotion/data/mqtt/properties.py +203 -0
- pymammotion/data/mqtt/status.py +57 -0
- pymammotion/event/__init__.py +6 -0
- pymammotion/event/event.py +96 -0
- pymammotion/homeassistant/__init__.py +3 -0
- pymammotion/homeassistant/mower_api.py +514 -0
- pymammotion/homeassistant/rtk_api.py +54 -0
- pymammotion/http/__init__.py +0 -0
- pymammotion/http/encryption.py +220 -0
- pymammotion/http/http.py +673 -0
- pymammotion/http/model/__init__.py +0 -0
- pymammotion/http/model/camera_stream.py +31 -0
- pymammotion/http/model/http.py +249 -0
- pymammotion/http/model/response_factory.py +61 -0
- pymammotion/http/model/rtk.py +16 -0
- pymammotion/mammotion/__init__.py +0 -0
- pymammotion/mammotion/commands/__init__.py +0 -0
- pymammotion/mammotion/commands/abstract_message.py +24 -0
- pymammotion/mammotion/commands/mammotion_command.py +81 -0
- pymammotion/mammotion/commands/messages/__init__.py +0 -0
- pymammotion/mammotion/commands/messages/basestation.py +43 -0
- pymammotion/mammotion/commands/messages/driver.py +122 -0
- pymammotion/mammotion/commands/messages/media.py +87 -0
- pymammotion/mammotion/commands/messages/navigation.py +564 -0
- pymammotion/mammotion/commands/messages/network.py +205 -0
- pymammotion/mammotion/commands/messages/ota.py +38 -0
- pymammotion/mammotion/commands/messages/system.py +330 -0
- pymammotion/mammotion/commands/messages/video.py +33 -0
- pymammotion/mammotion/control/__init__.py +0 -0
- pymammotion/mammotion/control/joystick.py +145 -0
- pymammotion/mammotion/devices/__init__.py +29 -0
- pymammotion/mammotion/devices/base.py +163 -0
- pymammotion/mammotion/devices/mammotion.py +571 -0
- pymammotion/mammotion/devices/mammotion_bluetooth.py +496 -0
- pymammotion/mammotion/devices/mammotion_cloud.py +355 -0
- pymammotion/mammotion/devices/mammotion_mower_ble.py +48 -0
- pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
- pymammotion/mammotion/devices/managers/managers.py +81 -0
- pymammotion/mammotion/devices/mower_device.py +120 -0
- pymammotion/mammotion/devices/mower_manager.py +107 -0
- pymammotion/mammotion/devices/rtk_ble.py +89 -0
- pymammotion/mammotion/devices/rtk_cloud.py +115 -0
- pymammotion/mammotion/devices/rtk_device.py +50 -0
- pymammotion/mammotion/devices/rtk_manager.py +125 -0
- pymammotion/mqtt/__init__.py +6 -0
- pymammotion/mqtt/aliyun_mqtt.py +237 -0
- pymammotion/mqtt/linkkit/__init__.py +5 -0
- pymammotion/mqtt/linkkit/h2client.py +585 -0
- pymammotion/mqtt/linkkit/linkkit.py +3025 -0
- pymammotion/mqtt/mammotion_future.py +26 -0
- pymammotion/mqtt/mammotion_mqtt.py +214 -0
- pymammotion/mqtt/mqtt_models.py +66 -0
- pymammotion/proto/__init__.py +4841 -0
- pymammotion/proto/basestation.proto +51 -0
- pymammotion/proto/basestation_pb2.py +35 -0
- pymammotion/proto/basestation_pb2.pyi +89 -0
- pymammotion/proto/common.proto +7 -0
- pymammotion/proto/common_pb2.py +25 -0
- pymammotion/proto/common_pb2.pyi +13 -0
- pymammotion/proto/dev_net.proto +321 -0
- pymammotion/proto/dev_net_pb2.py +111 -0
- pymammotion/proto/dev_net_pb2.pyi +515 -0
- pymammotion/proto/luba_msg.proto +76 -0
- pymammotion/proto/luba_msg_pb2.py +41 -0
- pymammotion/proto/luba_msg_pb2.pyi +97 -0
- pymammotion/proto/luba_mul.proto +129 -0
- pymammotion/proto/luba_mul_pb2.py +61 -0
- pymammotion/proto/luba_mul_pb2.pyi +178 -0
- pymammotion/proto/mctrl_driver.proto +107 -0
- pymammotion/proto/mctrl_driver_pb2.py +57 -0
- pymammotion/proto/mctrl_driver_pb2.pyi +167 -0
- pymammotion/proto/mctrl_nav.proto +591 -0
- pymammotion/proto/mctrl_nav_pb2.py +136 -0
- pymammotion/proto/mctrl_nav_pb2.pyi +1067 -0
- pymammotion/proto/mctrl_ota.proto +80 -0
- pymammotion/proto/mctrl_ota_pb2.py +45 -0
- pymammotion/proto/mctrl_ota_pb2.pyi +128 -0
- pymammotion/proto/mctrl_pept.proto +34 -0
- pymammotion/proto/mctrl_pept_pb2.py +33 -0
- pymammotion/proto/mctrl_pept_pb2.pyi +58 -0
- pymammotion/proto/mctrl_sys.proto +741 -0
- pymammotion/proto/mctrl_sys_pb2.py +206 -0
- pymammotion/proto/mctrl_sys_pb2.pyi +1213 -0
- pymammotion/proto/message_pool.py +3 -0
- pymammotion/proto/py.typed +0 -0
- pymammotion/py.typed +0 -0
- pymammotion/utility/constant/__init__.py +3 -0
- pymammotion/utility/constant/device_constant.py +315 -0
- pymammotion/utility/conversions.py +5 -0
- pymammotion/utility/datatype_converter.py +124 -0
- pymammotion/utility/device_config.py +755 -0
- pymammotion/utility/device_type.py +489 -0
- pymammotion/utility/map.py +259 -0
- pymammotion/utility/movement.py +18 -0
- pymammotion/utility/mur_mur_hash.py +159 -0
- pymammotion/utility/periodic.py +106 -0
- pymammotion/utility/rocker_util.py +194 -0
- pymammotion-0.5.69.dist-info/METADATA +93 -0
- pymammotion-0.5.69.dist-info/RECORD +154 -0
- pymammotion-0.5.69.dist-info/WHEEL +4 -0
- 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,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"
|