Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@

### 新增

- 增加独立 <code>SecsFrame.Sml</code> 程序集,为动态消息和全部 Item 类型
提供确定性 SML 调试文本写出与严格读取。
- 增加 ASCII 可逆转义、JIS-8 原始十六进制字节、InvariantCulture 数字、
无 Body/空 List 区分,以及深度、Item、值和文本长度资源边界。
- 增加 SML 黄金向量、全类型往返、非法语法、源位置和资源限制测试;该
profile 不声明标准 SML 合规。
- 初始化 SecsFrame 仓库、工程规范和 CI。
- 增加 HSMS 帧头、严格长度定界与编解码基础实现。
- 增加 SEMI E5-0725 的完整 SECS-II Item 动态数据模型与严格二进制编解码。
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
|---|---|
| `SecsFrame` | SECS-II 数据模型、HSMS-SS 会话与事务基础 |
| `SecsFrame.Gem` | GEM 通用状态模型与能力服务 |
| `SecsFrame.Sml` | 动态消息的确定性 SML 调试读写 |
| `SecsFrame.Smn` | E173 SMN 日志、文档与消息表示 |
| `SecsFrame.Sedd` | E172 SEDD 设备接口数据字典 |

Expand All @@ -36,6 +37,9 @@ Deselect、Separate、T6/T7 和 Selected 数据门控。内部数据事务 actor
Equipment 应用发送策略的 Collection Event、报警目录查询、单报警发送
启停,以及最小报警通知、Equipment 报警通知发送策略、远程命令运行期精确
目录/逐项可用性/全局回退和应用状态接受策略。
独立 <code>SecsFrame.Sml</code> 为相同动态消息模型提供确定性文本写出、
严格读取、全部 Item 类型往返和显式资源边界;它是非规范性调试 profile,
不进入线上通讯路径。
Item 用法与边界见
[docs/SECS-II-ITEMS.md](docs/SECS-II-ITEMS.md),消息集成见
[docs/SECS-MESSAGES.md](docs/SECS-MESSAGES.md),传输适配边界见
Expand All @@ -53,6 +57,8 @@ Host/Equipment 端点见
[docs/SECS-ENDPOINT-ROLES.md](docs/SECS-ENDPOINT-ROLES.md),
独立 GEM 基础行为见
[docs/GEM-FOUNDATION.md](docs/GEM-FOUNDATION.md),
SML 调试文本见
[docs/SML.md](docs/SML.md),
官方 secs4net 跨实现证据见
[docs/SECS4NET-INTEROP.md](docs/SECS4NET-INTEROP.md),
整体路线图见
Expand Down
1 change: 1 addition & 0 deletions SecsFrame.slnx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/SecsFrame.Gem/SecsFrame.Gem.csproj" />
<Project Path="src/SecsFrame.Sml/SecsFrame.Sml.csproj" />
<Project Path="src/SecsFrame/SecsFrame.csproj" />
</Folder>
<Folder Name="/test/">
Expand Down
3 changes: 3 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ StreamFrame TCP、Pipe、帧边界、连接生命周期
```

E172 SEDD 与 E173 SMN 是可选元数据/表示层,不进入核心通讯路径。
<code>SecsFrame.Sml</code> 同样是只依赖核心动态消息模型的可选调试表示层;
核心包不反向依赖它。

## 核心原则

Expand Down Expand Up @@ -157,5 +159,6 @@ HSMS 头 Session ID 与 System Bytes。没有 W-Bit 的出站消息在写出后

- `SecsFrame` 可以依赖 `StreamFrame`。
- `SecsFrame.Gem` 依赖 `SecsFrame`。
- `SecsFrame.Sml` 依赖 `SecsFrame`,不得依赖 GEM 或线上传输实现。
- 核心包不得依赖 GEM、SML/SMN、依赖注入容器或具体日志实现。
- 状态机计时使用可替换的时间抽象,测试不得依赖真实长时间等待。
11 changes: 6 additions & 5 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,15 @@ Collection Event 发送策略沿用相同的默认不注册模式;拒绝发生
串行化决策,库不引入后台循环或未经 E30 核对的状态矩阵。

阶段 2 其余报警历史/批量控制和完整命令权限/调度都需要先取得授权
E30-0526 材料。下一可执行优先级转入阶段 3 的 SML 调试读写;最小垂直切片
先核对可公开、可许可的语法来源,再为现有动态 <code>SecsMessage</code> 和
全部 <code>SecsItem</code> 类型提供确定性文本写出、读取回环与严格向量,
不宣称标准 SML 合规。
E30-0526 材料。下一可执行优先级已转入阶段 3;SML 调试读写已经基于许可
清晰的公开实现完成非规范性 profile,为现有动态 <code>SecsMessage</code>
和全部 <code>SecsItem</code> 类型提供确定性文本写出、严格读取、资源边界与
自主协议向量,不宣称标准 SML 合规。下一切片在该稳定文本表示上增加 Trace
导出、结构化脱敏与受控重放。

## 阶段 3:元数据与工具

- SML 调试读写。
- [x] SML 调试读写。
- E173 SMN 日志与消息文档。
- E172 SEDD 设备字典导入、校验与发现。
- Trace 重放、脱敏和互操作诊断工具。
Expand Down
74 changes: 74 additions & 0 deletions docs/SML.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# SML 调试读写

## 范围

<code>SecsFrame.Sml</code> 为现有动态 <code>SecsMessage</code> 与全部
<code>SecsItem</code> 类型提供确定性文本写出和严格读取。它是独立的可选
表示层,不进入 HSMS 会话、事务或线上二进制编解码路径。

当前实现是 SecsFrame 自主定义并验证的非规范性 SML 调试 profile,不声明
符合某个 SEMI 标准、厂商方言或一致性测试。消息模型没有名称字段,因此
profile 只表示 Stream、Function、W-Bit 和可选单根 Item。

## 使用

~~~csharp
using SecsFrame;
using SecsFrame.Sml;

var message = new SecsMessage(
stream: 6,
function: 11,
replyExpected: true,
rootItem: SecsItem.List(
SecsItem.Ascii("LOT-001"),
SecsItem.U4(1001)));

var codec = new SmlMessageCodec();
var text = codec.Encode(message);
var decoded = codec.Decode(text);
~~~

写出固定使用 LF、InvariantCulture 数字、明确的 Item 数量和稳定缩进:

~~~text
'S6F11'W
<L [2]
<A [7] 'LOT-001'>
<U4 [1] 1001>
>
.
~~~

读取允许 token 之间存在空白,但格式名、Boolean 和十六进制 token 的大小写
保持严格;完整消息之后的尾随文本会被拒绝。无 Body 使用消息头后直接跟
句点,空 List 使用 <code>&lt;L [0] ... &gt;</code>,两者不会混淆。

## 文本约定

- Item 格式名为 <code>L</code>、<code>B</code>、<code>Boolean</code>、
<code>A</code>、<code>J</code>、<code>I1/I2/I4/I8</code>、
<code>U1/U2/U4/U8</code> 和 <code>F4/F8</code>。
- Binary 与 JIS-8 使用 <code>0xHH</code> 原始字节;JIS-8 不猜测现场代码页。
- Boolean 使用 <code>True</code> 或 <code>False</code>。
- ASCII 位于单引号内,反斜线、单引号、回车、换行、制表符和不可打印
七位字符分别使用可逆转义;<code>\xHH</code> 仍必须位于七位范围。
- 浮点数使用 .NET round-trip 格式,保留有限值、非数和正负无穷的往返。
- 读取严格验证声明数量、数值宽度、单根 Item、消息终止符和尾随文本。

<code>SmlMessageCodec</code> 默认沿用核心 Item codec 的最大嵌套深度和
Item 总数,并另外限制单个 Item 的值数量及输入/输出文本长度。调用方可以
在构造时收紧这些限制;解析失败使用带字符偏移、行和列的
<code>SmlParseException</code>。

## 参考与版权边界

实现前只读交叉核对了 MIT 许可的
[secs4net](https://github.com/mkjeff/secs4net) 中公开的
<code>Secs4Net.Sml</code> 项目,以确认常见的消息头、Item 格式名和 Boolean
拼写。本实现围绕 SecsFrame 的不可变模型和严格资源边界独立编写,没有复制
其源码;测试向量也在本仓库自主构造。JIS-8 原始字节与 ASCII 转义是本
profile 为无损调试增加的明确约定。

该参考不参与构建。官方 secs4net 互操作项目仍只使用 nuget.org 发布并固定
为 3.1.0 的 NuGet 包。
5 changes: 5 additions & 0 deletions docs/STANDARDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@
未来如引入实质性开源代码,必须保留对应版权与许可证文本,并在变更说明
中记录来源。

<code>SecsFrame.Sml</code> 的调试 profile 不是规范性标准实现。开发前只读
参考了 MIT 许可的 secs4net 公开 SML 项目以交叉核对常见文本拼写;当前
实现和测试向量均独立编写,JIS-8 原始字节与 ASCII 转义边界见
[SML.md](SML.md)。

与官方 Secs4Net 3.1.0 的 Select、Linktest 和动态消息往返是独立软件实现
之间的工程互操作证据,不代表任何一方通过 SEMI 一致性认证。具体矩阵见
[SECS4NET-INTEROP.md](SECS4NET-INTEROP.md)。
23 changes: 23 additions & 0 deletions src/SecsFrame.Sml/SecsFrame.Sml.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net8.0;net10.0</TargetFrameworks>
<Description>Deterministic SML debug text support for SecsFrame messages.</Description>
<PackageId>SecsFrame.Sml</PackageId>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>secs;secs-ii;sml;debug;serialization;secsframe</PackageTags>
<Version>0.1.0-alpha.1</Version>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Meziantou.Analyzer" PrivateAssets="all" />
<PackageReference Include="PolySharp" PrivateAssets="all" />
<ProjectReference Include="..\SecsFrame\SecsFrame.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>
87 changes: 87 additions & 0 deletions src/SecsFrame.Sml/SmlMessageCodec.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
namespace SecsFrame.Sml;

/// <summary>
/// Encodes and decodes the deterministic SecsFrame SML debug profile.
/// </summary>
/// <remarks>
/// This is a non-normative diagnostic representation. It does not claim SEMI
/// SML conformance.
/// </remarks>
public sealed class SmlMessageCodec
{
/// <summary>The default number of spaces added for each nested Item.</summary>
public const int DefaultIndentSize = 4;

/// <summary>The default maximum root-inclusive Item depth.</summary>
public const int DefaultMaxNestingDepth = SecsItemCodec.DefaultMaxNestingDepth;

/// <summary>The default maximum number of Items in one message.</summary>
public const int DefaultMaxItemCount = SecsItemCodec.DefaultMaxItemCount;

/// <summary>The default maximum primitive values in one Item.</summary>
public const int DefaultMaxValueCount = 1_000_000;

/// <summary>The default maximum input or output text length.</summary>
public const int DefaultMaxTextLength = 16 * 1024 * 1024;

/// <summary>Creates a deterministic SML debug codec with explicit resource limits.</summary>
public SmlMessageCodec(
int indentSize = DefaultIndentSize,
int maxNestingDepth = DefaultMaxNestingDepth,
int maxItemCount = DefaultMaxItemCount,
int maxValueCount = DefaultMaxValueCount,
int maxTextLength = DefaultMaxTextLength)
{
if (indentSize <= 0 || indentSize > 16)
throw new ArgumentOutOfRangeException(nameof(indentSize), indentSize, "The indent size must be between 1 and 16.");
if (maxNestingDepth <= 0)
throw new ArgumentOutOfRangeException(nameof(maxNestingDepth), maxNestingDepth, "The maximum depth must be positive.");
if (maxItemCount <= 0)
throw new ArgumentOutOfRangeException(nameof(maxItemCount), maxItemCount, "The maximum Item count must be positive.");
if (maxValueCount <= 0)
throw new ArgumentOutOfRangeException(nameof(maxValueCount), maxValueCount, "The maximum value count must be positive.");
if (maxTextLength <= 0)
throw new ArgumentOutOfRangeException(nameof(maxTextLength), maxTextLength, "The maximum text length must be positive.");

IndentSize = indentSize;
MaxNestingDepth = maxNestingDepth;
MaxItemCount = maxItemCount;
MaxValueCount = maxValueCount;
MaxTextLength = maxTextLength;
}

/// <summary>Gets the number of spaces added for each nested Item.</summary>
public int IndentSize { get; }

/// <summary>Gets the maximum root-inclusive Item depth.</summary>
public int MaxNestingDepth { get; }

/// <summary>Gets the maximum number of Items in one message.</summary>
public int MaxItemCount { get; }

/// <summary>Gets the maximum primitive values in one Item.</summary>
public int MaxValueCount { get; }

/// <summary>Gets the maximum accepted or produced text length.</summary>
public int MaxTextLength { get; }

/// <summary>Encodes one dynamic message to deterministic LF-terminated text.</summary>
public string Encode(SecsMessage message)
{
if (message is null)
throw new ArgumentNullException(nameof(message));

return new SmlMessageWriter(this).Write(message);
}

/// <summary>Strictly decodes one complete SML message.</summary>
public SecsMessage Decode(string text)
{
if (text is null)
throw new ArgumentNullException(nameof(text));
if (text.Length > MaxTextLength)
throw new ArgumentOutOfRangeException(nameof(text), text.Length, $"The SML text length cannot exceed {MaxTextLength} characters.");

return new SmlMessageParser(text, this).Parse();
}
}
Loading
Loading