revert Revert "Merge remote-tracking branch 'tbnobody/OpenDTU/master' into development"

merge of v23.9.11 broke the system. As a workaround upgrade espressif32 from 6.3.2 to 6.4.0 is skipped. See #440
This commit is contained in:
helgeerbe 2023-09-14 13:45:23 +02:00
parent 24018a1432
commit f7bd4a40d8
71 changed files with 1073 additions and 522 deletions

View File

@ -95,6 +95,4 @@ class JkBmsBatteryStats : public BatteryStats {
private: private:
JkBms::DataPointContainer _dataPoints; JkBms::DataPointContainer _dataPoints;
mutable uint32_t _lastMqttPublish = 0;
mutable uint32_t _lastFullMqttPublish = 0;
}; };

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include <Arduino.h> #include <cstdint>
#define CONFIG_FILENAME "/config.json" #define CONFIG_FILENAME "/config.json"
#define CONFIG_VERSION 0x00011900 // 0.1.24 // make sure to clean all after change #define CONFIG_VERSION 0x00011900 // 0.1.24 // make sure to clean all after change
@ -54,6 +54,9 @@ struct INVERTER_CONFIG_T {
bool Poll_Enable_Night; bool Poll_Enable_Night;
bool Command_Enable; bool Command_Enable;
bool Command_Enable_Night; bool Command_Enable_Night;
uint8_t ReachableThreshold;
bool ZeroRuntimeDataIfUnrechable;
bool ZeroYieldDayOnMidnight;
CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT]; CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT];
}; };
@ -72,18 +75,18 @@ struct POWERMETER_HTTP_PHASE_CONFIG_T {
struct CONFIG_T { struct CONFIG_T {
uint32_t Cfg_Version; uint32_t Cfg_Version;
uint Cfg_SaveCount; uint32_t Cfg_SaveCount;
char WiFi_Ssid[WIFI_MAX_SSID_STRLEN + 1]; char WiFi_Ssid[WIFI_MAX_SSID_STRLEN + 1];
char WiFi_Password[WIFI_MAX_PASSWORD_STRLEN + 1]; char WiFi_Password[WIFI_MAX_PASSWORD_STRLEN + 1];
byte WiFi_Ip[4]; uint8_t WiFi_Ip[4];
byte WiFi_Netmask[4]; uint8_t WiFi_Netmask[4];
byte WiFi_Gateway[4]; uint8_t WiFi_Gateway[4];
byte WiFi_Dns1[4]; uint8_t WiFi_Dns1[4];
byte WiFi_Dns2[4]; uint8_t WiFi_Dns2[4];
bool WiFi_Dhcp; bool WiFi_Dhcp;
char WiFi_Hostname[WIFI_MAX_HOSTNAME_STRLEN + 1]; char WiFi_Hostname[WIFI_MAX_HOSTNAME_STRLEN + 1];
uint WiFi_ApTimeout; uint32_t WiFi_ApTimeout;
char Ntp_Server[NTP_MAX_SERVER_STRLEN + 1]; char Ntp_Server[NTP_MAX_SERVER_STRLEN + 1];
char Ntp_Timezone[NTP_MAX_TIMEZONE_STRLEN + 1]; char Ntp_Timezone[NTP_MAX_TIMEZONE_STRLEN + 1];
@ -95,7 +98,7 @@ struct CONFIG_T {
bool Mqtt_Enabled; bool Mqtt_Enabled;
char Mqtt_Hostname[MQTT_MAX_HOSTNAME_STRLEN + 1]; char Mqtt_Hostname[MQTT_MAX_HOSTNAME_STRLEN + 1];
bool Mqtt_VerboseLogging; bool Mqtt_VerboseLogging;
uint Mqtt_Port; uint32_t Mqtt_Port;
char Mqtt_Username[MQTT_MAX_USERNAME_STRLEN + 1]; char Mqtt_Username[MQTT_MAX_USERNAME_STRLEN + 1];
char Mqtt_Password[MQTT_MAX_PASSWORD_STRLEN + 1]; char Mqtt_Password[MQTT_MAX_PASSWORD_STRLEN + 1];
char Mqtt_Topic[MQTT_MAX_TOPIC_STRLEN + 1]; char Mqtt_Topic[MQTT_MAX_TOPIC_STRLEN + 1];
@ -104,6 +107,7 @@ struct CONFIG_T {
char Mqtt_LwtValue_Online[MQTT_MAX_LWTVALUE_STRLEN + 1]; char Mqtt_LwtValue_Online[MQTT_MAX_LWTVALUE_STRLEN + 1];
char Mqtt_LwtValue_Offline[MQTT_MAX_LWTVALUE_STRLEN + 1]; char Mqtt_LwtValue_Offline[MQTT_MAX_LWTVALUE_STRLEN + 1];
uint32_t Mqtt_PublishInterval; uint32_t Mqtt_PublishInterval;
bool Mqtt_CleanSession;
INVERTER_CONFIG_T Inverter[INV_MAX_COUNT]; INVERTER_CONFIG_T Inverter[INV_MAX_COUNT];

View File

@ -31,16 +31,16 @@ public:
float getTotalDcIrradiation(); float getTotalDcIrradiation();
// Amount of relevant digits for yield total // Amount of relevant digits for yield total
unsigned int getTotalAcYieldTotalDigits(); uint32_t getTotalAcYieldTotalDigits();
// Amount of relevant digits for yield total // Amount of relevant digits for yield total
unsigned int getTotalAcYieldDayDigits(); uint32_t getTotalAcYieldDayDigits();
// Amount of relevant digits for AC power // Amount of relevant digits for AC power
unsigned int getTotalAcPowerDigits(); uint32_t getTotalAcPowerDigits();
// Amount of relevant digits for DC power // Amount of relevant digits for DC power
unsigned int getTotalDcPowerDigits(); uint32_t getTotalDcPowerDigits();
// True, if at least one inverter is reachable // True, if at least one inverter is reachable
bool getIsAtLeastOneReachable(); bool getIsAtLeastOneReachable();
@ -68,10 +68,10 @@ private:
float _totalDcPowerIrradiation = 0; float _totalDcPowerIrradiation = 0;
float _totalDcIrradiationInstalled = 0; float _totalDcIrradiationInstalled = 0;
float _totalDcIrradiation = 0; float _totalDcIrradiation = 0;
unsigned int _totalAcYieldTotalDigits = 0; uint32_t _totalAcYieldTotalDigits = 0;
unsigned int _totalAcYieldDayDigits = 0; uint32_t _totalAcYieldDayDigits = 0;
unsigned int _totalAcPowerDigits = 0; uint32_t _totalAcPowerDigits = 0;
unsigned int _totalDcPowerDigits = 0; uint32_t _totalDcPowerDigits = 0;
bool _isAtLeastOneReachable = false; bool _isAtLeastOneReachable = false;
bool _isAtLeastOneProducing = false; bool _isAtLeastOneProducing = false;
bool _isAllEnabledProducing = false; bool _isAllEnabledProducing = false;

View File

@ -185,10 +185,6 @@ class DataPoint {
std::string const& getUnitText() const { return _strUnit; } std::string const& getUnitText() const { return _strUnit; }
uint32_t getTimestamp() const { return _timestamp; } uint32_t getTimestamp() const { return _timestamp; }
bool operator==(DataPoint const& other) const {
return _value == other._value;
}
private: private:
std::string _strLabel; std::string _strLabel;
std::string _strValue; std::string _strValue;

View File

@ -3,6 +3,7 @@
#include "Configuration.h" #include "Configuration.h"
#include <Hoymiles.h> #include <Hoymiles.h>
#include <TimeoutHelper.h>
#include <espMqttClient.h> #include <espMqttClient.h>
class MqttHandleInverterClass { class MqttHandleInverterClass {
@ -19,6 +20,8 @@ private:
uint32_t _lastPublishStats[INV_MAX_COUNT]; uint32_t _lastPublishStats[INV_MAX_COUNT];
uint32_t _lastPublish; uint32_t _lastPublish;
TimeoutHelper _statsTimeout;
FieldId_t _publishFields[14] = { FieldId_t _publishFields[14] = {
FLD_UDC, FLD_UDC,
FLD_IDC, FLD_IDC,

View File

@ -63,10 +63,10 @@ private:
void NetworkEvent(WiFiEvent_t event); void NetworkEvent(WiFiEvent_t event);
bool adminEnabled = true; bool adminEnabled = true;
bool forceDisconnection = false; bool forceDisconnection = false;
int adminTimeoutCounter = 0; uint32_t adminTimeoutCounter = 0;
int adminTimeoutCounterMax = 0; uint32_t adminTimeoutCounterMax = 0;
int connectTimeoutTimer = 0; uint32_t connectTimeoutTimer = 0;
int connectRedoTimer = 0; uint32_t connectRedoTimer = 0;
uint32_t lastTimerCall = 0; uint32_t lastTimerCall = 0;
const byte DNS_PORT = 53; const byte DNS_PORT = 53;
IPAddress apIp; IPAddress apIp;

View File

@ -22,8 +22,8 @@ private:
SunSet _sun; SunSet _sun;
bool _isDayPeriod = true; bool _isDayPeriod = true;
bool _isSunsetAvailable = true; bool _isSunsetAvailable = true;
uint _sunriseMinutes = 0; uint32_t _sunriseMinutes = 0;
uint _sunsetMinutes = 0; uint32_t _sunsetMinutes = 0;
uint32_t _lastUpdate = 0; uint32_t _lastUpdate = 0;
bool _isValidInfo = false; bool _isValidInfo = false;

View File

@ -8,6 +8,7 @@
#include "WebApi_dtu.h" #include "WebApi_dtu.h"
#include "WebApi_eventlog.h" #include "WebApi_eventlog.h"
#include "WebApi_firmware.h" #include "WebApi_firmware.h"
#include "WebApi_gridprofile.h"
#include "WebApi_inverter.h" #include "WebApi_inverter.h"
#include "WebApi_limit.h" #include "WebApi_limit.h"
#include "WebApi_maintenance.h" #include "WebApi_maintenance.h"
@ -52,6 +53,7 @@ private:
WebApiDtuClass _webApiDtu; WebApiDtuClass _webApiDtu;
WebApiEventlogClass _webApiEventlog; WebApiEventlogClass _webApiEventlog;
WebApiFirmwareClass _webApiFirmware; WebApiFirmwareClass _webApiFirmware;
WebApiGridProfileClass _webApiGridprofile;
WebApiInverterClass _webApiInverter; WebApiInverterClass _webApiInverter;
WebApiLimitClass _webApiLimit; WebApiLimitClass _webApiLimit;
WebApiMaintenanceClass _webApiMaintenance; WebApiMaintenanceClass _webApiMaintenance;

View File

@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <ESPAsyncWebServer.h>
class WebApiGridProfileClass {
public:
void init(AsyncWebServer* server);
void loop();
private:
void onGridProfileStatus(AsyncWebServerRequest* request);
AsyncWebServer* _server;
};

View File

@ -25,4 +25,6 @@ private:
uint32_t _lastInvUpdateCheck = 0; uint32_t _lastInvUpdateCheck = 0;
uint32_t _lastWsCleanup = 0; uint32_t _lastWsCleanup = 0;
uint32_t _newestInverterTimestamp = 0; uint32_t _newestInverterTimestamp = 0;
std::mutex _mutex;
}; };

View File

@ -74,6 +74,7 @@
#define MQTT_LWT_ONLINE "online" #define MQTT_LWT_ONLINE "online"
#define MQTT_LWT_OFFLINE "offline" #define MQTT_LWT_OFFLINE "offline"
#define MQTT_PUBLISH_INTERVAL 5U #define MQTT_PUBLISH_INTERVAL 5U
#define MQTT_CLEAN_SESSION true
#define DTU_SERIAL 0x99978563412U #define DTU_SERIAL 0x99978563412U
#define DTU_POLL_INTERVAL 5U #define DTU_POLL_INTERVAL 5U
@ -95,6 +96,8 @@
#define DISPLAY_CONTRAST 60U #define DISPLAY_CONTRAST 60U
#define DISPLAY_LANGUAGE 0U #define DISPLAY_LANGUAGE 0U
#define REACHABLE_THRESHOLD 2U
#define VEDIRECT_ENABLED false #define VEDIRECT_ENABLED false
#define VEDIRECT_VERBOSE_LOGGING false #define VEDIRECT_VERBOSE_LOGGING false
#define VEDIRECT_UPDATESONLY true #define VEDIRECT_UPDATESONLY true

View File

@ -3,6 +3,7 @@
* Copyright (C) 2022 Thomas Basler and others * Copyright (C) 2022 Thomas Basler and others
*/ */
#include "Hoymiles.h" #include "Hoymiles.h"
#include "Utils.h"
#include "inverters/HMS_1CH.h" #include "inverters/HMS_1CH.h"
#include "inverters/HMS_2CH.h" #include "inverters/HMS_2CH.h"
#include "inverters/HMS_4CH.h" #include "inverters/HMS_4CH.h"
@ -12,18 +13,10 @@
#include "inverters/HM_4CH.h" #include "inverters/HM_4CH.h"
#include <Arduino.h> #include <Arduino.h>
#define HOY_SEMAPHORE_TAKE() \
do { \
} while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS)
#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore)
HoymilesClass Hoymiles; HoymilesClass Hoymiles;
void HoymilesClass::init() void HoymilesClass::init()
{ {
_xSemaphore = xSemaphoreCreateMutex();
HOY_SEMAPHORE_GIVE(); // release before first use
_pollInterval = 0; _pollInterval = 0;
_radioNrf.reset(new HoymilesRadio_NRF()); _radioNrf.reset(new HoymilesRadio_NRF());
_radioCmt.reset(new HoymilesRadio_CMT()); _radioCmt.reset(new HoymilesRadio_CMT());
@ -41,7 +34,7 @@ void HoymilesClass::initCMT(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8
void HoymilesClass::loop() void HoymilesClass::loop()
{ {
HOY_SEMAPHORE_TAKE(); std::lock_guard<std::mutex> lock(_mutex);
_radioNrf->loop(); _radioNrf->loop();
_radioCmt->loop(); _radioCmt->loop();
@ -57,6 +50,8 @@ void HoymilesClass::loop()
} }
if (iv != nullptr && iv->getRadio()->isInitialized() && iv->getRadio()->isQueueEmpty()) { if (iv != nullptr && iv->getRadio()->isInitialized() && iv->getRadio()->isQueueEmpty()) {
if (iv->getEnablePolling() || iv->getEnableCommands()) {
_messageOutput->print("Fetch inverter: "); _messageOutput->print("Fetch inverter: ");
_messageOutput->println(iv->serial(), HEX); _messageOutput->println(iv->serial(), HEX);
@ -71,8 +66,7 @@ void HoymilesClass::loop()
iv->sendAlarmLogRequest(force); iv->sendAlarmLogRequest(force);
// Fetch limit // Fetch limit
if ((iv->SystemConfigPara()->getLastLimitRequestSuccess() == CMD_NOK) if (((millis() - iv->SystemConfigPara()->getLastUpdateRequest() > HOY_SYSTEM_CONFIG_PARA_POLL_INTERVAL)
|| ((millis() - iv->SystemConfigPara()->getLastUpdateRequest() > HOY_SYSTEM_CONFIG_PARA_POLL_INTERVAL)
&& (millis() - iv->SystemConfigPara()->getLastUpdateCommand() > HOY_SYSTEM_CONFIG_PARA_POLL_MIN_DURATION))) { && (millis() - iv->SystemConfigPara()->getLastUpdateCommand() > HOY_SYSTEM_CONFIG_PARA_POLL_MIN_DURATION))) {
_messageOutput->println("Request SystemConfigPara"); _messageOutput->println("Request SystemConfigPara");
iv->sendSystemConfigParaRequest(); iv->sendSystemConfigParaRequest();
@ -108,16 +102,38 @@ void HoymilesClass::loop()
} }
} }
if (++inverterPos >= getNumInverters()) { // Fetch grid profile
inverterPos = 0; if (iv->Statistics()->getLastUpdate() > 0 && iv->GridProfile()->getLastUpdate() == 0) {
iv->sendGridOnProFileParaRequest();
} }
_lastPoll = millis(); _lastPoll = millis();
} }
if (++inverterPos >= getNumInverters()) {
inverterPos = 0;
} }
} }
HOY_SEMAPHORE_GIVE(); // Perform housekeeping of all inverters on day change
int8_t currentWeekDay = Utils::getWeekDay();
static int8_t lastWeekDay = -1;
if (lastWeekDay == -1) {
lastWeekDay = currentWeekDay;
} else {
if (currentWeekDay != lastWeekDay) {
for (auto& inv : _inverters) {
if (inv->getZeroYieldDayOnMidnight()) {
inv->Statistics()->zeroDailyData();
}
}
lastWeekDay = currentWeekDay;
}
}
}
}
} }
std::shared_ptr<InverterAbstract> HoymilesClass::addInverter(const char* name, uint64_t serial) std::shared_ptr<InverterAbstract> HoymilesClass::addInverter(const char* name, uint64_t serial)
@ -195,9 +211,8 @@ void HoymilesClass::removeInverterBySerial(uint64_t serial)
{ {
for (uint8_t i = 0; i < _inverters.size(); i++) { for (uint8_t i = 0; i < _inverters.size(); i++) {
if (_inverters[i]->serial() == serial) { if (_inverters[i]->serial() == serial) {
HOY_SEMAPHORE_TAKE(); std::lock_guard<std::mutex> lock(_mutex);
_inverters.erase(_inverters.begin() + i); _inverters.erase(_inverters.begin() + i);
HOY_SEMAPHORE_GIVE();
return; return;
} }
} }

View File

@ -45,7 +45,7 @@ private:
std::unique_ptr<HoymilesRadio_NRF> _radioNrf; std::unique_ptr<HoymilesRadio_NRF> _radioNrf;
std::unique_ptr<HoymilesRadio_CMT> _radioCmt; std::unique_ptr<HoymilesRadio_CMT> _radioCmt;
SemaphoreHandle_t _xSemaphore; std::mutex _mutex;
uint32_t _pollInterval = 0; uint32_t _pollInterval = 0;
bool _verboseLogging = true; bool _verboseLogging = true;

View File

@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
*/
#include "Utils.h"
#include <time.h>
uint8_t Utils::getWeekDay()
{
time_t raw;
struct tm info;
time(&raw);
localtime_r(&raw, &info);
return info.tm_mday;
}

9
lib/Hoymiles/src/Utils.h Normal file
View File

@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstdint>
class Utils {
public:
static uint8_t getWeekDay();
};

View File

@ -0,0 +1,40 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
*/
#include "GridOnProFilePara.h"
#include "Hoymiles.h"
#include "inverters/InverterAbstract.h"
GridOnProFilePara::GridOnProFilePara(uint64_t target_address, uint64_t router_address, time_t time)
: MultiDataCommand(target_address, router_address)
{
setTime(time);
setDataType(0x02);
setTimeout(500);
}
String GridOnProFilePara::getCommandName()
{
return "GridOnProFilePara";
}
bool GridOnProFilePara::handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id)
{
// Check CRC of whole payload
if (!MultiDataCommand::handleResponse(inverter, fragment, max_fragment_id)) {
return false;
}
// Move all fragments into target buffer
uint8_t offs = 0;
inverter->GridProfile()->beginAppendFragment();
inverter->GridProfile()->clearBuffer();
for (uint8_t i = 0; i < max_fragment_id; i++) {
inverter->GridProfile()->appendFragment(offs, fragment[i].fragment, fragment[i].len);
offs += (fragment[i].len);
}
inverter->GridProfile()->endAppendFragment();
inverter->GridProfile()->setLastUpdate(millis());
return true;
}

View File

@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "MultiDataCommand.h"
class GridOnProFilePara : public MultiDataCommand {
public:
explicit GridOnProFilePara(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0);
virtual String getCommandName();
virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id);
};

View File

@ -8,6 +8,7 @@
* AlarmDataCommand * AlarmDataCommand
* DevInfoAllCommand * DevInfoAllCommand
* DevInfoSimpleCommand * DevInfoSimpleCommand
* GridOnProFilePara
* RealTimeRunDataCommand * RealTimeRunDataCommand
* SystemConfigParaCommand * SystemConfigParaCommand
* ParaSetCommand * ParaSetCommand

View File

@ -55,4 +55,9 @@ bool RealTimeRunDataCommand::handleResponse(InverterAbstract* inverter, fragment
void RealTimeRunDataCommand::gotTimeout(InverterAbstract* inverter) void RealTimeRunDataCommand::gotTimeout(InverterAbstract* inverter)
{ {
inverter->Statistics()->incrementRxFailureCount(); inverter->Statistics()->incrementRxFailureCount();
if (inverter->getZeroValuesIfUnreachable() && !inverter->isReachable()) {
Hoymiles.getMessageOutput()->println("Set runtime data to zero");
inverter->Statistics()->zeroRuntimeData();
}
} }

View File

@ -3,6 +3,7 @@
* Copyright (C) 2022 Thomas Basler and others * Copyright (C) 2022 Thomas Basler and others
*/ */
#include "SystemConfigParaCommand.h" #include "SystemConfigParaCommand.h"
#include "Hoymiles.h"
#include "inverters/InverterAbstract.h" #include "inverters/InverterAbstract.h"
SystemConfigParaCommand::SystemConfigParaCommand(uint64_t target_address, uint64_t router_address, time_t time) SystemConfigParaCommand::SystemConfigParaCommand(uint64_t target_address, uint64_t router_address, time_t time)
@ -25,6 +26,18 @@ bool SystemConfigParaCommand::handleResponse(InverterAbstract* inverter, fragmen
return false; return false;
} }
// Check if at least all required bytes are received
// In case of low power in the inverter it occours that some incomplete fragments
// with a valid CRC are received.
uint8_t fragmentsSize = getTotalFragmentSize(fragment, max_fragment_id);
uint8_t expectedSize = inverter->SystemConfigPara()->getExpectedByteCount();
if (fragmentsSize < expectedSize) {
Hoymiles.getMessageOutput()->printf("ERROR in %s: Received fragment size: %d, min expected size: %d\r\n",
getCommandName().c_str(), fragmentsSize, expectedSize);
return false;
}
// Move all fragments into target buffer // Move all fragments into target buffer
uint8_t offs = 0; uint8_t offs = 0;
inverter->SystemConfigPara()->beginAppendFragment(); inverter->SystemConfigPara()->beginAppendFragment();

View File

@ -8,6 +8,7 @@
#include "commands/AlarmDataCommand.h" #include "commands/AlarmDataCommand.h"
#include "commands/DevInfoAllCommand.h" #include "commands/DevInfoAllCommand.h"
#include "commands/DevInfoSimpleCommand.h" #include "commands/DevInfoSimpleCommand.h"
#include "commands/GridOnProFilePara.h"
#include "commands/PowerControlCommand.h" #include "commands/PowerControlCommand.h"
#include "commands/RealTimeRunDataCommand.h" #include "commands/RealTimeRunDataCommand.h"
#include "commands/SystemConfigParaCommand.h" #include "commands/SystemConfigParaCommand.h"
@ -211,3 +212,25 @@ bool HM_Abstract::resendPowerControlRequest()
break; break;
} }
} }
bool HM_Abstract::sendGridOnProFileParaRequest()
{
if (!getEnablePolling()) {
return false;
}
struct tm timeinfo;
if (!getLocalTime(&timeinfo, 5)) {
return false;
}
time_t now;
time(&now);
auto cmd = _radio->prepareCommand<GridOnProFilePara>();
cmd->setTime(now);
cmd->setTargetAddress(serial());
_radio->enqueCommand(cmd);
return true;
}

