SkillAgentSearch skills...

JT808

JT/T808 protocol,JT808 protocol, GB808 protocol (support 2011, 2013, 2019 version) JT/T808协议、JT808协议、GB808协议(支持2011、2013、2019版本)

Install / Use

/learn @SmallChi/JT808

README

JT/T808 协议

MIT Licence

<p> <span>中文</span> | <a href="README.en.md">English</a> </p>

前提条件

  1. 掌握进制转换:二进制转十六进制;
  2. 掌握 BCD 编码、Hex 编码;
  3. 掌握各种位移、异或;
  4. 掌握常用反射;
  5. 掌握 JObject 的用法;
  6. 掌握快速 ctrl+c、ctrl+v;
  7. 掌握 Span<T>的基本用法
  8. 掌握以上装逼技能,就可以开始搬砖了。

JT808 数据结构解析

数据包[JT808Package]

| 头标识 | 数据头 | 数据体/分包数据体 | 校验码 | 尾标识 | | :----: | :---------: | :----------------------------: | :-------: | :----: | | Begin | JT808Header | JT808Bodies/JT808SubDataBodies | CheckCode | End | | 7E | - | - | - | 7E |

数据头[JT808Header]

| 消息 ID | 消息体属性 | 协议版本号(2019 版本) | 终端手机号 | 消息流水号 | 消息总包数(依赖是否分包) | 包序号(依赖是否分包) | | :-----: | :----------------------------: | :-------------------: | :-------------: | :--------: | :----------------------: | :------------------: | | MsgId | JT808HeaderMessageBodyProperty | ProtocolVersion | TerminalPhoneNo | MsgNum | PackgeCount | PackageIndex |

数据头-消息体属性[JT808HeaderMessageBodyProperty]

| 版本标识(2019 版本) | 是否分包 | 加密标识 | 消息体长度 | | :-----------------: | :------: | :------: | :--------: | | VersionFlag | IsPackge | Encrypt | DataLength |

消息体属性[JT808Bodies]

根据对应消息 ID:MsgId

注意:数据内容(除去头和尾标识)进行转义判断

转义规则如下:

  1. 若数据内容中有出现字符 0x7e 的,需替换为字符 0x7d 紧跟字符 0x02;
  2. 若数据内容中有出现字符 0x7d 的,需替换为字符 0x7d 紧跟字符 0x01;

反转义的原因:确认 JT808 协议的 TCP 消息边界。

举个栗子 1

1.组包:

MsgId 0x0200:位置信息汇报


JT808Package jT808Package = new JT808Package();

jT808Package.Header = new JT808Header
{
    MsgId = (ushort)JT808.Protocol.Enums.JT808MsgId._0x0200,
    ManualMsgNum = 126,
    TerminalPhoneNo = "123456789012"
};

JT808_0x0200 jT808_0x0200 = new JT808_0x0200();
jT808_0x0200.AlarmFlag = 1;
jT808_0x0200.Altitude = 40;
jT808_0x0200.GPSTime = DateTime.Parse("2018-10-15 10:10:10");
jT808_0x0200.Lat = 12222222;
jT808_0x0200.Lng = 132444444;
jT808_0x0200.Speed = 60;
jT808_0x0200.Direction = 0;
jT808_0x0200.StatusFlag = 2;
jT808_0x0200.BasicLocationAttachData = new Dictionary<byte, JT808_0x0200_BodyBase>();

jT808_0x0200.BasicLocationAttachData.Add(JT808Constants.JT808_0x0200_0x01, new JT808_0x0200_0x01
{
    Mileage = 100
});

jT808_0x0200.BasicLocationAttachData.Add(JT808Constants.JT808_0x0200_0x02, new JT808_0x0200_0x02
{
    Oil = 125
});

jT808Package.Bodies = jT808_0x0200;

byte[] data = JT808Serializer.Serialize(jT808Package);

var hex = data.ToHexString();

// 输出结果Hex:
// 7E 02 00 00 26 12 34 56 78 90 12 00 7D 02 00 00 00 01 00 00 00 02 00 BA 7F 0E 07 E4 F1 1C 00 28 00 3C 00 00 18 10 15 10 10 10 01 04 00 00 00 64 02 02 00 7D 01 13 7E

2.手动解包:

1.原包:
7E 02 00 00 26 12 34 56 78 90 12 00 (7D 02) 00 00 00 01 00 00 00 02 00 BA 7F 0E 07 E4 F1 1C 00 28 00 3C 00 00 18 10 15 10 10 10 01 04 00 00 00 64 02 02 00 (7D 01) 13 7E

2.进行反转义
7D 02 ->7E
7D 01 ->7D
反转义后
7E 02 00 00 26 12 34 56 78 90 12 00 7E 00 00 00 01 00 00 00 02 00 BA 7F 0E 07 E4 F1 1C 00 28 00 3C 00 00 18 10 15 10 10 10 01 04 00 00 00 64 02 02 00 7D 13 7E

