release: ef-1.0.5 - stability, memory, and upstream merges
All checks were successful
CI / build (push) Successful in 4m51s
Compile Release / build-release (push) Successful in 1m18s

Webserver: JSON batching, removed MD5 blocking, simplified flow control
Memory: QR code caching, WiFi scan optimization, cover buffer leak fix
EPUB: Fixed errant underlining before styled inline elements
Flash screen: Version string overflow fix, half refresh for cleaner display

Upstream merges:
- PR #522: HAL abstraction layer (HalDisplay, HalGPIO)
- PR #603: Sunlight fading fix toggle in Display settings
This commit is contained in:
cottongin 2026-01-30 23:13:08 -05:00
parent 520a0cb124
commit fbe7d2feb4
No known key found for this signature in database
GPG Key ID: 0ECC91FE4655C262
17 changed files with 108 additions and 106 deletions

View File

@ -32,9 +32,16 @@ Base: CrossPoint Reader 0.15.0
- **Display Quality**: Changed flashing screen to half refresh for cleaner appearance
- **Timing**: Adjusted pre-flash script timing for half refresh completion
### Upstream Merges
- **PR #522 - HAL Abstraction Layer**: Merged hardware abstraction layer refactor introducing `HalDisplay` and `HalGPIO` classes, decoupling application code from direct hardware access
- **PR #603 - Sunlight Fading Fix**: Added user-toggleable setting to turn off display between refreshes, mitigating the sunlight fading issue on e-ink displays
- New "Sunlight Fading Fix" toggle in Display settings (OFF/ON)
- Passes `turnOffScreen` parameter through display stack when enabled
### Files Changed
- `src/main.cpp` - flash screen fixes, cover buffer free on File Transfer entry
- `src/main.cpp` - flash screen fixes, cover buffer free on File Transfer entry, fading fix integration
- `scripts/pre_flash.py` - timing adjustments for full refresh
- `src/network/CrossPointWebServer.cpp` - JSON batching, removed MD5 from listings
- `src/network/CrossPointWebServer.h` - removed md5 from FileInfo, simplified sendContentSafe
@ -42,6 +49,16 @@ Base: CrossPoint Reader 0.15.0
- `src/activities/network/CrossPointWebServerActivity.h` - QR code cache members
- `src/activities/network/WifiSelectionActivity.cpp` - WiFi scan memory optimization
- `lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp` - flush buffer before style changes
- `lib/hal/HalDisplay.h` - new HAL abstraction for display (PR #522), turnOffScreen parameter (PR #603)
- `lib/hal/HalDisplay.cpp` - HAL display implementation with fading fix passthrough
- `lib/hal/HalGPIO.h` - new HAL abstraction for GPIO (PR #522)
- `lib/hal/HalGPIO.cpp` - HAL GPIO implementation
- `lib/GfxRenderer/GfxRenderer.h` - updated for HAL layer, added fadingFix member
- `lib/GfxRenderer/GfxRenderer.cpp` - updated for HAL layer, passes fadingFix to display
- `src/CrossPointSettings.h` - added fadingFix setting
- `src/CrossPointSettings.cpp` - fadingFix persistence
- `src/activities/settings/SettingsActivity.cpp` - added Sunlight Fading Fix toggle
- `open-x4-sdk` - updated submodule with turnOffScreen support in EInkDisplay
---

View File

@ -380,19 +380,15 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const
// *** CRITICAL STEP: CONSUME DATA USING MOVE + ERASE ***
// Move first lineWordCount elements from words into lineWords
std::vector<std::string> lineWords(
std::make_move_iterator(words.begin()),
std::vector<std::string> lineWords(std::make_move_iterator(words.begin()),
std::make_move_iterator(words.begin() + lineWordCount));
words.erase(words.begin(), words.begin() + lineWordCount);
std::vector<EpdFontFamily::Style> lineWordStyles(
std::make_move_iterator(wordStyles.begin()),
std::vector<EpdFontFamily::Style> lineWordStyles(std::make_move_iterator(wordStyles.begin()),
std::make_move_iterator(wordStyles.begin() + lineWordCount));
wordStyles.erase(wordStyles.begin(), wordStyles.begin() + lineWordCount);
std::vector<bool> lineWordUnderlines(
wordUnderlines.begin(),
wordUnderlines.begin() + lineWordCount);
std::vector<bool> lineWordUnderlines(wordUnderlines.begin(), wordUnderlines.begin() + lineWordCount);
wordUnderlines.erase(wordUnderlines.begin(), wordUnderlines.begin() + lineWordCount);
for (auto& word : lineWords) {

View File

@ -2,9 +2,9 @@
#include <EpdFontFamily.h>
#include <SdFat.h>
#include <vector>
#include <memory>
#include <string>
#include <vector>
#include "Block.h"
#include "BlockStyle.h"
@ -30,7 +30,8 @@ class TextBlock final : public Block {
public:
explicit TextBlock(std::vector<std::string> words, std::vector<uint16_t> word_xpos,
std::vector<EpdFontFamily::Style> word_styles, const Style style,
const BlockStyle& blockStyle = BlockStyle(), std::vector<bool> word_underlines = std::vector<bool>())
const BlockStyle& blockStyle = BlockStyle(),
std::vector<bool> word_underlines = std::vector<bool>())
: words(std::move(words)),
wordXpos(std::move(word_xpos)),
wordStyles(std::move(word_styles)),

View File

@ -299,8 +299,7 @@ bool StarDict::decompressDefinition(uint32_t offset, uint32_t size, std::string&
const uint32_t endChunk = (offset + size - 1) / dzInfo.chunkLength;
const uint32_t startOffsetInChunk = offset % dzInfo.chunkLength;
Serial.printf("[DICT-DBG] Chunks: start=%lu, end=%lu, total=%u\n",
startChunk, endChunk, dzInfo.chunkCount);
Serial.printf("[DICT-DBG] Chunks: start=%lu, end=%lu, total=%u\n", startChunk, endChunk, dzInfo.chunkCount);
if (endChunk >= dzInfo.chunkCount) {
Serial.printf("[DICT-DBG] endChunk %lu >= chunkCount %u\n", endChunk, dzInfo.chunkCount);
@ -324,8 +323,8 @@ bool StarDict::decompressDefinition(uint32_t offset, uint32_t size, std::string&
// Allocate buffers - allocate inflator FIRST (smallest) to reduce fragmentation impact
// tinfl_decompressor is ~11KB, so total allocations are ~85KB
Serial.printf("[DICT-DBG] Allocating inflator=%u, comp=%lu, decomp=%u bytes\n",
sizeof(tinfl_decompressor), maxCompressedSize, dzInfo.chunkLength);
Serial.printf("[DICT-DBG] Allocating inflator=%u, comp=%lu, decomp=%u bytes\n", sizeof(tinfl_decompressor),
maxCompressedSize, dzInfo.chunkLength);
auto* inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
if (!inflator) {
@ -469,8 +468,7 @@ StarDict::LookupResult StarDict::lookup(const std::string& word) {
return result;
}
Serial.printf("[DICT-DBG] Searching for: '%s' (normalized: '%s')\n",
word.c_str(), normalizedSearch.c_str());
Serial.printf("[DICT-DBG] Searching for: '%s' (normalized: '%s')\n", word.c_str(), normalizedSearch.c_str());
// First try .idx (main entries) - use prefix jump table for fast lookup
const std::string idxPath = basePath + ".idx";
@ -487,8 +485,8 @@ StarDict::LookupResult StarDict::lookup(const std::string& word) {
const uint16_t prefixIdx = DictPrefixIndex::prefixToIndex(normalizedSearch[0], normalizedSearch[1]);
position = DictPrefixIndex::dictPrefixOffsets[prefixIdx];
}
Serial.printf("[DICT-DBG] Starting at position %lu (prefix: %c%c)\n",
position, normalizedSearch[0], normalizedSearch[1]);
Serial.printf("[DICT-DBG] Starting at position %lu (prefix: %c%c)\n", position, normalizedSearch[0],
normalizedSearch[1]);
bool found = false;
uint32_t wordCount = 0;
@ -501,19 +499,18 @@ StarDict::LookupResult StarDict::lookup(const std::string& word) {
}
wordCount++;
if (wordCount % 50000 == 0) {
Serial.printf("[DICT-DBG] Progress: %lu words scanned, pos=%lu, current='%s'\n",
wordCount, position, currentWord.c_str());
Serial.printf("[DICT-DBG] Progress: %lu words scanned, pos=%lu, current='%s'\n", wordCount, position,
currentWord.c_str());
}
// Use stardictStrcmp for case-insensitive matching
const int cmp = stardictStrcmp(normalizedSearch, currentWord);
if (cmp == 0) {
Serial.printf("[DICT-DBG] MATCH: '%s' == '%s' (offset=%lu, size=%lu)\n",
normalizedSearch.c_str(), currentWord.c_str(), dictOffset, dictSize);
Serial.printf("[DICT-DBG] MATCH: '%s' == '%s' (offset=%lu, size=%lu)\n", normalizedSearch.c_str(),
currentWord.c_str(), dictOffset, dictSize);
std::string definition;
const bool loaded = useUncompressed
? readDefinitionDirect(dictOffset, dictSize, definition)
const bool loaded = useUncompressed ? readDefinitionDirect(dictOffset, dictSize, definition)
: decompressDefinition(dictOffset, dictSize, definition);
if (loaded) {
Serial.printf("[DICT-DBG] Definition loaded, %u bytes\n", definition.length());
@ -537,8 +534,7 @@ StarDict::LookupResult StarDict::lookup(const std::string& word) {
// may not land exactly at target position
}
Serial.printf("[DICT-DBG] Search complete: %lu words scanned, found=%s\n",
wordCount, found ? "YES" : "NO");
Serial.printf("[DICT-DBG] Search complete: %lu words scanned, found=%s\n", wordCount, found ? "YES" : "NO");
idxFile.close();
// If not found in main index, try synonym file with prefix jump
@ -591,8 +587,7 @@ StarDict::LookupResult StarDict::lookup(const std::string& word) {
uint32_t dictOffset, dictSize;
if (readWordAtPosition(idxFile2, pos, mainWord, dictOffset, dictSize)) {
std::string definition;
const bool loaded = useUncompressed
? readDefinitionDirect(dictOffset, dictSize, definition)
const bool loaded = useUncompressed ? readDefinitionDirect(dictOffset, dictSize, definition)
: decompressDefinition(dictOffset, dictSize, definition);
if (loaded) {
result.word = synWord;

View File

@ -3,7 +3,7 @@ default_envs = default
[crosspoint]
# 0.15.0 CrossPoint base, ef-1.0.0 is the first release of the ef branch
version = 0.15.ef-1.0.4
version = 0.15.ef-1.0.5
[base]
platform = espressif32 @ 6.12.0

View File

@ -378,7 +378,8 @@ bool BookManager::clearBookCache(const std::string& bookPath, bool preserveProgr
dir.close();
}
Serial.printf("[%lu] [%s] Cache cleared: %d items deleted, %d failed\n", millis(), LOG_TAG, deletedCount, failedCount);
Serial.printf("[%lu] [%s] Cache cleared: %d items deleted, %d failed\n", millis(), LOG_TAG, deletedCount,
failedCount);
return failedCount == 0;
}

View File

@ -144,8 +144,8 @@ void DictionaryResultActivity::paginateDefinition() {
constexpr size_t CHUNK_SIZE_BASE = 1500; // Base chunk size
const size_t chunkSize = std::max(CHUNK_SIZE_BASE, static_cast<size_t>(linesPerPage * 120));
Serial.printf("[DICT-DBG] Chunked parsing: defLen=%u, chunkSize=%u, linesPerPage=%d\n",
rawDefinition.length(), chunkSize, linesPerPage);
Serial.printf("[DICT-DBG] Chunked parsing: defLen=%u, chunkSize=%u, linesPerPage=%d\n", rawDefinition.length(),
chunkSize, linesPerPage);
// Determine how much to parse for first page
size_t parseEnd;
@ -163,15 +163,13 @@ void DictionaryResultActivity::paginateDefinition() {
std::string chunk = rawDefinition.substr(0, parseEnd);
parsePosition = parseEnd;
Serial.printf("[DICT-DBG] Parsing first chunk: 0-%u of %u, hasMore=%d\n",
parseEnd, rawDefinition.length(), hasMoreContent);
Serial.printf("[DICT-DBG] Parsing first chunk: 0-%u of %u, hasMore=%d\n", parseEnd, rawDefinition.length(),
hasMoreContent);
// Parse this chunk into TextBlocks
std::vector<std::shared_ptr<TextBlock>> allBlocks;
DictHtmlParser::parse(chunk, UI_10_FONT_ID, renderer, textWidth,
[&allBlocks](std::shared_ptr<TextBlock> block) {
allBlocks.push_back(block);
});
[&allBlocks](std::shared_ptr<TextBlock> block) { allBlocks.push_back(block); });
Serial.printf("[DICT-DBG] First chunk parsed, %u TextBlocks\n", allBlocks.size());
if (allBlocks.empty()) {
@ -269,8 +267,7 @@ void DictionaryResultActivity::parseNextChunk() {
return;
}
Serial.printf("[DICT-DBG] parseNextChunk starting at position %u of %u\n",
parsePosition, rawDefinition.length());
Serial.printf("[DICT-DBG] parseNextChunk starting at position %u of %u\n", parsePosition, rawDefinition.length());
// Get margins with button hint space for all orientations
int marginTop, marginRight, marginBottom, marginLeft;
@ -315,9 +312,7 @@ void DictionaryResultActivity::parseNextChunk() {
// Parse this chunk into TextBlocks
std::vector<std::shared_ptr<TextBlock>> allBlocks;
DictHtmlParser::parse(chunk, UI_10_FONT_ID, renderer, textWidth,
[&allBlocks](std::shared_ptr<TextBlock> block) {
allBlocks.push_back(block);
});
[&allBlocks](std::shared_ptr<TextBlock> block) { allBlocks.push_back(block); });
Serial.printf("[DICT-DBG] Chunk parsed, %u TextBlocks\n", allBlocks.size());
@ -359,8 +354,8 @@ void DictionaryResultActivity::parseNextChunk() {
Serial.printf("[DICT-DBG] Trimmed old page, firstPageNumber now %d\n", firstPageNumber);
}
Serial.printf("[DICT-DBG] After chunk: %u cached pages (pages %d-%d)\n",
pages.size(), firstPageNumber, firstPageNumber + static_cast<int>(pages.size()) - 1);
Serial.printf("[DICT-DBG] After chunk: %u cached pages (pages %d-%d)\n", pages.size(), firstPageNumber,
firstPageNumber + static_cast<int>(pages.size()) - 1);
}
void DictionaryResultActivity::reparseToPage(int targetPageNumber) {
@ -381,8 +376,7 @@ void DictionaryResultActivity::reparseToPage(int targetPageNumber) {
}
// Now position currentPage to show the target page
if (targetPageNumber >= firstPageNumber &&
targetPageNumber < firstPageNumber + static_cast<int>(pages.size())) {
if (targetPageNumber >= firstPageNumber && targetPageNumber < firstPageNumber + static_cast<int>(pages.size())) {
currentPage = targetPageNumber - firstPageNumber;
} else {
// Target page doesn't exist (definition is shorter than expected)
@ -390,8 +384,8 @@ void DictionaryResultActivity::reparseToPage(int targetPageNumber) {
if (currentPage < 0) currentPage = 0;
}
Serial.printf("[DICT-DBG] reparseToPage done: currentPage=%d, firstPageNumber=%d, pages=%u\n",
currentPage, firstPageNumber, pages.size());
Serial.printf("[DICT-DBG] reparseToPage done: currentPage=%d, firstPageNumber=%d, pages=%u\n", currentPage,
firstPageNumber, pages.size());
}
void DictionaryResultActivity::displayTaskLoop() {

View File

@ -1,7 +1,7 @@
#include "EpubWordSelectionActivity.h"
#include <HalDisplay.h>
#include <GfxRenderer.h>
#include <HalDisplay.h>
#include <algorithm>
#include <cctype>

View File

@ -71,14 +71,14 @@ class CrossPointWebServerActivity final : public ActivityWithSubactivity {
// Avoids recomputing QR data on every render (every 30s stats refresh)
// Marked mutable since QR drawing doesn't modify logical state but qrcode_getModule takes non-const
bool qrCacheValid = false;
mutable QRCode qrWebBrowser;
mutable QRCode qrCompanionApp;
mutable QRCode qrCompanionAppLibrary;
mutable QRCode qrWifiConfig; // For AP mode WiFi connection QR
uint8_t qrWebBrowserBuffer[QR_BUFFER_SIZE];
uint8_t qrCompanionAppBuffer[QR_BUFFER_SIZE];
uint8_t qrCompanionAppLibraryBuffer[QR_BUFFER_SIZE];
uint8_t qrWifiConfigBuffer[QR_BUFFER_SIZE];
mutable QRCode qrWebBrowser = {};
mutable QRCode qrCompanionApp = {};
mutable QRCode qrCompanionAppLibrary = {};
mutable QRCode qrWifiConfig = {}; // For AP mode WiFi connection QR
uint8_t qrWebBrowserBuffer[QR_BUFFER_SIZE] = {};
uint8_t qrCompanionAppBuffer[QR_BUFFER_SIZE] = {};
uint8_t qrCompanionAppLibraryBuffer[QR_BUFFER_SIZE] = {};
uint8_t qrWifiConfigBuffer[QR_BUFFER_SIZE] = {};
static void taskTrampoline(void* param);
[[noreturn]] void displayTaskLoop();

View File

@ -761,8 +761,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);
Serial.printf("[%lu] [ERS] Page out of bounds: %d (max %d)\n", millis(), section->currentPage, section->pageCount);
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Out of bounds", true, EpdFontFamily::BOLD);
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
renderer.displayBuffer();

View File

@ -237,7 +237,8 @@ void ClearCacheActivity::clearCache() {
HomeActivity::freeCoverBufferIfAllocated();
MyLibraryActivity::clearThumbExistsCache();
Serial.printf("[%lu] [CLEAR_CACHE] Cache cleared: %d items removed, %d failed\n", millis(), clearedCount, failedCount);
Serial.printf("[%lu] [CLEAR_CACHE] Cache cleared: %d items removed, %d failed\n", millis(), clearedCount,
failedCount);
state = SUCCESS;
updateRequired = true;

View File

@ -1,7 +1,7 @@
#include "KeyboardEntryActivity.h"
#include "activities/dictionary/DictionaryMargins.h"
#include "MappedInputManager.h"
#include "activities/dictionary/DictionaryMargins.h"
#include "fontIds.h"
// Keyboard layouts - lowercase

View File

@ -1269,9 +1269,7 @@ bool CrossPointWebServer::sendContentSafe(const char* content) const {
return server->client().connected();
}
bool CrossPointWebServer::sendContentSafe(const String& content) const {
return sendContentSafe(content.c_str());
}
bool CrossPointWebServer::sendContentSafe(const String& content) const { return sendContentSafe(content.c_str()); }
bool CrossPointWebServer::copyFile(const String& srcPath, const String& destPath) const {
FsFile srcFile;