837 lines
30 KiB
TypeScript
837 lines
30 KiB
TypeScript
/// <reference lib="deno.worker" />
|
||
|
||
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<string> = 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<Order>
|
||
const order: Headerless<Order> = {
|
||
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<InstantActions> = {
|
||
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<Order> = {
|
||
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<InstantActions> = {
|
||
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));
|
||
}); |