如何在Node-RED中高效读取西门子PLC的结构化数据(区域与加热器)
高效读取西门子PLC数组/Struct到Node-RED的方案
核心思路
node-red-contrib-s7支持一次性读取连续的PLC数据块字节,你只需要确认数组的起始地址和总字节长度,读取后用Node-RED函数节点将原始Buffer解析为结构化JSON即可,无需为每个变量单独创建节点。
步骤1:确认PLC中数组的内存布局
首先在TIA Portal中打开对应的DB块,查看zones数组的起始地址和每个区域struct的总字节数:
- 比如
zones数组起始于DB1.DBB0,每个区域struct占12字节,数组有5个元素,那么总读取长度就是5*12=60字节。 - 注意西门子PLC的struct会自动做字节对齐,务必以TIA Portal中显示的实际字节偏移为准(比如布尔类型会占1字节,实数占4字节,不足对齐字节的会填充空白)。
步骤2:配置node-red-contrib-s7读取节点
- 添加一个
s7 in节点,配置好PLC的连接参数(IP、机架号、槽号等)。 - 在节点的"Address"字段填写数组的起始地址和长度:
- 格式示例:
DB1,DBB0,60(表示读取DB1从字节0开始的60个字节)。 - 如果是按字读取,也可以用
DB1,DBW0,30(30个字=60字节)。
- 格式示例:
步骤3:用函数节点解析Buffer为结构化JSON
将s7 in节点的输出连接到一个function节点,编写解析代码,根据你PLC中struct的结构提取数据。以下是示例代码(需根据实际struct结构调整):
// 读取到的原始字节数据存在msg.payload中 const rawBuffer = msg.payload; const zones = []; // 配置参数:根据你的PLC struct调整 const zoneByteSize = 12; // 单个区域struct的总字节数 const heaterCountPerZone = 2; // 每个区域的加热器数量 const tempByteLength = 4; // 温度(实数)占4字节 const statusByteOffset = 4; // 加热器状态在区域内的字节偏移 // 循环解析每个区域 for (let zoneIdx = 0; zoneIdx < rawBuffer.length / zoneByteSize; zoneIdx++) { const zoneBaseOffset = zoneIdx * zoneByteSize; const currentZone = { heaters: [] }; // 循环解析区域内的每个加热器 for (let heaterIdx = 0; heaterIdx < heaterCountPerZone; heaterIdx++) { const heaterBaseOffset = zoneBaseOffset + heaterIdx * (tempByteLength + 2); // 考虑对齐填充 currentZone.heaters.push({ // 读取实数温度(注意字节序:西门子默认是大端,用readFloatBE;如果不对试readFloatLE) temperature: rawBuffer.readFloatBE(heaterBaseOffset), // 读取布尔状态(取字节的第0位) status: (rawBuffer.readUInt8(heaterBaseOffset + tempByteLength) & 0x01) === 1 }); } zones.push(currentZone); } // 将结构化数据作为新的payload输出 msg.payload = zones; return msg;
最佳实践
- 复用解析逻辑:将解析函数封装为可复用的子流,后续新增区域或加热器时,只需修改函数中的配置参数和解析逻辑,无需重新搭建节点。
- 动态适配数组长度:如果数组长度可能变化,可以在PLC的DB中单独存储数组的当前元素数量,先读取这个数值,再根据数值计算需要读取的总字节数,实现动态读取。
- 调试验证:先用
s7 in节点读取单个区域的字节,在调试面板查看Buffer内容,验证解析代码的正确性,再扩展到整个数组。 - 性能优化:一次性读取连续的字节块比多次读取单个变量效率高很多,尤其适合大量数据的场景。
内容的提问来源于stack exchange,提问作者Gortex




