Loadpoints: track metrics (#29143)

Co-authored-by: Michael Geers <michael@geers.tv>
This commit is contained in:
andig 2026-05-03 10:40:51 +02:00 • committed by GitHub
parent 1fbe26a6ca
commit 1ac513c913
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 53 additions and 36 deletions

View file

@ -23,6 +23,7 @@ import (
"github.com/evcc-io/evcc/core/circuit"
"github.com/evcc-io/evcc/core/keys"
"github.com/evcc-io/evcc/core/loadpoint"
"github.com/evcc-io/evcc/core/metrics"
coresettings "github.com/evcc-io/evcc/core/settings"
"github.com/evcc-io/evcc/hems"
hemsapi "github.com/evcc-io/evcc/hems/hems"
@ -1301,14 +1302,25 @@ func configureSite(conf map[string]any, loadpoints []*core.Loadpoint, tariffs *t
return site, nil
}
func newLoadpoint(idx int, name string, other map[string]any, settingsFn func(*util.Logger) coresettings.Settings) (*core.Loadpoint, error) {
log := util.NewLoggerWithLoadpoint("lp-"+strconv.Itoa(idx), idx)
collector, err := metrics.NewCollector(metrics.Loadpoint, name)
if err != nil {
return nil, err
}
return core.NewLoadpointFromConfig(log, settingsFn(log), collector, other)
}
func configureLoadpoints(conf globalconfig.All) error {
for id, cc := range conf.Loadpoints {
cc.Name = "lp-" + strconv.Itoa(id+1)
idx := id + 1
cc.Name = "lp-" + strconv.Itoa(idx)
log := util.NewLoggerWithLoadpoint(cc.Name, id+1)
settings := coresettings.NewDatabaseSettingsAdapter(fmt.Sprintf("lp%d.", id+1))
instance, err := core.NewLoadpointFromConfig(log, settings, cc.Other)
instance, err := newLoadpoint(idx, cc.Name, cc.Other, func(*util.Logger) coresettings.Settings {
return coresettings.NewDatabaseSettingsAdapter(fmt.Sprintf("lp%d.", idx))
})
if err != nil {
return &DeviceError{cc.Name, err}
}
@ -1326,19 +1338,16 @@ func configureLoadpoints(conf globalconfig.All) error {
for _, conf := range configurable {
cc := conf.Named()
id := len(config.Loadpoints().Devices())
name := "lp-" + strconv.Itoa(id+1)
log := util.NewLoggerWithLoadpoint(name, id+1)
settings := coresettings.NewConfigSettingsAdapter(log, &conf)
idx := len(config.Loadpoints().Devices()) + 1
dynamic, static, err := loadpoint.SplitConfig(cc.Other)
if err != nil {
return &DeviceError{cc.Name, err}
}
instance, err := core.NewLoadpointFromConfig(log, settings, static)
instance, err := newLoadpoint(idx, cc.Name, static, func(log *util.Logger) coresettings.Settings {
return coresettings.NewConfigSettingsAdapter(log, &conf)
})
if err != nil {
err = &DeviceError{cc.Name, err}
}

View file

@ -17,6 +17,7 @@ import (
"github.com/evcc-io/evcc/core/coordinator"
"github.com/evcc-io/evcc/core/keys"
"github.com/evcc-io/evcc/core/loadpoint"
"github.com/evcc-io/evcc/core/metrics"
"github.com/evcc-io/evcc/core/planner"
"github.com/evcc-io/evcc/core/session"
"github.com/evcc-io/evcc/core/settings"
@ -137,10 +138,11 @@ type Loadpoint struct {
chargeRater api.ChargeRater
chargedAtStartup float64 // session energy at startup
circuit api.Circuit // Circuit
chargeMeter api.Meter // Charger usage meter
vehicle api.Vehicle // Currently active vehicle
defaultVehicle api.Vehicle // Default vehicle (disables detection)
circuit api.Circuit // Circuit
chargeMeter api.Meter // Charger usage meter
chargeEnergy *metrics.Collector // Charger usage collector
vehicle api.Vehicle // Currently active vehicle
defaultVehicle api.Vehicle // Default vehicle (disables detection)
coordinator coordinator.API
socEstimator *soc.Estimator
@ -183,7 +185,7 @@ type Loadpoint struct {
}
// NewLoadpointFromConfig creates a new loadpoint
func NewLoadpointFromConfig(log *util.Logger, settings settings.Settings, other map[string]any) (*Loadpoint, error) {
func NewLoadpointFromConfig(log *util.Logger, settings settings.Settings, collector *metrics.Collector, other map[string]any) (*Loadpoint, error) {
lp := NewLoadpoint(log, settings)
if err := util.DecodeOther(other, lp); err != nil {
return lp, err
@ -266,6 +268,10 @@ func NewLoadpointFromConfig(log *util.Logger, settings settings.Settings, other
}
lp.configureChargerType(lp.charger)
// add collector
if lp.chargeMeter != nil {
lp.chargeEnergy = collector
}
// phase switching defaults based on charger capabilities
if !lp.hasPhaseSwitching() {
@ -1733,8 +1739,18 @@ func (lp *Loadpoint) publishChargeProgress() {
// TODO deprecated: use sessionEnergy instead
lp.publish(keys.ChargedEnergy, lp.GetChargedEnergy())
lp.publish(keys.ChargeDuration, lp.chargeDuration)
// update energy, prefer totals
var importTotal *float64
if api.HasCap[api.MeterEnergy](lp.chargeMeter) {
lp.publish(keys.ChargeTotalImport, lp.chargeMeterTotal())
if f := lp.chargeMeterTotal(); f > 0 {
lp.publish(keys.ChargeTotalImport, f)
importTotal = &f
}
}
if lp.chargeEnergy != nil {
lp.chargeEnergy.AddEnergy(importTotal, nil, lp.chargePower)
}
}

View file

@ -9,9 +9,11 @@ import (
const (
// groups
Grid = "grid"
PV = "pv"
Home = "home" // meter and group (virtual measurement)
Battery = "battery"
Grid = "grid"
PV = "pv"
Home = "home" // meter and group (virtual measurement)
Loadpoint = "loadpoint"
)
type Collector struct {

View file

@ -36,24 +36,13 @@ func SetupSchema() error {
m := db.Instance.Migrator()
// entites: create entity first to make sure foreign keys for existing data work
hasTable := m.HasTable(new(entity))
if err := db.Instance.AutoMigrate(new(entity)); err != nil {
return err
}
// entites: add entity id 1
if hasTable {
var res entity
if err := db.Instance.Where(&entity{Id: 1, Name: Home}).FirstOrCreate(&res).Error; err != nil {
return err
}
if res.Group == "" {
res.Group = Home
if err := db.Instance.Save(&res).Error; err != nil {
return err
}
}
// ensure home entity exists (reserves id=1 for legacy meter FK references)
if _, err := createEntity(Home, Home); err != nil {
return err
}
// drop obsolete indexes

View file

@ -165,7 +165,8 @@ func newLoadpointHandler() http.HandlerFunc {
}
settings := coresettings.NewConfigSettingsAdapter(log, &conf)
instance, err := core.NewLoadpointFromConfig(log, settings, static)
instance, err := core.NewLoadpointFromConfig(log, settings, nil, static)
if err != nil {
conf.Delete()
jsonError(w, http.StatusBadRequest, err)