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

二、运行效果:


三、源码解析
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);// 00101bool 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);// 40101index++;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)及类型
相关文章:
C#上位机序列9: 批量读写+事件广播+数据类型处理
一、源码结构: 二、运行效果: 三、源码解析 1. 读取配置文件及创建变量信息(点位名称,地址,数据类型(bool/short/int/float/long/double)) 2. 异步任务处理:读任务&…...
科技资讯|2023全球智能手表预估出货1.3亿块,智能穿戴提升AI功能
根据集邦咨询公布的最新报告,受全球经济低迷影响,2023 年全球智能手表出货量预估为 1.3 亿块。苹果以超过 30% 的份额领先,其次是三星(接近 10%)、华为、Garmin、Fitbit 等。 报告认为苹果、三星和华为等主要智能手表…...
技术架构之术
架构特征 1、结构性特征 易理解、可复用、可移植、可扩展、可配置、可维护、可测试 2、运行时特征 可靠性、稳定性、高安全、可伸缩、易用性、可用性、高性能、可观测 3、交付性特征 高效率、高适配、标准化、灵活性、易定制、统一性、开放性 如何开展我们的架构工作 价值分…...
【自用重要】概率论中θ和θ尖的区别【计算时的一般方法】
θ就相当于x,是一个值。 θ尖就相当于X,是一个量。 在做分布函数的时候,最好把θ尖换成Z的形式,因为他们都是量,这样比较好看。 在做不等式的时候,一般把量放在中间进行计算,因为随机变量有分…...
Redis设计与实现笔记 - 数据结构篇
Redis设计与实现笔记 - 数据结构篇 相信在我们日常使用中,会经常跟 Redis 打交道。数据结构 String、Hash、List、Set 和 ZSet 都是常用的数据类型。对于使用场景,我们可以滔滔不绝地说很多,但是我们从来就没有关心过它们的底层实现…...
线性代数-Python-01:向量的基本运算 -手写Vector -学习numpy的基本用法
文章目录 代码目录结构Vector.py_globals.pymain_vector.pymain_numpy_vector.py 一、创建属于自己的向量1.1 在控制台测试__repr__和__str__方法1.2 创建实例测试代码 二、向量的基本运算2.1 加法2.2 数量乘法2.3 向量运算的基本性质2.4 零向量2.5 向量的长度2.6 单位向量2.7 …...
数字图像处理实验记录二(直方图和直方图均衡化)
文章目录 一、基础知识1,什么是直方图2,直方图有什么用3,直方图均衡化4、原理代码实现 二、实验要求任务1:任务2: 三、实验记录任务1:任务2: 四、结果展示任务1:任务2: 五…...
大数据Flink(九十九):SQL 函数的解析顺序和系统内置函数
文章目录 SQL 函数的解析顺序和系统内置函数 一、SQL 函数...
TODO Vue typescript forEach的bug,需要再核實
forEach 一個string[],只有最後一個匹配條件有效,其它條件無效。 所以,只能替換成普通的for循環。 console.log(taskList)// for (const _task of taskList.value) {// if (_task invoiceSendEmail) {// form.value.invoiceSendEmail…...
简记一个错误
简记一个Flutter错误: Using hardware rendering with device sdk gphone64 x86 64. If you notice graphics artifacts, consider enabling software rendering with “–enable-software-rendering”. Launching lib\main.dart on sdk gphone64 x86 64 in debug …...
第四次作业
1.打印各种图形 A.矩形 a int(input("请输入行数: ")) i 0 while i < a:print("*"*10)i1 结果: B.直角三角形 a int(input("请输入行数: ")) i 0 while i<a:print("*"*(i1))i1 结果: C.反直角三角形 …...
面试问题整理总结
1.自我介绍 2.为什么想转测试 想换一个方向,测试开发在一定程度上也是属于开发,而且站在测试的角度能看到全局的东西更多,对需求的理解需要更深”,之前的开发工作比较专一,测试的视野更加开阔,想要站在更高…...
进阶JAVA篇- Collection 类的常用的API与 Collection 集合的遍历方式
目录 1.0 Collection 类的说明 1.1 Collection 类中的实例方法 2.0 Collection 集合的遍历方式(重点) 2.1 使用迭代器( Iterator )进行遍历 2.2 使用增强型 for 循环进行遍历 2.3 使用 Java 8的 Stream API 进行遍历(使…...
CentOS | 添加普通用户并授权sudo
sudo -i adduser peter passwd peter whereis sudoers nano /etc/sudoers添加一行新用户到root组 ## Allow root to run any commands anywhere root ALL(ALL) ALL peter ALL(ALL) ALL如果提升权限后无法cd到其他目录等,修改 /etc/passwd 文件&…...
【MyBatis】mybatis工具类迭代
目录 MyBatis工具类的迭代 ThreadLocal使用 mybatis工具类终极版: MyBatis工具类的迭代 public class MyBatisUtil {//工具类构造方法私有化private void MyBatisUtil() {}//方法一public static SqlSession getSqlSession(){try {SqlSessionFactoryBuilder sql…...
MSQL系列(六) Mysql实战-SQL语句优化
Mysql实战-SQL语句优化 前面我们讲解了索引的存储结构,BTree的索引结构,以及索引最左侧匹配原则,Explain的用法,可以看到是否使用了索引,今天我们讲解一下SQL语句的优化及如何优化 文章目录 Mysql实战-SQL语句优化1.…...
kaggle新赛:UBC卵巢癌亚型分类和异常检测大赛【图像分类】
赛题名称:UBC Ovarian Cancer Subtype Classification and Outlier Detection (UBC-OCEAN) 赛题链接:https://www.kaggle.com/competitions/UBC-OCEAN 赛题背景 卵巢癌是女性生殖系统最致命的癌症。目前,卵巢癌诊断依赖病理学家评估亚型。…...
基于nodejs+vue云旅青城系统
目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性:…...
《孙哥说Spring5》笔记汇总
时隔两个多月,终于将《孙哥说Spring5》的笔记文章全部整理完了,在这里做个汇总。孙哥的Spring课讲的非常好,深度和广度都有所兼顾,推荐大家去看 点击学习《孙哥说Spring5》 基础铺垫 1️⃣ Spring5应用之基础扫盲2️⃣ Spring5应…...
在使用了spring-cloud-starter-gateway后,为什么还会发生cors问题
//1.需要配置类 import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.reactive.CorsWebFilter; import org.sp…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...
BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
