From f700dc6a770e61ea5c212fcfa0ff95f6da0f7134 Mon Sep 17 00:00:00 2001 From: Joni Lehtola <30690083+jonilehtola@users.noreply.github.com> Date: Mon, 22 Jun 2026 19:38:21 +0300 Subject: [PATCH] DriveSomethingGreater: fix charge status for conservation charging and plugged-in detection (#30879) --- vehicle/vw/eudataact/eudataact_test.go | 45 ++++++++++++++++++++++++++ vehicle/vw/eudataact/provider.go | 17 +++++++++- vehicle/vw/eudataact/types.go | 1 + 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/vehicle/vw/eudataact/eudataact_test.go b/vehicle/vw/eudataact/eudataact_test.go index 316ca2360..170aa4619 100644 --- a/vehicle/vw/eudataact/eudataact_test.go +++ b/vehicle/vw/eudataact/eudataact_test.go @@ -43,6 +43,51 @@ func TestStatusPlugStates(t *testing.T) { } } +func TestStatusConservationCharging(t *testing.T) { + tc := []struct { + field string + value string + expected api.ChargeStatus + }{ + {FieldChargingState, "conservationCharging", api.StatusC}, + {FieldCurrentChargeState, "CHARGE_STATE_CONSERVATION_CHARGING", api.StatusC}, + } + + for _, tc := range tc { + data := map[string]point{tc.field: {Value: tc.value}} + p := testProvider(data) + + status, err := p.Status() + require.NoError(t, err) + assert.Equal(t, tc.expected, status, "field=%q value=%q", tc.field, tc.value) + } +} + +func TestStatusChargingScenario(t *testing.T) { + tc := []struct { + scenario string + expected api.ChargeStatus + }{ + {"CHARGING_SCENARIO_IMMEDIATELY_CHARGING_FINISHED", api.StatusB}, + {"CHARGING_SCENARIO_OPTIMISED_CHARGING_FINISHED", api.StatusB}, + {"CHARGING_SCENARIO_CHARGING_TO_DEPARTURE_TIME_FINISHED", api.StatusB}, + {"CHARGING_SCENARIO_IMMEDIATELY_CHARGING_ACTIVE", api.StatusC}, + {"CHARGING_SCENARIO_CHARGING_TO_DEPARTURE_TIME_ACTIVE", api.StatusC}, + {"CHARGING_SCENARIO_OFF", api.StatusA}, + // case-insensitivity: mixed-case value should behave like its uppercased equivalent + {"Charging_Scenario_Off", api.StatusA}, + } + + for _, tc := range tc { + data := map[string]point{FieldChargingScenario: {Value: tc.scenario}} + p := testProvider(data) + + status, err := p.Status() + require.NoError(t, err) + assert.Equal(t, tc.expected, status, "scenario=%q", tc.scenario) + } +} + func TestResolveBrand(t *testing.T) { for _, name := range []string{"audi", "AUDI", "Audi", "aUdI"} { b, ok := resolveBrand(name) diff --git a/vehicle/vw/eudataact/provider.go b/vehicle/vw/eudataact/provider.go index 7707500ad..c0300bdba 100644 --- a/vehicle/vw/eudataact/provider.go +++ b/vehicle/vw/eudataact/provider.go @@ -157,15 +157,30 @@ func (v *Provider) Status() (api.ChargeStatus, error) { return status, err } + // block 1: explicit plug state if p := lookup(data, FieldPlugState, FieldChargingPlug1ConnectionState); p != nil && strings.EqualFold(p.Value, "connected") { status = api.StatusB } + // block 2: flat charging_state field and the current_charge_state field if p := lookup(data, FieldChargingState, FieldCurrentChargeState); p != nil && - (strings.EqualFold(p.Value, "charging") || strings.Contains(strings.ToUpper(p.Value), "CHARGING_HV")) { + (strings.EqualFold(p.Value, "charging") || strings.Contains(strings.ToUpper(p.Value), "CHARGING_HV") || + strings.EqualFold(p.Value, "conservationCharging") || strings.EqualFold(p.Value, "CHARGE_STATE_CONSERVATION_CHARGING")) { status = api.StatusC } + // block 3: charging_scenario is the most explicit plug/charge signal + if p := lookup(data, FieldChargingScenario); p != nil && status != api.StatusC { + upper := strings.ToUpper(p.Value) + switch { + case strings.HasSuffix(upper, "_ACTIVE"): + status = api.StatusC + // the car reports finished if it's plugged in but not charging + case strings.HasSuffix(upper, "_FINISHED"): + status = api.StatusB + } + } + return status, nil } diff --git a/vehicle/vw/eudataact/types.go b/vehicle/vw/eudataact/types.go index e9413c265..35b2ff6f5 100644 --- a/vehicle/vw/eudataact/types.go +++ b/vehicle/vw/eudataact/types.go @@ -104,6 +104,7 @@ const ( FieldChargingState = "charging_state" FieldChargingPlug1ConnectionState = "charging_plug1_connectionstate" FieldCurrentChargeState = "charging_state_report.current_charge_state" + FieldChargingScenario = "charging_state_report.charging_scenario" FieldPlugState = "plug_state" // soc