3.拆解
7E                  --头标识
02 00               --数据头->消息ID
00 26               --数据头->消息体属性
12 34 56 78 90 12   --数据头->终端手机号
00 7E               --数据头->消息流水号
00 00 00 01         --消息体->报警标志
00 00 00 02         --消息体->状态位标志
00 BA 7F 0E         --消息体->纬度
07 E4 F1 1C         --消息体->经度
00 28               --消息体->海拔高度
00 3C               --消息体->速度
00 00               --消息体->方向
18 10 15 10 10 10   --消息体->GPS时间
01                  --消息体->附加信息->里程
04                  --消息体->附加信息->长度
00 00 00 64         --消息体->附加信息->数据
02                  --消息体->附加信息->油量
02                  --消息体->附加信息->长度
00 7D               --消息体->附加信息->数据
13                  --检验码
7E                  --尾标识

3.程序解包:

//1.转成byte数组
byte[] bytes = "7E 02 00 00 26 12 34 56 78 90 12 00 7D 02 00 00 00 01 00 00 00 02 00 BA 7F 0E 07 E4 F1 1C 00 28 00 3C 00 00 18 10 15 10 10 10 01 04 00 00 00 64 02 02 00 7D 01 13 7E".ToHexBytes();

//2.将数组反序列化
var jT808Package = JT808Serializer.Deserialize(bytes);

//3.数据包头
Assert.Equal(Enums.JT808MsgId._0x0200, jT808Package.Header.MsgId);
Assert.Equal(38, jT808Package.Header.MessageBodyProperty.DataLength);
Assert.Equal(126, jT808Package.Header.MsgNum);
Assert.Equal("123456789012", jT808Package.Header.TerminalPhoneNo);
Assert.False(jT808Package.Header.MessageBodyProperty.IsPackge);
Assert.Equal(0, jT808Package.Header.PackageIndex);
Assert.Equal(0, jT808Package.Header.PackgeCount);
Assert.Equal(JT808EncryptMethod.None, jT808Package.Header.MessageBodyProperty.Encrypt);

//4.数据包体
JT808_0x0200 jT808_0x0200 = (JT808_0x0200)jT808Package.Bodies;
Assert.Equal((uint)1, jT808_0x0200.AlarmFlag);
Assert.Equal((uint)40, jT808_0x0200.Altitude);
Assert.Equal(DateTime.Parse("2018-10-15 10:10:10"), jT808_0x0200.GPSTime);
Assert.Equal(12222222, jT808_0x0200.Lat);
Assert.Equal(132444444, jT808_0x0200.Lng);
Assert.Equal(60, jT808_0x0200.Speed);
Assert.Equal(0, jT808_0x0200.Direction);
Assert.Equal((uint)2, jT808_0x0200.StatusFlag);
//4.1.附加信息1
Assert.Equal(100, ((JT808_0x0200_0x01)jT808_0x0200.BasicLocationAttachData[JT808Constants.JT808_0x0200_0x01]).Mileage);
//4.2.附加信息2
Assert.Equal(125, ((JT808_0x0200_0x02)jT808_0x0200.BasicLocationAttachData[JT808Constants.JT808_0x0200_0x02]).Oil);

举个栗子 2

// 使用消息Id的扩展方法创建JT808Package包
JT808Package jT808Package = Enums.JT808MsgId._0x0200.Create("123456789012",
    new JT808_0x0200 {
        AlarmFlag = 1,
        Altitude = 40,
        GPSTime = DateTime.Parse("2018-10-15 10:10:10"),
        Lat = 12222222,
        Lng = 132444444,
        Speed = 60,
        Direction = 0,
        StatusFlag = 2,
        BasicLocationAttachData = new Dictionary<byte, JT808LocationAttachBase>
        {
            { JT808Constants.JT808_0x0200_0x01,new JT808_0x0200_0x01{Mileage = 100}},
            { JT808Constants.JT808_0x0200_0x02,new JT808_0x0200_0x02{Oil = 125}}
        }
});

byte[] data = JT808Serializer.Serialize(jT808Package);

var hex = data.ToHexString();
//输出结果Hex:
//7E 02 00 00 26 12 34 56 78 90 12 00 01 00 00 00 01 00 00 00 02 00 BA 7F 0E 07 E4 F1 1C 00 28 00 3C 00 00 18 10 15 10 10 10 01 04 00 00 00 64 02 02 00 7D 01 6C 7E

举个栗子 3

// 初始化配置
IJT808Config DT1JT808Config = new DT1Config();
IJT808Config DT2JT808Config = new DT2Config();
// 注册自定义消息外部程序集
DT1JT808Config.Register(Assembly.GetExecutingAssembly());
// 跳过校验和验证
DT1JT808Config.SkipCRCCode = true;
// 根据不同的设备终端号,添加自定义消息Id
DT1JT808Config.MsgIdFactory.SetMap<DT1Demo6>();
DT1JT808Config.FormatterFactory.SetMap<DT1Demo6>();
DT2JT808Config.MsgIdFactory.SetMap<DT2Demo6>();
DT2JT808Config.FormatterFactory.SetMap<DT2Demo6>();
// 初始化序列化实例
JT808Serializer DT1JT808Serializer = new JT808Serializer(DT1JT808Config);
JT808Serializer DT2JT808Serializer = new JT808Serializer(DT2JT808Config);

