The Pikachus of the Clock page are icons

Stored as icons at boot from the firmware's bitmaps (cut to 80 x 19), so
they can be edited; the firmware copies stay the fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-16 22:18:58 +02:00
1 parent 51b8d87e86
commit 839e86e8f1
17 files changed
+68 -6

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 KiB

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 KiB

After

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 KiB

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 131 KiB

+4 -2
View File
@@ -72,7 +72,7 @@ Start, stop and reset the stopwatch that pages can show (also in Home Assistant)
## Bitmaps ## Bitmaps
All bitmaps with a preview: show one as the page, send it live, open it in the editor, All bitmaps with a preview: show one as the page, send it live, open it in the editor,
export or delete it. The built-in ones (Nintendo, Pikachu, Pikachu yawning) can't be export or delete it. The built-in one (Nintendo) can't be
deleted, nor can a bitmap that a page, the schedule or a rule uses. "Make icon" turns a deleted, nor can a bitmap that a page, the schedule or a rule uses. "Make icon" turns a
bitmap that is only part of pages into an [icon](#icons). bitmap that is only part of pages into an [icon](#icons).
@@ -85,7 +85,9 @@ element, or a background (drawn at the top left). Icons are never shown on their
they are not in the page lists (Show, Schedule, rules, Home Assistant "Mode"). Each icon they are not in the page lists (Show, Schedule, rules, Home Assistant "Mode"). Each icon
shows its size and the pages that use it. The editor: draw dots, change the size (the dots shows its size and the pages that use it. The editor: draw dots, change the size (the dots
stay at the top left), crop to the dots, clear, invert, load an image (scaled to the icon stay at the top left), crop to the dots, clear, invert, load an image (scaled to the icon
size), show it on the display. "Make bitmap" turns an icon into a full screen bitmap. size), show it on the display. "Make bitmap" turns an icon into a full screen bitmap. The two Pikachus of the Clock page are built into the
firmware: they are stored as icons at boot and can be edited, the firmware keeps the
originals.
Firmware 2.10.0 made icons of all uploaded bitmaps that only pages used (backgrounds and Firmware 2.10.0 made icons of all uploaded bitmaps that only pages used (backgrounds and
images, not shown by the schedule, a rule, the chosen page or the off image), once, after images, not shown by the schedule, a rule, the chosen page or the off image), once, after
+2
View File
@@ -423,6 +423,8 @@ const char *appBitmapToIcon(const char *name)
const char *appIconToBitmap(const char *name) const char *appIconToBitmap(const char *name)
{ {
if (builtinIconName(name))
return "Built-in icons stay icons (the firmware has them as icons)";
Icon icon; Icon icon;
if (!loadIcon(name, icon)) if (!loadIcon(name, icon))
return "Icon not found"; return "Icon not found";
+1 -1
View File
@@ -118,7 +118,7 @@ const uint32_t SETUP_AP_DELAY = 20000; // open the AP when WiFi fails thi
const uint32_t WIFI_CONNECT_TIMEOUT = 30000; const uint32_t WIFI_CONNECT_TIMEOUT = 30000;
const uint32_t WIFI_RESTART_TIME = 10UL * 60 * 1000; const uint32_t WIFI_RESTART_TIME = 10UL * 60 * 1000;
#define FIRMWARE_VERSION "2.11.0" #define FIRMWARE_VERSION "2.12.0"
// Home Assistant device id (do not change: Home Assistant's entities are linked to it) // Home Assistant device id (do not change: Home Assistant's entities are linked to it)
const uint8_t HA_DEVICE_ID[] = {0x34, 0x86, 0x5d, 0x3a, 0x08, 0x28}; const uint8_t HA_DEVICE_ID[] = {0x34, 0x86, 0x5d, 0x3a, 0x08, 0x28};
+1 -2
View File
@@ -16,8 +16,7 @@ struct BuiltinBitmap
static const BuiltinBitmap BUILTIN[] = { static const BuiltinBitmap BUILTIN[] = {
{"Nintendo", nintendo_bitmap_Nintendo_new}, {"Nintendo", nintendo_bitmap_Nintendo_new},
{"Pikachu", pikachu_clock_bitmap}, // The Pikachus of the Clock page are icons (display/icon_store)
{"Pikachu yawning", pikachu_yawning_clock_bitmap},
}; };
static bool mounted = false; static bool mounted = false;
+52 -1
View File
@@ -6,11 +6,45 @@
#include <strings.h> #include <strings.h>
#include "display/bitmap_store.h" #include "display/bitmap_store.h"
#include "display/bitmaps.h"
static const char *DIR = "/icons"; static const char *DIR = "/icons";
// Written when the bitmaps were moved (not a valid name, so never listed) // Written when the bitmaps were moved (not a valid name, so never listed)
static const char *MIGRATED = "/icons/.migrated"; static const char *MIGRATED = "/icons/.migrated";
// Built into the firmware (full screen bitmaps, cut to icons): stored at boot so they can
// be edited, and the fallback when they are not stored
struct BuiltinIcon
{
const char *name;
const uint8_t *bitmap; // PROGMEM, BITMAP_BYTES
};
static const BuiltinIcon BUILTIN[] = {
{"Pikachu", pikachu_clock_bitmap},
{"Pikachu yawning", pikachu_yawning_clock_bitmap},
};
static bool builtinIcon(const char *name, Icon &icon)
{
for (const BuiltinIcon &b : BUILTIN)
if (strcasecmp(b.name, name) == 0)
{
uint8_t bitmap[BITMAP_BYTES];
memcpy_P(bitmap, b.bitmap, BITMAP_BYTES);
iconFromBitmap(bitmap, icon);
return true;
}
return false;
}
bool builtinIconName(const char *name)
{
for (const BuiltinIcon &b : BUILTIN)
if (strcasecmp(b.name, name) == 0)
return true;
return false;
}
static uint32_t revision = 0; static uint32_t revision = 0;
// Counted once (listing the directory is slow), then kept up to date; -1: not yet // Counted once (listing the directory is slow), then kept up to date; -1: not yet
static int count = -1; static int count = -1;
@@ -37,9 +71,24 @@ static bool ensureDir()
return LittleFS.exists(DIR) || LittleFS.mkdir(DIR); return LittleFS.exists(DIR) || LittleFS.mkdir(DIR);
} }
void iconStoreBegin()
{
for (const BuiltinIcon &b : BUILTIN)
{
Icon icon;
if (!bitmapStorageAvailable() || LittleFS.exists(path(b.name)) || !builtinIcon(b.name, icon))
continue;
const char *error = saveIcon(b.name, icon);
Serial.printf("Icon \"%s\" stored from the firmware: %s\n", b.name, error ? error : "ok");
}
}
std::vector<String> listIcons() std::vector<String> listIcons()
{ {
std::vector<String> names = storedNames(DIR, ".bin"); std::vector<String> names = storedNames(DIR, ".bin");
for (const BuiltinIcon &b : BUILTIN) // not stored (no storage)
if (std::none_of(names.begin(), names.end(), [&b](const String &n) { return strcasecmp(n.c_str(), b.name) == 0; }))
names.push_back(b.name);
std::sort(names.begin(), names.end(), std::sort(names.begin(), names.end(),
[](const String &a, const String &b) { return strcasecmp(a.c_str(), b.c_str()) < 0; }); [](const String &a, const String &b) { return strcasecmp(a.c_str(), b.c_str()) < 0; });
return names; return names;
@@ -59,7 +108,7 @@ bool iconExists(const char *name)
bool loadIcon(const char *name, Icon &icon) bool loadIcon(const char *name, Icon &icon)
{ {
if (!bitmapStorageAvailable() || !validBitmapName(name)) if (!bitmapStorageAvailable() || !validBitmapName(name))
return false; return builtinIcon(name, icon);
auto cached = cache.find(name); auto cached = cache.find(name);
if (cached != cache.end()) if (cached != cache.end())
{ {
@@ -88,6 +137,8 @@ bool loadIcon(const char *name, Icon &icon)
file.close(); file.close();
} }
} }
if (!ok)
ok = builtinIcon(name, loaded);
if (cache.size() >= CACHE_MAX) if (cache.size() >= CACHE_MAX)
cache.clear(); cache.clear();
CachedIcon &entry = cache[name]; CachedIcon &entry = cache[name];
+6
View File
@@ -12,6 +12,12 @@
const int MAX_ICONS = 200; const int MAX_ICONS = 200;
// At boot: stores the icons built into the firmware (the Pikachus of the Clock page) when
// they are not stored, so they can be edited. The built-in ones stay the fallback.
void iconStoreBegin();
// One of them (it always exists)
bool builtinIconName(const char *name);
// Sorted without case // Sorted without case
std::vector<String> listIcons(); std::vector<String> listIcons();
// Changes each time an icon is saved or deleted // Changes each time an icon is saved or deleted
+2
View File
@@ -7,6 +7,7 @@
#include "app.h" #include "app.h"
#include "config.h" #include "config.h"
#include "display/bitmap_store.h" #include "display/bitmap_store.h"
#include "display/icon_store.h"
#include "display/page_store.h" #include "display/page_store.h"
#include "logic/status_led.h" #include "logic/status_led.h"
#include "nano_link/nano_flash_bridge.h" #include "nano_link/nano_flash_bridge.h"
@@ -34,6 +35,7 @@ void setup()
BootMode boot_mode = firmwareGuardBegin(); BootMode boot_mode = firmwareGuardBegin();
bitmapStoreBegin(); bitmapStoreBegin();
iconStoreBegin(); // the Pikachus as icons that can be edited
pageStoreBegin(); // the Clock page as a page that can be edited pageStoreBegin(); // the Clock page as a page that can be edited
appLoadSettings(); appLoadSettings();