pymammotion 0.3.8__tar.gz → 0.4.0__tar.gz

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.
Files changed (141) hide show
  1. {pymammotion-0.3.8 → pymammotion-0.4.0}/PKG-INFO +9 -9
  2. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/__init__.py +2 -2
  3. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/aliyun/cloud_gateway.py +12 -9
  4. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/aliyun/model/aep_response.py +1 -2
  5. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/aliyun/model/dev_by_account_response.py +7 -8
  6. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/aliyun/model/login_by_oauth_response.py +2 -3
  7. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/aliyun/model/regions_response.py +3 -3
  8. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/aliyun/model/session_by_authcode_response.py +1 -2
  9. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/aliyun/model/stream_subscription_response.py +1 -2
  10. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/bluetooth/ble.py +5 -5
  11. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/bluetooth/ble_message.py +9 -13
  12. pymammotion-0.4.0/pymammotion/data/model/device.py +136 -0
  13. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/model/device_config.py +0 -10
  14. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/model/device_info.py +13 -0
  15. pymammotion-0.4.0/pymammotion/data/model/device_limits.py +49 -0
  16. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/model/hash_list.py +6 -2
  17. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/model/plan.py +0 -3
  18. pymammotion-0.4.0/pymammotion/data/model/raw_data.py +215 -0
  19. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/model/region_data.py +10 -11
  20. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/model/report_info.py +1 -1
  21. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/mqtt/event.py +18 -14
  22. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/mqtt/properties.py +1 -1
  23. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/mqtt/status.py +1 -1
  24. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/state_manager.py +83 -23
  25. pymammotion-0.4.0/pymammotion/http/encryption.py +220 -0
  26. pymammotion-0.4.0/pymammotion/http/http.py +142 -0
  27. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/http/model/http.py +2 -2
  28. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mammotion/commands/abstract_message.py +2 -2
  29. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mammotion/commands/messages/driver.py +28 -21
  30. pymammotion-0.4.0/pymammotion/mammotion/commands/messages/media.py +30 -0
  31. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mammotion/commands/messages/navigation.py +14 -11
  32. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mammotion/commands/messages/network.py +15 -12
  33. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mammotion/commands/messages/ota.py +9 -14
  34. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mammotion/commands/messages/system.py +27 -24
  35. pymammotion-0.4.0/pymammotion/mammotion/commands/messages/video.py +29 -0
  36. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mammotion/devices/base.py +7 -14
  37. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mammotion/devices/mammotion.py +22 -13
  38. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mammotion/devices/mammotion_bluetooth.py +15 -4
  39. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mammotion/devices/mammotion_cloud.py +30 -12
  40. pymammotion-0.4.0/pymammotion/mqtt/linkkit/__init__.py +5 -0
  41. pymammotion-0.4.0/pymammotion/mqtt/linkkit/h2client.py +585 -0
  42. pymammotion-0.4.0/pymammotion/mqtt/linkkit/linkkit.py +3020 -0
  43. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mqtt/mammotion_mqtt.py +13 -9
  44. pymammotion-0.4.0/pymammotion/proto/__init__.py +2181 -0
  45. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/luba_mul.proto +1 -0
  46. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/luba_mul_pb2.py +8 -8
  47. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/luba_mul_pb2.pyi +1 -0
  48. pymammotion-0.4.0/pymammotion/proto/mctrl_nav_pb2.py +130 -0
  49. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/mctrl_nav_pb2.pyi +13 -5
  50. pymammotion-0.4.0/pymammotion/proto/mctrl_sys_pb2.py +146 -0
  51. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/mctrl_sys_pb2.pyi +34 -11
  52. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/utility/constant/device_constant.py +14 -5
  53. pymammotion-0.4.0/pymammotion/utility/device_config.py +754 -0
  54. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/utility/device_type.py +64 -16
  55. {pymammotion-0.3.8 → pymammotion-0.4.0}/pyproject.toml +73 -100
  56. pymammotion-0.3.8/pymammotion/aliyun/cloud_service.py +0 -65
  57. pymammotion-0.3.8/pymammotion/data/model/device.py +0 -333
  58. pymammotion-0.3.8/pymammotion/http/http.py +0 -89
  59. pymammotion-0.3.8/pymammotion/mammotion/commands/messages/media.py +0 -34
  60. pymammotion-0.3.8/pymammotion/mammotion/commands/messages/video.py +0 -34
  61. pymammotion-0.3.8/pymammotion/proto/__init__.py +0 -6
  62. pymammotion-0.3.8/pymammotion/proto/basestation.py +0 -59
  63. pymammotion-0.3.8/pymammotion/proto/common.py +0 -12
  64. pymammotion-0.3.8/pymammotion/proto/dev_net.py +0 -381
  65. pymammotion-0.3.8/pymammotion/proto/luba_msg.py +0 -81
  66. pymammotion-0.3.8/pymammotion/proto/luba_mul.py +0 -76
  67. pymammotion-0.3.8/pymammotion/proto/mctrl_driver.py +0 -100
  68. pymammotion-0.3.8/pymammotion/proto/mctrl_nav.py +0 -664
  69. pymammotion-0.3.8/pymammotion/proto/mctrl_nav_pb2.py +0 -128
  70. pymammotion-0.3.8/pymammotion/proto/mctrl_ota.py +0 -48
  71. pymammotion-0.3.8/pymammotion/proto/mctrl_pept.py +0 -41
  72. pymammotion-0.3.8/pymammotion/proto/mctrl_sys.py +0 -574
  73. pymammotion-0.3.8/pymammotion/proto/mctrl_sys_pb2.py +0 -142
  74. {pymammotion-0.3.8 → pymammotion-0.4.0}/LICENSE +0 -0
  75. {pymammotion-0.3.8 → pymammotion-0.4.0}/README.md +0 -0
  76. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/aliyun/__init__.py +0 -0
  77. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/aliyun/model/connect_response.py +0 -0
  78. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/aliyun/tmp_constant.py +0 -0
  79. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/bluetooth/__init__.py +0 -0
  80. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/bluetooth/const.py +0 -0
  81. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/bluetooth/data/__init__.py +0 -0
  82. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/bluetooth/data/convert.py +0 -0
  83. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/bluetooth/data/framectrldata.py +0 -0
  84. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/bluetooth/data/notifydata.py +0 -0
  85. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/bluetooth/model/__init__.py +0 -0
  86. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/bluetooth/model/atomic_integer.py +0 -0
  87. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/const.py +0 -0
  88. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/__init__.py +0 -0
  89. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/model/__init__.py +0 -0
  90. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/model/account.py +0 -0
  91. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/model/enums.py +0 -0
  92. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/model/excute_boarder_params.py +0 -0
  93. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/model/execute_boarder.py +0 -0
  94. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/model/generate_route_information.py +1 -1
  95. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/model/location.py +0 -0
  96. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/model/mowing_modes.py +0 -0
  97. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/model/rapid_state.py +0 -0
  98. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/data/mqtt/__init__.py +0 -0
  99. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/event/__init__.py +0 -0
  100. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/event/event.py +0 -0
  101. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/http/_init_.py +0 -0
  102. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mammotion/__init__.py +0 -0
  103. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mammotion/commands/__init__.py +0 -0
  104. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mammotion/commands/mammotion_command.py +0 -0
  105. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mammotion/commands/messages/__init__.py +0 -0
  106. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mammotion/control/__init__.py +0 -0
  107. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mammotion/control/joystick.py +0 -0
  108. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mammotion/devices/__init__.py +0 -0
  109. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mqtt/__init__.py +0 -0
  110. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/mqtt/mammotion_future.py +0 -0
  111. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/basestation.proto +0 -0
  112. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/basestation_pb2.py +0 -0
  113. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/basestation_pb2.pyi +0 -0
  114. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/common.proto +0 -0
  115. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/common_pb2.py +0 -0
  116. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/common_pb2.pyi +0 -0
  117. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/dev_net.proto +0 -0
  118. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/dev_net_pb2.py +0 -0
  119. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/dev_net_pb2.pyi +0 -0
  120. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/luba_msg.proto +0 -0
  121. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/luba_msg_pb2.py +0 -0
  122. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/luba_msg_pb2.pyi +0 -0
  123. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/mctrl_driver.proto +0 -0
  124. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/mctrl_driver_pb2.py +0 -0
  125. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/mctrl_driver_pb2.pyi +0 -0
  126. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/mctrl_nav.proto +0 -0
  127. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/mctrl_ota.proto +0 -0
  128. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/mctrl_ota_pb2.py +0 -0
  129. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/mctrl_ota_pb2.pyi +0 -0
  130. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/mctrl_pept.proto +0 -0
  131. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/mctrl_pept_pb2.py +0 -0
  132. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/mctrl_pept_pb2.pyi +0 -0
  133. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/proto/mctrl_sys.proto +0 -0
  134. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/py.typed +0 -0
  135. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/utility/constant/__init__.py +0 -0
  136. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/utility/conversions.py +0 -0
  137. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/utility/datatype_converter.py +0 -0
  138. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/utility/map.py +0 -0
  139. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/utility/movement.py +0 -0
  140. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/utility/periodic.py +0 -0
  141. {pymammotion-0.3.8 → pymammotion-0.4.0}/pymammotion/utility/rocker_util.py +0 -0
