HJ212环境质量检测开发

1.HJ212协议介绍

1.1.概述

HJ212 协议是应用于环境监测领域的一种数据传输标准协议。定义了监测设备与数据采集服务器之间的通信流程,包括数据传输、控制指令传输等环节,确保通信的可靠性和稳定性。广泛应用于大气和废气环境监测、水和废水监测、固体废弃物监测、土壤监测、生物污染监测、环境噪声监测、环境放射性监测等多个环境监测领域。例如,在污水处理厂、垃圾填埋场、工厂废气排放口等场所的监测设备,都需要按照 HJ212 协议将监测数据传输到环保部门的监控平台。

1.2.通讯协议数据结构

通讯包结构组成如下:

数据段结构组成如下:

 更多详情信息请参考HJ212的协议文档。

2.数据包解析示例

系统收到设备上报的报文,打印出16进制如下:

232330333133514e3d32303234313131353139303430383030303b53543d32373b434e3d323031313b50573d3132333435363b4d4e3d415149323431313133323630303333323b466c61673d353b43503d26264461746154696d653d32303234313131353139303430383b6132313032362d5274643d3131373b6132313030342d5274643d31363b6f33313130342d5274643d32393b6132313030352d5274643d302e31343b6133343030342d5274643d363b6133343030322d5274643d363b6133343030312d5274643d363b6130313030312d5274643d32382e313b6130313030322d5274643d36383b4c412d5274643d34393b6130313030362d5274643d313030323b6130363030312d5274643d302e303b6130343030332d5274643d303b6130313030372d5274643d302e303b6130313030382d5274643d37302626393930310d0a 

转出字符串如下:

对字符串进行解析说明如下:

## --包头固定为##
0127 --数据段长度;数据段的 ASCII 字符数,例如:长 255,则写为“0255”

QN=20210320163058511; --请求编码 QN;精确到毫秒的时间戳:QN=YYYYMMDDhhmmsszzz,用来唯一标识一次命令交互
ST=32; --ST=系统编码, 系统编码取值详见 6.6.1 章节的表 5《系统编码表》
CN=2081; --CN=命令编码, 命令编码取值详见 6.6.5 章节的表 9《命令编码表》
PW=123456; --PW=访问密码
MN=81733553213013; --MN=设备唯一标识,这个标识固化在设备中,用于唯一标识一个设备。MN 由 EPC-96 编码转化的字符串组成,即 MN 由 24 个 0~9,A~F 的字符组成(标头(8 bit)厂商识别代码(28 bit)对象分类代码(24 bit)序列号(36 bit))
Flag=4; --标志位,这个标志位包含标准版本号、是否拆分包、数据是否应答。V5~V0:标准版本号;Bit:000000 表示标准 HJ/T 212-2005,000001 表示本次标准修订版本号。 A:命令是否应答;Bit:1-应答,0-不应答。 
D:是否有数据包序号;Bit:1-数据包中包含包号和总包数两部分,0-数据包中不包含包号和总包数两部分。 示例:Flag=7 表示标准版本为本次修订版本号,数据段需要拆分并且命令需要应答
PNUM--PNUM 指示本次通讯中总共包含的包数注:不分包时可以没有本字段,与标志位有关
PNO --PNO指示当前数据包的包号注:不分包时可以没有本字段,与标志位有关
CP=&& --CP=&&数据区&&,数据区定义见 6.3.3 章节
	DataTime=20210320163058;RestartTime=20210320000006
&&

A781 --CRC校验
     --这里还有一个回车换行符

3.核心代码

3.1.解析消息主体

   /**
     * 解析消息体
     * @param ctx
     * @param msgBuf
     * @param out
     * @throws Exception
     */
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf msgBuf, List<Object> out) throws Exception {
        HJ212Message message=new HJ212Message();
        //消息头
        msgBuf.readUnsignedShort();
        int msgBodyLen=0;
        //消息体长度
        byte[] msgLenArr = new byte[4];
        msgBuf.readBytes(msgLenArr);
        msgBodyLen=Integer.parseInt(new String(msgLenArr));
        //通过分割符";"分割消息
        Map<String,String> attrMap=new HashMap<>();
        while (msgBuf.readableBytes() > 6) {
            int index = msgBuf.bytesBefore(HJ212Constant.MSG_SPLITER);
            int itemLen = index >= 0 ? index : msgBuf.readableBytes() - 6;
            if (itemLen > 0) {
                msgBuf.markReaderIndex();
                byte[] byteArr = new byte[itemLen];
                msgBuf.readBytes(byteArr);
                String attrContent = new String(byteArr);
                String[] attrArr = attrContent.split("=");
                if (attrArr.length > 1) {
                    if (attrArr[0].toUpperCase().equals(HJ212Constant.DATA_ATTR)) {
                        msgBuf.resetReaderIndex();
                        byte[] dataContentArr = new byte[msgBuf.readableBytes()-6];
                        msgBuf.readBytes(dataContentArr);
                        String dataContent = CommonUtils.stringExtractWithRegex(new String(dataContentArr), "&&(.*?)&&");
                        //剩余的消息体长度大于“CP=&&dataContent&&”;这个7指的就是CP=&&&&部分的长度
                        if(dataContentArr.length>dataContent.length()+7){
                            //跳过CP消息内容,剩余的进入下个循环
                            msgBuf.resetReaderIndex();
                            msgBuf.skipBytes(dataContent.length()+7);
                        }
                        attrMap.put(HJ212Constant.DATA_ATTR,dataContent);
                    }else{
                        attrMap.put(attrArr[0].toUpperCase(),attrArr[1]);
                        msgBuf.readByte();
                    }
                }
            }
        }
        msgBuf.readInt();
        msgBuf.readShort();
        if(attrMap.containsKey("QN")){
            message.setQn(attrMap.get("QN"));
        }
        if(attrMap.containsKey("ST")){
            message.setSysId(attrMap.get("ST"));
        }
        if(attrMap.containsKey("CN")){
            message.setMsgId(attrMap.get("CN"));
        }
        if(attrMap.containsKey("PW")){
            message.setPassword(attrMap.get("PW"));
        }
        if(attrMap.containsKey("MN")){
            String mn=attrMap.get("MN");
            message.setTerminalNum(mn);
            message.setTerminalNumArr(mn.getBytes());
        }
        if(attrMap.containsKey("FLAG")){
            message.setFlag(Integer.parseInt(attrMap.get("FLAG")));
        }
        if(attrMap.containsKey("PNUM")){
            message.setPNum(Integer.parseInt(attrMap.get("PNUM")));
        }
        if(attrMap.containsKey("PNO")){
            message.setPNo(Integer.parseInt(attrMap.get("PNO")));
        }
        if(attrMap.containsKey("CP")){
            String bodyContent =attrMap.get("CP");
            message.setBodyContent(bodyContent);
            message.setMsgBodyArr(bodyContent.getBytes());
            message.setMsgBody(Unpooled.wrappedBuffer(bodyContent.getBytes()));
        }
        out.add(message);
    }

