fix: enrich jt808 location additional parsing
This commit is contained in:
@@ -331,23 +331,29 @@ func parseAdditional(data []byte, location *Location) []AdditionalItem {
|
|||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case id == 0x01 && size == 4:
|
case id == 0x01 && size == 4:
|
||||||
mileage := float64(binary.BigEndian.Uint32(value)) / 10
|
rawMileage := binary.BigEndian.Uint32(value)
|
||||||
|
mileage := float64(rawMileage) / 10
|
||||||
location.TotalMileageKM = &mileage
|
location.TotalMileageKM = &mileage
|
||||||
item.Parsed = map[string]any{
|
item.Parsed = map[string]any{
|
||||||
"name": envelope.FieldTotalMileageKM,
|
"name": envelope.FieldTotalMileageKM,
|
||||||
|
"raw_value_tenth_km": rawMileage,
|
||||||
"value": mileage,
|
"value": mileage,
|
||||||
"unit": "km",
|
"unit": "km",
|
||||||
}
|
}
|
||||||
case id == 0x02 && size == 2:
|
case id == 0x02 && size == 2:
|
||||||
|
rawFuel := binary.BigEndian.Uint16(value)
|
||||||
item.Parsed = map[string]any{
|
item.Parsed = map[string]any{
|
||||||
"name": "fuel_l",
|
"name": "fuel_l",
|
||||||
"value": float64(binary.BigEndian.Uint16(value)) / 10,
|
"raw_value_tenth_l": rawFuel,
|
||||||
|
"value": float64(rawFuel) / 10,
|
||||||
"unit": "L",
|
"unit": "L",
|
||||||
}
|
}
|
||||||
case id == 0x03 && size == 2:
|
case id == 0x03 && size == 2:
|
||||||
|
rawSpeed := binary.BigEndian.Uint16(value)
|
||||||
item.Parsed = map[string]any{
|
item.Parsed = map[string]any{
|
||||||
"name": "recorder_speed_kmh",
|
"name": "recorder_speed_kmh",
|
||||||
"value": float64(binary.BigEndian.Uint16(value)) / 10,
|
"raw_value_tenth_kmh": rawSpeed,
|
||||||
|
"value": float64(rawSpeed) / 10,
|
||||||
"unit": "km/h",
|
"unit": "km/h",
|
||||||
}
|
}
|
||||||
case id == 0x04 && size == 2:
|
case id == 0x04 && size == 2:
|
||||||
@@ -355,21 +361,55 @@ func parseAdditional(data []byte, location *Location) []AdditionalItem {
|
|||||||
"name": "manual_alarm_event_id",
|
"name": "manual_alarm_event_id",
|
||||||
"value": binary.BigEndian.Uint16(value),
|
"value": binary.BigEndian.Uint16(value),
|
||||||
}
|
}
|
||||||
|
case id == 0x05 && size == 30:
|
||||||
|
item.Parsed = map[string]any{
|
||||||
|
"name": "tire_pressure",
|
||||||
|
"values": parseTirePressure(value),
|
||||||
|
}
|
||||||
case id == 0x06 && size == 2:
|
case id == 0x06 && size == 2:
|
||||||
item.Parsed = map[string]any{
|
item.Parsed = map[string]any{
|
||||||
"name": "carriage_temperature_c",
|
"name": "carriage_temperature_c",
|
||||||
"value": int16(binary.BigEndian.Uint16(value)),
|
"value": int16(binary.BigEndian.Uint16(value)),
|
||||||
"unit": "C",
|
"unit": "C",
|
||||||
}
|
}
|
||||||
|
case id == 0x11 && (size == 1 || size == 5):
|
||||||
|
parsed := map[string]any{
|
||||||
|
"name": "overspeed_alarm",
|
||||||
|
"location_type": value[0],
|
||||||
|
}
|
||||||
|
if size == 5 {
|
||||||
|
parsed["area_id"] = binary.BigEndian.Uint32(value[1:5])
|
||||||
|
}
|
||||||
|
item.Parsed = parsed
|
||||||
|
case id == 0x12 && size == 6:
|
||||||
|
item.Parsed = map[string]any{
|
||||||
|
"name": "area_route_alarm",
|
||||||
|
"location_type": value[0],
|
||||||
|
"area_id": binary.BigEndian.Uint32(value[1:5]),
|
||||||
|
"direction": value[5],
|
||||||
|
}
|
||||||
|
case id == 0x13 && size == 7:
|
||||||
|
item.Parsed = map[string]any{
|
||||||
|
"name": "road_section_driving_time_alarm",
|
||||||
|
"road_section_id": binary.BigEndian.Uint32(value[0:4]),
|
||||||
|
"driving_time_seconds": binary.BigEndian.Uint16(value[4:6]),
|
||||||
|
"result": value[6],
|
||||||
|
"result_description": roadSectionDrivingTimeResult(value[6]),
|
||||||
|
"driving_time_result_source": "jt808_0x0200_additional_0x13",
|
||||||
|
}
|
||||||
case id == 0x25 && size == 4:
|
case id == 0x25 && size == 4:
|
||||||
|
rawStatus := binary.BigEndian.Uint32(value)
|
||||||
item.Parsed = map[string]any{
|
item.Parsed = map[string]any{
|
||||||
"name": "extended_vehicle_signal_status",
|
"name": "extended_vehicle_signal_status",
|
||||||
"value": binary.BigEndian.Uint32(value),
|
"value": rawStatus,
|
||||||
|
"bits": parseExtendedVehicleSignal(rawStatus),
|
||||||
}
|
}
|
||||||
case id == 0x2a && size == 2:
|
case id == 0x2a && size == 2:
|
||||||
|
rawStatus := binary.BigEndian.Uint16(value)
|
||||||
item.Parsed = map[string]any{
|
item.Parsed = map[string]any{
|
||||||
"name": "io_status",
|
"name": "io_status",
|
||||||
"value": binary.BigEndian.Uint16(value),
|
"value": rawStatus,
|
||||||
|
"bits": parseIOStatus(rawStatus),
|
||||||
}
|
}
|
||||||
case id == 0x2b && size == 4:
|
case id == 0x2b && size == 4:
|
||||||
analog := binary.BigEndian.Uint32(value)
|
analog := binary.BigEndian.Uint32(value)
|
||||||
@@ -432,6 +472,55 @@ func additionalToMaps(items []AdditionalItem) []map[string]any {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseTirePressure(data []byte) []map[string]any {
|
||||||
|
out := make([]map[string]any, 0, len(data))
|
||||||
|
for index, value := range data {
|
||||||
|
out = append(out, map[string]any{
|
||||||
|
"position": index,
|
||||||
|
"value": value,
|
||||||
|
"valid": value != 0xff,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseExtendedVehicleSignal(value uint32) map[string]bool {
|
||||||
|
return map[string]bool{
|
||||||
|
"low_beam": value&0x0001 != 0,
|
||||||
|
"high_beam": value&0x0002 != 0,
|
||||||
|
"right_turn": value&0x0004 != 0,
|
||||||
|
"left_turn": value&0x0008 != 0,
|
||||||
|
"brake": value&0x0010 != 0,
|
||||||
|
"reverse_gear": value&0x0020 != 0,
|
||||||
|
"fog_light": value&0x0040 != 0,
|
||||||
|
"clearance_light": value&0x0080 != 0,
|
||||||
|
"horn": value&0x0100 != 0,
|
||||||
|
"air_conditioner": value&0x0200 != 0,
|
||||||
|
"neutral": value&0x0400 != 0,
|
||||||
|
"retarder": value&0x0800 != 0,
|
||||||
|
"abs": value&0x1000 != 0,
|
||||||
|
"heater": value&0x2000 != 0,
|
||||||
|
"clutch": value&0x4000 != 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseIOStatus(value uint16) map[string]bool {
|
||||||
|
return map[string]bool{
|
||||||
|
"deep_sleep": value&0x0001 != 0,
|
||||||
|
"sleep": value&0x0002 != 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func roadSectionDrivingTimeResult(value byte) string {
|
||||||
|
if value == 0 {
|
||||||
|
return "insufficient"
|
||||||
|
}
|
||||||
|
if value == 1 {
|
||||||
|
return "too_long"
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
func bcdString(data []byte) string {
|
func bcdString(data []byte) string {
|
||||||
out := make([]byte, 0, len(data)*2)
|
out := make([]byte, 0, len(data)*2)
|
||||||
for _, value := range data {
|
for _, value := range data {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
const sampleLocationFrame = "7E020000320133077954250001000000000048000301D2C4C707376139000A00E6004F26063016235701040001900C2504000000000202000030011F31010F867E"
|
const sampleLocationFrame = "7E020000320133077954250001000000000048000301D2C4C707376139000A00E6004F26063016235701040001900C2504000000000202000030011F31010F867E"
|
||||||
const real2019LocationFrame = "7E02004046010000000001489413506007CB00000000004C000301D276AB06FBFF4B000D014E00E826070122092214040000000017020000010400013361030201402504000000012A02000030011D310112EA0402008000C57E"
|
const real2019LocationFrame = "7E02004046010000000001489413506007CB00000000004C000301D276AB06FBFF4B000D014E00E826070122092214040000000017020000010400013361030201402504000000012A02000030011D310112EA0402008000C57E"
|
||||||
|
const fullAdditionalLocationFrame = "7e020000800123456789017fff000004000000080006eeb6ad02633df701380003006320070719235901040000000b02020016030200210402002c051e3737370000000000000000000000000000000000000000000000000000001105420000004212064d0000004d4d1307000000580058582504000000632a02000a2b040000001430011e31012806020001927e"
|
||||||
|
|
||||||
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,13 @@ func TestExtractFramesUnescapesAndParsesLocationWithTotalMileage(t *testing.T) {
|
|||||||
if additional[0]["id"] != "0x01" || additional[0]["value_hex"] != "0001900C" {
|
if additional[0]["id"] != "0x01" || additional[0]["value_hex"] != "0001900C" {
|
||||||
t.Fatalf("unexpected first additional item: %#v", additional[0])
|
t.Fatalf("unexpected first additional item: %#v", additional[0])
|
||||||
}
|
}
|
||||||
|
parsed, ok := additional[0]["parsed"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("mileage parsed field missing: %#v", additional[0])
|
||||||
|
}
|
||||||
|
if parsed["raw_value_tenth_km"] != uint32(102412) || parsed["value"] != 10241.2 {
|
||||||
|
t.Fatalf("unexpected mileage parsed value: %#v", parsed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseFrameSupportsJT8082019VersionedHeader(t *testing.T) {
|
func TestParseFrameSupportsJT8082019VersionedHeader(t *testing.T) {
|
||||||
@@ -115,6 +123,59 @@ func TestParseFrameSupportsJT8082019VersionedHeader(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseFrameParsesCommonLocationAdditionalItems(t *testing.T) {
|
||||||
|
stream, err := hex.DecodeString(fullAdditionalLocationFrame)
|
||||||
|
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, "127.0.0.1:808")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParseFrame() error = %v", err)
|
||||||
|
}
|
||||||
|
if env.Phone != "012345678901" {
|
||||||
|
t.Fatalf("phone = %q", env.Phone)
|
||||||
|
}
|
||||||
|
assertFloatField(t, env, envelope.FieldTotalMileageKM, 1.1)
|
||||||
|
|
||||||
|
location := env.Parsed["location"].(map[string]any)
|
||||||
|
additional := location["additional"].([]map[string]any)
|
||||||
|
byID := map[string]map[string]any{}
|
||||||
|
for _, item := range additional {
|
||||||
|
byID[item["id"].(string)] = item["parsed"].(map[string]any)
|
||||||
|
}
|
||||||
|
if byID["0x01"]["raw_value_tenth_km"] != uint32(11) || byID["0x01"]["value"] != 1.1 {
|
||||||
|
t.Fatalf("mileage parsed = %#v", byID["0x01"])
|
||||||
|
}
|
||||||
|
if byID["0x05"]["name"] != "tire_pressure" {
|
||||||
|
t.Fatalf("tire pressure missing: %#v", byID["0x05"])
|
||||||
|
}
|
||||||
|
tireValues := byID["0x05"]["values"].([]map[string]any)
|
||||||
|
if tireValues[0]["value"] != byte(0x37) || tireValues[0]["valid"] != true {
|
||||||
|
t.Fatalf("tire pressure first value = %#v", tireValues[0])
|
||||||
|
}
|
||||||
|
if byID["0x11"]["location_type"] != byte(0x42) || byID["0x11"]["area_id"] != uint32(0x42) {
|
||||||
|
t.Fatalf("overspeed parsed = %#v", byID["0x11"])
|
||||||
|
}
|
||||||
|
if byID["0x12"]["location_type"] != byte(0x4d) || byID["0x12"]["area_id"] != uint32(0x4d) || byID["0x12"]["direction"] != byte(0x4d) {
|
||||||
|
t.Fatalf("area route parsed = %#v", byID["0x12"])
|
||||||
|
}
|
||||||
|
if byID["0x13"]["road_section_id"] != uint32(0x58) || byID["0x13"]["driving_time_seconds"] != uint16(0x58) {
|
||||||
|
t.Fatalf("road section parsed = %#v", byID["0x13"])
|
||||||
|
}
|
||||||
|
ioBits := byID["0x2A"]["bits"].(map[string]bool)
|
||||||
|
if ioBits["deep_sleep"] || !ioBits["sleep"] {
|
||||||
|
t.Fatalf("io bits = %#v", ioBits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseFrameRejectsTruncatedSubpackageHeader(t *testing.T) {
|
func TestParseFrameRejectsTruncatedSubpackageHeader(t *testing.T) {
|
||||||
payload := []byte{0x02, 0x00, 0x20, 0x00}
|
payload := []byte{0x02, 0x00, 0x20, 0x00}
|
||||||
payload = append(payload, encodeBCD("013307795425", 6)...)
|
payload = append(payload, encodeBCD("013307795425", 6)...)
|
||||||
|
|||||||
Reference in New Issue
Block a user