跳到主要内容
版本:Next

LMES 出站采集逻辑详解

1. 引言

1.1. 文档目的

本文档旨在详细阐述 LMES 系统中“出站采集”功能的内部实现逻辑、技术架构和业务流程。主要面向技术开发、实施顾问及高级运维人员,帮助他们深入理解该功能模块,以便进行二次开发、系统配置和问题排查。

1.2. 适用范围

本文档适用于 LMES 制造执行系统,特别是与生产执行和流程管理相关的模块。

1.3. 核心概念定义

  • 出站 (Outbound): 在 MES 中,指出一个产品或载具完成在某个工位的加工、检验等所有预定操作,即将离开该工位的业务节点。
  • 采集 (Collection): 指在生产过程中,从设备(如 PLC)、传感器、扫码枪或操作员界面等来源获取数据的动作。
  • 流程引擎 (Process Engine): 一种用于自动化和管理业务流程的核心技术组件。在本系统中,它通过解析预定义的流程图(PFD 文件),动态地执行、协调和监控出站过程中的每一步活动。

2. PLC交互时序图

下图清晰地展示了LMES与PLC在一次典型的出站流程中的详细交互顺序,包括了信号的触发、数据的读写以及最终的复位过程。

2.1. 核心交互原则:谁触发,谁复位

LMES与PLC之间的信号交互严格遵循“谁触发,谁复位”的原则。这意味着,一个信号由哪一方发起(例如,从0变为1),那么在交互完成后,也应由同一方负责将其恢复到初始状态(从1变回0)。

  • PLC触发: Outbound_FinishedSignal 由PLC置为1来启动流程,因此在收到LMES的完成信号后,也必须由PLC将其复位为0
  • LMES触发: Outbound_CompletedSignal 由LMES置为1来通知PLC处理完毕,因此在监听到PLC已复位Outbound_FinishedSignal后,也必须由LMES将其复位为0

这个原则确保了每次交互都是一个完整的“握手”过程,避免了信号状态的混乱。


3. 流程核心变量详解

下表列出了在 10003.xml 流程模板中定义的 逻辑变量标识。这些标识是流程内部使用的标准名称。它们并非直接对应PLC地址,而是需要通过 工艺管理 -> 工位配置 界面,与CMS基座中预先定义好的 变量(Variable) 进行绑定。最终的PLC地址是在CMS基座的变量中进行配置的。

逻辑标识 (Logical Identifier)数据类型 (Data Type)读/写 (R/W)说明 (Description)
Outbound_FinishedSignalintPLC通知LMES加工完成的触发信号,值为1时启动流程。PLC处理完后需复位为0
Outbound_SerialNumberVariablestring读/写用于传递产品序列号。LMES读取或在关联查询后写回。
Outbound_MaterialVariablestring当“条码获取方式”为物料关联时,LMES从此变量读取物料码。
Outbound_TrayVariablestring当“条码获取方式”为托盘关联时,LMES从此变量读取托盘码。
Outbound_QualityVariablestring当“合格判断方式”包含PLC判断时,LMES从此变量读取PLC给出的原始加工结果。
Outbound_ResultVariablestringLMES向PLC下发最终的合格判断结果(例如 "1" 代表OK, "2" 代表NG)。
Outbound_CompletedSignalintLMES通知PLC出站流程已处理完成的信号。
Outbound_FinishVariableintLMES向PLC下发本次出站的最终结果代码(详见下文定义)。
Outbound_Descriptionstring当出站发生异常或有需要时,LMES向PLC下发具体的文本说明。

4. 业务逻辑与配置指南

4.1. 业务处理顺序

在接收到PLC的Outbound_FinishedSignal=1信号后,LMES内部严格按照以下顺序执行业务逻辑:

  1. 出站初始化 (OutboundInitializeActivity): 准备流程环境,加载产品上下文。
  2. 参数采集 (ParameterCollectActivity): 从PLC一次性读取所有需要的工艺参数和质量信号。
  3. 合格判断 (...QualificationJudgmentActivity): 这是核心决策步骤。
    • PLC判断: 直接采信 Outbound_QualityVariable 的值。
    • 本机判断: 将采集的参数与配方规格进行比较。
    • 综合判断: 同时执行以上两种判断,结果取最严苛的。
  4. 参数存储 (ParameterSaveActivity): 将所有采集的数据、判断结果存入数据库,形成追溯记录。
  5. 物料记录 (MaterialUsageRecordActivity): 记录本次生产所消耗的物料。
  6. 结果下发: 将 Outbound_FinishVariable 等结果变量的值写入PLC。

