pymammotion 0.5.27__py3-none-any.whl → 0.5.44__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/client.py +3 -0
- pymammotion/aliyun/cloud_gateway.py +117 -19
- pymammotion/aliyun/model/dev_by_account_response.py +198 -20
- pymammotion/const.py +3 -0
- pymammotion/data/model/device.py +1 -0
- pymammotion/data/model/device_config.py +1 -1
- pymammotion/data/model/enums.py +5 -3
- pymammotion/data/model/generate_route_information.py +2 -2
- pymammotion/data/model/hash_list.py +113 -33
- pymammotion/data/model/region_data.py +4 -4
- pymammotion/data/{state_manager.py → mower_state_manager.py} +17 -7
- 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/homeassistant/__init__.py +3 -0
- pymammotion/homeassistant/mower_api.py +446 -0
- pymammotion/homeassistant/rtk_api.py +54 -0
- pymammotion/http/http.py +431 -18
- pymammotion/http/model/http.py +82 -2
- pymammotion/http/model/response_factory.py +10 -4
- pymammotion/mammotion/commands/mammotion_command.py +20 -0
- pymammotion/mammotion/commands/messages/navigation.py +10 -6
- pymammotion/mammotion/commands/messages/system.py +0 -14
- pymammotion/mammotion/devices/__init__.py +27 -3
- pymammotion/mammotion/devices/base.py +22 -146
- pymammotion/mammotion/devices/mammotion.py +367 -206
- pymammotion/mammotion/devices/mammotion_bluetooth.py +8 -5
- pymammotion/mammotion/devices/mammotion_cloud.py +47 -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 +121 -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 +2 -2
- pymammotion/proto/mctrl_nav.proto +2 -2
- pymammotion/proto/mctrl_nav_pb2.py +1 -1
- pymammotion/proto/mctrl_nav_pb2.pyi +4 -4
- pymammotion/proto/mctrl_sys.proto +1 -1
- pymammotion/utility/datatype_converter.py +13 -12
- pymammotion/utility/device_type.py +88 -3
- pymammotion/utility/mur_mur_hash.py +132 -87
- {pymammotion-0.5.27.dist-info → pymammotion-0.5.44.dist-info}/METADATA +25 -30
- {pymammotion-0.5.27.dist-info → pymammotion-0.5.44.dist-info}/RECORD +61 -47
- {pymammotion-0.5.27.dist-info → pymammotion-0.5.44.dist-info}/WHEEL +1 -1
- pymammotion/http/_init_.py +0 -0
- {pymammotion-0.5.27.dist-info → pymammotion-0.5.44.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 "",
|
pymammotion/aliyun/client.py
CHANGED
|
@@ -124,6 +124,9 @@ class Client:
|
|
|
124
124
|
_request.headers["x-ca-signature"] = APIGatewayUtilClient.get_signature(_request, self._app_secret)
|
|
125
125
|
_last_request = _request
|
|
126
126
|
_response = TeaCore.do_action(_request, _runtime)
|
|
127
|
+
if _response.body.get("code") == 20056:
|
|
128
|
+
raise Exception("Gateway timeout.")
|
|
129
|
+
|
|
127
130
|
return _response
|
|
128
131
|
except Exception as e:
|
|
129
132
|
if TeaCore.is_retryable(e):
|
|
@@ -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,
|
|
@@ -600,11 +603,16 @@ class CloudIOTGateway:
|
|
|
600
603
|
await self.sign_out()
|
|
601
604
|
raise CheckSessionException("Error check or refresh token: " + response_body_dict.__str__())
|
|
602
605
|
|
|
606
|
+
if response_body_dict.get("code") == 2401:
|
|
607
|
+
await self.sign_out()
|
|
608
|
+
raise CheckSessionException("Error check or refresh token: " + response_body_dict.__str__())
|
|
609
|
+
|
|
603
610
|
session = SessionByAuthCodeResponse.from_dict(response_body_dict)
|
|
604
611
|
session_data = session.data
|
|
605
612
|
|
|
606
613
|
if (
|
|
607
|
-
session_data
|
|
614
|
+
session_data is None
|
|
615
|
+
or session_data.identityId is None
|
|
608
616
|
or session_data.refreshTokenExpire is None
|
|
609
617
|
or session_data.iotToken is None
|
|
610
618
|
or session_data.iotTokenExpire is None
|
|
@@ -615,7 +623,7 @@ class CloudIOTGateway:
|
|
|
615
623
|
self._session_by_authcode_response = session
|
|
616
624
|
self._iot_token_issued_at = int(time.time())
|
|
617
625
|
|
|
618
|
-
async def list_binding_by_account(self) ->
|
|
626
|
+
async def list_binding_by_account(self) -> ListingDevAccountResponse:
|
|
619
627
|
"""List bindings by account."""
|
|
620
628
|
config = Config(
|
|
621
629
|
app_key=self._app_key,
|
|
@@ -654,9 +662,9 @@ class CloudIOTGateway:
|
|
|
654
662
|
response_body_dict = self.parse_json_response(response_body_str)
|
|
655
663
|
|
|
656
664
|
if int(response_body_dict.get("code")) != 200:
|
|
657
|
-
raise Exception("Error in creating session: " + response_body_dict["
|
|
665
|
+
raise Exception("Error in creating session: " + response_body_dict["message"])
|
|
658
666
|
|
|
659
|
-
self._devices_by_account_response =
|
|
667
|
+
self._devices_by_account_response = ListingDevAccountResponse.from_dict(response_body_dict)
|
|
660
668
|
return self._devices_by_account_response
|
|
661
669
|
|
|
662
670
|
async def list_binding_by_dev(self, iot_id: str):
|
|
@@ -694,10 +702,94 @@ class CloudIOTGateway:
|
|
|
694
702
|
# Load the JSON string into a dictionary
|
|
695
703
|
response_body_dict = self.parse_json_response(response_body_str)
|
|
696
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
|
+
|
|
697
789
|
if int(response_body_dict.get("code")) != 200:
|
|
698
790
|
raise Exception("Error in creating session: " + response_body_dict["msg"])
|
|
699
791
|
|
|
700
|
-
self._devices_by_account_response =
|
|
792
|
+
self._devices_by_account_response = ListingDevAccountResponse.from_dict(response_body_dict)
|
|
701
793
|
return self._devices_by_account_response
|
|
702
794
|
|
|
703
795
|
async def send_cloud_command(self, iot_id: str, command: bytes) -> str:
|
|
@@ -774,7 +866,9 @@ class CloudIOTGateway:
|
|
|
774
866
|
logger.debug("too many requests.")
|
|
775
867
|
if self.message_delay > 8:
|
|
776
868
|
raise TooManyRequestsException(response.status_message, iot_id)
|
|
777
|
-
asyncio.get_event_loop().call_later(
|
|
869
|
+
asyncio.get_event_loop().call_later(
|
|
870
|
+
self.message_delay, lambda: asyncio.ensure_future(self.send_cloud_command(iot_id, command))
|
|
871
|
+
)
|
|
778
872
|
self.message_delay = self.message_delay * 2
|
|
779
873
|
return message_id
|
|
780
874
|
|
|
@@ -801,6 +895,10 @@ class CloudIOTGateway:
|
|
|
801
895
|
if response_body_dict.get("code") == 6205:
|
|
802
896
|
raise DeviceOfflineException(response_body_dict.get("code"), iot_id)
|
|
803
897
|
|
|
898
|
+
if response_body_dict.get("code") == 460:
|
|
899
|
+
logger.debug("iotToken expired, must re-login.")
|
|
900
|
+
raise CheckSessionException(response_body_dict.get("message"))
|
|
901
|
+
|
|
804
902
|
if self.message_delay != 1:
|
|
805
903
|
self.message_delay = 1
|
|
806
904
|
|
|
@@ -857,11 +955,11 @@ class CloudIOTGateway:
|
|
|
857
955
|
self.mammotion_http = mammotion_http
|
|
858
956
|
|
|
859
957
|
@property
|
|
860
|
-
def region_response(self) -> RegionResponse:
|
|
958
|
+
def region_response(self) -> RegionResponse | None:
|
|
861
959
|
return self._region_response
|
|
862
960
|
|
|
863
961
|
@property
|
|
864
|
-
def aep_response(self) -> AepResponse:
|
|
962
|
+
def aep_response(self) -> AepResponse | None:
|
|
865
963
|
return self._aep_response
|
|
866
964
|
|
|
867
965
|
@property
|
|
@@ -873,9 +971,9 @@ class CloudIOTGateway:
|
|
|
873
971
|
return self._client_id
|
|
874
972
|
|
|
875
973
|
@property
|
|
876
|
-
def login_by_oauth_response(self) -> LoginByOAuthResponse:
|
|
974
|
+
def login_by_oauth_response(self) -> LoginByOAuthResponse | None:
|
|
877
975
|
return self._login_by_oauth_response
|
|
878
976
|
|
|
879
977
|
@property
|
|
880
|
-
def connect_response(self) -> ConnectResponse:
|
|
978
|
+
def connect_response(self) -> ConnectResponse | None:
|
|
881
979
|
return self._connect_response
|
|
@@ -6,30 +6,208 @@ from mashumaro.mixins.orjson import DataClassORJSONMixin
|
|
|
6
6
|
|
|
7
7
|
@dataclass
|
|
8
8
|
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
|
|
9
|
+
"""Unified device model supporting both Device and ShareNotification data"""
|
|
10
|
+
|
|
11
|
+
# Core device fields (from Device model)
|
|
12
|
+
gmt_modified: int
|
|
13
|
+
node_type: str
|
|
14
|
+
device_name: str
|
|
15
|
+
product_name: str
|
|
24
16
|
status: int
|
|
25
|
-
|
|
17
|
+
identity_id: str
|
|
18
|
+
|
|
19
|
+
# Required fields from original Device model
|
|
20
|
+
net_type: str
|
|
21
|
+
category_key: str
|
|
22
|
+
product_key: str
|
|
23
|
+
is_edge_gateway: bool
|
|
24
|
+
category_name: str
|
|
25
|
+
identity_alias: str
|
|
26
|
+
iot_id: str
|
|
27
|
+
bind_time: int
|
|
28
|
+
owned: int
|
|
29
|
+
thing_type: str
|
|
30
|
+
|
|
31
|
+
# Optional fields (common to both or nullable)
|
|
32
|
+
nick_name: str | None = None
|
|
26
33
|
description: str | None = None
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
product_image: str | None = None
|
|
35
|
+
category_image: str | None = None
|
|
36
|
+
product_model: str | None = None
|
|
37
|
+
|
|
38
|
+
# Optional fields from ShareNotification only
|
|
39
|
+
target_id: str | None = None
|
|
40
|
+
receiver_identity_id: str | None = None
|
|
41
|
+
target_type: str | None = None
|
|
42
|
+
gmt_create: int | None = None
|
|
43
|
+
batch_id: str | None = None
|
|
44
|
+
record_id: str | None = None
|
|
45
|
+
initiator_identity_id: str | None = None
|
|
46
|
+
is_receiver: int | None = None
|
|
47
|
+
initiator_alias: str | None = None
|
|
48
|
+
receiver_alias: str | None = None
|
|
30
49
|
|
|
31
50
|
class Config(BaseConfig):
|
|
32
51
|
omit_default = True
|
|
52
|
+
serialize_by_alias = True
|
|
53
|
+
aliases = {
|
|
54
|
+
# Original Device model aliases
|
|
55
|
+
"gmt_modified": "gmtModified",
|
|
56
|
+
"net_type": "netType",
|
|
57
|
+
"category_key": "categoryKey",
|
|
58
|
+
"product_key": "productKey",
|
|
59
|
+
"node_type": "nodeType",
|
|
60
|
+
"is_edge_gateway": "isEdgeGateway",
|
|
61
|
+
"device_name": "deviceName",
|
|
62
|
+
"category_name": "categoryName",
|
|
63
|
+
"identity_alias": "identityAlias",
|
|
64
|
+
"product_name": "productName",
|
|
65
|
+
"iot_id": "iotId",
|
|
66
|
+
"bind_time": "bindTime",
|
|
67
|
+
"identity_id": "identityId",
|
|
68
|
+
"thing_type": "thingType",
|
|
69
|
+
"nick_name": "nickName",
|
|
70
|
+
"product_image": "productImage",
|
|
71
|
+
"category_image": "categoryImage",
|
|
72
|
+
"product_model": "productModel",
|
|
73
|
+
# ShareNotification specific aliases
|
|
74
|
+
"target_id": "targetId",
|
|
75
|
+
"receiver_identity_id": "receiverIdentityId",
|
|
76
|
+
"target_type": "targetType",
|
|
77
|
+
"gmt_create": "gmtCreate",
|
|
78
|
+
"batch_id": "batchId",
|
|
79
|
+
"record_id": "recordId",
|
|
80
|
+
"initiator_identity_id": "initiatorIdentityId",
|
|
81
|
+
"is_receiver": "isReceiver",
|
|
82
|
+
"initiator_alias": "initiatorAlias",
|
|
83
|
+
"receiver_alias": "receiverAlias",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# # Alternative: Keep them separate but with a common base class
|
|
88
|
+
# @dataclass
|
|
89
|
+
# class BaseDevice(DataClassORJSONMixin):
|
|
90
|
+
# """Base device model with common fields"""
|
|
91
|
+
#
|
|
92
|
+
# gmt_modified: int
|
|
93
|
+
# node_type: str
|
|
94
|
+
# device_name: str
|
|
95
|
+
# product_name: str
|
|
96
|
+
# status: int
|
|
97
|
+
# product_image: Optional[str] = None
|
|
98
|
+
# category_image: Optional[str] = None
|
|
99
|
+
# description: Optional[str] = None
|
|
100
|
+
#
|
|
101
|
+
# class Config(BaseConfig):
|
|
102
|
+
# omit_default = True
|
|
103
|
+
# serialize_by_alias = True
|
|
104
|
+
# aliases = {
|
|
105
|
+
# "gmt_modified": "gmtModified",
|
|
106
|
+
# "node_type": "nodeType",
|
|
107
|
+
# "device_name": "deviceName",
|
|
108
|
+
# "product_name": "productName",
|
|
109
|
+
# "product_image": "productImage",
|
|
110
|
+
# "category_image": "categoryImage",
|
|
111
|
+
# }
|
|
112
|
+
#
|
|
113
|
+
#
|
|
114
|
+
# @dataclass
|
|
115
|
+
# class Device(BaseDevice):
|
|
116
|
+
# """Full device model"""
|
|
117
|
+
#
|
|
118
|
+
# net_type: str
|
|
119
|
+
# category_key: str
|
|
120
|
+
# product_key: str
|
|
121
|
+
# is_edge_gateway: bool
|
|
122
|
+
# category_name: str
|
|
123
|
+
# identity_alias: str
|
|
124
|
+
# iot_id: str
|
|
125
|
+
# bind_time: int
|
|
126
|
+
# owned: int
|
|
127
|
+
# identity_id: str
|
|
128
|
+
# thing_type: str
|
|
129
|
+
# nick_name: Optional[str] = None
|
|
130
|
+
# product_model: Optional[str] = None
|
|
131
|
+
#
|
|
132
|
+
# class Config(BaseConfig):
|
|
133
|
+
# omit_default = True
|
|
134
|
+
# serialize_by_alias = True
|
|
135
|
+
# aliases = {
|
|
136
|
+
# **BaseDevice.Config.aliases,
|
|
137
|
+
# "net_type": "netType",
|
|
138
|
+
# "category_key": "categoryKey",
|
|
139
|
+
# "product_key": "productKey",
|
|
140
|
+
# "is_edge_gateway": "isEdgeGateway",
|
|
141
|
+
# "category_name": "categoryName",
|
|
142
|
+
# "identity_alias": "identityAlias",
|
|
143
|
+
# "iot_id": "iotId",
|
|
144
|
+
# "bind_time": "bindTime",
|
|
145
|
+
# "identity_id": "identityId",
|
|
146
|
+
# "thing_type": "thingType",
|
|
147
|
+
# "nick_name": "nickName",
|
|
148
|
+
# "product_model": "productModel",
|
|
149
|
+
# }
|
|
150
|
+
#
|
|
151
|
+
#
|
|
152
|
+
# @dataclass
|
|
153
|
+
# class ShareNotification(BaseDevice):
|
|
154
|
+
# """Share notification model extending base device"""
|
|
155
|
+
#
|
|
156
|
+
# target_id: str
|
|
157
|
+
# receiver_identity_id: str
|
|
158
|
+
# target_type: str
|
|
159
|
+
# gmt_create: int
|
|
160
|
+
# batch_id: str
|
|
161
|
+
# record_id: str
|
|
162
|
+
# initiator_identity_id: str
|
|
163
|
+
# is_receiver: int
|
|
164
|
+
# initiator_alias: str
|
|
165
|
+
# receiver_alias: str
|
|
166
|
+
#
|
|
167
|
+
# # Optional fields that Device has but ShareNotification might not
|
|
168
|
+
# net_type: Optional[str] = None
|
|
169
|
+
# category_key: Optional[str] = None
|
|
170
|
+
# product_key: Optional[str] = None
|
|
171
|
+
# is_edge_gateway: Optional[bool] = None
|
|
172
|
+
# category_name: Optional[str] = None
|
|
173
|
+
# identity_alias: Optional[str] = None
|
|
174
|
+
# iot_id: Optional[str] = None
|
|
175
|
+
# bind_time: Optional[int] = None
|
|
176
|
+
# owned: Optional[int] = None
|
|
177
|
+
# identity_id: Optional[str] = None
|
|
178
|
+
# thing_type: Optional[str] = None
|
|
179
|
+
# nick_name: Optional[str] = None
|
|
180
|
+
# product_model: Optional[str] = None
|
|
181
|
+
#
|
|
182
|
+
# class Config(BaseConfig):
|
|
183
|
+
# omit_default = True
|
|
184
|
+
# serialize_by_alias = True
|
|
185
|
+
# aliases = {
|
|
186
|
+
# **BaseDevice.Config.aliases,
|
|
187
|
+
# "target_id": "targetId",
|
|
188
|
+
# "receiver_identity_id": "receiverIdentityId",
|
|
189
|
+
# "target_type": "targetType",
|
|
190
|
+
# "gmt_create": "gmtCreate",
|
|
191
|
+
# "batch_id": "batchId",
|
|
192
|
+
# "record_id": "recordId",
|
|
193
|
+
# "initiator_identity_id": "initiatorIdentityId",
|
|
194
|
+
# "is_receiver": "isReceiver",
|
|
195
|
+
# "initiator_alias": "initiatorAlias",
|
|
196
|
+
# "receiver_alias": "receiverAlias",
|
|
197
|
+
# # Device fields that might be present
|
|
198
|
+
# "net_type": "netType",
|
|
199
|
+
# "category_key": "categoryKey",
|
|
200
|
+
# "product_key": "productKey",
|
|
201
|
+
# "is_edge_gateway": "isEdgeGateway",
|
|
202
|
+
# "category_name": "categoryName",
|
|
203
|
+
# "identity_alias": "identityAlias",
|
|
204
|
+
# "iot_id": "iotId",
|
|
205
|
+
# "bind_time": "bindTime",
|
|
206
|
+
# "identity_id": "identityId",
|
|
207
|
+
# "thing_type": "thingType",
|
|
208
|
+
# "nick_name": "nickName",
|
|
209
|
+
# "product_model": "productModel",
|
|
210
|
+
# }
|
|
33
211
|
|
|
34
212
|
|
|
35
213
|
@dataclass
|
|
@@ -41,7 +219,7 @@ class Data(DataClassORJSONMixin):
|
|
|
41
219
|
|
|
42
220
|
|
|
43
221
|
@dataclass
|
|
44
|
-
class
|
|
222
|
+
class ListingDevAccountResponse(DataClassORJSONMixin):
|
|
45
223
|
code: int
|
|
46
224
|
data: Data | None
|
|
47
225
|
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
|
@@ -96,6 +96,7 @@ class MowingDevice(DataClassORJSONMixin):
|
|
|
96
96
|
self.location.device = coordinate_converter.enu_to_lla(
|
|
97
97
|
parse_double(location.real_pos_y, 4.0), parse_double(location.real_pos_x, 4.0)
|
|
98
98
|
)
|
|
99
|
+
self.map.invalidate_maps(location.bol_hash)
|
|
99
100
|
if location.zone_hash:
|
|
100
101
|
self.location.work_zone = (
|
|
101
102
|
location.zone_hash if self.report_data.dev.sys_status == WorkMode.MODE_WORKING else 0
|
|
@@ -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:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
import logging
|
|
3
3
|
|
|
4
4
|
logger = logging.getLogger(__name__)
|
|
@@ -8,7 +8,7 @@ logger = logging.getLogger(__name__)
|
|
|
8
8
|
class GenerateRouteInformation:
|
|
9
9
|
"""Creates a model for generating route information and mowing plan before starting a job."""
|
|
10
10
|
|
|
11
|
-
one_hashs: list[int] = list
|
|
11
|
+
one_hashs: list[int] = field(default_factory=list)
|
|
12
12
|
job_mode: int = 4 # taskMode
|
|
13
13
|
job_version: int = 0
|
|
14
14
|
job_id: int = 0
|