@@ -1,12 +1,12 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: pymammotion
3
- Version: 0.3.8
3
+ Version: 0.4.0
4
4
  Summary:
5
- License: GNU-3.0
5
+ License: GPL-3.0
6
6
  Author: Michael Arthur
7
7
  Author-email: michael@jumblesoft.co.nz
8
- Requires-Python: >=3.10,<3.13
9
- Classifier: License :: Other/Proprietary License
8
+ Requires-Python: >=3.10
9
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
@@ -15,17 +15,17 @@ Requires-Dist: aiohttp (>=3.9.1,<4.0.0)
15
15
  Requires-Dist: alibabacloud-apigateway-util (>=0.0.2,<0.0.3)
16
16
  Requires-Dist: alibabacloud-iot-api-gateway (>=0.0.4,<0.0.5)
17
17
  Requires-Dist: alicloud-gateway-iot (>=1.0.0,<2.0.0)
18
- Requires-Dist: aliyun-iot-linkkit (>=1.2.12,<2.0.0)
19
- Requires-Dist: aliyun-python-sdk-iot (>=8.57.0,<9.0.0)
20
18
  Requires-Dist: async-timeout (>=4.0.3,<5.0.0)
21
- Requires-Dist: betterproto (>=1.2.5,<2.0.0)
19
+ Requires-Dist: betterproto (>=2.0.0b7,<3.0.0)
22
20
  Requires-Dist: bleak (>=0.21.0)
