Skip to content

Commit 37614eb

Browse files
committed
feat(modbus): 实现默认关闭的 TCP slave endpoint
1 parent 73806fb commit 37614eb

20 files changed

Lines changed: 1704 additions & 26 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050

5151
### Added
5252

53+
- **M34 #294 默认关闭的 Modbus TCP slave endpoint**:Server 在全局 `SonnetDBServer:Modbus:Enabled` 与 endpoint `ENABLED TRUE` 双重门禁下动态监听,汇总同一 endpoint 的表绑定并按固定 `ROW KEY` 响应 Coil、Discrete Input、Holding Register 与 Input Register 读取;严格校验 MBAP、Unit ID、bit/register 请求上限和完整地址覆盖,复用现有类型、字节序、字序、scale/offset 编码。连接先经过 IPv4/IPv6 精确地址或 CIDR allowlist,再受 `MAX_CONNECTIONS` 长连接配额限制;空 allowlist 仅允许回环。#295 前所有写 function 均返回 `Illegal Function`,不会 staging 或改表。新增 endpoint `disabled/starting/listening/degraded` 实时状态、稳定 listener 错误码、Prometheus/OpenTelemetry 低基数连接与读取指标,以及真实 TCP 四区读取、跨表绑定、白名单、连接配额、异常响应、状态和停机取消测试。
5354
- **M34 #293 Modbus 采集质量与失败策略**:source 表新增可选 `INT QUALITY` 列角色与稳定 `GOOD(0)` / `STALE(1)` / `BAD(2)` / `PARTIAL(4)` / `NO_VALUE(8)` 位;成功轮次写 `GOOD`,最终失败保留最后一次重试的部分快照,并按每张绑定的 `KEEP_LAST``NULL``SKIP``MARK_BAD` 在 LATEST 固定行或 HISTORY 追加行上执行确定性行为。`KEEP_LAST` 无历史值时不伪造行,`NULL/MARK_BAD` 要求可读映射列允许空值,`SKIP` 只更新 health。公开 `ModbusErrorCodes` 固定 timeout/transport/MBAP/PDU/device/decode/ingest 错误码;`SHOW/DESCRIBE MODBUS SOURCE` 以 extend-only 列追加最后尝试、最后错误时间和连续失败数,成功恢复归零连续失败但保留最近错误诊断。writer 先完成整轮解码准备再落库,轮询与远端写共享的 source 互斥不跨 reconnect 延时持有;parser/metadata、LATEST/HISTORY 四策略、部分快照、模拟 PLC 连续失败与恢复均有回归测试。
5455
- **M34 #292 受限 Modbus Source 控制写**:新增仅限 Server REST SQL 的 `WRITE MODBUS <table> SET <column> = <value> DRY RUN|PREVIEW|CONFIRM '<token>'` 与 Admin-only `SHOW MODBUS WRITE AUDIT`。只允许唯一 LATEST 行上声明 `ACCESS WRITE/READ_WRITE` 的完整 Coil/Holding Register 映射;dry-run 不联网,preview 签发五分钟一次性令牌并绑定凭据、数据库、source、表列、catalog revision、整行指纹和编码值,confirm 在 source 级互斥内重新核验后以 `0x05/0x06/0x10` 单请求写入,超过 123 个寄存器拒绝拆分。合法设备成功响应和 durable `remote_succeeded` 审计均完成后才更新本地 LATEST/ROWVERSION;超时、断连、设备异常、回包不匹配、行/catalog 冲突或审计不可用均不报告本地成功。普通 INSERT/IMPORT JSON 不能创建 Source 本地影子行,普通 UPDATE 不能改映射列;嵌入式与 Frame SQL 明确拒绝联网写。审计使用 source-generated NDJSON、逐条 durable flush、数据库隔离、值/令牌及慢查询文本脱敏;轮询与控制写共享的互斥锁不再跨 poll interval 延时持有。
5556
- **M34 #291 Modbus TCP master runtime**:Server 新增默认关闭的 `SonnetDBServer:Modbus:Enabled` 全局门禁;显式开启后为 catalog 中 `ENABLED TRUE` 的 source 建立独立后台 worker,动态发现数据库和绑定,按四类地址空间合并连续/重叠区间,并遵守 Modbus TCP 每次最多 2,000 bit 或 125 register 的读取上限。纯托管 TCP client 校验 MBAP transaction/protocol/unit/length、四个读 function 和设备异常响应,source 级 timeout/cancellation 覆盖连接与 socket I/O;单轮按 `RETRY` 指数退避并重建连接,彻底失败后按有界重连退避继续轮询。成功样本复用严格 `ModbusValueCodec` 解码并写入本地 LATEST/HISTORY 表,普通 `SELECT` 始终只读本地状态;新建 binding 记录为启用,升级前 Phase A binding 按存在即参与兼容,避免因旧版硬编码 `false` 且没有启停 DDL 而永久失效。`SHOW/DESCRIBE MODBUS SOURCE` 现返回 `starting/idle/healthy/degraded/disabled` 瞬时状态、最近成功时间和稳定错误码且不修改 catalog;新增 OTel poll/read/row/reconnect counter 与 poll histogram,并在兼容 `/metrics` 暴露对应累计指标。模拟 PLC 测试覆盖全局关闭零连接、四区读取、连续寄存器合批、重试/退避/重连、超时、调用方取消、落库和指标;#292 远端写与 #293 失败质量/错误策略均已独立交付。

