首页 - 关于研博 - 技术笔记 - 电动汽车GB/T32960-2016协议介绍开发
电动汽车GB/T32960-2016协议介绍开发
2025.02.21

一、协议介绍

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

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

 

通讯包结构组成如下:

命令标识定义如下:

 

应答标志定义如下:

时间定义如下:

数据单元格式和定义

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

更多关于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> 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 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 COMMAND_MAP = new HashMap();
    //应答标识
    private final static Map ANSWER_MAP = new HashMap();
    //加密方式
    private final static Map ENCRYPT_MAP = new HashMap();

    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> 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 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 parseData(String command, byte[] data) {

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

    /**
     * 车辆登入
     */
    private Map carLogin(byte[] data) {
        Map 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):车企和政府的数据共享平台等。

获取相关资料
下载地址将会发送至您填写的邮箱
相关新闻
IEC104协议解析
2025-03-07
HJ212环境监测数据传输协议
2025-02-14
电能表DLT645协议解析
2025-02-07
  • 在线客服
  • 电话咨询
  • 微信
  • 短视频