123
This commit is contained in:
+47
-19
@@ -80,7 +80,9 @@ CREATE STABLE IF NOT EXISTS collection_data (
|
||||
);
|
||||
```
|
||||
|
||||
`value` 可为 NULL。读取失败或断线时使用 `quality=bad`、`value=NULL`;超出有效范围时保留实际值并使用 `quality=bad`。
|
||||
`value` 可为 NULL。读取失败或断线时使用 `quality=bad`、`value=NULL`;采集点已移除有效范围配置,协议读取和解析成功即写入 `quality=good`。
|
||||
|
||||
TDengine 写入和查询时间字面量统一使用带上海时区偏移的 RFC3339 样式,例如 `2026-07-13T10:05:41.002+08:00`。读回时间按真实 instant 转换为 `Asia/Shanghai` 输出,避免无时区字符串在 REST 链路中产生 8 小时偏移。
|
||||
|
||||
### 2.2 预留数据来源:非采集点位(内部数据)
|
||||
|
||||
@@ -234,18 +236,15 @@ ALTER DATABASE ${TDENGINE_DATABASE} KEEP 730;
|
||||
| POST | /api/v1/history/query | 查询历史数据(曲线模式) |
|
||||
| POST | /api/v1/history/query-table | 查询历史数据(表格模式) |
|
||||
| POST | /api/v1/history/export | 导出历史数据为CSV |
|
||||
| POST | /api/v1/history/archive/cleanup | 清理已逻辑删除点位的归档历史子表 |
|
||||
|
||||
### 5.2 接口详细定义
|
||||
|
||||
#### 5.2.1 GET /api/v1/history/tree — 获取历史点位树
|
||||
|
||||
Query parameters:
|
||||
当前接口不需要查询参数,默认包含活跃点位和仍有历史数据的归档点位。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| include_archived | bool | 否 | true | 是否包含仍有历史数据的归档点位 |
|
||||
|
||||
历史模块调用 `ListHistoryPointMetadata(IncludeArchived=true)`,批量检查 TDengine 历史存在性并构建树;不得直接查询数据管理 PostgreSQL 表。
|
||||
历史模块调用 `ListHistoryPointMetadata(IncludeArchived=true)` 等价能力,批量检查 TDengine 历史存在性并构建树;不得直接查询数据管理模块以外的私有 Repository。当前实现通过进程内 PostgreSQL Store 读取包括逻辑删除记录在内的元数据。
|
||||
|
||||
Response (200):
|
||||
|
||||
@@ -272,6 +271,7 @@ Response (200):
|
||||
"group_name": "曝气池",
|
||||
"lifecycle_status": "active",
|
||||
"has_history_data": true,
|
||||
"can_cleanup": false,
|
||||
"latest_value": {
|
||||
"value": 2.35,
|
||||
"quality": "good",
|
||||
@@ -291,6 +291,7 @@ Response (200):
|
||||
"group_name": "曝气池",
|
||||
"lifecycle_status": "archived",
|
||||
"has_history_data": true,
|
||||
"can_cleanup": true,
|
||||
"latest_value": null
|
||||
}
|
||||
]
|
||||
@@ -331,8 +332,9 @@ Request body:
|
||||
1. 调用 `GetHistoryPointMetadata(..., includeArchived=true)` 校验并批量取得当前名称、单位和类型;
|
||||
2. 由 UUID 派生并白名单校验 `p_<uuid32>` 子表名;
|
||||
3. 查询 `ts, value, quality, quality_reason`;
|
||||
4. 原始点数超过 `max_samples` 时执行自适应 min-max 降采样,保留首尾、极值、质量变化和 gap 边界;
|
||||
5. 返回每条序列的采样统计。
|
||||
4. 按点位 `history_interval` 判断断档,相邻样本间隔超过 `3 × history_interval` 时插入 `value=null, quality=none` 的断点,避免曲线跨长时间停采区间连线;
|
||||
5. 原始点数超过 `max_samples` 时执行自适应 min-max 降采样,保留首尾、极值、质量变化和 gap 边界;
|
||||
6. 返回每条序列的采样统计。
|
||||
|
||||
Response (200):
|
||||
|
||||
@@ -349,12 +351,13 @@ Response (200):
|
||||
"unit": "mg/L",
|
||||
"sampled": false,
|
||||
"raw_count": 5,
|
||||
"sample_count": 5,
|
||||
"sample_count": 6,
|
||||
"data": [
|
||||
{"ts":"2026-07-09T00:00:01+08:00","value":2.35,"quality":"good","quality_reason":null},
|
||||
{"ts":"2026-07-09T00:00:02+08:00","value":2.36,"quality":"good","quality_reason":null},
|
||||
{"ts":"2026-07-09T00:00:05+08:00","value":2.38,"quality":"bad","quality_reason":"out_of_range"},
|
||||
{"ts":"2026-07-09T00:00:05+08:00","value":2.38,"quality":"good","quality_reason":null},
|
||||
{"ts":"2026-07-09T00:00:08+08:00","value":null,"quality":"bad","quality_reason":"timeout"},
|
||||
{"ts":"2026-07-09T00:00:09+08:00","value":null,"quality":"none","quality_reason":null},
|
||||
{"ts":"2026-07-09T00:00:10+08:00","value":2.40,"quality":"good","quality_reason":null}
|
||||
]
|
||||
}
|
||||
@@ -434,13 +437,37 @@ Response (200):
|
||||
报表 CSV 允许本地化动态标题:
|
||||
|
||||
```csv
|
||||
时间,曝气池DO_01[mg/L],曝气池DO_01[mg/L]_质量,曝气池温度[℃],曝气池温度[℃]_质量
|
||||
2026-07-09 00:00:00,2.35,good,25.1,good
|
||||
2026-07-09 00:10:00,2.40,good,25.0,good
|
||||
2026-07-09 00:20:00,—,—,25.3,good
|
||||
时间,曝气池DO_01[mg/L],曝气池温度[℃]
|
||||
2026-07-09 00:00:00,2.35,25.1
|
||||
2026-07-09 00:10:00,2.40,25.0
|
||||
2026-07-09 00:20:00,,25.3
|
||||
```
|
||||
|
||||
重复点位名称追加 UUID 前8位。bad 时数值列显示 `—`、质量列显示 `bad`;none 时两列均显示 `—`。
|
||||
重复点位名称追加 UUID 前8位。CSV 每个测点只导出一列实际值,不额外导出质量列;`bad`、`none` 或空值时该值列留空。
|
||||
|
||||
#### 5.2.5 POST /api/v1/history/archive/cleanup — 清理已删除点位归档数据
|
||||
|
||||
该接口仅清理采集点已逻辑删除,或所属设备已逻辑删除的历史子表。禁用但仍可恢复的点位、`store_history=false` 但配置仍存在的点位不纳入清理。
|
||||
|
||||
Request body:无。
|
||||
|
||||
Response (200):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"deleted_points": 13
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
执行规则:
|
||||
|
||||
- 由 PostgreSQL 枚举已逻辑删除点位 UUID;
|
||||
- TDengine 子表名仍只能由 UUID 派生为 `p_<uuid32>`;
|
||||
- 删除使用 `DROP TABLE IF EXISTS`,清理不可逆,前端必须二次确认。
|
||||
|
||||
### 5.3 错误码
|
||||
|
||||
@@ -715,10 +742,11 @@ ECharts 使用封装在 `src/components/charts/` 的纯函数生成映射和 opt
|
||||
| H009 | 模式切换 | 保持点位勾选状态和时间范围不变 |
|
||||
| H010 | CSV导出 | 报表型动态标题,安全服务端文件名,最多50,000行 |
|
||||
| H011 | 查询间隔建议 | 小于最大history_interval时仅提示,不禁止;查询最大31天 |
|
||||
| H012 | 曲线采样 | 每序列默认最多2000点,超限执行自适应min-max降采样 |
|
||||
| H012 | 曲线采样 | 每序列默认最多2000点,先按3倍history_interval插入断档点,再超限执行自适应min-max降采样 |
|
||||
| H013 | 缺失值协议 | 固定返回value=null、quality=none、matched_ts=null对象 |
|
||||
| H014 | 归档清理 | 仅清理已逻辑删除点位或已删除设备下点位的 TDengine 子表,禁用点位不清理 |
|
||||
|
||||
---
|
||||
|
||||
> 本文档版本:v1.1
|
||||
> 最后更新:2026-07-11
|
||||
> 本文档版本:v1.2
|
||||
> 最后更新:2026-07-13
|
||||
|
||||
+88
-78
@@ -87,8 +87,8 @@ CREATE UNIQUE INDEX idx_devices_name ON devices(name) WHERE deleted = FALSE;
|
||||
|
||||
| 协议类型标识 | 说明 | 当前状态 |
|
||||
|-------------|------|---------|
|
||||
| `S7` | 西门子S7协议 | 已支持 |
|
||||
| `MODBUS_TCP` | Modbus TCP协议 | 已支持 |
|
||||
| `S7` | 西门子S7协议 | 采集、写入和现场联调已支持 |
|
||||
| `MODBUS_TCP` | Modbus TCP协议 | 已支持运行时连接、采集和写入 |
|
||||
| (后续扩展) | 新增协议通过插件机制添加 | 待扩展 |
|
||||
|
||||
#### 2.1.3 各协议 protocol_config 定义
|
||||
@@ -143,6 +143,7 @@ CREATE UNIQUE INDEX idx_devices_name ON devices(name) WHERE deleted = FALSE;
|
||||
|
||||
说明:
|
||||
- `connection_status` 是运行时状态,由采集引擎维护在内存中,**不存储到数据库**
|
||||
- `last_online_at`、`last_offline_at` 也是运行时内存字段,仅在状态实际变化时更新,用于设备列表展示最近在线/离线时间
|
||||
- 设备逻辑删除(deleted=true)时,在接口响应中表现为 `disabled`
|
||||
- 采集引擎未运行时,所有 enabled=true 的设备均返回 `disconnected`
|
||||
|
||||
@@ -237,9 +238,9 @@ PUT 使用完整替换语义,以下字段全部必填:
|
||||
```
|
||||
|
||||
- 校验规则与新增一致,额外要求 `enabled` 为布尔值。
|
||||
- 修改连接参数或协议配置后,Service 在事务提交后发布配置变更事件;采集引擎断开旧连接并使用新参数重连。
|
||||
- 修改连接参数或协议配置后,Service 保存成功即调用连接管理器使该设备旧连接失效;下一次采集或写入会按新参数重新建立连接。
|
||||
- `enabled=false` 时运行时状态立即进入 `disabled`;`enabled=true` 后先返回 `disconnected`,连接成功后变为 `connected`。
|
||||
- 配置变更正常情况下 1 秒内生效,5 秒全量校对保证最终一致。
|
||||
- 采集调度器每秒重新加载活动采集点,点位配置变更通常在下一个调度周期生效。
|
||||
|
||||
**DELETE /api/v1/devices/{id}** — 逻辑删除
|
||||
|
||||
@@ -317,6 +318,8 @@ Response (200):
|
||||
"reconnect_interval": 10,
|
||||
"protocol_config": { "rack": 0, "slot": 1 },
|
||||
"connection_status": "connected",
|
||||
"last_online_at": "2026-07-13T09:30:00+08:00",
|
||||
"last_offline_at": null,
|
||||
"created_at": "...",
|
||||
"updated_at": "..."
|
||||
}
|
||||
@@ -386,8 +389,6 @@ CREATE TABLE collection_points (
|
||||
address VARCHAR(256) NOT NULL,
|
||||
data_type VARCHAR(16) NOT NULL, -- 'BOOL', 'INT', 'REAL'
|
||||
unit VARCHAR(32),
|
||||
valid_min DOUBLE PRECISION,
|
||||
valid_max DOUBLE PRECISION,
|
||||
|
||||
collect_interval INTEGER NOT NULL DEFAULT 1 CHECK (collect_interval >= 1),
|
||||
store_history BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
@@ -397,9 +398,7 @@ CREATE TABLE collection_points (
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
created_by VARCHAR(64),
|
||||
updated_by VARCHAR(64),
|
||||
|
||||
CHECK (valid_min IS NULL OR valid_max IS NULL OR valid_min <= valid_max)
|
||||
updated_by VARCHAR(64)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_collection_points_name
|
||||
@@ -410,9 +409,23 @@ CREATE INDEX idx_collection_points_group_name
|
||||
ON collection_points(group_name) WHERE deleted = FALSE;
|
||||
```
|
||||
|
||||
`history_started_at` 在点位首次准备写入 TDengine 前设置。一旦非空,`device_id` 和 `data_type` 不可修改;需要改变设备归属或数据类型时必须新建点位,防止同一历史子表混入不同语义的数据。
|
||||
有效最小值/最大值已从采集点配置中移除,采集质量不再执行业务有效范围判断;协议读写成功即为 `good`,失败才标记 `bad`。
|
||||
|
||||
#### 3.1.1.1 历史模块只读元数据接口
|
||||
`history_started_at` 在点位首次准备写入 TDengine 前设置。当前实现允许编辑采集点的 `device_id` 和 `data_type`;历史子表中的 `device_name`、`point_name`、`data_type` 仍是写入时快照,历史页面展示以 PostgreSQL 当前元数据为准。若业务需要严格保留历史语义,后续应通过新建点位或迁移流程处理。
|
||||
|
||||
#### 3.1.1.1 数据库表:collection_groups
|
||||
|
||||
```sql
|
||||
CREATE TABLE collection_groups (
|
||||
name VARCHAR(64) PRIMARY KEY,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
`collection_groups` 是采集点和写入点共享的扁平分组字典。启动迁移会把历史 `group_name` 同步进该表,并确保 `default` 分组存在。
|
||||
|
||||
#### 3.1.1.2 历史模块只读元数据接口
|
||||
|
||||
设备与采集点配置由数据管理模块拥有。历史模块必须调用公开的进程内只读领域接口,不得直接查询 `devices`、`collection_points` 表或调用数据管理 Repository。
|
||||
|
||||
@@ -456,11 +469,17 @@ GetHistoryPointMetadata(ctx context.Context, pointIDs []uuid.UUID, includeArchiv
|
||||
| BOOL | `DB2.10.0` | 读DB2的第10字节的第0位 |
|
||||
| INT | `DB2.10` | 读DB2的第10字节开始的2字节(16位整数) |
|
||||
| REAL | `DB2.10` | 读DB2的第10字节开始的4字节(32位浮点数) |
|
||||
| REAL | `DB2.1186.0` | 兼容现场 DB 数值地址末尾 `.0` 的工程记法,实际按字节偏移 1186 连续读取 4 字节 |
|
||||
| REAL | `MD540` | M 区双字地址,按字节偏移 540 连续读写 4 字节 |
|
||||
| INT | `MW540` | M 区字地址,按字节偏移 540 连续读写 2 字节 |
|
||||
|
||||
解析规则:
|
||||
- 地址格式:`{type_prefix}{numbers}`,其中 numbers 以 `.` 分隔
|
||||
- 对于 BOOL 类型:必须有3段(或2段,M/I/Q类型),如 `DB2.10.0` 或 `M4.0`
|
||||
- 对于 INT/REAL 类型:BOOL类型的地址去掉最后的 .bit 部分,如 `DB2.10`、`M4`
|
||||
- 对于 INT/REAL 类型:BOOL类型的地址通常去掉最后的 .bit 部分,如 `DB2.10`、`M4`
|
||||
- 为兼容现场工程记法,DB 数值地址允许末尾 `.0`,驱动解析时忽略该尾段;其他非 BOOL 地址不得包含 bit
|
||||
- M/Q 区数值地址允许 `MW`/`QW` 表示 INT,`MD`/`QD` 表示 REAL;I 区只读,写入点不得使用 I 区地址
|
||||
- S7 REAL 读取按 IEEE-754 大端解析,并统一四舍五入到小数点后最多 3 位
|
||||
|
||||
**Modbus TCP协议地址格式**:
|
||||
|
||||
@@ -496,8 +515,12 @@ GetHistoryPointMetadata(ctx context.Context, pointIDs []uuid.UUID, includeArchiv
|
||||
|
||||
- 默认分组名:`default`
|
||||
- 分组名支持任意字符串(建议不超过64字符)
|
||||
- 一个采集点只能属于一个分组
|
||||
- 分组的增删改通过修改采集点的 group_name 字段实现
|
||||
- 一个采集点或写入点只能属于一个分组
|
||||
- 分组名称保存在 `collection_groups` 表;采集点和写入点通过各自的 `group_name` 字段引用
|
||||
- 新建分组直接插入 `collection_groups`
|
||||
- 改名分组时,同步更新该分组下未逻辑删除的采集点和写入点
|
||||
- 删除非 `default` 分组时,先将该分组下未逻辑删除的采集点和写入点迁移到 `default`,再删除分组;不逻辑删除任何点位
|
||||
- `default` 分组不可删除
|
||||
- 查询时支持按分组名筛选
|
||||
|
||||
#### 3.1.4 实时数据
|
||||
@@ -548,8 +571,6 @@ Request body:
|
||||
"address": "DB2.10",
|
||||
"data_type": "REAL",
|
||||
"unit": "mg/L",
|
||||
"valid_min": 0,
|
||||
"valid_max": 20,
|
||||
"collect_interval": 1,
|
||||
"store_history": true,
|
||||
"history_interval": 1
|
||||
@@ -563,12 +584,11 @@ Request body:
|
||||
- address:必填,1~256字符,根据设备协议类型校验格式
|
||||
- data_type:必填,枚举值:`BOOL`、`INT`、`REAL`
|
||||
- unit:可选,0~32字符;用于历史表格和图表展示
|
||||
- valid_min / valid_max:可选;同时提供时必须 valid_min <= valid_max,超范围值记为 bad 但保留原始值
|
||||
- collect_interval:必填,最小值1(秒)
|
||||
- store_history:可选,默认true
|
||||
- history_interval:当 store_history=true 时必填,必须为 1~1440 的整数(分钟);store_history=false 时可省略并使用默认值 1,若提供仍须符合该范围
|
||||
- address 和 data_type 的兼容性校验:
|
||||
- S7协议:BOOL类型必须包含bit位(如 `DB2.10.0`),INT/REAL类型不能包含bit位
|
||||
- S7协议:BOOL类型必须包含bit位(如 `DB2.10.0`);INT/REAL 类型通常不包含 bit,仅 DB 数值地址兼容末尾 `.0`
|
||||
- Modbus协议:00001/10001 地址只能使用BOOL类型,30001/40001 地址只能使用INT/REAL类型
|
||||
|
||||
**PUT /api/v1/collection-points/{id}** — 修改采集点
|
||||
@@ -584,17 +604,14 @@ PUT 使用完整替换语义,请求字段与新增一致,并额外要求 `en
|
||||
"address": "DB2.10",
|
||||
"data_type": "REAL",
|
||||
"unit": "mg/L",
|
||||
"valid_min": 0,
|
||||
"valid_max": 20,
|
||||
"collect_interval": 1,
|
||||
"store_history": true,
|
||||
"history_interval": 5
|
||||
}
|
||||
```
|
||||
|
||||
- `history_started_at` 非空时,修改 `device_id` 或 `data_type` 返回 HTTP 409、`code=41009`。
|
||||
- 名称、分组、地址、单位、有效范围、采集周期和历史间隔可以修改。
|
||||
- Service 在事务提交后发布配置变更事件,采集引擎停止旧任务并按新配置启动;5 秒内保证最终生效。
|
||||
- 当前实现允许修改 `device_id`、`data_type`、名称、分组、地址、单位、采集周期和历史间隔;修改后历史已有数据仍保留在原子表中,展示名称和单位以当前 PostgreSQL 元数据为准。
|
||||
- 当前采集调度器每秒重新加载活动采集点;点位新增、修改、删除和启停通常在下一个调度周期生效。
|
||||
|
||||
**DELETE /api/v1/collection-points/{id}** — 逻辑删除
|
||||
|
||||
@@ -636,8 +653,6 @@ Response (200):
|
||||
"address": "DB2.10",
|
||||
"data_type": "REAL",
|
||||
"unit": "mg/L",
|
||||
"valid_min": 0,
|
||||
"valid_max": 20,
|
||||
"collect_interval": 1,
|
||||
"store_history": true,
|
||||
"history_interval": 1,
|
||||
@@ -668,15 +683,21 @@ Response (200):
|
||||
"groups": [
|
||||
{
|
||||
"name": "default",
|
||||
"count": 10
|
||||
"count": 10,
|
||||
"collection_count": 10,
|
||||
"write_count": 2
|
||||
},
|
||||
{
|
||||
"name": "曝气池",
|
||||
"count": 25
|
||||
"count": 25,
|
||||
"collection_count": 25,
|
||||
"write_count": 4
|
||||
},
|
||||
{
|
||||
"name": "加药间",
|
||||
"count": 15
|
||||
"count": 15,
|
||||
"collection_count": 15,
|
||||
"write_count": 6
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -688,10 +709,10 @@ Response (200):
|
||||
CSV格式定义:
|
||||
|
||||
```csv
|
||||
name,group_name,device_name,address,data_type,unit,valid_min,valid_max,collect_interval,store_history,history_interval,enabled
|
||||
曝气池DO_01,曝气池,一期曝气柜PLC,DB2.10,REAL,mg/L,0,20,1,TRUE,1,TRUE
|
||||
进水pH,进水仪表,进水仪表柜PLC,DB1.0,REAL,pH,0,14,5,TRUE,5,TRUE
|
||||
风机运行状态,曝气池,一期曝气柜PLC,M4.0,BOOL,,,,1,TRUE,1,TRUE
|
||||
name,group_name,device_name,address,data_type,unit,collect_interval,store_history,history_interval,enabled
|
||||
曝气池DO_01,曝气池,一期曝气柜PLC,DB2.10,REAL,mg/L,1,TRUE,1,TRUE
|
||||
进水pH,进水仪表,进水仪表柜PLC,DB1.0,REAL,pH,5,TRUE,5,TRUE
|
||||
风机运行状态,曝气池,一期曝气柜PLC,M4.0,BOOL,,1,TRUE,1,TRUE
|
||||
```
|
||||
|
||||
注意:
|
||||
@@ -703,7 +724,6 @@ name,group_name,device_name,address,data_type,unit,valid_min,valid_max,collect_i
|
||||
|
||||
- 导入逻辑:以 name 为唯一标识,存在则更新,不存在则新增。
|
||||
- 先校验 `device_name`,再按对应协议校验地址和数据类型。
|
||||
- 若更新已有点位且 `history_started_at` 非空,CSV 不得改变 `device_name` 对应的设备或 `data_type`。
|
||||
- 响应沿用设备导入的 `{total,created,updated,failed,errors}` 结构。
|
||||
|
||||
**GET /api/v1/collection-points/{id}** 返回列表项的完整对象,并包含 `history_started_at`;无实时缓存时 `latest_value=null`。
|
||||
@@ -724,15 +744,12 @@ CREATE TABLE write_points (
|
||||
name VARCHAR(128) NOT NULL,
|
||||
group_name VARCHAR(64) NOT NULL DEFAULT 'default',
|
||||
device_id UUID NOT NULL REFERENCES devices(id),
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
write_enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
address VARCHAR(256) NOT NULL,
|
||||
data_type VARCHAR(16) NOT NULL, -- 'BOOL', 'INT', 'REAL'
|
||||
unit VARCHAR(32),
|
||||
readback_tolerance DOUBLE PRECISION NOT NULL DEFAULT 0.0001
|
||||
CHECK (readback_tolerance >= 0),
|
||||
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
@@ -763,7 +780,7 @@ CREATE INDEX idx_write_points_group_name
|
||||
|
||||
- 地址格式与采集点一致,但只允许可写区域:S7 的 DB/M/Q,Modbus 的 Coil/保持寄存器。
|
||||
- BOOL、INT 回读必须严格相等。
|
||||
- REAL 使用 `abs(readback-target) <= readback_tolerance`;默认容差 0.0001。
|
||||
- REAL 使用固定协议精度 `abs(readback-target) <= 0.0001`,该容差不作为写入点配置或 API 字段暴露。
|
||||
- 同一设备的采集、写入和回读通过设备连接命令队列串行执行,避免协议客户端并发冲突。
|
||||
|
||||
### 4.2 RESTful API
|
||||
@@ -791,28 +808,25 @@ CREATE INDEX idx_write_points_group_name
|
||||
"name": "加药泵频率",
|
||||
"group_name": "加药间",
|
||||
"device_id": "uuid-of-device",
|
||||
"enabled": true,
|
||||
"write_enabled": false,
|
||||
"address": "DB2.10",
|
||||
"data_type": "REAL",
|
||||
"unit": "Hz",
|
||||
"readback_tolerance": 0.01
|
||||
"unit": "Hz"
|
||||
}
|
||||
```
|
||||
|
||||
- POST 中 `enabled` 可省略,默认 true;PUT 中全部字段必填。
|
||||
- POST/PUT 均只使用 `write_enabled` 作为写入权限开关,不再提供独立 `enabled` 字段。
|
||||
- 名称全局唯一;设备必须存在且未删除;地址必须可写并与数据类型兼容。
|
||||
- `readback_tolerance` 仅对 REAL 生效,范围 0~1,000,000。
|
||||
- GET 列表支持 `page`、`page_size`、`keyword`、`device_id`、`group_name`、`data_type`、`enabled`、`write_enabled`。
|
||||
- GET 列表支持 `page`、`page_size`、`keyword`、`device_id`、`group_name`、`data_type`、`write_enabled`。
|
||||
- DELETE 设置 `deleted=true` 并立即拒绝新的写入请求。
|
||||
|
||||
成功响应中的写入点对象包含上述字段及 `id`、`device_name`、`protocol_type`、`created_at`、`updated_at`。
|
||||
成功响应中的写入点对象包含上述字段及 `id`、`device_name`、`protocol_type`、`readback_value`、`created_at`、`updated_at`;`readback_value` 来自该写入点最近一条写入日志,无记录时为 `null`。
|
||||
|
||||
配置 CSV:
|
||||
|
||||
```csv
|
||||
name,group_name,device_name,address,data_type,unit,enabled,write_enabled,readback_tolerance
|
||||
加药泵频率,加药间,一期加药间PLC,DB2.10,REAL,Hz,TRUE,FALSE,0.01
|
||||
name,group_name,device_name,address,data_type,unit,write_enabled
|
||||
加药泵频率,加药间,一期加药间PLC,DB2.10,REAL,Hz,FALSE
|
||||
```
|
||||
|
||||
导入以 `name` 为唯一标识,存在则更新;失败结果返回行号、字段和稳定错误原因。写入点 CSV 成功时直接返回文件流,不使用 JSON envelope;失败时返回统一 JSON 错误。
|
||||
@@ -836,7 +850,7 @@ name,group_name,device_name,address,data_type,unit,enabled,write_enabled,readbac
|
||||
|
||||
执行流程:
|
||||
|
||||
1. 校验点位、设备、`enabled` 和 `write_enabled`;
|
||||
1. 校验点位、设备和 `write_enabled`;
|
||||
2. 通过设备级连接队列写入;
|
||||
3. 回读并按数据类型验证;不一致时重试一次;
|
||||
4. 无论成功、失败或超时都写入 `write_logs`;
|
||||
@@ -962,12 +976,12 @@ CREATE INDEX idx_write_logs_created_at ON write_logs(created_at DESC);
|
||||
|
||||
```
|
||||
1. Web 服务完成依赖初始化后启动采集引擎
|
||||
2. 从数据库加载所有 enabled=true AND deleted=false 的设备
|
||||
3. 从数据库加载所有 enabled=true AND deleted=false 的采集点,按 device_id 分组
|
||||
4. 对每个设备,尝试建立连接:
|
||||
a. 连接成功 → 标记为"已连接",启动该设备下所有采集点的采集任务
|
||||
b. 连接失败 → 标记为"断开",启动重连计时器
|
||||
5. 调度器按 collect_interval 生成点位任务并投递到有界 worker pool;同一设备的协议操作串行执行
|
||||
2. 启动固定数量 worker,并启动每秒一次的调度循环
|
||||
3. 调度循环每秒从 PostgreSQL 加载所有未删除的采集点及其设备元数据
|
||||
4. 跳过 `enabled=false` 的采集点;按每个点位的 `collect_interval` 判断是否到期
|
||||
5. 到期点位投递到有界 worker pool;队列满时记录告警并跳过该次调度
|
||||
6. worker 执行点位任务时通过连接管理器获取设备连接;连接不存在时按设备配置懒创建
|
||||
7. 同一设备连接内部串行执行协议读写
|
||||
```
|
||||
|
||||
### 5.3 采集任务执行流程
|
||||
@@ -977,21 +991,18 @@ CREATE INDEX idx_write_logs_created_at ON write_logs(created_at DESC);
|
||||
1. 调度器生成任务并投递到有界 worker pool;队列满时记录告警,不无限创建 goroutine。
|
||||
2. 获取该设备的独立 ProtocolConnection,并进入设备级串行命令队列。
|
||||
3. 读取和解析:
|
||||
- 成功且值在有效范围内(或未配置范围)→ value=实际值, quality=good;
|
||||
- 成功但超出 valid_min/valid_max → value=实际值, quality=bad, quality_reason=out_of_range;
|
||||
- 成功 → value=实际值, quality=good;
|
||||
- 超时/断线/读取失败/解析失败 → value=null, quality=bad,并填写稳定 quality_reason。
|
||||
4. 更新内存 latest_values:{value, quality, quality_reason, ts}。
|
||||
5. store_history=true 且到达 history_interval 时写入 TDengine。
|
||||
6. 首次准备写入历史数据前设置 history_started_at;设置成功后即禁止修改 device_id/data_type。
|
||||
7. 如配置 MQTT,由独立发布队列异步发送,MQTT 失败不得阻塞采集任务。
|
||||
6. 首次准备写入历史数据前设置 history_started_at。
|
||||
```
|
||||
|
||||
### 5.4 质量戳判定规则
|
||||
|
||||
| 条件 | value | quality | quality_reason | TDengine quality |
|
||||
|---|---:|---|---|---:|
|
||||
| 读取、解析成功且在有效范围内 | 实际值 | `good` | null | 0 |
|
||||
| 读取成功但超出有效范围 | 实际值 | `bad` | `out_of_range` | 1 |
|
||||
| 读取、解析成功 | 实际值 | `good` | null | 0 |
|
||||
| 协议超时 | null | `bad` | `timeout` | 1 |
|
||||
| 设备断开 | null | `bad` | `disconnected` | 1 |
|
||||
| 读取异常 | null | `bad` | `read_error` | 1 |
|
||||
@@ -1003,15 +1014,12 @@ API 始终返回字符串质量戳。`none` 只表示查询没有匹配记录;
|
||||
### 5.5 断线重连机制
|
||||
|
||||
```
|
||||
1. 采集引擎检测到设备连接断开(读取失败或连接异常断开)
|
||||
2. 立即标记该设备状态为"断开"
|
||||
3. 该设备下所有采集点标记为 bad
|
||||
4. 启动重连计时器(间隔 = 设备配置的 reconnect_interval)
|
||||
5. 每次重连尝试:
|
||||
a. 连接成功 → 标记设备为"已连接",恢复采集
|
||||
b. 连接失败 → 继续等待下一次重连
|
||||
6. 重连成功后,自动恢复所有采集点的正常采集
|
||||
7. 重连无次数限制,持续尝试直到成功或设备被禁用
|
||||
1. 采集任务获取连接或读取失败时,连接管理器将设备标记为 `disconnected`,并记录最近离线时间。
|
||||
2. 失败点位本次最新值写入内存缓存:`value=null, quality=bad`,`quality_reason` 按失败阶段记录。
|
||||
3. 后续点位到期时再次通过连接管理器获取连接;连接不存在或已失效时重新按设备配置建立连接。
|
||||
4. 任一采集读取成功后调用 `MarkConnected` 恢复设备状态为 `connected`,并仅在状态变化时更新最近在线时间。
|
||||
5. `reconnect_interval` 作为设备配置保留字段;当前实现没有独立重连计时器,实际重试节奏由点位采集周期和任务调度决定。
|
||||
6. 设备禁用或逻辑删除后不再执行采集任务。
|
||||
```
|
||||
|
||||
### 5.6 动态配置更新
|
||||
@@ -1029,7 +1037,7 @@ API 始终返回字符串质量戳。`none` 只表示查询没有匹配记录;
|
||||
| 删除采集点 | 停止该点的采集任务 |
|
||||
| 启用/禁用采集点 | 禁用则停止,启用则启动 |
|
||||
|
||||
实现方式:Service 在配置事务提交后发布进程内变更事件,采集引擎正常情况下 1 秒内处理;同时每 5 秒按配置版本执行一次全量校对,作为丢事件后的兜底,因此对外保证 5 秒内最终生效。
|
||||
实现方式:当前实现不使用独立配置事件总线。采集调度器每秒重新读取活动采集点,因此点位配置通常在下一个调度周期生效;设备配置保存成功后立即使旧连接失效,下一次采集或写入按新配置重新建连。
|
||||
|
||||
### 5.7 TDengine 数据写入
|
||||
|
||||
@@ -1068,7 +1076,7 @@ USING collection_data TAGS (
|
||||
#### 5.7.3 写入策略
|
||||
|
||||
- 首次准备历史写入前设置 `history_started_at`,然后使用 `INSERT INTO ... USING ... TAGS` 创建/写入子表
|
||||
- 批量写入:每1秒或每100条数据一批;服务关闭前尽力刷新
|
||||
- 当前实现到达 `history_interval` 后立即写入 TDengine;后续如引入批量队列,必须保证服务关闭前尽力刷新
|
||||
- `store_history=false` 时停止新增历史记录,但已存在的数据仍可在历史模块中查询
|
||||
- 写入频率由 `history_interval` 控制
|
||||
- 动态子表名只由已验证 UUID 派生并通过 `^p_[0-9a-f]{32}$` 校验;时间和值参数仍使用参数绑定
|
||||
@@ -1126,7 +1134,7 @@ type DeviceRuntimeStatusProvider interface {
|
||||
### 6.2 写入安全策略
|
||||
|
||||
1. **write_enabled 开关**:写入点必须显式开启 write_enabled=true 才能写入,防止误操作
|
||||
2. **回读验证**:BOOL/INT 严格相等;REAL 满足 `abs(actual-target) <= readback_tolerance` 才算成功
|
||||
2. **回读验证**:BOOL/INT 严格相等;REAL 满足固定协议精度 `abs(actual-target) <= 0.0001` 才算成功
|
||||
3. **操作记录**:所有写入操作记录到 write_logs,包含日志 ID、时间、点位、目标值、回读值、结果、失败原因和原因;当前阶段不记录具体操作者
|
||||
4. **访问边界**:当前阶段未实施身份认证,写入 API 仅应部署在受信任的内部网络;接入身份认证后再补充操作者归属与权限控制
|
||||
|
||||
@@ -1229,6 +1237,7 @@ func (r *ProtocolRegistry) Get(protocolType string) (ProtocolDriverFactory, erro
|
||||
| 连接粒度 | 每设备一个连接实例 |
|
||||
| 地址解析 | 见 3.1.2 |
|
||||
| 配置 | `rack`、`slot` |
|
||||
| 运行时状态 | 已接入 `gos7`,现场已完成 DB REAL 采集、MD REAL 写入和回读验证 |
|
||||
|
||||
#### 7.4.2 Modbus TCP
|
||||
|
||||
@@ -1239,6 +1248,7 @@ func (r *ProtocolRegistry) Get(protocolType string) (ProtocolDriverFactory, erro
|
||||
| 连接粒度 | 每设备一个连接实例 |
|
||||
| 地址解析 | 见 3.1.2 |
|
||||
| 配置 | `unit_id`、`float32_order`,其中 `float32_order ∈ {ABCD,BADC,CDAB,DCBA}` |
|
||||
| 运行时状态 | 已实现 Modbus TCP 客户端;支持线圈、离散输入、输入寄存器、保持寄存器读取,支持线圈、保持寄存器写入,并支持 `ABCD/BADC/CDAB/DCBA` REAL 字节序 |
|
||||
|
||||
---
|
||||
|
||||
@@ -1301,15 +1311,15 @@ func (r *ProtocolRegistry) Get(protocolType string) (ProtocolDriverFactory, erro
|
||||
| R006 | 采集周期最小1秒 | 不可低于1秒 |
|
||||
| R007 | 历史存储间隔范围 | 1~1440 分钟;当store_history=true时必填 |
|
||||
| R008 | 人工写入记录 | 服务端固定记录 source=manual、operator=null;当前阶段不支持自动写入 |
|
||||
| R009 | 写入回读验证 | BOOL/INT严格相等;REAL按readback_tolerance判断,不一致重试1次 |
|
||||
| R009 | 写入回读验证 | BOOL/INT严格相等;REAL按固定0.0001协议精度判断,不一致重试1次 |
|
||||
| R010 | 写入点地址类型限制 | 必须使用可写地址类型 |
|
||||
| R011 | 质量戳自动判定 | 失败时value=null且bad;越界时保留value并标记bad;无匹配记录为none |
|
||||
| R012 | 断线无限重连 | 持续按配置间隔重连,直到成功或设备被禁用 |
|
||||
| R013 | 配置变更动态生效 | 事务后事件正常1秒内生效,5秒全量校对保证最终一致 |
|
||||
| R014 | 历史语义不可变 | history_started_at非空后不可修改device_id或data_type |
|
||||
| R011 | 质量戳自动判定 | 失败时value=null且bad;读取成功即good;无匹配记录为none |
|
||||
| R012 | 断线恢复 | 后续采集任务按采集周期重新建连;任一读取成功后恢复在线状态 |
|
||||
| R013 | 配置变更动态生效 | 点位配置由每秒调度加载生效;设备配置保存后旧连接立即失效并在下次使用时重连 |
|
||||
| R014 | 历史启动标记 | 首次准备写入历史前设置history_started_at;当前实现不阻断后续device_id或data_type编辑 |
|
||||
| R015 | 协议连接隔离 | 注册Factory,每个设备独立Connection,同设备读写串行 |
|
||||
|
||||
---
|
||||
|
||||
> 本文档版本:v1.2
|
||||
> 最后更新:2026-07-11
|
||||
> 本文档版本:v1.3
|
||||
> 最后更新:2026-07-13
|
||||
|
||||
+13
-11
@@ -44,8 +44,8 @@
|
||||
|
||||
### ADR-006|历史元数据与配置变更
|
||||
|
||||
- `collection_points` 增加 `unit`、`valid_min`、`valid_max`、`history_started_at`。
|
||||
- 首次准备写入历史数据前设置 `history_started_at`;一旦非空,禁止修改 `device_id` 和 `data_type`。需要改变时必须新建点位。
|
||||
- `collection_points` 保留 `unit`、`history_started_at`;`valid_min`、`valid_max` 已移除。
|
||||
- 首次准备写入历史数据前设置 `history_started_at`。当前实现允许后续修改 `device_id` 和 `data_type`;历史展示以 PostgreSQL 当前元数据为准,TDengine 旧行保留写入时快照。
|
||||
- 点位名称、设备名称、分组和单位可以修改;历史 API 使用 PostgreSQL 当前元数据展示。
|
||||
- TDengine 中的 `point_name`、`device_name` 和 `data_type` 是写入时快照,不是当前配置的权威来源。
|
||||
|
||||
@@ -54,8 +54,8 @@
|
||||
- API 质量枚举:`good | bad | none`。
|
||||
- TDengine 只存储 `good=0`、`bad=1`;`none` 表示查询无匹配记录,不落库。
|
||||
- `value` 允许为 `null`:读取失败、断线或解析失败时写入 `quality=bad`、`value=null`。
|
||||
- 读取成功但超出配置有效范围时写入 `quality=bad` 且保留数值。
|
||||
- TDengine 和 API 增加可空 `quality_reason`,使用稳定代码,例如 `timeout`、`disconnected`、`parse_error`、`out_of_range`。
|
||||
- 采集质量不再执行有效范围判断;协议读取和解析成功即为 `good`。
|
||||
- TDengine 和 API 增加可空 `quality_reason`,使用稳定代码,例如 `timeout`、`disconnected`、`parse_error`、`read_error`。
|
||||
- 表格无匹配记录统一返回 `{value:null, quality:"none", quality_reason:null, matched_ts:null}`,不得返回整个元素为 `null`。
|
||||
|
||||
### ADR-008|历史查询边界与降采样
|
||||
@@ -93,13 +93,15 @@
|
||||
### ADR-013|写入值和回读
|
||||
|
||||
- 写入日志数据库可用 TEXT 保存原始值,但 API 必须按 `data_type` 返回 JSON boolean/number。
|
||||
- `write_points` 增加 `readback_tolerance`;BOOL/INT 严格相等,REAL 使用绝对误差 `abs(actual-target) <= readback_tolerance`。
|
||||
- `write_points` 仅保留 `write_enabled` 写入权限开关;`enabled` 和 `readback_tolerance` 已移除。
|
||||
- BOOL/INT 回读严格相等;REAL 使用固定协议精度 `abs(actual-target) <= 0.0001`。
|
||||
- 当前阶段仅支持内部网络中的未认证人工写入,服务端固定记录 `source=manual`、`operator=null`。
|
||||
|
||||
### ADR-014|配置生效时限
|
||||
|
||||
- Service 在事务提交后发布进程内配置变更事件,正常情况下 1 秒内生效。
|
||||
- 采集引擎每 5 秒执行一次全量版本校对作为丢事件后的兜底;因此对外 SLA 为 5 秒内最终生效。
|
||||
- 当前实现不使用独立配置事件总线。
|
||||
- 采集调度器每秒重新读取活动采集点,点位配置通常在下一个调度周期生效。
|
||||
- 设备配置保存成功后立即使旧连接失效,下一次采集或写入按新配置重新建连。
|
||||
|
||||
### ADR-015|历史图表职责
|
||||
|
||||
@@ -115,9 +117,9 @@
|
||||
|
||||
| 文档 | 版本 |
|
||||
|---|---|
|
||||
| `spec-数据管理.md` | v1.2 |
|
||||
| `spec-历史数据.md` | v1.1 |
|
||||
| `spec-数据管理.md` | v1.3 |
|
||||
| `spec-历史数据.md` | v1.2 |
|
||||
| `code-standards.md` | v1.2 |
|
||||
| 本基线 | v1.0 |
|
||||
| 本基线 | v1.1 |
|
||||
|
||||
> 最后更新:2026-07-11
|
||||
> 最后更新:2026-07-13
|
||||
|
||||
Reference in New Issue
Block a user