Files
Flipdot-display/src/esp32/display/bitmap_store.cpp
T

194 lines
4.7 KiB
C++
Raw Normal View History

2026-10-13 01:52:34 +02:00
#include "display/bitmap_store.h"
#include <LittleFS.h>
#include <map>
2026-10-13 01:52:34 +02:00
#include <strings.h>
#include "display/bitmaps.h"
static const char *DIR = "/bitmaps";
struct BuiltinBitmap
{
const char *name;
const uint8_t *data; // PROGMEM, BITMAP_BYTES
};
static const BuiltinBitmap BUILTIN[] = {
{"Nintendo", nintendo_bitmap_Nintendo_new},
{"Pikachu", pikachu_clock_bitmap},
{"Pikachu yawning", pikachu_yawning_clock_bitmap},
2026-10-13 01:52:34 +02:00
};
static bool mounted = false;
static uint32_t revision = 0;
2026-10-13 01:52:34 +02:00
// Bitmaps read before, also the names that don't exist (images try a name per state, then
// "default"). Reading one from LittleFS takes about 100 ms, and pages are drawn again for
// every frame (scrolling, seconds) and for every preview in the web UI. Cleared when a
// bitmap is saved or deleted, and when it holds CACHE_MAX names.
struct CachedBitmap
{
bool exists;
uint8_t data[BITMAP_BYTES];
};
static std::map<String, CachedBitmap> cache;
static const size_t CACHE_MAX = 64;
2026-10-13 01:52:34 +02:00
static const BuiltinBitmap *builtin(const char *name)
{
for (const BuiltinBitmap &b : BUILTIN)
if (strcasecmp(b.name, name) == 0)
return &b;
return nullptr;
}
static String path(const char *name)
{
return String(DIR) + "/" + name + ".bin";
}
bool bitmapStoreBegin()
{
mounted = LittleFS.begin(true);
for (const char *dir : {DIR, "/pages", "/fonts", "/icons"})
if (mounted && !LittleFS.exists(dir))
LittleFS.mkdir(dir);
2026-10-13 01:52:34 +02:00
Serial.printf("Bitmap storage %s\n", mounted ? "ready" : "not available (old flash layout?)");
return mounted;
}
bool bitmapStorageAvailable()
{
return mounted;
}
void bitmapStorageUsage(size_t &used, size_t &total)
{
used = mounted ? LittleFS.usedBytes() : 0;
total = mounted ? LittleFS.totalBytes() : 0;
}
std::vector<String> storedNames(const char *dir_path, const char *extension)
2026-10-13 01:52:34 +02:00
{
std::vector<String> names;
2026-10-13 01:52:34 +02:00
if (!mounted)
return names;
File dir = LittleFS.open(dir_path);
if (!dir)
return names;
2026-10-13 01:52:34 +02:00
for (File file = dir.openNextFile(); file; file = dir.openNextFile())
{
String name = file.name();
int slash = name.lastIndexOf('/');
if (slash >= 0)
name = name.substring(slash + 1);
if (!name.endsWith(extension))
2026-10-13 01:52:34 +02:00
continue;
name.remove(name.length() - strlen(extension));
if (validBitmapName(name.c_str()))
names.push_back(name);
2026-10-13 01:52:34 +02:00
}
return names;
}
std::vector<BitmapInfo> listBitmaps()
{
std::vector<BitmapInfo> list;
for (const BuiltinBitmap &b : BUILTIN)
list.push_back({b.name, true});
for (const String &name : storedNames(DIR, ".bin"))
if (!builtin(name.c_str()))
list.push_back({name, false});
2026-10-13 01:52:34 +02:00
return list;
}
uint32_t bitmapListRevision()
{
return revision;
}
bool builtinBitmap(const char *name)
{
return builtin(name) != nullptr;
}
2026-10-13 01:52:34 +02:00
bool bitmapExists(const char *name)
{
if (builtin(name))
return true;
uint8_t bitmap[BITMAP_BYTES];
return loadBitmap(name, bitmap); // through the cache
2026-10-13 01:52:34 +02:00
}
bool loadBitmap(const char *name, uint8_t *bitmap)
{
const BuiltinBitmap *b = builtin(name);
if (b)
{
memcpy_P(bitmap, b->data, BITMAP_BYTES);
return true;
}
if (!mounted || !validBitmapName(name))
return false;
auto cached = cache.find(name);
if (cached != cache.end())
{
if (cached->second.exists)
memcpy(bitmap, cached->second.data, BITMAP_BYTES);
return cached->second.exists;
}
// Checked first: open() logs an error for a missing file (images try names by state)
bool ok = false;
if (LittleFS.exists(path(name)))
{
File file = LittleFS.open(path(name), "r");
if (file)
{
ok = file.read(bitmap, BITMAP_BYTES) == (size_t)BITMAP_BYTES;
file.close();
}
}
if (cache.size() >= CACHE_MAX)
cache.clear();
CachedBitmap &entry = cache[name];
entry.exists = ok;
if (ok)
memcpy(entry.data, bitmap, BITMAP_BYTES);
2026-10-13 01:52:34 +02:00
return ok;
}
const char *saveBitmap(const char *name, const uint8_t *bitmap)
{
if (!validBitmapName(name))
return "Invalid name: use 1-24 letters, digits, spaces, - or _";
if (builtin(name))
return "A built-in bitmap has this name";
if (!mounted)
return "No bitmap storage (install the new flash layout over USB, see README)";
if (!LittleFS.exists(path(name)) && listBitmaps().size() >= (size_t)MAX_BITMAPS + (sizeof(BUILTIN) / sizeof(BUILTIN[0])))
return "Too many bitmaps, delete some first";
File file = LittleFS.open(path(name), "w");
if (!file)
return "Could not write the file";
bool ok = file.write(bitmap, BITMAP_BYTES) == (size_t)BITMAP_BYTES;
file.close();
revision++;
cache.clear();
2026-10-13 01:52:34 +02:00
return ok ? nullptr : "Storage full";
}
const char *deleteBitmap(const char *name)
{
if (builtin(name))
return "Built-in bitmaps can't be deleted";
if (!mounted || !validBitmapName(name) || !LittleFS.exists(path(name)))
return "Bitmap not found";
if (!LittleFS.remove(path(name)))
return "Could not delete the file";
revision++;
cache.clear();
return nullptr;
2026-10-13 01:52:34 +02:00
}