在實際應(yīng)用中,車載終端需要通過JT/T808協(xié)議接入到監(jiān)控平臺中。接入過程中,車載終端需要進(jìn)行注冊和鑒權(quán),以確保通信雙方的身份合法性。一旦接入成功,車載終端就可以向監(jiān)控平臺發(fā)送位置、狀態(tài)、報警等信息,并接收來自監(jiān)控平臺的控制指令。同時,監(jiān)控平臺也可以對車載終端進(jìn)行遠(yuǎn)程管理和控制。
(1)終端注冊
車載終端在安裝完成后,需要向監(jiān)控平臺進(jìn)行注冊。
注冊過程中,終端需要提供設(shè)備的相關(guān)信息,如設(shè)備ID、制造商ID、車輛VIN碼等。
監(jiān)控平臺在接收到注冊信息后,會驗證這些信息的有效性,并分配一個唯一的終端ID給車載終端。
終端收到平臺返回的終端ID后,注冊流程完成。
(2)鑒權(quán)
注冊成功后,車載終端需要進(jìn)行鑒權(quán),以確保只有經(jīng)過授權(quán)的終端才能與監(jiān)控平臺進(jìn)行通信。
終端會向監(jiān)控平臺發(fā)送鑒權(quán)請求,請求中包含終端ID和鑒權(quán)碼。
監(jiān)控平臺會校驗鑒權(quán)碼的正確性,以確認(rèn)車載終端的合法性。
如果鑒權(quán)碼校驗通過,則車載終端與監(jiān)控平臺之間的通信鏈路建立成功。
(3)數(shù)據(jù)上報
車載終端會根據(jù)預(yù)設(shè)的上報間隔,定期向監(jiān)控平臺發(fā)送數(shù)據(jù),包括位置信息、狀態(tài)信息、報警信息等。
這些數(shù)據(jù)會按照J(rèn)T/T808協(xié)議規(guī)定的消息格式進(jìn)行封裝,并通過之前建立的通信鏈路發(fā)送給監(jiān)控平臺。
(4)命令下發(fā)
監(jiān)控平臺可以向車載終端發(fā)送控制命令,如遠(yuǎn)程控制、信息查詢等。
這些命令同樣會按照J(rèn)T/T808協(xié)議規(guī)定的消息格式進(jìn)行封裝,并通過通信鏈路發(fā)送給車載終端。
車載終端在接收到命令后,會進(jìn)行相應(yīng)的處理,并將處理結(jié)果返回給監(jiān)控平臺。
(5)連接維護(hù)
在通信過程中,車載終端會周期性地向監(jiān)控平臺發(fā)送心跳消息,以保持連接的活躍狀態(tài)。
如果連接中斷,車載終端會嘗試重新建立連接,并重新進(jìn)行鑒權(quán)流程。
(6)數(shù)據(jù)解析與處理
監(jiān)控平臺在接收到車載終端發(fā)送的數(shù)據(jù)后,會進(jìn)行解析和處理。
根據(jù)數(shù)據(jù)的類型和內(nèi)容,監(jiān)控平臺可以進(jìn)行車輛定位、狀態(tài)監(jiān)測、報警處理等操作。
同時,監(jiān)控平臺還可以根據(jù)需要對車載終端發(fā)送控制命令,實現(xiàn)遠(yuǎn)程監(jiān)管和服務(wù)。
通過以上步驟,車載終端可以成功通過JT/T808協(xié)議接入到監(jiān)控平臺,實現(xiàn)遠(yuǎn)程監(jiān)管和服務(wù)的功能。
JT/T808標(biāo)準(zhǔn)廣泛應(yīng)用于車輛遠(yuǎn)程監(jiān)管、物流管理、車輛安防等領(lǐng)域。例如,在車隊管理系統(tǒng)中,通過衛(wèi)星定位和通信技術(shù),監(jiān)控中心可以實時追蹤車輛位置、行駛狀態(tài)和駕駛行為;在物流行業(yè)中,物流公司可以實現(xiàn)對運輸車輛的實時監(jiān)控和路徑優(yōu)化,提高運輸效率;在車輛安全方面,通過實時監(jiān)控和追蹤,可以及時發(fā)現(xiàn)車輛異常情況,如變更路線、事故等,以便及時采取應(yīng)對措施。
數(shù)據(jù)類型 |
描述及要求 |
BYTE |
無符號單字節(jié)整形(字節(jié), 8 位) |
WORD |
無符號雙字節(jié)整形(字, 16 位) |
DWORD |
無符號四字節(jié)整形(雙字, 32 位) |
BYTE[n] |
n 字節(jié) |
BCD[n] |
8421 碼, n 字節(jié) |
STRING |
GBK 編碼,若無數(shù)據(jù),置空 |
標(biāo)識位 |
消息頭 |
消息體 |
校驗碼 |
標(biāo)識位 |
1byte(0x7e) |
16byte |
1byte |
1byte(0x7e) |
消息ID(0-1) 消息體屬性(2-3) 終端手機號(4-9) 消息流水號(10-11) 消息包封裝項(12-15)
byte[0-1] 消息ID word(16)
byte[2-3] 消息體屬性 word(16)
bit[0-9] 消息體長度
bit[10-12] 數(shù)據(jù)加密方式
此三位都為 0,表示消息體不加密
第 10 位為 1,表示消息體經(jīng)過 RSA 算法加密
其它保留
bit[13] 分包
1:消息體衛(wèi)長消息,進(jìn)行分包發(fā)送處理,具體分包信息由消息包封裝項決定
0:則消息頭中無消息包封裝項字段
bit[14-15] 保留
byte[4-9] 終端手機號或設(shè)備ID bcd[6]
根據(jù)安裝后終端自身的手機號轉(zhuǎn)換
手機號不足12 位,則在前面補 0
byte[10-11] 消息流水號 word(16)
按發(fā)送順序從 0 開始循環(huán)累加
byte[12-15] 消息包封裝項
byte[0-1] 消息包總數(shù)(word(16))
該消息分包后得總包數(shù)
byte[2-3] 包序號(word(16))
從 1 開始
如果消息體屬性中相關(guān)標(biāo)識位確定消息分包處理,則該項有內(nèi)容
否則無該項
本次協(xié)議解析基于研博工業(yè)物聯(lián)網(wǎng)統(tǒng)一接入系統(tǒng)(stew-ot)協(xié)議擴展規(guī)范開發(fā)。
解析協(xié)議中用到的實體類
public class PackageData {
/**
* 16byte 消息頭
*/
protected MsgHeader msgHeader;
// 消息體
protected byte[] msgHeaderBytes;
// 消息體字節(jié)數(shù)組
@JSONField(serialize=false)
protected byte[] msgBodyBytes;
/**
* 校驗碼 1byte
*/
protected int checkSum;
@JSONField(serialize=false)
protected Channel channel;
public MsgHeader getMsgHeader() {
return msgHeader;
}
public void setMsgHeader(MsgHeader msgHeader) {
this.msgHeader = msgHeader;
}
public byte[] getMsgBodyBytes() {
return msgBodyBytes;
}
public byte[] getMsgHeaderBytes() {
return msgHeaderBytes;
}
public void setMsgHeaderBytes(byte[] msgHeaderBytes) {
this.msgHeaderBytes = msgHeaderBytes;
}
public void setMsgBodyBytes(byte[] msgBodyBytes) {
this.msgBodyBytes = msgBodyBytes;
}
public int getCheckSum() {
return checkSum;
}
public void setCheckSum(int checkSum) {
this.checkSum = checkSum;
}
public Channel getChannel() {
return channel;
}
public void setChannel(Channel channel) {
this.channel = channel;
}
@Override
public String toString() {
return "PackageData [msgHeader=" + msgHeader + ", msgBodyBytes=" + Arrays.toString(msgBodyBytes) + ", checkSum="
+ checkSum + ", address=" + channel + "]";
}
public static class MsgHeader {
// 消息ID
protected int msgId;
/////// ========消息體屬性
// byte[2-3]
protected int msgBodyPropsField;
// 消息體長度
protected int msgBodyLength;
// 數(shù)據(jù)加密方式
protected int encryptionType;
// 是否分包,true==>有消息包封裝項
protected boolean hasSubPackage;
// 保留位[14-15]
protected String reservedBit;
/////// ========消息體屬性
// 終端手機號
protected String terminalPhone;
// 流水號
protected int flowId;
//////// =====消息包封裝項
// byte[12-15]
protected int packageInfoField;
// 消息包總數(shù)(word(16))
protected long totalSubPackage;
// 包序號(word(16))這次發(fā)送的這個消息包是分包中的第幾個消息包, 從 1 開始
protected long subPackageSeq;
//////// =====消息包封裝項
public int getMsgId() {
return msgId;
}
public void setMsgId(int msgId) {
this.msgId = msgId;
}
public int getMsgBodyLength() {
return msgBodyLength;
}
public void setMsgBodyLength(int msgBodyLength) {
this.msgBodyLength = msgBodyLength;
}
public int getEncryptionType() {
return encryptionType;
}
public void setEncryptionType(int encryptionType) {
this.encryptionType = encryptionType;
}
public String getTerminalPhone() {
return terminalPhone;
}
public void setTerminalPhone(String terminalPhone) {
this.terminalPhone = terminalPhone;
}
public int getFlowId() {
return flowId;
}
public void setFlowId(int flowId) {
this.flowId = flowId;
}
public boolean isHasSubPackage() {
return hasSubPackage;
}
public void setHasSubPackage(boolean hasSubPackage) {
this.hasSubPackage = hasSubPackage;
}
public String getReservedBit() {
return reservedBit;
}
public void setReservedBit(String reservedBit) {
this.reservedBit = reservedBit;
}
public long getTotalSubPackage() {
return totalSubPackage;
}
public void setTotalSubPackage(long totalPackage) {
this.totalSubPackage = totalPackage;
}
public long getSubPackageSeq() {
return subPackageSeq;
}
public void setSubPackageSeq(long packageSeq) {
this.subPackageSeq = packageSeq;
}
public int getMsgBodyPropsField() {
return msgBodyPropsField;
}
public void setMsgBodyPropsField(int msgBodyPropsField) {
this.msgBodyPropsField = msgBodyPropsField;
}
public void setPackageInfoField(int packageInfoField) {
this.packageInfoField = packageInfoField;
}
public int getPackageInfoField() {
return packageInfoField;
}
@Override
public String toString() {
return "MsgHeader [msgId=" + msgId + ", msgBodyPropsField=" + msgBodyPropsField + ", msgBodyLength="
+ msgBodyLength + ", encryptionType=" + encryptionType + ", hasSubPackage=" + hasSubPackage
+ ", reservedBit=" + reservedBit + ", terminalPhone=" + terminalPhone + ", flowId=" + flowId
+ ", packageInfoField=" + packageInfoField + ", totalSubPackage=" + totalSubPackage
+ ", subPackageSeq=" + subPackageSeq + "]";
}
}
}
實現(xiàn)com.yanboot.iot.sdk.protocol.ProtocolCodec接口,重寫support方法,指定協(xié)議的唯一標(biāo)識、名稱、特性等內(nèi)容。
@Override
public ProtocolSupport support() {
return new ProtocolSupport(TransportProtocol.TCP)
.id("JT/T808-tcp-codec")
.name("JT/T808-tcp協(xié)議")
.description("JT/T808-2019版")
.feature(new
ProtocolFeature().remoteUpgrade(false).keepOnline(true).keepOnlineTimeoutSeconds(360));
}
實現(xiàn)com.yanboot.iot.sdk.protocol.ProtocolCodec接口的decode方法,完成協(xié)議的解碼工作。
@Override
public void decode(OperatorSupplier supplier, DeviceSession deviceSession, ProtocolMessage<?> message, MessageExporter<DeviceMessage<?>> messageExporter) {
TcpProtocolMessage tcpProtocolMessage = (TcpProtocolMessage) message;
ByteBuf payload = tcpProtocolMessage.getPayload();
if (payload.readableBytes() <= 0) {
throw new RuntimeException("數(shù)據(jù)包異常");
}
byte[] bytes = new byte[payload.readableBytes()];
payload.readBytes(bytes);
PackageData pkg = DecodeHelper.bytes2PackageData(bytes);
log.info("pkg{}", pkg);
processPackageData(pkg, deviceSession);
}
//業(yè)務(wù)處理
private static void processPackageData(PackageData packageData, DeviceSession deviceSession) {
final PackageData.MsgHeader header = packageData.getMsgHeader();
// 1. 終端心跳-消息體為空 ==> 平臺通用應(yīng)答
if (TPMSConsts.msg_id_terminal_heart_beat == header.getMsgId()) {
log.info(">>>>>[終端心跳],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
try {
sendCommonReplay(deviceSession, packageData);
} catch (Exception e) {
log.error("<<<<<[終端心跳]處理錯誤,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(),
e.getMessage());
e.printStackTrace();
}
}
// 5. 終端鑒權(quán) ==> 平臺通用應(yīng)答
else if (TPMSConsts.msg_id_terminal_authentication == header.getMsgId()) {
log.info(">>>>>[終端鑒權(quán)],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
try {
// TerminalAuthenticationMsg authenticationMsg = new TerminalAuthenticationMsg(packageData);
// this.msgProcessService.processAuthMsg(authenticationMsg);
//TODO 待解析鑒權(quán)消息
sendCommonReplay(deviceSession, packageData);
log.info("<<<<<[終端鑒權(quán)],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
} catch (Exception e) {
log.error("<<<<<[終端鑒權(quán)]處理錯誤,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(),
e.getMessage());
e.printStackTrace();
}
}
// 6. 終端注冊 ==> 終端注冊應(yīng)答
else if (TPMSConsts.msg_id_terminal_register == header.getMsgId()) {
log.info(">>>>>[終端注冊],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
try {
TcpProtocolMessage tcpProtocolMessage = new TcpProtocolMessage();
TerminalRegisterMsg msg = DecodeHelper.toTerminalRegisterMsg(packageData);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(packageData.getMsgHeader().getFlowId());
//TODO
outputStream.write(0);
outputStream.write(0);
byte[] byteArray = outputStream.toByteArray();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.write(126);
byteArrayOutputStream.write(packageData.getMsgHeaderBytes());
byteArrayOutputStream.write(byteArray);
byte[] byteArray1 = byteArrayOutputStream.toByteArray();
byte checkSum = 0;
for (int i = 1; i < byteArray1.length; i++) {
checkSum ^= byteArray1[i];
}
ByteArrayOutputStream result = new ByteArrayOutputStream(byteArray1.length + 2);
result.write(byteArray1);
result.write(checkSum);
result.write(126);
tcpProtocolMessage.payload(result.toByteArray());
deviceSession.send(tcpProtocolMessage);
log.info("<<<<<[終端注冊],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
log.info("注冊信息:{}", msg.getTerminalRegInfo());
} catch (Exception e) {
log.error("<<<<<[終端注冊]處理錯誤,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(),
e.getMessage());
e.printStackTrace();
}
}
// 7. 終端注銷(終端注銷數(shù)據(jù)消息體為空) ==> 平臺通用應(yīng)答
else if (TPMSConsts.msg_id_terminal_log_out == header.getMsgId()) {
log.info(">>>>>[終端注銷],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
try {
// this.msgProcessService.processTerminalLogoutMsg(packageData);
sendCommonReplay(deviceSession, packageData);
log.info("<<<<<[終端注銷],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
} catch (Exception e) {
log.error("<<<<<[終端注銷]處理錯誤,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(),
e.getMessage());
e.printStackTrace();
}
}
// 其他情況
else {
log.error(">>>>>>[未知消息類型],phone={},msgId={},package={}", header.getTerminalPhone(), header.getMsgId(),
packageData);
}
}
public static void sendCommonReplay(DeviceSession deviceSession, PackageData packageData) {
try {
PackageData.MsgHeader header = packageData.getMsgHeader();
TcpProtocolMessage tcpProtocolMessage1 = new TcpProtocolMessage();
int msgId = header.getMsgId();
int flowId = header.getFlowId();
byte success = ServerCommonRespMsgBody.success;
// byte[] bytes = JT808ProtocolUtils.generateMsgHeader(header.getTerminalPhone(), msgId, header.getMsgBodyPropsField(), header.getFlowId());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 1. 消息ID word(16)
BitOperator bitOperator = new BitOperator();
baos.write(bitOperator.integerTo2Bytes(msgId));
baos.write(bitOperator.integerTo2Bytes(flowId));
baos.write(success);
byte[] byteBody = baos.toByteArray();
byte checkSum = 0;
for (int i = 0; i < byteBody.length; i++) {
checkSum ^= byteBody[i];
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.write(126);
byteArrayOutputStream.write(packageData.getMsgHeaderBytes());
byteArrayOutputStream.write(byteBody);
byteArrayOutputStream.write(checkSum);
byteArrayOutputStream.write(126);
byte[] result = byteArrayOutputStream.toByteArray();
tcpProtocolMessage1.payload(result);
deviceSession.send(tcpProtocolMessage1);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
解析協(xié)議中用到的工具類
package com.yanboot.iot;
import com.yanboot.iot.common.TPMSConsts;
import com.yanboot.iot.util.BCD8421Operater;
import com.yanboot.iot.util.BitOperator;
import com.yanboot.iot.vo.PackageData;
import com.yanboot.iot.vo.req.TerminalRegisterMsg;
import lombok.extern.slf4j.Slf4j;
import com.yanboot.iot.vo.req.TerminalRegisterMsg.TerminalRegInfo;
import com.yanboot.iot.vo.PackageData.MsgHeader;
import java.nio.charset.StandardCharsets;
@Slf4j
public class DecodeHelper {
private static final BitOperator bitOperator = new BitOperator();
private static final BCD8421Operater bcd8421Operater = new BCD8421Operater();
public DecodeHelper() {
// this.bitOperator = new BitOperator();
// this.bcd8421Operater = new BCD8421Operater();
}
public static PackageData bytes2PackageData(byte[] data) {
PackageData ret = new PackageData();
// 0. 終端套接字地址信息
// ret.setChannel(msg.getChannel());
// 1. 16byte 或 12byte 消息頭
MsgHeader msgHeader = parseMsgHeaderFromBytes(data);
ret.setMsgHeader(msgHeader);
int msgBodyByteStartIndex = 12;
// 2. 消息體
// 有子包信息,消息體起始字節(jié)后移四個字節(jié):消息包總數(shù)(word(16))+包序號(word(16))
if (msgHeader.isHasSubPackage()) {
msgBodyByteStartIndex = 16;
}
byte[] tmp = new byte[msgHeader.getMsgBodyLength()];
byte[] headerBytes = new byte[msgBodyByteStartIndex];
System.arraycopy(data, 0, headerBytes, 0, headerBytes.length);
System.arraycopy(data, msgBodyByteStartIndex, tmp, 0, tmp.length);
ret.setMsgHeaderBytes(headerBytes);
ret.setMsgBodyBytes(tmp);
// 3. 去掉分隔符之后,最后一位就是校驗碼
// int checkSumInPkg =
// this.bitOperator.oneByteToInteger(data[data.length - 1]);
int checkSumInPkg = data[data.length - 1];
int calculatedCheckSum = bitOperator.getCheckSum4JT808(data, 0, data.length - 1);
ret.setCheckSum(checkSumInPkg);
if (checkSumInPkg != calculatedCheckSum) {
log.warn("檢驗碼不一致,msgid:{},pkg:{},calculated:{}", msgHeader.getMsgId(), checkSumInPkg, calculatedCheckSum);
}
return ret;
}
//消息頭解析
private static MsgHeader parseMsgHeaderFromBytes(byte[] data) {
MsgHeader msgHeader = new MsgHeader();
// 1. 消息ID word(16)
// byte[] tmp = new byte[2];
// System.arraycopy(data, 0, tmp, 0, 2);
// msgHeader.setMsgId(this.bitOperator.twoBytesToInteger(tmp));
msgHeader.setMsgId(parseIntFromBytes(data, 0, 2));
// 2. 消息體屬性 word(16)=================>
// System.arraycopy(data, 2, tmp, 0, 2);
// int msgBodyProps = this.bitOperator.twoBytesToInteger(tmp);
int msgBodyProps = parseIntFromBytes(data, 2, 2);
msgHeader.setMsgBodyPropsField(msgBodyProps);
// [ 0-9 ] 0000,0011,1111,1111(3FF)(消息體長度)
msgHeader.setMsgBodyLength(msgBodyProps & 0x1ff);
// [10-12] 0001,1100,0000,0000(1C00)(加密類型)
msgHeader.setEncryptionType((msgBodyProps & 0xe00) >> 10);
// [ 13_ ] 0010,0000,0000,0000(2000)(是否有子包)
msgHeader.setHasSubPackage(((msgBodyProps & 0x2000) >> 13) == 1);
// [14-15] 1100,0000,0000,0000(C000)(保留位)
msgHeader.setReservedBit(((msgBodyProps & 0xc000) >> 14) + "");
// 消息體屬性 word(16)<=================
// 3. 終端手機號 bcd[6]
// tmp = new byte[6];
// System.arraycopy(data, 4, tmp, 0, 6);
// msgHeader.setTerminalPhone(this.bcd8421Operater.bcd2String(tmp));
msgHeader.setTerminalPhone(parseBcdStringFromBytes(data, 4, 6));
// 4. 消息流水號 word(16) 按發(fā)送順序從 0 開始循環(huán)累加
// tmp = new byte[2];
// System.arraycopy(data, 10, tmp, 0, 2);
// msgHeader.setFlowId(this.bitOperator.twoBytesToInteger(tmp));
msgHeader.setFlowId(parseIntFromBytes(data, 10, 2));
// 5. 消息包封裝項
// 有子包信息
if (msgHeader.isHasSubPackage()) {
// 消息包封裝項字段
msgHeader.setPackageInfoField(parseIntFromBytes(data, 12, 4));
// byte[0-1] 消息包總數(shù)(word(16))
// tmp = new byte[2];
// System.arraycopy(data, 12, tmp, 0, 2);
// msgHeader.setTotalSubPackage(this.bitOperator.twoBytesToInteger(tmp));
msgHeader.setTotalSubPackage(parseIntFromBytes(data, 12, 2));
// byte[2-3] 包序號(word(16)) 從 1 開始
// tmp = new byte[2];
// System.arraycopy(data, 14, tmp, 0, 2);
// msgHeader.setSubPackageSeq(this.bitOperator.twoBytesToInteger(tmp));
msgHeader.setSubPackageSeq(parseIntFromBytes(data, 12, 2));
}
return msgHeader;
}
// protected static String parseStringFromBytes(byte[] data, int startIndex, int lenth) {
// return parseStringFromBytes(data, startIndex, lenth, null);
// }
private static String parseStringFromBytes(byte[] data, int startIndex, int lenth, String defaultVal) {
try {
byte[] tmp = new byte[lenth];
System.arraycopy(data, startIndex, tmp, 0, lenth);
return new String(tmp, TPMSConsts.string_charset);
} catch (Exception e) {
log.error("解析字符串出錯:{}", e.getMessage());
e.printStackTrace();
return defaultVal;
}
}
private static String parseBcdStringFromBytes(byte[] data, int startIndex, int lenth) {
return parseBcdStringFromBytes(data, startIndex, lenth, null);
}
private static String parseBcdStringFromBytes(byte[] data, int startIndex, int lenth, String defaultVal) {
try {
byte[] tmp = new byte[lenth];
log.debug("拷貝的數(shù)組長度:{},實際拷貝長度:{},開始的未知:{}", data.length, lenth, startIndex);
System.arraycopy(data, startIndex, tmp, 0, lenth);
return bcd8421Operater.bcd2String(tmp);
} catch (Exception e) {
log.error("解析BCD(8421碼)出錯:{}", e.getMessage());
e.printStackTrace();
return defaultVal;
}
}
// private static int parseIntFromBytes(byte[] data, int startIndex, int length) {
// return parseIntFromBytes(data, startIndex, length, 0);
// }
private static int parseIntFromBytes(byte[] data, int startIndex, int length, int defaultVal) {
try {
// 字節(jié)數(shù)大于4,從起始索引開始向后處理4個字節(jié),其余超出部分丟棄
final int len = length > 4 ? 4 : length;
byte[] tmp = new byte[len];
System.arraycopy(data, startIndex, tmp, 0, len);
return bitOperator.byteToInteger(tmp);
} catch (Exception e) {
log.error("解析整數(shù)出錯:{}", e.getMessage());
e.printStackTrace();
return defaultVal;
}
}
private static int parseIntFromBytes(byte[] data, int offset, int length) {
int value = 0;
for (int i = 0; i < length; i++) {
value = (value << 8) | (data[offset + i] & 0xFF);
}
return value;
}
private static String parseStringFromBytes(byte[] data, int offset, int length) {
return new String(data, offset, length, StandardCharsets.ISO_8859_1).trim();
}
//終端注冊
public static TerminalRegisterMsg toTerminalRegisterMsg(PackageData packageData) {
TerminalRegisterMsg ret = new TerminalRegisterMsg(packageData);
byte[] data = ret.getMsgBodyBytes();
TerminalRegInfo body = new TerminalRegInfo();
// 1. byte[0-1] 省域ID(WORD)
// 設(shè)備安裝車輛所在的省域,省域ID采用GB/T2260中規(guī)定的行政區(qū)劃代碼6位中前兩位
// 0保留,由平臺取默認(rèn)值
body.setProvinceId(parseIntFromBytes(data, 0, 2));
// 2. byte[2-3] 設(shè)備安裝車輛所在的市域或縣域,市縣域ID采用GB/T2260中規(guī)定的行 政區(qū)劃代碼6位中后四位
// 0保留,由平臺取默認(rèn)值
body.setCityId(parseIntFromBytes(data, 2, 2));
// 3. byte[4-8] 制造商ID(BYTE[5]) 5 個字節(jié),終端制造商編碼
// byte[] tmp = new byte[5];
body.setManufacturerId(parseStringFromBytes(data, 4, 5));
// 4. byte[9-16] 終端型號(BYTE[8]) 八個字節(jié), 此終端型號 由制造商自行定義 位數(shù)不足八位的,補空格。
body.setTerminalType(parseStringFromBytes(data, 9, 20));
// 5. byte[17-23] 終端ID(BYTE[7]) 七個字節(jié), 由大寫字母 和數(shù)字組成, 此終端 ID由制造 商自行定義
body.setTerminalId(parseStringFromBytes(data, 29, 7));
// 6. byte[24] 車牌顏色(BYTE) 車牌顏 色按照J(rèn)T/T415-2006 中5.4.12 的規(guī)定
body.setLicensePlateColor(parseIntFromBytes(data, 36, 1));
// 7. byte[25-x] 車牌(STRING) 公安交 通管理部門頒 發(fā)的機動車號牌
body.setLicensePlate(parseStringFromBytes(data, 37, data.length - 37));
ret.setTerminalRegInfo(body);
return ret;
}
//終端鑒權(quán)
public static TerminalRegisterMsg toTerminalAuth(PackageData packageData) {
return null;
}
}
package com.yanboot.iot.util;
import java.io.ByteArrayOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* JT808協(xié)議轉(zhuǎn)義工具類
*
* <pre>
* 0x7d01 <====> 0x7d
* 0x7d02 <====> 0x7e
* </pre>
*
* @author hylexus
*/
public class JT808ProtocolUtils {
private final Logger log = LoggerFactory.getLogger(getClass());
private static BitOperator bitOperator = new BitOperator();
private static BCD8421Operater bcd8421Operater = new BCD8421Operater();
public JT808ProtocolUtils() {
// this.bitOperator = new BitOperator();
// this.bcd8421Operater = new BCD8421Operater();
}
/**
* 接收消息時轉(zhuǎn)義<br>
*
* <pre>
* 0x7d01 <====> 0x7d
* 0x7d02 <====> 0x7e
* </pre>
*
* @param bs 要轉(zhuǎn)義的字節(jié)數(shù)組
* @param start 起始索引
* @param end 結(jié)束索引
* @return 轉(zhuǎn)義后的字節(jié)數(shù)組
* @throws Exception
*/
public byte[] doEscape4Receive(byte[] bs, int start, int end) throws Exception {
if (start < 0 || end > bs.length)
throw new ArrayIndexOutOfBoundsException("doEscape4Receive error : index out of bounds(start=" + start
+ ",end=" + end + ",bytes length=" + bs.length + ")");
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
for (int i = 0; i < start; i++) {
baos.write(bs[i]);
}
for (int i = start; i < end - 1; i++) {
if (bs[i] == 0x7d && bs[i + 1] == 0x01) {
baos.write(0x7d);
i++;
} else if (bs[i] == 0x7d && bs[i + 1] == 0x02) {
baos.write(0x7e);
i++;
} else {
baos.write(bs[i]);
}
}
for (int i = end - 1; i < bs.length; i++) {
baos.write(bs[i]);
}
return baos.toByteArray();
} catch (Exception e) {
throw e;
} finally {
if (baos != null) {
baos.close();
baos = null;
}
}
}
/**
* 發(fā)送消息時轉(zhuǎn)義<br>
*
* <pre>
* 0x7e <====> 0x7d02
* </pre>
*
* @param bs 要轉(zhuǎn)義的字節(jié)數(shù)組
* @param start 起始索引
* @param end 結(jié)束索引
* @return 轉(zhuǎn)義后的字節(jié)數(shù)組
* @throws Exception
*/
public byte[] doEscape4Send(byte[] bs, int start, int end) throws Exception {
if (start < 0 || end > bs.length)
throw new ArrayIndexOutOfBoundsException("doEscape4Send error : index out of bounds(start=" + start
+ ",end=" + end + ",bytes length=" + bs.length + ")");
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
for (int i = 0; i < start; i++) {
baos.write(bs[i]);
}
for (int i = start; i < end; i++) {
if (bs[i] == 0x7e) {
baos.write(0x7d);
baos.write(0x02);
} else {
baos.write(bs[i]);
}
}
for (int i = end; i < bs.length; i++) {
baos.write(bs[i]);
}
return baos.toByteArray();
} catch (Exception e) {
throw e;
} finally {
if (baos != null) {
baos.close();
baos = null;
}
}
}
public int generateMsgBodyProps(int msgLen, int enctyptionType, boolean isSubPackage, int reversed_14_15) {
// [ 0-9 ] 0000,0011,1111,1111(3FF)(消息體長度)
// [10-12] 0001,1100,0000,0000(1C00)(加密類型)
// [ 13_ ] 0010,0000,0000,0000(2000)(是否有子包)
// [14-15] 1100,0000,0000,0000(C000)(保留位)
if (msgLen >= 1024)
log.warn("The max value of msgLen is 1023, but {} .", msgLen);
int subPkg = isSubPackage ? 1 : 0;
int ret = (msgLen & 0x3FF) | ((enctyptionType << 10) & 0x1C00) | ((subPkg << 13) & 0x2000)
| ((reversed_14_15 << 14) & 0xC000);
return ret & 0xffff;
}
public static byte[] generateMsgHeader(String phone, int msgType, int msgBodyProps, int flowId)
throws Exception {
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
// 1. 消息ID word(16)
baos.write(bitOperator.integerTo2Bytes(msgType));
// 2. 消息體屬性 word(16)
baos.write(bitOperator.integerTo2Bytes(msgBodyProps));
// 3. 終端手機號 bcd[6]
baos.write(bcd8421Operater.string2Bcd(phone));
// 4. 消息流水號 word(16),按發(fā)送順序從 0 開始循環(huán)累加
baos.write(bitOperator.integerTo2Bytes(flowId));
// 消息包封裝項 此處不予考慮
return baos.toByteArray();
} finally {
if (baos != null) {
baos.close();
}
}
}
}
package com.yanboot.iot.util;
public class HexStringUtils {
private static final char[] DIGITS_HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
protected static char[] encodeHex(byte[] data) {
int l = data.length;
char[] out = new char[l << 1];
for (int i = 0, j = 0; i < l; i++) {
out[j++] = DIGITS_HEX[(0xF0 & data[i]) >>> 4];
out[j++] = DIGITS_HEX[0x0F & data[i]];
}
return out;
}
protected static byte[] decodeHex(char[] data) {
int len = data.length;
if ((len & 0x01) != 0) {
throw new RuntimeException("字符個數(shù)應(yīng)該為偶數(shù)");
}
byte[] out = new byte[len >> 1];
for (int i = 0, j = 0; j < len; i++) {
int f = toDigit(data[j], j) << 4;
j++;
f |= toDigit(data[j], j);
j++;
out[i] = (byte) (f & 0xFF);
}
return out;
}
protected static int toDigit(char ch, int index) {
int digit = Character.digit(ch, 16);
if (digit == -1) {
throw new RuntimeException("Illegal hexadecimal character " + ch + " at index " + index);
}
return digit;
}
public static String toHexString(byte[] bs) {
return new String(encodeHex(bs));
}
public static String hexString2Bytes(String hex) {
return new String(decodeHex(hex.toCharArray()));
}
public static byte[] chars2Bytes(char[] bs) {
return decodeHex(bs);
}
public static void main(String[] args) {
String s = "abc你好";
String hex = toHexString(s.getBytes());
String decode = hexString2Bytes(hex);
System.out.println("原字符串:" + s);
System.out.println("十六進(jìn)制字符串:" + hex);
System.out.println("還原:" + decode);
}
}