From f06a46b3cc725b82bd72e8f0d33bc7958f006813 Mon Sep 17 00:00:00 2001 From: Alex Savin Date: Sat, 5 Jul 2025 12:33:34 -0400 Subject: [PATCH] Refactor service request URLs and enhance retry logic in vehicle service methods --- vehicle.go | 175 ++++++++++++++++++++++++++--------------------------- 1 file changed, 85 insertions(+), 90 deletions(-) diff --git a/vehicle.go b/vehicle.go index 4ac5e71..1ed4901 100644 --- a/vehicle.go +++ b/vehicle.go @@ -219,20 +219,17 @@ func (v *Vehicle) Lock() (chan string, error) { return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"]) } - if v.Vin != (v.client).currentVin { - v.selectVehicle() - } params := map[string]string{ "delay": "0", "vin": v.Vin, "pin": v.client.credentials.PIN, "forceKeyInCar": "false"} - reqURL := MOBILE_API_VERSION + urlToGen(apiURLs["API_LOCK"], v.getAPIGen()) - pollingURL := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] + reqUrl := MOBILE_API_VERSION + urlToGen(apiURLs["API_LOCK"], v.getAPIGen()) + pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] ch := make(chan string) go func() { defer close(ch) - v.executeServiceRequest(params, reqURL, pollingURL, ch) + v.executeServiceRequest(params, reqUrl, pollingUrl, ch, 1) }() return ch, nil @@ -246,20 +243,17 @@ func (v *Vehicle) Unlock() (chan string, error) { return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"]) } - if v.Vin != (v.client).currentVin { - v.selectVehicle() - } params := map[string]string{ "delay": "0", "vin": v.Vin, "pin": v.client.credentials.PIN, "unlockDoorType": "ALL_DOORS_CMD"} // FRONT_LEFT_DOOR_CMD | ALL_DOORS_CMD - reqURL := MOBILE_API_VERSION + urlToGen(apiURLs["API_UNLOCK"], v.getAPIGen()) - pollingURL := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] + reqUrl := MOBILE_API_VERSION + urlToGen(apiURLs["API_UNLOCK"], v.getAPIGen()) + pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] ch := make(chan string) go func() { defer close(ch) - v.executeServiceRequest(params, reqURL, pollingURL, ch) + v.executeServiceRequest(params, reqUrl, pollingUrl, ch, 1) }() return ch, nil @@ -273,9 +267,6 @@ func (v *Vehicle) EngineStart() (chan string, error) { return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"]) } - if v.Vin != (v.client).currentVin { - v.selectVehicle() - } // TODO: Get Quick Climate Preset from the Currect Car params := map[string]string{ "delay": "0", @@ -294,12 +285,12 @@ func (v *Vehicle) EngineStart() (chan string, error) { "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 } - reqURL := MOBILE_API_VERSION + apiURLs["API_G2_REMOTE_ENGINE_START"] - pollingURL := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] + reqUrl := MOBILE_API_VERSION + apiURLs["API_G2_REMOTE_ENGINE_START"] + pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] ch := make(chan string) go func() { defer close(ch) - v.executeServiceRequest(params, reqURL, pollingURL, ch) + v.executeServiceRequest(params, reqUrl, pollingUrl, ch, 1) }() return ch, nil @@ -312,20 +303,18 @@ func (v *Vehicle) EngineStop() (chan string, error) { v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"]) return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"]) } - if v.Vin != (v.client).currentVin { - v.selectVehicle() - } + params := map[string]string{ "delay": "0", "vin": v.Vin, "pin": v.client.credentials.PIN} - reqURL := MOBILE_API_VERSION + apiURLs["API_G2_REMOTE_ENGINE_STOP"] - pollingURL := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] + reqUrl := MOBILE_API_VERSION + apiURLs["API_G2_REMOTE_ENGINE_STOP"] + pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] ch := make(chan string) go func() { defer close(ch) - v.executeServiceRequest(params, reqURL, pollingURL, ch) + v.executeServiceRequest(params, reqUrl, pollingUrl, ch, 1) }() return ch, nil @@ -339,22 +328,19 @@ func (v *Vehicle) LightsStart() (chan string, error) { return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"]) } - if v.Vin != (v.client).currentVin { - v.selectVehicle() - } params := map[string]string{ "delay": "0", "vin": v.Vin, "pin": v.client.credentials.PIN} - reqURL := MOBILE_API_VERSION + urlToGen(apiURLs["API_LIGHTS"], v.getAPIGen()) - pollingURL := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] + reqUrl := MOBILE_API_VERSION + urlToGen(apiURLs["API_LIGHTS"], v.getAPIGen()) + pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] if v.getAPIGen() == FEATURE_G1_TELEMATICS { - pollingURL = MOBILE_API_VERSION + apiURLs["API_G1_HORN_LIGHTS_STATUS"] + pollingUrl = MOBILE_API_VERSION + apiURLs["API_G1_HORN_LIGHTS_STATUS"] } ch := make(chan string) go func() { defer close(ch) - v.executeServiceRequest(params, reqURL, pollingURL, ch) + v.executeServiceRequest(params, reqUrl, pollingUrl, ch, 1) }() return ch, nil } @@ -367,22 +353,19 @@ func (v *Vehicle) LightsStop() (chan string, error) { return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"]) } - if v.Vin != (v.client).currentVin { - v.selectVehicle() - } params := map[string]string{ "delay": "0", "vin": v.Vin, "pin": v.client.credentials.PIN} - reqURL := MOBILE_API_VERSION + urlToGen(apiURLs["API_LIGHTS_STOP"], v.getAPIGen()) - pollingURL := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] + reqUrl := MOBILE_API_VERSION + urlToGen(apiURLs["API_LIGHTS_STOP"], v.getAPIGen()) + pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] if v.getAPIGen() == FEATURE_G1_TELEMATICS { - pollingURL = MOBILE_API_VERSION + apiURLs["API_G1_HORN_LIGHTS_STATUS"] + pollingUrl = MOBILE_API_VERSION + apiURLs["API_G1_HORN_LIGHTS_STATUS"] } ch := make(chan string) go func() { defer close(ch) - v.executeServiceRequest(params, reqURL, pollingURL, ch) + v.executeServiceRequest(params, reqUrl, pollingUrl, ch, 1) }() return ch, nil @@ -395,22 +378,20 @@ func (v *Vehicle) HornStart() (chan string, error) { v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"]) return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"]) } - if v.Vin != (v.client).currentVin { - v.selectVehicle() - } + params := map[string]string{ "delay": "0", "vin": v.Vin, "pin": v.client.credentials.PIN} - reqURL := MOBILE_API_VERSION + urlToGen(apiURLs["API_HORN_LIGHTS"], v.getAPIGen()) - pollingURL := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] + reqUrl := MOBILE_API_VERSION + urlToGen(apiURLs["API_HORN_LIGHTS"], v.getAPIGen()) + pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] if v.getAPIGen() == FEATURE_G1_TELEMATICS { - pollingURL = MOBILE_API_VERSION + apiURLs["API_G1_HORN_LIGHTS_STATUS"] + pollingUrl = MOBILE_API_VERSION + apiURLs["API_G1_HORN_LIGHTS_STATUS"] } ch := make(chan string) go func() { defer close(ch) - v.executeServiceRequest(params, reqURL, pollingURL, ch) + v.executeServiceRequest(params, reqUrl, pollingUrl, ch, 1) }() return ch, nil @@ -424,22 +405,19 @@ func (v *Vehicle) HornStop() (chan string, error) { return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"]) } - if v.Vin != (v.client).currentVin { - v.selectVehicle() - } params := map[string]string{ "delay": "0", "vin": v.Vin, "pin": v.client.credentials.PIN} - reqURL := MOBILE_API_VERSION + urlToGen(apiURLs["API_HORN_LIGHTS_STOP"], v.getAPIGen()) - pollingURL := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] + reqUrl := MOBILE_API_VERSION + urlToGen(apiURLs["API_HORN_LIGHTS_STOP"], v.getAPIGen()) + pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] if v.getAPIGen() == FEATURE_G1_TELEMATICS { - pollingURL = MOBILE_API_VERSION + apiURLs["API_G1_HORN_LIGHTS_STATUS"] + pollingUrl = MOBILE_API_VERSION + apiURLs["API_G1_HORN_LIGHTS_STATUS"] } ch := make(chan string) go func() { defer close(ch) - v.executeServiceRequest(params, reqURL, pollingURL, ch) + v.executeServiceRequest(params, reqUrl, pollingUrl, ch, 1) }() return ch, nil @@ -452,19 +430,16 @@ func (v *Vehicle) ChargeOn() (chan string, error) { return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"]) } if v.isEV() { - if v.Vin != (v.client).currentVin { - v.selectVehicle() - } params := map[string]string{ "delay": "0", "vin": v.Vin, "pin": v.client.credentials.PIN} - reqURL := MOBILE_API_VERSION + apiURLs["API_EV_CHARGE_NOW"] - pollingURL := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] + reqUrl := MOBILE_API_VERSION + apiURLs["API_EV_CHARGE_NOW"] + pollingUrl := MOBILE_API_VERSION + apiURLs["API_REMOTE_SVC_STATUS"] ch := make(chan string) go func() { defer close(ch) - v.executeServiceRequest(params, reqURL, pollingURL, ch) + v.executeServiceRequest(params, reqUrl, pollingUrl, ch, 1) }() return ch, nil } else { @@ -479,26 +454,23 @@ func (v *Vehicle) GetLocation(force bool) (chan string, error) { return nil, errors.New(APP_ERRORS["SUBSCRIBTION_REQUIRED"]) } - var reqURL, pollingURL string + var reqUrl, pollingUrl string var params map[string]string - if v.Vin != (v.client).currentVin { - v.selectVehicle() - } if force { // Sends a locate command to the vehicle to get real time position - reqURL = MOBILE_API_VERSION + apiURLs["API_G2_LOCATE_UPDATE"] - pollingURL = MOBILE_API_VERSION + apiURLs["API_G2_LOCATE_STATUS"] + reqUrl = MOBILE_API_VERSION + apiURLs["API_G2_LOCATE_UPDATE"] + pollingUrl = MOBILE_API_VERSION + apiURLs["API_G2_LOCATE_STATUS"] params = map[string]string{ "vin": v.Vin, "pin": v.client.credentials.PIN} if v.getAPIGen() == FEATURE_G1_TELEMATICS { - reqURL = MOBILE_API_VERSION + apiURLs["API_G1_LOCATE_UPDATE"] - pollingURL = MOBILE_API_VERSION + apiURLs["API_G1_LOCATE_STATUS"] + reqUrl = MOBILE_API_VERSION + apiURLs["API_G1_LOCATE_UPDATE"] + pollingUrl = MOBILE_API_VERSION + apiURLs["API_G1_LOCATE_STATUS"] } } else { // Reports the last location the vehicle has reported to Subaru params = map[string]string{ "vin": v.Vin, "pin": v.client.credentials.PIN} - reqURL = MOBILE_API_VERSION + urlToGen(apiURLs["API_LOCATE"], v.getAPIGen()) + reqUrl = MOBILE_API_VERSION + urlToGen(apiURLs["API_LOCATE"], v.getAPIGen()) } // Simulate sending multiple messages to the channel @@ -521,40 +493,63 @@ func (v *Vehicle) GetLocation(force bool) (chan string, error) { ch := make(chan string) go func() { defer close(ch) - v.executeServiceRequest(params, reqURL, pollingURL, ch) + v.executeServiceRequest(params, reqUrl, pollingUrl, ch, 1) }() return ch, nil } // executeServiceRequest -func (v *Vehicle) executeServiceRequest(params map[string]string, reqURL, pollingURL string, ch chan string) error { +func (v *Vehicle) executeServiceRequest(params map[string]string, reqUrl, pollingUrl string, ch chan string, attempt int) error { + var maxAttempts = 15 + + if attempt >= maxAttempts { + v.client.logger.Error("maximum attempts reached for service request", "request", reqUrl, "attempts", attempt) + ch <- "error" + return errors.New("maximum attempts reached for service request") + } + if v.Vin != v.client.currentVin { v.selectVehicle() } - resp, _ := v.client.execute(reqURL, POST, params, true) + var url string + if attempt == 1 { + url = reqUrl + } else { + url = pollingUrl + } + resp, err := v.client.execute(url, POST, params, true) + if err != nil { + v.client.logger.Error("error while executing service request", "request", reqUrl, "error", err.Error()) + ch <- "error" + return err + } // dataName field has the list of the states [ remoteServiceStatus | errorResponse ] if resp.DataName == "remoteServiceStatus" { if sr, ok := v.parseServiceRequest([]byte(resp.Data)); ok { - switch { - case sr.RemoteServiceState == "finished": + switch sr.RemoteServiceState { + + case "finished": // Finished RemoteServiceState Service Request does not include Service Request ID v.client.logger.Debug("Remote service request completed successfully") ch <- sr.RemoteServiceState - case sr.RemoteServiceState == "started": + + case "started": time.Sleep(5 * time.Second) v.client.logger.Debug("Subaru API reports remote service request (started) is in progress", "id", sr.ServiceRequestID) - v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqURL, pollingURL, ch) + v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqUrl, pollingUrl, ch, attempt+1) ch <- sr.RemoteServiceState - case sr.RemoteServiceState == "stopping": + + case "stopping": time.Sleep(5 * time.Second) v.client.logger.Debug("Subaru API reports remote service request (stopping) is in progress", "id", sr.ServiceRequestID) - v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqURL, pollingURL, ch) + v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqUrl, pollingUrl, ch, attempt+1) ch <- sr.RemoteServiceState + default: time.Sleep(5 * time.Second) - v.client.logger.Debug("Subaru API reports remote service request (stopping) is in progress") - v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqURL, pollingURL, ch) + v.client.logger.Debug("Subaru API reports remote service request (default)") + v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqUrl, pollingUrl, ch, attempt+1) ch <- sr.RemoteServiceState } return nil @@ -587,8 +582,8 @@ func (v *Vehicle) GetClimatePresets() error { if v.Vin != (v.client).currentVin { v.selectVehicle() } - reqURL := MOBILE_API_VERSION + apiURLs["API_G2_FETCH_RES_SUBARU_PRESETS"] - resp, _ := v.client.execute(GET, reqURL, map[string]string{}, false) + reqUrl := MOBILE_API_VERSION + apiURLs["API_G2_FETCH_RES_SUBARU_PRESETS"] + resp, _ := v.client.execute(GET, reqUrl, map[string]string{}, false) re1 := regexp.MustCompile(`\"`) result := re1.ReplaceAllString(string(resp.Data), "") @@ -646,8 +641,8 @@ func (v *Vehicle) GetClimateQuickPresets() error { if v.Vin != (v.client).currentVin { v.selectVehicle() } - reqURL := MOBILE_API_VERSION + apiURLs["API_G2_FETCH_RES_QUICK_START_SETTINGS"] - resp, _ := v.client.execute(GET, reqURL, map[string]string{}, false) + reqUrl := MOBILE_API_VERSION + apiURLs["API_G2_FETCH_RES_QUICK_START_SETTINGS"] + resp, _ := v.client.execute(GET, reqUrl, map[string]string{}, false) // v.client.logger.Debug("http request output", "request", "GetClimateQuickPresets", "body", resp) re1 := regexp.MustCompile(`\"`) @@ -682,8 +677,8 @@ func (v *Vehicle) GetClimateUserPresets() error { if v.Vin != (v.client).currentVin { v.selectVehicle() } - reqURL := MOBILE_API_VERSION + apiURLs["API_G2_FETCH_RES_USER_PRESETS"] - resp, _ := v.client.execute(GET, reqURL, map[string]string{}, false) + reqUrl := MOBILE_API_VERSION + apiURLs["API_G2_FETCH_RES_USER_PRESETS"] + resp, _ := v.client.execute(GET, reqUrl, map[string]string{}, false) re1 := regexp.MustCompile(`\"`) result := re1.ReplaceAllString(string(resp.Data), "") @@ -727,8 +722,8 @@ func (v *Vehicle) GetVehicleStatus() error { if v.Vin != (v.client).currentVin { v.selectVehicle() } - reqURL := MOBILE_API_VERSION + urlToGen(apiURLs["API_VEHICLE_STATUS"], v.getAPIGen()) - resp, _ := v.client.execute(GET, reqURL, map[string]string{}, false) + reqUrl := MOBILE_API_VERSION + urlToGen(apiURLs["API_VEHICLE_STATUS"], v.getAPIGen()) + resp, _ := v.client.execute(GET, reqUrl, map[string]string{}, false) // v.client.logger.Info("http request output", "request", "GetVehicleStatus", "body", resp) var vs VehicleStatus @@ -784,8 +779,8 @@ func (v *Vehicle) GetVehicleCondition() error { if v.Vin != (v.client).currentVin { v.selectVehicle() } - reqURL := MOBILE_API_VERSION + urlToGen(apiURLs["API_CONDITION"], v.getAPIGen()) - resp, _ := v.client.execute(GET, reqURL, map[string]string{}, false) + reqUrl := MOBILE_API_VERSION + urlToGen(apiURLs["API_CONDITION"], v.getAPIGen()) + resp, _ := v.client.execute(GET, reqUrl, map[string]string{}, false) // v.client.logger.Info("http request output", "request", "GetVehicleCondition", "body", resp) var sr ServiceRequest @@ -836,8 +831,8 @@ func (v *Vehicle) GetVehicleHealth() error { params := map[string]string{ "vin": v.Vin, "_": timestamp()} - reqURL := MOBILE_API_VERSION + apiURLs["API_VEHICLE_HEALTH"] - resp, _ := v.client.execute(GET, reqURL, params, false) + reqUrl := MOBILE_API_VERSION + apiURLs["API_VEHICLE_HEALTH"] + resp, _ := v.client.execute(GET, reqUrl, params, false) // v.client.logger.Debug("http request output", "request", "GetVehicleHealth", "body", resp) var vh VehicleHealth