HJ212协议C#代码解析实现

HJ212协议C#代码解析实现

HJ212协议是环保中一个非常重要的标准协议(字符串协议),之前写了两篇C++ HJ212协议解析的相关博文:

HJ212协议简介

由于是做环保相关的,有时需要对212协议进行拆包和解包。HJ212协议是一种字符串协议,数据传输通讯包主要由包头、数据段长度、数据段、CRC校验、包尾组成,其中“数据段”内容包括请求编码、系统编码、命令编码、密码、设备唯一标识、总包数、包号、指令参数。请求编码为请求的时间戳,系统编码ST统一规定为22,命令编码CN为该数据包的时间类型,访问密码、设备唯一标识在对接时由平台提供,指令参数为数据内容。通讯协议的数据结构如图4所示。
通讯协议的数据结构
图4 通讯协议的数据结构

6.1.1通讯包结构组成

名称 类型 长度 描述
包头 字符 2 固定为##
数据段长度 十进制整数 4 数据段的ASCII字符数。例如数据段的字符数为128,则写为“0128”
数据段 字符 0<=n<=9999 变长的数据
CRC校验 十六进制 4 数据段的校验结果,例如C901,如果CRC错,即执行超时
包尾 字符 2 回车换行(\r\n)

《污染物在线监控(监测)系统数据传输标准》简称《HJ212-2017》标准PDF文档可以从中华人民共和国生态环境部的官网下载,具体地址为:HJ212-2017》标准PDF文档
如下图所示:
HJ212-2017标准下载
目前HJ212标准协议已经发布了两个版本,一个是HJ/T 212-2005,另一个是 HJ 212-2017,最新的HJ 212-2017下载地址为:污染物在线监控(监测)系统数据传输标准(HJ 212-2017代替HJ/T 212-2005)

## 基于C#的HJ212解析类

首先创建一个基于C# .Net的库项目,名称为:HJ212ParseLibrary,相关类实现代码如下:

(1)、通用工具类 CommonUtils

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace HJ212ParseLibrary
{
    /// <summary>
    /// 通用工具类
    /// </summary>
    public class CommonUtils
    {
        /// <summary>
        /// 切分数据
        /// </summary>
        /// <param name="cp"></param>
        /// <returns></returns>
        public static List<Dictionary<string, string>>  SplitKV(string cp)
        {
            List<Dictionary<string, string>> resultList = new List<Dictionary<string, string>>();
            var arr1 = cp.Split(';');
            foreach (var i in arr1)
            {
                Dictionary<string, string> item = new Dictionary<string, string>();
                var arr2 = i.Split(',');
                foreach(var j in arr2)
                {
                    var arrKv = j.Split('=');
                    if (arrKv.Length == 2)
                    {
                        item.Add(arrKv[0], arrKv[1]);   
                    }
                }
                resultList.Add(item);
            }
            return resultList;
        }
        /// <summary>
        /// 组合数据
        /// </summary>
        /// <param name=""></param>
        /// <returns></returns>
        public static string JoinKV(List<Dictionary<string, string>> myList)
        {
            List<string> item = new List<string>();
            foreach (var i in myList)
            {
                List<string> arrKv = new List<string>();
                foreach (var j in i)
                {
                    arrKv.Add(j.Key + "=" + j.Value);
                }
                item.Add(Join(arrKv, ','));
            }
            return Join(item, ';');
        }
        /// <summary>
        /// 数据组合字符串
        /// </summary>
        /// <param name="arrKv">字符串列表</param>
        /// <param name="sep">分隔符</param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        private static string Join(List<string> arrKv, char sep = ' ')
        {
            string result = "";
            bool isFirst = true;
            foreach (var item in arrKv)
            {
                if (isFirst)
                {
                    result = item;
                    isFirst = false;
                    continue;
                }
                result += sep + item;
            }
            return result;
        }
        /// <summary>
        /// 将字符串数组按照sep分隔符分割之后,生成新的字符串
        /// </summary>
        /// <param name="arrKv">字符串数组</param>
        /// <param name="sep">分隔符</param>
        /// <returns></returns>
        private static string Join(string[] arrKv, char sep = ' ')
        {
            string result = "";
            bool isFirst = true;
            foreach (var item in arrKv)
            {
                if (isFirst)
                {
                    result = item;
                    isFirst = false;
                    continue;
                }
                result += sep + item;
            }
            return result;
        }
        /// <summary>
        /// 计算计算字节数组的CRC-16校验码
        /// </summary>
        /// <param name="byteArr">字节数组</param>
        /// <returns>CRC-16校验码</returns>
        public static uint GetCrc16(byte[] byteArr)
        {
            uint crc_reg, check;
            int i, j;
            crc_reg = 0xFFFF;
            for (i = 0; i < byteArr.Length; i++)
            {
                crc_reg = (crc_reg >> 8) ^ byteArr[i];
                for (j = 0; j < 8; j++)
                {
                    check = crc_reg & 0x0001;
                    crc_reg >>= 1;
                    if (check == 0x0001)
                    {
                        crc_reg ^= 0xA001;
                    }
                }
            }
            return crc_reg;
        }
        /// <summary>
        /// 将对应位置置1,n从1开始
        /// </summary>
        /// <param name="x"></param>
        /// <param name="n"></param>
        public static void SetBit(int x, int n)
        {
            x |= 1 << (n - 1);
        }
        /// <summary>
        /// 将对应位置置0,n从1开始
        /// </summary>
        /// <param name="x"></param>
        /// <param name="n"></param>
        public static void CtrlBit(int x, int n)
        {
            x &= ~(1 << (n - 1));
        }
        public static int GetBit(int x, int n)
        {
            return x & (1 << (n - 1));
        }
    }
}

