C#对接部标JT808协议实现北斗定位设备数据接收服务端
一、前言介绍
开发一套能够支撑几万台北斗定位设备数据接收的服务端,用于接收北斗定位器定位数据的平台。项目基于windows平台,C#语言开发框架Net Framework4.8,TCP主要基于SuperSocket用来构建一个服务器端 Socket 程序,JT808.Protocol JT/T808协议快速开发包,开发工具采用了微软的Visual Studio 2022,数据库MSSQL2014(分库分表)。
二、主要流程说明
注册->鉴权->心跳->位置上报
首次连接必须注册,注册成功后返回鉴权码,之后每次建立连接必须先鉴权
否则不响应心跳和地址上报。
三、常用工具
字符串和16进制转换: http://www.bejson.com/convert/ox2str/
在线进制转换: http://tool.oschina.net/hexconvert/
计算校验码工具: http://www.ip33.com/bcc.html
也可以找一个模拟JT808发包收包的工具
四、开发包引用
引用的包都是开源可修改的,建议下载源码附加进项目中,便于修改调试。
1、SuperSocket
https://github.com/kerryjiang/SuperSocket
采用了SuperSocket1.6.1版本简化Socket服务端的开发工作。
在Visual Studio中可以使用NuGet安装SuperSocket。
2、JT808.Protocol版本 2.6.5.0
https://github.com/SmallChi/JT808
五、主要类实现
1、自定义请求(RequestInfo)
主要用来封装JT808数据包,便于下一步处理。
JT808RequestInfo.cs
public class JT808RequestInfo : IRequestInfo { public JT808RequestInfo() { } public string Key{get;set;} = "jt808"; public byte[] SourceBytes { get;set;} /// <summary> /// 起始符 /// </summary> public const byte BeginFlag = 0x7e; /// <summary> /// 终止符 /// </summary> public const byte EndFlag = 0x7e; public JT808RequestInfo(ushort msgId, JT808HeaderMessageBody messageBodyProperty, JT808Version version, string terminalPhoneNo, ushort msgNum, byte[] bodies, byte checkCode) { MsgId = msgId; MessageBodyProperty = messageBodyProperty; Version = version; TerminalPhoneNo = terminalPhoneNo; MsgNum = msgNum; Bodies = bodies; CheckCode = checkCode; } /// <summary> /// 起始符,1字节 /// </summary> public byte Begin { get; set; } = BeginFlag; /// <summary> /// 消息ID,2字节 /// </summary> public ushort MsgId { get; set; } /// <summary> /// 消息体属性 /// </summary> public JT808HeaderMessageBody MessageBodyProperty { get; set; } /// <summary> /// 808版本号 /// </summary> public JT808Version Version { get; set; } /// <summary> /// 终端手机号 /// 根据安装后终端自身的手机号转换。手机号不足 12 位,则在前补充数字,大陆手机号补充数字 0,港澳台则根据其区号进行位数补充 /// (2019版本)手机号不足 20 位,则在前补充数字 0 /// </summary> public string TerminalPhoneNo { get; set; } /// <summary> /// 消息流水号 /// 发送计数器 /// 占用两个字节,为发送信息的序列号,用于接收方检测是否有信息的丢失,上级平台和下级平台接自己发送数据包的个数计数,互不影响。 /// 程序开始运行时等于零,发送第一帧数据时开始计数,到最大数后自动归零 /// </summary> public ushort MsgNum { get; set; } /// <summary> /// 消息总包数 /// </summary> public ushort PackgeCount { get; set; } /// <summary> /// 报序号 从1开始 /// </summary> public ushort PackageIndex { get; set; } /// <summary> /// 数据体 /// </summary> public byte[] Bodies { get; set; } /// <summary> /// 校验码 /// 从消息头开始,同后一字节异或,直到校验码前一个字节,占用一个字节。 /// </summary> public byte CheckCode { get; set; } /// <summary> /// 终止符 /// </summary> public byte End { get; set; } = EndFlag; }
2、自定义AppSession
AppSession 代表一个和客户端的逻辑连接,基于连接的操作应该定于在该类之中。你可以用该类的实例发送数据到客户端,接收客户端发送的数据或者关闭连接。
SocketSession.cs
/// <summary> /// 自定义连接类SocketSession,继承AppSession,并传入到AppSession /// </summary> public class SocketSession : AppSession<SocketSession, JT808RequestInfo> { public override void Send(string message) { Console.WriteLine("发送消息:" + message); base.Send(message); } protected override void OnSessionStarted() { //输出客户端IP地址 Console.WriteLine(this.LocalEndPoint.Address.ToString()); //this.Send("Hello User,Welcome to SuperSocket Telnet Server!"); } /// <summary> /// 连接关闭 /// </summary> /// <param name="reason"></param> protected override void OnSessionClosed(CloseReason reason) { base.OnSessionClosed(reason); } //protected override void HandleUnknownRequest(JT808RequestInfo requestInfo) //{ // Console.WriteLine($"遇到未知的请求 Key:" + requestInfo.Key + $" Body:" + requestInfo.Body); // base.HandleUnknownRequest(requestInfo); //} /// <summary> /// 捕捉异常并输出 /// </summary> /// <param name="e"></param> protected override void HandleException(Exception e) { this.Send("error: {0}", e.Message); } }
3、自定义AppServer
AppServer 代表了监听客户端连接,承载TCP连接的服务器实例。理想情况下,我们可以通过AppServer实例获取任何你想要的客户端连接,服务器级别的操作和逻辑应该定义在此类之中。
SocketServer.cs
public class SocketServer : AppServer<SocketSession, JT808RequestInfo> { public SocketServer() : base(new MyReceiveFilterFactory()) { //业务处理线程1 Thread rcfsth = new Thread(xxxxx); rcfsth.IsBackground = true; rcfsth.Start(); //业务处理线程2 Thread locationTh = new Thread(xxxxxxxx); locationTh.IsBackground = true; locationTh.Start(); } protected override bool Setup(IRootConfig rootConfig, IServerConfig config) { Console.WriteLine("正在准备配置文件"); return base.Setup(rootConfig, config); } protected override void OnStarted() { Console.WriteLine("服务已开始"); base.OnStarted(); } protected override void OnStopped() { Console.WriteLine("服务已停止"); base.OnStopped(); } /// <summary> /// 输出新连接信息 /// </summary> /// <param name="session"></param> protected override void OnNewSessionConnected(SocketSession session) { base.OnNewSessionConnected(session); //输出客户端IP地址 Console.Write("\r\n" + session.LocalEndPoint.Address.ToString() + ":连接"); } /// <summary> /// 输出断开连接信息 /// </summary> /// <param name="session"></param> /// <param name="reason"></param> protected override void OnSessionClosed(SocketSession session, CloseReason reason) { base.OnSessionClosed(session, reason); Console.Write("\r\n" + session.LocalEndPoint.Address.ToString() + ":断开连接"); } }
4、自定义接收过滤器(ReceiveFilter)
由于JT808数据包每个数据包起始标记和结束标记都以7E作为特殊标记,所以这里采用BeginEndMarkReceiveFilter - 带起止符的协议,来接收客户端发来的数据包。
将该过滤器接收到的数据包根据JT808-2019协议内容去处理,并封装成JT808RequestInfo对象返回,这里我使用了Skip().Take()方式去取其中的字节数组,如果对效率有要求的小伙伴可以用Array.Copy()方式取。
MyReceiveFilter.cs
public class JT808ReceiveFilter : BeginEndMarkReceiveFilter<JT808RequestInfo> { /// <summary> /// log4net 记录日志 /// </summary> private static readonly ILog Logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); //开始和结束标记也可以是两个或两个以上的字节 private readonly static byte[] BeginMark = new byte[] { 0x7e }; private readonly static byte[] EndMark = new byte[] { 0x7e }; //private readonly static byte[] decode7d01 = new byte[] { 0x7d, 0x01 }; //private readonly static byte[] decode7d02 = new byte[] { 0x7d, 0x02 }; public JT808ReceiveFilter() : base(BeginMark, EndMark) { } protected override JT808RequestInfo ProcessMatchedRequest(byte[] readBuffer, int offset, int length) { //解析808协议 byte[] sourceBytes = readBuffer; string readBufferStr = ByteUtils.ToHexStrFromByte(readBuffer).Replace("7D 02", "7E").Replace("7D 01", "7D"); Logger.Info(readBufferStr); //============================采用 SmallChi 的解包方法 //JT808Serializer JT808Msg = new JT808Serializer(); //ReadOnlySpan<byte> readOnlySpan = sourceBytes; JT808Package jT808Package = new JT808Package(); var reader = new JT808MessagePackReader(sourceBytes); reader.Decode(); IJT808Config jT808Config = new DefaultGlobalConfig(); jT808Package = jT808Package.Deserialize(ref reader, jT808Config); JT808RequestInfo jT808RequestInfo = new JT808RequestInfo((ushort)jT808Package.Header.MsgId, jT808Package.Header.MessageBodyProperty, jT808Package.Version, jT808Package.Header.TerminalPhoneNo, (ushort)jT808Package.Header.MsgNum, jT808Package.Bodies, jT808Package.CheckCode); jT808RequestInfo.PackgeCount =jT808Package.Header.PackgeCount; jT808RequestInfo.PackageIndex = jT808Package.Header.PackageIndex; jT808RequestInfo.SourceBytes = sourceBytes; return jT808RequestInfo; } } /// <summary> /// 默认全局配置 /// </summary> public class DefaultGlobalConfigExt : GlobalConfigBase { /// <summary> /// 配置Id /// </summary> public override string ConfigId { get; protected set; } /// <summary> /// /// </summary> /// <param name="configId"></param> public DefaultGlobalConfigExt(string configId = "Default") { ConfigId = configId; } }
5、实现接收过滤器工厂(ReceiveFilterFactory)
MyReceiveFilterFactory.cs
public class MyReceiveFilterFactory : IReceiveFilterFactory<JT808RequestInfo> { public IReceiveFilter<JT808RequestInfo> CreateFilter(IAppServer appServer, IAppSession appSession, IPEndPoint remoteEndPoint) { return new MyReceiveFilter(); } }
6、自定义Command处理封装好的数据包内容
命令行协议是一种被广泛应用的协议。一些成熟的协议如 Telnet, SMTP, POP3 和 FTP 都是基于命令行协议的。 在SuperSocket 中, 如果你没有定义自己的协议,SuperSocket 将会使用命令行协议, 这会使这样的协议的开发变得很简单。
public class JT808PackCommand : CommandBase<SocketSession, JT808RequestInfo> { /// <summary> /// 平台通用应答 /// </summary> /// <param name="session"></param> /// <param name="requestInfo"></param> /// <param name="ackMsgId"></param> public void PlatformCommonReply(SocketSession session, JT808RequestInfo requestInfo,ushort ackMsgId) { JT808Package jT808Package = new JT808Package(); jT808Package.Header = new JT808Header { MsgId = (ushort)JT808MsgId._0x8001, ManualMsgNum = 0, TerminalPhoneNo = requestInfo.TerminalPhoneNo }; JT808_0x8001 jT808_8001 = new JT808_0x8001(); jT808_8001.MsgNum = requestInfo.MsgNum; jT808_8001.AckMsgId = ackMsgId; jT808_8001.JT808PlatformResult = JT808PlatformResult.succeed; jT808Package.Bodies = jT808_8001; JT808Serializer jT808Serializer = new JT808Serializer(); byte[] data = jT808Serializer.Serialize(jT808Package, JT808Version.JTT2019); session.Send(data, 0, data.Length); //Console.WriteLine(jT808_8001.Description); } public override string Name { get { return "jt808"; } } public override void ExecuteCommand(SocketSession session, JT808RequestInfo requestInfo) { ushort msgId = requestInfo.MsgId; //Console.WriteLine("收到一条jt808消息,消息ID:" + msgId); try { switch (msgId) { //终端通用应答 case 0x0001: { PlatformCommonReply(session, requestInfo, jT808_0X0001.ReplyMsgId); } break; //查询服务器时间 case 0x0004: { //Console.WriteLine("查询服务器时间"); JT808Package jT808Package = new JT808Package(); jT808Package.Header = new JT808Header { MsgId = (ushort)JT808MsgId._0x8004, ManualMsgNum = 0, TerminalPhoneNo = requestInfo.TerminalPhoneNo }; JT808_0x8004 jT808_8004 = new JT808_0x8004(); jT808_8004.Time = DateTime.UtcNow; jT808Package.Bodies = jT808_8004; JT808Serializer jT808Serializer = new JT808Serializer(); byte[] data = jT808Serializer.Serialize(jT808Package, JT808Version.JTT2019); session.Send(data, 0, data.Length); } break; //终端注册 case 0x0100: { Custom_JT808_0x0100 jT808_0X0100 = Custom_JT808_0x0100.Deserialize(requestInfo.Bodies); JT808Package jT808Package = new JT808Package(); jT808Package.Header = new JT808Header { MsgId = (ushort)JT808MsgId._0x8100, ManualMsgNum = 0, TerminalPhoneNo = requestInfo.TerminalPhoneNo }; JT808_0x8100 jT808_8100 = new JT808_0x8100(); jT808_8100.AckMsgNum = requestInfo.MsgNum; jT808_8100.JT808TerminalRegisterResult = JT808TerminalRegisterResult.success; jT808_8100.Code = jT808_0X0100.TerminalId + "," + jT808_0X0100.PlateNo; jT808Package.Bodies = jT808_8100; JT808Serializer jT808Serializer = new JT808Serializer(); byte[] data = jT808Serializer.Serialize(jT808Package, JT808Version.JTT2019); session.Send(data, 0, data.Length); } break; //终端鉴权 case 0x0102: { Custom_JT808_0x0102 jT808_0102 = Custom_JT808_0x0102.Deserialize(requestInfo.Bodies); //鉴权成功后保存JT808终端客户端信息 JT808ClientCache.AddJT808ClientCache(requestInfo.TerminalPhoneNo, session.SessionID); PlatformCommonReply(session, requestInfo, requestInfo.MsgId); } break; //位置信息汇报 case 0x0200: { Custom_JT808_0x0200 jT808_0X0200 = Custom_JT808_0x0200.Deserialize(requestInfo.Bodies); Console.WriteLine(jT808_0X0200.Description); PlatformCommonReply(session, requestInfo, requestInfo.MsgId); } break; //定位数据批量上传 case 0x0704: { PlatformCommonReply(session, requestInfo, requestInfo.MsgId); } break; //多媒体数据上传 case 0x0801: { //如果有分包内容 if (requestInfo.PackgeCount > 0) { //Console.WriteLine(requestInfo.PackageIndex); string phone = requestInfo.TerminalPhoneNo; //第一个多媒体数据包 if (requestInfo.PackageIndex == 1) { Custom_JT808_0x0801 jT808_0X0801 = Custom_JT808_0x0801.Deserialize(requestInfo.Bodies); //添加多媒体数据包缓存 MultimediaCache.AddMultimediaCache(phone, jT808_0X0801); PlatformCommonReply(session, requestInfo, requestInfo.MsgId); } //中间的数据包 if (requestInfo.PackageIndex > 1 && requestInfo.PackageIndex < requestInfo.PackgeCount) { //追加多媒体数据包缓存 MultimediaCache.AppendMultimediaByte(phone, requestInfo.Bodies); PlatformCommonReply(session, requestInfo, requestInfo.MsgId); } //最后一个包,保存数据,并回应 if (requestInfo.PackageIndex == requestInfo.PackgeCount) { //追加多媒体数据包缓存 MultimediaCache.AppendMultimediaByte(phone, requestInfo.Bodies); //保存到本地 Custom_JT808_0x0801 jT808_0X0801 = MultimediaCache.GetMultimediaCache(phone); byte[] multimediaDataPackage = jT808_0X0801.MultimediaDataPackage; //Console.WriteLine(ByteUtils.ToHexStrFromByte(multimediaDataPackage).Replace(" ","")); uint mediaId = jT808_0X0801.MultimediaId; //ByteUtils.BytesToFile(multimediaDataPackage, "sample.jpg"); File.WriteAllBytes("Img\\" + requestInfo.MsgNum + "_saved_image.jpg", multimediaDataPackage); //移除多媒体数据包缓存 MultimediaCache.RemoveMultimediaCache(phone); //应答 JT808Package jT808Package = new JT808Package(); jT808Package.Header = new JT808Header { MsgId = (ushort)JT808MsgId._0x8800, ManualMsgNum = 0, TerminalPhoneNo = requestInfo.TerminalPhoneNo }; JT808_0x8800 jT808_0X8800 = new JT808_0x8800(); jT808_0X8800.MultimediaId = mediaId; jT808_0X8800.RetransmitPackageCount = 0; jT808_0X8800.RetransmitPackageIds = new byte[0];//一定要定义一个空数组 jT808Package.Bodies = jT808_0X8800; JT808Serializer jT808Serializer = new JT808Serializer(); byte[] data = jT808Serializer.Serialize(jT808Package, JT808Version.JTT2019); session.Send(data, 0, data.Length); } } } break; default: { PrintMessage.PrintLn("未知命令" + msgId.ToString("x8"),ConsoleColor.Yellow); PlatformCommonReply(session, requestInfo, requestInfo.MsgId); } break; } }catch (Exception ex) { LogHelper.WriteError2(ex, "ExecuteCommand error"); } } }
其中Custom_xxx是去解析JT808数据包中的包内容并封装的,解析方式参考过滤器那里,可以自己根据808协议去写。如果用了SmallChi的方式在过滤器中,可以直接使用JT808Package去处理业务代码,无需定义自己的Custom_xxx去解析包内容。本文直接使用JT808Package处理业务,业务数据分库分表处理。
六、JT808包解析说明-重要
平台应答指的是服务端接收到数据后回发给客户端(北斗设备)的。
1.心跳: 终端请求: 7e000200000857502162890001c67e 7e # 标识位 000200000857502162890001 # 消息头 0002 # 消息ID 0000 # 消息体属性,消息体属性每个位都为零,也即第12-15位的消息包封装项不存在,消息体也为空 085750216289 # 终端手机号或设备号,这里是设备号 0001 # 流水号 c6 # 校验码 7e # 标识位 平台通用应答:7e8001000508575021628900010001000200437e 7e # 标识位 800100050857502162890001 # 消息头 8001 # 消息ID 0005 # 消息体属性,消息体属性每个位都为零,也即第12-15位的消息包封装项不存在,消息体也为空 085750216289 # 终端手机号或设备号,这里是设备号 0001 # 流水号 0001000200 # 消息体 0001 # 应答流水号,对终端发送消息的流水号 0002 # 应答ID,对应终端发送消息的ID 00 # 结果 c6 # 校验码 7e # 标识位 2.注册包: 终端注册-请求:7e0100002e0188554850150025002c0133373039363054372d54383038000000000000000000000000003033323931373001e6b599413636363636557e 7e # 标识位 0100002e0188554850150025 # 消息头 0100 # 消息ID 002e # 消息体属性,消息体属性每个位都为零,也即第12-15位的消息包封装项不存在,消息体也为空 018855485015 # 终端手机号或设备号,这里是设备号 0025 # 流水号 002c0133373039363054372d54383038000000000000000000000000003033323931373001e6b599413636363636 # 消息体 002c # 省份id 0133 # 城市id 3730393630 # 制造商id 54372d5438303800000000000000000000000000 # 终端型号 30333239313730 # 终端id 01 # 车牌颜色 e6b599413636363636 # 车牌标识 55 # 校验码 7e # 标识位 终端注册-平台应答:7e810000100188554850150025002500313535323935353938373437307f7e 7e # 标识位 810000100188554850150025 # 消息头 8100 # 消息ID 0010 # 消息体属性,消息体属性每个位都为零,也即第12-15位的消息包封装项不存在,消息体也为空 018855485015 # 终端手机号或设备号,这里是设备号 0025 # 流水号 00250031353532393535393837343730 # 消息体 0025 # 应答流水号,对终端发送消息的流水号 00 # 结果 31353532393535393837343730 # 鉴权码,平台生成的鉴权码为字符串 1552955987470,但转换为16进制,就是31353532393535393837343730 7f # 校验码 7e # 标识位 4.位置上报: 终端请求: 7e020000386857502162891ac000000100000c100101d5c86a0732c2610000000000001903131538032b040000000030011e310100e10400000000e20804600036583a8e7a657e 7e # 标识位 020000386857502162891ac0 # 消息头 0200 # 消息ID 0038 # 消息体属性,消息体属性每个位都为零,也即第12-15位的消息包封装项不存在,消息体也为空 685750216289 # 终端手机号或设备号,这里是设备号 1ac0 # 流水号 00000100000c100101d5c86a0732c2610000000000001903131538032b040000000030011e310100e10400000000e20804600036583a8e7a # 消息体 00000100000c100101d5c86a0732c261000000000000190313153803 #消息体-位置基本信息 00000100 # 报警标志 000c1001 # 状态 01d5c86a # 纬度 0732c261 # 经度 0000 # 高度 0000 # 速度 0000 # 方向 190313153803 # 时间 2b040000000030011e310100e10400000000e20804600036583a8e7a #消息体-位置附加信息,可选 2b0400000000 # 2b 模拟量 04 长度为4 00000000 附加信息内容 30011e # 30 信号强度 01 长度为1 1e 附加信息内容 310100 # 31 卫星数 01 长度为1 00 附加信息内容 e10400000000 # e1 自定义 04 长度为4 00000000 附加信息内容 e20804600036583a8e7a # e1 自定义 08 长度为8 04600036583a8e7a 附加信息内容 65 # 校验码 7e # 标识位 平台通用应答: 7e800100050857502162891ac01ac0020000437e 7e # 标识位 800100050857502162891ac0 # 消息头 8001 # 消息ID 0005 # 消息体属性,消息体属性每个位都为零,也即第12-15位的消息包封装项不存在,消息体也为空 085750216289 # 终端手机号或设备号,这里是设备号 1ac0 # 流水号 1ac0020000 # 消息体 1ac0 # 应答流水号,对终端发送消息的流水号 0200 # 应答ID,对应终端发送消息的ID 00 # 结果 43 # 校验码 7e # 标识位 5.鉴权 终端请求: 7e010200060188554850150001323835313131db7e 7e # 标识位 010200060188554850150001 # 消息头 0102 # 消息ID 0006 # 消息体属性,消息体属性每个位都为零,也即第12-15位的消息包封装项不存在,消息体也为空 018855485015 # 终端手机号或设备号,这里是设备号 0001 # 流水号 323835313131 # 消息体 323835313131 # 鉴权码,这里是16进制,转换成字符串为285111 db # 校验码 7e # 标识位 平台通用应答: 7e8001000501885548501500010001010200567e 7e # 标识位 800100050188554850150001 # 消息头 8001 # 消息ID 0005 # 消息体属性,消息体属性每个位都为零,也即第12-15位的消息包封装项不存在,消息体也为空 018855485015 # 终端手机号或设备号,这里是设备号 0001 # 流水号 0001010200 # 消息体 0001 # 应答流水号,对终端发送消息的流水号 0102 # 应答ID,对应终端发送消息的ID 00 # 结果 43 # 校验码 7e # 标识位 6.查询终端参数 平台下发请求:7e810400000188554850150001557e 7e # 标识位 810400000188554850150001 # 消息头 8104 # 消息ID 0000 # 消息体属性,消息体属性每个位都为零,也即第12-15位的消息包封装项不存在,消息体也为空 018855485015 # 终端手机号或设备号,这里是设备号 0001 # 流水号 55 # 校验码 7e # 标识位 7.查询终端参数应答(前提:先鉴权) 7e0104009e0188554850151ac01abf100000000104000000b400000002040000001e00000003040000000800000010000000001100000000120000000013147365727665722e6e6174617070667265652e636300000018040000802300000027040000000000000029040000000f0000005a04000000000000005d0200000000005e0200000000f00110000000000000000000000000000000000000f002060000000000000000f0030100987e 7e # 标识位 0104009e0188554850151ac0 # 消息头 0104 # 消息ID 009e # 消息体属性,消息体属性每个位都为零,也即第12-15位的消息包封装项不存在,消息体也为空 018855485015 # 终端手机号或设备号,这里是设备号 1ac0 # 流水号 1abf100000000104000000b400000002040000001e00000003040000000800000010000000001100000000120000000013147365727665722e6e6174617070667265652e636300000018040000802300000027040000000000000029040000000f0000005a04000000000000005d0200000000005e0200000000f00110000000000000000000000000000000000000f002060000000000000000f0030100 # 消息体 1abf # 应答流水号,对应平台下发终端参数查询的流水号 10 # 应答参数个数 0000000104000000b4 # 00000001 终端心跳间隔 04 长度为4 000000b4 参数值 00000002040000001e #和上面参数类似,就不一一注解了。 000000030400000008 0000001000 0000001100 0000001200 00000013147365727665722e6e6174617070667265652e6363 000000180400008023 000000270400000000 00000029040000000f 0000005a0400000000 0000005d020000 0000005e020000 0000f0011000000000000000000000000000000000 0000f00206000000000000 0000f0030100 98 # 校验码 7e # 标识位 8.设置终端参数 7e8103003001885548501500010700000001040000003c00000002040000000a00000003040000000a00000010000000001100000000120000000013005d7e 7e # 标识位 810300300188554850150001 # 消息头 8103 # 消息ID 0030 # 消息体属性,消息体属性每个位都为零,也即第12-15位的消息包封装项不存在,消息体也为空 018855485015 # 终端手机号或设备号,这里是设备号 0001 # 流水号 0700000001040000003c00000002040000000a00000003040000000a0000001000000000110000000012000000001300 # 消息体 07 # 参数总数 00000001040000003c # 00000001 终端心跳间隔 04 长度为4 0000003c 参数值 00000002040000000a # 和上面参数类似,就不一一注解了。 00000003040000000a 0000001000 0000001100 0000001200 0000001300 5d # 校验码 7e # 标识位 9.终端控制 7e81050001018855485015000104517e 7e # 标识位 810500010188554850150001 # 消息头 8105 # 消息ID 0001 # 消息体属性,消息体属性每个位都为零,也即第12-15位的消息包封装项不存在,消息体也为空 018855485015 # 终端手机号或设备号,这里是设备号 0001 # 流水号 04 # 消息体 04 # 终端复位 51 # 校验码 7e # 标识位 10.文本信息下发 7e83000004018855485015000100736174317e 7e # 标识位 830000040188554850150001 # 消息头 8300 # 消息ID 0004 # 消息体属性,消息体属性每个位都为零,也即第12-15位的消息包封装项不存在,消息体也为空 018855485015 # 终端手机号或设备号,这里是设备号 0001 # 流水号 00736174 # 消息体 00 # 标志 736174 # 文本信息,这里是16进制,对应字符串为sat 31 # 校验码 7e # 标识位
七、结尾说明
本文主要用于备忘记录,站在巨人的肩膀上看的更高更远,感谢各开源博主。JT808协议分为2011版、2013版、2019版本。市面上大多是2013版,少数2019版。2013和2019版本的最大区别是报文固定头部把手机号从原来的6字节BCD码改成了10字节BCD码。部标1078协议文档明确说明了,协议是在JT/T 808协议的基础上进行增加了大量的视频指令,以前的终端32位报警,由于增加了视频报警,拓展为64位报警。能仔细看到这的应该是能帮到你。