Refactor vehicle climate control methods to improve parameter handling and add new climate preset updates
Some checks failed
Golan Testing / testing (1.24.x, ubuntu-latest) (push) Failing after 24s

This commit is contained in:
2025-07-10 08:36:31 -04:00
parent ebe98e685a
commit 89b3d44d82
3 changed files with 153 additions and 50 deletions

View File

@ -70,8 +70,6 @@ var apiURLs = map[string]string{
"API_EV_FETCH_CHARGE_SETTINGS": "/service/g2/phevGetTimerSettings/execute.json", "API_EV_FETCH_CHARGE_SETTINGS": "/service/g2/phevGetTimerSettings/execute.json",
"API_EV_SAVE_CHARGE_SETTINGS": "/service/g2/phevSendTimerSetting/execute.json", "API_EV_SAVE_CHARGE_SETTINGS": "/service/g2/phevSendTimerSetting/execute.json",
"API_EV_DELETE_CHARGE_SCHEDULE": "/service/g2/phevDeleteTimerSetting/execute.json", "API_EV_DELETE_CHARGE_SCHEDULE": "/service/g2/phevDeleteTimerSetting/execute.json",
// "API_G2_FETCH_CLIMATE_SETTINGS": "/service/g2/remoteEngineStart/fetch.json",
// "API_G2_SAVE_CLIMATE_SETTINGS": "/service/g2/remoteEngineStart/save.json",
} }
// TODO: Get back and add wrapper to support Feature List // TODO: Get back and add wrapper to support Feature List
@ -267,7 +265,7 @@ const (
REAR_AC_ON = "true" REAR_AC_ON = "true"
REAR_AC_OFF = "false" REAR_AC_OFF = "false"
START_CONFIG = "startConfiguration" START_CONFIG = "startConfiguration"
START_CONFIG_DEFAULT_EV = "start_Climate_Control_only_allow_key_in_ignition" START_CONFIG_DEFAULT_EV = "START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION"
START_CONFIG_DEFAULT_RES = "START_ENGINE_ALLOW_KEY_IN_IGNITION" START_CONFIG_DEFAULT_RES = "START_ENGINE_ALLOW_KEY_IN_IGNITION"
WHICH_DOOR = "unlockDoorType" // Unlock doors constants WHICH_DOOR = "unlockDoorType" // Unlock doors constants
ALL_DOORS = "ALL_DOORS_CMD" ALL_DOORS = "ALL_DOORS_CMD"
@ -411,3 +409,32 @@ const (
// ] // ]
// BAD_BINARY_SENSOR_VALUES = [UNKNOWN, VENTED, NOT_EQUIPPED] // BAD_BINARY_SENSOR_VALUES = [UNKNOWN, VENTED, NOT_EQUIPPED]
) )
// RAW_API_FIELDS_TO_REDACT = [
// "cachedStateCode",
// "customer",
// "email",
// "firstName",
// "lastName",
// "latitude",
// "licensePlate",
// "licensePlateState",
// "longitude",
// "nickname",
// "odometer",
// "odometerValue",
// "odometerValueKilometers",
// "oemCustId",
// "phone",
// "preferredDealer",
// "sessionCustomer",
// "timeZone",
// "userOemCustId",
// "vehicleGeoPosition",
// "vehicleKey",
// "vehicleMileage",
// "vehicleName",
// "vhsId",
// "vin",
// "zip",
// ]

View File

