在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):车企和政府的数据共享平台等。