Commit 47d3c48
* fix: release VPP control when IDLE at the reserve floor (#592)
Growatt VPP mapped every IDLE period to vpp_power=+1 with remote control
enabled ("battery first"), regardless of state of charge. Because remote
control stays enabled, _apply_period_vpp rewrites the command every period
to refresh the inverter's fallback timer, so through a long overnight idle
the inverter is never handed back and its BMS never idles down. Reported on
real hardware.
The battery_first hold exists to protect stored energy from self-consumption
(#466): IDLE's own DP cost model never credits battery discharge for load,
so self-consumption must come from grid/solar. At the configured minimum SoE
there is no stored energy left to protect, so the hold buys nothing and costs
the inverter its sleep.
IDLE at the floor now returns (0, False) -- released to the inverter's own
load_first self-use. Above the floor nothing changes, so #466 is preserved.
Releasing rather than writing power=0 with remote control still enabled
(grid_first) is what keeps this flow-neutral. load_first still absorbs
passive solar surplus exactly as the battery_first hold does, where
grid_first holds against charging and would bypass that surplus to the grid
-- a real change, since IDLE's DP cost model does credit that absorption.
The v10.0.2 VPP regression baseline is unchanged on all 37 fixtures, 24 of
which contain IDLE-at-floor periods.
at_reserve_floor is derived live in BatterySystemManager._at_reserve_floor()
and threaded through apply_period the same way block_passive_charging (#355)
and strategic_intent (#413) already are. It is read fresh at every write --
including the retry path and the discharge-inhibit path, both of which would
otherwise default it to False and silently re-assert the hold.
Caveat, documented in INVERTER_PLATFORMS.md: how far the battery can fall
under released self-use is the inverter's own discharge_stop_soc, and VPP
mode never writes that register (#309). Not new here -- LOAD_SUPPORT and
SOLAR_STORAGE already release control at any SoC -- but it means the fix
ships experimental pending the reporter's hardware confirmation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tmoey3FXSMsMsmVv8zmKNB
* fix: wire at_reserve_floor through display and simulator, guard unreadable SoC
Addresses both Stage 4 reviews on #619.
- _at_reserve_floor() reuses _get_current_battery_soc()'s validation and
holds (does not release) on an unreadable sensor, logged explicitly.
Previously float * None raised an uncaught TypeError from two unguarded
apscheduler jobs, on every platform.
- _planned_at_reserve_floor() derives the flag from the plan's SoE
trajectory and feeds _vpp_display_state, so the schedule API no longer
reports the old hold for periods production releases.
- derive_vpp_commands threads a per-period at_reserve_floor from the
simulated SoE trajectory, so the VPP fixture corpus actually exercises
the new branch.
Refs #592
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiPyLE5Kz9RvNiQemAdjym
* test: re-pin the VPP baseline commands, plans untouched
Both halves of the pin moved once derive_vpp_commands actually receives
at_reserve_floor -- including test_vpp_execution_of_the_baseline_plan_is_unchanged,
since this PR edits the execution model that half replays the historical
plan through. --repin-current skips that half by design, so the historical
plans were extracted from the baseline's own plan fields and replayed via
--from-plans, then --add-new restored the three post-tag plan:null fixtures.
No tag checkout, so the v10.0.2 plans are byte-identical.
half entries periods d(cost) d(SoE)
v10.0.2 (hist) 23 234 0.000000000000 0.000000000000
current 27 265 0.000000000000 0.000000000000
499 periods change [1, True] -> [0, False]; nothing moves a joule or an ore.
Verified after writing: fixture set identical, zero plans altered on either
half, only the 50 command arrays re-pinned -- so the drift signal that
test_drift_from_the_released_version_is_recorded depends on survives intact.
Refs #592
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiPyLE5Kz9RvNiQemAdjym
* fix: read the entering SoE, and drop the getattr masking it
Both findings from the Stage 4 round on 43295e6, confirmed against source.
1. rules.md:57 forbids getattr(obj, key, default). state_of_energy is a
required constructor arg on DPSchedule, so the attribute always exists on
a real instance -- the getattr was masking current_schedule being None,
which is now checked directly.
2. Off-by-one. state_of_energy is combined_soe, whose only writer stores
period_data.energy.battery_soe_end (battery_system_manager.py:2254);
battery_soe_start and battery_soe_end are distinct fields on EnergyData.
So index p is the SoE LEAVING period p, and the SoE entering it is p-1.
Reading p reported the release one period early at the crossing, and one
late on the way back up -- the exact display/write disagreement the method
exists to prevent.
The existing display tests could not see this: their fixtures pin
state_of_energy to a uniform value, where p and p-1 agree by construction.
test_the_crossing_period_still_displays_the_hold varies the trajectory across
the boundary and fails on the old index.
Removing the getattr also surfaced a fixture that was never shaped like a real
DPSchedule: MagicMock(spec=DPSchedule) specs off the class, so it omits
state_of_energy, which __init__ sets. The silent fallback had been hiding it.
Refs #592
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiPyLE5Kz9RvNiQemAdjym
* docs: say what the VPP baseline actually shows
Two doc passages still asserted the claim this PR withdrew -- that the
v10.0.2 VPP baseline is unchanged. It is not, and it could not have been:
before at_reserve_floor reached derive_vpp_commands the branch was
unreachable from the capture harness, so an unchanged baseline meant the
change was never exercised, not that it was flow-neutral.
Both now state what is actually unchanged. Commands move at every
IDLE-at-floor period (499 across 50 entries, [1, true] -> [0, false]);
realized cost and the SoE trajectory are bit-identical, 0.000000000000 on
both halves. Commands moving with the energy fixed is the evidence.
This matters most in bess-knowledge.md, which CLAUDE.md designates as ground
truth for any agent answering BESS behaviour questions -- a wrong claim there
misinforms future work rather than just this PR.
Also corrects _planned_at_reserve_floor's docstring, which justified reading
index 0 for period 0 by asserting period 0 is always the optimization period.
It is not: on a schedule re-optimized mid-day, index 0 is historical and holds
battery_soe_end like every other index, so the read is one period early there.
Display-only and bounded to period 0. Stated rather than papered over -- the
array has no entering value for period 0 to read, so recording one is a change
to _create_updated_schedule, not to this method.
Refs #592
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiPyLE5Kz9RvNiQemAdjym
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent beaf562 commit 47d3c48
16 files changed
Lines changed: 1912 additions & 1180 deletions
File tree
- core/bess
- simulation
- tests
- integration
- unit
- data/baselines
- docs
- agents
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| 25 | + | |
25 | 26 | | |
26 | 27 | | |
27 | 28 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2715 | 2715 | | |
2716 | 2716 | | |
2717 | 2717 | | |
| 2718 | + | |
| 2719 | + | |
2718 | 2720 | | |
2719 | 2721 | | |
2720 | 2722 | | |
| |||
2764 | 2766 | | |
2765 | 2767 | | |
2766 | 2768 | | |
| 2769 | + | |
2767 | 2770 | | |
2768 | 2771 | | |
2769 | 2772 | | |
| |||
2790 | 2793 | | |
2791 | 2794 | | |
2792 | 2795 | | |
| 2796 | + | |
| 2797 | + | |
| 2798 | + | |
| 2799 | + | |
| 2800 | + | |
| 2801 | + | |
| 2802 | + | |
| 2803 | + | |
| 2804 | + | |
| 2805 | + | |
| 2806 | + | |
| 2807 | + | |
| 2808 | + | |
| 2809 | + | |
| 2810 | + | |
| 2811 | + | |
| 2812 | + | |
| 2813 | + | |
| 2814 | + | |
| 2815 | + | |
| 2816 | + | |
| 2817 | + | |
| 2818 | + | |
| 2819 | + | |
| 2820 | + | |
| 2821 | + | |
| 2822 | + | |
| 2823 | + | |
| 2824 | + | |
| 2825 | + | |
| 2826 | + | |
| 2827 | + | |
| 2828 | + | |
| 2829 | + | |
| 2830 | + | |
| 2831 | + | |
| 2832 | + | |
| 2833 | + | |
| 2834 | + | |
| 2835 | + | |
| 2836 | + | |
| 2837 | + | |
| 2838 | + | |
| 2839 | + | |
| 2840 | + | |
2793 | 2841 | | |
2794 | 2842 | | |
2795 | 2843 | | |
| |||
2838 | 2886 | | |
2839 | 2887 | | |
2840 | 2888 | | |
| 2889 | + | |
2841 | 2890 | | |
2842 | 2891 | | |
2843 | 2892 | | |
| |||
3456 | 3505 | | |
3457 | 3506 | | |
3458 | 3507 | | |
| 3508 | + | |
| 3509 | + | |
| 3510 | + | |
| 3511 | + | |
3459 | 3512 | | |
3460 | 3513 | | |
3461 | 3514 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
578 | 578 | | |
579 | 579 | | |
580 | 580 | | |
| 581 | + | |
581 | 582 | | |
582 | 583 | | |
583 | 584 | | |
| |||
599 | 600 | | |
600 | 601 | | |
601 | 602 | | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
602 | 609 | | |
603 | 610 | | |
604 | 611 | | |
| |||
668 | 675 | | |
669 | 676 | | |
670 | 677 | | |
671 | | - | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
672 | 683 | | |
673 | 684 | | |
674 | 685 | | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
| 728 | + | |
675 | 729 | | |
676 | 730 | | |
677 | 731 | | |
678 | 732 | | |
679 | 733 | | |
680 | 734 | | |
| 735 | + | |
681 | 736 | | |
682 | 737 | | |
683 | 738 | | |
| |||
704 | 759 | | |
705 | 760 | | |
706 | 761 | | |
707 | | - | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
708 | 767 | | |
709 | 768 | | |
710 | 769 | | |
| |||
0 commit comments