/
electricitymaps.go
129 lines (106 loc) Β· 2.57 KB
/
electricitymaps.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package tariff
import (
"errors"
"fmt"
"slices"
"strings"
"sync"
"time"
"github.com/cenkalti/backoff/v4"
"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
uri string
zone string
data *util.Monitor[api.Rates]
}
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),
data: util.NewMonitor[api.Rates](2 * time.Hour),
}
t.Client.Transport = &transport.Decorator{
Base: t.Client.Transport,
Decorator: transport.DecorateHeaders(map[string]string{
"X-BLOBR-KEY": cc.Token,
}),
}
done := make(chan error)
go t.run(done)
err := <-done
return t, err
}
func (t *ElectricityMaps) run(done chan error) {
var once sync.Once
bo := newBackoff()
uri := fmt.Sprintf("%s/carbon-intensity/forecast?zone=%s", t.uri, t.zone)
tick := time.NewTicker(time.Hour)
for ; true; <-tick.C {
var res CarbonIntensity
if err := backoff.Retry(func() error {
return backoffPermanentError(t.GetJSON(uri, &res))
}, bo); err != nil {
if res.Error != "" {
err = errors.New(res.Error)
}
once.Do(func() { done <- err })
t.log.ERROR.Println(err)
continue
}
data := make(api.Rates, 0, len(res.Forecast))
for _, r := range res.Forecast {
ar := api.Rate{
Start: r.Datetime.Local(),
End: r.Datetime.Add(time.Hour).Local(),
Price: r.CarbonIntensity,
}
data = append(data, ar)
}
data.Sort()
t.data.Set(data)
once.Do(func() { close(done) })
}
}
func (t *ElectricityMaps) 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 *ElectricityMaps) Type() api.TariffType {
return api.TariffTypeCo2
}