using NLog; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Windows.Threading; using static GeneralGommunication.Modbus_Transaction; namespace GeneralGommunication { /// /// 异步modbus ASCII /// 3个线程会调用它: /// 1.接收数据线程调用 RecMsg, func解码后,需要转为Dispatcher的线程触发func回复通知; /// func解码后,也会触发SendMsgEvent事件; /// 2.外部调用func,触发SendMsgEvent事件; /// 3.超时定时器,触发SendMsgEvent事件; /// public class ModbusAsciiAsync : IModbusAsciiAsync { public Logger logger = NLog.LogManager.GetCurrentClassLogger(); public event PropertyChangedEventHandler PropertyChanged; /// /// 包出错次数 /// public int ErrCnt { get; protected set; } /// /// 通讯速度测量模块 /// public CommSpeedMeasuring Csm => csm; /// /// 有数据需要发送 /// public event SendDataEventHandler SendMsgEvent; public event DeviceConnectEventHander DeviceConnectEvent; List currPack = new List(); /// /// 指令队列,必须等上1条指令回复了,才能发下条指令 /// List Transactions; /// /// 当前正在等待回复的指令 /// Modbus_Transaction currTran; CommSpeedMeasuring csm = new CommSpeedMeasuring(); /// /// currTran 重发次数 /// int retryCnt = 0; List deviceConnected = new List(); /// /// 对于全部 有返回的函数调用,都使用Dispatcher,使线程同步 /// Dispatcher dispatcher; DispatcherTimer timerTimeOut;//等待回复超时 public ModbusAsciiAsync() { Transactions = new List(); timerTimeOut = new DispatcherTimer(); timerTimeOut.Interval = TimeSpan.FromSeconds(0.2); timerTimeOut.Tick += TimerTimeOut_Tick; dispatcher = timerTimeOut.Dispatcher; } /// /// 通讯中 /// public bool IsBusy() { lock (Transactions) { if (currTran != null) return true; if (Transactions.Count() > 0) return true; return false; } } /// /// 检查发生列表是否包含某条指令 /// /// /// /// public bool Contain(int deviceNo, string desription) { lock (Transactions) { if (currTran != null) { if (currTran.deviceNo == deviceNo && currTran.desription == desription) { return true; } } if (Transactions.Count() > 0) { return Transactions.Any(t => t.deviceNo == deviceNo && t.desription == desription); } else { return false; } } } private void TimerTimeOut_Tick(object sender, EventArgs e) { timerTimeOut.Stop(); RetrySendMsg(); } public bool IsConnected(byte deviceNo) { if (deviceConnected.Contains(deviceNo)) return true; return false; } void RetrySendMsg() { bool isNeedToSendMsg = false; bool isDisconnected = false; int deviceNo = 0; lock (Transactions) { if (currTran == null)//没有数据需要发送 return; //大于1秒也没回复,异常 //重试2次 retryCnt++; if (retryCnt >= 3) { //已经发送了2次,放弃 AckErrNoRec($"第{retryCnt}次重试失败 放弃"); if (deviceConnected.Contains(currTran.deviceNo)) { //之前连接是好的。 deviceConnected.Remove(currTran.deviceNo); isDisconnected = true; deviceNo = currTran.deviceNo; } //删除发送给这个设备的全部指令 Transactions.RemoveAll(t => t.deviceNo == currTran.deviceNo); currTran = null; retryCnt = 0; if (Transactions.Count() != 0) //还要其它需要发送 { isNeedToSendMsg = true; } } else { AckErrNoRec($"第{retryCnt + 1}次重试"); //重新把指令放回队列 Transactions.Insert(0, currTran); currTran = null; //再发一次指令 isNeedToSendMsg = true; } } if (isDisconnected) { TriggerDeviceConnectEvent(new DeviceConnectEventArgs(){ deviceNo = deviceNo, isConnected = false, errMsg = "无回复"}); } if (isNeedToSendMsg) { var buf = GetSendMsg(); TriggerSendMsgEvent(buf); } } void TriggerDeviceConnectEvent(DeviceConnectEventArgs e) { dispatcher.BeginInvoke(DeviceConnectEvent, this, e); } void TriggerSendMsgEvent(byte[] data) { dispatcher.BeginInvoke(SendMsgEvent, this, data); } void TriggerCurrTranAsyncDelegate() { //线程同步执行 dispatcher.BeginInvoke(currTran.asyncDelegate, currTran.asyncContext, currTran.retData); } public void RecMsg(byte[] recBuf) { //停止超时检查 timerTimeOut.Stop(); csm.IncRec(recBuf.Count()); bool isFinished = false; bool isError = false; lock (Transactions) { if (currTran == null) { //没有请求。。。 return; } if (currTran.EnableLog) { AckDebug_Rec(recBuf); } //这次需要判断的位置 int index = currPack.Count(); currPack.AddRange(recBuf); for (int i = 0; i < recBuf.Count(); i++) { if (ParsePack(index + i, out isError)) { //处理完,删除全部接收 currPack.Clear(); isFinished = true; break; } } } if (isFinished) { if (isError) { //异常,需要重发 RetrySendMsg(); } else { //成功解析出包 retryCnt = 0; //复位重发次数 ParsePack2(); } return; } else { //还没接收完!!!! timerTimeOut.Start(); } } /// /// 获取 发送队列 第1条msg /// /// byte[] GetSendMsg() { timerTimeOut.Stop(); //TODO 要处理 TimeOut / ParseFuncPack / GetSendMsg 线性同步问题 lock (Transactions) { if (currTran == null) { //当前没有指令正在发送 if (Transactions.Count() == 0)//队列没有需要发送的指令 return null; currTran = Transactions.First(); Transactions.RemoveAt(0); } else { //发送出去中,等待回复 return null; } //调试: 打印发送信息 if (currTran.EnableLog) { ReqDebug(); } //开始计时 timerTimeOut.Start(); return currTran.sendBuf.ToArray(); } } /// /// 复位全部状态,通常由于通讯模块检测到连接断开导致的 /// public void ResetMsg() { lock (Transactions) { Transactions.Clear(); currTran = null; } retryCnt = 0; timerTimeOut.Stop(); csm.Reset(); //触发通知 var isConneced = deviceConnected.ToArray(); deviceConnected.Clear(); foreach (var deviceNo in isConneced) { //触发通知 TriggerDeviceConnectEvent(new DeviceConnectEventArgs(){deviceNo = deviceNo,isConnected = false,errMsg= "链路断开" }); } } void ReqDebug() { if (logger.IsDebugEnabled) { string msg = currTran.sendBuf.ToString_Bytes2Hex(); string msg_ascii = currTran.sendBuf.ToString_Bytes2Ascii(); logger.Debug($"REQ {currTran.deviceNo:00} {currTran.desription} | {msg}| {msg_ascii}"); } } void AckDebug_Rec(IEnumerable recBuf) { if (logger.IsDebugEnabled) { string msg = recBuf.ToString_Bytes2Hex(); string msg_ascii = recBuf.ToString_Bytes2Ascii(); logger.Debug($"rec {currTran.deviceNo:00} {currTran.desription} | {msg}| {msg_ascii}"); } ErrCnt++; } void AckErrNoRec(string errMsg) { if (logger.IsErrorEnabled) { logger.Error($"ACK {currTran.deviceNo:00} {currTran.desription} {errMsg}"); } ErrCnt++; } void AckErr(string errMsg) { if (logger.IsErrorEnabled) { string msg = currPack.ToString_Bytes2Hex(); string msg_ascii = currPack.ToString_Bytes2Ascii(); logger.Error($"ACK {currTran.deviceNo:00} {currTran.desription} {errMsg} | {msg}| {msg_ascii}"); } ErrCnt++; } void AckDebug(string debugMsg) { if (logger.IsDebugEnabled) { string msg = currTran.funcData.ToString_Bytes2Hex(); logger.Debug($"ACK {currTran.deviceNo:00} {currTran.desription} {debugMsg} | {msg}"); } ErrCnt++; } /// /// 每个接收byte解析, 处理完,返回true; /// 还需要更多byte,返回false /// /// 当前处理的currPack[index] bool ParsePack(int index, out bool isError) { isError = false; if (index == 0) { if (currPack[index] != (byte)':') { //包头错,可以删除 AckErr("包头错"); isError = true; return true; } } else { //匹配 0x0d,0x0a if (currPack[index] == 0x0a) { if (currPack[index - 1] != 0x0d) { //结尾出错, 可以删除 AckErr("包尾错"); isError = true; return true; } //: \r\n 已经判断完 int len = index + 1; if (len < 10) { AckErr("字节数太少"); isError = true; return true; } byte? addr = currPack.Ascii2Hex(1); if (addr == null) { AckErr("地址错"); isError = true; return true; } if (addr != currTran.deviceNo) { //不是回复这里的 //AckErr("地址错"); isError = true; return true; } //可以解析了 //转为hex byte[] recBuf = currPack.Ascii2Hex(1, len - 3); //:,\d,\a 3个不要的字符 if (recBuf == null) { AckErr($"不能翻译为 ASCII"); isError = true; return true; } byte lrc = recBuf.LRC8(0, recBuf.Count() - 1); byte rec_lrc = recBuf[recBuf.Count() - 1]; if (lrc != rec_lrc) { AckErr($"LRC 校验出错 接收:{rec_lrc:X4} 计算:{lrc:X2}"); isError = true; return true; } if (recBuf[1] != currTran.func) { AckErr($"错误码:{currPack[1]} 异常码:{currPack[2]}"); isError = true; return true; } //if (len != currTran.expRecBytes) //{ // AckErr("字节数异常 期待{currTran.expRecBytes}Bytes 实际收到{len}"); // return true; //} //成功解析出一个包 currTran.funcData.AddRange(recBuf.Skip(2).Take(recBuf.Count() - 3));//不要 devNo, func, lrc 这 3个byte csm.IncPack(1); return true; } else if (currPack[index - 1] == 0x0d) { AckErr("包尾错"); isError = true; return true; } } return false; } void ParsePack2() { //触发连接通知 int deviceNo; lock (Transactions) { deviceNo = currTran.deviceNo; } if (!deviceConnected.Contains(deviceNo)) { deviceConnected.Add(deviceNo); TriggerDeviceConnectEvent(new DeviceConnectEventArgs() { deviceNo = deviceNo, isConnected = true }); } lock (Transactions) { switch (currTran.func) { case 0x03: case 0x04: { List values = new List(); //index=0 为字节数 int index = 1; while (index < currTran.funcData.Count()) { values.Add(currTran.funcData.ToUInt16_Big_Endian(index)); index += 2; } currTran.retData = currTran.parse16FuncPack(values); if (currTran.EnableLog) { if (currTran.retData != null) { string msg = $"{Newtonsoft.Json.JsonConvert.SerializeObject(currTran.retData)}"; AckDebug(msg); } } } break; case 0x06: if (currTran.EnableLog) { AckDebug("OK"); } break; case 0x10: if (currTran.EnableLog) { AckDebug("OK"); } break; default: //不受支持的功能 { currTran.retData = currTran.funcData; if (currTran.EnableLog) { AckDebug("OK"); } } break; } } //有很多指令是没有回复数据的, 回调只是通知 指令已经执行了而已 //调用回调 if (currTran.asyncDelegate != null) { TriggerCurrTranAsyncDelegate(); } bool isNeedToSendMsg = false; lock (Transactions) { //空出当前交易位置 currTran = null; if (Transactions.Count() > 0) { //队列还有需要发送的指令,通知外部获取指令发送 isNeedToSendMsg = true; } } if (isNeedToSendMsg) { var buf = GetSendMsg(); TriggerSendMsgEvent(buf); } } public void AddTran(Modbus_Transaction tran) { bool isNeedToSendMsg = false; //放入 交易队列 lock (Transactions) { Transactions.Add(tran); if (currTran == null) { //当前没有指令正在发送 isNeedToSendMsg = true; } } if (isNeedToSendMsg) { var buf = GetSendMsg(); TriggerSendMsgEvent(buf); } } /// /// 读多个Holding REGs /// /// /// /// /// /// /// /// /// public Modbus_Transaction Do_03(int deviceNo, int addr, int cnt, string desription, ParseU16FuncPackHandler parseU16FuncPack, CallBackHandler asyncDelegate, object asyncContext) { //发送 //: (1byte) //devNo (1 ascii = 2 byte) //func (1 ascii = 2 byte) //addr (2 ascii = 4 byte) //regCnt (2 ascii = 4 byte) //lrc (1 ascii = 2 byte) //0x0d,0x0a (2 byte) //接收 //: (1 byte) //devNo (1 ascii = 2 byte) //func (1 ascii = 2 byte) //byteCnt (1 ascii = 2 byte) //reg*byteCnt/2 (2 * byteCnt/2 ascii = 4*N byte) //lrc (1 ascii = 2 byte) //0x0d,0x0a (2 byte) int expRecBytes = (cnt * 2 + 4) * 2 + 3; Modbus_Transaction tran = new Modbus_Transaction() { deviceNo = deviceNo, func = 0x03, addr = addr, cnt = cnt, expRecBytes = expRecBytes, 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; } /// /// 读多个Input REGs /// /// /// /// /// /// /// /// /// public Modbus_Transaction Do_04(int deviceNo, int addr, int cnt, string desription, ParseU16FuncPackHandler parseU16FuncPack, CallBackHandler asyncDelegate, object asyncContext) { //发送 //: (1byte) //devNo (1 ascii = 2 byte) //func (1 ascii = 2 byte) //addr (2 ascii = 4 byte) //regCnt (2 ascii = 4 byte) //lrc (1 ascii = 2 byte) //0x0d,0x0a (2 byte) //接收 //: (1 byte) //devNo (1 ascii = 2 byte) //func (1 ascii = 2 byte) //byteCnt (1 ascii = 2 byte) //reg*byteCnt/2 (2 * byteCnt/2 ascii = 4*N byte) //lrc (1 ascii = 2 byte) //0x0d,0x0a (2 byte) int expRecBytes = (cnt * 2 + 4) * 2 + 3; Modbus_Transaction tran = new Modbus_Transaction() { deviceNo = deviceNo, func = 0x04, addr = addr, cnt = cnt, expRecBytes = expRecBytes, 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; } public void ToSendBuf(Modbus_Transaction tran) { var data = new List(); data.Add((byte)tran.deviceNo); data.Add(tran.func); data.AddRange(tran.funcData); byte lrc = data.LRC8(0, data.Count()); data.Add(lrc); tran.funcData.Clear(); //ASCII TO HEX byte[] recBuf = data.HexToAscii(); tran.sendBuf.Add((byte)':'); tran.sendBuf.AddRange(recBuf); tran.sendBuf.Add(0x0d); tran.sendBuf.Add(0x0a); } /// /// 写单个 Holding REG /// /// /// /// /// /// /// /// public Modbus_Transaction Do_06(int deviceNo, int addr, UInt16 value, string desription, CallBackHandler asyncDelegate, object asyncContext) { //发送 //: (1byte) //devNo (1 ascii = 2 byte) //func (1 ascii = 2 byte) //addr (2 ascii = 4 byte) //reg (2 ascii = 4 byte) //lrc (1 ascii = 2 byte) //0x0d,0x0a (2 byte) //接收 //: (1 byte) //devNo (1 ascii = 2 byte) //func (1 ascii = 2 byte) //addr (2 ascii = 4 byte) //reg (2 ascii = 4 byte) //lrc (1 ascii = 2 byte) //0x0d,0x0a (2 byte) int expRecBytes = (7 * 2) + 3; Modbus_Transaction tran = new Modbus_Transaction() { deviceNo = deviceNo, func = 0x06, addr = addr, expRecBytes = expRecBytes, 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) { //发送 //: (1byte) //devNo (1 ascii = 2 byte) //func (1 ascii = 2 byte) //addr (2 ascii = 4 byte) //regCnt (2 ascii = 4 byte) //reg*regCnt (2*regCnt ascii = 2*regCnt*2 byte) //lrc (1 ascii = 2 byte) //0x0d,0x0a (2 byte) //接收 //: (1 byte) //devNo (1 ascii = 2 byte) //func (1 ascii = 2 byte) //addr (2 ascii = 4 byte) //regCnt (2 ascii = 4 byte) //lrc (1 ascii = 2 byte) //0x0d,0x0a (2 byte) int expRecBytes = (7 * 2) + 3; Modbus_Transaction tran = new Modbus_Transaction() { deviceNo = deviceNo, func = 0x10, addr = addr, expRecBytes = expRecBytes, 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; } } }