@thejrsoft/subway-protocol 1.3.0
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.
- package/ACK_MESSAGES_IMPLEMENTATION_SUMMARY.md +128 -0
- package/ACK_MESSAGE_DESIGN.md +457 -0
- package/CHANGELOG.md +58 -0
- package/COMMAND_VALIDATION_RULES.md +178 -0
- package/DOCUMENTATION_REORGANIZATION_SUMMARY.md +81 -0
- package/DOCUMENTATION_STRUCTURE.md +106 -0
- package/GATEWAY_MIGRATION_GUIDE.md +130 -0
- package/GATEWAY_PROTOCOL_COMPARISON.md +216 -0
- package/INTEGRATION_GUIDE.md +190 -0
- package/OPTIONAL_FIELDS_WITHOUT_DEFAULTS.md +97 -0
- package/PROTOCOL_UTILS_USAGE.md +278 -0
- package/README.md +237 -0
- package/TYPE_FIXES_SUMMARY.md +210 -0
- package/UPDATE_ENUM_VALUES.md +139 -0
- package/dist/asyncapi-sync.d.ts +47 -0
- package/dist/asyncapi-sync.d.ts.map +1 -0
- package/dist/asyncapi-sync.js +85 -0
- package/dist/asyncapi-sync.js.map +1 -0
- package/dist/command-factory.d.ts +62 -0
- package/dist/command-factory.d.ts.map +1 -0
- package/dist/command-factory.js +137 -0
- package/dist/command-factory.js.map +1 -0
- package/dist/command-types.d.ts +27 -0
- package/dist/command-types.d.ts.map +1 -0
- package/dist/command-types.js +31 -0
- package/dist/command-types.js.map +1 -0
- package/dist/index.d.ts +403 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +413 -0
- package/dist/index.js.map +1 -0
- package/dist/message-validator.d.ts +102 -0
- package/dist/message-validator.d.ts.map +1 -0
- package/dist/message-validator.js +640 -0
- package/dist/message-validator.js.map +1 -0
- package/dist/protocol-utils.d.ts +108 -0
- package/dist/protocol-utils.d.ts.map +1 -0
- package/dist/protocol-utils.js +293 -0
- package/dist/protocol-utils.js.map +1 -0
- package/docs/01-protocol/README.md +45 -0
- package/docs/01-protocol/design-rationale.md +198 -0
- package/docs/01-protocol/message-types.md +669 -0
- package/docs/01-protocol/specification.md +1466 -0
- package/docs/02-commands/README.md +56 -0
- package/docs/02-commands/batch-command.md +435 -0
- package/docs/02-commands/complex-command.md +537 -0
- package/docs/02-commands/simple-command.md +332 -0
- package/docs/02-commands/typed-commands.md +362 -0
- package/docs/03-architecture/README.md +66 -0
- package/docs/03-architecture/device-protocol.md +430 -0
- package/docs/03-architecture/edge-proxy.md +727 -0
- package/docs/03-architecture/routing-flow.md +893 -0
- package/docs/04-integration/README.md +144 -0
- package/docs/04-integration/backend-guide.md +551 -0
- package/docs/04-integration/edge-guide.md +684 -0
- package/docs/04-integration/gateway-guide.md +180 -0
- package/docs/04-integration/migration-guide.md +226 -0
- package/docs/05-examples/README.md +141 -0
- package/docs/05-examples/progress-update-examples.md +757 -0
- package/docs/06-reference/README.md +67 -0
- package/docs/06-reference/api.md +572 -0
- package/docs/06-reference/faq.md +302 -0
- package/docs/06-reference/glossary.md +232 -0
- package/examples/backend-upgrade.ts +279 -0
- package/examples/edge-multi-device.ts +513 -0
- package/examples/gateway-upgrade.ts +150 -0
- package/examples/protocol-implementation.ts +715 -0
- package/package.json +48 -0
- package/scripts/validate-asyncapi.ts +78 -0
- package/src/__tests__/protocol.test.ts +297 -0
- package/src/asyncapi-sync.ts +84 -0
- package/src/command-factory.ts +183 -0
- package/src/command-types.ts +72 -0
- package/src/edge-proxy.ts +494 -0
- package/src/gateway-extensions.ts +278 -0
- package/src/index.ts +792 -0
- package/src/message-validator.ts +726 -0
- package/src/protocol-utils.ts +355 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# 集成指南
|
|
2
|
+
|
|
3
|
+
本目录包含将 JRSoft Subway 协议集成到各个组件的详细指南。
|
|
4
|
+
|
|
5
|
+
## 📚 文档列表
|
|
6
|
+
|
|
7
|
+
### [Gateway 集成指南](./gateway-guide.md)
|
|
8
|
+
Gateway 组件的集成说明:
|
|
9
|
+
- WebSocket 服务器配置
|
|
10
|
+
- 客户端连接管理
|
|
11
|
+
- 消息路由实现
|
|
12
|
+
- 性能优化建议
|
|
13
|
+
|
|
14
|
+
### [Backend 集成指南](./backend-guide.md)
|
|
15
|
+
Backend 服务的集成说明:
|
|
16
|
+
- FastAPI WebSocket 客户端
|
|
17
|
+
- 命令发送和响应处理
|
|
18
|
+
- 异步任务管理
|
|
19
|
+
- 错误处理策略
|
|
20
|
+
|
|
21
|
+
### [Edge 集成指南](./edge-guide.md)
|
|
22
|
+
Edge 节点的集成说明:
|
|
23
|
+
- 双向代理实现
|
|
24
|
+
- 设备连接管理
|
|
25
|
+
- 消息转发逻辑
|
|
26
|
+
- 故障恢复机制
|
|
27
|
+
|
|
28
|
+
### [迁移指南](./migration-guide.md)
|
|
29
|
+
从旧系统迁移到新协议:
|
|
30
|
+
- 版本兼容性说明
|
|
31
|
+
- 迁移步骤详解
|
|
32
|
+
- 常见问题解决
|
|
33
|
+
- 回滚方案
|
|
34
|
+
|
|
35
|
+
## 🚀 快速开始
|
|
36
|
+
|
|
37
|
+
根据您的角色选择相应的指南:
|
|
38
|
+
|
|
39
|
+
- **Gateway 开发者** → 阅读 [Gateway 集成指南](./gateway-guide.md)
|
|
40
|
+
- **Backend 开发者** → 阅读 [Backend 集成指南](./backend-guide.md)
|
|
41
|
+
- **Edge 开发者** → 阅读 [Edge 集成指南](./edge-guide.md)
|
|
42
|
+
- **系统升级** → 阅读 [迁移指南](./migration-guide.md)
|
|
43
|
+
|
|
44
|
+
## 🏗️ 集成架构
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
┌─────────────┐
|
|
48
|
+
│ Backend │ ← HTTP/WebSocket 混合模式
|
|
49
|
+
└──────┬──────┘
|
|
50
|
+
│
|
|
51
|
+
┌──────▼──────┐
|
|
52
|
+
│ Gateway │ ← 中心路由节点
|
|
53
|
+
└──────┬──────┘
|
|
54
|
+
│
|
|
55
|
+
┌──────▼──────┐
|
|
56
|
+
│ Edge │ ← 设备代理节点
|
|
57
|
+
└──────┬──────┘
|
|
58
|
+
│
|
|
59
|
+
┌──────▼──────┐
|
|
60
|
+
│ Device │ ← 终端设备
|
|
61
|
+
└─────────────┘
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 🔑 核心集成要点
|
|
65
|
+
|
|
66
|
+
### 1. 协议版本
|
|
67
|
+
- 当前版本:1.0
|
|
68
|
+
- 所有组件必须使用相同版本
|
|
69
|
+
- 版本信息在每个消息中携带
|
|
70
|
+
|
|
71
|
+
### 2. 连接管理
|
|
72
|
+
- 自动重连机制
|
|
73
|
+
- 心跳保活(30秒间隔)
|
|
74
|
+
- 优雅断开处理
|
|
75
|
+
|
|
76
|
+
### 3. 消息验证
|
|
77
|
+
- 使用提供的验证函数
|
|
78
|
+
- 严格的类型检查
|
|
79
|
+
- 详细的错误信息
|
|
80
|
+
|
|
81
|
+
### 4. 性能考虑
|
|
82
|
+
- 消息批处理
|
|
83
|
+
- 连接池复用
|
|
84
|
+
- 异步处理模式
|
|
85
|
+
|
|
86
|
+
## 📋 集成检查清单
|
|
87
|
+
|
|
88
|
+
### Gateway
|
|
89
|
+
- [ ] WebSocket 服务器启动(端口 18081)
|
|
90
|
+
- [ ] 客户端注册处理
|
|
91
|
+
- [ ] 消息路由实现
|
|
92
|
+
- [ ] 心跳机制
|
|
93
|
+
- [ ] 错误处理
|
|
94
|
+
|
|
95
|
+
### Backend
|
|
96
|
+
- [ ] WebSocket 客户端连接
|
|
97
|
+
- [ ] 命令发送接口
|
|
98
|
+
- [ ] 响应处理逻辑
|
|
99
|
+
- [ ] 超时处理
|
|
100
|
+
- [ ] 日志记录
|
|
101
|
+
|
|
102
|
+
### Edge
|
|
103
|
+
- [ ] Gateway 连接管理
|
|
104
|
+
- [ ] 设备连接接受
|
|
105
|
+
- [ ] 双向消息转发
|
|
106
|
+
- [ ] 设备状态跟踪
|
|
107
|
+
- [ ] 故障恢复
|
|
108
|
+
|
|
109
|
+
## 🛠️ 调试工具
|
|
110
|
+
|
|
111
|
+
### 消息调试
|
|
112
|
+
```bash
|
|
113
|
+
# 监听 WebSocket 消息
|
|
114
|
+
wscat -c ws://localhost:18081
|
|
115
|
+
|
|
116
|
+
# 发送测试消息
|
|
117
|
+
echo '{"type":"heartbeat","clientId":"test","timestamp":"2024-01-20T10:00:00Z","version":"1.0"}' | wscat -c ws://localhost:18081
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 日志查看
|
|
121
|
+
```bash
|
|
122
|
+
# Gateway 日志
|
|
123
|
+
tail -f /var/log/jrsoft-gateway/gateway.log
|
|
124
|
+
|
|
125
|
+
# Backend 日志
|
|
126
|
+
tail -f /var/log/jrsoft-backend/backend.log
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## 📊 监控指标
|
|
130
|
+
|
|
131
|
+
建议监控的关键指标:
|
|
132
|
+
|
|
133
|
+
1. **连接数** - 活跃的 WebSocket 连接数
|
|
134
|
+
2. **消息吞吐** - 每秒处理的消息数
|
|
135
|
+
3. **响应时间** - 命令响应的平均时间
|
|
136
|
+
4. **错误率** - 失败消息的比例
|
|
137
|
+
5. **重连次数** - 连接断开和重连的频率
|
|
138
|
+
|
|
139
|
+
## 🔗 相关资源
|
|
140
|
+
|
|
141
|
+
- [协议规范](../01-protocol/) - 了解协议细节
|
|
142
|
+
- [命令系统](../02-commands/) - 了解命令类型
|
|
143
|
+
- [架构文档](../03-architecture/) - 了解系统架构
|
|
144
|
+
- [示例代码](../05-examples/) - 查看实现示例
|
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
# Backend 集成指南
|
|
2
|
+
|
|
3
|
+
本文档说明如何在 Backend 服务中集成 JRSoft Subway WebSocket 协议。
|
|
4
|
+
|
|
5
|
+
## 概述
|
|
6
|
+
|
|
7
|
+
Backend 作为业务逻辑层,通过 WebSocket 连接到 Gateway 发送命令并接收响应。Backend 使用 FastAPI 框架,支持异步操作。
|
|
8
|
+
|
|
9
|
+
## 集成步骤
|
|
10
|
+
|
|
11
|
+
### 1. 安装依赖
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install websockets aiohttp pydantic
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### 2. WebSocket 客户端实现
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import asyncio
|
|
21
|
+
import json
|
|
22
|
+
from typing import Dict, Optional, Callable
|
|
23
|
+
import websockets
|
|
24
|
+
from datetime import datetime
|
|
25
|
+
|
|
26
|
+
class GatewayClient:
|
|
27
|
+
def __init__(self, gateway_url: str, client_id: str):
|
|
28
|
+
self.gateway_url = gateway_url
|
|
29
|
+
self.client_id = client_id
|
|
30
|
+
self.websocket: Optional[websockets.WebSocketClientProtocol] = None
|
|
31
|
+
self.pending_requests: Dict[str, asyncio.Future] = {}
|
|
32
|
+
self.message_handlers: Dict[str, Callable] = {}
|
|
33
|
+
|
|
34
|
+
async def connect(self):
|
|
35
|
+
"""连接到 Gateway"""
|
|
36
|
+
self.websocket = await websockets.connect(self.gateway_url)
|
|
37
|
+
|
|
38
|
+
# 发送注册消息
|
|
39
|
+
await self.register()
|
|
40
|
+
|
|
41
|
+
# 启动消息接收循环
|
|
42
|
+
asyncio.create_task(self.receive_messages())
|
|
43
|
+
|
|
44
|
+
# 启动心跳
|
|
45
|
+
asyncio.create_task(self.heartbeat_loop())
|
|
46
|
+
|
|
47
|
+
async def register(self):
|
|
48
|
+
"""注册客户端"""
|
|
49
|
+
register_message = {
|
|
50
|
+
"type": "REGISTER",
|
|
51
|
+
"clientId": self.client_id,
|
|
52
|
+
"clientType": "BACKEND",
|
|
53
|
+
"clientInfo": {
|
|
54
|
+
"name": "JRSoft Backend Service",
|
|
55
|
+
"version": "1.0.0",
|
|
56
|
+
"capabilities": ["command", "query", "monitor"]
|
|
57
|
+
},
|
|
58
|
+
"timestamp": datetime.utcnow().isoformat() + "Z",
|
|
59
|
+
"version": "1.0"
|
|
60
|
+
}
|
|
61
|
+
await self.websocket.send(json.dumps(register_message))
|
|
62
|
+
|
|
63
|
+
async def heartbeat_loop(self):
|
|
64
|
+
"""心跳循环"""
|
|
65
|
+
while True:
|
|
66
|
+
try:
|
|
67
|
+
if self.websocket and not self.websocket.closed:
|
|
68
|
+
heartbeat = {
|
|
69
|
+
"type": "HEARTBEAT",
|
|
70
|
+
"clientId": self.client_id,
|
|
71
|
+
"timestamp": datetime.utcnow().isoformat() + "Z",
|
|
72
|
+
"version": "1.0"
|
|
73
|
+
}
|
|
74
|
+
await self.websocket.send(json.dumps(heartbeat))
|
|
75
|
+
await asyncio.sleep(30) # 30秒心跳间隔
|
|
76
|
+
except Exception as e:
|
|
77
|
+
print(f"Heartbeat error: {e}")
|
|
78
|
+
await self.reconnect()
|
|
79
|
+
|
|
80
|
+
async def receive_messages(self):
|
|
81
|
+
"""接收消息循环"""
|
|
82
|
+
try:
|
|
83
|
+
async for message in self.websocket:
|
|
84
|
+
data = json.loads(message)
|
|
85
|
+
await self.handle_message(data)
|
|
86
|
+
except websockets.exceptions.ConnectionClosed:
|
|
87
|
+
await self.reconnect()
|
|
88
|
+
|
|
89
|
+
async def handle_message(self, message: dict):
|
|
90
|
+
"""处理接收到的消息"""
|
|
91
|
+
message_type = message.get("type")
|
|
92
|
+
|
|
93
|
+
if message_type == "Ucommand_response":
|
|
94
|
+
# 处理命令响应
|
|
95
|
+
request_ref = message.get("requestRef")
|
|
96
|
+
if request_ref in self.pending_requests:
|
|
97
|
+
future = self.pending_requests.pop(request_ref)
|
|
98
|
+
future.set_result(message)
|
|
99
|
+
|
|
100
|
+
elif message_type == "Uprogress_update":
|
|
101
|
+
# 处理进度更新
|
|
102
|
+
handler = self.message_handlers.get("Uprogress_update")
|
|
103
|
+
if handler:
|
|
104
|
+
await handler(message)
|
|
105
|
+
|
|
106
|
+
elif message_type == "Uerror":
|
|
107
|
+
# 处理错误消息
|
|
108
|
+
request_ref = message.get("requestRef")
|
|
109
|
+
if request_ref in self.pending_requests:
|
|
110
|
+
future = self.pending_requests.pop(request_ref)
|
|
111
|
+
future.set_exception(Exception(message.get("message")))
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 3. 命令发送接口
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
import uuid
|
|
118
|
+
from enum import Enum
|
|
119
|
+
from typing import Any, Dict, Optional
|
|
120
|
+
|
|
121
|
+
class Priority(Enum):
|
|
122
|
+
LOW = "low"
|
|
123
|
+
NORMAL = "Unormal"
|
|
124
|
+
HIGH = "Uhigh"
|
|
125
|
+
URGENT = "urgent"
|
|
126
|
+
|
|
127
|
+
class CommandSender:
|
|
128
|
+
def __init__(self, gateway_client: GatewayClient):
|
|
129
|
+
self.gateway_client = gateway_client
|
|
130
|
+
|
|
131
|
+
async def send_command(
|
|
132
|
+
self,
|
|
133
|
+
target_client_id: str,
|
|
134
|
+
command: Dict[str, Any],
|
|
135
|
+
priority: Priority = Priority.NORMAL,
|
|
136
|
+
timeout: int = 5000,
|
|
137
|
+
callback: Optional[str] = None
|
|
138
|
+
) -> Dict[str, Any]:
|
|
139
|
+
"""发送命令并等待响应"""
|
|
140
|
+
request_ref = f"cmd-{uuid.uuid4()}"
|
|
141
|
+
|
|
142
|
+
message = {
|
|
143
|
+
"type": "COMMAND",
|
|
144
|
+
"requestRef": request_ref,
|
|
145
|
+
"targetClientId": target_client_id,
|
|
146
|
+
"command": command,
|
|
147
|
+
"priority": priority.value,
|
|
148
|
+
"timeout": timeout,
|
|
149
|
+
"timestamp": datetime.utcnow().isoformat() + "Z",
|
|
150
|
+
"version": "1.0"
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if callback:
|
|
154
|
+
message["callback"] = callback
|
|
155
|
+
|
|
156
|
+
# 创建 Future 用于等待响应
|
|
157
|
+
future = asyncio.Future()
|
|
158
|
+
self.gateway_client.pending_requests[request_ref] = future
|
|
159
|
+
|
|
160
|
+
# 发送命令
|
|
161
|
+
await self.gateway_client.websocket.send(json.dumps(message))
|
|
162
|
+
|
|
163
|
+
# 等待响应或超时
|
|
164
|
+
try:
|
|
165
|
+
response = await asyncio.wait_for(future, timeout=timeout/1000)
|
|
166
|
+
return response
|
|
167
|
+
except asyncio.TimeoutError:
|
|
168
|
+
self.gateway_client.pending_requests.pop(request_ref, None)
|
|
169
|
+
raise TimeoutError(f"Command timeout: {request_ref}")
|
|
170
|
+
|
|
171
|
+
async def send_simple_command(
|
|
172
|
+
self,
|
|
173
|
+
edge_id: str,
|
|
174
|
+
device_id: str,
|
|
175
|
+
command_code: str,
|
|
176
|
+
device_type: str,
|
|
177
|
+
operation_type: str,
|
|
178
|
+
parameters: Dict[str, Any],
|
|
179
|
+
**kwargs
|
|
180
|
+
) -> Dict[str, Any]:
|
|
181
|
+
"""发送 Simple 类型命令"""
|
|
182
|
+
command = {
|
|
183
|
+
"commandType": "SIMPLE",
|
|
184
|
+
"commandCode": command_code,
|
|
185
|
+
"deviceType": device_type,
|
|
186
|
+
"deviceId": device_id,
|
|
187
|
+
"operationType": operation_type,
|
|
188
|
+
"parameters": parameters
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
target_client_id = f"{edge_id}:{device_id}"
|
|
192
|
+
return await self.send_command(target_client_id, command, **kwargs)
|
|
193
|
+
|
|
194
|
+
async def send_batch_command(
|
|
195
|
+
self,
|
|
196
|
+
edge_id: str,
|
|
197
|
+
device_id: str,
|
|
198
|
+
command_code: str,
|
|
199
|
+
device_type: str,
|
|
200
|
+
targets: str,
|
|
201
|
+
operation_type: str,
|
|
202
|
+
parameters: Dict[str, Any],
|
|
203
|
+
**kwargs
|
|
204
|
+
) -> Dict[str, Any]:
|
|
205
|
+
"""发送 Batch 类型命令"""
|
|
206
|
+
command = {
|
|
207
|
+
"commandType": "BATCH",
|
|
208
|
+
"commandCode": command_code,
|
|
209
|
+
"deviceType": device_type,
|
|
210
|
+
"deviceId": targets, # 批量目标
|
|
211
|
+
"operationType": operation_type,
|
|
212
|
+
"parameters": {
|
|
213
|
+
**parameters,
|
|
214
|
+
"targets": targets,
|
|
215
|
+
"Ubatch": True
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
target_client_id = f"{edge_id}:{device_id}"
|
|
220
|
+
return await self.send_command(target_client_id, command, **kwargs)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 4. FastAPI 集成
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
from fastapi import FastAPI, HTTPException, WebSocket
|
|
227
|
+
from contextlib import asynccontextmanager
|
|
228
|
+
import logging
|
|
229
|
+
|
|
230
|
+
# 配置日志
|
|
231
|
+
logging.basicConfig(level=logging.INFO)
|
|
232
|
+
logger = logging.getLogger(__name__)
|
|
233
|
+
|
|
234
|
+
# 全局 Gateway 客户端
|
|
235
|
+
gateway_client: Optional[GatewayClient] = None
|
|
236
|
+
command_sender: Optional[CommandSender] = None
|
|
237
|
+
|
|
238
|
+
@asynccontextmanager
|
|
239
|
+
async def lifespan(app: FastAPI):
|
|
240
|
+
"""应用生命周期管理"""
|
|
241
|
+
global gateway_client, command_sender
|
|
242
|
+
|
|
243
|
+
# 启动时连接 Gateway
|
|
244
|
+
gateway_client = GatewayClient(
|
|
245
|
+
"ws://localhost:18081",
|
|
246
|
+
"backend-001"
|
|
247
|
+
)
|
|
248
|
+
await gateway_client.connect()
|
|
249
|
+
command_sender = CommandSender(gateway_client)
|
|
250
|
+
logger.info("Connected to Gateway")
|
|
251
|
+
|
|
252
|
+
yield
|
|
253
|
+
|
|
254
|
+
# 关闭时断开连接
|
|
255
|
+
if gateway_client.websocket:
|
|
256
|
+
await gateway_client.websocket.close()
|
|
257
|
+
logger.info("Disconnected from Gateway")
|
|
258
|
+
|
|
259
|
+
app = FastAPI(lifespan=lifespan)
|
|
260
|
+
|
|
261
|
+
# API 端点示例
|
|
262
|
+
@app.post("/api/devices/{edge_id}/{device_id}/control")
|
|
263
|
+
async def control_device(
|
|
264
|
+
edge_id: str,
|
|
265
|
+
device_id: str,
|
|
266
|
+
command_code: str,
|
|
267
|
+
parameters: Dict[str, Any]
|
|
268
|
+
):
|
|
269
|
+
"""控制设备的 API 端点"""
|
|
270
|
+
try:
|
|
271
|
+
response = await command_sender.send_simple_command(
|
|
272
|
+
edge_id=edge_id,
|
|
273
|
+
device_id=device_id,
|
|
274
|
+
command_code=command_code,
|
|
275
|
+
device_type="pillar",
|
|
276
|
+
operation_type="Uwrite",
|
|
277
|
+
parameters=parameters,
|
|
278
|
+
priority=Priority.NORMAL,
|
|
279
|
+
timeout=5000
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
if response.get("status") == "Ucompleted":
|
|
283
|
+
return {"success": True, "data": response.get("result")}
|
|
284
|
+
else:
|
|
285
|
+
raise HTTPException(status_code=500, detail="Command failed")
|
|
286
|
+
|
|
287
|
+
except TimeoutError:
|
|
288
|
+
raise HTTPException(status_code=504, detail="Command timeout")
|
|
289
|
+
except Exception as e:
|
|
290
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
291
|
+
|
|
292
|
+
@app.websocket("/ws/monitor/{request_id}")
|
|
293
|
+
async def monitor_progress(websocket: WebSocket, request_id: str):
|
|
294
|
+
"""监控命令执行进度的 WebSocket 端点"""
|
|
295
|
+
await websocket.accept()
|
|
296
|
+
|
|
297
|
+
progress_updates = []
|
|
298
|
+
|
|
299
|
+
async def progress_handler(message):
|
|
300
|
+
if message.get("requestRef") == request_id:
|
|
301
|
+
progress_updates.append(message)
|
|
302
|
+
await websocket.send_json(message)
|
|
303
|
+
|
|
304
|
+
# 注册进度处理器
|
|
305
|
+
gateway_client.message_handlers["Uprogress_update"] = progress_handler
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
# 保持连接直到客户端断开
|
|
309
|
+
while True:
|
|
310
|
+
await websocket.receive_text()
|
|
311
|
+
except:
|
|
312
|
+
pass
|
|
313
|
+
finally:
|
|
314
|
+
# 清理处理器
|
|
315
|
+
gateway_client.message_handlers.pop("Uprogress_update", None)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### 5. 异步任务处理
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
from typing import List
|
|
322
|
+
import asyncio
|
|
323
|
+
|
|
324
|
+
class BatchCommandExecutor:
|
|
325
|
+
"""批量命令执行器"""
|
|
326
|
+
|
|
327
|
+
def __init__(self, command_sender: CommandSender):
|
|
328
|
+
self.command_sender = command_sender
|
|
329
|
+
|
|
330
|
+
async def execute_on_multiple_devices(
|
|
331
|
+
self,
|
|
332
|
+
devices: List[Dict[str, str]],
|
|
333
|
+
command_code: str,
|
|
334
|
+
parameters: Dict[str, Any]
|
|
335
|
+
) -> List[Dict[str, Any]]:
|
|
336
|
+
"""在多个设备上执行命令"""
|
|
337
|
+
tasks = []
|
|
338
|
+
|
|
339
|
+
for device in devices:
|
|
340
|
+
task = self.command_sender.send_simple_command(
|
|
341
|
+
edge_id=device["edge_id"],
|
|
342
|
+
device_id=device["device_id"],
|
|
343
|
+
command_code=command_code,
|
|
344
|
+
device_type=device["device_type"],
|
|
345
|
+
operation_type="Uwrite",
|
|
346
|
+
parameters=parameters
|
|
347
|
+
)
|
|
348
|
+
tasks.append(task)
|
|
349
|
+
|
|
350
|
+
# 并发执行所有命令
|
|
351
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
352
|
+
|
|
353
|
+
# 处理结果
|
|
354
|
+
processed_results = []
|
|
355
|
+
for i, result in enumerate(results):
|
|
356
|
+
if isinstance(result, Exception):
|
|
357
|
+
processed_results.append({
|
|
358
|
+
"Udevice": devices[i],
|
|
359
|
+
"success": False,
|
|
360
|
+
"Uerror": str(result)
|
|
361
|
+
})
|
|
362
|
+
else:
|
|
363
|
+
processed_results.append({
|
|
364
|
+
"Udevice": devices[i],
|
|
365
|
+
"success": True,
|
|
366
|
+
"result": result
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
return processed_results
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### 6. 错误处理和重试
|
|
373
|
+
|
|
374
|
+
```python
|
|
375
|
+
from typing import TypeVar, Callable
|
|
376
|
+
import asyncio
|
|
377
|
+
|
|
378
|
+
T = TypeVar('T')
|
|
379
|
+
|
|
380
|
+
async def retry_async(
|
|
381
|
+
func: Callable[..., T],
|
|
382
|
+
max_attempts: int = 3,
|
|
383
|
+
delay: float = 1.0,
|
|
384
|
+
backoff: float = 2.0,
|
|
385
|
+
exceptions: tuple = (Exception,)
|
|
386
|
+
) -> T:
|
|
387
|
+
"""异步重试装饰器"""
|
|
388
|
+
attempt = 0
|
|
389
|
+
current_delay = delay
|
|
390
|
+
|
|
391
|
+
while attempt < max_attempts:
|
|
392
|
+
try:
|
|
393
|
+
return await func()
|
|
394
|
+
except exceptions as e:
|
|
395
|
+
attempt += 1
|
|
396
|
+
if attempt >= max_attempts:
|
|
397
|
+
raise
|
|
398
|
+
|
|
399
|
+
logger.warning(f"Attempt {attempt} failed: {e}. Retrying in {current_delay}s...")
|
|
400
|
+
await asyncio.sleep(current_delay)
|
|
401
|
+
current_delay *= backoff
|
|
402
|
+
|
|
403
|
+
# 使用示例
|
|
404
|
+
async def send_command_with_retry(target_client_id: str, command: dict):
|
|
405
|
+
return await retry_async(
|
|
406
|
+
lambda: command_sender.send_command(target_client_id, command),
|
|
407
|
+
max_attempts=3,
|
|
408
|
+
delay=1.0,
|
|
409
|
+
exceptions=(TimeoutError, ConnectionError)
|
|
410
|
+
)
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## 最佳实践
|
|
414
|
+
|
|
415
|
+
### 1. 连接管理
|
|
416
|
+
|
|
417
|
+
```python
|
|
418
|
+
class ConnectionManager:
|
|
419
|
+
"""连接管理器,处理断线重连"""
|
|
420
|
+
|
|
421
|
+
def __init__(self, gateway_client: GatewayClient):
|
|
422
|
+
self.gateway_client = gateway_client
|
|
423
|
+
self.reconnect_delay = 5
|
|
424
|
+
self.max_reconnect_delay = 60
|
|
425
|
+
|
|
426
|
+
async def ensure_connected(self):
|
|
427
|
+
"""确保连接可用"""
|
|
428
|
+
if not self.gateway_client.websocket or self.gateway_client.websocket.closed:
|
|
429
|
+
await self.reconnect()
|
|
430
|
+
|
|
431
|
+
async def reconnect(self):
|
|
432
|
+
"""重连逻辑"""
|
|
433
|
+
delay = self.reconnect_delay
|
|
434
|
+
|
|
435
|
+
while True:
|
|
436
|
+
try:
|
|
437
|
+
logger.info(f"Attempting to reconnect...")
|
|
438
|
+
await self.gateway_client.connect()
|
|
439
|
+
logger.info("Reconnected successfully")
|
|
440
|
+
self.reconnect_delay = 5 # 重置延迟
|
|
441
|
+
break
|
|
442
|
+
except Exception as e:
|
|
443
|
+
logger.error(f"Reconnection failed: {e}")
|
|
444
|
+
await asyncio.sleep(delay)
|
|
445
|
+
delay = min(delay * 2, self.max_reconnect_delay)
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### 2. 性能优化
|
|
449
|
+
|
|
450
|
+
```python
|
|
451
|
+
# 使用连接池
|
|
452
|
+
class GatewayConnectionPool:
|
|
453
|
+
def __init__(self, size: int = 5):
|
|
454
|
+
self.connections: List[GatewayClient] = []
|
|
455
|
+
self.size = size
|
|
456
|
+
self.current = 0
|
|
457
|
+
|
|
458
|
+
async def initialize(self, gateway_url: str, client_id_prefix: str):
|
|
459
|
+
for i in range(self.size):
|
|
460
|
+
client = GatewayClient(
|
|
461
|
+
gateway_url,
|
|
462
|
+
f"{client_id_prefix}-{i}"
|
|
463
|
+
)
|
|
464
|
+
await client.connect()
|
|
465
|
+
self.connections.append(client)
|
|
466
|
+
|
|
467
|
+
def get_connection(self) -> GatewayClient:
|
|
468
|
+
"""轮询获取连接"""
|
|
469
|
+
conn = self.connections[self.current]
|
|
470
|
+
self.current = (self.current + 1) % self.size
|
|
471
|
+
return conn
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### 3. 监控和日志
|
|
475
|
+
|
|
476
|
+
```python
|
|
477
|
+
import time
|
|
478
|
+
from prometheus_client import Counter, Histogram, Gauge
|
|
479
|
+
|
|
480
|
+
# Prometheus 指标
|
|
481
|
+
command_sent_total = Counter('backend_command_sent_total', 'Total commands sent')
|
|
482
|
+
command_duration = Histogram('backend_command_duration_seconds', 'Command execution duration')
|
|
483
|
+
active_connections = Gauge('backend_active_connections', 'Number of active Gateway connections')
|
|
484
|
+
|
|
485
|
+
class MonitoredCommandSender(CommandSender):
|
|
486
|
+
async def send_command(self, *args, **kwargs):
|
|
487
|
+
start_time = time.time()
|
|
488
|
+
command_sent_total.inc()
|
|
489
|
+
|
|
490
|
+
try:
|
|
491
|
+
result = await super().send_command(*args, **kwargs)
|
|
492
|
+
command_duration.observe(time.time() - start_time)
|
|
493
|
+
return result
|
|
494
|
+
except Exception as e:
|
|
495
|
+
command_duration.observe(time.time() - start_time)
|
|
496
|
+
raise
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
## 故障排查
|
|
500
|
+
|
|
501
|
+
### 常见问题
|
|
502
|
+
|
|
503
|
+
1. **连接失败**
|
|
504
|
+
```python
|
|
505
|
+
# 检查 Gateway 是否运行
|
|
506
|
+
# 检查网络连接
|
|
507
|
+
# 验证 WebSocket URL
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
2. **命令超时**
|
|
511
|
+
```python
|
|
512
|
+
# 增加超时时间
|
|
513
|
+
# 检查目标设备是否在线
|
|
514
|
+
# 查看 Gateway 日志
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
3. **消息格式错误**
|
|
518
|
+
```python
|
|
519
|
+
# 使用协议提供的验证函数
|
|
520
|
+
# 检查必填字段
|
|
521
|
+
# 验证数据类型
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### 调试技巧
|
|
525
|
+
|
|
526
|
+
```python
|
|
527
|
+
# 启用详细日志
|
|
528
|
+
logging.getLogger("websockets").setLevel(logging.DEBUG)
|
|
529
|
+
|
|
530
|
+
# 消息追踪
|
|
531
|
+
class DebugGatewayClient(GatewayClient):
|
|
532
|
+
async def send(self, message: str):
|
|
533
|
+
logger.debug(f"Sending: {message}")
|
|
534
|
+
await super().send(message)
|
|
535
|
+
|
|
536
|
+
async def handle_message(self, message: dict):
|
|
537
|
+
logger.debug(f"Received: {message}")
|
|
538
|
+
await super().handle_message(message)
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
## 总结
|
|
542
|
+
|
|
543
|
+
Backend 集成的关键点:
|
|
544
|
+
|
|
545
|
+
1. **异步架构** - 充分利用 Python 的异步特性
|
|
546
|
+
2. **错误处理** - 完善的错误处理和重试机制
|
|
547
|
+
3. **性能优化** - 连接池、批量处理、并发控制
|
|
548
|
+
4. **监控日志** - 完整的监控指标和日志记录
|
|
549
|
+
5. **容错设计** - 自动重连、优雅降级
|
|
550
|
+
|
|
551
|
+
遵循这些指南,可以构建一个稳定、高效的 Backend 服务。
|