4.2. 相关模块配置

要正确配置一个出站流程,需要在以下几个模块中进行设置:

  • 流程管理 (FlowManagement):
    • 流程定义文件 (10003_1.pfd): 定义了出站业务逻辑的执行顺序、条件和活动。此文件为系统的核心模板,通常由研发人员维护。
    • 流程变量配置文件 (10003.xml): 定义了流程所需的 逻辑变量标识。此文件也是系统的核心模板,通常由研发人员维护。
  • 工艺管理 (ProcessManagement):
    • 工位配置: 这是最关键的实施配置环节。在此处,需要将流程模板中定义的 逻辑变量标识 (如 Outbound_FinishedSignal) 与CMS基座中已定义好的 变量(Variable) 进行绑定。LMES本身不直接配置PLC地址,而是通过引用这些基座变量来间接实现与设备的通信。
    • 定义工位需要采集哪些过程参数 (ParameterCollectActivity 会读取这些配置)。
    • 定义工位有哪些不良原因及其对应的PLC信号 (PLCQualificationJudgmentActivity 会使用这些配置)。
  • 配方管理 (FormulaManagement):
    • 定义产品在某个工位的工艺参数标准(如上下限),这是 LocalQualificationJudgmentActivity 进行本机判断的依据。
  • 物料管理 (MaterialManagement):
    • 定义产品的BOM清单,MaterialUsageRecordActivity 会依据此清单记录物料消耗。

5. 流程详解

本章节将完全依据流程图,对出站采集的核心逻辑进行详细解析。

5.1. 启动 --> 完工信号监听

  • 描述: 流程启动后,进入等待状态,持续监听PLC的Outbound_FinishedSignal变量。当该变量值变为1时,流程被激活。
  • 实现: 由框架的VariableMonitorActivity活动完成。

5.2. 条码获取方式 (条件分支)

  • 描述: 流程根据Outbound_ObtainBarcodeType配置决定如何获取产品码。
  • 分支 1 (方式=1): 直接从Outbound_SerialNumberVariable读取产品码。
  • 分支 2 (方式=2): 读取Outbound_MaterialVariable中的物料码,通过MaterialAssociationActivity关联查询出产品码,再通过VariableWriteActivity将产品码下发回PLC。
  • 分支 3 (方式=3): 读取Outbound_TrayVariable中的托盘码,通过TrayAssociationActivity关联查询出产品码,再通过VariableWriteActivity将产品码下发回PLC。

5.3. 出站初始化

  • 描述: 在获取到产品标识后,进行统一的初始化操作。如果在此阶段发生异常(如产品码为空),流程将直接跳转到“出站结果下发”步骤,并写入错误代码。如果正常,则继续。
  • 实现: OutboundInitializeActivity (业务活动)。

5.4. 过程参数采集 & 合格判断

  • 描述: 首先,通过ParameterCollectActivity从PLC批量读取所有预设的过程参数。然后,根据QualifiedJudgmentType配置进入不同的判断分支。
  • 分支 1 (方式=1): PLCQualificationJudgmentActivity直接采信PLC的判断结果。
  • 分支 2 (方式=2): LocalQualificationJudgmentActivity在MES内部根据配方规格进行比较判断。
  • 分支 3 (方式=3): CompositeQualificationJudgmentActivity结合前两种方式进行综合判断。

5.5. 过程参数存储

  • 描述: 所有分支的判断结果最终都汇集于此。ParameterSaveActivity将采集到的所有数据和判断结果存入数据库,形成追溯记录。此步骤完成后,会并行触发(SplitAND)后续的“出站结果下发”和“物料使用记录”。
  • 实现: ParameterSaveActivity (业务活动)。

5.6. 物料使用记录

  • 描述: 与结果下发并行执行,MaterialUsageRecordActivity负责记录本次生产所消耗的物料信息。
  • 实现: MaterialUsageRecordActivity (业务活动)。

5.7. 出站结果下发

  • 描述: 将最终结果下发给PLC,并通知SOP系统。此步骤完成后,同样会并行触发(SplitAND)“完工信号复位监听”和“SOP出站通知”。
  • 实现: 由框架的VariableWriteActivity活动完成结果下发。

