☰
工业拧紧控制器开放协议:Revision矩阵、能力协商与编号映射
2026/10/28 13:42:40 网站建设 项目流程

简介:《Appendix for PowerFocus 6000 Open Protocol》是阿特拉斯·科普柯发布的开放协议附录规范,面向从事拧紧系统集成、PLC 与上位机开发的自动化工程师,用于解决 PowerFocus 6000 控制器在远程控制与数据订阅中的协议对接问题。文档以 MID 支持表为主线,逐项标注各条消息可用版本、部分支持与不支持情形,并单列 MID Relay 与数字输入信号对照、修订版本兼容范围、MID 64/65 旧结果数据的特殊处理方式,以及紧固程序(Pset)选择、批次数量设定和序列(Job)选择的具体用法,基本覆盖联调时最常查阅的条目。压缩包内仅有 1 个 PDF 文件,约 980KB,篇幅紧凑、表格密集,适合随查随用。目前已有 195 人学习。对需要核对报文兼容性、排查指令无响应或结果数据异常的开发者来说,这份附录可作为对照官方实现的速查手册,帮助快速确认可用消息集与版本边界,减少试错成本。

1. 附录里的那个「p」,比协议正文更该先读

多数人啃工业拧紧控制器的开放协议,第一步都是去背报文结构:四位长度、四位 MID、两位 Revision、结尾补空格加结束符。格式背熟就以为能开工,结果第一次联调就卡住——报文格式全对,控制器就是不理你。真正让人翻车的东西在另一份文件里:附录。协议正文定义了「报文能长什么样」,附录定义了「这台具体型号的控制器到底认哪几条、认到第几版、哪些字段填不满」。这份附录甚至不讲协议原理,只有三张表,一张 MID/Revision 支持矩阵,一张继电器功能号表,一张数字输入功能号表。

这三张表看起来枯燥,其实是整个远程控制与数据订阅能力的边界契约。哪些 MID 能订阅、Revision 数字代表什么区间、继电器功能号怎么分配、Pset 和 Job 的选择为什么是订阅加确认的来回而不是一问一答,全部藏在这些编号里。把这份附录读透,后面写的代码才不会出现「字段解析全对但语义错位」这种玄学问题。

2. Revision 矩阵:一个数字为什么代表一整段历史

2.1 区间语义:写 8 不等于只支持第 8 版

支持表里 Revision 那一列给的是单个数,比如 Last tightening result data 写 8、Tool data upload reply 写 5。这容易误读成「只支持第 8 版」。实际语义反过来:那个数字是上界,表示从 0 到该数字的全部 Revision 都支持。写成 8,意思是 Rev 0、1、2、3、4、5、6、7、8 全部认。

这个约定省掉了整张表的行数。如果逐版列出,结果类 MID 一条就要占八九行,整张表会膨胀到无法阅读。用区间表示还有个隐含前提:同一 MID 的高版本必须向下兼容低版本的结构,新版本只能在尾部加字段,不能改动已有字段的偏移和语义。这条前提在整个协议家族里都成立,所以区间写法才敢用。反过来说,一旦某天厂商真的做了破坏性改动,这张表就没法维持区间写法了,那才是需要警惕的信号。

表里另外两个符号也要看清楚。-表示完全不支持,连发都不该发,发了大概率收到 Command error 或者干脆石沉大海。p表示部分支持,至少有一个 Revision 是残缺的,能跑通但字段不全。第三种符号比前两种麻烦得多,因为它不是二值判断,代码里没法简单用布尔量表达。

2.2 「p」几乎全部落在回传链路上

把表里所有带p的 MID 挑出来排一遍,规律非常整齐:

MID名称Revision方向
0002通信启动应答6应答
0013Pset 数据上传应答4应答
0015已选 Pset 状态1上报
0033Job 数据上传应答2应答
0035Job 信息4上报
0041工具数据上传应答5应答
0061最近一次拧紧结果8上报
0065历史结果上传应答8应答
0250 至 0253选组器插槽信息四件套1全链路
0140执行动态 Job 请求1请求
0200设置外部受控继电器1请求

十一个里,八个是「数据回传」——要么是控制器向上吐数据的应答,要么是订阅推送的状态帧。剩下三个是写入类,但都依赖现场硬件配置。

原因不难推。回传类报文的载荷是随业务演进的:结果数据里要加扭矩曲线采样、要加角度窗口、要加多段拧紧的判定细节,每加一批字段 Revision 就涨一版。而控制器固件里那颗芯片的算力和内存是固定的,不可能把新版本所有可选字段都填满,于是只能支持到某个 Revision,再往上就是「结构认,字段空」。请求类 MID 反过来,载荷简单、字段稳定,改版机会少,所以基本都是满支持。