View File

@ -15,6 +15,7 @@ public:
bool sendPowerControlRequest(bool turnOn); bool sendPowerControlRequest(bool turnOn);
bool sendRestartControlRequest(); bool sendRestartControlRequest();
bool resendPowerControlRequest(); bool resendPowerControlRequest();
bool sendGridOnProFileParaRequest();
private: private:
uint8_t _lastAlarmLogCnt = 0; uint8_t _lastAlarmLogCnt = 0;

View File

@ -20,6 +20,7 @@ InverterAbstract::InverterAbstract(HoymilesRadio* radio, uint64_t serial)
_alarmLogParser.reset(new AlarmLogParser()); _alarmLogParser.reset(new AlarmLogParser());
_devInfoParser.reset(new DevInfoParser()); _devInfoParser.reset(new DevInfoParser());
_gridProfileParser.reset(new GridProfileParser());
_powerCommandParser.reset(new PowerCommandParser()); _powerCommandParser.reset(new PowerCommandParser());
_statisticsParser.reset(new StatisticsParser()); _statisticsParser.reset(new StatisticsParser());
_systemConfigParaParser.reset(new SystemConfigParaParser()); _systemConfigParaParser.reset(new SystemConfigParaParser());
@ -73,7 +74,7 @@ bool InverterAbstract::isProducing()
bool InverterAbstract::isReachable() bool InverterAbstract::isReachable()
{ {
return _enablePolling && Statistics()->getRxFailureCount() <= MAX_ONLINE_FAILURE_COUNT; return _enablePolling && Statistics()->getRxFailureCount() <= _reachableThreshold;
} }
void InverterAbstract::setEnablePolling(bool enabled) void InverterAbstract::setEnablePolling(bool enabled)
@ -96,6 +97,36 @@ bool InverterAbstract::getEnableCommands()
return _enableCommands; return _enableCommands;
} }
void InverterAbstract::setReachableThreshold(uint8_t threshold)
{
_reachableThreshold = threshold;
}
uint8_t InverterAbstract::getReachableThreshold()
{
return _reachableThreshold;
}
void InverterAbstract::setZeroValuesIfUnreachable(bool enabled)
{
_zeroValuesIfUnreachable = enabled;
}
bool InverterAbstract::getZeroValuesIfUnreachable()
{
return _zeroValuesIfUnreachable;
}
void InverterAbstract::setZeroYieldDayOnMidnight(bool enabled)
{
_zeroYieldDayOnMidnight = enabled;
}
bool InverterAbstract::getZeroYieldDayOnMidnight()
{
return _zeroYieldDayOnMidnight;
}
bool InverterAbstract::sendChangeChannelRequest() bool InverterAbstract::sendChangeChannelRequest()
{ {
return false; return false;
@ -116,6 +147,11 @@ DevInfoParser* InverterAbstract::DevInfo()
return _devInfoParser.get(); return _devInfoParser.get();
} }
GridProfileParser* InverterAbstract::GridProfile()
{
return _gridProfileParser.get();
}
PowerCommandParser* InverterAbstract::PowerCommand() PowerCommandParser* InverterAbstract::PowerCommand()
{ {
return _powerCommandParser.get(); return _powerCommandParser.get();

View File

@ -4,6 +4,7 @@
#include "../commands/ActivePowerControlCommand.h" #include "../commands/ActivePowerControlCommand.h"
#include "../parser/AlarmLogParser.h" #include "../parser/AlarmLogParser.h"
#include "../parser/DevInfoParser.h" #include "../parser/DevInfoParser.h"
#include "../parser/GridProfileParser.h"
#include "../parser/PowerCommandParser.h" #include "../parser/PowerCommandParser.h"
#include "../parser/StatisticsParser.h" #include "../parser/StatisticsParser.h"
#include "../parser/SystemConfigParaParser.h" #include "../parser/SystemConfigParaParser.h"
@ -24,7 +25,6 @@ enum {
}; };
#define MAX_RF_FRAGMENT_COUNT 13 #define MAX_RF_FRAGMENT_COUNT 13
#define MAX_ONLINE_FAILURE_COUNT 2
class CommandAbstract; class CommandAbstract;
@ -49,6 +49,15 @@ public:
void setEnableCommands(bool enabled); void setEnableCommands(bool enabled);
bool getEnableCommands(); bool getEnableCommands();
void setReachableThreshold(uint8_t threshold);
uint8_t getReachableThreshold();
void setZeroValuesIfUnreachable(bool enabled);
bool getZeroValuesIfUnreachable();
void setZeroYieldDayOnMidnight(bool enabled);
bool getZeroYieldDayOnMidnight();
void clearRxFragmentBuffer(); void clearRxFragmentBuffer();
void addRxFragment(uint8_t fragment[], uint8_t len); void addRxFragment(uint8_t fragment[], uint8_t len);
uint8_t verifyAllFragments(CommandAbstract* cmd); uint8_t verifyAllFragments(CommandAbstract* cmd);
@ -63,11 +72,13 @@ public:
virtual bool sendRestartControlRequest() = 0; virtual bool sendRestartControlRequest() = 0;
virtual bool resendPowerControlRequest() = 0; virtual bool resendPowerControlRequest() = 0;
virtual bool sendChangeChannelRequest(); virtual bool sendChangeChannelRequest();
virtual bool sendGridOnProFileParaRequest() = 0;
HoymilesRadio* getRadio(); HoymilesRadio* getRadio();
AlarmLogParser* EventLog(); AlarmLogParser* EventLog();
DevInfoParser* DevInfo(); DevInfoParser* DevInfo();
GridProfileParser* GridProfile();
PowerCommandParser* PowerCommand(); PowerCommandParser* PowerCommand();
StatisticsParser* Statistics(); StatisticsParser* Statistics();
SystemConfigParaParser* SystemConfigPara(); SystemConfigParaParser* SystemConfigPara();
@ -87,8 +98,14 @@ private:
bool _enablePolling = true; bool _enablePolling = true;
bool _enableCommands = true; bool _enableCommands = true;
uint8_t _reachableThreshold = 3;
bool _zeroValuesIfUnreachable = false;
bool _zeroYieldDayOnMidnight = false;
std::unique_ptr<AlarmLogParser> _alarmLogParser; std::unique_ptr<AlarmLogParser> _alarmLogParser;
std::unique_ptr<DevInfoParser> _devInfoParser; std::unique_ptr<DevInfoParser> _devInfoParser;
std::unique_ptr<GridProfileParser> _gridProfileParser;
std::unique_ptr<PowerCommandParser> _powerCommandParser; std::unique_ptr<PowerCommandParser> _powerCommandParser;
std::unique_ptr<StatisticsParser> _statisticsParser; std::unique_ptr<StatisticsParser> _statisticsParser;
std::unique_ptr<SystemConfigParaParser> _systemConfigParaParser; std::unique_ptr<SystemConfigParaParser> _systemConfigParaParser;

View File

@ -86,16 +86,9 @@ const std::array<const AlarmMessage_t, ALARM_MSG_COUNT> AlarmLogParser::_alarmMe
{ AlarmMessageType_t::ALL, 9000, "Microinverter is suspected of being stolen" }, { 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() AlarmLogParser::AlarmLogParser()
: Parser() : Parser()
{ {
_xSemaphore = xSemaphoreCreateMutex();
HOY_SEMAPHORE_GIVE(); // release before first use
clearBuffer(); clearBuffer();
} }
@ -115,16 +108,6 @@ void AlarmLogParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t le
_alarmLogLength += len; _alarmLogLength += len;
} }
void AlarmLogParser::beginAppendFragment()
{
HOY_SEMAPHORE_TAKE();
}
void AlarmLogParser::endAppendFragment()
{
HOY_SEMAPHORE_GIVE();
}
uint8_t AlarmLogParser::getEntryCount() uint8_t AlarmLogParser::getEntryCount()
{ {
if (_alarmLogLength < 2) { if (_alarmLogLength < 2) {

View File

@ -1,7 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include "Parser.h" #include "Parser.h"
#include <Arduino.h>
#include <array> #include <array>
#include <cstdint> #include <cstdint>
@ -34,8 +33,6 @@ public:
AlarmLogParser(); AlarmLogParser();
void clearBuffer(); void clearBuffer();
void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len);
void beginAppendFragment();
void endAppendFragment();
uint8_t getEntryCount(); uint8_t getEntryCount();
void getLogEntry(uint8_t entryId, AlarmLogEntry_t* entry); void getLogEntry(uint8_t entryId, AlarmLogEntry_t* entry);
@ -56,6 +53,4 @@ private:
AlarmMessageType_t _messageType = AlarmMessageType_t::ALL; AlarmMessageType_t _messageType = AlarmMessageType_t::ALL;
static const std::array<const AlarmMessage_t, ALARM_MSG_COUNT> _alarmMessages; static const std::array<const AlarmMessage_t, ALARM_MSG_COUNT> _alarmMessages;
SemaphoreHandle_t _xSemaphore;
}; };

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
/* /*
* Copyright (C) 2022 Thomas Basler and others * Copyright (C) 2022 - 2023 Thomas Basler and others
*/ */
#include "DevInfoParser.h" #include "DevInfoParser.h"
#include "../Hoymiles.h" #include "../Hoymiles.h"
@ -46,16 +46,9 @@ const devInfo_t devInfo[] = {
{ { 0x10, 0x33, 0x31, ALL }, 2250, "HMT-2250" } // 01 { { 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() DevInfoParser::DevInfoParser()
: Parser() : Parser()
{ {
_xSemaphore = xSemaphoreCreateMutex();
HOY_SEMAPHORE_GIVE(); // release before first use
clearBufferSimple(); clearBufferSimple();
clearBufferAll(); clearBufferAll();
} }
@ -92,16 +85,6 @@ void DevInfoParser::appendFragmentSimple(uint8_t offset, uint8_t* payload, uint8
_devInfoSimpleLength += len; _devInfoSimpleLength += len;
} }
void DevInfoParser::beginAppendFragment()
{
HOY_SEMAPHORE_TAKE();
}
void DevInfoParser::endAppendFragment()
{
HOY_SEMAPHORE_GIVE();
}
uint32_t DevInfoParser::getLastUpdateAll() uint32_t DevInfoParser::getLastUpdateAll()
{ {
return _lastUpdateAll; return _lastUpdateAll;

View File

@ -1,7 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include "Parser.h" #include "Parser.h"
#include <Arduino.h>
#define DEV_INFO_SIZE 20 #define DEV_INFO_SIZE 20
@ -14,9 +13,6 @@ public:
void clearBufferSimple(); void clearBufferSimple();
void appendFragmentSimple(uint8_t offset, uint8_t* payload, uint8_t len); void appendFragmentSimple(uint8_t offset, uint8_t* payload, uint8_t len);
void beginAppendFragment();
void endAppendFragment();
uint32_t getLastUpdateAll(); uint32_t getLastUpdateAll();
void setLastUpdateAll(uint32_t lastUpdate); void setLastUpdateAll(uint32_t lastUpdate);
@ -47,6 +43,4 @@ private:
uint8_t _payloadDevInfoSimple[DEV_INFO_SIZE] = {}; uint8_t _payloadDevInfoSimple[DEV_INFO_SIZE] = {};
uint8_t _devInfoSimpleLength = 0; uint8_t _devInfoSimpleLength = 0;
SemaphoreHandle_t _xSemaphore;
}; };

View File

@ -0,0 +1,40 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
*/
#include "GridProfileParser.h"
#include "../Hoymiles.h"
#include <cstring>
GridProfileParser::GridProfileParser()
: Parser()
{
clearBuffer();
}
void GridProfileParser::clearBuffer()
{
memset(_payloadGridProfile, 0, GRID_PROFILE_SIZE);
_gridProfileLength = 0;
}
void GridProfileParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len)
{
if (offset + len > GRID_PROFILE_SIZE) {
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) grid profile packet too large for buffer\r\n", __FILE__, __LINE__);
return;
}
memcpy(&_payloadGridProfile[offset], payload, len);
_gridProfileLength += len;
}
std::vector<uint8_t> GridProfileParser::getRawData()
{
std::vector<uint8_t> ret;
HOY_SEMAPHORE_TAKE();
for (uint8_t i = 0; i < GRID_PROFILE_SIZE; i++) {
ret.push_back(_payloadGridProfile[i]);
}
HOY_SEMAPHORE_GIVE();
return ret;
}

