在票据打印、工业终端等场景中,设备常通过串口发送 ESC/P 协议的控制流来驱动针式打印机。这类数据流混合了文本、控制命令和光栅位图,如果只靠串口助手查看会非常杂乱。本文讲解如何用 Python 把一段 ESC/P 串行数据中的图像部分解析出来,并写出为 BMP 文件。

ESC/P 中的光栅图像命令
ESC/P 是爱普生制定的打印机控制语言。其中最常用的光栅图像指令是 m 和 * 开头的命令。例如 ESC * 用于进入位图模式,后面跟随参数指明垂直点数、每行字节数和位图数据。解析时要按字节流顺序读取,遇到 ESC 转义符就进入命令解析分支。
典型位图命令结构
- 0x1B 0x2A:ESC * 命令头
- 后续一个字节表示打印模式(如 0 表示单密度)
- 两个字节表示每行像素宽度(低位在前)
- 紧跟着对应数量的列数据字节
Python 解析与转换思路
我们可以把串口收到的 bytes 对象缓存起来,逐字节扫描。发现 ESC * 后,按协议读出宽度与数据,将每一个字节的 8 个位映射为黑白像素,最后使用 PIL 库保存为 BMP。
示例代码
下面示例演示如何从字节流提取一帧单色位图并写出 BMP:
import struct
from PIL import Image
def parse_esc_p_bitmap(data: bytes):
# data 为串口收到的 ESC/P 字节流
i = 0
width = 0
bitmap_rows = []
while i < len(data):
if data[i] == 0x1B and i + 1 < len(data) and data[i+1] == 0x2A:
# 遇到 ESC *
mode = data[i+2]
col_bytes = data[i+3] | (data[i+4] << 8)
width = col_bytes * 8
i += 5
row = []
for b in range(col_bytes):
byte = data[i]
i += 1
# 将一个字节转为 8 个像素,1 为黑
for bit in range(8):
if (byte >> (7 - bit)) & 1:
row.append(0)
else:
row.append(255)
bitmap_rows.append(row)
else:
i += 1
if not bitmap_rows:
return None
img = Image.new('L', (width, len(bitmap_rows)))
for y, row in enumerate(bitmap_rows):
for x, val in enumerate(row):
img.putpixel((x, y), val)
img.save('output.bmp')
return 'output.bmp'
# 假设 serial_data 是串口读取的字节
# serial_data = ser.read(4096)
# parse_esc_p_bitmap(serial_data)
注意事项
实际设备中 ESC/P 流可能包含压缩指令或多页内容,上述代码仅处理最基础的未压缩光栅块。若遇到 ESC . 等压缩命令,需要按对应算法解压后再拼装像素。另外,生成的 BMP 默认为单通道灰度,可用画图工具直接打开查看。