Files
cloud-services/pkg/immobilizer/immobilizerditto/immoDitto.go

236 lines
7.7 KiB
Go

package immobilizerditto
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"sync"
"time"
"fiskerinc.com/modules/common"
"fiskerinc.com/modules/dbc/state"
"fiskerinc.com/modules/immobilizer/immobilizershared"
"fiskerinc.com/modules/logger"
"fiskerinc.com/modules/redisv2"
)
// I should put these parked values as a constant somewhere TODO
func (id *ImmobilizeDitto) VCUGearSig(vin string, newState int) {
//0 "Undefined_initial_value" 1 "gear_P" 2 "gear_N" 3 "R_gear" 4 "D_gear" 5 "Reserved" 6 "gear_E" 7 "gear_S" 8 "Reserved" 9 "Reserved" 10 "Reserved" 11 "Reserved" 12 "Reserved" 13 "Reserved" 14 "Reserved" 15 "Reserved" ;
id.WatchListSync.RLock()
track, ok := id.WatchList[vin]
id.WatchListSync.RUnlock()
if !ok {
return
}
switch track.State {
case immobilizershared.STATE_IMMOBILIZED:
// If we are immobilized, but we are out of park, thats an issue
if newState == 1 || newState == 2 {
// Car is parked, and possible already immobilized, but we are going to enforce in case we were wrong
// id.sendLockCommand(vin)
} else if newState > 2 {
logger.Error().Str("VIN", vin).Int("New Gear", newState).Str("MSG", "Marked as Immobilized, but received driving gear").Msg("VCUGearSig")
go alertAmericanLeaseMovingVehicle(vin)
}
case immobilizershared.STATE_IMMOBILIZING:
if newState == 1 || newState == 2 {
// Car is parked, send that lock command. We know the car is awake, so no need to send a wake up sms
id.sendLockCommand(vin)
}
case immobilizershared.STATE_MOBILIZING:
if newState > 2 {
// unsure if we should accept this as done
immobilizershared.UpdateCarMobilized(vin, id.RedisClient)
}
}
}
func (id *ImmobilizeDitto) sendLockCommand(vin string) {
msg := common.RemoteCommandSource{}
msg.Command = "doors_lock"
// 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,
})
res := id.RedisClient.RPush(context.Background(), redisv2.QueueKey(common.TRex.Key(vin)), data)
err := res.Err()
if err != nil {
logger.Err(err).Msg("Failed to send lock command")
}
}
func alertAmericanLeaseMovingVehicle(vin string) {
url := "https://mobile.americanlease.net/api/v1/fisker/command/failure"
token := "Bearer f3d795d3-5325-42d7-8dd8-ccb416c52ae2"
type AmericanLeaseBody struct {
VIN string `json:"vin"`
UTCMobileAt time.Time `json:"utcMobileAt"` //"utcMobileAt":"2025-03-07T09:00:00",
UUID string `json:"uuid"` //"uuid": "db6e407d-791b-4d00-a742-da856a42c4ba"
}
body := AmericanLeaseBody{
VIN: vin,
UTCMobileAt: time.Now().UTC(),
UUID: "00000000-0000-0000-0000-000000000000",
}
b, _ := json.Marshal(body)
// Create the HTTP request
req, err := http.NewRequest("POST", url, bytes.NewBuffer(b))
if err != nil {
logger.Err(err).Msg("failed to make request to american lease")
return
}
// Set headers
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", token)
// Execute the request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
logger.Err(err).Msg("failed to do request to american lease")
return
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
respBody, _ := io.ReadAll(resp.Body)
logger.Error().Str("response body", string(respBody)).Str("response header", resp.Status).Msg("failed to contact american lease about moving car")
}
}
// So we got the VCU Immo sts, most likely a new value compared to what was had before
func (id *ImmobilizeDitto) VCUIMMOSts(vin string, newState string) {
// Possible values for newState
// "Immo_Active",
// "Immo_Inactive",
// "Reserved",
// "Invalid",
// Check if we are tracking this vehicle
id.WatchListSync.RLock()
track, ok := id.WatchList[vin]
id.WatchListSync.RUnlock()
if !ok {
return
}
logger.Debug().Str("New State", newState).Str("Tracked", track.State.String()).Str("VIN", vin).Msg("VCUIMMOSts signal")
switch track.State {
case immobilizershared.STATE_IMMOBILIZED:
// Car is currently marked as immobilized
if newState == "Immo_Inactive" {
// This is an issue, we have the car as being immobilized, but it just got moving. Now this could possible be a timing issue between a different ditto
// If this actually happens, we need to set the vehicle to Immobilizing
logger.Error().Str("VIN", vin).Str("MSG", "Marked as Immobilized, but received Immo_Inactive").Msg("Ditto Immobilizer")
}
// if newState == "Immo_Active" {
// // This is can be ignored
// }
case immobilizershared.STATE_IMMOBILIZING:
// We are trying to immobilize the vehicle
if newState == "Immo_Active" {
immobilizershared.UpdateCarImmobilized(vin, id.RedisClient)
// Send lock command some more
id.sendLockCommand(vin)
}
case immobilizershared.STATE_MOBILIZING:
// Hey great, we know that the car is now active
if newState == "Immo_Inactive" {
immobilizershared.UpdateCarMobilized(vin, id.RedisClient)
}
}
}
func (id *ImmobilizeDitto) PKC_KeyStsMod(vin string, newState string) {
id.WatchListSync.RLock()
track, ok := id.WatchList[vin]
id.WatchListSync.RUnlock()
if !ok {
return
}
logger.Debug().Str("New State", newState).Str("Tracked", track.State.String()).Str("VIN", vin).Msg("PKC_KeyStsMode signal")
switch track.State {
case immobilizershared.STATE_IMMOBILIZED:
// Car is currently marked as immobilized
if newState == state.PKC_KeyStsMod_disabled {
// we thought the car was immobilized, but we just got a signal saying its been mobilized
logger.Error().Str("VIN", vin).Str("MSG", "Marked as Immobilized, but received PKC_KeyStsMod: disabled").Msg("Ditto Immobilizer")
}
case immobilizershared.STATE_IMMOBILIZING:
// We were trying to immobilize, and we got confirmation that it is
if newState == state.PKC_KeyStsMod_enabled {
immobilizershared.UpdateCarImmobilized(vin, id.RedisClient)
}
case immobilizershared.STATE_MOBILIZING:
// trying to get car moving, and got confirmation it is
if newState == state.PKC_KeyStsMod_disabled {
immobilizershared.UpdateCarMobilized(vin, id.RedisClient)
}
}
}
// Ongoing process, needs to be run in da background
func (id *ImmobilizeDitto) ListenToRedisChanges() {
subscription := id.RedisClient.Subscribe(context.Background(), immobilizershared.IMMOBILIZER_PUBSUB_CHANNEL)
subChannel := subscription.Channel()
for msg := range subChannel {
rim := immobilizershared.RedisImmobilizerMsg{}
err := json.Unmarshal([]byte(msg.Payload), &rim)
if err != nil {
logger.Err(err).Str("Payload", msg.Payload).Msg("ListenToRedisChanges: Failed to parse payload")
continue
}
id.WatchListSync.Lock()
if rim.State == immobilizershared.STATE_MOBILIZED {
delete(id.WatchList, rim.VIN)
} else {
id.WatchList[rim.VIN] = immobilizershared.ImmobilizeTrack{State: rim.State}
}
id.WatchListSync.Unlock()
}
}
// Read in redis to get out initial state
func (id *ImmobilizeDitto) SeedLocalData() {
res, err := immobilizershared.ReadVehicleStatuses(id.RedisClient)
if err != nil {
return
}
id.WatchListSync.Lock()
id.WatchList = res
id.WatchListSync.Unlock()
}
// Don't want to become de-synced, so will seed once an hour
func (id *ImmobilizeDitto) SeedHourly() {
id.SeedLocalData()
time.AfterFunc(time.Hour, id.SeedHourly)
}
func InitImmobilizerDitto(connection *redisv2.Connection) (id *ImmobilizeDitto) {
id = &ImmobilizeDitto{}
id.RedisClient = connection
go id.SeedHourly()
go id.ListenToRedisChanges()
return id
}
// Wether a car is to start mobilizing or immobilizing is a decision by the server, not from a cars messaging
type ImmobilizeDitto struct {
RedisClient *redisv2.Connection
WatchList map[string]immobilizershared.ImmobilizeTrack
WatchListSync sync.RWMutex
}