可用于 .NET 工业设备或采集系统快速实现 SECS/GEM 通信,完成机台对接、数据采集与指令下发。提供 SECS-II 编解码、SML 渲染、HSMS 主动/被动连接、心跳重连及常用消息构造,兼容旧版与新版 .NET,基于内存管道提升性能。【此简介由AI生成】
当前访问频次受限,请登录后继续访问
Msecs
.NET 的轻量 SECS/GEM 协议库: SECS-II 编解码、SML、HSMS-SS 双端,以及常用 GEM / GEM300 消息构造器。 只做协议,不做行为。
msecs 面向半导体设备、EAP/MES 采集器和虚拟设备。它把 SECS-II/HSMS-SS 的“链路上的事”封装好:
你在应用层只写 S1F13 该回什么、S6F11 该推什么,不用自己拼 10 字节头、维护 System Bytes 或处理 Select 握手。
- SECS-II: Item 树编解码,线上格式与 secsgem 对齐,核心报文用 secsgem 黄金字节锁定
- HSMS-SS 双端: 机台侧
HsmsPassivePeer,Host 侧HsmsActivePeer,Select/Linktest/Separate 自动处理 - 完整生命周期:
SendAsync请求-应答(T3)、T5 自动重连、T6 心跳断链、T7 NOT SELECTED 超时、T8 半帧超时 - GEM / GEM300 构造器: 常用 S1/S2/S5/S6/S7/S9/S10,以及 E87/E39/E40/E94 的 S3/S14/S16
- 低分配: 解码零拷贝,编码走
IBufferWriter<byte>,写路径直接对接PipeWriter
安装
dotnet add package Msecs
| 目标框架 | 适用 |
|---|---|
netstandard2.0 |
.NET Framework 4.6.2+、.NET Core 2.x+ |
net8.0 |
.NET 8+ |
依赖只有 Microsoft 官方包: net8.0 为 System.IO.Pipelines;netstandard2.0 另补 System.Memory、System.Threading.Channels、System.Threading.Tasks.Extensions。
快速开始
设备侧(机台,Passive)
链路上的 Select/Linktest/Separate 全部自动,你只写业务分发:
using Msecs.Hsms;
using Msecs.Messages;
using Msecs.Secs2;
using var peer = new HsmsPassivePeer("0.0.0.0", 5000);
// 报文流日志: 一行 SML 看清收发内容
peer.MessageReceived += m =>
Console.WriteLine($"<- {m.Header.SmlTag()} {Sml.RenderCompact(m.BodyItem)}");
peer.DataMessageReceived += (h, body) =>
{
switch ((h.Stream, h.Function))
{
case (1, 13): // Establish Communications
peer.Send(SecsMessages.S1F14(h, "MY-TOOL", "1.0.0"));
break;
case (2, 41): // Remote Command
peer.Send(SecsMessages.S2F42(h, 0)); // HCACK = 0
break;
case (1, 3): // Selected Equipment Status
var req = (ListItem)Item.Decode(body);
var values = req.Items
.Select(_ => (Item)Item.F4(233.5f))
.ToList();
peer.Send(SecsMessages.S1F4(h, values));
break;
}
};
peer.StateChanged += s =>
{
if (s == HsmsState.Selected)
{
// 主动推事件; System Bytes 由连接自动分配
peer.Send(SecsMessages.S6F11(1, 2501,
new[] { (3501, new Item[] { Item.F4(233.5f) }) }));
}
};
peer.Start();
Console.WriteLine("waiting for host...");
Console.ReadLine();
采集侧(Host,Active)
using Msecs.Hsms;
using Msecs.Messages;
using Msecs.Secs2;
using var host = new HsmsActivePeer("192.168.1.10", 5000)
{
LinktestInterval = TimeSpan.FromSeconds(30), // 心跳,Zero = 不发
AutoReconnect = true, // 意外断线按 T5 重连
T3ReplyTimeout = TimeSpan.FromSeconds(45), // SendAsync 等应答上限
};
host.StateChanged += s => Console.WriteLine($"[state] {s}");
host.DataMessageReceived += (h, body) => // 机台主动推的 S6F11/S5F1
Console.WriteLine($"<- {h.SmlTag()} {Sml.RenderCompact(body.Length > 0 ? Item.Decode(body) : null)}");
await host.ConnectAsync(); // 返回即 SELECTED
var s1f14 = await host.SendAsync(SecsMessages.S1F13()); // 按 System 自动配对
Console.WriteLine(Sml.RenderCompact(s1f14.BodyItem));
host.Send(SecsMessages.S2F33( // 报告定义
new[] { (3501u, new uint[] { 1501, 1502 }) }));
host.Send(SecsMessages.S2F35( // 报告链到 CEID
new[] { (2501u, new uint[] { 3501 }) }));
host.Send(SecsMessages.S2F41("START")); // 远程命令
await host.Stop(); // 发 Separate.req 干净断链
SendAsync 的异常三态可区分:
TimeoutException: T3 超时IOException: 连接断开,含断线杀掉在途请求OperationCanceledException: 调用方取消
Select 被拒抛 HsmsException(带 StatusCode)。断线自动重连成功后,断线时失败的请求由应用层决定是否重发。
只用 SECS-II 编解码
不需要连接,也不需要 HSMS:
var body = Item.L(
Item.Binary(new byte[] { 0 }),
Item.L(Item.A("PECVD-X1"), Item.A("1.0.0"))).ToBytes();
// 01 02 21 01 00 01 02 41 08 ... — 与 secsgem 编码一致
var root = (ListItem)Item.Decode(body);
var mdln = ((ListItem)root.Items[1]).Items[0];
Console.WriteLine(((ValueItem)mdln).GetAscii()); // PECVD-X1
核心能力
| 能力 | 说明 |
|---|---|
| SECS-II | E5 数据类型编解码、Item 树、SML 紧凑渲染 |
| HSMS-SS | Active / Passive 双端,共用同一套连接状态机 |
| 请求-应答 | SendAsync 按 System Bytes 自动配对,支持 T3 与取消 |
| 连接生命周期 | Select / Linktest / Deselect / Separate / Reject 自动处理 |
| 超时 | T3 应答、T5 重连、T6 控制事务、T7 NOT SELECTED、T8 字节间隔 |
| DeviceId | 可配 SessionID;ValidateDeviceId 不匹配自动回 S9F1 |
| 多连接保护 | ProtectSelectedConnection 防误连/端口扫描踢掉在用 Host |
| 消息构造器 | 核心 GEM + GEM300,回复类消息自动回显 System/SessionID |
| 诊断 | MessageReceived / MessageSent + SML,直接看报文流 |
性能
本机简易基准(bench/Msecs.Bench,net8.0 Release,200k 次,量级参考):
| 场景 | 吞吐 | 分配 |
|---|---|---|
| S6F11(3 报告)构造+编码 | ~76 万 msg/s | ~1.5 KB/msg |
| S6F11 解码 | ~116 万 msg/s | ~1.3 KB/msg |
| Active↔Passive 本地回环(S1F13↔S1F14) | ~68 万请求/s | — |
与 secs4net 3.1.0 的 codec 对照(同机 .NET 8、同一条 80 B S6F11、编码字节完全一致,预热后取最好值):
| 场景 | msecs | secs4net |
|---|---|---|
| 构造+编码(writer 路径) | ~0.33 µs / 1384 B | ~0.34–0.36 µs / 1304 B |
| 只编码(预构造) | ~0.11 µs / 0 B | ~0.13–0.16 µs / 0 B |
| 小报文解码 | ~0.34 µs / 1288 B | ~1.1 µs / 1304 B |
| 64 KB Binary 解码 | ~0.04 µs / 136 B | ~1.4 µs / 152 B |
只比较 codec,不含 TCP 回环、重连和 GEM 行为。解码的零拷贝优势有代价:解码结果持有输入缓冲区,不要把池化数组提前归还。
消息覆盖
常用构造器,不是 E5/E30 全量:
| Stream | 覆盖 |
|---|---|
| S1 | F1/F2、F3/F4、F11/F12、F13/F14、F15–F18、F21–F24 |
| S2 | F13/F14、F15/F16、F17/F18、F21/F22、F25/F26、F29/F30、F31–F38、F39–F48 |
| S3 | F15–F20、F23–F32(E87 载具管理) |
| S5 | F1/F2、F3–F8(告警) |
| S6 | F5/F6、F11/F12、F15/F16、F19/F20、F23/F24 |
| S7 | F1–F6、F17–F20、F25/F26(配方) |
| S9 | F1/F3/F5/F7/F9/F11/F13 |
| S10 | F1–F6(终端) |
| S14 | F1–F12、F19–F24(E39 对象服务) |
| S16 | F1/F2、F5–F12、F15–F30(E40 工艺任务 + E94 控制任务) |
S2F30 是标准 Equipment Constant Namelist: L[n] <L[6] <ECID> <ECNAME> <ECMIN> <ECMAX> <ECDEF> <UNITS>>,与 secsgem SecsS02F30 一致。
架构与边界
┌────────────────────────────────────────────┐
│ Messages SecsMessages / GEM / GEM300 │
├────────────────────────────────────────────┤
│ HSMS-SS HsmsActivePeer / HsmsPassivePeer │
│ HsmsConnection(共享状态机/事务) │
├────────────────────────────────────────────┤
│ SECS-II Item / Sml / 编解码 │
└────────────────────────────────────────────┘
msecs 只做协议,不做行为。 以下都归应用层:
- SVID/CEID/ALID 管理、事件调度、GEM 通讯状态机
- GEM300 对象模型与状态迁移(E39 对象生命周期、E87 载具状态、E90 基片追踪)
- 配方管理、报警业务规则、场景编排
库提供 S3/S14/S16 的报文构造器,但不提供 DTO、对象模型或状态机。
行为与限制
- System Bytes 自动管理: 应用层不用也不该自己维护计数器;需要请求-应答配对时用
SendAsync。 - DeviceId: 默认
0xFFFF(HSMS-SS)。ValidateDeviceId = true时,非0xFFFF且不匹配的数据消息回 S9F1 并丢弃;0xFFFF一律放行,S9 流报文不回 S9F1(防互刷死循环)。 - 协议错: 非 SELECTED 收数据回
Reject.reqreason 4,未知 SType reason 1,PType≠0 reason 2,无主控制应答 reason 3。 - Select.rsp / Deselect.rsp: 状态码在头部字节 3,SessionID 回显请求;
ProtectSelectedConnection拒绝另一条连接时回 Select Status 1(Already Active)。 - T7/T8: T7 覆盖被动端建链后、Active Deselect 后的 NOT SELECTED 阶段;T8 约束半帧内相邻字节间隔。T8 不是 TCP 能保证的东西。
Send返回 bool: 未连接/已断开/尚未 SELECTED/出站队列满 = false;出站队列有界(65536)。- 文本编码 Latin1:
Item.A对 char > 0xFF 抛ArgumentException,不会静默替换成?;中文等文本请自行编码后用Item.Binary。 Item.L(existingArray)直接持有传入数组: 构造后不要修改它;L(a, b, c)是编译器新建数组,不受影响。- 零拷贝解码: 解码结果引用输入缓冲区;如果输入来自池化数组,解码结果生命周期内不要归还。
升级到 0.9.0
S2F30改为标准 Equipment Constant Namelist,签名发生变化;旧的L[n]{ECID, ECACK}结构是错的。- 补齐
S1F3、S2F29、S5F2。 Send(SecsMessage)在未 SELECTED 时返回 false;SendAsync在未 SELECTED 时抛InvalidOperationException,断线后仍抛IOException。Item.A拒绝非 Latin1;U1/U2/I1等工厂对越界值抛ArgumentOutOfRangeException。Item.L(existingArray)不再复制数组,构造后不要修改。ProtectSelectedConnection的“已有连接占用”状态码从 3 改为 1(Already Active)。
完整变更见 CHANGELOG。
测试
dotnet test
- 255 个测试: 黄金字节、编解码边界、SML、Active↔Passive 真 TCP 回环、FakeEquipment、T3/T5/T6/T7/T8、Reject/S9、GEM300 结构
- 测试运行在
net8.0;netstandard2.0作为打包目标参与编译 - 基准项目: bench/Msecs.Bench
- 最小示例: demo/Msecs.Demo
路线图
- 协议层: 继续补边界用例、性能热点路径和 CI
- GEM300 对象模型与状态机: 按设计留给应用层,库只做报文
- 1.0 之前仍可能有 API 调整;破坏性变更会写进 CHANGELOG
License
关键词: SECS、SECS-II、SECS2、HSMS、HSMS-SS、GEM、GEM300、SEMI E5/E30/E37/E87/E39/E40/E94、半导体设备、EAP、CIM、MES、设备自动化、.NET、C#