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
|
|||
|
};
|