feat: expand vehicle data platform capabilities
This commit is contained in:
120
go/vehicle-gateway/internal/stats/pure_hydrogen.go
Normal file
120
go/vehicle-gateway/internal/stats/pure_hydrogen.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
|
||||
)
|
||||
|
||||
const (
|
||||
gb32960FuelCellEngineWorkMode = 2
|
||||
yutongFuelCellRunningMode = 4
|
||||
yutongFuelCellDrivingMode = 11
|
||||
)
|
||||
|
||||
// PureHydrogenModeFromEnvelope returns whether the reported vehicle is in a
|
||||
// fuel-cell-only work mode and whether the source value is understood.
|
||||
//
|
||||
// The 32960 fuel-cell extension reports engine_work_state; production frames
|
||||
// use mode 2 for normal fuel-cell operation. Yutong reports the equivalent
|
||||
// state through TRIANGLE_STATE; production frames use 1 for inactive and 4/11
|
||||
// for active fuel-cell operating modes.
|
||||
func PureHydrogenModeFromEnvelope(env envelope.FrameEnvelope) (active bool, known bool) {
|
||||
value, exists := env.Fields[envelope.FieldFuelCellWorkMode]
|
||||
if !exists {
|
||||
return false, false
|
||||
}
|
||||
mode, ok := integerMode(value)
|
||||
if !ok {
|
||||
return false, false
|
||||
}
|
||||
switch env.Protocol {
|
||||
case envelope.ProtocolGB32960:
|
||||
switch mode {
|
||||
case gb32960FuelCellEngineWorkMode:
|
||||
return true, true
|
||||
case 0, 1:
|
||||
return false, true
|
||||
default:
|
||||
return false, false
|
||||
}
|
||||
case envelope.ProtocolYutongMQTT:
|
||||
switch mode {
|
||||
case yutongFuelCellRunningMode, yutongFuelCellDrivingMode:
|
||||
return true, true
|
||||
case 1:
|
||||
return false, true
|
||||
default:
|
||||
return false, false
|
||||
}
|
||||
default:
|
||||
return false, false
|
||||
}
|
||||
}
|
||||
|
||||
// PureHydrogenMileageDelta returns the odometer delta that can be attributed
|
||||
// to pure-hydrogen operation. Both endpoints must report a known active mode;
|
||||
// this deliberately leaves transitions and missing mode data unclassified.
|
||||
func PureHydrogenMileageDelta(
|
||||
previousKM float64,
|
||||
currentKM float64,
|
||||
previousActive bool,
|
||||
previousKnown bool,
|
||||
currentActive bool,
|
||||
currentKnown bool,
|
||||
) float64 {
|
||||
delta := currentKM - previousKM
|
||||
if !previousKnown || !previousActive || !currentKnown || !currentActive {
|
||||
return 0
|
||||
}
|
||||
if delta < 0 || delta > maxSelectedDailyMileageKM {
|
||||
return 0
|
||||
}
|
||||
return delta
|
||||
}
|
||||
|
||||
func integerMode(value any) (int, bool) {
|
||||
switch typed := value.(type) {
|
||||
case json.Number:
|
||||
parsed, err := strconv.Atoi(typed.String())
|
||||
return parsed, err == nil
|
||||
case string:
|
||||
parsed, err := strconv.Atoi(strings.TrimSpace(typed))
|
||||
return parsed, err == nil
|
||||
case int:
|
||||
return typed, true
|
||||
case int8:
|
||||
return int(typed), true
|
||||
case int16:
|
||||
return int(typed), true
|
||||
case int32:
|
||||
return int(typed), true
|
||||
case int64:
|
||||
return int(typed), true
|
||||
case uint:
|
||||
return int(typed), true
|
||||
case uint8:
|
||||
return int(typed), true
|
||||
case uint16:
|
||||
return int(typed), true
|
||||
case uint32:
|
||||
return int(typed), true
|
||||
case uint64:
|
||||
if typed > uint64(^uint(0)>>1) {
|
||||
return 0, false
|
||||
}
|
||||
return int(typed), true
|
||||
case float32:
|
||||
parsed := int(typed)
|
||||
return parsed, float32(parsed) == typed
|
||||
case float64:
|
||||
parsed := int(typed)
|
||||
return parsed, float64(parsed) == typed
|
||||
default:
|
||||
parsed, err := strconv.Atoi(strings.TrimSpace(fmt.Sprint(value)))
|
||||
return parsed, err == nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user