Skip to content

Commit be3cfcf

Browse files
Merge pull request #3727 from springfall2008/copilot/fix-ev-charging-houseload-issue
Fix: LoadML component ignores car_charging_hold switch changes at runtime
2 parents 4dc8e9c + 743718b commit be3cfcf

2 files changed

Lines changed: 23 additions & 5 deletions

File tree

‎apps/predbat/load_ml_component.py‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -318,15 +318,26 @@ async def _fetch_load_data(self):
318318
load_power_data, load_minutes_age = self.base.minute_data_load(self.now_utc, "load_power", days_to_fetch, required_unit="W", load_scaling=1.0, interpolate=True, pad=False)
319319
load_minutes = self.base.fill_load_from_power(load_minutes, load_power_data)
320320

321+
# Re-read car charging settings dynamically so switch changes are picked up without a restart.
322+
# Note: car_charging_from_battery does not affect EV subtraction from load history; it only controls
323+
# whether the battery is allowed to discharge for car charging in the prediction model.
324+
car_charging_hold = self.get_arg("car_charging_hold", True)
325+
car_energy_reported_load = self.get_arg("car_energy_reported_load", True)
326+
if not car_energy_reported_load:
327+
# Car energy is not part of the house load (outside CT clamp), so don't subtract it
328+
car_charging_hold = False
329+
car_charging_energy_scale = self.get_arg("car_charging_energy_scale", 1.0)
330+
self.log("ML Component: Car charging hold {}, car energy reported load {}".format(car_charging_hold, car_energy_reported_load))
331+
321332
car_charging_energy = {}
322333
if self.get_arg("car_charging_energy", default=None, indirect=False):
323-
car_charging_energy = self.base.minute_data_import_export(days_to_fetch, self.now_utc, "car_charging_energy", scale=self.car_charging_energy_scale, required_unit="kWh", pad=False)
334+
car_charging_energy = self.base.minute_data_import_export(days_to_fetch, self.now_utc, "car_charging_energy", scale=car_charging_energy_scale, required_unit="kWh", pad=False)
324335

325336
max_minute = max(load_minutes.keys()) if load_minutes else 0
326337
max_minute = (max_minute // 5) * 5 # Align to 5-minute intervals
327338
load_minutes_new = {}
328339

329-
if self.car_charging_hold and car_charging_energy:
340+
if car_charging_hold and car_charging_energy:
330341
# Use improved car subtraction method that handles timing misalignment and gaps
331342
self.log("ML Component: Applying improved car charging subtraction with hold and interpolation")
332343
load_minutes_new = self.car_subtraction(load_minutes, car_charging_energy, step=PREDICT_STEP, interpolate_gaps=True, max_gap_minutes=60, smoothing_window=3)
@@ -336,7 +347,7 @@ async def _fetch_load_data(self):
336347
total_load_energy = 0
337348
for minute in range(max_minute, -PREDICT_STEP, -PREDICT_STEP):
338349
car_delta = 0.0
339-
if self.car_charging_hold:
350+
if car_charging_hold:
340351
load_now = self.get_from_incrementing(load_minutes, minute, PREDICT_STEP, backwards=True)
341352
if load_now >= self.car_charging_threshold * PREDICT_STEP:
342353
car_delta = self.car_charging_rate * PREDICT_STEP

‎apps/predbat/tests/test_load_ml.py‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2329,6 +2329,15 @@ async def test_step_size_calculation():
23292329
mock_base_step_size.minute_data_load = MagicMock(return_value=(load_data, 1.0))
23302330
mock_base_step_size.minute_data_import_export = MagicMock(return_value={})
23312331
mock_base_step_size.fill_load_from_power = MagicMock(side_effect=lambda x, y: x)
2332+
# Override get_arg to disable car_charging_hold (test the non-car-charging branch)
2333+
original_get_arg = mock_base_step_size.get_arg
2334+
2335+
def mock_get_arg_no_car_hold(key, default=None, indirect=True, combine=False, attribute=None, index=None, domain=None, can_override=True, required_unit=None):
2336+
if key == "car_charging_hold":
2337+
return False
2338+
return original_get_arg(key, default=default, indirect=indirect, combine=combine, attribute=attribute, index=index, domain=domain, can_override=can_override, required_unit=required_unit)
2339+
2340+
mock_base_step_size.get_arg = mock_get_arg_no_car_hold
23322341

23332342
component = LoadMLComponent(mock_base_step_size, load_ml_enable=True)
23342343
# Override default values for testing
@@ -2340,8 +2349,6 @@ async def test_step_size_calculation():
23402349
component.ml_time_decay_days = 7
23412350
component.ml_max_load_kw = 23.0
23422351
component.ml_max_model_age_hours = 48
2343-
# IMPORTANT: Disable car charging hold to test the non-car-charging branch (the bug scenario)
2344-
component.car_charging_hold = False
23452352

23462353
result_data, result_age, result_now, result_pv, result_temp, result_import_rates, result_export_rates = await component._fetch_load_data()
23472354

0 commit comments

Comments
 (0)