pymammotion 0.3.7__tar.gz → 0.4.0a0__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.
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/PKG-INFO +2 -1
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/__init__.py +2 -2
- pymammotion-0.4.0a0/pymammotion/data/model/device.py +115 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/model/device_config.py +0 -10
- pymammotion-0.4.0a0/pymammotion/data/model/device_limits.py +49 -0
- pymammotion-0.3.7/pymammotion/data/model/device.py → pymammotion-0.4.0a0/pymammotion/data/model/raw_data.py +13 -123
- pymammotion-0.4.0a0/pymammotion/http/encryption.py +221 -0
- pymammotion-0.4.0a0/pymammotion/http/http.py +147 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/http/model/http.py +2 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/commands/messages/system.py +5 -5
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/devices/base.py +4 -2
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/devices/mammotion.py +2 -2
- pymammotion-0.4.0a0/pymammotion/utility/device_config.py +363 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pyproject.toml +4 -3
- pymammotion-0.3.7/pymammotion/http/http.py +0 -89
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/LICENSE +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/README.md +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/aliyun/__init__.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/aliyun/cloud_gateway.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/aliyun/cloud_service.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/aliyun/model/aep_response.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/aliyun/model/connect_response.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/aliyun/model/dev_by_account_response.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/aliyun/model/login_by_oauth_response.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/aliyun/model/regions_response.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/aliyun/model/session_by_authcode_response.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/aliyun/model/stream_subscription_response.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/aliyun/tmp_constant.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/bluetooth/__init__.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/bluetooth/ble.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/bluetooth/ble_message.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/bluetooth/const.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/bluetooth/data/__init__.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/bluetooth/data/convert.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/bluetooth/data/framectrldata.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/bluetooth/data/notifydata.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/bluetooth/model/__init__.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/bluetooth/model/atomic_integer.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/const.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/__init__.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/model/__init__.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/model/account.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/model/device_info.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/model/enums.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/model/excute_boarder_params.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/model/execute_boarder.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/model/generate_route_information.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/model/hash_list.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/model/location.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/model/mowing_modes.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/model/plan.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/model/rapid_state.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/model/region_data.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/model/report_info.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/mqtt/__init__.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/mqtt/event.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/mqtt/properties.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/mqtt/status.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/data/state_manager.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/event/__init__.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/event/event.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/http/_init_.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/__init__.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/commands/__init__.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/commands/abstract_message.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/commands/mammotion_command.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/commands/messages/__init__.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/commands/messages/driver.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/commands/messages/media.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/commands/messages/navigation.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/commands/messages/network.py +2 -2
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/commands/messages/ota.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/commands/messages/video.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/control/__init__.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/control/joystick.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/devices/__init__.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/devices/mammotion_bluetooth.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mammotion/devices/mammotion_cloud.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mqtt/__init__.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mqtt/mammotion_future.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/mqtt/mammotion_mqtt.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/__init__.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/basestation.proto +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/basestation.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/basestation_pb2.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/basestation_pb2.pyi +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/common.proto +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/common.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/common_pb2.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/common_pb2.pyi +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/dev_net.proto +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/dev_net.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/dev_net_pb2.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/dev_net_pb2.pyi +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/luba_msg.proto +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/luba_msg.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/luba_msg_pb2.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/luba_msg_pb2.pyi +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/luba_mul.proto +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/luba_mul.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/luba_mul_pb2.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/luba_mul_pb2.pyi +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_driver.proto +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_driver.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_driver_pb2.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_driver_pb2.pyi +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_nav.proto +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_nav.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_nav_pb2.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_nav_pb2.pyi +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_ota.proto +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_ota.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_ota_pb2.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_ota_pb2.pyi +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_pept.proto +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_pept.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_pept_pb2.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_pept_pb2.pyi +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_sys.proto +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_sys.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_sys_pb2.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/proto/mctrl_sys_pb2.pyi +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/py.typed +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/utility/constant/__init__.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/utility/constant/device_constant.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/utility/conversions.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/utility/datatype_converter.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/utility/device_type.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/utility/map.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/utility/movement.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/utility/periodic.py +0 -0
- {pymammotion-0.3.7 → pymammotion-0.4.0a0}/pymammotion/utility/rocker_util.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: pymammotion
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.0a0
|
4
4
|
Summary:
|
5
5
|
License: GNU-3.0
|
6
6
|
Author: Michael Arthur
|
@@ -21,6 +21,7 @@ Requires-Dist: async-timeout (>=4.0.3,<5.0.0)
|
|
21
21
|
Requires-Dist: betterproto (>=1.2.5,<2.0.0)
|
22
22
|
Requires-Dist: bleak (>=0.21.0)
|
23
23
|
Requires-Dist: bleak-retry-connector (>=3.5.0,<4.0.0)
|
24
|
+
Requires-Dist: cryptography (>=43.0.1)
|
24
25
|
Requires-Dist: jsonic (>=1.0.0,<2.0.0)
|
25
26
|
Requires-Dist: mashumaro (>=3.13,<4.0)
|
26
27
|
Requires-Dist: numpy (>=1.26.0)
|
@@ -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
|
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", "
|
23
|
+
__all__ = ["MammotionBLE", "MammotionHTTP", "MammotionMQTT", "logger"]
|
24
24
|
|
25
25
|
|
26
26
|
# TODO provide interface to pick between mqtt/cloud/bluetooth
|
@@ -0,0 +1,115 @@
|
|
1
|
+
"""MowingDevice class to wrap around the betterproto dataclasses."""
|
2
|
+
|
3
|
+
from dataclasses import dataclass, field
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
import betterproto
|
7
|
+
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
8
|
+
|
9
|
+
from pymammotion.data.model import HashList, RapidState
|
10
|
+
from pymammotion.data.model.device_info import MowerInfo
|
11
|
+
from pymammotion.data.model.location import Location
|
12
|
+
from pymammotion.data.model.report_info import ReportData
|
13
|
+
from pymammotion.data.mqtt.properties import ThingPropertiesMessage
|
14
|
+
from pymammotion.http.model.http import ErrorInfo
|
15
|
+
from pymammotion.proto.mctrl_sys import (
|
16
|
+
MowToAppInfoT,
|
17
|
+
ReportInfoData,
|
18
|
+
SystemRapidStateTunnelMsg,
|
19
|
+
SystemUpdateBufMsg,
|
20
|
+
)
|
21
|
+
from pymammotion.utility.constant import WorkMode
|
22
|
+
from pymammotion.utility.conversions import parse_double
|
23
|
+
from pymammotion.utility.map import CoordinateConverter
|
24
|
+
|
25
|
+
|
26
|
+
@dataclass
|
27
|
+
class MowingDevice(DataClassORJSONMixin):
|
28
|
+
"""Wraps the betterproto dataclasses, so we can bypass the groups for keeping all data."""
|
29
|
+
|
30
|
+
mower_state: MowerInfo = field(default_factory=MowerInfo)
|
31
|
+
mqtt_properties: ThingPropertiesMessage | None = None
|
32
|
+
map: HashList = field(default_factory=HashList)
|
33
|
+
location: Location = field(default_factory=Location)
|
34
|
+
mowing_state: RapidState = field(default_factory=RapidState)
|
35
|
+
report_data: ReportData = field(default_factory=ReportData)
|
36
|
+
err_code_list: list = field(default_factory=list)
|
37
|
+
err_code_list_time: Optional[list] = field(default_factory=list)
|
38
|
+
error_codes: dict[str, ErrorInfo] = field(default_factory=dict)
|
39
|
+
|
40
|
+
def buffer(self, buffer_list: SystemUpdateBufMsg) -> None:
|
41
|
+
"""Update the device based on which buffer we are reading from."""
|
42
|
+
match buffer_list.update_buf_data[0]:
|
43
|
+
case 1:
|
44
|
+
# 4 speed
|
45
|
+
self.location.RTK.latitude = parse_double(buffer_list.update_buf_data[5], 8.0)
|
46
|
+
self.location.RTK.longitude = parse_double(buffer_list.update_buf_data[6], 8.0)
|
47
|
+
self.location.dock.latitude = parse_double(buffer_list.update_buf_data[7], 4.0)
|
48
|
+
self.location.dock.longitude = parse_double(buffer_list.update_buf_data[8], 4.0)
|
49
|
+
self.location.dock.rotation = buffer_list.update_buf_data[3] + 180
|
50
|
+
case 2:
|
51
|
+
self.err_code_list.clear()
|
52
|
+
self.err_code_list_time.clear()
|
53
|
+
self.err_code_list.extend(
|
54
|
+
[
|
55
|
+
buffer_list.update_buf_data[3],
|
56
|
+
buffer_list.update_buf_data[5],
|
57
|
+
buffer_list.update_buf_data[7],
|
58
|
+
buffer_list.update_buf_data[9],
|
59
|
+
buffer_list.update_buf_data[11],
|
60
|
+
buffer_list.update_buf_data[13],
|
61
|
+
buffer_list.update_buf_data[15],
|
62
|
+
buffer_list.update_buf_data[17],
|
63
|
+
buffer_list.update_buf_data[19],
|
64
|
+
buffer_list.update_buf_data[21],
|
65
|
+
]
|
66
|
+
)
|
67
|
+
self.err_code_list_time.extend(
|
68
|
+
[
|
69
|
+
buffer_list.update_buf_data[4],
|
70
|
+
buffer_list.update_buf_data[6],
|
71
|
+
buffer_list.update_buf_data[8],
|
72
|
+
buffer_list.update_buf_data[10],
|
73
|
+
buffer_list.update_buf_data[12],
|
74
|
+
buffer_list.update_buf_data[14],
|
75
|
+
buffer_list.update_buf_data[16],
|
76
|
+
buffer_list.update_buf_data[18],
|
77
|
+
buffer_list.update_buf_data[20],
|
78
|
+
buffer_list.update_buf_data[22],
|
79
|
+
]
|
80
|
+
)
|
81
|
+
|
82
|
+
def update_report_data(self, toapp_report_data: ReportInfoData) -> None:
|
83
|
+
coordinate_converter = CoordinateConverter(self.location.RTK.latitude, self.location.RTK.longitude)
|
84
|
+
for index, location in enumerate(toapp_report_data.locations):
|
85
|
+
if index == 0 and location.real_pos_y != 0:
|
86
|
+
self.location.position_type = location.pos_type
|
87
|
+
self.location.orientation = location.real_toward / 10000
|
88
|
+
self.location.device = coordinate_converter.enu_to_lla(
|
89
|
+
parse_double(location.real_pos_y, 4.0), parse_double(location.real_pos_x, 4.0)
|
90
|
+
)
|
91
|
+
if location.zone_hash:
|
92
|
+
self.location.work_zone = (
|
93
|
+
location.zone_hash if self.report_data.dev.sys_status == WorkMode.MODE_WORKING else 0
|
94
|
+
)
|
95
|
+
|
96
|
+
self.report_data.update(toapp_report_data.to_dict(casing=betterproto.Casing.SNAKE))
|
97
|
+
|
98
|
+
def run_state_update(self, rapid_state: SystemRapidStateTunnelMsg) -> None:
|
99
|
+
coordinate_converter = CoordinateConverter(self.location.RTK.latitude, self.location.RTK.longitude)
|
100
|
+
self.mowing_state = RapidState().from_raw(rapid_state.rapid_state_data)
|
101
|
+
self.location.position_type = self.mowing_state.pos_type
|
102
|
+
self.location.orientation = self.mowing_state.toward / 10000
|
103
|
+
self.location.device = coordinate_converter.enu_to_lla(
|
104
|
+
parse_double(self.mowing_state.pos_y, 4.0), parse_double(self.mowing_state.pos_x, 4.0)
|
105
|
+
)
|
106
|
+
if self.mowing_state.zone_hash:
|
107
|
+
self.location.work_zone = (
|
108
|
+
self.mowing_state.zone_hash if self.report_data.dev.sys_status == WorkMode.MODE_WORKING else 0
|
109
|
+
)
|
110
|
+
|
111
|
+
def mow_info(self, toapp_mow_info: MowToAppInfoT) -> None:
|
112
|
+
pass
|
113
|
+
|
114
|
+
def report_missing_data(self) -> None:
|
115
|
+
"""Report missing data so we can refetch it."""
|
@@ -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."""
|
@@ -0,0 +1,49 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
|
3
|
+
|
4
|
+
@dataclass
|
5
|
+
class RangeLimit:
|
6
|
+
min: float
|
7
|
+
max: float
|
8
|
+
|
9
|
+
|
10
|
+
@dataclass
|
11
|
+
class DeviceLimits:
|
12
|
+
cutter_height: RangeLimit = RangeLimit(min=30, max=100)
|
13
|
+
working_speed: RangeLimit = RangeLimit(min=0.2, max=1.2)
|
14
|
+
working_path: RangeLimit = RangeLimit(min=15, max=35)
|
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
|
+
)
|
@@ -1,18 +1,8 @@
|
|
1
|
-
"""MowingDevice class to wrap around the betterproto dataclasses."""
|
2
|
-
|
3
1
|
from dataclasses import dataclass, field
|
4
2
|
from typing import Optional
|
5
3
|
|
6
|
-
import betterproto
|
7
4
|
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
8
5
|
|
9
|
-
from pymammotion.data.model import HashList, RapidState
|
10
|
-
from pymammotion.data.model.device_config import DeviceLimits
|
11
|
-
from pymammotion.data.model.device_info import MowerInfo
|
12
|
-
from pymammotion.data.model.location import Location
|
13
|
-
from pymammotion.data.model.report_info import ReportData
|
14
|
-
from pymammotion.data.mqtt.properties import ThingPropertiesMessage
|
15
|
-
from pymammotion.http.model.http import ErrorInfo
|
16
6
|
from pymammotion.proto.dev_net import DevNet
|
17
7
|
from pymammotion.proto.luba_msg import LubaMsg
|
18
8
|
from pymammotion.proto.luba_mul import SocMul
|
@@ -20,156 +10,56 @@ from pymammotion.proto.mctrl_driver import MctlDriver
|
|
20
10
|
from pymammotion.proto.mctrl_nav import MctlNav
|
21
11
|
from pymammotion.proto.mctrl_ota import MctlOta
|
22
12
|
from pymammotion.proto.mctrl_pept import MctlPept
|
23
|
-
from pymammotion.proto.mctrl_sys import
|
24
|
-
MctlSys,
|
25
|
-
MowToAppInfoT,
|
26
|
-
ReportInfoData,
|
27
|
-
SystemRapidStateTunnelMsg,
|
28
|
-
SystemUpdateBufMsg,
|
29
|
-
)
|
30
|
-
from pymammotion.utility.constant import WorkMode
|
31
|
-
from pymammotion.utility.conversions import parse_double
|
32
|
-
from pymammotion.utility.map import CoordinateConverter
|
13
|
+
from pymammotion.proto.mctrl_sys import MctlSys
|
33
14
|
|
34
15
|
|
35
16
|
@dataclass
|
36
|
-
class
|
37
|
-
|
38
|
-
|
39
|
-
mower_state: MowerInfo = field(default_factory=MowerInfo)
|
40
|
-
mqtt_properties: ThingPropertiesMessage | None = None
|
41
|
-
map: HashList = field(default_factory=HashList)
|
42
|
-
location: Location = field(default_factory=Location)
|
43
|
-
mowing_state: RapidState = field(default_factory=RapidState)
|
44
|
-
report_data: ReportData = field(default_factory=ReportData)
|
45
|
-
err_code_list: list = field(default_factory=list)
|
46
|
-
err_code_list_time: Optional[list] = field(default_factory=list)
|
47
|
-
limits: DeviceLimits = field(default_factory=DeviceLimits)
|
48
|
-
device: Optional[LubaMsg] = field(default_factory=LubaMsg)
|
49
|
-
error_codes: dict[str, ErrorInfo] = field(default_factory=dict)
|
17
|
+
class RawMowerData:
|
18
|
+
raw: Optional[LubaMsg] = field(default_factory=LubaMsg)
|
50
19
|
|
51
20
|
@classmethod
|
52
|
-
def from_raw(cls, raw: dict) -> "
|
21
|
+
def from_raw(cls, raw: dict) -> "RawMowerData":
|
53
22
|
"""Take in raw data to hold in the betterproto dataclass."""
|
54
|
-
|
55
|
-
mowing_device.device = LubaMsg(**raw)
|
56
|
-
return mowing_device
|
23
|
+
return RawMowerData(raw=LubaMsg(**raw))
|
57
24
|
|
58
25
|
def update_raw(self, raw: dict) -> None:
|
59
26
|
"""Update the raw LubaMsg data."""
|
60
|
-
self.
|
61
|
-
|
62
|
-
def buffer(self, buffer_list: SystemUpdateBufMsg) -> None:
|
63
|
-
"""Update the device based on which buffer we are reading from."""
|
64
|
-
match buffer_list.update_buf_data[0]:
|
65
|
-
case 1:
|
66
|
-
# 4 speed
|
67
|
-
self.location.RTK.latitude = parse_double(buffer_list.update_buf_data[5], 8.0)
|
68
|
-
self.location.RTK.longitude = parse_double(buffer_list.update_buf_data[6], 8.0)
|
69
|
-
self.location.dock.latitude = parse_double(buffer_list.update_buf_data[7], 4.0)
|
70
|
-
self.location.dock.longitude = parse_double(buffer_list.update_buf_data[8], 4.0)
|
71
|
-
self.location.dock.rotation = buffer_list.update_buf_data[3] + 180
|
72
|
-
case 2:
|
73
|
-
self.err_code_list.clear()
|
74
|
-
self.err_code_list_time.clear()
|
75
|
-
self.err_code_list.extend(
|
76
|
-
[
|
77
|
-
buffer_list.update_buf_data[3],
|
78
|
-
buffer_list.update_buf_data[5],
|
79
|
-
buffer_list.update_buf_data[7],
|
80
|
-
buffer_list.update_buf_data[9],
|
81
|
-
buffer_list.update_buf_data[11],
|
82
|
-
buffer_list.update_buf_data[13],
|
83
|
-
buffer_list.update_buf_data[15],
|
84
|
-
buffer_list.update_buf_data[17],
|
85
|
-
buffer_list.update_buf_data[19],
|
86
|
-
buffer_list.update_buf_data[21],
|
87
|
-
]
|
88
|
-
)
|
89
|
-
self.err_code_list_time.extend(
|
90
|
-
[
|
91
|
-
buffer_list.update_buf_data[4],
|
92
|
-
buffer_list.update_buf_data[6],
|
93
|
-
buffer_list.update_buf_data[8],
|
94
|
-
buffer_list.update_buf_data[10],
|
95
|
-
buffer_list.update_buf_data[12],
|
96
|
-
buffer_list.update_buf_data[14],
|
97
|
-
buffer_list.update_buf_data[16],
|
98
|
-
buffer_list.update_buf_data[18],
|
99
|
-
buffer_list.update_buf_data[20],
|
100
|
-
buffer_list.update_buf_data[22],
|
101
|
-
]
|
102
|
-
)
|
103
|
-
|
104
|
-
def update_report_data(self, toapp_report_data: ReportInfoData) -> None:
|
105
|
-
coordinate_converter = CoordinateConverter(self.location.RTK.latitude, self.location.RTK.longitude)
|
106
|
-
for index, location in enumerate(toapp_report_data.locations):
|
107
|
-
if index == 0 and location.real_pos_y != 0:
|
108
|
-
self.location.position_type = location.pos_type
|
109
|
-
self.location.orientation = location.real_toward / 10000
|
110
|
-
self.location.device = coordinate_converter.enu_to_lla(
|
111
|
-
parse_double(location.real_pos_y, 4.0), parse_double(location.real_pos_x, 4.0)
|
112
|
-
)
|
113
|
-
if location.zone_hash:
|
114
|
-
self.location.work_zone = (
|
115
|
-
location.zone_hash if self.report_data.dev.sys_status == WorkMode.MODE_WORKING else 0
|
116
|
-
)
|
117
|
-
|
118
|
-
self.report_data.update(toapp_report_data.to_dict(casing=betterproto.Casing.SNAKE))
|
119
|
-
|
120
|
-
def run_state_update(self, rapid_state: SystemRapidStateTunnelMsg) -> None:
|
121
|
-
coordinate_converter = CoordinateConverter(self.location.RTK.latitude, self.location.RTK.longitude)
|
122
|
-
self.mowing_state = RapidState().from_raw(rapid_state.rapid_state_data)
|
123
|
-
self.location.position_type = self.mowing_state.pos_type
|
124
|
-
self.location.orientation = self.mowing_state.toward / 10000
|
125
|
-
self.location.device = coordinate_converter.enu_to_lla(
|
126
|
-
parse_double(self.mowing_state.pos_y, 4.0), parse_double(self.mowing_state.pos_x, 4.0)
|
127
|
-
)
|
128
|
-
if self.mowing_state.zone_hash:
|
129
|
-
self.location.work_zone = (
|
130
|
-
self.mowing_state.zone_hash if self.report_data.dev.sys_status == WorkMode.MODE_WORKING else 0
|
131
|
-
)
|
132
|
-
|
133
|
-
def mow_info(self, toapp_mow_info: MowToAppInfoT) -> None:
|
134
|
-
pass
|
135
|
-
|
136
|
-
def report_missing_data(self) -> None:
|
137
|
-
"""Report missing data so we can refetch it."""
|
27
|
+
self.raw = LubaMsg(**raw)
|
138
28
|
|
139
29
|
@property
|
140
30
|
def net(self):
|
141
31
|
"""Will return a wrapped betterproto of net."""
|
142
|
-
return DevNetData(net=self.
|
32
|
+
return DevNetData(net=self.raw.net)
|
143
33
|
|
144
34
|
@property
|
145
35
|
def sys(self):
|
146
36
|
"""Will return a wrapped betterproto of sys."""
|
147
|
-
return SysData(sys=self.
|
37
|
+
return SysData(sys=self.raw.sys)
|
148
38
|
|
149
39
|
@property
|
150
40
|
def nav(self):
|
151
41
|
"""Will return a wrapped betterproto of nav."""
|
152
|
-
return NavData(nav=self.
|
42
|
+
return NavData(nav=self.raw.nav)
|
153
43
|
|
154
44
|
@property
|
155
45
|
def driver(self):
|
156
46
|
"""Will return a wrapped betterproto of driver."""
|
157
|
-
return DriverData(driver=self.
|
47
|
+
return DriverData(driver=self.raw.driver)
|
158
48
|
|
159
49
|
@property
|
160
50
|
def mul(self):
|
161
51
|
"""Will return a wrapped betterproto of mul."""
|
162
|
-
return MulData(mul=self.
|
52
|
+
return MulData(mul=self.raw.mul)
|
163
53
|
|
164
54
|
@property
|
165
55
|
def ota(self):
|
166
56
|
"""Will return a wrapped betterproto of ota."""
|
167
|
-
return OtaData(ota=self.
|
57
|
+
return OtaData(ota=self.raw.ota)
|
168
58
|
|
169
59
|
@property
|
170
60
|
def pept(self):
|
171
61
|
"""Will return a wrapped betterproto of pept."""
|
172
|
-
return PeptData(pept=self.
|
62
|
+
return PeptData(pept=self.raw.pept)
|
173
63
|
|
174
64
|
|
175
65
|
@dataclass
|
@@ -0,0 +1,221 @@
|
|
1
|
+
import base64
|
2
|
+
import logging
|
3
|
+
import secrets
|
4
|
+
import string
|
5
|
+
from typing import Optional
|
6
|
+
|
7
|
+
from cryptography.hazmat.backends import default_backend
|
8
|
+
from cryptography.hazmat.primitives import padding, serialization
|
9
|
+
from cryptography.hazmat.primitives.asymmetric import padding as rsa_padding
|
10
|
+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
11
|
+
|
12
|
+
_LOGGER = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
class EncryptionUtils:
|
16
|
+
PRIVATE_KEY = """MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOFizbd1fC5XNKJ89u0XNvPZNR/L
|
17
|
+
0h547iSWjOCuvvMu76ZSaS3/Tu2C1C+XmlnmBWTyY4ON+xECiNUXm/aWQ3P0g+wf60zjPbNzgL2Q
|
18
|
+
7njXJG6wka4KkbdQxUdS0TTpL256LnV1LsG855bsbJIJiQPbfUq6HbB5xH7sXdrmFu1DAgMBAAEC
|
19
|
+
gYEAoT2TGE1ncquWjyxBZup1uMvKkp25C23OSMSfslmxZ75LWjyY3HxK1eYDsKyPkwLZFxfFE6du
|
20
|
+
VwPuKiyCuk1ToPfnb4niTGzXPyC2PbO4SFrWL8n1YZ80M0bfTGI9dMCZvpmZJ41WYUsBaf2374lt
|
21
|
+
oEiDEHJp7MeXk/970xiKP1ECQQD65rLHk840q+FZS6kZVexJucPZj/YAII6klU1E20ctioe8Pi5m
|
22
|
+
WSPqclH27/t4FqdvP7tFqaavyXg+CEQpxmxLAkEA5fddDuzcjWgF9pl9fP7/baFMYjUS9z1Vc3gx
|
23
|
+
CnvAgCnv71wjDQhvsUc6sAiidsBGFDyud06RyyLcOlQchMb36QJBAIui/Xjpn+fciQxjeXcqRNk7
|
24
|
+
U+6vml+zvu+GUHyz9Uc5RBXWHYjEr6J5gXiHU1MgeIsH0zgQFT7cR9luTFFbp0UCQFIntfogCocG
|
25
|
+
E6NOoHMoUi5jQnuPRHBJXB69YJ/DKDlhQhN8EhWU3voxXTkITKop9J9EMnvy+MjecljwNaQFxQkC
|
26
|
+
QB9lz67iDe9Gj8NxSElVZxUm9EfbL1RPqTZPx/lADR06CPB8pP3Bl5/5/5RGzc+UTZ+wX5GWKvC7
|
27
|
+
zUJaROxQB+E=""".replace(" ", "")
|
28
|
+
|
29
|
+
PUBLIC_KEY_PROD = """MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApLbeSgOvnwLTWbhaBQWNnnHMtSDAi
|
30
|
+
Gz0PEDbrtd1tLYoO0hukW5PSa6eHykch0Hc6etiqEx1xziS+vNf+iOXds70I4htaYit6yRToZlQ
|
31
|
+
Mim3DQxaZX68nIHIZogur0zGv9U8j01v5l/rHRxyDdlVx3+JkBg6Cqx4U1PXEnAJriqcyg0B8Gm
|
32
|
+
V8Lnmfng+aJLRyq5MkhstYCRv9AsmWu8NpZDJ1ffbkaS02Z9/wpubXTiFP6DG3V2mDw2VvzEcHi
|
33
|
+
cchw49oXmTi92yui+kBgSYlNygssOAyU6H071AfmRUeH3+TsV5u5rg+bCiKyHemVmcKdd3hhZB+
|
34
|
+
HjA8o3On6rg5wIDAQAB""".replace(" ", "")
|
35
|
+
|
36
|
+
PUBLIC_KEY_TEST = """MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1nAzH31arNBmYKvTlvKgkxI1MIr4HpfLbmM
|
37
|
+
XPIhd8D/cXB0dYY1ppUq4a/ezq41YShN88e0elyZgqdnFrkhiLpnKWa7jXtVRgXi9eS18PLO8ns
|
38
|
+
eHude9URaj7relK1AZ0xovKsbLKHd01PpmngLXZfnKA06J2ru/zH+cnpXdy8QIDAQAB""".replace(" ", "")
|
39
|
+
|
40
|
+
def __init__(self) -> None:
|
41
|
+
self.AES_PASW = self.get_aes_key() # Get from previous implementation
|
42
|
+
self.IV = self.get_iv() # Get from previous implementation
|
43
|
+
self._public_key = self.load_public_key()
|
44
|
+
self._private_key = self.load_private_key()
|
45
|
+
|
46
|
+
@staticmethod
|
47
|
+
def load_private_key():
|
48
|
+
"""Load the private key from base64 encoded string"""
|
49
|
+
try:
|
50
|
+
private_key_bytes = base64.b64decode(EncryptionUtils.PRIVATE_KEY)
|
51
|
+
return serialization.load_der_private_key(private_key_bytes, password=None, backend=default_backend())
|
52
|
+
except Exception as e:
|
53
|
+
raise Exception(f"Failed to load private key: {str(e)}")
|
54
|
+
|
55
|
+
@staticmethod
|
56
|
+
def load_public_key(is_production: bool = True):
|
57
|
+
"""Load the public key from base64 encoded string
|
58
|
+
|
59
|
+
Args:
|
60
|
+
is_production (bool): If True, uses production key, else uses test key
|
61
|
+
|
62
|
+
"""
|
63
|
+
try:
|
64
|
+
key_string = EncryptionUtils.PUBLIC_KEY_PROD if is_production else EncryptionUtils.PUBLIC_KEY_TEST
|
65
|
+
public_key_bytes = base64.b64decode(key_string)
|
66
|
+
return serialization.load_der_public_key(public_key_bytes, backend=default_backend())
|
67
|
+
except Exception as e:
|
68
|
+
raise Exception(f"Failed to load public key: {str(e)}")
|
69
|
+
|
70
|
+
@staticmethod
|
71
|
+
def encrypt(plaintext: str, key: str, iv: str) -> str:
|
72
|
+
"""Encrypt text using AES/CBC/PKCS5Padding
|
73
|
+
|
74
|
+
Args:
|
75
|
+
plaintext (str): Text to encrypt
|
76
|
+
key (str): Encryption key
|
77
|
+
iv (str): Initialization vector
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
str: Base64 encoded encrypted string
|
81
|
+
|
82
|
+
Raises:
|
83
|
+
Exception: If encryption fails
|
84
|
+
|
85
|
+
"""
|
86
|
+
try:
|
87
|
+
# Convert strings to bytes
|
88
|
+
plaintext_bytes = plaintext.encode("utf-8")
|
89
|
+
key_bytes = key.encode("utf-8")
|
90
|
+
iv_bytes = iv.encode("utf-8")
|
91
|
+
|
92
|
+
# Create padder
|
93
|
+
padder = padding.PKCS7(128).padder()
|
94
|
+
padded_data = padder.update(plaintext_bytes) + padder.finalize()
|
95
|
+
|
96
|
+
# Create cipher
|
97
|
+
cipher = Cipher(algorithms.AES(key_bytes), modes.CBC(iv_bytes), backend=default_backend())
|
98
|
+
|
99
|
+
# Encrypt
|
100
|
+
encryptor = cipher.encryptor()
|
101
|
+
encrypted_bytes = encryptor.update(padded_data) + encryptor.finalize()
|
102
|
+
|
103
|
+
# Encode to base64
|
104
|
+
return base64.b64encode(encrypted_bytes).decode("utf-8")
|
105
|
+
|
106
|
+
except Exception as e:
|
107
|
+
raise Exception(f"Encryption failed: {str(e)}")
|
108
|
+
|
109
|
+
def encryption_by_aes(self, text: str) -> str:
|
110
|
+
"""Encrypt text using AES with class-level key and IV
|
111
|
+
|
112
|
+
Args:
|
113
|
+
text (str): Text to encrypt
|
114
|
+
|
115
|
+
Returns:
|
116
|
+
str: Encrypted text or None if encryption fails
|
117
|
+
|
118
|
+
"""
|
119
|
+
try:
|
120
|
+
# Perform encryption
|
121
|
+
encrypted = self.encrypt(text, self.AES_PASW, self.IV)
|
122
|
+
|
123
|
+
return encrypted
|
124
|
+
|
125
|
+
except Exception as e:
|
126
|
+
_LOGGER.error(f"Encryption failed: {str(e)}")
|
127
|
+
return None
|
128
|
+
|
129
|
+
def encrypt_by_public_key(self) -> Optional[str]:
|
130
|
+
"""Encrypt data using RSA public key.
|
131
|
+
|
132
|
+
Args:
|
133
|
+
|
134
|
+
Returns:
|
135
|
+
Optional[str]: Base64 encoded encrypted data or None if encryption fails
|
136
|
+
|
137
|
+
"""
|
138
|
+
|
139
|
+
data = f"{self.AES_PASW},{self.IV}"
|
140
|
+
|
141
|
+
if not self._public_key:
|
142
|
+
_LOGGER.error("Public key not initialized")
|
143
|
+
return None
|
144
|
+
|
145
|
+
try:
|
146
|
+
# Convert input string to bytes
|
147
|
+
data_bytes = data.encode("utf-8")
|
148
|
+
|
149
|
+
# Encrypt the data padding.PKCS7(128).padder()
|
150
|
+
encrypted_bytes = self._public_key.encrypt(data_bytes, rsa_padding.PKCS1v15())
|
151
|
+
|
152
|
+
# Convert to base64 string
|
153
|
+
encrypted_str = base64.b64encode(encrypted_bytes).decode("utf-8")
|
154
|
+
_LOGGER.debug("Data encrypted successfully")
|
155
|
+
|
156
|
+
return encrypted_str
|
157
|
+
|
158
|
+
except Exception as err:
|
159
|
+
_LOGGER.error("Encryption failed: %s", str(err))
|
160
|
+
return None
|
161
|
+
|
162
|
+
@staticmethod
|
163
|
+
def get_random_string(length: int) -> str:
|
164
|
+
"""Generate a random string of specified length using alphanumeric characters.
|
165
|
+
|
166
|
+
Args:
|
167
|
+
length (int): The desired length of the random string
|
168
|
+
|
169
|
+
Returns:
|
170
|
+
str: A random alphanumeric string of specified length
|
171
|
+
|
172
|
+
Raises:
|
173
|
+
ValueError: If length is less than 1
|
174
|
+
|
175
|
+
"""
|
176
|
+
if length < 1:
|
177
|
+
raise ValueError("Length must be positive")
|
178
|
+
|
179
|
+
charset = string.ascii_letters + string.digits
|
180
|
+
return "".join(secrets.choice(charset) for _ in range(length))
|
181
|
+
|
182
|
+
@staticmethod
|
183
|
+
def get_random_int(length: int) -> str:
|
184
|
+
"""Generate a random string of specified length containing only digits.
|
185
|
+
|
186
|
+
Args:
|
187
|
+
length (int): The desired length of the random number string
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
str: A string of random digits of specified length
|
191
|
+
|
192
|
+
Raises:
|
193
|
+
ValueError: If length is less than 1
|
194
|
+
|
195
|
+
"""
|
196
|
+
if length < 1:
|
197
|
+
raise ValueError("Length must be positive")
|
198
|
+
|
199
|
+
return "".join(secrets.choice(string.digits) for _ in range(length))
|
200
|
+
|
201
|
+
@staticmethod
|
202
|
+
def get_aes_key() -> str:
|
203
|
+
"""Generate a random AES key of 16 characters using alphanumeric characters.
|
204
|
+
Matches Java implementation behavior.
|
205
|
+
|
206
|
+
Returns:
|
207
|
+
str: A 16-character random string for AES key
|
208
|
+
|
209
|
+
"""
|
210
|
+
return EncryptionUtils.get_random_string(16)
|
211
|
+
|
212
|
+
@staticmethod
|
213
|
+
def get_iv() -> str:
|
214
|
+
"""Generate a random initialization vector of 16 digits.
|
215
|
+
Matches Java implementation behavior.
|
216
|
+
|
217
|
+
Returns:
|
218
|
+
str: A 16-digit random string for initialization vector
|
219
|
+
|
220
|
+
"""
|
221
|
+
return EncryptionUtils.get_random_int(16)
|