C#上位机序列9: 批量读写+事件广播+数据类型处理

news2025/1/15 23:20:58

一、源码结构:

 二、运行效果:

三、源码解析

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.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 = 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(100);
                }
            }, 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

                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(100);
                }
            }, TaskCreationOptions.LongRunning);
        }
    }
}

复制代码

3. 自定义控件绑定参数,绑定事件,当收到数据变化事件,通过属性get方式,如果是bool类型,直接取Bool字典的点位数据;如果是Word类型,根据数据类型拼装Word字典中的word数据,得到对应数据类型的点位数据;通过set方式,加入到写队列。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1111644.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++_learning-并发与多线程

并发与多线程 并发&#xff1a;进程&#xff1a;线程&#xff1a;基本概念&#xff1a;线程安全&#xff1a;问题出现的场景&#xff1a;涉及的性质&#xff1a;如何保证线程安全&#xff1f; 并发的实现手段&#xff08;优先使用多线程并发&#xff09;&#xff1a;多进程并发…

【特征重要性揭秘:为什么同一个数据集会有不同结果】

文章目录 特征重要性概要为什么特征重要性分析很重要特征重要性分析方法内置特征重要性(coef_或feature_importances_)Leave-one-out相关性分析递归特征消除 Recursive Feature EliminationXGBoost特性重要性主成分分析 PCA方差分析 ANOVA卡方检验&#xff08;Chi-Square Test&…

求最大公约数的几种常见的方法 【详解】

目录 一、关于公约数 二、计算最大公约数的方法 1. 辗转相除法&#xff08;欧几里得算法&#xff09; 2. 更相减损法&#xff08;辗转相减法&#xff09; 3. 分解质因数法 4. 穷举法 5. 递归法 6. 短除法 三、总结 一、关于公约数 首先 &#xff0c;先介绍一下公约…

matplotlib python 画图教程(2)

