Files
lingniu-vehicle-ingest/go/vehicle-gateway/internal/stats/pure_hydrogen.go
2026-07-27 16:46:15 +08:00

121 lines
2.9 KiB
Go

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
}
}