Repository: evcc-io/evcc
Pull request #7598 (2023-04-21 23:53:33)

tesla: workaround for north america single phase
Fix https://github.com/evcc-io/evcc/issues/7599

Ignore incorrect values in TWC3 vitals API when `singlephase: true` is set in configuration, use only the A phase and total vehicle current and return values as a single phase charger. Hopefully my translation to the German is close enough.

andig: Thank you @thekev. Are you writing from North America? Would be great if evcc did make it that far :)

Can we avoid the additional setting and the case switch between vehicle and "normal" current? 
thekev: > Thank you @thekev. Are you writing from North America? Would be great if evcc did make it that far :)
Yep. Utah, USA. Tesla EV, 2x Powerwalls, and 19.2kW Solar. A second EV is expected this year, so I’ll also be looking for EVCC to manage two vehicles and load points in the future. 

> Can we avoid the additional setting and the case switch between vehicle and "normal" current? Any way to auto-detect the situation, maybe using the 2-phase voltage supply?
I think I actually can. The anomalous reading on phase C (might be L1 to ground) is a likely indicator I expect you won’t see with true three phase service. I’ll give that a try. Thanks for the feedback. 

andig: Good idea- lets take the 120V +/- 10% or similar as an indicator? 
thekev: > Good idea- lets take the 120V +/- 10% or similar as an indicator?

Something like that. I think I’d compare it to the other phases and 45-55% as the condition to assume it’s the NA version. 

The amperage per phase is also not making sense. Where is that current on nonexistent phase C coming from? If vehicle reading is at 30A, and the sum of two wired phases is ~20A, but the sum of all three is 35A… I don’t know. Something in here has to make the math work. I’ll do some experiments. 

Do you want to avoid using the vehicle number because it may be representing the DC charge rate, and not the AC side (conversion losses)? I’m not clear why there’s a separate reading for that.  

andig: > Do you want to avoid using the vehicle number because it may be representing the DC charge rate, and not the AC side (conversion losses)? I’m not clear why there’s a separate reading for that.

I don't care really. But with https://github.com/evcc-io/evcc/pull/6897 we want to move apparent power calculation to the loadpoint instead of per-meter. To do that we need "real" currents from the chargers, not fake values. Would be great to also sanitise the currents for that purpose.
thekev: @andig I refactored it to remove the configuration directive, instead using the single phase workaround based on the anomalous voltage seen on phase C. I think that should remain compatible with three phase units. I also adjusted the calculation for power to consider each phase's voltage in deriving apparent power.

Patch:
diff --git a/charger/easee.go b/charger/easee.go
index 31fe6f94..0eca474e 100644
--- a/charger/easee.go
+++ b/charger/easee.go
@@ -45,14 +45,13 @@ type Easee struct {
 	charger               string
 	site, circuit         int
 	updated               time.Time
-	chargeStatus          api.ChargeStatus
 	log                   *util.Logger
 	mux                   *sync.Cond
 	dynamicChargerCurrent float64
 	current               float64
 	chargerEnabled        bool
 	smartCharging         bool
-	enabledStatus         bool
+	opMode                int
 	phaseMode             int
 	currentPower, sessionEnergy, totalEnergy,
 	currentL1, currentL2, currentL3 float64
@@ -311,23 +310,7 @@ func (c *Easee) ProductUpdate(i json.RawMessage) {
 			}
 		}
 	case easee.CHARGER_OP_MODE:
-		switch value.(int) {
-		case easee.ModeDisconnected:
-			c.chargeStatus = api.StatusA
-		case easee.ModeAwaitingStart, easee.ModeCompleted, easee.ModeReadyToCharge:
-			c.chargeStatus = api.StatusB
-		case easee.ModeCharging:
-			c.chargeStatus = api.StatusC
-		case easee.ModeError:
-			c.chargeStatus = api.StatusF
-		default:
-			c.chargeStatus = api.StatusNone
-			c.log.ERROR.Printf("unknown opmode: %d", value.(int))
-		}
-		c.enabledStatus = value.(int) == easee.ModeCharging ||
-			value.(int) == easee.ModeAwaitingStart ||
-			value.(int) == easee.ModeCompleted ||
-			value.(int) == easee.ModeReadyToCharge
+		c.opMode = value.(int)
 	}
 }
 
@@ -361,7 +344,21 @@ func (c *Easee) Status() (api.ChargeStatus, error) {
 	c.mux.L.Lock()
 	defer c.mux.L.Unlock()
 
-	return c.chargeStatus, nil
+	res := api.StatusNone
+
+	switch c.opMode {
+	case easee.ModeDisconnected:
+		res = api.StatusA
+	case easee.ModeAwaitingStart, easee.ModeCompleted, easee.ModeReadyToCharge,
+		easee.ModeAwaitingAuthentication, easee.ModeDeauthenticating:
+		res = api.StatusB
+	case easee.ModeCharging:
+		res = api.StatusC
+	default:
+		return res, fmt.Errorf("invalid opmode: %d", c.opMode)
+	}
+
+	return res, nil
 }
 
 // Enabled implements the api.Charger interface
@@ -369,7 +366,12 @@ func (c *Easee) Enabled() (bool, error) {
 	c.mux.L.Lock()
 	defer c.mux.L.Unlock()
 
-	return c.enabledStatus && c.dynamicChargerCurrent > 0, nil
+	enabled := c.opMode == easee.ModeCharging ||
+		c.opMode == easee.ModeAwaitingStart ||
+		c.opMode == easee.ModeCompleted ||
+		c.opMode == easee.ModeReadyToCharge
+
+	return enabled && c.dynamicChargerCurrent > 0, nil
 }
 
 // Enable implements the api.Charger interface