View File

@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Parser.h"
#define GRID_PROFILE_SIZE 141
class GridProfileParser : public Parser {
public:
GridProfileParser();
void clearBuffer();
void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len);
std::vector<uint8_t> getRawData();
private:
uint8_t _payloadGridProfile[GRID_PROFILE_SIZE] = {};
uint8_t _gridProfileLength = 0;
};

View File

@ -1,9 +1,15 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
/* /*
* Copyright (C) 2022 Thomas Basler and others * Copyright (C) 2022 - 2023 Thomas Basler and others
*/ */
#include "Parser.h" #include "Parser.h"
Parser::Parser()
{
_xSemaphore = xSemaphoreCreateMutex();
HOY_SEMAPHORE_GIVE(); // release before first use
}
uint32_t Parser::getLastUpdate() uint32_t Parser::getLastUpdate()
{ {
return _lastUpdate; return _lastUpdate;
@ -13,3 +19,13 @@ void Parser::setLastUpdate(uint32_t lastUpdate)
{ {
_lastUpdate = lastUpdate; _lastUpdate = lastUpdate;
} }
void Parser::beginAppendFragment()
{
HOY_SEMAPHORE_TAKE();
}
void Parser::endAppendFragment()
{
HOY_SEMAPHORE_GIVE();
}

View File

@ -1,7 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include <Arduino.h>
#include <cstdint> #include <cstdint>
#define HOY_SEMAPHORE_TAKE() \
do { \
} while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS)
#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore)
typedef enum { typedef enum {
CMD_OK, CMD_OK,
CMD_NOK, CMD_NOK,
@ -10,9 +16,16 @@ typedef enum {
class Parser { class Parser {
public: public:
Parser();
uint32_t getLastUpdate(); uint32_t getLastUpdate();
void setLastUpdate(uint32_t lastUpdate); void setLastUpdate(uint32_t lastUpdate);
void beginAppendFragment();
void endAppendFragment();
protected:
SemaphoreHandle_t _xSemaphore;
private: private:
uint32_t _lastUpdate = 0; uint32_t _lastUpdate = 0;
}; };

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
/* /*
* Copyright (C) 2022 Thomas Basler and others * Copyright (C) 2022 - 2023 Thomas Basler and others
*/ */
#include "PowerCommandParser.h" #include "PowerCommandParser.h"

View File

@ -1,7 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include "Parser.h" #include "Parser.h"
#include <Arduino.h>
class PowerCommandParser : public Parser { class PowerCommandParser : public Parser {
public: public:

View File

@ -1,15 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
/* /*
* Copyright (C) 2022 Thomas Basler and others * Copyright (C) 2022 - 2023 Thomas Basler and others
*/ */
#include "StatisticsParser.h" #include "StatisticsParser.h"
#include "../Hoymiles.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 calcYieldTotalCh0(StatisticsParser* iv, uint8_t arg0);
static float calcYieldDayCh0(StatisticsParser* iv, uint8_t arg0); static float calcYieldDayCh0(StatisticsParser* iv, uint8_t arg0);
static float calcUdcCh(StatisticsParser* iv, uint8_t arg0); static float calcUdcCh(StatisticsParser* iv, uint8_t arg0);
@ -33,11 +28,35 @@ const calcFunc_t calcFunctions[] = {
{ CALC_IRR_CH, &calcIrradiation } { CALC_IRR_CH, &calcIrradiation }
}; };
const FieldId_t runtimeFields[] = {
FLD_UDC,
FLD_IDC,
FLD_PDC,
FLD_UAC,
FLD_IAC,
FLD_PAC,
FLD_F,
FLD_T,
FLD_PF,
FLD_Q,
FLD_UAC_1N,
FLD_UAC_2N,
FLD_UAC_3N,
FLD_UAC_12,
FLD_UAC_23,
FLD_UAC_31,
FLD_IAC_1,
FLD_IAC_2,
FLD_IAC_3,
};
const FieldId_t dailyProductionFields[] = {
FLD_YD,
};
StatisticsParser::StatisticsParser() StatisticsParser::StatisticsParser()
: Parser() : Parser()
{ {
_xSemaphore = xSemaphoreCreateMutex();
HOY_SEMAPHORE_GIVE(); // release before first use
clearBuffer(); clearBuffer();
} }
@ -75,16 +94,6 @@ void StatisticsParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t
_statisticLength += len; _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) const byteAssign_t* StatisticsParser::getAssignmentByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
{ {
for (uint8_t i = 0; i < _byteAssignmentSize; i++) { for (uint8_t i = 0; i < _byteAssignmentSize; i++) {
@ -150,6 +159,47 @@ float StatisticsParser::getChannelFieldValue(ChannelType_t type, ChannelNum_t ch
return 0; return 0;
} }
bool StatisticsParser::setChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float value)
{
const byteAssign_t* pos = getAssignmentByChannelField(type, channel, fieldId);
fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId);
if (pos == NULL) {
return false;
}
uint8_t ptr = pos->start + pos->num - 1;
uint8_t end = pos->start;
uint16_t div = pos->div;
if (CMD_CALC == div) {
return false;
}
if (setting != NULL) {
value -= setting->offset;
}
value *= static_cast<float>(div);
uint32_t val = 0;
if (pos->isSigned && pos->num == 2) {
val = static_cast<uint32_t>(static_cast<int16_t>(value));
} else if (pos->isSigned && pos->num == 4) {
val = static_cast<uint32_t>(static_cast<int32_t>(value));
} else {
val = static_cast<uint32_t>(value);
}
HOY_SEMAPHORE_TAKE();
do {
_payloadStatistic[ptr] = val;
val >>= 8;
} while (--ptr >= end);
HOY_SEMAPHORE_GIVE();
return true;
}
String StatisticsParser::getChannelFieldValueString(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) String StatisticsParser::getChannelFieldValueString(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
{ {
return String( return String(
@ -253,6 +303,30 @@ uint32_t StatisticsParser::getRxFailureCount()
return _rxFailureCount; return _rxFailureCount;
} }
void StatisticsParser::zeroRuntimeData()
{
zeroFields(runtimeFields);
}
void StatisticsParser::zeroDailyData()
{
zeroFields(dailyProductionFields);
}
void StatisticsParser::zeroFields(const FieldId_t* fields)
{
// Loop all channels
for (auto& t : getChannelTypes()) {
for (auto& c : getChannelsByType(t)) {
for (uint8_t i = 0; i < (sizeof(runtimeFields) / sizeof(runtimeFields[0])); i++) {
if (hasChannelFieldValue(t, c, fields[i])) {
setChannelFieldValue(t, c, fields[i], 0);
}
}
}
}
}
static float calcYieldTotalCh0(StatisticsParser* iv, uint8_t arg0) static float calcYieldTotalCh0(StatisticsParser* iv, uint8_t arg0)
{ {
float yield = 0; float yield = 0;

View File

@ -1,7 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include "Parser.h" #include "Parser.h"
#include <Arduino.h>
#include <cstdint> #include <cstdint>
#include <list> #include <list>
@ -107,8 +106,6 @@ public:
StatisticsParser(); StatisticsParser();
void clearBuffer(); void clearBuffer();
void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len);
void beginAppendFragment();
void endAppendFragment();
void setByteAssignment(const byteAssign_t* byteAssignment, uint8_t size); void setByteAssignment(const byteAssign_t* byteAssignment, uint8_t size);
@ -125,6 +122,8 @@ public:
const char* getChannelFieldName(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); const char* getChannelFieldName(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
uint8_t getChannelFieldDigits(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); uint8_t getChannelFieldDigits(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
bool setChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float value);
float getChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); float getChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
void setChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float offset); void setChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float offset);
@ -139,7 +138,12 @@ public:
void incrementRxFailureCount(); void incrementRxFailureCount();
uint32_t getRxFailureCount(); uint32_t getRxFailureCount();
void zeroRuntimeData();
void zeroDailyData();
private: private:
void zeroFields(const FieldId_t* fields);
uint8_t _payloadStatistic[STATISTIC_PACKET_SIZE] = {}; uint8_t _payloadStatistic[STATISTIC_PACKET_SIZE] = {};
uint8_t _statisticLength = 0; uint8_t _statisticLength = 0;
uint16_t _stringMaxPower[CH_CNT]; uint16_t _stringMaxPower[CH_CNT];
@ -150,6 +154,4 @@ private:
std::list<fieldSettings_t> _fieldSettings; std::list<fieldSettings_t> _fieldSettings;
uint32_t _rxFailureCount = 0; uint32_t _rxFailureCount = 0;
SemaphoreHandle_t _xSemaphore;
}; };

View File

@ -1,21 +1,14 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
/* /*
* Copyright (C) 2022 Thomas Basler and others * Copyright (C) 2022 - 2023 Thomas Basler and others
*/ */
#include "SystemConfigParaParser.h" #include "SystemConfigParaParser.h"
#include "../Hoymiles.h" #include "../Hoymiles.h"
#include <cstring> #include <cstring>
#define HOY_SEMAPHORE_TAKE() \
do { \
} while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS)
#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore)
SystemConfigParaParser::SystemConfigParaParser() SystemConfigParaParser::SystemConfigParaParser()
: Parser() : Parser()
{ {
_xSemaphore = xSemaphoreCreateMutex();
HOY_SEMAPHORE_GIVE(); // release before first use
clearBuffer(); clearBuffer();
} }
@ -35,16 +28,6 @@ void SystemConfigParaParser::appendFragment(uint8_t offset, uint8_t* payload, ui
_payloadLength += len; _payloadLength += len;
} }
void SystemConfigParaParser::beginAppendFragment()
{
HOY_SEMAPHORE_TAKE();
}
void SystemConfigParaParser::endAppendFragment()
{
HOY_SEMAPHORE_GIVE();
}
float SystemConfigParaParser::getLimitPercent() float SystemConfigParaParser::getLimitPercent()
{ {
HOY_SEMAPHORE_TAKE(); HOY_SEMAPHORE_TAKE();
@ -102,3 +85,8 @@ void SystemConfigParaParser::setLastUpdateRequest(uint32_t lastUpdate)
_lastUpdateRequest = lastUpdate; _lastUpdateRequest = lastUpdate;
setLastUpdate(lastUpdate); setLastUpdate(lastUpdate);
} }
uint8_t SystemConfigParaParser::getExpectedByteCount()
{
return SYSTEM_CONFIG_PARA_SIZE;
}

View File

@ -1,7 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include "Parser.h" #include "Parser.h"
#include <Arduino.h>
#define SYSTEM_CONFIG_PARA_SIZE 16 #define SYSTEM_CONFIG_PARA_SIZE 16
@ -10,8 +9,6 @@ public:
SystemConfigParaParser(); SystemConfigParaParser();
void clearBuffer(); void clearBuffer();
void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len);
void beginAppendFragment();
void endAppendFragment();
float getLimitPercent(); float getLimitPercent();
void setLimitPercent(float value); void setLimitPercent(float value);
@ -26,6 +23,9 @@ public:
uint32_t getLastUpdateRequest(); uint32_t getLastUpdateRequest();
void setLastUpdateRequest(uint32_t lastUpdate); void setLastUpdateRequest(uint32_t lastUpdate);
// Returns 1 based amount of expected bytes of data
uint8_t getExpectedByteCount();
private: private:
uint8_t _payload[SYSTEM_CONFIG_PARA_SIZE]; uint8_t _payload[SYSTEM_CONFIG_PARA_SIZE];
uint8_t _payloadLength; uint8_t _payloadLength;
@ -35,6 +35,4 @@ private:
uint32_t _lastUpdateCommand = 0; uint32_t _lastUpdateCommand = 0;
uint32_t _lastUpdateRequest = 0; uint32_t _lastUpdateRequest = 0;
SemaphoreHandle_t _xSemaphore;
}; };

View File

@ -33,7 +33,7 @@ build_unflags =
lib_deps = lib_deps =
https://github.com/yubox-node-org/ESPAsyncWebServer https://github.com/yubox-node-org/ESPAsyncWebServer
bblanchon/ArduinoJson @ ^6.21.3 bblanchon/ArduinoJson @ ^6.21.3
https://github.com/bertmelis/espMqttClient.git#v1.4.4 https://github.com/bertmelis/espMqttClient.git#v1.4.5
nrf24/RF24 @ ^1.4.7 nrf24/RF24 @ ^1.4.7
olikraus/U8g2 @ ^2.35.4 olikraus/U8g2 @ ^2.35.4
buelowp/sunset @ ^1.1.7 buelowp/sunset @ ^1.1.7

View File

@ -1,8 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <vector>
#include <algorithm>
#include "BatteryStats.h" #include "BatteryStats.h"
#include "Configuration.h"
#include "MqttSettings.h" #include "MqttSettings.h"
#include "JkBmsDataPoints.h" #include "JkBmsDataPoints.h"
@ -148,34 +145,6 @@ void PylontechBatteryStats::mqttPublish() const
void JkBmsBatteryStats::mqttPublish() const void JkBmsBatteryStats::mqttPublish() const
{ {
BatteryStats::mqttPublish(); BatteryStats::mqttPublish();
using Label = JkBms::DataPointLabel;
static std::vector<Label> mqttSkip = {
Label::CellsMilliVolt, // complex data format
Label::ModificationPassword, // sensitive data
Label::BatterySoCPercent // already published by base class
};
CONFIG_T& config = Configuration.get();
// publish all topics every minute, unless the retain flag is enabled
bool fullPublish = _lastFullMqttPublish + 60 * 1000 < millis();
fullPublish &= !config.Mqtt_Retain;
for (auto iter = _dataPoints.cbegin(); iter != _dataPoints.cend(); ++iter) {
// skip data points that did not change since last published
if (!fullPublish && iter->second.getTimestamp() < _lastMqttPublish) { continue; }
auto skipMatch = std::find(mqttSkip.begin(), mqttSkip.end(), iter->first);
if (skipMatch != mqttSkip.end()) { continue; }
String topic((std::string("battery/") + iter->second.getLabelText()).c_str());
MqttSettings.publish(topic, iter->second.getValueText().c_str());
}
_lastMqttPublish = millis();
if (fullPublish) { _lastFullMqttPublish = _lastMqttPublish; }
} }
void JkBmsBatteryStats::updateFrom(JkBms::DataPointContainer const& dp) void JkBmsBatteryStats::updateFrom(JkBms::DataPointContainer const& dp)

View File

