diff --git a/api/feature.go b/api/feature.go index 9f46f6331..32cb8351c 100644 --- a/api/feature.go +++ b/api/feature.go @@ -18,4 +18,5 @@ const ( WelcomeCharge // vehicle ClimaterDisabled // vehicle - ignore climater state for charge control AutodetectDisabled // vehicle - do not try to identify vehicle by status + WakeUpDisabled // vehicle - do not send wake-up calls ) diff --git a/api/feature_enumer.go b/api/feature_enumer.go index b2510adb2..b07256fe2 100644 --- a/api/feature_enumer.go +++ b/api/feature_enumer.go @@ -7,11 +7,11 @@ import ( "strings" ) -const _FeatureName = "CoarseCurrentIntegratedDeviceSwitchDeviceHeatingContinuousAverageCacheableOfflineRetryableStreamingWelcomeChargeClimaterDisabledAutodetectDisabled" +const _FeatureName = "CoarseCurrentIntegratedDeviceSwitchDeviceHeatingContinuousAverageCacheableOfflineRetryableStreamingWelcomeChargeClimaterDisabledAutodetectDisabledWakeUpDisabled" -var _FeatureIndex = [...]uint8{0, 13, 29, 41, 48, 58, 65, 74, 81, 90, 99, 112, 128, 146} +var _FeatureIndex = [...]uint8{0, 13, 29, 41, 48, 58, 65, 74, 81, 90, 99, 112, 128, 146, 160} -const _FeatureLowerName = "coarsecurrentintegrateddeviceswitchdeviceheatingcontinuousaveragecacheableofflineretryablestreamingwelcomechargeclimaterdisabledautodetectdisabled" +const _FeatureLowerName = "coarsecurrentintegrateddeviceswitchdeviceheatingcontinuousaveragecacheableofflineretryablestreamingwelcomechargeclimaterdisabledautodetectdisabledwakeupdisabled" func (i Feature) String() string { i -= 1 @@ -38,9 +38,10 @@ func _FeatureNoOp() { _ = x[WelcomeCharge-(11)] _ = x[ClimaterDisabled-(12)] _ = x[AutodetectDisabled-(13)] + _ = x[WakeUpDisabled-(14)] } -var _FeatureValues = []Feature{CoarseCurrent, IntegratedDevice, SwitchDevice, Heating, Continuous, Average, Cacheable, Offline, Retryable, Streaming, WelcomeCharge, ClimaterDisabled, AutodetectDisabled} +var _FeatureValues = []Feature{CoarseCurrent, IntegratedDevice, SwitchDevice, Heating, Continuous, Average, Cacheable, Offline, Retryable, Streaming, WelcomeCharge, ClimaterDisabled, AutodetectDisabled, WakeUpDisabled} var _FeatureNameToValueMap = map[string]Feature{ _FeatureName[0:13]: CoarseCurrent, @@ -69,6 +70,8 @@ var _FeatureNameToValueMap = map[string]Feature{ _FeatureLowerName[112:128]: ClimaterDisabled, _FeatureName[128:146]: AutodetectDisabled, _FeatureLowerName[128:146]: AutodetectDisabled, + _FeatureName[146:160]: WakeUpDisabled, + _FeatureLowerName[146:160]: WakeUpDisabled, } var _FeatureNames = []string{ @@ -85,6 +88,7 @@ var _FeatureNames = []string{ _FeatureName[99:112], _FeatureName[112:128], _FeatureName[128:146], + _FeatureName[146:160], } // FeatureString retrieves an enum value from the enum constants string name. diff --git a/core/loadpoint.go b/core/loadpoint.go index c1582b3f4..000cb379c 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -962,7 +962,7 @@ func (lp *Loadpoint) setLimit(current float64) error { if err != nil { v := lp.GetVehicle() - if vv, ok := api.Cap[api.Resurrector](v); ok && errors.Is(err, api.ErrAsleep) { + if vv, ok := api.Cap[api.Resurrector](v); ok && errors.Is(err, api.ErrAsleep) && !hasFeature(v, api.WakeUpDisabled) { // https://github.com/evcc-io/evcc/issues/8254 // wakeup vehicle lp.log.DEBUG.Printf("set charge current limit: waking up vehicle") @@ -983,7 +983,7 @@ func (lp *Loadpoint) setLimit(current float64) error { if enabled := current >= effMinCurrent; enabled != lp.enabled { if err := lp.charger.Enable(enabled); err != nil { v := lp.GetVehicle() - if vv, ok := api.Cap[api.Resurrector](v); enabled && ok && errors.Is(err, api.ErrAsleep) { + if vv, ok := api.Cap[api.Resurrector](v); enabled && ok && errors.Is(err, api.ErrAsleep) && !hasFeature(v, api.WakeUpDisabled) { // https://github.com/evcc-io/evcc/issues/8254 // wakeup vehicle lp.log.DEBUG.Printf("charger %s: waking up vehicle", status[enabled]) @@ -1959,6 +1959,10 @@ func (lp *Loadpoint) processTasks() { // startWakeUpTimer starts wakeUpTimer func (lp *Loadpoint) startWakeUpTimer() { + if lp.vehicleHasFeature(api.WakeUpDisabled) { + return + } + lp.log.DEBUG.Printf("wake-up timer: start") lp.wakeUpTimer.Start() } diff --git a/core/loadpoint_phases_test.go b/core/loadpoint_phases_test.go index 677d34278..36ff6ab50 100644 --- a/core/loadpoint_phases_test.go +++ b/core/loadpoint_phases_test.go @@ -218,6 +218,7 @@ func TestPvScalePhases(t *testing.T) { vehicle := api.NewMockVehicle(ctrl) vehicle.EXPECT().Phases().Return(tc.vehicle).MinTimes(1) vehicle.EXPECT().OnIdentified().Return(api.ActionConfig{}).AnyTimes() + vehicle.EXPECT().Features().AnyTimes() lp := &Loadpoint{ log: util.NewLogger("foo"), diff --git a/core/loadpoint_vehicle_test.go b/core/loadpoint_vehicle_test.go index e3bebee27..f58c8f5f2 100644 --- a/core/loadpoint_vehicle_test.go +++ b/core/loadpoint_vehicle_test.go @@ -353,6 +353,34 @@ func TestDisconnectIntegratedDeviceKeepsMode(t *testing.T) { assert.Equal(t, api.ModeOff, lp.GetMode(), "integrated device disconnect must not reset mode") } +func TestStartWakeUpTimerDisabled(t *testing.T) { + for _, tc := range []struct { + name string + features []api.Feature + wantRunning bool + }{ + {"enabled", nil, true}, + {"disabled", []api.Feature{api.WakeUpDisabled}, false}, + } { + t.Run(tc.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + + vehicle := api.NewMockVehicle(ctrl) + vehicle.EXPECT().Features().Return(tc.features).AnyTimes() + + lp := &Loadpoint{ + log: util.NewLogger("foo"), + vehicle: vehicle, + wakeUpTimer: NewTimer(), + } + + lp.startWakeUpTimer() + + assert.Equal(t, tc.wantRunning, lp.wakeUpTimer.Running()) + }) + } +} + func TestReconnectVehicle(t *testing.T) { tc := []struct { name string diff --git a/templates/definition/vehicle/iso15118.yaml b/templates/definition/vehicle/iso15118.yaml index 3e44b5e06..1b7479f3e 100644 --- a/templates/definition/vehicle/iso15118.yaml +++ b/templates/definition/vehicle/iso15118.yaml @@ -22,6 +22,8 @@ params: deprecated: true - name: autodetectdisabled deprecated: true + - name: wakeupdisabled + deprecated: true render: | type: custom {{ include "vehicle-common" . }} diff --git a/templates/definition/vehicle/offline.yaml b/templates/definition/vehicle/offline.yaml index 6437c90e2..509704b46 100644 --- a/templates/definition/vehicle/offline.yaml +++ b/templates/definition/vehicle/offline.yaml @@ -16,6 +16,8 @@ params: deprecated: true - name: autodetectdisabled deprecated: true + - name: wakeupdisabled + deprecated: true render: | type: custom {{- include "vehicle-common" . }} diff --git a/util/templates/defaults.yaml b/util/templates/defaults.yaml index d4df8a203..b276acc03 100644 --- a/util/templates/defaults.yaml +++ b/util/templates/defaults.yaml @@ -342,6 +342,14 @@ params: help: en: Ignore the vehicle for auto detection of connected vehicles de: Das Fahrzeug von der automatischen Erkennung beim Anschließen eines Fahrzeugs ausschließen + - name: wakeupdisabled + type: bool + description: + en: Disable wake-up calls + de: Aufweck-Aufrufe deaktivieren + help: + en: Do not send wake-up calls to the vehicle or charger, useful for vehicles that refuse to charge after repeated wake-ups. + de: Keine Aufweck-Aufrufe an Fahrzeug oder Ladegerät senden, hilfreich bei Fahrzeugen, die nach wiederholtem Aufwecken das Laden verweigern. - name: heating type: bool description: @@ -611,11 +619,15 @@ presets: advanced: true - name: autodetectdisabled advanced: true + - name: wakeupdisabled + advanced: true vehicle-online: - name: climaterdisabled advanced: true - name: autodetectdisabled advanced: true + - name: wakeupdisabled + advanced: true charger-features: - name: heating advanced: true diff --git a/util/templates/includes/vehicle-features.tpl b/util/templates/includes/vehicle-features.tpl index 07fa44445..f7deac5b5 100644 --- a/util/templates/includes/vehicle-features.tpl +++ b/util/templates/includes/vehicle-features.tpl @@ -1,5 +1,5 @@ {{ define "vehicle-features" }} -{{- if or .basefeatures (eq .coarsecurrent "true") (eq .welcomecharge "true") (eq .streaming "true") (eq .climaterdisabled "true") (eq .autodetectdisabled "true") }} +{{- if or .basefeatures (eq .coarsecurrent "true") (eq .welcomecharge "true") (eq .streaming "true") (eq .climaterdisabled "true") (eq .autodetectdisabled "true") (eq .wakeupdisabled "true") }} features: {{- range .basefeatures }} - {{ . }} @@ -19,5 +19,8 @@ features: {{- if eq .autodetectdisabled "true" }} - autodetectdisabled {{- end }} +{{- if eq .wakeupdisabled "true" }} +- wakeupdisabled +{{- end }} {{- end }} {{- end }} diff --git a/vehicle/template_test.go b/vehicle/template_test.go index 43451ffbc..69f4337f8 100644 --- a/vehicle/template_test.go +++ b/vehicle/template_test.go @@ -42,9 +42,9 @@ func TestTemplates(t *testing.T) { }) } -// universalVehicleFeatures render via the shared vehicle-features include, so a +// onlineVehicleFeatures render via the shared vehicle-features include, so a // stored config may carry them; dropping the param breaks reload (discussion #31291). -var universalVehicleFeatures = []string{"climaterdisabled", "autodetectdisabled"} +var onlineVehicleFeatures = []string{"climaterdisabled", "autodetectdisabled", "wakeupdisabled"} func TestVehicleFeatureParamsConsistent(t *testing.T) { for _, tmpl := range templates.ByClass(templates.Vehicle, templates.WithDeprecated()) { @@ -52,7 +52,7 @@ func TestVehicleFeatureParamsConsistent(t *testing.T) { continue } - for _, feat := range universalVehicleFeatures { + for _, feat := range onlineVehicleFeatures { values := tmpl.Defaults(templates.RenderModeUnitTest) values["template"] = tmpl.Template values[feat] = true