Drop the converters for old data

2.15.0 stored all settings, schedules and pages on the display in the
current formats (old_data_left 0), so only those are read now: settings
version 7, schedule version 4, page format 7. The one-time migrations
(icons from 2.10.0, data from 2.15.0) are removed too. About 9 KB less
flash. CLAUDE.md says how to keep data over a future format change.

Version 2.15.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-17 21:21:25 +02:00
1 parent 968492ca35
commit 9959c1aa74
14 files changed
+28 -996

No files matched your search

-18
View File
@@ -10,7 +10,6 @@
static const char *DIR = "/icons";
// Written when the bitmaps were moved (not a valid name, so never listed)
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
@@ -197,20 +196,3 @@ const char *deleteIcon(const char *name)
cache.clear();
return nullptr;
}
bool iconMigrationDone()
{
return !bitmapStorageAvailable() || LittleFS.exists(MIGRATED);
}
void setIconMigrationDone()
{
if (!ensureDir())
return;
File file = LittleFS.open(MIGRATED, "w");
if (file)
{
file.print("1");
file.close();
}
}
-3
View File
@@ -29,6 +29,3 @@ bool loadIcon(const char *name, Icon &icon);
const char *saveIcon(const char *name, const Icon &icon);
const char *deleteIcon(const char *name);
// The one-time move of the bitmaps that only pages use (app.cpp) is done
bool iconMigrationDone();
void setIconMigrationDone();
+2 -465
View File
@@ -7,10 +7,9 @@
static const char *DIR = "/pages";
static const char *EXTENSION = ".pg";
// Increase when DynamicPage changes; older files are ignored unless converted below
// Increase when DynamicPage changes; files of other versions are ignored (the built-in page
// of that name, if any, is used). 2.15.0 rewrote all older pages in this version.
static const uint16_t VERSION = 7;
// Elements per page up to version 5 (the old formats below are frozen with it)
static const int OLD_PAGE_ELEMENTS = 8;
struct StoredPage
{
@@ -19,379 +18,6 @@ struct StoredPage
DynamicPage page;
};
// Version 1 (before scrolling, countdown, stopwatch, images and the multiplier): frozen
// copy of the layout, converted when loaded. Saving writes the current version.
namespace v1
{
struct Element
{
ElementType type;
int16_t x, y;
Align align;
Overlay overlay;
char font[PAGE_NAME_SIZE];
uint8_t format;
Language language;
int8_t decimals;
char text[ELEMENT_TEXT_SIZE];
char suffix[ELEMENT_SUFFIX_SIZE];
char entity[ELEMENT_ENTITY_SIZE];
char attribute[ELEMENT_ATTRIBUTE_SIZE];
};
struct Page
{
uint8_t slot_count;
PageSlot slots[PAGE_SLOTS];
uint8_t element_count;
Element elements[OLD_PAGE_ELEMENTS];
};
struct Stored
{
uint16_t version;
uint16_t size;
Page page;
};
static void convert(const Page &old, DynamicPage &page)
{
clearPage(page);
page.slot_count = old.slot_count;
memcpy(page.slots, old.slots, sizeof(page.slots));
page.element_count = old.element_count;
for (int i = 0; i < OLD_PAGE_ELEMENTS; i++)
{
const Element &o = old.elements[i];
PageElement &e = page.elements[i];
e.type = o.type;
e.x = o.x;
e.y = o.y;
e.align = o.align;
e.overlay = o.overlay;
memcpy(e.font, o.font, sizeof(e.font));
e.format = o.format;
e.language = o.language;
e.decimals = o.decimals;
memcpy(e.text, o.text, sizeof(e.text));
memcpy(e.suffix, o.suffix, sizeof(e.suffix));
memcpy(e.entity, o.entity, sizeof(e.entity));
memcpy(e.attribute, o.attribute, sizeof(e.attribute));
}
}
} // namespace v1
// Version 2 (before the Home Assistant value conversions): the elements lacked the fields
// from `show` on. Frozen copy, converted when loaded.
namespace v2
{
struct Element
{
ElementType type;
int16_t x, y;
Align align;
Overlay overlay;
char font[PAGE_NAME_SIZE];
uint8_t format;
Language language;
int8_t decimals;
char text[ELEMENT_TEXT_SIZE];
char suffix[ELEMENT_SUFFIX_SIZE];
char entity[ELEMENT_ENTITY_SIZE];
char attribute[ELEMENT_ATTRIBUTE_SIZE];
float factor;
uint8_t scroll_width;
uint8_t scroll_speed;
uint32_t target;
};
struct Page
{
uint8_t slot_count;
PageSlot slots[PAGE_SLOTS];
uint8_t element_count;
Element elements[OLD_PAGE_ELEMENTS];
};
struct Stored
{
uint16_t version;
uint16_t size;
Page page;
};
static void convert(const Page &old, DynamicPage &page)
{
clearPage(page);
page.slot_count = old.slot_count;
memcpy(page.slots, old.slots, sizeof(page.slots));
page.element_count = old.element_count;
for (int i = 0; i < OLD_PAGE_ELEMENTS; i++)
{
const Element &o = old.elements[i];
PageElement &e = page.elements[i];
e.type = o.type;
e.x = o.x;
e.y = o.y;
e.align = o.align;
e.overlay = o.overlay;
memcpy(e.font, o.font, sizeof(e.font));
e.format = o.format;
e.language = o.language;
e.decimals = o.decimals;
memcpy(e.text, o.text, sizeof(e.text));
memcpy(e.suffix, o.suffix, sizeof(e.suffix));
memcpy(e.entity, o.entity, sizeof(e.entity));
memcpy(e.attribute, o.attribute, sizeof(e.attribute));
e.factor = o.factor;
e.scroll_width = o.scroll_width;
e.scroll_speed = o.scroll_speed;
e.target = o.target;
}
}
} // namespace v2
// Version 4 (before scroll speeds below 1): scroll_speed was a whole number (uint8_t).
// Frozen copy, converted when loaded. Version 3 is converted through it.
namespace v4
{
struct Element
{
ElementType type;
int16_t x, y;
Align align;
Overlay overlay;
char font[PAGE_NAME_SIZE];
uint8_t format;
Language language;
int8_t decimals;
char text[ELEMENT_TEXT_SIZE];
char suffix[ELEMENT_SUFFIX_SIZE];
char entity[ELEMENT_ENTITY_SIZE];
char attribute[ELEMENT_ATTRIBUTE_SIZE];
float factor;
uint8_t scroll_width;
uint8_t scroll_speed;
uint32_t target;
HaShow show;
uint8_t input_unit;
bool auto_unit;
bool absolute;
char map[ELEMENT_MAP_SIZE];
uint8_t bar_width = 40;
uint8_t bar_height = 3;
float bar_max = 100;
bool bar_frame = true;
};
struct Page
{
uint8_t slot_count;
PageSlot slots[PAGE_SLOTS];
uint8_t element_count;
Element elements[OLD_PAGE_ELEMENTS];
};
struct Stored
{
uint16_t version;
uint16_t size;
Page page;
};
// Up to scroll_speed the layout is the same, so that part is copied as a block
static const size_t BEFORE_SPEED = offsetof(Element, scroll_speed);
static_assert(offsetof(PageElement, scroll_speed) >= BEFORE_SPEED &&
offsetof(PageElement, scroll_width) == offsetof(Element, scroll_width),
"same layout up to scroll_width");
static void convert(const Page &old, DynamicPage &page)
{
clearPage(page);
page.slot_count = old.slot_count;
memcpy(page.slots, old.slots, sizeof(page.slots));
page.element_count = old.element_count;
for (int i = 0; i < OLD_PAGE_ELEMENTS; i++)
{
const Element &o = old.elements[i];
PageElement &e = page.elements[i];
memcpy((void *)&e, &o, BEFORE_SPEED);
e.scroll_speed = o.scroll_speed;
e.target = o.target;
e.show = o.show;
e.input_unit = o.input_unit;
e.auto_unit = o.auto_unit;
e.absolute = o.absolute;
memcpy(e.map, o.map, sizeof(e.map));
e.bar_width = o.bar_width;
e.bar_height = o.bar_height;
e.bar_max = o.bar_max;
e.bar_frame = o.bar_frame;
}
}
} // namespace v4
// Version 3 (before the progress bar): the version 4 elements without the fields from
// `bar_width` on. The rest has the same layout, so it is copied as a block, then
// converted like version 4.
namespace v3
{
struct Element
{
ElementType type;
int16_t x, y;
Align align;
Overlay overlay;
char font[PAGE_NAME_SIZE];
uint8_t format;
Language language;
int8_t decimals;
char text[ELEMENT_TEXT_SIZE];
char suffix[ELEMENT_SUFFIX_SIZE];
char entity[ELEMENT_ENTITY_SIZE];
char attribute[ELEMENT_ATTRIBUTE_SIZE];
float factor;
uint8_t scroll_width;
uint8_t scroll_speed;
uint32_t target;
HaShow show;
uint8_t input_unit;
bool auto_unit;
bool absolute;
char map[ELEMENT_MAP_SIZE];
};
struct Page
{
uint8_t slot_count;
PageSlot slots[PAGE_SLOTS];
uint8_t element_count;
Element elements[OLD_PAGE_ELEMENTS];
};
struct Stored
{
uint16_t version;
uint16_t size;
Page page;
};
static const size_t COMMON = offsetof(Element, map) + sizeof(Element::map);
static_assert(offsetof(v4::Element, map) == offsetof(Element, map), "same layout up to map");
static void convert(const Page &old, DynamicPage &page)
{
v4::Page *mid = new v4::Page; // bar fields at their defaults
mid->slot_count = old.slot_count;
memcpy(mid->slots, old.slots, sizeof(mid->slots));
mid->element_count = old.element_count;
for (int i = 0; i < OLD_PAGE_ELEMENTS; i++)
memcpy((void *)&mid->elements[i], &old.elements[i], COMMON);
v4::convert(*mid, page);
delete mid;
}
} // namespace v3
// Version 5 (before 16 elements and the day offset of dates): 8 elements of the current
// layout without day_offset. Frozen copy, the elements are copied as a block.
namespace v5
{
struct Element
{
ElementType type;
int16_t x, y;
Align align;
Overlay overlay;
char font[PAGE_NAME_SIZE];
uint8_t format;
Language language;
int8_t decimals;
char text[ELEMENT_TEXT_SIZE];
char suffix[ELEMENT_SUFFIX_SIZE];
char entity[ELEMENT_ENTITY_SIZE];
char attribute[ELEMENT_ATTRIBUTE_SIZE];
float factor;
uint8_t scroll_width;
float scroll_speed;
uint32_t target;
HaShow show;
uint8_t input_unit;
bool auto_unit;
bool absolute;
char map[ELEMENT_MAP_SIZE];
uint8_t bar_width;
uint8_t bar_height;
float bar_max;
bool bar_frame;
};
struct Page
{
uint8_t slot_count;
PageSlot slots[PAGE_SLOTS];
uint8_t element_count;
Element elements[OLD_PAGE_ELEMENTS];
};
struct Stored
{
uint16_t version;
uint16_t size;
Page page;
};
static const size_t COMMON = offsetof(Element, bar_frame) + sizeof(Element::bar_frame);
static_assert(offsetof(PageElement, bar_frame) == offsetof(Element, bar_frame) &&
offsetof(PageElement, map) == offsetof(Element, map),
"same layout up to bar_frame");
static void convert(const Page &old, DynamicPage &page)
{
clearPage(page); // day_offset 0
page.slot_count = old.slot_count;
memcpy(page.slots, old.slots, sizeof(page.slots));
page.element_count = old.element_count;
for (int i = 0; i < OLD_PAGE_ELEMENTS; i++)
memcpy((void *)&page.elements[i], &old.elements[i], COMMON);
}
} // namespace v5
// Version 6 (before the refresh effect per page): the current layout without effect.
// Frozen copy; the elements did not change since, so they are copied as a block.
namespace v6
{
struct Page
{
uint8_t slot_count;
PageSlot slots[PAGE_SLOTS];
uint8_t element_count;
PageElement elements[PAGE_ELEMENTS];
};
struct Stored
{
uint16_t version;
uint16_t size;
Page page;
};
static_assert(offsetof(DynamicPage, elements) == offsetof(Page, elements), "same layout up to the elements");
static void convert(const Page &old, DynamicPage &page)
{
clearPage(page); // the default effect
page.slot_count = old.slot_count;
memcpy(page.slots, old.slots, sizeof(page.slots));
page.element_count = old.element_count;
memcpy((void *)page.elements, old.elements, sizeof(page.elements));
}
} // namespace v6
static_assert(sizeof(v1::Stored) != sizeof(v2::Stored) && sizeof(v2::Stored) != sizeof(v3::Stored) &&
sizeof(v3::Stored) != sizeof(v4::Stored) && sizeof(v4::Stored) != sizeof(v5::Stored) &&
sizeof(v5::Stored) != sizeof(v6::Stored) && sizeof(v6::Stored) != sizeof(StoredPage),
"the versions are told apart by their size");
static uint32_t revision = 0;
static String path(const char *name)
@@ -460,99 +86,10 @@ bool loadPage(const char *name, DynamicPage &page)
page = stored->page;
delete stored;
}
else if (size == sizeof(v6::Stored))
{
v6::Stored *stored = new v6::Stored;
ok = file.read((uint8_t *)stored, sizeof(v6::Stored)) == sizeof(v6::Stored) && stored->version == 6 &&
stored->size == sizeof(v6::Page);
if (ok)
{
v6::convert(stored->page, page);
ok = validatePage(page) == nullptr;
}
delete stored;
}
else if (size == sizeof(v5::Stored))
{
v5::Stored *stored = new v5::Stored;
ok = file.read((uint8_t *)stored, sizeof(v5::Stored)) == sizeof(v5::Stored) && stored->version == 5 &&
stored->size == sizeof(v5::Page);
if (ok)
{
v5::convert(stored->page, page);
ok = validatePage(page) == nullptr;
}
delete stored;
}
else if (size == sizeof(v4::Stored))
{
v4::Stored *stored = new v4::Stored;
ok = file.read((uint8_t *)stored, sizeof(v4::Stored)) == sizeof(v4::Stored) && stored->version == 4 &&
stored->size == sizeof(v4::Page);
if (ok)
{
v4::convert(stored->page, page);
ok = validatePage(page) == nullptr;
}
delete stored;
}
else if (size == sizeof(v3::Stored))
{
v3::Stored *stored = new v3::Stored;
ok = file.read((uint8_t *)stored, sizeof(v3::Stored)) == sizeof(v3::Stored) && stored->version == 3 &&
stored->size == sizeof(v3::Page);
if (ok)
{
v3::convert(stored->page, page);
ok = validatePage(page) == nullptr;
}
delete stored;
}
else if (size == sizeof(v2::Stored))
{
v2::Stored *stored = new v2::Stored;
ok = file.read((uint8_t *)stored, sizeof(v2::Stored)) == sizeof(v2::Stored) && stored->version == 2 &&
stored->size == sizeof(v2::Page);
if (ok)
{
v2::convert(stored->page, page);
ok = validatePage(page) == nullptr;
}
delete stored;
}
else if (size == sizeof(v1::Stored))
{
v1::Stored *stored = new v1::Stored;
ok = file.read((uint8_t *)stored, sizeof(v1::Stored)) == sizeof(v1::Stored) && stored->version == 1 &&
stored->size == sizeof(v1::Page);
if (ok)
{
v1::convert(stored->page, page);
ok = validatePage(page) == nullptr;
}
delete stored;
}
file.close();
return ok || builtinPage(name, page);
}
std::vector<String> storedPageNames()
{
return storedNames(DIR, EXTENSION);
}
bool pageInOldFormat(const char *name)
{
if (!storedPage(name))
return false;
File file = LittleFS.open(path(name), "r");
if (!file)
return false;
bool old = file.size() != sizeof(StoredPage);
file.close();
return old;
}
const char *savePage(const char *name, const DynamicPage &page)
{
if (!validBitmapName(name))
-4
View File
@@ -31,10 +31,6 @@ bool pageExists(const char *name);
// A built-in page that no stored page replaces
bool builtinPageExists(const char *name);
bool loadPage(const char *name, DynamicPage &page);
// The stored pages (slow: lists the directory) and whether one is stored in an older
// format (loadPage() converts it, savePage() writes the current one)
std::vector<String> storedPageNames();
bool pageInOldFormat(const char *name);
// Return nullptr on success, otherwise a message for the user. savePage() validates the
// page, but does not check that its bitmaps and fonts exist.