From cd02ce1fb9d1db44d2345846d564532d4d3c2975 Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 6 Apr 2026 10:13:48 +0200 Subject: [PATCH] Add switch devices (#28803) --- api/feature.go | 5 +- api/feature_enumer.go | 76 ++++++++++--------- charger/embed_test.go | 25 ++++++ charger/vaillant.go | 2 +- templates/definition/charger/emsesp.yaml | 1 + templates/definition/charger/homewizard.yaml | 1 + templates/definition/charger/lg-therma.yaml | 1 + templates/definition/charger/luxtronik.yaml | 1 + templates/definition/charger/stiebel-lwa.yaml | 1 + templates/definition/charger/stiebel-wpm.yaml | 1 + templates/definition/charger/viessmann.yaml | 1 + .../definition/charger/weishaupt-wpm.yaml | 1 + util/templates/includes/heatpumpswitch.tpl | 7 ++ util/templates/includes/switchsocket.tpl | 1 + 14 files changed, 85 insertions(+), 39 deletions(-) create mode 100644 charger/embed_test.go create mode 100644 util/templates/includes/heatpumpswitch.tpl diff --git a/api/feature.go b/api/feature.go index 452a3b268..117e4a338 100644 --- a/api/feature.go +++ b/api/feature.go @@ -6,8 +6,9 @@ type Feature int const ( _ Feature = iota CoarseCurrent // charger - IntegratedDevice // charger - Heating // charger - heating device + IntegratedDevice // charger - always connected - no vehicle, no charging sessions + SwitchDevice // charger - no current control - heat pumps or switch sockets + Heating // charger - heating device - soc ist temperature (°C) Continuous // charger - heating device where disabled means "normal operation" Average // tariff Cacheable // tariff diff --git a/api/feature_enumer.go b/api/feature_enumer.go index cf271aa77..65a9b871b 100644 --- a/api/feature_enumer.go +++ b/api/feature_enumer.go @@ -7,11 +7,11 @@ import ( "strings" ) -const _FeatureName = "CoarseCurrentIntegratedDeviceHeatingContinuousAverageCacheableOfflineRetryableStreamingWelcomeCharge" +const _FeatureName = "CoarseCurrentIntegratedDeviceSwitchDeviceHeatingContinuousAverageCacheableOfflineRetryableStreamingWelcomeCharge" -var _FeatureIndex = [...]uint8{0, 13, 29, 36, 46, 53, 62, 69, 78, 87, 100} +var _FeatureIndex = [...]uint8{0, 13, 29, 41, 48, 58, 65, 74, 81, 90, 99, 112} -const _FeatureLowerName = "coarsecurrentintegrateddeviceheatingcontinuousaveragecacheableofflineretryablestreamingwelcomecharge" +const _FeatureLowerName = "coarsecurrentintegrateddeviceswitchdeviceheatingcontinuousaveragecacheableofflineretryablestreamingwelcomecharge" func (i Feature) String() string { i -= 1 @@ -27,52 +27,56 @@ func _FeatureNoOp() { var x [1]struct{} _ = x[CoarseCurrent-(1)] _ = x[IntegratedDevice-(2)] - _ = x[Heating-(3)] - _ = x[Continuous-(4)] - _ = x[Average-(5)] - _ = x[Cacheable-(6)] - _ = x[Offline-(7)] - _ = x[Retryable-(8)] - _ = x[Streaming-(9)] - _ = x[WelcomeCharge-(10)] + _ = x[SwitchDevice-(3)] + _ = x[Heating-(4)] + _ = x[Continuous-(5)] + _ = x[Average-(6)] + _ = x[Cacheable-(7)] + _ = x[Offline-(8)] + _ = x[Retryable-(9)] + _ = x[Streaming-(10)] + _ = x[WelcomeCharge-(11)] } -var _FeatureValues = []Feature{CoarseCurrent, IntegratedDevice, Heating, Continuous, Average, Cacheable, Offline, Retryable, Streaming, WelcomeCharge} +var _FeatureValues = []Feature{CoarseCurrent, IntegratedDevice, SwitchDevice, Heating, Continuous, Average, Cacheable, Offline, Retryable, Streaming, WelcomeCharge} var _FeatureNameToValueMap = map[string]Feature{ _FeatureName[0:13]: CoarseCurrent, _FeatureLowerName[0:13]: CoarseCurrent, _FeatureName[13:29]: IntegratedDevice, _FeatureLowerName[13:29]: IntegratedDevice, - _FeatureName[29:36]: Heating, - _FeatureLowerName[29:36]: Heating, - _FeatureName[36:46]: Continuous, - _FeatureLowerName[36:46]: Continuous, - _FeatureName[46:53]: Average, - _FeatureLowerName[46:53]: Average, - _FeatureName[53:62]: Cacheable, - _FeatureLowerName[53:62]: Cacheable, - _FeatureName[62:69]: Offline, - _FeatureLowerName[62:69]: Offline, - _FeatureName[69:78]: Retryable, - _FeatureLowerName[69:78]: Retryable, - _FeatureName[78:87]: Streaming, - _FeatureLowerName[78:87]: Streaming, - _FeatureName[87:100]: WelcomeCharge, - _FeatureLowerName[87:100]: WelcomeCharge, + _FeatureName[29:41]: SwitchDevice, + _FeatureLowerName[29:41]: SwitchDevice, + _FeatureName[41:48]: Heating, + _FeatureLowerName[41:48]: Heating, + _FeatureName[48:58]: Continuous, + _FeatureLowerName[48:58]: Continuous, + _FeatureName[58:65]: Average, + _FeatureLowerName[58:65]: Average, + _FeatureName[65:74]: Cacheable, + _FeatureLowerName[65:74]: Cacheable, + _FeatureName[74:81]: Offline, + _FeatureLowerName[74:81]: Offline, + _FeatureName[81:90]: Retryable, + _FeatureLowerName[81:90]: Retryable, + _FeatureName[90:99]: Streaming, + _FeatureLowerName[90:99]: Streaming, + _FeatureName[99:112]: WelcomeCharge, + _FeatureLowerName[99:112]: WelcomeCharge, } var _FeatureNames = []string{ _FeatureName[0:13], _FeatureName[13:29], - _FeatureName[29:36], - _FeatureName[36:46], - _FeatureName[46:53], - _FeatureName[53:62], - _FeatureName[62:69], - _FeatureName[69:78], - _FeatureName[78:87], - _FeatureName[87:100], + _FeatureName[29:41], + _FeatureName[41:48], + _FeatureName[48:58], + _FeatureName[58:65], + _FeatureName[65:74], + _FeatureName[74:81], + _FeatureName[81:90], + _FeatureName[90:99], + _FeatureName[99:112], } // FeatureString retrieves an enum value from the enum constants string name. diff --git a/charger/embed_test.go b/charger/embed_test.go new file mode 100644 index 000000000..c69d37ed4 --- /dev/null +++ b/charger/embed_test.go @@ -0,0 +1,25 @@ +package charger + +import ( + "testing" + + "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/util" + "github.com/stretchr/testify/require" +) + +func TestEmbed(t *testing.T) { + embed := embed{ + Icon_: "heatpump", + Features_: []api.Feature{api.Continuous, api.Heating, api.IntegratedDevice}, + } + + other := map[string]any{ + "features": []string{"switchdevice"}, + } + + require.NoError(t, util.DecodeOther(other, &embed)) + + // note: slices are not merged + require.Len(t, embed.Features_, 1) +} diff --git a/charger/vaillant.go b/charger/vaillant.go index 33dfa393f..ed3877527 100644 --- a/charger/vaillant.go +++ b/charger/vaillant.go @@ -58,7 +58,7 @@ func NewVaillantFromConfig(ctx context.Context, other map[string]any) (api.Charg }{ embed: embed{ Icon_: "heatpump", - Features_: []api.Feature{api.Continuous, api.Heating, api.IntegratedDevice}, + Features_: []api.Feature{api.Continuous, api.Heating, api.IntegratedDevice, api.SwitchDevice}, }, Realm: sensonet.REALM_GERMANY, Cache: time.Minute, diff --git a/templates/definition/charger/emsesp.yaml b/templates/definition/charger/emsesp.yaml index 677183e7a..5ad96970b 100644 --- a/templates/definition/charger/emsesp.yaml +++ b/templates/definition/charger/emsesp.yaml @@ -192,3 +192,4 @@ render: | uri: http://{{ .host }}/api/boiler/dhw/settemp jq: .value {{- end }} + {{ include "heatpumpswitch" . }} diff --git a/templates/definition/charger/homewizard.yaml b/templates/definition/charger/homewizard.yaml index fd839424a..01f44d4d4 100644 --- a/templates/definition/charger/homewizard.yaml +++ b/templates/definition/charger/homewizard.yaml @@ -8,3 +8,4 @@ params: render: | type: homewizard uri: http://{{ .host }} + {{ include "switchsocket" . }} diff --git a/templates/definition/charger/lg-therma.yaml b/templates/definition/charger/lg-therma.yaml index bad09e27d..1c45b8ea8 100644 --- a/templates/definition/charger/lg-therma.yaml +++ b/templates/definition/charger/lg-therma.yaml @@ -104,3 +104,4 @@ render: | type: input encoding: uint16 {{- end }} + {{ include "heatpumpswitch" . }} diff --git a/templates/definition/charger/luxtronik.yaml b/templates/definition/charger/luxtronik.yaml index 2e72ff302..8395cb1ca 100644 --- a/templates/definition/charger/luxtronik.yaml +++ b/templates/definition/charger/luxtronik.yaml @@ -277,3 +277,4 @@ render: | type: input decode: uint16 scale: 0.1 + {{ include "heatpumpswitch" . }} diff --git a/templates/definition/charger/stiebel-lwa.yaml b/templates/definition/charger/stiebel-lwa.yaml index daed38336..eb1677b39 100644 --- a/templates/definition/charger/stiebel-lwa.yaml +++ b/templates/definition/charger/stiebel-lwa.yaml @@ -110,3 +110,4 @@ render: | encoding: int16 scale: 0.1 {{- end }} + {{ include "heatpumpswitch" . }} diff --git a/templates/definition/charger/stiebel-wpm.yaml b/templates/definition/charger/stiebel-wpm.yaml index ac7012b8e..872c5251e 100644 --- a/templates/definition/charger/stiebel-wpm.yaml +++ b/templates/definition/charger/stiebel-wpm.yaml @@ -107,3 +107,4 @@ render: | encoding: int16 scale: 0.1 {{- end }} + {{ include "heatpumpswitch" . }} diff --git a/templates/definition/charger/viessmann.yaml b/templates/definition/charger/viessmann.yaml index c682522ea..f159ee2c8 100644 --- a/templates/definition/charger/viessmann.yaml +++ b/templates/definition/charger/viessmann.yaml @@ -205,3 +205,4 @@ render: | set: source: error error: ErrNotAvailable + {{ include "heatpumpswitch" . }} diff --git a/templates/definition/charger/weishaupt-wpm.yaml b/templates/definition/charger/weishaupt-wpm.yaml index bf36f203d..a79e9c306 100644 --- a/templates/definition/charger/weishaupt-wpm.yaml +++ b/templates/definition/charger/weishaupt-wpm.yaml @@ -122,3 +122,4 @@ render: | encoding: int16 scale: 0.1 {{- end }} + {{ include "heatpumpswitch" . }} diff --git a/util/templates/includes/heatpumpswitch.tpl b/util/templates/includes/heatpumpswitch.tpl new file mode 100644 index 000000000..e94ce1a60 --- /dev/null +++ b/util/templates/includes/heatpumpswitch.tpl @@ -0,0 +1,7 @@ +{{ define "heatpumpswitch" }} +features: +- continuous +- heating +- integrateddevice +- switchdevice +{{- end }} diff --git a/util/templates/includes/switchsocket.tpl b/util/templates/includes/switchsocket.tpl index 524f069de..01b1056e9 100644 --- a/util/templates/includes/switchsocket.tpl +++ b/util/templates/includes/switchsocket.tpl @@ -1,6 +1,7 @@ {{ define "switchsocket" }} standbypower: {{ .standbypower }} features: +- switchdevice {{- if and .integrateddevice (ne .integrateddevice "false") }} - integrateddevice {{- end }}