JT/808协议全成是《JT/T808-2011道路运输车辆卫星定位系统终端通讯协议及数据格式》,是交通部2011年为GPS定位车载终端和监控平台之间的通信制定的规范。通信协议采用TCP或UDP,平台作为服务器端,终端作为客户端。

unit JT808.Protocol;

interface

  uses System.SysUtils;

type
  TBCD6 = array [0 .. 5] of Byte;

  // 消息头
  TJT808Header = packed record // 12 bytes
    MsgId: Word;               // 消息ID
    Attr: Word;                // 属性
    Phone: TBCD6;              // 手机号
    MsgNum: Word;              // 流水号
  end;

  // 通用应答
  TCommonResponse = packed record // 19 bytes
    StartFlag: Byte;              //起始标志
    Header: TJT808Header;         //消息头
    MsgNum: Word;                 //消息流水号
    MsgId: Word;                  //消息Id
    &Result: Byte;                //结果,0=成功/确认, 1=失败, 2=消息有误,3=不支持, 4=报警消息确认
    EndFlag: Byte;                //结束标志
  end;

  // 设备注册消息体
  TDeviceRegisterBody = packed record     // 33 bytes
    ProvinceID: Word;                     // 省域ID
    CityID: Word;                         // 市域ID
    VendorId: array [0 .. 4] of Byte;     // 制造商ID
    DeviceModel: array [0 .. 19] of Byte; // 设备型号
    DeviceId: array [0 .. 6] of Byte;     // 设备ID
    PlateColor: Byte;                     // 车牌颜色
    PlateNo: array [0 .. 7] of Byte;      // 车牌号码
  end;

  // 设备注册消息
  TDeviceRegisterMsg = packed record
    StartFlag: Byte;
    Header: TJT808Header;
    Body: TDeviceRegisterBody;
    CheckSum: Byte;
    EndFlag: Byte;
  end;

  // 设备注册应答
  TRegisterResponse = packed record
    StartFlag: Byte;
    Header: TJT808Header;
    MsgNum: Word;
    &Result: Byte;
  end;

  // 位置信息消息体
  TGPSLocationBody = packed record
    Alarm: LongWord;           //报警标志位
    Status: LongWord;          //状态位
    Lat: LongWord;             //以度为单位的纬度值乘以10的6次方,精确到百万分之一度
    lng: LongWord;             //以度为单位的经度值乘以10的6次方,精确到百万分之一度
    Altitude: Word;            //海拔高度,米
    Speed: Word;               //车速1/10km/h
    Angle: Word;               //方向0~359
    Time: TBCD6;               //时间
  end;

  // 位置信息
  TGPSLocationMsg = packed record
    StartFlag: Byte;
    Header: TJT808Header;
    Body: TGPSLocationBody;
    CheckSum: Byte;
    EndFlag: Byte;
  end;

  // 心跳消息,消息体为空
  TKeepLiveMsg = packed record
    StartFlag: Byte;
    Header: TJT808Header;
    CheckSum: Byte;
    EndFlag: Byte;
  end;

const
  JT808_DATA_FLAG: Word            = $7E;   // 消息头尾标记
  JT808_ID_DEVICE_RESP: Word       = $0001; // 终端普通应答
  JT808_ID_SERVER_COMMONRESP: Word = $8001; // 平台普通应答
  JT808_ID_SERVER_RESP: Word       = $8100; // 平台对终端注册应答
  JT808_ID_DEVICE_KEEP_LIVE: Word  = $0002; // 终端心跳
  JT808_ID_DEVICE_REG: Word        = $0100; // 终端注册
  JT808_ID_DEVICE_LOGOUT: Word     = $0003; // 终端注销
  JT808_ID_DEVICE_AUTH: Word       = $0102; // 终端鉴权
  JT808_ID_DEVICE_LOCATION: Word   = $0200; // 位置信息

  SERVER_RESP_RESULTS: array [0 .. 4] of string = ('成功/确认', '失败', '消息有误', '不支持', '报警消息确认');

  //返回新的消息流水号
  function NewMsgNum: Word;
  function StrColorToInt(const Value: string): Integer;

  /// <summary>
  /// 校验码,从消息头开始,同后一字节异或,直到校验码前一字节。
  /// </summary>
  /// <param name="Buff">校验的数据</param>
  /// <param name="Count">数据长度</param>
  /// <param name="Offset">偏移量</param>
  /// <returns>校验码</returns>
  function GetCheckSum(Buff: TBytes; Count: Integer; const Offset: Integer = 0): Byte;

  /// <summary>
  /// 纬度转换为整型
  /// </summary>
  /// <param name="Value">经度</param>
  /// <returns>经度乘以10的6次方</returns>
  function LatLngToInt(Value: Double): LongWord;

  /// <summary>
  /// 经度转换为整型
  /// </summary>
  /// <param name="Value">经度</param>
  /// <returns>经度乘以10的6次方</returns>
  function IntToLatlng(Value: LongWord): Double;

  function StrToBcd6(Value: string): TBCD6;
  /// <summary>
  /// 字节转义,因为数据头尾标识位是7E,其他地方出现则需要转义。
  /// </summary>
  /// <param name="Source">要转义的内容</param>
  /// <param name="Count">转义内容长度</param>
  /// <param name="Dest">转义后内容</param>
  /// <returns>转义后长度</returns>
  function JT808Encode(Source: TBytes; Count: Integer; var Dest:TBytes):Integer;
  function JT808Decode(Source: TBytes; Count: Integer; var Dest:TBytes): Integer;

