JAVA实现GBT32960报文解析(二):报文协议-数据包结构解析源码

**JAVA实现GBT32960报文解析系列文章链接:**
JAVA实现GBT32960报文解析(一):掌握协议中的各种数据类型和完整报文结构
JAVA实现GBT32960报文解析(二):数据包结构解析源码
JAVA实现GBT32960报文解析(三):0x01整车数据解析源码
JAVA实现GBT32960报文解析(四):0x02驱动电机数据解析源码
JAVA实现GBT32960报文解析(五):0x03燃料电池数据解析源码(待更…)
JAVA实现GBT32960报文解析(六):0x04发动机数据解析源码(待更…)
JAVA实现GBT32960报文解析(七):0x05车辆位置数据解析源码(待更…)
JAVA实现GBT32960报文解析(八):0x06极值数据解析源码(待更…)
JAVA实现GBT32960报文解析(九):0x07报警数据解析源码(待更…)
实战需求示例:车辆月度充电详情统计报表,含快充、慢充区分统计(待更…)



前言

报文的最前段数据信息,即是国标协议文档中所提到的“数据包结构定义”,也就是本篇文章所讲的报文头部信息。如下:
在这里插入图片描述
已知是实际信息上报的报文,因此文章结尾所提供下载的示例代码也不会包含业务的处理部分,仅是针对解析。且为了代码运行的便捷,将这整个小项目用一个Main()运行起来的方式去实现。

提示:从本篇章开始用一段命令标识为0x02【实际信息上报】的完整数据包进行逐字节解析。

以下是后续代码实现所使用的一段完整的国标报文信息,代码如下(示例):

	String hexStr = "232303FE584C32434546355030414C30303730323301012D15071C110B320102030100000000010E0C58271000021013510065020101014F4E204E2055001F271005000633555501D2D2CE0601220D00011F0B1501014B010B49070300000450000000000801010C58271000600001600CBD0CD90CD00CCF0CE00CF00CE00CCB0CE10CE00CDF0CC80CC50CD30CCF0CE70CE50CE30CD90CE90CF60CEF0CE10CDF0CEA0CED0CE80CD10CDA0CEE0B150CD30CED0D000CCA0CED0CD60CDA0CED0CDC0CE70CF00CED0CDF0CF40CF30CDC0CE50CE00CEF0CE10CD20CDD0CE80CD60CEA0CDD0CE90CD90CD60CC90CCB0CDE0CCC0CD30CD60CE60CF20CE20CCC0CE90CE40CF80CA00CE50CE10CE90CE80CE20CF70CF50CEA0CCD0CF80CE50CDD0CE00CE10CE90CDD0CEB0CEE0CF40CF60CFC0CE109010100104B4A4A4A4949494A4A49494A4A4A4A4A5F";

一、命令单元

命令单元包含它的命令标识和应答标志

1.命令标识

在这里插入图片描述
关于状态类型的解析其实就是对应字符的匹配,可以通过switch()、if()等逻辑判断方式。出于对代码简洁和阅读便捷考虑,我个人更习惯在构造函数中采用Map键值对的方式定义,使其在初始化时就存储定义好,等到解析调用时通过Map.get()方法去取出对应的value值。

解析命令标识,代码如下(示例):

		// 构造函数中定义命令标识
		commandMap = new HashMap<String, String>();
    	commandMap.put("0x01", "车辆登入");
    	commandMap.put("0x02", "实时信息上报");
    	commandMap.put("0x03", "补发信息上报");
    	commandMap.put("0x04", "车辆登出");
    	commandMap.put("0x05", "平台登入");
    	commandMap.put("0x06", "平台登出");
    	
    	/**这里我们还是暂不考虑终端和系统之间的业务处理。所以其他状态标识的处理省略掉..**/
    	
		// 解析调用(因为报文起始符前面有两位固定的0x23、0x23,所以从下标2开始解析)
		map.put("命令标识", commandMap.get("0x" + bytes[2]));

2.应答标志

在这里插入图片描述

解析应答标志,代码如下(示例):

		// 构造函数中定义应答标识
        answerMap = new HashMap<String, String>();
        answerMap.put("0x01", "成功");
        answerMap.put("0x02", "错误");
        answerMap.put("0x03", "VIN重复");
        answerMap.put("0xFE", "命令");
		// 解析调用
		map.put("应答标识", answerMap.get("0x" + bytes[3]));

二、唯一标识码VIN

解析VIN码,代码如下(示例):

		// VIN解析
        String vin = "";
        for(int i=4; i<=20; i++)
        {
            vin += ParseUtils.hexStr2Str(bytes[i]);
        }
        map.put("VIN", vin);

提示:ParseUtils类是在第一篇文章中实现的代码类

三、数据单元

1.数据单元加密方式、数据单元长度

在这里插入图片描述

