Merge remote-tracking branch 'tbnobody/OpenDTU/master'

This commit is contained in:
helgeerbe 2022-11-03 09:27:56 +01:00
commit b3295f5f33
45 changed files with 331 additions and 194 deletions

View File

@ -24,6 +24,9 @@ serial will be replaced with the serial number of the inverter.
| [serial]/device/fwbuilddatetime | R | Build date / time of inverter firmware | | | [serial]/device/fwbuilddatetime | R | Build date / time of inverter firmware | |
| [serial]/device/hwpartnumber | R | Hardware part number of the inverter | | | [serial]/device/hwpartnumber | R | Hardware part number of the inverter | |
| [serial]/device/hwversion | R | Hardware version of the inverter | | | [serial]/device/hwversion | R | Hardware version of the inverter | |
| [serial]/status/reachable | R | Indicates whether the inverter is reachable | 0 or 1 |
| [serial]/status/producing | R | Indicates whether the inverter is producing AC power | 0 or 1 |
| [serial]/status/last_update | R | Unix timestamp of last inverter statistics udpate | seconds since JAN 01 1970 (UTC) |
### AC channel / global specific topics ### AC channel / global specific topics
@ -62,8 +65,6 @@ cmd topics are used to set values. Status topics are updated from values set in
| ----------------------------------------- | ----- | ---------------------------------------------------- | -------------------------- | | ----------------------------------------- | ----- | ---------------------------------------------------- | -------------------------- |
| [serial]/status/limit_relative | R | Current applied production limit of the inverter | % of total possible output | | [serial]/status/limit_relative | R | Current applied production limit of the inverter | % of total possible output |
| [serial]/status/limit_absolute | R | Current applied production limit of the inverter | Watt (W) | | [serial]/status/limit_absolute | R | Current applied production limit of the inverter | Watt (W) |
| [serial]/status/reachable | R | Indicates whether the inverter is reachable | 0 or 1 |
| [serial]/status/producing | R | Indicates whether the inverter is producing AC power | 0 or 1 |
| [serial]/cmd/limit_persistent_relative | W | Set the inverter limit as a percentage of total production capability. The value will survive the night without power. The updated value will show up in the web GUI and limit_relative topic immediatly. | % | | [serial]/cmd/limit_persistent_relative | W | Set the inverter limit as a percentage of total production capability. The value will survive the night without power. The updated value will show up in the web GUI and limit_relative topic immediatly. | % |
| [serial]/cmd/limit_persistent_absolute | W | Set the inverter limit as a absolute value. The value will survive the night without power. The updated value will show up in the web GUI and limit_relative topic after around 4 minutes. | Watt (W) | | [serial]/cmd/limit_persistent_absolute | W | Set the inverter limit as a absolute value. The value will survive the night without power. The updated value will show up in the web GUI and limit_relative topic after around 4 minutes. | Watt (W) |
| [serial]/cmd/limit_nonpersistent_relative | W | Set the inverter limit as a percentage of total production capability. The value will reset to the last persistent value at night without power. The updated value will show up in the web GUI and limit_relative topic immediatly. | % | | [serial]/cmd/limit_nonpersistent_relative | W | Set the inverter limit as a percentage of total production capability. The value will reset to the last persistent value at night without power. The updated value will show up in the web GUI and limit_relative topic immediatly. | % |

View File

@ -44,7 +44,7 @@ const byteAssign_fieldDeviceClass_t deviceFieldAssignment[] = {
{ FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT }, { FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT },
{ FLD_F, DEVICE_CLS_FREQ, STATE_CLS_MEASUREMENT }, { FLD_F, DEVICE_CLS_FREQ, STATE_CLS_MEASUREMENT },
{ FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT }, { FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT },
{ FLD_PCT, DEVICE_CLS_POWER_FACTOR, STATE_CLS_MEASUREMENT }, { FLD_PF, DEVICE_CLS_POWER_FACTOR, STATE_CLS_MEASUREMENT },
{ FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE }, { FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE },
{ FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE }, { FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE },
{ FLD_PRA, DEVICE_CLS_REACTIVE_POWER, STATE_CLS_MEASUREMENT } { FLD_PRA, DEVICE_CLS_REACTIVE_POWER, STATE_CLS_MEASUREMENT }

View File

@ -30,7 +30,7 @@ private:
FLD_PAC, FLD_PAC,
FLD_F, FLD_F,
FLD_T, FLD_T,
FLD_PCT, FLD_PF,
FLD_EFF, FLD_EFF,
FLD_IRR, FLD_IRR,
FLD_PRA FLD_PRA

View File

@ -13,7 +13,8 @@ public:
private: private:
void generateJsonResponse(JsonVariant& root); void generateJsonResponse(JsonVariant& root);
void addField(JsonVariant& root, uint8_t idx, std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId, String topic = ""); void addField(JsonObject& root, uint8_t idx, std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId, String topic = "");
void addTotalField(JsonObject& root, String name, float value, String unit, uint8_t digits);
void onLivedataStatus(AsyncWebServerRequest* request); void onLivedataStatus(AsyncWebServerRequest* request);
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);

View File

@ -3,6 +3,7 @@
* CommandAbstract * CommandAbstract
* DevControlCommand * DevControlCommand
* ActivePowerControlCommand * ActivePowerControlCommand
* PowerControlCommand
* MultiDataCommand * MultiDataCommand
* AlarmDataCommand * AlarmDataCommand
* DevInfoAllCommand * DevInfoAllCommand

View File

@ -24,9 +24,9 @@ private:
{ FLD_PAC, UNIT_W, CH0, 18, 2, 10, false }, { FLD_PAC, UNIT_W, CH0, 18, 2, 10, false },
{ FLD_PRA, UNIT_VA, CH0, 20, 2, 10, false }, { FLD_PRA, UNIT_VA, CH0, 20, 2, 10, false },
{ FLD_F, UNIT_HZ, CH0, 16, 2, 100, false }, { FLD_F, UNIT_HZ, CH0, 16, 2, 100, false },
{ FLD_PCT, UNIT_PCT, CH0, 24, 2, 10, false }, { FLD_PF, UNIT_NONE, CH0, 24, 2, 1000, false },
{ FLD_T, UNIT_C, CH0, 26, 2, 10, true }, { FLD_T, UNIT_C, CH0, 26, 2, 10, true },
{ FLD_EVT_LOG, UNIT_CNT, CH0, 28, 2, 1, false }, { FLD_EVT_LOG, UNIT_NONE, CH0, 28, 2, 1, false },
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC, false }, { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC, false },
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC, false }, { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC, false },
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC, false }, { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC, false },

View File

@ -31,9 +31,9 @@ private:
{ FLD_PAC, UNIT_W, CH0, 30, 2, 10, false }, { FLD_PAC, UNIT_W, CH0, 30, 2, 10, false },
{ FLD_PRA, UNIT_VA, CH0, 32, 2, 10, false }, { FLD_PRA, UNIT_VA, CH0, 32, 2, 10, false },
{ FLD_F, UNIT_HZ, CH0, 28, 2, 100, false }, { FLD_F, UNIT_HZ, CH0, 28, 2, 100, false },
{ FLD_PCT, UNIT_PCT, CH0, 36, 2, 10, false }, { FLD_PF, UNIT_NONE, CH0, 36, 2, 1000, false },
{ FLD_T, UNIT_C, CH0, 38, 2, 10, true }, { FLD_T, UNIT_C, CH0, 38, 2, 10, true },
{ FLD_EVT_LOG, UNIT_CNT, CH0, 40, 2, 1, false }, { FLD_EVT_LOG, UNIT_NONE, CH0, 40, 2, 1, false },
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC, false }, { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC, false },
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC, false }, { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC, false },
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC, false }, { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC, false },

View File

@ -45,9 +45,9 @@ private:
{ FLD_PAC, UNIT_W, CH0, 50, 2, 10, false }, { FLD_PAC, UNIT_W, CH0, 50, 2, 10, false },
{ FLD_PRA, UNIT_VA, CH0, 52, 2, 10, false }, { FLD_PRA, UNIT_VA, CH0, 52, 2, 10, false },
{ FLD_F, UNIT_HZ, CH0, 48, 2, 100, false }, { FLD_F, UNIT_HZ, CH0, 48, 2, 100, false },
{ FLD_PCT, UNIT_PCT, CH0, 56, 2, 10, false }, { FLD_PF, UNIT_NONE, CH0, 56, 2, 1000, false },
{ FLD_T, UNIT_C, CH0, 58, 2, 10, true }, { FLD_T, UNIT_C, CH0, 58, 2, 10, true },
{ FLD_EVT_LOG, UNIT_CNT, CH0, 60, 2, 1, false }, { FLD_EVT_LOG, UNIT_NONE, CH0, 60, 2, 1, false },
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC, false }, { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC, false },
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC, false }, { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC, false },
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC, false }, { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC, false },

