194 lines
6.2 KiB
Go
194 lines
6.2 KiB
Go
package background
|
|
|
|
import (
|
|
"encoding/json"
|
|
"maps"
|
|
"otaupdate/services"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/fiskerinc/cloud-services/pkg/cache"
|
|
"github.com/fiskerinc/cloud-services/pkg/carcommand"
|
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
|
"github.com/fiskerinc/cloud-services/pkg/redis"
|
|
)
|
|
|
|
// Anywhere a comment says lock, its probably actually lock/unlock
|
|
|
|
// Have a local list we check and run against
|
|
// and have a redis cache list that we will modify
|
|
// keep track of time started, last updated time, number of times sent, and if its a lock or unlock
|
|
|
|
var (
|
|
immobilizerOnce sync.Once
|
|
immobilizer *Immobilizer
|
|
)
|
|
|
|
// Message will stay alive in redis for an hour before it is removed
|
|
const CAR_CHECK_INTERVAL time.Duration = time.Duration(5 * time.Minute) // Minutes between checking car status and sending command
|
|
const REDIS_UPDATE_INTERVAL time.Duration = time.Duration(time.Hour * 2) // Minutes between updating redis with the current cache list. // need to keep track of last updated incase our service goes down
|
|
const REDIS_EXPIRATION_DURATION time.Duration = time.Duration(24 * time.Hour) // how old a time should be before we pick it up for us to process
|
|
|
|
const CAR_LOCK_ATTEMPT_COUNT = 15 // how many times to send the lock command
|
|
|
|
type CarTrack struct {
|
|
TimesModified int `json:"times_modified"`
|
|
Immobilize bool `json:"immobilize"` // 0: unlock, 1: lock
|
|
}
|
|
|
|
type Immobilizer struct {
|
|
VINs map[string]*CarTrack // By having a pointer here, should be able to modify timesModified without write lock
|
|
sync.RWMutex // mutex for handling the reading and writing of the VIN's map
|
|
}
|
|
|
|
func GetImmobilizer() *Immobilizer {
|
|
immobilizerOnce.Do(func() {
|
|
if immobilizer != nil {
|
|
return
|
|
}
|
|
logger.Info().Msg("Init Immobilizer instance")
|
|
immobilizer = InitiateImmobilizer()
|
|
})
|
|
return immobilizer
|
|
}
|
|
|
|
func InitiateImmobilizer() *Immobilizer {
|
|
imm := &Immobilizer{}
|
|
imm.VINs = make(map[string]*CarTrack)
|
|
go time.AfterFunc(CAR_CHECK_INTERVAL, imm.CheckCars)
|
|
// go time.AfterFunc(REDIS_UPDATE_INTERVAL, imm.UpdateRedis)
|
|
// Have a random offset so multiple ota's starting at same time don't look and claim at the same time
|
|
// go time.AfterFunc(REDIS_EXPIRATION_DURATION+(time.Duration(rand.IntN(20))*time.Minute), imm.ClaimRedis)
|
|
return imm
|
|
}
|
|
|
|
|
|
func (imm *Immobilizer) GetVINList() (vinList []string) {
|
|
imm.RLock()
|
|
defer imm.RUnlock()
|
|
for vin := range imm.VINs {
|
|
vinList = append(vinList, vin)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (imm *Immobilizer) CheckCars() {
|
|
clientPool := services.RedisClientPool()
|
|
twins, _ := cache.GetVINListDigitalTwin(imm.GetVINList(), clientPool)
|
|
parkedVINs := []string{} // parkedVINs are the cars we are going to send the lock command to
|
|
for vin, twin := range twins {
|
|
gear := twin.Gear
|
|
if gear == nil {
|
|
continue
|
|
}
|
|
if gear.InPark {
|
|
parkedVINs = append(parkedVINs, vin)
|
|
}
|
|
}
|
|
// Have a list of vins we can now modify
|
|
// Send the lock/unlock command, send wake up command
|
|
hopefulImmob := imm.sendRemoteCommands(parkedVINs)
|
|
// Not on hopeful immob, we do not check if the car ever wakes up from its sms, so its possible if parked deep in a garage it will not
|
|
// Possible fix: remove redis timeout for car lock commands
|
|
go imm.RemoveVINs(hopefulImmob)
|
|
time.AfterFunc(CAR_CHECK_INTERVAL, imm.CheckCars)
|
|
}
|
|
|
|
func (imm *Immobilizer) sendRemoteCommands(parkedVINs []string) (removableVins []string) {
|
|
// First send wake up message
|
|
|
|
// Send lock or unlock command
|
|
|
|
// for _, vin := range request.VINs {
|
|
// Action logger should get added to when the user adds car to the list
|
|
// go func() {
|
|
// actionLog := actionlogger.ActionLog{
|
|
// VIN: vin,
|
|
// Action: actionlogger.RemoteCommand,
|
|
// UserIdentifier: httphandlers.GetClientID(r),
|
|
// CallLocation: "github.com/fiskerinc/cloud-services/services/ota_update_go/handlers/vehicle_command.go",
|
|
// Description: string(description),
|
|
// }
|
|
// err = alDB.Insert(actionLog)
|
|
// if err != nil {
|
|
// logger.Err(err).Msg("failed to insert action log inside HandleVehicleCommand")
|
|
// }
|
|
// }()
|
|
// vehicle_command.go has an extremely convoluted way to get remote commands to car. It pushed it to a kafka queue to to be picked up
|
|
// then kafka does some delivery re-trying stuff in some way, wakes up the car and then puts the message into redis.
|
|
// we are going straight to the redis section
|
|
|
|
// remoteCommands := make([]string, 0, len(parkedVINs))
|
|
imm.RLock()
|
|
batch := redis.NewRedisBatchCommands()
|
|
smsClient := services.GetSMSClient()
|
|
wake := carcommand.NewCarWakeUp(services.GetDB().GetCars(), smsClient)
|
|
for _, vin := range parkedVINs {
|
|
temp := imm.VINs[vin]
|
|
temp.TimesModified -= 1
|
|
if temp.TimesModified < 0 {
|
|
removableVins = append(removableVins, vin)
|
|
continue
|
|
}
|
|
// probably faster to create the list of things to push and batch, but fine for now
|
|
msg := common.RemoteCommandSource{}
|
|
if temp.Immobilize {
|
|
msg.Command = "doors_lock"
|
|
} else {
|
|
msg.Command = "doors_unlock"
|
|
}
|
|
// SafeQueueMessage auto deletes after one hour, which I do not want with this lock command, rather it sat around indefinitely
|
|
data, _ := json.Marshal(common.Message{
|
|
Handler: "remote_command",
|
|
Data: msg,
|
|
})
|
|
batch.Add("RPUSH", redis.QueueKey(common.TRex.Key(vin)), data)
|
|
|
|
// try to wake up car
|
|
wake.WakeUp(vin, false)
|
|
}
|
|
imm.RUnlock()
|
|
|
|
redisClient := services.RedisClientPool().GetFromPool()
|
|
defer redisClient.Close()
|
|
_, err := redisClient.ExecuteBatch(batch)
|
|
if err != nil {
|
|
logger.Err(err).Msg("failed to push car immobilizer commands to redis")
|
|
}
|
|
return
|
|
}
|
|
|
|
func (imm *Immobilizer) RemoveVINs(vins []string) {
|
|
imm.Lock()
|
|
defer imm.Unlock()
|
|
for _, v := range vins {
|
|
delete(imm.VINs, v)
|
|
}
|
|
}
|
|
|
|
func (imm *Immobilizer) AddVINs(vins []string, immobilize bool) {
|
|
imm.Lock()
|
|
defer imm.Unlock()
|
|
for _, v := range vins {
|
|
imm.VINs[v] = &CarTrack{TimesModified: CAR_LOCK_ATTEMPT_COUNT, Immobilize: immobilize}
|
|
}
|
|
}
|
|
|
|
// Just returns the information about the vins we are currently tracking
|
|
// not sure about memory safety on this
|
|
func (imm *Immobilizer) GetVINInformation() (vinInfo map[string]*CarTrack) {
|
|
imm.RLock()
|
|
defer imm.RUnlock()
|
|
vinInfo = maps.Clone(imm.VINs)
|
|
return
|
|
}
|
|
|
|
func (imm *Immobilizer) UpdateRedis() {
|
|
|
|
}
|
|
|
|
func (imm *Immobilizer) ClaimRedis() {
|
|
|
|
}
|