diff --git a/charger/easee/types.go b/charger/easee/types.go
index 506f6ce0..b1ae9f6f 100644
--- a/charger/easee/types.go
+++ b/charger/easee/types.go
@@ -10,13 +10,15 @@ const (
 
 // charge mode definition
 const (
-	ModeOffline       int = 0
-	ModeDisconnected  int = 1
-	ModeAwaitingStart int = 2
-	ModeCharging      int = 3
-	ModeCompleted     int = 4
-	ModeError         int = 5
-	ModeReadyToCharge int = 6
+	ModeOffline                int = 0
+	ModeDisconnected           int = 1
+	ModeAwaitingStart          int = 2
+	ModeCharging               int = 3
+	ModeCompleted              int = 4
+	ModeError                  int = 5
+	ModeReadyToCharge          int = 6
+	ModeAwaitingAuthentication int = 7
+	ModeDeauthenticating       int = 8
 )
 
 // Charger is the charger type
diff --git a/charger/homematic.go b/charger/homematic.go
index aa06a644..30c547f3 100644
--- a/charger/homematic.go
+++ b/charger/homematic.go
@@ -1,6 +1,8 @@
 package charger
 
 import (
+	"time"
+
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/meter/homematic"
 	"github.com/evcc-io/evcc/util"
@@ -18,7 +20,7 @@ func init() {
 
 // NewCCUFromConfig creates a Homematic charger from generic config
 func NewCCUFromConfig(other map[string]interface{}) (api.Charger, error) {
-	var cc struct {
+	cc := struct {
 		embed         `mapstructure:",squash"`
 		URI           string
 		Device        string
@@ -27,18 +29,21 @@ func NewCCUFromConfig(other map[string]interface{}) (api.Charger, error) {
 		User          string
 		Password      string
 		StandbyPower  float64
+		Cache         time.Duration
+	}{
+		Cache: time.Second,
 	}
 
 	if err := util.DecodeOther(other, &cc); err != nil {
 		return nil, err
 	}
 
-	return NewCCU(cc.embed, cc.URI, cc.Device, cc.MeterChannel, cc.SwitchChannel, cc.User, cc.Password, cc.StandbyPower)
+	return NewCCU(cc.embed, cc.URI, cc.Device, cc.MeterChannel, cc.SwitchChannel, cc.User, cc.Password, cc.StandbyPower, cc.Cache)
 }
 
 // NewCCU creates a new connection with standbypower for charger
-func NewCCU(embed embed, uri, deviceid, meterid, switchid, user, password string, standbypower float64) (*CCU, error) {
-	conn, err := homematic.NewConnection(uri, deviceid, meterid, switchid, user, password)
+func NewCCU(embed embed, uri, deviceid, meterid, switchid, user, password string, standbypower float64, cache time.Duration) (*CCU, error) {
+	conn, err := homematic.NewConnection(uri, deviceid, meterid, switchid, user, password, cache)
 
 	c := &CCU{
 		conn: conn,
diff --git a/charger/twc3.go b/charger/twc3.go
index b9dc6486..faaacdff 100644
--- a/charger/twc3.go
+++ b/charger/twc3.go
@@ -3,6 +3,7 @@ package charger
 import (
 	"errors"
 	"fmt"
+	"math"
 	"strings"
 	"time"
 
@@ -16,6 +17,7 @@ import (
 // Twc3 is an api.Vehicle implementation for Twc3 cars
 type Twc3 struct {
 	*request.Helper
+	log     *util.Logger
 	lp      loadpoint.API
 	uri     string
 	vitalsG func() (Vitals, error)
@@ -74,6 +76,7 @@ func NewTwc3FromConfig(other map[string]interface{}) (api.Charger, error) {
 	c := &Twc3{
 		Helper: request.NewHelper(log),
 		uri:    util.DefaultScheme(strings.TrimSuffix(cc.URI, "/"), "http"),
+		log:    log,
 	}
 
 	c.vitalsG = provider.Cached(func() (Vitals, error) {
@@ -161,11 +164,25 @@ func (v *Twc3) ChargingTime() (time.Duration, error) {
 	return time.Duration(res.SessionS) * time.Second, err
 }
 
+// Use workaround if voltageC_v is approximately half of grid_v
+//
+//	"voltageA_v": 241.5,
+//	"voltageB_v": 241.5,
+//	"voltageC_v": 118.7,
+//
+// Default state is ~2V on all phases unless charging
+func (v *Twc3) isSplitPhase(res Vitals) bool {
+	return math.Abs(res.VoltageCV-res.GridV/2) < 25
+}
+
 var _ api.PhaseCurrents = (*Twc3)(nil)
 
 // Currents implements the api.PhaseCurrents interface
 func (v *Twc3) Currents() (float64, float64, float64, error) {
 	res, err := v.vitalsG()
+	if v.isSplitPhase(res) {
+		return res.CurrentAA + res.CurrentBA, 0, 0, err
+	}
 	return res.CurrentAA, res.CurrentBA, res.CurrentCA, err
 }
 
@@ -175,7 +192,10 @@ var _ api.Meter = (*Twc3)(nil)
 func (v *Twc3) CurrentPower() (float64, error) {
 	res, err := v.vitalsG()
 	if res.ContactorClosed {
-		return (res.CurrentAA + res.CurrentBA + res.CurrentCA) * res.GridV, err
+		if v.isSplitPhase(res) {
+			return (res.CurrentAA * res.VoltageAV) + (res.CurrentBA * res.VoltageBV), err
+		}
+		return (res.CurrentAA * res.VoltageAV) + (res.CurrentBA * res.VoltageBV) + (res.CurrentCA * res.VoltageCV), err
 	}
 	return 0, err
 }
@@ -185,6 +205,9 @@ var _ api.PhaseVoltages = (*Twc3)(nil)
 // Voltages implements the api.PhaseVoltages interface
 func (v *Twc3) Voltages() (float64, float64, float64, error) {
 	res, err := v.vitalsG()
+	if v.isSplitPhase(res) {
+		return (res.VoltageAV + res.VoltageBV) / 2, 0, 0, err
+	}
 	return res.VoltageAV, res.VoltageBV, res.VoltageCV, err
 }
 
diff --git a/go.mod b/go.mod
index d990ba8a..b7ffc0a1 100644
--- a/go.mod
+++ b/go.mod
@@ -15,7 +15,7 @@ require (
 	github.com/basgys/goxml2json v1.1.0
 	github.com/basvdlei/gotsmart v0.0.3
 	github.com/benbjohnson/clock v1.3.0
-	github.com/bogosj/tesla v1.1.0
+	github.com/bogosj/tesla v1.1.1-0.20230430222423-0d6e63ee90c9
 	github.com/cenkalti/backoff v2.2.1+incompatible
 	github.com/cjrd/allocate v0.0.0-20220510215731-986f24f0fb18
 	github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
@@ -27,7 +27,7 @@ require (
 	github.com/dustin/go-humanize v1.0.1
 	github.com/dylanmei/iso8601 v0.1.0
 	github.com/eclipse/paho.mqtt.golang v1.4.2
-	github.com/enbility/cemd v0.2.1
+	github.com/enbility/cemd v0.2.2
 	github.com/enbility/eebus-go v0.2.0
 	github.com/fatih/structs v1.1.0
 	github.com/foogod/go-powerwall v0.2.0
@@ -62,7 +62,7 @@ require (
 	github.com/libp2p/zeroconf/v2 v2.2.0
 	github.com/lorenzodonini/ocpp-go v0.16.1-0.20230416102136-782920b66533
 	github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
-	github.com/mabunixda/wattpilot v1.4.1
+	github.com/mabunixda/wattpilot v1.5.0
 	github.com/manifoldco/promptui v0.9.0
 	github.com/mergermarket/go-pkcs7 v0.0.0-20170926155232-153b18ea13c9
 	github.com/mitchellh/go-homedir v1.1.0
@@ -130,6 +130,9 @@ require (
 	github.com/go-logfmt/logfmt v0.6.0 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/gobwas/httphead v0.1.0 // indirect
+	github.com/gobwas/pool v0.2.1 // indirect
+	github.com/gobwas/ws v1.2.0 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/google/go-querystring v1.1.0 // indirect
diff --git a/go.sum b/go.sum
index 4b262e33..df151fda 100644
--- a/go.sum
+++ b/go.sum
@@ -266,8 +266,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
 github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
 github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
 github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
-github.com/bogosj/tesla v1.1.0 h1:p+BOY8HGMT9GYOgvPM7Heu0d66TTkvDbVlVkErkbzDA=
-github.com/bogosj/tesla v1.1.0/go.mod h1:Rh1oHqNrnEVsKvdlOPELq4sLa6Swsuj+lbmZDBk1kfM=
+github.com/bogosj/tesla v1.1.1-0.20230430222423-0d6e63ee90c9 h1:Wd6BCHLS720MX1s2WHvu5Y7lo3HvRC/qONo7VbEbuac=
+github.com/bogosj/tesla v1.1.1-0.20230430222423-0d6e63ee90c9/go.mod h1:eF5996x0SVG6eslBpMA5kEjT1j9Q5gHO3lqovDmZKls=
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
 github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
@@ -347,8 +347,8 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
 github.com/eclipse/paho.mqtt.golang v1.4.2 h1:66wOzfUHSSI1zamx7jR6yMEI5EuHnT1G6rNA5PM12m4=
 github.com/eclipse/paho.mqtt.golang v1.4.2/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA=
 github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
-github.com/enbility/cemd v0.2.1 h1:5EYO4bWl4BowK//CPEQ3ESjFkK4sDtwRxWIf4VL5F00=
-github.com/enbility/cemd v0.2.1/go.mod h1:BZoHbJQJ9/7le4WMFAJWRSgKCCTfNVEOM0c1E3H1JxE=
+github.com/enbility/cemd v0.2.2 h1:NrN7DCxv7C6YD5CaYgiebS/iPA3QmQeugF/hvNFKHNA=
+github.com/enbility/cemd v0.2.2/go.mod h1:BZoHbJQJ9/7le4WMFAJWRSgKCCTfNVEOM0c1E3H1JxE=
 github.com/enbility/eebus-go v0.2.0 h1:znQUfG1QYk0Q+vOacrsSNtXmitF1F2Rx9+ohwcRNlRw=
 github.com/enbility/eebus-go v0.2.0/go.mod h1:Ozg1eDUfSbHfQ1dWfyAUa3h8dMtgM/01eO30kHca5zk=
 github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
@@ -436,12 +436,15 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78
 github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
 github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
 github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA=
-github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
 github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
-github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
+github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
+github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
 github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
-github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
+github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
+github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
+github.com/gobwas/ws v1.2.0 h1:u0p9s3xLYpZCA1z5JgCkMeB34CKCMMQbM+G8Ii7YD0I=
+github.com/gobwas/ws v1.2.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
 github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@@ -565,8 +568,8 @@ github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK6
 github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
 github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
 github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
 github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
@@ -750,8 +753,8 @@ github.com/lorenzodonini/ocpp-go v0.16.1-0.20230416102136-782920b66533/go.mod h1
 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
 github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
-github.com/mabunixda/wattpilot v1.4.1 h1:lQcJ0+w8T+6J7BW2l7RxUeUA393koHSmgM9PK1RVtCg=
-github.com/mabunixda/wattpilot v1.4.1/go.mod h1:1nie1EpKFupf9Eem0azKS2U66OjV/uAIAZDDeovcs+s=
+github.com/mabunixda/wattpilot v1.5.0 h1:+0dINGlqdEhS/hUnkPaJGBMvUwg9LOjbON60vIpYwaE=
+github.com/mabunixda/wattpilot v1.5.0/go.mod h1:NK48nAFsHsP70SwcVfH9OaYBND0UON9i3YrScrWRloA=
 github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
 github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
 github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
@@ -994,9 +997,9 @@ github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
+github.com/smartystreets/assertions v1.13.1 h1:Ef7KhSmjZcK6AVf9YbJdvPYG9avaF0ZxudX+ThRdWfU=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
+github.com/smartystreets/goconvey v1.8.0 h1:Oi49ha/2MURE0WexF052Z0m+BNSGirfjg5RL+JXWq3w=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
diff --git a/meter/homematic.go b/meter/homematic.go
index 0b417f9f..36aec499 100644
--- a/meter/homematic.go
+++ b/meter/homematic.go
@@ -1,6 +1,8 @@
 package meter
 
 import (
+	"time"
+
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/meter/homematic"
 	"github.com/evcc-io/evcc/util"
@@ -18,7 +20,7 @@ func init() {
 
 // NewCCUFromConfig creates a Homematic meter from generic config
 func NewCCUFromConfig(other map[string]interface{}) (api.Meter, error) {
-	var cc struct {
+	cc := struct {
 		URI           string
 		Device        string
 		MeterChannel  string
@@ -26,18 +28,21 @@ func NewCCUFromConfig(other map[string]interface{}) (api.Meter, error) {
 		User          string
 		Password      string
 		Usage         string
+		Cache         time.Duration
+	}{
+		Cache: time.Second,
 	}
 
 	if err := util.DecodeOther(other, &cc); err != nil {
 		return nil, err
 	}
 
-	return NewCCU(cc.URI, cc.Device, cc.MeterChannel, cc.SwitchChannel, cc.User, cc.Password, cc.Usage)
+	return NewCCU(cc.URI, cc.Device, cc.MeterChannel, cc.SwitchChannel, cc.User, cc.Password, cc.Usage, cc.Cache)
 }
 
 // NewCCU creates a new connection with usage for meter
-func NewCCU(uri, deviceid, meterid, switchid, user, password, usage string) (*CCU, error) {
-	conn, err := homematic.NewConnection(uri, deviceid, meterid, switchid, user, password)
+func NewCCU(uri, deviceid, meterid, switchid, user, password, usage string, cache time.Duration) (*CCU, error) {
+	conn, err := homematic.NewConnection(uri, deviceid, meterid, switchid, user, password, cache)
 
 	m := &CCU{
 		conn:  conn,
diff --git a/meter/homematic/connection.go b/meter/homematic/connection.go
index 30f0f854..691789dc 100644
--- a/meter/homematic/connection.go
+++ b/meter/homematic/connection.go
@@ -5,7 +5,9 @@ import (
 	"fmt"
 	"net/http"
 	"strings"
+	"time"
 
+	"github.com/evcc-io/evcc/provider"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/evcc-io/evcc/util/transport"
@@ -18,6 +20,7 @@ import (
 // Homematic CCU settings
 type Settings struct {
 	URI, Device, MeterChannel, SwitchChannel, User, Password string
+	Cache                                                    time.Duration
 }
 
 // Connection is the Homematic CCU connection
@@ -25,10 +28,12 @@ type Connection struct {
 	log *util.Logger
 	*request.Helper
 	*Settings
+	meterCache  provider.Cacheable[MethodResponse]
+	switchCache provider.Cacheable[MethodResponse]
 }
 
 // NewConnection creates a new Homematic device connection.
-func NewConnection(uri, device, meterchannel, switchchannel, user, password string) (*Connection, error) {
+func NewConnection(uri, device, meterchannel, switchchannel, user, password string, cache time.Duration) (*Connection, error) {
 	log := util.NewLogger("homematic")
 
 	settings := &Settings{
@@ -36,6 +41,7 @@ func NewConnection(uri, device, meterchannel, switchchannel, user, password stri
 		Device:        device,
 		MeterChannel:  meterchannel,
 		SwitchChannel: switchchannel,
+		Cache:         cache,
 	}
 
 	conn := &Connection{
@@ -51,13 +57,82 @@ func NewConnection(uri, device, meterchannel, switchchannel, user, password stri
 		conn.Client.Transport = transport.BasicAuth(user, password, conn.Client.Transport)
 	}
 
-	if err := conn.Init(); err != nil {
-		return conn, err
-	}
+	conn.switchCache = provider.ResettableCached(func() (MethodResponse, error) {
+		return conn.XmlCmd("getParamset", conn.SwitchChannel, Param{CCUString: "VALUES"})
+	}, conn.Cache)
+
+	conn.meterCache = provider.ResettableCached(func() (MethodResponse, error) {
+		return conn.XmlCmd("getParamset", conn.MeterChannel, Param{CCUString: "VALUES"})
+	}, conn.Cache)
 
 	return conn, nil
 }
 
+// Enable sets the homematic HMIP-PSM switchchannel state to true=on/false=off
+func (c *Connection) Enable(enable bool) error {
+	onoff := map[bool]string{true: "1", false: "0"}
+	_, err := c.XmlCmd("setValue", c.SwitchChannel, Param{CCUString: "STATE"}, Param{CCUBool: onoff[enable]})
+	if err == nil {
+		c.switchCache.Reset()
+		c.meterCache.Reset()
+	}
+	return err
+}
+
+// Enabled reads the homematic HMIP-PSM switchchannel state true=on/false=off
+func (c *Connection) Enabled() (bool, error) {
+	res, err := c.switchCache.Get()
+	if err != nil {
+		return false, err
+	}
+	return getBoolValue(res, "STATE"), nil
+}
+
+// CurrentPower reads the homematic HMIP-PSM meterchannel power in W
+func (c *Connection) CurrentPower() (float64, error) {
+	res, err := c.meterCache.Get()
+	if err != nil {
+		return 0, err
+	}
+	return getFloatValue(res, "POWER"), nil
+}
+
+// TotalEnergy reads the homematic HMIP-PSM meterchannel energy in Wh
+func (c *Connection) TotalEnergy() (float64, error) {
+	res, err := c.meterCache.Get()
+	if err != nil {
+		return 0, err
+	}
+	return getFloatValue(res, "ENERGY_COUNTER") / 1e3, nil
+}
+
+// Currents reads the homematic HMIP-PSM meterchannel L1 current in A
+func (c *Connection) Currents() (float64, float64, float64, error) {
+	res, err := c.meterCache.Get()
+	if err != nil {
+		return 0, 0, 0, err
+	}
+	return getFloatValue(res, "CURRENT") / 1e3, 0, 0, nil
+}
+
+// GridCurrentPower reads the homematic HM-ES-TX-WM grid meterchannel power in W
+func (c *Connection) GridCurrentPower() (float64, error) {
+	res, err := c.meterCache.Get()
+	if err != nil {
+		return 0, err
+	}
+	return getFloatValue(res, "IEC_POWER"), nil
+}
+
+// GridTotalEnergy reads the homematic HM-ES-TX-WM grid meterchannel energy in kWh
+func (c *Connection) GridTotalEnergy() (float64, error) {
+	res, err := c.meterCache.Get()
+	if err != nil {
+		return 0, err
+	}
+	return getFloatValue(res, "IEC_ENERGY_COUNTER"), nil
+}
+
 func (c *Connection) XmlCmd(method, channel string, values ...Param) (MethodResponse, error) {
 	target := fmt.Sprintf("%s:%s", c.Device, channel)
 	hmc := MethodCall{
@@ -97,53 +172,26 @@ func (c *Connection) XmlCmd(method, channel string, values ...Param) (MethodResp
 	return hmr, parseError(hmr)
 }
 
-// Initialze CCU methods via system.listMethods call
-func (c *Connection) Init() error {
-	_, err := c.XmlCmd("system.listMethods", c.SwitchChannel)
-	return err
-}
-
-// Enabled reads the homematic HMIP-PSM switchchannel state true=on/false=off
-func (c *Connection) Enabled() (bool, error) {
-	res, err := c.XmlCmd("getValue", c.SwitchChannel, Param{CCUString: "STATE"})
-	return res.Value.CCUBool == "1", err
-}
-
-// Enable sets the homematic HMIP-PSM switchchannel state to true=on/false=off
-func (c *Connection) Enable(enable bool) error {
-	onoff := map[bool]string{true: "1", false: "0"}
-	_, err := c.XmlCmd("setValue", c.SwitchChannel, Param{CCUString: "STATE"}, Param{CCUBool: onoff[enable]})
-	return err
-}
-
-// CurrentPower reads the homematic HMIP-PSM meterchannel power in W
-func (c *Connection) CurrentPower() (float64, error) {
-	res, err := c.XmlCmd("getValue", c.MeterChannel, Param{CCUString: "POWER"})
-	return res.Value.CCUFloat, err
-}
+// getCCUFloat selects a float value of a CCU API response member
+func getFloatValue(res MethodResponse, valueName string) float64 {
+	for _, m := range res.Member {
+		if m.Name == valueName {
+			return m.Value.CCUFloat
+		}
+	}
 
-// TotalEnergy reads the homematic HMIP-PSM meterchannel energy in Wh
-func (c *Connection) TotalEnergy() (float64, error) {
-	res, err := c.XmlCmd("getValue", c.MeterChannel, Param{CCUString: "ENERGY_COUNTER"})
-	return res.Value.CCUFloat / 1000, err
+	return 0
 }
 
-// Currents reads the homematic HMIP-PSM meterchannel L1 current in A
-func (c *Connection) Currents() (float64, float64, float64, error) {
-	res, err := c.XmlCmd("getValue", c.MeterChannel, Param{CCUString: "CURRENT"})
-	return res.Value.CCUFloat / 1000, 0, 0, err
-}
-
-// GridCurrentPower reads the homematic HM-ES-TX-WM grid meterchannel power in W
-func (c *Connection) GridCurrentPower() (float64, error) {
-	res, err := c.XmlCmd("getValue", c.MeterChannel, Param{CCUString: "IEC_POWER"})
-	return res.Value.CCUFloat, err
-}
+// getCCUBool selects a float value of a CCU API response member
+func getBoolValue(res MethodResponse, valueName string) bool {
+	for _, m := range res.Member {
+		if m.Name == valueName {
+			return m.Value.CCUBool
+		}
+	}
 
-// GridTotalEnergy reads the homematic HM-ES-TX-WM grid meterchannel energy in Wh
-func (c *Connection) GridTotalEnergy() (float64, error) {
-	res, err := c.XmlCmd("getValue", c.MeterChannel, Param{CCUString: "IEC_ENERGY_COUNTER"})
-	return res.Value.CCUFloat, err
+	return false
 }
 
 // parseError checks on Homematic CCU error codes
@@ -162,6 +210,10 @@ func parseError(res MethodResponse) error {
 		}
 	}
 
+	if faultString == "" {
+		faultString = "Unknown Homematic API Error"
+	}
+
 	if faultCode != 0 {
 		return fmt.Errorf("%s (%v)", faultString, faultCode)
 	}
diff --git a/meter/homematic/types.go b/meter/homematic/types.go
index ee0d84ca..13464c30 100644
--- a/meter/homematic/types.go
+++ b/meter/homematic/types.go
@@ -8,32 +8,36 @@ import (
 // https://homematic-ip.com/sites/default/files/downloads/HM_XmlRpc_API.pdf
 // https://homematic-ip.com/sites/default/files/downloads/HMIP_XmlRpc_API_Addendum.pdf
 
-type Param struct {
-	CCUBool   string  `xml:"value>boolean,omitempty"`
-	CCUFloat  float64 `xml:"value>double,omitempty"`
-	CCUInt    int64   `xml:"value>i4,omitempty"`
-	CCUString string  `xml:"value>string,omitempty"`
-}
-
 type MethodCall struct {
 	XMLName    xml.Name `xml:"methodCall"`
 	MethodName string   `xml:"methodName"`
 	Params     []Param  `xml:"params>param,omitempty"`
 }
 
+type Param struct {
+	CCUBool   string  `xml:"value>boolean,omitempty"`
+	CCUFloat  float64 `xml:"value>double,omitempty"`
+	CCUInt    int64   `xml:"value>i4,omitempty"`
+	CCUString string  `xml:"value>string,omitempty"`
+}
 type Member struct {
-	Name  string     `xml:"name,omitempty"`
-	Value FaultValue `xml:"value,omitempty"`
+	Name  string `xml:"name,omitempty"`
+	Value Value  `xml:"value,omitempty"`
 }
 
-type FaultValue struct {
+type MethodResponse struct {
+	XMLName   xml.Name `xml:"methodResponse"`
+	CCUBool   string   `xml:"params>param>value>boolean,omitempty"`
+	CCUFloat  float64  `xml:"params>param>value>double,omitempty"`
+	CCUInt    int64    `xml:"params>param>value>i4,omitempty"`
+	CCUString string   `xml:"params>param>value>string,omitempty"`
+	Member    []Member `xml:"params>param>value>struct>member,omitempty"`
+	Fault     []Member `xml:"fault>value>struct>member,omitempty"`
+}
+type Value struct {
 	XMLName   xml.Name `xml:"value"`
 	CCUString string   `xml:",chardata"`
 	CCUInt    int64    `xml:"i4,omitempty"`
-}
-
-type MethodResponse struct {
-	XMLName xml.Name `xml:"methodResponse"`
-	Value   Param    `xml:"params>param,omitempty"`
-	Fault   []Member `xml:"fault>value>struct>member,omitempty"`
+	CCUBool   bool     `xml:"boolean,omitempty"`
+	CCUFloat  float64  `xml:"double,omitempty"`
 }
diff --git a/provider/mqtt/client.go b/provider/mqtt/client.go
index afdcaf65..4373822b 100644
--- a/provider/mqtt/client.go
+++ b/provider/mqtt/client.go
@@ -4,12 +4,12 @@ import (
 	"crypto/tls"
 	"fmt"
 	"math/rand"
-	"os"
 	"strings"
 	"sync"
 	"time"
 
 	paho "github.com/eclipse/paho.mqtt.golang"
+	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/util"
 )
 
@@ -129,7 +129,7 @@ func (m *Client) Publish(topic string, retained bool, payload interface{}) error
 	if token.WaitTimeout(publishTimeout) {
 		return token.Error()
 	}
-	return os.ErrDeadlineExceeded
+	return api.ErrTimeout
 }
 
 // Listen validates uniqueness and registers and attaches listener
@@ -146,7 +146,7 @@ func (m *Client) ListenSetter(topic string, callback func(string)) {
 	m.Listen(topic, func(payload string) {
 		callback(payload)
 		if err := m.Publish(topic, true, ""); err != nil {
-			m.log.ERROR.Printf("clear: %v", err)
+			m.log.ERROR.Printf("clear: %s: %v", topic, err)
 		}
 	})
 }
@@ -158,24 +158,22 @@ func (m *Client) listen(topic string) {
 		m.log.TRACE.Printf("recv %s: '%v'", topic, payload)
 		if len(payload) > 0 {
 			m.mux.Lock()
-			callbacks := m.listener[topic]
-			m.mux.Unlock()
-
-			for _, cb := range callbacks {
-				cb(payload)
+			for _, cb := range m.listener[topic] {
+				go cb(payload)
 			}
+			m.mux.Unlock()
 		}
 	})
-	m.WaitForToken(token)
+	m.WaitForToken("subscribe", topic, token)
 }
 
 // WaitForToken synchronously waits until token operation completed
-func (m *Client) WaitForToken(token paho.Token) {
+func (m *Client) WaitForToken(action, topic string, token paho.Token) {
+	err := api.ErrTimeout
 	if token.WaitTimeout(publishTimeout) {
-		if token.Error() != nil {
-			m.log.ERROR.Printf("error: %s", token.Error())
-		}
-	} else {
-		m.log.DEBUG.Println("timeout")
+		err = token.Error()
+	}
+	if err != nil {
+		m.log.ERROR.Printf("%s: %s: %v", action, topic, err)
 	}
 }
diff --git a/server/mqtt.go b/server/mqtt.go
index 9fc8c185..346e5efb 100644
--- a/server/mqtt.go
+++ b/server/mqtt.go
@@ -62,7 +62,7 @@ func (m *MQTT) encode(v interface{}) string {
 
 func (m *MQTT) publishSingleValue(topic string, retained bool, payload interface{}) {
 	token := m.Handler.Client.Publish(topic, m.Handler.Qos, retained, m.encode(payload))
-	go m.Handler.WaitForToken(token)
+	go m.Handler.WaitForToken("send", topic, token)
 }
 
 func (m *MQTT) publish(topic string, retained bool, payload interface{}) {
diff --git a/templates/definition/charger/elli-charger-connect.yaml b/templates/definition/charger/elli-charger-connect.yaml
index 46b6f978..2aba3073 100644
--- a/templates/definition/charger/elli-charger-connect.yaml
+++ b/templates/definition/charger/elli-charger-connect.yaml
@@ -2,8 +2,19 @@ template: elliconnect
 products:
   - brand: Elli
     description:
-      de: Charger Connect (ohne Meter) (Beta)
-      en: Charger Connect (without Meter) (Beta)
+      generic: Charger Connect (Beta)
+  - brand: Volkswagen
+    description:
+      generic: ID. Charger Connect (Beta)
+  - brand: Skoda
+    description:
+      generic: iV Charger Connect (Beta)
+  - brand: Elli
+    description:
+      generic: Charger Connect (Beta)
+  - brand: Audi
+    description:
+      generic: Wallbox plus (Beta)
 capabilities: ["mA"]
 requirements:
   evcc: ["eebus"]
@@ -14,12 +25,16 @@ requirements:
       Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
 
       Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+
+      Hinweis: Wenn du deiner Wallbox nachträglich einen Energiezähler hinzugefügt hast, nutze bitte die Pro bzw. Connected+ Integration.
     en: |
       The device has to have a fix IP address (manuall or via DHCP).
 
       The identification of a vehicle using the RFID card is not possible.
 
       The support is in beta state and problems can occur!
+
+      Note: If you've added an energy meter to your charger please use the Pro or Connected+ integration.
 params:
   - preset: eebus
   - name: ip
diff --git a/templates/definition/charger/elli-charger-pro.yaml b/templates/definition/charger/elli-charger-pro.yaml
index d30be709..a6f11b7d 100644
--- a/templates/definition/charger/elli-charger-pro.yaml
+++ b/templates/definition/charger/elli-charger-pro.yaml
@@ -2,8 +2,19 @@ template: ellipro
 products:
   - brand: Elli
     description:
-      de: Charger Connect (mit Meter)/Pro (Beta)
-      en: Charger Connect (with Meter)/Pro (Beta)
+      generic: Charger Pro (Beta)
+  - brand: Volkswagen
+    description:
+      generic: ID. Charger Pro (Beta)
+  - brand: Skoda
+    description:
+      generic: iV Charger Connect+ (Beta)
+  - brand: Cupra
+    description:
+      generic: Charger Pro (Beta)
+  - brand: Audi
+    description:
+      generic: Audi Wallbox pro (Beta)
 capabilities: ["mA"]
 requirements:
   evcc: ["eebus"]
diff --git a/templates/definition/meter/shelly-1pm.yaml b/templates/definition/meter/shelly-1pm.yaml
index 1c8d38c3..f6514060 100644
--- a/templates/definition/meter/shelly-1pm.yaml
+++ b/templates/definition/meter/shelly-1pm.yaml
@@ -2,7 +2,7 @@ template: shelly-1pm
 products:
   - brand: Shelly
     description:
-      generic: 1PM, EM
+      generic: 1PM, EM, Plug S
 group: switchsockets
 params:
   - name: usage
diff --git a/templates/docs/charger/elliconnect_0.yaml b/templates/docs/charger/elliconnect_0.yaml
index df80550c..0db51911 100644
--- a/templates/docs/charger/elliconnect_0.yaml
+++ b/templates/docs/charger/elliconnect_0.yaml
@@ -1,6 +1,6 @@
 product:
   brand: Elli
-  description: Charger Connect (ohne Meter) (Beta)
+  description: Charger Connect (Beta)
 capabilities: ["mA"]
 requirements: ["eebus"]
 description: |
@@ -10,6 +10,8 @@ description: |
 
   Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
 
+  Hinweis: Wenn du deiner Wallbox nachträglich einen Energiezähler hinzugefügt hast, nutze bitte die Pro bzw. Connected+ Integration.
+
 render:
   - default: |
       type: template
diff --git a/templates/docs/charger/elliconnect_1.yaml b/templates/docs/charger/elliconnect_1.yaml
new file mode 100644
index 00000000..f7f2619a
--- /dev/null
+++ b/templates/docs/charger/elliconnect_1.yaml
@@ -0,0 +1,20 @@
+product:
+  brand: Volkswagen
+  description: ID. Charger Connect (Beta)
+capabilities: ["mA"]
+requirements: ["eebus"]
+description: |
+  Dem Gerät muss eine feste IP Adresse zugewiesen sein (Manuell oder per DHCP).
+
+  Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
+
+  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+
+  Hinweis: Wenn du deiner Wallbox nachträglich einen Energiezähler hinzugefügt hast, nutze bitte die Pro bzw. Connected+ Integration.
+
+render:
+  - default: |
+      type: template
+      template: elliconnect
+      ski: # Die SKI der Wallbox, üblicherweise im Web Interface der Wallbox zu finden
+      ip: 192.0.2.2 # IP-Adresse
diff --git a/templates/docs/charger/elliconnect_2.yaml b/templates/docs/charger/elliconnect_2.yaml
new file mode 100644
index 00000000..3dcb1a7f
--- /dev/null
+++ b/templates/docs/charger/elliconnect_2.yaml
@@ -0,0 +1,20 @@
+product:
+  brand: Skoda
+  description: iV Charger Connect (Beta)
+capabilities: ["mA"]
+requirements: ["eebus"]
+description: |
+  Dem Gerät muss eine feste IP Adresse zugewiesen sein (Manuell oder per DHCP).
+
+  Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
+
+  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+
+  Hinweis: Wenn du deiner Wallbox nachträglich einen Energiezähler hinzugefügt hast, nutze bitte die Pro bzw. Connected+ Integration.
+
+render:
+  - default: |
+      type: template
+      template: elliconnect
+      ski: # Die SKI der Wallbox, üblicherweise im Web Interface der Wallbox zu finden
+      ip: 192.0.2.2 # IP-Adresse
diff --git a/templates/docs/charger/elliconnect_3.yaml b/templates/docs/charger/elliconnect_3.yaml
new file mode 100644
index 00000000..0db51911
--- /dev/null
+++ b/templates/docs/charger/elliconnect_3.yaml
@@ -0,0 +1,20 @@
+product:
+  brand: Elli
+  description: Charger Connect (Beta)
+capabilities: ["mA"]
+requirements: ["eebus"]
+description: |
+  Dem Gerät muss eine feste IP Adresse zugewiesen sein (Manuell oder per DHCP).
+
+  Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
+
+  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+
+  Hinweis: Wenn du deiner Wallbox nachträglich einen Energiezähler hinzugefügt hast, nutze bitte die Pro bzw. Connected+ Integration.
+
+render:
+  - default: |
+      type: template
+      template: elliconnect
+      ski: # Die SKI der Wallbox, üblicherweise im Web Interface der Wallbox zu finden
+      ip: 192.0.2.2 # IP-Adresse
diff --git a/templates/docs/charger/elliconnect_4.yaml b/templates/docs/charger/elliconnect_4.yaml
new file mode 100644
index 00000000..070abde3
--- /dev/null
+++ b/templates/docs/charger/elliconnect_4.yaml
@@ -0,0 +1,20 @@
+product:
+  brand: Audi
+  description: Wallbox plus (Beta)
+capabilities: ["mA"]
+requirements: ["eebus"]
+description: |
+  Dem Gerät muss eine feste IP Adresse zugewiesen sein (Manuell oder per DHCP).
+
+  Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
+
+  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+
+  Hinweis: Wenn du deiner Wallbox nachträglich einen Energiezähler hinzugefügt hast, nutze bitte die Pro bzw. Connected+ Integration.
+
+render:
+  - default: |
+      type: template
+      template: elliconnect
+      ski: # Die SKI der Wallbox, üblicherweise im Web Interface der Wallbox zu finden
+      ip: 192.0.2.2 # IP-Adresse
diff --git a/templates/docs/charger/ellipro_0.yaml b/templates/docs/charger/ellipro_0.yaml
index 4e527dd0..196da10e 100644
--- a/templates/docs/charger/ellipro_0.yaml
+++ b/templates/docs/charger/ellipro_0.yaml
@@ -1,6 +1,6 @@
 product:
   brand: Elli
-  description: Charger Connect (mit Meter)/Pro (Beta)
+  description: Charger Pro (Beta)
 capabilities: ["mA"]
 requirements: ["eebus"]
 description: |
diff --git a/templates/docs/charger/ellipro_1.yaml b/templates/docs/charger/ellipro_1.yaml
new file mode 100644
index 00000000..951700fa
--- /dev/null
+++ b/templates/docs/charger/ellipro_1.yaml
@@ -0,0 +1,18 @@
+product:
+  brand: Volkswagen
+  description: ID. Charger Pro (Beta)
+capabilities: ["mA"]
+requirements: ["eebus"]
+description: |
+  Dem Gerät muss eine feste IP Adresse zugewiesen sein (Manuell oder per DHCP).
+
+  Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
+
+  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+
+render:
+  - default: |
+      type: template
+      template: ellipro
+      ski: # Die SKI der Wallbox, üblicherweise im Web Interface der Wallbox zu finden
+      ip: 192.0.2.2 # IP-Adresse
diff --git a/templates/docs/charger/ellipro_2.yaml b/templates/docs/charger/ellipro_2.yaml
new file mode 100644
index 00000000..c5ad01e8
--- /dev/null
+++ b/templates/docs/charger/ellipro_2.yaml
@@ -0,0 +1,18 @@
+product:
+  brand: Skoda
+  description: iV Charger Connect+ (Beta)
+capabilities: ["mA"]
+requirements: ["eebus"]
+description: |
+  Dem Gerät muss eine feste IP Adresse zugewiesen sein (Manuell oder per DHCP).
+
+  Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
+
+  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+
+render:
+  - default: |
+      type: template
+      template: ellipro
+      ski: # Die SKI der Wallbox, üblicherweise im Web Interface der Wallbox zu finden
+      ip: 192.0.2.2 # IP-Adresse
diff --git a/templates/docs/charger/ellipro_3.yaml b/templates/docs/charger/ellipro_3.yaml
new file mode 100644
index 00000000..3167c13f
--- /dev/null
+++ b/templates/docs/charger/ellipro_3.yaml
@@ -0,0 +1,18 @@
+product:
+  brand: Cupra
+  description: Charger Pro (Beta)
+capabilities: ["mA"]
+requirements: ["eebus"]
+description: |
+  Dem Gerät muss eine feste IP Adresse zugewiesen sein (Manuell oder per DHCP).
+
+  Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
+
+  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+
+render:
+  - default: |
+      type: template
+      template: ellipro
+      ski: # Die SKI der Wallbox, üblicherweise im Web Interface der Wallbox zu finden
+      ip: 192.0.2.2 # IP-Adresse
diff --git a/templates/docs/charger/ellipro_4.yaml b/templates/docs/charger/ellipro_4.yaml
new file mode 100644
index 00000000..9c1abcfb
--- /dev/null
+++ b/templates/docs/charger/ellipro_4.yaml
@@ -0,0 +1,18 @@
+product:
+  brand: Audi
+  description: Audi Wallbox pro (Beta)
+capabilities: ["mA"]
+requirements: ["eebus"]
+description: |
+  Dem Gerät muss eine feste IP Adresse zugewiesen sein (Manuell oder per DHCP).
+
+  Eine Identifikation des Fahrzeugs über die RFID Karte ist nicht möglich.
+
+  Die Unterstützung ist im Beta Stadium und es kann noch zu Problemen kommen!
+
+render:
+  - default: |
+      type: template
+      template: ellipro
+      ski: # Die SKI der Wallbox, üblicherweise im Web Interface der Wallbox zu finden
+      ip: 192.0.2.2 # IP-Adresse
diff --git a/templates/docs/meter/shelly-1pm_0.yaml b/templates/docs/meter/shelly-1pm_0.yaml
index a162b63a..e8308512 100644
--- a/templates/docs/meter/shelly-1pm_0.yaml
+++ b/templates/docs/meter/shelly-1pm_0.yaml
@@ -1,6 +1,6 @@
 product:
   brand: Shelly
-  description: 1PM, EM
+  description: 1PM, EM, Plug S
   group: Schaltbare Steckdosen
 render:
   - usage: pv
diff --git a/templates/evcc.io/brands.json b/templates/evcc.io/brands.json
index 5334dcd6..2c197136 100644
--- a/templates/evcc.io/brands.json
+++ b/templates/evcc.io/brands.json
@@ -4,10 +4,12 @@
   "ABL",
   "Alfen",
   "Alphatec",
+  "Audi",
   "Bender",
   "BMW",
   "cFos",
   "Compleo",
+  "Cupra",
   "Dadapower",
   "DaheimLaden",
   "E.ON Drive",
@@ -43,6 +45,7 @@
   "Schrack",
   "SENEC",
   "Siemens",
+  "Skoda",
   "SMA",
   "Sonnen",
   "Stark in Strom",
@@ -51,6 +54,7 @@
   "TinkerForge",
   "Ubitricity",
   "Vestel",
+  "Volkswagen",
   "Wallbe",
   "wallbox",
   "Walther Werke",
diff --git a/vehicle/tesla.go b/vehicle/tesla.go
index 9eee564e..2e2adff4 100644
--- a/vehicle/tesla.go
+++ b/vehicle/tesla.go
@@ -15,10 +15,8 @@ import (
 // Tesla is an api.Vehicle implementation for Tesla cars
 type Tesla struct {
 	*embed
-	vehicle       *tesla.Vehicle
-	chargeStateG  func() (*tesla.ChargeState, error)
-	vehicleStateG func() (*tesla.VehicleState, error)
-	driveStateG   func() (*tesla.DriveState, error)
+	vehicle *tesla.Vehicle
+	dataG   func() (*tesla.VehicleData, error)
 }
 
 func init() {
@@ -78,19 +76,17 @@ func NewTeslaFromConfig(other map[string]interface{}) (api.Vehicle, error) {
 		v.Title_ = v.vehicle.DisplayName
 	}
 
-	v.chargeStateG = provider.Cached(v.vehicle.ChargeState, cc.Cache)
-	v.vehicleStateG = provider.Cached(v.vehicle.VehicleState, cc.Cache)
-	v.driveStateG = provider.Cached(v.vehicle.DriveState, cc.Cache)
+	v.dataG = provider.Cached(v.vehicle.Data, cc.Cache)
 
 	return v, nil
 }
 
 // Soc implements the api.Vehicle interface
 func (v *Tesla) Soc() (float64, error) {
-	res, err := v.chargeStateG()
+	res, err := v.dataG()
 
 	if err == nil {
-		return float64(res.UsableBatteryLevel), nil
+		return float64(res.Response.ChargeState.UsableBatteryLevel), nil
 	}
 
 	return 0, err
@@ -101,31 +97,30 @@ var _ api.ChargeState = (*Tesla)(nil)
 // Status implements the api.ChargeState interface
 func (v *Tesla) Status() (api.ChargeStatus, error) {
 	status := api.StatusA // disconnected
-	res, err := v.chargeStateG()
+	res, err := v.dataG()
+	if err != nil {
+		return status, err
+	}
 
-	if err == nil {
-		if res.ChargingState == "Stopped" || res.ChargingState == "NoPower" || res.ChargingState == "Complete" {
-			status = api.StatusB
-		}
-		if res.ChargingState == "Charging" {
-			status = api.StatusC
-		}
+	switch res.Response.ChargeState.ChargingState {
+	case "Stopped", "NoPower", "Complete":
+		status = api.StatusB
+	case "Charging":
+		status = api.StatusC
 	}
 
-	return status, err
+	return status, nil
 }
 
 var _ api.ChargeRater = (*Tesla)(nil)
 
 // ChargedEnergy implements the api.ChargeRater interface
 func (v *Tesla) ChargedEnergy() (float64, error) {
-	res, err := v.chargeStateG()
-
-	if err == nil {
-		return res.ChargeEnergyAdded, nil
+	res, err := v.dataG()
+	if err != nil {
+		return 0, err
 	}
-
-	return 0, err
+	return res.Response.ChargeState.ChargeEnergyAdded, nil
 }
 
 const kmPerMile = 1.609344
@@ -134,42 +129,35 @@ var _ api.VehicleRange = (*Tesla)(nil)
 
 // Range implements the api.VehicleRange interface
 func (v *Tesla) Range() (int64, error) {
-	res, err := v.chargeStateG()
-
-	if err == nil {
-		// miles to km
-		return int64(kmPerMile * res.BatteryRange), nil
+	res, err := v.dataG()
+	if err != nil {
+		return 0, err
 	}
-
-	return 0, err
+	// miles to km
+	return int64(kmPerMile * res.Response.ChargeState.BatteryRange), nil
 }
 
 var _ api.VehicleOdometer = (*Tesla)(nil)
 
 // Odometer implements the api.VehicleOdometer interface
 func (v *Tesla) Odometer() (float64, error) {
-	res, err := v.vehicleStateG()
-
-	if err == nil {
-		// miles to km
-		return kmPerMile * res.Odometer, nil
+	res, err := v.dataG()
+	if err != nil {
+		return 0, err
 	}
-
-	return 0, err
+	// miles to km
+	return kmPerMile * res.Response.VehicleState.Odometer, nil
 }
 
 var _ api.VehicleFinishTimer = (*Tesla)(nil)
 
 // FinishTime implements the api.VehicleFinishTimer interface
 func (v *Tesla) FinishTime() (time.Time, error) {
-	res, err := v.chargeStateG()
-
-	if err == nil {
-		t := time.Now()
-		return t.Add(time.Duration(res.MinutesToFullCharge) * time.Minute), err
+	res, err := v.dataG()
+	if err != nil {
+		return time.Time{}, err
 	}
-
-	return time.Time{}, err
+	return time.Now().Add(time.Duration(res.Response.ChargeState.MinutesToFullCharge) * time.Minute), nil
 }
 
 // TODO api.Climater implementation has been removed as it drains battery. Re-check at a later time.
@@ -178,24 +166,22 @@ var _ api.VehiclePosition = (*Tesla)(nil)
 
 // Position implements the api.VehiclePosition interface
 func (v *Tesla) Position() (float64, float64, error) {
-	res, err := v.driveStateG()
-	if err == nil {
-		return res.Latitude, res.Longitude, nil
+	res, err := v.dataG()
+	if err != nil {
+		return 0, 0, err
 	}
-
-	return 0, 0, err
+	return res.Response.DriveState.Latitude, res.Response.DriveState.Longitude, nil
 }
 
 var _ api.SocLimiter = (*Tesla)(nil)
 
 // TargetSoc implements the api.SocLimiter interface
 func (v *Tesla) TargetSoc() (float64, error) {
-	res, err := v.chargeStateG()
-	if err == nil {
-		return float64(res.ChargeLimitSoc), nil
+	res, err := v.dataG()
+	if err != nil {
+		return 0, err
 	}
-
-	return 0, err
+	return float64(res.Response.ChargeState.ChargeLimitSoc), nil
 }
 
 var _ api.CurrentLimiter = (*Tesla)(nil)


Test patch:
diff --git a/meter/homematic/types_test.go b/meter/homematic/types_test.go
index 9d999248..ac60e93c 100644
--- a/meter/homematic/types_test.go
+++ b/meter/homematic/types_test.go
@@ -12,13 +12,38 @@ import (
 func TestUnmarshalMethodResponse(t *testing.T) {
 
 	{
-		// Double response test
+		// BidCos-RF (Port 2001) getParamset measure-channel response test
 		var res MethodResponse
 
-		xmlstr := `<?xml version="1.0" encoding="ISO-8859-1"?><methodResponse><params><param><value><double>20698.0</double></value></param></params></methodResponse>`
+		xmlstr := `<?xml version="1.0" encoding="iso-8859-1"?><methodResponse><params><param><value><struct><member><name>IEC_ENERGY_COUNTER</name><value><double>689.586500</double></value></member><member><name>IEC_POWER</name><value><double>166.390000</double></value></member></struct></value></param></params></methodResponse>`
+		assert.NoError(t, xml.Unmarshal([]byte(strings.Replace(string(xmlstr), "iso-8859-1", "UTF-8", 1)), &res))
+
+		assert.Equal(t, "IEC_ENERGY_COUNTER", res.Member[0].Name)
+		assert.Equal(t, float64(689.586500), res.Member[0].Value.CCUFloat)
+	}
+
+	{
+		// BidCos-IP (Port 2010) getParamset measure-channel response test
+		var res MethodResponse
+
+		xmlstr := `<?xml version="1.0" encoding="ISO-8859-1"?><methodResponse><params><param><value><struct><member><name>VOLTAGE</name><value><double>230.6</double></value></member><member><name>POWER_STATUS</name><value><i4>0</i4></value></member><member><name>ENERGY_COUNTER</name><value><double>10888.7</double></value></member><member><name>CURRENT_STATUS</name><value><i4>0</i4></value></member><member><name>FREQUENCY</name><value><double>49.97</double></value></member><member><name>ENERGY_COUNTER_OVERFLOW</name><value><boolean>0</boolean></value></member><member><name>POWER</name><value><double>0.05</double></value></member><member><name>VOLTAGE_STATUS</name><value><i4>0</i4></value></member><member><name>CURRENT</name><value><double>0.0</double></value></member><member><name>FREQUENCY_STATUS</name><value><i4>0</i4></value></member></struct></value></param></params></methodResponse>`
+		assert.NoError(t, xml.Unmarshal([]byte(strings.Replace(string(xmlstr), "ISO-8859-1", "UTF-8", 1)), &res))
+
+		assert.Equal(t, "ENERGY_COUNTER", res.Member[2].Name)
+		assert.Equal(t, float64(10888.7), res.Member[2].Value.CCUFloat)
+		assert.Equal(t, "POWER", res.Member[6].Name)
+		assert.Equal(t, float64(0.05), res.Member[6].Value.CCUFloat)
+	}
+
+	{
+		// BidCos-IP (Port 2010) getParamset switch-channel response test
+		var res MethodResponse
+
+		xmlstr := `<?xml version="1.0" encoding="ISO-8859-1"?><methodResponse><params><param><value><struct><member><name>SECTION_STATUS</name><value><i4>0</i4></value></member><member><name>PROCESS</name><value><i4>0</i4></value></member><member><name>STATE</name><value><boolean>1</boolean></value></member><member><name>SECTION</name><value><i4>2</i4></value></member></struct></value></param></params></methodResponse>`
 		assert.NoError(t, xml.Unmarshal([]byte(strings.Replace(string(xmlstr), "ISO-8859-1", "UTF-8", 1)), &res))
 
-		assert.Equal(t, float64(20698), res.Value.CCUFloat)
+		assert.Equal(t, "STATE", res.Member[2].Name)
+		assert.Equal(t, true, res.Member[2].Value.CCUBool)
 	}
 
 	{