Skip to content

Commit

Permalink
Tariffs: publish forecast (#18692)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig authored Feb 9, 2025
1 parent 6e1422c commit 8f05f13
Show file tree
Hide file tree
Showing 10 changed files with 64 additions and 18 deletions.
6 changes: 6 additions & 0 deletions api/rates.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package api

import (
"encoding/json"
"fmt"
"slices"
"time"
Expand Down Expand Up @@ -42,3 +43,8 @@ func (r Rates) Current(now time.Time) (Rate, error) {
return Rate{}, fmt.Errorf("no matching rate for: %s, %d rates (%s to %s)",
now.Local().Format(time.RFC3339), len(r), r[0].Start.Local().Format(time.RFC3339), r[len(r)-1].End.Local().Format(time.RFC3339))
}

// MarshalMQTT implements server.MQTTMarshaler
func (r Rates) MarshalMQTT() ([]byte, error) {
return json.Marshal(r)
}
2 changes: 1 addition & 1 deletion api/tariff.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type TariffUsage int
const (
_ TariffUsage = iota
TariffUsageCo2
TariffUsageFeedin
TariffUsageFeedIn
TariffUsageGrid
TariffUsagePlanner
TariffUsageSolar
Expand Down
8 changes: 4 additions & 4 deletions api/tariffusage_enumer.go

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

4 changes: 2 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,10 @@ func runRoot(cmd *cobra.Command, args []string) {
// eliminate duplicate values
dedupe := pipe.NewDeduplicator(30*time.Minute,
keys.VehicleSoc, keys.VehicleRange, keys.VehicleOdometer,
keys.TariffGrid, keys.TariffFeedIn, keys.TariffCo2,
keys.TariffCo2, keys.TariffFeedIn, keys.TariffGrid,
keys.ChargedEnergy, keys.ChargeRemainingEnergy)
go influx.Run(site, dedupe.Pipe(
pipe.NewDropper(append(ignoreLogs, ignoreEmpty)...).Pipe(tee.Attach()),
pipe.NewDropper(append(ignoreLogs, ignoreEmpty, keys.Forecast)...).Pipe(tee.Attach()),
))
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,7 @@ func configureTariffs(conf globalconfig.Tariffs) (*tariff.Tariffs, error) {

var eg errgroup.Group
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.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) })
Expand Down
2 changes: 1 addition & 1 deletion cmd/tariff.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func runTariff(cmd *cobra.Command, args []string) {

for u, tf := range map[api.TariffUsage]api.Tariff{
api.TariffUsageGrid: tariffs.Grid,
api.TariffUsageFeedin: tariffs.FeedIn,
api.TariffUsageFeedIn: tariffs.FeedIn,
api.TariffUsageCo2: tariffs.Co2,
api.TariffUsagePlanner: tariffs.Planner,
api.TariffUsageSolar: tariffs.Solar,
Expand Down
1 change: 1 addition & 0 deletions core/keys/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
SiteTitle = "siteTitle"
SmartCostType = "smartCostType"
Statistics = "statistics"
Forecast = "forecast"
TariffCo2 = "tariffCo2"
TariffCo2Home = "tariffCo2Home"
TariffCo2Loadpoints = "tariffCo2Loadpoints"
Expand Down
27 changes: 20 additions & 7 deletions core/site.go
Original file line number Diff line number Diff line change
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 := tariff.CurrentPrice(site.GetTariff(api.TariffUsageGrid)); err == nil {
feedin, err := tariff.CurrentPrice(site.GetTariff(api.TariffUsageFeedin))
if grid, err := tariff.Now(site.GetTariff(api.TariffUsageGrid)); err == nil {
feedin, err := tariff.Now(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 := tariff.CurrentPrice(site.GetTariff(api.TariffUsageCo2)); err == nil {
if co2, err := tariff.Now(site.GetTariff(api.TariffUsageCo2)); err == nil {
effCo2 := co2 * (1 - greenShare)
return &effCo2
}
Expand All @@ -781,16 +781,16 @@ func (site *Site) publishTariffs(greenShareHome float64, greenShareLoadpoints fl
site.publish(keys.GreenShareHome, greenShareHome)
site.publish(keys.GreenShareLoadpoints, greenShareLoadpoints)

if v, err := tariff.CurrentPrice(site.GetTariff(api.TariffUsageGrid)); err == nil {
if v, err := tariff.Now(site.GetTariff(api.TariffUsageGrid)); err == nil {
site.publish(keys.TariffGrid, v)
}
if v, err := tariff.CurrentPrice(site.GetTariff(api.TariffUsageFeedin)); err == nil {
if v, err := tariff.Now(site.GetTariff(api.TariffUsageFeedIn)); err == nil {
site.publish(keys.TariffFeedIn, v)
}
if v, err := tariff.CurrentPrice(site.GetTariff(api.TariffUsageCo2)); err == nil {
if v, err := tariff.Now(site.GetTariff(api.TariffUsageCo2)); err == nil {
site.publish(keys.TariffCo2, v)
}
if v, err := tariff.CurrentPrice(site.GetTariff(api.TariffUsageSolar)); err == nil {
if v, err := tariff.Now(site.GetTariff(api.TariffUsageSolar)); err == nil {
site.publish(keys.TariffSolar, v)
}
if v := site.effectivePrice(greenShareHome); v != nil {
Expand All @@ -805,6 +805,19 @@ func (site *Site) publishTariffs(greenShareHome float64, greenShareLoadpoints fl
if v := site.effectiveCo2(greenShareLoadpoints); v != nil {
site.publish(keys.TariffCo2Loadpoints, v)
}

// forecast
site.publish(keys.Forecast, struct {
Co2 api.Rates `json:"co2,omitempty"`
FeedIn api.Rates `json:"feedin,omitempty"`
Grid api.Rates `json:"grid,omitempty"`
Solar api.Rates `json:"solar,omitempty"`
}{
Co2: tariff.Forecast(site.GetTariff(api.TariffUsageCo2)),
FeedIn: tariff.Forecast(site.GetTariff(api.TariffUsageFeedIn)),
Grid: tariff.Forecast(site.GetTariff(api.TariffUsageGrid)),
Solar: tariff.Forecast(site.GetTariff(api.TariffUsageSolar)),
})
}

// updateLoadpoints updates all loadpoints' charge power
Expand Down
15 changes: 15 additions & 0 deletions server/mqtt.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ import (
"github.com/evcc-io/evcc/util"
)

// MQTTMarshaler is the interface implemented by types that
// can marshal themselves into valid an MQTT string representation.
type MQTTMarshaler interface {
MarshalMQTT() ([]byte, error)
}

// MQTT is the MQTT server. It uses the MQTT client for publishing.
type MQTT struct {
log *util.Logger
Expand Down Expand Up @@ -76,6 +82,15 @@ func (m *MQTT) publishComplex(topic string, retained bool, payload interface{})
return
}

if mm, ok := payload.(MQTTMarshaler); ok {
if b, err := mm.MarshalMQTT(); err == nil {
m.publishSingleValue(topic, retained, string(b))
} else {
m.log.ERROR.Printf("marshal mqtt: %v", err)
}
return
}

switch typ := reflect.TypeOf(payload); typ.Kind() {
case reflect.Slice:
// publish count
Expand Down
15 changes: 13 additions & 2 deletions tariff/tariffs.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tariff

import (
"slices"
"time"

"github.com/evcc-io/evcc/api"
Expand All @@ -12,7 +13,7 @@ type Tariffs struct {
Grid, FeedIn, Co2, Planner, Solar api.Tariff
}

func CurrentPrice(t api.Tariff) (float64, error) {
func Now(t api.Tariff) (float64, error) {
if t != nil {
if rr, err := t.Rates(); err == nil {
if r, err := rr.Current(time.Now()); err == nil {
Expand All @@ -23,12 +24,22 @@ func CurrentPrice(t api.Tariff) (float64, error) {
return 0, api.ErrNotAvailable
}

func Forecast(t api.Tariff) api.Rates {
staticTariffs := []api.TariffType{api.TariffTypePriceStatic, api.TariffTypePriceDynamic}
if t != nil && !slices.Contains(staticTariffs, t.Type()) {
if rr, err := t.Rates(); err == nil {
return rr
}
}
return nil
}

func (t *Tariffs) Get(u api.TariffUsage) api.Tariff {
switch u {
case api.TariffUsageCo2:
return t.Co2

case api.TariffUsageFeedin:
case api.TariffUsageFeedIn:
return t.FeedIn

case api.TariffUsageGrid:
Expand Down

0 comments on commit 8f05f13

Please sign in to comment.