1303 lines
43 KiB
TypeScript
1303 lines
43 KiB
TypeScript
// simulator_main.ts - 主进程管理器
|
||
import { loadConfig, RawConfig } from "./simulator_config.ts";
|
||
import { createModuleLogger, perfMonitor, logMemoryUsage, setDebugLevel, LogLevel } from "./debug_logger.ts";
|
||
|
||
interface WorkerMessage {
|
||
type: "init" | "close" | "reconnect";
|
||
data?: any;
|
||
}
|
||
|
||
interface MainMessage {
|
||
type: "ready" | "error" | "status" | "log" | "device_request";
|
||
data?: any;
|
||
}
|
||
|
||
interface DeviceWorkerMessage {
|
||
type: "init" | "close" | "reconnect" | "action";
|
||
data?: any;
|
||
}
|
||
|
||
interface DeviceMainMessage {
|
||
type: "ready" | "error" | "status" | "log" | "reset";
|
||
data?: any;
|
||
}
|
||
|
||
interface AgvWorker {
|
||
worker: Worker;
|
||
agvId: string;
|
||
status: "starting" | "running" | "error" | "stopped";
|
||
config: any;
|
||
}
|
||
|
||
interface DeviceWorker {
|
||
worker: Worker;
|
||
deviceId: string;
|
||
status: "starting" | "running" | "error" | "stopped";
|
||
config: any;
|
||
deviceInfo: {
|
||
ip: string;
|
||
port: string;
|
||
slaveId: string;
|
||
deviceName: string;
|
||
protocolType: string;
|
||
brandName: string;
|
||
};
|
||
}
|
||
|
||
interface DeviceKV {
|
||
[deviceId: string]: {
|
||
status: string;
|
||
createdAt: string;
|
||
lastActivity: string;
|
||
deviceInfo: any;
|
||
// 添加寄存器配置信息
|
||
registersConfig?: string;
|
||
actionParameters?: Array<{
|
||
key: string;
|
||
value: string;
|
||
}>;
|
||
};
|
||
}
|
||
|
||
// 持久化设备信息接口
|
||
interface PersistedDeviceInfo {
|
||
deviceId: string;
|
||
deviceInfo: {
|
||
ip: string;
|
||
port: string;
|
||
slaveId: string;
|
||
deviceName: string;
|
||
protocolType: string;
|
||
brandName: string;
|
||
};
|
||
createdAt: string;
|
||
lastActivity: string;
|
||
// 添加寄存器配置信息
|
||
registersConfig?: string;
|
||
actionParameters?: Array<{
|
||
key: string;
|
||
value: string;
|
||
}>;
|
||
}
|
||
|
||
interface PersistedDevices {
|
||
devices: PersistedDeviceInfo[];
|
||
version: string;
|
||
lastUpdated: string;
|
||
}
|
||
|
||
class DevicePersistence {
|
||
private filePath: string;
|
||
|
||
constructor(filePath: string = "./device_registry.json") {
|
||
this.filePath = filePath;
|
||
}
|
||
|
||
async saveDevices(devices: PersistedDeviceInfo[]): Promise<void> {
|
||
const data: PersistedDevices = {
|
||
devices,
|
||
version: "1.0.0",
|
||
lastUpdated: new Date().toISOString()
|
||
};
|
||
|
||
try {
|
||
await Deno.writeTextFile(this.filePath, JSON.stringify(data, null, 2));
|
||
console.log(`📁 Saved ${devices.length} devices to ${this.filePath}`);
|
||
} catch (error) {
|
||
console.error(`❌ Failed to save devices: ${(error as Error).message}`);
|
||
}
|
||
}
|
||
|
||
async loadDevices(): Promise<PersistedDeviceInfo[]> {
|
||
try {
|
||
const data = await Deno.readTextFile(this.filePath);
|
||
const parsed: PersistedDevices = JSON.parse(data);
|
||
console.log(`📁 Loaded ${parsed.devices.length} devices from ${this.filePath}`);
|
||
return parsed.devices || [];
|
||
} catch (error) {
|
||
if (error instanceof Deno.errors.NotFound) {
|
||
console.log(`📁 No existing device registry found at ${this.filePath}`);
|
||
return [];
|
||
}
|
||
console.error(`❌ Failed to load devices: ${(error as Error).message}`);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
async deviceExists(deviceId: string): Promise<boolean> {
|
||
const devices = await this.loadDevices();
|
||
return devices.some(device => device.deviceId === deviceId);
|
||
}
|
||
|
||
async addDevice(deviceInfo: PersistedDeviceInfo): Promise<void> {
|
||
const devices = await this.loadDevices();
|
||
|
||
// 检查是否已存在,如果存在则更新
|
||
const existingIndex = devices.findIndex(d => d.deviceId === deviceInfo.deviceId);
|
||
if (existingIndex >= 0) {
|
||
devices[existingIndex] = deviceInfo;
|
||
console.log(`📝 Updated existing device: ${deviceInfo.deviceId}`);
|
||
} else {
|
||
devices.push(deviceInfo);
|
||
console.log(`📝 Added new device: ${deviceInfo.deviceId}`);
|
||
}
|
||
|
||
await this.saveDevices(devices);
|
||
}
|
||
|
||
async removeDevice(deviceId: string): Promise<void> {
|
||
const devices = await this.loadDevices();
|
||
const filteredDevices = devices.filter(d => d.deviceId !== deviceId);
|
||
|
||
if (filteredDevices.length < devices.length) {
|
||
await this.saveDevices(filteredDevices);
|
||
console.log(`📝 Removed device: ${deviceId}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
class SimulatorManager {
|
||
private workers: Map<string, AgvWorker> = new Map();
|
||
private deviceWorkers: Map<string, DeviceWorker> = new Map();
|
||
private deviceKV: DeviceKV = {};
|
||
private config!: RawConfig;
|
||
private devicePersistence: DevicePersistence;
|
||
private logger = createModuleLogger("SIMULATOR_MAIN");
|
||
|
||
constructor() {
|
||
this.devicePersistence = new DevicePersistence();
|
||
this.logger.info("🎯 SimulatorManager initialized");
|
||
}
|
||
|
||
async initialize() {
|
||
this.config = await loadConfig();
|
||
console.log("📋 Loaded configuration for", this.config.settings.robotCount, "robots");
|
||
console.log("🔧 Device simulator management initialized");
|
||
|
||
// 恢复持久化的设备
|
||
await this.restorePersistedDevices();
|
||
}
|
||
|
||
private async restorePersistedDevices() {
|
||
console.log("🔄 Restoring persisted devices...");
|
||
|
||
try {
|
||
const persistedDevices = await this.devicePersistence.loadDevices();
|
||
|
||
if (persistedDevices.length === 0) {
|
||
console.log("📱 No persisted devices found");
|
||
return;
|
||
}
|
||
|
||
console.log(`🚀 Starting restoration of ${persistedDevices.length} devices...`);
|
||
|
||
// helper to restore a device with retries on failure
|
||
const restoreWithRetry = async (pd: PersistedDeviceInfo) => {
|
||
try {
|
||
console.log(`🔧 Restoring device: ${pd.deviceId} (${pd.deviceInfo.deviceName})`);
|
||
await this.createDeviceWorkerFromPersisted(pd);
|
||
this.deviceKV[pd.deviceId] = {
|
||
status: "running",
|
||
createdAt: pd.createdAt,
|
||
lastActivity: new Date().toISOString(),
|
||
deviceInfo: pd.deviceInfo,
|
||
registersConfig: pd.registersConfig,
|
||
actionParameters: pd.actionParameters
|
||
};
|
||
console.log(`✅ Device ${pd.deviceId} restored successfully`);
|
||
} catch (error) {
|
||
console.error(`❌ Failed to restore device ${pd.deviceId}:`, (error as Error).message);
|
||
setTimeout(() => restoreWithRetry(pd), 5000);
|
||
}
|
||
};
|
||
|
||
for (const persistedDevice of persistedDevices) {
|
||
restoreWithRetry(persistedDevice);
|
||
}
|
||
|
||
console.log(`🎉 Device restoration tasks scheduled. Active devices: ${this.getDeviceCount()}`);
|
||
} catch (error) {
|
||
console.error("❌ Failed to restore persisted devices:", (error as Error).message);
|
||
}
|
||
}
|
||
|
||
private async createDeviceWorkerFromPersisted(persistedDevice: PersistedDeviceInfo): Promise<void> {
|
||
const { deviceId, deviceInfo } = persistedDevice;
|
||
|
||
// 创建Worker配置
|
||
const brokerUrl = `mqtt://${this.config.mqttBroker.host}:${this.config.mqttBroker.port}`;
|
||
const workerConfig = {
|
||
deviceId,
|
||
deviceInfo,
|
||
vdaInterface: this.config.mqttBroker.vdaInterface,
|
||
mqtt: {
|
||
brokerUrl,
|
||
options: {
|
||
clean: true,
|
||
keepalive: 60,
|
||
}
|
||
},
|
||
vehicle: {
|
||
manufacturer: this.config.vehicle.manufacturer,
|
||
serialNumber: deviceId,
|
||
vdaVersion: this.config.vehicle.vdaVersion,
|
||
}
|
||
};
|
||
|
||
await this.startDeviceWorker(deviceId, deviceInfo, workerConfig);
|
||
}
|
||
|
||
async startAllWorkers() {
|
||
console.log(`🚀 Starting ${this.config.settings.robotCount} AGV workers...`);
|
||
|
||
const brokerUrl = `mqtt://${this.config.mqttBroker.host}:${this.config.mqttBroker.port}`;
|
||
|
||
for (let i = 0; i < this.config.settings.robotCount; i++) {
|
||
const agvId = `${this.config.vehicle.serialNumber}${i}`;
|
||
|
||
const workerConfig = {
|
||
vdaInterface: this.config.mqttBroker.vdaInterface,
|
||
zoneSetId: this.config.settings.mapId,
|
||
mapId: this.config.settings.mapId,
|
||
vehicle: {
|
||
manufacturer: this.config.vehicle.manufacturer,
|
||
serialNumber: agvId,
|
||
vdaVersion: this.config.vehicle.vdaVersion,
|
||
},
|
||
mqtt: {
|
||
brokerUrl,
|
||
options: {
|
||
clean: true,
|
||
keepalive: 60,
|
||
}
|
||
},
|
||
settings: {
|
||
robotCount: this.config.settings.robotCount,
|
||
stateFrequency: this.config.settings.stateFrequency,
|
||
visualizationFrequency: this.config.settings.visualizationFrequency,
|
||
speed: this.config.settings.speed,
|
||
},
|
||
};
|
||
|
||
await this.startWorker(agvId, workerConfig);
|
||
}
|
||
}
|
||
|
||
private async startWorker(agvId: string, config: any): Promise<void> {
|
||
if (this.workers.has(agvId)) {
|
||
console.log(`⚠️ Worker ${agvId} already exists, stopping it first`);
|
||
await this.stopWorker(agvId);
|
||
}
|
||
|
||
console.log(`🔧 Creating worker for AGV ${agvId}`);
|
||
|
||
const worker = new Worker(new URL("./simulator.ts", import.meta.url).href, {
|
||
type: "module",
|
||
});
|
||
|
||
const agvWorker: AgvWorker = {
|
||
worker,
|
||
agvId,
|
||
status: "starting",
|
||
config,
|
||
};
|
||
|
||
this.workers.set(agvId, agvWorker);
|
||
this.setupWorkerHandlers(agvWorker);
|
||
|
||
// 发送初始化消息
|
||
worker.postMessage({ type: "init", data: config } as WorkerMessage);
|
||
|
||
// 等待worker准备就绪
|
||
return new Promise((resolve, reject) => {
|
||
const timeout = setTimeout(() => {
|
||
agvWorker.status = "error";
|
||
reject(new Error(`Worker ${agvId} failed to start within timeout`));
|
||
}, 15000); // 15秒超时
|
||
|
||
const originalHandler = worker.onmessage;
|
||
worker.onmessage = (event: MessageEvent<MainMessage>) => {
|
||
if (event.data.type === "ready") {
|
||
clearTimeout(timeout);
|
||
agvWorker.status = "running";
|
||
worker.onmessage = originalHandler;
|
||
console.log(`✅ Worker ${agvId} started successfully`);
|
||
resolve();
|
||
} else if (event.data.type === "error") {
|
||
clearTimeout(timeout);
|
||
agvWorker.status = "error";
|
||
worker.onmessage = originalHandler;
|
||
reject(new Error(`Worker ${agvId} failed to start: ${event.data.data?.error}`));
|
||
}
|
||
};
|
||
});
|
||
}
|
||
|
||
private setupWorkerHandlers(agvWorker: AgvWorker) {
|
||
const { worker, agvId } = agvWorker;
|
||
|
||
worker.onmessage = (event: MessageEvent<MainMessage>) => {
|
||
const { type, data } = event.data;
|
||
|
||
switch (type) {
|
||
case "ready":
|
||
agvWorker.status = "running";
|
||
console.log(`✅ AGV ${agvId} is ready`);
|
||
break;
|
||
|
||
case "error":
|
||
agvWorker.status = "error";
|
||
console.error(`❌ AGV ${agvId} error:`, data?.error);
|
||
break;
|
||
|
||
case "status":
|
||
console.log(`📊 AGV ${agvId} status:`, data?.status);
|
||
if (data?.status === "closed") {
|
||
agvWorker.status = "stopped";
|
||
}
|
||
break;
|
||
|
||
case "log":
|
||
// 转发日志到控制台
|
||
console.log(data?.message);
|
||
break;
|
||
|
||
case "device_request":
|
||
// 处理设备模拟器请求
|
||
this.handleDeviceRequest(agvId, data);
|
||
break;
|
||
|
||
default:
|
||
console.warn(`Unknown message type from ${agvId}:`, type);
|
||
}
|
||
};
|
||
|
||
worker.onerror = (error) => {
|
||
agvWorker.status = "error";
|
||
console.error(`❌ Worker ${agvId} error:`, error.message);
|
||
};
|
||
|
||
worker.onmessageerror = (error) => {
|
||
console.error(`❌ Message error from worker ${agvId}:`, error);
|
||
};
|
||
}
|
||
|
||
async stopWorker(agvId: string): Promise<void> {
|
||
const agvWorker = this.workers.get(agvId);
|
||
if (!agvWorker) {
|
||
console.warn(`⚠️ Worker ${agvId} not found`);
|
||
return;
|
||
}
|
||
|
||
console.log(`🛑 Stopping worker ${agvId}`);
|
||
|
||
return new Promise((resolve) => {
|
||
const timeout = setTimeout(() => {
|
||
agvWorker.worker.terminate();
|
||
this.workers.delete(agvId);
|
||
console.log(`🔄 Force terminated worker ${agvId}`);
|
||
resolve();
|
||
}, 5000);
|
||
|
||
agvWorker.worker.onmessage = (event: MessageEvent<MainMessage>) => {
|
||
if (event.data.type === "status" && event.data.data?.status === "closed") {
|
||
clearTimeout(timeout);
|
||
agvWorker.worker.terminate();
|
||
this.workers.delete(agvId);
|
||
console.log(`✅ Worker ${agvId} stopped gracefully`);
|
||
resolve();
|
||
}
|
||
};
|
||
|
||
agvWorker.worker.postMessage({ type: "close" } as WorkerMessage);
|
||
});
|
||
}
|
||
|
||
async stopAllWorkers(): Promise<void> {
|
||
console.log("🛑 Stopping all workers...");
|
||
const stopPromises = Array.from(this.workers.keys()).map(agvId => this.stopWorker(agvId));
|
||
await Promise.all(stopPromises);
|
||
|
||
// 同时停止所有设备模拟器
|
||
await this.stopAllDeviceWorkers();
|
||
|
||
console.log("✅ All workers stopped");
|
||
}
|
||
|
||
async resetWorker(agvId: string): Promise<void> {
|
||
const agvWorker = this.workers.get(agvId);
|
||
if (!agvWorker) {
|
||
console.error(`❌ Worker ${agvId} not found`);
|
||
return;
|
||
}
|
||
|
||
console.log(`🔄 Resetting worker ${agvId} (restarting worker process)`);
|
||
const config = agvWorker.config;
|
||
await this.stopWorker(agvId);
|
||
await this.startWorker(agvId, config);
|
||
}
|
||
|
||
async reconnectWorker(agvId: string): Promise<void> {
|
||
const agvWorker = this.workers.get(agvId);
|
||
if (!agvWorker) {
|
||
console.error(`❌ Worker ${agvId} not found`);
|
||
return;
|
||
}
|
||
|
||
console.log(`🔌 Reconnecting worker ${agvId}`);
|
||
agvWorker.worker.postMessage({ type: "reconnect" } as WorkerMessage);
|
||
}
|
||
|
||
async restartWorker(agvId: string): Promise<void> {
|
||
const agvWorker = this.workers.get(agvId);
|
||
if (!agvWorker) {
|
||
console.error(`❌ Worker ${agvId} not found`);
|
||
return;
|
||
}
|
||
|
||
console.log(`🔄 Restarting worker ${agvId}`);
|
||
const config = agvWorker.config;
|
||
await this.stopWorker(agvId);
|
||
await this.startWorker(agvId, config);
|
||
}
|
||
|
||
getWorkerStatus(agvId?: string) {
|
||
if (agvId) {
|
||
const worker = this.workers.get(agvId);
|
||
return worker ? { agvId, status: worker.status } : null;
|
||
}
|
||
|
||
return Array.from(this.workers.entries()).map(([id, worker]) => ({
|
||
agvId: id,
|
||
status: worker.status
|
||
}));
|
||
}
|
||
|
||
getWorkerCount(): number {
|
||
return this.workers.size;
|
||
}
|
||
|
||
// 交互式命令处理
|
||
async handleCommand(command: string, args: string[] = []) {
|
||
const cmd = command.toLowerCase();
|
||
|
||
switch (cmd) {
|
||
case "status":
|
||
if (args[0]) {
|
||
const status = this.getWorkerStatus(args[0]);
|
||
if (status && !Array.isArray(status)) {
|
||
console.log(`📊 AGV ${args[0]}: ${status.status}`);
|
||
} else {
|
||
console.log(`❌ AGV ${args[0]} not found`);
|
||
}
|
||
} else {
|
||
const statuses = this.getWorkerStatus();
|
||
console.log("📊 AGV Worker Status:");
|
||
if (Array.isArray(statuses)) {
|
||
statuses.forEach((s: any) => console.log(` ${s.agvId}: ${s.status}`));
|
||
}
|
||
|
||
// 显示设备状态
|
||
console.log(`📱 Device Workers: ${this.getDeviceCount()}`);
|
||
const deviceKV = this.getDeviceKV();
|
||
for (const [deviceId, info] of Object.entries(deviceKV)) {
|
||
console.log(` ${deviceId}: ${info.status} (${info.deviceInfo.deviceName})`);
|
||
}
|
||
}
|
||
break;
|
||
|
||
case "devices":{
|
||
const deviceKV = this.getDeviceKV();
|
||
console.log(`📱 Active Device Simulators (${this.getDeviceCount()}):`);
|
||
for (const [deviceId, info] of Object.entries(deviceKV)) {
|
||
console.log(` ${deviceId}:`);
|
||
console.log(` Name: ${info.deviceInfo.deviceName} (${info.deviceInfo.brandName})`);
|
||
console.log(` Address: ${info.deviceInfo.ip}:${info.deviceInfo.port}/${info.deviceInfo.slaveId}`);
|
||
console.log(` Protocol: ${info.deviceInfo.protocolType}`);
|
||
console.log(` Status: ${info.status}`);
|
||
console.log(` Created: ${info.createdAt}`);
|
||
console.log(` Last Activity: ${info.lastActivity}`);
|
||
|
||
// 显示寄存器配置信息
|
||
if (info.registersConfig) {
|
||
console.log(` 📝 Registers Configuration:`);
|
||
try {
|
||
const registers = JSON.parse(info.registersConfig);
|
||
console.log(` Total Registers: ${registers.length}`);
|
||
registers.forEach((reg: any, index: number) => {
|
||
console.log(` [${index + 1}] ${reg.name}:`);
|
||
console.log(` Function Code: ${reg.fnCode} (${this.getFunctionCodeDescription(reg.fnCode)})`);
|
||
console.log(` Address: ${reg.regAddress}`);
|
||
console.log(` Count: ${reg.regCount}`);
|
||
});
|
||
} catch (error) {
|
||
console.log(` ❌ Invalid JSON format: ${info.registersConfig}`);
|
||
}
|
||
} else {
|
||
console.log(` 📝 Registers Configuration: None`);
|
||
}
|
||
|
||
// 显示其他动作参数
|
||
if (info.actionParameters && info.actionParameters.length > 0) {
|
||
console.log(` 🔧 Action Parameters:`);
|
||
info.actionParameters.forEach((param: any, index: number) => {
|
||
if (param.key !== "registers") { // registers已经单独显示了
|
||
const displayValue = param.value.length > 50 ?
|
||
param.value.substring(0, 50) + '...' :
|
||
param.value;
|
||
console.log(` [${index + 1}] ${param.key}: ${displayValue}`);
|
||
}
|
||
});
|
||
}
|
||
|
||
console.log(); // 空行分隔
|
||
}
|
||
break;
|
||
}
|
||
case "devicestop":
|
||
if (args[0]) {
|
||
await this.stopDeviceSimulator(args[0]);
|
||
} else {
|
||
console.log("Usage: devicestop <deviceId>");
|
||
console.log("Available devices:");
|
||
const devices = this.getDeviceKV();
|
||
for (const deviceId of Object.keys(devices)) {
|
||
console.log(` ${deviceId}`);
|
||
}
|
||
}
|
||
break;
|
||
|
||
case "persistent":
|
||
if (args[0] === "list") {
|
||
await this.listPersistedDevices();
|
||
} else if (args[0] === "clear") {
|
||
await this.clearPersistedDevices();
|
||
} else if (args[0] === "restore") {
|
||
await this.restorePersistedDevices();
|
||
} else {
|
||
console.log("Usage: persistent <list|clear|restore>");
|
||
console.log(" list - Show all persisted devices");
|
||
console.log(" clear - Clear all persisted devices");
|
||
console.log(" restore - Restore persisted devices (force reload)");
|
||
}
|
||
break;
|
||
|
||
case "start":
|
||
await this.startAllWorkers();
|
||
break;
|
||
|
||
case "stop":
|
||
if (args[0]) {
|
||
await this.stopWorker(args[0]);
|
||
} else {
|
||
await this.stopAllWorkers();
|
||
}
|
||
break;
|
||
|
||
case "reset":
|
||
if (args[0]) {
|
||
await this.resetWorker(args[0]);
|
||
} else {
|
||
// 重置所有workers
|
||
console.log("🔄 Resetting all workers...");
|
||
const workerIds = Array.from(this.workers.keys());
|
||
for (const agvId of workerIds) {
|
||
await this.resetWorker(agvId);
|
||
}
|
||
}
|
||
break;
|
||
|
||
case "reconnect":
|
||
if (args[0]) {
|
||
await this.reconnectWorker(args[0]);
|
||
} else {
|
||
console.log("Usage: reconnect <agvId>");
|
||
}
|
||
break;
|
||
|
||
case "restart":
|
||
if (args[0]) {
|
||
await this.restartWorker(args[0]);
|
||
} else {
|
||
console.log("Usage: restart <agvId>");
|
||
}
|
||
break;
|
||
|
||
case "count":
|
||
console.log(`📊 Active Workers: ${this.getWorkerCount()}`);
|
||
console.log(`📱 Active Devices: ${this.getDeviceCount()}`);
|
||
break;
|
||
|
||
case "debug":
|
||
if (args[0] === "level") {
|
||
if (args[1]) {
|
||
const level = args[1].toUpperCase();
|
||
const module = args[2];
|
||
try {
|
||
setDebugLevel(level, module);
|
||
} catch (error) {
|
||
console.log(`❌ Invalid debug level: ${level}`);
|
||
console.log("Valid levels: ERROR, WARN, INFO, DEBUG, TRACE");
|
||
}
|
||
} else {
|
||
console.log("Usage: debug level <LEVEL> [module]");
|
||
console.log("Levels: ERROR, WARN, INFO, DEBUG, TRACE");
|
||
console.log("Modules: DEVICE_SIMULATOR, MODBUS, MQTT, REGISTER_CONFIG, DEVICE_MANAGER, SIMULATOR_MAIN");
|
||
}
|
||
} else if (args[0] === "memory") {
|
||
logMemoryUsage("SIMULATOR_MAIN");
|
||
} else {
|
||
console.log(`
|
||
🔧 Debug Commands:
|
||
debug level <LEVEL> [module] - Set debug level (ERROR/WARN/INFO/DEBUG/TRACE)
|
||
debug memory - Show current memory usage
|
||
|
||
Examples:
|
||
debug level DEBUG - Set global debug level
|
||
debug level TRACE DEVICE_SIMULATOR - Set module-specific level
|
||
debug memory - Show memory usage
|
||
`);
|
||
}
|
||
break;
|
||
|
||
case "help":
|
||
console.log(`
|
||
📖 Available Commands:
|
||
AGV Management:
|
||
status [agvId] - Show AGV worker status
|
||
start - Start all AGV workers
|
||
stop [agvId] - Stop AGV worker(s)
|
||
reset [agvId] - Reset AGV worker(s) (stop and restart)
|
||
reconnect <agvId> - Reconnect AGV worker MQTT
|
||
restart <agvId> - Restart AGV worker process
|
||
count - Show worker and device counts
|
||
|
||
Device Management:
|
||
devices - List all active device simulators
|
||
devicestop <id> - Stop specific device simulator
|
||
persistent list - Show all persisted devices
|
||
persistent clear - Clear all persisted devices
|
||
persistent restore - Force restore persisted devices
|
||
|
||
Debug Commands:
|
||
debug level <LEVEL> [module] - Set debug level
|
||
debug memory - Show memory usage
|
||
|
||
General:
|
||
help - Show this help
|
||
quit/exit - Stop all workers and exit
|
||
`);
|
||
break;
|
||
|
||
case "quit":
|
||
case "exit":
|
||
await this.stopAllWorkers();
|
||
Deno.exit(0);
|
||
break;
|
||
|
||
default:
|
||
console.log(`❌ Unknown command: ${command}. Type 'help' for available commands.`);
|
||
}
|
||
}
|
||
|
||
async startInteractiveMode() {
|
||
console.log(`
|
||
🎮 Interactive Mode Started
|
||
Type 'help' for available commands
|
||
Type 'quit' or 'exit' to stop all workers and exit
|
||
`);
|
||
|
||
// 简单的命令行界面
|
||
const encoder = new TextEncoder();
|
||
const decoder = new TextDecoder();
|
||
|
||
while (true) {
|
||
// 显示提示符
|
||
await Deno.stdout.write(encoder.encode("simulator> "));
|
||
|
||
// 读取用户输入
|
||
const buf = new Uint8Array(1024);
|
||
const n = await Deno.stdin.read(buf);
|
||
if (n === null) break;
|
||
|
||
const input = decoder.decode(buf.subarray(0, n)).trim();
|
||
if (!input) continue;
|
||
|
||
const [command, ...args] = input.split(/\s+/);
|
||
|
||
try {
|
||
await this.handleCommand(command, args);
|
||
} catch (error) {
|
||
console.error(`❌ Command failed:`, (error as Error).message);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 设备模拟器管理方法
|
||
private async handleDeviceRequest(agvId: string, data: any): Promise<void> {
|
||
const { action, deviceInfo, deviceId, originalAction } = data;
|
||
|
||
console.log(`🔧 Handling device request from ${agvId}: ${action}`);
|
||
|
||
switch (action) {
|
||
case "create":
|
||
await this.createDeviceSimulator(deviceInfo, originalAction);
|
||
break;
|
||
case "stop":
|
||
await this.stopDeviceSimulator(deviceId);
|
||
break;
|
||
case "delete":
|
||
await this.stopDeviceSimulator(deviceId);
|
||
break;
|
||
case "forward":
|
||
await this.forwardActionToDevice(deviceId, originalAction);
|
||
break;
|
||
default:
|
||
console.warn(`Unknown device action: ${action}`);
|
||
}
|
||
}
|
||
|
||
private async createDeviceSimulator(deviceInfo: any, originalAction: any): Promise<void> {
|
||
const deviceId = `${deviceInfo.ip}-${deviceInfo.port}-${deviceInfo.slaveId}`;
|
||
|
||
console.log(`🆕 Processing device creation request: ${deviceId}`);
|
||
console.log(`📱 Device: ${deviceInfo.deviceName} (${deviceInfo.brandName})`);
|
||
console.log(`🔌 Protocol: ${deviceInfo.protocolType}`);
|
||
|
||
// 检查设备模拟器是否已经在运行中
|
||
if (this.deviceWorkers.has(deviceId)) {
|
||
console.log(`⚠️ Device ${deviceId} worker is already running, ignoring duplicate creation request`);
|
||
console.log(`📡 Forwarding action to existing device instead`);
|
||
await this.forwardActionToDevice(deviceId, originalAction);
|
||
return;
|
||
}
|
||
|
||
// 检查是否在持久化存储中存在(可能是重启后还未恢复的设备)
|
||
const persistedExists = await this.devicePersistence.deviceExists(deviceId);
|
||
if (persistedExists) {
|
||
console.log(`📁 Device ${deviceId} exists in persistent storage but worker not running`);
|
||
console.log(`🚀 Starting worker for existing persisted device`);
|
||
} else {
|
||
console.log(`🆕 Creating new device: ${deviceId}`);
|
||
}
|
||
|
||
// 创建Worker配置
|
||
const brokerUrl = `mqtt://${this.config.mqttBroker.host}:${this.config.mqttBroker.port}`;
|
||
const workerConfig = {
|
||
deviceId,
|
||
deviceInfo,
|
||
vdaInterface: this.config.mqttBroker.vdaInterface,
|
||
mqtt: {
|
||
brokerUrl,
|
||
options: {
|
||
clean: true,
|
||
keepalive: 60,
|
||
}
|
||
},
|
||
vehicle: {
|
||
manufacturer: this.config.vehicle.manufacturer,
|
||
serialNumber: deviceId,
|
||
vdaVersion: this.config.vehicle.vdaVersion,
|
||
}
|
||
};
|
||
|
||
// Attempt to start the device worker, but proceed regardless of success or failure
|
||
let startError: Error | null = null;
|
||
try {
|
||
await this.startDeviceWorker(deviceId, deviceInfo, workerConfig);
|
||
} catch (error) {
|
||
startError = error as Error;
|
||
console.error(`❌ Failed to start device worker for ${deviceId}:`, startError.message);
|
||
}
|
||
|
||
// Record and persist the device in all cases
|
||
const now = new Date().toISOString();
|
||
// 提取寄存器配置
|
||
const registersParam = originalAction.actionParameters?.find((p: any) => p.key === "registers");
|
||
const registersConfig = registersParam?.value;
|
||
|
||
// 更新KV存储
|
||
this.deviceKV[deviceId] = {
|
||
status: startError ? "error" : "running",
|
||
createdAt: now,
|
||
lastActivity: now,
|
||
deviceInfo,
|
||
registersConfig,
|
||
actionParameters: originalAction.actionParameters
|
||
};
|
||
|
||
// 保存到持久化存储
|
||
const persistedDevice: PersistedDeviceInfo = {
|
||
deviceId,
|
||
deviceInfo,
|
||
createdAt: now,
|
||
lastActivity: now,
|
||
registersConfig,
|
||
actionParameters: originalAction.actionParameters
|
||
};
|
||
await this.devicePersistence.addDevice(persistedDevice);
|
||
|
||
// 等待设备模拟器启动完成后发送初始action
|
||
setTimeout(async () => {
|
||
await this.forwardActionToDevice(deviceId, originalAction);
|
||
}, 2000);
|
||
|
||
// Provide a combined status message
|
||
if (startError) {
|
||
console.log(`⚠ Device simulator ${deviceId} added but worker start failed: ${startError.message}`);
|
||
} else {
|
||
console.log(`✅ Device simulator ${deviceId} created and persisted successfully`);
|
||
}
|
||
}
|
||
|
||
private async startDeviceWorker(deviceId: string, deviceInfo: any, config: any): Promise<void> {
|
||
console.log(`🔧 Starting device worker for ${deviceId}`);
|
||
|
||
const worker = new Worker(new URL("./device_simulator.ts", import.meta.url).href, {
|
||
type: "module",
|
||
});
|
||
|
||
const deviceWorker: DeviceWorker = {
|
||
worker,
|
||
deviceId,
|
||
status: "starting",
|
||
config,
|
||
deviceInfo,
|
||
};
|
||
|
||
this.deviceWorkers.set(deviceId, deviceWorker);
|
||
this.setupDeviceWorkerHandlers(deviceWorker);
|
||
|
||
// 发送初始化消息
|
||
worker.postMessage({ type: "init", data: config } as DeviceWorkerMessage);
|
||
|
||
// 等待worker准备就绪
|
||
return new Promise((resolve, reject) => {
|
||
const timeout = setTimeout(() => {
|
||
deviceWorker.status = "error";
|
||
reject(new Error(`Device worker ${deviceId} failed to start within timeout`));
|
||
}, 15000);
|
||
|
||
const originalHandler = worker.onmessage;
|
||
worker.onmessage = (event: MessageEvent<DeviceMainMessage>) => {
|
||
if (event.data.type === "ready") {
|
||
clearTimeout(timeout);
|
||
deviceWorker.status = "running";
|
||
worker.onmessage = originalHandler;
|
||
console.log(`✅ Device worker ${deviceId} started successfully`);
|
||
resolve();
|
||
} else if (event.data.type === "error") {
|
||
clearTimeout(timeout);
|
||
deviceWorker.status = "error";
|
||
worker.onmessage = originalHandler;
|
||
reject(new Error(`Device worker ${deviceId} failed to start: ${event.data.data?.error}`));
|
||
}
|
||
};
|
||
});
|
||
}
|
||
|
||
private setupDeviceWorkerHandlers(deviceWorker: DeviceWorker) {
|
||
const { worker, deviceId } = deviceWorker;
|
||
|
||
worker.onmessage = (event: MessageEvent<DeviceMainMessage>) => {
|
||
const { type, data } = event.data;
|
||
|
||
switch (type) {
|
||
case "ready":
|
||
deviceWorker.status = "running";
|
||
console.log(`✅ Device ${deviceId} is ready`);
|
||
break;
|
||
|
||
case "error":
|
||
deviceWorker.status = "error";
|
||
console.error(`❌ Device ${deviceId} error:`, data?.error);
|
||
break;
|
||
|
||
case "status":
|
||
console.log(`📊 Device ${deviceId} status:`, data?.status);
|
||
if (data?.status === "closed") {
|
||
deviceWorker.status = "stopped";
|
||
// 设备 worker 自我关闭时,需要完整清理所有数据
|
||
this.handleDeviceWorkerClosure(deviceId);
|
||
}
|
||
break;
|
||
|
||
case "log":
|
||
// 转发日志到控制台
|
||
console.log(data?.message);
|
||
break;
|
||
|
||
case "reset":
|
||
console.log(`🔄 Worker reset requested for device ${deviceId}: ${data?.reason}`);
|
||
console.log(`Error: ${data?.error}`);
|
||
this.handleDeviceWorkerReset(deviceWorker, data);
|
||
break;
|
||
|
||
default:
|
||
console.warn(`Unknown message type from device ${deviceId}:`, type);
|
||
}
|
||
|
||
// 更新最后活动时间
|
||
if (this.deviceKV[deviceId]) {
|
||
this.deviceKV[deviceId].lastActivity = new Date().toISOString();
|
||
}
|
||
};
|
||
|
||
worker.onerror = (error) => {
|
||
deviceWorker.status = "error";
|
||
console.error(`❌ Device worker ${deviceId} error:`, error.message);
|
||
};
|
||
|
||
worker.onmessageerror = (error) => {
|
||
console.error(`❌ Message error from device worker ${deviceId}:`, error);
|
||
};
|
||
}
|
||
|
||
private async handleDeviceWorkerReset(deviceWorker: DeviceWorker, resetData: any): Promise<void> {
|
||
const { deviceId } = deviceWorker;
|
||
console.log(`🔄 Resetting device worker ${deviceId} due to: ${resetData?.reason}`);
|
||
|
||
try {
|
||
// 终止当前worker
|
||
deviceWorker.worker.terminate();
|
||
|
||
// 创建新的worker
|
||
const newWorker = new Worker(new URL("./device_simulator.ts", import.meta.url).href, {
|
||
type: "module",
|
||
});
|
||
|
||
// 更新worker引用
|
||
deviceWorker.worker = newWorker;
|
||
deviceWorker.status = "starting";
|
||
|
||
// 重新设置消息处理
|
||
this.setupDeviceWorkerHandlers(deviceWorker);
|
||
|
||
// 重新初始化
|
||
newWorker.postMessage({ type: "init", data: deviceWorker.config } as DeviceWorkerMessage);
|
||
|
||
console.log(`✅ Device worker ${deviceId} reset completed`);
|
||
} catch (error) {
|
||
console.error(`❌ Failed to reset device worker ${deviceId}:`, (error as Error).message);
|
||
deviceWorker.status = "error";
|
||
}
|
||
}
|
||
|
||
private async forwardActionToDevice(deviceId: string, action: any): Promise<void> {
|
||
const deviceWorker = this.deviceWorkers.get(deviceId);
|
||
if (!deviceWorker) {
|
||
console.warn(`⚠️ Device worker ${deviceId} not found, cannot forward action`);
|
||
return;
|
||
}
|
||
|
||
console.log(`📡 Forwarding action to device ${deviceId}: ${action.actionType}`);
|
||
deviceWorker.worker.postMessage({
|
||
type: "action",
|
||
data: action
|
||
} as DeviceWorkerMessage);
|
||
}
|
||
|
||
// 处理设备 worker 自我关闭(如 deviceDelete 触发的关闭)
|
||
private async handleDeviceWorkerClosure(deviceId: string): Promise<void> {
|
||
console.log(`🔄 Handling device worker closure for ${deviceId}`);
|
||
|
||
// 清理内存引用
|
||
this.cleanupDeviceReferences(deviceId);
|
||
|
||
// 检查是否是 deviceDelete 操作导致的关闭,如果是,则清理持久化数据
|
||
// 注意:这里我们需要判断是正常删除还是意外关闭
|
||
// 由于 deviceDelete 会主动关闭 worker,我们检查持久化存储中是否仍然存在此设备
|
||
try {
|
||
const deviceExists = await this.devicePersistence.deviceExists(deviceId);
|
||
if (deviceExists) {
|
||
console.log(`🗑️ Device ${deviceId} closure appears to be from deviceDelete, cleaning up persistent data...`);
|
||
await this.cleanupDeviceData(deviceId);
|
||
} else {
|
||
console.log(`ℹ️ Device ${deviceId} closure - no persistent data cleanup needed`);
|
||
}
|
||
} catch (error) {
|
||
console.error(`❌ Error checking device existence during closure: ${(error as Error).message}`);
|
||
}
|
||
|
||
console.log(`✅ Device worker closure handling completed for ${deviceId}`);
|
||
}
|
||
|
||
// 只停止设备worker,不删除持久化数据(用于应用退出时)
|
||
private async stopDeviceWorker(deviceId: string): Promise<void> {
|
||
const deviceWorker = this.deviceWorkers.get(deviceId);
|
||
if (!deviceWorker) {
|
||
console.warn(`⚠️ Device worker ${deviceId} not found`);
|
||
return;
|
||
}
|
||
|
||
console.log(`🛑 Stopping device worker ${deviceId} (preserving persistence)`);
|
||
|
||
return new Promise<void>((resolve) => {
|
||
const timeout = setTimeout(() => {
|
||
deviceWorker.worker.terminate();
|
||
this.deviceWorkers.delete(deviceId);
|
||
delete this.deviceKV[deviceId];
|
||
console.log(`🔄 Force terminated device worker ${deviceId}`);
|
||
resolve();
|
||
}, 5000);
|
||
|
||
deviceWorker.worker.onmessage = (event: MessageEvent<DeviceMainMessage>) => {
|
||
if (event.data.type === "status" && event.data.data?.status === "closed") {
|
||
clearTimeout(timeout);
|
||
deviceWorker.worker.terminate();
|
||
this.deviceWorkers.delete(deviceId);
|
||
delete this.deviceKV[deviceId];
|
||
console.log(`✅ Device worker ${deviceId} stopped gracefully`);
|
||
resolve();
|
||
}
|
||
};
|
||
|
||
deviceWorker.worker.postMessage({ type: "close" } as DeviceWorkerMessage);
|
||
});
|
||
}
|
||
|
||
// 停止设备模拟器并删除持久化数据(用于明确的设备删除)
|
||
private async stopDeviceSimulator(deviceId: string): Promise<void> {
|
||
const deviceWorker = this.deviceWorkers.get(deviceId);
|
||
if (!deviceWorker) {
|
||
console.warn(`⚠️ Device worker ${deviceId} not found`);
|
||
// 即使worker不存在,也要尝试清理持久化数据
|
||
await this.cleanupDeviceData(deviceId);
|
||
return;
|
||
}
|
||
|
||
console.log(`🛑 Stopping device simulator ${deviceId} (removing all data)`);
|
||
|
||
return new Promise<void>((resolve) => {
|
||
const timeout = setTimeout(() => {
|
||
console.log(`⚠️ Timeout waiting for ${deviceId} to close gracefully, forcing termination`);
|
||
deviceWorker.worker.terminate();
|
||
this.cleanupDeviceReferences(deviceId);
|
||
console.log(`🔄 Force terminated device worker ${deviceId}`);
|
||
resolve();
|
||
}, 5000);
|
||
|
||
deviceWorker.worker.onmessage = (event: MessageEvent<DeviceMainMessage>) => {
|
||
if (event.data.type === "status" && event.data.data?.status === "closed") {
|
||
clearTimeout(timeout);
|
||
deviceWorker.worker.terminate();
|
||
this.cleanupDeviceReferences(deviceId);
|
||
console.log(`✅ Device worker ${deviceId} stopped gracefully`);
|
||
resolve();
|
||
}
|
||
};
|
||
|
||
deviceWorker.worker.postMessage({ type: "close" } as DeviceWorkerMessage);
|
||
}).then(async () => {
|
||
// 完整清理所有设备数据
|
||
await this.cleanupDeviceData(deviceId);
|
||
});
|
||
}
|
||
|
||
// 清理内存中的设备引用
|
||
private cleanupDeviceReferences(deviceId: string): void {
|
||
console.log(`🧹 Cleaning up device references for ${deviceId}`);
|
||
|
||
// 清理设备Worker映射
|
||
if (this.deviceWorkers.has(deviceId)) {
|
||
this.deviceWorkers.delete(deviceId);
|
||
console.log(` ✅ Removed device worker reference`);
|
||
}
|
||
|
||
// 清理设备KV存储
|
||
if (this.deviceKV[deviceId]) {
|
||
delete this.deviceKV[deviceId];
|
||
console.log(` ✅ Removed device KV data`);
|
||
}
|
||
}
|
||
|
||
// 清理持久化的设备数据
|
||
private async cleanupDeviceData(deviceId: string): Promise<void> {
|
||
console.log(`🗑️ Cleaning up persistent data for ${deviceId}`);
|
||
|
||
try {
|
||
// 从持久化存储中移除设备
|
||
await this.devicePersistence.removeDevice(deviceId);
|
||
console.log(` ✅ Device ${deviceId} removed from persistent storage`);
|
||
|
||
// 验证删除是否成功
|
||
const stillExists = await this.devicePersistence.deviceExists(deviceId);
|
||
if (stillExists) {
|
||
console.warn(` ⚠️ Device ${deviceId} still exists in persistent storage after deletion attempt`);
|
||
} else {
|
||
console.log(` ✅ Verified: Device ${deviceId} successfully removed from persistent storage`);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error(` ❌ Failed to remove device ${deviceId} from persistent storage:`, (error as Error).message);
|
||
|
||
// 尝试强制清理
|
||
console.log(` 🔄 Attempting force cleanup for ${deviceId}`);
|
||
try {
|
||
const devices = await this.devicePersistence.loadDevices();
|
||
const filteredDevices = devices.filter(d => d.deviceId !== deviceId);
|
||
await this.devicePersistence.saveDevices(filteredDevices);
|
||
console.log(` ✅ Force cleanup completed for ${deviceId}`);
|
||
} catch (forceError) {
|
||
console.error(` ❌ Force cleanup failed:`, (forceError as Error).message);
|
||
}
|
||
}
|
||
|
||
console.log(`🎯 Device ${deviceId} cleanup completed`);
|
||
}
|
||
|
||
private async stopAllDeviceWorkers(): Promise<void> {
|
||
console.log("🛑 Stopping all device workers...");
|
||
// 使用新的stopDeviceWorker方法,只停止worker但保留持久化数据
|
||
const stopPromises = Array.from(this.deviceWorkers.keys()).map(deviceId => this.stopDeviceWorker(deviceId));
|
||
await Promise.all(stopPromises);
|
||
console.log("✅ All device workers stopped");
|
||
}
|
||
|
||
getDeviceKV(): DeviceKV {
|
||
return { ...this.deviceKV };
|
||
}
|
||
|
||
getDeviceCount(): number {
|
||
return this.deviceWorkers.size;
|
||
}
|
||
|
||
private getFunctionCodeDescription(fnCode: string): string {
|
||
const descriptions: Record<string, string> = {
|
||
"1": "Read Coils (读线圈)",
|
||
"2": "Read Discrete Inputs (读离散输入)",
|
||
"3": "Read Holding Registers (读保持寄存器)",
|
||
"4": "Read Input Registers (读输入寄存器)",
|
||
"5": "Write Single Coil (写单个线圈)",
|
||
"6": "Write Single Register (写单个寄存器)",
|
||
"15": "Write Multiple Coils (写多个线圈)",
|
||
"16": "Write Multiple Registers (写多个寄存器)"
|
||
};
|
||
return descriptions[fnCode] || `Unknown Function Code (未知功能码 ${fnCode})`;
|
||
}
|
||
|
||
private async listPersistedDevices() {
|
||
const devices = await this.devicePersistence.loadDevices();
|
||
console.log(`📁 Persisted Devices (${devices.length}):`);
|
||
devices.forEach((device, index) => {
|
||
console.log(`${index + 1}. ${device.deviceId}:`);
|
||
console.log(` Name: ${device.deviceInfo.deviceName} (${device.deviceInfo.brandName})`);
|
||
console.log(` Address: ${device.deviceInfo.ip}:${device.deviceInfo.port}/${device.deviceInfo.slaveId}`);
|
||
console.log(` Protocol: ${device.deviceInfo.protocolType}`);
|
||
console.log(` Created: ${device.createdAt}`);
|
||
console.log(` Last Activity: ${device.lastActivity}`);
|
||
|
||
// 显示寄存器配置信息
|
||
if (device.registersConfig) {
|
||
console.log(` 📝 Registers Configuration:`);
|
||
try {
|
||
const registers = JSON.parse(device.registersConfig);
|
||
console.log(` Total Registers: ${registers.length}`);
|
||
registers.forEach((reg: any, regIndex: number) => {
|
||
console.log(` [${regIndex + 1}] ${reg.name}:`);
|
||
console.log(` Function Code: ${reg.fnCode} (${this.getFunctionCodeDescription(reg.fnCode)})`);
|
||
console.log(` Address: ${reg.regAddress}`);
|
||
console.log(` Count: ${reg.regCount}`);
|
||
});
|
||
} catch (error) {
|
||
console.log(` ❌ Invalid JSON format: ${device.registersConfig}`);
|
||
}
|
||
} else {
|
||
console.log(` 📝 Registers Configuration: None`);
|
||
}
|
||
|
||
console.log(); // 空行分隔
|
||
});
|
||
}
|
||
|
||
private async clearPersistedDevices() {
|
||
await this.devicePersistence.saveDevices([]);
|
||
console.log("📁 All persisted devices cleared");
|
||
}
|
||
}
|
||
|
||
// 主程序入口
|
||
if (import.meta.main) {
|
||
const manager = new SimulatorManager();
|
||
|
||
// 重试配置
|
||
const RETRY_CONFIG = {
|
||
maxRetries: 10, // 最大重试次数
|
||
initialDelay: 1000, // 初始延迟 1秒
|
||
maxDelay: 30000, // 最大延迟 30秒
|
||
backoffMultiplier: 2 // 指数退避倍数
|
||
};
|
||
|
||
let retryCount = 0;
|
||
let currentDelay = RETRY_CONFIG.initialDelay;
|
||
|
||
// 延迟函数
|
||
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||
|
||
// 启动函数
|
||
const startSimulator = async (): Promise<void> => {
|
||
try {
|
||
console.log(`🚀 Starting simulator (attempt ${retryCount + 1}/${RETRY_CONFIG.maxRetries + 1})...`);
|
||
|
||
await manager.initialize();
|
||
await manager.startAllWorkers();
|
||
|
||
console.log(`
|
||
🎯 All ${manager.getWorkerCount()} AGV workers started successfully!
|
||
Starting interactive mode...
|
||
`);
|
||
|
||
// 重置重试计数器
|
||
retryCount = 0;
|
||
currentDelay = RETRY_CONFIG.initialDelay;
|
||
|
||
return; // 成功启动,退出重试循环
|
||
|
||
} catch (error) {
|
||
const errorMessage = (error as Error).message;
|
||
console.error(`❌ Startup failed (attempt ${retryCount + 1}):`, errorMessage);
|
||
|
||
retryCount++;
|
||
|
||
if (retryCount > RETRY_CONFIG.maxRetries) {
|
||
console.error(`💥 Maximum retry attempts (${RETRY_CONFIG.maxRetries}) exceeded. Giving up.`);
|
||
console.error("🔧 Please check your configuration and network connectivity.");
|
||
Deno.exit(1);
|
||
}
|
||
|
||
console.log(`⏳ Waiting ${currentDelay / 1000}s before retry ${retryCount + 1}...`);
|
||
await delay(currentDelay);
|
||
|
||
// 指数退避,但不超过最大延迟
|
||
currentDelay = Math.min(
|
||
currentDelay * RETRY_CONFIG.backoffMultiplier,
|
||
RETRY_CONFIG.maxDelay
|
||
);
|
||
|
||
// 递归重试
|
||
return startSimulator();
|
||
}
|
||
};
|
||
|
||
// 启动模拟器
|
||
await startSimulator();
|
||
|
||
// 处理进程信号
|
||
Deno.addSignalListener("SIGINT", async () => {
|
||
console.log("\n⚠️ Received SIGINT, shutting down...");
|
||
await manager.stopAllWorkers();
|
||
Deno.exit(0);
|
||
});
|
||
|
||
// SIGTERM只在非Windows系统支持
|
||
if (Deno.build.os !== "windows") {
|
||
Deno.addSignalListener("SIGTERM", async () => {
|
||
console.log("\n⚠️ Received SIGTERM, shutting down...");
|
||
await manager.stopAllWorkers();
|
||
Deno.exit(0);
|
||
});
|
||
}
|
||
|
||
// 启动交互模式
|
||
await manager.startInteractiveMode();
|
||
}
|