cbdcad3154
Previously a 5xx / timeout / malformed response fired epd_fill(COLOR_YELLOW),
which writes the yellow nibble across the entire 800×480 framebuffer and
destroys the last good image — exactly what FR38 forbids ("Last image
persists ... yellow border signals state"). The device then got stuck on a
blank yellow screen because the next 304 didn't redraw.
Changes:
- New epd_draw_image_with_border streams the cached .bin row-by-row,
overwrites border-region pixels in the row buffer, and pushes a single
composited framebuffer (same pattern as the existing setup-QR overlay).
- normal_operation_impl else-branch now redraws the cached image with a
yellow border, falling back to epd_fill only when no cache exists
(first-boot error). Sets a new NVS_KEY_ERR_BORDER flag.
- 200 and 304 paths clear NVS_KEY_ERR_BORDER. The 304 branch now
triggers a clean repaint when the err flag is set, so the device
recovers from the stuck-yellow state on the next healthy poll
without waiting for rotation to advance.
- LittleFS read mock now returns invalid File when the file doesn't
exist (matches real LittleFS), so the no-cache fallback path is
actually exercisable in tests.
Tests:
- Replaces the old test_fw06_error_fills_yellow (which locked in the
buggy fill behavior) with FW-06a..e covering: error+cache draws
border (no fill), error+no-cache falls back to fill, 304 after
error repaints clean, steady-state 304 touches nothing (the
regression the user flagged), 200 after error clears the flag.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
53 lines
1.4 KiB
C++
53 lines
1.4 KiB
C++
#pragma once
|
|
#include "Arduino.h"
|
|
#include <map>
|
|
#include <vector>
|
|
|
|
struct File {
|
|
std::string* _buf = nullptr;
|
|
bool _valid = false;
|
|
bool _write = false;
|
|
size_t _pos = 0;
|
|
|
|
explicit operator bool() const { return _valid; }
|
|
void close() { _valid = false; }
|
|
size_t write(const uint8_t* data, size_t len) {
|
|
if (_buf && _write) { _buf->append((const char*)data, len); return len; }
|
|
return 0;
|
|
}
|
|
int read() {
|
|
if (_buf && _pos < _buf->size()) return (uint8_t)(*_buf)[_pos++];
|
|
return -1;
|
|
}
|
|
size_t size() { return _buf ? _buf->size() : 0; }
|
|
};
|
|
|
|
struct LittleFSClass {
|
|
std::map<std::string, std::string> files;
|
|
|
|
bool begin(bool) { return true; }
|
|
|
|
File open(const char* path, const char* mode, bool create = false) {
|
|
File f;
|
|
f._write = (mode[0] == 'w');
|
|
f._pos = 0;
|
|
if (f._write) {
|
|
f._buf = &files[path];
|
|
f._buf->clear();
|
|
f._valid = true;
|
|
} else {
|
|
// Read mode: behave like real LittleFS — return invalid when file
|
|
// doesn't exist (do NOT create an empty entry via operator[]).
|
|
auto it = files.find(path);
|
|
if (it == files.end()) {
|
|
f._valid = false;
|
|
f._buf = nullptr;
|
|
} else {
|
|
f._buf = &it->second;
|
|
f._valid = true;
|
|
}
|
|
}
|
|
return f;
|
|
}
|
|
} LittleFS;
|