diff --git a/RX_FSK/RX_FSK.ino b/RX_FSK/RX_FSK.ino index 396a8b8..c6d4e9b 100644 --- a/RX_FSK/RX_FSK.ino +++ b/RX_FSK/RX_FSK.ino @@ -34,6 +34,7 @@ #include "src/pmu.h" + /* Data exchange connectors */ #if FEATURE_CHASEMAPPER #include "src/conn-chasemapper.h" @@ -287,7 +288,7 @@ void setupChannelList() { file.close(); } -const char *HTMLHEAD = " "; +const char *HTMLHEAD = " "; void HTMLBODY(char *ptr, const char *which) { strcat(ptr, "
"); + strcpy(ptr, ""); if (run) { strcat(ptr, "

Doing update, wait until reboot

"); } else { @@ -1230,16 +1239,38 @@ void SetupAsyncServer() { } request->send(SPIFFS, filename, "text/plain"); }); + server.on("/file", HTTP_POST, [](AsyncWebServerRequest * request) { request->send(200); }, handleUpload); +#if FEATURE_SDCARD + server.on("/sd/data.csv", HTTP_GET, [](AsyncWebServerRequest *request) { + Serial.println("Opening SD card file\n"); + const File SDFile = SD.open("/data.csv", FILE_READ); + if(SDFile) { Serial.printf("SD file opened\n"); } + else { Serial.printf("SD file does not exist"); request->send(404); return; } + AsyncWebServerResponse *response = request->beginChunkedResponse("text/plain", [SDFile](uint8_t *buf, size_t maxLen, size_t index) -> size_t { + File SDLambdaFile = SDFile; + // if(maxLen>1024) maxLen=1024; + Serial.printf("[HTTP]\t[%d]\tINDEX [%d]\tBUFFER_MAX_LENGHT [%d]\r\n", index, SDLambdaFile.size(), maxLen); + return SDLambdaFile.read(buf, maxLen); + }); + request->send(response); + }); + server.serveStatic("/sd/", SD, "/"); +#endif server.on("/edit.html", HTTP_GET, [](AsyncWebServerRequest * request) { // new version: // Open file // store file object in request->_tempObject //request->send(200, "text/html", createEditForm(request->getParam(0)->value())); - const String filename = request->getParam(0)->value(); + AsyncWebParameter *param = request->getParam(0); + if(!param) { + request->send(404); + return; + } + const String filename = param->value(); File file = SPIFFS.open("/" + filename, "r"); int state = 0; request->send("text/html", 0, [state, file, filename](uint8_t *buffer, size_t maxLen, size_t index) mutable -> size_t { @@ -1252,9 +1283,13 @@ void SetupAsyncServer() { if (ret == NULL) request->send(200, "text/html", "ERROR

Something went wrong (probably ESP32 out of memory). Uploaded file is empty.

