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

View File

@@ -0,0 +1,235 @@
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
}

View File

@@ -0,0 +1,6 @@
So need to listen to redis updates to know when a new car is added to the system.
Take that signal and add it to our map thing
Ditto should special listen for the parked signal, the immobilize signal, and possible another if we need
When we receive the immobilize signal, we can turn it off. Signal is continues sent