电动汽车GB/T32960-2016协议介绍开发

一、协议介绍

在GB/T32960-2016协议中规定了电动汽车远程服务于管理系统中协议结构、通信连接、数据包结构与定义、数据单元格式与定义。本协议采用大端模式的网络字节序来传递字和双字。

协议中传输的数据类型如下:

通讯包结构组成如下:

起始字节

定义

数据类型

描述及要求

0

起始符

STRING

固定为ASCII字符‘##’,用“0x23,0x23”表示。

2

命令单元

命令标识

BYTE

命令单元定义见后续说明。

3

应答标志

BYTE

4

唯一识别码

STRING

当传输车辆数据时,应使用车辆VIN,其字码应符合GB16735的规定。如传输其他数据,则使用唯一自定义编码。

21

数据单元加密方式

BYTE

0x01:数据不加密;0x02:数据经过RSA算法加密;0x03:数据经过AES128位算法加密;0xFE标识异常;0xFF表示无效,其他预留。

22

数据单元长度

WORD

数据单元的总字节数,有效值范围:0~65531。

24

数据单元

数据单元格式和定义见后续说明。

倒数第1位

校验码

BYTE

采用BCC法,校验范围从命令单元的第一个字节开始,同后一个字节异或,知道校验码前一字节位置,校验码占用一个字节,当数据单元存在加密时,应先加密后校验,先校验后解密。

命令标识定义如下:

应答标志定义如下:

时间定义如下:

数据单元格式和定义

本次只有车辆登入的报文解析,车辆登入的数据格式和定义如下:

更多关于GB/T32960-2016的内容可点击查看。

二、协议解析开发

本次协议解析基于研博工业物联网统一接入系统(stew-ot)协议扩展规范开发。示例只针对GB/T32960-2016协议的车辆登入数据解码,不涉及对该类设备的控制。

新建类com.yanboot.iot.protocol.tcp.GBT32960.GBT32960ProtocolCodec,根据SDK包开发规范完成协议报文的解析工作。

  • 实现com.yanboot.iot.sdk.protocol.ProtocolCodec接口,重写support方法,指定协议的唯一标识、名称、特性等内容。
@Override
public ProtocolSupport support() {
    return new ProtocolSupport(TransportProtocol.TCP)
    .id("GB/T 32960.3-2016")
    .name("GB/T 32960-2016《电动汽车远程服务与管理技术规范》")
    .feature(new ProtocolFeature().keepOnline(true).keepOnlineTimeoutSeconds(1800))
    .description("GB/T 32960-2016《电动汽车远程服务与管理技术规范》")
    ;
}
  • 实现com.yanboot.iot.sdk.protocol.ProtocolCodec接口的decode方法,完成协议的解码工作。
@Override
public void decode(OperatorSupplier supplier, DeviceSession deviceSession, ProtocolMessage<?> message, MessageExporter<DeviceMessage<?>> messageExporter) {
    TcpProtocolMessage tcpProtocolMessage = (TcpProtocolMessage) message;
    byte[] payload = tcpProtocolMessage.payloadAsBytes();

    //起始符
    if (START_BIT != payload[0] || START_BIT != payload[1]) {
        log.error("数据包起始符不正确,请检查数据包数据!{}", Arrays.toString(payload));
        return;
    }
    //校验码
    if (!checkSum(payload)) {
        log.error("数据包校验码校验不正确,请检查数据包数据!{}", Arrays.toString(payload));
        return;
    }
    //命令标识
    String command = COMMAND_MAP.get(payload[2] + "");
    //应答标志
    String reply = ANSWER_MAP.get(payload[3] + "");
    //唯一识别码
    StringBuffer vin = new StringBuffer();
    for (int i = 4; i < 21; i++) {
        vin.append(payload[i]);
    }
    //数据单元加密方式
    String encrypt = ENCRYPT_MAP.get(payload[21] + "");
    //数据单元长度
    int dataLen = Integer.parseInt(payload[22] + "" + payload[23], 16);
    //数据单元
    byte[] data = Arrays.copyOfRange(payload, 24, 24 + dataLen);
    Map<String, Object> dataMap = parseData(command, data);
    if (ObjectUtil.isNull(dataMap)) {
        return;
    }
    dataMap.put("command", command);
    dataMap.put("reply", reply);
    dataMap.put("encrypt", encrypt);
    messageExporter.export(new ReportPropertyMessage().deviceId(vin.toString()).properties(dataMap).timestamp(Long.parseLong(dataMap.getOrDefault("datetime",System.currentTimeMillis()).toString())));
}
  • 完整代码如下:
package com.yanboot.iot.protocol.tcp.GBT32960;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSON;
import com.yanboot.iot.core.constant.TransportProtocol;
import com.yanboot.iot.core.message.device.DeviceMessage;
import com.yanboot.iot.core.message.device.impl.ReportPropertyMessage;
import com.yanboot.iot.core.message.protocol.ProtocolMessage;
import com.yanboot.iot.core.message.protocol.impl.TcpProtocolMessage;
import com.yanboot.iot.core.operator.OperatorSupplier;
import com.yanboot.iot.core.session.DeviceSession;
import com.yanboot.iot.sdk.protocol.MessageExporter;
import com.yanboot.iot.sdk.protocol.ProtocolCodec;
import com.yanboot.iot.sdk.protocol.ProtocolFeature;
import com.yanboot.iot.sdk.protocol.ProtocolSupport;
import lombok.extern.slf4j.Slf4j;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 *
 */
@Slf4j
public class GBT32960ProtocolCodec implements ProtocolCodec {
    private final static byte START_BIT = 0X23;
    private final static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyMMddHHmmss");

