Initial cloud-services repo - gateway service + pkg modules

This commit is contained in:
Chris Rai
2026-01-30 23:14:52 -05:00
commit fbb820d7b3
1037 changed files with 171318 additions and 0 deletions

59
pkg/digitaltwin/cache.go Normal file
View File

@@ -0,0 +1,59 @@
package digitaltwin
import (
"fiskerinc.com/modules/cache"
"fiskerinc.com/modules/logger"
)
// var existsCount ExistsCount
type DigitalTwinCacheInterface interface {
Exists(vin string, prop string, value interface{}) bool
}
type DigitalTwinCache struct {
ringMap *cache.RingMap
// existsChan chan<- bool
}
func NewDigitalTwinCache(capacity int) DigitalTwinCacheInterface {
// existsChannel := make(chan bool, 100)
// existsCount = ExistsCount{
// Channel: existsChannel,
// }
// go existsCount.Run()
return &DigitalTwinCache{
ringMap: cache.NewRingMap(capacity),
// existsChan: existsChannel,
}
}
func (d *DigitalTwinCache) Exists(vin string, prop string, value interface{}) bool {
exists := d.ringMap.Exists(vin+prop, value)
// If the channel is full, we just fall through
// select {
// case d.existsChan <- exists:
// default:
// }
return exists
}
type ExistsCount struct {
Total int
Exists int
Channel <-chan bool
}
func (ec *ExistsCount) Run() {
for exists := range ec.Channel {
ec.Total++
if exists {
ec.Exists += 1
}
if ec.Total >= 180000000 {
logger.Error().Int("Cached Entries", ec.Exists).Int("Total Entries", ec.Total).Msg("Digital Twin Cache Results")
ec.Total = 0
ec.Exists = 0
}
}
}

View File

@@ -0,0 +1,42 @@
package digitaltwin_test
import (
"testing"
"fiskerinc.com/modules/digitaltwin"
"github.com/stretchr/testify/assert"
)
func TestDigitalTwinCache(t *testing.T) {
vin1 := "11111111111111111"
vin2 := "22222222222222222"
prop1 := "prop1"
prop2 := "prop2"
cache := digitaltwin.NewDigitalTwinCache(3)
exists := cache.Exists(vin1, prop1, 1)
assert.Equal(t, false, exists)
exists = cache.Exists(vin2, prop1, 1)
assert.Equal(t, false, exists)
exists = cache.Exists(vin1, prop1, 1)
assert.Equal(t, true, exists)
exists = cache.Exists(vin1, prop1, int64(2))
assert.Equal(t, false, exists)
exists = cache.Exists(vin1, prop2, true)
assert.Equal(t, false, exists)
exists = cache.Exists(vin2, prop2, true)
assert.Equal(t, false, exists)
exists = cache.Exists(vin2, prop1, true)
assert.Equal(t, false, exists)
exists = cache.Exists(vin1, prop1, int64(2))
assert.Equal(t, false, exists)
}

218
pkg/digitaltwin/send.go Normal file
View File

@@ -0,0 +1,218 @@
package digitaltwin
import (
"encoding/json"
"fiskerinc.com/modules/cache"
"fiskerinc.com/modules/common"
"fiskerinc.com/modules/db/queries"
"fiskerinc.com/modules/logger"
"fiskerinc.com/modules/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
}

View File

