首页 > 编程语言 >C#上位机序列9: 批量读写+事件广播

C#上位机序列9: 批量读写+事件广播

时间:2023-10-18 17:24:58浏览次数:32  
标签:string DataType C# 读写 上位 static address var public

1. 读取配置文件及创建变量信息(点位名称,地址,数据类型(bool/short/int/float/long/double))

2. 读任务&写任务,数据有变化时事件广播通知

using HslCommunication;
using HslCommunication.Core;
using HslCommunication.ModBus;
using PLCEvent.Util;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using UtilHelper;

namespace PLCEvent.Service
{
    public class PLCService
    { 
        public static Action<string> OnDataChange;
        public static ConcurrentDictionary<string, bool> DicBoolData = new ConcurrentDictionary<string, bool>();
        public static ConcurrentDictionary<string, Word> DicAllData = new ConcurrentDictionary<string, Word>();
        public static ConcurrentDictionary<string, Word> DicChangeData = new ConcurrentDictionary<string, Word>();
        static ModbusTcpNet client = null;
        static IByteTransform byteTransform;
        static ConcurrentQueue<PLCModel> queueWrite = new ConcurrentQueue<PLCModel>();
         
        public static void DataChange(string address)
        {
            OnDataChange?.Invoke(address);
        }

        /// <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.Obj2Short(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);
        }

        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();
        }

        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>
        /// 获取字节(1个word,2个字节)
        /// </summary> 
        static Word GetAddressWord(string address, int add)
        {
            address = FillAddress((Convert.ToInt32(address) + add).ToString());
            bool exist = DicAllData.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; byte[] temp_h = null;

                while (!CommonMethods.CTS.IsCancellationRequested)
                {
                    try
                    {
                        DicChangeData.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) temp_c = new bool[array_c.Length];
                            CheckBoolChange("0", start_c, temp_c, array_c);
                            Array.Copy(array_c, temp_c, array_c.Length);
                        }

                        if (null != array_h)
                        {
                            // word类型数据位(2,4,8),所以要先读取全部的数据,再通知变化
                            if (null == temp_h) temp_h = new byte[array_h.Length];
                            CheckWordChange("4", start_h, temp_h, array_h);
                            Array.Copy(array_h, temp_h, array_h.Length);

                            if (DicChangeData.Count > 0)
                            {
                                foreach (var item in DicChangeData)
                                {
                                    DataChange(item.Key);
                                }
                            }
                        } 
                    }
                    catch (Exception ex)
                    { 
                        Logger.Info("[Error] PLCMgr,TskPlcRead,errmsg" + ex.Message);
                    } 

                    await Task.Delay(1000);
                }
            }, TaskCreationOptions.LongRunning);
        }

        /// <summary>
        /// 检查数据是否有变化(bool类型)
        /// </summary> 
        public static void CheckBoolChange(string flg, string start, bool[] oldbuffer, bool[] newbuffer)
        {
            for (int i = 0; i < newbuffer.Length; i++)
            {
                string address = flg + FillAddress((i + Convert.ToInt32(start)).ToString(), 4);// 00101
                bool value = newbuffer[i];

                DicBoolData.AddOrUpdate1(address, value); 
               
                if (oldbuffer[i] != value)
                { 
                    OnDataChange(address);
                }
            }
        }

        /// <summary>
        /// 检查数据是否有变化(word类型)
        /// </summary> 
        public static void CheckWordChange(string flg, string start, byte[] oldbuffer, byte[] newbuffer)
        {
            int index = 0;
            for (int i = 0; i < newbuffer.Length; i = i + 2)
            {
                string address = flg + FillAddress((index + Convert.ToInt32(start)).ToString(), 4);// 40101

                if (address == "40130")
                {

                }
                index++;
                byte byte1 = newbuffer[i];
                byte byte2 = newbuffer[i + 1];

                Word buff = new Word() { Byte1 = byte1, Byte2 = byte2 };

                DicAllData.AddOrUpdate1(address, buff);

                if (oldbuffer[i] != byte1 || oldbuffer[i + 1] != byte2)
                {
                    DicChangeData.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(500);
                }
            }, TaskCreationOptions.LongRunning);
        }
    }
}

3. 自定义控件绑定参数,监听数据变化事件

 

