pymammotion 0.5.34__py3-none-any.whl → 0.5.53__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.
- pymammotion/__init__.py +3 -3
- pymammotion/aliyun/cloud_gateway.py +106 -18
- pymammotion/aliyun/model/dev_by_account_response.py +169 -21
- pymammotion/const.py +3 -0
- pymammotion/data/model/device.py +22 -9
- pymammotion/data/model/device_config.py +1 -1
- pymammotion/data/model/device_info.py +1 -0
- pymammotion/data/model/enums.py +5 -3
- pymammotion/data/model/events.py +14 -0
- pymammotion/data/model/generate_geojson.py +551 -0
- pymammotion/data/model/generate_route_information.py +2 -2
- pymammotion/data/model/hash_list.py +129 -33
- pymammotion/data/model/location.py +4 -4
- pymammotion/data/model/region_data.py +4 -4
- pymammotion/data/model/report_info.py +7 -0
- pymammotion/data/{state_manager.py → mower_state_manager.py} +75 -11
- pymammotion/data/mqtt/event.py +47 -22
- pymammotion/data/mqtt/mammotion_properties.py +257 -0
- pymammotion/data/mqtt/properties.py +32 -29
- pymammotion/data/mqtt/status.py +17 -16
- pymammotion/event/event.py +5 -2
- pymammotion/homeassistant/__init__.py +3 -0
- pymammotion/homeassistant/mower_api.py +484 -0
- pymammotion/homeassistant/rtk_api.py +54 -0
- pymammotion/http/http.py +394 -14
- pymammotion/http/model/http.py +82 -2
- pymammotion/http/model/response_factory.py +10 -4
- pymammotion/mammotion/commands/mammotion_command.py +6 -0
- pymammotion/mammotion/commands/messages/navigation.py +39 -6
- pymammotion/mammotion/devices/__init__.py +27 -3
- pymammotion/mammotion/devices/base.py +16 -138
- pymammotion/mammotion/devices/mammotion.py +369 -200
- pymammotion/mammotion/devices/mammotion_bluetooth.py +7 -5
- pymammotion/mammotion/devices/mammotion_cloud.py +42 -83
- pymammotion/mammotion/devices/mammotion_mower_ble.py +49 -0
- pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
- pymammotion/mammotion/devices/managers/managers.py +81 -0
- pymammotion/mammotion/devices/mower_device.py +124 -0
- pymammotion/mammotion/devices/mower_manager.py +107 -0
- pymammotion/mammotion/devices/rtk_ble.py +89 -0
- pymammotion/mammotion/devices/rtk_cloud.py +113 -0
- pymammotion/mammotion/devices/rtk_device.py +50 -0
- pymammotion/mammotion/devices/rtk_manager.py +122 -0
- pymammotion/mqtt/__init__.py +2 -1
- pymammotion/mqtt/aliyun_mqtt.py +232 -0
- pymammotion/mqtt/mammotion_mqtt.py +174 -192
- pymammotion/mqtt/mqtt_models.py +66 -0
- pymammotion/proto/__init__.py +3 -3
- pymammotion/proto/mctrl_nav.proto +1 -1
- pymammotion/proto/mctrl_nav_pb2.py +1 -1
- pymammotion/proto/mctrl_nav_pb2.pyi +4 -4
- pymammotion/proto/mctrl_sys.proto +1 -1
- pymammotion/proto/mctrl_sys_pb2.py +1 -1
- pymammotion/utility/constant/device_constant.py +1 -1
- pymammotion/utility/datatype_converter.py +13 -12
- pymammotion/utility/device_type.py +88 -3
- pymammotion/utility/map.py +238 -51
- pymammotion/utility/mur_mur_hash.py +132 -87
- {pymammotion-0.5.34.dist-info → pymammotion-0.5.53.dist-info}/METADATA +26 -31
- {pymammotion-0.5.34.dist-info → pymammotion-0.5.53.dist-info}/RECORD +67 -51
- {pymammotion-0.5.34.dist-info → pymammotion-0.5.53.dist-info}/WHEEL +1 -1
- pymammotion/http/_init_.py +0 -0
- {pymammotion-0.5.34.dist-info → pymammotion-0.5.53.dist-info/licenses}/LICENSE +0 -0
pymammotion/__init__.py
CHANGED
|
@@ -15,12 +15,12 @@ from pymammotion.bluetooth.ble import MammotionBLE
|
|
|
15
15
|
from pymammotion.http.http import MammotionHTTP
|
|
16
16
|
|
|
17
17
|
# TODO make a working device that will work outside HA too.
|
|
18
|
-
from pymammotion.mqtt import MammotionMQTT
|
|
18
|
+
from pymammotion.mqtt import AliyunMQTT, MammotionMQTT
|
|
19
19
|
|
|
20
20
|
logger = logging.getLogger(__name__)
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
__all__ = ["MammotionBLE", "MammotionHTTP", "MammotionMQTT", "logger"]
|
|
23
|
+
__all__ = ["MammotionBLE", "MammotionHTTP", "AliyunMQTT", "MammotionMQTT", "logger"]
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
# TODO provide interface to pick between mqtt/cloud/bluetooth
|
|
@@ -37,7 +37,7 @@ if __name__ == "__main__":
|
|
|
37
37
|
REGION = os.environ.get("REGION")
|
|
38
38
|
mammotion_http = MammotionHTTP()
|
|
39
39
|
cloud_client = CloudIOTGateway(mammotion_http)
|
|
40
|
-
luba =
|
|
40
|
+
luba = AliyunMQTT(
|
|
41
41
|
iot_token=IOT_TOKEN or "",
|
|
42
42
|
region_id=REGION or "",
|
|
43
43
|
product_key=PRODUCT_KEY or "",
|
|
@@ -22,7 +22,7 @@ from Tea.exceptions import UnretryableException
|
|
|
22
22
|
from pymammotion.aliyun.client import Client
|
|
23
23
|
from pymammotion.aliyun.model.aep_response import AepResponse
|
|
24
24
|
from pymammotion.aliyun.model.connect_response import ConnectResponse
|
|
25
|
-
from pymammotion.aliyun.model.dev_by_account_response import
|
|
25
|
+
from pymammotion.aliyun.model.dev_by_account_response import ListingDevAccountResponse
|
|
26
26
|
from pymammotion.aliyun.model.login_by_oauth_response import LoginByOAuthResponse
|
|
27
27
|
from pymammotion.aliyun.model.regions_response import RegionResponse
|
|
28
28
|
from pymammotion.aliyun.model.session_by_authcode_response import SessionByAuthCodeResponse
|
|
@@ -103,6 +103,9 @@ class CheckSessionException(Exception):
|
|
|
103
103
|
"""Raise exception when checking session results in a failure."""
|
|
104
104
|
|
|
105
105
|
|
|
106
|
+
EXPIRED_CREDENTIAL_EXCEPTIONS = (CheckSessionException, SetupException)
|
|
107
|
+
|
|
108
|
+
|
|
106
109
|
class CloudIOTGateway:
|
|
107
110
|
"""Class for interacting with Aliyun Cloud IoT Gateway."""
|
|
108
111
|
|
|
@@ -120,7 +123,7 @@ class CloudIOTGateway:
|
|
|
120
123
|
aep_response: AepResponse | None = None,
|
|
121
124
|
session_by_authcode_response: SessionByAuthCodeResponse | None = None,
|
|
122
125
|
region_response: RegionResponse | None = None,
|
|
123
|
-
dev_by_account:
|
|
126
|
+
dev_by_account: ListingDevAccountResponse | None = None,
|
|
124
127
|
) -> None:
|
|
125
128
|
"""Initialize the CloudIOTGateway."""
|
|
126
129
|
self.mammotion_http: MammotionHTTP = mammotion_http
|
|
@@ -146,7 +149,7 @@ class CloudIOTGateway:
|
|
|
146
149
|
)
|
|
147
150
|
|
|
148
151
|
@staticmethod
|
|
149
|
-
def generate_random_string(length: int):
|
|
152
|
+
def generate_random_string(length: int) -> str:
|
|
150
153
|
"""Generate a random string of specified length."""
|
|
151
154
|
characters = string.ascii_letters + string.digits
|
|
152
155
|
return "".join(random.choice(characters) for _ in range(length))
|
|
@@ -165,7 +168,7 @@ class CloudIOTGateway:
|
|
|
165
168
|
logger.error("Couldn't decode message %s", response_body_str)
|
|
166
169
|
return {"code": 22000}
|
|
167
170
|
|
|
168
|
-
def sign(self, data):
|
|
171
|
+
def sign(self, data: dict) -> str:
|
|
169
172
|
"""Generate signature for the given data."""
|
|
170
173
|
keys = ["appKey", "clientId", "deviceSn", "timestamp"]
|
|
171
174
|
concatenated_str = ""
|
|
@@ -180,7 +183,7 @@ class CloudIOTGateway:
|
|
|
180
183
|
hashlib.sha1,
|
|
181
184
|
).hexdigest()
|
|
182
185
|
|
|
183
|
-
async def get_region(self, country_code: str):
|
|
186
|
+
async def get_region(self, country_code: str) -> RegionResponse:
|
|
184
187
|
"""Get the region based on country code and auth code."""
|
|
185
188
|
auth_code = self.mammotion_http.login_info.authorization_code
|
|
186
189
|
|
|
@@ -244,7 +247,7 @@ class CloudIOTGateway:
|
|
|
244
247
|
|
|
245
248
|
return response.body
|
|
246
249
|
|
|
247
|
-
async def aep_handle(self):
|
|
250
|
+
async def aep_handle(self) -> AepResponse:
|
|
248
251
|
"""Handle AEP authentication."""
|
|
249
252
|
aep_domain = self.domain
|
|
250
253
|
|
|
@@ -300,9 +303,9 @@ class CloudIOTGateway:
|
|
|
300
303
|
|
|
301
304
|
logger.debug(response_body_dict)
|
|
302
305
|
|
|
303
|
-
return
|
|
306
|
+
return self._aep_response
|
|
304
307
|
|
|
305
|
-
async def connect(self):
|
|
308
|
+
async def connect(self) -> ConnectResponse:
|
|
306
309
|
"""Connect to the Aliyun Cloud IoT Gateway."""
|
|
307
310
|
region_url = "sdk.openaccount.aliyun.com"
|
|
308
311
|
time_now = time.time()
|
|
@@ -449,7 +452,7 @@ class CloudIOTGateway:
|
|
|
449
452
|
return self._login_by_oauth_response
|
|
450
453
|
raise LoginException(data)
|
|
451
454
|
|
|
452
|
-
async def session_by_auth_code(self):
|
|
455
|
+
async def session_by_auth_code(self) -> SessionByAuthCodeResponse:
|
|
453
456
|
"""Create a session by auth code."""
|
|
454
457
|
config = Config(
|
|
455
458
|
app_key=self._app_key,
|
|
@@ -608,7 +611,8 @@ class CloudIOTGateway:
|
|
|
608
611
|
session_data = session.data
|
|
609
612
|
|
|
610
613
|
if (
|
|
611
|
-
session_data
|
|
614
|
+
session_data is None
|
|
615
|
+
or session_data.identityId is None
|
|
612
616
|
or session_data.refreshTokenExpire is None
|
|
613
617
|
or session_data.iotToken is None
|
|
614
618
|
or session_data.iotTokenExpire is None
|
|
@@ -619,7 +623,7 @@ class CloudIOTGateway:
|
|
|
619
623
|
self._session_by_authcode_response = session
|
|
620
624
|
self._iot_token_issued_at = int(time.time())
|
|
621
625
|
|
|
622
|
-
async def list_binding_by_account(self) ->
|
|
626
|
+
async def list_binding_by_account(self) -> ListingDevAccountResponse:
|
|
623
627
|
"""List bindings by account."""
|
|
624
628
|
config = Config(
|
|
625
629
|
app_key=self._app_key,
|
|
@@ -658,9 +662,9 @@ class CloudIOTGateway:
|
|
|
658
662
|
response_body_dict = self.parse_json_response(response_body_str)
|
|
659
663
|
|
|
660
664
|
if int(response_body_dict.get("code")) != 200:
|
|
661
|
-
raise Exception("Error in creating session: " + response_body_dict["
|
|
665
|
+
raise Exception("Error in creating session: " + response_body_dict["message"])
|
|
662
666
|
|
|
663
|
-
self._devices_by_account_response =
|
|
667
|
+
self._devices_by_account_response = ListingDevAccountResponse.from_dict(response_body_dict)
|
|
664
668
|
return self._devices_by_account_response
|
|
665
669
|
|
|
666
670
|
async def list_binding_by_dev(self, iot_id: str):
|
|
@@ -698,10 +702,94 @@ class CloudIOTGateway:
|
|
|
698
702
|
# Load the JSON string into a dictionary
|
|
699
703
|
response_body_dict = self.parse_json_response(response_body_str)
|
|
700
704
|
|
|
705
|
+
if int(response_body_dict.get("code")) != 200:
|
|
706
|
+
raise Exception("Error in getting shared device list: " + response_body_dict["msg"])
|
|
707
|
+
|
|
708
|
+
self._devices_by_account_response = ListingDevAccountResponse.from_dict(response_body_dict)
|
|
709
|
+
return self._devices_by_account_response
|
|
710
|
+
|
|
711
|
+
async def confirm_share(self, record_list: list[str]) -> bool:
|
|
712
|
+
config = Config(
|
|
713
|
+
app_key=self._app_key,
|
|
714
|
+
app_secret=self._app_secret,
|
|
715
|
+
domain=self._region_response.data.apiGatewayEndpoint,
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
client = Client(config)
|
|
719
|
+
|
|
720
|
+
# build request
|
|
721
|
+
request = CommonParams(
|
|
722
|
+
api_ver="1.0.7",
|
|
723
|
+
language="en-US",
|
|
724
|
+
iot_token=self._session_by_authcode_response.data.iotToken,
|
|
725
|
+
)
|
|
726
|
+
body = IoTApiRequest(
|
|
727
|
+
id=str(uuid.uuid4()),
|
|
728
|
+
params={"agree": 1, "recordIdList": record_list},
|
|
729
|
+
request=request,
|
|
730
|
+
version="1.0",
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
# send request
|
|
734
|
+
response = await client.async_do_request("/uc/confirmShare", "https", "POST", None, body, RuntimeOptions())
|
|
735
|
+
logger.debug(response.status_message)
|
|
736
|
+
logger.debug(response.headers)
|
|
737
|
+
logger.debug(response.status_code)
|
|
738
|
+
logger.debug(response.body)
|
|
739
|
+
|
|
740
|
+
# Decode the response body
|
|
741
|
+
response_body_str = response.body.decode("utf-8")
|
|
742
|
+
|
|
743
|
+
# Load the JSON string into a dictionary
|
|
744
|
+
response_body_dict = self.parse_json_response(response_body_str)
|
|
745
|
+
|
|
746
|
+
if int(response_body_dict.get("code")) != 200:
|
|
747
|
+
raise Exception("Error in accepting share: " + response_body_dict["msg"])
|
|
748
|
+
|
|
749
|
+
return True
|
|
750
|
+
|
|
751
|
+
async def get_shared_notice_list(self):
|
|
752
|
+
### status 0 accepted status -1 ready to be accepted 3 expired
|
|
753
|
+
config = Config(
|
|
754
|
+
app_key=self._app_key,
|
|
755
|
+
app_secret=self._app_secret,
|
|
756
|
+
domain=self._region_response.data.apiGatewayEndpoint,
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
client = Client(config)
|
|
760
|
+
|
|
761
|
+
# build request
|
|
762
|
+
request = CommonParams(
|
|
763
|
+
api_ver="1.0.9",
|
|
764
|
+
language="en-US",
|
|
765
|
+
iot_token=self._session_by_authcode_response.data.iotToken,
|
|
766
|
+
)
|
|
767
|
+
body = IoTApiRequest(
|
|
768
|
+
id=str(uuid.uuid4()),
|
|
769
|
+
params={"pageSize": 100, "pageNo": 1},
|
|
770
|
+
request=request,
|
|
771
|
+
version="1.0",
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
# send request
|
|
775
|
+
response = await client.async_do_request(
|
|
776
|
+
"/uc/getShareNoticeList", "https", "POST", None, body, RuntimeOptions()
|
|
777
|
+
)
|
|
778
|
+
logger.debug(response.status_message)
|
|
779
|
+
logger.debug(response.headers)
|
|
780
|
+
logger.debug(response.status_code)
|
|
781
|
+
logger.debug(response.body)
|
|
782
|
+
|
|
783
|
+
# Decode the response body
|
|
784
|
+
response_body_str = response.body.decode("utf-8")
|
|
785
|
+
|
|
786
|
+
# Load the JSON string into a dictionary
|
|
787
|
+
response_body_dict = self.parse_json_response(response_body_str)
|
|
788
|
+
|
|
701
789
|
if int(response_body_dict.get("code")) != 200:
|
|
702
790
|
raise Exception("Error in creating session: " + response_body_dict["msg"])
|
|
703
791
|
|
|
704
|
-
self._devices_by_account_response =
|
|
792
|
+
self._devices_by_account_response = ListingDevAccountResponse.from_dict(response_body_dict)
|
|
705
793
|
return self._devices_by_account_response
|
|
706
794
|
|
|
707
795
|
async def send_cloud_command(self, iot_id: str, command: bytes) -> str:
|
|
@@ -867,11 +955,11 @@ class CloudIOTGateway:
|
|
|
867
955
|
self.mammotion_http = mammotion_http
|
|
868
956
|
|
|
869
957
|
@property
|
|
870
|
-
def region_response(self) -> RegionResponse:
|
|
958
|
+
def region_response(self) -> RegionResponse | None:
|
|
871
959
|
return self._region_response
|
|
872
960
|
|
|
873
961
|
@property
|
|
874
|
-
def aep_response(self) -> AepResponse:
|
|
962
|
+
def aep_response(self) -> AepResponse | None:
|
|
875
963
|
return self._aep_response
|
|
876
964
|
|
|
877
965
|
@property
|
|
@@ -883,9 +971,9 @@ class CloudIOTGateway:
|
|
|
883
971
|
return self._client_id
|
|
884
972
|
|
|
885
973
|
@property
|
|
886
|
-
def login_by_oauth_response(self) -> LoginByOAuthResponse:
|
|
974
|
+
def login_by_oauth_response(self) -> LoginByOAuthResponse | None:
|
|
887
975
|
return self._login_by_oauth_response
|
|
888
976
|
|
|
889
977
|
@property
|
|
890
|
-
def connect_response(self) -> ConnectResponse:
|
|
978
|
+
def connect_response(self) -> ConnectResponse | None:
|
|
891
979
|
return self._connect_response
|
|
@@ -1,35 +1,183 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
+
from typing import Annotated, Optional
|
|
2
3
|
|
|
3
4
|
from mashumaro.config import BaseConfig
|
|
4
5
|
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
|
6
|
+
from mashumaro.types import Alias
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
@dataclass
|
|
8
10
|
class Device(DataClassORJSONMixin):
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
categoryName: str
|
|
17
|
-
identityAlias: str
|
|
18
|
-
productName: str
|
|
19
|
-
iotId: str
|
|
20
|
-
bindTime: int
|
|
21
|
-
owned: int
|
|
22
|
-
identityId: str
|
|
23
|
-
thingType: str
|
|
11
|
+
"""Unified device model supporting both Device and ShareNotification data"""
|
|
12
|
+
|
|
13
|
+
# Core device fields (from Device model)
|
|
14
|
+
gmt_modified: Annotated[int, Alias("gmtModified")]
|
|
15
|
+
node_type: Annotated[str, Alias("nodeType")]
|
|
16
|
+
device_name: Annotated[str, Alias("deviceName")]
|
|
17
|
+
product_name: Annotated[str, Alias("productName")]
|
|
24
18
|
status: int
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
identity_id: Annotated[str, Alias("identityId")]
|
|
20
|
+
|
|
21
|
+
# Required fields from original Device model
|
|
22
|
+
net_type: Annotated[str, Alias("netType")]
|
|
23
|
+
category_key: Annotated[str, Alias("categoryKey")]
|
|
24
|
+
product_key: Annotated[str, Alias("productKey")]
|
|
25
|
+
is_edge_gateway: Annotated[bool, Alias("isEdgeGateway")]
|
|
26
|
+
category_name: Annotated[str, Alias("categoryName")]
|
|
27
|
+
identity_alias: Annotated[str, Alias("identityAlias")]
|
|
28
|
+
iot_id: Annotated[str, Alias("iotId")]
|
|
29
|
+
bind_time: Annotated[int, Alias("bindTime")]
|
|
30
|
+
owned: int
|
|
31
|
+
thing_type: Annotated[str, Alias("thingType")]
|
|
32
|
+
|
|
33
|
+
# Optional fields (common to both or nullable)
|
|
34
|
+
nick_name: Annotated[Optional[str], Alias("nickName")] = None
|
|
35
|
+
description: Optional[str] = None
|
|
36
|
+
product_image: Annotated[Optional[str], Alias("productImage")] = None
|
|
37
|
+
category_image: Annotated[Optional[str], Alias("categoryImage")] = None
|
|
38
|
+
product_model: Annotated[Optional[str], Alias("productModel")] = None
|
|
39
|
+
|
|
40
|
+
# Optional fields from ShareNotification only
|
|
41
|
+
target_id: Annotated[Optional[str], Alias("targetId")] = None
|
|
42
|
+
receiver_identity_id: Annotated[Optional[str], Alias("receiverIdentityId")] = None
|
|
43
|
+
target_type: Annotated[Optional[str], Alias("targetType")] = None
|
|
44
|
+
gmt_create: Annotated[Optional[int], Alias("gmtCreate")] = None
|
|
45
|
+
batch_id: Annotated[Optional[str], Alias("batchId")] = None
|
|
46
|
+
record_id: Annotated[Optional[str], Alias("recordId")] = None
|
|
47
|
+
initiator_identity_id: Annotated[Optional[str], Alias("initiatorIdentityId")] = None
|
|
48
|
+
is_receiver: Annotated[Optional[int], Alias("isReceiver")] = None
|
|
49
|
+
initiator_alias: Annotated[Optional[str], Alias("initiatorAlias")] = None
|
|
50
|
+
receiver_alias: Annotated[Optional[str], Alias("receiverAlias")] = None
|
|
30
51
|
|
|
31
52
|
class Config(BaseConfig):
|
|
32
53
|
omit_default = True
|
|
54
|
+
allow_deserialization_not_by_alias = True
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# # Alternative: Keep them separate but with a common base class
|
|
58
|
+
# @dataclass
|
|
59
|
+
# class BaseDevice(DataClassORJSONMixin):
|
|
60
|
+
# """Base device model with common fields"""
|
|
61
|
+
#
|
|
62
|
+
# gmt_modified: int
|
|
63
|
+
# node_type: str
|
|
64
|
+
# device_name: str
|
|
65
|
+
# product_name: str
|
|
66
|
+
# status: int
|
|
67
|
+
# product_image: Optional[str] = None
|
|
68
|
+
# category_image: Optional[str] = None
|
|
69
|
+
# description: Optional[str] = None
|
|
70
|
+
#
|
|
71
|
+
# class Config(BaseConfig):
|
|
72
|
+
# omit_default = True
|
|
73
|
+
# serialize_by_alias = True
|
|
74
|
+
# aliases = {
|
|
75
|
+
# "gmt_modified": "gmtModified",
|
|
76
|
+
# "node_type": "nodeType",
|
|
77
|
+
# "device_name": "deviceName",
|
|
78
|
+
# "product_name": "productName",
|
|
79
|
+
# "product_image": "productImage",
|
|
80
|
+
# "category_image": "categoryImage",
|
|
81
|
+
# }
|
|
82
|
+
#
|
|
83
|
+
#
|
|
84
|
+
# @dataclass
|
|
85
|
+
# class Device(BaseDevice):
|
|
86
|
+
# """Full device model"""
|
|
87
|
+
#
|
|
88
|
+
# net_type: str
|
|
89
|
+
# category_key: str
|
|
90
|
+
# product_key: str
|
|
91
|
+
# is_edge_gateway: bool
|
|
92
|
+
# category_name: str
|
|
93
|
+
# identity_alias: str
|
|
94
|
+
# iot_id: str
|
|
95
|
+
# bind_time: int
|
|
96
|
+
# owned: int
|
|
97
|
+
# identity_id: str
|
|
98
|
+
# thing_type: str
|
|
99
|
+
# nick_name: Optional[str] = None
|
|
100
|
+
# product_model: Optional[str] = None
|
|
101
|
+
#
|
|
102
|
+
# class Config(BaseConfig):
|
|
103
|
+
# omit_default = True
|
|
104
|
+
# serialize_by_alias = True
|
|
105
|
+
# aliases = {
|
|
106
|
+
# **BaseDevice.Config.aliases,
|
|
107
|
+
# "net_type": "netType",
|
|
108
|
+
# "category_key": "categoryKey",
|
|
109
|
+
# "product_key": "productKey",
|
|
110
|
+
# "is_edge_gateway": "isEdgeGateway",
|
|
111
|
+
# "category_name": "categoryName",
|
|
112
|
+
# "identity_alias": "identityAlias",
|
|
113
|
+
# "iot_id": "iotId",
|
|
114
|
+
# "bind_time": "bindTime",
|
|
115
|
+
# "identity_id": "identityId",
|
|
116
|
+
# "thing_type": "thingType",
|
|
117
|
+
# "nick_name": "nickName",
|
|
118
|
+
# "product_model": "productModel",
|
|
119
|
+
# }
|
|
120
|
+
#
|
|
121
|
+
#
|
|
122
|
+
# @dataclass
|
|
123
|
+
# class ShareNotification(BaseDevice):
|
|
124
|
+
# """Share notification model extending base device"""
|
|
125
|
+
#
|
|
126
|
+
# target_id: str
|
|
127
|
+
# receiver_identity_id: str
|
|
128
|
+
# target_type: str
|
|
129
|
+
# gmt_create: int
|
|
130
|
+
# batch_id: str
|
|
131
|
+
# record_id: str
|
|
132
|
+
# initiator_identity_id: str
|
|
133
|
+
# is_receiver: int
|
|
134
|
+
# initiator_alias: str
|
|
135
|
+
# receiver_alias: str
|
|
136
|
+
#
|
|
137
|
+
# # Optional fields that Device has but ShareNotification might not
|
|
138
|
+
# net_type: Optional[str] = None
|
|
139
|
+
# category_key: Optional[str] = None
|
|
140
|
+
# product_key: Optional[str] = None
|
|
141
|
+
# is_edge_gateway: Optional[bool] = None
|
|
142
|
+
# category_name: Optional[str] = None
|
|
143
|
+
# identity_alias: Optional[str] = None
|
|
144
|
+
# iot_id: Optional[str] = None
|
|
145
|
+
# bind_time: Optional[int] = None
|
|
146
|
+
# owned: Optional[int] = None
|
|
147
|
+
# identity_id: Optional[str] = None
|
|
148
|
+
# thing_type: Optional[str] = None
|
|
149
|
+
# nick_name: Optional[str] = None
|
|
150
|
+
# product_model: Optional[str] = None
|
|
151
|
+
#
|
|
152
|
+
# class Config(BaseConfig):
|
|
153
|
+
# omit_default = True
|
|
154
|
+
# serialize_by_alias = True
|
|
155
|
+
# aliases = {
|
|
156
|
+
# **BaseDevice.Config.aliases,
|
|
157
|
+
# "target_id": "targetId",
|
|
158
|
+
# "receiver_identity_id": "receiverIdentityId",
|
|
159
|
+
# "target_type": "targetType",
|
|
160
|
+
# "gmt_create": "gmtCreate",
|
|
161
|
+
# "batch_id": "batchId",
|
|
162
|
+
# "record_id": "recordId",
|
|
163
|
+
# "initiator_identity_id": "initiatorIdentityId",
|
|
164
|
+
# "is_receiver": "isReceiver",
|
|
165
|
+
# "initiator_alias": "initiatorAlias",
|
|
166
|
+
# "receiver_alias": "receiverAlias",
|
|
167
|
+
# # Device fields that might be present
|
|
168
|
+
# "net_type": "netType",
|
|
169
|
+
# "category_key": "categoryKey",
|
|
170
|
+
# "product_key": "productKey",
|
|
171
|
+
# "is_edge_gateway": "isEdgeGateway",
|
|
172
|
+
# "category_name": "categoryName",
|
|
173
|
+
# "identity_alias": "identityAlias",
|
|
174
|
+
# "iot_id": "iotId",
|
|
175
|
+
# "bind_time": "bindTime",
|
|
176
|
+
# "identity_id": "identityId",
|
|
177
|
+
# "thing_type": "thingType",
|
|
178
|
+
# "nick_name": "nickName",
|
|
179
|
+
# "product_model": "productModel",
|
|
180
|
+
# }
|
|
33
181
|
|
|
34
182
|
|
|
35
183
|
@dataclass
|
|
@@ -41,7 +189,7 @@ class Data(DataClassORJSONMixin):
|
|
|
41
189
|
|
|
42
190
|
|
|
43
191
|
@dataclass
|
|
44
|
-
class
|
|
192
|
+
class ListingDevAccountResponse(DataClassORJSONMixin):
|
|
45
193
|
code: int
|
|
46
194
|
data: Data | None
|
|
47
195
|
id: str | None = None
|
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"
|
pymammotion/data/model/device.py
CHANGED
|
@@ -8,6 +8,7 @@ from mashumaro.mixins.orjson import DataClassORJSONMixin
|
|
|
8
8
|
from pymammotion.data.model import HashList, RapidState
|
|
9
9
|
from pymammotion.data.model.device_info import DeviceFirmwares, DeviceNonWorkingHours, MowerInfo
|
|
10
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
|
|
13
14
|
from pymammotion.data.model.work import CurrentTaskSettings
|
|
@@ -41,6 +42,7 @@ class MowingDevice(DataClassORJSONMixin):
|
|
|
41
42
|
device_firmwares: DeviceFirmwares = field(default_factory=DeviceFirmwares)
|
|
42
43
|
errors: DeviceErrors = field(default_factory=DeviceErrors)
|
|
43
44
|
non_work_hours: DeviceNonWorkingHours = field(default_factory=DeviceNonWorkingHours)
|
|
45
|
+
events: Events = field(default_factory=Events)
|
|
44
46
|
|
|
45
47
|
def buffer(self, buffer_list: SystemUpdateBufMsg) -> None:
|
|
46
48
|
"""Update the device based on which buffer we are reading from."""
|
|
@@ -51,9 +53,11 @@ class MowingDevice(DataClassORJSONMixin):
|
|
|
51
53
|
self.location.RTK.latitude = parse_double(buffer_list.update_buf_data[5], 8.0)
|
|
52
54
|
self.location.RTK.longitude = parse_double(buffer_list.update_buf_data[6], 8.0)
|
|
53
55
|
if buffer_list.update_buf_data[7] != 0:
|
|
54
|
-
|
|
55
|
-
self.location.dock.longitude = parse_double(buffer_list.update_buf_data[
|
|
56
|
-
self.location.dock.
|
|
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
|
+
|
|
57
61
|
case 2:
|
|
58
62
|
self.errors.err_code_list.clear()
|
|
59
63
|
self.errors.err_code_list_time.clear()
|
|
@@ -85,6 +89,20 @@ class MowingDevice(DataClassORJSONMixin):
|
|
|
85
89
|
buffer_list.update_buf_data[22],
|
|
86
90
|
]
|
|
87
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
|
|
88
106
|
|
|
89
107
|
def update_report_data(self, toapp_report_data: ReportInfoData) -> None:
|
|
90
108
|
"""Set report data for the mower."""
|
|
@@ -96,16 +114,11 @@ class MowingDevice(DataClassORJSONMixin):
|
|
|
96
114
|
self.location.device = coordinate_converter.enu_to_lla(
|
|
97
115
|
parse_double(location.real_pos_y, 4.0), parse_double(location.real_pos_x, 4.0)
|
|
98
116
|
)
|
|
117
|
+
self.map.invalidate_maps(location.bol_hash)
|
|
99
118
|
if location.zone_hash:
|
|
100
119
|
self.location.work_zone = (
|
|
101
120
|
location.zone_hash if self.report_data.dev.sys_status == WorkMode.MODE_WORKING else 0
|
|
102
121
|
)
|
|
103
|
-
# if location.bol_hash:
|
|
104
|
-
# for loc in toapp_report_data.locations:
|
|
105
|
-
# if loc.bol_hash:
|
|
106
|
-
# if loc.bol_hash != location.bol_hash:
|
|
107
|
-
# self.map = HashList()
|
|
108
|
-
# MurMurHashUtil.hash_unsigned_list(list(self.map.area.keys()))
|
|
109
122
|
|
|
110
123
|
if toapp_report_data.fw_info:
|
|
111
124
|
self.update_device_firmwares(toapp_report_data.fw_info)
|
|
@@ -30,7 +30,7 @@ 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:
|
|
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:
|
pymammotion/data/model/enums.py
CHANGED
|
@@ -4,9 +4,11 @@ from enum import Enum
|
|
|
4
4
|
class ConnectionPreference(Enum):
|
|
5
5
|
"""Enum for connection preference."""
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
ANY = 0
|
|
8
8
|
WIFI = 1
|
|
9
9
|
BLUETOOTH = 2
|
|
10
|
+
PREFER_WIFI = 3
|
|
11
|
+
PREFER_BLUETOOTH = 4
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class PositionMode(Enum):
|
|
@@ -17,7 +19,7 @@ class PositionMode(Enum):
|
|
|
17
19
|
UNKNOWN = 4
|
|
18
20
|
|
|
19
21
|
@staticmethod
|
|
20
|
-
def from_value(value: int):
|
|
22
|
+
def from_value(value: int) -> "PositionMode":
|
|
21
23
|
if value == 0:
|
|
22
24
|
return PositionMode.FIX
|
|
23
25
|
elif value == 1:
|
|
@@ -50,7 +52,7 @@ class RTKStatus(Enum):
|
|
|
50
52
|
UNKNOWN = 6
|
|
51
53
|
|
|
52
54
|
@staticmethod
|
|
53
|
-
def from_value(value: int):
|
|
55
|
+
def from_value(value: int) -> "RTKStatus":
|
|
54
56
|
if value == 0:
|
|
55
57
|
return RTKStatus.NONE
|
|
56
58
|
elif value == 1 or value == 2:
|
|
@@ -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)
|