Skip to content

Commit

Permalink
Non-solar use cases: make grid meter optional (evcc-io#14341)
Browse files Browse the repository at this point in the history
  • Loading branch information
naltatis authored Jun 22, 2024
1 parent 598bf55 commit 280976c
Show file tree
Hide file tree
Showing 20 changed files with 275 additions and 49 deletions.
14 changes: 12 additions & 2 deletions assets/js/components/Energyflow/Energyflow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,19 @@
>
<div class="d-flex justify-content-between align-items-end mb-4">
<h3 class="m-0">In</h3>
<span class="fw-bold">
<span v-if="pvPossible" class="fw-bold">
<AnimatedNumber :to="inPower" :format="kw" />
</span>
</div>
<div>
<EnergyflowEntry
v-if="pvPossible"
:name="$t('main.energyflow.pvProduction')"
icon="sun"
:power="pvProduction"
:powerTooltip="pvTooltip"
:powerInKw="powerInKw"
data-testid="energyflow-entry-production"
/>
<EnergyflowEntry
v-if="batteryConfigured"
Expand Down Expand Up @@ -106,19 +108,21 @@
>
<div class="d-flex justify-content-between align-items-end mb-4">
<h3 class="m-0">Out</h3>
<span class="fw-bold">
<span v-if="pvPossible" class="fw-bold">
<AnimatedNumber :to="outPower" :format="kw" />
</span>
</div>
<div>
<EnergyflowEntry
v-if="pvPossible"
:name="$t('main.energyflow.homePower')"
icon="home"
:power="homePower"
:powerInKw="powerInKw"
:details="detailsValue(tariffPriceHome, tariffCo2Home)"
:detailsFmt="detailsFmt"
:detailsTooltip="detailsTooltip(tariffPriceHome, tariffCo2Home)"
data-testid="energyflow-entry-home"
/>
<EnergyflowEntry
:name="
Expand All @@ -139,6 +143,7 @@
:detailsTooltip="
detailsTooltip(tariffPriceLoadpoints, tariffCo2Loadpoints)
"
data-testid="energyflow-entry-loadpoints"
/>
<EnergyflowEntry
v-if="batteryConfigured"
Expand All @@ -153,13 +158,15 @@
@details-clicked="openBatterySettingsModal"
/>
<EnergyflowEntry
v-if="pvPossible"
:name="$t('main.energyflow.pvExport')"
icon="powersupply"
:power="pvExport"
:powerInKw="powerInKw"
:details="detailsValue(-tariffFeedIn)"
:detailsFmt="detailsFmt"
:detailsTooltip="detailsTooltip(-tariffFeedIn)"
data-testid="energyflow-entry-gridexport"
/>
</div>
</div>
Expand Down Expand Up @@ -293,6 +300,9 @@ export default {
co2Available() {
return this.smartCostType === CO2_TYPE;
},
pvPossible() {
return this.pvConfigured || this.gridConfigured;
},
},
mounted() {
window.addEventListener("resize", this.updateHeight);
Expand Down
7 changes: 2 additions & 5 deletions assets/js/components/Energyflow/Visualization.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,8 @@
:format="fmtBarValue"
/>
</div>
<div
v-if="totalAdjusted <= 0"
class="site-progress-bar bg-light border no-wrap w-100 text-dark"
>
<span>{{ $t("main.energyflow.noEnergy") }}</span>
<div v-if="totalAdjusted <= 0" class="site-progress-bar w-100 grid-import">
<span>{{ fmtKw(0, false, true) }}</span>
</div>
</div>
<div class="label-scale d-flex">
Expand Down
14 changes: 13 additions & 1 deletion assets/js/components/Loadpoint.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<LoadpointSettingsButton :id="id" class="d-block d-sm-none" />
</div>
<div class="mb-3 d-flex align-items-center">
<Mode class="flex-grow-1" :mode="mode" @updated="setTargetMode" />
<Mode class="flex-grow-1" v-bind="modeProps" @updated="setTargetMode" />
<LoadpointSettingsButton :id="id" class="d-none d-sm-block ms-2" />
</div>
</div>
Expand Down Expand Up @@ -98,6 +98,7 @@ import LoadpointSettingsButton from "./LoadpointSettingsButton.vue";
import LoadpointSettingsModal from "./LoadpointSettingsModal.vue";
import VehicleIcon from "./VehicleIcon";
import LoadpointSessionInfo from "./LoadpointSessionInfo.vue";
import smartCostAvailable from "../utils/smartCostAvailable";
export default {
name: "Loadpoint",
Expand Down Expand Up @@ -189,6 +190,8 @@ export default {
tariffCo2: Number,
currency: String,
multipleLoadpoints: Boolean,
gridConfigured: Boolean,
pvConfigured: Boolean,
},
data() {
return {
Expand Down Expand Up @@ -218,6 +221,9 @@ export default {
phasesProps: function () {
return this.collectProps(Phases);
},
modeProps: function () {
return this.collectProps(Mode);
},
sessionInfoProps: function () {
return this.collectProps(LoadpointSessionInfo);
},
Expand Down Expand Up @@ -251,6 +257,12 @@ export default {
socBasedPlanning: function () {
return this.socBasedCharging && this.vehicle?.capacity > 0;
},
pvPossible: function () {
return this.pvConfigured || this.gridConfigured;
},
hasSmartCost: function () {
return smartCostAvailable(this.smartCostType);
},
},
watch: {
phaseRemaining() {
Expand Down
4 changes: 4 additions & 0 deletions assets/js/components/Loadpoints.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
:tariffCo2="tariffCo2"
:currency="currency"
:multiple-loadpoints="loadpoints.length > 1"
:grid-configured="gridConfigured"
:pv-configured="pvConfigured"
class="h-100"
:class="{ 'loadpoint-unselected': !selected(index) }"
@click="scrollTo(index)"
Expand Down Expand Up @@ -67,6 +69,8 @@ export default {
tariffGrid: Number,
tariffCo2: Number,
currency: String,
gridConfigured: Boolean,
pvConfigured: Boolean,
},
data() {
return { selectedIndex: 0, snapTimeout: null };
Expand Down
29 changes: 23 additions & 6 deletions assets/js/components/Mode.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="mode-group border d-inline-flex" role="group">
<div class="mode-group border d-inline-flex" role="group" data-testid="mode">
<button
v-for="m in modes"
:key="m"
Expand All @@ -8,7 +8,7 @@
:class="{ active: isActive(m) }"
@click="setTargetMode(m)"
>
{{ $t(`main.mode.${m}`) }}
{{ label(m) }}
</button>
</div>
</template>
Expand All @@ -18,14 +18,31 @@ export default {
name: "Mode",
props: {
mode: String,
pvPossible: Boolean,
hasSmartCost: Boolean,
},
emits: ["updated"],
data() {
return {
modes: ["off", "pv", "minpv", "now"],
};
computed: {
modes: function () {
if (this.pvPossible) {
return ["off", "pv", "minpv", "now"];
}
if (this.hasSmartCost) {
return ["off", "pv", "now"];
}
return ["off", "now"];
},
},
methods: {
label: function (mode) {
// rename pv mode to smart for non-pv and dynamic tariffs scenarios
// TODO: rollout smart name for everyting later
if (mode === "pv" && !this.pvPossible && this.hasSmartCost) {
return this.$t("main.mode.smart");
}
return this.$t(`main.mode.${mode}`);
},
isActive: function (mode) {
return this.mode === mode;
},
Expand Down
2 changes: 2 additions & 0 deletions assets/js/components/Site.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
:tariffGrid="tariffGrid"
:tariffCo2="tariffCo2"
:currency="currency"
:gridConfigured="gridConfigured"
:pvConfigured="pvConfigured"
/>
<Footer v-bind="footer"></Footer>
</div>
Expand Down
15 changes: 10 additions & 5 deletions assets/js/views/Config.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
</p>
<p class="mb-1"><strong>Missing features</strong></p>
<ul>
<li>grid meter</li>
<li>aux meters</li>
<li>loadpoints and chargers</li>
<li>custom/plugin meters and vehicles</li>
Expand All @@ -41,16 +40,22 @@
<ul class="p-0 config-list">
<DeviceCard
:name="$t('config.grid.title')"
:editable="!!gridMeter?.id"
:editable="!gridMeter || !!gridMeter.id"
:error="deviceError('meter', gridMeter?.name)"
data-testid="grid"
@edit="editMeter(gridMeter.id, 'grid')"
@edit="gridMeter?.id ? editMeter(gridMeter.id, 'pv') : addMeter('grid')"
>
<template #icon>
<shopicon-regular-powersupply></shopicon-regular-powersupply>
</template>
<template #tags>
<DeviceTags :tags="deviceTags('meter', gridMeter?.name)" />
<DeviceTags
:tags="
gridMeter
? deviceTags('meter', gridMeter.name)
: { configured: { value: false } }
"
/>
</template>
</DeviceCard>
<DeviceCard
Expand Down Expand Up @@ -547,7 +552,7 @@ export default {
removeMeterFromSite(type, name) {
if (type === "grid") {
this.site.grid = "";
} else {
} else if (this.site[type]) {
this.site[type] = this.site[type].filter((i) => i !== name);
}
this.saveSite(type);
Expand Down
6 changes: 0 additions & 6 deletions core/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package core

import (
"context"
"errors"
"fmt"
"math"
"strings"
Expand Down Expand Up @@ -215,11 +214,6 @@ func (site *Site) Boot(log *util.Logger, loadpoints []*Loadpoint, tariffs *tarif
site.auxMeters = append(site.auxMeters, dev.Instance())
}

// configure meter from references
if site.gridMeter == nil && len(site.pvMeters) == 0 {
return errors.New("missing either grid or pv meter")
}

// revert battery mode on shutdown
shutdown.Register(func() {
if mode := site.GetBatteryMode(); batteryModeModified(mode) {
Expand Down
1 change: 1 addition & 0 deletions i18n/de.toml
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ minpv = "Min+PV"
now = "Schnell"
off = "Aus"
pv = "PV"
smart = "Smart"

[main.provider]
login = "anmelden"
Expand Down
1 change: 1 addition & 0 deletions i18n/en.toml
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ minpv = "Min+Solar"
now = "Fast"
off = "Off"
pv = "Solar"
smart = "Smart"

[main.provider]
login = "log in"
Expand Down
8 changes: 4 additions & 4 deletions tests/config-meters.spec.js → tests/config-battery.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { test, expect } from "@playwright/test";
import { start, stop, restart, baseUrl } from "./evcc";
import { startSimulator, stopSimulator, simulatorUrl, simulatorHost } from "./simulator";

const CONFIG_EMPTY = "config-empty.evcc.yaml";
const CONFIG_GRID_ONLY = "config-grid-only.evcc.yaml";

test.use({ baseURL: baseUrl() });

test.beforeAll(async () => {
await start(CONFIG_EMPTY, "password.sql");
await start(CONFIG_GRID_ONLY, "password.sql");
await startSimulator();
});
test.afterAll(async () => {
Expand All @@ -29,7 +29,7 @@ async function enableExperimental(page) {
await page.getByRole("button", { name: "Close" }).click();
}

test.describe("meters", async () => {
test.describe("battery meter", async () => {
test("create, edit and remove battery meter", async ({ page }) => {
// setup test data for mock openems api
await page.goto(simulatorUrl());
Expand Down Expand Up @@ -71,7 +71,7 @@ test.describe("meters", async () => {
await expect(battery.getByTestId("device-tag-capacity")).toContainText("20.0 kWh");

// restart and check in main ui
await restart(CONFIG_EMPTY);
await restart(CONFIG_GRID_ONLY);
await page.goto("/");
await page.getByTestId("visualization").click();
await expect(page.getByTestId("energyflow")).toContainText("Battery charging75%2.5 kW");
Expand Down
10 changes: 0 additions & 10 deletions tests/config-empty.evcc.yaml
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
site:
title: Hello World
meters:
grid: grid

meters:
- name: grid
type: custom
power:
source: js
script: |
1000

loadpoints:
- title: Carport
Expand Down
34 changes: 34 additions & 0 deletions tests/config-grid-only.evcc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
site:
title: Hello World
meters:
grid: grid

meters:
- name: grid
type: custom
power:
source: js
script: |
1000
loadpoints:
- title: Carport
charger: charger

chargers:
- name: charger
type: custom
enable:
source: js
script:
enabled:
source: js
script: |
false
status:
source: js
script: |
"B"
maxcurrent:
source: js
script:
Loading

0 comments on commit 280976c

Please sign in to comment.