#include "logbook.h" #include "constants.h" #include String UDPlogold = ""; 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 += ""; html += ""; html += ""; html += ""; html += "\"FMDX"; html += ""; html += "

" + String(textUI(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); if (columnIndex == piCodeIndex) piCode = cell; if (columnIndex == frequencyIndex) frequency = cell; html += ""; startIndex = endIndex + 1; columnIndex++; } frequency.replace(" MHz", ""); html += ""; html += ""; } } file.close(); } else html += ""; if (!hasData) html += ""; html += "
" + column; html += "
" + cell + "🌐
" + String(textUI(299)) + "
" + String(textUI(288)) + "
"; html += ""; webserver.send(200, "text/html", html); } void handleDownloadCSV() { fs::File file = SPIFFS.open("/logbook.csv", "r"); if (!file) { webserver.send(500, "text/plain", "Failed to open logbook for download"); return; } webserver.sendHeader("Content-Type", "text/csv"); webserver.sendHeader("Content-Disposition", "attachment; filename=logbook.csv"); webserver.streamFile(file, "text/csv"); file.close(); } bool handleCreateNewLogbook() { if (SPIFFS.exists("/logbook.csv")) { if (!SPIFFS.remove("/logbook.csv")) return false; } fs::File file = SPIFFS.open("/logbook.csv", "w"); if (!file) return false; file.print("Date,Time,Frequency,PI,Signal,Stereo,TA,TP,PTY,ECC,PS,Radiotext\n"); file.flush(); file.close(); logcounter = 0; EEPROM.writeUInt(EE_UINT16_LOGCOUNTER, logcounter); EEPROM.commit(); return true; } byte addRowToCSV() { if (SPIFFS.totalBytes() - SPIFFS.usedBytes() < 150 || logcounter > 1500) return 2; fs::File file = SPIFFS.open("/logbook.csv", "a"); if (!file) return 1; String currentDateTime = getCurrentDateTime(false); if (currentDateTime == "") currentDateTime = "-,-"; 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 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"; String radioText = String(radio.rds.stationText + " " + radio.rds.stationText32); if (radio.rds.hasEnhancedRT) radioText += " eRT: " + String(radio.rds.enhancedRTtext); String stationName = radio.rds.stationName; String radioTextModified = (scanhold > 4 ? radioText : ""); stationName.replace(",", " "); radioTextModified.replace(",", " "); String TA = radio.rds.hasTA ? "•" : " "; String TP = radio.rds.TP ? "•" : " "; String Stereo = radio.getStereoStatus() ? "•" : " "; String pty = String(radio.rds.PTY); String ECC = "--"; if (radio.rds.hasECC) { char eccBuffer[3]; snprintf(eccBuffer, sizeof(eccBuffer), "%02X", radio.rds.ECC); // Format ECC as uppercase 2-digit hex ECC = String(eccBuffer); } String row = currentDateTime + "," + frequencyFormatted + "," + String(radio.rds.picode).substring(0, 4) + "," + signal + "," + Stereo + "," + TA + "," + TP + "," + pty + "," + ECC + "," + stationName + "," + radioTextModified + "\n"; if (file.print(row)) { file.close(); logcounter++; EEPROM.writeUInt(EE_UINT16_LOGCOUNTER, logcounter); EEPROM.commit(); return 0; } else { file.close(); return 1; } } String getCurrentDateTime(bool inUTC) { if (!rtcset) return "-,-"; struct tm timeInfo; if (!getLocalTime(&timeInfo)) return "-,-"; time_t currentEpoch = mktime(&timeInfo); int utcOffsetHours = 0; if (!inUTC) { currentEpoch += (NTPupdated ? NTPoffset * 3600 : radio.rds.offset); // Apply GMT offset if NTPupdated, else RDS offset if (NTPupdated && autoDST) { struct tm tempTimeInfo; localtime_r(¤tEpoch, &tempTimeInfo); if (isDST(mktime(&tempTimeInfo))) currentEpoch += 3600; } } else utcOffsetHours = (NTPupdated ? NTPoffset : radio.rds.offset / 3600); localtime_r(¤tEpoch, &timeInfo); char buf[20]; if (clockampm) { strftime(buf, sizeof(buf), "%m/%d/%Y", &timeInfo); int hour = timeInfo.tm_hour; String ampm = (hour >= 12) ? "PM" : "AM"; if (hour == 0) hour = 12; else if (hour > 12) hour -= 12; String timeWithAMPM = String(hour) + ":" + (timeInfo.tm_min < 10 ? "0" : "") + String(timeInfo.tm_min) + ":" + (timeInfo.tm_sec < 10 ? "0" : "") + String(timeInfo.tm_sec) + " " + ampm; if (inUTC) { String utcOffsetStr = ", " + String(utcOffsetHours) + ","; return String(buf) + "," + timeWithAMPM + utcOffsetStr; } return String(buf) + "," + timeWithAMPM; } else { strftime(buf, sizeof(buf), "%d-%m-%Y", &timeInfo); 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 if (inUTC) { String utcOffsetStr = ", " + String(utcOffsetHours) + ","; return String(buf) + "," + timeEuropean + utcOffsetStr; } return String(buf) + "," + timeEuropean; } } bool isDST(time_t t) { struct tm timeInfo; localtime_r(&t, &timeInfo); int month = timeInfo.tm_mon + 1; int day = timeInfo.tm_mday; int hour = timeInfo.tm_hour; int weekday = timeInfo.tm_wday; if (month == 3) { int lastSunday = 31 - ((weekday + 31 - day) % 7); if (day > lastSunday || (day == lastSunday && hour >= 2)) return true; } if (month == 10) { int lastSunday = 31 - ((weekday + 31 - day) % 7); if (day < lastSunday || (day == lastSunday && hour < 3)) return false; } 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() { fs::File file = SPIFFS.open("/logbook.csv", "r"); if (!file) { Serial.println("Failed to open logbook!"); return; } Serial.println("===== Start of logbook.csv ====="); while (file.available()) { String line = file.readStringUntil('\n'); Serial.println(line); } file.close(); Serial.println("===== End of logbook.csv ====="); } void sendUDPlog() { String currentDateTime = getCurrentDateTime(true); if (currentDateTime == "") currentDateTime = "-,-"; 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); 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 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"; String radioText = String(radio.rds.stationText + " " + radio.rds.stationText32); if (radio.rds.hasEnhancedRT) radioText += " eRT: " + String(radio.rds.enhancedRTtext); String stationName = radio.rds.stationName; stationName.replace(",", " "); String radioTextModified = radioText; radioTextModified.replace(",", " "); String ECC = ""; if (radio.rds.hasECC) { char eccBuffer[3]; snprintf(eccBuffer, sizeof(eccBuffer), "%02X", radio.rds.ECC); ECC = String(eccBuffer); } String AF = ""; if (radio.rds.hasAF && radio.af_counter > 0) { for (byte i = 0; i < radio.af_counter; i++) AF += String(radio.af[i].frequency / 100) + "." + String((radio.af[i].frequency % 100) / 10) + (i == radio.af_counter - 1 ? "" : ";"); } String EON = ""; if (radio.eon_counter > 0) { for (byte i = 0; i < radio.eon_counter; i++) { EON += String(radio.eon[i].picode) + (radio.eon[i].ps.length() > 0 ? String(";" + radio.eon[i].ps) : ";") + (radio.eon[i].mappedfreq > 0 ? String(";" + String(radio.eon[i].mappedfreq / 100) + "." + String((radio.eon[i].mappedfreq % 100) / 10)) : ";") + (radio.eon[i].mappedfreq2 > 0 ? String(";" + String(radio.eon[i].mappedfreq2 / 100) + "." + String((radio.eon[i].mappedfreq2 % 100) / 10)) : ";") + (radio.eon[i].mappedfreq3 > 0 ? String(";" + String(radio.eon[i].mappedfreq3 / 100) + "." + String((radio.eon[i].mappedfreq3 % 100) / 10)) : ";") + (i == radio.eon_counter - 1 ? "" : ";"); } } String CHIP = ""; switch (chipmodel) { case 0: CHIP = "TEF6686"; break; case 1: CHIP = "TEF6687"; break; case 2: CHIP = "TEF6688"; break; case 3: CHIP = "TEF6689"; break; } String RTPLUS = ""; if (radio.rds.hasRTplus) RTPLUS += radio.rds.RTContent1 + ";" + radio.rds.RTContent2; // Construct the data row to send via UDP String row = CHIP + "," + VERSION + "," + String(scandxmode) + "," + currentDateTime + "," + frequencyFormatted + "," + String(radio.rds.picode).substring(0, 4) + "," + signal + "," + String(radio.getStereoStatus()) + "," + String(radio.rds.hasTA) + "," + String(radio.rds.TP) + "," + String(radio.rds.hasTMC) + "," + String(radio.rds.PTY) + "," + ECC + "," + stationName + "," + radioTextModified + "," + AF + "," + EON + "," + RTPLUS + "\n"; // Send the data via UDP if it's new if (UDPlogold != row) { IPAddress broadcastIP = makeBroadcastAddress(remoteip); Udp.beginPacket(broadcastIP, 9100); Udp.print(row); Udp.endPacket(); UDPlogold = row; } } IPAddress makeBroadcastAddress(IPAddress ip) { // Assuming a typical subnet mask of 255.255.255.0 return IPAddress(ip[0], ip[1], ip[2], 255); }