package digitaltwin import ( "encoding/json" "github.com/fiskerinc/cloud-services/pkg/cache" "github.com/fiskerinc/cloud-services/pkg/common" "github.com/fiskerinc/cloud-services/pkg/db/queries" "github.com/fiskerinc/cloud-services/pkg/logger" "github.com/fiskerinc/cloud-services/pkg/redis" "github.com/pkg/errors" ) func NewSendDigitalTwin(redis redis.ClientPoolInterface, cars queries.CarsInterface) SendDigitalTwin { return SendDigitalTwin{ redisClientPool: redis, carsDB: cars, } } type SendDigitalTwin struct { redisClientPool redis.ClientPoolInterface carsDB queries.CarsInterface parser *cache.VehicleState } func (d *SendDigitalTwin) Close() { d.carsDB = nil } func (d *SendDigitalTwin) GetDigitalTwin(vin string) (*common.JSONDigitalTwin, error) { state, err := d.getVehicleState(vin) if err != nil { return nil, err } return CreateDigitalTwinResponse(vin, state), nil } func CreateDigitalTwinResponse(vin string, state common.CarState) *common.JSONDigitalTwin { var battery *common.JSONBattery var windows *common.JSONWindows var climateControl *common.JSONClimateControl battery = consolidateBattery(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 } func consolidateBattery(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 *SendDigitalTwin) 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") client := d.redisClientPool.GetFromPool() defer client.Close() err = client.SafePublishMessage( common.Mobile.Key(driver), common.Message{ Handler: "digital_twin", Data: twin, }, ) return err } func (d *SendDigitalTwin) 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 *SendDigitalTwin) 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 *SendDigitalTwin) getVehicleState(vin string) (common.CarState, error) { return d.getParser().Get(vin) } func (d *SendDigitalTwin) verifyCarToDriver(vin string, driver string) (bool, error) { return cache.VerifyCarToDriver(d.redisClientPool, d.carsDB, vin, driver) } func (d *SendDigitalTwin) retrieveDriverIDs(vin string) ([]string, error) { drivers := cache.NewDriversCache(d.redisClientPool, d.carsDB) return drivers.RetrieveDriverIDs(vin) } func (d *SendDigitalTwin) getParser() *cache.VehicleState { if d.parser == nil { d.parser = cache.NewVehicleState(d.redisClientPool) } return d.parser }