using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Models; namespace JiShe.CollectBus.Common.Extensions { public static class HexStringExtensions { //起始字符 private const string startStr = "68"; //结束字符 private const string endStr = "16"; //头部字节长度 private const int hearderLen = 6; //消息认证码字段长度 private const int pWLen = 16; private const int tPLen = 6; private const int FixedLength = 18; public static object GetAnalyzeValue(this List hexStringList, CommandChunkEnum chunk) { if (hexStringList.Count < hearderLen || hexStringList[0] != startStr || hexStringList[5] != startStr || hexStringList.Count < FixedLength) { return null; } switch (chunk) { case CommandChunkEnum.AFN: var aFn = hexStringList[(int)CommandChunkEnum.AFN].HexToDec();//1字节 return aFn; case CommandChunkEnum.FN: //(DT2*8)+DT1=fn var dt1Bin = hexStringList[(int)CommandChunkEnum.FN - 1].HexToBin(); var dt1 = dt1Bin != "0" ? dt1Bin.Length : 0; var dt2 = hexStringList[(int)CommandChunkEnum.FN].HexToDec(); var fn = dt2 * 8 + dt1; return fn; case CommandChunkEnum.A: var aHexList = hexStringList.Skip((int)CommandChunkEnum.A).Take(5).ToList(); var a1 = aHexList[1] + aHexList[0]; var a2 = aHexList[3] + aHexList[2]; var a2Dec = a2.HexToDec(); var a3 = aHexList[4]; var a = $"{a1}{a2Dec.ToString().PadLeft(5, '0')}"; var a3Bin = aHexList[4].HexToBin().PadLeft(8, '0'); var msa = a3Bin.Substring(0, 7).BinToDec(); return new Tuple(a, msa); case CommandChunkEnum.SEQ: var seq = hexStringList[(int)CommandChunkEnum.SEQ].HexToBin().PadLeft(8, '0'); var tpV = (TpV)Convert.ToInt32(seq.Substring(0, 1)); var firfin = (FIRFIN)Convert.ToInt32(seq.Substring(1, 2)); var con = (CON)Convert.ToInt32(seq.Substring(3, 1)); var prseqBin = seq.Substring(4, 4); return new Seq { CON = con, FIRFIN = firfin, PRSEQ = prseqBin.BinToDec(), TpV = tpV }; case CommandChunkEnum.Data: var lenIndex = (int)CommandChunkEnum.Len; var lenBin = (hexStringList[lenIndex + 1]+hexStringList[lenIndex]).HexToBin(); var len = lenBin.Remove(lenBin.Length - 2).BinToDec(); //验证长度 2=(帧校验和+结束字符) if (hexStringList.Count - 2 != hearderLen + len) return null; var dataHexList = hexStringList.Skip(FixedLength).Take(len + hearderLen - FixedLength).ToList(); return dataHexList; default: throw new ArgumentOutOfRangeException(nameof(chunk), chunk, null); } } public static bool IsStartStr(this string str) { return str == startStr ? true : false; } /// /// 字节加33 /// /// /// public static List AddHex33(this List hexStringList) { for (int i = 0; i < hexStringList.Count; i++) { hexStringList[i] = (Convert.ToInt32(hexStringList[i], 16) + Convert.ToInt32("33", 16)).ToString("X2"); if (hexStringList[i].Length > 2) { hexStringList[i] = hexStringList[i].Substring(hexStringList[i].Length - 2); } } return hexStringList; } #region 376.1下行命令 /// /// 构建电表参数设置-下发命令 /// /// /// /// public static byte[] BuildAmmeterParameterSetSendCmd(ReqParameter reqParameter, List meterParameters) { var dataUnit = BuildAmmeterParameterSendDataUnit(meterParameters); var bytes = BuildSendCommandBytes(reqParameter, dataUnit); return bytes; } /// /// 构建电表参数读取-下发命令 /// /// /// 对象序号 public static void BuildAmmeterParameterReadingSendCmd(ReqParameter reqParameter, List meterNumberList) { var dataUnit = new List(); var countHex = meterNumberList.Count().DecToHex().PadLeft(4, '0'); var countHexPairs = countHex.StringToPairs(); countHexPairs.Reverse(); dataUnit.AddRange(countHexPairs); foreach (var number in meterNumberList) { var numberHex = number.DecToHex().PadLeft(4, '0'); var numberHexPairs = numberHex.StringToPairs(); numberHexPairs.Reverse(); dataUnit.AddRange(numberHexPairs); } var bytes = BuildSendCommandBytes(reqParameter, dataUnit); } /// /// 构建透明转发-下发数据单元 /// /// 终端通信端口 1~31 /// 0~7 对应300,600,1200,2400,4800,7200,9600,19200 /// /// /// /// public static List BuildTransparentForwardingSendDataUnit(int port, BaudRate baudRate, StopBit stopBit, Parity parity, DataBit dataBit, int waitContentTimeout, int waitByteTimeout, List datas) { var dataUnit = new List(); var portHex = port.DecToHex().PadLeft(2, '0'); dataUnit.Add(portHex); var baudRateBin = ((int)baudRate).DecToBin().PadLeft(3, '0'); var stopBitBin = ((int)stopBit).DecToBin(); var parityBin = parity != Parity.None ? $"1{((int)parity).DecToBin()}" : $"0{((int)parity).DecToBin()}"; var dataBitBin = ((int)dataBit).DecToBin().PadLeft(2, '0'); var controlHex = $"{baudRateBin}{stopBitBin}{parityBin}{dataBitBin}".BinToHex().PadLeft(2, '0'); ; dataUnit.Add(controlHex); var waitContentTimeoutBin = $"1{waitContentTimeout.DecToBin().PadLeft(7, '0')}"; var waitContentTimeoutHex = waitContentTimeoutBin.BinToHex().PadLeft(2, '0'); var waitByteTimeoutHex = waitByteTimeout.DecToHex().PadLeft(2, '0'); dataUnit.Add(waitContentTimeoutHex); dataUnit.Add(waitByteTimeoutHex); var countHex = datas.Count.DecToHex().PadLeft(4, '0'); var countHexPairs = countHex.StringToPairs(); countHexPairs.Reverse(); dataUnit.AddRange(countHexPairs); dataUnit.AddRange(datas); return dataUnit; } /// /// 构建下发命令 /// /// /// /// public static byte[] BuildSendCommandBytes(ReqParameter reqParameter, List? dataUnit = null) { var cmdStrList = new List(); var userDatas = BuildUserData(reqParameter, dataUnit); var hearders = BuildHeaders(userDatas.Count); var cs = GetCS(userDatas); cmdStrList.AddRange(hearders); cmdStrList.AddRange(userDatas); cmdStrList.Add(cs); cmdStrList.Add(endStr); var bytes = cmdStrList.Select(x => Convert.ToByte(x, 16)).ToArray(); return bytes; } /// /// 构建电表参数设置-下发数据单元 /// /// /// private static List BuildAmmeterParameterSendDataUnit(List meterParameters) { var hexDatas = new List(); var countHex = meterParameters.Count().DecToHex().PadLeft(4, '0'); hexDatas.Add(countHex); //TODO 优化代码:目标数据入参,返回类型为出参 for (int i = 0; i <= meterParameters.Count - 1; i++) { var meter = meterParameters[i]; var indexHex = (i + 1).DecToHex().PadLeft(4, '0'); hexDatas.Add(indexHex); var pnHex = meter.Pn.DecToHex().PadLeft(4, '0'); hexDatas.Add(pnHex); var baudRateBin = meter.BaudRate.DecToBin().PadLeft(3, '0'); var portBin = meter.Port.DecToBin().PadLeft(5, '0'); var baudRateAndPortHex = $"{baudRateBin}{portBin}".BinToHex().PadLeft(2, '0'); hexDatas.Add(baudRateAndPortHex); var protocolTypeHex = ((int)meter.ProtocolType).DecToHex().PadLeft(2, '0'); hexDatas.Add(protocolTypeHex); hexDatas.Add(meter.Address); hexDatas.Add(meter.Password.PadLeft(12, '0')); var rateNumberBin = $"0000{meter.RateNumber.DecToBin().PadLeft(4, '0')}"; var rateNumberHex = rateNumberBin.BinToHex().PadLeft(2, '0'); hexDatas.Add(rateNumberHex); var intBitNumberBin = (meter.IntegerBitNumber - 4).DecToBin().PadLeft(2, '0'); var decBitNumberBin = (meter.DecimalBitNumber - 1).DecToBin().PadLeft(2, '0'); var intAndDecBitNumberBin = $"0000{intBitNumberBin}{decBitNumberBin}"; var intAndDecBitNumberHex = intAndDecBitNumberBin.BinToHex().PadLeft(2, '0'); hexDatas.Add(intAndDecBitNumberHex); hexDatas.Add(meter.CollectorAddress.PadLeft(12, '0')); var userCategoryNumberBin = meter.UserCategoryNumber.DecToBin().PadLeft(4, '0'); var userSubclassNumberBin = meter.UserSubclassNumber.DecToBin().PadLeft(4, '0'); var userNumberHex = $"{userCategoryNumberBin}{userSubclassNumberBin}".BinToHex().PadLeft(2, '0'); hexDatas.Add(userNumberHex); } //高位在前,低位在后 var datas = new List(); foreach (var hexData in hexDatas) { if (hexData.Length == 2) datas.Add(hexData); else { var lst = hexData.StringToPairs(); lst.Reverse(); datas.AddRange(lst); } } datas.AddRange(GetPW()); return datas; } //AUX=消息认证码字段(PW,16个字节) private static List GetPW() { var str = "00"; var pWList = Enumerable.Repeat(str, pWLen).ToList(); return pWList; } /// /// 帧校验和 /// /// 用户数据区 /// private static string GetCS(List userData) { byte sum = 0; foreach (var d in userData) { var b = Convert.ToByte(d, 16); sum += b; } return sum.ToString("X2"); } /// /// 用户数据区 /// /// /// private static List BuildUserData(ReqParameter reqParameter, List? dataUnit) { var c = BuildC(reqParameter.FunCode, reqParameter.PRM); var a = BuildAList(reqParameter.A, reqParameter.MSA); var linkUserData = BuildLinkUserData(reqParameter.AFN, reqParameter.Seq, ((ReqParameter2)reqParameter).Pn, ((ReqParameter2)reqParameter).Fn, dataUnit); var list = new List() { c }; list.AddRange(a); list.AddRange(linkUserData); return list; } /// /// 固定长度的报文头 起始字符+长度+长度+起始字符 /// /// /// private static List BuildHeaders(int length) { var headers = new List(); headers.Add(startStr); var l = BuildLength(length); headers.AddRange(l); headers.AddRange(l); headers.Add(startStr); return headers; } /// /// 长度 2字节 [用户数据区长度] /// /// private static List BuildLength(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; } /// /// 控制域 /// /// 功能码 /// /// /// private static string BuildC(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; } /// /// 地址域 3220 09872 /// /// 行政区划码 BCD码 3220=2032 /// 逻辑地址 BIN 09872=2690=>9026 /// 主站地址 BIN 0~127 /// private static List BuildAList(string a, int mSA) { var list = new List(); 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; } private static List BuildLinkUserData(AFN aFN, Seq seq, int pn, int fn, List? dataUnit) { var aFNValue = ((int)aFN).DecToHex().PadLeft(2, '0'); var sEQ = BuildSEQ(seq.TpV, seq.FIRFIN, seq.CON, seq.PRSEQ); var dA = BuildDA(pn); var dT = BuildDT(fn); var list = new List() { 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(BuildTp("00")); return list; } /// /// 帧序列域 /// /// /// /// /// private static string BuildSEQ(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; } /// /// 信息点标识 /// /// 计量点 /// private static List BuildDA(int pn) { if (pn == 0) return new List() { "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() { dA1Hex.PadLeft(2, '0'), dA2Hex.PadLeft(2, '0') }; } /// /// 数据单元标识 /// /// /// private static List BuildDT(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() { dT1Hex.PadLeft(2, '0'), dT2Hex.PadLeft(2, '0') }; } /// /// 时间标签 /// /// 启动帧帧序号计数器PFC 1字节 /// 允许发送传输延时时间 min 1字节 /// private static List BuildTp(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() { pFC, seconds, minutes, hours, day, delayTime.ToString().PadLeft(2, '0') }; } #endregion #region 645下行命令 /// /// 构建电表阀控下发数据单元 /// /// 电表地址 /// 特殊控制码 /// 密码 /// 是否为开阀 /// public static List BuildAmmeterValveControlSendDataUnit(string address, string specialnocode, string password, bool state, string modelCode = "") { var code = string.Empty; if (state) { if (string.IsNullOrEmpty(specialnocode)) code = "1B"; else code = specialnocode == "1B" || specialnocode == "1C" ? specialnocode : "1C"; } else code = "1A";//跳闸 if (specialnocode == "1W") { if (state) code = "1A"; else code = "1C"; } var pwdLevel = "02"; if (modelCode == "HL_DTSU2625" || modelCode == "DDZY9866") pwdLevel = "04"; else if (modelCode == "DDS2705") pwdLevel = "03"; //不带安全认证密级 if (!string.IsNullOrWhiteSpace(password) && password.Contains("|")) { var sp = password.Split('|'); pwdLevel = sp[1]; password = sp[0]; } var strDate = DateTime.Now.AddYears(3).ToString("000012ddMMyy").StrAddSpan();//命令有效截止时间 if (specialnocode == "1D" || modelCode == "SZBD_DDZY1225") strDate = "FF FF FF FF FF FF"; var strP = password.StrAddSpan().StrReverseOrder(); var strSJY = " " + pwdLevel + " " + strP + " 01 00 00 00 " + code + " 00 " + strDate; var dataUnit = strSJY.Replace(" ", "").StringToPairs(); var dataList = BuildSendCommand(address, "1C", dataUnit); return dataList; //string strLen = (strSJY.Replace(" ", "").Length / 2).ToString("X2"); //string strReturn = "68 " + address.StrAddSpan().StrReverseOrder() + " 68 1C " + strLen + " " + strSJY.StrAddHex33() + " "; //string strSum = strReturn.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries).Select(i => Convert.ToInt32(i, 16)).Sum().ToString("X"); //strReturn += strSum.Substring(strSum.Length - 2) + " 16"; //return strReturn.Split(' ').ToList(); } /// /// 构建电表保电下发数据单元 /// /// 电表地址 /// /// /// public static List BuildAmmeterLockSendDataUnit(string address, string password, bool state, string modelCode = "") { string code = string.Empty; if (state) code = "3A"; else code = "3B"; string strDate = (code + DateTime.Now.AddDays(1).ToString("00000012ddMMyy")).StrAddSpan(); if (modelCode == "SZBD_DDZY1225") strDate = $"{code} 00 FF FF FF FF FF FF"; string strP = password.StrAddSpan().StrReverseOrder(); string strSJY = " 02 " + strP + " 01 00 00 00 " + strDate; var dataUnit = strSJY.Replace(" ", "").StringToPairs(); var dataList = BuildSendCommand(address, "1C", dataUnit); return dataList; //string strLen = (strSJY.Replace(" ", "").Length / 2).ToString("X2"); //string strReturn = "68 " + address.StrAddSpan().StrReverseOrder() + " 68 1C " + strLen + " " + strSJY.StrAddHex33() + " "; //string strSum = strReturn.Split(new string[] { " " }, System.StringSplitOptions.RemoveEmptyEntries).Select(i => Convert.ToInt32(i, 16)).Sum().ToString("X"); //strReturn += strSum.Substring(strSum.Length - 2) + " 16"; //return strReturn.Split(' ').ToList(); } /// /// 构建645协议下发命令 /// /// 电表地址 /// 控制码 /// 数据域 发送方按字节进行加33处理,接收方按字节减33 /// public static List BuildSendCommand(string ammeterAddress, string controlCode, List? dataUnit) { var cmdStrList = new List(); cmdStrList.Add(startStr); ammeterAddress = ammeterAddress.PadLeft(12, '0'); var addressList = ammeterAddress.StringToPairs(); addressList.Reverse(); cmdStrList.AddRange(addressList); cmdStrList.Add(startStr); var len = dataUnit != null ? dataUnit.Count.DecToHex().PadLeft(2,'0') : "00"; cmdStrList.Add(len); if (dataUnit != null) { cmdStrList.AddRange(dataUnit.AddHex33()); } var strSum = cmdStrList.Select(i => Convert.ToInt32(i, 16)).Sum().ToString("X"); strSum = strSum.Substring(strSum.Length - 2); cmdStrList.Add(strSum); cmdStrList.Add(endStr); return cmdStrList; } /// /// 构建写数据 TODO:待优化 /// /// public static List AssembleWrite(string address, string password, string Di0_3, string[] data) { var address_str = address.StrAddSpan().StrReverseOrder(); var L = (12 + data.Length).ToString("X2"); var m = "00 11 22 33"; var Di0_3_str = Di0_3.StrAddSpan().StrReverseOrder(); var pwd_str = password.StrAddSpan().StrReverseOrder(); var data_str = string.Join(" ", data); var data_add33 = (Di0_3_str + " 02 " + pwd_str + " " + m + " " + data_str).StrAddHex33(); var frame = $"68 {address_str} 68 14 {L} {data_add33}"; string strSum = frame.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries).Select(i => Convert.ToInt32(i, 16)).Sum().ToString("X"); var cs = strSum.Substring(strSum.Length - 2); return $"{frame} {cs} 16".Split(' ').ToList(); } #endregion /// /// 标准 188协议阀控 WaterMeterSend /// /// /// /// public static List Confirm188WaterValve(string address, bool state, string mtype = "10") { if (string.IsNullOrWhiteSpace(address)) return null; var frm = $"68 {mtype} [00 00 00 00 00 00 00] 04 04 A0 17 00 {(state ? "55" : "99")} 21 16".Split(' ').ToList(); address = address.PadLeft(14, '0'); int n = 0; for (int i = 7; i > 0; i--) { frm[i + 1] = address.Substring(n, 2); n += 2; } frm[frm.Count - 2] = GetCRC(frm.Take(frm.Count - 2).ToList()); return frm; } public static string GetCRC(List inputFrm, int startIndex = 0) { int sum = 0; for (int i = startIndex; i < inputFrm.Count; i++) { sum += (Convert.ToInt32(inputFrm[i], 16)); } var sum16 = Convert.ToString(sum, 16); return sum16.Substring(sum16.Length - 2, 2).ToUpper(); } } }