C#转盘电机的上位机数据采集仿真模拟,使用多个异步任务
本文是一种仿真模拟软件,适应于转盘型电机。主要功能:启动,运行【旋转60°】,清料。下料时保存相关生产数据
假设一个转盘型电机,共有6个工位,分别执行动作:【A】上料、【B】扫码、【C】采集轮廓仪数据、【D】拍照、【E】焊接、【F】下料。每旋转60°后,6个工位同时开始工作,所有工位都完成以后,方可执行下一步的转动60°操作。因此需要定义一个类TurntableData数组,固定含有6个数组。
一、新建windows窗体应用程序TurntableDemo。【.net framwork 4.5】,将默认的Form1窗体重命名为FormTurntable。
窗体FormTurntable设计如图:
二、新建转盘所有数据对象类TurntableData.cs,源程序如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TurntableDemo
{
/// <summary>
/// 转盘【六个工位】所有数据组成的统一对象
/// </summary>
public class TurntableData
{
/// <summary>
/// 【A】上料:自增的唯一编号
/// </summary>
public int Id { get; set; }
/// <summary>
/// 【B】扫码:通过扫码枪获取二维码条码
/// </summary>
public string Barcode { get; set; }
/// <summary>
/// 【C】采集轮廓仪数据
/// </summary>
public double ContourData { get; set; }
/// <summary>
/// 【D】拍照相机偏移数据X
/// </summary>
public double PhotoX { get; set; }
/// <summary>
/// 【D】拍照相机偏移数据Y
/// </summary>
public double PhotoY { get; set; }
/// <summary>
/// 【E】焊接工位:焊接功率
/// </summary>
public double WeldPower { get; set; }
/// <summary>
/// 【E】焊接工位:焊接速度
/// </summary>
public double WeldSpeed { get; set; }
/// <summary>
/// 【F】下料工位:生产完成时间
/// </summary>
public DateTime ProductTime { get; set; }
}
}
三、新建保存生产数据文件类CsvUtil.cs,源程序如下:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TurntableDemo
{
/// <summary>
/// Csv操作类
/// </summary>
public class CsvUtil
{
private static Object thisLock = new Object();
/// <summary>
/// 写入csv文件
/// </summary>
/// <param name="_path">路径,如 @"D:\MESLog\ABC\"</param>
/// <param name="_name">名称,不带.csv</param>
/// <param name="_writeData">需要写入的数据</param>
/// <param name="_append">是否拼接</param>
/// <returns></returns>
public static bool WriteCsv(string _path, string _name, List<string[]> _writeData, bool _append)
{
lock (thisLock)
{
try
{
//判断文件夹是否存在,不存在就创建
DirectoryInfo directoryInfo = new DirectoryInfo(_path);
if (!directoryInfo.Exists)
{
directoryInfo.Create();
}
string _tmPath = Path.Combine(_path, _name) + ".csv";
using (StreamWriter write = new StreamWriter(_tmPath, _append, Encoding.Default))
{
foreach (String[] strArr in _writeData)
{
write.WriteLine(String.Join(",", strArr));
}
}
return true;
}
catch (Exception e)
{
System.Windows.Forms.MessageBox.Show("CSV文件写入失败:" + e.Message);
return false;
}
}
}
/// <summary>
/// 保存转盘生产数据到csv文件
/// </summary>
/// <param name="alarmData"></param>
/// <returns></returns>
public static bool SaveProductData(TurntableData turntableData)
{
try
{
string path = AppDomain.CurrentDomain.BaseDirectory + "ProductData\\";
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
string fileName = DateTime.Now.ToString("yyyy-MM-dd");
//写字段名
List<string> listName = new List<string>();
listName.Add("编号");
listName.Add("条码");
listName.Add("轮廓数据");
listName.Add("拍照X偏移");
listName.Add("拍照Y偏移");
listName.Add("焊接输出功率");
listName.Add("焊接速度");
listName.Add("生产时间");
List<string[]> iniList = new List<string[]>();
iniList.Add(listName.ToArray());
bool ret = File.Exists(path + fileName + ".csv");
if (!ret)
{
bool ret2 = WriteCsv(path, fileName, iniList, false);
if (!ret2)
{
throw new Exception("生产日志字段名转换为csv格式失败,请检查文件是否被打开、被占用:" + path);
}
}
iniList.Clear();
List<string> listData = new List<string>();
listData.Add(turntableData.Id.ToString());
listData.Add(turntableData.Barcode);
listData.Add(turntableData.ContourData.ToString());
listData.Add(turntableData.PhotoX.ToString());
listData.Add(turntableData.PhotoY.ToString());
listData.Add(turntableData.WeldPower.ToString());
listData.Add(turntableData.WeldSpeed.ToString());
listData.Add(turntableData.ProductTime.ToString("yyyy-MM-dd HH:mm:ss"));
iniList.Add(listData.ToArray());
bool rtn = WriteCsv(path, fileName, iniList, true);
if (!rtn)
{
throw new Exception("生产厂日志具体信息转换为本地csv失败,请检查文件是否被打开、被占用:" + path);
}
return true;
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(string.Format("保存生产日志出现异常:{0}", ex.Message), "出错");
return false;
}
}
}
}
四、新建转盘电机每旋转一次,执行的主要流程逻辑类RotateUtil.cs,源程序如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TurntableDemo
{
/// <summary>
/// 旋转60°执行的操作
/// </summary>
public class RotateUtil
{
/// <summary>
/// 六个转盘数据数组,每旋转60°,所有元素的对象都要更新
/// 依次代表A、B、C、D、E、F工位
/// </summary>
public static TurntableData[] TurntableDataArray = new TurntableData[6];
/// <summary>
/// A上料工位对应的编号数据
/// </summary>
private static int CurrentId = 0;
/// <summary>
/// 当前第几次旋转60°操作
/// </summary>
public static int Sequence = 0;
/// <summary>
/// 是否触发清料
/// </summary>
public static bool TrigCleaning = false;
/// <summary>
/// 初始化转盘
/// </summary>
public static void InitTurntable()
{
Sequence = 0;
TrigCleaning = false;
for (int i = 0; i < TurntableDataArray.Length; i++)
{
TurntableDataArray[i] = new TurntableData()
{
Id = -1,
Barcode = string.Empty
};
}
}
/// <summary>
/// 电机转动一次的数据处理
/// 关键逻辑:①同时启动6个任务,并等待6个任务全部完成 ②依次进行偏移一个索引赋值,并修改当前值 ③保存数据,转动次数加一
/// </summary>
public static bool Rotate()
{
TurntableData turntable = new TurntableData();
//一、同时开始六个工位的任务
Task taskA = Task.Factory.StartNew(() =>
{
if (TrigCleaning)
{
//当触发清料时,强行置默认值-1
turntable.Id = -1;
}
else
{
turntable.Id = Feeding();
}
});
Task taskB = Task.Factory.StartNew(() =>
{
if (Sequence >= 1)
{
turntable.Barcode = GetBarcode();
}
});
Task taskC = Task.Factory.StartNew(() =>
{
if (Sequence >= 2)
{
turntable.ContourData = GetContourData();
}
});
Task taskD = Task.Factory.StartNew(() =>
{
if (Sequence >= 3)
{
double photoX;
double photoY;
GetPhotoData(out photoX, out photoY);
turntable.PhotoX = photoX;
turntable.PhotoY = photoY;
}
});
Task taskE = Task.Factory.StartNew(() =>
{
if (Sequence >= 4)
{
double weldPower;
double weldSpeed;
GetWeldData(out weldPower, out weldSpeed);
turntable.WeldPower = weldPower;
turntable.WeldSpeed = weldSpeed;
}
});
Task taskF = Task.Factory.StartNew(() =>
{
if (Sequence >= 5)
{
turntable.ProductTime = Blanking();
}
});
//二、等待六个工位完成任务 在6秒内,是否已完成执行所有任务
int millisecondsTimeout = 6000;
bool completedExecute = Task.WaitAll(new Task[] { taskA, taskB, taskC, taskD, taskE, taskF }, millisecondsTimeout);
if (!completedExecute)
{
MessageBox.Show($"等待【{millisecondsTimeout}】ms六个工位存在尚未完成,请检查", "超时");
return false;
}
//三、依次进行偏移一个索引赋值 A->B->C->D->E->F
for (int i = TurntableDataArray.Length - 1; i > 0; i--)
{
TurntableDataArray[i].Id = TurntableDataArray[i - 1].Id;
TurntableDataArray[i].Barcode = TurntableDataArray[i - 1].Barcode;
TurntableDataArray[i].ContourData = TurntableDataArray[i - 1].ContourData;
TurntableDataArray[i].PhotoX = TurntableDataArray[i - 1].PhotoX;
TurntableDataArray[i].PhotoY = TurntableDataArray[i - 1].PhotoY;
TurntableDataArray[i].WeldPower = TurntableDataArray[i - 1].WeldPower;
TurntableDataArray[i].WeldSpeed = TurntableDataArray[i - 1].WeldSpeed;
TurntableDataArray[i].ProductTime = TurntableDataArray[i - 1].ProductTime;
}
//修正当前值
TurntableDataArray[0].Id = turntable.Id;
TurntableDataArray[1].Barcode = turntable.Barcode;
TurntableDataArray[2].ContourData = turntable.ContourData;
TurntableDataArray[3].PhotoX = turntable.PhotoX;
TurntableDataArray[3].PhotoY = turntable.PhotoY;
TurntableDataArray[4].WeldPower = turntable.WeldPower;
TurntableDataArray[4].WeldSpeed = turntable.WeldSpeed;
TurntableDataArray[5].ProductTime = turntable.ProductTime;
//四、保存数据,只用保存最后一个数据
if (TurntableDataArray[5].Id != -1)
{
CsvUtil.SaveProductData(TurntableDataArray[5]);
}
//转台 转动次数加1
Sequence++;
return true;
}
#region 六个工位分别执行的动作
/// <summary>
/// A工位上料,用时100~500ms
/// </summary>
/// <returns></returns>
public static int Feeding()
{
Thread.Sleep(GetRandomTimespan(100, 500));
Interlocked.Add(ref CurrentId, 1);
return CurrentId;
}
/// <summary>
/// B工位扫码,用时200~3000ms
/// </summary>
/// <returns></returns>
public static string GetBarcode()
{
Thread.Sleep(GetRandomTimespan(200, 3000));
return System.IO.Path.GetRandomFileName();
}
/// <summary>
/// C工位采集轮廓仪数据,用时300~1800ms
/// </summary>
/// <returns></returns>
public static double GetContourData()
{
Thread.Sleep(GetRandomTimespan(300, 1800));
return new Random().Next(-100, 101) * 0.001;
}
/// <summary>
/// D工位 获取拍照偏移数据,用时700~2400ms
/// </summary>
/// <param name="photoX"></param>
/// <param name="photoY"></param>
public static void GetPhotoData(out double photoX, out double photoY)
{
Thread.Sleep(GetRandomTimespan(700, 2400));
photoX = new Random().Next(-300, 301) * 0.01;
photoY = new Random().Next(-200, 201) * 0.01;
}
/// <summary>
/// E工位 获取焊接相关数据,用时2200~5000ms
/// </summary>
/// <param name="weldPower"></param>
/// <param name="weldSpeed"></param>
public static void GetWeldData(out double weldPower, out double weldSpeed)
{
Thread.Sleep(GetRandomTimespan(2200, 5000));
weldPower = new Random().Next(400, 601);
weldSpeed = new Random().Next(100, 301);
}
/// <summary>
/// F工位 下料
/// </summary>
public static DateTime Blanking()
{
Thread.Sleep(100);
return DateTime.Now;
}
#endregion
/// <summary>
/// 获得一个指定范围内的随机时间间隔,单位:毫秒
/// </summary>
/// <param name="minValue"></param>
/// <param name="maxValue"></param>
/// <returns></returns>
private static int GetRandomTimespan(int minValue, int maxValue)
{
return new Random(Guid.NewGuid().GetHashCode()).Next(minValue, maxValue);
}
}
}
五、窗体FormTurntable.cs的关键逻辑代码【事件】如下(忽略设计器自动生成的代码):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TurntableDemo
{
public partial class FormTurntable : Form
{
/*
* 一个转盘型电机,共有6个工位,分别执行动作:【A】上料、【B】扫码、【C】采集轮廓仪数据、【D】拍照、【E】焊接、【F】下料
* 每旋转60°后,6个工位同时开始工作,所有工位都完成以后,方可执行下一步的转动60°操作
* 【A】上料:自增的唯一编号int Id
* 【B】扫码:通过扫码枪获取二维码条码 string Barcode
* 【C】采集轮廓仪数据 double ContourData
* 【D】拍照相机偏移数据 double PhotoX,double PhotoY
* 【E】焊接功率,焊接速度 double WeldPower,double WeldSpeed
* 【F】下料,生产完成时间,记录数据到本地,出站 DateTime ProductTime
* 因此,需要定义一个结构数组,【所有工位数据组成的结构】,共有6个元素,每次旋转60°,所有结构数据都要更新
*/
public FormTurntable()
{
InitializeComponent();
}
private void FormTurntable_Load(object sender, EventArgs e)
{
btnStart.Enabled = true;
btnNext.Enabled = false;
btnCleaning.Enabled = false;
}
private void btnStart_Click(object sender, EventArgs e)
{
btnStart.Enabled = false;
btnNext.Enabled = true;
btnCleaning.Enabled = true;
RotateUtil.InitTurntable();
DisplayMessage("初始化转盘成功...");
DisplayMessage($"启动转盘电机,已执行【A】上料。当前序列【{RotateUtil.Sequence}】");
}
private void btnNext_Click(object sender, EventArgs e)
{
DisplayMessage($"开始同时执行6个工位操作,执行中...");
Application.DoEvents();
RotateAndPrintMessage();
}
private void btnCleaning_Click(object sender, EventArgs e)
{
RotateUtil.TrigCleaning = true;
DisplayMessage($"当前开始进行清料操作,当前序列【{RotateUtil.Sequence}】");
btnNext.Enabled = false;
btnCleaning.Enabled = false;
Application.DoEvents();
//这里执行清料逻辑
while (ExistProduct(RotateUtil.TurntableDataArray))
{
RotateAndPrintMessage();
}
btnStart.Enabled = true;
RotateUtil.TrigCleaning = false;
DisplayMessage("清料完成");
}
/// <summary>
/// 是否存在产品未出站,所有的产品都出站完成,则清料完成
/// </summary>
/// <param name="turntableDataArray"></param>
/// <returns></returns>
private bool ExistProduct(TurntableData[] turntableDataArray)
{
for (int i = 0; i < turntableDataArray.Length; i++)
{
if (turntableDataArray[i].Id != -1)
{
return true;
}
}
return false;
}
/// <summary>
/// 转盘转动60°并打印相关信息
/// </summary>
private void RotateAndPrintMessage()
{
bool result = RotateUtil.Rotate();
DisplayMessage($"当前转盘执行完成:【{result}】,当前第【{RotateUtil.Sequence}】次转动");
Application.DoEvents();
for (int i = 0; i < RotateUtil.TurntableDataArray.Length; i++)
{
DisplayMessage(RotateUtil.TurntableDataArray[i], i);
}
Application.DoEvents();
}
/// <summary>
/// 显示富文本框的消息
/// </summary>
/// <param name="message"></param>
private void DisplayMessage(string message)
{
this.BeginInvoke(new Action(() =>
{
if (rtxtDisplay.TextLength >= 102400)
{
rtxtDisplay.Clear();
}
rtxtDisplay.AppendText($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} --> {message}\n");
rtxtDisplay.ScrollToCaret();
}));
}
/// <summary>
/// 显示当前工位数据
/// </summary>
/// <param name="data"></param>
/// <param name="index"></param>
private void DisplayMessage(TurntableData data, int index)
{
List<string> list = new List<string>();
list.Add($"编号:【{data.Id}】");
list.Add($"条码:【{data.Barcode}】");
list.Add($"轮廓数据:【{data.ContourData}】");
list.Add($"拍照X偏移:【{data.PhotoX}】");
list.Add($"拍照Y偏移:【{data.PhotoY}】");
list.Add($"焊接输出功率:【{data.WeldPower}】");
list.Add($"焊接速度:【{data.WeldSpeed}】");
list.Add($"生产时间:【{data.ProductTime}】");
if (data.Id == -1)
{
DisplayMessage($"当前第【{(index + 1)}】个工位数据:暂无数据");
}
else
{
DisplayMessage($"当前第【{(index + 1)}】个工位数据:{string.Join(",", list)}");
}
}
}
}
六、程序运行如图:
执行清料后: