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

+1 -1
View File
@@ -26,7 +26,7 @@ const char *builtinPageName(const char *name)
static void clockPage(DynamicPage &page)
{
page = DynamicPage();
clearPage(page);
page.slot_count = 2;
page.slots[0].start = 0;
strcpy(page.slots[0].bitmap, "Pikachu");
+33 -2
View File
@@ -127,7 +127,7 @@ static bool strncasecmpAscii(const char *a, const char *b, size_t n)
static bool validAttribute(const char *attribute)
{
for (const char *c = attribute; *c; c++)
if (!isalnum((unsigned char)*c) && *c != '_')
if (!isalnum((unsigned char)*c) && *c != '_' && *c != '.') // dots: a path, e.g. forecast.1.condition
return false;
return true;
}
@@ -196,6 +196,8 @@ const char *validatePage(const DynamicPage &page)
return "The list of texts can only contain plain characters, up to 63";
if (e.format >= formatCount(e))
return "Unknown format";
if (e.day_offset < 0 || e.day_offset > DAY_OFFSET_MAX)
return "Date: 0-7 days after today";
if (!isfinite(e.factor) || e.factor == 0)
return "The multiplier must be a number other than 0";
if (e.scroll_width > 2 * BITMAP_WIDTH ||
@@ -368,8 +370,37 @@ static int language(const PageElement &e)
return e.language < Language::Count ? (int)e.language : 0;
}
static void formatDate(const PageElement &e, const LocalTime &t, char *out, size_t size)
static int daysInMonth(int year, int month)
{
static const int DAYS[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
bool leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
return month == 2 && leap ? 29 : DAYS[(month - 1 + 12) % 12];
}
LocalTime addDays(const LocalTime &time, int days)
{
LocalTime t = time;
if (t.month < 1 || t.month > 12)
return t;
for (int i = 0; i < days; i++)
{
t.weekday = (t.weekday + 1) % 7;
if (++t.day > daysInMonth(t.year, t.month))
{
t.day = 1;
if (++t.month > 12)
{
t.month = 1;
t.year++;
}
}
}
return t;
}
static void formatDate(const PageElement &e, const LocalTime &today, char *out, size_t size)
{
LocalTime t = addDays(today, e.day_offset);
int lang = language(e);
int wd = t.weekday >= 0 && t.weekday < 7 ? t.weekday : 0;
int mo = t.month >= 1 && t.month <= 12 ? t.month - 1 : 0;
+16 -1
View File
@@ -1,5 +1,6 @@
#pragma once
#include <new>
#include <stddef.h>
#include <stdint.h>
@@ -16,7 +17,7 @@
const int PAGE_NAME_SIZE = BITMAP_NAME_MAX + 1;
const int PAGE_SLOTS = 8;
const int PAGE_ELEMENTS = 8;
const int PAGE_ELEMENTS = 16;
const int ELEMENT_TEXT_SIZE = 33;
const int ELEMENT_SUFFIX_SIZE = 17;
const int ELEMENT_ENTITY_SIZE = 65;
@@ -78,6 +79,8 @@ const uint8_t DATE_NUMERIC_YEAR = 4; // 15-10-2026
const uint8_t DATE_WEEKDAY = 5; // donderdag / Thursday
const uint8_t DATE_WEEKDAY_SHORT = 6; // do / Thu
const uint8_t DATE_FORMAT_COUNT = 7;
// Date elements can show a later day (a forecast's weekday)
const int8_t DAY_OFFSET_MAX = 7;
// Countdown formats
const uint8_t COUNTDOWN_SMART = 0; // 12 dagen, 5 uur, 34 min, then 0:45
@@ -171,6 +174,8 @@ struct PageElement
uint8_t bar_height = 3;
float bar_max = 100; // Bar: the value (after the multiplier) that fills it
bool bar_frame = true; // Bar: a line around it, filled inside
// Added in page format 6
int8_t day_offset = 0; // Date: days after today (0-DAY_OFFSET_MAX), e.g. 1: tomorrow
};
struct DynamicPage
@@ -181,6 +186,13 @@ struct DynamicPage
PageElement elements[PAGE_ELEMENTS];
};
// Back to an empty page with the defaults, without `page = DynamicPage()`: that can put a
// copy (about 4 KB) on the stack
inline void clearPage(DynamicPage &page)
{
new (&page) DynamicPage();
}
// Returns nullptr when the page is fine, otherwise a message for the user. Does not check
// that the bitmaps and fonts exist.
const char *validatePage(const DynamicPage &page);
@@ -241,5 +253,8 @@ bool parseTimestamp(const char *text, int64_t &seconds);
bool parseTarget(const char *text, uint32_t &minutes);
// out must hold 17 characters
void formatTarget(uint32_t minutes, char *out);
// The date `days` (0 or more) after this one, with the weekday; the time stays
LocalTime addDays(const LocalTime &time, int days);
// The local time in minutes since 2000-01-01 00:00 (seconds left out)
uint32_t minutesSince2000(const LocalTime &time);