@@ -0,0 +1,139 @@
package digitaltwin_test
import (
"encoding/json"
"fiskerinc.com/modules/common"
"fiskerinc.com/modules/duration"
)
func clone(orig *common.JSONDigitalTwin) (*common.JSONDigitalTwin, error) {
origJSON, err := json.Marshal(orig)
if err != nil {
return nil, err
}
clone := common.JSONDigitalTwin{}
if err = json.Unmarshal(origJSON, &clone); err != nil {
return nil, err
}
return &clone, nil
}
func cloneAndChangeOnline(orig *common.JSONDigitalTwin) (*common.JSONDigitalTwin, error) {
cloned, err := clone(orig)
if err != nil {
return nil, err
}
cloned.Online = !cloned.Online
return cloned, nil
}
func cloneAndChangeHMIOnline(orig *common.JSONDigitalTwin) (*common.JSONDigitalTwin, error) {
cloned, err := clone(orig)
if err != nil {
return nil, err
}
cloned.OnlineHMI = !cloned.OnlineHMI
return cloned, nil
}
func cloneAndChangeVehicleSpeed(orig *common.JSONDigitalTwin) (*common.JSONDigitalTwin, error) {
cloned, err := clone(orig)
if err != nil {
return nil, err
}
cloned.VehicleSpeed.Speed = cloned.VehicleSpeed.Speed + 10.0
return cloned, nil
}
func cloneAndChangeGear(orig *common.JSONDigitalTwin) (*common.JSONDigitalTwin, error) {
cloned, err := clone(orig)
if err != nil {
return nil, err
}
cloned.Gear.InPark = !cloned.Gear.InPark
return cloned, nil
}
func cloneAndChangeBattery(orig *common.JSONDigitalTwin) (*common.JSONDigitalTwin, error) {
cloned, err := clone(orig)
if err != nil {
return nil, err
}
*cloned.Battery.Percent = *cloned.Battery.Percent - 10.0
return cloned, nil
}
func cloneAndChangeDoor(orig *common.JSONDigitalTwin) (*common.JSONDigitalTwin, error) {
cloned, err := clone(orig)
if err != nil {
return nil, err
}
cloned.Doors.LeftFront = !cloned.Doors.LeftFront
return cloned, nil
}
func cloneAndChangeLocation(orig *common.JSONDigitalTwin) (*common.JSONDigitalTwin, error) {
cloned, err := clone(orig)
if err != nil {
return nil, err
}
cloned.Location.Altitude = cloned.Location.Altitude + 100.0
return cloned, nil
}
func cloneAndChangeLocks(orig *common.JSONDigitalTwin) (*common.JSONDigitalTwin, error) {
cloned, err := clone(orig)
if err != nil {
return nil, err
}
cloned.Locks.Driver = !cloned.Locks.Driver
return cloned, nil
}
func cloneAndChangeWindows(orig *common.JSONDigitalTwin) (*common.JSONDigitalTwin, error) {
cloned, err := clone(orig)
if err != nil {
return nil, err
}
cloned.Windows.LeftFront = cloned.Windows.LeftFront + 1
return cloned, nil
}
func cloneAndChangeClimateControl(orig *common.JSONDigitalTwin) (*common.JSONDigitalTwin, error) {
cloned, err := clone(orig)
if err != nil {
return nil, err
}
cloned.ClimateControl.AmbientTemperature = cloned.ClimateControl.AmbientTemperature + 1
return cloned, nil
}
func cloneAndChangeTrexVersion(orig *common.JSONDigitalTwin) (*common.JSONDigitalTwin, error) {
cloned, err := clone(orig)
if err != nil {
return nil, err
}
cloned.TRexVersion = cloned.TRexVersion + "new_version"
return cloned, nil
}
func cloneAndChangeIP(orig *common.JSONDigitalTwin) (*common.JSONDigitalTwin, error) {
cloned, err := clone(orig)
if err != nil {
return nil, err
}
cloned.IP = "172.0.0.99"
return cloned, nil
}
func cloneAndUpdateAt(orig *common.JSONDigitalTwin) (*common.JSONDigitalTwin, error) {
cloned, err := clone(orig)
if err != nil {
return nil, err
}
*cloned.UpdatedAt = cloned.UpdatedAt.Add(-1 * duration.Minute)
return cloned, nil
}

212
pkg/digitaltwin/sendv2.go Normal file
View File

@@ -0,0 +1,212 @@
package digitaltwin
import (
"encoding/json"
cache "fiskerinc.com/modules/cachev2"
"fiskerinc.com/modules/common"
"fiskerinc.com/modules/db/queries"
"fiskerinc.com/modules/logger"
redis "fiskerinc.com/modules/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
}