Add ve.direct live view
This commit is contained in:
parent
63ddbed359
commit
ece592287c
@ -66,40 +66,40 @@ void WebApiWsVedirectLiveClass::loop()
|
||||
void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root)
|
||||
{
|
||||
// device info
|
||||
root[0][F("data_age")] = (millis() - VeDirect.getLastUpdate() ) / 1000;
|
||||
root[0][F("age_critical")] = ((millis() - VeDirect.getLastUpdate()) / 1000) > Configuration.get().Vedirect_PollInterval * 5;
|
||||
root[0][F("PID")] = VeDirect.getPidAsString(VeDirect.veMap["PID"].c_str());
|
||||
root[0][F("SER")] = VeDirect.veMap["SER"];
|
||||
root[0][F("FW")] = VeDirect.veMap["FW"];
|
||||
root[0][F("LOAD")] = VeDirect.veMap["LOAD"];
|
||||
root[0][F("CS")] = VeDirect.getCsAsString(VeDirect.veMap["CS"].c_str());
|
||||
root[0][F("ERR")] = VeDirect.getErrAsString(VeDirect.veMap["ERR"].c_str());
|
||||
root[0][F("OR")] = VeDirect.getOrAsString(VeDirect.veMap["OR"].c_str());
|
||||
root[0][F("MPPT")] = VeDirect.getMpptAsString(VeDirect.veMap["MPPT"].c_str());
|
||||
root[0][F("HSDS")]["v"] = VeDirect.veMap["HSDS"].toInt();
|
||||
root[0][F("HSDS")]["u"] = "Days";
|
||||
root[F("data_age")] = (millis() - VeDirect.getLastUpdate() ) / 1000;
|
||||
root[F("age_critical")] = ((millis() - VeDirect.getLastUpdate()) / 1000) > Configuration.get().Vedirect_PollInterval * 5;
|
||||
root[F("PID")] = VeDirect.getPidAsString(VeDirect.veMap["PID"].c_str());
|
||||
root[F("SER")] = VeDirect.veMap["SER"];
|
||||
root[F("FW")] = VeDirect.veMap["FW"];
|
||||
root[F("LOAD")] = VeDirect.veMap["LOAD"];
|
||||
root[F("CS")] = VeDirect.getCsAsString(VeDirect.veMap["CS"].c_str());
|
||||
root[F("ERR")] = VeDirect.getErrAsString(VeDirect.veMap["ERR"].c_str());
|
||||
root[F("OR")] = VeDirect.getOrAsString(VeDirect.veMap["OR"].c_str());
|
||||
root[F("MPPT")] = VeDirect.getMpptAsString(VeDirect.veMap["MPPT"].c_str());
|
||||
root[F("HSDS")]["v"] = VeDirect.veMap["HSDS"].toInt();
|
||||
root[F("HSDS")]["u"] = "Days";
|
||||
|
||||
// battery info
|
||||
root[1][F("V")]["v"] = round(VeDirect.veMap["V"].toDouble() / 10.0) / 100.0;
|
||||
root[1][F("V")]["u"] = "V";
|
||||
root[1][F("I")]["v"] = round(VeDirect.veMap["I"].toDouble() / 10.0) / 100.0;
|
||||
root[1][F("I")]["u"] = "A";
|
||||
root[F("V")]["v"] = round(VeDirect.veMap["V"].toDouble() / 10.0) / 100.0;
|
||||
root[F("V")]["u"] = "V";
|
||||
root[F("I")]["v"] = round(VeDirect.veMap["I"].toDouble() / 10.0) / 100.0;
|
||||
root[F("I")]["u"] = "A";
|
||||
|
||||
// panel info
|
||||
root[2][F("VPV")]["v"] = round(VeDirect.veMap["VPV"].toDouble() / 10.0) / 100.0;
|
||||
root[2][F("VPV")]["u"] = "V";
|
||||
root[2][F("PPV")]["v"] = VeDirect.veMap["PPV"].toInt();
|
||||
root[2][F("PPV")]["u"] = "W";
|
||||
root[2][F("H19")]["v"] = VeDirect.veMap["H19"].toDouble() / 100.0;
|
||||
root[2][F("H19")]["u"] = "kWh";
|
||||
root[2][F("H20")]["v"] = VeDirect.veMap["H20"].toDouble() / 100.0;
|
||||
root[2][F("H20")]["u"] = "kWh";
|
||||
root[2][F("H21")]["v"] = VeDirect.veMap["H21"].toInt();
|
||||
root[2][F("H21")]["u"] = "W";
|
||||
root[2][F("H22")]["v"] = VeDirect.veMap["H22"].toDouble() / 100.0;
|
||||
root[2][F("H22")]["u"] = "kWh";
|
||||
root[2][F("H23")]["v"] = VeDirect.veMap["H23"].toInt();
|
||||
root[2][F("H23")]["u"] = "W";
|
||||
root[F("VPV")]["v"] = round(VeDirect.veMap["VPV"].toDouble() / 10.0) / 100.0;
|
||||
root[F("VPV")]["u"] = "V";
|
||||
root[F("PPV")]["v"] = VeDirect.veMap["PPV"].toInt();
|
||||
root[F("PPV")]["u"] = "W";
|
||||
root[F("H19")]["v"] = VeDirect.veMap["H19"].toDouble() / 100.0;
|
||||
root[F("H19")]["u"] = "kWh";
|
||||
root[F("H20")]["v"] = VeDirect.veMap["H20"].toDouble() / 100.0;
|
||||
root[F("H20")]["u"] = "kWh";
|
||||
root[F("H21")]["v"] = VeDirect.veMap["H21"].toInt();
|
||||
root[F("H21")]["u"] = "W";
|
||||
root[F("H22")]["v"] = VeDirect.veMap["H22"].toDouble() / 100.0;
|
||||
root[F("H22")]["u"] = "kWh";
|
||||
root[F("H23")]["v"] = VeDirect.veMap["H23"].toInt();
|
||||
root[F("H23")]["u"] = "W";
|
||||
|
||||
if (VeDirect.getLastUpdate() > _newestVedirectTimestamp) {
|
||||
_newestVedirectTimestamp = VeDirect.getLastUpdate();
|
||||
@ -121,7 +121,7 @@ void WebApiWsVedirectLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWe
|
||||
|
||||
void WebApiWsVedirectLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
|
||||
{
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(true, 1024U);
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 1024U);
|
||||
JsonVariant root = response->getRoot().as<JsonVariant>();
|
||||
generateJsonResponse(root);
|
||||
|
||||
|
||||
@ -77,6 +77,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<VedirectView />
|
||||
|
||||
<div class="modal" id="eventView" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
@ -137,6 +139,7 @@ import InverterChannelInfo from "@/components/partials/InverterChannelInfo.vue";
|
||||
import * as bootstrap from 'bootstrap';
|
||||
import EventLog from '@/components/partials/EventLog.vue';
|
||||
import DevInfo from '@/components/partials/DevInfo.vue';
|
||||
import VedirectView from '@/components/partials/VedirectView.vue';
|
||||
|
||||
declare interface Inverter {
|
||||
serial: number,
|
||||
@ -150,7 +153,8 @@ export default defineComponent({
|
||||
components: {
|
||||
InverterChannelInfo,
|
||||
EventLog,
|
||||
DevInfo
|
||||
DevInfo,
|
||||
VedirectView
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
280
webapp/src/components/partials/VedirectView.vue
Normal file
280
webapp/src/components/partials/VedirectView.vue
Normal file
@ -0,0 +1,280 @@
|
||||
<template>
|
||||
|
||||
<div class="text-center" v-if="dataLoading">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div class="row gy-3">
|
||||
<div class="col-sm-3 col-md-2">
|
||||
<div class="nav nav-pills row-cols-sm-1" id="v-pills-tab" role="tablist"
|
||||
aria-orientation="vertical">
|
||||
<button class="nav-link"
|
||||
:id="'v-pills-vedirect-tab'" data-bs-toggle="pill"
|
||||
:data-bs-target="'#v-pills-vedirect'" type="button" role="tab"
|
||||
aria-controls="'v-pills-vedirect'" aria-selected="true">
|
||||
Ve.Direct
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content col-sm-9 col-md-10" id="v-pills-tabContent">
|
||||
<div class="tab-pane fade show"
|
||||
:id="'v-pills-vedirect'" role="tabpanel"
|
||||
:aria-labelledby="'v-pills-vedirect-tab'" tabindex="0">
|
||||
<div class="card">
|
||||
<div class="card-header text-white bg-primary d-flex justify-content-between align-items-center"
|
||||
:class="{
|
||||
'bg-danger': vedirectData.age_critical,
|
||||
'bg-primary': !vedirectData.age_critical,
|
||||
}">
|
||||
{{ vedirectData.PID }} (Serial Number:
|
||||
{{ vedirectData.SER }}, Firmware
|
||||
{{ vedirectData.FW }}) (Data Age:
|
||||
{{ vedirectData.data_age }} seconds)
|
||||
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col order-0">
|
||||
<div class="card" :class="{ 'border-info': true }">
|
||||
<div class="card-header bg-info">Device Info</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Property</th>
|
||||
<th style="text-align: right" scope="col">Value</th>
|
||||
<th scope="col">Unit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Load output state </th>
|
||||
<td style="text-align: right">{{vedirectData.LOAD}}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">State of operation </th>
|
||||
<td style="text-align: right">{{vedirectData.CS}}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Tracker operation mode </th>
|
||||
<td style="text-align: right">{{vedirectData.MPPT}}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Off reason </th>
|
||||
<td style="text-align: right">{{vedirectData.OR}}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Error code </th>
|
||||
<td style="text-align: right">{{vedirectData.ERR}}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Day sequence number (0..364) </th>
|
||||
<td style="text-align: right">{{vedirectData.HSDS.v}}</td>
|
||||
<td>{{vedirectData.HSDS.u}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col order-1">
|
||||
<div class="card" :class="{ 'border-info': false }">
|
||||
<div class="card-header">Battery</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Property</th>
|
||||
<th style="text-align: right" scope="col">Value</th>
|
||||
<th scope="col">Unit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Battery voltage </th>
|
||||
<td style="text-align: right">{{formatNumber(vedirectData.V.v)}}</td>
|
||||
<td>{{vedirectData.V.u}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Battery current </th>
|
||||
<td style="text-align: right">{{formatNumber(vedirectData.I.v)}}</td>
|
||||
<td>{{vedirectData.I.u}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col order-2">
|
||||
<div class="card" :class="{ 'border-info': false }">
|
||||
<div class="card-header">Panel</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Property</th>
|
||||
<th style="text-align: right" scope="col">Value</th>
|
||||
<th scope="col">Unit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Panel voltage </th>
|
||||
<td style="text-align: right">{{formatNumber(vedirectData.VPV.v)}}</td>
|
||||
<td>{{vedirectData.VPV.u}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Panel power </th>
|
||||
<td style="text-align: right">{{formatNumber(vedirectData.PPV.v)}}</td>
|
||||
<td>{{vedirectData.PPV.u}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Yield total (user resettable counter) </th>
|
||||
<td style="text-align: right">{{formatNumber(vedirectData.H19.v)}}</td>
|
||||
<td>{{vedirectData.H19.u}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Yield today </th>
|
||||
<td style="text-align: right">{{formatNumber(vedirectData.H20.v)}}</td>
|
||||
<td>{{vedirectData.H20.u}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Maximum power today </th>
|
||||
<td style="text-align: right">{{formatNumber(vedirectData.H21.v)}}</td>
|
||||
<td>{{vedirectData.H21.u}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Yield yesterday </th>
|
||||
<td style="text-align: right">{{formatNumber(vedirectData.H22.v)}}</td>
|
||||
<td>{{vedirectData.H22.u}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Maximum power yesterday </th>
|
||||
<td style="text-align: right">{{formatNumber(vedirectData.H23.v)}}</td>
|
||||
<td>{{vedirectData.H23.u}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
declare interface Vedirect {
|
||||
SER: string,
|
||||
PID: string,
|
||||
FW: string,
|
||||
age_critical: boolean,
|
||||
data_age: 0,
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
socket: {} as WebSocket,
|
||||
heartInterval: 0,
|
||||
dataAgeInterval: 0,
|
||||
dataLoading: true,
|
||||
vedirectData: {} as Vedirect,
|
||||
isFirstFetchAfterConnect: true,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getInitialData();
|
||||
this.initSocket();
|
||||
this.initDataAgeing();
|
||||
},
|
||||
unmounted() {
|
||||
this.closeSocket();
|
||||
},
|
||||
methods: {
|
||||
getInitialData() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/vedirectlivedata/status")
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
this.vedirectData = data;
|
||||
this.dataLoading = false;
|
||||
});
|
||||
},
|
||||
initSocket() {
|
||||
console.log("Starting connection to WebSocket Server");
|
||||
|
||||
const { protocol, host } = location;
|
||||
const webSocketUrl = `${protocol === "https:" ? "wss" : "ws"
|
||||
}://${host}/vedirectlivedata`;
|
||||
|
||||
this.socket = new WebSocket(webSocketUrl);
|
||||
|
||||
this.socket.onmessage = (event) => {
|
||||
console.log(event);
|
||||
this.vedirectData = JSON.parse(event.data);
|
||||
this.dataLoading = false;
|
||||
this.heartCheck(); // Reset heartbeat detection
|
||||
};
|
||||
|
||||
this.socket.onopen = function (event) {
|
||||
console.log(event);
|
||||
console.log("Successfully connected to the echo websocket server...");
|
||||
};
|
||||
|
||||
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
|
||||
window.onbeforeunload = () => {
|
||||
this.closeSocket();
|
||||
};
|
||||
},
|
||||
initDataAgeing() {
|
||||
this.dataAgeInterval = setInterval(() => {
|
||||
this.vedirectData.data_age++;
|
||||
}, 1000);
|
||||
},
|
||||
// Send heartbeat packets regularly * 59s Send a heartbeat
|
||||
heartCheck() {
|
||||
this.heartInterval && clearTimeout(this.heartInterval);
|
||||
this.heartInterval = setInterval(() => {
|
||||
if (this.socket.readyState === 1) {
|
||||
// Connection status
|
||||
this.socket.send("ping");
|
||||
} else {
|
||||
this.initSocket(); // Breakpoint reconnection 5 Time
|
||||
}
|
||||
}, 59 * 1000);
|
||||
},
|
||||
/** To break off websocket Connect */
|
||||
closeSocket() {
|
||||
this.socket.close();
|
||||
this.heartInterval && clearTimeout(this.heartInterval);
|
||||
this.isFirstFetchAfterConnect = true;
|
||||
},
|
||||
formatNumber(num: string) {
|
||||
return new Intl.NumberFormat(
|
||||
undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }
|
||||
).format(parseFloat(num));
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user