(2)、HJ212协议 CP数据部分 HJ212DataCP

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace HJ212ParseLibrary
{
    /// <summary>
    /// HJ212协议 CP数据部分
    /// </summary>
    public class HJ212DataCP
    {
        public HJ212DataCP()
        {

        }
        public HJ212DataCP(string s)
        {
            List<Dictionary<string, string>> arr = CommonUtils.SplitKV(s);
            foreach (var i in arr)
            {
                int kvlen = i.Count;
                string key = "";
                foreach (var j in i)
                {
                    // 对指定监测因子的项,统一使用因子代表
                    if (j.Key == "PolId")
                    {
                        key = j.Value;
                        this.KVDict[key] = new Dictionary<string, string>();
                        continue;
                    }
                    string name, field;
                    // 查询是否包含标准协议中的设备状态
                    var f1 = j.Key.IndexOf("SB");
                    var f2 = j.Key.IndexOf("-");
                    if (f2 != -1 && f1 != 0)
                    {
                        // a20004-Rtd   name-field
                        name = j.Key.Substring(0, f2);
                        field = j.Key.Substring(f2 + 1);
                        // i11001-Info   field
                        if (field == "Info")
                        {
                            field = name;
                        }
                        else
                        {
                            key = name;
                        }
                    }
                    else
                    {
                        if (j.Key == "DataTime")
                        {
                            this.DataTime = j.Value;
                        }
                        name = j.Key;
                        key = name;
                        field = "value";
                    }
                    if (this.KVDict.ContainsKey(key))
                    {
                        // 如果存在key,则追加
                        this.KVDict[key].Add(field, j.Value);
                    } else
                    {
                        // 如果不存在该key,则创建一个字典对象,并添加到KVDict中
                        Dictionary<string, string> dict = new Dictionary<string, string>();
                        dict.Add(field, j.Value);
                        this.KVDict.Add(key, dict);
                    }
                }
            }
        }
        public void Clear()
        {
            this.KVDict.Clear();
        }
        public Dictionary<string, Dictionary<string, string>> KVDict = new Dictionary<string, Dictionary<string, string>>();   // 监测因子数据字典
        string DataTime;
    }
}

