Add Loadpoint Settings Modal (MinSoC, Phases) (#4121)
This commit is contained in:
parent
5e0c67c56b
commit
9e3111dedf
18 changed files with 655 additions and 149 deletions
|
|
@ -265,10 +265,8 @@ small {
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
@media (--dark-mode) {
|
||||
.btn-close {
|
||||
filter: invert(1);
|
||||
}
|
||||
.dark .btn-close {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
|
|
@ -285,8 +283,11 @@ small {
|
|||
color: var(--bs-white);
|
||||
}
|
||||
|
||||
@media (--dark-mode) {
|
||||
.dropdown-menu {
|
||||
box-shadow: 0 0 8px var(--evcc-background);
|
||||
}
|
||||
.dark .dropdown-menu {
|
||||
box-shadow: 0 0 8px var(--evcc-background);
|
||||
}
|
||||
.dark .form-select {
|
||||
background-color: var(--evcc-box);
|
||||
color: var(--evcc-default-text);
|
||||
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/></svg>");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,24 @@
|
|||
<template>
|
||||
<div class="loadpoint pt-4 pb-2 px-3 px-sm-4 mx-2 mx-sm-0">
|
||||
<div class="d-block d-sm-flex justify-content-between align-items-center mb-3">
|
||||
<h3 class="mb-3 me-2 text-truncate">
|
||||
{{ title || $t("main.loadpoint.fallbackName") }}
|
||||
</h3>
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3 class="me-2 mb-0 text-truncate">
|
||||
{{ title || $t("main.loadpoint.fallbackName") }}
|
||||
</h3>
|
||||
<LoadpointSettingsButton :id="id" class="d-block d-sm-none" />
|
||||
</div>
|
||||
<div class="mb-3 d-flex align-items-center">
|
||||
<Mode class="w-100 w-sm-auto" :mode="mode" @updated="setTargetMode" />
|
||||
<button v-if="$hiddenFeatures" class="btn btn-link text-gray p-0 flex-shrink-0">
|
||||
<shopicon-filled-options size="s"></shopicon-filled-options>
|
||||
</button>
|
||||
<Mode class="flex-grow-1" :mode="mode" @updated="setTargetMode" />
|
||||
<LoadpointSettingsButton :id="id" class="d-none d-sm-block ms-2" />
|
||||
</div>
|
||||
</div>
|
||||
<LoadpointSettingsModal
|
||||
v-bind="settingsModal"
|
||||
@maxcurrent-updated="setMaxCurrent"
|
||||
@mincurrent-updated="setMinCurrent"
|
||||
@phasesconfigured-updated="setPhasesConfigured"
|
||||
@minsoc-updated="setMinSoC"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="remoteDisabled"
|
||||
|
|
@ -86,7 +94,7 @@
|
|||
|
||||
<script>
|
||||
import "@h2d2/shopicons/es/regular/lightning";
|
||||
import "@h2d2/shopicons/es/filled/options";
|
||||
import "@h2d2/shopicons/es/regular/adjust";
|
||||
import api from "../api";
|
||||
import Mode from "./Mode.vue";
|
||||
import Vehicle from "./Vehicle.vue";
|
||||
|
|
@ -94,10 +102,19 @@ import Phases from "./Phases.vue";
|
|||
import LabelAndValue from "./LabelAndValue.vue";
|
||||
import formatter from "../mixins/formatter";
|
||||
import collector from "../mixins/collector";
|
||||
import LoadpointSettingsButton from "./LoadpointSettingsButton.vue";
|
||||
import LoadpointSettingsModal from "./LoadpointSettingsModal.vue";
|
||||
|
||||
export default {
|
||||
name: "Loadpoint",
|
||||
components: { Mode, Vehicle, Phases, LabelAndValue },
|
||||
components: {
|
||||
Mode,
|
||||
Vehicle,
|
||||
Phases,
|
||||
LabelAndValue,
|
||||
LoadpointSettingsButton,
|
||||
LoadpointSettingsModal,
|
||||
},
|
||||
mixins: [formatter, collector],
|
||||
props: {
|
||||
id: Number,
|
||||
|
|
@ -138,6 +155,7 @@ export default {
|
|||
|
||||
// other information
|
||||
phases: Number,
|
||||
phasesConfigured: Number,
|
||||
minCurrent: Number,
|
||||
maxCurrent: Number,
|
||||
phasesActive: Number,
|
||||
|
|
@ -165,6 +183,9 @@ export default {
|
|||
phasesProps: function () {
|
||||
return this.collectProps(Phases);
|
||||
},
|
||||
settingsModal: function () {
|
||||
return this.collectProps(LoadpointSettingsModal);
|
||||
},
|
||||
vehicle: function () {
|
||||
return this.collectProps(Vehicle);
|
||||
},
|
||||
|
|
@ -216,6 +237,18 @@ export default {
|
|||
setTargetSoC: function (soc) {
|
||||
api.post(this.apiPath("targetsoc") + "/" + soc);
|
||||
},
|
||||
setMaxCurrent: function (maxCurrent) {
|
||||
api.post(this.apiPath("maxcurrent") + "/" + maxCurrent);
|
||||
},
|
||||
setMinCurrent: function (minCurrent) {
|
||||
api.post(this.apiPath("mincurrent") + "/" + minCurrent);
|
||||
},
|
||||
setPhasesConfigured: function (phases) {
|
||||
api.post(this.apiPath("phases") + "/" + phases);
|
||||
},
|
||||
setMinSoC: function (soc) {
|
||||
api.post(this.apiPath("minsoc") + "/" + soc);
|
||||
},
|
||||
setTargetTime: function (date) {
|
||||
api.post(`${this.apiPath("targetcharge")}/${this.targetSoC}/${date.toISOString()}`);
|
||||
},
|
||||
|
|
|
|||
21
assets/js/components/LoadpointSettingsButton.vue
Normal file
21
assets/js/components/LoadpointSettingsButton.vue
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<button
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
:data-bs-target="`#loadpointSettingsModal_${id}`"
|
||||
class="btn btn-sm btn-outline-secondary position-relative border-0 p-2 evcc-gray"
|
||||
>
|
||||
<shopicon-regular-adjust size="s"></shopicon-regular-adjust>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import "@h2d2/shopicons/es/regular/adjust";
|
||||
|
||||
export default {
|
||||
name: "LoadpointSettingsButton",
|
||||
props: {
|
||||
id: [String, Number],
|
||||
},
|
||||
};
|
||||
</script>
|
||||
326
assets/js/components/LoadpointSettingsModal.vue
Normal file
326
assets/js/components/LoadpointSettingsModal.vue
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
<template>
|
||||
<Teleport to="body">
|
||||
<div
|
||||
:id="`loadpointSettingsModal_${id}`"
|
||||
class="modal fade text-dark"
|
||||
data-bs-backdrop="true"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
{{ $t("main.loadpointSettings.title", [title]) }}
|
||||
</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container">
|
||||
<h4 class="d-flex align-items-center mb-3 mt-0 text-evcc">
|
||||
{{ $t("main.loadpointSettings.vehicle") }}
|
||||
<shopicon-bold-car3 class="ms-2"></shopicon-bold-car3>
|
||||
</h4>
|
||||
<div class="mb-3 row">
|
||||
<label
|
||||
:for="formId('minsoc')"
|
||||
class="col-sm-4 col-form-label pt-0 pt-sm-1"
|
||||
>
|
||||
{{ $t("main.loadpointSettings.minSoC.label") }}
|
||||
</label>
|
||||
<div class="col-sm-8 pe-0">
|
||||
<select
|
||||
:id="formId('minsoc')"
|
||||
v-model.number="selectedMinSoC"
|
||||
class="form-select form-select-sm mb-2 w-50"
|
||||
@change="changeMinSoC"
|
||||
>
|
||||
<option
|
||||
v-for="soc in [
|
||||
0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50,
|
||||
]"
|
||||
:key="soc"
|
||||
:value="soc"
|
||||
>
|
||||
{{ soc ? `${soc}%` : "--" }}
|
||||
</option>
|
||||
</select>
|
||||
<small>
|
||||
{{
|
||||
$t("main.loadpointSettings.minSoC.description", [
|
||||
selectedMinSoC || "x",
|
||||
])
|
||||
}}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4
|
||||
v-if="showConfigurablePhases || showCurrentSettings"
|
||||
class="d-flex align-items-center mb-3 mt-4 text-evcc"
|
||||
>
|
||||
{{ $t("main.loadpointSettings.currents") }}
|
||||
<shopicon-bold-lightning class="ms-1"></shopicon-bold-lightning>
|
||||
</h4>
|
||||
<div v-if="showConfigurablePhases" class="mb-3 row">
|
||||
<label
|
||||
:for="formId('phases_0')"
|
||||
class="col-sm-4 col-form-label pt-0"
|
||||
>
|
||||
{{ $t("main.loadpointSettings.phasesConfigured.label") }}
|
||||
</label>
|
||||
<div class="col-sm-8 pe-0">
|
||||
<div class="form-check">
|
||||
<input
|
||||
:id="formId('phases_0')"
|
||||
v-model.number="selectedPhases"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
:name="formId('phases')"
|
||||
:value="0"
|
||||
@change="changePhasesConfigured"
|
||||
/>
|
||||
<label class="form-check-label" :for="formId('phases_0')">
|
||||
{{
|
||||
$t(
|
||||
"main.loadpointSettings.phasesConfigured.phases_0"
|
||||
)
|
||||
}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
:id="formId('phases_1')"
|
||||
v-model.number="selectedPhases"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
:name="formId('phases')"
|
||||
:value="1"
|
||||
@change="changePhasesConfigured"
|
||||
/>
|
||||
<label class="form-check-label" :for="formId('phases_1')">
|
||||
{{
|
||||
$t(
|
||||
"main.loadpointSettings.phasesConfigured.phases_1"
|
||||
)
|
||||
}}
|
||||
<small>
|
||||
{{
|
||||
$t(
|
||||
"main.loadpointSettings.phasesConfigured.phases_1_hint",
|
||||
{ min: minPower1p, max: maxPower1p }
|
||||
)
|
||||
}}
|
||||
</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
:id="formId('phases_3')"
|
||||
v-model.number="selectedPhases"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
:name="formId('phases')"
|
||||
:value="3"
|
||||
@change="changePhasesConfigured"
|
||||
/>
|
||||
<label class="form-check-label" :for="formId('phases_3')">
|
||||
{{
|
||||
$t(
|
||||
"main.loadpointSettings.phasesConfigured.phases_3"
|
||||
)
|
||||
}}
|
||||
<small>
|
||||
{{
|
||||
$t(
|
||||
"main.loadpointSettings.phasesConfigured.phases_3_hint",
|
||||
{ min: minPower3p, max: maxPower3p }
|
||||
)
|
||||
}}
|
||||
</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="$hiddenFeatures" class="mb-3 row">
|
||||
<label
|
||||
:for="formId('maxcurrent')"
|
||||
class="col-sm-4 col-form-label pt-0 pt-sm-2"
|
||||
>
|
||||
{{ $t("main.loadpointSettings.maxCurrent.label") }}
|
||||
</label>
|
||||
<div class="col-sm-8 pe-0 d-flex align-items-center">
|
||||
<select
|
||||
:id="formId('maxcurrent')"
|
||||
v-model.number="selectedMaxCurrent"
|
||||
class="form-select form-select-sm w-50"
|
||||
@change="changeMaxCurrent"
|
||||
>
|
||||
<option
|
||||
v-for="{ value, name } in currentOptions(true, 16)"
|
||||
:key="value"
|
||||
:value="value"
|
||||
>
|
||||
{{ name }}
|
||||
</option>
|
||||
</select>
|
||||
<small class="ms-3">~ {{ maxPower }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="$hiddenFeatures" class="mb-3 row">
|
||||
<label
|
||||
:for="formId('mincurrent')"
|
||||
class="col-sm-4 col-form-label pt-0 pt-sm-2"
|
||||
>
|
||||
{{ $t("main.loadpointSettings.minCurrent.label") }}
|
||||
</label>
|
||||
<div class="col-sm-8 pe-0 d-flex align-items-center">
|
||||
<select
|
||||
:id="formId('mincurrent')"
|
||||
v-model.number="selectedMinCurrent"
|
||||
class="form-select form-select-sm w-50"
|
||||
@change="changeMinCurrent"
|
||||
>
|
||||
<option
|
||||
v-for="{ value, name } in currentOptions(false, 6)"
|
||||
:key="value"
|
||||
:value="value"
|
||||
>
|
||||
{{ name }}
|
||||
</option>
|
||||
</select>
|
||||
<small class="ms-3">~ {{ minPower }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="small mt-3 text-muted mb-0">
|
||||
<strong class="text-evcc">
|
||||
{{ $t("main.loadpointSettings.disclaimerHint") }}
|
||||
</strong>
|
||||
{{ $t("main.loadpointSettings.disclaimerText") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import "@h2d2/shopicons/es/bold/lightning";
|
||||
import "@h2d2/shopicons/es/bold/car3";
|
||||
import formatter from "../mixins/formatter";
|
||||
|
||||
const V = 230;
|
||||
|
||||
export default {
|
||||
name: "LoadpointSettingsModal",
|
||||
mixins: [formatter],
|
||||
props: {
|
||||
id: [String, Number],
|
||||
phasesConfigured: Number,
|
||||
minSoC: Number,
|
||||
maxCurrent: Number,
|
||||
minCurrent: Number,
|
||||
title: String,
|
||||
},
|
||||
emits: [
|
||||
"phasesconfigured-updated",
|
||||
"maxcurrent-updated",
|
||||
"mincurrent-updated",
|
||||
"minsoc-updated",
|
||||
],
|
||||
data: function () {
|
||||
return {
|
||||
selectedMaxCurrent: this.maxCurrent,
|
||||
selectedMinCurrent: this.minCurrent,
|
||||
selectedPhases: this.phasesConfigured,
|
||||
selectedMinSoC: this.minSoC,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
maxPower1p: function () {
|
||||
return this.fmtKw(this.maxCurrent * V);
|
||||
},
|
||||
minPower1p: function () {
|
||||
return this.fmtKw(this.minCurrent * V);
|
||||
},
|
||||
maxPower3p: function () {
|
||||
return this.fmtKw(this.maxCurrent * V * 3);
|
||||
},
|
||||
minPower3p: function () {
|
||||
return this.fmtKw(this.minCurrent * V * 3);
|
||||
},
|
||||
maxPower: function () {
|
||||
return this.phasesConfigured === 1 ? this.maxPower1p : this.maxPower3p;
|
||||
},
|
||||
minPower: function () {
|
||||
return this.phasesConfigured === 3 ? this.minPower3p : this.minPower1p;
|
||||
},
|
||||
showConfigurablePhases: function () {
|
||||
return [0, 1, 3].includes(this.phasesConfigured);
|
||||
},
|
||||
showCurrentSettings: function () {
|
||||
return this.$hiddenFeatures;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
maxCurrent: function (value) {
|
||||
this.selectedMaxCurrent = value;
|
||||
},
|
||||
minCurrent: function (value) {
|
||||
this.selectedMinCurrent = value;
|
||||
},
|
||||
phasesConfigured: function (value) {
|
||||
this.selectedPhases = value;
|
||||
},
|
||||
minSoC: function (value) {
|
||||
this.selectedMinSoC = value;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
formId: function (name) {
|
||||
return `loadpoint_${this.id}_${name}`;
|
||||
},
|
||||
changeMaxCurrent: function () {
|
||||
this.$emit("maxcurrent-updated", this.selectedMaxCurrent);
|
||||
},
|
||||
changeMinCurrent: function () {
|
||||
this.$emit("mincurrent-updated", this.selectedMinCurrent);
|
||||
},
|
||||
changePhasesConfigured: function () {
|
||||
this.$emit("phasesconfigured-updated", this.selectedPhases);
|
||||
},
|
||||
changeMinSoC: function () {
|
||||
this.$emit("minsoc-updated", this.selectedMinSoC);
|
||||
},
|
||||
currentOptions: function (max, defaultCurrent = 16) {
|
||||
const result = [];
|
||||
const toValue = max ? 32 : this.maxCurrent;
|
||||
const fromValue = max ? this.minCurrent : 6;
|
||||
for (let value = toValue; value >= fromValue; value--) {
|
||||
let name = `${value} A`;
|
||||
if (value === defaultCurrent) {
|
||||
name += ` (${this.$t("main.loadpointSettings.default")})`;
|
||||
}
|
||||
result.push({ value, name });
|
||||
}
|
||||
return result;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.container {
|
||||
margin-left: calc(var(--bs-gutter-x) * -0.5);
|
||||
margin-right: calc(var(--bs-gutter-x) * -0.5);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -22,7 +22,7 @@ export default {
|
|||
emits: ["updated"],
|
||||
data() {
|
||||
return {
|
||||
modes: ["off", "minpv", "pv", "now"],
|
||||
modes: ["off", "pv", "minpv", "now"],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -41,6 +41,7 @@ export default {
|
|||
border: 2px solid var(--evcc-default-text);
|
||||
border-radius: 20px;
|
||||
padding: 4px;
|
||||
min-width: 255px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export default {
|
|||
props: {
|
||||
title: String,
|
||||
icon: String,
|
||||
value: String,
|
||||
value: [String, Number],
|
||||
unit: String,
|
||||
sub1: String,
|
||||
sub2: String,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
:loadpoints="loadpoints"
|
||||
:vehicles="vehicles"
|
||||
/>
|
||||
<Vehicles v-if="$hiddenFeatures" />
|
||||
<Vehicles v-if="showParkingLot" />
|
||||
<Footer v-bind="footer"></Footer>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -108,6 +108,10 @@ export default {
|
|||
const vehicleLogins = this.auth ? this.auth.vehicles : {};
|
||||
return { vehicleLogins };
|
||||
},
|
||||
showParkingLot: function () {
|
||||
// work in progess
|
||||
return false;
|
||||
},
|
||||
footer: function () {
|
||||
return {
|
||||
version: {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<template>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
data-bs-toggle="dropdown"
|
||||
data-bs-target="#navbarNavAltMarkup"
|
||||
aria-controls="navbarNavAltMarkup"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation"
|
||||
class="btn btn-sm btn-outline-secondary position-relative border-0 menu-button"
|
||||
>
|
||||
<shopicon-filled-options size="s"></shopicon-filled-options>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<slot />
|
||||
</div>
|
||||
<ul class="dropdown-menu dropdown-menu-start">
|
||||
<li>
|
||||
<h6 class="dropdown-header">{{ $t("main.vehicle.changeVehicle") }}</h6>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,5 @@
|
|||
<template>
|
||||
<div class="d-flex justify-content-between mb-3 align-items-center">
|
||||
<h4 class="d-flex align-items-center m-0 flex-grow-1 overflow-hidden">
|
||||
<shopicon-regular-car3
|
||||
v-if="carIcon"
|
||||
class="me-2 flex-shrink-0 car-icon"
|
||||
></shopicon-regular-car3>
|
||||
<shopicon-regular-cablecharge
|
||||
v-else
|
||||
class="me-2 flex-shrink-0 car-icon"
|
||||
></shopicon-regular-cablecharge>
|
||||
<span class="flex-grow-1 text-truncate"> {{ name }} </span>
|
||||
</h4>
|
||||
<VehicleOptions
|
||||
v-if="showOptions"
|
||||
class="options"
|
||||
|
|
@ -18,7 +7,21 @@
|
|||
:is-unknown="isUnknown"
|
||||
@change-vehicle="changeVehicle"
|
||||
@remove-vehicle="removeVehicle"
|
||||
/>
|
||||
>
|
||||
<h4 class="d-flex align-items-center m-0 flex-grow-1 overflow-hidden">
|
||||
<shopicon-regular-car3
|
||||
v-if="carIcon"
|
||||
class="me-2 flex-shrink-0 car-icon"
|
||||
></shopicon-regular-car3>
|
||||
<shopicon-regular-cablecharge
|
||||
v-else
|
||||
class="me-2 flex-shrink-0 car-icon"
|
||||
></shopicon-regular-cablecharge>
|
||||
<span class="flex-grow-1 text-truncate vehicle-name">
|
||||
{{ name }}
|
||||
</span>
|
||||
</h4>
|
||||
</VehicleOptions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -82,4 +85,8 @@ export default {
|
|||
.options {
|
||||
margin-right: -0.75rem;
|
||||
}
|
||||
.vehicle-name {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: var(--evcc-gray);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -84,6 +84,34 @@ export default {
|
|||
duration: "Dauer",
|
||||
remaining: "Restzeit",
|
||||
},
|
||||
loadpointSettings: {
|
||||
title: 'Einstellungen "{0}"',
|
||||
vehicle: "Fahrzeug",
|
||||
currents: "Ladestrom",
|
||||
minSoC: {
|
||||
label: "Mindestladestand",
|
||||
description:
|
||||
'Reichweite für Notfälle. Fahrzeug wird im PV-Modus "schnell" auf {0}% geladen. Danach weiter mit PV-Überschuss.',
|
||||
},
|
||||
phasesConfigured: {
|
||||
label: "Phasen",
|
||||
phases_0: "automatischer Wechsel",
|
||||
phases_1: "1-phasig",
|
||||
phases_1_hint: "({min} bis {max})",
|
||||
phases_3: "3-phasig",
|
||||
phases_3_hint: "({min} bis {max})",
|
||||
},
|
||||
maxCurrent: {
|
||||
label: "Max. Ladestrom",
|
||||
},
|
||||
minCurrent: {
|
||||
label: "Min. Ladestrom",
|
||||
},
|
||||
default: "default",
|
||||
disclaimerHint: "Hinweis:",
|
||||
disclaimerText:
|
||||
"Änderungen sind aktuell noch nicht persistent und werden nach einem Serverneustart wieder zurückgesetzt.",
|
||||
},
|
||||
vehicles: "Parkplatz",
|
||||
vehicle: {
|
||||
fallbackName: "Fahrzeug",
|
||||
|
|
|
|||
|
|
@ -84,6 +84,33 @@ export default {
|
|||
duration: "Duration",
|
||||
remaining: "Remaining",
|
||||
},
|
||||
loadpointSettings: {
|
||||
title: 'Settings "{0}"',
|
||||
vehicle: "Vehicle",
|
||||
currents: "Charging",
|
||||
minSoC: {
|
||||
label: "Minimal SoC",
|
||||
description:
|
||||
'Range for emergencies. Vehicle gets "fast" charged to {0}% in PV mode. Then continues with PV surplus only.',
|
||||
},
|
||||
phasesConfigured: {
|
||||
label: "Phases",
|
||||
phases_0: "automatic switching",
|
||||
phases_1: "1 phase",
|
||||
phases_1_hint: "({min} to {max})",
|
||||
phases_3: "3 phases",
|
||||
phases_3_hint: "({min} to {max})",
|
||||
},
|
||||
maxCurrent: {
|
||||
label: "Max. Current",
|
||||
},
|
||||
minCurrent: {
|
||||
label: "Min. Current",
|
||||
},
|
||||
default: "default",
|
||||
disclaimerHint: "Note:",
|
||||
disclaimerText: "Changes are not persistent yet. They will be reset after server restart.",
|
||||
},
|
||||
vehicles: "Parking",
|
||||
vehicle: {
|
||||
fallbackName: "Vehicle",
|
||||
|
|
|
|||
|
|
@ -20,15 +20,14 @@ func init() {
|
|||
registry.Add(api.Custom, NewConfigurableFromConfig)
|
||||
}
|
||||
|
||||
//go:generate go run ../cmd/tools/decorate.go -f decorateCustom -b *Charger -r api.Charger -t "api.Identifier,Identify,func() (string, error)"
|
||||
// go:generate go run ../cmd/tools/decorate.go -f decorateCustom -b *Charger -r api.Charger -t "api.Identifier,Identify,func() (string, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) (error)"
|
||||
|
||||
// NewConfigurableFromConfig creates a new configurable charger
|
||||
func NewConfigurableFromConfig(other map[string]interface{}) (api.Charger, error) {
|
||||
var cc struct {
|
||||
Status, Enable, Enabled, MaxCurrent provider.Config
|
||||
Identify *provider.Config
|
||||
Identify, Phases1p3p *provider.Config
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -55,13 +54,24 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Charger, error
|
|||
|
||||
c, err := NewConfigurable(status, enabled, enable, maxcurrent)
|
||||
|
||||
// decorate identifier
|
||||
if err == nil && cc.Identify != nil {
|
||||
identify, err := provider.NewStringGetterFromConfig(*cc.Identify)
|
||||
return decorateCustom(c, identify), err
|
||||
// decorator phases
|
||||
var phases1p3p func(int) error
|
||||
if err == nil && cc.Phases1p3p != nil {
|
||||
var phases1p3pi64 func(int64) error
|
||||
phases1p3pi64, err = provider.NewIntSetterFromConfig("phases", *cc.Phases1p3p)
|
||||
|
||||
phases1p3p = func(phases int) error {
|
||||
return phases1p3pi64(int64(phases))
|
||||
}
|
||||
}
|
||||
|
||||
return c, err
|
||||
// decorator identifier
|
||||
var identify func() (string, error)
|
||||
if err == nil && cc.Identify != nil {
|
||||
identify, err = provider.NewStringGetterFromConfig(*cc.Identify)
|
||||
}
|
||||
|
||||
return decorateCustom(c, identify, phases1p3p), err
|
||||
}
|
||||
|
||||
// NewConfigurable creates a new charger
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ import (
|
|||
"github.com/evcc-io/evcc/api"
|
||||
)
|
||||
|
||||
func decorateCustom(base *Charger, identifier func() (string, error)) api.Charger {
|
||||
func decorateCustom(base *Charger, identifier func() (string, error), phaseSwitcher func(int) error) api.Charger {
|
||||
switch {
|
||||
case identifier == nil:
|
||||
case identifier == nil && phaseSwitcher == nil:
|
||||
return base
|
||||
|
||||
case identifier != nil:
|
||||
case identifier != nil && phaseSwitcher == nil:
|
||||
return &struct {
|
||||
*Charger
|
||||
api.Identifier
|
||||
|
|
@ -21,6 +21,32 @@ func decorateCustom(base *Charger, identifier func() (string, error)) api.Charge
|
|||
identifier: identifier,
|
||||
},
|
||||
}
|
||||
|
||||
case identifier == nil && phaseSwitcher != nil:
|
||||
return &struct {
|
||||
*Charger
|
||||
api.PhaseSwitcher
|
||||
}{
|
||||
Charger: base,
|
||||
PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
|
||||
phaseSwitcher: phaseSwitcher,
|
||||
},
|
||||
}
|
||||
|
||||
case identifier != nil && phaseSwitcher != nil:
|
||||
return &struct {
|
||||
*Charger
|
||||
api.Identifier
|
||||
api.PhaseSwitcher
|
||||
}{
|
||||
Charger: base,
|
||||
Identifier: &decorateCustomIdentifierImpl{
|
||||
identifier: identifier,
|
||||
},
|
||||
PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
|
||||
phaseSwitcher: phaseSwitcher,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -33,3 +59,11 @@ type decorateCustomIdentifierImpl struct {
|
|||
func (impl *decorateCustomIdentifierImpl) Identify() (string, error) {
|
||||
return impl.identifier()
|
||||
}
|
||||
|
||||
type decorateCustomPhaseSwitcherImpl struct {
|
||||
phaseSwitcher func(int) error
|
||||
}
|
||||
|
||||
func (impl *decorateCustomPhaseSwitcherImpl) Phases1p3p(phases int) error {
|
||||
return impl.phaseSwitcher(phases)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,6 +104,13 @@ chargers:
|
|||
var lp = state.loadpoints[0];
|
||||
lp.maxcurrent = val;
|
||||
if (lp.enabled) lp.chargepower = lp.maxcurrent * 230 * lp.phases; else lp.chargepower = 0;
|
||||
phases1p3p:
|
||||
source: js
|
||||
vm: shared
|
||||
script: |
|
||||
set();
|
||||
if (val === 1) lp.phases = 1; else lp.phases = 3;
|
||||
lp.phases;
|
||||
|
||||
- name: charger_2
|
||||
type: custom
|
||||
|
|
@ -133,6 +140,13 @@ chargers:
|
|||
var lp = state.loadpoints[1];
|
||||
lp.maxcurrent = val;
|
||||
if (lp.enabled) lp.chargepower = lp.maxcurrent * 230 * lp.phases; else lp.chargepower = 0;
|
||||
phases1p3p:
|
||||
source: js
|
||||
vm: shared
|
||||
script: |
|
||||
set();
|
||||
if (val === 1) lp.phases = 1; else lp.phases = 3;
|
||||
lp.phases;
|
||||
|
||||
vehicles:
|
||||
- name: vehicle_1
|
||||
|
|
|
|||
97
dist/assets/index.5a450559.js
vendored
97
dist/assets/index.5a450559.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
97
dist/assets/index.f014e26a.js
vendored
Normal file
97
dist/assets/index.f014e26a.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
dist/index.html
vendored
4
dist/index.html
vendored
|
|
@ -20,8 +20,8 @@
|
|||
<meta name="theme-color" content="#020318" />
|
||||
|
||||
<title>evcc</title>
|
||||
<script type="module" crossorigin src="./assets/index.5a450559.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index.9eb90bf3.css">
|
||||
<script type="module" crossorigin src="./assets/index.f014e26a.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index.c0100ffd.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue