From 89b3d44d82bdd8819e7ac2d4e9415423e287d5c3 Mon Sep 17 00:00:00 2001 From: Alex Savin Date: Thu, 10 Jul 2025 08:36:31 -0400 Subject: [PATCH] Refactor vehicle climate control methods to improve parameter handling and add new climate preset updates --- consts.go | 33 ++++++++++++-- mysubaru.go | 49 ++++++++------------- vehicle.go | 121 +++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 153 insertions(+), 50 deletions(-) diff --git a/consts.go b/consts.go index e7f8871..ba217f8 100644 --- a/consts.go +++ b/consts.go @@ -70,8 +70,6 @@ var apiURLs = map[string]string{ "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_G2_FETCH_CLIMATE_SETTINGS": "/service/g2/remoteEngineStart/fetch.json", - // "API_G2_SAVE_CLIMATE_SETTINGS": "/service/g2/remoteEngineStart/save.json", } // TODO: Get back and add wrapper to support Feature List @@ -267,7 +265,7 @@ const ( 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_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" @@ -411,3 +409,32 @@ const ( // ] // BAD_BINARY_SENSOR_VALUES = [UNKNOWN, VENTED, NOT_EQUIPPED] ) + +// RAW_API_FIELDS_TO_REDACT = [ +// "cachedStateCode", +// "customer", +// "email", +// "firstName", +// "lastName", +// "latitude", +// "licensePlate", +// "licensePlateState", +// "longitude", +// "nickname", +// "odometer", +// "odometerValue", +// "odometerValueKilometers", +// "oemCustId", +// "phone", +// "preferredDealer", +// "sessionCustomer", +// "timeZone", +// "userOemCustId", +// "vehicleGeoPosition", +// "vehicleKey", +// "vehicleMileage", +// "vehicleName", +// "vhsId", +// "vin", +// "zip", +// ] diff --git a/mysubaru.go b/mysubaru.go index 4afd809..8788d26 100644 --- a/mysubaru.go +++ b/mysubaru.go @@ -349,38 +349,23 @@ type VehicleCondition struct { 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) ] + Name string `json:"name"` + VehicleType string `json:"vehicleType,omitempty"` // vehicleType [ gas | phev ] + PresetType string `json:"presetType"` // presetType [ subaruPreset | userPreset ] + StartConfiguration string `json:"startConfiguration"` // startConfiguration [ START_ENGINE_ALLOW_KEY_IN_IGNITION (gas) | START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION (phev) ] + RunTimeMinutes int `json:"runTimeMinutes,string"` // runTimeMinutes [ 5 | 10 ] + HeatedRearWindowActive string `json:"heatedRearWindowActive"` // heatedRearWindowActive: [ false | true ] + HeatedSeatFrontRight string `json:"heatedSeatFrontRight"` // heatedSeatFrontRight: [ OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT ] + HeatedSeatFrontLeft string `json:"heatedSeatFrontLeft"` // heatedSeatFrontLeft: [ OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT ] + 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 int `json:"climateZoneFrontAirVolume,string"` // climateZoneFrontAirVolume: [ AUTO | 2 | 4 | 7 ] + OuterAirCirculation string `json:"outerAirCirculation"` // airConditionOn: [ false | true ] + AirConditionOn bool `json:"airConditionOn"` // airConditionOn: [ false | true ] + CanEdit bool `json:"canEdit"` // canEdit [ false | true ] + Disabled bool `json:"disabled"` // disabled [ false | true ] } // GeoLocation represents the geographical location of a Subaru vehicle. @@ -497,7 +482,7 @@ type CustomTime1 struct { 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 + s := strings.Trim(string(b), "\\\"") // Remove surrounding escapes and quotes (parsing time \"\\\"2025-07-09T00:23:19\\\"\" as \"2006-01-02T15:04:05\") if string(b) == "null" { ct.Time = time.Time{} return nil @@ -515,7 +500,7 @@ type CustomTime2 struct { 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 + s := strings.Trim(string(b), "\\\"") // Remove surrounding escapes and quotes ((parsing time \"\\\"2025-07-09T00:23:19\\\"\" as \"2006-01-02T15:04:05\")) if string(b) == "null" { ct.Time = time.Time{} return nil diff --git a/vehicle.go b/vehicle.go index de9d6c3..b1bb1e0 100644 --- a/vehicle.go +++ b/vehicle.go @@ -201,24 +201,34 @@ func (v *Vehicle) Unlock() (chan string, error) { // EngineStart // Sends a command to start engine and set climate control. -func (v *Vehicle) EngineStart() (chan string, error) { - // TODO: Get Quick Climate Preset from the Currect Car +func (v *Vehicle) EngineStart(run, delay int, horn bool) (chan string, error) { + if run < 1 || run > 10 { + return nil, errors.New("run time must be between 1 and 10 minutes") + } + + var startConfig string + if v.EV { + startConfig = START_CONFIG_DEFAULT_EV + } else { + startConfig = START_CONFIG_DEFAULT_RES + } + params := map[string]string{ - "delay": "0", + "delay": strconv.Itoa(delay), "vin": v.Vin, "pin": v.client.credentials.PIN, - "horn": "true", - "climateSettings": "climateSettings", // climateSettings - "climateZoneFrontTemp": "65", // 60-86 - "climateZoneFrontAirMode": "WINDOW", // FEET_FACE_BALANCED | FEET_WINDOW | WINDOW | FEET - "climateZoneFrontAirVolume": "6", // 1-7 - "heatedSeatFrontLeft": "OFF", // OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT | low_cool | medium_cool | high_cool - "heatedSeatFrontRight": "OFF", // ---//--- - "heatedRearWindowActive": "true", // boolean - "outerAirCirculation": "outsideAir", // outsideAir | recirculation - "airConditionOn": "false", // boolean - "runTimeMinutes": "10", // 1-10 - "startConfiguration": START_CONFIG_DEFAULT_RES, // START_ENGINE_ALLOW_KEY_IN_IGNITION | ONLY FOR PHEV > START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION + "horn": strconv.FormatBool(horn), + "climateSettings": "climateSettings", // climateSettings + "climateZoneFrontTemp": "65", // 60-86 + "climateZoneFrontAirMode": "WINDOW", // FEET_FACE_BALANCED | FEET_WINDOW | WINDOW | FEET + "climateZoneFrontAirVolume": "6", // 1-7 + "heatedSeatFrontLeft": "OFF", // OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT | LOW_COOL | MEDIUM_COOL | HIGH_COOL + "heatedSeatFrontRight": "OFF", // ---//--- + "heatedRearWindowActive": "true", // boolean + "outerAirCirculation": "outsideAir", // outsideAir | recirculation + "airConditionOn": "false", // boolean + "runTimeMinutes": strconv.Itoa(run), // 1-10 + "startConfiguration": startConfig, // START_ENGINE_ALLOW_KEY_IN_IGNITION | ONLY FOR PHEV > START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION } reqUrl := MOBILE_API_VERSION + apiURLs["API_G2_REMOTE_ENGINE_START"] pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] @@ -500,6 +510,47 @@ func (v *Vehicle) GetClimateQuickPresets() error { return nil } +// UpdateClimateQuickPresets +// Updates the quick climate presets by fetching them from the MySubaru API. +func (v *Vehicle) UpdateClimateQuickPresets() error { + if !v.getRemoteOptionsStatus() { + v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"]) + return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"]) + } + + // Validate session before executing the request + if !v.client.validateSession() { + v.client.logger.Error(APP_ERRORS["SESSION_EXPIRED"]) + return errors.New(APP_ERRORS["SESSION_EXPIRED"]) + } + + if v.Vin != (v.client).currentVin { + v.selectVehicle() + } + + params := map[string]string{ + "presetType": "userPreset", + "name": "Cooling", + "runTimeMinutes": "10", + "climateZoneFrontTemp": "65", + "climateZoneFrontAirMode": "FEET_FACE_BALANCED", + "climateZoneFrontAirVolume": "7", + "outerAirCirculation": "outsideAir", + "heatedRearWindowActive": "false", + "heatedSeatFrontLeft": "HIGH_COOL", + "airConditionOn": "false", + "startConfiguration": "START_ENGINE_ALLOW_KEY_IN_IGNITION", + // "canEdit": "true", + // "disabled": "false", + } + reqUrl := MOBILE_API_VERSION + apiURLs["API_G2_SAVE_RES_QUICK_START_SETTINGS"] + resp, _ := v.client.execute(POST, reqUrl, params, false) + + v.client.logger.Debug("http request output", "request", "UpdateClimateUserPresets", "body", resp) + + return nil +} + // GetClimateUserPresets func (v *Vehicle) GetClimateUserPresets() error { if !v.getRemoteOptionsStatus() { @@ -552,6 +603,46 @@ func (v *Vehicle) GetClimateUserPresets() error { return nil } +// UpdateClimateUserPresets +// Updates the user's climate presets by fetching them from the MySubaru API. +func (v *Vehicle) UpdateClimateUserPresets() error { + if !v.getRemoteOptionsStatus() { + v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"]) + return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"]) + } + + // Validate session before executing the request + if !v.client.validateSession() { + v.client.logger.Error(APP_ERRORS["SESSION_EXPIRED"]) + return errors.New(APP_ERRORS["SESSION_EXPIRED"]) + } + + if v.Vin != (v.client).currentVin { + v.selectVehicle() + } + params := map[string]string{ + "presetType": "userPreset", + "name": "Cooling", + "runTimeMinutes": "10", + "climateZoneFrontTemp": "65", + "climateZoneFrontAirMode": "FEET_FACE_BALANCED", + "climateZoneFrontAirVolume": "7", + "outerAirCirculation": "outsideAir", + "heatedRearWindowActive": "false", + "heatedSeatFrontLeft": "HIGH_COOL", + "airConditionOn": "false", + "startConfiguration": "START_ENGINE_ALLOW_KEY_IN_IGNITION", + // "canEdit": "true", + // "disabled": "false", + } + reqUrl := MOBILE_API_VERSION + apiURLs["API_G2_SAVE_RES_SETTINGS"] + resp, _ := v.client.execute(POST, reqUrl, params, false) + + v.client.logger.Debug("http request output", "request", "UpdateClimateUserPresets", "body", resp) + + return nil +} + // GetVehicleStatus . func (v *Vehicle) GetVehicleStatus() error { if !v.getRemoteOptionsStatus() {