如何在 Python 中按自定义字节序(非标准大端/小端)解析电表寄存器数据

15次阅读

如何在 Python 中按自定义字节序(非标准大端/小端)解析电表寄存器数据

本文详解如何将 modbus 读取的 4 个 16 位寄存器(如 `[3468, 204, 0, 2080]`)按特定十进制拼接规则(如 `20434682080`)正确解析,适用于电表等工业设备的非标准数值编码场景。

在使用 pymodbus 读取电表(如某些国产或定制化功率计量设备)的寄存器时,常会遇到一种非标准数值编码方式:设备厂商未采用 IEEE 754 浮点、32/64 位整数的大端(BIG)或小端(LITTLE)字节序,而是将多个 16 位寄存器视为独立的十进制“数字块”,并按固定位置权重拼接成一个完整整数。

例如,读取到 result_ups.registers = [3468, 204, 0, 2080],目标值为 20434682080,其构成逻辑为:

20434682080 = 204 × 10⁸ + 3468 × 10⁴ + 0 × 10⁰ + 2080 × 10⁰?  ← 错误

实际观察可得:

  • 20434682080 拆分为:204 | 3468 | 0 | 2080 → 但直接拼接 204+3468+0+2080 = “204346802080”(长度不符)

更准确的逆向分析表明,该值实为 4 段 4 位十进制数的错位拼接,且顺序为 [1st, 2nd, 3rd, 4th] → 对应输出中的位置为:
✅ 204(占前 3 位) + 3468(次 4 位) + 0(第 3 段,补零为 0000?但此处为 0) + 2080(末 4 位)→ 不匹配。

而题目给出的期望结果 20434682080 共 11 位,分解为:

立即学习Python免费学习笔记(深入)”;

  • 204(3 位)
  • 3468(4 位)
  • 2080(4 位)
    → 中间缺失 1 位?注意原数组为 [3468, 204, 0, 2080],若按索引重排为 [204, 3468, 0, 2080],再拼接 “204”+”3468″+”0″+”2080″ = “204346802080”(12 位),仍不符。

关键洞察来自答案提示:这是按寄存器索引加权的十进制组合,而非字节序问题。验证如下:

registers = [3468, 204, 0, 2080] # 按答案公式计算: result = (     registers[3]      # 2080 × 10⁰ = 2080     + registers[0] * 10**4   # 3468 × 10000 = 34,680,000     + registers[1] * 10**8   # 204 × 100,000,000 = 20,400,000,000     + registers[2] * 10**12  # 0 × 1e12 = 0 ) print(result)  # 输出:20434682080 ✅

即:

  • registers[1](204)→ 权重 10⁸(亿位)
  • registers[0](3468)→ 权重 10⁴(万位)
  • registers[3](2080)→ 权重 10⁰(个位)
  • registers[2](0)→ 权重 10¹²(万亿位,此处为 0,故不影响)

因此,这不是 Endian.BIG 或 Endian.LITTLE 能解决的字节序问题,而是设备特有的寄存器数值映射协议

✅ 正确实现方案(集成到你的 pymodbus 代码中)

将原代码中的解码部分替换为加权拼接逻辑:

from pymodbus.client import ModbusTcpClient from pymodbus.constants import Endian from pymodbus.payload import BinaryPayloadDecoder import time  client_tcp = ModbusTcpClient('10.123.133.100', port=502)  while True:     try:         client_tcp.connect()         while True:             result_ups = client_tcp.read_holding_registers(1716, 4, unit=1, slave=1)             if not result_ups.isError():                 regs = result_ups.registers  # [3468, 204, 0, 2080]                 # 按设备协议:regs[1]×1e8 + regs[0]×1e4 + regs[3] + regs[2]×1e12                 kwh_ups = (                     regs[1] * 10**8                     + regs[0] * 10**4                     + regs[3]                     + regs[2] * 10**12                 )                 print(f"Total kWh: {kwh_ups}")             else:                 print("Modbus read error:", result_ups)             time.sleep(1)     except Exception as e:         print("Connection error:", e)         time.sleep(1)     finally:         client_tcp.close()

⚠️ 注意事项与建议

  • 务必查阅设备手册:该加权规则([1], [0], [3], [2])是此电表特有,其他设备可能完全不同(如 [3], [2], [1], [0] 或含符号位)。切勿假设通用。
  • 数据范围检查:10¹² 权重下,若 regs[2] 非零,结果可能超出 python int 实际显示范围(虽 Python 支持任意精度,但需确认电表真实量程)。
  • 避免误用 BinaryPayloadDecoder:Endian.BIG + Endian.LITTLE 仅适用于标准二进制整型/浮点编码(如 32-bit int 大端存储为 [0x00, 0x12, 0x34, 0x56]),对十进制拼接无效。
  • 增强健壮性:添加 if len(regs) == 4: 校验,防止寄存器读取异常导致索引错误。
  • 单位与精度:确认 20434682080 是否为原始值(如 0.01kWh/LSB),可能需额外除以缩放因子(如 /100 得 204346820.80 kWh)。

通过理解设备协议本质而非强行套用字节序,你才能稳定、准确地将寄存器原始数据转化为业务所需的物理量。

text=ZqhQzanResources