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

数据有变化时,自动更新
















![进程概念[下]](https://img-blog.csdnimg.cn/e3f63698ac274c9f9cf18789f037fb70.png)