1、bar 柱状图 import matplotlib.pyplot as plt import numpy as np n12 xnp.arange(12) y1(1-x/float(n))*np.random.uniform(0.5,1,n) y2(1-x/float(n))*np.random.uniform(0.5,1,n) plt.xlim(-.5,n) plt.ylim(-1.25,1.25) plt.xticks([]) plt.yticks([]) plt.bar(x,y1,fac…

利用TypeScript 和 jsdom 库实现自动化抓取数据

以下是一个使用 TypeScript 和 jsdom 库的下载器程序&#xff0c;用于下载zhihu的内容。此程序使用了 duoip.cn/get_proxy 这段代码。 import { JSDOM } from jsdom; import { getProxy } from https://www.duoip.cn/get_proxy;const zhihuUrl https://www.zhihu.com;(async (…

01、Python 安装 ,Pycharm 安装

目录 安装安装 Python安装 Pycharm 创建项目简单添加文件运行 简单爬取下载小视频 安装 python-3.8.10-amd64.exe – 先安装这个 pycharm-community-2022.2.exe 再安装这个 安装 Python python-3.8.10-amd64.exe 安装&#xff08;这个是其他版本的安装&#xff0c;步骤一样…

Linux安装MINIO

MINIO简介MINIO目录 mkdir -p /opt/minio/data && cd /opt/minio MINIO下载 wget https://dl.minio.org.cn/server/minio/release/linux-amd64/minio MINIO授权 chmod x minio MINIO端口 firewall-cmd --zonepublic --add-port7171/tcp --permanent && firewal…

RTOS(6)任务管理

任务状态理论 我们是怎么实现&#xff0c;两个同优先级的任务之间交替执行的呢&#xff1f; 任务切换的基础&#xff1a;tick中断&#xff01; tick为1ms一个周期&#xff0c;可以通过修改时钟配置修改&#xff1b; running&#xff1a;正在进行的任务3为running&#xff…

20231019 filezilla 配置 Windows与Ubuntu文件传输

SFTP协议&#xff0c;传文件&#xff0c;否则会报无权限错

RTOS(7)同步互斥与通信概述

同步与互斥 同步的例子 循环检测有缺陷&#xff0c;还是得blocked掉&#xff0c;不然会很占用cpu&#xff0c;浪费资源&#xff1b; 互斥的例子 单纯的使用全局变量来实现互斥不太靠谱&#xff0c;当执行时间过长的时候会概率性的出现错误 通信的例子 FreeRtos的解决方案 …

Linux操作系统从BIOS到bootloader是如何运行的

操作系统一般都会在安装在硬盘上&#xff0c;在 BIOS 的界面上。你会看到一个启动盘的选项。启动盘有什么特点呢&#xff1f;它一般在第一个扇区&#xff0c;占 512 字节&#xff0c;而且以 0xAA55 结束。这是一个约定&#xff0c;当满足这个条件的时候&#xff0c;就说明这是一…

浅谈轨道交通建筑能耗分析及节能措施

叶根胜 安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;面对城市轨道交通的能耗增长&#xff0c;优化地铁车站建筑、降低运营能耗是促进公共交通可持续化发展的必经之路。通风空调系统的能耗占比较大&#xff0c;节能潜力也是大的。本文以上海首条绿色地铁的项目实…

C#计数排序算法

前言 计数排序是一种非比较性的排序算法&#xff0c;适用于排序一定范围内的整数。它的基本思想是通过统计每个元素的出现次数&#xff0c;然后根据元素的大小依次输出排序结果。 实现原理 首先找出待排序数组中的最大值max和最小值min。创建一个长度为max-min1的数组count&a…

售后服务管理升级要怎么做?如何用好售后工单管理系统?

随着数字经济的发展&#xff0c;市场服务趋于多样化&#xff0c;客户对售后服务也有更高的要求&#xff0c;市场竞争日益激烈&#xff0c;越来越多的供应商企业认识到单凭优异的质量&#xff0c;颇具竞争力的价格已不能保证企业的竞争优势&#xff0c;事实表明&#xff0c;用户…

[原创] ElasticSearch集群故障案例分析: 警惕通配符查询

[携程旅行网&#xff1a; 吴晓刚] 许多有RDBMS/SQL背景的开发者&#xff0c;在初次踏入ElasticSearch世界的时候&#xff0c;很容易就想到使用(Wildcard Query)来实现模糊查询&#xff08;比如用户输入补全)&#xff0c;因为这是和SQL里like操作最相似的查询方式&#xff0c;用…

常见面试题-Redis专栏(一)

typora-copy-images-to: imgs了解 redis 中的大key吗&#xff1f;多大算是大key呢&#xff1f;如何解决&#xff1f; 答&#xff1a; redis 的大 key 指的是 key 对应的 value 所占用的内存比较大。 对于 string 类型来说&#xff0c;一般情况下超过 10KB 则认为是大 key&…

waf、yakit和ssh免密登录

WAF安全狗 脏数据适用于所有漏洞绕过waf&#xff0c;但是前提条件垃圾信息必须放在危险信息前&#xff0c;是不能打断原有数据包的结构&#xff0c;不能影响后端对数据包的解析。 以DVWA靶场文件上传为例 新建php文件 上传文件被安全狗拦截 使用bp抓包查看 在数据包Content-…

Anomalib 图像异常检测算法

图像异常检测算法 1.简介1.1. 问题描述1.2. 挑战与需求 2. 图像异常检测定义2.1 异常的定义及类型2.2 图像数据中的异常 3. 图像异常检测技术研究现状4.方法5.安装和使用5.1 安装PyPI 安装本地安装 5.2 训练5.3 特征提取与&#xff08;预训练&#xff09;backones5.4 自定义数据…

DVWA-弱会话IDS

弱会话IDS Session简介&#xff1a; 用户登录后&#xff0c;在服务器就会创建一个会话(session)&#xff0c;叫做会话控制&#xff0c;接着访问页面的时候就不用登录&#xff0c;只需要携带Session去访问即可。 sessionID作为特定用户访问站点所需要的唯一内容。如果能够计算…

HTML构建图文并茂类页面----HTML插入图片

插入图片 插入方式1&#xff1a;使用img标签插入图片 例如&#xff1a;<img src"img/2ee18-231100-163232346010b0.jpg" alt"用户注册按钮位置" title"用户注册1" width"1138px"/> 插入方式2 图片做背景&#xff1a;使用styl…