Better vehicle status messages (delays, timer) (#6743)
This commit is contained in:
parent
61c701bb04
commit
1878897d1f
8 changed files with 221 additions and 12 deletions
|
|
@ -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++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -109,6 +109,8 @@ export default {
|
|||
phaseRemainingInterpolated: Number,
|
||||
pvAction: String,
|
||||
pvRemainingInterpolated: Number,
|
||||
guardAction: String,
|
||||
guardRemainingInterpolated: Number,
|
||||
vehicles: Array,
|
||||
},
|
||||
emits: [
|
||||
|
|
|
|||
167
assets/js/components/VehicleStatus.test.js
Normal file
167
assets/js/components/VehicleStatus.test.js
Normal 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 }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -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");
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
17
i18n/de.toml
17
i18n/de.toml
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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}…"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue