/
savings.go
149 lines (121 loc) · 4.24 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
package core
import (
"time"
"github.com/benbjohnson/clock"
"github.com/thommyho/robotui/server/db/settings"
"github.com/thommyho/robotui/tariff"
)
const (
DefaultGridPrice = 0.30
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)
hasPublished bool // Has initial publish happened?
}
func NewSavings(tariffs tariff.Tariffs) *Savings {
clock := clock.New()
savings := &Savings{
clock: clock,
tariffs: tariffs,
started: clock.Now(),
updated: clock.Now(),
}
savings.load()
return savings
}
func (s *Savings) load() {
s.started, _ = settings.Time("savings.started")
s.gridCharged, _ = settings.Float("savings.gridCharged")
s.gridCost, _ = settings.Float("savings.gridCost")
s.gridSavedCost, _ = settings.Float("savings.gridSavedCost")
s.selfConsumptionCharged, _ = settings.Float("savings.selfConsumptionCharged")
s.selfConsumptionCost, _ = settings.Float("savings.selfConsumptionCost")
}
func (s *Savings) save() {
settings.SetTime("savings.started", s.started)
settings.SetFloat("savings.gridCharged", s.gridCharged)
settings.SetFloat("savings.gridCost", s.gridCost)
settings.SetFloat("savings.gridSavedCost", s.gridSavedCost)
settings.SetFloat("savings.selfConsumptionCharged", s.selfConsumptionCharged)
settings.SetFloat("savings.selfConsumptionCost", s.selfConsumptionCost)
}
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) currentGridPrice() float64 {
price, err := s.tariffs.CurrentGridPrice()
if err != nil {
price = DefaultGridPrice
}
return price
}
func (s *Savings) currentFeedInPrice() float64 {
price, err := s.tariffs.CurrentFeedInPrice()
if err != nil {
price = DefaultFeedInPrice
}
return price
}
// Update savings calculation and return grid/green energy added since last update
func (s *Savings) Update(p publisher, greenShare, chargePower float64) float64 {
defer func() { s.updated = s.clock.Now() }()
// no charging, no need to update
if chargePower == 0 && s.hasPublished {
return 0
}
// assume charge power as constant over the duration -> rough kWh estimate
deltaCharged := s.clock.Since(s.updated).Hours() * chargePower / 1e3
deltaSelf := deltaCharged * greenShare
deltaGrid := deltaCharged - deltaSelf
gridPrice := s.currentGridPrice()
feedInPrice := s.currentFeedInPrice()
s.gridCharged += deltaGrid
s.gridCost += deltaGrid * gridPrice
s.gridSavedCost += deltaSelf * (gridPrice - feedInPrice)
s.selfConsumptionCharged += deltaSelf
s.selfConsumptionCost += deltaSelf * 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())
s.hasPublished = true
s.save()
return deltaCharged
}