149 lines
4.2 KiB
Go
149 lines
4.2 KiB
Go
package core
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/benbjohnson/clock"
|
|
"github.com/evcc-io/evcc/server/db/settings"
|
|
"github.com/evcc-io/evcc/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
|
|
}
|