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; } } }