diff --git a/cmd/setup.go b/cmd/setup.go index 74a28f1a73..3a55e14a8f 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -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" @@ -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 } @@ -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 { @@ -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) diff --git a/cmd/tariff.go b/cmd/tariff.go index b5cc3a3eb3..92047d4c5e 100644 --- a/cmd/tariff.go +++ b/cmd/tariff.go @@ -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" ) @@ -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 diff --git a/core/site.go b/core/site.go index 2d38e87072..72cd2a8995 100644 --- a/core/site.go +++ b/core/site.go @@ -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" @@ -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 @@ -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 { @@ -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())) @@ -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 { @@ -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 @@ -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) diff --git a/core/site/api.go b/core/site/api.go index 9098615227..7fc02c86ee 100644 --- a/core/site/api.go +++ b/core/site/api.go @@ -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 @@ -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 diff --git a/core/site_api.go b/core/site_api.go index b098e1e1ee..f389e0dbd0 100644 --- a/core/site_api.go +++ b/core/site_api.go @@ -9,6 +9,7 @@ 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" ) @@ -16,12 +17,6 @@ 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) @@ -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: diff --git a/server/http.go b/server/http.go index b3e34b4ed8..0df867a048 100644 --- a/server/http.go +++ b/server/http.go @@ -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}, diff --git a/server/http_config_device_handler.go b/server/http_config_device_handler.go index 74e09c9b91..9ab1694335 100644 --- a/server/http_config_device_handler.go +++ b/server/http_config_device_handler.go @@ -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" @@ -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 { @@ -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() @@ -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() @@ -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 { diff --git a/server/http_config_helper.go b/server/http_config_helper.go index 936cc3fc3f..2f4c9cb9bc 100644 --- a/server/http_config_helper.go +++ b/server/http_config_helper.go @@ -2,6 +2,7 @@ package server import ( "errors" + "net/http" "sync" "github.com/evcc-io/evcc/api" @@ -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 +} diff --git a/server/http_config_site_handler.go b/server/http_config_site_handler.go index eb3b0a2208..3fc93a55f1 100644 --- a/server/http_config_site_handler.go +++ b/server/http_config_site_handler.go @@ -27,16 +27,6 @@ func siteHandler(site site.API) http.HandlerFunc { } } -func validateRefs(w http.ResponseWriter, refs []string) bool { - for _, m := range refs { - if _, err := config.Meters().ByName(m); err != nil { - jsonError(w, http.StatusBadRequest, err) - return false - } - } - return true -} - // siteHandler returns a device configurations by class func updateSiteHandler(site site.API) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -52,12 +42,14 @@ func updateSiteHandler(site site.API) http.HandlerFunc { return } + meters := config.Meters() + if payload.Title != nil { site.SetTitle(*payload.Title) } if payload.Grid != nil { - if *payload.Grid != "" && !validateRefs(w, []string{*payload.Grid}) { + if *payload.Grid != "" && !validateRefs(w, meters, []string{*payload.Grid}) { return } @@ -66,7 +58,7 @@ func updateSiteHandler(site site.API) http.HandlerFunc { } if payload.PV != nil { - if !validateRefs(w, *payload.PV) { + if !validateRefs(w, meters, *payload.PV) { return } @@ -75,7 +67,7 @@ func updateSiteHandler(site site.API) http.HandlerFunc { } if payload.Battery != nil { - if !validateRefs(w, *payload.Battery) { + if !validateRefs(w, meters, *payload.Battery) { return } diff --git a/server/http_config_tariffs_handler.go b/server/http_config_tariffs_handler.go new file mode 100644 index 0000000000..482036fd07 --- /dev/null +++ b/server/http_config_tariffs_handler.go @@ -0,0 +1,124 @@ +package server + +import ( + "encoding/json" + "errors" + "net/http" + + "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/core/site" + "github.com/evcc-io/evcc/tariff/tariffs" + "github.com/evcc-io/evcc/util/config" + "github.com/gorilla/mux" +) + +// tariffHandler returns the configured tariff +func tariffHandler(site site.API) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + tariff := vars["tariff"] + + t := site.GetTariff(tariff) + if t == nil { + jsonError(w, http.StatusNotFound, errors.New("tariff not available")) + return + } + + rates, err := t.Rates() + if err != nil { + jsonError(w, http.StatusNotFound, err) + return + } + + res := struct { + Rates api.Rates `json:"rates"` + }{ + Rates: rates, + } + + jsonResult(w, res) + } +} + +// tariffsHandler returns a device configurations by class +func tariffsHandler(site site.API) http.HandlerFunc { + tfs := site.GetTariffs() + + return func(w http.ResponseWriter, r *http.Request) { + res := struct { + Currency string `json:"currency,omitempty"` + Grid string `json:"grid,omitempty"` + Feedin string `json:"feedin,omitempty"` + Co2 string `json:"co2,omitempty"` + Planner string `json:"planner,omitempty"` + }{ + Currency: tfs.GetCurrency().String(), + Grid: tfs.GetRef(tariffs.Grid), + Feedin: tfs.GetRef(tariffs.Feedin), + Co2: tfs.GetRef(tariffs.Co2), + Planner: tfs.GetRef(tariffs.Planner), + } + + jsonResult(w, res) + } +} + +// updateTariffsHandler returns a device configurations by class +func updateTariffsHandler(site site.API) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var payload struct { + Currency *string `json:"currency"` + Grid *string `json:"grid"` + Feedin *string `json:"feedin"` + Co2 *string `json:"co2"` + Planner *string `json:"planner"` + } + + if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { + jsonError(w, http.StatusBadRequest, err) + return + } + + tfs := site.GetTariffs() + + if payload.Currency != nil { + if err := tfs.SetCurrency(*payload.Currency); err != nil { + jsonError(w, http.StatusBadRequest, err) + return + } + + setConfigDirty() + } + + update := func(tariff string, ref *string) bool { + if ref != nil { + if *ref != "" && !validateRefs(w, config.Tariffs(), []string{*ref}) { + return false + } + + tfs.SetRef(tariff, *ref) + setConfigDirty() + } + return true + } + + if !update(tariffs.Grid, payload.Grid) { + return + } + + if !update(tariffs.Feedin, payload.Feedin) { + return + } + + if !update(tariffs.Co2, payload.Co2) { + return + } + + if !update(tariffs.Planner, payload.Planner) { + return + } + + status := map[bool]int{false: http.StatusOK, true: http.StatusAccepted} + w.WriteHeader(status[ConfigDirty()]) + } +} diff --git a/server/http_site_handler.go b/server/http_site_handler.go index aa2e99beb5..42cb82e4a4 100644 --- a/server/http_site_handler.go +++ b/server/http_site_handler.go @@ -2,7 +2,6 @@ package server import ( "encoding/json" - "errors" "fmt" "io/fs" "math" @@ -11,7 +10,6 @@ import ( "strings" "text/template" - "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/core/site" "github.com/evcc-io/evcc/server/assets" "github.com/evcc-io/evcc/util" @@ -223,34 +221,6 @@ func healthHandler(site site.API) http.HandlerFunc { } } -// tariffHandler returns the configured tariff -func tariffHandler(site site.API) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - tariff := vars["tariff"] - - t := site.GetTariff(tariff) - if t == nil { - jsonError(w, http.StatusNotFound, errors.New("tariff not available")) - return - } - - rates, err := t.Rates() - if err != nil { - jsonError(w, http.StatusNotFound, err) - return - } - - res := struct { - Rates api.Rates `json:"rates"` - }{ - Rates: rates, - } - - jsonResult(w, res) - } -} - // socketHandler attaches websocket handler to uri func socketHandler(hub *SocketHub) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { diff --git a/tariff/tariffs.go b/tariff/tariffs.go deleted file mode 100644 index ffeb024cf2..0000000000 --- a/tariff/tariffs.go +++ /dev/null @@ -1,42 +0,0 @@ -package tariff - -import ( - "time" - - "github.com/evcc-io/evcc/api" - "golang.org/x/text/currency" -) - -type Tariffs struct { - Currency currency.Unit - Grid, FeedIn, Co2, Planner api.Tariff -} - -func currentPrice(t api.Tariff) (float64, error) { - if t != nil { - if rr, err := t.Rates(); err == nil { - if r, err := rr.Current(time.Now()); err == nil { - return r.Price, nil - } - } - } - return 0, api.ErrNotAvailable -} - -// CurrentGridPrice returns the current grid price. -func (t *Tariffs) CurrentGridPrice() (float64, error) { - return currentPrice(t.Grid) -} - -// CurrentFeedInPrice returns the current feed-in price. -func (t *Tariffs) CurrentFeedInPrice() (float64, error) { - return currentPrice(t.FeedIn) -} - -// CurrentCo2 determines the grids co2 emission. -func (t *Tariffs) CurrentCo2() (float64, error) { - if t.Co2 != nil { - return currentPrice(t.Co2) - } - return 0, api.ErrNotAvailable -} diff --git a/tariff/tariffs/api.go b/tariff/tariffs/api.go new file mode 100644 index 0000000000..3fb94bc989 --- /dev/null +++ b/tariff/tariffs/api.go @@ -0,0 +1,21 @@ +package tariffs + +import ( + "github.com/evcc-io/evcc/api" + "golang.org/x/text/currency" +) + +type API interface { + GetCurrency() currency.Unit + SetCurrency(string) error + + GetRef(ref string) string + SetRef(ref, value string) + + GetInstance(ref string) api.Tariff + SetInstance(ref string, tariff api.Tariff) + + CurrentGridPrice() (float64, error) + CurrentFeedInPrice() (float64, error) + CurrentCo2() (float64, error) +} diff --git a/tariff/tariffs/tariffs.go b/tariff/tariffs/tariffs.go new file mode 100644 index 0000000000..3089a75fa3 --- /dev/null +++ b/tariff/tariffs/tariffs.go @@ -0,0 +1,149 @@ +package tariffs + +import ( + "sync" + "time" + + "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/server/db/settings" + "golang.org/x/text/currency" +) + +const ( + SettingsKey = "tariffs." + + Grid = "grid" + Feedin = "feedin" + Planner = "planner" + Co2 = "co2" +) + +// Tariffs is the tariffs container +type Tariffs struct { + mu sync.RWMutex + currency currency.Unit + grid, feedin, co2, planner api.Tariff +} + +var _ API = (*Tariffs)(nil) + +// New creates a new tariffs container +func New(currency currency.Unit) *Tariffs { + return &Tariffs{ + currency: currency, + } +} + +func currentPrice(t api.Tariff) (float64, error) { + if t != nil { + if rr, err := t.Rates(); err == nil { + if r, err := rr.Current(time.Now()); err == nil { + return r.Price, nil + } + } + } + return 0, api.ErrNotAvailable +} + +func (t *Tariffs) GetCurrency() currency.Unit { + return t.currency +} + +func (t *Tariffs) SetCurrency(s string) error { + c, err := currency.ParseISO(s) + if err == nil { + t.currency = c + settings.SetString(SettingsKey+"currency", s) + } + return err +} + +func getRef(ref string, t api.Tariff) string { + val, err := settings.String(SettingsKey + ref) + if err == nil && val != "" { + return val + } + if t != nil { + return ref + } + return "" +} + +func (t *Tariffs) GetRef(ref string) string { + t.mu.RLock() + defer t.mu.RUnlock() + + switch ref { + case Grid: + return getRef(Grid, t.grid) + case Feedin: + return getRef(Feedin, t.feedin) + case Planner: + return getRef(Planner, t.planner) + case Co2: + return getRef(Co2, t.co2) + default: + panic("invalid tariff ref: " + ref) + } +} + +func (t *Tariffs) SetRef(ref, value string) { + t.mu.Lock() + defer t.mu.Unlock() + + settings.SetString(SettingsKey+ref, value) +} + +func (t *Tariffs) GetInstance(ref string) api.Tariff { + t.mu.RLock() + defer t.mu.RUnlock() + + switch ref { + case Grid: + return t.grid + case Feedin: + return t.feedin + case Planner: + return t.planner + case Co2: + return t.co2 + default: + panic("invalid tariff ref: " + ref) + } +} + +func (t *Tariffs) SetInstance(ref string, tariff api.Tariff) { + t.mu.Lock() + defer t.mu.Unlock() + + switch ref { + case Grid: + t.grid = tariff + case Feedin: + t.feedin = tariff + case Planner: + t.planner = tariff + case Co2: + t.co2 = tariff + default: + panic("invalid tariff ref: " + ref) + } +} + +// CurrentGridPrice returns the current grid price. +func (t *Tariffs) CurrentGridPrice() (float64, error) { + return currentPrice(t.grid) +} + +// CurrentFeedInPrice returns the current feed-in price. +func (t *Tariffs) CurrentFeedInPrice() (float64, error) { + return currentPrice(t.feedin) +} + +// CurrentCo2 determines the grids co2 emission. +func (t *Tariffs) CurrentCo2() (float64, error) { + if t.co2 != nil { + return currentPrice(t.co2) + } + return 0, api.ErrNotAvailable +} diff --git a/util/config/instance.go b/util/config/instance.go index 2fd2dcfacd..5e18703858 100644 --- a/util/config/instance.go +++ b/util/config/instance.go @@ -11,10 +11,12 @@ var instance = struct { meters *handler[api.Meter] chargers *handler[api.Charger] vehicles *handler[api.Vehicle] + tariffs *handler[api.Tariff] }{ meters: &handler[api.Meter]{topic: "meter"}, chargers: &handler[api.Charger]{topic: "charger"}, vehicles: &handler[api.Vehicle]{topic: "vehicle"}, + tariffs: &handler[api.Tariff]{topic: "tariff"}, } type Handler[T any] interface { @@ -37,6 +39,10 @@ func Vehicles() Handler[api.Vehicle] { return instance.vehicles } +func Tariffs() Handler[api.Tariff] { + return instance.tariffs +} + // Instances returns the instances of the given devices func Instances[T any](devices []Device[T]) []T { res := make([]T, 0, len(devices))