Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add schedule for planner #16091

Draft
wants to merge 71 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
eb0cb24
wip
Maschga Sep 12, 2024
a3a936c
weip
Maschga Sep 13, 2024
6e952d9
wip
Maschga Sep 13, 2024
6c6c591
running lint
Maschga Sep 13, 2024
584b78a
wip
Maschga Sep 13, 2024
618e8ca
remove comment
Maschga Sep 13, 2024
762b84d
running lint
Maschga Sep 13, 2024
3a89e00
replacing `repetitive` with `repeating`
Maschga Sep 16, 2024
01ee9ca
better id-name
Maschga Sep 16, 2024
561eb32
using unique id-name
Maschga Sep 16, 2024
39c70e7
removing unnecessary parameter `socBasedPlanning`
Maschga Sep 16, 2024
88a8ae9
Merge branch 'add-schedule-for-planner' of github.com:Maschga/evcc in…
Maschga Sep 16, 2024
3ab074c
using `Intl` to get weekdays-translations
Maschga Sep 16, 2024
d308765
fix bug
Maschga Sep 16, 2024
b85c08d
add unit tests for `getShortenedWeekdaysLabel()`-method
Maschga Sep 16, 2024
46b78f9
fix syntax bug
Maschga Sep 16, 2024
1727afc
using `this.$i18n?.locale`
Maschga Sep 16, 2024
685ef0b
rewriting tests for english weekdays
Maschga Sep 16, 2024
cf9dc23
Revert "rewriting tests for english weekdays"
Maschga Sep 16, 2024
b9f6d7e
fix test
Maschga Sep 16, 2024
e56ccbb
add newline at end of file
Maschga Sep 16, 2024
1e12244
giving tags unique data-testids
Maschga Sep 16, 2024
a07f746
use hyphen only for more than 2 consecutive weekdays
Maschga Sep 28, 2024
0645cc3
using en-dash and whitespaces
Maschga Sep 28, 2024
6851ffe
add missing `-`
Maschga Sep 28, 2024
f079ea6
make data-testid unique
Maschga Sep 28, 2024
a0cb4d0
sort toml keys alphabetically
Maschga Oct 4, 2024
0ad95a1
update event names
Maschga Oct 4, 2024
a280d31
run lint
Maschga Oct 4, 2024
05e8e17
rename variables
Maschga Oct 4, 2024
1c8172d
wip: implement backend
Maschga Oct 4, 2024
568b2fb
remove unused import
Maschga Oct 4, 2024
77fdab2
make key camelCase
Maschga Oct 5, 2024
77a5fd6
run porcelain
Maschga Oct 5, 2024
b585907
Update mock.go
Maschga Oct 5, 2024
a3d652f
remove name `entry` + wip: implement backend
Maschga Oct 7, 2024
51c1f87
change key to `repeatingPlans`
Maschga Oct 7, 2024
2eb2417
synchronize data via websocket
Maschga Oct 8, 2024
e4411d6
recursion fix
naltatis Oct 14, 2024
22cd019
use key `plans` instead of `repeatingPlans`
Maschga Oct 14, 2024
ce592b4
use `splice()` instead of `Object.assign` to avoid a reference
Maschga Oct 14, 2024
d0ae604
improve debug message
Maschga Oct 14, 2024
fa9d77f
remove comment
Maschga Oct 14, 2024
0672649
remove unnecessary tag
Maschga Oct 14, 2024
171bad6
add tag for space
Maschga Oct 14, 2024
4c1f580
show apply-button on the right side
Maschga Oct 14, 2024
319abef
run lint
Maschga Oct 14, 2024
a5aa0f8
lower first character of variable
Maschga Oct 14, 2024
d856c8f
WIP: show next plan
Maschga Oct 18, 2024
c3cd3aa
avoid circular import
Maschga Oct 19, 2024
fac2ab5
disable last remaining field
Maschga Oct 21, 2024
6c22e58
correct name
Maschga Oct 21, 2024
fa77c94
handle utc
Maschga Oct 21, 2024
b5ef2c6
add onlyActivePlans-parameter
Maschga Oct 21, 2024
af83894
fix bugs
Maschga Oct 27, 2024
bfd883f
fix test
Maschga Oct 28, 2024
6fabcf2
Merge remote-tracking branch 'upstream/master' into add-schedule-for-…
Maschga Oct 28, 2024
3337d15
run porcelain
Maschga Oct 28, 2024
f4d343b
fix tests
Maschga Oct 28, 2024
aa4ddcf
run lint
Maschga Oct 28, 2024
ff42d34
fix bug
Maschga Oct 28, 2024
77f9c79
rename method `vehiclePlanSoc()` to `nextVehiclePlanSoc()`
Maschga Nov 2, 2024
841d080
rename `AdapterStruct` to `adapter`
Maschga Nov 2, 2024
795f1a4
update the time format on language-change
Maschga Nov 2, 2024
e40f0fd
rename functions
Maschga Nov 11, 2024
fa76656
get next plan
Maschga Nov 11, 2024
fc673c2
refactor code
Maschga Nov 11, 2024
0facffe
begin week with sunday
Maschga Nov 11, 2024
4daa42c
add test for `getWeekdaysList()`
Maschga Nov 11, 2024
27f403f
add `getNextLocalePlan()`
Maschga Nov 11, 2024
a147c1b
add preview for repeating plans
Maschga Nov 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions assets/js/components/ChargingPlan.vue
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@
<ChargingPlanSettings
v-if="departureTabActive"
v-bind="chargingPlanSettingsProps"
@plan-updated="updatePlan"
@plan-removed="removePlan"
@static-plan-updated="updatePlan"
@static-plan-removed="removePlan"
/>
<ChargingPlanArrival
v-if="arrivalTabActive"
Expand Down
98 changes: 98 additions & 0 deletions assets/js/components/ChargingPlanRepetitiveSettingsEntries.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<template>
<div v-for="(plan, index) in plans" :key="index">
<ChargingPlanRepetitiveSettingsEntry
class="mb-4"
:id="index"
v-bind="plan"
:socBasedPlanning="socBasedPlanning"
:rangePerSoc="rangePerSoc"
@repetitive-plan-updated="updateRepetitivePlan"
@repetitive-plan-removed="removeRepetitivePlan"
/>
</div>
<div class="d-flex align-items-baseline">
<button
type="button"
class="d-flex btn btn-sm btn-outline-primary border-0 ps-0 align-items-baseline"
data-testid="repetitive-plan-add"
@click="addRepetitivePlan"
>
<shopicon-regular-plus size="s" class="flex-shrink-0 me-2"></shopicon-regular-plus>
<p class="mb-0">{{ $t("main.chargingPlan.addRepetitivePlan") }}</p>
</button>
<button
v-if="hasDataChanged"
type="button"
class="btn btn-sm btn-outline-primary ms-3 border-0 text-decoration-underline"
data-testid="plan-apply"
@click="update"
>
{{ $t("main.chargingPlan.update") }}
</button>
</div>
</template>

