From 88286f146749d2c4ebd9aa1356b1965a0b051318 Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 24 Sep 2023 14:32:02 +0200 Subject: [PATCH 1/7] Allow configuring tariffs --- server/http_config_handler.go | 13 +++++++++++++ util/config/instance.go | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/server/http_config_handler.go b/server/http_config_handler.go index 6495d50f7c..556f8d314a 100644 --- a/server/http_config_handler.go +++ b/server/http_config_handler.go @@ -11,6 +11,7 @@ import ( "github.com/evcc-io/evcc/api" "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" @@ -244,6 +245,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 { @@ -306,6 +310,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()) } if err != nil { @@ -367,6 +374,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()) } if err != nil { @@ -431,6 +441,9 @@ func testHandler(w http.ResponseWriter, r *http.Request) { case templates.Vehicle: instance, err = testDevice(id, class, req, vehicle.NewFromConfig, config.Vehicles()) + + case templates.Tariff: + instance, err = testDevice(id, class, req, tariff.NewFromConfig, config.Tariffs()) } if err != nil { 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)) From 0b2db7d3c8b3353da502e117cd984b0cd4748fba Mon Sep 17 00:00:00 2001 From: andig Date: Sat, 10 Feb 2024 17:15:51 +0100 Subject: [PATCH 2/7] wip --- cmd/setup.go | 21 +++-- core/site.go | 8 +- core/site_api.go | 34 +++----- server/http_config_device_handler.go | 4 +- server/http_config_tariffs_handler.go.1 | 89 ++++++++++++++++++++ tariff/api.go | 20 +++++ tariff/tariffs.go | 106 ++++++++++++++++++++++-- 7 files changed, 238 insertions(+), 44 deletions(-) create mode 100644 server/http_config_tariffs_handler.go.1 create mode 100644 tariff/api.go diff --git a/cmd/setup.go b/cmd/setup.go index 74a28f1a73..0329a53db5 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -592,7 +592,7 @@ func configureMessengers(conf messagingConfig, vehicles push.Vehicles, valueChan return messageChan, nil } -func configureTariff(name string, conf config.Typed, t *api.Tariff, wg *sync.WaitGroup) { +func configureTariff(name string, conf config.Typed, t *tariff.Tariffs, wg *sync.WaitGroup) { defer wg.Done() if conf.Type == "" { @@ -605,25 +605,24 @@ func configureTariff(name string, conf config.Typed, t *api.Tariff, wg *sync.Wai return } - *t = res + t.SetInstance(name, res) } func configureTariffs(conf tariffConfig) (*tariff.Tariffs, error) { - tariffs := tariff.Tariffs{ - Currency: currency.EUR, - } - + c := currency.EUR if conf.Currency != "" { - tariffs.Currency = currency.MustParseISO(conf.Currency) + c = currency.MustParseISO(conf.Currency) } + tariffs := tariff.NewTariffs(c) + var wg sync.WaitGroup wg.Add(4) - 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) + go configureTariff("grid", conf.Grid, tariffs, &wg) + go configureTariff("feedin", conf.FeedIn, tariffs, &wg) + go configureTariff("co2", conf.Co2, tariffs, &wg) + go configureTariff("planner", conf.Planner, tariffs, &wg) wg.Wait() diff --git a/core/site.go b/core/site.go index 2d38e87072..3d9a12cb34 100644 --- a/core/site.go +++ b/core/site.go @@ -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 tariff.API // Tariffs coordinator *coordinator.Coordinator // Vehicles prioritizer *prioritizer.Prioritizer // Power budgets stats *Stats // Stats @@ -140,7 +140,7 @@ func NewSiteFromConfig( }) } - tariff := site.GetTariff(PlannerTariff) + tariff := site.GetTariff(tariff.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(tariff.Planner); tariff != nil && tariff.Type() != api.TariffTypePriceStatic { rates, err := tariff.Rates() var rate api.Rate @@ -864,7 +864,7 @@ func (site *Site) prepare() { site.publish(keys.Currency, site.tariffs.Currency) site.publish(keys.SmartCostActive, false) site.publish(keys.SmartCostLimit, site.smartCostLimit) - if tariff := site.GetTariff(PlannerTariff); tariff != nil { + if tariff := site.GetTariff(tariff.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 b098e1e1ee..ae5a73fa71 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" "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) @@ -279,34 +274,31 @@ func (site *Site) SetSmartCostLimit(val float64) error { } // GetTariff returns the respective tariff if configured or nil -func (site *Site) GetTariff(tariff string) api.Tariff { +func (site *Site) GetTariff(tf string) api.Tariff { site.RLock() defer site.RUnlock() - switch tariff { - case GridTariff: - return site.tariffs.Grid - - case FeedinTariff: - return site.tariffs.FeedIn + switch tf { + case tariff.Grid, tariff.Feedin, tariff.Co2: + return site.tariffs.GetInstance(tf) - case PlannerTariff: + case tariff.Planner: switch { - case site.tariffs.Planner != nil: + case site.tariffs.GetInstance(tariff.Planner) != nil: // prio 0: manually set planner tariff - return site.tariffs.Planner + return site.tariffs.GetInstance(tariff.Planner) - case site.tariffs.Grid != nil && site.tariffs.Grid.Type() == api.TariffTypePriceForecast: + case site.tariffs.GetInstance(tariff.Grid) != nil && site.tariffs.GetInstance(tariff.Grid).Type() == api.TariffTypePriceForecast: // prio 1: grid tariff with forecast - return site.tariffs.Grid + return site.tariffs.GetInstance(tariff.Grid) - case site.tariffs.Co2 != nil: + case site.tariffs.GetInstance(tariff.Co2) != nil: // prio 2: co2 tariff - return site.tariffs.Co2 + return site.tariffs.GetInstance(tariff.Co2) default: // prio 3: static grid tariff - return site.tariffs.Grid + return site.tariffs.GetInstance(tariff.Grid) } default: diff --git a/server/http_config_device_handler.go b/server/http_config_device_handler.go index 14bd8eb229..9ab1694335 100644 --- a/server/http_config_device_handler.go +++ b/server/http_config_device_handler.go @@ -422,10 +422,10 @@ func testConfigHandler(w http.ResponseWriter, r *http.Request) { instance, err = testConfig(id, class, req, meter.NewFromConfig, config.Meters()) case templates.Vehicle: - instance, err = testDevice(id, class, req, vehicle.NewFromConfig, config.Vehicles()) + instance, err = testConfig(id, class, req, vehicle.NewFromConfig, config.Vehicles()) case templates.Tariff: - instance, err = testDevice(id, class, req, tariff.NewFromConfig, config.Tariffs()) + instance, err = testConfig(id, class, req, tariff.NewFromConfig, config.Tariffs()) } if err != nil { diff --git a/server/http_config_tariffs_handler.go.1 b/server/http_config_tariffs_handler.go.1 new file mode 100644 index 0000000000..98c0785590 --- /dev/null +++ b/server/http_config_tariffs_handler.go.1 @@ -0,0 +1,89 @@ +package server + +import ( + "encoding/json" + "net/http" + + "github.com/evcc-io/evcc/core/site" + "github.com/evcc-io/evcc/util/config" +) + +// tariffsHandler returns a device configurations by class +func tariffsHandler(site site.API) http.HandlerFunc { + tariffs := site.GetTariffs() + + return func(w http.ResponseWriter, r *http.Request) { + res := struct { + Grid string `json:"grid"` + FeedIn string `json:"feedin"` + Co2 string `json:"co2"` + Planner string `json:"planner"` + }{ + Grid: site.GetGridMeterRef(), + PV: site.GetPVMeterRefs(), + } + + jsonResult(w, res) + } +} + +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 +} + +// tariffsHandler returns a device configurations by class +func updateTariffsHandler(site site.API) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var payload struct { + Title *string + Grid *string + PV *[]string + Battery *[]string + } + + if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { + jsonError(w, http.StatusBadRequest, err) + return + } + + if payload.Title != nil { + site.SetTitle(*payload.Title) + } + + if payload.Grid != nil { + if *payload.Grid != "" && !validateRefs(w, []string{*payload.Grid}) { + return + } + + site.SetGridMeterRef(*payload.Grid) + setConfigDirty() + } + + if payload.PV != nil { + if !validateRefs(w, *payload.PV) { + return + } + + site.SetPVMeterRefs(*payload.PV) + setConfigDirty() + } + + if payload.Battery != nil { + if !validateRefs(w, *payload.Battery) { + return + } + + site.SetBatteryMeterRefs(*payload.Battery) + setConfigDirty() + } + + status := map[bool]int{false: http.StatusOK, true: http.StatusAccepted} + w.WriteHeader(status[ConfigDirty()]) + } +} diff --git a/tariff/api.go b/tariff/api.go new file mode 100644 index 0000000000..524e36b9a5 --- /dev/null +++ b/tariff/api.go @@ -0,0 +1,20 @@ +package tariff + +import ( + "github.com/evcc-io/evcc/api" + "golang.org/x/text/currency" +) + +type API interface { + Currency() currency.Unit + + 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.go b/tariff/tariffs.go index ffeb024cf2..3248c19e0f 100644 --- a/tariff/tariffs.go +++ b/tariff/tariffs.go @@ -1,15 +1,33 @@ package tariff import ( + "sync" "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/server/db/settings" "golang.org/x/text/currency" ) +const ( + Grid = "grid" + Feedin = "feedin" + Planner = "planner" + Co2 = "co2" +) + type Tariffs struct { - Currency currency.Unit - Grid, FeedIn, Co2, Planner api.Tariff + mu sync.RWMutex + currency currency.Unit + grid, feedin, co2, planner api.Tariff +} + +var _ API = (*Tariffs)(nil) + +func NewTariffs(currency currency.Unit) *Tariffs { + return &Tariffs{ + currency: currency, + } } func currentPrice(t api.Tariff) (float64, error) { @@ -23,20 +41,96 @@ func currentPrice(t api.Tariff) (float64, error) { return 0, api.ErrNotAvailable } +func getRef(ref string, t api.Tariff) string { + val, err := settings.String("tariffs." + ref) + if err == nil && val != "" { + return val + } + if t != nil { + return ref + } + return "" +} + +func (t *Tariffs) Currency() currency.Unit { + return t.currency +} + +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("tariffs."+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) + return currentPrice(t.grid) } // CurrentFeedInPrice returns the current feed-in price. func (t *Tariffs) CurrentFeedInPrice() (float64, error) { - return currentPrice(t.FeedIn) + return currentPrice(t.feedin) } // CurrentCo2 determines the grids co2 emission. func (t *Tariffs) CurrentCo2() (float64, error) { - if t.Co2 != nil { - return currentPrice(t.Co2) + if t.co2 != nil { + return currentPrice(t.co2) } return 0, api.ErrNotAvailable } From 92d805e8fd971e35ac41a14aec3e6a9cdfbc9b6a Mon Sep 17 00:00:00 2001 From: andig Date: Sat, 10 Feb 2024 17:53:54 +0100 Subject: [PATCH 3/7] Add tariffs site api --- cmd/setup.go | 2 +- core/site.go | 2 +- core/site/api.go | 3 +- core/site_api.go | 5 ++ server/http.go | 2 + server/http_config_helper.go | 12 +++ server/http_config_site_handler.go | 16 +--- server/http_config_tariffs_handler.go | 105 ++++++++++++++++++++++++ server/http_config_tariffs_handler.go.1 | 89 -------------------- tariff/api.go | 3 +- tariff/tariffs.go | 17 +++- 11 files changed, 147 insertions(+), 109 deletions(-) create mode 100644 server/http_config_tariffs_handler.go delete mode 100644 server/http_config_tariffs_handler.go.1 diff --git a/cmd/setup.go b/cmd/setup.go index 0329a53db5..4f587452aa 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -626,7 +626,7 @@ func configureTariffs(conf tariffConfig) (*tariff.Tariffs, error) { wg.Wait() - return &tariffs, nil + return tariffs, nil } func configureDevices(conf globalConfig) error { diff --git a/core/site.go b/core/site.go index 3d9a12cb34..39fc2cdcba 100644 --- a/core/site.go +++ b/core/site.go @@ -861,7 +861,7 @@ 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(tariff.Planner); tariff != nil { diff --git a/core/site/api.go b/core/site/api.go index 9098615227..dbf6d76b18 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" ) // API is the external site API @@ -45,7 +46,7 @@ type API interface { // tariffs and costs // - // GetTariff returns the respective tariff + GetTariffs() tariff.API GetTariff(string) api.Tariff GetSmartCostLimit() float64 SetSmartCostLimit(float64) error diff --git a/core/site_api.go b/core/site_api.go index ae5a73fa71..00ece93341 100644 --- a/core/site_api.go +++ b/core/site_api.go @@ -273,6 +273,11 @@ func (site *Site) SetSmartCostLimit(val float64) error { return nil } +// GetTariffs returns the tariffs api +func (site *Site) GetTariffs() tariff.API { + return site.tariffs +} + // GetTariff returns the respective tariff if configured or nil func (site *Site) GetTariff(tf string) api.Tariff { site.RLock() diff --git a/server/http.go b/server/http.go index b3e34b4ed8..05c27389fa 100644 --- a/server/http.go +++ b/server/http.go @@ -94,6 +94,8 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API, cache *util.Cache) { "deletedevice": {[]string{"DELETE", "OPTIONS"}, "/config/devices/{class:[a-z]+}/{id:[0-9.]+}", deleteDeviceHandler}, "testconfig": {[]string{"POST", "OPTIONS"}, "/config/test/{class:[a-z]+}", testConfigHandler}, "testmerged": {[]string{"POST", "OPTIONS"}, "/config/test/{class:[a-z]+}/merge/{id:[0-9.]+}", testConfigHandler}, + "tariffs": {[]string{"GET"}, "/config/tariffs", tariffsHandler(site)}, + "updatetariffs": {[]string{"PUT", "OPTIONS"}, "/config/tariffs", updateTariffsHandler(site)}, "buffersoc": {[]string{"POST", "OPTIONS"}, "/buffersoc/{value:[0-9.]+}", floatHandler(site.SetBufferSoc, site.GetBufferSoc)}, "bufferstartsoc": {[]string{"POST", "OPTIONS"}, "/bufferstartsoc/{value:[0-9.]+}", floatHandler(site.SetBufferStartSoc, site.GetBufferStartSoc)}, "batterydischargecontrol": {[]string{"POST", "OPTIONS"}, "/batterydischargecontrol/{value:[a-z]+}", boolHandler(site.SetBatteryDischargeControl, site.GetBatteryDischargeControl)}, 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..780de4c121 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) { @@ -57,7 +47,7 @@ func updateSiteHandler(site site.API) http.HandlerFunc { } if payload.Grid != nil { - if *payload.Grid != "" && !validateRefs(w, []string{*payload.Grid}) { + if *payload.Grid != "" && !validateRefs(w, config.Meters(), []string{*payload.Grid}) { return } @@ -66,7 +56,7 @@ func updateSiteHandler(site site.API) http.HandlerFunc { } if payload.PV != nil { - if !validateRefs(w, *payload.PV) { + if !validateRefs(w, config.Meters(), *payload.PV) { return } @@ -75,7 +65,7 @@ func updateSiteHandler(site site.API) http.HandlerFunc { } if payload.Battery != nil { - if !validateRefs(w, *payload.Battery) { + if !validateRefs(w, config.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..db1242d023 --- /dev/null +++ b/server/http_config_tariffs_handler.go @@ -0,0 +1,105 @@ +package server + +import ( + "encoding/json" + "net/http" + + "github.com/evcc-io/evcc/core/site" + "github.com/evcc-io/evcc/tariff" + "github.com/evcc-io/evcc/util/config" +) + +// tariffsHandler returns a device configurations by class +func tariffsHandler(site site.API) http.HandlerFunc { + tariffs := 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: tariffs.GetCurrency().String(), + Grid: tariffs.GetRef(tariff.Grid), + Feedin: tariffs.GetRef(tariff.Feedin), + Co2: tariffs.GetRef(tariff.Co2), + Planner: tariffs.GetRef(tariff.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 + } + + tariffs := site.GetTariffs() + + if payload.Currency != nil { + if err := tariffs.SetCurrency(*payload.Currency); err != nil { + jsonError(w, http.StatusBadRequest, err) + return + } + + setConfigDirty() + } + + if payload.Grid != nil { + ref := *payload.Grid + if ref != "" && !validateRefs(w, config.Tariffs(), []string{ref}) { + return + } + + tariffs.SetRef(tariff.Grid, ref) + setConfigDirty() + } + + if payload.Feedin != nil { + ref := *payload.Feedin + if ref != "" && !validateRefs(w, config.Tariffs(), []string{ref}) { + return + } + + tariffs.SetRef(tariff.Feedin, ref) + setConfigDirty() + } + + if payload.Co2 != nil { + ref := *payload.Co2 + if ref != "" && !validateRefs(w, config.Tariffs(), []string{ref}) { + return + } + + tariffs.SetRef(tariff.Co2, ref) + setConfigDirty() + } + + if payload.Planner != nil { + ref := *payload.Planner + if ref != "" && !validateRefs(w, config.Tariffs(), []string{ref}) { + return + } + + tariffs.SetRef(tariff.Planner, ref) + setConfigDirty() + } + + status := map[bool]int{false: http.StatusOK, true: http.StatusAccepted} + w.WriteHeader(status[ConfigDirty()]) + } +} diff --git a/server/http_config_tariffs_handler.go.1 b/server/http_config_tariffs_handler.go.1 deleted file mode 100644 index 98c0785590..0000000000 --- a/server/http_config_tariffs_handler.go.1 +++ /dev/null @@ -1,89 +0,0 @@ -package server - -import ( - "encoding/json" - "net/http" - - "github.com/evcc-io/evcc/core/site" - "github.com/evcc-io/evcc/util/config" -) - -// tariffsHandler returns a device configurations by class -func tariffsHandler(site site.API) http.HandlerFunc { - tariffs := site.GetTariffs() - - return func(w http.ResponseWriter, r *http.Request) { - res := struct { - Grid string `json:"grid"` - FeedIn string `json:"feedin"` - Co2 string `json:"co2"` - Planner string `json:"planner"` - }{ - Grid: site.GetGridMeterRef(), - PV: site.GetPVMeterRefs(), - } - - jsonResult(w, res) - } -} - -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 -} - -// tariffsHandler returns a device configurations by class -func updateTariffsHandler(site site.API) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var payload struct { - Title *string - Grid *string - PV *[]string - Battery *[]string - } - - if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { - jsonError(w, http.StatusBadRequest, err) - return - } - - if payload.Title != nil { - site.SetTitle(*payload.Title) - } - - if payload.Grid != nil { - if *payload.Grid != "" && !validateRefs(w, []string{*payload.Grid}) { - return - } - - site.SetGridMeterRef(*payload.Grid) - setConfigDirty() - } - - if payload.PV != nil { - if !validateRefs(w, *payload.PV) { - return - } - - site.SetPVMeterRefs(*payload.PV) - setConfigDirty() - } - - if payload.Battery != nil { - if !validateRefs(w, *payload.Battery) { - return - } - - site.SetBatteryMeterRefs(*payload.Battery) - setConfigDirty() - } - - status := map[bool]int{false: http.StatusOK, true: http.StatusAccepted} - w.WriteHeader(status[ConfigDirty()]) - } -} diff --git a/tariff/api.go b/tariff/api.go index 524e36b9a5..8f2c48c9ec 100644 --- a/tariff/api.go +++ b/tariff/api.go @@ -6,7 +6,8 @@ import ( ) type API interface { - Currency() currency.Unit + GetCurrency() currency.Unit + SetCurrency(string) error GetRef(ref string) string SetRef(ref, value string) diff --git a/tariff/tariffs.go b/tariff/tariffs.go index 3248c19e0f..edbdfa1770 100644 --- a/tariff/tariffs.go +++ b/tariff/tariffs.go @@ -10,6 +10,8 @@ import ( ) const ( + settingsPrefix = "tariffs." + Grid = "grid" Feedin = "feedin" Planner = "planner" @@ -42,7 +44,7 @@ func currentPrice(t api.Tariff) (float64, error) { } func getRef(ref string, t api.Tariff) string { - val, err := settings.String("tariffs." + ref) + val, err := settings.String(settingsPrefix + ref) if err == nil && val != "" { return val } @@ -52,10 +54,19 @@ func getRef(ref string, t api.Tariff) string { return "" } -func (t *Tariffs) Currency() currency.Unit { +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(settingsPrefix+"currency", s) + } + return err +} + func (t *Tariffs) GetRef(ref string) string { t.mu.RLock() defer t.mu.RUnlock() @@ -78,7 +89,7 @@ func (t *Tariffs) SetRef(ref, value string) { t.mu.Lock() defer t.mu.Unlock() - settings.SetString("tariffs."+ref, value) + settings.SetString(settingsPrefix+ref, value) } func (t *Tariffs) GetInstance(ref string) api.Tariff { From 0eed494ad2c7101cfca739a98abfc134c274e60f Mon Sep 17 00:00:00 2001 From: andig Date: Sat, 10 Feb 2024 17:59:04 +0100 Subject: [PATCH 4/7] wip --- server/http.go | 4 ++-- server/http_config_tariffs_handler.go | 31 +++++++++++++++++++++++++++ server/http_site_handler.go | 30 -------------------------- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/server/http.go b/server/http.go index 05c27389fa..0df867a048 100644 --- a/server/http.go +++ b/server/http.go @@ -94,8 +94,6 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API, cache *util.Cache) { "deletedevice": {[]string{"DELETE", "OPTIONS"}, "/config/devices/{class:[a-z]+}/{id:[0-9.]+}", deleteDeviceHandler}, "testconfig": {[]string{"POST", "OPTIONS"}, "/config/test/{class:[a-z]+}", testConfigHandler}, "testmerged": {[]string{"POST", "OPTIONS"}, "/config/test/{class:[a-z]+}/merge/{id:[0-9.]+}", testConfigHandler}, - "tariffs": {[]string{"GET"}, "/config/tariffs", tariffsHandler(site)}, - "updatetariffs": {[]string{"PUT", "OPTIONS"}, "/config/tariffs", updateTariffsHandler(site)}, "buffersoc": {[]string{"POST", "OPTIONS"}, "/buffersoc/{value:[0-9.]+}", floatHandler(site.SetBufferSoc, site.GetBufferSoc)}, "bufferstartsoc": {[]string{"POST", "OPTIONS"}, "/bufferstartsoc/{value:[0-9.]+}", floatHandler(site.SetBufferStartSoc, site.GetBufferStartSoc)}, "batterydischargecontrol": {[]string{"POST", "OPTIONS"}, "/batterydischargecontrol/{value:[a-z]+}", boolHandler(site.SetBatteryDischargeControl, site.GetBatteryDischargeControl)}, @@ -103,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_tariffs_handler.go b/server/http_config_tariffs_handler.go index db1242d023..528a3b6de8 100644 --- a/server/http_config_tariffs_handler.go +++ b/server/http_config_tariffs_handler.go @@ -2,13 +2,44 @@ 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" "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 { tariffs := site.GetTariffs() 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) { From 08d509cda45b7ef600231ca0eb82371fc5021509 Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 11 Feb 2024 11:40:50 +0100 Subject: [PATCH 5/7] Move tariffs to separate package --- cmd/setup.go | 21 +++++----- cmd/tariff.go | 9 +++-- core/site.go | 14 +++---- core/site/api.go | 4 +- core/site_api.go | 28 ++++++------- server/http_config_site_handler.go | 8 ++-- server/http_config_tariffs_handler.go | 57 +++++++++++---------------- tariff/{ => tariffs}/api.go | 2 +- tariff/{ => tariffs}/tariffs.go | 14 ++++--- 9 files changed, 75 insertions(+), 82 deletions(-) rename tariff/{ => tariffs}/api.go (96%) rename tariff/{ => tariffs}/tariffs.go (89%) diff --git a/cmd/setup.go b/cmd/setup.go index 4f587452aa..838cded512 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,7 +593,7 @@ func configureMessengers(conf messagingConfig, vehicles push.Vehicles, valueChan return messageChan, nil } -func configureTariff(name string, conf config.Typed, t *tariff.Tariffs, wg *sync.WaitGroup) { +func configureTariff(name string, conf config.Typed, t *tariffs.Tariffs, wg *sync.WaitGroup) { defer wg.Done() if conf.Type == "" { @@ -608,25 +609,25 @@ func configureTariff(name string, conf config.Typed, t *tariff.Tariffs, wg *sync t.SetInstance(name, res) } -func configureTariffs(conf tariffConfig) (*tariff.Tariffs, error) { +func configureTariffs(conf tariffConfig) (*tariffs.Tariffs, error) { c := currency.EUR if conf.Currency != "" { c = currency.MustParseISO(conf.Currency) } - tariffs := tariff.NewTariffs(c) + res := tariffs.New(c) var wg sync.WaitGroup wg.Add(4) - go configureTariff("grid", conf.Grid, tariffs, &wg) - go configureTariff("feedin", conf.FeedIn, tariffs, &wg) - go configureTariff("co2", conf.Co2, tariffs, &wg) - go configureTariff("planner", conf.Planner, tariffs, &wg) + go configureTariff(tariffs.Grid, conf.Grid, res, &wg) + go configureTariff(tariffs.Feedin, conf.Feedin, res, &wg) + go configureTariff(tariffs.Co2, conf.Co2, res, &wg) + go configureTariff(tariffs.Planner, conf.Planner, res, &wg) wg.Wait() - return tariffs, nil + return res, nil } func configureDevices(conf globalConfig) error { @@ -657,7 +658,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 39fc2cdcba..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.API // 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(tariff.Planner) + 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(tariff.Planner); 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 @@ -864,7 +864,7 @@ func (site *Site) prepare() { site.publish(keys.Currency, site.tariffs.GetCurrency()) site.publish(keys.SmartCostActive, false) site.publish(keys.SmartCostLimit, site.smartCostLimit) - if tariff := site.GetTariff(tariff.Planner); 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 dbf6d76b18..7fc02c86ee 100644 --- a/core/site/api.go +++ b/core/site/api.go @@ -3,7 +3,7 @@ package site import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/core/loadpoint" - "github.com/evcc-io/evcc/tariff" + "github.com/evcc-io/evcc/tariff/tariffs" ) // API is the external site API @@ -46,7 +46,7 @@ type API interface { // tariffs and costs // - GetTariffs() tariff.API + 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 00ece93341..f389e0dbd0 100644 --- a/core/site_api.go +++ b/core/site_api.go @@ -9,7 +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" + "github.com/evcc-io/evcc/tariff/tariffs" "github.com/evcc-io/evcc/util/config" ) @@ -274,36 +274,36 @@ func (site *Site) SetSmartCostLimit(val float64) error { } // GetTariffs returns the tariffs api -func (site *Site) GetTariffs() tariff.API { +func (site *Site) GetTariffs() tariffs.API { return site.tariffs } // GetTariff returns the respective tariff if configured or nil -func (site *Site) GetTariff(tf string) api.Tariff { +func (site *Site) GetTariff(ref string) api.Tariff { site.RLock() defer site.RUnlock() - switch tf { - case tariff.Grid, tariff.Feedin, tariff.Co2: - return site.tariffs.GetInstance(tf) + switch ref { + case tariffs.Grid, tariffs.Feedin, tariffs.Co2: + return site.tariffs.GetInstance(ref) - case tariff.Planner: + case tariffs.Planner: switch { - case site.tariffs.GetInstance(tariff.Planner) != nil: + case site.tariffs.GetInstance(tariffs.Planner) != nil: // prio 0: manually set planner tariff - return site.tariffs.GetInstance(tariff.Planner) + return site.tariffs.GetInstance(tariffs.Planner) - case site.tariffs.GetInstance(tariff.Grid) != nil && site.tariffs.GetInstance(tariff.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.GetInstance(tariff.Grid) + return site.tariffs.GetInstance(tariffs.Grid) - case site.tariffs.GetInstance(tariff.Co2) != nil: + case site.tariffs.GetInstance(tariffs.Co2) != nil: // prio 2: co2 tariff - return site.tariffs.GetInstance(tariff.Co2) + return site.tariffs.GetInstance(tariffs.Co2) default: // prio 3: static grid tariff - return site.tariffs.GetInstance(tariff.Grid) + return site.tariffs.GetInstance(tariffs.Grid) } default: diff --git a/server/http_config_site_handler.go b/server/http_config_site_handler.go index 780de4c121..3fc93a55f1 100644 --- a/server/http_config_site_handler.go +++ b/server/http_config_site_handler.go @@ -42,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, config.Meters(), []string{*payload.Grid}) { + if *payload.Grid != "" && !validateRefs(w, meters, []string{*payload.Grid}) { return } @@ -56,7 +58,7 @@ func updateSiteHandler(site site.API) http.HandlerFunc { } if payload.PV != nil { - if !validateRefs(w, config.Meters(), *payload.PV) { + if !validateRefs(w, meters, *payload.PV) { return } @@ -65,7 +67,7 @@ func updateSiteHandler(site site.API) http.HandlerFunc { } if payload.Battery != nil { - if !validateRefs(w, config.Meters(), *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 index 528a3b6de8..c98a8dd8f5 100644 --- a/server/http_config_tariffs_handler.go +++ b/server/http_config_tariffs_handler.go @@ -7,7 +7,6 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/core/site" - "github.com/evcc-io/evcc/tariff" "github.com/evcc-io/evcc/util/config" "github.com/gorilla/mux" ) @@ -53,10 +52,10 @@ func tariffsHandler(site site.API) http.HandlerFunc { Planner string `json:"planner,omitempty"` }{ Currency: tariffs.GetCurrency().String(), - Grid: tariffs.GetRef(tariff.Grid), - Feedin: tariffs.GetRef(tariff.Feedin), - Co2: tariffs.GetRef(tariff.Co2), - Planner: tariffs.GetRef(tariff.Planner), + Grid: tariffs.GetRef(tariffs.Grid), + Feedin: tariffs.GetRef(tariffs.Feedin), + Co2: tariffs.GetRef(tariffs.Co2), + Planner: tariffs.GetRef(tariffs.Planner), } jsonResult(w, res) @@ -90,44 +89,32 @@ func updateTariffsHandler(site site.API) http.HandlerFunc { setConfigDirty() } - if payload.Grid != nil { - ref := *payload.Grid - if ref != "" && !validateRefs(w, config.Tariffs(), []string{ref}) { - return - } - - tariffs.SetRef(tariff.Grid, ref) - setConfigDirty() - } + update := func(tariff string, ref *string) bool { + if ref != nil { + if *ref != "" && !validateRefs(w, config.Tariffs(), []string{*ref}) { + return false + } - if payload.Feedin != nil { - ref := *payload.Feedin - if ref != "" && !validateRefs(w, config.Tariffs(), []string{ref}) { - return + tariffs.SetRef(tariff, *ref) + setConfigDirty() } - - tariffs.SetRef(tariff.Feedin, ref) - setConfigDirty() + return true } - if payload.Co2 != nil { - ref := *payload.Co2 - if ref != "" && !validateRefs(w, config.Tariffs(), []string{ref}) { - return - } + if !update(tariffs.Grid, payload.Grid) { + return + } - tariffs.SetRef(tariff.Co2, ref) - setConfigDirty() + if !update(tariffs.Feedin, payload.Feedin) { + return } - if payload.Planner != nil { - ref := *payload.Planner - if ref != "" && !validateRefs(w, config.Tariffs(), []string{ref}) { - return - } + if !update(tariffs.Co2, payload.Co2) { + return + } - tariffs.SetRef(tariff.Planner, ref) - setConfigDirty() + if !update(tariffs.Planner, payload.Planner) { + return } status := map[bool]int{false: http.StatusOK, true: http.StatusAccepted} diff --git a/tariff/api.go b/tariff/tariffs/api.go similarity index 96% rename from tariff/api.go rename to tariff/tariffs/api.go index 8f2c48c9ec..3fb94bc989 100644 --- a/tariff/api.go +++ b/tariff/tariffs/api.go @@ -1,4 +1,4 @@ -package tariff +package tariffs import ( "github.com/evcc-io/evcc/api" diff --git a/tariff/tariffs.go b/tariff/tariffs/tariffs.go similarity index 89% rename from tariff/tariffs.go rename to tariff/tariffs/tariffs.go index edbdfa1770..23d8dd07bc 100644 --- a/tariff/tariffs.go +++ b/tariff/tariffs/tariffs.go @@ -1,4 +1,4 @@ -package tariff +package tariffs import ( "sync" @@ -10,7 +10,7 @@ import ( ) const ( - settingsPrefix = "tariffs." + SettingsKey = "tariffs." Grid = "grid" Feedin = "feedin" @@ -18,6 +18,7 @@ const ( Co2 = "co2" ) +// Tariffs is the tariffs container type Tariffs struct { mu sync.RWMutex currency currency.Unit @@ -26,7 +27,8 @@ type Tariffs struct { var _ API = (*Tariffs)(nil) -func NewTariffs(currency currency.Unit) *Tariffs { +// New creates a new tariffs container +func New(currency currency.Unit) *Tariffs { return &Tariffs{ currency: currency, } @@ -44,7 +46,7 @@ func currentPrice(t api.Tariff) (float64, error) { } func getRef(ref string, t api.Tariff) string { - val, err := settings.String(settingsPrefix + ref) + val, err := settings.String(SettingsKey + ref) if err == nil && val != "" { return val } @@ -62,7 +64,7 @@ func (t *Tariffs) SetCurrency(s string) error { c, err := currency.ParseISO(s) if err == nil { t.currency = c - settings.SetString(settingsPrefix+"currency", s) + settings.SetString(SettingsKey+"currency", s) } return err } @@ -89,7 +91,7 @@ func (t *Tariffs) SetRef(ref, value string) { t.mu.Lock() defer t.mu.Unlock() - settings.SetString(settingsPrefix+ref, value) + settings.SetString(SettingsKey+ref, value) } func (t *Tariffs) GetInstance(ref string) api.Tariff { From 19a6099226e27e47e79a0afc674f53dbf078b33e Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 11 Feb 2024 12:01:54 +0100 Subject: [PATCH 6/7] Get tariffs from database --- cmd/setup.go | 53 +++++++++++++++++++-------- server/http_config_tariffs_handler.go | 19 +++++----- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/cmd/setup.go b/cmd/setup.go index 838cded512..3a55e14a8f 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -593,20 +593,46 @@ func configureMessengers(conf messagingConfig, vehicles push.Vehicles, valueChan return messageChan, nil } -func configureTariff(name string, conf config.Typed, t *tariffs.Tariffs, 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.SetInstance(name, res) + + return nil } func configureTariffs(conf tariffConfig) (*tariffs.Tariffs, error) { @@ -617,17 +643,14 @@ func configureTariffs(conf tariffConfig) (*tariffs.Tariffs, error) { res := tariffs.New(c) - var wg sync.WaitGroup - wg.Add(4) - - go configureTariff(tariffs.Grid, conf.Grid, res, &wg) - go configureTariff(tariffs.Feedin, conf.Feedin, res, &wg) - go configureTariff(tariffs.Co2, conf.Co2, res, &wg) - go configureTariff(tariffs.Planner, conf.Planner, res, &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 res, nil + return res, err } func configureDevices(conf globalConfig) error { diff --git a/server/http_config_tariffs_handler.go b/server/http_config_tariffs_handler.go index c98a8dd8f5..482036fd07 100644 --- a/server/http_config_tariffs_handler.go +++ b/server/http_config_tariffs_handler.go @@ -7,6 +7,7 @@ import ( "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" ) @@ -41,7 +42,7 @@ func tariffHandler(site site.API) http.HandlerFunc { // tariffsHandler returns a device configurations by class func tariffsHandler(site site.API) http.HandlerFunc { - tariffs := site.GetTariffs() + tfs := site.GetTariffs() return func(w http.ResponseWriter, r *http.Request) { res := struct { @@ -51,11 +52,11 @@ func tariffsHandler(site site.API) http.HandlerFunc { Co2 string `json:"co2,omitempty"` Planner string `json:"planner,omitempty"` }{ - Currency: tariffs.GetCurrency().String(), - Grid: tariffs.GetRef(tariffs.Grid), - Feedin: tariffs.GetRef(tariffs.Feedin), - Co2: tariffs.GetRef(tariffs.Co2), - Planner: tariffs.GetRef(tariffs.Planner), + 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) @@ -78,10 +79,10 @@ func updateTariffsHandler(site site.API) http.HandlerFunc { return } - tariffs := site.GetTariffs() + tfs := site.GetTariffs() if payload.Currency != nil { - if err := tariffs.SetCurrency(*payload.Currency); err != nil { + if err := tfs.SetCurrency(*payload.Currency); err != nil { jsonError(w, http.StatusBadRequest, err) return } @@ -95,7 +96,7 @@ func updateTariffsHandler(site site.API) http.HandlerFunc { return false } - tariffs.SetRef(tariff, *ref) + tfs.SetRef(tariff, *ref) setConfigDirty() } return true From fb3f115d70a84b5423427be9d41cb5f26f3e982d Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 11 Feb 2024 12:06:32 +0100 Subject: [PATCH 7/7] wip --- tariff/tariffs/tariffs.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tariff/tariffs/tariffs.go b/tariff/tariffs/tariffs.go index 23d8dd07bc..3089a75fa3 100644 --- a/tariff/tariffs/tariffs.go +++ b/tariff/tariffs/tariffs.go @@ -45,17 +45,6 @@ func currentPrice(t api.Tariff) (float64, error) { return 0, api.ErrNotAvailable } -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) GetCurrency() currency.Unit { return t.currency } @@ -69,6 +58,17 @@ func (t *Tariffs) SetCurrency(s string) error { 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()