190 lines
6.0 KiB
TypeScript
190 lines
6.0 KiB
TypeScript
// device_protocol_config.ts - 设备协议配置示例
|
||
// 展示如何根据设备信息自动配置Modbus参数
|
||
import { createModuleLogger } from "./debug_logger.ts";
|
||
|
||
const logger = createModuleLogger("REGISTER_CONFIG");
|
||
|
||
// 获取默认轮询间隔(基于设备品牌优化)
|
||
export function getDefaultPollInterval(brandName?: string): number {
|
||
// 根据设备品牌返回优化的轮询间隔
|
||
const interval = (() => {
|
||
switch (brandName) {
|
||
case "西门子": return 500; // 西门子设备响应较快
|
||
case "台达": return 1000; // 台达设备标准间隔
|
||
case "三菱": return 1500; // 三菱设备较保守间隔
|
||
default: return 2000; // 通用设备默认间隔
|
||
}
|
||
})();
|
||
|
||
logger.debug("⏱️ Poll interval determined", {
|
||
brandName: brandName || "unknown",
|
||
interval: `${interval}ms`,
|
||
reason: brandName ? "brand_specific" : "default"
|
||
});
|
||
|
||
return interval;
|
||
}
|
||
|
||
// 寄存器定义接口
|
||
export interface RegisterDefinition {
|
||
fnCode: string; // 功能码:1=读线圈,2=读离散输入,3=读保持寄存器,4=读输入寄存器,6=写单个寄存器
|
||
name: string; // 寄存器名称
|
||
bind?: string; // 可选:绑定 ID
|
||
regCount: string; // 寄存器数量
|
||
regAddress: string; // 寄存器地址
|
||
}
|
||
|
||
// 功能码映射到Modbus函数
|
||
const FUNCTION_CODE_MAP: Record<string, "readHoldingRegisters" | "readInputRegisters" | "readCoils" | "readDiscreteInputs"> = {
|
||
"1": "readCoils", // 读线圈
|
||
"2": "readDiscreteInputs", // 读离散输入
|
||
"3": "readHoldingRegisters", // 读保持寄存器
|
||
"4": "readInputRegisters", // 读输入寄存器
|
||
"6": "readHoldingRegisters" // 写单个寄存器(读取时用保持寄存器)
|
||
};
|
||
|
||
// 从寄存器定义创建轮询配置
|
||
export function createModbusPollConfigFromRegisters(registers: RegisterDefinition[]) {
|
||
logger.info("🔄 Creating Modbus polling configuration", {
|
||
totalRegisters: registers.length,
|
||
registers: registers.map(r => ({ name: r.name, fnCode: r.fnCode, address: r.regAddress }))
|
||
});
|
||
|
||
const pollConfig = [];
|
||
const skippedRegisters = [];
|
||
|
||
for (const reg of registers) {
|
||
const fnCode = reg.fnCode;
|
||
const modbusFunction = FUNCTION_CODE_MAP[fnCode];
|
||
|
||
logger.trace("🔍 Processing register", {
|
||
name: reg.name,
|
||
fnCode,
|
||
address: reg.regAddress,
|
||
count: reg.regCount,
|
||
modbusFunction
|
||
});
|
||
|
||
if (!modbusFunction) {
|
||
const warning = `Unsupported function code: ${fnCode} for register ${reg.name}`;
|
||
logger.warn("⚠️ " + warning, {
|
||
register: reg,
|
||
supportedCodes: Object.keys(FUNCTION_CODE_MAP)
|
||
});
|
||
console.warn(warning);
|
||
skippedRegisters.push({ register: reg.name, reason: "unsupported_function_code" });
|
||
continue;
|
||
}
|
||
|
||
// 只为读取功能码创建轮询配置(功能码6是写操作,但我们也可以读取它的当前值)
|
||
if (["1", "2", "3", "4", "6"].includes(fnCode)) {
|
||
const pollItem = {
|
||
fn: modbusFunction,
|
||
start: parseInt(reg.regAddress),
|
||
len: parseInt(reg.regCount),
|
||
mqttKey: `device/register/${reg.name}`,
|
||
bind: reg.bind || reg.name
|
||
};
|
||
|
||
logger.debug("✅ Created poll config for register", {
|
||
register: reg.name,
|
||
pollItem
|
||
});
|
||
|
||
pollConfig.push(pollItem);
|
||
} else {
|
||
logger.warn("⚠️ Function code not supported for polling", {
|
||
register: reg.name,
|
||
fnCode,
|
||
reason: "not_readable"
|
||
});
|
||
skippedRegisters.push({ register: reg.name, reason: "not_readable" });
|
||
}
|
||
}
|
||
|
||
logger.info("🎯 Polling configuration creation completed", {
|
||
totalInput: registers.length,
|
||
totalOutput: pollConfig.length,
|
||
skipped: skippedRegisters.length,
|
||
skippedDetails: skippedRegisters,
|
||
successRate: `${((pollConfig.length / registers.length) * 100).toFixed(1)}%`
|
||
});
|
||
|
||
return pollConfig;
|
||
}
|
||
|
||
// 解析寄存器字符串
|
||
export function parseRegistersFromString(registersStr: string): RegisterDefinition[] {
|
||
logger.debug("📝 Parsing registers string", {
|
||
inputLength: registersStr.length,
|
||
inputPreview: registersStr.substring(0, 100) + (registersStr.length > 100 ? '...' : '')
|
||
});
|
||
|
||
try {
|
||
const parsed = JSON.parse(registersStr);
|
||
|
||
logger.trace("🔍 JSON parsing successful", {
|
||
parsedType: typeof parsed,
|
||
isArray: Array.isArray(parsed),
|
||
length: Array.isArray(parsed) ? parsed.length : 'not_array'
|
||
});
|
||
|
||
if (!Array.isArray(parsed)) {
|
||
logger.error("❌ Parsed data is not an array", { parsedType: typeof parsed, parsed });
|
||
return [];
|
||
}
|
||
|
||
const registers = parsed as RegisterDefinition[];
|
||
const validRegisters = [];
|
||
const invalidRegisters = [];
|
||
|
||
for (const reg of registers) {
|
||
const isValid = reg.fnCode && reg.name && reg.regCount && reg.regAddress;
|
||
|
||
logger.trace("🔍 Validating register", {
|
||
register: reg,
|
||
isValid,
|
||
missingFields: {
|
||
fnCode: !reg.fnCode,
|
||
name: !reg.name,
|
||
regCount: !reg.regCount,
|
||
regAddress: !reg.regAddress
|
||
}
|
||
});
|
||
|
||
if (isValid) {
|
||
validRegisters.push(reg);
|
||
} else {
|
||
invalidRegisters.push({
|
||
register: reg,
|
||
missingFields: Object.keys(reg).filter(key => !reg[key as keyof RegisterDefinition])
|
||
});
|
||
}
|
||
}
|
||
|
||
logger.info("✅ Register parsing completed", {
|
||
totalInput: registers.length,
|
||
validRegisters: validRegisters.length,
|
||
invalidRegisters: invalidRegisters.length,
|
||
invalidDetails: invalidRegisters,
|
||
successRate: `${((validRegisters.length / registers.length) * 100).toFixed(1)}%`
|
||
});
|
||
|
||
return validRegisters;
|
||
} catch (error) {
|
||
logger.error("❌ Failed to parse registers string", {
|
||
error: (error as Error).message,
|
||
inputString: registersStr
|
||
});
|
||
console.error("Failed to parse registers string:", error);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
|
||
|
||
export default {
|
||
getDefaultPollInterval,
|
||
createModbusPollConfigFromRegisters,
|
||
parseRegistersFromString
|
||
};
|