<script>
import api from "../api";
import ChargingPlanRepetitiveSettingsEntry from "./ChargingPlanRepetitiveSettingsEntry.vue";
const DEFAULT_WEEKDAYS = [0];
const DEFAULT_TARGET_TIME = "12:00";
const DEFAULT_TARGET_SOC = 80;
export default {
name: "ChargingPlanRepetitiveSettingsEntries",
components: {
ChargingPlanRepetitiveSettingsEntry,
},
props: {
id: Number,
socBasedPlanning: Boolean,
rangePerSoc: Number,
},
data: function () {
return {
plans: [],
initialPlans: [],
};
},
mounted() {
this.fetchRepetitivePlans();
},
computed: {
hasDataChanged: function () {
return JSON.stringify(this.initialPlans) !== JSON.stringify(this.plans);
},
},
methods: {
fetchRepetitivePlans: async function () {
let response = await api.get(`/loadpoints/${this.id}/plan/repetitive`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Die Komponente sollte sich nicht selbst die Daten holen. Das Muster was wir standardmäßig verwenden ist, dass wir Schreiboperationen via REST machen die aktualisierten Daten aber über Websockets gepusht werden. Die landen dann im globalen State. Da dieses Feature nur für Fahrzeuge mit SoC verfügbar sein wird sollten wir den Ladeplan auch direkt am Fahrzeug pushen. Siehe hier:

Bildschirmfoto 2024-09-16 um 08 35 18

Damit ist er dann auch gleich über MQTT verfügbar und wir lösen das Problem Datensync zwischen Geräten. Also mehrere offene Browserfenster.

Aktuell veröffentlichen wir den "einmaligen Plan" auch noch nicht am Fahrzeug. Das müssen wir aber in diesem Zuge mit machen. Momentan kommen die aktuellen Plandaten die für die Statusanzeige in der UI und für das Ladeplanformular verwendet werden vom Ladepunkt. Mit der Einführung von mehreren Plänen würde ich am Ladepunkt nur noch die Daten des "als nächstes anstehenden Plans" veröffentlichen. Dass kann ein einmaliger sein, aber auch eine Ladung, die aufgrund einer Wiederholungsregel ansteht. Bislang mussten wir das nicht trennen. Jetzt ist das aber erforderlich.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beim Vehicle werden zwei Arrays mitgegeben: plans und repeatingPlans.
Beim Loadpoint werden die effectivePlanSoc und effectivePlanTime-Felder mitgeschickt.
Passt das so?

this.plans = response.data.result;
this.initialPlans = [...response.data.result]; // clone array
},
addRepetitivePlan: function () {
this.plans.push({
weekdays: DEFAULT_WEEKDAYS,
time: DEFAULT_TARGET_TIME,
soc: DEFAULT_TARGET_SOC,
active: false,
});
},
update: async function () {
// TODO: update data
this.initialPlans = [...this.plans];
},
updateRepetitivePlan: function (newData) {
this.plans[newData.id] = {
weekdays: newData.weekdays,
time: newData.time,
soc: newData.soc,
active: newData.active,
};
},
removeRepetitivePlan: function (index) {
this.plans.splice(index, 1);
},
},
};
</script>
182 changes: 182 additions & 0 deletions assets/js/components/ChargingPlanRepetitiveSettingsEntry.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<template>
<div>
<div class="row d-none d-lg-flex mb-2">
<div class="col-6 col-lg-4">
<label :for="formId('day')">
{{ $t("main.chargingPlan.weekdays") }}
</label>
</div>
<div class="col-6 col-lg-2">
<label :for="formId('time')">
{{ $t("main.chargingPlan.time") }}
</label>
</div>
<div class="col-6 col-lg-3">
<label :for="formId('goal')">
{{ $t("main.chargingPlan.goal") }}
</label>
</div>
<div class="col-6 col-lg-1">
<label :for="formId('active')"> {{ $t("main.chargingPlan.active") }} </label>
</div>
<div class="col-6 col-lg-1" />
</div>
<div class="row">
<div class="col-5 d-lg-none col-form-label">
<label :for="formId('day')">
Maschga marked this conversation as resolved.
Show resolved Hide resolved
{{ $t("main.chargingPlan.weekdays") }}
</label>
</div>
<div class="col-7 col-lg-4 mb-2 mb-lg-0">
<MultiSelect
id="chargingPlanWeekdaySelect"
Maschga marked this conversation as resolved.
Show resolved Hide resolved
:value="selectedWeekdays"
:options="dayOptions()"
:selectAllLabel="$t('main.chargingPlan.selectAll')"
@update:modelValue="changeSelectedWeekdays"
>
{{ weekdaysLabel }}
</MultiSelect>
</div>
<div class="col-5 d-lg-none col-form-label">
<label :for="formId('day')">
{{ $t("main.chargingPlan.time") }}
</label>
</div>
<div class="col-7 col-lg-2 mb-2 mb-lg-0">
<input
:id="formId('time')"
v-model="selectedTime"
type="time"
class="form-control mx-0"
:step="60 * 5"
data-testid="plan-time"
required
@change="update"
/>
</div>
<div class="col-5 d-lg-none col-form-label">
<label :for="formId('goal')">
{{ $t("main.chargingPlan.goal") }}
</label>
</div>
<div class="col-7 col-lg-3 mb-2 mb-lg-0">
<select
:id="formId('goal')"
v-model="selectedSoc"
class="form-select mx-0"
data-testid="plan-soc"
@change="update"
>
<option v-for="opt in socOptions" :key="opt.value" :value="opt.value">
{{ opt.name }}
</option>
</select>
</div>
<div class="col-5 d-lg-none col-form-label">
<label :for="formId('active')">
{{ $t("main.chargingPlan.active") }}
</label>
</div>
<div class="col-1 d-flex align-items-center justify-content-start">
<div class="form-check form-switch">
<input
:id="formId('active')"
class="form-check-input"
v-model="selectedActive"
type="checkbox"
role="switch"
data-testid="plan-active"
:checked="selectedActive"
@change="update"
/>
</div>
</div>
<div class="col-1 mx-auto d-flex align-items-center justify-content-start">
<button
type="button"
class="btn btn-sm btn-outline-secondary border-0"
data-testid="plan-delete"
@click="$emit('repetitive-plan-removed', id)"
>
<shopicon-regular-trash size="s" class="flex-shrink-0"></shopicon-regular-trash>
</button>
</div>
</div>
</div>
</template>

<script>
import "@h2d2/shopicons/es/regular/trash";
import { distanceUnit } from "../units";
import MultiSelect from "./MultiSelect.vue";

import formatter from "../mixins/formatter";

export default {
name: "ChargingPlanRepetitiveSettingsEntry",
components: {
MultiSelect,
},
mixins: [formatter],
props: {
id: Number,
weekdays: { type: Array, default: () => [] },
time: String,
soc: Number,
active: Boolean,
socBasedPlanning: Boolean,
Maschga marked this conversation as resolved.
Show resolved Hide resolved
rangePerSoc: Number,
},
emits: ["repetitive-plan-updated", "repetitive-plan-removed"],
data: function () {
return {
selectedWeekdays: this.weekdays,
selectedTime: this.time,
selectedSoc: this.soc,
selectedActive: this.active,
};
},
computed: {
weekdaysLabel: function () {
return this.getShortenedWeekdaysLabel(this.selectedWeekdays);
},
socOptions: function () {
// a list of entries from 5 to 100 with a step of 5
return Array.from(Array(20).keys())
.map((i) => 5 + i * 5)
.map(this.socOption);
},
},
methods: {
changeSelectedWeekdays: function (weekdays) {
this.selectedWeekdays = weekdays;
this.update();
},
formId: function (name) {
return `chargingplan-${this.id}-${name}`;
},
socOption: function (value) {
const name = this.fmtSocOption(value, this.rangePerSoc, distanceUnit());
return { value, name };
},
dayOptions: function () {
return this.WEEKDAYS.map((weekday, index) => {
return {
name: this.$t(`main.chargingPlan.${weekday}`),
value: index,
};
});
},
update: function () {
this.$emit("repetitive-plan-updated", {
id: this.id,
weekdays: this.selectedWeekdays,
time: this.selectedTime,
soc: this.selectedSoc,
active: this.selectedActive,
});
},
},
};
</script>
40 changes: 28 additions & 12 deletions assets/js/components/ChargingPlanSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,27 @@
<div class="mt-4">
<div class="form-group d-lg-flex align-items-baseline justify-content-between">
<div class="container px-0 mb-3">
<ChargingPlanSettingsEntry
<ChargingPlanStaticSettingsEntry
:id="`${id}_0`"
class="mb-2"
v-bind="plans[0] || {}"
:capacity="capacity"
:range-per-soc="rangePerSoc"
:soc-per-kwh="socPerKwh"
:soc-based-planning="socBasedPlanning"
@plan-updated="(data) => updatePlan({ index: 0, ...data })"
@plan-removed="() => removePlan(0)"
@static-plan-updated="(data) => updateStaticPlan({ index: 0, ...data })"
@static-plan-removed="() => removeStaticPlan(0)"
@plan-preview="previewPlan"
/>
<div v-if="socBasedPlanning">
<hr class="w-75 mx-auto mt-5" />
<h5>
<div class="inner" data-testid="plan-preview-title">
{{ $t("main.chargingPlan.repetitivePlan") }}
</div>
</h5>
<ChargingPlanRepetitiveSettingsEntries :id="id" :rangePerSoc="rangePerSoc" />
</div>
</div>
</div>
<ChargingPlanWarnings v-bind="chargingPlanWarningsProps" class="mb-4" />
Expand All @@ -28,8 +37,10 @@
</template>

<script>
import "@h2d2/shopicons/es/regular/plus";
import ChargingPlanPreview from "./ChargingPlanPreview.vue";
import ChargingPlanSettingsEntry from "./ChargingPlanSettingsEntry.vue";
import ChargingPlanStaticSettingsEntry from "./ChargingPlanStaticSettingsEntry.vue";
import ChargingPlanRepetitiveSettingsEntries from "./ChargingPlanRepetitiveSettingsEntries.vue";
import ChargingPlanWarnings from "./ChargingPlanWarnings.vue";
import formatter from "../mixins/formatter";
import collector from "../mixins/collector";
Expand All @@ -41,7 +52,12 @@ const LAST_TARGET_TIME_KEY = "last_target_time";

export default {
name: "ChargingPlanSettings",
components: { ChargingPlanPreview, ChargingPlanSettingsEntry, ChargingPlanWarnings },
components: {
ChargingPlanPreview,
ChargingPlanStaticSettingsEntry,
ChargingPlanRepetitiveSettingsEntries,
ChargingPlanWarnings,
},
mixins: [formatter, collector],
props: {
id: [String, Number],
Expand All @@ -63,7 +79,7 @@ export default {
vehicleLimitSoc: Number,
planOverrun: Number,
},
emits: ["plan-removed", "plan-updated"],
emits: ["static-plan-removed", "static-plan-updated"],
data: function () {
return {
tariff: {},
Expand Down Expand Up @@ -160,18 +176,18 @@ export default {
}
return target;
},
addPlan: function () {
this.$emit("plan-updated", {
addStaticPlan: function () {
this.$emit("static-plan-updated", {
time: this.defaultDate(),
soc: 100,
energy: this.capacity || 10,
});
},
removePlan: function (index) {
this.$emit("plan-removed", index);
removeStaticPlan: function (index) {
this.$emit("static-plan-removed", index);
},
updatePlan: function (data) {
this.$emit("plan-updated", data);
updateStaticPlan: function (data) {
this.$emit("static-plan-updated", data);
},
previewPlan: function (data) {
this.fetchPlanDebounced(data);
Expand Down
Loading
Loading