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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,9 @@
处理器并在原事务上自动回复,无需预生成消息类型或完整消息目录。
- 未匹配连接事件继续由应用处理;处理器注册可释放替换,异常、取消和
W-Bit 契约错误明确传播。
- 增加拥有连接与动态路由生命周期的 <code>SecsHost</code> 和
<code>SecsEquipment</code> 公共端点,角色与 Active/Passive 保持正交。
- 增加 Host Active/Equipment Passive 及反向拓扑的双向 Primary/Secondary
真实 TCP 回环测试。

[Unreleased]: https://github.com/CSJ608/SecsFrame/compare/main...HEAD
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Deselect、Separate、T6/T7 和 Selected 数据门控。内部数据事务 actor
[docs/HSMS-DIAGNOSTICS.md](docs/HSMS-DIAGNOSTICS.md),
运行期消息处理见
[docs/HSMS-PRIMARY-ROUTING.md](docs/HSMS-PRIMARY-ROUTING.md),
Host/Equipment 端点见
[docs/SECS-ENDPOINT-ROLES.md](docs/SECS-ENDPOINT-ROLES.md),
官方 secs4net 跨实现证据见
[docs/SECS4NET-INTEROP.md](docs/SECS4NET-INTEROP.md),
整体路线图见
Expand Down
5 changes: 5 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ API。#38 已合入上游但尚未发布,#39 正在实现;正式包可用后
修改。Active/Passive 共享状态模型,Host/Equipment 角色不参与连接模式
判断。

公共 <code>SecsHost</code> 与 <code>SecsEquipment</code> 在连接和动态路由
之上建立角色组合边界。两者都拥有连接生命周期、事件流、动态发送和
Primary 处理能力;角色固定但 Active/Passive 仍由独立选项决定。后续 GEM
能力依赖具体角色类型,不反向污染 HSMS 状态机。

~~~text
TCP SessionOpened
|
Expand Down
9 changes: 5 additions & 4 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@
- [x] 公共结构化诊断模型,覆盖运输、协议、会话、事务和解码故障。
- [x] 运行期 Primary 路由与可释放处理器注册,不依赖预生成消息类型。
- [ ] 使用授权 E37/E37.1 核对 T5/T8 默认值与精确启停边界。
- [ ] Host、Equipment 双角色能力及对应回环集成测试。
- [x] Host、Equipment 双角色端点及两种 Active/Passive 拓扑的双向回环。

阶段 1 的下一最小切片是建立 Host/Equipment 角色能力及双角色回环,
同时继续跟踪 StreamFrame #38/#39 的正式包发布并准备等价迁移。任何标准
默认值和完整合规结论都依赖团队合法获得的 E37/E37.1 副本与一致性测试。
阶段 1 的工程能力已经形成完整基础链路。下一切片进入 GEM 通用行为,同时
继续跟踪 StreamFrame #38/#39 的正式包发布并准备等价迁移。任何标准默认值
和完整合规结论都依赖团队合法获得的 E37/E37.1 及相关 GEM 标准副本与
一致性测试。

## 阶段 2:GEM 通用能力

Expand Down
66 changes: 66 additions & 0 deletions docs/SECS-ENDPOINT-ROLES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Host 与 Equipment 端点

## 角色与连接模式正交

<code>SecsHost</code> 和 <code>SecsEquipment</code> 表达 SECS 应用角色;
<code>HsmsConnectionMode.Active</code> 与 <code>Passive</code> 只表达哪一端建立
TCP 连接。库不从其中一个推断另一个。

当前真实 TCP 回环持续验证以下两种常见拓扑:

| Host | Equipment | 已验证方向 |
|---|---|---|
| Active | Passive | Host Primary/Equipment Secondary;Equipment Primary/Host Secondary |
| Passive | Active | Host Primary/Equipment Secondary;Equipment Primary/Host Secondary |

这两种连接方向具有相同的动态消息和事务能力。角色不会改变 Select、T3、
T5、T6、T7、T8、Linktest 或运输重连语义。

## 端点所有权

每个角色端点拥有一个 <code>HsmsConnection</code> 和一个
<code>HsmsPrimaryRouter</code>,并公开以下组合能力:

- Start、等待 Selected、读取状态和单消费者事件流;
- 发送动态 Primary、手工回复未匹配 Primary;
- 运行期注册与分派 Primary 处理器;
- Linktest、Deselect 和 Separate;
- 异步释放整个端点。

释放角色端点会释放其连接。角色在构造后固定,连接方向、地址、协议
Session ID 和计时器仍通过显式 <code>HsmsConnectionOptions</code> 提供。

~~~csharp
await using var equipment = new SecsEquipment(
new HsmsConnectionOptions(
IPAddress.Any,
port: 5000,
HsmsConnectionMode.Passive,
sessionId: 0,
t3,
t5,
t6,
t7,
t8));

using var route = equipment.RegisterPrimaryHandler(
stream: 1,
function: 1,
HandleAreYouOnlineAsync);

equipment.Start();
await equipment.WaitUntilSelectedAsync(cancellationToken);
~~~

以上只展示 API 组合,消息含义和计时器值不是标准推荐或默认值。

## 能力边界

