Compare commits
26 Commits
699dc13118
...
main
Author | SHA1 | Date | |
---|---|---|---|
143d100793 | |||
92d4266f8b | |||
d7944123dd | |||
0e5b9aae19 | |||
3809ed5883 | |||
32dfb8fb6e | |||
6604b8ccc3 | |||
20f7ab5aa2 | |||
d41a2a7618 | |||
cbca67a61d | |||
c3d0fa53f1 | |||
89b3d44d82 | |||
ebe98e685a | |||
ed6ee5aacc | |||
7f5b092c64 | |||
c018850e34 | |||
7ec4dc5f1a | |||
a455733f8b | |||
0b2ed38ca3 | |||
07e3005e9c | |||
e8a1d8f54e | |||
30bd0bde44 | |||
aec4b8435b | |||
1d8d175be0 | |||
ac19db1271 | |||
f850b55b52 |
275
client.go
275
client.go
@ -5,8 +5,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.savin.nyc/alex/mysubaru/config"
|
"git.savin.nyc/alex/mysubaru/config"
|
||||||
"resty.dev/v3"
|
"resty.dev/v3"
|
||||||
@ -16,14 +18,15 @@ import (
|
|||||||
type Client struct {
|
type Client struct {
|
||||||
credentials config.Credentials
|
credentials config.Credentials
|
||||||
httpClient *resty.Client
|
httpClient *resty.Client
|
||||||
country string // USA | CA
|
country string // USA | CA
|
||||||
updateInterval int // 7200
|
contactMethods dataMap // List of contact methods for 2FA
|
||||||
fetchInterval int // 360
|
|
||||||
currentVin string
|
currentVin string
|
||||||
listOfVins []string
|
listOfVins []string
|
||||||
isAuthenticated bool
|
isAuthenticated bool
|
||||||
isRegistered bool
|
isRegistered bool
|
||||||
isAlive bool
|
isAlive bool
|
||||||
|
updateInterval int // 7200
|
||||||
|
fetchInterval int // 360
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
@ -48,45 +51,73 @@ func New(config *config.Config) (*Client, error) {
|
|||||||
"X-Requested-With": MOBILE_APP[client.country],
|
"X-Requested-With": MOBILE_APP[client.country],
|
||||||
"Accept-Language": "en-US,en;q=0.9",
|
"Accept-Language": "en-US,en;q=0.9",
|
||||||
"Accept-Encoding": "gzip, deflate",
|
"Accept-Encoding": "gzip, deflate",
|
||||||
"Accept": "*/*"})
|
"Accept": "*/*"},
|
||||||
|
)
|
||||||
|
|
||||||
client.httpClient = httpClient
|
client.httpClient = httpClient
|
||||||
resp, err := client.auth()
|
|
||||||
if err != nil {
|
if ok, err := client.auth(); !ok {
|
||||||
client.logger.Error("error while executing auth request", "request", "auth", "error", err.Error())
|
client.logger.Error("error while executing auth request", "request", "auth", "error", err.Error())
|
||||||
return nil, errors.New("error while executing auth request: " + err.Error())
|
return nil, errors.New("error while executing auth request: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// auth authenticates the client with the MySubaru API using the provided credentials.
|
||||||
|
func (c *Client) auth() (bool, error) {
|
||||||
|
params := map[string]string{
|
||||||
|
"env": "cloudprod",
|
||||||
|
"deviceType": "android",
|
||||||
|
"loginUsername": c.credentials.Username,
|
||||||
|
"password": c.credentials.Password,
|
||||||
|
"deviceId": c.credentials.DeviceID,
|
||||||
|
"passwordToken": "",
|
||||||
|
"selectedVin": "",
|
||||||
|
"pushToken": ""}
|
||||||
|
reqURL := MOBILE_API_VERSION + apiURLs["API_LOGIN"]
|
||||||
|
resp, err := c.execute(POST, reqURL, params, false)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("error while executing auth request", "request", "auth", "error", err.Error())
|
||||||
|
return false, errors.New("error while executing auth request: " + err.Error())
|
||||||
|
}
|
||||||
|
c.logger.Debug("http request output", "request", "auth", "body", resp)
|
||||||
|
|
||||||
var sd SessionData
|
var sd SessionData
|
||||||
err = json.Unmarshal(resp.Data, &sd)
|
err = json.Unmarshal(resp.Data, &sd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
client.logger.Error("error while parsing json", "request", "auth", "error", err.Error())
|
c.logger.Error("error while parsing json", "request", "auth", "error", err.Error())
|
||||||
}
|
}
|
||||||
// client.logger.Debug("unmarshaled json data", "request", "auth", "type", "sessionData", "body", sd)
|
// client.logger.Debug("unmarshaled json data", "request", "auth", "type", "sessionData", "body", sd)
|
||||||
|
|
||||||
if sd.DeviceRegistered && sd.RegisteredDevicePermanent {
|
if !sd.DeviceRegistered {
|
||||||
// client.logger.Debug("client authentication successful")
|
err := c.getContactMethods()
|
||||||
client.isAuthenticated = true
|
if err != nil {
|
||||||
client.isRegistered = true
|
c.logger.Error("error while getting contact methods", "request", "auth", "error", err.Error())
|
||||||
}
|
return false, errors.New("error while getting contact methods: " + err.Error())
|
||||||
// TODO: Work on registerDevice()
|
}
|
||||||
// } else {
|
|
||||||
// // client.logger.Debug("client authentication successful, but devices is not registered")
|
c.logger.Error("device is not registered", "request", "auth", "deviceId", c.credentials.DeviceID)
|
||||||
// client.registerDevice()
|
return false, errors.New("device is not registered: " + c.credentials.DeviceID)
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
if sd.DeviceRegistered && sd.RegisteredDevicePermanent {
|
||||||
|
c.isAuthenticated = true
|
||||||
|
c.isRegistered = true
|
||||||
|
c.isAlive = true
|
||||||
|
}
|
||||||
|
c.logger.Debug("MySubaru API client authenticated")
|
||||||
|
|
||||||
// client.logger.Debug("parsing cars assigned to the account", "quantity", len(sd.Vehicles))
|
|
||||||
if len(sd.Vehicles) > 0 {
|
if len(sd.Vehicles) > 0 {
|
||||||
for _, vehicle := range sd.Vehicles {
|
for _, vehicle := range sd.Vehicles {
|
||||||
// client.logger.Debug("parsing car", "vin", vehicle.Vin)
|
c.listOfVins = append(c.listOfVins, vehicle.Vin)
|
||||||
client.listOfVins = append(client.listOfVins, vehicle.Vin)
|
|
||||||
}
|
}
|
||||||
client.currentVin = client.listOfVins[0]
|
c.currentVin = c.listOfVins[0]
|
||||||
} else {
|
} else {
|
||||||
client.logger.Error("there are no cars assigned to the account")
|
c.logger.Error("there are no vehicles associated with the account", "request", "auth", "error", "no vehicles found")
|
||||||
return nil, err
|
return false, err
|
||||||
}
|
}
|
||||||
return client, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelectVehicle selects a vehicle by its VIN. If no VIN is provided, it uses the current VIN.
|
// SelectVehicle selects a vehicle by its VIN. If no VIN is provided, it uses the current VIN.
|
||||||
@ -141,17 +172,17 @@ func (c *Client) GetVehicleByVin(vin string) (*Vehicle, error) {
|
|||||||
reqURL := MOBILE_API_VERSION + apiURLs["API_SELECT_VEHICLE"]
|
reqURL := MOBILE_API_VERSION + apiURLs["API_SELECT_VEHICLE"]
|
||||||
resp, err := c.execute(GET, reqURL, params, false)
|
resp, err := c.execute(GET, reqURL, params, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error("error while executing GetVehicleByVIN request", "request", "GetVehicleByVIN", "error", err.Error())
|
c.logger.Error("error while executing GetVehicleByVin request", "request", "GetVehicleByVin", "error", err.Error())
|
||||||
return nil, errors.New("error while executing GetVehicleByVIN request: " + err.Error())
|
return nil, errors.New("error while executing GetVehicleByVin request: " + err.Error())
|
||||||
}
|
}
|
||||||
// c.logger.Debug("http request output", "request", "GetVehicleByVIN", "body", resp)
|
// c.logger.Debug("http request output", "request", "GetVehicleByVin", "body", resp)
|
||||||
|
|
||||||
var vd VehicleData
|
var vd VehicleData
|
||||||
err = json.Unmarshal(resp.Data, &vd)
|
err = json.Unmarshal(resp.Data, &vd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error("error while parsing json", "request", "GetVehicleByVIN", "error", err.Error())
|
c.logger.Error("error while parsing json", "request", "GetVehicleByVin", "error", err.Error())
|
||||||
}
|
}
|
||||||
// c.logger.Debug("http request output", "request", "GetVehicleByVIN", "body", resp)
|
// c.logger.Debug("http request output", "request", "GetVehicleByVin", "body", resp)
|
||||||
|
|
||||||
vehicle = &Vehicle{
|
vehicle = &Vehicle{
|
||||||
Vin: vin,
|
Vin: vin,
|
||||||
@ -196,13 +227,124 @@ func (c *Client) GetVehicleByVin(vin string) (*Vehicle, error) {
|
|||||||
return nil, errors.New("vin code is not in the list of the available vin codes")
|
return nil, errors.New("vin code is not in the list of the available vin codes")
|
||||||
}
|
}
|
||||||
|
|
||||||
// func isPINRequired() {}
|
// RefreshVehicles refreshes the list of vehicles associated with the client's account.
|
||||||
// func getVehicles() {}
|
// {"success":true,"dataName":"sessionData","data":{"sessionChanged":true,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751835464000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"0E154A99FA3D014D866840123E5E666A","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":{"firstName":"Tatiana","lastName":"Savin","title":"","suffix":"","email":"tanya@savin.nyc","address":"29a Marion Ave","address2":"","city":"New Providence","state":"NJ","zip":"07974-1906","cellularPhone":"","workPhone":"","homePhone":"4013050505","countryCode":"USA","relationshipType":null,"gender":"","dealerCode":null,"oemCustId":"CRM-41PLM-5TYE","createMysAccount":null,"sourceSystemCode":"mys","vehicles":[{"vin":"4S4BSETC4H3265676","siebelVehicleRelationship":"Previous Owner","primary":false,"oemCustId":"1-8K7OBOJ","status":""},{"vin":"4S4BSETC4H3265676","siebelVehicleRelationship":"Previous TM Subscriber","primary":false,"oemCustId":"1-8JY3UVS","status":"Inactive"},{"vin":"4S4BTGND8L3137058","siebelVehicleRelationship":"Previous TM Subscriber","primary":false,"oemCustId":"CRM-44UFUA14-V","status":"Draft"},{"vin":"4S4BTGPD0P3199198","siebelVehicleRelationship":"TM Subscriber","primary":true,"oemCustId":"CRM-41PLM-5TYE","status":"Active"}],"phone":"","zip5Digits":"07974","primaryPersonalCountry":"USA"},"email":"","firstName":"Tatiana","lastName":"Savin","zip":"07974-1906","oemCustId":"CRM-41PLM-5TYE","phone":""},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"4S4BTGPD0P3199198","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":"tanya@savin.nyc","firstName":"Tatiana","lastName":"Savin","subscriptionFeatures":["REMOTE","SAFETY","Retail3"],"accessLevel":-1,"zip":"07974-1906","oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":"","timeZone":"America/New_York","features":["ABS_MIL","ACCS","AHBL_MIL","ATF_MIL","AWD_MIL","BSD","BSDRCT_MIL","CEL_MIL","CP1_5HHU","EBD_MIL","EOL_MIL","EPAS_MIL","EPB_MIL","ESS_MIL","EYESIGHT","ISS_MIL","MOONSTAT","OPL_MIL","PANPM-TUIRWAOC","PWAAADWWAP","RAB_MIL","RCC","REARBRK","RES","RESCC","RES_HVAC_HFS","RES_HVAC_VFS","RHSF","RPOI","RPOIA","RTGU","RVFS","SRH_MIL","SRS_MIL","SXM360L","T23DCM","TEL_MIL","TIF_35","TIR_33","TLD","TPMS_MIL","VALET","VDC_MIL","WASH_MIL","WDWSTAT","g3"],"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":"ACTIVE","authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false,"vehicleBranded":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","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","currentVehicleIndex":0,"handoffToken":"$2a$08$EvONydxMhrhVgFsv4OgX3O8QaOu3naCFYex2Crqhl27cPQwJYXera$1751837730624","satelliteViewEnabled":true,"registeredDevicePermanent":true}}
|
||||||
// func getEVStatus() {}
|
func (c *Client) RefreshVehicles() error {
|
||||||
// func getRemoteOptionsStatus() {}
|
params := map[string]string{}
|
||||||
// func getRemoteStartStatus() {}
|
reqURL := MOBILE_API_VERSION + apiURLs["API_REFRESH_VEHICLES"]
|
||||||
// func getSafetyStatus() {}
|
resp, err := c.execute(GET, reqURL, params, false)
|
||||||
// func getSubscriptionStatus() {}
|
if err != nil {
|
||||||
|
c.logger.Error("error while executing RefreshVehicles request", "request", "RefreshVehicles", "error", err.Error())
|
||||||
|
return errors.New("error while executing RefreshVehicles request: " + err.Error())
|
||||||
|
}
|
||||||
|
c.logger.Debug("http request output", "request", "RefreshVehicles", "body", resp)
|
||||||
|
|
||||||
|
// var vd VehicleData
|
||||||
|
// err = json.Unmarshal(resp.Data, &vd)
|
||||||
|
// if err != nil {
|
||||||
|
// c.logger.Error("error while parsing json", "request", "SelectVehicle", "error", err.Error())
|
||||||
|
// return errors.New("error while parsing json while vehicle selection")
|
||||||
|
// }
|
||||||
|
// c.logger.Debug("http request output", "request", "SelectVehicle", "body", resp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestAuthCode requests an authentication code for two-factor authentication (2FA).
|
||||||
|
// (?!^).(?=.*@)
|
||||||
|
// (?!^): This is a negative lookbehind assertion. It ensures that the matched character is not at the beginning of the string.
|
||||||
|
// .: This matches any single character (except newline, by default).
|
||||||
|
// (?=.*@): This is a positive lookahead assertion. It ensures that the matched character is followed by any characters (.*) and then an "@" symbol. This targets the username part of the email address.
|
||||||
|
func (c *Client) RequestAuthCode(email string) error {
|
||||||
|
email, err := emailMasking(email)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("error while hiding email", "request", "RequestAuthCode", "error", err.Error())
|
||||||
|
return errors.New("error while hiding email: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !containsValueInStruct(c.contactMethods, email) {
|
||||||
|
c.logger.Error("email is not in the list of contact methods", "request", "RequestAuthCode", "email", email)
|
||||||
|
return errors.New("email is not in the list of contact methods: " + email)
|
||||||
|
}
|
||||||
|
|
||||||
|
params := map[string]string{
|
||||||
|
"contactMethod": email,
|
||||||
|
"languagePreference": "EN"}
|
||||||
|
reqUrl := MOBILE_API_VERSION + apiURLs["API_2FA_SEND_VERIFICATION"]
|
||||||
|
resp, err := c.execute(POST, reqUrl, params, false)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("error while executing RequestAuthCode request", "request", "RequestAuthCode", "error", err.Error())
|
||||||
|
return errors.New("error while executing RequestAuthCode request: " + err.Error())
|
||||||
|
}
|
||||||
|
c.logger.Debug("http request output", "request", "RequestAuthCode", "body", resp)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubmitAuthCode submits the authentication code received from the RequestAuthCode method.
|
||||||
|
func (c *Client) SubmitAuthCode(code string, permanent bool) error {
|
||||||
|
regex := regexp.MustCompile(`^\d{6}$`)
|
||||||
|
if !regex.MatchString(code) {
|
||||||
|
c.logger.Error("invalid verification code format", "request", "SubmitAuthCode", "code", code)
|
||||||
|
return errors.New("invalid verification code format, must be 6 digits")
|
||||||
|
}
|
||||||
|
|
||||||
|
params := map[string]string{
|
||||||
|
"deviceId": c.credentials.DeviceID,
|
||||||
|
"deviceName": c.credentials.DeviceName,
|
||||||
|
"verificationCode": code}
|
||||||
|
if permanent {
|
||||||
|
params["rememberDevice"] = "on"
|
||||||
|
}
|
||||||
|
|
||||||
|
reqUrl := MOBILE_API_VERSION + apiURLs["API_2FA_AUTH_VERIFY"]
|
||||||
|
resp, err := c.execute(POST, reqUrl, params, false)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("error while executing SubmitAuthCode request", "request", "SubmitAuthCode", "error", err.Error())
|
||||||
|
return errors.New("error while executing SubmitAuthCode request: " + err.Error())
|
||||||
|
}
|
||||||
|
c.logger.Debug("http request output", "request", "SubmitAuthCode", "body", resp)
|
||||||
|
|
||||||
|
// Device registration does not always immediately take effect
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
|
||||||
|
// Reauthenticate after submitting the code
|
||||||
|
if ok, err := c.auth(); !ok {
|
||||||
|
c.logger.Error("error while executing auth request", "request", "auth", "error", err.Error())
|
||||||
|
return errors.New("error while executing auth request: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getContactMethods retrieves the available contact methods for two-factor authentication (2FA).
|
||||||
|
// {"success":true,"dataName":"dataMap","data":{"userName":"a**x@savin.nyc","email":"t***a@savin.nyc"}}
|
||||||
|
func (c *Client) getContactMethods() error {
|
||||||
|
// // Validate session before executing the request
|
||||||
|
// if !c.validateSession() {
|
||||||
|
// c.logger.Error(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
// return errors.New(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
// }
|
||||||
|
|
||||||
|
params := map[string]string{}
|
||||||
|
reqUrl := MOBILE_API_VERSION + apiURLs["API_2FA_CONTACT"]
|
||||||
|
resp, err := c.execute(POST, reqUrl, params, false)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("error while executing getContactMethods request", "request", "getContactMethods", "error", err.Error())
|
||||||
|
return errors.New("error while executing getContactMethods request: " + err.Error())
|
||||||
|
}
|
||||||
|
c.logger.Debug("http request output", "request", "getContactMethods", "body", resp)
|
||||||
|
|
||||||
|
var dm dataMap
|
||||||
|
err = json.Unmarshal(resp.Data, &dm)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("error while parsing json", "request", "getContactMethods", "error", err.Error())
|
||||||
|
return errors.New("error while parsing json while getting contact methods: " + err.Error())
|
||||||
|
}
|
||||||
|
c.contactMethods = dm
|
||||||
|
c.logger.Debug("contact methods successfully retrieved", "request", "getContactMethods", "methods", dm)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// IsAlive checks if the Client instance is alive
|
// IsAlive checks if the Client instance is alive
|
||||||
func (c *Client) IsAlive() bool {
|
func (c *Client) IsAlive() bool {
|
||||||
@ -261,6 +403,8 @@ func (c *Client) execute(method string, url string, params map[string]string, j
|
|||||||
}
|
}
|
||||||
c.logger.Debug("parsed http request output", "data", string(resBytes))
|
c.logger.Debug("parsed http request output", "data", string(resBytes))
|
||||||
|
|
||||||
|
c.httpClient.SetCookies(resp.Cookies())
|
||||||
|
|
||||||
if r, ok := c.parseResponse(resBytes); ok {
|
if r, ok := c.parseResponse(resBytes); ok {
|
||||||
c.isAlive = true
|
c.isAlive = true
|
||||||
c.Unlock()
|
c.Unlock()
|
||||||
@ -287,28 +431,6 @@ func (c *Client) execute(method string, url string, params map[string]string, j
|
|||||||
return nil, errors.New("request is not successfull, HTTP code: " + resp.Status())
|
return nil, errors.New("request is not successfull, HTTP code: " + resp.Status())
|
||||||
}
|
}
|
||||||
|
|
||||||
// auth authenticates the client with the MySubaru API using the provided credentials.
|
|
||||||
func (c *Client) auth() (*Response, error) {
|
|
||||||
params := map[string]string{
|
|
||||||
"env": "cloudprod",
|
|
||||||
"deviceType": "android",
|
|
||||||
"loginUsername": c.credentials.Username,
|
|
||||||
"password": c.credentials.Password,
|
|
||||||
"deviceId": c.credentials.DeviceID,
|
|
||||||
"passwordToken": "",
|
|
||||||
"selectedVin": "",
|
|
||||||
"pushToken": ""}
|
|
||||||
reqURL := MOBILE_API_VERSION + apiURLs["API_LOGIN"]
|
|
||||||
resp, err := c.execute(POST, reqURL, params, false)
|
|
||||||
c.logger.Debug("AUTH HTTP OUTPUT", "body", resp)
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Error("error while executing auth request", "request", "auth", "error", err.Error())
|
|
||||||
return nil, errors.New("error while executing auth request: " + err.Error())
|
|
||||||
}
|
|
||||||
c.logger.Debug("AUTH HTTP OUTPUT", "body", resp)
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseResponse parses the JSON response from the MySubaru API into a Response struct.
|
// parseResponse parses the JSON response from the MySubaru API into a Response struct.
|
||||||
func (c *Client) parseResponse(b []byte) (Response, bool) {
|
func (c *Client) parseResponse(b []byte) (Response, bool) {
|
||||||
var r Response
|
var r Response
|
||||||
@ -321,8 +443,8 @@ func (c *Client) parseResponse(b []byte) (Response, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateSession checks if the current session is valid by making a request to the vehicle status API.
|
// ValidateSession checks if the current session is valid by making a request to the vehicle status API.
|
||||||
func (c *Client) ValidateSession() bool {
|
func (c *Client) validateSession() bool {
|
||||||
reqURL := MOBILE_API_VERSION + apiURLs["API_VEHICLE_STATUS"]
|
reqURL := MOBILE_API_VERSION + apiURLs["API_VALIDATE_SESSION"]
|
||||||
resp, err := c.execute(GET, reqURL, map[string]string{}, false)
|
resp, err := c.execute(GET, reqURL, map[string]string{}, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error("error while executing validateSession request", "request", "validateSession", "error", err.Error())
|
c.logger.Error("error while executing validateSession request", "request", "validateSession", "error", err.Error())
|
||||||
@ -330,9 +452,42 @@ func (c *Client) ValidateSession() bool {
|
|||||||
}
|
}
|
||||||
c.logger.Debug("http request output", "request", "validateSession", "body", resp)
|
c.logger.Debug("http request output", "request", "validateSession", "body", resp)
|
||||||
|
|
||||||
|
if resp.Success {
|
||||||
|
_, err := c.SelectVehicle(c.currentVin)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("error while selecting vehicle", "request", "validateSession", "error", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !resp.Success {
|
||||||
|
_, err := c.auth()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("error while re-authenticating", "request", "validateSession", "error", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err = c.SelectVehicle(c.currentVin)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("error while selecting vehicle", "request", "validateSession", "error", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isPINRequired .
|
||||||
|
// Return if a vehicle with an active remote service subscription exists.
|
||||||
|
// func (v *Vehicle) isPINRequired() bool {
|
||||||
|
// return v.getRemoteOptionsStatus()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func isPINRequired() {}
|
||||||
|
// func getEVStatus() {}
|
||||||
|
// func getRemoteOptionsStatus() {}
|
||||||
|
// func getRemoteStartStatus() {}
|
||||||
|
// func getSafetyStatus() {}
|
||||||
|
// func getSubscriptionStatus() {}
|
||||||
|
|
||||||
// validateSession .
|
// validateSession .
|
||||||
// TODO: add session validation process and add it to the proper functions
|
// TODO: add session validation process and add it to the proper functions
|
||||||
// func (c *Client) validateSession() bool {
|
// func (c *Client) validateSession() bool {
|
||||||
|
File diff suppressed because one or more lines are too long
78
consts.go
78
consts.go
@ -1,6 +1,6 @@
|
|||||||
package mysubaru
|
package mysubaru
|
||||||
|
|
||||||
var MOBILE_API_VERSION = "/g2v30"
|
var MOBILE_API_VERSION = "/g2v31"
|
||||||
|
|
||||||
var MOBILE_API_SERVER = map[string]string{
|
var MOBILE_API_SERVER = map[string]string{
|
||||||
"USA": "https://mobileapi.prod.subarucs.com",
|
"USA": "https://mobileapi.prod.subarucs.com",
|
||||||
@ -70,8 +70,6 @@ var apiURLs = map[string]string{
|
|||||||
"API_EV_FETCH_CHARGE_SETTINGS": "/service/g2/phevGetTimerSettings/execute.json",
|
"API_EV_FETCH_CHARGE_SETTINGS": "/service/g2/phevGetTimerSettings/execute.json",
|
||||||
"API_EV_SAVE_CHARGE_SETTINGS": "/service/g2/phevSendTimerSetting/execute.json",
|
"API_EV_SAVE_CHARGE_SETTINGS": "/service/g2/phevSendTimerSetting/execute.json",
|
||||||
"API_EV_DELETE_CHARGE_SCHEDULE": "/service/g2/phevDeleteTimerSetting/execute.json",
|
"API_EV_DELETE_CHARGE_SCHEDULE": "/service/g2/phevDeleteTimerSetting/execute.json",
|
||||||
// "API_G2_FETCH_CLIMATE_SETTINGS": "/service/g2/remoteEngineStart/fetch.json",
|
|
||||||
// "API_G2_SAVE_CLIMATE_SETTINGS": "/service/g2/remoteEngineStart/save.json",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Get back and add wrapper to support Feature List
|
// TODO: Get back and add wrapper to support Feature List
|
||||||
@ -95,23 +93,23 @@ var apiURLs = map[string]string{
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
var API_ERRORS = map[string]string{
|
var API_ERRORS = map[string]string{
|
||||||
"403-soa-unableToParseResponseBody": "ERROR_SOA_403", // G2 Error Codes
|
"API_ERROR_SOA_403": "403-soa-unableToParseResponseBody", // G2
|
||||||
"InvalidCredentials": "ERROR_INVALID_CREDENTIALS",
|
"API_ERROR_NO_ACCOUNT": "accountNotFound", // G2
|
||||||
"ServiceAlreadyStarted": "ERROR_SERVICE_ALREADY_STARTED",
|
"API_ERROR_INVALID_ACCOUNT": "invalidAccount", // G2
|
||||||
"invalidAccount": "ERROR_INVALID_ACCOUNT",
|
"API_ERROR_INVALID_CREDENTIALS": "InvalidCredentials", // G2
|
||||||
"passwordWarning": "ERROR_PASSWORD_WARNING",
|
"API_ERROR_INVALID_TOKEN": "InvalidToken", // G2
|
||||||
"accountLocked": "ERROR_ACCOUNT_LOCKED",
|
"API_ERROR_PASSWORD_WARNING": "passwordWarning", // G2
|
||||||
"noVehiclesOnAccount": "ERROR_NO_VEHICLES",
|
"API_ERROR_TOO_MANY_ATTEMPTS": "tooManyAttempts", // G2
|
||||||
"noVehiclesAvailable": "ERROR_NO_VEHICLE_AVAILABLE",
|
"API_ERROR_ACCOUNT_LOCKED": "accountLocked", // G2
|
||||||
"VEHICLESETUPERROR": "ERROR_VEHICLE_SETUP_ERROR", // Vehicle Select
|
"API_ERROR_NO_VEHICLES": "noVehiclesOnAccount", // G2
|
||||||
"accountNotFound": "ERROR_NO_ACCOUNT",
|
"API_ERROR_VEHICLE_SETUP": "VEHICLESETUPERROR", // G2
|
||||||
"tooManyAttempts": "ERROR_TOO_MANY_ATTEMPTS",
|
"API_ERROR_VEHICLE_NOT_IN_ACCOUNT": "vehicleNotInAccount", // G2
|
||||||
"vehicleNotInAccount": "ERROR_VEHICLE_NOT_IN_ACCOUNT",
|
"API_ERROR_SERVICE_ALREADY_STARTED": "ServiceAlreadyStarted", // G2
|
||||||
"SXM40004": "ERROR_G1_NO_SUBSCRIPTION", // G1 Error Codes
|
"API_ERROR_G1_NO_SUBSCRIPTION": "SXM40004", // G1
|
||||||
"SXM40005": "ERROR_G1_STOLEN_VEHICLE",
|
"API_ERROR_G1_STOLEN_VEHICLE": "SXM40005", // G1
|
||||||
"SXM40006": "ERROR_G1_INVALID_PIN",
|
"API_ERROR_G1_INVALID_PIN": "SXM40006", // G1
|
||||||
"SXM40009": "ERROR_G1_SERVICE_ALREADY_STARTED",
|
"API_ERROR_G1_SERVICE_ALREADY_STARTED": "SXM40009", // G1
|
||||||
"SXM40017": "ERROR_G1_PIN_LOCKED",
|
"API_ERROR_G1_PIN_LOCKED": "SXM40017", // G1
|
||||||
}
|
}
|
||||||
|
|
||||||
var APP_ERRORS = map[string]string{
|
var APP_ERRORS = map[string]string{
|
||||||
@ -152,7 +150,7 @@ var features = map[string]string{
|
|||||||
"PANPM-TUIRWAOC": "Power Moonroof",
|
"PANPM-TUIRWAOC": "Power Moonroof",
|
||||||
"PANPM-DG2G": "Panoramic Moonroof",
|
"PANPM-DG2G": "Panoramic Moonroof",
|
||||||
"PHEV": "Electric Vehicle",
|
"PHEV": "Electric Vehicle",
|
||||||
"RES": "Remote Start",
|
"RES": "Remote Engine Start",
|
||||||
"REARBRK": "Reverse Auto Braking",
|
"REARBRK": "Reverse Auto Braking",
|
||||||
"TIF_35": "Tire Pressure Front 35",
|
"TIF_35": "Tire Pressure Front 35",
|
||||||
"TIR_33": "Tire Pressure Rear 35",
|
"TIR_33": "Tire Pressure Rear 35",
|
||||||
@ -163,6 +161,13 @@ var features = map[string]string{
|
|||||||
"RCC": "Remote Climate Control",
|
"RCC": "Remote Climate Control",
|
||||||
"ACCS": "Adaptive Cruise Control",
|
"ACCS": "Adaptive Cruise Control",
|
||||||
"SXM360L": "SiriusXM with 360L",
|
"SXM360L": "SiriusXM with 360L",
|
||||||
|
"WDWSTAT": "Window Status",
|
||||||
|
"MOONSTAT": "Moonroof Status",
|
||||||
|
"RTGU": "Remote Trunk / Rear Gate Unlock",
|
||||||
|
"RVFS": "Remote Vehicle Find System",
|
||||||
|
"TLD": "Tire Pressure Low Detection",
|
||||||
|
"DOOR_LU_STAT": "Door Lock/Unlock Status",
|
||||||
|
"RPOI": "Remote Geo Point of Interest",
|
||||||
}
|
}
|
||||||
|
|
||||||
var troubles = map[string]string{
|
var troubles = map[string]string{
|
||||||
@ -267,7 +272,7 @@ const (
|
|||||||
REAR_AC_ON = "true"
|
REAR_AC_ON = "true"
|
||||||
REAR_AC_OFF = "false"
|
REAR_AC_OFF = "false"
|
||||||
START_CONFIG = "startConfiguration"
|
START_CONFIG = "startConfiguration"
|
||||||
START_CONFIG_DEFAULT_EV = "start_Climate_Control_only_allow_key_in_ignition"
|
START_CONFIG_DEFAULT_EV = "START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION"
|
||||||
START_CONFIG_DEFAULT_RES = "START_ENGINE_ALLOW_KEY_IN_IGNITION"
|
START_CONFIG_DEFAULT_RES = "START_ENGINE_ALLOW_KEY_IN_IGNITION"
|
||||||
WHICH_DOOR = "unlockDoorType" // Unlock doors constants
|
WHICH_DOOR = "unlockDoorType" // Unlock doors constants
|
||||||
ALL_DOORS = "ALL_DOORS_CMD"
|
ALL_DOORS = "ALL_DOORS_CMD"
|
||||||
@ -411,3 +416,32 @@ const (
|
|||||||
// ]
|
// ]
|
||||||
// BAD_BINARY_SENSOR_VALUES = [UNKNOWN, VENTED, NOT_EQUIPPED]
|
// BAD_BINARY_SENSOR_VALUES = [UNKNOWN, VENTED, NOT_EQUIPPED]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RAW_API_FIELDS_TO_REDACT = [
|
||||||
|
// "cachedStateCode",
|
||||||
|
// "customer",
|
||||||
|
// "email",
|
||||||
|
// "firstName",
|
||||||
|
// "lastName",
|
||||||
|
// "latitude",
|
||||||
|
// "licensePlate",
|
||||||
|
// "licensePlateState",
|
||||||
|
// "longitude",
|
||||||
|
// "nickname",
|
||||||
|
// "odometer",
|
||||||
|
// "odometerValue",
|
||||||
|
// "odometerValueKilometers",
|
||||||
|
// "oemCustId",
|
||||||
|
// "phone",
|
||||||
|
// "preferredDealer",
|
||||||
|
// "sessionCustomer",
|
||||||
|
// "timeZone",
|
||||||
|
// "userOemCustId",
|
||||||
|
// "vehicleGeoPosition",
|
||||||
|
// "vehicleKey",
|
||||||
|
// "vehicleMileage",
|
||||||
|
// "vehicleName",
|
||||||
|
// "vhsId",
|
||||||
|
// "vin",
|
||||||
|
// "zip",
|
||||||
|
// ]
|
||||||
|
@ -30,8 +30,6 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// subaru := ms.SelectVehicle("4S4BTGPD0P3199198")
|
|
||||||
|
|
||||||
// 11.6MMAN ABS_MIL ACCS AHBL_MIL ATF_MIL AWD_MIL BSD BSDRCT_MIL CEL_MIL EBD_MIL EOL_MIL EPAS_MIL EPB_MIL ESS_MIL EYESIGHT ISS_MIL NAV_TOMTOM OPL_MIL RAB_MIL RCC REARBRK RES RESCC RHSF RPOI RPOIA SRS_MIL TEL_MIL TIF_35 TIR_33 TPMS_MIL VDC_MIL WASH_MIL g2
|
// 11.6MMAN ABS_MIL ACCS AHBL_MIL ATF_MIL AWD_MIL BSD BSDRCT_MIL CEL_MIL EBD_MIL EOL_MIL EPAS_MIL EPB_MIL ESS_MIL EYESIGHT ISS_MIL NAV_TOMTOM OPL_MIL RAB_MIL RCC REARBRK RES RESCC RHSF RPOI RPOIA SRS_MIL TEL_MIL TIF_35 TIR_33 TPMS_MIL VDC_MIL WASH_MIL g2
|
||||||
// subaru1 := mysubaru.GetVehicleByVIN("4S4BTGND8L3137058")
|
// subaru1 := mysubaru.GetVehicleByVIN("4S4BTGND8L3137058")
|
||||||
// fmt.Printf("SUBARU #2 (Vehicle Status):\n")
|
// fmt.Printf("SUBARU #2 (Vehicle Status):\n")
|
||||||
@ -47,54 +45,65 @@ func main() {
|
|||||||
cfg.Logger.Error("cannot get a vehicle by VIN", "error", err)
|
cfg.Logger.Error("cannot get a vehicle by VIN", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("SUBARU #1 (Vehicle Status): %s\n", outback)
|
fmt.Printf("SUBARU #1 (Vehicle Status): %s\n", outback)
|
||||||
|
|
||||||
// // Execute a LightsStart command
|
// // Execute a Lock command
|
||||||
// events, err := outback.LightsStart()
|
// events, err := outback.EngineStart(5, 0, true)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// cfg.Logger.Error("cannot execute LightsStart command", "error", err)
|
// cfg.Logger.Error("cannot execute Lock command", "error", err)
|
||||||
// os.Exit(1)
|
// os.Exit(1)
|
||||||
// }
|
// }
|
||||||
// for event := range events {
|
// for event := range events {
|
||||||
// fmt.Printf("Lights Start Event: %+v\n", event)
|
// fmt.Printf("Lock Event: %+v\n", event)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // Wait for a while to see the lights on
|
// Execute a LightsStart command
|
||||||
// time.Sleep(20 * time.Second)
|
events, err := outback.LightsStart()
|
||||||
|
|
||||||
// // Execute a LightsStop command
|
|
||||||
// events, err = outback.LightsStop()
|
|
||||||
// if err != nil {
|
|
||||||
// cfg.Logger.Error("cannot execute LightsStop command", "error", err)
|
|
||||||
// os.Exit(1)
|
|
||||||
// }
|
|
||||||
// for event := range events {
|
|
||||||
// fmt.Printf("Lights Stop Event: %+v\n", event)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Execute a Unlock command
|
|
||||||
events, err := outback.Unlock()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cfg.Logger.Error("cannot execute Unlock command", "error", err)
|
cfg.Logger.Error("cannot execute LightsStart command", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
for event := range events {
|
for event := range events {
|
||||||
fmt.Printf("Unlock Event: %+v\n", event)
|
fmt.Printf("Lights Start Event: %+v\n", event)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for a while to see the lights on
|
// Wait for a while to see the lights on
|
||||||
time.Sleep(20 * time.Second)
|
time.Sleep(20 * time.Second)
|
||||||
|
|
||||||
// Execute a Lock command
|
// Execute a LightsStop command
|
||||||
events, err = outback.Lock()
|
events, err = outback.LightsStop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cfg.Logger.Error("cannot execute Lock command", "error", err)
|
cfg.Logger.Error("cannot execute LightsStop command", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
for event := range events {
|
for event := range events {
|
||||||
fmt.Printf("Lock Event: %+v\n", event)
|
fmt.Printf("Lights Stop Event: %+v\n", event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// // Execute a Unlock command
|
||||||
|
// events, err := outback.Unlock()
|
||||||
|
// if err != nil {
|
||||||
|
// cfg.Logger.Error("cannot execute Unlock command", "error", err)
|
||||||
|
// os.Exit(1)
|
||||||
|
// }
|
||||||
|
// for event := range events {
|
||||||
|
// fmt.Printf("Unlock Event: %+v\n", event)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Wait for a while to see the lights on
|
||||||
|
// time.Sleep(20 * time.Second)
|
||||||
|
|
||||||
|
// // Execute a Lock command
|
||||||
|
// events, err = outback.Lock()
|
||||||
|
// if err != nil {
|
||||||
|
// cfg.Logger.Error("cannot execute Lock command", "error", err)
|
||||||
|
// os.Exit(1)
|
||||||
|
// }
|
||||||
|
// for event := range events {
|
||||||
|
// fmt.Printf("Lock Event: %+v\n", event)
|
||||||
|
// }
|
||||||
|
|
||||||
// Execute a forced GetLocation command
|
// Execute a forced GetLocation command
|
||||||
// events, err = outback.GetLocation(true)
|
// events, err = outback.GetLocation(true)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
@ -105,3 +114,10 @@ func main() {
|
|||||||
// fmt.Printf("GeoLocation Event: %+v\n", event)
|
// fmt.Printf("GeoLocation Event: %+v\n", event)
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// {"time":"2025-07-21T18:05:30.987314-04:00","level":"DEBUG","source":{"function":"git.savin.nyc/alex/mysubaru.(*Client).execute","file":"/Users/alex/go/pkg/mod/git.savin.nyc/alex/mysubaru@v0.0.0-20250721200300-3809ed5883b4/client.go","line":372},"msg":"executed GET request","method":"GET","url":"/g2v31/service/g2/remoteEngineQuickStartSettings/fetch.json","params":{}}
|
||||||
|
// {"time":"2025-07-21T18:05:31.014014-04:00","level":"DEBUG","source":{"function":"git.savin.nyc/alex/mysubaru.(*Client).execute","file":"/Users/alex/go/pkg/mod/git.savin.nyc/alex/mysubaru@v0.0.0-20250721200300-3809ed5883b4/client.go","line":404},"msg":"parsed http request output","data":"{\"success\":true,\"errorCode\":null,\"dataName\":null,\"data\":\"{\\\"name\\\":\\\"Cooling\\\",\\\"runTimeMinutes\\\":\\\"10\\\",\\\"climateZoneFrontTemp\\\":\\\"65\\\",\\\"climateZoneFrontAirMode\\\":\\\"FEET_FACE_BALANCED\\\",\\\"climateZoneFrontAirVolume\\\":\\\"7\\\",\\\"outerAirCirculation\\\":\\\"outsideAir\\\",\\\"heatedRearWindowActive\\\":\\\"false\\\",\\\"heatedSeatFrontLeft\\\":\\\"HIGH_COOL\\\",\\\"heatedSeatFrontRight\\\":\\\"HIGH_COOL\\\",\\\"airConditionOn\\\":\\\"false\\\",\\\"canEdit\\\":\\\"true\\\",\\\"disabled\\\":\\\"false\\\",\\\"presetType\\\":\\\"userPreset\\\",\\\"startConfiguration\\\":\\\"START_ENGINE_ALLOW_KEY_IN_IGNITION\\\"}\"}"}
|
||||||
|
|
||||||
|
// {"time":"2025-07-21T18:03:44.461646-04:00","level":"DEBUG","source":{"function":"git.savin.nyc/alex/mysubaru.(*Client).execute","file":"/Users/alex/go/pkg/mod/git.savin.nyc/alex/mysubaru@v0.0.0-20250721200300-3809ed5883b4/client.go","line":372},"msg":"executed GET request","method":"GET","url":"/g2v31/service/g2/remoteEngineQuickStartSettings/fetch.json","params":{}}
|
||||||
|
// {"time":"2025-07-21T18:03:44.475745-04:00","level":"DEBUG","source":{"function":"git.savin.nyc/alex/mysubaru.(*Client).execute","file":"/Users/alex/go/pkg/mod/git.savin.nyc/alex/mysubaru@v0.0.0-20250721200300-3809ed5883b4/client.go","line":404},"msg":"parsed http request output","data":"{\"success\":true,\"errorCode\":null,\"dataName\":null,\"data\":\"{\\\"airConditionOn\\\":\\\"false\\\",\\\"climateSettings\\\":\\\"climateSettings\\\",\\\"climateZoneFrontAirMode\\\":\\\"FEET_WINDOW\\\",\\\"climateZoneFrontAirVolume\\\":\\\"7\\\",\\\"climateZoneFrontTemp\\\":\\\"65\\\",\\\"heatedRearWindowActive\\\":\\\"false\\\",\\\"heatedSeatFrontLeft\\\":\\\"HIGH_COOL\\\",\\\"heatedSeatFrontRight\\\":\\\"HIGH_COOL\\\",\\\"name\\\":\\\"Cooling\\\",\\\"outerAirCirculation\\\":\\\"outsideAir\\\",\\\"runTimeMinutes\\\":\\\"10\\\",\\\"startConfiguration\\\":\\\"START_ENGINE_ALLOW_KEY_IN_IGNITION\\\"}\\n\"}"}
|
||||||
|
// {"time":"2025-07-21T18:03:44.475885-04:00","level":"ERROR","source":{"function":"git.savin.nyc/alex/mysubaru.(*Vehicle).GetClimateQuickPresets","file":"/Users/alex/go/pkg/mod/git.savin.nyc/alex/mysubaru@v0.0.0-20250721200300-3809ed5883b4/vehicle.go","line":498},"msg":"error while parsing climate quick presets json","request":"GetClimateQuickPresets","error":"invalid character '\"' after top-level value"}
|
||||||
|
@ -2,12 +2,12 @@ module example
|
|||||||
|
|
||||||
go 1.24
|
go 1.24
|
||||||
|
|
||||||
require git.savin.nyc/alex/mysubaru v0.0.0-20250706191512-3c927fd83b1f
|
require git.savin.nyc/alex/mysubaru v0.0.0-20250722205404-92d4266f8b5a
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||||
golang.org/x/net v0.41.0 // indirect
|
golang.org/x/net v0.42.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
resty.dev/v3 v3.0.0-beta.3 // indirect
|
resty.dev/v3 v3.0.0-beta.3 // indirect
|
||||||
)
|
)
|
||||||
|
2
go.mod
2
go.mod
@ -12,6 +12,6 @@ require (
|
|||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
golang.org/x/net v0.41.0 // indirect
|
golang.org/x/net v0.42.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||||
)
|
)
|
||||||
|
502
mysubaru.go
502
mysubaru.go
@ -2,6 +2,11 @@ package mysubaru
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Response represents the structure of a response from the MySubaru API.
|
// Response represents the structure of a response from the MySubaru API.
|
||||||
@ -12,18 +17,59 @@ type Response struct {
|
|||||||
Data json.RawMessage `json:"data"` // Data struct
|
Data json.RawMessage `json:"data"` // Data struct
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse .
|
// parse parses the JSON response from the MySubaru API into a Response struct.
|
||||||
// func (r *Response) parse(b []byte, logger *slog.Logger) bool {
|
func (r *Response) parse(b []byte, logger *slog.Logger) (*Response, error) {
|
||||||
// err := json.Unmarshal(b, &r)
|
err := json.Unmarshal(b, &r)
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// logger.Error("error while parsing json", "error", err.Error())
|
logger.Error("error while parsing json", "error", err.Error())
|
||||||
// return false
|
return nil, errors.New("error while parsing json: " + err.Error())
|
||||||
// }
|
}
|
||||||
// return true
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Unmarshal .
|
if !r.Success && r.ErrorCode != "" {
|
||||||
// func (r *Response) Unmarshal(b []byte) {}
|
logger.Error("error in response", "errorCode", r.ErrorCode, "dataName", r.DataName)
|
||||||
|
switch r.ErrorCode {
|
||||||
|
// G2 API errors
|
||||||
|
case API_ERRORS["API_ERROR_NO_ACCOUNT"]:
|
||||||
|
return r, errors.New("error in response: Account not found")
|
||||||
|
case API_ERRORS["API_ERROR_INVALID_ACCOUNT"]:
|
||||||
|
return r, errors.New("error in response: Invalid Account")
|
||||||
|
case API_ERRORS["API_ERROR_INVALID_CREDENTIALS"]:
|
||||||
|
return r, errors.New("error in response: Invalid Credentials")
|
||||||
|
case API_ERRORS["API_ERROR_INVALID_TOKEN"]:
|
||||||
|
return r, errors.New("error in response: Invalid Token")
|
||||||
|
case API_ERRORS["API_ERROR_PASSWORD_WARNING"]:
|
||||||
|
return r, errors.New("error in response: Mutiple failed login attempts, password warning")
|
||||||
|
case API_ERRORS["API_ERROR_TOO_MANY_ATTEMPTS"]:
|
||||||
|
return r, errors.New("error in response: Too many attempts, please try again later")
|
||||||
|
case API_ERRORS["API_ERROR_ACCOUNT_LOCKED"]:
|
||||||
|
return r, errors.New("error in response: Account Locked")
|
||||||
|
case API_ERRORS["API_ERROR_NO_VEHICLES"]:
|
||||||
|
return r, errors.New("error in response: No vehicles found for the account")
|
||||||
|
case API_ERRORS["API_ERROR_VEHICLE_SETUP"]:
|
||||||
|
return r, errors.New("error in response: Vehicle setup is not complete")
|
||||||
|
case API_ERRORS["API_ERROR_VEHICLE_NOT_IN_ACCOUNT"]:
|
||||||
|
return r, errors.New("error in response: Vehicle not in account")
|
||||||
|
case API_ERRORS["API_ERROR_SERVICE_ALREADY_STARTED"]:
|
||||||
|
return r, errors.New("error in response: Service already started")
|
||||||
|
case API_ERRORS["API_ERROR_SOA_403"]:
|
||||||
|
return r, errors.New("error in response: Unable to parse response body, SOA 403 error")
|
||||||
|
// G1 API errors
|
||||||
|
case API_ERRORS["API_ERROR_G1_NO_SUBSCRIPTION"]:
|
||||||
|
return r, errors.New("error in response: No subscription found for the vehicle")
|
||||||
|
case API_ERRORS["API_ERROR_G1_STOLEN_VEHICLE"]:
|
||||||
|
return r, errors.New("error in response: Car is reported as stolen")
|
||||||
|
case API_ERRORS["API_ERROR_G1_INVALID_PIN"]:
|
||||||
|
return r, errors.New("error in response: Invalid PIN")
|
||||||
|
case API_ERRORS["API_ERROR_G1_PIN_LOCKED"]:
|
||||||
|
return r, errors.New("error in response: PIN is locked")
|
||||||
|
case API_ERRORS["API_ERROR_G1_SERVICE_ALREADY_STARTED"]:
|
||||||
|
return r, errors.New("error in response: Service already started")
|
||||||
|
}
|
||||||
|
return r, errors.New("error in response: " + r.ErrorCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Request represents the structure of a request to the MySubaru API.
|
// Request represents the structure of a request to the MySubaru API.
|
||||||
type Request struct {
|
type Request struct {
|
||||||
@ -46,41 +92,83 @@ type Request struct {
|
|||||||
StartConfiguration *string `json:"startConfiguration,omitempty"` //
|
StartConfiguration *string `json:"startConfiguration,omitempty"` //
|
||||||
}
|
}
|
||||||
|
|
||||||
// Account .
|
// account .
|
||||||
type Account struct {
|
type account struct {
|
||||||
MarketID int `json:"marketId"`
|
MarketID int `json:"marketId"`
|
||||||
AccountKey int `json:"accountKey"`
|
AccountKey int `json:"accountKey"`
|
||||||
FirstName string `json:"firstName"`
|
FirstName string `json:"firstName"`
|
||||||
LastName string `json:"lastName"`
|
LastName string `json:"lastName"`
|
||||||
ZipCode string `json:"zipCode"`
|
ZipCode string `json:"zipCode"`
|
||||||
ZipCode5 string `json:"zipCode5"`
|
ZipCode5 string `json:"zipCode5"`
|
||||||
LastLoginDate int64 `json:"lastLoginDate"`
|
LastLoginDate UnixTime `json:"lastLoginDate"`
|
||||||
CreatedDate int64 `json:"createdDate"`
|
CreatedDate UnixTime `json:"createdDate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Customer .
|
// Customer .
|
||||||
type Customer struct {
|
type Customer struct {
|
||||||
SessionCustomer string `json:"sessionCustomer"`
|
SessionCustomer SessionCustomer `json:"sessionCustomer,omitempty"` // struct | Only by performing a RefreshVehicles request
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
FirstName string `json:"firstName"`
|
FirstName string `json:"firstName"`
|
||||||
LastName string `json:"lastName"`
|
LastName string `json:"lastName"`
|
||||||
Zip string `json:"zip"`
|
Zip string `json:"zip"`
|
||||||
OemCustID string `json:"oemCustId"`
|
OemCustID string `json:"oemCustId"`
|
||||||
Phone string `json:"phone"`
|
Phone string `json:"phone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionCustomer .
|
||||||
|
type SessionCustomer struct {
|
||||||
|
FirstName string `json:"firstName,omitempty"`
|
||||||
|
LastName string `json:"lastName,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Suffix string `json:"suffix,omitempty"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Address2 string `json:"address2,omitempty"`
|
||||||
|
City string `json:"city"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Zip string `json:"zip"`
|
||||||
|
CellularPhone string `json:"cellularPhone,omitempty"`
|
||||||
|
WorkPhone string `json:"workPhone,omitempty"`
|
||||||
|
HomePhone string `json:"homePhone,omitempty"`
|
||||||
|
CountryCode string `json:"countryCode"`
|
||||||
|
RelationshipType any `json:"relationshipType,omitempty"`
|
||||||
|
Gender string `json:"gender,omitempty"`
|
||||||
|
DealerCode any `json:"dealerCode,omitempty"`
|
||||||
|
OemCustID string `json:"oemCustId"`
|
||||||
|
CreateMysAccount any `json:"createMysAccount,omitempty"`
|
||||||
|
SourceSystemCode string `json:"sourceSystemCode"`
|
||||||
|
Vehicles []struct {
|
||||||
|
Vin string `json:"vin"`
|
||||||
|
SiebelVehicleRelationship string `json:"siebelVehicleRelationship"` // TM Subscriber | Previous TM Subscriber | Previous Owner
|
||||||
|
Primary bool `json:"primary"` // true | false
|
||||||
|
OemCustID string `json:"oemCustId"` // CRM-41PLM-5TYE | 1-8K7OBOJ | 1-8JY3UVS | CRM-44UFUA14-V
|
||||||
|
Status string `json:"status,omitempty"` // "Active" | "Draft" | "Inactive"
|
||||||
|
} `json:"vehicles"`
|
||||||
|
Phone string `json:"phone,omitempty"`
|
||||||
|
Zip5Digits string `json:"zip5Digits"`
|
||||||
|
PrimaryPersonalCountry string `json:"primaryPersonalCountry"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataMap .
|
||||||
|
// "dataName": "dataMap"
|
||||||
|
type dataMap struct {
|
||||||
|
Username string `json:"userName"`
|
||||||
|
Email string `json:"email"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionData .
|
// SessionData .
|
||||||
// "dataName": "sessionData"
|
// "dataName": "sessionData"
|
||||||
type SessionData struct {
|
type SessionData struct {
|
||||||
SessionChanged bool `json:"sessionChanged"`
|
Account account `json:"account"`
|
||||||
VehicleInactivated bool `json:"vehicleInactivated"`
|
|
||||||
Account Account `json:"account"`
|
|
||||||
ResetPassword bool `json:"resetPassword"`
|
|
||||||
DeviceID string `json:"deviceId"`
|
|
||||||
SessionID string `json:"sessionId"`
|
|
||||||
DeviceRegistered bool `json:"deviceRegistered"`
|
|
||||||
PasswordToken string `json:"passwordToken"`
|
PasswordToken string `json:"passwordToken"`
|
||||||
|
ResetPassword bool `json:"resetPassword"`
|
||||||
|
SessionID string `json:"sessionId"`
|
||||||
|
SessionChanged bool `json:"sessionChanged"`
|
||||||
|
DeviceID string `json:"deviceId"`
|
||||||
|
DeviceRegistered bool `json:"deviceRegistered"`
|
||||||
|
RegisteredDevicePermanent bool `json:"registeredDevicePermanent"`
|
||||||
Vehicles []VehicleData `json:"vehicles"`
|
Vehicles []VehicleData `json:"vehicles"`
|
||||||
|
VehicleInactivated bool `json:"vehicleInactivated"`
|
||||||
RightToRepairEnabled bool `json:"rightToRepairEnabled"`
|
RightToRepairEnabled bool `json:"rightToRepairEnabled"`
|
||||||
RightToRepairStates string `json:"rightToRepairStates"`
|
RightToRepairStates string `json:"rightToRepairStates"`
|
||||||
CurrentVehicleIndex int `json:"currentVehicleIndex"`
|
CurrentVehicleIndex int `json:"currentVehicleIndex"`
|
||||||
@ -93,63 +181,63 @@ type SessionData struct {
|
|||||||
DigitalGlobeTransparentTileService string `json:"digitalGlobeTransparentTileService"`
|
DigitalGlobeTransparentTileService string `json:"digitalGlobeTransparentTileService"`
|
||||||
TomtomKey string `json:"tomtomKey"`
|
TomtomKey string `json:"tomtomKey"`
|
||||||
SatelliteViewEnabled bool `json:"satelliteViewEnabled"`
|
SatelliteViewEnabled bool `json:"satelliteViewEnabled"`
|
||||||
RegisteredDevicePermanent bool `json:"registeredDevicePermanent"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vehicle .
|
// Vehicle .
|
||||||
// "dataName": "vehicle"
|
// "dataName": "vehicle"
|
||||||
type VehicleData struct {
|
type VehicleData struct {
|
||||||
Customer Customer `json:"customer"` // Customer struct
|
Customer Customer `json:"customer"` // Customer struct
|
||||||
UserOemCustID string `json:"userOemCustId"` // CRM-631-HQN48K
|
OemCustID string `json:"oemCustId"` // CRM-631-HQN48K
|
||||||
OemCustID string `json:"oemCustId"` // CRM-631-HQN48K
|
UserOemCustID string `json:"userOemCustId"` // CRM-631-HQN48K
|
||||||
Active bool `json:"active"` // true | false
|
Active bool `json:"active"` // true | false
|
||||||
Email string `json:"email"` // null | email@address.com
|
Email string `json:"email"` // null | email@address.com
|
||||||
FirstName string `json:"firstName"` // null | First Name
|
FirstName string `json:"firstName,omitempty"` // null | First Name
|
||||||
LastName string `json:"lastName"` // null | Last Name
|
LastName string `json:"lastName,omitempty"` // null | Last Name
|
||||||
Zip string `json:"zip"` // 12345
|
Zip string `json:"zip"` // 12345
|
||||||
Phone string `json:"phone"` // null | 123-456-7890
|
Phone string `json:"phone,omitempty"` // null | 123-456-7890
|
||||||
StolenVehicle bool `json:"stolenVehicle"` // true | false
|
StolenVehicle bool `json:"stolenVehicle"` // true | false
|
||||||
VehicleName string `json:"vehicleName"` // Subaru Outback LXT
|
VehicleName string `json:"vehicleName"` // Subaru Outback LXT
|
||||||
Features []string `json:"features"` // "11.6MMAN", "ABS_MIL", "ACCS", "AHBL_MIL", "ATF_MIL", "AWD_MIL", "BSD", "BSDRCT_MIL", "CEL_MIL", "EBD_MIL", "EOL_MIL", "EPAS_MIL", "EPB_MIL", "ESS_MIL", "EYESIGHT", "ISS_MIL", "NAV_TOMTOM", "OPL_MIL", "RAB_MIL", "RCC", "REARBRK", "RES", "RESCC", "RHSF", "RPOI", "RPOIA", "SRH_MIL", "SRS_MIL", "TEL_MIL", "TPMS_MIL", "VDC_MIL", "WASH_MIL", "g2"
|
Features []string `json:"features"` // "11.6MMAN", "ABS_MIL", "ACCS", "AHBL_MIL", "ATF_MIL", "AWD_MIL", "BSD", "BSDRCT_MIL", "CEL_MIL", "EBD_MIL", "EOL_MIL", "EPAS_MIL", "EPB_MIL", "ESS_MIL", "EYESIGHT", "ISS_MIL", "NAV_TOMTOM", "OPL_MIL", "RAB_MIL", "RCC", "REARBRK", "RES", "RESCC", "RHSF", "RPOI", "RPOIA", "SRH_MIL", "SRS_MIL", "TEL_MIL", "TPMS_MIL", "VDC_MIL", "WASH_MIL", "g2"
|
||||||
Vin string `json:"vin"` // 4Y1SL65848Z411439
|
Vin string `json:"vin"` // 4Y1SL65848Z411439
|
||||||
VehicleKey int64 `json:"vehicleKey"` // 3832950
|
VehicleKey int64 `json:"vehicleKey"` // 3832950
|
||||||
Nickname string `json:"nickname"` // Subaru Outback LXT
|
Nickname string `json:"nickname"` // Subaru Outback LXT
|
||||||
ModelName string `json:"modelName"` // Outback
|
ModelName string `json:"modelName"` // Outback
|
||||||
ModelYear string `json:"modelYear"` // 2020
|
ModelYear string `json:"modelYear"` // 2020
|
||||||
ModelCode string `json:"modelCode"` // LDJ
|
ModelCode string `json:"modelCode"` // LDJ
|
||||||
ExtDescrip string `json:"extDescrip"` // Abyss Blue Pearl (ext color)
|
ExtDescrip string `json:"extDescrip"` // Abyss Blue Pearl (ext color)
|
||||||
IntDescrip string `json:"intDescrip"` // Gray (int color)
|
IntDescrip string `json:"intDescrip"` // Gray (int color)
|
||||||
TransCode string `json:"transCode"` // CVT
|
TransCode string `json:"transCode"` // CVT
|
||||||
EngineSize float64 `json:"engineSize"` // 2.4
|
EngineSize float64 `json:"engineSize"` // 2.4
|
||||||
Phev bool `json:"phev"` // null
|
Phev bool `json:"phev"` // null
|
||||||
CachedStateCode string `json:"cachedStateCode"` // NJ
|
CachedStateCode string `json:"cachedStateCode"` // NJ
|
||||||
LicensePlate string `json:"licensePlate"` // NJ
|
LicensePlate string `json:"licensePlate"` // NJ
|
||||||
LicensePlateState string `json:"licensePlateState"` // ABCDEF
|
LicensePlateState string `json:"licensePlateState"` // ABCDEF
|
||||||
SubscriptionStatus string `json:"subscriptionStatus"` // ACTIVE
|
SubscriptionStatus string `json:"subscriptionStatus"` // ACTIVE
|
||||||
SubscriptionFeatures []string `json:"subscriptionFeatures"` // "[ REMOTE ], [ SAFETY ], [ Retail | Finance3 | RetailPHEV ]""
|
SubscriptionFeatures []string `json:"subscriptionFeatures"` // "[ REMOTE ], [ SAFETY ], [ Retail | Finance3 | RetailPHEV ]""
|
||||||
SubscriptionPlans []string `json:"subscriptionPlans"` // []
|
SubscriptionPlans []string `json:"subscriptionPlans,omitempty"` // []
|
||||||
VehicleGeoPosition GeoPosition `json:"vehicleGeoPosition"` // GeoPosition struct
|
VehicleGeoPosition GeoPosition `json:"vehicleGeoPosition"` // GeoPosition struct
|
||||||
AccessLevel int `json:"accessLevel"` // -1
|
AccessLevel int `json:"accessLevel"` // -1
|
||||||
VehicleMileage int `json:"vehicleMileage"` // null
|
VehicleMileage int `json:"vehicleMileage,omitempty"` // null
|
||||||
CrmRightToRepair bool `json:"crmRightToRepair"` // true | false
|
CrmRightToRepair bool `json:"crmRightToRepair"` // true | false
|
||||||
AuthorizedVehicle bool `json:"authorizedVehicle"` // false | true
|
AuthorizedVehicle bool `json:"authorizedVehicle"` // false | true
|
||||||
NeedMileagePrompt bool `json:"needMileagePrompt"` // false | true
|
NeedMileagePrompt bool `json:"needMileagePrompt"` // false | true
|
||||||
RemoteServicePinExist bool `json:"remoteServicePinExist"` // true | false
|
RemoteServicePinExist bool `json:"remoteServicePinExist"` // true | false
|
||||||
NeedEmergencyContactPrompt bool `json:"needEmergencyContactPrompt"` // false | true
|
NeedEmergencyContactPrompt bool `json:"needEmergencyContactPrompt"` // false | true
|
||||||
Show3GSunsetBanner bool `json:"show3gSunsetBanner"` // false | true
|
Show3GSunsetBanner bool `json:"show3gSunsetBanner"` // false | true
|
||||||
Provisioned bool `json:"provisioned"` // true | false
|
Provisioned bool `json:"provisioned"` // true | false
|
||||||
TimeZone string `json:"timeZone"` // America/New_York
|
TimeZone string `json:"timeZone"` // America/New_York
|
||||||
SunsetUpgraded bool `json:"sunsetUpgraded"` // true | false
|
SunsetUpgraded bool `json:"sunsetUpgraded"` // true | false
|
||||||
PreferredDealer string `json:"preferredDealer"` // null |
|
PreferredDealer string `json:"preferredDealer,omitempty"` // null |
|
||||||
|
VehicleBranded bool `json:"vehicleBranded"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeoPosition .
|
// GeoPosition .
|
||||||
type GeoPosition struct {
|
type GeoPosition struct {
|
||||||
Latitude float64 `json:"latitude"` // 40.700184
|
Latitude float64 `json:"latitude"` // 40.700184
|
||||||
Longitude float64 `json:"longitude"` // -74.401375
|
Longitude float64 `json:"longitude"` // -74.401375
|
||||||
Speed float64 `json:"speed,omitempty"` // 62
|
Speed int `json:"speed,omitempty"` // 62
|
||||||
Heading int `json:"heading,omitempty"` // 155
|
Heading int `json:"heading,omitempty"` // 155
|
||||||
Timestamp string `json:"timestamp"` // "2021-12-22T13:14:47"
|
Timestamp CustomTime1 `json:"timestamp"` // "2021-12-22T13:14:47"
|
||||||
}
|
}
|
||||||
|
|
||||||
// type GeoPositionTime time.Time
|
// type GeoPositionTime time.Time
|
||||||
@ -166,65 +254,58 @@ type GeoPosition struct {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// VehicleStatus .
|
// VehicleStatus .
|
||||||
// "dataName":null
|
|
||||||
// parts = []{"door", "tire", "tyre", "window"}
|
|
||||||
// prefix = []{"pressure", "status"}
|
|
||||||
// suffix = []{"status", "position", "unit", "psi"}
|
|
||||||
// positions = []{"front", "rear", "sunroof", "boot", "enginehood"}
|
|
||||||
// subPositions []{"left", "right"}
|
|
||||||
|
|
||||||
type VehicleStatus struct {
|
type VehicleStatus struct {
|
||||||
VehicleId int64 `json:"vhsId"` // + 9969776690 5198812434
|
VehicleId int64 `json:"vhsId"` // + 9969776690 5198812434
|
||||||
OdometerValue int `json:"odometerValue"` // + 23787
|
OdometerValue int `json:"odometerValue"` // + 23787
|
||||||
OdometerValueKm int `json:"odometerValueKilometers"` // + 38273
|
OdometerValueKm int `json:"odometerValueKilometers"` // + 38273
|
||||||
EventDate int64 `json:"eventDate"` // + 1701896993000
|
EventDate UnixTime `json:"eventDate"` // + 1701896993000
|
||||||
EventDateStr string `json:"eventDateStr"` // + 2023-12-06T21:09+0000
|
EventDateStr string `json:"eventDateStr"` // + 2023-12-06T21:09+0000
|
||||||
EventDateCarUser int64 `json:"eventDateCarUser"` // + 1701896993000
|
EventDateCarUser UnixTime `json:"eventDateCarUser"` // + 1701896993000
|
||||||
EventDateStrCarUser string `json:"eventDateStrCarUser"` // + 2023-12-06T21:09+0000
|
EventDateStrCarUser string `json:"eventDateStrCarUser"` // + 2023-12-06T21:09+0000
|
||||||
Latitude float64 `json:"latitude"` // + 40.700183
|
Latitude float64 `json:"latitude"` // + 40.700183
|
||||||
Longitude float64 `json:"longitude"` // + -74.401372
|
Longitude float64 `json:"longitude"` // + -74.401372
|
||||||
Heading int `json:"positionHeadingDegree,string"` // + "154"
|
Heading int `json:"positionHeadingDegree,string"` // + "154"
|
||||||
DistanceToEmptyFuelMiles float64 `json:"distanceToEmptyFuelMiles"` // + 209.4
|
DistanceToEmptyFuelMiles float64 `json:"distanceToEmptyFuelMiles"` // + 209.4
|
||||||
DistanceToEmptyFuelKilometers int `json:"distanceToEmptyFuelKilometers"` // + 337
|
DistanceToEmptyFuelKilometers int `json:"distanceToEmptyFuelKilometers"` // + 337
|
||||||
DistanceToEmptyFuelMiles10s int `json:"distanceToEmptyFuelMiles10s"` // + 210
|
DistanceToEmptyFuelMiles10s int `json:"distanceToEmptyFuelMiles10s"` // + 210
|
||||||
DistanceToEmptyFuelKilometers10s int `json:"distanceToEmptyFuelKilometers10s"` // + 340
|
DistanceToEmptyFuelKilometers10s int `json:"distanceToEmptyFuelKilometers10s"` // + 340
|
||||||
AvgFuelConsumptionMpg float64 `json:"avgFuelConsumptionMpg"` // + 18.4
|
AvgFuelConsumptionMpg float64 `json:"avgFuelConsumptionMpg"` // + 18.4
|
||||||
AvgFuelConsumptionLitersPer100Kilometers float64 `json:"avgFuelConsumptionLitersPer100Kilometers"` // + 12.8
|
AvgFuelConsumptionLitersPer100Kilometers float64 `json:"avgFuelConsumptionLitersPer100Kilometers"` // + 12.8
|
||||||
RemainingFuelPercent int `json:"remainingFuelPercent"` // + 82
|
RemainingFuelPercent int `json:"remainingFuelPercent"` // + 82
|
||||||
TirePressureFrontLeft int `json:"tirePressureFrontLeft,string,omitempty"` // + "2275"
|
TirePressureFrontLeft int `json:"tirePressureFrontLeft,string,omitempty"` // + "2275"
|
||||||
TirePressureFrontRight int `json:"tirePressureFrontRight,string,omitempty"` // + "2344"
|
TirePressureFrontRight int `json:"tirePressureFrontRight,string,omitempty"` // + "2344"
|
||||||
TirePressureRearLeft int `json:"tirePressureRearLeft,string,omitempty"` // + "2413"
|
TirePressureRearLeft int `json:"tirePressureRearLeft,string,omitempty"` // + "2413"
|
||||||
TirePressureRearRight int `json:"tirePressureRearRight,string,omitempty"` // + "2344"
|
TirePressureRearRight int `json:"tirePressureRearRight,string,omitempty"` // + "2344"
|
||||||
TirePressureFrontLeftPsi float64 `json:"tirePressureFrontLeftPsi,string,omitempty"` // + "33"
|
TirePressureFrontLeftPsi float64 `json:"tirePressureFrontLeftPsi,string,omitempty"` // + "33"
|
||||||
TirePressureFrontRightPsi float64 `json:"tirePressureFrontRightPsi,string,omitempty"` // + "34"
|
TirePressureFrontRightPsi float64 `json:"tirePressureFrontRightPsi,string,omitempty"` // + "34"
|
||||||
TirePressureRearLeftPsi float64 `json:"tirePressureRearLeftPsi,string,omitempty"` // + "35"
|
TirePressureRearLeftPsi float64 `json:"tirePressureRearLeftPsi,string,omitempty"` // + "35"
|
||||||
TirePressureRearRightPsi float64 `json:"tirePressureRearRightPsi,string,omitempty"` // + "34"
|
TirePressureRearRightPsi float64 `json:"tirePressureRearRightPsi,string,omitempty"` // + "34"
|
||||||
TyreStatusFrontLeft string `json:"tyreStatusFrontLeft"` // + "UNKNOWN"
|
TyreStatusFrontLeft string `json:"tyreStatusFrontLeft"` // + "UNKNOWN"
|
||||||
TyreStatusFrontRight string `json:"tyreStatusFrontRight"` // + "UNKNOWN"
|
TyreStatusFrontRight string `json:"tyreStatusFrontRight"` // + "UNKNOWN"
|
||||||
TyreStatusRearLeft string `json:"tyreStatusRearLeft"` // + "UNKNOWN"
|
TyreStatusRearLeft string `json:"tyreStatusRearLeft"` // + "UNKNOWN"
|
||||||
TyreStatusRearRight string `json:"tyreStatusRearRight"` // + "UNKNOWN"
|
TyreStatusRearRight string `json:"tyreStatusRearRight"` // + "UNKNOWN"
|
||||||
EvStateOfChargePercent float64 `json:"evStateOfChargePercent,omitempty"` // + null
|
EvStateOfChargePercent float64 `json:"evStateOfChargePercent,omitempty"` // + null
|
||||||
EvDistanceToEmptyMiles int `json:"evDistanceToEmptyMiles,omitempty"` // + null
|
EvDistanceToEmptyMiles int `json:"evDistanceToEmptyMiles,omitempty"` // + null
|
||||||
EvDistanceToEmptyKilometers int `json:"evDistanceToEmptyKilometers,omitempty"` // + null
|
EvDistanceToEmptyKilometers int `json:"evDistanceToEmptyKilometers,omitempty"` // + null
|
||||||
EvDistanceToEmptyByStateMiles int `json:"evDistanceToEmptyByStateMiles,omitempty"` // + null
|
EvDistanceToEmptyByStateMiles int `json:"evDistanceToEmptyByStateMiles,omitempty"` // + null
|
||||||
EvDistanceToEmptyByStateKilometers int `json:"evDistanceToEmptyByStateKilometers,omitempty"` // + null
|
EvDistanceToEmptyByStateKilometers int `json:"evDistanceToEmptyByStateKilometers,omitempty"` // + null
|
||||||
VehicleStateType string `json:"vehicleStateType"` // + "IGNITION_OFF | IGNITION_ON"
|
VehicleStateType string `json:"vehicleStateType"` // + "IGNITION_OFF | IGNITION_ON"
|
||||||
WindowFrontLeftStatus string `json:"windowFrontLeftStatus"` // CLOSE | VENTED | OPEN
|
WindowFrontLeftStatus string `json:"windowFrontLeftStatus"` // CLOSE | VENTED | OPEN
|
||||||
WindowFrontRightStatus string `json:"windowFrontRightStatus"` // CLOSE | VENTED | OPEN
|
WindowFrontRightStatus string `json:"windowFrontRightStatus"` // CLOSE | VENTED | OPEN
|
||||||
WindowRearLeftStatus string `json:"windowRearLeftStatus"` // CLOSE | VENTED | OPEN
|
WindowRearLeftStatus string `json:"windowRearLeftStatus"` // CLOSE | VENTED | OPEN
|
||||||
WindowRearRightStatus string `json:"windowRearRightStatus"` // CLOSE | VENTED | OPEN
|
WindowRearRightStatus string `json:"windowRearRightStatus"` // CLOSE | VENTED | OPEN
|
||||||
WindowSunroofStatus string `json:"windowSunroofStatus"` // CLOSE | SLIDE_PARTLY_OPEN | OPEN | TILT
|
WindowSunroofStatus string `json:"windowSunroofStatus"` // CLOSE | SLIDE_PARTLY_OPEN | OPEN | TILT
|
||||||
DoorBootPosition string `json:"doorBootPosition"` // CLOSED | OPEN
|
DoorBootPosition string `json:"doorBootPosition"` // CLOSED | OPEN
|
||||||
DoorEngineHoodPosition string `json:"doorEngineHoodPosition"` // CLOSED | OPEN
|
DoorEngineHoodPosition string `json:"doorEngineHoodPosition"` // CLOSED | OPEN
|
||||||
DoorFrontLeftPosition string `json:"doorFrontLeftPosition"` // CLOSED | OPEN
|
DoorFrontLeftPosition string `json:"doorFrontLeftPosition"` // CLOSED | OPEN
|
||||||
DoorFrontRightPosition string `json:"doorFrontRightPosition"` // CLOSED | OPEN
|
DoorFrontRightPosition string `json:"doorFrontRightPosition"` // CLOSED | OPEN
|
||||||
DoorRearLeftPosition string `json:"doorRearLeftPosition"` // CLOSED | OPEN
|
DoorRearLeftPosition string `json:"doorRearLeftPosition"` // CLOSED | OPEN
|
||||||
DoorRearRightPosition string `json:"doorRearRightPosition"` // CLOSED | OPEN
|
DoorRearRightPosition string `json:"doorRearRightPosition"` // CLOSED | OPEN
|
||||||
DoorBootLockStatus string `json:"doorBootLockStatus"` // LOCKED | UNLOCKED
|
DoorBootLockStatus string `json:"doorBootLockStatus"` // LOCKED | UNLOCKED
|
||||||
DoorFrontLeftLockStatus string `json:"doorFrontLeftLockStatus"` // LOCKED | UNLOCKED
|
DoorFrontLeftLockStatus string `json:"doorFrontLeftLockStatus"` // LOCKED | UNLOCKED
|
||||||
DoorFrontRightLockStatus string `json:"doorFrontRightLockStatus"` // LOCKED | UNLOCKED
|
DoorFrontRightLockStatus string `json:"doorFrontRightLockStatus"` // LOCKED | UNLOCKED
|
||||||
DoorRearLeftLockStatus string `json:"doorRearLeftLockStatus"` // LOCKED | UNLOCKED
|
DoorRearLeftLockStatus string `json:"doorRearLeftLockStatus"` // LOCKED | UNLOCKED
|
||||||
DoorRearRightLockStatus string `json:"doorRearRightLockStatus"` // LOCKED | UNLOCKED
|
DoorRearRightLockStatus string `json:"doorRearRightLockStatus"` // LOCKED | UNLOCKED
|
||||||
}
|
}
|
||||||
|
|
||||||
// VehicleCondition .
|
// VehicleCondition .
|
||||||
@ -268,25 +349,41 @@ type VehicleCondition struct {
|
|||||||
LastUpdatedTime string `json:"lastUpdatedTime"` // "2023-04-10T17:50:54+0000",
|
LastUpdatedTime string `json:"lastUpdatedTime"` // "2023-04-10T17:50:54+0000",
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClimateSettings .
|
// ClimateProfile represents a climate control profile for a Subaru vehicle.
|
||||||
// "dataName":null
|
type ClimateProfile struct {
|
||||||
// type ClimateSettings struct {
|
Name string `json:"name"`
|
||||||
// RunTimeMinutes string `json:"runTimeMinutes"`
|
VehicleType string `json:"vehicleType,omitempty"` // vehicleType [ gas | phev ]
|
||||||
// StartConfiguration string `json:"startConfiguration"`
|
PresetType string `json:"presetType"` // presetType [ subaruPreset | userPreset ]
|
||||||
// AirConditionOn string `json:"airConditionOn"`
|
StartConfiguration string `json:"startConfiguration"` // startConfiguration [ START_ENGINE_ALLOW_KEY_IN_IGNITION (gas) | START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION (phev) ]
|
||||||
// OuterAirCirculation string `json:"outerAirCirculation"`
|
RunTimeMinutes int `json:"runTimeMinutes,string"` // runTimeMinutes [ 0 | 1 | 5 | 10 ]
|
||||||
// ClimateZoneFrontAirMode string `json:"climateZoneFrontAirMode"`
|
HeatedRearWindowActive string `json:"heatedRearWindowActive"` // heatedRearWindowActive: [ false | true ]
|
||||||
// ClimateZoneFrontTemp string `json:"climateZoneFrontTemp"`
|
HeatedSeatFrontRight string `json:"heatedSeatFrontRight"` // heatedSeatFrontRight: [ OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT ]
|
||||||
// ClimateZoneFrontAirVolume string `json:"climateZoneFrontAirVolume"`
|
HeatedSeatFrontLeft string `json:"heatedSeatFrontLeft"` // heatedSeatFrontLeft: [ OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT ]
|
||||||
// HeatedSeatFrontLeft string `json:"heatedSeatFrontLeft"`
|
ClimateZoneFrontTemp int `json:"climateZoneFrontTemp,string"` // climateZoneFrontTemp: [ for _ in range(60, 85 + 1)] // climateZoneFrontTempCelsius: [for _ in range(15, 30 + 1) ]
|
||||||
// HeatedSeatFrontRight string `json:"heatedSeatFrontRight"`
|
ClimateZoneFrontAirMode string `json:"climateZoneFrontAirMode"` // climateZoneFrontAirMode: [ WINDOW | FEET_WINDOW | FACE | FEET | FEET_FACE_BALANCED | AUTO ]
|
||||||
// HeatedRearWindowActive string `json:"heatedRearWindowActive"`
|
ClimateZoneFrontAirVolume string `json:"climateZoneFrontAirVolume"` // climateZoneFrontAirVolume: [ AUTO | 2 | 4 | 7 ]
|
||||||
// }
|
OuterAirCirculation string `json:"outerAirCirculation"` // airConditionOn: [ auto | outsideAir | true ]
|
||||||
|
AirConditionOn string `json:"airConditionOn"` // airConditionOn: [ false | true ]
|
||||||
|
CanEdit string `json:"canEdit"` // canEdit [ false | true ]
|
||||||
|
Disabled string `json:"disabled"` // disabled [ false | true ]
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClimateProfiles map[string]ClimateProfile
|
||||||
|
|
||||||
|
// GeoLocation represents the geographical location of a Subaru vehicle.
|
||||||
|
type GeoLocation struct {
|
||||||
|
Latitude float64 `json:"latitude"` // 40.700184
|
||||||
|
Longitude float64 `json:"longitude"` // -74.401375
|
||||||
|
Heading int `json:"heading,omitempty"` // 189
|
||||||
|
Speed int `json:"speed,omitempty"` // 0.00
|
||||||
|
Updated CustomTime1 `json:"timestamp"` // "2025-07-08T19:05:07"
|
||||||
|
}
|
||||||
|
|
||||||
// ServiceRequest .
|
// ServiceRequest .
|
||||||
// "dataName": "remoteServiceStatus"
|
// "dataName": "remoteServiceStatus"
|
||||||
type ServiceRequest struct {
|
type ServiceRequest struct {
|
||||||
ServiceRequestID string `json:"serviceRequestId,omitempty"` // 4S4BTGND8L3137058_1640294426029_19_@NGTP
|
ServiceRequestID string `json:"serviceRequestId,omitempty"` // 4S4BTGND8L3137058_1640294426029_19_@NGTP
|
||||||
|
Vin string `json:"vin"` // 4S4BTGND8L3137058
|
||||||
Success bool `json:"success"` // false | true // Could be in the false state while the executed request in the progress
|
Success bool `json:"success"` // false | true // Could be in the false state while the executed request in the progress
|
||||||
Cancelled bool `json:"cancelled"` // false | true
|
Cancelled bool `json:"cancelled"` // false | true
|
||||||
RemoteServiceType string `json:"remoteServiceType"` // vehicleStatus | condition | locate | unlock | lock | lightsOnly | engineStart | engineStop | phevChargeNow
|
RemoteServiceType string `json:"remoteServiceType"` // vehicleStatus | condition | locate | unlock | lock | lightsOnly | engineStart | engineStop | phevChargeNow
|
||||||
@ -294,8 +391,29 @@ type ServiceRequest struct {
|
|||||||
SubState string `json:"subState,omitempty"` // null
|
SubState string `json:"subState,omitempty"` // null
|
||||||
ErrorCode string `json:"errorCode,omitempty"` // null:null
|
ErrorCode string `json:"errorCode,omitempty"` // null:null
|
||||||
Result json.RawMessage `json:"result,omitempty"` // struct
|
Result json.RawMessage `json:"result,omitempty"` // struct
|
||||||
UpdateTime int64 `json:"updateTime,omitempty"` // timestamp
|
UpdateTime UnixTime `json:"updateTime,omitempty"` // timestamp // is empty if the request is started
|
||||||
Vin string `json:"vin"` // 4S4BTGND8L3137058
|
}
|
||||||
|
|
||||||
|
// parse parses the JSON response from the MySubaru API into a ServiceRequest struct.
|
||||||
|
func (sr *ServiceRequest) parse(b []byte, logger *slog.Logger) error {
|
||||||
|
err := json.Unmarshal(b, &sr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("error while parsing json", "request", "GetVehicleCondition", "error", err.Error())
|
||||||
|
}
|
||||||
|
if !sr.Success && sr.ErrorCode != "" {
|
||||||
|
logger.Error("error in response", "request", "GetVehicleCondition", "errorCode", sr.ErrorCode, "remoteServiceType", sr.RemoteServiceType)
|
||||||
|
switch sr.ErrorCode {
|
||||||
|
case API_ERRORS["API_ERROR_SERVICE_ALREADY_STARTED"]:
|
||||||
|
return errors.New("error in response: Service already started")
|
||||||
|
case API_ERRORS["API_ERROR_VEHICLE_NOT_IN_ACCOUNT"]:
|
||||||
|
return errors.New("error in response: Vehicle not in account")
|
||||||
|
case API_ERRORS["API_ERROR_SOA_403"]:
|
||||||
|
return errors.New("error in response: Unable to parse response body, SOA 403 error")
|
||||||
|
default:
|
||||||
|
return errors.New("error in response: " + sr.ErrorCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// climateSettings: [ climateSettings ]
|
// climateSettings: [ climateSettings ]
|
||||||
@ -316,12 +434,12 @@ type VehicleHealth struct {
|
|||||||
LastUpdatedDate int64 `json:"lastUpdatedDate"`
|
LastUpdatedDate int64 `json:"lastUpdatedDate"`
|
||||||
}
|
}
|
||||||
type VehicleHealthItem struct {
|
type VehicleHealthItem struct {
|
||||||
B2cCode string `json:"b2cCode"`
|
WarningCode int `json:"warningCode"` // internal code used by MySubaru, not documented
|
||||||
FeatureCode string `json:"featureCode"`
|
B2cCode string `json:"b2cCode"` // oilTemp | airbag | oilLevel | etc.
|
||||||
IsTrouble bool `json:"isTrouble"`
|
FeatureCode string `json:"featureCode"` // SRS_MIL | CEL_MIL | ATF_MIL | etc.
|
||||||
OnDaiID int `json:"onDaiId"` // Has a number, probably id, but I couldn't find it purpose
|
IsTrouble bool `json:"isTrouble"` // false | true
|
||||||
OnDates []int64 `json:"onDates,omitempty"` // List of the timestamps
|
OnDaiID int `json:"onDaiId"` // Has a number, probably internal record id
|
||||||
WarningCode int `json:"warningCode"`
|
OnDates []UnixTime `json:"onDates,omitempty"` // List of the timestamps
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorResponse .
|
// ErrorResponse .
|
||||||
@ -334,3 +452,61 @@ type ErrorResponse struct {
|
|||||||
ErrorLabel string `json:"errorLabel"` // "404-soa-unableToParseResponseBody"
|
ErrorLabel string `json:"errorLabel"` // "404-soa-unableToParseResponseBody"
|
||||||
ErrorDescription string `json:"errorDescription,omitempty"` // null
|
ErrorDescription string `json:"errorDescription,omitempty"` // null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnixTime is a wrapper around time.Time that allows us to marshal and unmarshal Unix timestamps
|
||||||
|
type UnixTime struct {
|
||||||
|
time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON is the method that satisfies the Unmarshaller interface
|
||||||
|
// Note that it uses a pointer receiver. It needs this because it will be modifying the embedded time.Time instance
|
||||||
|
func (u *UnixTime) UnmarshalJSON(b []byte) error {
|
||||||
|
var timestamp int64
|
||||||
|
err := json.Unmarshal(b, ×tamp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u.Time = time.Unix(timestamp, 0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON turns our time.Time back into an int
|
||||||
|
func (u UnixTime) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(fmt.Sprintf("%d", (u.Time.Unix()))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomTime1 "2021-12-22T13:14:47" is a custom type for unmarshalling time strings
|
||||||
|
type CustomTime1 struct {
|
||||||
|
time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface
|
||||||
|
func (ct *CustomTime1) UnmarshalJSON(b []byte) (err error) {
|
||||||
|
// Use the correct layout string for the desired format
|
||||||
|
const layout = "2006-01-02T15:04:05"
|
||||||
|
s := strings.Trim(string(b), "\\\"") // Remove surrounding escapes and quotes (parsing time \"\\\"2025-07-09T00:23:19\\\"\" as \"2006-01-02T15:04:05\")
|
||||||
|
if string(b) == "null" {
|
||||||
|
ct.Time = time.Time{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ct.Time, err = time.Parse(layout, s) // Parse the string with your custom layout
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomTime2 "2023-04-10T17:50:54+0000" is a custom type for unmarshalling time strings
|
||||||
|
type CustomTime2 struct {
|
||||||
|
time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface
|
||||||
|
func (ct *CustomTime2) UnmarshalJSON(b []byte) (err error) {
|
||||||
|
// Use the correct layout string for the desired format
|
||||||
|
const layout = "2006-01-02T15:04:05-0700"
|
||||||
|
s := strings.Trim(string(b), "\\\"") // Remove surrounding escapes and quotes ((parsing time \"\\\"2025-07-09T00:23:19\\\"\" as \"2006-01-02T15:04:05\"))
|
||||||
|
if string(b) == "null" {
|
||||||
|
ct.Time = time.Time{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ct.Time, err = time.Parse(layout, s) // Parse the string with your custom layout
|
||||||
|
return
|
||||||
|
}
|
||||||
|
204
mysubaru_test.go
Normal file
204
mysubaru_test.go
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
package mysubaru
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnixTime_UnmarshalJSON(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
wantTime time.Time
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid unix timestamp",
|
||||||
|
input: "1700000000",
|
||||||
|
wantTime: time.Unix(1700000000, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid string",
|
||||||
|
input: "\"notanumber\"",
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty input",
|
||||||
|
input: "",
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float value",
|
||||||
|
input: "1700000000.123",
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero timestamp",
|
||||||
|
input: "0",
|
||||||
|
wantTime: time.Unix(0, 0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var ut UnixTime
|
||||||
|
err := ut.UnmarshalJSON([]byte(tt.input))
|
||||||
|
if (err != nil) != tt.wantError {
|
||||||
|
t.Errorf("UnmarshalJSON() error = %v, wantError %v", err, tt.wantError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !tt.wantError && !ut.Time.Equal(tt.wantTime) {
|
||||||
|
t.Errorf("UnmarshalJSON() got = %v, want %v", ut.Time, tt.wantTime)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnixTime_UnmarshalJSON_withJSONUnmarshal(t *testing.T) {
|
||||||
|
type testStruct struct {
|
||||||
|
Time UnixTime `json:"time"`
|
||||||
|
}
|
||||||
|
input := `{"time":1700000000}`
|
||||||
|
var ts testStruct
|
||||||
|
err := json.Unmarshal([]byte(input), &ts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("json.Unmarshal failed: %v", err)
|
||||||
|
}
|
||||||
|
want := time.Unix(1700000000, 0)
|
||||||
|
if !ts.Time.Time.Equal(want) {
|
||||||
|
t.Errorf("UnmarshalJSON() got = %v, want %v", ts.Time.Time, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestUnixTime_MarshalJSON(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input time.Time
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "epoch",
|
||||||
|
input: time.Unix(0, 0),
|
||||||
|
want: "0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive unix time",
|
||||||
|
input: time.Unix(1700000000, 0),
|
||||||
|
want: "1700000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative unix time",
|
||||||
|
input: time.Unix(-100, 0),
|
||||||
|
want: "-100",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ut := UnixTime{Time: tt.input}
|
||||||
|
got, err := ut.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("MarshalJSON() error = %v", err)
|
||||||
|
}
|
||||||
|
if string(got) != tt.want {
|
||||||
|
t.Errorf("MarshalJSON() = %s, want %s", string(got), tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnixTime_MarshalJSON_withJSONMarshal(t *testing.T) {
|
||||||
|
type testStruct struct {
|
||||||
|
Time UnixTime `json:"time"`
|
||||||
|
}
|
||||||
|
ts := testStruct{Time: UnixTime{Time: time.Unix(1700000000, 0)}}
|
||||||
|
b, err := json.Marshal(ts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("json.Marshal failed: %v", err)
|
||||||
|
}
|
||||||
|
want := `{"time":1700000000}`
|
||||||
|
if string(b) != want {
|
||||||
|
t.Errorf("json.Marshal() = %s, want %s", string(b), want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// func TestResponse_parse(t *testing.T) {
|
||||||
|
// tests := []struct {
|
||||||
|
// name string
|
||||||
|
// input string
|
||||||
|
// wantErr error
|
||||||
|
// wantCode string
|
||||||
|
// wantLog string
|
||||||
|
// }{
|
||||||
|
// {
|
||||||
|
// name: "success response",
|
||||||
|
// input: `{"success":true,"dataName":"foo","data":{}}`,
|
||||||
|
// wantErr: nil,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "invalid json",
|
||||||
|
// input: `{"success":tru`,
|
||||||
|
// wantErr: errors.New("error while parsing json:"),
|
||||||
|
// wantLog: "error while parsing json",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "API_ERROR_NO_ACCOUNT",
|
||||||
|
// input: `{"success":false,"errorCode":"noAccount","dataName":"errorResponse","data":{}}`,
|
||||||
|
// wantErr: errors.New("error in response: Account not found"),
|
||||||
|
// wantCode: "noAccount",
|
||||||
|
// wantLog: "error in response",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "API_ERROR_INVALID_CREDENTIALS",
|
||||||
|
// input: `{"success":false,"errorCode":"invalidCredentials","dataName":"errorResponse","data":{}}`,
|
||||||
|
// wantErr: errors.New("error in response: Invalid Credentials"),
|
||||||
|
// wantCode: "invalidCredentials",
|
||||||
|
// wantLog: "error in response",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "API_ERROR_SOA_403",
|
||||||
|
// input: `{"success":false,"errorCode":"404-soa-unableToParseResponseBody","dataName":"errorResponse","data":{}}`,
|
||||||
|
// wantErr: errors.New("error in response: Unable to parse response body, SOA 403 error"),
|
||||||
|
// wantCode: "404-soa-unableToParseResponseBody",
|
||||||
|
// wantLog: "error in response",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "unknown error code",
|
||||||
|
// input: `{"success":false,"errorCode":"somethingElse","dataName":"errorResponse","data":{}}`,
|
||||||
|
// wantErr: errors.New("error in response: somethingElse"),
|
||||||
|
// wantCode: "somethingElse",
|
||||||
|
// wantLog: "error in response",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "no errorCode but not success",
|
||||||
|
// input: `{"success":false,"dataName":"errorResponse","data":{}}`,
|
||||||
|
// wantErr: nil,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
|
||||||
|
// for _, tt := range tests {
|
||||||
|
// t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// var resp Response
|
||||||
|
// logger := slog.New(slog.NewTextHandler(nil, nil))
|
||||||
|
// got, err := resp.parse([]byte(tt.input), logger)
|
||||||
|
// if tt.wantErr != nil {
|
||||||
|
// if err == nil {
|
||||||
|
// t.Fatalf("expected error, got nil")
|
||||||
|
// }
|
||||||
|
// if !contains(err.Error(), tt.wantErr.Error()) {
|
||||||
|
// t.Errorf("parse() error = %v, want %v", err, tt.wantErr)
|
||||||
|
// }
|
||||||
|
// } else if err != nil {
|
||||||
|
// t.Errorf("parse() unexpected error: %v", err)
|
||||||
|
// }
|
||||||
|
// if tt.wantCode != "" && got != nil && got.ErrorCode != tt.wantCode {
|
||||||
|
// t.Errorf("parse() got.ErrorCode = %v, want %v", got.ErrorCode, tt.wantCode)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // contains is a helper for substring matching.
|
||||||
|
// func contains(s, substr string) bool {
|
||||||
|
// return bytes.Contains([]byte(s), []byte(substr))
|
||||||
|
// }
|
62
utils.go
62
utils.go
@ -1,7 +1,10 @@
|
|||||||
package mysubaru
|
package mysubaru
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"net/mail"
|
||||||
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -114,6 +117,65 @@ func transcodeDigits(vin string) int {
|
|||||||
return digitSum
|
return digitSum
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// emailMasking takes an email address as input and returns a version of the email
|
||||||
|
// with the username part partially hidden for privacy. Only the first and last
|
||||||
|
// characters of the username are visible, with the middle characters replaced by asterisks.
|
||||||
|
// The function validates the email format before processing.
|
||||||
|
// Returns the obfuscated email or an error if the input is not a valid email address.
|
||||||
|
func emailMasking(email string) (string, error) {
|
||||||
|
_, err := mail.ParseAddress(email)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("invalid email address: %s", email)
|
||||||
|
}
|
||||||
|
|
||||||
|
re1 := regexp.MustCompile(`^(.*?)@(.*)$`)
|
||||||
|
matches := re1.FindStringSubmatch(email)
|
||||||
|
|
||||||
|
var username, domain string
|
||||||
|
if len(matches) == 3 { // Expecting the full match, username, and domain
|
||||||
|
username = matches[1]
|
||||||
|
domain = matches[2]
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("invalid email format: %s", email)
|
||||||
|
}
|
||||||
|
|
||||||
|
re2 := regexp.MustCompile(`(.)(.*)(.)`)
|
||||||
|
|
||||||
|
replacedString := re2.ReplaceAllStringFunc(username, func(s string) string {
|
||||||
|
firstChar := string(s[0])
|
||||||
|
lastChar := string(s[len(s)-1])
|
||||||
|
middleCharsCount := len(s) - 2
|
||||||
|
|
||||||
|
if middleCharsCount < 0 { // Should not happen with the length check above, but for robustness
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return firstChar + strings.Repeat("*", middleCharsCount) + lastChar
|
||||||
|
})
|
||||||
|
|
||||||
|
return replacedString + "@" + domain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// containsValueInStruct checks if any string field in the given struct 's' contains the specified 'search' substring (case-insensitive).
|
||||||
|
// It returns true if at least one string field contains the substring, and false otherwise.
|
||||||
|
// If 's' is not a struct, it returns false.
|
||||||
|
func containsValueInStruct(s any, search string) bool {
|
||||||
|
val := reflect.ValueOf(s)
|
||||||
|
if val.Kind() != reflect.Struct {
|
||||||
|
return false // Not a struct
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < val.NumField(); i++ {
|
||||||
|
field := val.Field(i)
|
||||||
|
if field.Kind() == reflect.String {
|
||||||
|
if strings.Contains(strings.ToLower(field.String()), strings.ToLower(search)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// timeTrack .
|
// timeTrack .
|
||||||
// func timeTrack(name string) {
|
// func timeTrack(name string) {
|
||||||
// start := time.Now()
|
// start := time.Now()
|
||||||
|
@ -128,3 +128,89 @@ func TestUrlToGen_NoApiGen(t *testing.T) {
|
|||||||
t.Errorf("urlToGen(%q, %q) = %q, want %q", url, gen, got, url)
|
t.Errorf("urlToGen(%q, %q) = %q, want %q", url, gen, got, url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEmailHidder(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
email string
|
||||||
|
expected string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"alex@example.com", "a**x@example.com", false},
|
||||||
|
{"a@example.com", "a@example.com", false},
|
||||||
|
{"ab@example.com", "ab@example.com", false},
|
||||||
|
{"", "", true},
|
||||||
|
{"notanemail", "", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
got, err := emailMasking(tt.email)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("emailHidder(%q) error = %v, wantErr %v", tt.email, err, tt.wantErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got != tt.expected {
|
||||||
|
t.Errorf("emailHidder(%q) = %q, want %q", tt.email, got, tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestContainsValueInStruct(t *testing.T) {
|
||||||
|
type TestStruct struct {
|
||||||
|
Name string
|
||||||
|
Address string
|
||||||
|
Age int
|
||||||
|
Note string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
s any
|
||||||
|
search string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
s: TestStruct{Name: "Alice", Address: "123 Main St", Age: 30, Note: "VIP customer"},
|
||||||
|
search: "alice",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
s: TestStruct{Name: "Bob", Address: "456 Elm St", Age: 25, Note: "Regular"},
|
||||||
|
search: "elm",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
s: TestStruct{Name: "Charlie", Address: "789 Oak St", Age: 40, Note: "VIP"},
|
||||||
|
search: "vip",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
s: TestStruct{Name: "Diana", Address: "101 Pine St", Age: 22, Note: ""},
|
||||||
|
search: "xyz",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
s: TestStruct{Name: "", Address: "", Age: 0, Note: ""},
|
||||||
|
search: "",
|
||||||
|
want: true, // empty string is contained in all strings
|
||||||
|
},
|
||||||
|
{
|
||||||
|
s: struct{ Foo int }{Foo: 42},
|
||||||
|
search: "42",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
s: "not a struct",
|
||||||
|
search: "struct",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
s: struct{ S string }{S: "CaseInsensitive"},
|
||||||
|
search: "caseinsensitive",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
got := containsValueInStruct(tt.s, tt.search)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("Test %d: containsStringInStruct(%#v, %q) = %v, want %v", i, tt.s, tt.search, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
330
vehicle.go
330
vehicle.go
@ -12,30 +12,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// var parts = map[string]map[string][]string{
|
|
||||||
// "door": {
|
|
||||||
// "suffix": {"position", "status"},
|
|
||||||
// "position1": {"front", "rear", "boot", "enginehood"},
|
|
||||||
// "position2": {"right", "left"},
|
|
||||||
// },
|
|
||||||
// "window": {
|
|
||||||
// "suffix": {"status"},
|
|
||||||
// "position1": {"front", "rear", "sunroof"},
|
|
||||||
// "position2": {"right", "left"},
|
|
||||||
// },
|
|
||||||
// "tire": {
|
|
||||||
// "prefix": {"status"},
|
|
||||||
// "position1": {"front", "rear"},
|
|
||||||
// "position2": {"right", "left"},
|
|
||||||
// },
|
|
||||||
// "tyre": {
|
|
||||||
// "prefix": {"pressure"},
|
|
||||||
// "suffix": {"psi", "unit"},
|
|
||||||
// "position1": {"front", "rear"},
|
|
||||||
// "position2": {"right", "left"},
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Vehicle represents a Subaru vehicle with various attributes and methods to interact with it.
|
// Vehicle represents a Subaru vehicle with various attributes and methods to interact with it.
|
||||||
type Vehicle struct {
|
type Vehicle struct {
|
||||||
CarId int64
|
CarId int64
|
||||||
@ -88,34 +64,6 @@ type Vehicle struct {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClimateProfile represents a climate control profile for a Subaru vehicle.
|
|
||||||
type ClimateProfile struct {
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
VehicleType string `json:"vehicleType,omitempty"` // vehicleType [ gas | phev ]
|
|
||||||
PresetType string `json:"presetType,omitempty"` // presetType [ subaruPreset | userPreset ]
|
|
||||||
CanEdit bool `json:"canEdit,string,omitempty"` // canEdit [ false | true ]
|
|
||||||
Disabled bool `json:"disabled,string,omitempty"` // disabled [ false | true ]
|
|
||||||
RunTimeMinutes int `json:"runTimeMinutes,string"` // runTimeMinutes [ 5 | 10 ]
|
|
||||||
ClimateZoneFrontTemp int `json:"climateZoneFrontTemp,string"` // climateZoneFrontTemp: [ for _ in range(60, 85 + 1)] // climateZoneFrontTempCelsius: [for _ in range(15, 30 + 1) ]
|
|
||||||
ClimateZoneFrontAirMode string `json:"climateZoneFrontAirMode"` // climateZoneFrontAirMode: [ WINDOW | FEET_WINDOW | FACE | FEET | FEET_FACE_BALANCED | AUTO ]
|
|
||||||
ClimateZoneFrontAirVolume string `json:"climateZoneFrontAirVolume"` // climateZoneFrontAirVolume: [ AUTO | 2 | 4 | 7 ]
|
|
||||||
OuterAirCirculation string `json:"outerAirCirculation"` // outerAirCirculation: [ outsideAir, recirculation ]
|
|
||||||
HeatedRearWindowActive bool `json:"heatedRearWindowActive,string"` // heatedRearWindowActive: [ false | true ]
|
|
||||||
AirConditionOn bool `json:"airConditionOn,string"` // airConditionOn: [ false | true ]
|
|
||||||
HeatedSeatFrontLeft string `json:"heatedSeatFrontLeft"` // heatedSeatFrontLeft: [ OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT ]
|
|
||||||
HeatedSeatFrontRight string `json:"heatedSeatFrontRight"` // heatedSeatFrontRight: [ OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT ]
|
|
||||||
StartConfiguration string `json:"startConfiguration"` // startConfiguration [ START_ENGINE_ALLOW_KEY_IN_IGNITION (gas) | START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION (phev) ]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GeoLocation represents the geographical location of a Subaru vehicle.
|
|
||||||
type GeoLocation struct {
|
|
||||||
Latitude float64 // 40.700184
|
|
||||||
Longitude float64 // -74.401375
|
|
||||||
Heading int // 189
|
|
||||||
Speed float64 // 0.00
|
|
||||||
Updated time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// Door represents a door of a Subaru vehicle with its position, sub-position, status, and lock state.
|
// Door represents a door of a Subaru vehicle with its position, sub-position, status, and lock state.
|
||||||
type Door struct {
|
type Door struct {
|
||||||
Position string // front | rear | boot | enginehood
|
Position string // front | rear | boot | enginehood
|
||||||
@ -214,11 +162,6 @@ func (v *Vehicle) String() string {
|
|||||||
// Lock
|
// Lock
|
||||||
// Sends a command to lock doors.
|
// Sends a command to lock doors.
|
||||||
func (v *Vehicle) Lock() (chan string, error) {
|
func (v *Vehicle) Lock() (chan string, error) {
|
||||||
if !v.getRemoteOptionsStatus() {
|
|
||||||
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
}
|
|
||||||
|
|
||||||
params := map[string]string{
|
params := map[string]string{
|
||||||
"delay": "0",
|
"delay": "0",
|
||||||
"vin": v.Vin,
|
"vin": v.Vin,
|
||||||
@ -226,6 +169,7 @@ func (v *Vehicle) Lock() (chan string, error) {
|
|||||||
"forceKeyInCar": "false"}
|
"forceKeyInCar": "false"}
|
||||||
reqUrl := MOBILE_API_VERSION + urlToGen(apiURLs["API_LOCK"], v.getAPIGen())
|
reqUrl := MOBILE_API_VERSION + urlToGen(apiURLs["API_LOCK"], v.getAPIGen())
|
||||||
pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"]
|
pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"]
|
||||||
|
|
||||||
ch := make(chan string)
|
ch := make(chan string)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(ch)
|
defer close(ch)
|
||||||
@ -238,11 +182,6 @@ func (v *Vehicle) Lock() (chan string, error) {
|
|||||||
// Unlock
|
// Unlock
|
||||||
// Send command to unlock doors.
|
// Send command to unlock doors.
|
||||||
func (v *Vehicle) Unlock() (chan string, error) {
|
func (v *Vehicle) Unlock() (chan string, error) {
|
||||||
if !v.getRemoteOptionsStatus() {
|
|
||||||
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
}
|
|
||||||
|
|
||||||
params := map[string]string{
|
params := map[string]string{
|
||||||
"delay": "0",
|
"delay": "0",
|
||||||
"vin": v.Vin,
|
"vin": v.Vin,
|
||||||
@ -250,6 +189,7 @@ func (v *Vehicle) Unlock() (chan string, error) {
|
|||||||
"unlockDoorType": "ALL_DOORS_CMD"} // FRONT_LEFT_DOOR_CMD | ALL_DOORS_CMD
|
"unlockDoorType": "ALL_DOORS_CMD"} // FRONT_LEFT_DOOR_CMD | ALL_DOORS_CMD
|
||||||
reqUrl := MOBILE_API_VERSION + urlToGen(apiURLs["API_UNLOCK"], v.getAPIGen())
|
reqUrl := MOBILE_API_VERSION + urlToGen(apiURLs["API_UNLOCK"], v.getAPIGen())
|
||||||
pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"]
|
pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"]
|
||||||
|
|
||||||
ch := make(chan string)
|
ch := make(chan string)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(ch)
|
defer close(ch)
|
||||||
@ -261,32 +201,38 @@ func (v *Vehicle) Unlock() (chan string, error) {
|
|||||||
|
|
||||||
// EngineStart
|
// EngineStart
|
||||||
// Sends a command to start engine and set climate control.
|
// Sends a command to start engine and set climate control.
|
||||||
func (v *Vehicle) EngineStart() (chan string, error) {
|
func (v *Vehicle) EngineStart(run, delay int, horn bool) (chan string, error) {
|
||||||
if !v.getRemoteOptionsStatus() {
|
if slices.Contains([]int{0, 1, 5, 10}, run) {
|
||||||
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
return nil, errors.New("run time must be 0, 1, 5 or 10 minutes")
|
||||||
return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
}
|
||||||
|
|
||||||
|
var startConfig string
|
||||||
|
if v.EV {
|
||||||
|
startConfig = START_CONFIG_DEFAULT_EV
|
||||||
|
} else {
|
||||||
|
startConfig = START_CONFIG_DEFAULT_RES
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Get Quick Climate Preset from the Currect Car
|
|
||||||
params := map[string]string{
|
params := map[string]string{
|
||||||
"delay": "0",
|
"delay": strconv.Itoa(delay),
|
||||||
"vin": v.Vin,
|
"vin": v.Vin,
|
||||||
"pin": v.client.credentials.PIN,
|
"pin": v.client.credentials.PIN,
|
||||||
"horn": "true",
|
"horn": strconv.FormatBool(horn),
|
||||||
"climateSettings": "climateSettings", // climateSettings
|
"climateSettings": "climateSettings", // climateSettings
|
||||||
"climateZoneFrontTemp": "65", // 60-86
|
"climateZoneFrontTemp": "65", // 60-86
|
||||||
"climateZoneFrontAirMode": "WINDOW", // FEET_FACE_BALANCED | FEET_WINDOW | WINDOW | FEET
|
"climateZoneFrontAirMode": "FEET_WINDOW", // FEET_FACE_BALANCED | FEET_WINDOW | WINDOW | FEET
|
||||||
"climateZoneFrontAirVolume": "6", // 1-7
|
"climateZoneFrontAirVolume": "7", // 1-7
|
||||||
"heatedSeatFrontLeft": "OFF", // OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT | low_cool | medium_cool | high_cool
|
"heatedSeatFrontLeft": "HIGH_COOL", // OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT | LOW_COOL | MEDIUM_COOL | HIGH_COOL
|
||||||
"heatedSeatFrontRight": "OFF", // ---//---
|
"heatedSeatFrontRight": "HIGH_COOL", // ---//---
|
||||||
"heatedRearWindowActive": "true", // boolean
|
"heatedRearWindowActive": "false", // boolean
|
||||||
"outerAirCirculation": "outsideAir", // outsideAir | recirculation
|
"outerAirCirculation": "outsideAir", // outsideAir | recirculation
|
||||||
"airConditionOn": "false", // boolean
|
"airConditionOn": "true", // boolean
|
||||||
"runTimeMinutes": "10", // 1-10
|
"runTimeMinutes": strconv.Itoa(run), // 1-10
|
||||||
"startConfiguration": START_CONFIG_DEFAULT_RES, // START_ENGINE_ALLOW_KEY_IN_IGNITION | ONLY FOR PHEV > START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION
|
"startConfiguration": startConfig, // START_ENGINE_ALLOW_KEY_IN_IGNITION | ONLY FOR PHEV > START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION
|
||||||
}
|
}
|
||||||
reqUrl := MOBILE_API_VERSION + apiURLs["API_G2_REMOTE_ENGINE_START"]
|
reqUrl := MOBILE_API_VERSION + apiURLs["API_G2_REMOTE_ENGINE_START"]
|
||||||
pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"]
|
pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"]
|
||||||
|
|
||||||
ch := make(chan string)
|
ch := make(chan string)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(ch)
|
defer close(ch)
|
||||||
@ -299,11 +245,6 @@ func (v *Vehicle) EngineStart() (chan string, error) {
|
|||||||
// EngineStop
|
// EngineStop
|
||||||
// Sends a command to stop engine.
|
// Sends a command to stop engine.
|
||||||
func (v *Vehicle) EngineStop() (chan string, error) {
|
func (v *Vehicle) EngineStop() (chan string, error) {
|
||||||
if !v.getRemoteOptionsStatus() {
|
|
||||||
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
}
|
|
||||||
|
|
||||||
params := map[string]string{
|
params := map[string]string{
|
||||||
"delay": "0",
|
"delay": "0",
|
||||||
"vin": v.Vin,
|
"vin": v.Vin,
|
||||||
@ -323,11 +264,6 @@ func (v *Vehicle) EngineStop() (chan string, error) {
|
|||||||
// LightsStart
|
// LightsStart
|
||||||
// Sends a command to flash lights.
|
// Sends a command to flash lights.
|
||||||
func (v *Vehicle) LightsStart() (chan string, error) {
|
func (v *Vehicle) LightsStart() (chan string, error) {
|
||||||
if !v.getRemoteOptionsStatus() {
|
|
||||||
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
}
|
|
||||||
|
|
||||||
params := map[string]string{
|
params := map[string]string{
|
||||||
"delay": "0",
|
"delay": "0",
|
||||||
"vin": v.Vin,
|
"vin": v.Vin,
|
||||||
@ -337,22 +273,19 @@ func (v *Vehicle) LightsStart() (chan string, error) {
|
|||||||
if v.getAPIGen() == FEATURE_G1_TELEMATICS {
|
if v.getAPIGen() == FEATURE_G1_TELEMATICS {
|
||||||
pollingUrl = MOBILE_API_VERSION + apiURLs["API_G1_HORN_LIGHTS_STATUS"]
|
pollingUrl = MOBILE_API_VERSION + apiURLs["API_G1_HORN_LIGHTS_STATUS"]
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan string)
|
ch := make(chan string)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(ch)
|
defer close(ch)
|
||||||
v.executeServiceRequest(params, reqUrl, pollingUrl, ch, 1)
|
v.executeServiceRequest(params, reqUrl, pollingUrl, ch, 1)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return ch, nil
|
return ch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LightsStop
|
// LightsStop
|
||||||
// Sends a command to stop flash lights.
|
// Sends a command to stop flash lights.
|
||||||
func (v *Vehicle) LightsStop() (chan string, error) {
|
func (v *Vehicle) LightsStop() (chan string, error) {
|
||||||
if !v.getRemoteOptionsStatus() {
|
|
||||||
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
}
|
|
||||||
|
|
||||||
params := map[string]string{
|
params := map[string]string{
|
||||||
"delay": "0",
|
"delay": "0",
|
||||||
"vin": v.Vin,
|
"vin": v.Vin,
|
||||||
@ -362,6 +295,7 @@ func (v *Vehicle) LightsStop() (chan string, error) {
|
|||||||
if v.getAPIGen() == FEATURE_G1_TELEMATICS {
|
if v.getAPIGen() == FEATURE_G1_TELEMATICS {
|
||||||
pollingUrl = MOBILE_API_VERSION + apiURLs["API_G1_HORN_LIGHTS_STATUS"]
|
pollingUrl = MOBILE_API_VERSION + apiURLs["API_G1_HORN_LIGHTS_STATUS"]
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan string)
|
ch := make(chan string)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(ch)
|
defer close(ch)
|
||||||
@ -374,11 +308,6 @@ func (v *Vehicle) LightsStop() (chan string, error) {
|
|||||||
// HornStart
|
// HornStart
|
||||||
// Send command to sound horn.
|
// Send command to sound horn.
|
||||||
func (v *Vehicle) HornStart() (chan string, error) {
|
func (v *Vehicle) HornStart() (chan string, error) {
|
||||||
if !v.getRemoteOptionsStatus() {
|
|
||||||
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
}
|
|
||||||
|
|
||||||
params := map[string]string{
|
params := map[string]string{
|
||||||
"delay": "0",
|
"delay": "0",
|
||||||
"vin": v.Vin,
|
"vin": v.Vin,
|
||||||
@ -388,6 +317,7 @@ func (v *Vehicle) HornStart() (chan string, error) {
|
|||||||
if v.getAPIGen() == FEATURE_G1_TELEMATICS {
|
if v.getAPIGen() == FEATURE_G1_TELEMATICS {
|
||||||
pollingUrl = MOBILE_API_VERSION + apiURLs["API_G1_HORN_LIGHTS_STATUS"]
|
pollingUrl = MOBILE_API_VERSION + apiURLs["API_G1_HORN_LIGHTS_STATUS"]
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan string)
|
ch := make(chan string)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(ch)
|
defer close(ch)
|
||||||
@ -400,11 +330,6 @@ func (v *Vehicle) HornStart() (chan string, error) {
|
|||||||
// HornStop
|
// HornStop
|
||||||
// Send command to sound horn.
|
// Send command to sound horn.
|
||||||
func (v *Vehicle) HornStop() (chan string, error) {
|
func (v *Vehicle) HornStop() (chan string, error) {
|
||||||
if !v.getRemoteOptionsStatus() {
|
|
||||||
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
}
|
|
||||||
|
|
||||||
params := map[string]string{
|
params := map[string]string{
|
||||||
"delay": "0",
|
"delay": "0",
|
||||||
"vin": v.Vin,
|
"vin": v.Vin,
|
||||||
@ -414,6 +339,7 @@ func (v *Vehicle) HornStop() (chan string, error) {
|
|||||||
if v.getAPIGen() == FEATURE_G1_TELEMATICS {
|
if v.getAPIGen() == FEATURE_G1_TELEMATICS {
|
||||||
pollingUrl = MOBILE_API_VERSION + apiURLs["API_G1_HORN_LIGHTS_STATUS"]
|
pollingUrl = MOBILE_API_VERSION + apiURLs["API_G1_HORN_LIGHTS_STATUS"]
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan string)
|
ch := make(chan string)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(ch)
|
defer close(ch)
|
||||||
@ -425,10 +351,6 @@ func (v *Vehicle) HornStop() (chan string, error) {
|
|||||||
|
|
||||||
// ChargeStart
|
// ChargeStart
|
||||||
func (v *Vehicle) ChargeOn() (chan string, error) {
|
func (v *Vehicle) ChargeOn() (chan string, error) {
|
||||||
if !v.getRemoteOptionsStatus() {
|
|
||||||
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
}
|
|
||||||
if v.isEV() {
|
if v.isEV() {
|
||||||
params := map[string]string{
|
params := map[string]string{
|
||||||
"delay": "0",
|
"delay": "0",
|
||||||
@ -442,18 +364,12 @@ func (v *Vehicle) ChargeOn() (chan string, error) {
|
|||||||
v.executeServiceRequest(params, reqUrl, pollingUrl, ch, 1)
|
v.executeServiceRequest(params, reqUrl, pollingUrl, ch, 1)
|
||||||
}()
|
}()
|
||||||
return ch, nil
|
return ch, nil
|
||||||
} else {
|
|
||||||
return nil, errors.New("not an EV car")
|
|
||||||
}
|
}
|
||||||
|
return nil, errors.New("not an EV car")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLocation
|
// GetLocation
|
||||||
func (v *Vehicle) GetLocation(force bool) (chan string, error) {
|
func (v *Vehicle) GetLocation(force bool) (chan string, error) {
|
||||||
if !v.getRemoteOptionsStatus() {
|
|
||||||
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
|
||||||
}
|
|
||||||
|
|
||||||
var reqUrl, pollingUrl string
|
var reqUrl, pollingUrl string
|
||||||
var params map[string]string
|
var params map[string]string
|
||||||
if force { // Sends a locate command to the vehicle to get real time position
|
if force { // Sends a locate command to the vehicle to get real time position
|
||||||
@ -491,6 +407,13 @@ func (v *Vehicle) GetClimatePresets() error {
|
|||||||
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate session before executing the request
|
||||||
|
if !v.client.validateSession() {
|
||||||
|
v.client.logger.Error(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
return errors.New(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
}
|
||||||
|
|
||||||
if v.Vin != (v.client).currentVin {
|
if v.Vin != (v.client).currentVin {
|
||||||
v.selectVehicle()
|
v.selectVehicle()
|
||||||
}
|
}
|
||||||
@ -550,6 +473,13 @@ func (v *Vehicle) GetClimateQuickPresets() error {
|
|||||||
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate session before executing the request
|
||||||
|
if !v.client.validateSession() {
|
||||||
|
v.client.logger.Error(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
return errors.New(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
}
|
||||||
|
|
||||||
if v.Vin != (v.client).currentVin {
|
if v.Vin != (v.client).currentVin {
|
||||||
v.selectVehicle()
|
v.selectVehicle()
|
||||||
}
|
}
|
||||||
@ -580,12 +510,60 @@ func (v *Vehicle) GetClimateQuickPresets() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateClimateQuickPresets
|
||||||
|
// Updates the quick climate presets by fetching them from the MySubaru API.
|
||||||
|
// {"success":true,"data":null}
|
||||||
|
func (v *Vehicle) UpdateClimateQuickPresets() error {
|
||||||
|
if !v.getRemoteOptionsStatus() {
|
||||||
|
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
|
return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate session before executing the request
|
||||||
|
if !v.client.validateSession() {
|
||||||
|
v.client.logger.Error(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
return errors.New(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Vin != (v.client).currentVin {
|
||||||
|
v.selectVehicle()
|
||||||
|
}
|
||||||
|
|
||||||
|
params := map[string]string{
|
||||||
|
"name": "Cooling",
|
||||||
|
"runTimeMinutes": "10",
|
||||||
|
"climateSettings": "climateSettings", // climateSettings
|
||||||
|
"climateZoneFrontTemp": "65", // 60-86
|
||||||
|
"climateZoneFrontAirMode": "FEET_WINDOW", // FEET_FACE_BALANCED | FEET_WINDOW | WINDOW | FEET
|
||||||
|
"climateZoneFrontAirVolume": "7", // 1-7
|
||||||
|
"heatedSeatFrontLeft": "HIGH_COOL", // OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT | LOW_COOL | MEDIUM_COOL | HIGH_COOL
|
||||||
|
"heatedSeatFrontRight": "HIGH_COOL", // ---//---
|
||||||
|
"heatedRearWindowActive": "false", // boolean
|
||||||
|
"outerAirCirculation": "outsideAir", // outsideAir | recirculation
|
||||||
|
"airConditionOn": "false", // boolean
|
||||||
|
"startConfiguration": "START_ENGINE_ALLOW_KEY_IN_IGNITION", // START_ENGINE_ALLOW_KEY_IN_IGNITION | ONLY FOR PHEV > START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION
|
||||||
|
}
|
||||||
|
reqUrl := MOBILE_API_VERSION + apiURLs["API_G2_SAVE_RES_QUICK_START_SETTINGS"]
|
||||||
|
resp, _ := v.client.execute(POST, reqUrl, params, true)
|
||||||
|
|
||||||
|
v.client.logger.Debug("http request output", "request", "UpdateClimateUserPresets", "body", resp)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetClimateUserPresets
|
// GetClimateUserPresets
|
||||||
func (v *Vehicle) GetClimateUserPresets() error {
|
func (v *Vehicle) GetClimateUserPresets() error {
|
||||||
if !v.getRemoteOptionsStatus() {
|
if !v.getRemoteOptionsStatus() {
|
||||||
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate session before executing the request
|
||||||
|
if !v.client.validateSession() {
|
||||||
|
v.client.logger.Error(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
return errors.New(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
}
|
||||||
|
|
||||||
if v.Vin != (v.client).currentVin {
|
if v.Vin != (v.client).currentVin {
|
||||||
v.selectVehicle()
|
v.selectVehicle()
|
||||||
}
|
}
|
||||||
@ -625,12 +603,63 @@ func (v *Vehicle) GetClimateUserPresets() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateClimateUserPresets
|
||||||
|
// Updates the user's climate presets by fetching them from the MySubaru API.
|
||||||
|
func (v *Vehicle) UpdateClimateUserPresets() error {
|
||||||
|
if !v.getRemoteOptionsStatus() {
|
||||||
|
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
|
return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate session before executing the request
|
||||||
|
if !v.client.validateSession() {
|
||||||
|
v.client.logger.Error(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
return errors.New(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Vin != (v.client).currentVin {
|
||||||
|
v.selectVehicle()
|
||||||
|
}
|
||||||
|
params := map[string]string{
|
||||||
|
"presetType": "userPreset",
|
||||||
|
"name": "Cooling",
|
||||||
|
"runTimeMinutes": "10",
|
||||||
|
"climateZoneFrontTemp": "65",
|
||||||
|
"climateZoneFrontAirMode": "FEET_FACE_BALANCED",
|
||||||
|
"climateZoneFrontAirVolume": "7",
|
||||||
|
"outerAirCirculation": "outsideAir",
|
||||||
|
"heatedRearWindowActive": "false",
|
||||||
|
"heatedSeatFrontLeft": "HIGH_COOL",
|
||||||
|
"airConditionOn": "false",
|
||||||
|
"startConfiguration": "START_ENGINE_ALLOW_KEY_IN_IGNITION",
|
||||||
|
// "canEdit": "true",
|
||||||
|
// "disabled": "false",
|
||||||
|
}
|
||||||
|
reqUrl := MOBILE_API_VERSION + apiURLs["API_G2_SAVE_RES_SETTINGS"]
|
||||||
|
resp, _ := v.client.execute(POST, reqUrl, params, false)
|
||||||
|
|
||||||
|
v.client.logger.Debug("http request output", "request", "UpdateClimateUserPresets", "body", resp)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Vehicle) DeleteClimateUserPresets() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetVehicleStatus .
|
// GetVehicleStatus .
|
||||||
func (v *Vehicle) GetVehicleStatus() error {
|
func (v *Vehicle) GetVehicleStatus() error {
|
||||||
if !v.getRemoteOptionsStatus() {
|
if !v.getRemoteOptionsStatus() {
|
||||||
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate session before executing the request
|
||||||
|
if !v.client.validateSession() {
|
||||||
|
v.client.logger.Error(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
return errors.New(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
}
|
||||||
|
|
||||||
if v.Vin != (v.client).currentVin {
|
if v.Vin != (v.client).currentVin {
|
||||||
v.selectVehicle()
|
v.selectVehicle()
|
||||||
}
|
}
|
||||||
@ -692,6 +721,13 @@ func (v *Vehicle) GetVehicleCondition() error {
|
|||||||
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate session before executing the request
|
||||||
|
if !v.client.validateSession() {
|
||||||
|
v.client.logger.Error(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
return errors.New(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
}
|
||||||
|
|
||||||
if v.Vin != (v.client).currentVin {
|
if v.Vin != (v.client).currentVin {
|
||||||
v.selectVehicle()
|
v.selectVehicle()
|
||||||
}
|
}
|
||||||
@ -735,12 +771,20 @@ func (v *Vehicle) GetVehicleCondition() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVehicleHealth .
|
// GetVehicleHealth
|
||||||
|
// Retrieves the vehicle health status from MySubaru API.
|
||||||
func (v *Vehicle) GetVehicleHealth() error {
|
func (v *Vehicle) GetVehicleHealth() error {
|
||||||
if !v.getRemoteOptionsStatus() {
|
if !v.getRemoteOptionsStatus() {
|
||||||
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate session before executing the request
|
||||||
|
if !v.client.validateSession() {
|
||||||
|
v.client.logger.Error(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
return errors.New(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
}
|
||||||
|
|
||||||
if v.Vin != (v.client).currentVin {
|
if v.Vin != (v.client).currentVin {
|
||||||
v.selectVehicle()
|
v.selectVehicle()
|
||||||
}
|
}
|
||||||
@ -785,15 +829,27 @@ func (v *Vehicle) GetFeaturesList() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// executeServiceRequest
|
// executeServiceRequest
|
||||||
|
// Executes a service request to the Subaru API and handles the response.
|
||||||
func (v *Vehicle) executeServiceRequest(params map[string]string, reqUrl, pollingUrl string, ch chan string, attempt int) error {
|
func (v *Vehicle) executeServiceRequest(params map[string]string, reqUrl, pollingUrl string, ch chan string, attempt int) error {
|
||||||
var maxAttempts = 15
|
var maxAttempts = 15
|
||||||
|
|
||||||
if attempt >= maxAttempts {
|
if attempt >= maxAttempts {
|
||||||
v.client.logger.Error("maximum attempts reached for service request", "request", reqUrl, "attempts", attempt)
|
v.client.logger.Error("maximum attempts reached for service request", "request", reqUrl, "attempts", attempt)
|
||||||
ch <- "error"
|
ch <- "error"
|
||||||
return errors.New("maximum attempts reached for service request")
|
return errors.New("maximum attempts reached for service request")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the vehicle has a valid subscription for remote services
|
||||||
|
if !v.getRemoteOptionsStatus() {
|
||||||
|
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
|
return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate session before executing the request
|
||||||
|
if !v.client.validateSession() {
|
||||||
|
v.client.logger.Error(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
return errors.New(APP_ERRORS["SESSION_EXPIRED"])
|
||||||
|
}
|
||||||
|
|
||||||
if v.Vin != v.client.currentVin {
|
if v.Vin != v.client.currentVin {
|
||||||
v.selectVehicle()
|
v.selectVehicle()
|
||||||
}
|
}
|
||||||
@ -828,16 +884,16 @@ func (v *Vehicle) executeServiceRequest(params map[string]string, reqUrl, pollin
|
|||||||
|
|
||||||
case "started":
|
case "started":
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
v.client.logger.Debug("Subaru API reports remote service request (started) is in progress", "id", sr.ServiceRequestID)
|
v.client.logger.Debug("MySubaru API reports remote service request (started) is in progress", "id", sr.ServiceRequestID)
|
||||||
v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqUrl, pollingUrl, ch, attempt+1)
|
v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqUrl, pollingUrl, ch, attempt+1)
|
||||||
|
|
||||||
case "stopping":
|
case "stopping":
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
v.client.logger.Debug("Subaru API reports remote service request (stopping) is in progress", "id", sr.ServiceRequestID)
|
v.client.logger.Debug("MySubaru API reports remote service request (stopping) is in progress", "id", sr.ServiceRequestID)
|
||||||
v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqUrl, pollingUrl, ch, attempt+1)
|
v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqUrl, pollingUrl, ch, attempt+1)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
v.client.logger.Debug("Subaru API reports remote service request (default)")
|
v.client.logger.Debug("MySubaru API reports remote service request (default)")
|
||||||
v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqUrl, pollingUrl, ch, attempt+1)
|
v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqUrl, pollingUrl, ch, attempt+1)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -871,7 +927,7 @@ func (v *Vehicle) selectVehicle() {
|
|||||||
v.GeoLocation.Longitude = vData.VehicleGeoPosition.Longitude
|
v.GeoLocation.Longitude = vData.VehicleGeoPosition.Longitude
|
||||||
v.GeoLocation.Heading = vData.VehicleGeoPosition.Heading
|
v.GeoLocation.Heading = vData.VehicleGeoPosition.Heading
|
||||||
v.GeoLocation.Speed = vData.VehicleGeoPosition.Speed
|
v.GeoLocation.Speed = vData.VehicleGeoPosition.Speed
|
||||||
v.GeoLocation.Updated = time.Now()
|
v.GeoLocation.Updated = vData.VehicleGeoPosition.Timestamp
|
||||||
v.Updated = time.Now()
|
v.Updated = time.Now()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -891,12 +947,6 @@ func (v *Vehicle) getAPIGen() string {
|
|||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
// isPINRequired .
|
|
||||||
// Return if a vehicle with an active remote service subscription exists.
|
|
||||||
// func (v *Vehicle) isPINRequired() bool {
|
|
||||||
// return v.getRemoteOptionsStatus()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// isEV .
|
// isEV .
|
||||||
// Get whether the specified car is an Electric Vehicle.
|
// Get whether the specified car is an Electric Vehicle.
|
||||||
func (v *Vehicle) isEV() bool {
|
func (v *Vehicle) isEV() bool {
|
||||||
@ -1014,27 +1064,3 @@ func (v *Vehicle) parseParts(name string, value any) {
|
|||||||
// func (v *Vehicle) getSubscriptionStatus() bool {
|
// func (v *Vehicle) getSubscriptionStatus() bool {
|
||||||
// return slices.Contains(v.SubscriptionFeatures, FEATURE_ACTIVE)
|
// return slices.Contains(v.SubscriptionFeatures, FEATURE_ACTIVE)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // getVehicleName .
|
|
||||||
// // Get the nickname of a specified VIN.
|
|
||||||
// func (v *Vehicle) getVehicleName() string {
|
|
||||||
// return v.CarName
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func getClimateData() {}
|
|
||||||
// func saveClimateSettings() {}
|
|
||||||
|
|
||||||
// "vhsId": 914631252,
|
|
||||||
|
|
||||||
// "odometerValue": 23865,
|
|
||||||
// "odometerValueKilometers": 38399,
|
|
||||||
|
|
||||||
// "tirePressureFrontLeft": "2344",
|
|
||||||
// "tirePressureFrontRight": "2344",
|
|
||||||
// "tirePressureRearLeft": "2413",
|
|
||||||
// "tirePressureRearRight": "2344",
|
|
||||||
|
|
||||||
// "tirePressureFrontLeftPsi": "34",
|
|
||||||
// "tirePressureFrontRightPsi": "34",
|
|
||||||
// "tirePressureRearLeftPsi": "35",
|
|
||||||
// "tirePressureRearRightPsi": "34",
|
|
||||||
|
@ -15,6 +15,12 @@ func TestGetClimatePresets_Success(t *testing.T) {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","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","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`)
|
fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","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","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`)
|
||||||
}
|
}
|
||||||
|
// Handle API_VALIDATE_SESSION endpoint
|
||||||
|
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_VALIDATE_SESSION"] && r.Method == http.MethodGet {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":null,"data":null}`)
|
||||||
|
}
|
||||||
// Handle SELECT_VEHICLE endpoint
|
// Handle SELECT_VEHICLE endpoint
|
||||||
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet {
|
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
@ -59,6 +65,12 @@ func TestGetClimateQuickPresets_Success(t *testing.T) {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","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","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`)
|
fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","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","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`)
|
||||||
}
|
}
|
||||||
|
// Handle API_VALIDATE_SESSION endpoint
|
||||||
|
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_VALIDATE_SESSION"] && r.Method == http.MethodGet {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":null,"data":null}`)
|
||||||
|
}
|
||||||
// Handle SELECT_VEHICLE endpoint
|
// Handle SELECT_VEHICLE endpoint
|
||||||
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet {
|
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
@ -103,6 +115,12 @@ func TestGetClimateUserPresets_Success(t *testing.T) {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","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","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`)
|
fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","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","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`)
|
||||||
}
|
}
|
||||||
|
// Handle API_VALIDATE_SESSION endpoint
|
||||||
|
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_VALIDATE_SESSION"] && r.Method == http.MethodGet {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":null,"data":null}`)
|
||||||
|
}
|
||||||
// Handle SELECT_VEHICLE endpoint
|
// Handle SELECT_VEHICLE endpoint
|
||||||
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet {
|
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
@ -147,6 +165,12 @@ func TestGetVehicleStatus_Success(t *testing.T) {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","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","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`)
|
fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","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","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`)
|
||||||
}
|
}
|
||||||
|
// Handle API_VALIDATE_SESSION endpoint
|
||||||
|
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_VALIDATE_SESSION"] && r.Method == http.MethodGet {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":null,"data":null}`)
|
||||||
|
}
|
||||||
// Handle SELECT_VEHICLE endpoint
|
// Handle SELECT_VEHICLE endpoint
|
||||||
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet {
|
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
@ -191,6 +215,12 @@ func TestGetVehicleCondition_Success(t *testing.T) {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","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","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`)
|
fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","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","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`)
|
||||||
}
|
}
|
||||||
|
// Handle API_VALIDATE_SESSION endpoint
|
||||||
|
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_VALIDATE_SESSION"] && r.Method == http.MethodGet {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":null,"data":null}`)
|
||||||
|
}
|
||||||
// Handle SELECT_VEHICLE endpoint
|
// Handle SELECT_VEHICLE endpoint
|
||||||
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet {
|
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
@ -235,6 +265,12 @@ func TestGetVehicleHealth_Success(t *testing.T) {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","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","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`)
|
fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","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","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`)
|
||||||
}
|
}
|
||||||
|
// Handle API_VALIDATE_SESSION endpoint
|
||||||
|
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_VALIDATE_SESSION"] && r.Method == http.MethodGet {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":null,"data":null}`)
|
||||||
|
}
|
||||||
// Handle SELECT_VEHICLE endpoint
|
// Handle SELECT_VEHICLE endpoint
|
||||||
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet {
|
if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
Reference in New Issue
Block a user