Msecs 0.8.2

dotnet add package Msecs --version 0.8.2
                    
NuGet\Install-Package Msecs -Version 0.8.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Msecs" Version="0.8.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Msecs" Version="0.8.2" />
                    
Directory.Packages.props
<PackageReference Include="Msecs" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Msecs --version 0.8.2
                    
#r "nuget: Msecs, 0.8.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Msecs@0.8.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Msecs&version=0.8.2
                    
Install as a Cake Addin
#tool nuget:?package=Msecs&version=0.8.2
                    
Install as a Cake Tool

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.8.2 0 9/18/2026
0.8.1 84 9/11/2026
0.8.0 93 9/9/2026
0.7.0 93 9/4/2026
0.6.0 87 9/2/2026
0.4.0 95 9/1/2026
0.3.0 92 9/1/2026