Public Access
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:
1 parent
968492ca35
commit
9959c1aa74
14 files changed
+28
-996
No files matched your search
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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))
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user