Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add simple DataLogger and support for SD cards. #2125

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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;
Expand Down
27 changes: 27 additions & 0 deletions include/DataLogger.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#include <TaskSchedulerDeclarations.h>
#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);

uint64_t getTime();
};

extern DataLoggerClass DataLogger;
6 changes: 6 additions & 0 deletions include/PinMapping.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -50,6 +55,7 @@ class PinMappingClass {
bool isValidNrf24Config() const;
bool isValidCmt2300Config() const;
bool isValidEthConfig() const;
bool isValidSdConfig() const;

private:
PinMapping_t _pinMapping;
Expand Down
2 changes: 2 additions & 0 deletions include/WebApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -49,6 +50,7 @@ class WebApiClass {
WebApiDeviceClass _webApiDevice;
WebApiDevInfoClass _webApiDevInfo;
WebApiDtuClass _webApiDtu;
WebApiDataLoggerClass _webApiDataLogger;
WebApiEventlogClass _webApiEventlog;
WebApiFirmwareClass _webApiFirmware;
WebApiGridProfileClass _webApiGridprofile;
Expand Down
14 changes: 14 additions & 0 deletions include/WebApi_datalogger.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>

class WebApiDataLoggerClass {
public:
void init(AsyncWebServer& server, Scheduler& scheduler);

private:
void onDataLoggerAdminGet(AsyncWebServerRequest* request);
void onDataLoggerAdminPost(AsyncWebServerRequest* request);
};
7 changes: 7 additions & 0 deletions include/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<JsonObject>();
datalogger["datalogger_enabled"] = config.DataLogger.Enabled;
datalogger["saveinterval"] = config.DataLogger.SaveInterval;
datalogger["filename"] = config.DataLogger.FileName;

JsonObject datalogger_output = datalogger["output_config"].to<JsonObject>();
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<JsonObject>();
security["password"] = config.Security.Password;
security["allow_readonly"] = config.Security.AllowReadonly;
Expand Down Expand Up @@ -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.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));
config.Security.AllowReadonly = security["allow_readonly"] | SECURITY_ALLOW_READONLY;
Expand Down
101 changes: 101 additions & 0 deletions src/DataLogger.cpp
Original file line number Diff line number Diff line change
@@ -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();
}

uint64_t 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;
}
34 changes: 34 additions & 0 deletions src/PinMapping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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;
}
1 change: 1 addition & 0 deletions src/WebApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading