// agv_worker.ts // 捕获未处理的 promise,避免 worker 因 grpc 异常退出 globalThis.addEventListener("unhandledrejection", (event) => { console.error("Unhandled promise rejection:", event.reason); event.preventDefault(); }); // Deno + npm 包 import * as grpc from "npm:@grpc/grpc-js@1.12.1"; import * as protoLoader from "npm:@grpc/proto-loader"; import { delay } from "https://deno.land/std/async/delay.ts"; import { createWorkerEventHelper } from "./worker_event_helper.ts"; // 创建事件助手 const eventHelper = createWorkerEventHelper("agvWorker"); // Proto 加载 const PROTO_PATH = "./proto/vda5050.proto"; const packageDefinition = protoLoader.loadSync(PROTO_PATH, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true, }); const proto = grpc.loadPackageDefinition(packageDefinition) as any; const vda5050 = proto.vda5050 as any; // 运行时参数 & 设备列表 let SERVER_URL: string; let devices: Device[] = []; // 全局 gRPC 流 & AGV 实例映射 let stream: grpc.ClientDuplexStream | null = null; const agvMap = new Map(); // gRPC 连接状态标记 let isGrpcConnected = false; // 全局自增 headerId let globalHeaderId = 1; // 设备接口 interface Device { serialNumber: string; manufacturer: string; } // 新增 AGV 模拟器状态类型定义 interface Position { x: number; y: number; theta: number; } interface NodeState { nodeId: string; sequenceId: number; nodeDescription: string; nodePosition: Position; released: boolean; } interface EdgeState { edgeId: string; sequenceId: number; edgeDescription: string; released: boolean; trajectory: Position[]; } interface AgvPosition { x: number; y: number; theta: number; mapId: string; mapDescription: string; positionInitialized: boolean; deviationRange: number; localizationScore: number; } interface Velocity { vx: number; vy: number; omega: number; } interface BatteryState { batteryCharge: number; charging: boolean; batteryVoltage?: number; batteryHealth?: number; reach?: number; } interface ErrorState { errorType: string; errorDescription: string; errorLevel: string; } interface SafetyState { eStop: boolean; fieldViolation: boolean; } interface ActionState { actionId: string; actionType: string; actionDescription: string; actionStatus: string; resultDescription: string; } interface AgvState { headerId: number; timestamp: string; version: string; manufacturer: string; serialNumber: string; orderId: string; orderUpdateId: number; lastNodeId: string; lastNodeSequenceId: number; driving: boolean; waitingForInteractionZoneRelease: boolean; paused: boolean; forkState?: string; newBaseRequest: boolean; distanceSinceLastNode: number; operatingMode: string; nodeStates: NodeState[]; edgeStates: EdgeState[]; agvPosition: AgvPosition; velocity: Velocity; loads: any[]; batteryState: BatteryState; errors: ErrorState[]; information: any[]; safetyState: SafetyState; actionStates: ActionState[]; } // 建立/重建 gRPC 流 function initGrpcStream() { try { if (stream) { console.log("关闭旧 gRPC 流"); try { // 移除所有事件监听,避免它们触发重复 reconnectAll stream.removeAllListeners(); stream.end(); } catch (_) { /* ignore */ } stream = null; } const client = new vda5050.AgvManagement( SERVER_URL, grpc.credentials.createInsecure(), ); stream = client.CommunicationChannel(); console.log("gRPC 流已建立"); isGrpcConnected = true; if (!stream) { console.error("无法创建 gRPC 流"); return false; } stream.on("data", (msg: any) => { if(msg) { const sim = agvMap.get(msg.targetAgvId); if (!sim) { console.warn("未知 AGV,丢弃消息:", msg.agvId); return; }else{ if (msg.order) { // console.log(`${msg.targetAgvId} << Order`, msg); sim.handleOrder(msg.order); } else if (msg.instantActions) { const acts = msg.instantActions.instantActions || []; // console.log(`${msg.targetAgvId} << InstantActions`, msg); sim.handleInstantActions(acts); } } }else{ console.warn("AGV 消息为空,丢弃消息:", msg.agvId); return; } }); stream.on("error", async (err: any) => { console.error("gRPC 流错误,全体重连"); isGrpcConnected = false; stream = null; // 清空引用,防止重复使用失效的流 // 发送 gRPC 连接丢失事件 eventHelper.dispatchEvent("grpc-connection-lost", { error: err.message || "unknown error", timestamp: Date.now() }); await reconnectAll(); }); // stream.on("end", async () => { // console.warn("gRPC 流结束,全体重连"); // isGrpcConnected = false; // stream = null; // 清空引用,防止重复使用失效的流 // // 发送 gRPC 连接结束事件 // eventHelper.dispatchEvent("grpc-connection-ended", { // timestamp: Date.now() // }); // await reconnectAll(); // }); return true; } catch (err) { console.error("gRPC 流初始化失败:", err); stream = null; return false; } } // 安全发送消息到流 function sendToStream(msg: any): boolean { if (!stream || !isGrpcConnected) { console.warn("无法发送消息,gRPC 流未初始化"); return false; } try { stream.write(msg); return true; } catch (err) { console.error("发送消息失败:", err); return false; } } // 重连:停所有定时器,延迟后重建流,重发 Connection 并重启状态上报 async function reconnectAll() { // 停止所有状态更新 agvMap.forEach((s) => s.stopStatusUpdates()); // 等待一段时间再重连 await delay(500); // 尝试重建流 let retryCount = 0; const maxRetries = 5; while (retryCount < maxRetries) { console.log(`正在尝试重建 gRPC 流 等待次数 (${retryCount + 1}/${maxRetries})...`); // if (initGrpcStream()) { // console.log("gRPC 流重建成功"); // break; // } retryCount++; await delay(100 * retryCount); // 递增等待时间 } if (!stream) { console.error(`无法重建 gRPC 流,已达到最大重试等待次数 ${maxRetries}`); // 使用全局事件系统发送重连请求 eventHelper.dispatchEvent("reconnect-all", { reason: "grpc-stream-failed", retryCount: maxRetries, timestamp: Date.now() }); self.postMessage({ type: "reconnect-all"}); await delay(10000); return; } // 成功重建流,重新发送所有 Connection 并重启状态更新 for (const sim of agvMap.values()) { // sim.sendConnection("OFFLINE"); // 主动发送 ONLINE 状态 sim.resetLastOnlineStatus(); sim.startStatusUpdates(); await delay(200); } } // AGV 模拟器 class AgvSimulator { private batteryLevel: number; private position = { x: 0, y: 0, theta: 0 }; private orderId = ""; private orderUpdateId = 0; private state!: AgvState; private updateIntervalId: number | null = null; private lastOperatingMode = "NONE"; private lastOnlineStatus = "OFFLINE"; private agvId: string; private manufacturer: string; private serialNumber: string; constructor( agvId: string, manufacturer: string, serialNumber: string, ) { this.agvId = agvId; this.manufacturer = manufacturer; this.serialNumber = serialNumber; // 随机初始化 this.batteryLevel = 75 + Math.random() * 25; this.position = { x: Math.random() * 100, y: Math.random() * 100, theta: Math.random() * Math.PI * 2, }; this.initState(); } // 构造初始 State private initState() { this.state = { headerId: globalHeaderId++, timestamp: new Date().toISOString(), version: "2.0.0", manufacturer: this.manufacturer, serialNumber: this.serialNumber, orderId: this.orderId, orderUpdateId: this.orderUpdateId, lastNodeId: "", lastNodeSequenceId: 0, driving: false, waitingForInteractionZoneRelease: false, paused: false, forkState: undefined, newBaseRequest: false, distanceSinceLastNode: 0, operatingMode: "NONE", // 初始离线 nodeStates: [], edgeStates: [], agvPosition: { x: this.position.x, y: this.position.y, theta: this.position.theta, mapId: "warehouse", mapDescription: "", positionInitialized: true, deviationRange: 0, localizationScore: 0.95, }, velocity: { vx: 0, vy: 0, omega: 0 }, loads: [], actionStates: [], batteryState: { batteryCharge: this.batteryLevel, batteryVoltage: 24.5, batteryHealth: 100, charging: false, reach: 0, }, errors: [], information: [], safetyState: { eStop: false, fieldViolation: false }, }; } // 上行 Connection,默认为 ONLINE public sendConnection(state: "ONLINE" | "OFFLINE" = "OFFLINE") { const msg = { agvId: this.agvId, connection: { headerId: globalHeaderId++, timestamp: new Date().toISOString(), version: "2.0.0", manufacturer: this.manufacturer, serialNumber: this.serialNumber, connectionState: state, }, }; // console.log("发送 Connection 消息", msg); if (sendToStream(msg)) { // console.log(`${this.agvId} >> Connection(${state})`); return true; } return false; } public resetLastOnlineStatus() { this.lastOnlineStatus = "OFFLINE"; } // 定时上报 State public startStatusUpdates() { this.stopStatusUpdates(); this.updateIntervalId = setInterval(async () => { // 检查在线/离线状态 this.checkOnlineStatus(); // console.log(`${this.agvId}:${this.state.operatingMode}:${this.lastOnlineStatus} 状态更新>>>>`); if (this.lastOnlineStatus === "ONLINE") { // console.log(`${this.agvId} 状态更新>>>>`); this.updateAndSendState().catch(err => { console.error(`${this.agvId} 状态更新失败:`, err); }); } }, 1000); } private async updateAndSendState() { // 1) 请求 KV,添加随机ID const requestId = Math.random().toString(36).substring(2, 15); self.postMessage({ type: "requestKVData", requestId, key: `device:${this.manufacturer}/${this.serialNumber}`, }); } sendStateToGrpc(data: any) { // console.log("发送状态到 gRPC 服务器",this.agvId, this.state.serialNumber, data.agvId.serialNumber, data.state.serialNumber); // console.log("更新状态并发送-->",this.agvId, this.state.serialNumber); // 若请求失败则保持当前状态 if (data === null) return; // console.log(`${this.agvId}: KV 数据获取成功`, data); // 发送设备状态更新事件 // eventHelper.dispatchEvent("device-status-update", { // agvId: this.agvId, // batteryLevel: this.batteryLevel, // operatingMode: data.state?.operatingMode || this.state.operatingMode, // position: this.position, // timestamp: Date.now() // }); // 更新 headerId 和时间戳 this.state.headerId = globalHeaderId++; this.state.timestamp = new Date(data.lastSeen).toISOString(); // 根据KV数据更新状态 if (data.state) { // 更新操作模式 if (data.state.operatingMode) { this.state.operatingMode = data.state.operatingMode; this.lastOperatingMode = data.state.operatingMode; } // 更新电池状态 if (data.state.batteryState) { const kvBattery = data.state.batteryState; this.batteryLevel = kvBattery.batteryCharge !== undefined ? kvBattery.batteryCharge : this.batteryLevel; // 更新完整的电池状态 this.state.batteryState = { batteryCharge: this.batteryLevel, batteryVoltage: kvBattery.batteryVoltage || this.state.batteryState.batteryVoltage, batteryHealth: kvBattery.batteryHealth || this.state.batteryState.batteryHealth, charging: kvBattery.charging !== undefined ? kvBattery.charging : this.state.batteryState.charging, reach: kvBattery.reach !== undefined ? kvBattery.reach : this.state.batteryState.reach }; } else if (this.state.driving) { // 如果KV中没有电池数据但AGV在移动,模拟电量消耗 this.batteryLevel = Math.max(0, this.batteryLevel - 0.05); this.state.batteryState.batteryCharge = this.batteryLevel; } // 更新位置信息 if (data.state.agvPosition) { const kvPos = data.state.agvPosition; // 使用KV中的位置数据 this.position = { x: kvPos.x !== undefined ? kvPos.x : this.position.x, y: kvPos.y !== undefined ? kvPos.y : this.position.y, theta: kvPos.theta !== undefined ? kvPos.theta : this.position.theta }; // 更新完整的位置状态 this.state.agvPosition = { x: this.position.x, y: this.position.y, theta: this.position.theta, mapId: kvPos.mapId || this.state.agvPosition.mapId, mapDescription: kvPos.mapDescription || this.state.agvPosition.mapDescription, positionInitialized: kvPos.positionInitialized !== undefined ? kvPos.positionInitialized : this.state.agvPosition.positionInitialized, deviationRange: kvPos.deviationRange !== undefined ? kvPos.deviationRange : this.state.agvPosition.deviationRange, localizationScore: kvPos.localizationScore !== undefined ? kvPos.localizationScore : this.state.agvPosition.localizationScore }; } else if (this.state.driving) { // 如果KV中没有位置数据但AGV在移动,模拟位置变化 this.position.x += (Math.random() - 0.5) * 0.5; this.position.y += (Math.random() - 0.5) * 0.5; this.position.theta += (Math.random() - 0.5) * 0.1; this.state.agvPosition.x = this.position.x; this.state.agvPosition.y = this.position.y; this.state.agvPosition.theta = this.position.theta; } // 更新其他状态字段 if (data.state.orderId !== undefined) { this.state.orderId = data.state.orderId; } if (data.state.orderUpdateId !== undefined) { this.state.orderUpdateId = data.state.orderUpdateId; } if (data.state.lastNodeId !== undefined) { this.state.lastNodeId = data.state.lastNodeId; } if (data.state.lastNodeSequenceId !== undefined) { this.state.lastNodeSequenceId = data.state.lastNodeSequenceId; } if (data.state.driving !== undefined) { this.state.driving = data.state.driving; } if (data.state.waitingForInteractionZoneRelease !== undefined) { this.state.waitingForInteractionZoneRelease = data.state.waitingForInteractionZoneRelease; } if (data.state.paused !== undefined) { this.state.paused = data.state.paused; } if (data.state.nodeStates) { this.state.nodeStates = data.state.nodeStates; } if (data.state.edgeStates) { this.state.edgeStates = data.state.edgeStates; } if (data.state.actionStates) { this.state.actionStates = data.state.actionStates; } if (data.state.errors) { this.state.errors = data.state.errors; } if (data.state.safetyState) { this.state.safetyState = data.state.safetyState; } if (data.state.serialNumber) { this.state.serialNumber = data.state.serialNumber; } if (data.state.manufacturer) { this.state.manufacturer = data.state.manufacturer; } if (data.state.velocity) { this.state.velocity = data.state.velocity; } if (data.state.loads) { this.state.loads = data.state.loads; } if (data.state.information) { this.state.information = data.state.information; } if (data.state.forkState) { this.state.forkState = data.state.forkState; } if (data.state.safetyState) { this.state.safetyState = data.state.safetyState; } if (data.state.actionStates) { this.state.actionStates = data.state.actionStates; } if (data.state.headerId) { this.state.headerId = data.state.headerId; } if (data.state.timestamp) { this.state.timestamp = data.state.timestamp; } if (data.state.version) { this.state.version = data.state.version; } if (data.state.operatingMode) { this.state.operatingMode = data.state.operatingMode; } if (data.state.nodeStates) { this.state.nodeStates = data.state.nodeStates; } if (data.state.edgeStates) { this.state.edgeStates = data.state.edgeStates; } } // 发送 State,按照 gRPC 定义的 AgvMessage 结构 const agvMessage = { agvId: this.state.serialNumber, state: this.state // message_type 会由 oneof 自动处理,我们只需提供 state 字段 }; if (!sendToStream(agvMessage)) { console.error(`${this.agvId}: 发送状态到 gRPC 服务器失败`); } } public stopStatusUpdates() { if (this.updateIntervalId != null) { clearInterval(this.updateIntervalId); this.updateIntervalId = null; } } // 下行 Order 处理 public handleOrder(order: any) { // 1) 更新内部状态 // console.log("=====>",order); this.orderId = order.orderId; this.orderUpdateId = order.orderUpdateId; this.state.orderId = this.orderId; this.state.orderUpdateId = this.orderUpdateId; this.state.nodeStates = order.nodes.map((n: any, i: number) => ({ nodeId: n.nodeId, sequenceId: n.sequenceId ?? i, nodeDescription: n.nodeDescription ?? "", nodePosition: n.nodePosition, released: true, })); this.state.edgeStates = order.edges.map((e: any, i: number) => ({ edgeId: e.edgeId, sequenceId: e.sequenceId ?? i, edgeDescription: e.edgeDescription ?? "", released: true, trajectory: e.trajectory, })); this.state.driving = true; // 2) 主动通知主线程收到新 Order self.postMessage({ type: "orderForwarded", agvId: this.agvId, data: { manufacturer: order.manufacturer, serialNumber: order.serialNumber, orderId: this.orderId, orderUpdateId: this.orderUpdateId, nodes: this.state.nodeStates, edges: this.state.edgeStates, }, }); } // 下行 InstantActions 处理 public handleInstantActions(actions: any[]) { // 1) 更新内部状态 // console.log(`${this.agvId} 收到即时动作`, actions); actions.forEach((a) => { switch (a.actionType) { case "STOP": this.state.driving = false; break; case "RESUME": this.state.driving = true; break; case "CHARGE": this.state.batteryState.charging = true; break; case "pick": console.log("pick"); break; case "drop": console.log("drop"); break; case "stopPause": this.state.paused = true; this.state.driving = false; console.log("stopPause"); break; case "startPose": console.log("startPose"); this.state.driving = true; break; case "factsheetRequest": console.log("factsheetRequest"); break; case "instantActions": console.log("instantActions"); break; case "finePositioning": console.log("finePositioning"); break; case "startCharging": console.log("startCharging"); break; case "stopCharging": console.log("stopCharging"); break; case "initPosition": console.log("initPosition"); break; case "stateRequst": console.log("stateRequst"); break; case "logReport": console.log("logReport"); break; case "detectObject": console.log("detectObject"); break; case "waitForRtigger": console.log("waitForRtigger"); break; case "Standard": console.log("Standard"); break; case "switchMap": console.log("switchMap"); break; case "reloc": console.log("reloc"); break; case "turn": console.log("turn"); break; case "confirmLoc": console.log("confirmLoc"); break; case "cancelReloc": console.log("cancelReloc"); break; case "rotateAgv": console.log("rotateAgv"); break; case "rotateLoad": console.log("rotateLoad"); break; case "deviceSetup": console.log("deviceSetup:", a); break; case "deviceWrite": console.log("deviceWrite:", a); break; case "deviceDelete": console.log("deviceDelete:", a); break; default: console.warn(`${this.agvId} 未知即时动作`, a); } }); // 2) 主动通知主线程收到 InstantActions self.postMessage({ type: "instantActionsForwarded", agvId: this.agvId, data: actions.map((a) => ({ actionType: a.actionType, actionParameters: a.actionParameters, actionDescription: a.actionDescription, actionId: a.actionId, blockingType: a.blockingType, // …你需要的其它字段… })), }); } sendOnlineStatusGrpc(data: any) { // 若请求失败则保持当前状态 if (data === null) return; // agvId: { manufacturer: "gateway", serialNumber: "ZKG-2" } if (data.agvId.manufacturer === this.manufacturer && data.agvId.serialNumber === this.agvId && this.agvId === this.serialNumber) { // console.log(`${this.agvId}: KV 数据请求成功`, data); // 3) 计算是否在线 const lastSeen = data?.lastSeen ? Date.now() - new Date(data.lastSeen).getTime() < 3180 //如果上次 KV 中的 lastSeen 距今超过 3.18 秒,就算离线(无 connection) : false; const isOnline = lastSeen && data.state === "ONLINE"; const newMode = isOnline ? "ONLINE" : "OFFLINE"; // 只有状态变化时才发送 Connection if (newMode !== this.lastOnlineStatus) { this.lastOnlineStatus = newMode; this.sendConnection(this.lastOnlineStatus as any); } } } // 检查在线/离线:从主线程取 KV,再比较时间,若状态变更则发 Connection private async checkOnlineStatus() { // console.log(`${this.agvId} 检查在线状态`); try { // 1) 请求 KV const requestId = Math.random().toString(36).substring(2, 15); self.postMessage({ type: "requestKVDataOnline", requestId, key: `device-online:${this.manufacturer}/${this.serialNumber}`, }); } catch (err) { console.error(`${this.agvId}: 检查在线状态失败:`, err); } } // 简易状态用于 UI public getStatus() { return { agvId: this.agvId, batteryCharge: this.batteryLevel, timestamp: this.state.timestamp, position: this.state.agvPosition, driving: this.state.driving, charging: this.state.batteryState.charging, operatingMode: this.state.operatingMode, }; } } // 主流程 async function main() { console.log("agv_workermain"); // 设置事件监听器 eventHelper.addEventListener("test-event", (event) => { console.log("🧪 AGV Worker 收到测试事件:", event); }); eventHelper.addEventListener("grpc-reconnect", (event) => { console.log("🔄 AGV Worker 收到 gRPC 重连指令:", event); reconnectAll(); }); if (!SERVER_URL || devices.length === 0) { console.error("缺少 SERVER_URL 或 无设备,无法启动"); return; } // 初始化 gRPC 流 if (!initGrpcStream()) { console.error("gRPC 流初始化失败,无法启动"); return; } // 初始化所有 AGV 并发送上线消息 for (const dev of devices) { const sim = new AgvSimulator(dev.serialNumber, dev.manufacturer, dev.serialNumber); agvMap.set(dev.serialNumber, sim); sim.sendConnection("OFFLINE"); // 发送 OFFLINE 状态 sim.startStatusUpdates(); await delay(200); } console.log("所有 AGV 已上线并开始状态上报"); // 发送 AGV Worker 启动完成事件 eventHelper.dispatchEvent("agv-worker-ready", { deviceCount: devices.length, timestamp: Date.now() }); // 回主线程汇总状态 setInterval(() => { const statuses = Array.from(agvMap.values()).map((s) => s.getStatus()); self.postMessage({ type: "devicesStatus", data: statuses }); }, 1000); } // worker 接收消息 self.addEventListener("message", (evt: MessageEvent) => { const msg = evt.data; if (msg.type === "init") { SERVER_URL = msg.data.serverUrl; self.postMessage({ type: "inited"}); console.log("设置 SERVER_URL =", SERVER_URL); } else if (msg.type === "deviceList") { devices = msg.data as Device[]; console.log("devices", devices[0]); main().catch(err => console.error("主流程启动失败:", err)); } else if (msg.type === "shutdown") { console.log("Shutdown:关闭 gRPC 流 & 停止所有 AGV"); if (stream) { try { stream.end(); } catch (e) { /* 忽略关闭错误 */ } } agvMap.forEach((s) => s.stopStatusUpdates()); setTimeout(() => self.close(), 500); } else if (msg.type === "factsheetResponse") { // const agvMessage = { // agvId: msg.data.agvId, // state: msg.data.factsheet // // message_type 会由 oneof 自动处理,我们只需提供 state 字段 // }; // message Factsheet { // uint32 headerId = 1; // string timestamp = 2; // string version = 3; // string manufacturer = 4; // string serialNumber = 5; // TypeSpecification typeSpecification = 6; // PhysicalParameters physicalParameters = 7; // ProtocolLimits protocolLimits = 8; // ProtocolFeatures protocolFeatures = 9; // AgvGeometry agvGeometry = 10; // LoadSpecification loadSpecification = 11; // VehicleConfig vehicleConfig = 12; // } const factsheetMsg = { agvId: msg.data.agvId.serialNumber, factsheet: { headerId: globalHeaderId++, timestamp: new Date().toISOString(), version: "2.0.0", manufacturer: msg.data.agvId.manufacturer, serialNumber: msg.data.agvId.serialNumber, typeSpecification: msg.data.factsheet.typeSpecification, physicalParameters: msg.data.factsheet.physicalParameters, }, }; // console.log("==>factsheetResponse", factsheetMsg); if (!sendToStream(factsheetMsg)) { console.error(`${msg.data.agvId}: 发送状态到 factsheet 的 gRPC 服务器失败`); } } else if (msg.type === "requestKVData") { // console.log("收到 Worker requestKVData 消息,准备更新设备状态",msg.data.agvId); if(msg && msg.data && msg.data.agvId && msg.data.agvId.serialNumber) { const agv = agvMap.get(msg.data.agvId.serialNumber); if (agv) { agv.sendStateToGrpc(msg.data); } } } else if (msg.type === "requestKVDataOnline") { // console.log("收到 Worker requestKVDataOnline 消息,准备更新设备状态",msg.data.agvId); if(msg && msg.data && msg.data.agvId && msg.data.agvId.serialNumber) { const agv = agvMap.get(msg.data.agvId.serialNumber); if (agv) { agv.sendOnlineStatusGrpc(msg.data); } } } });