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.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);// 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);// 40101if (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
数据有变化时,自动更新
相关文章:

C#上位机序列9: 批量读写+事件广播
1. 读取配置文件及创建变量信息(点位名称,地址,数据类型(bool/short/int/float/long/double)) 2. 读任务&写任务,数据有变化时事件广播通知 using HslCommunication; using HslCommunication.Core; usi…...

ARM +FPGA GPIB IP核实现
目前在数据发生其技术上居领先的是美国的 Tektronix 公司和 Agilent 公司。 Agilent 公司的台式脉冲 / 数据发生器家族的最高时钟频率达 3GHz (定 时发生器),数据发生器 E81200 在通道数为 8CH 时数据速率为 660Mb/s, 即可以产…...

有消息称苹果Vision Pro会有廉价版
据外媒爆料,苹果公司苹果正在研发的头显产品Vision Pro,将会有廉价版。据透露,这款产品预计售价在1500美元至2500美元之间,虽然仍不算低,但较现有的Vision Pro 3499美元的起售价,还是有明显降低。 透露廉价…...

jenkins整合gerrit
背景 公司项目之前使用jenkins整合了gitlab,后面代码迁移到gerrit,所以需要修改jenkins配置。下面就简单的介绍一下jenkins如何整合gerrit。 环境 服务器:linux 环境:docker、jenkins 代码仓库:gerrit 前提 docke…...

PMP考完后应该考什么?
PMP(项目管理专业)认证是全球范围内最受认可和尊重的项目管理资格证书之一。通过PMP考试的人已经展示了他们在项目管理领域的知识和技能。然而,项目管理是一个不断发展和变化的领域,持续学习和进一步提升自己的能力是非常重要的。…...

科技资讯|苹果Vision Pro可通过手势ID检测不同用户
近日,美国专利局公布了苹果公司的一项专利申请,该专利申请涉及基于手部特征验证用户身份的技术。苹果指出,可能是多个家庭成员都想使用 Apple Vision Pro,系统必须识别不同的手势以控制 visionOS。在另一个示例中,苹果…...
CUDA编程模型- 层次结构
层次结构的划分 在GPU上,为了满足其大规模并行处理的特性,执行模型采用了大量并行化的轻量级线程。当我们谈到CUDA编程模型时,我们首先要考虑的是其线程执行层次结构。这种层次结构起始于一个被称为kernel的函数,当它在GPU上执行…...
国际站阿里云服务器无法安装程序怎么办?
阿里云服务器是阿里云推出的一种云核算产品,它能够帮助企业和个人快速建立、扩展和管理网络服务。可是,有时候在运用阿里云服务器时,或许会遇到无法装置程序的问题。本文将具体介绍如何处理这个问题。 阿里云服务器无法装置程序或许是由多种原…...

基于Vue+webpack之H5打包资源优化
前言 基于公司的业务以及今年接触到的项目大部分都是APP混合开发,即原生Android/ios H5页面开发APP。项目从产品需求的评审到方案的评审再到开发提测...这一套流程下来让我收货颇多。总想找个时间好好记录一番,大概还是自己懒惰了,一直拖到现…...

C#中DataAdapter对象
目录 一、DataAdapter对象概述 二、Fill()方法填充数据集DataSet 1.举例 2.源码 3.生成效果 三、Update()方法 1.Update()方法更新数据源 2.设置数据库主键 3.源码 4.生成效果 一、DataAdapter对象概述 DataAdapter对象是一个数据适配器对象,是DataSet与…...

Nginx正向代理,反向代理,负载均衡
Nginx正向代理,反向代理,负载均衡 Nginx当中有两种代理方式: 七层代理(http协议) 四层代理(tcp/udp流量转发) 七层代理:七层代理,代理的是http的请求和响应 客户端请求…...

安防视频监控平台EasyCVR出现视频流播放卡顿情况,如何优化?
视频集中存储/云存储/视频监控管理平台EasyCVR能在复杂的网络环境中,将分散的各类视频资源进行统一汇聚、整合、集中管理,实现视频资源的鉴权管理、按需调阅、全网分发、智能分析等。AI智能/大数据视频分析EasyCVR平台已经广泛应用在工地、工厂、园区、楼…...

VRRP基础
1.VRRP概述 VRRP( Virtual Router Redundancy Protocol,虚拟路由器冗余协议)既能够实现网关的备份,又能解决多个网关之间互相冲突的问题,从而提高网络可靠性。 通过把几台路由设备联合组成一台虚拟的“路由设备”&…...

虚实融合 智兴百业 | 赵捷副市长莅临拓世科技集团筹备展台指导,本月19号!拓世科技集团与您相约世界VR产业大会
新时代科技革命中,虚拟现实技术、5G和“元宇宙”概念崛起,助力全球范围内的数字经济和产业转型。我国也正迈向高质量发展攻坚阶段,在中部腹地的江西,政府结合全球技术趋势和自身发展需求,选择虚拟现实为新的经济增长点…...

2000-2023年省市县人工智能企业数量数据
2000-2023年省市县人工智能企业数量数据 1、时间:2000-2023年7月 2、指标:所属年度、所属省份、所属城市、所属区县、人工智能企业数量(省人工智能企业数量、地级市人工智能企业数量、区县人工智能企业数量) 3、来源࿱…...
CSP模拟58联测20 牵着她的手
题目大意 考虑所有 n n n行 m m m列的矩阵,矩阵中每个元素的值都在 1 1 1到 k k k之间。对于这样的矩阵 A A A,按照下面规则构造序列 x 1 , x 2 , ⋯ , x n m x_1,x_2,\cdots,x_{nm} x1,x2,⋯,xnm: 对于 1 ≤ i ≤ n 1\leq i\leq n …...

电脑版便签软件下载用哪个?
在面对每天繁忙的工作日程,电脑是许多上班族不可或缺的工作助手,而一款得心应手的电脑便签软件,更是可以帮助大家记录、提醒、督促各项任务按时完成的得力助手。那么,究竟在众多的电脑便签软。件中,哪一位能够真正成为…...

别再卷组件库了,Vue 拖拽库都断代了!
前言 最近在测试 Tailwind CSS 和 Uno CSS 这两种原子化 CSS 工具是否能够有效减少打包后的文件体积时,先开始分析这些工具的优缺点,然后再直接上数据,最后做了一款经典的 TodoList 来进行测试,文章都写好了就差最后的数据了。 …...
利用服务器打造创新的在线社区
在这个数字化时代,服务器是实现创意项目的关键工具之一。虽然有许多用途,但其中最引人注目的是将服务器用于构建创新的在线社区。 为什么选择在线社区? 在线社区是连接人们、促进互动和分享知识的强大工具。它们可以围绕共同的兴趣、目标或…...
CSS动画实现节流
目录 介绍: 实现代码: 介绍: 节流指的避免过于频繁的执行一个函数,例如:一个保存按钮,为了避免重复提交或者服务器考虑,往往需要对点击行为做一定的限制,不然会频繁的请求接口,之前基本上是通过js去控制节…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...

通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...