diff --git a/go/vehicle-gateway/internal/protocol/jt808/parser.go b/go/vehicle-gateway/internal/protocol/jt808/parser.go index e9a09729..db927e6c 100644 --- a/go/vehicle-gateway/internal/protocol/jt808/parser.go +++ b/go/vehicle-gateway/internal/protocol/jt808/parser.go @@ -77,31 +77,52 @@ func ParseFrame(raw []byte, receivedAtMS int64, sourceEndpoint string) (envelope messageID := binary.BigEndian.Uint16(raw[0:2]) props := binary.BigEndian.Uint16(raw[2:4]) bodySize := props & 0x03ff + versioned := props&0x4000 != 0 headerLen := 12 + phoneStart := 4 + phoneEnd := 10 + serialStart := 10 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 { - headerLen = 16 + headerLen += 4 packageInfo = map[string]any{ - "total": binary.BigEndian.Uint16(raw[12:14]), - "index": binary.BigEndian.Uint16(raw[14:16]), + "total": binary.BigEndian.Uint16(raw[serialStart+2 : serialStart+4]), + "index": binary.BigEndian.Uint16(raw[serialStart+4 : serialStart+6]), } } if len(raw) < headerLen+int(bodySize)+1 { 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) + phoneTrimZero := strings.TrimLeft(phone, "0") + if versioned && phoneTrimZero != "" { + phone = phoneTrimZero + } body := raw[headerLen : headerLen+int(bodySize)] parsed := map[string]any{ "header": map[string]any{ "message_id": fmt.Sprintf("0x%04X", messageID), + "protocol_version": protocolVersion, + "versioned": versioned, "properties": props, "body_size": bodySize, "phone_bcd_hex": strings.ToUpper(hex.EncodeToString(phoneBCD)), "phone": phone, - "phone_trim_zero": strings.TrimLeft(phone, "0"), - "sequence": binary.BigEndian.Uint16(raw[10:12]), + "phone_trim_zero": phoneTrimZero, + "sequence": binary.BigEndian.Uint16(raw[serialStart : serialStart+2]), "subpackage": props&0x2000 != 0, "package_info": packageInfo, "encryption_flags": (props >> 10) & 0x07, @@ -135,7 +156,7 @@ func ParseFrame(raw []byte, receivedAtMS int64, sourceEndpoint string) (envelope env := envelope.FrameEnvelope{ Protocol: envelope.ProtocolJT808, MessageID: fmt.Sprintf("0x%04X", messageID), - Sequence: binary.BigEndian.Uint16(raw[10:12]), + Sequence: binary.BigEndian.Uint16(raw[serialStart : serialStart+2]), Phone: phone, SourceEndpoint: sourceEndpoint, EventTimeMS: eventTimeMS, diff --git a/go/vehicle-gateway/internal/protocol/jt808/parser_test.go b/go/vehicle-gateway/internal/protocol/jt808/parser_test.go index e1b587d8..3ceaa58e 100644 --- a/go/vehicle-gateway/internal/protocol/jt808/parser_test.go +++ b/go/vehicle-gateway/internal/protocol/jt808/parser_test.go @@ -8,6 +8,7 @@ import ( ) const sampleLocationFrame = "7E020000320133077954250001000000000048000301D2C4C707376139000A00E6004F26063016235701040001900C2504000000000202000030011F31010F867E" +const real2019LocationFrame = "7E02004046010000000001489413506007CB00000000004C000301D276AB06FBFF4B000D014E00E826070122092214040000000017020000010400013361030201402504000000012A02000030011D310112EA0402008000C57E" func TestExtractFramesUnescapesAndParsesLocationWithTotalMileage(t *testing.T) { 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) { 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)))...)