Skip to content

Commit 54d4f52

Browse files
authored
Merge pull request #238 from renaudallard/main
Fix charging prediction
2 parents fe5021d + 6bee00a commit 54d4f52

3 files changed

Lines changed: 25 additions & 14 deletions

File tree

custom_components/cardata/coordinator.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ def _descriptor_float(state: DescriptorState | None) -> float | None:
8080
return None
8181

8282

83+
def _has_ac_power_data(vehicle_state: dict[str, DescriptorState]) -> bool:
84+
"""Check if AC voltage × current data is available."""
85+
voltage = _descriptor_float(vehicle_state.get("vehicle.drivetrain.electricEngine.charging.acVoltage"))
86+
current = _descriptor_float(vehicle_state.get("vehicle.drivetrain.electricEngine.charging.acAmpere"))
87+
return bool(voltage and current)
88+
89+
8390
@dataclass
8491
class CardataCoordinator:
8592
hass: HomeAssistant
@@ -513,7 +520,7 @@ def _anchor_soc_session(self, vin: str, vehicle_state: dict[str, DescriptorState
513520
except (TypeError, ValueError):
514521
pass
515522
else:
516-
# AC: seed with voltage × current if available
523+
# AC: try voltage × current first, fall back to charging.power
517524
voltage = _descriptor_float(vehicle_state.get("vehicle.drivetrain.electricEngine.charging.acVoltage"))
518525
current = _descriptor_float(vehicle_state.get("vehicle.drivetrain.electricEngine.charging.acAmpere"))
519526
phases = _descriptor_float(vehicle_state.get("vehicle.drivetrain.electricEngine.charging.phaseNumber"))
@@ -526,6 +533,17 @@ def _anchor_soc_session(self, vin: str, vehicle_state: dict[str, DescriptorState
526533
pass
527534
if voltage and current:
528535
self._soc_predictor.update_ac_charging_data(vin, voltage, current, phases, aux_kw)
536+
else:
537+
# Fallback: seed from charging.power when V×A is unavailable
538+
power_state = vehicle_state.get("vehicle.powertrain.electric.battery.charging.power")
539+
if power_state and power_state.value is not None:
540+
try:
541+
power_val = float(power_state.value)
542+
unit = (power_state.unit or "").lower()
543+
power_kw = power_val / 1000.0 if unit == "w" else power_val
544+
self._soc_predictor.update_power_reading(vin, power_kw, aux_power_kw=aux_kw)
545+
except (TypeError, ValueError):
546+
pass
529547

530548
def _end_soc_session(self, vin: str, vehicle_state: dict[str, DescriptorState]) -> None:
531549
"""End SOC prediction session when charging stops.
@@ -983,7 +1001,7 @@ async def _async_handle_message_locked(
9831001
# Charging started - try to anchor session
9841002
self._anchor_soc_session(vin, vehicle_state)
9851003
# End any active driving session (can't drive and charge)
986-
self._magic_soc.cancel_driving_session(vin)
1004+
self._end_driving_session(vin, vehicle_state)
9871005
elif was_charging:
9881006
# Charging stopped - end session for learning
9891007
self._end_soc_session(vin, vehicle_state)
@@ -1004,10 +1022,11 @@ async def _async_handle_message_locked(
10041022
if value:
10051023
self._soc_predictor.set_charging_method(vin, str(value))
10061024

1007-
# Update power reading with direct power value (DC charging only;
1008-
# AC charging uses voltage × amps instead)
1025+
# Update power reading with direct power value (DC charging,
1026+
# or AC fallback when voltage × amps data is unavailable)
10091027
elif descriptor == "vehicle.powertrain.electric.battery.charging.power":
1010-
if self._soc_predictor.get_charging_method(vin) == "DC":
1028+
method = self._soc_predictor.get_charging_method(vin)
1029+
if method == "DC" or (method is not None and not _has_ac_power_data(vehicle_state)):
10111030
power_kw = None
10121031
if value is not None:
10131032
try:

custom_components/cardata/magic_soc.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -537,14 +537,6 @@ def update_aux_power(self, vin: str, power_kw: float) -> None:
537537
session.last_aux_power_kw = power_kw
538538
session.last_aux_update_at = now
539539

540-
def cancel_driving_session(self, vin: str) -> None:
541-
"""Cancel a driving session without saving prediction for continuity.
542-
543-
Used when charging starts (genuine session end, not isMoving flap).
544-
"""
545-
self._driving_sessions.pop(vin, None)
546-
self._last_driving_predicted_soc.pop(vin, None)
547-
548540
# --- Prediction ---
549541

550542
def _get_aux_energy(self, session: DrivingSession) -> float:

custom_components/cardata/number.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ async def async_setup_entry(
6363

6464
# Get all known VINs (both live MQTT data and restored metadata)
6565
all_vins = set(coordinator.data.keys()) | set(coordinator.device_metadata.keys())
66-
66+
6767
# Filter by allowed VINs if initialized
6868
if coordinator._allowed_vins_initialized:
6969
all_vins = all_vins & coordinator._allowed_vins

0 commit comments

Comments
 (0)