前言

有一个传感器是通过水文协议进行上报的,估按照协议对报文进行解析,报文分类很多种,我这种是 定时报文 ,所以当前文章是对定时报文的解析,如果需要其他报文则不用观看本文章了…

需要了解水文协议报文

文件在目录下方下载
在这里插入图片描述

解析工具类

解析用的到一些自写工具类

数组工具类

用于将一串字符串进行切割

public class ArrayUtils {

    public static List<String> split(String data) {
        List<String> result = new ArrayList<>();
        for (int i = 0; i < data.length(); i += 2) {
            result.add(data.substring(i, i + 2));
        }
        return result;
    }


}

byte工具类

将字符串转换成byte,其中用到 netty 的Buffer

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-buffer</artifactId>
            <version>4.1.67.Final</version>
        </dependency>

工具类

public class ByteUtils {


    public static ByteBuf hexString2ByteBuf(String src) {
        return Unpooled.buffer().writeBytes(hexString2Bytes(src));
    }


    public static byte[] hexString2Bytes(String src) {
        if (null == src || src.isEmpty()) {
            return null;
        }
        byte[] ret = new byte[src.length() / 2];
        byte[] tmp = src.getBytes();
        for (int i = 0; i < (tmp.length / 2); i++) {
            ret[i] = uniteBytes(tmp[i * 2], tmp[i * 2 + 1]);
        }
        return ret;
    }

    public static byte uniteBytes(byte src0, byte src1) {
        byte _b0 = Byte.decode("0x" + new String(new byte[]{src0}));
        _b0 = (byte) (_b0 << 4);
        byte _b1 = Byte.decode("0x" + new String(new byte[]{src1}));
        return (byte) (_b0 ^ _b1);
    }

}

处理数据长度

用于处理数据长度和小数位

public class DataLengthParser {

    /**
     * 解析一个字节,返回数据长度和小数位信息
     *
     * @param b 输入字节(如 (byte) 0x19)
     * @return LengthInfo 解析结果
     */
    public static LengthInfo parse(byte b) {
        // 转为无符号整数(byte 是有符号的,范围 -128~127,需要转成 0~255)
        int unsignedByte = b & 0xFF;

        // 高5位:(unsignedByte >> 3) & 0x1F
        // 低3位:unsignedByte & 0x07
        int length = (unsignedByte >> 3) & 0x1F;  // 取高5位
        int decimals = unsignedByte & 0x07;       // 取低3位

        return new LengthInfo(length, decimals);
    }

    /**
     * 解析单个字节,提取高5位和低3位
     */
    @Data
    public static class LengthInfo {
        private final int dataLength; // 数据部分占用字节数(高5位)
        private final int decimalDigits; // 小数位数(低3位)

        public LengthInfo(int dataLength, int decimalDigits) {
            this.dataLength = dataLength;
            this.decimalDigits = decimalDigits;
        }

        @Override
        public String toString() {
            return String.format("数据长度: %d 字节, 小数位数: %d 位", dataLength, decimalDigits);
        }
    }
}

解析类

    @Data
    public static class TestBody {

        /**
         * 起始位
         */
        private String qsw;
        /**
         * 中心站 范围为1~255
         */
        private String zxz;
        /**
         * 遥测站地址
         */
        private String ycdz;
        /**
         * 密码
         */
        private String mm;
        /**
         * 功能码
         */
        private String gnm;
        /**
         * 数据长度
         */
        private Integer sjcd;
        /**
         * 数据起始符
         * 02 : 一个帧包含了当前数据..
         * 16 : 相当于是进行了分包把
         */
        private String sjqsf;

        private YCZDSBSXBody yczdsbsxBody;


        public TestBody(List<String> split) {
            this.qsw = split.get(0) + split.get(1);
            this.zxz = split.get(2);
            this.ycdz = split.get(3) + split.get(4) + split.get(5) + split.get(6) + split.get(7);
            this.mm = split.get(8) + split.get(9);
            this.gnm = split.get(10);
            // 将数据长度进行转换
            ByteBuf byteBuf = ByteUtils.hexString2ByteBuf(split.get(11) + split.get(12));
            this.sjcd = byteBuf.getUnsignedShort(0);
            this.sjqsf = split.get(13);
            this.yczdsbsxBody = new YCZDSBSXBody(split, this.sjcd);
        }


    }

    /**
     * 遥测站 定时报 上行 报文 正文
     */
    @Data
    private static class YCZDSBSXBody {
        /**
         * 流水号
         */
        private Integer lsh;
        /**
         * 发报时间
         */
        private StringBuilder fbsj = new StringBuilder();
        /**
         * 遥测站地址
         */
        private StringBuilder yczdz = new StringBuilder();

        /**
         * 遥测站分类
         */
        private String yczfl;

        /**
         * 观测时间
         */
        private StringBuilder yczsj = new StringBuilder();

        /**
         * 要素标识符
         */
        private String ysbsf;

        private StringBuilder data = new StringBuilder();


        public YCZDSBSXBody(List<String> split, Integer sjcd) {
            // 从14帧开始截取,截取到 特定帧 减去 电源电压
            List<String> body = split.subList(14, 14 + (sjcd - 4));
            // 流水号
            ByteBuf byteBuf = ByteUtils.hexString2ByteBuf(body.get(0) + body.get(1));
            this.lsh = byteBuf.getUnsignedShort(0);
            byteBuf.release();

            // 发报时间
            for (int i = 2; i <= 7; i++) {
                fbsj.append(i != 7 ? body.get(i) + "-" : body.get(i));

            }
            //  遥测地址
            for (int i = 8; i <= 14; i++) {
                yczdz.append(body.get(i));
            }
            // 遥测站分类
            this.yczfl = body.get(15);
            // 观测时间
            for (int i = 16; i <= 22; i++) {
                yczsj.append(i != 22 ? body.get(i) + "-" : body.get(i));
            }
            // 计算数据..
            // 39 23 00 00 03 70
            this.ysbsf = body.get(23);
            DataLengthParser.LengthInfo parse = DataLengthParser.parse(ByteUtils.hexString2Bytes(body.get(24))[0]);
            // 从 25 截取到 多少个字节
            for (int i = 25; i <= 25 + parse.getDataLength() - 1; i++) {
                data.append(body.get(i));
            }
            BigDecimal bigDecimal = formatNumber(Integer.parseInt(data.toString()), parse.getDecimalDigits());
            System.out.println(bigDecimal); // 0.370

        }
    }

formatNumber

    public static BigDecimal formatNumber(int number, int decimalPlaces) {
        BigDecimal decimal = new BigDecimal(number);
        return decimal.movePointLeft(decimalPlaces);
    }

测试

    public static void main(String[] args) {
        String data = "7E7E014160325858A00032002102001A250429183000F1F1416032585848F0F025042918303923000003703812122803027E";
        List<String> split = ArrayUtils.split(data);
        TestBody testBody = new TestBody(split);
        System.out.println(testBody);
    }

后言

部分内容我没解析是因为我用不到,我实际上用的就是数据这块,只是为了方便排查估才将整个报文都通过截取的方式解析了一遍…

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