View File

@ -120,6 +120,25 @@ const char* StatisticsParser::getChannelFieldName(uint8_t channel, uint8_t field
return fields[b[pos].fieldId]; return fields[b[pos].fieldId];
} }
uint8_t StatisticsParser::getChannelFieldDigits(uint8_t channel, uint8_t fieldId)
{
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
const byteAssign_t* b = _byteAssignment;
switch (b[pos].div) {
case 1:
return 0;
case 10:
return 1;
case 100:
return 2;
case 1000:
return 3;
default:
return 2;
}
}
uint8_t StatisticsParser::getChannelCount() uint8_t StatisticsParser::getChannelCount()
{ {
const byteAssign_t* b = _byteAssignment; const byteAssign_t* b = _byteAssignment;

View File

@ -16,7 +16,7 @@ enum {
UNIT_C, UNIT_C,
UNIT_PCT, UNIT_PCT,
UNIT_VA, UNIT_VA,
UNIT_CNT UNIT_NONE
}; };
const char* const units[] = { "V", "A", "W", "Wh", "kWh", "Hz", "°C", "%", "var", "" }; const char* const units[] = { "V", "A", "W", "Wh", "kWh", "Hz", "°C", "%", "var", "" };
@ -32,7 +32,7 @@ enum {
FLD_PAC, FLD_PAC,
FLD_F, FLD_F,
FLD_T, FLD_T,
FLD_PCT, FLD_PF,
FLD_EFF, FLD_EFF,
FLD_IRR, FLD_IRR,
FLD_PRA, FLD_PRA,
@ -83,6 +83,7 @@ public:
bool hasChannelFieldValue(uint8_t channel, uint8_t fieldId); bool hasChannelFieldValue(uint8_t channel, uint8_t fieldId);
const char* getChannelFieldUnit(uint8_t channel, uint8_t fieldId); const char* getChannelFieldUnit(uint8_t channel, uint8_t fieldId);
const char* getChannelFieldName(uint8_t channel, uint8_t fieldId); const char* getChannelFieldName(uint8_t channel, uint8_t fieldId);
uint8_t getChannelFieldDigits(uint8_t channel, uint8_t fieldId);
uint8_t getChannelCount(); uint8_t getChannelCount();

View File

@ -71,6 +71,12 @@ void MqttPublishingClass::loop()
MqttSettings.publish(subtopic + "/status/reachable", String(inv->isReachable())); MqttSettings.publish(subtopic + "/status/reachable", String(inv->isReachable()));
MqttSettings.publish(subtopic + "/status/producing", String(inv->isProducing())); MqttSettings.publish(subtopic + "/status/producing", String(inv->isProducing()));
if (inv->Statistics()->getLastUpdate() > 0) {
MqttSettings.publish(subtopic + "/status/last_update", String(std::time(0) - (millis() - inv->Statistics()->getLastUpdate()) / 1000));
} else {
MqttSettings.publish(subtopic + "/status/last_update", String(0));
}
uint32_t lastUpdate = inv->Statistics()->getLastUpdate(); uint32_t lastUpdate = inv->Statistics()->getLastUpdate();
if (lastUpdate > 0 && lastUpdate != _lastPublishStats[i]) { if (lastUpdate > 0 && lastUpdate != _lastPublishStats[i]) {
_lastPublishStats[i] = lastUpdate; _lastPublishStats[i] = lastUpdate;

View File

@ -73,57 +73,78 @@ void WebApiWsLiveClass::loop()
void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root) void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
{ {
JsonArray invArray = root.createNestedArray("inverters");
float totalPower = 0;
float totalYieldDay = 0;
float totalYieldTotal = 0;
// Loop all inverters // Loop all inverters
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
auto inv = Hoymiles.getInverterByPos(i); auto inv = Hoymiles.getInverterByPos(i);
if (inv == nullptr) {
continue;
}
root[i][F("serial")] = inv->serialString(); JsonObject invObject = invArray.createNestedObject();
root[i][F("name")] = inv->name();
root[i][F("data_age")] = (millis() - inv->Statistics()->getLastUpdate()) / 1000; invObject[F("serial")] = inv->serialString();
root[i][F("reachable")] = inv->isReachable(); invObject[F("name")] = inv->name();
root[i][F("producing")] = inv->isProducing(); invObject[F("data_age")] = (millis() - inv->Statistics()->getLastUpdate()) / 1000;
root[i][F("limit_relative")] = inv->SystemConfigPara()->getLimitPercent(); invObject[F("reachable")] = inv->isReachable();
invObject[F("producing")] = inv->isProducing();
invObject[F("limit_relative")] = inv->SystemConfigPara()->getLimitPercent();
if (inv->DevInfo()->getMaxPower() > 0) { if (inv->DevInfo()->getMaxPower() > 0) {
root[i][F("limit_absolute")] = inv->SystemConfigPara()->getLimitPercent() * inv->DevInfo()->getMaxPower() / 100.0; invObject[F("limit_absolute")] = inv->SystemConfigPara()->getLimitPercent() * inv->DevInfo()->getMaxPower() / 100.0;
} else { } else {
root[i][F("limit_absolute")] = -1; invObject[F("limit_absolute")] = -1;
} }
// Loop all channels // Loop all channels
for (uint8_t c = 0; c <= inv->Statistics()->getChannelCount(); c++) { for (uint8_t c = 0; c <= inv->Statistics()->getChannelCount(); c++) {
addField(root, i, inv, c, FLD_PAC); addField(invObject, i, inv, c, FLD_PAC);
addField(root, i, inv, c, FLD_UAC); addField(invObject, i, inv, c, FLD_UAC);
addField(root, i, inv, c, FLD_IAC); addField(invObject, i, inv, c, FLD_IAC);
if (c == 0) { if (c == 0) {
addField(root, i, inv, c, FLD_PDC, F("Power DC")); addField(invObject, i, inv, c, FLD_PDC, F("Power DC"));
} else { } else {
addField(root, i, inv, c, FLD_PDC); addField(invObject, i, inv, c, FLD_PDC);
} }
addField(root, i, inv, c, FLD_UDC); addField(invObject, i, inv, c, FLD_UDC);
addField(root, i, inv, c, FLD_IDC); addField(invObject, i, inv, c, FLD_IDC);
addField(root, i, inv, c, FLD_YD); addField(invObject, i, inv, c, FLD_YD);
addField(root, i, inv, c, FLD_YT); addField(invObject, i, inv, c, FLD_YT);
addField(root, i, inv, c, FLD_F); addField(invObject, i, inv, c, FLD_F);
addField(root, i, inv, c, FLD_T); addField(invObject, i, inv, c, FLD_T);
addField(root, i, inv, c, FLD_PCT); addField(invObject, i, inv, c, FLD_PF);
addField(root, i, inv, c, FLD_PRA); addField(invObject, i, inv, c, FLD_PRA);
addField(root, i, inv, c, FLD_EFF); addField(invObject, i, inv, c, FLD_EFF);
addField(root, i, inv, c, FLD_IRR); addField(invObject, i, inv, c, FLD_IRR);
} }
if (inv->Statistics()->hasChannelFieldValue(CH0, FLD_EVT_LOG)) { if (inv->Statistics()->hasChannelFieldValue(CH0, FLD_EVT_LOG)) {
root[i][F("events")] = inv->EventLog()->getEntryCount(); invObject[F("events")] = inv->EventLog()->getEntryCount();
} else { } else {
root[i][F("events")] = -1; invObject[F("events")] = -1;
} }
if (inv->Statistics()->getLastUpdate() > _newestInverterTimestamp) { if (inv->Statistics()->getLastUpdate() > _newestInverterTimestamp) {
_newestInverterTimestamp = inv->Statistics()->getLastUpdate(); _newestInverterTimestamp = inv->Statistics()->getLastUpdate();
} }
}
totalPower += inv->Statistics()->getChannelFieldValue(CH0, FLD_PAC);
totalYieldDay += inv->Statistics()->getChannelFieldValue(CH0, FLD_YD);
totalYieldTotal += inv->Statistics()->getChannelFieldValue(CH0, FLD_YT);
} }
void WebApiWsLiveClass::addField(JsonVariant& root, uint8_t idx, std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId, String topic) JsonObject totalObj = root.createNestedObject("total");
// todo: Fixed hard coded name, unit and digits
addTotalField(totalObj, "Power", totalPower, "W", 1);
addTotalField(totalObj, "YieldDay", totalYieldDay, "Wh", 0);
addTotalField(totalObj, "YieldTotal", totalYieldTotal, "kWh", 2);
}
void WebApiWsLiveClass::addField(JsonObject& root, uint8_t idx, std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId, String topic)
{ {
if (inv->Statistics()->hasChannelFieldValue(channel, fieldId)) { if (inv->Statistics()->hasChannelFieldValue(channel, fieldId)) {
String chanName; String chanName;
@ -132,11 +153,19 @@ void WebApiWsLiveClass::addField(JsonVariant& root, uint8_t idx, std::shared_ptr
} else { } else {
chanName = topic; chanName = topic;
} }
root[idx][String(channel)][chanName]["v"] = inv->Statistics()->getChannelFieldValue(channel, fieldId); root[String(channel)][chanName]["v"] = inv->Statistics()->getChannelFieldValue(channel, fieldId);
root[idx][String(channel)][chanName]["u"] = inv->Statistics()->getChannelFieldUnit(channel, fieldId); root[String(channel)][chanName]["u"] = inv->Statistics()->getChannelFieldUnit(channel, fieldId);
root[String(channel)][chanName]["d"] = inv->Statistics()->getChannelFieldDigits(channel, fieldId);
} }
} }
void WebApiWsLiveClass::addTotalField(JsonObject& root, String name, float value, String unit, uint8_t digits)
{
root[name]["v"] = value;
root[name]["u"] = unit;
root[name]["d"] = digits;
}
void WebApiWsLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) void WebApiWsLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
{ {
if (type == WS_EVT_CONNECT) { if (type == WS_EVT_CONNECT) {
@ -152,8 +181,8 @@ void WebApiWsLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketC
void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request) void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
{ {
AsyncJsonResponse* response = new AsyncJsonResponse(true, 40960U); AsyncJsonResponse* response = new AsyncJsonResponse(false, 40960U);
JsonVariant root = response->getRoot().as<JsonVariant>(); JsonVariant root = response->getRoot();
generateJsonResponse(root); generateJsonResponse(root);

View File

@ -21,18 +21,18 @@
"devDependencies": { "devDependencies": {
"@rushstack/eslint-patch": "^1.2.0", "@rushstack/eslint-patch": "^1.2.0",
"@types/bootstrap": "^5.2.5", "@types/bootstrap": "^5.2.5",
"@types/node": "^18.11.4", "@types/node": "^18.11.8",
"@types/spark-md5": "^3.0.2", "@types/spark-md5": "^3.0.2",
"@vitejs/plugin-vue": "^3.1.2", "@vitejs/plugin-vue": "^3.2.0",
"@vue/eslint-config-typescript": "^11.0.2", "@vue/eslint-config-typescript": "^11.0.2",
"@vue/tsconfig": "^0.1.3", "@vue/tsconfig": "^0.1.3",
"eslint": "^8.26.0", "eslint": "^8.26.0",
"eslint-plugin-vue": "^9.6.0", "eslint-plugin-vue": "^9.7.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"typescript": "^4.8.4", "typescript": "^4.8.4",
"vite": "^3.1.8", "vite": "^3.2.2",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-css-injected-by-js": "^2.1.0", "vite-plugin-css-injected-by-js": "^2.1.1",
"vue-tsc": "^1.0.9" "vue-tsc": "^1.0.9"
} }
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary"> <div class="card-header text-bg-primary">
Firmware Information Firmware Information
</div> </div>
<div class="card-body"> <div class="card-body">

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary"> <div class="card-header text-bg-primary">
Hardware Information Hardware Information
</div> </div>
<div class="card-body"> <div class="card-body">

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary"> <div class="card-header text-bg-primary">
Network Interface (Access Point) Network Interface (Access Point)
</div> </div>
<div class="card-body"> <div class="card-body">

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary"> <div class="card-header text-bg-primary">
Network Interface ({{ networkStatus.network_mode }}) Network Interface ({{ networkStatus.network_mode }})
</div> </div>
<div class="card-body"> <div class="card-body">

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="card" :class="{ 'border-info': channelNumber == 0 }"> <div class="card" :class="{ 'border-info': channelNumber == 0 }">
<div v-if="channelNumber >= 1" class="card-header">String {{ channelNumber }}</div> <div v-if="channelNumber >= 1" class="card-header">String {{ channelNumber }}</div>
<div v-if="channelNumber == 0" class="card-header bg-info">Phase {{ channelNumber + 1 }}</div> <div v-if="channelNumber == 0" class="card-header text-bg-info">Phase {{ channelNumber + 1 }}</div>
<div class="card-body"> <div class="card-body">
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">
<thead> <thead>
@ -15,7 +15,7 @@
<tr v-for="(property, key) in channelData" :key="`prop-${key}`"> <tr v-for="(property, key) in channelData" :key="`prop-${key}`">
<template v-if="property"> <template v-if="property">
<th scope="row">{{ key }}</th> <th scope="row">{{ key }}</th>
<td style="text-align: right">{{ formatNumber(property.v) }}</td> <td style="text-align: right">{{ formatNumber(property.v, property.d) }}</td>
<td>{{ property.u }}</td> <td>{{ property.u }}</td>
</template> </template>
</tr> </tr>
@ -28,6 +28,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, type PropType } from 'vue'; import { defineComponent, type PropType } from 'vue';
import type { InverterStatistics } from '@/types/LiveDataStatus'; import type { InverterStatistics } from '@/types/LiveDataStatus';
import { formatNumber } from '@/utils';
export default defineComponent({ export default defineComponent({
props: { props: {
@ -35,11 +36,7 @@ export default defineComponent({
channelNumber: { type: Number, required: true }, channelNumber: { type: Number, required: true },
}, },
methods: { methods: {
formatNumber(num: number) { formatNumber,
return new Intl.NumberFormat(
undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }
).format(num);
},
}, },
}); });
</script> </script>

View File

@ -0,0 +1,55 @@
<template>
<div class="row row-cols-1 row-cols-md-3 g-3">
<div class="col">
<div class="card">
<div class="card-header text-bg-success">Total Yield Total</div>
<div class="card-body">
<p class="card-text text-center">
<h2>{{ formatNumber(totalData.YieldTotal.v, totalData.YieldTotal.d) }}
<small class="text-muted">{{ totalData.YieldTotal.u }}</small>
</h2>
</p>
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="card-header text-bg-success">Total Yield Day</div>
<div class="card-body">
<p class="card-text text-center">
<h2>{{ formatNumber(totalData.YieldDay.v, totalData.YieldDay.d) }}
<small class="text-muted">{{ totalData.YieldDay.u }}</small>
</h2>
</p>
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="card-header text-bg-success">Total Power</div>
<div class="card-body">
<p class="card-text text-center">
<h2>{{ formatNumber(totalData.Power.v, totalData.Power.d) }}
<small class="text-muted">{{ totalData.Power.u }}</small>
</h2>
</p>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, type PropType } from 'vue';
import type { Total } from '@/types/LiveDataStatus';
import { formatNumber } from '@/utils';
export default defineComponent({
props: {
totalData: { type: Object as PropType<Total>, required: true },
},
methods: {
formatNumber,
},
});
</script>

View File

@ -3,7 +3,7 @@
<tbody> <tbody>
<tr> <tr>
<td>Current Limit</td> <td>Current Limit</td>
<td>{{ formatNumber(limitData.limit) }}%</td> <td>{{ formatNumber(limitData.limit, 2) }}%</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -11,6 +11,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { formatNumber } from '@/utils';
declare interface LimitData { declare interface LimitData {
limit: number, limit: number,
@ -20,14 +21,8 @@ export default defineComponent({
props: { props: {
limitData: { type: Object as () => LimitData, required: true }, limitData: { type: Object as () => LimitData, required: true },
}, },
computed: { methods: {
formatNumber() { formatNumber,
return (num: number) => { }
return new Intl.NumberFormat(
undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }
).format(num)
};
},
},
}); });
</script> </script>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary">Memory Information</div> <div class="card-header text-bg-primary">Memory Information</div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover table-condensed"> <table class="table table-hover table-condensed">

View File

@ -1,7 +1,7 @@
<template> <template>
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark"> <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="#">OpenDTU</a> <a class="navbar-brand" href="#"><span class="text-warning"><BIconSun width="30" height="30" class="d-inline-block align-text-top"/></span> OpenDTU</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup"
aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation"> aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
@ -85,8 +85,12 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { BIconSun } from 'bootstrap-icons-vue';
export default defineComponent({ export default defineComponent({
components: {
BIconSun,
},
methods: { methods: {
onClick() { onClick() {
this.$refs.navbarCollapse && (this.$refs.navbarCollapse as HTMLElement).classList.remove("show"); this.$refs.navbarCollapse && (this.$refs.navbarCollapse as HTMLElement).classList.remove("show");

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary"> <div class="card-header text-bg-primary">
Radio Information Radio Information
</div> </div>
<div class="card-body"> <div class="card-body">
@ -10,8 +10,8 @@
<tr> <tr>
<th>Chip Status</th> <th>Chip Status</th>
<td class="badge" :class="{ <td class="badge" :class="{
'bg-danger': !systemStatus.radio_connected, 'text-bg-danger': !systemStatus.radio_connected,
'bg-success': systemStatus.radio_connected, 'text-bg-success': systemStatus.radio_connected,
}"> }">
<span v-if="systemStatus.radio_connected">connected</span> <span v-if="systemStatus.radio_connected">connected</span>
<span v-else>not connected</span> <span v-else>not connected</span>
@ -20,9 +20,9 @@
<tr> <tr>
<th>Chip Type</th> <th>Chip Type</th>
<td class="badge" :class="{ <td class="badge" :class="{
'bg-danger': systemStatus.radio_connected && !systemStatus.radio_pvariant, 'text-bg-danger': systemStatus.radio_connected && !systemStatus.radio_pvariant,
'bg-success': systemStatus.radio_connected && systemStatus.radio_pvariant, 'text-bg-success': systemStatus.radio_connected && systemStatus.radio_pvariant,
'bg-secondary': !systemStatus.radio_connected, 'text-bg-secondary': !systemStatus.radio_connected,
}"> }">
<span <span
v-if="systemStatus.radio_connected && systemStatus.radio_pvariant">nRF24L01+</span> v-if="systemStatus.radio_connected && systemStatus.radio_pvariant">nRF24L01+</span>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary"> <div class="card-header text-bg-primary">
WiFi Information (Access Point) WiFi Information (Access Point)
</div> </div>
<div class="card-body"> <div class="card-body">
@ -10,8 +10,8 @@
<tr> <tr>
<th>Status</th> <th>Status</th>
<td class="badge" :class="{ <td class="badge" :class="{
'bg-danger': !networkStatus.ap_status, 'text-bg-danger': !networkStatus.ap_status,
'bg-success': networkStatus.ap_status, 'text-bg-success': networkStatus.ap_status,
}"> }">
<span v-if="networkStatus.ap_status">enabled</span> <span v-if="networkStatus.ap_status">enabled</span>
<span v-else>disabled</span> <span v-else>disabled</span>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary"> <div class="card-header text-bg-primary">
WiFi Information (Station) WiFi Information (Station)
</div> </div>
<div class="card-body"> <div class="card-body">
@ -10,8 +10,8 @@
<tr> <tr>
<th>Status</th> <th>Status</th>
<td class="badge" :class="{ <td class="badge" :class="{
'bg-danger': !networkStatus.sta_status, 'text-bg-danger': !networkStatus.sta_status,
'bg-success': networkStatus.sta_status, 'text-bg-success': networkStatus.sta_status,
}"> }">
<span v-if="networkStatus.sta_status">enabled</span> <span v-if="networkStatus.sta_status">enabled</span>
<span v-else>disabled</span> <span v-else>disabled</span>

View File

@ -1,6 +1,7 @@
export interface ValueObject { export interface ValueObject {
v: number, // value v: number, // value
u: string, // unit u: string, // unit
d: number, // digits
}; };
export interface InverterStatistics { export interface InverterStatistics {
@ -30,7 +31,16 @@ export interface Inverter {
[key: number]: InverterStatistics, [key: number]: InverterStatistics,
}; };
export interface Inverters extends Array<Inverter>{}; export interface Total {
Power: ValueObject,
YieldDay: ValueObject,
YieldTotal: ValueObject,
};
export interface LiveData {
inverters: Inverter[],
total: Total,
}
// Ve.Direct // Ve.Direct
export interface Vedirect { export interface Vedirect {

View File

@ -1,9 +1,12 @@
import { timestampToString } from './time'; import { timestampToString } from './time';
import { formatNumber } from './number';
export { export {
timestampToString, timestampToString,
formatNumber,
}; };
export default { export default {
timestampToString, timestampToString,
formatNumber,
} }

View File

@ -0,0 +1,5 @@
export const formatNumber = (num: number, digits: number): string => {
return new Intl.NumberFormat(
undefined, { minimumFractionDigits: digits, maximumFractionDigits: digits }
).format(num);
}

View File

@ -5,7 +5,7 @@
<h2 class="accordion-header" id="headingOne"> <h2 class="accordion-header" id="headingOne">
<button class="accordion-button" type="button" data-bs-toggle="collapse" <button class="accordion-button" type="button" data-bs-toggle="collapse"
data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne"> data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
<span class="badge bg-secondary"> <span class="badge text-bg-secondary">
<BIconInfoCircle class="fs-4" /> <BIconInfoCircle class="fs-4" />
</span>&nbsp;Project Origin </span>&nbsp;Project Origin
</button> </button>
@ -41,7 +41,7 @@
<h2 class="accordion-header" id="headingTwo"> <h2 class="accordion-header" id="headingTwo">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"> data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
<span class="badge bg-secondary"> <span class="badge text-bg-secondary">
<BIconActivity class="fs-4" /> <BIconActivity class="fs-4" />
</span>&nbsp;News &amp; Updates </span>&nbsp;News &amp; Updates
</button> </button>
@ -58,7 +58,7 @@
<h2 class="accordion-header" id="headingThree"> <h2 class="accordion-header" id="headingThree">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree"> data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
<span class="badge bg-secondary"> <span class="badge text-bg-secondary">
<BIconBug class="fs-4" /> <BIconBug class="fs-4" />
</span>&nbsp;Error Reporting </span>&nbsp;Error Reporting
</button> </button>
@ -75,7 +75,7 @@
<h2 class="accordion-header" id="headingFour"> <h2 class="accordion-header" id="headingFour">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour"> data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
<span class="badge bg-secondary"> <span class="badge text-bg-secondary">
<BIconChat class="fs-4" /> <BIconChat class="fs-4" />
</span>&nbsp;Discussion </span>&nbsp;Discussion
</button> </button>

View File

@ -5,7 +5,7 @@
</BootstrapAlert> </BootstrapAlert>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary">Backup: Configuration File Backup</div> <div class="card-header text-bg-primary">Backup: Configuration File Backup</div>
<div class="card-body text-center"> <div class="card-body text-center">
Backup the configuration file Backup the configuration file
<button class="btn btn-primary" @click="downloadConfig">Backup <button class="btn btn-primary" @click="downloadConfig">Backup
@ -14,7 +14,7 @@
</div> </div>
<div class="card mt-5"> <div class="card mt-5">
<div class="card-header text-white bg-primary">Restore: Restore the Configuration File</div> <div class="card-header text-bg-primary">Restore: Restore the Configuration File</div>
<div class="card-body text-center"> <div class="card-body text-center">
<div v-if="!uploading && UploadError != ''"> <div v-if="!uploading && UploadError != ''">
@ -66,7 +66,7 @@
</div> </div>
<div class="card mt-5"> <div class="card mt-5">
<div class="card-header text-white bg-primary">Initialize: Perform Factory Reset</div> <div class="card-header text-bg-primary">Initialize: Perform Factory Reset</div>
<div class="card-body text-center"> <div class="card-body text-center">
<button class="btn btn-danger" @click="onFactoryResetModal">Restore Factory-Default Settings <button class="btn btn-danger" @click="onFactoryResetModal">Restore Factory-Default Settings

View File

@ -6,7 +6,7 @@
<form @submit="saveDtuConfig"> <form @submit="saveDtuConfig">
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary">DTU Configuration</div> <div class="card-header text-bg-primary">DTU Configuration</div>
<div class="card-body"> <div class="card-body">
<div class="row mb-3"> <div class="row mb-3">
<label for="inputDtuSerial" class="col-sm-2 col-form-label">Serial:</label> <label for="inputDtuSerial" class="col-sm-2 col-form-label">Serial:</label>

View File

@ -9,7 +9,7 @@
</div> </div>
<div v-if="!loading && !uploading && OTAError != ''" class="card"> <div v-if="!loading && !uploading && OTAError != ''" class="card">
<div class="card-header text-white bg-danger">OTA Error</div> <div class="card-header text-bg-danger">OTA Error</div>
<div class="card-body text-center"> <div class="card-body text-center">
<p class="h1 mb-2"> <p class="h1 mb-2">
<BIconExclamationCircleFill /> <BIconExclamationCircleFill />
@ -30,12 +30,12 @@
</div> </div>
<div v-else-if="!loading && !uploading && OTASuccess" class="card"> <div v-else-if="!loading && !uploading && OTASuccess" class="card">
<div class="card-header text-white bg-success">OTA Status</div> <div class="card-header text-bg-success">OTA Status</div>
<div class="card-body text-center"> <div class="card-body text-center">
<span class="h1 mb-2"> <span class="h1 mb-2">
<BIconCheckCircle /> <BIconCheckCircle />
</span> </span>
<span> OTA Success </span> <span> OTA Success. The unit has been automatically restarted and will be available again in a few moments. </span>
<br /> <br />
<br /> <br />
<button class="btn btn-primary" @click="clear"> <button class="btn btn-primary" @click="clear">
@ -45,7 +45,7 @@
</div> </div>
<div v-else-if="!loading && !uploading" class="card"> <div v-else-if="!loading && !uploading" class="card">
<div class="card-header text-white bg-primary">Firmware Upload</div> <div class="card-header text-bg-primary">Firmware Upload</div>
<div class="card-body text-center"> <div class="card-body text-center">
<div class="form-group pt-2 mt-3"> <div class="form-group pt-2 mt-3">
<input class="form-control" type="file" ref="file" accept=".bin,.bin.gz" @change="uploadOTA" /> <input class="form-control" type="file" ref="file" accept=".bin,.bin.gz" @change="uploadOTA" />
@ -54,7 +54,7 @@
</div> </div>
<div v-else-if="!loading && uploading" class="card"> <div v-else-if="!loading && uploading" class="card">
<div class="card-header text-white bg-primary">Upload Progress</div> <div class="card-header text-bg-primary">Upload Progress</div>
<div class="card-body text-center"> <div class="card-body text-center">
<div class="progress"> <div class="progress">
<div class="progress-bar" role="progressbar" :style="{ width: progress + '%' }" <div class="progress-bar" role="progressbar" :style="{ width: progress + '%' }"

View File

@ -1,5 +1,6 @@
<template> <template>
<BasePage :title="'Live Data'" :isLoading="dataLoading" :isWideScreen="true"> <BasePage :title="'Live Data'" :isLoading="dataLoading" :isWideScreen="true">
<InverterTotalInfo :totalData="liveData.total" /><br />
<div class="row gy-3"> <div class="row gy-3">
<div class="col-sm-3 col-md-2" :style="[inverterData.length == 1 ? { 'display': 'none' } : {}]"> <div class="col-sm-3 col-md-2" :style="[inverterData.length == 1 ? { 'display': 'none' } : {}]">
<div class="nav nav-pills row-cols-sm-1" id="v-pills-tab" role="tablist" aria-orientation="vertical"> <div class="nav nav-pills row-cols-sm-1" id="v-pills-tab" role="tablist" aria-orientation="vertical">
@ -15,17 +16,19 @@
</div> </div>
</div> </div>
<div class="tab-content" id="v-pills-tabContent" :class="{'col-sm-9 col-md-10': inverterData.length > 1, <div class="tab-content" id="v-pills-tabContent" :class="{
'col-sm-12 col-md-12': inverterData.length == 1 }"> 'col-sm-9 col-md-10': inverterData.length > 1,
'col-sm-12 col-md-12': inverterData.length == 1
}">
<div v-for="inverter in inverterData" :key="inverter.serial" class="tab-pane fade show" <div v-for="inverter in inverterData" :key="inverter.serial" class="tab-pane fade show"
:id="'v-pills-' + inverter.serial" role="tabpanel" :id="'v-pills-' + inverter.serial" role="tabpanel"
:aria-labelledby="'v-pills-' + inverter.serial + '-tab'" tabindex="0"> :aria-labelledby="'v-pills-' + inverter.serial + '-tab'" tabindex="0">
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary d-flex justify-content-between align-items-center" <div class="card-header d-flex justify-content-between align-items-center"
:class="{ :class="{
'bg-danger': !inverter.reachable, 'text-bg-danger': !inverter.reachable,
'bg-warning': inverter.reachable && !inverter.producing, 'text-bg-warning': inverter.reachable && !inverter.producing,
'bg-primary': inverter.reachable && inverter.producing, 'text-bg-primary': inverter.reachable && inverter.producing,
}"> }">
<div class="p-1 flex-grow-1"> <div class="p-1 flex-grow-1">
<div class="d-flex flex-wrap"> <div class="d-flex flex-wrap">
@ -37,9 +40,8 @@
</div> </div>
<div style="padding-right: 2em;"> <div style="padding-right: 2em;">
Current Limit: <template v-if="inverter.limit_absolute > -1"> {{ Current Limit: <template v-if="inverter.limit_absolute > -1"> {{
inverter.limit_absolute.toFixed(0) }}W | </template>{{ formatNumber(inverter.limit_absolute, 0)
inverter.limit_relative.toFixed(0) }} W | </template>{{ formatNumber(inverter.limit_relative, 0) }} %
}}%
</div> </div>
<div style="padding-right: 2em;"> <div style="padding-right: 2em;">
Data Age: {{ inverter.data_age }} seconds Data Age: {{ inverter.data_age }} seconds
@ -77,7 +79,7 @@
@click="onShowEventlog(inverter.serial)" title="Show Eventlog"> @click="onShowEventlog(inverter.serial)" title="Show Eventlog">
<BIconJournalText style="font-size:24px;" /> <BIconJournalText style="font-size:24px;" />
<span <span
class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger"> class="position-absolute top-0 start-100 translate-middle badge rounded-pill text-bg-danger">
{{ inverter.events }} {{ inverter.events }}
<span class="visually-hidden">unread messages</span> <span class="visually-hidden">unread messages</span>
</span> </span>
@ -180,15 +182,16 @@
Limit:</label> Limit:</label>
<div class="col-sm-4"> <div class="col-sm-4">
<div class="input-group"> <div class="input-group">
<input type="number" class="form-control" id="inputCurrentLimit" <input type="text" class="form-control" id="inputCurrentLimit"
aria-describedby="currentLimitType" v-model="currentLimitRelative" disabled /> aria-describedby="currentLimitType" v-model="currentLimitRelative"
disabled />
<span class="input-group-text" id="currentLimitType">%</span> <span class="input-group-text" id="currentLimitType">%</span>
</div> </div>
</div> </div>
<div class="col-sm-4" v-if="currentLimitList.max_power > 0"> <div class="col-sm-4" v-if="currentLimitList.max_power > 0">
<div class="input-group"> <div class="input-group">
<input type="number" class="form-control" id="inputCurrentLimitAbsolute" <input type="text" class="form-control" id="inputCurrentLimitAbsolute"
aria-describedby="currentLimitTypeAbsolute" v-model="currentLimitAbsolute" aria-describedby="currentLimitTypeAbsolute" v-model="currentLimitAbsolute"
disabled /> disabled />
<span class="input-group-text" id="currentLimitTypeAbsolute">W</span> <span class="input-group-text" id="currentLimitTypeAbsolute">W</span>
@ -201,10 +204,10 @@
Status:</label> Status:</label>
<div class="col-sm-9"> <div class="col-sm-9">
<span class="badge" :class="{ <span class="badge" :class="{
'bg-danger': currentLimitList.limit_set_status == 'Failure', 'text-bg-danger': currentLimitList.limit_set_status == 'Failure',
'bg-warning': currentLimitList.limit_set_status == 'Pending', 'text-bg-warning': currentLimitList.limit_set_status == 'Pending',
'bg-success': currentLimitList.limit_set_status == 'Ok', 'text-bg-success': currentLimitList.limit_set_status == 'Ok',
'bg-secondary': currentLimitList.limit_set_status == 'Unknown', 'text-bg-secondary': currentLimitList.limit_set_status == 'Unknown',
}"> }">
{{ currentLimitList.limit_set_status }} {{ currentLimitList.limit_set_status }}
</span> </span>
@ -276,10 +279,10 @@
Status:</label> Status:</label>
<div class="col"> <div class="col">
<span class="badge" :class="{ <span class="badge" :class="{
'bg-danger': successCommandPower == 'Failure', 'text-bg-danger': successCommandPower == 'Failure',
'bg-warning': successCommandPower == 'Pending', 'text-bg-warning': successCommandPower == 'Pending',
'bg-success': successCommandPower == 'Ok', 'text-bg-success': successCommandPower == 'Ok',
'bg-secondary': successCommandPower == 'Unknown', 'text-bg-secondary': successCommandPower == 'Unknown',
}"> }">
{{ successCommandPower }} {{ successCommandPower }}
</span> </span>
@ -330,17 +333,20 @@ import EventLog from '@/components/EventLog.vue';
import DevInfo from '@/components/DevInfo.vue'; import DevInfo from '@/components/DevInfo.vue';
import BootstrapAlert from '@/components/BootstrapAlert.vue'; import BootstrapAlert from '@/components/BootstrapAlert.vue';
import InverterChannelInfo from "@/components/InverterChannelInfo.vue"; import InverterChannelInfo from "@/components/InverterChannelInfo.vue";
import InverterTotalInfo from '@/components/InverterTotalInfo.vue';
import VedirectView from '@/views/VedirectView.vue'; import VedirectView from '@/views/VedirectView.vue';
import type { DevInfoStatus } from '@/types/DevInfoStatus'; import type { DevInfoStatus } from '@/types/DevInfoStatus';
import type { EventlogItems } from '@/types/EventlogStatus'; import type { EventlogItems } from '@/types/EventlogStatus';
import type { Inverters } from '@/types/LiveDataStatus'; import type { LiveData, Inverter } from '@/types/LiveDataStatus';
import type { LimitStatus } from '@/types/LimitStatus'; import type { LimitStatus } from '@/types/LimitStatus';
import type { LimitConfig } from '@/types/LimitConfig'; import type { LimitConfig } from '@/types/LimitConfig';
import { formatNumber } from '@/utils';
export default defineComponent({ export default defineComponent({
components: { components: {
BasePage, BasePage,
InverterChannelInfo, InverterChannelInfo,
InverterTotalInfo,
EventLog, EventLog,
DevInfo, DevInfo,
BootstrapAlert, BootstrapAlert,
@ -362,7 +368,7 @@ export default defineComponent({
heartInterval: 0, heartInterval: 0,
dataAgeInterval: 0, dataAgeInterval: 0,
dataLoading: true, dataLoading: true,
inverterData: [] as Inverters, liveData: {} as LiveData,
isFirstFetchAfterConnect: true, isFirstFetchAfterConnect: true,
eventLogView: {} as bootstrap.Modal, eventLogView: {} as bootstrap.Modal,
eventLogList: {} as EventlogItems, eventLogList: {} as EventlogItems,
@ -377,7 +383,7 @@ export default defineComponent({
currentLimitList: {} as LimitStatus, currentLimitList: {} as LimitStatus,
targetLimitList: {} as LimitConfig, targetLimitList: {} as LimitConfig,
targetLimitMin: 10, targetLimitMin: 2,
targetLimitMax: 100, targetLimitMax: 100,
targetLimitTypeText: "Relative (%)", targetLimitTypeText: "Relative (%)",
targetLimitType: 1, targetLimitType: 1,
@ -428,23 +434,27 @@ export default defineComponent({
} }
}, },
computed: { computed: {
currentLimitAbsolute(): number { currentLimitAbsolute(): string {
if (this.currentLimitList.max_power > 0) { if (this.currentLimitList.max_power > 0) {
return Number((this.currentLimitList.limit_relative * this.currentLimitList.max_power / 100).toFixed(1)); return formatNumber(this.currentLimitList.limit_relative * this.currentLimitList.max_power / 100, 2);
} }
return 0; return "0";
}, },
currentLimitRelative(): number { currentLimitRelative(): string {
return Number((this.currentLimitList.limit_relative).toFixed(1)); return formatNumber(this.currentLimitList.limit_relative, 2);
},
inverterData(): Inverter[] {
return this.liveData.inverters;
} }
}, },
methods: { methods: {
formatNumber,
getInitialData() { getInitialData() {
this.dataLoading = true; this.dataLoading = true;
fetch("/api/livedata/status") fetch("/api/livedata/status")
.then((response) => response.json()) .then((response) => response.json())
.then((data) => { .then((data) => {
this.inverterData = data; this.liveData = data;
this.dataLoading = false; this.dataLoading = false;
}); });
}, },
@ -459,7 +469,7 @@ export default defineComponent({
this.socket.onmessage = (event) => { this.socket.onmessage = (event) => {
console.log(event); console.log(event);
this.inverterData = JSON.parse(event.data); this.liveData = JSON.parse(event.data);
this.dataLoading = false; this.dataLoading = false;
this.heartCheck(); // Reset heartbeat detection this.heartCheck(); // Reset heartbeat detection
}; };
@ -585,7 +595,7 @@ export default defineComponent({
onSelectType(type: number) { onSelectType(type: number) {
if (type == 1) { if (type == 1) {
this.targetLimitTypeText = "Relative (%)"; this.targetLimitTypeText = "Relative (%)";
this.targetLimitMin = 10; this.targetLimitMin = 2;
this.targetLimitMax = 100; this.targetLimitMax = 100;
} else { } else {
this.targetLimitTypeText = "Absolute (W)"; this.targetLimitTypeText = "Absolute (W)";

View File

@ -5,7 +5,7 @@
</BootstrapAlert> </BootstrapAlert>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary">Add a new Inverter</div> <div class="card-header text-bg-primary">Add a new Inverter</div>
<div class="card-body"> <div class="card-body">
<form class="form-inline" v-on:submit.prevent="onSubmit"> <form class="form-inline" v-on:submit.prevent="onSubmit">
<div class="form-group"> <div class="form-group">
@ -30,7 +30,7 @@
</div> </div>
<div class="card mt-5"> <div class="card mt-5">
<div class="card-header text-white bg-primary">Inverter List</div> <div class="card-header text-bg-primary">Inverter List</div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="table-responsive">
<table class="table"> <table class="table">

View File

@ -6,7 +6,7 @@
<form @submit="saveMqttConfig"> <form @submit="saveMqttConfig">
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary">MqTT Configuration</div> <div class="card-header text-bg-primary">MqTT Configuration</div>
<div class="card-body"> <div class="card-body">
<div class="row mb-3"> <div class="row mb-3">
<label class="col-sm-4 form-check-label" for="inputMqtt">Enable MqTT</label> <label class="col-sm-4 form-check-label" for="inputMqtt">Enable MqTT</label>
@ -32,7 +32,7 @@
</div> </div>
<div class="card mt-5" v-show="mqttConfigList.mqtt_enabled"> <div class="card mt-5" v-show="mqttConfigList.mqtt_enabled">
<div class="card-header text-white bg-primary"> <div class="card-header text-bg-primary">
MqTT Broker Parameter MqTT Broker Parameter
</div> </div>
<div class="card-body"> <div class="card-body">
@ -127,7 +127,7 @@
</div> </div>
<div class="card mt-5" v-show="mqttConfigList.mqtt_enabled"> <div class="card mt-5" v-show="mqttConfigList.mqtt_enabled">
<div class="card-header text-white bg-primary">LWT Parameters</div> <div class="card-header text-bg-primary">LWT Parameters</div>
<div class="card-body"> <div class="card-body">
<div class="row mb-3"> <div class="row mb-3">
<label for="inputLwtTopic" class="col-sm-2 col-form-label">LWT Topic:</label> <label for="inputLwtTopic" class="col-sm-2 col-form-label">LWT Topic:</label>
@ -164,7 +164,7 @@
</div> </div>
<div class="card mt-5" v-show="mqttConfigList.mqtt_enabled && mqttConfigList.mqtt_hass_enabled"> <div class="card mt-5" v-show="mqttConfigList.mqtt_enabled && mqttConfigList.mqtt_hass_enabled">
<div class="card-header text-white bg-primary">Home Assistant MQTT Auto Discovery Parameters</div> <div class="card-header text-bg-primary">Home Assistant MQTT Auto Discovery Parameters</div>
<div class="card-body"> <div class="card-body">
<div class="row mb-3"> <div class="row mb-3">
<label for="inputHassTopic" class="col-sm-2 col-form-label">Prefix Topic:</label> <label for="inputHassTopic" class="col-sm-2 col-form-label">Prefix Topic:</label>

View File

@ -1,7 +1,7 @@
<template> <template>
<BasePage :title="'MqTT Info'" :isLoading="dataLoading"> <BasePage :title="'MqTT Info'" :isLoading="dataLoading">
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary">Configuration Summary</div> <div class="card-header text-bg-primary">Configuration Summary</div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover table-condensed"> <table class="table table-hover table-condensed">
@ -9,8 +9,8 @@
<tr> <tr>
<th>Status</th> <th>Status</th>
<td class="badge" :class="{ <td class="badge" :class="{
'bg-danger': !mqttDataList.mqtt_enabled, 'text-bg-danger': !mqttDataList.mqtt_enabled,
'bg-success': mqttDataList.mqtt_enabled, 'text-bg-success': mqttDataList.mqtt_enabled,
}"> }">
<span v-if="mqttDataList.mqtt_enabled">enabled</span> <span v-if="mqttDataList.mqtt_enabled">enabled</span>
<span v-else>disabled</span> <span v-else>disabled</span>
@ -39,8 +39,8 @@
<tr> <tr>
<th>Retain</th> <th>Retain</th>
<td class="badge" :class="{ <td class="badge" :class="{
'bg-danger': !mqttDataList.mqtt_retain, 'text-bg-danger': !mqttDataList.mqtt_retain,
'bg-success': mqttDataList.mqtt_retain, 'text-bg-success': mqttDataList.mqtt_retain,
}"> }">
<span v-if="mqttDataList.mqtt_retain">enabled</span> <span v-if="mqttDataList.mqtt_retain">enabled</span>
<span v-else>disabled</span> <span v-else>disabled</span>
@ -49,8 +49,8 @@
<tr> <tr>
<th>TLS</th> <th>TLS</th>
<td class="badge" :class="{ <td class="badge" :class="{
'bg-danger': !mqttDataList.mqtt_tls, 'text-bg-danger': !mqttDataList.mqtt_tls,
'bg-success': mqttDataList.mqtt_tls, 'text-bg-success': mqttDataList.mqtt_tls,
}"> }">
<span v-if="mqttDataList.mqtt_tls">enabled</span> <span v-if="mqttDataList.mqtt_tls">enabled</span>
<span v-else>disabled</span> <span v-else>disabled</span>
@ -67,7 +67,7 @@
</div> </div>
<div class="card mt-5"> <div class="card mt-5">
<div class="card-header text-white bg-primary">Home Assistant MQTT Auto Discovery Configuration Summary <div class="card-header text-bg-primary">Home Assistant MQTT Auto Discovery Configuration Summary
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="table-responsive">
@ -76,8 +76,8 @@
<tr> <tr>
<th>Status</th> <th>Status</th>
<td class="badge" :class="{ <td class="badge" :class="{
'bg-danger': !mqttDataList.mqtt_hass_enabled, 'text-bg-danger': !mqttDataList.mqtt_hass_enabled,
'bg-success': mqttDataList.mqtt_hass_enabled, 'text-bg-success': mqttDataList.mqtt_hass_enabled,
}"> }">
<span v-if="mqttDataList.mqtt_hass_enabled">enabled</span> <span v-if="mqttDataList.mqtt_hass_enabled">enabled</span>
<span v-else>disabled</span> <span v-else>disabled</span>
@ -90,8 +90,8 @@
<tr> <tr>
<th>Retain</th> <th>Retain</th>
<td class="badge" :class="{ <td class="badge" :class="{
'bg-danger': !mqttDataList.mqtt_hass_retain, 'text-bg-danger': !mqttDataList.mqtt_hass_retain,
'bg-success': mqttDataList.mqtt_hass_retain, 'text-bg-success': mqttDataList.mqtt_hass_retain,
}"> }">
<span v-if="mqttDataList.mqtt_hass_retain">enabled</span> <span v-if="mqttDataList.mqtt_hass_retain">enabled</span>
<span v-else>disabled</span> <span v-else>disabled</span>
@ -100,8 +100,8 @@
<tr> <tr>
<th>Expire</th> <th>Expire</th>
<td class="badge" :class="{ <td class="badge" :class="{
'bg-danger': !mqttDataList.mqtt_hass_expire, 'text-bg-danger': !mqttDataList.mqtt_hass_expire,
'bg-success': mqttDataList.mqtt_hass_expire, 'text-bg-success': mqttDataList.mqtt_hass_expire,
}"> }">
<span v-if="mqttDataList.mqtt_hass_expire">enabled</span> <span v-if="mqttDataList.mqtt_hass_expire">enabled</span>
<span v-else>disabled</span> <span v-else>disabled</span>
@ -110,8 +110,8 @@
<tr> <tr>
<th>Individual Panels</th> <th>Individual Panels</th>
<td class="badge" :class="{ <td class="badge" :class="{
'bg-danger': !mqttDataList.mqtt_hass_individualpanels, 'text-bg-danger': !mqttDataList.mqtt_hass_individualpanels,
'bg-success': mqttDataList.mqtt_hass_individualpanels, 'text-bg-success': mqttDataList.mqtt_hass_individualpanels,
}"> }">
<span v-if="mqttDataList.mqtt_hass_individualpanels">enabled</span> <span v-if="mqttDataList.mqtt_hass_individualpanels">enabled</span>
<span v-else>disabled</span> <span v-else>disabled</span>
@ -124,7 +124,7 @@
</div> </div>
<div class="card mt-5"> <div class="card mt-5">
<div class="card-header text-white bg-primary">Runtime Summary</div> <div class="card-header text-bg-primary">Runtime Summary</div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover table-condensed"> <table class="table table-hover table-condensed">
@ -132,8 +132,8 @@
<tr> <tr>
<th>Connection Status</th> <th>Connection Status</th>
<td class="badge" :class="{ <td class="badge" :class="{
'bg-danger': !mqttDataList.mqtt_connected, 'text-bg-danger': !mqttDataList.mqtt_connected,
'bg-success': mqttDataList.mqtt_connected, 'text-bg-success': mqttDataList.mqtt_connected,
}"> }">
<span v-if="mqttDataList.mqtt_connected">connected</span> <span v-if="mqttDataList.mqtt_connected">connected</span>
<span v-else>disconnected</span> <span v-else>disconnected</span>

View File

@ -6,7 +6,7 @@
<form @submit="saveNetworkConfig"> <form @submit="saveNetworkConfig">
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary">WiFi Configuration</div> <div class="card-header text-bg-primary">WiFi Configuration</div>
<div class="card-body"> <div class="card-body">
<div class="row mb-3"> <div class="row mb-3">
<label for="inputSSID" class="col-sm-2 col-form-label">WiFi SSID:</label> <label for="inputSSID" class="col-sm-2 col-form-label">WiFi SSID:</label>
@ -50,7 +50,7 @@
</div> </div>
<div class="card" v-show="!networkConfigList.dhcp"> <div class="card" v-show="!networkConfigList.dhcp">
<div class="card-header text-white bg-primary"> <div class="card-header text-bg-primary">
Static IP Configuration Static IP Configuration
</div> </div>
<div class="card-body"> <div class="card-body">

View File

@ -6,7 +6,7 @@
<form @submit="saveNtpConfig"> <form @submit="saveNtpConfig">
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary">NTP Configuration</div> <div class="card-header text-bg-primary">NTP Configuration</div>
<div class="card-body"> <div class="card-body">
<div class="row mb-3"> <div class="row mb-3">
<label for="inputNtpServer" class="col-sm-2 col-form-label">Time Server:</label> <label for="inputNtpServer" class="col-sm-2 col-form-label">Time Server:</label>
@ -41,7 +41,7 @@
</form> </form>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary">Manual Time Synchronization</div> <div class="card-header text-bg-primary">Manual Time Synchronization</div>
<div class="card-body"> <div class="card-body">
<div class="row mb-3"> <div class="row mb-3">
<label for="currentMcuTime" class="col-sm-2 col-form-label">Current OpenDTU Time:</label> <label for="currentMcuTime" class="col-sm-2 col-form-label">Current OpenDTU Time:</label>

View File

@ -1,7 +1,7 @@
<template> <template>
<BasePage :title="'NTP Info'" :isLoading="dataLoading"> <BasePage :title="'NTP Info'" :isLoading="dataLoading">
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary">Configuration Summary</div> <div class="card-header text-bg-primary">Configuration Summary</div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover table-condensed"> <table class="table table-hover table-condensed">
@ -25,7 +25,7 @@
</div> </div>
<div class="card mt-5"> <div class="card mt-5">
<div class="card-header text-white bg-primary">Current Time</div> <div class="card-header text-bg-primary">Current Time</div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover table-condensed"> <table class="table table-hover table-condensed">
@ -33,8 +33,8 @@
<tr> <tr>
<th>Status</th> <th>Status</th>
<td class="badge" :class="{ <td class="badge" :class="{
'bg-danger': !ntpDataList.ntp_status, 'text-bg-danger': !ntpDataList.ntp_status,
'bg-success': ntpDataList.ntp_status, 'text-bg-success': ntpDataList.ntp_status,
}"> }">
<span v-if="ntpDataList.ntp_status">synced</span> <span v-if="ntpDataList.ntp_status">synced</span>
<span v-else>not synced</span> <span v-else>not synced</span>

View File

@ -6,7 +6,7 @@
<form @submit="savePasswordConfig"> <form @submit="savePasswordConfig">
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary">Admin password</div> <div class="card-header text-bg-primary">Admin password</div>
<div class="card-body"> <div class="card-body">
<div class="row mb-3"> <div class="row mb-3">
<label for="inputPassword" class="col-sm-2 col-form-label">Password:</label> <label for="inputPassword" class="col-sm-2 col-form-label">Password:</label>

View File

@ -15,7 +15,7 @@
<template v-if="!dataLoading"> <template v-if="!dataLoading">
<form @submit="saveVedirectConfig"> <form @submit="saveVedirectConfig">
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary">Ve.direct Configuration</div> <div class="card-header bg-primary">Ve.direct Configuration</div>
<div class="card-body"> <div class="card-body">
<div class="row mb-3"> <div class="row mb-3">
<label class="col-sm-2 form-check-label" for="inputVedirect">Enable Ve.direct</label> <label class="col-sm-2 form-check-label" for="inputVedirect">Enable Ve.direct</label>

View File

@ -12,7 +12,7 @@
<template v-if="!dataLoading"> <template v-if="!dataLoading">
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary">Configuration Summary</div> <div class="card-header bg-primary">Configuration Summary</div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover table-condensed"> <table class="table table-hover table-condensed">

View File

@ -10,7 +10,7 @@
<div class="row gy-3"> <div class="row gy-3">
<div class="tab-content col-sm-12 col-md-12" id="v-pills-tabContent"> <div class="tab-content col-sm-12 col-md-12" id="v-pills-tabContent">
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary d-flex justify-content-between align-items-center" <div class="card-header d-flex justify-content-between align-items-center"
:class="{ :class="{
'bg-danger': vedirectData.age_critical, 'bg-danger': vedirectData.age_critical,
'bg-primary': !vedirectData.age_critical, 'bg-primary': !vedirectData.age_critical,

View File

@ -99,10 +99,10 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
"@types/node@^18.11.4": "@types/node@^18.11.8":
version "18.11.4" version "18.11.8"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.4.tgz#7017a52e18dfaad32f55eebd539993014441949c" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.8.tgz#16d222a58d4363a2a359656dd20b28414de5d265"
integrity sha512-BxcJpBu8D3kv/GZkx/gSMz6VnTJREBj/4lbzYOQueUOELkt8WrO6zAcSPmp9uRPEW/d+lUO8QK0W2xnS1hEU0A== integrity sha512-uGwPWlE0Hj972KkHtCDVwZ8O39GmyjfMane1Z3GUBGGnkZ2USDq7SxLpVIiIHpweY9DS0QTDH0Nw7RNBsAAZ5A==
"@types/spark-md5@^3.0.2": "@types/spark-md5@^3.0.2":
version "3.0.2" version "3.0.2"
@ -223,10 +223,10 @@
"@typescript-eslint/types" "5.32.0" "@typescript-eslint/types" "5.32.0"
eslint-visitor-keys "^3.3.0" eslint-visitor-keys "^3.3.0"
"@vitejs/plugin-vue@^3.1.2": "@vitejs/plugin-vue@^3.2.0":
version "3.1.2" version "3.2.0"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-3.1.2.tgz#3cd52114e8871a0b5e7bd7d837469c032e503036" resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-3.2.0.tgz#a1484089dd85d6528f435743f84cdd0d215bbb54"
integrity sha512-3zxKNlvA3oNaKDYX0NBclgxTQ1xaFdL7PzwF6zj9tGFziKwmBa3Q/6XcJQxudlT81WxDjEhHmevvIC4Orc1LhQ== integrity sha512-E0tnaL4fr+qkdCNxJ+Xd0yM31UwMkQje76fsDVBBUCoGOUPexu2VDUYHL8P4CwV+zMvWw6nlRw19OnRKmYAJpw==
"@volar/language-core@1.0.9": "@volar/language-core@1.0.9":
version "1.0.9" version "1.0.9"
@ -785,10 +785,10 @@ escape-string-regexp@^4.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
eslint-plugin-vue@^9.6.0: eslint-plugin-vue@^9.7.0:
version "9.6.0" version "9.7.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.6.0.tgz#5d1825b93d54595b1ba97106843e1d28cf3bb291" resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.7.0.tgz#d391b9864f128ea2d1ee4dabeafb5f7c0cea981f"
integrity sha512-zzySkJgVbFCylnG2+9MDF7N+2Rjze2y0bF8GyUNpFOnT8mCMfqqtLDJkHBuYu9N/psW1A6DVbQhPkP92E+qakA== integrity sha512-DrOO3WZCZEwcLsnd3ohFwqCoipGRSTKTBTnLwdhqAbYZtzWl0o7D+D8ZhlmiZvABKTEl8AFsqH1GHGdybyoQmw==
dependencies: dependencies:
eslint-utils "^3.0.0" eslint-utils "^3.0.0"
natural-compare "^1.4.0" natural-compare "^1.4.0"
@ -1633,7 +1633,7 @@ postcss@^8.1.10:
picocolors "^1.0.0" picocolors "^1.0.0"
source-map-js "^1.0.2" source-map-js "^1.0.2"
postcss@^8.4.16: postcss@^8.4.18:
version "8.4.18" version "8.4.18"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.18.tgz#6d50046ea7d3d66a85e0e782074e7203bc7fbca2" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.18.tgz#6d50046ea7d3d66a85e0e782074e7203bc7fbca2"
integrity sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA== integrity sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==
@ -1706,10 +1706,10 @@ rimraf@^3.0.2:
dependencies: dependencies:
glob "^7.1.3" glob "^7.1.3"
rollup@~2.78.0: rollup@^2.79.1:
version "2.78.1" version "2.79.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.78.1.tgz#52fe3934d9c83cb4f7c4cb5fb75d88591be8648f" resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7"
integrity sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg== integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
@ -1978,20 +1978,20 @@ vite-plugin-compression@^0.5.1:
debug "^4.3.3" debug "^4.3.3"
fs-extra "^10.0.0" fs-extra "^10.0.0"
vite-plugin-css-injected-by-js@^2.1.0: vite-plugin-css-injected-by-js@^2.1.1:
version "2.1.0" version "2.1.1"
resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-2.1.0.tgz#3c75c8f2eb41128f6fe22ca9e3eae6bde63231f0" resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-2.1.1.tgz#a79275241c61f1c8d55d228f5b2dded450a580e4"
integrity sha512-p5Tn63xfYd1j4Hhnn9YuVMF3t7eLpiHejKNcIeGOWNU60bsw8WXmLQSVGtraw3C6qNz5LnF3S8R1SQLe4/LBYw== integrity sha512-gjIG6iFWde32oRr/bK9CsfN+jdbura2y4GlDzeOiEm6py38ud8dXzFl9zwmHjOjZdr8XEgQ9TovzVGNzp47esw==
vite@^3.1.8: vite@^3.2.2:
version "3.1.8" version "3.2.2"
resolved "https://registry.yarnpkg.com/vite/-/vite-3.1.8.tgz#fa29144167d19b773baffd65b3972ea4c12359c9" resolved "https://registry.yarnpkg.com/vite/-/vite-3.2.2.tgz#280762bfaf47bcea1d12698427331c0009ac7c1f"
integrity sha512-m7jJe3nufUbuOfotkntGFupinL/fmuTNuQmiVE7cH2IZMuf4UbfbGYMUT3jVWgGYuRVLY9j8NnrRqgw5rr5QTg== integrity sha512-pLrhatFFOWO9kS19bQ658CnRYzv0WLbsPih6R+iFeEEhDOuYgYCX2rztUViMz/uy/V8cLCJvLFeiOK7RJEzHcw==
dependencies: dependencies:
esbuild "^0.15.9" esbuild "^0.15.9"
postcss "^8.4.16" postcss "^8.4.18"
resolve "^1.22.1" resolve "^1.22.1"
rollup "~2.78.0" rollup "^2.79.1"
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"

Binary file not shown.