kW/W units; tighter icon threshold; ignore batterypower below 50W; revert navbar link; i18n number formats (#1352)

This commit is contained in:
Michael Geers 2021-08-14 08:56:34 +02:00 • committed by GitHub
parent 51824bb8a0
commit 1e223014cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 88 additions and 40 deletions

View file

@ -2,6 +2,14 @@
import { shallowMount } from "@vue/test-utils";
import Energyflow from "./Energyflow.vue";
const mocks = {
$t: (x) => x,
$n: function (value, options) {
const n = new Intl.NumberFormat("en-EN", options);
return n.format(value);
},
};
describe("Energyflow.vue", () => {
const defaultProps = {
gridConfigured: true,
@ -15,7 +23,7 @@ describe("Energyflow.vue", () => {
it("using pv and grid power", async () => {
const wrapper = shallowMount(Energyflow, {
mocks: { $t: (x) => x },
mocks,
propsData: { ...defaultProps, gridPower: 1000, pvPower: 4000 },
});
await wrapper.find(".energyflow").trigger("click");
@ -31,7 +39,7 @@ describe("Energyflow.vue", () => {
it("exporting all pv power, no usage", async () => {
const wrapper = shallowMount(Energyflow, {
mocks: { $t: (x) => x },
mocks,
propsData: { ...defaultProps, gridPower: -4000, pvPower: 4000 },
});
@ -48,7 +56,7 @@ describe("Energyflow.vue", () => {
it("more grid export than pv, grid value wins (invalid state)", async () => {
const wrapper = shallowMount(Energyflow, {
mocks: { $t: (x) => x },
mocks,
propsData: { ...defaultProps, gridPower: -4000, pvPower: 3000 },
});
@ -65,7 +73,7 @@ describe("Energyflow.vue", () => {
it("only grid usage, no pv, idleBattery", async () => {
const wrapper = shallowMount(Energyflow, {
mocks: { $t: (x) => x },
mocks,
propsData: {
...defaultProps,
gridPower: 360,
@ -77,23 +85,23 @@ describe("Energyflow.vue", () => {
await wrapper.find(".energyflow").trigger("click");
expect(wrapper.find("[data-test-grid-import]").text()).toMatch("0.4 kW");
expect(wrapper.find("[data-test-self-consumption]").text()).toMatch("0.0 kW");
expect(wrapper.find("[data-test-pv-export]").text()).toMatch("0.0 kW");
expect(wrapper.find("[data-test-grid-import]").text()).toMatch("360 W");
expect(wrapper.find("[data-test-self-consumption]").text()).toMatch("0 W");
expect(wrapper.find("[data-test-pv-export]").text()).toMatch("0 W");
expect(wrapper.find("[data-test-house-consumption]").text()).toMatch("0.4 kW");
expect(wrapper.find("[data-test-pv-production]").text()).toMatch("0.0 kW");
expect(wrapper.find("[data-test-house-consumption]").text()).toMatch("360 W");
expect(wrapper.find("[data-test-pv-production]").text()).toMatch("0 W");
expect(wrapper.find("[data-test-battery]").text()).toMatch("main.energyflow.battery");
});
it("grid and battery usage, no pv", async () => {
const wrapper = shallowMount(Energyflow, {
mocks: { $t: (x) => x },
mocks,
propsData: {
...defaultProps,
gridPower: 300,
batteryConfigured: true,
batteryPower: 200,
batteryPower: 234,
batterySoC: 77,
pvPower: 0,
},
@ -101,20 +109,20 @@ describe("Energyflow.vue", () => {
await wrapper.find(".energyflow").trigger("click");
expect(wrapper.find("[data-test-grid-import]").text()).toMatch("0.3 kW");
expect(wrapper.find("[data-test-self-consumption]").text()).toMatch("0.2 kW");
expect(wrapper.find("[data-test-pv-export]").text()).toMatch("0.0 kW");
expect(wrapper.find("[data-test-grid-import]").text()).toMatch("300 W");
expect(wrapper.find("[data-test-self-consumption]").text()).toMatch("234 W");
expect(wrapper.find("[data-test-pv-export]").text()).toMatch("0 W");
expect(wrapper.find("[data-test-house-consumption]").text()).toMatch("0.5 kW");
expect(wrapper.find("[data-test-pv-production]").text()).toMatch("0.0 kW");
expect(wrapper.find("[data-test-battery]").text()).toMatch("0.2 kW");
expect(wrapper.find("[data-test-house-consumption]").text()).toMatch("534 W");
expect(wrapper.find("[data-test-pv-production]").text()).toMatch("0 W");
expect(wrapper.find("[data-test-battery]").text()).toMatch("234 W");
expect(wrapper.find("[data-test-battery]").text()).toMatch("77%");
expect(wrapper.find("[data-test-battery]").text()).toMatch("main.energyflow.batteryDischarge");
});
it("battery charge, pv export", async () => {
const wrapper = shallowMount(Energyflow, {
mocks: { $t: (x) => x },
mocks,
propsData: {
...defaultProps,
gridPower: -2500,
@ -138,7 +146,7 @@ describe("Energyflow.vue", () => {
it("thresholds", async () => {
const wrapper = shallowMount(Energyflow, {
mocks: { $t: (x) => x },
mocks,
propsData: {
...defaultProps,
gridPower: 5555,

View file

@ -100,3 +100,15 @@ GridOnly.args = {
batterySoC: 0,
siteTitle: "Home",
};
export const LowEnergy = Template.bind({});
LowEnergy.args = {
gridConfigured: true,
pvConfigured: true,
batteryConfigured: true,
gridPower: -352,
pvPower: 710,
batteryPower: 86,
batterySoC: 55,
siteTitle: "Home",
};

View file

@ -29,6 +29,7 @@
:pvProduction="pvProduction"
:houseConsumption="houseConsumption"
:batterySoC="batterySoC"
:valuesInKw="valuesInKw"
/>
<div
class="col-12 col-sm-6 col-md-5 col-lg-3 col-xl-3 order-md-1 mt-2 mt-md-0"
@ -153,25 +154,32 @@ export default {
this.pvProduction + this.gridPower - this.batteryCharge
);
},
batteryPowerAdjusted: function () {
const batteryPowerThreshold = 50;
return Math.abs(this.batteryPower) < batteryPowerThreshold ? 0 : this.batteryPower;
},
batteryDischarge: function () {
return Math.max(0, this.batteryPower);
return Math.max(0, this.batteryPowerAdjusted);
},
batteryCharge: function () {
return Math.min(0, this.batteryPower) * -1;
return Math.min(0, this.batteryPowerAdjusted) * -1;
},
houseConsumption: function () {
return this.gridImport + this.pvConsumption + this.batteryDischarge;
return Math.max(0, this.gridImport + this.pvConsumption + this.batteryDischarge);
},
selfConsumption: function () {
return this.batteryDischarge + this.pvConsumption + this.batteryCharge;
return Math.max(0, this.batteryDischarge + this.pvConsumption + this.batteryCharge);
},
pvExport: function () {
return Math.min(0, this.gridPower) * -1;
return Math.max(0, this.gridPower * -1);
},
valuesInKw: function () {
return this.gridImport + this.selfConsumption + this.pvExport > 1000;
},
},
methods: {
kw: function (watt) {
return Math.max(0, watt / 1000).toFixed(1) + " kW";
return this.fmtKw(watt, this.valuesInKw);
},
toggleDetails() {
this.showDetails = !this.showDetails;

View file

@ -5,7 +5,7 @@
<div
class="label-bar label-bar--down"
:class="{
'label-bar--hide-icon': hideLabelIcon(batteryDischarge),
'label-bar--hide-icon': hideLabelIcon(batteryDischarge, 44),
'label-bar--hidden': !selfConsumptionAdjusted,
}"
:style="{ width: widthTotal(batteryDischarge) }"
@ -85,7 +85,7 @@
<div
class="label-bar label-bar--up"
:class="{
'label-bar--hide-icon': hideLabelIcon(batteryCharge),
'label-bar--hide-icon': hideLabelIcon(batteryCharge, 44),
'label-bar--hidden': !selfConsumptionAdjusted,
}"
:style="{ width: widthTotal(batteryCharge) }"
@ -119,6 +119,7 @@ export default {
pvProduction: { type: Number, default: 0 },
houseConsumption: { type: Number, default: 0 },
batterySoC: { type: Number, default: 0 },
valuesInKw: { type: Boolean, default: false },
},
data: function () {
return { width: 0 };
@ -161,7 +162,7 @@ export default {
return (100 / this.totalAdjusted) * power + "%";
},
kw: function (watt) {
return Math.max(0, watt / 1000).toFixed(1) + " kW";
return this.fmtKw(watt, this.valuesInKw);
},
hidePowerLabel(power) {
if (this.totalAdjusted === 0) return true;
@ -169,9 +170,8 @@ export default {
const percent = (100 / this.totalAdjusted) * power;
return (this.width / 100) * percent < minWidth;
},
hideLabelIcon(power) {
hideLabelIcon(power, minWidth = 32) {
if (this.totalAdjusted === 0) return true;
const minWidth = 60;
const percent = (100 / this.totalAdjusted) * power;
return (this.width / 100) * percent < minWidth;
},
@ -258,7 +258,7 @@ export default {
.label-bar-icon {
background-color: white;
color: var(--bs-gray);
padding: 0 0.75rem;
padding: 0 0.3rem;
opacity: 1;
transition: opacity 250ms ease-in;
}

View file

@ -17,6 +17,14 @@ export default {
val = Math.abs(val);
return val >= this.fmtLimit ? this.round(val / 1e3, this.fmtDigits) : this.round(val, 0);
},
fmtKw: function (watt, kw = true) {
const digits = kw ? 1 : 0;
const value = kw ? watt / 1000 : watt;
const unit = kw ? " kW" : " W";
return (
this.$n(value, { minimumFractionDigits: digits, maximumFractionDigits: digits }) + unit
);
},
fmtUnit: function (val) {
return Math.abs(val) >= this.fmtLimit ? "k" : "";
},

View file

@ -3,8 +3,8 @@
<div class="flex-grow-1 d-flex flex-column justify-content-between">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/" @click.prevent="reload">
<Logo class="logo me-3"></Logo>
<a class="navbar-brand" href="https://github.com/andig/evcc">
<Logo class="logo"></Logo>
</a>
<div class="d-flex">
<div class="d-flex">

1
dist/css/index.637a2552.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/index.html vendored
View file

@ -1,4 +1,4 @@
<!DOCTYPE html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="description" content="EV Charge Controller"><meta name="author" content="andig"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><link rel="apple-touch-icon" sizes="180x180" href="ico/apple-touch-icon.png?[[.Version]]"><link rel="icon" type="image/png" sizes="32x32" href="ico/favicon-32x32.png?[[.Version]]"><link rel="icon" type="image/png" sizes="16x16" href="ico/favicon-16x16.png?[[.Version]]"><link rel="manifest" href="ico/site.webmanifest"><link rel="mask-icon" href="ico/safari-pinned-tab.svg?[[.Version]]" color="#18191a"><link rel="shortcut icon" href="ico/favicon.ico?[[.Version]]"><meta name="apple-mobile-web-app-title" content="evcc"><meta name="application-name" content="evcc"><meta name="msapplication-TileColor" content="#18191a"><meta name="msapplication-config" content="ico/browserconfig.xml"><meta name="theme-color" content="#18191a"><title>evcc</title><link href="css/chunk-vendors.882a611f.css" rel="preload" as="style"><link href="css/index.d2c9ca1b.css" rel="preload" as="style"><link href="js/chunk-vendors.1a7ce110.js" rel="preload" as="script"><link href="js/index.6e9352a0.js" rel="preload" as="script"><link href="css/chunk-vendors.882a611f.css" rel="stylesheet"><link href="css/index.d2c9ca1b.css" rel="stylesheet"></head><body><script>window.evcc = {
<!DOCTYPE html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="description" content="EV Charge Controller"><meta name="author" content="andig"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><link rel="apple-touch-icon" sizes="180x180" href="ico/apple-touch-icon.png?[[.Version]]"><link rel="icon" type="image/png" sizes="32x32" href="ico/favicon-32x32.png?[[.Version]]"><link rel="icon" type="image/png" sizes="16x16" href="ico/favicon-16x16.png?[[.Version]]"><link rel="manifest" href="ico/site.webmanifest"><link rel="mask-icon" href="ico/safari-pinned-tab.svg?[[.Version]]" color="#18191a"><link rel="shortcut icon" href="ico/favicon.ico?[[.Version]]"><meta name="apple-mobile-web-app-title" content="evcc"><meta name="application-name" content="evcc"><meta name="msapplication-TileColor" content="#18191a"><meta name="msapplication-config" content="ico/browserconfig.xml"><meta name="theme-color" content="#18191a"><title>evcc</title><link href="css/chunk-vendors.882a611f.css" rel="preload" as="style"><link href="css/index.637a2552.css" rel="preload" as="style"><link href="js/chunk-vendors.1a7ce110.js" rel="preload" as="script"><link href="js/index.95321b25.js" rel="preload" as="script"><link href="css/chunk-vendors.882a611f.css" rel="stylesheet"><link href="css/index.637a2552.css" rel="stylesheet"></head><body><script>window.evcc = {
version: "[[.Version]]",
configured: "[[.Configured]]",
};</script><div id="app"></div><script src="js/chunk-vendors.1a7ce110.js"></script><script src="js/index.6e9352a0.js"></script></body></html>
};</script><div id="app"></div><script src="js/chunk-vendors.1a7ce110.js"></script><script src="js/index.95321b25.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/js/index.95321b25.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/js/index.95321b25.js.map vendored Normal file

File diff suppressed because one or more lines are too long

14
package-lock.json generated
View file

@ -18434,6 +18434,16 @@
"minipass": "^3.0.0"
}
},
"minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"dev": true,
"requires": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
}
},
"mississippi": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
@ -23084,12 +23094,14 @@
},
"tar": {
"version": "6.1.0",
"resolved": "",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz",
"integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==",
"dev": true,
"requires": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^3.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
},