Repository: evcc-io/evcc
Pull request #18378 (2025-01-23 16:15:00)

chore: refactor caching


Patch:
diff --git a/assets/js/views/Config.vue b/assets/js/views/Config.vue
index 916ef768..0067c650 100644
--- a/assets/js/views/Config.vue
+++ b/assets/js/views/Config.vue
@@ -619,16 +619,17 @@ export default {
 		async updateValues() {
 			clearTimeout(this.deviceValueTimeout);
 			if (!this.offline) {
-				const promises = [
-					...this.meters.map((meter) => this.updateDeviceValue("meter", meter.name)),
-					...this.vehicles.map((vehicle) =>
-						this.updateDeviceValue("vehicle", vehicle.name)
-					),
-				];
-
-				await Promise.all(promises);
+				for (const meter of this.meters) {
+					await this.updateDeviceValue("meter", meter.name);
+				}
+				for (const vehicle of this.vehicles) {
+					await this.updateDeviceValue("vehicle", vehicle.name);
+				}
 			}
-			this.deviceValueTimeout = setTimeout(this.updateValues, 10000);
+			// ensure that component is still mounted
+			if (!this.$el) return;
+			const interval = (store.state?.interval || 30) * 1000;
+			this.deviceValueTimeout = setTimeout(this.updateValues, interval);
 		},
 		deviceTags(type, id) {
 			return this.deviceValues[type]?.[id] || [];
diff --git a/charger/connectiq.go b/charger/connectiq.go
index 33337049..65ff6334 100644
--- a/charger/connectiq.go
+++ b/charger/connectiq.go
@@ -25,7 +25,6 @@ import (
 
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/charger/connectiq"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 )
@@ -71,7 +70,7 @@ func NewConnectIq(uri string, cache time.Duration) (api.Charger, error) {
 	}
 
 	// cache meter readings
-	wb.meterG = plugin.Cached(func() (connectiq.MeterStatus, error) {
+	wb.meterG = util.Cached(func() (connectiq.MeterStatus, error) {
 		var res connectiq.MeterStatus
 		uri := fmt.Sprintf("%s/meter/status", wb.uri)
 		err := wb.GetJSON(uri, &res)
diff --git a/charger/daheimladen.go b/charger/daheimladen.go
index 1ae40880..c409165c 100644
--- a/charger/daheimladen.go
+++ b/charger/daheimladen.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/charger/daheimladen"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"golang.org/x/oauth2"
@@ -22,8 +21,8 @@ type DaheimLaden struct {
 	idTag         string
 	token         string
 	transactionID int32
-	statusG       plugin.Cacheable[daheimladen.GetLatestStatus]
-	meterG        plugin.Cacheable[daheimladen.GetLatestMeterValueResponse]
+	statusG       util.Cacheable[daheimladen.GetLatestStatus]
+	meterG        util.Cacheable[daheimladen.GetLatestMeterValueResponse]
 	cache         time.Duration
 }
 
@@ -67,13 +66,13 @@ func NewDaheimLaden(token, stationID string, cache time.Duration) (*DaheimLaden,
 		Base: c.Client.Transport,
 	}
 
-	c.statusG = plugin.ResettableCached(func() (daheimladen.GetLatestStatus, error) {
+	c.statusG = util.ResettableCached(func() (daheimladen.GetLatestStatus, error) {
 		var res daheimladen.GetLatestStatus
 		err := c.GetJSON(fmt.Sprintf("%s/cs/%s/status", daheimladen.BASE_URL, c.stationID), &res)
 		return res, err
 	}, c.cache)
 
-	c.meterG = plugin.ResettableCached(func() (daheimladen.GetLatestMeterValueResponse, error) {
+	c.meterG = util.ResettableCached(func() (daheimladen.GetLatestMeterValueResponse, error) {
 		var res daheimladen.GetLatestMeterValueResponse
 		err := c.GetJSON(fmt.Sprintf("%s/cs/%s/metervalue", daheimladen.BASE_URL, c.stationID), &res)
 		return res, err
diff --git a/charger/eebus.go b/charger/eebus.go
index 522f346b..b1e1268e 100644
--- a/charger/eebus.go
+++ b/charger/eebus.go
@@ -15,7 +15,6 @@ import (
 	"github.com/enbility/spine-go/model"
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/core/loadpoint"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/server/eebus"
 	"github.com/evcc-io/evcc/util"
 	"github.com/samber/lo"
@@ -88,7 +87,7 @@ func NewEEBus(ski, ip string, hasMeter, hasChargedEnergy, vasVW bool) (api.Charg
 	}
 
 	c.Connector = eebus.NewConnector()
-	c.minMaxG = plugin.Cached(c.minMax, time.Second)
+	c.minMaxG = util.Cached(c.minMax, time.Second)
 
 	if err := eebus.Instance.RegisterDevice(ski, ip, c); err != nil {
 		return nil, err
diff --git a/charger/evsewifi.go b/charger/evsewifi.go
index aa3254d0..24d5f95c 100644
--- a/charger/evsewifi.go
+++ b/charger/evsewifi.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/charger/evse"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 )
@@ -20,7 +19,7 @@ type EVSEWifi struct {
 	alwaysActive bool
 	current      int64 // current will always be the physical value sent to the API
 	hires        bool
-	paramG       plugin.Cacheable[evse.ListEntry]
+	paramG       util.Cacheable[evse.ListEntry]
 }
 
 func init() {
@@ -122,7 +121,7 @@ func NewEVSEWifi(uri string, cache time.Duration) (*EVSEWifi, error) {
 		current: 6, // 6A defined value
 	}
 
-	wb.paramG = plugin.ResettableCached(func() (evse.ListEntry, error) {
+	wb.paramG = util.ResettableCached(func() (evse.ListEntry, error) {
 		var res evse.ParameterResponse
 		uri := fmt.Sprintf("%s/getParameters", wb.uri)
 		if err := wb.GetJSON(uri, &res); err != nil {
diff --git a/charger/hardybarth-ecb1.go b/charger/hardybarth-ecb1.go
index eb2f2a54..fc62cccc 100644
--- a/charger/hardybarth-ecb1.go
+++ b/charger/hardybarth-ecb1.go
@@ -30,7 +30,6 @@ import (
 	"github.com/evcc-io/evcc/charger/echarge"
 	"github.com/evcc-io/evcc/charger/echarge/ecb1"
 	"github.com/evcc-io/evcc/meter/obis"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/evcc-io/evcc/util/sponsor"
@@ -85,7 +84,7 @@ func NewHardyBarth(uri string, chargecontrol, meter int, cache time.Duration) (a
 	}
 
 	// cache meter readings
-	wb.meterG = plugin.Cached(func() (ecb1.Meter, error) {
+	wb.meterG = util.Cached(func() (ecb1.Meter, error) {
 		var res struct {
 			Meter struct {
 				ecb1.Meter
diff --git a/charger/hardybarth-salia.go b/charger/hardybarth-salia.go
index 63880b63..f55415af 100644
--- a/charger/hardybarth-salia.go
+++ b/charger/hardybarth-salia.go
@@ -30,7 +30,6 @@ import (
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/charger/echarge"
 	"github.com/evcc-io/evcc/charger/echarge/salia"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/evcc-io/evcc/util/sponsor"
@@ -46,7 +45,7 @@ type Salia struct {
 	uri     string
 	current int64
 	fw      int // 2 if fw 2.0
-	apiG    plugin.Cacheable[salia.Api]
+	apiG    util.Cacheable[salia.Api]
 }
 
 func init() {
@@ -84,7 +83,7 @@ func NewSalia(ctx context.Context, uri string, cache time.Duration) (api.Charger
 		current: 6,
 	}
 
-	wb.apiG = plugin.ResettableCached(func() (salia.Api, error) {
+	wb.apiG = util.ResettableCached(func() (salia.Api, error) {
 		var res salia.Api
 		err := wb.GetJSON(wb.uri, &res)
 		return res, err
diff --git a/charger/mystrom.go b/charger/mystrom.go
index 504d5dd7..1e38957d 100644
--- a/charger/mystrom.go
+++ b/charger/mystrom.go
@@ -6,7 +6,6 @@ import (
 
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/meter/mystrom"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 )
 
@@ -21,7 +20,7 @@ func init() {
 type MyStrom struct {
 	*switchSocket
 	conn    *mystrom.Connection
-	reportG plugin.Cacheable[mystrom.Report]
+	reportG util.Cacheable[mystrom.Report]
 }
 
 // NewMyStromFromConfig creates a myStrom charger from generic config
@@ -44,7 +43,7 @@ func NewMyStromFromConfig(other map[string]interface{}) (api.Charger, error) {
 	}
 
 	c.switchSocket = NewSwitchSocket(&cc.embed, c.Enabled, c.conn.CurrentPower, cc.StandbyPower)
-	c.reportG = plugin.ResettableCached(c.conn.Report, cc.Cache)
+	c.reportG = util.ResettableCached(c.conn.Report, cc.Cache)
 
 	return c, nil
 }
diff --git a/charger/nrgconnect.go b/charger/nrgconnect.go
index cffc63d8..4403850d 100644
--- a/charger/nrgconnect.go
+++ b/charger/nrgconnect.go
@@ -9,7 +9,6 @@ import (
 
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/charger/nrg/connect"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 )
@@ -23,8 +22,8 @@ type NRGKickConnect struct {
 	mac           string
 	password      string
 	enabled       bool
-	settingsG     plugin.Cacheable[connect.Settings]
-	measurementsG plugin.Cacheable[connect.Measurements]
+	settingsG     util.Cacheable[connect.Settings]
+	measurementsG util.Cacheable[connect.Measurements]
 }
 
 func init() {
@@ -56,7 +55,7 @@ func NewNRGKickConnect(uri, mac, password string, cache time.Duration) (*NRGKick
 		password: password,
 	}
 
-	nrg.settingsG = plugin.ResettableCached(func() (connect.Settings, error) {
+	nrg.settingsG = util.ResettableCached(func() (connect.Settings, error) {
 		var res connect.Settings
 
 		err := nrg.GetJSON(nrg.apiURL(connect.SettingsPath), &res)
@@ -67,7 +66,7 @@ func NewNRGKickConnect(uri, mac, password string, cache time.Duration) (*NRGKick
 		return res, err
 	}, cache)
 
-	nrg.measurementsG = plugin.ResettableCached(func() (connect.Measurements, error) {
+	nrg.measurementsG = util.ResettableCached(func() (connect.Measurements, error) {
 		var res connect.Measurements
 
 		err := nrg.GetJSON(nrg.apiURL(connect.MeasurementsPath), &res)
diff --git a/charger/openevse.go b/charger/openevse.go
index bf7e171e..43a5e34a 100644
--- a/charger/openevse.go
+++ b/charger/openevse.go
@@ -10,7 +10,6 @@ import (
 
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/charger/openevse"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/evcc-io/evcc/util/transport"
@@ -20,7 +19,7 @@ import (
 type OpenEVSE struct {
 	*request.Helper
 	uri     string
-	statusG plugin.Cacheable[openevse.Status]
+	statusG util.Cacheable[openevse.Status]
 	current int
 	enabled bool
 }
@@ -67,7 +66,7 @@ func NewOpenEVSE(uri, user, password string, cache time.Duration) (api.Charger,
 		c.Client.Transport = transport.BasicAuth(user, password, c.Client.Transport)
 	}
 
-	c.statusG = plugin.ResettableCached(func() (openevse.Status, error) {
+	c.statusG = util.ResettableCached(func() (openevse.Status, error) {
 		var res openevse.Status
 
 		uri := fmt.Sprintf("%s/status", c.uri)
diff --git a/charger/openwb-pro.go b/charger/openwb-pro.go
index 5a53dd2c..01c44758 100644
--- a/charger/openwb-pro.go
+++ b/charger/openwb-pro.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/charger/openwb/pro"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 )
@@ -24,7 +23,7 @@ type OpenWBPro struct {
 	*request.Helper
 	uri     string
 	current float64
-	statusG plugin.Cacheable[pro.Status]
+	statusG util.Cacheable[pro.Status]
 }
 
 // NewOpenWBProFromConfig creates a OpenWBPro charger from generic config
@@ -53,7 +52,7 @@ func NewOpenWBPro(ctx context.Context, uri string, cache time.Duration) (*OpenWB
 		current: 6, // 6A defined value
 	}
 
-	wb.statusG = plugin.ResettableCached(func() (pro.Status, error) {
+	wb.statusG = util.ResettableCached(func() (pro.Status, error) {
 		var res pro.Status
 		uri := fmt.Sprintf("%s/%s", wb.uri, "connect.php")
 		err := wb.GetJSON(uri, &res)
diff --git a/charger/smaevcharger.go b/charger/smaevcharger.go
index cd32f098..5b7ef852 100644
--- a/charger/smaevcharger.go
+++ b/charger/smaevcharger.go
@@ -26,7 +26,6 @@ import (
 
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/charger/smaevcharger"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/evcc-io/evcc/util/sponsor"
@@ -41,8 +40,8 @@ type Smaevcharger struct {
 	uri          string // 192.168.XXX.XXX
 	cache        time.Duration
 	oldstate     float64
-	measurementG plugin.Cacheable[[]smaevcharger.Measurements]
-	parameterG   plugin.Cacheable[[]smaevcharger.Parameters]
+	measurementG util.Cacheable[[]smaevcharger.Measurements]
+	parameterG   util.Cacheable[[]smaevcharger.Parameters]
 }
 
 func init() {
@@ -95,8 +94,8 @@ func NewSmaevcharger(uri, user, password string, cache time.Duration) (api.Charg
 	}
 
 	// setup cached values
-	wb.measurementG = plugin.ResettableCached(wb._measurementData, wb.cache)
-	wb.parameterG = plugin.ResettableCached(wb._parameterData, wb.cache)
+	wb.measurementG = util.ResettableCached(wb._measurementData, wb.cache)
+	wb.parameterG = util.ResettableCached(wb._parameterData, wb.cache)
 
 	ts, err := smaevcharger.TokenSource(log, wb.uri, user, password)
 	if err != nil {
diff --git a/charger/trydan.go b/charger/trydan.go
index f9534caa..b8257a57 100644
--- a/charger/trydan.go
+++ b/charger/trydan.go
@@ -9,7 +9,6 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/evcc-io/evcc/util/sponsor"
@@ -43,7 +42,7 @@ type RealTimeData struct {
 type Trydan struct {
 	*request.Helper
 	uri     string
-	statusG plugin.Cacheable[RealTimeData]
+	statusG util.Cacheable[RealTimeData]
 	current int
 	enabled bool
 }
@@ -83,7 +82,7 @@ func NewTrydan(uri string, cache time.Duration) (api.Charger, error) {
 		uri:    util.DefaultScheme(strings.TrimSuffix(uri, "/"), "http"),
 	}
 
-	c.statusG = plugin.ResettableCached(func() (RealTimeData, error) {
+	c.statusG = util.ResettableCached(func() (RealTimeData, error) {
 		var res RealTimeData
 		uri := fmt.Sprintf("%s/RealTimeData", c.uri)
 		err := c.GetJSON(uri, &res)
diff --git a/charger/twc3.go b/charger/twc3.go
index 2372698b..1b8657dd 100644
--- a/charger/twc3.go
+++ b/charger/twc3.go
@@ -9,7 +9,6 @@ import (
 
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/core/loadpoint"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 )
@@ -73,7 +72,7 @@ func NewTwc3FromConfig(other map[string]interface{}) (api.Charger, error) {
 	client := request.NewHelper(util.NewLogger("twc3"))
 	uri := fmt.Sprintf("%s/api/1/vitals", util.DefaultScheme(strings.TrimSuffix(cc.URI, "/"), "http"))
 
-	c.vitalsG = plugin.Cached(func() (Vitals, error) {
+	c.vitalsG = util.Cached(func() (Vitals, error) {
 		var res Vitals
 		err := client.GetJSON(uri, &res)
 		return res, err
diff --git a/charger/vaillant.go b/charger/vaillant.go
index 87aa39bd..7eae9b8c 100644
--- a/charger/vaillant.go
+++ b/charger/vaillant.go
@@ -26,7 +26,6 @@ import (
 
 	"github.com/WulfgarW/sensonet"
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/samber/lo"
@@ -127,7 +126,7 @@ func NewVaillantFromConfig(ctx context.Context, other map[string]interface{}) (a
 
 	var power func() (float64, error)
 	if devices, _ := conn.GetMpcData(systemId); len(devices) > 0 {
-		power = plugin.Cached(func() (float64, error) {
+		power = util.Cached(func() (float64, error) {
 			res, err := conn.GetMpcData(systemId)
 			return lo.SumBy(res, func(d sensonet.MpcDevice) float64 {
 				return d.CurrentPower
@@ -153,7 +152,7 @@ func NewVaillantFromConfig(ctx context.Context, other map[string]interface{}) (a
 
 	var temp func() (float64, error)
 	if !heating || heatingTempSensor {
-		temp = plugin.Cached(func() (float64, error) {
+		temp = util.Cached(func() (float64, error) {
 			system, err := conn.GetSystem(systemId)
 			if err != nil {
 				return 0, err
diff --git a/charger/versicharge.go b/charger/versicharge.go
index 90670c29..72bb5b96 100644
--- a/charger/versicharge.go
+++ b/charger/versicharge.go
@@ -149,7 +149,9 @@ func (wb *Versicharge) CurrentPower() (float64, error) {
 
 	var sum float64
 	for i := range 3 {
-		sum += float64(binary.BigEndian.Uint16(b[2*i:]))
+		if u := binary.BigEndian.Uint16(b[2*i:]); u != 0xFFFF {
+			sum += float64(u)
+		}
 	}
 
 	return sum, nil
diff --git a/charger/zaptec.go b/charger/zaptec.go
index 871154f3..252038ac 100644
--- a/charger/zaptec.go
+++ b/charger/zaptec.go
@@ -28,7 +28,6 @@ import (
 	"github.com/coreos/go-oidc/v3/oidc"
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/charger/zaptec"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/evcc-io/evcc/util/sponsor"
@@ -43,7 +42,7 @@ import (
 type Zaptec struct {
 	*request.Helper
 	log      *util.Logger
-	statusG  plugin.Cacheable[zaptec.StateResponse]
+	statusG  util.Cacheable[zaptec.StateResponse]
 	id       string
 	enabled  bool
 	priority bool
@@ -91,7 +90,7 @@ func NewZaptec(user, password, id string, priority bool, cache time.Duration) (a
 	}
 
 	// setup cached values
-	c.statusG = plugin.ResettableCached(func() (zaptec.StateResponse, error) {
+	c.statusG = util.ResettableCached(func() (zaptec.StateResponse, error) {
 		var res zaptec.StateResponse
 
 		uri := fmt.Sprintf("%s/api/chargers/%s/state", zaptec.ApiURL, c.id)
diff --git a/cmd/setup.go b/cmd/setup.go
index 3254f4a7..5cbeae6b 100644
--- a/cmd/setup.go
+++ b/cmd/setup.go
@@ -97,9 +97,9 @@ func loadConfigFile(conf *globalconfig.All, checkDB bool) error {
 	}
 
 	// user did not specify a database path
-	if conf.Database.Dsn == "" {
+	if conf.Database.Dsn == "" && checkDB {
 		// check if service database exists
-		if _, err := os.Stat(serviceDB); err == nil && checkDB {
+		if _, err := os.Stat(serviceDB); err == nil {
 			// service database found, ask user what to do
 			sudo := ""
 			if !isWritable(serviceDB) {
@@ -120,9 +120,6 @@ evcc --database ~/.evcc/evcc.db
 If you know what you're doing, you can skip the database check with the --ignore-db flag.
 			`)
 		}
-
-		// default to user database
-		conf.Database.Dsn = userDB
 	}
 
 	// parse log levels after reading config
@@ -525,7 +522,7 @@ func configureEnvironment(cmd *cobra.Command, conf *globalconfig.All) (err error
 // configureDatabase configures session database
 func configureDatabase(conf globalconfig.DB) error {
 	if conf.Dsn == "" {
-		return errors.New("database dsn not configured")
+		conf.Dsn = userDB
 	}
 
 	if err := db.NewInstance(conf.Type, conf.Dsn); err != nil {
diff --git a/core/loadpoint.go b/core/loadpoint.go
index bca0447b..e6cff397 100644
--- a/core/loadpoint.go
+++ b/core/loadpoint.go
@@ -23,7 +23,6 @@ import (
 	"github.com/evcc-io/evcc/core/soc"
 	"github.com/evcc-io/evcc/core/vehicle"
 	"github.com/evcc-io/evcc/core/wrapper"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/push"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/config"
@@ -457,7 +456,7 @@ func (lp *Loadpoint) evChargeStopHandler() {
 	}
 
 	// soc update reset
-	plugin.ResetCached()
+	util.ResetCached()
 	lp.socUpdated = time.Time{}
 
 	// reset pv enable/disable timer
diff --git a/core/loadpoint_vehicle.go b/core/loadpoint_vehicle.go
index 2b280e1a..e7022d47 100644
--- a/core/loadpoint_vehicle.go
+++ b/core/loadpoint_vehicle.go
@@ -13,7 +13,7 @@ import (
 	"github.com/evcc-io/evcc/core/session"
 	"github.com/evcc-io/evcc/core/soc"
 	"github.com/evcc-io/evcc/core/vehicle"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 )
 
 const (
@@ -234,7 +234,7 @@ func (lp *Loadpoint) vehicleUnidentified() bool {
 	select {
 	case <-lp.vehicleDetectTicker.C:
 		lp.log.DEBUG.Println("vehicle api refresh")
-		plugin.ResetCached()
+		util.ResetCached()
 	default:
 	}
 
@@ -260,7 +260,7 @@ func (lp *Loadpoint) vehicleDefaultOrDetect() {
 func (lp *Loadpoint) startVehicleDetection() {
 	// flush all vehicles before detection starts
 	lp.log.DEBUG.Println("vehicle api refresh")
-	plugin.ResetCached()
+	util.ResetCached()
 
 	lp.vehicleDetect = lp.clock.Now()
 	lp.vehicleDetectTicker = lp.clock.Ticker(vehicleDetectInterval)
diff --git a/go.mod b/go.mod
index c8803c2e..231188b6 100644
--- a/go.mod
+++ b/go.mod
@@ -56,7 +56,7 @@ require (
 	github.com/jeremywohl/flatten v1.0.1
 	github.com/jinzhu/copier v0.4.0
 	github.com/jinzhu/now v1.1.5
-	github.com/joeshaw/carwings v0.0.0-20240517194654-cf29a185820c
+	github.com/joeshaw/carwings v0.0.0-20250124122309-e366d592915c
 	github.com/joho/godotenv v1.5.1
 	github.com/jpfielding/go-http-digest v0.0.0-20240123121450-cffc47d5d6d8
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
diff --git a/go.sum b/go.sum
index d464b6ab..21e65b86 100644
--- a/go.sum
+++ b/go.sum
@@ -358,8 +358,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
-github.com/joeshaw/carwings v0.0.0-20240517194654-cf29a185820c h1:2yTL3V9KSVPWO+Q/Uu6p9y/3ribJNFnUvosKhKkOtTw=
-github.com/joeshaw/carwings v0.0.0-20240517194654-cf29a185820c/go.mod h1:tB0OlpicmRVTL1Vksc5XRiYo+wkK2kl/GI7eGMIl6Rs=
+github.com/joeshaw/carwings v0.0.0-20250124122309-e366d592915c h1:0B5ZN4KU2Zy6xn21hSnovLsXQX1pF+vNYG7rlh5m3iM=
+github.com/joeshaw/carwings v0.0.0-20250124122309-e366d592915c/go.mod h1:tB0OlpicmRVTL1Vksc5XRiYo+wkK2kl/GI7eGMIl6Rs=
 github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
 github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
diff --git a/hems/eebus/eebus.go b/hems/eebus/eebus.go
index 21eebe39..12f75515 100644
--- a/hems/eebus/eebus.go
+++ b/hems/eebus/eebus.go
@@ -9,7 +9,6 @@ import (
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/core/circuit"
 	"github.com/evcc-io/evcc/core/site"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/server/eebus"
 	"github.com/evcc-io/evcc/util"
 )
@@ -30,7 +29,7 @@ type EEBus struct {
 	failsafeLimit    float64
 	failsafeDuration time.Duration
 
-	heartbeat *plugin.Value[struct{}]
+	heartbeat *util.Value[struct{}]
 }
 
 type Limits struct {
@@ -90,7 +89,7 @@ func NewEEBus(ski string, limits Limits, root api.Circuit) (*EEBus, error) {
 		root:      root,
 		uc:        eebus.Instance.ControllableSystem(),
 		Connector: eebus.NewConnector(),
-		heartbeat: plugin.NewValue[struct{}](2 * time.Minute), // LPC-031
+		heartbeat: util.NewValue[struct{}](2 * time.Minute), // LPC-031
 
 		consumptionLimit: &ucapi.LoadLimit{
 			Value:        limits.ConsumptionLimit,
diff --git a/meter/discovergy.go b/meter/discovergy.go
index 0a310179..5b4fb9e8 100644
--- a/meter/discovergy.go
+++ b/meter/discovergy.go
@@ -6,7 +6,6 @@ import (
 
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/meter/discovergy"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/evcc-io/evcc/util/transport"
@@ -74,7 +73,7 @@ func NewDiscovergyFromConfig(other map[string]interface{}) (api.Meter, error) {
 		}))
 	}
 
-	dataG := plugin.Cached(func() (discovergy.Reading, error) {
+	dataG := util.Cached(func() (discovergy.Reading, error) {
 		var res discovergy.Reading
 		uri := fmt.Sprintf("%s/last_reading?meterId=%s", discovergy.API, meterID)
 		err := client.GetJSON(uri, &res)
diff --git a/meter/eebus.go b/meter/eebus.go
index fdd54068..909130ce 100644
--- a/meter/eebus.go
+++ b/meter/eebus.go
@@ -8,7 +8,6 @@ import (
 	"github.com/enbility/eebus-go/usecases/ma/mgcp"
 	spineapi "github.com/enbility/spine-go/api"
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/server/eebus"
 	"github.com/evcc-io/evcc/util"
 )
@@ -19,8 +18,8 @@ type EEBus struct {
 	*eebus.Connector
 	uc *eebus.UseCasesCS
 
-	power, energy      *plugin.Value[float64]
-	voltages, currents *plugin.Value[[]float64]
+	power, energy      *util.Value[float64]
+	voltages, currents *util.Value[[]float64]
 }
 
 func init() {
@@ -54,10 +53,10 @@ func NewEEBus(ski, ip string, timeout time.Duration) (*EEBus, error) {
 		log:       util.NewLogger("eebus"),
 		uc:        eebus.Instance.ControllableSystem(),
 		Connector: eebus.NewConnector(),
-		power:     plugin.NewValue[float64](timeout),
-		energy:    plugin.NewValue[float64](timeout),
-		voltages:  plugin.NewValue[[]float64](timeout),
-		currents:  plugin.NewValue[[]float64](timeout),
+		power:     util.NewValue[float64](timeout),
+		energy:    util.NewValue[float64](timeout),
+		voltages:  util.NewValue[[]float64](timeout),
+		currents:  util.NewValue[[]float64](timeout),
 	}
 
 	if err := eebus.Instance.RegisterDevice(ski, ip, c); err != nil {
diff --git a/meter/homematic/connection.go b/meter/homematic/connection.go
index ce7ae0ac..59083d1f 100644
--- a/meter/homematic/connection.go
+++ b/meter/homematic/connection.go
@@ -8,7 +8,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/evcc-io/evcc/util/transport"
@@ -29,8 +28,8 @@ type Connection struct {
 	log *util.Logger
 	*request.Helper
 	*Settings
-	meterG  plugin.Cacheable[MethodResponse]
-	switchG plugin.Cacheable[MethodResponse]
+	meterG  util.Cacheable[MethodResponse]
+	switchG util.Cacheable[MethodResponse]
 }
 
 // NewConnection creates a new Homematic device connection.
@@ -58,11 +57,11 @@ func NewConnection(uri, device, meterchannel, switchchannel, user, password stri
 		conn.Client.Transport = transport.BasicAuth(user, password, conn.Client.Transport)
 	}
 
-	conn.switchG = plugin.ResettableCached(func() (MethodResponse, error) {
+	conn.switchG = util.ResettableCached(func() (MethodResponse, error) {
 		return conn.XmlCmd("getParamset", conn.SwitchChannel, Param{CCUString: "VALUES"})
 	}, conn.Cache)
 
-	conn.meterG = plugin.ResettableCached(func() (MethodResponse, error) {
+	conn.meterG = util.ResettableCached(func() (MethodResponse, error) {
 		return conn.XmlCmd("getParamset", conn.MeterChannel, Param{CCUString: "VALUES"})
 	}, conn.Cache)
 
diff --git a/meter/homewizard/connection.go b/meter/homewizard/connection.go
index db5c2d9c..773cd6b3 100644
--- a/meter/homewizard/connection.go
+++ b/meter/homewizard/connection.go
@@ -7,7 +7,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/evcc-io/evcc/util/transport"
@@ -19,8 +18,8 @@ type Connection struct {
 	uri         string
 	usage       string
 	ProductType string
-	dataG       plugin.Cacheable[DataResponse]
-	stateG      plugin.Cacheable[StateResponse]
+	dataG       util.Cacheable[DataResponse]
+	stateG      util.Cacheable[StateResponse]
 }
 
 // NewConnection creates a homewizard connection
@@ -50,13 +49,13 @@ func NewConnection(uri string, usage string, cache time.Duration) (*Connection,
 	c.uri = c.uri + "/" + res.ApiVersion
 	c.ProductType = res.ProductType
 
-	c.dataG = plugin.ResettableCached(func() (DataResponse, error) {
+	c.dataG = util.ResettableCached(func() (DataResponse, error) {
 		var res DataResponse
 		err := c.GetJSON(fmt.Sprintf("%s/data", c.uri), &res)
 		return res, err
 	}, cache)
 
-	c.stateG = plugin.ResettableCached(func() (StateResponse, error) {
+	c.stateG = util.ResettableCached(func() (StateResponse, error) {
 		var res StateResponse
 		err := c.GetJSON(fmt.Sprintf("%s/state", c.uri), &res)
 		return res, err
diff --git a/meter/lgpcs/lgpcs.go b/meter/lgpcs/lgpcs.go
index 00e5de06..56ff7915 100644
--- a/meter/lgpcs/lgpcs.go
+++ b/meter/lgpcs/lgpcs.go
@@ -10,7 +10,6 @@ import (
 	"sync"
 	"time"
 
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/evcc-io/evcc/util/transport"
@@ -63,7 +62,7 @@ func GetInstance(uri, registration, password string, cache time.Duration, essTyp
 
 		// caches the data access for the "cache" time duration
 		// sends a new request to the pcs if the cache is expired and Data() requested
-		instance.dataG = plugin.Cached(instance.refreshData, cache)
+		instance.dataG = util.Cached(instance.refreshData, cache)
 
 		// do first login if no authKey exists and uri and password exist
 		if instance.authKey == "" && instance.uri != "" && instance.password != "" {
diff --git a/meter/powerwall.go b/meter/powerwall.go
index f45a821b..457a43b8 100644
--- a/meter/powerwall.go
+++ b/meter/powerwall.go
@@ -13,7 +13,6 @@ import (
 	"github.com/andig/go-powerwall"
 	"github.com/bogosj/tesla"
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"golang.org/x/oauth2"
@@ -90,7 +89,7 @@ func NewPowerWall(uri, usage, user, password string, cache time.Duration, refres
 	m := &PowerWall{
 		client: client,
 		usage:  strings.ToLower(usage),
-		meterG: plugin.Cached(client.GetMetersAggregates, cache),
+		meterG: util.Cached(client.GetMetersAggregates, cache),
 	}
 
 	var batteryControl bool
diff --git a/meter/tasmota/connection.go b/meter/tasmota/connection.go
index d252edc6..4986a4de 100644
--- a/meter/tasmota/connection.go
+++ b/meter/tasmota/connection.go
@@ -7,7 +7,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/evcc-io/evcc/util/transport"
@@ -18,8 +17,8 @@ type Connection struct {
 	*request.Helper
 	uri, user, password string
 	channels            []int
-	statusSnsG          plugin.Cacheable[StatusSNSResponse]
-	statusStsG          plugin.Cacheable[StatusSTSResponse]
+	statusSnsG          util.Cacheable[StatusSNSResponse]
+	statusStsG          util.Cacheable[StatusSTSResponse]
 }
 
 // NewConnection creates a Tasmota connection
@@ -50,7 +49,7 @@ func NewConnection(uri, user, password string, channels []int, cache time.Durati
 
 	c.Client.Transport = request.NewTripper(log, transport.Insecure())
 
-	c.statusSnsG = plugin.ResettableCached(func() (StatusSNSResponse, error) {
+	c.statusSnsG = util.ResettableCached(func() (StatusSNSResponse, error) {
 		parameters := url.Values{
 			"user":     {c.user},
 			"password": {c.password},
@@ -61,7 +60,7 @@ func NewConnection(uri, user, password string, channels []int, cache time.Durati
 		return res, err
 	}, cache)
 
-	c.statusStsG = plugin.ResettableCached(func() (StatusSTSResponse, error) {
+	c.statusStsG = util.ResettableCached(func() (StatusSTSResponse, error) {
 		parameters := url.Values{
 			"user":     {c.user},
 			"password": {c.password},
diff --git a/meter/tq-em.go b/meter/tq-em.go
index 000829b6..bd3210d1 100644
--- a/meter/tq-em.go
+++ b/meter/tq-em.go
@@ -10,7 +10,6 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 )
@@ -91,7 +90,7 @@ func NewTqEmFromConfig(other map[string]interface{}) (api.Meter, error) {
 		return nil, errors.New("no serial")
 	}
 
-	dataG := plugin.Cached(func() (tqemData, error) {
+	dataG := util.Cached(func() (tqemData, error) {
 		var res tqemData
 
 		uri := fmt.Sprintf("%s/mum-webservice/data.php", base)
diff --git a/meter/tq-em420.go b/meter/tq-em420.go
index 71788d52..4697c768 100644
--- a/meter/tq-em420.go
+++ b/meter/tq-em420.go
@@ -8,7 +8,6 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 )
@@ -115,7 +114,7 @@ func NewTqEm420FromConfig(other map[string]interface{}) (api.Meter, error) {
 
 	base := util.DefaultScheme(strings.TrimRight(cc.URI, "/"), "http")
 
-	dataG := plugin.Cached(func() (TqEm420Data, error) {
+	dataG := util.Cached(func() (TqEm420Data, error) {
 		var res TqEm420Data
 
 		headers := map[string]string{
diff --git a/tariff/tariff.go b/tariff/tariff.go
index b1f08208..783db9aa 100644
--- a/tariff/tariff.go
+++ b/tariff/tariff.go
@@ -57,7 +57,7 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]interface{}
 		return nil, fmt.Errorf("price: %w", err)
 	}
 	if priceG != nil {
-		priceG = plugin.Cached(priceG, cc.Cache)
+		priceG = util.Cached(priceG, cc.Cache)
 	}
 
 	forecastG, err := cc.Forecast.StringGetter(ctx)
diff --git a/templates/definition/charger/weishaupt-wpm.yaml b/templates/definition/charger/weishaupt-wpm.yaml
index 4d93aab6..0da1828f 100644
--- a/templates/definition/charger/weishaupt-wpm.yaml
+++ b/templates/definition/charger/weishaupt-wpm.yaml
@@ -1,4 +1,5 @@
 template: weishaupt-wpm
+deprecated: true
 products:
   - brand: Weishaupt
     description:
diff --git a/plugin/cache.go b/util/cache.go
similarity index 99%
rename from plugin/cache.go
rename to util/cache.go
index b50ed287..982adc99 100644
--- a/plugin/cache.go
+++ b/util/cache.go
@@ -1,4 +1,4 @@
-package plugin
+package util
 
 import (
 	"errors"
diff --git a/vehicle/aiways/provider.go b/vehicle/aiways/provider.go
index 8ee70594..2b48e82d 100644
--- a/vehicle/aiways/provider.go
+++ b/vehicle/aiways/provider.go
@@ -5,7 +5,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 )
 
 // Provider implements the vehicle api
@@ -16,7 +16,7 @@ type Provider struct {
 // NewProvider creates a vehicle api provider
 func NewProvider(api *API, user int64, vin string, cache time.Duration) *Provider {
 	impl := &Provider{
-		statusG: plugin.Cached(func() (StatusResponse, error) {
+		statusG: util.Cached(func() (StatusResponse, error) {
 			return api.Status(user, vin)
 		}, cache),
 	}
diff --git a/vehicle/bluelink/provider.go b/vehicle/bluelink/provider.go
index b0cf617d..81b916b2 100644
--- a/vehicle/bluelink/provider.go
+++ b/vehicle/bluelink/provider.go
@@ -4,7 +4,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 )
 
 const refreshTimeout = 2 * time.Minute
@@ -28,13 +28,13 @@ func NewProvider(api *API, vehicle Vehicle, expiry, cache time.Duration) *Provid
 		expiry: expiry,
 	}
 
-	v.statusG = plugin.Cached(func() (BluelinkVehicleStatus, error) {
+	v.statusG = util.Cached(func() (BluelinkVehicleStatus, error) {
 		return v.status(
 			func() (BluelinkVehicleStatusLatest, error) { return api.StatusLatest(vehicle) },
 		)
 	}, cache)
 
-	v.statusLG = plugin.Cached(func() (BluelinkVehicleStatusLatest, error) {
+	v.statusLG = util.Cached(func() (BluelinkVehicleStatusLatest, error) {
 		return api.StatusLatest(vehicle)
 	}, cache)
 
diff --git a/vehicle/bmw/provider.go b/vehicle/bmw/provider.go
index 57c88599..48e06b35 100644
--- a/vehicle/bmw/provider.go
+++ b/vehicle/bmw/provider.go
@@ -4,7 +4,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 )
 
 // Provider implements the vehicle api
@@ -16,7 +16,7 @@ type Provider struct {
 // NewProvider creates a vehicle api provider
 func NewProvider(api *API, vin string, cache time.Duration) *Provider {
 	impl := &Provider{
-		statusG: plugin.Cached(func() (VehicleStatus, error) {
+		statusG: util.Cached(func() (VehicleStatus, error) {
 			return api.Status(vin)
 		}, cache),
 		actionS: func(action string) error {
diff --git a/vehicle/carwings.go b/vehicle/carwings.go
index ab99924f..e5ac36f6 100644
--- a/vehicle/carwings.go
+++ b/vehicle/carwings.go
@@ -8,7 +8,6 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/joeshaw/carwings"
@@ -90,8 +89,8 @@ func NewCarWingsFromConfig(other map[string]interface{}) (api.Vehicle, error) {
 		return nil, fmt.Errorf("login failed: %w", err)
 	}
 
-	v.statusG = plugin.Cached(v.status, cc.Cache)
-	v.climateG = plugin.Cached(v.session.ClimateControlStatus, cc.Cache)
+	v.statusG = util.Cached(v.status, cc.Cache)
+	v.climateG = util.Cached(v.session.ClimateControlStatus, cc.Cache)
 
 	return v, nil
 }
diff --git a/vehicle/cloud.go b/vehicle/cloud.go
index 3e47db7d..373daf3c 100644
--- a/vehicle/cloud.go
+++ b/vehicle/cloud.go
@@ -7,7 +7,6 @@ import (
 
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/api/proto/pb"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/cloud"
 	"github.com/evcc-io/evcc/util/request"
@@ -63,7 +62,7 @@ func NewCloudFromConfig(other map[string]interface{}) (api.Vehicle, error) {
 
 	err = v.prepareVehicle()
 
-	v.chargeStateG = plugin.Cached(v.chargeState, cc.Cache)
+	v.chargeStateG = util.Cached(v.chargeState, cc.Cache)
 
 	return v, err
 }
diff --git a/vehicle/fiat/provider.go b/vehicle/fiat/provider.go
index 9672ac74..c7982002 100644
--- a/vehicle/fiat/provider.go
+++ b/vehicle/fiat/provider.go
@@ -7,7 +7,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 )
 
@@ -23,10 +23,10 @@ type Provider struct {
 
 func NewProvider(api *API, vin, pin string, expiry, cache time.Duration) *Provider {
 	impl := &Provider{
-		statusG: plugin.Cached(func() (StatusResponse, error) {
+		statusG: util.Cached(func() (StatusResponse, error) {
 			return api.Status(vin)
 		}, cache),
-		locationG: plugin.Cached(func() (LocationResponse, error) {
+		locationG: util.Cached(func() (LocationResponse, error) {
 			return api.Location(vin)
 		}, cache),
 		action: func(action, cmd string) (ActionResponse, error) {
@@ -37,7 +37,7 @@ func NewProvider(api *API, vin, pin string, expiry, cache time.Duration) *Provid
 
 	// use pin for refreshing
 	if pin != "" {
-		impl.statusG = plugin.Cached(func() (StatusResponse, error) {
+		impl.statusG = util.Cached(func() (StatusResponse, error) {
 			return impl.status(
 				func() (StatusResponse, error) { return api.Status(vin) },
 			)
diff --git a/vehicle/ford/connect/provider.go b/vehicle/ford/connect/provider.go
index 61e822c4..b85d4ad7 100644
--- a/vehicle/ford/connect/provider.go
+++ b/vehicle/ford/connect/provider.go
@@ -4,7 +4,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 )
 
 type Provider struct {
@@ -14,7 +14,7 @@ type Provider struct {
 
 func NewProvider(api *API, vin string, cache time.Duration) *Provider {
 	impl := &Provider{
-		statusG: plugin.Cached(func() (Vehicle, error) {
+		statusG: util.Cached(func() (Vehicle, error) {
 			return api.Status(vin)
 		}, cache),
 		// refreshG: func() error {
diff --git a/vehicle/ford/provider.go b/vehicle/ford/provider.go
index 7f9e9173..1bf4dc87 100644
--- a/vehicle/ford/provider.go
+++ b/vehicle/ford/provider.go
@@ -5,7 +5,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/vehicle/ford/autonomic"
 )
 
@@ -16,7 +16,7 @@ type Provider struct {
 
 func NewProvider(api *autonomic.API, vin string, cache time.Duration) *Provider {
 	impl := &Provider{
-		statusG: plugin.Cached(func() (autonomic.MetricsResponse, error) {
+		statusG: util.Cached(func() (autonomic.MetricsResponse, error) {
 			return api.Status(vin)
 		}, cache),
 		refreshG: func() error {
diff --git a/vehicle/jlr/provider.go b/vehicle/jlr/provider.go
index f5b13fb2..0b9d9766 100644
--- a/vehicle/jlr/provider.go
+++ b/vehicle/jlr/provider.go
@@ -4,7 +4,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 )
 
 type Provider struct {
@@ -15,10 +15,10 @@ type Provider struct {
 
 func NewProvider(api *API, vin, user string, cache time.Duration) *Provider {
 	impl := &Provider{
-		statusG: plugin.Cached(func() (StatusResponse, error) {
+		statusG: util.Cached(func() (StatusResponse, error) {
 			return api.Status(vin)
 		}, cache),
-		positionG: plugin.Cached(func() (PositionResponse, error) {
+		positionG: util.Cached(func() (PositionResponse, error) {
 			return api.Position(vin)
 		}, cache),
 		chargeS: func(enable bool) error {
diff --git a/vehicle/mercedes/provider.go b/vehicle/mercedes/provider.go
index b7482089..78fa3a70 100644
--- a/vehicle/mercedes/provider.go
+++ b/vehicle/mercedes/provider.go
@@ -4,7 +4,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 )
 
 type Provider struct {
@@ -13,7 +13,7 @@ type Provider struct {
 
 func NewProvider(api *API, vin string, cache time.Duration) *Provider {
 	impl := &Provider{
-		dataG: plugin.Cached(func() (StatusResponse, error) {
+		dataG: util.Cached(func() (StatusResponse, error) {
 			return api.Status(vin)
 		}, cache),
 	}
diff --git a/vehicle/nissan/provider.go b/vehicle/nissan/provider.go
index 00e54cf5..6107cc7f 100644
--- a/vehicle/nissan/provider.go
+++ b/vehicle/nissan/provider.go
@@ -5,7 +5,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 )
 
 const refreshTimeout = 2 * time.Minute
@@ -28,7 +28,7 @@ func NewProvider(api *API, vin, version string, expiry, cache time.Duration) *Pr
 		expiry: expiry,
 	}
 
-	impl.statusG = plugin.Cached(func() (StatusResponse, error) {
+	impl.statusG = util.Cached(func() (StatusResponse, error) {
 		return impl.status(
 			func() (StatusResponse, error) { return api.BatteryStatus(vin, version) },
 			func() (ActionResponse, error) { return api.RefreshRequest(vin, "RefreshBatteryStatus") },
diff --git a/vehicle/niu.go b/vehicle/niu.go
index e7da3b80..114a8f3c 100644
--- a/vehicle/niu.go
+++ b/vehicle/niu.go
@@ -10,7 +10,6 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/evcc-io/evcc/vehicle/niu"
@@ -62,7 +61,7 @@ func NewNiuFromConfig(other map[string]interface{}) (api.Vehicle, error) {
 		serial:   strings.ToUpper(cc.Serial),
 	}
 
-	v.apiG = plugin.Cached(v.batteryAPI, cc.Cache)
+	v.apiG = util.Cached(v.batteryAPI, cc.Cache)
 
 	return v, nil
 }
diff --git a/vehicle/ovms.go b/vehicle/ovms.go
index 3ea15d2d..1f9a94fc 100644
--- a/vehicle/ovms.go
+++ b/vehicle/ovms.go
@@ -9,7 +9,6 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/evcc-io/evcc/vehicle/ovms"
@@ -59,9 +58,9 @@ func NewOvmsFromConfig(other map[string]interface{}) (api.Vehicle, error) {
 		cache:     cc.Cache,
 	}
 
-	v.chargeG = plugin.Cached(v.batteryAPI, cc.Cache)
-	v.statusG = plugin.Cached(v.statusAPI, cc.Cache)
-	v.locationG = plugin.Cached(v.locationAPI, cc.Cache)
+	v.chargeG = util.Cached(v.batteryAPI, cc.Cache)
+	v.statusG = util.Cached(v.statusAPI, cc.Cache)
+	v.locationG = util.Cached(v.locationAPI, cc.Cache)
 
 	v.Jar, _ = cookiejar.New(&cookiejar.Options{
 		PublicSuffixList: publicsuffix.List,
diff --git a/vehicle/polestar/provider.go b/vehicle/polestar/provider.go
index aa01bde4..efaa706b 100644
--- a/vehicle/polestar/provider.go
+++ b/vehicle/polestar/provider.go
@@ -5,7 +5,6 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 )
 
@@ -16,12 +15,12 @@ type Provider struct {
 
 func NewProvider(log *util.Logger, api *API, vin string, timeout, cache time.Duration) *Provider {
 	v := &Provider{
-		statusG: plugin.Cached(func() (BatteryData, error) {
+		statusG: util.Cached(func() (BatteryData, error) {
 			ctx, cancel := context.WithTimeout(context.Background(), timeout)
 			defer cancel()
 			return api.Status(ctx, vin)
 		}, cache),
-		odoG: plugin.Cached(func() (OdometerData, error) {
+		odoG: util.Cached(func() (OdometerData, error) {
 			ctx, cancel := context.WithTimeout(context.Background(), timeout)
 			defer cancel()
 			return api.Odometer(ctx, vin)
diff --git a/vehicle/porsche/provider.go b/vehicle/porsche/provider.go
index a58b822b..795bb16d 100644
--- a/vehicle/porsche/provider.go
+++ b/vehicle/porsche/provider.go
@@ -5,7 +5,6 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 )
 
@@ -19,11 +18,11 @@ type Provider struct {
 // NewProvider creates a vehicle api provider
 func NewProvider(log *util.Logger, connect *API, emobility *EmobilityAPI, vin, carModel string, cache time.Duration) *Provider {
 	impl := &Provider{
-		statusG: plugin.Cached(func() (StatusResponse, error) {
+		statusG: util.Cached(func() (StatusResponse, error) {
 			return connect.Status(vin)
 		}, cache),
 
-		emobilityG: plugin.Cached(func() (EmobilityResponse, error) {
+		emobilityG: util.Cached(func() (EmobilityResponse, error) {
 			if carModel != "" {
 				return emobility.Status(vin, carModel)
 			}
diff --git a/vehicle/psa/provider.go b/vehicle/psa/provider.go
index 38ab9ed2..d24277a9 100644
--- a/vehicle/psa/provider.go
+++ b/vehicle/psa/provider.go
@@ -5,7 +5,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 )
 
 // Provider is an api.Vehicle implementation for PSA cars
@@ -16,7 +16,7 @@ type Provider struct {
 // NewProvider creates a vehicle api provider
 func NewProvider(api *API, vid string, cache time.Duration) *Provider {
 	impl := &Provider{
-		statusG: plugin.Cached(func() (Status, error) {
+		statusG: util.Cached(func() (Status, error) {
 			return api.Status(vid)
 		}, cache),
 	}
diff --git a/vehicle/renault/provider.go b/vehicle/renault/provider.go
index 3e4194bd..659a66e5 100644
--- a/vehicle/renault/provider.go
+++ b/vehicle/renault/provider.go
@@ -7,7 +7,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/evcc-io/evcc/vehicle/renault/kamereon"
 )
@@ -25,13 +25,13 @@ type Provider struct {
 // NewProvider creates a vehicle api provider
 func NewProvider(api *kamereon.API, accountID, vin string, alternativeWakeup bool, cache time.Duration) *Provider {
 	impl := &Provider{
-		batteryG: plugin.Cached(func() (kamereon.Response, error) {
+		batteryG: util.Cached(func() (kamereon.Response, error) {
 			return api.Battery(accountID, vin)
 		}, cache),
-		cockpitG: plugin.Cached(func() (kamereon.Response, error) {
+		cockpitG: util.Cached(func() (kamereon.Response, error) {
 			return api.Cockpit(accountID, vin)
 		}, cache),
-		hvacG: plugin.Cached(func() (kamereon.Response, error) {
+		hvacG: util.Cached(func() (kamereon.Response, error) {
 			return api.Hvac(accountID, vin)
 		}, cache),
 		wakeup: func() (kamereon.Response, error) {
diff --git a/vehicle/saic/provider.go b/vehicle/saic/provider.go
index 0348745b..b75b8c2f 100644
--- a/vehicle/saic/provider.go
+++ b/vehicle/saic/provider.go
@@ -5,7 +5,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/vehicle/saic/requests"
 )
 
@@ -25,14 +25,14 @@ var TargetSocVals = [...]int{0, 40, 50, 60, 70, 80, 90, 100}
 
 // Provider implements the vehicle api
 type Provider struct {
-	status plugin.Cacheable[requests.ChargeStatus]
+	status util.Cacheable[requests.ChargeStatus]
 	wakeup func() error
 }
 
 // NewProvider creates a vehicle api provider
 func NewProvider(api *API, vin string, cache time.Duration) *Provider {
 	impl := &Provider{
-		status: plugin.ResettableCached(func() (requests.ChargeStatus, error) {
+		status: util.ResettableCached(func() (requests.ChargeStatus, error) {
 			return api.Status(vin)
 		}, cache),
 		wakeup: func() error {
diff --git a/vehicle/seat/cupra/provider.go b/vehicle/seat/cupra/provider.go
index 234d1551..efc4d26b 100644
--- a/vehicle/seat/cupra/provider.go
+++ b/vehicle/seat/cupra/provider.go
@@ -5,7 +5,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 )
 
 // Provider is an api.Vehicle implementation for Seat Cupra cars
@@ -19,13 +19,13 @@ type Provider struct {
 // NewProvider creates a vehicle api provider
 func NewProvider(api *API, userID, vin string, cache time.Duration) *Provider {
 	impl := &Provider{
-		statusG: plugin.Cached(func() (Status, error) {
+		statusG: util.Cached(func() (Status, error) {
 			return api.Status(userID, vin)
 		}, cache),
-		positionG: plugin.Cached(func() (Position, error) {
+		positionG: util.Cached(func() (Position, error) {
 			return api.ParkingPosition(vin)
 		}, cache),
-		milageG: plugin.Cached(func() (Mileage, error) {
+		milageG: util.Cached(func() (Mileage, error) {
 			return api.Mileage(vin)
 		}, cache),
 		action: func(action, cmd string) error {
diff --git a/vehicle/skoda/myskoda/provider.go b/vehicle/skoda/myskoda/provider.go
index 3a9982b4..286d12ab 100644
--- a/vehicle/skoda/myskoda/provider.go
+++ b/vehicle/skoda/myskoda/provider.go
@@ -4,7 +4,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 )
 
 // Provider implements the vehicle api
@@ -20,16 +20,16 @@ type Provider struct {
 // NewProvider creates a vehicle api provider
 func NewProvider(api *API, vin string, cache time.Duration) *Provider {
 	impl := &Provider{
-		statusG: plugin.Cached(func() (StatusResponse, error) {
+		statusG: util.Cached(func() (StatusResponse, error) {
 			return api.Status(vin)
 		}, cache),
-		chargerG: plugin.Cached(func() (ChargerResponse, error) {
+		chargerG: util.Cached(func() (ChargerResponse, error) {
 			return api.Charger(vin)
 		}, cache),
-		climateG: plugin.Cached(func() (ClimaterResponse, error) {
+		climateG: util.Cached(func() (ClimaterResponse, error) {
 			return api.Climater(vin)
 		}, cache),
-		settingsG: plugin.Cached(func() (SettingsResponse, error) {
+		settingsG: util.Cached(func() (SettingsResponse, error) {
 			return api.Settings(vin)
 		}, cache),
 		action: func(action, value string) error {
diff --git a/vehicle/skoda/provider.go b/vehicle/skoda/provider.go
index 2ada52e8..c85c4a08 100644
--- a/vehicle/skoda/provider.go
+++ b/vehicle/skoda/provider.go
@@ -4,7 +4,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 )
 
 // Provider implements the vehicle api
@@ -18,16 +18,16 @@ type Provider struct {
 // NewProvider creates a vehicle api provider
 func NewProvider(api *API, vin string, cache time.Duration) *Provider {
 	impl := &Provider{
-		statusG: plugin.Cached(func() (StatusResponse, error) {
+		statusG: util.Cached(func() (StatusResponse, error) {
 			return api.Status(vin)
 		}, cache),
-		chargerG: plugin.Cached(func() (ChargerResponse, error) {
+		chargerG: util.Cached(func() (ChargerResponse, error) {
 			return api.Charger(vin)
 		}, cache),
-		// climateG: plugin.Cached(func() (interface{}, error) {
+		// climateG: util.Cached(func() (interface{}, error) {
 		// 	return api.Climater(vin)
 		// }, cache),
-		settingsG: plugin.Cached(func() (SettingsResponse, error) {
+		settingsG: util.Cached(func() (SettingsResponse, error) {
 			return api.Settings(vin)
 		}, cache),
 		action: func(action, value string) error {
diff --git a/vehicle/smart/hello/provider.go b/vehicle/smart/hello/provider.go
index d9232351..e1d83e9d 100644
--- a/vehicle/smart/hello/provider.go
+++ b/vehicle/smart/hello/provider.go
@@ -4,7 +4,6 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 )
 
@@ -16,7 +15,7 @@ type Provider struct {
 
 func NewProvider(log *util.Logger, api *API, vin string, cache time.Duration) *Provider {
 	v := &Provider{
-		statusG: plugin.Cached(func() (VehicleStatus, error) {
+		statusG: util.Cached(func() (VehicleStatus, error) {
 			return api.Status(vin)
 		}, cache),
 	}
diff --git a/vehicle/smart/provider.go b/vehicle/smart/provider.go
index 716f78ca..e974b314 100644
--- a/vehicle/smart/provider.go
+++ b/vehicle/smart/provider.go
@@ -5,7 +5,6 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 )
 
@@ -21,7 +20,7 @@ func NewProvider(log *util.Logger, api *API, vin string, expiry, cache time.Dura
 		expiry: expiry,
 	}
 
-	v.statusG = plugin.Cached(func() (StatusResponse, error) {
+	v.statusG = util.Cached(func() (StatusResponse, error) {
 		return v.status(
 			func() (StatusResponse, error) { return api.Status(vin) },
 			func() (StatusResponse, error) { return api.Refresh(vin) },
diff --git a/vehicle/tesla/helper.go b/vehicle/tesla/helper.go
index caf92632..c35ef5fb 100644
--- a/vehicle/tesla/helper.go
+++ b/vehicle/tesla/helper.go
@@ -26,7 +26,7 @@ func addInstance(subject string, identity *Identity) {
 func apiError(err error) error {
 	if err != nil && (errors.Is(err, inet.ErrVehicleNotAwake) ||
 		strings.HasSuffix(err.Error(), "408 Request Timeout") || strings.HasSuffix(err.Error(), "408 (Request Timeout)") ||
-		strings.HasSuffix(err.Error(), "vehicle is offline or asleep")) {
+		strings.Contains(err.Error(), "vehicle is offline or asleep")) {
 		err = api.ErrAsleep
 	}
 	return err
diff --git a/vehicle/tesla/provider.go b/vehicle/tesla/provider.go
index 293dc5a7..c590cf4a 100644
--- a/vehicle/tesla/provider.go
+++ b/vehicle/tesla/provider.go
@@ -4,7 +4,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/tesla-proxy-client"
 )
 
@@ -15,7 +15,7 @@ type Provider struct {
 
 func NewProvider(vehicle *tesla.Vehicle, cache time.Duration) *Provider {
 	impl := &Provider{
-		dataG: plugin.Cached(func() (*tesla.VehicleData, error) {
+		dataG: util.Cached(func() (*tesla.VehicleData, error) {
 			res, err := vehicle.Data()
 			return res, apiError(err)
 		}, cache),
diff --git a/vehicle/tronity.go b/vehicle/tronity.go
index d584703b..4c6e84c4 100644
--- a/vehicle/tronity.go
+++ b/vehicle/tronity.go
@@ -26,7 +26,6 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/oauth"
 	"github.com/evcc-io/evcc/util/request"
@@ -120,7 +119,7 @@ func NewTronityFromConfig(other map[string]interface{}) (api.Vehicle, error) {
 	}
 
 	v.vid = vehicle.ID
-	v.bulkG = plugin.Cached(v.bulk, cc.Cache)
+	v.bulkG = util.Cached(v.bulk, cc.Cache)
 
 	var status func() (api.ChargeStatus, error)
 	if slices.Contains(vehicle.Scopes, tronity.ReadCharge) {
diff --git a/vehicle/volvo.go b/vehicle/volvo.go
index 0711809c..b62b7a3c 100644
--- a/vehicle/volvo.go
+++ b/vehicle/volvo.go
@@ -5,7 +5,6 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/evcc-io/evcc/util/transport"
@@ -64,7 +63,7 @@ func NewVolvoFromConfig(other map[string]interface{}) (api.Vehicle, error) {
 		}),
 	}
 
-	v.statusG = plugin.Cached(v.StatusRequest, cc.Cache)
+	v.statusG = util.Cached(v.StatusRequest, cc.Cache)
 
 	var err error
 	v.vin, err = ensureVehicle(cc.VIN, v.Vehicles)
diff --git a/vehicle/volvo/connected/provider.go b/vehicle/volvo/connected/provider.go
index 0e613372..2b04d8fd 100644
--- a/vehicle/volvo/connected/provider.go
+++ b/vehicle/volvo/connected/provider.go
@@ -4,7 +4,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 )
 
 // Provider implements the vehicle api
@@ -15,7 +15,7 @@ type Provider struct {
 // NewProvider creates a vehicle api provider
 func NewProvider(api *API, vin string, cache time.Duration) *Provider {
 	impl := &Provider{
-		statusG: plugin.Cached(func() (RechargeStatus, error) {
+		statusG: util.Cached(func() (RechargeStatus, error) {
 			return api.RechargeStatus(vin)
 		}, cache),
 	}
diff --git a/vehicle/vw/id/provider.go b/vehicle/vw/id/provider.go
index 8335ada0..5556e2e2 100644
--- a/vehicle/vw/id/provider.go
+++ b/vehicle/vw/id/provider.go
@@ -6,7 +6,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 )
 
 // Provider is an api.Vehicle implementation for VW ID cars
@@ -18,7 +18,7 @@ type Provider struct {
 // NewProvider creates a vehicle api provider
 func NewProvider(api *API, vin string, cache time.Duration) *Provider {
 	impl := &Provider{
-		statusG: plugin.Cached(func() (Status, error) {
+		statusG: util.Cached(func() (Status, error) {
 			return api.Status(vin)
 		}, cache),
 		action: func(action, value string) error {
diff --git a/vehicle/vw/provider.go b/vehicle/vw/provider.go
index c8f90f2a..02505535 100644
--- a/vehicle/vw/provider.go
+++ b/vehicle/vw/provider.go
@@ -11,7 +11,7 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 	"github.com/samber/lo"
 )
 
@@ -28,16 +28,16 @@ type Provider struct {
 // NewProvider creates a vehicle api provider
 func NewProvider(api *API, vin string, cache time.Duration) *Provider {
 	impl := &Provider{
-		chargerG: plugin.Cached(func() (ChargerResponse, error) {
+		chargerG: util.Cached(func() (ChargerResponse, error) {
 			return api.Charger(vin)
 		}, cache),
-		statusG: plugin.Cached(func() (StatusResponse, error) {
+		statusG: util.Cached(func() (StatusResponse, error) {
 			return api.Status(vin)
 		}, cache),
-		climateG: plugin.Cached(func() (ClimaterResponse, error) {
+		climateG: util.Cached(func() (ClimaterResponse, error) {
 			return api.Climater(vin)
 		}, cache),
-		positionG: plugin.Cached(func() (PositionResponse, error) {
+		positionG: util.Cached(func() (PositionResponse, error) {
 			return api.Position(vin)
 		}, cache),
 		action: func(action, value string) error {
diff --git a/vehicle/zero/provider.go b/vehicle/zero/provider.go
index 2c96c2f1..cbb463c4 100644
--- a/vehicle/zero/provider.go
+++ b/vehicle/zero/provider.go
@@ -4,18 +4,18 @@ import (
 	"time"
 
 	"github.com/evcc-io/evcc/api"
-	"github.com/evcc-io/evcc/plugin"
+	"github.com/evcc-io/evcc/util"
 )
 
 // Provider implements the vehicle api
 type Provider struct {
-	status plugin.Cacheable[State]
+	status util.Cacheable[State]
 }
 
 // NewProvider creates a vehicle api provider
 func NewProvider(api *API, unitId string, cache time.Duration) *Provider {
 	impl := &Provider{
-		status: plugin.ResettableCached(func() (State, error) {
+		status: util.ResettableCached(func() (State, error) {
 			return api.Status(unitId)
 		}, cache),
 	}


Test patch:
diff --git a/plugin/cache_test.go b/util/cache_test.go
similarity index 99%
rename from plugin/cache_test.go
rename to util/cache_test.go
index 8d4984f2..07fcf1de 100644
--- a/plugin/cache_test.go
+++ b/util/cache_test.go
@@ -1,4 +1,4 @@
-package plugin
+package util
 
 import (
 	"errors"