单元数据加密方式、单元数据长度的解析,代码如下(示例):

		// 构造函数中定义加密方式
        encryptMap = new HashMap<String, String>();
        encryptMap.put("0x01", "数据不加密");
        encryptMap.put("0x02", "数据经过RSA算法加密");
        encryptMap.put("0x03", "数据经过AES128位算法加密");
        encryptMap.put("0xFE", "异常");
        encryptMap.put("0xFF", "无效");
		// 解析加密方式
        String encrypt = "0x" + bytes[21];
        map.put("加密方式", encryptMap.get(encrypt));

        // 解析数据单元长度
        int length = Integer.valueOf(bytes[22] + bytes[23],16);
        map.put("数据长度", String.valueOf(length));

2.数据单元解密处理

如果单元数据经过了RSA算法或AES-128位算法加密,则需要将其进行解密。

数据单元解密,代码如下(示例):

        // 如果使用了加密方式
        if(encrypt.equals("0x02") || encrypt.equals("0x03"))
        {
            // 获取单元数据:从单元数据第一位,到报文末尾效验码的前一位
            String[] unitBytes = ParseUtils.subArray(bytes, 24, bytes.length - (24 + 1));
            if (encrypt.equals("0x02")) {
                // 将数据单元部分,做RSA算法解密处理
                //unitBytes = RSAUtil.decryptByPrivateKey(unitBytes, privateKey);
            } else if (encrypt.equals("0x03")) {
                // 将数据单元部分,AES-128位算法解密处理
                //unitBytes = AESUtil.decryptByPrivateKey(unitBytes, privateKey);
            }
            // 替换已解密的单元数据部分
            System.arraycopy(unitBytes, 0, bytes, 24, unitBytes.length);
        }

四、校验码&异或校验处理

在这里插入图片描述

通过效验码,做异或校验处理。代码如下(示例):

		// 解析校验码
        String checkCode = bytes[bytes.length - 1];
        // 获取校验内容:从完整报文固定0x23,0x23过后,到末尾校验码前
        String[] checkStr = ParseUtils.subArray(bytes, 2, bytes.length - (2 + 1));
        // 执行异或校验
        String checkResult= BCCVerifyUtils.xorComputeResult(checkStr);
        if(null != checkResult && checkCode.equals(checkResult))
            map.put("异或校验结果", "true");
        else
            map.put("异或校验结果", "false");

提示:代码片段中第6行异或校验所用到的BCCVerifyUtils类方法,可在文章末尾链接中下载。

五、时间

无论前面命令标识的解析是任何一种,后续紧跟的字符其实都是数据的时间。因此我放在这里就一并解析它
在这里插入图片描述

需要根据命令标识来确定,报文所描述的时间是什么时间。代码如下(示例):

		// 构造函数中根据命令标识,定义时间标题
		timeMap = new HashMap<String, String>();
    	timeMap.put("0x01", "车辆登入时间");
    	timeMap.put("0x02", "数据采集时间");
    	timeMap.put("0x03", "数据采集时间");	//补发信息义是数据采集的时间
    	timeMap.put("0x04", "车辆登出时间");
    	timeMap.put("0x05", "平台登入时间");
    	timeMap.put("0x06", "平台登出时间");
        // 解析数据时间
        StringBuffer time = new StringBuffer();
        time.append("20" + Integer.valueOf(bytes[24],16));
        time.append("-" + Integer.valueOf(bytes[25],16));
        time.append("-" + Integer.valueOf(bytes[26],16));
        time.append(" " + Integer.valueOf(bytes[27],16));
        time.append(":" + Integer.valueOf(bytes[28],16));
        time.append(":" + Integer.valueOf(bytes[29],16));
        map.put(timeMap.get("0x" + bytes[2]), time.toString());

总结

根据文章结尾可下载的完整代码示例,最终通过System.out.println("Structure Map => " + map);打印到控制台。

控制台输出结果如下:

Structure Map => {数据长度=301, 异或校验结果=true, 应答标识=命令, 加密方式=数据不加密, VIN=XL2CEF5P0AL007023, 数据采集时间=2021-7-28 17:11:50, 命令标识=补发信息上报}

从下一篇文章,继续解析:数据单元信息标识为0x01的整车数据


** 相关下载:**
国标协议GBT32960文档 (包含《GB/T 32960-2016 电动汽车远程服务与管理系统技术规范》完整的1、2、3部分)
源码部分:
JAVA解析GBT32960协议 - 数据包结构源码(另含ParseUtils.java和BCCVerifyUtils.java两个工具类)
JAVA解析GBT32960协议 - 0x01整车数据源码
JAVA解析GBT32960协议 - 0x02驱动电机数据源码
JAVA解析GBT32960协议 - 0x03燃料电池数据源码(待更…)
JAVA解析GBT32960协议 - 0x04发动机数据源码(待更…)
JAVA解析GBT32960协议 - 0x05车辆位置数据源码(待更…)
JAVA解析GBT32960协议 - 0x06极值数据源码(待更…)
JAVA解析GBT32960协议 - 0x07报警数据源码(待更…)

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值