关于JTT 808数据协议这一块网上资料还是比较少的,而且做的人也少;所以无形给不熟悉的开发人员带来了很大的难度,所以这篇文章也就将我自己经历的一个808项目写出来分享给大家。
看这篇文章需要一定的基础知识,如下:
byte 字节、byte数组、二进制、十六进制、各种数据类型 进制的转换、byte转二进制 也就是8个bit位,scoket基础知识…
使用Netty全新封装了JTT808,JTT1078数据上传示例,点击查看
一:首先先介绍一下这个JTT808
全称:JTT 808- 道路运输车辆卫星定位系统终端通讯协议及数据格式
文档截了下开头的图片如下:
二:JTT 808中所使用到的数据类型
三:重点:数据传输一般采用的是Socket Tcp进行收发数据,首先说下他的数据格式,以及包装数据的步骤
1.消息的结构:每条消息由标识位、消息头、消息体和校验码组成,如下
2.标识位
采用 0x7E 表示,若校验码、消息头以及消息体中出现 0x7E,则要进行转义处理,转义规则定义如下:
0x7E <————> 0x7D 后紧跟一个 0x02;
0x7D<————> 0x7D 后紧跟一个 0x01。
转义处理过程如下:发送消息时: 消息封装 ——> 计算并填充校验码 ——>转义; 接收消息时: 转义还原——>验证校验码——>解析消息 举个例子:
发送一包内容为 0x30 0x7E 0x08 0x7D 0x55 的数据包则经过转义如下:0x30 0x7D 0x02 0x08 0x7D 0x01 0x55
3.消息头数据格式如下
消息体属性格式结构图 如图:3.消息体
消息体就需要根据你实际发送什么数据给服务器,不同的数据消息Id都不一样;详情可以查看文档,后面会通过一个上传经纬度示例来说明。3.校验码
校验码指从消息头开始(也就是消息头和消息体),同后一字节异或,直到校验码前一个字节,占用一个字节。
talk is nonsense, show me the code!
/*** BCC 校验算法** @param data* @return 十六进制*/public static String getBCC(byte[] data) {String ret = "";byte BCC[] = new byte[1];for (int i = 0; i < data.length; i++) {BCC[0] = (byte) (BCC[0] ^ data[i]);}String hex = Integer.toHexString(BCC[0] & 0xFF);if (hex.length() == 1) {hex = '0' + hex;}ret += hex.toUpperCase();return ret;
四:连接808服务器的步骤:首先使用socket连接服务器——>发送注册数据包——>发送鉴权数据包——>鉴权通过就可以开启心跳保持链接了
五:说了那么多废话,该回归正题了;关于连接808服务器这块就简单贴下代码吧(也就是个Tcp通信而已)
连接服务器Socket socket = new Socket(IP, PORT);socket.setSoTimeout(5000);OutputStream out = socket.getOutputStream();InputStream in = socket.getInputStream();DataInputStream dis = new DataInputStream(in);String s = "808服务器: " + IP + ":" + PORT + " 连接成功!";
发送数据,后文所说的发送数据都是通过如下代码发送的
private void sendMsg(byte[] msg){OutputStream out = socket.getOutputStream();out.write(msg);out.flush();}
1.现在服务器连上了,那第一步就需要注册终端设备了;我们首先构建消息体,再去构建消息头(因为消息头中需要知道消息体的数据长度)
注册的消息体,查看文档8.5章节
:消息ID:0x0100,终端注册消息体数据格式见如下表构建消息体,如下代码,对于取不到的数据 我这里都是以“0”补齐,你可以根据你对接的平台结合实际情况处理/*** 终端注册* @return*/public static byte[] register() {//省域 ID int转2个字节的bytesbyte[] o = BitOperator.numToByteArray(0, 2);//省域 市县域 ID int转2个字节的bytesbyte[] t = BitOperator.numToByteArray(0, 2);//制造商 IDbyte[] th = "TYUAN".getBytes();//终端型号byte[] f = "U47DPZAMSWAAHQCQ0000".getBytes();//终端 IDbyte[] fi = "LMY74DT".getBytes();//车牌颜色 车辆标识byte[] s = {0, 0};//合并多个数组成一个数组return ByteUtil.byteMergerAll(o, t, th, f, fi, s);}
2.消息体构建好了,现在最难的就是构建消息头了下面来贴代码了:
/*** 包装808数据** @param msgId 消息id* @param phone 终端手机号* @param msgBody 消息体* @return*/public static byte[] generate808(int msgId, String phone, byte[] msgBody) {//=========================标识位==================================//byte[] flag = new byte[]{0x7E};//=========================消息头==================================////[0,1]消息Idbyte[] msgIdb = BitOperator.numToByteArray(msgId, 2);//[2,3]消息体属性byte[] msgBodyAttributes = msgBodyAttributes(msgBody.length);//[4,9]终端手机号 BCD[6](占6位)byte[] terminalPhone = BCD8421Operater.string2Bcd(phone);//[10,11]流水号byte[] flowNum = BitOperator.numToByteArray(0, 2);//[12]消息包封装项 不分包 就没有byte[] msgHeader = ByteUtil.byteMergerAll(msgIdb, msgBodyAttributes, terminalPhone, flowNum);//=========================数据合并(消息头,消息体)=====================//byte[] bytes = ByteUtil.byteMergerAll(msgHeader, msgBody);//=========================计算校验码==================================//String checkCodeHexStr = HexUtil.getBCC(bytes);byte[] checkCode = HexUtil.hexStringToByte(checkCodeHexStr);//=========================合并:消息头 消息体 校验码 得到总数据============//byte[] AllData = ByteUtil.byteMergerAll(bytes, checkCode);//=========================转义 7E和7D==================================//// 转成16进制字符串String hexStr = HexUtil.byte2HexStr(AllData);// 替换 7E和7DString replaceHexStr = hexStr.replaceAll(FLAG_7D, " 7D 01").replaceAll(FLAG_7E, " 7D 02")// 最后去除空格.replaceAll(" ", "");//替换好后 转回byte[]byte[] replaceByte = HexUtil.hexStringToByte(replaceHexStr);//=========================最终传输给服务器的数据==================================//return ByteUtil.byteMergerAll(flag, replaceByte, flag);}
生成消息体属性这一步便是808的核心了,各种数据格式的转换,拼接…
/*** 生成消息体属性** @param subpackage [13]是否分包 0:不分包 1:分包*/private static byte[] msgBodyAttributes(int msgLength, int subpackage) {byte[] length = BitOperator.numToByteArray(msgLength, 2);//[0,9]消息体长度String msgBodyLength = "" +//第一个字节最后2bit+(byte) ((length[0] >> 1) & 0x1) + (byte) ((length[0] >> 0) & 0x1)//第二个字节8bit+ (byte) ((length[1] >> 7) & 0x1) + (byte) ((length[1] >> 6) & 0x1)+ (byte) ((length[1] >> 5) & 0x1) + (byte) ((length[1] >> 4) & 0x1)+ (byte) ((length[1] >> 3) & 0x1) + (byte) ((length[1] >> 2) & 0x1)+ (byte) ((length[1] >> 1) & 0x1) + (byte) ((length[1] >> 0) & 0x1);//[10,12]数据加密方式 0 表示不加密String encryption = "000";//[13]分包String subpackageB = String.valueOf(subpackage);//[14,15]保留位String reserve = "00";String msgAttributes = reserve + subpackageB + encryption + msgBodyLength;// 消息体属性int msgBodyAttr = Integer.parseInt(msgAttributes, 2);return BitOperator.numToByteArray(msgBodyAttr, 2);}
3.说了那么多屁话,发送注册的数据包就只需要简单调用了:
byte[] register = JTT808.register();byte[] sendByte = JTT808.generate808(0x0100, "040045503457", register);sendMsg(sendByte);
六:注册说完了,接下来就该鉴权
了,也就是平台根据你注册过来的信息然后会返回鉴权码给你;然后你通过拿到的鉴权码在组装成808数据格式
发送给服务器:
返回的鉴权码,得根据你实际对接的平台来解析数据。鉴权消息体格式如下见文档8.8章节
:消息ID:0x0102, 终端鉴权消息体数据格式见如下表只需要调用如下通用的打包成808数据的方法,然后传入注册返回的鉴权码即可/*** 注册成功需要鉴权*/public void auth(byte[] bytes) {byte[] auth = JTT808.generate808(0x0102, "040045503457", bytes);sendMsg(auth);}
七: 终端心跳包的发送,这个就更简单了不需要消息体
终端心跳见文档8.3章节
:消息 ID:0x0002, 终端心跳数据消息体为空。/*** 发送心跳*/public void heartPkg() {byte[] heart = JTT808.generate808(0x0002, "040045503457", new byte[]{});sendMsg(heart);}
八:上传当前经纬度信息至808服务器
终端心跳见文档8.18章节
:消息ID:0x0200,位置信息汇报消息体由位置基本信息和位置附加信息项列表组成,消息结构图如下图所 示:这个消息体就比较复杂,不过我们这里只上传一个经纬度的话就不需要上传位置的附加信息了消息体表格太多了,这里就不截图出来了(可以在文末下载)/*** 单独上报经纬度** @param lat 纬度* @param lng 经度* @return*/public static byte[] reportLatLng(long lat, long lng) {byte[] alarm = {0, 0, 0, 0};//32 位二进制 从高到低位String radix2State = "00000000000000000000000000000010";//2进制转int 在装4个字节的bytebyte[] state = ByteUtil.int2Bytes(Integer.parseInt(radix2State, 2));//DWORD经纬度byte[] latb = ByteUtil.longToDword(lat);byte[] lngb = ByteUtil.longToDword(lng);byte[] gaoChen = {0, 0};byte[] speedb = {0, 0};byte[] orientation = {0, 0};//bcd时间byte[] bcdTime = TimeUtils.getBcdTime();//位置信息附加项return ByteUtil.byteMergerAll(alarm, state, latb, lngb, gaoChen, speedb, orientation, bcdTime);}
上报经纬度只需要调用如下方法
/*** 上传经纬度*/public synchronized void uploadLatLng() {long lat = 22581626;long lng = 113918790;byte[] bytes = JTT808.reportLatLng(lat, lng);byte[] sendByte = JTT808.generate808(0x0200, "040045503457", bytes);socket.sendMsg(sendByte);}