|
|
@@ -0,0 +1,357 @@
|
|
|
+# -*- 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()
|