3.2.解析2011消息负载

    /**
     * 解析实时采样数据
     * @param terminalProto
     * @param msg
     * @param contentArr
     * @return
     */
    public static Hj212SampleDataProto parseRealTimeSampleData(TerminalProto terminalProto, HJ212Message msg, String [] contentArr){
        Hj212SampleDataProto sampleDataProto=new Hj212SampleDataProto();
        sampleDataProto.setTerminalId(terminalProto.getTerminalId());
        sampleDataProto.setTerminalNum(terminalProto.getTerminalNum());
        //当前时间
        ZonedDateTime currentDateTime = ZonedDateTime.now(ZoneOffset.UTC);
        sampleDataProto.setRecvTime(currentDateTime.toString());
        sampleDataProto.setRecvTimestamp(currentDateTime.toInstant().toEpochMilli());
        sampleDataProto.setMessageId(msg.getMsgId());
        //解析属性值
        Map<String, SampleData> dataMap=new HashMap<>();
        for(String strAttr:contentArr){
            SampleData sampleData=new SampleData();
            String[] attrArr =strAttr.split(",");
            for(String attrItem:attrArr){
                String[] fieldArr =attrItem.split("=");
                if(fieldArr.length>1) {
                    if (fieldArr[0].toLowerCase().equals("datatime")) {
                        ZonedDateTime zonedDateTime = CommonUtils.parseBcdTime(fieldArr[1]);
                        sampleDataProto.setDateTime(zonedDateTime.toString());
                        sampleDataProto.setDateTimestamp(zonedDateTime.toInstant().toEpochMilli());
                    } else {
                        if (fieldArr[0].toLowerCase().endsWith("-rtd")) {
                            sampleData.setFactor(fieldArr[0].split("-")[0]);
                            sampleData.setRtd(fieldArr[1]);
                        }else if (fieldArr[0].toLowerCase().endsWith("-sampletime")) {
                            sampleData.setFactor(fieldArr[0].split("-")[0]);
                            ZonedDateTime zonedDateTime = CommonUtils.parseBcdTime(fieldArr[1]);
                            sampleData.setTime(zonedDateTime.toString());
                            sampleData.setTimestamp(zonedDateTime.toInstant().toEpochMilli());
                        }else if (fieldArr[0].toLowerCase().endsWith("-flag")) {
                            sampleData.setFactor(fieldArr[0].split("-")[0]);
                            sampleData.setFlag(fieldArr[1]);
                        }
                    }
                }
            }
            if(StringUtils.isNotBlank(sampleData.getFactor())){
                dataMap.put(sampleData.getFactor(),sampleData);
            }
        }
        if(dataMap.size()>0){
            sampleDataProto.setRealTime(1);
        }else{
            sampleDataProto.setRealTime(0);
        }
        sampleDataProto.setDataMapJson(JSON.toJSONString(dataMap));
        return sampleDataProto;
    }

3.2.CRC循环冗余计算

    /**
     * CRC循环冗余计算
     * @param data
     * @return
     */
    public static int calculateCRC16(byte[] data){
        int crc_reg = 0xFFFF;
        int check;
        for (byte b : data) {
            crc_reg = (crc_reg>>8)^b;
            for (int i = 0; i < 8; i++) {
                check = crc_reg & 0x0001;
                crc_reg >>= 1;
                if(check==0x0001){
                    crc_reg ^= 0xA001;
                }
            }
        }
        return crc_reg&0xFFFF;
    }

4.实现效果

4.1.设备全览

4.2. 单个设备实时详情

4.3. 历史采样分析

4.4.设置报警规则,产生实时检测报警

如果有项目需求,或者源码需求的可以加我微信沟通.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大鱼>

一分也是爱

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值