生产作业计划协同指令调度组件
region 核心枚举定义(MES标准)/// 产线轴类型(主轴/支轴)/// 主轴(核心产线)MainAxis,/// 支轴(配套工位)BranchAxis/// 作业计划状态/// 待执行Pending,/// 执行中Running,/// 暂停Paused,/// 已完成Completed,/// 异常Exception,/// 已取消Cancelled/// 事件类型// 作业计划事件//
欢迎各位观众大大浏览阅读我的博客,有空麻烦加一下博客主页关注,谢谢
MES 生产作业计划协同 WinForm 中间件组件(主支轴事件委托 + 多指令调度)
你需要开发一套面向 MES 生产作业计划协同的 WinForm 中间件,核心基于产线工站流程的主支轴事件委托封装设计模式,实现多指令端 / 应用的调度协同,让主产线(主轴)与分支工位(支轴)的作业计划通过事件委托机制完成联动调度,确保生产流程的时序一致性和指令协同性。
以下是完整的工业级解决方案,包含主支轴事件委托核心架构、多指令调度中心、可视化作业计划协同界面,兼顾 MES 系统的实时性、可靠性和可扩展性。
一、核心设计理念与架构
1. 主支轴事件委托设计
MES 产线作业流程中,主轴代表核心产线节拍(如总装线),支轴代表配套工位(如预装工位、检测工位),通过事件委托实现:
- 主轴事件触发支轴联动(如主轴到料 → 支轴开始预装)
- 支轴状态回调主轴(如支轴完成 → 主轴允许流转)
- 异常事件全局广播(如支轴缺料 → 主轴暂停 + 告警)
委托注册 事件触发 状态回调 指令封装 指令分发 主轴控制器 主轴事件中心 支轴控制器1 支轴控制器2 支轴控制器N 支轴1执行作业 支轴2执行作业支轴N执行作业 多指令调度中心 MES数据库 产线执行端 告警中心
graph TD
A[主轴控制器] -->|委托注册| B[主轴事件中心]
C[支轴控制器1] -->|委托注册| B
D[支轴控制器2] -->|委托注册| B
E[支轴控制器N] -->|委托注册| B
B -->|事件触发| C1[支轴1执行作业]
B -->|事件触发| D1[支轴2执行作业]
B -->|事件触发| E1[支轴N执行作业]
C1 -->|状态回调| B
D1 -->|状态回调| B
E1 -->|状态回调| B
B -->|指令封装| F[多指令调度中心]
F -->|指令分发| G[MES数据库]
F -->|指令分发| H[产线执行端]
F -->|指令分发| I[告警中心]
委托注册 事件触发 状态回调 指令封装 指令分发 主轴控制器 主轴事件中心 支轴控制器1 支轴控制器2 支轴控制器N 支轴1执行作业 支轴2执行作业 支轴N执行作业 多指令调度中心 MES数据库 产线执行端 告警中心
2. 核心事件类型(主支轴协同)
| 事件分类 | 主轴事件 | 支轴事件 | 全局事件 |
|---|---|---|---|
| 作业计划事件 | 主轴计划启动 / 暂停 / 完成 / 取消 | 支轴计划绑定 / 启动 / 完成 / 异常 | 计划同步 / 版本更新 / 冲突检测 |
| 物料协同事件 | 主轴物料齐套 / 缺料 / 补料 | 支轴物料需求 / 齐套确认 / 消耗上报 | 物料调拨指令 / 库存告警 |
| 流程控制事件 | 主轴节拍调整 / 工序跳转 / 批次切换 | 支轴工序就绪 / 等待 / 流转 | 产线启停 / 模式切换 / 紧急停机 |
| 异常处理事件 | 主轴设备异常 / 质量异常 | 支轴作业异常 / 人员异常 | 异常上报 / 自动恢复 / 人工干预 |
3. 多指令端调度规则
| 指令端类型 | 接收事件 | 输出指令 | 委托回调机制 |
|---|---|---|---|
| 主轴控制端 | 支轴完成 / 异常 / 物料齐套 | 主轴流转 / 暂停 / 节拍调整 | 注册支轴状态回调委托 |
| 支轴执行端 | 主轴启动 / 节拍调整 / 暂停 | 支轴作业启动 / 调整 / 等待 | 注册主轴指令委托 |
| MES 计划端 | 主支轴状态变更 | 计划更新 / 进度同步 / 报表 | 注册全局状态变更委托 |
| 物料调拨端 | 支轴物料需求 / 主轴缺料 | 调拨指令 / 补料通知 | 注册物料需求委托 |
| 异常告警端 | 主支轴异常事件 | 告警指令 / 派工通知 | 注册异常事件委托 |
二、核心代码实现
1. 主支轴事件委托核心定义
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using Dapper;
using Newtonsoft.Json;
#region 核心枚举定义(MES标准)
/// <summary>
/// 产线轴类型(主轴/支轴)
/// </summary>
public enum AxisType
{
/// <summary>
/// 主轴(核心产线)
/// </summary>
MainAxis,
/// <summary>
/// 支轴(配套工位)
/// </summary>
BranchAxis
}
/// <summary>
/// 作业计划状态
/// </summary>
public enum JobPlanStatus
{
/// <summary>
/// 待执行
/// </summary>
Pending,
/// <summary>
/// 执行中
/// </summary>
Running,
/// <summary>
/// 暂停
/// </summary>
Paused,
/// <summary>
/// 已完成
/// </summary>
Completed,
/// <summary>
/// 异常
/// </summary>
Exception,
/// <summary>
/// 已取消
/// </summary>
Cancelled
}
/// <summary>
/// 事件类型
/// </summary>
public enum MesEventType
{
// 作业计划事件
JobPlanStart,
JobPlanPause,
JobPlanComplete,
JobPlanCancel,
JobPlanException,
// 物料协同事件
MaterialReady,
MaterialShortage,
MaterialReplenish,
// 流程控制事件
BeatAdjust,
ProcessJump,
BatchSwitch,
// 异常处理事件
EquipmentException,
QualityException,
PersonnelException
}
/// <summary>
/// 指令执行状态
/// </summary>
public enum CommandStatus
{
Pending,
Executing,
Completed,
Failed,
Cancelled
}
#endregion
#region 主支轴核心模型
/// <summary>
/// 产线轴基础信息(主轴/支轴)
/// </summary>
public class ProductionAxis
{
/// <summary>
/// 轴编码(主轴:MA-001,支轴:BA-001-01)
/// </summary>
public string AxisCode { get; set; }
/// <summary>
/// 轴名称
/// </summary>
public string AxisName { get; set; }
/// <summary>
/// 产线编码
/// </summary>
public string LineCode { get; set; }
/// <summary>
/// 轴类型(主轴/支轴)
/// </summary>
public AxisType AxisType { get; set; }
/// <summary>
/// 父轴编码(支轴关联的主轴)
/// </summary>
public string ParentAxisCode { get; set; }
/// <summary>
/// 工位编码列表(逗号分隔)
/// </summary>
public string StationCodes { get; set; }
/// <summary>
/// 当前绑定的作业计划编号
/// </summary>
public string BindJobPlanNo { get; set; }
/// <summary>
/// 轴状态
/// </summary>
public JobPlanStatus CurrentStatus { get; set; } = JobPlanStatus.Pending;
/// <summary>
/// 节拍(秒/件)
/// </summary>
public int Beat { get; set; } = 60;
/// <summary>
/// 排序号
/// </summary>
public int SortNo { get; set; }
}
/// <summary>
/// MES生产作业计划(主支轴协同版)
/// </summary>
public class MesCoopJobPlan
{
/// <summary>
/// 计划编号
/// </summary>
public string JobPlanNo { get; set; }
/// <summary>
/// 计划名称
/// </summary>
public string JobPlanName { get; set; }
/// <summary>
/// 产线编码
/// </summary>
public string LineCode { get; set; }
/// <summary>
/// 主轴编码
/// </summary>
public string MainAxisCode { get; set; }
/// <summary>
/// 产品编码
/// </summary>
public string ProductCode { get; set; }
/// <summary>
/// 产品名称
/// </summary>
public string ProductName { get; set; }
/// <summary>
/// 计划产量
/// </summary>
public int PlanQty { get; set; }
/// <summary>
/// 已完成产量
/// </summary>
public int CompletedQty { get; set; }
/// <summary>
/// 计划开始时间
/// </summary>
public DateTime PlanStartTime { get; set; }
/// <summary>
/// 计划结束时间
/// </summary>
public DateTime PlanEndTime { get; set; }
/// <summary>
/// 实际开始时间
/// </summary>
public DateTime? ActualStartTime { get; set; }
/// <summary>
/// 实际结束时间
/// </summary>
public DateTime? ActualEndTime { get; set; }
/// <summary>
/// 计划状态
/// </summary>
public JobPlanStatus PlanStatus { get; set; } = JobPlanStatus.Pending;
/// <summary>
/// 关联的支轴计划(JSON)
/// </summary>
public string BranchPlanJson { get; set; }
/// <summary>
/// 创建人
/// </summary>
public string Creator { get; set; }
/// <summary>
/// 版本号
/// </summary>
public int Version { get; set; } = 1;
/// <summary>
/// 支轴计划列表(反序列化)
/// </summary>
[JsonIgnore]
public List<MesBranchJobPlan> BranchPlans
{
get => string.IsNullOrEmpty(BranchPlanJson)
? new List<MesBranchJobPlan>()
: JsonConvert.DeserializeObject<List<MesBranchJobPlan>>(BranchPlanJson);
set => BranchPlanJson = JsonConvert.SerializeObject(value);
}
}
/// <summary>
/// 支轴作业计划
/// </summary>
public class MesBranchJobPlan
{
/// <summary>
/// 支轴编码
/// </summary>
public string BranchAxisCode { get; set; }
/// <summary>
/// 支轴计划编号
/// </summary>
public string BranchPlanNo { get; set; }
/// <summary>
/// 作业内容
/// </summary>
public string JobContent { get; set; }
/// <summary>
/// 计划产量
/// </summary>
public int PlanQty { get; set; }
/// <summary>
/// 已完成产量
/// </summary>
public int CompletedQty { get; set; }
/// <summary>
/// 计划开始时间(相对主轴偏移秒数)
/// </summary>
public int OffsetSeconds { get; set; }
/// <summary>
/// 状态
/// </summary>
public JobPlanStatus Status { get; set; } = JobPlanStatus.Pending;
}
/// <summary>
/// MES事件参数(主支轴协同)
/// </summary>
public class MesAxisEventArgs : EventArgs
{
/// <summary>
/// 事件ID
/// </summary>
public string EventId { get; set; } = Guid.NewGuid().ToString("N");
/// <summary>
/// 事件类型
/// </summary>
public MesEventType EventType { get; set; }
/// <summary>
/// 触发轴编码
/// </summary>
public string TriggerAxisCode { get; set; }
/// <summary>
/// 目标轴编码(空表示全局)
/// </summary>
public string TargetAxisCode { get; set; }
/// <summary>
/// 作业计划编号
/// </summary>
public string JobPlanNo { get; set; }
/// <summary>
/// 事件内容(JSON)
/// </summary>
public string EventContent { get; set; }
/// <summary>
/// 事件触发时间
/// </summary>
public DateTime TriggerTime { get; set; } = DateTime.Now;
/// <summary>
/// 处理状态
/// </summary>
public bool IsHandled { get; set; } = false;
/// <summary>
/// 处理结果
/// </summary>
public string HandleResult { get; set; }
}
/// <summary>
/// 多指令调度模型
/// </summary>
public class MesDispatchCommand
{
/// <summary>
/// 指令ID
/// </summary>
public string CommandId { get; set; } = Guid.NewGuid().ToString("N");
/// <summary>
/// 关联事件ID
/// </summary>
public string EventId { get; set; }
/// <summary>
/// 指令来源端
/// </summary>
public string SourceTerminal { get; set; }
/// <summary>
/// 指令目标端
/// </summary>
public string TargetTerminal { get; set; }
/// <summary>
/// 指令类型
/// </summary>
public string CommandType { get; set; }
/// <summary>
/// 轴编码
/// </summary>
public string AxisCode { get; set; }
/// <summary>
/// 产线编码
/// </summary>
public string LineCode { get; set; }
/// <summary>
/// 指令内容(JSON)
/// </summary>
public string CommandContent { get; set; }
/// <summary>
/// 指令状态
/// </summary>
public CommandStatus CommandStatus { get; set; } = CommandStatus.Pending;
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; } = DateTime.Now;
/// <summary>
/// 执行时间
/// </summary>
public DateTime? ExecuteTime { get; set; }
/// <summary>
/// 失败原因
/// </summary>
public string FailReason { get; set; }
}
#endregion
#region 主支轴事件委托核心封装
/// <summary>
/// 主支轴事件委托定义
/// </summary>
/// <param name="sender">触发源</param>
/// <param name="e">事件参数</param>
public delegate void MesAxisEventHandler(object sender, MesAxisEventArgs e);
/// <summary>
/// MES主支轴事件中心(单例模式)
/// 核心封装事件委托的注册、触发、注销逻辑
/// </summary>
public sealed class MesAxisEventCenter
{
// 单例实例
private static readonly Lazy<MesAxisEventCenter> _instance = new Lazy<MesAxisEventCenter>(() => new MesAxisEventCenter());
public static MesAxisEventCenter Instance => _instance.Value;
// 事件委托注册表:键=事件类型,值=委托列表
private readonly Dictionary<MesEventType, List<MesAxisEventHandler>> _eventHandlers = new Dictionary<MesEventType, List<MesAxisEventHandler>>();
// 轴委托映射:键=轴编码,值=关联的事件类型列表
private readonly Dictionary<string, List<MesEventType>> _axisEventMap = new Dictionary<string, List<MesEventType>>();
// 线程安全锁
private readonly object _lockObj = new object();
// 私有构造函数
private MesAxisEventCenter()
{
// 初始化所有事件类型的委托列表
foreach (MesEventType eventType in Enum.GetValues(typeof(MesEventType)))
{
_eventHandlers[eventType] = new List<MesAxisEventHandler>();
}
}
#region 委托注册
/// <summary>
/// 注册事件委托(全局)
/// </summary>
/// <param name="eventType">事件类型</param>
/// <param name="handler">事件委托</param>
public void RegisterHandler(MesEventType eventType, MesAxisEventHandler handler)
{
lock (_lockObj)
{
if (!_eventHandlers[eventType].Contains(handler))
{
_eventHandlers[eventType].Add(handler);
MesLog.Info($"全局事件委托注册成功:{eventType}", "事件中心");
}
}
}
/// <summary>
/// 注册轴专属事件委托
/// </summary>
/// <param name="axisCode">轴编码</param>
/// <param name="eventType">事件类型</param>
/// <param name="handler">事件委托</param>
public void RegisterAxisHandler(string axisCode, MesEventType eventType, MesAxisEventHandler handler)
{
lock (_lockObj)
{
// 注册委托
if (!_eventHandlers[eventType].Contains(handler))
{
_eventHandlers[eventType].Add(handler);
}
// 维护轴-事件映射
if (!_axisEventMap.ContainsKey(axisCode))
{
_axisEventMap[axisCode] = new List<MesEventType>();
}
if (!_axisEventMap[axisCode].Contains(eventType))
{
_axisEventMap[axisCode].Add(eventType);
}
MesLog.Info($"轴{axisCode}事件委托注册成功:{eventType}", "事件中心");
}
}
#endregion
#region 事件触发
/// <summary>
/// 触发事件(核心方法)
/// </summary>
/// <param name="e">事件参数</param>
/// <param name="sender">触发源</param>
public void TriggerEvent(MesAxisEventArgs e, object sender = null)
{
if (e == null) return;
lock (_lockObj)
{
MesLog.Info($"触发事件:{e.EventType} | 触发轴:{e.TriggerAxisCode} | 目标轴:{e.TargetAxisCode}", "事件中心");
// 1. 记录事件日志
SaveEventLog(e);
// 2. 获取需要执行的委托列表
var handlers = new List<MesAxisEventHandler>(_eventHandlers[e.EventType]);
// 3. 异步执行所有委托(避免阻塞主线程)
foreach (var handler in handlers)
{
try
{
// 异步执行委托
handler.BeginInvoke(sender, e, ar =>
{
try
{
handler.EndInvoke(ar);
e.IsHandled = true;
e.HandleResult = "处理成功";
MesLog.Info($"事件{e.EventId}委托执行成功", "事件中心");
}
catch (Exception ex)
{
e.IsHandled = true;
e.HandleResult = $"处理失败:{ex.Message}";
MesLog.Error($"事件{e.EventId}委托执行失败", ex, "事件中心");
}
}, null);
}
catch (Exception ex)
{
MesLog.Error($"事件{e.EventId}委托调用异常", ex, "事件中心");
}
}
// 4. 事件触发后自动生成调度指令
GenerateDispatchCommand(e);
}
}
/// <summary>
/// 触发轴专属事件
/// </summary>
/// <param name="axisCode">轴编码</param>
/// <param name="e">事件参数</param>
/// <param name="sender">触发源</param>
public void TriggerAxisEvent(string axisCode, MesAxisEventArgs e, object sender = null)
{
if (!_axisEventMap.ContainsKey(axisCode))
{
MesLog.Warn($"轴{axisCode}无注册的事件委托", "事件中心");
return;
}
// 仅触发该轴注册的事件类型
if (_axisEventMap[axisCode].Contains(e.EventType))
{
TriggerEvent(e, sender);
}
}
#endregion
#region 委托注销
/// <summary>
/// 注销事件委托
/// </summary>
/// <param name="eventType">事件类型</param>
/// <param name="handler">事件委托</param>
public void UnregisterHandler(MesEventType eventType, MesAxisEventHandler handler)
{
lock (_lockObj)
{
if (_eventHandlers[eventType].Contains(handler))
{
_eventHandlers[eventType].Remove(handler);
MesLog.Info($"事件委托注销成功:{eventType}", "事件中心");
}
}
}
/// <summary>
/// 注销轴所有事件委托
/// </summary>
/// <param name="axisCode">轴编码</param>
public void UnregisterAxisAllHandlers(string axisCode)
{
lock (_lockObj)
{
if (!_axisEventMap.ContainsKey(axisCode)) return;
// 移除该轴关联的所有委托
foreach (var eventType in _axisEventMap[axisCode])
{
// 此处可根据实际需求优化:仅移除该轴注册的委托
// 简化实现:清空该事件类型的所有委托(生产环境需精准匹配)
_eventHandlers[eventType].Clear();
}
// 移除轴-事件映射
_axisEventMap.Remove(axisCode);
MesLog.Info($"轴{axisCode}所有事件委托已注销", "事件中心");
}
}
#endregion
#region 辅助方法
/// <summary>
/// 保存事件日志
/// </summary>
private void SaveEventLog(MesAxisEventArgs e)
{
try
{
using var conn = new SqlConnection(MesConfig.GetConnStr());
conn.Execute(
"INSERT INTO MesAxisEventLog (EventId, EventType, TriggerAxisCode, TargetAxisCode, " +
"JobPlanNo, EventContent, TriggerTime, IsHandled, HandleResult) " +
"VALUES (@EventId, @EventType, @TriggerAxisCode, @TargetAxisCode, " +
"@JobPlanNo, @EventContent, @TriggerTime, @IsHandled, @HandleResult)",
new
{
e.EventId,
EventType = e.EventType.ToString(),
e.TriggerAxisCode,
e.TargetAxisCode,
e.JobPlanNo,
e.EventContent,
e.TriggerTime,
e.IsHandled,
e.HandleResult
});
}
catch (Exception ex)
{
MesLog.Error($"保存事件日志失败", ex, "事件中心");
}
}
/// <summary>
/// 根据事件生成调度指令
/// </summary>
private void GenerateDispatchCommand(MesAxisEventArgs e)
{
try
{
// 获取轴信息
var axis = GetAxisInfo(e.TriggerAxisCode);
if (axis == null) return;
// 构建调度指令
var command = new MesDispatchCommand
{
EventId = e.EventId,
SourceTerminal = GetSourceTerminal(e.EventType, axis.AxisType),
TargetTerminal = GetTargetTerminal(e.EventType, axis.AxisType),
CommandType = e.EventType.ToString(),
AxisCode = e.TriggerAxisCode,
LineCode = axis.LineCode,
CommandContent = e.EventContent
};
// 保存并执行指令
var dispatchService = new MesDispatchService();
dispatchService.ExecuteCommand(command);
MesLog.Info($"事件{e.EventId}生成调度指令:{command.CommandId}", "事件中心");
}
catch (Exception ex)
{
MesLog.Error($"生成调度指令失败", ex, "事件中心");
}
}
/// <summary>
/// 获取事件来源端
/// </summary>
private string GetSourceTerminal(MesEventType eventType, AxisType axisType)
{
return axisType == AxisType.MainAxis ? "主轴控制端" : "支轴执行端";
}
/// <summary>
/// 获取事件目标端
/// </summary>
private string GetTargetTerminal(MesEventType eventType, AxisType axisType)
{
return eventType switch
{
MesEventType.MaterialReady or MesEventType.MaterialShortage or MesEventType.MaterialReplenish => "物料调拨端",
MesEventType.EquipmentException or MesEventType.QualityException or MesEventType.PersonnelException => "异常告警端",
_ => axisType == AxisType.MainAxis ? "支轴执行端" : "主轴控制端"
};
}
/// <summary>
/// 获取轴信息
/// </summary>
private ProductionAxis GetAxisInfo(string axisCode)
{
using var conn = new SqlConnection(MesConfig.GetConnStr());
return conn.QuerySingleOrDefault<ProductionAxis>(
"SELECT AxisCode, AxisName, LineCode, AxisType, ParentAxisCode, StationCodes, " +
"BindJobPlanNo, CurrentStatus, Beat, SortNo FROM ProductionAxis WHERE AxisCode = @AxisCode",
new { AxisCode = axisCode });
}
#endregion
}
#endregion
#region 辅助类
/// <summary>
/// MES配置助手
/// </summary>
public static class MesConfig
{
/// <summary>
/// MES数据库连接字符串
/// </summary>
public static string GetConnStr()
{
return "Data Source=.;Initial Catalog=MES_Cooperation;Integrated Security=True;";
}
}
/// <summary>
/// MES日志助手
/// </summary>
public static class MesLog
{
public static void Info(string msg, string module = "通用")
{
WriteLog("INFO", module, msg);
}
public static void Warn(string msg, string module = "通用")
{
WriteLog("WARN", module, msg);
}
public static void Error(string msg, Exception ex = null, string module = "通用")
{
var errMsg = ex == null ? msg : $"{msg} | 异常:{ex.Message}\n{ex.StackTrace}";
WriteLog("ERROR", module, errMsg);
}
private static void WriteLog(string level, string module, string msg)
{
var log = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] [{module}] {msg}";
Console.WriteLine(log);
// 写入数据库日志
try
{
using var conn = new SqlConnection(MesConfig.GetConnStr());
conn.Execute(
"INSERT INTO MesSystemLog (LogTime, Level, Module, Content) " +
"VALUES (@LogTime, @Level, @Module, @Content)",
new { LogTime = DateTime.Now, Level = level, Module = module, Content = msg });
}
catch
{
// 日志写入失败不影响主流程
}
}
}
#endregion
2. 多指令调度服务实现
/// <summary>
/// MES调度服务(多指令端核心)
/// </summary>
public class MesDispatchService
{
private readonly string _connStr;
public MesDispatchService()
{
_connStr = MesConfig.GetConnStr();
}
/// <summary>
/// 执行调度指令
/// </summary>
/// <param name="command">指令对象</param>
/// <returns>执行结果</returns>
public bool ExecuteCommand(MesDispatchCommand command)
{
// 1. 保存指令初始状态
SaveCommand(command);
try
{
// 2. 更新指令状态为执行中
command.CommandStatus = CommandStatus.Executing;
command.ExecuteTime = DateTime.Now;
UpdateCommandStatus(command);
// 3. 根据指令类型执行业务逻辑
var result = ExecuteCommandLogic(command);
// 4. 更新指令最终状态
command.CommandStatus = result ? CommandStatus.Completed : CommandStatus.Failed;
command.FailReason = result ? "" : "业务逻辑执行失败";
UpdateCommandStatus(command);
MesLog.Info($"指令{command.CommandId}执行{(result ? "成功" : "失败")}", "调度服务");
return result;
}
catch (Exception ex)
{
// 5. 异常处理
command.CommandStatus = CommandStatus.Failed;
command.FailReason = ex.Message;
UpdateCommandStatus(command);
MesLog.Error($"指令{command.CommandId}执行异常", ex, "调度服务");
return false;
}
}
/// <summary>
/// 执行指令业务逻辑
/// </summary>
private bool ExecuteCommandLogic(MesDispatchCommand command)
{
switch (command.CommandType)
{
// 作业计划指令
case nameof(MesEventType.JobPlanStart):
return ExecuteJobPlanStart(command);
case nameof(MesEventType.JobPlanPause):
return ExecuteJobPlanPause(command);
case nameof(MesEventType.JobPlanComplete):
return ExecuteJobPlanComplete(command);
// 物料协同指令
case nameof(MesEventType.MaterialReady):
return ExecuteMaterialReady(command);
case nameof(MesEventType.MaterialShortage):
return ExecuteMaterialShortage(command);
// 异常处理指令
case nameof(MesEventType.EquipmentException):
return ExecuteEquipmentException(command);
// 其他指令类型
default:
MesLog.Warn($"未实现的指令类型:{command.CommandType}", "调度服务");
return true; // 未知指令默认视为成功
}
}
#region 指令业务逻辑实现
/// <summary>
/// 执行作业计划启动指令
/// </summary>
private bool ExecuteJobPlanStart(MesDispatchCommand command)
{
var content = JsonConvert.DeserializeObject<JobPlanStartContent>(command.CommandContent);
if (content == null) return false;
using var conn = new SqlConnection(_connStr);
using var tran = conn.BeginTransaction();
try
{
// 更新作业计划状态
conn.Execute(
"UPDATE MesCoopJobPlan SET PlanStatus = @Status, ActualStartTime = @Now " +
"WHERE JobPlanNo = @JobPlanNo",
new { Status = JobPlanStatus.Running, Now = DateTime.Now, JobPlanNo = content.JobPlanNo }, tran);
// 更新轴状态
conn.Execute(
"UPDATE ProductionAxis SET CurrentStatus = @Status, BindJobPlanNo = @JobPlanNo " +
"WHERE AxisCode = @AxisCode",
new { Status = JobPlanStatus.Running, JobPlanNo = content.JobPlanNo, AxisCode = command.AxisCode }, tran);
// 如果是主轴启动,自动触发支轴启动
if (IsMainAxis(command.AxisCode))
{
TriggerBranchAxisStart(command, tran);
}
tran.Commit();
return true;
}
catch
{
tran.Rollback();
return false;
}
}
/// <summary>
/// 执行作业计划暂停指令
/// </summary>
private bool ExecuteJobPlanPause(MesDispatchCommand command)
{
var content = JsonConvert.DeserializeObject<JobPlanPauseContent>(command.CommandContent);
if (content == null) return false;
using var conn = new SqlConnection(_connStr);
// 更新计划状态
conn.Execute(
"UPDATE MesCoopJobPlan SET PlanStatus = @Status WHERE JobPlanNo = @JobPlanNo",
new { Status = JobPlanStatus.Paused, JobPlanNo = content.JobPlanNo });
// 更新轴状态
conn.Execute(
"UPDATE ProductionAxis SET CurrentStatus = @Status WHERE AxisCode = @AxisCode",
new { Status = JobPlanStatus.Paused, AxisCode = command.AxisCode });
return true;
}
/// <summary>
/// 执行作业计划完成指令
/// </summary>
private bool ExecuteJobPlanComplete(MesDispatchCommand command)
{
var content = JsonConvert.DeserializeObject<JobPlanCompleteContent>(command.CommandContent);
if (content == null) return false;
using var conn = new SqlConnection(_connStr);
// 更新计划状态和完成产量
conn.Execute(
"UPDATE MesCoopJobPlan SET PlanStatus = @Status, CompletedQty = @CompletedQty, ActualEndTime = @Now " +
"WHERE JobPlanNo = @JobPlanNo",
new { Status = JobPlanStatus.Completed, CompletedQty = content.CompletedQty, Now = DateTime.Now, JobPlanNo = content.JobPlanNo });
// 更新轴状态
conn.Execute(
"UPDATE ProductionAxis SET CurrentStatus = @Status WHERE AxisCode = @AxisCode",
new { Status = JobPlanStatus.Completed, AxisCode = command.AxisCode });
return true;
}
/// <summary>
/// 执行物料齐套指令
/// </summary>
private bool ExecuteMaterialReady(MesDispatchCommand command)
{
var content = JsonConvert.DeserializeObject<MaterialReadyContent>(command.CommandContent);
if (content == null) return false;
// 更新轴物料状态
using var conn = new SqlConnection(_connStr);
conn.Execute(
"UPDATE ProductionAxis SET MaterialStatus = '齐套' WHERE AxisCode = @AxisCode",
new { AxisCode = command.AxisCode });
// 通知事件中心物料已齐套
var eventArgs = new MesAxisEventArgs
{
EventType = MesEventType.MaterialReady,
TriggerAxisCode = command.AxisCode,
EventContent = command.CommandContent,
JobPlanNo = content.JobPlanNo
};
MesAxisEventCenter.Instance.TriggerEvent(eventArgs);
return true;
}
/// <summary>
/// 执行物料缺料指令
/// </summary>
private bool ExecuteMaterialShortage(MesDispatchCommand command)
{
var content = JsonConvert.DeserializeObject<MaterialShortageContent>(command.CommandContent);
if (content == null) return false;
// 更新轴物料状态
using var conn = new SqlConnection(_connStr);
conn.Execute(
"UPDATE ProductionAxis SET MaterialStatus = '缺料' WHERE AxisCode = @AxisCode",
new { AxisCode = command.AxisCode });
// 触发主轴暂停(如果是支轴缺料)
if (!IsMainAxis(command.AxisCode))
{
var parentAxisCode = conn.QuerySingle<string>(
"SELECT ParentAxisCode FROM ProductionAxis WHERE AxisCode = @AxisCode",
new { AxisCode = command.AxisCode });
var pauseCommand = new MesDispatchCommand
{
EventId = command.EventId,
SourceTerminal = "物料调拨端",
TargetTerminal = "主轴控制端",
CommandType = nameof(MesEventType.JobPlanPause),
AxisCode = parentAxisCode,
LineCode = command.LineCode,
CommandContent = JsonConvert.SerializeObject(new JobPlanPauseContent
{
JobPlanNo = content.JobPlanNo,
Reason = "支轴缺料"
})
};
ExecuteCommand(pauseCommand);
}
return true;
}
/// <summary>
/// 执行设备异常指令
/// </summary>
private bool ExecuteEquipmentException(MesDispatchCommand command)
{
var content = JsonConvert.DeserializeObject<EquipmentExceptionContent>(command.CommandContent);
if (content == null) return false;
// 更新轴状态为异常
using var conn = new SqlConnection(_connStr);
conn.Execute(
"UPDATE ProductionAxis SET CurrentStatus = @Status WHERE AxisCode = @AxisCode",
new { Status = JobPlanStatus.Exception, AxisCode = command.AxisCode });
// 记录异常日志
conn.Execute(
"INSERT INTO MesExceptionLog (ExceptionId, AxisCode, ExceptionType, ExceptionContent, " +
"OccurTime, Handler, HandleStatus) VALUES (@ExceptionId, @AxisCode, @ExceptionType, " +
"@ExceptionContent, @OccurTime, @Handler, @HandleStatus)",
new
{
ExceptionId = Guid.NewGuid().ToString("N"),
command.AxisCode,
ExceptionType = "设备异常",
ExceptionContent = content.ExceptionContent,
OccurTime = DateTime.Now,
Handler = "",
HandleStatus = "未处理"
});
return true;
}
#endregion
#region 辅助方法
/// <summary>
/// 保存指令
/// </summary>
private void SaveCommand(MesDispatchCommand command)
{
using var conn = new SqlConnection(_connStr);
conn.Execute(
"INSERT INTO MesDispatchCommand (CommandId, EventId, SourceTerminal, TargetTerminal, " +
"CommandType, AxisCode, LineCode, CommandContent, CommandStatus, CreateTime, ExecuteTime, FailReason) " +
"VALUES (@CommandId, @EventId, @SourceTerminal, @TargetTerminal, @CommandType, " +
"@AxisCode, @LineCode, @CommandContent, @CommandStatus, @CreateTime, @ExecuteTime, @FailReason)",
command);
}
/// <summary>
/// 更新指令状态
/// </summary>
private void UpdateCommandStatus(MesDispatchCommand command)
{
using var conn = new SqlConnection(_connStr);
conn.Execute(
"UPDATE MesDispatchCommand SET CommandStatus = @CommandStatus, ExecuteTime = @ExecuteTime, " +
"FailReason = @FailReason WHERE CommandId = @CommandId",
new
{
command.CommandStatus,
command.ExecuteTime,
command.FailReason,
command.CommandId
});
}
/// <summary>
/// 判断是否为主轴
/// </summary>
private bool IsMainAxis(string axisCode)
{
using var conn = new SqlConnection(_connStr);
var axisType = conn.QuerySingle<AxisType>(
"SELECT AxisType FROM ProductionAxis WHERE AxisCode = @AxisCode",
new { AxisCode = axisCode });
return axisType == AxisType.MainAxis;
}
/// <summary>
/// 触发支轴启动
/// </summary>
private void TriggerBranchAxisStart(MesDispatchCommand command, SqlTransaction tran)
{
// 获取主轴关联的支轴
var branchAxises = tran.Connection.Query<string>(
"SELECT AxisCode FROM ProductionAxis WHERE ParentAxisCode = @ParentAxisCode",
new { ParentAxisCode = command.AxisCode }, tran);
// 为每个支轴生成启动指令
foreach (var branchAxisCode in branchAxises)
{
var branchCommand = new MesDispatchCommand
{
EventId = command.EventId,
SourceTerminal = "主轴控制端",
TargetTerminal = "支轴执行端",
CommandType = nameof(MesEventType.JobPlanStart),
AxisCode = branchAxisCode,
LineCode = command.LineCode,
CommandContent = command.CommandContent
};
// 保存支轴指令
tran.Connection.Execute(
"INSERT INTO MesDispatchCommand (CommandId, EventId, SourceTerminal, TargetTerminal, " +
"CommandType, AxisCode, LineCode, CommandContent, CommandStatus, CreateTime) " +
"VALUES (@CommandId, @EventId, @SourceTerminal, @TargetTerminal, @CommandType, " +
"@AxisCode, @LineCode, @CommandContent, @CommandStatus, @CreateTime)",
branchCommand, tran);
MesLog.Info($"主轴{command.AxisCode}触发支轴{branchAxisCode}启动指令:{branchCommand.CommandId}", "调度服务");
}
}
/// <summary>
/// 查询指令列表
/// </summary>
public DataTable GetCommandList(string lineCode = "", string axisCode = "")
{
var dt = new DataTable();
dt.Columns.Add("指令ID", typeof(string));
dt.Columns.Add("事件ID", typeof(string));
dt.Columns.Add("来源端", typeof(string));
dt.Columns.Add("目标端", typeof(string));
dt.Columns.Add("指令类型", typeof(string));
dt.Columns.Add("轴编码", typeof(string));
dt.Columns.Add("产线编码", typeof(string));
dt.Columns.Add("指令状态", typeof(string));
dt.Columns.Add("创建时间", typeof(DateTime));
dt.Columns.Add("执行时间", typeof(DateTime));
try
{
using var conn = new SqlConnection(_connStr);
var sql = "SELECT CommandId, EventId, SourceTerminal, TargetTerminal, CommandType, " +
"AxisCode, LineCode, CommandStatus, CreateTime, ExecuteTime " +
"FROM MesDispatchCommand WHERE 1=1 ";
var param = new DynamicParameters();
if (!string.IsNullOrEmpty(lineCode))
{
sql += "AND LineCode = @LineCode ";
param.Add("LineCode", lineCode);
}
if (!string.IsNullOrEmpty(axisCode))
{
sql += "AND AxisCode = @AxisCode ";
param.Add("AxisCode", axisCode);
}
foreach (var cmd in conn.Query(sql + "ORDER BY CreateTime DESC", param))
{
dt.Rows.Add(
cmd.CommandId, cmd.EventId, cmd.SourceTerminal, cmd.TargetTerminal,
cmd.CommandType, cmd.AxisCode, cmd.LineCode, cmd.CommandStatus,
cmd.CreateTime, cmd.ExecuteTime ?? DBNull.Value);
}
}
catch (Exception ex)
{
MesLog.Error("查询指令列表失败", ex, "调度服务");
}
return dt;
}
#endregion
}
#region 指令内容DTO
/// <summary>
/// 作业计划启动内容
/// </summary>
public class JobPlanStartContent
{
public string JobPlanNo { get; set; }
public string Operator { get; set; }
public DateTime StartTime { get; set; } = DateTime.Now;
}
/// <summary>
/// 作业计划暂停内容
/// </summary>
public class JobPlanPauseContent
{
public string JobPlanNo { get; set; }
public string Reason { get; set; }
public string Operator { get; set; }
}
/// <summary>
/// 作业计划完成内容
/// </summary>
public class JobPlanCompleteContent
{
public string JobPlanNo { get; set; }
public int CompletedQty { get; set; }
public string Operator { get; set; }
public DateTime CompleteTime { get; set; } = DateTime.Now;
}
/// <summary>
/// 物料齐套内容
/// </summary>
public class MaterialReadyContent
{
public string JobPlanNo { get; set; }
public string MaterialCode { get; set; }
public int Qty { get; set; }
public string Confirmer { get; set; }
}
/// <summary>
/// 物料缺料内容
/// </summary>
public class MaterialShortageContent
{
public string JobPlanNo { get; set; }
public string MaterialCode { get; set; }
public int ShortageQty { get; set; }
public string Reporter { get; set; }
}
/// <summary>
/// 设备异常内容
/// </summary>
public class EquipmentExceptionContent
{
public string ExceptionContent { get; set; }
public string EquipmentCode { get; set; }
public string Reporter { get; set; }
}
#endregion
3. WinForm 可视化界面组件
/// <summary>
/// 主支轴作业计划协同控件
/// </summary>
public partial class MesAxisCoopControl : UserControl
{
private readonly MesAxisEventCenter _eventCenter;
private readonly MesDispatchService _dispatchService;
private List<ProductionAxis> _axisList;
private ProductionAxis _selectedAxis;
private MesCoopJobPlan _currentJobPlan;
// 绘图资源
private readonly Pen _mainAxisPen = new Pen(Color.DarkBlue, 3);
private readonly Pen _branchAxisPen = new Pen(Color.DarkGreen, 2);
private readonly Pen _selectedPen = new Pen(Color.Red, 4);
private readonly Pen _connectionPen = new Pen(Color.Orange, 1);
private readonly Font _axisFont = new Font("微软雅黑", 10, FontStyle.Bold);
private readonly Font _statusFont = new Font("微软雅黑", 8);
public MesAxisCoopControl()
{
InitializeComponent();
DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
// 初始化核心服务
_eventCenter = MesAxisEventCenter.Instance;
_dispatchService = new MesDispatchService();
// 注册事件委托
RegisterEventHandlers();
// 绑定交互事件
Paint += OnPaint;
MouseDown += OnMouseDown;
MouseWheel += OnMouseWheel;
// 初始化数据
LoadAxisList();
}
#region 数据加载
/// <summary>
/// 加载轴列表
/// </summary>
public void LoadAxisList(string lineCode = "")
{
try
{
using var conn = new SqlConnection(MesConfig.GetConnStr());
var sql = "SELECT AxisCode, AxisName, LineCode, AxisType, ParentAxisCode, StationCodes, " +
"BindJobPlanNo, CurrentStatus, Beat, SortNo FROM ProductionAxis WHERE 1=1 ";
var param = new DynamicParameters();
if (!string.IsNullOrEmpty(lineCode))
{
sql += "AND LineCode = @LineCode ";
param.Add("LineCode", lineCode);
}
_axisList = conn.Query<ProductionAxis>(sql + "ORDER BY SortNo", param).ToList();
Refresh();
// 更新轴选择下拉框
cboAxisCode.DataSource = _axisList;
cboAxisCode.DisplayMember = "AxisName";
cboAxisCode.ValueMember = "AxisCode";
MesLog.Info($"加载轴列表成功,共{_axisList.Count}个轴", "协同控件");
}
catch (Exception ex)
{
MesLog.Error("加载轴列表失败", ex, "协同控件");
MessageBox.Show($"加载轴列表失败:{ex.Message}", "MES提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 加载作业计划
/// </summary>
public void LoadJobPlan(string jobPlanNo)
{
try
{
using var conn = new SqlConnection(MesConfig.GetConnStr());
_currentJobPlan = conn.QuerySingle<MesCoopJobPlan>(
"SELECT JobPlanNo, JobPlanName, LineCode, MainAxisCode, ProductCode, ProductName, " +
"PlanQty, CompletedQty, PlanStartTime, PlanEndTime, ActualStartTime, ActualEndTime, " +
"PlanStatus, BranchPlanJson, Creator, Version FROM MesCoopJobPlan WHERE JobPlanNo = @JobPlanNo",
new { JobPlanNo = jobPlanNo });
// 显示计划信息
lblPlanInfo.Text = $"计划名称:{_currentJobPlan.JobPlanName}\n" +
$"产品:{_currentJobPlan.ProductName}({_currentJobPlan.ProductCode})\n" +
$"计划产量:{_currentJobPlan.PlanQty} | 已完成:{_currentJobPlan.CompletedQty}\n" +
$"状态:{_currentJobPlan.PlanStatus}";
// 显示支轴计划
dgvBranchPlans.DataSource = _currentJobPlan.BranchPlans.ToList();
MesLog.Info($"加载作业计划{jobPlanNo}成功", "协同控件");
}
catch (Exception ex)
{
MesLog.Error($"加载作业计划{jobPlanNo}失败", ex, "协同控件");
MessageBox.Show($"加载作业计划失败:{ex.Message}", "MES提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
#endregion
#region 事件委托注册
/// <summary>
/// 注册事件委托
/// </summary>
private void RegisterEventHandlers()
{
// 注册作业计划启动事件委托
_eventCenter.RegisterHandler(MesEventType.JobPlanStart, OnJobPlanStart);
// 注册作业计划暂停事件委托
_eventCenter.RegisterHandler(MesEventType.JobPlanPause, OnJobPlanPause);
// 注册物料缺料事件委托
_eventCenter.RegisterHandler(MesEventType.MaterialShortage, OnMaterialShortage);
// 注册设备异常事件委托
_eventCenter.RegisterHandler(MesEventType.EquipmentException, OnEquipmentException);
MesLog.Info("事件委托注册完成", "协同控件");
}
#region 事件处理方法
/// <summary>
/// 作业计划启动事件处理
/// </summary>
private void OnJobPlanStart(object sender, MesAxisEventArgs e)
{
// 跨线程更新UI
Invoke(new Action(() =>
{
lblEventLog.Text = $"[{DateTime.Now:HH:mm:ss}] 轴{e.TriggerAxisCode}作业计划启动成功!\n" + lblEventLog.Text;
LoadAxisList(); // 刷新轴状态
Refresh(); // 重绘界面
}));
}
/// <summary>
/// 作业计划暂停事件处理
/// </summary>
private void OnJobPlanPause(object sender, MesAxisEventArgs e)
{
Invoke(new Action(() =>
{
lblEventLog.Text = $"[{DateTime.Now:HH:mm:ss}] 轴{e.TriggerAxisCode}作业计划暂停:{e.EventContent}\n" + lblEventLog.Text;
LoadAxisList();
Refresh();
}));
}
/// <summary>
/// 物料缺料事件处理
/// </summary>
private void OnMaterialShortage(object sender, MesAxisEventArgs e)
{
Invoke(new Action(() =>
{
lblEventLog.Text = $"[{DateTime.Now:HH:mm:ss}] 轴{e.TriggerAxisCode}物料缺料告警!\n" + lblEventLog.Text;
// 显示告警提示
MessageBox.Show($"轴{e.TriggerAxisCode}物料缺料!", "MES告警", MessageBoxButtons.OK, MessageBoxIcon.Warning);
LoadAxisList();
Refresh();
}));
}
/// <summary>
/// 设备异常事件处理
/// </summary>
private void OnEquipmentException(object sender, MesAxisEventArgs e)
{
Invoke(new Action(() =>
{
lblEventLog.Text = $"[{DateTime.Now:HH:mm:ss}] 轴{e.TriggerAxisCode}设备异常:{e.EventContent}\n" + lblEventLog.Text;
// 显示紧急告警
MessageBox.Show($"轴{e.TriggerAxisCode}设备异常!\n{e.EventContent}", "MES紧急告警", MessageBoxButtons.OK, MessageBoxIcon.Error);
LoadAxisList();
Refresh();
}));
}
#endregion
#endregion
#region 可视化绘制
private void OnPaint(object sender, PaintEventArgs e)
{
if (_axisList == null || _axisList.Count == 0) return;
var g = e.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
// 绘制主轴
var mainAxis = _axisList.FirstOrDefault(a => a.AxisType == AxisType.MainAxis);
if (mainAxis != null)
{
DrawAxis(g, mainAxis, 100, 100, true);
// 绘制支轴(围绕主轴排列)
var branchAxises = _axisList.Where(a => a.ParentAxisCode == mainAxis.AxisCode).ToList();
for (int i = 0; i < branchAxises.Count; i++)
{
var x = 100 + (i % 2 == 0 ? 300 : -200);
var y = 100 + i * 100;
DrawAxis(g, branchAxises[i], x, y, false);
// 绘制主轴-支轴连接
g.DrawLine(_connectionPen, 150, 150, x + 50, y + 40);
DrawArrow(g, 150, 150, x + 50, y + 40);
}
}
}
/// <summary>
/// 绘制轴
/// </summary>
private void DrawAxis(Graphics g, ProductionAxis axis, int x, int y, bool isMainAxis)
{
// 轴矩形
var rect = new Rectangle(x, y, 100, 80);
// 选中状态
var isSelected = _selectedAxis != null && _selectedAxis.AxisCode == axis.AxisCode;
// 状态颜色
var brush = GetAxisBrush(axis.CurrentStatus);
// 绘制轴背景
g.FillRectangle(brush, rect);
// 绘制轴边框
g.DrawRectangle(isSelected ? _selectedPen : (isMainAxis ? _mainAxisPen : _branchAxisPen), rect);
// 绘制轴信息
g.DrawString(axis.AxisName, _axisFont, Brushes.Black, x + 5, y + 5);
g.DrawString(axis.AxisCode, _statusFont, Brushes.Gray, x + 5, y + 25);
g.DrawString($"状态:{axis.CurrentStatus}", _statusFont, GetStatusColor(axis.CurrentStatus), x + 5, y + 40);
g.DrawString($"节拍:{axis.Beat}s", _statusFont, Brushes.Black, x + 5, y + 55);
}
/// <summary>
/// 绘制箭头
/// </summary>
private void DrawArrow(Graphics g, int startX, int startY, int endX, int endY)
{
var angle = Math.Atan2(endY - startY, endX - startX);
var p1 = new Point(
(int)(endX - 8 * Math.Cos(angle - Math.PI / 6)),
(int)(endY - 8 * Math.Sin(angle - Math.PI / 6)));
var p2 = new Point(
(int)(endX - 8 * Math.Cos(angle + Math.PI / 6)),
(int)(endY - 8 * Math.Sin(angle + Math.PI / 6)));
g.DrawLine(_connectionPen, endX, endY, p1);
g.DrawLine(_connectionPen, endX, endY, p2);
}
/// <summary>
/// 获取轴背景色
/// </summary>
private Brush GetAxisBrush(JobPlanStatus status)
{
return status switch
{
JobPlanStatus.Running => Brushes.LightGreen,
JobPlanStatus.Paused => Brushes.LightYellow,
JobPlanStatus.Exception => Brushes.LightPink,
JobPlanStatus.Completed => Brushes.LightBlue,
JobPlanStatus.Cancelled => Brushes.LightGray,
_ => Brushes.WhiteSmoke
};
}
/// <summary>
/// 获取状态文字颜色
/// </summary>
private Brush GetStatusColor(JobPlanStatus status)
{
return status switch
{
JobPlanStatus.Running => Brushes.Green,
JobPlanStatus.Paused => Brushes.Orange,
JobPlanStatus.Exception => Brushes.Red,
JobPlanStatus.Completed => Brushes.Blue,
JobPlanStatus.Cancelled => Brushes.Gray,
_ => Brushes.Black
};
}
#endregion
#region 交互操作
private void OnMouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left || _axisList == null) return;
// 检测点击的轴
var mainAxis = _axisList.FirstOrDefault(a => a.AxisType == AxisType.MainAxis);
if (mainAxis != null && new Rectangle(100, 100, 100, 80).Contains(e.X, e.Y))
{
_selectedAxis = mainAxis;
OnAxisSelected(mainAxis);
Refresh();
return;
}
// 检测支轴
var branchAxises = _axisList.Where(a => a.ParentAxisCode == mainAxis?.AxisCode).ToList();
for (int i = 0; i < branchAxises.Count; i++)
{
var x = 100 + (i % 2 == 0 ? 300 : -200);
var y = 100 + i * 100;
if (new Rectangle(x, y, 100, 80).Contains(e.X, e.Y))
{
_selectedAxis = branchAxises[i];
OnAxisSelected(branchAxises[i]);
Refresh();
return;
}
}
// 取消选中
_selectedAxis = null;
Refresh();
}
/// <summary>
/// 轴选中事件
/// </summary>
private void OnAxisSelected(ProductionAxis axis)
{
lblAxisInfo.Text = $"轴编码:{axis.AxisCode}\n" +
$"轴名称:{axis.AxisName}\n" +
$"类型:{(axis.AxisType == AxisType.MainAxis ? "主轴" : "支轴")}\n" +
$"绑定计划:{axis.BindJobPlanNo}\n" +
$"当前状态:{axis.CurrentStatus}\n" +
$"节拍:{axis.Beat}秒/件\n" +
$"工位:{axis.StationCodes}";
// 加载关联的作业计划
if (!string.IsNullOrEmpty(axis.BindJobPlanNo))
{
LoadJobPlan(axis.BindJobPlanNo);
}
// 加载指令日志
dgvCommandLogs.DataSource = _dispatchService.GetCommandList(axisCode: axis.AxisCode);
// 启用操作按钮
btnStartPlan.Enabled = axis.CurrentStatus == JobPlanStatus.Pending;
btnPausePlan.Enabled = axis.CurrentStatus == JobPlanStatus.Running;
btnCompletePlan.Enabled = axis.CurrentStatus == JobPlanStatus.Running;
btnReportMaterialShortage.Enabled = true;
btnReportEquipmentException.Enabled = true;
}
/// <summary>
/// 鼠标滚轮缩放
/// </summary>
private void OnMouseWheel(object sender, MouseEventArgs e)
{
// 此处可实现界面缩放功能
}
#endregion
#region 操作按钮事件
/// <summary>
/// 启动作业计划
/// </summary>
private void BtnStartPlan_Click(object sender, EventArgs e)
{
if (_selectedAxis == null)
{
MessageBox.Show("请先选择轴!", "MES提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// 构建启动事件参数
var eventArgs = new MesAxisEventArgs
{
EventType = MesEventType.JobPlanStart,
TriggerAxisCode = _selectedAxis.AxisCode,
TargetAxisCode = "",
JobPlanNo = _selectedAxis.BindJobPlanNo,
EventContent = JsonConvert.SerializeObject(new JobPlanStartContent
{
JobPlanNo = _selectedAxis.BindJobPlanNo,
Operator = Environment.UserName
})
};
// 触发事件
_eventCenter.TriggerAxisEvent(_selectedAxis.AxisCode, eventArgs, this);
}
/// <summary>
/// 暂停作业计划
/// </summary>
private void BtnPausePlan_Click(object sender, EventArgs e)
{
if (_selectedAxis == null) return;
var reason = Microsoft.VisualBasic.Interaction.InputBox("请输入暂停原因:", "暂停作业计划", "人工干预");
if (string.IsNullOrEmpty(reason)) return;
var eventArgs = new MesAxisEventArgs
{
EventType = MesEventType.JobPlanPause,
TriggerAxisCode = _selectedAxis.AxisCode,
JobPlanNo = _selectedAxis.BindJobPlanNo,
EventContent = JsonConvert.SerializeObject(new JobPlanPauseContent
{
JobPlanNo = _selectedAxis.BindJobPlanNo,
Reason = reason,
Operator = Environment.UserName
})
};
_eventCenter.TriggerAxisEvent(_selectedAxis.AxisCode, eventArgs, this);
}
/// <summary>
/// 完成作业计划
/// </summary>
private void BtnCompletePlan_Click(object sender, EventArgs e)
{
if (_selectedAxis == null || _currentJobPlan == null) return;
var qtyStr = Microsoft.VisualBasic.Interaction.InputBox($"请输入完成产量(计划:{_currentJobPlan.PlanQty}):",
"完成作业计划", _currentJobPlan.PlanQty.ToString());
if (!int.TryParse(qtyStr, out int completedQty) || completedQty <= 0)
{
MessageBox.Show("请输入有效的完成产量!", "MES提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var eventArgs = new MesAxisEventArgs
{
EventType = MesEventType.JobPlanComplete,
TriggerAxisCode = _selectedAxis.AxisCode,
JobPlanNo = _selectedAxis.BindJobPlanNo,
EventContent = JsonConvert.SerializeObject(new JobPlanCompleteContent
{
JobPlanNo = _selectedAxis.BindJobPlanNo,
CompletedQty = completedQty,
Operator = Environment.UserName
})
};
_eventCenter.TriggerAxisEvent(_selectedAxis.AxisCode, eventArgs, this);
}
/// <summary>
/// 上报物料缺料
/// </summary>
private void BtnReportMaterialShortage_Click(object sender, EventArgs e)
{
if (_selectedAxis == null) return;
var materialCode = Microsoft.VisualBasic.Interaction.InputBox("请输入缺料物料编码:", "物料缺料上报", "");
var shortageQtyStr = Microsoft.VisualBasic.Interaction.InputBox("请输入缺料数量:", "物料缺料上报", "");
if (string.IsNullOrEmpty(materialCode) || !int.TryParse(shortageQtyStr, out int shortageQty))
{
MessageBox.Show("请输入有效的物料编码和数量!", "MES提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var eventArgs = new MesAxisEventArgs
{
EventType = MesEventType.MaterialShortage,
TriggerAxisCode = _selectedAxis.AxisCode,
JobPlanNo = _selectedAxis.BindJobPlanNo,
EventContent = JsonConvert.SerializeObject(new MaterialShortageContent
{
JobPlanNo = _selectedAxis.BindJobPlanNo,
MaterialCode = materialCode,
ShortageQty = shortageQty,
Reporter = Environment.UserName
})
};
_eventCenter.TriggerAxisEvent(_selectedAxis.AxisCode, eventArgs, this);
}
/// <summary>
/// 上报设备异常
/// </summary>
private void BtnReportEquipmentException_Click(object sender, EventArgs e)
{
if (_selectedAxis == null) return;
var equipmentCode = Microsoft.VisualBasic.Interaction.InputBox("请输入异常设备编码:", "设备异常上报", "");
var exceptionContent = Microsoft.VisualBasic.Interaction.InputBox("请输入异常描述:", "设备异常上报", "");
if (string.IsNullOrEmpty(equipmentCode) || string.IsNullOrEmpty(exceptionContent))
{
MessageBox.Show("请输入有效的设备编码和异常描述!", "MES提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var eventArgs = new MesAxisEventArgs
{
EventType = MesEventType.EquipmentException,
TriggerAxisCode = _selectedAxis.AxisCode,
JobPlanNo = _selectedAxis.BindJobPlanNo,
EventContent = JsonConvert.SerializeObject(new EquipmentExceptionContent
{
EquipmentCode = equipmentCode,
ExceptionContent = exceptionContent,
Reporter = Environment.UserName
})
};
_eventCenter.TriggerAxisEvent(_selectedAxis.AxisCode, eventArgs, this);
}
#endregion
#region 资源释放
protected override void Dispose(bool disposing)
{
if (disposing)
{
// 注销事件委托
_eventCenter.UnregisterHandler(MesEventType.JobPlanStart, OnJobPlanStart);
_eventCenter.UnregisterHandler(MesEventType.JobPlanPause, OnJobPlanPause);
_eventCenter.UnregisterHandler(MesEventType.MaterialShortage, OnMaterialShortage);
_eventCenter.UnregisterHandler(MesEventType.EquipmentException, OnEquipmentException);
// 释放绘图资源
_mainAxisPen.Dispose();
_branchAxisPen.Dispose();
_selectedPen.Dispose();
_connectionPen.Dispose();
_axisFont.Dispose();
_statusFont.Dispose();
}
base.Dispose(disposing);
}
#endregion
}
/// <summary>
/// MES主支轴协同主界面
/// </summary>
public partial class MesAxisCoopMainForm : Form
{
private readonly MesAxisCoopControl _coopControl;
public MesAxisCoopMainForm()
{
InitializeComponent();
// 初始化协同控件
_coopControl = new MesAxisCoopControl();
_coopControl.Dock = DockStyle.Fill;
panelMain.Controls.Add(_coopControl);
// 加载产线列表
LoadLineList();
}
/// <summary>
/// 加载产线列表
/// </summary>
private void LoadLineList()
{
try
{
using var conn = new SqlConnection(MesConfig.GetConnStr());
var lines = conn.Query<dynamic>("SELECT LineCode, LineName FROM ProductionLine ORDER BY LineCode");
cboLineCode.DataSource = lines;
cboLineCode.DisplayMember = "LineName";
cboLineCode.ValueMember = "LineCode";
}
catch (Exception ex)
{
MesLog.Error("加载产线列表失败", ex, "主界面");
}
}
/// <summary>
/// 产线选择变更
/// </summary>
private void CboLineCode_SelectedIndexChanged(object sender, EventArgs e)
{
if (cboLineCode.SelectedValue == null) return;
_coopControl.LoadAxisList(cboLineCode.SelectedValue.ToString());
}
}
4. 数据库脚本
-- 产线表
CREATE TABLE ProductionLine (
LineCode VARCHAR(50) PRIMARY KEY,
LineName VARCHAR(100) NOT NULL,
LineType VARCHAR(20) NOT NULL,
Status VARCHAR(20) DEFAULT '正常',
Description VARCHAR(500)
);
-- 产线轴表(主轴/支轴)
CREATE TABLE ProductionAxis (
AxisCode VARCHAR(50) PRIMARY KEY,
AxisName VARCHAR(100) NOT NULL,
LineCode VARCHAR(50) NOT NULL,
AxisType INT NOT NULL, -- 0=主轴,1=支轴
ParentAxisCode VARCHAR(50),
StationCodes VARCHAR(500),
BindJobPlanNo VARCHAR(50),
CurrentStatus INT DEFAULT 0, -- 0=待执行,1=执行中,2=暂停,3=已完成,4=异常,5=已取消
MaterialStatus VARCHAR(20) DEFAULT '待调拨',
Beat INT DEFAULT 60,
SortNo INT DEFAULT 0,
FOREIGN KEY (LineCode) REFERENCES ProductionLine(LineCode),
FOREIGN KEY (ParentAxisCode) REFERENCES ProductionAxis(AxisCode)
);
-- 协同作业计划表
CREATE TABLE MesCoopJobPlan (
JobPlanNo VARCHAR(50) PRIMARY KEY,
JobPlanName VARCHAR(200) NOT NULL,
LineCode VARCHAR(50) NOT NULL,
MainAxisCode VARCHAR(50) NOT NULL,
ProductCode VARCHAR(50) NOT NULL,
ProductName VARCHAR(100) NOT NULL,
PlanQty INT NOT NULL,
MES的生产作业计划协同,产线的工站流程的组合主支轴事件委托封装形式,开发一个多指令端/应用调度模式的Winform中间件功能界面组件
MES 生产作业计划协同 WinForm 中间件组件(主支轴事件委托 + 多指令调度)
你需要开发一套面向 MES 生产作业计划协同的 WinForm 中间件,核心是基于产线工站流程的主支轴事件委托封装模式,实现多指令端 / 应用的调度协同,让主轴(核心产线)与支轴(分支工位)的作业计划通过事件委托机制联动,同时支持多指令端的调度交互,最终落地为可视化的 WinForm 界面组件。
以下是可直接落地的工业级解决方案,包含完整的架构设计、核心代码、界面实现和部署说明,兼顾 MES 系统的实时性、可靠性和可扩展性。
一、核心设计思路
1. 主支轴事件委托核心逻辑
- 主轴:核心产线节拍(如总装线),作为调度核心,触发支轴联动
- 支轴:配套工位(如预装 / 检测工位),响应主轴指令并反馈状态
- 事件委托:封装主轴→支轴、支轴→主轴的事件触发 / 回调逻辑,解耦主支轴依赖
- 多指令调度:支持 MES 计划端、产线执行端、物料调拨端、异常告警端等多端指令协同
2. 整体架构
委托注册 指令分发 状态同步 主轴控制器 事件中心<单例> 支轴控制器1 支轴控制器2 多指令调度中心 MES计划端 产线执行端 物料调拨端 异常告警端 WinForm可视化界面
graph TD
A[主轴控制器] -->|委托注册| B[事件中心<单例>]
C[支轴控制器1] -->|委托注册| B
D[支轴控制器2] -->|委托注册| B
E[多指令调度中心] <--> B
E -->|指令分发| F[MES计划端]
E -->|指令分发| G[产线执行端]
E -->|指令分发| H[物料调拨端]
E -->|指令分发| I[异常告警端]
B -->|状态同步| J[WinForm可视化界面]
委托注册 指令分发 状态同步 主轴控制器 事件中心<单例> 支轴控制器1 支轴控制器2 多指令调度中心 MES计划端 产线执行端 物料调拨端 异常告警端 WinForm可视化界面
二、完整代码实现
1. 核心枚举与模型定义
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using Dapper;
using Newtonsoft.Json;
#region 核心枚举
/// <summary>
/// 轴类型(主轴/支轴)
/// </summary>
public enum AxisType
{
MainAxis = 0, // 主轴
BranchAxis = 1 // 支轴
}
/// <summary>
/// 作业计划状态
/// </summary>
public enum JobPlanStatus
{
Pending = 0, // 待执行
Running = 1, // 执行中
Paused = 2, // 暂停
Completed = 3, // 已完成
Exception = 4, // 异常
Cancelled = 5 // 已取消
}
/// <summary>
/// MES事件类型
/// </summary>
public enum MesEventType
{
JobPlanStart, // 计划启动
JobPlanPause, // 计划暂停
JobPlanComplete, // 计划完成
MaterialShortage, // 物料缺料
EquipmentException // 设备异常
}
/// <summary>
/// 指令执行状态
/// </summary>
public enum CommandStatus
{
Pending = 0, // 待执行
Executing = 1, // 执行中
Completed = 2, // 已完成
Failed = 3 // 失败
}
#endregion
#region 核心模型
/// <summary>
/// 产线轴信息(主轴/支轴)
/// </summary>
public class ProductionAxis
{
public string AxisCode { get; set; } // 轴编码(MA-001/BA-001-01)
public string AxisName { get; set; } // 轴名称
public string LineCode { get; set; } // 产线编码
public AxisType AxisType { get; set; } // 轴类型
public string ParentAxisCode { get; set; } // 父轴编码(支轴关联主轴)
public string BindJobPlanNo { get; set; } // 绑定的计划编号
public JobPlanStatus CurrentStatus { get; set; } // 当前状态
public int Beat { get; set; } = 60; // 节拍(秒/件)
}
/// <summary>
/// MES事件参数
/// </summary>
public class MesAxisEventArgs : EventArgs
{
public string EventId { get; set; } = Guid.NewGuid().ToString("N");
public MesEventType EventType { get; set; }
public string TriggerAxisCode { get; set; } // 触发轴编码
public string JobPlanNo { get; set; } // 计划编号
public string EventContent { get; set; } // 事件内容(JSON)
public DateTime TriggerTime { get; set; } = DateTime.Now;
public bool IsHandled { get; set; } = false;
}
/// <summary>
/// 调度指令模型
/// </summary>
public class MesDispatchCommand
{
public string CommandId { get; set; } = Guid.NewGuid().ToString("N");
public string EventId { get; set; }
public string SourceTerminal { get; set; } // 指令来源端
public string TargetTerminal { get; set; } // 指令目标端
public MesEventType CommandType { get; set; }
public string AxisCode { get; set; }
public string CommandContent { get; set; }
public CommandStatus CommandStatus { get; set; } = CommandStatus.Pending;
public DateTime CreateTime { get; set; } = DateTime.Now;
}
#endregion
2. 事件委托核心封装(单例事件中心)
csharp
运行
#region 事件委托定义
/// <summary>
/// MES轴事件委托
/// </summary>
/// <param name="sender">触发源</param>
/// <param name="e">事件参数</param>
public delegate void MesAxisEventHandler(object sender, MesAxisEventArgs e);
#endregion
/// <summary>
/// MES主支轴事件中心(单例模式)
/// 核心封装事件委托的注册、触发、注销
/// </summary>
public sealed class MesAxisEventCenter
{
// 单例实例
private static readonly Lazy<MesAxisEventCenter> _instance = new Lazy<MesAxisEventCenter>(() => new MesAxisEventCenter());
public static MesAxisEventCenter Instance => _instance.Value;
// 事件委托注册表
private readonly Dictionary<MesEventType, List<MesAxisEventHandler>> _eventHandlers = new Dictionary<MesEventType, List<MesAxisEventHandler>>();
private readonly object _lockObj = new object();
// 私有构造函数
private MesAxisEventCenter()
{
// 初始化所有事件类型的委托列表
foreach (MesEventType type in Enum.GetValues(typeof(MesEventType)))
{
_eventHandlers[type] = new List<MesAxisEventHandler>();
}
}
#region 委托注册
/// <summary>
/// 注册全局事件委托
/// </summary>
public void RegisterHandler(MesEventType eventType, MesAxisEventHandler handler)
{
lock (_lockObj)
{
if (!_eventHandlers[eventType].Contains(handler))
{
_eventHandlers[eventType].Add(handler);
LogHelper.Info($"注册事件委托:{eventType}");
}
}
}
/// <summary>
/// 注册指定轴的事件委托
/// </summary>
public void RegisterAxisHandler(string axisCode, MesEventType eventType, MesAxisEventHandler handler)
{
lock (_lockObj)
{
if (!_eventHandlers[eventType].Contains(handler))
{
_eventHandlers[eventType].Add(handler);
LogHelper.Info($"轴[{axisCode}]注册事件委托:{eventType}");
}
}
}
#endregion
#region 事件触发
/// <summary>
/// 触发事件(核心方法)
/// </summary>
public void TriggerEvent(MesAxisEventArgs e, object sender = null)
{
if (e == null) return;
lock (_lockObj)
{
LogHelper.Info($"触发事件:{e.EventType} | 轴:{e.TriggerAxisCode}");
// 异步执行所有注册的委托(避免阻塞UI)
foreach (var handler in _eventHandlers[e.EventType].ToList())
{
try
{
handler.BeginInvoke(sender, e, ar =>
{
try { handler.EndInvoke(ar); e.IsHandled = true; }
catch (Exception ex) { LogHelper.Error("委托执行失败", ex); }
}, null);
}
catch (Exception ex)
{
LogHelper.Error($"触发事件{e.EventType}失败", ex);
}
}
// 事件触发后生成调度指令
GenerateDispatchCommand(e);
}
}
#endregion
#region 辅助方法
/// <summary>
/// 根据事件生成调度指令
/// </summary>
private void GenerateDispatchCommand(MesAxisEventArgs e)
{
var command = new MesDispatchCommand
{
EventId = e.EventId,
SourceTerminal = GetSourceTerminal(e.TriggerAxisCode),
TargetTerminal = GetTargetTerminal(e.EventType),
CommandType = e.EventType,
AxisCode = e.TriggerAxisCode,
CommandContent = e.EventContent
};
// 执行并保存指令
var dispatchService = new MesDispatchService();
dispatchService.ExecuteCommand(command);
}
/// <summary>
/// 获取指令来源端
/// </summary>
private string GetSourceTerminal(string axisCode)
{
var axis = DbHelper.GetAxisInfo(axisCode);
return axis?.AxisType == AxisType.MainAxis ? "主轴控制端" : "支轴执行端";
}
/// <summary>
/// 获取指令目标端
/// </summary>
private string GetTargetTerminal(MesEventType eventType)
{
return eventType switch
{
MesEventType.MaterialShortage => "物料调拨端",
MesEventType.EquipmentException => "异常告警端",
_ => "MES计划端"
};
}
#endregion
}
3. 多指令调度服务
/// <summary>
/// MES调度服务(多指令端核心)
/// </summary>
public class MesDispatchService
{
/// <summary>
/// 执行调度指令
/// </summary>
public bool ExecuteCommand(MesDispatchCommand command)
{
// 1. 保存指令
DbHelper.SaveDispatchCommand(command);
try
{
// 2. 更新指令状态为执行中
command.CommandStatus = CommandStatus.Executing;
DbHelper.UpdateCommandStatus(command);
// 3. 执行业务逻辑
bool result = ExecuteCommandLogic(command);
// 4. 更新最终状态
command.CommandStatus = result ? CommandStatus.Completed : CommandStatus.Failed;
DbHelper.UpdateCommandStatus(command);
LogHelper.Info($"指令[{command.CommandId}]执行{(result ? "成功" : "失败")}");
return result;
}
catch (Exception ex)
{
command.CommandStatus = CommandStatus.Failed;
DbHelper.UpdateCommandStatus(command);
LogHelper.Error($"指令[{command.CommandId}]执行异常", ex);
return false;
}
}
/// <summary>
/// 指令业务逻辑执行
/// </summary>
private bool ExecuteCommandLogic(MesDispatchCommand command)
{
switch (command.CommandType)
{
case MesEventType.JobPlanStart:
return StartJobPlan(command);
case MesEventType.JobPlanPause:
return PauseJobPlan(command);
case MesEventType.MaterialShortage:
return HandleMaterialShortage(command);
case MesEventType.EquipmentException:
return HandleEquipmentException(command);
default:
LogHelper.Warn($"未实现的指令类型:{command.CommandType}");
return true;
}
}
#region 指令逻辑实现
/// <summary>
/// 启动作业计划
/// </summary>
private bool StartJobPlan(MesDispatchCommand command)
{
var content = JsonConvert.DeserializeObject<JobPlanStartContent>(command.CommandContent);
if (content == null) return false;
// 更新计划状态
return DbHelper.ExecuteNonQuery(
"UPDATE MesCoopJobPlan SET PlanStatus = 1, ActualStartTime = GETDATE() WHERE JobPlanNo = @JobPlanNo",
new { content.JobPlanNo }) > 0;
}
/// <summary>
/// 暂停作业计划
/// </summary>
private bool PauseJobPlan(MesDispatchCommand command)
{
var content = JsonConvert.DeserializeObject<JobPlanPauseContent>(command.CommandContent);
if (content == null) return false;
// 更新计划状态
return DbHelper.ExecuteNonQuery(
"UPDATE MesCoopJobPlan SET PlanStatus = 2 WHERE JobPlanNo = @JobPlanNo",
new { content.JobPlanNo }) > 0;
}
/// <summary>
/// 处理物料缺料
/// </summary>
private bool HandleMaterialShortage(MesDispatchCommand command)
{
var content = JsonConvert.DeserializeObject<MaterialShortageContent>(command.CommandContent);
if (content == null) return false;
// 1. 更新轴状态为异常
DbHelper.ExecuteNonQuery(
"UPDATE ProductionAxis SET CurrentStatus = 4 WHERE AxisCode = @AxisCode",
new { command.AxisCode });
// 2. 如果是支轴缺料,暂停主轴
var axis = DbHelper.GetAxisInfo(command.AxisCode);
if (axis.AxisType == AxisType.BranchAxis && !string.IsNullOrEmpty(axis.ParentAxisCode))
{
DbHelper.ExecuteNonQuery(
"UPDATE ProductionAxis SET CurrentStatus = 2 WHERE AxisCode = @AxisCode",
new { axis.ParentAxisCode });
}
return true;
}
/// <summary>
/// 处理设备异常
/// </summary>
private bool HandleEquipmentException(MesDispatchCommand command)
{
var content = JsonConvert.DeserializeObject<EquipmentExceptionContent>(command.CommandContent);
if (content == null) return false;
// 更新轴状态为异常
return DbHelper.ExecuteNonQuery(
"UPDATE ProductionAxis SET CurrentStatus = 4 WHERE AxisCode = @AxisCode",
new { command.AxisCode }) > 0;
}
#endregion
}
#region 指令内容DTO
public class JobPlanStartContent
{
public string JobPlanNo { get; set; }
public string Operator { get; set; }
}
public class JobPlanPauseContent
{
public string JobPlanNo { get; set; }
public string Reason { get; set; }
}
public class MaterialShortageContent
{
public string MaterialCode { get; set; }
public int ShortageQty { get; set; }
}
public class EquipmentExceptionContent
{
public string EquipmentCode { get; set; }
public string ExceptionDesc { get; set; }
}
#endregion
4. 数据库辅助类
/// <summary>
/// 数据库操作辅助类
/// </summary>
public static class DbHelper
{
private static readonly string _connStr = "Data Source=.;Initial Catalog=MES_Cooperation;Integrated Security=True;";
/// <summary>
/// 获取轴信息
/// </summary>
public static ProductionAxis GetAxisInfo(string axisCode)
{
using var conn = new SqlConnection(_connStr);
return conn.QueryFirstOrDefault<ProductionAxis>(
"SELECT * FROM ProductionAxis WHERE AxisCode = @AxisCode",
new { AxisCode = axisCode });
}
/// <summary>
/// 获取轴列表
/// </summary>
public static List<ProductionAxis> GetAxisList(string lineCode = "")
{
using var conn = new SqlConnection(_connStr);
var sql = "SELECT * FROM ProductionAxis WHERE 1=1";
if (!string.IsNullOrEmpty(lineCode))
{
sql += " AND LineCode = @LineCode";
}
return conn.Query<ProductionAxis>(sql, new { LineCode = lineCode }).ToList();
}
/// <summary>
/// 执行非查询语句
/// </summary>
public static int ExecuteNonQuery(string sql, object param = null)
{
using var conn = new SqlConnection(_connStr);
return conn.Execute(sql, param);
}
/// <summary>
/// 保存调度指令
/// </summary>
public static void SaveDispatchCommand(MesDispatchCommand command)
{
using var conn = new SqlConnection(_connStr);
conn.Execute(
"INSERT INTO MesDispatchCommand (CommandId, EventId, SourceTerminal, TargetTerminal, " +
"CommandType, AxisCode, CommandContent, CommandStatus, CreateTime) " +
"VALUES (@CommandId, @EventId, @SourceTerminal, @TargetTerminal, " +
"@CommandType, @AxisCode, @CommandContent, @CommandStatus, @CreateTime)",
command);
}
/// <summary>
/// 更新指令状态
/// </summary>
public static void UpdateCommandStatus(MesDispatchCommand command)
{
using var conn = new SqlConnection(_connStr);
conn.Execute(
"UPDATE MesDispatchCommand SET CommandStatus = @CommandStatus WHERE CommandId = @CommandId",
new { command.CommandStatus, command.CommandId });
}
/// <summary>
/// 获取指令日志
/// </summary>
public static DataTable GetCommandLog(string axisCode = "")
{
var dt = new DataTable();
using var conn = new SqlConnection(_connStr);
var sql = "SELECT CommandId, SourceTerminal, TargetTerminal, CommandType, " +
"CommandStatus, CreateTime FROM MesDispatchCommand WHERE 1=1";
if (!string.IsNullOrEmpty(axisCode))
{
sql += " AND AxisCode = @AxisCode";
}
sql += " ORDER BY CreateTime DESC";
using var adapter = new SqlDataAdapter(sql, conn);
if (!string.IsNullOrEmpty(axisCode))
{
adapter.SelectCommand.Parameters.AddWithValue("@AxisCode", axisCode);
}
adapter.Fill(dt);
return dt;
}
}
/// <summary>
/// 日志辅助类
/// </summary>
public static class LogHelper
{
public static void Info(string msg)
{
WriteLog("INFO", msg);
}
public static void Warn(string msg)
{
WriteLog("WARN", msg);
}
public static void Error(string msg, Exception ex = null)
{
var errMsg = ex == null ? msg : $"{msg}\n{ex.Message}\n{ex.StackTrace}";
WriteLog("ERROR", errMsg);
}
private static void WriteLog(string level, string msg)
{
var log = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] {msg}";
Console.WriteLine(log);
// 可选:写入日志文件/数据库
try
{
System.IO.File.AppendAllText("MES_Log.txt", log + Environment.NewLine);
}
catch { }
}
}
5. WinForm 可视化界面组件
/// <summary>
/// MES主支轴协同控件(核心界面组件)
/// </summary>
public partial class MesAxisCoopControl : UserControl
{
private readonly MesAxisEventCenter _eventCenter;
private readonly MesDispatchService _dispatchService;
private List<ProductionAxis> _axisList;
private ProductionAxis _selectedAxis;
// 绘图资源
private readonly Pen _mainAxisPen = new Pen(Color.DarkBlue, 3);
private readonly Pen _branchAxisPen = new Pen(Color.DarkGreen, 2);
private readonly Pen _selectedPen = new Pen(Color.Red, 4);
private readonly Pen _connPen = new Pen(Color.Orange, 1);
private readonly Font _axisFont = new Font("微软雅黑", 10, FontStyle.Bold);
public MesAxisCoopControl()
{
InitializeComponent();
DoubleBuffered = true; // 防止闪烁
// 初始化核心服务
_eventCenter = MesAxisEventCenter.Instance;
_dispatchService = new MesDispatchService();
// 注册事件委托
RegisterEventHandlers();
// 绑定UI事件
Paint += OnPaint;
MouseDown += OnMouseDown;
// 加载初始数据
LoadAxisData();
}
#region 数据加载
/// <summary>
/// 加载轴数据
/// </summary>
public void LoadAxisData(string lineCode = "")
{
try
{
_axisList = DbHelper.GetAxisList(lineCode);
cboAxis.DataSource = _axisList;
cboAxis.DisplayMember = "AxisName";
cboAxis.ValueMember = "AxisCode";
Refresh(); // 重绘界面
LogHelper.Info($"加载轴数据成功,共{_axisList.Count}条");
}
catch (Exception ex)
{
LogHelper.Error("加载轴数据失败", ex);
MessageBox.Show($"加载数据失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
#endregion
#region 事件委托注册与处理
/// <summary>
/// 注册事件委托
/// </summary>
private void RegisterEventHandlers()
{
_eventCenter.RegisterHandler(MesEventType.JobPlanStart, OnJobPlanStart);
_eventCenter.RegisterHandler(MesEventType.JobPlanPause, OnJobPlanPause);
_eventCenter.RegisterHandler(MesEventType.MaterialShortage, OnMaterialShortage);
_eventCenter.RegisterHandler(MesEventType.EquipmentException, OnEquipmentException);
}
/// <summary>
/// 计划启动事件处理
/// </summary>
private void OnJobPlanStart(object sender, MesAxisEventArgs e)
{
Invoke(new Action(() =>
{
txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] 轴{e.TriggerAxisCode}计划启动成功!\r\n");
LoadAxisData(); // 刷新数据
}));
}
/// <summary>
/// 计划暂停事件处理
/// </summary>
private void OnJobPlanPause(object sender, MesAxisEventArgs e)
{
Invoke(new Action(() =>
{
txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] 轴{e.TriggerAxisCode}计划暂停:{e.EventContent}\r\n");
LoadAxisData();
}));
}
/// <summary>
/// 物料缺料事件处理
/// </summary>
private void OnMaterialShortage(object sender, MesAxisEventArgs e)
{
Invoke(new Action(() =>
{
txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] 轴{e.TriggerAxisCode}物料缺料告警!\r\n");
MessageBox.Show($"轴{e.TriggerAxisCode}物料缺料!", "MES告警", MessageBoxButtons.OK, MessageBoxIcon.Warning);
LoadAxisData();
}));
}
/// <summary>
/// 设备异常事件处理
/// </summary>
private void OnEquipmentException(object sender, MesAxisEventArgs e)
{
Invoke(new Action(() =>
{
txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] 轴{e.TriggerAxisCode}设备异常:{e.EventContent}\r\n");
MessageBox.Show($"轴{e.TriggerAxisCode}设备异常!\n{e.EventContent}", "紧急告警", MessageBoxButtons.OK, MessageBoxIcon.Error);
LoadAxisData();
}));
}
#endregion
#region 界面绘制
private void OnPaint(object sender, PaintEventArgs e)
{
if (_axisList == null || _axisList.Count == 0) return;
var g = e.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
// 绘制主轴
var mainAxis = _axisList.FirstOrDefault(a => a.AxisType == AxisType.MainAxis);
if (mainAxis != null)
{
DrawAxis(g, mainAxis, 150, 100, true);
// 绘制支轴
var branchAxises = _axisList.Where(a => a.ParentAxisCode == mainAxis.AxisCode).ToList();
for (int i = 0; i < branchAxises.Count; i++)
{
var x = i % 2 == 0 ? 350 : 50;
var y = 100 + (i + 1) * 120;
DrawAxis(g, branchAxises[i], x, y, false);
// 绘制主轴-支轴连接线
g.DrawLine(_connPen, 200, 140, x + 50, y + 40);
DrawArrow(g, 200, 140, x + 50, y + 40);
}
}
}
/// <summary>
/// 绘制轴图形
/// </summary>
private void DrawAxis(Graphics g, ProductionAxis axis, int x, int y, bool isMainAxis)
{
var rect = new Rectangle(x, y, 100, 80);
var isSelected = _selectedAxis != null && _selectedAxis.AxisCode == axis.AxisCode;
var brush = GetStatusBrush(axis.CurrentStatus);
// 绘制背景和边框
g.FillRectangle(brush, rect);
g.DrawRectangle(isSelected ? _selectedPen : (isMainAxis ? _mainAxisPen : _branchAxisPen), rect);
// 绘制文本
g.DrawString(axis.AxisName, _axisFont, Brushes.Black, x + 5, y + 5);
g.DrawString($"状态:{axis.CurrentStatus}", new Font("微软雅黑", 8), Brushes.Black, x + 5, y + 30);
g.DrawString($"节拍:{axis.Beat}s", new Font("微软雅黑", 8), Brushes.Black, x + 5, y + 50);
}
/// <summary>
/// 绘制箭头
/// </summary>
private void DrawArrow(Graphics g, int startX, int startY, int endX, int endY)
{
var angle = Math.Atan2(endY - startY, endX - startX);
var p1 = new Point((int)(endX - 8 * Math.Cos(angle - Math.PI / 6)),
(int)(endY - 8 * Math.Sin(angle - Math.PI / 6)));
var p2 = new Point((int)(endX - 8 * Math.Cos(angle + Math.PI / 6)),
(int)(endY - 8 * Math.Sin(angle + Math.PI / 6)));
g.DrawLine(_connPen, endX, endY, p1);
g.DrawLine(_connPen, endX, endY, p2);
}
/// <summary>
/// 获取状态背景色
/// </summary>
private Brush GetStatusBrush(JobPlanStatus status)
{
return status switch
{
JobPlanStatus.Running => Brushes.LightGreen,
JobPlanStatus.Paused => Brushes.LightYellow,
JobPlanStatus.Exception => Brushes.LightPink,
JobPlanStatus.Completed => Brushes.LightBlue,
_ => Brushes.WhiteSmoke
};
}
#endregion
#region 界面交互
private void OnMouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left || _axisList == null) return;
// 检测点击的轴
_selectedAxis = null;
var mainAxis = _axisList.FirstOrDefault(a => a.AxisType == AxisType.MainAxis);
if (mainAxis != null && new Rectangle(150, 100, 100, 80).Contains(e.X, e.Y))
{
_selectedAxis = mainAxis;
}
else
{
var branchAxises = _axisList.Where(a => a.ParentAxisCode == mainAxis?.AxisCode).ToList();
for (int i = 0; i < branchAxises.Count; i++)
{
var x = i % 2 == 0 ? 350 : 50;
var y = 100 + (i + 1) * 120;
if (new Rectangle(x, y, 100, 80).Contains(e.X, e.Y))
{
_selectedAxis = branchAxises[i];
break;
}
}
}
// 更新选中轴信息
if (_selectedAxis != null)
{
lblAxisInfo.Text = $"轴编码:{_selectedAxis.AxisCode}\r\n" +
$"轴名称:{_selectedAxis.AxisName}\r\n" +
$"类型:{(int)_selectedAxis.AxisType == 0 ? "主轴" : "支轴"}\r\n" +
$"当前状态:{_selectedAxis.CurrentStatus}\r\n" +
$"绑定计划:{_selectedAxis.BindJobPlanNo}";
// 加载指令日志
dgvCommandLog.DataSource = DbHelper.GetCommandLog(_selectedAxis.AxisCode);
}
Refresh();
}
/// <summary>
/// 启动计划按钮点击
/// </summary>
private void btnStartPlan_Click(object sender, EventArgs e)
{
if (_selectedAxis == null || string.IsNullOrEmpty(_selectedAxis.BindJobPlanNo))
{
MessageBox.Show("请选择已绑定计划的轴!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// 构建事件参数
var eventArgs = new MesAxisEventArgs
{
EventType = MesEventType.JobPlanStart,
TriggerAxisCode = _selectedAxis.AxisCode,
JobPlanNo = _selectedAxis.BindJobPlanNo,
EventContent = JsonConvert.SerializeObject(new JobPlanStartContent
{
JobPlanNo = _selectedAxis.BindJobPlanNo,
Operator = Environment.UserName
})
};
// 触发事件
_eventCenter.TriggerEvent(eventArgs, this);
}
/// <summary>
/// 暂停计划按钮点击
/// </summary>
private void btnPausePlan_Click(object sender, EventArgs e)
{
if (_selectedAxis == null) return;
var reason = Microsoft.VisualBasic.Interaction.InputBox("请输入暂停原因:", "暂停计划", "人工干预");
if (string.IsNullOrEmpty(reason)) return;
var eventArgs = new MesAxisEventArgs
{
EventType = MesEventType.JobPlanPause,
TriggerAxisCode = _selectedAxis.AxisCode,
JobPlanNo = _selectedAxis.BindJobPlanNo,
EventContent = JsonConvert.SerializeObject(new JobPlanPauseContent
{
JobPlanNo = _selectedAxis.BindJobPlanNo,
Reason = reason
})
};
_eventCenter.TriggerEvent(eventArgs, this);
}
/// <summary>
/// 上报缺料按钮点击
/// </summary>
private void btnReportMaterialShortage_Click(object sender, EventArgs e)
{
if (_selectedAxis == null) return;
var materialCode = Microsoft.VisualBasic.Interaction.InputBox("物料编码:", "缺料上报", "");
var qtyStr = Microsoft.VisualBasic.Interaction.InputBox("缺料数量:", "缺料上报", "");
if (!int.TryParse(qtyStr, out int qty) || string.IsNullOrEmpty(materialCode)) return;
var eventArgs = new MesAxisEventArgs
{
EventType = MesEventType.MaterialShortage,
TriggerAxisCode = _selectedAxis.AxisCode,
EventContent = JsonConvert.SerializeObject(new MaterialShortageContent
{
MaterialCode = materialCode,
ShortageQty = qty
})
};
_eventCenter.TriggerEvent(eventArgs, this);
}
/// <summary>
/// 上报设备异常按钮点击
/// </summary>
private void btnReportEquipmentException_Click(object sender, EventArgs e)
{
if (_selectedAxis == null) return;
var eqCode = Microsoft.VisualBasic.Interaction.InputBox("设备编码:", "异常上报", "");
var desc = Microsoft.VisualBasic.Interaction.InputBox("异常描述:", "异常上报", "");
if (string.IsNullOrEmpty(eqCode) || string.IsNullOrEmpty(desc)) return;
var eventArgs = new MesAxisEventArgs
{
EventType = MesEventType.EquipmentException,
TriggerAxisCode = _selectedAxis.AxisCode,
EventContent = JsonConvert.SerializeObject(new EquipmentExceptionContent
{
EquipmentCode = eqCode,
ExceptionDesc = desc
})
};
_eventCenter.TriggerEvent(eventArgs, this);
}
#endregion
#region 资源释放
protected override void Dispose(bool disposing)
{
if (disposing)
{
// 注销委托
_eventCenter.RegisterHandler(MesEventType.JobPlanStart, OnJobPlanStart);
_eventCenter.RegisterHandler(MesEventType.JobPlanPause, OnJobPlanPause);
_eventCenter.RegisterHandler(MesEventType.MaterialShortage, OnMaterialShortage);
_eventCenter.RegisterHandler(MesEventType.EquipmentException, OnEquipmentException);
// 释放绘图资源
_mainAxisPen.Dispose();
_branchAxisPen.Dispose();
_selectedPen.Dispose();
_connPen.Dispose();
_axisFont.Dispose();
}
base.Dispose(disposing);
}
#endregion
#region 设计器生成的代码
private System.ComponentModel.IContainer components = null;
private ComboBox cboAxis;
private Button btnStartPlan;
private Button btnPausePlan;
private Button btnReportMaterialShortage;
private Button btnReportEquipmentException;
private Label lblAxisInfo;
private TextBox txtLog;
private DataGridView dgvCommandLog;
private void InitializeComponent()
{
this.cboAxis = new System.Windows.Forms.ComboBox();
this.btnStartPlan = new System.Windows.Forms.Button();
this.btnPausePlan = new System.Windows.Forms.Button();
this.btnReportMaterialShortage = new System.Windows.Forms.Button();
this.btnReportEquipmentException = new System.Windows.Forms.Button();
this.lblAxisInfo = new System.Windows.Forms.Label();
this.txtLog = new System.Windows.Forms.TextBox();
this.dgvCommandLog = new System.Windows.Forms.DataGridView();
this.SuspendLayout();
// cboAxis
this.cboAxis.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cboAxis.Location = new System.Drawing.Point(10, 10);
this.cboAxis.Name = "cboAxis";
this.cboAxis.Size = new System.Drawing.Size(200, 23);
this.cboAxis.TabIndex = 0;
// btnStartPlan
this.btnStartPlan.Location = new System.Drawing.Point(220, 10);
this.btnStartPlan.Name = "btnStartPlan";
this.btnStartPlan.Size = new System.Drawing.Size(80, 23);
this.btnStartPlan.Text = "启动计划";
this.btnStartPlan.Click += new System.EventHandler(this.btnStartPlan_Click);
// btnPausePlan
this.btnPausePlan.Location = new System.Drawing.Point(310, 10);
this.btnPausePlan.Name = "btnPausePlan";
this.btnPausePlan.Size = new System.Drawing.Size(80, 23);
this.btnPausePlan.Text = "暂停计划";
this.btnPausePlan.Click += new System.EventHandler(this.btnPausePlan_Click);
// btnReportMaterialShortage
this.btnReportMaterialShortage.Location = new System.Drawing.Point(400, 10);
this.btnReportMaterialShortage.Name = "btnReportMaterialShortage";
this.btnReportMaterialShortage.Size = new System.Drawing.Size(80, 23);
this.btnReportMaterialShortage.Text = "上报缺料";
this.btnReportMaterialShortage.Click += new System.EventHandler(this.btnReportMaterialShortage_Click);
// btnReportEquipmentException
this.btnReportEquipmentException.Location = new System.Drawing.Point(490, 10);
this.btnReportEquipmentException.Name = "btnReportEquipmentException";
this.btnReportEquipmentException.Size = new System.Drawing.Size(80, 23);
this.btnReportEquipmentException.Text = "设备异常";
this.btnReportEquipmentException.Click += new System.EventHandler(this.btnReportEquipmentException_Click);
// lblAxisInfo
this.lblAxisInfo.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.lblAxisInfo.Location = new System.Drawing.Point(10, 40);
this.lblAxisInfo.Name = "lblAxisInfo";
this.lblAxisInfo.Size = new System.Drawing.Size(200, 120);
this.lblAxisInfo.Text = "轴信息";
// txtLog
this.txtLog.Location = new System.Drawing.Point(10, 170);
this.txtLog.Multiline = true;
this.txtLog.Name = "txtLog";
this.txtLog.ReadOnly = true;
this.txtLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.txtLog.Size = new System.Drawing.Size(200, 200);
// dgvCommandLog
this.dgvCommandLog.AllowUserToAddRows = false;
this.dgvCommandLog.AllowUserToDeleteRows = false;
this.dgvCommandLog.Location = new System.Drawing.Point(220, 40);
this.dgvCommandLog.Name = "dgvCommandLog";
this.dgvCommandLog.ReadOnly = true;
this.dgvCommandLog.Size = new System.Drawing.Size(350, 330);
this.dgvCommandLog.TabIndex = 1;
// MesAxisCoopControl
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.cboAxis);
this.Controls.Add(this.btnStartPlan);
this.Controls.Add(this.btnPausePlan);
this.Controls.Add(this.btnReportMaterialShortage);
this.Controls.Add(this.btnReportEquipmentException);
this.Controls.Add(this.lblAxisInfo);
this.Controls.Add(this.txtLog);
this.Controls.Add(this.dgvCommandLog);
this.Name = "MesAxisCoopControl";
this.Size = new System.Drawing.Size(600, 400);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
}
/// <summary>
/// MES协同主窗体
/// </summary>
public partial class MesCoopMainForm : Form
{
public MesCoopMainForm()
{
InitializeComponent();
// 添加核心控件
var coopControl = new MesAxisCoopControl();
coopControl.Dock = DockStyle.Fill;
this.Controls.Add(coopControl);
this.Text = "MES生产作业计划协同中间件";
this.Size = new Size(650, 450);
this.StartPosition = FormStartPosition.CenterScreen;
}
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Text = "MES生产作业计划协同中间件";
}
}
// 程序入口
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MesCoopMainForm());
}
}
6. 数据库初始化脚本
sql
-- 产线表
CREATE TABLE ProductionLine (
LineCode VARCHAR(50) PRIMARY KEY,
LineName VARCHAR(100) NOT NULL,
Status VARCHAR(20) DEFAULT '正常'
);
-- 产线轴表
CREATE TABLE ProductionAxis (
AxisCode VARCHAR(50) PRIMARY KEY,
AxisName VARCHAR(100) NOT NULL,
LineCode VARCHAR(50) NOT NULL,
AxisType INT NOT NULL, -- 0=主轴,1=支轴
ParentAxisCode VARCHAR(50),
BindJobPlanNo VARCHAR(50),
CurrentStatus INT DEFAULT 0, -- 0=待执行,1=执行中,2=暂停,3=已完成,4=异常,5=已取消
Beat INT DEFAULT 60,
FOREIGN KEY (LineCode) REFERENCES ProductionLine(LineCode)
);
-- 作业计划表
CREATE TABLE MesCoopJobPlan (
JobPlanNo VARCHAR(50) PRIMARY KEY,
JobPlanName VARCHAR(200) NOT NULL,
LineCode VARCHAR(50) NOT NULL,
MainAxisCode VARCHAR(50) NOT NULL,
ProductCode VARCHAR(50) NOT NULL,
PlanQty INT NOT NULL,
CompletedQty INT DEFAULT 0,
PlanStatus INT DEFAULT 0, -- 同JobPlanStatus
PlanStartTime DATETIME NOT NULL,
ActualStartTime DATETIME
);
-- 调度指令表
CREATE TABLE MesDispatchCommand (
CommandId VARCHAR(50) PRIMARY KEY,
EventId VARCHAR(50) NOT NULL,
SourceTerminal VARCHAR(50) NOT NULL,
TargetTerminal VARCHAR(50) NOT NULL,
CommandType INT NOT NULL, -- 同MesEventType
AxisCode VARCHAR(50) NOT NULL,
CommandContent NVARCHAR(MAX),
CommandStatus INT DEFAULT 0, -- 同CommandStatus
CreateTime DATETIME DEFAULT GETDATE()
);
-- 测试数据
INSERT INTO ProductionLine (LineCode, LineName) VALUES ('LINE-001', '总装线');
INSERT INTO ProductionAxis (AxisCode, AxisName, LineCode, AxisType, ParentAxisCode, Beat)
VALUES ('MA-001', '总装主轴', 'LINE-001', 0, NULL, 60),
('BA-001-01', '预装工位', 'LINE-001', 1, 'MA-001', 50),
('BA-001-02', '检测工位', 'LINE-001', 1, 'MA-001', 70);
INSERT INTO MesCoopJobPlan (JobPlanNo, JobPlanName, LineCode, MainAxisCode, ProductCode, PlanQty, PlanStartTime)
VALUES ('JP-20260101-001', 'A产品总装计划', 'LINE-001', 'MA-001', 'PROD-001', 1000, '2026-01-01 08:00:00');
UPDATE ProductionAxis SET BindJobPlanNo = 'JP-20260101-001' WHERE AxisCode IN ('MA-001', 'BA-001-01', 'BA-001-02');
三、部署与使用说明
1. 环境准备
- .NET Framework 4.8 或更高版本
- SQL Server 2016 或更高版本
- Visual Studio 2019/2022(WinForm 开发环境)
2. 部署步骤
- 执行数据库初始化脚本,创建 MES_Cooperation 数据库及表结构
- 在 Visual Studio 中创建 WinForm 项目,引用 Newtonsoft.Json 和 Dapper NuGet 包
- 将上述代码复制到项目中,调整数据库连接字符串
- 编译并运行项目
3. 核心功能使用
- 轴选择:下拉框选择轴,或直接点击界面上的轴图形
- 计划操作:启动 / 暂停作业计划,自动触发主支轴联动
- 异常上报:上报物料缺料 / 设备异常,自动暂停关联轴并触发告警
- 指令日志:实时查看多指令端的调度指令执行情况
- 事件日志:查看所有事件的触发和处理记录
四、总结
关键点回顾
- 主支轴事件委托封装:通过单例事件中心实现委托的注册 / 触发 / 注销,解耦主支轴依赖,支持异步事件处理
- 多指令调度模式:基于事件自动生成调度指令,分发到 MES 计划端、物料调拨端等多指令端,确保协同一致性
- 可视化 WinForm 组件:集成轴状态可视化、操作按钮、日志展示,支持鼠标交互和状态实时刷新
- 工业级可靠性:包含异常处理、日志记录、数据库事务,符合 MES 系统的稳定性要求
该组件可直接集成到现有 MES 系统中,支持产线工站流程的主支轴协同调度,适配多指令端的交互需求,具备良好的扩展性和维护性。
更多推荐


所有评论(0)