Ext/Aux meters: track energy metrics (#29836)

This commit is contained in:
Michael Geers 2026-05-15 17:06:02 +02:00 • committed by GitHub
parent 468dfa69af
commit a74982acc5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 52 additions and 20 deletions

View file

@ -14,6 +14,7 @@ const (
PV = "pv"
Home = "home" // meter and group (virtual measurement)
Loadpoint = "loadpoint"
Meter = "meter" // generic meter (ext/aux)
Forecast = "forecast"
)

View file

@ -85,11 +85,7 @@ type Site struct {
prioritizer *prioritizer.Prioritizer // Power budgets
stats *Stats // Stats
// metrics
fcstEnergy *metrics.Collector
pvEnergy map[string]*metrics.Collector
homeEnergy, gridEnergy *metrics.Collector
batteryEnergy map[string]*metrics.Collector // per-battery, keyed by meter ref
collectors map[string]*metrics.Collector // keyed by meter ref
// cached state
gridPower float64 // Grid power
@ -144,7 +140,7 @@ func (site *Site) Boot(log *util.Logger, loadpoints []*Loadpoint, tariffs *tarif
if err != nil {
return err
}
site.homeEnergy = me
site.collectors[metrics.Home] = me
// upload telemetry on shutdown
if telemetry.Enabled() {
@ -196,7 +192,7 @@ func (site *Site) Boot(log *util.Logger, loadpoints []*Loadpoint, tariffs *tarif
if err != nil {
return err
}
site.gridEnergy = me
site.collectors[site.Meters.GridMeterRef] = me
}
// multiple pv
@ -212,7 +208,7 @@ func (site *Site) Boot(log *util.Logger, loadpoints []*Loadpoint, tariffs *tarif
if err != nil {
return err
}
site.pvEnergy[ref] = me
site.collectors[ref] = me
}
// solar forecast collector (mirrors PV history shape, used for scale lookup)
@ -220,7 +216,7 @@ func (site *Site) Boot(log *util.Logger, loadpoints []*Loadpoint, tariffs *tarif
if err != nil {
return err
}
site.fcstEnergy = fc
site.collectors[metrics.Forecast] = fc
// multiple batteries
for _, ref := range site.Meters.BatteryMetersRef {
@ -234,7 +230,7 @@ func (site *Site) Boot(log *util.Logger, loadpoints []*Loadpoint, tariffs *tarif
if err != nil {
return err
}
site.batteryEnergy[ref] = me
site.collectors[ref] = me
}
// meters used only for monitoring
@ -244,6 +240,12 @@ func (site *Site) Boot(log *util.Logger, loadpoints []*Loadpoint, tariffs *tarif
return err
}
site.extMeters = append(site.extMeters, dev)
me, err := metrics.NewCollector(metrics.Meter, ref)
if err != nil {
return err
}
site.collectors[ref] = me
}
// auxiliary meters
@ -253,6 +255,12 @@ func (site *Site) Boot(log *util.Logger, loadpoints []*Loadpoint, tariffs *tarif
return err
}
site.auxMeters = append(site.auxMeters, dev)
me, err := metrics.NewCollector(metrics.Meter, ref)
if err != nil {
return err
}
site.collectors[ref] = me
}
// revert battery mode on shutdown
@ -270,10 +278,9 @@ func (site *Site) Boot(log *util.Logger, loadpoints []*Loadpoint, tariffs *tarif
// NewSite creates a Site with sane defaults
func NewSite() *Site {
site := &Site{
log: util.NewLogger("site"),
Voltage: 230, // V
pvEnergy: make(map[string]*metrics.Collector),
batteryEnergy: make(map[string]*metrics.Collector),
log: util.NewLogger("site"),
Voltage: 230, // V
collectors: make(map[string]*metrics.Collector),
}
return site
@ -580,7 +587,7 @@ func (site *Site) updatePvMeters() {
// persist per-meter PV energy slots (used for history and forecast scaling)
for i, dev := range site.pvMeters {
c := site.pvEnergy[dev.Config().Name]
c := site.collectors[dev.Config().Name]
var importEnergy *float64
if mm[i].Energy > 0 {
@ -657,7 +664,7 @@ func (site *Site) updateBatteryMeters() {
// accumulate per-battery energy (charging = import, discharging = export — from battery POV toward grid root)
for i, dev := range site.batteryMeters {
ref := dev.Config().Name
c, ok := site.batteryEnergy[ref]
c, ok := site.collectors[ref]
if !ok {
continue
}
@ -692,6 +699,24 @@ func weightedSumOfSocs(mm []types.Measurement) float64 {
})
}
// addMeterEnergy persists per-meter energy (positive power = import).
func (site *Site) addMeterEnergy(meters []config.Device[api.Meter], mm []types.Measurement) {
for i, dev := range meters {
ref := dev.Config().Name
c, ok := site.collectors[ref]
if !ok {
continue
}
var importEnergy *float64
if mm[i].Energy > 0 {
importEnergy = &mm[i].Energy
}
if err := c.AddEnergy(importEnergy, nil, mm[i].Power); err != nil {
site.log.ERROR.Printf("persist meter %s energy: %v", ref, err)
}
}
}
// updateAuxMeters updates aux meters
func (site *Site) updateAuxMeters() {
if len(site.auxMeters) == 0 {
@ -707,6 +732,8 @@ func (site *Site) updateAuxMeters() {
site.log.DEBUG.Printf("aux power: %.0fW", site.auxPower)
}
site.addMeterEnergy(site.auxMeters, mm)
site.publish(keys.AuxPower, site.auxPower)
site.publish(keys.Aux, mm)
}
@ -718,6 +745,9 @@ func (site *Site) updateExtMeters() {
}
mm := site.collectMeters("ext", site.extMeters)
site.addMeterEnergy(site.extMeters, mm)
site.publish(keys.Ext, mm)
}
@ -770,7 +800,7 @@ func (site *Site) updateGridMeter() error {
}
}
site.gridEnergy.AddEnergy(importEnergy, nil, mm.Power)
site.collectors[site.Meters.GridMeterRef].AddEnergy(importEnergy, nil, mm.Power)
site.publish(keys.Grid, mm)
@ -949,7 +979,7 @@ func (site *Site) update(lp updater) {
site.publish(keys.HomePower, homePower)
if homePower > 0 {
if err := site.homeEnergy.AddEnergy(nil, nil, homePower); err != nil {
if err := site.collectors[metrics.Home].AddEnergy(nil, nil, homePower); err != nil {
site.log.ERROR.Printf("persist home consumption: %v", err)
}
}

View file

@ -15,6 +15,7 @@ import (
"github.com/evcc-io/evcc/api"
"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/types"
"github.com/evcc-io/evcc/tariff"
"github.com/evcc-io/evcc/util/config"
@ -550,7 +551,7 @@ func loadpointProfile(lp loadpoint.API, minLen int) []float64 {
// homeProfile returns the home base load in Wh
func (site *Site) homeProfile(minLen int) ([]float64, error) {
// kWh over last 30 days
profile, err := site.homeEnergy.ImportProfile(now.BeginningOfDay().AddDate(0, 0, -30))
profile, err := site.collectors[metrics.Home].ImportProfile(now.BeginningOfDay().AddDate(0, 0, -30))
if err != nil {
return nil, err
}

View file

@ -147,7 +147,7 @@ func (site *Site) solarDetails(solar api.Rates) solarDetails {
}
if r, err := solar.At(time.Now()); err == nil {
if err := site.fcstEnergy.AddEnergy(nil, nil, r.Value); err != nil {
if err := site.collectors[metrics.Forecast].AddEnergy(nil, nil, r.Value); err != nil {
site.log.ERROR.Printf("solar forecast collector: %v", err)
}
}