diff --git a/.github/workflows/cpplint.yml b/.github/workflows/cpplint.yml index 0514bb81..af5e8b79 100644 --- a/.github/workflows/cpplint.yml +++ b/.github/workflows/cpplint.yml @@ -18,4 +18,4 @@ jobs: pip install cpplint - name: Linting run: | - cpplint --repository=. --recursive --filter=-runtime/references,-readability/braces,-whitespace,-legal,-build/include ./src ./include ./lib/Hoymiles ./lib/MqttSubscribeParser ./lib/TimeoutHelper ./lib/ResetReason + cpplint --repository=. --recursive --filter=-build/c++11,-runtime/references,-readability/braces,-whitespace,-legal,-build/include ./src ./include ./lib/Hoymiles ./lib/MqttSubscribeParser ./lib/TimeoutHelper ./lib/ResetReason diff --git a/include/Configuration.h b/include/Configuration.h index da90d226..425aed54 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -61,6 +61,7 @@ struct CONFIG_T { byte WiFi_Dns2[4]; bool WiFi_Dhcp; char WiFi_Hostname[WIFI_MAX_HOSTNAME_STRLEN + 1]; + uint WiFi_ApTimeout; char Ntp_Server[NTP_MAX_SERVER_STRLEN + 1]; char Ntp_Timezone[NTP_MAX_TIMEZONE_STRLEN + 1]; @@ -70,6 +71,7 @@ struct CONFIG_T { uint8_t Ntp_SunsetType; bool Mqtt_Enabled; + char Mqtt_Hostname[MQTT_MAX_HOSTNAME_STRLEN + 1]; uint Mqtt_Port; char Mqtt_Username[MQTT_MAX_USERNAME_STRLEN + 1]; char Mqtt_Password[MQTT_MAX_PASSWORD_STRLEN + 1]; @@ -80,6 +82,18 @@ struct CONFIG_T { char Mqtt_LwtValue_Offline[MQTT_MAX_LWTVALUE_STRLEN + 1]; uint32_t Mqtt_PublishInterval; + bool Mqtt_Hass_Enabled; + bool Mqtt_Hass_Retain; + char Mqtt_Hass_Topic[MQTT_MAX_TOPIC_STRLEN + 1]; + bool Mqtt_Hass_IndividualPanels; + bool Mqtt_Hass_Expire; + + bool Mqtt_Tls; + char Mqtt_RootCaCert[MQTT_MAX_CERT_STRLEN + 1]; + bool Mqtt_TlsCertLogin; + char Mqtt_ClientCert[MQTT_MAX_CERT_STRLEN + 1]; + char Mqtt_ClientKey[MQTT_MAX_CERT_STRLEN + 1]; + INVERTER_CONFIG_T Inverter[INV_MAX_COUNT]; uint64_t Dtu_Serial; @@ -88,20 +102,6 @@ struct CONFIG_T { int8_t Dtu_CmtPaLevel; uint32_t Dtu_CmtFrequency; - bool Mqtt_Hass_Enabled; - bool Mqtt_Hass_Retain; - char Mqtt_Hass_Topic[MQTT_MAX_TOPIC_STRLEN + 1]; - bool Mqtt_Hass_IndividualPanels; - bool Mqtt_Tls; - char Mqtt_RootCaCert[MQTT_MAX_CERT_STRLEN + 1]; - bool Mqtt_TlsCertLogin; - char Mqtt_ClientCert[MQTT_MAX_CERT_STRLEN + 1]; - char Mqtt_ClientKey[MQTT_MAX_CERT_STRLEN + 1]; - - char Mqtt_Hostname[MQTT_MAX_HOSTNAME_STRLEN + 1]; - - bool Mqtt_Hass_Expire; - char Security_Password[WIFI_MAX_PASSWORD_STRLEN + 1]; bool Security_AllowReadonly; diff --git a/include/Datastore.h b/include/Datastore.h index 84923195..fd42174d 100644 --- a/include/Datastore.h +++ b/include/Datastore.h @@ -2,12 +2,10 @@ #pragma once #include -#include -#include +#include class DatastoreClass { public: - DatastoreClass(); void init(); void loop(); @@ -61,7 +59,7 @@ public: private: TimeoutHelper _updateTimeout; - SemaphoreHandle_t _xSemaphore; + std::mutex _mutex; float _totalAcYieldTotalEnabled = 0; float _totalAcYieldDayEnabled = 0; diff --git a/include/MessageOutput.h b/include/MessageOutput.h index a47b0278..7c56a6f4 100644 --- a/include/MessageOutput.h +++ b/include/MessageOutput.h @@ -4,14 +4,15 @@ #include #include #include +#include #define BUFFER_SIZE 500 class MessageOutputClass : public Print { public: - MessageOutputClass(); void loop(); - size_t write(uint8_t c); + size_t write(uint8_t c) override; + size_t write(const uint8_t *buffer, size_t size) override; void register_ws_output(AsyncWebSocket* output); private: @@ -21,7 +22,7 @@ private: uint32_t _lastSend = 0; bool _forceSend = false; - SemaphoreHandle_t _lock; + std::mutex _msgLock; }; extern MessageOutputClass MessageOutput; \ No newline at end of file diff --git a/include/MqttSettings.h b/include/MqttSettings.h index e68c230f..68b12c9c 100644 --- a/include/MqttSettings.h +++ b/include/MqttSettings.h @@ -5,6 +5,7 @@ #include #include #include +#include class MqttSettingsClass { public: @@ -37,6 +38,7 @@ private: String willTopic; Ticker mqttReconnectTimer; MqttSubscribeParser _mqttSubscribeParser; + std::mutex _clientLock; }; extern MqttSettingsClass MqttSettings; \ No newline at end of file diff --git a/include/NetworkSettings.h b/include/NetworkSettings.h index 507167b0..87c8dce9 100644 --- a/include/NetworkSettings.h +++ b/include/NetworkSettings.h @@ -64,6 +64,7 @@ private: bool adminEnabled = true; bool forceDisconnection = false; int adminTimeoutCounter = 0; + int adminTimeoutCounterMax = 0; int connectTimeoutTimer = 0; int connectRedoTimer = 0; uint32_t lastTimerCall = 0; diff --git a/include/WebApi.h b/include/WebApi.h index bcad724f..8b98ab37 100644 --- a/include/WebApi.h +++ b/include/WebApi.h @@ -2,11 +2,11 @@ #pragma once #include "WebApi_config.h" +#include "WebApi_device.h" #include "WebApi_devinfo.h" #include "WebApi_dtu.h" #include "WebApi_eventlog.h" #include "WebApi_firmware.h" -#include "WebApi_device.h" #include "WebApi_inverter.h" #include "WebApi_limit.h" #include "WebApi_maintenance.h" diff --git a/include/WebApi_errors.h b/include/WebApi_errors.h index 8107840d..ac91941e 100644 --- a/include/WebApi_errors.h +++ b/include/WebApi_errors.h @@ -63,6 +63,7 @@ enum WebApiError { NetworkGatewayInvalid, NetworkDns1Invalid, NetworkDns2Invalid, + NetworkApTimeoutInvalid, NtpBase = 9000, NtpServerLength, diff --git a/include/defaults.h b/include/defaults.h index 7420dd19..1df7d774 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -9,10 +9,10 @@ #define ACCESS_POINT_NAME "OpenDTU-" #define ACCESS_POINT_PASSWORD "openDTU42" +#define ACCESS_POINT_TIMEOUT 3; #define AUTH_USERNAME "admin" #define SECURITY_ALLOW_READONLY true -#define ADMIN_TIMEOUT 180 #define WIFI_RECONNECT_TIMEOUT 15 #define WIFI_RECONNECT_REDO_TIMEOUT 600 @@ -25,11 +25,11 @@ #define NTP_TIMEZONEDESCR "Europe/Berlin" #define NTP_LONGITUDE 10.4515f #define NTP_LATITUDE 51.1657f -#define NTP_SUNSETTYPE 1 +#define NTP_SUNSETTYPE 1U #define MQTT_ENABLED false #define MQTT_HOST "" -#define MQTT_PORT 1883 +#define MQTT_PORT 1883U #define MQTT_USER "" #define MQTT_PASSWORD "" #define MQTT_TOPIC "solar/" @@ -73,13 +73,13 @@ #define MQTT_LWT_TOPIC "dtu/status" #define MQTT_LWT_ONLINE "online" #define MQTT_LWT_OFFLINE "offline" -#define MQTT_PUBLISH_INTERVAL 5 +#define MQTT_PUBLISH_INTERVAL 5U -#define DTU_SERIAL 0x99978563412 -#define DTU_POLL_INTERVAL 5 -#define DTU_NRF_PA_LEVEL 0 +#define DTU_SERIAL 0x99978563412U +#define DTU_POLL_INTERVAL 5U +#define DTU_NRF_PA_LEVEL 0U #define DTU_CMT_PA_LEVEL 0 -#define DTU_CMT_FREQUENCY 865000 +#define DTU_CMT_FREQUENCY 865000U #define MQTT_HASS_ENABLED false #define MQTT_HASS_EXPIRE true @@ -91,6 +91,6 @@ #define DISPLAY_POWERSAFE true #define DISPLAY_SCREENSAVER true -#define DISPLAY_ROTATION 2 -#define DISPLAY_CONTRAST 60 -#define DISPLAY_LANGUAGE 0 \ No newline at end of file +#define DISPLAY_ROTATION 2U +#define DISPLAY_CONTRAST 60U +#define DISPLAY_LANGUAGE 0U \ No newline at end of file diff --git a/lib/Hoymiles/src/Hoymiles.h b/lib/Hoymiles/src/Hoymiles.h index 66bc0c13..a8f06550 100644 --- a/lib/Hoymiles/src/Hoymiles.h +++ b/lib/Hoymiles/src/Hoymiles.h @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include "HoymilesRadio_NRF.h" #include "HoymilesRadio_CMT.h" +#include "HoymilesRadio_NRF.h" #include "inverters/InverterAbstract.h" #include "types.h" #include diff --git a/lib/Hoymiles/src/HoymilesRadio.cpp b/lib/Hoymiles/src/HoymilesRadio.cpp index fc1ca409..4afc447a 100644 --- a/lib/Hoymiles/src/HoymilesRadio.cpp +++ b/lib/Hoymiles/src/HoymilesRadio.cpp @@ -99,7 +99,7 @@ void HoymilesRadio::handleReceivedPackage() } } else if (!_busyFlag) { // Currently in idle mode --> send packet if one is in the queue - if (!_commandQueue.empty()) { + if (!isQueueEmpty()) { CommandAbstract* cmd = _commandQueue.front().get(); auto inv = Hoymiles.getInverterBySerial(cmd->getTargetAddress()); diff --git a/lib/Hoymiles/src/HoymilesRadio.h b/lib/Hoymiles/src/HoymilesRadio.h index 21477293..fa2f6945 100644 --- a/lib/Hoymiles/src/HoymilesRadio.h +++ b/lib/Hoymiles/src/HoymilesRadio.h @@ -5,7 +5,7 @@ #include "commands/CommandAbstract.h" #include "types.h" #include -#include +#include class HoymilesRadio { public: @@ -16,11 +16,15 @@ public: bool isQueueEmpty(); bool isInitialized(); - template - T* enqueCommand() + void enqueCommand(std::shared_ptr cmd) { - _commandQueue.push(std::make_shared()); - return static_cast(_commandQueue.back().get()); + _commandQueue.push(cmd); + } + + template + std::shared_ptr prepareCommand() + { + return std::make_shared(); } protected: @@ -34,7 +38,7 @@ protected: void handleReceivedPackage(); serial_u _dtuSerial; - std::queue> _commandQueue; + ThreadSafeQueue> _commandQueue; bool _isInitialized = false; bool _busyFlag = false; diff --git a/lib/Hoymiles/src/commands/ActivePowerControlCommand.cpp b/lib/Hoymiles/src/commands/ActivePowerControlCommand.cpp index bf4b04de..78bcd55e 100644 --- a/lib/Hoymiles/src/commands/ActivePowerControlCommand.cpp +++ b/lib/Hoymiles/src/commands/ActivePowerControlCommand.cpp @@ -35,11 +35,11 @@ void ActivePowerControlCommand::setActivePowerLimit(float limit, PowerLimitContr // limit _payload[12] = (l >> 8) & 0xff; - _payload[13] = (l) & 0xff; + _payload[13] = (l)&0xff; // type _payload[14] = (type >> 8) & 0xff; - _payload[15] = (type) & 0xff; + _payload[15] = (type)&0xff; udpateCRC(CRC_SIZE); } diff --git a/lib/Hoymiles/src/commands/AlarmDataCommand.cpp b/lib/Hoymiles/src/commands/AlarmDataCommand.cpp index 46b62e7c..574e0be2 100644 --- a/lib/Hoymiles/src/commands/AlarmDataCommand.cpp +++ b/lib/Hoymiles/src/commands/AlarmDataCommand.cpp @@ -27,11 +27,13 @@ bool AlarmDataCommand::handleResponse(InverterAbstract* inverter, fragment_t fra // Move all fragments into target buffer uint8_t offs = 0; + inverter->EventLog()->beginAppendFragment(); inverter->EventLog()->clearBuffer(); for (uint8_t i = 0; i < max_fragment_id; i++) { inverter->EventLog()->appendFragment(offs, fragment[i].fragment, fragment[i].len); offs += (fragment[i].len); } + inverter->EventLog()->endAppendFragment(); inverter->EventLog()->setLastAlarmRequestSuccess(CMD_OK); inverter->EventLog()->setLastUpdate(millis()); return true; diff --git a/lib/Hoymiles/src/commands/DevInfoAllCommand.cpp b/lib/Hoymiles/src/commands/DevInfoAllCommand.cpp index 3da57f64..b175822e 100644 --- a/lib/Hoymiles/src/commands/DevInfoAllCommand.cpp +++ b/lib/Hoymiles/src/commands/DevInfoAllCommand.cpp @@ -27,11 +27,13 @@ bool DevInfoAllCommand::handleResponse(InverterAbstract* inverter, fragment_t fr // Move all fragments into target buffer uint8_t offs = 0; + inverter->DevInfo()->beginAppendFragment(); inverter->DevInfo()->clearBufferAll(); for (uint8_t i = 0; i < max_fragment_id; i++) { inverter->DevInfo()->appendFragmentAll(offs, fragment[i].fragment, fragment[i].len); offs += (fragment[i].len); } + inverter->DevInfo()->endAppendFragment(); inverter->DevInfo()->setLastUpdateAll(millis()); return true; } \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/DevInfoSimpleCommand.cpp b/lib/Hoymiles/src/commands/DevInfoSimpleCommand.cpp index 2985c9a9..09d5a467 100644 --- a/lib/Hoymiles/src/commands/DevInfoSimpleCommand.cpp +++ b/lib/Hoymiles/src/commands/DevInfoSimpleCommand.cpp @@ -27,11 +27,13 @@ bool DevInfoSimpleCommand::handleResponse(InverterAbstract* inverter, fragment_t // Move all fragments into target buffer uint8_t offs = 0; + inverter->DevInfo()->beginAppendFragment(); inverter->DevInfo()->clearBufferSimple(); for (uint8_t i = 0; i < max_fragment_id; i++) { inverter->DevInfo()->appendFragmentSimple(offs, fragment[i].fragment, fragment[i].len); offs += (fragment[i].len); } + inverter->DevInfo()->endAppendFragment(); inverter->DevInfo()->setLastUpdateSimple(millis()); return true; } \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp index d59e1443..3f0aed36 100644 --- a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp +++ b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp @@ -40,11 +40,13 @@ bool RealTimeRunDataCommand::handleResponse(InverterAbstract* inverter, fragment // Move all fragments into target buffer uint8_t offs = 0; + inverter->Statistics()->beginAppendFragment(); inverter->Statistics()->clearBuffer(); for (uint8_t i = 0; i < max_fragment_id; i++) { inverter->Statistics()->appendFragment(offs, fragment[i].fragment, fragment[i].len); offs += (fragment[i].len); } + inverter->Statistics()->endAppendFragment(); inverter->Statistics()->resetRxFailureCount(); inverter->Statistics()->setLastUpdate(millis()); return true; diff --git a/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp b/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp index 8d310f4c..ef285469 100644 --- a/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp +++ b/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp @@ -27,11 +27,13 @@ bool SystemConfigParaCommand::handleResponse(InverterAbstract* inverter, fragmen // Move all fragments into target buffer uint8_t offs = 0; + inverter->SystemConfigPara()->beginAppendFragment(); inverter->SystemConfigPara()->clearBuffer(); for (uint8_t i = 0; i < max_fragment_id; i++) { inverter->SystemConfigPara()->appendFragment(offs, fragment[i].fragment, fragment[i].len); offs += (fragment[i].len); } + inverter->SystemConfigPara()->endAppendFragment(); inverter->SystemConfigPara()->setLastUpdateRequest(millis()); inverter->SystemConfigPara()->setLastLimitRequestSuccess(CMD_OK); return true; diff --git a/lib/Hoymiles/src/inverters/HMS_Abstract.cpp b/lib/Hoymiles/src/inverters/HMS_Abstract.cpp index 326ac839..f67ff11b 100644 --- a/lib/Hoymiles/src/inverters/HMS_Abstract.cpp +++ b/lib/Hoymiles/src/inverters/HMS_Abstract.cpp @@ -18,9 +18,10 @@ bool HMS_Abstract::sendChangeChannelRequest() return false; } - ChannelChangeCommand* cmdChannel = _radio->enqueCommand(); + auto cmdChannel = _radio->prepareCommand(); cmdChannel->setChannel(HoymilesRadio_CMT::getChannelFromFrequency(Hoymiles.getRadioCmt()->getInverterTargetFrequency())); cmdChannel->setTargetAddress(serial()); + _radio->enqueCommand(cmdChannel); return true; }; diff --git a/lib/Hoymiles/src/inverters/HMT_Abstract.cpp b/lib/Hoymiles/src/inverters/HMT_Abstract.cpp index b561ee7f..c345be97 100644 --- a/lib/Hoymiles/src/inverters/HMT_Abstract.cpp +++ b/lib/Hoymiles/src/inverters/HMT_Abstract.cpp @@ -20,9 +20,10 @@ bool HMT_Abstract::sendChangeChannelRequest() return false; } - ChannelChangeCommand* cmdChannel = _radio->enqueCommand(); + auto cmdChannel = _radio->prepareCommand(); cmdChannel->setChannel(HoymilesRadio_CMT::getChannelFromFrequency(Hoymiles.getRadioCmt()->getInverterTargetFrequency())); cmdChannel->setTargetAddress(serial()); + _radio->enqueCommand(cmdChannel); return true; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HM_Abstract.cpp b/lib/Hoymiles/src/inverters/HM_Abstract.cpp index 097fffeb..7da96929 100644 --- a/lib/Hoymiles/src/inverters/HM_Abstract.cpp +++ b/lib/Hoymiles/src/inverters/HM_Abstract.cpp @@ -29,9 +29,10 @@ bool HM_Abstract::sendStatsRequest() time_t now; time(&now); - RealTimeRunDataCommand* cmd = _radio->enqueCommand(); + auto cmd = _radio->prepareCommand(); cmd->setTime(now); cmd->setTargetAddress(serial()); + _radio->enqueCommand(cmd); return true; } @@ -60,10 +61,11 @@ bool HM_Abstract::sendAlarmLogRequest(bool force) time_t now; time(&now); - AlarmDataCommand* cmd = _radio->enqueCommand(); + auto cmd = _radio->prepareCommand(); cmd->setTime(now); cmd->setTargetAddress(serial()); EventLog()->setLastAlarmRequestSuccess(CMD_PENDING); + _radio->enqueCommand(cmd); return true; } @@ -82,13 +84,15 @@ bool HM_Abstract::sendDevInfoRequest() time_t now; time(&now); - DevInfoAllCommand* cmdAll = _radio->enqueCommand(); + auto cmdAll = _radio->prepareCommand(); cmdAll->setTime(now); cmdAll->setTargetAddress(serial()); + _radio->enqueCommand(cmdAll); - DevInfoSimpleCommand* cmdSimple = _radio->enqueCommand(); + auto cmdSimple = _radio->prepareCommand(); cmdSimple->setTime(now); cmdSimple->setTargetAddress(serial()); + _radio->enqueCommand(cmdSimple); return true; } @@ -107,10 +111,11 @@ bool HM_Abstract::sendSystemConfigParaRequest() time_t now; time(&now); - SystemConfigParaCommand* cmd = _radio->enqueCommand(); + auto cmd = _radio->prepareCommand(); cmd->setTime(now); cmd->setTargetAddress(serial()); SystemConfigPara()->setLastLimitRequestSuccess(CMD_PENDING); + _radio->enqueCommand(cmd); return true; } @@ -128,10 +133,11 @@ bool HM_Abstract::sendActivePowerControlRequest(float limit, PowerLimitControlTy _activePowerControlLimit = limit; _activePowerControlType = type; - ActivePowerControlCommand* cmd = _radio->enqueCommand(); + auto cmd = _radio->prepareCommand(); cmd->setActivePowerLimit(limit, type); cmd->setTargetAddress(serial()); SystemConfigPara()->setLastLimitCommandSuccess(CMD_PENDING); + _radio->enqueCommand(cmd); return true; } @@ -153,10 +159,11 @@ bool HM_Abstract::sendPowerControlRequest(bool turnOn) _powerState = 0; } - PowerControlCommand* cmd = _radio->enqueCommand(); + auto cmd = _radio->prepareCommand(); cmd->setPowerOn(turnOn); cmd->setTargetAddress(serial()); PowerCommand()->setLastPowerCommandSuccess(CMD_PENDING); + _radio->enqueCommand(cmd); return true; } @@ -169,10 +176,11 @@ bool HM_Abstract::sendRestartControlRequest() _powerState = 2; - PowerControlCommand* cmd = _radio->enqueCommand(); + auto cmd = _radio->prepareCommand(); cmd->setRestart(); cmd->setTargetAddress(serial()); PowerCommand()->setLastPowerCommandSuccess(CMD_PENDING); + _radio->enqueCommand(cmd); return true; } diff --git a/lib/Hoymiles/src/parser/AlarmLogParser.cpp b/lib/Hoymiles/src/parser/AlarmLogParser.cpp index 3e1dc664..10ed3052 100644 --- a/lib/Hoymiles/src/parser/AlarmLogParser.cpp +++ b/lib/Hoymiles/src/parser/AlarmLogParser.cpp @@ -6,7 +6,7 @@ #include "../Hoymiles.h" #include -const std::array AlarmLogParser::_alarmMessages = {{ +const std::array AlarmLogParser::_alarmMessages = { { { AlarmMessageType_t::ALL, 1, "Inverter start" }, { AlarmMessageType_t::ALL, 2, "DTU command failed" }, { AlarmMessageType_t::ALL, 73, "Temperature >80°C" }, // https://github.com/tbnobody/OpenDTU/discussions/590#discussioncomment-6049750 @@ -84,7 +84,20 @@ const std::array AlarmLogParser::_alarmMe { AlarmMessageType_t::ALL, 5200, "Firmware error" }, { AlarmMessageType_t::ALL, 8310, "Shut down" }, { AlarmMessageType_t::ALL, 9000, "Microinverter is suspected of being stolen" }, -}}; +} }; + +#define HOY_SEMAPHORE_TAKE() \ + do { \ + } while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS) +#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore) + +AlarmLogParser::AlarmLogParser() + : Parser() +{ + _xSemaphore = xSemaphoreCreateMutex(); + HOY_SEMAPHORE_GIVE(); // release before first use + clearBuffer(); +} void AlarmLogParser::clearBuffer() { @@ -102,8 +115,21 @@ void AlarmLogParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t le _alarmLogLength += len; } +void AlarmLogParser::beginAppendFragment() +{ + HOY_SEMAPHORE_TAKE(); +} + +void AlarmLogParser::endAppendFragment() +{ + HOY_SEMAPHORE_GIVE(); +} + uint8_t AlarmLogParser::getEntryCount() { + if (_alarmLogLength < 2) { + return 0; + } return (_alarmLogLength - 2) / ALARM_LOG_ENTRY_SIZE; } @@ -128,6 +154,8 @@ void AlarmLogParser::getLogEntry(uint8_t entryId, AlarmLogEntry_t* entry) int timezoneOffset = getTimezoneOffset(); + HOY_SEMAPHORE_TAKE(); + uint32_t wcode = (uint16_t)_payloadAlarmLog[entryStartOffset] << 8 | _payloadAlarmLog[entryStartOffset + 1]; uint32_t startTimeOffset = 0; if (((wcode >> 13) & 0x01) == 1) { @@ -143,6 +171,8 @@ void AlarmLogParser::getLogEntry(uint8_t entryId, AlarmLogEntry_t* entry) entry->StartTime = (((uint16_t)_payloadAlarmLog[entryStartOffset + 4] << 8) | ((uint16_t)_payloadAlarmLog[entryStartOffset + 5])) + startTimeOffset + timezoneOffset; entry->EndTime = ((uint16_t)_payloadAlarmLog[entryStartOffset + 6] << 8) | ((uint16_t)_payloadAlarmLog[entryStartOffset + 7]); + HOY_SEMAPHORE_GIVE(); + if (entry->EndTime > 0) { entry->EndTime += (endTimeOffset + timezoneOffset); } diff --git a/lib/Hoymiles/src/parser/AlarmLogParser.h b/lib/Hoymiles/src/parser/AlarmLogParser.h index 0286db15..5c37589d 100644 --- a/lib/Hoymiles/src/parser/AlarmLogParser.h +++ b/lib/Hoymiles/src/parser/AlarmLogParser.h @@ -2,8 +2,8 @@ #pragma once #include "Parser.h" #include -#include #include +#include #define ALARM_LOG_ENTRY_COUNT 15 #define ALARM_LOG_ENTRY_SIZE 12 @@ -31,8 +31,11 @@ typedef struct { class AlarmLogParser : public Parser { public: + AlarmLogParser(); void clearBuffer(); void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); + void beginAppendFragment(); + void endAppendFragment(); uint8_t getEntryCount(); void getLogEntry(uint8_t entryId, AlarmLogEntry_t* entry); @@ -46,11 +49,13 @@ private: static int getTimezoneOffset(); uint8_t _payloadAlarmLog[ALARM_LOG_PAYLOAD_SIZE]; - uint8_t _alarmLogLength; + uint8_t _alarmLogLength = 0; LastCommandSuccess _lastAlarmRequestSuccess = CMD_NOK; // Set to NOK to fetch at startup AlarmMessageType_t _messageType = AlarmMessageType_t::ALL; static const std::array _alarmMessages; + + SemaphoreHandle_t _xSemaphore; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/DevInfoParser.cpp b/lib/Hoymiles/src/parser/DevInfoParser.cpp index d5994dca..44fe59e1 100644 --- a/lib/Hoymiles/src/parser/DevInfoParser.cpp +++ b/lib/Hoymiles/src/parser/DevInfoParser.cpp @@ -46,6 +46,20 @@ const devInfo_t devInfo[] = { { { 0x10, 0x33, 0x31, ALL }, 2250, "HMT-2250" } // 01 }; +#define HOY_SEMAPHORE_TAKE() \ + do { \ + } while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS) +#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore) + +DevInfoParser::DevInfoParser() + : Parser() +{ + _xSemaphore = xSemaphoreCreateMutex(); + HOY_SEMAPHORE_GIVE(); // release before first use + clearBufferSimple(); + clearBufferAll(); +} + void DevInfoParser::clearBufferAll() { memset(_payloadDevInfoAll, 0, DEV_INFO_SIZE); @@ -78,6 +92,16 @@ void DevInfoParser::appendFragmentSimple(uint8_t offset, uint8_t* payload, uint8 _devInfoSimpleLength += len; } +void DevInfoParser::beginAppendFragment() +{ + HOY_SEMAPHORE_TAKE(); +} + +void DevInfoParser::endAppendFragment() +{ + HOY_SEMAPHORE_GIVE(); +} + uint32_t DevInfoParser::getLastUpdateAll() { return _lastUpdateAll; @@ -102,12 +126,16 @@ void DevInfoParser::setLastUpdateSimple(uint32_t lastUpdate) uint16_t DevInfoParser::getFwBuildVersion() { - return (((uint16_t)_payloadDevInfoAll[0]) << 8) | _payloadDevInfoAll[1]; + HOY_SEMAPHORE_TAKE(); + uint16_t ret = (((uint16_t)_payloadDevInfoAll[0]) << 8) | _payloadDevInfoAll[1]; + HOY_SEMAPHORE_GIVE(); + return ret; } time_t DevInfoParser::getFwBuildDateTime() { struct tm timeinfo = {}; + HOY_SEMAPHORE_TAKE(); timeinfo.tm_year = ((((uint16_t)_payloadDevInfoAll[2]) << 8) | _payloadDevInfoAll[3]) - 1900; timeinfo.tm_mon = ((((uint16_t)_payloadDevInfoAll[4]) << 8) | _payloadDevInfoAll[5]) / 100 - 1; @@ -115,13 +143,17 @@ time_t DevInfoParser::getFwBuildDateTime() timeinfo.tm_hour = ((((uint16_t)_payloadDevInfoAll[6]) << 8) | _payloadDevInfoAll[7]) / 100; timeinfo.tm_min = ((((uint16_t)_payloadDevInfoAll[6]) << 8) | _payloadDevInfoAll[7]) % 100; + HOY_SEMAPHORE_GIVE(); return timegm(&timeinfo); } uint16_t DevInfoParser::getFwBootloaderVersion() { - return (((uint16_t)_payloadDevInfoAll[8]) << 8) | _payloadDevInfoAll[9]; + HOY_SEMAPHORE_TAKE(); + uint16_t ret = (((uint16_t)_payloadDevInfoAll[8]) << 8) | _payloadDevInfoAll[9]; + HOY_SEMAPHORE_GIVE(); + return ret; } uint32_t DevInfoParser::getHwPartNumber() @@ -129,8 +161,10 @@ uint32_t DevInfoParser::getHwPartNumber() uint16_t hwpn_h; uint16_t hwpn_l; + HOY_SEMAPHORE_TAKE(); hwpn_h = (((uint16_t)_payloadDevInfoSimple[2]) << 8) | _payloadDevInfoSimple[3]; hwpn_l = (((uint16_t)_payloadDevInfoSimple[4]) << 8) | _payloadDevInfoSimple[5]; + HOY_SEMAPHORE_GIVE(); return ((uint32_t)hwpn_h << 16) | ((uint32_t)hwpn_l); } @@ -138,7 +172,9 @@ uint32_t DevInfoParser::getHwPartNumber() String DevInfoParser::getHwVersion() { char buf[8]; + HOY_SEMAPHORE_TAKE(); snprintf(buf, sizeof(buf), "%02d.%02d", _payloadDevInfoSimple[6], _payloadDevInfoSimple[7]); + HOY_SEMAPHORE_GIVE(); return buf; } @@ -162,26 +198,37 @@ String DevInfoParser::getHwModelName() uint8_t DevInfoParser::getDevIdx() { + uint8_t ret = 0xff; uint8_t pos; + + HOY_SEMAPHORE_TAKE(); + // Check for all 4 bytes first for (pos = 0; pos < sizeof(devInfo) / sizeof(devInfo_t); pos++) { if (devInfo[pos].hwPart[0] == _payloadDevInfoSimple[2] && devInfo[pos].hwPart[1] == _payloadDevInfoSimple[3] && devInfo[pos].hwPart[2] == _payloadDevInfoSimple[4] && devInfo[pos].hwPart[3] == _payloadDevInfoSimple[5]) { - return pos; + ret = pos; + break; } } - // Then only for 3 bytes - for (pos = 0; pos < sizeof(devInfo) / sizeof(devInfo_t); pos++) { - if (devInfo[pos].hwPart[0] == _payloadDevInfoSimple[2] - && devInfo[pos].hwPart[1] == _payloadDevInfoSimple[3] - && devInfo[pos].hwPart[2] == _payloadDevInfoSimple[4]) { - return pos; + // Then only for 3 bytes but only if not already found + if (ret == 0xff) { + for (pos = 0; pos < sizeof(devInfo) / sizeof(devInfo_t); pos++) { + if (devInfo[pos].hwPart[0] == _payloadDevInfoSimple[2] + && devInfo[pos].hwPart[1] == _payloadDevInfoSimple[3] + && devInfo[pos].hwPart[2] == _payloadDevInfoSimple[4]) { + ret = pos; + break; + } } } - return 0xff; + + HOY_SEMAPHORE_GIVE(); + + return ret; } /* struct tm to seconds since Unix epoch */ diff --git a/lib/Hoymiles/src/parser/DevInfoParser.h b/lib/Hoymiles/src/parser/DevInfoParser.h index 02b5307d..18c3d7c8 100644 --- a/lib/Hoymiles/src/parser/DevInfoParser.h +++ b/lib/Hoymiles/src/parser/DevInfoParser.h @@ -7,12 +7,16 @@ class DevInfoParser : public Parser { public: + DevInfoParser(); void clearBufferAll(); void appendFragmentAll(uint8_t offset, uint8_t* payload, uint8_t len); void clearBufferSimple(); void appendFragmentSimple(uint8_t offset, uint8_t* payload, uint8_t len); + void beginAppendFragment(); + void endAppendFragment(); + uint32_t getLastUpdateAll(); void setLastUpdateAll(uint32_t lastUpdate); @@ -41,4 +45,6 @@ private: uint8_t _payloadDevInfoSimple[DEV_INFO_SIZE] = {}; uint8_t _devInfoSimpleLength = 0; + + SemaphoreHandle_t _xSemaphore; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/StatisticsParser.cpp b/lib/Hoymiles/src/parser/StatisticsParser.cpp index 59d0148b..2218ca4b 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.cpp +++ b/lib/Hoymiles/src/parser/StatisticsParser.cpp @@ -5,6 +5,11 @@ #include "StatisticsParser.h" #include "../Hoymiles.h" +#define HOY_SEMAPHORE_TAKE() \ + do { \ + } while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS) +#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore) + static float calcYieldTotalCh0(StatisticsParser* iv, uint8_t arg0); static float calcYieldDayCh0(StatisticsParser* iv, uint8_t arg0); static float calcUdcCh(StatisticsParser* iv, uint8_t arg0); @@ -28,6 +33,14 @@ const calcFunc_t calcFunctions[] = { { CALC_IRR_CH, &calcIrradiation } }; +StatisticsParser::StatisticsParser() + : Parser() +{ + _xSemaphore = xSemaphoreCreateMutex(); + HOY_SEMAPHORE_GIVE(); // release before first use + clearBuffer(); +} + void StatisticsParser::setByteAssignment(const byteAssign_t* byteAssignment, uint8_t size) { _byteAssignment = byteAssignment; @@ -62,6 +75,16 @@ void StatisticsParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t _statisticLength += len; } +void StatisticsParser::beginAppendFragment() +{ + HOY_SEMAPHORE_TAKE(); +} + +void StatisticsParser::endAppendFragment() +{ + HOY_SEMAPHORE_GIVE(); +} + const byteAssign_t* StatisticsParser::getAssignmentByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) { for (uint8_t i = 0; i < _byteAssignmentSize; i++) { @@ -98,10 +121,12 @@ float StatisticsParser::getChannelFieldValue(ChannelType_t type, ChannelNum_t ch if (CMD_CALC != div) { // Value is a static value uint32_t val = 0; + HOY_SEMAPHORE_TAKE(); do { val <<= 8; val |= _payloadStatistic[ptr]; } while (++ptr != end); + HOY_SEMAPHORE_GIVE(); float result; if (pos->isSigned && pos->num == 2) { diff --git a/lib/Hoymiles/src/parser/StatisticsParser.h b/lib/Hoymiles/src/parser/StatisticsParser.h index 9af48aa8..436dba27 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.h +++ b/lib/Hoymiles/src/parser/StatisticsParser.h @@ -104,8 +104,11 @@ typedef struct { class StatisticsParser : public Parser { public: + StatisticsParser(); void clearBuffer(); void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); + void beginAppendFragment(); + void endAppendFragment(); void setByteAssignment(const byteAssign_t* byteAssignment, uint8_t size); @@ -142,8 +145,10 @@ private: const byteAssign_t* _byteAssignment; uint8_t _byteAssignmentSize; - uint8_t _expectedByteCount; + uint8_t _expectedByteCount = 0; std::list _fieldSettings; uint32_t _rxFailureCount = 0; + + SemaphoreHandle_t _xSemaphore; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/SystemConfigParaParser.cpp b/lib/Hoymiles/src/parser/SystemConfigParaParser.cpp index 0ab4cec5..2756e1ec 100644 --- a/lib/Hoymiles/src/parser/SystemConfigParaParser.cpp +++ b/lib/Hoymiles/src/parser/SystemConfigParaParser.cpp @@ -6,6 +6,19 @@ #include "../Hoymiles.h" #include +#define HOY_SEMAPHORE_TAKE() \ + do { \ + } while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS) +#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore) + +SystemConfigParaParser::SystemConfigParaParser() + : Parser() +{ + _xSemaphore = xSemaphoreCreateMutex(); + HOY_SEMAPHORE_GIVE(); // release before first use + clearBuffer(); +} + void SystemConfigParaParser::clearBuffer() { memset(_payload, 0, SYSTEM_CONFIG_PARA_SIZE); @@ -22,15 +35,30 @@ void SystemConfigParaParser::appendFragment(uint8_t offset, uint8_t* payload, ui _payloadLength += len; } +void SystemConfigParaParser::beginAppendFragment() +{ + HOY_SEMAPHORE_TAKE(); +} + +void SystemConfigParaParser::endAppendFragment() +{ + HOY_SEMAPHORE_GIVE(); +} + float SystemConfigParaParser::getLimitPercent() { - return ((((uint16_t)_payload[2]) << 8) | _payload[3]) / 10.0; + HOY_SEMAPHORE_TAKE(); + float ret = ((((uint16_t)_payload[2]) << 8) | _payload[3]) / 10.0; + HOY_SEMAPHORE_GIVE(); + return ret; } void SystemConfigParaParser::setLimitPercent(float value) { + HOY_SEMAPHORE_TAKE(); _payload[2] = ((uint16_t)(value * 10)) >> 8; _payload[3] = ((uint16_t)(value * 10)); + HOY_SEMAPHORE_GIVE(); } void SystemConfigParaParser::setLastLimitCommandSuccess(LastCommandSuccess status) diff --git a/lib/Hoymiles/src/parser/SystemConfigParaParser.h b/lib/Hoymiles/src/parser/SystemConfigParaParser.h index 5aced3c8..4ec73817 100644 --- a/lib/Hoymiles/src/parser/SystemConfigParaParser.h +++ b/lib/Hoymiles/src/parser/SystemConfigParaParser.h @@ -7,8 +7,11 @@ class SystemConfigParaParser : public Parser { public: + SystemConfigParaParser(); void clearBuffer(); void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); + void beginAppendFragment(); + void endAppendFragment(); float getLimitPercent(); void setLimitPercent(float value); @@ -32,4 +35,6 @@ private: uint32_t _lastUpdateCommand = 0; uint32_t _lastUpdateRequest = 0; + + SemaphoreHandle_t _xSemaphore; }; \ No newline at end of file diff --git a/lib/ThreadSafeQueue/ThreadSafeQueue.h b/lib/ThreadSafeQueue/ThreadSafeQueue.h new file mode 100644 index 00000000..9a195c60 --- /dev/null +++ b/lib/ThreadSafeQueue/ThreadSafeQueue.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include +#include + +template +class ThreadSafeQueue { +public: + ThreadSafeQueue() = default; + ThreadSafeQueue(const ThreadSafeQueue&) = delete; + ThreadSafeQueue& operator=(const ThreadSafeQueue&) = delete; + + ThreadSafeQueue(ThreadSafeQueue&& other) + { + std::lock_guard lock(_mutex); + _queue = std::move(other._queue); + } + + virtual ~ThreadSafeQueue() { } + + unsigned long size() const + { + std::lock_guard lock(_mutex); + return _queue.size(); + } + + std::optional pop() + { + std::lock_guard lock(_mutex); + if (_queue.empty()) { + return {}; + } + T tmp = _queue.front(); + _queue.pop(); + return tmp; + } + + void push(const T& item) + { + std::lock_guard lock(_mutex); + _queue.push(item); + } + + T front() + { + std::lock_guard lock(_mutex); + return _queue.front(); + } + +private: + // Moved out of public interface to prevent races between this + // and pop(). + bool empty() const + { + return _queue.empty(); + } + + std::queue _queue; + mutable std::mutex _mutex; +}; diff --git a/platformio.ini b/platformio.ini index 6aa3dc81..3bf04dac 100644 --- a/platformio.ini +++ b/platformio.ini @@ -32,10 +32,10 @@ build_unflags = lib_deps = https://github.com/yubox-node-org/ESPAsyncWebServer - bblanchon/ArduinoJson @ ^6.21.2 + bblanchon/ArduinoJson @ ^6.21.3 https://github.com/bertmelis/espMqttClient.git#v1.4.4 nrf24/RF24 @ ^1.4.7 - olikraus/U8g2 @ ^2.34.22 + olikraus/U8g2 @ ^2.35.4 buelowp/sunset @ ^1.1.7 extra_scripts = @@ -64,6 +64,7 @@ board = esp32-c3-devkitc-02 custom_patches = esp32c3 build_flags = ${env.build_flags} + [env:generic_esp32c3_usb] board = esp32-c3-devkitc-02 custom_patches = esp32c3 @@ -71,6 +72,7 @@ build_flags = ${env.build_flags} -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 + [env:generic_esp32s3] board = esp32-s3-devkitc-1 build_flags = ${env.build_flags} diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 867adeaf..8ebb2fab 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -39,6 +39,7 @@ bool ConfigurationClass::write() wifi["dns2"] = IPAddress(config.WiFi_Dns2).toString(); wifi["dhcp"] = config.WiFi_Dhcp; wifi["hostname"] = config.WiFi_Hostname; + wifi["aptimeout"] = config.WiFi_ApTimeout; JsonObject ntp = doc.createNestedObject("ntp"); ntp["server"] = config.Ntp_Server; @@ -184,6 +185,7 @@ bool ConfigurationClass::read() config.WiFi_Dns2[3] = wifi_dns2[3]; config.WiFi_Dhcp = wifi["dhcp"] | WIFI_DHCP; + config.WiFi_ApTimeout = wifi["aptimeout"] | ACCESS_POINT_TIMEOUT; JsonObject ntp = doc["ntp"]; strlcpy(config.Ntp_Server, ntp["server"] | NTP_SERVER, sizeof(config.Ntp_Server)); @@ -300,7 +302,7 @@ void ConfigurationClass::migrate() config.Mqtt_PublishInterval = mqtt["publish_invterval"]; } - if (config.Cfg_Version < 0x00011900) { + if (config.Cfg_Version < 0x00011900) { JsonObject dtu = doc["dtu"]; config.Dtu_NrfPaLevel = dtu["pa_level"]; } diff --git a/src/Datastore.cpp b/src/Datastore.cpp index 875a8777..0afe2c76 100644 --- a/src/Datastore.cpp +++ b/src/Datastore.cpp @@ -6,19 +6,8 @@ #include "Configuration.h" #include -#define DAT_SEMAPHORE_TAKE() \ - do { \ - } while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS) -#define DAT_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore) - DatastoreClass Datastore; -DatastoreClass::DatastoreClass() -{ - _xSemaphore = xSemaphoreCreateMutex(); - DAT_SEMAPHORE_GIVE(); // release before first use -} - void DatastoreClass::init() { _updateTimeout.set(1000); @@ -32,7 +21,7 @@ void DatastoreClass::loop() uint8_t isReachable = 0; uint8_t pollEnabledCount = 0; - DAT_SEMAPHORE_TAKE(); + std::lock_guard lock(_mutex); _totalAcYieldTotalEnabled = 0; _totalAcYieldTotalDigits = 0; @@ -116,136 +105,102 @@ void DatastoreClass::loop() _totalDcIrradiation = _totalDcIrradiationInstalled > 0 ? _totalDcPowerIrradiation / _totalDcIrradiationInstalled * 100.0f : 0; - DAT_SEMAPHORE_GIVE(); - _updateTimeout.reset(); } } float DatastoreClass::getTotalAcYieldTotalEnabled() { - DAT_SEMAPHORE_TAKE(); - float retval = _totalAcYieldTotalEnabled; - DAT_SEMAPHORE_GIVE(); - return retval; + std::lock_guard lock(_mutex); + return _totalAcYieldTotalEnabled; } float DatastoreClass::getTotalAcYieldDayEnabled() { - DAT_SEMAPHORE_TAKE(); - float retval = _totalAcYieldDayEnabled; - DAT_SEMAPHORE_GIVE(); - return retval; + std::lock_guard lock(_mutex); + return _totalAcYieldDayEnabled; } float DatastoreClass::getTotalAcPowerEnabled() { - DAT_SEMAPHORE_TAKE(); - float retval = _totalAcPowerEnabled; - DAT_SEMAPHORE_GIVE(); - return retval; + std::lock_guard lock(_mutex); + return _totalAcPowerEnabled; } float DatastoreClass::getTotalDcPowerEnabled() { - DAT_SEMAPHORE_TAKE(); - float retval = _totalDcPowerEnabled; - DAT_SEMAPHORE_GIVE(); - return retval; + std::lock_guard lock(_mutex); + return _totalDcPowerEnabled; } float DatastoreClass::getTotalDcPowerIrradiation() { - DAT_SEMAPHORE_TAKE(); - float retval = _totalDcPowerIrradiation; - DAT_SEMAPHORE_GIVE(); - return retval; + std::lock_guard lock(_mutex); + return _totalDcPowerIrradiation; } float DatastoreClass::getTotalDcIrradiationInstalled() { - DAT_SEMAPHORE_TAKE(); - float retval = _totalDcIrradiationInstalled; - DAT_SEMAPHORE_GIVE(); - return retval; + std::lock_guard lock(_mutex); + return _totalDcIrradiationInstalled; } float DatastoreClass::getTotalDcIrradiation() { - DAT_SEMAPHORE_TAKE(); - float retval = _totalDcIrradiation; - DAT_SEMAPHORE_GIVE(); - return retval; + std::lock_guard lock(_mutex); + return _totalDcIrradiation; } unsigned int DatastoreClass::getTotalAcYieldTotalDigits() { - DAT_SEMAPHORE_TAKE(); - unsigned int retval = _totalAcYieldTotalDigits; - DAT_SEMAPHORE_GIVE(); - return retval; + std::lock_guard lock(_mutex); + return _totalAcYieldTotalDigits; } unsigned int DatastoreClass::getTotalAcYieldDayDigits() { - DAT_SEMAPHORE_TAKE(); - unsigned int retval = _totalAcYieldDayDigits; - DAT_SEMAPHORE_GIVE(); - return retval; + std::lock_guard lock(_mutex); + return _totalAcYieldDayDigits; } unsigned int DatastoreClass::getTotalAcPowerDigits() { - DAT_SEMAPHORE_TAKE(); - unsigned int retval = _totalAcPowerDigits; - DAT_SEMAPHORE_GIVE(); - return retval; + std::lock_guard lock(_mutex); + return _totalAcPowerDigits; } unsigned int DatastoreClass::getTotalDcPowerDigits() { - DAT_SEMAPHORE_TAKE(); - unsigned int retval = _totalDcPowerDigits; - DAT_SEMAPHORE_GIVE(); - return retval; + std::lock_guard lock(_mutex); + return _totalDcPowerDigits; } bool DatastoreClass::getIsAtLeastOneReachable() { - DAT_SEMAPHORE_TAKE(); - bool retval = _isAtLeastOneReachable; - DAT_SEMAPHORE_GIVE(); - return retval; + std::lock_guard lock(_mutex); + return _isAtLeastOneReachable; } bool DatastoreClass::getIsAtLeastOneProducing() { - DAT_SEMAPHORE_TAKE(); - bool retval = _isAtLeastOneProducing; - DAT_SEMAPHORE_GIVE(); - return retval; + std::lock_guard lock(_mutex); + return _isAtLeastOneProducing; } bool DatastoreClass::getIsAllEnabledProducing() { - DAT_SEMAPHORE_TAKE(); - bool retval = _isAllEnabledProducing; - DAT_SEMAPHORE_GIVE(); - return retval; + std::lock_guard lock(_mutex); + return _isAllEnabledProducing; } bool DatastoreClass::getIsAllEnabledReachable() { - DAT_SEMAPHORE_TAKE(); - bool retval = _isAllEnabledReachable; - DAT_SEMAPHORE_GIVE(); - return retval; + std::lock_guard lock(_mutex); + return _isAllEnabledReachable; } bool DatastoreClass::getIsAtLeastOnePollEnabled() { - DAT_SEMAPHORE_TAKE(); - bool retval = _isAtLeastOnePollEnabled; - DAT_SEMAPHORE_GIVE(); - return retval; + std::lock_guard lock(_mutex); + return _isAtLeastOnePollEnabled; } diff --git a/src/InverterSettings.cpp b/src/InverterSettings.cpp index a0482c0a..c7318ba9 100644 --- a/src/InverterSettings.cpp +++ b/src/InverterSettings.cpp @@ -15,7 +15,7 @@ // for all generations, this is equivalent to SPI3_HOST in the lower level driver // For ESP32-C2, the only externally usable HW SPI controller is SPI2, its signal names // being prefixed with FSPI. -#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 +#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 #define SPI_NRF HSPI #elif CONFIG_IDF_TARGET_ESP32C3 #define SPI_NRF FSPI diff --git a/src/MessageOutput.cpp b/src/MessageOutput.cpp index dc9c6715..23c644bc 100644 --- a/src/MessageOutput.cpp +++ b/src/MessageOutput.cpp @@ -8,17 +8,6 @@ MessageOutputClass MessageOutput; -#define MSG_LOCK() \ - do { \ - } while (xSemaphoreTake(_lock, portMAX_DELAY) != pdPASS) -#define MSG_UNLOCK() xSemaphoreGive(_lock) - -MessageOutputClass::MessageOutputClass() -{ - _lock = xSemaphoreCreateMutex(); - MSG_UNLOCK(); -} - void MessageOutputClass::register_ws_output(AsyncWebSocket* output) { _ws = output; @@ -27,10 +16,9 @@ void MessageOutputClass::register_ws_output(AsyncWebSocket* output) size_t MessageOutputClass::write(uint8_t c) { if (_buff_pos < BUFFER_SIZE) { - MSG_LOCK(); + std::lock_guard lock(_msgLock); _buffer[_buff_pos] = c; _buff_pos++; - MSG_UNLOCK(); } else { _forceSend = true; } @@ -38,11 +26,23 @@ size_t MessageOutputClass::write(uint8_t c) return Serial.write(c); } +size_t MessageOutputClass::write(const uint8_t* buffer, size_t size) +{ + std::lock_guard lock(_msgLock); + if (_buff_pos + size < BUFFER_SIZE) { + memcpy(&_buffer[_buff_pos], buffer, size); + _buff_pos += size; + } + _forceSend = true; + + return Serial.write(buffer, size); +} + void MessageOutputClass::loop() { // Send data via websocket if either time is over or buffer is full if (_forceSend || (millis() - _lastSend > 1000)) { - MSG_LOCK(); + std::lock_guard lock(_msgLock); if (_ws && _buff_pos > 0) { _ws->textAll(_buffer, _buff_pos); _buff_pos = 0; @@ -50,7 +50,6 @@ void MessageOutputClass::loop() if (_forceSend) { _buff_pos = 0; } - MSG_UNLOCK(); _forceSend = false; } } \ No newline at end of file diff --git a/src/MqttSettings.cpp b/src/MqttSettings.cpp index d5b29faf..81624455 100644 --- a/src/MqttSettings.cpp +++ b/src/MqttSettings.cpp @@ -3,8 +3,8 @@ * Copyright (C) 2022 Thomas Basler and others */ #include "MqttSettings.h" -#include "MessageOutput.h" #include "Configuration.h" +#include "MessageOutput.h" MqttSettingsClass::MqttSettingsClass() { @@ -32,21 +32,30 @@ void MqttSettingsClass::onMqttConnect(bool sessionPresent) const CONFIG_T& config = Configuration.get(); publish(config.Mqtt_LwtTopic, config.Mqtt_LwtValue_Online); - for (const auto& cb : _mqttSubscribeParser.get_callbacks()) { - mqttClient->subscribe(cb.topic.c_str(), cb.qos); + std::lock_guard lock(_clientLock); + if (mqttClient != nullptr) { + for (const auto& cb : _mqttSubscribeParser.get_callbacks()) { + mqttClient->subscribe(cb.topic.c_str(), cb.qos); + } } } void MqttSettingsClass::subscribe(const String& topic, uint8_t qos, const espMqttClientTypes::OnMessageCallback& cb) { _mqttSubscribeParser.register_callback(topic.c_str(), qos, cb); - mqttClient->subscribe(topic.c_str(), qos); + std::lock_guard lock(_clientLock); + if (mqttClient != nullptr) { + mqttClient->subscribe(topic.c_str(), qos); + } } void MqttSettingsClass::unsubscribe(const String& topic) { _mqttSubscribeParser.unregister_callback(topic.c_str()); - mqttClient->unsubscribe(topic.c_str()); + std::lock_guard lock(_clientLock); + if (mqttClient != nullptr) { + mqttClient->unsubscribe(topic.c_str()); + } } void MqttSettingsClass::onMqttDisconnect(espMqttClientTypes::DisconnectReason reason) @@ -97,6 +106,12 @@ void MqttSettingsClass::performConnect() using std::placeholders::_4; using std::placeholders::_5; using std::placeholders::_6; + + std::lock_guard lock(_clientLock); + if (mqttClient == nullptr) { + return; + } + MessageOutput.println("Connecting to MQTT..."); const CONFIG_T& config = Configuration.get(); willTopic = getPrefix() + config.Mqtt_LwtTopic; @@ -132,6 +147,10 @@ void MqttSettingsClass::performDisconnect() { const CONFIG_T& config = Configuration.get(); publish(config.Mqtt_LwtTopic, config.Mqtt_LwtValue_Offline); + std::lock_guard lock(_clientLock); + if (mqttClient == nullptr) { + return; + } mqttClient->disconnect(); } @@ -147,6 +166,10 @@ void MqttSettingsClass::performReconnect() bool MqttSettingsClass::getConnected() { + std::lock_guard lock(_clientLock); + if (mqttClient == nullptr) { + return false; + } return mqttClient->connected(); } @@ -157,6 +180,11 @@ String MqttSettingsClass::getPrefix() void MqttSettingsClass::publish(const String& subtopic, const String& payload) { + std::lock_guard lock(_clientLock); + if (mqttClient == nullptr) { + return; + } + String topic = getPrefix(); topic += subtopic; @@ -168,6 +196,10 @@ void MqttSettingsClass::publish(const String& subtopic, const String& payload) void MqttSettingsClass::publishGeneric(const String& topic, const String& payload, bool retain, uint8_t qos) { + std::lock_guard lock(_clientLock); + if (mqttClient == nullptr) { + return; + } mqttClient->publish(topic.c_str(), qos, retain, payload.c_str()); } @@ -181,8 +213,11 @@ void MqttSettingsClass::init() void MqttSettingsClass::createMqttClientObject() { - if (mqttClient != nullptr) + std::lock_guard lock(_clientLock); + if (mqttClient != nullptr) { delete mqttClient; + mqttClient = nullptr; + } const CONFIG_T& config = Configuration.get(); if (config.Mqtt_Tls) { mqttClient = static_cast(new espMqttClientSecure); diff --git a/src/NetworkSettings.cpp b/src/NetworkSettings.cpp index d4857aeb..e0957545 100644 --- a/src/NetworkSettings.cpp +++ b/src/NetworkSettings.cpp @@ -21,6 +21,9 @@ void NetworkSettingsClass::init() { using std::placeholders::_1; + WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); + WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); + WiFi.onEvent(std::bind(&NetworkSettingsClass::NetworkEvent, this, _1)); setupMode(); } @@ -121,8 +124,6 @@ void NetworkSettingsClass::setupMode() dnsServer->stop(); dnsServerStatus = false; if (_networkMode == network_mode::WiFi) { - WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); - WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); WiFi.mode(WIFI_STA); } else { WiFi.mode(WIFI_MODE_NULL); @@ -139,6 +140,7 @@ void NetworkSettingsClass::enableAdminMode() { adminEnabled = true; adminTimeoutCounter = 0; + adminTimeoutCounterMax = Configuration.get().WiFi_ApTimeout * 60; setupMode(); } @@ -158,8 +160,7 @@ void NetworkSettingsClass::loop() setStaticIp(); setHostname(); } - } else - if (_networkMode != network_mode::WiFi) { + } else if (_networkMode != network_mode::WiFi) { // Do stuff when switching to Ethernet mode MessageOutput.println("Switch to WiFi mode"); _networkMode = network_mode::WiFi; @@ -168,7 +169,12 @@ void NetworkSettingsClass::loop() } if (millis() - lastTimerCall > 1000) { - adminTimeoutCounter++; + if (adminEnabled && adminTimeoutCounterMax > 0) { + adminTimeoutCounter++; + if (adminTimeoutCounter % 10 == 0) { + MessageOutput.printf("Admin AP remaining seconds: %d / %d\r\n", adminTimeoutCounter, adminTimeoutCounterMax); + } + } connectTimeoutTimer++; connectRedoTimer++; lastTimerCall = millis(); @@ -178,9 +184,9 @@ void NetworkSettingsClass::loop() if (!isConnected()) { adminTimeoutCounter = 0; } - // If WiFi is connected to AP for more than ADMIN_TIMEOUT + // If WiFi is connected to AP for more than adminTimeoutCounterMax // seconds, disable the internal Access Point - if (adminTimeoutCounter > ADMIN_TIMEOUT) { + if (adminTimeoutCounter > adminTimeoutCounterMax) { adminEnabled = false; MessageOutput.println("Admin mode disabled"); setupMode(); @@ -248,8 +254,7 @@ void NetworkSettingsClass::setHostname() WiFi.mode(WIFI_MODE_APSTA); WiFi.mode(WIFI_MODE_STA); setupMode(); - } - else if (_networkMode == network_mode::Ethernet) { + } else if (_networkMode == network_mode::Ethernet) { if (ETH.setHostname(getHostname().c_str())) { MessageOutput.println("done"); } else { @@ -275,8 +280,7 @@ void NetworkSettingsClass::setStaticIp() IPAddress(Configuration.get().WiFi_Dns2)); MessageOutput.println("done"); } - } - else if (_networkMode == network_mode::Ethernet) { + } else if (_networkMode == network_mode::Ethernet) { if (Configuration.get().WiFi_Dhcp) { MessageOutput.print("Configuring Ethernet DHCP IP... "); ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE); diff --git a/src/PinMapping.cpp b/src/PinMapping.cpp index b9532d66..68395b6f 100644 --- a/src/PinMapping.cpp +++ b/src/PinMapping.cpp @@ -11,23 +11,23 @@ #define JSON_BUFFER_SIZE 6144 #ifndef DISPLAY_TYPE -#define DISPLAY_TYPE 0 +#define DISPLAY_TYPE 0U #endif #ifndef DISPLAY_DATA -#define DISPLAY_DATA 255 +#define DISPLAY_DATA 255U #endif #ifndef DISPLAY_CLK -#define DISPLAY_CLK 255 +#define DISPLAY_CLK 255U #endif #ifndef DISPLAY_CS -#define DISPLAY_CS 255 +#define DISPLAY_CS 255U #endif #ifndef DISPLAY_RESET -#define DISPLAY_RESET 255 +#define DISPLAY_RESET 255U #endif #ifndef LED0 diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index e64f80ab..26331ee1 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -125,7 +125,8 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) return; } - if (!(root.containsKey("curPin") || root.containsKey("display"))) { + if (!(root.containsKey("curPin") + || root.containsKey("display"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; response->setLength(); diff --git a/src/WebApi_dtu.cpp b/src/WebApi_dtu.cpp index fc4dadef..1ae7c408 100644 --- a/src/WebApi_dtu.cpp +++ b/src/WebApi_dtu.cpp @@ -89,7 +89,11 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request) return; } - if (!(root.containsKey("serial") && root.containsKey("pollinterval") && root.containsKey("nrf_palevel") && root.containsKey("cmt_palevel") && root.containsKey("cmt_frequency"))) { + if (!(root.containsKey("serial") + && root.containsKey("pollinterval") + && root.containsKey("nrf_palevel") + && root.containsKey("cmt_palevel") + && root.containsKey("cmt_frequency"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; response->setLength(); diff --git a/src/WebApi_inverter.cpp b/src/WebApi_inverter.cpp index 495b5fac..a8165925 100644 --- a/src/WebApi_inverter.cpp +++ b/src/WebApi_inverter.cpp @@ -121,7 +121,8 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request) return; } - if (!(root.containsKey("serial") && root.containsKey("name"))) { + if (!(root.containsKey("serial") + && root.containsKey("name"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; response->setLength(); @@ -443,7 +444,7 @@ void WebApiInverterClass::onInverterOrder(AsyncWebServerRequest* request) // The order array contains list or id in the right order JsonArray orderArray = root["order"].as(); uint8_t order = 0; - for(JsonVariant id : orderArray) { + for (JsonVariant id : orderArray) { uint8_t inverter_id = id.as(); if (inverter_id < INV_MAX_COUNT) { INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[inverter_id]; diff --git a/src/WebApi_mqtt.cpp b/src/WebApi_mqtt.cpp index 62e54e72..2ab426d1 100644 --- a/src/WebApi_mqtt.cpp +++ b/src/WebApi_mqtt.cpp @@ -221,8 +221,8 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) } if (root["mqtt_root_ca_cert"].as().length() > MQTT_MAX_CERT_STRLEN - || root["mqtt_client_cert"].as().length() > MQTT_MAX_CERT_STRLEN - || root["mqtt_client_key"].as().length() > MQTT_MAX_CERT_STRLEN) { + || root["mqtt_client_cert"].as().length() > MQTT_MAX_CERT_STRLEN + || root["mqtt_client_key"].as().length() > MQTT_MAX_CERT_STRLEN) { retMsg["message"] = "Certificates must not be longer than " STR(MQTT_MAX_CERT_STRLEN) " characters!"; retMsg["code"] = WebApiError::MqttCertificateLength; retMsg["param"]["max"] = MQTT_MAX_CERT_STRLEN; diff --git a/src/WebApi_network.cpp b/src/WebApi_network.cpp index 1226917f..9c05e7cc 100644 --- a/src/WebApi_network.cpp +++ b/src/WebApi_network.cpp @@ -75,6 +75,7 @@ void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request) root["dns2"] = IPAddress(config.WiFi_Dns2).toString(); root["ssid"] = config.WiFi_Ssid; root["password"] = config.WiFi_Password; + root["aptimeout"] = config.WiFi_ApTimeout; response->setLength(); request->send(response); @@ -119,7 +120,16 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request) return; } - if (!(root.containsKey("ssid") && root.containsKey("password") && root.containsKey("hostname") && root.containsKey("dhcp") && root.containsKey("ipaddress") && root.containsKey("netmask") && root.containsKey("gateway") && root.containsKey("dns1") && root.containsKey("dns2"))) { + if (!(root.containsKey("ssid") + && root.containsKey("password") + && root.containsKey("hostname") + && root.containsKey("dhcp") + && root.containsKey("ipaddress") + && root.containsKey("netmask") + && root.containsKey("gateway") + && root.containsKey("dns1") + && root.containsKey("dns2") + && root.containsKey("aptimeout"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; response->setLength(); @@ -188,6 +198,13 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request) request->send(response); return; } + if (root["aptimeout"].as() > 99999) { + retMsg["message"] = "ApTimeout must be a number between 0 and 99999!"; + retMsg["code"] = WebApiError::NetworkApTimeoutInvalid; + response->setLength(); + request->send(response); + return; + } CONFIG_T& config = Configuration.get(); config.WiFi_Ip[0] = ipaddress[0]; @@ -218,6 +235,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request) } else { config.WiFi_Dhcp = false; } + config.WiFi_ApTimeout = root["aptimeout"].as(); Configuration.write(); retMsg["type"] = "success"; diff --git a/src/WebApi_ntp.cpp b/src/WebApi_ntp.cpp index 67b30851..52c665e2 100644 --- a/src/WebApi_ntp.cpp +++ b/src/WebApi_ntp.cpp @@ -133,7 +133,11 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request) return; } - if (!(root.containsKey("ntp_server") && root.containsKey("ntp_timezone") && root.containsKey("longitude") && root.containsKey("latitude") && root.containsKey("sunsettype"))) { + if (!(root.containsKey("ntp_server") + && root.containsKey("ntp_timezone") + && root.containsKey("longitude") + && root.containsKey("latitude") + && root.containsKey("sunsettype"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; response->setLength(); diff --git a/src/WebApi_power.cpp b/src/WebApi_power.cpp index 6be68a88..ca792364 100644 --- a/src/WebApi_power.cpp +++ b/src/WebApi_power.cpp @@ -90,7 +90,8 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request) } if (!(root.containsKey("serial") - && (root.containsKey("power") || root.containsKey("restart")))) { + && (root.containsKey("power") + || root.containsKey("restart")))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; response->setLength(); diff --git a/src/WebApi_prometheus.cpp b/src/WebApi_prometheus.cpp index 28b00331..18664946 100644 --- a/src/WebApi_prometheus.cpp +++ b/src/WebApi_prometheus.cpp @@ -143,8 +143,7 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, String& se idx, inv->name(), channel, - config.Inverter[idx].channel[channel].Name - ); + config.Inverter[idx].channel[channel].Name); if (printHelp) { stream->print("# HELP opendtu_MaxPower panel maximum output power\n"); @@ -155,8 +154,7 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, String& se idx, inv->name(), channel, - config.Inverter[idx].channel[channel].MaxChannelPower - ); + config.Inverter[idx].channel[channel].MaxChannelPower); if (printHelp) { stream->print("# HELP opendtu_YieldTotalOffset panel yield offset (for used inverters)\n"); @@ -167,6 +165,5 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, String& se idx, inv->name(), channel, - config.Inverter[idx].channel[channel].YieldTotalOffset - ); + config.Inverter[idx].channel[channel].YieldTotalOffset); } diff --git a/src/WebApi_webapp.cpp b/src/WebApi_webapp.cpp index fd42da5c..90516ad6 100644 --- a/src/WebApi_webapp.cpp +++ b/src/WebApi_webapp.cpp @@ -58,7 +58,7 @@ void WebApiWebappClass::init(AsyncWebServer* server) #ifdef AUTO_GIT_HASH // check client If-None-Match header vs ETag/AUTO_GIT_HASH bool eTagMatch = false; - if(request->hasHeader("If-None-Match")){ + if (request->hasHeader("If-None-Match")) { AsyncWebHeader* h = request->getHeader("If-None-Match"); if (strncmp(AUTO_GIT_HASH, h->value().c_str(), strlen(AUTO_GIT_HASH)) == 0) { eTagMatch = true; diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index 0213cc94..8b7c6a2e 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -169,9 +169,7 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root) JsonObject hintObj = root.createNestedObject("hints"); struct tm timeinfo; hintObj["time_sync"] = !getLocalTime(&timeinfo, 5); - hintObj["radio_problem"] = - (Hoymiles.getRadioNrf()->isInitialized() && (!Hoymiles.getRadioNrf()->isConnected() || !Hoymiles.getRadioNrf()->isPVariant())) || - (Hoymiles.getRadioCmt()->isInitialized() && (!Hoymiles.getRadioCmt()->isConnected())); + hintObj["radio_problem"] = (Hoymiles.getRadioNrf()->isInitialized() && (!Hoymiles.getRadioNrf()->isConnected() || !Hoymiles.getRadioNrf()->isPVariant())) || (Hoymiles.getRadioCmt()->isInitialized() && (!Hoymiles.getRadioCmt()->isConnected())); if (!strcmp(Configuration.get().Security_Password, ACCESS_POINT_PASSWORD)) { hintObj["default_password"] = true; } else { diff --git a/webapp/package.json b/webapp/package.json index 42ac9f79..bc326ef8 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@popperjs/core": "^2.11.8", - "bootstrap": "^5.3.0", + "bootstrap": "^5.3.1", "bootstrap-icons-vue": "^1.10.3", "mitt": "^3.0.1", "sortablejs": "^1.15.0", @@ -24,24 +24,24 @@ }, "devDependencies": { "@intlify/unplugin-vue-i18n": "^0.12.2", - "@rushstack/eslint-patch": "^1.3.2", + "@rushstack/eslint-patch": "^1.3.3", "@tsconfig/node18": "^18.2.0", "@types/bootstrap": "^5.2.6", - "@types/node": "^20.4.1", + "@types/node": "^20.4.8", "@types/sortablejs": "^1.15.1", "@types/spark-md5": "^3.0.2", "@vitejs/plugin-vue": "^4.2.3", "@vue/eslint-config-typescript": "^11.0.3", "@vue/tsconfig": "^0.4.0", - "eslint": "^8.44.0", - "eslint-plugin-vue": "^9.15.1", + "eslint": "^8.46.0", + "eslint-plugin-vue": "^9.16.1", "npm-run-all": "^4.1.5", - "sass": "^1.63.6", - "terser": "^5.19.0", + "sass": "^1.64.2", + "terser": "^5.19.2", "typescript": "^5.1.6", - "vite": "^4.4.3", + "vite": "^4.4.9", "vite-plugin-compression": "^0.5.1", - "vite-plugin-css-injected-by-js": "^3.2.0", - "vue-tsc": "^1.8.4" + "vite-plugin-css-injected-by-js": "^3.3.0", + "vue-tsc": "^1.8.8" } } diff --git a/webapp/src/components/BootstrapAlert.vue b/webapp/src/components/BootstrapAlert.vue index b6b447d1..df96fb62 100644 --- a/webapp/src/components/BootstrapAlert.vue +++ b/webapp/src/components/BootstrapAlert.vue @@ -52,7 +52,11 @@ export default defineComponent({ _countDownTimeout = undefined; }; - const countDown = ref(parseCountDown(props.modelValue)); + var countDown = ref(); + watch(() => props.modelValue, () => { + countDown.value = parseCountDown(props.modelValue); + }); + const isAlertVisible = computed(() => props.modelValue || props.show); onBeforeUnmount(() => { diff --git a/webapp/src/components/PinInfo.vue b/webapp/src/components/PinInfo.vue index bb140035..af7e788c 100644 --- a/webapp/src/components/PinInfo.vue +++ b/webapp/src/components/PinInfo.vue @@ -17,11 +17,11 @@ {{ capitalizeFirstLetter(category) }} {{ prop }} -