网页视觉设计流程seo学校培训
一、源码结构
二、运行效果
三、源码解析
PLC批量读写+点对点更新+数据类型处理
优点:根据数据类型,判定监听的地址范围(40120_int 监听两个word:40120 40121;40130_long 监听四个word:40130 40131 40132 40133),添加到UI字典中,PLC批量读取,判定数据变化,查找控件集合,点对点更新,效率高
实现流程:
1. 读取配置文件及创建变量信息(点位名称,地址,数据类型(bool/short/int/float/long/double))
2. 自定义控件绑定参数,用UI字典存储,通过属性get方式,如果是bool类型,直接取Bool字典的点位数据;如果是Word类型,根据数据类型拼装Word字典中的word数据,得到对应数据类型的点位数据;通过set方式,加入到写队列。
using PLCBind.CustomControls; using PLCBind.Service; using PLCBind.Util; using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms;namespace PLCBind {public partial class MainForm : Form{ public MainForm(){InitializeComponent();}private void MainForm_Load(object sender, EventArgs e){CommonMethods.LoadVar();// 读取配置文件及创建变量信息(点位名称,地址,类型)PLCService.Init(); // 读任务&写任务,数据有变化时事件广播通知(自定义控件预先绑定事件)BindControlParamModel();// 为按钮绑定参数}/// <summary>/// 为控件绑定参数/// </summary>private void BindControlParamModel(){InitControlTag(); SetControlParamModel();}/// <summary>/// 绑定控件变量/// </summary>void InitControlTag(){// 左皮带lblYX1.Tag = ucBeltLeft1.Tag = "00101";// 启动lblZS1.Tag = "40101";// 转速lblDY1.Tag = "40120";// 电压lblDL1.Tag = "40130";// 电流 }/// <summary>/// 赋值控件参数/// </summary>void SetControlParamModel(){foreach (Control item in this.pnlMain.Controls){if (item is ITransferUI objItem){var address = item.Tag.ToString();var common = CommonMethods.HomeVariables.Where(obj => obj.PLCAddress == address).FirstOrDefault();if (common != null){objItem.ParamModel = common;List<string> lstAddress = null;switch (common.DataType){case DataType.Bool:lstAddress = PLCService.RangeAddress(common.PLCAddress, 0);break;case DataType.Short:lstAddress = PLCService.RangeAddress(common.PLCAddress, 0);break;case DataType.Int:case DataType.Float:lstAddress = PLCService.RangeAddress(common.PLCAddress, 2);// 40120 监听两个word:40120 40121break;case DataType.Long:case DataType.Double:lstAddress = PLCService.RangeAddress(common.PLCAddress, 4);// 40130 监听四个word:40130 40131 40132 40133break;} foreach (var range in lstAddress){ CommonMethods.AddControl(CommonMethods.DicHomeControl, range, item);} }}}}private void tabControl1_SelectedIndexChanged(object sender, EventArgs e){var index = tabControl1.SelectedIndex;switch (index){case 0:this.ucParameter1.RemoveParams();break;case 1:// 参数设置this.pnlSet.Controls.Clear();this.pnlSet.Controls.Add(ucParameter1);this.ucParameter1.ListParams = CommonMethods.SetVariables.Where(s => s.Group == "顺序启动参数").ToList();break;}} } }
using PLCBind.Service;namespace PLCBind.UIForm {public class BaseParams{/// <summary>/// 描述/// </summary>public string Description { get; set; }/// <summary>/// PLC地址, 多个输入时,用";"分隔开/// </summary>public string PLCAddress { get; set; } /// <summary>/// 数据类型/// </summary>public DataType DataType { get; set; }/// <summary>/// 数据分组/// </summary>public string Group { get; set; }/// <summary>/// 单位/// </summary>public string Unit { get; set; }/// <summary>/// 设置与获取PLC值/// </summary>public object PLCValue{get{object obj = null; switch (DataType){case DataType.Bool:obj = PLCService.GetBool(PLCAddress);break;case DataType.Short:obj = PLCService.GetShort(PLCAddress);break;case DataType.Int:obj = PLCService.GetInt(PLCAddress);break;case DataType.Float:obj = PLCService.GetFloat(PLCAddress);break;case DataType.Long:obj = PLCService.GetLong(PLCAddress);break;case DataType.Double:obj = PLCService.GetDouble(PLCAddress);break;} return obj;}set{PLCService.AddWriteVariable(PLCAddress, value, DataType);}} } }
3. 异步任务处理:读任务&写任务,将读到的数据存到Data字典中,判断数据是否有发生变化,如果数据有变化,通过UI字典获取控件集合,调用更新方法
using HslCommunication; using HslCommunication.Core; using HslCommunication.ModBus; using PLCBind.CustomControls; using PLCBind.Util; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; using System.Windows.Forms; using UtilHelper;namespace PLCBind.Service {public class PLCService{ public static ConcurrentDictionary<string, bool> DicBoolData = new ConcurrentDictionary<string, bool>();public static ConcurrentDictionary<string, Word> DicWordData = new ConcurrentDictionary<string, Word>();public static ConcurrentDictionary<string, Word> DicWordChange = new ConcurrentDictionary<string, Word>();//static ModbusTcpNet client = null;static IByteTransform byteTransform;static ConcurrentQueue<PLCModel> queueWrite = new ConcurrentQueue<PLCModel>();// UI通知static void NoticeUI(string address, ConcurrentDictionary<string, List<Control>> dicControl){dicControl.TryGetValue(address, out List<Control> lstControl);if (null != lstControl){foreach (var item in lstControl){if (item is ITransferUI objItem){objItem.NoticeChange();}}}}/// <summary>/// 事件触发/// </summary> public static void DataChange(string address){Task.Run(() => NoticeUI(address, CommonMethods.DicHomeControl));Task.Run(() => NoticeUI(address, CommonMethods.DicSetControl));}/// <summary>/// 自定义控件内接收到数据变化事件,根据传入address,以及DataType查询监听地址所需要的监听范围(40120_int 监听两个word:40120 40121;40130_long 监听四个word:40130 40131 40132 40133),判断是否属于本控件监听/// </summary>public static List<string> RangeAddress(string address, int length){List<string> lstaddress = new List<string>();if (0 == length){lstaddress.Add(address);}else{for (int i = 0; i < length; i++){lstaddress.Add(FillAddress((DataHelper.Obj2Int(address) + i).ToString()));}}return lstaddress;}/// <summary>/// 读取时,按位补充0/// </summary>public static string FillAddress(string val, int length = 5){return val.PadLeft(length, '0');}/// <summary>/// 写入时,格式化地址,如:40101 -> 101/// </summary> public static string FormatAddress(string val){if (val.Length < 5) return val;return val.Substring(1, val.Length - 1);}/// <summary>/// 初始化plc通信,开启读写任务/// </summary>public static void Init(){client = new ModbusTcpNet(CommonMethods.PLCConfig.HostAddress, CommonMethods.PLCConfig.PortNumber);client.AddressStartWithZero = false;client.DataFormat = DataFormat.CDAB;byteTransform = client.ByteTransform;TskPlcRead();TskPlcWrite();}/// <summary>/// 获取bool(bool类型)/// </summary>/// <param name="address"></param>/// <returns></returns>public static bool GetBool(string address){ try{bool exist = DicBoolData.TryGetValue(address, out var value);// 字典存储if (!exist){Logger.Info($"[Error] PLCService,GetBool,errmsg:查无点位数据({address})");}return value;}catch (Exception ex){Logger.Info("[Error] PLCService,GetBool,errmsg:" + ex.Message);}return false;}/// <summary>/// 获取word(1个word,2个字节)/// </summary> static Word GetAddressWord(string address, int add){address = FillAddress((Convert.ToInt32(address) + add).ToString());bool exist = DicWordData.TryGetValue(address, out var value);if (!exist){Logger.Info($"[Error] PLCService,GetAddressWord,errmsg:查无点位数据({address})");}return value;}/// <summary>/// 拼接字节(多个word)/// </summary> static byte[] JoinAddressWord(string address, DataType datatype){byte[] ret = null;switch (datatype){ case DataType.Short:{ var buff = GetAddressWord(address, 0);ret = new byte[2] { buff.Byte1, buff.Byte2 };}break;case DataType.Int:case DataType.Float:{var buff1 = GetAddressWord(address, 0);var buff2 = GetAddressWord(address, 1);ret = new byte[4] { buff1.Byte1, buff1.Byte2, buff2.Byte1, buff2.Byte2 };} break;case DataType.Long: case DataType.Double:{var buff1 = GetAddressWord(address, 0);var buff2 = GetAddressWord(address, 1);var buff3 = GetAddressWord(address, 2);var buff4 = GetAddressWord(address, 3);ret = new byte[8] { buff1.Byte1, buff1.Byte2, buff2.Byte1, buff2.Byte2, buff3.Byte1, buff3.Byte2, buff4.Byte1, buff4.Byte2 };} break;}return ret;}public static ushort GetShort(string address){try{ var buff = JoinAddressWord(address, DataType.Short);return byteTransform.TransUInt16(buff, 0); }catch (Exception ex){Logger.Info("[Error] PLCService,GetShort,errmsg:" + ex.Message);}return 0;}public static uint GetInt(string address){try{var buff = JoinAddressWord(address, DataType.Int);return byteTransform.TransUInt32(buff, 0);}catch (Exception ex){Logger.Info("[Error] PLCService,GetInt,errmsg:" + ex.Message);}return 0;}public static float GetFloat(string address){try{var buff = JoinAddressWord(address, DataType.Float);return byteTransform.TransSingle(buff, 0);}catch (Exception ex){Logger.Info("[Error] PLCService,GetFloat,errmsg:" + ex.Message);}return 0;}public static ulong GetLong(string address){try{var buff = JoinAddressWord(address, DataType.Long);return byteTransform.TransUInt64(buff, 0);}catch (Exception ex){Logger.Info("[Error] PLCService,GetLong,errmsg:" + ex.Message);}return 0;}public static double GetDouble(string address){try{var buff = JoinAddressWord(address, DataType.Double);return byteTransform.TransDouble(buff, 0);}catch (Exception ex){Logger.Info("[Error] PLCService,GetDouble,errmsg:" + ex.Message);}return 0;}/// <summary>/// 定时读取/// </summary>static void TskPlcRead(){Task.Factory.StartNew(async () =>{var start_c = CommonMethods.PLCConfig.ReadStart_Coil;var start_h = CommonMethods.PLCConfig.ReadStart_Holding;bool[] temp_c = null; bool init_c = false;byte[] temp_h = null; bool init_h = false;while (!CommonMethods.CTS.IsCancellationRequested){try{DicWordChange.Clear();var array_c = (await client.ReadBoolAsync(start_c, (ushort)CommonMethods.PLCConfig.ReadCount_Coil)).Content;var array_h = (await client.ReadAsync(start_h, (ushort)(CommonMethods.PLCConfig.ReadCount_Holding * 2))).Content;// ushort占两个字节if (null != array_c){// bool类型只占1位,数据有变化直接通知if (null == temp_c){init_c = true;temp_c = new bool[array_c.Length];}CheckBoolChange("0", start_c, temp_c, array_c, init_c); init_c = false;Array.Copy(array_c, temp_c, array_c.Length);}if (null != array_h){// word类型数据位(2,4,8),所以要先读取全部的数据,再通知变化if (null == temp_h){init_h = true;temp_h = new byte[array_h.Length];}CheckWordChange("4", start_h, temp_h, array_h, init_h); init_h = false;Array.Copy(array_h, temp_h, array_h.Length);if (DicWordChange.Count > 0){foreach (var item in DicWordChange){DataChange(item.Key);}}}}catch (Exception ex){Logger.Info("[Error] PLCMgr,TskPlcRead,errmsg" + ex.Message);}await Task.Delay(100);}}, TaskCreationOptions.LongRunning);}/// <summary>/// 检查数据是否有变化(bool类型)/// </summary> public static void CheckBoolChange(string flg, string start, bool[] oldbuffer, bool[] newbuffer, bool init){for (int i = 0; i < newbuffer.Length; i++){// 00101string address = flg + FillAddress((i + Convert.ToInt32(start)).ToString(), 4); bool value = newbuffer[i];DicBoolData.AddOrUpdate1(address, value); if (init || oldbuffer[i] != value){ DataChange(address);}}}/// <summary>/// 检查数据是否有变化(word类型)/// </summary> public static void CheckWordChange(string flg, string start, byte[] oldbuffer, byte[] newbuffer, bool init){int index = 0;for (int i = 0; i < newbuffer.Length; i = i + 2){// 40101string address = flg + FillAddress((index + Convert.ToInt32(start)).ToString(), 4); index++; byte byte1 = newbuffer[i];byte byte2 = newbuffer[i + 1];Word buff = new Word() { Byte1 = byte1, Byte2 = byte2 };DicWordData.AddOrUpdate1(address, buff);if (init || (oldbuffer[i] != byte1 || oldbuffer[i + 1] != byte2)){DicWordChange.AddOrUpdate1(address, buff);}}}/// <summary>/// 添加写入值/// </summary> public static void AddWriteVariable(string address, object value, DataType datatype){queueWrite.Enqueue(new PLCModel() { Address = address, Value = value, PLCDataType = datatype });//加载值进队列} /// <summary>/// 定时写入/// </summary>static void TskPlcWrite(){ Task.Factory.StartNew(async () =>{while (!CommonMethods.CTS.IsCancellationRequested){try{if (!queueWrite.IsEmpty){PLCModel model = null; OperateResult result = null;queueWrite.TryDequeue(out model);var dataype = model.PLCDataType;switch (dataype){case DataType.Bool:result = await client.WriteAsync(FormatAddress(model.Address), Convert.ToBoolean(model.Value));break;case DataType.Short:result = await client.WriteAsync(FormatAddress(model.Address), Convert.ToUInt16(model.Value)); break;case DataType.Int:result = await client.WriteAsync(FormatAddress(model.Address), Convert.ToUInt32(model.Value));break;case DataType.Float:result = await client.WriteAsync(FormatAddress(model.Address), Convert.ToSingle(model.Value));break;case DataType.Long:result = await client.WriteAsync(FormatAddress(model.Address), Convert.ToUInt64(model.Value));break;case DataType.Double:result = await client.WriteAsync(FormatAddress(model.Address), Convert.ToDouble(model.Value));break;}if (!result.IsSuccess){Logger.Info("[Error] PLCMgr,TskPlcWrite,errmsg:写入失败," + result.Message);}}}catch (Exception ex){ Logger.Info("[Error] PLCMgr,TskPlcWrite,errmsg:" + ex.Message);} await Task.Delay(100);}}, TaskCreationOptions.LongRunning);}} }
4. 主界面控件都是静态加载,参数设置的控件是动态加载(点击进入,动态加载变量并监听;离开,移除不监听)
using PLCBind.Util; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms;namespace PLCBind.UIForm {public partial class ucParameter : UserControl{public ucParameter(){InitializeComponent();} /// <summary>/// 参数集合/// </summary>public object ListParams{set{ RemoveParams();if (value is List<BaseParams> Parameters){AddParams(Parameters);}}}/// <summary>/// 移除参数/// </summary>public void RemoveParams(){foreach (Control item in this.tableLayoutPanel1.Controls){if (item is ucTextSetting ctrText){CommonMethods.RemoveControl(CommonMethods.DicSetControl, ctrText.Address);// 移除集合}}this.tableLayoutPanel1.Controls.Clear(); // 移除控件}/// <summary>/// 添加参数/// </summary> void AddParams(List<BaseParams> objParams){ var pamramCount = objParams.Count;var pamrammIndex = 0;for (int columnIndex = 0; columnIndex < tableLayoutPanel1.ColumnCount; columnIndex++){for (int rowIndex = 0; rowIndex < tableLayoutPanel1.RowCount; rowIndex++){if (pamramCount > pamrammIndex){var common = objParams[pamrammIndex];var address = common.PLCAddress;ucTextSetting ucLbText = new ucTextSetting();ucLbText.Anchor = ((((AnchorStyles.Top | AnchorStyles.Bottom) | AnchorStyles.Left)));ucLbText.ParamModel = common;ucLbText.Address = address;CommonMethods.AddControl(CommonMethods.DicSetControl, address, ucLbText);// 添加集合tableLayoutPanel1.Controls.Add(ucLbText, columnIndex, rowIndex);// 添加控件 pamrammIndex++;}}}}private void TableLayoutPanel1_CellPaint(object sender, TableLayoutCellPaintEventArgs e){if (e.Row % 2 == 1)e.Graphics.FillRectangle(Brushes.White, e.CellBounds);elsee.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(192, 224, 248)), e.CellBounds);}} }
注意事项:
1. 字典类型
Data字典:ConcurrentDictionary<string, bool> DicBoolData;ConcurrentDictionary<string, Word> DicWordData;Word:byte1,byte2
UI字典:ConcurrentDictionary<string, List<Control>> DicHomeControl;ConcurrentDictionary<string, List<Control>> DicSetControl
2. bool类型只占1位,数据有变化直接通知
3. word类型数据位(short:2,int/float:4,long/double:8),所以要先读取全部的数据,再通知变化
4. 自定义控件继承ITransferUI类(属性:ParamModel,方法:NoticeChange),赋值属性ParamModel,其中PLCValue get:通过不同的数据类型,获取字典中的word数据,并拼接合成相应的数据类型;set:传入地址(写入时格式化地址,如:40101->101)及类型