23
21
  Requires-Dist: bleak-retry-connector (>=3.5.0,<4.0.0)
22
+ Requires-Dist: crcmod (>=1.7,<2.0)
23
+ Requires-Dist: cryptography (>=43.0.1)
24
24
  Requires-Dist: jsonic (>=1.0.0,<2.0.0)
25
25
  Requires-Dist: mashumaro (>=3.13,<4.0)
26
26
  Requires-Dist: numpy (>=1.26.0)
27
27
  Requires-Dist: orjson (>=3.9.15,<4.0.0)
28
- Requires-Dist: paho-mqtt (>=1.6.1,<2.0.0)
28
+ Requires-Dist: paho-mqtt (>=2.1.0,<3.0.0)
29
29
  Requires-Dist: protobuf (>=4.23.1)
30
30
  Requires-Dist: py-jsonic (>=0.0.2,<0.0.3)
31
31
  Description-Content-Type: text/markdown
@@ -12,7 +12,7 @@ from pymammotion.aliyun.cloud_gateway import CloudIOTGateway
12
12
 
13
13
  # works outside HA on its own
14
14
  from pymammotion.bluetooth.ble import MammotionBLE
15
- from pymammotion.http.http import MammotionHTTP, connect_http
15
+ from pymammotion.http.http import MammotionHTTP
16
16
 
17
17
  # TODO make a working device that will work outside HA too.
18
18
  from pymammotion.mqtt import MammotionMQTT
@@ -20,7 +20,7 @@ from pymammotion.mqtt import MammotionMQTT
20
20
  logger = logging.getLogger(__name__)
21
21
 
22
22
 
23
- __all__ = ["MammotionBLE", "MammotionHTTP", "connect_http", "MammotionMQTT", "logger"]
23
+ __all__ = ["MammotionBLE", "MammotionHTTP", "MammotionMQTT", "logger"]
24
24
 
25
25
 
26
26
  # TODO provide interface to pick between mqtt/cloud/bluetooth
@@ -5,11 +5,11 @@ import hashlib
5
5
  import hmac
6
6
  import itertools
7
7
  import json
8
+ from logging import getLogger
8
9
  import random
9
10
  import string
10
11
  import time
11
12
  import uuid
12
- from logging import getLogger
13
13
 
14
14
  from aiohttp import ClientSession
15
15
  from alibabacloud_iot_api_gateway.client import Client
@@ -19,14 +19,10 @@ from alibabacloud_tea_util.models import RuntimeOptions
19
19
 
20
20
  from pymammotion.aliyun.model.aep_response import AepResponse
21
21
  from pymammotion.aliyun.model.connect_response import ConnectResponse
22
- from pymammotion.aliyun.model.dev_by_account_response import (
23
- ListingDevByAccountResponse,
24
- )
22
+ from pymammotion.aliyun.model.dev_by_account_response import ListingDevByAccountResponse
25
23
  from pymammotion.aliyun.model.login_by_oauth_response import LoginByOAuthResponse
26
24
  from pymammotion.aliyun.model.regions_response import RegionResponse