注意点:
1. bool类型只占1位,数据有变化直接通知
2. word类型数据位(short:2,int/float:4,long/double:8),所以要先读取全部的数据,再通知变化
3. 自定义控件内接收到数据变化事件,根据传入address,以及DataType查询监听地址所需要的监听范围(40120_int 监听两个word:40120 40121;40130_long 监听四个word:40130 40131 40132 40133),判断是否属于本控件监听
4. 自定义控件继承BaseParams类, PLCValue get:通过不同的数据类型,获取字典中的word数据,并拼接合成相应的数据类型;set:传入地址(写入时格式化地址,如:40101->101)及类型

 

实现效果:

开启Modbus Server工具

 双击数字,编辑值,点击更新后,写入modbus

数据有变化时,自动更新

 

标签:string,DataType,C#,读写,上位,static,address,var,public
From: https://www.cnblogs.com/chen1880/p/17772862.html

相关文章

  • oracle TDE使用
    环境:OS:Centos6DB:11.2.0.43节点组成的rac环境 1.先要创建一个"wallet钱包",这个钱包里面保存着密钥,Oracle就是通过这个密钥对列进行加密和解密的.在其中一个节点上操作,我这里是在节点1上操作su-oracle[oracle@rac01~]$mkdir$ORACLE_BASE/wallet[oracle@rac01~]$c......
  • IO流,通过字节缓冲流来提高读写效率
    BufferedInputStream和BufferedOutputStream  两个流是缓冲字节流,通过内部缓存数组来提高操作流的效率。 当我们开启了很多流时,关闭顺序为:先开的后关闭(后开的先关闭)  在这个缓冲区中,byte数组的默认长度为8192,也是2的整数幂   练习代码如下: 结果是在指定文......
  • Error:java: Compilation failed: internal java compiler error
    Error:java:Compilationfailed:internaljavacompilererror出现这个错误的原因主要是因为JDK版本问题,有两个原因,一个是编译器版本不匹配,一个是当前项目JDK版本不支持。File-->ProjectStructure-->ProjectSettings-->Project或者Ctrl+Alt+shift+S打开项目的......
  • vue进行跳转之后出现Cannot read properties of undefined (reading 'router') TypeEr
    问题描述使用router进行页面跳转时,就出现了这样的问题:也就是这里出现了问题:问题解决本来是按照网上的教程:const_this=this;但是,但是,我本来就是用的这种方法呀~然后就打算直接在这个界面引用:importrouterfrom'@/router'router.push('/one');里面引用的跳转页面......
  • 【Spring Boot+LogBack】高效记录日志,实现日志文件本地化保存!
    ......
  • 2023跟我一起成为docker大牛:swarm 教程:部署篇「上」
    2023跟我一起成为docker大牛:swarm教程:部署篇「上」Swarm模式是用于管理一组Docker守护程序的高级功能。ip规划:Manager:Manager:172.16.95.137Node1:172.16.95.138Node2:172.16.95.1391、manager节点初始化swarmdockerswarminit--advertise-addr172.16.95.137输出:docker......
  • Java 新手如何使用Spring MVC 中的双向数据绑定?
    数据绑定,正如其名称本身一样,是一个不言自明的词。在数据绑定中,我们要做的是捕获或存储数据,以便我们可以根据需要将该数据与另一个资源绑定(例如在前端部分显示数据),或者我们也可以从一个变量并根据我们的要求显示它。例如,有一个谷歌表单,用户在该表单中输入所有详细信息,我们必须捕获/......
  • Java 新手如何使用Spring MVC 中的查询字符串和查询参数?
    Java新手如何使用SpringMVC中的查询字符串和查询参数?根据维基百科的说法,“查询字符串是统一资源定位符(URL)的一部分,它为指定的参数分配值。查询字符串通常包括由Web浏览器或其他客户端应用程序添加到基本URL的字段,例如作为HTML的一部分、选择页面的外观或跳转到多媒体内容......
  • C++模板笔记
    参考文章:https://juejin.cn/post/7078530622527897631模板是C++的泛型编程机制,这种机制可以最大程度复用代码并且不会增加运行时开销模板分为函数模板和类模板函数模板函数模板是对函数的参数进行泛型化,传递给模板函数的类型实参可以是类,也可以是整型值,还可以是模板名比如://......
  • Spring MVC,Mybatis常见问题
    如果您面试一个只做过SpringMVC+MyBatis项目的候选人,您可能会问一些问题来评估其在这两个技术上的了解和经验。以下是一些可能的问题及其答案:什么是SpringMVC和MyBatis?它们在项目中的作用是什么?答:SpringMVC是一个基于Spring框架的用于构建Web应用程序的模块,它使用MVC(Model-......