@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,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 命令类型定义 - 桥接 C# 生成的类型
|
|
3
|
+
*
|
|
4
|
+
* 这个文件作为适配层,将 C# 模型生成的 TypeScript 类型
|
|
5
|
+
* 与 WebSocket 协议层连接起来,处理字段名称差异
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { OperationType, CommandType } from './index';
|
|
9
|
+
|
|
10
|
+
// TODO: 实际使用时,从 C# 生成的类型导入
|
|
11
|
+
// import {
|
|
12
|
+
// LedSwitchCommand as CSharpLedSwitchCommand,
|
|
13
|
+
// LedSwitchParameters as CSharpLedSwitchParameters
|
|
14
|
+
// } from '@jrsoft/csharp-model/typescript-schemas';
|
|
15
|
+
|
|
16
|
+
// 示例命令参数接口
|
|
17
|
+
export interface ExampleCommandParameters {
|
|
18
|
+
exampleField: string;
|
|
19
|
+
exampleValue: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 示例命令接口(仅用于展示命令结构,不可实际使用)
|
|
23
|
+
export interface ExampleCommand {
|
|
24
|
+
commandType: CommandType; // 协议需要的字段
|
|
25
|
+
commandCode: string;
|
|
26
|
+
deviceType: string;
|
|
27
|
+
deviceId: number | number[] | string; // 支持单个设备或批量操作
|
|
28
|
+
operationType: string;
|
|
29
|
+
parameters?: ExampleCommandParameters;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// TODO: 实际使用时,从 C# 生成的类型导入更多命令
|
|
33
|
+
// import { ... } from '@jrsoft/csharp-model/typescript-schemas';
|
|
34
|
+
|
|
35
|
+
// 联合类型:所有支持的具体命令
|
|
36
|
+
export type SpecificCommand =
|
|
37
|
+
| ExampleCommand
|
|
38
|
+
// TODO: 实际使用时,这里应该列出所有从 C# 生成的命令类型
|
|
39
|
+
;
|
|
40
|
+
|
|
41
|
+
// 命令代码到类型的映射
|
|
42
|
+
export interface CommandTypeMap {
|
|
43
|
+
'ExampleCommand': ExampleCommand;
|
|
44
|
+
// TODO: 实际使用时,这里应该映射所有从 C# 生成的命令
|
|
45
|
+
// 例如: 'LedSwitch': LedSwitchCommand,
|
|
46
|
+
// 'BlockPlay': BlockPlayCommand,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 类型安全的命令创建器
|
|
50
|
+
export function createTypedCommand<T extends keyof CommandTypeMap>(
|
|
51
|
+
commandCode: T,
|
|
52
|
+
baseProps: Omit<CommandTypeMap[T], 'commandCode'>
|
|
53
|
+
): CommandTypeMap[T] {
|
|
54
|
+
return {
|
|
55
|
+
commandCode,
|
|
56
|
+
...baseProps
|
|
57
|
+
} as CommandTypeMap[T];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 示例类型守卫
|
|
61
|
+
export function isExampleCommand(cmd: any): cmd is ExampleCommand {
|
|
62
|
+
return cmd?.commandCode === 'ExampleCommand';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 通用类型守卫
|
|
66
|
+
export function isSpecificCommand(cmd: any): cmd is SpecificCommand {
|
|
67
|
+
return cmd && typeof cmd.commandCode === 'string' &&
|
|
68
|
+
cmd.commandCode in ({
|
|
69
|
+
'ExampleCommand': true,
|
|
70
|
+
// TODO: 实际使用时,这里应该包含所有真实的命令
|
|
71
|
+
} as Record<keyof CommandTypeMap, boolean>);
|
|
72
|
+
}
|
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge 代理模式扩展
|
|
3
|
+
* Edge 作为设备的代理,管理本地设备并与 Gateway 通信
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
BaseMessage,
|
|
8
|
+
MessageType,
|
|
9
|
+
ClientType,
|
|
10
|
+
CommandMessage,
|
|
11
|
+
CommandResponseMessage,
|
|
12
|
+
RegisterMessage,
|
|
13
|
+
MessageFactory,
|
|
14
|
+
Priority
|
|
15
|
+
} from './index';
|
|
16
|
+
|
|
17
|
+
// Edge 管理的设备信息
|
|
18
|
+
export interface ManagedDevice {
|
|
19
|
+
deviceId: string;
|
|
20
|
+
deviceType: string;
|
|
21
|
+
status: 'online' | 'offline' | 'error';
|
|
22
|
+
lastSeen: string;
|
|
23
|
+
capabilities?: string[];
|
|
24
|
+
metadata?: Record<string, any>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Edge 注册消息(包含管理的设备列表)
|
|
28
|
+
export interface EdgeRegisterMessage extends RegisterMessage {
|
|
29
|
+
managedDevices?: ManagedDevice[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Edge 状态报告
|
|
33
|
+
export interface EdgeStatusReport {
|
|
34
|
+
edgeId: string;
|
|
35
|
+
status: 'healthy' | 'degraded' | 'error';
|
|
36
|
+
managedDevices: ManagedDevice[];
|
|
37
|
+
statistics: {
|
|
38
|
+
totalDevices: number;
|
|
39
|
+
onlineDevices: number;
|
|
40
|
+
commandsProcessed: number;
|
|
41
|
+
errors: number;
|
|
42
|
+
uptime: number;
|
|
43
|
+
};
|
|
44
|
+
timestamp: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 设备状态变更通知
|
|
48
|
+
export interface DeviceStatusChangeMessage extends BaseMessage {
|
|
49
|
+
type: 'device_status_change';
|
|
50
|
+
edgeId: string;
|
|
51
|
+
deviceId: string;
|
|
52
|
+
previousStatus: string;
|
|
53
|
+
currentStatus: string;
|
|
54
|
+
reason?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Edge 批量设备命令
|
|
58
|
+
export interface EdgeBatchCommandMessage extends CommandMessage {
|
|
59
|
+
targetDevices: string[]; // Edge 管理的设备ID列表
|
|
60
|
+
failureStrategy?: 'stop_on_first_failure' | 'continue_on_failure';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Edge 命令路由信息
|
|
64
|
+
export interface EdgeCommandRoute {
|
|
65
|
+
deviceId: string;
|
|
66
|
+
edgeId: string;
|
|
67
|
+
lastUpdated: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Edge 代理工具类
|
|
72
|
+
*/
|
|
73
|
+
export class EdgeProxyUtils {
|
|
74
|
+
/**
|
|
75
|
+
* 创建 Edge 注册消息(包含管理的设备)
|
|
76
|
+
*/
|
|
77
|
+
static createEdgeRegisterMessage(
|
|
78
|
+
edgeId: string,
|
|
79
|
+
managedDevices: ManagedDevice[],
|
|
80
|
+
capabilities?: string[]
|
|
81
|
+
): EdgeRegisterMessage {
|
|
82
|
+
return {
|
|
83
|
+
type: MessageType.REGISTER,
|
|
84
|
+
clientId: edgeId,
|
|
85
|
+
clientType: ClientType.EDGE,
|
|
86
|
+
clientInfo: {
|
|
87
|
+
version: '1.0.0',
|
|
88
|
+
platform: 'edge-proxy',
|
|
89
|
+
capabilities: [
|
|
90
|
+
'device_proxy',
|
|
91
|
+
'batch_command',
|
|
92
|
+
'status_report',
|
|
93
|
+
...(capabilities || [])
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
managedDevices,
|
|
97
|
+
timestamp: new Date().toISOString(),
|
|
98
|
+
version: '2.0'
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 创建设备状态变更通知
|
|
104
|
+
*/
|
|
105
|
+
static createDeviceStatusChange(
|
|
106
|
+
edgeId: string,
|
|
107
|
+
deviceId: string,
|
|
108
|
+
previousStatus: string,
|
|
109
|
+
currentStatus: string,
|
|
110
|
+
reason?: string
|
|
111
|
+
): DeviceStatusChangeMessage {
|
|
112
|
+
return {
|
|
113
|
+
type: 'device_status_change',
|
|
114
|
+
edgeId,
|
|
115
|
+
deviceId,
|
|
116
|
+
previousStatus,
|
|
117
|
+
currentStatus,
|
|
118
|
+
reason,
|
|
119
|
+
timestamp: new Date().toISOString()
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 创建 Edge 状态报告
|
|
125
|
+
*/
|
|
126
|
+
static createEdgeStatusReport(
|
|
127
|
+
edgeId: string,
|
|
128
|
+
managedDevices: ManagedDevice[],
|
|
129
|
+
statistics: EdgeStatusReport['statistics']
|
|
130
|
+
): EdgeStatusReport {
|
|
131
|
+
return {
|
|
132
|
+
edgeId,
|
|
133
|
+
status: this.calculateEdgeHealth(managedDevices, statistics),
|
|
134
|
+
managedDevices,
|
|
135
|
+
statistics,
|
|
136
|
+
timestamp: new Date().toISOString()
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 创建批量设备命令
|
|
142
|
+
*/
|
|
143
|
+
static createEdgeBatchCommand(
|
|
144
|
+
requestRef: string,
|
|
145
|
+
edgeId: string,
|
|
146
|
+
targetDevices: string[],
|
|
147
|
+
command: CommandMessage['command'],
|
|
148
|
+
options?: {
|
|
149
|
+
priority?: Priority;
|
|
150
|
+
timeout?: number;
|
|
151
|
+
failureStrategy?: EdgeBatchCommandMessage['failureStrategy'];
|
|
152
|
+
}
|
|
153
|
+
): EdgeBatchCommandMessage {
|
|
154
|
+
return {
|
|
155
|
+
type: MessageType.COMMAND,
|
|
156
|
+
requestRef,
|
|
157
|
+
clientId: edgeId,
|
|
158
|
+
command,
|
|
159
|
+
targetDevices,
|
|
160
|
+
priority: options?.priority || Priority.NORMAL,
|
|
161
|
+
timeout: options?.timeout || (10000 * targetDevices.length),
|
|
162
|
+
failureStrategy: options?.failureStrategy || 'continue_on_failure',
|
|
163
|
+
timestamp: new Date().toISOString(),
|
|
164
|
+
version: '2.0'
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 解析设备 ID 获取 Edge 路由
|
|
170
|
+
* 假设设备 ID 格式: edge001:device001
|
|
171
|
+
*/
|
|
172
|
+
static parseDeviceRoute(deviceId: string): EdgeCommandRoute | null {
|
|
173
|
+
const parts = deviceId.split(':');
|
|
174
|
+
if (parts.length !== 2) return null;
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
edgeId: parts[0],
|
|
178
|
+
deviceId: parts[1],
|
|
179
|
+
lastUpdated: new Date().toISOString()
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 构建完整的设备 ID
|
|
185
|
+
*/
|
|
186
|
+
static buildFullDeviceId(edgeId: string, localDeviceId: string): string {
|
|
187
|
+
return `${edgeId}:${localDeviceId}`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 转换 Backend 命令为 Edge 本地命令
|
|
192
|
+
*/
|
|
193
|
+
static convertToLocalCommand(
|
|
194
|
+
command: CommandMessage,
|
|
195
|
+
targetDeviceId: string
|
|
196
|
+
): CommandMessage {
|
|
197
|
+
const route = this.parseDeviceRoute(command.clientId);
|
|
198
|
+
if (!route) return command;
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
...command,
|
|
202
|
+
clientId: targetDeviceId,
|
|
203
|
+
metadata: {
|
|
204
|
+
...command.metadata,
|
|
205
|
+
originalSiteId: command.clientId,
|
|
206
|
+
routedThrough: route.edgeId
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 聚合多个设备响应
|
|
213
|
+
*/
|
|
214
|
+
static aggregateDeviceResponses(
|
|
215
|
+
originalRequestRef: string,
|
|
216
|
+
deviceResponses: Array<{
|
|
217
|
+
deviceId: string;
|
|
218
|
+
response: CommandResponseMessage;
|
|
219
|
+
}>
|
|
220
|
+
): CommandResponseMessage {
|
|
221
|
+
const allSuccess = deviceResponses.every(r => r.response.status === 'completed');
|
|
222
|
+
const results = deviceResponses.map(r => ({
|
|
223
|
+
deviceId: r.deviceId,
|
|
224
|
+
status: r.response.status,
|
|
225
|
+
result: r.response.result,
|
|
226
|
+
error: r.response.error
|
|
227
|
+
}));
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
type: MessageType.COMMAND_RESPONSE,
|
|
231
|
+
requestRef: originalRequestRef,
|
|
232
|
+
status: allSuccess ? 'completed' : 'failed',
|
|
233
|
+
result: {
|
|
234
|
+
success: allSuccess,
|
|
235
|
+
data: {
|
|
236
|
+
deviceCount: deviceResponses.length,
|
|
237
|
+
results
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
timestamp: new Date().toISOString()
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 计算 Edge 健康状态
|
|
246
|
+
*/
|
|
247
|
+
private static calculateEdgeHealth(
|
|
248
|
+
devices: ManagedDevice[],
|
|
249
|
+
statistics: EdgeStatusReport['statistics']
|
|
250
|
+
): EdgeStatusReport['status'] {
|
|
251
|
+
const onlineRatio = statistics.onlineDevices / statistics.totalDevices;
|
|
252
|
+
const errorRate = statistics.errors / (statistics.commandsProcessed || 1);
|
|
253
|
+
|
|
254
|
+
if (onlineRatio < 0.5 || errorRate > 0.1) {
|
|
255
|
+
return 'error';
|
|
256
|
+
} else if (onlineRatio < 0.8 || errorRate > 0.05) {
|
|
257
|
+
return 'degraded';
|
|
258
|
+
}
|
|
259
|
+
return 'healthy';
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Edge 代理实现示例
|
|
265
|
+
*/
|
|
266
|
+
export class EdgeProxy {
|
|
267
|
+
private edgeId: string;
|
|
268
|
+
private devices = new Map<string, ManagedDevice>();
|
|
269
|
+
private gateway: any; // WebSocket connection
|
|
270
|
+
private statistics = {
|
|
271
|
+
commandsProcessed: 0,
|
|
272
|
+
errors: 0,
|
|
273
|
+
startTime: Date.now()
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
constructor(edgeId: string) {
|
|
277
|
+
this.edgeId = edgeId;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 添加本地设备
|
|
282
|
+
*/
|
|
283
|
+
addDevice(device: ManagedDevice) {
|
|
284
|
+
this.devices.set(device.deviceId, device);
|
|
285
|
+
|
|
286
|
+
// 通知 Gateway 设备状态变更
|
|
287
|
+
if (this.gateway) {
|
|
288
|
+
const notification = EdgeProxyUtils.createDeviceStatusChange(
|
|
289
|
+
this.edgeId,
|
|
290
|
+
device.deviceId,
|
|
291
|
+
'offline',
|
|
292
|
+
device.status
|
|
293
|
+
);
|
|
294
|
+
this.gateway.send(JSON.stringify(notification));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* 注册到 Gateway
|
|
300
|
+
*/
|
|
301
|
+
registerToGateway() {
|
|
302
|
+
const managedDevices = Array.from(this.devices.values());
|
|
303
|
+
const registerMsg = EdgeProxyUtils.createEdgeRegisterMessage(
|
|
304
|
+
this.edgeId,
|
|
305
|
+
managedDevices,
|
|
306
|
+
['local_storage', 'offline_mode']
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
this.gateway.send(JSON.stringify(registerMsg));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* 处理来自 Gateway 的命令
|
|
314
|
+
*/
|
|
315
|
+
async handleGatewayCommand(message: CommandMessage) {
|
|
316
|
+
// 检查是否是批量命令
|
|
317
|
+
const batchCmd = message as EdgeBatchCommandMessage;
|
|
318
|
+
if (batchCmd.targetDevices) {
|
|
319
|
+
await this.handleBatchCommand(batchCmd);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// 解析目标设备
|
|
324
|
+
const route = EdgeProxyUtils.parseDeviceRoute(message.clientId);
|
|
325
|
+
if (!route || route.edgeId !== this.edgeId) {
|
|
326
|
+
this.sendErrorResponse(message.requestRef, 'Invalid device route');
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// 检查设备是否在线
|
|
331
|
+
const device = this.devices.get(route.deviceId);
|
|
332
|
+
if (!device || device.status !== 'online') {
|
|
333
|
+
this.sendErrorResponse(
|
|
334
|
+
message.requestRef,
|
|
335
|
+
`Device ${route.deviceId} is ${device?.status || 'not found'}`
|
|
336
|
+
);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
// 转换为本地命令并执行
|
|
342
|
+
const localCommand = EdgeProxyUtils.convertToLocalCommand(message, route.deviceId);
|
|
343
|
+
const result = await this.executeDeviceCommand(route.deviceId, localCommand);
|
|
344
|
+
|
|
345
|
+
// 发送响应
|
|
346
|
+
const response: CommandResponseMessage = {
|
|
347
|
+
type: MessageType.COMMAND_RESPONSE,
|
|
348
|
+
requestRef: message.requestRef,
|
|
349
|
+
status: 'completed',
|
|
350
|
+
result,
|
|
351
|
+
timestamp: new Date().toISOString()
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
this.gateway.send(JSON.stringify(response));
|
|
355
|
+
this.statistics.commandsProcessed++;
|
|
356
|
+
|
|
357
|
+
} catch (error) {
|
|
358
|
+
this.sendErrorResponse(message.requestRef, error.message);
|
|
359
|
+
this.statistics.errors++;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* 处理批量命令
|
|
365
|
+
*/
|
|
366
|
+
private async handleBatchCommand(command: EdgeBatchCommandMessage) {
|
|
367
|
+
const responses: Array<{ deviceId: string; response: CommandResponseMessage }> = [];
|
|
368
|
+
|
|
369
|
+
for (const deviceId of command.targetDevices) {
|
|
370
|
+
const device = this.devices.get(deviceId);
|
|
371
|
+
if (!device || device.status !== 'online') {
|
|
372
|
+
responses.push({
|
|
373
|
+
deviceId,
|
|
374
|
+
response: {
|
|
375
|
+
type: MessageType.COMMAND_RESPONSE,
|
|
376
|
+
requestRef: `${command.requestRef}-${deviceId}`,
|
|
377
|
+
status: 'failed',
|
|
378
|
+
error: `Device ${deviceId} is ${device?.status || 'not found'}`,
|
|
379
|
+
timestamp: new Date().toISOString()
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
if (command.failureStrategy === 'stop_on_first_failure') {
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
try {
|
|
390
|
+
const localCommand = {
|
|
391
|
+
...command,
|
|
392
|
+
clientId: deviceId
|
|
393
|
+
};
|
|
394
|
+
const result = await this.executeDeviceCommand(deviceId, localCommand);
|
|
395
|
+
|
|
396
|
+
responses.push({
|
|
397
|
+
deviceId,
|
|
398
|
+
response: {
|
|
399
|
+
type: MessageType.COMMAND_RESPONSE,
|
|
400
|
+
requestRef: `${command.requestRef}-${deviceId}`,
|
|
401
|
+
status: 'completed',
|
|
402
|
+
result,
|
|
403
|
+
timestamp: new Date().toISOString()
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
} catch (error) {
|
|
408
|
+
responses.push({
|
|
409
|
+
deviceId,
|
|
410
|
+
response: {
|
|
411
|
+
type: MessageType.COMMAND_RESPONSE,
|
|
412
|
+
requestRef: `${command.requestRef}-${deviceId}`,
|
|
413
|
+
status: 'failed',
|
|
414
|
+
error: error.message,
|
|
415
|
+
timestamp: new Date().toISOString()
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
if (command.failureStrategy === 'stop_on_first_failure') {
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// 聚合响应
|
|
426
|
+
const aggregatedResponse = EdgeProxyUtils.aggregateDeviceResponses(
|
|
427
|
+
command.requestRef,
|
|
428
|
+
responses
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
this.gateway.send(JSON.stringify(aggregatedResponse));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* 执行设备命令(模拟)
|
|
436
|
+
*/
|
|
437
|
+
private async executeDeviceCommand(deviceId: string, command: any): Promise<any> {
|
|
438
|
+
// 实际实现中,这里会与物理设备通信
|
|
439
|
+
console.log(`Executing command on device ${deviceId}:`, command);
|
|
440
|
+
|
|
441
|
+
// 模拟命令执行
|
|
442
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
success: true,
|
|
446
|
+
data: {
|
|
447
|
+
deviceId,
|
|
448
|
+
commandCode: command.command.commandCode,
|
|
449
|
+
executedAt: new Date().toISOString()
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* 发送错误响应
|
|
456
|
+
*/
|
|
457
|
+
private sendErrorResponse(requestRef: string, errorMessage: string) {
|
|
458
|
+
const response: CommandResponseMessage = {
|
|
459
|
+
type: MessageType.COMMAND_RESPONSE,
|
|
460
|
+
requestRef,
|
|
461
|
+
status: 'failed',
|
|
462
|
+
error: {
|
|
463
|
+
code: 'EDGE_ERROR',
|
|
464
|
+
message: errorMessage,
|
|
465
|
+
retryable: false
|
|
466
|
+
},
|
|
467
|
+
timestamp: new Date().toISOString()
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
this.gateway.send(JSON.stringify(response));
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* 定期发送状态报告
|
|
475
|
+
*/
|
|
476
|
+
sendStatusReport() {
|
|
477
|
+
const report = EdgeProxyUtils.createEdgeStatusReport(
|
|
478
|
+
this.edgeId,
|
|
479
|
+
Array.from(this.devices.values()),
|
|
480
|
+
{
|
|
481
|
+
totalDevices: this.devices.size,
|
|
482
|
+
onlineDevices: Array.from(this.devices.values()).filter(d => d.status === 'online').length,
|
|
483
|
+
commandsProcessed: this.statistics.commandsProcessed,
|
|
484
|
+
errors: this.statistics.errors,
|
|
485
|
+
uptime: Date.now() - this.statistics.startTime
|
|
486
|
+
}
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
this.gateway.send(JSON.stringify({
|
|
490
|
+
type: 'edge_status_report',
|
|
491
|
+
...report
|
|
492
|
+
}));
|
|
493
|
+
}
|
|
494
|
+
}
|