Experimental: add telemetry for virtual power plant (#4343)
This commit is contained in:
parent
b272f24d24
commit
f57f9edeaa
6 changed files with 102 additions and 15 deletions
|
|
@ -28,6 +28,7 @@ type config struct {
|
|||
Network networkConfig
|
||||
Log string
|
||||
SponsorToken string
|
||||
Telemetry bool
|
||||
Metrics bool
|
||||
Profile bool
|
||||
Levels map[string]string
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/pipe"
|
||||
"github.com/evcc-io/evcc/util/sponsor"
|
||||
"github.com/evcc-io/evcc/util/telemetry"
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/text/currency"
|
||||
|
|
@ -51,6 +52,10 @@ func configureEnvironment(conf config) (err error) {
|
|||
err = sponsor.ConfigureSponsorship(conf.SponsorToken)
|
||||
}
|
||||
|
||||
if err == nil && conf.Telemetry {
|
||||
err = telemetry.Create(sponsor.Token)
|
||||
}
|
||||
|
||||
// setup mqtt client listener
|
||||
if err == nil && conf.Mqtt.Broker != "" {
|
||||
err = configureMQTT(conf.Mqtt)
|
||||
|
|
|
|||
|
|
@ -125,27 +125,28 @@ func (s *Savings) updatePrices(p publisher) (float64, float64) {
|
|||
return gridPrice, feedinPrice
|
||||
}
|
||||
|
||||
func (s *Savings) Update(p publisher, gridPower, pvPower, batteryPower, chargePower float64) {
|
||||
// Update savings calculation and return grid/green energy added since last update
|
||||
func (s *Savings) Update(p publisher, gridPower, pvPower, batteryPower, chargePower float64) (float64, float64) {
|
||||
gridPrice, feedinPrice := s.updatePrices(p)
|
||||
defer func() { s.updated = s.clock.Now() }()
|
||||
|
||||
// no charging, no need to update
|
||||
if chargePower == 0 {
|
||||
return
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// assume charge power as constant over the duration -> rough kWh estimate
|
||||
energyAdded := s.clock.Since(s.updated).Hours() * chargePower / 1e3
|
||||
deltaCharged := s.clock.Since(s.updated).Hours() * chargePower / 1e3
|
||||
share := s.shareOfSelfProducedEnergy(gridPower, pvPower, batteryPower)
|
||||
|
||||
addedSelfConsumption := energyAdded * share
|
||||
addedGrid := energyAdded - addedSelfConsumption
|
||||
deltaSelf := deltaCharged * share
|
||||
deltaGrid := deltaCharged - deltaSelf
|
||||
|
||||
s.gridCharged += addedGrid
|
||||
s.gridCost += addedGrid * gridPrice
|
||||
s.gridSavedCost += addedSelfConsumption * (gridPrice - feedinPrice)
|
||||
s.selfConsumptionCharged += addedSelfConsumption
|
||||
s.selfConsumptionCost += addedSelfConsumption * feedinPrice
|
||||
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)
|
||||
|
|
@ -153,4 +154,6 @@ func (s *Savings) Update(p publisher, gridPower, pvPower, batteryPower, chargePo
|
|||
p.publish("savingsSelfConsumptionPercent", s.SelfConsumptionPercent())
|
||||
p.publish("savingsEffectivePrice", s.EffectivePrice())
|
||||
p.publish("savingsAmount", s.SavingsAmount())
|
||||
|
||||
return deltaCharged, deltaSelf
|
||||
}
|
||||
|
|
|
|||
14
core/site.go
14
core/site.go
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/evcc-io/evcc/push"
|
||||
"github.com/evcc-io/evcc/tariff"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/telemetry"
|
||||
)
|
||||
|
||||
// Updater abstracts the LoadPoint implementation for testing
|
||||
|
|
@ -325,7 +326,7 @@ func (site *Site) updateMeters() error {
|
|||
if err == nil {
|
||||
site.batteryPower += power
|
||||
} else {
|
||||
site.log.ERROR.Println(fmt.Errorf("battery meter %d: %v", id, err))
|
||||
site.log.ERROR.Printf("battery meter %d: %v", id, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -342,7 +343,7 @@ func (site *Site) updateMeters() error {
|
|||
site.log.DEBUG.Printf("grid currents: %.3gA", []float64{i1, i2, i3})
|
||||
site.publish("gridCurrents", []float64{i1, i2, i3})
|
||||
} else {
|
||||
site.log.ERROR.Println(fmt.Errorf("grid meter currents: %v", err))
|
||||
site.log.ERROR.Printf("grid meter currents: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -371,7 +372,7 @@ func (site *Site) sitePower(totalChargePower float64) (float64, error) {
|
|||
site.gridPower = totalChargePower - site.pvPower
|
||||
}
|
||||
|
||||
// allow using Grid and charge as estimate for pv power
|
||||
// allow using grid and charge as estimate for pv power
|
||||
if site.pvMeters == nil {
|
||||
site.pvPower = totalChargePower - site.gridPower + site.ResidualPower
|
||||
if site.pvPower < 0 {
|
||||
|
|
@ -448,9 +449,12 @@ func (site *Site) update(lp Updater) {
|
|||
site.Health.Update()
|
||||
}
|
||||
|
||||
// update savings
|
||||
// update savings and aggregate telemetry
|
||||
// TODO: use energy instead of current power for better results
|
||||
site.savings.Update(site, site.gridPower, site.pvPower, site.batteryPower, totalChargePower)
|
||||
deltaCharged, deltaSelf := site.savings.Update(site, site.gridPower, site.pvPower, site.batteryPower, totalChargePower)
|
||||
if totalChargePower > 0 {
|
||||
go telemetry.ChargeProgress(site.log, totalChargePower, deltaCharged, deltaSelf)
|
||||
}
|
||||
}
|
||||
|
||||
// prepare publishes initial values
|
||||
|
|
|
|||
|
|
@ -15,6 +15,19 @@ interval: 10s # control cycle interval
|
|||
# sponsor token enables optional features (request at https://cloud.evcc.io)
|
||||
# sponsortoken:
|
||||
|
||||
# telemetry enables aggregated statistics
|
||||
#
|
||||
# Telemetry allows collecting usage data (grid and green energy, charge power).
|
||||
# Data is aggregated, no individual charging sessions are tracked. The collected,
|
||||
# anonymous data can be retrieved using https://api.evcc.io.
|
||||
#
|
||||
# See https://github.com/evcc-io/evcc/pull/4343 or details.
|
||||
#
|
||||
# For time being, this is only available to sponsors, hence data is associated with
|
||||
# the sponsor token's identity.
|
||||
#
|
||||
# telemetry: true
|
||||
|
||||
# log settings
|
||||
log: info
|
||||
levels:
|
||||
|
|
|
|||
61
util/telemetry/community.go
Normal file
61
util/telemetry/community.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
package telemetry
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/request"
|
||||
"github.com/evcc-io/evcc/util/sponsor"
|
||||
)
|
||||
|
||||
var (
|
||||
api = "https://api.evcc.io"
|
||||
enabled bool
|
||||
)
|
||||
|
||||
func Create(token string) error {
|
||||
if token == "" {
|
||||
return errors.New("community requires sponsorship")
|
||||
}
|
||||
|
||||
enabled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func ChargeProgress(log *util.Logger, power, deltaCharged, deltaGreen float64) {
|
||||
log.DEBUG.Printf("community: Δ%.0f/%.0fkWh @ %.0fW", deltaGreen, deltaCharged, power)
|
||||
|
||||
if !enabled {
|
||||
return
|
||||
}
|
||||
|
||||
data := struct {
|
||||
ChargePower float64 `json:"chargePower"`
|
||||
ChargeEnergy float64 `json:"chargeEnergy"`
|
||||
GreenEnergy float64 `json:"greenEnergy"`
|
||||
}{
|
||||
ChargePower: power,
|
||||
ChargeEnergy: deltaCharged,
|
||||
GreenEnergy: deltaGreen,
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s/charged/%s", api, sponsor.Token)
|
||||
req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data))
|
||||
|
||||
var res struct {
|
||||
Error string
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
client := request.NewHelper(log)
|
||||
if err = client.DoJSON(req, &res); err == nil && res.Error != "" {
|
||||
err = errors.New(res.Error)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.ERROR.Printf("community api: %v", err)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue