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

使用pyserial录制二进制传感器数据并转换为可读输出方案咨询

二进制传感器数据解析实现方案

前置依赖

你需要用到Python标准库即可完成核心功能,不需要额外依赖:

  • struct:负责二进制和float32类型的转换
  • html:如果你的数据是从带HTML转义的文本中提取的,需要用它还原原始字节
  • 可选依赖crcmod:可以快速实现CRC校验,也可以直接用下方手动实现的CRC16函数

核心解析步骤

帧结构确认

首先确认单帧的结构总长度:

总长度 = 3字节头部(同步头+采样计数+包长度) + 6*4字节浮点数据 + 2字节CRC = 29字节

你可以根据包长度字段的值做校验,避免解析错误。

步骤1:原始数据预处理

如果你的数据是从网页复制的带有>/<这类HTML转义符的,先做转义还原:

import html
raw_str = r"你的bytearray字面量字符串"
fixed_str = html.unescape(raw_str)
raw_data = eval(fixed_str) # 得到原始bytearray

步骤2:帧同步与粘包处理

串口数据是流式传输,存在半帧/多帧粘包的情况,需要做数据缓存和同步头匹配:

  • 维护一个全局缓存buffer = bytearray(),每次新来的字节都追加到缓存末尾
  • 循环查找缓存中0x7E同步头的位置,如果找到就尝试读取后续的包长度字段,判断缓存长度是否足够解析完整一帧
  • 完整解析一帧后将对应字节从缓存中移除,剩余字节留到下次解析

步骤3:CRC16校验

校验通过的帧才做解析,避免脏数据:

def crc16_modbus(data: bytes) -> int:
    crc = 0xFFFF
    for pos in data:
        crc ^= pos
        for _ in range(8):
            if (crc & 1) != 0:
                crc >>= 1
                crc ^= 0xA001
            else:
                crc >>= 1
    return crc

校验逻辑:取除了最后2字节的帧数据计算CRC,和最后2字节的小端uint16值对比,相等则校验通过。如果你的传感器用的是其他CRC多项式,修改上面的魔数即可。

步骤4:字段解析

用struct模块解包浮点数据,注意匹配传感器的字节序(大多数嵌入式设备为小端序,格式串用<,大端序用>):

import struct

def parse_frame(frame: bytes) -> dict:
    # 帧结构:[0x7E, 采样计数, 包长度, 6个float, CRC低, CRC高]
    sample_cnt = frame[1]
    # 解包6个float:前3个是IACC,后3个是IOMG
    payload = frame[3:3+24]
    iacc_x, iacc_y, iacc_z, iomg_x, iomg_y, iomg_z = struct.unpack('<6f', payload)
    return {
        "sample_cnt": sample_cnt,
        "iacc": [round(iacc_x,6), round(iacc_y,6), round(iacc_z,6)],
        "iomg": [round(iomg_x,6), round(iomg_y,6), round(iomg_z,6)]
    }

可读输出方案

你可以选择两种输出方式:

  • 控制台格式化输出:按行打印采样计数和两个传感器的三轴数值,方便调试
  • 持久化存储为CSV文件:表头设置为采样计数,IACC_X,IACC_Y,IACC_Z,IOMG_X,IOMG_Y,IOMG_Z,方便后续用Excel、Python pandas等工具分析

完整测试示例