"); else { - String f = request->getParam(0)->value(); + AsyncWebParameter *param = request->getParam(0); + if(!param) { + request->send(404); + return; + } + String f = param->value(); request->redirect("/edit.html?file=" + f); - //request->send(200, "text/html", createEditForm(request->getParam(0)->value())); } }, NULL, @@ -1265,8 +1300,12 @@ void SetupAsyncServer() { // Route to load style.css file server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest * request) { AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/style.css", "text/css"); - response->addHeader("Cache-Control", "max-age=86400"); - request->send(response); + if(response) { + response->addHeader("Cache-Control", "max-age=86400"); + request->send(response); + } else { + request->send(404); + } }); server.on("/live.kml", HTTP_GET, [](AsyncWebServerRequest * request) { @@ -1296,7 +1335,10 @@ void SetupAsyncServer() { // This happens with concurrent requests, notably if a browser fetches rdz.js and cfg.js concurrently for config.html // With the cache, rdz.js is likely already in the cache...0 Serial.printf("URL is %s\n", url.c_str()); - AsyncWebServerResponse *response = request->beginResponse(SPIFFS, url, "text/html"); + const char *type = "text/html"; + if(url.endsWith(".js")) type="text/javascript"; + Serial.printf("Responding with type %si (url %s)\n", type, url.c_str()); + AsyncWebServerResponse *response = request->beginResponse(SPIFFS, url, type); if(response) { response->addHeader("Cache-Control", "max-age=900"); request->send(response); @@ -1693,14 +1735,13 @@ void setup() char buf[12]; // Open serial communications and wait for port to open: - Serial.begin(/*921600 */115200); + Serial.begin(115200); for (int i = 0; i < 39; i++) { int v = gpio_get_level((gpio_num_t)i); Serial.printf("%d:%d ", i, v); } - //NimBLEDevice::init("NimBLE-Arduino"); - //NimBLEServer* pServer = NimBLEDevice::createServer();; +#ifndef REMOVE_ALL_FOR_TESTING Serial.println(""); #ifdef ESP_MEM_DEBUG @@ -1810,10 +1851,8 @@ void setup() setupWifiList(); Serial.printf("before disp.initFromFile... layouts is %p\n", disp.layouts); - Serial.printf("test\n"); - disp.initFromFile(sonde.config.screenfile); - Serial.printf("disp.initFromFile... layouts is %p", disp.layouts); + Serial.printf("disp.initFromFile... layouts is %p\n", disp.layouts); // == show initial values from config.txt ========================= // @@ -1929,9 +1968,25 @@ void setup() #if FEATURE_APRS connAPRS.init(); #endif +#if FEATURE_SDCARD + connSDCard.init(); +#endif WiFi.onEvent(WiFiEvent); getKeyPress(); // clear key buffer + +#else +/// DEBUG ONLY + WiFi.begin("Dinosauro", "03071975"); + while(!WiFi.isConnected()) { delay(500); Serial.print(":"); } + Serial.println("... WiFi is connected!\n"); + SetupAsyncServer(); + sonde.config.sd.cs = 13; + sonde.config.sd.clk = 14; + sonde.config.sd.miso = 2; + sonde.config.sd.mosi = 15; + connSDCard.init(); +#endif } void enterMode(int mode) { @@ -2882,6 +2937,8 @@ int fetchHTTPheader(int *validType) { void loop() { Serial.printf("\nMAIN: Running loop in state %d [currentDisp:%d, lastDisp:%d]. free heap: %d, unused stack: %d\n", mainState, currentDisplay, lastDisplay, ESP.getFreeHeap(), uxTaskGetStackHighWaterMark(0)); + +#ifndef REMOVE_ALL_FOR_TESTING switch (mainState) { case ST_DECODER: #ifndef DISABLE_MAINRX @@ -2911,7 +2968,9 @@ void loop() { sonde.updateDisplay(); lastDisplay = currentDisplay; } - +#else + delay(1000); +#endif } diff --git a/RX_FSK/data/cfg.js b/RX_FSK/data/cfg.js index a0df69e..1f4eee7 100644 --- a/RX_FSK/data/cfg.js +++ b/RX_FSK/data/cfg.js @@ -74,6 +74,13 @@ var cfgs = [ [ "sondehub.fiinterval", "Import frequency (minutes, ≥ 5)" ], [ "sondehub.fimaxdist", "Import maximum distance (km, ≤ 700)" ], [ "sondehub.fimaxage", "Import maximum age (hours, ≤ 48)" ], +[ "", "SD card logger configuration", "https://github.com/dl9rdz/rdz_ttgo_sonde/wiki/SDcard-configuration"], +[ "cs", "SD card CS" ], +[ "miso", "SD card MISO/DI" ], +[ "mosi", "SD card MOSI/DO" ], +[ "clk", "SD card CLK" ], +[ "sync", "SD card sync interval [s]" ], +[ "name", "SD card naming (0=by sondeid, 1=by day)" ], [ "", "Hardware configuration (requires reboot)", "https://github.com/dl9rdz/rdz_ttgo_sonde/wiki/Hardware-configuration"], [ "disptype", "Display type (0=OLED/SSD1306, 1=ILI9225, 2=OLED/SH1106, 3=ILI9341, 4=ILI9342, 5=ST7789)"], [ "oled_sda", "OLED SDA/TFT SDA"], diff --git a/RX_FSK/features.h b/RX_FSK/features.h index 85ffb7e..c9abf99 100644 --- a/RX_FSK/features.h +++ b/RX_FSK/features.h @@ -34,7 +34,7 @@ // Safeboot image is a minimalistic version that only includes WiFi / OTA functionality #define FEATURE_SONDEHUB 0 #define FEATURE_MQTT 0 -#define FEATURE_SDARD 0 +#define FEATURE_SDCARD 0 #define FEATURE_APRS 0 #define LEGACY_FONTS_IN_CODEBIN 0 #endif diff --git a/RX_FSK/src/Sonde.cpp b/RX_FSK/src/Sonde.cpp index 438322e..337886b 100644 --- a/RX_FSK/src/Sonde.cpp +++ b/RX_FSK/src/Sonde.cpp @@ -106,6 +106,10 @@ void Sonde::defaultConfig() { config.sx1278_sck = SCK; // config.oled_rst = 16; config.oled_rst = -1; // GPIO16 is Flash CS on Lora32 v1.6 + config.sd.cs = -1; + config.sd.miso = -1; + config.sd.mosi = -1; + config.sd.clk = -1; config.disptype = 0; config.dispcontrast = -1; config.tft_orient = 1; @@ -225,6 +229,10 @@ void Sonde::defaultConfig() { config.button2_pin = 14 + 128; // GPIO14 / T6 config.led_pout = 25; config.batt_adc = 35; + config.sd.cs = 13; + config.sd.miso = 2; + config.sd.mosi = 15; + config.sd.clk = 14; } } // diff --git a/RX_FSK/src/Sonde.h b/RX_FSK/src/Sonde.h index 422d584..ee82f47 100644 --- a/RX_FSK/src/Sonde.h +++ b/RX_FSK/src/Sonde.h @@ -229,6 +229,15 @@ struct st_sondehub { double fimaxage; }; +struct st_sdcard { + int cs; + int miso; + int mosi; + int clk; + int sync; + int name; +}; + // to be extended enum { TYPE_TTGO, TYPE_M5_CORE2 }; @@ -297,6 +306,7 @@ typedef struct st_rdzconfig { struct st_mqtt mqtt; struct st_sondehub sondehub; struct st_cm cm; + struct st_sdcard sd; } RDZConfig; diff --git a/RX_FSK/src/conn-sdcard.cpp b/RX_FSK/src/conn-sdcard.cpp index ad89e13..7ce29c9 100644 --- a/RX_FSK/src/conn-sdcard.cpp +++ b/RX_FSK/src/conn-sdcard.cpp @@ -4,45 +4,84 @@ #include "conn-sdcard.h" -// TODO: Move into config -#define CS 13 -#define SYNC_INTERVAL 10 +static SPIClass sdspi(HSPI); void ConnSDCard::init() { - /* Initialize SD card */ - initok = SD.begin(CS); - Serial.printf("SD card init: %s\n", initok?"OK":"Failed"); + if(sonde.config.sd.clk==-1) + return; + /* Initialize SD card */ + // SPI (==VSPI) is used by SX127x. + // On LoRa32, SD-Card is on different pins, so cannot share VSPI + // Use HSPI (if using a TFT with SPI, you have to make sure that the same pins are used for both (MISO/MOSI/CLK) + sdspi.begin(sonde.config.sd.clk, sonde.config.sd.miso, sonde.config.sd.mosi, sonde.config.sd.cs); + initok = SD.begin(sonde.config.sd.cs, sdspi); + Serial.printf("SD card init: %s\n", initok ? "OK" : "Failed"); + uint8_t cardType = SD.cardType(); + + if (cardType == CARD_NONE) { + Serial.println("No SD card attached"); + return; + } + + Serial.print("SD Card Type: "); + if (cardType == CARD_MMC) { + Serial.println("MMC"); + } else if (cardType == CARD_SD) { + Serial.println("SDSC"); + } else if (cardType == CARD_SDHC) { + Serial.println("SDHC"); + } else { + Serial.println("UNKNOWN"); + } + uint64_t cardSize = SD.cardSize() / (1024 * 1024); + Serial.printf("SD Card Size: %luMB\n", cardSize); + uint64_t usedSize = SD.usedBytes() / (1024 * 1024); + //uint64_t totalSize = SD.totalBytes() / (1024 * 1024); + uint64_t totalSize = SD.totalBytes(); + Serial.printf("SD Card used/total: %lu/%lu MB\n", usedSize, totalSize); + + file = SD.open("/data.csv", FILE_APPEND); + if (!file) { + Serial.println("Cannot open file"); + return; + } + file.printf("Hello word... test\n"); + file.close(); + + //sdf = SD.open("/data.csv", FILE_READ); } void ConnSDCard::netsetup() { - /* empty function, we don't use any network here */ + /* empty function, we don't use any network here */ } String ConnSDCard::getStatus() { - return String(""); + return String(""); } +// Rotation by time or by id. + void ConnSDCard::updateSonde( SondeInfo *si ) { - if (!initok) return; - if (!file) { - file = SD.open("/data.csv", FILE_APPEND); - } - if (!file) { - Serial.println("Error opening file"); - return; - } - SondeData *sd = &si->d; - file.printf("%d,%s,%s,%d," - "%f,%f,%f,%f,%f,%f,%d,%d," - "%d,%d,%d,%d\n", - sd->validID, sd->ser, sd->typestr, sd->subtype, - sd->lat, sd->lon, sd->alt, sd->vs, sd->hs, sd->dir, sd->sats, sd->validPos, - sd->time, sd->frame, sd->vframe, sd->validTime); - wcount++; - if(wcount >= SYNC_INTERVAL) { - file.flush(); - wcount = 0; - } + if (!initok) return; + if (!file) { + file = SD.open("/data.csv", FILE_APPEND); + } + if (!file) { + Serial.println("Error opening file"); + return; + } + SondeData *sd = &si->d; + file.printf("%d,%s,%s,%d," + "%f,%f,%f,%f,%f,%f,%d,%d," + "%d,%d,%d,%d\n", + sd->validID, sd->ser, sd->typestr, sd->subtype, + sd->lat, sd->lon, sd->alt, sd->vs, sd->hs, sd->dir, sd->sats, sd->validPos, + sd->time, sd->frame, sd->vframe, sd->validTime); + wcount++; + if (wcount >= sonde.config.sd.sync) { + file.flush(); + wcount = 0; + } } diff --git a/RX_FSK/src/conn-sdcard.h b/RX_FSK/src/conn-sdcard.h index 8d01bb5..e9272a5 100644 --- a/RX_FSK/src/conn-sdcard.h +++ b/RX_FSK/src/conn-sdcard.h @@ -39,5 +39,7 @@ private: }; +extern ConnSDCard connSDCard; + #endif