Repository: evcc-io/evcc
Pull request #18288 (2025-01-19 14:16:45)

chore: simplify and rename energy metrics to avoid confusion
alternative auch `sessionMetrics`?


Patch:
diff --git a/Makefile b/Makefile
index 832337dd..cbc3b03a 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ VERSION := $(if $(TAG_NAME),$(TAG_NAME),$(SHA))
 BUILD_DATE := $(shell date -u '+%Y-%m-%d_%H:%M:%S')
 BUILD_TAGS := -tags=release
 TESLA_CLIENT_ID := ${TESLA_CLIENT_ID}
-LD_FLAGS := -X github.com/evcc-io/evcc/server.Version=$(VERSION) -X github.com/evcc-io/evcc/server.Commit=$(COMMIT) -X github.com/evcc-io/evcc/vehicle/tesla.TESLA_CLIENT_ID=$(TESLA_CLIENT_ID) -s -w
+LD_FLAGS := -X github.com/evcc-io/evcc/server.Version=$(VERSION) -X github.com/evcc-io/evcc/server.Commit=$(COMMIT) -s -w
 BUILD_ARGS := -trimpath -ldflags='$(LD_FLAGS)'
 
 # docker
diff --git a/assets/js/components/Energyflow/Energyflow.story.vue b/assets/js/components/Energyflow/Energyflow.story.vue
index b84420f1..cd06dd36 100644
--- a/assets/js/components/Energyflow/Energyflow.story.vue
+++ b/assets/js/components/Energyflow/Energyflow.story.vue
@@ -42,8 +42,8 @@ import Energyflow from "./Energyflow.vue";
 				currency="EUR"
 				siteTitle="Home"
 				:battery="[
-					{ soc: 44.999, capacity: 13.3 },
-					{ soc: 82.3331, capacity: 21 },
+					{ soc: 44.999, capacity: 13.3, power: 350 },
+					{ soc: 82.3331, capacity: 21, power: 450 },
 				]"
 			/>
 		</Variant>
diff --git a/assets/js/components/Energyflow/Energyflow.vue b/assets/js/components/Energyflow/Energyflow.vue
index 76013265..35316e4f 100644
--- a/assets/js/components/Energyflow/Energyflow.vue
+++ b/assets/js/components/Energyflow/Energyflow.vue
@@ -91,6 +91,7 @@
 							:name="batteryDischargeLabel"
 							icon="battery"
 							:power="batteryDischarge"
+							:powerTooltip="batteryDischargeTooltip"
 							:powerUnit="powerUnit"
 							:iconProps="{
 								hold: batteryHold,
@@ -166,6 +167,7 @@
 							:name="batteryChargeLabel"
 							icon="battery"
 							:power="batteryCharge"
+							:powerTooltip="batteryChargeTooltip"
 							:powerUnit="powerUnit"
 							:iconProps="{
 								hold: batteryHold,
@@ -276,10 +278,10 @@ export default {
 			return Math.abs(this.pvPower);
 		},
 		batteryDischarge: function () {
-			return Math.abs(Math.max(0, this.batteryPower));
+			return this.dischargePower(this.batteryPower);
 		},
 		batteryCharge: function () {
-			return Math.abs(Math.min(0, this.batteryPower) * -1);
+			return this.chargePower(this.batteryPower);
 		},
 		batteryChargeLabel: function () {
 			return this.$t(`main.energyflow.battery${this.batteryHold ? "Hold" : "Charge"}`);
@@ -344,6 +346,12 @@ export default {
 			}
 			return this.pv.map(({ power }) => this.fmtW(power, this.powerUnit));
 		},
+		batteryDischargeTooltip() {
+			return this.batteryTooltip(true);
+		},
+		batteryChargeTooltip() {
+			return this.batteryTooltip(false);
+		},
 		batteryFmt() {
 			return (soc) => this.fmtPercentage(soc, 0);
 		},
@@ -360,7 +368,9 @@ export default {
 			return this.fmtPricePerKWh(this.tariffGrid, this.currency, true);
 		},
 		batteryGridChargeLimitSet() {
-			return this.batteryGridChargeLimit !== null;
+			return (
+				this.batteryGridChargeLimit !== null && this.batteryGridChargeLimit !== undefined
+			);
 		},
 		batteryGridChargeLimitFmt() {
 			if (!this.batteryGridChargeLimitSet) {
@@ -437,6 +447,23 @@ export default {
 			);
 			modal.show();
 		},
+		dischargePower(power) {
+			return Math.abs(Math.max(0, power));
+		},
+		chargePower(power) {
+			return Math.abs(Math.min(0, power) * -1);
+		},
+		batteryTooltip(discharge = false) {
+			if (!Array.isArray(this.battery) || this.battery.length <= 1) {
+				return;
+			}
+			return this.battery.map(({ power, soc }) => {
+				const value = discharge ? this.dischargePower(power) : this.chargePower(power);
+				const powerFmt = this.fmtW(value, this.powerUnit);
+				const socFmt = this.fmtPercentage(soc, 0);
+				return `${powerFmt} (${socFmt})`;
+			});
+		},
 	},
 };
 </script>
diff --git a/charger/switchsocket.go b/charger/switchsocket.go
index 17107501..65b903f1 100644
--- a/charger/switchsocket.go
+++ b/charger/switchsocket.go
@@ -19,7 +19,7 @@ type SwitchSocket struct {
 	*switchSocket
 }
 
