From fa6f0f6c9d6af48cb815823544491ee801ee7d8c Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Thu, 22 Oct 2026 07:19:01 +0200 Subject: [PATCH 1/3] fix(feedback): a charger that draws against FTW's order is not following A Core limit returned "limited" before the charger's readings were compared with the order. In 0.155.0 an Easee Cloud charger went on drawing 3.1 kW after FTW cut it to 0 W for the main fuse, and the verdict stayed "Limited by the main fuse". A limit explains a lower order, not a draw that differs from it. A settled mismatch now goes to the normal comparison: drawing above the order is power_while_idle or power_above_target, a warning that becomes an alarm after ten more minutes; drawing below it is power_below_target, a warning, as under a plan order. Command evidence kept the last eight orders. Core sends a charger its order on every tick, so the two-minute response window shrank to 16 to 40 s and a slow charger was judged on an order it could not have applied yet. A repeat of the last order is no longer stored again. Co-Authored-By: Claude Opus 5.5 --- .changeset/ev-charger-not-following.md | 5 + go/internal/api/api_control_feedback.go | 28 +++- go/internal/api/api_ev_not_following_test.go | 137 ++++++++++++++++++ go/internal/telemetry/command_evidence.go | 4 +- .../telemetry/command_evidence_test.go | 24 +++ 5 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 .changeset/ev-charger-not-following.md create mode 100644 go/internal/api/api_ev_not_following_test.go diff --git a/.changeset/ev-charger-not-following.md b/.changeset/ev-charger-not-following.md new file mode 100644 index 000000000..11913ecf0 --- /dev/null +++ b/.changeset/ev-charger-not-following.md @@ -0,0 +1,5 @@ +--- +"ftw": patch +--- + +A charger that keeps charging after FTW tells it to stop or slow down now shows as Not following, not Limited. In 0.155.0 an Easee Cloud charger that went on drawing 3.1 kW after FTW cut it to 0 W for the main fuse showed "Limited by the main fuse" for as long as it drew. A limit explains why FTW ordered less; it does not explain a charger that takes more. FTW now warns once the charger has drawn more than its order for four minutes, and raises an alarm ten minutes later. A car that takes less than a limited order is judged as it is under a plan order. Sending the same order on every tick no longer pushes the earlier order out of the two-minute response window, so a slow cloud charger is not judged on an order it could not yet have applied. diff --git a/go/internal/api/api_control_feedback.go b/go/internal/api/api_control_feedback.go index ca84538ed..71371c4ff 100644 --- a/go/internal/api/api_control_feedback.go +++ b/go/internal/api/api_control_feedback.go @@ -37,6 +37,7 @@ type ControlFeedback struct { State string `json:"-"` VerificationTier *int `json:"-"` VerificationLost bool `json:"-"` + Escalated bool `json:"-"` // not following long enough to be an alarm Response string `json:"-"` SiteBeforeW *float64 `json:"-"` SiteAfterW *float64 `json:"-"` @@ -360,6 +361,9 @@ func setControlStatus(f *ControlFeedback) { if status == "following" && (f.VerificationTier == nil || *f.VerificationTier < 1) { status = "waiting" } + if status == "not_following" && f.Escalated { + severity = "alarm" + } f.Status, f.Severity = status, severity f.Evidence = "none" if f.VerificationTier != nil { @@ -390,6 +394,15 @@ func controlVerificationLost(f ControlFeedback, cmd telemetry.CommandEvidence, n return !cmd.Since.IsZero() && now.Sub(cmd.Since) >= grace && now.Sub(cmd.LastMeasuredAt) >= grace } +// evMismatchWait is how long a charger's fresh readings must disagree with +// the orders in force before it is not following. evOverdrawAlarm is how +// much longer one that draws more than ordered may go on before the warning +// becomes an alarm. +const ( + evMismatchWait = 2 * time.Minute + evOverdrawAlarm = 10 * time.Minute +) + func classifyControlFeedback(f *ControlFeedback, cmd telemetry.CommandEvidence, commanded, fresh bool, coreReason string, connected *bool, now time.Time) { set := func(state, reason, severity string) { f.State, f.Reason, f.Severity = state, reason, severity } if !fresh { @@ -419,8 +432,8 @@ func classifyControlFeedback(f *ControlFeedback, cmd telemetry.CommandEvidence, } // A positive clamp explains the lower offer, not prolonged zero delivery. // Keep known safety pauses and unconfirmed/stale commands authoritative. - evMismatchObserved := !cmd.PowerMismatchSince.IsZero() && cmd.LastObservation.Sub(cmd.PowerMismatchSince) >= 2*time.Minute - noPowerObserved := evMismatchObserved || !cmd.NoPowerSince.IsZero() && cmd.NoPowerAt.Sub(cmd.NoPowerSince) >= 2*time.Minute + evMismatchObserved := !cmd.PowerMismatchSince.IsZero() && cmd.LastObservation.Sub(cmd.PowerMismatchSince) >= evMismatchWait + noPowerObserved := evMismatchObserved || !cmd.NoPowerSince.IsZero() && cmd.NoPowerAt.Sub(cmd.NoPowerSince) >= evMismatchWait if f.Kind == "ev" && commanded && cmd.Result == "accepted" && coreReason != "site_meter_stale" && coreReason != "site_phase_currents_stale" && coreReason != "fuse_cooldown" && f.SentW != nil && *f.SentW > 100 && f.ActualW != nil && math.Abs(*f.ActualW) < 100 && @@ -432,7 +445,11 @@ func classifyControlFeedback(f *ControlFeedback, cmd telemetry.CommandEvidence, set("limited", "device_limit", "warning") return } - if coreReason == "site_meter_stale" || coreReason == "site_phase_currents_stale" || coreReason == "fuse_cooldown" || coreReason == "fuse_limit" || coreReason == "import_limit" || coreReason == "charger_limit" { + // A limit explains a lower order, not a draw that differs from it. A + // charger whose fresh readings have disagreed with the order past its + // response time is judged below, as under any other order. + limit := coreReason == "site_meter_stale" || coreReason == "site_phase_currents_stale" || coreReason == "fuse_cooldown" || coreReason == "fuse_limit" || coreReason == "import_limit" || coreReason == "charger_limit" + if limit && !(f.Kind == "ev" && evMismatchObserved) { set("limited", coreReason, "warning") return } @@ -501,6 +518,11 @@ func classifyControlFeedback(f *ControlFeedback, cmd telemetry.CommandEvidence, reason = "power_below_target" } set("warning", reason, "warning") + // FTW cannot stop a charger that draws more than it is told. A car + // that takes less only charges slower. + if f.Kind == "ev" && (reason == "power_while_idle" || reason == "power_above_target") { + f.Escalated = cmd.LastObservation.Sub(cmd.PowerMismatchSince) >= evMismatchWait+evOverdrawAlarm + } return } if coreReason == "core_limit" { diff --git a/go/internal/api/api_ev_not_following_test.go b/go/internal/api/api_ev_not_following_test.go new file mode 100644 index 000000000..87e16d2b8 --- /dev/null +++ b/go/internal/api/api_ev_not_following_test.go @@ -0,0 +1,137 @@ +package api + +import ( + "fmt" + "testing" + "testing/synctest" + "time" + + "github.com/srcfl/ftw/go/internal/loadpoint" + "github.com/srcfl/ftw/go/internal/telemetry" +) + +// evTrace is an Easee Cloud charger on a loadpoint: a command and a poll +// every five seconds, as Core and the driver send them. The cloud stamps a +// power value when it changes and each poll confirms it (easee_cloud.lua). +type evTrace struct { + tel *telemetry.Store + mgr *loadpoint.Manager + srv *Server + changedAt time.Time + drawW float64 +} + +func newEVTrace(drawW float64) *evTrace { + tel := telemetry.NewStore() + tel.DriverHealthMut("easee") + mgr := loadpoint.NewManager() + mgr.Load([]loadpoint.Config{{ID: "garage", DriverName: "easee", MinChargeW: 4140, MaxChargeW: 11040}}) + return &evTrace{tel: tel, mgr: mgr, srv: New(&Deps{Tel: tel, Loadpoints: mgr}), changedAt: time.Now(), drawW: drawW} +} + +// tick orders w for reason, polls the charger and waits five seconds. +func (e *evTrace) tick(w float64, reason string) { + c := e.tel.BeginCommand("easee", []byte(fmt.Sprintf(`{"action":"ev_set_current","power_w":%g}`, w)), time.Now()) + e.tel.CompleteCommand(c, "accepted") + e.mgr.SetCommanded("garage", w, reason) + e.tel.Update("easee", telemetry.DerEV, e.drawW, nil, []byte(fmt.Sprintf( + `{"connected":true,"control_power_observed_at":%q,"control_power_confirmed":true,"power_max_age_s":180}`, + e.changedAt.Format(time.RFC3339Nano)))) + e.mgr.Observe("garage", true, e.drawW, 0, true) + time.Sleep(5 * time.Second) +} + +func (e *evTrace) verdict(t *testing.T) ControlFeedback { + t.Helper() + for _, f := range e.srv.controlFeedback(time.Now()) { + if f.Kind == "ev" { + return f + } + } + t.Fatal("no EV verdict") + return ControlFeedback{} +} + +// Field report, 22 Oct 2026, v0.155.0-beta.1, Easee Cloud: the charger drew +// 3.13 kW, Core cut its order to 0 W for the main fuse at 06:32 and the +// charger kept drawing. The verdict said "Limited by the main fuse" for as +// long as it drew. A limit explains a lower order, never a draw above it. +func TestChargerDrawingAgainstAFuseStopIsNotFollowing(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + e := newEVTrace(3130) + for range 60 * 60 / 5 { + e.tick(7590, "plan") + } + cut := time.Now() + var first, alarm time.Duration + for range 20 * 60 / 5 { + e.tick(0, "fuse_limit") + f, since := e.verdict(t), time.Since(cut) + if f.Status == "not_following" && first == 0 { + first = since + if f.Reason != "power_while_idle" || f.Severity != "warning" || f.Evidence != "measured" { + t.Fatalf("first not following at %v: %s/%s/%s", since, f.Reason, f.Severity, f.Evidence) + } + } + if f.Severity == "alarm" && alarm == 0 { + alarm = since + if f.Status != "not_following" { + t.Fatalf("alarm at %v is %s/%s", since, f.Status, f.Reason) + } + } + if first != 0 && f.Status != "not_following" { + t.Fatalf("at %v the verdict went back to %s/%s while the charger still draws", since, f.Status, f.Reason) + } + } + t.Logf("not following from %v after the cut, alarm from %v", first, alarm) + // Easee declares 180 s for its power to show; Core waits two minutes + // for an order to take hold and two more of measured disagreement. + if first < 3*time.Minute || first > 5*time.Minute { + t.Fatalf("not following after %v of drawing against a stop, want within 3-5 min", first) + } + if alarm < first+10*time.Minute || alarm > first+11*time.Minute { + t.Fatalf("alarm after %v (not following from %v), want ten minutes later", alarm, first) + } + }) +} + +// A charger that obeys the cut within its response time stays Limited. +func TestChargerFollowingAFuseStopStaysLimited(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + e := newEVTrace(7500) + for range 10 * 60 / 5 { + e.tick(7590, "plan") + } + cut := time.Now() + for range 15 * 60 / 5 { + if time.Since(cut) >= 100*time.Second && e.drawW > 0 { + e.drawW, e.changedAt = 0, time.Now() + } + e.tick(0, "fuse_limit") + if f := e.verdict(t); f.Status != "limited" || f.Reason != "fuse_limit" { + t.Fatalf("at %v a charger that stopped reads %s/%s", time.Since(cut), f.Status, f.Reason) + } + } + }) +} + +// A car that takes less than a fuse-limited order is not the fuse's doing. +// It reads as it would under a plan order: not following, a warning. +func TestCarBelowAFuseLimitedOrderIsNotFollowing(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + e := newEVTrace(3130) + for range 10 * 60 / 5 { + e.tick(8280, "fuse_limit") + } + f := e.verdict(t) + if f.Status != "not_following" || f.Reason != "power_below_target" || f.Severity != "warning" { + t.Fatalf("3.1 kW against an 8.3 kW fuse-limited order reads %s/%s/%s", f.Status, f.Reason, f.Severity) + } + for range 30 * 60 / 5 { + e.tick(8280, "fuse_limit") + } + if f := e.verdict(t); f.Severity != "warning" { + t.Fatalf("a car drawing less than offered escalated to %s", f.Severity) + } + }) +} diff --git a/go/internal/telemetry/command_evidence.go b/go/internal/telemetry/command_evidence.go index 1216e6851..e46df9da4 100644 --- a/go/internal/telemetry/command_evidence.go +++ b/go/internal/telemetry/command_evidence.go @@ -155,7 +155,9 @@ func (s *Store) BeginCommand(driver string, payload []byte, now time.Time) Comma c.PowerObservedSince, c.LastObservation, c.LastGapW = old.PowerObservedSince, old.LastObservation, old.LastGapW } } - if c.PowerW != nil { + // A repeat of the last order is the same order: keeping it would push + // the earlier ones out of Recent long before their response time ends. + if n := len(c.Recent); c.PowerW != nil && (n == 0 || c.Recent[n-1].PowerW != *c.PowerW) { c.Recent = append(c.Recent, CommandPoint{PowerW: *c.PowerW, At: now}) } if len(c.Recent) > 8 { diff --git a/go/internal/telemetry/command_evidence_test.go b/go/internal/telemetry/command_evidence_test.go index 1e2c7410e..d45adce7e 100644 --- a/go/internal/telemetry/command_evidence_test.go +++ b/go/internal/telemetry/command_evidence_test.go @@ -178,3 +178,27 @@ func TestLastMeasuredResponseSurvivesNewCommandButNotRelease(t *testing.T) { t.Fatal("intentional release kept loss alarm armed") } } + +// Core sends a charger its order on every tick. A repeated order must not +// push the one before it out of the response window: a cloud charger may +// still show it for two minutes. +func TestRepeatedOrderKeepsEarlierOrderInForce(t *testing.T) { + s := NewStore() + start := time.Now() + send := func(w string, at time.Time) { + c := s.BeginCommand("easee", []byte(`{"action":"ev_set_current","power_w":`+w+`}`), at) + s.CompleteCommand(c, "accepted") + } + send("7590", start) + for i := range 30 { + send("0", start.Add(time.Duration(10+2*i)*time.Second)) + } + at := start.Add(70 * time.Second) + s.mu.Lock() + s.observeCommand("easee", DerEV, 3130, json.RawMessage(`{}`), at) + s.mu.Unlock() + got, _ := s.CommandEvidence("easee", "ev") + if got.LastGapW == nil || *got.LastGapW != 0 || !got.PowerMismatchSince.IsZero() { + t.Fatalf("1 min after a cut the earlier 7590 W order was out of force: gap %v, mismatch since %v", got.LastGapW, got.PowerMismatchSince) + } +} From 468424df09701b307397b8e5e6b82faafd089ba1 Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Thu, 22 Oct 2026 08:06:21 +0200 Subject: [PATCH 2/3] fix(feedback): the overdraw alarm counts only time drawing above the order PowerMismatchSince covers both directions. A car that drew 3 kW against a 7 kW order for 13 minutes and then rose to 9 kW was an alarm on the first reading above the order. Command evidence now keeps the start of the run above every command in force (PowerOverSince), and the alarm waits two minutes plus ten from there. Tests: a cut obeyed at 175 s stays Limited; under then over warns at once and alarms after 12 minutes of overdraw. Co-Authored-By: Claude Opus 5.5 --- go/internal/api/api_control_feedback.go | 2 +- go/internal/api/api_ev_not_following_test.go | 55 ++++++++++++++++---- go/internal/telemetry/command_evidence.go | 20 ++++--- 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/go/internal/api/api_control_feedback.go b/go/internal/api/api_control_feedback.go index 71371c4ff..f63a24a10 100644 --- a/go/internal/api/api_control_feedback.go +++ b/go/internal/api/api_control_feedback.go @@ -521,7 +521,7 @@ func classifyControlFeedback(f *ControlFeedback, cmd telemetry.CommandEvidence, // FTW cannot stop a charger that draws more than it is told. A car // that takes less only charges slower. if f.Kind == "ev" && (reason == "power_while_idle" || reason == "power_above_target") { - f.Escalated = cmd.LastObservation.Sub(cmd.PowerMismatchSince) >= evMismatchWait+evOverdrawAlarm + f.Escalated = !cmd.PowerOverSince.IsZero() && cmd.LastObservation.Sub(cmd.PowerOverSince) >= evMismatchWait+evOverdrawAlarm } return } diff --git a/go/internal/api/api_ev_not_following_test.go b/go/internal/api/api_ev_not_following_test.go index 87e16d2b8..2592f6100 100644 --- a/go/internal/api/api_ev_not_following_test.go +++ b/go/internal/api/api_ev_not_following_test.go @@ -95,23 +95,58 @@ func TestChargerDrawingAgainstAFuseStopIsNotFollowing(t *testing.T) { }) } -// A charger that obeys the cut within its response time stays Limited. +// A charger that obeys the cut within its response time stays Limited, +// including Easee Cloud at the end of its declared 180 s. func TestChargerFollowingAFuseStopStaysLimited(t *testing.T) { + for _, after := range []time.Duration{100 * time.Second, 175 * time.Second} { + synctest.Test(t, func(t *testing.T) { + e := newEVTrace(7500) + for range 10 * 60 / 5 { + e.tick(7590, "plan") + } + cut := time.Now() + for range 15 * 60 / 5 { + if time.Since(cut) >= after && e.drawW > 0 { + e.drawW, e.changedAt = 0, time.Now() + } + e.tick(0, "fuse_limit") + if f := e.verdict(t); f.Status != "limited" || f.Reason != "fuse_limit" || f.Severity == "alarm" { + t.Fatalf("stopped after %v: at %v the verdict is %s/%s/%s", after, time.Since(cut), f.Status, f.Reason, f.Severity) + } + } + }) + } +} + +// Time spent drawing below the order is not time drawing above it. After +// 13 minutes at 3 kW against a 7 kW order, a car that rises to 9 kW is +// drawing more than it is told: a warning now, an alarm only ten minutes +// after that wait has passed for the overdraw itself. +func TestOverdrawAfterAShortfallAlarmsOnItsOwnTime(t *testing.T) { synctest.Test(t, func(t *testing.T) { - e := newEVTrace(7500) - for range 10 * 60 / 5 { - e.tick(7590, "plan") + e := newEVTrace(3000) + for range 13 * 60 / 5 { + e.tick(7000, "plan") } - cut := time.Now() + if f := e.verdict(t); f.Reason != "power_below_target" || f.Severity != "warning" { + t.Fatalf("3 kW against 7 kW reads %s/%s", f.Reason, f.Severity) + } + e.drawW, e.changedAt = 9000, time.Now() + over := time.Now() + var alarm time.Duration for range 15 * 60 / 5 { - if time.Since(cut) >= 100*time.Second && e.drawW > 0 { - e.drawW, e.changedAt = 0, time.Now() + e.tick(7000, "plan") + f := e.verdict(t) + if f.Reason != "power_above_target" { + t.Fatalf("at %v 9 kW against 7 kW reads %s", time.Since(over), f.Reason) } - e.tick(0, "fuse_limit") - if f := e.verdict(t); f.Status != "limited" || f.Reason != "fuse_limit" { - t.Fatalf("at %v a charger that stopped reads %s/%s", time.Since(cut), f.Status, f.Reason) + if f.Severity == "alarm" && alarm == 0 { + alarm = time.Since(over) } } + if alarm < 12*time.Minute || alarm > 13*time.Minute { + t.Fatalf("overdraw alarmed after %v, want two minutes plus ten", alarm) + } }) } diff --git a/go/internal/telemetry/command_evidence.go b/go/internal/telemetry/command_evidence.go index e46df9da4..528205359 100644 --- a/go/internal/telemetry/command_evidence.go +++ b/go/internal/telemetry/command_evidence.go @@ -26,10 +26,13 @@ type CommandEvidence struct { Result string ReadbackMismatchSince time.Time PowerMismatchSince time.Time - PowerMatchSince time.Time - PowerObservedSince time.Time - LastObservation time.Time - LastMeasuredAt time.Time + // PowerOverSince is the start of the current run of readings above + // every command in force, a part of PowerMismatchSince's run. + PowerOverSince time.Time + PowerMatchSince time.Time + PowerObservedSince time.Time + LastObservation time.Time + LastMeasuredAt time.Time // NoPower is an observed EV stop during uninterrupted positive orders. // Retuning the current cannot restart this clock; a pause, failed command, // missing measurement or observed power ends it. @@ -152,6 +155,7 @@ func (s *Store) BeginCommand(driver string, payload []byte, now time.Time) Comma if !materialStep(old.PowerW, c.PowerW) { c.Since, c.Baseline, c.StepAfter, c.StepAfterAt = old.Since, old.Baseline, old.StepAfter, old.StepAfterAt c.ReadbackMismatchSince, c.PowerMismatchSince, c.PowerMatchSince = old.ReadbackMismatchSince, old.PowerMismatchSince, old.PowerMatchSince + c.PowerOverSince = old.PowerOverSince c.PowerObservedSince, c.LastObservation, c.LastGapW = old.PowerObservedSince, old.LastObservation, old.LastGapW } } @@ -294,6 +298,7 @@ func (s *Store) endCommandControlAt(driver string, failed bool, now time.Time) { c.Result = "released" } c.ReadbackMismatchSince, c.PowerMismatchSince, c.PowerMatchSince = time.Time{}, time.Time{}, time.Time{} + c.PowerOverSince = time.Time{} c.PowerObservedSince, c.LastObservation, c.LastGapW = time.Time{}, time.Time{}, nil c.Recent, c.StepAfter, c.StepAfterAt = nil, nil, time.Time{} if !failed { @@ -377,6 +382,7 @@ func (s *Store) observeCommand(driver string, kind DerType, power float64, data if json.Unmarshal(data, &d) != nil { c.NoPowerSince, c.NoPowerAt = time.Time{}, time.Time{} c.ReadbackMismatchSince, c.PowerMismatchSince, c.PowerMatchSince = time.Time{}, time.Time{}, time.Time{} + c.PowerOverSince = time.Time{} c.PowerObservedSince, c.LastObservation = time.Time{}, time.Time{} s.commands[k] = c return @@ -384,7 +390,7 @@ func (s *Store) observeCommand(driver string, kind DerType, power float64, data c.MaxAge = DeclaredPowerMaxAge(data) if now.Sub(c.LastObservation) > max(time.Minute, c.MaxAge) { c.ReadbackMismatchSince, c.PowerMismatchSince, c.PowerMatchSince = time.Time{}, time.Time{}, time.Time{} - c.PowerObservedSince = time.Time{} + c.PowerOverSince, c.PowerObservedSince = time.Time{}, time.Time{} } mark := func(since *time.Time, mismatch bool, at time.Time) { if !mismatch { @@ -425,7 +431,7 @@ func (s *Store) observeCommand(driver string, kind DerType, power float64, data // delivery of one source sample also cannot extend a measured time window. if !fresh || !finite(power) || observedAt.Before(c.Since) { c.PowerMismatchSince, c.PowerMatchSince, c.LastObservation = time.Time{}, time.Time{}, time.Time{} - c.PowerObservedSince, c.LastGapW = time.Time{}, nil + c.PowerOverSince, c.PowerObservedSince, c.LastGapW = time.Time{}, time.Time{}, nil s.commands[k] = c return } @@ -443,6 +449,8 @@ func (s *Store) observeCommand(driver string, kind DerType, power float64, data tolerance := ControlToleranceW(*c.PowerW) mismatch := !finite(gap) || gap > tolerance mark(&c.PowerMismatchSince, mismatch, observedAt) + // Above the latest command and off every one in force is above them all. + mark(&c.PowerOverSince, mismatch && kind != DerPV && power > *c.PowerW, observedAt) follows := !mismatch if kind == DerPV { // Weak sun alone cannot prove that a ceiling took effect. From f19b3b0918881e044d32412e4ec277a3a3d42f5e Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Thu, 22 Oct 2026 11:12:11 +0200 Subject: [PATCH 3/3] test(control): delete the field rows #1795 turns green On master with #1799: all 28 rows labelled #1795 pass, and 16 D rows whose only failure was the verdict now read not following. No row fails that did not before. Co-Authored-By: Claude Opus 5.5 --- .../control/testdata/sitesim_field_known.txt | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/go/internal/control/testdata/sitesim_field_known.txt b/go/internal/control/testdata/sitesim_field_known.txt index c71ce0c70..409d68ae6 100644 --- a/go/internal/control/testdata/sitesim_field_known.txt +++ b/go/internal/control/testdata/sitesim_field_known.txt @@ -25,26 +25,18 @@ # loadpoint, and on a charger whose table stops below 4,140 W # Core floors the order to 0 W (fuse_limit). help1022/20A/auto/own/cloud60s/car1p/2A/chargenow over-cut netting -help1022/20A/auto/own/cloud60s/car1p/2A/chargenow verdict #1795 help1022/20A/auto/own/cloud60s/car1p/2A/planned energy D -help1022/20A/auto/own/cloud60s/car1p/2A/planned verdict D help1022/20A/auto/own/cloud60s/car3p/2A/chargenow over-cut netting help1022/20A/auto/own/cloud60s/car3p/2A/pause over-cut netting help1022/20A/auto/own/easee120s/car1p/2A/chargenow over-cut netting -help1022/20A/auto/own/easee120s/car1p/2A/chargenow verdict #1795 help1022/20A/auto/own/easee120s/car1p/2A/planned energy A -help1022/20A/auto/own/easee120s/car1p/2A/planned verdict D help1022/20A/auto/own/easee120s/car3p/2A/chargenow over-cut netting help1022/20A/auto/own/easee120s/car3p/2A/pause over-cut netting help1022/20A/auto/own/easee40s/car1p/2A/chargenow over-cut netting -help1022/20A/auto/own/easee40s/car1p/2A/chargenow verdict #1795 help1022/20A/auto/own/easee40s/car1p/2A/planned energy D -help1022/20A/auto/own/easee40s/car1p/2A/planned verdict D help1022/20A/auto/own/easee40s/car3p/2A/chargenow over-cut netting help1022/20A/auto/own/easee40s/car3p/2A/pause over-cut netting -help1022/20A/auto/own/fast2s/car1p/2A/chargenow verdict #1795 help1022/20A/auto/own/fast2s/car1p/2A/planned energy D -help1022/20A/auto/own/fast2s/car1p/2A/planned verdict D homebox/16A/empty/none/cloud60s/car3p/balanced/chargenow over-cut netting homebox/16A/empty/none/cloud60s/car3p/balanced/pause limit netting homebox/16A/empty/none/easee120s/car3p/balanced/chargenow over-cut netting @@ -66,77 +58,50 @@ sweep/16A/1p/1p/easee40s/car3p/balanced/chargenow over-cut netting sweep/16A/1p/1p/easee40s/car3p/balanced/planned over-cut netting sweep/16A/1p/none/easee40s/car3p/balanced/chargenow over-cut netting sweep/16A/1p/none/easee40s/car3p/balanced/planned over-cut netting -sweep/16A/3p/3p/easee40s/car1p/L1/chargenow verdict #1795 sweep/16A/3p/3p/easee40s/car1p/L1/planned energy D sweep/16A/3p/3p/easee40s/car1p/balanced/chargenow over-cut netting -sweep/16A/3p/3p/easee40s/car1p/balanced/chargenow verdict #1795 sweep/16A/3p/3p/easee40s/car1p/balanced/planned energy D sweep/16A/3p/3p/easee40s/car1p/balanced/planned over-cut netting -sweep/16A/3p/3p/easee40s/car1p/balanced/planned verdict #1795 sweep/16A/3p/3p/easee40s/car3p/balanced/chargenow over-cut netting -sweep/16A/3p/mixed/easee40s/car1p/L1/chargenow verdict #1795 sweep/16A/3p/mixed/easee40s/car1p/L1/planned energy D sweep/16A/3p/mixed/easee40s/car1p/balanced/chargenow over-cut netting -sweep/16A/3p/mixed/easee40s/car1p/balanced/chargenow verdict #1795 sweep/16A/3p/mixed/easee40s/car1p/balanced/planned energy D sweep/16A/3p/mixed/easee40s/car1p/balanced/planned over-cut netting -sweep/16A/3p/mixed/easee40s/car1p/balanced/planned verdict #1795 sweep/16A/3p/mixed/easee40s/car3p/balanced/chargenow over-cut netting -sweep/16A/3p/none/easee40s/car1p/L1/chargenow verdict #1795 sweep/16A/3p/none/easee40s/car1p/L1/planned energy D sweep/16A/3p/none/easee40s/car1p/balanced/chargenow over-cut netting -sweep/16A/3p/none/easee40s/car1p/balanced/chargenow verdict #1795 sweep/16A/3p/none/easee40s/car1p/balanced/planned energy D sweep/16A/3p/none/easee40s/car1p/balanced/planned over-cut netting -sweep/16A/3p/none/easee40s/car1p/balanced/planned verdict #1795 sweep/16A/3p/none/easee40s/car3p/balanced/chargenow over-cut netting -sweep/16A/auto/3p/easee40s/car1p/L1/chargenow verdict #1795 sweep/16A/auto/3p/easee40s/car1p/L1/planned energy D sweep/16A/auto/3p/easee40s/car1p/balanced/chargenow over-cut netting -sweep/16A/auto/3p/easee40s/car1p/balanced/chargenow verdict #1795 sweep/16A/auto/3p/easee40s/car1p/balanced/planned energy D sweep/16A/auto/3p/easee40s/car1p/balanced/planned over-cut D -sweep/16A/auto/3p/easee40s/car1p/balanced/planned verdict D sweep/16A/auto/3p/easee40s/car3p/balanced/chargenow over-cut netting -sweep/16A/auto/mixed/easee40s/car1p/L1/chargenow verdict #1795 sweep/16A/auto/mixed/easee40s/car1p/L1/planned energy D sweep/16A/auto/mixed/easee40s/car1p/balanced/chargenow over-cut netting -sweep/16A/auto/mixed/easee40s/car1p/balanced/chargenow verdict #1795 sweep/16A/auto/mixed/easee40s/car1p/balanced/planned energy D sweep/16A/auto/mixed/easee40s/car1p/balanced/planned over-cut netting -sweep/16A/auto/mixed/easee40s/car1p/balanced/planned verdict D sweep/16A/auto/mixed/easee40s/car3p/balanced/chargenow over-cut netting -sweep/16A/auto/none/easee40s/car1p/L1/chargenow verdict #1795 sweep/16A/auto/none/easee40s/car1p/L1/planned energy D sweep/16A/auto/none/easee40s/car1p/balanced/chargenow over-cut netting -sweep/16A/auto/none/easee40s/car1p/balanced/chargenow verdict #1795 sweep/16A/auto/none/easee40s/car1p/balanced/planned energy D sweep/16A/auto/none/easee40s/car1p/balanced/planned over-cut D -sweep/16A/auto/none/easee40s/car1p/balanced/planned verdict D sweep/16A/auto/none/easee40s/car3p/balanced/chargenow over-cut netting -sweep/16A/empty/3p/easee40s/car1p/L1/chargenow verdict #1795 sweep/16A/empty/3p/easee40s/car1p/L1/planned energy D sweep/16A/empty/3p/easee40s/car1p/balanced/chargenow over-cut netting -sweep/16A/empty/3p/easee40s/car1p/balanced/chargenow verdict #1795 sweep/16A/empty/3p/easee40s/car1p/balanced/planned energy D sweep/16A/empty/3p/easee40s/car1p/balanced/planned over-cut D -sweep/16A/empty/3p/easee40s/car1p/balanced/planned verdict D sweep/16A/empty/3p/easee40s/car3p/balanced/chargenow over-cut netting -sweep/16A/empty/mixed/easee40s/car1p/L1/chargenow verdict #1795 sweep/16A/empty/mixed/easee40s/car1p/L1/planned energy D sweep/16A/empty/mixed/easee40s/car1p/balanced/chargenow over-cut netting -sweep/16A/empty/mixed/easee40s/car1p/balanced/chargenow verdict #1795 sweep/16A/empty/mixed/easee40s/car1p/balanced/planned energy D sweep/16A/empty/mixed/easee40s/car1p/balanced/planned over-cut netting -sweep/16A/empty/mixed/easee40s/car1p/balanced/planned verdict D sweep/16A/empty/mixed/easee40s/car3p/balanced/chargenow over-cut netting -sweep/16A/empty/none/easee40s/car1p/L1/chargenow verdict #1795 sweep/16A/empty/none/easee40s/car1p/L1/planned energy D sweep/16A/empty/none/easee40s/car1p/balanced/chargenow over-cut netting -sweep/16A/empty/none/easee40s/car1p/balanced/chargenow verdict #1795 sweep/16A/empty/none/easee40s/car1p/balanced/planned energy D sweep/16A/empty/none/easee40s/car1p/balanced/planned over-cut D -sweep/16A/empty/none/easee40s/car1p/balanced/planned verdict D sweep/25A/1p/1p/easee40s/car3p/L1/chargenow over-cut netting sweep/25A/1p/1p/easee40s/car3p/L1/planned over-cut netting sweep/25A/1p/1p/easee40s/car3p/balanced/chargenow over-cut netting @@ -145,63 +110,54 @@ sweep/25A/1p/none/easee40s/car3p/L1/planned over-cut netting sweep/25A/1p/none/easee40s/car3p/balanced/chargenow over-cut netting sweep/25A/3p/3p/easee40s/car1p/L1/chargenow over-cut netting sweep/25A/3p/3p/easee40s/car1p/L1/planned energy D -sweep/25A/3p/3p/easee40s/car1p/L1/planned verdict #1795 sweep/25A/3p/3p/easee40s/car1p/balanced/chargenow over-cut netting sweep/25A/3p/3p/easee40s/car1p/balanced/planned energy D sweep/25A/3p/3p/easee40s/car3p/L1/chargenow over-cut netting sweep/25A/3p/3p/easee40s/car3p/balanced/chargenow over-cut netting sweep/25A/3p/mixed/easee40s/car1p/L1/chargenow over-cut netting sweep/25A/3p/mixed/easee40s/car1p/L1/planned energy D -sweep/25A/3p/mixed/easee40s/car1p/L1/planned verdict #1795 sweep/25A/3p/mixed/easee40s/car1p/balanced/chargenow over-cut netting sweep/25A/3p/mixed/easee40s/car1p/balanced/planned energy D sweep/25A/3p/mixed/easee40s/car3p/L1/chargenow over-cut netting sweep/25A/3p/mixed/easee40s/car3p/balanced/chargenow over-cut netting sweep/25A/3p/none/easee40s/car1p/L1/chargenow over-cut netting sweep/25A/3p/none/easee40s/car1p/L1/planned energy D -sweep/25A/3p/none/easee40s/car1p/L1/planned verdict #1795 sweep/25A/3p/none/easee40s/car1p/balanced/chargenow over-cut netting sweep/25A/3p/none/easee40s/car1p/balanced/planned energy D sweep/25A/3p/none/easee40s/car3p/L1/chargenow over-cut netting sweep/25A/3p/none/easee40s/car3p/balanced/chargenow over-cut netting sweep/25A/auto/3p/easee40s/car1p/L1/chargenow over-cut netting sweep/25A/auto/3p/easee40s/car1p/L1/planned energy D -sweep/25A/auto/3p/easee40s/car1p/L1/planned verdict D sweep/25A/auto/3p/easee40s/car1p/balanced/chargenow over-cut netting sweep/25A/auto/3p/easee40s/car1p/balanced/planned energy D sweep/25A/auto/3p/easee40s/car3p/L1/chargenow over-cut netting sweep/25A/auto/3p/easee40s/car3p/balanced/chargenow over-cut netting sweep/25A/auto/mixed/easee40s/car1p/L1/chargenow over-cut netting sweep/25A/auto/mixed/easee40s/car1p/L1/planned energy D -sweep/25A/auto/mixed/easee40s/car1p/L1/planned verdict D sweep/25A/auto/mixed/easee40s/car1p/balanced/chargenow over-cut netting sweep/25A/auto/mixed/easee40s/car1p/balanced/planned energy D sweep/25A/auto/mixed/easee40s/car3p/L1/chargenow over-cut netting sweep/25A/auto/mixed/easee40s/car3p/balanced/chargenow over-cut netting sweep/25A/auto/none/easee40s/car1p/L1/chargenow over-cut netting sweep/25A/auto/none/easee40s/car1p/L1/planned energy D -sweep/25A/auto/none/easee40s/car1p/L1/planned verdict D sweep/25A/auto/none/easee40s/car1p/balanced/chargenow over-cut netting sweep/25A/auto/none/easee40s/car1p/balanced/planned energy D sweep/25A/auto/none/easee40s/car3p/L1/chargenow over-cut netting sweep/25A/auto/none/easee40s/car3p/balanced/chargenow over-cut netting sweep/25A/empty/3p/easee40s/car1p/L1/chargenow over-cut netting sweep/25A/empty/3p/easee40s/car1p/L1/planned energy D -sweep/25A/empty/3p/easee40s/car1p/L1/planned verdict D sweep/25A/empty/3p/easee40s/car1p/balanced/chargenow over-cut netting sweep/25A/empty/3p/easee40s/car1p/balanced/planned energy D sweep/25A/empty/3p/easee40s/car3p/L1/chargenow over-cut netting sweep/25A/empty/3p/easee40s/car3p/balanced/chargenow over-cut netting sweep/25A/empty/mixed/easee40s/car1p/L1/chargenow over-cut netting sweep/25A/empty/mixed/easee40s/car1p/L1/planned energy D -sweep/25A/empty/mixed/easee40s/car1p/L1/planned verdict D sweep/25A/empty/mixed/easee40s/car1p/balanced/chargenow over-cut netting sweep/25A/empty/mixed/easee40s/car1p/balanced/planned energy D sweep/25A/empty/mixed/easee40s/car3p/L1/chargenow over-cut netting sweep/25A/empty/mixed/easee40s/car3p/balanced/chargenow over-cut netting sweep/25A/empty/none/easee40s/car1p/L1/chargenow over-cut netting sweep/25A/empty/none/easee40s/car1p/L1/planned energy D -sweep/25A/empty/none/easee40s/car1p/L1/planned verdict D sweep/25A/empty/none/easee40s/car1p/balanced/chargenow over-cut netting sweep/25A/empty/none/easee40s/car1p/balanced/planned energy D sweep/25A/empty/none/easee40s/car3p/L1/chargenow over-cut netting