(3)、HJ212协议 数据区 HJ212Data

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace HJ212ParseLibrary
{
    /// <summary>
    /// HJ212协议 数据区
    /// 请求编码QN + 系统编码ST + 命令编码CN + 密码PW + 设备唯一标识MN + 标志位Flag + 总包数PNUM + 包号PNO + 指令参数CP
    /// 其中,指令CP字符数范围为:[0,9899],CP=&&数据区&&
    /// </summary>
    public class HJ212Data
    {
        public string QN;		// 请求编码 20字符	QN=YYYYMMDDHHmmssZZZ
        public string ST;		// 系统编码 5字符		ST=21
        public string CN;		// 命令编码 7字符		CN=2011
        public string PW;		// 访问密码 9字符		PW=123456
        public string MN;		// 设备标识 27字符	MN=[0-9A-F]
        public string Flag;	    // 标志位   8整数		Flag=7	bit:000001(协议版本)0(是否有包号)0(是否需应答)
        public string PNUM;	    // 总包数   9字符		PNUM=0000	[不分包则没有本字段]
        public string PNO;		// 包号     8字符	PNO=0000	[不分包则没有本字段]
        public string CP;		// 指令参数	<=9899字符	CP=&&数据区&&
        public HJ212DataCP CPs;
        private bool bValid = false;
        // 设置Flag,bit从1开始
        public void SetFlag(int bit, bool enable)
        {
            int flag = 4;
            flag = Int32.Parse(Flag);
            if (enable)
            {
                CommonUtils.SetBit(flag, bit);
            } else
            {
                CommonUtils.CtrlBit(flag, bit);
            }
            this.Flag = flag.ToString();
        }

        // 获取Flag,bit从1开始
        public int GetFlag(int bit)
        {
            int flag = 4;
            if (Int32.TryParse(Flag, out flag))
            {
                return CommonUtils.GetBit(flag, bit);
            }
            return CommonUtils.GetBit(flag, bit);
        }

        // 有效性
        public bool IsValid()
        {
            return bValid;
        }

        // 长度
        public int Size()
        {
            return QN.Length + ST.Length + CN.Length + PW.Length + MN.Length +
                Flag.Length + PNUM.Length + PNO.Length + CP.Length;
        }
        // 构造函数
        public HJ212Data()
        {

        }
        public HJ212Data(string s)
        {
            CopyStr(s);
        }
        public void CopyStr(string s)
        {
            // 查找数据段
            int d1 = s.IndexOf("CP=&&");        // 数据段开始位置
            int d2 = s.IndexOf("&&", d1 + 5);   // 数据段结束位置
            String tmpStr = "";
            if (d1 != -1 && d2 != -1)
            {
                CP = s.Substring(d1 + 5, d2 - d1 - 5);
                CPs = new HJ212DataCP(CP);
                tmpStr = s.Substring(0, d1) + s.Substring(d2 + 2);
            }
            else
            {
                tmpStr = s;
            }
            Dictionary<string, string> strDict = new Dictionary<string, string>();
            var arr1 = tmpStr.Split(';');
            foreach (var i in arr1)
            {
                var arr2 = i.Split('=');
                if (arr2.Length == 2)
                {
                    strDict.Add(arr2[0], arr2[1]);
                }
            }
            if (strDict.ContainsKey("QN"))
            {
                QN = strDict["QN"];
            }
            if (strDict.ContainsKey("ST"))
            {
                ST = strDict["ST"];
            }
            if (strDict.ContainsKey("CN"))
            {
                CN = strDict["CN"];
            }
            if (strDict.ContainsKey("PW"))
            {
                PW = strDict["PW"];
            }
            if (strDict.ContainsKey("MN"))
            {
                MN = strDict["MN"];
            }
            if (strDict.ContainsKey("Flag"))
            {
                Flag = strDict["Flag"];
            }
            if (strDict.ContainsKey("PNUM"))
            {
                PNUM = strDict["PNUM"];
            }
            if (strDict.ContainsKey("PNO"))
            {
                PNO = strDict["PNO"];
            }
            bValid = true;
        }
        // 获取数据项
        public List<Dictionary<string, string>> Cp2Object()
        {
            return CommonUtils.SplitKV(CP);
        }
        // 字符串输出
        public override string ToString()
        {
            string str = "";
            if (!string.IsNullOrEmpty(QN))
            {
                str += ("QN=" + QN + ";");
            }
            if (!string.IsNullOrEmpty(ST))
            {
                str += ("ST=" + ST + ";");
            }
            if (!string.IsNullOrEmpty(CN))
            {
                str += ("CN=" + CN + ";");
            }
            if (!string.IsNullOrEmpty(PW))
            {
                str += ("PW=" + PW + ";");
            }
            if (!string.IsNullOrEmpty(MN))
            {
                str += ("MN=" + MN + ";");
            }
            if (!string.IsNullOrEmpty(Flag))
            {
                str += ("Flag=" + Flag + ";");
            }
            if (!string.IsNullOrEmpty(PNUM))
            {
                str += ("PNUM=" + PNUM + ";");
            }
            if (!string.IsNullOrEmpty(PNO))
            {
                str += ("PNO=" + PNO + ";");
            }
            str += ("CP=&&" + CP + "&&");
            return str;
        }
    }
}

