Tariffs: wait for startup

This commit is contained in:
andig 2022-12-30 19:52:00 +01:00
parent 3b438c69a4
commit c13235a73e
6 changed files with 143 additions and 30 deletions

View file

@ -14,6 +14,9 @@ var ErrSponsorRequired = errors.New("sponsorship required, see https://github.co
// ErrMissingCredentials indicates that user/password are missing
var ErrMissingCredentials = errors.New("missing credentials")
// ErrOutdated indicates that result is outdated
var ErrOutdated = errors.New("outdated")
// ErrTimeout is the error returned when a timeout happened.
// Modeled after context.DeadlineError
var ErrTimeout error = errTimeoutError{}

74
cmd/tariff.go Normal file
View file

@ -0,0 +1,74 @@
package cmd
import (
"fmt"
"os"
"text/tabwriter"
"github.com/evcc-io/evcc/tariff"
"github.com/spf13/cobra"
)
// tariffCmd represents the vehicle command
var tariffCmd = &cobra.Command{
Use: "tariff [name]",
Short: "Query configured tariff",
Run: runTariff,
}
func init() {
rootCmd.AddCommand(tariffCmd)
tariffCmd.PersistentFlags().StringP(flagName, "n", "", fmt.Sprintf(flagNameDescription, "vehicle"))
}
func runTariff(cmd *cobra.Command, args []string) {
// load config
if err := loadConfigFile(&conf); err != nil {
fatal(err)
}
// setup environment
if err := configureEnvironment(cmd, conf); err != nil {
fatal(err)
}
name := cmd.Flags().Lookup(flagName).Value.String()
for key, cc := range map[string]typedConfig{
"grid": conf.Tariffs.Grid,
"feedin": conf.Tariffs.FeedIn,
"planner": conf.Tariffs.Planner,
} {
if cc.Type == "" || (name != "" && key != name) {
continue
}
if name == "" {
fmt.Println(key + ":")
}
tf, err := tariff.NewFromConfig(cc.Type, cc.Other)
if err != nil {
fatal(err)
}
rates, err := tf.Rates()
if err != nil {
fatal(err)
}
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
fmt.Fprintln(tw, "From\tTo\tPrice/Cost")
const format = "2006-01-02 15:04:05"
for _, r := range rates {
fmt.Fprintf(tw, "%s\t%s\t%.3f\n", r.Start.Local().Format(format), r.End.Local().Format(format), r.Price)
}
tw.Flush()
fmt.Println()
}
// wait for shutdown
<-shutdownDoneC()
}

View file

@ -13,10 +13,11 @@ import (
)
type Awattar struct {
mux sync.Mutex
log *util.Logger
uri string
data api.Rates
mux sync.Mutex
log *util.Logger
uri string
data api.Rates
updated time.Time
}
var _ api.Tariff = (*Awattar)(nil)
@ -47,12 +48,15 @@ func NewAwattarFromConfig(other map[string]interface{}) (api.Tariff, error) {
t.log.WARN.Println("cheap rate configuration has been replaced by target charging and is deprecated")
}
go t.Run()
done := make(chan error)
go t.run(done)
err := <-done
return t, nil
return t, err
}
func (t *Awattar) Run() {
func (t *Awattar) run(done chan error) {
var once sync.Once
client := request.NewHelper(t.log)
for ; true; <-time.NewTicker(time.Hour).C {
@ -62,9 +66,12 @@ func (t *Awattar) Run() {
continue
}
t.mux.Lock()
t.data = make(api.Rates, 0, len(res.Data))
once.Do(func() { close(done) })
t.mux.Lock()
t.updated = time.Now()
t.data = make(api.Rates, 0, len(res.Data))
for _, r := range res.Data {
ar := api.Rate{
Start: r.StartTimestamp,
@ -82,5 +89,5 @@ func (t *Awattar) Run() {
func (t *Awattar) Rates() (api.Rates, error) {
t.mux.Lock()
defer t.mux.Unlock()
return append([]api.Rate{}, t.data...), nil
return append([]api.Rate{}, t.data...), outdatedError(t.updated, time.Hour)
}

View file

@ -15,11 +15,12 @@ import (
type ElectricityMaps struct {
*request.Helper
log *util.Logger
mux sync.Mutex
uri string
zone string
data []CarbonIntensitySlot
log *util.Logger
mux sync.Mutex
uri string
zone string
data []CarbonIntensitySlot
updated time.Time
}
type CarbonIntensity struct {
@ -29,8 +30,8 @@ type CarbonIntensity struct {
}
type CarbonIntensitySlot struct {
CarbonIntensity float64 // : 626,
Datetime time.Time // : "2022-12-12T16:00:00.000Z"
CarbonIntensity float64 // 626,
Datetime time.Time // "2022-12-12T16:00:00.000Z"
}
var _ api.Tariff = (*ElectricityMaps)(nil)
@ -68,12 +69,15 @@ func NewElectricityMapsFromConfig(other map[string]interface{}) (api.Tariff, err
}),
}
go t.Run()
done := make(chan error)
go t.run(done)
err := <-done
return t, nil
return t, err
}
func (t *ElectricityMaps) Run() {
func (t *ElectricityMaps) run(done chan error) {
var once sync.Once
uri := fmt.Sprintf("%s/carbon-intensity/forecast?zone=%s", t.uri, t.zone)
for ; true; <-time.NewTicker(time.Hour).C {
@ -83,11 +87,16 @@ func (t *ElectricityMaps) Run() {
err = errors.New(res.Error)
}
once.Do(func() { done <- err })
t.log.ERROR.Println(err)
continue
}
once.Do(func() { close(done) })
t.mux.Lock()
t.updated = time.Now()
t.data = res.Forecast
t.mux.Unlock()
}
@ -107,5 +116,5 @@ func (t *ElectricityMaps) Rates() (api.Rates, error) {
res = append(res, ar)
}
return res, nil
return res, outdatedError(t.updated, time.Hour)
}

View file

@ -1,6 +1,8 @@
package tariff
import (
"time"
"github.com/evcc-io/evcc/api"
"golang.org/x/text/currency"
)
@ -22,3 +24,11 @@ func NewTariffs(currency currency.Unit, grid, feedin, planner api.Tariff) *Tarif
Planner: planner,
}
}
// outdatedError returns api.ErrOutdated if t is older than 2*d
func outdatedError(t time.Time, d time.Duration) error {
if time.Since(t) > 2*d {
return api.ErrOutdated
}
return nil
}

View file

@ -14,11 +14,12 @@ import (
)
type Tibber struct {
mux sync.Mutex
log *util.Logger
homeID string
client *tibber.Client
data api.Rates
mux sync.Mutex
log *util.Logger
homeID string
client *tibber.Client
data api.Rates
updated time.Time
}
var _ api.Tariff = (*Tibber)(nil)
@ -62,12 +63,16 @@ func NewTibberFromConfig(other map[string]interface{}) (api.Tariff, error) {
t.log.WARN.Println("cheap rate configuration has been replaced by target charging and is deprecated")
}
go t.Run()
done := make(chan error)
go t.run(done)
err := <-done
return t, nil
return t, err
}
func (t *Tibber) Run() {
func (t *Tibber) run(done chan error) {
var once sync.Once
for ; true; <-time.NewTicker(time.Hour).C {
var res struct {
Viewer struct {
@ -88,11 +93,16 @@ func (t *Tibber) Run() {
cancel()
if err != nil {
once.Do(func() { done <- err })
t.log.ERROR.Println(err)
continue
}
once.Do(func() { close(done) })
t.mux.Lock()
t.updated = time.Now()
pi := res.Viewer.Home.CurrentSubscription.PriceInfo
t.data = make(api.Rates, 0, len(pi.Today)+len(pi.Tomorrow))
@ -119,5 +129,5 @@ func (t *Tibber) rates(pi []tibber.PriceInfo) api.Rates {
func (t *Tibber) Rates() (api.Rates, error) {
t.mux.Lock()
defer t.mux.Unlock()
return append([]api.Rate{}, t.data...), nil
return append([]api.Rate{}, t.data...), outdatedError(t.updated, time.Hour)
}