using Autofac;
|
using Bro.Common.Factory;
|
using Bro.Common.Helper;
|
using Bro.Common.Interface;
|
using Bro.Common.Model;
|
using System;
|
using System.Collections;
|
using System.Collections.Generic;
|
using System.ComponentModel;
|
using System.ComponentModel.Design;
|
using System.Diagnostics;
|
using System.Drawing.Design;
|
using System.IO;
|
using System.Linq;
|
using System.Threading;
|
using System.Threading.Tasks;
|
using System.Windows.Forms;
|
using System.Windows.Forms.Design;
|
using static Bro.Common.Helper.EnumHelper;
|
|
namespace Bro.Common.Base
|
{
|
public abstract class PLCBase : DeviceBase, IMonitor, IMotion
|
{
|
public PLCInitialConfigBase PLCIConfig
|
{
|
get => InitialConfig as PLCInitialConfigBase;
|
}
|
|
public abstract List<int> Read(int startAddress, int length);
|
|
public abstract void ReadItem(PLCItem item);
|
|
public abstract void WriteItem(PLCItem item, bool waitForReply = true);
|
|
public abstract void WriteSingleAddress(int address, int writeValue, bool waitForReply = true);
|
|
#region IMonitor
|
public event Action<DateTime, string, IDevice, IMonitorSet> OnMonitorInvoke;
|
public event Action<DateTime, IDevice, IWarningSet> OnMonitorAlarm;
|
|
public List<int> MonitorValues { get; set; } = new List<int>();
|
|
public abstract List<int> GetMonitorValues(int startAddress, int length);
|
|
public virtual void Monitor()
|
{
|
if (PLCIConfig != null
|
&& PLCIConfig.EventStartAddress > 0
|
&& PLCIConfig.EventLength > 0
|
&& PLCIConfig.MonitorInterval >= 0)
|
{
|
while (CurrentState != EnumHelper.DeviceState.DSClose && CurrentState != EnumHelper.DeviceState.DSExcept)
|
{
|
if (!PLCIConfig.IsEnableMonitor)
|
return;
|
|
try
|
{
|
List<int> newValues = GetMonitorValues(PLCIConfig.EventStartAddress, PLCIConfig.EventLength);
|
|
if (newValues == null || newValues.Count == 0)
|
continue;
|
|
Stopwatch sw = new Stopwatch();
|
sw.Start();
|
if (MonitorValues.Count == newValues.Count)
|
{
|
var tempNew = new List<int>(newValues);
|
var tempOld = new List<int>(MonitorValues);
|
MonitorCheckAndInvoke(tempNew, tempOld);
|
}
|
MonitorValues = new List<int>(newValues);
|
sw.Stop();
|
|
if (sw.ElapsedMilliseconds > 20)
|
{
|
LogAsync(DateTime.Now, $"{this.Name}轮询时间:{sw.ElapsedMilliseconds}", "");
|
}
|
|
if (PLCIConfig.MonitorInterval > 0)
|
{
|
Thread.Sleep(PLCIConfig.MonitorInterval);
|
}
|
}
|
catch (Exception ex)
|
{
|
if (CurrentState == DeviceState.DSOpen)
|
{
|
LogAsync(DateTime.Now, $"{this.Name}监听异常", ex.GetExceptionMessage());
|
}
|
}
|
}
|
}
|
}
|
|
protected virtual void MonitorCheckAndInvoke(List<int> tempNew, List<int> tempOld)
|
{
|
#region PLC警报信息
|
//PLCIConfig.WarningSetCollection.ForEach(w =>
|
Parallel.ForEach(PLCIConfig.WarningSetCollection, w =>
|
{
|
if (w.WarningIndex_Word < 0 || w.WarningIndex_Word >= tempNew.Count)
|
return;
|
|
if (w.WarningIndex_Bit < 0 || w.WarningIndex_Bit > 16)
|
return;
|
|
bool isOn = ((tempNew[w.WarningIndex_Word] >> w.WarningIndex_Bit) & 1) == (w.TriggerValue ? 1 : 0);
|
|
if (w.CurrentStatus != isOn)
|
{
|
w.CurrentStatus = isOn;
|
w.TriggerTime = DateTime.Now;
|
|
SaveAlarmCSVAsync(DateTime.Now, this.Name, w);
|
OnMonitorAlarm?.BeginInvoke(DateTime.Now, this, w, null, null);
|
}
|
});
|
#endregion
|
|
//PLCIConfig.MonitorSetCollection.ForEach(set =>
|
Parallel.ForEach(PLCIConfig.MonitorSetCollection, set =>
|
{
|
MonitorSet monitorSet = set as MonitorSet;
|
if (monitorSet.TriggerIndex < 0 || monitorSet.TriggerIndex >= tempNew.Count)
|
{
|
return;
|
}
|
|
int newValue = tempNew[monitorSet.TriggerIndex];
|
int oldValue = tempOld[monitorSet.TriggerIndex];
|
|
if (newValue != oldValue)
|
{
|
if (monitorSet.TriggerValue == -999 || newValue == monitorSet.TriggerValue)
|
{
|
if (monitorSet.OpConfig == null)
|
{
|
monitorSet.OpConfig = new OperationConfigBase();
|
}
|
|
monitorSet.OpConfig.InputPara = monitorSet.InputDataIndex.ConvertAll(index =>
|
{
|
return tempNew[index];
|
}).ToList();
|
|
OnMonitorInvoke?.BeginInvoke(DateTime.Now, monitorSet.InvokeDevice, this, monitorSet, OnMethodInvoked, monitorSet);
|
}
|
}
|
});
|
}
|
|
private void OnMethodInvoked(IAsyncResult ar)
|
{
|
MonitorSet monitorSet = ar.AsyncState as MonitorSet;
|
|
ProcessResponse resValues = monitorSet.Response;
|
|
if (resValues.ResultValue == (int)ReplyValue.IGNORE)
|
{
|
return;
|
}
|
|
Stopwatch sw = new Stopwatch();
|
sw.Start();
|
|
if (monitorSet.ReplyDataAddress != -1 && resValues.DataList.Count > 0)
|
{
|
PLCItem item = new PLCItem
|
{
|
ItemLength = resValues.DataList.Count,
|
Address = monitorSet.ReplyDataAddress.ToString(),
|
ItemValues = resValues.DataList.ConvertAll(s => int.Parse(s.ToString())).ToList()
|
};
|
WriteItem(item, false);
|
}
|
|
if (monitorSet.NoticeAddress != -1)
|
{
|
int repeatTime = 5;
|
|
//LogAsync(DateTime.Now, methodCode + "开始反馈", "");
|
do
|
{
|
try
|
{
|
WriteSingleAddress(monitorSet.NoticeAddress, resValues.ResultValue, false);
|
repeatTime = 0;
|
}
|
catch (Exception ex)
|
{
|
repeatTime--;
|
|
if (repeatTime <= 0)
|
{
|
new ProcessException("PLC反馈写入异常", ex, ExceptionLevel.Warning);
|
}
|
}
|
} while (repeatTime > 0);
|
}
|
|
sw.Stop();
|
LogAsync(DateTime.Now, $"{Name}反馈完成,耗时{sw.ElapsedMilliseconds}ms", $"{resValues.GetDisplayText()}");
|
}
|
|
#region Alarm
|
public virtual void ResetAlarm()
|
{
|
PLCIConfig.WarningSetCollection.ForEach(u => u.CurrentStatus = !u.TriggerValue);
|
}
|
|
object _alarmLock = new object();
|
private async void SaveAlarmCSVAsync(DateTime now, string plcName, WarningSet ws)
|
{
|
await Task.Run(() =>
|
{
|
lock (_alarmLock)
|
{
|
DirectoryInfo dir = new DirectoryInfo(this.PLCIConfig.LogPath);
|
if (!dir.Exists)
|
{
|
dir.Create();
|
}
|
|
string path = Path.Combine(PLCIConfig.LogPath, $"Alarm_{Name}_{now.ToString("yyyyMMdd")}.csv");
|
bool fileExist = File.Exists(path);
|
|
using (StreamWriter writer = new StreamWriter(path, true, System.Text.Encoding.UTF8))
|
{
|
if (!fileExist)
|
{
|
writer.WriteLine("Time,Source,AlarmCode,AlarmDescription,AlarmStatus");
|
}
|
|
writer.WriteLine($"{now.ToString("HH:mm:ss.fff")},{plcName},{ws.WarningCode},{ws.WarningDescription},{(ws.CurrentStatus ? "报警" : "停止")}");
|
|
writer.Flush();
|
writer.Close();
|
}
|
}
|
});
|
}
|
#endregion
|
#endregion
|
|
protected override void Pause()
|
{
|
}
|
|
protected override void Resume()
|
{
|
}
|
|
protected override void Start()
|
{
|
Task.Run(() =>
|
{
|
Monitor();
|
});
|
}
|
|
#region IMotion
|
public List<AxisInfo> GetCurrentAxisInfo(params string[] axisName)
|
{
|
List<PLCMotionDefinition_State> mds = new List<PLCMotionDefinition_State>(PLCIConfig.MotionStateCollection);
|
|
if (axisName.Length > 0)
|
{
|
mds = mds.Where(u => axisName.Contains(u.AxisName)).ToList();
|
}
|
|
var locations = mds.ConvertAll(u =>
|
{
|
AxisInfo axisInfo = new AxisInfo();
|
axisInfo.AxisName = u.AxisName;
|
axisInfo.AxisLocation = MonitorValues.Skip(u.RegisterIndex).Take(2).ToList().ParseUnsignShortListToInt()[0];
|
return axisInfo;
|
}).ToList();
|
|
return locations;
|
}
|
|
public bool MoveToPoint(IOperationConfig opConfig)
|
{
|
throw new NotImplementedException();
|
}
|
#endregion
|
}
|
|
public abstract class PLCOperationConfigBase : OperationConfigBase
|
{
|
/// <summary>
|
/// 需要操作的PLC项
|
/// </summary>
|
public List<PLCItem> Items { get; set; } = new List<PLCItem>();
|
}
|
|
public class PLCInitialConfigBase : InitialConfigBase, IMonitorConfig
|
{
|
[Category("驱动类型")]
|
[Description("驱动类型")]
|
[DisplayName("驱动类型")]
|
[TypeConverter(typeof(PLCTypeConverter))]
|
public override string DriverType { get; set; }
|
|
[Category("警报配置")]
|
[Description("警报配置列表")]
|
[DisplayName("警报配置")]
|
[TypeConverter(typeof(CollectionCountConvert))]
|
[Editor(typeof(WarningSetsEditor), typeof(UITypeEditor))]
|
public List<WarningSet> WarningSetCollection { get; set; } = new List<WarningSet>();
|
|
#region IMonitorConfig
|
[Category("监听设置")]
|
[Description("监听操作配置集合")]
|
[DisplayName("监听配置")]
|
[TypeConverter(typeof(CollectionCountConvert))]
|
[Editor(typeof(ComplexCollectionEditor<MonitorSet>), typeof(UITypeEditor))]
|
public List<IMonitorSet> MonitorSetCollection { get; set; } = new List<IMonitorSet>();
|
|
[Category("监听设置")]
|
[Description("true:启动监听 false:关闭监听")]
|
[DisplayName("监听启用")]
|
public bool IsEnableMonitor { get; set; } = true;
|
|
[Category("监听设置")]
|
[Description("扫描间隔时间,单位:ms")]
|
[DisplayName("扫描间隔")]
|
public int MonitorInterval { get; set; } = 100;
|
|
[Category("监听设置")]
|
[Description("超时设置,单位:ms")]
|
[DisplayName("监听超时")]
|
public int MonitorTimeout { get; set; } = 500;
|
|
[Category("事件地址设置")]
|
[Description("事件开始地址,PLC的实际寄存器地址。十进制,不包含功能码。")]
|
[DisplayName("监听开始地址")]
|
public int EventStartAddress { get; set; } = 8000;
|
|
[Category("事件地址设置")]
|
[Description("事件地址长度,最大长度128")]
|
[DisplayName("监听长度")]
|
public int EventLength { get; set; } = 120;
|
|
public List<IMonitorSet> GetAllMonitorSet()
|
{
|
WarningSetCollection.ForEach(m => m.Source = this.Name);
|
|
MonitorSetCollection.ForEach(m => m.SourceDevice = this.Id);
|
return MonitorSetCollection;
|
}
|
#endregion
|
|
#region IMotion Related
|
[Category("运动配置")]
|
[Description("运动轴状态集合")]
|
[DisplayName("运动轴状态集合")]
|
[TypeConverter(typeof(CollectionCountConvert))]
|
[Editor(typeof(ComplexCollectionEditor<PLCMotionDefinition_State>), typeof(UITypeEditor))]
|
public List<PLCMotionDefinition_State> MotionStateCollection { get; set; } = new List<PLCMotionDefinition_State>();
|
#endregion
|
}
|
|
#region Converter & Editor
|
public class PLCTypeConverter : StringConverter
|
{
|
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
|
{
|
return true;
|
}
|
|
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
|
{
|
return true;
|
}
|
|
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
|
{
|
List<string> devices = DeviceFactory.GetAllSupportDeviceTypeNames(typeof(PLCBase));
|
devices.Insert(0, "");
|
|
return new StandardValuesCollection(devices);
|
}
|
}
|
|
public class PLCInitialConfigEditor : UITypeEditor
|
{
|
public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
|
{
|
return UITypeEditorEditStyle.Modal;
|
}
|
|
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
|
{
|
IWindowsFormsEditorService edSvc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
|
|
if (edSvc != null)
|
{
|
Form form = new Form
|
{
|
FormBorderStyle = FormBorderStyle.SizableToolWindow,
|
StartPosition = FormStartPosition.CenterParent,
|
Text = context.PropertyDescriptor.DisplayName + "--" + context.PropertyDescriptor.Description
|
};
|
|
PropertyGrid pg = new PropertyGrid
|
{
|
Dock = DockStyle.Fill,
|
ToolbarVisible = false
|
};
|
|
string driverType = (value as PLCInitialConfigBase).DriverType;
|
|
if (!string.IsNullOrWhiteSpace(driverType))
|
{
|
IInitialConfig initial = ConfigFactory.GetInitialConfig(driverType);
|
initial.DataFrom(value);
|
value = initial;
|
}
|
|
pg.SelectedObject = value;
|
|
form.Controls.Add(pg);
|
|
form.ShowDialog();
|
|
value = pg.SelectedObject;
|
return value;
|
}
|
|
return base.EditValue(context, provider, value);
|
}
|
}
|
|
public class WarningSetsEditor : CollectionEditor
|
{
|
CollectionForm collectionForm = null;
|
protected override CollectionForm CreateCollectionForm()
|
{
|
collectionForm = base.CreateCollectionForm();
|
|
var prop = collectionForm.GetType().GetField("propertyBrowser", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
if (prop != null)
|
{
|
if (prop.GetValue(collectionForm) is PropertyGrid grid)
|
{
|
grid.HelpVisible = true;
|
grid.ToolbarVisible = false;
|
}
|
}
|
|
var addButtonField = collectionForm.GetType().GetField("addButton", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
ButtonBase addButton = addButtonField.GetValue(collectionForm) as ButtonBase;
|
Button importBtn = new Button();
|
importBtn.Text = "导入报警";
|
importBtn.Anchor = AnchorStyles.Top | AnchorStyles.Left;
|
importBtn.Size = addButton.Size;
|
importBtn.Location = new System.Drawing.Point(addButton.Location.X, addButton.Location.Y + addButton.Height + 15);
|
|
importBtn.Click += ImportBtn_Click;
|
|
addButton.Parent.Controls.Add(importBtn);
|
|
return collectionForm;
|
}
|
|
private void ImportBtn_Click(object sender, EventArgs e)
|
{
|
try
|
{
|
if (this.Context.Instance is PLCInitialConfigBase iConfig)
|
{
|
collectionForm.EditValue = iConfig.WarningSetCollection = GetWarningSets();
|
}
|
}
|
catch (Exception ex)
|
{
|
MessageBox.Show($"导入警报信息异常\r\n{ex.GetExceptionMessage()}");
|
}
|
}
|
|
private List<WarningSet> GetWarningSets()
|
{
|
List<WarningSet> wsList = new List<WarningSet>();
|
|
OpenFileDialog ofd = new OpenFileDialog();
|
ofd.Multiselect = false;
|
ofd.Filter = "CSV文件;TXT文件|*.csv;*.txt|全部文件|*.*";
|
if (ofd.ShowDialog() == DialogResult.OK)
|
{
|
using (StreamReader reader = new StreamReader(ofd.FileName, System.Text.Encoding.UTF8))
|
{
|
while (!reader.EndOfStream)
|
{
|
string data = reader.ReadLine();
|
|
var list = data.Split(new char[] { ',' }).ToList();
|
|
if (!string.IsNullOrWhiteSpace(list[1]))
|
{
|
WarningSet ws = new WarningSet();
|
|
ws.WarningCode = list[0];
|
ws.WarningDescription = list[1];
|
ws.WarningLvl = 0;
|
ws.WarningIndex_Word = int.Parse(list[2]);
|
ws.WarningIndex_Bit = int.Parse(list[3]);
|
|
wsList.Add(ws);
|
}
|
}
|
}
|
}
|
|
return wsList;
|
}
|
|
public WarningSetsEditor(Type type) : base(type)
|
{
|
}
|
|
/// <summary>
|
/// 限制一次选一个实例
|
/// </summary>
|
/// <returns></returns>
|
protected override bool CanSelectMultipleInstances()
|
{
|
return false;
|
}
|
|
/// <summary>
|
/// 指定创建的对象类型
|
/// </summary>
|
/// <returns></returns>
|
protected override Type CreateCollectionItemType()
|
{
|
return typeof(WarningSet);
|
}
|
|
protected override string GetDisplayText(object value)
|
{
|
if (value is IComplexDisplay)
|
{
|
return (value as IComplexDisplay).GetDisplayText();
|
}
|
|
return base.GetDisplayText(value);
|
}
|
|
//protected override void DestroyInstance(object instance)
|
//{
|
// base.DestroyInstance(instance);//重要!自动删除组件的设计时代码!
|
//}
|
}
|
|
public class PLCDeviceConverter : ComboBoxItemTypeConvert
|
{
|
public override Hashtable GetConvertHash(ITypeDescriptorContext context)
|
{
|
Hashtable table = new Hashtable();
|
using (var scope = GlobalVar.Container.BeginLifetimeScope())
|
{
|
var config = scope.Resolve<IProcessConfig>();
|
|
config.PLCConfigCollection.ForEach(plc =>
|
{
|
table[plc.Id] = plc.Name;
|
});
|
}
|
|
return table;
|
}
|
}
|
#endregion
|
|
#region IMotion Related
|
public class PLCMotionDefinition_State : IComplexDisplay
|
{
|
[Category("运动轴信息配置")]
|
[Description("运动轴名称")]
|
public string AxisName { get; set; }
|
|
[Category("运动轴信息配置")]
|
[Description("运动轴位置信息索引,和PLC监听地址配合使用,从0开始,长度默认为2")]
|
public int RegisterIndex { get; set; }
|
|
public string GetDisplayText()
|
{
|
return $"{AxisName} {RegisterIndex}";
|
}
|
}
|
|
public class PLCMotionDefinition_P2P
|
{
|
public string AxisName { get; set; }
|
|
public string VelocityRegister { get; set; }
|
|
public string DestinationRegister { get; set; }
|
|
public string TriggerRegister { get; set; }
|
}
|
|
public class PLCMotionDefinition_Jog
|
{
|
public string AxisName { get; set; }
|
public string VelocityRegister { get; set; }
|
public string TriggerRegister { get; set; }
|
}
|
#endregion
|
}
|