#include "logbook.h" #include "language.h" #include "constants.h" #include #include "language.h" void handleRoot() { fs::File file = SPIFFS.open("/logbook.csv", "r"); if (!file) { webserver.send(500, "text/plain", "Failed to open logbook"); return; } String html = ""; html += ""; html += ""; // Add CSS styling html += ""; html += ""; // Add logo as a clickable link html += ""; html += "\"FMDX"; html += ""; html += "

" + String(myLanguage[language][286]) + "

"; html += ""; html += ""; // Sorting function with icons html += ""; html += ""; String header = ""; if (file.available()) { header = file.readStringUntil('\n'); html += ""; int startIndex = 0; int columnIndex = 0; // Generate table headers with sorting functionality while (startIndex < header.length()) { int endIndex = header.indexOf(',', startIndex); if (endIndex == -1) endIndex = header.length(); String column = header.substring(startIndex, endIndex); // Add clickable headers for sorting html += ""; // Sorting icon placeholder startIndex = endIndex + 1; columnIndex++; } html += ""; } bool hasData = false; int piCodeIndex = -1, frequencyIndex = -1; // Identify column indices for PI code and Frequency int startIndex = 0, columnIndex = 0; while (startIndex < header.length()) { int endIndex = header.indexOf(',', startIndex); if (endIndex == -1) endIndex = header.length(); String column = header.substring(startIndex, endIndex); if (column.equalsIgnoreCase("PI")) piCodeIndex = columnIndex; if (column.equalsIgnoreCase("Frequency")) frequencyIndex = columnIndex; startIndex = endIndex + 1; columnIndex++; } if (file.available()) hasData = true; if (logcounter < 130) { // Generate rows while (file.available()) { String line = file.readStringUntil('\n'); if (line.length() > 0) { html += ""; String piCode = "", frequency = ""; startIndex = 0, columnIndex = 0; while (startIndex < line.length()) { int endIndex = line.indexOf(',', startIndex); if (endIndex == -1) endIndex = line.length(); String cell = line.substring(startIndex, endIndex); // Extract PI code and Frequency if (columnIndex == piCodeIndex) piCode = cell; if (columnIndex == frequencyIndex) frequency = cell; html += ""; startIndex = endIndex + 1; columnIndex++; } // Remove " MHz" from Frequency frequency.replace(" MHz", ""); // Make row clickable html += ""; html += ""; } } file.close(); } else { html += ""; } if (!hasData) { html += ""; } html += "
" + column; html += "
" + cell + "🌐
" + String(myLanguage[language][300]) + "
" + String(myLanguage[language][288]) + "
"; html += ""; webserver.send(200, "text/html", html); } void handleDownloadCSV() { // Attempt to open the CSV file from SPIFFS in read mode fs::File file = SPIFFS.open("/logbook.csv", "r"); // Check if the file was successfully opened if (!file) { // If the file could not be opened, send an error response webserver.send(500, "text/plain", "Failed to open logbook for download"); return; // Exit the function if the file cannot be opened } // Set the headers to specify that the response will be a CSV file for download webserver.sendHeader("Content-Type", "text/csv"); // Set MIME type for CSV files webserver.sendHeader("Content-Disposition", "attachment; filename=logbook.csv"); // Suggests the file name for download // Stream the CSV file content directly to the browser webserver.streamFile(file, "text/csv"); // Close the file after streaming the content to release resources file.close(); } bool handleCreateNewLogbook() { // Check if the file "logbook.csv" already exists if (SPIFFS.exists("/logbook.csv")) { // If it exists, delete the file if (!SPIFFS.remove("/logbook.csv")) { // Return false if the file could not be deleted return false; } } // Create a new "logbook.csv" file in write mode fs::File file = SPIFFS.open("/logbook.csv", "w"); // Check if the file was successfully created if (!file) { // Return false if file creation fails return false; } // Write the header to the new CSV file String header = "Date,Time,Frequency,PI,Signal,Stereo,TA,TP,PTY,ECC,PS,Radiotext\n"; file.print(header); // Ensure that the header is written properly // Make sure the data is written before closing the file file.flush(); // Ensure that everything is written to the file file.close(); // Close the file after writing logcounter = 0; // Reset logcounter EEPROM.writeUInt(EE_UINT16_LOGCOUNTER, logcounter); EEPROM.commit(); // Return true if the function runs without problems return true; } byte addRowToCSV() { // Ensure there is at least 150 bytes of free space in SPIFFS before proceeding if (SPIFFS.totalBytes() - SPIFFS.usedBytes() < 150 || logcounter > 1500) { return 2; // Return 2 if insufficient free space is available } // Open the "logbook.csv" file in append mode fs::File file = SPIFFS.open("/logbook.csv", "a"); // Check if the file could not be opened if (!file) { return 1; // Return 1 if the file cannot be opened } // Fetch the current date and time as a string String currentDateTime = getCurrentDateTime(); // Use a placeholder ("-,-") if the date and time could not be retrieved if (currentDateTime == "") { currentDateTime = "-,-"; } // Prepare the frequency in a formatted string (e.g., "XX.XX MHz") int freqInt = (band == BAND_OIRT) ? (int)frequency_OIRT : (int)frequency; int adjustedFreq = freqInt + (band != BAND_OIRT ? ConverterSet * 100 : 0); String frequencyFormatted = String(adjustedFreq / 100) + "." + ((adjustedFreq % 100 < 10) ? "0" : "") + String(adjustedFreq % 100) + " MHz"; // Calculate signal strength based on the selected unit int SStatusPrint = 0; if (unit == 0) SStatusPrint = SStatus; // dBμV else if (unit == 1) SStatusPrint = ((SStatus * 100) + 10875) / 100; // dBf else if (unit == 2) SStatusPrint = round((float(SStatus) / 10.0 - 10.0 * log10(75) - 90.0) * 10.0); // dBm // Format the signal strength with appropriate decimal places and unit String signal = String(SStatusPrint / 10) + "." + String(abs(SStatusPrint % 10)); if (unit == 0) signal += " dBμV"; else if (unit == 1) signal += " dBf"; else if (unit == 2) signal += " dBm"; // Prepare the radio text with station information, including enhanced options if available String radioText = String(radio.rds.stationText + " " + radio.rds.stationText32); if (radio.rds.hasEnhancedRT) { radioText += " eRT: " + String(radio.rds.enhancedRTtext); } // Replace commas in the station name and radio text to avoid CSV conflicts String stationName = radio.rds.stationName; String radioTextModified = radioText; stationName.replace(",", " "); // Replace commas in station name radioTextModified.replace(",", " "); // Replace commas in radio text // Handle ECC, PTY, TA, TP, and Stereo flag String TA = radio.rds.hasTA ? "•" : " "; String TP = radio.rds.hasTP ? "•" : " "; String Stereo = radio.getStereoStatus() ? "•" : " "; String pty = String(radio.rds.stationTypeCode); String ECC = "--"; if (radio.rds.hasECC) { char eccBuffer[3]; // Buffer to hold 2-digit hex value + null terminator snprintf(eccBuffer, sizeof(eccBuffer), "%02X", radio.rds.ECC); // Format ECC as uppercase 2-digit hex ECC = String(eccBuffer); } // Construct the CSV row data String row = currentDateTime + "," + frequencyFormatted + "," + String(radio.rds.picode).substring(0, 4) + "," + signal + "," + Stereo + "," + TA + "," + TP + "," + pty + "," + ECC + "," + stationName + "," + radioTextModified + "\n"; // Write the row to the file and close it if (file.print(row)) { file.close(); // Successfully wrote to the file logcounter++; EEPROM.writeUInt(EE_UINT16_LOGCOUNTER, logcounter); EEPROM.commit(); return 0; // Return 0 to indicate success } else { file.close(); // Close the file if writing fails return 1; // Return 1 to indicate failure } } String getCurrentDateTime() { // Check if the RTC has been set if (!rtcset) { return "-,-"; // Return placeholder when the RTC is not set } // Use the ESP32's time functions to retrieve the current time struct tm timeInfo; if (!getLocalTime(&timeInfo)) { return "-,-"; // Return placeholder if local time is unavailable } // Adjust timeInfo using the GMT offset time_t currentEpoch = mktime(&timeInfo); // Convert struct tm to time_t format // Calculate GMT offset currentEpoch += (NTPupdated ? NTPoffset * 3600 : radio.rds.offset); // Apply GMT offset if NTPupdated, else RDS offset // Apply DST adjustment if NTPupdated and autoDST are true if (NTPupdated && autoDST) { struct tm tempTimeInfo; localtime_r(¤tEpoch, &tempTimeInfo); // Convert to struct tm for DST calculation if (isDST(mktime(&tempTimeInfo))) { // Check if DST is in effect currentEpoch += 3600; // Add 1-hour DST offset } } // Convert adjusted time back to struct tm format localtime_r(¤tEpoch, &timeInfo); // Buffer for formatted date-time string char buf[20]; if (clockampm) { // USA format: MM/DD/YYYY, HH:MM:SS AM/PM strftime(buf, sizeof(buf), "%m/%d/%Y", &timeInfo); // Format as MM/DD/YYYY // Format time in 12-hour format with AM/PM int hour = timeInfo.tm_hour; String ampm = (hour >= 12) ? "PM" : "AM"; // Determine AM or PM if (hour == 0) hour = 12; // Convert 0 hour to 12 AM else if (hour > 12) hour -= 12; // Convert to 12-hour format for PM String timeWithAMPM = String(hour) + ":" + (timeInfo.tm_min < 10 ? "0" : "") + String(timeInfo.tm_min) + ":" + (timeInfo.tm_sec < 10 ? "0" : "") + String(timeInfo.tm_sec) + " " + ampm; // Return the final formatted date and time for the USA region return String(buf) + "," + timeWithAMPM; } else { // European format: DD-MM-YYYY, HH:MM:SS strftime(buf, sizeof(buf), "%d-%m-%Y", &timeInfo); // Format as DD-MM-YYYY String timeEuropean = String(timeInfo.tm_hour) + ":" + (timeInfo.tm_min < 10 ? "0" : "") + String(timeInfo.tm_min) + ":" + (timeInfo.tm_sec < 10 ? "0" : "") + String(timeInfo.tm_sec); // Format time with leading zero if needed return String(buf) + "," + timeEuropean; } } bool isDST(time_t t) { struct tm timeInfo; localtime_r(&t, &timeInfo); // Convert time_t to struct tm int month = timeInfo.tm_mon + 1; // tm_mon is 0-based, so add 1 int day = timeInfo.tm_mday; // tm_mday is the day of the month int hour = timeInfo.tm_hour; // tm_hour is the hour of the day int weekday = timeInfo.tm_wday; // tm_wday is the day of the week (0 = Sunday) // DST starts last Sunday in March at 2:00 AM if (month == 3) { int lastSunday = 31 - ((weekday + 31 - day) % 7); if (day > lastSunday || (day == lastSunday && hour >= 2)) { return true; } } // DST ends last Sunday in October at 3:00 AM if (month == 10) { int lastSunday = 31 - ((weekday + 31 - day) % 7); if (day < lastSunday || (day == lastSunday && hour < 3)) { return false; } } // DST is active from April to September if (month > 3 && month < 10) { return true; } return false; } void handleLogo() { fs::File file = SPIFFS.open("/logo.png", "r"); if (!file) { webserver.send(404, "text/plain", "Logo not found"); return; } webserver.streamFile(file, "image/png"); file.close(); } void printLogbookCSV() { // Attempt to open the CSV file stored in SPIFFS fs::File file = SPIFFS.open("/logbook.csv", "r"); // Check if the file was successfully opened if (!file) { Serial.println("Failed to open logbook!"); return; } // Print a message indicating the start of the file content Serial.println("===== Start of logbook.csv ====="); // Read and print the contents of the file line by line while (file.available()) { String line = file.readStringUntil('\n'); // Read one line at a time Serial.println(line); // Print the line to the Serial Monitor } // Close the file after reading file.close(); // Print a message indicating the end of the file content Serial.println("===== End of logbook.csv ====="); } void sendUDPlog() { // Fetch the current date and time as a string String currentDateTime = getCurrentDateTime(); // Use a placeholder ("-,-") if the date and time could not be retrieved if (currentDateTime == "") { currentDateTime = "-,-"; } // Prepare the frequency in a formatted string (e.g., "XX.XX MHz") int freqInt = (band == BAND_OIRT) ? (int)frequency_OIRT : (int)frequency; int adjustedFreq = freqInt + (band != BAND_OIRT ? ConverterSet * 100 : 0); String frequencyFormatted = String(adjustedFreq / 100) + "." + ((adjustedFreq % 100 < 10) ? "0" : "") + String(adjustedFreq % 100) + " MHz"; // Calculate signal strength based on the selected unit int SStatusPrint = 0; if (unit == 0) SStatusPrint = SStatus; // dBμV else if (unit == 1) SStatusPrint = ((SStatus * 100) + 10875) / 100; // dBf else if (unit == 2) SStatusPrint = round((float(SStatus) / 10.0 - 10.0 * log10(75) - 90.0) * 10.0); // dBm // Format the signal strength with appropriate decimal places and unit String signal = String(SStatusPrint / 10) + "." + String(abs(SStatusPrint % 10)); if (unit == 0) signal += " dBμV"; else if (unit == 1) signal += " dBf"; else if (unit == 2) signal += " dBm"; // Prepare the radio text with station information, including enhanced options if available String radioText = String(radio.rds.stationText + " " + radio.rds.stationText32); if (radio.rds.hasEnhancedRT) { radioText += " eRT: " + String(radio.rds.enhancedRTtext); } // Replace commas in the station name and radio text to avoid CSV conflicts String stationName = radio.rds.stationName; String radioTextModified = radioText; stationName.replace(",", " "); // Replace commas in station name radioTextModified.replace(",", " "); // Replace commas in radio text // Handle ECC, PTY, TA, TP, and Stereo flag String TA = radio.rds.hasTA ? "•" : " "; String TP = radio.rds.hasTP ? "•" : " "; String Stereo = radio.getStereoStatus() ? "•" : " "; String pty = String(radio.rds.stationTypeCode); String ECC = "--"; if (radio.rds.hasECC) { char eccBuffer[3]; // Buffer to hold 2-digit hex value + null terminator snprintf(eccBuffer, sizeof(eccBuffer), "%02X", radio.rds.ECC); // Format ECC as uppercase 2-digit hex ECC = String(eccBuffer); } // Construct the CSV row data String row = currentDateTime + "," + frequencyFormatted + "," + String(radio.rds.picode).substring(0, 4) + "," + signal + "," + Stereo + "," + TA + "," + TP + "," + pty + "," + ECC + "," + stationName + "," + radioTextModified + "\n"; Udp.beginPacket(remoteip, 9100); Udp.print(row); Udp.endPacket(); }