Merge remote-tracking branch 'origin/master' into GoogleCharts

This commit is contained in:
Ralf Bauer 2023-08-13 21:40:29 +02:00
commit dd3c12d351
59 changed files with 672 additions and 352 deletions

View File

@ -18,4 +18,4 @@ jobs:
pip install cpplint
- name: Linting
run: |
cpplint --repository=. --recursive --filter=-runtime/references,-readability/braces,-whitespace,-legal,-build/include ./src ./include ./lib/Hoymiles ./lib/MqttSubscribeParser ./lib/TimeoutHelper ./lib/ResetReason
cpplint --repository=. --recursive --filter=-build/c++11,-runtime/references,-readability/braces,-whitespace,-legal,-build/include ./src ./include ./lib/Hoymiles ./lib/MqttSubscribeParser ./lib/TimeoutHelper ./lib/ResetReason

View File

@ -61,6 +61,7 @@ struct CONFIG_T {
byte WiFi_Dns2[4];
bool WiFi_Dhcp;
char WiFi_Hostname[WIFI_MAX_HOSTNAME_STRLEN + 1];
uint WiFi_ApTimeout;
char Ntp_Server[NTP_MAX_SERVER_STRLEN + 1];
char Ntp_Timezone[NTP_MAX_TIMEZONE_STRLEN + 1];
@ -70,6 +71,7 @@ struct CONFIG_T {
uint8_t Ntp_SunsetType;
bool Mqtt_Enabled;
char Mqtt_Hostname[MQTT_MAX_HOSTNAME_STRLEN + 1];
uint Mqtt_Port;
char Mqtt_Username[MQTT_MAX_USERNAME_STRLEN + 1];
char Mqtt_Password[MQTT_MAX_PASSWORD_STRLEN + 1];
@ -80,6 +82,18 @@ struct CONFIG_T {
char Mqtt_LwtValue_Offline[MQTT_MAX_LWTVALUE_STRLEN + 1];
uint32_t Mqtt_PublishInterval;
bool Mqtt_Hass_Enabled;
bool Mqtt_Hass_Retain;
char Mqtt_Hass_Topic[MQTT_MAX_TOPIC_STRLEN + 1];
bool Mqtt_Hass_IndividualPanels;
bool Mqtt_Hass_Expire;
bool Mqtt_Tls;
char Mqtt_RootCaCert[MQTT_MAX_CERT_STRLEN + 1];
bool Mqtt_TlsCertLogin;
char Mqtt_ClientCert[MQTT_MAX_CERT_STRLEN + 1];
char Mqtt_ClientKey[MQTT_MAX_CERT_STRLEN + 1];
INVERTER_CONFIG_T Inverter[INV_MAX_COUNT];
uint64_t Dtu_Serial;
@ -88,20 +102,6 @@ struct CONFIG_T {
int8_t Dtu_CmtPaLevel;
uint32_t Dtu_CmtFrequency;
bool Mqtt_Hass_Enabled;
bool Mqtt_Hass_Retain;
char Mqtt_Hass_Topic[MQTT_MAX_TOPIC_STRLEN + 1];
bool Mqtt_Hass_IndividualPanels;
bool Mqtt_Tls;
char Mqtt_RootCaCert[MQTT_MAX_CERT_STRLEN + 1];
bool Mqtt_TlsCertLogin;
char Mqtt_ClientCert[MQTT_MAX_CERT_STRLEN + 1];
char Mqtt_ClientKey[MQTT_MAX_CERT_STRLEN + 1];
char Mqtt_Hostname[MQTT_MAX_HOSTNAME_STRLEN + 1];
bool Mqtt_Hass_Expire;
char Security_Password[WIFI_MAX_PASSWORD_STRLEN + 1];
bool Security_AllowReadonly;

View File

@ -2,12 +2,10 @@
#pragma once
#include <TimeoutHelper.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <mutex>
class DatastoreClass {
public:
DatastoreClass();
void init();
void loop();
@ -61,7 +59,7 @@ public:
private:
TimeoutHelper _updateTimeout;
SemaphoreHandle_t _xSemaphore;
std::mutex _mutex;
float _totalAcYieldTotalEnabled = 0;
float _totalAcYieldDayEnabled = 0;

View File

@ -4,14 +4,15 @@
#include <AsyncWebSocket.h>
#include <HardwareSerial.h>
#include <Stream.h>
#include <mutex>
#define BUFFER_SIZE 500
class MessageOutputClass : public Print {
public:
MessageOutputClass();
void loop();
size_t write(uint8_t c);
size_t write(uint8_t c) override;
size_t write(const uint8_t *buffer, size_t size) override;
void register_ws_output(AsyncWebSocket* output);
private:
@ -21,7 +22,7 @@ private:
uint32_t _lastSend = 0;
bool _forceSend = false;
SemaphoreHandle_t _lock;
std::mutex _msgLock;
};
extern MessageOutputClass MessageOutput;

View File

@ -5,6 +5,7 @@
#include <MqttSubscribeParser.h>
#include <Ticker.h>
#include <espMqttClient.h>
#include <mutex>
class MqttSettingsClass {
public:
@ -37,6 +38,7 @@ private:
String willTopic;
Ticker mqttReconnectTimer;
MqttSubscribeParser _mqttSubscribeParser;
std::mutex _clientLock;
};
extern MqttSettingsClass MqttSettings;

View File

@ -64,6 +64,7 @@ private:
bool adminEnabled = true;
bool forceDisconnection = false;
int adminTimeoutCounter = 0;
int adminTimeoutCounterMax = 0;
int connectTimeoutTimer = 0;
int connectRedoTimer = 0;
uint32_t lastTimerCall = 0;

View File

@ -2,11 +2,11 @@
#pragma once
#include "WebApi_config.h"
#include "WebApi_device.h"
#include "WebApi_devinfo.h"
#include "WebApi_dtu.h"
#include "WebApi_eventlog.h"
#include "WebApi_firmware.h"
#include "WebApi_device.h"
#include "WebApi_inverter.h"
#include "WebApi_limit.h"
#include "WebApi_maintenance.h"

View File

@ -63,6 +63,7 @@ enum WebApiError {
NetworkGatewayInvalid,
NetworkDns1Invalid,
NetworkDns2Invalid,
NetworkApTimeoutInvalid,
NtpBase = 9000,
NtpServerLength,

View File

@ -9,10 +9,10 @@
#define ACCESS_POINT_NAME "OpenDTU-"
#define ACCESS_POINT_PASSWORD "openDTU42"
#define ACCESS_POINT_TIMEOUT 3;
#define AUTH_USERNAME "admin"
#define SECURITY_ALLOW_READONLY true
#define ADMIN_TIMEOUT 180
#define WIFI_RECONNECT_TIMEOUT 15
#define WIFI_RECONNECT_REDO_TIMEOUT 600
@ -25,11 +25,11 @@
#define NTP_TIMEZONEDESCR "Europe/Berlin"
#define NTP_LONGITUDE 10.4515f
#define NTP_LATITUDE 51.1657f
#define NTP_SUNSETTYPE 1
#define NTP_SUNSETTYPE 1U
#define MQTT_ENABLED false
#define MQTT_HOST ""
#define MQTT_PORT 1883
#define MQTT_PORT 1883U
#define MQTT_USER ""
#define MQTT_PASSWORD ""
#define MQTT_TOPIC "solar/"
@ -73,13 +73,13 @@
#define MQTT_LWT_TOPIC "dtu/status"
#define MQTT_LWT_ONLINE "online"
#define MQTT_LWT_OFFLINE "offline"
#define MQTT_PUBLISH_INTERVAL 5
#define MQTT_PUBLISH_INTERVAL 5U
#define DTU_SERIAL 0x99978563412
#define DTU_POLL_INTERVAL 5
#define DTU_NRF_PA_LEVEL 0
#define DTU_SERIAL 0x99978563412U
#define DTU_POLL_INTERVAL 5U
#define DTU_NRF_PA_LEVEL 0U
#define DTU_CMT_PA_LEVEL 0
#define DTU_CMT_FREQUENCY 865000
#define DTU_CMT_FREQUENCY 865000U
#define MQTT_HASS_ENABLED false
#define MQTT_HASS_EXPIRE true
@ -91,6 +91,6 @@
#define DISPLAY_POWERSAFE true
#define DISPLAY_SCREENSAVER true
#define DISPLAY_ROTATION 2
#define DISPLAY_CONTRAST 60
#define DISPLAY_LANGUAGE 0
#define DISPLAY_ROTATION 2U
#define DISPLAY_CONTRAST 60U
#define DISPLAY_LANGUAGE 0U

View File

@ -1,8 +1,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "HoymilesRadio_NRF.h"
#include "HoymilesRadio_CMT.h"
#include "HoymilesRadio_NRF.h"
#include "inverters/InverterAbstract.h"
#include "types.h"
#include <Print.h>

View File

@ -99,7 +99,7 @@ void HoymilesRadio::handleReceivedPackage()
}
} else if (!_busyFlag) {
// Currently in idle mode --> send packet if one is in the queue
if (!_commandQueue.empty()) {
if (!isQueueEmpty()) {
CommandAbstract* cmd = _commandQueue.front().get();
auto inv = Hoymiles.getInverterBySerial(cmd->getTargetAddress());

View File

@ -5,7 +5,7 @@
#include "commands/CommandAbstract.h"
#include "types.h"
#include <memory>
#include <queue>
#include <ThreadSafeQueue.h>
class HoymilesRadio {
public:
@ -16,11 +16,15 @@ public:
bool isQueueEmpty();
bool isInitialized();
template <typename T>
T* enqueCommand()
void enqueCommand(std::shared_ptr<CommandAbstract> cmd)
{
_commandQueue.push(std::make_shared<T>());
return static_cast<T*>(_commandQueue.back().get());
_commandQueue.push(cmd);
}
template <typename T>
std::shared_ptr<T> prepareCommand()
{
return std::make_shared<T>();
}
protected:
@ -34,7 +38,7 @@ protected:
void handleReceivedPackage();
serial_u _dtuSerial;
std::queue<std::shared_ptr<CommandAbstract>> _commandQueue;
ThreadSafeQueue<std::shared_ptr<CommandAbstract>> _commandQueue;
bool _isInitialized = false;
bool _busyFlag = false;

View File

@ -35,11 +35,11 @@ void ActivePowerControlCommand::setActivePowerLimit(float limit, PowerLimitContr
// limit
_payload[12] = (l >> 8) & 0xff;
_payload[13] = (l) & 0xff;
_payload[13] = (l)&0xff;
// type
_payload[14] = (type >> 8) & 0xff;
_payload[15] = (type) & 0xff;
_payload[15] = (type)&0xff;
udpateCRC(CRC_SIZE);
}

View File

@ -27,11 +27,13 @@ bool AlarmDataCommand::handleResponse(InverterAbstract* inverter, fragment_t fra
// Move all fragments into target buffer
uint8_t offs = 0;
inverter->EventLog()->beginAppendFragment();
inverter->EventLog()->clearBuffer();
for (uint8_t i = 0; i < max_fragment_id; i++) {
inverter->EventLog()->appendFragment(offs, fragment[i].fragment, fragment[i].len);
offs += (fragment[i].len);
}
inverter->EventLog()->endAppendFragment();
inverter->EventLog()->setLastAlarmRequestSuccess(CMD_OK);
inverter->EventLog()->setLastUpdate(millis());
return true;

View File

@ -27,11 +27,13 @@ bool DevInfoAllCommand::handleResponse(InverterAbstract* inverter, fragment_t fr
// Move all fragments into target buffer
uint8_t offs = 0;
inverter->DevInfo()->beginAppendFragment();
inverter->DevInfo()->clearBufferAll();
for (uint8_t i = 0; i < max_fragment_id; i++) {
inverter->DevInfo()->appendFragmentAll(offs, fragment[i].fragment, fragment[i].len);
offs += (fragment[i].len);
}
inverter->DevInfo()->endAppendFragment();
inverter->DevInfo()->setLastUpdateAll(millis());
return true;
}

View File

@ -27,11 +27,13 @@ bool DevInfoSimpleCommand::handleResponse(InverterAbstract* inverter, fragment_t
// Move all fragments into target buffer
uint8_t offs = 0;
inverter->DevInfo()->beginAppendFragment();
inverter->DevInfo()->clearBufferSimple();
for (uint8_t i = 0; i < max_fragment_id; i++) {
inverter->DevInfo()->appendFragmentSimple(offs, fragment[i].fragment, fragment[i].len);
offs += (fragment[i].len);
}
inverter->DevInfo()->endAppendFragment();
inverter->DevInfo()->setLastUpdateSimple(millis());
return true;
}

View File

@ -40,11 +40,13 @@ bool RealTimeRunDataCommand::handleResponse(InverterAbstract* inverter, fragment
// Move all fragments into target buffer
uint8_t offs = 0;
inverter->Statistics()->beginAppendFragment();
inverter->Statistics()->clearBuffer();
for (uint8_t i = 0; i < max_fragment_id; i++) {
inverter->Statistics()->appendFragment(offs, fragment[i].fragment, fragment[i].len);
offs += (fragment[i].len);
}
inverter->Statistics()->endAppendFragment();
inverter->Statistics()->resetRxFailureCount();
inverter->Statistics()->setLastUpdate(millis());
return true;

View File

@ -27,11 +27,13 @@ bool SystemConfigParaCommand::handleResponse(InverterAbstract* inverter, fragmen
// Move all fragments into target buffer
uint8_t offs = 0;
inverter->SystemConfigPara()->beginAppendFragment();
inverter->SystemConfigPara()->clearBuffer();
for (uint8_t i = 0; i < max_fragment_id; i++) {
inverter->SystemConfigPara()->appendFragment(offs, fragment[i].fragment, fragment[i].len);
offs += (fragment[i].len);
}
inverter->SystemConfigPara()->endAppendFragment();
inverter->SystemConfigPara()->setLastUpdateRequest(millis());
inverter->SystemConfigPara()->setLastLimitRequestSuccess(CMD_OK);
return true;

View File

@ -18,9 +18,10 @@ bool HMS_Abstract::sendChangeChannelRequest()
return false;
}
ChannelChangeCommand* cmdChannel = _radio->enqueCommand<ChannelChangeCommand>();
auto cmdChannel = _radio->prepareCommand<ChannelChangeCommand>();
cmdChannel->setChannel(HoymilesRadio_CMT::getChannelFromFrequency(Hoymiles.getRadioCmt()->getInverterTargetFrequency()));
cmdChannel->setTargetAddress(serial());
_radio->enqueCommand(cmdChannel);
return true;
};

View File

@ -20,9 +20,10 @@ bool HMT_Abstract::sendChangeChannelRequest()
return false;
}
ChannelChangeCommand* cmdChannel = _radio->enqueCommand<ChannelChangeCommand>();
auto cmdChannel = _radio->prepareCommand<ChannelChangeCommand>();
cmdChannel->setChannel(HoymilesRadio_CMT::getChannelFromFrequency(Hoymiles.getRadioCmt()->getInverterTargetFrequency()));
cmdChannel->setTargetAddress(serial());
_radio->enqueCommand(cmdChannel);
return true;
};

View File

@ -29,9 +29,10 @@ bool HM_Abstract::sendStatsRequest()
time_t now;
time(&now);
RealTimeRunDataCommand* cmd = _radio->enqueCommand<RealTimeRunDataCommand>();
auto cmd = _radio->prepareCommand<RealTimeRunDataCommand>();
cmd->setTime(now);
cmd->setTargetAddress(serial());
_radio->enqueCommand(cmd);
return true;
}
@ -60,10 +61,11 @@ bool HM_Abstract::sendAlarmLogRequest(bool force)
time_t now;
time(&now);
AlarmDataCommand* cmd = _radio->enqueCommand<AlarmDataCommand>();
auto cmd = _radio->prepareCommand<AlarmDataCommand>();
cmd->setTime(now);
cmd->setTargetAddress(serial());
EventLog()->setLastAlarmRequestSuccess(CMD_PENDING);
_radio->enqueCommand(cmd);
return true;
}
@ -82,13 +84,15 @@ bool HM_Abstract::sendDevInfoRequest()
time_t now;
time(&now);
DevInfoAllCommand* cmdAll = _radio->enqueCommand<DevInfoAllCommand>();
auto cmdAll = _radio->prepareCommand<DevInfoAllCommand>();
cmdAll->setTime(now);
cmdAll->setTargetAddress(serial());
_radio->enqueCommand(cmdAll);
DevInfoSimpleCommand* cmdSimple = _radio->enqueCommand<DevInfoSimpleCommand>();
auto cmdSimple = _radio->prepareCommand<DevInfoSimpleCommand>();
cmdSimple->setTime(now);
cmdSimple->setTargetAddress(serial());
_radio->enqueCommand(cmdSimple);
return true;
}
@ -107,10 +111,11 @@ bool HM_Abstract::sendSystemConfigParaRequest()
time_t now;
time(&now);
SystemConfigParaCommand* cmd = _radio->enqueCommand<SystemConfigParaCommand>();
auto cmd = _radio->prepareCommand<SystemConfigParaCommand>();
cmd->setTime(now);
cmd->setTargetAddress(serial());
SystemConfigPara()->setLastLimitRequestSuccess(CMD_PENDING);
_radio->enqueCommand(cmd);
return true;
}
@ -128,10 +133,11 @@ bool HM_Abstract::sendActivePowerControlRequest(float limit, PowerLimitControlTy
_activePowerControlLimit = limit;
_activePowerControlType = type;
ActivePowerControlCommand* cmd = _radio->enqueCommand<ActivePowerControlCommand>();
auto cmd = _radio->prepareCommand<ActivePowerControlCommand>();
cmd->setActivePowerLimit(limit, type);
cmd->setTargetAddress(serial());
SystemConfigPara()->setLastLimitCommandSuccess(CMD_PENDING);
_radio->enqueCommand(cmd);
return true;
}
@ -153,10 +159,11 @@ bool HM_Abstract::sendPowerControlRequest(bool turnOn)
_powerState = 0;
}
PowerControlCommand* cmd = _radio->enqueCommand<PowerControlCommand>();
auto cmd = _radio->prepareCommand<PowerControlCommand>();
cmd->setPowerOn(turnOn);
cmd->setTargetAddress(serial());
PowerCommand()->setLastPowerCommandSuccess(CMD_PENDING);
_radio->enqueCommand(cmd);
return true;
}
@ -169,10 +176,11 @@ bool HM_Abstract::sendRestartControlRequest()
_powerState = 2;
PowerControlCommand* cmd = _radio->enqueCommand<PowerControlCommand>();
auto cmd = _radio->prepareCommand<PowerControlCommand>();
cmd->setRestart();
cmd->setTargetAddress(serial());
PowerCommand()->setLastPowerCommandSuccess(CMD_PENDING);
_radio->enqueCommand(cmd);
return true;
}