27
- from pymammotion.aliyun.model.session_by_authcode_response import (
28
- SessionByAuthCodeResponse,
29
- )
25
+ from pymammotion.aliyun.model.session_by_authcode_response import SessionByAuthCodeResponse
30
26
  from pymammotion.const import ALIYUN_DOMAIN, APP_KEY, APP_SECRET, APP_VERSION
31
27
  from pymammotion.http.http import MammotionHTTP
32
28
  from pymammotion.utility.datatype_converter import DatatypeConverter
@@ -58,6 +54,10 @@ class DeviceOfflineException(Exception):
58
54
  """Raise exception when device is offline."""
59
55
 
60
56
 
57
+ class GatewayTimeoutException(Exception):
58
+ """Raise exception when the gateway times out."""
59
+
60
+
61
61
  class LoginException(Exception):
62
62
  """Raise exception when library cannot log in."""
63
63
 
@@ -657,7 +657,7 @@ class CloudIOTGateway:
657
657
  iot_token=self._session_by_authcode_response.data.iotToken,
658
658
  )
659
659
 
660
- # TODO move to using InvokeThingServiceRequest()
660
+ # TODO move to using InvokeThingServiceRequest()
661
661
 
662
662
  message_id = str(uuid.uuid4())
663
663
 
@@ -689,13 +689,16 @@ class CloudIOTGateway:
689
689
  str(response_body_dict.get("code")),
690
690
  str(response_body_dict.get("message")),
691
691
  )
692
+ if response_body_dict.get("code") == 20056:
693
+ logger.debug("Gateway timeout.")
694
+ raise GatewayTimeoutException(response_body_dict.get("code"))
695
+
692
696
  if response_body_dict.get("code") == 29003:
693
697
  logger.debug(self._session_by_authcode_response.data.identityId)
694
698
  self.sign_out()
695
699
  raise SetupException(response_body_dict.get("code"))
696
700
  if response_body_dict.get("code") == 6205:
697
701
  raise DeviceOfflineException(response_body_dict.get("code"))
698
- """Device is offline."""
699
702
 
700
703
  return message_id
701
704
 
@@ -1,5 +1,4 @@
1
1
  from dataclasses import dataclass
2
- from typing import Optional
3
2
 
4
3
  from mashumaro.config import BaseConfig
5
4
  from mashumaro.mixins.orjson import DataClassORJSONMixin
@@ -16,7 +15,7 @@ class DeviceData(DataClassORJSONMixin):
16
15
  class AepResponse(DataClassORJSONMixin):
17
16
  code: int
18
17
  data: DeviceData
19
- id: Optional[str] = None
18
+ id: str | None = None
20
19
 
21
20
  class Config(BaseConfig):
22
21
  omit_default = True
@@ -1,5 +1,4 @@
1
1
  from dataclasses import dataclass
2
- from typing import Optional
3
2
 
4
3
  from mashumaro.config import BaseConfig
5
4
  from mashumaro.mixins.orjson import DataClassORJSONMixin
@@ -23,11 +22,11 @@ class Device(DataClassORJSONMixin):
23
22
  identityId: str
24
23
  thingType: str
25
24
  status: int
26
- nickName: Optional[str] = None
27
- description: Optional[str] = None
28
- productImage: Optional[str] = None
29
- categoryImage: Optional[str] = None
30
- productModel: Optional[str] = None
25
+ nickName: str | None = None
26
+ description: str | None = None
27
+ productImage: str | None = None
28
+ categoryImage: str | None = None
29
+ productModel: str | None = None
31
30
 
32
31
  class Config(BaseConfig):
33
32
  omit_default = True
@@ -44,5 +43,5 @@ class Data(DataClassORJSONMixin):
44
43
  @dataclass
45
44
  class ListingDevByAccountResponse(DataClassORJSONMixin):
46
45
  code: int
47
- data: Optional[Data]
48
- id: Optional[str] = None
46
+ data: Data | None
47
+ id: str | None = None
@@ -1,5 +1,4 @@
1
1
  from dataclasses import dataclass
2
- from typing import Optional
3
2
 
4
3
  from mashumaro.mixins.orjson import DataClassORJSONMixin
5
4
 
@@ -18,7 +17,7 @@ class OpenAccount(DataClassORJSONMixin):
18
17
  domainId: int
19
18
  enableDevice: str
20
19
  status: int
21
- country: Optional[str] = None
20
+ country: str | None = None
22
21
 
23
22
 
24
23
  @dataclass
@@ -54,7 +53,7 @@ class InnerData(DataClassORJSONMixin):
54
53
  subCode: int
55
54
  message: str
