Better vehicle status messages (delays, timer) (#6743)

This commit is contained in:
Michael Geers 2023-03-19 16:36:07 +01:00 • committed by GitHub
parent 61c701bb04
commit 1878897d1f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 221 additions and 12 deletions

View file

@ -197,12 +197,15 @@ export default {
phaseRemaining: Number,
pvRemaining: Number,
pvAction: String,
guardRemaining: Number,
guardAction: String,
},
data() {
return {
tickerHandler: null,
phaseRemainingInterpolated: this.phaseRemaining,
pvRemainingInterpolated: this.pvRemaining,
guardRemainingInterpolated: this.guardRemaining,
chargeDurationInterpolated: this.chargeDuration,
chargeRemainingDurationInterpolated: this.chargeRemainingDuration,
};
@ -237,6 +240,9 @@ export default {
pvRemaining() {
this.pvRemainingInterpolated = this.pvRemaining;
},
guardRemaining() {
this.guardRemainingInterpolated = this.guardRemaining;
},
chargeDuration() {
this.chargeDurationInterpolated = this.chargeDuration;
},
@ -258,6 +264,9 @@ export default {
if (this.pvRemainingInterpolated > 0) {
this.pvRemainingInterpolated--;
}
if (this.guardRemainingInterpolated > 0) {
this.guardRemainingInterpolated--;
}
if (this.chargeDurationInterpolated > 0 && this.charging) {
this.chargeDurationInterpolated++;
}

View file

@ -100,6 +100,9 @@ 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

@ -109,6 +109,8 @@ export default {
phaseRemainingInterpolated: Number,
pvAction: String,
pvRemainingInterpolated: Number,
guardAction: String,
guardRemainingInterpolated: Number,
vehicles: Array,
},
emits: [

View file

@ -0,0 +1,167 @@
import { mount, config } from "@vue/test-utils";
import { describe, expect, test } from "vitest";
import VehicleStatus from "./VehicleStatus.vue";
const serializeData = (data) => (data ? `:${JSON.stringify(data)}` : "");
config.global.mocks["$t"] = (key, data) => `${key}${serializeData(data)}`;
config.global.mocks["$i18n"] = { locale: "de-DE" };
const expectStatus = (props, messageKey, data) => {
const wrapper = mount(VehicleStatus, { props });
expect(wrapper.find("div").text()).eq(`main.vehicleStatus.${messageKey}${serializeData(data)}`);
};
describe("basics", () => {
test("no vehicle is connected", () => {
expectStatus({ connected: false }, "disconnected");
});
test("vehicle is connected", () => {
expectStatus({ connected: true }, "connected");
});
test("show waiting for vehicle if charger is enabled but not charging", () => {
expectStatus({ enabled: true, connected: true }, "waitForVehicle");
});
test("vehicle is charging", () => {
expectStatus({ connected: true, charging: true }, "charging");
});
});
describe("min charge", () => {
test("active when vehicle soc is below", () => {
expectStatus({ connected: true, minSoc: 20, vehicleSoc: 10 }, "minCharge", { soc: 20 });
});
test("not active when vehicle soc is above", () => {
expectStatus({ connected: true, minSoc: 20, vehicleSoc: 21 }, "connected");
});
test("not active when vehicle soc is equal", () => {
expectStatus({ connected: true, minSoc: 20, vehicleSoc: 20 }, "connected");
});
test("not active when limit is 0", () => {
expectStatus({ connected: true, minSoc: 0, vehicleSoc: 10 }, "connected");
});
});
describe("target charge", () => {
const targetTime = "2020-03-16T06:00:00Z";
const planProjectedStart = "2020-03-16T02:00:00Z";
test("active if target time is set and status is charging", () => {
expectStatus({ targetTime, charging: true, connected: true }, "targetChargeActive");
});
test("waiting for vehicle if a target time is set, the charger is enabled but not charging", () => {
expectStatus({ targetTime, enabled: true, connected: true }, "targetChargeWaitForVehicle");
});
test("show projected start if not enabled yet", () => {
expectStatus({ targetTime, planProjectedStart, connected: true }, "targetChargePlanned", {
time: "Mo 03:00",
});
});
});
describe("timer", () => {
test("show pv enable timer if not enabled yet and timer exists", () => {
expectStatus(
{
pvAction: "enable",
connected: true,
pvRemainingInterpolated: 90,
},
"pvEnable",
{ remaining: "1:30m" }
);
});
test("don't show pv enable timer if value is zero", () => {
expectStatus(
{
pvAction: "enable",
connected: true,
pvRemainingInterpolated: 0,
},
"connected"
);
});
test("show pv disable timer if charging and timer exists", () => {
expectStatus(
{
pvAction: "disable",
connected: true,
charging: true,
pvRemainingInterpolated: 90,
},
"pvDisable",
{ remaining: "1:30m" }
);
});
test("show phase enable timer if it exists", () => {
expectStatus(
{
phaseAction: "scale1p",
connected: true,
charging: true,
phaseRemainingInterpolated: 90,
},
"scale1p",
{ remaining: "1:30m" }
);
});
test("show phase disable timer if it exists", () => {
expectStatus(
{
phaseAction: "scale3p",
connected: true,
charging: true,
phaseRemainingInterpolated: 90,
},
"scale3p",
{ remaining: "1:30m" }
);
});
test("show guard timer if it exists", () => {
expectStatus(
{
guardAction: "enable",
connected: true,
guardRemainingInterpolated: 90,
},
"guard",
{ remaining: "1:30m" }
);
});
test("don't show guard timer if charging", () => {
expectStatus(
{
guardAction: "enable",
connected: true,
charging: true,
guardRemainingInterpolated: 90,
},
"charging"
);
});
});
describe("vehicle target soc", () => {
test("show target reached if charger enabled but soc has reached vehicle limit", () => {
expectStatus(
{
connected: true,
enabled: true,
vehicleTargetSoc: 70,
vehicleSoc: 70,
},
"vehicleTargetReached",
{ soc: 70 }
);
});
test("show reached message even if vehicle is slightly below its limit", () => {
expectStatus(
{
connected: true,
enabled: true,
vehicleTargetSoc: 70,
vehicleSoc: 69,
},
"vehicleTargetReached",
{ soc: 70 }
);
});
});

View file

@ -21,6 +21,8 @@ export default {
phaseRemainingInterpolated: Number,
pvAction: String,
pvRemainingInterpolated: Number,
guardAction: String,
guardRemainingInterpolated: Number,
targetChargeDisabled: Boolean,
},
computed: {
@ -35,6 +37,9 @@ export default {
this.pvRemainingInterpolated > 0 && ["enable", "disable"].includes(this.pvAction)
);
},
guardTimerActive() {
return this.guardRemainingInterpolated > 0 && this.guardAction === "enable";
},
message: function () {
const t = (key, data) => {
return this.$t(`main.vehicleStatus.${key}`, data);
@ -43,7 +48,6 @@ export default {
if (!this.connected) {
return t("disconnected");
}
// min charge active
if (this.minSoc > 0 && this.vehicleSoc < this.minSoc) {
return t("minCharge", { soc: this.minSoc });
@ -93,6 +97,12 @@ export default {
return t("charging");
}
if (this.guardTimerActive) {
return t("guard", {
remaining: this.fmtShortDuration(this.guardRemainingInterpolated, true),
});
}
return t("connected");
},
},

View file

@ -39,6 +39,9 @@ const (
pvEnable = "enable"
pvDisable = "disable"
guardTimer = "guard"
guardEnable = "enable"
phaseTimer = "phase"
phaseScale1p = "scale1p"
phaseScale3p = "scale3p"
@ -562,6 +565,7 @@ func (lp *Loadpoint) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Even
lp.publish(phasesActive, lp.activePhases())
lp.publishTimer(phaseTimer, 0, timerInactive)
lp.publishTimer(pvTimer, 0, timerInactive)
lp.publishTimer(guardTimer, 0, timerInactive)
// charger features
for _, f := range []api.Feature{api.IntegratedDevice} {
@ -652,9 +656,10 @@ func (lp *Loadpoint) setLimit(chargeCurrent float64, force bool) error {
// set enabled/disabled
if enabled := chargeCurrent >= lp.GetMinCurrent(); enabled != lp.enabled {
if remaining := (lp.GuardDuration - lp.clock.Since(lp.guardUpdated)).Truncate(time.Second); remaining > 0 && !force {
lp.log.DEBUG.Printf("charger %s: contactor delay %v", status[enabled], remaining)
lp.publishTimer(guardTimer, lp.GuardDuration, guardEnable)
return nil
}
lp.elapseGuard()
// remote stop
// TODO https://github.com/evcc-io/evcc/discussions/1929
@ -877,9 +882,9 @@ func (lp *Loadpoint) elapsePVTimer() {
lp.log.DEBUG.Printf("pv timer elapse")
lp.pvTimer = elapsed
lp.guardUpdated = elapsed
lp.publishTimer(pvTimer, 0, timerInactive)
lp.elapseGuard()
}
// resetPVTimer resets the pv enable/disable timer to disabled state
@ -1047,6 +1052,9 @@ 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 {
@ -1401,6 +1409,14 @@ 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

View file

@ -176,20 +176,21 @@ ready = "bereit"
vehicleTarget = "Fahrzeuglimit: {soc}%"
[main.vehicleStatus]
charging = "Ladevorgang aktiv..."
charging = "Ladevorgang aktiv…"
connected = "Verbunden."
disconnected = "Nicht verbunden."
guard = "Starte in {remaining}…"
minCharge = "Mindestladung bis {soc}%."
pvDisable = "Zu wenig Überschuss. Pausiere in {remaining}."
pvEnable = "Überschuss verfügbar. Starte in {remaining}."
scale1p = "Reduziere auf einphasig in {remaining}."
scale3p = "Erhöhe auf dreiphasig in {remaining}."
targetChargeActive = "Zielladen aktiv."
pvDisable = "Zu wenig Überschuss. Pausiere in {remaining}…"
pvEnable = "Überschuss verfügbar. Starte in {remaining}…"
scale1p = "Reduziere auf einphasig in {remaining}…"
scale3p = "Erhöhe auf dreiphasig in {remaining}…"
targetChargeActive = "Zielladen aktiv…"
targetChargePlanned = "Zielladen geplant. Ladung startet {time} Uhr."
targetChargeWaitForVehicle = "Zielladen bereit. Warte auf Fahrzeug..."
targetChargeWaitForVehicle = "Zielladen bereit. Warte auf Fahrzeug…"
unknown = ""
vehicleTargetReached = "Fahrzeuglimit {soc}% erreicht."
waitForVehicle = "Ladebereit. Warte auf Fahrzeug..."
waitForVehicle = "Ladebereit. Warte auf Fahrzeug…"
[notifications]
dismissAll = "Meldungen entfernen"

View file

@ -179,6 +179,7 @@ vehicleTarget = "Vehicle limit: {soc}%"
charging = "Charging…"
connected = "Connected."
disconnected = "Disconnected."
guard = "Starting in {remaining}…"
minCharge = "Minimum charging to {soc}%."
pvDisable = "Not enough surplus. Pausing in {remaining}…"
pvEnable = "Surplus available. Starting in {remaining}…"