View File

@ -6,7 +6,7 @@
#include "../Hoymiles.h"
#include <cstring>
const std::array<const AlarmMessage_t, ALARM_MSG_COUNT> 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
@ -84,7 +84,20 @@ const std::array<const AlarmMessage_t, ALARM_MSG_COUNT> AlarmLogParser::_alarmMe
{ AlarmMessageType_t::ALL, 5200, "Firmware error" },
{ AlarmMessageType_t::ALL, 8310, "Shut down" },
{ AlarmMessageType_t::ALL, 9000, "Microinverter is suspected of being stolen" },
}};
} };
#define HOY_SEMAPHORE_TAKE() \
do { \
} while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS)
#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore)
AlarmLogParser::AlarmLogParser()
: Parser()
{
_xSemaphore = xSemaphoreCreateMutex();
HOY_SEMAPHORE_GIVE(); // release before first use
clearBuffer();
}
void AlarmLogParser::clearBuffer()
{
@ -102,8 +115,21 @@ void AlarmLogParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t le
_alarmLogLength += len;
}
void AlarmLogParser::beginAppendFragment()
{
HOY_SEMAPHORE_TAKE();
}
void AlarmLogParser::endAppendFragment()
{
HOY_SEMAPHORE_GIVE();
}
uint8_t AlarmLogParser::getEntryCount()
{
if (_alarmLogLength < 2) {
return 0;
}
return (_alarmLogLength - 2) / ALARM_LOG_ENTRY_SIZE;
}
@ -128,6 +154,8 @@ void AlarmLogParser::getLogEntry(uint8_t entryId, AlarmLogEntry_t* entry)
int timezoneOffset = getTimezoneOffset();
HOY_SEMAPHORE_TAKE();
uint32_t wcode = (uint16_t)_payloadAlarmLog[entryStartOffset] << 8 | _payloadAlarmLog[entryStartOffset + 1];
uint32_t startTimeOffset = 0;
if (((wcode >> 13) & 0x01) == 1) {
@ -143,6 +171,8 @@ void AlarmLogParser::getLogEntry(uint8_t entryId, AlarmLogEntry_t* entry)
entry->StartTime = (((uint16_t)_payloadAlarmLog[entryStartOffset + 4] << 8) | ((uint16_t)_payloadAlarmLog[entryStartOffset + 5])) + startTimeOffset + timezoneOffset;
entry->EndTime = ((uint16_t)_payloadAlarmLog[entryStartOffset + 6] << 8) | ((uint16_t)_payloadAlarmLog[entryStartOffset + 7]);
HOY_SEMAPHORE_GIVE();
if (entry->EndTime > 0) {
entry->EndTime += (endTimeOffset + timezoneOffset);
}

View File

@ -2,8 +2,8 @@
#pragma once
#include "Parser.h"
#include <Arduino.h>
#include <cstdint>
#include <array>
#include <cstdint>
#define ALARM_LOG_ENTRY_COUNT 15
#define ALARM_LOG_ENTRY_SIZE 12
@ -31,8 +31,11 @@ typedef struct {
class AlarmLogParser : public Parser {
public:
AlarmLogParser();
void clearBuffer();
void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len);
void beginAppendFragment();
void endAppendFragment();
uint8_t getEntryCount();
void getLogEntry(uint8_t entryId, AlarmLogEntry_t* entry);
@ -46,11 +49,13 @@ private:
static int getTimezoneOffset();
uint8_t _payloadAlarmLog[ALARM_LOG_PAYLOAD_SIZE];
uint8_t _alarmLogLength;
uint8_t _alarmLogLength = 0;
LastCommandSuccess _lastAlarmRequestSuccess = CMD_NOK; // Set to NOK to fetch at startup
AlarmMessageType_t _messageType = AlarmMessageType_t::ALL;
static const std::array<const AlarmMessage_t, ALARM_MSG_COUNT> _alarmMessages;
SemaphoreHandle_t _xSemaphore;
};

View File

@ -46,6 +46,20 @@ const devInfo_t devInfo[] = {
{ { 0x10, 0x33, 0x31, ALL }, 2250, "HMT-2250" } // 01
};
#define HOY_SEMAPHORE_TAKE() \
do { \
} while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS)
#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore)
DevInfoParser::DevInfoParser()
: Parser()
{
_xSemaphore = xSemaphoreCreateMutex();
HOY_SEMAPHORE_GIVE(); // release before first use
clearBufferSimple();
clearBufferAll();
}
void DevInfoParser::clearBufferAll()
{
memset(_payloadDevInfoAll, 0, DEV_INFO_SIZE);
@ -78,6 +92,16 @@ void DevInfoParser::appendFragmentSimple(uint8_t offset, uint8_t* payload, uint8
_devInfoSimpleLength += len;
}
void DevInfoParser::beginAppendFragment()
{
HOY_SEMAPHORE_TAKE();
}
void DevInfoParser::endAppendFragment()
{
HOY_SEMAPHORE_GIVE();
}
uint32_t DevInfoParser::getLastUpdateAll()
{
return _lastUpdateAll;
@ -102,12 +126,16 @@ void DevInfoParser::setLastUpdateSimple(uint32_t lastUpdate)
uint16_t DevInfoParser::getFwBuildVersion()
{
return (((uint16_t)_payloadDevInfoAll[0]) << 8) | _payloadDevInfoAll[1];
HOY_SEMAPHORE_TAKE();
uint16_t ret = (((uint16_t)_payloadDevInfoAll[0]) << 8) | _payloadDevInfoAll[1];
HOY_SEMAPHORE_GIVE();
return ret;
}
time_t DevInfoParser::getFwBuildDateTime()
{
struct tm timeinfo = {};
HOY_SEMAPHORE_TAKE();
timeinfo.tm_year = ((((uint16_t)_payloadDevInfoAll[2]) << 8) | _payloadDevInfoAll[3]) - 1900;
timeinfo.tm_mon = ((((uint16_t)_payloadDevInfoAll[4]) << 8) | _payloadDevInfoAll[5]) / 100 - 1;
@ -115,13 +143,17 @@ time_t DevInfoParser::getFwBuildDateTime()
timeinfo.tm_hour = ((((uint16_t)_payloadDevInfoAll[6]) << 8) | _payloadDevInfoAll[7]) / 100;
timeinfo.tm_min = ((((uint16_t)_payloadDevInfoAll[6]) << 8) | _payloadDevInfoAll[7]) % 100;
HOY_SEMAPHORE_GIVE();
return timegm(&timeinfo);
}
uint16_t DevInfoParser::getFwBootloaderVersion()
{
return (((uint16_t)_payloadDevInfoAll[8]) << 8) | _payloadDevInfoAll[9];
HOY_SEMAPHORE_TAKE();
uint16_t ret = (((uint16_t)_payloadDevInfoAll[8]) << 8) | _payloadDevInfoAll[9];
HOY_SEMAPHORE_GIVE();
return ret;
}
uint32_t DevInfoParser::getHwPartNumber()
@ -129,8 +161,10 @@ uint32_t DevInfoParser::getHwPartNumber()
uint16_t hwpn_h;
uint16_t hwpn_l;
HOY_SEMAPHORE_TAKE();
hwpn_h = (((uint16_t)_payloadDevInfoSimple[2]) << 8) | _payloadDevInfoSimple[3];
hwpn_l = (((uint16_t)_payloadDevInfoSimple[4]) << 8) | _payloadDevInfoSimple[5];
HOY_SEMAPHORE_GIVE();
return ((uint32_t)hwpn_h << 16) | ((uint32_t)hwpn_l);
}
@ -138,7 +172,9 @@ uint32_t DevInfoParser::getHwPartNumber()
String DevInfoParser::getHwVersion()
{
char buf[8];
HOY_SEMAPHORE_TAKE();
snprintf(buf, sizeof(buf), "%02d.%02d", _payloadDevInfoSimple[6], _payloadDevInfoSimple[7]);
HOY_SEMAPHORE_GIVE();
return buf;
}
@ -162,26 +198,37 @@ String DevInfoParser::getHwModelName()
uint8_t DevInfoParser::getDevIdx()
{
uint8_t ret = 0xff;
uint8_t pos;
HOY_SEMAPHORE_TAKE();
// Check for all 4 bytes first
for (pos = 0; pos < sizeof(devInfo) / sizeof(devInfo_t); pos++) {
if (devInfo[pos].hwPart[0] == _payloadDevInfoSimple[2]
&& devInfo[pos].hwPart[1] == _payloadDevInfoSimple[3]
&& devInfo[pos].hwPart[2] == _payloadDevInfoSimple[4]
&& devInfo[pos].hwPart[3] == _payloadDevInfoSimple[5]) {
return pos;
ret = pos;
break;
}
}
// Then only for 3 bytes
// Then only for 3 bytes but only if not already found
if (ret == 0xff) {
for (pos = 0; pos < sizeof(devInfo) / sizeof(devInfo_t); pos++) {
if (devInfo[pos].hwPart[0] == _payloadDevInfoSimple[2]
&& devInfo[pos].hwPart[1] == _payloadDevInfoSimple[3]
&& devInfo[pos].hwPart[2] == _payloadDevInfoSimple[4]) {
return pos;
ret = pos;
break;
}
}
return 0xff;
}
HOY_SEMAPHORE_GIVE();
return ret;
}
/* struct tm to seconds since Unix epoch */

View File

@ -7,12 +7,16 @@
class DevInfoParser : public Parser {
public:
DevInfoParser();
void clearBufferAll();
void appendFragmentAll(uint8_t offset, uint8_t* payload, uint8_t len);
void clearBufferSimple();
void appendFragmentSimple(uint8_t offset, uint8_t* payload, uint8_t len);
void beginAppendFragment();
void endAppendFragment();
uint32_t getLastUpdateAll();
void setLastUpdateAll(uint32_t lastUpdate);
@ -41,4 +45,6 @@ private:
uint8_t _payloadDevInfoSimple[DEV_INFO_SIZE] = {};
uint8_t _devInfoSimpleLength = 0;
SemaphoreHandle_t _xSemaphore;
};

View File

@ -5,6 +5,11 @@
#include "StatisticsParser.h"
#include "../Hoymiles.h"
#define HOY_SEMAPHORE_TAKE() \
do { \
} while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS)
#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore)
static float calcYieldTotalCh0(StatisticsParser* iv, uint8_t arg0);
static float calcYieldDayCh0(StatisticsParser* iv, uint8_t arg0);
static float calcUdcCh(StatisticsParser* iv, uint8_t arg0);
@ -28,6 +33,14 @@ const calcFunc_t calcFunctions[] = {
{ CALC_IRR_CH, &calcIrradiation }
};
StatisticsParser::StatisticsParser()
: Parser()
{
_xSemaphore = xSemaphoreCreateMutex();
HOY_SEMAPHORE_GIVE(); // release before first use
clearBuffer();
}
void StatisticsParser::setByteAssignment(const byteAssign_t* byteAssignment, uint8_t size)
{
_byteAssignment = byteAssignment;
@ -62,6 +75,16 @@ void StatisticsParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t
_statisticLength += len;
}
void StatisticsParser::beginAppendFragment()
{
HOY_SEMAPHORE_TAKE();
}
void StatisticsParser::endAppendFragment()
{
HOY_SEMAPHORE_GIVE();
}
const byteAssign_t* StatisticsParser::getAssignmentByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
{
for (uint8_t i = 0; i < _byteAssignmentSize; i++) {
@ -98,10 +121,12 @@ float StatisticsParser::getChannelFieldValue(ChannelType_t type, ChannelNum_t ch
if (CMD_CALC != div) {
// Value is a static value
uint32_t val = 0;
HOY_SEMAPHORE_TAKE();
do {
val <<= 8;
val |= _payloadStatistic[ptr];
} while (++ptr != end);
HOY_SEMAPHORE_GIVE();
float result;
if (pos->isSigned && pos->num == 2) {

View File

@ -104,8 +104,11 @@ typedef struct {
class StatisticsParser : public Parser {
public:
StatisticsParser();
void clearBuffer();
void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len);
void beginAppendFragment();
void endAppendFragment();
void setByteAssignment(const byteAssign_t* byteAssignment, uint8_t size);
@ -142,8 +145,10 @@ private:
const byteAssign_t* _byteAssignment;
uint8_t _byteAssignmentSize;
uint8_t _expectedByteCount;
uint8_t _expectedByteCount = 0;
std::list<fieldSettings_t> _fieldSettings;
uint32_t _rxFailureCount = 0;
SemaphoreHandle_t _xSemaphore;
};

View File

@ -6,6 +6,19 @@
#include "../Hoymiles.h"
#include <cstring>
#define HOY_SEMAPHORE_TAKE() \
do { \
} while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS)
#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore)
SystemConfigParaParser::SystemConfigParaParser()
: Parser()
{
_xSemaphore = xSemaphoreCreateMutex();
HOY_SEMAPHORE_GIVE(); // release before first use
clearBuffer();
}
void SystemConfigParaParser::clearBuffer()
{
memset(_payload, 0, SYSTEM_CONFIG_PARA_SIZE);
@ -22,15 +35,30 @@ void SystemConfigParaParser::appendFragment(uint8_t offset, uint8_t* payload, ui
_payloadLength += len;
}
void SystemConfigParaParser::beginAppendFragment()
{
HOY_SEMAPHORE_TAKE();
}
void SystemConfigParaParser::endAppendFragment()
{
HOY_SEMAPHORE_GIVE();
}
float SystemConfigParaParser::getLimitPercent()
{
return ((((uint16_t)_payload[2]) << 8) | _payload[3]) / 10.0;
HOY_SEMAPHORE_TAKE();
float ret = ((((uint16_t)_payload[2]) << 8) | _payload[3]) / 10.0;
HOY_SEMAPHORE_GIVE();
return ret;
}
void SystemConfigParaParser::setLimitPercent(float value)
{
HOY_SEMAPHORE_TAKE();
_payload[2] = ((uint16_t)(value * 10)) >> 8;
_payload[3] = ((uint16_t)(value * 10));
HOY_SEMAPHORE_GIVE();
}
void SystemConfigParaParser::setLastLimitCommandSuccess(LastCommandSuccess status)

View File

@ -7,8 +7,11 @@
class SystemConfigParaParser : public Parser {
public:
SystemConfigParaParser();
void clearBuffer();
void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len);
void beginAppendFragment();
void endAppendFragment();
float getLimitPercent();
void setLimitPercent(float value);
@ -32,4 +35,6 @@ private:
uint32_t _lastUpdateCommand = 0;
uint32_t _lastUpdateRequest = 0;
SemaphoreHandle_t _xSemaphore;
};

View File

@ -0,0 +1,62 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include <optional>
#include <queue>
template <typename T>
class ThreadSafeQueue {
public:
ThreadSafeQueue() = default;
ThreadSafeQueue(const ThreadSafeQueue<T>&) = delete;
ThreadSafeQueue& operator=(const ThreadSafeQueue<T>&) = delete;
ThreadSafeQueue(ThreadSafeQueue<T>&& other)
{
std::lock_guard<std::mutex> lock(_mutex);
_queue = std::move(other._queue);
}
virtual ~ThreadSafeQueue() { }
unsigned long size() const
{
std::lock_guard<std::mutex> lock(_mutex);
return _queue.size();
}
std::optional<T> pop()
{
std::lock_guard<std::mutex> lock(_mutex);
if (_queue.empty()) {
return {};
}
T tmp = _queue.front();
_queue.pop();
return tmp;
}
void push(const T& item)
{
std::lock_guard<std::mutex> lock(_mutex);
_queue.push(item);
}
T front()
{
std::lock_guard<std::mutex> lock(_mutex);
return _queue.front();
}
private:
// Moved out of public interface to prevent races between this
// and pop().
bool empty() const
{
return _queue.empty();
}
std::queue<T> _queue;
mutable std::mutex _mutex;
};

View File

@ -32,10 +32,10 @@ build_unflags =
lib_deps =
https://github.com/yubox-node-org/ESPAsyncWebServer
bblanchon/ArduinoJson @ ^6.21.2
bblanchon/ArduinoJson @ ^6.21.3
https://github.com/bertmelis/espMqttClient.git#v1.4.4
nrf24/RF24 @ ^1.4.7
olikraus/U8g2 @ ^2.34.22
olikraus/U8g2 @ ^2.35.4
buelowp/sunset @ ^1.1.7
extra_scripts =
@ -64,6 +64,7 @@ board = esp32-c3-devkitc-02
custom_patches = esp32c3
build_flags = ${env.build_flags}
[env:generic_esp32c3_usb]
board = esp32-c3-devkitc-02
custom_patches = esp32c3
@ -71,6 +72,7 @@ build_flags = ${env.build_flags}
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1
[env:generic_esp32s3]
board = esp32-s3-devkitc-1
build_flags = ${env.build_flags}

View File

@ -39,6 +39,7 @@ bool ConfigurationClass::write()
wifi["dns2"] = IPAddress(config.WiFi_Dns2).toString();
wifi["dhcp"] = config.WiFi_Dhcp;
wifi["hostname"] = config.WiFi_Hostname;
wifi["aptimeout"] = config.WiFi_ApTimeout;
JsonObject ntp = doc.createNestedObject("ntp");
ntp["server"] = config.Ntp_Server;
@ -184,6 +185,7 @@ bool ConfigurationClass::read()
config.WiFi_Dns2[3] = wifi_dns2[3];
config.WiFi_Dhcp = wifi["dhcp"] | WIFI_DHCP;
config.WiFi_ApTimeout = wifi["aptimeout"] | ACCESS_POINT_TIMEOUT;
JsonObject ntp = doc["ntp"];
strlcpy(config.Ntp_Server, ntp["server"] | NTP_SERVER, sizeof(config.Ntp_Server));

View File

@ -6,19 +6,8 @@
#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()
{
_updateTimeout.set(1000);
@ -32,7 +21,7 @@ void DatastoreClass::loop()
uint8_t isReachable = 0;
uint8_t pollEnabledCount = 0;
DAT_SEMAPHORE_TAKE();
std::lock_guard<std::mutex> lock(_mutex);
_totalAcYieldTotalEnabled = 0;
_totalAcYieldTotalDigits = 0;
@ -116,136 +105,102 @@ void DatastoreClass::loop()
_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;
std::lock_guard<std::mutex> lock(_mutex);
return _totalAcYieldTotalEnabled;
}
float DatastoreClass::getTotalAcYieldDayEnabled()
{
DAT_SEMAPHORE_TAKE();
float retval = _totalAcYieldDayEnabled;
DAT_SEMAPHORE_GIVE();
return retval;
std::lock_guard<std::mutex> lock(_mutex);
return _totalAcYieldDayEnabled;
}
float DatastoreClass::getTotalAcPowerEnabled()
{
DAT_SEMAPHORE_TAKE();
float retval = _totalAcPowerEnabled;
DAT_SEMAPHORE_GIVE();
return retval;
std::lock_guard<std::mutex> lock(_mutex);
return _totalAcPowerEnabled;
}
float DatastoreClass::getTotalDcPowerEnabled()
{
DAT_SEMAPHORE_TAKE();
float retval = _totalDcPowerEnabled;
DAT_SEMAPHORE_GIVE();
return retval;
std::lock_guard<std::mutex> lock(_mutex);
return _totalDcPowerEnabled;
}
float DatastoreClass::getTotalDcPowerIrradiation()
{
DAT_SEMAPHORE_TAKE();
float retval = _totalDcPowerIrradiation;
DAT_SEMAPHORE_GIVE();
return retval;
std::lock_guard<std::mutex> lock(_mutex);
return _totalDcPowerIrradiation;
}
float DatastoreClass::getTotalDcIrradiationInstalled()
{
DAT_SEMAPHORE_TAKE();
float retval = _totalDcIrradiationInstalled;
DAT_SEMAPHORE_GIVE();
return retval;
std::lock_guard<std::mutex> lock(_mutex);
return _totalDcIrradiationInstalled;
}
float DatastoreClass::getTotalDcIrradiation()
{
DAT_SEMAPHORE_TAKE();
float retval = _totalDcIrradiation;
DAT_SEMAPHORE_GIVE();
return retval;
std::lock_guard<std::mutex> lock(_mutex);
return _totalDcIrradiation;
}
unsigned int DatastoreClass::getTotalAcYieldTotalDigits()
{
DAT_SEMAPHORE_TAKE();
unsigned int retval = _totalAcYieldTotalDigits;
DAT_SEMAPHORE_GIVE();
return retval;
std::lock_guard<std::mutex> lock(_mutex);
return _totalAcYieldTotalDigits;
}
unsigned int DatastoreClass::getTotalAcYieldDayDigits()
{
DAT_SEMAPHORE_TAKE();
unsigned int retval = _totalAcYieldDayDigits;
DAT_SEMAPHORE_GIVE();
return retval;
std::lock_guard<std::mutex> lock(_mutex);
return _totalAcYieldDayDigits;
}
unsigned int DatastoreClass::getTotalAcPowerDigits()
{
DAT_SEMAPHORE_TAKE();
unsigned int retval = _totalAcPowerDigits;
DAT_SEMAPHORE_GIVE();
return retval;
std::lock_guard<std::mutex> lock(_mutex);
return _totalAcPowerDigits;
}
unsigned int DatastoreClass::getTotalDcPowerDigits()
{
DAT_SEMAPHORE_TAKE();
unsigned int retval = _totalDcPowerDigits;
DAT_SEMAPHORE_GIVE();
return retval;
std::lock_guard<std::mutex> lock(_mutex);
return _totalDcPowerDigits;
}
bool DatastoreClass::getIsAtLeastOneReachable()
{
DAT_SEMAPHORE_TAKE();
bool retval = _isAtLeastOneReachable;
DAT_SEMAPHORE_GIVE();
return retval;
std::lock_guard<std::mutex> lock(_mutex);
return _isAtLeastOneReachable;
}
bool DatastoreClass::getIsAtLeastOneProducing()
{
DAT_SEMAPHORE_TAKE();
bool retval = _isAtLeastOneProducing;
DAT_SEMAPHORE_GIVE();
return retval;
std::lock_guard<std::mutex> lock(_mutex);
return _isAtLeastOneProducing;
}
bool DatastoreClass::getIsAllEnabledProducing()
{
DAT_SEMAPHORE_TAKE();
bool retval = _isAllEnabledProducing;
DAT_SEMAPHORE_GIVE();
return retval;
std::lock_guard<std::mutex> lock(_mutex);
return _isAllEnabledProducing;
}
bool DatastoreClass::getIsAllEnabledReachable()
{
DAT_SEMAPHORE_TAKE();
bool retval = _isAllEnabledReachable;
DAT_SEMAPHORE_GIVE();
return retval;
std::lock_guard<std::mutex> lock(_mutex);
return _isAllEnabledReachable;
}
bool DatastoreClass::getIsAtLeastOnePollEnabled()
{
DAT_SEMAPHORE_TAKE();
bool retval = _isAtLeastOnePollEnabled;
DAT_SEMAPHORE_GIVE();
return retval;
std::lock_guard<std::mutex> lock(_mutex);
return _isAtLeastOnePollEnabled;
}

View File

@ -8,17 +8,6 @@
MessageOutputClass MessageOutput;
#define MSG_LOCK() \
do { \
} while (xSemaphoreTake(_lock, portMAX_DELAY) != pdPASS)
#define MSG_UNLOCK() xSemaphoreGive(_lock)
MessageOutputClass::MessageOutputClass()
{
_lock = xSemaphoreCreateMutex();
MSG_UNLOCK();
}
void MessageOutputClass::register_ws_output(AsyncWebSocket* output)
{
_ws = output;
@ -27,10 +16,9 @@ void MessageOutputClass::register_ws_output(AsyncWebSocket* output)
size_t MessageOutputClass::write(uint8_t c)
{
if (_buff_pos < BUFFER_SIZE) {
MSG_LOCK();
std::lock_guard<std::mutex> lock(_msgLock);
_buffer[_buff_pos] = c;
_buff_pos++;
MSG_UNLOCK();
} else {
_forceSend = true;
}
@ -38,11 +26,23 @@ size_t MessageOutputClass::write(uint8_t c)
return Serial.write(c);
}
size_t MessageOutputClass::write(const uint8_t* buffer, size_t size)
{
std::lock_guard<std::mutex> lock(_msgLock);
if (_buff_pos + size < BUFFER_SIZE) {
memcpy(&_buffer[_buff_pos], buffer, size);
_buff_pos += size;
}
_forceSend = true;
return Serial.write(buffer, size);
}
void MessageOutputClass::loop()
{
// Send data via websocket if either time is over or buffer is full
if (_forceSend || (millis() - _lastSend > 1000)) {
MSG_LOCK();
std::lock_guard<std::mutex> lock(_msgLock);
if (_ws && _buff_pos > 0) {
_ws->textAll(_buffer, _buff_pos);
_buff_pos = 0;
@ -50,7 +50,6 @@ void MessageOutputClass::loop()
if (_forceSend) {
_buff_pos = 0;
}
MSG_UNLOCK();
_forceSend = false;
}
}

View File

@ -3,8 +3,8 @@
* Copyright (C) 2022 Thomas Basler and others
*/
#include "MqttSettings.h"
#include "MessageOutput.h"
#include "Configuration.h"
#include "MessageOutput.h"
MqttSettingsClass::MqttSettingsClass()
{
@ -32,21 +32,30 @@ void MqttSettingsClass::onMqttConnect(bool sessionPresent)
const CONFIG_T& config = Configuration.get();
publish(config.Mqtt_LwtTopic, config.Mqtt_LwtValue_Online);
std::lock_guard<std::mutex> lock(_clientLock);
if (mqttClient != nullptr) {
for (const auto& cb : _mqttSubscribeParser.get_callbacks()) {
mqttClient->subscribe(cb.topic.c_str(), cb.qos);
}
}
}
void MqttSettingsClass::subscribe(const String& topic, uint8_t qos, const espMqttClientTypes::OnMessageCallback& cb)
{
_mqttSubscribeParser.register_callback(topic.c_str(), qos, cb);
std::lock_guard<std::mutex> lock(_clientLock);
if (mqttClient != nullptr) {
mqttClient->subscribe(topic.c_str(), qos);
}
}
void MqttSettingsClass::unsubscribe(const String& topic)
{
_mqttSubscribeParser.unregister_callback(topic.c_str());
std::lock_guard<std::mutex> lock(_clientLock);
if (mqttClient != nullptr) {
mqttClient->unsubscribe(topic.c_str());
}
}
void MqttSettingsClass::onMqttDisconnect(espMqttClientTypes::DisconnectReason reason)
@ -97,6 +106,12 @@ void MqttSettingsClass::performConnect()
using std::placeholders::_4;
using std::placeholders::_5;
using std::placeholders::_6;
std::lock_guard<std::mutex> lock(_clientLock);
if (mqttClient == nullptr) {
return;
}
MessageOutput.println("Connecting to MQTT...");
const CONFIG_T& config = Configuration.get();
willTopic = getPrefix() + config.Mqtt_LwtTopic;
@ -132,6 +147,10 @@ void MqttSettingsClass::performDisconnect()
{
const CONFIG_T& config = Configuration.get();
publish(config.Mqtt_LwtTopic, config.Mqtt_LwtValue_Offline);
std::lock_guard<std::mutex> lock(_clientLock);
if (mqttClient == nullptr) {
return;
}
mqttClient->disconnect();
}
@ -147,6 +166,10 @@ void MqttSettingsClass::performReconnect()
bool MqttSettingsClass::getConnected()
{
std::lock_guard<std::mutex> lock(_clientLock);
if (mqttClient == nullptr) {
return false;
}
return mqttClient->connected();
}
@ -157,6 +180,11 @@ String MqttSettingsClass::getPrefix()
void MqttSettingsClass::publish(const String& subtopic, const String& payload)
{
std::lock_guard<std::mutex> lock(_clientLock);
if (mqttClient == nullptr) {
return;
}
String topic = getPrefix();
topic += subtopic;
@ -168,6 +196,10 @@ void MqttSettingsClass::publish(const String& subtopic, const String& payload)
void MqttSettingsClass::publishGeneric(const String& topic, const String& payload, bool retain, uint8_t qos)
{
std::lock_guard<std::mutex> lock(_clientLock);
if (mqttClient == nullptr) {
return;
}
mqttClient->publish(topic.c_str(), qos, retain, payload.c_str());
}
@ -181,8 +213,11 @@ void MqttSettingsClass::init()
void MqttSettingsClass::createMqttClientObject()
{
if (mqttClient != nullptr)
std::lock_guard<std::mutex> lock(_clientLock);
if (mqttClient != nullptr) {
delete mqttClient;
mqttClient = nullptr;
}
const CONFIG_T& config = Configuration.get();
if (config.Mqtt_Tls) {
mqttClient = static_cast<MqttClient*>(new espMqttClientSecure);

View File

@ -21,6 +21,9 @@ void NetworkSettingsClass::init()
{
using std::placeholders::_1;
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
WiFi.onEvent(std::bind(&NetworkSettingsClass::NetworkEvent, this, _1));
setupMode();
}
@ -121,8 +124,6 @@ void NetworkSettingsClass::setupMode()
dnsServer->stop();
dnsServerStatus = false;
if (_networkMode == network_mode::WiFi) {
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
WiFi.mode(WIFI_STA);
} else {
WiFi.mode(WIFI_MODE_NULL);
@ -139,6 +140,7 @@ void NetworkSettingsClass::enableAdminMode()
{
adminEnabled = true;
adminTimeoutCounter = 0;
adminTimeoutCounterMax = Configuration.get().WiFi_ApTimeout * 60;
setupMode();
}
@ -158,8 +160,7 @@ void NetworkSettingsClass::loop()
setStaticIp();
setHostname();
}
} else
if (_networkMode != network_mode::WiFi) {
} else if (_networkMode != network_mode::WiFi) {
// Do stuff when switching to Ethernet mode
MessageOutput.println("Switch to WiFi mode");
_networkMode = network_mode::WiFi;
@ -168,7 +169,12 @@ void NetworkSettingsClass::loop()
}
if (millis() - lastTimerCall > 1000) {
if (adminEnabled && adminTimeoutCounterMax > 0) {
adminTimeoutCounter++;
if (adminTimeoutCounter % 10 == 0) {
MessageOutput.printf("Admin AP remaining seconds: %d / %d\r\n", adminTimeoutCounter, adminTimeoutCounterMax);
}
}
connectTimeoutTimer++;
connectRedoTimer++;
lastTimerCall = millis();
@ -178,9 +184,9 @@ void NetworkSettingsClass::loop()
if (!isConnected()) {
adminTimeoutCounter = 0;
}
// If WiFi is connected to AP for more than ADMIN_TIMEOUT
// If WiFi is connected to AP for more than adminTimeoutCounterMax
// seconds, disable the internal Access Point
if (adminTimeoutCounter > ADMIN_TIMEOUT) {
if (adminTimeoutCounter > adminTimeoutCounterMax) {
adminEnabled = false;
MessageOutput.println("Admin mode disabled");
setupMode();
@ -248,8 +254,7 @@ void NetworkSettingsClass::setHostname()
WiFi.mode(WIFI_MODE_APSTA);
WiFi.mode(WIFI_MODE_STA);
setupMode();
}
else if (_networkMode == network_mode::Ethernet) {
} else if (_networkMode == network_mode::Ethernet) {
if (ETH.setHostname(getHostname().c_str())) {
MessageOutput.println("done");
} else {
@ -275,8 +280,7 @@ void NetworkSettingsClass::setStaticIp()
IPAddress(Configuration.get().WiFi_Dns2));
MessageOutput.println("done");
}
}
else if (_networkMode == network_mode::Ethernet) {
} else if (_networkMode == network_mode::Ethernet) {
if (Configuration.get().WiFi_Dhcp) {
MessageOutput.print("Configuring Ethernet DHCP IP... ");
ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);

View File

@ -11,23 +11,23 @@
#define JSON_BUFFER_SIZE 6144
#ifndef DISPLAY_TYPE
#define DISPLAY_TYPE 0
#define DISPLAY_TYPE 0U
#endif
#ifndef DISPLAY_DATA
#define DISPLAY_DATA 255
#define DISPLAY_DATA 255U
#endif
#ifndef DISPLAY_CLK
#define DISPLAY_CLK 255
#define DISPLAY_CLK 255U
#endif
#ifndef DISPLAY_CS
#define DISPLAY_CS 255
#define DISPLAY_CS 255U
#endif
#ifndef DISPLAY_RESET
#define DISPLAY_RESET 255
#define DISPLAY_RESET 255U
#endif
#ifndef LED0

View File

@ -125,7 +125,8 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
return;
}
if (!(root.containsKey("curPin") || root.containsKey("display"))) {
if (!(root.containsKey("curPin")
|| root.containsKey("display"))) {
retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength();

View File

@ -89,7 +89,11 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
return;
}
if (!(root.containsKey("serial") && root.containsKey("pollinterval") && root.containsKey("nrf_palevel") && root.containsKey("cmt_palevel") && root.containsKey("cmt_frequency"))) {
if (!(root.containsKey("serial")
&& root.containsKey("pollinterval")
&& root.containsKey("nrf_palevel")
&& root.containsKey("cmt_palevel")
&& root.containsKey("cmt_frequency"))) {
retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength();

View File

@ -121,7 +121,8 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
return;
}
if (!(root.containsKey("serial") && root.containsKey("name"))) {
if (!(root.containsKey("serial")
&& root.containsKey("name"))) {
retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength();
@ -443,7 +444,7 @@ void WebApiInverterClass::onInverterOrder(AsyncWebServerRequest* request)
// 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) {
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];

View File

@ -75,6 +75,7 @@ void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request)
root["dns2"] = IPAddress(config.WiFi_Dns2).toString();
root["ssid"] = config.WiFi_Ssid;
root["password"] = config.WiFi_Password;
root["aptimeout"] = config.WiFi_ApTimeout;
response->setLength();
request->send(response);
@ -119,7 +120,16 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
return;
}
if (!(root.containsKey("ssid") && root.containsKey("password") && root.containsKey("hostname") && root.containsKey("dhcp") && root.containsKey("ipaddress") && root.containsKey("netmask") && root.containsKey("gateway") && root.containsKey("dns1") && root.containsKey("dns2"))) {
if (!(root.containsKey("ssid")
&& root.containsKey("password")
&& root.containsKey("hostname")
&& root.containsKey("dhcp")
&& root.containsKey("ipaddress")
&& root.containsKey("netmask")
&& root.containsKey("gateway")
&& root.containsKey("dns1")
&& root.containsKey("dns2")
&& root.containsKey("aptimeout"))) {
retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength();
@ -188,6 +198,13 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
request->send(response);
return;
}
if (root["aptimeout"].as<uint>() > 99999) {
retMsg["message"] = "ApTimeout must be a number between 0 and 99999!";
retMsg["code"] = WebApiError::NetworkApTimeoutInvalid;
response->setLength();
request->send(response);
return;
}
CONFIG_T& config = Configuration.get();
config.WiFi_Ip[0] = ipaddress[0];
@ -218,6 +235,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
} else {
config.WiFi_Dhcp = false;
}
config.WiFi_ApTimeout = root["aptimeout"].as<uint>();
Configuration.write();
retMsg["type"] = "success";

View File

@ -133,7 +133,11 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
return;
}
if (!(root.containsKey("ntp_server") && root.containsKey("ntp_timezone") && root.containsKey("longitude") && root.containsKey("latitude") && root.containsKey("sunsettype"))) {
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();

View File

@ -90,7 +90,8 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
}
if (!(root.containsKey("serial")
&& (root.containsKey("power") || root.containsKey("restart")))) {
&& (root.containsKey("power")
|| root.containsKey("restart")))) {
retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength();

View File

@ -143,8 +143,7 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, String& se
idx,
inv->name(),
channel,
config.Inverter[idx].channel[channel].Name
);
config.Inverter[idx].channel[channel].Name);
if (printHelp) {
stream->print("# HELP opendtu_MaxPower panel maximum output power\n");
@ -155,8 +154,7 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, String& se
idx,
inv->name(),
channel,
config.Inverter[idx].channel[channel].MaxChannelPower
);
config.Inverter[idx].channel[channel].MaxChannelPower);
if (printHelp) {
stream->print("# HELP opendtu_YieldTotalOffset panel yield offset (for used inverters)\n");
@ -167,6 +165,5 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, String& se
idx,
inv->name(),
channel,
config.Inverter[idx].channel[channel].YieldTotalOffset
);
config.Inverter[idx].channel[channel].YieldTotalOffset);
}

View File

@ -58,7 +58,7 @@ void WebApiWebappClass::init(AsyncWebServer* server)
#ifdef AUTO_GIT_HASH
// check client If-None-Match header vs ETag/AUTO_GIT_HASH
bool eTagMatch = false;
if(request->hasHeader("If-None-Match")){
if (request->hasHeader("If-None-Match")) {
AsyncWebHeader* h = request->getHeader("If-None-Match");
if (strncmp(AUTO_GIT_HASH, h->value().c_str(), strlen(AUTO_GIT_HASH)) == 0) {
eTagMatch = true;

View File

@ -169,9 +169,7 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
JsonObject hintObj = root.createNestedObject("hints");
struct tm timeinfo;
hintObj["time_sync"] = !getLocalTime(&timeinfo, 5);
hintObj["radio_problem"] =
(Hoymiles.getRadioNrf()->isInitialized() && (!Hoymiles.getRadioNrf()->isConnected() || !Hoymiles.getRadioNrf()->isPVariant())) ||
(Hoymiles.getRadioCmt()->isInitialized() && (!Hoymiles.getRadioCmt()->isConnected()));
hintObj["radio_problem"] = (Hoymiles.getRadioNrf()->isInitialized() && (!Hoymiles.getRadioNrf()->isConnected() || !Hoymiles.getRadioNrf()->isPVariant())) || (Hoymiles.getRadioCmt()->isInitialized() && (!Hoymiles.getRadioCmt()->isConnected()));
if (!strcmp(Configuration.get().Security_Password, ACCESS_POINT_PASSWORD)) {
hintObj["default_password"] = true;
} else {

View File

@ -12,7 +12,7 @@
},
"dependencies": {
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.0",
"bootstrap": "^5.3.1",
"bootstrap-icons-vue": "^1.10.3",
"mitt": "^3.0.1",
"sortablejs": "^1.15.0",
@ -24,24 +24,24 @@
},
"devDependencies": {
"@intlify/unplugin-vue-i18n": "^0.12.2",
"@rushstack/eslint-patch": "^1.3.2",
"@rushstack/eslint-patch": "^1.3.3",
"@tsconfig/node18": "^18.2.0",
"@types/bootstrap": "^5.2.6",
"@types/node": "^20.4.1",
"@types/node": "^20.4.8",
"@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.44.0",
"eslint-plugin-vue": "^9.15.1",
"eslint": "^8.46.0",
"eslint-plugin-vue": "^9.16.1",
"npm-run-all": "^4.1.5",
"sass": "^1.63.6",
"terser": "^5.19.0",
"sass": "^1.64.2",
"terser": "^5.19.2",
"typescript": "^5.1.6",
"vite": "^4.4.3",
"vite": "^4.4.9",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-css-injected-by-js": "^3.2.0",
"vue-tsc": "^1.8.4"
"vite-plugin-css-injected-by-js": "^3.3.0",
"vue-tsc": "^1.8.8"
}
}

View File

@ -52,7 +52,11 @@ export default defineComponent({
_countDownTimeout = undefined;
};
const countDown = ref(parseCountDown(props.modelValue));
var countDown = ref();
watch(() => props.modelValue, () => {
countDown.value = parseCountDown(props.modelValue);
});
const isAlertVisible = computed(() => props.modelValue || props.show);
onBeforeUnmount(() => {

View File

@ -17,11 +17,11 @@
{{ capitalizeFirstLetter(category) }}</td>
<td :class="{ 'table-danger': !isEqual(category, prop) }">{{ prop }}</td>
<td>
<template v-if="((selectedPinAssignment as Device)[category as keyof Device])">
<template v-if="selectedPinAssignment && category in selectedPinAssignment">
{{ (selectedPinAssignment as any)[category][prop] }}</template>
</td>
<td>
<template v-if="((currentPinAssignment as Device)[category as keyof Device])">
<template v-if="currentPinAssignment && category in currentPinAssignment">
{{ (currentPinAssignment as any)[category][prop] }}</template>
</td>
</tr>
@ -65,12 +65,12 @@ export default defineComponent({
methods: {
properties(category: string): string[] {
let curArray: Array<string> = [];
if ((this.currentPinAssignment as Device)[category as keyof Device]) {
if (this.currentPinAssignment && category in this.currentPinAssignment) {
curArray = Object.keys((this.currentPinAssignment as Device)[category as keyof Device]);
}
let selArray: Array<string> = [];
if ((this.selectedPinAssignment as Device)[category as keyof Device]) {
if (this.selectedPinAssignment && category in this.selectedPinAssignment) {
selArray = Object.keys((this.selectedPinAssignment as Device)[category as keyof Device]);
}
@ -83,10 +83,10 @@ export default defineComponent({
let comSel = 999999;
let comCur = 999999;
if ((this.selectedPinAssignment as Device)[category as keyof Device]) {
if (this.selectedPinAssignment && category in this.selectedPinAssignment) {
comSel = (this.selectedPinAssignment as any)[category][prop];
}
if ((this.currentPinAssignment as Device)[category as keyof Device]) {
if (this.currentPinAssignment && category in this.currentPinAssignment) {
comCur = (this.currentPinAssignment as any)[category][prop];
}

View File

@ -78,6 +78,7 @@
"8003": "Standardgateway ist ungültig!",
"8004": "DNS-Server-IP 1 ist ungültig!",
"8005": "DNS-Server-IP 2 ist ungültig!",
"8006": "Administrative AccessPoint Timeout-Wert ist ungültig!",
"9001": "Zeitserver muss zwischen 1 und {max} Zeichen lang sein!",
"9002": "Zeitzone muss zwischen 1 und {max} Zeichen lang sein!",
"9003": "Zeitzonenbeschreibung muss zwischen 1 und {max} Zeichen lang sein!",
@ -388,6 +389,10 @@
"Netmask": "Netzmaske:",
"DefaultGateway": "Standardgateway:",
"Dns": "DNS-Server {num}:",
"AdminAp": "WLAN-Konfiguration (Admin AccessPoint)",
"ApTimeout": "AccessPoint Zeitlimit:",
"ApTimeoutHint": "Zeit die der AccessPoint offen gehalten wird. Ein Wert von 0 bedeutet unendlich.",
"Minutes": "Minuten",
"Save": "@:dtuadmin.Save"
},
"mqttadmin": {
@ -527,6 +532,7 @@
},
"deviceadmin": {
"DeviceManager": "Hardware-Einstellungen",
"ParseError": "Syntaxfehler in 'pin_mapping.json': {error}",
"PinAssignment": "Anschlusseinstellungen",
"SelectedProfile": "Ausgewähltes Profil:",
"DefaultProfile": "(Standardeinstellungen)",

View File

@ -78,6 +78,7 @@
"8003": "Gateway is invalid!",
"8004": "DNS Server IP 1 is invalid!",
"8005": "DNS Server IP 2 is invalid!",
"8006": "Administrative AccessPoint Timeout value is invalid",
"9001": "NTP Server must between 1 and {max} characters long!",
"9002": "Timezone must between 1 and {max} characters long!",
"9003": "Timezone description must between 1 and {max} characters long!",
@ -388,6 +389,10 @@
"Netmask": "Netmask:",
"DefaultGateway": "Default Gateway:",
"Dns": "DNS Server {num}:",
"AdminAp": "WiFi Configuration (Admin AccessPoint)",
"ApTimeout": "AccessPoint Timeout:",
"ApTimeoutHint": "Time which the AccessPoint is kept open. A value of 0 means infinite.",
"Minutes": "minutes",
"Save": "@:dtuadmin.Save"
},
"mqttadmin": {
@ -527,6 +532,7 @@
},
"deviceadmin": {
"DeviceManager": "Device-Manager",
"ParseError": "Parse error in 'pin_mapping.json': {error}",
"PinAssignment": "Connection settings",
"SelectedProfile": "Selected profile:",
"DefaultProfile": "(Default settings)",

View File

@ -78,6 +78,7 @@
"8003": "La passerelle n'est pas valide !",
"8004": "L'adresse IP du serveur DNS primaire n'est pas valide !",
"8005": "L'adresse IP du serveur DNS secondaire n'est pas valide !",
"8006": "La valeur du délai d'attente du point d'accès administratif n'est pas valide !",
"9001": "Le serveur NTP doit avoir une longueur comprise entre 1 et {max} caractères !",
"9002": "Le fuseau horaire doit comporter entre 1 et {max} caractères !",
"9003": "La description du fuseau horaire doit comporter entre 1 et {max} caractères !",
@ -388,6 +389,10 @@
"Netmask": "Masque de réseau",
"DefaultGateway": "Passerelle par défaut",
"Dns": "Serveur DNS {num}",
"AdminAp": "Configuration du réseau WiFi (Point d'accès)",
"ApTimeout": "Délai d'attente du point d'accès",
"ApTimeoutHint": "Durée pendant laquelle le point d'accès reste ouvert. Une valeur de 0 signifie infini.",
"Minutes": "minutes",
"Save": "@:dtuadmin.Save"
},
"mqttadmin": {
@ -527,6 +532,7 @@
},
"deviceadmin": {
"DeviceManager": "Gestionnaire de périphériques",
"ParseError": "Erreur d'analyse dans 'pin_mapping.json': {error}",
"PinAssignment": "Paramètres de connexion",
"SelectedProfile": "Profil sélectionné",
"DefaultProfile": "(Réglages par défaut)",

View File

@ -8,4 +8,5 @@ export interface NetworkConfig {
gateway: string;
dns1: string;
dns2: string;
aptimeout: number;
}

View File

@ -73,10 +73,10 @@ export function handleResponse(response: Response, emitter: Emitter<Record<Event
// auto logout if 401 response returned from api
logout();
emitter.emit("logged-out");
router.push({path: "/login", query: { returnUrl: router.currentRoute.value.fullPath }});
router.push({ path: "/login", query: { returnUrl: router.currentRoute.value.fullPath } });
}
const error = (data && data.message) || response.statusText;
const error = { message: (data && data.message) || response.statusText, status: response.status || 0 };
return Promise.reject(error);
}

View File

@ -157,7 +157,12 @@ export default defineComponent({
this.pinMappingList = data;
}
)
.catch(() => {
.catch((error) => {
if (error.status != 404) {
this.alertMessage = this.$t('deviceadmin.ParseError', { error: error.message });
this.alertType = 'danger';
this.showAlert = true;
}
this.pinMappingList = Array<Device>();
})
.finally(() => {

View File

@ -50,6 +50,13 @@
type="text" maxlength="32"/>
</CardElement>
<CardElement :text="$t('networkadmin.AdminAp')" textVariant="text-bg-primary" add-space>
<InputElement :label="$t('networkadmin.ApTimeout')"
v-model="networkConfigList.aptimeout"
type="number" min="0" max="99999"
:postfix="$t('networkadmin.Minutes')"
:tooltip="$t('networkadmin.ApTimeoutHint')"/>
</CardElement>
<button type="submit" class="btn btn-primary mb-3">{{ $t('networkadmin.Save') }}</button>
</form>
</BasePage>

View File

@ -134,10 +134,10 @@
dependencies:
eslint-visitor-keys "^3.3.0"
"@eslint-community/eslint-utils@^4.3.0":
version "4.3.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.3.0.tgz#a556790523a351b4e47e9d385f47265eaaf9780a"
integrity sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA==
"@eslint-community/eslint-utils@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
dependencies:
eslint-visitor-keys "^3.3.0"
@ -146,10 +146,15 @@
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.4.0.tgz#3e61c564fcd6b921cb789838631c5ee44df09403"
integrity sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==
"@eslint/eslintrc@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.0.tgz#82256f164cc9e0b59669efc19d57f8092706841d"
integrity sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==
"@eslint-community/regexpp@^4.6.1":
version "4.6.2"
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8"
integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==
"@eslint/eslintrc@^2.1.1":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.1.tgz#18d635e24ad35f7276e8a49d135c7d3ca6a46f93"
integrity sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
@ -161,10 +166,10 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/js@8.44.0":
version "8.44.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.44.0.tgz#961a5903c74139390478bdc808bcde3fc45ab7af"
integrity sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==
"@eslint/js@^8.46.0":
version "8.46.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.46.0.tgz#3f7802972e8b6fe3f88ed1aabc74ec596c456db6"
integrity sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==
"@humanwhocodes/config-array@^0.11.10":
version "0.11.10"
@ -350,10 +355,10 @@
estree-walker "^2.0.2"
picomatch "^2.3.1"
"@rushstack/eslint-patch@^1.3.2":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.3.2.tgz#31b9c510d8cada9683549e1dbb4284cca5001faf"
integrity sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw==
"@rushstack/eslint-patch@^1.3.3":
version "1.3.3"
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.3.3.tgz#16ab6c727d8c2020a5b6e4a176a243ecd88d8d69"
integrity sha512-0xd7qez0AQ+MbHatZTlI1gu5vkG8r7MYRUJAHPAHJBmGLs16zpkrpAVLvjQKQOqaXPDUBwOiJzNc00znHSCVBw==
"@tsconfig/node18@^18.2.0":
version "18.2.0"
@ -377,10 +382,10 @@
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.4.1":
version "20.4.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.1.tgz#a6033a8718653c50ac4962977e14d0f984d9527d"
integrity sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg==
"@types/node@^20.4.8":
version "20.4.8"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.8.tgz#b5dda19adaa473a9bf0ab5cbd8f30ec7d43f5c85"
integrity sha512-0mHckf6D2DiIAzh8fM8f3HQCvMKDpK94YQ0DSVkfWTG9BZleYIWudw9cJxX8oCk9bM+vAkDyujDV6dmKHbvQpg==
"@types/semver@^7.3.12":
version "7.3.13"
@ -486,26 +491,26 @@
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz#ee0b6dfcc62fe65364e6395bf38fa2ba10bb44b6"
integrity sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==
"@volar/language-core@1.8.0", "@volar/language-core@~1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-1.8.0.tgz#11e7d85265e70e85b7342dbf531db582406ec677"
integrity sha512-ZHTvZPM3pEbOOuaq+ybNz5TQlHUqPQPK0G1+SonvApGq0e3qgGijjhtL5T7hsCtUEmxfix8FrAuCH14tMBOhTg==
"@volar/language-core@1.10.0", "@volar/language-core@~1.10.0":
version "1.10.0"
resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-1.10.0.tgz#fb6b3ad22e75c53a1ae4d644c4a788b47d411b9d"
integrity sha512-ddyWwSYqcbEZNFHm+Z3NZd6M7Ihjcwl/9B5cZd8kECdimVXUFdFi60XHWD27nrWtUQIsUYIG7Ca1WBwV2u2LSQ==
dependencies:
"@volar/source-map" "1.8.0"
"@volar/source-map" "1.10.0"
"@volar/source-map@1.8.0", "@volar/source-map@~1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-1.8.0.tgz#2893a7bc9a41563da803377b33fbf8407eb4c5e1"
integrity sha512-d35aV0yFkIrkynRSKgrN5hgbMv6ekkFvcJsJGmOZ8UEjqLStto9zq7RSvpp6/PZ7/pa4Gn1f6K1qDt0bq0oUew==
"@volar/source-map@1.10.0", "@volar/source-map@~1.10.0":
version "1.10.0"
resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-1.10.0.tgz#2413eb190ce69fc1a382f58524a3f82306668024"
integrity sha512-/ibWdcOzDGiq/GM1JU2eX8fH1bvAhl66hfe8yEgLEzg9txgr6qb5sQ/DEz5PcDL75tF5H5sCRRwn8Eu8ezi9mw==
dependencies:
muggle-string "^0.3.1"
"@volar/typescript@~1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-1.8.0.tgz#ef231b9b54b192c1e50b9c2cf0142a975dd81651"
integrity sha512-T/U1XLLhXv6tNr40Awznfc6QZWizSL99t6M0DeXtIMbnvSCqjjCVRnwlsq+DK9C1RlO3k8+i0Z8iJn7O1GGtoA==
"@volar/typescript@~1.10.0":
version "1.10.0"
resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-1.10.0.tgz#3b16cf7c4c1802eac023ba4e57fe52bdb6d3016f"
integrity sha512-OtqGtFbUKYC0pLNIk3mHQp5xWnvL1CJIUc9VE39VdZ/oqpoBh5jKfb9uJ45Y4/oP/WYTrif/Uxl1k8VTPz66Gg==
dependencies:
"@volar/language-core" "1.8.0"
"@volar/language-core" "1.10.0"
"@vue/compiler-core@3.2.47":
version "3.2.47"
@ -628,13 +633,13 @@
"@typescript-eslint/parser" "^5.59.1"
vue-eslint-parser "^9.1.1"
"@vue/language-core@1.8.4":
version "1.8.4"
resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-1.8.4.tgz#9b34095987baaf3f2482ba5b34911d248d8ffcb4"
integrity sha512-pnNtNcJVfkGYluW0vsVO+Y1gyX+eA0voaS7+1JOhCp5zKeCaL/PAmGYOgfvwML62neL+2H8pnhY7sffmrGpEhw==
"@vue/language-core@1.8.8":
version "1.8.8"
resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-1.8.8.tgz#5a8aa8363f4dfacdfcd7808a9926744d7c310ae6"
integrity sha512-i4KMTuPazf48yMdYoebTkgSOJdFraE4pQf0B+FTOFkbB+6hAfjrSou/UmYWRsWyZV6r4Rc6DDZdI39CJwL0rWw==
dependencies:
"@volar/language-core" "~1.8.0"
"@volar/source-map" "~1.8.0"
"@volar/language-core" "~1.10.0"
"@volar/source-map" "~1.10.0"
"@vue/compiler-dom" "^3.3.0"
"@vue/reactivity" "^3.3.0"
"@vue/shared" "^3.3.0"
@ -723,13 +728,13 @@
resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.4.0.tgz#f01e2f6089b5098136fb084a0dd0cdd4533b72b0"
integrity sha512-CPuIReonid9+zOG/CGTT05FXrPYATEqoDGNrEaqS4hwcw5BUNM2FguC0mOwJD4Jr16UpRVl9N0pY3P+srIbqmg==
"@vue/typescript@1.8.4":
version "1.8.4"
resolved "https://registry.yarnpkg.com/@vue/typescript/-/typescript-1.8.4.tgz#01a976f731acaf9fd3166fa9374a44da0bca05dc"
integrity sha512-sioQfIY5xcmEAz+cPLvv6CtzGPtGhIdR0Za87zB8M4mPe4OSsE3MBGkXcslf+EzQgF+fm6Gr1SRMSX8r5ZmzDA==
"@vue/typescript@1.8.8":
version "1.8.8"
resolved "https://registry.yarnpkg.com/@vue/typescript/-/typescript-1.8.8.tgz#8efb375d448862134492a044f4e96afada547500"
integrity sha512-jUnmMB6egu5wl342eaUH236v8tdcEPXXkPgj+eI/F6JwW/lb+yAU6U07ZbQ3MVabZRlupIlPESB7ajgAGixhow==
dependencies:
"@volar/typescript" "~1.8.0"
"@vue/language-core" "1.8.4"
"@volar/typescript" "~1.10.0"
"@vue/language-core" "1.8.8"
acorn-jsx@^5.2.0, acorn-jsx@^5.3.2:
version "5.3.2"
@ -756,7 +761,7 @@ acorn@^8.9.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
ajv@^6.10.0, ajv@^6.12.4:
ajv@^6.12.4:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@ -823,10 +828,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:
version "5.3.0"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.0.tgz#0718a7cc29040ee8dbf1bd652b896f3436a87c29"
integrity sha512-UnBV3E3v4STVNQdms6jSGO2CvOkjUMdDAVR2V5N4uCMdaIkaQjbcEAMqRimDHIs4uqBYzDAKCQwCB+97tJgHQw==
bootstrap@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.1.tgz#8ca07040ad15d7f75891d1504cf14c5dedfb1cfe"
integrity sha512-jzwza3Yagduci2x0rr9MeFSORjcHpt0lRZukZPZQJT1Dth5qzV7XcgGqYzi39KGAVYR8QEDVoO0ubFKOxzMG+g==
brace-expansion@^1.1.7:
version "1.1.11"
@ -1099,17 +1104,17 @@ escodegen@^2.0.0:
optionalDependencies:
source-map "~0.6.1"
eslint-plugin-vue@^9.15.1:
version "9.15.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.15.1.tgz#3c09e0edab444b5d4d9239a12a645a0e2e2ea5be"
integrity sha512-CJE/oZOslvmAR9hf8SClTdQ9JLweghT6JCBQNrT2Iel1uVw0W0OLJxzvPd6CxmABKCvLrtyDnqGV37O7KQv6+A==
eslint-plugin-vue@^9.16.1:
version "9.16.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.16.1.tgz#3508d9279d797b40889db76da2fd26524e9144e6"
integrity sha512-2FtnTqazA6aYONfDuOZTk0QzwhAwi7Z4+uJ7+GHeGxcKapjqWlDsRWDenvyG/utyOfAS5bVRmAG3cEWiYEz2bA==
dependencies:
"@eslint-community/eslint-utils" "^4.3.0"
"@eslint-community/eslint-utils" "^4.4.0"
natural-compare "^1.4.0"
nth-check "^2.0.1"
postcss-selector-parser "^6.0.9"
semver "^7.3.5"
vue-eslint-parser "^9.3.0"
nth-check "^2.1.1"
postcss-selector-parser "^6.0.13"
semver "^7.5.4"
vue-eslint-parser "^9.3.1"
xml-name-validator "^4.0.0"
eslint-scope@^5.1.1:
@ -1128,10 +1133,10 @@ eslint-scope@^7.1.1:
esrecurse "^4.3.0"
estraverse "^5.2.0"
eslint-scope@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b"
integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==
eslint-scope@^7.2.2:
version "7.2.2"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f"
integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==
dependencies:
esrecurse "^4.3.0"
estraverse "^5.2.0"
@ -1158,27 +1163,32 @@ eslint-visitor-keys@^3.4.1:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994"
integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
eslint@^8.44.0:
version "8.44.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.44.0.tgz#51246e3889b259bbcd1d7d736a0c10add4f0e500"
integrity sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==
eslint-visitor-keys@^3.4.2:
version "3.4.2"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz#8c2095440eca8c933bedcadf16fefa44dbe9ba5f"
integrity sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==
eslint@^8.46.0:
version "8.46.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.46.0.tgz#a06a0ff6974e53e643acc42d1dcf2e7f797b3552"
integrity sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.4.0"
"@eslint/eslintrc" "^2.1.0"
"@eslint/js" "8.44.0"
"@eslint-community/regexpp" "^4.6.1"
"@eslint/eslintrc" "^2.1.1"
"@eslint/js" "^8.46.0"
"@humanwhocodes/config-array" "^0.11.10"
"@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8"
ajv "^6.10.0"
ajv "^6.12.4"
chalk "^4.0.0"
cross-spawn "^7.0.2"
debug "^4.3.2"
doctrine "^3.0.0"
escape-string-regexp "^4.0.0"
eslint-scope "^7.2.0"
eslint-visitor-keys "^3.4.1"
espree "^9.6.0"
eslint-scope "^7.2.2"
eslint-visitor-keys "^3.4.2"
espree "^9.6.1"
esquery "^1.4.2"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
@ -1188,7 +1198,6 @@ eslint@^8.44.0:
globals "^13.19.0"
graphemer "^1.4.0"
ignore "^5.2.0"
import-fresh "^3.0.0"
imurmurhash "^0.1.4"
is-glob "^4.0.0"
is-path-inside "^3.0.3"
@ -1200,7 +1209,6 @@ eslint@^8.44.0:
natural-compare "^1.4.0"
optionator "^0.9.3"
strip-ansi "^6.0.1"
strip-json-comments "^3.1.0"
text-table "^0.2.0"
espree@^6.0.0:
@ -1230,6 +1238,15 @@ espree@^9.6.0:
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.4.1"
espree@^9.6.1:
version "9.6.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
dependencies:
acorn "^8.9.0"
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.4.1"
esprima@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
@ -1532,7 +1549,7 @@ immutable@^4.0.0:
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef"
integrity sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==
import-fresh@^3.0.0, import-fresh@^3.2.1:
import-fresh@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
@ -1916,7 +1933,7 @@ npm-run-all@^4.1.5:
shell-quote "^1.6.1"
string.prototype.padend "^3.0.0"
nth-check@^2.0.1:
nth-check@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==
@ -2074,10 +2091,10 @@ pkg-types@^1.0.3:
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"
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
postcss-selector-parser@^6.0.13:
version "6.0.13"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b"
integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
@ -2091,10 +2108,10 @@ postcss@^8.1.10:
picocolors "^1.0.0"
source-map-js "^1.0.2"
postcss@^8.4.25:
version "8.4.25"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.25.tgz#4a133f5e379eda7f61e906c3b1aaa9b81292726f"
integrity sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==
postcss@^8.4.27:
version "8.4.27"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.27.tgz#234d7e4b72e34ba5a92c29636734349e0d9c3057"
integrity sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"
@ -2171,10 +2188,10 @@ rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
rollup@^3.25.2:
version "3.26.2"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.26.2.tgz#2e76a37606cb523fc9fef43e6f59c93f86d95e7c"
integrity sha512-6umBIGVz93er97pMgQO08LuH3m6PUb3jlDUUGFsNJB6VgTCUaDFpupf5JfU30529m/UKOgmiX+uY6Sx8cOYpLA==
rollup@^3.27.1:
version "3.27.2"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.27.2.tgz#59adc973504408289be89e5978e938ce852c9520"
integrity sha512-YGwmHf7h2oUHkVBT248x0yt6vZkYQ3/rvE5iQuVBh3WO8GcJ6BNeOkpoX1yMHIiBm18EMLjBPIoUDkhgnyxGOQ==
optionalDependencies:
fsevents "~2.3.2"
@ -2194,10 +2211,10 @@ safe-regex-test@^1.0.0:
get-intrinsic "^1.1.3"
is-regex "^1.1.4"
sass@^1.63.6:
version "1.63.6"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.63.6.tgz#481610e612902e0c31c46b46cf2dad66943283ea"
integrity sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==
sass@^1.64.2:
version "1.64.2"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.64.2.tgz#0d9805ad6acf31c59c3acc725fcfb91b7fcc6909"
integrity sha512-TnDlfc+CRnUAgLO9D8cQLFu/GIjJIzJCGkE7o4ekIGQOH7T3GetiRR/PsTWJUHhkzcSPrARkPI+gNWn5alCzDg==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
@ -2213,7 +2230,7 @@ semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.3.5, semver@^7.3.6, semver@^7.3.7:
semver@^7.3.6, semver@^7.3.7:
version "7.3.7"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
@ -2227,6 +2244,13 @@ semver@^7.3.8:
dependencies:
lru-cache "^6.0.0"
semver@^7.5.4:
version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
dependencies:
lru-cache "^6.0.0"
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@ -2368,7 +2392,7 @@ strip-bom@^3.0.0:
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==
strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
strip-json-comments@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
@ -2392,10 +2416,10 @@ 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.19.0:
version "5.19.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.0.tgz#7b3137b01226bdd179978207b9c8148754a6da9c"
integrity sha512-JpcpGOQLOXm2jsomozdMDpd5f8ZHh1rR48OFgWUH3QsyZcfPgv2qDCYbcDEAYNd4OZRj2bWYKpwdll/udZCk/Q==
terser@^5.19.2:
version "5.19.2"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.2.tgz#bdb8017a9a4a8de4663a7983f45c506534f9234e"
integrity sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==
dependencies:
"@jridgewell/source-map" "^0.3.3"
acorn "^8.8.2"
@ -2509,19 +2533,19 @@ vite-plugin-compression@^0.5.1:
debug "^4.3.3"
fs-extra "^10.0.0"
vite-plugin-css-injected-by-js@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.2.0.tgz#0e0fa1de9281a918f9945bc6401b46f7d396d7f9"
integrity sha512-a6MtYm/qfswVCtLezzzKJWL2ZpxrXXQd6/1UHy0t/G0IILHl4GG6n4OprbcC93aXIwkeS19JNbuKWD0Xt+pZtg==
vite-plugin-css-injected-by-js@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.3.0.tgz#c19480a9e42a95c5bced976a9dde1446f9bd91ff"
integrity sha512-xG+jyHNCmUqi/TXp6q88wTJGeAOrNLSyUUTp4qEQ9QZLGcHWQQsCsSSKa59rPMQr8sOzfzmWDd8enGqfH/dBew==
vite@^4.4.3:
version "4.4.3"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.4.3.tgz#dfaf86f4cba3058bf2724e2e2c88254fb0f21a5a"
integrity sha512-IMnXQXXWgLi5brBQx/4WzDxdzW0X3pjO4nqFJAuNvwKtxzAmPzFE1wszW3VDpAGQJm3RZkm/brzRdyGsnwgJIA==
vite@^4.4.9:
version "4.4.9"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.4.9.tgz#1402423f1a2f8d66fd8d15e351127c7236d29d3d"
integrity sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==
dependencies:
esbuild "^0.18.10"
postcss "^8.4.25"
rollup "^3.25.2"
postcss "^8.4.27"
rollup "^3.27.1"
optionalDependencies:
fsevents "~2.3.2"
@ -2538,10 +2562,10 @@ vue-eslint-parser@^9.1.1:
lodash "^4.17.21"
semver "^7.3.6"
vue-eslint-parser@^9.3.0:
version "9.3.0"
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.3.0.tgz#775a974a0603c9a73d85fed8958ed9e814a4a816"
integrity sha512-48IxT9d0+wArT1+3wNIy0tascRoywqSUe2E1YalIC1L8jsUGe5aJQItWfRok7DVFGz3UYvzEI7n5wiTXsCMAcQ==
vue-eslint-parser@^9.3.1:
version "9.3.1"
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.3.1.tgz#429955e041ae5371df5f9e37ebc29ba046496182"
integrity sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==
dependencies:
debug "^4.3.4"
eslint-scope "^7.1.1"
@ -2576,13 +2600,13 @@ vue-template-compiler@^2.7.14:
de-indent "^1.0.2"
he "^1.2.0"
vue-tsc@^1.8.4:
version "1.8.4"
resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.8.4.tgz#8087d7b34e7f0ead5896b83783c32c1a99d850fa"
integrity sha512-+hgpOhIx11vbi8/AxEdaPj3fiRwN9wy78LpsNNw2V995/IWa6TMyQxHbaw2ZKUpdwjySSHgrT6ohDEhUgFxGYw==
vue-tsc@^1.8.8:
version "1.8.8"
resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.8.8.tgz#67317693eb2ef6747e89e6d834eeb6d2deb8871d"
integrity sha512-bSydNFQsF7AMvwWsRXD7cBIXaNs/KSjvzWLymq/UtKE36697sboX4EccSHFVxvgdBlI1frYPc/VMKJNB7DFeDQ==
dependencies:
"@vue/language-core" "1.8.4"
"@vue/typescript" "1.8.4"
"@vue/language-core" "1.8.8"
"@vue/typescript" "1.8.8"
semver "^7.3.8"
vue@^3.3.4: