Smart grid charging per loadpoint (BC) (#12135)
This commit is contained in:
parent
974cc85b6c
commit
93a1faf755
48 changed files with 615 additions and 598 deletions
|
|
@ -460,3 +460,17 @@ input[type="time"]::-webkit-calendar-picker-indicator {
|
|||
width: calc(1.2rem + 0.75rem);
|
||||
border-radius: 3rem;
|
||||
}
|
||||
|
||||
.form-switch .form-check-input + .form-check-label {
|
||||
margin-top: 0.1rem;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.w-sm-100 {
|
||||
width: 100% !important;
|
||||
}
|
||||
.w-sm-50 {
|
||||
width: 50% !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<div
|
||||
id="batterySettingsModal"
|
||||
ref="modal"
|
||||
class="modal fade text-dark"
|
||||
class="modal fade text-dark modal-xl"
|
||||
data-bs-backdrop="true"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
<div
|
||||
class="d-flex justify-content-between align-items-center align-items-sm-start pb-2 flex-column flex-sm-row-reverse"
|
||||
>
|
||||
<div class="battery mb-5 mb-sm-3 me-5 me-sm-0">
|
||||
<div class="battery mb-5 mb-sm-3 me-5 me-sm-0 w-sm-50">
|
||||
<div class="batteryLimits">
|
||||
<label
|
||||
class="bufferSoc p-2 end-0"
|
||||
|
|
@ -130,7 +130,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="me-sm-4">
|
||||
<div class="legend me-sm-4 align-self-start w-sm-50">
|
||||
<p>
|
||||
{{ $t("batterySettings.batteryLevel") }}:
|
||||
<strong>{{ fmtSoc(batterySoc) }}</strong>
|
||||
|
|
@ -212,12 +212,8 @@
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<FormRow
|
||||
v-if="controllable"
|
||||
id="batteryDischargeControl"
|
||||
:label="`${$t('batterySettings.control')}`"
|
||||
>
|
||||
<div class="form-check form-switch col-form-label">
|
||||
<div v-if="!controllable">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
id="batteryDischargeControl"
|
||||
:checked="batteryDischargeControl"
|
||||
|
|
@ -232,7 +228,7 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</FormRow>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -245,14 +241,12 @@ import "@h2d2/shopicons/es/regular/lightning";
|
|||
import "@h2d2/shopicons/es/regular/car3";
|
||||
import "@h2d2/shopicons/es/regular/home";
|
||||
import formatter from "../mixins/formatter";
|
||||
import FormRow from "./FormRow.vue";
|
||||
|
||||
import collector from "../mixins/collector";
|
||||
import api from "../api";
|
||||
|
||||
export default {
|
||||
name: "BatterySettingsModal",
|
||||
components: { FormRow },
|
||||
mixins: [formatter],
|
||||
mixins: [formatter, collector],
|
||||
props: {
|
||||
bufferSoc: Number,
|
||||
prioritySoc: Number,
|
||||
|
|
@ -460,6 +454,11 @@ export default {
|
|||
height: 300px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.legend {
|
||||
min-width: 260px;
|
||||
}
|
||||
|
||||
.batteryLimits {
|
||||
width: 50px;
|
||||
position: relative;
|
||||
|
|
@ -521,11 +520,13 @@ export default {
|
|||
border-radius: 0.5rem 0 0 0.5rem;
|
||||
}
|
||||
.progress {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
width: 100px;
|
||||
min-width: 100px;
|
||||
max-width: 150px;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
border-radius: 1rem;
|
||||
background-color: var(--evcc-box) !important;
|
||||
}
|
||||
.progress-bar {
|
||||
|
|
|
|||
|
|
@ -89,10 +89,8 @@
|
|||
:powerInKw="powerInKw"
|
||||
:details="detailsValue(tariffGrid, tariffCo2)"
|
||||
:detailsFmt="detailsFmt"
|
||||
:detailsClickable="gridModalAvailable"
|
||||
:detailsTooltip="detailsTooltip(tariffGrid, tariffCo2)"
|
||||
data-testid="energyflow-entry-gridimport"
|
||||
@details-clicked="openGridSettingsModal"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -173,7 +171,6 @@ import AnimatedNumber from "../AnimatedNumber.vue";
|
|||
import settings from "../../settings";
|
||||
import { CO2_TYPE } from "../../units";
|
||||
import collector from "../../mixins/collector";
|
||||
import gridModalAvailable from "../../utils/gridModalAvailable";
|
||||
|
||||
export default {
|
||||
name: "Energyflow",
|
||||
|
|
@ -204,7 +201,6 @@ export default {
|
|||
tariffCo2Home: { type: Number },
|
||||
tariffPriceLoadpoints: { type: Number },
|
||||
tariffCo2Loadpoints: { type: Number },
|
||||
smartCostLimit: { type: Number },
|
||||
smartCostType: { type: String },
|
||||
currency: { type: String },
|
||||
prioritySoc: { type: Number },
|
||||
|
|
@ -212,12 +208,9 @@ export default {
|
|||
bufferStartSoc: { type: Number },
|
||||
},
|
||||
data: () => {
|
||||
return { detailsOpen: false, detailsCompleteHeight: null, gridSettingsModal: null };
|
||||
return { detailsOpen: false, detailsCompleteHeight: null };
|
||||
},
|
||||
computed: {
|
||||
gridModalAvailable: function () {
|
||||
return gridModalAvailable(this.smartCostType);
|
||||
},
|
||||
gridImport: function () {
|
||||
return Math.max(0, this.gridPower);
|
||||
},
|
||||
|
|
@ -334,10 +327,6 @@ export default {
|
|||
updateHeight: function () {
|
||||
this.detailsCompleteHeight = this.$refs.detailsInner.offsetHeight;
|
||||
},
|
||||
openGridSettingsModal() {
|
||||
const modal = Modal.getOrCreateInstance(document.getElementById("gridSettingsModal"));
|
||||
modal.show();
|
||||
},
|
||||
openBatterySettingsModal() {
|
||||
const modal = Modal.getOrCreateInstance(
|
||||
document.getElementById("batterySettingsModal")
|
||||
|
|
|
|||
|
|
@ -188,6 +188,7 @@ export default {
|
|||
tariffGrid: Number,
|
||||
tariffCo2: Number,
|
||||
currency: String,
|
||||
multipleLoadpoints: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
<Teleport to="body">
|
||||
<div
|
||||
:id="`loadpointSettingsModal_${id}`"
|
||||
class="modal fade text-dark"
|
||||
class="modal fade text-dark modal-xl"
|
||||
ref="modal"
|
||||
data-bs-backdrop="true"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
aria-hidden="true"
|
||||
data-testid="loadpoint-settings-modal"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
|
|
@ -23,7 +25,12 @@
|
|||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container">
|
||||
<h4 class="d-flex align-items-center mb-3 mt-4 text-evcc">
|
||||
<SmartCostLimit
|
||||
v-if="isModalVisible && smartCostAvailable"
|
||||
v-bind="smartCostLimitProps"
|
||||
class="mt-2"
|
||||
/>
|
||||
<h4 class="d-flex align-items-center mb-3 mt-5 text-evcc">
|
||||
{{ $t("main.loadpointSettings.currents") }}
|
||||
</h4>
|
||||
<div v-if="phasesOptions.length" class="mb-3 row">
|
||||
|
|
@ -142,7 +149,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import collector from "../mixins/collector";
|
||||
import formatter from "../mixins/formatter";
|
||||
import SmartCostLimit from "./SmartCostLimit.vue";
|
||||
import smartCostAvailable from "../utils/smartCostAvailable";
|
||||
|
||||
const V = 230;
|
||||
|
||||
|
|
@ -161,7 +171,8 @@ const insertSorted = (arr, num) => {
|
|||
|
||||
export default {
|
||||
name: "LoadpointSettingsModal",
|
||||
mixins: [formatter],
|
||||
mixins: [formatter, collector],
|
||||
components: { SmartCostLimit },
|
||||
props: {
|
||||
id: [String, Number],
|
||||
phasesConfigured: Number,
|
||||
|
|
@ -172,6 +183,11 @@ export default {
|
|||
maxCurrent: Number,
|
||||
minCurrent: Number,
|
||||
title: String,
|
||||
smartCostLimit: Number,
|
||||
smartCostType: String,
|
||||
tariffGrid: Number,
|
||||
currency: String,
|
||||
multipleLoadpoints: Boolean,
|
||||
},
|
||||
emits: ["phasesconfigured-updated", "maxcurrent-updated", "mincurrent-updated"],
|
||||
data: function () {
|
||||
|
|
@ -179,6 +195,7 @@ export default {
|
|||
selectedMaxCurrent: this.maxCurrent,
|
||||
selectedMinCurrent: this.minCurrent,
|
||||
selectedPhases: this.phasesConfigured,
|
||||
isModalVisible: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -228,6 +245,15 @@ export default {
|
|||
const opt2 = insertSorted(opt1, this.maxCurrent);
|
||||
return opt2.map((value) => this.currentOption(value, value === 16));
|
||||
},
|
||||
smartCostLimitProps: function () {
|
||||
return this.collectProps(SmartCostLimit);
|
||||
},
|
||||
loadpointId: function () {
|
||||
return this.id;
|
||||
},
|
||||
smartCostAvailable() {
|
||||
return smartCostAvailable(this.smartCostType);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
maxCurrent: function (value) {
|
||||
|
|
@ -243,6 +269,14 @@ export default {
|
|||
this.selectedMinSoc = value;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.modal.addEventListener("show.bs.modal", this.modalVisible);
|
||||
this.$refs.modal.addEventListener("hidden.bs.modal", this.modalInvisible);
|
||||
},
|
||||
unmounted() {
|
||||
this.$refs.modal?.removeEventListener("show.bs.modal", this.modalVisible);
|
||||
this.$refs.modal?.removeEventListener("hidden.bs.modal", this.modalInvisible);
|
||||
},
|
||||
methods: {
|
||||
maxPowerPhases: function (phases) {
|
||||
return this.fmtKw(this.maxCurrent * V * phases);
|
||||
|
|
@ -269,6 +303,12 @@ export default {
|
|||
}
|
||||
return { value, name };
|
||||
},
|
||||
modalVisible: function () {
|
||||
this.isModalVisible = true;
|
||||
},
|
||||
modalInvisible: function () {
|
||||
this.isModalVisible = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -13,12 +13,11 @@
|
|||
:id="index + 1"
|
||||
data-testid="loadpoint"
|
||||
:vehicles="vehicles"
|
||||
:smartCostLimit="smartCostLimit"
|
||||
:smartCostType="smartCostType"
|
||||
:smartCostActive="smartCostActive"
|
||||
:tariffGrid="tariffGrid"
|
||||
:tariffCo2="tariffCo2"
|
||||
:currency="currency"
|
||||
:multiple-loadpoints="loadpoints.length > 1"
|
||||
class="h-100"
|
||||
:class="{ 'loadpoint-unselected': !selected(index) }"
|
||||
@click="scrollTo(index)"
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ export default {
|
|||
batteryPower: Number,
|
||||
batterySoc: Number,
|
||||
batteryDischargeControl: Boolean,
|
||||
batterySmartCostLimit: Number,
|
||||
batteryMode: String,
|
||||
battery: Array,
|
||||
gridCurrents: Array,
|
||||
|
|
|
|||
|
|
@ -1,104 +1,77 @@
|
|||
<template>
|
||||
<Teleport to="body">
|
||||
<div
|
||||
id="gridSettingsModal"
|
||||
ref="modal"
|
||||
class="modal fade text-dark modal-xl"
|
||||
data-bs-backdrop="true"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
{{ $t("gridSettings.modalTitle") }}
|
||||
</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="smartCostLimit mb-4">
|
||||
<div class="d-flex justify-content-between align-items-baseline mb-2">
|
||||
<label for="smartCostLimit" class="pt-1">
|
||||
{{
|
||||
isCo2
|
||||
? $t("gridSettings.co2Limit")
|
||||
: $t("gridSettings.priceLimit")
|
||||
}}
|
||||
</label>
|
||||
<select
|
||||
id="smartCostLimit"
|
||||
v-model.number="selectedSmartCostLimit"
|
||||
class="form-select smartCostLimitSelect"
|
||||
@change="changeSmartCostLimit"
|
||||
>
|
||||
<option value="0">{{ $t("gridSettings.none") }}</option>
|
||||
<option
|
||||
v-for="{ value, name } in costOptions"
|
||||
:key="value"
|
||||
:value="value"
|
||||
>
|
||||
{{ name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<small v-if="selectedSmartCostLimit !== 0">
|
||||
{{ $t("gridSettings.costLimitDescription") }}
|
||||
</small>
|
||||
</div>
|
||||
<div class="justify-content-between mb-2 d-flex justify-content-between">
|
||||
<div class="text-start">
|
||||
<div class="label">
|
||||
{{ $t("gridSettings.activeHoursLabel") }}
|
||||
</div>
|
||||
<div
|
||||
class="value"
|
||||
:class="
|
||||
chargingSlots.length ? 'text-primary' : 'value-inactive'
|
||||
"
|
||||
>
|
||||
{{
|
||||
$t("gridSettings.activeHours", {
|
||||
total: totalSlots.length,
|
||||
charging: chargingSlots.length,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="label">
|
||||
<span v-if="activeSlot">{{ activeSlotName }}</span>
|
||||
<span v-else-if="isCo2">{{ $t("gridSettings.co2Label") }}</span>
|
||||
<span v-else>{{ $t("gridSettings.priceLabel") }}</span>
|
||||
</div>
|
||||
<div v-if="activeSlot" class="value text-primary">
|
||||
{{ activeSlotCost }}
|
||||
</div>
|
||||
<div v-else-if="chargingSlots.length" class="value text-primary">
|
||||
{{ chargingCostRange }}
|
||||
</div>
|
||||
<div v-else class="value value-inactive">
|
||||
{{ totalCostRange }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TariffChart
|
||||
v-if="tariff"
|
||||
:slots="slots"
|
||||
@slot-hovered="slotHovered"
|
||||
@slot-selected="slotSelected"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-evcc mb-3">{{ title }}</h4>
|
||||
<p>
|
||||
{{ description }}
|
||||
</p>
|
||||
<div class="row mb-3">
|
||||
<label :for="formId" class="col-sm-4 col-form-label pt-0 pt-sm-2">
|
||||
{{ isCo2 ? $t("smartCost.co2Limit") : $t("smartCost.priceLimit") }}
|
||||
</label>
|
||||
<div class="col-sm-8 col-lg-4 pe-0">
|
||||
<select
|
||||
:id="formId"
|
||||
v-model.number="selectedSmartCostLimit"
|
||||
class="form-select form-select-sm mb-1"
|
||||
@change="changeSmartCostLimit"
|
||||
>
|
||||
<option value="0">{{ $t("smartCost.none") }}</option>
|
||||
<option v-for="{ value, name } in costOptions" :key="value" :value="value">
|
||||
{{ name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
v-if="applyToAllVisible"
|
||||
class="col-sm-8 offset-sm-4 offset-lg-0 col-lg-4 d-flex align-items-baseline"
|
||||
>
|
||||
<div class="text-primary">{{ $t("smartCost.saved") }}</div>
|
||||
<button class="ms-1 btn btn-sm btn-link text-muted p-0" @click="applyToAll">
|
||||
{{ $t("smartCost.applyToAll") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="justify-content-between mb-2 d-flex justify-content-between">
|
||||
<div class="text-start">
|
||||
<div class="label">
|
||||
{{ $t("smartCost.activeHoursLabel") }}
|
||||
</div>
|
||||
<div
|
||||
class="value"
|
||||
:class="chargingSlots.length ? 'text-primary' : 'value-inactive'"
|
||||
>
|
||||
{{
|
||||
$t("smartCost.activeHours", {
|
||||
total: totalSlots.length,
|
||||
charging: chargingSlots.length,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="label">
|
||||
<span v-if="activeSlot">{{ activeSlotName }}</span>
|
||||
<span v-else-if="isCo2">{{ $t("smartCost.co2Label") }}</span>
|
||||
<span v-else>{{ $t("smartCost.priceLabel") }}</span>
|
||||
</div>
|
||||
<div v-if="activeSlot" class="value text-primary">
|
||||
{{ activeSlotCost }}
|
||||
</div>
|
||||
<div v-else-if="chargingSlots.length" class="value text-primary">
|
||||
{{ chargingCostRange }}
|
||||
</div>
|
||||
<div v-else class="value value-inactive">
|
||||
{{ totalCostRange }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
<TariffChart
|
||||
v-if="tariff"
|
||||
:slots="slots"
|
||||
@slot-hovered="slotHovered"
|
||||
@slot-selected="slotSelected"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -108,22 +81,24 @@ import { CO2_TYPE } from "../units";
|
|||
import api from "../api";
|
||||
|
||||
export default {
|
||||
name: "GridSettingsModal",
|
||||
name: "SmartCostLimit",
|
||||
components: { TariffChart },
|
||||
mixins: [formatter],
|
||||
props: {
|
||||
smartCostLimit: Number,
|
||||
smartCostLimit: { type: Number, default: 0 },
|
||||
smartCostType: String,
|
||||
tariffGrid: Number,
|
||||
currency: String,
|
||||
loadpointId: Number,
|
||||
multipleLoadpoints: Boolean,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
isModalVisible: false,
|
||||
selectedSmartCostLimit: 0,
|
||||
tariff: null,
|
||||
startTime: null,
|
||||
activeIndex: null,
|
||||
applyToAllVisible: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -235,30 +210,27 @@ export default {
|
|||
}
|
||||
return this.fmtPricePerKWh(price, this.currency);
|
||||
},
|
||||
title() {
|
||||
return this.$t(`smartCost.${this.isCo2 ? "clean" : "cheap"}Title`);
|
||||
},
|
||||
description() {
|
||||
return this.$t(`smartCost.${this.loadpointId ? "loadpoint" : "battery"}Description`);
|
||||
},
|
||||
formId() {
|
||||
return `smartCostLimit-${this.loadpointId || "battery"}`;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
isModalVisible(visible) {
|
||||
if (visible) {
|
||||
this.updateTariff();
|
||||
}
|
||||
},
|
||||
tariffGrid() {
|
||||
if (this.isModalVisible) {
|
||||
this.updateTariff();
|
||||
}
|
||||
this.updateTariff();
|
||||
},
|
||||
smartCostLimit(limit) {
|
||||
this.selectedSmartCostLimit = limit;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.updateTariff();
|
||||
this.selectedSmartCostLimit = this.smartCostLimit;
|
||||
this.$refs.modal.addEventListener("show.bs.modal", this.modalVisible);
|
||||
this.$refs.modal.addEventListener("hidden.bs.modal", this.modalInvisible);
|
||||
},
|
||||
unmounted() {
|
||||
this.$refs.modal?.removeEventListener("show.bs.modal", this.modalVisible);
|
||||
this.$refs.modal?.removeEventListener("hidden.bs.modal", this.modalInvisible);
|
||||
},
|
||||
methods: {
|
||||
updateTariff: async function () {
|
||||
|
|
@ -269,12 +241,6 @@ export default {
|
|||
console.error(e);
|
||||
}
|
||||
},
|
||||
modalVisible: function () {
|
||||
this.isModalVisible = true;
|
||||
},
|
||||
modalInvisible: function () {
|
||||
this.isModalVisible = false;
|
||||
},
|
||||
convertDates(list) {
|
||||
if (!list?.length) {
|
||||
return [];
|
||||
|
|
@ -337,8 +303,23 @@ export default {
|
|||
this.saveSmartCostLimit($event.target.value);
|
||||
},
|
||||
async saveSmartCostLimit(limit) {
|
||||
const isLoadpoint = !!this.loadpointId;
|
||||
const url = isLoadpoint
|
||||
? `loadpoints/${this.loadpointId}/smartcostlimit`
|
||||
: "batterysmartcostlimit"; // currently not implemented
|
||||
try {
|
||||
await api.post(`smartcostlimit/${encodeURIComponent(limit)}`);
|
||||
await api.post(`${url}/${encodeURIComponent(limit)}`);
|
||||
if (isLoadpoint && this.multipleLoadpoints) {
|
||||
this.applyToAllVisible = true;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
},
|
||||
async applyToAll() {
|
||||
try {
|
||||
await api.post(`smartcostlimit/${encodeURIComponent(this.selectedSmartCostLimit)}`);
|
||||
this.applyToAllVisible = false;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
|
@ -348,12 +329,6 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.smartCostLimit {
|
||||
max-width: 400px;
|
||||
}
|
||||
.smartCostLimitSelect {
|
||||
width: auto;
|
||||
}
|
||||
.value {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
|
|
@ -113,7 +113,6 @@ import "@h2d2/shopicons/es/regular/moonstars";
|
|||
import "@h2d2/shopicons/es/regular/menu";
|
||||
import "@h2d2/shopicons/es/regular/newtab";
|
||||
import collector from "../mixins/collector";
|
||||
import gridModalAvailable from "../utils/gridModalAvailable";
|
||||
|
||||
import baseAPI from "../baseapi";
|
||||
|
||||
|
|
@ -130,7 +129,6 @@ export default {
|
|||
sponsor: String,
|
||||
sponsorTokenExpires: Number,
|
||||
batteryConfigured: Boolean,
|
||||
smartCostType: String,
|
||||
},
|
||||
computed: {
|
||||
logoutCount() {
|
||||
|
|
@ -153,9 +151,6 @@ export default {
|
|||
batteryModalAvailable() {
|
||||
return this.batteryConfigured;
|
||||
},
|
||||
gridModalAvailable: function () {
|
||||
return gridModalAvailable(this.smartCostType);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const $el = document.getElementById("topNavigatonDropdown");
|
||||
|
|
@ -191,10 +186,6 @@ export default {
|
|||
);
|
||||
modal.show();
|
||||
},
|
||||
openGridSettingsModal() {
|
||||
const modal = Modal.getOrCreateInstance(document.getElementById("gridSettingsModal"));
|
||||
modal.show();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
<GlobalSettingsModal v-bind="globalSettingsProps" />
|
||||
<BatterySettingsModal v-if="batteryModalAvailabe" v-bind="batterySettingsProps" />
|
||||
<GridSettingsModal v-if="gridModalAvailable" v-bind="gridSettingsProps" />
|
||||
<HelpModal />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -13,14 +12,12 @@
|
|||
import store from "../store";
|
||||
import GlobalSettingsModal from "../components/GlobalSettingsModal.vue";
|
||||
import BatterySettingsModal from "../components/BatterySettingsModal.vue";
|
||||
import GridSettingsModal from "../components/GridSettingsModal.vue";
|
||||
import HelpModal from "../components/HelpModal.vue";
|
||||
import collector from "../mixins/collector";
|
||||
import gridModalAvailable from "../utils/gridModalAvailable";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: { GlobalSettingsModal, HelpModal, BatterySettingsModal, GridSettingsModal },
|
||||
components: { GlobalSettingsModal, HelpModal, BatterySettingsModal },
|
||||
mixins: [collector],
|
||||
props: {
|
||||
notifications: Array,
|
||||
|
|
@ -34,9 +31,6 @@ export default {
|
|||
return { title: siteTitle ? `${siteTitle} | evcc` : "evcc" };
|
||||
},
|
||||
computed: {
|
||||
gridModalAvailable: function () {
|
||||
return gridModalAvailable(store.state.smartCostType);
|
||||
},
|
||||
batteryModalAvailabe: function () {
|
||||
return store.state.batteryConfigured;
|
||||
},
|
||||
|
|
@ -46,9 +40,6 @@ export default {
|
|||
batterySettingsProps() {
|
||||
return this.collectProps(BatterySettingsModal, store.state);
|
||||
},
|
||||
gridSettingsProps() {
|
||||
return this.collectProps(GridSettingsModal, store.state);
|
||||
},
|
||||
},
|
||||
mounted: function () {
|
||||
this.connect();
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ const (
|
|||
Connected = "connected" // connected
|
||||
Charging = "charging" // charging
|
||||
|
||||
// smart charging
|
||||
SmartCostActive = "smartCostActive" // smart cost active
|
||||
SmartCostLimit = "smartCostLimit" // smart cost limit
|
||||
|
||||
// effective values
|
||||
EffectivePriority = "effectivePriority" // effective priority
|
||||
EffectivePlanTime = "effectivePlanTime" // effective plan time
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ const (
|
|||
PvPower = "pvPower"
|
||||
ResidualPower = "residualPower"
|
||||
SiteTitle = "siteTitle"
|
||||
SmartCostActive = "smartCostActive"
|
||||
SmartCostLimit = "smartCostLimit"
|
||||
SmartCostType = "smartCostType"
|
||||
Statistics = "statistics"
|
||||
TariffCo2 = "tariffCo2"
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ type Loadpoint struct {
|
|||
configuredPhases int // Charger configured phase mode 0/1/3
|
||||
limitSoc int // Session limit for soc
|
||||
limitEnergy float64 // Session limit for energy
|
||||
smartCostLimit float64 // always charge if cost is below this value
|
||||
|
||||
mode api.ChargeMode
|
||||
enabled bool // Charger enabled state
|
||||
|
|
@ -324,7 +325,9 @@ func (lp *Loadpoint) restoreSettings() {
|
|||
if v, err := lp.settings.Float(keys.LimitEnergy); err == nil && v > 0 {
|
||||
lp.setLimitEnergy(v)
|
||||
}
|
||||
|
||||
if v, err := lp.settings.Float(keys.SmartCostLimit); err == nil {
|
||||
lp.SetSmartCostLimit(v)
|
||||
}
|
||||
t, err1 := lp.settings.Time(keys.PlanTime)
|
||||
v, err2 := lp.settings.Float(keys.PlanEnergy)
|
||||
if err1 == nil && err2 == nil {
|
||||
|
|
@ -1495,6 +1498,7 @@ func (lp *Loadpoint) phaseSwitchCompleted() bool {
|
|||
|
||||
// Update is the main control function. It reevaluates meters and charger state
|
||||
func (lp *Loadpoint) Update(sitePower float64, autoCharge, batteryBuffered, batteryStart bool, greenShare float64, effPrice, effCo2 *float64) {
|
||||
lp.publish(keys.SmartCostActive, autoCharge)
|
||||
lp.processTasks()
|
||||
|
||||
// read and publish meters first- charge power has already been updated by the site
|
||||
|
|
|
|||
|
|
@ -105,6 +105,15 @@ type API interface {
|
|||
// RemoteControl sets remote status demand
|
||||
RemoteControl(string, RemoteDemand)
|
||||
|
||||
//
|
||||
// smart grid charging
|
||||
//
|
||||
|
||||
// GetSmartChargingActive determines if smart charging is active
|
||||
GetSmartCostLimit() float64
|
||||
// SetSmartCostLimit sets the smart cost limit
|
||||
SetSmartCostLimit(limit float64)
|
||||
|
||||
//
|
||||
// power and energy
|
||||
//
|
||||
|
|
|
|||
|
|
@ -351,6 +351,20 @@ func (mr *MockAPIMockRecorder) GetRemainingEnergy() *gomock.Call {
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRemainingEnergy", reflect.TypeOf((*MockAPI)(nil).GetRemainingEnergy))
|
||||
}
|
||||
|
||||
// GetSmartCostLimit mocks base method.
|
||||
func (m *MockAPI) GetSmartCostLimit() float64 {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetSmartCostLimit")
|
||||
ret0, _ := ret[0].(float64)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetSmartCostLimit indicates an expected call of GetSmartCostLimit.
|
||||
func (mr *MockAPIMockRecorder) GetSmartCostLimit() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSmartCostLimit", reflect.TypeOf((*MockAPI)(nil).GetSmartCostLimit))
|
||||
}
|
||||
|
||||
// GetStatus mocks base method.
|
||||
func (m *MockAPI) GetStatus() api.ChargeStatus {
|
||||
m.ctrl.T.Helper()
|
||||
|
|
@ -559,6 +573,18 @@ func (mr *MockAPIMockRecorder) SetPriority(arg0 any) *gomock.Call {
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPriority", reflect.TypeOf((*MockAPI)(nil).SetPriority), arg0)
|
||||
}
|
||||
|
||||
// SetSmartCostLimit mocks base method.
|
||||
func (m *MockAPI) SetSmartCostLimit(arg0 float64) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetSmartCostLimit", arg0)
|
||||
}
|
||||
|
||||
// SetSmartCostLimit indicates an expected call of SetSmartCostLimit.
|
||||
func (mr *MockAPIMockRecorder) SetSmartCostLimit(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSmartCostLimit", reflect.TypeOf((*MockAPI)(nil).SetSmartCostLimit), arg0)
|
||||
}
|
||||
|
||||
// SetVehicle mocks base method.
|
||||
func (m *MockAPI) SetVehicle(arg0 api.Vehicle) {
|
||||
m.ctrl.T.Helper()
|
||||
|
|
|
|||
|
|
@ -477,3 +477,24 @@ func (lp *Loadpoint) StartVehicleDetection() {
|
|||
// start auto-detect
|
||||
lp.startVehicleDetection()
|
||||
}
|
||||
|
||||
// GetSmartCostLimit gets the smart cost limit
|
||||
func (lp *Loadpoint) GetSmartCostLimit() float64 {
|
||||
lp.RLock()
|
||||
defer lp.RUnlock()
|
||||
return lp.smartCostLimit
|
||||
}
|
||||
|
||||
// SetSmartCostLimit sets the smart cost limit
|
||||
func (lp *Loadpoint) SetSmartCostLimit(val float64) {
|
||||
lp.Lock()
|
||||
defer lp.Unlock()
|
||||
|
||||
lp.log.DEBUG.Println("set smart cost limit:", val)
|
||||
|
||||
if lp.smartCostLimit != val {
|
||||
lp.smartCostLimit = val
|
||||
lp.settings.SetFloat(keys.SmartCostLimit, lp.smartCostLimit)
|
||||
lp.publish(keys.SmartCostLimit, lp.smartCostLimit)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
23
core/site.go
23
core/site.go
|
|
@ -33,8 +33,8 @@ import (
|
|||
|
||||
const standbyPower = 10 // consider less than 10W as charger in standby
|
||||
|
||||
// Updater abstracts the Loadpoint implementation for testing
|
||||
type Updater interface {
|
||||
// updater abstracts the Loadpoint implementation for testing
|
||||
type updater interface {
|
||||
loadpoint.API
|
||||
Update(availablePower float64, autoCharge, batteryBuffered, batteryStart bool, greenShare float64, effectivePrice, effectiveCo2 *float64)
|
||||
}
|
||||
|
|
@ -77,9 +77,6 @@ type Site struct {
|
|||
batteryMeters []api.Meter // Battery charging meters
|
||||
auxMeters []api.Meter // Auxiliary meters
|
||||
|
||||
// cost settings
|
||||
smartCostLimit float64 // always charge if cost is below this value
|
||||
|
||||
// battery settings
|
||||
prioritySoc float64 // prefer battery up to this Soc
|
||||
bufferSoc float64 // continue charging on battery above this Soc
|
||||
|
|
@ -270,11 +267,6 @@ func (site *Site) restoreSettings() error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
if v, err := settings.Float(keys.SmartCostLimit); err == nil {
|
||||
if err := site.SetSmartCostLimit(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if v, err := settings.Float(keys.PrioritySoc); err == nil {
|
||||
if err := site.SetPrioritySoc(v); err != nil {
|
||||
return err
|
||||
|
|
@ -771,7 +763,7 @@ func (site *Site) publishTariffs(greenShareHome float64, greenShareLoadpoints fl
|
|||
}
|
||||
}
|
||||
|
||||
func (site *Site) update(lp Updater) {
|
||||
func (site *Site) update(lp updater) {
|
||||
site.log.DEBUG.Println("----")
|
||||
|
||||
// update all loadpoint's charge power
|
||||
|
|
@ -799,9 +791,8 @@ func (site *Site) update(lp Updater) {
|
|||
}
|
||||
|
||||
if err == nil {
|
||||
limit := site.GetSmartCostLimit()
|
||||
limit := lp.GetSmartCostLimit()
|
||||
smartCostActive = limit != 0 && rate.Price <= limit
|
||||
site.publish(keys.SmartCostActive, smartCostActive)
|
||||
} else {
|
||||
site.log.ERROR.Println("smartCost:", err)
|
||||
}
|
||||
|
|
@ -862,8 +853,6 @@ func (site *Site) prepare() {
|
|||
site.publish(keys.ResidualPower, site.ResidualPower)
|
||||
|
||||
site.publish(keys.Currency, site.tariffs.Currency)
|
||||
site.publish(keys.SmartCostActive, false)
|
||||
site.publish(keys.SmartCostLimit, site.smartCostLimit)
|
||||
if tariff := site.GetTariff(PlannerTariff); tariff != nil {
|
||||
site.publish(keys.SmartCostType, tariff.Type())
|
||||
} else {
|
||||
|
|
@ -919,7 +908,7 @@ func (site *Site) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Event)
|
|||
}
|
||||
|
||||
// loopLoadpoints keeps iterating across loadpoints sending the next to the given channel
|
||||
func (site *Site) loopLoadpoints(next chan<- Updater) {
|
||||
func (site *Site) loopLoadpoints(next chan<- updater) {
|
||||
for {
|
||||
for _, lp := range site.loadpoints {
|
||||
next <- lp
|
||||
|
|
@ -936,7 +925,7 @@ func (site *Site) Run(stopC chan struct{}, interval time.Duration) {
|
|||
site.log.WARN.Printf("interval <%.0fs can lead to unexpected behavior, see https://docs.evcc.io/docs/reference/configuration/interval", max.Seconds())
|
||||
}
|
||||
|
||||
loadpointChan := make(chan Updater)
|
||||
loadpointChan := make(chan updater)
|
||||
go site.loopLoadpoints(loadpointChan)
|
||||
|
||||
ticker := time.NewTicker(interval)
|
||||
|
|
|
|||
|
|
@ -47,8 +47,6 @@ type API interface {
|
|||
|
||||
// GetTariff returns the respective tariff
|
||||
GetTariff(string) api.Tariff
|
||||
GetSmartCostLimit() float64
|
||||
SetSmartCostLimit(float64) error
|
||||
|
||||
//
|
||||
// battery control
|
||||
|
|
|
|||
|
|
@ -255,29 +255,6 @@ func (site *Site) SetResidualPower(power float64) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetSmartCostLimit returns the smartCostLimit
|
||||
func (site *Site) GetSmartCostLimit() float64 {
|
||||
site.RLock()
|
||||
defer site.RUnlock()
|
||||
return site.smartCostLimit
|
||||
}
|
||||
|
||||
// SetSmartCostLimit sets the smartCostLimit
|
||||
func (site *Site) SetSmartCostLimit(val float64) error {
|
||||
site.Lock()
|
||||
defer site.Unlock()
|
||||
|
||||
site.log.DEBUG.Println("set smart cost limit:", val)
|
||||
|
||||
if site.smartCostLimit != val {
|
||||
site.smartCostLimit = val
|
||||
settings.SetFloat(keys.SmartCostLimit, site.smartCostLimit)
|
||||
site.publish(keys.SmartCostLimit, site.smartCostLimit)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTariff returns the respective tariff if configured or nil
|
||||
func (site *Site) GetTariff(tariff string) api.Tariff {
|
||||
site.RLock()
|
||||
|
|
|
|||
|
|
@ -108,5 +108,5 @@ modalUpdateNow = "Инсталирай сега"
|
|||
modalUpdateStarted = "Стартирай новата версия на evcc..."
|
||||
modalUpdateStatusStart = "Инсталацията започна:"
|
||||
|
||||
[gridSettings]
|
||||
[smartCost]
|
||||
activeHours = "{charging} от {total}"
|
||||
|
|
|
|||
22
i18n/ca.toml
22
i18n/ca.toml
|
|
@ -75,17 +75,6 @@ modalUpdateNow = "Instal·lar ara"
|
|||
modalUpdateStarted = "Després de l'actualització, evcc es reiniciarà."
|
||||
modalUpdateStatusStart = "Instal·lació iniciada:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} de {total}"
|
||||
activeHoursLabel = "Hores funcionant"
|
||||
co2Label = "Emissions de CO₂"
|
||||
co2Limit = "Límit de CO₂"
|
||||
costLimitDescription = "Habilita temporalment la càrrega ràpida en mode solar"
|
||||
modalTitle = "Xarxa intel·ligent de càrrega"
|
||||
none = "cap"
|
||||
priceLabel = "Preu de l'energia"
|
||||
priceLimit = "Límit de preu"
|
||||
|
||||
[header]
|
||||
about = "Sobre"
|
||||
blog = "Blog"
|
||||
|
|
@ -327,6 +316,17 @@ km = "km"
|
|||
label = "Unitats"
|
||||
mi = "milles"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} de {total}"
|
||||
activeHoursLabel = "Hores funcionant"
|
||||
co2Label = "Emissions de CO₂"
|
||||
co2Limit = "Límit de CO₂"
|
||||
loadpointDescription = "Habilita temporalment la càrrega ràpida en mode solar"
|
||||
modalTitle = "Xarxa intel·ligent de càrrega"
|
||||
none = "cap"
|
||||
priceLabel = "Preu de l'energia"
|
||||
priceLimit = "Límit de preu"
|
||||
|
||||
[startupError]
|
||||
configFile = "Arxiu de configuració utilitzat:"
|
||||
configuration = "Configuració"
|
||||
|
|
|
|||
22
i18n/cs.toml
22
i18n/cs.toml
|
|
@ -100,17 +100,6 @@ modalUpdateNow = "Instalovat nyní"
|
|||
modalUpdateStarted = "Spouštím novou verzi evcc..."
|
||||
modalUpdateStatusStart = "Instalace spuštěna:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} z {total}"
|
||||
activeHoursLabel = "Aktivně hodin"
|
||||
co2Label = "CO₂ emise"
|
||||
co2Limit = "CO₂ limit"
|
||||
costLimitDescription = "Umožňuje dočasné rychlé dobíjení v solárním režimu"
|
||||
modalTitle = "Smart Grid Nabíjení"
|
||||
none = "žádné"
|
||||
priceLabel = "Cena energie"
|
||||
priceLimit = "Cenový strop"
|
||||
|
||||
[header]
|
||||
about = "O"
|
||||
blog = "Blog"
|
||||
|
|
@ -188,3 +177,14 @@ label = "Min. Proud"
|
|||
|
||||
[main.loadpointSettings.minSoc]
|
||||
description = "Vozidlo se “rychle” nabije na {0} % v solárním režimu. Poté pokračuje solárním přebytkem. Užitečné pro zajištění minimálního dojezdu i pro temnější dny."
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} z {total}"
|
||||
activeHoursLabel = "Aktivně hodin"
|
||||
co2Label = "CO₂ emise"
|
||||
co2Limit = "CO₂ limit"
|
||||
loadpointDescription = "Umožňuje dočasné rychlé dobíjení v solárním režimu"
|
||||
modalTitle = "Smart Grid Nabíjení"
|
||||
none = "žádné"
|
||||
priceLabel = "Cena energie"
|
||||
priceLimit = "Cenový strop"
|
||||
|
|
|
|||
22
i18n/da.toml
22
i18n/da.toml
|
|
@ -166,17 +166,6 @@ modalUpdateNow = "Installer nu"
|
|||
modalUpdateStarted = "Starter den nye version af evcc..."
|
||||
modalUpdateStatusStart = "Installation startede:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} af {total}"
|
||||
activeHoursLabel = "Aktive timer"
|
||||
co2Label = "CO₂ udledning"
|
||||
co2Limit = "CO₂ grænse"
|
||||
costLimitDescription = "Aktiverer midlertidig hurtig ladning i solcelle tilstand"
|
||||
modalTitle = "Smart net opladning"
|
||||
none = "ingen"
|
||||
priceLabel = "Energi pris"
|
||||
priceLimit = "Pris grænse"
|
||||
|
||||
[header]
|
||||
about = "Om"
|
||||
blog = "Blog"
|
||||
|
|
@ -467,6 +456,17 @@ km = "km"
|
|||
label = "Enheder"
|
||||
mi = "mil"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} af {total}"
|
||||
activeHoursLabel = "Aktive timer"
|
||||
co2Label = "CO₂ udledning"
|
||||
co2Limit = "CO₂ grænse"
|
||||
loadpointDescription = "Aktiverer midlertidig hurtig ladning i solcelle tilstand"
|
||||
modalTitle = "Smart net opladning"
|
||||
none = "ingen"
|
||||
priceLabel = "Energi pris"
|
||||
priceLimit = "Pris grænse"
|
||||
|
||||
[startupError]
|
||||
configFile = "Konfigurationsfil brugt:"
|
||||
configuration = "Opsætning"
|
||||
|
|
|
|||
27
i18n/de.toml
27
i18n/de.toml
|
|
@ -164,17 +164,6 @@ modalUpdateNow = "Jetzt installieren"
|
|||
modalUpdateStarted = "Starte die neue Version von evcc ..."
|
||||
modalUpdateStatusStart = "Installation gestartet:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} von {total}"
|
||||
activeHoursLabel = "Aktive Stunden"
|
||||
co2Label = "CO₂-Emission"
|
||||
co2Limit = "CO₂-Grenze"
|
||||
costLimitDescription = "Aktiviert vorübergehendes Schnellladen im PV-Modus."
|
||||
modalTitle = "Smartes Netzladen"
|
||||
none = "keine"
|
||||
priceLabel = "Energiepreis"
|
||||
priceLimit = "Preisgrenze"
|
||||
|
||||
[header]
|
||||
about = "Über"
|
||||
blog = "Blog"
|
||||
|
|
@ -460,6 +449,22 @@ km = "Kilometer"
|
|||
label = "Einheit"
|
||||
mi = "Meilen"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} von {total}"
|
||||
activeHoursLabel = "Aktive Stunden"
|
||||
applyToAll = "Überall anwenden?"
|
||||
batteryDescription = "Lädt die Hausbatterie aus dem Netz."
|
||||
cheapTitle = "Günstiges Netzladen"
|
||||
cleanTitle = "Sauberes Netzladen"
|
||||
co2Label = "CO₂-Emission"
|
||||
co2Limit = "CO₂-Grenze"
|
||||
loadpointDescription = "Aktiviert vorübergehendes Schnellladen im PV-Modus."
|
||||
modalTitle = "Smartes Netzladen"
|
||||
none = "keine"
|
||||
priceLabel = "Energiepreis"
|
||||
priceLimit = "Preisgrenze"
|
||||
saved = "Gepeichert."
|
||||
|
||||
[startupError]
|
||||
configFile = "Verwendete Konfigurationsdatei:"
|
||||
configuration = "Konfiguration"
|
||||
|
|
|
|||
31
i18n/en.toml
31
i18n/en.toml
|
|
@ -161,17 +161,6 @@ modalUpdateNow = "Install now"
|
|||
modalUpdateStarted = "Starting the new version of evcc…"
|
||||
modalUpdateStatusStart = "Installation started:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} of {total}"
|
||||
activeHoursLabel = "Active hours"
|
||||
co2Label = "CO₂ emission"
|
||||
co2Limit = "CO₂ limit"
|
||||
costLimitDescription = "Enables temporary fast-charging in solar mode."
|
||||
modalTitle = "Smart Grid Charging"
|
||||
none = "none"
|
||||
priceLabel = "Energy price"
|
||||
priceLimit = "Price limit"
|
||||
|
||||
[header]
|
||||
about = "About"
|
||||
blog = "Blog"
|
||||
|
|
@ -251,10 +240,12 @@ remoteDisabledSoft = "{source}: turned off adaptive solar-charging"
|
|||
solar = "Solar"
|
||||
|
||||
[main.loadpointSettings]
|
||||
currents = "Charging current"
|
||||
currents = "Charging Current"
|
||||
default = "default"
|
||||
disclaimerHint = "Note:"
|
||||
onlyForSocBasedCharging = "These options are only available for vehicles with known charging level."
|
||||
smartCostCheap = "Cheap Grid Charging"
|
||||
smartCostClean = "Clean Grid Charging"
|
||||
title = "Settings {0}"
|
||||
vehicle = "Vehicle"
|
||||
|
||||
|
|
@ -457,6 +448,22 @@ km = "km"
|
|||
label = "Units"
|
||||
mi = "miles"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} of {total}"
|
||||
activeHoursLabel = "Active hours"
|
||||
applyToAll = "Apply everywhere?"
|
||||
batteryDescription = "Charges the home battery with energy from the grid."
|
||||
cheapTitle = "Cheap Grid Charging"
|
||||
cleanTitle = "Clean Grid Charging"
|
||||
co2Label = "CO₂ emission"
|
||||
co2Limit = "CO₂ limit"
|
||||
loadpointDescription = "Enables temporary fast-charging in solar mode."
|
||||
modalTitle = "Smart Grid Charging"
|
||||
none = "none"
|
||||
priceLabel = "Energy price"
|
||||
priceLimit = "Price limit"
|
||||
saved = "Saved."
|
||||
|
||||
[startupError]
|
||||
configFile = "Configuration file used:"
|
||||
configuration = "Config"
|
||||
|
|
|
|||
22
i18n/es.toml
22
i18n/es.toml
|
|
@ -166,17 +166,6 @@ modalUpdateNow = "Instalar ahora"
|
|||
modalUpdateStarted = "Después de la actualización, evcc se reiniciará."
|
||||
modalUpdateStatusStart = "Instalación iniciada:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} de {total}"
|
||||
activeHoursLabel = "Horas funcionando"
|
||||
co2Label = "Emisiones de CO₂"
|
||||
co2Limit = "Límite de CO₂"
|
||||
costLimitDescription = "Habilita temporalmente la carga rápida en modo solar"
|
||||
modalTitle = "Red inteligente de carga"
|
||||
none = "ninguno"
|
||||
priceLabel = "Precio de la energía"
|
||||
priceLimit = "Límite de precio"
|
||||
|
||||
[header]
|
||||
about = "Sobre"
|
||||
blog = "Blog"
|
||||
|
|
@ -478,6 +467,17 @@ km = "km"
|
|||
label = "Unidades"
|
||||
mi = "millas"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} de {total}"
|
||||
activeHoursLabel = "Horas funcionando"
|
||||
co2Label = "Emisiones de CO₂"
|
||||
co2Limit = "Límite de CO₂"
|
||||
loadpointDescription = "Habilita temporalmente la carga rápida en modo solar"
|
||||
modalTitle = "Red inteligente de carga"
|
||||
none = "ninguno"
|
||||
priceLabel = "Precio de la energía"
|
||||
priceLimit = "Límite de precio"
|
||||
|
||||
[startupError]
|
||||
configFile = "Archivo de configuración utilizado:"
|
||||
configuration = "Configuración"
|
||||
|
|
|
|||
22
i18n/fi.toml
22
i18n/fi.toml
|
|
@ -120,17 +120,6 @@ modalUpdateNow = "Asenna nyt"
|
|||
modalUpdateStarted = "Käynnistetään evcc:n uusin versio..."
|
||||
modalUpdateStatusStart = "Asennus aloitettu:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} / {total}"
|
||||
activeHoursLabel = "Aktiiviset tunnit"
|
||||
co2Label = "CO₂ päästö"
|
||||
co2Limit = "CO₂ raja"
|
||||
costLimitDescription = "Mahdollistaa väliaikaisesti latauksen nopeasti PV tilassa."
|
||||
modalTitle = "Älykäs lataus verkosta"
|
||||
none = "ei mitään"
|
||||
priceLabel = "Energian hinta"
|
||||
priceLimit = "Hintaraja"
|
||||
|
||||
[header]
|
||||
about = "Tietoa"
|
||||
blog = "Blogi"
|
||||
|
|
@ -396,6 +385,17 @@ km = "km"
|
|||
label = "Yksiköt"
|
||||
mi = "mailit"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} / {total}"
|
||||
activeHoursLabel = "Aktiiviset tunnit"
|
||||
co2Label = "CO₂ päästö"
|
||||
co2Limit = "CO₂ raja"
|
||||
loadpointDescription = "Mahdollistaa väliaikaisesti latauksen nopeasti PV tilassa."
|
||||
modalTitle = "Älykäs lataus verkosta"
|
||||
none = "ei mitään"
|
||||
priceLabel = "Energian hinta"
|
||||
priceLimit = "Hintaraja"
|
||||
|
||||
[startupError]
|
||||
configFile = "Käytetty määritystiedostoa:"
|
||||
configuration = "Määritys"
|
||||
|
|
|
|||
22
i18n/fr.toml
22
i18n/fr.toml
|
|
@ -122,17 +122,6 @@ modalUpdateNow = "Installer maintenant"
|
|||
modalUpdateStarted = "Démarrage de la nouvelle version de evcc..."
|
||||
modalUpdateStatusStart = "L'installation a commencé:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} sur {total}"
|
||||
activeHoursLabel = "Heures actives"
|
||||
co2Label = "Émissions de CO₂"
|
||||
co2Limit = "Limite CO₂"
|
||||
costLimitDescription = "Autorise la charge rapide temporaire en mode solaire."
|
||||
modalTitle = "Recharge réseau intelligente"
|
||||
none = "aucune"
|
||||
priceLabel = "Prix de l'énergie"
|
||||
priceLimit = "Limite de prix"
|
||||
|
||||
[header]
|
||||
about = "À propos"
|
||||
blog = "Blog"
|
||||
|
|
@ -418,6 +407,17 @@ km = "km"
|
|||
label = "Unités"
|
||||
mi = "miles"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} sur {total}"
|
||||
activeHoursLabel = "Heures actives"
|
||||
co2Label = "Émissions de CO₂"
|
||||
co2Limit = "Limite CO₂"
|
||||
loadpointDescription = "Autorise la charge rapide temporaire en mode solaire."
|
||||
modalTitle = "Recharge réseau intelligente"
|
||||
none = "aucune"
|
||||
priceLabel = "Prix de l'énergie"
|
||||
priceLimit = "Limite de prix"
|
||||
|
||||
[startupError]
|
||||
configFile = "Fichier de configuration utilisé:"
|
||||
configuration = "Configuration"
|
||||
|
|
|
|||
22
i18n/hr.toml
22
i18n/hr.toml
|
|
@ -123,17 +123,6 @@ modalUpdateNow = "Instaliraj sada"
|
|||
modalUpdateStarted = "Pokretanje nove evcc verzije …"
|
||||
modalUpdateStatusStart = "Instalacija je pokrenuta:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} od {total}"
|
||||
activeHoursLabel = "Aktivno radno vrijeme"
|
||||
co2Label = "Co₂ emisija"
|
||||
co2Limit = "Ograničenje CO₂"
|
||||
costLimitDescription = "Omogućuje privremeno brzo punjenje u solarnom modusu."
|
||||
modalTitle = "Smart Grid napajanje"
|
||||
none = "bez"
|
||||
priceLabel = "Cijena energije"
|
||||
priceLimit = "Ograničenje cijene"
|
||||
|
||||
[header]
|
||||
about = "O"
|
||||
blog = "Blog"
|
||||
|
|
@ -426,6 +415,17 @@ km = "km"
|
|||
label = "Jedinice"
|
||||
mi = "milje"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} od {total}"
|
||||
activeHoursLabel = "Aktivno radno vrijeme"
|
||||
co2Label = "Co₂ emisija"
|
||||
co2Limit = "Ograničenje CO₂"
|
||||
loadpointDescription = "Omogućuje privremeno brzo punjenje u solarnom modusu."
|
||||
modalTitle = "Smart Grid napajanje"
|
||||
none = "bez"
|
||||
priceLabel = "Cijena energije"
|
||||
priceLimit = "Ograničenje cijene"
|
||||
|
||||
[startupError]
|
||||
configFile = "Korištena konfiguracijska datoteka:"
|
||||
configuration = "Konfiguracija"
|
||||
|
|
|
|||
22
i18n/lt.toml
22
i18n/lt.toml
|
|
@ -166,17 +166,6 @@ modalUpdateNow = "Instaliuoti dabar"
|
|||
modalUpdateStarted = "Startuoja nauja evcc versija..."
|
||||
modalUpdateStatusStart = "Instaliavimas pradėtas:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} iš {total}"
|
||||
activeHoursLabel = "Aktyvumo valandos"
|
||||
co2Label = "CO₂ emisijos"
|
||||
co2Limit = "CO₂ limitas"
|
||||
costLimitDescription = "Laikinai aktyvuoja greitą įkrovimą (režime Saulė)."
|
||||
modalTitle = "Išmanaus tinklo įkrovimas"
|
||||
none = "nėra"
|
||||
priceLabel = "Energijos kaina"
|
||||
priceLimit = "Kainos limitas"
|
||||
|
||||
[header]
|
||||
about = "Apie"
|
||||
blog = "Tinklaraštis"
|
||||
|
|
@ -478,6 +467,17 @@ km = "km"
|
|||
label = "Vienetai"
|
||||
mi = "mylios"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} iš {total}"
|
||||
activeHoursLabel = "Aktyvumo valandos"
|
||||
co2Label = "CO₂ emisijos"
|
||||
co2Limit = "CO₂ limitas"
|
||||
loadpointDescription = "Laikinai aktyvuoja greitą įkrovimą (režime Saulė)."
|
||||
modalTitle = "Išmanaus tinklo įkrovimas"
|
||||
none = "nėra"
|
||||
priceLabel = "Energijos kaina"
|
||||
priceLimit = "Kainos limitas"
|
||||
|
||||
[startupError]
|
||||
configFile = "Naudojamas konfiguracijos failas:"
|
||||
configuration = "Konfiguracija"
|
||||
|
|
|
|||
22
i18n/nl.toml
22
i18n/nl.toml
|
|
@ -166,17 +166,6 @@ modalUpdateNow = "Nu installeren"
|
|||
modalUpdateStarted = "Starten van de nieuwe versie van evcc..."
|
||||
modalUpdateStatusStart = "Installatie gestart:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} van {total}"
|
||||
activeHoursLabel = "Actieve uren"
|
||||
co2Label = "CO₂ uitstoot"
|
||||
co2Limit = "CO₂ limiet"
|
||||
costLimitDescription = "Zet tijdelijk snel-laden in solar mode aan"
|
||||
modalTitle = "Smart Grid laden"
|
||||
none = "geen"
|
||||
priceLabel = "Energieprijs"
|
||||
priceLimit = "Prijslimiet"
|
||||
|
||||
[header]
|
||||
about = "Over"
|
||||
blog = "Blog"
|
||||
|
|
@ -475,6 +464,17 @@ km = "km"
|
|||
label = "Eenheden"
|
||||
mi = "mijlen"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} van {total}"
|
||||
activeHoursLabel = "Actieve uren"
|
||||
co2Label = "CO₂ uitstoot"
|
||||
co2Limit = "CO₂ limiet"
|
||||
loadpointDescription = "Zet tijdelijk snel-laden in solar mode aan"
|
||||
modalTitle = "Smart Grid laden"
|
||||
none = "geen"
|
||||
priceLabel = "Energieprijs"
|
||||
priceLimit = "Prijslimiet"
|
||||
|
||||
[startupError]
|
||||
configFile = "Gebruikt configuratiebestand:"
|
||||
configuration = "Configuratie"
|
||||
|
|
|
|||
22
i18n/no.toml
22
i18n/no.toml
|
|
@ -108,17 +108,6 @@ modalUpdateNow = "Installer nå"
|
|||
modalUpdateStarted = "Starter den nye versjonen av evcc..."
|
||||
modalUpdateStatusStart = "Installasjonen startet:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} av {total}"
|
||||
activeHoursLabel = "Aktive timer"
|
||||
co2Label = "CO₂-utslipp"
|
||||
co2Limit = "CO₂-grense"
|
||||
costLimitDescription = "Slår på midlertidig hurtiglading i solcellemodus."
|
||||
modalTitle = "Smart lysnettlading"
|
||||
none = "ingen"
|
||||
priceLabel = "Energipris"
|
||||
priceLimit = "Prisgrense"
|
||||
|
||||
[header]
|
||||
about = "Om"
|
||||
blog = "Blogg"
|
||||
|
|
@ -381,6 +370,17 @@ km = "km"
|
|||
label = "Enheter"
|
||||
mi = "mil"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} av {total}"
|
||||
activeHoursLabel = "Aktive timer"
|
||||
co2Label = "CO₂-utslipp"
|
||||
co2Limit = "CO₂-grense"
|
||||
loadpointDescription = "Slår på midlertidig hurtiglading i solcellemodus."
|
||||
modalTitle = "Smart lysnettlading"
|
||||
none = "ingen"
|
||||
priceLabel = "Energipris"
|
||||
priceLimit = "Prisgrense"
|
||||
|
||||
[startupError]
|
||||
configFile = "Brukt oppsettsfil:"
|
||||
configuration = "Oppsett"
|
||||
|
|
|
|||
22
i18n/pl.toml
22
i18n/pl.toml
|
|
@ -123,17 +123,6 @@ modalUpdateNow = "Instaluj teraz"
|
|||
modalUpdateStarted = "Nowa wersja evcc uruchomi się..."
|
||||
modalUpdateStatusStart = "Instalacja rozpoczęta:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} z {total}"
|
||||
activeHoursLabel = "Aktywne godziny"
|
||||
co2Label = "Emisja CO₂"
|
||||
co2Limit = "Limit CO₂"
|
||||
costLimitDescription = "Umożliwia tymczasowe szybkie ładowanie w trybie solarnym."
|
||||
modalTitle = "Ładowanie z inteligentnej sieci"
|
||||
none = "nie ma"
|
||||
priceLabel = "Cena energii"
|
||||
priceLimit = "Limit ceny"
|
||||
|
||||
[header]
|
||||
about = "O evcc"
|
||||
blog = "Blog"
|
||||
|
|
@ -425,6 +414,17 @@ km = "km"
|
|||
label = "Jednostki"
|
||||
mi = "mile"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} z {total}"
|
||||
activeHoursLabel = "Aktywne godziny"
|
||||
co2Label = "Emisja CO₂"
|
||||
co2Limit = "Limit CO₂"
|
||||
loadpointDescription = "Umożliwia tymczasowe szybkie ładowanie w trybie solarnym."
|
||||
modalTitle = "Ładowanie z inteligentnej sieci"
|
||||
none = "nie ma"
|
||||
priceLabel = "Cena energii"
|
||||
priceLimit = "Limit ceny"
|
||||
|
||||
[startupError]
|
||||
configFile = "Używany plik konfiguracyjny:"
|
||||
configuration = "Konfig"
|
||||
|
|
|
|||
22
i18n/pt.toml
22
i18n/pt.toml
|
|
@ -122,17 +122,6 @@ modalUpdateNow = "Instalar agora"
|
|||
modalUpdateStarted = "Iniciando a nova versão do evcc..."
|
||||
modalUpdateStatusStart = "Instalação iniciada:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} de {total}"
|
||||
activeHoursLabel = "Horas ativas"
|
||||
co2Label = "Emissão de CO₂"
|
||||
co2Limit = "Limite de CO₂"
|
||||
costLimitDescription = "Permite carregamento rápido temporário no modo solar."
|
||||
modalTitle = "Carregamento de Rede Inteligente"
|
||||
none = "sem"
|
||||
priceLabel = "Preço da energia"
|
||||
priceLimit = "Limite de preço"
|
||||
|
||||
[header]
|
||||
about = "Sobre"
|
||||
blog = "Blog"
|
||||
|
|
@ -427,6 +416,17 @@ km = "km"
|
|||
label = "Unidades"
|
||||
mi = "milhas"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} de {total}"
|
||||
activeHoursLabel = "Horas ativas"
|
||||
co2Label = "Emissão de CO₂"
|
||||
co2Limit = "Limite de CO₂"
|
||||
loadpointDescription = "Permite carregamento rápido temporário no modo solar."
|
||||
modalTitle = "Carregamento de Rede Inteligente"
|
||||
none = "sem"
|
||||
priceLabel = "Preço da energia"
|
||||
priceLimit = "Limite de preço"
|
||||
|
||||
[startupError]
|
||||
configFile = "Ficheiro de configuração em uso:"
|
||||
configuration = "Configuração"
|
||||
|
|
|
|||
22
i18n/ro.toml
22
i18n/ro.toml
|
|
@ -130,17 +130,6 @@ modalUpdateNow = "Instalează acum"
|
|||
modalUpdateStarted = "Pornesc noua versiune evcc..."
|
||||
modalUpdateStatusStart = "Instalare pornita:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} din {total}"
|
||||
activeHoursLabel = "Ore de functionare"
|
||||
co2Label = "emisii CO₂"
|
||||
co2Limit = "limita CO₂"
|
||||
costLimitDescription = "Activeaza temporar Incarcarea Rapida in mod Solar."
|
||||
modalTitle = "Incarcare Smart Grid"
|
||||
none = "nimic"
|
||||
priceLabel = "Pretul energiei"
|
||||
priceLimit = "Limita de pret"
|
||||
|
||||
[header]
|
||||
about = "Despre"
|
||||
blog = "Blog"
|
||||
|
|
@ -371,6 +360,17 @@ km = "km"
|
|||
label = "Unități de măsura"
|
||||
mi = "mile"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} din {total}"
|
||||
activeHoursLabel = "Ore de functionare"
|
||||
co2Label = "emisii CO₂"
|
||||
co2Limit = "limita CO₂"
|
||||
loadpointDescription = "Activeaza temporar Incarcarea Rapida in mod Solar."
|
||||
modalTitle = "Incarcare Smart Grid"
|
||||
none = "nimic"
|
||||
priceLabel = "Pretul energiei"
|
||||
priceLimit = "Limita de pret"
|
||||
|
||||
[startupError]
|
||||
configFile = "Fișier configurare folosit:"
|
||||
configuration = "Configurare"
|
||||
|
|
|
|||
22
i18n/sl.toml
22
i18n/sl.toml
|
|
@ -122,17 +122,6 @@ modalUpdateNow = "Namesti zdaj"
|
|||
modalUpdateStarted = "Zagon nove različice evcc…"
|
||||
modalUpdateStatusStart = "Namestitev se je začela:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} od {total}"
|
||||
activeHoursLabel = "Aktivne ure"
|
||||
co2Label = "Emisije CO₂"
|
||||
co2Limit = "Omejitev CO₂"
|
||||
costLimitDescription = "Omogoči začasno hitro polnjenje v solarnem načinu."
|
||||
modalTitle = "Smart Grid Polnjenje"
|
||||
none = "brez"
|
||||
priceLabel = "Cena energije"
|
||||
priceLimit = "Omejitev cene"
|
||||
|
||||
[header]
|
||||
about = "Informacije"
|
||||
blog = "Blog"
|
||||
|
|
@ -426,6 +415,17 @@ km = "km"
|
|||
label = "Enote"
|
||||
mi = "milje"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} od {total}"
|
||||
activeHoursLabel = "Aktivne ure"
|
||||
co2Label = "Emisije CO₂"
|
||||
co2Limit = "Omejitev CO₂"
|
||||
loadpointDescription = "Omogoči začasno hitro polnjenje v solarnem načinu."
|
||||
modalTitle = "Smart Grid Polnjenje"
|
||||
none = "brez"
|
||||
priceLabel = "Cena energije"
|
||||
priceLimit = "Omejitev cene"
|
||||
|
||||
[startupError]
|
||||
configFile = "Uporabljena konfiguracijska datoteka:"
|
||||
configuration = "Konfiguracija"
|
||||
|
|
|
|||
22
i18n/sv.toml
22
i18n/sv.toml
|
|
@ -166,17 +166,6 @@ modalUpdateNow = "Installera nu"
|
|||
modalUpdateStarted = "Starta den nya versionen av evcc..."
|
||||
modalUpdateStatusStart = "Installationen har börjat:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} av {total}"
|
||||
activeHoursLabel = "Aktiva timmar"
|
||||
co2Label = "CO₂ utsläpp"
|
||||
co2Limit = "CO₂ gräns"
|
||||
costLimitDescription = "Ladda när priset är lågt (sol-läge)"
|
||||
modalTitle = "Smartladdning elnät"
|
||||
none = "ingen"
|
||||
priceLabel = "Energipris"
|
||||
priceLimit = "Prisgräns"
|
||||
|
||||
[header]
|
||||
about = "Om"
|
||||
blog = "Blogg"
|
||||
|
|
@ -473,6 +462,17 @@ km = "km"
|
|||
label = "Enheter"
|
||||
mi = "miles"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} av {total}"
|
||||
activeHoursLabel = "Aktiva timmar"
|
||||
co2Label = "CO₂ utsläpp"
|
||||
co2Limit = "CO₂ gräns"
|
||||
loadpointDescription = "Ladda när priset är lågt (sol-läge)"
|
||||
modalTitle = "Smartladdning elnät"
|
||||
none = "ingen"
|
||||
priceLabel = "Energipris"
|
||||
priceLimit = "Prisgräns"
|
||||
|
||||
[startupError]
|
||||
configFile = "Konfigurationsfil som används:"
|
||||
configuration = "Konfiguration"
|
||||
|
|
|
|||
22
i18n/uk.toml
22
i18n/uk.toml
|
|
@ -109,17 +109,6 @@ modalUpdateNow = "Встановити зараз"
|
|||
modalUpdateStarted = "Початок нової версії evcc…"
|
||||
modalUpdateStatusStart = "Встановлення розпочато:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} зі {total}"
|
||||
activeHoursLabel = "Активні години"
|
||||
co2Label = "Викиди CO₂"
|
||||
co2Limit = "Ліміт CO₂"
|
||||
costLimitDescription = "Вмикає тимчасову швидку зарядку в сонячному режимі."
|
||||
modalTitle = "Розумна зарядка"
|
||||
none = "немає"
|
||||
priceLabel = "Ціна енергії"
|
||||
priceLimit = "Ліміт ціни"
|
||||
|
||||
[header]
|
||||
about = "Про evcc"
|
||||
blog = "Блог"
|
||||
|
|
@ -384,6 +373,17 @@ km = "км"
|
|||
label = "Одиниці вимірювання"
|
||||
mi = "милі"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} зі {total}"
|
||||
activeHoursLabel = "Активні години"
|
||||
co2Label = "Викиди CO₂"
|
||||
co2Limit = "Ліміт CO₂"
|
||||
loadpointDescription = "Вмикає тимчасову швидку зарядку в сонячному режимі."
|
||||
modalTitle = "Розумна зарядка"
|
||||
none = "немає"
|
||||
priceLabel = "Ціна енергії"
|
||||
priceLimit = "Ліміт ціни"
|
||||
|
||||
[startupError]
|
||||
configFile = "Використаний файл конфігурації:"
|
||||
configuration = "Конфігурація"
|
||||
|
|
|
|||
|
|
@ -119,17 +119,6 @@ modalUpdateNow = "现在安装"
|
|||
modalUpdateStarted = "启动新版本的evcc..."
|
||||
modalUpdateStatusStart = "安装已开始:"
|
||||
|
||||
[gridSettings]
|
||||
activeHours = "{charging} / {total}"
|
||||
activeHoursLabel = "运行小时"
|
||||
co2Label = "CO₂ 排放"
|
||||
co2Limit = "CO₂ 限制"
|
||||
costLimitDescription = "在太阳能模式下实现临时快速充电."
|
||||
modalTitle = "智能电网充电"
|
||||
none = "无"
|
||||
priceLabel = "电价"
|
||||
priceLimit = "限价"
|
||||
|
||||
[header]
|
||||
about = "关于"
|
||||
blog = "博客"
|
||||
|
|
@ -389,6 +378,17 @@ km = "km"
|
|||
label = "单位"
|
||||
mi = "英里"
|
||||
|
||||
[smartCost]
|
||||
activeHours = "{charging} / {total}"
|
||||
activeHoursLabel = "运行小时"
|
||||
co2Label = "CO₂ 排放"
|
||||
co2Limit = "CO₂ 限制"
|
||||
loadpointDescription = "在太阳能模式下实现临时快速充电."
|
||||
modalTitle = "智能电网充电"
|
||||
none = "无"
|
||||
priceLabel = "电价"
|
||||
priceLimit = "限价"
|
||||
|
||||
[startupError]
|
||||
configFile = "使用的配置文件:"
|
||||
configuration = "配置"
|
||||
|
|
|
|||
24
server/helper.go
Normal file
24
server/helper.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// pass converts a simple api without return value to api with nil error return value
|
||||
func pass[T any](f func(T)) func(T) error {
|
||||
return func(v T) error {
|
||||
f(v)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// parseFloat rejects NaN and Inf values
|
||||
func parseFloat(payload string) (float64, error) {
|
||||
f, err := strconv.ParseFloat(payload, 64)
|
||||
if err == nil && (math.IsNaN(f) || math.IsInf(f, 0)) {
|
||||
err = fmt.Errorf("invalid float value: %s", payload)
|
||||
}
|
||||
return f, err
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
eapi "github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/core/site"
|
||||
"github.com/evcc-io/evcc/server/assets"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
|
|
@ -99,7 +100,7 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API, cache *util.Cache) {
|
|||
"batterydischargecontrol": {[]string{"POST", "OPTIONS"}, "/batterydischargecontrol/{value:[a-z]+}", boolHandler(site.SetBatteryDischargeControl, site.GetBatteryDischargeControl)},
|
||||
"prioritysoc": {[]string{"POST", "OPTIONS"}, "/prioritysoc/{value:[0-9.]+}", floatHandler(site.SetPrioritySoc, site.GetPrioritySoc)},
|
||||
"residualpower": {[]string{"POST", "OPTIONS"}, "/residualpower/{value:[-0-9.]+}", floatHandler(site.SetResidualPower, site.GetResidualPower)},
|
||||
"smartcost": {[]string{"POST", "OPTIONS"}, "/smartcostlimit/{value:[-0-9.]+}", floatHandler(site.SetSmartCostLimit, site.GetSmartCostLimit)},
|
||||
"smartcost": {[]string{"POST", "OPTIONS"}, "/smartcostlimit/{value:[-0-9.]+}", updateSmartCostLimit(site)},
|
||||
"tariff": {[]string{"GET"}, "/tariff/{tariff:[a-z]+}", tariffHandler(site)},
|
||||
"sessions": {[]string{"GET"}, "/sessions", sessionHandler},
|
||||
"updatesession": {[]string{"PUT", "OPTIONS"}, "/session/{id:[0-9]+}", updateSessionHandler},
|
||||
|
|
@ -135,12 +136,12 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API, cache *util.Cache) {
|
|||
api := api.PathPrefix(fmt.Sprintf("/loadpoints/%d", id+1)).Subrouter()
|
||||
|
||||
routes := map[string]route{
|
||||
"mode": {[]string{"POST", "OPTIONS"}, "/mode/{value:[a-z]+}", chargeModeHandler(lp)},
|
||||
"mode": {[]string{"POST", "OPTIONS"}, "/mode/{value:[a-z]+}", handler(eapi.ChargeModeString, pass(lp.SetMode), lp.GetMode)},
|
||||
"limitsoc": {[]string{"POST", "OPTIONS"}, "/limitsoc/{value:[0-9]+}", intHandler(pass(lp.SetLimitSoc), lp.GetLimitSoc)},
|
||||
"limitenergy": {[]string{"POST", "OPTIONS"}, "/limitenergy/{value:[0-9.]+}", floatHandler(pass(lp.SetLimitEnergy), lp.GetLimitEnergy)},
|
||||
"mincurrent": {[]string{"POST", "OPTIONS"}, "/mincurrent/{value:[0-9.]+}", floatHandler(lp.SetMinCurrent, lp.GetMinCurrent)},
|
||||
"maxcurrent": {[]string{"POST", "OPTIONS"}, "/maxcurrent/{value:[0-9.]+}", floatHandler(lp.SetMaxCurrent, lp.GetMaxCurrent)},
|
||||
"phases": {[]string{"POST", "OPTIONS"}, "/phases/{value:[0-9]+}", phasesHandler(lp)},
|
||||
"phases": {[]string{"POST", "OPTIONS"}, "/phases/{value:[0-9]+}", intHandler(lp.SetPhases, lp.GetPhases)},
|
||||
"plan": {[]string{"GET"}, "/plan", planHandler(lp)},
|
||||
"planpreview": {[]string{"GET"}, "/plan/preview/{type:(?:soc|energy)}/{value:[0-9.]+}/{time:[0-9TZ:.-]+}", planPreviewHandler(lp)},
|
||||
"planenergy": {[]string{"POST", "OPTIONS"}, "/plan/energy/{value:[0-9.]+}/{time:[0-9TZ:.-]+}", planEnergyHandler(lp)},
|
||||
|
|
@ -151,6 +152,7 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API, cache *util.Cache) {
|
|||
"remotedemand": {[]string{"POST", "OPTIONS"}, "/remotedemand/{demand:[a-z]+}/{source:[0-9a-zA-Z_-]+}", remoteDemandHandler(lp)},
|
||||
"enableThreshold": {[]string{"POST", "OPTIONS"}, "/enable/threshold/{value:-?[0-9.]+}", floatHandler(pass(lp.SetEnableThreshold), lp.GetEnableThreshold)},
|
||||
"disableThreshold": {[]string{"POST", "OPTIONS"}, "/disable/threshold/{value:-?[0-9.]+}", floatHandler(pass(lp.SetDisableThreshold), lp.GetDisableThreshold)},
|
||||
"smartCostLimit": {[]string{"POST", "OPTIONS"}, "/smartcostlimit/{value:[0-9.]+}", floatHandler(pass(lp.SetSmartCostLimit), lp.GetSmartCostLimit)},
|
||||
// "priority": {[]string{"POST", "OPTIONS"}, "/priority/{value:[0-9.]+}", floatHandler(pass(lp.SetPriority), lp.GetPriority)},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,52 +13,6 @@ import (
|
|||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type moder interface {
|
||||
GetMode() api.ChargeMode
|
||||
SetMode(api.ChargeMode)
|
||||
}
|
||||
|
||||
// chargeModeHandler updates charge mode
|
||||
func chargeModeHandler(lp moder) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
mode, err := api.ChargeModeString(vars["value"])
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
lp.SetMode(mode)
|
||||
|
||||
jsonResult(w, lp.GetMode())
|
||||
}
|
||||
}
|
||||
|
||||
type phaser interface {
|
||||
GetPhases() int
|
||||
SetPhases(int) error
|
||||
}
|
||||
|
||||
// phasesHandler updates minimum soc
|
||||
func phasesHandler(lp phaser) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
phases, err := strconv.Atoi(vars["value"])
|
||||
if err == nil {
|
||||
err = lp.SetPhases(phases)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonResult(w, lp.GetPhases())
|
||||
}
|
||||
}
|
||||
|
||||
// remoteDemandHandler updates minimum soc
|
||||
func remoteDemandHandler(lp loadpoint.API) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
|||
|
|
@ -86,69 +86,37 @@ func jsonError(w http.ResponseWriter, status int, err error) {
|
|||
jsonWrite(w, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// pass converts a simple api without return value to api with nil error return value
|
||||
func pass[T any](f func(T)) func(T) error {
|
||||
return func(v T) error {
|
||||
f(v)
|
||||
return nil
|
||||
func handler[T any](conv func(string) (T, error), set func(T) error, get func() T) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
val, err := conv(vars["value"])
|
||||
if err == nil {
|
||||
err = set(val)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonResult(w, get())
|
||||
}
|
||||
}
|
||||
|
||||
// floatHandler updates float-param api
|
||||
func floatHandler(set func(float64) error, get func() float64) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
val, err := strconv.ParseFloat(vars["value"], 64)
|
||||
if err == nil {
|
||||
err = set(val)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonResult(w, get())
|
||||
}
|
||||
return handler(parseFloat, set, get)
|
||||
}
|
||||
|
||||
// intHandler updates int-param api
|
||||
func intHandler(set func(int) error, get func() int) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
val, err := strconv.Atoi(vars["value"])
|
||||
if err == nil {
|
||||
err = set(val)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonResult(w, get())
|
||||
}
|
||||
return handler(strconv.Atoi, set, get)
|
||||
}
|
||||
|
||||
// boolHandler updates bool-param api
|
||||
func boolHandler(set func(bool) error, get func() bool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
val, err := strconv.ParseBool(vars["value"])
|
||||
if err == nil {
|
||||
err = set(val)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonResult(w, get())
|
||||
}
|
||||
return handler(strconv.ParseBool, set, get)
|
||||
}
|
||||
|
||||
// boolGetHandler retrieves bool api values
|
||||
|
|
@ -171,6 +139,25 @@ func encodeFloats(data map[string]any) {
|
|||
}
|
||||
}
|
||||
|
||||
// updateSmartCostLimit sets the smart cost limit globally
|
||||
func updateSmartCostLimit(site site.API) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
val, err := parseFloat(vars["value"])
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, lp := range site.Loadpoints() {
|
||||
lp.SetSmartCostLimit(val)
|
||||
}
|
||||
|
||||
jsonResult(w, val)
|
||||
}
|
||||
}
|
||||
|
||||
// stateHandler returns the combined state
|
||||
func stateHandler(cache *util.Cache) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package server
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -170,11 +169,10 @@ func (m *MQTT) Listen(site site.API) error {
|
|||
|
||||
func (m *MQTT) listenSiteSetters(topic string, site site.API) error {
|
||||
for _, s := range []setter{
|
||||
{"/prioritySoc", floatSetterErr(site.SetPrioritySoc)},
|
||||
{"/bufferSoc", floatSetterErr(site.SetBufferSoc)},
|
||||
{"/bufferStartSoc", floatSetterErr(site.SetBufferStartSoc)},
|
||||
{"/residualPower", floatSetterErr(site.SetResidualPower)},
|
||||
{"/smartCostLimit", floatSetterErr(site.SetSmartCostLimit)},
|
||||
{"/prioritySoc", floatSetter(site.SetPrioritySoc)},
|
||||
{"/bufferSoc", floatSetter(site.SetBufferSoc)},
|
||||
{"/bufferStartSoc", floatSetter(site.SetBufferStartSoc)},
|
||||
{"/residualPower", floatSetter(site.SetResidualPower)},
|
||||
} {
|
||||
if err := m.Handler.ListenSetter(topic+s.topic, s.fun); err != nil {
|
||||
return err
|
||||
|
|
@ -186,14 +184,15 @@ func (m *MQTT) listenSiteSetters(topic string, site site.API) error {
|
|||
|
||||
func (m *MQTT) listenLoadpointSetters(topic string, site site.API, lp loadpoint.API) error {
|
||||
for _, s := range []setter{
|
||||
{"/mode", setterFunc(api.ChargeModeString, lp.SetMode)},
|
||||
{"/phases", intSetterErr(lp.SetPhases)},
|
||||
{"/limitSoc", intSetter(lp.SetLimitSoc)},
|
||||
{"/minCurrent", floatSetterErr(lp.SetMinCurrent)},
|
||||
{"/maxCurrent", floatSetterErr(lp.SetMaxCurrent)},
|
||||
{"/limitEnergy", floatSetter(lp.SetLimitEnergy)},
|
||||
{"/enableThreshold", floatSetter(lp.SetEnableThreshold)},
|
||||
{"/disableThreshold", floatSetter(lp.SetDisableThreshold)},
|
||||
{"/mode", setterFunc(api.ChargeModeString, pass(lp.SetMode))},
|
||||
{"/phases", intSetter(lp.SetPhases)},
|
||||
{"/limitSoc", intSetter(pass(lp.SetLimitSoc))},
|
||||
{"/minCurrent", floatSetter(lp.SetMinCurrent)},
|
||||
{"/maxCurrent", floatSetter(lp.SetMaxCurrent)},
|
||||
{"/limitEnergy", floatSetter(pass(lp.SetLimitEnergy))},
|
||||
{"/enableThreshold", floatSetter(pass(lp.SetEnableThreshold))},
|
||||
{"/disableThreshold", floatSetter(pass(lp.SetDisableThreshold))},
|
||||
{"/smartCostLimit", floatSetter(pass(lp.SetSmartCostLimit))},
|
||||
{"/planEnergy", func(payload string) error {
|
||||
var plan struct {
|
||||
Time time.Time `json:"time"`
|
||||
|
|
@ -228,8 +227,8 @@ func (m *MQTT) listenLoadpointSetters(topic string, site site.API, lp loadpoint.
|
|||
|
||||
func (m *MQTT) listenVehicleSetters(topic string, v vehicle.API) error {
|
||||
for _, s := range []setter{
|
||||
{topic + "/limitSoc", intSetter(v.SetLimitSoc)},
|
||||
{topic + "/minSoc", intSetter(v.SetMinSoc)},
|
||||
{topic + "/limitSoc", intSetter(pass(v.SetLimitSoc))},
|
||||
{topic + "/minSoc", intSetter(pass(v.SetMinSoc))},
|
||||
{topic + "/planSoc", func(payload string) error {
|
||||
var plan struct {
|
||||
Time time.Time `json:"time"`
|
||||
|
|
@ -303,12 +302,3 @@ func (m *MQTT) Run(site site.API, in <-chan util.Param) {
|
|||
m.publish(topic, true, p.Val)
|
||||
}
|
||||
}
|
||||
|
||||
// parseFloat rejects NaN and Inf values
|
||||
func parseFloat(payload string) (float64, error) {
|
||||
f, err := strconv.ParseFloat(payload, 64)
|
||||
if err == nil && (math.IsNaN(f) || math.IsInf(f, 0)) {
|
||||
err = fmt.Errorf("invalid float value: %s", payload)
|
||||
}
|
||||
return f, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ type setter struct {
|
|||
fun func(string) error
|
||||
}
|
||||
|
||||
func setterFuncErr[T any](conv func(string) (T, error), set func(T) error) func(string) error {
|
||||
func setterFunc[T any](conv func(string) (T, error), set func(T) error) func(string) error {
|
||||
return func(payload string) error {
|
||||
val, err := conv(payload)
|
||||
if err == nil {
|
||||
|
|
@ -19,25 +19,10 @@ func setterFuncErr[T any](conv func(string) (T, error), set func(T) error) func(
|
|||
}
|
||||
}
|
||||
|
||||
func setterFunc[T any](conv func(string) (T, error), set func(T)) func(string) error {
|
||||
return setterFuncErr(conv, func(val T) error {
|
||||
set(val)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func floatSetterErr(set func(float64) error) func(string) error {
|
||||
return setterFuncErr(parseFloat, set)
|
||||
}
|
||||
|
||||
func floatSetter(set func(float64)) func(string) error {
|
||||
func floatSetter(set func(float64) error) func(string) error {
|
||||
return setterFunc(parseFloat, set)
|
||||
}
|
||||
|
||||
func intSetterErr(set func(int) error) func(string) error {
|
||||
return setterFuncErr(strconv.Atoi, set)
|
||||
}
|
||||
|
||||
func intSetter(set func(int)) func(string) error {
|
||||
func intSetter(set func(int) error) func(string) error {
|
||||
return setterFunc(strconv.Atoi, set)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ test.describe("Basics", async () => {
|
|||
await page.getByTestId("topnavigation-button").click();
|
||||
await expect(page.getByRole("button", { name: "General Settings" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Battery Settings" })).not.toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Smart Grid Charging" })).not.toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Need help?" })).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
|
@ -64,7 +63,6 @@ test.describe("Advanced", async () => {
|
|||
await page.getByTestId("topnavigation-button").click();
|
||||
await expect(page.getByRole("button", { name: "General Settings" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Battery Settings" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Smart Grid Charging" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Need help?" })).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
|
@ -87,23 +85,4 @@ test.describe("Advanced", async () => {
|
|||
|
||||
await expect(page.getByRole("heading", { name: "Battery Settings" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("Smart Grid Charging from top navigation", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.getByTestId("topnavigation-button").click();
|
||||
await page.getByRole("button", { name: "Smart Grid Charging" }).click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Smart Grid Charging" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("Smart Grid Charging from energyflow", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.getByTestId("energyflow").click();
|
||||
await page
|
||||
.getByTestId("energyflow-entry-gridimport")
|
||||
.getByTestId("energyflow-entry-details")
|
||||
.click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Smart Grid Charging" })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
56
tests/smart-cost.spec.js
Normal file
56
tests/smart-cost.spec.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
const { test, expect } = require("@playwright/test");
|
||||
const { start, stop } = require("./evcc");
|
||||
const { startSimulator, stopSimulator, SIMULATOR_URL } = require("./simulator");
|
||||
|
||||
const CONFIG = "simulator.evcc.yaml";
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await start(CONFIG);
|
||||
await startSimulator();
|
||||
});
|
||||
test.afterAll(async () => {
|
||||
await stopSimulator();
|
||||
await stop();
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(SIMULATOR_URL);
|
||||
await page.getByLabel("PV Power").fill("6000");
|
||||
await page.getByTestId("loadpoint0").getByLabel("Power").fill("6000");
|
||||
await page.getByTestId("loadpoint0").getByText("C (charging)").click();
|
||||
await page.getByTestId("loadpoint0").getByText("Enabled").check();
|
||||
await page.getByRole("button", { name: "Apply changes" }).click();
|
||||
});
|
||||
|
||||
test.describe("smart cost limit", async () => {
|
||||
test("no limit, normal charging", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await expect(page.getByTestId("vehicle-status")).toHaveText("Charging…");
|
||||
});
|
||||
test("price below limit", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.getByTestId("loadpoint-settings-button").nth(1).click();
|
||||
await expect(page.getByTestId("loadpoint-settings-modal")).toBeVisible();
|
||||
await page
|
||||
.getByTestId("loadpoint-settings-modal")
|
||||
.getByLabel("Price limit")
|
||||
.selectOption("≤ 50.0 ct/kWh");
|
||||
await page.getByTestId("loadpoint-settings-modal").getByLabel("Close").click();
|
||||
await expect(page.getByTestId("loadpoint-settings-modal")).not.toBeVisible();
|
||||
await expect(page.getByTestId("vehicle-status")).toHaveText(
|
||||
"Charging cheap energy: 40.0 ct (limit 50.0 ct)"
|
||||
);
|
||||
});
|
||||
test("price above limit", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.getByTestId("loadpoint-settings-button").nth(1).click();
|
||||
await expect(page.getByTestId("loadpoint-settings-modal")).toBeVisible();
|
||||
await page
|
||||
.getByTestId("loadpoint-settings-modal")
|
||||
.getByLabel("Price limit")
|
||||
.selectOption("≤ 10.0 ct/kWh");
|
||||
await page.getByTestId("loadpoint-settings-modal").getByLabel("Close").click();
|
||||
await expect(page.getByTestId("loadpoint-settings-modal")).not.toBeVisible();
|
||||
await expect(page.getByTestId("vehicle-status")).toHaveText("Charging…");
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue