diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp index e003f45c..87033f0f 100644 --- a/lib/Epub/Epub.cpp +++ b/lib/Epub/Epub.cpp @@ -2,8 +2,8 @@ #include #include -#include #include +#include #include #include "Epub/parsers/ContainerParser.h" @@ -17,7 +17,7 @@ bool Epub::findContentOpfFile(std::string* contentOpfFile) const { // Get file size without loading it all into heap if (!getItemSize(containerPath, &containerSize)) { - Serial.printf("[%lu] [EBP] Could not find or size META-INF/container.xml\n", millis()); + LOG_ERR("EBP", "Could not find or size META-INF/container.xml"); return false; } @@ -29,13 +29,13 @@ bool Epub::findContentOpfFile(std::string* contentOpfFile) const { // Stream read (reusing your existing stream logic) if (!readItemContentsToStream(containerPath, containerParser, 512)) { - Serial.printf("[%lu] [EBP] Could not read META-INF/container.xml\n", millis()); + LOG_ERR("EBP", "Could not read META-INF/container.xml"); return false; } // Extract the result if (containerParser.fullPath.empty()) { - Serial.printf("[%lu] [EBP] Could not find valid rootfile in container.xml\n", millis()); + LOG_ERR("EBP", "Could not find valid rootfile in container.xml"); return false; } @@ -46,28 +46,28 @@ bool Epub::findContentOpfFile(std::string* contentOpfFile) const { bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata) { std::string contentOpfFilePath; if (!findContentOpfFile(&contentOpfFilePath)) { - Serial.printf("[%lu] [EBP] Could not find content.opf in zip\n", millis()); + LOG_ERR("EBP", "Could not find content.opf in zip"); return false; } contentBasePath = contentOpfFilePath.substr(0, contentOpfFilePath.find_last_of('/') + 1); - Serial.printf("[%lu] [EBP] Parsing content.opf: %s\n", millis(), contentOpfFilePath.c_str()); + LOG_DBG("EBP", "Parsing content.opf: %s", contentOpfFilePath.c_str()); size_t contentOpfSize; if (!getItemSize(contentOpfFilePath, &contentOpfSize)) { - Serial.printf("[%lu] [EBP] Could not get size of content.opf\n", millis()); + LOG_ERR("EBP", "Could not get size of content.opf"); return false; } ContentOpfParser opfParser(getCachePath(), getBasePath(), contentOpfSize, bookMetadataCache.get()); if (!opfParser.setup()) { - Serial.printf("[%lu] [EBP] Could not setup content.opf parser\n", millis()); + LOG_ERR("EBP", "Could not setup content.opf parser"); return false; } if (!readItemContentsToStream(contentOpfFilePath, opfParser, 1024)) { - Serial.printf("[%lu] [EBP] Could not read content.opf\n", millis()); + LOG_ERR("EBP", "Could not read content.opf"); return false; } @@ -90,18 +90,18 @@ bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata) { cssFiles = opfParser.cssFiles; } - Serial.printf("[%lu] [EBP] Successfully parsed content.opf\n", millis()); + LOG_DBG("EBP", "Successfully parsed content.opf"); return true; } bool Epub::parseTocNcxFile() const { // the ncx file should have been specified in the content.opf file if (tocNcxItem.empty()) { - Serial.printf("[%lu] [EBP] No ncx file specified\n", millis()); + LOG_DBG("EBP", "No ncx file specified"); return false; } - Serial.printf("[%lu] [EBP] Parsing toc ncx file: %s\n", millis(), tocNcxItem.c_str()); + LOG_DBG("EBP", "Parsing toc ncx file: %s", tocNcxItem.c_str()); const auto tmpNcxPath = getCachePath() + "/toc.ncx"; FsFile tempNcxFile; @@ -118,14 +118,14 @@ bool Epub::parseTocNcxFile() const { TocNcxParser ncxParser(contentBasePath, ncxSize, bookMetadataCache.get()); if (!ncxParser.setup()) { - Serial.printf("[%lu] [EBP] Could not setup toc ncx parser\n", millis()); + LOG_ERR("EBP", "Could not setup toc ncx parser"); tempNcxFile.close(); return false; } const auto ncxBuffer = static_cast(malloc(1024)); if (!ncxBuffer) { - Serial.printf("[%lu] [EBP] Could not allocate memory for toc ncx parser\n", millis()); + LOG_ERR("EBP", "Could not allocate memory for toc ncx parser"); tempNcxFile.close(); return false; } @@ -136,7 +136,7 @@ bool Epub::parseTocNcxFile() const { const auto processedSize = ncxParser.write(ncxBuffer, readSize); if (processedSize != readSize) { - Serial.printf("[%lu] [EBP] Could not process all toc ncx data\n", millis()); + LOG_ERR("EBP", "Could not process all toc ncx data"); free(ncxBuffer); tempNcxFile.close(); return false; @@ -147,18 +147,18 @@ bool Epub::parseTocNcxFile() const { tempNcxFile.close(); Storage.remove(tmpNcxPath.c_str()); - Serial.printf("[%lu] [EBP] Parsed TOC items\n", millis()); + LOG_DBG("EBP", "Parsed TOC items"); return true; } bool Epub::parseTocNavFile() const { // the nav file should have been specified in the content.opf file (EPUB 3) if (tocNavItem.empty()) { - Serial.printf("[%lu] [EBP] No nav file specified\n", millis()); + LOG_DBG("EBP", "No nav file specified"); return false; } - Serial.printf("[%lu] [EBP] Parsing toc nav file: %s\n", millis(), tocNavItem.c_str()); + LOG_DBG("EBP", "Parsing toc nav file: %s", tocNavItem.c_str()); const auto tmpNavPath = getCachePath() + "/toc.nav"; FsFile tempNavFile; @@ -178,13 +178,13 @@ bool Epub::parseTocNavFile() const { TocNavParser navParser(navContentBasePath, navSize, bookMetadataCache.get()); if (!navParser.setup()) { - Serial.printf("[%lu] [EBP] Could not setup toc nav parser\n", millis()); + LOG_ERR("EBP", "Could not setup toc nav parser"); return false; } const auto navBuffer = static_cast(malloc(1024)); if (!navBuffer) { - Serial.printf("[%lu] [EBP] Could not allocate memory for toc nav parser\n", millis()); + LOG_ERR("EBP", "Could not allocate memory for toc nav parser"); return false; } @@ -193,7 +193,7 @@ bool Epub::parseTocNavFile() const { const auto processedSize = navParser.write(navBuffer, readSize); if (processedSize != readSize) { - Serial.printf("[%lu] [EBP] Could not process all toc nav data\n", millis()); + LOG_ERR("EBP", "Could not process all toc nav data"); free(navBuffer); tempNavFile.close(); return false; @@ -204,7 +204,7 @@ bool Epub::parseTocNavFile() const { tempNavFile.close(); Storage.remove(tmpNavPath.c_str()); - Serial.printf("[%lu] [EBP] Parsed TOC nav items\n", millis()); + LOG_DBG("EBP", "Parsed TOC nav items"); return true; } @@ -215,35 +215,35 @@ bool Epub::loadCssRulesFromCache() const { if (Storage.openFileForRead("EBP", getCssRulesCache(), cssCacheFile)) { if (cssParser->loadFromCache(cssCacheFile)) { cssCacheFile.close(); - Serial.printf("[%lu] [EBP] Loaded CSS rules from cache\n", millis()); + LOG_DBG("EBP", "Loaded CSS rules from cache"); return true; } cssCacheFile.close(); - Serial.printf("[%lu] [EBP] CSS cache invalid, reparsing\n", millis()); + LOG_DBG("EBP", "CSS cache invalid, reparsing"); } return false; } void Epub::parseCssFiles() const { if (cssFiles.empty()) { - Serial.printf("[%lu] [EBP] No CSS files to parse, but CssParser created for inline styles\n", millis()); + LOG_DBG("EBP", "No CSS files to parse, but CssParser created for inline styles"); } // Try to load from CSS cache first if (!loadCssRulesFromCache()) { // Cache miss - parse CSS files for (const auto& cssPath : cssFiles) { - Serial.printf("[%lu] [EBP] Parsing CSS file: %s\n", millis(), cssPath.c_str()); + LOG_DBG("EBP", "Parsing CSS file: %s", cssPath.c_str()); // Extract CSS file to temp location const auto tmpCssPath = getCachePath() + "/.tmp.css"; FsFile tempCssFile; if (!Storage.openFileForWrite("EBP", tmpCssPath, tempCssFile)) { - Serial.printf("[%lu] [EBP] Could not create temp CSS file\n", millis()); + LOG_ERR("EBP", "Could not create temp CSS file"); continue; } if (!readItemContentsToStream(cssPath, tempCssFile, 1024)) { - Serial.printf("[%lu] [EBP] Could not read CSS file: %s\n", millis(), cssPath.c_str()); + LOG_ERR("EBP", "Could not read CSS file: %s", cssPath.c_str()); tempCssFile.close(); Storage.remove(tmpCssPath.c_str()); continue; @@ -252,7 +252,7 @@ void Epub::parseCssFiles() const { // Parse the CSS file if (!Storage.openFileForRead("EBP", tmpCssPath, tempCssFile)) { - Serial.printf("[%lu] [EBP] Could not open temp CSS file for reading\n", millis()); + LOG_ERR("EBP", "Could not open temp CSS file for reading"); Storage.remove(tmpCssPath.c_str()); continue; } @@ -268,14 +268,13 @@ void Epub::parseCssFiles() const { cssCacheFile.close(); } - Serial.printf("[%lu] [EBP] Loaded %zu CSS style rules from %zu files\n", millis(), cssParser->ruleCount(), - cssFiles.size()); + LOG_DBG("EBP", "Loaded %zu CSS style rules from %zu files", cssParser->ruleCount(), cssFiles.size()); } } // load in the meta data for the epub file bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) { - Serial.printf("[%lu] [EBP] Loading ePub: %s\n", millis(), filepath.c_str()); + LOG_DBG("EBP", "Loading ePub: %s", filepath.c_str()); // Initialize spine/TOC cache bookMetadataCache.reset(new BookMetadataCache(cachePath)); @@ -285,15 +284,15 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) { // Try to load existing cache first if (bookMetadataCache->load()) { if (!skipLoadingCss && !loadCssRulesFromCache()) { - Serial.printf("[%lu] [EBP] Warning: CSS rules cache not found, attempting to parse CSS files\n", millis()); + LOG_DBG("EBP", "Warning: CSS rules cache not found, attempting to parse CSS files"); // to get CSS file list if (!parseContentOpf(bookMetadataCache->coreMetadata)) { - Serial.printf("[%lu] [EBP] Could not parse content.opf from cached bookMetadata for CSS files\n", millis()); + LOG_ERR("EBP", "Could not parse content.opf from cached bookMetadata for CSS files"); // continue anyway - book will work without CSS and we'll still load any inline style CSS } parseCssFiles(); } - Serial.printf("[%lu] [EBP] Loaded ePub: %s\n", millis(), filepath.c_str()); + LOG_DBG("EBP", "Loaded ePub: %s", filepath.c_str()); return true; } @@ -303,14 +302,14 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) { } // Cache doesn't exist or is invalid, build it - Serial.printf("[%lu] [EBP] Cache not found, building spine/TOC cache\n", millis()); + LOG_DBG("EBP", "Cache not found, building spine/TOC cache"); setupCacheDir(); const uint32_t indexingStart = millis(); // Begin building cache - stream entries to disk immediately if (!bookMetadataCache->beginWrite()) { - Serial.printf("[%lu] [EBP] Could not begin writing cache\n", millis()); + LOG_ERR("EBP", "Could not begin writing cache"); return false; } @@ -318,23 +317,23 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) { const uint32_t opfStart = millis(); BookMetadataCache::BookMetadata bookMetadata; if (!bookMetadataCache->beginContentOpfPass()) { - Serial.printf("[%lu] [EBP] Could not begin writing content.opf pass\n", millis()); + LOG_ERR("EBP", "Could not begin writing content.opf pass"); return false; } if (!parseContentOpf(bookMetadata)) { - Serial.printf("[%lu] [EBP] Could not parse content.opf\n", millis()); + LOG_ERR("EBP", "Could not parse content.opf"); return false; } if (!bookMetadataCache->endContentOpfPass()) { - Serial.printf("[%lu] [EBP] Could not end writing content.opf pass\n", millis()); + LOG_ERR("EBP", "Could not end writing content.opf pass"); return false; } - Serial.printf("[%lu] [EBP] OPF pass completed in %lu ms\n", millis(), millis() - opfStart); + LOG_DBG("EBP", "OPF pass completed in %lu ms", millis() - opfStart); // TOC Pass - try EPUB 3 nav first, fall back to NCX const uint32_t tocStart = millis(); if (!bookMetadataCache->beginTocPass()) { - Serial.printf("[%lu] [EBP] Could not begin writing toc pass\n", millis()); + LOG_ERR("EBP", "Could not begin writing toc pass"); return false; } @@ -342,50 +341,50 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) { // Try EPUB 3 nav document first (preferred) if (!tocNavItem.empty()) { - Serial.printf("[%lu] [EBP] Attempting to parse EPUB 3 nav document\n", millis()); + LOG_DBG("EBP", "Attempting to parse EPUB 3 nav document"); tocParsed = parseTocNavFile(); } // Fall back to NCX if nav parsing failed or wasn't available if (!tocParsed && !tocNcxItem.empty()) { - Serial.printf("[%lu] [EBP] Falling back to NCX TOC\n", millis()); + LOG_DBG("EBP", "Falling back to NCX TOC"); tocParsed = parseTocNcxFile(); } if (!tocParsed) { - Serial.printf("[%lu] [EBP] Warning: Could not parse any TOC format\n", millis()); + LOG_ERR("EBP", "Warning: Could not parse any TOC format"); // Continue anyway - book will work without TOC } if (!bookMetadataCache->endTocPass()) { - Serial.printf("[%lu] [EBP] Could not end writing toc pass\n", millis()); + LOG_ERR("EBP", "Could not end writing toc pass"); return false; } - Serial.printf("[%lu] [EBP] TOC pass completed in %lu ms\n", millis(), millis() - tocStart); + LOG_DBG("EBP", "TOC pass completed in %lu ms", millis() - tocStart); // Close the cache files if (!bookMetadataCache->endWrite()) { - Serial.printf("[%lu] [EBP] Could not end writing cache\n", millis()); + LOG_ERR("EBP", "Could not end writing cache"); return false; } // Build final book.bin const uint32_t buildStart = millis(); if (!bookMetadataCache->buildBookBin(filepath, bookMetadata)) { - Serial.printf("[%lu] [EBP] Could not update mappings and sizes\n", millis()); + LOG_ERR("EBP", "Could not update mappings and sizes"); return false; } - Serial.printf("[%lu] [EBP] buildBookBin completed in %lu ms\n", millis(), millis() - buildStart); - Serial.printf("[%lu] [EBP] Total indexing completed in %lu ms\n", millis(), millis() - indexingStart); + LOG_DBG("EBP", "buildBookBin completed in %lu ms", millis() - buildStart); + LOG_DBG("EBP", "Total indexing completed in %lu ms", millis() - indexingStart); if (!bookMetadataCache->cleanupTmpFiles()) { - Serial.printf("[%lu] [EBP] Could not cleanup tmp files - ignoring\n", millis()); + LOG_DBG("EBP", "Could not cleanup tmp files - ignoring"); } // Reload the cache from disk so it's in the correct state bookMetadataCache.reset(new BookMetadataCache(cachePath)); if (!bookMetadataCache->load()) { - Serial.printf("[%lu] [EBP] Failed to reload cache after writing\n", millis()); + LOG_ERR("EBP", "Failed to reload cache after writing"); return false; } @@ -394,22 +393,22 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) { parseCssFiles(); } - Serial.printf("[%lu] [EBP] Loaded ePub: %s\n", millis(), filepath.c_str()); + LOG_DBG("EBP", "Loaded ePub: %s", filepath.c_str()); return true; } bool Epub::clearCache() const { if (!Storage.exists(cachePath.c_str())) { - Serial.printf("[%lu] [EPB] Cache does not exist, no action needed\n", millis()); + LOG_DBG("EPB", "Cache does not exist, no action needed"); return true; } if (!Storage.removeDir(cachePath.c_str())) { - Serial.printf("[%lu] [EPB] Failed to clear cache\n", millis()); + LOG_ERR("EPB", "Failed to clear cache"); return false; } - Serial.printf("[%lu] [EPB] Cache cleared successfully\n", millis()); + LOG_DBG("EPB", "Cache cleared successfully"); return true; } @@ -464,19 +463,19 @@ bool Epub::generateCoverBmp(bool cropped) const { } if (!bookMetadataCache || !bookMetadataCache->isLoaded()) { - Serial.printf("[%lu] [EBP] Cannot generate cover BMP, cache not loaded\n", millis()); + LOG_ERR("EBP", "Cannot generate cover BMP, cache not loaded"); return false; } const auto coverImageHref = bookMetadataCache->coreMetadata.coverItemHref; if (coverImageHref.empty()) { - Serial.printf("[%lu] [EBP] No known cover image\n", millis()); + LOG_ERR("EBP", "No known cover image"); return false; } if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" || coverImageHref.substr(coverImageHref.length() - 5) == ".jpeg") { - Serial.printf("[%lu] [EBP] Generating BMP from JPG cover image (%s mode)\n", millis(), cropped ? "cropped" : "fit"); + LOG_DBG("EBP", "Generating BMP from JPG cover image (%s mode)", cropped ? "cropped" : "fit"); const auto coverJpgTempPath = getCachePath() + "/.cover.jpg"; FsFile coverJpg; @@ -501,13 +500,13 @@ bool Epub::generateCoverBmp(bool cropped) const { Storage.remove(coverJpgTempPath.c_str()); if (!success) { - Serial.printf("[%lu] [EBP] Failed to generate BMP from JPG cover image\n", millis()); + LOG_ERR("EBP", "Failed to generate BMP from cover image"); Storage.remove(getCoverBmpPath(cropped).c_str()); } - Serial.printf("[%lu] [EBP] Generated BMP from JPG cover image, success: %s\n", millis(), success ? "yes" : "no"); + LOG_DBG("EBP", "Generated BMP from cover image, success: %s", success ? "yes" : "no"); return success; } else { - Serial.printf("[%lu] [EBP] Cover image is not a JPG, skipping\n", millis()); + LOG_ERR("EBP", "Cover image is not a supported format, skipping"); } return false; @@ -523,16 +522,16 @@ bool Epub::generateThumbBmp(int height) const { } if (!bookMetadataCache || !bookMetadataCache->isLoaded()) { - Serial.printf("[%lu] [EBP] Cannot generate thumb BMP, cache not loaded\n", millis()); + LOG_ERR("EBP", "Cannot generate thumb BMP, cache not loaded"); return false; } const auto coverImageHref = bookMetadataCache->coreMetadata.coverItemHref; if (coverImageHref.empty()) { - Serial.printf("[%lu] [EBP] No known cover image for thumbnail\n", millis()); + LOG_DBG("EBP", "No known cover image for thumbnail"); } else if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" || coverImageHref.substr(coverImageHref.length() - 5) == ".jpeg") { - Serial.printf("[%lu] [EBP] Generating thumb BMP from JPG cover image\n", millis()); + LOG_DBG("EBP", "Generating thumb BMP from JPG cover image"); const auto coverJpgTempPath = getCachePath() + "/.cover.jpg"; FsFile coverJpg; @@ -562,14 +561,13 @@ bool Epub::generateThumbBmp(int height) const { Storage.remove(coverJpgTempPath.c_str()); if (!success) { - Serial.printf("[%lu] [EBP] Failed to generate thumb BMP from JPG cover image\n", millis()); + LOG_ERR("EBP", "Failed to generate thumb BMP from JPG cover image"); Storage.remove(getThumbBmpPath(height).c_str()); } - Serial.printf("[%lu] [EBP] Generated thumb BMP from JPG cover image, success: %s\n", millis(), - success ? "yes" : "no"); + LOG_DBG("EBP", "Generated thumb BMP from JPG cover image, success: %s", success ? "yes" : "no"); return success; } else { - Serial.printf("[%lu] [EBP] Cover image is not a JPG, skipping thumbnail\n", millis()); + LOG_ERR("EBP", "Cover image is not a supported format, skipping thumbnail"); } // Write an empty bmp file to avoid generation attempts in the future @@ -581,7 +579,7 @@ bool Epub::generateThumbBmp(int height) const { uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size, const bool trailingNullByte) const { if (itemHref.empty()) { - Serial.printf("[%lu] [EBP] Failed to read item, empty href\n", millis()); + LOG_DBG("EBP", "Failed to read item, empty href"); return nullptr; } @@ -589,7 +587,7 @@ uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size const auto content = ZipFile(filepath).readFileToMemory(path.c_str(), size, trailingNullByte); if (!content) { - Serial.printf("[%lu] [EBP] Failed to read item %s\n", millis(), path.c_str()); + LOG_DBG("EBP", "Failed to read item %s", path.c_str()); return nullptr; } @@ -598,7 +596,7 @@ uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size bool Epub::readItemContentsToStream(const std::string& itemHref, Print& out, const size_t chunkSize) const { if (itemHref.empty()) { - Serial.printf("[%lu] [EBP] Failed to read item, empty href\n", millis()); + LOG_DBG("EBP", "Failed to read item, empty href"); return false; } @@ -622,12 +620,12 @@ size_t Epub::getCumulativeSpineItemSize(const int spineIndex) const { return get BookMetadataCache::SpineEntry Epub::getSpineItem(const int spineIndex) const { if (!bookMetadataCache || !bookMetadataCache->isLoaded()) { - Serial.printf("[%lu] [EBP] getSpineItem called but cache not loaded\n", millis()); + LOG_ERR("EBP", "getSpineItem called but cache not loaded"); return {}; } if (spineIndex < 0 || spineIndex >= bookMetadataCache->getSpineCount()) { - Serial.printf("[%lu] [EBP] getSpineItem index:%d is out of range\n", millis(), spineIndex); + LOG_ERR("EBP", "getSpineItem index:%d is out of range", spineIndex); return bookMetadataCache->getSpineEntry(0); } @@ -636,12 +634,12 @@ BookMetadataCache::SpineEntry Epub::getSpineItem(const int spineIndex) const { BookMetadataCache::TocEntry Epub::getTocItem(const int tocIndex) const { if (!bookMetadataCache || !bookMetadataCache->isLoaded()) { - Serial.printf("[%lu] [EBP] getTocItem called but cache not loaded\n", millis()); + LOG_DBG("EBP", "getTocItem called but cache not loaded"); return {}; } if (tocIndex < 0 || tocIndex >= bookMetadataCache->getTocCount()) { - Serial.printf("[%lu] [EBP] getTocItem index:%d is out of range\n", millis(), tocIndex); + LOG_DBG("EBP", "getTocItem index:%d is out of range", tocIndex); return {}; } @@ -659,18 +657,18 @@ int Epub::getTocItemsCount() const { // work out the section index for a toc index int Epub::getSpineIndexForTocIndex(const int tocIndex) const { if (!bookMetadataCache || !bookMetadataCache->isLoaded()) { - Serial.printf("[%lu] [EBP] getSpineIndexForTocIndex called but cache not loaded\n", millis()); + LOG_ERR("EBP", "getSpineIndexForTocIndex called but cache not loaded"); return 0; } if (tocIndex < 0 || tocIndex >= bookMetadataCache->getTocCount()) { - Serial.printf("[%lu] [EBP] getSpineIndexForTocIndex: tocIndex %d out of range\n", millis(), tocIndex); + LOG_ERR("EBP", "getSpineIndexForTocIndex: tocIndex %d out of range", tocIndex); return 0; } const int spineIndex = bookMetadataCache->getTocEntry(tocIndex).spineIndex; if (spineIndex < 0) { - Serial.printf("[%lu] [EBP] Section not found for TOC index %d\n", millis(), tocIndex); + LOG_DBG("EBP", "Section not found for TOC index %d", tocIndex); return 0; } @@ -688,14 +686,13 @@ size_t Epub::getBookSize() const { int Epub::getSpineIndexForTextReference() const { if (!bookMetadataCache || !bookMetadataCache->isLoaded()) { - Serial.printf("[%lu] [EBP] getSpineIndexForTextReference called but cache not loaded\n", millis()); + LOG_ERR("EBP", "getSpineIndexForTextReference called but cache not loaded"); return 0; } - Serial.printf("[%lu] [ERS] Core Metadata: cover(%d)=%s, textReference(%d)=%s\n", millis(), - bookMetadataCache->coreMetadata.coverItemHref.size(), - bookMetadataCache->coreMetadata.coverItemHref.c_str(), - bookMetadataCache->coreMetadata.textReferenceHref.size(), - bookMetadataCache->coreMetadata.textReferenceHref.c_str()); + LOG_DBG("EBP", "Core Metadata: cover(%d)=%s, textReference(%d)=%s", + bookMetadataCache->coreMetadata.coverItemHref.size(), bookMetadataCache->coreMetadata.coverItemHref.c_str(), + bookMetadataCache->coreMetadata.textReferenceHref.size(), + bookMetadataCache->coreMetadata.textReferenceHref.c_str()); if (bookMetadataCache->coreMetadata.textReferenceHref.empty()) { // there was no textReference in epub, so we return 0 (the first chapter) @@ -705,13 +702,13 @@ int Epub::getSpineIndexForTextReference() const { // loop through spine items to get the correct index matching the text href for (size_t i = 0; i < getSpineItemsCount(); i++) { if (getSpineItem(i).href == bookMetadataCache->coreMetadata.textReferenceHref) { - Serial.printf("[%lu] [ERS] Text reference %s found at index %d\n", millis(), - bookMetadataCache->coreMetadata.textReferenceHref.c_str(), i); + LOG_DBG("EBP", "Text reference %s found at index %d", bookMetadataCache->coreMetadata.textReferenceHref.c_str(), + i); return i; } } // This should not happen, as we checked for empty textReferenceHref earlier - Serial.printf("[%lu] [EBP] Section not found for text reference\n", millis()); + LOG_DBG("EBP", "Section not found for text reference"); return 0; } diff --git a/lib/Epub/Epub/BookMetadataCache.cpp b/lib/Epub/Epub/BookMetadataCache.cpp index c8ba7db5..236c6b17 100644 --- a/lib/Epub/Epub/BookMetadataCache.cpp +++ b/lib/Epub/Epub/BookMetadataCache.cpp @@ -1,6 +1,6 @@ #include "BookMetadataCache.h" -#include +#include #include #include @@ -21,12 +21,12 @@ bool BookMetadataCache::beginWrite() { buildMode = true; spineCount = 0; tocCount = 0; - Serial.printf("[%lu] [BMC] Entering write mode\n", millis()); + LOG_DBG("BMC", "Entering write mode"); return true; } bool BookMetadataCache::beginContentOpfPass() { - Serial.printf("[%lu] [BMC] Beginning content opf pass\n", millis()); + LOG_DBG("BMC", "Beginning content opf pass"); // Open spine file for writing return Storage.openFileForWrite("BMC", cachePath + tmpSpineBinFile, spineFile); @@ -38,7 +38,7 @@ bool BookMetadataCache::endContentOpfPass() { } bool BookMetadataCache::beginTocPass() { - Serial.printf("[%lu] [BMC] Beginning toc pass\n", millis()); + LOG_DBG("BMC", "Beginning toc pass"); if (!Storage.openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) { return false; @@ -66,7 +66,7 @@ bool BookMetadataCache::beginTocPass() { }); spineFile.seek(0); useSpineHrefIndex = true; - Serial.printf("[%lu] [BMC] Using fast index for %d spine items\n", millis(), spineCount); + LOG_DBG("BMC", "Using fast index for %d spine items", spineCount); } else { useSpineHrefIndex = false; } @@ -87,12 +87,12 @@ bool BookMetadataCache::endTocPass() { bool BookMetadataCache::endWrite() { if (!buildMode) { - Serial.printf("[%lu] [BMC] endWrite called but not in build mode\n", millis()); + LOG_DBG("BMC", "endWrite called but not in build mode"); return false; } buildMode = false; - Serial.printf("[%lu] [BMC] Wrote %d spine, %d TOC entries\n", millis(), spineCount, tocCount); + LOG_DBG("BMC", "Wrote %d spine, %d TOC entries", spineCount, tocCount); return true; } @@ -167,7 +167,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta ZipFile zip(epubPath); // Pre-open zip file to speed up size calculations if (!zip.open()) { - Serial.printf("[%lu] [BMC] Could not open EPUB zip for size calculations\n", millis()); + LOG_ERR("BMC", "Could not open EPUB zip for size calculations"); bookFile.close(); spineFile.close(); tocFile.close(); @@ -185,7 +185,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta bool useBatchSizes = false; if (spineCount >= LARGE_SPINE_THRESHOLD) { - Serial.printf("[%lu] [BMC] Using batch size lookup for %d spine items\n", millis(), spineCount); + LOG_DBG("BMC", "Using batch size lookup for %d spine items", spineCount); std::vector targets; targets.reserve(spineCount); @@ -208,7 +208,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta spineSizes.resize(spineCount, 0); int matched = zip.fillUncompressedSizes(targets, spineSizes); - Serial.printf("[%lu] [BMC] Batch lookup matched %d/%d spine items\n", millis(), matched, spineCount); + LOG_DBG("BMC", "Batch lookup matched %d/%d spine items", matched, spineCount); targets.clear(); targets.shrink_to_fit(); @@ -227,9 +227,8 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta // Not a huge deal if we don't fine a TOC entry for the spine entry, this is expected behaviour for EPUBs // Logging here is for debugging if (spineEntry.tocIndex == -1) { - Serial.printf( - "[%lu] [BMC] Warning: Could not find TOC entry for spine item %d: %s, using title from last section\n", - millis(), i, spineEntry.href.c_str()); + LOG_DBG("BMC", "Warning: Could not find TOC entry for spine item %d: %s, using title from last section", i, + spineEntry.href.c_str()); spineEntry.tocIndex = lastSpineTocIndex; } lastSpineTocIndex = spineEntry.tocIndex; @@ -240,13 +239,13 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta if (itemSize == 0) { const std::string path = FsHelpers::normalisePath(spineEntry.href); if (!zip.getInflatedFileSize(path.c_str(), &itemSize)) { - Serial.printf("[%lu] [BMC] Warning: Could not get size for spine item: %s\n", millis(), path.c_str()); + LOG_ERR("BMC", "Warning: Could not get size for spine item: %s", path.c_str()); } } } else { const std::string path = FsHelpers::normalisePath(spineEntry.href); if (!zip.getInflatedFileSize(path.c_str(), &itemSize)) { - Serial.printf("[%lu] [BMC] Warning: Could not get size for spine item: %s\n", millis(), path.c_str()); + LOG_ERR("BMC", "Warning: Could not get size for spine item: %s", path.c_str()); } } @@ -270,7 +269,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta spineFile.close(); tocFile.close(); - Serial.printf("[%lu] [BMC] Successfully built book.bin\n", millis()); + LOG_DBG("BMC", "Successfully built book.bin"); return true; } @@ -306,7 +305,7 @@ uint32_t BookMetadataCache::writeTocEntry(FsFile& file, const TocEntry& entry) c // this is because in this function we're marking positions of the items void BookMetadataCache::createSpineEntry(const std::string& href) { if (!buildMode || !spineFile) { - Serial.printf("[%lu] [BMC] createSpineEntry called but not in build mode\n", millis()); + LOG_DBG("BMC", "createSpineEntry called but not in build mode"); return; } @@ -318,7 +317,7 @@ void BookMetadataCache::createSpineEntry(const std::string& href) { void BookMetadataCache::createTocEntry(const std::string& title, const std::string& href, const std::string& anchor, const uint8_t level) { if (!buildMode || !tocFile || !spineFile) { - Serial.printf("[%lu] [BMC] createTocEntry called but not in build mode\n", millis()); + LOG_DBG("BMC", "createTocEntry called but not in build mode"); return; } @@ -340,7 +339,7 @@ void BookMetadataCache::createTocEntry(const std::string& title, const std::stri } if (spineIndex == -1) { - Serial.printf("[%lu] [BMC] createTocEntry: Could not find spine item for TOC href %s\n", millis(), href.c_str()); + LOG_DBG("BMC", "createTocEntry: Could not find spine item for TOC href %s", href.c_str()); } } else { spineFile.seek(0); @@ -352,7 +351,7 @@ void BookMetadataCache::createTocEntry(const std::string& title, const std::stri } } if (spineIndex == -1) { - Serial.printf("[%lu] [BMC] createTocEntry: Could not find spine item for TOC href %s\n", millis(), href.c_str()); + LOG_DBG("BMC", "createTocEntry: Could not find spine item for TOC href %s", href.c_str()); } } @@ -371,7 +370,7 @@ bool BookMetadataCache::load() { uint8_t version; serialization::readPod(bookFile, version); if (version != BOOK_CACHE_VERSION) { - Serial.printf("[%lu] [BMC] Cache version mismatch: expected %d, got %d\n", millis(), BOOK_CACHE_VERSION, version); + LOG_DBG("BMC", "Cache version mismatch: expected %d, got %d", BOOK_CACHE_VERSION, version); bookFile.close(); return false; } @@ -387,18 +386,18 @@ bool BookMetadataCache::load() { serialization::readString(bookFile, coreMetadata.textReferenceHref); loaded = true; - Serial.printf("[%lu] [BMC] Loaded cache data: %d spine, %d TOC entries\n", millis(), spineCount, tocCount); + LOG_DBG("BMC", "Loaded cache data: %d spine, %d TOC entries", spineCount, tocCount); return true; } BookMetadataCache::SpineEntry BookMetadataCache::getSpineEntry(const int index) { if (!loaded) { - Serial.printf("[%lu] [BMC] getSpineEntry called but cache not loaded\n", millis()); + LOG_ERR("BMC", "getSpineEntry called but cache not loaded"); return {}; } if (index < 0 || index >= static_cast(spineCount)) { - Serial.printf("[%lu] [BMC] getSpineEntry index %d out of range\n", millis(), index); + LOG_ERR("BMC", "getSpineEntry index %d out of range", index); return {}; } @@ -412,12 +411,12 @@ BookMetadataCache::SpineEntry BookMetadataCache::getSpineEntry(const int index) BookMetadataCache::TocEntry BookMetadataCache::getTocEntry(const int index) { if (!loaded) { - Serial.printf("[%lu] [BMC] getTocEntry called but cache not loaded\n", millis()); + LOG_ERR("BMC", "getTocEntry called but cache not loaded"); return {}; } if (index < 0 || index >= static_cast(tocCount)) { - Serial.printf("[%lu] [BMC] getTocEntry index %d out of range\n", millis(), index); + LOG_ERR("BMC", "getTocEntry index %d out of range", index); return {}; } diff --git a/lib/Epub/Epub/Page.cpp b/lib/Epub/Epub/Page.cpp index 92839eb7..cf9206e3 100644 --- a/lib/Epub/Epub/Page.cpp +++ b/lib/Epub/Epub/Page.cpp @@ -1,6 +1,6 @@ #include "Page.h" -#include +#include #include void PageLine::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) { @@ -60,7 +60,7 @@ std::unique_ptr Page::deserialize(FsFile& file) { auto pl = PageLine::deserialize(file); page->elements.push_back(std::move(pl)); } else { - Serial.printf("[%lu] [PGE] Deserialization failed: Unknown tag %u\n", millis(), tag); + LOG_ERR("PGE", "Deserialization failed: Unknown tag %u", tag); return nullptr; } } diff --git a/lib/Epub/Epub/ParsedText.cpp b/lib/Epub/Epub/ParsedText.cpp index 22162594..7020cc92 100644 --- a/lib/Epub/Epub/ParsedText.cpp +++ b/lib/Epub/Epub/ParsedText.cpp @@ -31,6 +31,9 @@ void stripSoftHyphensInPlace(std::string& word) { // Returns the rendered width for a word while ignoring soft hyphen glyphs and optionally appending a visible hyphen. uint16_t measureWordWidth(const GfxRenderer& renderer, const int fontId, const std::string& word, const EpdFontFamily::Style style, const bool appendHyphen = false) { + if (word.size() == 1 && word[0] == ' ' && !appendHyphen) { + return renderer.getSpaceWidth(fontId); + } const bool hasSoftHyphen = containsSoftHyphen(word); if (!hasSoftHyphen && !appendHyphen) { return renderer.getTextWidth(fontId, word.c_str(), style); diff --git a/lib/Epub/Epub/Section.cpp b/lib/Epub/Epub/Section.cpp index 6b9b49fd..5039c57b 100644 --- a/lib/Epub/Epub/Section.cpp +++ b/lib/Epub/Epub/Section.cpp @@ -1,6 +1,7 @@ #include "Section.h" #include +#include #include #include "Page.h" @@ -16,16 +17,16 @@ constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) + uint32_t Section::onPageComplete(std::unique_ptr page) { if (!file) { - Serial.printf("[%lu] [SCT] File not open for writing page %d\n", millis(), pageCount); + LOG_ERR("SCT", "File not open for writing page %d", pageCount); return 0; } const uint32_t position = file.position(); if (!page->serialize(file)) { - Serial.printf("[%lu] [SCT] Failed to serialize page %d\n", millis(), pageCount); + LOG_ERR("SCT", "Failed to serialize page %d", pageCount); return 0; } - Serial.printf("[%lu] [SCT] Page %d processed\n", millis(), pageCount); + LOG_DBG("SCT", "Page %d processed", pageCount); pageCount++; return position; @@ -36,7 +37,7 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi const uint16_t viewportHeight, const bool hyphenationEnabled, const bool embeddedStyle) { if (!file) { - Serial.printf("[%lu] [SCT] File not open for writing header\n", millis()); + LOG_DBG("SCT", "File not open for writing header"); return; } static_assert(HEADER_SIZE == sizeof(SECTION_FILE_VERSION) + sizeof(fontId) + sizeof(lineCompression) + @@ -70,7 +71,7 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con serialization::readPod(file, version); if (version != SECTION_FILE_VERSION) { file.close(); - Serial.printf("[%lu] [SCT] Deserialization failed: Unknown version %u\n", millis(), version); + LOG_ERR("SCT", "Deserialization failed: Unknown version %u", version); clearCache(); return false; } @@ -96,7 +97,7 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con viewportWidth != fileViewportWidth || viewportHeight != fileViewportHeight || hyphenationEnabled != fileHyphenationEnabled || embeddedStyle != fileEmbeddedStyle) { file.close(); - Serial.printf("[%lu] [SCT] Deserialization failed: Parameters do not match\n", millis()); + LOG_ERR("SCT", "Deserialization failed: Parameters do not match"); clearCache(); return false; } @@ -104,23 +105,23 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con serialization::readPod(file, pageCount); file.close(); - Serial.printf("[%lu] [SCT] Deserialization succeeded: %d pages\n", millis(), pageCount); + LOG_DBG("SCT", "Deserialization succeeded: %d pages", pageCount); return true; } // Your updated class method (assuming you are using the 'SD' object, which is a wrapper for a specific filesystem) bool Section::clearCache() const { if (!Storage.exists(filePath.c_str())) { - Serial.printf("[%lu] [SCT] Cache does not exist, no action needed\n", millis()); + LOG_DBG("SCT", "Cache does not exist, no action needed"); return true; } if (!Storage.remove(filePath.c_str())) { - Serial.printf("[%lu] [SCT] Failed to clear cache\n", millis()); + LOG_ERR("SCT", "Failed to clear cache"); return false; } - Serial.printf("[%lu] [SCT] Cache cleared successfully\n", millis()); + LOG_DBG("SCT", "Cache cleared successfully"); return true; } @@ -142,7 +143,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c uint32_t fileSize = 0; for (int attempt = 0; attempt < 3 && !success; attempt++) { if (attempt > 0) { - Serial.printf("[%lu] [SCT] Retrying stream (attempt %d)...\n", millis(), attempt + 1); + LOG_DBG("SCT", "Retrying stream (attempt %d)...", attempt + 1); delay(50); // Brief delay before retry } @@ -162,16 +163,16 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c // If streaming failed, remove the incomplete file immediately if (!success && Storage.exists(tmpHtmlPath.c_str())) { Storage.remove(tmpHtmlPath.c_str()); - Serial.printf("[%lu] [SCT] Removed incomplete temp file after failed attempt\n", millis()); + LOG_DBG("SCT", "Removed incomplete temp file after failed attempt"); } } if (!success) { - Serial.printf("[%lu] [SCT] Failed to stream item contents to temp file after retries\n", millis()); + LOG_ERR("SCT", "Failed to stream item contents to temp file after retries"); return false; } - Serial.printf("[%lu] [SCT] Streamed temp HTML to %s (%d bytes)\n", millis(), tmpHtmlPath.c_str(), fileSize); + LOG_DBG("SCT", "Streamed temp HTML to %s (%d bytes)", tmpHtmlPath.c_str(), fileSize); if (!Storage.openFileForWrite("SCT", filePath, file)) { return false; @@ -190,7 +191,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c Storage.remove(tmpHtmlPath.c_str()); if (!success) { - Serial.printf("[%lu] [SCT] Failed to parse XML and build pages\n", millis()); + LOG_ERR("SCT", "Failed to parse XML and build pages"); file.close(); Storage.remove(filePath.c_str()); return false; @@ -208,7 +209,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c } if (hasFailedLutRecords) { - Serial.printf("[%lu] [SCT] Failed to write LUT due to invalid page positions\n", millis()); + LOG_ERR("SCT", "Failed to write LUT due to invalid page positions"); file.close(); Storage.remove(filePath.c_str()); return false; diff --git a/lib/Epub/Epub/blocks/TextBlock.cpp b/lib/Epub/Epub/blocks/TextBlock.cpp index e9ed0f7b..cda885c5 100644 --- a/lib/Epub/Epub/blocks/TextBlock.cpp +++ b/lib/Epub/Epub/blocks/TextBlock.cpp @@ -1,13 +1,14 @@ #include "TextBlock.h" #include +#include #include void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const { // Validate iterator bounds before rendering if (words.size() != wordXpos.size() || words.size() != wordStyles.size()) { - Serial.printf("[%lu] [TXB] Render skipped: size mismatch (words=%u, xpos=%u, styles=%u)\n", millis(), - (uint32_t)words.size(), (uint32_t)wordXpos.size(), (uint32_t)wordStyles.size()); + LOG_ERR("TXB", "Render skipped: size mismatch (words=%u, xpos=%u, styles=%u)\n", (uint32_t)words.size(), + (uint32_t)wordXpos.size(), (uint32_t)wordStyles.size()); return; } @@ -42,8 +43,8 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int bool TextBlock::serialize(FsFile& file) const { if (words.size() != wordXpos.size() || words.size() != wordStyles.size()) { - Serial.printf("[%lu] [TXB] Serialization failed: size mismatch (words=%u, xpos=%u, styles=%u)\n", millis(), - words.size(), wordXpos.size(), wordStyles.size()); + LOG_ERR("TXB", "Serialization failed: size mismatch (words=%u, xpos=%u, styles=%u)\n", words.size(), + wordXpos.size(), wordStyles.size()); return false; } @@ -82,7 +83,7 @@ std::unique_ptr TextBlock::deserialize(FsFile& file) { // Sanity check: prevent allocation of unreasonably large vectors (max 10000 words per block) if (wc > 10000) { - Serial.printf("[%lu] [TXB] Deserialization failed: word count %u exceeds maximum\n", millis(), wc); + LOG_ERR("TXB", "Deserialization failed: word count %u exceeds maximum", wc); return nullptr; } diff --git a/lib/Epub/Epub/css/CssParser.cpp b/lib/Epub/Epub/css/CssParser.cpp index ba187e4c..0a11e80e 100644 --- a/lib/Epub/Epub/css/CssParser.cpp +++ b/lib/Epub/Epub/css/CssParser.cpp @@ -1,6 +1,6 @@ #include "CssParser.h" -#include +#include #include #include @@ -449,7 +449,7 @@ void CssParser::processRuleBlock(const std::string& selectorGroup, const std::st bool CssParser::loadFromStream(FsFile& source) { if (!source) { - Serial.printf("[%lu] [CSS] Cannot read from invalid file\n", millis()); + LOG_ERR("CSS", "Cannot read from invalid file"); return false; } @@ -470,7 +470,7 @@ bool CssParser::loadFromStream(FsFile& source) { processRuleBlock(selector, body); } - Serial.printf("[%lu] [CSS] Parsed %zu rules\n", millis(), rulesBySelector_.size()); + LOG_DBG("CSS", "Parsed %zu rules", rulesBySelector_.size()); return true; } @@ -582,7 +582,7 @@ bool CssParser::saveToCache(FsFile& file) const { file.write(reinterpret_cast(&definedBits), sizeof(definedBits)); } - Serial.printf("[%lu] [CSS] Saved %u rules to cache\n", millis(), ruleCount); + LOG_DBG("CSS", "Saved %u rules to cache", ruleCount); return true; } @@ -597,7 +597,7 @@ bool CssParser::loadFromCache(FsFile& file) { // Read and verify version uint8_t version = 0; if (file.read(&version, 1) != 1 || version != CSS_CACHE_VERSION) { - Serial.printf("[%lu] [CSS] Cache version mismatch (got %u, expected %u)\n", millis(), version, CSS_CACHE_VERSION); + LOG_DBG("CSS", "Cache version mismatch (got %u, expected %u)", version, CSS_CACHE_VERSION); return false; } @@ -694,6 +694,6 @@ bool CssParser::loadFromCache(FsFile& file) { rulesBySelector_[selector] = style; } - Serial.printf("[%lu] [CSS] Loaded %u rules from cache\n", millis(), ruleCount); + LOG_DBG("CSS", "Loaded %u rules from cache", ruleCount); return true; } diff --git a/lib/Epub/Epub/htmlEntities.cpp b/lib/Epub/Epub/htmlEntities.cpp new file mode 100644 index 00000000..82d3819a --- /dev/null +++ b/lib/Epub/Epub/htmlEntities.cpp @@ -0,0 +1,76 @@ +// from +// https://github.com/atomic14/diy-esp32-epub-reader/blob/2c2f57fdd7e2a788d14a0bcb26b9e845a47aac42/lib/Epub/RubbishHtmlParser/htmlEntities.cpp + +#include "htmlEntities.h" + +#include + +struct EntityPair { + const char* key; + const char* value; +}; + +static const EntityPair ENTITY_LOOKUP[] = { + {""", "\""}, {"⁄", "⁄"}, {"&", "&"}, {"<", "<"}, {">", ">"}, + {"À", "À"}, {"Á", "Á"}, {"Â", "Â"}, {"Ã", "Ã"}, {"Ä", "Ä"}, + {"Å", "Å"}, {"Æ", "Æ"}, {"Ç", "Ç"}, {"È", "È"}, {"É", "É"}, + {"Ê", "Ê"}, {"Ë", "Ë"}, {"Ì", "Ì"}, {"Í", "Í"}, {"Î", "Î"}, + {"Ï", "Ï"}, {"Ð", "Ð"}, {"Ñ", "Ñ"}, {"Ò", "Ò"}, {"Ó", "Ó"}, + {"Ô", "Ô"}, {"Õ", "Õ"}, {"Ö", "Ö"}, {"Ø", "Ø"}, {"Ù", "Ù"}, + {"Ú", "Ú"}, {"Û", "Û"}, {"Ü", "Ü"}, {"Ý", "Ý"}, {"Þ", "Þ"}, + {"ß", "ß"}, {"à", "à"}, {"á", "á"}, {"â", "â"}, {"ã", "ã"}, + {"ä", "ä"}, {"å", "å"}, {"æ", "æ"}, {"ç", "ç"}, {"è", "è"}, + {"é", "é"}, {"ê", "ê"}, {"ë", "ë"}, {"ì", "ì"}, {"í", "í"}, + {"î", "î"}, {"ï", "ï"}, {"ð", "ð"}, {"ñ", "ñ"}, {"ò", "ò"}, + {"ó", "ó"}, {"ô", "ô"}, {"õ", "õ"}, {"ö", "ö"}, {"ø", "ø"}, + {"ù", "ù"}, {"ú", "ú"}, {"û", "û"}, {"ü", "ü"}, {"ý", "ý"}, + {"þ", "þ"}, {"ÿ", "ÿ"}, {" ", "\xC2\xA0"}, {"¡", "¡"}, {"¢", "¢"}, + {"£", "£"}, {"¤", "¤"}, {"¥", "¥"}, {"¦", "¦"}, {"§", "§"}, + {"¨", "¨"}, {"©", "©"}, {"ª", "ª"}, {"«", "«"}, {"¬", "¬"}, + {"­", "­"}, {"®", "®"}, {"¯", "¯"}, {"°", "°"}, {"±", "±"}, + {"²", "²"}, {"³", "³"}, {"´", "´"}, {"µ", "µ"}, {"¶", "¶"}, + {"¸", "¸"}, {"¹", "¹"}, {"º", "º"}, {"»", "»"}, {"¼", "¼"}, + {"½", "½"}, {"¾", "¾"}, {"¿", "¿"}, {"×", "×"}, {"÷", "÷"}, + {"∀", "∀"}, {"∂", "∂"}, {"∃", "∃"}, {"∅", "∅"}, {"∇", "∇"}, + {"∈", "∈"}, {"∉", "∉"}, {"∋", "∋"}, {"∏", "∏"}, {"∑", "∑"}, + {"−", "−"}, {"∗", "∗"}, {"√", "√"}, {"∝", "∝"}, {"∞", "∞"}, + {"∠", "∠"}, {"∧", "∧"}, {"∨", "∨"}, {"∩", "∩"}, {"∪", "∪"}, + {"∫", "∫"}, {"∴", "∴"}, {"∼", "∼"}, {"≅", "≅"}, {"≈", "≈"}, + {"≠", "≠"}, {"≡", "≡"}, {"≤", "≤"}, {"≥", "≥"}, {"⊂", "⊂"}, + {"⊃", "⊃"}, {"⊄", "⊄"}, {"⊆", "⊆"}, {"⊇", "⊇"}, {"⊕", "⊕"}, + {"⊗", "⊗"}, {"⊥", "⊥"}, {"⋅", "⋅"}, {"Α", "Α"}, {"Β", "Β"}, + {"Γ", "Γ"}, {"Δ", "Δ"}, {"Ε", "Ε"}, {"Ζ", "Ζ"}, {"Η", "Η"}, + {"Θ", "Θ"}, {"Ι", "Ι"}, {"Κ", "Κ"}, {"Λ", "Λ"}, {"Μ", "Μ"}, + {"Ν", "Ν"}, {"Ξ", "Ξ"}, {"Ο", "Ο"}, {"Π", "Π"}, {"Ρ", "Ρ"}, + {"Σ", "Σ"}, {"Τ", "Τ"}, {"Υ", "Υ"}, {"Φ", "Φ"}, {"Χ", "Χ"}, + {"Ψ", "Ψ"}, {"Ω", "Ω"}, {"α", "α"}, {"β", "β"}, {"γ", "γ"}, + {"δ", "δ"}, {"ε", "ε"}, {"ζ", "ζ"}, {"η", "η"}, {"θ", "θ"}, + {"ι", "ι"}, {"κ", "κ"}, {"λ", "λ"}, {"μ", "μ"}, {"ν", "ν"}, + {"ξ", "ξ"}, {"ο", "ο"}, {"π", "π"}, {"ρ", "ρ"}, {"ς", "ς"}, + {"σ", "σ"}, {"τ", "τ"}, {"υ", "υ"}, {"φ", "φ"}, {"χ", "χ"}, + {"ψ", "ψ"}, {"ω", "ω"}, {"ϑ", "ϑ"}, {"ϒ", "ϒ"}, {"ϖ", "ϖ"}, + {"Œ", "Œ"}, {"œ", "œ"}, {"Š", "Š"}, {"š", "š"}, {"Ÿ", "Ÿ"}, + {"ƒ", "ƒ"}, {"ˆ", "ˆ"}, {"˜", "˜"}, {" ", " "}, {" ", " "}, + {" ", " "}, {"‌", "‌"}, {"‍", "‍"}, {"‎", "‎"}, {"‏", "‏"}, + {"–", "–"}, {"—", "—"}, {"‘", "‘"}, {"’", "’"}, {"‚", "‚"}, + {"“", "“"}, {"”", "”"}, {"„", "„"}, {"†", "†"}, {"‡", "‡"}, + {"•", "•"}, {"…", "…"}, {"‰", "‰"}, {"′", "′"}, {"″", "″"}, + {"‹", "‹"}, {"›", "›"}, {"‾", "‾"}, {"€", "€"}, {"™", "™"}, + {"←", "←"}, {"↑", "↑"}, {"→", "→"}, {"↓", "↓"}, {"↔", "↔"}, + {"↵", "↵"}, {"⌈", "⌈"}, {"⌉", "⌉"}, {"⌊", "⌊"}, {"⌋", "⌋"}, + {"◊", "◊"}, {"♠", "♠"}, {"♣", "♣"}, {"♥", "♥"}, {"♦", "♦"}}; + +static const size_t ENTITY_LOOKUP_COUNT = sizeof(ENTITY_LOOKUP) / sizeof(ENTITY_LOOKUP[0]); + +// Lookup a single HTML entity and return its UTF-8 value +const char* lookupHtmlEntity(const char* entity, int len) { + for (size_t i = 0; i < ENTITY_LOOKUP_COUNT; i++) { + const char* key = ENTITY_LOOKUP[i].key; + const size_t keyLen = strlen(key); + if (static_cast(len) == keyLen && memcmp(entity, key, keyLen) == 0) { + return ENTITY_LOOKUP[i].value; + } + } + + return nullptr; // Entity not found +} diff --git a/lib/Epub/Epub/htmlEntities.h b/lib/Epub/Epub/htmlEntities.h new file mode 100644 index 00000000..0221195f --- /dev/null +++ b/lib/Epub/Epub/htmlEntities.h @@ -0,0 +1,9 @@ +// from +// https://github.com/atomic14/diy-esp32-epub-reader/blob/2c2f57fdd7e2a788d14a0bcb26b9e845a47aac42/lib/Epub/RubbishHtmlParser/htmlEntities.cpp + +#pragma once +#include + +// Lookup a single HTML entity (including & and ;) and return its UTF-8 value +// Returns nullptr if entity is not found +const char* lookupHtmlEntity(const char* entity, int len); diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index b94f9e8e..e5512472 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -2,10 +2,11 @@ #include #include -#include +#include #include #include "../Page.h" +#include "../htmlEntities.h" const char* HEADER_TAGS[] = {"h1", "h2", "h3", "h4", "h5", "h6"}; constexpr int NUM_HEADER_TAGS = sizeof(HEADER_TAGS) / sizeof(HEADER_TAGS[0]); @@ -168,7 +169,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* } } - Serial.printf("[%lu] [EHP] Image alt: %s\n", millis(), alt.c_str()); + LOG_DBG("EHP", "Image alt: %s", alt.c_str()); self->startNewTextBlock(centeredBlockStyle); self->italicUntilDepth = min(self->italicUntilDepth, self->depth); @@ -359,6 +360,28 @@ void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char continue; } + // Detect U+00A0 (non-breaking space): UTF-8 encoding is 0xC2 0xA0 + // Render a visible space without allowing a line break around it. + if (static_cast(s[i]) == 0xC2 && i + 1 < len && static_cast(s[i + 1]) == 0xA0) { + // Flush any pending text so style is applied correctly. + if (self->partWordBufferIndex > 0) { + self->flushPartWordBuffer(); + } + + // Add a standalone space that attaches to the previous word. + self->partWordBuffer[0] = ' '; + self->partWordBuffer[1] = '\0'; + self->partWordBufferIndex = 1; + self->nextWordContinues = true; // Attach space to previous word (no break). + self->flushPartWordBuffer(); + + // Ensure the next real word attaches to this space (no break). + self->nextWordContinues = true; + + i++; // Skip the second byte (0xA0) + continue; + } + // Skip Zero Width No-Break Space / BOM (U+FEFF) = 0xEF 0xBB 0xBF const XML_Char FEFF_BYTE_1 = static_cast(0xEF); const XML_Char FEFF_BYTE_2 = static_cast(0xBB); @@ -386,13 +409,29 @@ void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char // memory. // Spotted when reading Intermezzo, there are some really long text blocks in there. if (self->currentTextBlock->size() > 750) { - Serial.printf("[%lu] [EHP] Text block too long, splitting into multiple pages\n", millis()); + LOG_DBG("EHP", "Text block too long, splitting into multiple pages"); self->currentTextBlock->layoutAndExtractLines( self->renderer, self->fontId, self->viewportWidth, [self](const std::shared_ptr& textBlock) { self->addLineToPage(textBlock); }, false); } } +void XMLCALL ChapterHtmlSlimParser::defaultHandlerExpand(void* userData, const XML_Char* s, const int len) { + // Check if this looks like an entity reference (&...;) + if (len >= 3 && s[0] == '&' && s[len - 1] == ';') { + const char* utf8Value = lookupHtmlEntity(s, len); + if (utf8Value != nullptr) { + // Known entity: expand to its UTF-8 value + characterData(userData, utf8Value, strlen(utf8Value)); + return; + } + // Unknown entity: preserve original &...; sequence + characterData(userData, s, len); + return; + } + // Not an entity we recognize - skip it +} + void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* name) { auto* self = static_cast(userData); @@ -477,10 +516,14 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() { int done; if (!parser) { - Serial.printf("[%lu] [EHP] Couldn't allocate memory for parser\n", millis()); + LOG_ERR("EHP", "Couldn't allocate memory for parser"); return false; } + // Handle HTML entities (like  ) that aren't in XML spec or DTD + // Using DefaultHandlerExpand preserves normal entity expansion from DOCTYPE + XML_SetDefaultHandlerExpand(parser, defaultHandlerExpand); + FsFile file; if (!Storage.openFileForRead("EHP", filepath, file)) { XML_ParserFree(parser); @@ -499,7 +542,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() { do { void* const buf = XML_GetBuffer(parser, 1024); if (!buf) { - Serial.printf("[%lu] [EHP] Couldn't allocate memory for buffer\n", millis()); + LOG_ERR("EHP", "Couldn't allocate memory for buffer"); XML_StopParser(parser, XML_FALSE); // Stop any pending processing XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks XML_SetCharacterDataHandler(parser, nullptr); @@ -511,7 +554,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() { const size_t len = file.read(buf, 1024); if (len == 0 && file.available() > 0) { - Serial.printf("[%lu] [EHP] File read error\n", millis()); + LOG_ERR("EHP", "File read error"); XML_StopParser(parser, XML_FALSE); // Stop any pending processing XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks XML_SetCharacterDataHandler(parser, nullptr); @@ -523,8 +566,8 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() { done = file.available() == 0; if (XML_ParseBuffer(parser, static_cast(len), done) == XML_STATUS_ERROR) { - Serial.printf("[%lu] [EHP] Parse error at line %lu:\n%s\n", millis(), XML_GetCurrentLineNumber(parser), - XML_ErrorString(XML_GetErrorCode(parser))); + LOG_ERR("EHP", "Parse error at line %lu:\n%s", XML_GetCurrentLineNumber(parser), + XML_ErrorString(XML_GetErrorCode(parser))); XML_StopParser(parser, XML_FALSE); // Stop any pending processing XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks XML_SetCharacterDataHandler(parser, nullptr); @@ -568,7 +611,7 @@ void ChapterHtmlSlimParser::addLineToPage(std::shared_ptr line) { void ChapterHtmlSlimParser::makePages() { if (!currentTextBlock) { - Serial.printf("[%lu] [EHP] !! No text block to make pages for !!\n", millis()); + LOG_ERR("EHP", "!! No text block to make pages for !!"); return; } diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h index 909913b1..761ee1d5 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h @@ -64,6 +64,7 @@ class ChapterHtmlSlimParser { // XML callbacks static void XMLCALL startElement(void* userData, const XML_Char* name, const XML_Char** atts); static void XMLCALL characterData(void* userData, const XML_Char* s, int len); + static void XMLCALL defaultHandlerExpand(void* userData, const XML_Char* s, int len); static void XMLCALL endElement(void* userData, const XML_Char* name); public: diff --git a/lib/Epub/Epub/parsers/ContainerParser.cpp b/lib/Epub/Epub/parsers/ContainerParser.cpp index da3a7b15..aa7a92a0 100644 --- a/lib/Epub/Epub/parsers/ContainerParser.cpp +++ b/lib/Epub/Epub/parsers/ContainerParser.cpp @@ -1,11 +1,11 @@ #include "ContainerParser.h" -#include +#include bool ContainerParser::setup() { parser = XML_ParserCreate(nullptr); if (!parser) { - Serial.printf("[%lu] [CTR] Couldn't allocate memory for parser\n", millis()); + LOG_ERR("CTR", "Couldn't allocate memory for parser"); return false; } @@ -34,7 +34,7 @@ size_t ContainerParser::write(const uint8_t* buffer, const size_t size) { while (remainingInBuffer > 0) { void* const buf = XML_GetBuffer(parser, 1024); if (!buf) { - Serial.printf("[%lu] [CTR] Couldn't allocate buffer\n", millis()); + LOG_DBG("CTR", "Couldn't allocate buffer"); return 0; } @@ -42,7 +42,7 @@ size_t ContainerParser::write(const uint8_t* buffer, const size_t size) { memcpy(buf, currentBufferPos, toRead); if (XML_ParseBuffer(parser, static_cast(toRead), remainingSize == toRead) == XML_STATUS_ERROR) { - Serial.printf("[%lu] [CTR] Parse error: %s\n", millis(), XML_ErrorString(XML_GetErrorCode(parser))); + LOG_ERR("CTR", "Parse error: %s", XML_ErrorString(XML_GetErrorCode(parser))); return 0; } diff --git a/lib/Epub/Epub/parsers/ContentOpfParser.cpp b/lib/Epub/Epub/parsers/ContentOpfParser.cpp index 6ae37d14..a88c2938 100644 --- a/lib/Epub/Epub/parsers/ContentOpfParser.cpp +++ b/lib/Epub/Epub/parsers/ContentOpfParser.cpp @@ -1,7 +1,7 @@ #include "ContentOpfParser.h" #include -#include +#include #include #include "../BookMetadataCache.h" @@ -15,7 +15,7 @@ constexpr char itemCacheFile[] = "/.items.bin"; bool ContentOpfParser::setup() { parser = XML_ParserCreate(nullptr); if (!parser) { - Serial.printf("[%lu] [COF] Couldn't allocate memory for parser\n", millis()); + LOG_DBG("COF", "Couldn't allocate memory for parser"); return false; } @@ -56,7 +56,7 @@ size_t ContentOpfParser::write(const uint8_t* buffer, const size_t size) { void* const buf = XML_GetBuffer(parser, 1024); if (!buf) { - Serial.printf("[%lu] [COF] Couldn't allocate memory for buffer\n", millis()); + LOG_ERR("COF", "Couldn't allocate memory for buffer"); XML_StopParser(parser, XML_FALSE); // Stop any pending processing XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks XML_SetCharacterDataHandler(parser, nullptr); @@ -69,8 +69,8 @@ size_t ContentOpfParser::write(const uint8_t* buffer, const size_t size) { memcpy(buf, currentBufferPos, toRead); if (XML_ParseBuffer(parser, static_cast(toRead), remainingSize == toRead) == XML_STATUS_ERROR) { - Serial.printf("[%lu] [COF] Parse error at line %lu: %s\n", millis(), XML_GetCurrentLineNumber(parser), - XML_ErrorString(XML_GetErrorCode(parser))); + LOG_DBG("COF", "Parse error at line %lu: %s", XML_GetCurrentLineNumber(parser), + XML_ErrorString(XML_GetErrorCode(parser))); XML_StopParser(parser, XML_FALSE); // Stop any pending processing XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks XML_SetCharacterDataHandler(parser, nullptr); @@ -119,9 +119,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name if (self->state == IN_PACKAGE && (strcmp(name, "manifest") == 0 || strcmp(name, "opf:manifest") == 0)) { self->state = IN_MANIFEST; if (!Storage.openFileForWrite("COF", self->cachePath + itemCacheFile, self->tempItemStore)) { - Serial.printf( - "[%lu] [COF] Couldn't open temp items file for writing. This is probably going to be a fatal error.\n", - millis()); + LOG_ERR("COF", "Couldn't open temp items file for writing. This is probably going to be a fatal error."); } return; } @@ -129,9 +127,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name if (self->state == IN_PACKAGE && (strcmp(name, "spine") == 0 || strcmp(name, "opf:spine") == 0)) { self->state = IN_SPINE; if (!Storage.openFileForRead("COF", self->cachePath + itemCacheFile, self->tempItemStore)) { - Serial.printf( - "[%lu] [COF] Couldn't open temp items file for reading. This is probably going to be a fatal error.\n", - millis()); + LOG_ERR("COF", "Couldn't open temp items file for reading. This is probably going to be a fatal error."); } // Sort item index for binary search if we have enough items @@ -140,7 +136,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name return a.idHash < b.idHash || (a.idHash == b.idHash && a.idLen < b.idLen); }); self->useItemIndex = true; - Serial.printf("[%lu] [COF] Using fast index for %zu manifest items\n", millis(), self->itemIndex.size()); + LOG_DBG("COF", "Using fast index for %zu manifest items", self->itemIndex.size()); } return; } @@ -148,11 +144,9 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name if (self->state == IN_PACKAGE && (strcmp(name, "guide") == 0 || strcmp(name, "opf:guide") == 0)) { self->state = IN_GUIDE; // TODO Remove print - Serial.printf("[%lu] [COF] Entering guide state.\n", millis()); + LOG_DBG("COF", "Entering guide state."); if (!Storage.openFileForRead("COF", self->cachePath + itemCacheFile, self->tempItemStore)) { - Serial.printf( - "[%lu] [COF] Couldn't open temp items file for reading. This is probably going to be a fatal error.\n", - millis()); + LOG_ERR("COF", "Couldn't open temp items file for reading. This is probably going to be a fatal error."); } return; } @@ -214,8 +208,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name if (self->tocNcxPath.empty()) { self->tocNcxPath = href; } else { - Serial.printf("[%lu] [COF] Warning: Multiple NCX files found in manifest. Ignoring duplicate: %s\n", millis(), - href.c_str()); + LOG_DBG("COF", "Warning: Multiple NCX files found in manifest. Ignoring duplicate: %s", href.c_str()); } } @@ -229,7 +222,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name // Properties is space-separated, check if "nav" is present as a word if (properties == "nav" || properties.find("nav ") == 0 || properties.find(" nav") != std::string::npos) { self->tocNavPath = href; - Serial.printf("[%lu] [COF] Found EPUB 3 nav document: %s\n", millis(), href.c_str()); + LOG_DBG("COF", "Found EPUB 3 nav document: %s", href.c_str()); } } @@ -310,7 +303,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name if (type == "text" || type == "start") { continue; } else { - Serial.printf("[%lu] [COF] Skipping non-text reference in guide: %s\n", millis(), type.c_str()); + LOG_DBG("COF", "Skipping non-text reference in guide: %s", type.c_str()); break; } } else if (strcmp(atts[i], "href") == 0) { @@ -318,7 +311,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name } } if ((type == "text" || (type == "start" && !self->textReferenceHref.empty())) && (textHref.length() > 0)) { - Serial.printf("[%lu] [COF] Found %s reference in guide: %s.\n", millis(), type.c_str(), textHref.c_str()); + LOG_DBG("COF", "Found %s reference in guide: %s.", type.c_str(), textHref.c_str()); self->textReferenceHref = textHref; } return; diff --git a/lib/Epub/Epub/parsers/TocNavParser.cpp b/lib/Epub/Epub/parsers/TocNavParser.cpp index 454b2437..8c8afdd5 100644 --- a/lib/Epub/Epub/parsers/TocNavParser.cpp +++ b/lib/Epub/Epub/parsers/TocNavParser.cpp @@ -1,14 +1,14 @@ #include "TocNavParser.h" #include -#include +#include #include "../BookMetadataCache.h" bool TocNavParser::setup() { parser = XML_ParserCreate(nullptr); if (!parser) { - Serial.printf("[%lu] [NAV] Couldn't allocate memory for parser\n", millis()); + LOG_DBG("NAV", "Couldn't allocate memory for parser"); return false; } @@ -39,7 +39,7 @@ size_t TocNavParser::write(const uint8_t* buffer, const size_t size) { while (remainingInBuffer > 0) { void* const buf = XML_GetBuffer(parser, 1024); if (!buf) { - Serial.printf("[%lu] [NAV] Couldn't allocate memory for buffer\n", millis()); + LOG_DBG("NAV", "Couldn't allocate memory for buffer"); XML_StopParser(parser, XML_FALSE); XML_SetElementHandler(parser, nullptr, nullptr); XML_SetCharacterDataHandler(parser, nullptr); @@ -52,8 +52,8 @@ size_t TocNavParser::write(const uint8_t* buffer, const size_t size) { memcpy(buf, currentBufferPos, toRead); if (XML_ParseBuffer(parser, static_cast(toRead), remainingSize == toRead) == XML_STATUS_ERROR) { - Serial.printf("[%lu] [NAV] Parse error at line %lu: %s\n", millis(), XML_GetCurrentLineNumber(parser), - XML_ErrorString(XML_GetErrorCode(parser))); + LOG_DBG("NAV", "Parse error at line %lu: %s", XML_GetCurrentLineNumber(parser), + XML_ErrorString(XML_GetErrorCode(parser))); XML_StopParser(parser, XML_FALSE); XML_SetElementHandler(parser, nullptr, nullptr); XML_SetCharacterDataHandler(parser, nullptr); @@ -88,7 +88,7 @@ void XMLCALL TocNavParser::startElement(void* userData, const XML_Char* name, co for (int i = 0; atts[i]; i += 2) { if ((strcmp(atts[i], "epub:type") == 0 || strcmp(atts[i], "type") == 0) && strcmp(atts[i + 1], "toc") == 0) { self->state = IN_NAV_TOC; - Serial.printf("[%lu] [NAV] Found nav toc element\n", millis()); + LOG_DBG("NAV", "Found nav toc element"); return; } } @@ -179,7 +179,7 @@ void XMLCALL TocNavParser::endElement(void* userData, const XML_Char* name) { if (strcmp(name, "nav") == 0 && self->state >= IN_NAV_TOC) { self->state = IN_BODY; - Serial.printf("[%lu] [NAV] Finished parsing nav toc\n", millis()); + LOG_DBG("NAV", "Finished parsing nav toc"); return; } } diff --git a/lib/Epub/Epub/parsers/TocNcxParser.cpp b/lib/Epub/Epub/parsers/TocNcxParser.cpp index 3e59451e..84379025 100644 --- a/lib/Epub/Epub/parsers/TocNcxParser.cpp +++ b/lib/Epub/Epub/parsers/TocNcxParser.cpp @@ -1,14 +1,14 @@ #include "TocNcxParser.h" #include -#include +#include #include "../BookMetadataCache.h" bool TocNcxParser::setup() { parser = XML_ParserCreate(nullptr); if (!parser) { - Serial.printf("[%lu] [TOC] Couldn't allocate memory for parser\n", millis()); + LOG_DBG("TOC", "Couldn't allocate memory for parser"); return false; } @@ -39,7 +39,7 @@ size_t TocNcxParser::write(const uint8_t* buffer, const size_t size) { while (remainingInBuffer > 0) { void* const buf = XML_GetBuffer(parser, 1024); if (!buf) { - Serial.printf("[%lu] [TOC] Couldn't allocate memory for buffer\n", millis()); + LOG_DBG("TOC", "Couldn't allocate memory for buffer"); XML_StopParser(parser, XML_FALSE); // Stop any pending processing XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks XML_SetCharacterDataHandler(parser, nullptr); @@ -52,8 +52,8 @@ size_t TocNcxParser::write(const uint8_t* buffer, const size_t size) { memcpy(buf, currentBufferPos, toRead); if (XML_ParseBuffer(parser, static_cast(toRead), remainingSize == toRead) == XML_STATUS_ERROR) { - Serial.printf("[%lu] [TOC] Parse error at line %lu: %s\n", millis(), XML_GetCurrentLineNumber(parser), - XML_ErrorString(XML_GetErrorCode(parser))); + LOG_DBG("TOC", "Parse error at line %lu: %s", XML_GetCurrentLineNumber(parser), + XML_ErrorString(XML_GetErrorCode(parser))); XML_StopParser(parser, XML_FALSE); // Stop any pending processing XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks XML_SetCharacterDataHandler(parser, nullptr); diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 81a69f5c..b39de08c 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -1,11 +1,12 @@ #include "GfxRenderer.h" +#include #include void GfxRenderer::begin() { frameBuffer = display.getFrameBuffer(); if (!frameBuffer) { - Serial.printf("[%lu] [GFX] !! No framebuffer\n", millis()); + LOG_ERR("GFX", "!! No framebuffer"); assert(false); } } @@ -57,7 +58,7 @@ void GfxRenderer::drawPixel(const int x, const int y, const bool state) const { // Bounds checking against physical panel dimensions if (phyX < 0 || phyX >= HalDisplay::DISPLAY_WIDTH || phyY < 0 || phyY >= HalDisplay::DISPLAY_HEIGHT) { - Serial.printf("[%lu] [GFX] !! Outside range (%d, %d) -> (%d, %d)\n", millis(), x, y, phyX, phyY); + LOG_ERR("GFX", "!! Outside range (%d, %d) -> (%d, %d)", x, y, phyX, phyY); return; } @@ -84,7 +85,7 @@ void GfxRenderer::drawPixelGray(const int x, const int y, const uint8_t val2bit) int GfxRenderer::getTextWidth(const int fontId, const char* text, const EpdFontFamily::Style style) const { if (fontMap.count(fontId) == 0) { - Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId); + LOG_ERR("GFX", "Font %d not found", fontId); return 0; } @@ -110,7 +111,7 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha } if (fontMap.count(fontId) == 0) { - Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId); + LOG_ERR("GFX", "Font %d not found", fontId); return; } const auto font = fontMap.at(fontId); @@ -143,7 +144,7 @@ void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) con } } else { // TODO: Implement - Serial.printf("[%lu] [GFX] Line drawing not supported\n", millis()); + LOG_ERR("GFX", "Line drawing not supported"); } } @@ -429,8 +430,8 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con bool isScaled = false; int cropPixX = std::floor(bitmap.getWidth() * cropX / 2.0f); int cropPixY = std::floor(bitmap.getHeight() * cropY / 2.0f); - Serial.printf("[%lu] [GFX] Cropping %dx%d by %dx%d pix, is %s\n", millis(), bitmap.getWidth(), bitmap.getHeight(), - cropPixX, cropPixY, bitmap.isTopDown() ? "top-down" : "bottom-up"); + LOG_DBG("GFX", "Cropping %dx%d by %dx%d pix, is %s", bitmap.getWidth(), bitmap.getHeight(), cropPixX, cropPixY, + bitmap.isTopDown() ? "top-down" : "bottom-up"); const float effectiveWidth = (1.0f - cropX) * bitmap.getWidth(); const float effectiveHeight = (1.0f - cropY) * bitmap.getHeight(); @@ -448,7 +449,7 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con scale = static_cast(maxHeight) / effectiveHeight; isScaled = true; } - Serial.printf("[%lu] [GFX] Scaling by %f - %s\n", millis(), scale, isScaled ? "scaled" : "not scaled"); + LOG_DBG("GFX", "Scaling by %f - %s", scale, isScaled ? "scaled" : "not scaled"); // Calculate output row size (2 bits per pixel, packed into bytes) // IMPORTANT: Use int, not uint8_t, to avoid overflow for images > 1020 pixels wide @@ -457,7 +458,7 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con auto* rowBytes = static_cast(malloc(bitmap.getRowBytes())); if (!outputRow || !rowBytes) { - Serial.printf("[%lu] [GFX] !! Failed to allocate BMP row buffers\n", millis()); + LOG_ERR("GFX", "!! Failed to allocate BMP row buffers"); free(outputRow); free(rowBytes); return; @@ -481,7 +482,7 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con } if (bitmap.readNextRow(outputRow, rowBytes) != BmpReaderError::Ok) { - Serial.printf("[%lu] [GFX] Failed to read row %d from bitmap\n", millis(), bmpY); + LOG_ERR("GFX", "Failed to read row %d from bitmap", bmpY); free(outputRow); free(rowBytes); return; @@ -564,7 +565,7 @@ void GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y, auto* rowBytes = static_cast(malloc(bitmap.getRowBytes())); if (!outputRow || !rowBytes) { - Serial.printf("[%lu] [GFX] !! Failed to allocate 1-bit BMP row buffers\n", millis()); + LOG_ERR("GFX", "!! Failed to allocate 1-bit BMP row buffers"); free(outputRow); free(rowBytes); return; @@ -573,7 +574,7 @@ void GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y, for (int bmpY = 0; bmpY < bitmap.getHeight(); bmpY++) { // Read rows sequentially using readNextRow if (bitmap.readNextRow(outputRow, rowBytes) != BmpReaderError::Ok) { - Serial.printf("[%lu] [GFX] Failed to read row %d from 1-bit bitmap\n", millis(), bmpY); + LOG_ERR("GFX", "Failed to read row %d from 1-bit bitmap", bmpY); free(outputRow); free(rowBytes); return; @@ -654,7 +655,7 @@ void GfxRenderer::fillPolygon(const int* xPoints, const int* yPoints, int numPoi // Allocate node buffer for scanline algorithm auto* nodeX = static_cast(malloc(numPoints * sizeof(int))); if (!nodeX) { - Serial.printf("[%lu] [GFX] !! Failed to allocate polygon node buffer\n", millis()); + LOG_ERR("GFX", "!! Failed to allocate polygon node buffer"); return; } @@ -721,7 +722,7 @@ void GfxRenderer::invertScreen() const { void GfxRenderer::displayBuffer(const HalDisplay::RefreshMode refreshMode) const { auto elapsed = millis() - start_ms; - Serial.printf("[%lu] [GFX] Time = %lu ms from clearScreen to displayBuffer\n", millis(), elapsed); + LOG_DBG("GFX", "Time = %lu ms from clearScreen to displayBuffer", elapsed); display.displayBuffer(refreshMode, fadingFix); } @@ -775,7 +776,7 @@ int GfxRenderer::getScreenHeight() const { int GfxRenderer::getSpaceWidth(const int fontId) const { if (fontMap.count(fontId) == 0) { - Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId); + LOG_ERR("GFX", "Font %d not found", fontId); return 0; } @@ -784,7 +785,7 @@ int GfxRenderer::getSpaceWidth(const int fontId) const { int GfxRenderer::getTextAdvanceX(const int fontId, const char* text) const { if (fontMap.count(fontId) == 0) { - Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId); + LOG_ERR("GFX", "Font %d not found", fontId); return 0; } @@ -798,7 +799,7 @@ int GfxRenderer::getTextAdvanceX(const int fontId, const char* text) const { int GfxRenderer::getFontAscenderSize(const int fontId) const { if (fontMap.count(fontId) == 0) { - Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId); + LOG_ERR("GFX", "Font %d not found", fontId); return 0; } @@ -807,7 +808,7 @@ int GfxRenderer::getFontAscenderSize(const int fontId) const { int GfxRenderer::getLineHeight(const int fontId) const { if (fontMap.count(fontId) == 0) { - Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId); + LOG_ERR("GFX", "Font %d not found", fontId); return 0; } @@ -816,7 +817,7 @@ int GfxRenderer::getLineHeight(const int fontId) const { int GfxRenderer::getTextHeight(const int fontId) const { if (fontMap.count(fontId) == 0) { - Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId); + LOG_ERR("GFX", "Font %d not found", fontId); return 0; } return fontMap.at(fontId).getData(EpdFontFamily::REGULAR)->ascender; @@ -830,7 +831,7 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y } if (fontMap.count(fontId) == 0) { - Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId); + LOG_ERR("GFX", "Font %d not found", fontId); return; } const auto font = fontMap.at(fontId); @@ -913,7 +914,7 @@ void GfxRenderer::drawTextRotated90CCW(const int fontId, const int x, const int } if (fontMap.count(fontId) == 0) { - Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId); + LOG_ERR("GFX", "Font %d not found", fontId); return; } const auto font = fontMap.at(fontId); @@ -1024,8 +1025,7 @@ bool GfxRenderer::storeBwBuffer() { for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) { // Check if any chunks are already allocated if (bwBufferChunks[i]) { - Serial.printf("[%lu] [GFX] !! BW buffer chunk %zu already stored - this is likely a bug, freeing chunk\n", - millis(), i); + LOG_ERR("GFX", "!! BW buffer chunk %zu already stored - this is likely a bug, freeing chunk", i); free(bwBufferChunks[i]); bwBufferChunks[i] = nullptr; } @@ -1034,8 +1034,7 @@ bool GfxRenderer::storeBwBuffer() { bwBufferChunks[i] = static_cast(malloc(BW_BUFFER_CHUNK_SIZE)); if (!bwBufferChunks[i]) { - Serial.printf("[%lu] [GFX] !! Failed to allocate BW buffer chunk %zu (%zu bytes)\n", millis(), i, - BW_BUFFER_CHUNK_SIZE); + LOG_ERR("GFX", "!! Failed to allocate BW buffer chunk %zu (%zu bytes)", i, BW_BUFFER_CHUNK_SIZE); // Free previously allocated chunks freeBwBufferChunks(); return false; @@ -1044,8 +1043,7 @@ bool GfxRenderer::storeBwBuffer() { memcpy(bwBufferChunks[i], frameBuffer + offset, BW_BUFFER_CHUNK_SIZE); } - Serial.printf("[%lu] [GFX] Stored BW buffer in %zu chunks (%zu bytes each)\n", millis(), BW_BUFFER_NUM_CHUNKS, - BW_BUFFER_CHUNK_SIZE); + LOG_DBG("GFX", "Stored BW buffer in %zu chunks (%zu bytes each)", BW_BUFFER_NUM_CHUNKS, BW_BUFFER_CHUNK_SIZE); return true; } @@ -1072,7 +1070,7 @@ void GfxRenderer::restoreBwBuffer() { for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) { // Check if chunk is missing if (!bwBufferChunks[i]) { - Serial.printf("[%lu] [GFX] !! BW buffer chunks not stored - this is likely a bug\n", millis()); + LOG_ERR("GFX", "!! BW buffer chunks not stored - this is likely a bug"); freeBwBufferChunks(); return; } @@ -1084,7 +1082,7 @@ void GfxRenderer::restoreBwBuffer() { display.cleanupGrayscaleBuffers(frameBuffer); freeBwBufferChunks(); - Serial.printf("[%lu] [GFX] Restored and freed BW buffer chunks\n", millis()); + LOG_DBG("GFX", "Restored and freed BW buffer chunks"); } /** @@ -1106,7 +1104,7 @@ void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp, // no glyph? if (!glyph) { - Serial.printf("[%lu] [GFX] No glyph for codepoint %d\n", millis(), cp); + LOG_ERR("GFX", "No glyph for codepoint %d", cp); return; } diff --git a/lib/JpegToBmpConverter/JpegToBmpConverter.cpp b/lib/JpegToBmpConverter/JpegToBmpConverter.cpp index e2125661..5e68aa79 100644 --- a/lib/JpegToBmpConverter/JpegToBmpConverter.cpp +++ b/lib/JpegToBmpConverter/JpegToBmpConverter.cpp @@ -1,7 +1,7 @@ #include "JpegToBmpConverter.h" #include -#include +#include #include #include @@ -201,8 +201,7 @@ unsigned char JpegToBmpConverter::jpegReadCallback(unsigned char* pBuf, const un // Internal implementation with configurable target size and bit depth bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight, bool oneBit, bool crop) { - Serial.printf("[%lu] [JPG] Converting JPEG to %s BMP (target: %dx%d)\n", millis(), oneBit ? "1-bit" : "2-bit", - targetWidth, targetHeight); + LOG_DBG("JPG", "Converting JPEG to %s BMP (target: %dx%d)", oneBit ? "1-bit" : "2-bit", targetWidth, targetHeight); // Setup context for picojpeg callback JpegReadContext context = {.file = jpegFile, .bufferPos = 0, .bufferFilled = 0}; @@ -211,12 +210,12 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm pjpeg_image_info_t imageInfo; const unsigned char status = pjpeg_decode_init(&imageInfo, jpegReadCallback, &context, 0); if (status != 0) { - Serial.printf("[%lu] [JPG] JPEG decode init failed with error code: %d\n", millis(), status); + LOG_ERR("JPG", "JPEG decode init failed with error code: %d", status); return false; } - Serial.printf("[%lu] [JPG] JPEG dimensions: %dx%d, components: %d, MCUs: %dx%d\n", millis(), imageInfo.m_width, - imageInfo.m_height, imageInfo.m_comps, imageInfo.m_MCUSPerRow, imageInfo.m_MCUSPerCol); + LOG_DBG("JPG", "JPEG dimensions: %dx%d, components: %d, MCUs: %dx%d", imageInfo.m_width, imageInfo.m_height, + imageInfo.m_comps, imageInfo.m_MCUSPerRow, imageInfo.m_MCUSPerCol); // Safety limits to prevent memory issues on ESP32 constexpr int MAX_IMAGE_WIDTH = 2048; @@ -224,8 +223,8 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm constexpr int MAX_MCU_ROW_BYTES = 65536; if (imageInfo.m_width > MAX_IMAGE_WIDTH || imageInfo.m_height > MAX_IMAGE_HEIGHT) { - Serial.printf("[%lu] [JPG] Image too large (%dx%d), max supported: %dx%d\n", millis(), imageInfo.m_width, - imageInfo.m_height, MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT); + LOG_DBG("JPG", "Image too large (%dx%d), max supported: %dx%d", imageInfo.m_width, imageInfo.m_height, + MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT); return false; } @@ -262,8 +261,8 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm scaleY_fp = (static_cast(imageInfo.m_height) << 16) / outHeight; needsScaling = true; - Serial.printf("[%lu] [JPG] Pre-scaling %dx%d -> %dx%d (fit to %dx%d)\n", millis(), imageInfo.m_width, - imageInfo.m_height, outWidth, outHeight, targetWidth, targetHeight); + LOG_DBG("JPG", "Pre-scaling %dx%d -> %dx%d (fit to %dx%d)", imageInfo.m_width, imageInfo.m_height, outWidth, + outHeight, targetWidth, targetHeight); } // Write BMP header with output dimensions @@ -282,7 +281,7 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm // Allocate row buffer auto* rowBuffer = static_cast(malloc(bytesPerRow)); if (!rowBuffer) { - Serial.printf("[%lu] [JPG] Failed to allocate row buffer\n", millis()); + LOG_ERR("JPG", "Failed to allocate row buffer"); return false; } @@ -293,15 +292,14 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm // Validate MCU row buffer size before allocation if (mcuRowPixels > MAX_MCU_ROW_BYTES) { - Serial.printf("[%lu] [JPG] MCU row buffer too large (%d bytes), max: %d\n", millis(), mcuRowPixels, - MAX_MCU_ROW_BYTES); + LOG_DBG("JPG", "MCU row buffer too large (%d bytes), max: %d", mcuRowPixels, MAX_MCU_ROW_BYTES); free(rowBuffer); return false; } auto* mcuRowBuffer = static_cast(malloc(mcuRowPixels)); if (!mcuRowBuffer) { - Serial.printf("[%lu] [JPG] Failed to allocate MCU row buffer (%d bytes)\n", millis(), mcuRowPixels); + LOG_ERR("JPG", "Failed to allocate MCU row buffer (%d bytes)", mcuRowPixels); free(rowBuffer); return false; } @@ -349,10 +347,9 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm const unsigned char mcuStatus = pjpeg_decode_mcu(); if (mcuStatus != 0) { if (mcuStatus == PJPG_NO_MORE_BLOCKS) { - Serial.printf("[%lu] [JPG] Unexpected end of blocks at MCU (%d, %d)\n", millis(), mcuX, mcuY); + LOG_ERR("JPG", "Unexpected end of blocks at MCU (%d, %d)", mcuX, mcuY); } else { - Serial.printf("[%lu] [JPG] JPEG decode MCU failed at (%d, %d) with error code: %d\n", millis(), mcuX, mcuY, - mcuStatus); + LOG_ERR("JPG", "JPEG decode MCU failed at (%d, %d) with error code: %d", mcuX, mcuY, mcuStatus); } free(mcuRowBuffer); free(rowBuffer); @@ -549,7 +546,7 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm free(mcuRowBuffer); free(rowBuffer); - Serial.printf("[%lu] [JPG] Successfully converted JPEG to BMP\n", millis()); + LOG_DBG("JPG", "Successfully converted JPEG to BMP"); return true; } diff --git a/lib/KOReaderSync/KOReaderCredentialStore.cpp b/lib/KOReaderSync/KOReaderCredentialStore.cpp index 01cb6c6b..d59afbce 100644 --- a/lib/KOReaderSync/KOReaderCredentialStore.cpp +++ b/lib/KOReaderSync/KOReaderCredentialStore.cpp @@ -1,7 +1,7 @@ #include "KOReaderCredentialStore.h" #include -#include +#include #include #include @@ -44,7 +44,7 @@ bool KOReaderCredentialStore::saveToFile() const { // Write username (plaintext - not particularly sensitive) serialization::writeString(file, username); - Serial.printf("[%lu] [KRS] Saving username: %s\n", millis(), username.c_str()); + LOG_DBG("KRS", "Saving username: %s", username.c_str()); // Write password (obfuscated) std::string obfuscatedPwd = password; @@ -58,14 +58,14 @@ bool KOReaderCredentialStore::saveToFile() const { serialization::writePod(file, static_cast(matchMethod)); file.close(); - Serial.printf("[%lu] [KRS] Saved KOReader credentials to file\n", millis()); + LOG_DBG("KRS", "Saved KOReader credentials to file"); return true; } bool KOReaderCredentialStore::loadFromFile() { FsFile file; if (!Storage.openFileForRead("KRS", KOREADER_FILE, file)) { - Serial.printf("[%lu] [KRS] No credentials file found\n", millis()); + LOG_DBG("KRS", "No credentials file found"); return false; } @@ -73,7 +73,7 @@ bool KOReaderCredentialStore::loadFromFile() { uint8_t version; serialization::readPod(file, version); if (version != KOREADER_FILE_VERSION) { - Serial.printf("[%lu] [KRS] Unknown file version: %u\n", millis(), version); + LOG_DBG("KRS", "Unknown file version: %u", version); file.close(); return false; } @@ -110,14 +110,14 @@ bool KOReaderCredentialStore::loadFromFile() { } file.close(); - Serial.printf("[%lu] [KRS] Loaded KOReader credentials for user: %s\n", millis(), username.c_str()); + LOG_DBG("KRS", "Loaded KOReader credentials for user: %s", username.c_str()); return true; } void KOReaderCredentialStore::setCredentials(const std::string& user, const std::string& pass) { username = user; password = pass; - Serial.printf("[%lu] [KRS] Set credentials for user: %s\n", millis(), user.c_str()); + LOG_DBG("KRS", "Set credentials for user: %s", user.c_str()); } std::string KOReaderCredentialStore::getMd5Password() const { @@ -140,12 +140,12 @@ void KOReaderCredentialStore::clearCredentials() { username.clear(); password.clear(); saveToFile(); - Serial.printf("[%lu] [KRS] Cleared KOReader credentials\n", millis()); + LOG_DBG("KRS", "Cleared KOReader credentials"); } void KOReaderCredentialStore::setServerUrl(const std::string& url) { serverUrl = url; - Serial.printf("[%lu] [KRS] Set server URL: %s\n", millis(), url.empty() ? "(default)" : url.c_str()); + LOG_DBG("KRS", "Set server URL: %s", url.empty() ? "(default)" : url.c_str()); } std::string KOReaderCredentialStore::getBaseUrl() const { @@ -163,6 +163,5 @@ std::string KOReaderCredentialStore::getBaseUrl() const { void KOReaderCredentialStore::setMatchMethod(DocumentMatchMethod method) { matchMethod = method; - Serial.printf("[%lu] [KRS] Set match method: %s\n", millis(), - method == DocumentMatchMethod::FILENAME ? "Filename" : "Binary"); + LOG_DBG("KRS", "Set match method: %s", method == DocumentMatchMethod::FILENAME ? "Filename" : "Binary"); } diff --git a/lib/KOReaderSync/KOReaderDocumentId.cpp b/lib/KOReaderSync/KOReaderDocumentId.cpp index 4cc259ce..efb18d1b 100644 --- a/lib/KOReaderSync/KOReaderDocumentId.cpp +++ b/lib/KOReaderSync/KOReaderDocumentId.cpp @@ -1,7 +1,7 @@ #include "KOReaderDocumentId.h" #include -#include +#include #include namespace { @@ -27,7 +27,7 @@ std::string KOReaderDocumentId::calculateFromFilename(const std::string& filePat md5.calculate(); std::string result = md5.toString().c_str(); - Serial.printf("[%lu] [KODoc] Filename hash: %s (from '%s')\n", millis(), result.c_str(), filename.c_str()); + LOG_DBG("KODoc", "Filename hash: %s (from '%s')", result.c_str(), filename.c_str()); return result; } @@ -44,12 +44,12 @@ size_t KOReaderDocumentId::getOffset(int i) { std::string KOReaderDocumentId::calculate(const std::string& filePath) { FsFile file; if (!Storage.openFileForRead("KODoc", filePath, file)) { - Serial.printf("[%lu] [KODoc] Failed to open file: %s\n", millis(), filePath.c_str()); + LOG_DBG("KODoc", "Failed to open file: %s", filePath.c_str()); return ""; } const size_t fileSize = file.fileSize(); - Serial.printf("[%lu] [KODoc] Calculating hash for file: %s (size: %zu)\n", millis(), filePath.c_str(), fileSize); + LOG_DBG("KODoc", "Calculating hash for file: %s (size: %zu)", filePath.c_str(), fileSize); // Initialize MD5 builder MD5Builder md5; @@ -70,7 +70,7 @@ std::string KOReaderDocumentId::calculate(const std::string& filePath) { // Seek to offset if (!file.seekSet(offset)) { - Serial.printf("[%lu] [KODoc] Failed to seek to offset %zu\n", millis(), offset); + LOG_DBG("KODoc", "Failed to seek to offset %zu", offset); continue; } @@ -90,7 +90,7 @@ std::string KOReaderDocumentId::calculate(const std::string& filePath) { md5.calculate(); std::string result = md5.toString().c_str(); - Serial.printf("[%lu] [KODoc] Hash calculated: %s (from %zu bytes)\n", millis(), result.c_str(), totalBytesRead); + LOG_DBG("KODoc", "Hash calculated: %s (from %zu bytes)", result.c_str(), totalBytesRead); return result; } diff --git a/lib/KOReaderSync/KOReaderSyncClient.cpp b/lib/KOReaderSync/KOReaderSyncClient.cpp index f0183452..66f7bee1 100644 --- a/lib/KOReaderSync/KOReaderSyncClient.cpp +++ b/lib/KOReaderSync/KOReaderSyncClient.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -30,12 +30,12 @@ bool isHttpsUrl(const std::string& url) { return url.rfind("https://", 0) == 0; KOReaderSyncClient::Error KOReaderSyncClient::authenticate() { if (!KOREADER_STORE.hasCredentials()) { - Serial.printf("[%lu] [KOSync] No credentials configured\n", millis()); + LOG_DBG("KOSync", "No credentials configured"); return NO_CREDENTIALS; } std::string url = KOREADER_STORE.getBaseUrl() + "/users/auth"; - Serial.printf("[%lu] [KOSync] Authenticating: %s\n", millis(), url.c_str()); + LOG_DBG("KOSync", "Authenticating: %s", url.c_str()); HTTPClient http; std::unique_ptr secureClient; @@ -53,7 +53,7 @@ KOReaderSyncClient::Error KOReaderSyncClient::authenticate() { const int httpCode = http.GET(); http.end(); - Serial.printf("[%lu] [KOSync] Auth response: %d\n", millis(), httpCode); + LOG_DBG("KOSync", "Auth response: %d", httpCode); if (httpCode == 200) { return OK; @@ -68,12 +68,12 @@ KOReaderSyncClient::Error KOReaderSyncClient::authenticate() { KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& documentHash, KOReaderProgress& outProgress) { if (!KOREADER_STORE.hasCredentials()) { - Serial.printf("[%lu] [KOSync] No credentials configured\n", millis()); + LOG_DBG("KOSync", "No credentials configured"); return NO_CREDENTIALS; } std::string url = KOREADER_STORE.getBaseUrl() + "/syncs/progress/" + documentHash; - Serial.printf("[%lu] [KOSync] Getting progress: %s\n", millis(), url.c_str()); + LOG_DBG("KOSync", "Getting progress: %s", url.c_str()); HTTPClient http; std::unique_ptr secureClient; @@ -99,7 +99,7 @@ KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& doc const DeserializationError error = deserializeJson(doc, responseBody); if (error) { - Serial.printf("[%lu] [KOSync] JSON parse failed: %s\n", millis(), error.c_str()); + LOG_ERR("KOSync", "JSON parse failed: %s", error.c_str()); return JSON_ERROR; } @@ -110,14 +110,13 @@ KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& doc outProgress.deviceId = doc["device_id"].as(); outProgress.timestamp = doc["timestamp"].as(); - Serial.printf("[%lu] [KOSync] Got progress: %.2f%% at %s\n", millis(), outProgress.percentage * 100, - outProgress.progress.c_str()); + LOG_DBG("KOSync", "Got progress: %.2f%% at %s", outProgress.percentage * 100, outProgress.progress.c_str()); return OK; } http.end(); - Serial.printf("[%lu] [KOSync] Get progress response: %d\n", millis(), httpCode); + LOG_DBG("KOSync", "Get progress response: %d", httpCode); if (httpCode == 401) { return AUTH_FAILED; @@ -131,12 +130,12 @@ KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& doc KOReaderSyncClient::Error KOReaderSyncClient::updateProgress(const KOReaderProgress& progress) { if (!KOREADER_STORE.hasCredentials()) { - Serial.printf("[%lu] [KOSync] No credentials configured\n", millis()); + LOG_DBG("KOSync", "No credentials configured"); return NO_CREDENTIALS; } std::string url = KOREADER_STORE.getBaseUrl() + "/syncs/progress"; - Serial.printf("[%lu] [KOSync] Updating progress: %s\n", millis(), url.c_str()); + LOG_DBG("KOSync", "Updating progress: %s", url.c_str()); HTTPClient http; std::unique_ptr secureClient; @@ -163,12 +162,12 @@ KOReaderSyncClient::Error KOReaderSyncClient::updateProgress(const KOReaderProgr std::string body; serializeJson(doc, body); - Serial.printf("[%lu] [KOSync] Request body: %s\n", millis(), body.c_str()); + LOG_DBG("KOSync", "Request body: %s", body.c_str()); const int httpCode = http.PUT(body.c_str()); http.end(); - Serial.printf("[%lu] [KOSync] Update progress response: %d\n", millis(), httpCode); + LOG_DBG("KOSync", "Update progress response: %d", httpCode); if (httpCode == 200 || httpCode == 202) { return OK; diff --git a/lib/KOReaderSync/ProgressMapper.cpp b/lib/KOReaderSync/ProgressMapper.cpp index 2c15ab71..bc0adf56 100644 --- a/lib/KOReaderSync/ProgressMapper.cpp +++ b/lib/KOReaderSync/ProgressMapper.cpp @@ -1,6 +1,6 @@ #include "ProgressMapper.h" -#include +#include #include @@ -23,8 +23,8 @@ KOReaderPosition ProgressMapper::toKOReader(const std::shared_ptr& epub, c const int tocIndex = epub->getTocIndexForSpineIndex(pos.spineIndex); const std::string chapterName = (tocIndex >= 0) ? epub->getTocItem(tocIndex).title : "unknown"; - Serial.printf("[%lu] [ProgressMapper] CrossPoint -> KOReader: chapter='%s', page=%d/%d -> %.2f%% at %s\n", millis(), - chapterName.c_str(), pos.pageNumber, pos.totalPages, result.percentage * 100, result.xpath.c_str()); + LOG_DBG("ProgressMapper", "CrossPoint -> KOReader: chapter='%s', page=%d/%d -> %.2f%% at %s", chapterName.c_str(), + pos.pageNumber, pos.totalPages, result.percentage * 100, result.xpath.c_str()); return result; } @@ -76,8 +76,8 @@ CrossPointPosition ProgressMapper::toCrossPoint(const std::shared_ptr& epu } } - Serial.printf("[%lu] [ProgressMapper] KOReader -> CrossPoint: %.2f%% at %s -> spine=%d, page=%d\n", millis(), - koPos.percentage * 100, koPos.xpath.c_str(), result.spineIndex, result.pageNumber); + LOG_DBG("ProgressMapper", "KOReader -> CrossPoint: %.2f%% at %s -> spine=%d, page=%d", koPos.percentage * 100, + koPos.xpath.c_str(), result.spineIndex, result.pageNumber); return result; } diff --git a/lib/Logging/Logging.cpp b/lib/Logging/Logging.cpp new file mode 100644 index 00000000..7d72c920 --- /dev/null +++ b/lib/Logging/Logging.cpp @@ -0,0 +1,47 @@ +#include "Logging.h" + +// Since logging can take a large amount of flash, we want to make the format string as short as possible. +// This logPrintf prepend the timestamp, level and origin to the user-provided message, so that the user only needs to +// provide the format string for the message itself. +void logPrintf(const char* level, const char* origin, const char* format, ...) { + if (!logSerial) { + return; // Serial not initialized, skip logging + } + va_list args; + va_start(args, format); + char buf[256]; + char* c = buf; + // add the timestamp + { + unsigned long ms = millis(); + int len = snprintf(c, sizeof(buf), "[%lu] ", ms); + if (len < 0) { + return; // encoding error, skip logging + } + c += len; + } + // add the level + { + const char* p = level; + size_t remaining = sizeof(buf) - (c - buf); + while (*p && remaining > 1) { + *c++ = *p++; + remaining--; + } + if (remaining > 1) { + *c++ = ' '; + } + } + // add the origin + { + int len = snprintf(c, sizeof(buf) - (c - buf), "[%s] ", origin); + if (len < 0) { + return; // encoding error, skip logging + } + c += len; + } + // add the user message + vsnprintf(c, sizeof(buf) - (c - buf), format, args); + va_end(args); + logSerial.print(buf); +} diff --git a/lib/Logging/Logging.h b/lib/Logging/Logging.h new file mode 100644 index 00000000..c8fce031 --- /dev/null +++ b/lib/Logging/Logging.h @@ -0,0 +1,71 @@ +#pragma once + +#include + +/* +Define ENABLE_SERIAL_LOG to enable logging +Can be set in platformio.ini build_flags or as a compile definition + +Define LOG_LEVEL to control log verbosity: +0 = ERR only +1 = ERR + INF +2 = ERR + INF + DBG +If not defined, defaults to 0 + +If you have a legitimate need for raw Serial access (e.g., binary data, +special formatting), use the underlying logSerial object directly: + logSerial.printf("Special case: %d\n", value); + logSerial.write(binaryData, length); + +The logSerial reference (defined below) points to the real Serial object and +won't trigger deprecation warnings. +*/ + +#ifndef LOG_LEVEL +#define LOG_LEVEL 0 +#endif + +static HWCDC& logSerial = Serial; + +void logPrintf(const char* level, const char* origin, const char* format, ...); + +#ifdef ENABLE_SERIAL_LOG +#if LOG_LEVEL >= 0 +#define LOG_ERR(origin, format, ...) logPrintf("[ERR]", origin, format "\n", ##__VA_ARGS__) +#else +#define LOG_ERR(origin, format, ...) +#endif + +#if LOG_LEVEL >= 1 +#define LOG_INF(origin, format, ...) logPrintf("[INF]", origin, format "\n", ##__VA_ARGS__) +#else +#define LOG_INF(origin, format, ...) +#endif + +#if LOG_LEVEL >= 2 +#define LOG_DBG(origin, format, ...) logPrintf("[DBG]", origin, format "\n", ##__VA_ARGS__) +#else +#define LOG_DBG(origin, format, ...) +#endif +#else +#define LOG_DBG(origin, format, ...) +#define LOG_ERR(origin, format, ...) +#define LOG_INF(origin, format, ...) +#endif + +class MySerialImpl : public Print { + public: + void begin(unsigned long baud) { logSerial.begin(baud); } + + // Support boolean conversion for compatibility with code like: + // if (Serial) or while (!Serial) + operator bool() const { return logSerial; } + + __attribute__((deprecated("Use LOG_* macro instead"))) size_t printf(const char* format, ...); + size_t write(uint8_t b) override; + size_t write(const uint8_t* buffer, size_t size) override; + void flush() override; + static MySerialImpl instance; +}; + +#define Serial MySerialImpl::instance \ No newline at end of file diff --git a/lib/OpdsParser/OpdsParser.cpp b/lib/OpdsParser/OpdsParser.cpp index 4b58d8f8..f4ce6960 100644 --- a/lib/OpdsParser/OpdsParser.cpp +++ b/lib/OpdsParser/OpdsParser.cpp @@ -1,6 +1,6 @@ #include "OpdsParser.h" -#include +#include #include @@ -8,7 +8,7 @@ OpdsParser::OpdsParser() { parser = XML_ParserCreate(nullptr); if (!parser) { errorOccured = true; - Serial.printf("[%lu] [OPDS] Couldn't allocate memory for parser\n", millis()); + LOG_DBG("OPDS", "Couldn't allocate memory for parser"); } } @@ -42,7 +42,7 @@ size_t OpdsParser::write(const uint8_t* xmlData, const size_t length) { void* const buf = XML_GetBuffer(parser, chunkSize); if (!buf) { errorOccured = true; - Serial.printf("[%lu] [OPDS] Couldn't allocate memory for buffer\n", millis()); + LOG_DBG("OPDS", "Couldn't allocate memory for buffer"); XML_ParserFree(parser); parser = nullptr; return length; @@ -53,8 +53,8 @@ size_t OpdsParser::write(const uint8_t* xmlData, const size_t length) { if (XML_ParseBuffer(parser, static_cast(toRead), 0) == XML_STATUS_ERROR) { errorOccured = true; - Serial.printf("[%lu] [OPDS] Parse error at line %lu: %s\n", millis(), XML_GetCurrentLineNumber(parser), - XML_ErrorString(XML_GetErrorCode(parser))); + LOG_DBG("OPDS", "Parse error at line %lu: %s", XML_GetCurrentLineNumber(parser), + XML_ErrorString(XML_GetErrorCode(parser))); XML_ParserFree(parser); parser = nullptr; return length; diff --git a/lib/Txt/Txt.cpp b/lib/Txt/Txt.cpp index 28bb9395..bb20a2bc 100644 --- a/lib/Txt/Txt.cpp +++ b/lib/Txt/Txt.cpp @@ -2,6 +2,7 @@ #include #include +#include Txt::Txt(std::string path, std::string cacheBasePath) : filepath(std::move(path)), cacheBasePath(std::move(cacheBasePath)) { @@ -16,13 +17,13 @@ bool Txt::load() { } if (!Storage.exists(filepath.c_str())) { - Serial.printf("[%lu] [TXT] File does not exist: %s\n", millis(), filepath.c_str()); + LOG_ERR("TXT", "File does not exist: %s", filepath.c_str()); return false; } FsFile file; if (!Storage.openFileForRead("TXT", filepath, file)) { - Serial.printf("[%lu] [TXT] Failed to open file: %s\n", millis(), filepath.c_str()); + LOG_ERR("TXT", "Failed to open file: %s", filepath.c_str()); return false; } @@ -30,7 +31,7 @@ bool Txt::load() { file.close(); loaded = true; - Serial.printf("[%lu] [TXT] Loaded TXT file: %s (%zu bytes)\n", millis(), filepath.c_str(), fileSize); + LOG_DBG("TXT", "Loaded TXT file: %s (%zu bytes)", filepath.c_str(), fileSize); return true; } @@ -74,7 +75,7 @@ std::string Txt::findCoverImage() const { for (const auto& ext : extensions) { std::string coverPath = folder + "/" + baseName + ext; if (Storage.exists(coverPath.c_str())) { - Serial.printf("[%lu] [TXT] Found matching cover image: %s\n", millis(), coverPath.c_str()); + LOG_DBG("TXT", "Found matching cover image: %s", coverPath.c_str()); return coverPath; } } @@ -85,7 +86,7 @@ std::string Txt::findCoverImage() const { for (const auto& ext : extensions) { std::string coverPath = folder + "/" + std::string(name) + ext; if (Storage.exists(coverPath.c_str())) { - Serial.printf("[%lu] [TXT] Found fallback cover image: %s\n", millis(), coverPath.c_str()); + LOG_DBG("TXT", "Found fallback cover image: %s", coverPath.c_str()); return coverPath; } } @@ -104,7 +105,7 @@ bool Txt::generateCoverBmp() const { std::string coverImagePath = findCoverImage(); if (coverImagePath.empty()) { - Serial.printf("[%lu] [TXT] No cover image found for TXT file\n", millis()); + LOG_DBG("TXT", "No cover image found for TXT file"); return false; } @@ -120,7 +121,7 @@ bool Txt::generateCoverBmp() const { if (isBmp) { // Copy BMP file to cache - Serial.printf("[%lu] [TXT] Copying BMP cover image to cache\n", millis()); + LOG_DBG("TXT", "Copying BMP cover image to cache"); FsFile src, dst; if (!Storage.openFileForRead("TXT", coverImagePath, src)) { return false; @@ -136,13 +137,13 @@ bool Txt::generateCoverBmp() const { } src.close(); dst.close(); - Serial.printf("[%lu] [TXT] Copied BMP cover to cache\n", millis()); + LOG_DBG("TXT", "Copied BMP cover to cache"); return true; } if (isJpg) { // Convert JPG/JPEG to BMP (same approach as Epub) - Serial.printf("[%lu] [TXT] Generating BMP from JPG cover image\n", millis()); + LOG_DBG("TXT", "Generating BMP from JPG cover image"); FsFile coverJpg, coverBmp; if (!Storage.openFileForRead("TXT", coverImagePath, coverJpg)) { return false; @@ -156,16 +157,16 @@ bool Txt::generateCoverBmp() const { coverBmp.close(); if (!success) { - Serial.printf("[%lu] [TXT] Failed to generate BMP from JPG cover image\n", millis()); + LOG_ERR("TXT", "Failed to generate BMP from JPG cover image"); Storage.remove(getCoverBmpPath().c_str()); } else { - Serial.printf("[%lu] [TXT] Generated BMP from JPG cover image\n", millis()); + LOG_DBG("TXT", "Generated BMP from JPG cover image"); } return success; } // PNG files are not supported (would need a PNG decoder) - Serial.printf("[%lu] [TXT] Cover image format not supported (only BMP/JPG/JPEG)\n", millis()); + LOG_ERR("TXT", "Cover image format not supported (only BMP/JPG/JPEG)"); return false; } diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index e01e4f0a..53d32cac 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -8,10 +8,10 @@ #include "Xtc.h" #include -#include +#include bool Xtc::load() { - Serial.printf("[%lu] [XTC] Loading XTC: %s\n", millis(), filepath.c_str()); + LOG_DBG("XTC", "Loading XTC: %s", filepath.c_str()); // Initialize parser parser.reset(new xtc::XtcParser()); @@ -19,28 +19,28 @@ bool Xtc::load() { // Open XTC file xtc::XtcError err = parser->open(filepath.c_str()); if (err != xtc::XtcError::OK) { - Serial.printf("[%lu] [XTC] Failed to load: %s\n", millis(), xtc::errorToString(err)); + LOG_ERR("XTC", "Failed to load: %s", xtc::errorToString(err)); parser.reset(); return false; } loaded = true; - Serial.printf("[%lu] [XTC] Loaded XTC: %s (%lu pages)\n", millis(), filepath.c_str(), parser->getPageCount()); + LOG_DBG("XTC", "Loaded XTC: %s (%lu pages)", filepath.c_str(), parser->getPageCount()); return true; } bool Xtc::clearCache() const { if (!Storage.exists(cachePath.c_str())) { - Serial.printf("[%lu] [XTC] Cache does not exist, no action needed\n", millis()); + LOG_DBG("XTC", "Cache does not exist, no action needed"); return true; } if (!Storage.removeDir(cachePath.c_str())) { - Serial.printf("[%lu] [XTC] Failed to clear cache\n", millis()); + LOG_ERR("XTC", "Failed to clear cache"); return false; } - Serial.printf("[%lu] [XTC] Cache cleared successfully\n", millis()); + LOG_DBG("XTC", "Cache cleared successfully"); return true; } @@ -119,12 +119,12 @@ bool Xtc::generateCoverBmp() const { } if (!loaded || !parser) { - Serial.printf("[%lu] [XTC] Cannot generate cover BMP, file not loaded\n", millis()); + LOG_ERR("XTC", "Cannot generate cover BMP, file not loaded"); return false; } if (parser->getPageCount() == 0) { - Serial.printf("[%lu] [XTC] No pages in XTC file\n", millis()); + LOG_ERR("XTC", "No pages in XTC file"); return false; } @@ -134,7 +134,7 @@ bool Xtc::generateCoverBmp() const { // Get first page info for cover xtc::PageInfo pageInfo; if (!parser->getPageInfo(0, pageInfo)) { - Serial.printf("[%lu] [XTC] Failed to get first page info\n", millis()); + LOG_DBG("XTC", "Failed to get first page info"); return false; } @@ -152,14 +152,14 @@ bool Xtc::generateCoverBmp() const { } uint8_t* pageBuffer = static_cast(malloc(bitmapSize)); if (!pageBuffer) { - Serial.printf("[%lu] [XTC] Failed to allocate page buffer (%lu bytes)\n", millis(), bitmapSize); + LOG_ERR("XTC", "Failed to allocate page buffer (%lu bytes)", bitmapSize); return false; } // Load first page (cover) size_t bytesRead = const_cast(parser.get())->loadPage(0, pageBuffer, bitmapSize); if (bytesRead == 0) { - Serial.printf("[%lu] [XTC] Failed to load cover page\n", millis()); + LOG_ERR("XTC", "Failed to load cover page"); free(pageBuffer); return false; } @@ -167,7 +167,7 @@ bool Xtc::generateCoverBmp() const { // Create BMP file FsFile coverBmp; if (!Storage.openFileForWrite("XTC", getCoverBmpPath(), coverBmp)) { - Serial.printf("[%lu] [XTC] Failed to create cover BMP file\n", millis()); + LOG_DBG("XTC", "Failed to create cover BMP file"); free(pageBuffer); return false; } @@ -297,7 +297,7 @@ bool Xtc::generateCoverBmp() const { coverBmp.close(); free(pageBuffer); - Serial.printf("[%lu] [XTC] Generated cover BMP: %s\n", millis(), getCoverBmpPath().c_str()); + LOG_DBG("XTC", "Generated cover BMP: %s", getCoverBmpPath().c_str()); return true; } @@ -311,12 +311,12 @@ bool Xtc::generateThumbBmp(int height) const { } if (!loaded || !parser) { - Serial.printf("[%lu] [XTC] Cannot generate thumb BMP, file not loaded\n", millis()); + LOG_ERR("XTC", "Cannot generate thumb BMP, file not loaded"); return false; } if (parser->getPageCount() == 0) { - Serial.printf("[%lu] [XTC] No pages in XTC file\n", millis()); + LOG_ERR("XTC", "No pages in XTC file"); return false; } @@ -326,7 +326,7 @@ bool Xtc::generateThumbBmp(int height) const { // Get first page info for cover xtc::PageInfo pageInfo; if (!parser->getPageInfo(0, pageInfo)) { - Serial.printf("[%lu] [XTC] Failed to get first page info\n", millis()); + LOG_DBG("XTC", "Failed to get first page info"); return false; } @@ -359,7 +359,7 @@ bool Xtc::generateThumbBmp(int height) const { } src.close(); } - Serial.printf("[%lu] [XTC] Copied cover to thumb (no scaling needed)\n", millis()); + LOG_DBG("XTC", "Copied cover to thumb (no scaling needed)"); return Storage.exists(getThumbBmpPath(height).c_str()); } return false; @@ -368,8 +368,8 @@ bool Xtc::generateThumbBmp(int height) const { uint16_t thumbWidth = static_cast(pageInfo.width * scale); uint16_t thumbHeight = static_cast(pageInfo.height * scale); - Serial.printf("[%lu] [XTC] Generating thumb BMP: %dx%d -> %dx%d (scale: %.3f)\n", millis(), pageInfo.width, - pageInfo.height, thumbWidth, thumbHeight, scale); + LOG_DBG("XTC", "Generating thumb BMP: %dx%d -> %dx%d (scale: %.3f)", pageInfo.width, pageInfo.height, thumbWidth, + thumbHeight, scale); // Allocate buffer for page data size_t bitmapSize; @@ -380,14 +380,14 @@ bool Xtc::generateThumbBmp(int height) const { } uint8_t* pageBuffer = static_cast(malloc(bitmapSize)); if (!pageBuffer) { - Serial.printf("[%lu] [XTC] Failed to allocate page buffer (%lu bytes)\n", millis(), bitmapSize); + LOG_ERR("XTC", "Failed to allocate page buffer (%lu bytes)", bitmapSize); return false; } // Load first page (cover) size_t bytesRead = const_cast(parser.get())->loadPage(0, pageBuffer, bitmapSize); if (bytesRead == 0) { - Serial.printf("[%lu] [XTC] Failed to load cover page for thumb\n", millis()); + LOG_ERR("XTC", "Failed to load cover page for thumb"); free(pageBuffer); return false; } @@ -395,7 +395,7 @@ bool Xtc::generateThumbBmp(int height) const { // Create thumbnail BMP file - use 1-bit format for fast home screen rendering (no gray passes) FsFile thumbBmp; if (!Storage.openFileForWrite("XTC", getThumbBmpPath(height), thumbBmp)) { - Serial.printf("[%lu] [XTC] Failed to create thumb BMP file\n", millis()); + LOG_DBG("XTC", "Failed to create thumb BMP file"); free(pageBuffer); return false; } @@ -558,8 +558,7 @@ bool Xtc::generateThumbBmp(int height) const { thumbBmp.close(); free(pageBuffer); - Serial.printf("[%lu] [XTC] Generated thumb BMP (%dx%d): %s\n", millis(), thumbWidth, thumbHeight, - getThumbBmpPath(height).c_str()); + LOG_DBG("XTC", "Generated thumb BMP (%dx%d): %s", thumbWidth, thumbHeight, getThumbBmpPath(height).c_str()); return true; } diff --git a/lib/Xtc/Xtc/XtcParser.cpp b/lib/Xtc/Xtc/XtcParser.cpp index a604b645..12e8a61d 100644 --- a/lib/Xtc/Xtc/XtcParser.cpp +++ b/lib/Xtc/Xtc/XtcParser.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include @@ -42,7 +42,7 @@ XtcError XtcParser::open(const char* filepath) { // Read header m_lastError = readHeader(); if (m_lastError != XtcError::OK) { - Serial.printf("[%lu] [XTC] Failed to read header: %s\n", millis(), errorToString(m_lastError)); + LOG_DBG("XTC", "Failed to read header: %s", errorToString(m_lastError)); m_file.close(); return m_lastError; } @@ -51,13 +51,13 @@ XtcError XtcParser::open(const char* filepath) { if (m_header.hasMetadata) { m_lastError = readTitle(); if (m_lastError != XtcError::OK) { - Serial.printf("[%lu] [XTC] Failed to read title: %s\n", millis(), errorToString(m_lastError)); + LOG_DBG("XTC", "Failed to read title: %s", errorToString(m_lastError)); m_file.close(); return m_lastError; } m_lastError = readAuthor(); if (m_lastError != XtcError::OK) { - Serial.printf("[%lu] [XTC] Failed to read author: %s\n", millis(), errorToString(m_lastError)); + LOG_DBG("XTC", "Failed to read author: %s", errorToString(m_lastError)); m_file.close(); return m_lastError; } @@ -66,7 +66,7 @@ XtcError XtcParser::open(const char* filepath) { // Read page table m_lastError = readPageTable(); if (m_lastError != XtcError::OK) { - Serial.printf("[%lu] [XTC] Failed to read page table: %s\n", millis(), errorToString(m_lastError)); + LOG_DBG("XTC", "Failed to read page table: %s", errorToString(m_lastError)); m_file.close(); return m_lastError; } @@ -74,14 +74,13 @@ XtcError XtcParser::open(const char* filepath) { // Read chapters if present m_lastError = readChapters(); if (m_lastError != XtcError::OK) { - Serial.printf("[%lu] [XTC] Failed to read chapters: %s\n", millis(), errorToString(m_lastError)); + LOG_DBG("XTC", "Failed to read chapters: %s", errorToString(m_lastError)); m_file.close(); return m_lastError; } m_isOpen = true; - Serial.printf("[%lu] [XTC] Opened file: %s (%u pages, %dx%d)\n", millis(), filepath, m_header.pageCount, - m_defaultWidth, m_defaultHeight); + LOG_DBG("XTC", "Opened file: %s (%u pages, %dx%d)", filepath, m_header.pageCount, m_defaultWidth, m_defaultHeight); return XtcError::OK; } @@ -106,8 +105,7 @@ XtcError XtcParser::readHeader() { // Verify magic number (accept both XTC and XTCH) if (m_header.magic != XTC_MAGIC && m_header.magic != XTCH_MAGIC) { - Serial.printf("[%lu] [XTC] Invalid magic: 0x%08X (expected 0x%08X or 0x%08X)\n", millis(), m_header.magic, - XTC_MAGIC, XTCH_MAGIC); + LOG_DBG("XTC", "Invalid magic: 0x%08X (expected 0x%08X or 0x%08X)", m_header.magic, XTC_MAGIC, XTCH_MAGIC); return XtcError::INVALID_MAGIC; } @@ -120,7 +118,7 @@ XtcError XtcParser::readHeader() { const bool validVersion = m_header.versionMajor == 1 && m_header.versionMinor == 0 || m_header.versionMajor == 0 && m_header.versionMinor == 1; if (!validVersion) { - Serial.printf("[%lu] [XTC] Unsupported version: %u.%u\n", millis(), m_header.versionMajor, m_header.versionMinor); + LOG_DBG("XTC", "Unsupported version: %u.%u", m_header.versionMajor, m_header.versionMinor); return XtcError::INVALID_VERSION; } @@ -129,9 +127,9 @@ XtcError XtcParser::readHeader() { return XtcError::CORRUPTED_HEADER; } - Serial.printf("[%lu] [XTC] Header: magic=0x%08X (%s), ver=%u.%u, pages=%u, bitDepth=%u\n", millis(), m_header.magic, - (m_header.magic == XTCH_MAGIC) ? "XTCH" : "XTC", m_header.versionMajor, m_header.versionMinor, - m_header.pageCount, m_bitDepth); + LOG_DBG("XTC", "Header: magic=0x%08X (%s), ver=%u.%u, pages=%u, bitDepth=%u", m_header.magic, + (m_header.magic == XTCH_MAGIC) ? "XTCH" : "XTC", m_header.versionMajor, m_header.versionMinor, + m_header.pageCount, m_bitDepth); return XtcError::OK; } @@ -146,7 +144,7 @@ XtcError XtcParser::readTitle() { m_file.read(titleBuf, sizeof(titleBuf) - 1); m_title = titleBuf; - Serial.printf("[%lu] [XTC] Title: %s\n", millis(), m_title.c_str()); + LOG_DBG("XTC", "Title: %s", m_title.c_str()); return XtcError::OK; } @@ -161,19 +159,19 @@ XtcError XtcParser::readAuthor() { m_file.read(authorBuf, sizeof(authorBuf) - 1); m_author = authorBuf; - Serial.printf("[%lu] [XTC] Author: %s\n", millis(), m_author.c_str()); + LOG_DBG("XTC", "Author: %s", m_author.c_str()); return XtcError::OK; } XtcError XtcParser::readPageTable() { if (m_header.pageTableOffset == 0) { - Serial.printf("[%lu] [XTC] Page table offset is 0, cannot read\n", millis()); + LOG_DBG("XTC", "Page table offset is 0, cannot read"); return XtcError::CORRUPTED_HEADER; } // Seek to page table if (!m_file.seek(m_header.pageTableOffset)) { - Serial.printf("[%lu] [XTC] Failed to seek to page table at %llu\n", millis(), m_header.pageTableOffset); + LOG_DBG("XTC", "Failed to seek to page table at %llu", m_header.pageTableOffset); return XtcError::READ_ERROR; } @@ -184,7 +182,7 @@ XtcError XtcParser::readPageTable() { PageTableEntry entry; size_t bytesRead = m_file.read(reinterpret_cast(&entry), sizeof(PageTableEntry)); if (bytesRead != sizeof(PageTableEntry)) { - Serial.printf("[%lu] [XTC] Failed to read page table entry %u\n", millis(), i); + LOG_DBG("XTC", "Failed to read page table entry %u", i); return XtcError::READ_ERROR; } @@ -201,7 +199,7 @@ XtcError XtcParser::readPageTable() { } } - Serial.printf("[%lu] [XTC] Read %u page table entries\n", millis(), m_header.pageCount); + LOG_DBG("XTC", "Read %u page table entries", m_header.pageCount); return XtcError::OK; } @@ -307,7 +305,7 @@ XtcError XtcParser::readChapters() { } m_hasChapters = !m_chapters.empty(); - Serial.printf("[%lu] [XTC] Chapters: %u\n", millis(), static_cast(m_chapters.size())); + LOG_DBG("XTC", "Chapters: %u", static_cast(m_chapters.size())); return XtcError::OK; } @@ -334,7 +332,7 @@ size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSiz // Seek to page data if (!m_file.seek(page.offset)) { - Serial.printf("[%lu] [XTC] Failed to seek to page %u at offset %lu\n", millis(), pageIndex, page.offset); + LOG_DBG("XTC", "Failed to seek to page %u at offset %lu", pageIndex, page.offset); m_lastError = XtcError::READ_ERROR; return 0; } @@ -343,7 +341,7 @@ size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSiz XtgPageHeader pageHeader; size_t headerRead = m_file.read(reinterpret_cast(&pageHeader), sizeof(XtgPageHeader)); if (headerRead != sizeof(XtgPageHeader)) { - Serial.printf("[%lu] [XTC] Failed to read page header for page %u\n", millis(), pageIndex); + LOG_DBG("XTC", "Failed to read page header for page %u", pageIndex); m_lastError = XtcError::READ_ERROR; return 0; } @@ -351,8 +349,8 @@ size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSiz // Verify page magic (XTG for 1-bit, XTH for 2-bit) const uint32_t expectedMagic = (m_bitDepth == 2) ? XTH_MAGIC : XTG_MAGIC; if (pageHeader.magic != expectedMagic) { - Serial.printf("[%lu] [XTC] Invalid page magic for page %u: 0x%08X (expected 0x%08X)\n", millis(), pageIndex, - pageHeader.magic, expectedMagic); + LOG_DBG("XTC", "Invalid page magic for page %u: 0x%08X (expected 0x%08X)", pageIndex, pageHeader.magic, + expectedMagic); m_lastError = XtcError::INVALID_MAGIC; return 0; } @@ -370,7 +368,7 @@ size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSiz // Check buffer size if (bufferSize < bitmapSize) { - Serial.printf("[%lu] [XTC] Buffer too small: need %u, have %u\n", millis(), bitmapSize, bufferSize); + LOG_DBG("XTC", "Buffer too small: need %u, have %u", bitmapSize, bufferSize); m_lastError = XtcError::MEMORY_ERROR; return 0; } @@ -378,7 +376,7 @@ size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSiz // Read bitmap data size_t bytesRead = m_file.read(buffer, bitmapSize); if (bytesRead != bitmapSize) { - Serial.printf("[%lu] [XTC] Page read error: expected %u, got %u\n", millis(), bitmapSize, bytesRead); + LOG_DBG("XTC", "Page read error: expected %u, got %u", bitmapSize, bytesRead); m_lastError = XtcError::READ_ERROR; return 0; } diff --git a/lib/ZipFile/ZipFile.cpp b/lib/ZipFile/ZipFile.cpp index a2e92762..d9baace8 100644 --- a/lib/ZipFile/ZipFile.cpp +++ b/lib/ZipFile/ZipFile.cpp @@ -1,7 +1,7 @@ #include "ZipFile.h" #include -#include +#include #include #include @@ -10,7 +10,7 @@ bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t* // Setup inflator const auto inflator = static_cast(malloc(sizeof(tinfl_decompressor))); if (!inflator) { - Serial.printf("[%lu] [ZIP] Failed to allocate memory for inflator\n", millis()); + LOG_ERR("ZIP", "Failed to allocate memory for inflator"); return false; } memset(inflator, 0, sizeof(tinfl_decompressor)); @@ -23,7 +23,7 @@ bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t* free(inflator); if (status != TINFL_STATUS_DONE) { - Serial.printf("[%lu] [ZIP] tinfl_decompress() failed with status %d\n", millis(), status); + LOG_ERR("ZIP", "tinfl_decompress() failed with status %d", status); return false; } @@ -195,13 +195,13 @@ long ZipFile::getDataOffset(const FileStatSlim& fileStat) { } if (read != localHeaderSize) { - Serial.printf("[%lu] [ZIP] Something went wrong reading the local header\n", millis()); + LOG_ERR("ZIP", "Something went wrong reading the local header"); return -1; } if (pLocalHeader[0] + (pLocalHeader[1] << 8) + (pLocalHeader[2] << 16) + (pLocalHeader[3] << 24) != 0x04034b50 /* MZ_ZIP_LOCAL_DIR_HEADER_SIG */) { - Serial.printf("[%lu] [ZIP] Not a valid zip file header\n", millis()); + LOG_ERR("ZIP", "Not a valid zip file header"); return -1; } @@ -222,7 +222,7 @@ bool ZipFile::loadZipDetails() { const size_t fileSize = file.size(); if (fileSize < 22) { - Serial.printf("[%lu] [ZIP] File too small to be a valid zip\n", millis()); + LOG_ERR("ZIP", "File too small to be a valid zip"); if (!wasOpen) { close(); } @@ -234,7 +234,7 @@ bool ZipFile::loadZipDetails() { const int scanRange = fileSize > 1024 ? 1024 : fileSize; const auto buffer = static_cast(malloc(scanRange)); if (!buffer) { - Serial.printf("[%lu] [ZIP] Failed to allocate memory for EOCD scan buffer\n", millis()); + LOG_ERR("ZIP", "Failed to allocate memory for EOCD scan buffer"); if (!wasOpen) { close(); } @@ -255,7 +255,7 @@ bool ZipFile::loadZipDetails() { } if (foundOffset == -1) { - Serial.printf("[%lu] [ZIP] EOCD signature not found in zip file\n", millis()); + LOG_ERR("ZIP", "EOCD signature not found in zip file"); free(buffer); if (!wasOpen) { close(); @@ -407,7 +407,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo const auto dataSize = trailingNullByte ? inflatedDataSize + 1 : inflatedDataSize; const auto data = static_cast(malloc(dataSize)); if (data == nullptr) { - Serial.printf("[%lu] [ZIP] Failed to allocate memory for output buffer (%zu bytes)\n", millis(), dataSize); + LOG_ERR("ZIP", "Failed to allocate memory for output buffer (%zu bytes)", dataSize); if (!wasOpen) { close(); } @@ -422,7 +422,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo } if (dataRead != inflatedDataSize) { - Serial.printf("[%lu] [ZIP] Failed to read data\n", millis()); + LOG_ERR("ZIP", "Failed to read data"); free(data); return nullptr; } @@ -432,7 +432,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo // Read out deflated content from file const auto deflatedData = static_cast(malloc(deflatedDataSize)); if (deflatedData == nullptr) { - Serial.printf("[%lu] [ZIP] Failed to allocate memory for decompression buffer\n", millis()); + LOG_ERR("ZIP", "Failed to allocate memory for decompression buffer"); if (!wasOpen) { close(); } @@ -445,7 +445,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo } if (dataRead != deflatedDataSize) { - Serial.printf("[%lu] [ZIP] Failed to read data, expected %d got %d\n", millis(), deflatedDataSize, dataRead); + LOG_ERR("ZIP", "Failed to read data, expected %d got %d", deflatedDataSize, dataRead); free(deflatedData); free(data); return nullptr; @@ -455,14 +455,14 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo free(deflatedData); if (!success) { - Serial.printf("[%lu] [ZIP] Failed to inflate file\n", millis()); + LOG_ERR("ZIP", "Failed to inflate file"); free(data); return nullptr; } // Continue out of block with data set } else { - Serial.printf("[%lu] [ZIP] Unsupported compression method\n", millis()); + LOG_ERR("ZIP", "Unsupported compression method"); if (!wasOpen) { close(); } @@ -498,7 +498,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch // no deflation, just read content const auto buffer = static_cast(malloc(chunkSize)); if (!buffer) { - Serial.printf("[%lu] [ZIP] Failed to allocate memory for buffer\n", millis()); + LOG_ERR("ZIP", "Failed to allocate memory for buffer"); if (!wasOpen) { close(); } @@ -509,7 +509,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch while (remaining > 0) { const size_t dataRead = file.read(buffer, remaining < chunkSize ? remaining : chunkSize); if (dataRead == 0) { - Serial.printf("[%lu] [ZIP] Could not read more bytes\n", millis()); + LOG_ERR("ZIP", "Could not read more bytes"); free(buffer); if (!wasOpen) { close(); @@ -532,7 +532,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch // Setup inflator const auto inflator = static_cast(malloc(sizeof(tinfl_decompressor))); if (!inflator) { - Serial.printf("[%lu] [ZIP] Failed to allocate memory for inflator\n", millis()); + LOG_ERR("ZIP", "Failed to allocate memory for inflator"); if (!wasOpen) { close(); } @@ -544,7 +544,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch // Setup file read buffer const auto fileReadBuffer = static_cast(malloc(chunkSize)); if (!fileReadBuffer) { - Serial.printf("[%lu] [ZIP] Failed to allocate memory for zip file read buffer\n", millis()); + LOG_ERR("ZIP", "Failed to allocate memory for zip file read buffer"); free(inflator); if (!wasOpen) { close(); @@ -554,7 +554,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch const auto outputBuffer = static_cast(malloc(TINFL_LZ_DICT_SIZE)); if (!outputBuffer) { - Serial.printf("[%lu] [ZIP] Failed to allocate memory for dictionary\n", millis()); + LOG_ERR("ZIP", "Failed to allocate memory for dictionary"); free(inflator); free(fileReadBuffer); if (!wasOpen) { @@ -605,7 +605,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch if (outBytes > 0) { processedOutputBytes += outBytes; if (out.write(outputBuffer + outputCursor, outBytes) != outBytes) { - Serial.printf("[%lu] [ZIP] Failed to write all output bytes to stream\n", millis()); + LOG_ERR("ZIP", "Failed to write all output bytes to stream"); if (!wasOpen) { close(); } @@ -619,7 +619,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch } if (status < 0) { - Serial.printf("[%lu] [ZIP] tinfl_decompress() failed with status %d\n", millis(), status); + LOG_ERR("ZIP", "tinfl_decompress() failed with status %d", status); if (!wasOpen) { close(); } @@ -630,8 +630,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch } if (status == TINFL_STATUS_DONE) { - Serial.printf("[%lu] [ZIP] Decompressed %d bytes into %d bytes\n", millis(), deflatedDataSize, - inflatedDataSize); + LOG_ERR("ZIP", "Decompressed %d bytes into %d bytes", deflatedDataSize, inflatedDataSize); if (!wasOpen) { close(); } @@ -643,7 +642,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch } // If we get here, EOF reached without TINFL_STATUS_DONE - Serial.printf("[%lu] [ZIP] Unexpected EOF\n", millis()); + LOG_ERR("ZIP", "Unexpected EOF"); if (!wasOpen) { close(); } @@ -657,6 +656,6 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch close(); } - Serial.printf("[%lu] [ZIP] Unsupported compression method\n", millis()); + LOG_ERR("ZIP", "Unsupported compression method"); return false; } diff --git a/lib/hal/HalPowerManager.cpp b/lib/hal/HalPowerManager.cpp index 745579a3..c4ca41bf 100644 --- a/lib/hal/HalPowerManager.cpp +++ b/lib/hal/HalPowerManager.cpp @@ -1,5 +1,6 @@ #include "HalPowerManager.h" +#include #include #include "HalGPIO.h" @@ -14,16 +15,16 @@ void HalPowerManager::setPowerSaving(bool enabled) { return; // invalid state } if (enabled && !isLowPower) { - Serial.printf("[%lu] [PWR] Going to low-power mode\n", millis()); + LOG_DBG("PWR", "Going to low-power mode"); if (!setCpuFrequencyMhz(LOW_POWER_FREQ)) { - Serial.printf("[%lu] [PWR] Failed to set low-power CPU frequency\n", millis()); + LOG_ERR("PWR", "Failed to set low-power CPU frequency"); return; } } if (!enabled && isLowPower) { - Serial.printf("[%lu] [PWR] Restoring normal CPU frequency\n", millis()); + LOG_DBG("PWR", "Restoring normal CPU frequency"); if (!setCpuFrequencyMhz(normalFreq)) { - Serial.printf("[%lu] [PWR] Failed to restore normal CPU frequency\n", millis()); + LOG_ERR("PWR", "Failed to restore normal CPU frequency"); return; } } diff --git a/platformio.ini b/platformio.ini index 626a6d30..3c117f51 100644 --- a/platformio.ini +++ b/platformio.ini @@ -54,6 +54,9 @@ extends = base build_flags = ${base.build_flags} -DCROSSPOINT_VERSION=\"${crosspoint.version}-dev\" + -DENABLE_SERIAL_LOG + -DLOG_LEVEL=2 ; Set log level to debug for development builds + [env:mod] extends = base @@ -62,15 +65,30 @@ extra_scripts = pre:scripts/inject_mod_version.py build_flags = ${base.build_flags} + -DENABLE_SERIAL_LOG + -DLOG_LEVEL=2 ; Set log level to debug for mod builds [env:gh_release] extends = base build_flags = ${base.build_flags} -DCROSSPOINT_VERSION=\"${crosspoint.version}\" + -DENABLE_SERIAL_LOG + -DLOG_LEVEL=0 ; Set log level to error for release builds [env:gh_release_rc] extends = base build_flags = ${base.build_flags} -DCROSSPOINT_VERSION=\"${crosspoint.version}-rc+${sysenv.CROSSPOINT_RC_HASH}\" + -DENABLE_SERIAL_LOG + -DLOG_LEVEL=1 ; Set log level to info for release candidate builds + +[env:slim] +extends = base +build_flags = + ${base.build_flags} + -DCROSSPOINT_VERSION=\"${crosspoint.version}-slim\" + ; serial output is disabled in slim builds to save space + -UENABLE_SERIAL_LOG + \ No newline at end of file diff --git a/scripts/debugging_monitor.py b/scripts/debugging_monitor.py index f03ffa5a..4755e61b 100755 --- a/scripts/debugging_monitor.py +++ b/scripts/debugging_monitor.py @@ -2,30 +2,57 @@ """ ESP32 Serial Monitor with Memory Graph -This script provides a real-time serial monitor for ESP32 devices with -integrated memory usage graphing capabilities. It reads serial output, -parses memory information, and displays it in both console and graphical form. +This script provides a comprehensive real-time serial monitor for ESP32 devices with +integrated memory usage graphing capabilities. It reads serial output, parses memory +information, and displays it in both console and graphical form. + +Features: +- Real-time serial output monitoring with color-coded log levels +- Interactive memory usage graphing with matplotlib +- Command input interface for sending commands to the ESP32 device +- Screenshot capture and processing (1-bit black/white format) +- Graceful shutdown handling with Ctrl-C signal processing +- Configurable filtering and suppression of log messages +- Thread-safe operation with coordinated shutdown events + +Usage: + python debugging_monitor.py [port] [options] + +The script will open a matplotlib window showing memory usage over time and provide +an interactive command prompt for sending commands to the device. Press Ctrl-C or +close the graph window to exit gracefully. """ -import sys +from __future__ import annotations + import argparse +import glob +import platform import re +import signal +import sys import threading -from datetime import datetime from collections import deque +from datetime import datetime # Try to import potentially missing packages PACKAGE_MAPPING: dict[str, str] = { "serial": "pyserial", "colorama": "colorama", "matplotlib": "matplotlib", + "PIL": "Pillow", } try: - import serial - from colorama import init, Fore, Style import matplotlib.pyplot as plt + import serial + from colorama import Fore, Style, init from matplotlib import animation + + try: + from PIL import Image + except ImportError: + Image = None except ImportError as e: ERROR_MSG = str(e).lower() missing_packages = [pkg for mod, pkg in PACKAGE_MAPPING.items() if mod in ERROR_MSG] @@ -53,6 +80,9 @@ free_mem_data: deque[float] = deque(maxlen=MAX_POINTS) total_mem_data: deque[float] = deque(maxlen=MAX_POINTS) data_lock: threading.Lock = threading.Lock() # Prevent reading while writing +# Global shutdown flag +shutdown_event = threading.Event() + # Initialize colors init(autoreset=True) @@ -121,6 +151,15 @@ COLOR_KEYWORDS: dict[str, list[str]] = { } +def signal_handler(signum, frame): + """Handle SIGINT (Ctrl-C) by setting the shutdown event.""" + # frame parameter is required by signal handler signature but not used + del frame # Explicitly mark as unused to satisfy linters + print(f"\n{Fore.YELLOW}Received signal {signum}. Shutting down...{Style.RESET_ALL}") + shutdown_event.set() + plt.close("all") + + # pylint: disable=R0912 def get_color_for_line(line: str) -> str: """ @@ -150,12 +189,13 @@ def parse_memory_line(line: str) -> tuple[int | None, int | None]: return None, None -def serial_worker(port: str, baud: int, kwargs: dict[str, str]) -> None: +def serial_worker(ser, kwargs: dict[str, str]) -> None: """ - Runs in a background thread. Handles reading serial, printing to console, - and updating the data lists. + Runs in a background thread. Handles reading serial data, printing to console, + updating memory usage data for graphing, and processing screenshot data. + Monitors the global shutdown event for graceful termination. """ - print(f"{Fore.CYAN}--- Opening {port} at {baud} baud ---{Style.RESET_ALL}") + print(f"{Fore.CYAN}--- Opening serial port ---{Style.RESET_ALL}") filter_keyword = kwargs.get("filter", "").lower() suppress = kwargs.get("suppress", "").lower() if filter_keyword and suppress and filter_keyword == suppress: @@ -173,62 +213,107 @@ def serial_worker(port: str, baud: int, kwargs: dict[str, str]) -> None: f"{Fore.YELLOW}Suppressing lines containing: '{suppress}'{Style.RESET_ALL}" ) - try: - ser = serial.Serial(port, baud, timeout=0.1) - ser.dtr = False - ser.rts = False - except serial.SerialException as e: - print(f"{Fore.RED}Error opening port: {e}{Style.RESET_ALL}") - return + expecting_screenshot = False + screenshot_size = 0 + screenshot_data = b"" try: - while True: - try: - raw_data = ser.readline().decode("utf-8", errors="replace") - - if not raw_data: + while not shutdown_event.is_set(): + if expecting_screenshot: + data = ser.read(screenshot_size - len(screenshot_data)) + if not data: continue + screenshot_data += data + if len(screenshot_data) == screenshot_size: + if Image: + img = Image.frombytes("1", (800, 480), screenshot_data) + # We need to rotate the image because the raw data is in landscape mode + img = img.transpose(Image.ROTATE_270) + img.save("screenshot.bmp") + print( + f"{Fore.GREEN}Screenshot saved to screenshot.bmp{Style.RESET_ALL}" + ) + else: + with open("screenshot.raw", "wb") as f: + f.write(screenshot_data) + print( + f"{Fore.GREEN}Screenshot saved to screenshot.raw (PIL not available){Style.RESET_ALL}" + ) + expecting_screenshot = False + screenshot_data = b"" + else: + try: + raw_data = ser.readline().decode("utf-8", errors="replace") - clean_line = raw_data.strip() - if not clean_line: - continue + if not raw_data: + continue - # Add PC timestamp - pc_time = datetime.now().strftime("%H:%M:%S") - formatted_line = re.sub(r"^\[\d+\]", f"[{pc_time}]", clean_line) + clean_line = raw_data.strip() + if not clean_line: + continue - # Check for Memory Line - if "[MEM]" in formatted_line: - free_val, total_val = parse_memory_line(formatted_line) - if free_val is not None and total_val is not None: - with data_lock: - time_data.append(pc_time) - free_mem_data.append(free_val / 1024) # Convert to KB - total_mem_data.append(total_val / 1024) # Convert to KB - # Apply filters - if filter_keyword and filter_keyword not in formatted_line.lower(): - continue - if suppress and suppress in formatted_line.lower(): - continue - # Print to console - line_color = get_color_for_line(formatted_line) - print(f"{line_color}{formatted_line}") + if clean_line.startswith("SCREENSHOT_START:"): + screenshot_size = int(clean_line.split(":")[1]) + expecting_screenshot = True + continue + elif clean_line == "SCREENSHOT_END": + continue # ignore - except (OSError, UnicodeDecodeError): - print(f"{Fore.RED}Device disconnected or data error.{Style.RESET_ALL}") - break + # Add PC timestamp + pc_time = datetime.now().strftime("%H:%M:%S") + formatted_line = re.sub(r"^\[\d+\]", f"[{pc_time}]", clean_line) + + # Check for Memory Line + if "[MEM]" in formatted_line: + free_val, total_val = parse_memory_line(formatted_line) + if free_val is not None and total_val is not None: + with data_lock: + time_data.append(pc_time) + free_mem_data.append(free_val / 1024) # Convert to KB + total_mem_data.append(total_val / 1024) # Convert to KB + # Apply filters + if filter_keyword and filter_keyword not in formatted_line.lower(): + continue + if suppress and suppress in formatted_line.lower(): + continue + # Print to console + line_color = get_color_for_line(formatted_line) + print(f"{line_color}{formatted_line}") + + except (OSError, UnicodeDecodeError): + print( + f"{Fore.RED}Device disconnected or data error.{Style.RESET_ALL}" + ) + break except KeyboardInterrupt: # If thread is killed violently (e.g. main exit), silence errors pass finally: - if "ser" in locals() and ser.is_open: - ser.close() + pass # ser closed in main + + +def input_worker(ser) -> None: + """ + Runs in a background thread. Handles user input to send commands to the ESP32 device. + Monitors the global shutdown event for graceful termination on Ctrl-C. + """ + while not shutdown_event.is_set(): + try: + cmd = input("Command: ") + ser.write(f"CMD:{cmd}\n".encode()) + except (EOFError, KeyboardInterrupt): + break def update_graph(frame) -> list: # pylint: disable=unused-argument """ - Called by Matplotlib animation to redraw the chart. + Called by Matplotlib animation to redraw the memory usage chart. + Monitors the global shutdown event and closes the plot when shutdown is requested. """ + if shutdown_event.is_set(): + plt.close("all") + return [] + with data_lock: if not time_data: return [] @@ -262,24 +347,65 @@ def update_graph(frame) -> list: # pylint: disable=unused-argument return [] +def get_auto_detected_port() -> list[str]: + """ + Attempts to auto-detect the serial port for the ESP32 device. + Returns a list of all detected ports. + If no suitable port is found, the list will be empty. + Darwin/Linux logic by jonasdiemer + """ + port_list = [] + system = platform.system() + # Code for darwin (macOS), linux, and windows + if system in ("Darwin", "Linux"): + pattern = "/dev/tty.usbmodem*" if system == "Darwin" else "/dev/ttyACM*" + port_list = sorted(glob.glob(pattern)) + elif system == "Windows": + from serial.tools import list_ports + + # Be careful with this pattern list - it should be specific + # enough to avoid picking up unrelated devices, but broad enough + # to catch all common USB-serial adapters used with ESP32 + # Caveat: localized versions of Windows may have different descriptions, + # so we also check for specific VID:PID (but that may not cover all clones) + pattern_list = ["CP210x", "CH340", "USB Serial"] + found_ports = list_ports.comports() + port_list = [ + port.device + for port in found_ports + if any(pat in port.description for pat in pattern_list) + or port.hwid.startswith( + "USB VID:PID=303A:1001" + ) # Add specific VID:PID for XTEINK X4 + ] + + return port_list + + def main() -> None: """ Main entry point for the ESP32 monitor application. - Sets up argument parsing, starts serial monitoring thread, and initializes the memory graph. + + Sets up argument parsing, initializes serial communication, starts background threads + for serial monitoring and command input, and launches the memory usage graph. + Implements graceful shutdown handling with signal processing for clean termination. + + Features: + - Serial port monitoring with color-coded output + - Real-time memory usage graphing + - Interactive command interface + - Screenshot capture capability + - Graceful shutdown on Ctrl-C or window close """ - parser = argparse.ArgumentParser(description="ESP32 Monitor with Graph") - if sys.platform.startswith("win"): - default_port = "COM8" - elif sys.platform.startswith("darwin"): - default_port = "/dev/cu.usbmodem101" - else: - default_port = "/dev/ttyACM0" + parser = argparse.ArgumentParser( + description="ESP32 Serial Monitor with Memory Graph - Real-time monitoring, graphing, and command interface" + ) default_baudrate = 115200 parser.add_argument( "port", nargs="?", - default=default_port, - help=f"Serial port (default: {default_port})", + default=None, + help="Serial port (leave empty for autodetection)", ) parser.add_argument( "--baud", @@ -300,19 +426,54 @@ def main() -> None: help="Suppress lines containing this keyword (case-insensitive)", ) args = parser.parse_args() + port = args.port + if port is None: + port_list = get_auto_detected_port() + if len(port_list) == 1: + port = port_list[0] + print(f"{Fore.CYAN}Auto-detected serial port: {port}{Style.RESET_ALL}") + elif len(port_list) > 1: + print(f"{Fore.YELLOW}Multiple serial ports found:{Style.RESET_ALL}") + for p in port_list: + print(f" - {p}") + print( + f"{Fore.YELLOW}Please specify the desired port as a command-line argument.{Style.RESET_ALL}" + ) + if port is None: + print(f"{Fore.RED}Error: No suitable serial port found.{Style.RESET_ALL}") + sys.exit(1) + + try: + ser = serial.Serial(port, args.baud, timeout=0.1) + ser.dtr = False + ser.rts = False + except serial.SerialException as e: + print(f"{Fore.RED}Error opening port: {e}{Style.RESET_ALL}") + return + + # Set up signal handler for graceful shutdown + signal.signal(signal.SIGINT, signal_handler) # 1. Start the Serial Reader in a separate thread # Daemon=True means this thread dies when the main program closes myargs = vars(args) # Convert Namespace to dict for easier passing - t = threading.Thread( - target=serial_worker, args=(args.port, args.baud, myargs), daemon=True - ) + t = threading.Thread(target=serial_worker, args=(ser, myargs), daemon=True) t.start() + # Start input thread + input_thread = threading.Thread(target=input_worker, args=(ser,), daemon=True) + input_thread.start() + # 2. Set up the Graph (Main Thread) try: import matplotlib.style as mplstyle # pylint: disable=import-outside-toplevel - default_styles = ("light_background", "ggplot", "seaborn", "dark_background", ) + + default_styles = ( + "light_background", + "ggplot", + "seaborn", + "dark_background", + ) styles = list(mplstyle.available) for default_style in default_styles: if default_style in styles: @@ -333,11 +494,13 @@ def main() -> None: try: print( - f"{Fore.YELLOW}Starting Graph Window... (Close window to exit){Style.RESET_ALL}" + f"{Fore.YELLOW}Starting Graph Window... (Close window or press Ctrl-C to exit){Style.RESET_ALL}" ) plt.show() except KeyboardInterrupt: print(f"\n{Fore.YELLOW}Exiting...{Style.RESET_ALL}") + finally: + shutdown_event.set() # Ensure all threads know to stop plt.close("all") # Force close any lingering plot windows diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index df1fb9c7..23bcc978 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -1,7 +1,7 @@ #include "CrossPointSettings.h" #include -#include +#include #include #include @@ -122,7 +122,7 @@ bool CrossPointSettings::saveToFile() const { // New fields added at end for backward compatibility outputFile.close(); - Serial.printf("[%lu] [CPS] Settings saved to file\n", millis()); + LOG_DBG("CPS", "Settings saved to file"); return true; } @@ -135,7 +135,7 @@ bool CrossPointSettings::loadFromFile() { uint8_t version; serialization::readPod(inputFile, version); if (version != SETTINGS_FILE_VERSION) { - Serial.printf("[%lu] [CPS] Deserialization failed: Unknown version %u\n", millis(), version); + LOG_ERR("CPS", "Deserialization failed: Unknown version %u", version); inputFile.close(); return false; } @@ -238,7 +238,7 @@ bool CrossPointSettings::loadFromFile() { } inputFile.close(); - Serial.printf("[%lu] [CPS] Settings loaded from file\n", millis()); + LOG_DBG("CPS", "Settings loaded from file"); return true; } diff --git a/src/CrossPointState.cpp b/src/CrossPointState.cpp index 5ca3cb45..ad263248 100644 --- a/src/CrossPointState.cpp +++ b/src/CrossPointState.cpp @@ -1,7 +1,7 @@ #include "CrossPointState.h" #include -#include +#include #include namespace { @@ -35,7 +35,7 @@ bool CrossPointState::loadFromFile() { uint8_t version; serialization::readPod(inputFile, version); if (version > STATE_FILE_VERSION) { - Serial.printf("[%lu] [CPS] Deserialization failed: Unknown version %u\n", millis(), version); + LOG_ERR("CPS", "Deserialization failed: Unknown version %u", version); inputFile.close(); return false; } diff --git a/src/RecentBooksStore.cpp b/src/RecentBooksStore.cpp index 00d641dd..25092d85 100644 --- a/src/RecentBooksStore.cpp +++ b/src/RecentBooksStore.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -72,7 +72,7 @@ bool RecentBooksStore::saveToFile() const { } outputFile.close(); - Serial.printf("[%lu] [RBS] Recent books saved to file (%d entries)\n", millis(), count); + LOG_DBG("RBS", "Recent books saved to file (%d entries)", count); return true; } @@ -83,7 +83,7 @@ RecentBook RecentBooksStore::getDataFromBook(std::string path) const { lastBookFileName = path.substr(lastSlash + 1); } - Serial.printf("[%lu] [RBS] Loading recent book: %s\n", millis(), path.c_str()); + LOG_DBG("RBS", "Loading recent book: %s", path.c_str()); // If epub, try to load the metadata for title/author and cover if (StringUtils::checkFileExtension(lastBookFileName, ".epub")) { @@ -136,7 +136,7 @@ bool RecentBooksStore::loadFromFile() { } } } else { - Serial.printf("[%lu] [RBS] Deserialization failed: Unknown version %u\n", millis(), version); + LOG_ERR("RBS", "Deserialization failed: Unknown version %u", version); inputFile.close(); return false; } @@ -158,6 +158,6 @@ bool RecentBooksStore::loadFromFile() { } inputFile.close(); - Serial.printf("[%lu] [RBS] Recent books loaded from file (%d entries)\n", millis(), recentBooks.size()); + LOG_DBG("RBS", "Recent books loaded from file (%d entries)", recentBooks.size()); return true; } diff --git a/src/WifiCredentialStore.cpp b/src/WifiCredentialStore.cpp index 4ac6629b..5af239ad 100644 --- a/src/WifiCredentialStore.cpp +++ b/src/WifiCredentialStore.cpp @@ -1,7 +1,7 @@ #include "WifiCredentialStore.h" #include -#include +#include #include // Initialize the static instance @@ -21,7 +21,7 @@ constexpr size_t KEY_LENGTH = sizeof(OBFUSCATION_KEY); } // namespace void WifiCredentialStore::obfuscate(std::string& data) const { - Serial.printf("[%lu] [WCS] Obfuscating/deobfuscating %zu bytes\n", millis(), data.size()); + LOG_DBG("WCS", "Obfuscating/deobfuscating %zu bytes", data.size()); for (size_t i = 0; i < data.size(); i++) { data[i] ^= OBFUSCATION_KEY[i % KEY_LENGTH]; } @@ -45,8 +45,7 @@ bool WifiCredentialStore::saveToFile() const { for (const auto& cred : credentials) { // Write SSID (plaintext - not sensitive) serialization::writeString(file, cred.ssid); - Serial.printf("[%lu] [WCS] Saving SSID: %s, password length: %zu\n", millis(), cred.ssid.c_str(), - cred.password.size()); + LOG_DBG("WCS", "Saving SSID: %s, password length: %zu", cred.ssid.c_str(), cred.password.size()); // Write password (obfuscated) std::string obfuscatedPwd = cred.password; @@ -55,7 +54,7 @@ bool WifiCredentialStore::saveToFile() const { } file.close(); - Serial.printf("[%lu] [WCS] Saved %zu WiFi credentials to file\n", millis(), credentials.size()); + LOG_DBG("WCS", "Saved %zu WiFi credentials to file", credentials.size()); return true; } @@ -69,7 +68,7 @@ bool WifiCredentialStore::loadFromFile() { uint8_t version; serialization::readPod(file, version); if (version > WIFI_FILE_VERSION) { - Serial.printf("[%lu] [WCS] Unknown file version: %u\n", millis(), version); + LOG_DBG("WCS", "Unknown file version: %u", version); file.close(); return false; } @@ -94,16 +93,15 @@ bool WifiCredentialStore::loadFromFile() { // Read and deobfuscate password serialization::readString(file, cred.password); - Serial.printf("[%lu] [WCS] Loaded SSID: %s, obfuscated password length: %zu\n", millis(), cred.ssid.c_str(), - cred.password.size()); + LOG_DBG("WCS", "Loaded SSID: %s, obfuscated password length: %zu", cred.ssid.c_str(), cred.password.size()); obfuscate(cred.password); // XOR is symmetric, so same function deobfuscates - Serial.printf("[%lu] [WCS] After deobfuscation, password length: %zu\n", millis(), cred.password.size()); + LOG_DBG("WCS", "After deobfuscation, password length: %zu", cred.password.size()); credentials.push_back(cred); } file.close(); - Serial.printf("[%lu] [WCS] Loaded %zu WiFi credentials from file\n", millis(), credentials.size()); + LOG_DBG("WCS", "Loaded %zu WiFi credentials from file", credentials.size()); return true; } @@ -113,19 +111,19 @@ bool WifiCredentialStore::addCredential(const std::string& ssid, const std::stri [&ssid](const WifiCredential& cred) { return cred.ssid == ssid; }); if (cred != credentials.end()) { cred->password = password; - Serial.printf("[%lu] [WCS] Updated credentials for: %s\n", millis(), ssid.c_str()); + LOG_DBG("WCS", "Updated credentials for: %s", ssid.c_str()); return saveToFile(); } // Check if we've reached the limit if (credentials.size() >= MAX_NETWORKS) { - Serial.printf("[%lu] [WCS] Cannot add more networks, limit of %zu reached\n", millis(), MAX_NETWORKS); + LOG_DBG("WCS", "Cannot add more networks, limit of %zu reached", MAX_NETWORKS); return false; } // Add new credential credentials.push_back({ssid, password}); - Serial.printf("[%lu] [WCS] Added credentials for: %s\n", millis(), ssid.c_str()); + LOG_DBG("WCS", "Added credentials for: %s", ssid.c_str()); return saveToFile(); } @@ -134,7 +132,7 @@ bool WifiCredentialStore::removeCredential(const std::string& ssid) { [&ssid](const WifiCredential& cred) { return cred.ssid == ssid; }); if (cred != credentials.end()) { credentials.erase(cred); - Serial.printf("[%lu] [WCS] Removed credentials for: %s\n", millis(), ssid.c_str()); + LOG_DBG("WCS", "Removed credentials for: %s", ssid.c_str()); if (ssid == lastConnectedSsid) { clearLastConnectedSsid(); } @@ -176,5 +174,5 @@ void WifiCredentialStore::clearAll() { credentials.clear(); lastConnectedSsid.clear(); saveToFile(); - Serial.printf("[%lu] [WCS] Cleared all WiFi credentials\n", millis()); + LOG_DBG("WCS", "Cleared all WiFi credentials"); } diff --git a/src/activities/Activity.h b/src/activities/Activity.h index 632d396d..68e16815 100644 --- a/src/activities/Activity.h +++ b/src/activities/Activity.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -18,8 +18,8 @@ class Activity { explicit Activity(std::string name, GfxRenderer& renderer, MappedInputManager& mappedInput) : name(std::move(name)), renderer(renderer), mappedInput(mappedInput) {} virtual ~Activity() = default; - virtual void onEnter() { Serial.printf("[%lu] [ACT] Entering activity: %s\n", millis(), name.c_str()); } - virtual void onExit() { Serial.printf("[%lu] [ACT] Exiting activity: %s\n", millis(), name.c_str()); } + virtual void onEnter() { LOG_DBG("ACT", "Entering activity: %s", name.c_str()); } + virtual void onExit() { LOG_DBG("ACT", "Exiting activity: %s", name.c_str()); } virtual void loop() {} virtual bool skipLoopDelay() { return false; } virtual bool preventAutoSleep() { return false; } diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 55b93f5b..e5d2add9 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -142,8 +143,7 @@ bool loadEdgeCache(const std::string& path, int screenWidth, int screenHeight, L file.close(); data.valid = true; - Serial.printf("[%lu] [SLP] Loaded edge cache from %s (avgA=%d, avgB=%d)\n", millis(), path.c_str(), data.avgA, - data.avgB); + LOG_DBG("SLP", "Loaded edge cache from %s (avgA=%d, avgB=%d)", path.c_str(), data.avgA, data.avgB); return true; } @@ -163,7 +163,7 @@ bool saveEdgeCache(const std::string& path, int screenWidth, int screenHeight, c serialization::writePod(file, static_cast(data.letterboxB)); file.close(); - Serial.printf("[%lu] [SLP] Saved edge cache to %s\n", millis(), path.c_str()); + LOG_DBG("SLP", "Saved edge cache to %s", path.c_str()); return true; } @@ -380,13 +380,13 @@ void SleepActivity::renderCustomSleepScreen() const { } if (filename.substr(filename.length() - 4) != ".bmp") { - Serial.printf("[%lu] [SLP] Skipping non-.bmp file name: %s\n", millis(), name); + LOG_DBG("SLP", "Skipping non-.bmp file name: %s", name); file.close(); continue; } Bitmap bitmap(file); if (bitmap.parseHeaders() != BmpReaderError::Ok) { - Serial.printf("[%lu] [SLP] Skipping invalid BMP file: %s\n", millis(), name); + LOG_DBG("SLP", "Skipping invalid BMP file: %s", name); file.close(); continue; } @@ -406,7 +406,7 @@ void SleepActivity::renderCustomSleepScreen() const { const auto filename = "/sleep/" + files[randomFileIndex]; FsFile file; if (Storage.openFileForRead("SLP", filename, file)) { - Serial.printf("[%lu] [SLP] Randomly loading: /sleep/%s\n", millis(), files[randomFileIndex].c_str()); + LOG_DBG("SLP", "Randomly loading: /sleep/%s", files[randomFileIndex].c_str()); delay(100); Bitmap bitmap(file, true); if (bitmap.parseHeaders() == BmpReaderError::Ok) { @@ -425,7 +425,7 @@ void SleepActivity::renderCustomSleepScreen() const { if (Storage.openFileForRead("SLP", "/sleep.bmp", file)) { Bitmap bitmap(file, true); if (bitmap.parseHeaders() == BmpReaderError::Ok) { - Serial.printf("[%lu] [SLP] Loading: /sleep.bmp\n", millis()); + LOG_DBG("SLP", "Loading: /sleep.bmp"); renderBitmapSleepScreen(bitmap); return; } @@ -458,37 +458,36 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap, const std::str const auto pageHeight = renderer.getScreenHeight(); float cropX = 0, cropY = 0; - Serial.printf("[%lu] [SLP] bitmap %d x %d, screen %d x %d\n", millis(), bitmap.getWidth(), bitmap.getHeight(), - pageWidth, pageHeight); + LOG_DBG("SLP", "bitmap %d x %d, screen %d x %d", bitmap.getWidth(), bitmap.getHeight(), pageWidth, pageHeight); // Always compute aspect-ratio-preserving scale and position (supports both larger and smaller images) float ratio = static_cast(bitmap.getWidth()) / static_cast(bitmap.getHeight()); const float screenRatio = static_cast(pageWidth) / static_cast(pageHeight); - Serial.printf("[%lu] [SLP] bitmap ratio: %f, screen ratio: %f\n", millis(), ratio, screenRatio); + LOG_DBG("SLP", "bitmap ratio: %f, screen ratio: %f", ratio, screenRatio); if (ratio > screenRatio) { // image wider than viewport ratio, needs to be centered vertically if (SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP) { cropX = 1.0f - (screenRatio / ratio); - Serial.printf("[%lu] [SLP] Cropping bitmap x: %f\n", millis(), cropX); + LOG_DBG("SLP", "Cropping bitmap x: %f", cropX); ratio = (1.0f - cropX) * static_cast(bitmap.getWidth()) / static_cast(bitmap.getHeight()); } x = 0; y = std::round((static_cast(pageHeight) - static_cast(pageWidth) / ratio) / 2); - Serial.printf("[%lu] [SLP] Centering with ratio %f to y=%d\n", millis(), ratio, y); + LOG_DBG("SLP", "Centering with ratio %f to y=%d", ratio, y); } else { // image taller than or equal to viewport ratio, needs to be centered horizontally if (SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP) { cropY = 1.0f - (ratio / screenRatio); - Serial.printf("[%lu] [SLP] Cropping bitmap y: %f\n", millis(), cropY); + LOG_DBG("SLP", "Cropping bitmap y: %f", cropY); ratio = static_cast(bitmap.getWidth()) / ((1.0f - cropY) * static_cast(bitmap.getHeight())); } x = std::round((static_cast(pageWidth) - static_cast(pageHeight) * ratio) / 2); y = 0; - Serial.printf("[%lu] [SLP] Centering with ratio %f to x=%d\n", millis(), ratio, x); + LOG_DBG("SLP", "Centering with ratio %f to x=%d", ratio, x); } - Serial.printf("[%lu] [SLP] drawing to %d x %d\n", millis(), x, y); + LOG_DBG("SLP", "drawing to %d x %d", x, y); // Compute the scale factor (same formula as drawBitmap) so we can map screen coords to source coords const float effectiveWidth = (1.0f - cropX) * bitmap.getWidth(); @@ -515,17 +514,16 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap, const std::str cacheLoaded = loadEdgeCache(edgeCachePath, pageWidth, pageHeight, fillData); } if (!cacheLoaded) { - Serial.printf("[%lu] [SLP] Letterbox detected (x=%d, y=%d), computing edge averages for %s fill\n", millis(), x, - y, fillModeName); + LOG_DBG("SLP", "Letterbox detected (x=%d, y=%d), computing edge averages for %s fill", x, y, fillModeName); fillData = computeEdgeAverages(bitmap, x, y, pageWidth, pageHeight, scale, cropX, cropY); if (fillData.valid && !edgeCachePath.empty()) { saveEdgeCache(edgeCachePath, pageWidth, pageHeight, fillData); } } if (fillData.valid) { - Serial.printf("[%lu] [SLP] Letterbox fill: %s, horizontal=%d, avgA=%d, avgB=%d, letterboxA=%d, letterboxB=%d\n", - millis(), fillModeName, fillData.horizontal, fillData.avgA, fillData.avgB, fillData.letterboxA, - fillData.letterboxB); + LOG_DBG("SLP", "Letterbox fill: %s, horizontal=%d, avgA=%d, avgB=%d, letterboxA=%d, letterboxB=%d", + fillModeName, fillData.horizontal, fillData.avgA, fillData.avgB, fillData.letterboxA, + fillData.letterboxB); } } @@ -596,12 +594,12 @@ void SleepActivity::renderCoverSleepScreen() const { // Handle XTC file Xtc lastXtc(APP_STATE.openEpubPath, "/.crosspoint"); if (!lastXtc.load()) { - Serial.printf("[%lu] [SLP] Failed to load last XTC\n", millis()); + LOG_ERR("SLP", "Failed to load last XTC"); return (this->*renderNoCoverSleepScreen)(); } if (!lastXtc.generateCoverBmp()) { - Serial.printf("[%lu] [SLP] Failed to generate XTC cover bmp\n", millis()); + LOG_ERR("SLP", "Failed to generate XTC cover bmp"); return (this->*renderNoCoverSleepScreen)(); } @@ -611,12 +609,12 @@ void SleepActivity::renderCoverSleepScreen() const { // Handle TXT file - looks for cover image in the same folder Txt lastTxt(APP_STATE.openEpubPath, "/.crosspoint"); if (!lastTxt.load()) { - Serial.printf("[%lu] [SLP] Failed to load last TXT\n", millis()); + LOG_ERR("SLP", "Failed to load last TXT"); return (this->*renderNoCoverSleepScreen)(); } if (!lastTxt.generateCoverBmp()) { - Serial.printf("[%lu] [SLP] No cover image found for TXT file\n", millis()); + LOG_ERR("SLP", "No cover image found for TXT file"); return (this->*renderNoCoverSleepScreen)(); } @@ -627,12 +625,12 @@ void SleepActivity::renderCoverSleepScreen() const { Epub lastEpub(APP_STATE.openEpubPath, "/.crosspoint"); // Skip loading css since we only need metadata here if (!lastEpub.load(true, true)) { - Serial.printf("[%lu] [SLP] Failed to load last epub\n", millis()); + LOG_ERR("SLP", "Failed to load last epub"); return (this->*renderNoCoverSleepScreen)(); } if (!lastEpub.generateCoverBmp(cropped)) { - Serial.printf("[%lu] [SLP] Failed to generate cover bmp\n", millis()); + LOG_ERR("SLP", "Failed to generate cover bmp"); return (this->*renderNoCoverSleepScreen)(); } @@ -653,7 +651,7 @@ void SleepActivity::renderCoverSleepScreen() const { if (Storage.openFileForRead("SLP", coverBmpPath, file)) { Bitmap bitmap(file); if (bitmap.parseHeaders() == BmpReaderError::Ok) { - Serial.printf("[%lu] [SLP] Rendering sleep cover: %s\n", millis(), coverBmpPath.c_str()); + LOG_DBG("SLP", "Rendering sleep cover: %s", coverBmpPath.c_str()); // Derive edge cache path from cover BMP path (e.g. cover.bmp -> cover_edges.bin) std::string edgeCachePath; const auto dotPos = coverBmpPath.rfind(".bmp"); diff --git a/src/activities/browser/OpdsBookBrowserActivity.cpp b/src/activities/browser/OpdsBookBrowserActivity.cpp index 340b5444..35c8c725 100644 --- a/src/activities/browser/OpdsBookBrowserActivity.cpp +++ b/src/activities/browser/OpdsBookBrowserActivity.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -78,14 +78,14 @@ void OpdsBookBrowserActivity::loop() { // Check if WiFi is still connected if (WiFi.status() == WL_CONNECTED && WiFi.localIP() != IPAddress(0, 0, 0, 0)) { // WiFi connected - just retry fetching the feed - Serial.printf("[%lu] [OPDS] Retry: WiFi connected, retrying fetch\n", millis()); + LOG_DBG("OPDS", "Retry: WiFi connected, retrying fetch"); state = BrowserState::LOADING; statusMessage = "Loading..."; updateRequired = true; fetchFeed(currentPath); } else { // WiFi not connected - launch WiFi selection - Serial.printf("[%lu] [OPDS] Retry: WiFi not connected, launching selection\n", millis()); + LOG_DBG("OPDS", "Retry: WiFi not connected, launching selection"); launchWifiSelection(); } } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { @@ -265,7 +265,7 @@ void OpdsBookBrowserActivity::fetchFeed(const std::string& path) { } std::string url = UrlUtils::buildUrl(serverUrl, path); - Serial.printf("[%lu] [OPDS] Fetching: %s\n", millis(), url.c_str()); + LOG_DBG("OPDS", "Fetching: %s", url.c_str()); OpdsParser parser; @@ -287,7 +287,7 @@ void OpdsBookBrowserActivity::fetchFeed(const std::string& path) { } entries = std::move(parser).getEntries(); - Serial.printf("[%lu] [OPDS] Found %d entries\n", millis(), entries.size()); + LOG_DBG("OPDS", "Found %d entries", entries.size()); selectorIndex = 0; if (entries.empty()) { @@ -351,7 +351,7 @@ void OpdsBookBrowserActivity::downloadBook(const OpdsEntry& book) { } std::string filename = "/" + StringUtils::sanitizeFilename(baseName) + ".epub"; - Serial.printf("[%lu] [OPDS] Downloading: %s -> %s\n", millis(), downloadUrl.c_str(), filename.c_str()); + LOG_DBG("OPDS", "Downloading: %s -> %s", downloadUrl.c_str(), filename.c_str()); const auto result = HttpDownloader::downloadToFile(downloadUrl, filename, [this](const size_t downloaded, const size_t total) { @@ -361,12 +361,12 @@ void OpdsBookBrowserActivity::downloadBook(const OpdsEntry& book) { }); if (result == HttpDownloader::OK) { - Serial.printf("[%lu] [OPDS] Download complete: %s\n", millis(), filename.c_str()); + LOG_DBG("OPDS", "Download complete: %s", filename.c_str()); // Invalidate any existing cache for this file to prevent stale metadata issues Epub epub(filename, "/.crosspoint"); epub.clearCache(); - Serial.printf("[%lu] [OPDS] Cleared cache for: %s\n", millis(), filename.c_str()); + LOG_DBG("OPDS", "Cleared cache for: %s", filename.c_str()); state = BrowserState::BROWSING; updateRequired = true; @@ -403,13 +403,13 @@ void OpdsBookBrowserActivity::onWifiSelectionComplete(const bool connected) { exitActivity(); if (connected) { - Serial.printf("[%lu] [OPDS] WiFi connected via selection, fetching feed\n", millis()); + LOG_DBG("OPDS", "WiFi connected via selection, fetching feed"); state = BrowserState::LOADING; statusMessage = "Loading..."; updateRequired = true; fetchFeed(currentPath); } else { - Serial.printf("[%lu] [OPDS] WiFi selection cancelled/failed\n", millis()); + LOG_DBG("OPDS", "WiFi selection cancelled/failed"); // Force disconnect to ensure clean state for next retry // This prevents stale connection status from interfering WiFi.disconnect(); diff --git a/src/activities/home/RecentBooksActivity.cpp b/src/activities/home/RecentBooksActivity.cpp index 2301cbc4..4ade2ae8 100644 --- a/src/activities/home/RecentBooksActivity.cpp +++ b/src/activities/home/RecentBooksActivity.cpp @@ -73,7 +73,7 @@ void RecentBooksActivity::loop() { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (!recentBooks.empty() && selectorIndex < static_cast(recentBooks.size())) { - Serial.printf("[%lu] [RBA] Selected recent book: %s\n", millis(), recentBooks[selectorIndex].path.c_str()); + LOG_DBG("RBA", "Selected recent book: %s", recentBooks[selectorIndex].path.c_str()); onSelectBook(recentBooks[selectorIndex].path); return; } diff --git a/src/activities/network/CalibreConnectActivity.cpp b/src/activities/network/CalibreConnectActivity.cpp index c7f636d6..dd5e14bb 100644 --- a/src/activities/network/CalibreConnectActivity.cpp +++ b/src/activities/network/CalibreConnectActivity.cpp @@ -96,7 +96,7 @@ void CalibreConnectActivity::startWebServer() { if (MDNS.begin(HOSTNAME)) { // mDNS is optional for the Calibre plugin but still helpful for users. - Serial.printf("[%lu] [CAL] mDNS started: http://%s.local/\n", millis(), HOSTNAME); + LOG_DBG("CAL", "mDNS started: http://%s.local/", HOSTNAME); } webServer.reset(new CrossPointWebServer()); @@ -131,7 +131,7 @@ void CalibreConnectActivity::loop() { if (webServer && webServer->isRunning()) { const unsigned long timeSinceLastHandleClient = millis() - lastHandleClientTime; if (lastHandleClientTime > 0 && timeSinceLastHandleClient > 100) { - Serial.printf("[%lu] [CAL] WARNING: %lu ms gap since last handleClient\n", millis(), timeSinceLastHandleClient); + LOG_DBG("CAL", "WARNING: %lu ms gap since last handleClient", timeSinceLastHandleClient); } esp_task_wdt_reset(); diff --git a/src/activities/network/CrossPointWebServerActivity.cpp b/src/activities/network/CrossPointWebServerActivity.cpp index fe568503..6c52d791 100644 --- a/src/activities/network/CrossPointWebServerActivity.cpp +++ b/src/activities/network/CrossPointWebServerActivity.cpp @@ -37,7 +37,7 @@ void CrossPointWebServerActivity::taskTrampoline(void* param) { void CrossPointWebServerActivity::onEnter() { ActivityWithSubactivity::onEnter(); - Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onEnter: %d bytes\n", millis(), ESP.getFreeHeap()); + LOG_DBG("WEBACT] [MEM", "Free heap at onEnter: %d bytes", ESP.getFreeHeap()); renderingMutex = xSemaphoreCreateMutex(); @@ -58,7 +58,7 @@ void CrossPointWebServerActivity::onEnter() { ); // Launch network mode selection subactivity - Serial.printf("[%lu] [WEBACT] Launching NetworkModeSelectionActivity...\n", millis()); + LOG_DBG("WEBACT", "Launching NetworkModeSelectionActivity..."); enterNewActivity(new NetworkModeSelectionActivity( renderer, mappedInput, [this](const NetworkMode mode) { onNetworkModeSelected(mode); }, [this]() { onGoBack(); } // Cancel goes back to home @@ -68,7 +68,7 @@ void CrossPointWebServerActivity::onEnter() { void CrossPointWebServerActivity::onExit() { ActivityWithSubactivity::onExit(); - Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onExit start: %d bytes\n", millis(), ESP.getFreeHeap()); + LOG_DBG("WEBACT] [MEM", "Free heap at onExit start: %d bytes", ESP.getFreeHeap()); state = WebServerActivityState::SHUTTING_DOWN; @@ -80,7 +80,7 @@ void CrossPointWebServerActivity::onExit() { // Stop DNS server if running (AP mode) if (dnsServer) { - Serial.printf("[%lu] [WEBACT] Stopping DNS server...\n", millis()); + LOG_DBG("WEBACT", "Stopping DNS server..."); dnsServer->stop(); delete dnsServer; dnsServer = nullptr; @@ -91,39 +91,39 @@ void CrossPointWebServerActivity::onExit() { // Disconnect WiFi gracefully if (isApMode) { - Serial.printf("[%lu] [WEBACT] Stopping WiFi AP...\n", millis()); + LOG_DBG("WEBACT", "Stopping WiFi AP..."); WiFi.softAPdisconnect(true); } else { - Serial.printf("[%lu] [WEBACT] Disconnecting WiFi (graceful)...\n", millis()); + LOG_DBG("WEBACT", "Disconnecting WiFi (graceful)..."); WiFi.disconnect(false); // false = don't erase credentials, send disconnect frame } delay(30); // Allow disconnect frame to be sent - Serial.printf("[%lu] [WEBACT] Setting WiFi mode OFF...\n", millis()); + LOG_DBG("WEBACT", "Setting WiFi mode OFF..."); WiFi.mode(WIFI_OFF); delay(30); // Allow WiFi hardware to power down - Serial.printf("[%lu] [WEBACT] [MEM] Free heap after WiFi disconnect: %d bytes\n", millis(), ESP.getFreeHeap()); + LOG_DBG("WEBACT] [MEM", "Free heap after WiFi disconnect: %d bytes", ESP.getFreeHeap()); // Acquire mutex before deleting task - Serial.printf("[%lu] [WEBACT] Acquiring rendering mutex before task deletion...\n", millis()); + LOG_DBG("WEBACT", "Acquiring rendering mutex before task deletion..."); xSemaphoreTake(renderingMutex, portMAX_DELAY); // Delete the display task - Serial.printf("[%lu] [WEBACT] Deleting display task...\n", millis()); + LOG_DBG("WEBACT", "Deleting display task..."); if (displayTaskHandle) { vTaskDelete(displayTaskHandle); displayTaskHandle = nullptr; - Serial.printf("[%lu] [WEBACT] Display task deleted\n", millis()); + LOG_DBG("WEBACT", "Display task deleted"); } // Delete the mutex - Serial.printf("[%lu] [WEBACT] Deleting mutex...\n", millis()); + LOG_DBG("WEBACT", "Deleting mutex..."); vSemaphoreDelete(renderingMutex); renderingMutex = nullptr; - Serial.printf("[%lu] [WEBACT] Mutex deleted\n", millis()); + LOG_DBG("WEBACT", "Mutex deleted"); - Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onExit end: %d bytes\n", millis(), ESP.getFreeHeap()); + LOG_DBG("WEBACT] [MEM", "Free heap at onExit end: %d bytes", ESP.getFreeHeap()); } void CrossPointWebServerActivity::onNetworkModeSelected(const NetworkMode mode) { @@ -133,7 +133,7 @@ void CrossPointWebServerActivity::onNetworkModeSelected(const NetworkMode mode) } else if (mode == NetworkMode::CREATE_HOTSPOT) { modeName = "Create Hotspot"; } - Serial.printf("[%lu] [WEBACT] Network mode selected: %s\n", millis(), modeName); + LOG_DBG("WEBACT", "Network mode selected: %s", modeName); networkMode = mode; isApMode = (mode == NetworkMode::CREATE_HOTSPOT); @@ -155,11 +155,11 @@ void CrossPointWebServerActivity::onNetworkModeSelected(const NetworkMode mode) if (mode == NetworkMode::JOIN_NETWORK) { // STA mode - launch WiFi selection - Serial.printf("[%lu] [WEBACT] Turning on WiFi (STA mode)...\n", millis()); + LOG_DBG("WEBACT", "Turning on WiFi (STA mode)..."); WiFi.mode(WIFI_STA); state = WebServerActivityState::WIFI_SELECTION; - Serial.printf("[%lu] [WEBACT] Launching WifiSelectionActivity...\n", millis()); + LOG_DBG("WEBACT", "Launching WifiSelectionActivity..."); enterNewActivity(new WifiSelectionActivity(renderer, mappedInput, [this](const bool connected) { onWifiSelectionComplete(connected); })); } else { @@ -171,7 +171,7 @@ void CrossPointWebServerActivity::onNetworkModeSelected(const NetworkMode mode) } void CrossPointWebServerActivity::onWifiSelectionComplete(const bool connected) { - Serial.printf("[%lu] [WEBACT] WifiSelectionActivity completed, connected=%d\n", millis(), connected); + LOG_DBG("WEBACT", "WifiSelectionActivity completed, connected=%d", connected); if (connected) { // Get connection info before exiting subactivity @@ -183,7 +183,7 @@ void CrossPointWebServerActivity::onWifiSelectionComplete(const bool connected) // Start mDNS for hostname resolution if (MDNS.begin(AP_HOSTNAME)) { - Serial.printf("[%lu] [WEBACT] mDNS started: http://%s.local/\n", millis(), AP_HOSTNAME); + LOG_DBG("WEBACT", "mDNS started: http://%s.local/", AP_HOSTNAME); } // Start the web server @@ -199,8 +199,8 @@ void CrossPointWebServerActivity::onWifiSelectionComplete(const bool connected) } void CrossPointWebServerActivity::startAccessPoint() { - Serial.printf("[%lu] [WEBACT] Starting Access Point mode...\n", millis()); - Serial.printf("[%lu] [WEBACT] [MEM] Free heap before AP start: %d bytes\n", millis(), ESP.getFreeHeap()); + LOG_DBG("WEBACT", "Starting Access Point mode..."); + LOG_DBG("WEBACT] [MEM", "Free heap before AP start: %d bytes", ESP.getFreeHeap()); // Configure and start the AP WiFi.mode(WIFI_AP); @@ -216,7 +216,7 @@ void CrossPointWebServerActivity::startAccessPoint() { } if (!apStarted) { - Serial.printf("[%lu] [WEBACT] ERROR: Failed to start Access Point!\n", millis()); + LOG_ERR("WEBACT", "ERROR: Failed to start Access Point!"); onGoBack(); return; } @@ -230,15 +230,15 @@ void CrossPointWebServerActivity::startAccessPoint() { connectedIP = ipStr; connectedSSID = AP_SSID; - Serial.printf("[%lu] [WEBACT] Access Point started!\n", millis()); - Serial.printf("[%lu] [WEBACT] SSID: %s\n", millis(), AP_SSID); - Serial.printf("[%lu] [WEBACT] IP: %s\n", millis(), connectedIP.c_str()); + LOG_DBG("WEBACT", "Access Point started!"); + LOG_DBG("WEBACT", "SSID: %s", AP_SSID); + LOG_DBG("WEBACT", "IP: %s", connectedIP.c_str()); // Start mDNS for hostname resolution if (MDNS.begin(AP_HOSTNAME)) { - Serial.printf("[%lu] [WEBACT] mDNS started: http://%s.local/\n", millis(), AP_HOSTNAME); + LOG_DBG("WEBACT", "mDNS started: http://%s.local/", AP_HOSTNAME); } else { - Serial.printf("[%lu] [WEBACT] WARNING: mDNS failed to start\n", millis()); + LOG_DBG("WEBACT", "WARNING: mDNS failed to start"); } // Start DNS server for captive portal behavior @@ -246,16 +246,16 @@ void CrossPointWebServerActivity::startAccessPoint() { dnsServer = new DNSServer(); dnsServer->setErrorReplyCode(DNSReplyCode::NoError); dnsServer->start(DNS_PORT, "*", apIP); - Serial.printf("[%lu] [WEBACT] DNS server started for captive portal\n", millis()); + LOG_DBG("WEBACT", "DNS server started for captive portal"); - Serial.printf("[%lu] [WEBACT] [MEM] Free heap after AP start: %d bytes\n", millis(), ESP.getFreeHeap()); + LOG_DBG("WEBACT] [MEM", "Free heap after AP start: %d bytes", ESP.getFreeHeap()); // Start the web server startWebServer(); } void CrossPointWebServerActivity::startWebServer() { - Serial.printf("[%lu] [WEBACT] Starting web server...\n", millis()); + LOG_DBG("WEBACT", "Starting web server..."); // Create the web server instance webServer.reset(new CrossPointWebServer()); @@ -263,16 +263,16 @@ void CrossPointWebServerActivity::startWebServer() { if (webServer->isRunning()) { state = WebServerActivityState::SERVER_RUNNING; - Serial.printf("[%lu] [WEBACT] Web server started successfully\n", millis()); + LOG_DBG("WEBACT", "Web server started successfully"); // Force an immediate render since we're transitioning from a subactivity // that had its own rendering task. We need to make sure our display is shown. xSemaphoreTake(renderingMutex, portMAX_DELAY); render(); xSemaphoreGive(renderingMutex); - Serial.printf("[%lu] [WEBACT] Rendered File Transfer screen\n", millis()); + LOG_DBG("WEBACT", "Rendered File Transfer screen"); } else { - Serial.printf("[%lu] [WEBACT] ERROR: Failed to start web server!\n", millis()); + LOG_ERR("WEBACT", "ERROR: Failed to start web server!"); webServer.reset(); // Go back on error onGoBack(); @@ -281,9 +281,9 @@ void CrossPointWebServerActivity::startWebServer() { void CrossPointWebServerActivity::stopWebServer() { if (webServer && webServer->isRunning()) { - Serial.printf("[%lu] [WEBACT] Stopping web server...\n", millis()); + LOG_DBG("WEBACT", "Stopping web server..."); webServer->stop(); - Serial.printf("[%lu] [WEBACT] Web server stopped\n", millis()); + LOG_DBG("WEBACT", "Web server stopped"); } webServer.reset(); } @@ -309,7 +309,7 @@ void CrossPointWebServerActivity::loop() { lastWifiCheck = millis(); const wl_status_t wifiStatus = WiFi.status(); if (wifiStatus != WL_CONNECTED) { - Serial.printf("[%lu] [WEBACT] WiFi disconnected! Status: %d\n", millis(), wifiStatus); + LOG_DBG("WEBACT", "WiFi disconnected! Status: %d", wifiStatus); // Show error and exit gracefully state = WebServerActivityState::SHUTTING_DOWN; updateRequired = true; @@ -318,7 +318,7 @@ void CrossPointWebServerActivity::loop() { // Log weak signal warnings const int rssi = WiFi.RSSI(); if (rssi < -75) { - Serial.printf("[%lu] [WEBACT] Warning: Weak WiFi signal: %d dBm\n", millis(), rssi); + LOG_DBG("WEBACT", "Warning: Weak WiFi signal: %d dBm", rssi); } } } @@ -329,8 +329,7 @@ void CrossPointWebServerActivity::loop() { // Log if there's a significant gap between handleClient calls (>100ms) if (lastHandleClientTime > 0 && timeSinceLastHandleClient > 100) { - Serial.printf("[%lu] [WEBACT] WARNING: %lu ms gap since last handleClient\n", millis(), - timeSinceLastHandleClient); + LOG_DBG("WEBACT", "WARNING: %lu ms gap since last handleClient", timeSinceLastHandleClient); } // Reset watchdog BEFORE processing - HTTP header parsing can be slow @@ -401,7 +400,7 @@ void drawQRCode(const GfxRenderer& renderer, const int x, const int y, const std // The structure to manage the QR code QRCode qrcode; uint8_t qrcodeBytes[qrcode_getBufferSize(4)]; - Serial.printf("[%lu] [WEBACT] QR Code (%lu): %s\n", millis(), data.length(), data.c_str()); + LOG_DBG("WEBACT", "QR Code (%lu): %s", data.length(), data.c_str()); qrcode_initText(&qrcode, qrcodeBytes, 4, ECC_LOW, data.c_str()); const uint8_t px = 6; // pixels per module diff --git a/src/activities/network/WifiSelectionActivity.cpp b/src/activities/network/WifiSelectionActivity.cpp index 83af4e07..547acc39 100644 --- a/src/activities/network/WifiSelectionActivity.cpp +++ b/src/activities/network/WifiSelectionActivity.cpp @@ -1,6 +1,7 @@ #include "WifiSelectionActivity.h" #include +#include #include #include @@ -62,7 +63,7 @@ void WifiSelectionActivity::onEnter() { if (!lastSsid.empty()) { const auto* cred = WIFI_STORE.findCredential(lastSsid); if (cred) { - Serial.printf("[%lu] [WIFI] Attempting to auto-connect to %s\n", millis(), lastSsid.c_str()); + LOG_DBG("WIFI", "Attempting to auto-connect to %s", lastSsid.c_str()); selectedSSID = cred->ssid; enteredPassword = cred->password; selectedRequiresPassword = !cred->password.empty(); @@ -82,12 +83,12 @@ void WifiSelectionActivity::onEnter() { void WifiSelectionActivity::onExit() { Activity::onExit(); - Serial.printf("[%lu] [WIFI] [MEM] Free heap at onExit start: %d bytes\n", millis(), ESP.getFreeHeap()); + LOG_DBG("WIFI] [MEM", "Free heap at onExit start: %d bytes", ESP.getFreeHeap()); // Stop any ongoing WiFi scan - Serial.printf("[%lu] [WIFI] Deleting WiFi scan...\n", millis()); + LOG_DBG("WIFI", "Deleting WiFi scan..."); WiFi.scanDelete(); - Serial.printf("[%lu] [WIFI] [MEM] Free heap after scanDelete: %d bytes\n", millis(), ESP.getFreeHeap()); + LOG_DBG("WIFI] [MEM", "Free heap after scanDelete: %d bytes", ESP.getFreeHeap()); // Note: We do NOT disconnect WiFi here - the parent activity // (CrossPointWebServerActivity) manages WiFi connection state. We just clean @@ -95,25 +96,25 @@ void WifiSelectionActivity::onExit() { // Acquire mutex before deleting task to ensure task isn't using it // This prevents hangs/crashes if the task holds the mutex when deleted - Serial.printf("[%lu] [WIFI] Acquiring rendering mutex before task deletion...\n", millis()); + LOG_DBG("WIFI", "Acquiring rendering mutex before task deletion..."); xSemaphoreTake(renderingMutex, portMAX_DELAY); // Delete the display task (we now hold the mutex, so task is blocked if it // needs it) - Serial.printf("[%lu] [WIFI] Deleting display task...\n", millis()); + LOG_DBG("WIFI", "Deleting display task..."); if (displayTaskHandle) { vTaskDelete(displayTaskHandle); displayTaskHandle = nullptr; - Serial.printf("[%lu] [WIFI] Display task deleted\n", millis()); + LOG_DBG("WIFI", "Display task deleted"); } // Now safe to delete the mutex since we own it - Serial.printf("[%lu] [WIFI] Deleting mutex...\n", millis()); + LOG_DBG("WIFI", "Deleting mutex..."); vSemaphoreDelete(renderingMutex); renderingMutex = nullptr; - Serial.printf("[%lu] [WIFI] Mutex deleted\n", millis()); + LOG_DBG("WIFI", "Mutex deleted"); - Serial.printf("[%lu] [WIFI] [MEM] Free heap at onExit end: %d bytes\n", millis(), ESP.getFreeHeap()); + LOG_DBG("WIFI] [MEM", "Free heap at onExit end: %d bytes", ESP.getFreeHeap()); } void WifiSelectionActivity::startWifiScan() { @@ -211,8 +212,7 @@ void WifiSelectionActivity::selectNetwork(const int index) { // Use saved password - connect directly enteredPassword = savedCred->password; usedSavedPassword = true; - Serial.printf("[%lu] [WiFi] Using saved password for %s, length: %zu\n", millis(), selectedSSID.c_str(), - enteredPassword.size()); + LOG_DBG("WiFi", "Using saved password for %s, length: %zu", selectedSSID.c_str(), enteredPassword.size()); attemptConnection(); return; } @@ -290,10 +290,9 @@ void WifiSelectionActivity::checkConnectionStatus() { updateRequired = true; } else { // Using saved password or open network - complete immediately - Serial.printf( - "[%lu] [WIFI] Connected with saved/open credentials, " - "completing immediately\n", - millis()); + LOG_DBG("WIFI", + "Connected with saved/open credentials, " + "completing immediately"); onComplete(true); } return; diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 3837966f..8669f009 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "CrossPointSettings.h" #include "CrossPointState.h" @@ -88,7 +89,7 @@ void EpubReaderActivity::onEnter() { currentSpineIndex = data[0] + (data[1] << 8); nextPageNumber = data[2] + (data[3] << 8); cachedSpineIndex = currentSpineIndex; - Serial.printf("[%lu] [ERS] Loaded cache: %d, %d\n", millis(), currentSpineIndex, nextPageNumber); + LOG_DBG("ERS", "Loaded cache: %d, %d", currentSpineIndex, nextPageNumber); } if (dataSize == 6) { cachedChapterTotalPageCount = data[4] + (data[5] << 8); @@ -101,8 +102,7 @@ void EpubReaderActivity::onEnter() { int textSpineIndex = epub->getSpineIndexForTextReference(); if (textSpineIndex != 0) { currentSpineIndex = textSpineIndex; - Serial.printf("[%lu] [ERS] Opened for first time, navigating to text reference at index %d\n", millis(), - textSpineIndex); + LOG_DBG("ERS", "Opened for first time, navigating to text reference at index %d", textSpineIndex); } } @@ -867,7 +867,7 @@ void EpubReaderActivity::renderScreen() { if (!section) { const auto filepath = epub->getSpineItem(currentSpineIndex).href; - Serial.printf("[%lu] [ERS] Loading file: %s, index: %d\n", millis(), filepath.c_str(), currentSpineIndex); + LOG_DBG("ERS", "Loading file: %s, index: %d", filepath.c_str(), currentSpineIndex); section = std::unique_ptr
(new Section(epub, currentSpineIndex, renderer)); const uint16_t viewportWidth = renderer.getScreenWidth() - orientedMarginLeft - orientedMarginRight; @@ -876,19 +876,19 @@ void EpubReaderActivity::renderScreen() { if (!section->loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle)) { - Serial.printf("[%lu] [ERS] Cache not found, building...\n", millis()); + LOG_DBG("ERS", "Cache not found, building..."); const auto popupFn = [this]() { GUI.drawPopup(renderer, "Indexing..."); }; if (!section->createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, popupFn)) { - Serial.printf("[%lu] [ERS] Failed to persist page data to SD\n", millis()); + LOG_ERR("ERS", "Failed to persist page data to SD"); section.reset(); return; } } else { - Serial.printf("[%lu] [ERS] Cache found, skipping build...\n", millis()); + LOG_DBG("ERS", "Cache found, skipping build..."); } if (nextPageNumber == UINT16_MAX) { @@ -922,7 +922,7 @@ void EpubReaderActivity::renderScreen() { renderer.clearScreen(); if (section->pageCount == 0) { - Serial.printf("[%lu] [ERS] No pages to render\n", millis()); + LOG_DBG("ERS", "No pages to render"); renderer.drawCenteredText(UI_12_FONT_ID, 300, "Empty chapter", true, EpdFontFamily::BOLD); renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft); renderer.displayBuffer(); @@ -930,7 +930,7 @@ void EpubReaderActivity::renderScreen() { } if (section->currentPage < 0 || section->currentPage >= section->pageCount) { - Serial.printf("[%lu] [ERS] Page out of bounds: %d (max %d)\n", millis(), section->currentPage, section->pageCount); + LOG_DBG("ERS", "Page out of bounds: %d (max %d)", section->currentPage, section->pageCount); renderer.drawCenteredText(UI_12_FONT_ID, 300, "Out of bounds", true, EpdFontFamily::BOLD); renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft); renderer.displayBuffer(); @@ -940,14 +940,14 @@ void EpubReaderActivity::renderScreen() { { auto p = section->loadPageFromSectionFile(); if (!p) { - Serial.printf("[%lu] [ERS] Failed to load page from SD - clearing section cache\n", millis()); + LOG_ERR("ERS", "Failed to load page from SD - clearing section cache"); section->clearCache(); section.reset(); return renderScreen(); } const auto start = millis(); renderContents(std::move(p), orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft); - Serial.printf("[%lu] [ERS] Rendered page in %dms\n", millis(), millis() - start); + LOG_DBG("ERS", "Rendered page in %dms", millis() - start); } saveProgress(currentSpineIndex, section->currentPage, section->pageCount); } @@ -964,9 +964,9 @@ void EpubReaderActivity::saveProgress(int spineIndex, int currentPage, int pageC data[5] = (pageCount >> 8) & 0xFF; f.write(data, 6); f.close(); - Serial.printf("[%lu] [ERS] Progress saved: Chapter %d, Page %d\n", millis(), spineIndex, currentPage); + LOG_DBG("ERS", "Progress saved: Chapter %d, Page %d", spineIndex, currentPage); } else { - Serial.printf("[%lu] [ERS] Could not save progress!\n", millis()); + LOG_ERR("ERS", "Could not save progress!"); } } void EpubReaderActivity::renderContents(std::unique_ptr page, const int orientedMarginTop, diff --git a/src/activities/reader/KOReaderSyncActivity.cpp b/src/activities/reader/KOReaderSyncActivity.cpp index 31878c19..c32f9650 100644 --- a/src/activities/reader/KOReaderSyncActivity.cpp +++ b/src/activities/reader/KOReaderSyncActivity.cpp @@ -1,6 +1,7 @@ #include "KOReaderSyncActivity.h" #include +#include #include #include @@ -32,9 +33,9 @@ void syncTimeWithNTP() { } if (retry < maxRetries) { - Serial.printf("[%lu] [KOSync] NTP time synced\n", millis()); + LOG_DBG("KOSync", "NTP time synced"); } else { - Serial.printf("[%lu] [KOSync] NTP sync timeout, using fallback\n", millis()); + LOG_DBG("KOSync", "NTP sync timeout, using fallback"); } } } // namespace @@ -48,12 +49,12 @@ void KOReaderSyncActivity::onWifiSelectionComplete(const bool success) { exitActivity(); if (!success) { - Serial.printf("[%lu] [KOSync] WiFi connection failed, exiting\n", millis()); + LOG_DBG("KOSync", "WiFi connection failed, exiting"); onCancel(); return; } - Serial.printf("[%lu] [KOSync] WiFi connected, starting sync\n", millis()); + LOG_DBG("KOSync", "WiFi connected, starting sync"); xSemaphoreTake(renderingMutex, portMAX_DELAY); state = SYNCING; @@ -88,7 +89,7 @@ void KOReaderSyncActivity::performSync() { return; } - Serial.printf("[%lu] [KOSync] Document hash: %s\n", millis(), documentHash.c_str()); + LOG_DBG("KOSync", "Document hash: %s", documentHash.c_str()); xSemaphoreTake(renderingMutex, portMAX_DELAY); statusMessage = "Fetching remote progress..."; @@ -188,12 +189,12 @@ void KOReaderSyncActivity::onEnter() { } // Turn on WiFi - Serial.printf("[%lu] [KOSync] Turning on WiFi...\n", millis()); + LOG_DBG("KOSync", "Turning on WiFi..."); WiFi.mode(WIFI_STA); // Check if already connected if (WiFi.status() == WL_CONNECTED) { - Serial.printf("[%lu] [KOSync] Already connected to WiFi\n", millis()); + LOG_DBG("KOSync", "Already connected to WiFi"); state = SYNCING; statusMessage = "Syncing time..."; updateRequired = true; @@ -216,7 +217,7 @@ void KOReaderSyncActivity::onEnter() { } // Launch WiFi selection subactivity - Serial.printf("[%lu] [KOSync] Launching WifiSelectionActivity...\n", millis()); + LOG_DBG("KOSync", "Launching WifiSelectionActivity..."); enterNewActivity(new WifiSelectionActivity(renderer, mappedInput, [this](const bool connected) { onWifiSelectionComplete(connected); })); } diff --git a/src/activities/reader/ReaderActivity.cpp b/src/activities/reader/ReaderActivity.cpp index dd3ea2e9..b336c9a3 100644 --- a/src/activities/reader/ReaderActivity.cpp +++ b/src/activities/reader/ReaderActivity.cpp @@ -30,7 +30,7 @@ bool ReaderActivity::isTxtFile(const std::string& path) { std::unique_ptr ReaderActivity::loadEpub(const std::string& path) { if (!Storage.exists(path.c_str())) { - Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str()); + LOG_ERR("READER", "File does not exist: %s", path.c_str()); return nullptr; } @@ -39,13 +39,13 @@ std::unique_ptr ReaderActivity::loadEpub(const std::string& path) { return epub; } - Serial.printf("[%lu] [ ] Failed to load epub\n", millis()); + LOG_ERR("READER", "Failed to load epub"); return nullptr; } std::unique_ptr ReaderActivity::loadXtc(const std::string& path) { if (!Storage.exists(path.c_str())) { - Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str()); + LOG_ERR("READER", "File does not exist: %s", path.c_str()); return nullptr; } @@ -54,13 +54,13 @@ std::unique_ptr ReaderActivity::loadXtc(const std::string& path) { return xtc; } - Serial.printf("[%lu] [ ] Failed to load XTC\n", millis()); + LOG_ERR("READER", "Failed to load XTC"); return nullptr; } std::unique_ptr ReaderActivity::loadTxt(const std::string& path) { if (!Storage.exists(path.c_str())) { - Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str()); + LOG_ERR("READER", "File does not exist: %s", path.c_str()); return nullptr; } @@ -69,7 +69,7 @@ std::unique_ptr ReaderActivity::loadTxt(const std::string& path) { return txt; } - Serial.printf("[%lu] [ ] Failed to load TXT\n", millis()); + LOG_ERR("READER", "Failed to load TXT"); return nullptr; } diff --git a/src/activities/reader/TxtReaderActivity.cpp b/src/activities/reader/TxtReaderActivity.cpp index 5c2b38ee..88f26900 100644 --- a/src/activities/reader/TxtReaderActivity.cpp +++ b/src/activities/reader/TxtReaderActivity.cpp @@ -200,8 +200,7 @@ void TxtReaderActivity::initializeReader() { linesPerPage = viewportHeight / lineHeight; if (linesPerPage < 1) linesPerPage = 1; - Serial.printf("[%lu] [TRS] Viewport: %dx%d, lines per page: %d\n", millis(), viewportWidth, viewportHeight, - linesPerPage); + LOG_DBG("TRS", "Viewport: %dx%d, lines per page: %d", viewportWidth, viewportHeight, linesPerPage); // Try to load cached page index first if (!loadPageIndexCache()) { @@ -224,7 +223,7 @@ void TxtReaderActivity::buildPageIndex() { size_t offset = 0; const size_t fileSize = txt->getFileSize(); - Serial.printf("[%lu] [TRS] Building page index for %zu bytes...\n", millis(), fileSize); + LOG_DBG("TRS", "Building page index for %zu bytes...", fileSize); GUI.drawPopup(renderer, "Indexing..."); @@ -253,7 +252,7 @@ void TxtReaderActivity::buildPageIndex() { } totalPages = pageOffsets.size(); - Serial.printf("[%lu] [TRS] Built page index: %d pages\n", millis(), totalPages); + LOG_DBG("TRS", "Built page index: %d pages", totalPages); } bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector& outLines, size_t& nextOffset) { @@ -268,7 +267,7 @@ bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector size_t chunkSize = std::min(CHUNK_SIZE, fileSize - offset); auto* buffer = static_cast(malloc(chunkSize + 1)); if (!buffer) { - Serial.printf("[%lu] [TRS] Failed to allocate %zu bytes\n", millis(), chunkSize); + LOG_ERR("TRS", "Failed to allocate %zu bytes", chunkSize); return false; } @@ -597,7 +596,7 @@ void TxtReaderActivity::loadProgress() { if (currentPage < 0) { currentPage = 0; } - Serial.printf("[%lu] [TRS] Loaded progress: page %d/%d\n", millis(), currentPage, totalPages); + LOG_DBG("TRS", "Loaded progress: page %d/%d", currentPage, totalPages); } f.close(); } @@ -619,7 +618,7 @@ bool TxtReaderActivity::loadPageIndexCache() { std::string cachePath = txt->getCachePath() + "/index.bin"; FsFile f; if (!Storage.openFileForRead("TRS", cachePath, f)) { - Serial.printf("[%lu] [TRS] No page index cache found\n", millis()); + LOG_DBG("TRS", "No page index cache found"); return false; } @@ -627,7 +626,7 @@ bool TxtReaderActivity::loadPageIndexCache() { uint32_t magic; serialization::readPod(f, magic); if (magic != CACHE_MAGIC) { - Serial.printf("[%lu] [TRS] Cache magic mismatch, rebuilding\n", millis()); + LOG_DBG("TRS", "Cache magic mismatch, rebuilding"); f.close(); return false; } @@ -635,7 +634,7 @@ bool TxtReaderActivity::loadPageIndexCache() { uint8_t version; serialization::readPod(f, version); if (version != CACHE_VERSION) { - Serial.printf("[%lu] [TRS] Cache version mismatch (%d != %d), rebuilding\n", millis(), version, CACHE_VERSION); + LOG_DBG("TRS", "Cache version mismatch (%d != %d), rebuilding", version, CACHE_VERSION); f.close(); return false; } @@ -643,7 +642,7 @@ bool TxtReaderActivity::loadPageIndexCache() { uint32_t fileSize; serialization::readPod(f, fileSize); if (fileSize != txt->getFileSize()) { - Serial.printf("[%lu] [TRS] Cache file size mismatch, rebuilding\n", millis()); + LOG_DBG("TRS", "Cache file size mismatch, rebuilding"); f.close(); return false; } @@ -651,7 +650,7 @@ bool TxtReaderActivity::loadPageIndexCache() { int32_t cachedWidth; serialization::readPod(f, cachedWidth); if (cachedWidth != viewportWidth) { - Serial.printf("[%lu] [TRS] Cache viewport width mismatch, rebuilding\n", millis()); + LOG_DBG("TRS", "Cache viewport width mismatch, rebuilding"); f.close(); return false; } @@ -659,7 +658,7 @@ bool TxtReaderActivity::loadPageIndexCache() { int32_t cachedLines; serialization::readPod(f, cachedLines); if (cachedLines != linesPerPage) { - Serial.printf("[%lu] [TRS] Cache lines per page mismatch, rebuilding\n", millis()); + LOG_DBG("TRS", "Cache lines per page mismatch, rebuilding"); f.close(); return false; } @@ -667,7 +666,7 @@ bool TxtReaderActivity::loadPageIndexCache() { int32_t fontId; serialization::readPod(f, fontId); if (fontId != cachedFontId) { - Serial.printf("[%lu] [TRS] Cache font ID mismatch (%d != %d), rebuilding\n", millis(), fontId, cachedFontId); + LOG_DBG("TRS", "Cache font ID mismatch (%d != %d), rebuilding", fontId, cachedFontId); f.close(); return false; } @@ -675,7 +674,7 @@ bool TxtReaderActivity::loadPageIndexCache() { int32_t margin; serialization::readPod(f, margin); if (margin != cachedScreenMargin) { - Serial.printf("[%lu] [TRS] Cache screen margin mismatch, rebuilding\n", millis()); + LOG_DBG("TRS", "Cache screen margin mismatch, rebuilding"); f.close(); return false; } @@ -683,7 +682,7 @@ bool TxtReaderActivity::loadPageIndexCache() { uint8_t alignment; serialization::readPod(f, alignment); if (alignment != cachedParagraphAlignment) { - Serial.printf("[%lu] [TRS] Cache paragraph alignment mismatch, rebuilding\n", millis()); + LOG_DBG("TRS", "Cache paragraph alignment mismatch, rebuilding"); f.close(); return false; } @@ -703,7 +702,7 @@ bool TxtReaderActivity::loadPageIndexCache() { f.close(); totalPages = pageOffsets.size(); - Serial.printf("[%lu] [TRS] Loaded page index cache: %d pages\n", millis(), totalPages); + LOG_DBG("TRS", "Loaded page index cache: %d pages", totalPages); return true; } @@ -711,7 +710,7 @@ void TxtReaderActivity::savePageIndexCache() const { std::string cachePath = txt->getCachePath() + "/index.bin"; FsFile f; if (!Storage.openFileForWrite("TRS", cachePath, f)) { - Serial.printf("[%lu] [TRS] Failed to save page index cache\n", millis()); + LOG_ERR("TRS", "Failed to save page index cache"); return; } @@ -732,5 +731,5 @@ void TxtReaderActivity::savePageIndexCache() const { } f.close(); - Serial.printf("[%lu] [TRS] Saved page index cache: %d pages\n", millis(), totalPages); + LOG_DBG("TRS", "Saved page index cache: %d pages", totalPages); } diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index 7297d5eb..108611e5 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -237,7 +237,7 @@ void XtcReaderActivity::renderPage() { // Allocate page buffer uint8_t* pageBuffer = static_cast(malloc(pageBufferSize)); if (!pageBuffer) { - Serial.printf("[%lu] [XTR] Failed to allocate page buffer (%lu bytes)\n", millis(), pageBufferSize); + LOG_ERR("XTR", "Failed to allocate page buffer (%lu bytes)", pageBufferSize); renderer.clearScreen(); renderer.drawCenteredText(UI_12_FONT_ID, 300, "Memory error", true, EpdFontFamily::BOLD); renderer.displayBuffer(); @@ -247,7 +247,7 @@ void XtcReaderActivity::renderPage() { // Load page data size_t bytesRead = xtc->loadPage(currentPage, pageBuffer, pageBufferSize); if (bytesRead == 0) { - Serial.printf("[%lu] [XTR] Failed to load page %lu\n", millis(), currentPage); + LOG_ERR("XTR", "Failed to load page %lu", currentPage); free(pageBuffer); renderer.clearScreen(); renderer.drawCenteredText(UI_12_FONT_ID, 300, "Page load error", true, EpdFontFamily::BOLD); @@ -296,8 +296,8 @@ void XtcReaderActivity::renderPage() { pixelCounts[getPixelValue(x, y)]++; } } - Serial.printf("[%lu] [XTR] Pixel distribution: White=%lu, DarkGrey=%lu, LightGrey=%lu, Black=%lu\n", millis(), - pixelCounts[0], pixelCounts[1], pixelCounts[2], pixelCounts[3]); + LOG_DBG("XTR", "Pixel distribution: White=%lu, DarkGrey=%lu, LightGrey=%lu, Black=%lu", pixelCounts[0], + pixelCounts[1], pixelCounts[2], pixelCounts[3]); // Pass 1: BW buffer - draw all non-white pixels as black for (uint16_t y = 0; y < pageHeight; y++) { @@ -360,8 +360,7 @@ void XtcReaderActivity::renderPage() { free(pageBuffer); - Serial.printf("[%lu] [XTR] Rendered page %lu/%lu (2-bit grayscale)\n", millis(), currentPage + 1, - xtc->getPageCount()); + LOG_DBG("XTR", "Rendered page %lu/%lu (2-bit grayscale)", currentPage + 1, xtc->getPageCount()); return; } else { // 1-bit mode: 8 pixels per byte, MSB first @@ -397,8 +396,7 @@ void XtcReaderActivity::renderPage() { pagesUntilFullRefresh--; } - Serial.printf("[%lu] [XTR] Rendered page %lu/%lu (%u-bit)\n", millis(), currentPage + 1, xtc->getPageCount(), - bitDepth); + LOG_DBG("XTR", "Rendered page %lu/%lu (%u-bit)", currentPage + 1, xtc->getPageCount(), bitDepth); } void XtcReaderActivity::saveProgress() const { @@ -420,7 +418,7 @@ void XtcReaderActivity::loadProgress() { uint8_t data[4]; if (f.read(data, 4) == 4) { currentPage = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); - Serial.printf("[%lu] [XTR] Loaded progress: page %lu\n", millis(), currentPage); + LOG_DBG("XTR", "Loaded progress: page %lu", currentPage); // Validate page number if (currentPage >= xtc->getPageCount()) { diff --git a/src/activities/settings/ClearCacheActivity.cpp b/src/activities/settings/ClearCacheActivity.cpp index b17627dc..9da9444b 100644 --- a/src/activities/settings/ClearCacheActivity.cpp +++ b/src/activities/settings/ClearCacheActivity.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include "MappedInputManager.h" #include "components/UITheme.h" @@ -104,12 +104,12 @@ void ClearCacheActivity::render() { } void ClearCacheActivity::clearCache() { - Serial.printf("[%lu] [CLEAR_CACHE] Clearing cache...\n", millis()); + LOG_DBG("CLEAR_CACHE", "Clearing cache..."); // Open .crosspoint directory auto root = Storage.open("/.crosspoint"); if (!root || !root.isDirectory()) { - Serial.printf("[%lu] [CLEAR_CACHE] Failed to open cache directory\n", millis()); + LOG_DBG("CLEAR_CACHE", "Failed to open cache directory"); if (root) root.close(); state = FAILED; updateRequired = true; @@ -128,14 +128,14 @@ void ClearCacheActivity::clearCache() { // Only delete directories starting with epub_ or xtc_ if (file.isDirectory() && (itemName.startsWith("epub_") || itemName.startsWith("xtc_"))) { String fullPath = "/.crosspoint/" + itemName; - Serial.printf("[%lu] [CLEAR_CACHE] Removing cache: %s\n", millis(), fullPath.c_str()); + LOG_DBG("CLEAR_CACHE", "Removing cache: %s", fullPath.c_str()); file.close(); // Close before attempting to delete if (Storage.removeDir(fullPath.c_str())) { clearedCount++; } else { - Serial.printf("[%lu] [CLEAR_CACHE] Failed to remove: %s\n", millis(), fullPath.c_str()); + LOG_ERR("CLEAR_CACHE", "Failed to remove: %s", fullPath.c_str()); failedCount++; } } else { @@ -144,7 +144,7 @@ void ClearCacheActivity::clearCache() { } root.close(); - Serial.printf("[%lu] [CLEAR_CACHE] Cache cleared: %d removed, %d failed\n", millis(), clearedCount, failedCount); + LOG_DBG("CLEAR_CACHE", "Cache cleared: %d removed, %d failed", clearedCount, failedCount); state = SUCCESS; updateRequired = true; @@ -153,7 +153,7 @@ void ClearCacheActivity::clearCache() { void ClearCacheActivity::loop() { if (state == WARNING) { if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { - Serial.printf("[%lu] [CLEAR_CACHE] User confirmed, starting cache clear\n", millis()); + LOG_DBG("CLEAR_CACHE", "User confirmed, starting cache clear"); xSemaphoreTake(renderingMutex, portMAX_DELAY); state = CLEARING; xSemaphoreGive(renderingMutex); @@ -164,7 +164,7 @@ void ClearCacheActivity::loop() { } if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { - Serial.printf("[%lu] [CLEAR_CACHE] User cancelled\n", millis()); + LOG_DBG("CLEAR_CACHE", "User cancelled"); goBack(); } return; diff --git a/src/activities/settings/OtaUpdateActivity.cpp b/src/activities/settings/OtaUpdateActivity.cpp index c6f16a78..e469efce 100644 --- a/src/activities/settings/OtaUpdateActivity.cpp +++ b/src/activities/settings/OtaUpdateActivity.cpp @@ -18,12 +18,12 @@ void OtaUpdateActivity::onWifiSelectionComplete(const bool success) { exitActivity(); if (!success) { - Serial.printf("[%lu] [OTA] WiFi connection failed, exiting\n", millis()); + LOG_ERR("OTA", "WiFi connection failed, exiting"); goBack(); return; } - Serial.printf("[%lu] [OTA] WiFi connected, checking for update\n", millis()); + LOG_DBG("OTA", "WiFi connected, checking for update"); xSemaphoreTake(renderingMutex, portMAX_DELAY); state = CHECKING_FOR_UPDATE; @@ -32,7 +32,7 @@ void OtaUpdateActivity::onWifiSelectionComplete(const bool success) { vTaskDelay(10 / portTICK_PERIOD_MS); const auto res = updater.checkForUpdate(); if (res != OtaUpdater::OK) { - Serial.printf("[%lu] [OTA] Update check failed: %d\n", millis(), res); + LOG_DBG("OTA", "Update check failed: %d", res); xSemaphoreTake(renderingMutex, portMAX_DELAY); state = FAILED; xSemaphoreGive(renderingMutex); @@ -41,7 +41,7 @@ void OtaUpdateActivity::onWifiSelectionComplete(const bool success) { } if (!updater.isUpdateNewer()) { - Serial.printf("[%lu] [OTA] No new update available\n", millis()); + LOG_DBG("OTA", "No new update available"); xSemaphoreTake(renderingMutex, portMAX_DELAY); state = NO_UPDATE; xSemaphoreGive(renderingMutex); @@ -68,11 +68,11 @@ void OtaUpdateActivity::onEnter() { ); // Turn on WiFi immediately - Serial.printf("[%lu] [OTA] Turning on WiFi...\n", millis()); + LOG_DBG("OTA", "Turning on WiFi..."); WiFi.mode(WIFI_STA); // Launch WiFi selection subactivity - Serial.printf("[%lu] [OTA] Launching WifiSelectionActivity...\n", millis()); + LOG_DBG("OTA", "Launching WifiSelectionActivity..."); enterNewActivity(new WifiSelectionActivity(renderer, mappedInput, [this](const bool connected) { onWifiSelectionComplete(connected); })); } @@ -116,8 +116,7 @@ void OtaUpdateActivity::render() { float updaterProgress = 0; if (state == UPDATE_IN_PROGRESS) { - Serial.printf("[%lu] [OTA] Update progress: %d / %d\n", millis(), updater.getProcessedSize(), - updater.getTotalSize()); + LOG_DBG("OTA", "Update progress: %d / %d", updater.getProcessedSize(), updater.getTotalSize()); updaterProgress = static_cast(updater.getProcessedSize()) / static_cast(updater.getTotalSize()); // Only update every 2% at the most if (static_cast(updaterProgress * 50) == lastUpdaterPercentage / 2) { @@ -190,7 +189,7 @@ void OtaUpdateActivity::loop() { if (state == WAITING_CONFIRMATION) { if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { - Serial.printf("[%lu] [OTA] New update available, starting download...\n", millis()); + LOG_DBG("OTA", "New update available, starting download..."); xSemaphoreTake(renderingMutex, portMAX_DELAY); state = UPDATE_IN_PROGRESS; xSemaphoreGive(renderingMutex); @@ -199,7 +198,7 @@ void OtaUpdateActivity::loop() { const auto res = updater.installUpdate(); if (res != OtaUpdater::OK) { - Serial.printf("[%lu] [OTA] Update failed: %d\n", millis(), res); + LOG_DBG("OTA", "Update failed: %d", res); xSemaphoreTake(renderingMutex, portMAX_DELAY); state = FAILED; xSemaphoreGive(renderingMutex); diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 7d3a6016..20383334 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -1,7 +1,7 @@ #include "SettingsActivity.h" #include -#include +#include #include "ButtonRemapActivity.h" #include "CalibreSettingsActivity.h" diff --git a/src/components/UITheme.cpp b/src/components/UITheme.cpp index 9dbe429e..a1ddd1f3 100644 --- a/src/components/UITheme.cpp +++ b/src/components/UITheme.cpp @@ -1,6 +1,7 @@ #include "UITheme.h" #include +#include #include @@ -23,12 +24,12 @@ void UITheme::reload() { void UITheme::setTheme(CrossPointSettings::UI_THEME type) { switch (type) { case CrossPointSettings::UI_THEME::CLASSIC: - Serial.printf("[%lu] [UI] Using Classic theme\n", millis()); + LOG_DBG("UI", "Using Classic theme"); currentTheme = new BaseTheme(); currentMetrics = &BaseMetrics::values; break; case CrossPointSettings::UI_THEME::LYRA: - Serial.printf("[%lu] [UI] Using Lyra theme\n", millis()); + LOG_DBG("UI", "Using Lyra theme"); currentTheme = new LyraTheme(); currentMetrics = &LyraMetrics::values; break; diff --git a/src/components/themes/BaseTheme.cpp b/src/components/themes/BaseTheme.cpp index 14783e55..5af1b725 100644 --- a/src/components/themes/BaseTheme.cpp +++ b/src/components/themes/BaseTheme.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -311,7 +312,7 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: if (Storage.openFileForRead("HOME", coverBmpPath, file)) { Bitmap bitmap(file); if (bitmap.parseHeaders() == BmpReaderError::Ok) { - Serial.printf("Rendering bmp\n"); + LOG_DBG("THEME", "Rendering bmp"); // Calculate position to center image within the book card int coverX, coverY; @@ -345,7 +346,7 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: // First render: if selected, draw selection indicators now if (bookSelected) { - Serial.printf("Drawing selection\n"); + LOG_DBG("THEME", "Drawing selection"); renderer.drawRect(bookX + 1, bookY + 1, bookWidth - 2, bookHeight - 2); renderer.drawRect(bookX + 2, bookY + 2, bookWidth - 4, bookHeight - 4); } diff --git a/src/main.cpp b/src/main.cpp index d5746acb..5aa9bca8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -203,8 +204,8 @@ void enterDeepSleep() { enterNewActivity(new SleepActivity(renderer, mappedInputManager)); display.deepSleep(); - Serial.printf("[%lu] [ ] Power button press calibration value: %lu ms\n", millis(), t2 - t1); - Serial.printf("[%lu] [ ] Entering deep sleep.\n", millis()); + LOG_DBG("MAIN", "Power button press calibration value: %lu ms", t2 - t1); + LOG_DBG("MAIN", "Entering deep sleep"); powerManager.startDeepSleep(gpio); } @@ -257,7 +258,7 @@ void onGoHome() { void setupDisplayAndFonts() { display.begin(); renderer.begin(); - Serial.printf("[%lu] [ ] Display initialized\n", millis()); + LOG_DBG("MAIN", "Display initialized"); renderer.insertFont(BOOKERLY_14_FONT_ID, bookerly14FontFamily); #ifndef OMIT_FONTS renderer.insertFont(BOOKERLY_12_FONT_ID, bookerly12FontFamily); @@ -276,7 +277,7 @@ void setupDisplayAndFonts() { renderer.insertFont(UI_10_FONT_ID, ui10FontFamily); renderer.insertFont(UI_12_FONT_ID, ui12FontFamily); renderer.insertFont(SMALL_FONT_ID, smallFontFamily); - Serial.printf("[%lu] [ ] Fonts setup\n", millis()); + LOG_DBG("MAIN", "Fonts setup"); } void setup() { @@ -298,7 +299,7 @@ void setup() { // SD Card Initialization // We need 6 open files concurrently when parsing a new chapter if (!Storage.begin()) { - Serial.printf("[%lu] [ ] SD card initialization failed\n", millis()); + LOG_ERR("MAIN", "SD card initialization failed"); setupDisplayAndFonts(); exitActivity(); enterNewActivity(new FullScreenMessageActivity(renderer, mappedInputManager, "SD card error", EpdFontFamily::BOLD)); @@ -313,12 +314,12 @@ void setup() { switch (gpio.getWakeupReason()) { case HalGPIO::WakeupReason::PowerButton: // For normal wakeups, verify power button press duration - Serial.printf("[%lu] [ ] Verifying power button press duration\n", millis()); + LOG_DBG("MAIN", "Verifying power button press duration"); verifyPowerButtonDuration(); break; case HalGPIO::WakeupReason::AfterUSBPower: // If USB power caused a cold boot, go back to sleep - Serial.printf("[%lu] [ ] Wakeup reason: After USB Power\n", millis()); + LOG_DBG("MAIN", "Wakeup reason: After USB Power"); powerManager.startDeepSleep(gpio); break; case HalGPIO::WakeupReason::AfterFlash: @@ -329,7 +330,7 @@ void setup() { } // First serial output only here to avoid timing inconsistencies for power button press duration verification - Serial.printf("[%lu] [ ] Starting CrossPoint version " CROSSPOINT_VERSION "\n", millis()); + LOG_DBG("MAIN", "Starting CrossPoint version " CROSSPOINT_VERSION); setupDisplayAndFonts(); @@ -367,11 +368,27 @@ void loop() { renderer.setFadingFix(SETTINGS.fadingFix); if (Serial && millis() - lastMemPrint >= 10000) { - Serial.printf("[%lu] [MEM] Free: %d bytes, Total: %d bytes, Min Free: %d bytes\n", millis(), ESP.getFreeHeap(), - ESP.getHeapSize(), ESP.getMinFreeHeap()); + LOG_INF("MEM", "Free: %d bytes, Total: %d bytes, Min Free: %d bytes", ESP.getFreeHeap(), ESP.getHeapSize(), + ESP.getMinFreeHeap()); lastMemPrint = millis(); } + // Handle incoming serial commands, + // nb: we use logSerial from logging to avoid deprecation warnings + if (logSerial.available() > 0) { + String line = logSerial.readStringUntil('\n'); + if (line.startsWith("CMD:")) { + String cmd = line.substring(4); + cmd.trim(); + if (cmd == "SCREENSHOT") { + logSerial.printf("SCREENSHOT_START:%d\n", HalDisplay::BUFFER_SIZE); + uint8_t* buf = display.getFrameBuffer(); + logSerial.write(buf, HalDisplay::BUFFER_SIZE); + logSerial.printf("SCREENSHOT_END\n"); + } + } + } + // Check for any user activity (button press or release) or active background work static unsigned long lastActivityTime = millis(); if (gpio.wasAnyPressed() || gpio.wasAnyReleased() || (currentActivity && currentActivity->preventAutoSleep())) { @@ -381,7 +398,7 @@ void loop() { const unsigned long sleepTimeoutMs = SETTINGS.getSleepTimeoutMs(); if (millis() - lastActivityTime >= sleepTimeoutMs) { - Serial.printf("[%lu] [SLP] Auto-sleep triggered after %lu ms of inactivity\n", millis(), sleepTimeoutMs); + LOG_DBG("SLP", "Auto-sleep triggered after %lu ms of inactivity", sleepTimeoutMs); enterDeepSleep(); // This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start return; @@ -403,8 +420,7 @@ void loop() { if (loopDuration > maxLoopDuration) { maxLoopDuration = loopDuration; if (maxLoopDuration > 50) { - Serial.printf("[%lu] [LOOP] New max loop duration: %lu ms (activity: %lu ms)\n", millis(), maxLoopDuration, - activityDuration); + LOG_DBG("LOOP", "New max loop duration: %lu ms (activity: %lu ms)", maxLoopDuration, activityDuration); } } diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index 70658413..3fd3ff87 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -44,7 +45,7 @@ void clearEpubCacheIfNeeded(const String& filePath) { // Only clear cache for .epub files if (StringUtils::checkFileExtension(filePath, ".epub")) { Epub(filePath.c_str(), "/.crosspoint").clearCache(); - Serial.printf("[%lu] [WEB] Cleared epub cache for: %s\n", millis(), filePath.c_str()); + LOG_DBG("WEB", "Cleared epub cache for: %s", filePath.c_str()); } } @@ -89,7 +90,7 @@ CrossPointWebServer::~CrossPointWebServer() { stop(); } void CrossPointWebServer::begin() { if (running) { - Serial.printf("[%lu] [WEB] Web server already running\n", millis()); + LOG_DBG("WEB", "Web server already running"); return; } @@ -99,18 +100,17 @@ void CrossPointWebServer::begin() { const bool isInApMode = (wifiMode & WIFI_MODE_AP) && (WiFi.softAPgetStationNum() >= 0); // AP is running if (!isStaConnected && !isInApMode) { - Serial.printf("[%lu] [WEB] Cannot start webserver - no valid network (mode=%d, status=%d)\n", millis(), wifiMode, - WiFi.status()); + LOG_DBG("WEB", "Cannot start webserver - no valid network (mode=%d, status=%d)", wifiMode, WiFi.status()); return; } // Store AP mode flag for later use (e.g., in handleStatus) apMode = isInApMode; - Serial.printf("[%lu] [WEB] [MEM] Free heap before begin: %d bytes\n", millis(), ESP.getFreeHeap()); - Serial.printf("[%lu] [WEB] Network mode: %s\n", millis(), apMode ? "AP" : "STA"); + LOG_DBG("WEB", "[MEM] Free heap before begin: %d bytes", ESP.getFreeHeap()); + LOG_DBG("WEB", "Network mode: %s", apMode ? "AP" : "STA"); - Serial.printf("[%lu] [WEB] Creating web server on port %d...\n", millis(), port); + LOG_DBG("WEB", "Creating web server on port %d...", port); server.reset(new WebServer(port)); // Disable WiFi sleep to improve responsiveness and prevent 'unreachable' errors. @@ -120,15 +120,15 @@ void CrossPointWebServer::begin() { // Note: WebServer class doesn't have setNoDelay() in the standard ESP32 library. // We rely on disabling WiFi sleep for responsiveness. - Serial.printf("[%lu] [WEB] [MEM] Free heap after WebServer allocation: %d bytes\n", millis(), ESP.getFreeHeap()); + LOG_DBG("WEB", "[MEM] Free heap after WebServer allocation: %d bytes", ESP.getFreeHeap()); if (!server) { - Serial.printf("[%lu] [WEB] Failed to create WebServer!\n", millis()); + LOG_ERR("WEB", "Failed to create WebServer!"); return; } // Setup routes - Serial.printf("[%lu] [WEB] Setting up routes...\n", millis()); + LOG_DBG("WEB", "Setting up routes..."); server->on("/", HTTP_GET, [this] { handleRoot(); }); server->on("/files", HTTP_GET, [this] { handleFileList(); }); @@ -157,43 +157,41 @@ void CrossPointWebServer::begin() { server->on("/api/settings", HTTP_POST, [this] { handlePostSettings(); }); server->onNotFound([this] { handleNotFound(); }); - Serial.printf("[%lu] [WEB] [MEM] Free heap after route setup: %d bytes\n", millis(), ESP.getFreeHeap()); + LOG_DBG("WEB", "[MEM] Free heap after route setup: %d bytes", ESP.getFreeHeap()); server->begin(); // Start WebSocket server for fast binary uploads - Serial.printf("[%lu] [WEB] Starting WebSocket server on port %d...\n", millis(), wsPort); + LOG_DBG("WEB", "Starting WebSocket server on port %d...", wsPort); wsServer.reset(new WebSocketsServer(wsPort)); wsInstance = const_cast(this); wsServer->begin(); wsServer->onEvent(wsEventCallback); - Serial.printf("[%lu] [WEB] WebSocket server started\n", millis()); + LOG_DBG("WEB", "WebSocket server started"); udpActive = udp.begin(LOCAL_UDP_PORT); - Serial.printf("[%lu] [WEB] Discovery UDP %s on port %d\n", millis(), udpActive ? "enabled" : "failed", - LOCAL_UDP_PORT); + LOG_DBG("WEB", "Discovery UDP %s on port %d", udpActive ? "enabled" : "failed", LOCAL_UDP_PORT); running = true; - Serial.printf("[%lu] [WEB] Web server started on port %d\n", millis(), port); + LOG_DBG("WEB", "Web server started on port %d", port); // Show the correct IP based on network mode const String ipAddr = apMode ? WiFi.softAPIP().toString() : WiFi.localIP().toString(); - Serial.printf("[%lu] [WEB] Access at http://%s/\n", millis(), ipAddr.c_str()); - Serial.printf("[%lu] [WEB] WebSocket at ws://%s:%d/\n", millis(), ipAddr.c_str(), wsPort); - Serial.printf("[%lu] [WEB] [MEM] Free heap after server.begin(): %d bytes\n", millis(), ESP.getFreeHeap()); + LOG_DBG("WEB", "Access at http://%s/", ipAddr.c_str()); + LOG_DBG("WEB", "WebSocket at ws://%s:%d/", ipAddr.c_str(), wsPort); + LOG_DBG("WEB", "[MEM] Free heap after server.begin(): %d bytes", ESP.getFreeHeap()); } void CrossPointWebServer::stop() { if (!running || !server) { - Serial.printf("[%lu] [WEB] stop() called but already stopped (running=%d, server=%p)\n", millis(), running, - server.get()); + LOG_DBG("WEB", "stop() called but already stopped (running=%d, server=%p)", running, server.get()); return; } - Serial.printf("[%lu] [WEB] STOP INITIATED - setting running=false first\n", millis()); + LOG_DBG("WEB", "STOP INITIATED - setting running=false first"); running = false; // Set this FIRST to prevent handleClient from using server - Serial.printf("[%lu] [WEB] [MEM] Free heap before stop: %d bytes\n", millis(), ESP.getFreeHeap()); + LOG_DBG("WEB", "[MEM] Free heap before stop: %d bytes", ESP.getFreeHeap()); // Close any in-progress WebSocket upload if (wsUploadInProgress && wsUploadFile) { @@ -203,11 +201,11 @@ void CrossPointWebServer::stop() { // Stop WebSocket server if (wsServer) { - Serial.printf("[%lu] [WEB] Stopping WebSocket server...\n", millis()); + LOG_DBG("WEB", "Stopping WebSocket server..."); wsServer->close(); wsServer.reset(); wsInstance = nullptr; - Serial.printf("[%lu] [WEB] WebSocket server stopped\n", millis()); + LOG_DBG("WEB", "WebSocket server stopped"); } if (udpActive) { @@ -219,18 +217,18 @@ void CrossPointWebServer::stop() { delay(20); server->stop(); - Serial.printf("[%lu] [WEB] [MEM] Free heap after server->stop(): %d bytes\n", millis(), ESP.getFreeHeap()); + LOG_DBG("WEB", "[MEM] Free heap after server->stop(): %d bytes", ESP.getFreeHeap()); // Brief delay before deletion delay(10); server.reset(); - Serial.printf("[%lu] [WEB] Web server stopped and deleted\n", millis()); - Serial.printf("[%lu] [WEB] [MEM] Free heap after delete server: %d bytes\n", millis(), ESP.getFreeHeap()); + LOG_DBG("WEB", "Web server stopped and deleted"); + LOG_DBG("WEB", "[MEM] Free heap after delete server: %d bytes", ESP.getFreeHeap()); // Note: Static upload variables (uploadFileName, uploadPath, uploadError) are declared // later in the file and will be cleared when they go out of scope or on next upload - Serial.printf("[%lu] [WEB] [MEM] Free heap final: %d bytes\n", millis(), ESP.getFreeHeap()); + LOG_DBG("WEB", "[MEM] Free heap final: %d bytes", ESP.getFreeHeap()); } void CrossPointWebServer::handleClient() { @@ -243,13 +241,13 @@ void CrossPointWebServer::handleClient() { // Double-check server pointer is valid if (!server) { - Serial.printf("[%lu] [WEB] WARNING: handleClient called with null server!\n", millis()); + LOG_DBG("WEB", "WARNING: handleClient called with null server!"); return; } // Print debug every 10 seconds to confirm handleClient is being called if (millis() - lastDebugPrint > 10000) { - Serial.printf("[%lu] [WEB] handleClient active, server running on port %d\n", millis(), port); + LOG_DBG("WEB", "handleClient active, server running on port %d", port); lastDebugPrint = millis(); } @@ -297,7 +295,7 @@ CrossPointWebServer::WsUploadStatus CrossPointWebServer::getWsUploadStatus() con void CrossPointWebServer::handleRoot() const { server->send(200, "text/html", HomePageHtml); - Serial.printf("[%lu] [WEB] Served root page\n", millis()); + LOG_DBG("WEB", "Served root page"); } void CrossPointWebServer::handleNotFound() const { @@ -326,17 +324,17 @@ void CrossPointWebServer::handleStatus() const { void CrossPointWebServer::scanFiles(const char* path, const std::function& callback) const { FsFile root = Storage.open(path); if (!root) { - Serial.printf("[%lu] [WEB] Failed to open directory: %s\n", millis(), path); + LOG_DBG("WEB", "Failed to open directory: %s", path); return; } if (!root.isDirectory()) { - Serial.printf("[%lu] [WEB] Not a directory: %s\n", millis(), path); + LOG_DBG("WEB", "Not a directory: %s", path); root.close(); return; } - Serial.printf("[%lu] [WEB] Scanning files in: %s\n", millis(), path); + LOG_DBG("WEB", "Scanning files in: %s", path); FsFile file = root.openNextFile(); char name[500]; @@ -422,7 +420,7 @@ void CrossPointWebServer::handleFileListData() const { const size_t written = serializeJson(doc, output, outputSize); if (written >= outputSize) { // JSON output truncated; skip this entry to avoid sending malformed JSON - Serial.printf("[%lu] [WEB] Skipping file entry with oversized JSON for name: %s\n", millis(), info.name.c_str()); + LOG_DBG("WEB", "Skipping file entry with oversized JSON for name: %s", info.name.c_str()); return; } @@ -436,7 +434,7 @@ void CrossPointWebServer::handleFileListData() const { server->sendContent("]"); // End of streamed response, empty chunk to signal client server->sendContent(""); - Serial.printf("[%lu] [WEB] Served file listing page for path: %s\n", millis(), currentPath.c_str()); + LOG_DBG("WEB", "Served file listing page for path: %s", currentPath.c_str()); } void CrossPointWebServer::handleDownload() const { @@ -517,8 +515,7 @@ static bool flushUploadBuffer(CrossPointWebServer::UploadState& state) { esp_task_wdt_reset(); // Reset watchdog after SD write if (written != state.bufferPos) { - Serial.printf("[%lu] [WEB] [UPLOAD] Buffer flush failed: expected %d, wrote %d\n", millis(), state.bufferPos, - written); + LOG_DBG("WEB", "[UPLOAD] Buffer flush failed: expected %d, wrote %d", state.bufferPos, written); state.bufferPos = 0; return false; } @@ -535,7 +532,7 @@ void CrossPointWebServer::handleUpload(UploadState& state) const { // Safety check: ensure server is still valid if (!running || !server) { - Serial.printf("[%lu] [WEB] [UPLOAD] ERROR: handleUpload called but server not running!\n", millis()); + LOG_DBG("WEB", "[UPLOAD] ERROR: handleUpload called but server not running!"); return; } @@ -572,8 +569,8 @@ void CrossPointWebServer::handleUpload(UploadState& state) const { state.path = "/"; } - Serial.printf("[%lu] [WEB] [UPLOAD] START: %s to path: %s\n", millis(), state.fileName.c_str(), state.path.c_str()); - Serial.printf("[%lu] [WEB] [UPLOAD] Free heap: %d bytes\n", millis(), ESP.getFreeHeap()); + LOG_DBG("WEB", "[UPLOAD] START: %s to path: %s", state.fileName.c_str(), state.path.c_str()); + LOG_DBG("WEB", "[UPLOAD] Free heap: %d bytes", ESP.getFreeHeap()); // Create file path String filePath = state.path; @@ -583,7 +580,7 @@ void CrossPointWebServer::handleUpload(UploadState& state) const { // Check if file already exists - SD operations can be slow esp_task_wdt_reset(); if (Storage.exists(filePath.c_str())) { - Serial.printf("[%lu] [WEB] [UPLOAD] Overwriting existing file: %s\n", millis(), filePath.c_str()); + LOG_DBG("WEB", "[UPLOAD] Overwriting existing file: %s", filePath.c_str()); esp_task_wdt_reset(); Storage.remove(filePath.c_str()); } @@ -592,12 +589,12 @@ void CrossPointWebServer::handleUpload(UploadState& state) const { esp_task_wdt_reset(); if (!Storage.openFileForWrite("WEB", filePath, state.file)) { state.error = "Failed to create file on SD card"; - Serial.printf("[%lu] [WEB] [UPLOAD] FAILED to create file: %s\n", millis(), filePath.c_str()); + LOG_DBG("WEB", "[UPLOAD] FAILED to create file: %s", filePath.c_str()); return; } esp_task_wdt_reset(); - Serial.printf("[%lu] [WEB] [UPLOAD] File created successfully: %s\n", millis(), filePath.c_str()); + LOG_DBG("WEB", "[UPLOAD] File created successfully: %s", filePath.c_str()); } else if (upload.status == UPLOAD_FILE_WRITE) { if (state.file && state.error.isEmpty()) { // Buffer incoming data and flush when buffer is full @@ -630,8 +627,8 @@ void CrossPointWebServer::handleUpload(UploadState& state) const { if (state.size - lastLoggedSize >= 102400) { const unsigned long elapsed = millis() - uploadStartTime; const float kbps = (elapsed > 0) ? (state.size / 1024.0) / (elapsed / 1000.0) : 0; - Serial.printf("[%lu] [WEB] [UPLOAD] %d bytes (%.1f KB), %.1f KB/s, %d writes\n", millis(), state.size, - state.size / 1024.0, kbps, writeCount); + LOG_DBG("WEB", "[UPLOAD] %d bytes (%.1f KB), %.1f KB/s, %d writes", state.size, state.size / 1024.0, kbps, + writeCount); lastLoggedSize = state.size; } } @@ -648,10 +645,10 @@ void CrossPointWebServer::handleUpload(UploadState& state) const { const unsigned long elapsed = millis() - uploadStartTime; const float avgKbps = (elapsed > 0) ? (state.size / 1024.0) / (elapsed / 1000.0) : 0; const float writePercent = (elapsed > 0) ? (totalWriteTime * 100.0 / elapsed) : 0; - Serial.printf("[%lu] [WEB] [UPLOAD] Complete: %s (%d bytes in %lu ms, avg %.1f KB/s)\n", millis(), - state.fileName.c_str(), state.size, elapsed, avgKbps); - Serial.printf("[%lu] [WEB] [UPLOAD] Diagnostics: %d writes, total write time: %lu ms (%.1f%%)\n", millis(), - writeCount, totalWriteTime, writePercent); + LOG_DBG("WEB", "[UPLOAD] Complete: %s (%d bytes in %lu ms, avg %.1f KB/s)", state.fileName.c_str(), state.size, + elapsed, avgKbps); + LOG_DBG("WEB", "[UPLOAD] Diagnostics: %d writes, total write time: %lu ms (%.1f%%)", writeCount, totalWriteTime, + writePercent); // Clear epub cache to prevent stale metadata issues when overwriting files String filePath = state.path; @@ -671,7 +668,7 @@ void CrossPointWebServer::handleUpload(UploadState& state) const { Storage.remove(filePath.c_str()); } state.error = "Upload aborted"; - Serial.printf("[%lu] [WEB] Upload aborted\n", millis()); + LOG_DBG("WEB", "Upload aborted"); } } @@ -716,7 +713,7 @@ void CrossPointWebServer::handleCreateFolder() const { if (!folderPath.endsWith("/")) folderPath += "/"; folderPath += folderName; - Serial.printf("[%lu] [WEB] Creating folder: %s\n", millis(), folderPath.c_str()); + LOG_DBG("WEB", "Creating folder: %s", folderPath.c_str()); // Check if already exists if (Storage.exists(folderPath.c_str())) { @@ -726,10 +723,10 @@ void CrossPointWebServer::handleCreateFolder() const { // Create the folder if (Storage.mkdir(folderPath.c_str())) { - Serial.printf("[%lu] [WEB] Folder created successfully: %s\n", millis(), folderPath.c_str()); + LOG_DBG("WEB", "Folder created successfully: %s", folderPath.c_str()); server->send(200, "text/plain", "Folder created: " + folderName); } else { - Serial.printf("[%lu] [WEB] Failed to create folder: %s\n", millis(), folderPath.c_str()); + LOG_DBG("WEB", "Failed to create folder: %s", folderPath.c_str()); server->send(500, "text/plain", "Failed to create folder"); } } @@ -808,10 +805,10 @@ void CrossPointWebServer::handleRename() const { file.close(); if (success) { - Serial.printf("[%lu] [WEB] Renamed file: %s -> %s\n", millis(), itemPath.c_str(), newPath.c_str()); + LOG_DBG("WEB", "Renamed file: %s -> %s", itemPath.c_str(), newPath.c_str()); server->send(200, "text/plain", "Renamed successfully"); } else { - Serial.printf("[%lu] [WEB] Failed to rename file: %s -> %s\n", millis(), itemPath.c_str(), newPath.c_str()); + LOG_ERR("WEB", "Failed to rename file: %s -> %s", itemPath.c_str(), newPath.c_str()); server->send(500, "text/plain", "Failed to rename file"); } } @@ -901,10 +898,10 @@ void CrossPointWebServer::handleMove() const { file.close(); if (success) { - Serial.printf("[%lu] [WEB] Moved file: %s -> %s\n", millis(), itemPath.c_str(), newPath.c_str()); + LOG_DBG("WEB", "Moved file: %s -> %s", itemPath.c_str(), newPath.c_str()); server->send(200, "text/plain", "Moved successfully"); } else { - Serial.printf("[%lu] [WEB] Failed to move file: %s -> %s\n", millis(), itemPath.c_str(), newPath.c_str()); + LOG_ERR("WEB", "Failed to move file: %s -> %s", itemPath.c_str(), newPath.c_str()); server->send(500, "text/plain", "Failed to move file"); } } @@ -935,7 +932,7 @@ void CrossPointWebServer::handleDelete() const { // Check if item starts with a dot (hidden/system file) if (itemName.startsWith(".")) { - Serial.printf("[%lu] [WEB] Delete rejected - hidden/system item: %s\n", millis(), itemPath.c_str()); + LOG_DBG("WEB", "Delete rejected - hidden/system item: %s", itemPath.c_str()); server->send(403, "text/plain", "Cannot delete system files"); return; } @@ -943,7 +940,7 @@ void CrossPointWebServer::handleDelete() const { // Check against explicitly protected items for (size_t i = 0; i < HIDDEN_ITEMS_COUNT; i++) { if (itemName.equals(HIDDEN_ITEMS[i])) { - Serial.printf("[%lu] [WEB] Delete rejected - protected item: %s\n", millis(), itemPath.c_str()); + LOG_DBG("WEB", "Delete rejected - protected item: %s", itemPath.c_str()); server->send(403, "text/plain", "Cannot delete protected items"); return; } @@ -951,12 +948,12 @@ void CrossPointWebServer::handleDelete() const { // Check if item exists if (!Storage.exists(itemPath.c_str())) { - Serial.printf("[%lu] [WEB] Delete failed - item not found: %s\n", millis(), itemPath.c_str()); + LOG_DBG("WEB", "Delete failed - item not found: %s", itemPath.c_str()); server->send(404, "text/plain", "Item not found"); return; } - Serial.printf("[%lu] [WEB] Attempting to delete %s: %s\n", millis(), itemType.c_str(), itemPath.c_str()); + LOG_DBG("WEB", "Attempting to delete %s: %s", itemType.c_str(), itemPath.c_str()); bool success = false; @@ -970,7 +967,7 @@ void CrossPointWebServer::handleDelete() const { // Folder is not empty entry.close(); dir.close(); - Serial.printf("[%lu] [WEB] Delete failed - folder not empty: %s\n", millis(), itemPath.c_str()); + LOG_DBG("WEB", "Delete failed - folder not empty: %s", itemPath.c_str()); server->send(400, "text/plain", "Folder is not empty. Delete contents first."); return; } @@ -983,17 +980,17 @@ void CrossPointWebServer::handleDelete() const { } if (success) { - Serial.printf("[%lu] [WEB] Successfully deleted: %s\n", millis(), itemPath.c_str()); + LOG_DBG("WEB", "Successfully deleted: %s", itemPath.c_str()); server->send(200, "text/plain", "Deleted successfully"); } else { - Serial.printf("[%lu] [WEB] Failed to delete: %s\n", millis(), itemPath.c_str()); + LOG_ERR("WEB", "Failed to delete: %s", itemPath.c_str()); server->send(500, "text/plain", "Failed to delete item"); } } void CrossPointWebServer::handleSettingsPage() const { server->send(200, "text/html", SettingsPageHtml); - Serial.printf("[%lu] [WEB] Served settings page\n", millis()); + LOG_DBG("WEB", "Served settings page"); } void CrossPointWebServer::handleGetSettings() const { @@ -1062,7 +1059,7 @@ void CrossPointWebServer::handleGetSettings() const { const size_t written = serializeJson(doc, output, outputSize); if (written >= outputSize) { - Serial.printf("[%lu] [WEB] Skipping oversized setting JSON for: %s\n", millis(), s.key); + LOG_DBG("WEB", "Skipping oversized setting JSON for: %s", s.key); continue; } @@ -1076,7 +1073,7 @@ void CrossPointWebServer::handleGetSettings() const { server->sendContent("]"); server->sendContent(""); - Serial.printf("[%lu] [WEB] Served settings API\n", millis()); + LOG_DBG("WEB", "Served settings API"); } void CrossPointWebServer::handlePostSettings() { @@ -1149,7 +1146,7 @@ void CrossPointWebServer::handlePostSettings() { SETTINGS.saveToFile(); - Serial.printf("[%lu] [WEB] Applied %d setting(s)\n", millis(), applied); + LOG_DBG("WEB", "Applied %d setting(s)", applied); server->send(200, "text/plain", String("Applied ") + String(applied) + " setting(s)"); } @@ -1169,7 +1166,7 @@ void CrossPointWebServer::wsEventCallback(uint8_t num, WStype_t type, uint8_t* p void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) { switch (type) { case WStype_DISCONNECTED: - Serial.printf("[%lu] [WS] Client %u disconnected\n", millis(), num); + LOG_DBG("WS", "Client %u disconnected", num); // Clean up any in-progress upload if (wsUploadInProgress && wsUploadFile) { wsUploadFile.close(); @@ -1178,20 +1175,20 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t* if (!filePath.endsWith("/")) filePath += "/"; filePath += wsUploadFileName; Storage.remove(filePath.c_str()); - Serial.printf("[%lu] [WS] Deleted incomplete upload: %s\n", millis(), filePath.c_str()); + LOG_DBG("WS", "Deleted incomplete upload: %s", filePath.c_str()); } wsUploadInProgress = false; break; case WStype_CONNECTED: { - Serial.printf("[%lu] [WS] Client %u connected\n", millis(), num); + LOG_DBG("WS", "Client %u connected", num); break; } case WStype_TEXT: { // Parse control messages String msg = String((char*)payload); - Serial.printf("[%lu] [WS] Text from client %u: %s\n", millis(), num, msg.c_str()); + LOG_DBG("WS", "Text from client %u: %s", num, msg.c_str()); if (msg.startsWith("START:")) { // Parse: START::: @@ -1216,8 +1213,8 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t* if (!filePath.endsWith("/")) filePath += "/"; filePath += wsUploadFileName; - Serial.printf("[%lu] [WS] Starting upload: %s (%d bytes) to %s\n", millis(), wsUploadFileName.c_str(), - wsUploadSize, filePath.c_str()); + LOG_DBG("WS", "Starting upload: %s (%d bytes) to %s", wsUploadFileName.c_str(), wsUploadSize, + filePath.c_str()); // Check if file exists and remove it esp_task_wdt_reset(); @@ -1283,8 +1280,8 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t* unsigned long elapsed = millis() - wsUploadStartTime; float kbps = (elapsed > 0) ? (wsUploadSize / 1024.0) / (elapsed / 1000.0) : 0; - Serial.printf("[%lu] [WS] Upload complete: %s (%d bytes in %lu ms, %.1f KB/s)\n", millis(), - wsUploadFileName.c_str(), wsUploadSize, elapsed, kbps); + LOG_DBG("WS", "Upload complete: %s (%d bytes in %lu ms, %.1f KB/s)", wsUploadFileName.c_str(), wsUploadSize, + elapsed, kbps); // Clear epub cache to prevent stale metadata issues when overwriting files String filePath = wsUploadPath; diff --git a/src/network/HttpDownloader.cpp b/src/network/HttpDownloader.cpp index 8ca07b2b..dff92e0e 100644 --- a/src/network/HttpDownloader.cpp +++ b/src/network/HttpDownloader.cpp @@ -1,7 +1,7 @@ #include "HttpDownloader.h" #include -#include +#include #include #include #include @@ -25,7 +25,7 @@ bool HttpDownloader::fetchUrl(const std::string& url, Stream& outContent) { } HTTPClient http; - Serial.printf("[%lu] [HTTP] Fetching: %s\n", millis(), url.c_str()); + LOG_DBG("HTTP", "Fetching: %s", url.c_str()); http.begin(*client, url.c_str()); http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); @@ -40,7 +40,7 @@ bool HttpDownloader::fetchUrl(const std::string& url, Stream& outContent) { const int httpCode = http.GET(); if (httpCode != HTTP_CODE_OK) { - Serial.printf("[%lu] [HTTP] Fetch failed: %d\n", millis(), httpCode); + LOG_ERR("HTTP", "Fetch failed: %d", httpCode); http.end(); return false; } @@ -49,7 +49,7 @@ bool HttpDownloader::fetchUrl(const std::string& url, Stream& outContent) { http.end(); - Serial.printf("[%lu] [HTTP] Fetch success\n", millis()); + LOG_DBG("HTTP", "Fetch success"); return true; } @@ -75,8 +75,8 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string& } HTTPClient http; - Serial.printf("[%lu] [HTTP] Downloading: %s\n", millis(), url.c_str()); - Serial.printf("[%lu] [HTTP] Destination: %s\n", millis(), destPath.c_str()); + LOG_DBG("HTTP", "Downloading: %s", url.c_str()); + LOG_DBG("HTTP", "Destination: %s", destPath.c_str()); http.begin(*client, url.c_str()); http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); @@ -91,13 +91,13 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string& const int httpCode = http.GET(); if (httpCode != HTTP_CODE_OK) { - Serial.printf("[%lu] [HTTP] Download failed: %d\n", millis(), httpCode); + LOG_ERR("HTTP", "Download failed: %d", httpCode); http.end(); return HTTP_ERROR; } const size_t contentLength = http.getSize(); - Serial.printf("[%lu] [HTTP] Content-Length: %zu\n", millis(), contentLength); + LOG_DBG("HTTP", "Content-Length: %zu", contentLength); // Remove existing file if present if (Storage.exists(destPath.c_str())) { @@ -107,7 +107,7 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string& // Open file for writing FsFile file; if (!Storage.openFileForWrite("HTTP", destPath.c_str(), file)) { - Serial.printf("[%lu] [HTTP] Failed to open file for writing\n", millis()); + LOG_ERR("HTTP", "Failed to open file for writing"); http.end(); return FILE_ERROR; } @@ -115,7 +115,7 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string& // Get the stream for chunked reading WiFiClient* stream = http.getStreamPtr(); if (!stream) { - Serial.printf("[%lu] [HTTP] Failed to get stream\n", millis()); + LOG_ERR("HTTP", "Failed to get stream"); file.close(); Storage.remove(destPath.c_str()); http.end(); @@ -143,7 +143,7 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string& const size_t written = file.write(buffer, bytesRead); if (written != bytesRead) { - Serial.printf("[%lu] [HTTP] Write failed: wrote %zu of %zu bytes\n", millis(), written, bytesRead); + LOG_ERR("HTTP", "Write failed: wrote %zu of %zu bytes", written, bytesRead); file.close(); Storage.remove(destPath.c_str()); http.end(); @@ -160,11 +160,11 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string& file.close(); http.end(); - Serial.printf("[%lu] [HTTP] Downloaded %zu bytes\n", millis(), downloaded); + LOG_DBG("HTTP", "Downloaded %zu bytes", downloaded); // Verify download size if known if (contentLength > 0 && downloaded != contentLength) { - Serial.printf("[%lu] [HTTP] Size mismatch: got %zu, expected %zu\n", millis(), downloaded, contentLength); + LOG_ERR("HTTP", "Size mismatch: got %zu, expected %zu", downloaded, contentLength); Storage.remove(destPath.c_str()); return HTTP_ERROR; } diff --git a/src/network/OtaUpdater.cpp b/src/network/OtaUpdater.cpp index 3c2dda8a..80138e6a 100644 --- a/src/network/OtaUpdater.cpp +++ b/src/network/OtaUpdater.cpp @@ -1,6 +1,7 @@ #include "OtaUpdater.h" #include +#include #include "esp_http_client.h" #include "esp_https_ota.h" @@ -39,7 +40,7 @@ esp_err_t event_handler(esp_http_client_event_t* event) { local_buf = static_cast(calloc(content_len + 1, sizeof(char))); output_len = 0; if (local_buf == NULL) { - Serial.printf("[%lu] [OTA] HTTP Client Out of Memory Failed, Allocation %d\n", millis(), content_len); + LOG_ERR("OTA", "HTTP Client Out of Memory Failed, Allocation %d", content_len); return ESP_ERR_NO_MEM; } } @@ -52,7 +53,7 @@ esp_err_t event_handler(esp_http_client_event_t* event) { /* Code might be hits here, It happened once (for version checking) but I need more logs to handle that */ int chunked_len; esp_http_client_get_chunk_length(event->client, &chunked_len); - Serial.printf("[%lu] [OTA] esp_http_client_is_chunked_response failed, chunked_len: %d\n", millis(), chunked_len); + LOG_DBG("OTA", "esp_http_client_is_chunked_response failed, chunked_len: %d", chunked_len); } return ESP_OK; @@ -88,20 +89,20 @@ OtaUpdater::OtaUpdaterError OtaUpdater::checkForUpdate() { esp_http_client_handle_t client_handle = esp_http_client_init(&client_config); if (!client_handle) { - Serial.printf("[%lu] [OTA] HTTP Client Handle Failed\n", millis()); + LOG_ERR("OTA", "HTTP Client Handle Failed"); return INTERNAL_UPDATE_ERROR; } esp_err = esp_http_client_set_header(client_handle, "User-Agent", "CrossPoint-ESP32-" CROSSPOINT_VERSION); if (esp_err != ESP_OK) { - Serial.printf("[%lu] [OTA] esp_http_client_set_header Failed : %s\n", millis(), esp_err_to_name(esp_err)); + LOG_ERR("OTA", "esp_http_client_set_header Failed : %s", esp_err_to_name(esp_err)); esp_http_client_cleanup(client_handle); return INTERNAL_UPDATE_ERROR; } esp_err = esp_http_client_perform(client_handle); if (esp_err != ESP_OK) { - Serial.printf("[%lu] [OTA] esp_http_client_perform Failed : %s\n", millis(), esp_err_to_name(esp_err)); + LOG_ERR("OTA", "esp_http_client_perform Failed : %s", esp_err_to_name(esp_err)); esp_http_client_cleanup(client_handle); return HTTP_ERROR; } @@ -109,7 +110,7 @@ OtaUpdater::OtaUpdaterError OtaUpdater::checkForUpdate() { /* esp_http_client_close will be called inside cleanup as well*/ esp_err = esp_http_client_cleanup(client_handle); if (esp_err != ESP_OK) { - Serial.printf("[%lu] [OTA] esp_http_client_cleanupp Failed : %s\n", millis(), esp_err_to_name(esp_err)); + LOG_ERR("OTA", "esp_http_client_cleanup Failed : %s", esp_err_to_name(esp_err)); return INTERNAL_UPDATE_ERROR; } @@ -119,17 +120,17 @@ OtaUpdater::OtaUpdaterError OtaUpdater::checkForUpdate() { filter["assets"][0]["size"] = true; const DeserializationError error = deserializeJson(doc, local_buf, DeserializationOption::Filter(filter)); if (error) { - Serial.printf("[%lu] [OTA] JSON parse failed: %s\n", millis(), error.c_str()); + LOG_ERR("OTA", "JSON parse failed: %s", error.c_str()); return JSON_PARSE_ERROR; } if (!doc["tag_name"].is()) { - Serial.printf("[%lu] [OTA] No tag_name found\n", millis()); + LOG_ERR("OTA", "No tag_name found"); return JSON_PARSE_ERROR; } if (!doc["assets"].is()) { - Serial.printf("[%lu] [OTA] No assets found\n", millis()); + LOG_ERR("OTA", "No assets found"); return JSON_PARSE_ERROR; } @@ -146,11 +147,11 @@ OtaUpdater::OtaUpdaterError OtaUpdater::checkForUpdate() { } if (!updateAvailable) { - Serial.printf("[%lu] [OTA] No firmware.bin asset found\n", millis()); + LOG_ERR("OTA", "No firmware.bin asset found"); return NO_UPDATE; } - Serial.printf("[%lu] [OTA] Found update: %s\n", millis(), latestVersion.c_str()); + LOG_DBG("OTA", "Found update: %s", latestVersion.c_str()); return OK; } @@ -233,7 +234,7 @@ OtaUpdater::OtaUpdaterError OtaUpdater::installUpdate() { esp_err = esp_https_ota_begin(&ota_config, &ota_handle); if (esp_err != ESP_OK) { - Serial.printf("[%lu] [OTA] HTTP OTA Begin Failed: %s\n", millis(), esp_err_to_name(esp_err)); + LOG_DBG("OTA", "HTTP OTA Begin Failed: %s", esp_err_to_name(esp_err)); return INTERNAL_UPDATE_ERROR; } @@ -249,24 +250,23 @@ OtaUpdater::OtaUpdaterError OtaUpdater::installUpdate() { esp_wifi_set_ps(WIFI_PS_MIN_MODEM); if (esp_err != ESP_OK) { - Serial.printf("[%lu] [OTA] esp_https_ota_perform Failed: %s\n", millis(), esp_err_to_name(esp_err)); + LOG_ERR("OTA", "esp_https_ota_perform Failed: %s", esp_err_to_name(esp_err)); esp_https_ota_finish(ota_handle); return HTTP_ERROR; } if (!esp_https_ota_is_complete_data_received(ota_handle)) { - Serial.printf("[%lu] [OTA] esp_https_ota_is_complete_data_received Failed: %s\n", millis(), - esp_err_to_name(esp_err)); + LOG_ERR("OTA", "esp_https_ota_is_complete_data_received Failed: %s", esp_err_to_name(esp_err)); esp_https_ota_finish(ota_handle); return INTERNAL_UPDATE_ERROR; } esp_err = esp_https_ota_finish(ota_handle); if (esp_err != ESP_OK) { - Serial.printf("[%lu] [OTA] esp_https_ota_finish Failed: %s\n", millis(), esp_err_to_name(esp_err)); + LOG_ERR("OTA", "esp_https_ota_finish Failed: %s", esp_err_to_name(esp_err)); return INTERNAL_UPDATE_ERROR; } - Serial.printf("[%lu] [OTA] Update completed\n", millis()); + LOG_INF("OTA", "Update completed"); return OK; } diff --git a/src/util/BookSettings.cpp b/src/util/BookSettings.cpp index ef196a05..7c3922ad 100644 --- a/src/util/BookSettings.cpp +++ b/src/util/BookSettings.cpp @@ -1,7 +1,7 @@ #include "BookSettings.h" #include -#include +#include #include namespace { @@ -37,15 +37,15 @@ BookSettings BookSettings::load(const std::string& cachePath) { } while (false); f.close(); - Serial.printf("[%lu] [BST] Loaded book settings from %s (letterboxFill=%d)\n", millis(), filePath(cachePath).c_str(), - settings.letterboxFillOverride); + LOG_DBG("BST", "Loaded book settings from %s (letterboxFill=%d)", filePath(cachePath).c_str(), + settings.letterboxFillOverride); return settings; } bool BookSettings::save(const std::string& cachePath, const BookSettings& settings) { FsFile f; if (!Storage.openFileForWrite("BST", filePath(cachePath), f)) { - Serial.printf("[%lu] [BST] Could not save book settings!\n", millis()); + LOG_ERR("BST", "Could not save book settings!"); return false; } @@ -55,6 +55,6 @@ bool BookSettings::save(const std::string& cachePath, const BookSettings& settin // New fields added here f.close(); - Serial.printf("[%lu] [BST] Saved book settings to %s\n", millis(), filePath(cachePath).c_str()); + LOG_DBG("BST", "Saved book settings to %s", filePath(cachePath).c_str()); return true; } diff --git a/src/util/BookmarkStore.cpp b/src/util/BookmarkStore.cpp index a370a481..bdedfd29 100644 --- a/src/util/BookmarkStore.cpp +++ b/src/util/BookmarkStore.cpp @@ -1,6 +1,7 @@ #include "BookmarkStore.h" #include +#include #include @@ -78,7 +79,7 @@ std::vector BookmarkStore::load(const std::string& cachePath) { bool BookmarkStore::save(const std::string& cachePath, const std::vector& bookmarks) { FsFile f; if (!Storage.openFileForWrite("BKM", filePath(cachePath), f)) { - Serial.printf("[%lu] [BKM] Could not save bookmarks!\n", millis()); + LOG_ERR("BKM", "Could not save bookmarks!"); return false; } @@ -107,7 +108,7 @@ bool BookmarkStore::save(const std::string& cachePath, const std::vector