56
55
  successful: str
57
- deviceId: Optional[str] = None
56
+ deviceId: str | None = None
58
57
 
59
58
 
60
59
  @dataclass
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from typing import Optional, TypeVar
2
+ from typing import TypeVar
3
3
 
4
4
  from mashumaro.config import BaseConfig
5
5
  from mashumaro.mixins.orjson import DataClassORJSONMixin
@@ -22,8 +22,8 @@ class RegionResponseData(DataClassORJSONMixin):
22
22
  class RegionResponse(DataClassORJSONMixin):
23
23
  data: RegionResponseData
24
24
  code: int
25
- id: Optional[str] = None
26
- msg: Optional[str] = None
25
+ id: str | None = None
26
+ msg: str | None = None
27
27
 
28
28
  class Config(BaseConfig):
29
29
  omit_default = True
@@ -1,5 +1,4 @@
1
1
  from dataclasses import dataclass
2
- from typing import Optional
3
2
 
4
3
  from mashumaro.mixins.orjson import DataClassORJSONMixin
5
4
 
@@ -16,4 +15,4 @@ class SessionOauthToken(DataClassORJSONMixin):
16
15
  @dataclass
17
16
  class SessionByAuthCodeResponse(DataClassORJSONMixin):
18
17
  code: int
19
- data: Optional[SessionOauthToken] = None
18
+ data: SessionOauthToken | None = None
@@ -1,5 +1,4 @@
1
1
  from dataclasses import dataclass
2
- from typing import List
3
2
 
4
3
  from mashumaro.mixins.orjson import DataClassORJSONMixin
5
4
 
@@ -13,7 +12,7 @@ class Camera(DataClassORJSONMixin):
13
12
  @dataclass
14
13
  class StreamSubscriptionResponse(DataClassORJSONMixin):
15
14
  appid: str
16
- cameras: List[Camera]
15
+ cameras: list[Camera]
17
16
  channelName: str
18
17
  token: str
19
18
  uid: int
@@ -1,14 +1,13 @@
1
1
  from bleak import BleakClient, BleakScanner, BLEDevice
2
2
  from bleak.backends.characteristic import BleakGATTCharacteristic
3
3
 
4
- from pymammotion.bluetooth.const import (
5
- SERVICE_CHANGED_CHARACTERISTIC,
6
- UUID_NOTIFICATION_CHARACTERISTIC,
7
- )
4
+ from pymammotion.bluetooth.const import SERVICE_CHANGED_CHARACTERISTIC, UUID_NOTIFICATION_CHARACTERISTIC
8
5
  from pymammotion.event.event import BleNotificationEvent
9
6
 
10
7
 
11
8
  class MammotionBLE:
9
+ """Class for basic ble connections to mowers."""
10
+
12
11
  client: BleakClient
13
12
 
14
13
  def __init__(self, bleEvt: BleNotificationEvent) -> None:
@@ -60,5 +59,6 @@ class MammotionBLE:
60
59
  await self.client.start_notify(UUID_NOTIFICATION_CHARACTERISTIC, self.notification_handler)
61
60
  await self.client.start_notify(SERVICE_CHANGED_CHARACTERISTIC, self.service_changed_handler)
62
61
 
63
- def getClient(self):
62
+ def get_client(self):
63
+ """Returns the ble client."""
64
64
  return self.client
@@ -1,12 +1,11 @@
1
+ from asyncio import sleep
2
+ from io import BytesIO
1
3
  import itertools
2
4
  import json
3
5
  import logging
4
6
  import queue
5
7
  import sys
6
8
  import time
7
- from asyncio import sleep
8
- from io import BytesIO
9
- from typing import Union
10
9
 
11
10
  from bleak import BleakClient
12
11
  from jsonic.serializable import serialize
@@ -17,10 +16,7 @@ from pymammotion.bluetooth.data.framectrldata import FrameCtrlData
17
16
  from pymammotion.bluetooth.data.notifydata import BlufiNotifyData
18
17
  from pymammotion.bluetooth.model.atomic_integer import AtomicInteger
19
18
  from pymammotion.data.model.execute_boarder import ExecuteBorder
20
- from pymammotion.proto import (
21
- dev_net_pb2,
22
- )
23
- from pymammotion.proto.luba_msg import LubaMsg, MsgAttr, MsgCmdType, MsgDevice
19
+ from pymammotion.proto import DevNet, DrvDevInfoReq, LubaMsg, MsgAttr, MsgCmdType, MsgDevice
24
20
  from pymammotion.utility.constant.device_constant import bleOrderCmd
25
21
 
26
22
  _LOGGER = logging.getLogger(__name__)
