/// import { MasterController, AgvId, ClientOptions, Topic, Order, State, Headerless, InstantActions, BlockingType, Connection, Factsheet, } from "vda-5050-lib"; import { v4 as uuidv4 } from "npm:uuid"; import { createWorkerEventHelper } from "./worker_event_helper.ts"; // 创建事件助手 const eventHelper = createWorkerEventHelper("vdaWorker"); console.log("VDA 5050 Worker initialized"); // 预注册设备列表(后续主程序可动态更新) const preRegisteredDevices: AgvId[] = [ ]; // 用于保存所有设备状态及动态更新设备列表 const currentDevices: Map< string, { agvId: AgvId; lastSeen: number; isOnline: boolean; state?: State } > = new Map(); // 内部方法:生成设备唯一 key function getDeviceKey(agvId: AgvId) { return `${agvId.manufacturer}-${agvId.serialNumber}`; } // 先将预注册设备保存到 currentDevices(初始状态为离线) preRegisteredDevices.forEach((device) => { const key = getDeviceKey(device); currentDevices.set(key, { agvId: device, lastSeen: 0, isOnline: false, }); }); // 用于保存已经订阅状态的设备,避免重复订阅 const subscribedDevices: Set = new Set(); // 全局保存 MasterController 实例 let masterController: MasterController | null = null; // 从初始化参数读取的接口名和制造商 let interfaceNameValue = "oagv"; let manufacturerValue = "gateway"; // 标记 Master Controller 是否启动完成 let clientStarted = false; /** * 当收到 init 消息后,从启动参数中提取 MQTT broker 地址,从而构造客户端选项并启动控制器 */ function initializeControllerWithOptions(brokerUrl: string, iValue: string) { // Validate input parameters if (!brokerUrl || !iValue) { console.error("❌ Invalid initialization parameters:"); console.error("brokerUrl:", brokerUrl); console.error("interfaceName:", iValue); return; } // Reset previous state clientStarted = false; if (masterController) { try { masterController.stop().catch(() => {}); } catch (e) { // Ignore stop errors } masterController = null; } const clientOptions: ClientOptions = { interfaceName: iValue, transport: { // 使用启动参数传入的 MQTT server 地址 brokerUrl: brokerUrl, heartbeat:5, reconnectPeriod:1000, connectTimeout:5000, }, vdaVersion: "2.0.0", }; console.log(`🚀 正在初始化VDA 5050 Master Controller...`); console.log(`📡 MQTT Broker: ${brokerUrl}`); console.log(`🏷️ Interface: ${iValue}`); console.log(`🏭 Manufacturer: ${manufacturerValue}`); // Test MQTT broker connectivity console.log(`🔍 Testing MQTT broker connectivity...`); try { const url = new URL(brokerUrl); console.log(`📍 Broker host: ${url.hostname}, port: ${url.port || 1883}`); } catch (urlError) { console.error("❌ Invalid broker URL format:", urlError); return; } try { masterController = new MasterController(clientOptions, {}); console.log("📦 MasterController instance created successfully"); } catch (error) { console.error("❌ Failed to create MasterController instance:", error); return; } // Add timeout to prevent hanging Promise.race([ masterController.start(), new Promise((_, reject) => setTimeout(() => reject(new Error("Master controller start timeout after 30 seconds")), 30000) ) ]) .then(() => { clientStarted = true; console.log("✅ VDA 5050 master controller started successfully"); self.postMessage({ type: "started" }); // 跟踪所有AGV连接状态 masterController!.trackAgvs((trackAgvId, connectionState, timestamp) => { const key = getDeviceKey(trackAgvId); // 如果设备不在设备列表中,则不予上线 // console.log("->", key, connectionState); // if (!currentDevices.has(key)) { // console.warn( // `收到未知设备 ${trackAgvId.manufacturer}/${trackAgvId.serialNumber} 的状态消息,忽略上线。` // ); // return; // } const ts = Number(timestamp); const lastSeen = isNaN(ts) ? Date.now() : ts; // 自动添加新设备到currentDevices,无需预先配置 let record = currentDevices.get(key); if (!record) { // 动态添加新发现的设备 record = { agvId: trackAgvId, lastSeen: 0, isOnline: false }; currentDevices.set(key, record); console.log(`🆕 自动添加新设备: ${trackAgvId.manufacturer}/${trackAgvId.serialNumber}`); } const wasOffline = !record.isOnline; // 无条件置为 ONLINE 并更新最后更新时间 currentDevices.set(key, { ...record, lastSeen, isOnline: true }); // 发送上线状态更新信息 self.postMessage({ type: "connectionState", data: { agvId: trackAgvId, state: "ONLINE", timestamp: lastSeen }, }); if (wasOffline) { // console.log( // `设备 ${trackAgvId.manufacturer}/${trackAgvId.serialNumber} 新上线` // ); // 额外通知一次"新上线" self.postMessage({ type: "connectionState", data: { agvId: trackAgvId, state: "ONLINE", timestamp: lastSeen }, }); } // 若设备首次出现,则订阅其状态更新 if (!subscribedDevices.has(key)) { subscribedDevices.add(key); try { // 使用 trackAgvId 本身作为 AgvId 订阅 masterController!.subscribe( Topic.State, { manufacturer: manufacturerValue, serialNumber: trackAgvId.serialNumber }, (state: State) => { const subKey = getDeviceKey(trackAgvId); // 自动添加新设备,无需预先配置 let existing = currentDevices.get(subKey); if (!existing) { existing = { agvId: trackAgvId, lastSeen: 0, isOnline: false }; currentDevices.set(subKey, existing); console.log(`🆕 状态订阅中自动添加新设备: ${trackAgvId.manufacturer}/${trackAgvId.serialNumber}`); } const wasOfflineInSub = !existing.isOnline; currentDevices.set(subKey, { ...existing, lastSeen: Date.now(), state, isOnline: true, }); if (wasOfflineInSub) { // console.log( // `设备 ${trackAgvId.manufacturer}/${trackAgvId.serialNumber} 新上线(订阅)` // ); self.postMessage({ type: "connectionState", data: { agvId: trackAgvId, state: "ONLINE", timestamp: Date.now(), }, }); } self.postMessage({ type: "stateUpdate", data: { agvId: trackAgvId, state: state, timestamp: Date.now(), }, }); } ); // Subscribe to Factsheet topic masterController!.subscribe( Topic.Factsheet, { manufacturer: manufacturerValue, serialNumber: trackAgvId.serialNumber }, (factsheet: Factsheet) => { // console.log("收到 factsheet 消息", factsheet); self.postMessage({ type: "factsheet", data: { agvId: trackAgvId, factsheet, timestamp: Date.now() }, }); } ); // Subscribe to Connection topic masterController!.subscribe( Topic.Connection, { manufacturer: manufacturerValue, serialNumber: trackAgvId.serialNumber }, (connection: Connection) => { self.postMessage({ type: "deviceDiscovered", data: { agvId: trackAgvId, connection, timestamp: Date.now() }, }); } ); } catch (error) { console.error(`Failed to subscribe to tracked device ${trackAgvId.manufacturer}/${trackAgvId.serialNumber}:`, error); // 移除已添加的订阅标记,以便重试 subscribedDevices.delete(key); } } }); // 定时检测设备状态,超时则标记为离线 const stateUpdateCycle = 5000; const offlineThreshold = stateUpdateCycle * 3; setInterval(() => { const now = Date.now(); currentDevices.forEach((device, key) => { // console.log( // `设备 ${device.agvId.manufacturer}/${device.agvId.serialNumber} - lastSeen: ${device.lastSeen}, isOnline: ${device.isOnline}` // ); if (now - device.lastSeen > offlineThreshold && device.isOnline) { device.isOnline = false; currentDevices.set(key, device); // console.log( // `设备 ${device.agvId.manufacturer}/${device.agvId.serialNumber} 超过 ${offlineThreshold} 毫秒未更新,标记为下线` // ); } self.postMessage({ type: "connectionState", data: { agvId: device.agvId, state: device.isOnline ? "ONLINE" : "OFFLINE", timestamp: now }, }); }); }, stateUpdateCycle); }) .catch((error) => { console.error("❌ Failed to start VDA 5050 master controller:"); console.error("Error details:", error); console.error("Error message:", error?.message || "Unknown error"); console.error("Error stack:", error?.stack || "No stack trace"); // Reset client state clientStarted = false; masterController = null; // Schedule retry after delay console.log("🔄 Scheduling retry in 10 seconds..."); setTimeout(() => { console.log("🔄 Retrying VDA 5050 master controller initialization..."); initializeControllerWithOptions(brokerUrl, iValue); eventHelper.dispatchEvent("reconnect-all", { reason: "grpc-stream-failed", retryCount: 5, timestamp: Date.now() }); self.postMessage({ type: "reconnect-all" }); }, 10000); }); } /** * 辅助函数: * 当客户端尚未启动时,延迟一段时间后重试订阅 */ function subscribeWithRetry(device: AgvId, retryCount = 0) { const key = getDeviceKey(device); // console.log("订阅设备->", key); // 自动添加新设备,无需预先配置 if (!currentDevices.has(key)) { currentDevices.set(key, { agvId: device, lastSeen: 0, isOnline: false }); console.log(`🆕 订阅时自动添加新设备: ${device.manufacturer}/${device.serialNumber}`); } if (!clientStarted) { if (retryCount < 50000) { // console.warn("Client not started, retry subscribing after delay...", device); setTimeout(() => { subscribeWithRetry(device, retryCount + 1); }, 3000); } else { console.error("订阅失败:超过最大重试次数", device); } return; } if (!subscribedDevices.has(key)) { subscribedDevices.add(key); try { // 双重检查客户端状态 if (!clientStarted || !masterController) { subscribedDevices.delete(key); setTimeout(() => { subscribeWithRetry(device, retryCount + 1); }, 3000); return; } // Subscribe to State topic masterController!.subscribe( Topic.State, { manufacturer: manufacturerValue, serialNumber: device.serialNumber }, (state: State) => { const subKey = getDeviceKey(device); // 自动添加新设备,无需预先配置 let existing = currentDevices.get(subKey); if (!existing) { existing = { agvId: device, lastSeen: 0, isOnline: false }; currentDevices.set(subKey, existing); console.log(`🆕 状态更新中自动添加新设备: ${device.manufacturer}/${device.serialNumber}`); } const wasOfflineInSub = !existing.isOnline; currentDevices.set(subKey, { ...existing, lastSeen: Date.now(), state, isOnline: true, }); if (wasOfflineInSub) { self.postMessage({ type: "connectionState", data: { agvId: device, state: "ONLINE", timestamp: Date.now() }, }); } self.postMessage({ type: "stateUpdate", data: { agvId: device, state, timestamp: Date.now() }, }); } ); // Subscribe to Factsheet topic masterController!.subscribe( Topic.Factsheet, { manufacturer: manufacturerValue, serialNumber: device.serialNumber }, (factsheet: Factsheet) => { // console.log("收到 factsheet 消息", factsheet); self.postMessage({ type: "factsheet", data: { agvId: device, factsheet, timestamp: Date.now() }, }); } ); // Subscribe to Connection topic masterController!.subscribe( Topic.Connection, { manufacturer: manufacturerValue, serialNumber: device.serialNumber }, (connection: Connection) => { self.postMessage({ type: "deviceDiscovered", data: { agvId: device, connection, timestamp: Date.now() }, }); } ); } catch (error) { console.error(`Failed to subscribe to device ${device.manufacturer}/${device.serialNumber}:`, error); // 移除已添加的订阅标记,以便重试 subscribedDevices.delete(key); // 如果客户端未启动,重新调度重试 if (!clientStarted) { setTimeout(() => { subscribeWithRetry(device, 0); }, 5000); } } } } // 处理来自主线程的消息 self.onmessage = async (event) => { const message = event.data; // console.log("Received message from main thread:", message); // 如果收到 init 消息,从启动参数中传入 MQTT server 地址 if (message.type === "init") { // 从主线程传入初始化参数 // data: { // brokerUrl: config.mqtt.brokerUrl, // interfaceName: config.interfaceName, // manufacturer: config.manufacturer, // instanceId: config.instanceId // }, console.log("event", message); const { brokerUrl, interfaceName, manufacturer, instanceId } = message.data; console.log(`init params → brokerUrl: ${brokerUrl}, interfaceName: ${interfaceName}, manufacturer: ${manufacturer}, instanceId: ${instanceId}`); manufacturerValue = instanceId; interfaceNameValue = interfaceName; initializeControllerWithOptions(brokerUrl, interfaceNameValue); } // 处理单个设备移除 if (message.type === "removeDevice") { const { manufacturer, serialNumber } = message.data; const deviceKey = getDeviceKey({ manufacturer, serialNumber }); if (currentDevices.has(deviceKey)) { // 移除设备 currentDevices.delete(deviceKey); subscribedDevices.delete(deviceKey); console.log(`🗑️ 已移除设备: ${manufacturer}/${serialNumber}`); // 通知主线程设备已移除 self.postMessage({ type: "deviceRemoved", data: { manufacturer, serialNumber, deviceKey, remainingDevices: currentDevices.size } }); } else { console.log(`⚠️ 设备 ${manufacturer}/${serialNumber} 不存在,无法移除`); } } // 动态更新设备列表(主程序从配置文件发送更新消息) if (message.type === "updateDeviceList") { const newDeviceList: AgvId[] = message.data; console.log(`🔄 VDA Worker 收到设备列表更新,包含 ${newDeviceList.length} 个设备`); // 构造新设备的 Key 集合 const newKeys = new Set(newDeviceList.map(getDeviceKey)); let addedCount = 0; let updatedCount = 0; // 遍历新设备列表,新增或更新记录,并进行状态订阅 newDeviceList.forEach((device) => { const key = getDeviceKey(device); if (!currentDevices.has(key)) { currentDevices.set(key, { agvId: device, lastSeen: 0, isOnline: false }); console.log(`➕ 新增设备: ${device.manufacturer}/${device.serialNumber}`); addedCount++; } else { // 更新 agvId 信息(如果有必要) const record = currentDevices.get(key)!; record.agvId = device; currentDevices.set(key, record); console.log(`🔄 更新设备: ${device.manufacturer}/${device.serialNumber}`); updatedCount++; } // 根据设备列表订阅对应的状态更新(重试订阅,直到客户端启动) subscribeWithRetry(device); }); // 对于 currentDevices 中存在但不在最新列表中的设备,置为离线 let removedCount = 0; currentDevices.forEach((device, key) => { if (!newKeys.has(key)) { device.isOnline = false; currentDevices.set(key, device); console.log(`⏸️ 设备离线: ${device.agvId.manufacturer}/${device.agvId.serialNumber}`); removedCount++; } }); console.log(`✅ 设备列表更新完成: 新增 ${addedCount}, 更新 ${updatedCount}, 离线 ${removedCount}`); console.log(`📊 当前管理设备总数: ${currentDevices.size}`); // 通知主线程最新设备列表情况 self.postMessage({ type: "deviceListUpdated", data: { total: currentDevices.size, added: addedCount, updated: updatedCount, removed: removedCount, devices: Array.from(currentDevices.values()).map(d => ({ manufacturer: d.agvId.manufacturer, serialNumber: d.agvId.serialNumber, isOnline: d.isOnline })) } }); } if (message.type === "orderForwarded") { // Check if client is started before processing orders if (!clientStarted || !masterController) { console.warn("❌ VDA client not started yet, ignoring order request"); return; } // console.log("收到 AGV 订单消息1", message); // 解构出 agvId 串号和 order 对象 const { agvId: agvSerial, order: msg } = message.data as { agvId: string; order: any }; // Rebuild order payload as Headerless const order: Headerless = { orderId: msg.orderId, orderUpdateId: msg.orderUpdateId || 0, nodes: msg.nodes.map((node: any, idx: number) => ({ nodeId: node.nodeId, sequenceId: idx * 2, released: node.released ?? true, nodePosition: node.nodePosition, actions: node.actions || [] })), edges: [] }; if (msg.nodes.length > 1) { for (let i = 0; i < msg.nodes.length - 1; i++) { order.edges.push({ edgeId: msg.edges?.[i]?.edgeId || `edge${i}to${i + 1}`, sequenceId: i * 2 + 1, startNodeId: msg.nodes[i].nodeId, endNodeId: msg.nodes[i + 1].nodeId, released: msg.edges?.[i]?.released ?? true, actions: msg.edges?.[i]?.actions || [] }); } } let devId: any = undefined; // console.log("检查设备", currentDevices, msg.agvId); currentDevices.forEach((device, key) => { // console.log("检查设备", device, key, device.agvId.serialNumber, agvSerial); if (device.agvId.serialNumber === agvSerial) { devId = device.agvId; } }); if (devId) { // console.log("收到 AGV 订单消息2", devId); try { // console.log("---->",{ manufacturer: manufacturerValue, serialNumber: devId.serialNumber }, order); await masterController!.assignOrder({ manufacturer: manufacturerValue, serialNumber: devId.serialNumber }, order, { onOrderProcessed: (err, canceled, active, ctx) => { if (err) { console.error("Order 被拒绝", err); } else if (canceled) { console.log("Order 被取消", ctx.order); } else if (active) { console.log("Order 正在执行", ctx.order); } else { console.log("Order 完成", ctx.order); } }, onNodeTraversed: (node, nextEdge, nextNode, ctx) => { console.log("节点遍历完成", node); }, onEdgeTraversing: (edge, start, end, stateChanges, count, ctx) => { console.log("开始路径", edge); }, onEdgeTraversed: (edge, start, end, ctx) => { console.log("路径遍历完成", edge); }, onActionStateChanged: (actionState, error) => { console.log("Action 状态变化", actionState, error || ""); } }); } catch (err) { console.error("assignOrder 异常", err); } } // end if(agvId) } // end if(message.type === "orderForwarded") // 2) 收到 InstantActions 转发 if (message.type === "instantActionsForwarded") { // Check if client is started before processing instant actions if (!clientStarted || !masterController) { console.warn("❌ VDA client not started yet, ignoring instant actions request"); return; } // console.log("收到 AGV 即时动作消息", message); const msg: any = message.data; // const actions: InstantActions = msg.actions; const { agvId, actions } = msg as { agvId: string; actions: Array<{ actionType: string; /* …其它可能的字段… */ actionParameters: Array<{ key: string; value: string }>; actionDescription: string; actionId: string; blockingType: string; }>; }; // console.log("收到 AGV 即时动作消息", agvId, actions); let devId: any = undefined; currentDevices.forEach((device, key) => { if (device.agvId.serialNumber === msg.agvId) { devId = device.agvId; } }); if (devId) { // console.log("收到 AGV 即时动作消息", msg); try { const headerless: Headerless = { actions: actions.map(a => ({ actionType: a.actionType, // 必填 actionId: a.actionId, // 必填 blockingType: a.blockingType === "HARD" ? BlockingType.Hard : BlockingType.None, // 必填,使用枚举 actionParameters: a.actionParameters || [], // 使用原始的actionParameters或空数组 actionDescription: "action parameters", // 可选 })) }; // console.log("=====>",headerless); await masterController!.initiateInstantActions({ manufacturer: manufacturerValue, serialNumber: devId.serialNumber }, headerless, { onActionStateChanged: (actionState, withError, action, agvId, state) => console.log("Instant action state changed: %o %o %o", actionState, withError, action), onActionError: (error, action, agvId, state) => console.log("Instant action error: %o %o %o", error, action, state), }); } catch (err) { console.error("initiateInstantActions 异常", err); } } // end if(agvId) } // end if(message.type === "instantActionsForwarded") // 处理发送订单请求 if (message.type === "sendOrder") { // Check if client is started before processing orders if (!clientStarted || !masterController) { console.warn("❌ VDA client not started yet, ignoring send order request"); return; } try { const order: Headerless = { orderId: message.orderId || masterController!.createUuid(), orderUpdateId: 0, nodes: message.nodes.map((node: any, index: number) => ({ nodeId: node.nodeId, sequenceId: index * 2, released: true, nodePosition: node.nodePosition, actions: node.actions || [], })), edges: [], }; if (message.nodes.length > 1) { for (let i = 0; i < message.nodes.length - 1; i++) { order.edges.push({ edgeId: `edge${i}to${i + 1}`, sequenceId: i * 2 + 1, startNodeId: message.nodes[i].nodeId, endNodeId: message.nodes[i + 1].nodeId, released: true, actions: [], }); } } // 使用预注册列表中的第一个设备发送订单 let devId: any = undefined; // currentDevices.forEach((device, key) => { // if ( device.agvId.serialNumber === 'ZKG-0') { // devId = device.agvId; // } // }); if (devId) { await masterController!.assignOrder({ manufacturer: manufacturerValue, serialNumber: devId.serialNumber }, order, { onOrderProcessed: (withError, byCancelation, active, ctx) => { console.log("Order processed", { withError, byCancelation, active }); self.postMessage({ type: "orderCompleted", orderId: order.orderId, withError, byCancelation, active, }); }, onNodeTraversed: (node, nextEdge, nextNode, ctx) => { console.log("Order node traversed:", node); self.postMessage({ type: "nodeTraversed", node, nextEdge, nextNode, }); }, onEdgeTraversing: ( edge, startNode, endNode, stateChanges, invocationCount, ctx ) => { console.log("Order edge traversing:", edge); self.postMessage({ type: "edgeTraversing", edge, startNode, endNode, }); }, onEdgeTraversed: (edge, startNode, endNode, ctx) => { console.log("Order edge traversed:", edge); self.postMessage({ type: "edgeTraversed", edge, startNode, endNode }); }, }); } console.log("Order assigned successfully"); self.postMessage({ type: "orderSent", orderId: order.orderId }); } catch (error) { console.error("Failed to send order:", error); self.postMessage({ type: "error", error: (error as Error).message }); } } // 处理取消订单请求 if (message.type === "cancelOrder") { // 此处添加取消订单逻辑…… } // 处理主动请求设备列表的消息 if (message.type === "discoverDevices") { const devicesList = Array.from(currentDevices.values()) .filter(d => d.isOnline) .map(d => ({ agvId: d.agvId, isOnline: d.isOnline, x: d.state?.agvPosition?.x, y: d.state?.agvPosition?.y, theta: d.state?.agvPosition?.theta, actionStatus: d.state?.actionStates, lastNodeId: d.state?.lastNodeId, lastNodeSequenceId: d.state?.lastNodeSequenceId, nodeStates: d.state?.nodeStates, edgeStates: d.state?.edgeStates, driving: d.state?.driving, errors: d.state?.errors?.map(err => ({ errorType: err.errorType, errorLevel: err.errorLevel, errorDescription: err.errorDescription, errorReferences: err.errorReferences?.map((ref: any) => ({ referenceKey: ref.referenceKey, referenceValue: ref.referenceValue })) })), information: d.state?.information || [] })); console.log("currentDevices", JSON.stringify(Array.from(currentDevices.values()), null, 2)); self.postMessage({ type: "devicesList", data: devicesList }); } // 处理factsheet请求 if (message.type === "factsheetRequest") { // Check if client is started before processing factsheet requests if (!clientStarted || !masterController) { console.warn("❌ VDA client not started yet, ignoring factsheet request"); return; } const { agvId } = message.data || {}; if (!agvId) { console.warn("❌ No agvId provided for factsheet request"); return; } console.log(`📋 Processing factsheet request for AGV: ${agvId}`); // Find the device in currentDevices let targetDevice: any = undefined; currentDevices.forEach((device, key) => { if (device.agvId.serialNumber === agvId) { targetDevice = device.agvId; } }); if (targetDevice) { try { // Request factsheet using instant action const factsheetAction: Headerless = { actions: [{ actionType: "factsheetRequest", actionId: `factsheet_${Date.now()}`, blockingType: BlockingType.None, actionParameters: [], actionDescription: "Request device factsheet" }] }; await masterController!.initiateInstantActions( { manufacturer: manufacturerValue, serialNumber: targetDevice.serialNumber }, factsheetAction, { onActionStateChanged: (actionState, withError, action, agvId, state) => { console.log("Factsheet action state changed:", actionState, withError ? "with error" : "success"); }, onActionError: (error, action, agvId, state) => { console.error("Factsheet action error:", error); }, } ); console.log(`✅ Factsheet request sent for AGV: ${agvId}`); } catch (err) { console.error("❌ Failed to send factsheet request:", err); } } else { console.warn(`⚠️ AGV ${agvId} not found in current devices`); } } // 处理 shutdown 消息 if (message === "shutdown" || message.type === "shutdown") { console.log("收到 shutdown 消息,退出 Worker"); // 此处可添加关闭逻辑 } }; // 在 worker 退出时关闭 Master Controller addEventListener("unload", () => { console.log("Closing VDA 5050 Worker"); masterController?.stop().catch((err: Error) => console.log(err)); });