@ -59,6 +59,7 @@ bool ConfigurationClass::write()
mqtt["topic"] = config.Mqtt_Topic; mqtt["topic"] = config.Mqtt_Topic;
mqtt["retain"] = config.Mqtt_Retain; mqtt["retain"] = config.Mqtt_Retain;
mqtt["publish_interval"] = config.Mqtt_PublishInterval; mqtt["publish_interval"] = config.Mqtt_PublishInterval;
mqtt["clean_session"] = config.Mqtt_CleanSession;
JsonObject mqtt_lwt = mqtt.createNestedObject("lwt"); JsonObject mqtt_lwt = mqtt.createNestedObject("lwt");
mqtt_lwt["topic"] = config.Mqtt_LwtTopic; mqtt_lwt["topic"] = config.Mqtt_LwtTopic;
@ -111,6 +112,9 @@ bool ConfigurationClass::write()
inv["poll_enable_night"] = config.Inverter[i].Poll_Enable_Night; inv["poll_enable_night"] = config.Inverter[i].Poll_Enable_Night;
inv["command_enable"] = config.Inverter[i].Command_Enable; inv["command_enable"] = config.Inverter[i].Command_Enable;
inv["command_enable_night"] = config.Inverter[i].Command_Enable_Night; inv["command_enable_night"] = config.Inverter[i].Command_Enable_Night;
inv["reachable_threshold"] = config.Inverter[i].ReachableThreshold;
inv["zero_runtime"] = config.Inverter[i].ZeroRuntimeDataIfUnrechable;
inv["zero_day"] = config.Inverter[i].ZeroYieldDayOnMidnight;
JsonArray channel = inv.createNestedArray("channel"); JsonArray channel = inv.createNestedArray("channel");
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
@ -278,6 +282,7 @@ bool ConfigurationClass::read()
strlcpy(config.Mqtt_Topic, mqtt["topic"] | MQTT_TOPIC, sizeof(config.Mqtt_Topic)); strlcpy(config.Mqtt_Topic, mqtt["topic"] | MQTT_TOPIC, sizeof(config.Mqtt_Topic));
config.Mqtt_Retain = mqtt["retain"] | MQTT_RETAIN; config.Mqtt_Retain = mqtt["retain"] | MQTT_RETAIN;
config.Mqtt_PublishInterval = mqtt["publish_interval"] | MQTT_PUBLISH_INTERVAL; config.Mqtt_PublishInterval = mqtt["publish_interval"] | MQTT_PUBLISH_INTERVAL;
config.Mqtt_CleanSession = mqtt["clean_session"] | MQTT_CLEAN_SESSION;
JsonObject mqtt_lwt = mqtt["lwt"]; JsonObject mqtt_lwt = mqtt["lwt"];
strlcpy(config.Mqtt_LwtTopic, mqtt_lwt["topic"] | MQTT_LWT_TOPIC, sizeof(config.Mqtt_LwtTopic)); strlcpy(config.Mqtt_LwtTopic, mqtt_lwt["topic"] | MQTT_LWT_TOPIC, sizeof(config.Mqtt_LwtTopic));
@ -331,6 +336,9 @@ bool ConfigurationClass::read()
config.Inverter[i].Poll_Enable_Night = inv["poll_enable_night"] | true; config.Inverter[i].Poll_Enable_Night = inv["poll_enable_night"] | true;
config.Inverter[i].Command_Enable = inv["command_enable"] | true; config.Inverter[i].Command_Enable = inv["command_enable"] | true;
config.Inverter[i].Command_Enable_Night = inv["command_enable_night"] | true; config.Inverter[i].Command_Enable_Night = inv["command_enable_night"] | true;
config.Inverter[i].ReachableThreshold = inv["reachable_threshold"] | REACHABLE_THRESHOLD;
config.Inverter[i].ZeroRuntimeDataIfUnrechable = inv["zero_runtime"] | false;
config.Inverter[i].ZeroYieldDayOnMidnight = inv["zero_day"] | false;
JsonArray channel = inv["channel"]; JsonArray channel = inv["channel"];
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {

View File

@ -151,25 +151,25 @@ float DatastoreClass::getTotalDcIrradiation()
return _totalDcIrradiation; return _totalDcIrradiation;
} }
unsigned int DatastoreClass::getTotalAcYieldTotalDigits() uint32_t DatastoreClass::getTotalAcYieldTotalDigits()
{ {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
return _totalAcYieldTotalDigits; return _totalAcYieldTotalDigits;
} }
unsigned int DatastoreClass::getTotalAcYieldDayDigits() uint32_t DatastoreClass::getTotalAcYieldDayDigits()
{ {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
return _totalAcYieldDayDigits; return _totalAcYieldDayDigits;
} }
unsigned int DatastoreClass::getTotalAcPowerDigits() uint32_t DatastoreClass::getTotalAcPowerDigits()
{ {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
return _totalAcPowerDigits; return _totalAcPowerDigits;
} }
unsigned int DatastoreClass::getTotalDcPowerDigits() uint32_t DatastoreClass::getTotalDcPowerDigits()
{ {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
return _totalDcPowerDigits; return _totalDcPowerDigits;

View File

@ -74,6 +74,9 @@ void InverterSettingsClass::init()
config.Inverter[i].Serial); config.Inverter[i].Serial);
if (inv != nullptr) { if (inv != nullptr) {
inv->setReachableThreshold(config.Inverter[i].ReachableThreshold);
inv->setZeroValuesIfUnreachable(config.Inverter[i].ZeroRuntimeDataIfUnrechable);
inv->setZeroYieldDayOnMidnight(config.Inverter[i].ZeroYieldDayOnMidnight);
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
inv->Statistics()->setStringMaxPower(c, config.Inverter[i].channel[c].MaxChannelPower); inv->Statistics()->setStringMaxPower(c, config.Inverter[i].channel[c].MaxChannelPower);
inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast<ChannelNum_t>(c), FLD_YT, config.Inverter[i].channel[c].YieldTotalOffset); inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast<ChannelNum_t>(c), FLD_YT, config.Inverter[i].channel[c].YieldTotalOffset);

View File

@ -312,9 +312,9 @@ void Controller::frameComplete()
ts, _buffer.size()); ts, _buffer.size());
for (size_t ctr = 0; ctr < _buffer.size(); ++ctr) { for (size_t ctr = 0; ctr < _buffer.size(); ++ctr) {
if (ctr % 16 == 0) { if (ctr % 16 == 0) {
MessageOutput.printf("\r\n[%11.3f] JK BMS:", ts); MessageOutput.printf("\r\n[%11.3f] JK BMS: ", ts);
} }
MessageOutput.printf(" %02x", _buffer[ctr]); MessageOutput.printf("%02x ", _buffer[ctr]);
} }
MessageOutput.println(); MessageOutput.println();
} }

View File

