Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/live-load-quality-flag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"ftw": patch
---

Home use on the energy flow no longer shows "≈" when every device is read, and "% self-powered now" is back. The page marked Home use as an estimate whenever /api/status carried a second, energy-averaged value, which it always did. It now follows Core's `load_quality` alone, and /api/status no longer sends `load_estimate`.

Home use has one value. 0.154.3 was never published as a beta; its change also ships here: Home use is the site meter minus every device that is read, shown when one device is offline (marked "≈"), and blank only when the site meter is missing or stale. The Home Assistant sensor "Load Power (energy average)" announced for 0.154.3 is removed before release; "Load Power" stays.
3 changes: 0 additions & 3 deletions go/internal/api/api_history_quality.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ func (s *Server) qualifyStatusHistory(resp map[string]any, siteMeter string) {
opts = s.deps.HistoryMeasurementSources()
}
r := s.deps.Tel.LiveMeasurementNow(siteMeter, opts)
// Interval metadata remains separate from the current live balance.
opts.UseWatchdogTimeout, opts.UseControlPower = true, true
resp["load_estimate"] = s.deps.Tel.HomeUseEstimate(time.Now(), siteMeter, opts)
var missing state.HistoryMissing
for kind, flag := range map[telemetry.DerType]state.HistoryMissing{telemetry.DerMeter: state.MissingGrid, telemetry.DerPV: state.MissingPV, telemetry.DerBattery: state.MissingBattery, telemetry.DerEV: state.MissingEV, telemetry.DerV2X: state.MissingV2X} {
if !r.Known[kind] {
Expand Down
21 changes: 9 additions & 12 deletions go/internal/api/api_home_estimate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ package api

import (
"encoding/json"
"math"
"testing"
"time"

"github.com/srcfl/ftw/go/internal/state"
"github.com/srcfl/ftw/go/internal/telemetry"
)

func TestStatusHomeEstimateIsSeparateFromInstantaneousPower(t *testing.T) {
// Status sends one Home use: the live balance and its quality. A second,
// energy-averaged number made every client show the live value as an estimate.
func TestStatusSendsOneHomeUse(t *testing.T) {
tel := telemetry.NewStore()
tel.EnsureDriverHealth("site")
base := time.Now().Add(-20 * time.Second)
Expand All @@ -22,17 +23,16 @@ func TestStatusHomeEstimateIsSeparateFromInstantaneousPower(t *testing.T) {
resp := map[string]any{}
for i := 0; i < 3; i++ {
srv.qualifyStatusHistory(resp, "site")
e, ok := resp["load_estimate"].(telemetry.HomeEstimate)
if !ok || e.Watts == nil || math.Abs(*e.Watts-1500) > 1e-6 || e.AgeMS < 15000 || e.AgeMS > 20000 || e.Method != "reported_energy_balance_v3" || e.TimingBasis != "source" || e.EnergyBasis != "power_integral" || e.EnergyWh == nil || e.EndAgeMS < 10000 {
t.Fatal(e)
if _, ok := resp["load_estimate"]; ok {
t.Fatal("status sent a second Home use")
}
if resp["load_w"] != float64(2000) || resp["grid_w"] != float64(2000) || resp["history_quality"] != "complete" {
if resp["load_w"] != float64(2000) || resp["load_quality"] != telemetry.LoadMeasured || resp["grid_w"] != float64(2000) || resp["history_quality"] != "complete" {
t.Fatal(resp)
}
}
tel.DriverHealthMut("site").SetOffline()
srv.qualifyStatusHistory(resp, "site")
if resp["load_w"] != nil || resp["load_estimate"].(telemetry.HomeEstimate).Watts != nil {
if resp["load_w"] != nil || resp["load_quality"] != telemetry.LoadUnknown {
t.Fatal(resp)
}
}
Expand Down Expand Up @@ -60,14 +60,11 @@ func TestStatusKeepsPowerBalanceWhenSessionEnergyCannotFormAnInterval(t *testing
if resp["load_w"] != float64(1234) || resp["history_quality"] != "complete" {
t.Fatal("valid power balance disappeared")
}
if e := resp["load_estimate"].(telemetry.HomeEstimate); e.Watts != nil || e.Reason != "incomplete_source_window" {
t.Fatal("old session was presented as an energy interval")
}
tel.DriverHealthMut("charger").SetOffline()
srv.qualifyStatusHistory(resp, "site")
// The offline charger is left out of live Home use, which says so.
if resp["load_w"] != float64(1234) || resp["load_quality"] != "estimated" || resp["load_estimate"].(telemetry.HomeEstimate).Watts != nil {
t.Fatal("offline charger blanked Home use or became an energy interval")
if resp["load_w"] != float64(1234) || resp["load_quality"] != "estimated" {
t.Fatal("offline charger blanked Home use")
}
tel.RecordDriverSuccess("charger")
tel.Update("charger", telemetry.DerEV, 6000, nil, nil)
Expand Down
11 changes: 1 addition & 10 deletions go/internal/ha/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,10 +492,8 @@ func (b *Bridge) publishDiscovery() {
{"battery_power", "Battery Power", "W", "power", "measurement", b.stateTopic("bat_w")},
{"ev_power", "EV Power", "W", "power", "measurement", b.stateTopic("ev_w")},
{"v2x_power", "V2X Power", "W", "power", "measurement", b.stateTopic("v2x_w")},
// Live balance as the dashboard shows it, and the energy-based mean
// of the last closed window (the quality-assured Home use).
// Live balance as the dashboard shows it.
{"load_power", "Load Power", "W", "power", "measurement", b.stateTopic("load_w")},
{"load_energy_power", "Load Power (energy average)", "W", "power", "measurement", b.stateTopic("load_energy_w")},
{"battery_soc", "Battery SoC", "%", "battery", "measurement", b.stateTopic("bat_soc_pct")},
// Control setpoints
{"grid_target", "Grid Target", "W", "power", "", b.stateTopic("grid_target_w")},
Expand Down Expand Up @@ -959,13 +957,6 @@ func (b *Bridge) publishState() {
// None replaces the previous value with unknown.
b.publishString("load_w", "None")
}
energyOpts := options
energyOpts.UseWatchdogTimeout, energyOpts.UseControlPower = true, true
if e := b.tel.HomeUseEstimate(time.Now(), siteMeter, energyOpts); e.Watts != nil {
b.publishValue("load_energy_w", *e.Watts)
} else {
b.publishString("load_energy_w", "None")
}
b.publishValue("bat_soc_pct", units.PercentFromFraction(avgSoC))
b.publishValue("grid_target_w", gridTarget)
b.publishValue("peak_limit_w", peakLimit)
Expand Down
13 changes: 5 additions & 8 deletions go/internal/ha/live_load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ func TestLoadPublishClearsUnknownAndRecovers(t *testing.T) {
check("0.00")
}

// HA gets two Home use series: the live balance as the dashboard shows it
// (negative included) and the energy-based mean (unknown until a full window).
func TestLoadPublishesDisplayAndEnergySeries(t *testing.T) {
// HA gets one Home use: the live balance as the dashboard shows it,
// negative included.
func TestLoadPublishesOneHomeUse(t *testing.T) {
b, client := liveLoadBridge(nil)
for _, d := range []string{"meter", "battery"} {
b.tel.EnsureDriverHealth(d)
Expand All @@ -134,20 +134,17 @@ func TestLoadPublishesDisplayAndEnergySeries(t *testing.T) {
if got := client.last(t, b.stateTopic("load_w")); got != "-800.00" {
t.Fatalf("display load = %s, want the negative balance as is", got)
}
if got := client.last(t, b.stateTopic("load_energy_w")); got != "None" {
t.Fatalf("energy load without a window = %s, want None", got)
}
b.publishDiscovery()
var display, energy bool
client.mu.Lock()
for topic, payloads := range client.payloads {
for _, p := range payloads {
display = display || strings.Contains(topic, "load_power/config") && strings.Contains(p, b.stateTopic("load_w"))
energy = energy || strings.Contains(topic, "load_energy_power/config") && strings.Contains(p, b.stateTopic("load_energy_w"))
energy = energy || strings.Contains(topic, "load_energy_power/config")
}
}
client.mu.Unlock()
if !display || !energy {
if !display || energy {
t.Fatalf("discovery: display=%t energy=%t", display, energy)
}
}
78 changes: 0 additions & 78 deletions go/internal/telemetry/home_cloud_timing_test.go

This file was deleted.

101 changes: 0 additions & 101 deletions go/internal/telemetry/home_energy.go

This file was deleted.

Loading
Loading