@@ -127,14 +127,21 @@ the cheapest scheduled slot. This compensates for deficit shrinking as PV
127127confidence recovers mid-day — without it, early expensive slots would
128128execute while later cheaper slots got dropped from a re-plan.
129129
130- ### State Transitions (coordinator.py:1195-1231 )
130+ ### State Transitions (coordinator.py ` _transition_to_state ` , ~ 1425-1482 )
131131
132132| State | econ_rule_1_enable | Voltage | SOC | Power |
133133| ---| ---| ---| ---| ---|
134134| charging | 1 | voltage_level (default 58V) | charge_max (default 100%) | safe_max_power (W) |
135- | discharging | 2 | discharge_min_voltage (default 50V) | discharge_min (default 20% ) | safe_max_power (W) |
135+ | discharging | 2 | discharge_min_voltage (default 50V) | discharge floor (see below ) | safe_max_power (W) |
136136| idle | 0 | * (not written)* | * (not written)* | * (not written)* |
137137
138+ ** Discharge SOC floor** : in auto mode (grid_mode active) the discharging
139+ SOC register is written from the * computed* ` _reserve_target_pct `
140+ (the planned reserve target), not the raw ` battery_discharge_min_level ` .
141+ This makes the inverter's hardware floor match the schedule's intended
142+ reserve rather than the absolute minimum. When the EMS is off it falls
143+ back to the user's ` discharge_min ` setting.
144+
138145### Model Differences
139146
140147| Aspect | TREX-5 | TREX-10 | TREX-25 | TREX-50 |
@@ -337,7 +344,7 @@ in the UI.
337344
338345---
339346
340- ## Safe Power Management (coordinator.py:1077-1193 )
347+ ## Safe Power Management (coordinator.py ` _check_safe_power ` , ~ 1307-1424 )
341348
342349Monitors grid current per phase and adjusts inverter power:
343350
@@ -398,11 +405,14 @@ Fetches `energy_state` history from HA API (throttled 60s), shows what actually
398405| price_threshold_level | number | 1-10 | 5 | Manual price level |
399406| battery_charge_max_level | number | 30-100% | 100 | Max SOC for charging |
400407| battery_discharge_min_level | number | 10-70% | 20 | Min SOC for discharging |
401- | battery_capacity_kwh | number | 1-100 kWh | 10 | Usable battery capacity |
408+ | battery_capacity_kwh | number | 1-200 kWh | 10 | Usable battery capacity |
402409| efficiency_factor | number | 0.70-1.00 | 0.90 | Round-trip efficiency |
403- | daily_consumption_estimate | number | 0-100 kWh | 10 | Fallback consumption |
410+ | daily_consumption_estimate | number | 0-120 kWh | 10 | Fallback consumption |
404411| reserve_target_pct | number | 0-100% | 0 | Fixed reserve floor (0=dynamic) |
405412| arbitrage_price_delta | number | 0-0.50 €/kWh | 0 | Price spread for full charge |
413+ | battery_cycle_cost_eur_kwh | number | 0-0.50 €/kWh | 0 | Battery wear cost (profitability filter) |
414+ | optimization_priority | select | cost/longevity/self_consumption | cost | Multi-objective strategy |
415+ | block_export_on_negative_price | select | on/off | on | Skip sell scheduling at p < 0 |
406416| max_amperage_per_phase | number | 10-63 A | 16 | Grid current limit |
407417| voltage_level | number | 48-60 V | 58 | Charge voltage setpoint |
408418| discharge_min_voltage | number | 48-55 V | 50 | Discharge voltage floor |
@@ -675,7 +685,23 @@ other entities) to be unusable on fresh installs.
675685keys (` max_amperage_per_phase ` , ` safe_power_management ` ,
676686` battery_cycle_cost_eur_kwh ` , ` optimization_priority ` ,
677687` block_export_on_negative_price ` ) so new installations get them
678- written at setup time.
688+ written at setup time. The migration block ` defaults_to_set ` in
689+ ` __init__.py ` (runs on every ` async_setup_entry ` ) mirrors the same
690+ keys so * existing* installs upgrading to a newer version backfill
691+ missing options too.
692+
693+ #### C6. UI Entities for Multi-Objective Knobs — IMPLEMENTED
694+ The three knobs added in the lifecycle overhaul previously had no UI
695+ control — they lived only in ` entry.options ` defaults and could not be
696+ changed by the user. They now have entities:
697+ - ` battery_cycle_cost_eur_kwh ` → number (0-0.50 €/kWh, number.py)
698+ - ` optimization_priority ` → select (cost/longevity/self_consumption, select.py)
699+ - ` block_export_on_negative_price ` → select (on/off, select.py)
700+
701+ Because ` block_export_on_negative_price ` is stored as a string ("on"/"off")
702+ by the select but consumed as a bool by ` EMSConfig ` , the coordinator
703+ converts it: ` str(opts.get(..., "on")).lower() not in ("off","false","0") ` .
704+ This also tolerates the legacy bool value from earlier installs.
679705
680706### Deferred Items (Need Separate Iteration)
681707
0 commit comments