@ -48,14 +48,7 @@ std::string dataPointValueToStr(tCells const& v) {
void DataPointContainer::updateFrom(DataPointContainer const& source) void DataPointContainer::updateFrom(DataPointContainer const& source)
{ {
for (auto iter = source.cbegin(); iter != source.cend(); ++iter) { for (auto iter = source.cbegin(); iter != source.cend(); ++iter) {
auto pos = _dataPoints.find(iter->first); _dataPoints.erase(iter->first);
if (pos != _dataPoints.end()) {
// do not update existing data points with the same value
if (pos->second == iter->second) { continue; }
_dataPoints.erase(pos);
}
_dataPoints.insert(*iter); _dataPoints.insert(*iter);
} }
} }

View File

@ -134,7 +134,7 @@ void MqttHandleHassClass::publishField(std::shared_ptr<InverterAbstract> inv, Ch
createDeviceInfo(deviceObj, inv); createDeviceInfo(deviceObj, inv);
if (Configuration.get().Mqtt_Hass_Expire) { if (Configuration.get().Mqtt_Hass_Expire) {
root["exp_aft"] = Hoymiles.getNumInverters() * Configuration.get().Mqtt_PublishInterval * 3; root["exp_aft"] = Hoymiles.getNumInverters() * max<uint32_t>(Hoymiles.PollInterval(), Configuration.get().Mqtt_PublishInterval) * inv->getReachableThreshold();
} }
if (devCls != 0) { if (devCls != 0) {
root["dev_cla"] = devCls; root["dev_cla"] = devCls;

View File

@ -14,6 +14,8 @@
#define TOPIC_SUB_POWER "power" #define TOPIC_SUB_POWER "power"
#define TOPIC_SUB_RESTART "restart" #define TOPIC_SUB_RESTART "restart"
#define PUBLISH_MAX_INTERVAL 60000
MqttHandleInverterClass MqttHandleInverter; MqttHandleInverterClass MqttHandleInverter;
void MqttHandleInverterClass::init() void MqttHandleInverterClass::init()
@ -92,9 +94,13 @@ void MqttHandleInverterClass::loop()
} }
uint32_t lastUpdate = inv->Statistics()->getLastUpdate(); uint32_t lastUpdate = inv->Statistics()->getLastUpdate();
if (lastUpdate > 0 && lastUpdate != _lastPublishStats[i]) { if (lastUpdate > 0 && (lastUpdate != _lastPublishStats[i] || ((inv->getZeroValuesIfUnreachable() || inv->getZeroYieldDayOnMidnight()) && _statsTimeout.occured()))) {
_lastPublishStats[i] = lastUpdate; _lastPublishStats[i] = lastUpdate;
// At first a change of the stats have to occour. Then the stats
// are published on every change or every 60 seconds
_statsTimeout.set(PUBLISH_MAX_INTERVAL);
// Loop all channels // Loop all channels
for (auto& t : inv->Statistics()->getChannelTypes()) { for (auto& t : inv->Statistics()->getChannelTypes()) {
for (auto& c : inv->Statistics()->getChannelsByType(t)) { for (auto& c : inv->Statistics()->getChannelsByType(t)) {

View File

@ -130,6 +130,7 @@ void MqttSettingsClass::performConnect()
} }
static_cast<espMqttClientSecure*>(mqttClient)->setWill(willTopic.c_str(), 2, config.Mqtt_Retain, config.Mqtt_LwtValue_Offline); static_cast<espMqttClientSecure*>(mqttClient)->setWill(willTopic.c_str(), 2, config.Mqtt_Retain, config.Mqtt_LwtValue_Offline);
static_cast<espMqttClientSecure*>(mqttClient)->setClientId(clientId.c_str()); static_cast<espMqttClientSecure*>(mqttClient)->setClientId(clientId.c_str());
static_cast<espMqttClientSecure*>(mqttClient)->setCleanSession(config.Mqtt_CleanSession);
static_cast<espMqttClientSecure*>(mqttClient)->onConnect(std::bind(&MqttSettingsClass::onMqttConnect, this, _1)); static_cast<espMqttClientSecure*>(mqttClient)->onConnect(std::bind(&MqttSettingsClass::onMqttConnect, this, _1));
static_cast<espMqttClientSecure*>(mqttClient)->onDisconnect(std::bind(&MqttSettingsClass::onMqttDisconnect, this, _1)); static_cast<espMqttClientSecure*>(mqttClient)->onDisconnect(std::bind(&MqttSettingsClass::onMqttDisconnect, this, _1));
static_cast<espMqttClientSecure*>(mqttClient)->onMessage(std::bind(&MqttSettingsClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); static_cast<espMqttClientSecure*>(mqttClient)->onMessage(std::bind(&MqttSettingsClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
@ -138,6 +139,7 @@ void MqttSettingsClass::performConnect()
static_cast<espMqttClient*>(mqttClient)->setCredentials(config.Mqtt_Username, config.Mqtt_Password); static_cast<espMqttClient*>(mqttClient)->setCredentials(config.Mqtt_Username, config.Mqtt_Password);
static_cast<espMqttClient*>(mqttClient)->setWill(willTopic.c_str(), 2, config.Mqtt_Retain, config.Mqtt_LwtValue_Offline); static_cast<espMqttClient*>(mqttClient)->setWill(willTopic.c_str(), 2, config.Mqtt_Retain, config.Mqtt_LwtValue_Offline);
static_cast<espMqttClient*>(mqttClient)->setClientId(clientId.c_str()); static_cast<espMqttClient*>(mqttClient)->setClientId(clientId.c_str());
static_cast<espMqttClient*>(mqttClient)->setCleanSession(config.Mqtt_CleanSession);
static_cast<espMqttClient*>(mqttClient)->onConnect(std::bind(&MqttSettingsClass::onMqttConnect, this, _1)); static_cast<espMqttClient*>(mqttClient)->onConnect(std::bind(&MqttSettingsClass::onMqttConnect, this, _1));
static_cast<espMqttClient*>(mqttClient)->onDisconnect(std::bind(&MqttSettingsClass::onMqttDisconnect, this, _1)); static_cast<espMqttClient*>(mqttClient)->onDisconnect(std::bind(&MqttSettingsClass::onMqttDisconnect, this, _1));
static_cast<espMqttClient*>(mqttClient)->onMessage(std::bind(&MqttSettingsClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); static_cast<espMqttClient*>(mqttClient)->onMessage(std::bind(&MqttSettingsClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));

View File

@ -1,9 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
/* /*
* Copyright (C) 2022 Thomas Basler and others * Copyright (C) 2022 - 2023 Thomas Basler and others
*/ */
#include "NtpSettings.h" #include "NtpSettings.h"
#include "Configuration.h" #include "Configuration.h"
#include <Arduino.h>
#include <time.h> #include <time.h>
NtpSettingsClass::NtpSettingsClass() NtpSettingsClass::NtpSettingsClass()

View File

@ -5,6 +5,7 @@
#include "SunPosition.h" #include "SunPosition.h"
#include "Configuration.h" #include "Configuration.h"
#include "Utils.h" #include "Utils.h"
#include <Arduino.h>
SunPositionClass SunPosition; SunPositionClass SunPosition;
@ -83,7 +84,7 @@ void SunPositionClass::updateSunData()
_sunriseMinutes = static_cast<int>(sunriseRaw); _sunriseMinutes = static_cast<int>(sunriseRaw);
_sunsetMinutes = static_cast<int>(sunsetRaw); _sunsetMinutes = static_cast<int>(sunsetRaw);
uint minutesPastMidnight = timeinfo.tm_hour * 60 + timeinfo.tm_min; uint32_t minutesPastMidnight = timeinfo.tm_hour * 60 + timeinfo.tm_min;
_isDayPeriod = (minutesPastMidnight >= _sunriseMinutes) && (minutesPastMidnight < _sunsetMinutes); _isDayPeriod = (minutesPastMidnight >= _sunriseMinutes) && (minutesPastMidnight < _sunsetMinutes);
_isSunsetAvailable = true; _isSunsetAvailable = true;

View File

@ -10,7 +10,7 @@
uint32_t Utils::getChipId() uint32_t Utils::getChipId()
{ {
uint32_t chipId = 0; uint32_t chipId = 0;
for (int i = 0; i < 17; i += 8) { for (uint8_t i = 0; i < 17; i += 8) {
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i; chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
} }
return chipId; return chipId;

View File

@ -24,6 +24,7 @@ void WebApiClass::init()
_webApiDtu.init(&_server); _webApiDtu.init(&_server);
_webApiEventlog.init(&_server); _webApiEventlog.init(&_server);
_webApiFirmware.init(&_server); _webApiFirmware.init(&_server);
_webApiGridprofile.init(&_server);
_webApiInverter.init(&_server); _webApiInverter.init(&_server);
_webApiLimit.init(&_server); _webApiLimit.init(&_server);
_webApiMaintenance.init(&_server); _webApiMaintenance.init(&_server);
@ -57,6 +58,7 @@ void WebApiClass::loop()
_webApiDtu.loop(); _webApiDtu.loop();
_webApiEventlog.loop(); _webApiEventlog.loop();
_webApiFirmware.loop(); _webApiFirmware.loop();
_webApiGridprofile.loop();
_webApiInverter.loop(); _webApiInverter.loop();
_webApiLimit.loop(); _webApiLimit.loop();
_webApiMaintenance.loop(); _webApiMaintenance.loop();

View File

@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
*/
#include "WebApi_gridprofile.h"
#include "WebApi.h"
#include <AsyncJson.h>
#include <Hoymiles.h>
void WebApiGridProfileClass::init(AsyncWebServer* server)
{
using std::placeholders::_1;
_server = server;
_server->on("/api/gridprofile/status", HTTP_GET, std::bind(&WebApiGridProfileClass::onGridProfileStatus, this, _1));
}
void WebApiGridProfileClass::loop()
{
}
void WebApiGridProfileClass::onGridProfileStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {
return;
}
AsyncJsonResponse* response = new AsyncJsonResponse(false, 4096);
JsonObject root = response->getRoot();
uint64_t serial = 0;
if (request->hasParam("inv")) {
String s = request->getParam("inv")->value();
serial = strtoll(s.c_str(), NULL, 16);
}
auto inv = Hoymiles.getInverterBySerial(serial);
if (inv != nullptr) {
auto raw = root.createNestedArray("raw");
auto data = inv->GridProfile()->getRawData();
copyArray(&data[0], data.size(), raw);
}
response->setLength();
request->send(response);
}

View File

@ -7,6 +7,7 @@
#include "MqttHandleHass.h" #include "MqttHandleHass.h"
#include "WebApi.h" #include "WebApi.h"
#include "WebApi_errors.h" #include "WebApi_errors.h"
#include "defaults.h"
#include "helper.h" #include "helper.h"
#include <AsyncJson.h> #include <AsyncJson.h>
#include <Hoymiles.h> #include <Hoymiles.h>
@ -57,6 +58,9 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
obj["poll_enable_night"] = config.Inverter[i].Poll_Enable_Night; obj["poll_enable_night"] = config.Inverter[i].Poll_Enable_Night;
obj["command_enable"] = config.Inverter[i].Command_Enable; obj["command_enable"] = config.Inverter[i].Command_Enable;
obj["command_enable_night"] = config.Inverter[i].Command_Enable_Night; obj["command_enable_night"] = config.Inverter[i].Command_Enable_Night;
obj["reachable_threshold"] = config.Inverter[i].ReachableThreshold;
obj["zero_runtime"] = config.Inverter[i].ZeroRuntimeDataIfUnrechable;
obj["zero_day"] = config.Inverter[i].ZeroYieldDayOnMidnight;
auto inv = Hoymiles.getInverterBySerial(config.Inverter[i].Serial); auto inv = Hoymiles.getInverterBySerial(config.Inverter[i].Serial);
uint8_t max_channels; uint8_t max_channels;
@ -281,6 +285,9 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
inverter.Poll_Enable_Night = root["poll_enable_night"] | true; inverter.Poll_Enable_Night = root["poll_enable_night"] | true;
inverter.Command_Enable = root["command_enable"] | true; inverter.Command_Enable = root["command_enable"] | true;
inverter.Command_Enable_Night = root["command_enable_night"] | true; inverter.Command_Enable_Night = root["command_enable_night"] | true;
inverter.ReachableThreshold = root["reachable_threshold"] | REACHABLE_THRESHOLD;
inverter.ZeroRuntimeDataIfUnrechable = root["zero_runtime"] | false;
inverter.ZeroYieldDayOnMidnight = root["zero_day"] | false;
arrayCount++; arrayCount++;
} }
@ -311,6 +318,9 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
if (inv != nullptr) { if (inv != nullptr) {
inv->setEnablePolling(inverter.Poll_Enable); inv->setEnablePolling(inverter.Poll_Enable);
inv->setEnableCommands(inverter.Command_Enable); inv->setEnableCommands(inverter.Command_Enable);
inv->setReachableThreshold(inverter.ReachableThreshold);
inv->setZeroValuesIfUnreachable(inverter.ZeroRuntimeDataIfUnrechable);
inv->setZeroYieldDayOnMidnight(inverter.ZeroYieldDayOnMidnight);
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
inv->Statistics()->setStringMaxPower(c, inverter.channel[c].MaxChannelPower); inv->Statistics()->setStringMaxPower(c, inverter.channel[c].MaxChannelPower);
inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast<ChannelNum_t>(c), FLD_YT, inverter.channel[c].YieldTotalOffset); inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast<ChannelNum_t>(c), FLD_YT, inverter.channel[c].YieldTotalOffset);

View File

@ -54,6 +54,7 @@ void WebApiMqttClass::onMqttStatus(AsyncWebServerRequest* request)
root["mqtt_client_cert_info"] = getTlsCertInfo(config.Mqtt_ClientCert); root["mqtt_client_cert_info"] = getTlsCertInfo(config.Mqtt_ClientCert);
root["mqtt_lwt_topic"] = String(config.Mqtt_Topic) + config.Mqtt_LwtTopic; root["mqtt_lwt_topic"] = String(config.Mqtt_Topic) + config.Mqtt_LwtTopic;
root["mqtt_publish_interval"] = config.Mqtt_PublishInterval; root["mqtt_publish_interval"] = config.Mqtt_PublishInterval;
root["mqtt_clean_session"] = config.Mqtt_CleanSession;
root["mqtt_hass_enabled"] = config.Mqtt_Hass_Enabled; root["mqtt_hass_enabled"] = config.Mqtt_Hass_Enabled;
root["mqtt_hass_expire"] = config.Mqtt_Hass_Expire; root["mqtt_hass_expire"] = config.Mqtt_Hass_Expire;
root["mqtt_hass_retain"] = config.Mqtt_Hass_Retain; root["mqtt_hass_retain"] = config.Mqtt_Hass_Retain;
@ -91,6 +92,7 @@ void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request)
root["mqtt_lwt_online"] = config.Mqtt_LwtValue_Online; root["mqtt_lwt_online"] = config.Mqtt_LwtValue_Online;
root["mqtt_lwt_offline"] = config.Mqtt_LwtValue_Offline; root["mqtt_lwt_offline"] = config.Mqtt_LwtValue_Offline;
root["mqtt_publish_interval"] = config.Mqtt_PublishInterval; root["mqtt_publish_interval"] = config.Mqtt_PublishInterval;
root["mqtt_clean_session"] = config.Mqtt_CleanSession;
root["mqtt_hass_enabled"] = config.Mqtt_Hass_Enabled; root["mqtt_hass_enabled"] = config.Mqtt_Hass_Enabled;
root["mqtt_hass_expire"] = config.Mqtt_Hass_Expire; root["mqtt_hass_expire"] = config.Mqtt_Hass_Expire;
root["mqtt_hass_retain"] = config.Mqtt_Hass_Retain; root["mqtt_hass_retain"] = config.Mqtt_Hass_Retain;
@ -156,6 +158,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
&& root.containsKey("mqtt_lwt_online") && root.containsKey("mqtt_lwt_online")
&& root.containsKey("mqtt_lwt_offline") && root.containsKey("mqtt_lwt_offline")
&& root.containsKey("mqtt_publish_interval") && root.containsKey("mqtt_publish_interval")
&& root.containsKey("mqtt_clean_session")
&& root.containsKey("mqtt_hass_enabled") && root.containsKey("mqtt_hass_enabled")
&& root.containsKey("mqtt_hass_expire") && root.containsKey("mqtt_hass_expire")
&& root.containsKey("mqtt_hass_retain") && root.containsKey("mqtt_hass_retain")
@ -321,6 +324,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
strlcpy(config.Mqtt_LwtValue_Online, root["mqtt_lwt_online"].as<String>().c_str(), sizeof(config.Mqtt_LwtValue_Online)); strlcpy(config.Mqtt_LwtValue_Online, root["mqtt_lwt_online"].as<String>().c_str(), sizeof(config.Mqtt_LwtValue_Online));
strlcpy(config.Mqtt_LwtValue_Offline, root["mqtt_lwt_offline"].as<String>().c_str(), sizeof(config.Mqtt_LwtValue_Offline)); strlcpy(config.Mqtt_LwtValue_Offline, root["mqtt_lwt_offline"].as<String>().c_str(), sizeof(config.Mqtt_LwtValue_Offline));
config.Mqtt_PublishInterval = root["mqtt_publish_interval"].as<uint32_t>(); config.Mqtt_PublishInterval = root["mqtt_publish_interval"].as<uint32_t>();
config.Mqtt_CleanSession = root["mqtt_clean_session"].as<bool>();
config.Mqtt_Hass_Enabled = root["mqtt_hass_enabled"].as<bool>(); config.Mqtt_Hass_Enabled = root["mqtt_hass_enabled"].as<bool>();
config.Mqtt_Hass_Expire = root["mqtt_hass_expire"].as<bool>(); config.Mqtt_Hass_Expire = root["mqtt_hass_expire"].as<bool>();
config.Mqtt_Hass_Retain = root["mqtt_hass_retain"].as<bool>(); config.Mqtt_Hass_Retain = root["mqtt_hass_retain"].as<bool>();

View File

@ -66,6 +66,11 @@ void WebApiWsLiveClass::loop()
if (millis() - _lastWsPublish > (10 * 1000) || (maxTimeStamp != _newestInverterTimestamp)) { if (millis() - _lastWsPublish > (10 * 1000) || (maxTimeStamp != _newestInverterTimestamp)) {
try { try {
std::lock_guard<std::mutex> lock(_mutex);
DynamicJsonDocument root(4096 * INV_MAX_COUNT);
JsonVariant var = root;
generateJsonResponse(var);
String buffer; String buffer;
// free JsonDocument as soon as possible // free JsonDocument as soon as possible
{ {
@ -152,6 +157,7 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
addField(chanTypeObj, i, inv, t, c, FLD_EFF); addField(chanTypeObj, i, inv, t, c, FLD_EFF);
if (t == TYPE_DC && inv->Statistics()->getStringMaxPower(c) > 0) { if (t == TYPE_DC && inv->Statistics()->getStringMaxPower(c) > 0) {
addField(chanTypeObj, i, inv, t, c, FLD_IRR); addField(chanTypeObj, i, inv, t, c, FLD_IRR);
chanTypeObj[String(c)][inv->Statistics()->getChannelFieldName(t, c, FLD_IRR)]["max"] = inv->Statistics()->getStringMaxPower(c);
} }
} }
} }
@ -231,13 +237,9 @@ void WebApiWsLiveClass::addTotalField(JsonObject& root, String name, float value
void WebApiWsLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) void WebApiWsLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
{ {
if (type == WS_EVT_CONNECT) { if (type == WS_EVT_CONNECT) {
char str[64]; MessageOutput.printf("Websocket: [%s][%u] connect\r\n", server->url(), client->id());
snprintf(str, sizeof(str), "Websocket: [%s][%u] connect", server->url(), client->id());
MessageOutput.println(str);
} else if (type == WS_EVT_DISCONNECT) { } else if (type == WS_EVT_DISCONNECT) {
char str[64]; MessageOutput.printf("Websocket: [%s][%u] disconnect\r\n", server->url(), client->id());
snprintf(str, sizeof(str), "Websocket: [%s][%u] disconnect", server->url(), client->id());
MessageOutput.println(str);
} }
} }
@ -248,7 +250,8 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
} }
try { try {
AsyncJsonResponse* response = new AsyncJsonResponse(false, 40960U); std::lock_guard<std::mutex> lock(_mutex);
AsyncJsonResponse* response = new AsyncJsonResponse(false, 4096 * INV_MAX_COUNT);
JsonVariant root = response->getRoot(); JsonVariant root = response->getRoot();
generateJsonResponse(root); generateJsonResponse(root);

View File

@ -18,29 +18,29 @@
"sortablejs": "^1.15.0", "sortablejs": "^1.15.0",
"spark-md5": "^3.0.2", "spark-md5": "^3.0.2",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-i18n": "^9.2.2", "vue-i18n": "^9.3.0",
"vue-router": "^4.2.4" "vue-router": "^4.2.4"
}, },
"devDependencies": { "devDependencies": {
"@intlify/unplugin-vue-i18n": "^0.12.3", "@intlify/unplugin-vue-i18n": "^1.2.0",
"@rushstack/eslint-patch": "^1.3.3", "@rushstack/eslint-patch": "^1.3.3",
"@tsconfig/node18": "^18.2.1", "@tsconfig/node18": "^18.2.1",
"@types/bootstrap": "^5.2.6", "@types/bootstrap": "^5.2.6",
"@types/node": "^20.5.7", "@types/node": "^20.5.9",
"@types/sortablejs": "^1.15.2", "@types/sortablejs": "^1.15.2",
"@types/spark-md5": "^3.0.2", "@types/spark-md5": "^3.0.2",
"@vitejs/plugin-vue": "^4.3.3", "@vitejs/plugin-vue": "^4.3.4",
"@vue/eslint-config-typescript": "^11.0.3", "@vue/eslint-config-typescript": "^11.0.3",
"@vue/tsconfig": "^0.4.0", "@vue/tsconfig": "^0.4.0",
"eslint": "^8.48.0", "eslint": "^8.49.0",
"eslint-plugin-vue": "^9.17.0", "eslint-plugin-vue": "^9.17.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"sass": "^1.64.2", "sass": "^1.66.1",
"terser": "^5.19.2", "terser": "^5.19.4",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^4.4.9", "vite": "^4.4.9",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-css-injected-by-js": "^3.3.0", "vite-plugin-css-injected-by-js": "^3.3.0",
"vue-tsc": "^1.8.8" "vue-tsc": "^1.8.10"
} }
} }

View File

@ -0,0 +1,50 @@
<template>
<BootstrapAlert :show="!hasValidData">
<h4 class="alert-heading">
<BIconInfoSquare class="fs-2" />&nbsp;{{ $t('gridprofile.NoInfo') }}
</h4>{{ $t('gridprofile.NoInfoLong') }}
</BootstrapAlert>
<template v-if="hasValidData">
<BootstrapAlert :show="true" variant="danger">
<h4 class="info-heading">
<BIconInfoSquare class="fs-2" />&nbsp;{{ $t('gridprofile.GridprofileSupport') }}
</h4><div v-html="$t('gridprofile.GridprofileSupportLong')"></div>
</BootstrapAlert>
<samp >
{{ rawContent() }}
</samp>
</template>
</template>
<script lang="ts">
import BootstrapAlert from '@/components/BootstrapAlert.vue';
import type { GridProfileStatus } from "@/types/GridProfileStatus";
import { BIconInfoSquare } from 'bootstrap-icons-vue';
import { defineComponent, type PropType } from 'vue';
export default defineComponent({
components: {
BootstrapAlert,
BIconInfoSquare,
},
props: {
gridProfileList: { type: Object as PropType<GridProfileStatus>, required: true },
},
computed: {
rawContent() {
return () => {
return this.gridProfileList.raw.map(function (x) {
let y = x.toString(16); // to hex
y = ("00" + y).substr(-2); // zero-pad to 2-digits
return y
}).join(' ');
}
},
hasValidData() {
return this.gridProfileList.raw.reduce((sum, x) => sum + x, 0) > 0;
},
},
});
</script>

View File

@ -134,7 +134,9 @@
"Failure": "Fehlgeschlagen", "Failure": "Fehlgeschlagen",
"Pending": "Ausstehend", "Pending": "Ausstehend",
"Ok": "Ok", "Ok": "Ok",
"Unknown": "Unbekannt" "Unknown": "Unbekannt",
"ShowGridProfile": "Zeige Grid Profil",
"GridProfile": "Grid Profil"
}, },
"vedirecthome": { "vedirecthome": {
"SerialNumber": "Seriennummer: ", "SerialNumber": "Seriennummer: ",
@ -192,6 +194,12 @@
"HardwarePartNumber": "Hardware-Teilenummer", "HardwarePartNumber": "Hardware-Teilenummer",
"HardwareVersion": "Hardware-Version" "HardwareVersion": "Hardware-Version"
}, },
"gridprofile": {
"NoInfo": "@:devinfo.NoInfo",
"NoInfoLong": "@:devinfo.NoInfoLong",
"GridprofileSupport": "Unterstütze die Entwicklung",
"GridprofileSupportLong": "Weitere Informationen sind <a href=\"https://github.com/tbnobody/OpenDTU/wiki/Grid-Profile-Parser\" target=\"_blank\">hier</a> zu finden."
},
"systeminfo": { "systeminfo": {
"SystemInfo": "System Informationen", "SystemInfo": "System Informationen",
"VersionError": "Fehler beim Abrufen von Versionsinformationen", "VersionError": "Fehler beim Abrufen von Versionsinformationen",
@ -308,6 +316,7 @@
"BaseTopic": "Basis Topic", "BaseTopic": "Basis Topic",
"PublishInterval": "Veröffentlichungsintervall", "PublishInterval": "Veröffentlichungsintervall",
"Seconds": "{sec} Sekunden", "Seconds": "{sec} Sekunden",
"CleanSession": "CleanSession Flag",
"Retain": "Retain", "Retain": "Retain",
"Tls": "TLS", "Tls": "TLS",
"RootCertifcateInfo": "Root CA-Zertifikat-Informationen", "RootCertifcateInfo": "Root CA-Zertifikat-Informationen",
@ -474,6 +483,7 @@
"BaseTopicHint": "Basis-Topic, wird allen veröffentlichten Themen vorangestellt (z.B. inverter/)", "BaseTopicHint": "Basis-Topic, wird allen veröffentlichten Themen vorangestellt (z.B. inverter/)",
"PublishInterval": "Veröffentlichungsintervall:", "PublishInterval": "Veröffentlichungsintervall:",
"Seconds": "Sekunden", "Seconds": "Sekunden",
"CleanSession": "CleanSession Flag aktivieren",
"EnableRetain": "Retain Flag aktivieren", "EnableRetain": "Retain Flag aktivieren",
"EnableTls": "TLS aktivieren", "EnableTls": "TLS aktivieren",
"RootCa": "CA-Root-Zertifikat (Standard Letsencrypt):", "RootCa": "CA-Root-Zertifikat (Standard Letsencrypt):",
@ -614,6 +624,9 @@
"SaveOrder": "Reihenfolge speichern", "SaveOrder": "Reihenfolge speichern",
"DeleteInverter": "Wechselrichter löschen", "DeleteInverter": "Wechselrichter löschen",
"EditInverter": "Wechselrichter bearbeiten", "EditInverter": "Wechselrichter bearbeiten",
"General": "Allgemein",
"String": "String",
"Advanced": "Erweitert",
"InverterSerial": "Wechselrichter Seriennummer:", "InverterSerial": "Wechselrichter Seriennummer:",
"InverterName": "Wechselrichter Name:", "InverterName": "Wechselrichter Name:",
"InverterNameHint": "Hier kann ein eigener Namen für den Wechselrichter angeben werden.", "InverterNameHint": "Hier kann ein eigener Namen für den Wechselrichter angeben werden.",
@ -629,6 +642,12 @@
"StringYtOffset": "Ertragsversatz String {num}:", "StringYtOffset": "Ertragsversatz String {num}:",
"StringYtOffsetHint": "Dieser Offset wird beim Auslesen des Gesamtertragswertes des Wechselrichters angewendet. Damit kann der Gesamtertrag des Wechselrichters auf Null gesetzt werden, wenn ein gebrauchter Wechselrichter verwendet wird.", "StringYtOffsetHint": "Dieser Offset wird beim Auslesen des Gesamtertragswertes des Wechselrichters angewendet. Damit kann der Gesamtertrag des Wechselrichters auf Null gesetzt werden, wenn ein gebrauchter Wechselrichter verwendet wird.",
"InverterHint": "*) Geben Sie die W<sub>p</sub> des Ports ein, um die Einstrahlung zu errechnen.", "InverterHint": "*) Geben Sie die W<sub>p</sub> des Ports ein, um die Einstrahlung zu errechnen.",
"ReachableThreshold": "Erreichbarkeit Schwellenwert:",
"ReachableThresholdHint": "Legt fest, wie viele Anfragen fehlschlagen dürfen, bis der Wechselrichter als unerreichbar eingestuft wird.",
"ZeroRuntime": "Nulle Laufzeit Daten",
"ZeroRuntimeHint": "Nulle Laufzeit Daten (keine Ertragsdaten), wenn der Wechselrichter nicht erreichbar ist.",
"ZeroDay": "Nulle Tagesertrag um Mitternacht",
"ZeroDayHint": "Das funktioniert nur wenn der Wechselrichter nicht erreichbar ist. Wenn Daten aus dem Wechselrichter gelesen werden, werden deren Werte verwendet. (Ein Reset erfolgt nur beim Neustarten)",
"Cancel": "@:maintenancereboot.Cancel", "Cancel": "@:maintenancereboot.Cancel",
"Save": "@:dtuadmin.Save", "Save": "@:dtuadmin.Save",
"DeleteMsg": "Soll der Wechselrichter \"{name}\" mit der Seriennummer {serial} wirklich gelöscht werden?", "DeleteMsg": "Soll der Wechselrichter \"{name}\" mit der Seriennummer {serial} wirklich gelöscht werden?",

View File

@ -134,7 +134,9 @@
"Failure": "Failure", "Failure": "Failure",
"Pending": "Pending", "Pending": "Pending",
"Ok": "Ok", "Ok": "Ok",
"Unknown": "Unknown" "Unknown": "Unknown",
"ShowGridProfile": "Show Grid Profile",
"GridProfile": "Grid Profile"
}, },
"vedirecthome": { "vedirecthome": {
"SerialNumber": "Serial Number: ", "SerialNumber": "Serial Number: ",
@ -192,6 +194,12 @@
"HardwarePartNumber": "Hardware Part Number", "HardwarePartNumber": "Hardware Part Number",
"HardwareVersion": "Hardware Version" "HardwareVersion": "Hardware Version"
}, },
"gridprofile": {
"NoInfo": "@:devinfo.NoInfo",
"NoInfoLong": "@:devinfo.NoInfoLong",
"GridprofileSupport": "Support the development",
"GridprofileSupportLong": "Please see <a href=\"https://github.com/tbnobody/OpenDTU/wiki/Grid-Profile-Parser\" target=\"_blank\">here</a> for further information."
},
"systeminfo": { "systeminfo": {
"SystemInfo": "System Info", "SystemInfo": "System Info",
"VersionError": "Error fetching version information", "VersionError": "Error fetching version information",
@ -310,6 +318,7 @@
"BaseTopic": "Base Topic", "BaseTopic": "Base Topic",
"PublishInterval": "Publish Interval", "PublishInterval": "Publish Interval",
"Seconds": "{sec} seconds", "Seconds": "{sec} seconds",
"CleanSession": "CleanSession flag",
"Retain": "Retain", "Retain": "Retain",
"Tls": "TLS", "Tls": "TLS",
"RootCertifcateInfo": "Root CA Certifcate Info", "RootCertifcateInfo": "Root CA Certifcate Info",
@ -476,6 +485,7 @@
"BaseTopicHint": "Base topic, will be prepend to all published topics (e.g. inverter/)", "BaseTopicHint": "Base topic, will be prepend to all published topics (e.g. inverter/)",
"PublishInterval": "Publish Interval:", "PublishInterval": "Publish Interval:",
"Seconds": "seconds", "Seconds": "seconds",
"CleanSession": "Enable CleanSession flag",
"EnableRetain": "Enable Retain Flag", "EnableRetain": "Enable Retain Flag",
"EnableTls": "Enable TLS", "EnableTls": "Enable TLS",
"RootCa": "CA-Root-Certificate (default Letsencrypt):", "RootCa": "CA-Root-Certificate (default Letsencrypt):",
@ -623,6 +633,9 @@
"SaveOrder": "Save order", "SaveOrder": "Save order",
"DeleteInverter": "Delete inverter", "DeleteInverter": "Delete inverter",
"EditInverter": "Edit inverter", "EditInverter": "Edit inverter",
"General": "General",
"String": "String",
"Advanced": "Advanced",
"InverterSerial": "Inverter Serial:", "InverterSerial": "Inverter Serial:",
"InverterName": "Inverter Name:", "InverterName": "Inverter Name:",
"InverterNameHint": "Here you can specify a custom name for your inverter.", "InverterNameHint": "Here you can specify a custom name for your inverter.",
@ -638,6 +651,12 @@
"StringYtOffset": "Yield total offset string {num}:", "StringYtOffset": "Yield total offset string {num}:",
"StringYtOffsetHint": "This offset is applied the read yield total value from the inverter. This can be used to set the yield total of the inverter to zero if a used inverter is used. But you can still try polling data.", "StringYtOffsetHint": "This offset is applied the read yield total value from the inverter. This can be used to set the yield total of the inverter to zero if a used inverter is used. But you can still try polling data.",
"InverterHint": "*) Enter the W<sub>p</sub> of the channel to calculate irradiation.", "InverterHint": "*) Enter the W<sub>p</sub> of the channel to calculate irradiation.",
"ReachableThreshold": "Reachable Threshold:",
"ReachableThresholdHint": "Defines how many requests are allowed to fail until the inverter is treated is not reachable.",
"ZeroRuntime": "Zero runtime data",
"ZeroRuntimeHint": "Zero runtime data (no yield data) if inverter becomes unreachable.",
"ZeroDay": "Zero daily yield at midnight",
"ZeroDayHint": "This only works if the inverter is unreachable. If data is read from the inverter, it's values will be used. (Reset only occours on power cycle)",
"Cancel": "@:maintenancereboot.Cancel", "Cancel": "@:maintenancereboot.Cancel",
"Save": "@:dtuadmin.Save", "Save": "@:dtuadmin.Save",
"DeleteMsg": "Are you sure you want to delete the inverter \"{name}\" with serial number {serial}?", "DeleteMsg": "Are you sure you want to delete the inverter \"{name}\" with serial number {serial}?",

View File

@ -134,7 +134,9 @@
"Failure": "Échec", "Failure": "Échec",
"Pending": "En attente", "Pending": "En attente",
"Ok": "OK", "Ok": "OK",
"Unknown": "Inconnu" "Unknown": "Inconnu",
"ShowGridProfile": "Show Grid Profile",
"GridProfile": "Grid Profile"
}, },
"vedirecthome": { "vedirecthome": {
"SerialNumber": "Serial Number: ", "SerialNumber": "Serial Number: ",
@ -192,6 +194,12 @@
"HardwarePartNumber": "Numéro d'article matériel", "HardwarePartNumber": "Numéro d'article matériel",
"HardwareVersion": "Version du matériel" "HardwareVersion": "Version du matériel"
}, },
"gridprofile": {
"NoInfo": "@:devinfo.NoInfo",
"NoInfoLong": "@:devinfo.NoInfoLong",
"GridprofileSupport": "Support the development",
"GridprofileSupportLong": "Please see <a href=\"https://github.com/tbnobody/OpenDTU/wiki/Grid-Profile-Parser\" target=\"_blank\">here</a> for further information."
},
"systeminfo": { "systeminfo": {
"SystemInfo": "Informations sur le système", "SystemInfo": "Informations sur le système",
"VersionError": "Erreur de récupération des informations de version", "VersionError": "Erreur de récupération des informations de version",
@ -308,6 +316,7 @@
"BaseTopic": "Sujet de base", "BaseTopic": "Sujet de base",
"PublishInterval": "Intervalle de publication", "PublishInterval": "Intervalle de publication",
"Seconds": "{sec} secondes", "Seconds": "{sec} secondes",
"CleanSession": "CleanSession Flag",
"Retain": "Conserver", "Retain": "Conserver",
"Tls": "TLS", "Tls": "TLS",
"RootCertifcateInfo": "Informations sur le certificat de l'autorité de certification racine", "RootCertifcateInfo": "Informations sur le certificat de l'autorité de certification racine",
@ -474,6 +483,7 @@
"BaseTopicHint": "Sujet de base, qui sera ajouté en préambule à tous les sujets publiés (par exemple, inverter/).", "BaseTopicHint": "Sujet de base, qui sera ajouté en préambule à tous les sujets publiés (par exemple, inverter/).",
"PublishInterval": "Intervalle de publication", "PublishInterval": "Intervalle de publication",
"Seconds": "secondes", "Seconds": "secondes",
"CleanSession": "Enable CleanSession flag",
"EnableRetain": "Activation du maintien", "EnableRetain": "Activation du maintien",
"EnableTls": "Activer le TLS", "EnableTls": "Activer le TLS",
"RootCa": "Certificat CA-Root (par défaut Letsencrypt)", "RootCa": "Certificat CA-Root (par défaut Letsencrypt)",
@ -521,6 +531,9 @@
"SaveOrder": "Save order", "SaveOrder": "Save order",
"DeleteInverter": "Supprimer l'onduleur", "DeleteInverter": "Supprimer l'onduleur",
"EditInverter": "Modifier l'onduleur", "EditInverter": "Modifier l'onduleur",
"General": "Général",
"String": "Ligne",
"Advanced": "Advanced",
"InverterSerial": "Numéro de série de l'onduleur", "InverterSerial": "Numéro de série de l'onduleur",
"InverterName": "Nom de l'onduleur :", "InverterName": "Nom de l'onduleur :",
"InverterNameHint": "Ici, vous pouvez spécifier un nom personnalisé pour votre onduleur.", "InverterNameHint": "Ici, vous pouvez spécifier un nom personnalisé pour votre onduleur.",
@ -536,6 +549,12 @@
"StringYtOffset": "Décalage du rendement total de la ligne {num} :", "StringYtOffset": "Décalage du rendement total de la ligne {num} :",
"StringYtOffsetHint": "Ce décalage est appliqué à la valeur de rendement total lue sur le variateur. Il peut être utilisé pour mettre le rendement total du variateur à zéro si un variateur usagé est utilisé.", "StringYtOffsetHint": "Ce décalage est appliqué à la valeur de rendement total lue sur le variateur. Il peut être utilisé pour mettre le rendement total du variateur à zéro si un variateur usagé est utilisé.",
"InverterHint": "*) Entrez le W<sub>p</sub> du canal pour calculer l'irradiation.", "InverterHint": "*) Entrez le W<sub>p</sub> du canal pour calculer l'irradiation.",
"ReachableThreshold": "Reachable Threshold:",
"ReachableThresholdHint": "Defines how many requests are allowed to fail until the inverter is treated is not reachable.",
"ZeroRuntime": "Zero runtime data",
"ZeroRuntimeHint": "Zero runtime data (no yield data) if inverter becomes unreachable.",
"ZeroDay": "Zero daily yield at midnight",
"ZeroDayHint": "This only works if the inverter is unreachable. If data is read from the inverter, it's values will be used. (Reset only occours on power cycle)",
"Cancel": "@:maintenancereboot.Cancel", "Cancel": "@:maintenancereboot.Cancel",
"Save": "@:dtuadmin.Save", "Save": "@:dtuadmin.Save",
"DeleteMsg": "Êtes-vous sûr de vouloir supprimer l'onduleur \"{name}\" avec le numéro de série \"{serial}\" ?", "DeleteMsg": "Êtes-vous sûr de vouloir supprimer l'onduleur \"{name}\" avec le numéro de série \"{serial}\" ?",

View File

@ -0,0 +1,3 @@
export interface GridProfileStatus {
raw: Array<number>;
}

View File

@ -2,6 +2,7 @@ export interface ValueObject {
v: number; // value v: number; // value
u: string; // unit u: string; // unit
d: number; // digits d: number; // digits
max: number;
} }
export interface InverterStatistics { export interface InverterStatistics {

View File

@ -7,6 +7,7 @@ export interface MqttConfig {
mqtt_password: string; mqtt_password: string;
mqtt_topic: string; mqtt_topic: string;
mqtt_publish_interval: number; mqtt_publish_interval: number;
mqtt_clean_session: boolean;
mqtt_retain: boolean; mqtt_retain: boolean;
mqtt_tls: boolean; mqtt_tls: boolean;
mqtt_root_ca_cert: string; mqtt_root_ca_cert: string;

View File

@ -6,6 +6,7 @@ export interface MqttStatus {
mqtt_username: string; mqtt_username: string;
mqtt_topic: string; mqtt_topic: string;
mqtt_publish_interval: number; mqtt_publish_interval: number;
mqtt_clean_session: boolean;
mqtt_retain: boolean; mqtt_retain: boolean;
mqtt_tls: boolean; mqtt_tls: boolean;
mqtt_root_ca_cert_info: string; mqtt_root_ca_cert_info: string;

View File

@ -78,6 +78,14 @@
</button> </button>
</div> </div>
<div class="btn-group me-2" role="group">
<button type="button" class="btn btn-sm btn-info"
@click="onShowGridProfile(inverter.serial)" v-tooltip :title="$t('home.ShowGridProfile')">
<BIconOutlet style="font-size:24px;" />
</button>
</div>
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button v-if="inverter.events >= 0" type="button" <button v-if="inverter.events >= 0" type="button"
class="btn btn-sm btn-secondary position-relative" class="btn btn-sm btn-secondary position-relative"
@ -98,7 +106,7 @@
<template v-for="channel in Object.keys(chanType.obj).sort().reverse().map(x=>+x)" :key="channel"> <template v-for="channel in Object.keys(chanType.obj).sort().reverse().map(x=>+x)" :key="channel">
<template v-if="(chanType.name != 'DC') || <template v-if="(chanType.name != 'DC') ||
(chanType.name == 'DC' && getSumIrridiation(inverter) == 0) || (chanType.name == 'DC' && getSumIrridiation(inverter) == 0) ||
(chanType.name == 'DC' && getSumIrridiation(inverter) > 0 && chanType.obj[channel].Irradiation?.v || 0 > 0) (chanType.name == 'DC' && getSumIrridiation(inverter) > 0 && chanType.obj[channel].Irradiation?.max || 0 > 0)
"> ">
<div class="col"> <div class="col">
<InverterChannelInfo :channelData="chanType.obj[channel]" <InverterChannelInfo :channelData="chanType.obj[channel]"
@ -170,6 +178,31 @@
</div> </div>
</div> </div>
<div class="modal" id="gridProfileView" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ $t('home.GridProfile') }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="text-center" v-if="gridProfileLoading">
<div class="spinner-border" role="status">
<span class="visually-hidden">{{ $t('home.Loading') }}</span>
</div>
</div>
<GridProfile v-if="!gridProfileLoading" :gridProfileList="gridProfileList" />
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="onHideGridProfile"
data-bs-dismiss="modal">{{ $t('home.Close') }}</button>
</div>
</div>
</div>
</div>
<div class="modal" id="limitSettingView" ref="limitSettingView" tabindex="-1"> <div class="modal" id="limitSettingView" ref="limitSettingView" tabindex="-1">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
@ -324,6 +357,7 @@ import BasePage from '@/components/BasePage.vue';
import BootstrapAlert from '@/components/BootstrapAlert.vue'; import BootstrapAlert from '@/components/BootstrapAlert.vue';
import DevInfo from '@/components/DevInfo.vue'; import DevInfo from '@/components/DevInfo.vue';
import EventLog from '@/components/EventLog.vue'; import EventLog from '@/components/EventLog.vue';
import GridProfile from '@/components/GridProfile.vue';
import HintView from '@/components/HintView.vue'; import HintView from '@/components/HintView.vue';
import InverterChannelInfo from "@/components/InverterChannelInfo.vue"; import InverterChannelInfo from "@/components/InverterChannelInfo.vue";
import InverterTotalInfo from '@/components/InverterTotalInfo.vue'; import InverterTotalInfo from '@/components/InverterTotalInfo.vue';
@ -332,6 +366,7 @@ import HuaweiView from '@/components/HuaweiView.vue'
import BatteryView from '@/components/BatteryView.vue' import BatteryView from '@/components/BatteryView.vue'
import type { DevInfoStatus } from '@/types/DevInfoStatus'; import type { DevInfoStatus } from '@/types/DevInfoStatus';
import type { EventlogItems } from '@/types/EventlogStatus'; import type { EventlogItems } from '@/types/EventlogStatus';
import type { GridProfileStatus } from '@/types/GridProfileStatus';
import type { LimitConfig } from '@/types/LimitConfig'; import type { LimitConfig } from '@/types/LimitConfig';
import type { LimitStatus } from '@/types/LimitStatus'; import type { LimitStatus } from '@/types/LimitStatus';
import type { Inverter, LiveData } from '@/types/LiveDataStatus'; import type { Inverter, LiveData } from '@/types/LiveDataStatus';
@ -343,6 +378,7 @@ import {
BIconCpu, BIconCpu,
BIconExclamationCircleFill, BIconExclamationCircleFill,
BIconJournalText, BIconJournalText,
BIconOutlet,
BIconPower, BIconPower,
BIconSpeedometer, BIconSpeedometer,
BIconToggleOff, BIconToggleOff,
@ -357,6 +393,7 @@ export default defineComponent({
BootstrapAlert, BootstrapAlert,
DevInfo, DevInfo,
EventLog, EventLog,
GridProfile,
HintView, HintView,
InverterChannelInfo, InverterChannelInfo,
InverterTotalInfo, InverterTotalInfo,
@ -365,6 +402,7 @@ export default defineComponent({
BIconCpu, BIconCpu,
BIconExclamationCircleFill, BIconExclamationCircleFill,
BIconJournalText, BIconJournalText,
BIconOutlet,
BIconPower, BIconPower,
BIconSpeedometer, BIconSpeedometer,
BIconToggleOff, BIconToggleOff,
@ -390,6 +428,9 @@ export default defineComponent({
devInfoView: {} as bootstrap.Modal, devInfoView: {} as bootstrap.Modal,
devInfoList: {} as DevInfoStatus, devInfoList: {} as DevInfoStatus,
devInfoLoading: true, devInfoLoading: true,
gridProfileView: {} as bootstrap.Modal,
gridProfileList: {} as GridProfileStatus,
gridProfileLoading: true,
limitSettingView: {} as bootstrap.Modal, limitSettingView: {} as bootstrap.Modal,
limitSettingLoading: true, limitSettingLoading: true,
@ -430,6 +471,7 @@ export default defineComponent({
mounted() { mounted() {
this.eventLogView = new bootstrap.Modal('#eventView'); this.eventLogView = new bootstrap.Modal('#eventView');
this.devInfoView = new bootstrap.Modal('#devInfoView'); this.devInfoView = new bootstrap.Modal('#devInfoView');
this.gridProfileView = new bootstrap.Modal('#gridProfileView');
this.limitSettingView = new bootstrap.Modal('#limitSettingView'); this.limitSettingView = new bootstrap.Modal('#limitSettingView');
this.powerSettingView = new bootstrap.Modal('#powerSettingView'); this.powerSettingView = new bootstrap.Modal('#powerSettingView');
@ -494,9 +536,15 @@ export default defineComponent({
this.socket.onmessage = (event) => { this.socket.onmessage = (event) => {
console.log(event); console.log(event);
if (event.data != "{}") {
this.liveData = JSON.parse(event.data); this.liveData = JSON.parse(event.data);
this.dataLoading = false; this.dataLoading = false;
this.heartCheck(); // Reset heartbeat detection this.heartCheck(); // Reset heartbeat detection
} else {
// Sometimes it does not recover automatically so have to force a reconnect
this.closeSocket();
this.heartCheck(10); // Reconnect faster
}
}; };
this.socket.onopen = function (event) { this.socket.onopen = function (event) {
@ -519,7 +567,7 @@ export default defineComponent({
}, 1000); }, 1000);
}, },
// Send heartbeat packets regularly * 59s Send a heartbeat // Send heartbeat packets regularly * 59s Send a heartbeat
heartCheck() { heartCheck(duration: number = 59) {
this.heartInterval && clearTimeout(this.heartInterval); this.heartInterval && clearTimeout(this.heartInterval);
this.heartInterval = setInterval(() => { this.heartInterval = setInterval(() => {
if (this.socket.readyState === 1) { if (this.socket.readyState === 1) {
@ -528,7 +576,7 @@ export default defineComponent({
} else { } else {
this.initSocket(); // Breakpoint reconnection 5 Time this.initSocket(); // Breakpoint reconnection 5 Time
} }
}, 59 * 1000); }, duration * 1000);
}, },
/** To break off websocket Connect */ /** To break off websocket Connect */
closeSocket() { closeSocket() {
@ -565,6 +613,20 @@ export default defineComponent({
this.devInfoView.show(); this.devInfoView.show();
}, },
onHideGridProfile() {
this.devInfoView.hide();
},
onShowGridProfile(serial: number) {
this.gridProfileLoading = true;
fetch("/api/gridprofile/status?inv=" + serial, { headers: authHeader() })
.then((response) => handleResponse(response, this.$emitter, this.$router))
.then((data) => {
this.gridProfileList = data;
this.gridProfileLoading = false;
});
this.gridProfileView.show();
},
onHideLimitSettings() { onHideLimitSettings() {
this.showAlertLimit = false; this.showAlertLimit = false;
}, },
@ -689,7 +751,7 @@ export default defineComponent({
getSumIrridiation(inv: Inverter): number { getSumIrridiation(inv: Inverter): number {
let total = 0; let total = 0;
Object.keys(inv.DC).forEach((key) => { Object.keys(inv.DC).forEach((key) => {
total += inv.DC[key as unknown as number].Irradiation?.v || 0; total += inv.DC[key as unknown as number].Irradiation?.max || 0;
}); });
return total; return total;
} }

View File

@ -80,6 +80,20 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form> <form>
<nav>
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<button class="nav-link active" id="nav-general-tab" data-bs-toggle="tab" data-bs-target="#nav-general"
type="button" role="tab" aria-controls="nav-general" aria-selected="true">{{
$t('inverteradmin.General')
}}</button>
<button class="nav-link" id="nav-string-tab" data-bs-toggle="tab" data-bs-target="#nav-string"
type="button" role="tab" aria-controls="nav-string">{{ $t('inverteradmin.String') }}</button>
<button class="nav-link" id="nav-advanced-tab" data-bs-toggle="tab" data-bs-target="#nav-advanced"
type="button" role="tab" aria-controls="nav-advanced">{{ $t('inverteradmin.Advanced') }}</button>
</div>
</nav>
<div class="tab-content" id="nav-tabContent">
<div class="tab-pane fade show active" id="nav-general" role="tabpanel" aria-labelledby="nav-general-tab" tabindex="0">
<div class="mb-3"> <div class="mb-3">
<label for="inverter-serial" class="col-form-label"> <label for="inverter-serial" class="col-form-label">
{{ $t('inverteradmin.InverterSerial') }} {{ $t('inverteradmin.InverterSerial') }}
@ -108,7 +122,9 @@
<div class="alert alert-secondary mt-3" role="alert" v-html="$t('inverteradmin.StatusHint')"></div> <div class="alert alert-secondary mt-3" role="alert" v-html="$t('inverteradmin.StatusHint')"></div>
</CardElement> </CardElement>
</div> </div>
</div>
<div class="tab-pane fade show" id="nav-string" role="tabpanel" aria-labelledby="nav-string-tab" tabindex="0">
<div v-for="(ch, index) in selectedInverterData.channel" :key="`${index}`"> <div v-for="(ch, index) in selectedInverterData.channel" :key="`${index}`">
<div class="row g-2"> <div class="row g-2">
<div class="col-md"> <div class="col-md">
@ -159,6 +175,25 @@
</div> </div>
<div :id="`inverter-customizer`" class="form-text" v-html="$t('inverteradmin.InverterHint')"> <div :id="`inverter-customizer`" class="form-text" v-html="$t('inverteradmin.InverterHint')">
</div> </div>
</div>
<div class="tab-pane fade show" id="nav-advanced" role="tabpanel" aria-labelledby="nav-advanced-tab" tabindex="0">
<InputElement :label="$t('inverteradmin.ReachableThreshold')"
v-model="selectedInverterData.reachable_threshold"
type="number" min="1" max="100"
:tooltip="$t('inverteradmin.ReachableThresholdHint')" wide />
<InputElement :label="$t('inverteradmin.ZeroRuntime')"
v-model="selectedInverterData.zero_runtime"
type="checkbox"
:tooltip="$t('inverteradmin.ZeroRuntimeHint')" wide/>
<InputElement :label="$t('inverteradmin.ZeroDay')"
v-model="selectedInverterData.zero_day"
type="checkbox"
:tooltip="$t('inverteradmin.ZeroDayHint')" wide/>
</div>
</div>
</form> </form>
</div> </div>
@ -231,6 +266,9 @@ declare interface Inverter {
poll_enable_night: boolean; poll_enable_night: boolean;
command_enable: boolean; command_enable: boolean;
command_enable_night: boolean; command_enable_night: boolean;
reachable_threshold: number;
zero_runtime: boolean;
zero_day: boolean;
channel: Array<Channel>; channel: Array<Channel>;
} }

View File

@ -53,6 +53,10 @@
type="number" min="5" max="86400" type="number" min="5" max="86400"
:postfix="$t('mqttadmin.Seconds')"/> :postfix="$t('mqttadmin.Seconds')"/>
<InputElement :label="$t('mqttadmin.CleanSession')"
v-model="mqttConfigList.mqtt_clean_session"
type="checkbox"/>
<InputElement :label="$t('mqttadmin.EnableRetain')" <InputElement :label="$t('mqttadmin.EnableRetain')"
v-model="mqttConfigList.mqtt_retain" v-model="mqttConfigList.mqtt_retain"
type="checkbox"/> type="checkbox"/>

View File

@ -36,6 +36,12 @@
<th>{{ $t('mqttinfo.PublishInterval') }}</th> <th>{{ $t('mqttinfo.PublishInterval') }}</th>
<td>{{ $t('mqttinfo.Seconds', { sec: mqttDataList.mqtt_publish_interval }) }}</td> <td>{{ $t('mqttinfo.Seconds', { sec: mqttDataList.mqtt_publish_interval }) }}</td>
</tr> </tr>
<tr>
<th>{{ $t('mqttinfo.CleanSession') }}</th>
<td>
<StatusBadge :status="mqttDataList.mqtt_clean_session" true_text="mqttinfo.Enabled" false_text="mqttinfo.Disabled" />
</td>
</tr>
<tr> <tr>
<th>{{ $t('mqttinfo.Retain') }}</th> <th>{{ $t('mqttinfo.Retain') }}</th>
<td> <td>

View File

@ -166,15 +166,15 @@
minimatch "^3.1.2" minimatch "^3.1.2"
strip-json-comments "^3.1.1" strip-json-comments "^3.1.1"
"@eslint/js@8.48.0": "@eslint/js@8.49.0":
version "8.48.0" version "8.49.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.48.0.tgz#642633964e217905436033a2bd08bf322849b7fb" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.49.0.tgz#86f79756004a97fa4df866835093f1df3d03c333"
integrity sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw== integrity sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==
"@humanwhocodes/config-array@^0.11.10": "@humanwhocodes/config-array@^0.11.11":
version "0.11.10" version "0.11.11"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844"
integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ== integrity sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==
dependencies: dependencies:
"@humanwhocodes/object-schema" "^1.2.1" "@humanwhocodes/object-schema" "^1.2.1"
debug "^4.1.1" debug "^4.1.1"
@ -190,72 +190,72 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
"@intlify/bundle-utils@^7.0.2": "@intlify/bundle-utils@^7.3.0":
version "7.0.2" version "7.3.0"
resolved "https://registry.yarnpkg.com/@intlify/bundle-utils/-/bundle-utils-7.0.2.tgz#2c92a4d815d97a160c2c662a9951a790296e2fd2" resolved "https://registry.yarnpkg.com/@intlify/bundle-utils/-/bundle-utils-7.3.0.tgz#11048e8aeede313f7492cefcf412359bd31eed76"
integrity sha512-8wbx9xhbawBFTE5LPTECiK26RRqrNS31jyWSur72ZXZZ4it5jiZTcG6eUJlNirr4+jXYio2DGY299JsGVT4cpw== integrity sha512-lcnfsLA5Dyd3TbvfoLS0ejLr1vAJYyT6VRYtE4LGNexy1ZD/GEcbXrC33fI9oQp7t2hDlHbCn2o4BBVgXaJFqg==
dependencies: dependencies:
"@intlify/message-compiler" "9.3.0-beta.24" "@intlify/message-compiler" "^9.4.0"
"@intlify/shared" "9.3.0-beta.24" "@intlify/shared" "^9.4.0"
acorn "^8.8.2" acorn "^8.8.2"
escodegen "^2.0.0" escodegen "^2.0.0"
estree-walker "^2.0.2" estree-walker "^2.0.2"
jsonc-eslint-parser "^1.0.1" jsonc-eslint-parser "^2.3.0"
magic-string "^0.30.0" magic-string "^0.30.0"
mlly "^1.2.0" mlly "^1.2.0"
source-map-js "^1.0.1" source-map-js "^1.0.1"
yaml-eslint-parser "^0.3.2" yaml-eslint-parser "^1.2.2"
"@intlify/core-base@9.2.2": "@intlify/core-base@9.3.0":
version "9.2.2" version "9.3.0"
resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.2.2.tgz#5353369b05cc9fe35cab95fe20afeb8a4481f939" resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.3.0.tgz#fa42e32f89f06d421772fcedeec7dfba845ba98f"
integrity sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA== integrity sha512-SRzn8TMnPZ6MY8OFrgouRq4DGaf01SHcJEF6FglYFYvRkgPzziEcQe+v2PD+O5lUp/rJafP4dabm1CmsVAA7rA==
dependencies: dependencies:
"@intlify/devtools-if" "9.2.2" "@intlify/devtools-if" "9.3.0"
"@intlify/message-compiler" "9.2.2" "@intlify/message-compiler" "9.3.0"
"@intlify/shared" "9.2.2" "@intlify/shared" "9.3.0"
"@intlify/vue-devtools" "9.2.2" "@intlify/vue-devtools" "9.3.0"
"@intlify/devtools-if@9.2.2": "@intlify/devtools-if@9.3.0":
version "9.2.2" version "9.3.0"
resolved "https://registry.yarnpkg.com/@intlify/devtools-if/-/devtools-if-9.2.2.tgz#b13d9ac4b4e2fe6d2e7daa556517a8061fe8bd39" resolved "https://registry.yarnpkg.com/@intlify/devtools-if/-/devtools-if-9.3.0.tgz#ae5a4d3577e05577e65bb6abbb0506d8af9309f6"
integrity sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg== integrity sha512-5aKZnqj0Ff4dfwBX2Oo+MheVs00CBnC0RzWK26aT2M4AF0cxdFLOJAs51/eHT01jmzrxSvfBMjdArUWHwgetfg==
dependencies: dependencies:
"@intlify/shared" "9.2.2" "@intlify/shared" "9.3.0"
"@intlify/message-compiler@9.2.2": "@intlify/message-compiler@9.3.0":
version "9.2.2" version "9.3.0"
resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.2.2.tgz#e42ab6939b8ae5b3d21faf6a44045667a18bba1c" resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.3.0.tgz#203366fe7448255731e951507c4d79093bf898b1"
integrity sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA== integrity sha512-D8tSJEhTCSFcCzkThjE4Sbk1tIdvzkYa1FaVIzUtZ8hKPATvokNrOiDw1i/h671m8A80l9Ywq594i/LPTB6EJA==
dependencies: dependencies:
"@intlify/shared" "9.2.2" "@intlify/shared" "9.3.0"
source-map "0.6.1"
"@intlify/message-compiler@9.3.0-beta.24":
version "9.3.0-beta.24"
resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.3.0-beta.24.tgz#0f1e2cbc4ac223be67b61dbaaac0d9eac8b0decb"
integrity sha512-prhHATkgp0mpPqoVgiAtLmUc1JMvs8fMH6w53AVEBn+VF87dLhzanfmWY5FoZWORG51ag54gBDBOoM/VFv3m3A==
dependencies:
"@intlify/shared" "9.3.0-beta.24"
source-map-js "^1.0.2" source-map-js "^1.0.2"
"@intlify/shared@9.2.2": "@intlify/message-compiler@^9.4.0":
version "9.2.2" version "9.4.0"
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.2.2.tgz#5011be9ca2b4ab86f8660739286e2707f9abb4a5" resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.4.0.tgz#c11fceb4cc07dfe6a4b3066ca6e9a9e44e897f23"
integrity sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q== integrity sha512-EdjqOH3bQqEjZcUOwo90wuW4ZuFr41FDxVni6WNKS0V0myKgwz9EuvLDcnEbjJMIX8vKAWPjWfnSTt5fMwKHLA==
"@intlify/shared@9.3.0-beta.24":
version "9.3.0-beta.24"
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.3.0-beta.24.tgz#23e08af9fc904fe3ef896786f9e659da6bb567b5"
integrity sha512-AKxJ8s7eKIQWkNaf4wyyoLRwf4puCuQgjSChlDJm5JBEt6T8HGgnYTJLRXu6LD/JACn3Qwu6hM/XRX1c9yvjmQ==
"@intlify/unplugin-vue-i18n@^0.12.3":
version "0.12.3"
resolved "https://registry.yarnpkg.com/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-0.12.3.tgz#fae7d92d3e7bfe9e710fb332b28d22e0f8d999f2"
integrity sha512-0riPtSfTM58JmGNMmJho/aHD2z3K24BESYAmkLvKlo61/LbaPvnjYU1DbSbJEm6bSjE2oEjUj+di3QaYxXei/w==
dependencies: dependencies:
"@intlify/bundle-utils" "^7.0.2" "@intlify/shared" "9.4.0"
"@intlify/shared" "9.3.0-beta.24" source-map-js "^1.0.2"
"@intlify/shared@9.3.0":
version "9.3.0"
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.3.0.tgz#8875120f1e6826be0e99b623e2618167402bfe41"
integrity sha512-MMGRz6zWxtz7rHtxIIdnyb8SYOIaaseN1IvUhAEs9tOW4u77RD4DFp4qgPXesp2Gxo/5QitH9kwSs0jnxGUNEw==
"@intlify/shared@9.4.0", "@intlify/shared@^9.4.0":
version "9.4.0"
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.4.0.tgz#4a78d462fc82433db900981e12eb5b1aae3d6085"
integrity sha512-AFqymip2kToqA0B6KZPg5jSrdcVHoli9t/VhGKE2iiMq9utFuMoGdDC/JOCIZgwxo6aXAk86QyU2XtzEoMuZ6A==
"@intlify/unplugin-vue-i18n@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-1.2.0.tgz#ca4ac742140f1eaf4668c9530db5db23069eb604"
integrity sha512-AzjlMZKj9DG10ICDR2VWjfJudHJ1XK2GNSHxw+VmOUYpUm4S0L/a7OAdvgnuY7S4plk1VnPdT4YilpZk+uQcqg==
dependencies:
"@intlify/bundle-utils" "^7.3.0"
"@intlify/shared" "^9.4.0"
"@rollup/pluginutils" "^5.0.2" "@rollup/pluginutils" "^5.0.2"
"@vue/compiler-sfc" "^3.2.47" "@vue/compiler-sfc" "^3.2.47"
debug "^4.3.3" debug "^4.3.3"
@ -267,13 +267,13 @@
source-map-js "^1.0.2" source-map-js "^1.0.2"
unplugin "^1.1.0" unplugin "^1.1.0"
"@intlify/vue-devtools@9.2.2": "@intlify/vue-devtools@9.3.0":
version "9.2.2" version "9.3.0"
resolved "https://registry.yarnpkg.com/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz#b95701556daf7ebb3a2d45aa3ae9e6415aed8317" resolved "https://registry.yarnpkg.com/@intlify/vue-devtools/-/vue-devtools-9.3.0.tgz#f3137319fadaa92686de11be5ef28209dc828455"
integrity sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg== integrity sha512-kEaxIz1VEgsz2q5RhoS+fBGTkXr/4+pxmK9mN14+speVGb82HPRntKBmz0GO18I1JisD4Z0vAva+KCTHGeAqbQ==
dependencies: dependencies:
"@intlify/core-base" "9.2.2" "@intlify/core-base" "9.3.0"
"@intlify/shared" "9.2.2" "@intlify/shared" "9.3.0"
"@jridgewell/gen-mapping@^0.3.0": "@jridgewell/gen-mapping@^0.3.0":
version "0.3.2" version "0.3.2"
@ -382,10 +382,10 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
"@types/node@^20.5.7": "@types/node@^20.5.9":
version "20.5.7" version "20.5.9"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.7.tgz#4b8ecac87fbefbc92f431d09c30e176fc0a7c377" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.9.tgz#a70ec9d8fa0180a314c3ede0e20ea56ff71aed9a"
integrity sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA== integrity sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==
"@types/semver@^7.3.12": "@types/semver@^7.3.12":
version "7.3.13" version "7.3.13"
@ -486,10 +486,10 @@
"@typescript-eslint/types" "5.59.1" "@typescript-eslint/types" "5.59.1"
eslint-visitor-keys "^3.3.0" eslint-visitor-keys "^3.3.0"
"@vitejs/plugin-vue@^4.3.3": "@vitejs/plugin-vue@^4.3.4":
version "4.3.3" version "4.3.4"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.3.3.tgz#3b2337f64495f95cfea5b1497d2d3f4a0b3382b2" resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.3.4.tgz#a289dff38e01949fe7be581d5542cabaeb961dec"
integrity sha512-ssxyhIAZqB0TrpUg6R0cBpCuMk9jTIlO1GNSKKQD6S8VjnXi6JXKfUXjSsxey9IwQiaRGsO1WnW9Rkl1L6AJVw== integrity sha512-ciXNIHKPriERBisHFBvnTbfKa6r9SAesOYXeGDzgegcvy9Q4xdScSHAmKbNT0M3O0S9LKhIf5/G+UYG4NnnzYw==
"@volar/language-core@1.10.0", "@volar/language-core@~1.10.0": "@volar/language-core@1.10.0", "@volar/language-core@~1.10.0":
version "1.10.0" version "1.10.0"
@ -614,11 +614,6 @@
"@vue/compiler-dom" "3.3.4" "@vue/compiler-dom" "3.3.4"
"@vue/shared" "3.3.4" "@vue/shared" "3.3.4"
"@vue/devtools-api@^6.2.1":
version "6.4.5"
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.4.5.tgz#d54e844c1adbb1e677c81c665ecef1a2b4bb8380"
integrity sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ==
"@vue/devtools-api@^6.5.0": "@vue/devtools-api@^6.5.0":
version "6.5.0" version "6.5.0"
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07" resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07"
@ -633,10 +628,10 @@
"@typescript-eslint/parser" "^5.59.1" "@typescript-eslint/parser" "^5.59.1"
vue-eslint-parser "^9.1.1" vue-eslint-parser "^9.1.1"
"@vue/language-core@1.8.8": "@vue/language-core@1.8.10":
version "1.8.8" version "1.8.10"
resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-1.8.8.tgz#5a8aa8363f4dfacdfcd7808a9926744d7c310ae6" resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-1.8.10.tgz#e37409ac8686f30963a00662d51bf6d07c599ca9"
integrity sha512-i4KMTuPazf48yMdYoebTkgSOJdFraE4pQf0B+FTOFkbB+6hAfjrSou/UmYWRsWyZV6r4Rc6DDZdI39CJwL0rWw== integrity sha512-db8PtM4ZZr7SYNH30XpKxUYnUBYaTvcuJ4c2whKK04fuAjbtjAIZ2al5GzGEfUlesmvkpgdbiSviRXUxgD9Omw==
dependencies: dependencies:
"@volar/language-core" "~1.10.0" "@volar/language-core" "~1.10.0"
"@volar/source-map" "~1.10.0" "@volar/source-map" "~1.10.0"
@ -728,23 +723,23 @@
resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.4.0.tgz#f01e2f6089b5098136fb084a0dd0cdd4533b72b0" resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.4.0.tgz#f01e2f6089b5098136fb084a0dd0cdd4533b72b0"
integrity sha512-CPuIReonid9+zOG/CGTT05FXrPYATEqoDGNrEaqS4hwcw5BUNM2FguC0mOwJD4Jr16UpRVl9N0pY3P+srIbqmg== integrity sha512-CPuIReonid9+zOG/CGTT05FXrPYATEqoDGNrEaqS4hwcw5BUNM2FguC0mOwJD4Jr16UpRVl9N0pY3P+srIbqmg==
"@vue/typescript@1.8.8": "@vue/typescript@1.8.10":
version "1.8.8" version "1.8.10"
resolved "https://registry.yarnpkg.com/@vue/typescript/-/typescript-1.8.8.tgz#8efb375d448862134492a044f4e96afada547500" resolved "https://registry.yarnpkg.com/@vue/typescript/-/typescript-1.8.10.tgz#70e3354e67498ef2270cba38eba092b29da1a494"
integrity sha512-jUnmMB6egu5wl342eaUH236v8tdcEPXXkPgj+eI/F6JwW/lb+yAU6U07ZbQ3MVabZRlupIlPESB7ajgAGixhow== integrity sha512-vPSpTXMk4chYwvyTGjM891cKgnx2r6vtbdANOp2mRU31f4HYGyLrZBlGgiua7SaO2cLjUg8y91OipJe0t8OFhA==
dependencies: dependencies:
"@volar/typescript" "~1.10.0" "@volar/typescript" "~1.10.0"
"@vue/language-core" "1.8.8" "@vue/language-core" "1.8.10"
acorn-jsx@^5.2.0, acorn-jsx@^5.3.2: acorn-jsx@^5.3.2:
version "5.3.2" version "5.3.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn@^7.1.1, acorn@^7.4.1: acorn@^8.5.0, acorn@^8.9.0:
version "7.4.1" version "8.10.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
acorn@^8.8.0: acorn@^8.8.0:
version "8.8.0" version "8.8.0"
@ -756,11 +751,6 @@ acorn@^8.8.2:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
acorn@^8.9.0:
version "8.10.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
ajv@^6.12.4: ajv@^6.12.4:
version "6.12.6" version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
@ -1141,17 +1131,10 @@ eslint-scope@^7.2.2:
esrecurse "^4.3.0" esrecurse "^4.3.0"
estraverse "^5.2.0" estraverse "^5.2.0"
eslint-utils@^2.1.0: eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.4.3:
version "2.1.0" version "3.4.3"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
dependencies:
eslint-visitor-keys "^1.1.0"
eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
eslint-visitor-keys@^3.3.0: eslint-visitor-keys@^3.3.0:
version "3.3.0" version "3.3.0"
@ -1163,21 +1146,16 @@ eslint-visitor-keys@^3.4.1:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994"
integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
eslint-visitor-keys@^3.4.3: eslint@^8.49.0:
version "3.4.3" version "8.49.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.49.0.tgz#09d80a89bdb4edee2efcf6964623af1054bf6d42"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== integrity sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==
eslint@^8.48.0:
version "8.48.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.48.0.tgz#bf9998ba520063907ba7bfe4c480dc8be03c2155"
integrity sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==
dependencies: dependencies:
"@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.6.1" "@eslint-community/regexpp" "^4.6.1"
"@eslint/eslintrc" "^2.1.2" "@eslint/eslintrc" "^2.1.2"
"@eslint/js" "8.48.0" "@eslint/js" "8.49.0"
"@humanwhocodes/config-array" "^0.11.10" "@humanwhocodes/config-array" "^0.11.11"
"@humanwhocodes/module-importer" "^1.0.1" "@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8" "@nodelib/fs.walk" "^1.2.8"
ajv "^6.12.4" ajv "^6.12.4"
@ -1211,14 +1189,14 @@ eslint@^8.48.0:
strip-ansi "^6.0.1" strip-ansi "^6.0.1"
text-table "^0.2.0" text-table "^0.2.0"
espree@^6.0.0: espree@^9.0.0, espree@^9.6.1:
version "6.2.1" version "9.6.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
dependencies: dependencies:
acorn "^7.1.1" acorn "^8.9.0"
acorn-jsx "^5.2.0" acorn-jsx "^5.3.2"
eslint-visitor-keys "^1.1.0" eslint-visitor-keys "^3.4.1"
espree@^9.3.1: espree@^9.3.1:
version "9.3.3" version "9.3.3"
@ -1238,15 +1216,6 @@ espree@^9.6.0:
acorn-jsx "^5.3.2" acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.4.1" 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: esprima@^4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
@ -1732,16 +1701,15 @@ json5@^2.2.3:
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
jsonc-eslint-parser@^1.0.1: jsonc-eslint-parser@^2.3.0:
version "1.4.1" version "2.3.0"
resolved "https://registry.yarnpkg.com/jsonc-eslint-parser/-/jsonc-eslint-parser-1.4.1.tgz#8cbe99f6f5199acbc5a823c4c0b6135411027fa6" resolved "https://registry.yarnpkg.com/jsonc-eslint-parser/-/jsonc-eslint-parser-2.3.0.tgz#7c2de97d01bff7227cbef2f25d1025d42a36198b"
integrity sha512-hXBrvsR1rdjmB2kQmUjf1rEIa+TqHBGMge8pwi++C+Si1ad7EjZrJcpgwym+QGK/pqTx+K7keFAtLlVNdLRJOg== integrity sha512-9xZPKVYp9DxnM3sd1yAsh/d59iIaswDkai8oTxbursfKYbg/ibjX0IzFt35+VZ8iEW453TVTXztnRvYUQlAfUQ==
dependencies: dependencies:
acorn "^7.4.1" acorn "^8.5.0"
eslint-utils "^2.1.0" eslint-visitor-keys "^3.0.0"
eslint-visitor-keys "^1.3.0" espree "^9.0.0"
espree "^6.0.0" semver "^7.3.5"
semver "^6.3.0"
jsonc-parser@^3.2.0: jsonc-parser@^3.2.0:
version "3.2.0" version "3.2.0"
@ -1795,7 +1763,7 @@ lodash.merge@^4.6.2:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash@^4.17.20, lodash@^4.17.21: lodash@^4.17.21:
version "4.17.21" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -2211,10 +2179,10 @@ safe-regex-test@^1.0.0:
get-intrinsic "^1.1.3" get-intrinsic "^1.1.3"
is-regex "^1.1.4" is-regex "^1.1.4"
sass@^1.64.2: sass@^1.66.1:
version "1.64.2" version "1.66.1"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.64.2.tgz#0d9805ad6acf31c59c3acc725fcfb91b7fcc6909" resolved "https://registry.yarnpkg.com/sass/-/sass-1.66.1.tgz#04b51c4671e4650aa393740e66a4e58b44d055b1"
integrity sha512-TnDlfc+CRnUAgLO9D8cQLFu/GIjJIzJCGkE7o4ekIGQOH7T3GetiRR/PsTWJUHhkzcSPrARkPI+gNWn5alCzDg== integrity sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA==
dependencies: dependencies:
chokidar ">=3.0.0 <4.0.0" chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0" immutable "^4.0.0"
@ -2225,10 +2193,12 @@ sass@^1.64.2:
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
semver@^6.3.0: semver@^7.3.5, semver@^7.5.4:
version "6.3.0" version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
dependencies:
lru-cache "^6.0.0"
semver@^7.3.6, semver@^7.3.7: semver@^7.3.6, semver@^7.3.7:
version "7.3.7" version "7.3.7"
@ -2244,13 +2214,6 @@ semver@^7.3.8:
dependencies: dependencies:
lru-cache "^6.0.0" 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: shebang-command@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@ -2312,7 +2275,7 @@ source-map-support@~0.5.20:
buffer-from "^1.0.0" buffer-from "^1.0.0"
source-map "^0.6.0" source-map "^0.6.0"
source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
version "0.6.1" version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
@ -2416,10 +2379,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" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
terser@^5.19.2: terser@^5.19.4:
version "5.19.2" version "5.19.4"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.2.tgz#bdb8017a9a4a8de4663a7983f45c506534f9234e" resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.4.tgz#941426fa482bf9b40a0308ab2b3cd0cf7c775ebd"
integrity sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA== integrity sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g==
dependencies: dependencies:
"@jridgewell/source-map" "^0.3.3" "@jridgewell/source-map" "^0.3.3"
acorn "^8.8.2" acorn "^8.8.2"
@ -2575,15 +2538,15 @@ vue-eslint-parser@^9.3.1:
lodash "^4.17.21" lodash "^4.17.21"
semver "^7.3.6" semver "^7.3.6"
vue-i18n@^9.2.2: vue-i18n@^9.3.0:
version "9.2.2" version "9.3.0"
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.2.2.tgz#aeb49d9424923c77e0d6441e3f21dafcecd0e666" resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.3.0.tgz#d85551098ab377e4237a101c77bfc05fd4887ff5"
integrity sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ== integrity sha512-+2L+ae/e4+fixhjym3lgzGCGQG8wVGlGrDHzjfdgUudheHvbVHu5i6tn6FF+buH75UFA7T5ZO2ZO7zrh6CzuaA==
dependencies: dependencies:
"@intlify/core-base" "9.2.2" "@intlify/core-base" "9.3.0"
"@intlify/shared" "9.2.2" "@intlify/shared" "9.3.0"
"@intlify/vue-devtools" "9.2.2" "@intlify/vue-devtools" "9.3.0"
"@vue/devtools-api" "^6.2.1" "@vue/devtools-api" "^6.5.0"
vue-router@^4.2.4: vue-router@^4.2.4:
version "4.2.4" version "4.2.4"
@ -2600,13 +2563,13 @@ vue-template-compiler@^2.7.14:
de-indent "^1.0.2" de-indent "^1.0.2"
he "^1.2.0" he "^1.2.0"
vue-tsc@^1.8.8: vue-tsc@^1.8.10:
version "1.8.8" version "1.8.10"
resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.8.8.tgz#67317693eb2ef6747e89e6d834eeb6d2deb8871d" resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.8.10.tgz#7a5253b078a4728d90286f7bef42108e39a191e7"
integrity sha512-bSydNFQsF7AMvwWsRXD7cBIXaNs/KSjvzWLymq/UtKE36697sboX4EccSHFVxvgdBlI1frYPc/VMKJNB7DFeDQ== integrity sha512-ptpTFFDoHQgkWJF7i5iERxooiQzOGtG1uKTfmAUuS3qPuSQGq+Ky/S8BFHhnFGwoOxq/PjmGN2QSZEfg1rtzQA==
dependencies: dependencies:
"@vue/language-core" "1.8.8" "@vue/language-core" "1.8.10"
"@vue/typescript" "1.8.8" "@vue/typescript" "1.8.10"
semver "^7.3.8" semver "^7.3.8"
vue@^3.3.4: vue@^3.3.4:
@ -2675,19 +2638,19 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yaml-eslint-parser@^0.3.2: yaml-eslint-parser@^1.2.2:
version "0.3.2" version "1.2.2"
resolved "https://registry.yarnpkg.com/yaml-eslint-parser/-/yaml-eslint-parser-0.3.2.tgz#c7f5f3904f1c06ad55dc7131a731b018426b4898" resolved "https://registry.yarnpkg.com/yaml-eslint-parser/-/yaml-eslint-parser-1.2.2.tgz#1a9673ebe254328cfc2fa99f297f6d8c9364ccd8"
integrity sha512-32kYO6kJUuZzqte82t4M/gB6/+11WAuHiEnK7FreMo20xsCKPeFH5tDBU7iWxR7zeJpNnMXfJyXwne48D0hGrg== integrity sha512-pEwzfsKbTrB8G3xc/sN7aw1v6A6c/pKxLAkjclnAyo5g5qOh6eL9WGu0o3cSDQZKrTNk4KL4lQSwZW+nBkANEg==
dependencies: dependencies:
eslint-visitor-keys "^1.3.0" eslint-visitor-keys "^3.0.0"
lodash "^4.17.20" lodash "^4.17.21"
yaml "^1.10.0" yaml "^2.0.0"
yaml@^1.10.0: yaml@^2.0.0:
version "1.10.2" version "2.3.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.2.tgz#f522db4313c671a0ca963a75670f1c12ea909144"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== integrity sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==
yocto-queue@^0.1.0: yocto-queue@^0.1.0:
version "0.1.0" version "0.1.0"

Binary file not shown.