fix: support jt808 versioned header
This commit is contained in:
@@ -77,31 +77,52 @@ func ParseFrame(raw []byte, receivedAtMS int64, sourceEndpoint string) (envelope
|
|||||||
messageID := binary.BigEndian.Uint16(raw[0:2])
|
messageID := binary.BigEndian.Uint16(raw[0:2])
|
||||||
props := binary.BigEndian.Uint16(raw[2:4])
|
props := binary.BigEndian.Uint16(raw[2:4])
|
||||||
bodySize := props & 0x03ff
|
bodySize := props & 0x03ff
|
||||||
|
versioned := props&0x4000 != 0
|
||||||
headerLen := 12
|
headerLen := 12
|
||||||
|
phoneStart := 4
|
||||||
|
phoneEnd := 10
|
||||||
|
serialStart := 10
|
||||||
packageInfo := map[string]any(nil)
|
packageInfo := map[string]any(nil)
|
||||||
|
protocolVersion := byte(0)
|
||||||
|
if versioned {
|
||||||
|
headerLen = 17
|
||||||
|
if len(raw) < headerLen {
|
||||||
|
return envelope.FrameEnvelope{}, fmt.Errorf("%w: versioned header len=%d", ErrFrameTooShort, len(raw))
|
||||||
|
}
|
||||||
|
protocolVersion = raw[4]
|
||||||
|
phoneStart = 5
|
||||||
|
phoneEnd = 15
|
||||||
|
serialStart = 15
|
||||||
|
}
|
||||||
if props&0x2000 != 0 {
|
if props&0x2000 != 0 {
|
||||||
headerLen = 16
|
headerLen += 4
|
||||||
packageInfo = map[string]any{
|
packageInfo = map[string]any{
|
||||||
"total": binary.BigEndian.Uint16(raw[12:14]),
|
"total": binary.BigEndian.Uint16(raw[serialStart+2 : serialStart+4]),
|
||||||
"index": binary.BigEndian.Uint16(raw[14:16]),
|
"index": binary.BigEndian.Uint16(raw[serialStart+4 : serialStart+6]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(raw) < headerLen+int(bodySize)+1 {
|
if len(raw) < headerLen+int(bodySize)+1 {
|
||||||
return envelope.FrameEnvelope{}, fmt.Errorf("%w: bodySize=%d len=%d", ErrFrameTooShort, bodySize, len(raw))
|
return envelope.FrameEnvelope{}, fmt.Errorf("%w: bodySize=%d len=%d", ErrFrameTooShort, bodySize, len(raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
phoneBCD := raw[4:10]
|
phoneBCD := raw[phoneStart:phoneEnd]
|
||||||
phone := bcdString(phoneBCD)
|
phone := bcdString(phoneBCD)
|
||||||
|
phoneTrimZero := strings.TrimLeft(phone, "0")
|
||||||
|
if versioned && phoneTrimZero != "" {
|
||||||
|
phone = phoneTrimZero
|
||||||
|
}
|
||||||
body := raw[headerLen : headerLen+int(bodySize)]
|
body := raw[headerLen : headerLen+int(bodySize)]
|
||||||
parsed := map[string]any{
|
parsed := map[string]any{
|
||||||
"header": map[string]any{
|
"header": map[string]any{
|
||||||
"message_id": fmt.Sprintf("0x%04X", messageID),
|
"message_id": fmt.Sprintf("0x%04X", messageID),
|
||||||
|
"protocol_version": protocolVersion,
|
||||||
|
"versioned": versioned,
|
||||||
"properties": props,
|
"properties": props,
|
||||||
"body_size": bodySize,
|
"body_size": bodySize,
|
||||||
"phone_bcd_hex": strings.ToUpper(hex.EncodeToString(phoneBCD)),
|
"phone_bcd_hex": strings.ToUpper(hex.EncodeToString(phoneBCD)),
|
||||||
"phone": phone,
|
"phone": phone,
|
||||||
"phone_trim_zero": strings.TrimLeft(phone, "0"),
|
"phone_trim_zero": phoneTrimZero,
|
||||||
"sequence": binary.BigEndian.Uint16(raw[10:12]),
|
"sequence": binary.BigEndian.Uint16(raw[serialStart : serialStart+2]),
|
||||||
"subpackage": props&0x2000 != 0,
|
"subpackage": props&0x2000 != 0,
|
||||||
"package_info": packageInfo,
|
"package_info": packageInfo,
|
||||||
"encryption_flags": (props >> 10) & 0x07,
|
"encryption_flags": (props >> 10) & 0x07,
|
||||||
@@ -135,7 +156,7 @@ func ParseFrame(raw []byte, receivedAtMS int64, sourceEndpoint string) (envelope
|
|||||||
env := envelope.FrameEnvelope{
|
env := envelope.FrameEnvelope{
|
||||||
Protocol: envelope.ProtocolJT808,
|
Protocol: envelope.ProtocolJT808,
|
||||||
MessageID: fmt.Sprintf("0x%04X", messageID),
|
MessageID: fmt.Sprintf("0x%04X", messageID),
|
||||||
Sequence: binary.BigEndian.Uint16(raw[10:12]),
|
Sequence: binary.BigEndian.Uint16(raw[serialStart : serialStart+2]),
|
||||||
Phone: phone,
|
Phone: phone,
|
||||||
SourceEndpoint: sourceEndpoint,
|
SourceEndpoint: sourceEndpoint,
|
||||||
EventTimeMS: eventTimeMS,
|
EventTimeMS: eventTimeMS,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const sampleLocationFrame = "7E020000320133077954250001000000000048000301D2C4C707376139000A00E6004F26063016235701040001900C2504000000000202000030011F31010F867E"
|
const sampleLocationFrame = "7E020000320133077954250001000000000048000301D2C4C707376139000A00E6004F26063016235701040001900C2504000000000202000030011F31010F867E"
|
||||||
|
const real2019LocationFrame = "7E02004046010000000001489413506007CB00000000004C000301D276AB06FBFF4B000D014E00E826070122092214040000000017020000010400013361030201402504000000012A02000030011D310112EA0402008000C57E"
|
||||||
|
|
||||||
func TestExtractFramesUnescapesAndParsesLocationWithTotalMileage(t *testing.T) {
|
func TestExtractFramesUnescapesAndParsesLocationWithTotalMileage(t *testing.T) {
|
||||||
stream, err := hex.DecodeString(sampleLocationFrame + "7E02000000")
|
stream, err := hex.DecodeString(sampleLocationFrame + "7E02000000")
|
||||||
@@ -63,6 +64,56 @@ func TestExtractFramesUnescapesAndParsesLocationWithTotalMileage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseFrameSupportsJT8082019VersionedHeader(t *testing.T) {
|
||||||
|
stream, err := hex.DecodeString(real2019LocationFrame)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
frames, remainder, err := ExtractFrames(stream)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExtractFrames() error = %v", err)
|
||||||
|
}
|
||||||
|
if len(frames) != 1 || len(remainder) != 0 {
|
||||||
|
t.Fatalf("unexpected split result frames=%d remainder=%s", len(frames), hex.EncodeToString(remainder))
|
||||||
|
}
|
||||||
|
|
||||||
|
env, err := ParseFrame(frames[0], 1782914967713, "115.231.168.135:43625")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParseFrame() error = %v", err)
|
||||||
|
}
|
||||||
|
if env.MessageID != "0x0200" {
|
||||||
|
t.Fatalf("message id = %q", env.MessageID)
|
||||||
|
}
|
||||||
|
if env.Phone != "14894135060" {
|
||||||
|
t.Fatalf("phone = %q", env.Phone)
|
||||||
|
}
|
||||||
|
if env.Sequence != 1995 {
|
||||||
|
t.Fatalf("sequence = %d", env.Sequence)
|
||||||
|
}
|
||||||
|
assertFloatField(t, env, envelope.FieldLatitude, 30.570155)
|
||||||
|
assertFloatField(t, env, envelope.FieldLongitude, 117.178187)
|
||||||
|
assertFloatField(t, env, envelope.FieldSpeedKMH, 33.4)
|
||||||
|
assertFloatField(t, env, envelope.FieldTotalMileageKM, 7868.9)
|
||||||
|
if env.Fields["altitude_m"] != uint16(13) {
|
||||||
|
t.Fatalf("altitude = %#v", env.Fields["altitude_m"])
|
||||||
|
}
|
||||||
|
if env.Fields["direction_deg"] != uint16(232) {
|
||||||
|
t.Fatalf("direction = %#v", env.Fields["direction_deg"])
|
||||||
|
}
|
||||||
|
|
||||||
|
header, ok := env.Parsed["header"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("parsed header missing: %#v", env.Parsed)
|
||||||
|
}
|
||||||
|
if header["versioned"] != true || header["protocol_version"] != byte(1) {
|
||||||
|
t.Fatalf("unexpected versioned header: %#v", header)
|
||||||
|
}
|
||||||
|
if header["phone_bcd_hex"] != "00000000014894135060" {
|
||||||
|
t.Fatalf("phone bcd = %#v", header["phone_bcd_hex"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestExtractFramesHandlesEscapedPayload(t *testing.T) {
|
func TestExtractFramesHandlesEscapedPayload(t *testing.T) {
|
||||||
payload := []byte{0x02, 0x00, 0x00, 0x02, 0x01, 0x33, 0x07, 0x79, 0x54, 0x25, 0x00, 0x01, 0x7e, 0x7d}
|
payload := []byte{0x02, 0x00, 0x00, 0x02, 0x01, 0x33, 0x07, 0x79, 0x54, 0x25, 0x00, 0x01, 0x7e, 0x7d}
|
||||||
frame := append([]byte{0x7e}, escape(append(payload, checksum(payload)))...)
|
frame := append([]byte{0x7e}, escape(append(payload, checksum(payload)))...)
|
||||||
|
|||||||
Reference in New Issue
Block a user