1.Jar包下载
jt707-sdk-1.0.0.jar 下载地址
如果需要Jar包开发源码,请与商务申请
2.集成开发说明
2.1.集成开发语言及框架说明
jt707-sdk-1.0.0.jar是基于Java语言,SpringBoot2.x框架,使用到了netty,fastjson,lombok,commons-lang3
BaseEnum:基础枚举
Constant:自定义常量
LocationData:定位实体类
Result:结果实体类
CommonUtil:公共方法类
NumberUtil:数字操作工具类
PacketUtil:用来处理预处理数据以及分包方法封装类
ParserUtil:解析方法工具类
DataParser:解析主方法
2.2.集成说明
将jt707-sdk-1.0.0.jar引入到自己的网关程序中,引入方法如下:
在pom.xml引入jar包
<dependency>
<groupId>com.jointech.sdk</groupId>
<artifactId>jt707-sdk</artifactId>
<version>1.0.0</version>
</dependency>
调用jt707-sdk-1.0.0.jar,DataParser类中receiveData()方法
receiveData()方法是重载方法
/**
* 解析Hex字符串原始数据
* @param strData 16进制字符串
* @return
*/
public static Object receiveData(String strData) {
int length=strData.length()%2>0?strData.length()/2+1:strData.length()/2;
ByteBuf msgBodyBuf = Unpooled.buffer(length);
msgBodyBuf.writeBytes(CommonUtil.hexStr2Byte(strData));
return receiveData(msgBodyBuf);
}
/**
* 解析byte[]原始数据
* @param bytes
* @return
*/
private static Object receiveData(byte[] bytes)
{
ByteBuf msgBodyBuf =Unpooled.buffer(bytes.length);
msgBodyBuf.writeBytes(bytes);
return receiveData(msgBodyBuf);
}
/**
* 解析ByteBuf原始数据
* @param in
* @return
*/
private static Object receiveData(ByteBuf in)
{
Object decoded = null;
in.markReaderIndex();
int header = in.readByte();
if (header == Constant.TEXT_MSG_HEADER) {
in.resetReaderIndex();
decoded = ParserUtil.decodeTextMessage(in);
} else if (header == Constant.BINARY_MSG_HEADER) {
in.resetReaderIndex();
decoded = ParserUtil.decodeBinaryMessage(in);
} else {
return null;
}
return JSONArray.toJSON(decoded).toString();
}
2.3.核心代码
ParserUtil:解析方法工具类
package com.jointech.sdk.jt707.utils;
import com.jointech.sdk.jt707.constants.Constant;
import com.jointech.sdk.jt707.model.LocationData;
import com.jointech.sdk.jt707.model.Result;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import org.apache.commons.lang3.StringUtils;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* <p>Description: 解析方法工具类</p>
* @author HyoJung
* @date 20210526
*/
public class ParserUtil {
/**
* 定位数据解析0x0200
* @param in
* @return
*/
public static Result decodeBinaryMessage(ByteBuf in) {
//对数据进行反转移处理
ByteBuf msg = (ByteBuf) PacketUtil.decodePacket(in);
//消息长度
int msgLen = msg.readableBytes();
//包头
msg.readByte();
//消息ID
int msgId = msg.readUnsignedShort();
//消息体属性
int msgBodyAttr = msg.readUnsignedShort();
//消息体长度
int msgBodyLen = msgBodyAttr & 0b00000011_11111111;
//是否分包
boolean multiPacket = (msgBodyAttr & 0b00100000_00000000) > 0;
//去除消息体的基础长度
int baseLen = Constant.BINARY_MSG_BASE_LENGTH;
//根据消息体长度和是否分包得出后面的包长
int ensureLen = multiPacket ? baseLen + msgBodyLen + 4 : baseLen + msgBodyLen;
if (msgLen != ensureLen) {
return null;
}
//终端号数组
byte[] terminalNumArr = new byte[6];
msg.readBytes(terminalNumArr);
//终端号(去除前面的0)
String terminalNumber = StringUtils.stripStart(ByteBufUtil.hexDump(terminalNumArr), "0");
//消息流水号
int msgFlowId = msg.readUnsignedShort();
//消息总包数
int packetTotalCount = 0;
//包序号
int packetOrder = 0;
//分包
if (multiPacket) {
packetTotalCount = msg.readShort();
packetOrder = msg.readShort();
}
//消息体
byte[] msgBodyArr = new byte[msgBodyLen];
msg.readBytes(msgBodyArr);
if(msgId==0x0200) {
//解析消息体
LocationData locationData=parseLocationBody(Unpooled.wrappedBuffer(msgBodyArr));
locationData.setIndex(msgFlowId);
locationData.setDataLength(msgBodyLen);
//校验码
int checkCode = msg.readUnsignedByte();
//包尾
msg.readByte();
//获取消息回复内容
String replyMsg= PacketUtil.replyBinaryMessage(terminalNumArr,msgFlowId);
//定义定位数据实体类
Result model = new Result();
model.setDeviceID(terminalNumber);
model.setMsgType("Location");
model.setDataBody(locationData);
model.setReplyMsg(replyMsg);
return model;
}else {
//定义定位数据实体类
Result model = new Result();
model.setDeviceID(terminalNumber);
model.setMsgType("heartbeat");
model.setDataBody(null);
return model;
}
}
/**
* 解析指令数据
* @param in 原始数据
* @return
*/
public static Result decodeTextMessage(ByteBuf in) {
//读指针设置到消息头
in.markReaderIndex();
//查找消息尾,如果未找到则继续等待下一包
int tailIndex = in.bytesBefore(Constant.TEXT_MSG_TAIL);
if (tailIndex < 0) {
in.resetReaderIndex();
return null;
}
//定义定位数据实体类
Result model = new Result();
//包头(
in.readByte();
//字段列表
List<String> itemList = new ArrayList<String>();
while (in.readableBytes() > 0) {
//查询逗号的下标截取数据
int index = in.bytesBefore(Constant.TEXT_MSG_SPLITER);
int itemLen = index > 0 ? index : in.readableBytes() - 1;
byte[] byteArr = new byte[itemLen];
in.readBytes(byteArr);
in.readByte();
itemList.add(new String(byteArr));
}
String msgType = "";
if (itemList.size() >= 5) {
msgType = itemList.get(3) + itemList.get(4);
}
Object dataBody=null;
if(itemList.size()>0){
dataBody="(";
for(String item :itemList) {
dataBody+=item+",";
}
dataBody=CommonUtil.trimEnd(dataBody.toString(),",");
dataBody += ")";
}
String replyMsg="";
if(msgType.equals("BASE2")&&itemList.get(5).toUpperCase().equals("TIME")) {
replyMsg=PacketUtil.replyBASE2Message(itemList);
}
model.setDeviceID(itemList.get(0));
model.setMsgType(msgType);
model.setDataBody(dataBody);
model.setReplyMsg(replyMsg);
return model;
}
/**
* 解析定位消息体
* @param msgBodyBuf
* @return
*/
private static LocationData parseLocationBody(ByteBuf msgBodyBuf){
//报警标志
long alarmFlag = msgBodyBuf.readUnsignedInt();
//状态
long status = msgBodyBuf.readUnsignedInt();
//纬度
double lat = NumberUtil.multiply(msgBodyBuf.readUnsignedInt(), NumberUtil.COORDINATE_PRECISION);
//经度
double lon = NumberUtil.multiply(msgBodyBuf.readUnsignedInt(), NumberUtil.COORDINATE_PRECISION);
//海拔高度,单位为米
int altitude = msgBodyBuf.readShort();
//速度
double speed = NumberUtil.multiply(msgBodyBuf.readUnsignedShort(), NumberUtil.ONE_PRECISION);
//方向
int direction = msgBodyBuf.readShort();
//定位时间
byte[] timeArr = new byte[6];
msgBodyBuf.readBytes(timeArr);
String bcdTimeStr = ByteBufUtil.hexDump(timeArr);
ZonedDateTime gpsZonedDateTime = CommonUtil.parseBcdTime(bcdTimeStr);
//根据状态位的值判断是否南纬和西经
if (NumberUtil.getBitValue(status, 2) == 1) {
lat = -lat;
}
if (NumberUtil.getBitValue(status, 3) == 1) {
lon = -lon;
}
//定位状态
int locationType=NumberUtil.getBitValue(status, 18);
if(locationType==0)
{
locationType = NumberUtil.getBitValue(status, 1);
}
if(locationType==0)
{
locationType = NumberUtil.getBitValue(status, 6) > 0 ? 2 : 0;
}
LocationData locationData=new LocationData();
locationData.setGpsTime(gpsZonedDateTime.toString());
locationData.setLatitude(lat);
locationData.setLongitude(lon);
locationData.setLocationType(locationType);
locationData.setSpeed((int)speed);
locationData.setDirection(direction);
//处理附加信息
if (msgBodyBuf.readableBytes() > 0) {
parseExtraInfo(msgBodyBuf, locationData);
}
return locationData;
}
/**
* 解析附加信息
*
* @param msgBody
* @param location
*/
private static void parseExtraInfo(ByteBuf msgBody, LocationData location) {
ByteBuf extraInfoBuf = null;
while (msgBody.readableBytes() > 1) {
int extraInfoId = msgBody.readUnsignedByte();
int extraInfoLen = msgBody.readUnsignedByte();
if (msgBody.readableBytes() < extraInfoLen) {
break;
}
extraInfoBuf = msgBody.readSlice(extraInfoLen);
switch (extraInfoId) {
case 0x0F:
//解析温度数据
double temperature = -1000.0;
temperature = parseTemperature(extraInfoBuf.readShort());
location.setTemperature((int)temperature);
break;
//无线通信网络信号强度
case 0x30:
int fCellSignal=extraInfoBuf.readByte();
location.setGSMSignal(fCellSignal);
break;
//卫星数
case 0x31:
int fGPSSignal=extraInfoBuf.readByte();
location.setGpsSignal(fGPSSignal);
break;
//电池电量百分比
case 0xD4:
int fBattery=extraInfoBuf.readUnsignedByte();
location.setBattery(fBattery);
break;
//电池电压
case 0xD5:
int fVoltage=extraInfoBuf.readUnsignedShort();
location.setBattery(fVoltage*0.01);
break;
case 0xDA:
//剪绳次数
int fTimes =extraInfoBuf.readUnsignedShort();
//状态位
int status =extraInfoBuf.readUnsignedByte();
//锁状态
int fLockStatus=(status&0b0000_0001)==1?0:1;
//运动状态
int fRunStatus=(status&0b0000_0010)>0?1:0;
//Sim卡状态
int fSimStatus=(status&0b0000_0100)>0?1:0;
//唤醒源
int fWakeSource=((status>>3)&0b0111);
location.setLockStatus(fLockStatus);
location.setLockRope(fLockStatus);
location.setUnLockTime(fTimes);
location.setRunStatus(fRunStatus);
location.setSimStatus(fSimStatus);
location.setAwaken(fWakeSource);
break;
case 0xDB:
//定位数据发送条数
int sendCount=extraInfoBuf.readUnsignedShort();
location.setSendDataCount(sendCount);
break;
case 0xDC:
//Debug上网状态
byte[] debugArr = new byte[4];
extraInfoBuf.readBytes(debugArr);
String strDebug=ByteBufUtil.hexDump(debugArr);
break;
case 0xF8:
//温度值
int temp=extraInfoBuf.readUnsignedShort();
if(temp==0xffff){
location.setTemperature(-1000);
}else {
temp=(temp / 10)-50;
location.setTemperature(temp);
}
break;
case 0xF9:
//版本号
int version=extraInfoBuf.readUnsignedShort();
break;
case 0xFD:
//小区码信息
int mcc=extraInfoBuf.readUnsignedShort();
location.setMCC(mcc);
int mnc=extraInfoBuf.readUnsignedByte();
location.setMNC(mnc);
long cellId=extraInfoBuf.readUnsignedInt();
location.setCELLID((int)cellId);
int lac=extraInfoBuf.readUnsignedShort();
location.setLAC(lac);
break;
case 0xFE:
long mileage = extraInfoBuf.readUnsignedInt();
location.setMileage(mileage);
break;
default:
ByteBufUtil.hexDump(extraInfoBuf);
break;
}
}
}
/**
* 温度解析
* @param temperatureInt
* @return
*/
private static double parseTemperature(int temperatureInt) {
if (temperatureInt == 0xFFFF) {
return -1000;
}
double temperature = ((short) (temperatureInt << 4) >> 4) * 0.1;
if ((temperatureInt >> 12) > 0) {
temperature = -temperature;
}
return temperature;
}
}
PacketUtil:用来处理预处理数据以及恢复方法封装类
package com.jointech.sdk.jt707.utils;
import com.jointech.sdk.jt707.constants.Constant;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.util.ReferenceCountUtil;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Random;
/**
* 解析包预处理(进行反转义)
* @author HyoJung
*/
public class PacketUtil {
/**
* 解析消息包
*
* @param in
* @return
*/
public static Object decodePacket(ByteBuf in) {
//可读长度不能小于基本长度
if (in.readableBytes() < Constant.BINARY_MSG_BASE_LENGTH) {
return null;
}
//防止非法码流攻击,数据太大为异常数据
if (in.readableBytes() > Constant.BINARY_MSG_MAX_LENGTH) {
in.skipBytes(in.readableBytes());
return null;
}
//查找消息尾,如果未找到则继续等待下一包
in.readByte();
int tailIndex = in.bytesBefore(Constant.BINARY_MSG_HEADER);
if (tailIndex < 0) {
in.resetReaderIndex();
return null;
}
int bodyLen = tailIndex;
//创建ByteBuf存放反转义后的数据
ByteBuf frame = ByteBufAllocator.DEFAULT.heapBuffer(bodyLen + 2);
frame.writeByte(Constant.BINARY_MSG_HEADER);
//消息头尾之间的数据进行反转义
unescape(in, frame, bodyLen);
in.readByte();
frame.writeByte(Constant.BINARY_MSG_HEADER);
//反转义后的长度不能小于基本长度
if (frame.readableBytes() < Constant.BINARY_MSG_BASE_LENGTH) {
ReferenceCountUtil.release(frame);
return null;
}
return frame;
}
/**
* 消息头、消息体、校验码中0x7D 0x02反转义为0x7E,0x7D 0x01反转义为0x7D
*
* @param in
* @param frame
* @param bodyLen
*/
public static void unescape(ByteBuf in, ByteBuf frame, int bodyLen) {
int i = 0;
while (i < bodyLen) {
int b = in.readUnsignedByte();
if (b == 0x7D) {
int nextByte = in.readUnsignedByte();
if (nextByte == 0x01) {
frame.writeByte(0x7D);
} else if (nextByte == 0x02) {
frame.writeByte(0x7E);
} else {
//异常数据
frame.writeByte(b);
frame.writeByte(nextByte);
}
i += 2;
} else {
frame.writeByte(b);
i++;
}
}
}
/**
* 消息头、消息体、校验码中0x7E转义为0x7D 0x02,0x7D转义为0x7D 0x01
*
* @param out
* @param bodyBuf
*/
public static void escape(ByteBuf out, ByteBuf bodyBuf) {
while (bodyBuf.readableBytes() > 0) {
int b = bodyBuf.readUnsignedByte();
if (b == 0x7E) {
out.writeShort(0x7D02);
} else if (b == 0x7D) {
out.writeShort(0x7D01);
} else {
out.writeByte(b);
}
}
}
/**
* 回复内容
* @param terminalNumArr
* @param msgFlowId
* @return
*/
public static String replyBinaryMessage(byte[] terminalNumArr,int msgFlowId) {
//去除包头包尾的长度
int contentLen = Constant.BINARY_MSG_BASE_LENGTH + 4;
ByteBuf bodyBuf = ByteBufAllocator.DEFAULT.heapBuffer(contentLen-2);
ByteBuf replyBuf = ByteBufAllocator.DEFAULT.heapBuffer(25);
try {
//消息ID
bodyBuf.writeShort(0x8001);
//数据长度
bodyBuf.writeShort(0x0005);
//终端ID
bodyBuf.writeBytes(terminalNumArr);
Random random = new Random();
//生成1-65534内的随机数
int index = random.nextInt() * (65534 - 1 + 1) + 1;
//当前消息流水号
bodyBuf.writeShort(index);
//回复消息流水号
bodyBuf.writeShort(msgFlowId);
//应答的消息ID
bodyBuf.writeShort(0x0200);
//应答结果
bodyBuf.writeByte(0x00);
//校验码
int checkCode = CommonUtil.xor(bodyBuf);
bodyBuf.writeByte(checkCode);
//包头
replyBuf.writeByte(Constant.BINARY_MSG_HEADER);
//读指针重置到起始位置
bodyBuf.readerIndex(0);
//转义
PacketUtil.escape(replyBuf, bodyBuf);
//包尾
replyBuf.writeByte(Constant.BINARY_MSG_HEADER);
return ByteBufUtil.hexDump(replyBuf);
} catch (Exception e) {
ReferenceCountUtil.release(replyBuf);
return "";
}
}
/**
* 授时指令回复
* @param itemList
*/
public static String replyBASE2Message(List<String> itemList) {
try {
//设置日期格式
ZonedDateTime currentDateTime = ZonedDateTime.now(ZoneOffset.UTC);
String strBase2Reply = String.format("(%s,%s,%s,%s,%s,%s)", itemList.get(0), itemList.get(1)
, itemList.get(2), itemList.get(3), itemList.get(4), DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(currentDateTime));
return strBase2Reply;
}catch (Exception e) {
return "";
}
}
}
NumberUtil:数字操作工具类
package com.jointech.sdk.jt707.utils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
* 数字工具类
* @author HyoJung
* @date 20210526
*/
public class NumberUtil {
/**
* 坐标精度
*/
public static final BigDecimal COORDINATE_PRECISION = new BigDecimal("0.000001");
/**
* 坐标因数
*/
public static final BigDecimal COORDINATE_FACTOR = new BigDecimal("1000000");
/**
* 小数点一位精度
*/
public static final BigDecimal ONE_PRECISION = new BigDecimal("0.1");
private NumberUtil() {
}
/**
* 格式化消息ID(转成0xXXXX)
*
* @param msgId 消息ID
* @return 格式化字符串
*/
public static String formatMessageId(int msgId) {
return String.format("0x%04x", msgId);
}
/**
* 格式化短数字
*
* @param num 数字
* @return 格式化字符串
*/
public static String formatShortNum(int num) {
return String.format("0x%02x", num);
}
/**
* 转4位的十六进制字符串
*
* @param num 数字
* @return 格式化字符串
*/
public static String hexStr(int num) {
return String.format("%04x", num).toUpperCase();
}
/**
* 解析short类型的值,获取值为1的位数
*
* @param number
* @return
*/
public static List<Integer> parseShortBits(int number) {
List<Integer> bits = new ArrayList<>();
for (int i = 0; i < 16; i++) {
if (getBitValue(number, i) == 1) {
bits.add(i);
}
}
return bits;
}
/**
* 解析int类型的值,获取值为1的位数
*
* @param number
* @return
*/
public static List<Integer> parseIntegerBits(long number) {
List<Integer> bits = new ArrayList<>();
for (int i = 0; i < 32; i++) {
if (getBitValue(number, i) == 1) {
bits.add(i);
}
}
return bits;
}
/**
* 获取二进制第index位的值
*
* @param number
* @param index
* @return
*/
public static int getBitValue(long number, int index) {
return (number & (1 << index)) > 0 ? 1 : 0;
}
/**
* bit列表转int
*
* @param bits
* @param len
* @return
*/
public static int bitsToInt(List<Integer> bits, int len) {
if (bits == null || bits.isEmpty()) {
return 0;
}
char[] chars = new char[len];
for (int i = 0; i < len; i++) {
char value = bits.contains(i) ? '1' : '0';
chars[len - 1 - i] = value;
}
int result = Integer.parseInt(new String(chars), 2);
return result;
}
/**
* bit列表转long
*
* @param bits
* @param len
* @return
*/
public static long bitsToLong(List<Integer> bits, int len) {
if (bits == null || bits.isEmpty()) {
return 0L;
}
char[] chars = new char[len];
for (int i = 0; i < len; i++) {
char value = bits.contains(i) ? '1' : '0';
chars[len - 1 - i] = value;
}
long result = Long.parseLong(new String(chars), 2);
return result;
}
/**
* BigDecimal乘法
*
* @param longNum
* @param precision
* @return
*/
public static double multiply(long longNum, BigDecimal precision) {
return new BigDecimal(String.valueOf(longNum)).multiply(precision).doubleValue();
}
/**
* BigDecimal乘法
*
* @param longNum
* @param precision
* @return
*/
public static double multiply(int longNum, BigDecimal precision) {
return new BigDecimal(String.valueOf(longNum)).multiply(precision).doubleValue();
}
}
CommonUtil:公共方法类
package com.jointech.sdk.jt707.utils;
import io.netty.buffer.ByteBuf;
import java.nio.ByteBuffer;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
/**
* <p>Description: 用来存储一些解析中遇到的公共方法</p>
*
* @author lenny
* @version 1.0.1
* @date 20210328
*/
public class CommonUtil {
/**
* 去掉字符串最后一个字符
* @param inStr 输入的字符串
* @param suffix 需要去掉的字符
* @return
*/
public static String trimEnd(String inStr, String suffix) {
while(inStr.endsWith(suffix)){
inStr = inStr.substring(0,inStr.length()-suffix.length());
}
return inStr;
}
/**
* 16进制转byte[]
* @param hex
* @return
*/
public static byte[] hexStr2Byte(String hex) {
ByteBuffer bf = ByteBuffer.allocate(hex.length() / 2);
for (int i = 0; i < hex.length(); i++) {
String hexStr = hex.charAt(i) + "";
i++;
hexStr += hex.charAt(i);
byte b = (byte) Integer.parseInt(hexStr, 16);
bf.put(b);
}
return bf.array();
}
/**
* 转换GPS时间
*
* @param bcdTimeStr
* @return
*/
public static ZonedDateTime parseBcdTime(String bcdTimeStr) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyMMddHHmmss");
LocalDateTime localDateTime = LocalDateTime.parse(bcdTimeStr, formatter);
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneOffset.UTC);
return zonedDateTime;
}
/**
* 每个字节进行异或求值
*
* @param buf
* @return
*/
public static int xor(ByteBuf buf) {
int checksum = 0;
while (buf.readableBytes() > 0) {
checksum ^= buf.readUnsignedByte();
}
return checksum;
}
}
BaseEnum:基础枚举
package com.jointech.sdk.jt707.base;
import java.io.Serializable;
/**
* 基础枚举
* @author HyoJung
*/
public interface BaseEnum <T> extends Serializable {
T getValue();
}
Constant:自定义常量
package com.jointech.sdk.jt707.constants;
/**
* 常量定义
* @author HyoJung
* @date 20210526
*/
public class Constant {
private Constant(){}
/**
* 二进制消息包头
*/
public static final byte BINARY_MSG_HEADER = 0x7E;
/**
* 不包含消息体的基本长度
*/
public static final int BINARY_MSG_BASE_LENGTH = 15;
/**
* 消息最大长度
*/
public static final int BINARY_MSG_MAX_LENGTH = 102400;
/**
* 文本消息包头
*/
public static final byte TEXT_MSG_HEADER = '(';
/**
* 文本消息包尾
*/
public static final byte TEXT_MSG_TAIL = ')';
/**
* 文本消息分隔符
*/
public static final byte TEXT_MSG_SPLITER = ',';
}
LocationData:定位实体类
package com.jointech.sdk.jt707.model;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.io.Serializable;
/**
* <p>Description: 定位实体类</p>
*
* @author lenny
* @version 1.0.1
* @date 20210328
*/
@Data
public class LocationData implements Serializable {
/**
* 消息体
*/
@JSONField(name = "DataLength")
public int DataLength;
/**
* 定位时间
*/
@JSONField(name = "GpsTime")
public String GpsTime;
/**
* 纬度
*/
@JSONField(name = "Latitude")
public double Latitude;
/**
* 经度
*/
@JSONField(name = "Longitude")
public double Longitude;
/**
* 定位方式
*/
@JSONField(name = "LocationType")
public int LocationType;
/**
* 速度
*/
@JSONField(name = "Speed")
public int Speed;
/**
* 方向
*/
@JSONField(name = "Direction")
public int Direction;
/**
* 里程
*/
@JSONField(name = "Mileage")
public long Mileage;
/**
* GPS信号值
*/
@JSONField(name = "GpsSignal")
public int GpsSignal;
/**
* GSM信号质量
*/
@JSONField(name = "GSMSignal")
public int GSMSignal;
/**
* 电量值
*/
@JSONField(name = "Battery")
public double Battery;
/**
* 电压
*/
@JSONField(name = "Voltage")
public int Voltage;
/**
* 锁状态
*/
@JSONField(name = "LockStatus")
public int LockStatus;
/**
* 锁绳状态
*/
@JSONField(name = "LockRope")
public int LockRope;
/**
* 剪绳次数
*/
@JSONField(name = "UnLockTime")
public int UnLockTime;
/**
* 运动状态
*/
@JSONField(name = "RunStatus")
public int RunStatus;
/**
* Sim卡类型
*/
@JSONField(name = "SimStatus")
public int SimStatus;
/**
* 定位数据发送条数
*/
@JSONField(name = "SendDataCount")
public int SendDataCount;
/**
* MCC
*/
@JSONField(name = "MCC")
public int MCC;
/**
* MNC
*/
@JSONField(name = "MNC")
public int MNC;
/**
* LAC
*/
@JSONField(name = "LAC")
public int LAC;
/**
* CELLID
*/
@JSONField(name = "CELLID")
public long CELLID;
/**
* Awaken
*/
@JSONField(name = "Awaken")
public int Awaken;
/**
* 流水号
*/
@JSONField(name = "Index")
public int Index;
/**
* 温度值
*/
@JSONField(name = "Temperature")
public int Temperature=-1000;
}
Result:结果实体类
package com.jointech.sdk.jt707.model;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.io.Serializable;
/**
* 结果实体类
* @author HyoJung
* @date 20210526
*/
@Data
public class Result implements Serializable {
@JSONField(name = "DeviceID")
private String DeviceID;
@JSONField(name = "MsgType")
private String MsgType;
@JSONField(name = "DataBody")
private Object DataBody;
@JSONField(name = "ReplyMsg")
private String ReplyMsg;
}
2.4.返回消息及说明
(1)心跳数据
原始数据:
7E00020000770191203915FFF2E47E
返回消息:
{"DeviceID":"770191203915","MsgType":"heartbeat"}
返回消息描述
{"DeviceID":设备ID,"MsgType":消息类型(heartbeat:心跳)}
(2)定位数据
原始数据:
7E02000047770191203915000D000000000104100201588F7D0206CA3F580000001C000121072201060230011F310106D40164D5020050DA03000105DB02000CDC0400000000FD0901CC00000010922866F80203421B7E
返回消息:
{
"DeviceID": "770191203915",
"DataBody": {
"GpsTime": "2021-07-22T01:06:02Z",
"Temperature": 33,
"MNC": 0,
"UnLockTime": 1,
"RunStatus": 0,
"Index": 13,
"Latitude": 22.581118,
"Awaken": 0,
"SimStatus": 1,
"Direction": 1,
"Battery": 100,
"GpsSignal": 6,
"Voltage": 0.8,
"Speed": 2,
"LockStatus": 0,
"Mileage": 0,
"MCC": 460,
"Longitude": 113.917784,
"LAC": 10342,
"DataLength": 71,
"CELLID": 4242,
"LockRope": 0,
"LocationType": 1,
"SendDataCount": 12,
"GSMSignal": 31
},
"ReplyMsg": "7e80010005770191203915ee25000d020000ab7e",
"MsgType": "Location"
}
返回消息描述
{
"DeviceID": 终端ID,
"DataBody": {
"Index": 数据流水号,
"DataLength": 数据长度,字节数,
"GpsTime": 定位数据时间(UTC时间),
"Latitude": 纬度(WGS84),
"Longitude": 经度(WGS84),
"Temperature": 温度值,33标识的当前温度是33℃,
"UnLockTime": 剪绳开锁次数,
"RunStatus": 运动状态(1:运动;0:静止),
"Awaken": 0:RTC上报 1:剪绳上报,2:插绳上报 3:开盖上报 4:关盖上报 5:充电/配置/模拟剪绳上报,
"SimStatus": SIM卡类型 0:eSIM卡;1:Micro SIM卡槽,
"Direction": 正北为0,顺时针0-359,
"Battery": 电量值(0~100%),
"GpsSignal": GPS当前接收到的卫星颗数,
"Voltage": 电压值,单位V,
"Speed": 速度,单位km/h,
"LockStatus": 锁状态(0:关;1:开),
"LockRope": 锁绳状态(0:插入;1:拔出),
"Mileage": 里程值,单位km,
"MCC": 小区码信息MCC,
"MNC": 小区码信息MNC,
"LAC": 小区码信息LAC,
"CELLID": 小区码信息CI,
"LocationType": 定位方式(0:不定位;1:GPS定位;2:基站定位),
"SendDataCount": 发送数据的条数,
"GSMSignal": GSM信号值
},
"ReplyMsg": 需要回复终端的内容(为空则表示不需要回复),
"MsgType": 数据类型(Location:定位数据)
}
(3)指令数据解析
原始数据:
283737303139313230333930362c312c3030312c424153452c362c352c33302c3529
返回消息:
{
"DeviceID": "770191203906",
"DataBody": "(770191203906,1,001,BASE,6,5,30,5)",
"ReplyMsg": "",
"MsgType": "BASE6"
}
返回消息描述
{
"DeviceID": 设备ID,
"DataBody": 消息体内容,
"ReplyMsg": 回复设备的消息(为空则表示不需要回复),
"MsgType": 指令类型
}
3.指令消息
3.1.查询终端基本信息
消息体内容(DataBody):
(700160818000,1,001,BASE,1,20150418_G300,0,BeiHuan,1137B03SIM900M64_ST_MMS, 89860042191130272549,12345678965433224653,012207005620932,460,00,4243,6877)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | BASE,1 | 指令类型(BASE1) |
3 | 20150418_G300 | 当前设备的版本号。 |
4 | 0,BeiHuan | 前面的0表示英文,1表示其它语言的Unicode码,ASCII码表示,如:报警62A5 8B66上传是8个字节。此处为英语,名称为BeiHuan |
5 | 1137B03SIM900M64_ST_MMS | GSM模块版本。 |
6 | 89860042191130272549 | eSIM卡的CCID。 |
7 | 460,00,4243,6877 | 网络信息:460 移动国家代码,即MCC信息,此处表示中国;00电信运营商网络号码,MNC信息(中国移动为00,中国联通为01);4243 基站编号CELL ID信息;6877 位置区域码LAC信息。CELL ID与LAC为十六进制,即4243转为十进制为16963。 |
3.2.授时(同步GMT时间)
消息体内容(DataBody):
(700160818000,1,001,BASE,2,20111018123820)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | BASE,2 | 指令类型(BASE2) |
3 | 20111018123820 | 设置的时间(yyyyMMddHHmmss) |
3.3.恢复终端出厂默认值
消息体内容(DataBody):
(700160818000,1,001,BASE,4,1)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | BASE,4 | 指令类型(BASE4) |
3 | 1 | 1表示全部信息恢复为出厂默认值;2表示除通信主从IP外,其它信息全部恢复为出厂默认值;3表示除通信主从IP、VIP号码外,其它信息全部恢复为出厂默认值 |
3.4.查询/设置上传间隔和休眠定时唤醒间隔
消息体内容(DataBody):
(700160818000,1,001,BASE,6,60,30,30)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | BASE,6 | 指令类型(BASE6) |
3 | 60 | 上传间隔(静止态),以分钟为单位。此值默认为30分钟。最小值为30,最大值为1440(24小时)。 |
4 | 30 | 固定为30,未使用 |
5 | 30 | 上传间隔(运动态),以分钟为单位。此值默认为30分钟。最小值为30,最大值为1440(24小时)。 |
3.5.时差设置
消息体内容(DataBody):
(2310915002,1,001,BASE,8, 480)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | BASE,8 | 指令类型(BASE8) |
3 | 480 | 时差值,此处单位为分钟 |
3.6.查询/设置主从IP地址与端口号、APN及用户名与密码等参数
消息体内容(DataBody):
(700160818000,1,001,BASE,10,211.154.112.98,1088,211.154.112.98,1088,CMNET,abc,123456)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | BASE,10 | 指令类型(BASE10) |
3 | 211.154.112.98 | 主IP(域名) |
4 | 1088 | 主端口 |
5 | 211.154.112.98 | 从IP(域名) |
6 | 1088 | 从端口 |
7 | CMNET | APN名称 |
8 | abc | 用户名 |
9 | 123456 | 密码 |
3.7.查询/设置关闭基站定位功能
消息体内容(DataBody):
(700160818000,1,001,BASE,29, 1)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | BASE,29 | 指令类型(BASE29) |
3 | 1 | 1表示开启基站定位功能,0关闭基站定位功能。 |
3.8.查询/设置/删除设备工作闹钟
消息体内容(DataBody):
(700160818000,1,001, BASE,32,1,8:9:10,12:13:14)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | BASE,32 | 指令类型(BASE32) |
3 | 1 | 1则表明当前闹钟有效,0表明当前闹钟无效。 |
4 | 8:9:10 | 8: 设置一个设备闹钟的小时;9: 设置一个设备闹钟的分钟;10:设置一个设备闹钟的秒钟 |
5 | 12:13:14 | 12: 设置一个设备闹钟的小时;13: 设置一个设备闹钟的分钟;14:设置一个设备闹钟的秒钟 |
3.9.查询/设置追踪模式
消息体内容(DataBody):
(700160818000,1,001,BASE,33,1,5,43200)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | BASE,33 | 指令类型(BASE33) |
3 | 1 | 1表示有效追踪模式,0表示失效追踪模式 |
4 | 5 | 5表示追踪上传间隔,分钟为单位 |
5 | 43200 | 43200表示追踪时间段,分钟为单位 |
3.10.查询/设置当前simCard
消息体内容(DataBody)示例1:
(700160818000,1,001,BASE,36,2)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | BASE,36 | 指令类型(BASE36) |
3 | 2 | 表示当前卡为卡2 |
消息体内容(DataBody)示例2:
(700160818000,1,001,BASE,36,454030220234116,89852031600002341160,454030220234117,89852031600002341161)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | BASE,36 | 指令类型(BASE36) |
3 | 454030220234116 | 当前卡1的IMSI |
4 | 89852031600002341160 | 当前卡1的ICCID |
5 | 454030220234117 | 当前卡2的IMSI |
6 | 89852031600002341161 | 当前卡2的ICCID |
3.11.查询/设置进行 OTA的FTP服务器的IP地址(或域名)和端口号
消息体内容(DataBody):
(700160818000,1,001,BASE,37,1,ftpupdate.jointcontrols.cn,2021)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | BASE,37 | 指令类型(BASE37) |
3 | 1 | 1表示设置,0表示查询(如果查询后面可以不接地址和断口号) |
4 | ftpupdate.jointcontrols.cn | OTA的FTP服务器的IP地址 |
5 | 2021 | OTA的FTP服务器的端口 |
3.12.通知设备无线模块升级
消息体内容(DataBody):
(2011091502,1,001,DEBUG,13,190508,180405)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | DEBUG,13 | 指令类型(DEBUG13) |
3 | 190508 | 请求升级的软件日期版本 |
4 | 180405 | 软件当前正在运行的软件日期版本 |
3.13.通知设备MCU升级
消息体内容(DataBody):
(700160818000,1,001,OTA,1,1,20120102)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | OTA,1 | 指令类型(OTA1) |
3 | 1 | 1:表示同意升级,只有当设备的当前版本号低于要升级的版本号的时候才返回1.若设备返回0表示拒绝升级,即设备当前的Firmware版本和升级的Firmware版本相同或者更高。服务器只有在收到同意升级以后才发送第一个Firmware数据包. |
4 | 20120102 | 当前设备的Firmware版本号. |
3.14.发送Firmware数据包
消息体内容(DataBody):
(700160818000,1,001,OTA,2,0,200,100)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | OTA,2 | 指令类型(OTA2) |
3 | 0 | 1表示保存成功,0表示保存失败,如果失败重发. |
4 | 200 | 接收到的Firmware数据包序号. |
5 | 100 | 下一条需要发送的数据包序号. |
3.15.取消Firmware升级
消息体内容(DataBody):
(700160818000,1,001,OTA,3,1,20120222)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | OTA,3 | 指令类型(OTA3) |
3 | 1 | 1表示取消成功,0表示取消失败。如果该指令的版本号和正在升级的固件版本号不符的话就可能取消失败. |
4 | 20120222 | 设备正在升级的版本号 |
3.16.完成Firmware传输
消息体内容(DataBody):
(700160818000,1,001,OTA,4,1)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | OTA,4 | 指令类型(OTA4) |
3 | 1 | 该参数可以为1或者为0,1表示接受该指令,并根据指令执行。0表示:接受的数据不完全,拒绝升级. |
3.17.查询发送下条Firmware数据包序号
消息体内容(DataBody):
(700160818000,1,001,OTA,5,1,200)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | OTA,5 | 指令类型(OTA5) |
3 | 1 | 1:代表目前正在接收的固件和查询的相同,0表示目前接收的固件和查询的不同. |
4 | 200 | 如果查询的固件版本号比设备的版本号新的话,该参数表示下一条需要发送的序号。如果查询的版本号和正在升级的版本号不同的话,则该参数为1.即代表需要重新下载现在查询的固件. |
3.18.查询当前stm32固件的版本号
消息体内容(DataBody):
(700160818000,1,001,OTA,6,JT707A_V103R001)
消息体内容描述:
序号 | 示例 | 说明 |
---|---|---|
1 | 700160818000 | 设备ID |
2 | OTA,6 | 指令类型(OTA6) |
3 | JT707A_V103R001 | 固件版本号 |