5.8. SOP出站通知

  • 描述: 与信号复位监听并行执行,SopControlActivity将出站事件通知给SOP显示终端。
  • 实现: SopControlActivity (业务活动)。

5.9. 完工信号复位监听 & 出站完成

  • 描述: 这是流程的最后阶段。LMES通过VariableMonitorActivity等待PLC将Outbound_FinishedSignal复位为0,这标志着PLC已确认收到出站结果。
  • 复位加工结果: 如果合格判断方式不是单纯的PLC判断(方式!=1),LMES会通过VariableWriteActivity主动将Outbound_ResultVariable也复位为0。
  • 出站完成: 最后,LMES通过VariableWriteActivityOutbound_CompletedSignal等信号变量复位为0,使整个工位恢复到待机状态,准备迎接下一个产品。

5.10. 结束

  • 描述: 流程正常结束。
  • 实现: 由框架的EndActivity活动标记。

6. 核心业务活动实现细节

本章节旨在为二次开发或深度配置人员提供出站流程核心活动的内部实现细节。值得注意的是,在所有工序流程相关的活动中,可以通过 Flow.DataItems.ApplicationData as ProcessModel 来获取当前工位的完整配置信息。

6.1. OutboundInitializeActivity

  • 流程定义(PFD)名称: 出站初始化
  • 核心职责: 为出站流程准备好所有必要的上下文数据。
  • 实现细节:
    1. 重置状态: 进入活动时,会首先清空/重置本次出站相关的流程变量,如 OutboundDescriptionValueOutboundFinishSignal
    2. 获取产品码: 检查 FlowItemCollection.SerialNumber 是否存在。如果为空(例如,上游的关联查询失败),则会立即设置一个错误结果(如 NoProductEntry),并终止后续正常流程。
    3. 加载上下文: 如果产品码存在,活动会智能地加载该产品的完整上下文。它首先检查当前流程实例中是否已包含工单 (OrderModel)、产品型号 (ProductModel) 等信息(通常由进站流程传入)。如果不存在,它会调用 GetProductionAsync 方法,从数据库中查询该产品码最后一次的生产记录,并以此来恢复其工单、产品型号、进站时间等关键信息。
  • 关键上下文变量:
    • : SerialNumber, TrayCodeAssociationDescriptionValue, MaterialCodeAssociationDescriptionValue
    • : OutboundFinishSignal, OutboundDescriptionValue, OrderModel, ProductModel, InboundEntryTime
  • 二次开发/配置建议: 此活动是流程的基础,通常无需修改。但理解其上下文加载机制(优先使用当前流程数据,其次查询历史记录)对于排查问题至关重要。

6.2. ParameterCollectActivity

  • 流程定义(PFD)名称: 过程参数采集
  • 核心职责: 从PLC批量读取所有与本次出站相关的变量值。
  • 实现细节:
    1. 构建变量列表: 活动会动态构建一个需要读取的PLC变量地址列表 (varNames)。这个列表的数据来源是高度可配置的,它会聚合以下所有点位:
      • 工位配置中定义的过程参数 (ProcessParameters)。
      • 工位配置中定义的物料参数 (MaterialParameters)。
      • 配方中定义的配方参数 (FormulaParameters),包括下发和监视变量。
      • 流程中硬编码需要的一些特殊变量,如 Outbound_QualityVariable
    2. 批量读取: 构建完列表后,它会调用 IFlowVariableService.ReadValueAsync 服务,向数据采集模块发起一次性的批量读取请求。
    3. 存入上下文: 将返回的包含所有变量值的字典(IReadOnlyDictionary<string, IVariableValue>)整体存入流程上下文 Flow.DataItems[FlowItemCollection.ParameterCollectData] 中,供后续活动使用。
  • 关键上下文变量:
    • : ApplicationData (获取 ProcessModel)
    • : ParameterCollectData
  • 二次开发/配置建议: 此活动完全由配置驱动。如果需要采集一个新的PLC点位作为过程参数,只需在工艺管理中添加相应的参数定义即可,无需任何代码改动。