这带来一条实践上的硬约束:解析回传报文时不能假定字段一定存在。写解析器要按「最小公共字段集 + 可选扩展」两层设计,先取保证有的长度、状态、扭矩、角度,再把高版本才有的字段放在try里或者按长度判断后再读,缺了就降级处理。

2.3 能力协商:先问 0001,再决定发什么

因为支持矩阵是按型号、按固件版本变的,客户端不能把 MID 列表写死在代码里。标准的做法是连上之后先发 Communication start,从应答里拿对端的能力信息,再决定后续发哪些 MID。下面这个结构是我惯用的做法,把附录里的表直接翻译成运行时数据:

# 能力表:数值 N 表示支持 0..N 全段 Revision;"p" 表示结构认但字段不全;None 表示不支持 CAPABILITY = { 1: 6, # 通信启动,满支持,可以放心发最高版 2: "p", # 通信启动应答,部分支持:只读你确定存在的字段 14: 1, # Pset 选择订阅 15: "p", # 已选 Pset 状态,回传字段有缺口 61: "p", # 最近拧紧结果,Rev 8 但字段不齐 65: "p", # 历史结果应答,Rev 8 但部分参数不支持 35: "p", # Job 信息,Rev 4 部分支持 200: "p", # 设置外部继电器,依赖硬件配置 } def probe(mid: int, want_rev: int) -> str: # 返回 ok / partial / downgrade / unsupported 四态,调用方按四态分支 cap = CAPABILITY.get(mid) if cap is None: return "unsupported" # 附录标 "-",直接跳过,别浪费一次往返 if cap == "p": return "partial" # 能发,但下行解析要按最低公共字段集 if want_rev <= cap: return "ok" return "downgrade" # 你要发的版本高于对端上界,必须降版重发

这里的关键不是代码本身,而是四态的含义。unsupported要整条链路跳过,不能发出去等超时;partial不是错误,是常态,多半数回传 MID 都在这个状态;downgrade才是真正需要处理的分支,意味着你手里的报文模板对端吃不下,得退回到它上界那一版的字段布局重新拼。很多现场问题的根因就是把partial当ok用了,解析器按满字段往下读,读到错位的数据还以为控制器吐的是脏数据。

注意:不要用「发一条试试看有没有回」的方式探测支持情况。部分不支持的行为是静默丢弃而不是返回 Command error,探测一次等于白等一个超时窗口。

3. 编号空间:X = 31 + 4×N 这条公式在算什么

3.1 前 31 个功能号是固定的内置区

继电器功能号表从 00 排到 353,密密麻麻一大片。但仔细看会发现,00 到 30 这一段的功能名是稳定的一组:00 关闭、01 OK、02 NOK、05 低扭矩、06 高扭矩、07 低角度、08 高角度、09 循环完成、10 告警、11 批次未达标、12 Job OK、13 Job NOK、14 Job 运行中、15 车身完成、18 控制器就绪、19 工具就绪、20 工具启动开关、21/22 旋向开关顺时针与逆时针。这三十一个号是控制器本体功能的固定映射,跟接了几个外部继电器模块无关。

固定区的存在有它的道理。外部继电器扩展是可选硬件,一个工位可能接 4 路也可能接 32 路。如果内置功能号跟着扩展数量浮动,那上位机就没法写死「读 01 号判断这台机器这次拧紧是否 OK」。把它钉死在低位,无论硬件怎么配,判断逻辑都能复用。

3.2 按槽位偏移:为什么必须是 4 的倍数

固定区之后才是外部受控继电器的编号,附录给出的分配公式是:

RELAY_BASE = 31 # 00..30 为内置功能固定区 SLOT = 4 # 每路外部继电器占 4 个连续功能号槽 GAP = 8 # 继电器区与数字输入区之间的保留间隙 def relay_addr(index: int, relay_count: int) -> int: """把第 index 路外部受控继电器映射到功能号;越界直接抛错,别回绕""" if not 0 <= index < relay_count * SLOT: raise ValueError("relay index out of range") return RELAY_BASE + index def digin_start(relay_count: int) -> int: """数字输入区的起始功能号 Y = X + 8 + 4*M,M 为数字输入路数""" x = RELAY_BASE + SLOT * relay_count return x + GAP