-//go:generate decorate -f decorateSwitchSocket -b *SwitchSocket -r api.Charger -t "api.MeterEnergy,TotalEnergy,func() (float64, error)"
+//go:generate decorate -f decorateSwitchSocket -b *SwitchSocket -r api.Charger -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Battery,Soc,func() (float64, error)"
 
 func NewSwitchSocketFromConfig(ctx context.Context, other map[string]interface{}) (api.Charger, error) {
 	var cc struct {
@@ -28,6 +28,7 @@ func NewSwitchSocketFromConfig(ctx context.Context, other map[string]interface{}
 		Enable       provider.Config
 		Power        provider.Config
 		Energy       *provider.Config
+		Soc          *provider.Config
 		StandbyPower float64
 	}
 
@@ -58,13 +59,21 @@ func NewSwitchSocketFromConfig(ctx context.Context, other map[string]interface{}
 		}
 	}
 
+	var soc func() (float64, error)
+	if cc.Soc != nil {
+		soc, err = provider.NewFloatGetterFromConfig(ctx, *cc.Soc)
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	c := &SwitchSocket{
 		enabled:      enabled,
 		enable:       enable,
 		switchSocket: NewSwitchSocket(&cc.embed, enabled, power, cc.StandbyPower),
 	}
 
-	return decorateSwitchSocket(c, energy), nil
+	return decorateSwitchSocket(c, energy, soc), nil
 }
 
 func (c *SwitchSocket) Enabled() (bool, error) {
diff --git a/charger/switchsocket_decorators.go b/charger/switchsocket_decorators.go
index c94042b3..0bb7fe0b 100644
--- a/charger/switchsocket_decorators.go
+++ b/charger/switchsocket_decorators.go
@@ -6,12 +6,12 @@ import (
 	"github.com/evcc-io/evcc/api"
 )
 
-func decorateSwitchSocket(base *SwitchSocket, meterEnergy func() (float64, error)) api.Charger {
+func decorateSwitchSocket(base *SwitchSocket, meterEnergy func() (float64, error), battery func() (float64, error)) api.Charger {
 	switch {
-	case meterEnergy == nil:
+	case battery == nil && meterEnergy == nil:
 		return base
 
-	case meterEnergy != nil:
+	case battery == nil && meterEnergy != nil:
 		return &struct {
 			*SwitchSocket
 			api.MeterEnergy
@@ -21,11 +21,45 @@ func decorateSwitchSocket(base *SwitchSocket, meterEnergy func() (float64, error
 				meterEnergy: meterEnergy,
 			},
 		}
+
+	case battery != nil && meterEnergy == nil:
+		return &struct {
+			*SwitchSocket
+			api.Battery
+		}{
+			SwitchSocket: base,
+			Battery: &decorateSwitchSocketBatteryImpl{
+				battery: battery,
+			},
+		}
+
+	case battery != nil && meterEnergy != nil:
+		return &struct {
+			*SwitchSocket
+			api.Battery
+			api.MeterEnergy
+		}{
+			SwitchSocket: base,
+			Battery: &decorateSwitchSocketBatteryImpl{
+				battery: battery,
+			},
+			MeterEnergy: &decorateSwitchSocketMeterEnergyImpl{
+				meterEnergy: meterEnergy,
+			},
+		}
 	}
 
 	return nil
 }
 
+type decorateSwitchSocketBatteryImpl struct {
+	battery func() (float64, error)
+}
+
+func (impl *decorateSwitchSocketBatteryImpl) Soc() (float64, error) {
+	return impl.battery()
+}
+
 type decorateSwitchSocketMeterEnergyImpl struct {
 	meterEnergy func() (float64, error)
 }
diff --git a/charger/vaillant.go b/charger/vaillant.go
index 0f7c34d8..c395fd26 100644
--- a/charger/vaillant.go
+++ b/charger/vaillant.go
@@ -94,7 +94,7 @@ func NewVaillantFromConfig(ctx context.Context, other map[string]interface{}) (a
 	}
 
 	systemId := homes[0].SystemID
-	heating := cc.HeatingZone > 0 && cc.HeatingSetpoint > 0
+	heating := cc.HeatingSetpoint > 0
 
 	set := func(mode int64) error {
 		switch mode {
@@ -105,9 +105,9 @@ func NewVaillantFromConfig(ctx context.Context, other map[string]interface{}) (a
 			return conn.StopHotWaterBoost(systemId, sensonet.HOTWATERINDEX_DEFAULT)
 		case Boost:
 			if heating {
-				return conn.StartZoneQuickVeto(systemId, cc.HeatingZone, cc.HeatingSetpoint, sensonet.ZONEVETODURATION_DEFAULT)
+				return conn.StartZoneQuickVeto(systemId, cc.HeatingZone, cc.HeatingSetpoint, 4) // hours
 			}
-			return conn.StartHotWaterBoost(systemId, sensonet.HOTWATERINDEX_DEFAULT)
+			return conn.StartHotWaterBoost(systemId, sensonet.HOTWATERINDEX_DEFAULT) // zone 255
 		default:
 			return api.ErrNotAvailable
 		}
diff --git a/core/energy_metrics.go b/core/energy_metrics.go
index 68934ed4..8534caea 100644
--- a/core/energy_metrics.go
+++ b/core/energy_metrics.go
@@ -11,13 +11,6 @@ type EnergyMetrics struct {
 	currentCo2        *float64 // Current co2 emissions
 }
 
-func NewEnergyMetrics() *EnergyMetrics {
-	em := &EnergyMetrics{}
-	em.Reset()
-
-	return em
-}
-
 // SetEnvironment updates site information like solar share, price, co2 for use in later calculations
 func (em *EnergyMetrics) SetEnvironment(greenShare float64, effPrice, effCo2 *float64) {
 	em.currentGreenShare = greenShare
diff --git a/core/loadpoint.go b/core/loadpoint.go
index 8c720ac8..1e45db4a 100644
--- a/core/loadpoint.go
+++ b/core/loadpoint.go
@@ -176,12 +176,12 @@ type Loadpoint struct {
 	wakeUpTimer    *Timer                 // Vehicle wake-up timeout
 
 	// charge progress
-	vehicleSoc              float64        // Vehicle Soc
-	chargeDuration          time.Duration  // Charge duration
-	sessionEnergy           *EnergyMetrics // Stats for charged energy by session
-	chargeRemainingDuration time.Duration  // Remaining charge duration
-	chargeRemainingEnergy   float64        // Remaining charge energy in Wh
-	progress                *Progress      // Step-wise progress indicator
+	vehicleSoc              float64       // Vehicle Soc
+	chargeDuration          time.Duration // Charge duration
+	energyMetrics           EnergyMetrics // Stats for charged energy by session
+	chargeRemainingDuration time.Duration // Remaining charge duration
+	chargeRemainingEnergy   float64       // Remaining charge energy in Wh
+	progress                *Progress     // Step-wise progress indicator
 
 	// session log
 	db      *session.DB
@@ -307,12 +307,11 @@ func NewLoadpoint(log *util.Logger, settings *Settings) *Loadpoint {
 				Mode:     pollCharging,
 			},
 		},
-		Enable:        ThresholdConfig{Delay: time.Minute, Threshold: 0},     // t, W
-		Disable:       ThresholdConfig{Delay: 3 * time.Minute, Threshold: 0}, // t, W
-		sessionEnergy: NewEnergyMetrics(),
-		progress:      NewProgress(0, 10),     // soc progress indicator
-		coordinator:   coordinator.NewDummy(), // dummy vehicle coordinator
-		tasks:         util.NewQueue[Task](),  // task queue
+		Enable:      ThresholdConfig{Delay: time.Minute, Threshold: 0},     // t, W
+		Disable:     ThresholdConfig{Delay: 3 * time.Minute, Threshold: 0}, // t, W
+		progress:    NewProgress(0, 10),                                    // soc progress indicator
+		coordinator: coordinator.NewDummy(),                                // dummy vehicle coordinator
+		tasks:       util.NewQueue[Task](),                                 // task queue
 	}
 
 	return lp
@@ -475,8 +474,8 @@ func (lp *Loadpoint) evVehicleConnectHandler() {
 	lp.log.INFO.Printf("car connected")
 
 	// energy
-	lp.sessionEnergy.Reset()
-	lp.sessionEnergy.Publish("session", lp)
+	lp.energyMetrics.Reset()
+	lp.energyMetrics.Publish("session", lp)
 	lp.publish(keys.ChargedEnergy, lp.getChargedEnergy())
 
 	// duration
@@ -514,7 +513,7 @@ func (lp *Loadpoint) evVehicleDisconnectHandler() {
 	lp.resetMeasuredPhases()
 
 	// energy and duration
-	lp.sessionEnergy.Publish("session", lp)
+	lp.energyMetrics.Publish("session", lp)
 	lp.publish(keys.ChargedEnergy, lp.getChargedEnergy())
 	lp.publish(keys.ConnectedDuration, lp.clock.Since(lp.connectedTime).Round(time.Second))
 
@@ -1580,7 +1579,7 @@ func (lp *Loadpoint) publishChargeProgress() {
 		// workaround for Go-E resetting during disconnect, see
 		// https://github.com/evcc-io/evcc/issues/5092
 		if f > lp.chargedAtStartup {
-			added, addedGreen := lp.sessionEnergy.Update(f - lp.chargedAtStartup)
+			added, addedGreen := lp.energyMetrics.Update(f - lp.chargedAtStartup)
 			if telemetry.Enabled() && added > 0 {
 				telemetry.UpdateEnergy(added, addedGreen)
 			}
@@ -1596,7 +1595,7 @@ func (lp *Loadpoint) publishChargeProgress() {
 	}
 
 	// TODO check if "session" prefix required?
-	lp.sessionEnergy.Publish("session", lp)
+	lp.energyMetrics.Publish("session", lp)
 
 	// TODO deprecated: use sessionEnergy instead
 	lp.publish(keys.ChargedEnergy, lp.getChargedEnergy())
@@ -1751,7 +1750,7 @@ func (lp *Loadpoint) Update(sitePower, batteryBoostPower float64, rates api.Rate
 	lp.updateChargeVoltages()
 	lp.phasesFromChargeCurrents()
 
-	lp.sessionEnergy.SetEnvironment(greenShare, effPrice, effCo2)
+	lp.energyMetrics.SetEnvironment(greenShare, effPrice, effCo2)
 
 	// update ChargeRater here to make sure initial meter update is caught
 	lp.bus.Publish(evChargeCurrent, lp.chargeCurrent)
diff --git a/core/loadpoint_api.go b/core/loadpoint_api.go
index 2570a9f0..a37429df 100644
--- a/core/loadpoint_api.go
+++ b/core/loadpoint_api.go
@@ -78,7 +78,7 @@ func (lp *Loadpoint) SetMode(mode api.ChargeMode) {
 func (lp *Loadpoint) getChargedEnergy() float64 {
 	lp.RLock()
 	defer lp.RUnlock()
-	return lp.sessionEnergy.TotalWh()
+	return lp.energyMetrics.TotalWh()
 }
 
 // GetPriority returns the loadpoint priority
diff --git a/core/loadpoint_session.go b/core/loadpoint_session.go
index c58bec59..1c11b385 100644
--- a/core/loadpoint_session.go
+++ b/core/loadpoint_session.go
@@ -3,6 +3,7 @@ package core
 import (
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/core/session"
+	"github.com/samber/lo"
 )
 
 func (lp *Loadpoint) chargeMeterTotal() float64 {
@@ -63,16 +64,15 @@ func (lp *Loadpoint) stopSession() {
 	}
 
 	if chargedEnergy := lp.getChargedEnergy() / 1e3; chargedEnergy > s.ChargedEnergy {
-		lp.sessionEnergy.Update(chargedEnergy)
+		lp.energyMetrics.Update(chargedEnergy)
 	}
 
-	solarPerc := lp.sessionEnergy.SolarPercentage()
-	s.SolarPercentage = &solarPerc
-	s.Price = lp.sessionEnergy.Price()
-	s.PricePerKWh = lp.sessionEnergy.PricePerKWh()
-	s.Co2PerKWh = lp.sessionEnergy.Co2PerKWh()
-	s.ChargedEnergy = lp.sessionEnergy.TotalWh() / 1e3
-	s.ChargeDuration = &lp.chargeDuration
+	s.SolarPercentage = lo.ToPtr(lp.energyMetrics.SolarPercentage())
+	s.Price = lp.energyMetrics.Price()
+	s.PricePerKWh = lp.energyMetrics.PricePerKWh()
+	s.Co2PerKWh = lp.energyMetrics.Co2PerKWh()
+	s.ChargedEnergy = lp.energyMetrics.TotalWh() / 1e3
+	s.ChargeDuration = lo.ToPtr(lp.chargeDuration.Abs())
 
 	lp.db.Persist(s)
 }
diff --git a/go.mod b/go.mod
index 3d45badf..51afd42d 100644
--- a/go.mod
+++ b/go.mod
@@ -209,3 +209,5 @@ replace gopkg.in/yaml.v3 => github.com/andig/yaml v0.0.0-20240531135838-1ff5761a
 replace github.com/grid-x/modbus => github.com/evcc-io/modbus v0.0.0-20241027151224-722a7a5ae529
 
 replace github.com/lorenzodonini/ocpp-go => github.com/evcc-io/ocpp-go v0.0.0-20241230132027-815870498cc3
+
+replace github.com/hasura/go-graphql-client => github.com/andig/go-graphql-client v0.0.0-20250120095858-26928861d4a0
diff --git a/go.sum b/go.sum
index 76ba03e4..192f1291 100644
--- a/go.sum
+++ b/go.sum
@@ -33,6 +33,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/andig/go-graphql-client v0.0.0-20250120095858-26928861d4a0 h1:84UxO6FZf2B93CPiLaKYWuKbQNObatPrv05o3rAfKHw=
+github.com/andig/go-graphql-client v0.0.0-20250120095858-26928861d4a0/go.mod h1:k7FF7h53C+hSNFRG3++DdVZWIuHdCaTbI7siTJ//zGQ=
 github.com/andig/go-powerwall v0.2.1-0.20230808194509-dd70cdb6e140 h1:C93T8vg7CN4Q4BBDbBMOvBoTYBA+CaBEvlY2SZF8aa0=
 github.com/andig/go-powerwall v0.2.1-0.20230808194509-dd70cdb6e140/go.mod h1:Xk09mD+7RTCuuHMX5RxlqgEUeh9oZdIX0rL0qiY6l/4=
 github.com/andig/gosunspec v0.0.0-20240918203654-860ce51d602b h1:81UMfM949I7StrRay7YDUZazY8M1u/JHkzwcFEjiilQ=
@@ -319,8 +321,6 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
 github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
 github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
-github.com/hasura/go-graphql-client v0.13.1 h1:kKbjhxhpwz58usVl+Xvgah/TDha5K2akNTRQdsEHN6U=
-github.com/hasura/go-graphql-client v0.13.1/go.mod h1:k7FF7h53C+hSNFRG3++DdVZWIuHdCaTbI7siTJ//zGQ=
 github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
 github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
diff --git a/i18n/da.toml b/i18n/da.toml
index e057d38d..1d505f7b 100644
--- a/i18n/da.toml
+++ b/i18n/da.toml
@@ -1,5 +1,5 @@
 [batterySettings]
-batteryLevel = "Batteriniveau"
+batteryLevel = "Batteri-niveau"
 capacity = "{energy} af {total}"
 control = "Batteri indstillinger"
 discharge = "Forhindrer afladning ved planlagt opladning og i hurtig mode"
@@ -43,6 +43,7 @@ labelResidualPower = "Tilbageværende effekt"
 title = "kontrol adfærd"
 
 [config.deviceValue]
+amount = "Mængde"
 broker = "Broker"
 bucket = "Bucket"
 capacity = "Kapacitet"
@@ -421,6 +422,8 @@ weekdays = "Dag"
 battery = "Batteri"
 batteryCharge = "Batteriet oplades"
 batteryDischarge = "Batteriet aflades"
+batteryGridChargeActive = "Oplader fra elnettet"
+batteryGridChargeLimit = "Oplader fra elnettet når"
 batteryHold = "Batteri (låst)"
 batteryTooltip = "{energi} af {total} ({soc})"
 gridImport = "Forbrug fra elnet"
diff --git a/i18n/fi.toml b/i18n/fi.toml
index e6056a3f..b11f095c 100644
--- a/i18n/fi.toml
+++ b/i18n/fi.toml
@@ -261,7 +261,7 @@ validateSave = "Vahvista ja tallenna"
 [footer.community]
 greenEnergy = "Aurinkoenergiaa"
 greenEnergySub1 = "ladattu evcc:llä"
-greenEnergySub2 = "Lokakuusta 2022 lähtien"
+greenEnergySub2 = "lokakuusta 2022 lähtien"
 greenShare = "Aurinkoenergian osuus"
 greenShareSub1 = "on peräisin"
 greenShareSub2 = "aurinkoenergiasta ja akkuvarastoinnista"
@@ -271,13 +271,13 @@ powerSub2 = "lataa..."
 tabTitle = "Yhteisö"
 
 [footer.savings]
-co2Saved = "{value} tallennettu"
+co2Saved = "{value} säästetty"
 co2Title = "CO₂ Päästöt"
 configurePriceCo2 = "Opi kuinka voit muokata hinta- ja CO₂ arvoja."
 footerLong = "{percent} aurinkoenergiaa"
 footerShort = "{percent} aurinkoenergiaa"
 modalTitle = "Latausenergian yhteenveto"
-moneySaved = "{value} tallennettu"
+moneySaved = "{value} säästetty"
 percentGrid = "{grid} kWh verkosta"
 percentSelf = "{self} kWh auringosta"
 percentTitle = "Aurinkoenergia"
diff --git a/i18n/fr.toml b/i18n/fr.toml
index 62412a76..bce3afb3 100644
--- a/i18n/fr.toml
+++ b/i18n/fr.toml
@@ -43,6 +43,7 @@ labelResidualPower = "Puissance résiduelle"
 title = "Comportement du contrôle"
 
 [config.deviceValue]
+amount = "Montant"
 broker = "Courtier"
 bucket = "Bucket"
 capacity = "Capacité"
@@ -426,6 +427,8 @@ weekdays = "Jours"
 battery = "Batterie"
 batteryCharge = "Batterie en charge"
 batteryDischarge = "Décharge de la batterie"
+batteryGridChargeActive = "charge réseau active"
+batteryGridChargeLimit = "charge réseau quand"
 batteryHold = "Batterie (verrouillée)"
 batteryTooltip = "{energy} sur {total} ({soc})"
 gridImport = "Utilisation du réseau"
diff --git a/i18n/hr.toml b/i18n/hr.toml
index 9c9c7afa..befe283f 100644
--- a/i18n/hr.toml
+++ b/i18n/hr.toml
@@ -418,6 +418,8 @@ weekdays = "Dani"
 battery = "Baterija"
 batteryCharge = "Punjenje baterije"
 batteryDischarge = "Pražnjenje baterije"
+batteryGridChargeActive = "aktivno punjenje putem mreže"
+batteryGridChargeLimit = "punjene putem mreže kad"
 batteryHold = "Baterija (zaključano)"
 batteryTooltip = "{energy} od {total} ({soc})"
 gridImport = "Upotreba mreže"
diff --git a/i18n/hu.toml b/i18n/hu.toml
index 3cb54677..6e23ce66 100644
--- a/i18n/hu.toml
+++ b/i18n/hu.toml
@@ -408,6 +408,8 @@ weekdays = "Napok"
 battery = "Battery"
 batteryCharge = "Energiatároló töltés"
 batteryDischarge = "Energiatároló kisütés"
+batteryGridChargeActive = "hálózatból töltés aktív"
+batteryGridChargeLimit = "hálózatból töltés ha"
 batteryHold = "Energiatároló (lezárva)"
 batteryTooltip = "{energy} / {total} ({soc})"
 gridImport = "Hálózatból import"
diff --git a/i18n/it.toml b/i18n/it.toml
index 92d924f8..9e22451d 100644
--- a/i18n/it.toml
+++ b/i18n/it.toml
@@ -33,7 +33,7 @@ description = "Garantisce che la somma dei carichi connessi ad un circuito non s
 title = "Gestione del Carico"
 
 [config.control]
-description = "Normalmente le impostazioni di base vanno bene. Modifica solo se sai cosa stai facendo."
+description = "Normalmente le impostazioni di default vanno bene. Modificale solo se sai cosa stai facendo."
 descriptionInterval = "Intervallo ciclo di aggiornamento in secondi. Definisce quanto spesso evcc rileva i dati, modifica la potenza di carica e aggiorna l'interfaccia utente. Intervalli ridotti (<30s) possono causare oscillazioni e comportamenti indesiderati."
 descriptionResidualPower = "Modifica la soglia di corrente per il ciclo di controllo. Se hai una batteria è consigliabile impostare un valore minimo di 100W, in questo modo la batteria riceverà una priorità rispetto all'uso della rete elettrica"
 labelInterval = "Intervallo di aggiornamento"
@@ -408,7 +408,7 @@ remove = "Rimuovi"
 repeating = "ricorrente"
 repeatingPlans = "Piani ricorrenti"
 selectAll = "Seleziona tutti"
-time = "Tempo"
+time = "Ora"
 title = "Piano"
 titleMinSoc = "Carica minima"
 titleTargetCharge = "Partenza"
@@ -507,8 +507,8 @@ descriptionEnergy = "Fino a quando dovrebbero essere {targetEnergy} caricati nel
 descriptionSoc = "Quando dovrebbe essere caricato il veicolo al {targetSoc}?"
 inactiveLabel = "Tempo target"
 modalTitle = "Imposta orario target"
-nextPlan = "“Prossimo piano\""
-notReachableInTime = "L'obbiettivo sarà raggiunto con un ritardo di {overrun}."
+nextPlan = "Prossimo piano"
+notReachableInTime = "L'obiettivo sarà raggiunto con un ritardo di {overrun}."
 onlyInPvMode = "Il piano di ricarica funziona solo in modalità Solare."
 planDuration = "Tempo di carica"
 planPeriodLabel = "Periodo"
diff --git a/i18n/lb.toml b/i18n/lb.toml
index 3c4a87f7..723a8eee 100644
--- a/i18n/lb.toml
+++ b/i18n/lb.toml
@@ -40,6 +40,7 @@ labelResidualPower = "Residuell Leeschtung"
 title = "Kontrollverhalen"
 
 [config.deviceValue]
+amount = "Unzahl"
 broker = "Broker"
 bucket = "Eemer"
 capacity = "Capacitéit"
@@ -419,6 +420,8 @@ weekdays = "Deeg"
 battery = "Batterie"
 batteryCharge = "Batterie oplueden"
 batteryDischarge = "Batterie entlueden"
+batteryGridChargeActive = "Netzlueden aktiv"
+batteryGridChargeLimit = "Netzluede wann"
 batteryHold = "Batterie (gespäert)"
 batteryTooltip = "{energy} vun {total} ({soc})"
 gridImport = "Verbrauch vum Stroumnetz"
diff --git a/i18n/lt.toml b/i18n/lt.toml
index 4dc18dc0..0bc16b00 100644
--- a/i18n/lt.toml
+++ b/i18n/lt.toml
@@ -43,6 +43,7 @@ labelResidualPower = "Likutinė galia"
 title = "Valdymo elgesys"
 
 [config.deviceValue]
+amount = "Kiekis"
 broker = "Broker sistema"
 bucket = "Bucket"
 capacity = "Talpa"
@@ -426,6 +427,8 @@ weekdays = "Dienos"
 battery = "Kaupiklis"
 batteryCharge = "Kaupiklis įkraunamas"
 batteryDischarge = "Kaupiklis iškraunamas"
+batteryGridChargeActive = "įkraunama iš tinklo"
+batteryGridChargeLimit = "įkrauti iš tinklo, kai"
 batteryHold = "Kaupiklis (užblokuotas)"
 batteryTooltip = "{energy} iš {total} ({soc})"
 gridImport = "Iš tinklo"
diff --git a/i18n/nl.toml b/i18n/nl.toml
index f33b818e..288c4899 100644
--- a/i18n/nl.toml
+++ b/i18n/nl.toml
@@ -402,14 +402,17 @@ vehicles = "Parking"
 
 [main.chargingPlan]
 active = "Actief"
+addRepeatingPlan = "Herhalend plan toevoegen"
 arrivalTab = "Aankomst"
 day = "Dag"
 departureTab = "Vertrek"
 goal = "Hoeveelheid"
 modalTitle = "Laad Plan"
 none = "geen"
+planNumber = "Plan {number}"
 remove = "Verwijderen"
 repeating = "herhalend"
+repeatingPlans = "Herhalende plannen"
 selectAll = "Alles selectreren"
 time = "Tijd"
 title = "Plan"
@@ -417,12 +420,14 @@ titleMinSoc = "Min lading"
 titleTargetCharge = "Vertrek"
 unsavedChanges = "Wijzigingen nog niet opgeslagen. Nu toepassen?"
 update = "Toepassen"
-weekdays = "Weekdagen"
+weekdays = "Dagen"
 
 [main.energyflow]
 battery = "Batterij"
 batteryCharge = "Batterij opladen"
 batteryDischarge = "Batterij ontladen"
+batteryGridChargeActive = "net laden actief"
+batteryGridChargeLimit = "net laden wanneer"
 batteryHold = "Batterij (aanhouden)"
 batteryTooltip = "{energy} van {total} ({soc})"
 gridImport = "Netafname"
@@ -512,6 +517,7 @@ descriptionEnergy = "Tegen wanneer moet {targetEnergy} in het voertuig geladen z
 descriptionSoc = "Wanneer moet het voertuig geladen zijn naar {targetSoc}?"
 inactiveLabel = "Ingestelde tijd"
 modalTitle = "Stel tijd in"
+nextPlan = "Volgend plan"
 notReachableInTime = "Laaddoel zal {overrun} later bereikt zijn."
 onlyInPvMode = "Laadplan werkt alleen in PV modus"
 planDuration = "Oplaadtijd"
@@ -588,6 +594,7 @@ scale3p = "Wordt verhoogd naar laden op 3 fasen."
 targetChargeActive = "Laadplan actief. Geschatte beëindiging over {duration}."
 targetChargePlanned = "Laadplan begint over {duration}."
 targetChargeWaitForVehicle = "Laadplan gereed. Wachten op voertuig..."
+unknown = ""
 vehicleLimit = "Voertuiglimiet."
 vehicleLimitReached = "Voertuiglimiet bereikt."
 waitForVehicle = "Klaar. Wachten op voertuig..."
diff --git a/i18n/pt.toml b/i18n/pt.toml
index a512b1a7..f3b9eaba 100644
--- a/i18n/pt.toml
+++ b/i18n/pt.toml
@@ -43,6 +43,7 @@ labelResidualPower = "Poder real"
 title = "Comportamento de controle"
 
 [config.deviceValue]
+amount = "Quantia"
 broker = "Corretor"
 bucket = "Balde"
 capacity = "Capacidade"
@@ -422,6 +423,8 @@ weekdays = "Dias"
 battery = "Bateria"
 batteryCharge = "Carrego de bateria"
 batteryDischarge = "Descarrego de bateria"
+batteryGridChargeActive = "carga da rede ativa"
+batteryGridChargeLimit = "carga da rede quando"
 batteryHold = "Bateria (suspensa)"
 batteryTooltip = "{energy} de {total} ({soc})"
 gridImport = "Consumo de rede"
diff --git a/i18n/sl.toml b/i18n/sl.toml
index 5e8bcb37..071b8693 100644
--- a/i18n/sl.toml
+++ b/i18n/sl.toml
@@ -397,19 +397,25 @@ vehicles = "Parkiranje"
 
 [main.chargingPlan]
 active = "Aktivno"
+addRepeatingPlan = "Dodaj ponavljajoči načrt"
 arrivalTab = "Prihod"
 day = "Dan"
 departureTab = "Odhod"
 goal = "Cilj polnjenja"
 modalTitle = "Načrt polnjenja"
 none = "brez"
+planNumber = "Načrt {number}"
 remove = "Odstrani"
+repeating = "ponavljajoči"
+repeatingPlans = "Ponavljajoči načrti"
+selectAll = "Izberi vse"
 time = "Čas"
 title = "Načrt"
 titleMinSoc = "Min. polnjenje"
 titleTargetCharge = "Odhod"
 unsavedChanges = "Obstajajo neshranjene spremembe. Želite uporabiti zdaj?"
 update = "Uporabi"
+weekdays = "Dnevi"
 
 [main.energyflow]
 battery = "Baterija"
diff --git a/i18n/sv.toml b/i18n/sv.toml
index d16e0142..eacae2d2 100644
--- a/i18n/sv.toml
+++ b/i18n/sv.toml
@@ -43,6 +43,7 @@ labelResidualPower = "Kvarvarande effekt"
 title = "Kontrollera beteende"
 
 [config.deviceValue]
+amount = "Belopp"
 broker = "Broker"
 bucket = "Bucket"
 capacity = "Kapacitet"
@@ -421,6 +422,8 @@ weekdays = "Dagar"
 battery = "Batteri"
 batteryCharge = "Batteri laddas"
 batteryDischarge = "Batteri laddas ur"
+batteryGridChargeActive = "nätladdning aktiv"
+batteryGridChargeLimit = "nätladdning när"
 batteryHold = "Batteri (låst)"
 batteryTooltip = "{energy} av {total} ({soc})"
 gridImport = "Import från elnät"
diff --git a/templates/definition/charger/ocpp-fronius-wattpilot.yaml b/templates/definition/charger/ocpp-fronius-wattpilot.yaml
index b296f4ee..172bee7f 100644
--- a/templates/definition/charger/ocpp-fronius-wattpilot.yaml
+++ b/templates/definition/charger/ocpp-fronius-wattpilot.yaml
@@ -7,12 +7,12 @@ requirements:
   description:
     de: |
       Hinweis zur Konfiguration des Wattpilot mittels solar.wattpilot app: das Symbol neben dem 
-      Beispiel-OCPP-Server "wss://<username>:<password>@ocpp.cloud.fronius.com/<UID>" dient zur Konfiguration. 
-      Hier muss der evcc URL "ws://<IP-vom-evcc-host>:8887/ eingetragen sein und "Füge SN zur URL" aktiviert werden.
+      Beispiel-OCPP-Server `wss://<username>:<password>@ocpp.cloud.fronius.com/<UID>` dient zur Konfiguration. 
+      Hier muss der evcc URL `ws://<evcc-address>:8887/` eingetragen sein und "Füge SN zur URL" aktiviert werden.
     en: |
       Note on configuring the Wattpilot using the solar.wattpilot app: The symbol next to the 
-      example OCPP server "wss://<username>:<password>@ocpp.cloud.fronius.com/<UID>" is used for configuration.
-      The evcc URL "ws://<IP-of-the-evcc-host>:8887/" must be entered here and "Add SN to URL" must be activated.
+      example OCPP server `wss://<username>:<password>@ocpp.cloud.fronius.com/<UID>` is used for configuration.
+      The evcc URL `ws://<evcc-address>:8887/` must be entered here and "Add SN to URL" must be activated.
   evcc: ["sponsorship", "skiptest"]
 params:
   - preset: ocpp
diff --git a/templates/definition/charger/vaillant.yaml b/templates/definition/charger/vaillant.yaml
index 2ca437f5..56469ee4 100644
--- a/templates/definition/charger/vaillant.yaml
+++ b/templates/definition/charger/vaillant.yaml
@@ -7,21 +7,21 @@ group: heating
 requirements:
   # evcc: ["sponsorship"]
   description:
-    de: Die Boost Funktion erwärmt Warmwasser und optional eine Boostzone. Die Boostzone wird durch die ID identifiziert. Die Boost Temperatur wird in Grad Celsius angegeben.
-    en: The boost function heats hot water and optionally a boost zone. The boost zone is identified by the ID. The boost temperature is specified in degrees Celsius.
+    de: Die Boost Funktion erwärmt Warmwasser oder eine Boostzone. Die Boostzone wird durch die ID identifiziert. Die Boost Temperatur wird in Grad Celsius angegeben. Ist eine Boost Temperatur angegeben, wird die Boostzone aktiviert, anderenfalls Warmwasser.
+    en: The boost function heats hot water or a boost zone. The boost zone is identified by the ID. The boost temperature is specified in degrees Celsius. If boost temperature is specified, the boost zone is activated, otherwise hot water.
 params:
   - name: user
   - name: password
   - name: zone
     type: int
     help:
-      de: Id der Boostzone (zusätzlich zu Warmwasser)
-      en: Boost zone id (in addition to hot water)
+      de: Id der Boostzone
+      en: Boost zone id
   - name: setpoint
     type: float
     help:
-      de: Boost Temperatur (zusätzlich zu Warmwasser)
-      en: Boost temperature setpoint (in addition to hot water)
+      de: Boost Temperatur
+      en: Boost temperature setpoint
   - name: phases
     type: int
     advanced: true
diff --git a/templates/definition/meter/alpha-ess-smile.yaml b/templates/definition/meter/alpha-ess-smile.yaml
index 564caa1a..9569c7a5 100644
--- a/templates/definition/meter/alpha-ess-smile.yaml
+++ b/templates/definition/meter/alpha-ess-smile.yaml
@@ -28,6 +28,7 @@ params:
     default: 95
     type: int
     advanced: true
+  - name: maxacpower
 render: |
   type: custom
   {{- if eq .usage "grid" }}
@@ -123,6 +124,7 @@ render: |
       type: holding
       decode: uint32
     scale: 0.01
+  maxacpower: {{ .maxacpower }}
   {{- end }}
   {{- if eq .usage "battery" }}
   power:
diff --git a/templates/definition/meter/deye-hybrid-3p.yaml b/templates/definition/meter/deye-hybrid-3p.yaml
index 2b2c09ef..cd423a82 100644
--- a/templates/definition/meter/deye-hybrid-3p.yaml
+++ b/templates/definition/meter/deye-hybrid-3p.yaml
@@ -26,6 +26,7 @@ params:
     type: int
     default: 95
     advanced: true
+  - name: maxacpower
 render: |
   type: custom
   {{- if eq .usage "grid" }}
@@ -103,6 +104,7 @@ render: |
       type: holding
       decode: uint32s
     scale: 0.1
+  maxacpower: {{ .maxacpower }}
   {{- end }}
   {{- if eq .usage "battery" }}
   power:
diff --git a/templates/definition/meter/deye-hybrid-hp3.yaml b/templates/definition/meter/deye-hybrid-hp3.yaml
index fb5dcd0e..08b73c0b 100644
--- a/templates/definition/meter/deye-hybrid-hp3.yaml
+++ b/templates/definition/meter/deye-hybrid-hp3.yaml
@@ -25,6 +25,7 @@ params:
     type: int
     default: 95
     advanced: true
+  - name: maxacpower
 render: |
   type: custom
   {{- if eq .usage "grid" }}
@@ -106,6 +107,7 @@ render: |
       type: holding
       decode: uint32s
     scale: 0.1
+  maxacpower: {{ .maxacpower }}
   {{- end }}
   {{- if eq .usage "battery" }}
   power:
diff --git a/templates/definition/meter/fox-ess-h3.yaml b/templates/definition/meter/fox-ess-h3.yaml
index e9ddc36d..2429279e 100644
--- a/templates/definition/meter/fox-ess-h3.yaml
+++ b/templates/definition/meter/fox-ess-h3.yaml
@@ -19,6 +19,7 @@ params:
   - name: maxsoc
     type: int
     advanced: true
+  - name: maxacpower
 render: |
   type: custom
   {{- if eq .usage "grid" }}
@@ -71,6 +72,7 @@ render: |
         address: 31005 # PV2
         type: holding
         decode: int16
+  maxacpower: {{ .maxacpower }}
   {{- end }}
   {{- if eq .usage "battery" }}
   power:
diff --git a/templates/definition/meter/huawei-smartlogger.yaml b/templates/definition/meter/huawei-smartlogger.yaml
index ffafd986..55da9550 100644
--- a/templates/definition/meter/huawei-smartlogger.yaml
+++ b/templates/definition/meter/huawei-smartlogger.yaml
@@ -18,6 +18,7 @@ params:
     default: 15s
   - name: capacity
     advanced: true
+  - name: maxacpower
 render: |
   type: custom
   {{- if eq .usage "grid" }}
@@ -84,6 +85,7 @@ render: |
       type: holding
       decode: uint32
     scale: 0.1
+  maxacpower: {{ .maxacpower }}
   {{- end }}
   {{- if eq .usage "battery" }}
   power:
diff --git a/templates/definition/meter/huawei-sun2000.yaml b/templates/definition/meter/huawei-sun2000.yaml
index a51725c9..7ecfc350 100644
--- a/templates/definition/meter/huawei-sun2000.yaml
+++ b/templates/definition/meter/huawei-sun2000.yaml
@@ -32,6 +32,7 @@ params:
     advanced: true
   - name: capacity
     advanced: true
+  - name: maxacpower
   - name: forceaccharging
     default: false
     advanced: true
@@ -110,6 +111,7 @@ render: |
       type: holding
       decode: uint32nan
     scale: 0.01
+  maxacpower: {{ .maxacpower }}
   {{- end }}
   {{- if eq .usage "battery" }}
   power:
diff --git a/templates/definition/meter/sofarsolar-g3.yaml b/templates/definition/meter/sofarsolar-g3.yaml
index baac1cd6..95798cd6 100644
--- a/templates/definition/meter/sofarsolar-g3.yaml
+++ b/templates/definition/meter/sofarsolar-g3.yaml
@@ -29,6 +29,7 @@ params:
     deprecated: true
   - name: capacity
     advanced: true
+  - name: maxacpower
   - name: defaultmode
     help:
       de: Standardmodus für die aktive Batteriesteuerung. Gültige Werte sind 0 (Eigenbedarfsmodus), 1 (Nutzungszeitmodus), 2 (Zeitmodus), 4 (Peak-shaving Modus)
@@ -125,6 +126,7 @@ render: |
       type: holding
       decode: uint32
     scale: 0.1
+  maxacpower: {{ .maxacpower }}
   {{- end }}
   {{- if eq .usage "battery" }}
   power:
diff --git a/templates/definition/meter/solarmax-maxstorage.yaml b/templates/definition/meter/solarmax-maxstorage.yaml
index 7bf178ad..d9858b65 100644
--- a/templates/definition/meter/solarmax-maxstorage.yaml
+++ b/templates/definition/meter/solarmax-maxstorage.yaml
@@ -17,6 +17,7 @@ params:
     id: 1
   - name: capacity
     advanced: true
+  - name: maxacpower
   # battery control
   - name: watchdog
     type: duration
@@ -41,6 +42,7 @@ render: |
       address: 110 # PV-Leistung MAX.STORAGE
       type: input
       decode: int32s
+  maxacpower: {{ .maxacpower }}
   {{- end }}
   {{- if eq .usage "battery" }}
     source: modbus
diff --git a/templates/definition/meter/victron-energy.yaml b/templates/definition/meter/victron-energy.yaml
index a046bd1b..2eeb1590 100644
--- a/templates/definition/meter/victron-energy.yaml
+++ b/templates/definition/meter/victron-energy.yaml
@@ -17,6 +17,7 @@ params:
     default: 502
   - name: capacity
     advanced: true
+  - name: maxacpower
   - name: meterid # grid meter VRM instance
     type: int
     usages: ["grid"]
@@ -138,6 +139,7 @@ render: |
         address: 850 # DC pv power
         type: input
         decode: uint16
+  maxacpower: {{ .maxacpower }}
   {{- end }}
   {{- if eq .usage "battery" }}
   power:
diff --git a/templates/definition/vehicle/tesla.yaml b/templates/definition/vehicle/tesla.yaml
index 7c2bc7c2..0cc19467 100644
--- a/templates/definition/vehicle/tesla.yaml
+++ b/templates/definition/vehicle/tesla.yaml
@@ -6,7 +6,7 @@ requirements:
   description:
     de: |
       Tesla bietet eine offizielle, aber kostenpflichtige Fahrzeug-API an.
-      Für private Nutzung kannst du dir einen [Tesla Developer Account](https://developer.tesla.com/) erstellen und erhältst ein monatliches API-Guthaben von $10. Das ist für die gängigen evcc-Anwendungsfälle in der Regel ausreichend.
+      Für private Nutzung kannst du dir einen [Tesla Developer Account](https://developer.tesla.com/) erstellen und erhältst ein monatliches API-Guthaben von 10$. Das ist für die gängigen evcc-Anwendungsfälle in der Regel ausreichend.
 
       Die Anleitung von [myteslamate.com](https://www.myteslamate.com/tesla-api-application-registration/) erklärt den Prozess und generiert dir kostenfrei die für evcc benötigten Access- und Refresh-Token.
       Mit diesem Tokenpaar und deiner im Tesla Developer Account erstellten Client ID kann evcc direkt mit der Tesla API kommunizieren. Dein verbrauchtes Guthaben kannst du bei Tesla einsehen.
@@ -14,7 +14,7 @@ requirements:
       Für die Nutzung des "Tesla Wall Connectors" benötigst du einen öffentlichen Command-Proxy-Server. [myteslamate.com](https://app.myteslamate.com/) stellt diesen Dienst für 12€/Jahr bereit. Konfiguriere dafür bei myteslamate.com die Command-Berechtigungen und trage das Proxy-Token hier ein. Start-, Stopp- und Stromstärken-Kommandos werden über diesen Proxy an Tesla geschickt.
     en: |
       Tesla offers an official, but paid vehicle API.
-      For private use, you can create a [Tesla Developer Account](https://developer.tesla.com/) and receive a monthly API credit of $10. This is usually sufficient for the common evcc use cases.
+      For private use, you can create a [Tesla Developer Account](https://developer.tesla.com/) and receive a monthly API credit of 10$. This is usually sufficient for the common evcc use cases.
 
       The [myteslamate.com](https://www.myteslamate.com/tesla-api-application-registration/) guide explains the process and generates a free Access and Refresh Token. With this token pair and your Client ID created in the Tesla Developer Account, evcc can directly communicate with the Tesla API. You can see your used credit at Tesla.
 
@@ -27,17 +27,16 @@ params:
       en: Client ID of your [Tesla Developer App](https://developer.tesla.com/dashboard).
       de: Kunden ID deiner [Tesla Developer App](https://developer.tesla.com/dashboard).
     required: true
-    advanced: true
   - name: accessToken
     help:
-      en: From [myteslamate.com](https://app.myteslamate.com/).
-      de: Von [myteslamate.com](https://app.myteslamate.com/).
+      en: from [myteslamate.com](https://app.myteslamate.com/).
+      de: von [myteslamate.com](https://app.myteslamate.com/).
     required: true
     mask: true
   - name: refreshToken
     help:
-      en: From [myteslamate.com](https://app.myteslamate.com/).
-      de: Von [myteslamate.com](https://app.myteslamate.com/).
+      en: from [myteslamate.com](https://app.myteslamate.com/).
+      de: von [myteslamate.com](https://app.myteslamate.com/).
     required: true
     mask: true
   - name: vin


Test patch:
diff --git a/core/energy_metrics_test.go b/core/energy_metrics_test.go
index 65ac760b..006260ca 100644
--- a/core/energy_metrics_test.go
+++ b/core/energy_metrics_test.go
@@ -117,7 +117,7 @@ func TestEnergyMetrics(t *testing.T) {
 	}
 
 	for _, tc := range tc {
-		s := NewEnergyMetrics()
+		var s EnergyMetrics
 
 		for _, tc := range tc.steps {
 			s.SetEnvironment(tc.greenShare, tc.effPrice, tc.effCo2)
@@ -145,7 +145,7 @@ func TestEnergyMetrics(t *testing.T) {
 	}
 
 	// reset
-	s := NewEnergyMetrics()
+	var s EnergyMetrics
 	s.SetEnvironment(1, f(1), f(1))
 	s.Update(1)
 	s.Reset()
diff --git a/core/loadpoint_session_test.go b/core/loadpoint_session_test.go
index 412d4f5f..946c6df7 100644
--- a/core/loadpoint_session_test.go
+++ b/core/loadpoint_session_test.go
@@ -38,11 +38,10 @@ func TestSession(t *testing.T) {
 	cm := &EnergyDecorator{Meter: mm, MeterEnergy: me}
 
 	lp := &Loadpoint{
-		log:           util.NewLogger("foo"),
-		clock:         clock,
-		db:            db,
-		chargeMeter:   cm,
-		sessionEnergy: NewEnergyMetrics(),
+		log:         util.NewLogger("foo"),
+		clock:       clock,
+		db:          db,
+		chargeMeter: cm,
 	}
 
 	// create session
@@ -60,7 +59,7 @@ func TestSession(t *testing.T) {
 
 	// stop charging
 	clock.Add(time.Hour)
-	lp.sessionEnergy.Update(1.23)
+	lp.energyMetrics.Update(1.23)
 	me.EXPECT().TotalEnergy().Return(1.0+lp.getChargedEnergy()/1e3, nil) // match chargedEnergy
 
 	lp.stopSession()
@@ -75,7 +74,7 @@ func TestSession(t *testing.T) {
 
 	// stop charging - 2nd leg
 	clock.Add(time.Hour)
-	lp.sessionEnergy.Update(lp.getChargedEnergy() * 2)
+	lp.energyMetrics.Update(lp.getChargedEnergy() * 2)
 	me.EXPECT().TotalEnergy().Return(3.0, nil) // doesn't match chargedEnergy
 
 	lp.stopSession()
diff --git a/core/loadpoint_test.go b/core/loadpoint_test.go
index 4adfbb80..f19590d3 100644
--- a/core/loadpoint_test.go
+++ b/core/loadpoint_test.go
@@ -156,19 +156,18 @@ func TestUpdatePowerZero(t *testing.T) {
 		charger := api.NewMockCharger(ctrl)
 
 		lp := &Loadpoint{
-			log:           util.NewLogger("foo"),
-			bus:           evbus.New(),
-			clock:         clck,
-			charger:       charger,
-			chargeMeter:   &Null{}, // silence nil panics
-			chargeRater:   &Null{}, // silence nil panics
-			chargeTimer:   &Null{}, // silence nil panics
-			wakeUpTimer:   NewTimer(),
-			sessionEnergy: NewEnergyMetrics(),
-			minCurrent:    minA,
-			maxCurrent:    maxA,
-			phases:        1,
-			status:        tc.status, // no status change
+			log:         util.NewLogger("foo"),
+			bus:         evbus.New(),
+			clock:       clck,
+			charger:     charger,
+			chargeMeter: &Null{}, // silence nil panics
+			chargeRater: &Null{}, // silence nil panics
+			chargeTimer: &Null{}, // silence nil panics
+			wakeUpTimer: NewTimer(),
+			minCurrent:  minA,
+			maxCurrent:  maxA,
+			phases:      1,
+			status:      tc.status, // no status change
 		}
 
 		attachListeners(t, lp)
@@ -402,13 +401,12 @@ func TestDisableAndEnableAtTargetSoc(t *testing.T) {
 		progress:    NewProgress(0, 10), // silence nil panics
 		wakeUpTimer: NewTimer(),         // silence nil panics
 		// coordinator:   coordinator.NewDummy(), // silence nil panics
-		minCurrent:    minA,
-		maxCurrent:    maxA,
-		vehicle:       vehicle,      // needed for targetSoc check
-		socEstimator:  socEstimator, // instead of vehicle: vehicle,
-		mode:          api.ModeNow,
-		sessionEnergy: NewEnergyMetrics(),
-		limitSoc:      90, // session limit
+		minCurrent:   minA,
+		maxCurrent:   maxA,
+		vehicle:      vehicle,      // needed for targetSoc check
+		socEstimator: socEstimator, // instead of vehicle: vehicle,
+		mode:         api.ModeNow,
+		limitSoc:     90, // session limit
 		Soc: SocConfig{
 			Poll: PollConfig{
 				Mode:     pollConnected, // allow polling when connected
@@ -472,19 +470,18 @@ func TestSetModeAndSocAtDisconnect(t *testing.T) {
 	charger := api.NewMockCharger(ctrl)
 
 	lp := &Loadpoint{
-		log:           util.NewLogger("foo"),
-		bus:           evbus.New(),
-		clock:         clock,
-		charger:       charger,
-		chargeMeter:   &Null{}, // silence nil panics
-		chargeRater:   &Null{}, // silence nil panics
-		chargeTimer:   &Null{}, // silence nil panics
-		wakeUpTimer:   NewTimer(),
-		sessionEnergy: NewEnergyMetrics(),
-		minCurrent:    minA,
-		maxCurrent:    maxA,
-		status:        api.StatusC,
-		Mode_:         api.ModeOff, // default mode
+		log:         util.NewLogger("foo"),
+		bus:         evbus.New(),
+		clock:       clock,
+		charger:     charger,
+		chargeMeter: &Null{}, // silence nil panics
+		chargeRater: &Null{}, // silence nil panics
+		chargeTimer: &Null{}, // silence nil panics
+		wakeUpTimer: NewTimer(),
+		minCurrent:  minA,
+		maxCurrent:  maxA,
+		status:      api.StatusC,
+		Mode_:       api.ModeOff, // default mode
 	}
 
 	attachListeners(t, lp)
@@ -540,18 +537,17 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
 	rater := api.NewMockChargeRater(ctrl)
 
 	lp := &Loadpoint{
-		log:           util.NewLogger("foo"),
-		bus:           evbus.New(),
-		clock:         clock,
-		charger:       charger,
-		chargeMeter:   &Null{}, // silence nil panics
-		chargeRater:   rater,
-		chargeTimer:   &Null{}, // silence nil panics
-		wakeUpTimer:   NewTimer(),
-		sessionEnergy: NewEnergyMetrics(),
-		minCurrent:    minA,
-		maxCurrent:    maxA,
-		status:        api.StatusC,
+		log:         util.NewLogger("foo"),
+		bus:         evbus.New(),
+		clock:       clock,
+		charger:     charger,
+		chargeMeter: &Null{}, // silence nil panics
+		chargeRater: rater,
+		chargeTimer: &Null{}, // silence nil panics
+		wakeUpTimer: NewTimer(),
+		minCurrent:  minA,
+		maxCurrent:  maxA,
+		status:      api.StatusC,
 	}
 
 	attachListeners(t, lp)
diff --git a/core/loadpoint_vehicle_test.go b/core/loadpoint_vehicle_test.go
index d60cf8da..ebe3bbb2 100644
--- a/core/loadpoint_vehicle_test.go
+++ b/core/loadpoint_vehicle_test.go
@@ -37,20 +37,19 @@ func TestPublishSocAndRange(t *testing.T) {
 
 	log := util.NewLogger("foo")
 	lp := &Loadpoint{
-		log:           log,
-		bus:           evbus.New(),
-		clock:         clck,
-		charger:       charger,
-		vehicle:       vehicle,
-		chargeMeter:   &Null{}, // silence nil panics
-		chargeRater:   &Null{}, // silence nil panics
-		chargeTimer:   &Null{}, // silence nil panics
-		socEstimator:  soc.NewEstimator(log, charger, vehicle, false),
-		sessionEnergy: NewEnergyMetrics(),
-		minCurrent:    minA,
-		maxCurrent:    maxA,
-		phases:        1,
-		mode:          api.ModeNow,
+		log:          log,
+		bus:          evbus.New(),
+		clock:        clck,
+		charger:      charger,
+		vehicle:      vehicle,
+		chargeMeter:  &Null{}, // silence nil panics
+		chargeRater:  &Null{}, // silence nil panics
+		chargeTimer:  &Null{}, // silence nil panics
+		socEstimator: soc.NewEstimator(log, charger, vehicle, false),
+		minCurrent:   minA,
+		maxCurrent:   maxA,
+		phases:       1,
+		mode:         api.ModeNow,
 	}
 
 	// populate channels
@@ -243,19 +242,18 @@ func TestReconnectVehicle(t *testing.T) {
 			charger.EXPECT().Status().Return(api.StatusB, nil).AnyTimes()
 
 			lp := &Loadpoint{
-				log:           util.NewLogger("foo"),
-				bus:           evbus.New(),
-				clock:         clck,
-				charger:       charger,
-				chargeMeter:   &Null{}, // silence nil panics
-				chargeRater:   &Null{}, // silence nil panics
-				chargeTimer:   &Null{}, // silence nil panics
-				wakeUpTimer:   NewTimer(),
-				sessionEnergy: NewEnergyMetrics(),
-				minCurrent:    minA,
-				maxCurrent:    maxA,
-				phases:        1,
-				mode:          api.ModeNow,
+				log:         util.NewLogger("foo"),
+				bus:         evbus.New(),
+				clock:       clck,
+				charger:     charger,
+				chargeMeter: &Null{}, // silence nil panics
+				chargeRater: &Null{}, // silence nil panics
+				chargeTimer: &Null{}, // silence nil panics
+				wakeUpTimer: NewTimer(),
+				minCurrent:  minA,
+				maxCurrent:  maxA,
+				phases:      1,
+				mode:        api.ModeNow,
 			}
 
 			lp.coordinator = coordinator.NewAdapter(lp, coordinator.New(util.NewLogger("foo"), []api.Vehicle{vehicle}))