数字线程——跨越系统也不会中断的“同一个名称”
数字线程并不是把数据集中到一个地方。 它要让分散的系统能够用同一个名称指代同一个对象,并沿着这些对象之间的关系和时间进行追溯。只做汇集,最终会变成数据沼泽;名称能够贯通,即使数据分散各处,也能形成线程。
本文将线程分为标识、关系、时间三条主线,从技术角度说明每条线会在哪里中断,以及如何将其连接起来。示例取自 PlantPulse 平台的实际实现,但其中的思路适用于任何工厂和任何技术栈。
负责打通 MES、ERP、历史数据库和 CMMS 的工程师、设计工厂数据模型的人员,以及曾经遇到“数据明明都有,为什么还是得不到答案”这一问题的读者。建议同时阅读制造业 AI——工厂数据对模型提出的要求。
1. 它是什么,又不是什么
一个产品经历设计、工艺确定、生成工单、接受检验,以及设备故障后的维修。每个阶段都由不同的系统记录。数字线程就是一条能够反向追溯这些记录的线索。
如果通过线程回答“这个批次的不良率为什么上升”,过程如下。
불량 로트 ─(어느 작업지시?)─▶ 워크오더 ─(어느 설비, 언제?)─▶ 설비 · 시간 구간
─(그때 그 설비의 신호는?)─▶ 태그 시계열 ─(상류는?)─▶ 공급 설비 · 유틸리티
─(그 설비의 정비 이력 · 절차서는?)─▶ 문서
每个箭头都代表一次跨越系统边界的联接。任何一处中断,问题就只能追到这里,随后便需要人工打开多个 Excel 文件逐项核对。
| 经常混淆的概念 | 区别 |
|---|---|
| 数字孪生 | 孪生是单个对象的状态模型(这台泵当前情况如何)。线程是连接对象及其时间的引用(这台泵向什么供料、用于哪个工单、当时发生了什么)。即使存在多个孪生,没有线程,它们仍是互不了解的孤岛 |
| 数据湖 | 数据湖解决的是存储。即使把所有数据都倒入同一个地方,只要 ID 不一致,就无法联接 |
| 集成平台 | 由一个产品承担全部功能,与多个系统使用同一个名称并不是一回事。线程指的是后者 |
2. 会在哪里中断
在现场,线程中断通常有以下四种形式。
| 中断形式 | 示例 |
|---|---|
| 名称不同 | 同一台泵在 SCADA 中称为 P-101A,在 CMMS 中称为 PUMP_101_A,在 ERP 中则使用资产编号 400123 |
| 缺少关系 | 有层级关系(属于哪条生产线),但流向关系(向什么供料)和依赖关系(依赖哪个公用工程)在任何地方都没有记录 |
| 时间不一致 | 设备时钟、服务器时钟和记录时间不同;线路中断后恢复时,延迟数据会一次性集中到达 |
| 副本发生分歧 | 在每个系统中都复制了一份设备主数据,结果一侧移动了设备,另一侧却毫不知情 |
第四种最难察觉。副本最初完全相同,因此看不到问题;但一旦发生分歧,就没有依据判断哪一侧才是正确的。
3. 第一条线——标识:权威数据源只有一个,其余均为派生
原则只有一个——将主数据的权威数据源(SSOT)置于一处,其他所有表现形式都从中派生。 派生数据可以随时重建,因此即使出现分歧,也能作出判断——以权威数据源为准。
在 PlantPulse 中,PostgreSQL 元数据存储就是权威数据源。企业、站点、资产树、标签、报警设置和命令定义都保存在这里,后文提到的图、主题路径以及 AI 所看到的结构,全部是读取这些数据后生成的派生结果。
通过“映射列”接收外部 ID
不能把其他系统的 ID 改写成自己的 ID。因为两个系统会按照不同规则生成 ID。正确做法是:自己的 ID 继续遵循自己的规则,同时单独设置一列记录对方的 ID。
| 实体 | 自有 ID | 对方系统 ID |
|---|---|---|
| 客户 | customer_id——仅限英文字母、数字和 _ | external_customer_id(MES、ERP) |
| 产品 | 产品 ID | 外部产品 ID |
这样,即使 ERP 更改编码体系,己方的任何联接都不会受到影响,只需修改映射列。
将人的表达也转换为 ID
“3号线压缩机”不是 ID。必须设置一个单独步骤,将人使用的名称解析为权威数据源中的 ID。PlantPulse AI 收到问题后,最先执行的就是这一步(search_domains)。如果这一步出错,后续分析即使再准确,也只会得到关于错误设备的准确答案。
4. 第二条线——关系:仅有层级还不够
层级回答“位于哪里”
ISA-95 层级是线程的骨架。
Enterprise ─ Site ─ Area ─ Line ─ Equipment ─ Tag ─ 값
有了这棵树,就可以回答“3号线的全部设备”“按站点汇总 OEE”等问题。PlantPulse 的 KPI 之所以能够按生产线、区域、站点和企业层级汇总,也是因为遵循了这棵树。
而且,消息地址也来自这棵树。 UNS(Unified Namespace)的 MQTT 主题直接使用层级路径。
pp/{site_id}/{area_id}/{line_id}/{equipment_id}/{tag_id}/point
只看主题名称,就能知道值来自哪里。订阅方无需另行查询,消息本身就携带上下文——相当于线程已经嵌入消息之中。
将标签移到其他设备后,该标签的 MQTT 主题也会随之变化。订阅旧主题的外部系统将会在不报错的情况下悄然停止接收数据。因此,在调整层级前,必须确认是否有系统正在订阅该路径。也正因如此,层级应当初期谨慎设计,后期尽量少改。PlantPulse 支持在 UNS 页面中预览并复制主题路径。
有向关系回答“会影响什么”
“这台设备停机会导致哪些地方停机”无法仅凭层级回答。位于同一条生产线,并不意味着设备之间一定存在供料关系;另一个区域的空气压缩机也可能导致整条生产线停机。因此,需要在层级之外,单独建立有方向的关系。
| 关系 | 含义 | 沿关系追溯时 |
|---|---|---|
FEEDS | 从上游向下游流动 | 向下游:停机影响范围;向上游:质量异常的候选原因 |
DEPENDS_ON | 依赖电力、压缩空气、冷却等公用工程 | 某个公用工程停运时,哪些设备会同时停机 |
PRODUCES | 生产什么 | 从设备追溯到产品 |
产品侧也建立同样的链条。产品路径(产品经过的单元工序顺序)→ 单元工序(工序定义及其所属设备)→ 连接关系(设备之间的流向和依赖)。工序页面中的“使用此工序的产品”之所以是只读的,是因为该值是从产品路径中推导出来的——如果允许在两个位置分别维护,副本就会发生分歧(第 2 节中的第四种情况)。
图是派生数据
对于需要沿关系追溯的问题,图非常方便。但如果把图数据库设为第二个权威数据源,就会破坏第 3 节的原则——此时设备主数据存在于两个位置,而同步管道将成为线程中最薄弱的环节。
PlantPulse 采用了相反的方式。
- 图是平台内的内存 RDF 模型(Apache Jena),通过读取元数据存储进行整体重建。 不存在同步存储库
- 重建时机为:收到查询且 TTL(默认 10 分钟)已过期时(
semantic.graph.refresh.minutes)。即使同时有多个查询,也只执行一次重建,其余查询使用上一版模型 - 代价也很明确——刚刚登记的设备在下一次刷新前可能不会出现在图中。 但作为交换,从结构上杜绝了图与权威数据源长期分歧的情况
采用同步方式的副本一旦出错,无法知道错误会持续多久。 周期性重建的派生数据即使暂时不正确,持续时间也不会超过一个周期。这一差异在线程设计中非常重要——延迟可以通过等待解决,而分歧必须由人工发现后才能解决。
5. 第三条线——时间:以工单作为联接键
标识和关系连接的是什么与哪里。但工厂中的问题几乎总会包含何时——例如“生产该批次时”“故障前两周”。
工单是时间轴上的联接键
作业指示(工单)在线程中具有特殊地位。一张工单会同时指向设备、产品、客户和负责人,并附有计划开始时间、计划结束时间及目标数量。也就是说,一张工单本身就是以下联接条件:
“这台设备在这一时间区间内的时序数据属于这个产品和这个客户”
之所以能够按工单计算 OEE,也能够在设备页面中筛选“仅限此设备的工单”,都得益于这一联接。反过来,如果只有时序数据而没有工单,即使数值再多,也无法知道这些值是在生产什么时产生的。
区分存储状态与推导状态
引入时间后,状态会分为两类。
| 示例 | 位于何处 | |
|---|---|---|
| 存储状态 | 等待、进行中、暂停、完成、异常结束 | 数据库 |
| 推导状态 | 延迟(已超过计划开始时间但尚未开始)、取消(未开始且已超过计划结束时间) | 页面根据当前时间计算——数据中不存在 |
不存储推导状态是有原因的。如果存储“延迟”状态,就会产生一个新问题:由谁在何时解除该状态。一旦更新延误,数据就会失真。其代价是:在 API 或数据库中搜索 DELAY 也找不到它——沿线程将状态传递给外部系统时,必须同时传递推导规则。
发生时间与到达时间并不相同
Edge 在线路中断时,会将数据暂存在本地,待恢复后一次性上传。这样可以避免数据缺失,但也意味着数小时前的值会在当前时刻到达。在线程中按时间联接时,必须始终使用发生时间(event time)。如果使用到达时间联接,恢复后到达的旧数据会被错误地关联到当前工单,而中断期间的工单仍然没有数据。
6. 不把值放入图中——放入指针
使用图表示线程时,很容易想把时序值也放进图中。但不应这样做。时序数据会持续无限增长,而且通常只按“某个标签的一段时间”读取,因此应保存在专门为此设计的存储中(时序数据库)。
图中的标签节点只需指明自己的值位于哪里,以及如何读取。
| 属性 | 含义 | 示例 |
|---|---|---|
seriesKey | 时序查询键 | 标签 ID |
seriesStore | 位于哪个存储的哪个表 | 时序存储标识符 |
seriesTool | 使用什么工具读取 | get_tag_history |
lastValue、lastTimestamp | 最新值快照 | 无需查询即可查看“当前”状态 |
seriesTool 的位置很有意思。由于图中不仅包含数据,还包含“读取方式”,沿图追溯的一方(无论是人还是 AI 智能体)可以直接从图中获取下一步需要调用的工具。 设备时间线也通过相同方式(timelineKey、timelineStore)进行引用。
这也是线程的一项通用原则——线程不搬运数据,而是连接通往数据的路径。
7. 标准用于“边界”
UNS、Sparkplug B、OPC-UA、AAS(Asset Administration Shell)等标准,是将线程发布到系统外部时使用的语言。
| 标准 | 标准化的内容 | 在 PlantPulse 中 |
|---|---|---|
| UNS(MQTT) | 将层级路径用作主题地址 | 根据层级生成并预览主题 |
| Sparkplug B | MQTT 载荷与状态(birth、death) | 在 Edge 上启用后使用(默认关闭) |
| OPC-UA | 设备信息模型与访问方式 | 驱动、标准 UI(:12780) |
| AAS V3 | 资产的“数字铭牌”——通过子模型组织技术信息 | 标准 UI(:8091) |
如果试图将标准直接用作内部模型,通常得不偿失。标准是多家公司协商形成的最大公约数,无法完整表达工厂内部的关系(例如什么向什么供料);而且每次标准修订,内部模型都会受到影响。内部使用自己的权威数据源,外部使用标准——在边界处进行转换,更不容易让线程中断。
8. AI 只有在线程之上才能保持准确
向语言模型询问“3号线压缩机最近怎么样?”时,模型最终执行的其实就是沿线程追溯。
① 「3라인 압축기」 → 설비 ID 식별
② 이 설비의 상류 · 하류 · 의존 관계 (그래프)
③ 태그 노드의 seriesTool 로 이력 조회 시간 (포인터)
④ 해당 기간의 워크오더 · 알람 · 문서 시간 + 식별
如果线程中断,模型会用貌似合理的内容填补断点。名称无法解析时,它会选择相似的设备;缺少关系时,它会仅凭设备位于同一条生产线来猜测影响。因此,制造业 AI 的准确度首先受制于线程的完整性,而不是模型规模。图查询工具在达到深度限制时显示 truncated,无法计数的总量则用 total_tag_count_unknown 表示“未知”,原因也是如此——必须明确说明断点确实存在,模型才不会自行填补。工具列表参见本体。
设计检查清单
在建立或巡检线程时,无论采用何种技术栈,都应确认以下问题。
| 问题 | 理想答案 |
|---|---|
| 设备主数据的权威数据源在哪里 | 只有一处。 其余均从中派生,并且可以重建 |
| 如何接收外部系统的 ID | 不更改自己的 ID,而是通过映射列接收 |
| 是否存在将人的表达转换为 ID 的步骤 | 有,而且这一步应当先于分析 |
| 除层级之外,是否存在有方向的关系 | 分别记录流向(FEEDS)和依赖(DEPENDS_ON) |
| 图是权威数据源还是派生数据 | 派生数据。 不使用同步存储库,而是重新构建,并明确新鲜度的上限 |
| 是否将时序值复制到图中 | 不复制,只保留指针和读取方式 |
| 时间联接使用什么 | 发生时间。 工单的开始和结束时间构成联接条件 |
| 是否存储推导状态 | 不存储。传递给外部系统时,应同时传递推导规则 |
| 更改层级会破坏什么 | 明确主题地址会随之变化,并预先确认订阅方 |
| 标准用于何处 | 用于边界。 内部使用自己的权威数据源,外部使用标准 |
延伸阅读
制造业 AI——工厂数据对模型提出的要求 · 一个值到达页面的路径——从 Edge 到页面 · UNS——根据层级生成的主题 · 本体——沿关系追溯的工具 · 数据库模型——权威数据源的数据表 · 整体架构