Public Access
The hardcoded clock is now the built-in dynamic page "Clock". A dynamic page has a background bitmap per time of day and up to 8 elements (clock, date, Home Assistant value, text), each with position, alignment, font and overlay. Pages are drawn on the ESP32, also for the web previews. - Pages tab in the web UI: page editor with drag and drop, font upload (TTF/OTF converted in the browser to the FDF1 dot font format) - Built-in fonts Small, Normal, Large and Full height (generated by tools/make_builtin_fonts.py), built-in bitmaps Pikachu and Pikachu yawning - Home Assistant values over the REST API (URL and token in the HA tab) - Settings version 4: evening settings removed (now time slots of the Clock page), old records are converted - HA Mode dropdown lists the dynamic pages and bitmaps Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
154 lines
3.6 KiB
C++
154 lines
3.6 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;
|
|
}
|
|
if (!mounted || !validBitmapName(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;
|
|
}
|