Commit 9ca3b355 authored by 潘栩锋's avatar 潘栩锋 🚴

添加 异步的modbusRTU

parent 3e0dd78a
...@@ -49,6 +49,7 @@ ...@@ -49,6 +49,7 @@
<Compile Include="IGeneralComm.cs" /> <Compile Include="IGeneralComm.cs" />
<Compile Include="IModbusBase.cs" /> <Compile Include="IModbusBase.cs" />
<Compile Include="ModbusASCII.cs" /> <Compile Include="ModbusASCII.cs" />
<Compile Include="ModbusRTUAsync.cs" />
<Compile Include="ModbusRTU.cs" /> <Compile Include="ModbusRTU.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Protocol7ECommon.cs" /> <Compile Include="Protocol7ECommon.cs" />
......
using Newtonsoft.Json.Linq;
using NLog;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
using static GeneralGommunication.Modbus_Transaction;
namespace GeneralGommunication
{
/// <summary>
/// 异步modbus RTU
/// </summary>
public class ModbusRTUAsync
{
public Logger logger = NLog.LogManager.GetCurrentClassLogger();
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// 包出错次数
/// </summary>
public int ErrCnt { get; protected set; }
/// <summary>
/// 连接成功;
/// 当命令多次发送失败,IsConnected = false
/// </summary>
public bool IsConnected { get; private set; }
/// <summary>
/// 通讯速度测量模块
/// </summary>
public CommSpeedMeasuring Csm => csm;
/// <summary>
/// 有数据需要发送
/// </summary>
public event SendMsgEventHander SendMsgEvent;
/// <summary>
/// 对于全部 有返回的函数调用,都使用Dispatcher,使线程同步
/// </summary>
public Dispatcher Dispatcher;
List<byte> currPack = new List<byte>();
/// <summary>
/// 指令队列,必须等上1条指令回复了,才能发下条指令
/// </summary>
List<Modbus_Transaction> Transactions;
/// <summary>
/// 当前正在等待回复的指令
/// </summary>
Modbus_Transaction currTran;
/// <summary>
/// currTran 发送后,开始计时
/// </summary>
Stopwatch stopwatch_timeOut;
CommSpeedMeasuring csm = new CommSpeedMeasuring();
/// <summary>
/// currTran 重发次数
/// </summary>
int retryCnt = 0;
public ModbusRTUAsync()
{
Transactions = new List<Modbus_Transaction>();
stopwatch_timeOut = new Stopwatch();
}
public void Init() {
}
/// <summary>
/// 发送指令的超时判断
/// </summary>
public void OnPoll_TimeOut()
{
//TODO 要处理 TimeOut / ParseFuncPack / GetSendMsg 线性同步问题
if (!IsConnected)
return;
if (currTran == null)
return;//没有指令
if (!stopwatch_timeOut.IsRunning)
return;//还没开始发送
if (stopwatch_timeOut.Elapsed < TimeSpan.FromSeconds(1))
return;//发送到现在,还没到1秒,继续等
//大于1秒也没回复,异常
//重试3次
retryCnt++;
stopwatch_timeOut.Stop();//停止,等下次发送
if (retryCnt >= 3)
{
//已经重试了3次,放弃
IsConnected = false;
currTran = null;
//清空 指令队列
Transactions.Clear();
return;
}
else
{
//再发一次指令
SendMsgEvent?.Invoke(this);
}
}
public void RecMsg(byte[] recBuf)
{
IsConnected = true;
csm.IncRec(recBuf.Count());
currPack.AddRange(recBuf);
if (logger.IsDebugEnabled) {
string msg = bytes2hex(recBuf);
logger.Debug($"REC {msg}");
}
ParsePack();
//OnPoll_TimeOut();
}
/// <summary>
/// 获取 发送队列 第1条msg
/// </summary>
/// <returns></returns>
public byte[] GetSendMsg()
{
//TODO 要处理 TimeOut / ParseFuncPack / GetSendMsg 线性同步问题
if (currTran == null)
{
//当前没有指令正在发送
if (Transactions.Count() == 0)//队列没有需要发送的指令
return null;
currTran = Transactions.First();
retryCnt = 0;
Transactions.RemoveAt(0);
}
else
{
//发送出去中,等待回复
if (stopwatch_timeOut.IsRunning)
return null;//已经发送了,计时器都启动了
}
var tran = currTran;
//调试: 打印发送信息
if(logger.IsDebugEnabled)
{
string msg = $"REQ N:{tran.deviceNo} F:{tran.func:00} A:{tran.addr} C:{tran.cnt} ";
if (tran.desription != null)
msg += tran.desription;
logger.Debug(msg);
//msg = bytes2hex(tran.sendBuf);
//logger.Debug($"SEND {msg}");
}
//开始计时
stopwatch_timeOut.Restart();
return tran.sendBuf.ToArray();
}
/// <summary>
/// 复位全部状态,通常由于通讯模块检测到连接断开导致的
/// </summary>
public void ResetMsg()
{
currTran = null;
stopwatch_timeOut.Stop();
csm.Reset();
IsConnected = false;
Transactions.Clear();
}
string bytes2hex(IEnumerable<byte> pack)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < pack.Count(); i++)
{
sb.Append($"{pack.ElementAt(i):X2} ");
}
return sb.ToString();
}
/// <summary>
/// 包解析
/// </summary>
/// <param name="datas"></param>
protected void ParsePack()
{
if (currTran == null)
{
//没有请求。。。
currPack.Clear();
return;
}
if (currPack.Count() < 3)
{
//数据太少
return;
}
if (currPack[0] != currTran.deviceNo)
{
logger.Error($"ACK 接收的SLAVE地址{currPack[0]}与发送的{currTran.deviceNo}不一致");
ErrCnt++;
currPack.Clear();
return;
}
if (currPack[1] != currTran.func)
{
logger.Error($"ACK 错误码:{currPack[1]} 异常码:{currPack[2]}");
ErrCnt++;
currPack.Clear();
return;
}
if (currPack.Count() < currTran.expRecBytes)
return;//数据还没收完
UInt16 crc = currPack.CRC16(0, currTran.expRecBytes - 2);
UInt16 rec_crc = currPack.ToUInt16_Little_Endian(currTran.expRecBytes - 2);
if (crc != rec_crc)
{
logger.Error($"ACK 指令码:{currTran.func:X2} CRC 校验出错 接收:{rec_crc:X4} 计算:{crc:X4}");
ErrCnt++;
currPack.Clear();
return;
}
//成功解析出一个包
currTran.funcData.AddRange(currPack.Skip(2).Take(currTran.expRecBytes - 4));
csm.IncPack(1);
currPack.Clear();
//停止超时检测
stopwatch_timeOut.Stop();
switch (currTran.func)
{
case 0x03:
{
List<UInt16> values = new List<UInt16>();
int index = 1;
while (index < currTran.funcData.Count())
{
values.Add(currTran.funcData.ToUInt16_Big_Endian(index));
index += 2;
}
currTran.retData = currTran.parse16FuncPack(values);
}
break;
}
if (logger.IsDebugEnabled)
{
if (currTran.retData == null)
logger.Debug($"ACK");
else
logger.Debug($"ACK {Newtonsoft.Json.JsonConvert.SerializeObject(currTran.retData)}");
}
//有很多指令是没有回复数据的, 回调只是通知 指令已经执行了而已
//调用回调
if (currTran.asyncDelegate != null)
{
if (Dispatcher != null)//线程同步执行
{
Dispatcher.BeginInvoke(currTran.asyncDelegate, currTran.asyncContext, currTran.retData);
}
else
{
currTran.asyncDelegate.Invoke(currTran.asyncContext, currTran.retData);
}
}
//空出当前交易位置
currTran = null;
if (Transactions.Count() > 0)
{
//队列还有需要发送的指令,通知外部获取指令发送
SendMsgEvent?.Invoke(this);
}
}
public void AddTran(Modbus_Transaction tran)
{
//放入 交易队列
Transactions.Add(tran);
if (currTran == null)
{
//当前没有指令正在发送
SendMsgEvent?.Invoke(this);
}
}
/// <summary>
/// 读多个Holding REGs
/// </summary>
/// <param name="deviceNo"></param>
/// <param name="addr"></param>
/// <param name="cnt"></param>
/// <param name="desription"></param>
/// <param name="parseU16FuncPack"></param>
/// <param name="asyncDelegate"></param>
/// <param name="asyncContext"></param>
/// <returns></returns>
public Modbus_Transaction Do_03(byte deviceNo, int addr, int cnt,
string desription,
ParseU16FuncPackHandler parseU16FuncPack,
CallBackHandler asyncDelegate, object asyncContext)
{
Modbus_Transaction tran = new Modbus_Transaction()
{
deviceNo = deviceNo,
func = 0x03,
addr = addr,
cnt = cnt,
expRecBytes = cnt * 2 + 5,
desription = desription,
parse16FuncPack = parseU16FuncPack,
asyncDelegate = asyncDelegate,
asyncContext = asyncContext
};
tran.funcData.AddRange(((UInt16)addr).GetBytes_Big_endian());
tran.funcData.AddRange(((UInt16)cnt).GetBytes_Big_endian());
ToSendBuf(tran);
return tran;
}
void ToSendBuf(Modbus_Transaction tran)
{
var data = tran.sendBuf;
data.Add(tran.deviceNo);
data.Add(tran.func);
data.AddRange(tran.funcData);
UInt16 crc = data.CRC16(0, data.Count());
data.AddRange(crc.GetBytes_Little_Endian());
tran.funcData.Clear();
}
/// <summary>
/// 写单个 Holding REG
/// </summary>
/// <param name="deviceNo"></param>
/// <param name="addr"></param>
/// <param name="cnt"></param>
/// <param name="desription"></param>
/// <param name="asyncDelegate"></param>
/// <param name="asyncContext"></param>
/// <returns></returns>
public Modbus_Transaction Do_06(byte deviceNo, int addr, UInt16 value,
string desription,
CallBackHandler asyncDelegate, object asyncContext)
{
Modbus_Transaction tran = new Modbus_Transaction()
{
deviceNo = deviceNo,
func = 0x06,
addr = addr,
expRecBytes = 8,
desription = desription,
asyncDelegate = asyncDelegate,
asyncContext = asyncContext
};
tran.funcData.AddRange(((UInt16)addr).GetBytes_Big_endian());
tran.funcData.AddRange(((UInt16)value).GetBytes_Big_endian());
ToSendBuf(tran);
return tran;
}
/// <summary>
/// 写多个 Holding REG
/// </summary>
/// <param name="deviceNo"></param>
/// <param name="addr"></param>
/// <param name="cnt"></param>
/// <param name="desription"></param>
/// <param name="asyncDelegate"></param>
/// <param name="asyncContext"></param>
/// <returns></returns>
public Modbus_Transaction Do_10(byte deviceNo, int addr, IEnumerable<UInt16> datas,
string desription,
CallBackHandler asyncDelegate, object asyncContext)
{
Modbus_Transaction tran = new Modbus_Transaction()
{
deviceNo = deviceNo,
func = 0x10,
addr = addr,
expRecBytes = 8,
desription = desription,
asyncDelegate = asyncDelegate,
asyncContext = asyncContext
};
tran.funcData.AddRange(((UInt16)tran.addr).GetBytes_Big_endian());
tran.funcData.AddRange(((UInt16)datas.Count()).GetBytes_Big_endian());
tran.funcData.Add((byte)(datas.Count() * 2));
for (int i = 0; i < datas.Count(); i++)
{
tran.funcData.AddRange(datas.ElementAt(i).GetBytes_Big_endian());
}
ToSendBuf(tran);
return tran;
}
}
public class Modbus_Transaction
{
/// <summary>
/// 交易描述
/// </summary>
public string desription;
/// <summary>
/// 设备编号 0~255 0是广播
/// </summary>
public byte deviceNo;
/// <summary>
/// modbus 功能号
/// </summary>
public byte func;
/// <summary>
/// 数据地址
/// </summary>
public int addr;
/// <summary>
/// 操作的寄存器数量
/// </summary>
public int cnt;
/// <summary>
/// 期待接收的数据量
/// </summary>
public int expRecBytes;
public List<byte> sendBuf = new List<byte>();
/// <summary>
/// 功能 的数据
/// </summary>
public List<byte> funcData = new List<byte>();
public delegate object ParseU16FuncPackHandler(List<UInt16> values);
/// <summary>
/// 数据回复处理
/// </summary>
public ParseU16FuncPackHandler parse16FuncPack;
public delegate object ParseBoolFuncPackHandler(List<bool> values);
/// <summary>
/// 数据回复处理
/// </summary>
public ParseBoolFuncPackHandler parseBoolFuncPack;
/// <summary>
/// 回复的数据,只用于调试而已
/// </summary>
public object retData;
/// <summary>
/// 回复 callback
/// </summary>
public CallBackHandler asyncDelegate;
/// <summary>
/// 上下文
/// </summary>
public object asyncContext;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment