package digitaltwin import ( "encoding/json" cache "github.com/fiskerinc/cloud-services/pkg/cachev2" "github.com/fiskerinc/cloud-services/pkg/common" "github.com/fiskerinc/cloud-services/pkg/db/queries" "github.com/fiskerinc/cloud-services/pkg/logger" redis "github.com/fiskerinc/cloud-services/pkg/redisv2" "github.com/pkg/errors" ) func NewSendDigitalTwinV2(redisClient redis.ClientInterface, cars queries.CarsInterface) SendDigitalTwinV2 { return SendDigitalTwinV2{ redisClient: redisClient, carsDB: cars, } } type SendDigitalTwinV2 struct { redisClient redis.ClientInterface carsDB queries.CarsInterface parser *cache.VehicleState } func (d *SendDigitalTwinV2) Close() { d.carsDB = nil } func (d *SendDigitalTwinV2) GetDigitalTwin(vin string) (*common.JSONDigitalTwin, error) { state, err := d.getVehicleState(vin) if err != nil { return nil, err } var battery *common.JSONBattery var windows *common.JSONWindows var climateControl *common.JSONClimateControl battery = consolidateBatteryV2(state) // Consolidate windows if state.Windows != nil && state.MiscWindows != nil && state.Sunroof != nil { windows = &common.JSONWindows{} // Main windows windows.LeftFront = state.Windows.LeftFront windows.LeftRear = state.Windows.LeftRear windows.RightFront = state.Windows.RightFront windows.RightRear = state.Windows.RightRear // Miscellaneous windows windows.LeftRearQuarter = state.MiscWindows.LeftRearQuarter windows.RightRearQuarter = state.MiscWindows.RightRearQuarter windows.RearWindshield = state.MiscWindows.RearWindshield // Sunroof windows.Sunroof = state.Sunroof.Sunroof } // Consolidate climate control if state.CabinClimate != nil && state.RearDefrost != nil && state.DriverSeatHeat != nil && state.PassengerSeatHeat != nil && state.SteeringWheelHeat != nil && state.AmbientTemperature != nil { climateControl = &common.JSONClimateControl{} climateControl.CabinTemperature = state.CabinClimate.CabinTemperature climateControl.RearDefrost = state.RearDefrost.On climateControl.DriverSeatHeat = state.DriverSeatHeat.Level climateControl.PassengerSeatHeat = state.PassengerSeatHeat.Level climateControl.SteeringWheelHeat = state.SteeringWheelHeat.On climateControl.AmbientTemperature = state.AmbientTemperature.Temperature climateControl.InternalTemperature = state.CabinClimate.InternalTemperature } twinForDrivers := &common.JSONDigitalTwin{ VIN: vin, Online: state.Online, OnlineHMI: state.OnlineHMI, VehicleSpeed: state.VehicleSpeed, Gear: state.Gear, Battery: battery, Doors: state.Doors, Location: state.Location, Locks: state.Locks, Windows: windows, ClimateControl: climateControl, TRexVersion: state.TRexVersion, IP: state.IP, VehicleReadyState: state.VehicleReadyState, ExpandedSignals: state.ExpandedSignals, UpdatedAt: state.UpdatedAt, } return twinForDrivers, nil } func consolidateBatteryV2(state common.CarState) *common.JSONBattery { battery := common.JSONBattery{} if state.StateOfCharge != nil { battery.StateOfCharge = &state.StateOfCharge.Usable } if state.Battery != nil { battery.Percent = &state.Battery.Percent battery.TotalMileageOdometer = &state.Battery.TotalMileageOdometer } if state.MaxRange != nil { battery.MaxMiles = &state.MaxRange.MaxMiles } if state.VCU0x260 != nil { battery.ChargeType = &state.VCU0x260.ChargeType } if state.ChargingMetrics != nil { battery.RemainingChargingTime = &state.ChargingMetrics.RemainingChargingTime } if state.ChargingMetrics != nil { battery.RemainingChargingTimeFull = &state.ChargingMetrics.RemainingChargingTimeFull } if state.CellTemperature != nil { battery.AvgCellTemperature = &state.CellTemperature.AvgBatteryTemp } if battery == (common.JSONBattery{}) { return nil } return &battery } func (d *SendDigitalTwinV2) sendToDriver(twin *common.JSONDigitalTwin, driver string) error { dt, err := json.Marshal(twin) if err != nil { logger.Error().Err(errors.WithStack(err)).Send() } sdt := string(dt) logger.Debug().Msg(sdt) logger.Info().Interface("digital twin", twin).Msg("send to driver add to redis") err = d.redisClient.SafePublishMessage( common.Mobile.Key(driver), common.Message{ Handler: "digital_twin", Data: twin, }, ) return err } func (d *SendDigitalTwinV2) SendToDriver(vin string, driver string) error { ok, err := d.verifyCarToDriver(vin, driver) if err != nil { return err } else if !ok { return cache.ErrInvalidCarToDriverAssociation(vin, driver) } twin, err := d.GetDigitalTwin(vin) if err != nil { return err } return d.sendToDriver(twin, driver) } func (d *SendDigitalTwinV2) Send(vin string) error { // Get the digital twin redisDigitalTwin, err := d.GetDigitalTwin(vin) if err != nil { return err } drivers, err := d.retrieveDriverIDs(vin) if err != nil { return err } if len(drivers) == 0 { return nil } if redisDigitalTwin != nil { for _, driver := range drivers { err = d.sendToDriver(redisDigitalTwin, driver) if err != nil { logger.Error().Err(err) } } } return nil } func (d *SendDigitalTwinV2) getVehicleState(vin string) (common.CarState, error) { return d.getParser().Get(vin) } func (d *SendDigitalTwinV2) verifyCarToDriver(vin string, driver string) (bool, error) { return cache.VerifyCarToDriver(d.redisClient, d.carsDB, vin, driver) } func (d *SendDigitalTwinV2) retrieveDriverIDs(vin string) ([]string, error) { drivers := cache.NewDriversCache(d.redisClient, d.carsDB) return drivers.RetrieveDriverIDs(vin) } func (d *SendDigitalTwinV2) getParser() *cache.VehicleState { if d.parser == nil { d.parser = cache.NewVehicleState(d.redisClient) } return d.parser }