171 lines
4.8 KiB
Go
171 lines
4.8 KiB
Go
package telemetry
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
|
|
)
|
|
|
|
type LocationProjection struct {
|
|
Latitude float64
|
|
Longitude float64
|
|
SpeedKMH *float64
|
|
SOCPercent *float64
|
|
AltitudeM *float64
|
|
DirectionDeg *float64
|
|
AlarmFlag *int64
|
|
StatusFlag *int64
|
|
}
|
|
|
|
type locationFieldMapping struct {
|
|
Latitude []string
|
|
Longitude []string
|
|
SpeedKMH []string
|
|
SOCPercent []string
|
|
AltitudeM []string
|
|
DirectionDeg []string
|
|
AlarmFlag []string
|
|
StatusFlag []string
|
|
}
|
|
|
|
// HasRealtimeFields separates protocol telemetry from headers, identity
|
|
// annotations, and transport metadata before realtime/stat projections.
|
|
func HasRealtimeFields(protocol envelope.Protocol, fields map[string]any) bool {
|
|
switch protocol {
|
|
case envelope.ProtocolGB32960:
|
|
for field := range fields {
|
|
if !strings.HasPrefix(field, "gb32960.") {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(field, "gb32960.header.") ||
|
|
strings.HasPrefix(field, "gb32960.platform.") ||
|
|
strings.HasPrefix(field, "gb32960.identity.") ||
|
|
strings.HasPrefix(field, "gb32960.device_time.") {
|
|
continue
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
case envelope.ProtocolJT808:
|
|
return hasPrefix(fields, "jt808.location.")
|
|
case envelope.ProtocolYutongMQTT:
|
|
return hasPrefix(fields, "yutong_mqtt.data.") ||
|
|
hasPrefix(fields, "yutong_mqtt.root.data.")
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// LocationProjectionForProtocol is the single protocol-field contract used by
|
|
// storage projections. It intentionally ignores bare standardized field names.
|
|
func LocationProjectionForProtocol(protocol envelope.Protocol, fields map[string]any) (LocationProjection, bool) {
|
|
mapping := locationMapping(protocol)
|
|
latitude, hasLatitude := firstNumber(fields, mapping.Latitude)
|
|
longitude, hasLongitude := firstNumber(fields, mapping.Longitude)
|
|
if !hasLatitude || !hasLongitude {
|
|
return LocationProjection{}, false
|
|
}
|
|
return LocationProjection{
|
|
Latitude: latitude,
|
|
Longitude: longitude,
|
|
SpeedKMH: numberPointer(fields, mapping.SpeedKMH),
|
|
SOCPercent: numberPointer(fields, mapping.SOCPercent),
|
|
AltitudeM: numberPointer(fields, mapping.AltitudeM),
|
|
DirectionDeg: numberPointer(fields, mapping.DirectionDeg),
|
|
AlarmFlag: integerPointer(fields, mapping.AlarmFlag),
|
|
StatusFlag: integerPointer(fields, mapping.StatusFlag),
|
|
}, true
|
|
}
|
|
|
|
func locationMapping(protocol envelope.Protocol) locationFieldMapping {
|
|
switch protocol {
|
|
case envelope.ProtocolGB32960:
|
|
return locationFieldMapping{
|
|
Latitude: []string{"gb32960.position.latitude"},
|
|
Longitude: []string{"gb32960.position.longitude"},
|
|
SpeedKMH: []string{"gb32960.vehicle.speed_kmh"},
|
|
SOCPercent: []string{"gb32960.vehicle.soc_percent"},
|
|
}
|
|
case envelope.ProtocolJT808:
|
|
return locationFieldMapping{
|
|
Latitude: []string{"jt808.location.latitude"},
|
|
Longitude: []string{"jt808.location.longitude"},
|
|
SpeedKMH: []string{"jt808.location.speed_kmh"},
|
|
AltitudeM: []string{"jt808.location.altitude_m"},
|
|
DirectionDeg: []string{"jt808.location.direction_deg"},
|
|
AlarmFlag: []string{"jt808.location.alarm_flag"},
|
|
StatusFlag: []string{"jt808.location.status_flag"},
|
|
}
|
|
case envelope.ProtocolYutongMQTT:
|
|
return locationFieldMapping{
|
|
Latitude: []string{
|
|
"yutong_mqtt.data.latitude",
|
|
"yutong_mqtt.root.data.latitude",
|
|
},
|
|
Longitude: []string{
|
|
"yutong_mqtt.data.longitude",
|
|
"yutong_mqtt.root.data.longitude",
|
|
},
|
|
SpeedKMH: []string{
|
|
"yutong_mqtt.data.meter_speed",
|
|
"yutong_mqtt.root.data.meter_speed",
|
|
"yutong_mqtt.data.speed_kmh",
|
|
"yutong_mqtt.root.data.speed_kmh",
|
|
"yutong_mqtt.data.speed",
|
|
"yutong_mqtt.root.data.speed",
|
|
},
|
|
SOCPercent: []string{
|
|
"yutong_mqtt.data.battery_capacity_soc",
|
|
"yutong_mqtt.root.data.battery_capacity_soc",
|
|
"yutong_mqtt.data.soc_percent",
|
|
"yutong_mqtt.root.data.soc_percent",
|
|
},
|
|
DirectionDeg: []string{
|
|
"yutong_mqtt.data.gpsdirection",
|
|
"yutong_mqtt.root.data.gpsdirection",
|
|
"yutong_mqtt.data.direction_deg",
|
|
"yutong_mqtt.root.data.direction_deg",
|
|
"yutong_mqtt.data.direction",
|
|
"yutong_mqtt.root.data.direction",
|
|
},
|
|
}
|
|
default:
|
|
return locationFieldMapping{}
|
|
}
|
|
}
|
|
|
|
func firstNumber(fields map[string]any, keys []string) (float64, bool) {
|
|
for _, key := range keys {
|
|
if value, ok := Number(fields, key); ok {
|
|
return value, true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func numberPointer(fields map[string]any, keys []string) *float64 {
|
|
value, ok := firstNumber(fields, keys)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return &value
|
|
}
|
|
|
|
func integerPointer(fields map[string]any, keys []string) *int64 {
|
|
value, ok := firstNumber(fields, keys)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
integer := int64(value)
|
|
return &integer
|
|
}
|
|
|
|
func hasPrefix(fields map[string]any, prefix string) bool {
|
|
for field := range fields {
|
|
if strings.HasPrefix(field, prefix) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|