4. 配方上传流程设计与实现
配方上传(Recipe Upload)在 SEMI 标准语境下,指的是设备把自身存储的配方内容通过通信链路回传给主机的过程。它与配方下载方向相反,但工程复杂度往往更高:下载时主机掌握全部数据,设备只需按序接收;上传时数据源在设备侧,主机必须先把“要什么、要多少、怎么切”讲清楚,设备才能按约定把配方内容分片送出。本节围绕这条链路,从请求、响应、分片、重组、确认到异常重试逐层展开。
一、先明确上传链路的角色与消息分工
在典型的 GEM 实现中,上传流程涉及三类消息:主机发出的上传请求(通常承载在 S2F49 或厂商自定义的 S2F 上,携带配方名、配方类型、目标版本等参数)、设备返回的上传响应(S2F50 或对应回复,告知是否接受请求、数据总长度、分片大小等),以及后续的数据分片传输(S6F11/S6F12 或 S2F51/S2F52 等,按片携带序号与内容)。设备端在收到请求后,需要先做一次“能不能给”的判断:配方是否存在、是否处于可读状态、是否被 PPID 锁定、当前是否正在被工艺使用。只有判断通过,才进入分片发送阶段。
这里有一个容易被忽略的细节:上传请求里的“配方标识”必须与设备本地存储的键一致。如果主机用 PPID 请求,而设备内部用 RecipeID 索引,就需要在设备侧做一次映射,否则会出现“主机认为存在、设备查不到”的假失败。建议在实现时把映射关系固化在配置表中,并在日志中同时打印两个标识,便于排障。
二、分片与重组:把大配方切成可管理的块
配方文件可能只有几 KB,也可能达到几百 KB 甚至更大。SECS 消息本身有长度限制,因此必须分片。分片策略的核心参数有三个:分片大小(Chunk Size)、总片数(Total Chunks)、片序号(Chunk Index)。设备端在发送前先计算总长度,按固定大小切分,最后一片可能不足整片,需要用实际长度标记。主机侧则按序号缓存,全部收齐后按序拼接,再校验整体长度与校验和。
下面给出一个设备端分片发送的伪代码示例,参数为示例假设值,实际项目中需根据设备能力与主机约定调整:
// 示例:分片大小 4096 字节,配方总长 10000 字节
chunkSize = 4096
totalLen = 10000
totalChunks = ceil(totalLen / chunkSize) // = 3
for i in 0 .. totalChunks-1:
offset = i * chunkSize
length = min(chunkSize, totalLen - offset)
payload = recipeData[offset : offset+length]
send S2F51 {
RecipeName : "RCP_A",
ChunkIndex : i,
TotalChunks: totalChunks,
ChunkLength: length,
Data : payload
}
wait for S2F52 ACK
主机侧重组时,建议维护一个接收位图,每收到一片就标记一片,全部标记完成后再拼接。如果某片超时未到,只重传该片,而不是整包重来。这样可以把重传代价从 O(总长) 降到 O(片长)。
三、上传确认:不只是“收到了”
上传确认分两层:片级确认和整体确认。片级确认由主机在收到每一片后立即回复,告诉设备“这一片我收下了,可以发下一片”。整体确认则在所有片收齐、拼接、校验通过后,由主机发出一个“上传完成”的确认,设备收到后才认为本次上传事务结束。如果主机校验失败,应返回明确的失败原因(长度不符、校验和不符、片序号缺失等),设备据此决定是否重传。
四、异常处理与重试策略
上传过程中常见的异常有四类:超时(某片发出后未收到 ACK)、片丢失(主机报告序号缺失)、校验失败(拼接后摘要不符)、事务中断(设备侧配方被删除或状态变化)。针对不同异常,重试策略也应不同:
- 片级超时:重传当前片,最多重试 N 次(示例 N=3),每次重试间隔可递增(如 1s、2s、4s),避免链路拥塞时雪崩。
- 片丢失:主机明确告知缺失序号,设备只补发缺失片,不重发已确认片。
- 校验失败:整包重传,因为无法定位具体损坏位置;重传前建议先重新读取配方内容,防止源数据已变化。
- 事务中断:立即终止上传,向主机发送失败响应,并记录中断原因;不要继续发送剩余片,否则主机侧会收到无意义的碎片。
重试次数和间隔应作为可配置参数,而不是硬编码。示例配置如下:
upload.retry.maxPerChunk = 3
upload.retry.backoffMs = [1000, 2000, 4000]
upload.retry.maxTotal = 10
upload.timeout.chunkAckMs = 5000
upload.timeout.overallMs = 120000
五、把链路串起来看
下图把设备端请求、主机响应、分片发送、主机重组、确认与异常重试放在同一条链路上,便于对照理解各环节的输入输出与判断分支。
从图中可以看到,上传不是一条单向的“设备发、主机收”,而是请求—响应—分片—确认—重试的闭环。设备端在④⑤之间循环,直到所有片都被确认;主机端在③⑤之间循环,直到重组校验通过。异常分支从⑤回到④,形成重试回环。理解这个闭环,就能在实现时把状态机设计清楚:设备侧至少需要“空闲、发送中、等待ACK、重试中、完成、失败”六个状态,主机侧需要“空闲、接收中、重组中、校验中、完成、失败”六个状态。状态迁移的触发条件就是图中各条箭头的实际消息到达或超时。
最后给出一个可直接参考的上传事务检查清单,用于实现自测或联调前核对:
| 检查项 | 判断依据 | 示例值 |
|---|---|---|
| 配方标识映射 | 主机请求标识与设备本地键一致 | PPID → RecipeID 映射表 |
| 分片大小 | 不超过 SECS 消息长度限制 | 4096 字节 |
| 片级超时 | 发出后未收到 ACK 的等待上限 | 5000 ms |
| 单片段重试次数 | 超过则终止事务 | 3 次 |
| 整体校验 | 长度 + 摘要双重校验 | CRC32 比对 |
| 事务总超时 | 从请求到整体确认的上限 | 120000 ms |
把这张清单落到代码里,就是一组可配置参数加一个状态机。上传流程的难点不在单条消息的收发,而在于分片边界、确认时机、重试范围这三件事的配合。只要把片序号、总片数、片长度、摘要这四个字段在设备与主机两侧对齐,上传链路就能稳定运行。