(4)、HJ212协议实体类 HJ212

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Threading.Tasks;



namespace HJ212ParseLibrary
{
    /// <summary>
    /// HJ212协议 整包=包头+数据段长度+数据段+CRC校验+包尾组成
    /// ##0637ST=22;CN=2011;PW=123456;MN=LB4200001;CP=&&DataTime=20210107143500;a05002-Rtd=0,a05002-Flag=N;a21005-Rtd=0.825,a21005-Flag=N;a34006-Rtd=874,a34006-Flag=N;a24088-Rtd=0,a24088-Flag=N;a21003-Rtd=5.656,a21003-Flag=N;a21004-Rtd=9.253,a21004-Flag=N;a21002-Rtd=17.894,a21002-Flag=N;a05024-Rtd=5.803,a05024-Flag=N;a34007-Rtd=2679,a34007-Flag=N;a34002-Rtd=123.97,a34002-Flag=N;a34004-Rtd=24.82,a34004-Flag=N;a01006-Rtd=1032.3,a01006-Flag=N;a01002-Rtd=20.9,a01002-Flag=N;a21026-Rtd=0.795,a21026-Flag=N;a01007-Rtd=3.3,a01007-Flag=N;a34049-Rtd=3553,a34049-Flag=N;a01001-Rtd=-0.6,a01001-Flag=N;a99999-Rtd=0,a99999-Flag=N;a01008-Rtd=349,a01008-Flag=N&&3300
    /// </summary>
    public class HJ212
    {
        public string header = "##";        // 包头 2字符
        public string dataLen = "0000";     // 数据段长度    4整数,如长度128,写为"0128"
        HJ212Data data;                     // 数据段  0<=n<=9999
        public string crc = "0000";         // CRC校验码  4hex
        public string tailer = "\r\n";      // 包尾 2字符
        public string full;                 // 全数据,整包
        /// 输出
        public override string ToString()
        {
            string dataStr = data.ToString();
            uint crc16 = CommonUtils.GetCrc16(Encoding.UTF8.GetBytes(dataStr));
            return string.Format("{0}{1:D4}{2}{3:X4}{4}", header, dataStr.Length, dataStr, crc16, tailer);  // D4标示4位10进制数字,X4表示4位16进制数
        }
        // 有效性
        public bool IsValid()
        {
            return bValid;
        }
        // 有效性
        public bool IsDataValid()
        {
            return this.data.CN == "2011" || this.data.CN == "2051" || this.data.CN == "2061"
            || this.data.CN == "2031" || this.data.CN == "2041" || this.data.CN == "3020";
        }
        // 长度
        public int Size()
        {
            return header.Length + dataLen.Length + data.Size() + crc.Length + tailer.Length;
        }
        // 清空数据区
        public void ClearCp()
        {
            this.data.CP = "";
            this.data.CPs.Clear();
        }
        // 设置是否需要应答
        public void SetNeedReply(bool need)
        {
            data.SetFlag(1, need);
        }
        // 设置是否有包号
        public void SetNeedSubPack(bool need)
        {
            data.SetFlag(2, need);
            if (!need)
            {
                data.PNUM = "";
                data.PNO = "";
            }
        }
        // 是否需应答
        public int IsNeedReply()
        {
            return data.GetFlag(1);
        }
        // 是否有包号
        public int IsNeedSubPack()
        {
            return data.GetFlag(2);
        }
        // 数据区。字段与其值用‘=’连接;
        // 在数据区中,同一项目的不同分类值间用‘,’来分隔,不同项目之间 用‘;’来分隔。
        public void Combine()
        {
            this.dataLen = string.Format("{0:D4", this.data.Size());
            string dataStr = this.data.ToString();
            uint jisuanCrc = CommonUtils.GetCrc16(Encoding.UTF8.GetBytes(dataStr));
            this.crc = string.Format("{0:D4", jisuanCrc);
            this.bValid = true;
        }
    public HJ212(string str)
    {
        this.full = str;
        int totalSize = str.Length;
        dataLen = str.Substring(header.Length, dataLen.Length);
        int dLen = Int32.Parse(dataLen);
        if ((10 + dLen) > totalSize) {
            return;
        }
        string dataStr = str.Substring(header.Length + dataLen.Length, dLen);
        data = new HJ212Data(dataStr);
        crc = str.Substring(header.Length + dataLen.Length + dLen, crc.Length);
        //uint jisuanCrc16 = CommonUtils.GetCrc16(dataStr.ToCharArray());
        uint jisuanCrc16 = CommonUtils.GetCrc16(Encoding.UTF8.GetBytes(dataStr));
        int srcCrc16 = Int32.Parse(crc, System.Globalization.NumberStyles.HexNumber);
        if (srcCrc16 != jisuanCrc16) {
            return;
        }
        bValid = true;
    }

