Expose solar surplus power and force-export window state as sensors - #3791
Expose solar surplus power and force-export window state as sensors#3791Pezmc wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a core “solar surplus car charging” mode that reuses Predbat’s existing car-charging slot signaling to divert excess PV export into EV charging, with new config controls and observability.
Changes:
- Introduces 3 new config entities to enable/shape surplus-charging behavior (master switch, W threshold, ignore SoC limit).
- Updates
execute_plan()to detect live surplus export with hysteresis and publish surplus-related binary sensors/attributes. - Extends execute/test infrastructure and documentation to cover the new feature.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/car-charging.md | Documents the new solar surplus charging feature, configuration, and sensors. |
| apps/predbat/config.py | Adds three new configuration items for surplus charging. |
| apps/predbat/fetch.py | Reads the new configuration items into runtime fields. |
| apps/predbat/execute.py | Implements surplus detection, battery-discharge hold integration, and sensor publishing. |
| apps/predbat/predbat.py | Initializes car_charging_solar_surplus_active in reset state. |
| apps/predbat/tests/test_infra.py | Adds defaults/reset fields for the new config/state in tests. |
| apps/predbat/tests/test_execute.py | Adds scenarios validating surplus activation/inhibition rules and status text. |
| .cspell/custom-dictionary-workspace.txt | Adds “deadband” to spelling dictionary. |
|
@springfall2008 I've addressed the copilot feedback, both suggestions were valid edge cases |
|
Hi Pezmc, One question I have, why is this hard-wired to inverter 0? There's a bit cleanup required, in that reset() should be updated to reset the self.* attributes used in this code and then the getattr() can also be removed. Do you want me to try to clean up or would you like to have a go? |
It's hard-wired to inverter.id == 0 solely so it only runs once (since it uses global state, not anything per-inverter). Following the pattern file e.g. the status_extra, for example. Happy to move it up above the inverter loop instead if you'd prefer!
I've attempted to address, and applied a slight refactor to handle number of cars changing. If you'd like further changes or want to make some tweaks, please have at it! Thanks for taking the time! |
|
Thanks for this work! I have been missing this and this will be an awesome addition. My 2 cents for two of items being added:
It would be awesome if instead the car charge rate were to be adjusted so that the car would charge with the correct surplus. It would have a minimum/maximum car charge rate.
Like soc min/recommended, it would be nice if we could define an upper limit that would be respected. This way we could continue to set a SoC target for Predbat to manage while allowing any surplus to charge the car slightly more. In short this would convert the Boolean (100%) value to a configurable integer. |
|
@bpinto thanks, appreciate the thoughts!
If your charger supports variable current, I think this is actually better handled in your own HA automation than inside Predbat. The automation already has to flip the charger on and off and can read One thing Predbat could usefully do is expose the surplus amount it's currently seeing as an attribute, so the automation doesn't have to recompute it. But actually modulating the rate from inside Predbat feels like feature creep to me, given Predbat can't actuate the charger directly anyway.
Yeah, that one's fair, turning the boolean into a configurable cap makes sense. It's a boolean to KISS for now. @springfall2008 do you think we should make this change now, or handle as a separate issue? |
|
Having sat on this overnight I'm going to change the boolean to a limit that defaults to 100%, to save needing to introduce backwards incompatible changes or having to maintain backwards compatibility going forward. Thanks for the suggestion @bpinto |
|
@Pezmc thanks for taking into consideration my comments! By no means, I think any of that is a blocker, but I think it's an extra/improvement.
I think it would make sense for predbat to be responsible for this since predbat does similar to battery/inverter settings. For instance, it automatically sets the SoC target for the batteries and can also configure the charging rate. Many of predbat's input support either a fixed value or a sensor, I haven't read how That said, not everyone uses chargers that supports variable charging rate, so the ideas are not exclusive since they support a different group of users. |
|
|
|
@springfall2008 Updated to use This is ready for re-review and any tweaks you wish to make! |
|
@springfall2008 I've been dog fooding this for a couple weeks and have added some additional test coverage and fixes based on that. |
|
I follow @bpinto , predbat should export the data as sensors. Predbat is really for the inverter and battery. Maybe a new 'app' called predcar could be born? But an option to default use predbat configuration as an default proposal/option? Now I use predbat as EMS for ha automation to control the EV charge Amps based on PV surplus when battery is full (Huawei here...) |
97a8dce to
41afe3d
Compare
|
Hi @springfall2008, just a friendly nudge, this has been open for about six weeks now and I've been running it on my own system without issues. Your earlier feedback has been addressed (reset cleanup, the limit replacing the boolean, hoisting out of the per-inverter loop) and tests are passing. @bpinto @ward0 on the idea of exposing sensor data: the PR now includes a predbat.car_charging_solar_surplus_power numeric sensor that reports the effective surplus in kW, so automations can already use that to drive variable-rate charging or other loads. I think that covers the sensor-data side of things. On the suggestion of a separate "predcar" project: I'd push back on that for now. Predbat already manages car charging schedules, tracks car SoC, and integrates with Octopus Intelligent. Solar surplus diversion is a natural extension of that existing functionality, not a separate concern. Splitting it out would mean duplicating all the inverter/rate/battery context that Predbat already has. I'd rather get this merged as-is and then discuss any further enhancements (like variable charge rate control) as follow-up issues. Ready for re-review whenever you have a moment. Happy for you to make any tweaks you'd like directly on the branch. Thanks! |
|
@Pezmc I hope your PR gets active soon. |
|
Anything new about this great PR? |
|
@Pezmc Can we still use your 'code'? Because this PR is still open :( |
|
@ward0 It seems like the PR is blocked by @springfall2008, so please indeed feel free to use as needed, with attribution. If @springfall2008 (or another reviewer) picks this up, I'll take the time to rebase on main (which has moved forward), so it's ready to merge again. |
|
Sorry for the long silence on this one, and thanks for the sustained work and patience @Pezmc. Our hesitation isn't about whether solar-surplus car charging is useful - it clearly is, and the demand in this thread backs that up. It's that the actual "when do I switch the car on" decision varies a lot by charger: some (Hypervolt's Super Eco, Zappi's Eco+, Ohme's own solar matching, etc.) already do proper real-time current modulation in hardware/firmware once told to enter that mode - an automation flipping a switch beats Predbat toggling on/off every 5-minute cycle for those. Others are dumb on/off relays with no charger-side intelligence at all, where someone has to make the call externally. Charge rates, number of phases, and where people's grid/CT power sensors actually sit also all vary enough that we're wary of Predbat owning a decision that's this charger-specific - we don't want to duplicate, and inevitably diverge from, logic that's often better handled at the charger or in a user's own automation. What we would welcome: the two pieces of state in here that are genuinely hard to get right outside Predbat, published as plain sensors, without the switch/threshold/hysteresis decision logic attached:
Neither of those is exposed anywhere today. That gives every charger type - smart or dumb - the one input that's actually hard to compute externally, while leaving the on/off (or rate) decision to whichever automation or charger firmware is best placed to make it for that specific setup. Happy to take another look if you'd like to pare this down to the sensor-only version - given the interest in this thread it seems worth landing something. |
41afe3d to
b4ba66b
Compare
|
Thanks - I'll look more in a bit - one observation (which is more churn I could have considered earlier) is that the solar excess now isn't specifically for the car - anything could use it - so perhaps we can remove "car" from the sensor name. |
predbat.solar_surplus_power reports the power available for a flexible load right now, aggregated over every inverter, so an automation can run a car charger, an immersion heater or anything else on spare solar without Predbat deciding anything. A car charger sits behind the grid meter, so once a car is charging the export collapses towards zero. car_charging_power is added back to recover what the export would be with the car off, which keeps the reading stable enough to drive a charger from - any other load can subtract the car_charging_power attribute if it wants the car's share left out. Battery discharge is subtracted so battery power is never offered as if it were solar: if cloud cover arrives mid-charge the sensor falls to the real surplus instead of reporting one the battery is paying for. The components are published as attributes so users can build their own variant, including a battery-versus-load priority, which is policy rather than something Predbat should pick.
binary_sensor.predbat_force_export_slot says whether the plan has a force export window running now, so an automation charging a car on solar surplus can leave the solar for Predbat to sell instead. It is taken from the plan rather than from the commanded state behind binary_sensor.predbat_exporting, which is off in read only mode, off during Hold exporting and only on for the minutes an export is actually being commanded. The plan-derived version stays on right through a slot and covers freeze export, where the battery is held and the PV is what gets sold.
predbat.load_power, battery_power, pv_power and grid_power are published in kW (publish_inverter_data divides by 1000 and sets unit_of_measurement kW) but the documentation called them Watts. Record the sign conventions while here.
Adds a 'Charging from solar surplus' section to the car charging docs covering the formula, the attributes, an example automation with on/off thresholds and a proportional-amps variant, and a table separating live surplus from the predicted export triggers and the commanded exporting sensor.
b4ba66b to
2172347
Compare
Two ways the sensor could report a surplus that was not there. A charger outside the CT clamp is never seen by the inverter's grid reading, so adding car_charging_power back invented a surplus equal to the whole charger draw. The add-back is now gated on car_energy_reported_load, the flag prediction.py already uses for this. car_charging_power_included says which applies. The result is capped at pv_power. A grid sensor wired positive-on-import without grid_power_invert made the surplus track the import instead, holding a charger at full rate. The cap cannot fix the sign in daylight, but it bounds the error and forces 0 after dark. The documented automation had three faults: a bare state trigger on car_charging_slot turned the charger on when the cheap slot ended, charging could not resume after a force export slot because numeric_state only fires on a crossing, and the modulating-current snippet floored at 6A with no off state. Docs now also cover the PV inventory and freshness checks, and correct the update interval to INVERTER_QUICK_UPDATE_SECONDS.
|
@chalfontchubby That's done and I've adjusted the name and the docs say it isn't car-specific. While renaming I ran another check and spotted:
So it's now:
Of note, the rework were both Claude-assisted, so a sceptical eye on the math in particular would be welcome. EDIT: Feel free to make any additional commits or tweaks needed to get this merged |
Pared down to the two sensors @chalfontchubby asked for in this comment. Predbat decides nothing about solar car charging. The automation or the charger firmware does.
Rebuilt on current
mainrather than rebased. Most of the old diff was the decision logic that has gone.The rework and its review pass were both Claude-assisted, so a sceptical eye on the arithmetic is welcome.
The two sensors
predbat.solar_surplus_powerbinary_sensor.predbat_force_export_slotonwhen the plan has a force export slot running, so the solar is worth more sold than put in the carcar_charging_poweris added back because a charger inside the CT clamp sits behind the grid meter, so export collapses to zero once the car starts. Without it the sensor would drop to nothing the moment you acted on it. This uses the reading added in #4715, so there is no hysteresis or stored state.The add-back is gated on
car_energy_reported_load. With that switch Off the charger is outside the clamp, the grid reading never saw the car, and adding it back would invent a surplus equal to the whole charger draw.prediction.pybranches on the same flag. Thecar_charging_power_includedattribute says which applies.Battery discharge is subtracted, so battery power is never reported as solar. When cloud arrives mid-charge the sensor falls to the real surplus instead of reporting one the battery is paying for. Battery charging is not added back, so a charging battery takes the solar first.
The result is capped at
pv_power. Nothing can be spare that was never generated. A grid sensor wired positive-on-import withoutgrid_power_invertwould otherwise make the surplus track the import. The cap cannot fix the sign in daylight, but it bounds the error and forces 0 after dark.Attributes carry every input:
grid_power,battery_power,pv_power,car_charging_power,car_charging_power_configuredandcar_charging_power_included.binary_sensor.predbat_force_export_slotcomes from the plan, using the same conditionpublish_export_limitalready uses.binary_sensor.predbat_exportingis the commanded state, so it is off in read-only mode, off during "Hold exporting", and only on for the minutes an export is actually being written. The plan-derived version stays on for the whole slot and covers freeze export.Example automation
1.4kW is 6A at 230V, the minimum most chargers take. The 1.2kW condition gives it something to sit in so passing cloud does not switch the charger repeatedly.
Every trigger is paired with a
to:state and every condition reads the entity, not the trigger that fired. A barestatetrigger fires when a slot ends as well as when it starts, and a trigger-id condition would then turn the charger on at the end of the cheap slot. Triggering on both edges offorce_export_slotis what lets charging resume when an export slot finishes, becausenumeric_stateonly fires on a threshold crossing.The docs also show setting charger amps from the surplus, and note that a charger with its own solar mode (Zappi Eco+, Hypervolt Super Eco, Ohme) only needs the sensors to switch that mode on and off.
Removed since the first version
Everything that made a decision: the
car_charging_solar_surplus/_threshold/_limitconfig items,detect_car_solar_surplus(), the previous-cycle hysteresis, the carHolding and pause-discharge changes with their "Hold for car (solar)" status, thecar_charging_slotoverride, andbinary_sensor.predbat_car_charging_solar_surplus. No changes toconfig.py,fetch.py,predbat.pyor the apps.yaml schema. No new switches.One open question
The surplus sensor is published unconditionally, unlike
predbat.car_charging_powerwhich only appears when a charger is configured. With no charger it is the grid export clamped at zero, andcar_charging_power_configuredsays whether the add-back is live. Happy to gate it the same way if you prefer.Tests
apps/predbat/tests/test_solar_surplus.py, run with--test solar_surplus. Covers plain export, import clamping, the car add-back, the CT clamp gate both ways, the generation cap, battery discharge subtraction, battery charging, the unconfigured case, and the export slot sensor across in-window, freeze, idle limit, out-of-window, export control off and no windows../run_all --quickand./run_pre_commitpass.Docs: a "Charging from solar surplus" section in
docs/car-charging.mdcovering the formula, the checks to make before driving a load from it, and both sensors indocs/output-data.md. One drive-by fix in there, the four current power sensors were documented as Watts but publish kW.