476 lines
18 KiB
C#
476 lines
18 KiB
C#
using JiShe.CollectBus.Common.Enums;
|
|
using JiShe.CollectBus.Common.Extensions;
|
|
using JiShe.CollectBus.Common.Models;
|
|
using JiShe.CollectBus.Protocol.Contracts.Interfaces;
|
|
using JiShe.CollectBus.Protocol.Contracts.Models;
|
|
using Microsoft.Extensions.Logging;
|
|
using System.Net.Sockets;
|
|
|
|
namespace JiShe.CollectBus.Protocol.Contracts.Abstracts
|
|
{
|
|
public abstract class BaseProtocolPlugin(ILogger<BaseProtocolPlugin> logger) : IProtocolPlugin
|
|
{
|
|
//起始字符
|
|
public const string stx = "68";
|
|
//结束字符
|
|
public const string end = "16";
|
|
//头部字节长度
|
|
public const int hearderLen = 6;
|
|
//消息认证码字段长度
|
|
public const int pWLen = 16;
|
|
|
|
public const int tPLen = 6;
|
|
|
|
|
|
public abstract Task<ProtocolInfo> GetAsync();
|
|
|
|
public abstract Task AnalyzeAsync(MessageReceivedEvent messageReceivedEvent, Action<byte[]>? sendAction = null);
|
|
|
|
/// <summary>
|
|
/// 登录帧解析
|
|
/// </summary>
|
|
/// <param name="messageReceivedEvent">报文</param>
|
|
/// <param name="sendAction">发送委托</param>
|
|
/// <returns></returns>
|
|
public virtual async Task LoginAsync(MessageReceivedLoginEvent messageReceivedEvent, Action<byte[]>? sendAction = null)
|
|
{
|
|
var hexStringList = messageReceivedEvent.MessageHexString.StringToPairs();
|
|
var aTuple = (Tuple<string, int>)hexStringList.GetAnalyzeValue(CommandChunkEnum.A);
|
|
var seq = (Seq)hexStringList.GetAnalyzeValue(CommandChunkEnum.SEQ);
|
|
var reqParam = new ReqParameter2
|
|
{
|
|
AFN = AFN.确认或否认,
|
|
FunCode = (int)CFromStationFunCode.链路数据,
|
|
PRM = PRM.从动站报文,
|
|
A = aTuple.Item1,
|
|
Seq = new Seq()
|
|
{
|
|
TpV = TpV.附加信息域中无时间标签,
|
|
FIRFIN = FIRFIN.单帧,
|
|
CON = CON.需要对该帧进行确认,
|
|
PRSEQ = seq.PRSEQ
|
|
},
|
|
MSA = aTuple.Item2,
|
|
Pn = 0,
|
|
Fn = 1
|
|
};
|
|
var bytes = GetCommandBytes(reqParam);
|
|
if (sendAction != null)
|
|
{
|
|
sendAction(bytes);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 心跳帧解析
|
|
/// </summary>
|
|
/// <param name="messageReceivedEvent">报文</param>
|
|
/// <param name="sendAction">发送委托</param>
|
|
/// <returns></returns>
|
|
public virtual async Task HeartbeatAsync(MessageReceivedHeartbeatEvent messageReceivedEvent, Action<byte[]>? sendAction = null)
|
|
{
|
|
var hexStringList = messageReceivedEvent.MessageHexString.StringToPairs();
|
|
var aTuple = (Tuple<string,int>)hexStringList.GetAnalyzeValue(CommandChunkEnum.A);
|
|
var seq = (Seq)hexStringList.GetAnalyzeValue(CommandChunkEnum.SEQ);
|
|
if (seq.TpV == TpV.附加信息域中带时间标签)
|
|
{
|
|
//解析
|
|
|
|
}
|
|
if (seq.CON == CON.需要对该帧进行确认)
|
|
{
|
|
var reqParam = new ReqParameter2()
|
|
{
|
|
AFN = AFN.确认或否认,
|
|
FunCode = (int)CFromStationFunCode.链路数据,
|
|
PRM = PRM.从动站报文,
|
|
A = aTuple.Item1,
|
|
Seq = new Seq()
|
|
{
|
|
TpV = TpV.附加信息域中无时间标签,
|
|
FIRFIN = FIRFIN.单帧,
|
|
CON = CON.不需要对该帧进行确认,
|
|
PRSEQ = seq.PRSEQ,
|
|
},
|
|
MSA = aTuple.Item2,
|
|
Pn = 0,
|
|
Fn = 1
|
|
};
|
|
var bytes = GetCommandBytes(reqParam);
|
|
if (sendAction != null)
|
|
{
|
|
sendAction(bytes);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 确认或否认
|
|
/// </summary>
|
|
/// <param name=""></param>
|
|
/// <returns></returns>
|
|
public virtual async Task AnswerAsync(MessageReceivedHeartbeatEvent messageReceivedEvent, Action<byte[]>? sendAction = null)
|
|
{
|
|
var hexStringList = messageReceivedEvent.MessageHexString.StringToPairs();
|
|
var fn = hexStringList.GetAnalyzeValue(CommandChunkEnum.FN);
|
|
//1:全部确认
|
|
//2:全部否认
|
|
//3:按数据单元表示确认和否认
|
|
//4 硬件安全认证错误应答
|
|
}
|
|
|
|
/// <summary>
|
|
/// 电表档案解析
|
|
/// </summary>
|
|
/// <param name="messageReceivedEvent"></param>
|
|
/// <param name="sendAction"></param>
|
|
/// <returns></returns>
|
|
public virtual async Task AmmeterParameterAsync(MessageReceivedHeartbeatEvent messageReceivedEvent, Action<byte[]>? sendAction = null)
|
|
{
|
|
var hexStringList = messageReceivedEvent.MessageHexString.StringToPairs();
|
|
var hexDatas = (List<string>)hexStringList.GetAnalyzeValue(CommandChunkEnum.Data);
|
|
|
|
var meterList = new List<AmmeterParameter>();
|
|
var count = (hexDatas[1] + hexDatas[0]).HexToDec();
|
|
//if (2 + count * 27 != hexDatas.Count - pWLen - tPLen - 2)
|
|
// return;
|
|
var index = 2;//数量
|
|
for (int i = 1; i <= count; i++)
|
|
{
|
|
var meterNumber = $"{hexDatas[index + 1]}{hexDatas[index]}".HexToDec();
|
|
index += 2;
|
|
|
|
var pn = $"{hexDatas[index + 1]}{hexDatas[index]}".HexToDec();
|
|
index += 2;
|
|
|
|
var baudRateAndPortBin = hexDatas[index].HexToBin().PadLeft(8, '0');
|
|
var baudRate = baudRateAndPortBin.Substring(0, 3).BinToDec();
|
|
var port = baudRateAndPortBin.Substring(3, 5).BinToDec();
|
|
index += 1;
|
|
|
|
var protocolType = (CommunicationProtocolType)hexDatas[index].HexToDec();
|
|
index += 1;
|
|
|
|
var addressHexList = hexDatas.Skip(index).Take(6).ToList();
|
|
addressHexList.Reverse();
|
|
var address = string.Join("", addressHexList);
|
|
index += 6;
|
|
|
|
var pwdHexList = hexDatas.Skip(index).Take(6).ToList();
|
|
pwdHexList.Reverse();
|
|
var password = string.Join("", pwdHexList.Take(3).ToList());
|
|
index += 6;
|
|
|
|
var rateNumberBin = hexDatas[index].HexToBin().PadLeft(8, '0');
|
|
var rateNumber = rateNumberBin.Substring(4).BinToDec();
|
|
index += 1;
|
|
|
|
var intBitAndDecBitNumberBin = hexDatas[index].HexToBin().PadLeft(8, '0');
|
|
var intBitNumber = intBitAndDecBitNumberBin.Substring(4, 2).BinToDec() + 4;
|
|
var decBitNumber = intBitAndDecBitNumberBin.Substring(6, 2).BinToDec() + 1;
|
|
index += 1;
|
|
|
|
// hexDatas.GetRange()
|
|
var collectorAddressHexList = hexDatas.Skip(index).Take(6).ToList();
|
|
collectorAddressHexList.Reverse();
|
|
var collectorAddress = string.Join("", collectorAddressHexList);
|
|
index += 6;
|
|
|
|
var userClassNumberBin = hexDatas[index].HexToBin().PadLeft(8, '0');
|
|
var userClass = userClassNumberBin.Substring(0, 4).BinToDec();
|
|
var userSubClass = userClassNumberBin.Substring(4, 4).BinToDec();
|
|
index += 1;
|
|
|
|
meterList.Add(new AmmeterParameter()
|
|
{
|
|
Pn = pn,
|
|
BaudRate = baudRate,
|
|
Port = port,
|
|
ProtocolType = protocolType,
|
|
Address = address,
|
|
Password = password,
|
|
RateNumber = rateNumber,
|
|
IntegerBitNumber = intBitNumber,
|
|
DecimalBitNumber = decBitNumber,
|
|
CollectorAddress = collectorAddress,
|
|
UserCategoryNumber = userClass,
|
|
UserSubclassNumber = userSubClass,
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// 当前正向有功电能示值
|
|
/// </summary>
|
|
/// <param name="messageReceivedEvent">报文</param>
|
|
/// <param name="sendAction">发送委托</param>
|
|
/// <returns></returns>
|
|
public virtual async Task ActivePowerIndicationAsync(MessageReceivedHeartbeatEvent messageReceivedEvent, Action<byte[]>? sendAction = null)
|
|
{
|
|
var hexStringList = messageReceivedEvent.MessageHexString.StringToPairs();
|
|
var hexDatas = (List<string>)hexStringList.GetAnalyzeValue(CommandChunkEnum.Data);
|
|
|
|
var minutes = Convert.ToInt32(hexDatas[0]); // 获取当前分钟数
|
|
var hours = Convert.ToInt32(hexDatas[1]); // 获取当前小时数
|
|
var day = Convert.ToInt32(hexDatas[2]); // 获取当前日期的日数
|
|
var month = Convert.ToInt32(hexDatas[3]); // 获取当前月份
|
|
var year = Convert.ToInt32(hexDatas[4]); // 获取当前日期的年份
|
|
|
|
var rateNumber = Convert.ToInt32(hexDatas[5]);
|
|
var kwhTotal = hexDatas.Skip(5).Take(5).ToList();
|
|
var kwhList = new List<decimal>();
|
|
var index = 11;
|
|
for (int i = 0; i < rateNumber; i++)
|
|
{
|
|
var kwhHexList = hexDatas.Skip(11).Take(5).ToList();
|
|
kwhHexList.Reverse();
|
|
var integerStr = $"{kwhHexList.Take(0)}{kwhHexList.Take(1)}{kwhHexList.Take(2)}";
|
|
var decimalValStr = $"{kwhHexList[3]}{kwhHexList[4]}";
|
|
var val = decimal.Parse($"{integerStr}{decimalValStr}");
|
|
kwhList.Add(val);
|
|
index += 5;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 日冻结正向有功电能示值
|
|
/// </summary>
|
|
/// <param name="messageReceivedEvent"></param>
|
|
/// <param name="sendAction"></param>
|
|
/// <returns></returns>
|
|
public virtual async Task DailyFrozenAsync(MessageReceivedHeartbeatEvent messageReceivedEvent, Action<byte[]>? sendAction = null)
|
|
{
|
|
var hexStringList = messageReceivedEvent.MessageHexString.StringToPairs();
|
|
var hexDatas = (List<string>)hexStringList.GetAnalyzeValue(CommandChunkEnum.Data);
|
|
|
|
|
|
}
|
|
//TODO:F17 当前总加有功功率 附录A.2 kw 2字节
|
|
//F18 当前总加无功功率 附录A.2 kvar 2字节
|
|
//F25 161
|
|
#region 通用解析
|
|
|
|
public byte[] GetCommandBytes(ReqParameter reqParameter, List<string>? dataUnit = null)
|
|
{
|
|
var cmdStrList = new List<string>();
|
|
var userDatas = GetUserData(reqParameter, dataUnit);
|
|
var hearders = GetHeaders(userDatas.Count);
|
|
var cs = GetCS(userDatas);
|
|
cmdStrList.AddRange(hearders);
|
|
cmdStrList.AddRange(userDatas);
|
|
cmdStrList.Add(cs);
|
|
cmdStrList.Add(end);
|
|
logger.LogInformation($"回复:{string.Join(" ", cmdStrList)}");
|
|
var bytes = cmdStrList.Select(x => Convert.ToByte(x, 16)).ToArray();
|
|
return bytes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 帧校验和
|
|
/// </summary>
|
|
/// <param name="userData">用户数据区</param>
|
|
/// <returns></returns>
|
|
public string GetCS(List<string> userData)
|
|
{
|
|
byte sum = 0;
|
|
foreach (var d in userData)
|
|
{
|
|
var b = Convert.ToByte(d, 16);
|
|
sum += b;
|
|
}
|
|
return sum.ToString("X2");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 用户数据区
|
|
/// </summary>
|
|
/// <param name="reqParameter"></param>
|
|
/// <returns></returns>
|
|
public List<string> GetUserData(ReqParameter reqParameter, List<string>? dataUnit)
|
|
{
|
|
var c = GetC(reqParameter.FunCode, reqParameter.PRM);
|
|
var a = GetAList(reqParameter.A, reqParameter.MSA);
|
|
|
|
var linkUserData = GetLinkUserData(reqParameter.AFN, reqParameter.Seq,
|
|
((ReqParameter2)reqParameter).Pn, ((ReqParameter2)reqParameter).Fn, dataUnit);
|
|
|
|
var list = new List<string>() { c };
|
|
list.AddRange(a);
|
|
list.AddRange(linkUserData);
|
|
return list;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 固定长度的报文头 起始字符+长度+长度+起始字符
|
|
/// </summary>
|
|
/// <param name="length"></param>
|
|
/// <returns></returns>
|
|
public List<string> GetHeaders(int length)
|
|
{
|
|
var headers = new List<string>();
|
|
headers.Add(stx);
|
|
var l = GetLength(length);
|
|
headers.AddRange(l);
|
|
headers.AddRange(l);
|
|
headers.Add(stx);
|
|
return headers;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 长度 2字节 [用户数据区长度]
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public List<string> GetLength(int length1)
|
|
{
|
|
var binaryLen = length1.DecToBin();
|
|
var protocolIdentification = Enum.Format(typeof(ProtocolIdentification),
|
|
ProtocolIdentification.本规约使用, "d").PadLeft(2, '0');
|
|
var lenStr = $"{binaryLen}{protocolIdentification}";
|
|
var hexLen = lenStr.BinToHex();
|
|
hexLen = hexLen.PadLeft(4, '0');
|
|
var list = hexLen.StringToPairs();
|
|
list.Reverse();
|
|
return list;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 控制域
|
|
/// </summary>
|
|
/// <param name="funCode">功能码</param>
|
|
/// <param name="fcb"></param>
|
|
/// <param name="fcv"></param>
|
|
/// <returns></returns>
|
|
public string GetC(int funCode, PRM pRM, int fcb = 0, FCV fcv = FCV.FCB位无效)
|
|
{
|
|
var cMasterStationFunCodeHex = funCode.DecToBin();
|
|
cMasterStationFunCodeHex = cMasterStationFunCodeHex.ToString().PadLeft(4, '0');
|
|
var strC = $"{(int)DIR.主站下行报文}{(int)pRM}{fcb}{(int)fcv}{cMasterStationFunCodeHex}";
|
|
var hexC = strC.BinToHex().PadLeft(2, '0');
|
|
return hexC;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 地址域 3220 09872
|
|
/// </summary>
|
|
/// <param name="a1">行政区划码 BCD码 3220=2032</param>
|
|
/// <param name="a2">逻辑地址 BIN 09872=2690=>9026</param>
|
|
/// <param name="a3">主站地址 BIN 0~127</param>
|
|
/// <returns></returns>
|
|
public List<string> GetAList(string a, int mSA)
|
|
{
|
|
var list = new List<string>();
|
|
|
|
var a1 = a.Substring(0, 4);
|
|
var a1Pairs = a1.StringToPairs();
|
|
a1Pairs.Reverse();
|
|
list.AddRange(a1Pairs);
|
|
|
|
var a2 = Convert.ToInt32(a.Substring(4));
|
|
var decA2 = a2.DecToHex();
|
|
var a2Pairs = decA2.PadLeft(4, '0').StringToPairs();
|
|
a2Pairs.Reverse();
|
|
list.AddRange(a2Pairs);
|
|
|
|
//TODO:主站地址和组地址标志
|
|
var a3Bin = $"{mSA.DecToBin().PadLeft(7, '0')}0";
|
|
list.Add(a3Bin.BinToHex().PadLeft(2, '0'));
|
|
|
|
return list;
|
|
}
|
|
|
|
public List<string> GetLinkUserData(AFN aFN, Seq seq, int pn, int fn, List<string>? dataUnit)
|
|
{
|
|
var aFNValue = ((int)aFN).DecToHex().PadLeft(2, '0');
|
|
var sEQ = GetSEQ(seq.TpV, seq.FIRFIN, seq.CON, seq.PRSEQ);
|
|
var dA = GetDA(pn);
|
|
var dT = GetDT(fn);
|
|
var list = new List<string>() { aFNValue, sEQ };
|
|
list.AddRange(dA);
|
|
list.AddRange(dT);
|
|
|
|
if (dataUnit != null)
|
|
{
|
|
list.AddRange(dataUnit);
|
|
}
|
|
//list.AddRange(GetDataUnit(aFN,seq));
|
|
|
|
if (seq.TpV == TpV.附加信息域中带时间标签)
|
|
list.AddRange(GetTp("00"));
|
|
|
|
return list;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 帧序列域
|
|
/// </summary>
|
|
/// <param name="tpV"></param>
|
|
/// <param name="fIRFIN"></param>
|
|
/// <param name="cON"></param>
|
|
/// <returns></returns>
|
|
public string GetSEQ(TpV tpV, FIRFIN fIRFIN, CON cON, int pRSEQ)
|
|
{
|
|
var tpVValue = Enum.Format(typeof(TpV),
|
|
tpV, "d");
|
|
var fIRFINValue = Enum.Format(typeof(FIRFIN),
|
|
fIRFIN, "d");
|
|
var cONValue = (int)cON;
|
|
var sEQBin = $"{tpVValue}{fIRFINValue}{cONValue}{pRSEQ.DecToBin().PadLeft(4, '0')}";
|
|
var hexSEQ = sEQBin.BinToHex().PadLeft(2, '0');
|
|
return hexSEQ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 信息点标识
|
|
/// </summary>
|
|
/// <param name="pn">计量点</param>
|
|
/// <returns></returns>
|
|
public List<string> GetDA(int pn)
|
|
{
|
|
if (pn == 0)
|
|
return new List<string>() { "00", "00" };
|
|
var dA2 = (pn - 1) / 8 + 1;//信息点组从1开始 第几组
|
|
var dA1 = pn - (dA2 - 1) * 8;//pn % 8
|
|
var dA1Hex = "1".PadRight(dA1, '0').BinToHex();//对位信息 第几位 二进制有效位
|
|
var dA2Hex = dA2.DecToHex();
|
|
return new List<string>() { dA1Hex.PadLeft(2, '0'), dA2Hex.PadLeft(2, '0') };
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// 数据单元标识
|
|
/// </summary>
|
|
/// <param name="fn"></param>
|
|
/// <returns></returns>
|
|
public List<string> GetDT(int fn)
|
|
{
|
|
var dT2 = (fn - 1) / 8;//从零开始 第几组
|
|
var dT1 = fn - dT2 * 8;
|
|
var dT1Hex = "1".PadRight(dT1, '0').BinToHex();//对位信息 第几位 二进制有效位
|
|
var dT2Hex = dT2.DecToHex();
|
|
return new List<string>() { dT1Hex.PadLeft(2, '0'), dT2Hex.PadLeft(2, '0') };
|
|
}
|
|
|
|
/// <summary>
|
|
/// 时间标签
|
|
/// </summary>
|
|
/// <param name="pFC">启动帧帧序号计数器PFC 1字节</param>
|
|
/// <param name="delayTime">允许发送传输延时时间 min 1字节</param>
|
|
/// <returns></returns>
|
|
public List<string> GetTp(string pFC = "00", int delayTime = 0)
|
|
{
|
|
|
|
var now = DateTime.Now; // 获取当前时间
|
|
var seconds = now.Second.ToString().PadLeft(2, '0'); // 获取当前秒数
|
|
var minutes = now.Minute.ToString().PadLeft(2, '0'); // 获取当前分钟数
|
|
var hours = now.Hour.ToString().PadLeft(2, '0'); // 获取当前小时数
|
|
var day = now.Day.ToString().PadLeft(2, '0'); // 获取当前日期的日数
|
|
return new List<string>() { pFC, seconds, minutes, hours, day, delayTime.ToString().PadLeft(2, '0') };
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
}
|