forked from evcc-io/evcc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsavings.go
154 lines (125 loc) · 4.42 KB
/
savings.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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package core
import (
"math"
"time"
"github.com/benbjohnson/clock"
"github.com/evcc-io/evcc/tariff"
)
const DefaultGridPrice = 0.30
const DefaultFeedInPrice = 0.08
// publisher gives access to the site's publish function
type publisher interface {
publish(key string, val interface{})
}
// Site is the main configuration container. A site can host multiple loadpoints.
type Savings struct {
clock clock.Clock
tariffs tariff.Tariffs
started time.Time // Boot time
updated time.Time // Time of last charged value update
gridCharged float64 // Grid energy charged since startup (kWh)
gridCost float64 // Running total of charged grid energy cost (e.g. EUR)
gridSavedCost float64 // Running total of saved cost from self consumption (e.g. EUR)
selfConsumptionCharged float64 // Self-produced energy charged since startup (kWh)
selfConsumptionCost float64 // Running total of charged self-produced energy cost (e.g. EUR)
lastGridPrice, lastFeedInPrice float64 // Stores the last published grid price. Needed to detect price changes (Awattar, ..)
}
func NewSavings(tariffs tariff.Tariffs) *Savings {
clock := clock.New()
savings := &Savings{
clock: clock,
tariffs: tariffs,
started: clock.Now(),
updated: clock.Now(),
}
return savings
}
func (s *Savings) Since() time.Time {
return s.started
}
func (s *Savings) SelfConsumptionPercent() float64 {
if s.TotalCharged() == 0 {
return 0
}
return s.selfConsumptionCharged / s.TotalCharged() * 100
}
func (s *Savings) TotalCharged() float64 {
return s.gridCharged + s.selfConsumptionCharged
}
func (s *Savings) CostTotal() float64 {
return s.gridCost + s.selfConsumptionCost
}
func (s *Savings) EffectivePrice() float64 {
if s.TotalCharged() == 0 {
return s.currentGridPrice()
}
return s.CostTotal() / s.TotalCharged()
}
func (s *Savings) SavingsAmount() float64 {
return s.gridSavedCost
}
func (s *Savings) shareOfSelfProducedEnergy(gridPower, pvPower, batteryPower float64) float64 {
batteryDischarge := math.Max(0, batteryPower)
batteryCharge := math.Min(0, batteryPower) * -1
pvConsumption := math.Min(pvPower, pvPower+gridPower-batteryCharge)
gridImport := math.Max(0, gridPower)
selfConsumption := math.Max(0, batteryDischarge+pvConsumption+batteryCharge)
share := selfConsumption / (gridImport + selfConsumption)
if math.IsNaN(share) {
return 0
}
return share
}
func (s *Savings) currentGridPrice() float64 {
if s.tariffs.Grid != nil {
if gridPrice, err := s.tariffs.Grid.CurrentPrice(); err == nil {
return gridPrice
}
}
return DefaultGridPrice
}
func (s *Savings) currentFeedInPrice() float64 {
if s.tariffs.FeedIn != nil {
if gridPrice, err := s.tariffs.FeedIn.CurrentPrice(); err == nil {
return gridPrice
}
}
return DefaultFeedInPrice
}
func (s *Savings) updatePrices(p publisher) (float64, float64) {
gridPrice := s.currentGridPrice()
if gridPrice != s.lastGridPrice {
s.lastGridPrice = gridPrice
p.publish("tariffGrid", gridPrice)
}
feedinPrice := s.currentFeedInPrice()
if feedinPrice != s.lastFeedInPrice {
s.lastFeedInPrice = feedinPrice
p.publish("tariffFeedIn", feedinPrice)
}
return gridPrice, feedinPrice
}
func (s *Savings) Update(p publisher, gridPower, pvPower, batteryPower, chargePower float64) {
gridPrice, feedinPrice := s.updatePrices(p)
defer func() { s.updated = s.clock.Now() }()
// no charging, no need to update
if chargePower == 0 {
return
}
// assume charge power as constant over the duration -> rough kWh estimate
energyAdded := s.clock.Since(s.updated).Hours() * chargePower / 1e3
share := s.shareOfSelfProducedEnergy(gridPower, pvPower, batteryPower)
addedSelfConsumption := energyAdded * share
addedGrid := energyAdded - addedSelfConsumption
s.gridCharged += addedGrid
s.gridCost += addedGrid * gridPrice
s.gridSavedCost += addedSelfConsumption * (gridPrice - feedinPrice)
s.selfConsumptionCharged += addedSelfConsumption
s.selfConsumptionCost += addedSelfConsumption * feedinPrice
p.publish("savingsTotalCharged", s.TotalCharged())
p.publish("savingsGridCharged", s.gridCharged)
p.publish("savingsSelfConsumptionCharged", s.selfConsumptionCharged)
p.publish("savingsSelfConsumptionPercent", s.SelfConsumptionPercent())
p.publish("savingsEffectivePrice", s.EffectivePrice())
p.publish("savingsAmount", s.SavingsAmount())
}