    public HJ212(HJ212 r)
        {
            this.header = r.header;
            this.dataLen = r.dataLen;
            //this.data = new HJ212Data(r.data.ToString());
            this.data = r.data;
            this.crc = r.crc;
            this.tailer = r.tailer;
            this.full = r.full;
            this.bValid = r.bValid;
        }
        public HJ212(string st, string cn, string mn, string pw, string cp, bool needReply)
        {
            this.data.QN = DateTime.Now.ToString("yyyyMMDDHHMMss000");
            this.data.ST = st;
            this.data.CN = cn;
            this.data.PW = pw;
            this.data.CP = cp;
            this.SetNeedReply(needReply);
            this.Combine();
        }
        // 获取响应报文
        public HJ212 GetDataResponse(string cn)
        {
            HJ212 outHJ212 = new HJ212(this);
            outHJ212.data.ST = "91";
            outHJ212.data.CN = cn;
            outHJ212.ClearCp();
            outHJ212.Combine();
            return outHJ212;
        }
        /// <summary>
        /// 解析字符串,获取HJ212协议数据列表
        /// </summary>
        /// <param name="buffer">输入字符串</param>
        /// <returns>返回解析后的HJ212协议列表</returns>
        public static List<HJ212> Parse(string buffer)
        {
            List<HJ212> hj212List = new List<HJ212>();
            int bufferSize = buffer.Length;
            string strTemp = "";
            for (int i = 0; i < bufferSize; i++)
            {
                if ( i < bufferSize - 1 && buffer[i] == '#' && buffer[i + 1] == '#')
                {
                    if (strTemp.Length > 0)
                    {
                        hj212List.Add(new HJ212(strTemp));
                        strTemp = "";
                    }
                }
                strTemp += buffer[i];
                // 遍历到HJ212协议末尾,末尾以\r\n结束
                if (i > 0 && buffer[i - 1] == '\r' && buffer[i] == '\n')
                {
                    if (strTemp.Length > 0)
                    {
                        hj212List.Add(new HJ212(strTemp));
                        strTemp = "";
                    }
                }
            }

            if (strTemp.Length > 0)
            {
                hj212List.Add(new HJ212(strTemp));
                strTemp = "";
            }
            return hj212List;
        }
        private bool bValid = false;
    }
}

