Custom charger: add optional finish timer (#29600)

This commit is contained in:
Copilot 2026-05-04 17:47:25 +02:00 • committed by GitHub
parent 0178d32302
commit 975caea21c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 92 additions and 11 deletions

View file

@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"time"
reg "github.com/evcc-io/evcc/util/registry"
)
@ -163,3 +164,24 @@ func (c *Config) BytesSetter(ctx context.Context, param string) (func([]byte) er
return prov.BytesSetter(param)
}
// TimeGetter returns a getter that parses the plugin value as an estimated finish time.
// The value may be an RFC3339 timestamp, a Go duration string, or a numeric number of seconds.
func (c *Config) TimeGetter(ctx context.Context) (func() (time.Time, error), error) {
if c == nil {
return nil, nil
}
stringG, err := c.StringGetter(ctx)
if err != nil {
return nil, err
}
return func() (time.Time, error) {
s, err := stringG()
if err != nil {
return time.Time{}, err
}
return parseRelativeTime(s)
}, nil
}

View file

@ -4,6 +4,7 @@ import (
"fmt"
"math"
"strconv"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util"
@ -42,3 +43,38 @@ func parseFloat(payload string) (float64, error) {
}
return f, err
}
// unixThreshold is the Unix timestamp for 2026-01-01 00:00:00 UTC, used to distinguish
// Unix timestamps from duration-in-seconds when parsing numeric finish times.
const unixThreshold = 1767225600
// parseRelativeTime parses a string into an absolute time.Time.
// Supported formats:
// - RFC3339 timestamp (e.g. "2026-05-03T14:00:00Z") → interpreted as absolute time
// - Go duration string (e.g. "1h30m") → interpreted as remaining duration, added to time.Now()
// - Numeric string ≥ 1767225600 (e.g. "1767225600") → interpreted as Unix timestamp (absolute time)
// - Numeric string < 1767225600 (e.g. "5400") → interpreted as remaining seconds, added to time.Now()
//
// For relative formats, time.Now() is evaluated at the time of each call, providing a
// fresh estimate. This matches the behavior of hardcoded charger/vehicle implementations.
func parseRelativeTime(s string) (time.Time, error) {
// Try RFC3339 timestamp first (absolute time)
if t, err := time.Parse(time.RFC3339, s); err == nil {
return t, nil
}
// Try Go duration string (relative)
if d, err := time.ParseDuration(s); err == nil {
return time.Now().Add(d), nil
}
// Try numeric: Unix timestamp (absolute) or duration in seconds (relative)
if f, err := strconv.ParseFloat(s, 64); err == nil {
if f >= unixThreshold {
return time.Unix(int64(f), 0), nil
}
return time.Now().Add(time.Duration(f * float64(time.Second))), nil
}
return time.Time{}, fmt.Errorf("invalid finish time: %s", s)
}