-
-
Notifications
You must be signed in to change notification settings - Fork 743
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Grünstromindex: request token on demand (#17928)
- Loading branch information
Showing
4 changed files
with
195 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package corrently | ||
|
||
import ( | ||
"net/http" | ||
"time" | ||
|
||
"github.com/evcc-io/evcc/util" | ||
"github.com/evcc-io/evcc/util/oauth" | ||
"github.com/evcc-io/evcc/util/request" | ||
"golang.org/x/oauth2" | ||
) | ||
|
||
type tokenSource struct { | ||
log *util.Logger | ||
} | ||
|
||
func TokenSource(log *util.Logger, token *oauth2.Token) oauth2.TokenSource { | ||
return oauth.RefreshTokenSource(token, &tokenSource{log}) | ||
} | ||
|
||
func (ts *tokenSource) RefreshToken(_ *oauth2.Token) (*oauth2.Token, error) { | ||
// "Content-Type: application/json" \ | ||
// --request POST \ | ||
// https://console.corrently.io/v2.0/auth/requestToken | ||
|
||
var res struct { | ||
Token string `json:"token"` | ||
Expires int64 `json:"expires"` | ||
} | ||
|
||
req, _ := request.New(http.MethodPost, "https://console.corrently.io/v2.0/auth/requestToken", nil, request.JSONEncoding) | ||
err := request.NewHelper(ts.log).DoJSON(req, &res) | ||
|
||
token := &oauth2.Token{ | ||
AccessToken: res.Token, | ||
Expiry: time.UnixMilli(res.Expires), | ||
} | ||
|
||
return token, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package corrently | ||
|
||
type Forecast struct { | ||
Support string `json:"support"` | ||
License string `json:"license"` | ||
Info string `json:"info"` | ||
Documentation string `json:"documentation"` | ||
Commercial string `json:"commercial"` | ||
Signee string `json:"signee"` | ||
Forecast []struct { | ||
Epochtime int `json:"epochtime"` | ||
Eevalue int `json:"eevalue"` | ||
Ewind int `json:"ewind"` | ||
Esolar int `json:"esolar"` | ||
Ensolar int `json:"ensolar"` | ||
Enwind int `json:"enwind"` | ||
Sci int `json:"sci"` | ||
Gsi float64 `json:"gsi"` | ||
TimeStamp int64 `json:"timeStamp"` | ||
Energyprice string `json:"energyprice"` | ||
Co2GStandard int `json:"co2_g_standard"` | ||
Co2GOekostrom int `json:"co2_g_oekostrom"` | ||
Timeframe struct { | ||
Start int64 `json:"start"` | ||
End int64 `json:"end"` | ||
} `json:"timeframe"` | ||
Iat int64 `json:"iat"` | ||
Zip string `json:"zip"` | ||
Signature string `json:"signature"` | ||
} `json:"forecast"` | ||
Location struct { | ||
Zip string `json:"zip"` | ||
City string `json:"city"` | ||
Signature string `json:"signature"` | ||
} `json:"location"` | ||
Err bool | ||
Message any | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package tariff | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"slices" | ||
"sync" | ||
"time" | ||
|
||
"github.com/cenkalti/backoff/v4" | ||
"github.com/evcc-io/evcc/api" | ||
"github.com/evcc-io/evcc/tariff/corrently" | ||
"github.com/evcc-io/evcc/util" | ||
"github.com/evcc-io/evcc/util/request" | ||
"golang.org/x/oauth2" | ||
) | ||
|
||
type GrünStromIndex struct { | ||
*request.Helper | ||
log *util.Logger | ||
zip string | ||
data *util.Monitor[api.Rates] | ||
} | ||
|
||
var _ api.Tariff = (*GrünStromIndex)(nil) | ||
|
||
func init() { | ||
registry.Add("grünstromindex", NewGrünStromIndexFromConfig) | ||
} | ||
|
||
func NewGrünStromIndexFromConfig(other map[string]interface{}) (api.Tariff, error) { | ||
var cc struct { | ||
Zip string | ||
Token string | ||
} | ||
|
||
if err := util.DecodeOther(other, &cc); err != nil { | ||
return nil, err | ||
} | ||
|
||
log := util.NewLogger("gsi").Redact(cc.Zip, cc.Token) | ||
|
||
t := &GrünStromIndex{ | ||
log: log, | ||
zip: cc.Zip, | ||
Helper: request.NewHelper(log), | ||
data: util.NewMonitor[api.Rates](2 * time.Hour), | ||
} | ||
|
||
t.Client.Transport = &oauth2.Transport{ | ||
Base: t.Client.Transport, | ||
Source: corrently.TokenSource(log, &oauth2.Token{AccessToken: cc.Token}), | ||
} | ||
|
||
done := make(chan error) | ||
go t.run(done) | ||
err := <-done | ||
|
||
return t, err | ||
} | ||
|
||
func (t *GrünStromIndex) run(done chan error) { | ||
var once sync.Once | ||
uri := fmt.Sprintf("https://api.corrently.io/v2.0/gsi/prediction?zip=%s", t.zip) | ||
|
||
for tick := time.Tick(time.Hour); ; <-tick { | ||
var res corrently.Forecast | ||
|
||
err := backoff.Retry(func() error { | ||
return backoffPermanentError(t.GetJSON(uri, &res)) | ||
}, bo()) | ||
|
||
if err == nil && res.Err { | ||
if s, ok := res.Message.(string); ok { | ||
err = errors.New(s) | ||
} else { | ||
err = api.ErrNotAvailable | ||
} | ||
} | ||
|
||
if err != nil { | ||
once.Do(func() { done <- err }) | ||
|
||
t.log.ERROR.Println(err) | ||
continue | ||
} | ||
|
||
data := make(api.Rates, 0, len(res.Forecast)) | ||
for _, r := range res.Forecast { | ||
data = append(data, api.Rate{ | ||
Price: float64(r.Co2GStandard), | ||
Start: time.UnixMilli(r.Timeframe.Start).Local(), | ||
End: time.UnixMilli(r.Timeframe.End).Local(), | ||
}) | ||
} | ||
|
||
mergeRates(t.data, data) | ||
once.Do(func() { close(done) }) | ||
} | ||
} | ||
|
||
// Rates implements the api.Tariff interface | ||
func (t *GrünStromIndex) Rates() (api.Rates, error) { | ||
var res api.Rates | ||
err := t.data.GetFunc(func(val api.Rates) { | ||
res = slices.Clone(val) | ||
}) | ||
return res, err | ||
} | ||
|
||
// Type implements the api.Tariff interface | ||
func (t *GrünStromIndex) Type() api.TariffType { | ||
return api.TariffTypeCo2 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters