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
{
///
/// 异步modbus RTU
///
public class ModbusRtuAsync:INotifyPropertyChanged
{
public Logger logger = NLog.LogManager.GetCurrentClassLogger();
public event PropertyChangedEventHandler PropertyChanged;
///
/// 包出错次数
///
public int ErrCnt { get; protected set; }
///
/// 通讯速度测量模块
///
public CommSpeedMeasuring Csm => csm;
///
/// 有数据需要发送
///
public event SendDataEventHandler SendMsgEvent;
///
/// 对于全部 有返回的函数调用,都使用Dispatcher,使线程同步
///
public Dispatcher Dispatcher;
List currPack = new List();
///
/// 指令队列,必须等上1条指令回复了,才能发下条指令
///
List Transactions;
///
/// 当前正在等待回复的指令
///
Modbus_Transaction currTran;
CommSpeedMeasuring csm = new CommSpeedMeasuring();
///
/// currTran 重发次数
///
int retryCnt = 0;
List deviceConnected = new List();
public event DeviceConnectEventHander DeviceConnectEvent;
///
/// 通讯中
///
public bool IsBusy
{
get {
if (currTran != null)
return true;
if (Transactions.Count() > 0)
return true;
return false;
}
}
System.Timers.Timer timer3d5t;//通讯包的 3.5T 时间间隔。 实际只是 15ms定时而已
System.Timers.Timer timerTimeOut;//等待回复超时
public ModbusRtuAsync()
{
Transactions = new List();
timer3d5t = new System.Timers.Timer();
timer3d5t.Interval = 30;//设得再小也没有用。 它只是用系统的15ms定时器触发而已。
timer3d5t.AutoReset = false;//触发一次
timer3d5t.Elapsed += Timer3d5t_Elapsed;
timerTimeOut = new System.Timers.Timer();
timerTimeOut.Interval = 500;
timerTimeOut.AutoReset = false;
timerTimeOut.Elapsed += TimerTimeOut_Elapsed;
}
public bool IsConnected(byte deviceNo)
{
if (deviceConnected.Contains(deviceNo))
return true;
return false;
}
private void Timer3d5t_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
ParsePack();
}
private void TimerTimeOut_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (currTran == null)//没有数据需要发送,异常
return;
//大于1秒也没回复,异常
//重试2次
retryCnt++;
logger.Error($"TIMEOUT 第{retryCnt}次超时");
if (retryCnt >= 2)
{
//已经发送了2次,放弃
if (deviceConnected.Contains(currTran.deviceNo))
{
//之前连接是好的。
deviceConnected.Remove(currTran.deviceNo);
//触发通知
DeviceConnectEvent?.Invoke(this, new DeviceConnectEventArgs()
{
deviceNo = currTran.deviceNo,
isConnected = false
});
}
//删除发送给这个设备的全部指令
Transactions.RemoveAll(t => t.deviceNo == currTran.deviceNo);
currTran = null;
retryCnt = 0;
if (Transactions.Count() != 0) //还要其它需要发送
{
byte[] msg = GetSendMsg();
SendMsgEvent?.Invoke(this,msg);
}
return;
}
else
{
//重新把指令放回队列
Transactions.Insert(0, currTran);
currTran = null;
//再发一次指令
byte[] msg = GetSendMsg();
SendMsgEvent?.Invoke(this, msg);
}
}
public void RecMsg(byte[] recBuf)
{
//停止超时检查
timerTimeOut.Stop();
//15ms后再处理。
timer3d5t.Stop();
csm.IncRec(recBuf.Count());
currPack.AddRange(recBuf);
if (logger.IsDebugEnabled) {
string msg = recBuf.ToString_Bytes2Hex();
logger.Debug($"ACK {msg}");
}
timer3d5t.Start();
}
///
/// 获取 发送队列 第1条msg
///
///
byte[] GetSendMsg()
{
//TODO 要处理 TimeOut / ParseFuncPack / GetSendMsg 线性同步问题
if (currTran == null)
{
//当前没有指令正在发送
if (Transactions.Count() == 0)//队列没有需要发送的指令
return null;
currTran = Transactions.First();
Transactions.RemoveAt(0);
}
else
{
//发送出去中,等待回复
return null;
}
var tran = currTran;
//调试: 打印发送信息
if(logger.IsDebugEnabled)
{
string msg = tran.sendBuf.ToString_Bytes2Hex();
if (tran.desription == null)
logger.Debug($"REQ {msg}");
else
logger.Debug($"REQ {tran.desription} {msg}");
}
//开始计时
timerTimeOut.Start();
return tran.sendBuf.ToArray();
}
///
/// 复位全部状态,通常由于通讯模块检测到连接断开导致的
///
public void ResetMsg()
{
Transactions.Clear();
currTran = null;
retryCnt = 0;
timer3d5t.Stop();
timerTimeOut.Stop();
csm.Reset();
//触发通知
var isConneced = deviceConnected.ToArray();
deviceConnected.Clear();
foreach (var deviceNo in isConneced)
{
//触发通知
DeviceConnectEvent?.Invoke(this, new DeviceConnectEventArgs()
{
deviceNo = deviceNo,
isConnected = false
});
}
}
///
/// 已经收完一份数据,不会再有更多,包解析
///
void ParsePack()
{
if (currTran == null)
{
//没有请求。。。
currPack.Clear();
return;
}
if (currPack.Count() < 3)
{
//异常 数据太少 通知重发
logger.Error($"ACK 接收数据小于3个byte");
ErrCnt++;
currPack.Clear();
TimerTimeOut_Elapsed(null, null);
return;
}
if (currPack[0] != currTran.deviceNo)
{
logger.Error($"ACK 接收的SLAVE地址{currPack[0]}与发送的{currTran.deviceNo}不一致");
ErrCnt++;
currPack.Clear();
TimerTimeOut_Elapsed(null, null);
return;
}
if (currPack[1] != currTran.func)
{
logger.Error($"ACK 错误码:{currPack[1]:X2} 异常码:{currPack[2]:X2}");
ErrCnt++;
currPack.Clear();
TimerTimeOut_Elapsed(null, null);
return;
}
if (currPack.Count() < currTran.expRecBytes)
{
logger.Error($"ACK 数据还没接收完");
ErrCnt++;
currPack.Clear();
TimerTimeOut_Elapsed(null, null);
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}");
//DeviceConnectEvent?.Invoke(this, new DeviceConnectEventArgs() { deviceNo = currTran.deviceNo, isConnected = false, errMsg = errMsg });
ErrCnt++;
currPack.Clear();
TimerTimeOut_Elapsed(null, null);
return;
}
//成功解析出一个包
currTran.funcData.AddRange(currPack.Skip(2).Take(currTran.expRecBytes - 4));
csm.IncPack(1);
currPack.Clear();
//触发连接通知
if (!deviceConnected.Contains(currTran.deviceNo))
{
deviceConnected.Add(currTran.deviceNo);
DeviceConnectEvent?.Invoke(this, new DeviceConnectEventArgs() { deviceNo = currTran.deviceNo, isConnected = true });
}
switch (currTran.func)
{
case 0x03:
{
List values = new List();
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)
{
//队列还有需要发送的指令,通知外部获取指令发送
byte[] msg = GetSendMsg();
SendMsgEvent?.Invoke(this,msg);
}
}
public void AddTran(Modbus_Transaction tran)
{
//放入 交易队列
Transactions.Add(tran);
if (currTran == null)
{
//当前没有指令正在发送
byte[] msg = GetSendMsg();
SendMsgEvent?.Invoke(this,msg);
}
}
///
/// 读多个Holding REGs
///
///
///
///
///
///
///
///
///
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((byte)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();
}
///
/// 写单个 Holding REG
///
///
///
///
///
///
///
///
public Modbus_Transaction Do_06(int 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;
}
///
/// 写多个 Holding REG
///
///
///
///
///
///
///
///
public Modbus_Transaction Do_10(int deviceNo, int addr, IEnumerable 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;
}
}
}