Webapp changes to display Huawei PSU values and to enable/disable the unit

This commit is contained in:
MalteSchm 2023-03-26 11:06:51 +02:00
parent 3b57550ead
commit 0e2b7767c7
12 changed files with 598 additions and 5 deletions

View File

@ -0,0 +1,358 @@
<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="tab-content col-sm-12 col-md-12" id="v-pills-tabContent">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center" :class="{
'text-bg-danger': huaweiData.data_age > 20,
'text-bg-primary': huaweiData.data_age < 19,
}">
<div class="p-1 flex-grow-1">
<div class="d-flex flex-wrap">
<div style="padding-right: 2em;">
Huawei R4850G2
</div>
<div style="padding-right: 2em;">
{{ $t('huawei.DataAge') }} {{ $t('huawei.Seconds', { 'val': huaweiData.data_age }) }}
</div>
</div>
</div>
<div class="btn-toolbar p-2" role="toolbar">
<div class="btn-group me-2" role="group">
<button :disabled="false" type="button" class="btn btn-sm btn-danger" @click="onShowLimitSettings()"
v-tooltip :title="$t('huawei.ShowSetLimit')">
<BIconSpeedometer style="font-size:24px;" />
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="row flex-row flex-wrap align-items-start g-3">
<div class="col order-0">
<div class="card" :class="{ 'border-info': true }">
<div class="card-header bg-info">{{ $t('huawei.Input') }}</div>
<div class="card-body">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">{{ $t('huawei.Property') }}</th>
<th style="text-align: right" scope="col">{{ $t('huawei.Value') }}</th>
<th scope="col">{{ $t('huawei.Unit') }}</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">{{ $t('huawei.input_voltage') }}</th>
<td style="text-align: right">{{ formatNumber(huaweiData.input_voltage.v) }}</td>
<td>{{ huaweiData.input_voltage.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.input_current') }}</th>
<td style="text-align: right">{{ formatNumber(huaweiData.input_current.v) }}</td>
<td>{{ huaweiData.input_current.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.input_power') }}</th>
<td style="text-align: right">{{ formatNumber(huaweiData.input_power.v) }}</td>
<td>{{ huaweiData.input_power.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.input_temp') }}</th>
<td style="text-align: right">{{ Math.round(huaweiData.input_temp.v) }}</td>
<td>{{ huaweiData.input_temp.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.efficiency') }}</th>
<td style="text-align: right">{{ huaweiData.efficiency.v.toFixed(3) }}</td>
<td>{{ huaweiData.efficiency.u }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col order-1">
<div class="card" :class="{ 'border-info': false }">
<div class="card-header bg-info">{{ $t('huawei.Output') }}</div>
<div class="card-body">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">{{ $t('huawei.Property') }}</th>
<th style="text-align: right" scope="col">{{ $t('huawei.Value') }}</th>
<th scope="col">{{ $t('huawei.Unit') }}</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">{{ $t('huawei.output_voltage') }}</th>
<td style="text-align: right">{{ huaweiData.output_voltage.v.toFixed(1) }}</td>
<td>{{ huaweiData.output_voltage.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.output_current') }}</th>
<td style="text-align: right">{{ huaweiData.output_current.v.toFixed(2) }}</td>
<td>{{ huaweiData.output_current.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.max_output_current') }}</th>
<td style="text-align: right">{{ huaweiData.max_output_current.v.toFixed(1) }}</td>
<td>{{ huaweiData.max_output_current.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.output_power') }}</th>
<td style="text-align: right">{{ huaweiData.output_power.v.toFixed(1) }}</td>
<td>{{ huaweiData.output_power.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.output_temp') }}</th>
<td style="text-align: right">{{ Math.round(huaweiData.output_temp.v) }}</td>
<td>{{ huaweiData.output_temp.u }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal" id="huaweiLimitSettingView" ref="huaweiLimitSettingView" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form @submit="onSubmitLimit">
<div class="modal-header">
<h5 class="modal-title">{{ $t('huawei.LimitSettings') }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row mb-3">
<label for="inputCurrentLimit" class="col-sm-3 col-form-label">{{ $t('huawei.CurrentLimit') }} </label>
</div>
<div class="row mb-3 align-items-center">
<label for="inputVoltageTargetLimit" class="col-sm-3 col-form-label">{{ $t('huawei.SetVoltageLimit')
}}</label>
<div class="col-sm-1">
<div class="form-switch form-check-inline">
<input class="form-check-input" type="checkbox" id="flexSwitchVoltage"
v-model="targetLimitList.voltage_valid">
</div>
</div>
<div class="col-sm-7">
<input type="number" name="inputVoltageTargetLimit" class="form-control" id="inputVoltageTargetLimit"
:min="targetVoltageLimitMin" :max="targetVoltageLimitMax" v-model="targetLimitList.voltage"
:disabled=!targetLimitList.voltage_valid>
</div>
</div>
<div class="row mb-3">
<div class="col-sm-9">
<div v-if="targetLimitList.voltage < targetVoltageLimitMinOffline" class="alert alert-secondary mt-3"
role="alert" v-html="$t('huawei.LimitHint')"></div>
</div>
</div>
<div class="row mb-3 align-items-center">
<label for="inputCurrentTargetLimit" class="col-sm-3 col-form-label">{{ $t('huawei.SetCurrentLimit')
}}</label>
<div class="col-sm-1">
<div class="form-switch form-check-inline">
<input class="form-check-input" type="checkbox" id="flexSwitchCurrentt"
v-model="targetLimitList.current_valid">
</div>
</div>
<div class="col-sm-7">
<input type="number" name="inputCurrentTargetLimit" class="form-control" id="inputCurrentTargetLimit"
:min="targetCurrentLimitMin" :max="targetCurrentLimitMax" v-model="targetLimitList.current"
:disabled=!targetLimitList.current_valid>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-danger" @click="onSetLimitSettings(true)">{{ $t('huawei.SetOnline')
}}</button>
<button type="submit" class="btn btn-danger" @click="onSetLimitSettings(false)">{{
$t('huawei.SetOffline')
}}</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ $t('huawei.Close') }}</button>
</div>
</form>
</div>
</div>
</div>
</template>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import type { Huawei } from '@/types/HuaweiDataStatus';
import type { HuaweiLimitConfig } from '@/types/HuaweiLimitConfig';
import { handleResponse, authHeader, authUrl } from '@/utils/authentication';
import * as bootstrap from 'bootstrap';
import {
BIconSpeedometer,
} from 'bootstrap-icons-vue';
export default defineComponent({
components: {
BIconSpeedometer
},
data() {
return {
socket: {} as WebSocket,
heartInterval: 0,
dataAgeInterval: 0,
dataLoading: true,
huaweiData: {} as Huawei,
isFirstFetchAfterConnect: true,
targetVoltageLimitMin: 42,
targetVoltageLimitMinOffline: 48,
targetVoltageLimitMax: 58,
targetCurrentLimitMin: 0,
targetCurrentLimitMax: 60,
targetLimitList: {} as HuaweiLimitConfig,
targetLimitPersistent: false,
huaweiLimitSettingView: {} as bootstrap.Modal,
alertMessageLimit: "",
alertTypeLimit: "info",
showAlertLimit: false,
checked: false,
};
},
created() {
this.getInitialData();
this.initSocket();
this.initDataAgeing();
},
unmounted() {
this.closeSocket();
},
methods: {
getInitialData() {
console.log("Get initalData for Huawei");
this.dataLoading = true;
fetch("/api/huaweilivedata/status", { headers: authHeader() })
.then((response) => handleResponse(response, this.$emitter, this.$router))
.then((data) => {
this.huaweiData = data;
this.dataLoading = false;
});
},
initSocket() {
console.log("Starting connection to Huawei WebSocket Server");
const { protocol, host } = location;
const authString = authUrl();
const webSocketUrl = `${protocol === "https:" ? "wss" : "ws"
}://${authString}${host}/huaweilivedata`;
this.socket = new WebSocket(webSocketUrl);
this.socket.onmessage = (event) => {
console.log(event);
this.huaweiData = 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 Huawei 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(() => {
if (this.huaweiData) {
this.huaweiData.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: number) {
return new Intl.NumberFormat(
undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }
).format(num);
},
onHideLimitSettings() {
this.showAlertLimit = false;
},
onShowLimitSettings() {
this.huaweiLimitSettingView = new bootstrap.Modal('#huaweiLimitSettingView');
this.huaweiLimitSettingView.show();
},
onSetLimitSettings(online: boolean) {
this.targetLimitList.online = online;
},
onSubmitLimit(e: Event) {
e.preventDefault();
const formData = new FormData();
formData.append("data", JSON.stringify(this.targetLimitList));
console.log(this.targetLimitList);
fetch("/api/huawei/limit/config", {
method: "POST",
headers: authHeader(),
body: formData,
})
.then((response) => handleResponse(response, this.$emitter, this.$router))
.then(
(response) => {
if (response.type == "success") {
this.huaweiLimitSettingView.hide();
} else {
this.alertMessageLimit = this.$t('apiresponse.' + response.code, response.param);
this.alertTypeLimit = response.type;
this.showAlertLimit = true;
}
}
)
},
},
});
</script>

View File

@ -57,6 +57,9 @@
<li>
<router-link @click="onClick" class="dropdown-item" to="/settings/battery">{{ $t('menu.BatterySettings') }}</router-link>
</li>
<li>
<router-link @click="onClick" class="dropdown-item" to="/settings/chargerac">{{ $t('menu.AcChargerSettings') }}</router-link>
</li>
<li>
<router-link @click="onClick" class="dropdown-item" to="/settings/device">{{ $t('menu.DeviceManager') }}</router-link>
</li>

View File

@ -10,7 +10,8 @@
"DTUSettings": "DTU",
"DeviceManager": "Hardware",
"VedirectSettings": "Ve.direct",
"BatterySettings": "Battery",
"BatterySettings": "Batterie",
"AcChargerSettings": "AC Ladegerät",
"ConfigManagement": "Konfigurationsverwaltung",
"FirmwareUpgrade": "Firmware-Aktualisierung",
"DeviceReboot": "Neustart",
@ -608,5 +609,40 @@
"Name": "Name",
"ValueSelected": "Ausgewählt",
"ValueActive": "Aktiv"
}
},
"huawei": {
"DataAge": "letzte Aktualisierung: ",
"Seconds": "vor {val} Sekunden",
"Input": "Eingang",
"Output": "Ausgang",
"Property": "Eigenschaft",
"Value": "Wert",
"Unit": "Einheit",
"input_voltage": "Eingangsspannung",
"input_current": "Eingangsstrom",
"input_power": "Eingangsleistung",
"input_temp": "Eingangstemperatur",
"efficiency": "Wirkungsgrad",
"output_voltage": "Ausgangsspannung",
"output_current": "Ausgangsstrom",
"max_output_current": "Maximaler Ausgangsstrom",
"output_power": "Ausgangsleistung",
"output_temp": "Ausgangstemperatur",
"ShowSetLimit": "Zeige / Setze Limit",
"LimitSettings": "Limit-Einstellungen",
"SetOffline": "Limit setzen, CAN Bus nicht verbunden",
"SetOnline": "Limit setzen, CAN Bus verbunden",
"LimitHint": "<b>Hinweis:</b> Spannungsbereich wenn CAN Bus nicht verbunden ist 48V-58.5V ",
"Close": "Schließen",
"SetVoltageLimit": "Spannungslimit:",
"SetCurrentLimit": "Stromlimit:",
"CurrentLimit": "Aktuelles Limit: "
},
"acchargeradmin": {
"ChargerSettings": "AC Ladegerät Einstellungen",
"Configuration": "AC Ladegerät Konfiguration",
"EnableHuawei": "Huawei R4850G2 an CAN Bus Interface aktiv",
"Seconds": "@:dtuadmin.Seconds",
"Save": "@:dtuadmin.Save"
}
}

View File

@ -11,6 +11,7 @@
"DeviceManager": "Device-Manager",
"VedirectSettings": "Ve.direct Settings",
"BatterySettings": "@:batteryadmin.BatterySettings",
"AcChargerSettings": "AC Charger",
"ConfigManagement": "Config Management",
"FirmwareUpgrade": "Firmware Upgrade",
"DeviceReboot": "Device Reboot",
@ -609,5 +610,40 @@
"Number": "Number",
"ValueSelected": "Selected",
"ValueActive": "Active"
}
},
"huawei": {
"DataAge": "Data Age: ",
"Seconds": " {val} seconds",
"Input": "Input",
"Output": "Output",
"Property": "Property",
"Value": "Value",
"Unit": "Unit",
"input_voltage": "Input voltage",
"input_current": "Input current",
"input_power": "Input power",
"input_temp": "Input temperature",
"efficiency": "Efficiency",
"output_voltage": "Output voltage",
"output_current": "Output current",
"max_output_current": "Maximum output current",
"output_power": "Output power",
"output_temp": "Output temperature",
"ShowSetLimit": "Show / Set Huawei Limit",
"LimitSettings": "Limit Settings",
"SetOffline": "Set limit, CAN bus not connected",
"SetOnline": "Set limit, CAN bus connected",
"LimitHint": "<b>Hint:</b> CAN bus not connected voltage limit is 48V-58.5V.",
"Close": "close",
"SetVoltageLimit": "Voltage limit:",
"SetCurrentLimit": "Current limit:",
"CurrentLimit": "Current limit:"
},
"acchargeradmin": {
"ChargerSettings": "AC Charger Settings",
"Configuration": "AC Charger Configuration",
"EnableHuawei": "Enable Huawei R4850G2 on CAN Bus Interface",
"Seconds": "@:dtuadmin.Seconds",
"Save": "@:dtuadmin.Save"
}
}

View File

@ -10,6 +10,8 @@
"DTUSettings": "DTU",
"DeviceManager": "Périphériques",
"VedirectSettings": "Ve.direct",
"BatterySettings": "Battery",
"AcChargerSettings": "AC Charger",
"ConfigManagement": "Gestion de la configuration",
"FirmwareUpgrade": "Mise à jour du firmware",
"DeviceReboot": "Redémarrage de l'appareil",
@ -567,5 +569,40 @@
"Name": "Nom",
"ValueSelected": "Sélectionné",
"ValueActive": "Activé"
}
},
"huawei": {
"DataAge": "Data Age: ",
"Seconds": " {val} seconds",
"Input": "Input",
"Output": "Output",
"Property": "Property",
"Value": "Value",
"Unit": "Unit",
"input_voltage": "Input voltage",
"input_current": "Input current",
"input_power": "Input power",
"input_temp": "Input temperature",
"efficiency": "Efficiency",
"output_voltage": "Output voltage",
"output_current": "Output current",
"max_output_current": "Maximum output current",
"output_power": "Output power",
"output_temp": "Output temperature",
"ShowSetLimit": "Show / Set Huawei Limit",
"LimitSettings": "Limit Settings",
"SetOffline": "Set limit, CAN bus not connected",
"SetOnline": "Set limit, CAN bus connected",
"LimitHint": "<b>Hint:</b> CAN bus not connected voltage limit is 48V-58.5V.",
"Close": "close",
"SetVoltageLimit": "Voltage limit:",
"SetCurrentLimit": "Current limit:",
"CurrentLimit": "Current limit:"
},
"acchargeradmin": {
"ChargerSettings": "AC Charger Settings",
"Configuration": "AC Charger Configuration",
"EnableHuawei": "Enable Huawei R4850G2 on CAN Bus Interface",
"Seconds": "@:dtuadmin.Seconds",
"Save": "@:dtuadmin.Save"
}
}

View File

@ -1,5 +1,6 @@
import AboutView from '@/views/AboutView.vue';
import BatteryAdminView from '@/views/BatteryAdminView.vue';
import AcChargerAdminView from '@/views/AcChargerAdminView.vue';
import ConfigAdminView from '@/views/ConfigAdminView.vue';
import ConsoleInfoView from '@/views/ConsoleInfoView.vue';
import DeviceAdminView from '@/views/DeviceAdminView.vue'
@ -96,6 +97,11 @@ const router = createRouter({
name: 'Battery Settings',
component: BatteryAdminView
},
{
path: '/settings/chargerac',
name: 'Charger Settings',
component: AcChargerAdminView
},
{
path: '/settings/mqtt',
name: 'MqTT Settings',

View File

@ -0,0 +1,3 @@
export interface AcChargerConfig {
enabled: boolean;
}

View File

@ -0,0 +1,18 @@
import type { ValueObject } from '@/types/LiveDataStatus';
// Huawei
export interface Huawei {
data_age: 0;
input_voltage: ValueObject;
input_frequency: ValueObject;
input_current: ValueObject;
input_power: ValueObject;
input_temp: ValueObject;
efficiency: ValueObject;
output_voltage: ValueObject;
output_current: ValueObject;
max_output_current: ValueObject;
output_power: ValueObject;
output_temp: ValueObject;
amp_hour: ValueObject;
}

View File

@ -0,0 +1,7 @@
export interface HuaweiLimitConfig {
voltage: number;
voltage_valid: boolean;
current: number;
current_valid: boolean;
online: boolean;
}

View File

@ -50,9 +50,14 @@ export interface Vedirect {
enabled: boolean;
}
export interface Huawei {
enabled: boolean;
}
export interface LiveData {
inverters: Inverter[];
total: Total;
hints: Hints;
vedirect: Vedirect;
huawei: Huawei;
}

View File

@ -0,0 +1,79 @@
<template>
<BasePage :title="$t('acchargeradmin.ChargerSettings')" :isLoading="dataLoading">
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
{{ alertMessage }}
</BootstrapAlert>
<form @submit="saveChargerConfig">
<CardElement :text="$t('acchargeradmin.Configuration')" textVariant="text-bg-primary">
<InputElement :label="$t('acchargeradmin.EnableHuawei')"
v-model="acChargerConfigList.enabled"
type="checkbox" wide/>
</CardElement>
<button type="submit" class="btn btn-primary mb-3">{{ $t('acchargeradmin.Save') }}</button>
</form>
</BasePage>
</template>
<script lang="ts">
import BasePage from '@/components/BasePage.vue';
import BootstrapAlert from "@/components/BootstrapAlert.vue";
import CardElement from '@/components/CardElement.vue';
import InputElement from '@/components/InputElement.vue';
import type { AcChargerConfig } from "@/types/AcChargerConfig";
import { authHeader, handleResponse } from '@/utils/authentication';
import { defineComponent } from 'vue';
export default defineComponent({
components: {
BasePage,
BootstrapAlert,
CardElement,
InputElement,
},
data() {
return {
dataLoading: true,
acChargerConfigList: {} as AcChargerConfig,
alertMessage: "",
alertType: "info",
showAlert: false,
};
},
created() {
this.getChargerConfig();
},
methods: {
getChargerConfig() {
this.dataLoading = true;
fetch("/api/huawei/config", { headers: authHeader() })
.then((response) => handleResponse(response, this.$emitter, this.$router))
.then((data) => {
this.acChargerConfigList = data;
this.dataLoading = false;
});
},
saveChargerConfig(e: Event) {
e.preventDefault();
const formData = new FormData();
formData.append("data", JSON.stringify(this.acChargerConfigList));
fetch("/api/huawei/config", {
method: "POST",
headers: authHeader(),
body: formData,
})
.then((response) => handleResponse(response, this.$emitter, this.$router))
.then(
(response) => {
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
this.alertType = response.type;
this.showAlert = true;
}
);
},
},
});
</script>

View File

@ -114,6 +114,9 @@
</div>
</div>
<VedirectView v-show="liveData.vedirect.enabled" />
<div v-show="liveData.huawei.enabled" >
<HuaweiView/>
</div>
</BasePage>
<div class="modal" id="eventView" tabindex="-1">
@ -325,6 +328,7 @@ import HintView from '@/components/HintView.vue';
import InverterChannelInfo from "@/components/InverterChannelInfo.vue";
import InverterTotalInfo from '@/components/InverterTotalInfo.vue';
import VedirectView from '@/components/VedirectView.vue';
import HuaweiView from '@/components/HuaweiView.vue'
import type { DevInfoStatus } from '@/types/DevInfoStatus';
import type { EventlogItems } from '@/types/EventlogStatus';
import type { LimitConfig } from '@/types/LimitConfig';
@ -365,7 +369,8 @@ export default defineComponent({
BIconToggleOff,
BIconToggleOn,
BIconXCircleFill,
VedirectView
VedirectView,
HuaweiView
},
data() {
return {