From 583c82e4ab9d34b5c3f8cfa47d6daf116331e011 Mon Sep 17 00:00:00 2001 From: Marvin Carstensen Date: Sat, 6 Jul 2024 11:15:37 +0200 Subject: [PATCH 1/6] Prepared configuration for sd and datalogger --- include/Configuration.h | 14 ++++++++++++++ include/PinMapping.h | 6 ++++++ include/defaults.h | 7 +++++++ src/Configuration.cpp | 20 ++++++++++++++++++++ src/PinMapping.cpp | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 81 insertions(+) diff --git a/include/Configuration.h b/include/Configuration.h index 0ad2cb397..6cf12ddaa 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -30,6 +30,8 @@ #define DEV_MAX_MAPPING_NAME_STRLEN 63 +#define DATALOGGER_MAX_FILENAME_STRLEN 128 + struct CHANNEL_CONFIG_T { uint16_t MaxChannelPower; char Name[CHAN_MAX_NAME_STRLEN]; @@ -132,6 +134,18 @@ struct CONFIG_T { } Cmt; } Dtu; + struct { + bool Enabled; + uint32_t SaveInterval; + char FileName[DATALOGGER_MAX_FILENAME_STRLEN + 1]; + + struct { + bool TotalYieldTotal; + bool TotalYieldDay; + bool TotalPower; + } OutputConfig; + } DataLogger; + struct { char Password[WIFI_MAX_PASSWORD_STRLEN + 1]; bool AllowReadonly; diff --git a/include/PinMapping.h b/include/PinMapping.h index e0db88b6f..ab2a108ea 100644 --- a/include/PinMapping.h +++ b/include/PinMapping.h @@ -39,6 +39,11 @@ struct PinMapping_t { uint8_t display_cs; uint8_t display_reset; int8_t led[PINMAPPING_LED_COUNT]; + + int8_t sd_miso; + int8_t sd_mosi; + int8_t sd_clk; + int8_t sd_cs; }; class PinMappingClass { @@ -50,6 +55,7 @@ class PinMappingClass { bool isValidNrf24Config() const; bool isValidCmt2300Config() const; bool isValidEthConfig() const; + bool isValidSdConfig() const; private: PinMapping_t _pinMapping; diff --git a/include/defaults.h b/include/defaults.h index fd41a3d0b..5337aaf33 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -87,6 +87,13 @@ #define DTU_CMT_FREQUENCY 865000000U #define DTU_CMT_COUNTRY_MODE 0U +#define DATALOGGER_ENABLED false +#define DATALOGGER_SAVE_INTERVAL 300 +#define DATALOGGER_FILENAME "data.csv" +#define DATALOGGER_OUTPUT_TOTAL_YIELD_TOTAL true +#define DATALOGGER_OUTPUT_TOTAL_YIELD_DAY true +#define DATALOGGER_OUTPUT_TOTAL_POWER true + #define MQTT_HASS_ENABLED false #define MQTT_HASS_EXPIRE true #define MQTT_HASS_RETAIN true diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 780abb8f4..0b5f48783 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -93,6 +93,16 @@ bool ConfigurationClass::write() dtu["cmt_frequency"] = config.Dtu.Cmt.Frequency; dtu["cmt_country_mode"] = config.Dtu.Cmt.CountryMode; + JsonObject datalogger = doc["datalogger"].to(); + datalogger["datalogger_enabled"] = config.DataLogger.Enabled; + datalogger["saveinterval"] = config.DataLogger.SaveInterval; + datalogger["filename"] = config.DataLogger.FileName; + + JsonObject datalogger_output = datalogger["output_config"].to(); + datalogger_output["total_yield_total"] = config.DataLogger.OutputConfig.TotalYieldTotal; + datalogger_output["total_yield_day"] = config.DataLogger.OutputConfig.TotalYieldDay; + datalogger_output["total_power"] = config.DataLogger.OutputConfig.TotalPower; + JsonObject security = doc["security"].to(); security["password"] = config.Security.Password; security["allow_readonly"] = config.Security.AllowReadonly; @@ -267,6 +277,16 @@ bool ConfigurationClass::read() config.Dtu.Cmt.Frequency = dtu["cmt_frequency"] | DTU_CMT_FREQUENCY; config.Dtu.Cmt.CountryMode = dtu["cmt_country_mode"] | DTU_CMT_COUNTRY_MODE; + JsonObject datalogger = doc["datalogger"]; + config.DataLogger.Enabled = datalogger["datalogger_enabled"] | DATALOGGER_ENABLED; + config.DataLogger.SaveInterval = datalogger["saveinterval"] | DATALOGGER_SAVE_INTERVAL; + strlcpy(config.DataLogger.FileName, datalogger["filename"] | DATALOGGER_FILENAME, sizeof(config.DataLogger.FileName)); + + JsonObject datalogger_output = datalogger["output_config"]; + config.DataLogger.OutputConfig.TotalYieldTotal = datalogger_output["total_yield_total"] | DATALOGGER_OUTPUT_TOTAL_YIELD_TOTAL; + config.DataLogger.OutputConfig.TotalYieldTotal = datalogger_output["total_yield_total"] | DATALOGGER_OUTPUT_TOTAL_YIELD_DAY; + config.DataLogger.OutputConfig.TotalYieldTotal = datalogger_output["total_yield_total"] | DATALOGGER_OUTPUT_TOTAL_POWER; + JsonObject security = doc["security"]; strlcpy(config.Security.Password, security["password"] | ACCESS_POINT_PASSWORD, sizeof(config.Security.Password)); config.Security.AllowReadonly = security["allow_readonly"] | SECURITY_ALLOW_READONLY; diff --git a/src/PinMapping.cpp b/src/PinMapping.cpp index 74f282855..874728d05 100644 --- a/src/PinMapping.cpp +++ b/src/PinMapping.cpp @@ -84,6 +84,22 @@ #define CMT_SDIO -1 #endif +#ifndef SD_PIN_SCLK +#define SD_PIN_SCLK -1 +#endif + +#ifndef SD_PIN_CS +#define SD_PIN_CS -1 +#endif + +#ifndef SD_PIN_MISO +#define SD_PIN_MISO -1 +#endif + +#ifndef SD_PIN_MOSI +#define SD_PIN_MOSI -1 +#endif + PinMappingClass PinMapping; PinMappingClass::PinMappingClass() @@ -124,6 +140,11 @@ PinMappingClass::PinMappingClass() _pinMapping.led[0] = LED0; _pinMapping.led[1] = LED1; + + _pinMapping.sd_clk = SD_PIN_SCLK; + _pinMapping.sd_cs = SD_PIN_CS; + _pinMapping.sd_miso = SD_PIN_MISO; + _pinMapping.sd_mosi = SD_PIN_MOSI; } PinMapping_t& PinMappingClass::get() @@ -186,6 +207,11 @@ bool PinMappingClass::init(const String& deviceMapping) _pinMapping.led[0] = doc[i]["led"]["led0"] | LED0; _pinMapping.led[1] = doc[i]["led"]["led1"] | LED1; + _pinMapping.sd_clk = doc[i]["sd"]["clk"] | SD_PIN_SCLK; + _pinMapping.sd_cs = doc[i]["sd"]["cs"] | SD_PIN_CS; + _pinMapping.sd_miso = doc[i]["sd"]["miso"] | SD_PIN_MISO; + _pinMapping.sd_mosi = doc[i]["sd"]["mosi"] | SD_PIN_MOSI; + return true; } } @@ -215,3 +241,11 @@ bool PinMappingClass::isValidEthConfig() const { return _pinMapping.eth_enabled; } + +bool PinMappingClass::isValidSdConfig() const +{ + return _pinMapping.sd_clk >= 0 + && _pinMapping.sd_cs >= 0 + && _pinMapping.sd_miso >= 0 + && _pinMapping.sd_mosi >= 0; +} From fc4607f98e0f0ffbcd47d47654f5caced9454d0b Mon Sep 17 00:00:00 2001 From: Marvin Carstensen Date: Sat, 6 Jul 2024 11:18:05 +0200 Subject: [PATCH 2/6] Updated existing webapi to display sd capacity and updated the ui accordingly --- src/WebApi_sysstatus.cpp | 3 +++ webapp/src/components/MemoryInfo.vue | 2 ++ webapp/src/types/PinMapping.ts | 8 ++++++++ webapp/src/types/SystemStatus.ts | 2 ++ 4 files changed, 15 insertions(+) diff --git a/src/WebApi_sysstatus.cpp b/src/WebApi_sysstatus.cpp index 646922a60..b2180b5ca 100644 --- a/src/WebApi_sysstatus.cpp +++ b/src/WebApi_sysstatus.cpp @@ -13,6 +13,7 @@ #include #include #include +#include "SD.h" void WebApiSysstatusClass::init(AsyncWebServer& server, Scheduler& scheduler) { @@ -46,6 +47,8 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request) root["sketch_used"] = ESP.getSketchSize(); root["littlefs_total"] = LittleFS.totalBytes(); root["littlefs_used"] = LittleFS.usedBytes(); + root["sd_total"] = SD.totalBytes(); + root["sd_used"] = SD.usedBytes(); root["chiprevision"] = ESP.getChipRevision(); root["chipmodel"] = ESP.getChipModel(); diff --git a/webapp/src/components/MemoryInfo.vue b/webapp/src/components/MemoryInfo.vue index b9153dac6..9a5f801cf 100644 --- a/webapp/src/components/MemoryInfo.vue +++ b/webapp/src/components/MemoryInfo.vue @@ -20,6 +20,8 @@ :used="systemStatus.littlefs_used" /> + diff --git a/webapp/src/types/PinMapping.ts b/webapp/src/types/PinMapping.ts index 51bb908aa..1c467b6cb 100644 --- a/webapp/src/types/PinMapping.ts +++ b/webapp/src/types/PinMapping.ts @@ -39,6 +39,13 @@ export interface Links { url: string; } +export interface Sd { + miso: number; + mosi: number; + clk: number; + cs: number; +} + export interface Device { name: string; links: Array; @@ -46,6 +53,7 @@ export interface Device { cmt: Cmt2300; eth: Ethernet; display: Display; + sd: Sd; } export interface PinMapping extends Array{} \ No newline at end of file diff --git a/webapp/src/types/SystemStatus.ts b/webapp/src/types/SystemStatus.ts index 1ee726e65..1402d1dc1 100644 --- a/webapp/src/types/SystemStatus.ts +++ b/webapp/src/types/SystemStatus.ts @@ -31,6 +31,8 @@ export interface SystemStatus { psram_used: number; sketch_total: number; sketch_used: number; + sd_total: number; + sd_used: number; // RadioInfo nrf_configured: boolean; nrf_connected: boolean; From e9b1f7c4db4aee4f23944c53faeead5ed559edf7 Mon Sep 17 00:00:00 2001 From: Marvin Carstensen Date: Sat, 6 Jul 2024 11:19:00 +0200 Subject: [PATCH 3/6] Added datalogger WebApi --- include/WebApi.h | 2 + include/WebApi_datalogger.h | 14 +++++++ src/WebApi.cpp | 1 + src/WebApi_datalogger.cpp | 82 +++++++++++++++++++++++++++++++++++++ src/WebApi_device.cpp | 6 +++ 5 files changed, 105 insertions(+) create mode 100644 include/WebApi_datalogger.h create mode 100644 src/WebApi_datalogger.cpp diff --git a/include/WebApi.h b/include/WebApi.h index b6fdbd089..6bcb23165 100644 --- a/include/WebApi.h +++ b/include/WebApi.h @@ -5,6 +5,7 @@ #include "WebApi_device.h" #include "WebApi_devinfo.h" #include "WebApi_dtu.h" +#include "WebApi_datalogger.h" #include "WebApi_errors.h" #include "WebApi_eventlog.h" #include "WebApi_firmware.h" @@ -49,6 +50,7 @@ class WebApiClass { WebApiDeviceClass _webApiDevice; WebApiDevInfoClass _webApiDevInfo; WebApiDtuClass _webApiDtu; + WebApiDataLoggerClass _webApiDataLogger; WebApiEventlogClass _webApiEventlog; WebApiFirmwareClass _webApiFirmware; WebApiGridProfileClass _webApiGridprofile; diff --git a/include/WebApi_datalogger.h b/include/WebApi_datalogger.h new file mode 100644 index 000000000..d537d05f9 --- /dev/null +++ b/include/WebApi_datalogger.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include + +class WebApiDataLoggerClass { +public: + void init(AsyncWebServer& server, Scheduler& scheduler); + +private: + void onDataLoggerAdminGet(AsyncWebServerRequest* request); + void onDataLoggerAdminPost(AsyncWebServerRequest* request); +}; diff --git a/src/WebApi.cpp b/src/WebApi.cpp index 1a5b28709..7674d6611 100644 --- a/src/WebApi.cpp +++ b/src/WebApi.cpp @@ -19,6 +19,7 @@ void WebApiClass::init(Scheduler& scheduler) _webApiDevice.init(_server, scheduler); _webApiDevInfo.init(_server, scheduler); _webApiDtu.init(_server, scheduler); + _webApiDataLogger.init(_server, scheduler); _webApiEventlog.init(_server, scheduler); _webApiFirmware.init(_server, scheduler); _webApiGridprofile.init(_server, scheduler); diff --git a/src/WebApi_datalogger.cpp b/src/WebApi_datalogger.cpp new file mode 100644 index 000000000..126242eb4 --- /dev/null +++ b/src/WebApi_datalogger.cpp @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2022-2024 Thomas Basler and others + */ +#include "WebApi_datalogger.h" +#include "Configuration.h" +#include "Utils.h" +#include "WebApi.h" +#include "WebApi_errors.h" +#include +#include + +void WebApiDataLoggerClass::init(AsyncWebServer& server, Scheduler& scheduler) +{ + using std::placeholders::_1; + + server.on("/api/datalogger/config", HTTP_GET, std::bind(&WebApiDataLoggerClass::onDataLoggerAdminGet, this, _1)); + server.on("/api/datalogger/config", HTTP_POST, std::bind(&WebApiDataLoggerClass::onDataLoggerAdminPost, this, _1)); +} + +void WebApiDataLoggerClass::onDataLoggerAdminGet(AsyncWebServerRequest* request) +{ + if (!WebApi.checkCredentials(request)) { + return; + } + + AsyncJsonResponse* response = new AsyncJsonResponse(); + auto& root = response->getRoot(); + const CONFIG_T& config = Configuration.get(); + + root["datalogger_enabled"] = config.DataLogger.Enabled; + root["saveinterval"] = config.DataLogger.SaveInterval; + root["filename"] = config.DataLogger.FileName; + + auto outputConfig = root["output_config"].to(); + outputConfig["total_yield_total"] = config.DataLogger.OutputConfig.TotalYieldTotal; + outputConfig["total_yield_day"] = config.DataLogger.OutputConfig.TotalYieldDay; + outputConfig["total_power"] = config.DataLogger.OutputConfig.TotalPower; + + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); +} + +void WebApiDataLoggerClass::onDataLoggerAdminPost(AsyncWebServerRequest* request) +{ + if (!WebApi.checkCredentials(request)) { + return; + } + + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { + return; + } + + auto& retMsg = response->getRoot(); + + if (!(root.containsKey("datalogger_enabled") + && root.containsKey("saveinterval") + && root.containsKey("filename") + && root.containsKey("output_config"))) { + retMsg["message"] = "Values are missing!"; + retMsg["code"] = WebApiError::GenericValueMissing; + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); + return; + } + + CONFIG_T& config = Configuration.get(); + + config.DataLogger.Enabled = root["datalogger_enabled"].as(); + config.DataLogger.SaveInterval = root["saveinterval"].as(); + strlcpy(config.DataLogger.FileName, root["filename"].as().c_str(), sizeof(config.DataLogger.FileName)); + + config.DataLogger.OutputConfig.TotalYieldTotal = root["output_config"]["total_yield_total"].as(); + config.DataLogger.OutputConfig.TotalYieldDay = root["output_config"]["total_yield_day"].as(); + config.DataLogger.OutputConfig.TotalPower = root["output_config"]["total_power"].as(); + + WebApi.writeConfig(retMsg); + + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); + + Utils::restartDtu(); +} diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index 078d5b4a1..4566de349 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -86,6 +86,12 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) led["brightness"] = config.Led_Single[i].Brightness; } + auto sdPinObj = curPin["sd"].to(); + sdPinObj["clk"] = pin.sd_clk; + sdPinObj["cs"] = pin.sd_cs; + sdPinObj["miso"] = pin.sd_miso; + sdPinObj["mosi"] = pin.sd_mosi; + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } From 0c2a9cd4e2d01f874125378d8873ea01ffc30245 Mon Sep 17 00:00:00 2001 From: Marvin Carstensen Date: Sat, 6 Jul 2024 11:20:00 +0200 Subject: [PATCH 4/6] Added datalogger logic, lang and ui. --- include/DataLogger.h | 27 +++++ src/DataLogger.cpp | 101 ++++++++++++++++ src/main.cpp | 26 +++++ webapp/src/components/NavBar.vue | 3 + webapp/src/locales/de.json | 15 ++- webapp/src/locales/en.json | 14 ++- webapp/src/locales/fr.json | 14 ++- webapp/src/router/index.ts | 8 +- webapp/src/types/DataLoggerConfig.ts | 12 ++ webapp/src/views/DataLoggerAdminView.vue | 143 +++++++++++++++++++++++ 10 files changed, 359 insertions(+), 4 deletions(-) create mode 100644 include/DataLogger.h create mode 100644 src/DataLogger.cpp create mode 100644 webapp/src/types/DataLoggerConfig.ts create mode 100644 webapp/src/views/DataLoggerAdminView.vue diff --git a/include/DataLogger.h b/include/DataLogger.h new file mode 100644 index 000000000..b265098aa --- /dev/null +++ b/include/DataLogger.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include "FS.h" + +class DataLoggerClass { +public: + DataLoggerClass(); + void init(Scheduler& scheduler); + + void logToSDCard(); + void setSaveInterval(const uint32_t interval); +private: + void loop(); + + Task _loopTask; + + uint32_t _saveInterval = 0; + uint32_t _lastSave = 0; + + void writeFile(FS &fs, const String path, const char * message); + void appendFile(FS &fs, const String path, const char * message); + + unsigned long getTime(); +}; + +extern DataLoggerClass DataLogger; diff --git a/src/DataLogger.cpp b/src/DataLogger.cpp new file mode 100644 index 000000000..01b380c69 --- /dev/null +++ b/src/DataLogger.cpp @@ -0,0 +1,101 @@ +#include "DataLogger.h" +#include "Configuration.h" +#include "DataStore.h" +#include "MessageOutput.h" +#include "SD.h" + +DataLoggerClass DataLogger; + +DataLoggerClass::DataLoggerClass() + : _loopTask(1 * TASK_SECOND, TASK_FOREVER, std::bind(&DataLoggerClass::loop, this)) +{ +} + +void DataLoggerClass::init(Scheduler& scheduler) { + scheduler.addTask(_loopTask); + _loopTask.enable(); + + const CONFIG_T& config = Configuration.get(); + setSaveInterval(config.DataLogger.SaveInterval); + + if(strlen(config.DataLogger.FileName) == 0) { + File file = SD.open("/" + String(config.DataLogger.FileName)); + if(!file) { + MessageOutput.println("Datalogger: File doesn't exist"); + MessageOutput.println("Datalogger: Creating file..."); + writeFile(SD, "/" + String(config.DataLogger.FileName), "timestamp; totalPower; totalYieldDay; totalYieldTotal \r\n"); + } + else { + MessageOutput.println("Datalogger: File already exists"); + } + file.close(); + } else { + MessageOutput.println("Datalogger: Filename is empty"); + } + +} + +void DataLoggerClass::loop() +{ + if (millis() - _lastSave > (_saveInterval * 1000)) { + logToSDCard(); + + _lastSave = millis(); + } +} + +void DataLoggerClass::logToSDCard() { + const CONFIG_T& config = Configuration.get(); + + String dataMessage = String(getTime()) + ";" + String(config.DataLogger.OutputConfig.TotalPower ? Datastore.getTotalAcPowerEnabled() : 0) + ";" + String(config.DataLogger.OutputConfig.TotalYieldDay ? Datastore.getTotalAcYieldDayEnabled() : 0) + ";" + String(config.DataLogger.OutputConfig.TotalYieldTotal ? Datastore.getTotalAcYieldTotalEnabled() : 0) + " \r\n"; + MessageOutput.printf("DataLogger: %s saved. \n", dataMessage.c_str()); + appendFile(SD, "/" + String(config.DataLogger.FileName), dataMessage.c_str()); +} + +void DataLoggerClass::setSaveInterval(const uint32_t interval) +{ + _saveInterval = interval; +} + +void DataLoggerClass::writeFile(fs::FS &fs, const String path, const char * message) { + MessageOutput.printf("DataLogger: Writing file: %s\n", path.c_str()); + + File file = fs.open(path, FILE_WRITE); + if(!file) { + MessageOutput.println("Failed to open file for writing"); + return; + } + if(file.print(message)) { + MessageOutput.println("DataLogger: File written successfully"); + } else { + MessageOutput.println("DataLogger: Write failed"); + } + file.close(); +} + +void DataLoggerClass::appendFile(fs::FS &fs, const String path, const char * message) { + MessageOutput.printf("DataLogger: Appending to file: %s\n", path.c_str()); + + File file = fs.open(path, FILE_APPEND); + if(!file) { + MessageOutput.println("DataLogger: Failed to open file for appending"); + return; + } + if(file.print(message)) { + MessageOutput.println("DataLogger: Data appended"); + } else { + MessageOutput.println("DataLogger: Append failed"); + } + file.close(); +} + +unsigned long DataLoggerClass::getTime() { + time_t now; + struct tm timeinfo; + if (!getLocalTime(&timeinfo)) { + MessageOutput.println("DataLogger: Failed to fetch current time"); + return(0); + } + time(&now); + return now; +} diff --git a/src/main.cpp b/src/main.cpp index 433619e1f..a355a37c3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,6 +25,8 @@ #include #include #include +#include "SD.h" +#include "DataLogger.h" void setup() { @@ -85,6 +87,30 @@ void setup() const auto& pin = PinMapping.get(); MessageOutput.println("done"); + // Initialize SD file system and datalogger + MessageOutput.print("Initialize SD FS... "); + if(PinMapping.isValidSdConfig()) { + SPI.begin(pin.sd_clk, pin.sd_miso, pin.sd_mosi, pin.sd_cs); + if (!SD.begin(pin.sd_cs)) { + MessageOutput.print("Card Mount Failed"); + } + uint8_t cardType = SD.cardType(); + + MessageOutput.println("done"); + + if (cardType == CARD_SD || cardType == CARD_SDHC) { + if(config.DataLogger.Enabled) { + MessageOutput.print("Initialize DataLogger... "); + DataLogger.init(scheduler); + MessageOutput.println("done"); + } + } else { + MessageOutput.println("No compatible SD card attached"); + } + } else { + MessageOutput.println("Invalid pin config"); + } + // Initialize WiFi MessageOutput.print("Initialize Network... "); NetworkSettings.init(scheduler); diff --git a/webapp/src/components/NavBar.vue b/webapp/src/components/NavBar.vue index e6eb58f27..fc279b296 100644 --- a/webapp/src/components/NavBar.vue +++ b/webapp/src/components/NavBar.vue @@ -50,6 +50,9 @@
  • {{ $t('menu.DeviceManager') }}
  • +
  • + {{ $t('menu.DataLoggerSettings') }} +
  • diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 8b132271f..5d05c11af 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -9,6 +9,7 @@ "SecuritySettings": "Sicherheit", "DTUSettings": "DTU", "DeviceManager": "Hardware", + "DataLoggerSettings": "Datenlogger", "ConfigManagement": "Konfigurationsverwaltung", "FirmwareUpgrade": "Firmware-Aktualisierung", "DeviceReboot": "Neustart", @@ -216,7 +217,8 @@ "Heap": "Heap", "PsRam": "PSRAM", "LittleFs": "LittleFs", - "Sketch": "Sketch" + "Sketch": "Sketch", + "Sd": "SD" }, "heapdetails": { "HeapDetails": "Detailinformationen zum Heap", @@ -523,6 +525,17 @@ "YieldDayCorrection": "Tagesertragskorrektur", "YieldDayCorrectionHint": "Summiert den Tagesertrag, auch wenn der Wechselrichter neu gestartet wird. Der Wert wird um Mitternacht zurückgesetzt" }, + "dataloggeradmin": { + "DataLoggerSettings": "Datenlogger Einstellungen", + "DataLoggerConfiguration": "Datenlogger Konfiguration", + "OutputConfiguration": "Ausgabe Konfiguration", + "EnableLogger": "Datenlogger aktivieren", + "SdConfigError": "Keine gültige Pin-Konfiguration für SD-Karten", + "SaveInterval": "Speicherintervall:", + "Seconds": "Sekunden", + "FileName": "Dateiname:", + "FileNameHint": "Dateiname (CSV-Format)" + }, "configadmin": { "ConfigManagement": "Konfigurationsverwaltung", "BackupHeader": "Sicherung: Sicherung der Konfigurationsdatei", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index c918e5375..db76265fc 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -216,7 +216,8 @@ "Heap": "Heap", "PsRam": "PSRAM", "LittleFs": "LittleFs", - "Sketch": "Sketch" + "Sketch": "Sketch", + "Sd": "SD" }, "heapdetails": { "HeapDetails": "Heap Details", @@ -523,6 +524,17 @@ "YieldDayCorrection": "Yield Day Correction", "YieldDayCorrectionHint": "Sum up daily yield even if the inverter is restarted. Value will be reset at midnight" }, + "dataloggeradmin": { + "DataLoggerSettings": "Datalogger Settings", + "DataLoggerConfiguration": "Datalogger Configuration", + "OutputConfiguration": "Output Configuration", + "EnableLogger": "Enable Datalogger", + "SdConfigError": "No valid pin configuration for SD cards", + "SaveInterval": "Save interval:", + "Seconds": "Seconds", + "FileName": "Filename:", + "FileNameHint": "Filename (CSV format)" + }, "configadmin": { "ConfigManagement": "Config Management", "BackupHeader": "Backup: Configuration File Backup", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index acd1280f7..f0942e3a0 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -216,7 +216,8 @@ "Heap": "Heap", "PsRam": "PSRAM", "LittleFs": "LittleFs", - "Sketch": "Sketch" + "Sketch": "Sketch", + "Sd": "SD" }, "heapdetails": { "HeapDetails": "Heap Details", @@ -523,6 +524,17 @@ "YieldDayCorrection": "Yield Day Correction", "YieldDayCorrectionHint": "Sum up daily yield even if the inverter is restarted. Value will be reset at midnight" }, + "dataloggeradmin": { + "DataLoggerSettings": "Datalogger Settings", + "DataLoggerConfiguration": "Datalogger Configuration", + "OutputConfiguration": "Output Configuration", + "EnableLogger": "Enable Datalogger", + "SdConfigError": "No valid pin configuration for SD cards", + "SaveInterval": "Save interval:", + "Seconds": "Seconds", + "FileName": "Filename:", + "FileNameHint": "Filename (CSV format)" + }, "configadmin": { "ConfigManagement": "Gestion de la configuration", "BackupHeader": "Sauvegarder le fichier de configuration", diff --git a/webapp/src/router/index.ts b/webapp/src/router/index.ts index 8fd3cfe83..84638d482 100644 --- a/webapp/src/router/index.ts +++ b/webapp/src/router/index.ts @@ -1,8 +1,9 @@ import AboutView from '@/views/AboutView.vue'; import ConfigAdminView from '@/views/ConfigAdminView.vue'; import ConsoleInfoView from '@/views/ConsoleInfoView.vue'; -import DeviceAdminView from '@/views/DeviceAdminView.vue' +import DeviceAdminView from '@/views/DeviceAdminView.vue'; import DtuAdminView from '@/views/DtuAdminView.vue'; +import DataLoggerAdminView from '@/views/DataLoggerAdminView.vue'; import ErrorView from '@/views/ErrorView.vue'; import FirmwareUpgradeView from '@/views/FirmwareUpgradeView.vue'; import HomeView from '@/views/HomeView.vue'; @@ -98,6 +99,11 @@ const router = createRouter({ name: 'Device Manager', component: DeviceAdminView }, + { + path: '/settings/datalogger', + name: 'Datalogger Settings', + component: DataLoggerAdminView + }, { path: '/firmware/upgrade', name: 'Firmware Upgrade', diff --git a/webapp/src/types/DataLoggerConfig.ts b/webapp/src/types/DataLoggerConfig.ts new file mode 100644 index 000000000..046e414ab --- /dev/null +++ b/webapp/src/types/DataLoggerConfig.ts @@ -0,0 +1,12 @@ +export interface DataLoggerOutputConfig { + total_yield_total: boolean; + total_yield_day: boolean; + total_power: boolean; +} + +export interface DataLoggerConfig { + datalogger_enabled: boolean; + saveinterval: number; + filename: string; + output_config: DataLoggerOutputConfig; +} diff --git a/webapp/src/views/DataLoggerAdminView.vue b/webapp/src/views/DataLoggerAdminView.vue new file mode 100644 index 000000000..f96787072 --- /dev/null +++ b/webapp/src/views/DataLoggerAdminView.vue @@ -0,0 +1,143 @@ + + + From dd3826a07a1cf83c6d2194e5a093b6d8283ccb0a Mon Sep 17 00:00:00 2001 From: Marvin Carstensen Date: Sat, 6 Jul 2024 11:43:12 +0200 Subject: [PATCH 5/6] Fixed linting issues --- include/DataLogger.h | 2 +- src/DataLogger.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/DataLogger.h b/include/DataLogger.h index b265098aa..34b084a9f 100644 --- a/include/DataLogger.h +++ b/include/DataLogger.h @@ -21,7 +21,7 @@ class DataLoggerClass { void writeFile(FS &fs, const String path, const char * message); void appendFile(FS &fs, const String path, const char * message); - unsigned long getTime(); + uint64_t getTime(); }; extern DataLoggerClass DataLogger; diff --git a/src/DataLogger.cpp b/src/DataLogger.cpp index 01b380c69..e1c7a81a0 100644 --- a/src/DataLogger.cpp +++ b/src/DataLogger.cpp @@ -1,6 +1,6 @@ #include "DataLogger.h" #include "Configuration.h" -#include "DataStore.h" +#include "Datastore.h" #include "MessageOutput.h" #include "SD.h" @@ -89,7 +89,7 @@ void DataLoggerClass::appendFile(fs::FS &fs, const String path, const char * mes file.close(); } -unsigned long DataLoggerClass::getTime() { +uint64_t DataLoggerClass::getTime() { time_t now; struct tm timeinfo; if (!getLocalTime(&timeinfo)) { From 91c13ee62d2de61760630d656859ff4c17cf8465 Mon Sep 17 00:00:00 2001 From: Marvin Carstensen Date: Sun, 7 Jul 2024 09:23:28 +0200 Subject: [PATCH 6/6] Finalizing implementation of datalogger --- src/Configuration.cpp | 4 ++-- src/DataLogger.cpp | 2 +- webapp/src/views/DataLoggerAdminView.vue | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 0b5f48783..9d147067c 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -284,8 +284,8 @@ bool ConfigurationClass::read() JsonObject datalogger_output = datalogger["output_config"]; config.DataLogger.OutputConfig.TotalYieldTotal = datalogger_output["total_yield_total"] | DATALOGGER_OUTPUT_TOTAL_YIELD_TOTAL; - config.DataLogger.OutputConfig.TotalYieldTotal = datalogger_output["total_yield_total"] | DATALOGGER_OUTPUT_TOTAL_YIELD_DAY; - config.DataLogger.OutputConfig.TotalYieldTotal = datalogger_output["total_yield_total"] | DATALOGGER_OUTPUT_TOTAL_POWER; + config.DataLogger.OutputConfig.TotalYieldDay = datalogger_output["total_yield_day"] | DATALOGGER_OUTPUT_TOTAL_YIELD_DAY; + config.DataLogger.OutputConfig.TotalPower = datalogger_output["total_power"] | DATALOGGER_OUTPUT_TOTAL_POWER; JsonObject security = doc["security"]; strlcpy(config.Security.Password, security["password"] | ACCESS_POINT_PASSWORD, sizeof(config.Security.Password)); diff --git a/src/DataLogger.cpp b/src/DataLogger.cpp index e1c7a81a0..c2a00c310 100644 --- a/src/DataLogger.cpp +++ b/src/DataLogger.cpp @@ -18,7 +18,7 @@ void DataLoggerClass::init(Scheduler& scheduler) { const CONFIG_T& config = Configuration.get(); setSaveInterval(config.DataLogger.SaveInterval); - if(strlen(config.DataLogger.FileName) == 0) { + if(strlen(config.DataLogger.FileName) > 0) { File file = SD.open("/" + String(config.DataLogger.FileName)); if(!file) { MessageOutput.println("Datalogger: File doesn't exist"); diff --git a/webapp/src/views/DataLoggerAdminView.vue b/webapp/src/views/DataLoggerAdminView.vue index f96787072..d610403ba 100644 --- a/webapp/src/views/DataLoggerAdminView.vue +++ b/webapp/src/views/DataLoggerAdminView.vue @@ -91,8 +91,10 @@ export default defineComponent({ fetch("/api/device/config", { headers: authHeader() }) .then((response) => handleResponse(response, this.$emitter, this.$router)) - .then(() => { + .then((data) => { + this.deviceConfigList = data; const currentPinMapping = this.deviceConfigList.curPin; + console.log(this.deviceConfigList); if(!currentPinMapping?.sd || !this.isValidSdConfig(currentPinMapping)) { this.alertMessage = this.$t('dataloggeradmin.SdConfigError');