ROADMAP.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
| 31 | 时序聚合类型语义 || selector / categorical aggregates 已落地。 |
3939
| 32 | Document MongoDB-like 易用性 || SDK、查询/更新、multikey/wildcard 索引、aggregation、mixed Bulk、迁移 CLI、Workbench、Quickstart 与结构化 gap report 已闭环。 |
4040
| 33 | 时序聚合执行与下推 || Geo 正确性、多聚合复用、残差流式化、count(*)、LIMIT/latest-N 下推已落地。 |
41-
| 34 | Modbus TCP 内建映射表 | 🚧 | #288~#293 已完成合同、DDL/catalog、地址/codec、默认关闭的 TCP master、受限远端写与失败质量/source health;slave endpoint、外部写治理、诊断和管理面仍待实现|
41+
| 34 | Modbus TCP 内建映射表 | 🚧 | #288~#294 已完成合同、DDL/catalog、地址/codec、默认关闭的 TCP master/slave 读取、受限远端写与失败质量/health;外部写治理和管理面仍待实现|
4242
| 35 | 语义内容与多模态检索 | 📋 | 尚未开始。 |
4343
| 36 | 八模型专用品类易用性对齐 | 📋 | 已完成参照分析;按真实缺口吸收高频工作流,不做协议或产品全集兼容。 |
4444
| 37 | 视图与物化视图 || #327 逻辑视图与 #328 显式全量刷新物化视图均已实现。 |
@@ -54,7 +54,7 @@
5454
2. 恢复 M20 Parity nightly 的有效报告,并补齐 M19/M25 目标硬件容量证据。
5555
3. 完成 M27 的真实 provider/Agent 接线、双网客户端 Copilot、工业 Demo 和 eval,消除历史虚标。
5656
4. 收口 M29 Studio 安装包/宿主生命周期实机验收。
57-
5. M34 的默认关闭 TCP master runtime、#292 受限远端写和 #293 失败质量/source health 已完成,下一步进入 #294 默认关闭的 TCP slave endpoint;M35 在过滤 ANN 与内容生命周期地基完成后再做媒体场景。
57+
5. M34 的默认关闭 TCP master/slave runtime、#292 受限远端写和 #293 失败质量/source health 已完成,下一步进入 #295 endpoint 外部写治理;M35 在过滤 ANN 与内容生命周期地基完成后再做媒体场景。
5858
6. M36 先完成八模型 golden journey 与 gap catalog;实现顺序为高频客户端工作流 -> 查询诊断 -> 高级治理,Document 复用已完成的 M32 结果,向量高级项复用 M35 地基。
5959
7. M39 先执行 #333 触发器 V2 证据门禁;未证明 V1 在真实 journey 上存在缺口前,不直接扩展 BEFORE、statement-level 或多模型触发器。
6060
8. M40 先完成 #341 的 workload/合同证据和 #342~#346 公共存储地基,再进入原生 Graph Preview;Phase 2 的关系映射规划和流式执行必须复用 M41 的公共计划/算子合同,不得另建一套关系优化器。正式产品定位在 M40 发布门禁通过前继续保持“八种数据模型,一套引擎”。
@@ -169,7 +169,7 @@ Phase A 已完成本地合同与持久化地基:DDL、Parser/AST、独立版
169169
| #291 | 默认关闭的 TCP master runtime、批量读取、轮询、取消、退避、超时、重连和指标。 ||
170170
| #292 | 受限 SQL 写寄存器、preview/dry-run、权限和审计;远端失败不得伪造本地成功。 ||
171171
| #293 | 质量位、错误码、source health、latest/history 与 KEEP_LAST/NULL/SKIP/MARK_BAD 策略。 ||
172-
| #294 | 默认关闭的 TCP slave endpoint、读请求、绑定/白名单/unit id/最大连接数。 | 📋 |
172+
| #294 | 默认关闭的 TCP slave endpoint、读请求、绑定/白名单/unit id/最大连接数。 | |
173173
| #295 | 外部写入的 REJECT/STAGED/UPDATE_TABLE 策略、待确认队列和审计;默认 STAGED。 | 📋 |
174174
| #296 | Web/Studio 管理面、模拟 PLC parity、文档,以及 IoTSharp Product/Collection Template/Gateway/EdgeNode 合同边界。 | 📋 |
175175

