# -*- coding: utf-8 -*- """Generate Word doc from qm-api device integration manual.""" from docx import Document from docx.shared import Pt, Cm, RGBColor from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.enum.table import WD_TABLE_ALIGNMENT from docx.oxml.ns import qn import os OUTPUT = os.path.join(os.path.dirname(__file__), "气密工位MES对接说明(设备方).docx") def set_cell_shading(cell, color="D9E2F3"): from docx.oxml import OxmlElement shading = OxmlElement("w:shd") shading.set(qn("w:fill"), color) shading.set(qn("w:val"), "clear") cell._tc.get_or_add_tcPr().append(shading) def add_heading(doc, text, level=1): h = doc.add_heading(text, level=level) for run in h.runs: run.font.name = "微软雅黑" run._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑") return h def add_para(doc, text, bold=False, size=11): p = doc.add_paragraph() run = p.add_run(text) run.font.name = "微软雅黑" run._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑") run.font.size = Pt(size) run.bold = bold p.paragraph_format.space_after = Pt(6) return p def add_code(doc, text): p = doc.add_paragraph() p.paragraph_format.left_indent = Cm(0.5) run = p.add_run(text) run.font.name = "Consolas" run._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑") run.font.size = Pt(9) run.font.color.rgb = RGBColor(0x33, 0x33, 0x33) p.paragraph_format.space_after = Pt(8) return p def add_table(doc, headers, rows, col_widths=None): table = doc.add_table(rows=1 + len(rows), cols=len(headers)) table.style = "Table Grid" table.alignment = WD_TABLE_ALIGNMENT.CENTER hdr = table.rows[0].cells for i, h in enumerate(headers): hdr[i].text = h set_cell_shading(hdr[i]) for p in hdr[i].paragraphs: for run in p.runs: run.bold = True run.font.name = "微软雅黑" run._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑") run.font.size = Pt(10) for ri, row in enumerate(rows): cells = table.rows[ri + 1].cells for ci, val in enumerate(row): cells[ci].text = str(val) for p in cells[ci].paragraphs: for run in p.runs: run.font.name = "微软雅黑" run._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑") run.font.size = Pt(10) if col_widths: for i, w in enumerate(col_widths): for row in table.rows: row.cells[i].width = Cm(w) doc.add_paragraph() return table def build(): doc = Document() sec = doc.sections[0] sec.top_margin = Cm(2.5) sec.bottom_margin = Cm(2.5) sec.left_margin = Cm(2.8) sec.right_margin = Cm(2.8) # Title title = doc.add_paragraph() title.alignment = WD_ALIGN_PARAGRAPH.CENTER tr = title.add_run("气密工位 MES 对接说明(设备方)") tr.bold = True tr.font.size = Pt(18) tr.font.name = "微软雅黑" tr._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑") sub = doc.add_paragraph() sub.alignment = WD_ALIGN_PARAGRAPH.CENTER sr = sub.add_run("文档版本:v1.0 更新日期:2026-06-08") sr.font.size = Pt(10) sr.font.color.rgb = RGBColor(0x66, 0x66, 0x66) sr.font.name = "微软雅黑" sr._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑") doc.add_paragraph() add_para(doc, "文档用途:气密检测设备与 MES 系统对接,完成本工位进站校验与结果上传。") add_para(doc, "对接方:气密设备软件 / 上位机开发") add_para(doc, "MES 地址:http://192.168.16.99:8980", bold=True) doc.add_paragraph() # Section 1 add_heading(doc, "一、先看这个(1 分钟读懂)", 1) add_para(doc, "您的设备软件只需要做 2 件事:") add_table(doc, ["时机", "调用哪个接口", "作用"], [ ["扫码后、开始测试前", "接口 1:qmcheck", "问 MES:这个工件能不能测?"], ["测试结束后", "接口 2:qmresult", "告诉 MES:测完了,结果 OK 还是 NG"], ], [4, 4.5, 6]) add_para(doc, "不需要登录、不需要 Token,设备能访问 MES 服务器 IP 即可。") add_para(doc, "MES 会提前提供给您的固定参数(请填入下表,对接前向 MES 方确认):") add_table(doc, ["参数", "含义", "示例(以 MES 方确认为准)"], [ ["oprno", "工位号", "OP450"], ["lineSn", "产线编号", "XT"], ], [3, 4, 6]) # Section 2 add_heading(doc, "二、整体流程(设备侧逻辑)", 1) add_code(doc, """操作员扫码(得到 sn) │ ▼ 调用 qmcheck(进站校验) │ result=true 且 data=UD ? │ 否 ──┴── 是 │ │ ▼ ▼ 屏幕提示 开始气密测试 禁止测试 (设备自行测试) │ ▼ 调用 qmresult(上传结果) │ result=true ? │ 否 ────┴──── 是 │ │ ▼ ▼ 提示失败 显示上传成功""") # Section 3 add_heading(doc, "三、通信方式(统一约定)", 1) add_table(doc, ["项目", "值"], [ ["协议", "HTTP"], ["方法", "POST"], ["参数格式", "表单:application/x-www-form-urlencoded"], ["编码", "UTF-8"], ["返回格式", "JSON 字符串"], ], [4, 10]) add_para(doc, "请求示例格式(所有接口相同):") add_code(doc, """POST http://192.168.16.99:8980/js/a/mes/mesProductRecord/接口名 Content-Type: application/x-www-form-urlencoded 参数1=值1&参数2=值2&...""") add_para(doc, "建议每个请求都带上:__ajax=json(固定值,便于返回 JSON)。") # Section 4 add_heading(doc, "四、接口 1:进站校验 qmcheck", 1) add_heading(doc, "4.1 什么时候调", 2) add_para(doc, "• 操作员扫完条码、设备尚未开始充气/测试之前") add_para(doc, "• 每个工件测一次(换件后重新调)") add_heading(doc, "4.2 地址", 2) add_code(doc, "POST http://192.168.16.99:8980/js/a/mes/mesProductRecord/qmcheck") add_heading(doc, "4.3 要传哪些参数", 2) add_table(doc, ["参数", "必填", "从哪里来", "说明"], [ ["sn", "是", "扫码枪 / 人工输入", "工件条码"], ["oprno", "是", "MES 提供,写死在配置里", "工位号"], ["lineSn", "是", "MES 提供,写死在配置里", "产线号"], ["workNum", "建议", "登录工号,没有就传 system", "操作员"], ["__ajax", "建议", "固定 json", "—"], ], [2.5, 1.5, 4.5, 4.5]) add_heading(doc, "4.4 请求示例", 2) add_para(doc, "表单内容:") add_code(doc, "__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT&workNum=system") add_para(doc, "curl(Windows):") add_code(doc, 'curl -X POST "http://192.168.16.99:8980/js/a/mes/mesProductRecord/qmcheck" ^\n -H "Content-Type: application/x-www-form-urlencoded" ^\n -d "__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT&workNum=system"') add_heading(doc, "4.5 返回怎么判断(设备程序重点)", 2) add_para(doc, "返回示例:") add_code(doc, '{\n "result": "true",\n "message": "工件可以加工",\n "data": "UD"\n}') add_para(doc, "判断规则(写进设备程序):") add_code(doc, 'IF result == "true" AND data == "UD"\n → 允许开始测试\nELSE\n → 不允许测试,屏幕显示 message 原文') add_table(doc, ["result", "data", "设备怎么处理"], [ ["true", "UD", "允许测试"], ["false", "任意", "禁止测试,显示 message"], ], [2.5, 2.5, 8]) add_para(doc, "常见 message 含义:") add_table(doc, ["message 示例", "含义"], [ ["工件可以加工", "正常,可以测"], ["该工件OP180未加工", "前面工位没做,不能测"], ["该工件本工位已加工,结果:OK", "已经测过且合格,不必再测"], ["该工件未录入系统", "条码错误或 MES 无此件"], ["两次气密必须间隔15分钟", "同一工件 15 分钟内不能重复测"], ["参数错误1", "sn / oprno / lineSn 有缺失"], ], [6, 7]) # Section 5 add_heading(doc, "五、接口 2:结果上传 qmresult", 1) add_heading(doc, "5.1 什么时候调", 2) add_para(doc, "• 气密测试结束后(无论 OK 还是 NG 都要调)") add_para(doc, "• 必须先 qmcheck 通过再测;测完再调本接口") add_heading(doc, "5.2 地址", 2) add_code(doc, "POST http://192.168.16.99:8980/js/a/mes/mesProductRecord/qmresult") add_heading(doc, "5.3 要传哪些参数", 2) add_para(doc, "必填(少了会报「参数错误1」):") add_table(doc, ["参数", "说明", "示例"], [ ["sn", "工件条码(与 qmcheck 相同)", "扫码值"], ["oprno", "工位号", "OP450"], ["lineSn", "产线号", "XT"], ["result", "测试结果", "OK 或 NG(必须大写)"], ["craft", "工艺号", "固定传 100000"], ["__ajax", "建议", "json"], ], [2.5, 5, 5.5]) add_para(doc, "建议传:workNum(操作员工号,没有传 system)") add_para(doc, "可选(有就传,便于 MES 存过程数据):") add_table(doc, ["参数", "是否编码", "说明"], [ ["testPressure", "Base64", "测试压力"], ["testPressureUnit", "明文", "单位,如 Pa"], ["leakVal", "Base64", "泄漏值"], ["leakValUnit", "明文", "单位"], ["testTime", "Base64", "测试时间,格式 yyyy-MM-dd HH:mm:ss"], ["cxm", "明文", "设备程序号"], ["deviceType", "明文", "设备编号"], ["remark", "明文", "备注"], ], [3.5, 2, 8]) add_para(doc, "关于 Base64:testPressure、leakVal、testTime 需先 Base64 编码再提交。若暂时不做 Base64,最少只传必填项也能完成工位对接。") add_para(doc, "Base64 对照(方便联调):") add_table(doc, ["原始值", "传参值"], [ ["200", "MjAw"], ["5", "NQ=="], ["2024-05-16 12:12:00", "MjAyNC0wNS0xNiAxMjoxMjowMA=="], ], [5, 8]) add_heading(doc, "5.4 请求示例", 2) add_para(doc, "最简版(能完成工位对接):") add_code(doc, "__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT&craft=100000&result=OK&workNum=system") add_para(doc, "完整版(含过程数据):") add_code(doc, "__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT&craft=100000&result=OK&workNum=system&testPressure=MjAw&testPressureUnit=Pa&leakVal=NQ==&leakValUnit=Pa&testTime=MjAyNC0wNS0xNiAxMjoxMjowMA==&cxm=PROGRAM_01") add_heading(doc, "5.5 返回怎么判断", 2) add_para(doc, "成功:") add_code(doc, '{\n "result": "true",\n "message": "操作成功!"\n}') add_para(doc, "失败:") add_code(doc, '{\n "result": "false",\n "message": "该工件本工位已加工,结果:OK"\n}') add_code(doc, 'IF result == "true"\n → 上传成功,MES 工位结果已更新\nELSE\n → 上传失败,显示 message,建议本地保存待人工处理') add_table(doc, ["常见 message", "原因", "建议"], [ ["操作成功!", "正常", "—"], ["参数错误1!", "缺 sn/oprno/lineSn/result", "检查必填参数"], ["子工位未配置!", "MES 未配置该工位", "联系 MES 管理员"], ["该工件本工位已加工…", "重复上传", "提示用户,勿重复提交"], ["该工件OPxxx未加工", "未做 qmcheck 或前序工位未完成", "先校验流程"], ], [4.5, 4.5, 4]) # Section 6 add_heading(doc, "六、设备程序伪代码", 1) add_code(doc, """// 配置项(MES 方提供,写死在设备软件里) MES_HOST = "192.168.16.99" MES_PORT = 8980 OPRNO = "OP450" // 工位号,向 MES 确认 LINE_SN = "XT" // 产线号,向 MES 确认 // 1. 扫码后 sn = 扫码结果 body = "__ajax=json&sn=" + urlEncode(sn) + "&oprno=" + OPRNO + "&lineSn=" + LINE_SN + "&workNum=" + urlEncode(当前工号或"system") resp = HTTP_POST("http://" + MES_HOST + ":8980/js/a/mes/mesProductRecord/qmcheck", body) if resp.result != "true" OR resp.data != "UD": 显示(resp.message); 停止,不允许测试 // 2. 设备自行做气密测试 testResult = 设备判定结果 // "OK" 或 "NG" // 3. 测试结束后上传 body = "__ajax=json&sn=" + urlEncode(sn) + "&oprno=" + OPRNO + "&lineSn=" + LINE_SN + "&craft=100000" + "&result=" + testResult + "&workNum=" + urlEncode(当前工号或"system") resp = HTTP_POST("http://" + MES_HOST + ":8980/js/a/mes/mesProductRecord/qmresult", body) if resp.result != "true": 显示("MES上传失败:" + resp.message); 本地记录,供补传 else 显示("MES上传成功")""") # Section 7 add_heading(doc, "七、联调步骤(双方配合)", 1) add_heading(doc, "第 1 步:网络", 2) add_para(doc, "□ 气密设备电脑能 ping 192.168.16.99") add_para(doc, "□ 浏览器或 Postman 能访问 http://192.168.16.99:8980") add_heading(doc, "第 2 步:用 Postman 测通两个接口", 2) add_para(doc, "1. 向 MES 方要一个真实可测的工件码 sn") add_para(doc, "2. 调 qmcheck,确认返回 result=true, data=UD") add_para(doc, "3. 调 qmresult,result=OK,确认返回「操作成功!」") add_para(doc, "4. MES 方在后台确认工位结果已更新") add_heading(doc, "第 3 步:设备软件接入", 2) add_para(doc, "按第六节伪代码接入;先实现最简参数,过程数据稳定后再补。") add_heading(doc, "第 4 步:现场验证", 2) add_para(doc, "□ 正常件:check 通过 → 测试 → result 上传成功") add_para(doc, "□ NG 件:result=NG 上传成功,MES 显示 NG") add_para(doc, "□ 重复扫码:check 提示已加工,不允许再测") add_para(doc, "□ 错误条码:check 提示未录入系统") # Section 8 FAQ add_heading(doc, "八、常见问题 FAQ", 1) faqs = [ ("Q1:要不要登录 MES?", "不需要。两个接口均已开放匿名访问。"), ("Q2:只调 qmresult 不调 qmcheck 可以吗?", "不建议。可能因前序工位、重复加工等原因上传失败。"), ("Q3:craft 为什么固定 100000?", "MES 约定 100000 表示工位总结果(OK/NG),必须传。"), ("Q4:result 用小写 ok/ng 可以吗?", "不可以,必须大写 OK / NG。"), ("Q5:qmcheck 和 qmresult 的 sn 必须一致吗?", "必须一致,为同一工件条码。"), ("Q6:上传失败要不要重试?", "看 message。若是「已加工」等业务拦截,不要盲重试;网络超时可在本地缓存后定时重试 qmresult。"), ("Q7:工件码有特殊字符怎么办?", "做 URL 编码(如空格、&、= 等)。"), ] for q, a in faqs: add_para(doc, q, bold=True) add_para(doc, "A:" + a) # Section 9 add_heading(doc, "九、对接确认单(请 MES 方填写后发给设备方)", 1) add_table(doc, ["项", "值"], [ ["MES 服务器 IP", "192.168.16.99"], ["端口", "8980"], ["工位号 oprno", "__________"], ["产线号 lineSn", "__________"], ["测试用工件码 sn", "__________"], ["是否需要传过程数据(压力/泄漏值)", "是 / 否"], ["MES 联系人", "__________"], ["联系电话 / 微信", "__________"], ], [5.5, 8.5]) doc.save(OUTPUT) print("Generated:", OUTPUT) if __name__ == "__main__": build()