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.

Files changed (56) hide show
  1. pymammotion/__init__.py +3 -3
  2. pymammotion/aliyun/client.py +3 -0
  3. pymammotion/aliyun/cloud_gateway.py +117 -19
  4. pymammotion/aliyun/model/dev_by_account_response.py +198 -20
  5. pymammotion/const.py +3 -0
  6. pymammotion/data/model/device.py +1 -0
  7. pymammotion/data/model/device_config.py +1 -1
  8. pymammotion/data/model/enums.py +5 -3
  9. pymammotion/data/model/generate_route_information.py +2 -2
  10. pymammotion/data/model/hash_list.py +113 -33
  11. pymammotion/data/model/region_data.py +4 -4
  12. pymammotion/data/{state_manager.py → mower_state_manager.py} +17 -7
  13. pymammotion/data/mqtt/event.py +47 -22
  14. pymammotion/data/mqtt/mammotion_properties.py +257 -0
  15. pymammotion/data/mqtt/properties.py +32 -29
  16. pymammotion/data/mqtt/status.py +17 -16
  17. pymammotion/homeassistant/__init__.py +3 -0
  18. pymammotion/homeassistant/mower_api.py +446 -0
  19. pymammotion/homeassistant/rtk_api.py +54 -0
  20. pymammotion/http/http.py +431 -18
  21. pymammotion/http/model/http.py +82 -2
  22. pymammotion/http/model/response_factory.py +10 -4
  23. pymammotion/mammotion/commands/mammotion_command.py +20 -0
  24. pymammotion/mammotion/commands/messages/navigation.py +10 -6
  25. pymammotion/mammotion/commands/messages/system.py +0 -14
  26. pymammotion/mammotion/devices/__init__.py +27 -3
  27. pymammotion/mammotion/devices/base.py +22 -146
  28. pymammotion/mammotion/devices/mammotion.py +367 -206
  29. pymammotion/mammotion/devices/mammotion_bluetooth.py +8 -5
  30. pymammotion/mammotion/devices/mammotion_cloud.py +47 -83
  31. pymammotion/mammotion/devices/mammotion_mower_ble.py +49 -0
  32. pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
  33. pymammotion/mammotion/devices/managers/managers.py +81 -0
  34. pymammotion/mammotion/devices/mower_device.py +121 -0
  35. pymammotion/mammotion/devices/mower_manager.py +107 -0
  36. pymammotion/mammotion/devices/rtk_ble.py +89 -0
  37. pymammotion/mammotion/devices/rtk_cloud.py +113 -0
  38. pymammotion/mammotion/devices/rtk_device.py +50 -0
  39. pymammotion/mammotion/devices/rtk_manager.py +122 -0
  40. pymammotion/mqtt/__init__.py +2 -1
  41. pymammotion/mqtt/aliyun_mqtt.py +232 -0
  42. pymammotion/mqtt/mammotion_mqtt.py +174 -192
  43. pymammotion/mqtt/mqtt_models.py +66 -0
  44. pymammotion/proto/__init__.py +2 -2
  45. pymammotion/proto/mctrl_nav.proto +2 -2
  46. pymammotion/proto/mctrl_nav_pb2.py +1 -1
  47. pymammotion/proto/mctrl_nav_pb2.pyi +4 -4
  48. pymammotion/proto/mctrl_sys.proto +1 -1
  49. pymammotion/utility/datatype_converter.py +13 -12
  50. pymammotion/utility/device_type.py +88 -3
  51. pymammotion/utility/mur_mur_hash.py +132 -87
  52. {pymammotion-0.5.27.dist-info → pymammotion-0.5.44.dist-info}/METADATA +25 -30
  53. {pymammotion-0.5.27.dist-info → pymammotion-0.5.44.dist-info}/RECORD +61 -47
  54. {pymammotion-0.5.27.dist-info → pymammotion-0.5.44.dist-info}/WHEEL +1 -1
  55. pymammotion/http/_init_.py +0 -0
  56. {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 = MammotionMQTT(
40
+ luba = AliyunMQTT(
41
41
  iot_token=IOT_TOKEN or "",
42
42
  region_id=REGION or "",
43
43
  product_key=PRODUCT_KEY or "",
@@ -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 ListingDevByAccountResponse
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: ListingDevByAccountResponse | None = None,
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 response.body
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.identityId is None
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) -> ListingDevByAccountResponse:
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["msg"])
665
+ raise Exception("Error in creating session: " + response_body_dict["message"])
658
666
 
659
- self._devices_by_account_response = ListingDevByAccountResponse.from_dict(response_body_dict)
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 = ListingDevByAccountResponse.from_dict(response_body_dict)
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(self.message_delay, self.send_cloud_command, iot_id, command)
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
- gmtModified: int
10
- netType: str
11
- categoryKey: str
12
- productKey: str
13
- nodeType: str
14
- isEdgeGateway: bool
15
- deviceName: str
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
- nickName: str | None = None
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
- productImage: str | None = None
28
- categoryImage: str | None = None
29
- productModel: str | None = None
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 ListingDevByAccountResponse(DataClassORJSONMixin):
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"
@@ -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: 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:
@@ -4,9 +4,11 @@ from enum import Enum
4
4
  class ConnectionPreference(Enum):
5
5
  """Enum for connection preference."""
6
6
 
7
- EITHER = 0
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