diff --git a/internal/charger/easee.go b/internal/charger/easee.go index cad3b6e24..728fdb61a 100644 --- a/internal/charger/easee.go +++ b/internal/charger/easee.go @@ -11,12 +11,12 @@ import ( "github.com/andig/evcc/util" "github.com/andig/evcc/util/request" "github.com/andig/evcc/util/sponsor" + "golang.org/x/oauth2" ) // Easee charger implementation type Easee struct { *request.Helper - *easee.Identity charger string site, circuit int status easee.ChargerStatus @@ -60,11 +60,17 @@ func NewEasee(user, password, charger string, cache time.Duration) (*Easee, erro cache: cache, } - var err error - if c.Identity, err = easee.NewIdentity(log, user, password); err != nil { + ts, err := easee.TokenSource(log, user, password) + if err != nil { return c, err } + // replace transport client with authenticated client + c.Client.Transport = &oauth2.Transport{ + Source: ts, + Base: c.Client.Transport, + } + // find charger if charger == "" { chargers, err := c.chargers() @@ -95,22 +101,9 @@ func NewEasee(user, password, charger string, cache time.Duration) (*Easee, erro return c, err } -// request creates JSON HTTP request with valid access token -func (c *Easee) request(method, path string, body interface{}) (*http.Request, error) { - uri := fmt.Sprintf("%s%s", easee.API, path) - - req, err := request.New(method, uri, request.MarshalJSON(body), request.JSONEncoding) - if err == nil { - var token string - token, err = c.Token() - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - } - - return req, err -} - func (c *Easee) chargers() (res []easee.Charger, err error) { - req, err := c.request(http.MethodGet, "/chargers", nil) + uri := fmt.Sprintf("%s/chargers", easee.API) + req, err := request.New(http.MethodGet, uri, nil, request.JSONEncoding) if err != nil { return nil, err } @@ -120,7 +113,8 @@ func (c *Easee) chargers() (res []easee.Charger, err error) { } func (c *Easee) chargerDetails() (res easee.Site, err error) { - req, err := c.request(http.MethodGet, fmt.Sprintf("/chargers/%s/site", c.charger), nil) + uri := fmt.Sprintf("%s/chargers/%s/site", easee.API, c.charger) + req, err := request.New(http.MethodGet, uri, nil, request.JSONEncoding) if err != nil { return res, err } @@ -134,7 +128,8 @@ func (c *Easee) state() (easee.ChargerStatus, error) { return c.status, nil } - req, err := c.request(http.MethodGet, fmt.Sprintf("/chargers/%s/state", c.charger), nil) + uri := fmt.Sprintf("%s/chargers/%s/state", easee.API, c.charger) + req, err := request.New(http.MethodGet, uri, nil, request.JSONEncoding) if err == nil { if err = c.DoJSON(req, &c.status); err == nil { c.updated = time.Now() @@ -185,7 +180,8 @@ func (c *Easee) Enable(enable bool) error { } var req *http.Request - if req, err = c.request(http.MethodPost, fmt.Sprintf("/chargers/%s/settings", c.charger), data); err == nil { + uri := fmt.Sprintf("%s/chargers/%s/settings", easee.API, c.charger) + if req, err = request.New(http.MethodGet, uri, request.MarshalJSON(data), request.JSONEncoding); err == nil { _, err = c.Do(req) c.updated = time.Time{} // clear cache } @@ -200,7 +196,8 @@ func (c *Easee) Enable(enable bool) error { } var req *http.Request - if req, err = c.request(http.MethodPost, fmt.Sprintf("/chargers/%s/commands/%s", c.charger, action), nil); err == nil { + uri := fmt.Sprintf("%s/chargers/%s/commands/%s", easee.API, c.charger, action) + if req, err = request.New(http.MethodGet, uri, nil, request.JSONEncoding); err == nil { _, err = c.Do(req) c.updated = time.Time{} // clear cache } @@ -224,7 +221,8 @@ func (c *Easee) MaxCurrentMillis(current float64) error { DynamicCircuitCurrentP3: &cur, } - req, err := c.request(http.MethodPost, fmt.Sprintf("/sites/%d/circuits/%d/settings", c.site, c.circuit), data) + uri := fmt.Sprintf("%s/sites/%d/circuits/%d/settings", easee.API, c.site, c.circuit) + req, err := request.New(http.MethodGet, uri, request.MarshalJSON(data), request.JSONEncoding) if err == nil { _, err = c.Do(req) c.updated = time.Time{} // clear cache diff --git a/internal/charger/easee/identity.go b/internal/charger/easee/identity.go index 9c5fa2850..ed7218a2d 100644 --- a/internal/charger/easee/identity.go +++ b/internal/charger/easee/identity.go @@ -6,35 +6,41 @@ import ( "time" "github.com/andig/evcc/util" + "github.com/andig/evcc/util/oauth" "github.com/andig/evcc/util/request" + "golang.org/x/oauth2" ) // Token is the Easee Token type Token struct { - AccessToken string `json:"accessToken"` - ExpiresIn float32 `json:"expiresIn"` - TokenType string `json:"tokenType"` - RefreshToken string `json:"refreshToken"` - Valid time.Time // helper to store validity timestamp + AccessToken string `json:"accessToken"` + ExpiresIn float32 `json:"expiresIn"` + TokenType string `json:"tokenType"` + RefreshToken string `json:"refreshToken"` } -// Identity manages the /api/accounts/token and /api/accounts/refresh_token response -type Identity struct { - *request.Helper - token Token -} - -// registry manages credentials sharing between multiple chargers -var registry = make(map[string]*Identity) - -// NewIdentity creates an Easee identity -func NewIdentity(log *util.Logger, user, password string) (*Identity, error) { - // get from registry - if c, ok := registry[user]; ok { - return c, nil +func (t *Token) AsOAuth2Token() *oauth2.Token { + if t == nil { + return nil } - c := &Identity{ + return &oauth2.Token{ + AccessToken: t.AccessToken, + TokenType: t.TokenType, + RefreshToken: t.RefreshToken, + Expiry: time.Now().Add(time.Second * time.Duration(t.ExpiresIn)), + } +} + +// tokenSource is an oauth2.TokenSource +type tokenSource struct { + *request.Helper + oauth2.TokenSource +} + +// TokenSource creates an Easee token source +func TokenSource(log *util.Logger, user, password string) (oauth2.TokenSource, error) { + c := &tokenSource{ Helper: request.NewHelper(log), } @@ -46,51 +52,38 @@ func NewIdentity(log *util.Logger, user, password string) (*Identity, error) { Password: password, } - uri := fmt.Sprintf("%s%s", API, "/accounts/token") + uri := fmt.Sprintf("%s/%s", API, "accounts/token") req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding) if err == nil { - err = c.DoJSON(req, &c.token) - c.token.Valid = time.Now().Add(time.Second * time.Duration(c.token.ExpiresIn)) + var token Token + if err = c.DoJSON(req, &token); err == nil { + c.TokenSource = oauth.RefreshTokenSource(token.AsOAuth2Token(), c) + } } - // add to registry - registry[user] = c - return c, err } -// Token returns the if necessary refreshed access token -func (c *Identity) Token() (string, error) { - var err error - if c.token.Valid.Before(time.Now()) { - err = c.refreshToken() - } - - return c.token.AccessToken, err -} - -func (c *Identity) refreshToken() error { +func (c *tokenSource) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { data := struct { AccessToken string `json:"accessToken"` RefreshToken string `json:"refreshToken"` }{ - AccessToken: c.token.AccessToken, - RefreshToken: c.token.RefreshToken, + AccessToken: token.AccessToken, + RefreshToken: token.RefreshToken, } - uri := fmt.Sprintf("%s%s", API, "/accounts/refresh_token") + uri := fmt.Sprintf("%s/%s", API, "accounts/refresh_token") req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding) - var token Token + var res *oauth2.Token if err == nil { - err = c.DoJSON(req, &token) - token.Valid = time.Now().Add(time.Second * time.Duration(token.ExpiresIn)) + var refreshed Token + if err = c.DoJSON(req, &refreshed); err == nil { + res = refreshed.AsOAuth2Token() + } } - if err == nil { - c.token = token - } - - return err + return res, err }