Msecs 0.8.2
dotnet add package Msecs --version 0.8.2
NuGet\Install-Package Msecs -Version 0.8.2
<PackageReference Include="Msecs" Version="0.8.2" />
<PackageVersion Include="Msecs" Version="0.8.2" />
<PackageReference Include="Msecs" />
paket add Msecs --version 0.8.2
#r "nuget: Msecs, 0.8.2"
#:package Msecs@0.8.2
#addin nuget:?package=Msecs&version=0.8.2
#tool nuget:?package=Msecs&version=0.8.2
msecs
.NET 的 SECS/GEM 协议库(手搓,Span/Pipelines 内核)。目标 netstandard2.0(.NET Framework 4.6.2+)与 net8.0——老工控机上的 .NET Framework 项目和现代 .NET 项目都能直接引用。三层设计:
- SECS-II 层:数据项(Item)树的编解码 + SML 紧凑渲染。编解码与 secsgem 线上格式逐字节一致,用 secsgem 真实编码出的黄金样本单测锁定;构造 API 是 SML 的直译(
Item.L(Item.U4(1), Item.F4(1.5f))),写 S1F14/S6F11 时几乎照抄报文结构。编码走IBufferWriter<byte>(可直写PipeWriter),解码走ReadOnlySpan<byte> - HSMS-SS 层:双端。被动端(机台侧)监听等 Host 连——与真实机台一致的行业惯例;主动端(Host/采集器侧)主动连机台、发 Select 握手、可选周期 Linktest 心跳。Select/Linktest/Separate 自动处理,优雅退出先发 Separate.req 让对端干净复位 GEM 状态。0.5.0 起带全套连接生命周期:
SendAsync请求-应答关联(T3)、T6 心跳无应答断链、T5 自动重连、T7 建连超时。0.6.0 补齐真机联调刚需:DeviceId 可配 + 收包校验(不匹配自动 S9F1)、协议错自动 Reject.req(非 SELECTED 收数据/未知 SType/PType≠0)、被动端多连接保护(Select.rsp status 3)与主动心跳、Stop 后可重启。0.7.0 新增SystemBytesStart:本端主动消息的 System 起点可挪到高位段——两端计数器都从 1 起步时,机台主动消息(S6F11/S5F1)会与 Host 在途请求撞号,被对端事务配对误收成应答(HSMS 无应答标志,配对只认 System 回显),机台侧设0x40000000 | 种子即可彻底避让。读循环基于PipeReader(帧直接在ReadOnlySequence上切,零整帧拷贝),写走PipeWriter.GetSpan一次装帧 - 消息构造器层:常用 SxFy 一行拼出。核心 GEM:S1F1/2、S1F3→4、S1F11→12(SV 名单)、S1F21→24(DV/CE 名单)、S1F13→14、S1F15→18(离线/在线控制)、S2F13→14、S2F15→16、S2F17→18、S2F21→22、S2F25→26(回环诊断)、S2F29→30、S2F31→32、S2F33→34、S2F35→36、S2F37→38、S2F39→40、S2F41→42、S2F43→44(spooling)、S2F45→48(变量限值)、S5F1、S5F3→8(告警名单)、S6F5→6、S6F11→12、S6F15→24、S7F1→6(配方装载)、S7F17→20(配方删除/目录)、S7F25→26(格式化配方)、S10F1→6(终端)、S9F1/3/5/7/9/11/13。GEM300:S3F15→32(E87 载具管理)、S14F1→24(E39 对象服务)、S16F1→30(E40 工艺任务 + E94 控制任务)
边界(重要)
msecs 只做协议,不做行为:SVID/CEID/ALID 的管理、事件调度、GEM 通讯状态机、场景编排归你的应用层。GEM300 报文构造器已覆盖(E87 载具 / E39 对象服务 / E40 工艺任务 / E94 控制任务),但对象模型与状态机(E39 对象生命周期、E87 载具状态、E90 基片追踪)仍归应用层——库里没有 DTO、没有状态迁移。
性能(本机简易基准,net8.0 Release,量级参考)
| 场景 | 吞吐 | 分配 |
|---|---|---|
| S6F11(3 报告)构造+编码 | ~31.7 万 msg/s | ~2.8 KB/msg(Item 树对象为主) |
| S6F11 解码 | ~130 万 msg/s | ~1.6 KB/msg |
| Active↔Passive 本地回环(S1F13↔S1F14 全往返) | ~13.5 万请求/s | — |
包结构与引入
| 项目 | 内容 |
|---|---|
| src/Msecs | 核心包(net8.0 零第三方依赖;ns2.0 由官方 System.* 包补齐) |
| demo/Msecs.Demo | 控制台最小虚拟机台:应答 S1F13/S1F3,打印 SML 报文流 |
| bench/Msecs.Bench | 简易基准 |
安装(NuGet 包 Msecs):
dotnet add package Msecs
dotnet test Msecs.sln # 227 个测试:黄金样本字节锁定 + Active↔Passive 全链路回环 + 定时器/重连/协议符合性边界
快速上手:设备侧(机台,Passive)
链路上的事(Select/Linktest/Separate/状态机)全部自动,你只写业务分发:
using Msecs.Hsms;
using Msecs.Messages;
using Msecs.Secs2;
var peer = new HsmsPassivePeer("0.0.0.0", 5000);
peer.MessageReceived += m =>
Console.WriteLine($"<- {m.Header.SmlTag()} {Sml.RenderCompact(m.Body.Length > 0 ? Item.Decode(m.Body) : null)}");
peer.DataMessageReceived += (h, body) =>
{
switch ((h.Stream, h.Function))
{
case (1, 13): peer.Send(SecsMessages.S1F14(h, "MY-TOOL", "1.0.0")); break;
case (2, 41): peer.Send(SecsMessages.S2F42(h, 0)); break; // 远程命令确认
case (1, 3):
var req = (ListItem)Item.Decode(body);
peer.Send(SecsMessages.S1F4(h, req.Items.Select(_ => Item.F4(233.5f)).ToList()));
break;
}
};
peer.Start(); // Host 连上来即走 Select → SELECTED → 收发
peer.Send(SecsMessages.S6F11(1, 2501, // 主动推事件(System Bytes 由库自动分配)
new[] { (3501, new Item[] { Item.F4(233.5f) }) }));
快速上手:采集侧(Host,Active)
using Msecs.Hsms;
using Msecs.Messages;
using Msecs.Secs2;
var host = new HsmsActivePeer("192.168.1.10", 5000)
{
LinktestInterval = TimeSpan.FromSeconds(30), // 可选心跳(带 T6 无应答断链);Zero = 不发
AutoReconnect = true, // 意外断线每 T5(默认 5s)自动重连;默认关
T3ReplyTimeout = TimeSpan.FromSeconds(45), // SendAsync 等应答上限(E30 推荐 45s)
};
host.StateChanged += s => Console.WriteLine($"[state] {s}");
host.DataMessageReceived += (h, body) => // 机台主动推的 S6F11/S5F1 走这里
Console.WriteLine($"<- {h.SmlTag()} {Sml.RenderCompact(Item.Decode(body))}");
await host.ConnectAsync(); // 连接 + Select 握手,返回即 SELECTED(T7 超时,默认 10s)
var s1f14 = await host.SendAsync(SecsMessages.S1F13()); // 请求-应答按 System 自动配对
host.Send(SecsMessages.S2F33( // 报告定义(不等应答的用 Send)
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。断线自动重连成功后,断线时失败的请求由应用层重发。
SECS-II 编解码单用(不需要连接):
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);
实现备注
- System Bytes 由连接在发送时自动分配,应用层不用也不该自己管计数器;按 System 关联应答直接用
SendAsync(配对在库内完成),配对的应答仍会走DataMessageReceived事件 - DeviceId(SessionID):E37 规定 HSMS-SS 恒 0xFFFF(本库默认);对端按具体 DeviceID 收发/校验时,两个 peer 都有
DeviceId属性,ValidateDeviceId = true后收包不匹配自动回 S9F1 并丢弃。0.8.1 起有两处例外:①SessionId 0xFFFF一律放行——它是 E37 给 HSMS-SS 单会话的「未指定」值,Host 在建链(S1F13)前一律用它,判成冒犯会让对端永远握不上手;② S9 流报文不回 S9F1——S9 本身就是错误报告,对 S9 再回 S9F1 会与同样实现的对端互刷成死循环。回复类消息(ForDataReply)恒回显请求的 SessionID - 协议错自动 Reject.req(E37,reason 在头部字节 3):非 SELECTED 收数据消息(reason 4)、未知 SType(reason 1)、PType≠0(reason 2)。此前是静默丢弃/忽略,对端会在 T3 里盲目重试
- 控制报文状态码在头部字节 3(0.8.2 修正):E37 §8.2.3 / §8.2.5 规定 Select.rsp 的 SelectStatus、Deselect.rsp 的 DeselectStatus 放在头部字节 3,Message Length 恒为 10(无 body)。0.8.1 及更早误放在 body,严格的对端按字节 3 读会一律读到 0(恒为「接受」),而本端想表达的 status=3 传不出去 —— 两端状态不一致。现按标准收发;读取侧兼容旧写法(头部字节 3 非 0 时以它为准,否则退到
body[0],都没有按 0),对端升级不同步不会断联 - 拒绝 Select 后关闭连接(0.8.2):E37.1 Table 1 #4 —— 决定拒绝并回非零 SelectStatus 之后必须关闭 TCP/IP 连接;0.8.1 及更早停在 CONNECTED。关之前先把 Select.rsp 刷出去,否则对端只看到 EOF。主动端收到非零 Select.rsp 同样关连接(Table 2 #4)
- 被动端多连接:
ProtectSelectedConnection = true时,已 SELECTED 期间新到连接的 Select.req 回状态 3(端口被占用),旧连接不动,随后按 E37.1 Table 1 #4 关闭那条新连接;旧连接断开后对端重连即被接受(SelectStatus 委托是每次请求求值的,旧连接一死就回 0)。默认 false = 新连接立即顶掉旧连接(0.5.0 行为) - 文本编码用 Latin1(ISO-8859-1, codepage 28591),与 secsgem 一致,任何 char 映射 0..255
- S5F1 的 ALCD 用
Binary而非U1——U1 的 format 码 secsgem 不认(实测踩坑) - 连接读写循环在内部
HsmsConnection上,Passive/Active 共用,只有"谁发起 Select"的角色差异;0.5.0 的请求-应答事务表(T3/T6/T7/断线失败)也在这层,两个 peer 对称受益 - Linktest 节拍 = 上一笔心跳事务结束(收到 rsp 或 T6 断链)后隔
LinktestInterval,不叠加发;被动端LinktestInterval默认 Zero(不发起,只应答),需要检测半开连接时可开启 Send返回 bool:未连接/已断开 = false,不再静默丢;HsmsMessage.BodyItem是 body 的 SECS-II 解码便捷视图(空 body 为 null,首次访问缓存)- T8(E37 网络字符间隔)在 TCP 之上由传输层保证,不单独实现
- netstandard2.0:
PipeReader.Create(Stream)不在包里,内部用自写泵把流灌进Pipe;Stream 的 Memory 重载 / ArrayBufferWriter 有垫片
路线图
- GEM300 对象模型与状态机(E39 对象生命周期 / E87 载具状态 / E90 基片追踪)——按设计只做报文,行为层留给应用
GEM300 消息族(E87 载具 / E39 对象服务 / E40 任务 / E94 控制作业)(0.8.0 已完成)T3/T5/T6 超时参数与 Linktest 超时检测(0.5.0 已完成)断线自动重连(Active)(0.5.0 已完成)DeviceId 支持、Reject/S9 自动应答、被动端心跳与多连接保护、S7/S10 消息族(0.6.0 已完成)
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- System.IO.Pipelines (>= 9.0.0)
- System.Memory (>= 4.5.5)
- System.Threading.Channels (>= 9.0.0)
- System.Threading.Tasks.Extensions (>= 4.5.4)
-
net8.0
- System.IO.Pipelines (>= 9.0.0)
- System.Threading.Channels (>= 9.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.