
前言
上一篇我们彻底落地了HAL硬件抽象层架构,完成了软硬件的彻底解耦,解决了工业项目硬件适配难、耦合严重、无法离线开发的核心痛点,搭建起了稳定、可拓展的硬件底层支撑体系。
截至目前,我们的项目已完成UI布局、模块化页面、DI依赖注入、业务服务分层、数据持久化、硬件抽象层的全闭环工程化架构。底层架构全部夯实完毕,正式进入工业自动化上位机的核心灵魂功能开发。
普通上位机只能实现固定逻辑、固定工序的设备控制,而自动化量产上位机的核心竞争力,就是可视化流程引擎。无需修改代码、无需重启程序,现场工程师可自由拖拽工序节点、组合设备动作、调整生产流程,适配不同产品、不同工艺、不同设备的生产需求。
市面上多数教程只会讲解简单的画布拖拽Demo,无法落地工业真实场景。本篇将从零搭建一套工业级可视化流程引擎,包含节点自定义、拖拽吸附、贝塞尔曲线连线、流程数据序列化、运行状态调度、硬件设备联动、急停暂停恢复全套能力,完全适配工业现场柔性生产需求。
本篇核心目标:从零开发可量产的工业可视化流程引擎,实现工艺自由编排、流程可视化、自动化调度、软硬件联动。
一、工业流程引擎的核心价值与行业痛点
在动手编码前,我们先理清工业场景下,流程引擎的核心作用,以及传统硬编码工序的致命弊端:
1. 传统硬编码工序的痛点
-
工艺固化无法修改:生产工序写死在代码中,换产品、换工艺必须改代码、重新编译打包
-
现场适配性极差:现场工艺微调、工序调换无法快速响应,研发迭代成本极高
-
非专业人员无法操作:只有开发者能修改流程,设备工程师、产线人员无法自主适配
-
流程状态不可视:程序运行中无法直观看到当前执行工序、卡顿节点、异常位置,排查问题困难
2. 工业流程引擎的核心价值
-
工艺柔性可配置:可视化拖拽编排工序,适配多产品、多工艺柔性生产
-
零代码修改适配:现场调整流程无需改动源码,无需重新部署程序
-
流程状态实时可视化:运行节点高亮、进度实时展示,故障节点精准定位
-
标准化流程复用:常用工艺流程可保存、加载、复用,大幅提升生产调试效率
-
联动底层硬件架构:流程节点绑定HAL硬件接口,实现工序与设备动作自动联动
二、工业流程引擎整体架构设计
结合工业自动化生产的启动、暂停、停止、急停、断点续跑、异常终止的核心场景,我们设计一套四层工业流程引擎架构,职责清晰、完全解耦、可无限拓展。
1. 数据模型层(FlowModel)
定义流程核心数据结构:流程画布数据、节点实体、连线实体、节点参数、运行状态,支持JSON序列化持久化,实现流程保存与加载。
2. 画布渲染层(FlowCanvas)
基于GDI+实现画布绘制、节点渲染、贝塞尔曲线连线、鼠标拖拽、选中、吸附对齐、层级刷新。
3. 流程调度层(FlowRunner)
核心运行器,负责流程启动、暂停、继续、停止、急停、节点顺序执行、异步调度、超时管控、异常捕获。
4. 节点业务层(FlowNode)
封装各类工业工序节点:设备启动、IO读写、延时等待、条件判断、循环工序、报警提示、数据保存,每个节点绑定对应的硬件与业务能力。
三、流程核心数据模型落地(可序列化)
工业流程必须支持保存本地、重启加载、复用流转,因此所有核心模型必须支持JSON序列化,我们先定义标准化节点、连线、流程数据模型。
1. 流程节点模型(FlowNodeModel)
csharp
using System;
namespace Industrial.Engine.Flow.Model
{
/// <summary>
/// 工业流程工序节点模型
/// </summary>
public class FlowNodeModel
{
/// <summary>
/// 节点唯一ID
/// </summary>
public string NodeId { get; set; } = Guid.NewGuid().ToString("N");
/// <summary>
/// 节点类型(启动/设备动作/延时/判断/结束)
/// </summary>
public string NodeType { get; set; }
/// <summary>
/// 节点展示名称
/// </summary>
public string NodeName { get; set; }
/// <summary>
/// 画布X坐标
/// </summary>
public float X { get; set; }
/// <summary>
/// 画布Y坐标
/// </summary>
public float Y { get; set; }
/// <summary>
/// 节点宽度
/// </summary>
public float Width { get; set; } = 120;
/// <summary>
/// 节点高度
/// </summary>
public float Height { get; set; } = 60;
/// <summary>
/// 节点自定义参数
/// </summary>
public string NodeParam { get; set; }
/// <summary>
/// 节点运行状态
/// </summary>
public string RunState { get; set; } = "Idle";
}
}
2. 流程连线模型(FlowLineModel)
csharp
namespace Industrial.Engine.Flow.Model
{
/// <summary>
/// 节点连线模型
/// </summary>
public class FlowLineModel
{
/// <summary>
/// 连线ID
/// </summary>
public string LineId { get; set; }
/// <summary>
/// 起始节点ID
/// </summary>
public string SourceNodeId { get; set; }
///
public string TargetNodeId { get; set; }
}
}
3. 整体流程画布模型(FlowCanvasModel)
csharp
using System.Collections.Generic;
namespace Industrial.Engine.Flow.Model
{
/// <summary>
/// 流程画布总模型
/// </summary>
public class FlowCanvasModel
{
/// <summary>
/// 流程名称
/// </summary>
public string FlowName { get; set; }
/// public List<FlowNodeModel> NodeList { get; set; } = new List<FlowNodeModel>();
///
public List<FlowLineModel> LineList { get; set; } = new List<FlowLineModel>();
}
}
四、GDI+画布渲染与拖拽交互核心实现
我们基于WinForms PictureBox封装专属流程画布控件,实现节点绘制、贝塞尔曲线连线、鼠标拖拽移动、节点选中、画布刷新全套交互能力。
1. 核心画布控件基础定义
csharp
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using Industrial.Engine.Flow.Model;
namespace Industrial.Engine.Flow.Canvas
{
public partial class FlowCanvasControl : PictureBox
{
// 当前画布流程数据
public FlowCanvasModel CurrentFlow { get; set; } = new FlowCanvasModel();
// 拖拽临时变量
private bool _isDragging = false;
private Point _dragStartPoint;
private FlowNodeModel _dragNode = null;
public FlowCanvasControl()
{
// 开启双缓冲,防止闪烁
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
UpdateStyles();
}
// 重绘画布
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
// 绘制连线
DrawLines(g);
// 绘制节点
DrawNodes(g);
}
}
}
2. 节点与连线绘制核心方法
csharp
// 绘制所有节点
private void DrawNodes(Graphics g)
{
foreach (var node in CurrentFlow.NodeList)
{
// 根据运行状态切换颜色
Brush backBrush = node.RunState switch
{
"Running" => Brushes.LightGreen,
"Finish" => Brushes.Green,
"Error" => Brushes.OrangeRed,
_ => Brushes.LightGray
};
// 绘制节点矩形
g.FillRectangle(backBrush, node.X, node.Y, node.Width, node.Height);
g.DrawRectangle(Pens.Black, node.X, node.Y, node.Width, node.Height);
// 绘制节点文字
g.DrawString(node.NodeName, SystemFonts.DefaultFont, Brushes.Black,
node.X + 10, node.Y + 15);
}
}
// 绘制贝塞尔曲线连线
private void DrawLines(Graphics g)
{
foreach (var line in CurrentFlow.LineList)
{
var sourceNode = CurrentFlow.NodeList.Find(x => x.NodeId == line.SourceNodeId);
var targetNode = CurrentFlow.NodeList.Find(x => x.NodeId == line.TargetNodeId);
if (sourceNode == null || targetNode == null) continue;
// 起点、终点坐标
PointF startPoint = new PointF(sourceNode.X + sourceNode.Width, sourceNode.Y + sourceNode.Height / 2);
PointF endPoint = new PointF(targetNode.X, targetNode.Y + targetNode.Height / 2);
// 贝塞尔曲线控制点
PointF control1 = new PointF(startPoint.X + 60, startPoint.Y);
PointF control2 = new PointF(endPoint.X - 60, endPoint.Y);
GraphicsPath path = new GraphicsPath();
path.AddBezier(startPoint, control1, control2, endPoint);
g.DrawPath(Pens.DodgerBlue, path);
}
}
3. 节点拖拽交互实现
csharp
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
// 命中节点检测
foreach (var node in CurrentFlow.NodeList)
{
if (e.X > node.X && e.X < node.X + node.Width &&
e.Y > node.Y && e.Y < node.Y + node.Height)
{
_isDragging = true;
_dragNode = node;
_dragStartPoint = e.Location;
break;
}
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (!_isDragging || _dragNode == null) return;
// 计算偏移量
float offsetX = e.X - _dragStartPoint.X;
float offsetY = e.Y - _dragStartPoint.Y;
// 更新节点坐标
_dragNode.X += offsetX;
_dragNode.Y += offsetY;
_dragStartPoint = e.Location;
// 实时重绘
Invalidate();
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
_isDragging = false;
_dragNode = null;
}
五、流程运行调度器核心实现(自动执行)
画布可视化编辑只是基础,自动调度执行、硬件联动、状态管控才是流程引擎的工业核心。我们封装FlowRunner运行器,实现流程启停、顺序执行、状态刷新、异常终止。
csharp
using System;
using System.Linq;
using System.Threading.Tasks;
using Industrial.Engine.Flow.Model;
using Industrial.HAL.Manager;
using Industrial.Core.DependencyInjection;
namespace Industrial.Engine.Flow.Runner
{
/// <summary>
/// 工业流程运行调度器
/// 负责流程启停、节点执行、状态调度、硬件联动
/// </summary>
public class FlowRunner
{
private readonly HardwareManager _hardwareManager;
private bool _isRunning = false;
private bool _isPause = false;
public FlowRunner()
{
// DI获取硬件管理服务
_hardwareManager = ServiceLocator.GetService<HardwareManager>();
}
/// <summary>
/// 启动流程
/// </summary>
public async Task StartFlow(FlowCanvasModel flowModel)
{
if (_isRunning) return;
_isRunning = true;
_isPause = false;
// 重置所有节点状态
flowModel.NodeList.ForEach(x => x.RunState = "Idle");
// 查找起始节点,开始顺序执行
var startNode = flowModel.NodeList.FirstOrDefault(x => x.NodeType == "Start");
if (startNode == null) return;
await ExecuteNode(flowModel, startNode);
}
/// <summary>
/// 执行单个节点
/// </summary>
private async Task ExecuteNode(FlowCanvasModel flow, FlowNodeModel node)
{
if (!_isRunning || _isPause) return;
// 更新运行状态
node.RunState = "Running";
// 根据节点类型执行对应工业逻辑
switch (node.NodeType)
{
case "PlcWrite":
ExcutePlcWrite(node);
break;
case "Delay":
int delay = int.Parse(node.NodeParam);
await Task.Delay(delay);
break;
case "End":
node.RunState = "Finish";
_isRunning = false;
return;
}
node.RunState = "Finish";
// 查找下一个节点,递归执行
var nextLine = flow.LineList.FirstOrDefault(x => x.SourceNodeId == node.NodeId);
if (nextLine == null)
{
_isRunning = false;
return;
}
var nextNode = flow.NodeList.FirstOrDefault(x => x.NodeId == nextLine.TargetNodeId);
if (nextNode != null)
await ExecuteNode(flow, nextNode);
}
/// <summary>
/// 执行PLC写入节点逻辑
/// </summary>
private void ExcutePlcWrite(FlowNodeModel node)
{
// 解析节点自定义参数,调用底层HAL硬件服务
var plc = _hardwareManager.InitPlcDevice("PLC001", "主站PLC");
if (plc.IsOnline)
{
plc.WriteBit("M100", true);
}
}
/// <summary>
/// 暂停流程
/// </summary>
public void PauseFlow() => _isPause = true;
/// <summary>
/// 继续流程
/// </summary>
public void ResumeFlow() => _isPause = false;
/// <summary>
/// 停止流程
/// </summary>
public void StopFlow()
{
_isRunning = false;
_isPause = false;
}
}
}
六、流程持久化:保存与加载功能实现
工业工艺需要长期复用,因此我们实现流程JSON序列化保存、本地文件加载功能,支持工艺配方留存与一键调用。
csharp
using System.IO;
using Newtonsoft.Json;
using Industrial.Engine.Flow.Model;
using Industrial.Core.Utils;
namespace Industrial.Engine.Flow.Helper
{
public static class FlowFileHelper
{
/// <summary>
/// 保存流程到本地文件
/// </summary>
public static void SaveFlow(FlowCanvasModel flow, string fileName)
{
string path = Path.Combine(PathHelper.FlowPath, $"{fileName}.json");
string json = JsonConvert.SerializeObject(flow, Formatting.Indented);
File.WriteAllText(path, json);
}
/// <summary>
/// 加载本地流程文件
/// </summary>
public static FlowCanvasModel LoadFlow(string fileName)
{
string path = Path.Combine(PathHelper.FlowPath, $"{fileName}.json");
if (!File.Exists(path)) return new FlowCanvasModel();
string json = File.ReadAllText(path);
return JsonConvert.DeserializeObject<FlowCanvasModel>(json);
}
}
}
七、DI注册引擎服务与页面实战调用
将流程运行器纳入DI全局托管,保证引擎全局唯一、状态统一,在UI页面实现流程编辑、运行、暂停、停止的完整操作。
1. 注册流程引擎服务
csharp
// AppBootstrap 新增注册逻辑
private static void RegisterEngineServices(ServiceCollection services)
{
// 流程运行器全局单例
services.AddSingleton<FlowRunner>();
}
2. 页面实战调用
csharp
// 流程编辑页面调用示例
private readonly FlowRunner _flowRunner;
public FlowEditView()
{
_flowRunner = ServiceLocator.GetService<FlowRunner>();
InitializeComponent();
}
// 启动流程按钮
private void btnStart_Click(object sender, EventArgs e)
{
_ = _flowRunner.StartFlow(flowCanvasControl1.CurrentFlow);
}
// 暂停流程按钮
private void btnPause_Click(object sender, EventArgs e)
{
_flowRunner.PauseFlow();
}
// 停止流程按钮
private void btnStop_Click(object sender, EventArgs e)
{
_flowRunner.StopFlow();
}
八、工业流程引擎核心能力拓展与规范
1. 标准节点拓展规范
后续新增工艺节点无需改动引擎核心代码,只需新增节点类型、编写对应执行逻辑,完全遵循开闭原则,可拓展:相机拍照、传感器检测、数据保存、条件跳转、循环工序、报警弹窗等节点。
2. 工业级运行容错机制
-
硬件异常终止:节点执行时检测硬件离线,自动终止流程并弹窗报警
-
超时容错:为每个节点配置执行超时,卡死自动跳过或报错终止
-
断点续跑:暂停停止后,可从当前节点继续执行,无需从头跑流程
-
状态实时同步:节点运行状态实时刷新,界面可视化展示工序进度
3. 流程执行核心优势
完美承接前文HAL硬件架构,流程层只调用抽象硬件接口,不依赖具体设备,真机/仿真模式一键切换,编辑调试完全不受硬件限制。
九、本篇总结
可视化流程引擎是工业自动化上位机的核心灵魂模块,也是区分"固定程序"和"柔性自动化平台"的关键分水岭。
本篇我们从零落地了完整的工业级流程引擎,实现了节点可视化拖拽、贝塞尔曲线连线、流程数据持久化、自动调度执行、硬件设备联动、启停暂停管控全套核心能力。
结合前面的底层架构,项目正式具备了工艺自由编排、柔性生产适配、可视化自动化运行的工业级能力,彻底摆脱硬编码工序的局限,完全适配现场多变的生产工艺需求。
下篇预告
下一篇我们进入第十篇Lua脚本引擎集成实战 !实现C#与Lua双向交互、在线脚本编辑、断点调试、动态更新工序逻辑,实现真正意义上的无需重启、无需编译、动态更新业务逻辑,进一步拉满项目的柔性拓展能力。