拧紧控制器Open Protocol MID能力表与MES集成避坑
简介:围绕阿特拉斯·科普柯 PowerFocus 6000 的 Open Protocol 开放协议附录,面向使用 PF6000 进行远程控制、数据订阅或二次开发的自动化工程师、PLC 程序员与系统集成人员。文档聚焦协议在 PF6000 上的实现差异,帮助解决开发中对 MID 与修订版本、Relay 及数字/模拟输入信号、Pset 与 Job 选择等细节把握不准的问题。资源包仅含 1 个 PDF 文件,约 980KB,为英文规范附录,便于离线查阅和现场对照;内容按章节梳理 MID 支持表、修订版本支持、MID 64/65 旧结果数据处理,以及紧固程序 Pset 选择、批量大小和序列 Job 选择等用法。已有 195 人学习下载。读者可据此核对消息与版本支持边界,明确 Pset/Job 调用方式,减少联调阶段的协议适配与排错成本,适合需要落地 PF6000 通信接口、进行上位机或 PLC 程序联调的中高级开发者参考,也可作为协议版本核对与接口调试的辅助清单。
1. 产线上拧紧数据接不进 MES,问题常出在开放协议附录没读透
产线上那台拧紧控制器能不能把每次拧紧的扭矩、角度、判定结果直接吐给 MES,很多时候卡住的不是网络,而是对开放协议附录里那几张 MID 表的理解。这份附录针对某型号拧紧控制器(下文简称 TC6)的 Open Protocol 实现,标注 release 2.8 Rev.2、自 2.8.14 起生效,核心内容就三块:哪些 MID 能用、每个 MID 支持到第几修订、继电器和数字输入的编号各代表什么。它解决的是“上位机按通用 Open Protocol 发报文,控制器却回错误码或不回”的问题,适合做产线数据采集、PLC 远程控制、MES 集成的工程师,也适合刚接手拧紧工位、需要快速判断“这个功能到底能不能远程做”的人。附录不是入门教程,它默认你已经知道报文长什么样,所以下面从帧结构开始补课,再把表里的坑逐条拆开。
2. 帧结构与握手:先把 0001/0002 跑通,再谈订阅
2.1 四段式帧:长度头、MID、数据体、空终止
Open Protocol 走 TCP,报文不是 JSON,也不是定长结构体,而是“长度 + 消息号 + 定长字段拼接 + 空字符”的四段式。长度头是 4 字节 ASCII 十进制,值等于 MID 加数据体的字节数,不含长度头自身,也不含结尾的 NUL。MID 是 4 字节 ASCII,比如0001、0061。数据体里的字段按附录规定的固定长度顺序排列,数字通常右对齐补空格或补零,具体补法以你手上的字段表为准。
| 段 | 长度 | 示例 | 说明 |
|---|---|---|---|
| Length | 4 字节 ASCII | 0020 | MID + Data 的字节数 |
| MID | 4 字节 ASCII | 0001 | 消息标识 |
| Data | 变长 | 0060000101 | 定长字段顺序拼接 |
| NUL | 1 字节 | \x00 | 报文终止 |
先把编解码写对,再谈业务。下面这段代码只做两件事:把 MID 和数据拼成可发送的字节流;从 socket 里按长度头精确读一帧。很多联调翻车就翻在“一次 recv 把两帧粘在一起”或者“长度头把 NUL 也算进去”。
import socket def encode_frame(mid: str, data: str = "") -> bytes: # 长度 = MID(4) + Data,不含长度头自身和结尾 NUL payload = f"{mid}{data}" length = f"{len(payload):04d}" return (length + payload).encode("ascii") + b"\x00" def recv_exact(sock: socket.socket, n: int) -> bytes: buf = b"" while len(buf) < n: chunk = sock.recv(n - len(buf)) if not chunk: raise ConnectionError("peer closed") buf += chunk return buf def recv_frame(sock: socket.socket) -> tuple[str, str]: # 先读 4 字节长度头,再按长度读 MID + Data,最后吞掉 1 字节 NUL len_raw = recv_exact(sock, 4) total = int(len_raw.decode("ascii")) body = recv_exact(sock, total).decode("ascii") recv_exact(sock, 1) # NUL return body[:4], body[4:]encode_frame里最容易错的是长度计算:把 NUL 算进去,控制器会多读一个字节,后续所有帧错位;少算 MID,控制器会把数据体开头当成下一个长度头。recv_frame必须用循环读满,不能假设一次recv就能拿到完整帧,工业现场交换机延迟一高,半帧到达是常态。
2.2 最小客户端:连接、发 0001、收 0002
握手只涉及三个 MID:0001 通信启动、0002 启动确认、0003 通信停止。0001 的数据体按附录是定长字段,常见做法是带 revision、cell ID、channel ID 三个字段;不同版本的字段表可能还要求控制器序列号,发之前对照你手上的附录确认一遍,别直接抄别人的报文。revision 填 6,因为附录表 1 里 0001 的 Revision 列写的是 6,表示 0 到 6 都支持,填高版本控制器会按自己的上限回退。
HOST, PORT = "192.168.1.50", 4545 # 控制器实际 IP 和端口以现场为准 sock = socket.create_connection((HOST, PORT), timeout=5) sock.settimeout(5) # revision 3 位、cell ID 4 位、channel ID 2 位,按附录字段表拼接 start_data = "006" + "0001" + "01" sock.sendall(encode_frame("0001", start_data)) mid, data = recv_frame(sock) print(mid, repr(data)) # 期望 0002,data 里带控制器协商后的 revision这段代码跑通的标准不是“没报错”,而是收到 0002 且里面的 revision 不低于你后续要用的 MID 所需版本。如果收到 0004,先别怀疑网络,按错误码查附录:常见是 cell ID 或 channel ID 长度不对,或者控制器已经有一条 Open Protocol 连接占着。0002 里的 revision 是控制器实际接受的版本,后续能力判断以它为准,不要拿你发送的 6 硬编码。
2.3 修订号协商与 0004/0005 的判读
0004 是命令错误,0005 是命令接受,这两个是同步回复,和订阅推送共用同一条 TCP 连接。也就是说,你发一条 0018 选 Pset,可能先收到 0005,再收到 0015 的 Pset 已选订阅推送,中间还夹着别的工位触发上来的 0061 结果。客户端不能假设“发一条收一条”,必须按 MID 做分发,把 0004/0005 当命令回执,把 0015/0035/0061 当异步事件。
修订号在附录表里是累计上限,不是“只支持这一个版本”。比如 0061 写 8,意思是 0 到 8 的修订都支持;你按修订 8 的字段表解析,控制器不会因为你是新客户端就降级。反过来,如果某个 MID 写的是-,比如 0044 断开工具请求、0130 Job off,那就是真不支持,发过去只会拿 0004,别在代码里留分支幻想它某天会通。
3. MID 能力表怎么读:修订上限、p 标记与客户端裁剪
3.1 修订号是累计上限,不是逐个枚举
附录表 1 有三列关键信息:MID、Name、Revision。Revision 数字表示“从 0 到这个数字全都支持”,所以看到 0001 是 6、0002 是 6、0061 是 8,不要理解成“只支持第 6 版”。真正要小心的是带p的行,比如 0002 是6 p、0012 是4 p、0015 是1 P、0033 是2 p、0041 是5 p、0061 是8 p、0065 是8p。p的意思是“至少有一个修订是部分支持”,不是全支持,也不是不支持。
读这张表的正确姿势是:先按 MID 找行,再看 Revision 判断“能不能用”,最后看有没有p判断“要不要探测”。比如 0012 参数集数据上传请求是4 p,你按修订 4 发过去,控制器可能只回部分字段;0065 老结果上传回复是8p,附录专门提示“部分参数不支持”,解析时不能按完整字段表硬解,否则字段错位,扭矩值会变成角度值。
3.2 p 标记:半支持功能的探测式启用
p标记的功能不能靠文档拍板,得靠探测。常见做法是:客户端启动后先跑一遍能力探测,把要用的 MID 各发一条最小请求,记录返回的 MID 和错误码。比如 0250 选择器插座信息订阅是1 P,你先订阅,看 0251 是否回、0252 是否确认;如果 0251 回了但字段明显缺,就降级到只读部分字段,不要阻塞主流程。
探测要有超时和次数上限,别在启动阶段死等。下面这段能力表把“支持 / 部分支持 / 不支持”分开存,后面所有业务分支都查这张表,避免代码里到处写if mid == "0061"。
CAPS = { "0001": {"rev": 6, "partial": False}, "0012": {"rev": 4, "partial": True}, "0018": {"rev": 1, "partial": False}, "0038": {"rev": 2, "partial": False}, "0061": {"rev": 8, "partial": True}, "0065": {"rev": 8, "partial": True}, "0215": {"rev": 2, "partial": False}, } def can_use(mid: str, need_rev: int) -> str: cap = CAPS.get(mid) if not cap: return "unknown" # 附录没列,按不支持处理 if cap["rev"] < need_rev: return "unsupported" # 修订不够 return "probe" if cap["partial"] else "full"need_rev是你业务实际要用的字段所在修订,不是控制器上限。partial为 True 时返回probe,调用方必须走探测分支;返回full才能直接按完整字段表解析。这张表建议从附录手工抄一遍,别用脚本爬,字段名和p标记抄错一个,后面排查成本远高于抄表的时间。
3.3 用能力矩阵裁剪客户端功能
能力矩阵的用处不是“记录文档”,而是“决定客户端启动后开哪些线程”。比如 0060 结果订阅是 8、非 partial,可以放心开主订阅;0064 老结果上传请求是 8、非 partial,但 0065 回复是8p,那补拉线程就要允许字段缺失;0215 IO 设备状态回复是 2、非 partial,IO 巡检线程可以全量解析。
裁剪时按“业务最小集”来:MES 只要扭矩、角度、判定,那就只依赖 0061 的对应字段,不要因为 0061 支持到修订 8 就把所有字段都解析一遍。字段越多,版本差异带来的错位风险越大。我的习惯是给每个 MID 写一个字段定义表,解析函数按表走,表里标清楚“修订几开始有”“是否可能缺失”,这样换现场、换控制器版本时只改表不改代码。
4. 结果数据链路:0060/0061/0062 与 0064/0065 的补拉策略
4.1 订阅、推送、确认的三段式
结果数据的主链路是 0060 订阅、0061 推送、0062 确认。0060 发出去后,控制器不会回一个“订阅成功”的独立 MID,而是每次有拧紧结果就推一条 0061;客户端收到 0061 后必须回 0062 确认,否则控制器可能认为客户端处理不过来,后续推送行为会变。0063 是取消订阅,工位切换或客户端退出时发,别直接断 TCP,断线重连后订阅状态不一定保留。
这里有个时序细节:0062 的确认数据里要带 0061 里的结果序号或时间标识,具体字段按附录表来。确认发早了、发晚了、带错序号,表现都是“前几条能收,后面不推了”。我一般把 0061 的解析和 0062 的组装放在同一个函数里,解析完立刻组确认,中间不做落库、不做 HTTP 上报,避免网络抖动把确认拖过控制器的等待窗口。
4.2 0061 报文字段与解析
0061 是定长字段拼接,字段多,而且修订 8 比早期版本多出不少字段。解析不要手写切片下标,用字段定义表驱动:每个字段给名字、长度、起始偏移(或按顺序累加),解析函数按表走。这样控制器修订变了,只改表。
# 字段表按你手上的附录修订 8 抄,长度单位是字符 RESULT_FIELDS = [ ("cell_id", 4), ("channel_id", 2), ("torque_status", 2), ("angle_status", 2), ("torque", 6), ("angle", 5), # 其余字段按附录顺序继续补 ] def parse_result(data: str) -> dict: out, pos = {}, 0 for name, length in RESULT_FIELDS: out[name] = data[pos:pos + length].strip() pos += length if pos != len(data): out["_unparsed"] = data[pos:] # 长度对不上时留证据,别静默丢 return outtorque和angle拿到的是字符串,转 float 前先判空;_unparsed是排错用的,长度对不上说明字段表版本和控制器不一致,这时候不要强行解析,把原始报文落盘,比对附录字段表。状态字段是数字码,别直接展示给业务,按附录的判定码表翻译成 OK/NOK。
4.3 0064/0065 老结果数据的特殊处理
0064 是按序号补拉历史结果,0065 是回复。附录把这一对单独拎出来讲,原因是 0065 的字段支持和 0061 不完全一样,而且 0065 带p标记,部分参数可能拿不到。补拉的正确用法是“断点续传”:客户端本地记一个已处理的最大结果序号,重连后从该序号之后开始发 0064,收到 0065 解析、落库、更新游标。
补拉要防重:0064 的序号边界是开区间还是闭区间,按附录来;如果不确定,宁可多拉一条,落库时按“控制器序列号 + 结果序号”做唯一键去重。0065 的字段缺失不要当错误,缺失字段填 None,不要用 0 代替,否则报表里会出现一批扭矩为 0 的假数据。补拉线程和 0061 订阅线程要互斥写游标,常见做法是加一把本地锁,或者让补拉只在上电初始化阶段跑,跑完再开订阅。
5. 控制面:Pset/Job 选择、批量计数与继电器/数字输入
5.1 Pset 选择的三种路径
Pset 选择在附录里对应 0010 到 0020 一组 MID。最直接的是 0018 选择参数集,数据体带 Pset 编号;0014 订阅“当前选中的 Pset”,控制器会在切换时推 0015;0016 是 0015 的确认。0010/0011 是上传 Pset ID 列表,0012/0013 是上传 Pset 数据,这两条都带p,字段可能不全,适合做展示,不适合做控制依据。
选 Pset 要注意幂等:同一条 0018 连发两次,控制器可能回两个 0005,也可能第二次回 0004,取决于当前状态。客户端不要因为没收到 0015 就疯狂重发,正确做法是发 0018 后等 0005 或 0004,再用 0014 订阅确认最终选中的是哪个。如果业务要求“选错就停线”,把 0015 里的 Pset 编号和期望值比对,不一致走报警,不要只信 0005。
5.2 批量大小与 Lock at batch done
0019 设置 Pset 批量大小,0020 重置批量计数,0021 到 0024 是 Lock at batch done 的订阅、上传、确认、取消订阅。附录对 0022 有一句关键提示:在 TC6 上这个信号表示“被批量序列完成锁定”。也就是说,批量做到数后工具会被锁,不是简单的计数器归零。
现场常见需求是“一托盘 N 颗,做完锁枪,换托盘再解锁”。做法是:0019 设 N,订阅 0021,收到 0022 后置锁定标志,换托盘时发 0020 重置计数,再发 0018 重选 Pset 或 0038 重选 Job。注意 0020 只重置计数,不解除锁定;解除锁定要靠重新选 Pset/Job 或附录里对应的解锁动作。顺序反了,表现是“计数归零了但枪还是按不动”。
5.3 Job 选择与重启
Job 相关是 0030 到 0039。0038 选择 Job,数据体带 Job 编号;0039 是 Job 重启,把当前 Job 的批量计数和步骤回到起点。0034 订阅 Job 信息,0035 推送,0036 确认。和 Pset 一样,0038 之后不要只等 0005,要等 0035 确认当前 Job 编号。
Job 和 Pset 的关系容易搞混:Pset 是拧紧参数集,Job 是包含多个 Pset 和步骤的序列。产线上如果按车型切换,通常选 Job;如果只是换一种螺栓参数,选 Pset 就够。两者都选的时候,以最后一条生效的命令为准,所以客户端要有明确的“当前上下文”状态机,别让 MES 的 Pset 切换和 PLC 的 Job 切换打架。
5.4 继电器编号空间:0215 回复里的分段规律
0215 是 IO 设备状态回复,附录表 22 列了继电器编号到功能的映射。编号不是连续的语义块,但有几段规律可循:00 到 30 是常用状态(01 OK、02 NOK、05 低扭矩、06 高扭矩、12 Job OK、13 Job NOK、18 控制器就绪、19 工具就绪);31 到 46 是 Pset/Job 运行位;47 到 142 是扩展状态、同步状态、外部控制位;251 到 266 是已完成批量和剩余批量的位域。
解析 0215 时不要按“编号 = 数组下标”硬编码,用字典映射,表里没有的编号留unknown。位域那几段(Running Pset bit0-8、Running Job bit0-8、Completed Batch bit0-7、Remaining Batch bit0-7)要按位拼,不能当独立布尔量读,否则批量 3 会被读成“bit0 和 bit1 都亮”。
5.5 数字输入:0220 到 0225 的订阅与设置
数字输入相关是 0220 订阅、0221 推送、0222 确认、0223 取消订阅、0224 设置功能、0225 重置功能。附录表里 DigIn 编号 00 是 Off,01 Reset batch,02 Unlock tool,05 禁止拧紧,06 禁止松开,07 远程启动脉冲,08 远程启动持续,10 批量递增,12 中止 Job,16 到 19 是 Pset 选择位。
0224 下发前先确认该 DigIn 没有被本地配置占用,下发后用 0220 订阅回读,比对功能号是否生效。回读不一致,常见原因是控制器处于手动模式或该输入被更高优先级的逻辑接管,不是报文写错了。
6. 避坑与排查:六个把联调拖到后半夜的现场问题
6.1 连接层:长度头算错导致粘包和错位
现象是前几条报文正常,跑几分钟后解析全乱,扭矩字段里出现字母。原因多半是长度头把 NUL 算进去了,或者数据体里有中文字符导致字节数和字符数不一致。解决:编码统一用 ASCII,长度按字节算,不用len(str)混算;接收端严格按“读长度、读 body、读 NUL”三步走,任何一步超时就整帧丢弃并重连,不要试图从半帧里恢复。
6.2 时序层:订阅推送和命令回复混在一条流里
现象是发 0018 后收到的第一条是 0061,代码把它当 0018 的回复解析,报字段错。原因是客户端假设“请求-响应”一问一答。解决:按 MID 分发,0004/0005 进命令回执队列,0015/0035/0061/0071 进事件队列;命令回执用“MID + 序号”匹配,匹配不上就超时,不要拿事件报文顶包。
6.3 数据层:0061 字段表版本和控制器不一致
现象是扭矩值看起来像角度值,或者判定状态永远是同一个码。原因是字段表抄的是旧修订,控制器按新修订推了更多字段,偏移整体后移。解决:先读 0002 里的 revision,再查附录确认 0061 对应修订的字段表;解析函数保留_unparsed,长度对不上就落原始报文,别静默截断。
6.4 控制层:Pset 批量计数没重置,第二托盘直接锁枪
现象是第一托盘正常,换托盘后第一颗就报批量完成。原因是 0019 的计数没随托盘重置,0022 的锁定标志也没清。解决:把“换托盘”做成一个固定序列——0020 重置计数、0018 重选 Pset、等 0015 确认、再放行启动;序列里任何一步超时都停线,不要跳过继续跑。
6.5 补拉层:0064/0065 重复入库
现象是断线重连后报表里同一颗螺栓出现两条记录。原因是补拉起点用了“最后一条的序号”而不是“最后一条序号 + 1”,或者 0064 的序号边界理解反了。解决:本地游标只存“已确认处理完的最大序号”,补拉从游标之后开始;入库用“控制器标识 + 结果序号”做唯一键,重复数据在数据库层拦掉。
6.6 部署层:重连后订阅丢了,数据不再推送
现象是网络闪断恢复后,TCP 通了但 0061 一条不来。原因是订阅状态挂在旧连接上,重连后没有重发 0060/0014/0034。解决:把“连接建立”和“业务就绪”分成两个状态,重连成功后按能力表重放订阅命令,每条订阅都等对应的确认或首条推送,全部就绪才把工位状态置为可生产。
7. 进阶:用 0064 做断点续传,再用回放验证整条链路
7.1 把补拉做成可重入的游标机
0064/0065 最适合做断点续传,但前提是游标可重入。我的做法是给每个控制器维护一个last_seq,补拉线程只做三件事:读last_seq、发 0064、收到 0065 后解析并更新last_seq。更新放在落库成功之后,落库失败就不推进游标,下次重跑自然重拉。这样即使补拉中途进程崩了,重启后也不会漏数,最多重复一条,靠唯一键拦住。
def backfill(sock, last_seq: int, batch: int = 50): seq = last_seq + 1 while True: # 0064 数据体按附录拼:起始序号 + 数量 sock.sendall(encode_frame("0064", f"{seq:010d}{batch:04d}")) mid, data = recv_frame(sock) if mid == "0004": break # 没有更多历史结果 if mid != "0065": continue # 跳过异步事件,别在这里解析 row = parse_old_result(data) save_once(row) # 唯一键去重 seq = row["seq"] + 1 # 落库成功才推进last_seq建议持久化到本地 SQLite 或 Redis,不要只放内存。batch不要一次拉太大,现场控制器历史缓冲有限,拉太多会超时;拉到 0004 就停,不要靠“连续空回复”判断结束。
7.2 上线前的回放验证
联调最后一步不是“能收数”,而是“回放能对上”。我会把现场抓的原始报文存成二进制文件,用同一套解析代码离线跑一遍,和控制器面板上显示的结果逐条比对。验收项至少覆盖:0001/0002 握手 revision 一致、0061 扭矩和角度与面板一致、0064 补拉条数与面板历史条数一致、0018 选 Pset 后 0015 回读一致、0020 重置后 0022 锁定标志能清掉。
| 验收项 | 通过标准 | 失败先看 |
|---|---|---|
| 握手 | 0002 revision ≥ 业务所需 | 0004 错误码、连接占用 |
| 结果订阅 | 0061 连续推送且 0062 确认无积压 | 确认序号、字段表版本 |
| 历史补拉 | 0065 条数与面板一致、无重复 | 游标边界、唯一键 |
| Pset 选择 | 0015 回读编号与下发一致 | 0005/0004、当前模式 |
| 批量锁定 | 0022 置位后 0020 能重置 | 重置顺序、Job 状态 |
回放通过再上产线,别在工位上拿真螺栓试错。从那以后我每次接新的拧紧控制器,都先把附录表 1 抄成能力表、把 0061 字段表抄成解析表、再抓一段原始报文离线回放,三件事做完才连现场网络。希望帮到你。
本文还有配套的精品资源,点击获取