Android JTT 808-2011 道路运输车辆卫星定位系统终端通讯协议及数据格式

关于JTT 808数据协议这一块网上资料还是比较少的,而且做的人也少;所以无形给不熟悉的开发人员带来了很大的难度,所以这篇文章也就将我自己经历的一个808项目写出来分享给大家。

看这篇文章需要一定的基础知识,如下:

byte 字节、byte数组、二进制、十六进制、各种数据类型 进制的转换、byte转二进制 也就是8个bit位,scoket基础知识…

使用Netty全新封装了JTT808,JTT1078数据上传示例,点击查看

在这里插入图片描述

一:首先先介绍一下这个JTT808

全称:JTT 808-2011 道路运输车辆卫星定位系统终端通讯协议及数据格式
文档截了下开头的图片如下:
在这里插入图片描述

二:JTT 808中所使用到的数据类型

数据类型 描述及要求
BYTE 无符号单字节整型(字节,8 位)
WORD 无符号双字节整型(字,16 位)
DWORD 无符号四字节整型(双字,32 位)
BYTE[n] n字节
BCD[n] 8421 码,n 字节
STRING GBK 编码,若无数据,置空

三:重点:数据传输一般采用的是Socket Tcp进行收发数据,首先说下他的数据格式,以及包装数据的步骤

1.消息的结构:每条消息由标识位、消息头、消息体和校验码组成,如下

标识位 消息头 消息体 校验吗 标识位

2.标识位

采用 0x7E 表示,若校验码、消息头以及消息体中出现 0x7E,则要进行转义处理,转义规则定义如下:

0x7E <————> 0x7D 后紧跟一个 0x02;

0x7D<————> 0x7D 后紧跟一个 0x01。

  • 转义处理过程如下:
  • 发送消息时:
    • 消息封装 ——> 计算并填充校验码 ——>转义;
  • 接收消息时:
    • 转义还原——>验证校验码——>解析消息
  • 举个例子:
发送一包内容为 0x30 0x7E 0x08 0x7D 0x55 的数据包
则经过封装如下:0x7E 0x30 7D 0x02 0x08 0x7D 0x01 0x55 0x7E

3.消息头数据格式如下

起始字节 字段 数据类型 描述及要求
0 消息ID WORD
2 消息体属性 WORD 消息体属性格式结构图见 下图
4 终端手机号 BCD[6] 根据安装后终端自身的手机号转换
手机号不足 12 位 则在前补充数字,
大陆手机号补充数字 0港澳 台
则根据其区号进行位数补充。
10 消息流水号 WORD 按发送顺序从 0 开始循环累加
12 消息包封装项 如果消息体属性中相关标识位确定消息分包处理,
则该项有内容,否则无该项
  • 消息体属性格式结构图 如图:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
保留 分包 数据加密方式 消息体长度

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 省域 ID WORD 标示终端安装车辆所在的省域,0 保留,由平台取默
认值。省域 ID 采用 GB/T 2260 中规定的行政区划代
码六位中前两位。
2 市县域 ID WORD 标示终端安装车辆所在的市域和县域,0 保留,由平
台取默认值。市县域 ID 采用 GB/T 2260 中规定的行
政区划代码六位中后四位。
4 制造商 ID BYTE[5] 5 个字节,终端制造商编码
9 终端型号 BYTE[20] 20 个字节,此终端型号由制造商自行定义,位数不
足时,后补“0X00”。
29 终端 ID BYTE[7] 7 个字节,由大写字母和数字组成,此终端 ID 由制
造商自行定义,位数不足时,后补“0X00”。
36 车牌颜色 BYTE 车牌颜色,按照 JT/T415-2006 的 5.4.12。
未上牌时,取值为 0。
37 车辆标识 STRING 车牌颜色为 0 时,表示车辆 VIN;
否则,表示公安交通管理部门颁发的机动车号牌。
  • 构建消息体,如下代码,对于取不到的数据 我这里都是以“0”补齐,你可以根据你对接的平台结合实际情况处理
    	/**
     * 终端注册
     * @return
     */
    public static byte[] register() {
        //省域 ID int转2个字节的bytes
        byte[] o = BitOperator.numToByteArray(0, 2);
        //省域 市县域 ID int转2个字节的bytes
        byte[] t = BitOperator.numToByteArray(0, 2);
        //制造商 ID
        byte[] th = "TYUAN".getBytes();
        //终端型号
        byte[] f = "U47DPZAMSWAAHQCQ0000".getBytes();
        //终端 ID
        byte[] 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]消息Id
    byte[] 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和7D
    String 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, 终端鉴权消息体数据格式见如下表
起始字节 字段 数据类型 描述及要求
0 鉴权码 STRING 终端重连后上报鉴权码
  • 只需要调用如下通用的打包成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个字节的byte
        byte[] 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);
    }

JTT808PDF文档下载

发布了140 篇原创文章 · 获赞 546 · 访问量 54万+

猜你喜欢

转载自blog.csdn.net/a_zhon/article/details/84948355