first commit
This commit is contained in:
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
coverage.out
|
||||
coverage.txt
|
||||
go.sum
|
||||
|
||||
.tmp
|
||||
|
||||
config.yaml
|
||||
|
||||
go-mysubaru.code-workspace
|
35
README.md
Normal file
35
README.md
Normal file
@ -0,0 +1,35 @@
|
||||
# MySubaru!GO
|
||||
Is a simple API client to interact with My Subaru service (https://www.mysubaru.com/) via HTTP, written in Go
|
||||
|
||||
## News
|
||||
* v0.0.0-dev First public release on April 11, 2023
|
||||
|
||||
## Features
|
||||
* Simple and chainable methods for settings and request
|
||||
|
||||
## Installation
|
||||
```bash
|
||||
# Go Modules
|
||||
go get github.com/alex-savin/go-mysubaru
|
||||
```
|
||||
|
||||
## Usage
|
||||
The following samples will assist you to become as comfortable as possible with mysubaru library.
|
||||
```go
|
||||
// Import hassky into your code and refer it as `mysubaru`.
|
||||
import "github.com/alex-savin/go-mysubaru"
|
||||
```
|
||||
|
||||
#### Create a new MySubaru connection and get a car by VIN
|
||||
```go
|
||||
// Create a MySubaru Client
|
||||
mysubaru, _ := New()
|
||||
outback := mysubaru.GetVehicleByVIN("VIN-CODE-HERE")
|
||||
```
|
||||
|
||||
#### Start/Stop Lights request
|
||||
```go
|
||||
outback.LightsStart()
|
||||
time.Sleep(30 * time.Second)
|
||||
outback.LightsStop()
|
||||
```
|
882
client.go
Normal file
882
client.go
Normal file
@ -0,0 +1,882 @@
|
||||
package mysubaru
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Jeffail/gabs/v2"
|
||||
"github.com/alex-savin/go-mysubaru/config"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
// credentials .
|
||||
type credentials struct {
|
||||
username string
|
||||
password string
|
||||
pin string
|
||||
deviceId string
|
||||
deviceName string
|
||||
}
|
||||
|
||||
// Client .
|
||||
type Client struct {
|
||||
baseURL string
|
||||
credentials credentials
|
||||
httpClient *resty.Client
|
||||
cookies []*http.Cookie
|
||||
country string //
|
||||
updateInterval int // 7200
|
||||
fetchInterval int // 360
|
||||
currentVin string
|
||||
listOfVins []string
|
||||
isAuthenticated bool
|
||||
isRegistered bool
|
||||
log *slog.Logger
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// Option .
|
||||
type Option func(*Client) error
|
||||
|
||||
// BaseURL allows overriding of API client baseURL for testing
|
||||
func BaseURL(baseURL string) Option {
|
||||
return func(c *Client) error {
|
||||
c.baseURL = baseURL
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Username .
|
||||
func Username(username string) Option {
|
||||
return func(c *Client) error {
|
||||
c.credentials.username = username
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Password .
|
||||
func Password(password string) Option {
|
||||
return func(c *Client) error {
|
||||
c.credentials.password = password
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// auth .
|
||||
func (c *Client) auth() []byte {
|
||||
// {
|
||||
// "success": true,
|
||||
// "errorCode": null,
|
||||
// "dataName": "sessionData",
|
||||
// "data": {
|
||||
// "sessionChanged": false,
|
||||
// "vehicleInactivated": false,
|
||||
// "account": {
|
||||
// "marketId": 1,
|
||||
// "createdDate": 1476984644000,
|
||||
// "firstName": "Tatiana",
|
||||
// "lastName": "Savin",
|
||||
// "zipCode": "07974",
|
||||
// "accountKey": 765268,
|
||||
// "lastLoginDate": 1640539132000,
|
||||
// "zipCode5": "07974"
|
||||
// },
|
||||
// "resetPassword": false,
|
||||
// "deviceId": "JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ",
|
||||
// "sessionId": "010A2C0C25A5D35AC3776DB6B7900A3B",
|
||||
// "deviceRegistered": true,
|
||||
// "passwordToken": null,
|
||||
// "vehicles": [
|
||||
// {
|
||||
// "customer": {
|
||||
// "sessionCustomer": null,
|
||||
// "email": null,
|
||||
// "firstName": null,
|
||||
// "lastName": null,
|
||||
// "zip": null,
|
||||
// "oemCustId": null,
|
||||
// "phone": null
|
||||
// },
|
||||
// "stolenVehicle": false,
|
||||
// "vehicleName": "Subaru Outback LXT",
|
||||
// "features": null,
|
||||
// "vin": "4S4BTGND8L3137058",
|
||||
// "modelYear": null,
|
||||
// "modelCode": null,
|
||||
// "engineSize": null,
|
||||
// "nickname": "Subaru Outback LXT",
|
||||
// "vehicleKey": 3832950,
|
||||
// "active": true,
|
||||
// "licensePlate": "",
|
||||
// "licensePlateState": "",
|
||||
// "email": null,
|
||||
// "firstName": null,
|
||||
// "lastName": null,
|
||||
// "subscriptionFeatures": null,
|
||||
// "accessLevel": -1,
|
||||
// "zip": null,
|
||||
// "oemCustId": "CRM-631-HQN48K",
|
||||
// "vehicleMileage": null,
|
||||
// "phone": null,
|
||||
// "userOemCustId": "CRM-631-HQN48K",
|
||||
// "subscriptionStatus": null,
|
||||
// "authorizedVehicle": false,
|
||||
// "preferredDealer": null,
|
||||
// "cachedStateCode": "NJ",
|
||||
// "subscriptionPlans": [],
|
||||
// "needMileagePrompt": false,
|
||||
// "phev": null,
|
||||
// "remoteServicePinExist": true,
|
||||
// "needEmergencyContactPrompt": false,
|
||||
// "vehicleGeoPosition": null,
|
||||
// "extDescrip": null,
|
||||
// "intDescrip": null,
|
||||
// "modelName": null,
|
||||
// "transCode": null,
|
||||
// "provisioned": true,
|
||||
// "timeZone": "America/New_York"
|
||||
// }
|
||||
// ],
|
||||
// "currentVehicleIndex": 0,
|
||||
// "handoffToken": "$2a$08$99me3RRpB00MNMSFxrG7AOg1T5BaDVacqXhbdTii0eRXoEoeFvEPy$1640540934344",
|
||||
// "enableXtime": true,
|
||||
// "termsAndConditionsAccepted": true,
|
||||
// "digitalGlobeConnectId": "0572e32b-2fcf-4bc8-abe0-1e3da8767132",
|
||||
// "digitalGlobeImageTileService": "https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/DigitalGlobe:ImageryTileService@EPSG:3857@png/{z}/{x}/{y}.png?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132",
|
||||
// "digitalGlobeTransparentTileService": "https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/Digitalglobe:OSMTransparentTMSTileService@EPSG:3857@png/{z}/{x}/{-y}.png/?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132",
|
||||
// "tomtomKey": "DHH9SwEQ4MW55Hj2TfqMeldbsDjTdgAs",
|
||||
// "satelliteViewEnabled": true
|
||||
// }
|
||||
// }
|
||||
params := map[string]string{
|
||||
"env": "cloudprod",
|
||||
"deviceType": "android",
|
||||
"loginUsername": c.credentials.username,
|
||||
"password": c.credentials.password,
|
||||
"deviceId": c.credentials.deviceId,
|
||||
"passwordToken": "",
|
||||
"selectedVin": "",
|
||||
"pushToken": ""}
|
||||
reqURL := MOBILE_API_VERSION + apiURLs["API_LOGIN"]
|
||||
resp := c.execute(reqURL, POST, params, "", false)
|
||||
|
||||
c.log.Debug("AUTH HTTP OUTPUT", "body", string([]byte(resp)))
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// GET
|
||||
// https://www.mysubaru.com/profile/verifyDeviceName.json?clientId=2574212&deviceName=Alex%20Google%20Pixel%204%20XL
|
||||
// RESP: true/false
|
||||
|
||||
// POST
|
||||
// https://www.mysubaru.com/profile/editDeviceName.json?clientId=2574212&deviceName=Alex%20Google%20Pixel%204%20XL
|
||||
// clientId: 2574212
|
||||
// deviceName: Alex Google Pixel 4 XL
|
||||
// RESP: true/false
|
||||
|
||||
// GET
|
||||
// https://www.mysubaru.com/listMyDevices.json
|
||||
// {"success":true,"dataName":"authorizedDevices","data":[{"telematicsClientDeviceKey":2574212,"deviceType":"android","deviceName":"Alex Google Pixel 4 XL","createdDate":"2019-11-29T20:32:21.000+0000","modifiedDate":"2020-06-08T17:48:22.000+0000"},{"telematicsClientDeviceKey":4847533,"deviceName":"Home Assistant: Added 2021-03-03","createdDate":"2021-03-03T20:53:44.000+0000","modifiedDate":"2021-03-03T20:53:47.000+0000"},{"telematicsClientDeviceKey":7222995,"deviceType":"android","deviceName":"Alex Google Pixel 6 Pro","createdDate":"2021-10-28T15:27:36.000+0000","modifiedDate":"2021-10-28T15:27:58.000+0000"},{"telematicsClientDeviceKey":8207130,"deviceName":"Mac/iOS Chrome","createdDate":"2021-12-21T21:19:40.000+0000","modifiedDate":"2021-12-21T21:19:40.000+0000"}]}
|
||||
// {"success":true,"dataName":"authorizedDevices","data":[{"telematicsClientDeviceKey":2574212,"deviceType":"android","deviceName":"Alex Google Pixel 4 XL","createdDate":"2019-11-29T20:32:21.000+0000","modifiedDate":"2020-06-08T17:48:22.000+0000"},{"telematicsClientDeviceKey":4847533,"deviceName":"Home Assistant: Added 2021-03-03","createdDate":"2021-03-03T20:53:44.000+0000","modifiedDate":"2021-03-03T20:53:47.000+0000"},{"telematicsClientDeviceKey":7222995,"deviceType":"android","deviceName":"Alex Google Pixel 6 Pro","createdDate":"2021-10-28T15:27:36.000+0000","modifiedDate":"2021-10-28T15:27:58.000+0000"},{"telematicsClientDeviceKey":8210723,"deviceName":"Hassio Golang Integration","createdDate":"2021-12-22T01:38:43.000+0000","modifiedDate":"2021-12-22T01:38:43.000+0000"},{"telematicsClientDeviceKey":8207130,"deviceName":"Mac/iOS Chrome","createdDate":"2021-12-21T21:19:40.000+0000","modifiedDate":"2021-12-21T21:19:40.000+0000"}]}
|
||||
|
||||
// registerDevice .
|
||||
func (c *Client) registerDevice() bool {
|
||||
// c.httpClient.
|
||||
// SetBaseURL(WEB_API_SERVER[c.country]).
|
||||
// R().
|
||||
// SetFormData(map[string]string{
|
||||
// "username": c.credentials.username,
|
||||
// "password": c.credentials.password,
|
||||
// "deviceId": c.credentials.deviceId,
|
||||
// }).
|
||||
// Post(apiURLs["WEB_API_LOGIN"])
|
||||
params := map[string]string{
|
||||
"username": c.credentials.username,
|
||||
"password": c.credentials.password,
|
||||
"deviceId": c.credentials.deviceId}
|
||||
reqURL := WEB_API_SERVER[c.country] + apiURLs["WEB_API_LOGIN"]
|
||||
c.execute(reqURL, POST, params, "", true)
|
||||
|
||||
// Authorizing device via web API
|
||||
// c.httpClient.
|
||||
// SetBaseURL(WEB_API_SERVER[c.country]).
|
||||
// R().
|
||||
// SetQueryParams(map[string]string{
|
||||
// "deviceId": c.credentials.deviceId,
|
||||
// }).
|
||||
// Get(apiURLs["WEB_API_AUTHORIZE_DEVICE"])
|
||||
params = map[string]string{
|
||||
"deviceId": c.credentials.deviceId}
|
||||
reqURL = WEB_API_SERVER[c.country] + apiURLs["WEB_API_AUTHORIZE_DEVICE"]
|
||||
c.execute(reqURL, GET, params, "", false)
|
||||
|
||||
return c.setDeviceName()
|
||||
}
|
||||
|
||||
// setDeviceName .
|
||||
func (c *Client) setDeviceName() bool {
|
||||
params := map[string]string{
|
||||
"deviceId": c.credentials.deviceId,
|
||||
"deviceName": c.credentials.deviceName}
|
||||
reqURL := WEB_API_SERVER[c.country] + apiURLs["WEB_API_NAME_DEVICE"]
|
||||
c.execute(reqURL, GET, params, "", false)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// listDevices .
|
||||
func (c *Client) listDevices() {
|
||||
// Accept: application/json, text/javascript, */*; q=0.01
|
||||
// Accept-Encoding: gzip, deflate, br
|
||||
// Accept-Language: en-US,en;q=0.9,ru;q=0.8
|
||||
// Connection: keep-alive
|
||||
// Cookie: ORA_OTD_JROUTE=ozLwELf5jS-NHQ2CKZorOFfRgb8uo6lL; soa-visitor=12212021VHWnkqERZYThWe87TLUhr2Db; AMCVS_94001C8B532957140A490D4D%40AdobeOrg=1; mys-referringCodes=7~direct~; s_cc=true; AMCVS_subarucom%40AdobeOrg=1; style=null; s_pv=login.html; AMCV_subarucom%40AdobeOrg=-1124106680%7CMCIDTS%7C18988%7CMCMID%7C81535064704660726005836131001032500276%7CMCAID%7CNONE%7CMCOPTOUT-1640567559s%7CNONE%7CvVersion%7C5.2.0; AMCV_94001C8B532957140A490D4D%40AdobeOrg=-1124106680%7CMCIDTS%7C18988%7CMCMID%7C76913534164341455390435376071204508177%7CMCAID%7CNONE%7CMCOPTOUT-1640567559s%7CNONE%7CvVersion%7C5.2.0; s_sq=subarumysubarucwpprod%3D%2526c.%2526a.%2526activitymap.%2526page%253Dlogin.html%2526link%253DLog%252520In%2526region%253DloginForm%2526pageIDType%253D1%2526.activitymap%2526.a%2526.c%2526pid%253Dlogin.html%2526pidt%253D1%2526oid%253DLog%252520In%2526oidt%253D3%2526ot%253DSUBMIT; JSESSIONID=9685CFEB7888A0E6E25239D559E3B580; X-Oracle-BMC-LBS-Route=89e3283ece707e8a0ba4850e1a622122e039fd3d27da03a11a2ff120e313e9b656c62fd8a7c42ae8061a49ad6e1caf63a49d7befe4ad2a0194b0aeca
|
||||
// Host: www.mysubaru.com
|
||||
// Referer: https://www.mysubaru.com/profile/authorizedDevices.html
|
||||
// User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
|
||||
// X-Requested-With: XMLHttpRequest
|
||||
resp, err := c.httpClient.
|
||||
SetBaseURL(WEB_API_SERVER[c.country]).
|
||||
R().
|
||||
EnableTrace().
|
||||
Get(apiURLs["WEB_API_LIST_DEVICES"])
|
||||
|
||||
// Explore response object
|
||||
fmt.Println("Response Info:")
|
||||
fmt.Println(" Error :", err)
|
||||
fmt.Println(" Status Code:", resp.StatusCode())
|
||||
fmt.Println(" Status :", resp.Status())
|
||||
fmt.Println(" Proto :", resp.Proto())
|
||||
fmt.Println(" Time :", resp.Time())
|
||||
fmt.Println(" Received At:", resp.ReceivedAt())
|
||||
// fmt.Println(" Body :\n", resp)
|
||||
fmt.Println()
|
||||
|
||||
// Explore trace info
|
||||
fmt.Println("Request Trace Info:")
|
||||
ti := resp.Request.TraceInfo()
|
||||
fmt.Println(" DNSLookup :", ti.DNSLookup)
|
||||
fmt.Println(" ConnTime :", ti.ConnTime)
|
||||
fmt.Println(" TCPConnTime :", ti.TCPConnTime)
|
||||
fmt.Println(" TLSHandshake :", ti.TLSHandshake)
|
||||
fmt.Println(" ServerTime :", ti.ServerTime)
|
||||
fmt.Println(" ResponseTime :", ti.ResponseTime)
|
||||
fmt.Println(" TotalTime :", ti.TotalTime)
|
||||
fmt.Println(" IsConnReused :", ti.IsConnReused)
|
||||
fmt.Println(" IsConnWasIdle :", ti.IsConnWasIdle)
|
||||
fmt.Println(" ConnIdleTime :", ti.ConnIdleTime)
|
||||
fmt.Println(" RequestAttempt:", ti.RequestAttempt)
|
||||
fmt.Println(" RemoteAddr :", ti.RemoteAddr.String())
|
||||
|
||||
// c.log.Debug("LIST DEVICES OUTPUT", "body", string([]byte(resp.Body())))
|
||||
|
||||
// c.httpClient.SetBaseURL(WEB_API_SERVER[c.country]).SetCookies(c.cookies)
|
||||
// reqURL := apiURLs["WEB_API_LIST_DEVICES"]
|
||||
// resp := c.execute(reqURL, GET, map[string]string{}, "", false)
|
||||
|
||||
// if isResponseSuccessfull(resp) {
|
||||
// log.Debugf("LIST DEVICES OUTPUT >> %v\n", string(resp))
|
||||
// }
|
||||
}
|
||||
|
||||
// validateSession .
|
||||
func (c *Client) validateSession() bool {
|
||||
// {
|
||||
// "success": true,
|
||||
// "errorCode": null,
|
||||
// "dataName": null,
|
||||
// "data": null
|
||||
// }
|
||||
reqURL := MOBILE_API_VERSION + apiURLs["API_VALIDATE_SESSION"]
|
||||
resp := c.execute(reqURL, GET, map[string]string{}, "", false)
|
||||
|
||||
if !c.isResponseSuccessfull(resp) {
|
||||
return false
|
||||
}
|
||||
// result = False
|
||||
// js_resp = await self.__open(API_VALIDATE_SESSION, GET)
|
||||
// _LOGGER.debug(pprint.pformat(js_resp))
|
||||
// if js_resp["success"]:
|
||||
// if vin != self._current_vin:
|
||||
// # API call for VIN that is not the current remote context.
|
||||
// _LOGGER.debug("Switching Subaru API vehicle context to: %s", vin)
|
||||
// if await self._select_vehicle(vin):
|
||||
// result = True
|
||||
// else:
|
||||
// result = True
|
||||
|
||||
// if result is False:
|
||||
// await self._authenticate(vin)
|
||||
// # New session cookie. Must call selectVehicle.json before any other API call.
|
||||
// if await self._select_vehicle(vin):
|
||||
// result = True
|
||||
|
||||
c.log.Debug("[DEBUG] Validate Session", "body", string([]byte(resp)))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Client) SelectVehicle(vin string) VehicleData {
|
||||
// {
|
||||
// "success": true,
|
||||
// "errorCode": null,
|
||||
// "dataName": "vehicle",
|
||||
// "data": {
|
||||
// "customer": {
|
||||
// "sessionCustomer": null,
|
||||
// "email": null,
|
||||
// "firstName": null,
|
||||
// "lastName": null,
|
||||
// "zip": null,
|
||||
// "oemCustId": null,
|
||||
// "phone": null
|
||||
// },
|
||||
// "stolenVehicle": false,
|
||||
// "vehicleName": "Subaru Outback LXT",
|
||||
// "features": [
|
||||
// "ATF_MIL",
|
||||
// "11.6MMAN",
|
||||
// "ABS_MIL",
|
||||
// "CEL_MIL",
|
||||
// "ACCS",
|
||||
// "RCC",
|
||||
// "REARBRK",
|
||||
// "TEL_MIL",
|
||||
// "VDC_MIL",
|
||||
// "TPMS_MIL",
|
||||
// "WASH_MIL",
|
||||
// "BSDRCT_MIL",
|
||||
// "OPL_MIL",
|
||||
// "EYESIGHT",
|
||||
// "RAB_MIL",
|
||||
// "SRS_MIL",
|
||||
// "ESS_MIL",
|
||||
// "RESCC",
|
||||
// "EOL_MIL",
|
||||
// "BSD",
|
||||
// "EBD_MIL",
|
||||
// "EPB_MIL",
|
||||
// "RES",
|
||||
// "RHSF",
|
||||
// "AWD_MIL",
|
||||
// "NAV_TOMTOM",
|
||||
// "ISS_MIL",
|
||||
// "RPOIA",
|
||||
// "EPAS_MIL",
|
||||
// "RPOI",
|
||||
// "AHBL_MIL",
|
||||
// "SRH_MIL",
|
||||
// "g2"
|
||||
// ],
|
||||
// "vin": "4S4BTGND8L3137058",
|
||||
// "modelYear": "2020",
|
||||
// "modelCode": "LDJ",
|
||||
// "engineSize": 2.4,
|
||||
// "nickname": "Subaru Outback LXT",
|
||||
// "vehicleKey": 3832950,
|
||||
// "active": true,
|
||||
// "licensePlate": "8KV8",
|
||||
// "licensePlateState": "NJ",
|
||||
// "email": null,
|
||||
// "firstName": null,
|
||||
// "lastName": null,
|
||||
// "subscriptionFeatures": [
|
||||
// "REMOTE",
|
||||
// "SAFETY",
|
||||
// "Retail"
|
||||
// ],
|
||||
// "accessLevel": -1,
|
||||
// "zip": null,
|
||||
// "oemCustId": "CRM-631-HQN48K",
|
||||
// "vehicleMileage": null,
|
||||
// "phone": null,
|
||||
// "userOemCustId": "CRM-631-HQN48K",
|
||||
// "subscriptionStatus": "ACTIVE",
|
||||
// "authorizedVehicle": false,
|
||||
// "preferredDealer": null,
|
||||
// "cachedStateCode": "NJ",
|
||||
// "subscriptionPlans": [],
|
||||
// "needMileagePrompt": false,
|
||||
// "phev": null,
|
||||
// "remoteServicePinExist": true,
|
||||
// "needEmergencyContactPrompt": false,
|
||||
// "vehicleGeoPosition": {
|
||||
// "latitude": 40.70019,
|
||||
// "longitude": -74.401375,
|
||||
// "speed": null,
|
||||
// "heading": null,
|
||||
// "timestamp": 1640494569000
|
||||
// },
|
||||
// "extDescrip": "Abyss Blue Pearl",
|
||||
// "intDescrip": "Gray",
|
||||
// "modelName": "Outback",
|
||||
// "transCode": "CVT",
|
||||
// "provisioned": true,
|
||||
// "timeZone": "America/New_York"
|
||||
// }
|
||||
// }
|
||||
|
||||
if vin == "" {
|
||||
vin = c.currentVin
|
||||
}
|
||||
|
||||
vinCheck(vin)
|
||||
|
||||
params := map[string]string{
|
||||
"vin": vin,
|
||||
"_": timestamp()}
|
||||
reqURL := MOBILE_API_VERSION + apiURLs["API_SELECT_VEHICLE"]
|
||||
resp := c.execute(reqURL, GET, params, "", false)
|
||||
|
||||
var vData VehicleData
|
||||
respParsed, err := gabs.ParseJSON([]byte(resp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
vdString := respParsed.Path("data").String()
|
||||
json.Unmarshal([]byte(vdString), &vData)
|
||||
|
||||
// resp := c.execute(reqURL, GET, params, "", false)
|
||||
// log.Debugf("SELECT VEHICLE OUTPUT >> %v\n", string([]byte(resp)))
|
||||
|
||||
// ERRORS
|
||||
// {"success":false,"errorCode":"vehicleNotInAccount","dataName":null,"data":null}
|
||||
|
||||
// """Select active vehicle for accounts with multiple VINs."""
|
||||
// params = {"vin": vin, "_": int(time.time())}
|
||||
// js_resp = await self.get(API_SELECT_VEHICLE, params=params)
|
||||
// _LOGGER.debug(pprint.pformat(js_resp))
|
||||
// if js_resp.get("success"):
|
||||
// self._current_vin = vin
|
||||
// _LOGGER.debug("Current vehicle: vin=%s", js_resp["data"]["vin"])
|
||||
// return js_resp["data"]
|
||||
// if not js_resp.get("success") and js_resp.get("errorCode") == "VEHICLESETUPERROR":
|
||||
// # Occasionally happens every few hours. Resetting the session seems to deal with it.
|
||||
// _LOGGER.warning("VEHICLESETUPERROR received. Resetting session.")
|
||||
// self.reset_session()
|
||||
// return False
|
||||
// _LOGGER.debug("Failed to switch vehicle errorCode=%s", js_resp.get("errorCode"))
|
||||
// # Something else is probably wrong with the backend server context - try resetting
|
||||
// self.reset_session()
|
||||
// raise SubaruException("Failed to switch vehicle %s - resetting session." % js_resp.get("errorCode"))
|
||||
return vData
|
||||
}
|
||||
|
||||
// New function creates a New MySubaru client
|
||||
func New(logger *slog.Logger, config *config.MySubaru) (*Client, error) {
|
||||
|
||||
credentials := credentials{
|
||||
username: config.Credentials.Username,
|
||||
password: config.Credentials.Password,
|
||||
pin: config.Credentials.PIN,
|
||||
deviceId: config.Credentials.DeviceID,
|
||||
deviceName: config.Credentials.DeviceName,
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
credentials: credentials,
|
||||
country: config.Region,
|
||||
updateInterval: 7200,
|
||||
fetchInterval: 360,
|
||||
log: logger,
|
||||
}
|
||||
|
||||
httpClient := resty.New()
|
||||
httpClient.
|
||||
SetBaseURL(MOBILE_API_SERVER[client.country]).
|
||||
SetHeaders(map[string]string{
|
||||
"User-Agent": "Mozilla/5.0 (Linux; Android 10; Android SDK built for x86 Build/QSR1.191030.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.185 Mobile Safari/537.36",
|
||||
"Origin": "file://",
|
||||
"X-Requested-With": MOBILE_APP[client.country],
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"Accept-Encoding": "gzip, deflate",
|
||||
"Accept": "*/*"})
|
||||
|
||||
client.httpClient = httpClient
|
||||
|
||||
resp := client.auth()
|
||||
|
||||
respParsed, err := gabs.ParseJSON(resp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if client.isResponseSuccessfull(resp) {
|
||||
logger.Debug("Client authentication successful", "isRegistered", respParsed.Path("data.deviceRegistered").Data().(bool))
|
||||
client.isAuthenticated = true
|
||||
client.isRegistered = respParsed.Path("data.deviceRegistered").Data().(bool)
|
||||
} else {
|
||||
error, _ := respParsed.Path("errorCode").Data().(string)
|
||||
switch {
|
||||
case error == apiErrors["ERROR_INVALID_ACCOUNT"]:
|
||||
logger.Debug("Invalid account")
|
||||
case error == apiErrors["ERROR_INVALID_CREDENTIALS"]:
|
||||
logger.Debug("Client authentication failed")
|
||||
case error == apiErrors["ERROR_PASSWORD_WARNING"]:
|
||||
logger.Debug("Multiple Password Failures.")
|
||||
default:
|
||||
logger.Debug("Uknown error")
|
||||
}
|
||||
}
|
||||
|
||||
if !client.isRegistered {
|
||||
client.registerDevice()
|
||||
}
|
||||
|
||||
// TODO
|
||||
fmt.Printf("Parcing cars: %d\n", len(respParsed.Path("data.vehicles").Children()))
|
||||
for _, vehicle := range respParsed.Path("data.vehicles").Children() {
|
||||
fmt.Printf("CAR: %s\n", vehicle.Path("vin").Data().(string))
|
||||
client.listOfVins = append(client.listOfVins, vehicle.Path("vin").Data().(string))
|
||||
}
|
||||
client.currentVin = respParsed.Path("data.vehicles.0.vin").Data().(string)
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// GetVehicles .
|
||||
func (c *Client) GetVehicles() []*Vehicle {
|
||||
var vehicles []*Vehicle
|
||||
for _, vin := range c.listOfVins {
|
||||
params := map[string]string{
|
||||
"vin": vin,
|
||||
"_": timestamp()}
|
||||
reqURL := MOBILE_API_VERSION + apiURLs["API_SELECT_VEHICLE"]
|
||||
resp := c.execute(reqURL, GET, params, "", false)
|
||||
|
||||
respParsed, err := gabs.ParseJSON([]byte(resp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
vData := VehicleData{}
|
||||
vdString := respParsed.Path("data").String()
|
||||
|
||||
json.Unmarshal([]byte(vdString), &vData)
|
||||
|
||||
// fmt.Printf("VEHICLE STRING: %+v\n\n", vdString)
|
||||
// fmt.Printf("VEHICLE DATA: %+v\n\n", vData)
|
||||
|
||||
vehicle := &Vehicle{
|
||||
Vin: vin,
|
||||
CarName: vData.VehicleName,
|
||||
CarNickname: vData.Nickname,
|
||||
ModelName: vData.ModelName,
|
||||
ModelYear: vData.ModelYear,
|
||||
ModelCode: vData.ModelCode,
|
||||
ExtDescrip: vData.ExtDescrip,
|
||||
IntDescrip: vData.IntDescrip,
|
||||
TransCode: vData.TransCode,
|
||||
EngineSize: vData.EngineSize,
|
||||
VehicleKey: vData.VehicleKey,
|
||||
LicensePlate: vData.LicensePlate,
|
||||
LicensePlateState: vData.LicensePlateState,
|
||||
Features: vData.Features,
|
||||
SubscriptionFeatures: vData.SubscriptionFeatures,
|
||||
client: c,
|
||||
}
|
||||
vehicle.GetVehicleStatus()
|
||||
vehicle.GetVehicleCondition()
|
||||
vehicle.GetClimatePresets()
|
||||
vehicle.GetClimateUserPresets()
|
||||
|
||||
vehicles = append(vehicles, vehicle)
|
||||
}
|
||||
|
||||
return vehicles
|
||||
}
|
||||
|
||||
// GetVehicleByVIN .
|
||||
func (c *Client) GetVehicleByVIN(vin string) *Vehicle {
|
||||
var vehicle *Vehicle
|
||||
if contains(c.listOfVins, vin) {
|
||||
params := map[string]string{
|
||||
"vin": vin,
|
||||
"_": timestamp()}
|
||||
reqURL := MOBILE_API_VERSION + apiURLs["API_SELECT_VEHICLE"]
|
||||
resp := c.execute(reqURL, GET, params, "", false)
|
||||
|
||||
respParsed, err := gabs.ParseJSON([]byte(resp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
vData := VehicleData{}
|
||||
vdString := respParsed.Path("data").String()
|
||||
json.Unmarshal([]byte(vdString), &vData)
|
||||
|
||||
// fmt.Printf("VEHICLE DATA: %+v\n\n", vData)
|
||||
|
||||
vehicle = &Vehicle{
|
||||
Vin: vin,
|
||||
CarName: vData.VehicleName,
|
||||
CarNickname: vData.Nickname,
|
||||
ModelName: vData.ModelName,
|
||||
ModelYear: vData.ModelYear,
|
||||
ModelCode: vData.ModelCode,
|
||||
ExtDescrip: vData.ExtDescrip,
|
||||
IntDescrip: vData.IntDescrip,
|
||||
TransCode: vData.TransCode,
|
||||
EngineSize: vData.EngineSize,
|
||||
VehicleKey: vData.VehicleKey,
|
||||
LicensePlate: vData.LicensePlate,
|
||||
LicensePlateState: vData.LicensePlateState,
|
||||
Features: vData.Features,
|
||||
SubscriptionFeatures: vData.SubscriptionFeatures,
|
||||
client: c,
|
||||
}
|
||||
vehicle.GetVehicleStatus()
|
||||
vehicle.GetVehicleCondition()
|
||||
vehicle.GetClimatePresets()
|
||||
vehicle.GetClimateUserPresets()
|
||||
}
|
||||
|
||||
return vehicle
|
||||
}
|
||||
|
||||
// GetVehicleStatus .
|
||||
func (c *Client) GetVehicleStatus() {
|
||||
// {
|
||||
// "dataName": null,
|
||||
// "errorCode": null,
|
||||
// "success": true,
|
||||
// "data": {
|
||||
// "avgFuelConsumptionLitersPer100Kilometers": 12.5,
|
||||
// "avgFuelConsumptionMpg": 18.8,
|
||||
// "distanceToEmptyFuelKilometers": 563,
|
||||
// "distanceToEmptyFuelKilometers10s": 560,
|
||||
// "distanceToEmptyFuelMiles": 349.83,
|
||||
// "distanceToEmptyFuelMiles10s": 350,
|
||||
// "evDistanceToEmptyByStateKilometers": null,
|
||||
// "evDistanceToEmptyByStateMiles": null,
|
||||
// "evDistanceToEmptyKilometers": null,
|
||||
// "evDistanceToEmptyMiles": null,
|
||||
// "evStateOfChargePercent": null,
|
||||
// "eventDate": 1640494569000,
|
||||
// "eventDateStr": "2021-12-26T04:56+0000",
|
||||
// "latitude": 40.700192,
|
||||
// "longitude": -74.401377,
|
||||
// "odometerValue": 24065,
|
||||
// "odometerValueKilometers": 38721,
|
||||
// "positionHeadingDegree": "150",
|
||||
// "tirePressureFrontLeft": "2413",
|
||||
// "tirePressureFrontLeftPsi": "35",
|
||||
// "tirePressureFrontRight": "2413",
|
||||
// "tirePressureFrontRightPsi": "35",
|
||||
// "tirePressureRearLeft": "2551",
|
||||
// "tirePressureRearLeftPsi": "37",
|
||||
// "tirePressureRearRight": "2482",
|
||||
// "tirePressureRearRightPsi": "36",
|
||||
// "vehicleStateType": "IGNITION_OFF",
|
||||
// "vhsId": 923920223
|
||||
// }
|
||||
// }
|
||||
|
||||
reqURL := MOBILE_API_VERSION + apiURLs["API_VEHICLE_STATUS"]
|
||||
resp := c.execute(reqURL, GET, map[string]string{}, "", false)
|
||||
|
||||
respParsed, err := gabs.ParseJSON(resp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.log.Debug("GET VEHICLE STATUS OUTPUT", "body", respParsed)
|
||||
|
||||
success, ok := respParsed.Path("success").Data().(bool)
|
||||
// value == string, ok == false
|
||||
if !ok {
|
||||
// TODO: Work with errorCode
|
||||
panic(success)
|
||||
}
|
||||
}
|
||||
|
||||
// GetVehicleStatus .
|
||||
func (c *Client) GetClimateSettings() {
|
||||
// {
|
||||
// "success": true,
|
||||
// "errorCode": null,
|
||||
// "dataName": null,
|
||||
// "data": {
|
||||
// "climateZoneFrontTemp": "70",
|
||||
// "runTimeMinutes": "10",
|
||||
// "climateZoneFrontAirMode": "WINDOW",
|
||||
// "heatedSeatFrontLeft": "LOW_HEAT",
|
||||
// "heatedSeatFrontRight": "LOW_HEAT",
|
||||
// "heatedRearWindowActive": "true",
|
||||
// "climateZoneFrontAirVolume": "6",
|
||||
// "outerAirCirculation": "outsideAir",
|
||||
// "airConditionOn": "false",
|
||||
// "startConfiguration": "START_ENGINE_ALLOW_KEY_IN_IGNITION"
|
||||
// }
|
||||
// }
|
||||
reqURL := MOBILE_API_VERSION + apiURLs["API_G2_FETCH_CLIMATE_SETTINGS"]
|
||||
resp := c.execute(reqURL, GET, map[string]string{}, "", false)
|
||||
|
||||
respParsed, err := gabs.ParseJSON(resp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.log.Debug("CLIMATE SETTINGS OUTPUT", "response", respParsed)
|
||||
|
||||
// ONLY FOR THAT REQUEST BECAUSE OF API SENDS BACK ESCAPING DATA IN DATA FIELD
|
||||
data, ok := respParsed.Path("data").Data().(string)
|
||||
// rawIn := json.RawMessage(in)
|
||||
// bytes, err := rawIn.MarshalJSON()
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
// value == string, ok == false
|
||||
if !ok {
|
||||
// TODO: Work with errorCode
|
||||
panic(data)
|
||||
}
|
||||
c.log.Debug("CLIMATE SETTINGS OUTPUT", "body", data)
|
||||
}
|
||||
|
||||
// func isPINRequired() {}
|
||||
// func getVehicles() {}
|
||||
// func getEVStatus() {}
|
||||
// func getRemoteOptionsStatus() {}
|
||||
// func getRemoteStartStatus() {}
|
||||
// func getSafetyStatus() {}
|
||||
// func getSubscriptionStatus() {}
|
||||
// func getAPIGen() {}
|
||||
// func getClimateData() {}
|
||||
// func saveClimateSettings() {}
|
||||
// func getVehicleName() {}
|
||||
// func fetch() {}
|
||||
|
||||
// Exec method executes a Client instance with the API URL
|
||||
func (c *Client) execute(requestUrl string, method string, params map[string]string, pollingUrl string, json bool) []byte {
|
||||
defer timeTrack("[TIMETRK] Executing Get Request")
|
||||
|
||||
// if !isNil(resp.Cookies()) {
|
||||
// log.Debugf("AUTH COOKIES OUTPUT >> %v\n", resp.Cookies())
|
||||
// c.cookies = resp.Cookies()
|
||||
// }
|
||||
|
||||
var resp *resty.Response
|
||||
// GET Requests
|
||||
if method == "GET" {
|
||||
resp, _ = c.httpClient.
|
||||
// SetBaseURL(MOBILE_API_SERVER[c.country]).
|
||||
R().
|
||||
SetQueryParams(params).
|
||||
Get(requestUrl)
|
||||
}
|
||||
|
||||
// POST Requests
|
||||
if method == "POST" {
|
||||
if json {
|
||||
// POST > JSON Body
|
||||
resp, _ = c.httpClient.R().
|
||||
SetBody(params).
|
||||
Post(requestUrl)
|
||||
} else {
|
||||
// POST > Form Data
|
||||
resp, _ = c.httpClient.R().
|
||||
SetFormData(params).
|
||||
Post(requestUrl)
|
||||
}
|
||||
}
|
||||
|
||||
respParsed, err := gabs.ParseJSON([]byte(resp.Body()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.log.Debug("HTTP OUTPUT", "body", string(resp.Body()))
|
||||
|
||||
_, ok := respParsed.Path("success").Data().(bool)
|
||||
// value == string, ok == false
|
||||
if !ok {
|
||||
// TODO: Work with errorCode
|
||||
// panic(success)
|
||||
fmt.Printf("ERROR: %+v", string([]byte(resp.Body())))
|
||||
}
|
||||
|
||||
if pollingUrl != "" {
|
||||
serviceRequestId, _ := respParsed.Path("data.serviceRequestId").Data().(string)
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
attempts := 20
|
||||
poolingLoop:
|
||||
for attempts > 0 {
|
||||
resp, _ = c.httpClient.
|
||||
SetBaseURL(MOBILE_API_SERVER[c.country]).
|
||||
R().
|
||||
SetQueryParams(map[string]string{
|
||||
"serviceRequestId": serviceRequestId,
|
||||
}).
|
||||
Get(pollingUrl)
|
||||
|
||||
c.log.Debug("POLLING HTTP OUTPUT", "body", string([]byte(resp.Body())))
|
||||
// {"success":false,"errorCode":"404-soa-unableToParseResponseBody","dataName":"errorResponse","data":{"errorLabel":"404-soa-unableToParseResponseBody","errorDescription":null}}
|
||||
|
||||
respParsed, err := gabs.ParseJSON(resp.Body())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
success, ok := respParsed.Path("success").Data().(bool)
|
||||
if !ok {
|
||||
panic(success)
|
||||
}
|
||||
if success {
|
||||
status, _ := respParsed.Path("data.remoteServiceState").Data().(string)
|
||||
switch {
|
||||
case status == "finished":
|
||||
c.log.Debug("Remote service request completed successfully", "request id", serviceRequestId)
|
||||
break poolingLoop
|
||||
case status == "started":
|
||||
c.log.Debug("Subaru API reports remote service request is in progress", "request id", serviceRequestId)
|
||||
}
|
||||
} else {
|
||||
c.log.Debug("Backend session expired, please try again")
|
||||
break poolingLoop
|
||||
}
|
||||
attempts--
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// fmt.Printf("[DEBUG] HTTP OUTPUT >> %v\n", string([]byte(resp.Body())))
|
||||
|
||||
return resp.Body()
|
||||
}
|
||||
|
||||
// isResponseSuccessfull .
|
||||
func (c *Client) isResponseSuccessfull(resp []byte) bool {
|
||||
respParsed, err := gabs.ParseJSON(resp)
|
||||
if err != nil {
|
||||
c.log.Debug("error while parsing json response", "error", err)
|
||||
}
|
||||
|
||||
success, ok := respParsed.Path("success").Data().(bool)
|
||||
if !ok {
|
||||
c.log.Debug("response is not successful", "error", resp)
|
||||
}
|
||||
|
||||
// ERRORS FROM CLIENT CREATION AFTER AUTH
|
||||
// error, _ := respParsed.Path("errorCode").Data().(string)
|
||||
// switch {
|
||||
// case error == apiErrors["ERROR_INVALID_ACCOUNT"]:
|
||||
// fmt.Println("Invalid account")
|
||||
// case error == apiErrors["ERROR_INVALID_CREDENTIALS"]:
|
||||
// {"success":false,"errorCode":"InvalidCredentials","dataName":"remoteServiceStatus","data":{"serviceRequestId":null,"success":false,"cancelled":false,"remoteServiceType":null,"remoteServiceState":null,"subState":null,"errorCode":null,"result":null,"updateTime":null,"vin":null,"errorDescription":"The credentials supplied are invalid, tries left 2"}}
|
||||
// fmt.Println("Client authentication failed")
|
||||
// case error == apiErrors["ERROR_PASSWORD_WARNING"]:
|
||||
// fmt.Println("Multiple Password Failures.")
|
||||
// default:
|
||||
// fmt.Println("Uknown error")
|
||||
// }
|
||||
|
||||
return success
|
||||
}
|
72
config/config.go
Normal file
72
config/config.go
Normal file
@ -0,0 +1,72 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Config .
|
||||
type Config struct {
|
||||
MySubaru MySubaru `json:"mysubaru" yaml:"mysubaru"`
|
||||
Timezone string `json:"timezone" yaml:"timezone"`
|
||||
Logging *Logging `json:"logging" yaml:"logging"`
|
||||
}
|
||||
|
||||
// Emporia .
|
||||
type MySubaru struct {
|
||||
Credentials Credentials `json:"credentials" yaml:"credentials"`
|
||||
Region string `json:"region" yaml:"region"`
|
||||
AutoReconnect bool `json:"auto_reconnect" yaml:"auto_reconnect"`
|
||||
}
|
||||
|
||||
// Credentials .
|
||||
type Credentials struct {
|
||||
Username string `json:"username" yaml:"username"`
|
||||
Password string `json:"password" yaml:"password"`
|
||||
PIN string `json:"pin" yaml:"pin"`
|
||||
DeviceID string `json:"deviceid" yaml:"deviceid"`
|
||||
DeviceName string `json:"devicename" yaml:"devicename"`
|
||||
}
|
||||
|
||||
// Logging .
|
||||
type Logging struct {
|
||||
Level string `json:"level" yaml:"level"`
|
||||
Output string `json:"output" yaml:"output"`
|
||||
Source bool `json:"source,omitempty" yaml:"source,omitempty"`
|
||||
}
|
||||
|
||||
func New() (*Config, error) {
|
||||
viper.SetConfigName("config") // name of config file (without extension)
|
||||
viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name
|
||||
viper.AddConfigPath(".") // optionally look for config in the working directory
|
||||
viper.AddConfigPath("/etc/mysubaru") // optionally look for config in the working directory
|
||||
|
||||
viper.AutomaticEnv()
|
||||
|
||||
viper.SetDefault("Timezone", "America/New_York")
|
||||
viper.SetDefault("MySubaru.AutoReconnect", true)
|
||||
viper.SetDefault("Logging.Level", "INFO")
|
||||
viper.SetDefault("Logging.Output", "TEXT")
|
||||
viper.SetDefault("Logging.Source", "false")
|
||||
|
||||
err := viper.ReadInConfig() // Find and read the config file
|
||||
if err != nil { // Handle errors reading the config file
|
||||
slog.Error("cannot open config file", "error", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// viper.WatchConfig()
|
||||
// viper.OnConfigChange(func(e fsnotify.Event) {
|
||||
// log.Println("Config file changed:", e.Name)
|
||||
// })
|
||||
|
||||
var config Config
|
||||
if err := viper.Unmarshal(&config); err != nil {
|
||||
slog.Error("cannot unmarshal config file", "error", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
357
consts.go
Normal file
357
consts.go
Normal file
@ -0,0 +1,357 @@
|
||||
package mysubaru
|
||||
|
||||
// Lastest version /g2v27
|
||||
var MOBILE_API_VERSION = "/g2v28"
|
||||
|
||||
var MOBILE_API_SERVER = map[string]string{
|
||||
"USA": "https://mobileapi.prod.subarucs.com",
|
||||
"CAN": "https://mobileapi.ca.prod.subarucs.com",
|
||||
}
|
||||
|
||||
var MOBILE_APP = map[string]string{
|
||||
"USA": "com.subaru.telematics.app.remote",
|
||||
"CAN": "ca.subaru.telematics.remote",
|
||||
}
|
||||
|
||||
var WEB_API_SERVER = map[string]string{
|
||||
"USA": "https://www.mysubaru.com",
|
||||
"CAN": "https://www.mysubaru.ca",
|
||||
}
|
||||
|
||||
var apiURLs = map[string]string{
|
||||
"WEB_API_LOGIN": "/login",
|
||||
"WEB_API_LIST_DEVICES": "/listMyDevices.json", // TODO
|
||||
"WEB_API_AUTHORIZE_DEVICE": "/profile/updateDeviceEntry.json",
|
||||
"WEB_API_NAME_DEVICE": "/profile/addDeviceName.json",
|
||||
"WEB_API_EDIT_NAME_DEVICE": "/profile/editDeviceName.json",
|
||||
"WEB_API_VERIFY_NAME_DEVICE": "/profile/verifyDeviceName.json",
|
||||
"API_LOGIN": "/login.json", // Same API for g1 and g2
|
||||
"API_REFRESH_VEHICLES": "/refreshVehicles.json",
|
||||
"API_SELECT_VEHICLE": "/selectVehicle.json",
|
||||
"API_VALIDATE_SESSION": "/validateSession.json",
|
||||
"API_VEHICLE_STATUS": "/vehicleStatus.json",
|
||||
"API_AUTHORIZE_DEVICE": "/authenticateDevice.json",
|
||||
"API_NAME_DEVICE": "/nameThisDevice.json",
|
||||
"API_VEHICLE_HEALTH": "/vehicleHealth.json",
|
||||
"API_LOCK": "/service/api_gen/lock/execute.json", // Similar API for g1 and g2 -- controller should replace 'api_gen' with either 'g1' or 'g2'
|
||||
"API_LOCK_CANCEL": "/service/api_gen/lock/cancel.json",
|
||||
"API_UNLOCK": "/service/api_gen/unlock/execute.json",
|
||||
"API_UNLOCK_CANCEL": "/service/api_gen/unlock/cancel.json",
|
||||
"API_HORN_LIGHTS": "/service/api_gen/hornLights/execute.json",
|
||||
"API_HORN_LIGHTS_CANCEL": "/service/api_gen/hornLights/cancel.json",
|
||||
"API_HORN_LIGHTS_STOP": "/service/api_gen/hornLights/stop.json",
|
||||
"API_LIGHTS": "/service/api_gen/lightsOnly/execute.json",
|
||||
"API_LIGHTS_CANCEL": "/service/api_gen/lightsOnly/cancel.json",
|
||||
"API_LIGHTS_STOP": "/service/api_gen/lightsOnly/stop.json",
|
||||
"API_CONDITION": "/service/api_gen/condition/execute.json",
|
||||
"API_LOCATE": "/service/api_gen/locate/execute.json", // Get the last location the vehicle has reported to Subaru
|
||||
"API_REMOTE_SVC_STATUS": "/service/g2/remoteService/status.json",
|
||||
"API_G1_LOCATE_UPDATE": "/service/g1/vehicleLocate/execute.json", // Different API for g1 and g2
|
||||
"API_G1_LOCATE_STATUS": "/service/g1/vehicleLocate/status.json",
|
||||
"API_G1_HORN_LIGHTS_STATUS": "/service/g1/hornLights/status.json", // g1-Only API
|
||||
"API_G2_LOCATE_UPDATE": "/service/g2/vehicleStatus/execute.json",
|
||||
"API_G2_LOCATE_STATUS": "/service/g2/vehicleStatus/locationStatus.json",
|
||||
"API_G2_SEND_POI": "/service/g2/sendPoi/execute.json", // g2-Only API
|
||||
"API_G2_SPEEDFENCE": "/service/g2/speedFence/execute.json",
|
||||
"API_G2_GEOFENCE": "/service/g2/geoFence/execute.json",
|
||||
"API_G2_CURFEW": "/service/g2/curfew/execute.json",
|
||||
"API_G2_REMOTE_ENGINE_START": "/service/g2/engineStart/execute.json",
|
||||
"API_G2_REMOTE_ENGINE_START_CANCEL": "/service/g2/engineStart/cancel.json",
|
||||
"API_G2_REMOTE_ENGINE_STOP": "/service/g2/engineStop/execute.json",
|
||||
|
||||
"API_G2_FETCH_CLIMATE_SETTINGS": "/service/g2/remoteEngineStart/fetch.json",
|
||||
"API_G2_SAVE_CLIMATE_SETTINGS": "/service/g2/remoteEngineStart/save.json",
|
||||
|
||||
"API_G2_FETCH_RES_QUICK_START_SETTINGS": "/service/g2/remoteEngineQuickStartSettings/fetch.json",
|
||||
"API_G2_FETCH_RES_USER_PRESETS": "/service/g2/remoteEngineStartSettings/fetch.json",
|
||||
"API_G2_FETCH_RES_SUBARU_PRESETS": "/service/g2/climatePresetSettings/fetch.json",
|
||||
"API_G2_SAVE_RES_SETTINGS": "/service/g2/remoteEngineStartSettings/save.json",
|
||||
"API_G2_SAVE_RES_QUICK_START_SETTINGS": "/service/g2/remoteEngineQuickStartSettings/save.json",
|
||||
|
||||
"API_EV_CHARGE_NOW": "/service/g2/phevChargeNow/execute.json", // EV-Only API
|
||||
"API_EV_FETCH_CHARGE_SETTINGS": "/service/g2/phevGetTimerSettings/execute.json",
|
||||
"API_EV_SAVE_CHARGE_SETTINGS": "/service/g2/phevSendTimerSetting/execute.json",
|
||||
"API_EV_DELETE_CHARGE_SCHEDULE": "/service/g2/phevDeleteTimerSetting/execute.json",
|
||||
}
|
||||
|
||||
// API_VEHICLE_FEATURES items that determine available functionality
|
||||
// 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
|
||||
// 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 OPL_MIL PANPM-TUIRWAOC PWAAADWWAP RAB_MIL RCC REARBRK RES RESCC RHSF RPOI RPOIA RTGU RVFS SRH_MIL SRS_MIL TEL_MIL TIF_35 TIR_33 TLD TPMS_MIL VALET VDC_MIL WASH_MIL g3
|
||||
var apiFeatures = map[string]string{
|
||||
"API_FEATURE_PHEV": "PHEV",
|
||||
"API_FEATURE_REMOTE_START": "RES",
|
||||
"API_FEATURE_REMOTE": "REMOTE",
|
||||
"API_FEATURE_SAFETY": "SAFETY",
|
||||
"API_FEATURE_ACTIVE": "ACTIVE",
|
||||
"API_FEATURE_MOONROOF_PANORAMIC": "PANPM-DG2G",
|
||||
"API_FEATURE_MOONROOF_POWER": "PANPM-TUIRWAOC",
|
||||
"API_FEATURE_POWER_WINDOWS": "PWAAADWWAP",
|
||||
"API_FEATURE_FRONT_TIRE_RECOMMENDED_PRESSURE_PREFIX": "TIF_",
|
||||
"API_FEATURE_REAR_TIRE_RECOMMENDED_PRESSURE_PREFIX": "TIR_",
|
||||
"API_FEATURE_G1_TELEMATICS": "g1",
|
||||
"API_FEATURE_G2_TELEMATICS": "g2",
|
||||
"API_FEATURE_G3_TELEMATICS": "g3",
|
||||
}
|
||||
|
||||
var apiErrors = map[string]string{
|
||||
"ERROR_SOA_403": "403-soa-unableToParseResponseBody", // G2 Error Codes
|
||||
"ERROR_INVALID_CREDENTIALS": "InvalidCredentials",
|
||||
"ERROR_SERVICE_ALREADY_STARTED": "ServiceAlreadyStarted",
|
||||
"ERROR_INVALID_ACCOUNT": "invalidAccount",
|
||||
"ERROR_PASSWORD_WARNING": "passwordWarning",
|
||||
"ERROR_ACCOUNT_LOCKED": "accountLocked",
|
||||
"ERROR_NO_VEHICLES": "noVehiclesOnAccount",
|
||||
"ERROR_NO_ACCOUNT": "accountNotFound",
|
||||
"ERROR_TOO_MANY_ATTEMPTS": "tooManyAttempts",
|
||||
"ERROR_VEHICLE_NOT_IN_ACCOUNT": "vehicleNotInAccount",
|
||||
"ERROR_G1_NO_SUBSCRIPTION": "SXM40004", // G1 Error Codes
|
||||
"ERROR_G1_STOLEN_VEHICLE": "SXM40005",
|
||||
"ERROR_G1_INVALID_PIN": "SXM40006",
|
||||
"ERROR_G1_SERVICE_ALREADY_STARTED": "SXM40009",
|
||||
"ERROR_G1_PIN_LOCKED": "SXM40017",
|
||||
}
|
||||
|
||||
// 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 OPL_MIL PANPM-TUIRWAOC PWAAADWWAP RAB_MIL RCC REARBRK RES RESCC RHSF RPOI RPOIA RTGU RVFS SRH_MIL SRS_MIL TEL_MIL TIF_35 TIR_33 TLD TPMS_MIL VALET VDC_MIL WASH_MIL g3
|
||||
// 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
|
||||
var features = map[string]string{
|
||||
"g1": "Generation #1",
|
||||
"g2": "Generation #2",
|
||||
"g3": "Generation #3",
|
||||
"11.6MMAN": "11.6-inch Infotainment System",
|
||||
"EYESIGHT": "EyeSight Exclusive Advanced Driver-Assist System",
|
||||
"NAV_TOMTOM": "TomTom Navigation",
|
||||
"PWAAADWWAP": "Power Windows",
|
||||
"PANPM-TUIRWAOC": "Power Moonroof",
|
||||
"PANPM-DG2G": "Panoramic Moonroof",
|
||||
"PHEV": "Electric Vehicle",
|
||||
"RES": "Remote Start",
|
||||
"TIF_35": "Tire Pressure Front 35",
|
||||
"TIR_33": "Tire Pressure Rear 35",
|
||||
"VALET": "Valet Parking",
|
||||
}
|
||||
|
||||
var errors = map[string]string{
|
||||
"ABS_MIL": "Anti-Lock Braking System",
|
||||
"AHBL_MIL": "Automatic Headlight Beam Leveler",
|
||||
"ATF_MIL": "Automatic Transmission Oil Temperature",
|
||||
"AWD_MIL": "Symmetrical Full-Time AWD",
|
||||
"BSDRCT_MIL": "Blind-Spot Detection",
|
||||
"CEL_MIL": "Check Engine Light",
|
||||
"EBD_MIL": "Electronic Brake Force Distribution",
|
||||
"EOL_MIL": "Engine Oil Level",
|
||||
"EPAS_MIL": "Electric Power Assisted Steering",
|
||||
"EPB_MIL": "Parking Brake",
|
||||
"ESS_MIL": "EyeSight Exclusive Advanced Driver-Assist System",
|
||||
"ISS_MIL": "iss",
|
||||
"OPL_MIL": "Oil Pressure",
|
||||
"RAB_MIL": "Reverse Auto Braking",
|
||||
"SRH_MIL": "Steering Responsive Headlights",
|
||||
"SRS_MIL": "Airbag System",
|
||||
"TEL_MIL": "telematics",
|
||||
"TPMS_MIL": "tpms",
|
||||
"VDC_MIL": "Vehicle Dynamics Control",
|
||||
"WASH_MIL": "Windshield Washer Fluid Level",
|
||||
}
|
||||
var modelCodes = map[string]string{
|
||||
"PDL": "Subaru Outback Touring XT",
|
||||
"LDJ": "Subaru Outback Limited XT",
|
||||
"KDF": "Outback 2.5i Limited",
|
||||
"LDD": "Outback Premium",
|
||||
"PDG": "Outback Touring",
|
||||
"KFB": "Forester Base Model",
|
||||
"HFJ": "Forester 2.5i Touring",
|
||||
"KFJ": "Forester Touring",
|
||||
"KAF": "Legacy 2.5i Limited",
|
||||
"KLF": "Impreza 2.0i Sport",
|
||||
"LRD": "Crosstrek Premium",
|
||||
"KRD": "Crosstrek 2.0i Premium",
|
||||
"PCL": "Ascent Limited 7-Passenger",
|
||||
}
|
||||
|
||||
const (
|
||||
GET = "GET"
|
||||
POST = "POST"
|
||||
|
||||
SERVICE_REQ_ID = "serviceRequestId"
|
||||
TEMP_F = "climateZoneFrontTemp" // Remote start constants
|
||||
TEMP_F_MAX = 85
|
||||
TEMP_F_MIN = 60
|
||||
TEMP_C = "climateZoneFrontTempCelsius"
|
||||
TEMP_C_MAX = 30
|
||||
TEMP_C_MIN = 15
|
||||
CLIMATE = "climateSettings"
|
||||
CLIMATE_DEFAULT = "climateSettings"
|
||||
RUNTIME = "runTimeMinutes"
|
||||
RUNTIME_DEFAULT = "10"
|
||||
MODE = "climateZoneFrontAirMode"
|
||||
MODE_DEFROST = "WINDOW"
|
||||
MODE_FEET_DEFROST = "FEET_WINDOW"
|
||||
MODE_FACE = "FACE"
|
||||
MODE_FEET = "FEET"
|
||||
MODE_SPLIT = "FEET_FACE_BALANCED"
|
||||
MODE_AUTO = "AUTO"
|
||||
HEAT_SEAT_LEFT = "heatedSeatFrontLeft"
|
||||
HEAT_SEAT_RIGHT = "heatedSeatFrontRight"
|
||||
HEAT_SEAT_HI = "HIGH_HEAT"
|
||||
HEAT_SEAT_MED = "MEDIUM_HEAT"
|
||||
HEAT_SEAT_LOW = "LOW_HEAT"
|
||||
HEAT_SEAT_OFF = "OFF"
|
||||
REAR_DEFROST = "heatedRearWindowActive"
|
||||
REAR_DEFROST_ON = "true"
|
||||
REAR_DEFROST_OFF = "false"
|
||||
FAN_SPEED = "climateZoneFrontAirVolume"
|
||||
FAN_SPEED_LOW = "2"
|
||||
FAN_SPEED_MED = "4"
|
||||
FAN_SPEED_HI = "7"
|
||||
FAN_SPEED_AUTO = "AUTO"
|
||||
RECIRCULATE = "outerAirCirculation"
|
||||
RECIRCULATE_OFF = "outsideAir"
|
||||
RECIRCULATE_ON = "recirculation"
|
||||
REAR_AC = "airConditionOn"
|
||||
REAR_AC_ON = "true"
|
||||
REAR_AC_OFF = "false"
|
||||
START_CONFIG = "startConfiguration"
|
||||
START_CONFIG_DEFAULT_EV = "start_Climate_Control_only_allow_key_in_ignition"
|
||||
START_CONFIG_DEFAULT_RES = "START_ENGINE_ALLOW_KEY_IN_IGNITION"
|
||||
WHICH_DOOR = "unlockDoorType" // Unlock doors constants
|
||||
ALL_DOORS = "ALL_DOORS_CMD"
|
||||
DRIVERS_DOOR = "FRONT_LEFT_DOOR_CMD"
|
||||
HEADING = "heading" // Location data constants
|
||||
LATITUDE = "latitude"
|
||||
LONGITUDE = "longitude"
|
||||
LOCATION_TIME = "locationTimestamp"
|
||||
SPEED = "speed"
|
||||
BAD_LATITUDE = 90.0
|
||||
BAD_LONGITUDE = 180.0
|
||||
AVG_FUEL_CONSUMPTION = "AVG_FUEL_CONSUMPTION" // Vehicle status constants
|
||||
BATTERY_VOLTAGE = "BATTERY_VOLTAGE" // NO LONGER
|
||||
DIST_TO_EMPTY = "DISTANCE_TO_EMPTY_FUEL"
|
||||
DOOR_BOOT_POSITION = "DOOR_BOOT_POSITION"
|
||||
DOOR_ENGINE_HOOD_POSITION = "DOOR_ENGINE_HOOD_POSITION"
|
||||
DOOR_FRONT_LEFT_POSITION = "DOOR_FRONT_LEFT_POSITION"
|
||||
DOOR_FRONT_RIGHT_POSITION = "DOOR_FRONT_RIGHT_POSITION"
|
||||
DOOR_REAR_LEFT_POSITION = "DOOR_REAR_LEFT_POSITION"
|
||||
DOOR_REAR_RIGHT_POSITION = "DOOR_REAR_RIGHT_POSITION"
|
||||
EV_CHARGER_STATE_TYPE = "EV_CHARGER_STATE_TYPE"
|
||||
EV_CHARGE_SETTING_AMPERE_TYPE = "EV_CHARGE_SETTING_AMPERE_TYPE"
|
||||
EV_CHARGE_VOLT_TYPE = "EV_CHARGE_VOLT_TYPE"
|
||||
EV_DISTANCE_TO_EMPTY = "EV_DISTANCE_TO_EMPTY"
|
||||
EV_IS_PLUGGED_IN = "EV_IS_PLUGGED_IN"
|
||||
EV_STATE_OF_CHARGE_MODE = "EV_STATE_OF_CHARGE_MODE"
|
||||
EV_STATE_OF_CHARGE_PERCENT = "EV_STATE_OF_CHARGE_PERCENT"
|
||||
EV_TIME_TO_FULLY_CHARGED = "EV_TIME_TO_FULLY_CHARGED"
|
||||
EV_TIME_TO_FULLY_CHARGED_UTC = "EV_TIME_TO_FULLY_CHARGED_UTC"
|
||||
EXTERNAL_TEMP = "EXT_EXTERNAL_TEMP"
|
||||
ODOMETER = "ODOMETER"
|
||||
POSITION_TIMESTAMP = "POSITION_TIMESTAMP"
|
||||
TIMESTAMP = "TIMESTAMP"
|
||||
TIRE_PRESSURE_FL = "TYRE_PRESSURE_FRONT_LEFT"
|
||||
TIRE_PRESSURE_FR = "TYRE_PRESSURE_FRONT_RIGHT"
|
||||
TIRE_PRESSURE_RL = "TYRE_PRESSURE_REAR_LEFT"
|
||||
TIRE_PRESSURE_RR = "TYRE_PRESSURE_REAR_RIGHT"
|
||||
VEHICLE_STATE = "VEHICLE_STATE_TYPE"
|
||||
WINDOW_FRONT_LEFT_STATUS = "WINDOW_FRONT_LEFT_STATUS"
|
||||
WINDOW_FRONT_RIGHT_STATUS = "WINDOW_FRONT_RIGHT_STATUS"
|
||||
WINDOW_REAR_LEFT_STATUS = "WINDOW_REAR_LEFT_STATUS"
|
||||
WINDOW_REAR_RIGHT_STATUS = "WINDOW_REAR_RIGHT_STATUS"
|
||||
CHARGING = "CHARGING"
|
||||
LOCKED_CONNECTED = "LOCKED_CONNECTED"
|
||||
UNLOCKED_CONNECTED = "UNLOCKED_CONNECTED"
|
||||
DOOR_OPEN = "OPEN"
|
||||
DOOR_CLOSED = "CLOSED"
|
||||
WINDOW_OPEN = "OPEN"
|
||||
WINDOW_CLOSED = "CLOSE"
|
||||
IGNITION_ON = "IGNITION_ON"
|
||||
NOT_EQUIPPED = "NOT_EQUIPPED"
|
||||
VS_AVG_FUEL_CONSUMPTION = "avgFuelConsumptionLitersPer100Kilometers" // vehicleStatus.json keys
|
||||
VS_DIST_TO_EMPTY = "distanceToEmptyFuelKilometers"
|
||||
VS_TIMESTAMP = "eventDate"
|
||||
VS_LATITUDE = "latitude"
|
||||
VS_LONGITUDE = "longitude"
|
||||
VS_HEADING = "positionHeadingDegree"
|
||||
VS_ODOMETER = "odometerValueKilometers"
|
||||
VS_VEHICLE_STATE = "vehicleStateType"
|
||||
VS_TIRE_PRESSURE_FL = "tirePressureFrontLeft"
|
||||
VS_TIRE_PRESSURE_FR = "tirePressureFrontRight"
|
||||
VS_TIRE_PRESSURE_RL = "tirePressureRearLeft"
|
||||
VS_TIRE_PRESSURE_RR = "tirePressureRearRight"
|
||||
BAD_AVG_FUEL_CONSUMPTION = "16383" // Erroneous Values
|
||||
BAD_DISTANCE_TO_EMPTY_FUEL = "16383"
|
||||
BAD_EV_TIME_TO_FULLY_CHARGED = "65535"
|
||||
BAD_TIRE_PRESSURE = "32767"
|
||||
BAD_ODOMETER = "None"
|
||||
BAD_EXTERNAL_TEMP = "-64.0"
|
||||
UNKNOWN = "UNKNOWN"
|
||||
VENTED = "VENTED"
|
||||
LOCATION_VALID = "location_valid"
|
||||
TIMESTAMP_FMT = "%Y-%m-%dT%H:%M:%S%z" // "2020-04-25T23:35:55+0000" // Timestamp Formats
|
||||
POSITION_TIMESTAMP_FMT = "%Y-%m-%dT%H:%M:%SZ" // "2020-04-25T23:35:55Z"
|
||||
ERROR_SOA_403 = "403-soa-unableToParseResponseBody" // G2 Error Codes
|
||||
ERROR_SOA_404 = "404-soa-unableToParseResponseBody" // Bad request body
|
||||
ERROR_INVALID_CREDENTIALS = "InvalidCredentials"
|
||||
ERROR_SERVICE_ALREADY_STARTED = "ServiceAlreadyStarted"
|
||||
ERROR_INVALID_ACCOUNT = "invalidAccount"
|
||||
ERROR_PASSWORD_WARNING = "passwordWarning"
|
||||
ERROR_ACCOUNT_LOCKED = "accountLocked"
|
||||
ERROR_NO_VEHICLES = "noVehiclesOnAccount"
|
||||
ERROR_NO_ACCOUNT = "accountNotFound"
|
||||
ERROR_TOO_MANY_ATTEMPTS = "tooManyAttempts"
|
||||
ERROR_VEHICLE_NOT_IN_ACCOUNT = "vehicleNotInAccount"
|
||||
ERROR_G1_NO_SUBSCRIPTION = "SXM40004" // G1 Error Codes
|
||||
ERROR_G1_STOLEN_VEHICLE = "SXM40005"
|
||||
ERROR_G1_INVALID_PIN = "SXM40006"
|
||||
ERROR_G1_SERVICE_ALREADY_STARTED = "SXM40009"
|
||||
ERROR_G1_PIN_LOCKED = "SXM40017"
|
||||
VEHICLE_ATTRIBUTES = "attributes" //Controller Vehicle Data Dict Keys
|
||||
VEHICLE_STATUS = "status"
|
||||
VEHICLE_ID = "id"
|
||||
VEHICLE_NAME = "nickname"
|
||||
VEHICLE_API_GEN = "api_gen"
|
||||
VEHICLE_LOCK = "lock"
|
||||
VEHICLE_LAST_UPDATE = "last_update_time"
|
||||
VEHICLE_LAST_FETCH = "last_fetch_time"
|
||||
VEHICLE_FEATURES = "features"
|
||||
VEHICLE_SUBSCRIPTION_FEATURES = "subscriptionFeatures"
|
||||
VEHICLE_SUBSCRIPTION_STATUS = "subscriptionStatus"
|
||||
FEATURE_PHEV = "PHEV"
|
||||
FEATURE_REMOTE_START = "RES"
|
||||
FEATURE_G1_TELEMATICS = "g1" // Vehicle has 2016-2018 telematics version.
|
||||
FEATURE_G2_TELEMATICS = "g2" // Vehicle has 2019+ telematics version.
|
||||
FEATURE_G3_TELEMATICS = "g3" // Vehicle has 2019+ telematics version.
|
||||
FEATURE_REMOTE = "REMOTE"
|
||||
FEATURE_SAFETY = "SAFETY"
|
||||
FEATURE_ACTIVE = "ACTIVE"
|
||||
DEFAULT_UPDATE_INTERVAL = 7200
|
||||
DEFAULT_FETCH_INTERVAL = 300
|
||||
// VALID_CLIMATE_OPTIONS = {
|
||||
// CLIMATE: [CLIMATE_DEFAULT],
|
||||
// TEMP_C: [str(_) for _ in range(TEMP_C_MIN, TEMP_C_MAX + 1)],
|
||||
// TEMP_F: [str(_) for _ in range(TEMP_F_MIN, TEMP_F_MAX + 1)],
|
||||
// FAN_SPEED: [FAN_SPEED_AUTO, FAN_SPEED_LOW, FAN_SPEED_MED, FAN_SPEED_HI],
|
||||
// HEAT_SEAT_LEFT: [HEAT_SEAT_OFF, HEAT_SEAT_LOW, HEAT_SEAT_MED, HEAT_SEAT_HI],
|
||||
// HEAT_SEAT_RIGHT: [HEAT_SEAT_OFF, HEAT_SEAT_LOW, HEAT_SEAT_MED, HEAT_SEAT_HI],
|
||||
// MODE: [
|
||||
// MODE_DEFROST,
|
||||
// MODE_FEET_DEFROST,
|
||||
// MODE_FACE,
|
||||
// MODE_FEET,
|
||||
// MODE_SPLIT,
|
||||
// MODE_AUTO,
|
||||
// ],
|
||||
// RECIRCULATE: [RECIRCULATE_OFF, RECIRCULATE_ON],
|
||||
// REAR_AC: [REAR_AC_OFF, REAR_AC_ON],
|
||||
// REAR_DEFROST: [REAR_DEFROST_OFF, REAR_DEFROST_ON],
|
||||
// START_CONFIG: [START_CONFIG_DEFAULT_EV, START_CONFIG_DEFAULT_RES],
|
||||
// RUNTIME: [str(RUNTIME_DEFAULT)],
|
||||
// }
|
||||
|
||||
// BAD_SENSOR_VALUES = [
|
||||
// BAD_AVG_FUEL_CONSUMPTION,
|
||||
// BAD_DISTANCE_TO_EMPTY_FUEL,
|
||||
// BAD_EV_TIME_TO_FULLY_CHARGED,
|
||||
// BAD_TIRE_PRESSURE,
|
||||
// BAD_ODOMETER,
|
||||
// BAD_EXTERNAL_TEMP,
|
||||
// ]
|
||||
// BAD_BINARY_SENSOR_VALUES = [UNKNOWN, VENTED, NOT_EQUIPPED]
|
||||
)
|
15
example/config.sample.yaml
Normal file
15
example/config.sample.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
credentials:
|
||||
username: user@email.com
|
||||
password: "Secr#TPassW0rd"
|
||||
pin: "PIN"
|
||||
deviceId: GENERATE-DEVICE-ID
|
||||
deviceName: Golang Integration
|
||||
mysubaru:
|
||||
region: USA
|
||||
mqtt:
|
||||
client_id: mysubaru
|
||||
username: mysubaru
|
||||
password:
|
||||
host: mqtt.hostname.com
|
||||
port: 1883
|
||||
log: info
|
129
example/example.go
Normal file
129
example/example.go
Normal file
@ -0,0 +1,129 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
mysubaru "github.com/alex-savin/go-mysubaru"
|
||||
"github.com/alex-savin/go-mysubaru/config"
|
||||
)
|
||||
|
||||
// var log = *slog.Logger
|
||||
var cfg = &config.Config{}
|
||||
|
||||
const (
|
||||
LoggingOutputJson = "JSON"
|
||||
LoggingOutputText = "TEXT"
|
||||
)
|
||||
|
||||
func configureLogging(config *config.Logging) *slog.Logger { //nolint:unparam
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var level slog.Level
|
||||
if err := level.UnmarshalText([]byte(config.Level)); err != nil {
|
||||
slog.Warn(err.Error())
|
||||
slog.Warn(fmt.Sprintf("logging level not recognized, defaulting to level %s", slog.LevelInfo.String()))
|
||||
level = slog.LevelInfo
|
||||
}
|
||||
|
||||
var handler slog.Handler
|
||||
switch config.Output {
|
||||
case LoggingOutputJson:
|
||||
handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: cfg.Logging.Source, Level: level})
|
||||
case LoggingOutputText:
|
||||
handler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{AddSource: cfg.Logging.Source, Level: level})
|
||||
default:
|
||||
handler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{AddSource: cfg.Logging.Source, Level: level})
|
||||
}
|
||||
|
||||
return slog.New(handler)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
var err error
|
||||
cfg, err = config.New()
|
||||
if err != nil { // Handle errors reading the config file
|
||||
slog.Error("Fatal error config file", "error", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logger := configureLogging(cfg.Logging)
|
||||
|
||||
logger.Debug("printting config", "config", cfg)
|
||||
|
||||
ms, _ := mysubaru.New(logger, &cfg.MySubaru)
|
||||
// mysubaru.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
|
||||
// subaru1 := mysubaru.GetVehicleByVIN("4S4BTGND8L3137058")
|
||||
// fmt.Printf("SUBARU #2 (Vehicle Status):\n")
|
||||
// subaru1.GetVehicleStatus()
|
||||
// fmt.Printf("SUBARU #2 (Vehicle Condition):\n")
|
||||
// subaru1.GetVehicleCondition()
|
||||
// fmt.Printf("SUBARU #1: %+v\n", subaru1)
|
||||
// fmt.Printf("GEN #1: %+v\n\n", subaru1.getAPIGen())
|
||||
|
||||
// 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 OPL_MIL PANPM-TUIRWAOC PWAAADWWAP RAB_MIL RCC REARBRK RES RESCC RHSF RPOI RPOIA RTGU RVFS SRH_MIL SRS_MIL TEL_MIL TIF_35 TIR_33 TLD TPMS_MIL VALET VDC_MIL WASH_MIL g3
|
||||
subaru := ms.GetVehicleByVIN("4S4BTGPD0P3199198")
|
||||
subaru.GetLocation(true)
|
||||
// subaru.EngineStart()
|
||||
fmt.Printf("SUBARU #1 (Vehicle Status):\n")
|
||||
subaru.GetVehicleStatus()
|
||||
// fmt.Printf("SUBARU #1 (Vehicle Condition):\n")
|
||||
// subaru.GetVehicleCondition()
|
||||
// fmt.Printf("SUBARU #1: %+v\n", subaru)
|
||||
// subaru.GetClimatePresets()
|
||||
// subaru.GetClimateUserPresets()
|
||||
// fmt.Printf("SUBARU #2: %+v\n", subaru)
|
||||
// subaru.GetVehicleHealth()
|
||||
// subaru.GetFeaturesList()
|
||||
|
||||
// subaru.LightsStart()
|
||||
// time.Sleep(10 * time.Second)
|
||||
// subaru.LightsStop()
|
||||
|
||||
// subaru := mysubaru.GetVehicles()[0]
|
||||
|
||||
// fmt.Printf("SUBARU: %+v\n", subaru)
|
||||
|
||||
// fmt.Printf("Subaru Gen: %+v\n\n", subaru.getAPIGen())
|
||||
|
||||
// subaru.EngineOn()
|
||||
// subaru.GetLocation(false)
|
||||
|
||||
// subaru.GetVehicleStatus()
|
||||
// subaru.GetVehicleCondition()
|
||||
// fmt.Printf("SUBARU: %+v\n", subaru)
|
||||
|
||||
// subaru.GetClimateQuickPresets()
|
||||
// subaru.GetClimatePresets()
|
||||
// subaru.GetClimateUserPresets()
|
||||
|
||||
// subaru.LightsStart()
|
||||
// time.Sleep(15 * time.Second)
|
||||
// subaru.LightsStop()
|
||||
|
||||
// subaru.listDevices()
|
||||
|
||||
// subaru.GetVehicleStatus()
|
||||
|
||||
// subaru.GetClimateSettings()
|
||||
|
||||
// subaru.EngineStart()
|
||||
// time.Sleep(15 * time.Second)
|
||||
// subaru.EngineStop()
|
||||
|
||||
// subaru.Unlock()
|
||||
// time.Sleep(20 * time.Second)
|
||||
// subaru.Lock()
|
||||
|
||||
// subaru.LightsStart()
|
||||
// time.Sleep(15 * time.Second)
|
||||
// subaru.LightsStop()
|
||||
|
||||
// subaru.GetLocation()
|
||||
}
|
31
example/go.mod
Normal file
31
example/go.mod
Normal file
@ -0,0 +1,31 @@
|
||||
module example
|
||||
|
||||
go 1.21
|
||||
|
||||
require github.com/alex-savin/go-mysubaru v0.0.0-20231207172256-23b354b0f2c1
|
||||
|
||||
require (
|
||||
github.com/Jeffail/gabs/v2 v2.7.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-resty/resty/v2 v2.10.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.18.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
38
go.mod
Normal file
38
go.mod
Normal file
@ -0,0 +1,38 @@
|
||||
module github.com/alex-savin/go-mysubaru
|
||||
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/Jeffail/gabs v1.4.0
|
||||
github.com/Jeffail/gabs/v2 v2.7.0
|
||||
github.com/go-resty/resty/v2 v2.16.5
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/viper v1.20.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/sagikazarmark/locafero v0.9.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.14.0 // indirect
|
||||
github.com/spf13/cast v1.8.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
129
main.go
Normal file
129
main.go
Normal file
@ -0,0 +1,129 @@
|
||||
package mysubaru
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
mysubaru "github.com/alex-savin/go-mysubaru"
|
||||
"github.com/alex-savin/go-mysubaru/config"
|
||||
)
|
||||
|
||||
// var log = *slog.Logger
|
||||
var cfg = &config.Config{}
|
||||
|
||||
const (
|
||||
LoggingOutputJson = "JSON"
|
||||
LoggingOutputText = "TEXT"
|
||||
)
|
||||
|
||||
func configureLogging(config *config.Logging) *slog.Logger { //nolint:unparam
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var level slog.Level
|
||||
if err := level.UnmarshalText([]byte(config.Level)); err != nil {
|
||||
slog.Warn(err.Error())
|
||||
slog.Warn(fmt.Sprintf("logging level not recognized, defaulting to level %s", slog.LevelInfo.String()))
|
||||
level = slog.LevelInfo
|
||||
}
|
||||
|
||||
var handler slog.Handler
|
||||
switch config.Output {
|
||||
case LoggingOutputJson:
|
||||
handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: cfg.Logging.Source, Level: level})
|
||||
case LoggingOutputText:
|
||||
handler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{AddSource: cfg.Logging.Source, Level: level})
|
||||
default:
|
||||
handler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{AddSource: cfg.Logging.Source, Level: level})
|
||||
}
|
||||
|
||||
return slog.New(handler)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
var err error
|
||||
cfg, err = config.New()
|
||||
if err != nil { // Handle errors reading the config file
|
||||
slog.Error("Fatal error config file", "error", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logger := configureLogging(cfg.Logging)
|
||||
|
||||
logger.Debug("printting config", "config", cfg)
|
||||
|
||||
ms, _ := mysubaru.New(logger, &cfg.MySubaru)
|
||||
// mysubaru.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
|
||||
// subaru1 := mysubaru.GetVehicleByVIN("4S4BTGND8L3137058")
|
||||
// fmt.Printf("SUBARU #2 (Vehicle Status):\n")
|
||||
// subaru1.GetVehicleStatus()
|
||||
// fmt.Printf("SUBARU #2 (Vehicle Condition):\n")
|
||||
// subaru1.GetVehicleCondition()
|
||||
// fmt.Printf("SUBARU #1: %+v\n", subaru1)
|
||||
// fmt.Printf("GEN #1: %+v\n\n", subaru1.getAPIGen())
|
||||
|
||||
// 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 OPL_MIL PANPM-TUIRWAOC PWAAADWWAP RAB_MIL RCC REARBRK RES RESCC RHSF RPOI RPOIA RTGU RVFS SRH_MIL SRS_MIL TEL_MIL TIF_35 TIR_33 TLD TPMS_MIL VALET VDC_MIL WASH_MIL g3
|
||||
subaru := ms.GetVehicleByVIN("4S4BTGPD0P3199198")
|
||||
subaru.GetLocation(true)
|
||||
// subaru.EngineStart()
|
||||
fmt.Printf("SUBARU #1 (Vehicle Status):\n")
|
||||
subaru.GetVehicleStatus()
|
||||
// fmt.Printf("SUBARU #1 (Vehicle Condition):\n")
|
||||
// subaru.GetVehicleCondition()
|
||||
// fmt.Printf("SUBARU #1: %+v\n", subaru)
|
||||
// subaru.GetClimatePresets()
|
||||
// subaru.GetClimateUserPresets()
|
||||
// fmt.Printf("SUBARU #2: %+v\n", subaru)
|
||||
// subaru.GetVehicleHealth()
|
||||
// subaru.GetFeaturesList()
|
||||
|
||||
// subaru.LightsStart()
|
||||
// time.Sleep(10 * time.Second)
|
||||
// subaru.LightsStop()
|
||||
|
||||
// subaru := mysubaru.GetVehicles()[0]
|
||||
|
||||
// fmt.Printf("SUBARU: %+v\n", subaru)
|
||||
|
||||
// fmt.Printf("Subaru Gen: %+v\n\n", subaru.getAPIGen())
|
||||
|
||||
// subaru.EngineOn()
|
||||
// subaru.GetLocation(false)
|
||||
|
||||
// subaru.GetVehicleStatus()
|
||||
// subaru.GetVehicleCondition()
|
||||
// fmt.Printf("SUBARU: %+v\n", subaru)
|
||||
|
||||
// subaru.GetClimateQuickPresets()
|
||||
// subaru.GetClimatePresets()
|
||||
// subaru.GetClimateUserPresets()
|
||||
|
||||
// subaru.LightsStart()
|
||||
// time.Sleep(15 * time.Second)
|
||||
// subaru.LightsStop()
|
||||
|
||||
// subaru.listDevices()
|
||||
|
||||
// subaru.GetVehicleStatus()
|
||||
|
||||
// subaru.GetClimateSettings()
|
||||
|
||||
// subaru.EngineStart()
|
||||
// time.Sleep(15 * time.Second)
|
||||
// subaru.EngineStop()
|
||||
|
||||
// subaru.Unlock()
|
||||
// time.Sleep(20 * time.Second)
|
||||
// subaru.Lock()
|
||||
|
||||
// subaru.LightsStart()
|
||||
// time.Sleep(15 * time.Second)
|
||||
// subaru.LightsStop()
|
||||
|
||||
// subaru.GetLocation()
|
||||
}
|
296
mysubaru.go
Normal file
296
mysubaru.go
Normal file
@ -0,0 +1,296 @@
|
||||
package mysubaru
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Jeffail/gabs/v2"
|
||||
)
|
||||
|
||||
// Response .
|
||||
type Response struct {
|
||||
Success bool `json:"success"` // true | false
|
||||
ErrorCode string `json:"errorCode,omitempty"` // string | Error message if Success is false
|
||||
DataName string `json:"dataName"` // string | Describes the structure which is incleded in Data field
|
||||
Data interface{} `json:"data"` // Data struct
|
||||
}
|
||||
|
||||
// Account .
|
||||
type Account struct {
|
||||
MarketID int `json:"marketId"`
|
||||
CreatedDate int64 `json:"createdDate"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
ZipCode string `json:"zipCode"`
|
||||
AccountKey int `json:"accountKey"`
|
||||
LastLoginDate time.Time `json:"lastLoginDate"`
|
||||
ZipCode5 string `json:"zipCode5"`
|
||||
}
|
||||
|
||||
// Customer .
|
||||
type Customer struct {
|
||||
SessionCustomer string `json:"sessionCustomer"`
|
||||
Email string `json:"email"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
Zip string `json:"zip"`
|
||||
OemCustID string `json:"oemCustId"`
|
||||
Phone string `json:"phone"`
|
||||
}
|
||||
|
||||
// SessionData .
|
||||
// "dataName": "sessionData"
|
||||
type SessionData struct {
|
||||
SessionChanged bool `json:"sessionChanged"`
|
||||
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"`
|
||||
Vehicles []VehicleData `json:"vehicles"`
|
||||
RightToRepairEnabled bool `json:"rightToRepairEnabled"`
|
||||
RightToRepairStates string `json:"rightToRepairStates"`
|
||||
CurrentVehicleIndex int `json:"currentVehicleIndex"`
|
||||
HandoffToken string `json:"handoffToken"`
|
||||
EnableXtime bool `json:"enableXtime"`
|
||||
TermsAndConditionsAccepted bool `json:"termsAndConditionsAccepted"`
|
||||
RightToRepairStartYear int `json:"rightToRepairStartYear"`
|
||||
DigitalGlobeConnectID string `json:"digitalGlobeConnectId"`
|
||||
DigitalGlobeImageTileService string `json:"digitalGlobeImageTileService"`
|
||||
DigitalGlobeTransparentTileService string `json:"digitalGlobeTransparentTileService"`
|
||||
TomtomKey string `json:"tomtomKey"`
|
||||
SatelliteViewEnabled bool `json:"satelliteViewEnabled"`
|
||||
RegisteredDevicePermanent bool `json:"registeredDevicePermanent"`
|
||||
}
|
||||
|
||||
// Vehicle .
|
||||
// "dataName": "vehicle"
|
||||
type VehicleData struct {
|
||||
Customer Customer `json:"customer"` // Customer struct
|
||||
UserOemCustID string `json:"userOemCustId"` // CRM-631-HQN48K
|
||||
OemCustID string `json:"oemCustId"` // CRM-631-HQN48K
|
||||
Active bool `json:"active"` // true | false
|
||||
Email string `json:"email"` // null | email@address.com
|
||||
FirstName string `json:"firstName"` // null | First Name
|
||||
LastName string `json:"lastName"` // null | Last Name
|
||||
Zip string `json:"zip"` // 12345
|
||||
Phone string `json:"phone"` // null | 123-456-7890
|
||||
StolenVehicle bool `json:"stolenVehicle"` // true | false
|
||||
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"
|
||||
Vin string `json:"vin"` // 4Y1SL65848Z411439
|
||||
VehicleKey int64 `json:"vehicleKey"` // 3832950
|
||||
Nickname string `json:"nickname"` // Subaru Outback LXT
|
||||
ModelName string `json:"modelName"` // Outback
|
||||
ModelYear string `json:"modelYear"` // 2020
|
||||
ModelCode string `json:"modelCode"` // LDJ
|
||||
ExtDescrip string `json:"extDescrip"` // Abyss Blue Pearl (ext color)
|
||||
IntDescrip string `json:"intDescrip"` // Gray (int color)
|
||||
TransCode string `json:"transCode"` // CVT
|
||||
EngineSize float64 `json:"engineSize"` // 2.4
|
||||
Phev bool `json:"phev"` // null
|
||||
CachedStateCode string `json:"cachedStateCode"` // NJ
|
||||
LicensePlate string `json:"licensePlate"` // NJ
|
||||
LicensePlateState string `json:"licensePlateState"` // ABCDEF
|
||||
SubscriptionStatus string `json:"subscriptionStatus"` // ACTIVE
|
||||
SubscriptionFeatures []string `json:"subscriptionFeatures"` // "REMOTE", "SAFETY", "Retail"
|
||||
SubscriptionPlans []string `json:"subscriptionPlans"` // []
|
||||
VehicleGeoPosition GeoPosition `json:"vehicleGeoPosition"` // GeoPosition struct
|
||||
AccessLevel int `json:"accessLevel"` // -1
|
||||
VehicleMileage int `json:"vehicleMileage"` // null
|
||||
CrmRightToRepair bool `json:"crmRightToRepair"` // true | false
|
||||
AuthorizedVehicle bool `json:"authorizedVehicle"` // false | true
|
||||
NeedMileagePrompt bool `json:"needMileagePrompt"` // false | true
|
||||
RemoteServicePinExist bool `json:"remoteServicePinExist"` // true | false
|
||||
NeedEmergencyContactPrompt bool `json:"needEmergencyContactPrompt"` // false | true
|
||||
Show3GSunsetBanner bool `json:"show3gSunsetBanner"` // false | true
|
||||
Provisioned bool `json:"provisioned"` // true | false
|
||||
TimeZone time.Location `json:"timeZone"` // America/New_York
|
||||
SunsetUpgraded bool `json:"sunsetUpgraded"` // true | false
|
||||
PreferredDealer string `json:"preferredDealer"` // null |
|
||||
}
|
||||
|
||||
// GeoPosition .
|
||||
type GeoPosition struct {
|
||||
Latitude float64 `json:"latitude"` // 40.700184
|
||||
Longitude float64 `json:"longitude"` // -74.401375
|
||||
Speed float64 `json:"speed,omitempty"` // 62
|
||||
Heading int `json:"heading,omitempty"` // 155
|
||||
Timestamp string `json:"timestamp,string"` // "2021-12-22T13:14:47"
|
||||
}
|
||||
|
||||
// type GeoPositionTime time.Time
|
||||
|
||||
// func (g *GeoPositionTime) UnmarshalJSON(b []byte) error {
|
||||
// s := strings.Trim(string(b), "\"")
|
||||
// t, err := time.Parse("2006-01-02T15:04:05", s)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// *g = GeoPositionTime(t)
|
||||
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// 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 {
|
||||
VehicleId int64 `json:"vhsId"` // + 9969776690 5198812434
|
||||
OdometerValue int `json:"odometerValue"` // + 23787
|
||||
OdometerValueKm int `json:"odometerValueKilometers"` // + 38273
|
||||
EventDate int64 `json:"eventDate"` // + 1701896993000
|
||||
EventDateStr string `json:"eventDateStr"` // + 2023-12-06T21:09+0000
|
||||
EventDateCarUser int64 `json:"eventDateCarUser"` // + 1701896993000
|
||||
EventDateStrCarUser string `json:"eventDateStrCarUser"` // + 2023-12-06T21:09+0000
|
||||
Latitude float64 `json:"latitude"` // + 40.700183
|
||||
Longitude float64 `json:"longitude"` // + -74.401372
|
||||
Heading string `json:"positionHeadingDegree"` // + "154"
|
||||
DistanceToEmptyFuelMiles float64 `json:"distanceToEmptyFuelMiles"` // + 209.4
|
||||
DistanceToEmptyFuelKilometers int `json:"distanceToEmptyFuelKilometers"` // + 337
|
||||
DistanceToEmptyFuelMiles10s int `json:"distanceToEmptyFuelMiles10s"` // + 210
|
||||
DistanceToEmptyFuelKilometers10s int `json:"distanceToEmptyFuelKilometers10s"` // + 340
|
||||
AvgFuelConsumptionMpg float64 `json:"avgFuelConsumptionMpg"` // + 18.4
|
||||
AvgFuelConsumptionLitersPer100Kilometers float64 `json:"avgFuelConsumptionLitersPer100Kilometers"` // + 12.8
|
||||
RemainingFuelPercent int `json:"remainingFuelPercent"` // + 82
|
||||
TirePressureFrontLeft string `json:"tirePressureFrontLeft"` // + "2275"
|
||||
TirePressureFrontRight string `json:"tirePressureFrontRight"` // + "2344"
|
||||
TirePressureRearLeft string `json:"tirePressureRearLeft"` // + "2413"
|
||||
TirePressureRearRight string `json:"tirePressureRearRight"` // + "2344"
|
||||
TirePressureFrontLeftPsi string `json:"tirePressureFrontLeftPsi"` // + "33"
|
||||
TirePressureFrontRightPsi string `json:"tirePressureFrontRightPsi"` // + "34"
|
||||
TirePressureRearLeftPsi string `json:"tirePressureRearLeftPsi"` // + "35"
|
||||
TirePressureRearRightPsi string `json:"tirePressureRearRightPsi"` // + "34"
|
||||
TyreStatusFrontLeft string `json:"tyreStatusFrontLeft"` // + "UNKNOWN"
|
||||
TyreStatusFrontRight string `json:"tyreStatusFrontRight"` // + "UNKNOWN"
|
||||
TyreStatusRearLeft string `json:"tyreStatusRearLeft"` // + "UNKNOWN"
|
||||
TyreStatusRearRight string `json:"tyreStatusRearRight"` // + "UNKNOWN"
|
||||
EvStateOfChargePercent float64 `json:"evStateOfChargePercent,omitempty"` // + null
|
||||
EvDistanceToEmptyMiles int `json:"evDistanceToEmptyMiles,omitempty"` // + null
|
||||
EvDistanceToEmptyKilometers int `json:"evDistanceToEmptyKilometers,omitempty"` // + null
|
||||
EvDistanceToEmptyByStateMiles int `json:"evDistanceToEmptyByStateMiles,omitempty"` // + null
|
||||
EvDistanceToEmptyByStateKilometers int `json:"evDistanceToEmptyByStateKilometers,omitempty"` // + null
|
||||
VehicleStateType string `json:"vehicleStateType"` // + "IGNITION_OFF | IGNITION_ON"
|
||||
WindowFrontLeftStatus string `json:"windowFrontLeftStatus"` // + "CLOSE"
|
||||
WindowFrontRightStatus string `json:"windowFrontRightStatus"` // + "CLOSE"
|
||||
WindowRearLeftStatus string `json:"windowRearLeftStatus"` // + "CLOSE"
|
||||
WindowRearRightStatus string `json:"windowRearRightStatus"` // + "CLOSE"
|
||||
WindowSunroofStatus string `json:"windowSunroofStatus"` // + "UNKNOWN"
|
||||
DoorBootPosition string `json:"doorBootPosition"` // CLOSED
|
||||
DoorEngineHoodPosition string `json:"doorEngineHoodPosition"` // CLOSED
|
||||
DoorFrontLeftPosition string `json:"doorFrontLeftPosition"` // CLOSED
|
||||
DoorFrontRightPosition string `json:"doorFrontRightPosition"` // CLOSED
|
||||
DoorRearLeftPosition string `json:"doorRearLeftPosition"` // CLOSED
|
||||
DoorRearRightPosition string `json:"doorRearRightPosition"` // CLOSED
|
||||
DoorBootLockStatus string `json:"doorBootLockStatus"` // LOCKED
|
||||
DoorFrontLeftLockStatus string `json:"doorFrontLeftLockStatus"` // LOCKED
|
||||
DoorFrontRightLockStatus string `json:"doorFrontRightLockStatus"` // LOCKED
|
||||
DoorRearLeftLockStatus string `json:"doorRearLeftLockStatus"` // LOCKED
|
||||
DoorRearRightLockStatus string `json:"doorRearRightLockStatus"` // LOCKED
|
||||
}
|
||||
|
||||
// VehicleCondition .
|
||||
// "dataName":"remoteServiceStatus"
|
||||
// "remoteServiceType":"condition"
|
||||
type VehicleCondition struct {
|
||||
VehicleStateType string `json:"vehicleStateType"` // "IGNITION_OFF",
|
||||
AvgFuelConsumption float64 `json:"avgFuelConsumption"` // null,
|
||||
AvgFuelConsumptionUnit string `json:"avgFuelConsumptionUnit"` // "MPG",
|
||||
DistanceToEmptyFuel int `json:"distanceToEmptyFuel"` // null,
|
||||
DistanceToEmptyFuelUnit string `json:"distanceToEmptyFuelUnit"` // "MILES",
|
||||
RemainingFuelPercent int `json:"remainingFuelPercent"` // "66",
|
||||
Odometer int `json:"odometer"` // 92,
|
||||
OdometerUnit string `json:"odometerUnit"` // "MILES",
|
||||
TirePressureFrontLeft float64 `json:"tirePressureFrontLeft"` // null,
|
||||
TirePressureFrontLeftUnit string `json:"tirePressureFrontLeftUnit"` // "PSI",
|
||||
TirePressureFrontRight float64 `json:"tirePressureFrontRight"` // null,
|
||||
TirePressureFrontRightUnit string `json:"tirePressureFrontRightUnit"` // "PSI",
|
||||
TirePressureRearLeft float64 `json:"tirePressureRearLeft"` // null,
|
||||
TirePressureRearLeftUnit string `json:"tirePressureRearLeftUnit"` // "PSI",
|
||||
TirePressureRearRight float64 `json:"tirePressureRearRight"` // null,
|
||||
TirePressureRearRightUnit string `json:"tirePressureRearRightUnit"` // "PSI",
|
||||
DoorBootPosition string `json:"doorBootPosition"` // "CLOSED",
|
||||
DoorEngineHoodPosition string `json:"doorEngineHoodPosition"` // "CLOSED",
|
||||
DoorFrontLeftPosition string `json:"doorFrontLeftPosition"` // "CLOSED",
|
||||
DoorFrontRightPosition string `json:"doorFrontRightPosition"` // "CLOSED",
|
||||
DoorRearLeftPosition string `json:"doorRearLeftPosition"` // "CLOSED",
|
||||
DoorRearRightPosition string `json:"doorRearRightPosition"` // "CLOSED"
|
||||
WindowFrontLeftStatus string `json:"windowFrontLeftStatus"` // "CLOSE",
|
||||
WindowFrontRightStatus string `json:"windowFrontRightStatus"` // "CLOSE",
|
||||
WindowRearLeftStatus string `json:"windowRearLeftStatus"` // "CLOSE",
|
||||
WindowRearRightStatus string `json:"windowRearRightStatus"` // "CLOSE",
|
||||
WindowSunroofStatus string `json:"windowSunroofStatus"` // "CLOSE",
|
||||
EvDistanceToEmpty int `json:"evDistanceToEmpty"` // null,
|
||||
EvDistanceToEmptyUnit string `json:"evDistanceToEmptyUnit"` // null,
|
||||
EvChargerStateType string `json:"evChargerStateType"` // null,
|
||||
EvIsPluggedIn bool `json:"evIsPluggedIn"` // null,
|
||||
EvStateOfChargeMode string `json:"evStateOfChargeMode"` // null,
|
||||
EvTimeToFullyCharged string `json:"evTimeToFullyCharged"` // null,
|
||||
EvStateOfChargePercent int `json:"evStateOfChargePercent"` // null,
|
||||
LastUpdatedTime string `json:"lastUpdatedTime,string"` // "2023-04-10T17:50:54+0000",
|
||||
}
|
||||
|
||||
// ClimateSettings .
|
||||
// "dataName":null
|
||||
// type ClimateSettings struct {
|
||||
// RunTimeMinutes string `json:"runTimeMinutes"`
|
||||
// StartConfiguration string `json:"startConfiguration"`
|
||||
// AirConditionOn string `json:"airConditionOn"`
|
||||
// OuterAirCirculation string `json:"outerAirCirculation"`
|
||||
// ClimateZoneFrontAirMode string `json:"climateZoneFrontAirMode"`
|
||||
// ClimateZoneFrontTemp string `json:"climateZoneFrontTemp"`
|
||||
// ClimateZoneFrontAirVolume string `json:"climateZoneFrontAirVolume"`
|
||||
// HeatedSeatFrontLeft string `json:"heatedSeatFrontLeft"`
|
||||
// HeatedSeatFrontRight string `json:"heatedSeatFrontRight"`
|
||||
// HeatedRearWindowActive string `json:"heatedRearWindowActive"`
|
||||
// }
|
||||
|
||||
// ServiceRequest .
|
||||
// "dataName": "remoteServiceStatus"
|
||||
type ServiceRequest struct {
|
||||
ServiceRequestID *string `json:"serviceRequestId,omitempty"` // 4S4BTGND8L3137058_1640294426029_19_@NGTP
|
||||
Success bool `json:"success"` // false | true
|
||||
Cancelled bool `json:"cancelled"` // false | true
|
||||
RemoteServiceType string `json:"remoteServiceType"` // unlock | lock | locate | vehicleStatus | lightsOnly | engineStart | engineStop | phevChargeNow | condition
|
||||
RemoteServiceState string `json:"remoteServiceState"` // started | finished | stopping
|
||||
SubState *string `json:"subState,omitempty"` // null
|
||||
ErrorCode *string `json:"errorCode,omitempty"` // null:null
|
||||
Result *gabs.Container `json:"result,omitempty"` // null
|
||||
UpdateTime *time.Time `json:"updateTime,omitempty"` // timestamp
|
||||
Vin string `json:"vin"` // 4S4BTGND8L3137058
|
||||
}
|
||||
|
||||
// ErrorResponse .
|
||||
// "dataName":"errorResponse"
|
||||
type ErrorResponse struct {
|
||||
ErrorLabel string `json:"errorLabel"` // "404-soa-unableToParseResponseBody"
|
||||
ErrorDescription *string `json:"errorDescription,omitempty"` //null
|
||||
}
|
||||
|
||||
// climateSettings: [ climateSettings ]
|
||||
// climateZoneFrontTempCelsius: [for _ in range(15, 30 + 1)]
|
||||
// climateZoneFrontTemp: [for _ in range(60, 85 + 1)]
|
||||
// climateZoneFrontAirVolume: [ AUTO | 2 | 4 | 7 ]
|
||||
// heatedSeatFrontLeft: [ OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT ]
|
||||
// heatedSeatFrontRight: [ OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT ]
|
||||
// climateZoneFrontAirMode: [ WINDOW | FEET_WINDOW | FACE | FEET | FEET_FACE_BALANCED | AUTO ]
|
||||
// outerAirCirculation: [ outsideAir, recirculation ]
|
||||
// airConditionOn: [ false | true ]
|
||||
// heatedRearWindowActive: [ false | true ]
|
||||
// startConfiguration: [ start_Climate_Control_only_allow_key_in_ignition | START_ENGINE_ALLOW_KEY_IN_IGNITION ]
|
||||
// runTimeMinutes: [ 10 ],
|
||||
|
||||
type VehicleHealthItem struct {
|
||||
B2cCode string `json:"b2cCode"`
|
||||
FeatureCode string `json:"featureCode"`
|
||||
IsTrouble bool `json:"isTrouble"`
|
||||
OnDaiID int `json:"onDaiId"`
|
||||
OnDates []string `json:"onDates"`
|
||||
WarningCode int `json:"warningCode"`
|
||||
}
|
143
utils.go
Normal file
143
utils.go
Normal file
@ -0,0 +1,143 @@
|
||||
package mysubaru
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// VinCheck - Vehicle Identification Number check digit validation
|
||||
// Parameter: string - 17 digit VIN
|
||||
// Return:
|
||||
//
|
||||
// 1- boolean - Validity flag. Set to true if VIN check digit is correct, false otherwise.
|
||||
// 2- string - Valid VIN. Same VIN passed as parameter but with the correct check digit on it.
|
||||
func vinCheck(vin string) (bool, string) {
|
||||
var valid = false
|
||||
vin = strings.ToUpper(vin)
|
||||
var retVin = vin
|
||||
|
||||
if len(vin) == 17 {
|
||||
traSum := transcodeDigits(vin)
|
||||
checkNum := math.Mod(float64(traSum), 11)
|
||||
var checkDigit byte
|
||||
if checkNum == 10 {
|
||||
checkDigit = byte('X')
|
||||
} else {
|
||||
checkDigitTemp := strconv.Itoa(int(checkNum))
|
||||
checkDigit = checkDigitTemp[len(checkDigitTemp)-1]
|
||||
}
|
||||
if retVin[8] == checkDigit {
|
||||
valid = true
|
||||
}
|
||||
retVin = retVin[:8] + string(checkDigit) + retVin[9:]
|
||||
} else {
|
||||
valid = false
|
||||
retVin = ""
|
||||
}
|
||||
|
||||
return valid, retVin
|
||||
}
|
||||
|
||||
func transcodeDigits(vin string) int {
|
||||
var digitSum = 0
|
||||
var code int
|
||||
for i, chr := range vin {
|
||||
code = 0
|
||||
|
||||
switch chr {
|
||||
case 'A', 'J', '1':
|
||||
code = 1
|
||||
case 'B', 'K', 'S', '2':
|
||||
code = 2
|
||||
case 'C', 'L', 'T', '3':
|
||||
code = 3
|
||||
case 'D', 'M', 'U', '4':
|
||||
code = 4
|
||||
case 'E', 'N', 'V', '5':
|
||||
code = 5
|
||||
case 'F', 'W', '6':
|
||||
code = 6
|
||||
case 'G', 'P', 'X', '7':
|
||||
code = 7
|
||||
case 'H', 'Y', '8':
|
||||
code = 8
|
||||
case 'R', 'Z', '9':
|
||||
code = 9
|
||||
case 'I', 'O', 'Q':
|
||||
code = 0
|
||||
}
|
||||
switch i + 1 {
|
||||
case 1, 11:
|
||||
digitSum += code * 8
|
||||
case 2, 12:
|
||||
digitSum += code * 7
|
||||
case 3, 13:
|
||||
digitSum += code * 6
|
||||
case 4, 14:
|
||||
digitSum += code * 5
|
||||
case 5, 15:
|
||||
digitSum += code * 4
|
||||
case 6, 16:
|
||||
digitSum += code * 3
|
||||
case 7, 17:
|
||||
digitSum += code * 2
|
||||
case 8:
|
||||
digitSum += code * 10
|
||||
case 9:
|
||||
digitSum += code * 0
|
||||
case 10:
|
||||
digitSum += code * 9
|
||||
}
|
||||
}
|
||||
|
||||
return digitSum
|
||||
}
|
||||
|
||||
// isNilFixed .
|
||||
// func isNil(i interface{}) bool {
|
||||
// if i == nil {
|
||||
// return true
|
||||
// }
|
||||
// switch reflect.TypeOf(i).Kind() {
|
||||
// case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice:
|
||||
// return reflect.ValueOf(i).IsNil()
|
||||
// }
|
||||
// return false
|
||||
// }
|
||||
|
||||
// timeTrack .
|
||||
func timeTrack(name string) {
|
||||
start := time.Now()
|
||||
fmt.Printf("%s took %v\n", name, time.Since(start))
|
||||
}
|
||||
|
||||
// timestamp is a function
|
||||
func timestamp() string {
|
||||
return strconv.FormatInt(time.Now().UnixNano()/1000000, 10)
|
||||
}
|
||||
|
||||
// contains .
|
||||
func contains(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// urlToGen .
|
||||
func urlToGen(url string, gen string) string {
|
||||
var re = regexp.MustCompile(`api_gen`)
|
||||
// dirty trick for current G3
|
||||
if gen == "g3" {
|
||||
gen = "g2"
|
||||
}
|
||||
url = re.ReplaceAllString(url, gen)
|
||||
|
||||
return url
|
||||
}
|
1283
vehicle.go
Normal file
1283
vehicle.go
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user