236 lines
7.8 KiB
Go
236 lines
7.8 KiB
Go
package immobilizerditto
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
|
"github.com/fiskerinc/cloud-services/pkg/dbc/state"
|
|
"github.com/fiskerinc/cloud-services/pkg/immobilizer/immobilizershared"
|
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
|
"github.com/fiskerinc/cloud-services/pkg/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
|
|
}
|