公式里那个 4 不是随便取的。一路继电器在协议里不只是一个开合状态位,配套还有该路的有效标志、触发沿配置、镜像状态,四个编号正好凑成一个对齐块。用 4 的倍数做槽宽,好处是任意一路继电器的元数据都落在同一个地址模 4 的余数上,固件里可以用位运算直接定位,不用查表。代价是浪费编号——如果某路继电器只需要一个状态位,剩下三个号就空着。编号空间有 3xx 那么大,浪费得起。

中间那 8 个号的间隙更值得注意。它是继电器区和数字输入区之间的缓冲,用来给未来的功能扩展留口子,同时也保证了两个区的起始地址不会紧挨着,避免某天继电器数量刚好加到某个值时和数字输入的起始号撞车。这类「刻意留白」在工业协议里很常见,是一种成本很低的后悔药。

3.3 Tracking event 这一列在区分什么

继电器表最右一列是 Tracking event,值只有 Yes 和 No。文档没解释它的含义,只给了标记。按我的理解,这一列标记的是「该信号是否被纳入追踪事件流」。

具体看哪些是 Yes:01 OK、02 NOK、05 低扭矩、06 高扭矩、07 低角度、08 高角度、12 Job OK、13 Job NOK、14 Job 运行中、18 控制器就绪、19 工具就绪、20 工具启动开关、21/22 旋向开关、90 使能扫码、91 产线控制启动、93 至 102 外部受控 1 到 10、277 有效松脱、293 功能按钮、295/296 拧紧与松脱禁用、302 增强模式自动。

这些全是「状态发生跳变时值得单独记一笔」的信号。而标 No 的那一大片——03 低、04 高、09 循环完成、10 告警、11 批次未达标、23 到 46 的各类运行状态、52 到 55 的三色灯、59 到 89 的同步位、107 到 146 的统计位——要么是持续电平、要么本身已经有独立的订阅 MID。

举个典型对照:09 循环完成标 No,但 01 OK 和 02 NOK 标 Yes。循环完成只能说明动作结束了,OK/NOK 才携带判定结论,事件流只关心后者。10 告警也标 No,因为告警有独立的 0070 到 0078 一整组订阅 MID,走另一条通道,不需要在继电器事件流里重复记一遍。

实践含义很直接:想知道某个信号能不能通过事件流被动拿到,先看这一列。标 No 的信号只能靠订阅 0216 到 0219 的 Relay function 系列,或者干脆轮询 0215 的 IO 设备状态。

3.4 输入用 4 位、上报用 10 位:位域宽度的不对称

Pset 编号在继电器侧的编码用了十个位:bit0 到 bit3 在 32 到 35,bit4 到 bit7 在 59 到 62,bit8 在 111,bit9 在 308。十位能表示 1024 个 Pset。Job 类似,bit0 到 bit3 在 36 到 39,bit4 到 bit7 在 63 到 66,bit8 在 110,九位表示 512 个 Job。

但数字输入侧完全不是这个规模。功能号 16 到 19 是 Pset 选择位 bit0 到 bit3,只给了四位,硬线只能选 16 个 Pset。20 号是 Job 选择位的起始,附录在这一行正好截断。

这个不对称挺说明问题。上报侧(控制器告诉上位机当前在跑哪个 Pset)没有任何物理约束,多分配几个功能号就能扩位宽,所以直接给到十位。输入侧受限于实际端子数量和排线长度,一个工位能接十几路数字输入已经很奢侈,没必要为它预留十位。所以硬件侧用少量位做粗选,需要精确定位某个 Pset 时改走命令通道——MID 0018 的 Select Parameter set,一条报文直接带编号,不受位宽限制。

副作用是这些位号散落在功能号的不同区段里,拼装时不能假设连续性。这一点在代码里必须显式建映射表,图省事用循环算偏移的话,bit4 一上来就会读错。

4. Pset 与 Job:控制器怎么把「选程序」变成状态机

4.1 订阅—确认模型:0014 到 0017 的四件套

Pset 选择不是一问一答。表里围绕它的一组 MID 是:0018 选择 Pset(下行命令)、0014 订阅「已选 Pset 变化」、0015 推送已选 Pset 状态、0016 状态确认、0017 取消订阅。四个动作组成一条完整的观察链路。

设计意图是把「谁改的」和「现在是什么」解耦。0018 是主动改,但改这个动作可能来自上位机,也可能来自控制器面板、来自数字输入的硬线选择、来自 Job 切换过程中的自动跳转。上位机如果只靠「我发了 0018 所以我假定 Pset 变了」来维护本地状态,迟早对不上账。走订阅通道就不一样,无论谁改的,0015 都会推一帧过来,本地状态永远跟着实际走。

