Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
5 changes: 5 additions & 0 deletions assets/js/components/Battery/BatterySettingsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@
v-if="isModalVisible"
v-show="gridTabActive"
:current-limit="batteryGridChargeLimit"
:last-limit="lastSmartCostLimit"
:smart-cost-type="smartCostType"
:currency="currency"
:tariff="gridChargeTariff"
Expand All @@ -263,6 +264,7 @@ import GenericModal from "../Helper/GenericModal.vue";
import formatter, { POWER_UNIT } from "@/mixins/formatter";
import collector from "@/mixins/collector.js";
import api from "@/api";
import settings from "@/settings";
import { defineComponent, type PropType } from "vue";
import type { BatteryMeter, SelectOption, CURRENCY, Forecast } from "@/types/evcc";
import { SMART_COST_TYPE } from "@/types/evcc";
Expand Down Expand Up @@ -389,6 +391,9 @@ export default defineComponent({
return `${name}${formattedEnergy}${formattedSoc}`;
});
},
lastSmartCostLimit() {
return settings.lastBatterySmartCostLimit;
},
},
watch: {
prioritySoc(soc) {
Expand Down
2 changes: 2 additions & 0 deletions assets/js/components/Loadpoints/Loadpoint.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
/>
</div>
</div>
<LoadpointSettingsModal

Check failure on line 26 in assets/js/components/Loadpoints/Loadpoint.vue

View workflow job for this annotation

GitHub Actions / UI

Argument of type '{ onBatteryboostUpdated: any; onPhasesconfiguredUpdated: any; onMincurrentUpdated: any; onMaxcurrentUpdated: any; }' is not assignable to parameter of type 'Partial<{} & { batteryBoost: boolean; maxCurrent: number; minCurrent: number; phasesConfigured: number; chargerPhases1p3p: boolean; chargerSinglePhase: boolean; smartCostLimit: number | null; ... 4 more ...; smartFeedInPriorityAvailable: boolean; }> & Omit<...> & Record<...>'.
v-bind="settingsModal"
@maxcurrent-updated="setMaxCurrent"
@mincurrent-updated="setMinCurrent"
Expand Down Expand Up @@ -224,6 +224,8 @@
gridConfigured: Boolean,
pvConfigured: Boolean,
forecast: Object as PropType<Forecast>,
lastSmartCostLimit: Number,
lastSmartFeedInPriorityLimit: Number,
},
data() {
return {
Expand Down
10 changes: 7 additions & 3 deletions assets/js/components/Loadpoints/SettingsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,21 @@
<div class="container">
<SmartCostLimit
:current-limit="smartCostLimit"
:last-limit="lastSmartCostLimit"
:smart-cost-type="smartCostType"
:currency="currency"
is-loadpoint
:loadpoint-id="Number(loadpointId)"
:loadpoint-id="loadpointId"
:multiple-loadpoints="multipleLoadpoints"
:possible="smartCostAvailable"
:tariff="forecast?.planner"
class="mt-2 mb-4"
/>
<SmartFeedInPriority
:current-limit="smartFeedInPriorityLimit"
:last-limit="lastSmartFeedInPriorityLimit"
:currency="currency"
:loadpoint-id="Number(loadpointId)"
:loadpoint-id="loadpointId"
:multiple-loadpoints="multipleLoadpoints"
:possible="smartFeedInPriorityAvailable"
:tariff="forecast?.feedin"
Expand Down Expand Up @@ -161,7 +163,7 @@ export default defineComponent({
},
mixins: [formatter, collector],
props: {
id: [String, Number],
id: { type: String, required: true },
phasesConfigured: { type: Number, default: 0 },
chargerPhases1p3p: Boolean,
chargerSinglePhase: Boolean,
Expand All @@ -181,6 +183,8 @@ export default defineComponent({
currency: String as PropType<CURRENCY>,
multipleLoadpoints: Boolean,
forecast: Object as PropType<Forecast>,
lastSmartCostLimit: Number,
lastSmartFeedInPriorityLimit: Number,
},
emits: [
"phasesconfigured-updated",
Expand Down
23 changes: 18 additions & 5 deletions assets/js/components/Tariff/SmartCostLimit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<SmartTariffBase
v-bind="labels"
:current-limit="currentLimit"
:last-limit="lastLimit"
:is-co2="isCo2"
:currency="currency"
:apply-all="multipleLoadpoints && isLoadpoint"
Expand All @@ -21,6 +22,8 @@
import SmartTariffBase from "./SmartTariffBase.vue";
import { defineComponent, type PropType } from "vue";
import api from "@/api";
import { setLoadpointLastSmartCostLimit } from "@/uiLoadpoints";
import settings from "@/settings";
import { type CURRENCY, SMART_COST_TYPE } from "@/types/evcc";
import { type ForecastSlot } from "../Forecast/types";

Expand All @@ -36,8 +39,9 @@ export default defineComponent({
currency: String as PropType<CURRENCY>,
multipleLoadpoints: Boolean,
isLoadpoint: Boolean,
loadpointId: Number,
loadpointId: String,
possible: Boolean,
lastLimit: Number,
tariff: Array as PropType<ForecastSlot[]>,
},
computed: {
Expand Down Expand Up @@ -69,18 +73,27 @@ export default defineComponent({
// Smart cost: charge when costs are below or equal to limit
return value <= this.currentLimit;
},
async saveLimit(limit: string) {
async saveLimit(limit: number) {
// save last selected value to be suggest again when reactivating limit
this.saveLastLimit(limit);

const url = this.isLoadpoint
? `loadpoints/${this.loadpointId}/smartcostlimit`
: "batterygridchargelimit";

if (limit === "null") {
await api.delete(url);
await api.post(`${url}/${encodeURIComponent(limit)}`);
},
saveLastLimit(limit: number) {
if (this.isLoadpoint) {
setLoadpointLastSmartCostLimit(this.loadpointId!, limit);
} else {
await api.post(`${url}/${encodeURIComponent(limit)}`);
settings.lastBatterySmartCostLimit = limit;
}
},
async deleteLimit() {
// save last selected value to be suggest again when reactivating limit
this.saveLastLimit(this.currentLimit || 0);

const url = this.isLoadpoint
? `loadpoints/${this.loadpointId}/smartcostlimit`
: "batterygridchargelimit";
Expand Down
23 changes: 15 additions & 8 deletions assets/js/components/Tariff/SmartFeedInPriority.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<SmartTariffBase
v-bind="labels"
:current-limit="currentLimit"
:last-limit="lastLimit"
:currency="currency"
:apply-all="multipleLoadpoints"
:possible="possible"
Expand All @@ -23,6 +24,7 @@ import { defineComponent, type PropType } from "vue";
import SmartTariffBase from "./SmartTariffBase.vue";
import api from "@/api";
import { type CURRENCY } from "@/types/evcc";
import { setLoadpointLastSmartFeedInPriorityLimit } from "@/uiLoadpoints";

export default defineComponent({
name: "SmartFeedInPriority",
Expand All @@ -32,8 +34,9 @@ export default defineComponent({
type: [Number, null] as PropType<number | null>,
required: true,
},
lastLimit: Number,
currency: String as PropType<CURRENCY>,
loadpointId: { type: Number, required: true },
loadpointId: { type: String, required: true },
multipleLoadpoints: Boolean,
possible: Boolean,
tariff: Array,
Expand Down Expand Up @@ -62,16 +65,20 @@ export default defineComponent({
// Smart feed-in priority: pause when rates are above or equal to limit
return value >= this.currentLimit;
},
async saveLimit(limit: string) {
const url = `loadpoints/${this.loadpointId}/smartfeedinprioritylimit`;
async saveLimit(limit: number) {
// save last selected value to be suggest again when reactivating limit
this.saveLastLimit(limit);

if (limit === "null") {
await api.delete(url);
} else {
await api.post(`${url}/${encodeURIComponent(limit)}`);
}
const url = `loadpoints/${this.loadpointId}/smartfeedinprioritylimit`;
await api.post(`${url}/${encodeURIComponent(limit)}`);
},
saveLastLimit(limit: number) {
setLoadpointLastSmartFeedInPriorityLimit(this.loadpointId, limit);
},
async deleteLimit() {
// save last selected value to be suggest again when reactivating limit
this.saveLastLimit(this.currentLimit || 0);

const url = `loadpoints/${this.loadpointId}/smartfeedinprioritylimit`;
await api.delete(url);
},
Expand Down
78 changes: 57 additions & 21 deletions assets/js/components/Tariff/SmartTariffBase.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,30 @@
{{ limitLabel }}
</label>
<div class="col-sm-8 col-lg-4 pe-0">
<select
:id="formId"
v-model.number="selectedLimit"
class="form-select form-select-sm mb-1"
@change="changeLimit"
>
<option value="null">{{ $t("smartCost.none") }}</option>
<option v-for="{ value, name } in limitOptions" :key="value" :value="value">
{{ name }}
</option>
</select>
<div class="input-group input-group-sm mb-1">
<div class="input-group-text">
<input
:id="formId + 'Active'"
:checked="active"
class="form-check-input mt-0"
type="checkbox"
role="switch"
aria-label="Checkbox for following text input"
@change="toggleActive"
/>
</div>
<select
:id="formId"
v-model.number="selectedLimit"
class="form-select form-select-sm"
:disabled="!active"
@change="changeLimit"
>
<option v-for="{ value, name } in limitOptions" :key="value" :value="value">
{{ name }}
</option>
</select>
</div>
</div>
<div
v-if="applyToAllVisible"
Expand Down Expand Up @@ -91,6 +104,7 @@ export default defineComponent({
type: [Number, null] as PropType<number | null>,
required: true,
},
lastLimit: { type: Number, default: 0 },
isCo2: Boolean,
currency: String as PropType<CURRENCY>,
applyAll: Boolean,
Expand Down Expand Up @@ -118,6 +132,7 @@ export default defineComponent({
selectedLimit: null as number | null,
activeIndex: null as number | null,
applyToAllVisible: false,
active: false,
};
},
computed: {
Expand All @@ -131,6 +146,9 @@ export default defineComponent({
}
return [];
},
activeLabel() {
return this.active ? this.$t("smartCost.active") : this.$t("smartCost.inactive");
},
limitOptions(): SelectOption<number>[] {
const { max } = this.optionsCostRange;

Expand Down Expand Up @@ -275,17 +293,26 @@ export default defineComponent({
},
},
watch: {
currentLimit(limit) {
this.selectedLimit = this.roundLimit(limit);
currentLimit() {
this.initLimit();
},
},
mounted() {
this.selectedLimit = this.roundLimit(this.currentLimit);
this.initLimit();
},
methods: {
roundLimit(limit: number | null): number | null {
return limit === null ? null : Math.round(limit * 1000) / 1000;
},
initLimit() {
if (this.currentLimit === null) {
this.active = false;
this.selectedLimit = this.lastLimit;
} else {
this.active = true;
this.selectedLimit = this.roundLimit(this.currentLimit);
}
},
formatLimit(limit: number | null): string {
if (limit === null) {
return this.$t("smartCost.none");
Expand Down Expand Up @@ -338,18 +365,27 @@ export default defineComponent({
// 3 decimal precision
const valueRounded = Math.ceil(value * 1000) / 1000;
this.selectedLimit = valueRounded;
this.saveLimit(`${valueRounded}`);
this.active = true;
this.saveLimit(valueRounded);
}
},
changeLimit($event: Event) {
const value = ($event.target as HTMLSelectElement).value;
if (value === "null") {
this.resetLimit();
const value = parseFloat(($event.target as HTMLSelectElement).value);
this.saveLimit(value);
},
toggleActive($event: Event) {
const active = ($event.target as HTMLInputElement).checked;
if (active) {
this.saveLimit(this.lastLimit);
} else {
this.saveLimit(value);
this.resetLimit();
}
this.active = active;
if (this.applyAll) {
this.applyToAllVisible = true;
}
},
saveLimit(limit: string) {
saveLimit(limit: number) {
this.$emit("save-limit", limit);
if (this.applyAll) {
this.applyToAllVisible = true;
Expand All @@ -362,7 +398,7 @@ export default defineComponent({
}
},
applyToAll() {
this.$emit("apply-to-all", this.selectedLimit);
this.$emit("apply-to-all", this.currentLimit);
this.applyToAllVisible = false;
},
},
Expand Down
16 changes: 16 additions & 0 deletions assets/js/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const SAVINGS_REGION = "savings_region";
const SESSIONS_GROUP = "sessions_group";
const SESSIONS_TYPE = "sessions_type";
const SETTINGS_SOLAR_ADJUSTED = "settings_solar_adjusted";
const LAST_BATTERY_SMART_COST_LIMIT = "last_battery_smart_cost_limit";

function read(key: string) {
return window.localStorage[key];
Expand Down Expand Up @@ -48,6 +49,16 @@ function saveBool(key: string) {
};
}

function readNumber(key: string) {
return read(key) ? parseFloat(read(key)) : undefined;
}

function saveNumber(key: string) {
return (value: number | undefined) => {
save(key)(value ? value.toString() : null);
};
}

function readArray(key: string) {
const value = read(key);
return value ? value.split(",") : [];
Expand Down Expand Up @@ -83,6 +94,8 @@ export interface LoadpointSettings {
order?: number;
visible?: boolean;
info?: SessionInfoKey;
lastSmartCostLimit?: number;
lastSmartFeedInPriorityLimit?: number;
}

export interface Settings {
Expand All @@ -103,6 +116,7 @@ export interface Settings {
sessionsType: string;
solarAdjusted: boolean;
loadpoints: Record<string, LoadpointSettings>;
lastBatterySmartCostLimit: number | undefined;
}

const settings: Settings = reactive({
Expand All @@ -123,6 +137,7 @@ const settings: Settings = reactive({
sessionsType: read(SESSIONS_TYPE),
solarAdjusted: readBool(SETTINGS_SOLAR_ADJUSTED),
loadpoints: readJSON(LOADPOINTS),
lastBatterySmartCostLimit: readNumber(LAST_BATTERY_SMART_COST_LIMIT),
});

watch(() => settings.locale, save(SETTINGS_LOCALE));
Expand All @@ -142,6 +157,7 @@ watch(() => settings.sessionsGroup, save(SESSIONS_GROUP));
watch(() => settings.sessionsType, save(SESSIONS_TYPE));
watch(() => settings.solarAdjusted, saveBool(SETTINGS_SOLAR_ADJUSTED));
watch(() => settings.loadpoints, saveJSON(LOADPOINTS), { deep: true });
watch(() => settings.lastBatterySmartCostLimit, saveNumber(LAST_BATTERY_SMART_COST_LIMIT));

export default settings;

Expand Down
2 changes: 2 additions & 0 deletions assets/js/types/evcc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,8 @@ export interface UiLoadpoint extends Loadpoint {
icon: string;
order: number | null;
visible: boolean;
lastSmartCostLimit: number | undefined;
lastSmartFeedInPriorityLimit: number | undefined;
}

export enum THEME {
Expand Down
Loading
Loading