inital version of full solar passthrough
Webinterface change to set full solar passthrough values Adding webapi and config changes to enable full solar passthrough over certain battery Soc inital version of full solar passthrough in power limiter Passthrough mode can be enabled via MQTT translations re-enable comment remove unused variable
This commit is contained in:
commit
e7c8a89bd3
@ -18,5 +18,30 @@
|
||||
"type": 0,
|
||||
"clk_mode": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "WT32-ETH01 with SSD1306",
|
||||
"nrf24": {
|
||||
"miso": 4,
|
||||
"mosi": 2,
|
||||
"clk": 32,
|
||||
"irq": 33,
|
||||
"en": 14,
|
||||
"cs": 15
|
||||
},
|
||||
"eth": {
|
||||
"enabled": true,
|
||||
"phy_addr": 1,
|
||||
"power": 16,
|
||||
"mdc": 23,
|
||||
"mdio": 18,
|
||||
"type": 0,
|
||||
"clk_mode": 0
|
||||
},
|
||||
"display": {
|
||||
"type": 2,
|
||||
"data": 5,
|
||||
"clk": 17
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -47,6 +47,7 @@ struct CHANNEL_CONFIG_T {
|
||||
struct INVERTER_CONFIG_T {
|
||||
uint64_t Serial;
|
||||
char Name[INV_MAX_NAME_STRLEN + 1];
|
||||
uint8_t Order;
|
||||
bool Poll_Enable;
|
||||
bool Poll_Enable_Night;
|
||||
bool Command_Enable;
|
||||
@ -82,6 +83,7 @@ struct CONFIG_T {
|
||||
char Ntp_TimezoneDescr[NTP_MAX_TIMEZONEDESCR_STRLEN + 1];
|
||||
double Ntp_Longitude;
|
||||
double Ntp_Latitude;
|
||||
uint8_t Ntp_SunsetType;
|
||||
|
||||
bool Mqtt_Enabled;
|
||||
uint Mqtt_Port;
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <TimeoutHelper.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
class DatastoreClass {
|
||||
public:
|
||||
@ -10,52 +12,69 @@ public:
|
||||
void loop();
|
||||
|
||||
// Sum of yield total of all enabled inverters, a inverter which is just disabled at night is also included
|
||||
float totalAcYieldTotalEnabled = 0;
|
||||
float getTotalAcYieldTotalEnabled();
|
||||
|
||||
// Sum of yield day of all enabled inverters, a inverter which is just disabled at night is also included
|
||||
float totalAcYieldDayEnabled = 0;
|
||||
float getTotalAcYieldDayEnabled();
|
||||
|
||||
// Sum of total AC power of all enabled inverters
|
||||
float totalAcPowerEnabled = 0;
|
||||
float getTotalAcPowerEnabled();
|
||||
|
||||
// Sum of total DC power of all enabled inverters
|
||||
float totalDcPowerEnabled = 0;
|
||||
float getTotalDcPowerEnabled();
|
||||
|
||||
// Sum of total DC power of all enabled inverters with maxStringPower set
|
||||
float totalDcPowerIrradiation = 0;
|
||||
float getTotalDcPowerIrradiation();
|
||||
|
||||
// Sum of total installed irradiation of all enabled inverters
|
||||
float totalDcIrradiationInstalled = 0;
|
||||
float getTotalDcIrradiationInstalled();
|
||||
|
||||
// Percentage (1-100) of total irradiation
|
||||
float totalDcIrradiation = 0;
|
||||
float getTotalDcIrradiation();
|
||||
|
||||
// Amount of relevant digits for yield total
|
||||
unsigned int totalAcYieldTotalDigits = 0;
|
||||
unsigned int getTotalAcYieldTotalDigits();
|
||||
|
||||
// Amount of relevant digits for yield total
|
||||
unsigned int totalAcYieldDayDigits = 0;
|
||||
unsigned int getTotalAcYieldDayDigits();
|
||||
|
||||
// Amount of relevant digits for AC power
|
||||
unsigned int totalAcPowerDigits = 0;
|
||||
unsigned int getTotalAcPowerDigits();
|
||||
|
||||
// Amount of relevant digits for DC power
|
||||
unsigned int totalDcPowerDigits = 0;
|
||||
unsigned int getTotalDcPowerDigits();
|
||||
|
||||
// True, if at least one inverter is reachable
|
||||
bool isAtLeastOneReachable = false;
|
||||
bool getIsAtLeastOneReachable();
|
||||
|
||||
// True if at least one inverter is producing
|
||||
bool isAtLeastOneProducing = false;
|
||||
bool getIsAtLeastOneProducing();
|
||||
|
||||
// True if all enabled inverters are producing
|
||||
bool isAllEnabledProducing = false;
|
||||
bool getIsAllEnabledProducing();
|
||||
|
||||
// True if all enabled inverters are reachable
|
||||
bool isAllEnabledReachable = false;
|
||||
bool getIsAllEnabledReachable();
|
||||
|
||||
private:
|
||||
TimeoutHelper _updateTimeout;
|
||||
SemaphoreHandle_t _xSemaphore;
|
||||
|
||||
float _totalAcYieldTotalEnabled = 0;
|
||||
float _totalAcYieldDayEnabled = 0;
|
||||
float _totalAcPowerEnabled = 0;
|
||||
float _totalDcPowerEnabled = 0;
|
||||
float _totalDcPowerIrradiation = 0;
|
||||
float _totalDcIrradiationInstalled = 0;
|
||||
float _totalDcIrradiation = 0;
|
||||
unsigned int _totalAcYieldTotalDigits = 0;
|
||||
unsigned int _totalAcYieldDayDigits = 0;
|
||||
unsigned int _totalAcPowerDigits = 0;
|
||||
unsigned int _totalDcPowerDigits = 0;
|
||||
bool _isAtLeastOneReachable = false;
|
||||
bool _isAtLeastOneProducing = false;
|
||||
bool _isAllEnabledProducing = false;
|
||||
bool _isAllEnabledReachable = false;
|
||||
};
|
||||
|
||||
extern DatastoreClass Datastore;
|
||||
@ -12,6 +12,7 @@ public:
|
||||
void loop();
|
||||
|
||||
bool isDayPeriod();
|
||||
bool isSunsetAvailable();
|
||||
bool sunsetTime(struct tm* info);
|
||||
bool sunriseTime(struct tm* info);
|
||||
|
||||
@ -20,6 +21,7 @@ private:
|
||||
|
||||
SunSet _sun;
|
||||
bool _isDayPeriod = true;
|
||||
bool _isSunsetAvailable = true;
|
||||
uint _sunriseMinutes = 0;
|
||||
uint _sunsetMinutes = 0;
|
||||
|
||||
|
||||
@ -28,6 +28,7 @@ enum WebApiError {
|
||||
InverterInvalidMaxChannel,
|
||||
InverterChanged,
|
||||
InverterDeleted,
|
||||
InverterOrdered,
|
||||
|
||||
LimitBase = 5000,
|
||||
LimitSerialZero,
|
||||
|
||||
@ -13,6 +13,7 @@ private:
|
||||
void onInverterAdd(AsyncWebServerRequest* request);
|
||||
void onInverterEdit(AsyncWebServerRequest* request);
|
||||
void onInverterDelete(AsyncWebServerRequest* request);
|
||||
void onInverterOrder(AsyncWebServerRequest* request);
|
||||
|
||||
AsyncWebServer* _server;
|
||||
};
|
||||
@ -25,6 +25,7 @@
|
||||
#define NTP_TIMEZONEDESCR "Europe/Berlin"
|
||||
#define NTP_LONGITUDE 10.4515f
|
||||
#define NTP_LATITUDE 51.1657f
|
||||
#define NTP_SUNSETTYPE 1
|
||||
|
||||
#define MQTT_ENABLED false
|
||||
#define MQTT_HOST ""
|
||||
|
||||
@ -121,17 +121,25 @@ void HoymilesRadio_CMT::loop()
|
||||
if (!_rxBuffer.empty()) {
|
||||
fragment_t f = _rxBuffer.back();
|
||||
if (checkFragmentCrc(&f)) {
|
||||
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterByFragment(&f);
|
||||
|
||||
if (nullptr != inv) {
|
||||
// Save packet in inverter rx buffer
|
||||
Hoymiles.getMessageOutput()->printf("RX %.2f MHz --> ", getFrequencyFromChannel(f.channel));
|
||||
dumpBuf(f.fragment, f.len, false);
|
||||
Hoymiles.getMessageOutput()->printf("| %d dBm\r\n", f.rssi);
|
||||
serial_u dtuId = convertSerialToRadioId(_dtuSerial);
|
||||
|
||||
inv->addRxFragment(f.fragment, f.len);
|
||||
} else {
|
||||
Hoymiles.getMessageOutput()->println("Inverter Not found!");
|
||||
// The CMT RF module does not filter foreign packages by itself.
|
||||
// Has to be done manually here.
|
||||
if (memcmp(&f.fragment[5], &dtuId.b[1], 4) == 0) {
|
||||
|
||||
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterByFragment(&f);
|
||||
|
||||
if (nullptr != inv) {
|
||||
// Save packet in inverter rx buffer
|
||||
Hoymiles.getMessageOutput()->printf("RX %.2f MHz --> ", getFrequencyFromChannel(f.channel));
|
||||
dumpBuf(f.fragment, f.len, false);
|
||||
Hoymiles.getMessageOutput()->printf("| %d dBm\r\n", f.rssi);
|
||||
|
||||
inv->addRxFragment(f.fragment, f.len);
|
||||
} else {
|
||||
Hoymiles.getMessageOutput()->println("Inverter Not found!");
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
@ -68,6 +68,11 @@ bool MultiDataCommand::handleResponse(InverterAbstract* inverter, fragment_t fra
|
||||
uint16_t crc = 0xffff, crcRcv = 0;
|
||||
|
||||
for (uint8_t i = 0; i < max_fragment_id; i++) {
|
||||
// Doublecheck if correct answer package
|
||||
if (fragment[i].mainCmd != (_payload[0] | 0x80)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (i == max_fragment_id - 1) {
|
||||
// Last packet
|
||||
crc = crc16(fragment[i].fragment, fragment[i].len - 2, crc);
|
||||
|
||||
@ -6,9 +6,10 @@
|
||||
#include "../Hoymiles.h"
|
||||
#include <cstring>
|
||||
|
||||
const std::array<const AlarmMessage_t, 76> AlarmLogParser::_alarmMessages = {{
|
||||
const std::array<const AlarmMessage_t, ALARM_MSG_COUNT> AlarmLogParser::_alarmMessages = {{
|
||||
{ AlarmMessageType_t::ALL, 1, "Inverter start" },
|
||||
{ AlarmMessageType_t::ALL, 2, "DTU command failed" },
|
||||
{ AlarmMessageType_t::ALL, 73, "Temperature >80°C" }, // https://github.com/tbnobody/OpenDTU/discussions/590#discussioncomment-6049750
|
||||
{ AlarmMessageType_t::ALL, 121, "Over temperature protection" },
|
||||
{ AlarmMessageType_t::ALL, 124, "Shut down by remote control" },
|
||||
{ AlarmMessageType_t::ALL, 125, "Grid configuration parameter error" },
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
#define ALARM_LOG_ENTRY_SIZE 12
|
||||
#define ALARM_LOG_PAYLOAD_SIZE (ALARM_LOG_ENTRY_COUNT * ALARM_LOG_ENTRY_SIZE + 4)
|
||||
|
||||
#define ALARM_MSG_COUNT 77
|
||||
|
||||
struct AlarmLogEntry_t {
|
||||
uint16_t MessageId;
|
||||
String Message;
|
||||
@ -50,5 +52,5 @@ private:
|
||||
|
||||
AlarmMessageType_t _messageType = AlarmMessageType_t::ALL;
|
||||
|
||||
static const std::array<const AlarmMessage_t, 76> _alarmMessages;
|
||||
static const std::array<const AlarmMessage_t, ALARM_MSG_COUNT> _alarmMessages;
|
||||
};
|
||||
@ -32,7 +32,8 @@ const devInfo_t devInfo[] = {
|
||||
{ { 0x10, 0x10, 0x51, ALL }, 450, "HMS-450" }, // 01
|
||||
{ { 0x10, 0x10, 0x71, ALL }, 500, "HMS-500" }, // 02
|
||||
{ { 0x10, 0x21, 0x11, ALL }, 600, "HMS-600" }, // 01
|
||||
{ { 0x10, 0x21, 0x41, ALL }, 800, "HMS-800" }, // 00
|
||||
{ { 0x10, 0x21, 0x41, ALL }, 800, "HMS-800" }, // 00
|
||||
{ { 0x10, 0x11, 0x51, ALL }, 900, "HMS-900" }, // 01
|
||||
{ { 0x10, 0x21, 0x71, ALL }, 1000, "HMS-1000" }, // 05
|
||||
{ { 0x10, 0x22, 0x41, ALL }, 1600, "HMS-1600" }, // 4
|
||||
{ { 0x10, 0x12, 0x51, ALL }, 1800, "HMS-1800" }, // 01
|
||||
|
||||
77
pio-scripts/create_factory_bin.py
Normal file
77
pio-scripts/create_factory_bin.py
Normal file
@ -0,0 +1,77 @@
|
||||
# Part of ESPEasy build toolchain.
|
||||
#
|
||||
# Combines separate bin files with their respective offsets into a single file
|
||||
# This single file must then be flashed to an ESP32 node with 0 offset.
|
||||
#
|
||||
# Original implementation: Bartłomiej Zimoń (@uzi18)
|
||||
# Maintainer: Gijs Noorlander (@TD-er)
|
||||
#
|
||||
# Special thanks to @Jason2866 (Tasmota) for helping debug flashing to >4MB flash
|
||||
# Thanks @jesserockz (esphome) for adapting to use esptool.py with merge_bin
|
||||
#
|
||||
# Typical layout of the generated file:
|
||||
# Offset | File
|
||||
# - 0x1000 | ~\.platformio\packages\framework-arduinoespressif32\tools\sdk\esp32\bin\bootloader_dout_40m.bin
|
||||
# - 0x8000 | ~\ESPEasy\.pio\build\<env name>\partitions.bin
|
||||
# - 0xe000 | ~\.platformio\packages\framework-arduinoespressif32\tools\partitions\boot_app0.bin
|
||||
# - 0x10000 | ~\ESPEasy\.pio\build\<env name>/<built binary>.bin
|
||||
|
||||
Import("env")
|
||||
|
||||
platform = env.PioPlatform()
|
||||
|
||||
import sys
|
||||
from os.path import join
|
||||
|
||||
sys.path.append(join(platform.get_package_dir("tool-esptoolpy")))
|
||||
import esptool
|
||||
|
||||
def esp32_create_combined_bin(source, target, env):
|
||||
print("Generating combined binary for serial flashing")
|
||||
|
||||
# The offset from begin of the file where the app0 partition starts
|
||||
# This is defined in the partition .csv file
|
||||
app_offset = 0x10000
|
||||
|
||||
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin")
|
||||
sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
|
||||
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
||||
chip = env.get("BOARD_MCU")
|
||||
flash_size = env.BoardConfig().get("upload.flash_size")
|
||||
flash_freq = env.BoardConfig().get("build.f_flash", '40m')
|
||||
flash_freq = flash_freq.replace('000000L', 'm')
|
||||
flash_mode = env.BoardConfig().get("build.flash_mode", "dio")
|
||||
memory_type = env.BoardConfig().get("build.arduino.memory_type", "qio_qspi")
|
||||
if flash_mode == "qio" or flash_mode == "qout":
|
||||
flash_mode = "dio"
|
||||
if memory_type == "opi_opi" or memory_type == "opi_qspi":
|
||||
flash_mode = "dout"
|
||||
cmd = [
|
||||
"--chip",
|
||||
chip,
|
||||
"merge_bin",
|
||||
"-o",
|
||||
new_file_name,
|
||||
"--flash_mode",
|
||||
flash_mode,
|
||||
"--flash_freq",
|
||||
flash_freq,
|
||||
"--flash_size",
|
||||
flash_size,
|
||||
]
|
||||
|
||||
print(" Offset | File")
|
||||
for section in sections:
|
||||
sect_adr, sect_file = section.split(" ", 1)
|
||||
print(f" - {sect_adr} | {sect_file}")
|
||||
cmd += [sect_adr, sect_file]
|
||||
|
||||
print(f" - {hex(app_offset)} | {firmware_name}")
|
||||
cmd += [hex(app_offset), firmware_name]
|
||||
|
||||
print('Using esptool.py arguments: %s' % ' '.join(cmd))
|
||||
|
||||
esptool.main(cmd)
|
||||
|
||||
|
||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)
|
||||
@ -29,7 +29,7 @@ build_unflags =
|
||||
lib_deps =
|
||||
https://github.com/yubox-node-org/ESPAsyncWebServer
|
||||
bblanchon/ArduinoJson @ ^6.21.2
|
||||
https://github.com/bertmelis/espMqttClient.git#v1.4.2
|
||||
https://github.com/bertmelis/espMqttClient.git#v1.4.3
|
||||
nrf24/RF24 @ ^1.4.5
|
||||
olikraus/U8g2 @ ^2.34.17
|
||||
buelowp/sunset @ ^1.1.7
|
||||
@ -40,6 +40,7 @@ lib_deps =
|
||||
extra_scripts =
|
||||
pre:pio-scripts/auto_firmware_version.py
|
||||
pre:pio-scripts/patch_apply.py
|
||||
post:pio-scripts/create_factory_bin.py
|
||||
|
||||
board_build.partitions = partitions_custom.csv
|
||||
board_build.filesystem = littlefs
|
||||
|
||||
@ -46,6 +46,7 @@ bool ConfigurationClass::write()
|
||||
ntp["timezone_descr"] = config.Ntp_TimezoneDescr;
|
||||
ntp["latitude"] = config.Ntp_Latitude;
|
||||
ntp["longitude"] = config.Ntp_Longitude;
|
||||
ntp["sunsettype"] = config.Ntp_SunsetType;
|
||||
|
||||
JsonObject mqtt = doc.createNestedObject("mqtt");
|
||||
mqtt["enabled"] = config.Mqtt_Enabled;
|
||||
@ -102,6 +103,7 @@ bool ConfigurationClass::write()
|
||||
JsonObject inv = inverters.createNestedObject();
|
||||
inv["serial"] = config.Inverter[i].Serial;
|
||||
inv["name"] = config.Inverter[i].Name;
|
||||
inv["order"] = config.Inverter[i].Order;
|
||||
inv["poll_enable"] = config.Inverter[i].Poll_Enable;
|
||||
inv["poll_enable_night"] = config.Inverter[i].Poll_Enable_Night;
|
||||
inv["command_enable"] = config.Inverter[i].Command_Enable;
|
||||
@ -241,6 +243,7 @@ bool ConfigurationClass::read()
|
||||
strlcpy(config.Ntp_TimezoneDescr, ntp["timezone_descr"] | NTP_TIMEZONEDESCR, sizeof(config.Ntp_TimezoneDescr));
|
||||
config.Ntp_Latitude = ntp["latitude"] | NTP_LATITUDE;
|
||||
config.Ntp_Longitude = ntp["longitude"] | NTP_LONGITUDE;
|
||||
config.Ntp_SunsetType = ntp["sunsettype"] | NTP_SUNSETTYPE;
|
||||
|
||||
JsonObject mqtt = doc["mqtt"];
|
||||
config.Mqtt_Enabled = mqtt["enabled"] | MQTT_ENABLED;
|
||||
@ -297,6 +300,7 @@ bool ConfigurationClass::read()
|
||||
JsonObject inv = inverters[i].as<JsonObject>();
|
||||
config.Inverter[i].Serial = inv["serial"] | 0ULL;
|
||||
strlcpy(config.Inverter[i].Name, inv["name"] | "", sizeof(config.Inverter[i].Name));
|
||||
config.Inverter[i].Order = inv["order"] | 0;
|
||||
|
||||
config.Inverter[i].Poll_Enable = inv["poll_enable"] | true;
|
||||
config.Inverter[i].Poll_Enable_Night = inv["poll_enable_night"] | true;
|
||||
|
||||
@ -6,10 +6,17 @@
|
||||
#include "Configuration.h"
|
||||
#include <Hoymiles.h>
|
||||
|
||||
#define DAT_SEMAPHORE_TAKE() \
|
||||
do { \
|
||||
} while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS)
|
||||
#define DAT_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore)
|
||||
|
||||
DatastoreClass Datastore;
|
||||
|
||||
DatastoreClass::DatastoreClass()
|
||||
{
|
||||
_xSemaphore = xSemaphoreCreateMutex();
|
||||
DAT_SEMAPHORE_GIVE(); // release before first use
|
||||
}
|
||||
|
||||
void DatastoreClass::init()
|
||||
@ -24,23 +31,25 @@ void DatastoreClass::loop()
|
||||
uint8_t isProducing = 0;
|
||||
uint8_t isReachable = 0;
|
||||
|
||||
totalAcYieldTotalEnabled = 0;
|
||||
totalAcYieldTotalDigits = 0;
|
||||
DAT_SEMAPHORE_TAKE();
|
||||
|
||||
totalAcYieldDayEnabled = 0;
|
||||
totalAcYieldDayDigits = 0;
|
||||
_totalAcYieldTotalEnabled = 0;
|
||||
_totalAcYieldTotalDigits = 0;
|
||||
|
||||
totalAcPowerEnabled = 0;
|
||||
totalAcPowerDigits = 0;
|
||||
_totalAcYieldDayEnabled = 0;
|
||||
_totalAcYieldDayDigits = 0;
|
||||
|
||||
totalDcPowerEnabled = 0;
|
||||
totalDcPowerDigits = 0;
|
||||
_totalAcPowerEnabled = 0;
|
||||
_totalAcPowerDigits = 0;
|
||||
|
||||
totalDcPowerIrradiation = 0;
|
||||
totalDcIrradiationInstalled = 0;
|
||||
_totalDcPowerEnabled = 0;
|
||||
_totalDcPowerDigits = 0;
|
||||
|
||||
isAllEnabledProducing = true;
|
||||
isAllEnabledReachable = true;
|
||||
_totalDcPowerIrradiation = 0;
|
||||
_totalDcIrradiationInstalled = 0;
|
||||
|
||||
_isAllEnabledProducing = true;
|
||||
_isAllEnabledReachable = true;
|
||||
|
||||
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
|
||||
auto inv = Hoymiles.getInverterByPos(i);
|
||||
@ -57,7 +66,7 @@ void DatastoreClass::loop()
|
||||
isProducing++;
|
||||
} else {
|
||||
if (inv->getEnablePolling()) {
|
||||
isAllEnabledProducing = false;
|
||||
_isAllEnabledProducing = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,42 +74,164 @@ void DatastoreClass::loop()
|
||||
isReachable++;
|
||||
} else {
|
||||
if (inv->getEnablePolling()) {
|
||||
isAllEnabledReachable = false;
|
||||
_isAllEnabledReachable = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& c : inv->Statistics()->getChannelsByType(TYPE_AC)) {
|
||||
if (cfg->Poll_Enable) {
|
||||
totalAcYieldTotalEnabled += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YT);
|
||||
totalAcYieldDayEnabled += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YD);
|
||||
_totalAcYieldTotalEnabled += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YT);
|
||||
_totalAcYieldDayEnabled += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YD);
|
||||
|
||||
totalAcYieldTotalDigits = max<unsigned int>(totalAcYieldTotalDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_YT));
|
||||
totalAcYieldDayDigits = max<unsigned int>(totalAcYieldDayDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_YD));
|
||||
_totalAcYieldTotalDigits = max<unsigned int>(_totalAcYieldTotalDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_YT));
|
||||
_totalAcYieldDayDigits = max<unsigned int>(_totalAcYieldDayDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_YD));
|
||||
}
|
||||
if (inv->getEnablePolling()) {
|
||||
totalAcPowerEnabled += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_PAC);
|
||||
totalAcPowerDigits = max<unsigned int>(totalAcPowerDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_PAC));
|
||||
_totalAcPowerEnabled += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_PAC);
|
||||
_totalAcPowerDigits = max<unsigned int>(_totalAcPowerDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_PAC));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& c : inv->Statistics()->getChannelsByType(TYPE_DC)) {
|
||||
if (inv->getEnablePolling()) {
|
||||
totalDcPowerEnabled += inv->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC);
|
||||
totalDcPowerDigits = max<unsigned int>(totalDcPowerDigits, inv->Statistics()->getChannelFieldDigits(TYPE_DC, c, FLD_PDC));
|
||||
_totalDcPowerEnabled += inv->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC);
|
||||
_totalDcPowerDigits = max<unsigned int>(_totalDcPowerDigits, inv->Statistics()->getChannelFieldDigits(TYPE_DC, c, FLD_PDC));
|
||||
|
||||
if (inv->Statistics()->getStringMaxPower(c) > 0) {
|
||||
totalDcPowerIrradiation += inv->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC);
|
||||
totalDcIrradiationInstalled += inv->Statistics()->getStringMaxPower(c);
|
||||
_totalDcPowerIrradiation += inv->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC);
|
||||
_totalDcIrradiationInstalled += inv->Statistics()->getStringMaxPower(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isAtLeastOneProducing = isProducing > 0;
|
||||
isAtLeastOneReachable = isReachable > 0;
|
||||
_isAtLeastOneProducing = isProducing > 0;
|
||||
_isAtLeastOneReachable = isReachable > 0;
|
||||
|
||||
totalDcIrradiation = totalDcIrradiationInstalled > 0 ? totalDcPowerIrradiation / totalDcIrradiationInstalled * 100.0f : 0;
|
||||
_totalDcIrradiation = _totalDcIrradiationInstalled > 0 ? _totalDcPowerIrradiation / _totalDcIrradiationInstalled * 100.0f : 0;
|
||||
|
||||
DAT_SEMAPHORE_GIVE();
|
||||
|
||||
_updateTimeout.reset();
|
||||
}
|
||||
}
|
||||
|
||||
float DatastoreClass::getTotalAcYieldTotalEnabled()
|
||||
{
|
||||
DAT_SEMAPHORE_TAKE();
|
||||
float retval = _totalAcYieldTotalEnabled;
|
||||
DAT_SEMAPHORE_GIVE();
|
||||
return retval;
|
||||
}
|
||||
|
||||
float DatastoreClass::getTotalAcYieldDayEnabled()
|
||||
{
|
||||
DAT_SEMAPHORE_TAKE();
|
||||
float retval = _totalAcYieldDayEnabled;
|
||||
DAT_SEMAPHORE_GIVE();
|
||||
return retval;
|
||||
}
|
||||
|
||||
float DatastoreClass::getTotalAcPowerEnabled()
|
||||
{
|
||||
DAT_SEMAPHORE_TAKE();
|
||||
float retval = _totalAcPowerEnabled;
|
||||
DAT_SEMAPHORE_GIVE();
|
||||
return retval;
|
||||
}
|
||||
|
||||
float DatastoreClass::getTotalDcPowerEnabled()
|
||||
{
|
||||
DAT_SEMAPHORE_TAKE();
|
||||
float retval = _totalDcPowerEnabled;
|
||||
DAT_SEMAPHORE_GIVE();
|
||||
return retval;
|
||||
}
|
||||
|
||||
float DatastoreClass::getTotalDcPowerIrradiation()
|
||||
{
|
||||
DAT_SEMAPHORE_TAKE();
|
||||
float retval = _totalDcPowerIrradiation;
|
||||
DAT_SEMAPHORE_GIVE();
|
||||
return retval;
|
||||
}
|
||||
|
||||
float DatastoreClass::getTotalDcIrradiationInstalled()
|
||||
{
|
||||
DAT_SEMAPHORE_TAKE();
|
||||
float retval = _totalDcIrradiationInstalled;
|
||||
DAT_SEMAPHORE_GIVE();
|
||||
return retval;
|
||||
}
|
||||
|
||||
float DatastoreClass::getTotalDcIrradiation()
|
||||
{
|
||||
DAT_SEMAPHORE_TAKE();
|
||||
float retval = _totalDcIrradiation;
|
||||
DAT_SEMAPHORE_GIVE();
|
||||
return retval;
|
||||
}
|
||||
|
||||
unsigned int DatastoreClass::getTotalAcYieldTotalDigits()
|
||||
{
|
||||
DAT_SEMAPHORE_TAKE();
|
||||
unsigned int retval = _totalAcYieldTotalDigits;
|
||||
DAT_SEMAPHORE_GIVE();
|
||||
return retval;
|
||||
}
|
||||
|
||||
unsigned int DatastoreClass::getTotalAcYieldDayDigits()
|
||||
{
|
||||
DAT_SEMAPHORE_TAKE();
|
||||
unsigned int retval = _totalAcYieldDayDigits;
|
||||
DAT_SEMAPHORE_GIVE();
|
||||
return retval;
|
||||
}
|
||||
|
||||
unsigned int DatastoreClass::getTotalAcPowerDigits()
|
||||
{
|
||||
DAT_SEMAPHORE_TAKE();
|
||||
unsigned int retval = _totalAcPowerDigits;
|
||||
DAT_SEMAPHORE_GIVE();
|
||||
return retval;
|
||||
}
|
||||
|
||||
unsigned int DatastoreClass::getTotalDcPowerDigits()
|
||||
{
|
||||
DAT_SEMAPHORE_TAKE();
|
||||
unsigned int retval = _totalDcPowerDigits;
|
||||
DAT_SEMAPHORE_GIVE();
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool DatastoreClass::getIsAtLeastOneReachable()
|
||||
{
|
||||
DAT_SEMAPHORE_TAKE();
|
||||
bool retval = _isAtLeastOneReachable;
|
||||
DAT_SEMAPHORE_GIVE();
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool DatastoreClass::getIsAtLeastOneProducing()
|
||||
{
|
||||
DAT_SEMAPHORE_TAKE();
|
||||
bool retval = _isAtLeastOneProducing;
|
||||
DAT_SEMAPHORE_GIVE();
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool DatastoreClass::getIsAllEnabledProducing()
|
||||
{
|
||||
DAT_SEMAPHORE_TAKE();
|
||||
bool retval = _isAllEnabledProducing;
|
||||
DAT_SEMAPHORE_GIVE();
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool DatastoreClass::getIsAllEnabledReachable()
|
||||
{
|
||||
DAT_SEMAPHORE_TAKE();
|
||||
bool retval = _isAllEnabledReachable;
|
||||
DAT_SEMAPHORE_GIVE();
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -141,12 +141,12 @@ void DisplayGraphicClass::loop()
|
||||
_display->clearBuffer();
|
||||
|
||||
//=====> Actual Production ==========
|
||||
if (Datastore.isAtLeastOneReachable) {
|
||||
if (Datastore.getIsAtLeastOneReachable()) {
|
||||
_display->setPowerSave(false);
|
||||
if (Datastore.totalAcPowerEnabled > 999) {
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_kw[_display_language], (Datastore.totalAcPowerEnabled / 1000));
|
||||
if (Datastore.getTotalAcPowerEnabled() > 999) {
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_kw[_display_language], (Datastore.getTotalAcPowerEnabled() / 1000));
|
||||
} else {
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_w[_display_language], Datastore.totalAcPowerEnabled);
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_w[_display_language], Datastore.getTotalAcPowerEnabled());
|
||||
}
|
||||
printText(_fmtText, 0);
|
||||
_previousMillis = millis();
|
||||
@ -164,10 +164,10 @@ void DisplayGraphicClass::loop()
|
||||
//<=======================
|
||||
|
||||
//=====> Today & Total Production =======
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_wh[_display_language], Datastore.totalAcYieldDayEnabled);
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_wh[_display_language], Datastore.getTotalAcYieldDayEnabled());
|
||||
printText(_fmtText, 1);
|
||||
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_total_kwh[_display_language], Datastore.totalAcYieldTotalEnabled);
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_total_kwh[_display_language], Datastore.getTotalAcYieldTotalEnabled());
|
||||
printText(_fmtText, 2);
|
||||
//<=======================
|
||||
|
||||
|
||||
@ -59,10 +59,10 @@ void LedSingleClass::loop()
|
||||
_ledState[1] = LedState_t::Off;
|
||||
if (Hoymiles.getNumInverters()) {
|
||||
// set LED status
|
||||
if (Datastore.isAllEnabledReachable && Datastore.isAllEnabledProducing) {
|
||||
if (Datastore.getIsAllEnabledReachable() && Datastore.getIsAllEnabledProducing()) {
|
||||
_ledState[1] = LedState_t::On;
|
||||
}
|
||||
if (Datastore.isAllEnabledReachable && !Datastore.isAllEnabledProducing) {
|
||||
if (Datastore.getIsAllEnabledReachable() && !Datastore.getIsAllEnabledProducing()) {
|
||||
_ledState[1] = LedState_t::Blink;
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,13 +22,13 @@ void MqttHandleInverterTotalClass::loop()
|
||||
}
|
||||
|
||||
if (_lastPublish.occured()) {
|
||||
MqttSettings.publish("ac/power", String(Datastore.totalAcPowerEnabled, Datastore.totalAcPowerDigits));
|
||||
MqttSettings.publish("ac/yieldtotal", String(Datastore.totalAcYieldTotalEnabled, Datastore.totalAcYieldTotalDigits));
|
||||
MqttSettings.publish("ac/yieldday", String(Datastore.totalAcYieldDayEnabled, Datastore.totalAcYieldDayDigits));
|
||||
MqttSettings.publish("ac/is_valid", String(Datastore.isAllEnabledReachable));
|
||||
MqttSettings.publish("dc/power", String(Datastore.totalDcPowerEnabled, Datastore.totalDcPowerDigits));
|
||||
MqttSettings.publish("dc/irradiation", String(Datastore.totalDcIrradiation, 3));
|
||||
MqttSettings.publish("dc/is_valid", String(Datastore.isAllEnabledReachable));
|
||||
MqttSettings.publish("ac/power", String(Datastore.getTotalAcPowerEnabled(), Datastore.getTotalAcPowerDigits()));
|
||||
MqttSettings.publish("ac/yieldtotal", String(Datastore.getTotalAcYieldTotalEnabled(), Datastore.getTotalAcYieldTotalDigits()));
|
||||
MqttSettings.publish("ac/yieldday", String(Datastore.getTotalAcYieldDayEnabled(), Datastore.getTotalAcYieldDayDigits()));
|
||||
MqttSettings.publish("ac/is_valid", String(Datastore.getIsAllEnabledReachable()));
|
||||
MqttSettings.publish("dc/power", String(Datastore.getTotalDcPowerEnabled(), Datastore.getTotalDcPowerDigits()));
|
||||
MqttSettings.publish("dc/irradiation", String(Datastore.getTotalDcIrradiation(), 3));
|
||||
MqttSettings.publish("dc/is_valid", String(Datastore.getIsAllEnabledReachable()));
|
||||
|
||||
_lastPublish.set(Configuration.get().Mqtt_PublishInterval * 1000);
|
||||
}
|
||||
|
||||
@ -29,6 +29,11 @@ bool SunPositionClass::isDayPeriod()
|
||||
return _isDayPeriod;
|
||||
}
|
||||
|
||||
bool SunPositionClass::isSunsetAvailable()
|
||||
{
|
||||
return _isSunsetAvailable;
|
||||
}
|
||||
|
||||
void SunPositionClass::updateSunData()
|
||||
{
|
||||
CONFIG_T const& config = Configuration.get();
|
||||
@ -37,7 +42,7 @@ void SunPositionClass::updateSunData()
|
||||
|
||||
struct tm timeinfo;
|
||||
if (!getLocalTime(&timeinfo, 5)) {
|
||||
_isDayPeriod = false;
|
||||
_isDayPeriod = true;
|
||||
_sunriseMinutes = 0;
|
||||
_sunsetMinutes = 0;
|
||||
_isValidInfo = false;
|
||||
@ -45,11 +50,43 @@ void SunPositionClass::updateSunData()
|
||||
}
|
||||
|
||||
_sun.setCurrentDate(1900 + timeinfo.tm_year, timeinfo.tm_mon + 1, timeinfo.tm_mday);
|
||||
_sunriseMinutes = static_cast<int>(_sun.calcCustomSunrise(SunSet::SUNSET_NAUTICAL));
|
||||
_sunsetMinutes = static_cast<int>(_sun.calcCustomSunset(SunSet::SUNSET_NAUTICAL));
|
||||
|
||||
double sunset_type;
|
||||
switch (config.Ntp_SunsetType) {
|
||||
case 0:
|
||||
sunset_type = SunSet::SUNSET_OFFICIAL;
|
||||
break;
|
||||
case 2:
|
||||
sunset_type = SunSet::SUNSET_CIVIL;
|
||||
break;
|
||||
case 3:
|
||||
sunset_type = SunSet::SUNSET_ASTONOMICAL;
|
||||
break;
|
||||
default:
|
||||
sunset_type = SunSet::SUNSET_NAUTICAL;
|
||||
break;
|
||||
}
|
||||
|
||||
double sunriseRaw = _sun.calcCustomSunrise(sunset_type);
|
||||
double sunsetRaw = _sun.calcCustomSunset(sunset_type);
|
||||
|
||||
// If no sunset/sunrise exists (e.g. astronomical calculation in summer)
|
||||
// assume it's day period
|
||||
if (std::isnan(sunriseRaw) || std::isnan(sunsetRaw)) {
|
||||
_isDayPeriod = true;
|
||||
_isSunsetAvailable = false;
|
||||
_sunriseMinutes = 0;
|
||||
_sunsetMinutes = 0;
|
||||
_isValidInfo = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_sunriseMinutes = static_cast<int>(sunriseRaw);
|
||||
_sunsetMinutes = static_cast<int>(sunsetRaw);
|
||||
uint minutesPastMidnight = timeinfo.tm_hour * 60 + timeinfo.tm_min;
|
||||
|
||||
_isDayPeriod = (minutesPastMidnight >= _sunriseMinutes) && (minutesPastMidnight < _sunsetMinutes);
|
||||
_isSunsetAvailable = true;
|
||||
_isValidInfo = true;
|
||||
}
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ void WebApiInverterClass::init(AsyncWebServer* server)
|
||||
_server->on("/api/inverter/add", HTTP_POST, std::bind(&WebApiInverterClass::onInverterAdd, this, _1));
|
||||
_server->on("/api/inverter/edit", HTTP_POST, std::bind(&WebApiInverterClass::onInverterEdit, this, _1));
|
||||
_server->on("/api/inverter/del", HTTP_POST, std::bind(&WebApiInverterClass::onInverterDelete, this, _1));
|
||||
_server->on("/api/inverter/order", HTTP_POST, std::bind(&WebApiInverterClass::onInverterOrder, this, _1));
|
||||
}
|
||||
|
||||
void WebApiInverterClass::loop()
|
||||
@ -44,6 +45,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
|
||||
JsonObject obj = data.createNestedObject();
|
||||
obj["id"] = i;
|
||||
obj["name"] = String(config.Inverter[i].Name);
|
||||
obj["order"] = config.Inverter[i].Order;
|
||||
|
||||
// Inverter Serial is read as HEX
|
||||
char buffer[sizeof(uint64_t) * 8 + 1];
|
||||
@ -390,3 +392,72 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
|
||||
|
||||
MqttHandleHass.forceUpdate();
|
||||
}
|
||||
|
||||
void WebApiInverterClass::onInverterOrder(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentials(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg["type"] = "warning";
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg["message"] = "No values found!";
|
||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg["message"] = "Data too large!";
|
||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
DynamicJsonDocument root(1024);
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg["message"] = "Failed to parse data!";
|
||||
retMsg["code"] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(root.containsKey("order"))) {
|
||||
retMsg["message"] = "Values are missing!";
|
||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
// The order array contains list or id in the right order
|
||||
JsonArray orderArray = root["order"].as<JsonArray>();
|
||||
uint8_t order = 0;
|
||||
for(JsonVariant id : orderArray) {
|
||||
uint8_t inverter_id = id.as<uint8_t>();
|
||||
if (inverter_id < INV_MAX_COUNT) {
|
||||
INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[inverter_id];
|
||||
inverter.Order = order;
|
||||
}
|
||||
order++;
|
||||
}
|
||||
|
||||
Configuration.write();
|
||||
|
||||
retMsg["type"] = "success";
|
||||
retMsg["message"] = "Inverter order saved!";
|
||||
retMsg["code"] = WebApiError::InverterOrdered;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
@ -52,14 +52,21 @@ void WebApiNtpClass::onNtpStatus(AsyncWebServerRequest* request)
|
||||
strftime(timeStringBuff, sizeof(timeStringBuff), "%A, %B %d %Y %H:%M:%S", &timeinfo);
|
||||
root["ntp_localtime"] = timeStringBuff;
|
||||
|
||||
SunPosition.sunriseTime(&timeinfo);
|
||||
strftime(timeStringBuff, sizeof(timeStringBuff), "%A, %B %d %Y %H:%M:%S", &timeinfo);
|
||||
if (SunPosition.sunriseTime(&timeinfo)) {
|
||||
strftime(timeStringBuff, sizeof(timeStringBuff), "%A, %B %d %Y %H:%M:%S", &timeinfo);
|
||||
} else {
|
||||
strcpy(timeStringBuff, "--");
|
||||
}
|
||||
root["sun_risetime"] = timeStringBuff;
|
||||
|
||||
SunPosition.sunsetTime(&timeinfo);
|
||||
strftime(timeStringBuff, sizeof(timeStringBuff), "%A, %B %d %Y %H:%M:%S", &timeinfo);
|
||||
if (SunPosition.sunsetTime(&timeinfo)) {
|
||||
strftime(timeStringBuff, sizeof(timeStringBuff), "%A, %B %d %Y %H:%M:%S", &timeinfo);
|
||||
} else {
|
||||
strcpy(timeStringBuff, "--");
|
||||
}
|
||||
root["sun_settime"] = timeStringBuff;
|
||||
|
||||
root["sun_isSunsetAvailable"] = SunPosition.isSunsetAvailable();
|
||||
root["sun_isDayPeriod"] = SunPosition.isDayPeriod();
|
||||
|
||||
response->setLength();
|
||||
@ -81,6 +88,7 @@ void WebApiNtpClass::onNtpAdminGet(AsyncWebServerRequest* request)
|
||||
root["ntp_timezone_descr"] = config.Ntp_TimezoneDescr;
|
||||
root["longitude"] = config.Ntp_Longitude;
|
||||
root["latitude"] = config.Ntp_Latitude;
|
||||
root["sunsettype"] = config.Ntp_SunsetType;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -125,7 +133,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(root.containsKey("ntp_server") && root.containsKey("ntp_timezone") && root.containsKey("longitude") && root.containsKey("latitude"))) {
|
||||
if (!(root.containsKey("ntp_server") && root.containsKey("ntp_timezone") && root.containsKey("longitude") && root.containsKey("latitude") && root.containsKey("sunsettype"))) {
|
||||
retMsg["message"] = "Values are missing!";
|
||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
@ -166,6 +174,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
||||
strlcpy(config.Ntp_TimezoneDescr, root["ntp_timezone_descr"].as<String>().c_str(), sizeof(config.Ntp_TimezoneDescr));
|
||||
config.Ntp_Latitude = root["latitude"].as<double>();
|
||||
config.Ntp_Longitude = root["longitude"].as<double>();
|
||||
config.Ntp_SunsetType = root["sunsettype"].as<uint8_t>();
|
||||
Configuration.write();
|
||||
|
||||
retMsg["type"] = "success";
|
||||
|
||||
@ -105,9 +105,14 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
|
||||
}
|
||||
|
||||
JsonObject invObject = invArray.createNestedObject();
|
||||
INVERTER_CONFIG_T* inv_cfg = Configuration.getInverterConfig(inv->serial());
|
||||
if (inv_cfg == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
invObject["serial"] = inv->serialString();
|
||||
invObject["name"] = inv->name();
|
||||
invObject["order"] = inv_cfg->Order;
|
||||
invObject["data_age"] = (millis() - inv->Statistics()->getLastUpdate()) / 1000;
|
||||
invObject["poll_enabled"] = inv->getEnablePolling();
|
||||
invObject["reachable"] = inv->isReachable();
|
||||
@ -124,10 +129,7 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
|
||||
JsonObject chanTypeObj = invObject.createNestedObject(inv->Statistics()->getChannelTypeName(t));
|
||||
for (auto& c : inv->Statistics()->getChannelsByType(t)) {
|
||||
if (t == TYPE_DC) {
|
||||
INVERTER_CONFIG_T* inv_cfg = Configuration.getInverterConfig(inv->serial());
|
||||
if (inv_cfg != nullptr) {
|
||||
chanTypeObj[String(static_cast<uint8_t>(c))]["name"]["u"] = inv_cfg->channel[c].Name;
|
||||
}
|
||||
chanTypeObj[String(static_cast<uint8_t>(c))]["name"]["u"] = inv_cfg->channel[c].Name;
|
||||
}
|
||||
addField(chanTypeObj, i, inv, t, c, FLD_PAC);
|
||||
addField(chanTypeObj, i, inv, t, c, FLD_UAC);
|
||||
@ -164,9 +166,9 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
|
||||
}
|
||||
|
||||
JsonObject totalObj = root.createNestedObject("total");
|
||||
addTotalField(totalObj, "Power", Datastore.totalAcPowerEnabled, "W", Datastore.totalAcPowerDigits);
|
||||
addTotalField(totalObj, "YieldDay", Datastore.totalAcYieldDayEnabled, "Wh", Datastore.totalAcYieldDayDigits);
|
||||
addTotalField(totalObj, "YieldTotal", Datastore.totalAcYieldTotalEnabled, "kWh", Datastore.totalAcYieldTotalDigits);
|
||||
addTotalField(totalObj, "Power", Datastore.getTotalAcPowerEnabled(), "W", Datastore.getTotalAcPowerDigits());
|
||||
addTotalField(totalObj, "YieldDay", Datastore.getTotalAcYieldDayEnabled(), "Wh", Datastore.getTotalAcYieldDayDigits());
|
||||
addTotalField(totalObj, "YieldTotal", Datastore.getTotalAcYieldTotalEnabled(), "kWh", Datastore.getTotalAcYieldTotalDigits());
|
||||
|
||||
JsonObject hintObj = root.createNestedObject("hints");
|
||||
struct tm timeinfo;
|
||||
|
||||
@ -11,32 +11,34 @@
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.7",
|
||||
"bootstrap": "^5.3.0-alpha3",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"bootstrap": "^5.3.0",
|
||||
"bootstrap-icons-vue": "^1.10.3",
|
||||
"mitt": "^3.0.0",
|
||||
"sortablejs": "^1.15.0",
|
||||
"spark-md5": "^3.0.2",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-router": "^4.2.1"
|
||||
"vue-router": "^4.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^0.10.0",
|
||||
"@intlify/unplugin-vue-i18n": "^0.11.0",
|
||||
"@rushstack/eslint-patch": "^1.3.0",
|
||||
"@tsconfig/node18": "^2.0.1",
|
||||
"@types/bootstrap": "^5.2.6",
|
||||
"@types/node": "^20.2.3",
|
||||
"@types/node": "^20.2.5",
|
||||
"@types/sortablejs": "^1.15.1",
|
||||
"@types/spark-md5": "^3.0.2",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"@vue/tsconfig": "^0.4.0",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-plugin-vue": "^9.14.0",
|
||||
"eslint-plugin-vue": "^9.14.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"sass": "^1.62.1",
|
||||
"terser": "^5.17.6",
|
||||
"typescript": "^5.0.4",
|
||||
"vite": "^4.3.8",
|
||||
"terser": "^5.17.7",
|
||||
"typescript": "^5.1.3",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-css-injected-by-js": "^3.1.1",
|
||||
"vue-tsc": "^1.6.5"
|
||||
|
||||
@ -56,6 +56,7 @@
|
||||
"4006": "Ungültige Anzahl an Kanalwerten übergeben!",
|
||||
"4007": "Wechselrichter geändert!",
|
||||
"4008": "Wechselrichter gelöscht!",
|
||||
"4009": "Wechselrichter Reihenfolge gespeichert!",
|
||||
"5001": "@:apiresponse.2001",
|
||||
"5002": "Das Limit muss zwischen 1 und {max} sein!",
|
||||
"5003": "Ungültiten Typ angegeben!",
|
||||
@ -276,8 +277,9 @@
|
||||
"Synced": "synchronisiert",
|
||||
"NotSynced": "nicht synchronisiert",
|
||||
"LocalTime": "Lokale Uhrzeit",
|
||||
"Sunrise": "Nautische Morgendämmerung",
|
||||
"Sunset": "Nautische Abenddämmerung",
|
||||
"Sunrise": "Morgendämmerung",
|
||||
"Sunset": "Abenddämmerung",
|
||||
"NotAvailable": "Nicht verfügbar",
|
||||
"Mode": "Modus",
|
||||
"Day": "Tag",
|
||||
"Night": "Nacht"
|
||||
@ -408,6 +410,12 @@
|
||||
"LocationConfiguration": "Standortkonfiguration",
|
||||
"Longitude": "Längengrad:",
|
||||
"Latitude": "Breitengrad:",
|
||||
"SunSetType": "Dämmerungstyp:",
|
||||
"SunSetTypeHint": "Beeinflusst die Tag/Nacht Berechnung. Es kann bis zu einer Minute dauern bis der neue Typ angewendet wurde.",
|
||||
"OFFICIAL": "Standard Dämmerung (90.8°)",
|
||||
"NAUTICAL": "Nautische Dämmerung (102°)",
|
||||
"CIVIL": "Bürgerliche Dämmerung (96°)",
|
||||
"ASTONOMICAL": "Astronomische Dämmerung (108°)",
|
||||
"Save": "@:dtuadmin.Save",
|
||||
"ManualTimeSynchronization": "Manuelle Zeitsynchronization",
|
||||
"CurrentOpenDtuTime": "Aktuelle OpenDTU-Zeit:",
|
||||
@ -564,6 +572,7 @@
|
||||
"StatusHint": "<b>Hinweis:</b> Der Wechselrichter wird über seinen DC-Eingang mit Strom versorgt. Wenn keine Sonne scheint, ist der Wechselrichter aus. Es können trotzdem Anfragen gesendet werden.",
|
||||
"Type": "Typ",
|
||||
"Action": "Aktion",
|
||||
"SaveOrder": "Reihenfolge speichern",
|
||||
"DeleteInverter": "Wechselrichter löschen",
|
||||
"EditInverter": "Wechselrichter bearbeiten",
|
||||
"InverterSerial": "Wechselrichter Seriennummer:",
|
||||
|
||||
@ -56,6 +56,7 @@
|
||||
"4006": "Invalid amount of max channel setting given!",
|
||||
"4007": "Inverter changed!",
|
||||
"4008": "Inverter deleted!",
|
||||
"4009": "Inverter order saved!",
|
||||
"5001": "@:apiresponse.2001",
|
||||
"5002": "Limit must between 1 and {max}!",
|
||||
"5003": "Invalid type specified!",
|
||||
@ -276,8 +277,9 @@
|
||||
"Synced": "synced",
|
||||
"NotSynced": "not synced",
|
||||
"LocalTime": "Local Time",
|
||||
"Sunrise": "Nautical Sunrise",
|
||||
"Sunset": "Nautical Sunset",
|
||||
"Sunrise": "Sunrise",
|
||||
"Sunset": "Sunset",
|
||||
"NotAvailable": "Not Available",
|
||||
"Mode": "Mode",
|
||||
"Day": "Day",
|
||||
"Night": "Night"
|
||||
@ -408,6 +410,12 @@
|
||||
"LocationConfiguration": "Location Configuration",
|
||||
"Longitude": "Longitude",
|
||||
"Latitude": "Latitude",
|
||||
"SunSetType": "Sunset type",
|
||||
"SunSetTypeHint": "Affects the day/night calculation. It can take up to one minute until the new type will be applied.",
|
||||
"OFFICIAL": "Standard dawn (90.8°)",
|
||||
"NAUTICAL": "Nautical dawn (102°)",
|
||||
"CIVIL": "Civil dawn (96°)",
|
||||
"ASTONOMICAL": "Astronomical dawn (108°)",
|
||||
"Save": "@:dtuadmin.Save",
|
||||
"ManualTimeSynchronization": "Manual Time Synchronization",
|
||||
"CurrentOpenDtuTime": "Current OpenDTU Time:",
|
||||
@ -568,6 +576,7 @@
|
||||
"StatusHint": "<b>Hint:</b> The inverter is powered by its DC input. If there is no sun, the inverter is off. Requests can still be sent.",
|
||||
"Type": "Type",
|
||||
"Action": "Action",
|
||||
"SaveOrder": "Save order",
|
||||
"DeleteInverter": "Delete inverter",
|
||||
"EditInverter": "Edit inverter",
|
||||
"InverterSerial": "Inverter Serial:",
|
||||
|
||||
@ -56,6 +56,7 @@
|
||||
"4006": "Réglage du montant maximal de canaux invalide !",
|
||||
"4007": "Onduleur modifié !",
|
||||
"4008": "Onduleur supprimé !",
|
||||
"4009": "Inverter order saved!",
|
||||
"5001": "@:apiresponse.2001",
|
||||
"5002": "La limite doit être comprise entre 1 et {max} !",
|
||||
"5003": "Type spécifié invalide !",
|
||||
@ -275,8 +276,9 @@
|
||||
"Synced": "synchronisée",
|
||||
"NotSynced": "pas synchronisée",
|
||||
"LocalTime": "Heure locale",
|
||||
"Sunrise": "Nautical Sunrise",
|
||||
"Sunset": "Nautical Sunset",
|
||||
"Sunrise": "Sunrise",
|
||||
"Sunset": "Sunset",
|
||||
"NotAvailable": "Not Available",
|
||||
"Mode": "Mode",
|
||||
"Day": "Day",
|
||||
"Night": "Night"
|
||||
@ -407,6 +409,12 @@
|
||||
"LocationConfiguration": "Géolocalisation",
|
||||
"Longitude": "Longitude",
|
||||
"Latitude": "Latitude",
|
||||
"SunSetType": "Sunset type",
|
||||
"SunSetTypeHint": "Affects the day/night calculation. It can take up to one minute until the new type will be applied.",
|
||||
"OFFICIAL": "Standard dawn (90.8°)",
|
||||
"NAUTICAL": "Nautical dawn (102°)",
|
||||
"CIVIL": "Civil dawn (96°)",
|
||||
"ASTONOMICAL": "Astronomical dawn (108°)",
|
||||
"Save": "@:dtuadmin.Save",
|
||||
"ManualTimeSynchronization": "Synchronisation manuelle de l'heure",
|
||||
"CurrentOpenDtuTime": "Heure actuelle de l'OpenDTU",
|
||||
@ -491,6 +499,7 @@
|
||||
"StatusHint": "<b>Astuce :</b> L'onduleur est alimenté par son entrée courant continu. S'il n'y a pas de soleil, l'onduleur est éteint, mais les requêtes peuvent toujours être envoyées.",
|
||||
"Type": "Type",
|
||||
"Action": "Action",
|
||||
"SaveOrder": "Save order",
|
||||
"DeleteInverter": "Supprimer l'onduleur",
|
||||
"EditInverter": "Modifier l'onduleur",
|
||||
"InverterSerial": "Numéro de série de l'onduleur",
|
||||
|
||||
@ -23,6 +23,7 @@ export interface InverterStatistics {
|
||||
export interface Inverter {
|
||||
serial: number;
|
||||
name: string;
|
||||
order: number;
|
||||
data_age: number;
|
||||
poll_enabled: boolean;
|
||||
reachable: boolean;
|
||||
|
||||
@ -4,4 +4,5 @@ export interface NtpConfig {
|
||||
ntp_timezone_descr: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
sunsettype: number;
|
||||
}
|
||||
@ -7,4 +7,5 @@ export interface NtpStatus {
|
||||
sun_risetime: string;
|
||||
sun_settime: string;
|
||||
sun_isDayPeriod: boolean;
|
||||
sun_isSunsetAvailable: boolean;
|
||||
}
|
||||
@ -470,7 +470,9 @@ export default defineComponent({
|
||||
'decimalTwoDigits');
|
||||
},
|
||||
inverterData(): Inverter[] {
|
||||
return this.liveData.inverters;
|
||||
return this.liveData.inverters.slice().sort((a : Inverter, b: Inverter) => {
|
||||
return a.order - b.order;
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th scope="col">{{ $t('inverteradmin.Status') }}</th>
|
||||
<th>{{ $t('inverteradmin.Serial') }}</th>
|
||||
<th>{{ $t('inverteradmin.Name') }}</th>
|
||||
@ -35,8 +36,9 @@
|
||||
<th>{{ $t('inverteradmin.Action') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="inverter in sortedInverters" v-bind:key="inverter.id">
|
||||
<tbody ref="invList">
|
||||
<tr v-for="inverter in inverters" v-bind:key="inverter.id" :data-id="inverter.id">
|
||||
<td><BIconGripHorizontal class="drag-handle" /></td>
|
||||
<td>
|
||||
<span class="badge" :title="$t('inverteradmin.Receive')" :class="{
|
||||
'text-bg-warning': !inverter.poll_enable_night,
|
||||
@ -63,6 +65,9 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="ml-auto text-right">
|
||||
<button class="btn btn-primary my-2" @click="onSaveOrder()">{{ $t('inverteradmin.SaveOrder') }}</button>
|
||||
</div>
|
||||
</CardElement>
|
||||
</BasePage>
|
||||
|
||||
@ -197,6 +202,7 @@ 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 Sortable from 'sortablejs';
|
||||
import { authHeader, handleResponse } from '@/utils/authentication';
|
||||
import * as bootstrap from 'bootstrap';
|
||||
import {
|
||||
@ -205,6 +211,7 @@ import {
|
||||
BIconTrash,
|
||||
BIconArrowDown,
|
||||
BIconArrowUp,
|
||||
BIconGripHorizontal,
|
||||
} from 'bootstrap-icons-vue';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
@ -219,6 +226,7 @@ declare interface Inverter {
|
||||
serial: number;
|
||||
name: string;
|
||||
type: string;
|
||||
order: number;
|
||||
poll_enable: boolean;
|
||||
poll_enable_night: boolean;
|
||||
command_enable: boolean;
|
||||
@ -244,6 +252,7 @@ export default defineComponent({
|
||||
BIconTrash,
|
||||
BIconArrowDown,
|
||||
BIconArrowUp,
|
||||
BIconGripHorizontal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -253,7 +262,8 @@ export default defineComponent({
|
||||
selectedInverterData: {} as Inverter,
|
||||
inverters: [] as Inverter[],
|
||||
dataLoading: true,
|
||||
alert: {} as AlertResponse
|
||||
alert: {} as AlertResponse,
|
||||
sortable: {} as Sortable,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@ -263,21 +273,27 @@ export default defineComponent({
|
||||
created() {
|
||||
this.getInverters();
|
||||
},
|
||||
computed: {
|
||||
sortedInverters(): Inverter[] {
|
||||
return this.inverters.slice().sort((a, b) => {
|
||||
return a.serial - b.serial;
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getInverters() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/inverter/list", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.inverters = data.inverter;
|
||||
this.inverters = data.inverter.slice().sort((a : Inverter, b: Inverter) => {
|
||||
return a.order - b.order;
|
||||
});
|
||||
this.dataLoading = false;
|
||||
|
||||
this.$nextTick(() => {
|
||||
const table = this.$refs.invList as HTMLElement;
|
||||
|
||||
this.sortable = Sortable.create(table, {
|
||||
sort: true,
|
||||
handle: '.drag-handle',
|
||||
animation: 150,
|
||||
draggable: 'tr',
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
callInverterApiEndpoint(endpoint: string, jsonData: string) {
|
||||
@ -316,7 +332,16 @@ export default defineComponent({
|
||||
},
|
||||
onCloseModal(modal: bootstrap.Modal) {
|
||||
modal.hide();
|
||||
}
|
||||
},
|
||||
onSaveOrder() {
|
||||
this.callInverterApiEndpoint("order", JSON.stringify({ order: this.sortable.toArray() }));
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.drag-handle {
|
||||
cursor: grab;
|
||||
}
|
||||
</style>
|
||||
@ -36,6 +36,21 @@
|
||||
<InputElement :label="$t('ntpadmin.Longitude')"
|
||||
v-model="ntpConfigList.longitude"
|
||||
type="number" min="-180" max="180" step="any"/>
|
||||
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label">
|
||||
{{ $t('ntpadmin.SunSetType') }}
|
||||
<BIconInfoCircle v-tooltip :title="$t('ntpadmin.SunSetTypeHint')" />
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-select" v-model="ntpConfigList.sunsettype">
|
||||
<option v-for="sunsettype in sunsetTypeList" :key="sunsettype.key" :value="sunsettype.key">
|
||||
{{ $t(`ntpadmin.` + sunsettype.value) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</CardElement>
|
||||
<button type="submit" class="btn btn-primary mb-3">{{ $t('ntpadmin.Save') }}</button>
|
||||
</form>
|
||||
@ -67,6 +82,7 @@ import InputElement from '@/components/InputElement.vue';
|
||||
import type { NtpConfig } from "@/types/NtpConfig";
|
||||
import { authHeader, handleResponse } from '@/utils/authentication';
|
||||
import { defineComponent } from 'vue';
|
||||
import { BIconInfoCircle } from 'bootstrap-icons-vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@ -74,6 +90,7 @@ export default defineComponent({
|
||||
BootstrapAlert,
|
||||
CardElement,
|
||||
InputElement,
|
||||
BIconInfoCircle,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -88,6 +105,12 @@ export default defineComponent({
|
||||
alertMessage: "",
|
||||
alertType: "info",
|
||||
showAlert: false,
|
||||
sunsetTypeList: [
|
||||
{ key: 0, value: 'OFFICIAL' },
|
||||
{ key: 1, value: 'NAUTICAL' },
|
||||
{ key: 2, value: 'CIVIL' },
|
||||
{ key: 3, value: 'ASTONOMICAL' },
|
||||
],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
|
||||
@ -38,11 +38,13 @@
|
||||
|
||||
<tr>
|
||||
<th>{{ $t('ntpinfo.Sunrise') }}</th>
|
||||
<td>{{ ntpDataList.sun_risetime }}</td>
|
||||
<td v-if="ntpDataList.sun_isSunsetAvailable">{{ ntpDataList.sun_risetime }}</td>
|
||||
<td v-else>{{ $t('ntpinfo.NotAvailable') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ $t('ntpinfo.Sunset') }}</th>
|
||||
<td>{{ ntpDataList.sun_settime }}</td>
|
||||
<td v-if="ntpDataList.sun_isSunsetAvailable">{{ ntpDataList.sun_settime }}</td>
|
||||
<td v-else>{{ $t('ntpinfo.NotAvailable') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ $t('ntpinfo.Mode') }}</th>
|
||||
|
||||
146
webapp/yarn.lock
146
webapp/yarn.lock
@ -180,10 +180,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
|
||||
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
|
||||
|
||||
"@intlify/bundle-utils@^5.4.0":
|
||||
version "5.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/bundle-utils/-/bundle-utils-5.4.0.tgz#12d1e2316a52cdf4818f5f183dc2726da35886c0"
|
||||
integrity sha512-oJbibbP5djdQYTv0cQC4PYRHPpS5nF/KZ7MWM1/yhdsGzjvCekJHWk25MCQIIOrfQ+aw5tKi2t66KpYEUki/tw==
|
||||
"@intlify/bundle-utils@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/bundle-utils/-/bundle-utils-6.0.1.tgz#889ef90a07f54d3285082f1fff99be06db49c65f"
|
||||
integrity sha512-BkeZNKZiC0B7K3OYMwiPLoAqsZmKH3SxTL75vYAkuQ//XWR8WO0NpfjXhTxgLTVFHxMcNb2agAopC0DP6fqDrg==
|
||||
dependencies:
|
||||
"@intlify/message-compiler" "9.3.0-beta.17"
|
||||
"@intlify/shared" "9.3.0-beta.17"
|
||||
@ -192,6 +192,7 @@
|
||||
estree-walker "^2.0.2"
|
||||
jsonc-eslint-parser "^1.0.1"
|
||||
magic-string "^0.30.0"
|
||||
mlly "^1.2.0"
|
||||
source-map "0.6.1"
|
||||
yaml-eslint-parser "^0.3.2"
|
||||
|
||||
@ -238,12 +239,12 @@
|
||||
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.3.0-beta.17.tgz#1180dcb0b30741555fad0b62e4621802e8272ee5"
|
||||
integrity sha512-mscf7RQsUTOil35jTij4KGW1RC9SWQjYScwLxP53Ns6g24iEd5HN7ksbt9O6FvTmlQuX77u+MXpBdfJsGqizLQ==
|
||||
|
||||
"@intlify/unplugin-vue-i18n@^0.10.0":
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-0.10.0.tgz#28a05a7b9e0a7cc35e91e6762e5e6e57f954a45c"
|
||||
integrity sha512-Sf8fe26/d8rBNcg+zBSb7RA1uyhrG9zhIM+CRX6lqcznMDjLRr/1tuVaJ9E6xqJkzjfPgRzNcCqwMt6rpNkL7Q==
|
||||
"@intlify/unplugin-vue-i18n@^0.11.0":
|
||||
version "0.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-0.11.0.tgz#43d2730d86ceb214e7e147b0cc8f78f7d7df0707"
|
||||
integrity sha512-ivcLZo08fvepHWV8o5lcKfhcKFSWqhwrqIAU6pUIbvq2ICo9fnXnIPYIZj7FeuHDLW1G3ADm44ZhQC3nYmvDlg==
|
||||
dependencies:
|
||||
"@intlify/bundle-utils" "^5.4.0"
|
||||
"@intlify/bundle-utils" "^6.0.1"
|
||||
"@intlify/shared" "9.3.0-beta.17"
|
||||
"@rollup/pluginutils" "^5.0.2"
|
||||
"@vue/compiler-sfc" "^3.2.47"
|
||||
@ -283,10 +284,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
|
||||
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
|
||||
|
||||
"@jridgewell/source-map@^0.3.2":
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb"
|
||||
integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==
|
||||
"@jridgewell/source-map@^0.3.3":
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.3.tgz#8108265659d4c33e72ffe14e33d6cc5eb59f2fda"
|
||||
integrity sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==
|
||||
dependencies:
|
||||
"@jridgewell/gen-mapping" "^0.3.0"
|
||||
"@jridgewell/trace-mapping" "^0.3.9"
|
||||
@ -325,10 +326,10 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@popperjs/core@^2.11.7":
|
||||
version "2.11.7"
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.7.tgz#ccab5c8f7dc557a52ca3288c10075c9ccd37fff7"
|
||||
integrity sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==
|
||||
"@popperjs/core@^2.11.8":
|
||||
version "2.11.8"
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
|
||||
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
||||
|
||||
"@popperjs/core@^2.9.2":
|
||||
version "2.11.5"
|
||||
@ -371,16 +372,21 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
||||
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
|
||||
|
||||
"@types/node@^20.2.3":
|
||||
version "20.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.2.3.tgz#b31eb300610c3835ac008d690de6f87e28f9b878"
|
||||
integrity sha512-pg9d0yC4rVNWQzX8U7xb4olIOFuuVL9za3bzMT2pu2SU0SNEi66i2qrvhE2qt0HvkhuCaWJu7pLNOt/Pj8BIrw==
|
||||
"@types/node@^20.2.5":
|
||||
version "20.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.2.5.tgz#26d295f3570323b2837d322180dfbf1ba156fefb"
|
||||
integrity sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==
|
||||
|
||||
"@types/semver@^7.3.12":
|
||||
version "7.3.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91"
|
||||
integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==
|
||||
|
||||
"@types/sortablejs@^1.15.1":
|
||||
version "1.15.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/sortablejs/-/sortablejs-1.15.1.tgz#123abafbe936f754fee5eb5b49009ce1f1075aa5"
|
||||
integrity sha512-g/JwBNToh6oCTAwNS8UGVmjO7NLDKsejVhvE4x1eWiPTC3uCuNsa/TD4ssvX3du+MLiM+SHPNDuijp8y76JzLQ==
|
||||
|
||||
"@types/spark-md5@^3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/spark-md5/-/spark-md5-3.0.2.tgz#da2e8a778a20335fc4f40b6471c4b0d86b70da55"
|
||||
@ -766,16 +772,16 @@ acorn@^7.1.1, acorn@^7.4.1:
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
||||
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
|
||||
|
||||
acorn@^8.5.0, acorn@^8.8.2:
|
||||
version "8.8.2"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
|
||||
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
|
||||
|
||||
acorn@^8.8.0:
|
||||
version "8.8.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
|
||||
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
|
||||
|
||||
acorn@^8.8.2:
|
||||
version "8.8.2"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
|
||||
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
|
||||
|
||||
ajv@^6.10.0, ajv@^6.12.4:
|
||||
version "6.12.6"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||
@ -843,10 +849,10 @@ bootstrap-icons-vue@^1.10.3:
|
||||
resolved "https://registry.yarnpkg.com/bootstrap-icons-vue/-/bootstrap-icons-vue-1.10.3.tgz#ae725513c9655ce86effa2a0b09e9e65b02c8f1a"
|
||||
integrity sha512-BzqmLufgHjFvSReJ1GQqNkl780UFK0rWT4Y1IQC7lZClXyOSsM5Ipw04BnuVmmrqgtSxzak83jcBwLJgCK3scg==
|
||||
|
||||
bootstrap@^5.3.0-alpha3:
|
||||
version "5.3.0-alpha3"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.0-alpha3.tgz#ad64d9a663c53ab7aca99c560e0bd16b5e023441"
|
||||
integrity sha512-FBhOWMxkCFr74hesJdchLXhqagPTXS+kRNU3gE0FR5Ki/AdPSz32Ik96Z28+yBluCnE/pc9st7l1yPwKgbtfSA==
|
||||
bootstrap@^5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.0.tgz#0718a7cc29040ee8dbf1bd652b896f3436a87c29"
|
||||
integrity sha512-UnBV3E3v4STVNQdms6jSGO2CvOkjUMdDAVR2V5N4uCMdaIkaQjbcEAMqRimDHIs4uqBYzDAKCQwCB+97tJgHQw==
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
@ -1119,10 +1125,10 @@ escodegen@^2.0.0:
|
||||
optionalDependencies:
|
||||
source-map "~0.6.1"
|
||||
|
||||
eslint-plugin-vue@^9.14.0:
|
||||
version "9.14.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.14.0.tgz#73004a62d794e276a60d471114d81ed8887efcb8"
|
||||
integrity sha512-4O7EuiqPGVQA1wYCzLvCzsBTv9JIPHLHhrf0k55DLzbwtmJbSw2TKS0G/l7pOwi9RWMSkjIT7ftChU5gZpgnJw==
|
||||
eslint-plugin-vue@^9.14.1:
|
||||
version "9.14.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.14.1.tgz#3b0c9857642dac547c7564031cfb09d283eafdd4"
|
||||
integrity sha512-LQazDB1qkNEKejLe/b5a9VfEbtbczcOaui5lQ4Qw0tbRBbQYREyxxOV5BQgNDTqGPs9pxqiEpbMi9ywuIaF7vw==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.3.0"
|
||||
natural-compare "^1.4.0"
|
||||
@ -1746,6 +1752,11 @@ jsonc-eslint-parser@^1.0.1:
|
||||
espree "^6.0.0"
|
||||
semver "^6.3.0"
|
||||
|
||||
jsonc-parser@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76"
|
||||
integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==
|
||||
|
||||
jsonfile@^6.0.1:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
|
||||
@ -1856,6 +1867,16 @@ mitt@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.0.tgz#69ef9bd5c80ff6f57473e8d89326d01c414be0bd"
|
||||
integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==
|
||||
|
||||
mlly@^1.2.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.3.0.tgz#3184cb80c6437bda861a9f452ae74e3434ed9cd1"
|
||||
integrity sha512-HT5mcgIQKkOrZecOjOX3DJorTikWXwsBfpcr/MGBkhfWcjiqvnaL/9ppxvIUXfjT6xt4DVIAsN9fMUz1ev4bIw==
|
||||
dependencies:
|
||||
acorn "^8.8.2"
|
||||
pathe "^1.1.0"
|
||||
pkg-types "^1.0.3"
|
||||
ufo "^1.1.2"
|
||||
|
||||
ms@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
@ -2045,7 +2066,7 @@ path-type@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||
|
||||
pathe@^1.0.0:
|
||||
pathe@^1.0.0, pathe@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.0.tgz#e2e13f6c62b31a3289af4ba19886c230f295ec03"
|
||||
integrity sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==
|
||||
@ -2070,6 +2091,15 @@ pify@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
|
||||
integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==
|
||||
|
||||
pkg-types@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.0.3.tgz#988b42ab19254c01614d13f4f65a2cfc7880f868"
|
||||
integrity sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==
|
||||
dependencies:
|
||||
jsonc-parser "^3.2.0"
|
||||
mlly "^1.2.0"
|
||||
pathe "^1.1.0"
|
||||
|
||||
postcss-selector-parser@^6.0.9:
|
||||
version "6.0.10"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
|
||||
@ -2266,6 +2296,11 @@ slash@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
||||
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
|
||||
|
||||
sortablejs@^1.15.0:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.15.0.tgz#53230b8aa3502bb77a29e2005808ffdb4a5f7e2a"
|
||||
integrity sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==
|
||||
|
||||
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||
@ -2383,13 +2418,13 @@ supports-preserve-symlinks-flag@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
terser@^5.17.6:
|
||||
version "5.17.6"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.6.tgz#d810e75e1bb3350c799cd90ebefe19c9412c12de"
|
||||
integrity sha512-V8QHcs8YuyLkLHsJO5ucyff1ykrLVsR4dNnS//L5Y3NiSXpbK1J+WMVUs67eI0KTxs9JtHhgEQpXQVHlHI92DQ==
|
||||
terser@^5.17.7:
|
||||
version "5.17.7"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.7.tgz#2a8b134826fe179b711969fd9d9a0c2479b2a8c3"
|
||||
integrity sha512-/bi0Zm2C6VAexlGgLlVxA0P2lru/sdLyfCVaRMfKVo9nWxbmz7f/sD8VPybPeSUJaJcwmCJis9pBIhcVcG1QcQ==
|
||||
dependencies:
|
||||
"@jridgewell/source-map" "^0.3.2"
|
||||
acorn "^8.5.0"
|
||||
"@jridgewell/source-map" "^0.3.3"
|
||||
acorn "^8.8.2"
|
||||
commander "^2.20.0"
|
||||
source-map-support "~0.5.20"
|
||||
|
||||
@ -2436,10 +2471,15 @@ type-fest@^0.20.2:
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
|
||||
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
|
||||
|
||||
typescript@^5.0.4:
|
||||
version "5.0.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b"
|
||||
integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==
|
||||
typescript@^5.1.3:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826"
|
||||
integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==
|
||||
|
||||
ufo@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.1.2.tgz#d0d9e0fa09dece0c31ffd57bd363f030a35cfe76"
|
||||
integrity sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==
|
||||
|
||||
unbox-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
@ -2500,10 +2540,10 @@ vite-plugin-css-injected-by-js@^3.1.1:
|
||||
resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.1.1.tgz#8324412636cf6fdada1a86f595aa2e78458e5ddb"
|
||||
integrity sha512-mwrFvEEy0TuH8Ul0cb2HgjmNboQ/JnEFy+kHCWqAJph3ikMOiIuyYVdx0JO4nEIWJyzSnc4TTdmoTulsikvJEg==
|
||||
|
||||
vite@^4.3.8:
|
||||
version "4.3.8"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-4.3.8.tgz#70cd6a294ab52d7fb8f37f5bc63d117dd19e9918"
|
||||
integrity sha512-uYB8PwN7hbMrf4j1xzGDk/lqjsZvCDbt/JC5dyfxc19Pg8kRm14LinK/uq+HSLNswZEoKmweGdtpbnxRtrAXiQ==
|
||||
vite@^4.3.9:
|
||||
version "4.3.9"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-4.3.9.tgz#db896200c0b1aa13b37cdc35c9e99ee2fdd5f96d"
|
||||
integrity sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==
|
||||
dependencies:
|
||||
esbuild "^0.17.5"
|
||||
postcss "^8.4.23"
|
||||
@ -2547,10 +2587,10 @@ vue-i18n@^9.2.2:
|
||||
"@intlify/vue-devtools" "9.2.2"
|
||||
"@vue/devtools-api" "^6.2.1"
|
||||
|
||||
vue-router@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.2.1.tgz#f8ab85c89e74682cad71519480fdf2b855e8c9e0"
|
||||
integrity sha512-nW28EeifEp8Abc5AfmAShy5ZKGsGzjcnZ3L1yc2DYUo+MqbBClrRP9yda3dIekM4I50/KnEwo1wkBLf7kHH5Cw==
|
||||
vue-router@^4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.2.2.tgz#b0097b66d89ca81c0986be03da244c7b32a4fd81"
|
||||
integrity sha512-cChBPPmAflgBGmy3tBsjeoe3f3VOSG6naKyY5pjtrqLGbNEXdzCigFUHgBvp9e3ysAtFtEx7OLqcSDh/1Cq2TQ==
|
||||
dependencies:
|
||||
"@vue/devtools-api" "^6.5.0"
|
||||
|
||||
|
||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user