129 lines
2.9 KiB
C++
129 lines
2.9 KiB
C++
#include "sd_logger.h"
|
|
#include "config.h"
|
|
#include <SD.h>
|
|
#include <SPI.h>
|
|
#include <time.h>
|
|
|
|
static bool g_sd_ready = false;
|
|
static SPIClass *g_sd_spi = nullptr;
|
|
|
|
static const char *fault_text(FaultType fault) {
|
|
switch (fault) {
|
|
case FaultType::MeterRead:
|
|
return "meter";
|
|
case FaultType::Decode:
|
|
return "decode";
|
|
case FaultType::LoraTx:
|
|
return "loratx";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
static bool ensure_dir(const String &path) {
|
|
if (SD.exists(path)) {
|
|
return true;
|
|
}
|
|
return SD.mkdir(path);
|
|
}
|
|
|
|
static String format_date_utc(uint32_t ts_utc) {
|
|
time_t t = static_cast<time_t>(ts_utc);
|
|
struct tm tm_utc;
|
|
gmtime_r(&t, &tm_utc);
|
|
char buf[16];
|
|
snprintf(buf, sizeof(buf), "%04d-%02d-%02d",
|
|
tm_utc.tm_year + 1900,
|
|
tm_utc.tm_mon + 1,
|
|
tm_utc.tm_mday);
|
|
return String(buf);
|
|
}
|
|
|
|
void sd_logger_init() {
|
|
if (!ENABLE_SD_LOGGING) {
|
|
g_sd_ready = false;
|
|
return;
|
|
}
|
|
if (!g_sd_spi) {
|
|
g_sd_spi = new SPIClass(HSPI);
|
|
}
|
|
g_sd_spi->begin(PIN_SD_SCK, PIN_SD_MISO, PIN_SD_MOSI, PIN_SD_CS);
|
|
g_sd_ready = SD.begin(PIN_SD_CS, *g_sd_spi);
|
|
if (SERIAL_DEBUG_MODE) {
|
|
if (g_sd_ready) {
|
|
uint8_t type = SD.cardType();
|
|
uint64_t size = SD.cardSize();
|
|
Serial.printf("sd: ok type=%u size=%llu\n", static_cast<unsigned>(type), static_cast<unsigned long long>(size));
|
|
} else {
|
|
Serial.println("sd: init failed");
|
|
}
|
|
}
|
|
}
|
|
|
|
bool sd_logger_is_ready() {
|
|
return g_sd_ready;
|
|
}
|
|
|
|
void sd_logger_log_sample(const MeterData &data, bool include_error_text) {
|
|
if (!g_sd_ready || data.ts_utc == 0) {
|
|
return;
|
|
}
|
|
|
|
String root_dir = "/dd3";
|
|
if (!ensure_dir(root_dir)) {
|
|
return;
|
|
}
|
|
|
|
String sender_dir = root_dir + "/" + String(data.device_id);
|
|
if (!ensure_dir(sender_dir)) {
|
|
return;
|
|
}
|
|
|
|
String filename = sender_dir + "/" + format_date_utc(data.ts_utc) + ".csv";
|
|
bool new_file = !SD.exists(filename);
|
|
File f = SD.open(filename, FILE_APPEND);
|
|
if (!f) {
|
|
return;
|
|
}
|
|
|
|
if (new_file) {
|
|
f.println("ts_utc,p_w,p1_w,p2_w,p3_w,e_kwh,bat_v,bat_pct,rssi,snr,err_m,err_d,err_tx,err_last");
|
|
}
|
|
|
|
f.print(data.ts_utc);
|
|
f.print(',');
|
|
f.print(data.total_power_w, 1);
|
|
f.print(',');
|
|
f.print(data.phase_power_w[0], 1);
|
|
f.print(',');
|
|
f.print(data.phase_power_w[1], 1);
|
|
f.print(',');
|
|
f.print(data.phase_power_w[2], 1);
|
|
f.print(',');
|
|
f.print(data.energy_total_kwh, 3);
|
|
f.print(',');
|
|
f.print(data.battery_voltage_v, 2);
|
|
f.print(',');
|
|
f.print(data.battery_percent);
|
|
f.print(',');
|
|
f.print(data.link_rssi_dbm);
|
|
f.print(',');
|
|
if (isnan(data.link_snr_db)) {
|
|
f.print("");
|
|
} else {
|
|
f.print(data.link_snr_db, 1);
|
|
}
|
|
f.print(',');
|
|
f.print(data.err_meter_read);
|
|
f.print(',');
|
|
f.print(data.err_decode);
|
|
f.print(',');
|
|
f.print(data.err_lora_tx);
|
|
f.print(',');
|
|
if (include_error_text && data.last_error != FaultType::None) {
|
|
f.print(fault_text(data.last_error));
|
|
}
|
|
f.println();
|
|
f.close();
|
|
}
|