代价是握手变长。收到 0015 之后必须回 0016 确认,不回的话控制器会当成上位机没收到,重推或者直接把这条订阅挂死。这类「推—确认」模式在整份协议里反复出现,告警是 0071/0072,结果是 0061/0062,Job 信息是 0035/0036,全是同一个套路。

提示:确认报文里的 MID 号要和收到的推送对应,不能复用同一个模板。把 0072 的确认报文拿去回 0061,控制器不会报错,只会静默不认,表现为推送停不下来。

4.2 0022 的语义漂移:Lock at batch done 不等于「批次完成」

批次相关的 MID 是 0019 设置批次大小、0020 重置批次计数器、0021 到 0024 的 lock at batch done 四件套。文档在 0022 上单独加了一句备注:在这个型号的控制器里,该信号表示的是「由批次序列完成而触发的锁定」,跟「批次做完」不是一回事。

这个差别很容易被忽略,但它决定了你该在什么时机解锁工具。如果代码逻辑是「收到 lock at batch done 就认为这一批做完了,可以开始下一批」,那么在批次因为异常中断、或者因为序列跳转提前退出的时候,锁是解了的,但批次并没有真正走完,计数也没清。正确做法是同时看 0022 的锁定信号和批次位域里的完成计数——251 到 258 的 Completed Batch bit0 到 bit7,以及 259 到 266 的 Remaining Batch bit0 到 bit7,两路对上了才算真完成。

还有一个细节:批次计数器的位宽是八位,最大 255,而 Pset 和 Job 的编号位宽是十位和九位。三个位域宽度不统一,不能写一个通用解位函数套用。

4.3 Job 的两种执行路径:0038 静态选择 vs 0140 动态拼装

Job 的入口有两个。0038 是 Select Job,带一个 Job 编号,把控制器切到预先配置好的那条序列上。0140 是 Execute dynamic Job request,接受的是运行时拼出来的序列内容,不是编号。后者在支持表里标了p,Revision 1 部分支持。

两条路径的适用场景完全不同。0038 依赖控制器里已经存好 Job 配置,上位机只需要知道编号,改配置要去改控制器或者走 0032 的 Job 数据上传。0140 把序列内容放在报文里下发,适合上位机侧完全掌握工艺配方、控制器只当执行器的架构,比如同一条产线上不同产品混流,配方在主控系统里而不是在控制器里。

0140 部分支持这件事必须提前验证。现场常见的失败模式是:报文结构对,字段填得也全,控制器返回 Command accepted,但实际执行的序列和下发的不一致——某些字段被忽略,落回了默认值。这类问题排查起来很难,因为协议层没有报错。稳妥的做法是下发之后立刻订阅 0034 的 Job info,把控制器实际认定的序列读回来比对,对不上就说明这条路径在当前固件下不可靠,退回 0038。

4.4 位域拼装:从散落的继电器号还原编号

把 Running Pset 和 Running Job 从继电器镜像还原成编号,是最容易写错的一段。位号不连续,必须建表:

# 位号 -> 继电器功能号,注意 4 到 7 跳到了 59..62,8 和 9 跳得更远 PSET_BIT = {0: 32, 1: 33, 2: 34, 3: 35, 4: 59, 5: 60, 6: 61, 7: 62, 8: 111, 9: 308} JOB_BIT = {0: 36, 1: 37, 2: 38, 3: 39, 4: 63, 5: 64, 6: 65, 7: 66, 8: 110} def decode_running(relay_state: dict[int, bool]) -> tuple[int, int]: """relay_state: 功能号 -> 电平。返回 (pset_id, job_id)""" pset = sum(1 << b for b, addr in PSET_BIT.items() if relay_state.get(addr, False)) job = sum(1 << b for b, addr in JOB_BIT.items() if relay_state.get(addr, False)) return pset, job

relay_state.get(addr, False)里的默认值不能省。设备实际配的继电器路数决定了这些功能号里哪几个真正存在,没配的那几路读回来是空,当成 False 处理才安全。如果默认成 True,一个只配了 8 路的工位会算出个根本不存在的 Pset 编号,而且这个错误编号看起来完全合法,排查的时候会往错误方向查很久。

同样的思路可以套到 295 到 296 的拧紧与松脱禁用、302 的增强模式自动状态上,这些都是单 bit 信号,直接按功能号取值即可,不需要拼装。

5. 避坑与排查:五条来自联调现场的记录

5.1 订阅发出去却收不到回音

现象:发完 0210 Status external monitored inputs subscribe,等了十几秒一帧 0211 都没来,但 0002 通信启动的应答是正常的。