if __name__ == "__main__":
    # 这里替换为你实际的原始数据预处理后的bytearray
    import html
    raw_str = r"bytearray(b'~P\x1a\x004&gt;e\x7f!&gt;\xa2\xfa\x1dA\xff\x05]\xbd\xf5\xe6\x88\xbc!\x8eL\xbc1\xd5IV~Q\x1a\x004&gt;bC\x1b&gt;^j\x1dA00\xee&lt;\xba\xf8\x88\xbb!\x8e\x00\x00\x00\x00O\xc7~R\x1a\x009&gt;\x98k\x1a&gt;Y\xf2\x1dA\x0f\x14*=\xa9\xb1\x88\xbb!\x8e\xaa&lt;\xa9\xb1\xe38~S\x1a\x00;&gt;\xa9\x0f\x19&gt;HN\x1dA&gt;\x02\x99=\xe4\x9f\xee\xbc\xba\xf8\x88;!\x8e\xa5`~T\x1a\x00&gt;&gt;\xccW\x1c&gt;j\x96\x1cA\xae\xfb\x88\xbc!\x8e\x88;!\x8e\x08\xbd!\x8e\xb1%~U\x1a\x006&gt;x_\x19&gt;HN\x1dA\xaf\x19\x88\xbc!\x8e\x08\xbd!\x8e\xaa\xbc\xa9\xb1&lt;c~V\x1a\x004&gt;e\x7f\x15&gt;#\xca\x1dA00\x80=&gt;\x05\xcc\xbc1\xd5\x88;!\x8eZ\x05~W\x1a\x00&lt;&gt;\xbb\xb3\x17&gt;6\xaa\x1dAP.\x88\xbb!\x8e\xaa&lt;\xa9\xb1*=\xa9\xb1(;~X\x1a\x00?&gt;\xdd\xfb!&gt;\x9e\x82\x1dA_\x0f\x88\xbc!\x8e\x08\xbd!\x8e\x08\xbc!\x8es\x1f~Y\x1a\x00A&gt;\xf1\xdb\x1e&gt;\x80\xb2\x1cA\xae\xfb\x88\xbb!\x8e\x08&lt;!\x8e\x08\xbd!\x8e\xc0\xde~Z\x1a\x00;&gt;\xa9\x0f\x14&gt;\x11&amp;\x1cAn\xff\x08&lt;!\x8e\x88;!\x8e\x08\xbc!\x8e&gt;:~[\x1a\x00;&gt;\xa9\x0f\x10&gt;\xed\xa1\x1dA\xa0)\x08&lt;!\x8e\xee\xbc\xba\xf8;\xbdm\xc3\x1a\x10~\\\x1a\x00A&gt;\xf1\xdb\x19&gt;E\x12\x1dA0!L\xbc1\xd5\x08\xbc!\x8e\x08&lt;!\x8e\xd3\x0f~]\x1a\x003&gt;V\x17\x10&gt;\xed\xa1\x1dA\xc16\x88;!\x8e\xcc&lt;1\xd5\x88\xbc!\x8e\x07\x05~^\x1a\x00A&gt;\xf1\xdb\x10&gt;\xeae\x1cA\x1e\xf5\xee\xbc\xba\xf8\x08&lt;!\x8e\x88;!\x8e\x8dh~_\x1a\x00&lt;&gt;\xbb\xb3\x12&gt;\xfeE\x1dA\xff\x05\x88&lt;!\x8e\x08\xbd!\x8e\x19=\xe4\x9f\xcfd~`\x1a\x004&gt;g\xbb\x10&gt;\xed\xa1\x1dA &quot;\x08\xbc!\x8e\x08\xbc!\x8e\xaa\xbc\xa9\xb1M\xc8~a\x1a\x009&gt;\x98k\x12&gt;\xfb\t\x1dA0!\x00\x00\x00\x00\x08&lt;!\x8e\x88\xbb!\x8e\x12\xb4~b\x1a\x008&gt;\x8a\x03\x1c&gt;j\x96\x1dA\xef\x15]\xbd\xf5\xe6\x19=\xe4\x9f\x08\xbd!\x8e\xa3Z~c\x1a\x007&gt;\x85\x8b\x12&gt;\x00\x82\x1dA&gt;\x02\x08&lt;!\x8e\xcc&lt;1\xd5\x08\xbc!\x8e\x84\x11~d\x1a\x00&lt;&gt;\xbb\xb3\x13&gt;\x0f\xea\x1cAn\xff\x08&lt;!\x8e\xcc\xbc1\xd5*=\xa9\xb1@6~e\x1a\x00&lt;&gt;\xbd\xef\x14&gt;\x11&amp;\x1dA\xef\x15\x88\xbb!\x8e\x88\xbc!\x8e\x88\xbb!\x8e\xcc\xa9~f\x1a\x00&gt;&gt;\xccW\x1c&gt;j\x96\x1dA\x0f\x14\x80\xbd&gt;\x05\x88&lt;!\x8e\xcc&lt;1\xd5Y\xe1~g\x1a\x00E&gt;\x13$!&gt;\xa0\xbe\x1dA\xb0(\xaa\xbc\xa9\xb1*=\xa9\xb1\xee&lt;\xba\xf8R\xa1~h\x1a\x00@&gt;\xe07\x19&gt;HN\x1dA\xe04\xee&lt;\xba\xf8\xee&lt;\xba\xf8\x88;!\x8eI3~i\x1a\x00&lt;&gt;\xbd\xef\x19&gt;HN\x1dAO\x10*=\xa9\xb1L&lt;1\xd5\xee&lt;\xba\xf8$f~j\x1a\x00@&gt;\xe07%&gt;\xc5B\x1cA\xae\xfb\x88;!\x8e\x08\xbc!\x8eL\xbc1\xd5\xa4\x12~k\x1a\x004&gt;bC)&gt;\xec\x02\x1cAn\xff\x88\xbc!\x8e*=\xa9\xb1\x08\xbc!\x8e\xe0\xb1~l\x1a\x00-&gt;\x1d\xb3,&gt;\x0fK\x1dA\xcf\x08\x00\x00\x00\x00\x08&lt;!\x8e\xee&lt;\xba\xf8\x8b\x15~m\x1a\x00,&gt;\x0c\x0f&quot;&gt;\xa7r\x1dA\xef\x06L&lt;1\xd5\xcc\xbc1\xd5\x08&lt;!\x8e\x1eA~n\x1a\x005&gt;q\xab &gt;\x91V\x1cAm\xe1\xcc&lt;1\xd5\x08=!\x8e\xcc\xbc1\xd5\xb4\x8d~o\x1a\x00,&gt;\x0c\x0f.&gt;&quot;+\x1dA\x8f\x1b]\xbd\xf5\xe6\xaa\xbc\xa9\xb1;\xbdm\xc3\xcaN~p\x1a\x004&gt;bC&quot;&gt;\xa56\x1dAN\x01\x08&lt;!\x8e\x88\xbc!\x8e\x19=\xe4\x9f\n')"
    fixed_str = html.unescape(raw_str)
    raw_data = eval(fixed_str)
    buffer = bytearray()
    buffer.extend(raw_data)
    frame_len = 29 # 按上述帧结构计算的单帧总长度,可根据实际包长度字段调整
    
    while len(buffer) >= frame_len:
        # 找同步头
        sync_pos = buffer.find(0x7E)
        if sync_pos == -1:
            # 没有同步头清空缓存
            buffer.clear()
            break
        if sync_pos > 0:
            # 移除同步头之前的无效字节
            buffer = buffer[sync_pos:]
        if len(buffer) < frame_len:
            # 剩余字节不够一帧,等下次数据
            break
        # 取一帧数据
        frame = buffer[:frame_len]
        # CRC校验
        calc_crc = crc16_modbus(frame[:-2])
        real_crc = struct.unpack('<H', frame[-2:])[0]
        if calc_crc == real_crc:
            parsed = parse_frame(frame)
            # 打印输出
            print(f"采样计数:{parsed['sample_cnt']}, IACC:{parsed['iacc']}, IOMG:{parsed['iomg']}")
        # 移除已解析的帧
        buffer = buffer[frame_len:]

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 07:27:03