6.3. ...QualificationJudgmentActivity (合格判断系列)

  • PLCQualificationJudgmentActivity (流程定义(PFD)名称: PLC合格判断):
    • 核心职责: 完全依赖设备自身的判断结果。
    • 实现细节: 逻辑最简单。它直接从 ParameterCollectData 中获取 Outbound_QualityVariable 的值。如果值为1true,则判定为合格。如果为2false,则会进一步调用 GetUnqualifiedReasons 方法,检查工位配置中的不良原因变量,找出具体是哪个不良信号被触发。
  • LocalQualificationJudgmentActivity (流程定义(PFD)名称: 本地合格判断):
    • 核心职责: 在MES内部完成质量判断。
    • 实现细节: 逻辑最复杂。它会:
      1. 获取当前产品的配方版本 (FormulaVersionModel)。
      2. 遍历工位配置中所有的过程参数
      3. 对于每个参数,在配方版本中查找其对应的规格(lower, upper)。
      4. ParameterCollectData 中的实际值与规格进行比较。
      5. 任何一个参数超差,都会将最终结果置为不合格,并记录下超差的参数名作为原因。
      6. 扩展点: 此活动会调用 ILocalJudgmentStrategy 接口的实现,允许开发人员通过依赖注入的方式,插入自定义的、非标准(例如需要复杂计算)的合格判断逻辑。
  • CompositeQualificationJudgmentActivity (流程定义(PFD)名称: 综合合格判断):
    • 核心职责: 结合PLC和MES的判断结果。
    • 实现细节: 它不包含独立逻辑,而是依次调用 PLCJudgmentAsyncLocalJudgmentAsync 两个方法,然后将两个结果进行“与”操作。只有两者都为合格时,最终结果才为合格。
  • 关键上下文变量:
    • : ParameterCollectData, OrderModel, ProductModel, FormulaApplyModel
    • : QualityResultValue, QualityResultReason
  • 二次开发/配置建议: 对于复杂的质量判断需求,最佳实践是实现 ILocalJudgmentStrategy 接口,而不是直接修改活动代码。

6.4. ParameterSaveActivity

  • 流程定义(PFD)名称: 过程参数存储
  • 核心职责: 组装并持久化本次出站的完整追溯记录。
  • 实现细节:
    1. 数据聚合: 这是活动的核心。它从 Flow.DataItems 中收集所有必要的信息,包括:产品码、工单、产品型号、进站时间、完成时间、最终合格判断结果、不合格原因、操作员,以及 ParameterCollectData 中的所有过程参数和物料参数。
    2. 构建模型: 将聚合的数据统一组装成一个 TraceModel 对象。
    3. 处理特殊逻辑: 它会检查SOP的交互结果,并判断本次追溯的数据类型(TraceDataType),例如是否为点检数据。
    4. 存入上下文: 将构建完成的 TraceModel 对象存回 Flow.DataItems[FlowItemCollection.TraceModel],以便后续的 MaterialUsageRecordActivity 等活动可以获取到完整的追溯信息。
  • 关键上下文变量:
    • : ApplicationData, OrderModel, ProductModel, ParameterCollectData, QualityResultValue, QualityResultReason, InboundEntryTime
    • : TraceModel
  • 二次开发/配置建议: 如果需要为追溯记录增加额外的自定义字段,最推荐的方式是将其添加到 TraceModel.ExtraProperties 字典中。这可以在此活动之前通过一个简单的自定义活动实现,或者直接修改此活动来添加。

7. 结果代码与异常排查

7.1. 出站结果代码定义 (Outbound_FinishVariable)

LMES通过 Outbound_FinishVariable 变量告知PLC最终的出站处理结果。这些代码的含义和数值由 ExitResultConfig.cs 文件定义,同时也可以在 工艺管理 -> 工序配置 -> 工序结果值配置 界面中进行查看和自定义。

代码枚举名描述
0Reset复位
1OK正常
6NoProductEntry产品码获取异常
9NG其他异常
10MaterialCodeNotBoundProductCode物料码未绑定产品码
11TrayCodeNotBoundProductCode托盘码未绑定产品码

7.2. 异常排查指南

