chore: rename metrics import/export to energy/returnEnergy throughout collector and accumulator (#30303)

This commit is contained in:
andig 2026-06-07 13:31:23 +02:00 • committed by GitHub
parent 8106dc8b76
commit 03d4565c0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 96 additions and 118 deletions

View file

@ -9,12 +9,12 @@ import (
)
type Accumulator struct {
clock clock.Clock
updated time.Time
importMeter *float64 // kWh
exportMeter *float64 // kWh
Energy float64 `json:"energy"` // kWh
ReturnEnergy float64 `json:"returnEnergy"` // kWh
clock clock.Clock
updated time.Time
energyMeter *float64 // kWh
returnEnergyMeter *float64 // kWh
Energy float64 `json:"energy"` // kWh
ReturnEnergy float64 `json:"returnEnergy"` // kWh
}
func WithClock(clock clock.Clock) func(*Accumulator) {
@ -37,63 +37,53 @@ func (m *Accumulator) Updated() time.Time {
func (m *Accumulator) String() string {
b := new(bytes.Buffer)
fmt.Fprintf(b, "Accumulated: %.3fkWh pos, %.3fkWh neg, updated: %v", m.Energy, m.ReturnEnergy, m.updated.Truncate(time.Second))
if m.importMeter != nil || m.exportMeter != nil {
fmt.Fprintf(b, " meter: ")
if m.importMeter != nil {
fmt.Fprintf(b, " %.3fkWh pos", *m.importMeter)
fmt.Fprintf(b, "Accumulated: %.3fkWh energy, %.3fkWh return energy, updated: %v", m.Energy, m.ReturnEnergy, m.updated.Truncate(time.Second))
if m.energyMeter != nil || m.returnEnergyMeter != nil {
fmt.Fprintf(b, " energy total:")
if m.energyMeter != nil {
fmt.Fprintf(b, " %.3fkWh", *m.energyMeter)
}
if m.exportMeter != nil {
fmt.Fprintf(b, " %.3fkWh pos", *m.exportMeter)
if m.returnEnergyMeter != nil {
fmt.Fprintf(b, " %.3fkWh return energy", *m.returnEnergyMeter)
}
}
return b.String()
}
// Imported returns the accumulated import energy in kWh
func (m *Accumulator) Imported() float64 {
return m.Energy
}
// Exported returns the accumulated export energy in kWh
func (m *Accumulator) Exported() float64 {
return m.ReturnEnergy
}
// SetImportMeterTotal adds the difference to the last total meter value in kWh
func (m *Accumulator) SetImportMeterTotal(v float64) {
// SetEnergyMeterTotal adds the difference to the last total meter value in kWh
func (m *Accumulator) SetEnergyMeterTotal(v float64) {
defer func() {
m.updated = m.clock.Now()
m.importMeter = new(v)
m.energyMeter = new(v)
}()
if m.importMeter == nil {
if m.energyMeter == nil {
return
}
if v >= *m.importMeter {
m.Energy += v - *m.importMeter
if v >= *m.energyMeter {
m.Energy += v - *m.energyMeter
}
}
// SetExportMeterTotal adds the difference to the last total meter value in kWh
func (m *Accumulator) SetExportMeterTotal(v float64) {
// SetReturnEnergyMeterTotal adds the difference to the last total meter value in kWh
func (m *Accumulator) SetReturnEnergyMeterTotal(v float64) {
defer func() {
m.updated = m.clock.Now()
m.exportMeter = new(v)
m.returnEnergyMeter = new(v)
}()
if m.exportMeter == nil {
if m.returnEnergyMeter == nil {
return
}
if v >= *m.exportMeter {
m.ReturnEnergy += v - *m.exportMeter
if v >= *m.returnEnergyMeter {
m.ReturnEnergy += v - *m.returnEnergyMeter
}
}
// AddImportEnergy adds the given energy in kWh to the positive meter
func (m *Accumulator) AddImportEnergy(v float64) {
// AddEnergy adds the given energy in kWh to the energy total
func (m *Accumulator) AddEnergy(v float64) {
defer func() { m.updated = m.clock.Now() }()
if m.updated.IsZero() {
@ -103,8 +93,8 @@ func (m *Accumulator) AddImportEnergy(v float64) {
m.Energy += v
}
// AddExportEnergy adds the given energy in kWh to the negative meter
func (m *Accumulator) AddExportEnergy(v float64) {
// AddReturnEnergy adds the given energy in kWh to the return energy total
func (m *Accumulator) AddReturnEnergy(v float64) {
defer func() { m.updated = m.clock.Now() }()
if m.updated.IsZero() {
@ -118,8 +108,8 @@ func (m *Accumulator) AddExportEnergy(v float64) {
func (m *Accumulator) AddPower(v float64) {
since := v * m.clock.Since(m.updated).Hours() / 1e3
if v >= 0 {
m.AddImportEnergy(since)
m.AddEnergy(since)
} else {
m.AddExportEnergy(-since)
m.AddReturnEnergy(-since)
}
}

View file

@ -15,12 +15,12 @@ func TestMeterEnergyMeterTotal(t *testing.T) {
me := &Accumulator{clock: clock}
me.SetImportMeterTotal(10)
assert.Equal(t, 0.0, me.Imported())
me.SetImportMeterTotal(11)
assert.Equal(t, 1.0, me.Imported())
me.SetImportMeterTotal(11)
assert.Equal(t, 1.0, me.Imported())
me.SetEnergyMeterTotal(10)
assert.Equal(t, 0.0, me.Energy)
me.SetEnergyMeterTotal(11)
assert.Equal(t, 1.0, me.Energy)
me.SetEnergyMeterTotal(11)
assert.Equal(t, 1.0, me.Energy)
}
func TestMeterEnergyAddPower(t *testing.T) {
@ -31,13 +31,13 @@ func TestMeterEnergyAddPower(t *testing.T) {
clock.Add(60 * time.Minute)
me.AddPower(1e3)
assert.Equal(t, 0.0, me.Imported())
assert.Equal(t, 0.0, me.Energy)
clock.Add(60 * time.Minute)
me.AddPower(1e3)
assert.Equal(t, 1.0, me.Imported())
assert.Equal(t, 1.0, me.Energy)
clock.Add(30 * time.Minute)
me.AddPower(1e3)
assert.Equal(t, 1.5, me.Imported())
assert.Equal(t, 1.5, me.Energy)
}

View file

@ -99,56 +99,44 @@ func (c *Collector) process(fun func()) error {
}
func (c *Collector) persist() error {
return persist(c.entity, c.started, c.accu.Imported(), c.accu.Exported())
return persist(c.entity, c.started, c.accu.Energy, c.accu.ReturnEnergy)
}
func (c *Collector) ImportProfile(from time.Time) (*[96]float64, error) {
return importProfile(c.entity, from)
func (c *Collector) EnergyProfile(from time.Time) (*[96]float64, error) {
return energyProfile(c.entity, from)
}
func (c *Collector) AddImportEnergy(v float64) error {
func (c *Collector) SetEnergyMeterTotal(v float64) error {
return c.process(func() {
c.accu.AddImportEnergy(v)
c.accu.SetEnergyMeterTotal(v)
})
}
func (c *Collector) AddExportEnergy(v float64) error {
func (c *Collector) SetReturnEnergyMeterTotal(v float64) error {
return c.process(func() {
c.accu.AddExportEnergy(v)
})
}
func (c *Collector) SetImportMeterTotal(v float64) error {
return c.process(func() {
c.accu.SetImportMeterTotal(v)
})
}
func (c *Collector) SetExportMeterTotal(v float64) error {
return c.process(func() {
c.accu.SetExportMeterTotal(v)
c.accu.SetReturnEnergyMeterTotal(v)
})
}
// AddEnergy adds energy using meter totals if available, falling back to power integration.
func (c *Collector) AddEnergy(importTotal, exportTotal *float64, power float64) error {
func (c *Collector) AddEnergy(energyTotal, returnEnergyTotal *float64, power float64) error {
return c.process(func() {
switch {
case importTotal != nil && exportTotal != nil:
c.accu.SetImportMeterTotal(*importTotal)
c.accu.SetExportMeterTotal(*exportTotal)
case importTotal != nil:
// export via power integration (before meter updates clock)
case energyTotal != nil && returnEnergyTotal != nil:
c.accu.SetEnergyMeterTotal(*energyTotal)
c.accu.SetReturnEnergyMeterTotal(*returnEnergyTotal)
case energyTotal != nil:
// return energy via power integration (before meter updates clock)
if power < 0 {
c.accu.AddPower(power)
}
c.accu.SetImportMeterTotal(*importTotal)
case exportTotal != nil:
// import via power integration (before meter updates clock)
c.accu.SetEnergyMeterTotal(*energyTotal)
case returnEnergyTotal != nil:
// energy via power integration (before meter updates clock)
if power >= 0 {
c.accu.AddPower(power)
}
c.accu.SetExportMeterTotal(*exportTotal)
c.accu.SetReturnEnergyMeterTotal(*returnEnergyTotal)
default:
c.accu.AddPower(power)
}

View file

@ -25,18 +25,18 @@ func TestCollectorAddEnergy(t *testing.T) {
clock.Add(5 * time.Minute)
require.NoError(t, col.AddEnergy(nil, nil, 1e3))
require.Equal(t, 1e3*5/60/1e3, col.accu.Imported()) // kWh
require.Equal(t, 1e3*5/60/1e3, col.accu.Energy) // kWh
clock.Add(5 * time.Minute)
require.NoError(t, col.AddEnergy(nil, nil, 1e3))
require.Equal(t, 0.0, col.accu.Imported()) // accumulator reset after 15 minutes
require.Equal(t, 0.0, col.accu.Energy) // accumulator reset after 15 minutes
clock.Add(15 * time.Minute)
require.NoError(t, col.AddEnergy(nil, nil, 1e3))
require.Equal(t, 0.0, col.accu.Imported()) // accumulator reset after 15 minutes
require.Equal(t, 0.0, col.accu.Energy) // accumulator reset after 15 minutes
}
func TestCollectorAddEnergyWithImportMeter(t *testing.T) {
func TestCollectorAddEnergyWithEnergyMeter(t *testing.T) {
clock := clock.NewMock()
require.NoError(t, db.NewInstance("sqlite", ":memory:"))
@ -48,20 +48,20 @@ func TestCollectorAddEnergyWithImportMeter(t *testing.T) {
// first call: seeds meter, no delta yet
clock.Add(5 * time.Minute)
require.NoError(t, col.AddEnergy(new(50000.0), nil, 1e3))
require.Equal(t, 0.0, col.accu.Imported())
require.Equal(t, 0.0, col.accu.Energy)
// second call: meter delta of 0.5 kWh, power ignored for import
// second call: meter delta of 0.5 kWh, power ignored for energy
clock.Add(5 * time.Minute)
require.NoError(t, col.AddEnergy(new(50000.5), nil, 1e3))
require.Equal(t, 0.5, col.accu.Imported())
require.Equal(t, 0.5, col.accu.Energy)
// implausible reading (decreased): ignored by guard
clock.Add(5 * time.Minute)
require.NoError(t, col.AddEnergy(new(49000.0), nil, 1e3))
require.Equal(t, 0.0, col.accu.Imported()) // reset at slot boundary
require.Equal(t, 0.0, col.accu.Energy) // reset at slot boundary
}
func TestCollectorAddEnergyWithImportMeterAndExport(t *testing.T) {
func TestCollectorAddEnergyWithEnergyMeterAndReturn(t *testing.T) {
clock := clock.NewMock()
require.NoError(t, db.NewInstance("sqlite", ":memory:"))
@ -70,24 +70,24 @@ func TestCollectorAddEnergyWithImportMeterAndExport(t *testing.T) {
col, err := NewCollector("baz", "baz", "", WithClock(clock))
require.NoError(t, err)
// seed import meter
// seed energy meter
clock.Add(3 * time.Minute)
require.NoError(t, col.AddEnergy(new(1000.0), nil, 0))
// positive power: import via meter delta, no export
// positive power: energy via meter delta, no return energy
clock.Add(3 * time.Minute)
require.NoError(t, col.AddEnergy(new(1000.3), nil, 500))
require.InDelta(t, 0.3, col.accu.Imported(), 1e-10)
require.Equal(t, 0.0, col.accu.Exported())
require.InDelta(t, 0.3, col.accu.Energy, 1e-10)
require.Equal(t, 0.0, col.accu.ReturnEnergy)
// negative power: import via meter (no change), export via power integration
// negative power: energy via meter (no change), return energy via power integration
clock.Add(3 * time.Minute)
require.NoError(t, col.AddEnergy(new(1000.3), nil, -600))
require.InDelta(t, 0.3, col.accu.Imported(), 1e-10)
require.InDelta(t, 600.0*3/60/1e3, col.accu.Exported(), 1e-10) // 0.03 kWh
require.InDelta(t, 0.3, col.accu.Energy, 1e-10)
require.InDelta(t, 600.0*3/60/1e3, col.accu.ReturnEnergy, 1e-10) // 0.03 kWh
}
func TestCollectorAddEnergyWithExportMeterAndImport(t *testing.T) {
func TestCollectorAddEnergyWithReturnEnergyMeterAndEnergy(t *testing.T) {
clock := clock.NewMock()
require.NoError(t, db.NewInstance("sqlite", ":memory:"))
@ -96,21 +96,21 @@ func TestCollectorAddEnergyWithExportMeterAndImport(t *testing.T) {
col, err := NewCollector("baz2", "baz2", "", WithClock(clock))
require.NoError(t, err)
// seed export meter
// seed return energy meter
clock.Add(3 * time.Minute)
require.NoError(t, col.AddEnergy(nil, new(1000.0), 0))
// negative power: export via meter delta, no import
// negative power: return energy via meter delta, no energy
clock.Add(3 * time.Minute)
require.NoError(t, col.AddEnergy(nil, new(1000.3), -500))
require.InDelta(t, 0.3, col.accu.Exported(), 1e-10)
require.Equal(t, 0.0, col.accu.Imported())
require.InDelta(t, 0.3, col.accu.ReturnEnergy, 1e-10)
require.Equal(t, 0.0, col.accu.Energy)
// positive power: export via meter (no change), import via power integration
// positive power: return energy via meter (no change), energy via power integration
clock.Add(3 * time.Minute)
require.NoError(t, col.AddEnergy(nil, new(1000.3), 600))
require.InDelta(t, 0.3, col.accu.Exported(), 1e-10)
require.InDelta(t, 600.0*3/60/1e3, col.accu.Imported(), 1e-10) // 0.03 kWh
require.InDelta(t, 0.3, col.accu.ReturnEnergy, 1e-10)
require.InDelta(t, 600.0*3/60/1e3, col.accu.Energy, 1e-10) // 0.03 kWh
}
func TestCollectorAddEnergyWithBothMeters(t *testing.T) {
@ -129,11 +129,11 @@ func TestCollectorAddEnergyWithBothMeters(t *testing.T) {
// both deltas used, power ignored
clock.Add(3 * time.Minute)
require.NoError(t, col.AddEnergy(new(1000.3), new(2000.7), 999))
require.InDelta(t, 0.3, col.accu.Imported(), 1e-10)
require.InDelta(t, 0.7, col.accu.Exported(), 1e-10)
require.InDelta(t, 0.3, col.accu.Energy, 1e-10)
require.InDelta(t, 0.7, col.accu.ReturnEnergy, 1e-10)
}
func TestCollectorSetImportAndExportMeterTotal(t *testing.T) {
func TestCollectorSetEnergyAndReturnEnergyMeterTotal(t *testing.T) {
clock := clock.NewMock()
require.NoError(t, db.NewInstance("sqlite", ":memory:"))
@ -142,17 +142,17 @@ func TestCollectorSetImportAndExportMeterTotal(t *testing.T) {
col, err := NewCollector("set", "set", "", WithClock(clock))
require.NoError(t, err)
// seed both import and export
// seed both energy and return energy
clock.Add(5 * time.Minute)
require.NoError(t, col.SetImportMeterTotal(1000))
require.NoError(t, col.SetExportMeterTotal(2000))
require.NoError(t, col.SetEnergyMeterTotal(1000))
require.NoError(t, col.SetReturnEnergyMeterTotal(2000))
// both deltas used
clock.Add(5 * time.Minute)
require.NoError(t, col.SetImportMeterTotal(1000.3))
require.NoError(t, col.SetExportMeterTotal(2000.7))
require.InDelta(t, 0.3, col.accu.Imported(), 1e-10)
require.InDelta(t, 0.7, col.accu.Exported(), 1e-10)
require.NoError(t, col.SetEnergyMeterTotal(1000.3))
require.NoError(t, col.SetReturnEnergyMeterTotal(2000.7))
require.InDelta(t, 0.3, col.accu.Energy, 1e-10)
require.InDelta(t, 0.7, col.accu.ReturnEnergy, 1e-10)
}
// TestCollectorSkipsPartialFirstSlot verifies that the first slot, joined

View file

@ -10,9 +10,9 @@ import (
var ErrIncomplete = errors.New("meter profile incomplete")
// importProfile returns a 15min average meter profile in Wh. The profile
// energyProfile returns a 15min average meter profile in Wh. The profile
// is sorted by timestamp starting at 00:00. It is guaranteed to contain 96 15min values.
func importProfile(entity entity, from time.Time) (*[96]float64, error) {
func energyProfile(entity entity, from time.Time) (*[96]float64, error) {
db, err := db.Instance.DB()
if err != nil {
return nil, err

View file

@ -201,7 +201,7 @@ func TestUpdateProfile(t *testing.T) {
{
from := clock.Now().Local().AddDate(0, 0, -2).Add(12 * time.Hour) // 12:00 of day 0
prof, err := importProfile(entity, from)
prof, err := energyProfile(entity, from)
require.NoError(t, err)
var expected [96]float64
@ -219,7 +219,7 @@ func TestUpdateProfile(t *testing.T) {
{
from := clock.Now().Local().AddDate(0, 0, -3).Add(12 * time.Hour) // 12:00 of day -1
prof, err := importProfile(entity, from)
prof, err := energyProfile(entity, from)
require.NoError(t, err)
var expected [96]float64

View file

@ -552,7 +552,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.collectors[metrics.Home].ImportProfile(now.BeginningOfDay().AddDate(0, 0, -30))
profile, err := site.collectors[metrics.Home].EnergyProfile(now.BeginningOfDay().AddDate(0, 0, -30))
if err != nil {
return nil, err
}