测试程序

新建一个基于C# .Net的控制台程序ConsoleHJ212App,然后输入如下测试代码:

using HJ212ParseLibrary;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleHJ212App
{
    public class Program
    {
        static void Main(string[] args)
        {
            string buffer = "##0637ST=22;CN=2011;PW=123456;MN=LB4200001;CP=&&DataTime=20210107143500;a05002-Rtd=0,a05002-Flag=N;a21005-Rtd=0.825,a21005-Flag=N;a34006-Rtd=874,a34006-Flag=N;a24088-Rtd=0,a24088-Flag=N;a21003-Rtd=5.656,a21003-Flag=N;a21004-Rtd=9.253,a21004-Flag=N;a21002-Rtd=17.894,a21002-Flag=N;a05024-Rtd=5.803,a05024-Flag=N;a34007-Rtd=2679,a34007-Flag=N;a34002-Rtd=123.97,a34002-Flag=N;a34004-Rtd=24.82,a34004-Flag=N;a01006-Rtd=1032.3,a01006-Flag=N;a01002-Rtd=20.9,a01002-Flag=N;a21026-Rtd=0.795,a21026-Flag=N;a01007-Rtd=3.3,a01007-Flag=N;a34049-Rtd=3553,a34049-Flag=N;a01001-Rtd=-0.6,a01001-Flag=N;a99999-Rtd=0,a99999-Flag=N;a01008-Rtd=349,a01008-Flag=N&&3300\r\n##0404ST=22;CN=2011;PW=123456;MN=LB4200001;CP=&&DataTime=20210107152600;a05002-Rtd=1.755,a05002-Flag=N;a21005-Rtd=0.699,a21005-Flag=N;a34006-Rtd=845,a34006-Flag=N;a24088-Rtd=4.945,a24088-Flag=N;a21003-Rtd=3.877,a21003-Flag=N;a21002-Rtd=14.502,a21002-Flag=N;a05024-Rtd=10.389,a05024-Flag=N;a34007-Rtd=2859,a34007-Flag=N;a21026-Rtd=1.525,a21026-Flag=N;a34049-Rtd=3704,a34049-Flag=N;a99999-Rtd=6.7,a99999-Flag=N&&9C01\r\n";
            List<HJ212> hj212List = HJ212.Parse(buffer);
            foreach (HJ212 item in hj212List)
            {
                Console.WriteLine(item.ToString());
            }
        }
    }
}

并引用HJ212ParseLibrary库项目,在VS2022中运行结果如下图所示:
调试运行结果1

运行结果如下:
解析HJ212协议测试实例
可以看到,我们输入的数据和解析到的数据是一致的,有2个协议数据报文。

相关推荐

  1. Ubuntu 22, CURL 分块上传文件C++代码实现

    2024-03-17 14:26:05       29 阅读
  2. 微软面试高频算法题解析代码实现C++)

    2024-03-17 14:26:05       35 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-17 14:26:05       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-17 14:26:05       106 阅读
  3. 在Django里面运行非项目文件

    2024-03-17 14:26:05       87 阅读
  4. Python语言-面向对象

    2024-03-17 14:26:05       96 阅读

热门阅读

  1. 蓝桥杯构造法|两道例题(C++)

    2024-03-17 14:26:05       42 阅读
  2. 《工厂模式(极简c++)》

    2024-03-17 14:26:05       42 阅读
  3. SpringMVC启动与请求处理流程解析

    2024-03-17 14:26:05       44 阅读
  4. 使用Go Validator在Go应用中有效验证数据

    2024-03-17 14:26:05       48 阅读
  5. PyTorch学习笔记之基础函数篇(十四)

    2024-03-17 14:26:05       41 阅读
  6. leetcode电话号码的字母组合

    2024-03-17 14:26:05       48 阅读
  7. C语言——母牛的故事

    2024-03-17 14:26:05       41 阅读
  8. Linux中的音频开发

    2024-03-17 14:26:05       45 阅读
  9. L1-080 乘法口诀数列(PTA)

    2024-03-17 14:26:05       43 阅读