Files
mysubaru/mysubaru.go
Alex Savin a455733f8b
All checks were successful
Golan Testing / testing (1.24.x, ubuntu-latest) (push) Successful in 25s
Enhance JSON unmarshalling for CustomTime types to handle null values and improve string parsing
2025-07-08 22:21:02 -04:00

526 lines
33 KiB
Go

package mysubaru
import (
"encoding/json"
"errors"
"fmt"
"log/slog"
"strings"
"time"
)
// Response represents the structure of a response from the MySubaru API.
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,omitempty"` // string | Describes the structure which is included in Data field
Data json.RawMessage `json:"data"` // Data struct
}
// parse parses the JSON response from the MySubaru API into a Response struct.
func (r *Response) parse(b []byte, logger *slog.Logger) (*Response, error) {
err := json.Unmarshal(b, &r)
if err != nil {
logger.Error("error while parsing json", "error", err.Error())
return nil, errors.New("error while parsing json: " + err.Error())
}
if !r.Success && r.ErrorCode != "" {
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.
type Request struct {
Vin string `json:"vin"` //
Pin string `json:"pin"` //
Delay int `json:"delay,string,omitempty"` //
ForceKeyInCar *bool `json:"forceKeyInCar,string,omitempty"` //
UnlockDoorType *string `json:"unlockDoorType,omitempty"` // [ ALL_DOORS_CMD | FRONT_LEFT_DOOR_CMD | ALL_DOORS_CMD ]
Horn *string `json:"horn,omitempty"` //
ClimateSettings *string `json:"climateSettings,omitempty"` //
ClimateZoneFrontTemp *string `json:"climateZoneFrontTemp,omitempty"` //
ClimateZoneFrontAirMode *string `json:"climateZoneFrontAirMode,omitempty"` //
ClimateZoneFrontAirVolume *string `json:"climateZoneFrontAirVolume,omitempty"` //
HeatedSeatFrontLeft *string `json:"heatedSeatFrontLeft,omitempty"` //
HeatedSeatFrontRight *string `json:"heatedSeatFrontRight,omitempty"` //
HeatedRearWindowActive *string `json:"heatedRearWindowActive,omitempty"` //
OuterAirCirculation *string `json:"outerAirCirculation,omitempty"` //
AirConditionOn *string `json:"airConditionOn,omitempty"` //
RunTimeMinutes *string `json:"runTimeMinutes,omitempty"` //
StartConfiguration *string `json:"startConfiguration,omitempty"` //
}
// account .
type account struct {
MarketID int `json:"marketId"`
AccountKey int `json:"accountKey"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
ZipCode string `json:"zipCode"`
ZipCode5 string `json:"zipCode5"`
LastLoginDate UnixTime `json:"lastLoginDate"`
CreatedDate UnixTime `json:"createdDate"`
}
// Customer .
type Customer struct {
SessionCustomer SessionCustomer `json:"sessionCustomer,omitempty"` // struct | Only by performing a RefreshVehicles request
Email string `json:"email"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
Zip string `json:"zip"`
OemCustID string `json:"oemCustId"`
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 .
// "dataName": "sessionData"
type SessionData struct {
Account account `json:"account"`
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"`
VehicleInactivated bool `json:"vehicleInactivated"`
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"`
}
// Vehicle .
// "dataName": "vehicle"
type VehicleData struct {
Customer Customer `json:"customer"` // Customer struct
OemCustID string `json:"oemCustId"` // CRM-631-HQN48K
UserOemCustID string `json:"userOemCustId"` // CRM-631-HQN48K
Active bool `json:"active"` // true | false
Email string `json:"email"` // null | email@address.com
FirstName string `json:"firstName,omitempty"` // null | First Name
LastName string `json:"lastName,omitempty"` // null | Last Name
Zip string `json:"zip"` // 12345
Phone string `json:"phone,omitempty"` // 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 | Finance3 | RetailPHEV ]""
SubscriptionPlans []string `json:"subscriptionPlans,omitempty"` // []
VehicleGeoPosition GeoPosition `json:"vehicleGeoPosition"` // GeoPosition struct
AccessLevel int `json:"accessLevel"` // -1
VehicleMileage int `json:"vehicleMileage,omitempty"` // 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 string `json:"timeZone"` // America/New_York
SunsetUpgraded bool `json:"sunsetUpgraded"` // true | false
PreferredDealer string `json:"preferredDealer,omitempty"` // null |
VehicleBranded bool `json:"vehicleBranded"`
}
// GeoPosition .
type GeoPosition struct {
Latitude float64 `json:"latitude"` // 40.700184
Longitude float64 `json:"longitude"` // -74.401375
Speed int `json:"speed,omitempty"` // 62
Heading int `json:"heading,omitempty"` // 155
Timestamp CustomTime1 `json:"timestamp"` // "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 .
type VehicleStatus struct {
VehicleId int64 `json:"vhsId"` // + 9969776690 5198812434
OdometerValue int `json:"odometerValue"` // + 23787
OdometerValueKm int `json:"odometerValueKilometers"` // + 38273
EventDate UnixTime `json:"eventDate"` // + 1701896993000
EventDateStr string `json:"eventDateStr"` // + 2023-12-06T21:09+0000
EventDateCarUser UnixTime `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 int `json:"positionHeadingDegree,string"` // + "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 int `json:"tirePressureFrontLeft,string,omitempty"` // + "2275"
TirePressureFrontRight int `json:"tirePressureFrontRight,string,omitempty"` // + "2344"
TirePressureRearLeft int `json:"tirePressureRearLeft,string,omitempty"` // + "2413"
TirePressureRearRight int `json:"tirePressureRearRight,string,omitempty"` // + "2344"
TirePressureFrontLeftPsi float64 `json:"tirePressureFrontLeftPsi,string,omitempty"` // + "33"
TirePressureFrontRightPsi float64 `json:"tirePressureFrontRightPsi,string,omitempty"` // + "34"
TirePressureRearLeftPsi float64 `json:"tirePressureRearLeftPsi,string,omitempty"` // + "35"
TirePressureRearRightPsi float64 `json:"tirePressureRearRightPsi,string,omitempty"` // + "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 | VENTED | OPEN
WindowFrontRightStatus string `json:"windowFrontRightStatus"` // CLOSE | VENTED | OPEN
WindowRearLeftStatus string `json:"windowRearLeftStatus"` // CLOSE | VENTED | OPEN
WindowRearRightStatus string `json:"windowRearRightStatus"` // CLOSE | VENTED | OPEN
WindowSunroofStatus string `json:"windowSunroofStatus"` // CLOSE | SLIDE_PARTLY_OPEN | OPEN | TILT
DoorBootPosition string `json:"doorBootPosition"` // CLOSED | OPEN
DoorEngineHoodPosition string `json:"doorEngineHoodPosition"` // CLOSED | OPEN
DoorFrontLeftPosition string `json:"doorFrontLeftPosition"` // CLOSED | OPEN
DoorFrontRightPosition string `json:"doorFrontRightPosition"` // CLOSED | OPEN
DoorRearLeftPosition string `json:"doorRearLeftPosition"` // CLOSED | OPEN
DoorRearRightPosition string `json:"doorRearRightPosition"` // CLOSED | OPEN
DoorBootLockStatus string `json:"doorBootLockStatus"` // LOCKED | UNLOCKED
DoorFrontLeftLockStatus string `json:"doorFrontLeftLockStatus"` // LOCKED | UNLOCKED
DoorFrontRightLockStatus string `json:"doorFrontRightLockStatus"` // LOCKED | UNLOCKED
DoorRearLeftLockStatus string `json:"doorRearLeftLockStatus"` // LOCKED | UNLOCKED
DoorRearRightLockStatus string `json:"doorRearRightLockStatus"` // LOCKED | UNLOCKED
}
// VehicleCondition .
// "dataName":"remoteServiceStatus"
// "remoteServiceType":"condition"
type VehicleCondition struct {
VehicleStateType string `json:"vehicleStateType"` // "IGNITION_OFF | IGNITION_ON"
AvgFuelConsumption float64 `json:"avgFuelConsumption,omitempty"` // null | 18.4
AvgFuelConsumptionUnit string `json:"avgFuelConsumptionUnit"` // "MPG"
DistanceToEmptyFuel int `json:"distanceToEmptyFuel,omitempty"` // null | 160
DistanceToEmptyFuelUnit string `json:"distanceToEmptyFuelUnit"` // "MILES"
RemainingFuelPercent int `json:"remainingFuelPercent,string"` // "66"
Odometer int `json:"odometer"` // 92
OdometerUnit string `json:"odometerUnit"` // "MILES"
TirePressureFrontLeft float64 `json:"tirePressureFrontLeft,omitempty"` // null | 36
TirePressureFrontLeftUnit string `json:"tirePressureFrontLeftUnit"` // "PSI"
TirePressureFrontRight float64 `json:"tirePressureFrontRight,omitempty"` // null | 36
TirePressureFrontRightUnit string `json:"tirePressureFrontRightUnit"` // "PSI",
TirePressureRearLeft float64 `json:"tirePressureRearLeft,omitempty"` // null | 36
TirePressureRearLeftUnit string `json:"tirePressureRearLeftUnit"` // "PSI"
TirePressureRearRight float64 `json:"tirePressureRearRight,omitempty"` // null | 36
TirePressureRearRightUnit string `json:"tirePressureRearRightUnit"` // "PSI"
DoorBootPosition string `json:"doorBootPosition"` // "CLOSED | OPEN"
DoorEngineHoodPosition string `json:"doorEngineHoodPosition"` // "CLOSED | OPEN"
DoorFrontLeftPosition string `json:"doorFrontLeftPosition"` // "CLOSED | OPEN"
DoorFrontRightPosition string `json:"doorFrontRightPosition"` // "CLOSED | OPEN"
DoorRearLeftPosition string `json:"doorRearLeftPosition"` // "CLOSED | OPEN"
DoorRearRightPosition string `json:"doorRearRightPosition"` // "CLOSED | OPEN"
WindowFrontLeftStatus string `json:"windowFrontLeftStatus"` // "CLOSE | VENTED | OPEN"
WindowFrontRightStatus string `json:"windowFrontRightStatus"` // "CLOSE | VENTED | OPEN"
WindowRearLeftStatus string `json:"windowRearLeftStatus"` // "CLOSE | VENTED | OPEN"
WindowRearRightStatus string `json:"windowRearRightStatus"` // "CLOSE | VENTED | OPEN"
WindowSunroofStatus string `json:"windowSunroofStatus"` // "CLOSE | VENTED | OPEN"
EvDistanceToEmpty int `json:"evDistanceToEmpty,omitempty"` // null,
EvDistanceToEmptyUnit string `json:"evDistanceToEmptyUnit,omitempty"` // null,
EvChargerStateType string `json:"evChargerStateType,omitempty"` // null,
EvIsPluggedIn bool `json:"evIsPluggedIn,omitempty"` // null,
EvStateOfChargeMode string `json:"evStateOfChargeMode,omitempty"` // null,
EvTimeToFullyCharged string `json:"evTimeToFullyCharged,omitempty"` // null,
EvStateOfChargePercent int `json:"evStateOfChargePercent,omitempty"` // null,
LastUpdatedTime string `json:"lastUpdatedTime"` // "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"`
// }
// 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 `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 .
// "dataName": "remoteServiceStatus"
type ServiceRequest struct {
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
Cancelled bool `json:"cancelled"` // false | true
RemoteServiceType string `json:"remoteServiceType"` // vehicleStatus | condition | locate | unlock | lock | lightsOnly | engineStart | engineStop | phevChargeNow
RemoteServiceState string `json:"remoteServiceState"` // started | finished | stopping
SubState string `json:"subState,omitempty"` // null
ErrorCode string `json:"errorCode,omitempty"` // null:null
Result json.RawMessage `json:"result,omitempty"` // struct
UpdateTime UnixTime `json:"updateTime,omitempty"` // timestamp // is empty if the request is started
}
// 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 ]
// 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 VehicleHealth struct {
VehicleHealthItems []VehicleHealthItem `json:"vehicleHealthItems"`
LastUpdatedDate int64 `json:"lastUpdatedDate"`
}
type VehicleHealthItem struct {
WarningCode int `json:"warningCode"` // internal code used by MySubaru, not documented
B2cCode string `json:"b2cCode"` // oilTemp | airbag | oilLevel | etc.
FeatureCode string `json:"featureCode"` // SRS_MIL | CEL_MIL | ATF_MIL | etc.
IsTrouble bool `json:"isTrouble"` // false | true
OnDaiID int `json:"onDaiId"` // Has a number, probably internal record id
OnDates []UnixTime `json:"onDates,omitempty"` // List of the timestamps
}
// ErrorResponse .
// "dataName":"errorResponse"
// {"success":false,"errorCode":"404-soa-unableToParseResponseBody","dataName":"errorResponse","data":{"errorLabel":"404-soa-unableToParseResponseBody","errorDescription":null}}
// {"success":false,"errorCode":"vehicleNotInAccount","dataName":null,"data":null}
// {"httpCode":500,"errorCode":"error","errorMessage":"java.lang.NullPointerException - null"}
// {"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"}}
type ErrorResponse struct {
ErrorLabel string `json:"errorLabel"` // "404-soa-unableToParseResponseBody"
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, &timestamp)
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 quotes
if s == "null" {
ct.Time = time.Time{}
return nil
}
ct.Time, err = time.Parse(layout, string(b))
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 quotes
if s == "null" {
ct.Time = time.Time{}
return nil
}
ct.Time, err = time.Parse(layout, s) // Parse the string with your custom layout
return
}