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