基础角色端点故意保持对称:Host 和 Equipment 都可能发起 Primary,也都
可能处理对端 Primary。后续 GEM 层会通过依赖具体的
<code>SecsHost</code> 或 <code>SecsEquipment</code> 类型提供不同业务能力,
而不是修改 HSMS 状态机或根据 Active/Passive 猜测角色。

当前切片没有实现 E30 的在线状态、变量、设备常量、报告、事件、报警或
远程命令语义,因此不声明 GEM 合规。相关行为必须继续记录标准版本、测试
证据与未核对边界。
89 changes: 89 additions & 0 deletions src/SecsFrame/Roles/SecsEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
namespace SecsFrame;

/// <summary>
/// Owns one role-specific SECS endpoint composed from an HSMS connection and
/// runtime Primary router.
/// </summary>
public abstract class SecsEndpoint : IAsyncDisposable
{
private readonly HsmsConnection _connection;
private readonly HsmsPrimaryRouter _router;

private protected SecsEndpoint(
SecsEndpointRole role,
HsmsConnectionOptions options)
{
Role = role;
_connection = new HsmsConnection(
options ?? throw new ArgumentNullException(nameof(options)));
_router = new HsmsPrimaryRouter(_connection);
}

/// <summary>Gets the fixed SECS application role.</summary>
public SecsEndpointRole Role { get; }

/// <summary>Gets the immutable HSMS connection settings.</summary>
public HsmsConnectionOptions Options => _connection.Options;

/// <summary>Gets the latest observed HSMS session state.</summary>
public HsmsSessionState State => _connection.State;

/// <summary>Starts the owned connection.</summary>
public void Start()
=> _connection.Start();

/// <summary>Waits until the owned connection next reaches Selected.</summary>
public Task WaitUntilSelectedAsync(
CancellationToken cancellationToken = default)
=> _connection.WaitUntilSelectedAsync(cancellationToken);

/// <summary>Gets the owned connection's single-consumer event stream.</summary>
public IAsyncEnumerable<HsmsConnectionEvent> GetEventsAsync(
CancellationToken cancellationToken = default)
=> _connection.GetEventsAsync(cancellationToken);

/// <summary>Sends a dynamic Primary through the owned connection.</summary>
public Task<HsmsDataMessage?> SendAsync(
SecsMessage primary,
CancellationToken cancellationToken = default)
=> _connection.SendAsync(primary, cancellationToken);

/// <summary>Replies once to an unhandled incoming Primary.</summary>
public Task ReplyAsync(
HsmsIncomingDataMessage incoming,
SecsMessage secondary,
CancellationToken cancellationToken = default)
=> _connection.ReplyAsync(
incoming,
secondary,
cancellationToken);

/// <summary>Registers one runtime Primary handler for this endpoint.</summary>
public HsmsPrimaryRouteRegistration RegisterPrimaryHandler(
byte stream,
byte function,
HsmsPrimaryHandler handler)
=> _router.Register(stream, function, handler);

/// <summary>Attempts to dispatch a connection event to a registered handler.</summary>
public ValueTask<bool> TryDispatchAsync(
HsmsConnectionEvent connectionEvent,
CancellationToken cancellationToken = default)
=> _router.TryDispatchAsync(connectionEvent, cancellationToken);

/// <summary>Sends Linktest Request and waits for its matching response.</summary>
public Task LinktestAsync(CancellationToken cancellationToken = default)
=> _connection.LinktestAsync(cancellationToken);

/// <summary>Sends Deselect Request and waits for its matching response.</summary>
public Task DeselectAsync(CancellationToken cancellationToken = default)
=> _connection.DeselectAsync(cancellationToken);

/// <summary>Sends Separate Request and closes the selected TCP session.</summary>
public Task SeparateAsync(CancellationToken cancellationToken = default)
=> _connection.SeparateAsync(cancellationToken);

/// <summary>Stops and disposes the owned connection.</summary>
public ValueTask DisposeAsync()
=> _connection.DisposeAsync();
}
11 changes: 11 additions & 0 deletions src/SecsFrame/Roles/SecsEndpointRole.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace SecsFrame;

/// <summary>Identifies the SECS application role of an endpoint.</summary>
public enum SecsEndpointRole
{
/// <summary>The endpoint represents a factory host.</summary>
Host,

/// <summary>The endpoint represents manufacturing equipment.</summary>
Equipment,
}
11 changes: 11 additions & 0 deletions src/SecsFrame/Roles/SecsEquipment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace SecsFrame;

/// <summary>An Equipment-role SECS endpoint independent of HSMS connection mode.</summary>
public sealed class SecsEquipment : SecsEndpoint
{
/// <summary>Creates an Equipment endpoint that owns a new HSMS connection.</summary>
public SecsEquipment(HsmsConnectionOptions options)
: base(SecsEndpointRole.Equipment, options)
{
}
}
11 changes: 11 additions & 0 deletions src/SecsFrame/Roles/SecsHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace SecsFrame;

/// <summary>A Host-role SECS endpoint independent of HSMS connection mode.</summary>
public sealed class SecsHost : SecsEndpoint
{
/// <summary>Creates a Host endpoint that owns a new HSMS connection.</summary>
public SecsHost(HsmsConnectionOptions options)
: base(SecsEndpointRole.Host, options)
{
}
}
Loading
Loading