using NLog; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; namespace GeneralGommunication { /// /// 独立线程运行。 固需要做线程同步。 /// 支持Push, Call /// 其中Call, 一“问”,必须一“答”。 长时间没答,重发“问”,连续3次都没 "答" 触发通讯断开。 /// 正常模式,多个"问" 会储存在缓存区, 等上一条 "答" ,才能发下一条"问"。 /// 打包模式, 多个"问" 没有先后关系,把它们打包, 不等 "答" 就同时发送"问"。 但 还是必须等 全部 "答" 都回复,才能执行一个动作。 /// public abstract class Dev7E:IDev7E { public Logger logger = NLog.LogManager.GetCurrentClassLogger(); public event PropertyChangedEventHandler PropertyChanged; /// /// 包出错次数 /// public int ErrCnt { get; protected set; } /// /// 连接成功; /// 当命令多次发送失败,IsConnected = false /// public bool IsConnected { get; private set; } /// /// 通讯速度测量模块 /// public CommSpeedMeasuring Csm => csm; /// /// 有数据需要发送 /// public event SendDataEventHandler SendMsgEvent; public event TimeOutEventHandler TimeOutEvent; List currPack = new List(); /// /// 指令队列,必须等上1条指令回复了,才能发下条指令 /// List Transactions; /// /// 当前正在等待回复的指令 /// COMMREQ_TransactionBase currTran; /// /// currTran 发送后,开始计时 /// Stopwatch stopwatch_timeOut; /// /// currTran 重发次数 /// int retryCnt = 0; CommSpeedMeasuring csm = new CommSpeedMeasuring(); /// /// 指令前序 序号 /// protected int PrefixIndex { get; set; } = 1; /// /// 对于全部 有返回的函数调用,都使用Dispatcher,使线程同步 /// Dispatcher dispatcher; DispatcherTimer timerTimeOut;//等待回复超时 public Dev7E() { Transactions = new List(); stopwatch_timeOut = new Stopwatch(); timerTimeOut = new DispatcherTimer(); timerTimeOut.Interval = TimeSpan.FromSeconds(0.5); timerTimeOut.Tick += TimerTimeOut_Tick; timerTimeOut.Start(); } #region 测量 #endregion private void TimerTimeOut_Tick(object sender, EventArgs e) { //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次,放弃 ResetMsg(); //连接断开 TimeOutEvent?.Invoke(this); return; } else { //再发一次指令 byte[] msg = GetSendMsg(); SendMsgEvent?.Invoke(this,msg); } } public void RecMsg(byte[] recBuf) { //IsConnected = true; csm.IncRec(recBuf.Count()); List packs = new List(); lock (currPack) { for (int i = 0; i < recBuf.Count(); i++) { if (recBuf[i] == 0x7e) { //找到头了 //结束之前的包 if (currPack.Count > 0) { var pack = currPack.ToArray(); packs.Add(pack); currPack.Clear(); csm.IncPack(1); } } currPack.Add(recBuf[i]); } } for (int i = 0; i < packs.Count(); i++) { ParsePack(packs[i]); } } /// /// 获取 发送队列 第1条msg /// /// 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;//已经发送了,计时器都启动了 } if (currTran is COMMREQ_Transaction) { var tran = currTran as COMMREQ_Transaction; //找出 COMMREQ var commReq = tran.commReq; List pack = new List(); //添加前序 pack.AddRange(commReq.Prefix); //添加数据 if (tran.datas != null) pack.AddRange(tran.datas); //调试: 打印发送信息 if (tran.datasObj == null) logger.Debug($"REQ {commReq.PrefixString}"); else logger.Debug($"REQ {commReq.PrefixString} {Newtonsoft.Json.JsonConvert.SerializeObject(tran.datasObj)}"); //转为 7E格式 var buf = GetSendPack(pack); //开始计时 stopwatch_timeOut.Restart(); return buf.ToArray(); } else { //集群交易 List bufs = new List(); var _tran = currTran as COMMREQ_TransactionMulti; //获取还没接收到数据的交易,不能没有交易 var trans = _tran.transactions.FindAll(t => !t.hasRet); if (trans.Count() == 0) throw new Exception("没有 [没接收到数据的交易],程序写错"); foreach (var tran in trans) { //找出 COMMREQ var commReq = tran.commReq; List pack = new List(); //添加前序 pack.AddRange(commReq.Prefix); //添加数据 if (tran.datas != null) pack.AddRange(tran.datas); //转为 7E格式 var buf = GetSendPack(pack); bufs.AddRange(buf); //调试: 打印发送信息 if (tran.datasObj == null) logger.Debug($"REQ multi {commReq.PrefixString}"); else logger.Debug($"REQ multi {commReq.PrefixString} {Newtonsoft.Json.JsonConvert.SerializeObject(tran.datasObj)}"); } //开始计时 stopwatch_timeOut.Restart(); return bufs.ToArray(); } } /// /// 复位全部状态,通常由于通讯模块检测到连接断开导致的 /// public void ResetMsg() { currTran = null; stopwatch_timeOut.Stop(); csm.Reset(); IsConnected = false; Transactions.Clear(); } /// 包解析 /// void ParsePack(byte[] pack) { //第0个肯定是7E if (pack.Count() == 1) { //只有一个 7E return; } //转义数据 if (!Protocol7ECommon.Pdu2Data(pack, out List datas)) { //异常 return; } //解析后的 datas 没有了 帧包装 7E pack = datas.ToArray(); if (pack.Count() == 1) { //只有一个字节,异常!!! ErrCnt++; return; } //解析后的 datas 没有了 帧包装 7E byte crc8 = Misc.CRC.CRC8(pack, 0, pack.Count() - 1); if (pack.Last() != crc8) { //CRC8 出错 ErrCnt++; return; } IsConnected = true; ParsePackAfterCheckCRC8(pack); } protected abstract void ParsePackAfterCheckCRC8(byte[] buf); /// /// 功能包解析 /// /// protected virtual void ParseFuncPack(byte[] pack) { if (currTran == null) { //没有请求。。。 return; } if (currTran is COMMREQ_Transaction) { ParseFuncPack_Single(pack); } else { ParseFuncPack_Multi(pack); } } protected bool ParseFuncPack_Single(byte[] pack) { object retData = null; var tran = currTran as COMMREQ_Transaction; var commReq = tran.commReq; //还有B0 if (commReq.ResponseTotalLen + PrefixIndex > pack.Count()) { //失败,指令长度不对!! logger.Error($"ACK expect:{commReq.PrefixString} len={commReq.ResponseTotalLen + 1}, but reponse len ={pack.Count()} reponse: {pack.ToString_Bytes2HexAndAscii()}"); ErrCnt++; return false; } if (commReq.IsMatch != null) { if (!commReq.IsMatch(commReq, pack, PrefixIndex, commReq.IsMatchContext, ref retData)) { //回复对不上请求 logger.Error($"ACK expect:{commReq.PrefixString} len={commReq.ResponseTotalLen + 1}, but reponse len ={pack.Count()} reponse: {pack.ToString_Bytes2HexAndAscii()}"); ErrCnt++; return false; } } else { if (!IsMatch(commReq, pack)) { //回复对不上请求 logger.Error($"ACK expect:{commReq.PrefixString} len={commReq.ResponseTotalLen + 1}, but reponse len ={pack.Count()} reponse: {pack.ToString_Bytes2HexAndAscii()}"); ErrCnt++; return false; } //处理指令 //还有B0 if (commReq.ParseFuncPack != null)//解析回复数据 retData = commReq.ParseFuncPack(pack, commReq.Prefix.Count() + PrefixIndex); } tran.hasRet = true; tran.retData = retData; if (retData == null) logger.Debug($"ACK {commReq.PrefixString}"); else logger.Debug($"ACK {commReq.PrefixString} {Newtonsoft.Json.JsonConvert.SerializeObject(retData)}"); //有很多指令是没有回复数据的, 回调只是通知 指令已经执行了而已 //调用回调 if (tran.asyncDelegate != null) { if (dispatcher != null)//线程同步执行 { dispatcher.BeginInvoke(tran.asyncDelegate, tran.asyncContext, retData); } else { tran.asyncDelegate.Invoke(tran.asyncContext, retData); } } //停止超时检测 stopwatch_timeOut.Stop(); //空出当前交易位置 currTran = null; if (Transactions.Count() > 0) { //队列还有需要发送的指令,通知外部获取指令发送 byte[] msg = GetSendMsg(); SendMsgEvent?.Invoke(this,msg); } return true; } bool IsMatch2(COMMREQ commReq, byte[] pack, ref object retData) { //还有B0 if (commReq.ResponseTotalLen + PrefixIndex > pack.Count()) { //失败,指令长度不对!! //logger.Error($"ACK expect:{commReq.PrefixString} len={commReq.ReponseTotalLen + 1}, but reponse len ={pack.Count()} reponse: {bytes2hex(pack)}"); //ErrCnt++; return false; } if (commReq.IsMatch != null) { //自定义IsMatch规则 if (!commReq.IsMatch(commReq, pack, PrefixIndex, commReq.IsMatchContext, ref retData)) { //回复对不上请求 //logger.Error($"ACK expect:{commReq.PrefixString} ,but reponse: {bytes2hex(pack)}"); return false; } } else { //通用IsMatch规则 if (!IsMatch(commReq, pack)) { //回复对不上请求 //logger.Error($"ACK expect:{commReq.PrefixString} ,but reponse: {bytes2hex(pack)}"); return false; } //处理指令 //还有B0 if (commReq.ParseFuncPack != null)//解析回复数据 retData = commReq.ParseFuncPack(pack, commReq.Prefix.Count() + PrefixIndex); } return true; } protected bool ParseFuncPack_Multi(byte[] pack) { object retData = null; var _tran = currTran as COMMREQ_TransactionMulti; //寻找合适的commReq var trans = _tran.transactions.FindAll(t => !t.hasRet); COMMREQ commReq = null; COMMREQ_Transaction tran = null; foreach (var t in trans) { tran = t; commReq = tran.commReq; if (IsMatch2(commReq, pack, ref retData))//匹配正确,就是这个tran的回复 break; commReq = null; tran = null; } if (commReq == null) { //不能解析 logger.Error($"ACK multi reponse len ={pack.Count()} reponse: {pack.ToString_Bytes2HexAndAscii()}"); ErrCnt++; return false; } tran.hasRet = true; tran.retData = retData; //解析成功 if (retData == null) logger.Debug($"ACK multi {commReq.PrefixString}"); else logger.Debug($"ACK multi {commReq.PrefixString} {Newtonsoft.Json.JsonConvert.SerializeObject(retData)}"); //有很多指令是没有回复数据的, 回调只是通知 指令已经执行了而已 //调用回调 if (tran.asyncDelegate != null) { if (dispatcher != null)//线程同步执行 { dispatcher.BeginInvoke(tran.asyncDelegate, tran.asyncContext, retData); } else { tran.asyncDelegate.Invoke(tran.asyncContext, retData); } } if (_tran.transactions.All(t => t.hasRet)) { //完成, 全部回复 if (_tran.asyncDelegate != null) { if (dispatcher != null)//线程同步执行 { dispatcher.BeginInvoke(_tran.asyncDelegate, _tran.asyncContext, _tran); } else { tran.asyncDelegate.Invoke(_tran.asyncContext, _tran); } } //停止超时检测 stopwatch_timeOut.Stop(); //空出当前交易位置 currTran = null; if (Transactions.Count() > 0) { //队列还有需要发送的指令,通知外部获取指令发送 byte[] msg = GetSendMsg(); SendMsgEvent?.Invoke(this,msg); } return true; } return false; } /// /// 与指令前序比较,一定要完全一样 /// /// /// /// protected bool IsMatch(COMMREQ commreq, byte[] datas) { int prefix_index = PrefixIndex; //datas[0] 是B0, 不应该用于判断 //与指令前序一个个比较 for (int i = 0; i < commreq.Prefix.Length; i++) { if (commreq.Prefix[i] != datas[prefix_index + i]) { return false; } } return true; } /// /// 数据发送,输入不需要包含开头的7E; 输出会添加CRC8 和 7E /// /// protected virtual List GetSendPack(List datas) { //需要在后面添加CRC8 byte crc8 = Misc.CRC.CRC8(datas, 0, datas.Count()); datas.Add(crc8); Protocol7ECommon.Data2Pdu(datas, out List pack); pack.Add(0x7E);//一定要加0x7E return pack; } protected void AddTran(COMMREQ_Transaction tran) { if (transactionMulti!=null) { transactionMulti.transactions.Add(tran); } else { //放入 交易队列 Transactions.Add(tran); if (currTran == null) { //当前没有指令正在发送 byte[] msg = GetSendMsg(); SendMsgEvent?.Invoke(this,msg); } } } COMMREQ_TransactionMulti transactionMulti; void BuildMultiTransAsync() { if (transactionMulti!=null) { throw new Exception("已经是 BuildMultiTrans, 不能再次执行, 必须调用 AddMultiTrans"); } transactionMulti = new COMMREQ_TransactionMulti(); } public void BuildMultiTrans(Action action) { BuildMultiTrans(action, null, null); } public void BuildMultiTrans(Action action, CallBackHandler asyncDelegate, object asyncContext) { BuildMultiTransAsync(); action(); AddMultiTransAsync(asyncDelegate, asyncContext); } void AddMultiTransAsync(CallBackHandler asyncDelegate, object asyncContext) { if (transactionMulti.transactions.Count() == 0) { throw new Exception("MultiTrans 为空"); } transactionMulti.asyncDelegate = asyncDelegate; transactionMulti.asyncContext = asyncContext; //放入 交易队列 Transactions.Add(transactionMulti); transactionMulti = null; if (currTran == null) { //当前没有指令正在发送 byte[] msg = GetSendMsg(); SendMsgEvent?.Invoke(this, msg); } } } }