Remove guard timer but keep charger sync logic (#12084)

This commit is contained in:
MarkusGH 2024-02-10 17:54:41 +01:00 • committed by GitHub
parent 75fcac5e7a
commit d25af7d2be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 19 additions and 267 deletions

View file

@ -182,8 +182,6 @@ export default {
phaseRemaining: Number,
pvRemaining: Number,
pvAction: String,
guardRemaining: Number,
guardAction: String,
smartCostLimit: Number,
smartCostType: String,
smartCostActive: Boolean,
@ -196,7 +194,6 @@ export default {
tickerHandler: null,
phaseRemainingInterpolated: this.phaseRemaining,
pvRemainingInterpolated: this.pvRemaining,
guardRemainingInterpolated: this.guardRemaining,
chargeDurationInterpolated: this.chargeDuration,
chargeRemainingDurationInterpolated: this.chargeRemainingDuration,
};
@ -252,9 +249,6 @@ export default {
pvRemaining() {
this.pvRemainingInterpolated = this.pvRemaining;
},
guardRemaining() {
this.guardRemainingInterpolated = this.guardRemaining;
},
chargeDuration() {
this.chargeDurationInterpolated = this.chargeDuration;
},
@ -276,9 +270,6 @@ export default {
if (this.pvRemainingInterpolated > 0) {
this.pvRemainingInterpolated--;
}
if (this.guardRemainingInterpolated > 0) {
this.guardRemainingInterpolated--;
}
if (this.chargeDurationInterpolated > 0 && this.charging) {
this.chargeDurationInterpolated++;
}

View file

@ -108,9 +108,6 @@ const hoursFromNow = function (hours) {
:pvRemainingInterpolated="155"
/>
</Variant>
<Variant title="guard timer">
<Vehicle v-bind="state" guardAction="enable" :guardRemainingInterpolated="123" />
</Variant>
<Variant title="vehicle switch">
<Vehicle
v-bind="state"

View file

@ -95,8 +95,6 @@ export default {
effectivePlanSoc: Number,
effectivePlanTime: String,
enabled: Boolean,
guardAction: String,
guardRemainingInterpolated: Number,
heating: Boolean,
id: [String, Number],
integratedDevice: Boolean,

View file

@ -144,43 +144,6 @@ describe("timer", () => {
{ remaining: "1:30\u202Fm" }
);
});
test("show guard timer if it exists", () => {
expectStatus(
{
connected: true,
guardAction: "enable",
guardRemainingInterpolated: 90,
},
"guard",
{ remaining: "1:30\u202Fm" }
);
});
test("don't show guard timer if another timer exists", () => {
expectStatus(
{
connected: true,
charging: true,
pvAction: "disable",
pvRemainingInterpolated: 30,
guardAction: "enable",
guardRemainingInterpolated: 90,
},
"pvDisable",
{ remaining: "30\u202Fs" }
);
});
test("show guard timer if charging and no other timer exists", () => {
expectStatus(
{
connected: true,
charging: true,
guardAction: "enable",
guardRemainingInterpolated: 90,
},
"guard",
{ remaining: "1:30\u202Fm" }
);
});
});
describe("vehicle target soc", () => {

View file

@ -25,8 +25,6 @@ export default {
phaseRemainingInterpolated: Number,
pvAction: String,
pvRemainingInterpolated: Number,
guardAction: String,
guardRemainingInterpolated: Number,
targetChargeDisabled: Boolean,
vehicleClimaterActive: Boolean,
smartCostLimit: Number,
@ -48,9 +46,6 @@ export default {
this.pvRemainingInterpolated > 0 && ["enable", "disable"].includes(this.pvAction)
);
},
guardTimerActive() {
return this.guardRemainingInterpolated > 0 && this.guardAction === "enable";
},
message: function () {
const t = (key, data) => {
if (this.heating) {
@ -128,12 +123,6 @@ export default {
});
}
if (this.guardTimerActive) {
return t("guard", {
remaining: this.fmtDuration(this.guardRemainingInterpolated),
});
}
if (this.charging) {
return t("charging");
}

View file

@ -43,9 +43,6 @@ const (
pvEnable = "enable"
pvDisable = "disable"
guardTimer = "guard"
guardEnable = "enable"
phaseTimer = "phase"
phaseScale1p = "scale1p"
phaseScale3p = "scale3p"
@ -55,9 +52,8 @@ const (
minActiveCurrent = 1.0 // minimum current at which a phase is treated as active
minActiveVoltage = 207 // minimum voltage at which a phase is treated as active
guardGracePeriod = 60 * time.Second // allow out of sync during this timespan
phaseSwitchCommandTimeout = 30 * time.Second // do not sync charger enabled/disabled state during this timespan
phaseSwitchDuration = 60 * time.Second // do not measure phases during this timespan
chargerSwitchDuration = 60 * time.Second // allow out of sync during this timespan
phaseSwitchDuration = 60 * time.Second // allow out of sync and do not measure phases during this timespan
)
// elapsed is the time an expired timer will be set to
@ -116,12 +112,12 @@ type Loadpoint struct {
MeterRef string `mapstructure:"meter"` // Charge meter reference
Soc SocConfig
Enable, Disable ThresholdConfig
GuardDuration time.Duration // charger enable/disable minimum holding time
// TODO deprecated
ConfiguredPhases_ int `mapstructure:"phases"`
MinCurrent_ float64 `mapstructure:"minCurrent"`
MaxCurrent_ float64 `mapstructure:"maxCurrent"`
GuardDuration_ time.Duration `mapstructure:"guardduration"` // charger enable/disable minimum holding time
ConfiguredPhases_ int `mapstructure:"phases"`
MinCurrent_ float64 `mapstructure:"minCurrent"`
MaxCurrent_ float64 `mapstructure:"maxCurrent"`
minCurrent float64 // PV mode: start current Min+PV mode: min current
maxCurrent float64 // Max allowed current. Physically ensured by the charger
@ -134,9 +130,9 @@ type Loadpoint struct {
phases int // Charger enabled phases, guarded by mutex
measuredPhases int // Charger physically measured phases
chargeCurrent float64 // Charger current limit
guardUpdated time.Time // Charger enabled/disabled timestamp
socUpdated time.Time // Soc updated timestamp (poll: connected)
vehicleDetect time.Time // Vehicle connected timestamp
chargerSwitched time.Time // Charger enabled/disabled timestamp
phasesSwitched time.Time // Phase switch timestamp
vehicleDetectTicker *clock.Ticker
vehicleIdentifier string
@ -295,7 +291,6 @@ func NewLoadpoint(log *util.Logger, settings *Settings) *Loadpoint {
},
Enable: ThresholdConfig{Delay: time.Minute, Threshold: 0}, // t, W
Disable: ThresholdConfig{Delay: 3 * time.Minute, Threshold: 0}, // t, W
GuardDuration: 5 * time.Minute,
sessionEnergy: NewEnergyMetrics(),
progress: NewProgress(0, 10), // soc progress indicator
coordinator: coordinator.NewDummy(), // dummy vehicle coordinator
@ -600,7 +595,6 @@ func (lp *Loadpoint) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Even
lp.publish(keys.PhasesActive, lp.ActivePhases())
lp.publishTimer(phaseTimer, 0, timerInactive)
lp.publishTimer(pvTimer, 0, timerInactive)
lp.publishTimer(guardTimer, 0, timerInactive)
if phases := lp.getChargerPhysicalPhases(); phases != 0 {
lp.publish(keys.ChargerPhysicalPhases, phases)
@ -641,7 +635,6 @@ func (lp *Loadpoint) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Even
// read initial charger state to prevent immediately disabling charger
if enabled, err := lp.charger.Enabled(); err == nil {
if lp.enabled = enabled; enabled {
lp.guardUpdated = lp.clock.Now()
// set defined current for use by pv mode
_ = lp.setLimit(lp.effectiveMinCurrent(), false)
}
@ -662,7 +655,7 @@ func (lp *Loadpoint) syncCharger() error {
return err
}
if lp.guardGracePeriodElapsed() {
if lp.chargerUpdateCompleted() {
defer func() {
lp.enabled = enabled
lp.publish(keys.Enabled, lp.enabled)
@ -672,11 +665,10 @@ func (lp *Loadpoint) syncCharger() error {
if !enabled && lp.charging() {
lp.log.WARN.Println("charger logic error: disabled but charging")
enabled = true // treat as enabled when charging
if lp.guardGracePeriodElapsed() {
if lp.chargerUpdateCompleted() {
if err := lp.charger.Enable(true); err != nil { // also enable charger to correct internal state
return err
}
lp.elapseGuard()
lp.elapsePVTimer()
return nil
}
@ -693,10 +685,9 @@ func (lp *Loadpoint) syncCharger() error {
// smallest adjustment most PWM-Controllers can do is: 100%÷256×0,6A = 0.234A
if math.Abs(lp.chargeCurrent-current) > 0.23 {
if lp.guardGracePeriodElapsed() {
if lp.chargerUpdateCompleted() {
lp.log.WARN.Printf("charger logic error: current mismatch (got %.3gA, expected %.3gA)", current, lp.chargeCurrent)
}
lp.chargeCurrent = current
lp.bus.Publish(evChargeCurrent, lp.chargeCurrent)
}
@ -705,13 +696,9 @@ func (lp *Loadpoint) syncCharger() error {
return nil
}
if enabled || lp.phaseSwitchCommandTimeoutElapsed() {
// ignore disabled state if vehicle was disconnected ^(lp.enabled && ^lp.connected)
if lp.guardGracePeriodElapsed() && lp.phaseSwitchCompleted() && (enabled || lp.connected()) {
lp.log.WARN.Printf("charger out of sync: expected %vd, got %vd", status[lp.enabled], status[enabled])
lp.elapseGuard()
}
return nil
// ignore disabled state if vehicle was disconnected ^(lp.enabled && ^lp.connected)
if lp.chargerUpdateCompleted() && lp.phaseSwitchCompleted() && (enabled || lp.connected()) {
lp.log.WARN.Printf("charger out of sync: expected %vd, got %vd", status[lp.enabled], status[enabled])
}
return nil
@ -752,12 +739,6 @@ func (lp *Loadpoint) setLimit(chargeCurrent float64, force bool) error {
lp.bus.Publish(evChargeCurrent, chargeCurrent)
}
if lp.clock.Since(lp.guardUpdated).Truncate(time.Second) < lp.GuardDuration && !force {
lp.publishTimer(guardTimer, lp.GuardDuration, guardEnable)
return nil
}
lp.elapseGuard()
// set enabled/disabled
if enabled := chargeCurrent >= lp.effectiveMinCurrent(); enabled != lp.enabled {
if err := lp.charger.Enable(enabled); err != nil {
@ -777,7 +758,7 @@ func (lp *Loadpoint) setLimit(chargeCurrent float64, force bool) error {
lp.log.DEBUG.Printf("charger %s", status[enabled])
lp.enabled = enabled
lp.publish(keys.Enabled, lp.enabled)
lp.guardUpdated = lp.clock.Now()
lp.chargerSwitched = lp.clock.Now()
lp.bus.Publish(evChargeCurrent, chargeCurrent)
@ -968,8 +949,6 @@ func (lp *Loadpoint) elapsePVTimer() {
lp.pvTimer = elapsed
lp.publishTimer(pvTimer, 0, timerInactive)
lp.elapseGuard()
}
// resetPVTimer resets the pv enable/disable timer to disabled state
@ -1061,7 +1040,7 @@ func (lp *Loadpoint) pvScalePhases(sitePower, minCurrent, maxCurrent float64) bo
// - https://github.com/evcc-io/evcc/issues/2613
measuredPhases := lp.getMeasuredPhases()
if phases > 0 && phases < measuredPhases {
if lp.guardGracePeriodElapsed() {
if lp.chargerUpdateCompleted() {
lp.log.WARN.Printf("ignoring inconsistent phases: %dp < %dp observed active", phases, measuredPhases)
}
lp.resetMeasuredPhases()
@ -1140,9 +1119,6 @@ func (lp *Loadpoint) publishTimer(name string, delay time.Duration, action strin
if name == phaseTimer {
timer = lp.phaseTimer
}
if name == guardTimer {
timer = lp.guardUpdated
}
remaining := delay - lp.clock.Since(timer)
if remaining < 0 {
@ -1472,14 +1448,6 @@ func (lp *Loadpoint) publishSocAndRange() {
}
}
func (lp *Loadpoint) elapseGuard() {
if lp.guardUpdated != elapsed {
lp.log.DEBUG.Print("charger: guard elapse")
lp.guardUpdated = elapsed
lp.publishTimer(guardTimer, 0, timerInactive)
}
}
// addTask adds a single task to the queue
func (lp *Loadpoint) addTask(task func()) {
// test guard
@ -1515,17 +1483,12 @@ func (lp *Loadpoint) stopWakeUpTimer() {
lp.wakeUpTimer.Stop()
}
// guardGracePeriodElapsed checks if last guard update is within guard grace period
func (lp *Loadpoint) guardGracePeriodElapsed() bool {
return time.Since(lp.guardUpdated) > guardGracePeriod
// chargerUpdateCompleted returns true if enable command should be already processed by the charger (so we can try to sync charger and loadpoint)
func (lp *Loadpoint) chargerUpdateCompleted() bool {
return time.Since(lp.chargerSwitched) > chargerSwitchDuration
}
// phaseSwitchCommandTimeoutElapsed returns true if phase switch command should be already processed by the charger
func (lp *Loadpoint) phaseSwitchCommandTimeoutElapsed() bool {
return time.Since(lp.phasesSwitched) > phaseSwitchCommandTimeout
}
// phaseSwitchCompleted returns true if phase switch has completed
// phaseSwitchCompleted returns true if phase switch command should be already processed by the charger (so we can try to sync charger and loadpoint and are able to measure currents)
func (lp *Loadpoint) phaseSwitchCompleted() bool {
return time.Since(lp.phasesSwitched) > phaseSwitchDuration
}

View file

@ -696,131 +696,6 @@ func TestSocPoll(t *testing.T) {
}
}
// test guard timer is properly published during disable cycle
func TestGuardPublish(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
clock := clock.NewMock()
charger := api.NewMockCharger(ctrl)
rater := api.NewMockChargeRater(ctrl)
startPointInTime := clock.Now()
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
guardUpdated: startPointInTime,
pvTimer: time.Time{},
wakeUpTimer: NewTimer(),
sessionEnergy: NewEnergyMetrics(),
minCurrent: minA,
maxCurrent: maxA,
chargeCurrent: maxA,
status: api.StatusC,
mode: api.ModePV,
enabled: true,
Disable: ThresholdConfig{Delay: 2 * time.Minute, Threshold: 0}, // t, W
GuardDuration: 3 * time.Minute,
}
util.LogLevel("DEBUG", nil)
attachListeners(t, lp)
// attach cache for verifying values
_, expectCache := cacheExpecter(t, lp)
rater.EXPECT().ChargedEnergy().AnyTimes()
charger.EXPECT().Enabled().DoAndReturn(func() (bool, error) { return lp.enabled, nil }).AnyTimes() // just follow the loadpoint
charger.EXPECT().Status().Return(api.StatusC, nil).Times(4)
charger.EXPECT().Enable(false).Return(nil)
tc := []struct {
step string
guardUpdates time.Time
guardTimerRemaining time.Duration
}{
{"kick off pv disable timer", startPointInTime, 3 * time.Minute},
{"continue pv disable timer", startPointInTime, 2 * time.Minute},
{"disable charger prevented by guard", startPointInTime, 1 * time.Minute},
{"guard elapse, publish new guard time", startPointInTime.Add(3 * time.Minute), time.Duration(0)},
}
for _, tc := range tc {
t.Logf("%+v", tc)
lp.Update(15000, false, false, false, 0, nil, nil)
assert.Equal(t, tc.guardUpdates, lp.guardUpdated)
expectCache(guardTimer+"Remaining", tc.guardTimerRemaining)
clock.Add(time.Minute)
}
}
// test guard timer is properly published if charger disable sequence is aborted
func TestGuardPublishOnDisableAbort(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
clock := clock.NewMock()
charger := api.NewMockCharger(ctrl)
rater := api.NewMockChargeRater(ctrl)
startPointInTime := clock.Now()
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
guardUpdated: startPointInTime,
pvTimer: time.Time{},
wakeUpTimer: NewTimer(),
sessionEnergy: NewEnergyMetrics(),
minCurrent: minA,
maxCurrent: maxA,
chargeCurrent: maxA,
status: api.StatusC,
mode: api.ModePV,
enabled: true,
Disable: ThresholdConfig{Delay: 2 * time.Minute, Threshold: 0}, // t, W
GuardDuration: 4 * time.Minute,
}
util.LogLevel("DEBUG", nil)
attachListeners(t, lp)
// attach cache for verifying values
_, expectCache := cacheExpecter(t, lp)
rater.EXPECT().ChargedEnergy().AnyTimes()
charger.EXPECT().Enabled().DoAndReturn(func() (bool, error) { return lp.enabled, nil }).AnyTimes() // just follow the loadpoint
charger.EXPECT().Status().Return(api.StatusC, nil).Times(5)
tc := []struct {
step string
sitePower float64
guardUpdated time.Time
guardTimerRemaining time.Duration
}{
{"kick off pv disable timer", 15000, startPointInTime, 4 * time.Minute},
{"continue pv disable timer", 15000, startPointInTime, 3 * time.Minute},
{"disable charger prevented by guard", 15000, startPointInTime, 2 * time.Minute},
{"disable charger reset, pv power is back", -1, startPointInTime, 1 * time.Minute},
{"guard elapsed, publish new guard time", -1, elapsed, time.Duration(0)},
}
for _, tc := range tc {
t.Logf("%+v", tc)
lp.Update(tc.sitePower, false, false, false, 0, nil, nil)
assert.Equal(t, tc.guardUpdated, lp.guardUpdated)
expectCache(guardTimer+"Remaining", tc.guardTimerRemaining)
clock.Add(time.Minute)
}
}
// test PV hysteresis after phase switch down, depending on remaining energy
func TestPVHysteresisAfterPhaseSwitch(t *testing.T) {
const dt = time.Minute

View file

@ -138,7 +138,6 @@ loadpoints:
disable: # pv mode disable behavior
delay: 3m # threshold must be exceeded for this long
threshold: 0 # maximum import power (W)
guardDuration: 5m # switch charger contactor not more often than this (default 5m)
# tariffs are the fixed or variable tariffs
tariffs:

View file

@ -241,7 +241,6 @@ cleanEnergyCharging = "Energia neta disponible. Carregant..."
climating = "Precondicionament detectat."
connected = "Connectat."
disconnected = "Desconnectat."
guard = "Protecció de commutació activa durant {remaining}"
minCharge = "Càrrega mínima fins a {soc}%."
pvDisable = "Excedent insuficient. Pausa en {remaining}..."
pvEnable = "Excedent disponible. Començar en {remaining}..."

View file

@ -370,7 +370,6 @@ cleanEnergyCharging = "Oplader med CO2 neutral energi: {co2} (limit {limit})"
climating = "Forkonditionering registreret."
connected = "Forbundet."
disconnected = "Afbrudt."
guard = "Switching-beskyttelse er aktiv i {remaining}…"
minCharge = "Minimum opladning til {soc} %."
pvDisable = "Ikke nok overskud. Pause i {remaining}..."
pvEnable = "Overskud tilgængeligt. Starter om {remaining}..."

View file

@ -362,7 +362,6 @@ cleanEnergyCharging = "Lade saubere Energie: {co2} (Grenze {limit})"
climating = "Vorklimatisierung erkannt."
connected = "Verbunden."
disconnected = "Nicht verbunden."
guard = "Schaltschutz aktiv für {remaining}."
minCharge = "Mindestladung bis {soc}%."
pvDisable = "Zu wenig Überschuss. Pausiere in {remaining} …"
pvEnable = "Überschuss verfügbar. Starte in {remaining} …"

View file

@ -359,7 +359,6 @@ cleanEnergyCharging = "Charging clean energy: {co2} (limit {limit})"
climating = "Pre-conditioning detected."
connected = "Connected."
disconnected = "Disconnected."
guard = "Switching protection active for {remaining}."
minCharge = "Minimum charging to {soc}%."
pvDisable = "Not enough surplus. Pausing in {remaining}…"
pvEnable = "Surplus available. Starting in {remaining}…"

View file

@ -375,9 +375,6 @@ cleanEnergyCharging = "Cargando con energía limpia: {co2} (límite {limit})"
climating = "Preacondicionamiento detectado."
connected = "Conectado."
disconnected = "Desconectado."
guard = "Cambiando la protección activa para {remaining}"
guardDisable = "Pausando en {remaining}..."
guardEnable = "Comenzando en {remaining}..."
minCharge = "Carga mínima hasta {soc}%."
pvDisable = "Excedente insuficiente. Pausa en {remaining}..."
pvEnable = "Excedente disponibile. Empezar en {remaining}..."

View file

@ -293,7 +293,6 @@ cleanEnergyCharging = "Lataa puhdasta energiaa: {co2} (limit {limit})"
climating = "Esilämmitys/-viilennys havaittu."
connected = "Yhdistetty."
disconnected = "Irroitettu."
guard = "Suojaus aktiivinen: {remaining}…"
minCharge = "Ladataan minimissään {soc}%."
pvDisable = "Ei tarpeeksi ylijäämää. Tauko {remaining}…"
pvEnable = "Ylijäämää saatavilla. Aloitus {remaining}…"

View file

@ -321,7 +321,6 @@ cleanEnergyCharging = "En charge avec énergie propre : {co2} (limit < {limit}
climating = "Pré-conditionnement détecté."
connected = "Connecté."
disconnected = "Déconnecté."
guard = "Protection de commutation active pour {remaining}..."
minCharge = "Charge minimale jusqu'à {soc}%."
pvDisable = "Pas assez de surplus. Pause dans {remaining}…"
pvEnable = "Surplus disponible. Démarre dans {remaining}…"

View file

@ -323,7 +323,6 @@ cleanEnergyCharging = "Napajanje čistom energijom: {co2} (ograničenje {limit})
climating = "Otkriveno predkondicioniranje."
connected = "Povezano."
disconnected = "Nepovezano."
guard = "Promjena zaštite aktivna za {remaining}."
minCharge = "Minimalno napajanje do {soc} %."
pvDisable = "Nema dovoljno viška. Zaustavlja se za {remaining} …"
pvEnable = "Dostupan je višak. Počinje za {remaining} …"

View file

@ -375,9 +375,6 @@ cleanEnergyCharging = "Įkraunama švaria energija: {co2} (limitas {limit})"
climating = "Aptiktas išankstinis kondicionavimas."
connected = "Prijungtas."
disconnected = "Neprijungtas."
guard = "Per dažno junginėjimo apsauga {remaining}…"
guardDisable = "Pauzė už {remaining}…"
guardEnable = "Pradedam už {remaining}…"
minCharge = "Minimalus įkrovimas iki {soc}%."
pvDisable = "Trūksta saulės. Pauzė už {remaining}..."
pvEnable = "Saulės užtenka, įkrovimas už {remaining}..."

View file

@ -372,7 +372,6 @@ cleanEnergyCharging = "Laden actief met schone energie. {co2} (limit {limit})"
climating = "Voor-conditionering gedetecteerd."
connected = "Verbonden."
disconnected = "Niet verbonden."
guard = "Schakelbeveiliging actief voor {remaining}."
minCharge = "Minimaal opladen naar {soc}%."
pvDisable = "Onvoldoende PV overschot beschikbaar. Pauzeren over {remaining}..."
pvEnable = "Voldoende PV overschot beschikbaar. Starten over {remaining}..."

View file

@ -284,7 +284,6 @@ cleanEnergyCharging = "Ren energi tilgjengelig. Lader …"
climating = "Klimaanlegg på forhånd oppdaget."
connected = "Tilkoblet."
disconnected = "Frakoblet."
guard = "Bryterbeskyttelse aktiv i {remaining} …"
minCharge = "Minimumslading til {soc}%."
pvDisable = "Ikke nok overskudd. Pause i {remaining}..."
pvEnable = "Overskudd tilgjengelig. Starter om {remaining}..."

View file

@ -322,7 +322,6 @@ cleanEnergyCharging = "Ładowanie czystą energią: {co2} (ograniczenie {limit})
climating = "Wykryto wstępne przygotowanie"
connected = "Połączony."
disconnected = "Rozłączony."
guard = "Ochrona przełączania aktywna {remaining}…"
minCharge = "Minimalne ładowanie do {soc}%."
pvDisable = "Za mało nadwyżki. Pauza po {remaining}..."
pvEnable = "Dostępna nadwyżka. Począwszy po {remaining}..."

View file

@ -323,9 +323,6 @@ cleanEnergyCharging = "Carregando energia limpa: {co2} (limit {limit})"
climating = "Pré-condicionamento detectado."
connected = "Ligado."
disconnected = "Não conectado."
guard = "Proteção de ligação ativa para {remaining}."
guardDisable = "Pausar em {remaining}…"
guardEnable = "Iniciando em {remaining}..."
minCharge = "Carga mínima até {soc}%"
pvDisable = "Excesso insuficiente. Pausa em {remaining}..."
pvEnable = "Excesso disponível. Carga em {remaining}..."

View file

@ -370,7 +370,6 @@ cleanEnergyCharging = "Laddar grön energi: {co2} (gräns {limit})"
climating = "Förvärmning identifierad."
connected = "Inkopplad."
disconnected = "Frånkopplad."
guard = "Brytarskydd aktivt i {remaining}…"
minCharge = "Minimiladdning till {soc}%."
pvDisable = "Inte tillräckligt med överskott. Pausar i {remaining}..."
pvEnable = "Överskott tillgängligt. Börjar om {remaining}..."

View file

@ -281,9 +281,6 @@ cleanEnergyCharging = "Чиста енергія доступна. Зарядж
climating = "Виявлено попередню підготовку."
connected = "Підключено."
disconnected = "Відключено."
guard = "Захист від перемикання активовано для {remaining}."
guardDisable = "Пауза через {remaining}…"
guardEnable = "Початок через {remaining}…"
minCharge = "Мінімальна зарядка до {soc}%."
pvDisable = "Недостатньо надлишку. Пауза через {remaining}…"
pvEnable = "Надлишок доступний. Початок через {remaining}…"