当出站流程出现问题时,应优先使用系统提供的可视化调试工具,其次再分析日志文件。

  • 使用工位控制台 (首选): LMES系统为每个工位提供了强大的 工位控制台,这是进行实时监控和故障排查的 首选工具

    • 核心优势:
      • 实时日志流: 控制台会实时推送当前工位关联流程的所有日志,包括 flowlog (流程逻辑日志), tracelog (追溯数据日志), 和 warnlog (警告/错误日志),无需手动刷新文件。
      • 变量实时监控: 可以实时看到所有在流程中定义的PLC变量的当前值及其变化。
      • 流程状态可视化: 能清晰地看到当前流程是正在运行、已停止还是处于异常状态。
      • 远程控制: 允许操作员或工程师直接从界面上对流程进行“重置”等操作,便于快速恢复。
    • 排查步骤:
      1. 打开对应工位的控制台。
      2. 观察 警告日志 窗口,查看是否有红色的错误信息。
      3. 切换到 流程日志 窗口,查看流程执行的详细步骤,定位到最后一条成功执行的日志,从而判断问题点。
      4. 观察 变量监控 区域,检查PLC信号是否按预期变化。
  • 分析日志文件 (离线/高级): 如果无法访问控制台,或需要进行离线的深度分析,推荐使用 LMES日志快速排查定位分析工具 (LMES Studio),具体使用方法请参考文档 doc/LMES日志分析工具使用说明.md。如果无法使用该工具,也可以直接在服务器上查看原始日志文件。

    • 使用流程实例ID: 在日志中搜索 【实例=...】 (例如 【实例=721876340764】),可以精确地串联起单次流程执行的所有相关日志。这是比产品码更精确的追踪方式。
    • 搜索产品码: 直接搜索出问题的产品序列号,可以定位到其相关的处理记录,但可能包含多次流程执行的日志。
    • 常见日志关键字:
      • 异常原因:: 这是最直接的错误信息,通常会明确指出问题所在。
      • LogExceptionMessage: 搜索此关键字可以找到代码层面的异常堆栈信息。
      • 合格信号: 在日志中搜索活动名 “PLC合格判断”,可以查看从PLC读取的原始合格信号值是否符合预期。
      • 参数范围: 搜索活动名 “本地合格判断”,可以看到每个参数的设定范围和实际采集值,便于排查是哪个参数超差。
      • 存储参数:: 搜索活动名 “过程参数存储”,可以看到最终存入数据库的完整追溯模型,确认数据是否正确。
      • 取消订阅: 如果流程卡死,检查是否有 取消订阅 日志,以判断是否是变量监控部分出现问题。

通过以上信息,可以高效地对出站流程进行配置、理解和故障排查。


8. 高级二次开发

对于需要深度定制或在不修改现有活动代码的情况下增加额外逻辑的场景,流程引擎提供了两种强大的扩展方式:IFlowProcessor 拦截器和 IMethodExecuter 自定义函数。

8.1. IFlowProcessor:全局流程拦截器

IFlowProcessor 是一个定义在 CMS.Plugin.FlowManagement.Abstractions 命名空间下的接口。任何实现了该接口的类,只要被正确地注册到依赖注入容器中,就会被流程引擎自动发现并执行。它允许开发者在流程中的 每一个活动(Activity) 执行的特定生命周期节点上注入自定义代码,实现AOP(面向切面编程)。

8.1.1. 核心拦截方法

IFlowProcessor 提供了三个核心方法,分别对应活动执行的三个阶段:

  • OnEnterAsync(flowProcessor, sender, args): 在任何活动即将 进入 (执行其 EnterAsync 方法) 之前被调用。
  • OnExecuteAsync(flowProcessor, sender, args): 在任何活动即将 执行 (执行其 ExecuteAsync 方法) 之前被调用。这是最常用的拦截点,用于处理核心业务逻辑。
  • OnExitAsync(flowProcessor, sender, args): 在任何活动即将 退出 (执行其 ExitAsync 方法) 之前被调用。

8.1.2. 如何使用

通过 sender as Activity,开发者可以获取到当前正在执行的活动实例,从而得知活动的 类型名称。通过 args as ProcessflowEventArgs,可以访问到流程的完整上下文 DataItems。其中,DataItems.ApplicationData 存放着当前工位的完整配置,可以安全地转换为 ProcessModel 类型进行访问:var processModel = (args as ProcessflowEventArgs).DataItems.ApplicationData as ProcessModel;

