| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- # -*- 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()
|