Files
cloud-services/pkg/cache/vehicle_state.go

584 lines
18 KiB
Go

package cache
import (
"fmt"
"strconv"
"strings"
"time"
"fiskerinc.com/modules/common"
dt "fiskerinc.com/modules/dbc/state"
"fiskerinc.com/modules/logger"
"fiskerinc.com/modules/redis"
"fiskerinc.com/modules/utils/querystring"
redigo "github.com/gomodule/redigo/redis"
"github.com/pkg/errors"
)
const UPDATED_TIME_FORMAT = "2006-01-02T15:04:05Z"
type stateParser func(state *common.CarState, key string, value []byte) error
func NewVehicleState(client redis.ClientPoolInterface) *VehicleState {
return &VehicleState{client: client}
}
type VehicleState struct {
client redis.ClientPoolInterface
}
func (v *VehicleState) Get(vin string) (common.CarState, error) {
var state common.CarState
values, err := v.queryVehicleState(vin)
if err != nil {
return state, err
}
state, err = v.ParsePayloadForVehicleState(values)
if err != nil {
return state, err
}
return state, nil
}
func (v *VehicleState) queryVehicleState(vin string) ([]interface{}, error) {
var payload []interface{}
client := v.client.GetFromPool()
defer client.Close()
batch := redis.NewRedisBatchCommands()
batch.Add("SISMEMBER", redis.CarSessionsKey(), vin)
batch.Add("SISMEMBER", redis.HMISessionsKey(), vin)
batch.Add("HGETALL", redis.CarStateHashKey(vin))
payload, err := redigo.Values(client.ExecuteBatch(batch))
if err != nil {
return payload, errors.WithStack(err)
}
return payload, nil
}
func (v *VehicleState) ParsePayloadForVehicleState(payload []interface{}) (common.CarState, error) {
var state common.CarState
if len(payload) != 3 {
return state, redis.ErrInvalidResults
}
online, err := redigo.Bool(payload[0], nil)
if err != nil {
return state, err
} else {
state.Online = online
}
online, err = redigo.Bool(payload[1], nil)
if err != nil {
return state, errors.WithStack(err)
} else {
state.OnlineHMI = online
}
err = v.parseCarStatePayload(&state, payload[2])
return state, err
}
func (v *VehicleState) parseCarStatePayload(state *common.CarState, payload interface{}) error {
stateValues, err := redigo.Values(payload, nil)
if err != nil {
return errors.WithStack(err)
}
if len(stateValues)%2 != 0 {
return errors.New("object does not contain equal number of key value pairs")
}
err = v.parseStateValues(state, stateValues, parseCarState)
return err
}
func (v *VehicleState) parseStateValues(state *common.CarState, stateValues []interface{}, parser stateParser) error {
for i := 0; i < len(stateValues); i += 2 {
key, okKey := stateValues[i].([]byte)
value, okValue := stateValues[i+1].([]byte)
if !okKey || !okValue {
return errors.New("cannot parse object into car state")
}
err := parser(state, string(key), value)
// log error, do not return error so we can read other properties for digital twin
if err != nil {
logger.Err(err).Send()
}
}
return nil
}
func parseCarState(state *common.CarState, key string, value []byte) error {
var err error
val := string(value)
switch key {
case dt.VCU_VehChrgDchgMod:
state.GetVCU0x260().ChargeType = string(value)
case dt.BMS_Bat_SoC_usable:
state.GetStateOfCharge().Usable, err = strconv.Atoi(val)
case dt.BMS_Bat_SOH:
state.GetStateOfCharge().Health, err = strconv.Atoi(val)
case dt.BCM_AP_FL_LeFrntWinPosnInfo:
state.GetWindows().LeftFront, err = strconv.Atoi(val)
case dt.BCM_AP_FL_RiFrntWinPosnInfo:
state.GetWindows().RightFront, err = strconv.Atoi(val)
case dt.BCM_AP_FL_LeReWinPosnInfo:
state.GetWindows().LeftRear, err = strconv.Atoi(val)
case dt.BCM_AP_FL_RiReWinPosnInfo:
state.GetWindows().RightRear, err = strconv.Atoi(val)
case dt.BMS_PwrBattRmngCpSOC:
state.GetBattery().Percent, err = strconv.Atoi(val)
case dt.BCM_ReDefrstHeatgCmd:
state.GetRearDefrost().On, err = strconv.ParseBool(val)
case dt.BCM_PasFrntDoorSts:
state.GetDoors().RightFront, err = strconv.ParseBool(val)
case dt.BCM_DrFrntDoorSts:
state.GetDoors().LeftFront, err = strconv.ParseBool(val)
case dt.BCM_FrntDrDoorLockSts:
state.GetLocks().Driver, err = notValue(strconv.ParseBool(val))
case dt.BCM_CenLockSwtSts:
state.GetLocks().All = (val == "2")
case dt.BCM_RiReDoorSts:
state.GetDoors().RightRear, err = strconv.ParseBool(val)
case dt.BCM_LeReDoorSts:
state.GetDoors().LeftRear, err = strconv.ParseBool(val)
case dt.BCM_FrntHoodLidSts:
state.GetDoors().Hood, err = strconv.ParseBool(val)
case dt.PLGM_TrSts:
state.GetDoors().Trunk, err = strconv.ParseBool(val)
case dt.BCM_SunroofPosnInfo:
state.GetSunroof().Sunroof, err = strconv.Atoi(val)
case dt.BCM_AP_TL_LeReWinPosnInfo:
state.GetMiscWindows().LeftRearQuarter, err = strconv.Atoi(val)
case dt.BCM_AP_TL_RiReWinPosnInfo:
state.GetMiscWindows().RightRearQuarter, err = strconv.Atoi(val)
case dt.BCM_AP_RW_WinPosnInfo:
state.GetMiscWindows().RearWindshield, err = strconv.Atoi(val)
case dt.BMS_BattAvrgT:
state.GetCellTemperature().AvgBatteryTemp, err = strconv.Atoi(val)
case dt.ECC_OutdT:
state.GetAmbientTemperature().Temperature, err = strconv.Atoi(val)
case dt.BCM_HeatedSteerWhlSt:
state.GetSteeringWheelHeat().On, err = strconv.ParseBool(val)
case dt.ESP_VehSpd:
state.GetVehicleSpeed().Speed, err = strconv.ParseFloat(val, 64)
case dt.VCU_DrvgMilg:
state.GetMaxRange().MaxMiles, err = strconv.Atoi(val)
case dt.PSM_PassSeatHeatgSts:
state.GetPassengerSeatHeat().Level, err = strconv.Atoi(val)
case dt.DSMC_DrvrSeatHeatgSts:
state.GetDriverSeatHeat().Level, err = strconv.Atoi(val)
case dt.ICC_TotMilg_ODO:
state.GetBattery().TotalMileageOdometer, err = querystring.ConvertStringToInt(val)
case dt.VCU_DCChrgRmngTi, dt.BMS_RmChrgTi_TrgtSoC:
state.GetChargingMetrics().RemainingChargingTime, err = strconv.Atoi(val)
case dt.IBS_BatteryVoltage:
state.GetBattery().BatteryVoltage, err = strconv.ParseFloat(val, 64)
state.GetBattery12V().IBS_BatteryVoltage = ref(state.GetBattery().BatteryVoltage)
case dt.VCU_GearSig:
var gear int
gear, err = strconv.Atoi(val)
state.GetGear().InPark = (gear <= 2)
case dt.BMS_RmChrgTi_FullChrg:
state.GetChargingMetrics().RemainingChargingTimeFull, err = strconv.Atoi(val)
case dt.ECC_InsdT:
state.GetCabinClimate().InternalTemperature, err = strconv.Atoi(val)
case dt.ECC_RemTSetSts:
state.GetCabinClimate().CabinTemperature, err = strconv.Atoi(val)
case dt.TBOX_GPSHei:
state.GetLocation().Altitude, err = strconv.ParseFloat(val, 64)
case dt.TBOX_GPSLongi:
state.GetLocation().Longitude, err = strconv.ParseFloat(val, 64)
case dt.TBOX_GPSLati:
state.GetLocation().Latitude, err = strconv.ParseFloat(val, 64)
case dt.DBC_VERSION:
state.DBCVersion = val
case dt.TREX_VERSION:
state.TRexVersion = val
case dt.TREX_IP:
state.IP = val
case dt.UPDATED_AT:
var t time.Time
t, err = time.Parse(UPDATED_TIME_FORMAT, strings.Trim(val, "\""))
if !t.IsZero() {
state.UpdatedAt = ref(t)
}
case dt.VCU_VehSt:
state.GetSafeState().VehicleSafeState = val == dt.VCU_VehSt_Safestate
case dt.VCU_VcuState:
state.GetSafeState().VCUSafeState = val == dt.VCU_VcuState_Safestate
case dt.MCU_F_ActSafeSt:
state.GetSafeState().MCUFrontSafeState = val == dt.MCU_F_ActSafeSt_AS0 || val == dt.MCU_F_ActSafeSt_ASC || val == dt.MCU_F_ActSafeSt_ASC_Emergency
case dt.MCU_R_ActSafeSt:
state.GetSafeState().MCURearSafeState = val == dt.MCU_R_ActSafeSt_AS0 || val == dt.MCU_R_ActSafeSt_ASC || val == dt.MCU_R_ActSafeSt_ASC_Emergency
case dt.MCU_R_Decoup_State:
state.GetSafeState().MCURearDecoupState = val == dt.MCU_R_Decoup_State_Connected
case dt.MCU_F_CrtMod:
state.GetSafeState().MCUFrontInverterError = val == dt.MCU_F_CrtMod_Internal_inverter_error || val == dt.MCU_F_CrtMod_Invalid
case dt.MCU_R_CrtMod:
state.GetSafeState().MCURearInverterError = val == dt.MCU_R_CrtMod_Internal_inverter_error || val == dt.MCU_R_CrtMod_Invalid
case dt.ACU_Drvr_Occpt_St:
var vi int
vi, err = strconv.Atoi(val)
state.DriverOccupySeatState = ref(vi)
case dt.BCM_PwrMod:
var vi int
vi, err = strconv.Atoi(val)
state.PowerMode = ref(vi)
case dt.PWC_ChrgSts:
var vi int
vi, err = strconv.Atoi(val)
state.ChargingStatus = ref(vi)
case dt.VCU_RdyLamp:
state.GetVehicleReadyState().IsVehicleReady, err = strconv.ParseBool(val)
// New untested signals
// case dt.IBS_SOCUpperTolerance:
// var vi float64
// vi, err = strconv.ParseFloat(val, 64)
// state.GetExpandedSignals().IBS_SOCUpperTolerance = ref(vi)
// case dt.IBS_SOCLowerTolerance:
// var vi float64
// vi, err = strconv.ParseFloat(val, 64)
// state.GetExpandedSignals().IBS_SOCLowerTolerance = ref(vi)
case dt.IBS_StateOfCharge:
var vi float64
vi, err = strconv.ParseFloat(val, 64)
state.GetBattery12V().IBS_StateOfCharge = ref(vi)
case dt.IBS_StateOfHealth:
var vi int
vi, err = strconv.Atoi(val)
state.GetBattery12V().IBS_StateOfHealth = ref(vi)
case dt.IBS_NominalCapacity:
var vi int
vi, err = strconv.Atoi(val)
state.GetExpandedSignals().IBS_NominalCapacity = ref(vi)
case dt.IBS_AvailableCapacity:
var vi int
vi, err = strconv.Atoi(val)
state.GetExpandedSignals().IBS_AvailableCapacity = ref(vi)
case dt.BCM_TotMilg_ODO:
var vi float64
vi, err = strconv.ParseFloat(val, 64)
state.GetExpandedSignals().BCM_TotMilg_ODO = ref(vi)
case dt.BMS_SwVersS:
var vi int
vi, err = strconv.Atoi(val)
state.GetExpandedSignals().BMS_SwVersS = ref(vi)
case dt.BMS_SwVersM:
var vi int
vi, err = strconv.Atoi(val)
state.GetExpandedSignals().BMS_SwVersM = ref(vi)
case dt.BMS_SwVers:
var vi int
vi, err = strconv.Atoi(val)
state.GetExpandedSignals().BMS_SwVers = ref(vi)
case dt.BMS_AccueDchaTotAh:
var vi int
vi, err = strconv.Atoi(val)
state.GetExpandedSignals().BMS_AccueDchaTotAh = ref(vi)
case dt.BMS_AccueChrgTotAh:
var vi int
vi, err = strconv.Atoi(val)
state.GetExpandedSignals().BMS_AccueChrgTotAh = ref(vi)
case dt.TBOX_Heading:
state.GetLocation().Heading, err = strconv.ParseFloat(val, 64)
case dt.PKC_KeyStsMod:
state.GetGear().Immobilizer = val
}
return errors.WithStack(err)
}
func ParseCarState(state *common.CarState, key string, value interface{}) (err error) {
found := false
ok := false
switch key {
case dt.VCU_VehChrgDchgMod:
found = true
state.GetVCU0x260().ChargeType = value.(string)
case dt.BMS_Bat_SoC_usable:
found = true
state.GetStateOfCharge().Usable, ok = value.(int)
case dt.BMS_Bat_SOH:
found = true
state.GetStateOfCharge().Health, ok = value.(int)
case dt.BCM_AP_FL_LeFrntWinPosnInfo:
found = true
state.GetWindows().LeftFront, ok = value.(int)
case dt.BCM_AP_FL_RiFrntWinPosnInfo:
found = true
state.GetWindows().RightFront, ok = value.(int)
case dt.BCM_AP_FL_LeReWinPosnInfo:
found = true
state.GetWindows().LeftRear, ok = value.(int)
case dt.BCM_AP_FL_RiReWinPosnInfo:
found = true
state.GetWindows().RightRear, ok = value.(int)
case dt.BMS_PwrBattRmngCpSOC:
found = true
state.GetBattery().Percent, ok = value.(int)
case dt.BCM_ReDefrstHeatgCmd:
found = true
state.GetRearDefrost().On, ok = value.(bool)
case dt.BCM_PasFrntDoorSts:
found = true
state.GetDoors().RightFront, ok = value.(bool)
case dt.BCM_DrFrntDoorSts:
found = true
state.GetDoors().LeftFront, ok = value.(bool)
case dt.BCM_FrntDrDoorLockSts:
found = true
var vv bool
vv, ok = value.(bool)
state.GetLocks().Driver = !vv
case dt.BCM_CenLockSwtSts:
found = true
state.GetLocks().All = strconv.Itoa(value.(int)) == "2"
case dt.BCM_RiReDoorSts:
found = true
state.GetDoors().RightRear, ok = value.(bool)
case dt.BCM_LeReDoorSts:
found = true
state.GetDoors().LeftRear, ok = value.(bool)
case dt.BCM_FrntHoodLidSts:
found = true
state.GetDoors().Hood, ok = value.(bool)
case dt.PLGM_TrSts:
found = true
state.GetDoors().Trunk, ok = value.(bool)
case dt.BCM_SunroofPosnInfo:
found = true
state.GetSunroof().Sunroof, ok = value.(int)
case dt.BCM_AP_TL_LeReWinPosnInfo:
found = true
state.GetMiscWindows().LeftRearQuarter, ok = value.(int)
case dt.BCM_AP_TL_RiReWinPosnInfo:
found = true
state.GetMiscWindows().RightRearQuarter, ok = value.(int)
case dt.BCM_AP_RW_WinPosnInfo:
found = true
state.GetMiscWindows().RearWindshield, ok = value.(int)
case dt.BMS_BattAvrgT:
found = true
state.GetCellTemperature().AvgBatteryTemp, ok = value.(int)
case dt.ECC_OutdT:
found = true
state.GetAmbientTemperature().Temperature, ok = value.(int)
case dt.BCM_HeatedSteerWhlSt:
found = true
state.GetSteeringWheelHeat().On, ok = value.(bool)
case dt.ESP_VehSpd:
found = true
state.GetVehicleSpeed().Speed, ok = value.(float64)
case dt.VCU_DrvgMilg:
found = true
state.GetMaxRange().MaxMiles, ok = value.(int)
case dt.PSM_PassSeatHeatgSts:
found = true
state.GetPassengerSeatHeat().Level, ok = value.(int)
case dt.DSMC_DrvrSeatHeatgSts:
found = true
state.GetDriverSeatHeat().Level, ok = value.(int)
case dt.ICC_TotMilg_ODO:
found = true
// Seems wierd its sometimes an int, sometimes a float
state.GetBattery().TotalMileageOdometer, ok = value.(int)
case dt.VCU_DCChrgRmngTi, dt.BMS_RmChrgTi_TrgtSoC:
found = true
state.GetChargingMetrics().RemainingChargingTime, ok = value.(int)
case dt.IBS_BatteryVoltage:
found = true
state.GetBattery().BatteryVoltage, ok = value.(float64)
state.GetBattery12V().IBS_BatteryVoltage = ref(state.GetBattery().BatteryVoltage)
case dt.VCU_GearSig:
found = true
var gear int
gear, ok = value.(int)
state.GetGear().InPark = (gear <= 2)
case dt.BMS_RmChrgTi_FullChrg:
found = true
state.GetChargingMetrics().RemainingChargingTimeFull, ok = value.(int)
case dt.ECC_InsdT:
found = true
state.GetCabinClimate().InternalTemperature, ok = value.(int)
case dt.ECC_RemTSetSts:
found = true
state.GetCabinClimate().CabinTemperature, ok = value.(int)
case dt.TBOX_GPSHei:
found = true
state.GetLocation().Altitude, ok = value.(float64)
case dt.TBOX_GPSLongi:
found = true
state.GetLocation().Longitude, ok = value.(float64)
case dt.TBOX_GPSLati:
found = true
state.GetLocation().Latitude, ok = value.(float64)
case dt.DBC_VERSION:
found = true
state.DBCVersion = value.(string)
case dt.TREX_VERSION:
found = true
state.TRexVersion = value.(string)
case dt.TREX_IP:
found = true
state.IP = value.(string)
case dt.UPDATED_AT:
var t time.Time
t, err = time.Parse(UPDATED_TIME_FORMAT, strings.Trim(value.(string), "\""))
if !t.IsZero() {
state.UpdatedAt = ref(t)
}
case dt.VCU_VehSt:
found = true
state.GetSafeState().VehicleSafeState = strconv.Itoa(value.(int)) == dt.VCU_VehSt_Safestate
case dt.VCU_VcuState:
found = true
state.GetSafeState().VCUSafeState = strconv.Itoa(value.(int)) == dt.VCU_VcuState_Safestate
case dt.MCU_F_ActSafeSt:
found = true
state.GetSafeState().MCUFrontSafeState = strconv.Itoa(value.(int)) == dt.MCU_F_ActSafeSt_AS0 || strconv.Itoa(value.(int)) == dt.MCU_F_ActSafeSt_ASC || strconv.Itoa(value.(int)) == dt.MCU_F_ActSafeSt_ASC_Emergency
case dt.MCU_R_ActSafeSt:
found = true
state.GetSafeState().MCURearSafeState = strconv.Itoa(value.(int)) == dt.MCU_R_ActSafeSt_AS0 || strconv.Itoa(value.(int)) == dt.MCU_R_ActSafeSt_ASC || strconv.Itoa(value.(int)) == dt.MCU_R_ActSafeSt_ASC_Emergency
case dt.MCU_R_Decoup_State:
found = true
state.GetSafeState().MCURearDecoupState = strconv.Itoa(value.(int)) == dt.MCU_R_Decoup_State_Connected
case dt.MCU_F_CrtMod:
found = true
state.GetSafeState().MCUFrontInverterError = strconv.Itoa(value.(int)) == dt.MCU_F_CrtMod_Internal_inverter_error || strconv.Itoa(value.(int)) == dt.MCU_F_CrtMod_Invalid
case dt.MCU_R_CrtMod:
found = true
state.GetSafeState().MCURearInverterError = strconv.Itoa(value.(int)) == dt.MCU_R_CrtMod_Internal_inverter_error || strconv.Itoa(value.(int)) == dt.MCU_R_CrtMod_Invalid
case dt.ACU_Drvr_Occpt_St:
found = true
var vi int
vi, ok = value.(int)
state.DriverOccupySeatState = ref(vi)
case dt.BCM_PwrMod:
found = true
var vi int
vi, ok = value.(int)
state.PowerMode = ref(vi)
case dt.PWC_ChrgSts:
found = true
var vi int
vi, ok = value.(int)
state.ChargingStatus = ref(vi)
case dt.VCU_RdyLamp:
found = true
state.GetVehicleReadyState().IsVehicleReady, ok = value.(bool)
case "online":
found = true
state.Online, ok = value.(bool)
case "online_hmi":
found = true
state.OnlineHMI, ok = value.(bool)
// New untested signals
// case dt.IBS_SOCUpperTolerance:
// found = true
// var vi float64
// vi, ok = value.(float64)
// state.GetExpandedSignals().IBS_SOCUpperTolerance = ref(vi)
// case dt.IBS_SOCLowerTolerance:
// found = true
// var vi float64
// vi, ok = value.(float64)
// state.GetExpandedSignals().IBS_SOCLowerTolerance = ref(vi)
case dt.IBS_StateOfCharge:
found = true
var vi float64
vi, ok = value.(float64)
state.GetBattery12V().IBS_StateOfCharge = ref(vi)
case dt.IBS_StateOfHealth:
found = true
var vi int
vi, ok = value.(int)
state.GetBattery12V().IBS_StateOfHealth = ref(vi)
case dt.IBS_NominalCapacity:
found = true
var vi int
vi, ok = value.(int)
state.GetExpandedSignals().IBS_NominalCapacity = ref(vi)
case dt.IBS_AvailableCapacity:
found = true
var vi int
vi, ok = value.(int)
state.GetExpandedSignals().IBS_AvailableCapacity = ref(vi)
case dt.BCM_TotMilg_ODO:
found = true
var vi float64
vi, ok = value.(float64)
state.GetExpandedSignals().BCM_TotMilg_ODO = ref(vi)
case dt.BMS_SwVersS:
found = true
var vi int
vi, ok = value.(int)
state.GetExpandedSignals().BMS_SwVersS = ref(vi)
case dt.BMS_SwVersM:
found = true
var vi int
vi, ok = value.(int)
state.GetExpandedSignals().BMS_SwVersM = ref(vi)
case dt.BMS_SwVers:
found = true
var vi int
vi, ok = value.(int)
state.GetExpandedSignals().BMS_SwVers = ref(vi)
case dt.BMS_AccueDchaTotAh:
found = true
var vi int
vi, ok = value.(int)
state.GetExpandedSignals().BMS_AccueDchaTotAh = ref(vi)
case dt.BMS_AccueChrgTotAh:
found = true
var vi int
vi, ok = value.(int)
state.GetExpandedSignals().BMS_AccueChrgTotAh = ref(vi)
case dt.TBOX_Heading:
found = true
state.GetLocation().Heading, ok = value.(float64)
case dt.PKC_KeyStsMod:
found = true
state.GetGear().Immobilizer = value.(string)
}
if found {
if !ok {
err = fmt.Errorf("failed on key %s value %v", key, value)
}
} else {
logger.Info().Str("key", key).Interface("value", value).Msgf("did not have parsing mode for key")
}
return errors.WithStack(err)
}
func ref[T any](v T) *T {
return &v
}
func IsCarOnline(clientPool redis.ClientPoolInterface, vin string) (bool, error) {
client := clientPool.GetFromPool()
defer client.Close()
return redigo.Bool(
client.Execute("SISMEMBER", redis.CarSessionsKey(), vin),
)
}
func notValue(value bool, err error) (bool, error) {
return !value, err
}