diff --git a/vehicle/bluelink/api.go b/vehicle/bluelink/api.go index 0c6d5a03d..1488e674b 100644 --- a/vehicle/bluelink/api.go +++ b/vehicle/bluelink/api.go @@ -4,22 +4,22 @@ import ( "errors" "fmt" "net/http" + "strings" "time" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" + "github.com/evcc-io/evcc/util/transport" ) const ( - VehiclesURL = "/api/v1/spa/vehicles" - StatusURL = "/api/v1/spa/vehicles/%s/status" - StatusLatestURL = "/api/v1/spa/vehicles/%s/status/latest" + VehiclesURL = "vehicles" + StatusURL = "vehicles/%s/status" + StatusLatestURL = "vehicles/%s/status/latest" ) const ( - resOK = "S" // auth fail: F - timeFormat = "20060102150405 -0700" // Note: must add timeOffset - timeOffset = " +0100" + resOK = "S" // auth fail: F ) // ErrAuthFail indicates authorization failure @@ -29,96 +29,51 @@ var ErrAuthFail = errors.New("authorization failed") // Based on https://github.com/Hacksore/bluelinky. type API struct { *request.Helper - log *util.Logger - identity *Identity + baseURI string +} + +type Requester interface { + Request(*http.Request) error } // New creates a new BlueLink API -func NewAPI(log *util.Logger, identity *Identity, cache time.Duration) *API { +func NewAPI(log *util.Logger, baseURI string, identity Requester, cache time.Duration) *API { v := &API{ - log: log, - identity: identity, - Helper: request.NewHelper(log), + Helper: request.NewHelper(log), + baseURI: strings.TrimSuffix(baseURI, "/api/v1/spa") + "/api/v1/spa", } // api is unbelievably slow when retrieving status - v.Helper.Client.Timeout = 120 * time.Second + v.Client.Timeout = 120 * time.Second + + v.Client.Transport = &transport.Decorator{ + Decorator: identity.Request, + Base: v.Client.Transport, + } return v } -type VehiclesResponse struct { - RetCode string - ResMsg struct { - Vehicles []Vehicle - } -} - type Vehicle struct { VIN, VehicleName, VehicleID string } func (v *API) Vehicles() ([]Vehicle, error) { - req, err := v.identity.Request(http.MethodGet, VehiclesURL) + var res VehiclesResponse - var resp VehiclesResponse - if err == nil { - err = v.DoJSON(req, &resp) - } + uri := fmt.Sprintf("%s/%s", v.baseURI, VehiclesURL) + err := v.GetJSON(uri, &res) - return resp.ResMsg.Vehicles, err -} - -type StatusResponse struct { - RetCode string - ResCode string - ResMsg StatusData -} - -type StatusLatestResponse struct { - RetCode string - ResCode string - ResMsg struct { - VehicleStatusInfo struct { - VehicleStatus StatusData - } - } -} - -type StatusData struct { - Time string - EvStatus struct { - BatteryStatus float64 - RemainTime2 struct { - Atc struct { - Value, Unit int - } - } - DrvDistance []DrivingDistance - } - Vehicles []Vehicle -} - -func (d *StatusData) Updated() (time.Time, error) { - return time.Parse(timeFormat, d.Time+timeOffset) -} - -type DrivingDistance struct { - RangeByFuel struct { - EvModeRange struct { - Value int - } - } + return res.ResMsg.Vehicles, err } func (v *API) Status(vid string) (StatusLatestResponse, error) { var res StatusLatestResponse - req, err := v.identity.Request(http.MethodGet, fmt.Sprintf(StatusLatestURL, vid)) - if err == nil { - if err = v.DoJSON(req, &res); err == nil && res.RetCode != resOK { - err = fmt.Errorf("unexpected response: %s", res.RetCode) - } + uri := fmt.Sprintf("%s/%s", v.baseURI, fmt.Sprintf(StatusLatestURL, vid)) + err := v.GetJSON(uri, &res) + if err == nil && res.RetCode != resOK { + err = fmt.Errorf("unexpected response: %s", res.RetCode) } return res, err @@ -128,11 +83,10 @@ func (v *API) Status(vid string) (StatusLatestResponse, error) { func (v *API) StatusPartial(vid string) (StatusResponse, error) { var res StatusResponse - req, err := v.identity.Request(http.MethodGet, fmt.Sprintf(StatusURL, vid)) - if err == nil { - if err = v.DoJSON(req, &res); err == nil && res.RetCode != resOK { - err = fmt.Errorf("unexpected response: %s", res.RetCode) - } + uri := fmt.Sprintf("%s/%s", v.baseURI, fmt.Sprintf(StatusURL, vid)) + err := v.GetJSON(uri, &res) + if err == nil && res.RetCode != resOK { + err = fmt.Errorf("unexpected response: %s", res.RetCode) } return res, err diff --git a/vehicle/bluelink/identity.go b/vehicle/bluelink/identity.go index 1308e1d86..065bad78d 100644 --- a/vehicle/bluelink/identity.go +++ b/vehicle/bluelink/identity.go @@ -355,21 +355,23 @@ func (v *Identity) Login(user, password string) (err error) { return err } -// Request creates authenticated request -func (v *Identity) Request(method, path string) (*http.Request, error) { +// Request decorates requests with authorization headers +func (v *Identity) Request(req *http.Request) error { token, err := v.Token() if err != nil { - return nil, err + return err } - headers := map[string]string{ + for k, v := range map[string]string{ "Authorization": "Bearer " + token.AccessToken, "ccsp-device-id": v.deviceID, "ccsp-application-id": v.config.CCSPApplicationID, "offset": "1", "User-Agent": "okhttp/3.10.0", "Stamp": v.stamp(), + } { + req.Header.Set(k, v) } - return request.New(method, v.config.URI+path, nil, headers) + return nil } diff --git a/vehicle/bluelink/types.go b/vehicle/bluelink/types.go new file mode 100644 index 000000000..a61df2bef --- /dev/null +++ b/vehicle/bluelink/types.go @@ -0,0 +1,57 @@ +package bluelink + +import "time" + +type VehiclesResponse struct { + RetCode string + ResMsg struct { + Vehicles []Vehicle + } +} + +type StatusResponse struct { + RetCode string + ResCode string + ResMsg StatusData +} + +type StatusLatestResponse struct { + RetCode string + ResCode string + ResMsg struct { + VehicleStatusInfo struct { + VehicleStatus StatusData + } + } +} + +type StatusData struct { + Time string + EvStatus struct { + BatteryStatus float64 + RemainTime2 struct { + Atc struct { + Value, Unit int + } + } + DrvDistance []DrivingDistance + } + Vehicles []Vehicle +} + +const ( + timeFormat = "20060102150405 -0700" // Note: must add timeOffset + timeOffset = " +0100" +) + +func (d *StatusData) Updated() (time.Time, error) { + return time.Parse(timeFormat, d.Time+timeOffset) +} + +type DrivingDistance struct { + RangeByFuel struct { + EvModeRange struct { + Value int + } + } +} diff --git a/vehicle/hyundai.go b/vehicle/hyundai.go index 900d97183..6c0b882e9 100644 --- a/vehicle/hyundai.go +++ b/vehicle/hyundai.go @@ -61,7 +61,7 @@ func NewHyundaiFromConfig(other map[string]interface{}) (api.Vehicle, error) { return nil, err } - api := bluelink.NewAPI(log, identity, cc.Cache) + api := bluelink.NewAPI(log, settings.URI, identity, cc.Cache) vehicles, err := api.Vehicles() if err != nil { diff --git a/vehicle/kia.go b/vehicle/kia.go index 23554647c..6f4a46baf 100644 --- a/vehicle/kia.go +++ b/vehicle/kia.go @@ -61,7 +61,7 @@ func NewKiaFromConfig(other map[string]interface{}) (api.Vehicle, error) { return nil, err } - api := bluelink.NewAPI(log, identity, cc.Cache) + api := bluelink.NewAPI(log, settings.URI, identity, cc.Cache) vehicles, err := api.Vehicles() if err != nil {