-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Collect 15min energy metrics #23185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Collect 15min energy metrics #23185
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes - here's some feedback:
- The Collector.process method only triggers persistence when slotStart.Sub(c.started) == SlotDuration which may skip slots if calls are delayed; consider using >= to catch full slots robustly.
- pvEnergy accumulators are initialized with a fresh clock.New() in Boot instead of the shared site clock, leading to mismatched time bases; use the same clock instance throughout.
- The updateHomeConsumption method is no longer used after inlining AddPower in update; remove it or refactor update to call it to avoid dead code and duplicated error handling.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The Collector.process method only triggers persistence when slotStart.Sub(c.started) == SlotDuration which may skip slots if calls are delayed; consider using >= to catch full slots robustly.
- pvEnergy accumulators are initialized with a fresh clock.New() in Boot instead of the shared site clock, leading to mismatched time bases; use the same clock instance throughout.
- The updateHomeConsumption method is no longer used after inlining AddPower in update; remove it or refactor update to call it to avoid dead code and duplicated error handling.
## Individual Comments
### Comment 1
<location> `core/site.go:257` </location>
<code_context>
// NewSite creates a Site with sane defaults
func NewSite() *Site {
+ clock := clock.New()
+
</code_context>
<issue_to_address>
Multiple clock.New() calls may lead to inconsistent time sources.
Previously, each meterEnergy had its own clock. If you need consistent timekeeping, share the same clock instance across all metrics objects.
Suggested implementation:
```golang
// NewSite creates a Site with sane defaults
func NewSite() *Site {
clock := clock.New()
```
```golang
site.pvMeters = append(site.pvMeters, dev)
// accumulator
site.pvEnergy[ref] = metrics.NewAccumulator(clock)
}
```
</issue_to_address>
### Comment 2
<location> `core/metrics/collector.go:22` </location>
<code_context>
+ }
+}
+
+func (c *Collector) process(fun func()) error {
+ now := c.accu.clock.Now()
+
</code_context>
<issue_to_address>
Consider removing the process closure and flattening slot logic by extracting a helper and inlining steps in each Add* method.
```suggestion
You can remove the closure indirection and flatten the slot‐boundary logic by:
1. Extracting slot rollover into a simple helper (`handleSlot`).
2. Inlining `Now()` + zero‐check + accumulator call in each `Add*` method.
This keeps all behavior but removes the extra abstraction:
```go
func (c *Collector) handleSlot(now time.Time) error {
slot := now.Truncate(SlotDuration)
if slot.After(c.started) {
// exactly one full slot elapsed ⇒ persist
if slot.Sub(c.started) == SlotDuration {
if err := Persist(c.started, c.accu.AccumulatedEnergy()); err != nil {
return err
}
}
c.started = slot
c.accu.Accumulated = 0
}
return nil
}
func (c *Collector) AddEnergy(v float64) error {
now := c.accu.clock.Now()
if c.accu.updated.IsZero() {
c.started = now
}
c.accu.AddEnergy(v)
return c.handleSlot(now)
}
func (c *Collector) AddMeterTotal(v float64) error {
now := c.accu.clock.Now()
if c.accu.updated.IsZero() {
c.started = now
}
c.accu.AddMeterTotal(v)
return c.handleSlot(now)
}
func (c *Collector) AddPower(v float64) error {
now := c.accu.clock.Now()
if c.accu.updated.IsZero() {
c.started = now
}
c.accu.AddPower(v)
return c.handleSlot(now)
}
```
This removes the `process` closure, flattens the nested `if`s, and makes each step explicit.
</issue_to_address>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
This comment was marked as resolved.
This comment was marked as resolved.
I'd suggest keeping it simpler and not introduce a foreign key relation. This is something we'd then have to manually manage. My suggestion would be to introduce a mandatory "type" (or category) and an optional "name" (device). Types: The types are similar to your existing meter types but they dont have to be limited to that (see Name: Advantages:
Disadvantages:
Naming: Lets use |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
Es wäre gut, wenn wir die Diskussion von den Anforderungen statt von der Implementierung her führen würden. Fwiw:
That's already done in the current code.
Mir ist nicht klar, wie wir die verwenden würden bzw. zu welchem Zweck wir Beide Attribute brauchen?
Gleicher Punkt wie drüber- ich habe noch kein klares Bild, wie wir die verwenden würden.
Super use case! Wäre ein weiterer Grund für eine
In einem orthogonalen Schema nicht.
Joins sind perfekt für Geschwindigkeit ;) |
Wollen wir Import/Export separat modellieren oder sind das zwei Seiten einer Medaille? Letzteres hätte den Vorteil, dass der Accumulator das intern handlen könnte (=2 Spalten pos/neg). Anderenfalls brauchst Du immer zwei Updates damit die "andere" Seite weiss, dass sich der Timestamp des letzten Updates geändert hat, auch wenn es keine Wertänderung gibt. |
Ich weiß nicht, was du mit ortohonalem schema meinst, aber die Kosten für diese geloggt Energiemenge würde ich damit direkt verbunden sehen und nicht noch eine allgemein Kostenloggingtabelle aufmachen. |
updated time.Time | ||
posMeter, negMeter *float64 // kWh | ||
Pos float64 `json:"pos"` // kWh | ||
Neg float64 `json:"neg"` // kWh |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming: Pos/Neg ist sehr abstrakt/technisch. Um hier ne saubere Datenbasis zu bekommen werden wir ja auch bei den den Netz-/Batteriezählern das jeweils andere Meter hinzufügen müssen (zumindest mittelfristig). Wie würden wir denn das nennen (energy[In|Out]
, import|export
, energy & energyReverse
)? Ich fänd gut, wenn wir das Naming dann hier bei den Metriken "kompatibel" haben.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming: Pos/Neg ist sehr abstrakt/technisch.
Es ist auch eine Datenbanktabelle! Das Naming entspricht 1:1 der evcc Logik der Energieflüsse. Ich wollte da keine neuen Begriffe erfinden. Import/Export sind mindestens bei der Batterie zweideutig.
Was wir aktuell übrigens (leider) nicht können ist hier mit Zählerständen zu agieren da wir die für 2-Richtungszähler mangels APIs nicht kennen sondern immer nur eine Richtung ausgeprägt haben...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Zwei Einwürfe:
- sollten wir die Erzeugung perspektivisch auf die einzelnen MPPTs aufsplitten um so einzele Strings besser loggen/visualisieren/vergleichen zu können?
- Import/Export wäre das imho das Gescheiteste, bräuchte aber eine Migration der aktuellen "energy-API" bei der wir uns immer auf die für uns primäre Flussrichtung festgelegt haben.
Aktuell erwartetes Energy-Register-Mapping:
- pv: Export
- battery: Export
- grid: Import
- charger: Import
Unterschiedliche Arten von Auswertung. Einmal das klassische "Gibt mir mal ne Jahresübersicht meiner PV Produktion". Dann würde ich einfach nach Kategorie "pv" filtern, aufsummieren und fertig. Egal wie viele WRs an der Produktion beteiligt sind oder waren. Keine joins, einfach super simples group und sum. Ich kann mir aber auch im Breakdown pro Wechselrichter vorstellen. Dann würde ich zusätzlich noch nach Gerät gruppieren. Geht beides auch mit Joins, die Frage ist nur was wir dadurch wirklich gewinnen. |
Schau mal, ist schon umgesetzt. Aktuell:
Für home habe ich eine Gruppe "virtueller" Zähler angedacht, die einen errechneten Meßwert repräsentieren (auch wenn an der Stelle egtl. nur "home" relevant ist). Lässt sich das Schema durchziehen? |
@naltatis hier der erste Aufschlag für 15min Metriken. Daten werden jeweils für 15min gesammelt und dann in die metrics Tabelle geschrieben.
TODO