Public Access
- Text elements can scroll in a window at a set speed; clock, countdown and stopwatch can show seconds. Moving pages are sent as instant frames (new Nano command D1) and paced by the estimated flip time. - Countdown to a date and time (smart, days, days + time, with seconds), stopwatch (web UI, HA switch and reset button), image element (fixed or by Home Assistant state), multiplier for HA values, degree sign (char 127) in all fonts and the font converter. - Page format version 2; version 1 pages are converted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
155 lines
3.7 KiB
C++
155 lines
3.7 KiB
C++
#include "display/bitmap_store.h"
|
|
|
|
#include <LittleFS.h>
|
|
#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},
|
|
};
|
|
|
|
static bool mounted = false;
|
|
static uint32_t revision = 0;
|
|
|
|
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"})
|
|
if (mounted && !LittleFS.exists(dir))
|
|
LittleFS.mkdir(dir);
|
|
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)
|
|
{
|
|
std::vector<String> names;
|
|
if (!mounted)
|
|
return names;
|
|
File dir = LittleFS.open(dir_path);
|
|
if (!dir)
|
|
return names;
|
|
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))
|
|
continue;
|
|
name.remove(name.length() - strlen(extension));
|
|
if (validBitmapName(name.c_str()))
|
|
names.push_back(name);
|
|
}
|
|
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});
|
|
return list;
|
|
}
|
|
|
|
uint32_t bitmapListRevision()
|
|
{
|
|
return revision;
|
|
}
|
|
|
|
bool bitmapExists(const char *name)
|
|
{
|
|
if (builtin(name))
|
|
return true;
|
|
return mounted && validBitmapName(name) && LittleFS.exists(path(name));
|
|
}
|
|
|
|
bool loadBitmap(const char *name, uint8_t *bitmap)
|
|
{
|
|
const BuiltinBitmap *b = builtin(name);
|
|
if (b)
|
|
{
|
|
memcpy_P(bitmap, b->data, BITMAP_BYTES);
|
|
return true;
|
|
}
|
|
// Checked first: open() logs an error for a missing file (images try names by state)
|
|
if (!mounted || !validBitmapName(name) || !LittleFS.exists(path(name)))
|
|
return false;
|
|
File file = LittleFS.open(path(name), "r");
|
|
if (!file)
|
|
return false;
|
|
bool ok = file.read(bitmap, BITMAP_BYTES) == (size_t)BITMAP_BYTES;
|
|
file.close();
|
|
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++;
|
|
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++;
|
|
return nullptr;
|
|
}
|