• C#上位机序列10: 批量读写+点对点更新+数据类型处理


    一、源码结构

    二、运行效果

    三、源码解析

    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();// 为按钮绑定参数
            }
    
            /// 
            /// 为控件绑定参数
            /// 
            private void BindControlParamModel()
            {
                InitControlTag(); 
                SetControlParamModel();
            }
    
            /// 
            /// 绑定控件变量
            /// 
            void InitControlTag()
            {
                // 左皮带
                lblYX1.Tag = ucBeltLeft1.Tag = "00101";// 启动
                lblZS1.Tag = "40101";// 转速
                lblDY1.Tag = "40120";// 电压
                lblDL1.Tag = "40130";// 电流 
            }
    
            /// 
            /// 赋值控件参数
            /// 
            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 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 40121
                                    break;
                                case DataType.Long:
                                case DataType.Double:
                                    lstAddress = PLCService.RangeAddress(common.PLCAddress, 4);// 40130 监听四个word:40130 40131 40132 40133
                                    break;
                            } 
    
                            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
        {
            /// 
            /// 描述
            /// 
            public string Description { get; set; }
    
            /// 
            /// PLC地址, 多个输入时,用";"分隔开
            /// 
            public string PLCAddress { get; set; } 
    
            /// 
            /// 数据类型
            /// 
            public DataType DataType { get; set; }
    
            /// 
            /// 数据分组
            /// 
            public string Group { get; set; }
    
            /// 
            /// 单位
            /// 
            public string Unit { get; set; }
    
            /// 
            /// 设置与获取PLC值
            /// 
            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 DicBoolData = new ConcurrentDictionary();
            public static ConcurrentDictionary DicWordData = new ConcurrentDictionary();
            public static ConcurrentDictionary DicWordChange = new ConcurrentDictionary();
            //
            static ModbusTcpNet client = null;
            static IByteTransform byteTransform;
            static ConcurrentQueue queueWrite = new ConcurrentQueue();
             
            // UI通知
            static void NoticeUI(string address, ConcurrentDictionary> dicControl)
            {
                dicControl.TryGetValue(address, out List lstControl);
                if (null != lstControl)
                {
                    foreach (var item in lstControl)
                    {
                        if (item is ITransferUI objItem)
                        {
                            objItem.NoticeChange();
                        }
                    }
                }
            }
    
            /// 
            /// 事件触发
            ///  
            public static void DataChange(string address)
            {
                Task.Run(() => NoticeUI(address, CommonMethods.DicHomeControl));
                Task.Run(() => NoticeUI(address, CommonMethods.DicSetControl));
            }
    
            /// 
            /// 自定义控件内接收到数据变化事件,根据传入address,以及DataType查询监听地址所需要的监听范围(40120_int 监听两个word:40120 40121;40130_long 监听四个word:40130 40131 40132 40133),判断是否属于本控件监听
            /// 
            public static List RangeAddress(string address, int length)
            {
                List lstaddress = new List();
    
                if (0 == length)
                {
                    lstaddress.Add(address);
                }
                else
                {
                    for (int i = 0; i < length; i++)
                    {
                        lstaddress.Add(FillAddress((DataHelper.Obj2Int(address) + i).ToString()));
                    }
                }
                return lstaddress;
            }
    
            /// 
            /// 读取时,按位补充0
            /// 
            public static string FillAddress(string val, int length = 5)
            {
                return val.PadLeft(length, '0');
            }
    
            /// 
            /// 写入时,格式化地址,如:40101 -> 101
            ///  
            public static string FormatAddress(string val)
            {
                if (val.Length < 5) return val;
    
                return val.Substring(1, val.Length - 1);
            }
    
            /// 
            /// 初始化plc通信,开启读写任务
            /// 
            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();
            }
    
            /// 
            /// 获取bool(bool类型)
            /// 
            /// 
            /// 
            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;
            }
    
            /// 
            /// 获取word(1个word,2个字节)
            ///  
            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;
            }
    
            /// 
            /// 拼接字节(多个word)
            ///  
            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;
            }
    
            /// 
            /// 定时读取
            /// 
            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);
            }
    
            /// 
            /// 检查数据是否有变化(bool类型)
            ///  
            public static void CheckBoolChange(string flg, string start, bool[] oldbuffer, bool[] newbuffer, bool init)
            {
                for (int i = 0; i < newbuffer.Length; i++)
                {
                    // 00101
                    string address = flg + FillAddress((i + Convert.ToInt32(start)).ToString(), 4); 
    
                    bool value = newbuffer[i];
    
                    DicBoolData.AddOrUpdate1(address, value); 
                   
                    if (init || oldbuffer[i] != value)
                    { 
                        DataChange(address);
                    }
                }
            }
    
            /// 
            /// 检查数据是否有变化(word类型)
            ///  
            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)
                {
                    // 40101
                    string 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);
                    }
                }
            }
    
            /// 
            /// 添加写入值
            ///  
            public static void AddWriteVariable(string address, object value, DataType datatype)
            {
                queueWrite.Enqueue(new PLCModel() { Address = address, Value = value, PLCDataType = datatype });//加载值进队列
            } 
    
            /// 
            /// 定时写入
            /// 
            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();
            } 
    
            /// 
            /// 参数集合
            /// 
            public object ListParams
            {
                set
                { 
                    RemoveParams();
                     
                    if (value is List Parameters)
                    {
                        AddParams(Parameters);
                    }
                }
            }
    
            /// 
            /// 移除参数
            /// 
            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(); // 移除控件
            }
    
            /// 
            /// 添加参数
            ///  
            void AddParams(List 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);
                else
                    e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(192, 224, 248)), e.CellBounds);
            }
        }
    }

    复制代码

    注意事项:
    1. 字典类型
    Data字典:ConcurrentDictionary DicBoolData;ConcurrentDictionary DicWordData;Word:byte1,byte2
    UI字典:ConcurrentDictionary> DicHomeControl;ConcurrentDictionary> 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)及类型

  • 相关阅读:
    遥感数据与作物模型同化
    Java 最常见的800道面试题,老话说:临阵磨枪,不快也光,涨薪指日可待
    Transform+ASM插桩系列(3)——Transform+ASM的实战
    Doris数仓的4大特点
    系统架构设计师(第二版)学习笔记----信息安全基础知识
    2022年最新青海机动车签字授权人模拟考试及答案
    如何快速构建研发效能度量的指标体系?
    ESP32C3 UDP数据传输
    Ansys Speos | 如何利用Speos联合optiSLang进行光导优化设计
    方舟:生存进化开服务器端口映射教程
  • 原文地址:https://blog.csdn.net/arno1988/article/details/134072578