From ea31ffb6510f9fe61a4880e7436a979e6fae27b9 Mon Sep 17 00:00:00 2001 From: Alex Savin Date: Tue, 27 May 2025 22:52:02 -0400 Subject: [PATCH] Updated config file and config struct --- client.go | 395 +++++++++--------------------------------- config/config.go | 88 ++++++---- config/config_test.go | 93 ++++++++++ go.mod | 20 +-- vehicle.go | 329 ++++------------------------------- 5 files changed, 266 insertions(+), 659 deletions(-) create mode 100644 config/config_test.go diff --git a/client.go b/client.go index 81b1aa5..b232fef 100644 --- a/client.go +++ b/client.go @@ -13,19 +13,9 @@ import ( "resty.dev/v3" ) -// credentials . -type credentials struct { - username string - password string - pin string - deviceId string - deviceName string -} - // Client . type Client struct { - baseURL string - credentials credentials + credentials config.Credentials httpClient *resty.Client country string // USA | CA updateInterval int // 7200 @@ -38,125 +28,14 @@ type Client struct { sync.RWMutex } -// Option . -type Option func(*Client) error - -// BaseURL allows overriding of API client baseURL for testing -func BaseURL(baseURL string) Option { - return func(c *Client) error { - c.baseURL = baseURL - return nil - } -} - -// Username . -func Username(username string) Option { - return func(c *Client) error { - c.credentials.username = username - return nil - } -} - -// Password . -func Password(password string) Option { - return func(c *Client) error { - c.credentials.password = password - return nil - } -} - // auth . func (c *Client) auth() []byte { - // { - // "success": true, - // "errorCode": null, - // "dataName": "sessionData", - // "data": { - // "sessionChanged": false, - // "vehicleInactivated": false, - // "account": { - // "marketId": 1, - // "createdDate": 1476984644000, - // "firstName": "Tatiana", - // "lastName": "Savin", - // "zipCode": "07974", - // "accountKey": 765268, - // "lastLoginDate": 1640539132000, - // "zipCode5": "07974" - // }, - // "resetPassword": false, - // "deviceId": "JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ", - // "sessionId": "010A2C0C25A5D35AC3776DB6B7900A3B", - // "deviceRegistered": true, - // "passwordToken": null, - // "vehicles": [ - // { - // "customer": { - // "sessionCustomer": null, - // "email": null, - // "firstName": null, - // "lastName": null, - // "zip": null, - // "oemCustId": null, - // "phone": null - // }, - // "stolenVehicle": false, - // "vehicleName": "Subaru Outback LXT", - // "features": null, - // "vin": "4S4BTGND8L3137058", - // "modelYear": null, - // "modelCode": null, - // "engineSize": null, - // "nickname": "Subaru Outback LXT", - // "vehicleKey": 3832950, - // "active": true, - // "licensePlate": "", - // "licensePlateState": "", - // "email": null, - // "firstName": null, - // "lastName": null, - // "subscriptionFeatures": null, - // "accessLevel": -1, - // "zip": null, - // "oemCustId": "CRM-631-HQN48K", - // "vehicleMileage": null, - // "phone": null, - // "userOemCustId": "CRM-631-HQN48K", - // "subscriptionStatus": null, - // "authorizedVehicle": false, - // "preferredDealer": null, - // "cachedStateCode": "NJ", - // "subscriptionPlans": [], - // "needMileagePrompt": false, - // "phev": null, - // "remoteServicePinExist": true, - // "needEmergencyContactPrompt": false, - // "vehicleGeoPosition": null, - // "extDescrip": null, - // "intDescrip": null, - // "modelName": null, - // "transCode": null, - // "provisioned": true, - // "timeZone": "America/New_York" - // } - // ], - // "currentVehicleIndex": 0, - // "handoffToken": "$2a$08$99me3RRpB00MNMSFxrG7AOg1T5BaDVacqXhbdTii0eRXoEoeFvEPy$1640540934344", - // "enableXtime": true, - // "termsAndConditionsAccepted": true, - // "digitalGlobeConnectId": "0572e32b-2fcf-4bc8-abe0-1e3da8767132", - // "digitalGlobeImageTileService": "https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/DigitalGlobe:ImageryTileService@EPSG:3857@png/{z}/{x}/{y}.png?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132", - // "digitalGlobeTransparentTileService": "https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/Digitalglobe:OSMTransparentTMSTileService@EPSG:3857@png/{z}/{x}/{-y}.png/?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132", - // "tomtomKey": "DHH9SwEQ4MW55Hj2TfqMeldbsDjTdgAs", - // "satelliteViewEnabled": true - // } - // } params := map[string]string{ "env": "cloudprod", "deviceType": "android", - "loginUsername": c.credentials.username, - "password": c.credentials.password, - "deviceId": c.credentials.deviceId, + "loginUsername": c.credentials.Username, + "password": c.credentials.Password, + "deviceId": c.credentials.DeviceID, "passwordToken": "", "selectedVin": "", "pushToken": ""} @@ -195,9 +74,9 @@ func (c *Client) registerDevice() bool { // }). // Post(apiURLs["WEB_API_LOGIN"]) params := map[string]string{ - "username": c.credentials.username, - "password": c.credentials.password, - "deviceId": c.credentials.deviceId} + "username": c.credentials.Username, + "password": c.credentials.Password, + "deviceId": c.credentials.DeviceID} reqURL := WEB_API_SERVER[c.country] + apiURLs["WEB_API_LOGIN"] c.execute(reqURL, POST, params, "", true) @@ -210,7 +89,7 @@ func (c *Client) registerDevice() bool { // }). // Get(apiURLs["WEB_API_AUTHORIZE_DEVICE"]) params = map[string]string{ - "deviceId": c.credentials.deviceId} + "deviceId": c.credentials.DeviceID} reqURL = WEB_API_SERVER[c.country] + apiURLs["WEB_API_AUTHORIZE_DEVICE"] c.execute(reqURL, GET, params, "", false) @@ -220,8 +99,8 @@ func (c *Client) registerDevice() bool { // setDeviceName . func (c *Client) setDeviceName() bool { params := map[string]string{ - "deviceId": c.credentials.deviceId, - "deviceName": c.credentials.deviceName} + "deviceId": c.credentials.DeviceID, + "deviceName": c.credentials.DeviceName} reqURL := WEB_API_SERVER[c.country] + apiURLs["WEB_API_NAME_DEVICE"] c.execute(reqURL, GET, params, "", false) @@ -315,111 +194,76 @@ func (c *Client) validateSession() bool { // if await self._select_vehicle(vin): // result = True - c.log.Debug("[DEBUG] Validate Session", "body", string([]byte(resp))) + c.log.Debug("session validation", "body", string([]byte(resp))) return true } -func (c *Client) SelectVehicle(vin string) VehicleData { - // { - // "success": true, - // "errorCode": null, - // "dataName": "vehicle", - // "data": { - // "customer": { - // "sessionCustomer": null, - // "email": null, - // "firstName": null, - // "lastName": null, - // "zip": null, - // "oemCustId": null, - // "phone": null - // }, - // "stolenVehicle": false, - // "vehicleName": "Subaru Outback LXT", - // "features": [ - // "ATF_MIL", - // "11.6MMAN", - // "ABS_MIL", - // "CEL_MIL", - // "ACCS", - // "RCC", - // "REARBRK", - // "TEL_MIL", - // "VDC_MIL", - // "TPMS_MIL", - // "WASH_MIL", - // "BSDRCT_MIL", - // "OPL_MIL", - // "EYESIGHT", - // "RAB_MIL", - // "SRS_MIL", - // "ESS_MIL", - // "RESCC", - // "EOL_MIL", - // "BSD", - // "EBD_MIL", - // "EPB_MIL", - // "RES", - // "RHSF", - // "AWD_MIL", - // "NAV_TOMTOM", - // "ISS_MIL", - // "RPOIA", - // "EPAS_MIL", - // "RPOI", - // "AHBL_MIL", - // "SRH_MIL", - // "g2" - // ], - // "vin": "4S4BTGND8L3137058", - // "modelYear": "2020", - // "modelCode": "LDJ", - // "engineSize": 2.4, - // "nickname": "Subaru Outback LXT", - // "vehicleKey": 3832950, - // "active": true, - // "licensePlate": "8KV8", - // "licensePlateState": "NJ", - // "email": null, - // "firstName": null, - // "lastName": null, - // "subscriptionFeatures": [ - // "REMOTE", - // "SAFETY", - // "Retail" - // ], - // "accessLevel": -1, - // "zip": null, - // "oemCustId": "CRM-631-HQN48K", - // "vehicleMileage": null, - // "phone": null, - // "userOemCustId": "CRM-631-HQN48K", - // "subscriptionStatus": "ACTIVE", - // "authorizedVehicle": false, - // "preferredDealer": null, - // "cachedStateCode": "NJ", - // "subscriptionPlans": [], - // "needMileagePrompt": false, - // "phev": null, - // "remoteServicePinExist": true, - // "needEmergencyContactPrompt": false, - // "vehicleGeoPosition": { - // "latitude": 40.70019, - // "longitude": -74.401375, - // "speed": null, - // "heading": null, - // "timestamp": 1640494569000 - // }, - // "extDescrip": "Abyss Blue Pearl", - // "intDescrip": "Gray", - // "modelName": "Outback", - // "transCode": "CVT", - // "provisioned": true, - // "timeZone": "America/New_York" - // } - // } +// New function creates a New MySubaru client +func New(logger *slog.Logger, config *config.MySubaru) (*Client, error) { + client := &Client{ + credentials: config.Credentials, + country: config.Region, + updateInterval: 7200, + fetchInterval: 360, + log: logger, + } + + httpClient := resty.New() + httpClient. + SetBaseURL(MOBILE_API_SERVER[client.country]). + SetHeaders(map[string]string{ + "User-Agent": "Mozilla/5.0 (Linux; Android 10; Android SDK built for x86 Build/QSR1.191030.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.185 Mobile Safari/537.36", + "Origin": "file://", + "X-Requested-With": MOBILE_APP[client.country], + "Accept-Language": "en-US,en;q=0.9", + "Accept-Encoding": "gzip, deflate", + "Accept": "*/*"}) + + client.httpClient = httpClient + + resp := client.auth() + respParsed, err := gabs.ParseJSON(resp) + if err != nil { + panic(err) + } + + if client.isResponseSuccessfull(resp) { + logger.Debug("Client authentication successful", "isRegistered", respParsed.Path("data.deviceRegistered").Data().(bool)) + client.isAuthenticated = true + client.isRegistered = respParsed.Path("data.deviceRegistered").Data().(bool) + } else { + error, _ := respParsed.Path("errorCode").Data().(string) + switch { + case error == apiErrors["ERROR_INVALID_ACCOUNT"]: + logger.Debug("Invalid account") + case error == apiErrors["ERROR_INVALID_CREDENTIALS"]: + logger.Debug("Client authentication failed") + case error == apiErrors["ERROR_PASSWORD_WARNING"]: + logger.Debug("Multiple Password Failures.") + default: + logger.Debug("Uknown error") + } + } + + if !client.isRegistered { + client.registerDevice() + } + + // TODO + fmt.Printf("Parcing cars: %d\n", len(respParsed.Path("data.vehicles").Children())) + for _, vehicle := range respParsed.Path("data.vehicles").Children() { + fmt.Printf("CAR: %s\n", vehicle.Path("vin").Data().(string)) + client.listOfVins = append(client.listOfVins, vehicle.Path("vin").Data().(string)) + } + client.currentVin = respParsed.Path("data.vehicles.0.vin").Data().(string) + + return client, nil +} + +func (c *Client) SelectVehicle(vin string) VehicleData { + // API > json > dataName > vehicle if vin == "" { vin = c.currentVin } @@ -467,78 +311,6 @@ func (c *Client) SelectVehicle(vin string) VehicleData { return vData } -// New function creates a New MySubaru client -func New(logger *slog.Logger, config *config.MySubaru) (*Client, error) { - - credentials := credentials{ - username: config.Credentials.Username, - password: config.Credentials.Password, - pin: config.Credentials.PIN, - deviceId: config.Credentials.DeviceID, - deviceName: config.Credentials.DeviceName, - } - - client := &Client{ - credentials: credentials, - country: config.Region, - updateInterval: 7200, - fetchInterval: 360, - log: logger, - } - - httpClient := resty.New() - httpClient. - SetBaseURL(MOBILE_API_SERVER[client.country]). - SetHeaders(map[string]string{ - "User-Agent": "Mozilla/5.0 (Linux; Android 10; Android SDK built for x86 Build/QSR1.191030.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.185 Mobile Safari/537.36", - "Origin": "file://", - "X-Requested-With": MOBILE_APP[client.country], - "Accept-Language": "en-US,en;q=0.9", - "Accept-Encoding": "gzip, deflate", - "Accept": "*/*"}) - - client.httpClient = httpClient - - resp := client.auth() - - respParsed, err := gabs.ParseJSON(resp) - if err != nil { - panic(err) - } - - if client.isResponseSuccessfull(resp) { - logger.Debug("Client authentication successful", "isRegistered", respParsed.Path("data.deviceRegistered").Data().(bool)) - client.isAuthenticated = true - client.isRegistered = respParsed.Path("data.deviceRegistered").Data().(bool) - } else { - error, _ := respParsed.Path("errorCode").Data().(string) - switch { - case error == apiErrors["ERROR_INVALID_ACCOUNT"]: - logger.Debug("Invalid account") - case error == apiErrors["ERROR_INVALID_CREDENTIALS"]: - logger.Debug("Client authentication failed") - case error == apiErrors["ERROR_PASSWORD_WARNING"]: - logger.Debug("Multiple Password Failures.") - default: - logger.Debug("Uknown error") - } - } - - if !client.isRegistered { - client.registerDevice() - } - - // TODO - fmt.Printf("Parcing cars: %d\n", len(respParsed.Path("data.vehicles").Children())) - for _, vehicle := range respParsed.Path("data.vehicles").Children() { - fmt.Printf("CAR: %s\n", vehicle.Path("vin").Data().(string)) - client.listOfVins = append(client.listOfVins, vehicle.Path("vin").Data().(string)) - } - client.currentVin = respParsed.Path("data.vehicles.0.vin").Data().(string) - - return client, nil -} - // GetVehicles . func (c *Client) GetVehicles() []*Vehicle { var vehicles []*Vehicle @@ -551,7 +323,7 @@ func (c *Client) GetVehicles() []*Vehicle { respParsed, err := gabs.ParseJSON([]byte(resp)) if err != nil { - panic(err) + c.log.Error("error which parsing json", "request", "GetVehicles", "error", err.Error()) } vData := VehicleData{} @@ -603,7 +375,7 @@ func (c *Client) GetVehicleByVIN(vin string) *Vehicle { respParsed, err := gabs.ParseJSON([]byte(resp)) if err != nil { - panic(err) + c.log.Error("error which parsing json", "request", "GetVehicleByVIN", "error", err.Error()) } vData := VehicleData{} @@ -682,7 +454,7 @@ func (c *Client) GetVehicleStatus() { respParsed, err := gabs.ParseJSON(resp) if err != nil { - panic(err) + c.log.Error("error which parsing json", "request", "GetVehicleStatus", "error", err.Error()) } c.log.Debug("GET VEHICLE STATUS OUTPUT", "body", respParsed) @@ -718,7 +490,7 @@ func (c *Client) GetClimateSettings() { respParsed, err := gabs.ParseJSON(resp) if err != nil { - panic(err) + c.log.Error("error which parsing json", "request", "GetClimateSettings", "error", err.Error()) } c.log.Debug("CLIMATE SETTINGS OUTPUT", "response", respParsed) @@ -755,12 +527,8 @@ func (c *Client) GetClimateSettings() { func (c *Client) execute(requestUrl string, method string, params map[string]string, pollingUrl string, json bool) []byte { defer timeTrack("[TIMETRK] Executing Get Request") - // if !isNil(resp.Cookies()) { - // log.Debugf("AUTH COOKIES OUTPUT >> %v\n", resp.Cookies()) - // c.cookies = resp.Cookies() - // } - var resp *resty.Response + // GET Requests if method == "GET" { resp, _ = c.httpClient. @@ -791,9 +559,8 @@ func (c *Client) execute(requestUrl string, method string, params map[string]str // } respParsed, err := gabs.ParseJSON(resBytes) if err != nil { - panic(err) + c.log.Error("error which parsing json", "request", "execute", "method", method, "url", requestUrl, "error", err.Error()) } - c.log.Debug("HTTP OUTPUT", "body", string(resBytes)) _, ok := respParsed.Path("success").Data().(bool) diff --git a/config/config.go b/config/config.go index 7a62d51..193c9c9 100644 --- a/config/config.go +++ b/config/config.go @@ -1,20 +1,33 @@ package config import ( + "encoding/json" "log/slog" "os" - "github.com/spf13/viper" + "gopkg.in/yaml.v3" +) + +const ( + LoggingOutputJson = "JSON" + LoggingOutputText = "TEXT" ) // Config . type Config struct { + MySubaru MySubaru + TimeZone string + Logger *slog.Logger +} + +// config defines the structure of configuration data to be parsed from a config source. +type config struct { MySubaru MySubaru `json:"mysubaru" yaml:"mysubaru"` - Timezone string `json:"timezone" yaml:"timezone"` + TimeZone string `json:"timezone" yaml:"timezone"` Logging *Logging `json:"logging" yaml:"logging"` } -// Emporia . +// MySubaru . type MySubaru struct { Credentials Credentials `json:"credentials" yaml:"credentials"` Region string `json:"region" yaml:"region"` @@ -37,36 +50,49 @@ type Logging struct { Source bool `json:"source,omitempty" yaml:"source,omitempty"` } -func New() (*Config, error) { - viper.SetConfigName("config") // name of config file (without extension) - viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name - viper.AddConfigPath(".") // optionally look for config in the working directory - viper.AddConfigPath("/etc/mysubaru") // optionally look for config in the working directory - - viper.AutomaticEnv() - - viper.SetDefault("Timezone", "America/New_York") - viper.SetDefault("MySubaru.AutoReconnect", true) - viper.SetDefault("Logging.Level", "INFO") - viper.SetDefault("Logging.Output", "TEXT") - viper.SetDefault("Logging.Source", "false") - - err := viper.ReadInConfig() // Find and read the config file - if err != nil { // Handle errors reading the config file - slog.Error("cannot open config file", "error", err.Error()) - os.Exit(1) +func (l Logging) ToLogger() *slog.Logger { + var level slog.Level + if err := level.UnmarshalText([]byte(l.Level)); err != nil { + level = slog.LevelInfo } - // viper.WatchConfig() - // viper.OnConfigChange(func(e fsnotify.Event) { - // log.Println("Config file changed:", e.Name) - // }) - - var config Config - if err := viper.Unmarshal(&config); err != nil { - slog.Error("cannot unmarshal config file", "error", err.Error()) - os.Exit(1) + var handler slog.Handler + switch l.Output { + case LoggingOutputJson: + handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: l.Source, Level: level}) + case LoggingOutputText: + handler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{AddSource: l.Source, Level: level}) + default: + handler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{AddSource: l.Source, Level: level}) } - return &config, nil + return slog.New(handler) +} + +// FromBytes unmarshals a byte slice of JSON or YAML config data into a valid server options value. +func FromBytes(b []byte) (*Config, error) { + c := new(config) + o := Config{} + + if len(b) == 0 { + return nil, nil + } + + if b[0] == '{' { + err := json.Unmarshal(b, c) + if err != nil { + return nil, err + } + } else { + err := yaml.Unmarshal(b, c) + if err != nil { + return nil, err + } + } + + o.MySubaru = c.MySubaru + o.TimeZone = c.TimeZone + o.Logger = c.Logging.ToLogger() + + return &o, nil } diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000..3e0a533 --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,93 @@ +package config + +import ( + "log/slog" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +var ( + yamlBytes = []byte(` +mysubaru: + credentials: + username: username@mysubaru.golang + password: "PASSWORD" + pin: "1234" + deviceid: AaBbCcDdEeFf0123456789 + devicename: MySubaru Golang Client + region: USA + auto_reconnect: true +timezone: "America/New_York" +logging: + level: INFO + output: TEXT + source: false +`) + + jsonBytes = []byte(`{ + "mysubaru": { + "credentials": { + "username": "username@mysubaru.golang", + "password": "PASSWORD", + "pin": "1234", + "deviceid": "AaBbCcDdEeFf0123456789", + "devicename": "MySubaru Golang Client" + }, + "region": "USA", + "auto_reconnect": true + }, + "timezone": "America/New_York", + "logging": { + "level": "INFO", + "output": "TEXT", + "source": false + } +} +`) + parsedOptions = Config{ + MySubaru: MySubaru{ + Credentials: Credentials{ + Username: "username@mysubaru.golang", + Password: "PASSWORD", + PIN: "1234", + DeviceID: "AaBbCcDdEeFf0123456789", + DeviceName: "MySubaru Golang Client", + }, + Region: "USA", + AutoReconnect: true, + }, + TimeZone: "America/New_York", + Logger: slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelInfo, + })), + } +) + +func TestFromBytesEmptyL(t *testing.T) { + _, err := FromBytes([]byte{}) + require.NoError(t, err) +} + +func TestFromBytesYAML(t *testing.T) { + o, err := FromBytes(yamlBytes) + require.NoError(t, err) + require.Equal(t, parsedOptions, *o) +} + +func TestFromBytesYAMLError(t *testing.T) { + _, err := FromBytes(append(yamlBytes, 'a')) + require.Error(t, err) +} + +func TestFromBytesJSON(t *testing.T) { + o, err := FromBytes(jsonBytes) + require.NoError(t, err) + require.Equal(t, parsedOptions, *o) +} + +func TestFromBytesJSONError(t *testing.T) { + _, err := FromBytes(append(jsonBytes, 'a')) + require.Error(t, err) +} diff --git a/go.mod b/go.mod index 99f7cc1..bd2dd9f 100644 --- a/go.mod +++ b/go.mod @@ -4,23 +4,15 @@ go 1.24 require ( github.com/Jeffail/gabs/v2 v2.7.0 - github.com/spf13/viper v1.20.1 + github.com/stretchr/testify v1.10.0 + gopkg.in/yaml.v3 v3.0.1 resty.dev/v3 v3.0.0-beta.3 ) require ( - github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/sagikazarmark/locafero v0.9.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.14.0 // indirect - github.com/spf13/cast v1.8.0 // indirect - github.com/spf13/pflag v1.0.6 // indirect - github.com/subosito/gotenv v1.6.0 // indirect - go.uber.org/multierr v1.11.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/net v0.40.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) diff --git a/vehicle.go b/vehicle.go index 35503cc..839063a 100644 --- a/vehicle.go +++ b/vehicle.go @@ -35,67 +35,6 @@ var parts = map[string]map[string][]string{ }, } -// { -// "success": true, -// "errorCode": null, -// "dataName": "vehicle", -// "data": { -// "customer": { -// "sessionCustomer": null, -// "email": null, -// "firstName": null, -// "lastName": null, -// "zip": null, -// "oemCustId": null, -// "phone": null -// }, -// "stolenVehicle": false, -// "vehicleName": "Subaru Outback LXT", -// "features": ["ATF_MIL","11.6MMAN","ABS_MIL","CEL_MIL","ACCS","RCC","REARBRK","TEL_MIL","VDC_MIL","TPMS_MIL","WASH_MIL","BSDRCT_MIL","OPL_MIL","EYESIGHT","RAB_MIL","SRS_MIL","ESS_MIL","RESCC","EOL_MIL","BSD","EBD_MIL","EPB_MIL","RES","RHSF","AWD_MIL","NAV_TOMTOM","ISS_MIL","RPOIA","EPAS_MIL","RPOI","AHBL_MIL","SRH_MIL","g2"], -// "vin": "4S4BTGND8L3137058", -// "modelYear": "2020", -// "modelCode": "LDJ", -// "engineSize": 2.4, -// "nickname": "Subaru Outback LXT", -// "vehicleKey": 3832950, -// "active": true, -// "licensePlate": "8KV8", -// "licensePlateState": "NJ", -// "email": null, -// "firstName": null, -// "lastName": null, -// "subscriptionFeatures": ["REMOTE","SAFETY","Retail"], -// "accessLevel": -1, -// "zip": null, -// "oemCustId": "CRM-631-HQN48K", -// "vehicleMileage": null, -// "phone": null, -// "userOemCustId": "CRM-631-HQN48K", -// "subscriptionStatus": "ACTIVE", -// "authorizedVehicle": false, -// "preferredDealer": null, -// "cachedStateCode": "NJ", -// "subscriptionPlans": [], -// "needMileagePrompt": false, -// "phev": null, -// "remoteServicePinExist": true, -// "needEmergencyContactPrompt": false, -// "vehicleGeoPosition": { -// "latitude": 40.70018, -// "longitude": -74.40139, -// "speed": null, -// "heading": null, -// "timestamp": 1642538410000 -// }, -// "extDescrip": "Abyss Blue Pearl", -// "intDescrip": "Gray", -// "modelName": "Outback", -// "transCode": "CVT", -// "provisioned": true, -// "timeZone": "America/New_York" -// } -// } - // Vehicle . type Vehicle struct { CarId int64 @@ -251,49 +190,14 @@ func (v *Vehicle) String() string { } // Lock . -// Send command to lock doors. +// Sends a command to lock doors. func (v *Vehicle) Lock() { - // { - // "success": true, - // "errorCode": null, - // "dataName": "remoteServiceStatus", - // "data": { - // "serviceRequestId": "4S4BTGND8L3137058_1640454085449_20_@NGTP", - // "success": false, - // "cancelled": false, - // "remoteServiceType": "lock", - // "remoteServiceState": "started", - // "subState": null, - // "errorCode": null, - // "result": null, - // "updateTime": 1640454085000, - // "vin": "4S4BTGND8L3137058" - // } - // } - // { - // "success": true, - // "errorCode": null, - // "dataName": "remoteServiceStatus", - // "data": { - // "serviceRequestId": "4S4BTGND8L3137058_1640454085449_20_@NGTP", - // "success": true, - // "cancelled": false, - // "remoteServiceType": "lock", - // "remoteServiceState": "finished", - // "subState": null, - // "errorCode": "null:null", - // "result": null, - // "updateTime": 1640454091000, - // "vin": "4S4BTGND8L3137058" - // } - // } - if v.getRemoteOptionsStatus() { v.selectVehicle() params := map[string]string{ "delay": "0", "vin": v.Vin, - "pin": v.client.credentials.pin, + "pin": v.client.credentials.PIN, "forceKeyInCar": "false"} reqURL := MOBILE_API_VERSION + urlToGen(apiURLs["API_LOCK"], v.getAPIGen()) pollingURL := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] @@ -306,47 +210,12 @@ func (v *Vehicle) Lock() { // Unlock . // Send command to unlock doors. func (v *Vehicle) Unlock() { - // { - // "success": true, - // "errorCode": null, - // "dataName": "remoteServiceStatus", - // "data": { - // "serviceRequestId": "4S4BTGND8L3137058_1640539133289_19_@NGTP", - // "success": false, - // "cancelled": false, - // "remoteServiceType": "unlock", - // "remoteServiceState": "started", - // "subState": null, - // "errorCode": null, - // "result": null, - // "updateTime": 1640539133000, - // "vin": "4S4BTGND8L3137058" - // } - // } - // { - // "success": true, - // "errorCode": null, - // "dataName": "remoteServiceStatus", - // "data": { - // "serviceRequestId": "4S4BTGND8L3137058_1640539133289_19_@NGTP", - // "success": true, - // "cancelled": false, - // "remoteServiceType": "unlock", - // "remoteServiceState": "finished", - // "subState": null, - // "errorCode": "null:null", - // "result": null, - // "updateTime": 1640539140000, - // "vin": "4S4BTGND8L3137058" - // } - // } - if v.getRemoteOptionsStatus() { v.selectVehicle() params := map[string]string{ "delay": "0", "vin": v.Vin, - "pin": v.client.credentials.pin, + "pin": v.client.credentials.PIN, "unlockDoorType": "ALL_DOORS_CMD"} // FRONT_LEFT_DOOR_CMD | ALL_DOORS_CMD reqURL := MOBILE_API_VERSION + urlToGen(apiURLs["API_UNLOCK"], v.getAPIGen()) pollingURL := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] @@ -382,50 +251,15 @@ func (v *Vehicle) Unlock() { // await asyncio.sleep(2) } -// EngineOn . -// Send command to start engine and set climate control. +// EngineStart . +// Sends a command to start engine and set climate control. func (v *Vehicle) EngineStart() { - // { - // "success": true, - // "errorCode": null, - // "dataName": "remoteServiceStatus", - // "data": { - // "serviceRequestId": "4S4BTGND8L3137058_1640456287656_22_@NGTP", - // "success": false, - // "cancelled": false, - // "remoteServiceType": "engineStart", - // "remoteServiceState": "started", - // "subState": null, - // "errorCode": null, - // "result": null, - // "updateTime": 1640456287000, - // "vin": "4S4BTGND8L3137058" - // } - // } - // { - // "success": true, - // "errorCode": null, - // "dataName": "remoteServiceStatus", - // "data": { - // "serviceRequestId": "4S4BTGND8L3137058_1640456287656_22_@NGTP", - // "success": true, - // "cancelled": false, - // "remoteServiceType": "engineStart", - // "remoteServiceState": "finished", - // "subState": null, - // "errorCode": "null:null", - // "result": null, - // "updateTime": 1640456302000, - // "vin": "4S4BTGND8L3137058" - // } - // } - if v.getRemoteOptionsStatus() { v.selectVehicle() params := map[string]string{ "delay": "0", "vin": v.Vin, - "pin": v.client.credentials.pin, + "pin": v.client.credentials.PIN, "horn": "true", "climateSettings": "climateSettings", // climateSettings "climateZoneFrontTemp": "65", // 60-86 @@ -447,50 +281,15 @@ func (v *Vehicle) EngineStart() { } } -// EngineOff . -// Send command to stop engine. +// EngineStop . +// Sends a command to stop engine. func (v *Vehicle) EngineStop() { - // { - // "success": true, - // "errorCode": null, - // "dataName": "remoteServiceStatus", - // "data": { - // "serviceRequestId": "4S4BTGND8L3137058_1640456318773_23_@NGTP", - // "success": false, - // "cancelled": false, - // "remoteServiceType": "engineStop", - // "remoteServiceState": "started", - // "subState": null, - // "errorCode": null, - // "result": null, - // "updateTime": null, - // "vin": "4S4BTGND8L3137058" - // } - // } - // { - // "success": true, - // "errorCode": null, - // "dataName": "remoteServiceStatus", - // "data": { - // "serviceRequestId": "4S4BTGND8L3137058_1640456318773_23_@NGTP", - // "success": true, - // "cancelled": false, - // "remoteServiceType": "engineStop", - // "remoteServiceState": "finished", - // "subState": null, - // "errorCode": "null:null", - // "result": null, - // "updateTime": 1640456321000, - // "vin": "4S4BTGND8L3137058" - // } - // } - if v.getRemoteOptionsStatus() { v.selectVehicle() params := map[string]string{ "delay": "0", "vin": v.Vin, - "pin": v.client.credentials.pin} + "pin": v.client.credentials.PIN} reqURL := MOBILE_API_VERSION + apiURLs["API_G2_REMOTE_ENGINE_STOP"] pollingURL := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] v.client.execute(reqURL, POST, params, pollingURL, true) @@ -500,49 +299,14 @@ func (v *Vehicle) EngineStop() { } // LightsStart . -// Send command to flash lights. +// Sends a command to flash lights. func (v *Vehicle) LightsStart() { - // { - // "success": true, - // "errorCode": null, - // "dataName": "remoteServiceStatus", - // "data": { - // "serviceRequestId": "4S4BTGND8L3137058_1640457256003_21_@NGTP", - // "success": false, - // "cancelled": false, - // "remoteServiceType": "lightsOnly", - // "remoteServiceState": "started", - // "subState": null, - // "errorCode": null, - // "result": null, - // "updateTime": 1640457256000, - // "vin": "4S4BTGND8L3137058" - // } - // } - // { - // "success": true, - // "errorCode": null, - // "dataName": "remoteServiceStatus", - // "data": { - // "serviceRequestId": "4S4BTGND8L3137058_1640457256003_21_@NGTP", - // "success": true, - // "cancelled": false, - // "remoteServiceType": "lightsOnly", - // "remoteServiceState": "finished", - // "subState": null, - // "errorCode": "null:null", - // "result": null, - // "updateTime": 1640457262000, - // "vin": "4S4BTGND8L3137058" - // } - // } - if v.getRemoteOptionsStatus() { v.selectVehicle() params := map[string]string{ "delay": "0", "vin": v.Vin, - "pin": v.client.credentials.pin} + "pin": v.client.credentials.PIN} reqURL := MOBILE_API_VERSION + urlToGen(apiURLs["API_LIGHTS"], v.getAPIGen()) pollingURL := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] if v.getAPIGen() == FEATURE_G1_TELEMATICS { @@ -555,49 +319,14 @@ func (v *Vehicle) LightsStart() { } // LightsStop . -// Send command to stop flash lights. +// Sends a command to stop flash lights. func (v *Vehicle) LightsStop() { - // { - // "success": true, - // "errorCode": null, - // "dataName": "remoteServiceStatus", - // "data": { - // "serviceRequestId": "4S4BTGND8L3137058_1640457280857_47_@NGTP", - // "success": false, - // "cancelled": false, - // "remoteServiceType": "lightsOnly", - // "remoteServiceState": "stopping", - // "subState": null, - // "errorCode": "null:null", - // "result": null, - // "updateTime": 1640457262000, - // "vin": "4S4BTGND8L3137058" - // } - // } - // { - // "success": true, - // "errorCode": null, - // "dataName": "remoteServiceStatus", - // "data": { - // "serviceRequestId": "4S4BTGND8L3137058_1640457280857_47_@NGTP", - // "success": true, - // "cancelled": false, - // "remoteServiceType": "lightsOnly", - // "remoteServiceState": "finished", - // "subState": null, - // "errorCode": "null:null", - // "result": null, - // "updateTime": 1640457280000, - // "vin": "4S4BTGND8L3137058" - // } - // } - if v.getRemoteOptionsStatus() { v.selectVehicle() params := map[string]string{ "delay": "0", "vin": v.Vin, - "pin": v.client.credentials.pin} + "pin": v.client.credentials.PIN} reqURL := MOBILE_API_VERSION + urlToGen(apiURLs["API_LIGHTS_STOP"], v.getAPIGen()) pollingURL := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] if v.getAPIGen() == FEATURE_G1_TELEMATICS { @@ -617,7 +346,7 @@ func (v *Vehicle) HornStart() { params := map[string]string{ "delay": "0", "vin": v.Vin, - "pin": v.client.credentials.pin} + "pin": v.client.credentials.PIN} reqURL := MOBILE_API_VERSION + urlToGen(apiURLs["API_HORN_LIGHTS"], v.getAPIGen()) pollingURL := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] if v.getAPIGen() == FEATURE_G1_TELEMATICS { @@ -637,7 +366,7 @@ func (v *Vehicle) HornStop() { params := map[string]string{ "delay": "0", "vin": v.Vin, - "pin": v.client.credentials.pin} + "pin": v.client.credentials.PIN} reqURL := MOBILE_API_VERSION + urlToGen(apiURLs["API_HORN_LIGHTS_STOP"], v.getAPIGen()) pollingURL := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] if v.getAPIGen() == FEATURE_G1_TELEMATICS { @@ -691,7 +420,7 @@ func (v *Vehicle) ChargeOn() { params := map[string]string{ "delay": "0", "vin": v.Vin, - "pin": v.client.credentials.pin} + "pin": v.client.credentials.PIN} reqURL := MOBILE_API_VERSION + apiURLs["API_EV_CHARGE_NOW"] pollingURL := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] v.client.execute(reqURL, POST, params, pollingURL, true) @@ -746,7 +475,7 @@ func (v *Vehicle) GetLocation(force bool) { pollingURL := MOBILE_API_VERSION + apiURLs["API_G2_LOCATE_STATUS"] params := map[string]string{ "vin": v.Vin, - "pin": v.client.credentials.pin} + "pin": v.client.credentials.PIN} if v.getAPIGen() == FEATURE_G1_TELEMATICS { reqURL = MOBILE_API_VERSION + apiURLs["API_G1_LOCATE_UPDATE"] pollingURL = MOBILE_API_VERSION + apiURLs["API_G1_LOCATE_STATUS"] @@ -779,7 +508,7 @@ func (v *Vehicle) GetLocation(force bool) { v.selectVehicle() params := map[string]string{ "vin": v.Vin, - "pin": v.client.credentials.pin} + "pin": v.client.credentials.PIN} reqURL := MOBILE_API_VERSION + urlToGen(apiURLs["API_LOCATE"], v.getAPIGen()) v.client.execute(reqURL, GET, params, "", false) } @@ -790,14 +519,14 @@ func (v *Vehicle) GetClimateQuickPresets() { if v.getRemoteOptionsStatus() { // params := map[string]string{ // "vin": v.Vin, - // "pin": v.Client.credentials.pin} + // "pin": v.Client.credentials.PIN} v.selectVehicle() reqURL := MOBILE_API_VERSION + apiURLs["API_G2_FETCH_RES_QUICK_START_SETTINGS"] resp := v.client.execute(reqURL, GET, map[string]string{}, "", false) respParsed, err := gabs.ParseJSON(resp) if err != nil { - panic(err) + v.client.log.Error("error which parsing json", "request", "GetClimateQuickPresets", "error", err.Error()) } v.client.log.Debug("CLIMATE SETTINGS OUTPUT", "body", respParsed) @@ -826,9 +555,10 @@ func (v *Vehicle) GetClimatePresets() { v.selectVehicle() reqURL := MOBILE_API_VERSION + apiURLs["API_G2_FETCH_RES_SUBARU_PRESETS"] resp := v.client.execute(reqURL, GET, map[string]string{}, "", false) + respParsed, err := gabs.ParseJSON(resp) if err != nil { - panic(err) + v.client.log.Error("error which parsing json", "request", "GetClimatePresets", "error", err.Error()) } // ONLY FOR THAT REQUEST BECAUSE OF API SENDS BACK ESCAPED DATA IN DATA FIELD @@ -856,9 +586,10 @@ func (v *Vehicle) GetClimateUserPresets() { v.selectVehicle() reqURL := MOBILE_API_VERSION + apiURLs["API_G2_FETCH_RES_USER_PRESETS"] resp := v.client.execute(reqURL, GET, map[string]string{}, "", false) + respParsed, err := gabs.ParseJSON(resp) if err != nil { - panic(err) + v.client.log.Error("error which parsing json", "request", "GetClimateUserPresets", "error", err.Error()) } v.client.log.Debug("CLIMATE USER SETTINGS OUTPUT", "body", respParsed) @@ -904,7 +635,7 @@ func (v *Vehicle) GetVehicleStatus() { if v.client.isResponseSuccessfull(resp) { respParsed, err := gabs.ParseJSON(resp) if err != nil { - v.client.log.Error("error which parsing json", "error", err.Error()) + v.client.log.Error("error which parsing json", "request", "GetVehicleStatus", "error", err.Error()) } vSta := VehicleStatus{} @@ -919,9 +650,7 @@ func (v *Vehicle) GetVehicleStatus() { v.DistanceToEmpty.Kilometers = vSta.DistanceToEmptyFuelKilometers v.DistanceToEmpty.Miles10s = vSta.DistanceToEmptyFuelMiles10s v.DistanceToEmpty.Kilometers10s = vSta.DistanceToEmptyFuelKilometers10s - if vSta.RemainingFuelPercent >= 0 && vSta.RemainingFuelPercent <= 100 { - v.DistanceToEmpty.Percentage = vSta.RemainingFuelPercent - } + v.DistanceToEmpty.Percentage = vSta.RemainingFuelPercent v.FuelConsumptionAvg.MPG = float64(vSta.AvgFuelConsumptionMpg) v.FuelConsumptionAvg.LP100Km = float64(vSta.AvgFuelConsumptionLitersPer100Kilometers) @@ -1047,7 +776,7 @@ func (v *Vehicle) GetVehicleStatus() { // } } -// GetVehicleStatus . +// GetVehicleCondition . func (v *Vehicle) GetVehicleCondition() { v.selectVehicle() reqURL := MOBILE_API_VERSION + urlToGen(apiURLs["API_CONDITION"], v.getAPIGen()) @@ -1056,7 +785,7 @@ func (v *Vehicle) GetVehicleCondition() { if v.client.isResponseSuccessfull(resp) { respParsed, err := gabs.ParseJSON(resp) if err != nil { - v.client.log.Error("error which parsing json", "error", err.Error()) + v.client.log.Error("error which parsing json", "request", "GetVehicleCondition", "error", err.Error()) } re := regexp.MustCompile(`[A-Z][^A-Z]*`) @@ -1166,7 +895,7 @@ func (v *Vehicle) GetVehicleHealth() { if v.client.isResponseSuccessfull(resp) { _, err := gabs.ParseJSON(resp) if err != nil { - v.client.log.Error("error which parsing json", "error", err.Error()) + v.client.log.Error("error which parsing json", "request", "GetVehicleHealth", "error", err.Error()) } // TODO: }