diff --git a/assets/css/app.css b/assets/css/app.css
index 0f3da24732..1ce4a3fa56 100644
--- a/assets/css/app.css
+++ b/assets/css/app.css
@@ -855,6 +855,13 @@ input[list]::-webkit-list-button {
}
}
+html.embedded {
+ --safe-area-inset-top: 0px;
+ --safe-area-inset-bottom: 0px;
+ --safe-area-inset-left: 0px;
+ --safe-area-inset-right: 0px;
+}
+
html.app .hide-in-app {
display: none !important;
}
diff --git a/assets/js/components/Config/CircuitsLegacyModal.vue b/assets/js/components/Config/CircuitsLegacyModal.vue
index 15b2142c78..f062f5e63c 100644
--- a/assets/js/components/Config/CircuitsLegacyModal.vue
+++ b/assets/js/components/Config/CircuitsLegacyModal.vue
@@ -4,7 +4,7 @@
name="circuitslegacy"
:title="`${$t('config.circuits.title')} (${$t('config.general.legacy')})`"
:description="$t('config.circuits.description')"
- docs="/reference/configuration/circuits"
+ docs="/features/loadmanagement"
:defaultYaml="defaultYaml"
endpoint="/config/circuits"
removeKey="circuits"
diff --git a/assets/js/components/Config/DeviceTags.vue b/assets/js/components/Config/DeviceTags.vue
index 66c04692bf..a47055e844 100644
--- a/assets/js/components/Config/DeviceTags.vue
+++ b/assets/js/components/Config/DeviceTags.vue
@@ -72,7 +72,7 @@ import TariffChart from "../Tariff/TariffChart.vue";
import { generateRateSlots, calculateCostRange } from "@/utils/tariffSlots";
import { distanceValue, distanceUnit } from "@/units";
-const HIDDEN_TAGS = ["icon", "heating", "integratedDevice", "continuous"];
+const HIDDEN_TAGS = ["icon", "heating", "integratedDevice", "continuous", "demandPredictor"];
const PHASE_TAGS = ["phaseCurrents", "phaseVoltages", "phasePowers"];
diff --git a/assets/js/components/Config/FormRow.vue b/assets/js/components/Config/FormRow.vue
index 66c8e549d3..13844dbec4 100644
--- a/assets/js/components/Config/FormRow.vue
+++ b/assets/js/components/Config/FormRow.vue
@@ -29,6 +29,7 @@
{{ $t("config.general.docsLink") }}
+
{{ $t("config.form.danger") }}: {{ danger }}
diff --git a/assets/js/components/Config/LoadpointModal.vue b/assets/js/components/Config/LoadpointModal.vue
index 7b935b43b3..e7046c0a78 100644
--- a/assets/js/components/Config/LoadpointModal.vue
+++ b/assets/js/components/Config/LoadpointModal.vue
@@ -447,7 +447,51 @@
-
{{ $t("config.loadpoint.temperatureRangeTitle") }}
+
{{ $t("config.loadpoint.demandPredictor.title") }}
+
+ {{ $t("config.loadpoint.demandPredictor.description") }}
+
+
+
+
+
+
+
+
+
+
+
{{ $t("config.loadpoint.temperatureRangeTitle") }}
{{ $t("config.loadpoint.temperatureRangeHelp") }}
@@ -568,10 +612,13 @@ import {
type VehicleOption,
type ConfigCircuit,
type ConfigLoadpoint,
+ type DemandPredictor,
} from "@/types/evcc";
const nsPerMin = 60 * 1e9;
+const DEMAND_PREDICTORS: DemandPredictor[] = ["daily", "weekday", "temperature"];
+
const { OFF, SMART, NOW } = CHARGE_MODE;
const defaultValues = {
@@ -627,6 +674,7 @@ export default {
},
voltage: Number,
gridConnection: String as PropType
,
+ temperatureTariffConfigured: Boolean,
},
emits: ["changed", "dismissed", "disable"],
data() {
@@ -645,6 +693,44 @@ export default {
id(): number | undefined {
return getModal("loadpoint")?.id;
},
+ defaultDemandPredictor(): DemandPredictor {
+ return this.chargerStatus?.demandPredictor?.value ?? "daily";
+ },
+ demandPredictorGroups() {
+ const t = (key: string) => this.$t(`config.loadpoint.demandPredictor.${key}`);
+ const label = (profile: DemandPredictor) => t(`${profile}.label`);
+ return [
+ {
+ label: t("defaultGroup"),
+ options: [{ value: "", label: label(this.defaultDemandPredictor) }],
+ },
+ {
+ label: t("profilesGroup"),
+ options: DEMAND_PREDICTORS.map((value) => ({ value, label: label(value) })),
+ },
+ ];
+ },
+ demandPredictorSelection: {
+ get(): string {
+ return this.values.demandPredictor ?? "";
+ },
+ set(value: string) {
+ this.values.demandPredictor = (value as DemandPredictor) || undefined;
+ },
+ },
+ activeDemandPredictor(): DemandPredictor {
+ return this.values.demandPredictor || this.defaultDemandPredictor;
+ },
+ demandPredictorHelp(): string {
+ return this.$t(
+ `config.loadpoint.demandPredictor.${this.activeDemandPredictor}.description`
+ );
+ },
+ temperatureTariffMissing(): boolean {
+ return (
+ this.activeDemandPredictor === "temperature" && !this.temperatureTariffConfigured
+ );
+ },
thresholdsSet(): boolean {
const { enable, disable } = this.values.thresholds;
return !!(enable.threshold || disable.threshold);
@@ -1020,6 +1106,9 @@ export default {
this.values.charger = "";
}
},
+ addTemperatureTariff() {
+ openModal("tariff", { type: "temperature" });
+ },
async editMeter() {
const meter = this.meters.find((m) => m.name === this.values.meter);
const result = await openModal("meter", {
diff --git a/assets/js/components/Config/Markdown.test.ts b/assets/js/components/Config/Markdown.test.ts
new file mode 100644
index 0000000000..2ef742d658
--- /dev/null
+++ b/assets/js/components/Config/Markdown.test.ts
@@ -0,0 +1,35 @@
+import { mount } from "@vue/test-utils";
+import { describe, expect, test } from "vite-plus/test";
+import Markdown from "./Markdown.vue";
+
+const render = (markdown: string) => mount(Markdown, { props: { markdown } }).html();
+
+describe("Markdown", () => {
+ test("keeps underscores inside words as text", () => {
+ const html = render("charge_before_export and a_b_c stay flat");
+ expect(html).toContain("charge_before_export and a_b_c stay flat");
+ expect(html).not.toContain("");
+ });
+
+ test("keeps underscores in firmware versions", () => {
+ const html = render("requires M3W_3.11STP or M3W_4.03PTB");
+ expect(html).toContain("requires M3W_3.11STP or M3W_4.03PTB");
+ expect(html).not.toContain("");
+ });
+
+ test("opens links in new window", () => {
+ expect(render("[docs](https://docs.evcc.io)")).toContain(
+ 'docs'
+ );
+ });
+
+ test("still emphasises whole words", () => {
+ expect(render("so _early_ and __bold__")).toContain(
+ "so early and bold"
+ );
+ });
+
+ test("keeps underscores inside code", () => {
+ expect(render("`charge_before_export`")).toContain("charge_before_export");
+ });
+});
diff --git a/assets/js/components/Config/Markdown.vue b/assets/js/components/Config/Markdown.vue
index 88514bec0e..3fa3cbc940 100644
--- a/assets/js/components/Config/Markdown.vue
+++ b/assets/js/components/Config/Markdown.vue
@@ -4,7 +4,7 @@