@@ -320,7 +316,7 @@ class BleMessage:
320
316
  hash_map = {"ctrl": 1}
321
317
  await self.post_custom_data(self.get_json_string(bleOrderCmd.bleAlive, hash_map))
322
318
 
323
- def get_json_string(self, cmd: int, hash_map: dict[str, object]) -> str:
319
+ def get_json_string(self, cmd: int, hash_map: dict[str, int]) -> str:
324
320
  jSONObject = {}
325
321
  try:
326
322
  jSONObject["cmd"] = cmd
@@ -334,7 +330,7 @@ class BleMessage:
334
330
  print(e)
335
331
  return ""
336
332
 
337
- def clearNotification(self) -> None:
333
+ def clear_notification(self) -> None:
338
334
  self.notification = None
339
335
  self.notification = BlufiNotifyData()
340
336
 
@@ -344,14 +340,14 @@ class BleMessage:
344
340
  async def send_device_info(self) -> None:
345
341
  """Currently not called"""
346
342
  luba_msg = LubaMsg(
347
- msgtype=MsgCmdType.MSG_CMD_TYPE_ESP,
343
+ msgtype=MsgCmdType.ESP,
348
344
  sender=MsgDevice.DEV_MOBILEAPP,
349
345
  rcver=MsgDevice.DEV_COMM_ESP,
350
- msgattr=MsgAttr.MSG_ATTR_REQ,
346
+ msgattr=MsgAttr.REQ,
351
347
  seqs=1,
352
348
  version=1,
353
349
  subtype=1,
354
- net=dev_net_pb2.DevNet(todev_ble_sync=1, todev_devinfo_req=dev_net_pb2.DrvDevInfoReq()),
350
+ net=DevNet(todev_ble_sync=1, todev_devinfo_req=DrvDevInfoReq()),
355
351
  )
356
352
  byte_arr = luba_msg.SerializeToString()
357
353
  await self.post_custom_data_bytes(byte_arr)
@@ -656,7 +652,7 @@ class BleMessage:
656
652
  return byteOS.getvalue()
657
653
 
658
654
  @staticmethod
