API: publish forecast as arrays with unix timestamps (BC) (#32391)
This commit is contained in:
parent
9d0bc5a865
commit
45e781dac0
35 changed files with 361 additions and 223 deletions
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/evcc-io/evcc/tariff"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/jinzhu/now"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type solarDetails struct {
|
||||
|
|
@ -25,6 +26,20 @@ type dailyDetails struct {
|
|||
Complete bool `json:"complete"`
|
||||
}
|
||||
|
||||
// forecastRates publishes rates as [start, end, value] with the timestamps in
|
||||
// unix seconds. The forecast is the largest payload evcc sends and RFC3339
|
||||
// timestamps are two thirds of it.
|
||||
func forecastRates(rr api.Rates) [][]float64 {
|
||||
// keep nil for empty rates: shards are published without omitempty
|
||||
if len(rr) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return lo.Map(rr, func(r api.Rate, _ int) []float64 {
|
||||
return []float64{float64(r.Start.Unix()), float64(r.End.Unix()), r.Value}
|
||||
})
|
||||
}
|
||||
|
||||
// greenShare returns
|
||||
// - the current green share, calculated for the part of the consumption between powerFrom and powerTo
|
||||
// the consumption below powerFrom will get the available green power first
|
||||
|
|
@ -101,18 +116,18 @@ func (site *Site) publishTariffs(greenShareHome float64, greenShareLoadpoints fl
|
|||
}
|
||||
|
||||
fc := struct {
|
||||
Co2 api.Rates `json:"co2,omitempty"`
|
||||
FeedIn api.Rates `json:"feedin,omitempty"`
|
||||
Grid api.Rates `json:"grid,omitempty"`
|
||||
Planner api.Rates `json:"planner,omitempty"`
|
||||
Co2 [][]float64 `json:"co2,omitempty"`
|
||||
FeedIn [][]float64 `json:"feedin,omitempty"`
|
||||
Grid [][]float64 `json:"grid,omitempty"`
|
||||
Planner [][]float64 `json:"planner,omitempty"`
|
||||
Solar *solarDetails `json:"solar,omitempty"`
|
||||
Temperature api.Rates `json:"temperature,omitempty"`
|
||||
Temperature [][]float64 `json:"temperature,omitempty"`
|
||||
}{
|
||||
Co2: tariff.Rates(site.GetTariff(api.TariffUsageCo2)),
|
||||
FeedIn: tariff.Rates(site.GetTariff(api.TariffUsageFeedIn)),
|
||||
Planner: tariff.Rates(site.GetTariff(api.TariffUsagePlanner)),
|
||||
Grid: tariff.Rates(site.GetTariff(api.TariffUsageGrid)),
|
||||
Temperature: tariff.Rates(site.GetTariff(api.TariffUsageTemperature)),
|
||||
Co2: forecastRates(tariff.Rates(site.GetTariff(api.TariffUsageCo2))),
|
||||
FeedIn: forecastRates(tariff.Rates(site.GetTariff(api.TariffUsageFeedIn))),
|
||||
Planner: forecastRates(tariff.Rates(site.GetTariff(api.TariffUsagePlanner))),
|
||||
Grid: forecastRates(tariff.Rates(site.GetTariff(api.TariffUsageGrid))),
|
||||
Temperature: forecastRates(tariff.Rates(site.GetTariff(api.TariffUsageTemperature))),
|
||||
}
|
||||
|
||||
// calculate adjusted solar rates
|
||||
|
|
|
|||
69
core/site_tariffs_test.go
Normal file
69
core/site_tariffs_test.go
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestForecastRates(t *testing.T) {
|
||||
start := time.Unix(1735689600, 0)
|
||||
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
rates api.Rates
|
||||
want string
|
||||
}{
|
||||
{desc: "nil", rates: nil, want: "null"},
|
||||
{desc: "empty", rates: api.Rates{}, want: "null"},
|
||||
{
|
||||
desc: "slots",
|
||||
rates: api.Rates{
|
||||
{Start: start, End: start.Add(time.Hour), Value: 0.25},
|
||||
{Start: start.Add(time.Hour), End: start.Add(2 * time.Hour), Value: -0.1},
|
||||
},
|
||||
want: "[[1735689600,1735693200,0.25],[1735693200,1735696800,-0.1]]",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
b, err := json.Marshal(forecastRates(tc.rates))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.want, string(b))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeseriesMarshal(t *testing.T) {
|
||||
start := time.Unix(1735689600, 0)
|
||||
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
ts timeseries
|
||||
want string
|
||||
}{
|
||||
{desc: "nil", ts: nil, want: "null"},
|
||||
{desc: "empty", ts: timeseries{}, want: "[]"},
|
||||
{
|
||||
desc: "entries",
|
||||
ts: timeseries{
|
||||
{Timestamp: start, Value: 1000},
|
||||
{Timestamp: start.Add(time.Hour), Value: 0},
|
||||
},
|
||||
want: "[[1735689600,1000],[1735693200,0]]",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
b, err := json.Marshal(tc.ts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.want, string(b))
|
||||
|
||||
b, err = tc.ts.MarshalBytes()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.want, string(b))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,20 @@ func (ts timeseries) MarshalBytes() ([]byte, error) {
|
|||
return json.Marshal(ts)
|
||||
}
|
||||
|
||||
// MarshalJSON publishes entries as [ts, val] with the timestamp in unix seconds
|
||||
func (ts timeseries) MarshalJSON() ([]byte, error) {
|
||||
if ts == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
|
||||
res := make([][]float64, 0, len(ts))
|
||||
for _, e := range ts {
|
||||
res = append(res, []float64{float64(e.Timestamp.Unix()), e.Value})
|
||||
}
|
||||
|
||||
return json.Marshal(res)
|
||||
}
|
||||
|
||||
type tsEntry struct {
|
||||
Timestamp time.Time `json:"ts"`
|
||||
Value float64 `json:"val"`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue