Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tariffs configuration #10042

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
69 changes: 46 additions & 23 deletions cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/evcc-io/evcc/server/db/settings"
"github.com/evcc-io/evcc/server/oauth2redirect"
"github.com/evcc-io/evcc/tariff"
"github.com/evcc-io/evcc/tariff/tariffs"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/config"
"github.com/evcc-io/evcc/util/locale"
Expand Down Expand Up @@ -132,7 +133,7 @@ type messagingConfig struct {
type tariffConfig struct {
Currency string
Grid config.Typed
FeedIn config.Typed
Feedin config.Typed
Co2 config.Typed
Planner config.Typed
}
Expand Down Expand Up @@ -592,42 +593,64 @@ func configureMessengers(conf messagingConfig, vehicles push.Vehicles, valueChan
return messageChan, nil
}

func configureTariff(name string, conf config.Typed, t *api.Tariff, wg *sync.WaitGroup) {
defer wg.Done()
func configureTariff(name string, conf config.Typed, t *tariffs.Tariffs) (err error) {
// ignore tariff errors and continue setup
defer (func() {
if err != nil {
log.ERROR.Printf("failed configuring %s tariff: %v", name, err)
err = nil
}
})()

// load tariff from settings
ref, err := settings.String(tariffs.SettingsKey + name)
if err == nil && ref != "" {
configurable, err := config.ConfigurationsByClass(templates.Tariff)
if err != nil {
return err
}

idx := slices.IndexFunc(configurable, func(conf config.Config) bool {
return conf.Named().Name == name
})

if idx < 0 {
return fmt.Errorf("cannot find tariff %s", name)
}

conf = configurable[idx].Typed()
}

if conf.Type == "" {
return
return nil
}

res, err := tariff.NewFromConfig(conf.Type, conf.Other)
if err != nil {
log.ERROR.Printf("failed configuring %s tariff: %v", name, err)
return
return err
}

*t = res
}
t.SetInstance(name, res)

func configureTariffs(conf tariffConfig) (*tariff.Tariffs, error) {
tariffs := tariff.Tariffs{
Currency: currency.EUR,
}
return nil
}

func configureTariffs(conf tariffConfig) (*tariffs.Tariffs, error) {
c := currency.EUR
if conf.Currency != "" {
tariffs.Currency = currency.MustParseISO(conf.Currency)
c = currency.MustParseISO(conf.Currency)
}

var wg sync.WaitGroup
wg.Add(4)
res := tariffs.New(c)

go configureTariff("grid", conf.Grid, &tariffs.Grid, &wg)
go configureTariff("feedin", conf.FeedIn, &tariffs.FeedIn, &wg)
go configureTariff("co2", conf.Co2, &tariffs.Co2, &wg)
go configureTariff("planner", conf.Planner, &tariffs.Planner, &wg)

wg.Wait()
g, _ := errgroup.WithContext(context.Background())
g.Go(func() error { return configureTariff(tariffs.Grid, conf.Grid, res) })
g.Go(func() error { return configureTariff(tariffs.Feedin, conf.Feedin, res) })
g.Go(func() error { return configureTariff(tariffs.Co2, conf.Co2, res) })
g.Go(func() error { return configureTariff(tariffs.Planner, conf.Planner, res) })
err := g.Wait()

return &tariffs, nil
return res, err
}

func configureDevices(conf globalConfig) error {
Expand Down Expand Up @@ -658,7 +681,7 @@ func configureSiteAndLoadpoints(conf globalConfig) (*core.Site, error) {
return configureSite(conf.Site, loadpoints, tariffs)
}

func configureSite(conf map[string]interface{}, loadpoints []*core.Loadpoint, tariffs *tariff.Tariffs) (*core.Site, error) {
func configureSite(conf map[string]interface{}, loadpoints []*core.Loadpoint, tariffs *tariffs.Tariffs) (*core.Site, error) {
site, err := core.NewSiteFromConfig(log, conf, loadpoints, tariffs)
if err != nil {
return nil, fmt.Errorf("failed configuring site: %w", err)
Expand Down
9 changes: 5 additions & 4 deletions cmd/tariff.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"text/tabwriter"

"github.com/evcc-io/evcc/tariff"
"github.com/evcc-io/evcc/tariff/tariffs"
"github.com/evcc-io/evcc/util/config"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -39,10 +40,10 @@ func runTariff(cmd *cobra.Command, args []string) {
}

for key, cc := range map[string]config.Typed{
"grid": conf.Tariffs.Grid,
"feedin": conf.Tariffs.FeedIn,
"co2": conf.Tariffs.Co2,
"planner": conf.Tariffs.Planner,
tariffs.Grid: conf.Tariffs.Grid,
tariffs.Feedin: conf.Tariffs.Feedin,
tariffs.Co2: conf.Tariffs.Co2,
tariffs.Planner: conf.Tariffs.Planner,
} {
if cc.Type == "" || (name != "" && key != name) {
continue
Expand Down
16 changes: 8 additions & 8 deletions core/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"github.com/evcc-io/evcc/push"
"github.com/evcc-io/evcc/server/db"
"github.com/evcc-io/evcc/server/db/settings"
"github.com/evcc-io/evcc/tariff"
"github.com/evcc-io/evcc/tariff/tariffs"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/config"
"github.com/evcc-io/evcc/util/telemetry"
Expand Down Expand Up @@ -87,7 +87,7 @@ type Site struct {
batteryDischargeControl bool // prevent battery discharge for fast and planned charging

loadpoints []*Loadpoint // Loadpoints
tariffs *tariff.Tariffs // Tariffs
tariffs tariffs.API // Tariffs
coordinator *coordinator.Coordinator // Vehicles
prioritizer *prioritizer.Prioritizer // Power budgets
stats *Stats // Stats
Expand Down Expand Up @@ -115,7 +115,7 @@ func NewSiteFromConfig(
log *util.Logger,
other map[string]interface{},
loadpoints []*Loadpoint,
tariffs *tariff.Tariffs,
tariffs_ *tariffs.Tariffs,
) (*Site, error) {
site := NewSite()
if err := util.DecodeOther(other, site); err != nil {
Expand All @@ -124,7 +124,7 @@ func NewSiteFromConfig(

Voltage = site.Voltage
site.loadpoints = loadpoints
site.tariffs = tariffs
site.tariffs = tariffs_

handler := config.Vehicles()
site.coordinator = coordinator.New(log, config.Instances(handler.Devices()))
Expand All @@ -140,7 +140,7 @@ func NewSiteFromConfig(
})
}

tariff := site.GetTariff(PlannerTariff)
tariff := site.GetTariff(tariffs.Planner)

// give loadpoints access to vehicles and database
for _, lp := range loadpoints {
Expand Down Expand Up @@ -790,7 +790,7 @@ func (site *Site) update(lp Updater) {
}

var smartCostActive bool
if tariff := site.GetTariff(PlannerTariff); tariff != nil && tariff.Type() != api.TariffTypePriceStatic {
if tariff := site.GetTariff(tariffs.Planner); tariff != nil && tariff.Type() != api.TariffTypePriceStatic {
rates, err := tariff.Rates()

var rate api.Rate
Expand Down Expand Up @@ -861,10 +861,10 @@ func (site *Site) prepare() {
site.publish(keys.BatteryDischargeControl, site.batteryDischargeControl)
site.publish(keys.ResidualPower, site.ResidualPower)

site.publish(keys.Currency, site.tariffs.Currency)
site.publish(keys.Currency, site.tariffs.GetCurrency())
site.publish(keys.SmartCostActive, false)
site.publish(keys.SmartCostLimit, site.smartCostLimit)
if tariff := site.GetTariff(PlannerTariff); tariff != nil {
if tariff := site.GetTariff(tariffs.Planner); tariff != nil {
site.publish(keys.SmartCostType, tariff.Type())
} else {
site.publish(keys.SmartCostType, nil)
Expand Down
3 changes: 2 additions & 1 deletion core/site/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package site
import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/core/loadpoint"
"github.com/evcc-io/evcc/tariff/tariffs"
)

// API is the external site API
Expand Down Expand Up @@ -45,7 +46,7 @@ type API interface {
// tariffs and costs
//

// GetTariff returns the respective tariff
GetTariffs() tariffs.API
GetTariff(string) api.Tariff
GetSmartCostLimit() float64
SetSmartCostLimit(float64) error
Expand Down
39 changes: 18 additions & 21 deletions core/site_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,14 @@ import (
"github.com/evcc-io/evcc/core/loadpoint"
"github.com/evcc-io/evcc/core/site"
"github.com/evcc-io/evcc/server/db/settings"
"github.com/evcc-io/evcc/tariff/tariffs"
"github.com/evcc-io/evcc/util/config"
)

var _ site.API = (*Site)(nil)

var ErrBatteryNotConfigured = errors.New("battery not configured")

const (
GridTariff = "grid"
FeedinTariff = "feedin"
PlannerTariff = "planner"
)

// isConfigurable checks if the meter is configurable
func isConfigurable(ref string) bool {
dev, _ := config.Meters().ByName(ref)
Expand Down Expand Up @@ -278,35 +273,37 @@ func (site *Site) SetSmartCostLimit(val float64) error {
return nil
}

// GetTariffs returns the tariffs api
func (site *Site) GetTariffs() tariffs.API {
return site.tariffs
}

// GetTariff returns the respective tariff if configured or nil
func (site *Site) GetTariff(tariff string) api.Tariff {
func (site *Site) GetTariff(ref string) api.Tariff {
site.RLock()
defer site.RUnlock()

switch tariff {
case GridTariff:
return site.tariffs.Grid

case FeedinTariff:
return site.tariffs.FeedIn
switch ref {
case tariffs.Grid, tariffs.Feedin, tariffs.Co2:
return site.tariffs.GetInstance(ref)

case PlannerTariff:
case tariffs.Planner:
switch {
case site.tariffs.Planner != nil:
case site.tariffs.GetInstance(tariffs.Planner) != nil:
// prio 0: manually set planner tariff
return site.tariffs.Planner
return site.tariffs.GetInstance(tariffs.Planner)

case site.tariffs.Grid != nil && site.tariffs.Grid.Type() == api.TariffTypePriceForecast:
case site.tariffs.GetInstance(tariffs.Grid) != nil && site.tariffs.GetInstance(tariffs.Grid).Type() == api.TariffTypePriceForecast:
// prio 1: grid tariff with forecast
return site.tariffs.Grid
return site.tariffs.GetInstance(tariffs.Grid)

case site.tariffs.Co2 != nil:
case site.tariffs.GetInstance(tariffs.Co2) != nil:
// prio 2: co2 tariff
return site.tariffs.Co2
return site.tariffs.GetInstance(tariffs.Co2)

default:
// prio 3: static grid tariff
return site.tariffs.Grid
return site.tariffs.GetInstance(tariffs.Grid)
}

default:
Expand Down
2 changes: 2 additions & 0 deletions server/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API, cache *util.Cache) {
"residualpower": {[]string{"POST", "OPTIONS"}, "/residualpower/{value:[-0-9.]+}", floatHandler(site.SetResidualPower, site.GetResidualPower)},
"smartcost": {[]string{"POST", "OPTIONS"}, "/smartcostlimit/{value:[-0-9.]+}", floatHandler(site.SetSmartCostLimit, site.GetSmartCostLimit)},
"tariff": {[]string{"GET"}, "/tariff/{tariff:[a-z]+}", tariffHandler(site)},
"tariffs": {[]string{"GET"}, "/tariffs", tariffsHandler(site)},
"updatetariffs": {[]string{"PUT", "OPTIONS"}, "/tariffs", updateTariffsHandler(site)},
"sessions": {[]string{"GET"}, "/sessions", sessionHandler},
"updatesession": {[]string{"PUT", "OPTIONS"}, "/session/{id:[0-9]+}", updateSessionHandler},
"deletesession": {[]string{"DELETE", "OPTIONS"}, "/session/{id:[0-9]+}", deleteSessionHandler},
Expand Down
13 changes: 13 additions & 0 deletions server/http_config_device_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/evcc-io/evcc/charger"
"github.com/evcc-io/evcc/meter"
"github.com/evcc-io/evcc/tariff"
"github.com/evcc-io/evcc/util/config"
"github.com/evcc-io/evcc/util/templates"
"github.com/evcc-io/evcc/vehicle"
Expand Down Expand Up @@ -218,6 +219,9 @@ func newDeviceHandler(w http.ResponseWriter, r *http.Request) {

case templates.Vehicle:
conf, err = newDevice(class, req, vehicle.NewFromConfig, config.Vehicles())

case templates.Tariff:
conf, err = newDevice(class, req, tariff.NewFromConfig, config.Tariffs())
}

if err != nil {
Expand Down Expand Up @@ -284,6 +288,9 @@ func updateDeviceHandler(w http.ResponseWriter, r *http.Request) {

case templates.Vehicle:
err = updateDevice(id, class, req, vehicle.NewFromConfig, config.Vehicles())

case templates.Tariff:
err = updateDevice(id, class, req, tariff.NewFromConfig, config.Tariffs())
}

setConfigDirty()
Expand Down Expand Up @@ -347,6 +354,9 @@ func deleteDeviceHandler(w http.ResponseWriter, r *http.Request) {

case templates.Vehicle:
err = deleteDevice(id, config.Vehicles())

case templates.Tariff:
err = deleteDevice(id, config.Tariffs())
}

setConfigDirty()
Expand Down Expand Up @@ -413,6 +423,9 @@ func testConfigHandler(w http.ResponseWriter, r *http.Request) {

case templates.Vehicle:
instance, err = testConfig(id, class, req, vehicle.NewFromConfig, config.Vehicles())

case templates.Tariff:
instance, err = testConfig(id, class, req, tariff.NewFromConfig, config.Tariffs())
}

if err != nil {
Expand Down
12 changes: 12 additions & 0 deletions server/http_config_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package server

import (
"errors"
"net/http"
"sync"

"github.com/evcc-io/evcc/api"
Expand Down Expand Up @@ -188,3 +189,14 @@ func testInstance(instance any) map[string]testResult {

return res
}

// validateRefs checks if the given references are valid and returns HTTP 400 otherwise
func validateRefs[T any](w http.ResponseWriter, handler config.Handler[T], refs []string) bool {
for _, m := range refs {
if _, err := handler.ByName(m); err != nil {
jsonError(w, http.StatusBadRequest, err)
return false
}
}
return true
}