使用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>e\x7f!>\xa2\xfa\x1dA\xff\x05]\xbd\xf5\xe6\x88\xbc!\x8eL\xbc1\xd5IV~Q\x1a\x004>bC\x1b>^j\x1dA00\xee<\xba\xf8\x88\xbb!\x8e\x00\x00\x00\x00O\xc7~R\x1a\x009>\x98k\x1a>Y\xf2\x1dA\x0f\x14*=\xa9\xb1\x88\xbb!\x8e\xaa<\xa9\xb1\xe38~S\x1a\x00;>\xa9\x0f\x19>HN\x1dA>\x02\x99=\xe4\x9f\xee\xbc\xba\xf8\x88;!\x8e\xa5`~T\x1a\x00>>\xccW\x1c>j\x96\x1cA\xae\xfb\x88\xbc!\x8e\x88;!\x8e\x08\xbd!\x8e\xb1%~U\x1a\x006>x_\x19>HN\x1dA\xaf\x19\x88\xbc!\x8e\x08\xbd!\x8e\xaa\xbc\xa9\xb1<c~V\x1a\x004>e\x7f\x15>#\xca\x1dA00\x80=>\x05\xcc\xbc1\xd5\x88;!\x8eZ\x05~W\x1a\x00<>\xbb\xb3\x17>6\xaa\x1dAP.\x88\xbb!\x8e\xaa<\xa9\xb1*=\xa9\xb1(;~X\x1a\x00?>\xdd\xfb!>\x9e\x82\x1dA_\x0f\x88\xbc!\x8e\x08\xbd!\x8e\x08\xbc!\x8es\x1f~Y\x1a\x00A>\xf1\xdb\x1e>\x80\xb2\x1cA\xae\xfb\x88\xbb!\x8e\x08<!\x8e\x08\xbd!\x8e\xc0\xde~Z\x1a\x00;>\xa9\x0f\x14>\x11&\x1cAn\xff\x08<!\x8e\x88;!\x8e\x08\xbc!\x8e>:~[\x1a\x00;>\xa9\x0f\x10>\xed\xa1\x1dA\xa0)\x08<!\x8e\xee\xbc\xba\xf8;\xbdm\xc3\x1a\x10~\\\x1a\x00A>\xf1\xdb\x19>E\x12\x1dA0!L\xbc1\xd5\x08\xbc!\x8e\x08<!\x8e\xd3\x0f~]\x1a\x003>V\x17\x10>\xed\xa1\x1dA\xc16\x88;!\x8e\xcc<1\xd5\x88\xbc!\x8e\x07\x05~^\x1a\x00A>\xf1\xdb\x10>\xeae\x1cA\x1e\xf5\xee\xbc\xba\xf8\x08<!\x8e\x88;!\x8e\x8dh~_\x1a\x00<>\xbb\xb3\x12>\xfeE\x1dA\xff\x05\x88<!\x8e\x08\xbd!\x8e\x19=\xe4\x9f\xcfd~`\x1a\x004>g\xbb\x10>\xed\xa1\x1dA "\x08\xbc!\x8e\x08\xbc!\x8e\xaa\xbc\xa9\xb1M\xc8~a\x1a\x009>\x98k\x12>\xfb\t\x1dA0!\x00\x00\x00\x00\x08<!\x8e\x88\xbb!\x8e\x12\xb4~b\x1a\x008>\x8a\x03\x1c>j\x96\x1dA\xef\x15]\xbd\xf5\xe6\x19=\xe4\x9f\x08\xbd!\x8e\xa3Z~c\x1a\x007>\x85\x8b\x12>\x00\x82\x1dA>\x02\x08<!\x8e\xcc<1\xd5\x08\xbc!\x8e\x84\x11~d\x1a\x00<>\xbb\xb3\x13>\x0f\xea\x1cAn\xff\x08<!\x8e\xcc\xbc1\xd5*=\xa9\xb1@6~e\x1a\x00<>\xbd\xef\x14>\x11&\x1dA\xef\x15\x88\xbb!\x8e\x88\xbc!\x8e\x88\xbb!\x8e\xcc\xa9~f\x1a\x00>>\xccW\x1c>j\x96\x1dA\x0f\x14\x80\xbd>\x05\x88<!\x8e\xcc<1\xd5Y\xe1~g\x1a\x00E>\x13$!>\xa0\xbe\x1dA\xb0(\xaa\xbc\xa9\xb1*=\xa9\xb1\xee<\xba\xf8R\xa1~h\x1a\x00@>\xe07\x19>HN\x1dA\xe04\xee<\xba\xf8\xee<\xba\xf8\x88;!\x8eI3~i\x1a\x00<>\xbd\xef\x19>HN\x1dAO\x10*=\xa9\xb1L<1\xd5\xee<\xba\xf8$f~j\x1a\x00@>\xe07%>\xc5B\x1cA\xae\xfb\x88;!\x8e\x08\xbc!\x8eL\xbc1\xd5\xa4\x12~k\x1a\x004>bC)>\xec\x02\x1cAn\xff\x88\xbc!\x8e*=\xa9\xb1\x08\xbc!\x8e\xe0\xb1~l\x1a\x00->\x1d\xb3,>\x0fK\x1dA\xcf\x08\x00\x00\x00\x00\x08<!\x8e\xee<\xba\xf8\x8b\x15~m\x1a\x00,>\x0c\x0f">\xa7r\x1dA\xef\x06L<1\xd5\xcc\xbc1\xd5\x08<!\x8e\x1eA~n\x1a\x005>q\xab >\x91V\x1cAm\xe1\xcc<1\xd5\x08=!\x8e\xcc\xbc1\xd5\xb4\x8d~o\x1a\x00,>\x0c\x0f.>"+\x1dA\x8f\x1b]\xbd\xf5\xe6\xaa\xbc\xa9\xb1;\xbdm\xc3\xcaN~p\x1a\x004>bC">\xa56\x1dAN\x01\x08<!\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
相关产品推荐
相关产品推荐