这使得开发者可以实现非常灵活的逻辑,例如:

  • 修改流程上下文: 在特定活动执行前后,动态地添加、修改或删除 DataItems 中的变量。
  • 增加通用校验: 实现一个IFlowProcessor,在OnExecuteAsync中判断如果当前活动是ParameterSaveActivity,则在它执行前,先执行一段通用的、跨所有流程的数据校验逻辑。
  • 动态日志记录: 针对特定类型的活动,在进入和退出时自动记录更详细的诊断日志。

8.2. IMethodExecuter: 定义自定义流程函数

第二种更直接的二次开发方式是实现 IMethodExecuter 接口。与作为全局拦截器的 IFlowProcessor 不同,IMethodExecuter 允许开发者创建可被 显式调用 的、具名的自定义“方法”或“函数”。这些函数通常由流程中的 MethodActivity 来触发。

8.2.1. IMethodExecuter 接口解析

此接口专为创建可重用的独立业务逻辑块而设计,其核心成员如下:

  • ExecuterName (属性): string 类型,必须提供一个 唯一的名称。这个名称就是该方法执行器的标识符,流程通过此名称来调用它。
  • Execute(ProcessflowEventArgs args) (方法): 这是执行器的核心方法。当流程调用此执行器时,该方法被触发。
    • args: 提供了对流程完整上下文 DataItems 的访问权限,允许您读取输入参数和写入返回值。
    • 返回值: Task<Dictionary<string, object>>。您可以将计算结果放入一个字典中返回,流程引擎会自动将这些键值对更新到流程的上下文 DataItems 中。

8.2.2. 工作机制

  1. 开发: 开发者创建一个或多个实现了 IMethodExecuter 接口的类。
  2. 注册: 将这些实现类注册到依赖注入容器中。
  3. 配置: 在流程设计器界面,实施人员可以拖拽一个 MethodActivity 活动到画布上。在活动的属性配置中,ExecuterName (执行器名称) 字段会以 下拉列表 的形式,展示所有已注册的 IMethodExecuter 实现。实施人员只需从中选择需要的执行器即可,无需手动输入名称。
  4. 执行: 当流程运行到该 MethodActivity 时,引擎会根据名称找到对应的 IMethodExecuter 实例,并调用其 Execute 方法。执行完毕后,返回的字典将被合并到流程上下文中。

8.2.3. 用例:复杂的物料追溯码解析

假设某个关键物料的二维码包含非常复杂的组合信息(如 供应商代码-批次号-生产日期-流水线号),需要解析后分别写入不同的PLC变量。

  1. 创建执行器:
    public class BarcodeParsingExecuter : IMethodExecuter
    {
    public string ExecuterName => "ParseComplexMaterialBarcode";
    public string ExecuterDescription => "解析复杂的物料追溯码";

    public Task<Dictionary<string, object>> Execute(ProcessflowEventArgs args)
    {
    var context = args.DataItems;
    var complexBarcode = context["ScannedMaterialBarcode"] as string;
    var result = new Dictionary<string, object>();

    if (!string.IsNullOrEmpty(complexBarcode))
    {
    var parts = complexBarcode.Split('-');
    if (parts.Length == 4)
    {
    result["ParsedVendorCode"] = parts[0];
    result["ParsedBatchNumber"] = parts[1];
    result["ParsedProductionDate"] = parts[2];
    result["ParsedLineNumber"] = parts[3];
    }
    }

    // 返回的结果会自动合并到流程上下文中
    return Task.FromResult(result);
    }
    }
  2. 在流程中使用:
    • ParameterCollectActivity 之后,添加一个 MethodActivity,并将其 ExecuterName 设置为 ParseComplexMaterialBarcode
    • 在此 MethodActivity 之后,添加一个 VariableWriteActivity,它可以直接从流程上下文中读取 ParsedVendorCodeParsedBatchNumber 等变量,并将它们分别写入到PLC对应的地址中。

通过 IMethodExecuter,可以将复杂的、与具体业务紧密相关的逻辑封装起来,供流程设计人员以“黑盒”方式调用,极大地提升了流程的可读性和系统的可维护性。


8.3. 二次开发场景实战:采集扩展字段并存入追溯记录

这是一个非常常见的二次开发需求:某个项目需要采集一个标准流程之外的自定义数据(例如,环境温度、设备运行时长等),并将其存入产品的追溯记录中,但又不希望修改任何标准的业务活动(Activity)代码。