docs/modbus-tcp.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
---
22
layout: default
33
title: "Modbus TCP 内建映射表合同"
4-
description: "Milestone 34 的 SQL DDL、TCP master 轮询、地址归一化、类型编解码、写入审批与运行时安全边界。"
4+
description: "Milestone 34 的 SQL DDL、TCP master 轮询、TCP slave 读取、地址归一化、类型编解码、写入审批与运行时安全边界。"
55
---
66

77
# Modbus TCP 内建映射表合同
88

9-
本文记录 Milestone 34(#288#292)已经落地的 SQL、安全、catalog、地址校验、编解码、TCP master 轮询与受限远端写合同,也是后续 slave endpoint、管理面和 parity 测试的共同输入。
9+
本文记录 Milestone 34(#288#294)已经落地的 SQL、安全、catalog、地址校验、编解码、TCP master 轮询、受限远端写与 TCP slave 读取合同,也是后续外部写治理、管理面和 parity 测试的共同输入。
1010

11-
> 当前状态:Phase A 的 DDL、Parser/AST、独立版本化 catalog、`SHOW/DESCRIBE MODBUS`、地址冲突校验和类型编解码已经可用;#291 的默认关闭 TCP client/master 轮询、#292 的受限 Source 写以及 #293 的采集质量、失败策略和 source health 已接入 Server。TCP server/slave 监听、外部写 staging/审批和管理界面仍属于 #294#296
11+
> 当前状态:Phase A 的 DDL、Parser/AST、独立版本化 catalog、`SHOW/DESCRIBE MODBUS`、地址冲突校验和类型编解码已经可用;#291 的默认关闭 TCP client/master 轮询、#292 的受限 Source 写、#293 的采集质量与 source health,以及 #294 的默认关闭 TCP server/slave 读取均已接入 Server。外部写 staging/审批和管理界面仍属于 #295#296
1212
1313
## 角色与方向
1414

@@ -21,7 +21,7 @@ SonnetDB 只定义两个互不混淆的 Modbus TCP 角色:
2121

2222
`SOURCE``ENDPOINT` 的上下文已经决定角色。第一版 DDL 不再接受冗余的 `ROLE MASTER``ROLE SLAVE`,也不允许用同一个对象同时承担两个方向。
2323

24-
Modbus runtime 全局默认关闭。创建 source、endpoint 或映射表只持久化 catalog;只有同时设置 `SonnetDBServer:Modbus:Enabled=true` source 声明 `ENABLED TRUE` 时,#291 master worker 才会连接和轮询#292 `PREVIEW/CONFIRM` 才允许进入联网写流程。`DRY RUN` 只做本地校验和编码,不要求启用 runtime。DDL 不能绕过全局门禁;endpoint 监听仍等待 #294
24+
Modbus runtime 全局默认关闭。创建 source、endpoint 或映射表只持久化 catalog;只有同时设置 `SonnetDBServer:Modbus:Enabled=true` 且对应 source 或 endpoint 声明 `ENABLED TRUE` 时,master worker 才会连接和轮询、slave endpoint 才会监听。#292 `PREVIEW/CONFIRM` 同样要求全局门禁与 source 均启用;`DRY RUN` 只做本地校验和编码,不要求启用 runtime。DDL 不能绕过全局门禁。
2525

2626
普通 `SELECT``SHOW``DESCRIBE` 始终读取 SonnetDB 本地状态,不在查询线程中连接 PLC,也不等待现场设备响应。实时采集只由后台 master worker 完成;显式维护命令也不得改变普通查询的这一合同。
2727

@@ -106,6 +106,18 @@ source 的 `TIMEOUT` 覆盖连接、写请求和读响应;宿主停止、数
106106

107107
兼容 `/metrics` 暴露 `sonnetdb_modbus_master_polls_total``sonnetdb_modbus_master_poll_failures_total``sonnetdb_modbus_master_read_batches_total``sonnetdb_modbus_master_rows_written_total``sonnetdb_modbus_master_reconnects_total`。启用 OpenTelemetry 时,`SonnetDB.Server` meter 还提供对应 poll/read/row/reconnect counter 与 `sonnetdb.modbus.master.poll.duration` histogram;指标不使用数据库名、source 名或地址作为高基数标签。
108108

109+
### TCP slave runtime(#294
110+
111+
显式启用全局 Modbus runtime 后,Server 周期扫描已注册数据库,为每个 `ENABLED TRUE` 的 endpoint 建立独立 listener。新增数据库、endpoint 或关系表绑定无需重启;endpoint 被移除、数据库关闭或宿主停止会取消 accept、活动连接和正在等待的 socket 读取。客户端连接可连续处理多个顺序请求,`MAX_CONNECTIONS` 限制的是 endpoint 的活动 TCP 长连接数;超额连接立即关闭,连接退出后配额立即归还。
112+
113+
连接先按 `ALLOWLIST` 校验,再占用连接配额。规则支持精确 IPv4/IPv6 地址和 CIDR;空 allowlist 只允许回环客户端。该网络边界不等价于数据库身份或写权限。非回环 `BIND` 仍必须配置非空 allowlist。
114+
115+
读取支持四个标准 function:Coil `0x01`、Discrete Input `0x02`、Holding Register `0x03` 和 Input Register `0x04`。bit 区单次最多 2,000 点,register 区单次最多 125 个寄存器;MBAP protocol id、长度、PDU 形态、地址范围和数量均严格校验。endpoint 的所有表绑定会汇总成同一地址空间,并按各自固定 `ROW KEY` 直接读取当前关系行,再复用 catalog 中已解析的字节序、字序、类型、scale、offset 和 `.BIT(n)` 映射编码响应。请求跨越未映射间隙、只写映射或不存在的固定行时不会返回部分或伪造值。
116+
117+
异常响应保持确定性:不支持的 function(包括 #295 前的全部写请求)返回 `0x01 Illegal Function`;未映射、映射间隙或只写地址返回 `0x02 Illegal Data Address`;非法数量、越界或 PDU 形态返回 `0x03 Illegal Data Value`;固定行缺失、值为 `NULL`、编码失败或 endpoint 内跨表映射歧义返回 `0x04 Server Device Failure`;Unit ID 不匹配返回 `0x0B Gateway Target Device Failed to Respond`#294 不接收、不暂存也不应用外部写入,catalog 中的 `WRITE_POLICY``ON_EXTERNAL_WRITE` 继续作为 #295 合同保留。
118+
119+
`SHOW/DESCRIBE MODBUS ENDPOINT` 公开非持久化的 `disabled``starting``listening``degraded` health;绑定失败与监听循环失败分别报告稳定错误码 `endpoint_bind_error``endpoint_listener_error`,不会修改 catalog revision。兼容 `/metrics` 暴露 `sonnetdb_modbus_slave_connections_total``sonnetdb_modbus_slave_connection_rejections_total``sonnetdb_modbus_slave_active_connections``sonnetdb_modbus_slave_read_requests_total``sonnetdb_modbus_slave_read_failures_total``SonnetDB.Server` meter 同时提供 `sonnetdb.modbus.slave.connections``sonnetdb.modbus.slave.connection.rejections``sonnetdb.modbus.slave.connections.active``sonnetdb.modbus.slave.read.requests`;标签只包含有限的结果、拒绝原因和地址区类型。
120+
109121
### Endpoint DDL
110122

111123
```sql
@@ -124,9 +136,9 @@ WITH (
124136
);
125137
```
126138

127-
endpoint 级 `WRITE_POLICY` 默认是 `STAGED`Phase A 只持久化以下两种策略;#294/#295 runtime 落地后按对应合同执行
139+
endpoint 级 `WRITE_POLICY` 默认是 `STAGED`当前只持久化以下两种策略,#294 读取 runtime 对所有写 function 统一返回 `0x01 Illegal Function`#295 落地后再按对应写入合同执行
128140

129-
- `REJECT`后续 runtime 拒绝全部外部 Modbus 写请求,并记录拒绝审计。
141+
- `REJECT`#295 runtime 拒绝全部外部 Modbus 写请求,并记录拒绝审计。
130142
- `STAGED`#295 runtime 先校验并持久化不可变的待审批请求;协议接受只表示请求已可靠进入待审批队列,不表示业务表已经改变。
131143

132144
后续 runtime 不存在 endpoint 级 `UPDATE_TABLE` 入口策略,也不存在 `AUDIT FALSE`。审计是不可关闭的运行时不变量。
@@ -156,7 +168,7 @@ WITH (
156168

157169
`ROW KEY` 固定 endpoint 暴露的单行。第一版不从 Unit ID 或寄存器块推导关系表游标。
158170

159-
`ON_EXTERNAL_WRITE` 可以省略,默认是 `STAGE_ONLY``STAGE_ONLY` 只记录审批结果,不更新绑定表;`UPDATE_TABLE` 定义后续 #295 runtime 在审批通过后的应用动作:授权审批者确认待审批请求后,SonnetDB 才按同一份已校验映射更新固定行。两种动作都不能覆盖 endpoint 的 `REJECT`,也不能把 `STAGED` 变成直接写表;Phase A 只持久化并展示该配置,不会接收或审批外部写请求。
171+
`ON_EXTERNAL_WRITE` 可以省略,默认是 `STAGE_ONLY``STAGE_ONLY` 只记录审批结果,不更新绑定表;`UPDATE_TABLE` 定义后续 #295 runtime 在审批通过后的应用动作:授权审批者确认待审批请求后,SonnetDB 才按同一份已校验映射更新固定行。两种动作都不能覆盖 endpoint 的 `REJECT`,也不能把 `STAGED` 变成直接写表;#294 只读取并展示该配置,不会接收或审批外部写请求。
160172

161173
## Catalog 持久化与恢复边界
162174

@@ -457,7 +469,8 @@ WITH (
457469
WORD_ORDER BIG_ENDIAN,
458470
ALLOWLIST ('192.168.10.0/24'),
459471
MAX_CONNECTIONS 32,
460-
WRITE_POLICY STAGED
472+
WRITE_POLICY STAGED,
473+
ENABLED TRUE
461474
);
462475

463476
CREATE TABLE line_shadow (
@@ -488,7 +501,7 @@ WITH (
488501
);
489502
```
490503

491-
上述示例当前只保存 #295 的未来运行时合同,Phase A 不会监听端口或接收外部 client 请求#295 落地后,外部 client 对 Coil 1 或 Holding Register 40001 的写请求必须先进入 staging,只有授权审批完成后才更新 `line_shadow` 的主键 `1` 行;Input Register 30001 始终只读,DDL 不得提供关闭审计或绕过 staging 的选项。
504+
当全局 `SonnetDBServer:Modbus:Enabled=true` 且主键 `1` 的固定行存在时,上述 endpoint 会监听 `192.168.10.20:1502`,允许白名单网段读取 Coil 1、Holding Register 40001 和 Input Register 30001#295 落地前,外部 client 对 Coil 1 或 Holding Register 40001 的写请求统一收到 `0x01 Illegal Function`,不会进入 staging 或改变表值;后续写入必须先进入 staging,只有授权审批完成后才可按策略更新固定行。Input Register 30001 始终只读,DDL 不得提供关闭审计或绕过 staging 的选项。
492505

493506
## SHOW / DESCRIBE 稳定合同
494507

@@ -522,7 +535,7 @@ last_error_code
522535

523536
Endpoint 结果同样追加 `configured_enabled``configuration_source``catalog_revision``DESCRIBE MODBUS SOURCE` / `ENDPOINT` 使用与对应 `SHOW` 相同的列合同并只返回指定对象的一行。
524537

525-
`DESCRIBE MODBUS SOURCE``DESCRIBE MODBUS ENDPOINT` 返回对应对象的全部有效配置及配置来源,不返回凭据或其他敏感值。Source 已反映 #291 全局门禁与 worker 状态;Endpoint #294 前仍固定为 `runtime_enabled=FALSE``health=disabled`。DDL 存在不等于 runtime 已启用。
538+
`DESCRIBE MODBUS SOURCE``DESCRIBE MODBUS ENDPOINT` 返回对应对象的全部有效配置及配置来源,不返回凭据或其他敏感值。Source 反映 #291 全局门禁与 worker 状态;Endpoint 反映 #294 全局门禁与 listener 状态,health 使用 `disabled``starting``listening``degraded`。DDL 存在不等于 runtime 已启用,元数据查询本身也不会建立连接或探测网络
526539

527540
`DESCRIBE MODBUS TABLE` 每个映射列返回一行,至少稳定包含:
528541

0 commit comments

Comments
 (0)