You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在JavaScript中将MQTT缓冲区数据转换为预定义结构体

通用Buffer转结构体方案

针对你的需求,最简洁的方式是用元数据定义结构体的成员规则,然后写一个通用解析函数自动遍历解析,避免手动逐个处理字段。具体步骤如下:

1. 定义结构体元数据配置

把PLC结构体的每个成员信息(名称、类型、字节长度)整理成一个配置数组,后续解析全靠这个配置驱动。如果有嵌套结构体,还可以递归配置:

// 结构体元数据配置,顺序要和PLC发送的字节顺序完全一致
const structSchema = [
  { name: 'Running', type: 'bool16', byteLength: 2 },
  { name: 'Faulted', type: 'bool16', byteLength: 2 },
  { name: 'InPosition', type: 'bool16', byteLength: 2 },
  { name: 'ActualPosition', type: 'float64', byteLength: 8 },
  { name: 'TargetPosition', type: 'float64', byteLength: 8 },
  { name: 'Mode', type: 'int16', byteLength: 2 },
  { name: 'SubMode', type: 'uint16', byteLength: 2 }, // 注意:你说SubMode是8位但占2字节,这里按实际传输的2字节无符号整数处理
  // 嵌套结构体示例(如果需要):
  // { name: 'NestedStruct', type: 'struct', byteLength: 10, subSchema: [/* 子结构体配置 */] }
];

2. 编写通用解析函数

基于上面的配置,写一个通用函数,通过偏移量跟踪解析位置,根据不同类型调用Buffer对应的读取方法:

function parseBufferToStruct(buffer, schema) {
  const result = {};
  let offset = 0;

  for (const field of schema) {
    switch (field.type) {
      case 'bool16':
        // 取第一个字节判断(根据PLC实际存储逻辑调整,这里假设非零为true)
        result[field.name] = buffer.readUInt8(offset) !== 0;
        offset += field.byteLength;
        break;
      case 'float64':
        // 注意:这里用readDoubleLE(小端字节序),如果PLC是大端用readDoubleBE
        result[field.name] = buffer.readDoubleLE(offset);
        offset += field.byteLength;
        break;
      case 'int16':
        result[field.name] = buffer.readInt16LE(offset);
        offset += field.byteLength;
        break;
      case 'uint16':
        result[field.name] = buffer.readUInt16LE(offset);
        offset += field.byteLength;
        break;
      case 'struct':
        // 递归解析嵌套结构体
        const subBuffer = buffer.slice(offset, offset + field.byteLength);
        result[field.name] = parseBufferToStruct(subBuffer, field.subSchema);
        offset += field.byteLength;
        break;
      // 可以扩展更多类型,比如uint32、int32等
      default:
        throw new Error(`不支持的字段类型:${field.type}`);
    }
  }

  return result;
}

3. 集成到MQTT订阅逻辑里

把解析函数放到你的MQTT message事件处理中,直接得到可用的结构体:

const mqtt = require('mqtt');

const client = mqtt.connect('mqtt://127.0.0.1');

// 结构体元数据配置(同上)
const structSchema = [
  { name: 'Running', type: 'bool16', byteLength: 2 },
  { name: 'Faulted', type: 'bool16', byteLength: 2 },
  { name: 'InPosition', type: 'bool16', byteLength: 2 },
  { name: 'ActualPosition', type: 'float64', byteLength: 8 },
  { name: 'TargetPosition', type: 'float64', byteLength: 8 },
  { name: 'Mode', type: 'int16', byteLength: 2 },
  { name: 'SubMode', type: 'uint16', byteLength: 2 },
];

// 通用解析函数(同上)
function parseBufferToStruct(buffer, schema) {
  const result = {};
  let offset = 0;

  for (const field of schema) {
    switch (field.type) {
      case 'bool16':
        result[field.name] = buffer.readUInt8(offset) !== 0;
        offset += field.byteLength;
        break;
      case 'float64':
        result[field.name] = buffer.readDoubleLE(offset);
        offset += field.byteLength;
        break;
      case 'int16':
        result[field.name] = buffer.readInt16LE(offset);
        offset += field.byteLength;
        break;
      case 'uint16':
        result[field.name] = buffer.readUInt16LE(offset);
        offset += field.byteLength;
        break;
      case 'struct':
        const subBuffer = buffer.slice(offset, offset + field.byteLength);
        result[field.name] = parseBufferToStruct(subBuffer, field.subSchema);
        offset += field.byteLength;
        break;
      default:
        throw new Error(`不支持的字段类型:${field.type}`);
    }
  }

  return result;
}

client.on('connect', function () {
  client.subscribe('potatoes', function (err) {
    if (!err) {
      client.publish('potatoes', 'If you get this message, you’re subscribed');
    }
  });
});

client.on('message', function (topic, message) {
  // 解析Buffer为结构体
  const myStruct = parseBufferToStruct(message, structSchema);
  
  // 现在可以直接用结构体字段了,比如绑定网页指示器
  console.log('设备状态:', myStruct);
  // 示例:更新网页红色指示器
  // document.getElementById('fault-indicator').style.backgroundColor = myStruct.Faulted ? 'red' : 'green';
});

关键注意事项

  • 字节序匹配:PLC的字节序(大端/小端)必须和JS里的读取方法一致,比如PLC用小端就用readXXXLE,大端用readXXXBE,如果解析出来的数值异常,先检查字节序。
  • 字段顺序:配置数组的顺序必须和PLC发送的字节顺序完全一致,否则解析结果会错乱。
  • 嵌套结构体扩展:如果有多层嵌套,只需要在配置里添加subSchema字段,函数会自动递归解析,不用修改核心解析逻辑。
  • 类型兼容:对于你提到的SubMode是8位但占2字节的情况,这里按2字节无符号整数处理,取完整的16位值,如果PLC只用到低8位,你可以在解析后再做一次& 0xFF处理。

这样不管结构体多大、多复杂,只要维护好元数据配置,就能自动完成解析,不用手动写每个字段的提取逻辑。

内容的提问来源于stack exchange,提问作者roarznz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 02:20:26