webapp: show precise data age for inverters and introduce and use DataAgeDisplay component

thanks to @schlimmchen
This commit is contained in:
Thomas Basler
2025-03-18 20:45:36 +01:00
parent 0b798056f3
commit fb34efcd44
11 changed files with 91 additions and 33 deletions

View File

@@ -61,6 +61,10 @@
"Light": "Φωτεινό",
"Auto": "Αυτόματο"
},
"dataagedisplay": {
"DataAge": "Τελευταία ενημέρωση πρίν από",
"SecondsSince": "0 δευτερόλεπτα | 1 δευτερόλεπτα | {n} δευτερόλεπτα"
},
"apiresponse": {
"1001": "Οι ρυθμίσεις αποθηκεύτηκαν!",
"1002": "Δεν βρέθηκαν τιμές!",
@@ -134,8 +138,6 @@
"LiveData": "Τηλεμετρία",
"SerialNumber": "Σειριακός Αριθμός: ",
"CurrentLimit": "Τρέχον Όριο: ",
"DataAge": "Τελευταία ενημέρωση πρίν από: ",
"Seconds": "{val} δευτερόλεπτα",
"ShowSetInverterLimit": "Εμφάνιση/Ρύθμιση ορίου μετατροπέα",
"TurnOnOff": "Ενεργοποίηση/απενεργοποίηση του μετατροπέα",
"ShowInverterInfo": "Εμφάνιση πληροφοριών του μετατροπέα",

View File

@@ -61,6 +61,10 @@
"Light": "Claro",
"Auto": "Automático"
},
"dataagedisplay": {
"DataAge": "Edad de los Datos",
"SecondsSince": "0 segundos | 1 segundos | {n} segundos"
},
"apiresponse": {
"1001": "¡Opciones guardadas!",
"1002": "No se encontraron valores",
@@ -134,8 +138,6 @@
"LiveData": "Datos en Vivo",
"SerialNumber": "Número de Serie: ",
"CurrentLimit": "Límite de Corriente: ",
"DataAge": "Edad de los Datos: ",
"Seconds": "{val} segundos",
"ShowSetInverterLimit": "Ver / Establecer Límite del Inversor",
"TurnOnOff": "Encender/Apagar el Inversor",
"ShowInverterInfo": "Ver Información del Inversor",

View File

@@ -61,6 +61,10 @@
"Light": "Chiaro",
"Auto": "Automatico"
},
"dataagedisplay": {
"DataAge": "Aggiornamento Dati",
"SecondsSince": "0 secondi | 1 secondi | {n} secondi"
},
"apiresponse": {
"1001": "Settings saved!",
"1002": "No values found!",
@@ -134,8 +138,6 @@
"LiveData": "Dati in tempo reale",
"SerialNumber": "Numero seriale: ",
"CurrentLimit": "Limite attuale: ",
"DataAge": "Aggiornamento Dati: ",
"Seconds": "{val} secondi",
"ShowSetInverterLimit": "Mostra / Imposta Limite di Potenza",
"TurnOnOff": "Accendi/Spegni Inverter",
"ShowInverterInfo": "Mostra info Inverter",

View File

@@ -61,6 +61,10 @@
"Light": "Jasny",
"Auto": "Automatyczny"
},
"dataagedisplay": {
"DataAge": "Aktualizacja danych",
"SecondsSince": "0 sekund | 1 sekund | {n} sekund"
},
"apiresponse": {
"1001": "Ustawienia zapisane!",
"1002": "Nie znaleziono żadnych wartości!",
@@ -134,8 +138,6 @@
"LiveData": "Dane na żywo",
"SerialNumber": "Numer seryjny: ",
"CurrentLimit": "Aktualny limit: ",
"DataAge": "Aktualizacja danych: ",
"Seconds": "{val} sekund",
"ShowSetInverterLimit": "Pokaż / ustaw limit mocy",
"TurnOnOff": "Włącz /wyłącz falownik",
"ShowInverterInfo": "Pokaż informacje o falowniku",

View File

@@ -146,6 +146,7 @@ void WebApiWsLiveClass::generateInverterCommonJsonResponse(JsonObject& root, std
root["name"] = inv->name();
root["order"] = inv_cfg->Order;
root["data_age"] = (millis() - inv->Statistics()->getLastUpdate()) / 1000;
root["data_age_ms"] = millis() - inv->Statistics()->getLastUpdate();
root["poll_enabled"] = inv->getEnablePolling();
root["reachable"] = inv->isReachable();
root["producing"] = inv->isProducing();

View File

@@ -0,0 +1,34 @@
<template>
<div style="padding-right: 2em">
{{ $t('dataagedisplay.DataAge') }}:
{{ $t('dataagedisplay.SecondsSince', { n: dataAgeSeconds }) }}
<template v-if="dataAgeMs > thresholdMs"> ({{ calculateAbsoluteTime(dataAgeMs) }}) </template>
</div>
</template>
<script lang="ts">
export default {
name: 'DataAgeDisplay',
props: {
dataAgeMs: {
type: Number,
required: true,
},
thresholdMs: {
type: Number,
default: 300000,
},
},
methods: {
calculateAbsoluteTime(lastTime: number): string {
const date = new Date(Date.now() - lastTime);
return this.$d(date, 'datetime');
},
},
computed: {
dataAgeSeconds() {
return Math.floor(this.dataAgeMs / 1000);
},
},
};
</script>

View File

@@ -46,6 +46,10 @@
"Light": "Hell",
"Auto": "Automatisch"
},
"dataagedisplay": {
"DataAge": "Letzte Aktualisierung",
"SecondsSince": "vor 0 Sekunden | vor 1 Sekunde | vor {n} Sekunden"
},
"apiresponse": {
"1001": "Einstellungen gespeichert!",
"1002": "Keine Werte gefunden!",
@@ -119,8 +123,6 @@
"LiveData": "Live-Daten",
"SerialNumber": "Seriennummer: ",
"CurrentLimit": "Aktuelles Limit: ",
"DataAge": "Letzte Aktualisierung: ",
"Seconds": "vor {val} Sekunden",
"ShowSetInverterLimit": "Zeige / Setze Wechselrichterlimit",
"TurnOnOff": "Schalte Wechselrichter ein oder aus",
"ShowInverterInfo": "Zeige Wechselrichter-Informationen",

View File

@@ -46,6 +46,10 @@
"Light": "Light",
"Auto": "Auto"
},
"dataagedisplay": {
"DataAge": "Data Age",
"SecondsSince": "0 seconds | 1 second | {n} seconds"
},
"apiresponse": {
"1001": "Settings saved!",
"1002": "No values found!",
@@ -119,8 +123,6 @@
"LiveData": "Live Data",
"SerialNumber": "Serial Number: ",
"CurrentLimit": "Current Limit: ",
"DataAge": "Data Age: ",
"Seconds": "{val} seconds",
"ShowSetInverterLimit": "Show / Set Inverter Limit",
"TurnOnOff": "Turn Inverter on/off",
"ShowInverterInfo": "Show Inverter Info",

View File

@@ -46,6 +46,10 @@
"Light": "Clair",
"Auto": "Auto"
},
"dataagedisplay": {
"DataAge": "Âge des données",
"SecondsSince": "0 secondes | 1 seconde | {n} secondes"
},
"apiresponse": {
"1001": "Paramètres enregistrés !",
"1002": "Aucune valeur trouvée !",
@@ -119,8 +123,6 @@
"LiveData": "Données en direct",
"SerialNumber": "Numéro de série : ",
"CurrentLimit": "Limite de courant : ",
"DataAge": "Âge des données : ",
"Seconds": "{val} secondes",
"ShowSetInverterLimit": "Afficher / Régler la limite de l'onduleur",
"TurnOnOff": "Allumer / Eteindre l'onduleur",
"ShowInverterInfo": "Afficher les informations sur l'onduleur",

View File

@@ -36,6 +36,7 @@ export interface Inverter {
name: string;
order: number;
data_age: number;
data_age_ms: number;
poll_enabled: boolean;
reachable: boolean;
producing: boolean;

View File

@@ -98,11 +98,7 @@
>{{ $n(inverter.limit_relative / 100, 'percentOneDigit') }}
</div>
<div style="padding-right: 2em">
{{ $t('home.DataAge') }}
{{ $t('home.Seconds', { val: $n(inverter.data_age) }) }}
<template v-if="inverter.data_age > 300">
/ {{ calculateAbsoluteTime(inverter.data_age) }}
</template>
<DataAgeDisplay :data-age-ms="inverter.data_age_ms" />
</div>
</div>
</div>
@@ -504,6 +500,7 @@
<script lang="ts">
import BasePage from '@/components/BasePage.vue';
import BootstrapAlert from '@/components/BootstrapAlert.vue';
import DataAgeDisplay from '@/components/DataAgeDisplay.vue';
import DevInfo from '@/components/DevInfo.vue';
import EventLog from '@/components/EventLog.vue';
import GridProfile from '@/components/GridProfile.vue';
@@ -513,8 +510,8 @@ import InverterTotalInfo from '@/components/InverterTotalInfo.vue';
import ModalDialog from '@/components/ModalDialog.vue';
import type { DevInfoStatus } from '@/types/DevInfoStatus';
import type { EventlogItems } from '@/types/EventlogStatus';
import type { GridProfileStatus } from '@/types/GridProfileStatus';
import type { GridProfileRawdata } from '@/types/GridProfileRawdata';
import type { GridProfileStatus } from '@/types/GridProfileStatus';
import type { LimitConfig } from '@/types/LimitConfig';
import type { LimitStatus } from '@/types/LimitStatus';
import type { Inverter, LiveData } from '@/types/LiveDataStatus';
@@ -538,6 +535,7 @@ export default defineComponent({
components: {
BasePage,
BootstrapAlert,
DataAgeDisplay,
DevInfo,
EventLog,
GridProfile,
@@ -562,7 +560,7 @@ export default defineComponent({
socket: {} as WebSocket,
heartInterval: 0,
dataAgeInterval: 0,
dataAgeTimers: {} as Record<string, number>,
dataLoading: true,
liveData: {} as LiveData,
isFirstFetchAfterConnect: true,
@@ -607,7 +605,6 @@ export default defineComponent({
created() {
this.getInitialData();
this.initSocket();
this.initDataAgeing();
this.$emitter.on('logged-in', () => {
this.isLogged = this.isLoggedIn();
});
@@ -706,8 +703,10 @@ export default defineComponent({
);
if (foundIdx == -1) {
Object.assign(this.liveData.inverters, newData.inverters);
this.liveData.inverters.forEach((inv) => this.resetDataAging(inv));
} else {
Object.assign(this.liveData.inverters[foundIdx], newData.inverters[0]);
this.resetDataAging(this.liveData.inverters[foundIdx]);
}
this.dataLoading = false;
this.heartCheck(); // Reset heartbeat detection
@@ -734,13 +733,26 @@ export default defineComponent({
this.closeSocket();
};
},
initDataAgeing() {
this.dataAgeInterval = setInterval(() => {
if (this.inverterData) {
this.inverterData.forEach((element) => {
element.data_age++;
});
}
resetDataAging(inv: Inverter) {
if (this.dataAgeTimers[inv.serial] !== undefined) {
clearTimeout(this.dataAgeTimers[inv.serial]);
}
const nextMs = 1000 - (inv.data_age_ms % 1000);
this.dataAgeTimers[inv.serial] = setTimeout(() => {
this.doDataAging(inv.serial);
}, nextMs);
},
doDataAging(serial: string) {
const inv = this.liveData?.inverters?.find((inv) => inv.serial === serial);
if (inv === undefined) {
return;
}
inv.data_age_ms += 1000;
this.dataAgeTimers[serial] = setTimeout(() => {
this.doDataAging(serial);
}, 1000);
},
// Send heartbeat packets regularly * 59s Send a heartbeat
@@ -918,10 +930,6 @@ export default defineComponent({
}
});
},
calculateAbsoluteTime(lastTime: number): string {
const date = new Date(Date.now() - lastTime * 1000);
return this.$d(date, 'datetime');
},
getSumIrridiation(inv: Inverter): number {
let total = 0;
Object.keys(inv.DC).forEach((key) => {