121 lines
2.9 KiB
Go
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
|
|
}
|
|
}
|