Add ElectricityMaps Co2 forecast (#5454)
This commit is contained in:
parent
a9666b57ee
commit
1684a0d8e0
10 changed files with 186 additions and 34 deletions
|
|
@ -113,6 +113,7 @@ type tariffConfig struct {
|
|||
Currency string
|
||||
Grid typedConfig
|
||||
FeedIn typedConfig
|
||||
Planner typedConfig
|
||||
}
|
||||
|
||||
type networkConfig struct {
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ func configureMessengers(conf messagingConfig, cache *util.Cache) (chan push.Eve
|
|||
}
|
||||
|
||||
func configureTariffs(conf tariffConfig) (tariff.Tariffs, error) {
|
||||
var grid, feedin api.Tariff
|
||||
var grid, feedin, planner api.Tariff
|
||||
var currencyCode currency.Unit = currency.EUR
|
||||
var err error
|
||||
|
||||
|
|
@ -243,11 +243,15 @@ func configureTariffs(conf tariffConfig) (tariff.Tariffs, error) {
|
|||
feedin, err = tariff.NewFromConfig(conf.FeedIn.Type, conf.FeedIn.Other)
|
||||
}
|
||||
|
||||
if err == nil && conf.Planner.Type != "" {
|
||||
planner, err = tariff.NewFromConfig(conf.Planner.Type, conf.Planner.Other)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed configuring tariff: %w", err)
|
||||
}
|
||||
|
||||
tariffs := tariff.NewTariffs(currencyCode, grid, feedin)
|
||||
tariffs := tariff.NewTariffs(currencyCode, grid, feedin, planner)
|
||||
|
||||
return *tariffs, err
|
||||
}
|
||||
|
|
|
|||
13
core/site.go
13
core/site.go
|
|
@ -127,16 +127,15 @@ func NewSiteFromConfig(
|
|||
})
|
||||
}
|
||||
|
||||
tariff := site.tariffs.Grid
|
||||
if site.tariffs.Planner != nil {
|
||||
tariff = site.tariffs.Planner
|
||||
}
|
||||
|
||||
// give loadpoints access to vehicles and database
|
||||
for _, lp := range loadpoints {
|
||||
lp.coordinator = coordinator.NewAdapter(lp, site.coordinator)
|
||||
|
||||
// planner
|
||||
gridTariff := site.tariffs.Grid
|
||||
if gridTariff == nil {
|
||||
gridTariff = new(tariff.Fixed)
|
||||
}
|
||||
lp.planner = planner.New(lp.log, gridTariff)
|
||||
lp.planner = planner.New(lp.log, tariff)
|
||||
|
||||
if serverdb.Instance != nil {
|
||||
var err error
|
||||
|
|
|
|||
|
|
@ -157,6 +157,10 @@ tariffs:
|
|||
# rate for feeding excess (pv) energy to the grid
|
||||
type: fixed
|
||||
price: 0.08 # EUR/kWh
|
||||
planner:
|
||||
# planner tariff can be used for target charging if not grid tariff is specified
|
||||
# for example, electricitymaps provides CO2 intensity forecast
|
||||
# type: electricitymaps
|
||||
|
||||
# mqtt message broker
|
||||
mqtt:
|
||||
|
|
|
|||
|
|
@ -21,7 +21,11 @@ type Awattar struct {
|
|||
|
||||
var _ api.Tariff = (*Awattar)(nil)
|
||||
|
||||
func NewAwattar(other map[string]interface{}) (*Awattar, error) {
|
||||
func init() {
|
||||
registry.Add("awattar", NewAwattarFromConfig)
|
||||
}
|
||||
|
||||
func NewAwattarFromConfig(other map[string]interface{}) (api.Tariff, error) {
|
||||
cc := struct {
|
||||
Cheap any // TODO deprecated
|
||||
Region string
|
||||
|
|
|
|||
|
|
@ -1,23 +1,40 @@
|
|||
package tariff
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
)
|
||||
|
||||
// NewFromConfig creates new HEMS from config
|
||||
func NewFromConfig(typ string, other map[string]interface{}) (t api.Tariff, err error) {
|
||||
switch strings.ToLower(typ) {
|
||||
case "fixed":
|
||||
t, err = NewFixed(other)
|
||||
case "awattar":
|
||||
t, err = NewAwattar(other)
|
||||
case "tibber":
|
||||
t, err = NewTibber(other)
|
||||
default:
|
||||
return nil, errors.New("unknown tariff: " + typ)
|
||||
type tariffRegistry map[string]func(map[string]interface{}) (api.Tariff, error)
|
||||
|
||||
func (r tariffRegistry) Add(name string, factory func(map[string]interface{}) (api.Tariff, error)) {
|
||||
if _, exists := r[name]; exists {
|
||||
panic(fmt.Sprintf("cannot register duplicate tariff type: %s", name))
|
||||
}
|
||||
r[name] = factory
|
||||
}
|
||||
|
||||
func (r tariffRegistry) Get(name string) (func(map[string]interface{}) (api.Tariff, error), error) {
|
||||
factory, exists := r[name]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("tariff type not registered: %s", name)
|
||||
}
|
||||
return factory, nil
|
||||
}
|
||||
|
||||
var registry tariffRegistry = make(map[string]func(map[string]interface{}) (api.Tariff, error))
|
||||
|
||||
// NewFromConfig creates tariff from configuration
|
||||
func NewFromConfig(typ string, other map[string]interface{}) (v api.Tariff, err error) {
|
||||
factory, err := registry.Get(strings.ToLower(typ))
|
||||
if err == nil {
|
||||
if v, err = factory(other); err != nil {
|
||||
err = fmt.Errorf("cannot create tariff '%s': %w", typ, err)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("invalid tariff type: %s", typ)
|
||||
}
|
||||
|
||||
return
|
||||
|
|
|
|||
111
tariff/electricitymaps.go
Normal file
111
tariff/electricitymaps.go
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
package tariff
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/request"
|
||||
"github.com/evcc-io/evcc/util/transport"
|
||||
)
|
||||
|
||||
type ElectricityMaps struct {
|
||||
*request.Helper
|
||||
log *util.Logger
|
||||
mux sync.Mutex
|
||||
uri string
|
||||
zone string
|
||||
data []CarbonIntensitySlot
|
||||
}
|
||||
|
||||
type CarbonIntensity struct {
|
||||
Error string
|
||||
Zone string
|
||||
Forecast []CarbonIntensitySlot
|
||||
}
|
||||
|
||||
type CarbonIntensitySlot struct {
|
||||
CarbonIntensity float64 // : 626,
|
||||
Datetime time.Time // : "2022-12-12T16:00:00.000Z"
|
||||
}
|
||||
|
||||
var _ api.Tariff = (*ElectricityMaps)(nil)
|
||||
|
||||
func init() {
|
||||
registry.Add("electricitymaps", NewElectricityMapsFromConfig)
|
||||
}
|
||||
|
||||
func NewElectricityMapsFromConfig(other map[string]interface{}) (api.Tariff, error) {
|
||||
cc := struct {
|
||||
Uri string
|
||||
Token string
|
||||
Zone string
|
||||
}{
|
||||
Zone: "DE",
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log := util.NewLogger("em").Redact(cc.Token)
|
||||
|
||||
t := &ElectricityMaps{
|
||||
log: log,
|
||||
Helper: request.NewHelper(log),
|
||||
uri: util.DefaultScheme(strings.TrimRight(cc.Uri, "/"), "https"),
|
||||
zone: strings.ToUpper(cc.Zone),
|
||||
}
|
||||
|
||||
t.Client.Transport = &transport.Decorator{
|
||||
Base: t.Client.Transport,
|
||||
Decorator: transport.DecorateHeaders(map[string]string{
|
||||
"X-BLOBR-KEY": cc.Token,
|
||||
}),
|
||||
}
|
||||
|
||||
go t.Run()
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (t *ElectricityMaps) Run() {
|
||||
uri := fmt.Sprintf("%s/carbon-intensity/forecast?zone=%s", t.uri, t.zone)
|
||||
|
||||
for ; true; <-time.NewTicker(time.Hour).C {
|
||||
var res CarbonIntensity
|
||||
if err := t.GetJSON(uri, &res); err != nil {
|
||||
if res.Error != "" {
|
||||
err = errors.New(res.Error)
|
||||
}
|
||||
|
||||
t.log.ERROR.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
t.mux.Lock()
|
||||
t.data = res.Forecast
|
||||
t.mux.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ElectricityMaps) Rates() (api.Rates, error) {
|
||||
t.mux.Lock()
|
||||
defer t.mux.Unlock()
|
||||
|
||||
res := make(api.Rates, 0, len(t.data))
|
||||
for _, r := range t.data {
|
||||
ar := api.Rate{
|
||||
Start: r.Datetime,
|
||||
End: r.Datetime.Add(time.Hour),
|
||||
Price: r.CarbonIntensity,
|
||||
}
|
||||
res = append(res, ar)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
|
@ -13,8 +13,12 @@ type Fixed struct {
|
|||
|
||||
var _ api.Tariff = (*Fixed)(nil)
|
||||
|
||||
func NewFixed(other map[string]interface{}) (*Fixed, error) {
|
||||
cc := Fixed{}
|
||||
func init() {
|
||||
registry.Add("fixed", NewFixedFromConfig)
|
||||
}
|
||||
|
||||
func NewFixedFromConfig(other map[string]interface{}) (api.Tariff, error) {
|
||||
var cc Fixed
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -6,15 +6,19 @@ import (
|
|||
)
|
||||
|
||||
type Tariffs struct {
|
||||
Currency currency.Unit
|
||||
Grid api.Tariff
|
||||
FeedIn api.Tariff
|
||||
Currency currency.Unit
|
||||
Grid, FeedIn, Planner api.Tariff
|
||||
}
|
||||
|
||||
func NewTariffs(currency currency.Unit, grid api.Tariff, feedin api.Tariff) *Tariffs {
|
||||
t := Tariffs{}
|
||||
t.Currency = currency
|
||||
t.Grid = grid
|
||||
t.FeedIn = feedin
|
||||
return &t
|
||||
func NewTariffs(currency currency.Unit, grid, feedin, planner api.Tariff) *Tariffs {
|
||||
if planner == nil {
|
||||
planner = grid
|
||||
}
|
||||
|
||||
return &Tariffs{
|
||||
Currency: currency,
|
||||
Grid: grid,
|
||||
FeedIn: feedin,
|
||||
Planner: planner,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,11 @@ type Tibber struct {
|
|||
|
||||
var _ api.Tariff = (*Tibber)(nil)
|
||||
|
||||
func NewTibber(other map[string]interface{}) (*Tibber, error) {
|
||||
func init() {
|
||||
registry.Add("tibber", NewTibberFromConfig)
|
||||
}
|
||||
|
||||
func NewTibberFromConfig(other map[string]interface{}) (api.Tariff, error) {
|
||||
var cc struct {
|
||||
Token string
|
||||
HomeID string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue