一、小程序功能设计

1、自定义协议帧格式设计

(1)功能背景:

①在软件开发岗位的工作中,经常会涉及企业自定义的私有协议,这些私有协议不像CAN通信、USB通信、串口通信,没有专门的软件对数据流进行梳理,对于帧格式的管理,也只能依靠文档。

②为了减少频繁核对协议帧格式带来的重复工作量,可以设计程序实现协议模板存储功能,直接在小程序中编辑协议帧的各字段定义并保存为模板,这样,这个模板不仅维护起来比文档更高效,更关键的是能够用于做数据帧生成和数据帧解析。

(2)协议模板管理功能定义:

①新建协议模板:点击“新建”,输入模板名称,即可新建协议模板(在添加字段前必须新建协议模板,或载入已有的协议模板,否则添加的字段信息无法识别属于哪一个模板)。

②保存协议模板:在载入模板并对字段定义进行修改,或新建模板后,需要点击“保存模板”,才能将编辑的协议模板保存在磁盘中。

③载入协议模板:在下拉框中可选择已保存的模板,然后点击“载入”,即可将协议模板导入,后续可根据该模板生成或解析数据流,也可以继续编辑该协议模板(编辑模板后需要点击“保存模板”)。

④删除协议模板:在下拉框中选择废弃的协议模板,然后点击“删除模板”,即可将该协议模板移除。

(3)协议模板字段编辑功能定义:

①添加字段:点击“添加字段”,即可进入字段配置页面,完成字段配置后,字段定义列表会显示刚刚添加的字段内容(关于字段配置,将在下面详细展开),如果先前已有定义字段,则新添加的字段会追加到已定义字段的后面。

②删除字段:选中需要删除的字段,然后点击“删除选中”,即可将选中字段移除。

③编辑字段:双击字段定义列表的已定义字段,即可重新编辑字段配置。

(4)字段配置功能定义:

①普通字段类型:普通字段只定义字段名称、字段长度以及大小端(不定长字段自动配置为原始数据流,不做端序转换),根据字段长度不同,会细分为三类字段。

[1]定长字段:长度固定,能够配置字段的字节长度;对字段的数据内容没有要求,取决于更上层对该字段的定义。

[2]不定长-绑定前置长度字段:长度不固定,不需要配置字段的字节长度,但是需要绑定指示该不定长字段长度的其它字段,且指示长度的字段需要排在该不定长字段之前;对字段的数据内容没有要求,取决于更上层对该字段的定义。

[3]不定长-自由段:长度不固定,不需要配置字段的字节长度,不需要绑定指示该不定长字段长度的其它字段,但一个协议帧格式中只能存在一个自由长度段,否则在解析数据流时,小程序无法识别不同自由长度段的实际长度;对字段的数据内容没有要求,取决于更上层对该字段的定义。

②枚举字段类型:枚举字段需要定义字段名称、字段长度、大小端以及枚举定义,枚举定义在枚举列表中配置,点击“+”可增加枚举,选中条目点击“-”可删除枚举,双击条目可编辑枚举的名称和对应数值,配置完成后点击“应用枚举列表”。

③校验(CRC)字段类型:校验字段需要定义字段名称、字段长度(字段长度需要与CRC模型匹配,否则会报错)、大小端以及校验模型,校验模型可选择小程序自带的默认配置,也可选择自定义CRC模型,同时也可选择校验的起始字段和截止字段,如果起始字段配置为空,则默认从首字段开始,如果终止字段配置为空,则默认截止在该校验字段的上一字段。

2、根据帧格式生成数据流

(1)功能背景:

①在嵌入式调试过程中,开发者经常需要使用上位机模拟节点,向单片机发送一串数据流,这需要对照协议格式进行数据流编码,不仅容易把数据流和帧格式的对应关系搞错(尤其是字节偏移),对于校验字段,还需要用其它的工具进行计算,非常麻烦。

②为了减少这种重复且容易出错的工作量,可以基于自定义的协议帧格式,直接列出输入面板,开发者只需根据字段填入十六进制信息,就能自动生成对应的数据流,并且校验字段也能自动计算。

(2)功能定义:

①帧参数输入面板:在有载入模板的前提下,帧参数输入面板会提示用户根据字段填入相应的内容,其中枚举字段通过下拉框选择枚举值,校验字段自动计算。

②生成协议帧:点击“生成协议帧”,即可根据输入面板输入的内容生成十六进制数据流,点击“复制”可以复制生成结果。

③清空输入:点击“清空输入”,即可清除生成结果区和输入面板的所有内容。

3、根据帧格式解析数据流

(1)功能背景:

①开发者面对一串数据流,经常要对照协议格式进行解析,如果数据流很长,还非常容易把数据流和帧格式的对应关系搞错(尤其是字节偏移),导致开发者误以为软件有bug,做很多排查问题的无用功,非常浪费时间。

②为了减少这种重复且容易出错的工作量,可以基于自定义的协议帧格式,直接解析数据流,开发者只需将捕获到的数据流填入输入框,小程序就能自动根据协议帧格式解析出各字段的内容。

(2)功能定义:

①解析协议帧:将数据流贴入下方输入框,点击“解析报文”,小程序会把数据流解析,并显示在输入面板中。

②解析协议帧功能默认通过校验,不会计算校验码是否有误,会自动跳过。

二、小程序开发

1、开发环境

(1)需要确认安装的Python是否自带tkinter库(图形依赖),有则无需单独安装。

(2)安装pyinstaller库,该库主要用于将.py打包成可执行文件.exe,使其运行能够脱离Python环境。

2、程序源码

(1)配置文件protocol_templates.json示例:

