@@ -137,6 +132,7 @@ import ModalDialog from '@/components/ModalDialog.vue';
import type { AlertResponse } from '@/types/Alert';
import type { FileInfo } from '@/types/File';
import { authHeader, handleResponse } from '@/utils/authentication';
+import { waitRestart } from '@/utils/waitRestart';
import * as bootstrap from 'bootstrap';
import {
BIconArrowLeft,
@@ -251,6 +247,7 @@ export default defineComponent({
// request.response will hold the response from the server
if (request.status === 200) {
this.UploadSuccess = true;
+ waitRestart(this.$router);
} else if (request.status !== 500) {
this.UploadError = `[HTTP ERROR] ${request.statusText}`;
} else {
diff --git a/webapp/src/views/FirmwareUpgradeView.vue b/webapp/src/views/FirmwareUpgradeView.vue
index 3828d00e9..15897c6f2 100644
--- a/webapp/src/views/FirmwareUpgradeView.vue
+++ b/webapp/src/views/FirmwareUpgradeView.vue
@@ -39,13 +39,6 @@
{{ $t('firmwareupgrade.OtaSuccess') }}
-
-
-
{
- // Check if the response status is OK (200-299 range)
- if (response.ok) {
- console.log('Remote host is available. Reloading page...');
- clearInterval(this.hostCheckInterval);
- this.hostCheckInterval = 0;
- // Perform a page reload
- window.location.replace('/');
- } else {
- console.log('Remote host is not reachable. Do something else if needed.');
- }
- })
- .catch((error) => {
- console.error('Error checking remote host:', error);
- });
- } else {
- console.log('Browser is offline. Cannot check remote host.');
- }
- },
},
mounted() {
if (!isLoggedIn()) {
@@ -229,8 +196,5 @@ export default defineComponent({
}
this.loading = false;
},
- unmounted() {
- clearInterval(this.hostCheckInterval);
- },
});
diff --git a/webapp/src/views/MaintenanceRebootView.vue b/webapp/src/views/MaintenanceRebootView.vue
index d80d050ca..682ce5006 100644
--- a/webapp/src/views/MaintenanceRebootView.vue
+++ b/webapp/src/views/MaintenanceRebootView.vue
@@ -36,6 +36,7 @@ import ModalDialog from '@/components/ModalDialog.vue';
import { authHeader, handleResponse, isLoggedIn } from '@/utils/authentication';
import * as bootstrap from 'bootstrap';
import { defineComponent } from 'vue';
+import { waitRestart } from '@/utils/waitRestart';
export default defineComponent({
components: {
@@ -80,6 +81,7 @@ export default defineComponent({
this.alertMessage = this.$t('apiresponse.' + data.code, data.param);
this.alertType = data.type;
this.showAlert = true;
+ waitRestart(this.$router);
});
this.onCloseModal(this.performReboot);
},
diff --git a/webapp/src/views/WaitRestartView.vue b/webapp/src/views/WaitRestartView.vue
new file mode 100644
index 000000000..95640e4b4
--- /dev/null
+++ b/webapp/src/views/WaitRestartView.vue
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
From 2a21e53422296ca72e875c8603b8f8ecb9637b32 Mon Sep 17 00:00:00 2001
From: Thomas Basler
Date: Mon, 21 Oct 2024 22:41:52 +0200
Subject: [PATCH 17/76] webapp: Rename interface to prevent lint errors
---
webapp/src/types/{Alert.ts => AlertResponse.ts} | 0
webapp/src/views/ConfigAdminView.vue | 2 +-
webapp/src/views/InverterAdminView.vue | 2 +-
3 files changed, 2 insertions(+), 2 deletions(-)
rename webapp/src/types/{Alert.ts => AlertResponse.ts} (100%)
diff --git a/webapp/src/types/Alert.ts b/webapp/src/types/AlertResponse.ts
similarity index 100%
rename from webapp/src/types/Alert.ts
rename to webapp/src/types/AlertResponse.ts
diff --git a/webapp/src/views/ConfigAdminView.vue b/webapp/src/views/ConfigAdminView.vue
index ff90a656b..08ed31d81 100644
--- a/webapp/src/views/ConfigAdminView.vue
+++ b/webapp/src/views/ConfigAdminView.vue
@@ -129,7 +129,7 @@ import BasePage from '@/components/BasePage.vue';
import BootstrapAlert from '@/components/BootstrapAlert.vue';
import CardElement from '@/components/CardElement.vue';
import ModalDialog from '@/components/ModalDialog.vue';
-import type { AlertResponse } from '@/types/Alert';
+import type { AlertResponse } from '@/types/AlertResponse';
import type { FileInfo } from '@/types/File';
import { authHeader, handleResponse } from '@/utils/authentication';
import { waitRestart } from '@/utils/waitRestart';
diff --git a/webapp/src/views/InverterAdminView.vue b/webapp/src/views/InverterAdminView.vue
index d07f630d3..4ab82d34c 100644
--- a/webapp/src/views/InverterAdminView.vue
+++ b/webapp/src/views/InverterAdminView.vue
@@ -349,7 +349,7 @@ import CardElement from '@/components/CardElement.vue';
import InputElement from '@/components/InputElement.vue';
import InputSerial from '@/components/InputSerial.vue';
import ModalDialog from '@/components/ModalDialog.vue';
-import type { AlertResponse } from '@/types/Alert';
+import type { AlertResponse } from '@/types/AlertResponse';
import type { Inverter } from '@/types/InverterConfig';
import { authHeader, handleResponse } from '@/utils/authentication';
import * as bootstrap from 'bootstrap';
From b1edb13b3c32c0e8f0e0196eed14de1c0ee8c923 Mon Sep 17 00:00:00 2001
From: Bernhard Kirchen
Date: Thu, 17 Oct 2024 21:46:32 +0200
Subject: [PATCH 18/76] add and use configuration write guard
the configuration write guard is now required when the configuration
struct shall be mutated. the write guards locks multiple writers against
each other and also, more importantly, makes the writes synchronous to
the main loop. all code running in the main loop can now be sure that
(1) reads from the configuration struct are non-preemtive and (2) the
configuration struct as a whole is in a consistent state when reading
from it.
NOTE that acquiring a write guard from within the main loop's task will
immediately cause a deadlock and the watchdog will trigger a reset. if
writing from inside the main loop should ever become necessary, the
write guard must be updated to only lock the mutex but not wait for a
signal.
---
include/Configuration.h | 24 +++++++++++--
src/Configuration.cpp | 58 ++++++++++++++++++++++++++++--
src/WebApi.cpp | 4 +--
src/WebApi_device.cpp | 35 ++++++++++--------
src/WebApi_dtu.cpp | 20 ++++++-----
src/WebApi_inverter.cpp | 78 +++++++++++++++++++++++------------------
src/WebApi_mqtt.cpp | 64 +++++++++++++++++----------------
src/WebApi_network.cpp | 64 +++++++++++++++++----------------
src/WebApi_ntp.cpp | 18 ++++++----
src/WebApi_security.cpp | 10 ++++--
src/main.cpp | 17 ++-------
11 files changed, 243 insertions(+), 149 deletions(-)
diff --git a/include/Configuration.h b/include/Configuration.h
index 4a802e4e1..ce7bfbeea 100644
--- a/include/Configuration.h
+++ b/include/Configuration.h
@@ -3,6 +3,9 @@
#include "PinMapping.h"
#include
+#include
+#include
+#include
#define CONFIG_FILENAME "/config.json"
#define CONFIG_VERSION 0x00011c00 // 0.1.28 // make sure to clean all after change
@@ -161,15 +164,32 @@ struct CONFIG_T {
class ConfigurationClass {
public:
- void init();
+ void init(Scheduler& scheduler);
bool read();
bool write();
void migrate();
- CONFIG_T& get();
+ CONFIG_T const& get();
+
+ class WriteGuard {
+ public:
+ WriteGuard();
+ CONFIG_T& getConfig();
+ ~WriteGuard();
+
+ private:
+ std::unique_lock _lock;
+ };
+
+ WriteGuard getWriteGuard();
INVERTER_CONFIG_T* getFreeInverterSlot();
INVERTER_CONFIG_T* getInverterConfig(const uint64_t serial);
void deleteInverterById(const uint8_t id);
+
+private:
+ void loop();
+
+ Task _loopTask;
};
extern ConfigurationClass Configuration;
diff --git a/src/Configuration.cpp b/src/Configuration.cpp
index db47d9c8a..34902e94f 100644
--- a/src/Configuration.cpp
+++ b/src/Configuration.cpp
@@ -13,8 +13,17 @@
CONFIG_T config;
-void ConfigurationClass::init()
+static std::condition_variable sWriterCv;
+static std::mutex sWriterMutex;
+static unsigned sWriterCount = 0;
+
+void ConfigurationClass::init(Scheduler& scheduler)
{
+ scheduler.addTask(_loopTask);
+ _loopTask.setCallback(std::bind(&ConfigurationClass::loop, this));
+ _loopTask.setIterations(TASK_FOREVER);
+ _loopTask.enable();
+
memset(&config, 0x0, sizeof(config));
}
@@ -318,6 +327,20 @@ bool ConfigurationClass::read()
}
f.close();
+
+ // Check for default DTU serial
+ MessageOutput.print("Check for default DTU serial... ");
+ if (config.Dtu.Serial == DTU_SERIAL) {
+ MessageOutput.print("generate serial based on ESP chip id: ");
+ const uint64_t dtuId = Utils::generateDtuSerial();
+ MessageOutput.printf("%0" PRIx32 "%08" PRIx32 "... ",
+ ((uint32_t)((dtuId >> 32) & 0xFFFFFFFF)),
+ ((uint32_t)(dtuId & 0xFFFFFFFF)));
+ config.Dtu.Serial = dtuId;
+ write();
+ }
+ MessageOutput.println("done");
+
return true;
}
@@ -390,11 +413,16 @@ void ConfigurationClass::migrate()
read();
}
-CONFIG_T& ConfigurationClass::get()
+CONFIG_T const& ConfigurationClass::get()
{
return config;
}
+ConfigurationClass::WriteGuard ConfigurationClass::getWriteGuard()
+{
+ return WriteGuard();
+}
+
INVERTER_CONFIG_T* ConfigurationClass::getFreeInverterSlot()
{
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
@@ -439,4 +467,30 @@ void ConfigurationClass::deleteInverterById(const uint8_t id)
}
}
+void ConfigurationClass::loop()
+{
+ std::unique_lock lock(sWriterMutex);
+ if (sWriterCount == 0) { return; }
+
+ sWriterCv.notify_all();
+ sWriterCv.wait(lock, [] { return sWriterCount == 0; });
+}
+
+CONFIG_T& ConfigurationClass::WriteGuard::getConfig()
+{
+ return config;
+}
+
+ConfigurationClass::WriteGuard::WriteGuard()
+ : _lock(sWriterMutex)
+{
+ sWriterCount++;
+ sWriterCv.wait(_lock);
+}
+
+ConfigurationClass::WriteGuard::~WriteGuard() {
+ sWriterCount--;
+ if (sWriterCount == 0) { sWriterCv.notify_all(); }
+}
+
ConfigurationClass Configuration;
diff --git a/src/WebApi.cpp b/src/WebApi.cpp
index b3255c417..31eb122cc 100644
--- a/src/WebApi.cpp
+++ b/src/WebApi.cpp
@@ -47,7 +47,7 @@ void WebApiClass::reload()
bool WebApiClass::checkCredentials(AsyncWebServerRequest* request)
{
- CONFIG_T& config = Configuration.get();
+ auto const& config = Configuration.get();
if (request->authenticate(AUTH_USERNAME, config.Security.Password)) {
return true;
}
@@ -65,7 +65,7 @@ bool WebApiClass::checkCredentials(AsyncWebServerRequest* request)
bool WebApiClass::checkCredentialsReadonly(AsyncWebServerRequest* request)
{
- CONFIG_T& config = Configuration.get();
+ auto const& config = Configuration.get();
if (config.Security.AllowReadonly) {
return true;
} else {
diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp
index 29686fe08..109ff1a99 100644
--- a/src/WebApi_device.cpp
+++ b/src/WebApi_device.cpp
@@ -129,23 +129,28 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
return;
}
- CONFIG_T& config = Configuration.get();
- bool performRestart = root["curPin"]["name"].as() != config.Dev_PinMapping;
-
- strlcpy(config.Dev_PinMapping, root["curPin"]["name"].as().c_str(), sizeof(config.Dev_PinMapping));
- config.Display.Rotation = root["display"]["rotation"].as();
- config.Display.PowerSafe = root["display"]["power_safe"].as();
- config.Display.ScreenSaver = root["display"]["screensaver"].as();
- config.Display.Contrast = root["display"]["contrast"].as();
- config.Display.Language = root["display"]["language"].as();
- config.Display.Diagram.Duration = root["display"]["diagramduration"].as();
- config.Display.Diagram.Mode = root["display"]["diagrammode"].as();
-
- for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
- config.Led_Single[i].Brightness = root["led"][i]["brightness"].as();
- config.Led_Single[i].Brightness = min(100, config.Led_Single[i].Brightness);
+ {
+ auto guard = Configuration.getWriteGuard();
+ auto& config = guard.getConfig();
+
+ strlcpy(config.Dev_PinMapping, root["curPin"]["name"].as().c_str(), sizeof(config.Dev_PinMapping));
+ config.Display.Rotation = root["display"]["rotation"].as();
+ config.Display.PowerSafe = root["display"]["power_safe"].as();
+ config.Display.ScreenSaver = root["display"]["screensaver"].as();
+ config.Display.Contrast = root["display"]["contrast"].as();
+ config.Display.Language = root["display"]["language"].as();
+ config.Display.Diagram.Duration = root["display"]["diagramduration"].as();
+ config.Display.Diagram.Mode = root["display"]["diagrammode"].as();
+
+ for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
+ config.Led_Single[i].Brightness = root["led"][i]["brightness"].as();
+ config.Led_Single[i].Brightness = min(100, config.Led_Single[i].Brightness);
+ }
}
+ auto const& config = Configuration.get();
+ bool performRestart = root["curPin"]["name"].as() != config.Dev_PinMapping;
+
Display.setDiagramMode(static_cast(config.Display.Diagram.Mode));
Display.setOrientation(config.Display.Rotation);
Display.enablePowerSafe = config.Display.PowerSafe;
diff --git a/src/WebApi_dtu.cpp b/src/WebApi_dtu.cpp
index 7c6c3f738..e6fb8994c 100644
--- a/src/WebApi_dtu.cpp
+++ b/src/WebApi_dtu.cpp
@@ -27,7 +27,7 @@ void WebApiDtuClass::init(AsyncWebServer& server, Scheduler& scheduler)
void WebApiDtuClass::applyDataTaskCb()
{
// Execute stuff in main thread to avoid busy SPI bus
- CONFIG_T& config = Configuration.get();
+ auto const& config = Configuration.get();
Hoymiles.getRadioNrf()->setPALevel((rf24_pa_dbm_e)config.Dtu.Nrf.PaLevel);
Hoymiles.getRadioCmt()->setPALevel(config.Dtu.Cmt.PaLevel);
Hoymiles.getRadioNrf()->setDtuSerial(config.Dtu.Serial);
@@ -153,14 +153,16 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
return;
}
- CONFIG_T& config = Configuration.get();
-
- config.Dtu.Serial = serial;
- config.Dtu.PollInterval = root["pollinterval"].as();
- config.Dtu.Nrf.PaLevel = root["nrf_palevel"].as();
- config.Dtu.Cmt.PaLevel = root["cmt_palevel"].as();
- config.Dtu.Cmt.Frequency = root["cmt_frequency"].as();
- config.Dtu.Cmt.CountryMode = root["cmt_country"].as();
+ {
+ auto guard = Configuration.getWriteGuard();
+ auto& config = guard.getConfig();
+ config.Dtu.Serial = serial;
+ config.Dtu.PollInterval = root["pollinterval"].as();
+ config.Dtu.Nrf.PaLevel = root["nrf_palevel"].as();
+ config.Dtu.Cmt.PaLevel = root["cmt_palevel"].as();
+ config.Dtu.Cmt.Frequency = root["cmt_frequency"].as();
+ config.Dtu.Cmt.CountryMode = root["cmt_country"].as();
+ }
WebApi.writeConfig(retMsg);
diff --git a/src/WebApi_inverter.cpp b/src/WebApi_inverter.cpp
index ef353158f..4db9a57c9 100644
--- a/src/WebApi_inverter.cpp
+++ b/src/WebApi_inverter.cpp
@@ -184,9 +184,9 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
}
// Interpret the string as a hex value and convert it to uint64_t
- const uint64_t serial = strtoll(root["serial"].as().c_str(), NULL, 16);
+ const uint64_t new_serial = strtoll(root["serial"].as().c_str(), NULL, 16);
- if (serial == 0) {
+ if (new_serial == 0) {
retMsg["message"] = "Serial must be a number > 0!";
retMsg["code"] = WebApiError::InverterSerialZero;
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
@@ -209,37 +209,42 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
return;
}
- INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[root["id"].as()];
-
- uint64_t new_serial = serial;
- uint64_t old_serial = inverter.Serial;
-
- // Interpret the string as a hex value and convert it to uint64_t
- inverter.Serial = new_serial;
- strncpy(inverter.Name, root["name"].as().c_str(), INV_MAX_NAME_STRLEN);
-
- inverter.Poll_Enable = root["poll_enable"] | true;
- inverter.Poll_Enable_Night = root["poll_enable_night"] | true;
- inverter.Command_Enable = root["command_enable"] | 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;
- inverter.ClearEventlogOnMidnight = root["clear_eventlog"] | false;
- inverter.YieldDayCorrection = root["yieldday_correction"] | false;
-
- uint8_t arrayCount = 0;
- for (JsonVariant channel : channelArray) {
- inverter.channel[arrayCount].MaxChannelPower = channel["max_power"].as();
- inverter.channel[arrayCount].YieldTotalOffset = channel["yield_total_offset"].as();
- strncpy(inverter.channel[arrayCount].Name, channel["name"] | "", sizeof(inverter.channel[arrayCount].Name));
- arrayCount++;
+ uint64_t old_serial = 0;
+
+ {
+ auto guard = Configuration.getWriteGuard();
+ auto& config = guard.getConfig();
+
+ INVERTER_CONFIG_T& inverter = config.Inverter[root["id"].as()];
+
+ old_serial = inverter.Serial;
+ inverter.Serial = new_serial;
+ strncpy(inverter.Name, root["name"].as().c_str(), INV_MAX_NAME_STRLEN);
+
+ inverter.Poll_Enable = root["poll_enable"] | true;
+ inverter.Poll_Enable_Night = root["poll_enable_night"] | true;
+ inverter.Command_Enable = root["command_enable"] | 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;
+ inverter.ClearEventlogOnMidnight = root["clear_eventlog"] | false;
+ inverter.YieldDayCorrection = root["yieldday_correction"] | false;
+
+ uint8_t arrayCount = 0;
+ for (JsonVariant channel : channelArray) {
+ inverter.channel[arrayCount].MaxChannelPower = channel["max_power"].as();
+ inverter.channel[arrayCount].YieldTotalOffset = channel["yield_total_offset"].as();
+ strncpy(inverter.channel[arrayCount].Name, channel["name"] | "", sizeof(inverter.channel[arrayCount].Name));
+ arrayCount++;
+ }
}
WebApi.writeConfig(retMsg, WebApiError::InverterChanged, "Inverter changed!");
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
+ INVERTER_CONFIG_T const& inverter = Configuration.get().Inverter[root["id"].as()];
std::shared_ptr inv = Hoymiles.getInverterBySerial(old_serial);
if (inv != nullptr && new_serial != old_serial) {
@@ -300,7 +305,7 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
}
uint8_t inverter_id = root["id"].as();
- INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[inverter_id];
+ INVERTER_CONFIG_T const& inverter = Configuration.get().Inverter[inverter_id];
Hoymiles.removeInverterBySerial(inverter.Serial);
@@ -337,13 +342,18 @@ 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) {
- uint8_t inverter_id = id.as();
- if (inverter_id < INV_MAX_COUNT) {
- INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[inverter_id];
- inverter.Order = order;
+ {
+ auto guard = Configuration.getWriteGuard();
+ auto& config = guard.getConfig();
+
+ for (JsonVariant id : orderArray) {
+ uint8_t inverter_id = id.as();
+ if (inverter_id < INV_MAX_COUNT) {
+ INVERTER_CONFIG_T& inverter = config.Inverter[inverter_id];
+ inverter.Order = order;
+ }
+ order++;
}
- order++;
}
WebApi.writeConfig(retMsg, WebApiError::InverterOrdered, "Inverter order saved!");
diff --git a/src/WebApi_mqtt.cpp b/src/WebApi_mqtt.cpp
index 0c8ee5c6e..7558110e3 100644
--- a/src/WebApi_mqtt.cpp
+++ b/src/WebApi_mqtt.cpp
@@ -271,36 +271,40 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
}
}
- CONFIG_T& config = Configuration.get();
- config.Mqtt.Enabled = root["mqtt_enabled"].as();
- config.Mqtt.Retain = root["mqtt_retain"].as();
- config.Mqtt.Tls.Enabled = root["mqtt_tls"].as();
- strlcpy(config.Mqtt.Tls.RootCaCert, root["mqtt_root_ca_cert"].as().c_str(), sizeof(config.Mqtt.Tls.RootCaCert));
- config.Mqtt.Tls.CertLogin = root["mqtt_tls_cert_login"].as();
- strlcpy(config.Mqtt.Tls.ClientCert, root["mqtt_client_cert"].as().c_str(), sizeof(config.Mqtt.Tls.ClientCert));
- strlcpy(config.Mqtt.Tls.ClientKey, root["mqtt_client_key"].as().c_str(), sizeof(config.Mqtt.Tls.ClientKey));
- config.Mqtt.Port = root["mqtt_port"].as();
- strlcpy(config.Mqtt.Hostname, root["mqtt_hostname"].as().c_str(), sizeof(config.Mqtt.Hostname));
- strlcpy(config.Mqtt.ClientId, root["mqtt_clientid"].as().c_str(), sizeof(config.Mqtt.ClientId));
- strlcpy(config.Mqtt.Username, root["mqtt_username"].as().c_str(), sizeof(config.Mqtt.Username));
- strlcpy(config.Mqtt.Password, root["mqtt_password"].as().c_str(), sizeof(config.Mqtt.Password));
- strlcpy(config.Mqtt.Lwt.Topic, root["mqtt_lwt_topic"].as().c_str(), sizeof(config.Mqtt.Lwt.Topic));
- strlcpy(config.Mqtt.Lwt.Value_Online, root["mqtt_lwt_online"].as().c_str(), sizeof(config.Mqtt.Lwt.Value_Online));
- strlcpy(config.Mqtt.Lwt.Value_Offline, root["mqtt_lwt_offline"].as().c_str(), sizeof(config.Mqtt.Lwt.Value_Offline));
- config.Mqtt.Lwt.Qos = root["mqtt_lwt_qos"].as();
- config.Mqtt.PublishInterval = root["mqtt_publish_interval"].as();
- config.Mqtt.CleanSession = root["mqtt_clean_session"].as();
- config.Mqtt.Hass.Enabled = root["mqtt_hass_enabled"].as();
- config.Mqtt.Hass.Expire = root["mqtt_hass_expire"].as();
- config.Mqtt.Hass.Retain = root["mqtt_hass_retain"].as();
- config.Mqtt.Hass.IndividualPanels = root["mqtt_hass_individualpanels"].as();
- strlcpy(config.Mqtt.Hass.Topic, root["mqtt_hass_topic"].as().c_str(), sizeof(config.Mqtt.Hass.Topic));
-
- // Check if base topic was changed
- if (strcmp(config.Mqtt.Topic, root["mqtt_topic"].as().c_str())) {
- MqttHandleInverter.unsubscribeTopics();
- strlcpy(config.Mqtt.Topic, root["mqtt_topic"].as().c_str(), sizeof(config.Mqtt.Topic));
- MqttHandleInverter.subscribeTopics();
+ {
+ auto guard = Configuration.getWriteGuard();
+ auto& config = guard.getConfig();
+
+ config.Mqtt.Enabled = root["mqtt_enabled"].as();
+ config.Mqtt.Retain = root["mqtt_retain"].as();
+ config.Mqtt.Tls.Enabled = root["mqtt_tls"].as();
+ strlcpy(config.Mqtt.Tls.RootCaCert, root["mqtt_root_ca_cert"].as().c_str(), sizeof(config.Mqtt.Tls.RootCaCert));
+ config.Mqtt.Tls.CertLogin = root["mqtt_tls_cert_login"].as();
+ strlcpy(config.Mqtt.Tls.ClientCert, root["mqtt_client_cert"].as().c_str(), sizeof(config.Mqtt.Tls.ClientCert));
+ strlcpy(config.Mqtt.Tls.ClientKey, root["mqtt_client_key"].as().c_str(), sizeof(config.Mqtt.Tls.ClientKey));
+ config.Mqtt.Port = root["mqtt_port"].as();
+ strlcpy(config.Mqtt.Hostname, root["mqtt_hostname"].as().c_str(), sizeof(config.Mqtt.Hostname));
+ strlcpy(config.Mqtt.ClientId, root["mqtt_clientid"].as().c_str(), sizeof(config.Mqtt.ClientId));
+ strlcpy(config.Mqtt.Username, root["mqtt_username"].as().c_str(), sizeof(config.Mqtt.Username));
+ strlcpy(config.Mqtt.Password, root["mqtt_password"].as().c_str(), sizeof(config.Mqtt.Password));
+ strlcpy(config.Mqtt.Lwt.Topic, root["mqtt_lwt_topic"].as().c_str(), sizeof(config.Mqtt.Lwt.Topic));
+ strlcpy(config.Mqtt.Lwt.Value_Online, root["mqtt_lwt_online"].as().c_str(), sizeof(config.Mqtt.Lwt.Value_Online));
+ strlcpy(config.Mqtt.Lwt.Value_Offline, root["mqtt_lwt_offline"].as().c_str(), sizeof(config.Mqtt.Lwt.Value_Offline));
+ config.Mqtt.Lwt.Qos = root["mqtt_lwt_qos"].as();
+ config.Mqtt.PublishInterval = root["mqtt_publish_interval"].as();
+ config.Mqtt.CleanSession = root["mqtt_clean_session"].as();
+ config.Mqtt.Hass.Enabled = root["mqtt_hass_enabled"].as();
+ config.Mqtt.Hass.Expire = root["mqtt_hass_expire"].as();
+ config.Mqtt.Hass.Retain = root["mqtt_hass_retain"].as();
+ config.Mqtt.Hass.IndividualPanels = root["mqtt_hass_individualpanels"].as();
+ strlcpy(config.Mqtt.Hass.Topic, root["mqtt_hass_topic"].as().c_str(), sizeof(config.Mqtt.Hass.Topic));
+
+ // Check if base topic was changed
+ if (strcmp(config.Mqtt.Topic, root["mqtt_topic"].as().c_str())) {
+ MqttHandleInverter.unsubscribeTopics();
+ strlcpy(config.Mqtt.Topic, root["mqtt_topic"].as().c_str(), sizeof(config.Mqtt.Topic));
+ MqttHandleInverter.subscribeTopics();
+ }
}
WebApi.writeConfig(retMsg);
diff --git a/src/WebApi_network.cpp b/src/WebApi_network.cpp
index 75275755f..51db32e43 100644
--- a/src/WebApi_network.cpp
+++ b/src/WebApi_network.cpp
@@ -164,37 +164,41 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
return;
}
- CONFIG_T& config = Configuration.get();
- config.WiFi.Ip[0] = ipaddress[0];
- config.WiFi.Ip[1] = ipaddress[1];
- config.WiFi.Ip[2] = ipaddress[2];
- config.WiFi.Ip[3] = ipaddress[3];
- config.WiFi.Netmask[0] = netmask[0];
- config.WiFi.Netmask[1] = netmask[1];
- config.WiFi.Netmask[2] = netmask[2];
- config.WiFi.Netmask[3] = netmask[3];
- config.WiFi.Gateway[0] = gateway[0];
- config.WiFi.Gateway[1] = gateway[1];
- config.WiFi.Gateway[2] = gateway[2];
- config.WiFi.Gateway[3] = gateway[3];
- config.WiFi.Dns1[0] = dns1[0];
- config.WiFi.Dns1[1] = dns1[1];
- config.WiFi.Dns1[2] = dns1[2];
- config.WiFi.Dns1[3] = dns1[3];
- config.WiFi.Dns2[0] = dns2[0];
- config.WiFi.Dns2[1] = dns2[1];
- config.WiFi.Dns2[2] = dns2[2];
- config.WiFi.Dns2[3] = dns2[3];
- strlcpy(config.WiFi.Ssid, root["ssid"].as().c_str(), sizeof(config.WiFi.Ssid));
- strlcpy(config.WiFi.Password, root["password"].as().c_str(), sizeof(config.WiFi.Password));
- strlcpy(config.WiFi.Hostname, root["hostname"].as().c_str(), sizeof(config.WiFi.Hostname));
- if (root["dhcp"].as()) {
- config.WiFi.Dhcp = true;
- } else {
- config.WiFi.Dhcp = false;
+ {
+ auto guard = Configuration.getWriteGuard();
+ auto& config = guard.getConfig();
+
+ config.WiFi.Ip[0] = ipaddress[0];
+ config.WiFi.Ip[1] = ipaddress[1];
+ config.WiFi.Ip[2] = ipaddress[2];
+ config.WiFi.Ip[3] = ipaddress[3];
+ config.WiFi.Netmask[0] = netmask[0];
+ config.WiFi.Netmask[1] = netmask[1];
+ config.WiFi.Netmask[2] = netmask[2];
+ config.WiFi.Netmask[3] = netmask[3];
+ config.WiFi.Gateway[0] = gateway[0];
+ config.WiFi.Gateway[1] = gateway[1];
+ config.WiFi.Gateway[2] = gateway[2];
+ config.WiFi.Gateway[3] = gateway[3];
+ config.WiFi.Dns1[0] = dns1[0];
+ config.WiFi.Dns1[1] = dns1[1];
+ config.WiFi.Dns1[2] = dns1[2];
+ config.WiFi.Dns1[3] = dns1[3];
+ config.WiFi.Dns2[0] = dns2[0];
+ config.WiFi.Dns2[1] = dns2[1];
+ config.WiFi.Dns2[2] = dns2[2];
+ config.WiFi.Dns2[3] = dns2[3];
+ strlcpy(config.WiFi.Ssid, root["ssid"].as().c_str(), sizeof(config.WiFi.Ssid));
+ strlcpy(config.WiFi.Password, root["password"].as().c_str(), sizeof(config.WiFi.Password));
+ strlcpy(config.WiFi.Hostname, root["hostname"].as().c_str(), sizeof(config.WiFi.Hostname));
+ if (root["dhcp"].as()) {
+ config.WiFi.Dhcp = true;
+ } else {
+ config.WiFi.Dhcp = false;
+ }
+ config.WiFi.ApTimeout = root["aptimeout"].as();
+ config.Mdns.Enabled = root["mdnsenabled"].as();
}
- config.WiFi.ApTimeout = root["aptimeout"].as();
- config.Mdns.Enabled = root["mdnsenabled"].as();
WebApi.writeConfig(retMsg);
diff --git a/src/WebApi_ntp.cpp b/src/WebApi_ntp.cpp
index 5dc874b53..f58a2bbd6 100644
--- a/src/WebApi_ntp.cpp
+++ b/src/WebApi_ntp.cpp
@@ -135,13 +135,17 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
return;
}
- CONFIG_T& config = Configuration.get();
- strlcpy(config.Ntp.Server, root["ntp_server"].as().c_str(), sizeof(config.Ntp.Server));
- strlcpy(config.Ntp.Timezone, root["ntp_timezone"].as().c_str(), sizeof(config.Ntp.Timezone));
- strlcpy(config.Ntp.TimezoneDescr, root["ntp_timezone_descr"].as().c_str(), sizeof(config.Ntp.TimezoneDescr));
- config.Ntp.Latitude = root["latitude"].as();
- config.Ntp.Longitude = root["longitude"].as();
- config.Ntp.SunsetType = root["sunsettype"].as();
+ {
+ auto guard = Configuration.getWriteGuard();
+ auto& config = guard.getConfig();
+
+ strlcpy(config.Ntp.Server, root["ntp_server"].as().c_str(), sizeof(config.Ntp.Server));
+ strlcpy(config.Ntp.Timezone, root["ntp_timezone"].as().c_str(), sizeof(config.Ntp.Timezone));
+ strlcpy(config.Ntp.TimezoneDescr, root["ntp_timezone_descr"].as().c_str(), sizeof(config.Ntp.TimezoneDescr));
+ config.Ntp.Latitude = root["latitude"].as();
+ config.Ntp.Longitude = root["longitude"].as();
+ config.Ntp.SunsetType = root["sunsettype"].as();
+ }
WebApi.writeConfig(retMsg);
diff --git a/src/WebApi_security.cpp b/src/WebApi_security.cpp
index 6be21ca6a..8ebd6fb6a 100644
--- a/src/WebApi_security.cpp
+++ b/src/WebApi_security.cpp
@@ -64,9 +64,13 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request)
return;
}
- CONFIG_T& config = Configuration.get();
- strlcpy(config.Security.Password, root["password"].as().c_str(), sizeof(config.Security.Password));
- config.Security.AllowReadonly = root["allow_readonly"].as();
+ {
+ auto guard = Configuration.getWriteGuard();
+ auto& config = guard.getConfig();
+
+ strlcpy(config.Security.Password, root["password"].as().c_str(), sizeof(config.Security.Password));
+ config.Security.AllowReadonly = root["allow_readonly"].as();
+ }
WebApi.writeConfig(retMsg);
diff --git a/src/main.cpp b/src/main.cpp
index b1d974d74..7b04e7cd6 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -65,11 +65,11 @@ void setup()
MessageOutput.println("done");
}
+ Configuration.init(scheduler);
+
// Read configuration values
MessageOutput.print("Reading configuration... ");
if (!Configuration.read()) {
- MessageOutput.print("initializing... ");
- Configuration.init();
if (Configuration.write()) {
MessageOutput.print("written... ");
} else {
@@ -146,19 +146,6 @@ void setup()
LedSingle.init(scheduler);
MessageOutput.println("done");
- // Check for default DTU serial
- MessageOutput.print("Check for default DTU serial... ");
- if (config.Dtu.Serial == DTU_SERIAL) {
- MessageOutput.print("generate serial based on ESP chip id: ");
- const uint64_t dtuId = Utils::generateDtuSerial();
- MessageOutput.printf("%0" PRIx32 "%08" PRIx32 "... ",
- ((uint32_t)((dtuId >> 32) & 0xFFFFFFFF)),
- ((uint32_t)(dtuId & 0xFFFFFFFF)));
- config.Dtu.Serial = dtuId;
- Configuration.write();
- }
- MessageOutput.println("done");
-
InverterSettings.init(scheduler);
Datastore.init(scheduler);
From 6113e0737b14e9792c3ab08bd5e3ac93ef1c474b Mon Sep 17 00:00:00 2001
From: Thomas Basler
Date: Fri, 25 Oct 2024 21:42:52 +0200
Subject: [PATCH 19/76] webapp: Fix: WaitRetstartView showed basic auth dialog
---
webapp/src/views/WaitRestartView.vue | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/webapp/src/views/WaitRestartView.vue b/webapp/src/views/WaitRestartView.vue
index 95640e4b4..5c9aa52cd 100644
--- a/webapp/src/views/WaitRestartView.vue
+++ b/webapp/src/views/WaitRestartView.vue
@@ -13,6 +13,7 @@
From 69c67f96e759cda77b68b875293788224578189a Mon Sep 17 00:00:00 2001
From: Thomas Basler
Date: Thu, 7 Nov 2024 18:16:18 +0100
Subject: [PATCH 64/76] webapp: Update dependencies
---
webapp/package.json | 2 +-
webapp/yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/webapp/package.json b/webapp/package.json
index 886ab2376..a646f6c0f 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -27,7 +27,7 @@
"@intlify/unplugin-vue-i18n": "^5.2.0",
"@tsconfig/node22": "^22.0.0",
"@types/bootstrap": "^5.2.10",
- "@types/node": "^22.8.6",
+ "@types/node": "^22.9.0",
"@types/pulltorefreshjs": "^0.1.7",
"@types/sortablejs": "^1.15.8",
"@types/spark-md5": "^3.0.5",
diff --git a/webapp/yarn.lock b/webapp/yarn.lock
index 044576e38..59f19e748 100644
--- a/webapp/yarn.lock
+++ b/webapp/yarn.lock
@@ -492,10 +492,10 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
-"@types/node@^22.8.6":
- version "22.8.6"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-22.8.6.tgz#e8a0c0871623283d8b3ef7d7b9b1bfdfd3028e22"
- integrity sha512-tosuJYKrIqjQIlVCM4PEGxOmyg3FCPa/fViuJChnGeEIhjA46oy8FMVoF9su1/v8PNs2a8Q0iFNyOx0uOF91nw==
+"@types/node@^22.9.0":
+ version "22.9.0"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-22.9.0.tgz#b7f16e5c3384788542c72dc3d561a7ceae2c0365"
+ integrity sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==
dependencies:
undici-types "~6.19.8"
From 63405a712c02ec26e8ec3a0ae1b2d160ae736398 Mon Sep 17 00:00:00 2001
From: Thomas Basler
Date: Thu, 7 Nov 2024 18:21:59 +0100
Subject: [PATCH 65/76] Upgrade ESPAsyncWebServer from 3.3.21 to 3.3.22
---
platformio.ini | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/platformio.ini b/platformio.ini
index 1c1964b4e..c1afca113 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -41,7 +41,7 @@ build_unflags =
-std=gnu++11
lib_deps =
- mathieucarbou/ESPAsyncWebServer @ 3.3.21
+ mathieucarbou/ESPAsyncWebServer @ 3.3.22
bblanchon/ArduinoJson @ 7.2.0
https://github.com/bertmelis/espMqttClient.git#v1.7.0
nrf24/RF24 @ 1.4.10
From 9a53d6e209aa4e1c0a9208cd3ae5bb692e6398a0 Mon Sep 17 00:00:00 2001
From: Thomas Basler
Date: Thu, 7 Nov 2024 18:30:29 +0100
Subject: [PATCH 66/76] Fix lint errors
---
src/Configuration.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Configuration.cpp b/src/Configuration.cpp
index 888a53670..dd8ff158a 100644
--- a/src/Configuration.cpp
+++ b/src/Configuration.cpp
@@ -335,8 +335,8 @@ bool ConfigurationClass::read()
MessageOutput.print("generate serial based on ESP chip id: ");
const uint64_t dtuId = Utils::generateDtuSerial();
MessageOutput.printf("%0" PRIx32 "%08" PRIx32 "... ",
- ((uint32_t)((dtuId >> 32) & 0xFFFFFFFF)),
- ((uint32_t)(dtuId & 0xFFFFFFFF)));
+ static_cast((dtuId >> 32) & 0xFFFFFFFF),
+ static_cast(dtuId & 0xFFFFFFFF));
config.Dtu.Serial = dtuId;
write();
}
From ecb5e9cc3233bcd04b17134f2d88058e533ccaae Mon Sep 17 00:00:00 2001
From: Thomas Basler
Date: Thu, 7 Nov 2024 19:10:41 +0100
Subject: [PATCH 67/76] Build factory.bin in every compile attempt
This is required to apply changes which are maybe only related to the data directory.
---
pio-scripts/create_factory_bin.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pio-scripts/create_factory_bin.py b/pio-scripts/create_factory_bin.py
index c0ef4a8cb..ec8274e21 100644
--- a/pio-scripts/create_factory_bin.py
+++ b/pio-scripts/create_factory_bin.py
@@ -130,4 +130,4 @@ def esp32_create_combined_bin(source, target, env):
esptool.main(cmd)
-env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)
+env.AddPostAction("buildprog", esp32_create_combined_bin)
From 3dc70ab40aade8b7eb9ed9a1c6605ca326299d18 Mon Sep 17 00:00:00 2001
From: Thomas Basler
Date: Thu, 7 Nov 2024 19:13:45 +0100
Subject: [PATCH 68/76] webapp: add app.js.gz
---
webapp_dist/js/app.js.gz | Bin 190874 -> 192821 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
diff --git a/webapp_dist/js/app.js.gz b/webapp_dist/js/app.js.gz
index 2bd91de77cb1181444891cc39861ea4a3c7bd540..860e9ee453ac65055484f9e58dc060ba0be0f029 100644
GIT binary patch
delta 192440
zcmYhC18g8))b`7*?QU&*YrEa8?QU&b({9~v?QU(`wr$(C@%8`Z{qnsxnPet8cP3}<
zFEc0ic~0sqT>TClKo|}U^4|c1J!@Z$-5rXddZLxDPf*GJC%7vdo$~9nCM9Nk8J|@O
zDVGb$utb*?8APf`@DH4Cg3+vE1ED?tme;7yh|jR*ns1XE1elSkb3K_(OB^&35|S&@
zW0RZPn~fv4k&Xu8M(ZL+VT0XD1$FJkxYD9_#s0zjzgd8ok$pdVX!Gr&@WO%aBqDBc
z*<25g`|*X#I_-e+VRbT<V=&f0tN
zMEIla2l;BgjF!{HAjjuz=ST44)%Eyn%LL=1Vd~-j@6+TO@4Rhom1rjy9n@pTlI7%nYZ#Qli%3ZBSyjQfDlGV>M|bwXUCR
zyZNkO?7J*(3oM#Hn6J(iigZ|ny_xH9xKFzU5C9hXwI^^A}Se^WB9nAXc_-Tno`<