目标: 在“过程参数存储”(ParameterSaveActivity)活动 进入时,从PLC读取一个额外的“环境湿度”变量,并将其存入追溯记录的扩展属性(ExtraProperties)中。

方案: 使用 IFlowProcessorParameterSaveActivity 即将进入时进行拦截,完成数据采集和上下文注入。选择 OnEnterAsync 拦截点是最佳实践,因为它确保了所有外部数据在活动的核心逻辑执行之前就已经准备就绪。

步骤1:创建 IFlowProcessor 实现类

首先,创建一个新的C#类,实现IFlowProcessor接口。

using CMS.Plugin.FlowManagement.Abstractions;
using CMS.Plugin.MesSuite.Abstractions.Models;
using Microsoft.Extensions.DependencyInjection;
using SYC.Flow.Kernel;
using Volo.Abp.Data;

namespace MyCompany.MyProject.FlowProcessors;

public class ExtraDataCollectionProcessor : IFlowProcessor
{
// 使用 OnEnterAsync 在活动进入时进行拦截
public async Task OnEnterAsync(FlowProcessor flowProcessor, object sender, EventArgs args)
{
// 1. 判断当前活动是否为“过程参数存储”
if (sender is ParameterSaveActivity saveActivity)
{
var eventArgs = args as ProcessflowEventArgs;
if (eventArgs == null) return;

var context = eventArgs.DataItems;
var serviceProvider = eventArgs.ServiceProvider;

// 2. 从服务容器获取变量服务
var variableService = serviceProvider.GetRequiredService<IFlowVariableService>();

// 3. 读取自定义的PLC变量(假设已在工位配置中绑定)
var humidityVarName = "Custom_Ambient_Humidity"; // 自定义逻辑变量名
var readResult = await variableService.ReadValueAsync(saveActivity, humidityVarName);
var humidityValue = readResult?.Value?.ToString();

// 4. 获取或创建ExtraProperties字典
var extraPropertiesKey = $"{FlowItemCollection.TraceModel}_ExtraProperties";
var extraProperties = context[extraPropertiesKey] as ExtraPropertyDictionary
?? new ExtraPropertyDictionary();

// 5. 将采集到的湿度值添加到字典中
extraProperties["AmbientHumidity"] = humidityValue;

// 6. 将更新后的字典写回流程上下文
context[extraPropertiesKey] = extraProperties;
}
}
}

步骤2:注册拦截器到依赖注入系统

为了让流程引擎能够发现并使用我们创建的ExtraDataCollectionProcessor,需要将其注册到应用的依赖注入(DI)容器中。这通常在项目的模块类(例如 MyProjectModule.cs)的 ConfigureServices 方法中完成。

public override void ConfigureServices(ServiceConfigurationContext context)
{
// ... 其他服务注册

// 将自定义的流程处理器注册为单例
context.Services.AddSingleton<IFlowProcessor, ExtraDataCollectionProcessor>();
}

步骤3:在工位中配置变量绑定

最后一步是让系统知道 Custom_Ambient_Humidity 这个逻辑名称对应哪个真实的PLC地址。

  1. 进入 工艺管理 -> 工艺配置 -> 过程参数,创建一个新的过程参数,例如命名为“环境湿度”。
  2. 进入 工艺管理 -> 工位配置,找到需要应用此逻辑的工位。
  3. 在工位的参数配置中,将刚刚创建的“环境湿度”参数与我们在代码中使用的逻辑名称 Custom_Ambient_Humidity 进行绑定。
  4. 最后,将 Custom_Ambient_Humidity 这个逻辑标识绑定到CMS基座中指向实际PLC湿度地址的变量(Variable)

结论

完成以上三步后,整个流程就实现了无侵入式的逻辑扩展。当任何工位的出站流程运行到“过程参数存储”活动时:

  • 我们的ExtraDataCollectionProcessor会在该活动 进入时 自动被触发。
  • 它会读取额外的湿度传感器数据。
  • 将数据巧妙地“塞”进即将被保存的追溯模型的ExtraProperties中。
  • ParameterSaveActivity 在稍后执行其核心逻辑时,并不知道这个扩展属性的存在,但它会忠实地将包含我们自定义数据在内的整个TraceModel保存到数据库中。

这种方法极大地保证了核心代码的稳定性,同时为二次开发提供了极高的灵活性。