implementation

var
  __MsgNum: Word = $100A;

function NewMsgNum: Word;
begin
  Result := __MsgNum;
  Inc(__MsgNum);
end;

function StrColorToInt(const Value: string): Integer;
const
  Colors: array[1..5] of string = ('蓝色', '黄色', '黑色', '白色', '红色');
begin
  for var I := 1 to High(Colors) do
    if Colors[I] = Value then
      Exit(I);

  Exit(0);
end;
function GetCheckSum(Buff: TBytes; Count: Integer; const Offset: Integer): Byte;
var
  I: Integer;
begin
  Result := Buff[Offset];
  for I := Offset to Count - Offset - 1 do
    Result := Result xor Buff[I];
end;


function LatLngToInt(Value: Double): LongWord;
begin
  Result := Trunc(Value * 1000000);
end;

function IntToLatlng(Value: LongWord): Double;
begin
  Result := Value / 1000000;
end;

function StrToBcd6(Value: string): TBCD6;
  function Encode(Left, Right: Char): Byte;
  begin
    Result := StrToInt(Left) shl 4 or StrToInt(Right);
  end;
var
  I, J: Integer;
begin
  FillChar(Result[0], SizeOf(TBCD6), 0);
  //补足12位
  Value := Value.PadLeft(12,'0');

  J := 0;
  I := 0;
  while I < Value.Length - 1 do
  begin
    Result[J] := Encode(Value.Chars[I], Value.Chars[I + 1]);
    Inc(J);
    Inc(I, 2);
  end;
end;

function JT808Encode(Source: TBytes; Count: Integer;var Dest:TBytes):Integer;
var
  B: Byte;
  I,J: Integer;
begin
  J :=  0;
  I := 0;

  SetLength(Dest,Count*2);

  Dest[J] := Source[I];
  Inc(J);
  for I := 1 to Count - 2 do
  begin
    B := Source[I];
    if B = $7E then
    begin
      Dest[J] := $7D;
      Inc(J);
      Dest[J] := $02;
    end
    else if B = $7D then
    begin
      Dest[J] := $7D;
      Inc(J);
      Dest[J] := $01;
    end
    else
    begin
      Dest[J] := Source[I];
    end;
    Inc(J);
  end;
  Dest[J] := Source[Count - 1];
  Inc(J);

  SetLength(Dest,J);
  Result := J;
end;

function JT808Decode(Source: TBytes; Count: Integer; var Dest:TBytes): Integer;
var
  I,J: Integer;
  B, C: Byte;
begin
  Result:=0;
  if (Count <= 0) or (Count > 2048) then
    Exit;

  SetLength(Dest,Count);

  I := 0;
  J := 0;
  while (I <= Count - 2) do
  begin
    B := Source[I];
    C := Source[I + 1];
    if (B = $7D) and (C = $02) then
    begin
      Dest[J] := $7E;
      Inc(I, 2);
    end
    else if (B = $7D) and (C = $01) then
    begin
      Dest[J] := $7D;
      Inc(I, 2);
    end
    else
    begin
      Dest[J] := Source[I];
      Inc(I, 1);
    end;
    Inc(J);
  end;
  if I < Count then
  begin
    Dest[J] := Source[I];
    Inc(J);
  end;
  SetLength(Dest,J);
  Result:=J;
end;

end.