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