@ -349,38 +349,23 @@ type VehicleCondition struct {
LastUpdatedTime string `json:"lastUpdatedTime"` // "2023-04-10T17:50:54+0000", 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. // ClimateProfile represents a climate control profile for a Subaru vehicle.
type ClimateProfile struct { type ClimateProfile struct {
Name string `json:"name,omitempty"` Name string `json:"name"`
VehicleType string `json:"vehicleType,omitempty"` // vehicleType [ gas | phev ] VehicleType string `json:"vehicleType,omitempty"` // vehicleType [ gas | phev ]
PresetType string `json:"presetType,omitempty"` // presetType [ subaruPreset | userPreset ] PresetType string `json:"presetType"` // presetType [ subaruPreset | userPreset ]
CanEdit bool `json:"canEdit,string,omitempty"` // canEdit [ false | true ] StartConfiguration string `json:"startConfiguration"` // startConfiguration [ START_ENGINE_ALLOW_KEY_IN_IGNITION (gas) | START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION (phev) ]
Disabled bool `json:"disabled,string,omitempty"` // disabled [ false | true ] RunTimeMinutes int `json:"runTimeMinutes,string"` // runTimeMinutes [ 5 | 10 ]
RunTimeMinutes int `json:"runTimeMinutes,string"` // runTimeMinutes [ 5 | 10 ] HeatedRearWindowActive string `json:"heatedRearWindowActive"` // heatedRearWindowActive: [ false | true ]
ClimateZoneFrontTemp int `json:"climateZoneFrontTemp,string"` // climateZoneFrontTemp: [ for _ in range(60, 85 + 1)] // climateZoneFrontTempCelsius: [for _ in range(15, 30 + 1) ] HeatedSeatFrontRight string `json:"heatedSeatFrontRight"` // heatedSeatFrontRight: [ OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT ]
ClimateZoneFrontAirMode string `json:"climateZoneFrontAirMode"` // climateZoneFrontAirMode: [ WINDOW | FEET_WINDOW | FACE | FEET | FEET_FACE_BALANCED | AUTO ] HeatedSeatFrontLeft string `json:"heatedSeatFrontLeft"` // heatedSeatFrontLeft: [ OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT ]
ClimateZoneFrontAirVolume string `json:"climateZoneFrontAirVolume"` // climateZoneFrontAirVolume: [ AUTO | 2 | 4 | 7 ] ClimateZoneFrontTemp int `json:"climateZoneFrontTemp,string"` // climateZoneFrontTemp: [ for _ in range(60, 85 + 1)] // climateZoneFrontTempCelsius: [for _ in range(15, 30 + 1) ]
OuterAirCirculation string `json:"outerAirCirculation"` // outerAirCirculation: [ outsideAir, recirculation ] ClimateZoneFrontAirMode string `json:"climateZoneFrontAirMode"` // climateZoneFrontAirMode: [ WINDOW | FEET_WINDOW | FACE | FEET | FEET_FACE_BALANCED | AUTO ]
HeatedRearWindowActive bool `json:"heatedRearWindowActive,string"` // heatedRearWindowActive: [ false | true ] ClimateZoneFrontAirVolume int `json:"climateZoneFrontAirVolume,string"` // climateZoneFrontAirVolume: [ AUTO | 2 | 4 | 7 ]
AirConditionOn bool `json:"airConditionOn,string"` // airConditionOn: [ false | true ] OuterAirCirculation string `json:"outerAirCirculation"` // airConditionOn: [ false | true ]
HeatedSeatFrontLeft string `json:"heatedSeatFrontLeft"` // heatedSeatFrontLeft: [ OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT ] AirConditionOn bool `json:"airConditionOn"` // airConditionOn: [ false | true ]
HeatedSeatFrontRight string `json:"heatedSeatFrontRight"` // heatedSeatFrontRight: [ OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT ] CanEdit bool `json:"canEdit"` // canEdit [ false | true ]
StartConfiguration string `json:"startConfiguration"` // startConfiguration [ START_ENGINE_ALLOW_KEY_IN_IGNITION (gas) | START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION (phev) ] Disabled bool `json:"disabled"` // disabled [ false | true ]
} }
// GeoLocation represents the geographical location of a Subaru vehicle. // GeoLocation represents the geographical location of a Subaru vehicle.
@ -497,7 +482,7 @@ type CustomTime1 struct {
func (ct *CustomTime1) UnmarshalJSON(b []byte) (err error) { func (ct *CustomTime1) UnmarshalJSON(b []byte) (err error) {
// Use the correct layout string for the desired format // Use the correct layout string for the desired format
const layout = "2006-01-02T15:04:05" 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" { if string(b) == "null" {
ct.Time = time.Time{} ct.Time = time.Time{}
return nil return nil
@ -515,7 +500,7 @@ type CustomTime2 struct {
func (ct *CustomTime2) UnmarshalJSON(b []byte) (err error) { func (ct *CustomTime2) UnmarshalJSON(b []byte) (err error) {
// Use the correct layout string for the desired format // Use the correct layout string for the desired format
const layout = "2006-01-02T15:04:05-0700" 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" { if string(b) == "null" {
ct.Time = time.Time{} ct.Time = time.Time{}
return nil return nil

View File

@ -201,24 +201,34 @@ func (v *Vehicle) Unlock() (chan string, error) {
// EngineStart // EngineStart
// Sends a command to start engine and set climate control. // Sends a command to start engine and set climate control.
func (v *Vehicle) EngineStart() (chan string, error) { func (v *Vehicle) EngineStart(run, delay int, horn bool) (chan string, error) {
// TODO: Get Quick Climate Preset from the Currect Car 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{ params := map[string]string{
"delay": "0", "delay": strconv.Itoa(delay),
"vin": v.Vin, "vin": v.Vin,
"pin": v.client.credentials.PIN, "pin": v.client.credentials.PIN,
"horn": "true", "horn": strconv.FormatBool(horn),
"climateSettings": "climateSettings", // climateSettings "climateSettings": "climateSettings", // climateSettings
"climateZoneFrontTemp": "65", // 60-86 "climateZoneFrontTemp": "65", // 60-86
"climateZoneFrontAirMode": "WINDOW", // FEET_FACE_BALANCED | FEET_WINDOW | WINDOW | FEET "climateZoneFrontAirMode": "WINDOW", // FEET_FACE_BALANCED | FEET_WINDOW | WINDOW | FEET
"climateZoneFrontAirVolume": "6", // 1-7 "climateZoneFrontAirVolume": "6", // 1-7
"heatedSeatFrontLeft": "OFF", // OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT | low_cool | medium_cool | high_cool "heatedSeatFrontLeft": "OFF", // OFF | LOW_HEAT | MEDIUM_HEAT | HIGH_HEAT | LOW_COOL | MEDIUM_COOL | HIGH_COOL
"heatedSeatFrontRight": "OFF", // ---//--- "heatedSeatFrontRight": "OFF", // ---//---
"heatedRearWindowActive": "true", // boolean "heatedRearWindowActive": "true", // boolean
"outerAirCirculation": "outsideAir", // outsideAir | recirculation "outerAirCirculation": "outsideAir", // outsideAir | recirculation
"airConditionOn": "false", // boolean "airConditionOn": "false", // boolean
"runTimeMinutes": "10", // 1-10 "runTimeMinutes": strconv.Itoa(run), // 1-10
"startConfiguration": START_CONFIG_DEFAULT_RES, // START_ENGINE_ALLOW_KEY_IN_IGNITION | ONLY FOR PHEV > START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION "startConfiguration": startConfig, // START_ENGINE_ALLOW_KEY_IN_IGNITION | ONLY FOR PHEV > START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION
} }
reqUrl := MOBILE_API_VERSION + apiURLs["API_G2_REMOTE_ENGINE_START"] reqUrl := MOBILE_API_VERSION + apiURLs["API_G2_REMOTE_ENGINE_START"]
pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"]
@ -500,6 +510,47 @@ func (v *Vehicle) GetClimateQuickPresets() error {
return nil 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 // GetClimateUserPresets
func (v *Vehicle) GetClimateUserPresets() error { func (v *Vehicle) GetClimateUserPresets() error {
if !v.getRemoteOptionsStatus() { if !v.getRemoteOptionsStatus() {
@ -552,6 +603,46 @@ func (v *Vehicle) GetClimateUserPresets() error {
return nil return nil
} }
// UpdateClimateUserPresets
// Updates the user's climate presets by fetching them from the MySubaru API.
func (v *Vehicle) UpdateClimateUserPresets() error {
if !v.getRemoteOptionsStatus() {
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
return errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
}
// Validate session before executing the request
if !v.client.validateSession() {
v.client.logger.Error(APP_ERRORS["SESSION_EXPIRED"])
return errors.New(APP_ERRORS["SESSION_EXPIRED"])
}
if v.Vin != (v.client).currentVin {
v.selectVehicle()
}
params := map[string]string{
"presetType": "userPreset",
"name": "Cooling",
"runTimeMinutes": "10",
"climateZoneFrontTemp": "65",
"climateZoneFrontAirMode": "FEET_FACE_BALANCED",
"climateZoneFrontAirVolume": "7",
"outerAirCirculation": "outsideAir",
"heatedRearWindowActive": "false",
"heatedSeatFrontLeft": "HIGH_COOL",
"airConditionOn": "false",
"startConfiguration": "START_ENGINE_ALLOW_KEY_IN_IGNITION",
// "canEdit": "true",
// "disabled": "false",
}
reqUrl := MOBILE_API_VERSION + apiURLs["API_G2_SAVE_RES_SETTINGS"]
resp, _ := v.client.execute(POST, reqUrl, params, false)
v.client.logger.Debug("http request output", "request", "UpdateClimateUserPresets", "body", resp)
return nil
}
// GetVehicleStatus . // GetVehicleStatus .
func (v *Vehicle) GetVehicleStatus() error { func (v *Vehicle) GetVehicleStatus() error {
if !v.getRemoteOptionsStatus() { if !v.getRemoteOptionsStatus() {