package cachev2 import ( "context" "strconv" "strings" "time" "fiskerinc.com/modules/common" dt "fiskerinc.com/modules/dbc/state" "fiskerinc.com/modules/logger" redis "fiskerinc.com/modules/redisv2" "fiskerinc.com/modules/utils/querystring" redispkg "github.com/redis/go-redis/v9" "github.com/pkg/errors" ) const UPDATED_TIME_FORMAT = "2006-01-02T15:04:05Z" type stateParser func(state *common.CarState, key string, value []byte) (found bool, err error) func NewVehicleState(client redis.ClientInterface) *VehicleState { return &VehicleState{redisClient: client} } type VehicleState struct { redisClient redis.ClientInterface } 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 } type QueryVehicleStateResponse struct { CarSessionExists bool HMISessionExists bool CarState map[string]string } type QueryVehicleStatePreResponse struct { CarSessionExists *redispkg.BoolCmd HMISessionExists *redispkg.BoolCmd CarState *redispkg.MapStringStringCmd } func (qvspr *QueryVehicleStatePreResponse) Resolve() (qvsr *QueryVehicleStateResponse, errR error) { var err error qvsr = &QueryVehicleStateResponse{} qvsr.CarSessionExists, err = qvspr.CarSessionExists.Result() if err != nil { errR = errors.Wrap(errR, err.Error()) } qvsr.HMISessionExists, err = qvspr.HMISessionExists.Result() if err != nil { errR = errors.Wrap(errR, err.Error()) } qvsr.CarState, err = qvspr.CarState.Result() if err != nil { errR = errors.Wrap(errR, err.Error()) } return } func (v *VehicleState) queryVehicleState(vin string) (QueryVehicleStateResponse, error) { payload := QueryVehicleStateResponse{} pipe := v.redisClient.GetClient().TxPipeline() carSessionKey := pipe.SIsMember(context.Background(), redis.CarSessionsKey(), vin) hmiSessionKey := pipe.SIsMember(context.Background(), redis.HMISessionsKey(), vin) carStateHash := pipe.HGetAll(context.Background(), redis.CarStateHashKey(vin)) _, err := pipe.Exec(context.Background()) if err != nil { return payload, errors.WithStack(err) } payload.CarSessionExists = carSessionKey.Val() payload.HMISessionExists = hmiSessionKey.Val() payload.CarState = carStateHash.Val() return payload, nil } func (v *VehicleState) ParsePayloadForVehicleState(payload QueryVehicleStateResponse) (common.CarState, error) { var state common.CarState state.Online = payload.CarSessionExists state.OnlineHMI = payload.HMISessionExists var err error err = v.parseStateValues(&state, payload.CarState, v.parseCarState) return state, err } func ParsePayloadForVehicleState(payload *QueryVehicleStateResponse) (state *common.CarState, err error) { state = &common.CarState{} state.Online = payload.CarSessionExists state.OnlineHMI = payload.HMISessionExists err = parseStateValues(state, payload.CarState, ParseCarState) return } func ParsePayloadForALVehicleState(payload *QueryVehicleStateResponse) (alState *common.CarStateAL, err error) { alState.CarState = &common.CarState{} alState.Online = payload.CarSessionExists alState.OnlineHMI = payload.HMISessionExists err = parseStateValues(alState.CarState, payload.CarState, ParseCarState) return } func (v *VehicleState) parseStateValues(state *common.CarState, stateValues map[string]string, parser stateParser) error { for key, value := range stateValues { _, err := parser(state, string(key), []byte(value)) // log error, do not return error so we can read other properties for digital twin if err != nil { // strconv.Atoi: parsing "127.5": invalid syntax, track down. Add better info logger.Err(err).Send() } } return nil } func parseStateValues(state *common.CarState, stateValues map[string]string, parser stateParser) error { for key, value := range stateValues { _, err := parser(state, string(key), []byte(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 (v *VehicleState) parseCarState(state *common.CarState, key string, value []byte) (bool, error) { var err error val := string(value) switch key { case dt.VCU_VehChrgDchgMod: state.GetVCU0x260().ChargeType = val 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 true, errors.WithStack(err) } func ParseCarState(state *common.CarState, key string, value []byte) (found bool, err error) { val := string(value) switch key { case dt.VCU_VehChrgDchgMod: found = true state.GetVCU0x260().ChargeType = val case dt.BMS_Bat_SoC_usable: found = true state.GetStateOfCharge().Usable, err = strconv.Atoi(val) case dt.BMS_Bat_SOH: found = true state.GetStateOfCharge().Health, err = strconv.Atoi(val) case dt.BCM_AP_FL_LeFrntWinPosnInfo: found = true state.GetWindows().LeftFront, err = strconv.Atoi(val) case dt.BCM_AP_FL_RiFrntWinPosnInfo: found = true state.GetWindows().RightFront, err = strconv.Atoi(val) case dt.BCM_AP_FL_LeReWinPosnInfo: found = true state.GetWindows().LeftRear, err = strconv.Atoi(val) case dt.BCM_AP_FL_RiReWinPosnInfo: found = true state.GetWindows().RightRear, err = strconv.Atoi(val) case dt.BMS_PwrBattRmngCpSOC: found = true state.GetBattery().Percent, err = strconv.Atoi(val) case dt.BCM_ReDefrstHeatgCmd: found = true state.GetRearDefrost().On, err = strconv.ParseBool(val) case dt.BCM_PasFrntDoorSts: found = true state.GetDoors().RightFront, err = strconv.ParseBool(val) case dt.BCM_DrFrntDoorSts: found = true state.GetDoors().LeftFront, err = strconv.ParseBool(val) case dt.BCM_FrntDrDoorLockSts: found = true state.GetLocks().Driver, err = notValue(strconv.ParseBool(val)) case dt.BCM_CenLockSwtSts: found = true state.GetLocks().All = (val == "2") case dt.BCM_RiReDoorSts: found = true state.GetDoors().RightRear, err = strconv.ParseBool(val) case dt.BCM_LeReDoorSts: found = true state.GetDoors().LeftRear, err = strconv.ParseBool(val) case dt.BCM_FrntHoodLidSts: found = true state.GetDoors().Hood, err = strconv.ParseBool(val) case dt.PLGM_TrSts: found = true state.GetDoors().Trunk, err = strconv.ParseBool(val) case dt.BCM_SunroofPosnInfo: found = true state.GetSunroof().Sunroof, err = strconv.Atoi(val) case dt.BCM_AP_TL_LeReWinPosnInfo: found = true state.GetMiscWindows().LeftRearQuarter, err = strconv.Atoi(val) case dt.BCM_AP_TL_RiReWinPosnInfo: found = true state.GetMiscWindows().RightRearQuarter, err = strconv.Atoi(val) case dt.BCM_AP_RW_WinPosnInfo: found = true state.GetMiscWindows().RearWindshield, err = strconv.Atoi(val) case dt.BMS_BattAvrgT: found = true state.GetCellTemperature().AvgBatteryTemp, err = strconv.Atoi(val) case dt.ECC_OutdT: found = true state.GetAmbientTemperature().Temperature, err = strconv.Atoi(val) case dt.BCM_HeatedSteerWhlSt: found = true state.GetSteeringWheelHeat().On, err = strconv.ParseBool(val) case dt.ESP_VehSpd: found = true state.GetVehicleSpeed().Speed, err = strconv.ParseFloat(val, 64) case dt.VCU_DrvgMilg: found = true state.GetMaxRange().MaxMiles, err = strconv.Atoi(val) case dt.PSM_PassSeatHeatgSts: found = true state.GetPassengerSeatHeat().Level, err = strconv.Atoi(val) case dt.DSMC_DrvrSeatHeatgSts: found = true state.GetDriverSeatHeat().Level, err = strconv.Atoi(val) case dt.ICC_TotMilg_ODO: found = true state.GetBattery().TotalMileageOdometer, err = querystring.ConvertStringToInt(val) case dt.VCU_DCChrgRmngTi, dt.BMS_RmChrgTi_TrgtSoC: found = true state.GetChargingMetrics().RemainingChargingTime, err = strconv.Atoi(val) case dt.IBS_BatteryVoltage: found = true state.GetBattery().BatteryVoltage, err = strconv.ParseFloat(val, 64) state.GetBattery12V().IBS_BatteryVoltage = ref(state.GetBattery().BatteryVoltage) case dt.VCU_GearSig: found = true var gear int gear, err = strconv.Atoi(val) state.GetGear().InPark = (gear <= 2) case dt.BMS_RmChrgTi_FullChrg: found = true state.GetChargingMetrics().RemainingChargingTimeFull, err = strconv.Atoi(val) case dt.ECC_InsdT: found = true state.GetCabinClimate().InternalTemperature, err = strconv.Atoi(val) case dt.ECC_RemTSetSts: found = true state.GetCabinClimate().CabinTemperature, err = strconv.Atoi(val) case dt.TBOX_GPSHei: found = true state.GetLocation().Altitude, err = strconv.ParseFloat(val, 64) case dt.TBOX_GPSLongi: found = true state.GetLocation().Longitude, err = strconv.ParseFloat(val, 64) case dt.TBOX_GPSLati: found = true state.GetLocation().Latitude, err = strconv.ParseFloat(val, 64) case dt.DBC_VERSION: found = true state.DBCVersion = val case dt.TREX_VERSION: found = true state.TRexVersion = val case dt.TREX_IP: found = true state.IP = val case dt.UPDATED_AT: found = true 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: found = true state.GetSafeState().VehicleSafeState = val == dt.VCU_VehSt_Safestate case dt.VCU_VcuState: found = true state.GetSafeState().VCUSafeState = val == dt.VCU_VcuState_Safestate case dt.MCU_F_ActSafeSt: found = true 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: found = true 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: found = true state.GetSafeState().MCURearDecoupState = val == dt.MCU_R_Decoup_State_Connected case dt.MCU_F_CrtMod: found = true state.GetSafeState().MCUFrontInverterError = val == dt.MCU_F_CrtMod_Internal_inverter_error || val == dt.MCU_F_CrtMod_Invalid case dt.MCU_R_CrtMod: found = true state.GetSafeState().MCURearInverterError = val == dt.MCU_R_CrtMod_Internal_inverter_error || val == dt.MCU_R_CrtMod_Invalid case dt.ACU_Drvr_Occpt_St: found = true var vi int vi, err = strconv.Atoi(val) state.DriverOccupySeatState = ref(vi) case dt.BCM_PwrMod: found = true var vi int vi, err = strconv.Atoi(val) state.PowerMode = ref(vi) case dt.PWC_ChrgSts: found = true var vi int vi, err = strconv.Atoi(val) state.ChargingStatus = ref(vi) case dt.VCU_RdyLamp: found = true state.GetVehicleReadyState().IsVehicleReady, err = strconv.ParseBool(val) // New untested signals // case dt.IBS_SOCUpperTolerance: found = true // var vi float64 // vi, err = strconv.ParseFloat(val, 64) // state.GetExpandedSignals().IBS_SOCUpperTolerance = ref(vi) // case dt.IBS_SOCLowerTolerance: found = true // var vi float64 // vi, err = strconv.ParseFloat(val, 64) // state.GetExpandedSignals().IBS_SOCLowerTolerance = ref(vi) case dt.IBS_StateOfCharge: found = true var vi float64 vi, err = strconv.ParseFloat(val, 64) state.GetBattery12V().IBS_StateOfCharge = ref(vi) case dt.IBS_StateOfHealth: found = true var vi int vi, err = strconv.Atoi(val) state.GetBattery12V().IBS_StateOfHealth = ref(vi) case dt.IBS_NominalCapacity: found = true var vi int vi, err = strconv.Atoi(val) state.GetExpandedSignals().IBS_NominalCapacity = ref(vi) case dt.IBS_AvailableCapacity: found = true var vi int vi, err = strconv.Atoi(val) state.GetExpandedSignals().IBS_AvailableCapacity = ref(vi) case dt.BCM_TotMilg_ODO: found = true var vi float64 vi, err = strconv.ParseFloat(val, 64) state.GetExpandedSignals().BCM_TotMilg_ODO = ref(vi) case dt.BMS_SwVersS: found = true var vi int vi, err = strconv.Atoi(val) state.GetExpandedSignals().BMS_SwVersS = ref(vi) case dt.BMS_SwVersM: found = true var vi int vi, err = strconv.Atoi(val) state.GetExpandedSignals().BMS_SwVersM = ref(vi) case dt.BMS_SwVers: found = true var vi int vi, err = strconv.Atoi(val) state.GetExpandedSignals().BMS_SwVers = ref(vi) case dt.BMS_AccueDchaTotAh: found = true var vi int vi, err = strconv.Atoi(val) state.GetExpandedSignals().BMS_AccueDchaTotAh = ref(vi) case dt.BMS_AccueChrgTotAh: found = true var vi int vi, err = strconv.Atoi(val) state.GetExpandedSignals().BMS_AccueChrgTotAh = ref(vi) case dt.TBOX_Heading: found = true state.GetLocation().Heading, err = strconv.ParseFloat(val, 64) case dt.PKC_KeyStsMod: found = true state.GetGear().Immobilizer = val } return found, errors.WithStack(err) } func ref[T any](v T) *T { return &v } func IsCarOnline(client redis.ClientInterface, vin string) (bool, error) { res := client.GetClient().SIsMember(context.Background(), redis.CarSessionsKey(), vin) return res.Result() } func notValue(value bool, err error) (bool, error) { return !value, err }