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 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 OnMonitorInvoke; public event Action OnMonitorAlarm; public List MonitorValues { get; set; } = new List(); public abstract List 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 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(newValues); var tempOld = new List(MonitorValues); MonitorCheckAndInvoke(tempNew, tempOld); } MonitorValues = new List(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 tempNew, List 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)PLCReplyValue.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 GetCurrentAxisInfo(params string[] axisName) { List mds = new List(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 { /// /// 需要操作的PLC项 /// public List Items { get; set; } = new List(); } 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 WarningSetCollection { get; set; } = new List(); #region IMonitorConfig [Category("监听设置")] [Description("监听操作配置集合")] [DisplayName("监听配置")] [TypeConverter(typeof(CollectionCountConvert))] [Editor(typeof(ComplexCollectionEditor), typeof(UITypeEditor))] public List MonitorSetCollection { get; set; } = new List(); [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 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), typeof(UITypeEditor))] public List MotionStateCollection { get; set; } = new List(); #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 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 GetWarningSets() { List wsList = new List(); 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) { } /// /// 限制一次选一个实例 /// /// protected override bool CanSelectMultipleInstances() { return false; } /// /// 指定创建的对象类型 /// /// 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(); 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 }