    //命令标识
    private final static Map<String, String> COMMAND_MAP = new HashMap<String, String>();
    //应答标识
    private final static Map<String, String> ANSWER_MAP = new HashMap<String, String>();
    //加密方式
    private final static Map<String, String> ENCRYPT_MAP = new HashMap<String, String>();

    static {
        // 定义命令标识
        COMMAND_MAP.put("01", "车辆登入");
        COMMAND_MAP.put("02", "实时信息上报");
        COMMAND_MAP.put("03", "补发信息上报");
        COMMAND_MAP.put("04", "车辆登出");
        COMMAND_MAP.put("05", "平台登入");
        COMMAND_MAP.put("06", "平台登出");
        // 定义应答标识
        ANSWER_MAP.put("01", "成功");
        ANSWER_MAP.put("02", "错误");
        ANSWER_MAP.put("03", "VIN重复");
        ANSWER_MAP.put("FE", "命令");
        // 定义加密方式
        ENCRYPT_MAP.put("01", "数据不加密");
        ENCRYPT_MAP.put("02", "数据经过RSA算法加密");
        ENCRYPT_MAP.put("03", "数据经过AES128位算法加密");
        ENCRYPT_MAP.put("FE", "异常");
        ENCRYPT_MAP.put("FF", "无效");
    }

    @Override
    public ProtocolSupport support() {
        return new ProtocolSupport(TransportProtocol.TCP)
                .id("GB/T 32960.3-2016")
                .name("GB/T 32960-2016《电动汽车远程服务与管理技术规范》")
                .feature(new ProtocolFeature().keepOnline(true).keepOnlineTimeoutSeconds(30))
                .description("GB/T 32960-2016《电动汽车远程服务与管理技术规范》")
                ;
    }


    /**
     * 解码
     */
    @Override
    public void decode(OperatorSupplier supplier, DeviceSession deviceSession, ProtocolMessage<?> message, MessageExporter<DeviceMessage<?>> messageExporter) {
        TcpProtocolMessage tcpProtocolMessage = (TcpProtocolMessage) message;
        byte[] payload = tcpProtocolMessage.payloadAsBytes();

        //起始符
        if (START_BIT != payload[0] || START_BIT != payload[1]) {
            log.error("数据包起始符不正确,请检查数据包数据!{}", Arrays.toString(payload));
            return;
        }
        //校验码
        if (!checkSum(payload)) {
            log.error("数据包校验码校验不正确,请检查数据包数据!{}", Arrays.toString(payload));
            return;
        }
        //命令标识
        String command = COMMAND_MAP.get(payload[2] + "");
        //应答标志
        String reply = ANSWER_MAP.get(payload[3] + "");
        //唯一识别码
        StringBuffer vin = new StringBuffer();
        for (int i = 4; i < 21; i++) {
            vin.append(payload[i]);
        }
        //数据单元加密方式
        String encrypt = ENCRYPT_MAP.get(payload[21] + "");
        //数据单元长度
        int dataLen = Integer.parseInt(payload[22] + "" + payload[23], 16);
        //数据单元
        byte[] data = Arrays.copyOfRange(payload, 24, 24 + dataLen);
        Map<String, Object> dataMap = parseData(command, data);
        if (ObjectUtil.isNull(dataMap)) {
            return;
        }
        dataMap.put("command", command);
        dataMap.put("reply", reply);
        dataMap.put("encrypt", encrypt);
        messageExporter.export(new ReportPropertyMessage().deviceId(vin.toString()).properties(dataMap).timestamp(Long.parseLong(dataMap.getOrDefault("datetime",System.currentTimeMillis()).toString())));
    }

    /**
     * 解析数据
     *
     * @param command 命令标识
     * @param data    数据单元
     * @return 解析结果
     */
    private Map<String, Object> parseData(String command, byte[] data) {

        switch (command) {
            case "01" -> {
                return carLogin(data);
            }
        }
        return null;
    }

    /**
     * 车辆登入
     */
    private Map<String, Object> carLogin(byte[] data) {
        Map<String, Object> dataMap = new HashMap<>();
        StringBuilder datetime = new StringBuilder();
        for (int i = 0; i < 6; i++) {
            byte datum = data[i];
            if (datum < 10) {
                datetime.append("0");
            }
            datetime.append(datum);
        }
        dataMap.put("datetime", LocalDateTime.parse(datetime.toString(),dtf).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        dataMap.put("loginSequenceNumber", Integer.parseInt(data[6] + "" + data[7], 16) + "");
        StringBuilder iccid = new StringBuilder();
        for (int i = 8; i < 28; i++) {
            iccid.append(data[i]);
        }
        dataMap.put("ICCID", iccid.toString());
        dataMap.put("energyStorageNumber", Integer.parseInt(data[28] + "", 16) + "");
        dataMap.put("energyStorageLength", Integer.parseInt(data[29] + "", 16) + "");
        int length = data.length;
        StringBuilder energyStorageCode = new StringBuilder();
        for (int i = 30; i < length; i++) {
            energyStorageCode.append(data[i]);
        }
        dataMap.put("energyStorageCode", energyStorageCode.toString());
        return dataMap;
    }


    /**
     * 校验码校验
     */
    public static boolean checkSum(byte[] payload) {
        int checkCode = Integer.parseInt(payload[payload.length - 1] + "", 16);
        int a = 0;
        for (int i = 2; i < payload.length - 2; i++) {
            a = a ^ Integer.parseInt(payload[i] + "", 16);
        }
        return checkCode == a;
    }
}

三、适用场景

GB/T 32960-2016《电动汽车远程服务与管理技术规范》适用于以下场景中:

(1):电动汽车健康监测;

(2):车辆使用记录;

(3):故障诊断;

(4):车企和政府的数据共享平台等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值