Bluelink: refactor (#1903)

This commit is contained in:
andig 2021-11-20 13:43:51 +01:00 • committed by GitHub
parent 3363c34722
commit 0b1d474a74
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 98 additions and 85 deletions

View file

@ -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

View file

@ -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
}

57
vehicle/bluelink/types.go Normal file
View file

@ -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
}
}
}

View file

@ -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 {

View file

@ -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 {