659
- def calc_crc(initial: int, data: Union[bytes, bytearray]) -> int:
655
+ def calc_crc(initial: int, data: bytes | bytearray) -> int:
660
656
  """Calculate CRC value for given initial value and byte array.
661
657
 
662
658
  Args:
@@ -0,0 +1,136 @@
1
+ """MowingDevice class to wrap around the betterproto dataclasses."""
2
+
3
+ from dataclasses import dataclass, field
4
+
5
+ import betterproto
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, MowerInfo
10
+ from pymammotion.data.model.location import Location
11
+ from pymammotion.data.model.report_info import ReportData
12
+ from pymammotion.data.mqtt.properties import ThingPropertiesMessage
13
+ from pymammotion.data.mqtt.status import ThingStatusMessage
14
+ from pymammotion.http.model.http import ErrorInfo
15
+ from pymammotion.proto import DeviceFwInfo, MowToAppInfoT, ReportInfoData, SystemRapidStateTunnelMsg, SystemUpdateBufMsg
16
+ from pymammotion.utility.constant import WorkMode
17
+ from pymammotion.utility.conversions import parse_double
18
+ from pymammotion.utility.map import CoordinateConverter
19
+
20
+
21
+ @dataclass
22
+ class MowingDevice(DataClassORJSONMixin):
23
+ """Wraps the betterproto dataclasses, so we can bypass the groups for keeping all data."""
24
+
25
+ online: bool = True
26
+ mower_state: MowerInfo = field(default_factory=MowerInfo)
27
+ mqtt_properties: ThingPropertiesMessage | None = None
28
+ status_properties: ThingStatusMessage | None = None
29
+ map: HashList = field(default_factory=HashList)
30
+ location: Location = field(default_factory=Location)
31
+ mowing_state: RapidState = field(default_factory=RapidState)
32
+ report_data: ReportData = field(default_factory=ReportData)
33
+ device_firmwares: DeviceFirmwares = field(default_factory=DeviceFirmwares)
34
+ err_code_list: list = field(default_factory=list)
35
+ err_code_list_time: list | None = field(default_factory=list)
36
+ error_codes: dict[str, ErrorInfo] = field(default_factory=dict)
37
+
38
+ def buffer(self, buffer_list: SystemUpdateBufMsg) -> None:
39
+ """Update the device based on which buffer we are reading from."""
40
+ match buffer_list.update_buf_data[0]:
41
+ case 1:
42
+ # 4 speed
43
+ self.location.RTK.latitude = parse_double(buffer_list.update_buf_data[5], 8.0)
44
+ self.location.RTK.longitude = parse_double(buffer_list.update_buf_data[6], 8.0)
45
+ self.location.dock.latitude = parse_double(buffer_list.update_buf_data[7], 4.0)
46
+ self.location.dock.longitude = parse_double(buffer_list.update_buf_data[8], 4.0)
47
+ self.location.dock.rotation = buffer_list.update_buf_data[3] + 180
48
+ case 2:
49
+ self.err_code_list.clear()
50
+ self.err_code_list_time.clear()
51
+ self.err_code_list.extend(
52
+ [
53
+ buffer_list.update_buf_data[3],
54
+ buffer_list.update_buf_data[5],
55
+ buffer_list.update_buf_data[7],
56
+ buffer_list.update_buf_data[9],
57
+ buffer_list.update_buf_data[11],
58
+ buffer_list.update_buf_data[13],
59
+ buffer_list.update_buf_data[15],
60
+ buffer_list.update_buf_data[17],
61
+ buffer_list.update_buf_data[19],
62
+ buffer_list.update_buf_data[21],
63
+ ]
64
+ )
65
+ self.err_code_list_time.extend(
66
+ [
67
+ buffer_list.update_buf_data[4],
68
+ buffer_list.update_buf_data[6],
69
+ buffer_list.update_buf_data[8],
70
+ buffer_list.update_buf_data[10],
71
+ buffer_list.update_buf_data[12],
72
+ buffer_list.update_buf_data[14],
73
+ buffer_list.update_buf_data[16],
74
+ buffer_list.update_buf_data[18],
75
+ buffer_list.update_buf_data[20],
76
+ buffer_list.update_buf_data[22],
77
+ ]
78
+ )
79
+
80
+ def update_report_data(self, toapp_report_data: ReportInfoData) -> None:
81
+ coordinate_converter = CoordinateConverter(self.location.RTK.latitude, self.location.RTK.longitude)
82
+ for index, location in enumerate(toapp_report_data.locations):
83
+ if index == 0 and location.real_pos_y != 0:
84
+ self.location.position_type = location.pos_type
85
+ self.location.orientation = int(location.real_toward / 10000)
86
+ self.location.device = coordinate_converter.enu_to_lla(
87
+ parse_double(location.real_pos_y, 4.0), parse_double(location.real_pos_x, 4.0)
88
+ )
89
+ if location.zone_hash:
90
+ self.location.work_zone = (
91
+ location.zone_hash if self.report_data.dev.sys_status == WorkMode.MODE_WORKING else 0
92
+ )
93
+
94
+ if toapp_report_data.fw_info:
95
+ self.update_device_firmwares(toapp_report_data.fw_info)
96
+
97
+ self.report_data.update(toapp_report_data.to_dict(casing=betterproto.Casing.SNAKE))
98
+
99
+ def run_state_update(self, rapid_state: SystemRapidStateTunnelMsg) -> None:
100
+ coordinate_converter = CoordinateConverter(self.location.RTK.latitude, self.location.RTK.longitude)
101
+ self.mowing_state = RapidState().from_raw(rapid_state.rapid_state_data)
102
+ self.location.position_type = self.mowing_state.pos_type
103
+ self.location.orientation = int(self.mowing_state.toward / 10000)
104
+ self.location.device = coordinate_converter.enu_to_lla(
105
+ parse_double(self.mowing_state.pos_y, 4.0), parse_double(self.mowing_state.pos_x, 4.0)
106
+ )
107
+ if self.mowing_state.zone_hash:
108
+ self.location.work_zone = (
109
+ self.mowing_state.zone_hash if self.report_data.dev.sys_status == WorkMode.MODE_WORKING else 0
110
+ )
111
+
112
+ def mow_info(self, toapp_mow_info: MowToAppInfoT) -> None:
113
+ pass
114
+
115
+ def report_missing_data(self) -> None:
116
+ """Report missing data so we can refetch it."""
117
+
118
+ def update_device_firmwares(self, fw_info: DeviceFwInfo) -> None:
119
+ """Sets firmware versions on all parts of the robot or RTK."""
120
+ for mod in fw_info.mod:
121
+ match mod.type:
122
+ case 1:
123
+ self.device_firmwares.main_controller = mod.version
124
+ case 3:
125
+ self.device_firmwares.left_motor_driver = mod.version
126
+ case 4:
127
+ self.device_firmwares.right_motor_driver = mod.version
128
+ case 5:
129
+ self.device_firmwares.rtk_rover_station = mod.version
130
+ case 101:
131
+ # RTK main board
132
+ self.device_firmwares.main_controller = mod.version
133
+ case 102:
134
+ self.device_firmwares.rtk_version = mod.version
135
+ case 103:
136
+ self.device_firmwares.lora_version = mod.version
@@ -5,16 +5,6 @@ from mashumaro.mixins.orjson import DataClassORJSONMixin
5
5
  from pymammotion.utility.device_type import DeviceType
6
6
 
7
7
 
8
- @dataclass
9
- class DeviceLimits(DataClassORJSONMixin):
10
- blade_height_min: int = 30
11
- blade_height_max: int = 100
12
- working_speed_min: float = 0.2
13
- working_speed_max: float = 1.2
14
- working_path_min: int = 15
15
- working_path_max: int = 35
16
-
17
-
18
8
  @dataclass
19
9
  class OperationSettings(DataClassORJSONMixin):
20
10
  """Operation settings for a device."""
@@ -23,3 +23,16 @@ class MowerInfo(DataClassORJSONMixin):
23
23
  swversion: str = ""
24
24
  product_key: str = ""
25
25
  model_id: str = ""
26
+ sub_model_id: str = ""
27
+
28
+
29
+ @dataclass
30
+ class DeviceFirmwares(DataClassORJSONMixin):
31
+ device_version: str = ""
32
+ left_motor_driver: str = ""
33
+ lora_version: str = ""
34
+ main_controller: str = ""
35
+ model_name: str = ""
36
+ right_motor_driver: str = ""
37
+ rtk_rover_station: str = ""
38
+ 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
+ cutter_height: RangeLimit = field(default_factory=RangeLimit)
13
+ working_speed: RangeLimit = field(default_factory=RangeLimit)
14
+ working_path: 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
+ "cutter_height": {"min": self.cutter_height.min, "max": self.cutter_height.max},
22
+ "working_speed": {"min": self.working_speed.min, "max": self.working_speed.max},
23
+ "working_path": {"min": self.working_path.min, "max": self.working_path.max},
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
+ cutter_height=RangeLimit(min=data["cutter_height"]["min"], max=data["cutter_height"]["max"]),
33
+ working_speed=RangeLimit(min=data["working_speed"]["min"], max=data["working_speed"]["max"]),
34
+ working_path=RangeLimit(min=data["working_path"]["min"], max=data["working_path"]["max"]),
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.cutter_height.min <= self.cutter_height.max,
44
+ self.working_speed.min <= self.working_speed.max,
45
+ self.working_path.min <= self.working_path.max,
46
+ self.work_area_num_max > 0,
47
+ self.display_image_type in (0, 1),
48
+ ]
49
+ )
@@ -3,7 +3,7 @@ from enum import IntEnum
3
3
 
4
4
  from mashumaro.mixins.orjson import DataClassORJSONMixin
5
5
 
6
- from pymammotion.proto.mctrl_nav import NavGetCommDataAck, NavGetHashListAck, SvgMessageAckT
6
+ from pymammotion.proto import NavGetCommDataAck, NavGetHashListAck, SvgMessageAckT
7
7
 
8
8
 
9
9
  class PathType(IntEnum):
@@ -93,7 +93,7 @@ class HashList(DataClassORJSONMixin):
93
93
  # If no match was found, append the new item
94
94
  self.root_hash_list.data.append(hash_list)
95
95
 
96
- def missing_hash_frame(self):
96
+ def missing_hash_frame(self) -> list[int]:
97
97
  return self._find_missing_frames(self.root_hash_list)
98
98
 
99
99
  def missing_frame(self, hash_data: NavGetCommDataAck | SvgMessageAckT) -> list[int]:
@@ -112,6 +112,8 @@ class HashList(DataClassORJSONMixin):
112
112
  if hash_data.type == PathType.SVG:
113
113
  return self._find_missing_frames(self.svg.get(hash_data.data_hash))
114
114
 
115
+ return []
116
+
115
117
  def update(self, hash_data: NavGetCommDataAck | SvgMessageAckT) -> bool:
116
118
  """Update the map data."""
117
119
  if hash_data.type == PathType.AREA:
@@ -133,6 +135,8 @@ class HashList(DataClassORJSONMixin):
133
135
  if hash_data.type == PathType.SVG:
134
136
  return self._add_hash_data(self.svg, hash_data)
135
137
 
138
+ return False
139
+
136
140
  @staticmethod
137
141
  def _find_missing_frames(frame_list: FrameList | RootHashList) -> list[int]:
138
142
  if frame_list.total_frame == len(frame_list.data):
@@ -1,6 +1,3 @@
1
- from typing import List
2
-
3
-
4
1
  class Plan:
5
2
  def __init__(self) -> None:
6
3
  self.pver: int = 0