可以参考 Simples 的 Demo6

举个栗子 4

遇到的问题-多设备多协议的自定义位置附加信息

场景: 一个设备厂商对应多个设备类型,不同设备类型可能存在相同的自定义位置附加信息 Id,导致自定义附加信息 Id 冲突,无法解析。

解决方式:

1.可以根据设备类型做个工厂,解耦对公共序列化器的依赖。

2.可以根据设备类型去实现(GlobalConfigBase)对应的配置,根据不同的 GlobalConfigBase 实例去绑定对应协议解析器。

可以参考 Simples 的 Demo4

可以参考 Simples 的 Demo6

要是哪位大佬还有其他的解决方式,请您告知我下,谢谢您了。

举个栗子 5

遇到的问题-多媒体数据上传进行分包处理

场景: 设备在上传多媒体数据的时候,由于数据比较多,一次上传不了,所以采用分包方式处理。

解决方式:

  1. 第一包数据上来采用平常的方式去解析数据;

  2. 当第二包上来跟第一包的分包数据体(SubDataBodies)进行合并

  3. 当 N 包数据上来,延续步骤 2 的方式。

普及知识点 1:由于消息体长度最大为 10bit 也就是 1023 的字节,所以这边就有个硬性条件不能超过最大长度。

普及知识点 2:一般行业分包是按 256 的整数倍,太多不行,太少也不行,必须刚刚好。

可以参考 Simples 的 Demo5

举个栗子 6

遇到的问题-多设备多协议的消息 ID 冲突

场景: 由于每个设备厂商不同,导致设备的私有协议可能使用相同的消息 ID 作为指令,导致平台解析不了。

解决方式:

可以根据设备类型去实现(GlobalConfigBase)对应的配置,根据不同的 GlobalConfigBase 实例去绑定对应协议解析器。

可以参考 Simples 的 Demo6

举个栗子 7

如何兼容 2019 版本

最新协议文档已经写好了如何做兼容,就是在消息体属性中第 14 位为版本标识。

  1. 当第 14 位为 0 时,标识协议为 2011 年的版本;

  2. 当第 14 位为 1 时,标识协议为 2019 年的版本。

可以参考 Simples 的 Demo7

举个栗子 8

协议分析器在数据出现异常和纠错的时候也是挺有用的,总不能凭借 24K 氪金眼去观察数据,那么可以在开发协议的同时就把协议分析器给写好,这样方便技术或者技术支持排查问题,提高效率。

可以参考 Simples 的 Demo8

举个栗子 9

增加行车记录仪序列化器,既可以单独的存在,也可以组装在 808 的数据包当中。

可以参考 Simples 的 Demo9

举个栗子 10

场景 1: 有些设备,不会按照国标的附加信息 Id 来搞,把附加信息 Id 搞为两个字节,这样在上报上来的数据就会存在重复的附加 Id,导致平台解析出错。

场景 2: 由于粤标的设备厂家自定义的附加信息长度可以为四 4 个字节的,所以需要兼容。

场景 3: 有些设备上报会出现两个相同的附加信息 Id,那么只能解析一个,另一个只能丢在异常附加信息里面去处理。

| 附加信息类说明 | 附加 ID 字节数 | 附加长度字节数 | 备注 | | :--------------------------- | :------------: | :------------: | :----: | | JT808_0x0200_CustomBodyBase | 1 BYTE | 1 BYTE | 标准 | | JT808_0x0200_CustomBodyBase2 | 2 BYTE | 1 BYTE | 自定义 | | JT808_0x0200_CustomBodyBase3 | 2 BYTE | 2 BYTE | 自定义 | | JT808_0x0200_CustomBodyBase4 | 1 BYTE | 4 BYTE | 自定义 |

可以参考 Simples 的 Demo10

注意:默认都是以标准的去解析,要是出现未知的附加,不一定解析就是正确,最好还是需要依照协议文档去开发然后自行注册解析器去解析。

举个栗子 11

场景: 有些设备,补报的定位数据有异常数据包内容长度跟原始的内容长度不一致导致整包的数据的解析出错,再设备不升级,改不了的情况下,尽量能解析多少补报的数据量,就解析多少。

可以参考 Simples 的 Demo11

举个栗子 12

场景: 由于粤标的设备把 2019 版本的 0x8105 终端控制消息命令参数做了扩展,所以需要兼容。

[可以参考 Simples 的 Demo12](https://github.com/SmallChi/JT808/bl

Related Skills

View on GitHub
GitHub Stars603
CategoryCustomer
Updated1d ago
Forks275

Languages

C#

Security Score

100/100

Audited on Mar 23, 2026

No findings