[
  {
    "name": "自定义协议帧",
    "fields": [
      {
        "name": "普通字段",
        "type": "普通",
        "length": 1,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {},
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      },
      {
        "name": "枚举字段",
        "type": "枚举",
        "length": 1,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {
          "枚举1": 0,
          "枚举2": 1
        },
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      },
      {
        "name": "校验字段",
        "type": "CRC",
        "length": 2,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {},
        "crc_model": "自定义",
        "crc_start_field": "普通字段",
        "crc_range_field": "枚举字段",
        "crc_custom": {
          "width": 16,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      },
      {
        "name": "有效数据长度",
        "type": "普通",
        "length": 1,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {},
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      },
      {
        "name": "数据字段",
        "type": "普通",
        "length": 1,
        "var_mode": "bind",
        "bind_field": "有效数据长度",
        "endian": "BE(大端)",
        "enum_dict": {},
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      },
      {
        "name": "帧结束标志",
        "type": "普通",
        "length": 1,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {},
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      }
    ]
  }
]

(2)程序源码文件ssh_quick_tool.py:

import json
import os
import tkinter as tk
from tkinter import ttk, messagebox

TEMPLATE_FILE = "protocol_templates.json"
CRC_MODEL_LIST = ["CRC-16/XMODEM", "CRC-16/MODBUS", "CRC-8", "CRC-32", "自定义"]
DEFAULT_PRESETS = {
    "CRC-16/XMODEM": {"width":16, "poly":0x1021, "init":0x0000, "xorout":0x0000, "refin":False, "refout":False},
    "CRC-16/MODBUS": {"width":16, "poly":0xA001, "init":0xFFFF, "xorout":0x0000, "refin":True, "refout":True},
    "CRC-8": {"width":8, "poly":0x07, "init":0x00, "xorout":0x00, "refin":False, "refout":False},
    "CRC-32": {"width":32, "poly":0x04C11DB7, "init":0xFFFFFFFF, "xorout":0xFFFFFFFF, "refin":True, "refout":True},
}

def reverse_bits(value, bit_width):
    result = 0
    for _ in range(bit_width):
        result = (result << 1) | (value & 1)
        value >>= 1
    return result

def calc_crc(data: bytes, width: int, poly: int, init: int, xorout: int, refin: bool, refout: bool):
    mask = (1 << width) - 1
    crc = init & mask
    for b in data:
        byte = b
        if refin:
            byte = reverse_bits(byte, 8)
        crc ^= (byte << (width - 8))
        for _ in range(8):
            if crc & (1 << (width - 1)):
                crc = ((crc << 1) ^ poly) & mask
            else:
                crc = (crc << 1) & mask
    if refout:
        crc = reverse_bits(crc, width)
    crc = (crc ^ xorout) & mask
    return crc

class ProtocolEditor:
    def __init__(self, root):
        self.root = root
        self.root.title("自定义私有协议帧编辑器")
        self.root.geometry("1120x760")
        self.templates = []
        self.current_template = None
        self.field_widgets = []
        self.load_templates()

        top_frame = ttk.Frame(root)
        top_frame.pack(fill="x", padx=10, pady=5)
        ttk.Label(top_frame, text="协议模板 NAME:").grid(row=0, column=0, sticky="w")
        self.cmb_template = ttk.Combobox(top_frame, width=34)
        self.cmb_template.grid(row=0, column=1, padx=5)
        ttk.Button(top_frame, text="载入", command=self.load_selected_template).grid(row=0, column=2)
        ttk.Button(top_frame, text="新建", command=self.new_template).grid(row=0, column=3, padx=2)
        ttk.Button(top_frame, text="保存模板", command=self.save_template).grid(row=0, column=4, padx=2)
        ttk.Button(top_frame, text="删除模板", command=self.del_template).grid(row=0, column=5, padx=2)

        ttk.Label(root, text="字段定义列表(双击条目编辑)").pack(anchor="w", padx=10)
        self.tree = ttk.Treeview(root, columns=("name","type","length_info","endian","extra"), show="headings", height=9)
        self.tree.heading("name", text="字段名")
        self.tree.heading("type", text="类型")
        self.tree.heading("length_info", text="长度信息")
        self.tree.heading("endian", text="大小端")
        self.tree.heading("extra", text="附加配置")
        self.tree.pack(fill="x", padx=10)
        self.tree.bind("<Double-1>", self.on_tree_double_click)

        btn_field = ttk.Frame(root)
        btn_field.pack(anchor="w", padx=10, pady=3)
        ttk.Button(btn_field, text="添加字段", command=self.add_field).pack(side="left")
        ttk.Button(btn_field, text="删除选中", command=self.del_field).pack(side="left", padx=5)

        ttk.Label(root, text="帧参数输入面板").pack(anchor="w", padx=10, pady=8)
        self.form_frame = ttk.Frame(root)
        self.form_frame.pack(fill="x", padx=10)

        btn_frame = ttk.Frame(root)
        btn_frame.pack(pady=8)
        ttk.Button(btn_frame, text="生成协议帧", command=self.build_frame).pack(side="left", padx=5)
        ttk.Button(btn_frame, text="解析接收报文", command=self.parse_frame).pack(side="left", padx=5)
        ttk.Button(btn_frame, text="清空输入", command=self.clear_input_values).pack(side="left", padx=5)

        out_frame = ttk.Frame(root)
        out_frame.pack(fill="x", padx=10)
        out_frame.columnconfigure(1, weight=1)
        ttk.Label(out_frame, text="待解析Hex / 生成结果:").grid(row=0, column=0, sticky="w")
        self.var_hex = tk.StringVar()
        ttk.Entry(out_frame, textvariable=self.var_hex, font=("Consolas",11)).grid(row=0, column=1, padx=6, sticky="ew")
        ttk.Button(out_frame, text="复制", command=lambda:self.root.clipboard_append(self.var_hex.get())).grid(row=0, column=2)
        self.refresh_template_combo()

    def load_templates(self):
        if os.path.exists(TEMPLATE_FILE):
            try:
                with open(TEMPLATE_FILE,"r",encoding="utf-8") as f:
                    self.templates = json.load(f)
            except:
                self.templates = []
        else:
            self.templates = []

    def save_templates(self):
        with open(TEMPLATE_FILE,"w",encoding="utf-8") as f:
            json.dump(self.templates, f, ensure_ascii=False, indent=2)

    def refresh_template_combo(self):
        self.cmb_template["values"] = [t["name"] for t in self.templates]

    def new_template(self):
        dlg = tk.Toplevel(self.root)
        dlg.title("新建协议模板")
        dlg.geometry("340x130")
        dlg.transient(self.root)
        dlg.grab_set()
        dlg.columnconfigure(1, weight=1)
        var_name = tk.StringVar()
        ttk.Label(dlg, text="模板名称:").grid(row=0, column=0, padx=10, pady=12, sticky="w")
        entry = ttk.Entry(dlg, textvariable=var_name)
        entry.grid(row=0, column=1, padx=10, pady=12, sticky="ew")
        entry.focus()
        def confirm():
            name = var_name.get().strip()
            if not name:
                messagebox.showwarning("提示", "模板名称不能为空", parent=dlg)
                return
            self.current_template = {"name": name, "fields": []}
            self.refresh_tree()
            self.build_form()
            dlg.destroy()
            messagebox.showinfo("完成", f"新建模板:{name}", parent=self.root)
        ttk.Button(dlg, text="确定", command=confirm).grid(row=1, column=0, columnspan=2, pady=5)
        dlg.bind("<Return>", lambda e: confirm())

    def save_template(self):
        if not self.current_template:
            messagebox.showwarning("提示", "请先新建/加载模板")
            return
        name = self.current_template["name"]
        found = False
        for idx, t in enumerate(self.templates):
            if t["name"] == name:
                self.templates[idx] = self.current_template
                found = True
                break
        if not found:
            self.templates.append(self.current_template)
        self.save_templates()
        self.refresh_template_combo()
        messagebox.showinfo("成功", "模板已保存")

    def del_template(self):
        name = self.cmb_template.get()
        self.templates = [t for t in self.templates if t["name"] != name]
        self.save_templates()
        self.refresh_template_combo()
        self.current_template = None
        self.refresh_tree()
        self.clear_form()
        messagebox.showinfo("完成", "模板已删除")

    def load_selected_template(self):
        name = self.cmb_template.get()
        for t in self.templates:
            if t["name"] == name:
                self.current_template = t
                self.refresh_tree()
                self.build_form()
                return

    def refresh_tree(self):
        for item in self.tree.get_children():
            self.tree.delete(item)
        if not self.current_template:
            return
        for f in self.current_template["fields"]:
            var_mode = f.get("var_mode", "fixed")
            bind_name = f.get("bind_field", "")
            if var_mode == "fixed":
                len_info = f"{f['length']}字节"
            elif var_mode == "bind":
                len_info = f"不定长(绑定:{bind_name})"
            else:
                len_info = "不定长(自由段)"
            ftype = f["type"]
            if ftype == "CRC":
                extra = f["crc_model"]
            elif ftype == "枚举":
                extra = str(f.get("enum_dict", {}))
            else:
                extra = ""

            # 不定长bind / free 统一显示原始数据流
            if f.get("var_mode") in ("bind", "free"):
                endian_show = "原始数据流"
            else:
                endian_show = f.get("endian", "BE(大端)")

            self.tree.insert("", "end", values=(f["name"], f["type"], len_info, endian_show, extra))

    def on_tree_double_click(self, event):
        sel = self.tree.selection()
        if not sel:
            return
        idx = self.tree.index(sel[0])
        field = self.current_template["fields"][idx]
        self.edit_field_window(field, idx)

    def add_field(self):
        empty_field = {
            "name":"","type":"普通","length":1,"var_mode":"fixed","bind_field":"",
            "endian":"BE(大端)","enum_dict":{},
            "crc_model":"CRC-8",
            "crc_start_field":"",
            "crc_range_field":"",
            "crc_custom":{"width":8,"poly":0,"init":0,"xorout":0,"refin":False,"refout":False}
        }
        self.edit_field_window(empty_field, -1)

    def del_enum(self, tree):
        tree.focus()
        sel = tree.selection()
        if not sel:
            messagebox.showwarning("提示", "请先选中一行枚举条目!")
            return
        tree.delete(sel[0])

    def edit_field_window(self, field, index):
        win = tk.Toplevel(self.root)
        win.title("字段配置")
        win.geometry("720x660")
        win.columnconfigure(1, weight=1)
        vars = {
            "name": tk.StringVar(value=field["name"]),
            "ftype": tk.StringVar(value=field["type"]),
            "length": tk.StringVar(value=str(field["length"])),
            "var_mode": tk.StringVar(value=field.get("var_mode","fixed")),
            "bind_field": tk.StringVar(value=field.get("bind_field","")),
            "endian": tk.StringVar(value=field.get("endian","BE(大端)")),
            "crc_model": tk.StringVar(value=field.get("crc_model","CRC-8")),
            "crc_start_field": tk.StringVar(value=field.get("crc_start_field","")),
            "crc_range_field": tk.StringVar(value=field.get("crc_range_field","")),
            "crc_width": tk.StringVar(value=str(field.get("crc_custom",{}).get("width",8))),
            "crc_poly": tk.StringVar(value=hex(field.get("crc_custom",{}).get("poly",0))[2:]),
            "crc_init": tk.StringVar(value=hex(field.get("crc_custom",{}).get("init",0))[2:]),
            "crc_xorout": tk.StringVar(value=hex(field.get("crc_custom",{}).get("xorout",0))[2:]),
            "crc_refin": tk.BooleanVar(value=field.get("crc_custom",{}).get("refin",False)),
            "crc_refout": tk.BooleanVar(value=field.get("crc_custom",{}).get("refout",False)),
        }
        enum_dict = field.get("enum_dict", {}).copy()
        types_list = ["普通","枚举","CRC"]
        container = ttk.Frame(win)
        container.pack(fill="both", expand=True, padx=8, pady=8)
        container.columnconfigure(1, weight=1)

        ttk.Label(container, text="字段名称:").grid(row=0,column=0,sticky="w",pady=6)
        ttk.Entry(container, textvariable=vars["name"]).grid(row=0,column=1,sticky="ew")
        ttk.Label(container, text="字段类型:").grid(row=1,column=0,sticky="w",pady=6)
        cb_type = ttk.Combobox(container, textvariable=vars["ftype"], values=types_list)
        cb_type.grid(row=1,column=1,sticky="ew")

        row_len = 2
        lbl_len = ttk.Label(container, text="字节长度:")
        ent_len = ttk.Entry(container, textvariable=vars["length"])
        lbl_len.grid(row=row_len,column=0,sticky="w",pady=6)
        ent_len.grid(row=row_len,column=1,sticky="ew")

        ttk.Label(container, text="大小端:").grid(row=3,column=0,sticky="w",pady=6)
        cb_endian = ttk.Combobox(container, textvariable=vars["endian"], values=["BE(大端)","LE(小端)"])
        cb_endian.grid(row=3,column=1,sticky="ew")

        var_frame = ttk.Frame(container)
        var_frame.grid(row=4,column=0,columnspan=2,sticky="ew",pady=4)
        rb_fixed = ttk.Radiobutton(var_frame, text="定长字段", variable=vars["var_mode"], value="fixed", command=lambda:refresh_ui())
        rb_bind = ttk.Radiobutton(var_frame, text="不定长-绑定前置长度字段", variable=vars["var_mode"], value="bind", command=lambda:refresh_ui())
        rb_free = ttk.Radiobutton(var_frame, text="不定长-自由段(最多1个)", variable=vars["var_mode"], value="free", command=lambda:refresh_ui())
        rb_fixed.pack(side="left")
        rb_bind.pack(side="left",padx=4)
        rb_free.pack(side="left")

        bind_frame = ttk.Frame(container)
        bind_frame.grid(row=5,column=0,columnspan=2,sticky="ew",pady=2)
        ttk.Label(bind_frame, text="绑定长度字段:").pack(side="left")
        cb_bind = ttk.Combobox(bind_frame, textvariable=vars["bind_field"])
        cb_bind.pack(side="left",fill="x",expand=True,padx=4)
        bind_names = []
        if self.current_template:
            for idx,f in enumerate(self.current_template["fields"]):
                if index == -1 or idx < index:
                    bind_names.append(f["name"])
        cb_bind["values"] = bind_names

        cfg_frame = ttk.Frame(container)
        cfg_frame.grid(row=6,column=0,columnspan=2,sticky="ew",pady=6)

        def refresh_ui():
            tp = vars["ftype"].get()
            mode = vars["var_mode"].get()

            # 找到大小端标签和下拉框
            endian_label = None
            endian_combo = None
            for child in container.winfo_children():
                if isinstance(child, ttk.Label) and child.cget("text") == "大小端:":
                    endian_label = child
                if isinstance(child, ttk.Combobox) and child.grid_info().get("row") == 3:
                    endian_combo = child

            # 自由段、绑定不定长全部置灰;仅fixed/枚举/CRC恢复只读
            if endian_combo is not None:
                if mode != "fixed":
                    endian_combo.config(state="disabled")
                else:
                    endian_combo.config(state="readonly")

            if tp == "普通":
                var_frame.grid()
                if mode == "fixed":
                    ent_len.config(state="normal")
                    bind_frame.grid_remove()
                elif mode == "bind":
                    ent_len.config(state="disabled")
                    bind_frame.grid()
                elif mode == "free":
                    ent_len.config(state="disabled")
                    bind_frame.grid_remove()
            else:
                vars["var_mode"].set("fixed")
                var_frame.grid_remove()
                bind_frame.grid_remove()
                ent_len.config(state="normal")
                if endian_combo is not None:
                    endian_combo.config(state="readonly")

            for w in cfg_frame.winfo_children():
                w.destroy()

            if tp == "CRC":
                r = 0
                ttk.Label(cfg_frame, text="CRC模型:").grid(row=r,column=0,sticky="w")
                cb_crc = ttk.Combobox(cfg_frame, textvariable=vars["crc_model"], values=CRC_MODEL_LIST)
                cb_crc.grid(row=r,column=1,sticky="ew")
                r += 1
                ttk.Label(cfg_frame, text="校验起始字段:").grid(row=r,column=0,sticky="w")
                cb_crc_start = ttk.Combobox(cfg_frame, textvariable=vars["crc_start_field"])
                crc_range_names = []
                if self.current_template:
                    for idx,f in enumerate(self.current_template["fields"]):
                        if index == -1 or idx < index:
                            crc_range_names.append(f["name"])
                cb_crc_start["values"] = [""] + crc_range_names
                cb_crc_start.grid(row=r,column=1,sticky="ew")
                r += 1
                ttk.Label(cfg_frame, text="校验截止字段:").grid(row=r,column=0,sticky="w")
                cb_crc_range = ttk.Combobox(cfg_frame, textvariable=vars["crc_range_field"])
                cb_crc_range["values"] = [""] + crc_range_names
                cb_crc_range.grid(row=r,column=1,sticky="ew")
                r += 1

                custom_frame = ttk.Frame(cfg_frame)
                def refresh_crc_custom():
                    for w in custom_frame.winfo_children():
                        w.destroy()
                    if vars["crc_model"].get() == "自定义":
                        ttk.Label(custom_frame, text="宽度WIDTH").grid(row=0,column=0,sticky="w")
                        ttk.Entry(custom_frame, textvariable=vars["crc_width"]).grid(row=0,column=1,sticky="ew")
                        ttk.Label(custom_frame, text="POLY(Hex)").grid(row=1,column=0,sticky="w")
                        ttk.Entry(custom_frame, textvariable=vars["crc_poly"]).grid(row=1,column=1,sticky="ew")
                        ttk.Label(custom_frame, text="INIT(Hex)").grid(row=2,column=0,sticky="w")
                        ttk.Entry(custom_frame, textvariable=vars["crc_init"]).grid(row=2,column=1,sticky="ew")
                        ttk.Label(custom_frame, text="XOROUT(Hex)").grid(row=3,column=0,sticky="w")
                        ttk.Entry(custom_frame, textvariable=vars["crc_xorout"]).grid(row=3,column=1,sticky="ew")
                        ck1 = ttk.Checkbutton(custom_frame, text="REFIN", variable=vars["crc_refin"])
                        ck1.grid(row=4,column=0,sticky="w")
                        ck2 = ttk.Checkbutton(custom_frame, text="REFOUT", variable=vars["crc_refout"])
                        ck2.grid(row=4,column=1,sticky="w")
                cb_crc.bind("<<ComboboxSelected>>", lambda e: refresh_crc_custom())
                custom_frame.grid(row=r,column=0,columnspan=2,sticky="ew",pady=4)
                refresh_crc_custom()

            elif tp == "枚举":
                top_bar = ttk.Frame(cfg_frame)
                top_bar.pack(fill="x")
                ttk.Label(top_bar, text="枚举列表(双击条目弹出编辑窗口)").pack(side="left")
                ttk.Button(top_bar, text="+", command=lambda:tree_enum.insert("","end",values=("名称","0x00"))).pack(side="right",padx=2)
                ttk.Button(top_bar, text="-", command=lambda:self.del_enum(tree_enum)).pack(side="right")
                tree_enum = ttk.Treeview(cfg_frame, columns=("name","val"), show="headings", height=4)
                tree_enum.heading("name", text="枚举名称")
                tree_enum.heading("val", text="数值(0xXX)")
                tree_enum.pack(fill="x",pady=4)

                def enum_double_pop(event):
                    item = tree_enum.focus()
                    if not item:
                        return
                    vals = tree_enum.item(item,"values")
                    old_name, old_val = vals[0], vals[1]
                    dlg = tk.Toplevel(win)
                    dlg.title("修改枚举项")
                    dlg.geometry("320x160")
                    dlg.transient(win)
                    dlg.grab_set()
                    var_n = tk.StringVar(value=old_name)
                    var_v = tk.StringVar(value=old_val)
                    ttk.Label(dlg, text="枚举名称:").grid(row=0,column=0,padx=8,pady=8,sticky="w")
                    ttk.Entry(dlg, textvariable=var_n).grid(row=0,column=1,padx=8,pady=8,sticky="ew")
                    ttk.Label(dlg, text="枚举数值(Hex):").grid(row=1,column=0,padx=8,pady=8,sticky="w")
                    ttk.Entry(dlg, textvariable=var_v).grid(row=1,column=1,padx=8,pady=8,sticky="ew")
                    def confirm():
                        nn = var_n.get().strip()
                        nv = var_v.get().strip()
                        if not nn or not nv:
                            messagebox.showwarning("提示","名称和数值不能为空", parent=dlg)
                            return
                        tree_enum.item(item, values=(nn, nv))
                        dlg.destroy()
                    ttk.Button(dlg, text="确定", command=confirm).grid(row=2,column=0,columnspan=2,pady=10)
                    dlg.columnconfigure(1, weight=1)

                tree_enum.bind("<Double-1>", enum_double_pop)
                for k,v in enum_dict.items():
                    tree_enum.insert("", "end", values=(k, f"0x{v:X}"))

                def save_enum():
                    nonlocal enum_dict
                    enum_dict.clear()
                    for it in tree_enum.get_children():
                        n,v = tree_enum.item(it,"values")
                        enum_dict[n] = int(v,0)
                ttk.Button(cfg_frame, text="应用枚举列表", command=save_enum).pack()

        cb_type.bind("<<ComboboxSelected>>", lambda e: refresh_ui())
        refresh_ui()

        def confirm_save():
            try:
                w = int(vars["length"].get())
                var_mode = vars["var_mode"].get()
                new_fields = self.current_template["fields"].copy() if self.current_template else []
                if index >= 0:
                    new_fields.pop(index)
                free_cnt = sum(1 for f in new_fields if f.get("var_mode","fixed")=="free")
                if var_mode == "free" and free_cnt >= 1:
                    messagebox.showerror("限制","协议最多只能存在1个【不定长-自由段】")
                    return
                bind_name = vars["bind_field"].get().strip()
                if var_mode == "bind" and bind_name == "":
                    messagebox.showerror("参数错误","绑定模式必须选择前置长度字段!")
                    return
                crc_custom = {
                    "width": int(vars["crc_width"].get()),
                    "poly": int(vars["crc_poly"].get(),16) if vars["crc_poly"].get().strip() else 0,
                    "init": int(vars["crc_init"].get(),16) if vars["crc_init"].get().strip() else 0,
                    "xorout": int(vars["crc_xorout"].get(),16) if vars["crc_xorout"].get().strip() else 0,
                    "refin": vars["crc_refin"].get(),
                    "refout": vars["crc_refout"].get(),
                }
                new_field = {
                    "name": vars["name"].get().strip(),
                    "type": vars["ftype"].get(),
                    "length": w,
                    "var_mode": var_mode,
                    "bind_field": bind_name,
                    "endian": vars["endian"].get(),
                    "enum_dict": enum_dict.copy(),
                    "crc_model": vars["crc_model"].get().strip(),
                    "crc_start_field": vars["crc_start_field"].get().strip(),
                    "crc_range_field": vars["crc_range_field"].get().strip(),
                    "crc_custom": crc_custom
                }
                if index == -1:
                    self.current_template["fields"].append(new_field)
                else:
                    self.current_template["fields"][index] = new_field
                self.refresh_tree()
                self.build_form()
                win.destroy()
            except Exception as e:
                messagebox.showerror("参数错误", str(e))
        ttk.Button(container, text="确认保存字段", command=confirm_save).grid(row=7,column=0,columnspan=2,pady=14)

    def del_field(self):
        sel = self.tree.selection()
        if not sel:
            return
        idx = self.tree.index(sel[0])
        del self.current_template["fields"][idx]
        self.refresh_tree()
        self.build_form()

    def clear_form(self):
        for w in self.form_frame.winfo_children():
            w.destroy()
        self.field_widgets = []
        self.var_hex.set("")

    def clear_input_values(self):
        self.var_hex.set("")
        for wdg in self.field_widgets:
            if "var" in wdg:
                wdg["var"].set("")

    def build_form(self):
        self.clear_form()
        if not self.current_template:
            return
        for fd in self.current_template["fields"]:
            fr = ttk.Frame(self.form_frame)
            fr.pack(anchor="w", pady=2, fill="x")
            fr.columnconfigure(0, weight=0)
            fr.columnconfigure(1, weight=1)

            label_text = f"{fd['name']} [{fd['type']}]"
            if fd["type"] == "普通":
                label_text += "  十六进制空格分隔"

            lbl = ttk.Label(fr, text=label_text, width=68)
            lbl.grid(row=0, column=0, padx=(0,10), sticky="w")

            ftype = fd["type"]
            enum_dict = fd.get("enum_dict", {})
            if ftype == "枚举":
                keys = list(enum_dict.keys())
                var = tk.StringVar(value=keys[0] if keys else "")
                cb = ttk.Combobox(fr, textvariable=var, values=keys)
                cb.grid(row=0, column=1, padx=(100, 0))
                self.field_widgets.append({"field":fd, "var":var, "enum":enum_dict})
            elif ftype == "CRC":
                ttk.Label(fr, text="【自动计算】").grid(row=0, column=1)
                self.field_widgets.append({"field":fd})
            else:
                var = tk.StringVar()
                entry = ttk.Entry(fr, textvariable=var, width=30)
                entry.grid(row=0, column=1, padx=(100, 0))
                self.field_widgets.append({"field":fd, "var":var})

    def _get_raw_field_bytes(self, field_name):
        """独立获取指定普通/枚举字段,定长数值全部按大小端打包"""
        for wdg in self.field_widgets:
            fd = wdg["field"]
            if fd["name"] != field_name:
                continue
            tp = fd["type"]
            endian = "big" if fd["endian"] == "BE(大端)" else "little"
            if tp == "枚举":
                val = wdg["enum"][wdg["var"].get()]
                return val.to_bytes(fd["length"], byteorder=endian)
            elif tp == "普通":
                var_mode = fd.get("var_mode", "fixed")
                if var_mode == "fixed":
                    # 定长数值:输入hex字符串转整数,再按端序打包
                    raw_str = wdg["var"].get().strip().replace(" ", "")
                    num = int(raw_str, 16) if raw_str else 0
                    return num.to_bytes(fd["length"], byteorder=endian)
                else:
                    # bind / free 直接原始字节流,不做数值端序转换
                    raw = wdg["var"].get().strip()
                    return bytes.fromhex(raw) if raw else b""
        return b""

    def _get_bind_length_value(self, bind_field_name):
        """读取绑定字段的长度数值,独立函数,全局复用"""
        try:
            target_fd = None
            for wdg in self.field_widgets:
                if wdg["field"]["name"] == bind_field_name:
                    target_fd = wdg["field"]
                    break
            if target_fd is None:
                return 0
            endian = "big" if target_fd["endian"] == "BE(大端)" else "little"
            data = self._get_raw_field_bytes(bind_field_name)
            val = int.from_bytes(data, byteorder=endian)
            return max(0, min(val, 4096))
        except Exception:
            return 0

    def _get_raw_undetermined_data(self, field_name):
        """获取不定长字段原始输入字节"""
        for wdg in self.field_widgets:
            fd = wdg["field"]
            if fd["name"] == field_name and fd["type"] == "普通":
                raw = wdg["var"].get().strip()
                if raw:
                    return bytes.fromhex(raw)
                return b""
        return b""

    def build_frame(self):
        try:
            final_buf = b""
            for wdg in self.field_widgets:
                fd = wdg["field"]
                tp = fd["type"]
                endian = "big" if fd["endian"] == "BE(大端)" else "little"
                var_mode = fd.get("var_mode","fixed")

                if tp == "枚举":
                    val = wdg["enum"][wdg["var"].get()]
                    final_buf += val.to_bytes(fd["length"], byteorder=endian)
                elif tp == "普通":
                    if var_mode == "fixed":
                        # 定长普通字段
                        bdata = self._get_raw_field_bytes(fd["name"])
                        final_buf += bdata
                    elif var_mode == "bind":
                        # 不定长绑定字段:独立读取原始数据+补齐,不再依赖ptr偏移
                        bind_len = self._get_bind_length_value(fd["bind_field"])
                        raw_data = self._get_raw_undetermined_data(fd["name"])
                        chunk = raw_data[:bind_len]
                        if len(chunk) < bind_len:
                            chunk = chunk.ljust(bind_len, b"\x00")
                        final_buf += chunk
                    elif var_mode == "free":
                        raw = wdg["var"].get().strip()
                        chunk = bytes.fromhex(raw) if raw else b""
                        final_buf += chunk
                elif tp == "CRC":
                    crc_start_name = fd.get("crc_start_field","")
                    crc_end_name = fd.get("crc_range_field","")
                    start_ptr = 0
                    end_ptr = len(final_buf)
                    if crc_start_name:
                        pos = 0
                        for f_wdg in self.field_widgets:
                            f = f_wdg["field"]
                            if f["name"] == crc_start_name:
                                start_ptr = pos
                                break
                            # 累加前面所有字段在final_buf里的长度
                            if f["type"] == "CRC":
                                pos += f["length"]
                            elif f.get("var_mode") == "bind":
                                pos += self._get_bind_length_value(f["bind_field"])
                            elif f.get("var_mode") == "free":
                                pos += 4096
                            else:
                                pos += f["length"]
                    if crc_end_name:
                        pos = 0
                        for f_wdg in self.field_widgets:
                            f = f_wdg["field"]
                            pos += self._get_field_actual_length(f_wdg)
                            if f["name"] == crc_end_name:
                                end_ptr = pos
                                break
                    calc_data = final_buf[start_ptr:end_ptr]
                    if fd["crc_model"] == "自定义":
                        cfg = fd["crc_custom"]
                        crc_cfg = {"width":cfg["width"],"poly":cfg["poly"],"init":cfg["init"],"xorout":cfg["xorout"],"refin":cfg["refin"],"refout":cfg["refout"]}
                    else:
                        crc_cfg = DEFAULT_PRESETS[fd["crc_model"]]
                    crc_val = calc_crc(calc_data,**crc_cfg)
                    crc_bytes = crc_val.to_bytes(fd["length"], byteorder="big")
                    if endian == "little":
                        crc_bytes = crc_bytes[::-1]
                    final_buf += crc_bytes

            hex_out = " ".join(f"{b:02X}" for b in final_buf)
            self.var_hex.set(hex_out)
        except ValueError as ve:
            messagebox.showerror("组帧失败", f"十六进制格式错误:{str(ve)}\n请检查输入内容")
        except Exception as e:
            messagebox.showerror("组帧失败", str(e))

    def _get_field_actual_length(self, wdg):
        fd = wdg["field"]
        vm = fd.get("var_mode","fixed")
        if vm == "bind":
            return self._get_bind_length_value(fd["bind_field"])
        elif vm == "free":
            return 4096
        else:
            return fd["length"]

    def parse_frame(self):
        hex_str = self.var_hex.get().strip()
        if not hex_str:
            messagebox.showwarning("提示", "请粘贴十六进制报文")
            return
        try:
            data = bytes.fromhex(hex_str)
            ptr = 0
            for wdg in self.field_widgets:
                fd = wdg["field"]
                tp = fd["type"]
                var_mode = fd.get("var_mode","fixed")
                endian = "big" if fd["endian"] == "BE(大端)" else "little"
                if tp == "CRC":
                    ptr += fd["length"]
                    continue
                if var_mode == "fixed":
                    if ptr + fd["length"] > len(data):
                        messagebox.showwarning("长度不足", f"{fd['name']}超出报文")
                        break
                    chunk = data[ptr:ptr+fd["length"]]
                    ptr += fd["length"]
                    if tp == "枚举":
                        val = int.from_bytes(chunk, byteorder=endian)
                        rev = {v:k for k,v in wdg["enum"].items()}
                        wdg["var"].set(rev.get(val, ""))
                    else:
                        chunk = chunk[::-1] if endian == "little" else chunk
                        hex_str_out = " ".join(f"{b:02X}" for b in chunk)
                        wdg["var"].set(hex_str_out)
                elif var_mode == "bind":
                    blen = self._get_bind_length_value(fd["bind_field"])
                    if ptr + blen > len(data):
                        messagebox.showwarning("长度不足", f"{fd['name']}超出报文")
                        break
                    chunk = data[ptr:ptr+blen]
                    ptr += blen
                    wdg["var"].set(" ".join(f"{b:02X}" for b in chunk))
                elif var_mode == "free":
                    chunk = data[ptr:]
                    ptr = len(data)
                    wdg["var"].set(" ".join(f"{b:02X}" for b in chunk))
        except Exception as e:
            messagebox.showerror("解析异常", str(e))

if __name__ == "__main__":
    root = tk.Tk()
    app = ProtocolEditor(root)
    root.mainloop()

3、可执行程序生成及注意事项

(1)在程序源码文件protocol_editor.py所在目录下打开终端,输入命令“pyinstaller -w -F protocol_editor.py”,即可生成可执行文件protocol_editor.exe。

(2)不管是运行protocol_editor.py还是protocol_editor.exe,其所在目录下如果没有protocol_templates.json文件,都会新建一个protocol_templates.json文件,用户保存的协议模板都存储在该文件中。

(3)如果希望增加默认的CRC校验模型,不想每次新增字段都单独配置,可基于上面的源码继续开发CRC校验模型保存功能,或者在DEFAULT_PRESETS中新增默认模型,并在CRC_MODEL_LIST中注册。

(4)当前小程序支持的字段最小长度单位为字节,不支持按位配置,已适用于大部分应用层协议,如果需要按位配置,可基于上面的源码继续开发,将最小单位调整为bit。

三、小程序使用演示

1、演示示例1——伪CAN协议帧

(1)伪CAN协议数据帧格式定义:

字段名

ID仲裁段

控制段

有效数据长度

数据段

CRC段

帧结束

类型

普通

枚举

普通

普通

校验

普通

长度信息

3字节

1字节

1字节

不定长

2字节

1字节

大小端

大端

大端

大端

大端

大端

大端

备注

/

0x00:读命令

0x01:写命令

0x02:读返回

0x03:写返回

/

长度取决于“有效数据长度”

使用CRC-16/MODBUS模型

固定为0F

(2)该示例的私有协议只有一种帧格式,所以只需要配置一个协议模板即可,点击“新建”,将模板名字配置为“伪CAN数据帧”,点击“确定”,完成模板的创建,然后点击“保存模板”,即可在本地保存模板信息。

(3)根据协议帧格式添加字段:

①ID仲裁段配置:

②控制段配置:

③有效数据长度配置:

④数据段配置:

⑤CRC段配置:

⑥帧结束配置:

(4)协议帧格式对应的JSON数据结构:

  {
    "name": "伪CAN数据帧",
    "fields": [
      {
        "name": "ID仲裁段",
        "type": "普通",
        "length": 3,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {},
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      },
      {
        "name": "控制段",
        "type": "枚举",
        "length": 1,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {
          "读命令": 0,
          "写命令": 1,
          "读返回": 2,
          "写返回": 3
        },
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      },
      {
        "name": "有效数据长度",
        "type": "普通",
        "length": 1,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {},
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      },
      {
        "name": "数据段",
        "type": "普通",
        "length": 1,
        "var_mode": "bind",
        "bind_field": "有效数据长度",
        "endian": "BE(大端)",
        "enum_dict": {},
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      },
      {
        "name": "CRC段",
        "type": "CRC",
        "length": 2,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {},
        "crc_model": "CRC-16/MODBUS",
        "crc_start_field": "ID仲裁段",
        "crc_range_field": "数据段",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      },
      {
        "name": "帧结束",
        "type": "普通",
        "length": 1,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {},
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      }
    ]
  }

2、演示示例2——伪USB协议帧

(1)伪USB协议数据帧格式定义:

①令牌伪USB报文格式:

字段名

PID

ADDR

EP

CRC-8

类型

枚举

普通

普通

普通

长度信息

1字节

1字节

1字节

1字节

大小端

大端

大端

大端

大端

备注

0x00:IN

0x01:OUT

0x02:SETUP

从机地址

端点编号

根据PID+ADDR+EP计算而得的校验码

②特殊伪USB报文格式:

字段名

PID

FRAME

CRC-8

类型

枚举

普通

普通

长度信息

1字节

2字节

1字节

大小端

大端

大端

大端

备注

0x03:SOF

帧计数器FrameNumber

根据PID+FRAME计算而得的校验码

③握手伪USB报文格式:

字段名

PID

类型

枚举

长度信息

1字节

大小端

大端

备注

0x04:ACK

0x05:NAK

0x06:STALL

④数据伪USB报文格式:

字段名

PID

Payload

CRC-16

类型

枚举

普通

普通

长度信息

1字节

不定长

2字节

大小端

大端

大端

大端

备注

0x07:DATA0

0x08:DATA1

长度自由,取决于帧总长度

根据PID+Payload计算而得的校验码

(2)该示例的私有协议有四种帧格式,所以需要分别配置四个协议模板,分别命名为“令牌伪USB数据帧”、“特殊伪USB数据帧”、“握手伪USB数据帧”、“数据伪USB数据帧”。

(3)根据协议帧格式添加字段:

①令牌伪USB数据帧字段定义列表及JSON数据结构:

  {
    "name": "令牌伪USB数据帧",
    "fields": [
      {
        "name": "PID",
        "type": "枚举",
        "length": 1,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {
          "IN": 0,
          "OUT": 1,
          "SETUP": 2
        },
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      },
      {
        "name": "ADDR",
        "type": "普通",
        "length": 1,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {},
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      },
      {
        "name": "EP",
        "type": "普通",
        "length": 1,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {},
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      },
      {
        "name": "CRC-8",
        "type": "CRC",
        "length": 1,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {},
        "crc_model": "CRC-8",
        "crc_start_field": "PID",
        "crc_range_field": "EP",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      }
    ]
  }

②特殊伪USB数据帧字段定义列表及JSON数据结构:

  {
    "name": "特殊伪USB数据帧",
    "fields": [
      {
        "name": "PID",
        "type": "枚举",
        "length": 1,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {
          "SOF": 3
        },
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      },
      {
        "name": "FRAME",
        "type": "普通",
        "length": 2,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {},
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      },
      {
        "name": "CRC-8",
        "type": "CRC",
        "length": 1,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {},
        "crc_model": "CRC-8",
        "crc_start_field": "PID",
        "crc_range_field": "FRAME",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      }
    ]
  }

③握手伪USB数据帧字段定义列表及JSON数据结构:

  {
    "name": "握手伪USB数据帧",
    "fields": [
      {
        "name": "PID",
        "type": "枚举",
        "length": 1,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {
          "ACK": 4,
          "NAK": 5,
          "STALL": 6
        },
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      }
    ]
  }

④数据伪USB数据帧字段定义列表及JSON数据结构:

  {
    "name": "数据伪USB数据帧",
    "fields": [
      {
        "name": "PID",
        "type": "枚举",
        "length": 1,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {
          "DATA0": 7,
          "DATA1": 8
        },
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      },
      {
        "name": "Payload",
        "type": "普通",
        "length": 1,
        "var_mode": "free",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {},
        "crc_model": "CRC-8",
        "crc_start_field": "",
        "crc_range_field": "",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      },
      {
        "name": "CRC-16",
        "type": "CRC",
        "length": 2,
        "var_mode": "fixed",
        "bind_field": "",
        "endian": "BE(大端)",
        "enum_dict": {},
        "crc_model": "CRC-16/MODBUS",
        "crc_start_field": "PID",
        "crc_range_field": "Payload",
        "crc_custom": {
          "width": 8,
          "poly": 0,
          "init": 0,
          "xorout": 0,
          "refin": false,
          "refout": false
        }
      }
    ]
  }

更多推荐