From 92de57a7602e7d3bd401b4f58cb5eb07c3e65318 Mon Sep 17 00:00:00 2001 From: Emanuele Trabattoni Date: Thu, 17 Jul 2025 18:01:03 +0200 Subject: [PATCH] Implemented config file and save to memory using ffat --- data/example.json | 3 + fatfs_partition.csv | 6 + platformio.ini | 7 + src/config.h | 306 ++++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 42 +++--- 5 files changed, 347 insertions(+), 17 deletions(-) create mode 100644 data/example.json create mode 100644 fatfs_partition.csv create mode 100644 src/config.h diff --git a/data/example.json b/data/example.json new file mode 100644 index 0000000..a5ae80c --- /dev/null +++ b/data/example.json @@ -0,0 +1,3 @@ +{ + "data": "value" +} \ No newline at end of file diff --git a/fatfs_partition.csv b/fatfs_partition.csv new file mode 100644 index 0000000..92ac93a --- /dev/null +++ b/fatfs_partition.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x300000, +app1, app, ota_1, 0x310000,0x300000, +ffat, data, fat, 0x610000,0x9E0000, diff --git a/platformio.ini b/platformio.ini index c1516cf..ba3b0ac 100644 --- a/platformio.ini +++ b/platformio.ini @@ -20,6 +20,10 @@ lib_deps = hideakitai/DebugLog@^0.8.4 build_type = release +board_build.filesystem = ffat +board_build.partitions = fatfs_partition.csv ; se stai usando uno custom + + [env:esp32-s3-waveshare8-debug] platform = ${env:esp32-s3-waveshare8.platform} board = ${env:esp32-s3-waveshare8.board} @@ -39,3 +43,6 @@ build_flags = -fno-ipa-sra -fno-tree-sra -fno-builtin + +board_build.filesystem = ffat +board_build.partitions = fatfs_partition.csv ; se stai usando uno custom diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..51efa7e --- /dev/null +++ b/src/config.h @@ -0,0 +1,306 @@ +#pragma once + +#define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG + +#include +#include +#include +#include + +#include + +class FSmount +{ +public: + FSmount() + { + if (!FFat.begin(false)) + { + LOG_ERROR("Unable to mount filesystem without formatting"); + if (!FFat.begin(true)) + { + LOG_ERROR("Formatted and mounted filesystem"); + } + } + + LOG_INFO("Local Filesystem Mounted Correctly"); + const auto totalBytes = FFat.totalBytes(); + const auto freeBytes = FFat.freeBytes(); + const auto usedBytes = FFat.usedBytes(); + const auto mountPoint = FFat.mountpoint(); + LOG_INFO("Local filesystem, total", totalBytes / 1024, "KB - used", usedBytes / 1024, "KB - free", freeBytes / 1024, "KB"); + LOG_INFO("Local filesystem, mountpoint", mountPoint); + } + + ~FSmount() + { + FFat.end(); // unmout filesystem to avoid corruption + LOG_INFO("Local Filesystem Unmounted Correctly"); + } +}; + +class Config +{ +public: + Config(const Config &) = delete; + Config &operator=(const Config &) = delete; + + Config() + { + FSmount mount; // scoped mount of the filesystem + + // Initialize and mount filesystem + LOG_INFO("Initializing Config"); + + if (!FFat.exists("/config.json")) + { + LOG_WARN("Initializing default config"); + saveConfig(); + } + + File file = FFat.open("/config.json", FILE_READ, false); + if (!file) + { + LOG_ERROR("Unable to open config.json"); + return; + } + + if (ArduinoJson::deserializeJson(m_configJson, file) != ArduinoJson::DeserializationError::Ok) + { + LOG_ERROR("Unable to load config.json"); + } + + std::string loadedConf; + ArduinoJson::serializeJsonPretty(m_configJson, loadedConf); + LOG_INFO("Loaded Configuration\n", loadedConf.c_str()); + + deserialize(); // convert from json format to class members + file.close(); // close config file before unmounting filesystem + }; + + void updateConfig(ArduinoJson::JsonDocument &json) + { + std::lock_guard lock(m_mutex); + { + FSmount mount; + m_configJson = json; + deserialize(); + saveConfig(); + }; // filesystem is unmounted here + delay(500); + esp_restart(); // configuration updates trigger a cpu restart + } + + void resetConfig() + { + std::lock_guard lock(m_mutex); + { + FSmount mount; + LOG_WARN("Removing config.json"); + if (!FFat.remove("/config.json")) + { + LOG_ERROR("Unable to remove config.json"); + } + LOG_WARN("Configuration reset, Restarting"); + }; //filesystem is unmounted here + delay(500); + esp_restart(); + } + +private: + void saveConfig() // write configuration to flash memory + { + File file = FFat.open("/config.json", FILE_WRITE, true); + if (!file) + { + LOG_ERROR("Unable to open config.json for writing"); + return; + } + serialize(); // serialize default configuration + if (ArduinoJson::serializeJson(m_configJson, file) == 0) + { + LOG_ERROR("Serialization Failed"); + } + file.close(); + } + + ////////////////////////////////////////////////////////////// + ////////////// SERIALIZATION + DESERIALIZATION /////////////// + ////////////////////////////////////////////////////////////// + + void serialize() + { + // form class members to json document + { + auto globals = m_configJson["globals"].to(); + globals["loopDelay"] = m_globalLoopDelay; + }; + + { + auto ethernet = m_configJson["ethernet"].to(); + ethernet["hostname"] = m_ethHostname; + ethernet["ipAddr"] = m_ethIpAddr; + ethernet["netmask "] = m_ethNetmask; + ethernet["gateway "] = m_ethGateway; + }; + + { + auto modbus = m_configJson["modbus"].to(); + modbus["relayAddr"] = m_modbusRelayAddr; + modbus["temperatureAddr"] = m_modbusTemperatureAddr; + modbus["senecaAddr"] = m_modbusSenecaAddr; + modbus["flowmeterAddr"] = m_modbusFlowmeterAddr; + modbus["tankLevelAddr"] = m_modbusTankLevelAddr; + }; + + { + auto temperature = m_configJson["temperature"].to(); + temperature["expectedSensors"] = m_tempExpectedSensors; + auto values = temperature["correctionValues"].to(); + for (auto v : m_tempCorrectionValues) + { + values.add(v); + } + }; + + { + auto ntp = m_configJson["ntp"].to(); + ntp["pool"] = m_ntpPool; + ntp["timezone"] = m_ntpTimezone; + ntp["updateInterval"] = m_ntpUpdateInterval; + ntp["retries"] = m_ntpRetries; + }; + + { + auto mqtt = m_configJson["mqtt"].to(); + mqtt["host"] = m_mqttHost; + mqtt["port"] = m_mqttPort; + mqtt["loopTime"] = m_mqttLoopTime; + mqtt["clientName"] = m_mqttClientName; + mqtt["retries"] = m_mqttRetries; + auto publish = mqtt["publish"].to(); + for (auto v : m_mqttSubscribe) + { + publish[v.first] = v.second; + } + auto subscribe = mqtt["subscribe"].to(); + for (auto v : m_mqttPublish) + { + subscribe[v.first] = v.second; + } + }; + }; + + void deserialize() + { // from json document to class members + if (m_configJson.isNull()) + { + LOG_ERROR("NUll config document"); + return; + } + + { + auto globals = m_configJson["globals"]; + m_globalLoopDelay = globals["loopDelay"].as(); + }; + + { + auto ethernet = m_configJson["ethernet"]; + m_ethHostname = ethernet["hostname"].as(); + m_ethIpAddr = ethernet["ipAddr"].as(); + m_ethNetmask = ethernet["netmask"].as(); + m_ethGateway = ethernet["gateway"].as(); + }; + + { + auto modbus = m_configJson["modbus"]; + m_modbusRelayAddr = modbus["relayAddr"].as(); + m_modbusTemperatureAddr = modbus["temperatureAddr"].as(); + m_modbusSenecaAddr = modbus["senecaAddr"].as(); + m_modbusFlowmeterAddr = modbus["flowmeterAddr"].as(); + m_modbusTankLevelAddr = modbus["tankLevelAddr"].as(); + }; + + { + auto temperature = m_configJson["temperature"]; + m_tempExpectedSensors = temperature["expectedSensors"].as(); + auto values = temperature["correctionValues"].as(); + m_tempCorrectionValues.reserve(values.size()); + for (auto v : values) + { + m_tempCorrectionValues.push_back(v.as()); + } + }; + + { + auto ntp = m_configJson["ntp"]; + m_ntpPool = ntp["pool"].as(); + m_ntpTimezone = ntp["timezone"].as(); + m_ntpUpdateInterval = ntp["updateInterval"].as(); + m_ntpRetries = ntp["retries"].as(); + }; + + { + auto mqtt = m_configJson["mqtt"]; + m_mqttHost = mqtt["host"].as(); + m_mqttPort = mqtt["port"].as(); + m_mqttLoopTime = mqtt["loopTime"].as(); + m_mqttRetries = mqtt["retries"].as(); + auto subscribe = mqtt["subsribe"].as(); + for (auto v : subscribe) + { + m_mqttSubscribe[v.key().c_str()] = v.value().as(); + } + auto publish = mqtt["publish"].as(); + for (auto v : publish) + { + m_mqttPublish[v.key().c_str()] = v.value().as(); + } + }; + }; + +private: + ArduinoJson::JsonDocument m_configJson; + std::mutex m_mutex; + +public: + // Globals + std::uint16_t m_globalLoopDelay = 1000; // in milliseconds + + // Ethernet + std::string m_ethHostname = "ETcontroller_PRO"; + std::string m_ethIpAddr = "10.0.2.251"; + std::string m_ethNetmask = "255.255.255.0"; + std::string m_ethGateway = "10.0.2.1"; + + // MODBUS + uint8_t m_modbusRelayAddr = 0x01; + uint8_t m_modbusTemperatureAddr = 0xAA; + uint8_t m_modbusSenecaAddr = 0xBB; + uint8_t m_modbusFlowmeterAddr = 0xCC; + uint8_t m_modbusTankLevelAddr = 0xDD; + + // Temperature Board + uint8_t m_tempExpectedSensors = 1; + std::vector m_tempCorrectionValues = std::vector(8, 0.0f); + + // NTP + std::string m_ntpPool = "pool.ntp.org"; + uint16_t m_ntpTimezone = 3600; // GTM +1 + uint16_t m_ntpUpdateInterval = 3600; // every hour + uint8_t m_ntpRetries = 5; + + // MQTT + std::string m_mqttHost = "10.0.2.249"; + uint16_t m_mqttPort = 1883; + uint16_t m_mqttLoopTime = 100; // in milliseconds + uint8_t m_mqttRetries = 5; + std::string m_mqttClientName = "etcontrollerPRO"; + + std::map m_mqttSubscribe = { + {"commands", "test/etcontroller/commands"}}; + std::map m_mqttPublish = { + {"heatpump", "test/etcontroller/heatpump"}, + {"temperature", "test/etcontroller/temperatures"}, + {"irrigation", "test/etcontroller/irrigation"}}; +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index a3933b3..880b6bb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,9 +2,11 @@ #include #include + #include #include +#include #include #include #include @@ -36,7 +38,6 @@ void myTask(void *mqtt) vTaskDelete(NULL); // delete the current task }; -/////////////// GLOBALS /////////////// void setup() { Serial.begin(9600); @@ -45,12 +46,11 @@ void setup() void loop() { - const uint8_t tempBoardAddr(0xAA); - const uint8_t relayBoardAddr(0x01); - const uint8_t senecaMeterAddr(0xBB); + /////////////// GLOBALS /////////////// + Config conf = Config(); + /////////////// GLOBALS /////////////// const uint8_t baseRegister(0x00); uint16_t k(0); - uint8_t ethRetries(0); uint8_t sensors(0); bool buzzing(false); @@ -59,12 +59,12 @@ void loop() auto i2c = drivers::I2C(); auto bus = drivers::MODBUS(9600, SERIAL_8N1); auto rtc = drivers::PCF85063(i2c, PCF85063_ADDRESS); - auto eth = drivers::Ethernet("waveshare-test"); - auto tmp = drivers::R4DCB08(bus, tempBoardAddr); + auto eth = drivers::Ethernet(conf.m_ethHostname); + auto tmp = drivers::R4DCB08(bus, conf.m_modbusTemperatureAddr); delay(100); - auto io = digitalIO(i2c, bus, {relayBoardAddr}); + auto io = digitalIO(i2c, bus, {conf.m_modbusRelayAddr}); delay(100); - auto seneca = drivers::S50140(bus, senecaMeterAddr); + auto seneca = drivers::S50140(bus, conf.m_modbusSenecaAddr); auto buzzer = drivers::Buzzer(); auto led = drivers::Led(); //////////////// DEVICES //////////////// @@ -76,14 +76,14 @@ void loop() // MQTT Test // NetworkClient tcp; PubSubClient mqtt(tcp); - mqtt.setServer("10.0.2.249", 1883); + mqtt.setServer(conf.m_mqttHost.c_str(), conf.m_mqttPort); mqtt.setCallback(callback); //////////////// NETWORK //////////////// //////////////// NETWORK //////////////// /////////////// CALLBACK //////////////// Network.onEvent( - [ð, &rtc, &mqtt, &buzzer, &led](arduino_event_id_t event, arduino_event_info_t info) -> void + [&conf, ð, &rtc, &mqtt, &buzzer, &led](arduino_event_id_t event, arduino_event_info_t info) -> void { eth.onEvent(event, info); // Arduino Ethernet event handler if (!eth.isConnected()) @@ -92,7 +92,7 @@ void loop() time_t ntpTime; uint8_t timeRetries(0); uint8_t mqttRetries(0); - while (timeRetries++ < 5) + while (timeRetries++ < conf.m_ntpRetries) { if (eth.getNtpTime(ntpTime) && rtc.setDatetime(drivers::PCF85063::fromEpoch(ntpTime))) { @@ -104,9 +104,9 @@ void loop() } break; } - while (mqttRetries++ < 5) + while (mqttRetries++ < conf.m_mqttRetries) { - if (!mqtt.connected() && mqtt.connect("esp32-client")) + if (!mqtt.connected() && mqtt.connect(conf.m_mqttClientName.c_str())) { mqtt.subscribe("test/esp32-in"); xTaskCreatePinnedToCore(myTask, "mqttLoop", 4096, &mqtt, 2, NULL, 1); @@ -144,7 +144,7 @@ void loop() drivers::S50140::powerinfo_t pinfo = seneca.getAll(); LOG_INFO("Power Info ==> V:", pinfo.v, "- A:", pinfo.a, "- W:", pinfo.pAct, "- F:", pinfo.f, "- Wh_t:", pinfo.whTot, "- Wh_p:", pinfo.whPar); - if (io.digitalIORead(0)) + if (io.digitalIORead(0)) // rosso { uint8_t regset(seneca.getRegset()); uint16_t countStat(seneca.getCounterStatus()); @@ -153,7 +153,7 @@ void loop() seneca.resetPartialCounters(); } delay(100); - if (io.digitalIORead(8)) + if (io.digitalIORead(8)) // blu { if (!buzzing) { @@ -170,7 +170,15 @@ void loop() LOG_INFO("Buzzing -> ", buzzing ? "True" : "False"); } - delay(2000); + if(io.digitalIORead(9)) { // verde + conf.resetConfig(); + } + + if(io.digitalIORead(10)) { // giallo + esp_restart(); + } + + delay(conf.m_globalLoopDelay); } ////////////////////////////////////////