Skip to content

Commit

Permalink
PoC: config write guard
Browse files Browse the repository at this point in the history
  • Loading branch information
schlimmchen committed Oct 17, 2024
1 parent dc5eb96 commit 9f4f4fa
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 149 deletions.
24 changes: 22 additions & 2 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

#include "PinMapping.h"
#include <cstdint>
#include <TaskSchedulerDeclarations.h>
#include <mutex>
#include <condition_variable>

#define CONFIG_FILENAME "/config.json"
#define CONFIG_VERSION 0x00011c00 // 0.1.28 // make sure to clean all after change
Expand Down Expand Up @@ -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<std::mutex> _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;
58 changes: 56 additions & 2 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down Expand Up @@ -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;
Configuration.write();
}
MessageOutput.println("done");

return true;
}

Expand Down Expand Up @@ -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++) {
Expand Down Expand Up @@ -439,4 +467,30 @@ void ConfigurationClass::deleteInverterById(const uint8_t id)
}
}

void ConfigurationClass::loop()
{
std::unique_lock<std::mutex> 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;
4 changes: 2 additions & 2 deletions src/WebApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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 {
Expand Down
35 changes: 20 additions & 15 deletions src/WebApi_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,23 +129,28 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
return;
}

CONFIG_T& config = Configuration.get();
bool performRestart = root["curPin"]["name"].as<String>() != config.Dev_PinMapping;

strlcpy(config.Dev_PinMapping, root["curPin"]["name"].as<String>().c_str(), sizeof(config.Dev_PinMapping));
config.Display.Rotation = root["display"]["rotation"].as<uint8_t>();
config.Display.PowerSafe = root["display"]["power_safe"].as<bool>();
config.Display.ScreenSaver = root["display"]["screensaver"].as<bool>();
config.Display.Contrast = root["display"]["contrast"].as<uint8_t>();
config.Display.Language = root["display"]["language"].as<uint8_t>();
config.Display.Diagram.Duration = root["display"]["diagramduration"].as<uint32_t>();
config.Display.Diagram.Mode = root["display"]["diagrammode"].as<DiagramMode_t>();

for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
config.Led_Single[i].Brightness = root["led"][i]["brightness"].as<uint8_t>();
config.Led_Single[i].Brightness = min<uint8_t>(100, config.Led_Single[i].Brightness);
{
auto guard = Configuration.getWriteGuard();
auto& config = guard.getConfig();

strlcpy(config.Dev_PinMapping, root["curPin"]["name"].as<String>().c_str(), sizeof(config.Dev_PinMapping));
config.Display.Rotation = root["display"]["rotation"].as<uint8_t>();
config.Display.PowerSafe = root["display"]["power_safe"].as<bool>();
config.Display.ScreenSaver = root["display"]["screensaver"].as<bool>();
config.Display.Contrast = root["display"]["contrast"].as<uint8_t>();
config.Display.Language = root["display"]["language"].as<uint8_t>();
config.Display.Diagram.Duration = root["display"]["diagramduration"].as<uint32_t>();
config.Display.Diagram.Mode = root["display"]["diagrammode"].as<DiagramMode_t>();

for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
config.Led_Single[i].Brightness = root["led"][i]["brightness"].as<uint8_t>();
config.Led_Single[i].Brightness = min<uint8_t>(100, config.Led_Single[i].Brightness);
}
}

auto const& config = Configuration.get();
bool performRestart = root["curPin"]["name"].as<String>() != config.Dev_PinMapping;

Display.setDiagramMode(static_cast<DiagramMode_t>(config.Display.Diagram.Mode));
Display.setOrientation(config.Display.Rotation);
Display.enablePowerSafe = config.Display.PowerSafe;
Expand Down
20 changes: 11 additions & 9 deletions src/WebApi_dtu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<uint32_t>();
config.Dtu.Nrf.PaLevel = root["nrf_palevel"].as<uint8_t>();
config.Dtu.Cmt.PaLevel = root["cmt_palevel"].as<int8_t>();
config.Dtu.Cmt.Frequency = root["cmt_frequency"].as<uint32_t>();
config.Dtu.Cmt.CountryMode = root["cmt_country"].as<CountryModeId_t>();
{
auto guard = Configuration.getWriteGuard();
auto& config = guard.getConfig();
config.Dtu.Serial = serial;
config.Dtu.PollInterval = root["pollinterval"].as<uint32_t>();
config.Dtu.Nrf.PaLevel = root["nrf_palevel"].as<uint8_t>();
config.Dtu.Cmt.PaLevel = root["cmt_palevel"].as<int8_t>();
config.Dtu.Cmt.Frequency = root["cmt_frequency"].as<uint32_t>();
config.Dtu.Cmt.CountryMode = root["cmt_country"].as<CountryModeId_t>();
}

WebApi.writeConfig(retMsg);

Expand Down
76 changes: 42 additions & 34 deletions src/WebApi_inverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>().c_str(), NULL, 16);
const uint64_t new_serial = strtoll(root["serial"].as<String>().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__);
Expand All @@ -209,37 +209,41 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
return;
}

INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[root["id"].as<uint8_t>()];

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<String>().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<uint16_t>();
inverter.channel[arrayCount].YieldTotalOffset = channel["yield_total_offset"].as<float>();
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<uint8_t>()];

old_serial = inverter.Serial;
inverter.Serial = new_serial;
strncpy(inverter.Name, root["name"].as<String>().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<uint16_t>();
inverter.channel[arrayCount].YieldTotalOffset = channel["yield_total_offset"].as<float>();
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<uint8_t>()];
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterBySerial(old_serial);

if (inv != nullptr && new_serial != old_serial) {
Expand Down Expand Up @@ -300,7 +304,7 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
}

uint8_t inverter_id = root["id"].as<uint8_t>();
INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[inverter_id];
INVERTER_CONFIG_T const& inverter = Configuration.get().Inverter[inverter_id];

Hoymiles.removeInverterBySerial(inverter.Serial);

Expand Down Expand Up @@ -337,13 +341,17 @@ void WebApiInverterClass::onInverterOrder(AsyncWebServerRequest* request)
// The order array contains list or id in the right order
JsonArray orderArray = root["order"].as<JsonArray>();
uint8_t order = 0;
for (JsonVariant id : orderArray) {
uint8_t inverter_id = id.as<uint8_t>();
if (inverter_id < INV_MAX_COUNT) {
INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[inverter_id];
inverter.Order = order;
{
auto guard = Configuration.getWriteGuard();
auto& config = guard.getConfig();
for (JsonVariant id : orderArray) {
uint8_t inverter_id = id.as<uint8_t>();
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!");
Expand Down
Loading

0 comments on commit 9f4f4fa

Please sign in to comment.