Weather forecast page, 16 elements per page, attribute paths, examples from the display

- Home Assistant attributes can be paths into lists (forecast.1.condition).
- Date elements can show a day ahead (day offset 0-7).
- Pages hold 16 elements (page format 6; the old formats are frozen at 8).
  Pages are reset in place (clearPage), not through a 4 KB temporary.
- Verwachting page: today (icon, temperature, max/min) and the next 2
  days with 7 x 7 icons (weer7 <condition>), from a template sensor
  (docs/home-assistant.md).
- examples/home-pages.json is exported from the display with all pages
  and their icons, private entity id parts replaced by placeholders
  (tools/export_examples.py replaces tools/make_example_pages.py).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-16 22:36:29 +02:00
1 parent 839e86e8f1
commit a6b946e17c
33 files changed
+1918 -1050

No files matched your search

+92 -13
View File
@@ -8,7 +8,9 @@
static const char *DIR = "/pages";
static const char *EXTENSION = ".pg";
// Increase when DynamicPage changes; older files are ignored unless converted below
static const uint16_t VERSION = 5;
static const uint16_t VERSION = 6;
// Elements per page up to version 5 (the old formats below are frozen with it)
static const int OLD_PAGE_ELEMENTS = 8;
struct StoredPage
{
@@ -42,7 +44,7 @@ struct Page
uint8_t slot_count;
PageSlot slots[PAGE_SLOTS];
uint8_t element_count;
Element elements[PAGE_ELEMENTS];
Element elements[OLD_PAGE_ELEMENTS];
};
struct Stored
@@ -54,11 +56,11 @@ struct Stored
static void convert(const Page &old, DynamicPage &page)
{
page = DynamicPage();
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 < PAGE_ELEMENTS; i++)
for (int i = 0; i < OLD_PAGE_ELEMENTS; i++)
{
const Element &o = old.elements[i];
PageElement &e = page.elements[i];
@@ -108,7 +110,7 @@ struct Page
uint8_t slot_count;
PageSlot slots[PAGE_SLOTS];
uint8_t element_count;
Element elements[PAGE_ELEMENTS];
Element elements[OLD_PAGE_ELEMENTS];
};
struct Stored
@@ -120,11 +122,11 @@ struct Stored
static void convert(const Page &old, DynamicPage &page)
{
page = DynamicPage();
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 < PAGE_ELEMENTS; i++)
for (int i = 0; i < OLD_PAGE_ELEMENTS; i++)
{
const Element &o = old.elements[i];
PageElement &e = page.elements[i];
@@ -187,7 +189,7 @@ struct Page
uint8_t slot_count;
PageSlot slots[PAGE_SLOTS];
uint8_t element_count;
Element elements[PAGE_ELEMENTS];
Element elements[OLD_PAGE_ELEMENTS];
};
struct Stored
@@ -205,11 +207,11 @@ static_assert(offsetof(PageElement, scroll_speed) >= BEFORE_SPEED &&
static void convert(const Page &old, DynamicPage &page)
{
page = DynamicPage();
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 < PAGE_ELEMENTS; i++)
for (int i = 0; i < OLD_PAGE_ELEMENTS; i++)
{
const Element &o = old.elements[i];
PageElement &e = page.elements[i];
@@ -264,7 +266,7 @@ struct Page
uint8_t slot_count;
PageSlot slots[PAGE_SLOTS];
uint8_t element_count;
Element elements[PAGE_ELEMENTS];
Element elements[OLD_PAGE_ELEMENTS];
};
struct Stored
@@ -283,15 +285,80 @@ static void convert(const Page &old, DynamicPage &page)
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 < PAGE_ELEMENTS; i++)
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
static_assert(sizeof(v1::Stored) != sizeof(v2::Stored) && sizeof(v2::Stored) != sizeof(v3::Stored) &&
sizeof(v3::Stored) != sizeof(v4::Stored) && sizeof(v4::Stored) != sizeof(StoredPage),
sizeof(v3::Stored) != sizeof(v4::Stored) && sizeof(v4::Stored) != sizeof(v5::Stored) &&
sizeof(v5::Stored) != sizeof(StoredPage),
"the versions are told apart by their size");
static uint32_t revision = 0;
@@ -362,6 +429,18 @@ bool loadPage(const char *name, DynamicPage &page)
page = stored->page;
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;