原因:附录在 0210 那一行专门标了一句,要收到第一帧 0211,设备侧必须先配置好外部监控信号。没配置的话,控制器认为没有可监控的输入,订阅请求被接受但不产生任何数据,连空帧都不发。

解决:订阅之前先发 0214 IO device status request 探一下设备配置,返回的 0215 里能看到实际接了几路继电器、几路数字输入。路数为零就别订阅了,先让现场把硬件配好。

现象:订阅 0034 Job info 后收到一帧 0035,回了 0036 确认,然后再也没有新数据。

原因:Job info 的推送是变化触发的,Job 没变化就不推。这跟结果数据不一样,结果数据每次拧紧都推,天然有节奏。

解决:把「收到推送」和「状态有效」分开。订阅之后那一帧就是当前状态,直接落库即可,后续没有推送不代表断链,要靠 9999 Keep alive 保活报文判断链路是否还活着。

现象:解析 0061 最近拧紧结果时,前几台设备一切正常,某台设备开始偶发字段错位。

原因:0061 是部分支持,Revision 8 但字段不齐。不同固件版本填的字段数量不同,按满字段硬解的解析器在字段少的设备上会一路读偏。

解决:解析前先按报文长度算实际载荷字节数,再决定读哪些字段。长度不够就只取最小公共集:状态、扭矩、角度、时间戳。别用固定偏移量硬读。

5.2 状态对不上账

现象:上位机显示当前 Pset 是 3 号,但控制器面板显示 5 号,两边对不上。

原因:本地状态是上位机自己维护的,只在发 0018 的时候更新。如果现场有人用手持设备或者数字输入硬线切了 Pset,上位机完全不知道。

解决:必须订阅 0014,把状态来源从「自己记」改成「控制器推」。收到 0015 就覆盖本地值,回 0016 确认。同理,Job 走 0034。

现象:继电器功能号 20 之后的功能查不到定义,代码里只能猜。

原因:这份附录的数字输入功能表在 20 号处截断了,20 号本身只写了「J」就断了。

解决:20 号之后别猜。真要用的功能先去查对应的 MID 定义,或者直接读 0215 返回的现有配置反推功能号含义。硬猜位含义,出来的错误比不实现更难查。

6. 设计取舍:把兼容性写成表,还是写成查询

这份附录背后有一个值得琢磨的架构选择:协议把「这台设备支持什么」固化成了文档里的静态表格,而不是设计一个运行时的能力查询接口。

静态表格的好处非常实际。它是一份可以提前阅读的契约,上位机在写代码之前就能知道边界在哪,不需要先连一台真机去问。表一旦发布就相对稳定,跨版本对比两张表就能看出哪几条 MID 变了,升级评估的成本极低。坏处也很明显:表就是接口本身,表错一行,所有按表写代码的客户端全错,而且错误发生在运行时而不是编译时。第 5 章里那几条踩坑记录,几乎全部源于「表里的字面语义和实际行为有偏差」。

另一种做法是运行时能力查询:设备提供一个接口,返回它支持的 MID 列表和每个 MID 的 Revision 上界。这种方式天然适应固件差异,客户端不用维护静态表。代价是客户端在连上真机之前完全无法设计,也不能离线验证,而且多一次往返,连上之后的每条命令都得等能力协商完成才能发。

这份附录选择了前者,我理解是因为它的使用场景大多是产线集成:上位机程序在一个工位上写完,要复制到几十个工位;工位的硬件配置和控制器固件是同批采购的,差异很小。这种场景里,提前知道边界比动态适应更值钱。如果换成设备型号混杂、固件版本随时打补丁的环境,静态表的维护成本就会反过来超过收益。

要验证一张静态表在真实环境里准不准,我的做法是做一次全量扫描:把表里每个支持和不支持的 MID 各发一次,记录实际响应,跟文档对一遍。20 分钟能扫完,扫出来的偏差大多集中在p标记那十几条上。那一次扫描之后,我在所有按附录写解析器的地方都加了一条默认规则:凡是标p的 MID,先按长度判断字段存在性,再解析内容,绝不假定字段齐全。

从那以后我每次拿到新一版附录,第一件事就是把p标记的 MID 挑出来单独列一张表,对着实际设备跑一轮长度核对,跑不通就不写解析器。这个习惯救过我好几次——有一次升级固件后某条结果 MID 的p从「部分支持」变成了满支持,字段数量悄悄多了两个,靠长度核对一眼就看出来了,没有等到线上数据出问题才发现。希望帮到你。

这份附录的三张表——Revision 矩阵、继电器功能号、数字输入功能号——是唯一能对着核的东西,值得存一份在手边,比任何二手笔记都可靠。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询