Skip to content

Commit c50ffa6

Browse files
xchillxxclaude
andcommitted
Car status 'Cheap, but home battery first' (v3.1.19)
User 10.10.: the dashboard said 'too little surplus' while the price was below the cheap threshold and only the home-battery rule for PV hours held the car back. New car status billig_akku for exactly that case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent c540447 commit c50ffa6

8 files changed

Lines changed: 24 additions & 12 deletions

File tree

‎custom_components/surplus_pilot/coordinator.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"netz_pflicht": "Abfahrtsziel, aus dem Netz (günstigste Slots)",
121121
"netz_billig": "Strom gerade billig, PV reicht nicht", "pflicht_minimum": "Abfahrtsziel, Mindeststrom",
122122
"zu_wenig_ueberschuss": "zu wenig Überschuss", "pflicht": "Abfahrtsziel aus PV",
123+
"billig_akku": "Strom billig, aber Hausakku zuerst",
123124
"pflicht_und_rest": "Abfahrtsziel + Rest", "vorrang_rest": "Vorrang, morgen keine PV-Chance",
124125
"rest": "Rest nach den Geräten", "haelt_minimum": "hält Minimum", "ladelimit_erreicht": "Ladelimit erreicht",
125126
}

‎custom_components/surplus_pilot/manifest.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@
99
"iot_class": "local_polling",
1010
"issue_tracker": "https://github.com/xchillxx/surplus-pilot/issues",
1111
"requirements": [],
12-
"version": "3.1.18"
12+
"version": "3.1.19"
1313
}

