device-protocol-sdk 1.2.6__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.
- device_protocol_sdk-1.2.6/PKG-INFO +32 -0
- device_protocol_sdk-1.2.6/README.md +18 -0
- device_protocol_sdk-1.2.6/device_protocol_sdk/__init__.py +0 -0
- device_protocol_sdk-1.2.6/device_protocol_sdk/abstract_device.py +222 -0
- device_protocol_sdk-1.2.6/device_protocol_sdk/device_pb2.py +48 -0
- device_protocol_sdk-1.2.6/device_protocol_sdk/device_pb2_grpc.py +183 -0
- device_protocol_sdk-1.2.6/device_protocol_sdk/model/__init__.py +0 -0
- device_protocol_sdk-1.2.6/device_protocol_sdk/model/action_item.py +12 -0
- device_protocol_sdk-1.2.6/device_protocol_sdk/model/device_key.py +8 -0
- device_protocol_sdk-1.2.6/device_protocol_sdk/model/device_status.py +35 -0
- device_protocol_sdk-1.2.6/device_protocol_sdk/pusher.py +789 -0
- device_protocol_sdk-1.2.6/device_protocol_sdk.egg-info/PKG-INFO +32 -0
- device_protocol_sdk-1.2.6/device_protocol_sdk.egg-info/SOURCES.txt +16 -0
- device_protocol_sdk-1.2.6/device_protocol_sdk.egg-info/dependency_links.txt +1 -0
- device_protocol_sdk-1.2.6/device_protocol_sdk.egg-info/requires.txt +6 -0
- device_protocol_sdk-1.2.6/device_protocol_sdk.egg-info/top_level.txt +1 -0
- device_protocol_sdk-1.2.6/setup.cfg +4 -0
- device_protocol_sdk-1.2.6/setup.py +25 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: device_protocol_sdk
|
|
3
|
+
Version: 1.2.6
|
|
4
|
+
Summary: 无人设备协议开发SDK
|
|
5
|
+
Author: fuhl
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: grpcio>=1.48.2
|
|
9
|
+
Requires-Dist: grpcio-tools>=1.48.2
|
|
10
|
+
Requires-Dist: paho-mqtt>=1.6.1
|
|
11
|
+
Requires-Dist: pydantic>=1.9.0
|
|
12
|
+
Requires-Dist: websockets>=10.0
|
|
13
|
+
Requires-Dist: shapely>=2.1.2
|
|
14
|
+
|
|
15
|
+
# Device Protocol SDK
|
|
16
|
+
|
|
17
|
+
无人机设备协议开发工具包,提供标准化接口实现多类型无人机设备的快速接入。
|
|
18
|
+
|
|
19
|
+
[](https://pypi.org/project/device-protocol-sdk/)
|
|
20
|
+
[](https://pypi.org/project/device-protocol-sdk/)
|
|
21
|
+
|
|
22
|
+
## 功能特性
|
|
23
|
+
|
|
24
|
+
- 🛠️ **协议无关抽象**:统一各类无人机设备的控制接口
|
|
25
|
+
- 🔌 **连接池管理**:自动维护设备连接,支持多设备并行控制
|
|
26
|
+
- 🚀 **实时状态推送**:内置WebSocket状态推送工具
|
|
27
|
+
- 🔍 **动态协议发现**:运行时自动加载用户协议实现
|
|
28
|
+
|
|
29
|
+
## 安装
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install device-protocol-sdk
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Device Protocol SDK
|
|
2
|
+
|
|
3
|
+
无人机设备协议开发工具包,提供标准化接口实现多类型无人机设备的快速接入。
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/device-protocol-sdk/)
|
|
6
|
+
[](https://pypi.org/project/device-protocol-sdk/)
|
|
7
|
+
|
|
8
|
+
## 功能特性
|
|
9
|
+
|
|
10
|
+
- 🛠️ **协议无关抽象**:统一各类无人机设备的控制接口
|
|
11
|
+
- 🔌 **连接池管理**:自动维护设备连接,支持多设备并行控制
|
|
12
|
+
- 🚀 **实时状态推送**:内置WebSocket状态推送工具
|
|
13
|
+
- 🔍 **动态协议发现**:运行时自动加载用户协议实现
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install device-protocol-sdk
|
|
File without changes
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
import threading
|
|
3
|
+
import json,uuid,time
|
|
4
|
+
from typing import List
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from .model.device_key import DeviceKey
|
|
7
|
+
from .model.action_item import ActionItem
|
|
8
|
+
from .model.device_status import DeviceStatus,MessageType,MessageLevel
|
|
9
|
+
from typing import Dict, Any, Optional
|
|
10
|
+
import grpc
|
|
11
|
+
from . import device_pb2
|
|
12
|
+
|
|
13
|
+
from contextlib import contextmanager
|
|
14
|
+
from typing import Generator
|
|
15
|
+
import logging
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AbstractDevice(ABC):
|
|
23
|
+
_status_cache: Dict[DeviceKey, dict] = {}
|
|
24
|
+
_connection_pool: Dict[DeviceKey, Any] = {} # 类级连接池
|
|
25
|
+
_lock = threading.RLock()
|
|
26
|
+
|
|
27
|
+
_monitor_flags = defaultdict(bool) # 状态监控标志
|
|
28
|
+
_monitor_futures = {}
|
|
29
|
+
_lock = threading.Lock() # 线程安全锁
|
|
30
|
+
_status_lock = threading.RLock() # 新增:状态读取可重入锁
|
|
31
|
+
|
|
32
|
+
def __init__(self):
|
|
33
|
+
self._grpc_stub = None
|
|
34
|
+
self._current_mission_id = None
|
|
35
|
+
|
|
36
|
+
def set_grpc_stub(self, grpc_stub):
|
|
37
|
+
"""设置gRPC stub用于发送消息"""
|
|
38
|
+
self._grpc_stub = grpc_stub
|
|
39
|
+
|
|
40
|
+
def set_current_mission_id(self, mission_id: str):
|
|
41
|
+
"""设置当前任务的mission_id"""
|
|
42
|
+
self._current_mission_id = mission_id
|
|
43
|
+
|
|
44
|
+
@contextmanager
|
|
45
|
+
def mission_context(self, mission_id: str) -> Generator[None, None, None]:
|
|
46
|
+
"""为任务提供独立的上下文"""
|
|
47
|
+
original_mission_id = getattr(self, '_current_mission_id', None)
|
|
48
|
+
try:
|
|
49
|
+
self._current_mission_id = mission_id
|
|
50
|
+
yield
|
|
51
|
+
finally:
|
|
52
|
+
self._current_mission_id = original_mission_id
|
|
53
|
+
def send_message(self, device_id: int, message_type: MessageType, content: str,level:MessageLevel,
|
|
54
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
55
|
+
mission_id: Optional[str] = None) -> bool:
|
|
56
|
+
"""
|
|
57
|
+
通过gRPC向服务端发送消息
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
message_type: 消息类型
|
|
61
|
+
content: 消息内容
|
|
62
|
+
metadata: 附加元数据
|
|
63
|
+
mission_id: 任务ID,如果为None则使用当前任务ID
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
bool: 发送是否成功
|
|
67
|
+
"""
|
|
68
|
+
target_mission_id = mission_id or self._current_mission_id
|
|
69
|
+
if not target_mission_id:
|
|
70
|
+
logger.warning("未提供mission_id且当前无任务上下文")
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
if not self._grpc_stub:
|
|
74
|
+
logger.warning("gRPC stub未设置,无法发送消息")
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
# 确定mission_id
|
|
78
|
+
target_mission_id = mission_id or self._current_mission_id
|
|
79
|
+
if not target_mission_id:
|
|
80
|
+
logger.warning("未提供mission_id且当前无任务ID,无法发送消息")
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
# 构建gRPC消息
|
|
85
|
+
device_message = device_pb2.DeviceMessage(
|
|
86
|
+
mission_id=target_mission_id,
|
|
87
|
+
message_type=message_type.value,
|
|
88
|
+
message_level=level.value,
|
|
89
|
+
content=content,
|
|
90
|
+
timestamp=int(time.time()),
|
|
91
|
+
metadata=json.dumps(metadata) if metadata else "",
|
|
92
|
+
device_id=int(device_id)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# 发送消息(假设服务端有ReceiveDeviceMessage方法)
|
|
96
|
+
self._grpc_stub.ReceiveDeviceMessage(device_message)
|
|
97
|
+
|
|
98
|
+
except grpc.RpcError as e:
|
|
99
|
+
logger.error(f"gRPC消息发送失败: {e}")
|
|
100
|
+
return False
|
|
101
|
+
except Exception as e:
|
|
102
|
+
logger.error(f"发送消息时发生异常: {e}")
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
def send_text_message(self, device_id: int, text: str,level:MessageLevel, **kwargs) -> bool:
|
|
106
|
+
"""发送纯文本消息的快捷方法"""
|
|
107
|
+
return self.send_message(device_id,MessageType.TEXT, text,level, **kwargs)
|
|
108
|
+
|
|
109
|
+
def send_image_message(self, device_id: int, image_data_or_url: str,level:MessageLevel, **kwargs) -> bool:
|
|
110
|
+
"""发送图像消息的快捷方法"""
|
|
111
|
+
metadata = kwargs.pop('metadata', {})
|
|
112
|
+
if 'description' not in metadata:
|
|
113
|
+
metadata['description'] = '设备上传的图像'
|
|
114
|
+
return self.send_message(device_id,MessageType.IMAGE, image_data_or_url,level, metadata, **kwargs)
|
|
115
|
+
|
|
116
|
+
def send_video_url_message(self, device_id: int, video_url: str,level:MessageLevel, **kwargs) -> bool:
|
|
117
|
+
"""发送视频URL消息的快捷方法"""
|
|
118
|
+
metadata = kwargs.pop('metadata', {})
|
|
119
|
+
if 'description' not in metadata:
|
|
120
|
+
metadata['description'] = '设备视频流地址'
|
|
121
|
+
return self.send_message(device_id,MessageType.VIDEO_URL, video_url,level, metadata, **kwargs)
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
@abstractmethod
|
|
125
|
+
def protocol_name(self) -> str:
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
def is_cluster(self):
|
|
129
|
+
return 0
|
|
130
|
+
|
|
131
|
+
@abstractmethod
|
|
132
|
+
def get_action_list(self) -> List[ActionItem]:
|
|
133
|
+
pass
|
|
134
|
+
def connect(self, device_key: DeviceKey) -> Any:
|
|
135
|
+
"""带设备ID的连接池实现"""
|
|
136
|
+
with self._lock:
|
|
137
|
+
if device_key not in self._connection_pool:
|
|
138
|
+
is_connect,client = self._create_client(device_key.device_id,device_key.connection_str)
|
|
139
|
+
if is_connect:
|
|
140
|
+
self._connection_pool[device_key] = client
|
|
141
|
+
return is_connect,client
|
|
142
|
+
else:
|
|
143
|
+
client = self._connection_pool[device_key]
|
|
144
|
+
return True,client
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def disconnect(self, device_key: DeviceKey):
|
|
148
|
+
with self._lock:
|
|
149
|
+
if device_key in self._connection_pool:
|
|
150
|
+
self._close_client(self._connection_pool[device_key],device_key.device_id,device_key.connection_str)
|
|
151
|
+
# 清空连接池中对应device_key的数据
|
|
152
|
+
del self._connection_pool[device_key]
|
|
153
|
+
logger.info(f"已从连接池中移除设备 {device_key}")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def is_connected(self, device_key: DeviceKey) -> bool:
|
|
157
|
+
with self._lock:
|
|
158
|
+
if conn := self._connection_pool.get(device_key):
|
|
159
|
+
return True
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
@abstractmethod
|
|
163
|
+
def _create_client(self,device_id:int, connection_str: str) -> tuple[bool, Any]:
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
@abstractmethod
|
|
167
|
+
def _close_client(self,client, device_id: int, connection_str: str) -> Any:
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
def get_device_status(self,client,device_id:str,connection_str:str) -> DeviceStatus:
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
def get_device_status_list(self,client,device_id:str,connection_str:str) -> List[DeviceStatus]:
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
def _ensure_connection(self, device_key: DeviceKey) -> Any:
|
|
177
|
+
"""确保设备连接存在,如果不存在则创建"""
|
|
178
|
+
client = self._connection_pool.get(device_key)
|
|
179
|
+
if client is not None:
|
|
180
|
+
return client
|
|
181
|
+
|
|
182
|
+
# 尝试连接
|
|
183
|
+
success, result = self.connect(device_key)
|
|
184
|
+
if not success:
|
|
185
|
+
raise ConnectionError(f"无法连接设备 {device_key.device_id}: {result}")
|
|
186
|
+
|
|
187
|
+
client = self._connection_pool.get(device_key)
|
|
188
|
+
if client is None:
|
|
189
|
+
raise ConnectionError(f"连接已建立但客户端对象缺失: {device_key.device_id}")
|
|
190
|
+
|
|
191
|
+
return client
|
|
192
|
+
|
|
193
|
+
def get_status(self, device_id: str, connection_str: str) -> DeviceStatus:
|
|
194
|
+
my_device = DeviceKey(device_id=device_id, connection_str=connection_str)
|
|
195
|
+
|
|
196
|
+
# 确保连接存在
|
|
197
|
+
client = self._ensure_connection(my_device)
|
|
198
|
+
is_cluster = self.is_cluster()
|
|
199
|
+
if is_cluster == 0:
|
|
200
|
+
return [self.get_device_status(client, device_id, connection_str)]
|
|
201
|
+
else:
|
|
202
|
+
return self.get_device_status_list(client, device_id, connection_str)
|
|
203
|
+
|
|
204
|
+
def excute_command(self,device_id:str,connection_str:str, command: str, params: Dict[str, Any],
|
|
205
|
+
mission_id: str):
|
|
206
|
+
my_device = DeviceKey(device_id=device_id, connection_str=connection_str)
|
|
207
|
+
this_client = self._ensure_connection(my_device)
|
|
208
|
+
with self.mission_context(mission_id):
|
|
209
|
+
self.execute(this_client,device_id,connection_str,command,params)
|
|
210
|
+
|
|
211
|
+
@abstractmethod
|
|
212
|
+
def execute(self,client,device_id:int,connection_str:str, command: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
213
|
+
"""执行控制指令"""
|
|
214
|
+
|
|
215
|
+
def to_json(self):
|
|
216
|
+
"""序列化设备信息"""
|
|
217
|
+
actions = [item.dict() for item in self.get_action_list()]
|
|
218
|
+
return json.dumps({
|
|
219
|
+
"protocol": self.protocol_name,
|
|
220
|
+
"action_list": actions,
|
|
221
|
+
"is_cluster": self.is_cluster()
|
|
222
|
+
}, ensure_ascii=False)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
|
+
# source: device.proto
|
|
5
|
+
# Protobuf Python Version: 6.31.1
|
|
6
|
+
"""Generated protocol buffer code."""
|
|
7
|
+
from google.protobuf import descriptor as _descriptor
|
|
8
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
9
|
+
from google.protobuf import runtime_version as _runtime_version
|
|
10
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
11
|
+
from google.protobuf.internal import builder as _builder
|
|
12
|
+
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
|
+
_runtime_version.Domain.PUBLIC,
|
|
14
|
+
6,
|
|
15
|
+
31,
|
|
16
|
+
1,
|
|
17
|
+
'',
|
|
18
|
+
'device.proto'
|
|
19
|
+
)
|
|
20
|
+
# @@protoc_insertion_point(imports)
|
|
21
|
+
|
|
22
|
+
_sym_db = _symbol_database.Default()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0c\x64\x65vice.proto\x12\x06\x64\x65vice\"l\n\x0fRegisterRequest\x12\x15\n\rprotocol_name\x18\x01 \x01(\t\x12\x12\n\nauth_token\x18\x02 \x01(\t\x12\x14\n\x0c\x63\x61pabilities\x18\x03 \x01(\t\x12\x18\n\x10type_description\x18\x04 \x01(\t\"\x95\x01\n\x10RegisterResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x17\n\x0fmqtt_broker_url\x18\x03 \x01(\t\x12\x18\n\x10mqtt_broker_port\x18\x04 \x01(\t\x12\x15\n\rmqtt_username\x18\x05 \x01(\t\x12\x15\n\rmqtt_password\x18\x06 \x01(\t\"r\n\x0e\x43ommandRequest\x12\x12\n\ncommand_id\x18\x01 \x01(\t\x12\x12\n\nmission_id\x18\x02 \x01(\t\x12\x10\n\x08\x64rone_id\x18\x03 \x01(\t\x12\x16\n\x0e\x63onnection_str\x18\x04 \x01(\t\x12\x0e\n\x06params\x18\x05 \x01(\t\"d\n\nCommandAck\x12\x12\n\ncommand_id\x18\x01 \x01(\t\x12\x12\n\nmission_id\x18\x02 \x01(\t\x12\x0f\n\x07success\x18\x03 \x01(\x08\x12\x0f\n\x07message\x18\x04 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x05 \x01(\t\"\x99\x01\n\rDeviceMessage\x12\x12\n\nmission_id\x18\x01 \x01(\t\x12\x14\n\x0cmessage_type\x18\x02 \x01(\t\x12\x15\n\rmessage_level\x18\x03 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x04 \x01(\t\x12\x11\n\ttimestamp\x18\x05 \x01(\x03\x12\x10\n\x08metadata\x18\x06 \x01(\t\x12\x11\n\tdevice_id\x18\x07 \x01(\x03\"9\n\x15\x44\x65viceMessageResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t2\xdd\x01\n\rDeviceService\x12=\n\x08Register\x12\x17.device.RegisterRequest\x1a\x18.device.RegisterResponse\x12?\n\rCommandStream\x12\x12.device.CommandAck\x1a\x16.device.CommandRequest(\x01\x30\x01\x12L\n\x14ReceiveDeviceMessage\x12\x15.device.DeviceMessage\x1a\x1d.device.DeviceMessageResponseb\x06proto3')
|
|
28
|
+
|
|
29
|
+
_globals = globals()
|
|
30
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
31
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'device_pb2', _globals)
|
|
32
|
+
if not _descriptor._USE_C_DESCRIPTORS:
|
|
33
|
+
DESCRIPTOR._loaded_options = None
|
|
34
|
+
_globals['_REGISTERREQUEST']._serialized_start=24
|
|
35
|
+
_globals['_REGISTERREQUEST']._serialized_end=132
|
|
36
|
+
_globals['_REGISTERRESPONSE']._serialized_start=135
|
|
37
|
+
_globals['_REGISTERRESPONSE']._serialized_end=284
|
|
38
|
+
_globals['_COMMANDREQUEST']._serialized_start=286
|
|
39
|
+
_globals['_COMMANDREQUEST']._serialized_end=400
|
|
40
|
+
_globals['_COMMANDACK']._serialized_start=402
|
|
41
|
+
_globals['_COMMANDACK']._serialized_end=502
|
|
42
|
+
_globals['_DEVICEMESSAGE']._serialized_start=505
|
|
43
|
+
_globals['_DEVICEMESSAGE']._serialized_end=658
|
|
44
|
+
_globals['_DEVICEMESSAGERESPONSE']._serialized_start=660
|
|
45
|
+
_globals['_DEVICEMESSAGERESPONSE']._serialized_end=717
|
|
46
|
+
_globals['_DEVICESERVICE']._serialized_start=720
|
|
47
|
+
_globals['_DEVICESERVICE']._serialized_end=941
|
|
48
|
+
# @@protoc_insertion_point(module_scope)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
|
2
|
+
"""Client and server classes corresponding to protobuf-defined services."""
|
|
3
|
+
import grpc
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
from . import device_pb2 as device__pb2
|
|
7
|
+
|
|
8
|
+
GRPC_GENERATED_VERSION = '1.74.0'
|
|
9
|
+
GRPC_VERSION = grpc.__version__
|
|
10
|
+
_version_not_supported = False
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from grpc._utilities import first_version_is_lower
|
|
14
|
+
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
|
|
15
|
+
except ImportError:
|
|
16
|
+
_version_not_supported = True
|
|
17
|
+
|
|
18
|
+
if _version_not_supported:
|
|
19
|
+
raise RuntimeError(
|
|
20
|
+
f'The grpc package installed is at version {GRPC_VERSION},'
|
|
21
|
+
+ f' but the generated code in device_pb2_grpc.py depends on'
|
|
22
|
+
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
|
|
23
|
+
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
|
|
24
|
+
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DeviceServiceStub(object):
|
|
29
|
+
"""Missing associated documentation comment in .proto file."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, channel):
|
|
32
|
+
"""Constructor.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
channel: A grpc.Channel.
|
|
36
|
+
"""
|
|
37
|
+
self.Register = channel.unary_unary(
|
|
38
|
+
'/device.DeviceService/Register',
|
|
39
|
+
request_serializer=device__pb2.RegisterRequest.SerializeToString,
|
|
40
|
+
response_deserializer=device__pb2.RegisterResponse.FromString,
|
|
41
|
+
_registered_method=True)
|
|
42
|
+
self.CommandStream = channel.stream_stream(
|
|
43
|
+
'/device.DeviceService/CommandStream',
|
|
44
|
+
request_serializer=device__pb2.CommandAck.SerializeToString,
|
|
45
|
+
response_deserializer=device__pb2.CommandRequest.FromString,
|
|
46
|
+
_registered_method=True)
|
|
47
|
+
self.ReceiveDeviceMessage = channel.unary_unary(
|
|
48
|
+
'/device.DeviceService/ReceiveDeviceMessage',
|
|
49
|
+
request_serializer=device__pb2.DeviceMessage.SerializeToString,
|
|
50
|
+
response_deserializer=device__pb2.DeviceMessageResponse.FromString,
|
|
51
|
+
_registered_method=True)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class DeviceServiceServicer(object):
|
|
55
|
+
"""Missing associated documentation comment in .proto file."""
|
|
56
|
+
|
|
57
|
+
def Register(self, request, context):
|
|
58
|
+
"""Missing associated documentation comment in .proto file."""
|
|
59
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
60
|
+
context.set_details('Method not implemented!')
|
|
61
|
+
raise NotImplementedError('Method not implemented!')
|
|
62
|
+
|
|
63
|
+
def CommandStream(self, request_iterator, context):
|
|
64
|
+
"""Missing associated documentation comment in .proto file."""
|
|
65
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
66
|
+
context.set_details('Method not implemented!')
|
|
67
|
+
raise NotImplementedError('Method not implemented!')
|
|
68
|
+
|
|
69
|
+
def ReceiveDeviceMessage(self, request, context):
|
|
70
|
+
"""Missing associated documentation comment in .proto file."""
|
|
71
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
72
|
+
context.set_details('Method not implemented!')
|
|
73
|
+
raise NotImplementedError('Method not implemented!')
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def add_DeviceServiceServicer_to_server(servicer, server):
|
|
77
|
+
rpc_method_handlers = {
|
|
78
|
+
'Register': grpc.unary_unary_rpc_method_handler(
|
|
79
|
+
servicer.Register,
|
|
80
|
+
request_deserializer=device__pb2.RegisterRequest.FromString,
|
|
81
|
+
response_serializer=device__pb2.RegisterResponse.SerializeToString,
|
|
82
|
+
),
|
|
83
|
+
'CommandStream': grpc.stream_stream_rpc_method_handler(
|
|
84
|
+
servicer.CommandStream,
|
|
85
|
+
request_deserializer=device__pb2.CommandAck.FromString,
|
|
86
|
+
response_serializer=device__pb2.CommandRequest.SerializeToString,
|
|
87
|
+
),
|
|
88
|
+
'ReceiveDeviceMessage': grpc.unary_unary_rpc_method_handler(
|
|
89
|
+
servicer.ReceiveDeviceMessage,
|
|
90
|
+
request_deserializer=device__pb2.DeviceMessage.FromString,
|
|
91
|
+
response_serializer=device__pb2.DeviceMessageResponse.SerializeToString,
|
|
92
|
+
),
|
|
93
|
+
}
|
|
94
|
+
generic_handler = grpc.method_handlers_generic_handler(
|
|
95
|
+
'device.DeviceService', rpc_method_handlers)
|
|
96
|
+
server.add_generic_rpc_handlers((generic_handler,))
|
|
97
|
+
server.add_registered_method_handlers('device.DeviceService', rpc_method_handlers)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# This class is part of an EXPERIMENTAL API.
|
|
101
|
+
class DeviceService(object):
|
|
102
|
+
"""Missing associated documentation comment in .proto file."""
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def Register(request,
|
|
106
|
+
target,
|
|
107
|
+
options=(),
|
|
108
|
+
channel_credentials=None,
|
|
109
|
+
call_credentials=None,
|
|
110
|
+
insecure=False,
|
|
111
|
+
compression=None,
|
|
112
|
+
wait_for_ready=None,
|
|
113
|
+
timeout=None,
|
|
114
|
+
metadata=None):
|
|
115
|
+
return grpc.experimental.unary_unary(
|
|
116
|
+
request,
|
|
117
|
+
target,
|
|
118
|
+
'/device.DeviceService/Register',
|
|
119
|
+
device__pb2.RegisterRequest.SerializeToString,
|
|
120
|
+
device__pb2.RegisterResponse.FromString,
|
|
121
|
+
options,
|
|
122
|
+
channel_credentials,
|
|
123
|
+
insecure,
|
|
124
|
+
call_credentials,
|
|
125
|
+
compression,
|
|
126
|
+
wait_for_ready,
|
|
127
|
+
timeout,
|
|
128
|
+
metadata,
|
|
129
|
+
_registered_method=True)
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def CommandStream(request_iterator,
|
|
133
|
+
target,
|
|
134
|
+
options=(),
|
|
135
|
+
channel_credentials=None,
|
|
136
|
+
call_credentials=None,
|
|
137
|
+
insecure=False,
|
|
138
|
+
compression=None,
|
|
139
|
+
wait_for_ready=None,
|
|
140
|
+
timeout=None,
|
|
141
|
+
metadata=None):
|
|
142
|
+
return grpc.experimental.stream_stream(
|
|
143
|
+
request_iterator,
|
|
144
|
+
target,
|
|
145
|
+
'/device.DeviceService/CommandStream',
|
|
146
|
+
device__pb2.CommandAck.SerializeToString,
|
|
147
|
+
device__pb2.CommandRequest.FromString,
|
|
148
|
+
options,
|
|
149
|
+
channel_credentials,
|
|
150
|
+
insecure,
|
|
151
|
+
call_credentials,
|
|
152
|
+
compression,
|
|
153
|
+
wait_for_ready,
|
|
154
|
+
timeout,
|
|
155
|
+
metadata,
|
|
156
|
+
_registered_method=True)
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def ReceiveDeviceMessage(request,
|
|
160
|
+
target,
|
|
161
|
+
options=(),
|
|
162
|
+
channel_credentials=None,
|
|
163
|
+
call_credentials=None,
|
|
164
|
+
insecure=False,
|
|
165
|
+
compression=None,
|
|
166
|
+
wait_for_ready=None,
|
|
167
|
+
timeout=None,
|
|
168
|
+
metadata=None):
|
|
169
|
+
return grpc.experimental.unary_unary(
|
|
170
|
+
request,
|
|
171
|
+
target,
|
|
172
|
+
'/device.DeviceService/ReceiveDeviceMessage',
|
|
173
|
+
device__pb2.DeviceMessage.SerializeToString,
|
|
174
|
+
device__pb2.DeviceMessageResponse.FromString,
|
|
175
|
+
options,
|
|
176
|
+
channel_credentials,
|
|
177
|
+
insecure,
|
|
178
|
+
call_credentials,
|
|
179
|
+
compression,
|
|
180
|
+
wait_for_ready,
|
|
181
|
+
timeout,
|
|
182
|
+
metadata,
|
|
183
|
+
_registered_method=True)
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# action_item.py
|
|
2
|
+
from dataclasses import dataclass, asdict
|
|
3
|
+
from typing import Dict, Any
|
|
4
|
+
@dataclass
|
|
5
|
+
class ActionItem:
|
|
6
|
+
name: str # 动作名称
|
|
7
|
+
command_type: str # 动作唯一标识
|
|
8
|
+
description: str # 描述
|
|
9
|
+
params: Dict[str, Any] # JSON Schema 或简单 dict 描述
|
|
10
|
+
|
|
11
|
+
def dict(self) -> Dict[str, Any]:
|
|
12
|
+
return asdict(self)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# device_status.py
|
|
2
|
+
from typing import TypedDict, Optional
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
class DeviceStatus(TypedDict, total=False):
|
|
6
|
+
"""设备状态固定格式"""
|
|
7
|
+
is_lock: Optional[int] # 必填
|
|
8
|
+
heartbeat: Optional[int] # 必填
|
|
9
|
+
battery: Optional[float] # 必填
|
|
10
|
+
airspeed: Optional[float] # 必填
|
|
11
|
+
groundspeed: Optional[float] # 必填
|
|
12
|
+
yaw_degrees: Optional[float] # 必填
|
|
13
|
+
roll: Optional[float] # 必填
|
|
14
|
+
pitch: Optional[float] # 必填
|
|
15
|
+
yaw: Optional[float] # 必填
|
|
16
|
+
lat: Optional[float] # 必填
|
|
17
|
+
lon: Optional[float] # 必填
|
|
18
|
+
alt: Optional[float] # 必填
|
|
19
|
+
vzspeed: Optional[float] # 必填
|
|
20
|
+
height: Optional[float] # 必填
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MessageType(Enum):
|
|
24
|
+
TEXT = "text" # 纯文本消息
|
|
25
|
+
IMAGE = "image" # 图像消息
|
|
26
|
+
VIDEO_URL = "video_url" # 视频流URL
|
|
27
|
+
PROGRESS = "progress" # 进度更新
|
|
28
|
+
STATUS = "status" # 状态更新
|
|
29
|
+
|
|
30
|
+
#消息级别: failed, warning, info, success
|
|
31
|
+
class MessageLevel(Enum):
|
|
32
|
+
FAILED = "failed"
|
|
33
|
+
WARNING = "warning"
|
|
34
|
+
INFO = "info"
|
|
35
|
+
SUCCESS = "success"
|