feat: track jt808 registration identity
This commit is contained in:
@@ -151,6 +151,24 @@ func ParseFrame(raw []byte, receivedAtMS int64, sourceEndpoint string) (envelope
|
||||
if location.TotalMileageKM != nil {
|
||||
fields[envelope.FieldTotalMileageKM] = *location.TotalMileageKM
|
||||
}
|
||||
} else if messageID == 0x0100 {
|
||||
registration, err := parseRegistration(body)
|
||||
if err != nil {
|
||||
return envelope.FrameEnvelope{}, err
|
||||
}
|
||||
parsed["registration"] = registration
|
||||
if deviceID, ok := registration["device_id"].(string); ok {
|
||||
fields["device_id"] = deviceID
|
||||
}
|
||||
if plate, ok := registration["plate"].(string); ok {
|
||||
fields["plate"] = plate
|
||||
}
|
||||
} else if messageID == 0x0102 {
|
||||
authentication := parseAuthentication(body)
|
||||
parsed["authentication"] = authentication
|
||||
if token, ok := authentication["token"].(string); ok {
|
||||
fields["auth_token"] = token
|
||||
}
|
||||
}
|
||||
|
||||
env := envelope.FrameEnvelope{
|
||||
@@ -166,10 +184,36 @@ func ParseFrame(raw []byte, receivedAtMS int64, sourceEndpoint string) (envelope
|
||||
Fields: fields,
|
||||
ParseStatus: envelope.ParseOK,
|
||||
}
|
||||
if registration, ok := parsed["registration"].(map[string]any); ok {
|
||||
env.DeviceID, _ = registration["device_id"].(string)
|
||||
env.Plate, _ = registration["plate"].(string)
|
||||
}
|
||||
env.EventID = env.StableEventID()
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func parseRegistration(body []byte) (map[string]any, error) {
|
||||
if len(body) < 37 {
|
||||
return nil, fmt.Errorf("%w: registration body len=%d", ErrFrameTooShort, len(body))
|
||||
}
|
||||
registration := map[string]any{
|
||||
"province": binary.BigEndian.Uint16(body[0:2]),
|
||||
"city": binary.BigEndian.Uint16(body[2:4]),
|
||||
"manufacturer": fixedText(body[4:9]),
|
||||
"device_type": fixedText(body[9:29]),
|
||||
"device_id": fixedText(body[29:36]),
|
||||
"plate_color": body[36],
|
||||
"plate": freeText(body[37:]),
|
||||
}
|
||||
return registration, nil
|
||||
}
|
||||
|
||||
func parseAuthentication(body []byte) map[string]any {
|
||||
token := freeText(body)
|
||||
out := map[string]any{"token": token}
|
||||
return out
|
||||
}
|
||||
|
||||
func parseLocation(body []byte) (Location, error) {
|
||||
if len(body) < 28 {
|
||||
return Location{}, fmt.Errorf("%w: location body len=%d", ErrFrameTooShort, len(body))
|
||||
@@ -269,6 +313,14 @@ func bcdString(data []byte) string {
|
||||
return string(out)
|
||||
}
|
||||
|
||||
func fixedText(data []byte) string {
|
||||
return strings.Trim(freeText(data), "\x00 ")
|
||||
}
|
||||
|
||||
func freeText(data []byte) string {
|
||||
return strings.TrimRight(strings.TrimSpace(string(data)), "\x00")
|
||||
}
|
||||
|
||||
func parseBCDTime(data []byte) time.Time {
|
||||
if len(data) != 6 {
|
||||
return time.Time{}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jt808
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
@@ -114,6 +115,61 @@ func TestParseFrameSupportsJT8082019VersionedHeader(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFrameParsesRegistrationIdentity(t *testing.T) {
|
||||
body := make([]byte, 0, 46)
|
||||
body = binary.BigEndian.AppendUint16(body, 16)
|
||||
body = binary.BigEndian.AppendUint16(body, 32)
|
||||
body = appendFixedASCII(body, "YUTNG", 5)
|
||||
body = appendFixedASCII(body, "ZK6105CHEVNPG4", 20)
|
||||
body = appendFixedASCII(body, "DEV0001", 7)
|
||||
body = append(body, 2)
|
||||
body = append(body, []byte("TEST123")...)
|
||||
|
||||
env, err := ParseFrame(buildFrame(0x0100, "013079963379", 7, body), 1782918600000, "115.231.168.135:43625")
|
||||
if err != nil {
|
||||
t.Fatalf("ParseFrame() error = %v", err)
|
||||
}
|
||||
if env.MessageID != "0x0100" {
|
||||
t.Fatalf("message id = %q", env.MessageID)
|
||||
}
|
||||
if env.Phone != "013079963379" {
|
||||
t.Fatalf("phone = %q", env.Phone)
|
||||
}
|
||||
if env.DeviceID != "DEV0001" {
|
||||
t.Fatalf("device id = %q", env.DeviceID)
|
||||
}
|
||||
if env.Plate != "TEST123" {
|
||||
t.Fatalf("plate = %q", env.Plate)
|
||||
}
|
||||
registration, ok := env.Parsed["registration"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("registration missing: %#v", env.Parsed)
|
||||
}
|
||||
if registration["manufacturer"] != "YUTNG" || registration["device_type"] != "ZK6105CHEVNPG4" {
|
||||
t.Fatalf("registration fields = %#v", registration)
|
||||
}
|
||||
if registration["plate_color"] != uint8(2) {
|
||||
t.Fatalf("plate color = %#v", registration["plate_color"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFrameParsesAuthenticationToken(t *testing.T) {
|
||||
env, err := ParseFrame(buildFrame(0x0102, "064646848757", 8, []byte("g7gps")), 1782918600000, "115.231.168.135:43625")
|
||||
if err != nil {
|
||||
t.Fatalf("ParseFrame() error = %v", err)
|
||||
}
|
||||
if env.MessageID != "0x0102" {
|
||||
t.Fatalf("message id = %q", env.MessageID)
|
||||
}
|
||||
auth, ok := env.Parsed["authentication"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("authentication missing: %#v", env.Parsed)
|
||||
}
|
||||
if auth["token"] != "g7gps" {
|
||||
t.Fatalf("token = %#v", auth["token"])
|
||||
}
|
||||
}
|
||||
|
||||
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)))...)
|
||||
@@ -131,6 +187,41 @@ func TestExtractFramesHandlesEscapedPayload(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func buildFrame(messageID uint16, phone string, sequence uint16, body []byte) []byte {
|
||||
payload := make([]byte, 0, 12+len(body)+1)
|
||||
payload = binary.BigEndian.AppendUint16(payload, messageID)
|
||||
payload = binary.BigEndian.AppendUint16(payload, uint16(len(body)))
|
||||
payload = append(payload, encodeBCD(phone, 6)...)
|
||||
payload = binary.BigEndian.AppendUint16(payload, sequence)
|
||||
payload = append(payload, body...)
|
||||
payload = append(payload, checksum(payload))
|
||||
return payload
|
||||
}
|
||||
|
||||
func appendFixedASCII(out []byte, value string, size int) []byte {
|
||||
start := len(out)
|
||||
out = append(out, make([]byte, size)...)
|
||||
copy(out[start:], []byte(value))
|
||||
return out
|
||||
}
|
||||
|
||||
func encodeBCD(value string, size int) []byte {
|
||||
out := make([]byte, size)
|
||||
if len(value)%2 == 1 {
|
||||
value = "0" + value
|
||||
}
|
||||
if len(value) > size*2 {
|
||||
value = value[len(value)-size*2:]
|
||||
}
|
||||
for len(value) < size*2 {
|
||||
value = "0" + value
|
||||
}
|
||||
for i := 0; i < size; i++ {
|
||||
out[i] = (value[i*2]-'0')<<4 | (value[i*2+1] - '0')
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func assertFloatField(t *testing.T, env envelope.FrameEnvelope, key string, want float64) {
|
||||
t.Helper()
|
||||
got, ok := env.Fields[key].(float64)
|
||||
|
||||
Reference in New Issue
Block a user