‎custom_components/surplus_pilot/planner.py‎

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -523,29 +523,31 @@ def _grid_slot_now(inp: Inputs, dep: Departure, uncovered_kwh: float,
523523

524524

525525
def _cheap_topup_now(inp: Inputs, dep: Departure | None,
526-
surplus: float) -> tuple[bool, float, tuple[datetime, datetime] | None]:
526+
surplus: float) -> tuple[bool, float, tuple[datetime, datetime] | None, bool]:
527527
"""Grid top-up while the price is below the cheap threshold — only for
528528
energy the PV forecast won't bring anyway before the next departure
529529
(24 h without one), and only in the cheapest published slots until then
530530
(10.10. live: charged at 05:00 for 18.9 ct, the midday before the
531531
departure was 17.2 ct). The block is planned even while the price is
532-
still above the threshold, so the devices know it in advance."""
532+
still above the threshold, so the devices know it in advance. Last value:
533+
the price is cheap now but only the home-battery rule for PV hours holds
534+
it back (status `billig_akku` instead of "too little surplus")."""
533535
car = inp.car
534536
if (inp.cheap_target_soc is None or inp.cheap_threshold is None
535537
or car is None or car.soc is None or not car.present):
536-
return False, 0.0, None
538+
return False, 0.0, None, False
537539
target = min(inp.cheap_target_soc, car.limit_soc)
538540
if car.soc >= target:
539-
return False, 0.0, None
541+
return False, 0.0, None, False
540542
if inp.battery_feeds_car:
541543
# the home battery covers any import first: with energy left it
542544
# would just empty itself into the car (10.10. live: 05:00, battery
543545
# 36 -> 12 % in 45 min, then the house bought at 22-24 ct all
544546
# morning); and with PV for the car there's no top-up at all
545547
if surplus >= car.min_kw:
546-
return False, 0.0, None
548+
return False, 0.0, None, False
547549
if inp.battery_soc is not None and inp.battery_soc > inp.battery_min_soc + CHEAP_BATTERY_MARGIN:
548-
return False, 0.0, None
550+
return False, 0.0, None, False
549551
need = (target - car.soc) / 100.0 * car.capacity_kwh / car.efficiency
550552
end = dep.start if dep is not None else inp.now + timedelta(hours=24)
551553
pv, _ = forecast_surplus_kwh(inp.forecast, inp.now, end, inp.base_kw + 0.3, CHEAP_FORECAST_TRUST)
@@ -554,10 +556,12 @@ def _cheap_topup_now(inp: Inputs, dep: Departure | None,
554556
battery_missing = max(0.0, (100.0 - inp.battery_soc) / 100.0 * inp.battery_capacity_kwh)
555557
from_grid = need - max(0.0, pv - battery_missing)
556558
if from_grid <= 0.5:
557-
return False, 0.0, None
559+
return False, 0.0, None, False
558560
in_block, block = _cheapest_free_slot_now(inp, end, from_grid, car.max_kw)
559561
now_ok = inp.price_now is not None and inp.price_now <= inp.cheap_threshold
560-
return in_block and now_ok, from_grid, block
562+
waits = (now_ok and not in_block and not inp.battery_feeds_car and inp.pv_kw > inp.base_kw + 0.3
563+
and not cheap_pv_battery_ok(inp))
564+
return in_block and now_ok, from_grid, block, waits
561565

562566

563567
def cheap_pv_battery_ok(inp: Inputs) -> bool:
@@ -668,8 +672,9 @@ def make_plan(inp: Inputs) -> Plan:
668672
budget = {"pv_kw": inp.pv_kw, "grundlast_kw": inp.base_kw, "akku_ziel_kw": target_b}
669673

670674
grid_reason = "netz_pflicht"
675+
cheap_waits = False
671676
if not grid and car_ok and inp.allow_grid_for_car:
672-
cheap, cheap_kwh, cheap_block = _cheap_topup_now(inp, dep, surplus)
677+
cheap, cheap_kwh, cheap_block, cheap_waits = _cheap_topup_now(inp, dep, surplus)
673678
block = block or cheap_block
674679
if cheap:
675680
grid, grid_reason = True, "netz_billig"
@@ -709,7 +714,7 @@ def make_plan(inp: Inputs) -> Plan:
709714
if must_kw > MUST_MIN_SURPLUS_KW and car_must > 0 and _lift_to_min(inp, dep, need_kwh, surplus):
710715
car_kw, car_reason = car.min_kw, "pflicht_minimum" # battery/grid fill the gap
711716
else:
712-
car_kw, car_reason = 0.0, "zu_wenig_ueberschuss"
717+
car_kw, car_reason = 0.0, "billig_akku" if cheap_waits else "zu_wenig_ueberschuss"
713718
else:
714719
car_reason = ("pflicht" if topup < 0.05 else "pflicht_und_rest") if car_must > 0 else (
715720
"vorrang_rest" if car_first else "rest")

‎custom_components/surplus_pilot/sensor.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"ueberschuss", "akku_reicht", "akku_reserve", "kein_ueberschuss", "akku_reicht_nicht", "zu_kurz",
1616
"manuell", "unbekannt"]
1717
CAR_REASONS = ["kein_auto", "nicht_da", "ladelimit_erreicht", "netz_pflicht", "netz_billig", "pflicht_minimum",
18-
"zu_wenig_ueberschuss", "pflicht", "pflicht_und_rest", "vorrang_rest", "rest", "haelt_minimum",
18+
"zu_wenig_ueberschuss", "billig_akku", "pflicht", "pflicht_und_rest", "vorrang_rest", "rest", "haelt_minimum",
1919
"pausiert", "unbekannt"]
2020

2121

‎custom_components/surplus_pilot/strings.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@
244244
"netz_billig": "Grid: cheap price, PV won't do it",
245245
"pflicht_minimum": "Departure target, minimum current",
246246
"zu_wenig_ueberschuss": "Not enough surplus",
247+
"billig_akku": "Cheap, but home battery first",
247248
"pflicht": "Departure target (PV)",
248249
"pflicht_und_rest": "Departure target + rest",
249250
"vorrang_rest": "Charging first (no PV chance tomorrow)",

‎custom_components/surplus_pilot/translations/de.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@
244244
"netz_billig": "Netz: billig, PV reicht nicht",
245245
"pflicht_minimum": "Abfahrtsziel, Mindeststrom",
246246
"zu_wenig_ueberschuss": "Zu wenig Überschuss",
247+
"billig_akku": "Billig, aber Hausakku zuerst",
247248
"pflicht": "Abfahrtsziel (PV)",
248249
"pflicht_und_rest": "Abfahrtsziel + Rest",
249250
"vorrang_rest": "Lädt vorrangig (morgen keine PV-Chance)",

‎custom_components/surplus_pilot/translations/en.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@
244244
"netz_billig": "Grid: cheap price, PV won't do it",
245245
"pflicht_minimum": "Departure target, minimum current",
246246
"zu_wenig_ueberschuss": "Not enough surplus",
247+
"billig_akku": "Cheap, but home battery first",
247248
"pflicht": "Departure target (PV)",
248249
"pflicht_und_rest": "Departure target + rest",
249250
"vorrang_rest": "Charging first (no PV chance tomorrow)",

‎tests/test_planner.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,9 @@ def inp_at(t, pv, battery, running=False, locked=False):
447447
return inp
448448

449449
assert not P.make_plan(inp_at(now, 4.5, 34.0)).car_grid # today: battery too low
450+
# 15:15 live: PV 2.4 kW, battery 21 %, 17.2 ct below the threshold -> the status names the battery
451+
assert P.make_plan(inp_at(now.replace(hour=15, minute=15), 2.4, 21.0)).car_reason == "billig_akku"
452+
assert P.make_plan(inp_at(now.replace(hour=15, minute=15), 2.4, 60.0)).car_reason == "netz_billig"
450453
assert P.make_plan(inp_at(now, 4.5, 55.0)).car_grid # full enough: PV + grid
451454
assert P.make_plan(inp_at(now, 4.5, 47.0, running=True)).car_grid # running: down to 45 %
452455
stop = inp_at(now, 4.5, 44.0, running=True)

0 commit comments

Comments
 (0)