Skip to content

Commit

Permalink
Add solar forecast using Solcast and Forecast.Solar (#18269)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig authored Feb 9, 2025
1 parent 7e8e182 commit 01faac1
Show file tree
Hide file tree
Showing 18 changed files with 415 additions and 90 deletions.
1 change: 1 addition & 0 deletions api/globalconfig/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ type Tariffs struct {
FeedIn config.Typed
Co2 config.Typed
Planner config.Typed
Solar config.Typed
}

type Network struct {
Expand Down
13 changes: 13 additions & 0 deletions api/tariff.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package api

//go:generate enumer -type TariffType -trimprefix TariffType -transform=lower -text
//go:generate enumer -type TariffUsage -trimprefix TariffUsage -transform=lower

type TariffType int

Expand All @@ -10,4 +11,16 @@ const (
TariffTypePriceDynamic
TariffTypePriceForecast
TariffTypeCo2
TariffTypeSolar
)

type TariffUsage int

const (
_ TariffUsage = iota
TariffUsageCo2
TariffUsageFeedin
TariffUsageGrid
TariffUsagePlanner
TariffUsageSolar
)
12 changes: 8 additions & 4 deletions api/tarifftype_enumer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

91 changes: 91 additions & 0 deletions api/tariffusage_enumer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 7 additions & 5 deletions cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -755,11 +755,12 @@ func tariffInstance(name string, conf config.Typed) (api.Tariff, error) {
return instance, nil
}

func configureTariff(name string, conf config.Typed, t *api.Tariff) error {
func configureTariff(u api.TariffUsage, conf config.Typed, t *api.Tariff) error {
if conf.Type == "" {
return nil
}

name := u.String()
res, err := tariffInstance(name, conf)
if err != nil {
return &DeviceError{name, err}
Expand All @@ -786,10 +787,11 @@ func configureTariffs(conf globalconfig.Tariffs) (*tariff.Tariffs, error) {
}

var eg errgroup.Group
eg.Go(func() error { return configureTariff("grid", conf.Grid, &tariffs.Grid) })
eg.Go(func() error { return configureTariff("feedin", conf.FeedIn, &tariffs.FeedIn) })
eg.Go(func() error { return configureTariff("co2", conf.Co2, &tariffs.Co2) })
eg.Go(func() error { return configureTariff("planner", conf.Planner, &tariffs.Planner) })
eg.Go(func() error { return configureTariff(api.TariffUsageGrid, conf.Grid, &tariffs.Grid) })
eg.Go(func() error { return configureTariff(api.TariffUsageFeedin, conf.FeedIn, &tariffs.FeedIn) })
eg.Go(func() error { return configureTariff(api.TariffUsageCo2, conf.Co2, &tariffs.Co2) })
eg.Go(func() error { return configureTariff(api.TariffUsagePlanner, conf.Planner, &tariffs.Planner) })
eg.Go(func() error { return configureTariff(api.TariffUsageSolar, conf.Solar, &tariffs.Solar) })

if err := eg.Wait(); err != nil {
return nil, &ClassError{ClassTariff, err}
Expand Down
26 changes: 20 additions & 6 deletions cmd/tariff.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@ func runTariff(cmd *cobra.Command, args []string) {
name = args[0]
}

for key, tf := range map[string]api.Tariff{
"grid": tariffs.Grid,
"feedin": tariffs.FeedIn,
"co2": tariffs.Co2,
"planner": tariffs.Planner,
for u, tf := range map[api.TariffUsage]api.Tariff{
api.TariffUsageGrid: tariffs.Grid,
api.TariffUsageFeedin: tariffs.FeedIn,
api.TariffUsageCo2: tariffs.Co2,
api.TariffUsagePlanner: tariffs.Planner,
api.TariffUsageSolar: tariffs.Solar,
} {
key := u.String()
if name != "" && key != name {
continue
}
Expand All @@ -65,8 +67,20 @@ func runTariff(cmd *cobra.Command, args []string) {
fatal(err)
}

unit := "Price/Cost"
switch tf.Type() {
case api.TariffTypeCo2:
unit += "Footprint (gCO2/kWh)"
case api.TariffTypeSolar:
unit = "Yield (W)"
default:
if c := conf.Tariffs.Currency; c != "" {
unit += fmt.Sprintf(" (%s/kWh)", c)
}
}

tw := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
fmt.Fprintln(tw, "From\tTo\tPrice/Cost")
fmt.Fprintln(tw, "From\tTo\t"+unit)
const format = "2006-01-02 15:04:05"

for _, r := range rates {
Expand Down
1 change: 1 addition & 0 deletions core/keys/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const (
TariffGrid = "tariffGrid"
TariffPriceHome = "tariffPriceHome"
TariffPriceLoadpoints = "tariffPriceLoadpoints"
TariffSolar = "tariffSolar"
Vehicles = "vehicles"

// meters
Expand Down
41 changes: 22 additions & 19 deletions core/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func (site *Site) Boot(log *util.Logger, loadpoints []*Loadpoint, tariffs *tarif
})
}

tariff := site.GetTariff(PlannerTariff)
tariff := site.GetTariff(api.TariffUsagePlanner)

// give loadpoints access to vehicles and database
for _, lp := range loadpoints {
Expand Down Expand Up @@ -757,8 +757,8 @@ func (site *Site) greenShare(powerFrom float64, powerTo float64) float64 {

// effectivePrice calculates the real energy price based on self-produced and grid-imported energy.
func (site *Site) effectivePrice(greenShare float64) *float64 {
if grid, err := site.tariffs.CurrentGridPrice(); err == nil {
feedin, err := site.tariffs.CurrentFeedInPrice()
if grid, err := tariff.CurrentPrice(site.GetTariff(api.TariffUsageGrid)); err == nil {
feedin, err := tariff.CurrentPrice(site.GetTariff(api.TariffUsageFeedin))
if err != nil {
feedin = 0
}
Expand All @@ -770,7 +770,7 @@ func (site *Site) effectivePrice(greenShare float64) *float64 {

// effectiveCo2 calculates the amount of emitted co2 based on self-produced and grid-imported energy.
func (site *Site) effectiveCo2(greenShare float64) *float64 {
if co2, err := site.tariffs.CurrentCo2(); err == nil {
if co2, err := tariff.CurrentPrice(site.GetTariff(api.TariffUsageCo2)); err == nil {
effCo2 := co2 * (1 - greenShare)
return &effCo2
}
Expand All @@ -781,26 +781,29 @@ func (site *Site) publishTariffs(greenShareHome float64, greenShareLoadpoints fl
site.publish(keys.GreenShareHome, greenShareHome)
site.publish(keys.GreenShareLoadpoints, greenShareLoadpoints)

if gridPrice, err := site.tariffs.CurrentGridPrice(); err == nil {
site.publish(keys.TariffGrid, gridPrice)
if v, err := tariff.CurrentPrice(site.GetTariff(api.TariffUsageGrid)); err == nil {
site.publish(keys.TariffGrid, v)
}
if feedInPrice, err := site.tariffs.CurrentFeedInPrice(); err == nil {
site.publish(keys.TariffFeedIn, feedInPrice)
if v, err := tariff.CurrentPrice(site.GetTariff(api.TariffUsageFeedin)); err == nil {
site.publish(keys.TariffFeedIn, v)
}
if co2, err := site.tariffs.CurrentCo2(); err == nil {
site.publish(keys.TariffCo2, co2)
if v, err := tariff.CurrentPrice(site.GetTariff(api.TariffUsageCo2)); err == nil {
site.publish(keys.TariffCo2, v)
}
if price := site.effectivePrice(greenShareHome); price != nil {
site.publish(keys.TariffPriceHome, price)
if v, err := tariff.CurrentPrice(site.GetTariff(api.TariffUsageSolar)); err == nil {
site.publish(keys.TariffSolar, v)
}
if co2 := site.effectiveCo2(greenShareHome); co2 != nil {
site.publish(keys.TariffCo2Home, co2)
if v := site.effectivePrice(greenShareHome); v != nil {
site.publish(keys.TariffPriceHome, v)
}
if price := site.effectivePrice(greenShareLoadpoints); price != nil {
site.publish(keys.TariffPriceLoadpoints, price)
if v := site.effectiveCo2(greenShareHome); v != nil {
site.publish(keys.TariffCo2Home, v)
}
if co2 := site.effectiveCo2(greenShareLoadpoints); co2 != nil {
site.publish(keys.TariffCo2Loadpoints, co2)
if v := site.effectivePrice(greenShareLoadpoints); v != nil {
site.publish(keys.TariffPriceLoadpoints, v)
}
if v := site.effectiveCo2(greenShareLoadpoints); v != nil {
site.publish(keys.TariffCo2Loadpoints, v)
}
}

Expand Down Expand Up @@ -926,7 +929,7 @@ func (site *Site) prepare() {
site.publish(keys.ResidualPower, site.GetResidualPower())

site.publish(keys.Currency, site.tariffs.Currency)
if tariff := site.GetTariff(PlannerTariff); tariff != nil {
if tariff := site.GetTariff(api.TariffUsagePlanner); tariff != nil {
site.publish(keys.SmartCostType, tariff.Type())
} else {
site.publish(keys.SmartCostType, nil)
Expand Down
2 changes: 1 addition & 1 deletion core/site/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ type API interface {
//

// GetTariff returns the respective tariff
GetTariff(string) api.Tariff
GetTariff(api.TariffUsage) api.Tariff

//
// battery control
Expand Down
39 changes: 2 additions & 37 deletions core/site_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,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)
Expand Down Expand Up @@ -272,39 +266,10 @@ func (site *Site) SetResidualPower(power float64) error {
}

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

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

case FeedinTariff:
return site.tariffs.FeedIn

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

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

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

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

default:
return nil
}
return site.tariffs.Get(tariff)
}

// GetBatteryDischargeControl returns the battery control mode (no discharge only)
Expand Down
Loading

0 comments on commit 01faac1

Please sign in to comment.