Custom icon per tile: 150 MDI icons in firmware and editor

- screen_manager/app/tile_icons.py is the single icon list (12 groups, 20 media
  and music icons); tools/generate_icons.py writes it into the three icon fonts
  of both boards and builds the editor's subset font.
- Firmware 0.2.18 shows o.icon when its fonts carry the glyph, else the domain
  icon. The app sends the chosen icon, or Home Assistant's own mdi icon on auto.
- Tile sheet gets an icon picker with search; the mockup shows the icon the
  screen actually draws.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-13 11:16:57 +02:00
parent efe82c9b09
commit ef0ca8b350
23 changed files with 1371 additions and 498 deletions
+4 -3
View File
@@ -539,8 +539,9 @@ orientation preset first. A tile is not a single block — it is six places in
call) and change the number:
1. **Substitutions** — a `TILE9_*` block. Copy an existing one wholesale.
2. **Font glyph** — add `MDI_GLYPH_9` and list it under `materialdesign_icons`.
A glyph that is not listed renders as blank space.
2. **Icon** — pick `TILE9_ICON` from `screen_manager/app/tile_icons.py`; the
icon fonts contain exactly that set. A glyph outside it renders as blank
space; add it there and run `tools/generate_icons.py`.
3. **Sensors** — under `sensor:` / `text_sensor:`. `ha_state_tile9` is the only
required one. Add the rest only if the tile actually uses them:
`tile9_brightness` (light), `tile9_percentage` + `tile9_preset` (fan),
@@ -567,4 +568,4 @@ PSRAM, so free heap is the real ceiling on how many tiles fit.
3. Click the icon → copy the Unicode codepoint (e.g. `F0769`)
4. Format it as `"\U000F0769"` (always 8 hex digits, padded with leading zeros)
Make sure the glyph is listed in the `glyphs:` section of the MDI font definition in the YAML (search for `materialdesign_icons`). If you use an icon that is not listed there, it will show as a blank space.
The icon fonts contain the tile icon set from `screen_manager/app/tile_icons.py` (150 icons plus the weather glyphs). An icon outside that set shows as a blank space: add it to that file and run `python3 tools/generate_icons.py` and `python3 tools/generate_packages.py`.
+1
View File
@@ -50,6 +50,7 @@ struct Tile {
float battery = NAN, volume = NAN;
uint32_t supported = 0, background = 0;
bool transparent = false; // "Achtergrond: geen": card fill and border hidden, contents unchanged.
std::string icon; // UTF-8 glyph of a chosen icon the icon fonts contain; empty keeps the domain icon.
std::string revision, pending_revision;
uint32_t pending_since = 0;
bool pending = false, confirmed = false, local_feedback = false;
+11 -1
View File
@@ -1,6 +1,7 @@
#pragma once
#include "runtime_model.h"
#include "tile_palette.h"
#include "tile_icon.h"
#include "screen_settings.h"
#include "esphome/core/preferences.h"
#include "cyd_ui.h"
@@ -81,6 +82,11 @@ struct Widgets {
};
constexpr unsigned POINT_BUFFER = 128;
inline std::array<Widgets, 10> widgets;
// All icon fonts carry the same generated glyph set, so the first bound one answers for all.
inline bool has_icon_glyph(uint32_t codepoint) {
for (auto &w : widgets) if (w.icon_font) { lv_font_glyph_dsc_t dsc; return lv_font_get_glyph_dsc(w.icon_font, &dsc, codepoint, 0); }
return false;
}
inline bool fresh() { return model.ready() && esphome::millis() - last_received < 95000; }
inline float number(JsonVariant value, float fallback = NAN) {
if (!value.is<float>() && !value.is<int>()) return fallback;
@@ -164,6 +170,9 @@ inline std::string receive(const std::string &payload) {
std::string background=string(options["background"],16);
tile.background = tile_palette::color(background);
tile.transparent = tile_palette::transparent(background);
// An icon these fonts lack (a newer set than this firmware) keeps the domain icon.
uint32_t icon = tile_icon::codepoint(string(options["icon"], 8));
tile.icon = icon && has_icon_glyph(icon) ? tile_icon::utf8(icon) : "";
tile.tap = string(options["tap"]); if (tile.tap.empty()) tile.tap="auto";
tile.display = string(options["display"]); if (tile.display.empty()) tile.display="standard";
tile.inline_control = string(options["inline"]); if (tile.inline_control.empty()) tile.inline_control="none";
@@ -512,6 +521,7 @@ inline const char *weather_text(const std::string &condition) {
return condition.c_str();
}
inline const char *icon_for(const Tile &tile) {
if (!tile.icon.empty()) return tile.icon.c_str();
auto d = tile.domain();
if (d == "light") return "\U000F0335";
if (d == "climate") return "\U000F001B";
@@ -790,7 +800,7 @@ inline void render_forecast(Widgets &w,const Tile &t,bool large,int width,int he
int left=large?150:96,icon_h=lv_font_get_line_height(w.icon_font),temp_h=lv_font_get_line_height(temp_font),text_h=lv_font_get_line_height(w.value_font);
int block=std::max(icon_h,temp_h)+2+text_h,y=std::max(0,(height-block)/2);
char b[24];snprintf(b,sizeof(b),"%.0f°",t.current);
auto *icon=part_label(w,0,w.icon_font,0,y+(std::max(icon_h,temp_h)-icon_h)/2,icon_h+4,LV_TEXT_ALIGN_LEFT,icon_for(t));
auto *icon=part_label(w,0,w.icon_font,0,y+(std::max(icon_h,temp_h)-icon_h)/2,icon_h+4,LV_TEXT_ALIGN_LEFT,t.available()?weather_icon(t.state):"\U000F0595");
lv_obj_set_width(icon,lv_font_get_line_height(w.icon_font)+4);
part_label(w,1,temp_font,icon_h+6,y+(std::max(icon_h,temp_h)-temp_h)/2,left-icon_h-6,LV_TEXT_ALIGN_LEFT,std::isfinite(t.current)?b:"");
part_label(w,2,w.value_font,0,y+std::max(icon_h,temp_h)+2,left-4,LV_TEXT_ALIGN_LEFT,weather_text(t.state));
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include <cstdint>
#include <string>
namespace tile_icon {
// ESP Screen Manager sends a chosen icon as its Material Design Icons codepoint
// in hex ("F06B5"). Anything else is 0: the tile keeps its domain icon.
inline uint32_t codepoint(const std::string &hex) {
if (hex.size() != 5) return 0;
uint32_t value = 0;
for (char c : hex) {
int digit = c >= '0' && c <= '9' ? c - '0' : c >= 'A' && c <= 'F' ? c - 'A' + 10 : c >= 'a' && c <= 'f' ? c - 'a' + 10 : -1;
if (digit < 0) return 0;
value = value << 4 | static_cast<uint32_t>(digit);
}
// MDI lives in Supplementary Private Use Area-A.
return value >= 0xF0000 && value <= 0xFFFFD ? value : 0;
}
// Four-byte UTF-8 for a label; empty for 0.
inline std::string utf8(uint32_t cp) {
if (cp < 0x10000 || cp > 0x10FFFF) return {};
return {static_cast<char>(0xF0 | cp >> 18), static_cast<char>(0x80 | (cp >> 12 & 0x3F)),
static_cast<char>(0x80 | (cp >> 6 & 0x3F)), static_cast<char>(0x80 | (cp & 0x3F))};
}
} // namespace tile_icon
+4 -4
View File
@@ -20,12 +20,12 @@ def demo_layout():
return validate_layout({'title': 'Demo kaarten', 'tiles': [
{'entity': 'screen.clock', 'name': '', 'options': {'display': 'analog', 'background': 'none'}},
{'entity': 'weather.demo', 'name': 'Buiten', 'options': {'display': 'forecast'}},
{'entity': 'sensor.demo_temperatuur', 'name': 'Woonkamer', 'options': {'display': 'graph'}},
{'entity': 'sensor.demo_temperatuur', 'name': 'Woonkamer', 'options': {'display': 'graph', 'icon': 'thermometer'}},
{'entity': 'person.demo', 'name': 'Max', 'options': {}},
{'entity': 'timer.demo', 'name': 'Eieren', 'options': {}},
{'entity': 'timer.demo', 'name': 'Eieren', 'options': {'icon': 'chef-hat'}},
{'entity': 'sun.sun', 'name': 'Zon', 'options': {'display': 'sunpath'}},
{'entity': 'sensor.demo_energie', 'name': 'Verbruik vandaag', 'options': {'display': 'graph', 'size': 'wide'}},
{'entity': 'light.demo', 'name': 'Tafellamp', 'options': {'display': 'watch', 'size': 'wide'}},
{'entity': 'light.demo', 'name': 'Tafellamp', 'options': {'display': 'watch', 'size': 'wide', 'icon': 'lamp'}},
{'entity': 'weather.demo_standaard', 'name': 'Weer nu', 'options': {}},
]})
@@ -36,7 +36,7 @@ def demo_states(now):
'weather.demo_standaard': {'state': 'rainy', 'attributes': {'temperature': 12.0, 'temperature_unit': '°C'}},
'sensor.demo_temperatuur': {'state': '21.5', 'attributes': {'unit_of_measurement': '°C'}},
'sensor.demo_energie': {'state': '7.42', 'attributes': {'unit_of_measurement': 'kWh'}},
'person.demo': {'state': 'home', 'attributes': {}},
'person.demo': {'state': 'home', 'attributes': {'icon': 'mdi:account-child'}},
'timer.demo': {'state': 'active', 'attributes': {'finishes_at': end.isoformat(), 'duration': '0:05:00', 'remaining': '0:05:00'}},
'sun.sun': {'state': 'above_horizon', 'attributes': {'next_rising': (now + timedelta(hours=9)).isoformat(), 'next_setting': (now + timedelta(hours=2)).isoformat()}},
'light.demo': {'state': 'on', 'attributes': {'brightness': 180}},
+17
View File
@@ -160,6 +160,23 @@ geweigerd. De ronde draait één scherm tegelijk, wacht op `Schermfirmware >=`
doelversie en één minuut stabiliteit, en stopt bij de eerste fout. Alleen de
nachtelijke ronde schrijft een `persistent_notification` in HA.
### Compatibiliteit 0.2.18 / firmware 0.2.18
Opslagversie en tegelprotocol blijven 1. `tiles[].options.icon` is additief:
`auto` of een naam uit `screen_manager/app/tile_icons.py`. Op de draad stuurt de
manager in `o.icon` alleen de opgeloste codepoint in hex (`F06B5`): de gekozen
naam, of bij `auto` het `mdi:`-icoon uit de HA-attributen als dat in de set zit.
Oudere firmware negeert het veld; nieuwe firmware controleert of de glyph in de
font zit en valt anders terug op het domeinicoon. Daarom geen `min_firmware`.
Een oude editor die `icon` weglaat behoudt de opgeslagen keuze.
`tile_icons.py` is de enige lijst. `tools/generate_icons.py` schrijft de glyphs
als YAML-anker in de drie MDI-fonts van beide bordprofielen en bouwt
`static/tile-icons.woff` voor de editor (met left bearing gelijk aan xMin, anders
staan iconen in de browser uit het midden); draai daarna `generate_packages.py`.
De substituties `MDI_GLYPH_*` zijn vervallen: `TILEn_ICON` in handmatige
profielen moet uit de set komen. Geen gewijzigde preferences of sleutels.
### Compatibiliteit 0.2.12 / firmware 0.2.14
Opslagversie en tegelprotocol blijven 1. Additief: `tiles[].options.size`
+3 -2
View File
@@ -176,7 +176,8 @@ actie, terugmelding vanuit HA en lange druk. Controleer ook dat externe
wijzigingen in HA op het scherm verschijnen. Voer daarna de
[acceptatietest](ACCEPTATIE.md) uit.
Extra iconen vereisen een glyph in de `materialdesign_icons`-fontlijst. De
bundel bevat de fonts lokaal. De oudere uitgebreide
De iconfonts bevatten de tegeliconenset uit `screen_manager/app/tile_icons.py`;
kies `TILEn_ICON` daaruit. Een ander icoon voeg je daar toe en draai je
`tools/generate_icons.py`. De bundel bevat de fonts lokaal. De oudere uitgebreide
[TILE_CONFIGURATION.md](../TILE_CONFIGURATION.md) beschrijft ook het oude
scrollprofiel; gebruik die niet als hardware-/layoutinstructie voor dit bord.
+32
View File
@@ -0,0 +1,32 @@
# App 0.2.18 / firmware 0.2.18 — eigen icoon per tegel
## Wijzigingen
- `screen_manager/app/tile_icons.py`: 150 kiesbare MDI-iconen in twaalf groepen
(20 voor media en muziek) plus 9 vaste firmwareglyphs; codepoints gecontroleerd
tegen `fonts/materialdesignicons-webfont.ttf`.
- Firmware: `tile_icon.h` (hex → codepoint → UTF-8), `Tile::icon`, `icon_for()`
gebruikt een gekozen icoon als de font de glyph bevat.
- App: validatie van `options.icon`, `o.icon` met codepoint (gekozen of uit HA),
entiteiticonen in de inventaris, icoonkiezer met zoeken in het tegelpaneel.
## Tests
- `.venv-portal/bin/python -m unittest discover -s tests`: 69 tests OK, waaronder
`tests/test_tile_icons.py` (set in alle zes fonts en beide pakketten, elke
firmwareglyph in de set, editorvoorspelling gelijk aan `icon_for`/`weather_icon`,
validatie, draadformaat, discovery, oude editor behoudt icoon).
- Alle `tests/*.cpp` OK, waaronder `test_tile_icon.cpp`.
- `tools/generate_packages.py --check` en `tools/generate_icons.py --check`: actueel.
- `esphome compile`: CYD 1.361.735 bytes flash (74,2%, was 1.339.824), RAM 30,2%;
Guition 1.454.503 bytes (17,9%).
- USB-flash na `esptool chip_id` (`usbserial-130` = ESP32/CYD, `usbserial-210` =
ESP32-S3/Guition); uploadlog toont de juiste `firmware.bin`. `device_info`:
beide 0.2.18, gecompileerd 11:10:02 (CYD) en 11:10:52 (Guition).
- Guition-render via `send_layout.py` + `capture_ui.py`: thermometer in de
grafiekstrook (mini-font), `account-child` automatisch uit HA, koksmuts op de
timer, lamp op de dubbelbrede grote waarde (watch-font). Max bevestigde dat
het op de schermen goed werkt.
- Lokale editor met nep-HA: kiezer, zoeken op "muziek", live mockup en kop,
opslaan stuurt `F04C3`/`F0769`/`F0FCE`; hint bij firmware 0.2.17. Iconen in de
browser gemeten gecentreerd na de bearing-fix (gemiddeld 0,3 px op 100 px).
+166 -118
View File
@@ -1,7 +1,7 @@
# Guition ESP32-S3-4848S040 / 4-inch EU wallbox, 480x480 IPS + GT911.
# Use guition-device.yaml; see docs/GUITION.md. CYD hardware is a separate profile.
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.17"
SCREEN_FIRMWARE_VERSION: "0.2.18"
DYNAMIC_TILES: "false"
DASHBOARD_INBOX_INTERNAL: "true"
DEVICE_NAME: "guition-wallbox"
@@ -49,21 +49,9 @@ substitutions:
# ============================================================
# FONT GLYPHS
# List every unique MDI icon used across your tiles — each value must appear exactly once.
# Reusing the same icon on multiple tiles is fine: just list that glyph once here.
# If you use fewer than 6 unique icons, leave the extra slots with their default values
# (unused glyphs add a few bytes to the font but cause no errors).
# The icon fonts carry the full tile icon set from screen_manager/app/tile_icons.py
# (tools/generate_icons.py). Pick TILEn_ICON from that set; no glyph list to maintain.
# ============================================================
MDI_GLYPH_1: "\U000F001B"
MDI_GLYPH_2: "\U000F0387" # mdi:music
MDI_GLYPH_3: "\U000F02E3"
MDI_GLYPH_4: "\U000F059C"
MDI_GLYPH_5: "\U000F0425"
MDI_GLYPH_6: "\U000F070D"
MDI_GLYPH_7: "\U000F06B5" # mdi:lamp
MDI_GLYPH_8: "\U000F06EE" # mdi:mailbox
MDI_GLYPH_9: "\U000F04B9" # mdi:sofa
MDI_GLYPH_10: "\U000F002A" # mdi:alarm-light
# ============================================================
# TILE 1
@@ -73,7 +61,7 @@ substitutions:
TILE1_ENTITY: "light.voorbeeld_lamp" # main Home Assistant entity for this tile
TILE1_STATE_ENTITY: "light.voorbeeld_lamp" # entity used for active/inactive state styling
TILE1_TITLE: "Lamp" # tile title shown in the UI
TILE1_ICON: "\U000F001B" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE1_ICON: "\U000F001B" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE1_TYPE: "light" # tile type: light | fan | switch | scene | script | cover | climate
TILE1_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE1_LONGPRESS: "slider" # long press mode: none | slider | action
@@ -122,7 +110,7 @@ substitutions:
TILE2_ENTITY: "switch.voorbeeld_stekker" # main Home Assistant entity for this tile
TILE2_STATE_ENTITY: "switch.voorbeeld_stekker" # entity used for active/inactive state styling
TILE2_TITLE: "Stekker" # tile title shown in the UI
TILE2_ICON: "\U000F0387" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE2_ICON: "\U000F0387" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE2_TYPE: "switch" # tile type: light | fan | switch | scene | script | cover | climate
TILE2_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE2_LONGPRESS: "none" # long press mode: none | slider | action
@@ -171,7 +159,7 @@ substitutions:
TILE3_ENTITY: "scene.voorbeeld_avond" # main Home Assistant entity for this tile
TILE3_STATE_ENTITY: "scene.voorbeeld_avond" # entity used for active/inactive state styling
TILE3_TITLE: "Avond" # tile title shown in the UI
TILE3_ICON: "\U000F02E3" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE3_ICON: "\U000F02E3" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE3_TYPE: "scene" # tile type: light | fan | switch | scene | script | cover | climate
TILE3_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE3_LONGPRESS: "none" # long press mode: none | slider | action
@@ -220,7 +208,7 @@ substitutions:
TILE4_ENTITY: "script.voorbeeld_uit" # main Home Assistant entity for this tile
TILE4_STATE_ENTITY: "script.voorbeeld_uit" # entity used for active/inactive state styling
TILE4_TITLE: "Alles uit" # tile title shown in the UI
TILE4_ICON: "\U000F059C" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE4_ICON: "\U000F059C" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE4_TYPE: "script" # tile type: light | fan | switch | scene | script | cover | climate
TILE4_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE4_LONGPRESS: "none" # long press mode: none | slider | action
@@ -269,7 +257,7 @@ substitutions:
TILE5_ENTITY: "climate.voorbeeld_airco" # main Home Assistant entity for this tile
TILE5_STATE_ENTITY: "climate.voorbeeld_airco" # entity used for active/inactive state styling
TILE5_TITLE: "Airco" # tile title shown in the UI
TILE5_ICON: "\U000F0425" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE5_ICON: "\U000F0425" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE5_TYPE: "climate" # tile type: light | fan | switch | scene | script | cover | climate
TILE5_TAP_ACTION: "toggle" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE5_LONGPRESS: "slider" # long press mode: none | slider | action
@@ -318,7 +306,7 @@ substitutions:
TILE6_ENTITY: "vacuum.voorbeeld_robot" # main Home Assistant entity for this tile
TILE6_STATE_ENTITY: "vacuum.voorbeeld_robot" # entity used for active/inactive state styling
TILE6_TITLE: "Stofzuiger" # tile title shown in the UI
TILE6_ICON: "\U000F070D" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE6_ICON: "\U000F070D" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE6_TYPE: "vacuum" # tile type: light | fan | switch | scene | script | cover | climate
TILE6_TAP_ACTION: "custom" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE6_LONGPRESS: "vacuum" # long press mode: none | slider | action
@@ -367,7 +355,7 @@ substitutions:
TILE7_ENTITY: "light.ongebruikt_7" # main Home Assistant entity for this tile
TILE7_STATE_ENTITY: "light.ongebruikt_7" # entity used for active/inactive state styling
TILE7_TITLE: "Tegel 7" # tile title shown in the UI
TILE7_ICON: "\U000F06B5" # mdi:lamp — also listed in MDI_GLYPH_7 above
TILE7_ICON: "\U000F06B5" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE7_TYPE: "light" # tile type: light | fan | switch | scene | script | cover | climate
TILE7_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset
TILE7_LONGPRESS: "none" # long press mode: none | slider | action | vacuum
@@ -411,7 +399,7 @@ substitutions:
TILE8_ENTITY: "light.ongebruikt_8" # main Home Assistant entity for this tile
TILE8_STATE_ENTITY: "light.ongebruikt_8" # entity used for active/inactive state styling
TILE8_TITLE: "Tegel 8" # tile title shown in the UI
TILE8_ICON: "\U000F06EE" # mdi:mailbox — also listed in MDI_GLYPH_8 above
TILE8_ICON: "\U000F06EE" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE8_TYPE: "light" # generiek type; het echte gedrag komt uit TAP_SERVICE hieronder
TILE8_TAP_ACTION: "auto" # activate + TAP_SERVICE = losse service call
TILE8_LONGPRESS: "none" # long press stuurt alleen het event naar HA
@@ -455,7 +443,7 @@ substitutions:
TILE9_ENTITY: "light.ongebruikt_9" # main Home Assistant entity for this tile
TILE9_STATE_ENTITY: "light.ongebruikt_9" # entity used for active/inactive state styling
TILE9_TITLE: "Tegel 9" # tile title shown in the UI
TILE9_ICON: "\U000F04B9" # mdi:sofa — also listed in MDI_GLYPH_9 above
TILE9_ICON: "\U000F04B9" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE9_TYPE: "light" # tile type: light | fan | switch | scene | script | cover | climate
TILE9_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset
TILE9_LONGPRESS: "none" # long press mode: none | slider | action | vacuum
@@ -498,7 +486,7 @@ substitutions:
TILE10_ENTITY: "light.ongebruikt_10" # main Home Assistant entity for this tile
TILE10_STATE_ENTITY: "light.ongebruikt_10" # entity used for active/inactive state styling
TILE10_TITLE: "Tegel 10" # tile title shown in the UI
TILE10_ICON: "\U000F002A" # mdi:alarm-light — also listed in MDI_GLYPH_10 above
TILE10_ICON: "\U000F002A" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE10_TYPE: "light" # generiek aan/uit-type; de service hieronder doet het echte werk
TILE10_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset
TILE10_LONGPRESS: "none" # long press stuurt alleen het event naar HA
@@ -1064,116 +1052,176 @@ font:
- file: 'fonts/materialdesignicons-webfont.ttf'
id: materialdesign_icons
size: 42
glyphs:
- "\U000F0335"
- "\U000F0210"
- "\U000F111C"
- "\U000F029A"
- "${MDI_GLYPH_1}"
- "${MDI_GLYPH_2}"
- "${MDI_GLYPH_3}"
- "${MDI_GLYPH_4}"
- "${MDI_GLYPH_5}"
- "${MDI_GLYPH_6}"
- "${MDI_GLYPH_7}"
- "${MDI_GLYPH_8}"
- "${MDI_GLYPH_9}"
- "${MDI_GLYPH_10}"
- "\U000F012C" # check
glyphs: &tile_icons # GENERATED by tools/generate_icons.py from screen_manager/app/tile_icons.py
- "\U000F0335" # lightbulb
- "\U000F1253" # lightbulb-group
- "\U000F0769" # ceiling-light
- "\U000F1793" # chandelier
- "\U000F06B5" # lamp
- "\U000F08DD" # floor-lamp
- "\U000F095F" # desk-lamp
- "\U000F091D" # wall-sconce-flat
- "\U000F179B" # light-recessed
- "\U000F04C9" # spotlight-beam
- "\U000F1051" # led-strip-variant
- "\U000F1054" # outdoor-lamp
- "\U000F02DC" # home
- "\U000F04B9" # sofa
- "\U000F02E3" # bed
- "\U000F0A70" # silverware-fork-knife
- "\U000F0B7C" # chef-hat
- "\U000F09A0" # shower
- "\U000F1818" # bathtub
- "\U000F09AB" # toilet
- "\U000F1239" # desk
- "\U000F06D9" # garage
- "\U000F0E2E" # fireplace
- "\U000F050F" # thermometer
- "\U000F0393" # thermostat
- "\U000F001B" # air-conditioner
- "\U000F0438" # radiator
- "\U000F1A43" # heat-pump
- "\U000F0238" # fire
- "\U000F0717" # snowflake
- "\U000F058E" # water-percent
- "\U000F0210" # fan
- "\U000F1797" # ceiling-fan
- "\U000F0D44" # air-purifier
- "\U000F07E4" # molecule-co2
- "\U000F0599" # weather-sunny
- "\U000F0594" # weather-night
- "\U000F0590" # weather-cloudy
- "\U000F0595" # weather-partly-cloudy
- "\U000F0590" # weather-cloudy
- "\U000F0597" # weather-rainy
- "\U000F0596" # weather-pouring
- "\U000F0598" # weather-snowy
- "\U000F067F" # weather-snowy-rainy
- "\U000F059D" # weather-windy
- "\U000F0594" # weather-night
- "\U000F059C" # weather-sunset-up
- "\U000F0387" # music-note
- "\U000F075A" # music
- "\U000F0CB8" # playlist-music
- "\U000F0025" # album
- "\U000F099A" # record-player
- "\U000F04C7" # spotify
- "\U000F0439" # radio
- "\U000F0994" # podcast
- "\U000F04C3" # speaker
- "\U000F0D38" # speaker-multiple
- "\U000F17DB" # soundbar
- "\U000F02CB" # headphones
- "\U000F067D" # piano
- "\U000F0771" # guitar-acoustic
- "\U000F0502" # television
- "\U000F042E" # projector
- "\U000F0FCE" # movie-open
- "\U000F0118" # cast
- "\U000F0297" # gamepad-variant
- "\U000F0EC5" # remote-tv
- "\U000F033E" # lock
- "\U000F0FC6" # lock-open-variant
- "\U000F068A" # shield-home
- "\U000F078F" # alarm-light
- "\U000F009A" # bell
- "\U000F12E6" # doorbell
- "\U000F07AE" # cctv
- "\U000F0D91" # motion-sensor
- "\U000F081B" # door-closed
- "\U000F081C" # door-open
- "\U000F11DB" # window-closed-variant
- "\U000F0392" # smoke-detector
- "\U000F1502" # water-alert
- "\U000F072A" # washing-machine
- "\U000F0917" # tumble-dryer
- "\U000F0AAC" # dishwasher
- "\U000F0290" # fridge
- "\U000F04DE" # stove
- "\U000F0C99" # microwave
- "\U000F0CD3" # toaster-oven
- "\U000F109F" # coffee-maker
- "\U000F05FA" # kettle
- "\U000F070D" # robot-vacuum
- "\U000F11F7" # robot-mower
- "\U000F042B" # printer-3d
- "\U000F06A5" # power-plug
- "\U000F07E7" # power-socket-eu
- "\U000F0322" # laptop
- "\U000F0469" # router-wireless
- "\U000F0241" # flash
- "\U000F0A72" # solar-power
- "\U000F0D9B" # solar-panel
- "\U000F1901" # home-battery
- "\U000F12A3" # battery-high
- "\U000F05F1" # ev-station
- "\U000F0B6C" # car-electric
- "\U000F1A57" # meter-electric
- "\U000F1A59" # meter-gas
- "\U000F058C" # water
- "\U000F0F92" # water-boiler
- "\U000F029A" # gauge
- "\U000F111C" # window-shutter
- "\U000F111E" # window-shutter-open
- "\U000F00AC" # blinds
- "\U000F1011" # blinds-open
- "\U000F1A6C" # roller-shade-closed
- "\U000F1846" # curtains
- "\U000F1847" # curtains-closed
- "\U000F1B88" # awning-outline
- "\U000F024A" # flower
- "\U000F0531" # tree
- "\U000F0E66" # sprout
- "\U000F1510" # grass
- "\U000F105F" # sprinkler
- "\U000F0606" # pool
- "\U000F0E45" # grill
- "\U000F06EE" # mailbox
- "\U000F0A79" # trash-can
- "\U000F044C" # recycle
- "\U000F0A43" # dog
- "\U000F011B" # cat
- "\U000F0004" # account
- "\U000F0849" # account-group
- "\U000F0A89" # account-child
- "\U000F0826" # home-account
- "\U000F04B2" # sleep
- "\U000F070E" # run
- "\U000F00D6" # briefcase
- "\U000F010B" # car
- "\U000F00A3" # bike
- "\U000F001D" # airplane
- "\U000F034E" # map-marker
- "\U000F0425" # power
- "\U000F0521" # toggle-switch
- "\U000F12A8" # gesture-tap-button
- "\U000F040A" # play
- "\U000F0BC2" # script-text
- "\U000F03D8" # palette
- "\U000F0020" # alarm
- "\U000F0150" # clock-outline
- "\U000F051B" # timer-outline
- "\U000F00ED" # calendar
- "\U000F009E" # bell-ring
- "\U000F02D1" # heart
- "\U000F002A" # alert-outline
- "\U000F0493" # cog
- "\U000F00E2" # broom
- "\U000F05D6" # alert-circle-outline
- "\U000F012C" # check
- "\U000F0591" # weather-fog
- "\U000F0592" # weather-hail
- "\U000F0593" # weather-lightning
- "\U000F067E" # weather-lightning-rainy
- "\U000F059D" # weather-windy
- "\U000F05D6" # alert-circle-outline
- "\U000F0596" # weather-pouring
- "\U000F067F" # weather-snowy-rainy
- "\U000F059B" # weather-sunset-down
- "\U000F0150" # clock-outline
- "\U000F051B" # timer-outline
- "\U000F0004" # account
- file: 'fonts/materialdesignicons-webfont.ttf'
id: materialdesign_icons_mini
size: 26
glyphs:
- "\U000F0335"
- "\U000F0210"
- "\U000F111C"
- "\U000F029A"
- "${MDI_GLYPH_1}"
- "${MDI_GLYPH_2}"
- "${MDI_GLYPH_3}"
- "${MDI_GLYPH_4}"
- "${MDI_GLYPH_5}"
- "${MDI_GLYPH_6}"
- "${MDI_GLYPH_7}"
- "${MDI_GLYPH_8}"
- "${MDI_GLYPH_9}"
- "${MDI_GLYPH_10}"
- "\U000F012C" # check
- "\U000F0599" # weather-sunny
- "\U000F0594" # weather-night
- "\U000F0590" # weather-cloudy
- "\U000F0595" # weather-partly-cloudy
- "\U000F0597" # weather-rainy
- "\U000F0596" # weather-pouring
- "\U000F0598" # weather-snowy
- "\U000F067F" # weather-snowy-rainy
- "\U000F0591" # weather-fog
- "\U000F0592" # weather-hail
- "\U000F0593" # weather-lightning
- "\U000F067E" # weather-lightning-rainy
- "\U000F059D" # weather-windy
- "\U000F05D6" # alert-circle-outline
- "\U000F059B" # weather-sunset-down
- "\U000F0150" # clock-outline
- "\U000F051B" # timer-outline
- "\U000F0004" # account
glyphs: *tile_icons
- file: 'fonts/materialdesignicons-webfont.ttf'
id: watch_icon
size: 18
glyphs:
- "\U000F0335"
- "\U000F0210"
- "\U000F111C"
- "\U000F029A"
- "${MDI_GLYPH_1}"
- "${MDI_GLYPH_2}"
- "${MDI_GLYPH_3}"
- "${MDI_GLYPH_4}"
- "${MDI_GLYPH_5}"
- "${MDI_GLYPH_6}"
- "${MDI_GLYPH_7}"
- "${MDI_GLYPH_8}"
- "${MDI_GLYPH_9}"
- "${MDI_GLYPH_10}"
- "\U000F012C" # check
- "\U000F0599" # weather-sunny
- "\U000F0594" # weather-night
- "\U000F0590" # weather-cloudy
- "\U000F0595" # weather-partly-cloudy
- "\U000F0597" # weather-rainy
- "\U000F0596" # weather-pouring
- "\U000F0598" # weather-snowy
- "\U000F067F" # weather-snowy-rainy
- "\U000F0591" # weather-fog
- "\U000F0592" # weather-hail
- "\U000F0593" # weather-lightning
- "\U000F067E" # weather-lightning-rainy
- "\U000F059D" # weather-windy
- "\U000F05D6" # alert-circle-outline
- "\U000F059B" # weather-sunset-down
- "\U000F0150" # clock-outline
- "\U000F051B" # timer-outline
- "\U000F0004" # account
glyphs: *tile_icons
# Geheugen zichtbaar maken: dit bord heeft geen PSRAM, dus de vrije heap
# bepaalt hoeveel UI erbij kan. Verschijnt ook als entity in Home Assistant.
+168 -120
View File
@@ -21,8 +21,8 @@
#
# How to customize
# 1) Create device.yaml with tools/new_device.py; edit its TILE1..TILE10 overrides.
# 2) If you change icons, update the FONT GLYPHS block in the substitutions below.
# Each glyph must appear exactly once — same icon on multiple tiles is fine, just list it once.
# 2) Icons come from the tile icon set in screen_manager/app/tile_icons.py; the
# icon fonts already contain all of them (tools/generate_icons.py).
# 3) The background is a gradient (BG_TOP_COLOR / BG_BOTTOM_COLOR), not an image.
# Required fonts are bundled in fonts/ (Roboto and Material Design Icons).
#
@@ -58,7 +58,7 @@
############################################################
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.17"
SCREEN_FIRMWARE_VERSION: "0.2.18"
DYNAMIC_TILES: "false"
DASHBOARD_INBOX_INTERNAL: "true"
DEVICE_NAME: "cyd-new"
@@ -112,21 +112,9 @@ substitutions:
# ============================================================
# FONT GLYPHS
# List every unique MDI icon used across your tiles — each value must appear exactly once.
# Reusing the same icon on multiple tiles is fine: just list that glyph once here.
# If you use fewer than 6 unique icons, leave the extra slots with their default values
# (unused glyphs add a few bytes to the font but cause no errors).
# The icon fonts carry the full tile icon set from screen_manager/app/tile_icons.py
# (tools/generate_icons.py). Pick TILEn_ICON from that set; no glyph list to maintain.
# ============================================================
MDI_GLYPH_1: "\U000F001B"
MDI_GLYPH_2: "\U000F0387" # mdi:music
MDI_GLYPH_3: "\U000F02E3"
MDI_GLYPH_4: "\U000F059C"
MDI_GLYPH_5: "\U000F0425"
MDI_GLYPH_6: "\U000F070D"
MDI_GLYPH_7: "\U000F06B5" # mdi:lamp
MDI_GLYPH_8: "\U000F06EE" # mdi:mailbox
MDI_GLYPH_9: "\U000F04B9" # mdi:sofa
MDI_GLYPH_10: "\U000F002A" # mdi:alarm-light
# ============================================================
# TILE 1
@@ -136,7 +124,7 @@ substitutions:
TILE1_ENTITY: "light.voorbeeld_lamp" # main Home Assistant entity for this tile
TILE1_STATE_ENTITY: "light.voorbeeld_lamp" # entity used for active/inactive state styling
TILE1_TITLE: "Lamp" # tile title shown in the UI
TILE1_ICON: "\U000F001B" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE1_ICON: "\U000F001B" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE1_TYPE: "light" # tile type: light | fan | switch | scene | script | cover | climate
TILE1_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE1_LONGPRESS: "slider" # long press mode: none | slider | action
@@ -185,7 +173,7 @@ substitutions:
TILE2_ENTITY: "switch.voorbeeld_stekker" # main Home Assistant entity for this tile
TILE2_STATE_ENTITY: "switch.voorbeeld_stekker" # entity used for active/inactive state styling
TILE2_TITLE: "Stekker" # tile title shown in the UI
TILE2_ICON: "\U000F0387" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE2_ICON: "\U000F0387" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE2_TYPE: "switch" # tile type: light | fan | switch | scene | script | cover | climate
TILE2_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE2_LONGPRESS: "none" # long press mode: none | slider | action
@@ -234,7 +222,7 @@ substitutions:
TILE3_ENTITY: "scene.voorbeeld_avond" # main Home Assistant entity for this tile
TILE3_STATE_ENTITY: "scene.voorbeeld_avond" # entity used for active/inactive state styling
TILE3_TITLE: "Avond" # tile title shown in the UI
TILE3_ICON: "\U000F02E3" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE3_ICON: "\U000F02E3" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE3_TYPE: "scene" # tile type: light | fan | switch | scene | script | cover | climate
TILE3_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE3_LONGPRESS: "none" # long press mode: none | slider | action
@@ -283,7 +271,7 @@ substitutions:
TILE4_ENTITY: "script.voorbeeld_uit" # main Home Assistant entity for this tile
TILE4_STATE_ENTITY: "script.voorbeeld_uit" # entity used for active/inactive state styling
TILE4_TITLE: "Alles uit" # tile title shown in the UI
TILE4_ICON: "\U000F059C" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE4_ICON: "\U000F059C" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE4_TYPE: "script" # tile type: light | fan | switch | scene | script | cover | climate
TILE4_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE4_LONGPRESS: "none" # long press mode: none | slider | action
@@ -332,7 +320,7 @@ substitutions:
TILE5_ENTITY: "climate.voorbeeld_airco" # main Home Assistant entity for this tile
TILE5_STATE_ENTITY: "climate.voorbeeld_airco" # entity used for active/inactive state styling
TILE5_TITLE: "Airco" # tile title shown in the UI
TILE5_ICON: "\U000F0425" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE5_ICON: "\U000F0425" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE5_TYPE: "climate" # tile type: light | fan | switch | scene | script | cover | climate
TILE5_TAP_ACTION: "toggle" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE5_LONGPRESS: "slider" # long press mode: none | slider | action
@@ -381,7 +369,7 @@ substitutions:
TILE6_ENTITY: "vacuum.voorbeeld_robot" # main Home Assistant entity for this tile
TILE6_STATE_ENTITY: "vacuum.voorbeeld_robot" # entity used for active/inactive state styling
TILE6_TITLE: "Stofzuiger" # tile title shown in the UI
TILE6_ICON: "\U000F070D" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE6_ICON: "\U000F070D" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE6_TYPE: "vacuum" # tile type: light | fan | switch | scene | script | cover | climate
TILE6_TAP_ACTION: "custom" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE6_LONGPRESS: "vacuum" # long press mode: none | slider | action
@@ -430,7 +418,7 @@ substitutions:
TILE7_ENTITY: "light.ongebruikt_7" # main Home Assistant entity for this tile
TILE7_STATE_ENTITY: "light.ongebruikt_7" # entity used for active/inactive state styling
TILE7_TITLE: "Tegel 7" # tile title shown in the UI
TILE7_ICON: "\U000F06B5" # mdi:lamp — also listed in MDI_GLYPH_7 above
TILE7_ICON: "\U000F06B5" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE7_TYPE: "light" # tile type: light | fan | switch | scene | script | cover | climate
TILE7_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset
TILE7_LONGPRESS: "none" # long press mode: none | slider | action | vacuum
@@ -474,7 +462,7 @@ substitutions:
TILE8_ENTITY: "light.ongebruikt_8" # main Home Assistant entity for this tile
TILE8_STATE_ENTITY: "light.ongebruikt_8" # entity used for active/inactive state styling
TILE8_TITLE: "Tegel 8" # tile title shown in the UI
TILE8_ICON: "\U000F06EE" # mdi:mailbox — also listed in MDI_GLYPH_8 above
TILE8_ICON: "\U000F06EE" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE8_TYPE: "light" # generiek type; het echte gedrag komt uit TAP_SERVICE hieronder
TILE8_TAP_ACTION: "auto" # activate + TAP_SERVICE = losse service call
TILE8_LONGPRESS: "none" # long press stuurt alleen het event naar HA
@@ -518,7 +506,7 @@ substitutions:
TILE9_ENTITY: "light.ongebruikt_9" # main Home Assistant entity for this tile
TILE9_STATE_ENTITY: "light.ongebruikt_9" # entity used for active/inactive state styling
TILE9_TITLE: "Tegel 9" # tile title shown in the UI
TILE9_ICON: "\U000F04B9" # mdi:sofa — also listed in MDI_GLYPH_9 above
TILE9_ICON: "\U000F04B9" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE9_TYPE: "light" # tile type: light | fan | switch | scene | script | cover | climate
TILE9_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset
TILE9_LONGPRESS: "none" # long press mode: none | slider | action | vacuum
@@ -561,7 +549,7 @@ substitutions:
TILE10_ENTITY: "light.ongebruikt_10" # main Home Assistant entity for this tile
TILE10_STATE_ENTITY: "light.ongebruikt_10" # entity used for active/inactive state styling
TILE10_TITLE: "Tegel 10" # tile title shown in the UI
TILE10_ICON: "\U000F002A" # mdi:alarm-light — also listed in MDI_GLYPH_10 above
TILE10_ICON: "\U000F002A" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE10_TYPE: "light" # generiek aan/uit-type; de service hieronder doet het echte werk
TILE10_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset
TILE10_LONGPRESS: "none" # long press stuurt alleen het event naar HA
@@ -1254,116 +1242,176 @@ font:
- file: 'fonts/materialdesignicons-webfont.ttf'
id: materialdesign_icons
size: 28
glyphs:
- "\U000F0335"
- "\U000F0210"
- "\U000F111C"
- "\U000F029A"
- "${MDI_GLYPH_1}"
- "${MDI_GLYPH_2}"
- "${MDI_GLYPH_3}"
- "${MDI_GLYPH_4}"
- "${MDI_GLYPH_5}"
- "${MDI_GLYPH_6}"
- "${MDI_GLYPH_7}"
- "${MDI_GLYPH_8}"
- "${MDI_GLYPH_9}"
- "${MDI_GLYPH_10}"
- "\U000F012C" # check
glyphs: &tile_icons # GENERATED by tools/generate_icons.py from screen_manager/app/tile_icons.py
- "\U000F0335" # lightbulb
- "\U000F1253" # lightbulb-group
- "\U000F0769" # ceiling-light
- "\U000F1793" # chandelier
- "\U000F06B5" # lamp
- "\U000F08DD" # floor-lamp
- "\U000F095F" # desk-lamp
- "\U000F091D" # wall-sconce-flat
- "\U000F179B" # light-recessed
- "\U000F04C9" # spotlight-beam
- "\U000F1051" # led-strip-variant
- "\U000F1054" # outdoor-lamp
- "\U000F02DC" # home
- "\U000F04B9" # sofa
- "\U000F02E3" # bed
- "\U000F0A70" # silverware-fork-knife
- "\U000F0B7C" # chef-hat
- "\U000F09A0" # shower
- "\U000F1818" # bathtub
- "\U000F09AB" # toilet
- "\U000F1239" # desk
- "\U000F06D9" # garage
- "\U000F0E2E" # fireplace
- "\U000F050F" # thermometer
- "\U000F0393" # thermostat
- "\U000F001B" # air-conditioner
- "\U000F0438" # radiator
- "\U000F1A43" # heat-pump
- "\U000F0238" # fire
- "\U000F0717" # snowflake
- "\U000F058E" # water-percent
- "\U000F0210" # fan
- "\U000F1797" # ceiling-fan
- "\U000F0D44" # air-purifier
- "\U000F07E4" # molecule-co2
- "\U000F0599" # weather-sunny
- "\U000F0594" # weather-night
- "\U000F0590" # weather-cloudy
- "\U000F0595" # weather-partly-cloudy
- "\U000F0590" # weather-cloudy
- "\U000F0597" # weather-rainy
- "\U000F0596" # weather-pouring
- "\U000F0598" # weather-snowy
- "\U000F067F" # weather-snowy-rainy
- "\U000F059D" # weather-windy
- "\U000F0594" # weather-night
- "\U000F059C" # weather-sunset-up
- "\U000F0387" # music-note
- "\U000F075A" # music
- "\U000F0CB8" # playlist-music
- "\U000F0025" # album
- "\U000F099A" # record-player
- "\U000F04C7" # spotify
- "\U000F0439" # radio
- "\U000F0994" # podcast
- "\U000F04C3" # speaker
- "\U000F0D38" # speaker-multiple
- "\U000F17DB" # soundbar
- "\U000F02CB" # headphones
- "\U000F067D" # piano
- "\U000F0771" # guitar-acoustic
- "\U000F0502" # television
- "\U000F042E" # projector
- "\U000F0FCE" # movie-open
- "\U000F0118" # cast
- "\U000F0297" # gamepad-variant
- "\U000F0EC5" # remote-tv
- "\U000F033E" # lock
- "\U000F0FC6" # lock-open-variant
- "\U000F068A" # shield-home
- "\U000F078F" # alarm-light
- "\U000F009A" # bell
- "\U000F12E6" # doorbell
- "\U000F07AE" # cctv
- "\U000F0D91" # motion-sensor
- "\U000F081B" # door-closed
- "\U000F081C" # door-open
- "\U000F11DB" # window-closed-variant
- "\U000F0392" # smoke-detector
- "\U000F1502" # water-alert
- "\U000F072A" # washing-machine
- "\U000F0917" # tumble-dryer
- "\U000F0AAC" # dishwasher
- "\U000F0290" # fridge
- "\U000F04DE" # stove
- "\U000F0C99" # microwave
- "\U000F0CD3" # toaster-oven
- "\U000F109F" # coffee-maker
- "\U000F05FA" # kettle
- "\U000F070D" # robot-vacuum
- "\U000F11F7" # robot-mower
- "\U000F042B" # printer-3d
- "\U000F06A5" # power-plug
- "\U000F07E7" # power-socket-eu
- "\U000F0322" # laptop
- "\U000F0469" # router-wireless
- "\U000F0241" # flash
- "\U000F0A72" # solar-power
- "\U000F0D9B" # solar-panel
- "\U000F1901" # home-battery
- "\U000F12A3" # battery-high
- "\U000F05F1" # ev-station
- "\U000F0B6C" # car-electric
- "\U000F1A57" # meter-electric
- "\U000F1A59" # meter-gas
- "\U000F058C" # water
- "\U000F0F92" # water-boiler
- "\U000F029A" # gauge
- "\U000F111C" # window-shutter
- "\U000F111E" # window-shutter-open
- "\U000F00AC" # blinds
- "\U000F1011" # blinds-open
- "\U000F1A6C" # roller-shade-closed
- "\U000F1846" # curtains
- "\U000F1847" # curtains-closed
- "\U000F1B88" # awning-outline
- "\U000F024A" # flower
- "\U000F0531" # tree
- "\U000F0E66" # sprout
- "\U000F1510" # grass
- "\U000F105F" # sprinkler
- "\U000F0606" # pool
- "\U000F0E45" # grill
- "\U000F06EE" # mailbox
- "\U000F0A79" # trash-can
- "\U000F044C" # recycle
- "\U000F0A43" # dog
- "\U000F011B" # cat
- "\U000F0004" # account
- "\U000F0849" # account-group
- "\U000F0A89" # account-child
- "\U000F0826" # home-account
- "\U000F04B2" # sleep
- "\U000F070E" # run
- "\U000F00D6" # briefcase
- "\U000F010B" # car
- "\U000F00A3" # bike
- "\U000F001D" # airplane
- "\U000F034E" # map-marker
- "\U000F0425" # power
- "\U000F0521" # toggle-switch
- "\U000F12A8" # gesture-tap-button
- "\U000F040A" # play
- "\U000F0BC2" # script-text
- "\U000F03D8" # palette
- "\U000F0020" # alarm
- "\U000F0150" # clock-outline
- "\U000F051B" # timer-outline
- "\U000F00ED" # calendar
- "\U000F009E" # bell-ring
- "\U000F02D1" # heart
- "\U000F002A" # alert-outline
- "\U000F0493" # cog
- "\U000F00E2" # broom
- "\U000F05D6" # alert-circle-outline
- "\U000F012C" # check
- "\U000F0591" # weather-fog
- "\U000F0592" # weather-hail
- "\U000F0593" # weather-lightning
- "\U000F067E" # weather-lightning-rainy
- "\U000F059D" # weather-windy
- "\U000F05D6" # alert-circle-outline
- "\U000F0596" # weather-pouring
- "\U000F067F" # weather-snowy-rainy
- "\U000F059B" # weather-sunset-down
- "\U000F0150" # clock-outline
- "\U000F051B" # timer-outline
- "\U000F0004" # account
- file: 'fonts/materialdesignicons-webfont.ttf'
id: materialdesign_icons_mini
size: 18
glyphs:
- "\U000F0335"
- "\U000F0210"
- "\U000F111C"
- "\U000F029A"
- "${MDI_GLYPH_1}"
- "${MDI_GLYPH_2}"
- "${MDI_GLYPH_3}"
- "${MDI_GLYPH_4}"
- "${MDI_GLYPH_5}"
- "${MDI_GLYPH_6}"
- "${MDI_GLYPH_7}"
- "${MDI_GLYPH_8}"
- "${MDI_GLYPH_9}"
- "${MDI_GLYPH_10}"
- "\U000F012C" # check
- "\U000F0599" # weather-sunny
- "\U000F0594" # weather-night
- "\U000F0590" # weather-cloudy
- "\U000F0595" # weather-partly-cloudy
- "\U000F0597" # weather-rainy
- "\U000F0596" # weather-pouring
- "\U000F0598" # weather-snowy
- "\U000F067F" # weather-snowy-rainy
- "\U000F0591" # weather-fog
- "\U000F0592" # weather-hail
- "\U000F0593" # weather-lightning
- "\U000F067E" # weather-lightning-rainy
- "\U000F059D" # weather-windy
- "\U000F05D6" # alert-circle-outline
- "\U000F059B" # weather-sunset-down
- "\U000F0150" # clock-outline
- "\U000F051B" # timer-outline
- "\U000F0004" # account
glyphs: *tile_icons
- file: 'fonts/materialdesignicons-webfont.ttf'
id: watch_icon
size: 12
glyphs:
- "\U000F0335"
- "\U000F0210"
- "\U000F111C"
- "\U000F029A"
- "${MDI_GLYPH_1}"
- "${MDI_GLYPH_2}"
- "${MDI_GLYPH_3}"
- "${MDI_GLYPH_4}"
- "${MDI_GLYPH_5}"
- "${MDI_GLYPH_6}"
- "${MDI_GLYPH_7}"
- "${MDI_GLYPH_8}"
- "${MDI_GLYPH_9}"
- "${MDI_GLYPH_10}"
- "\U000F012C" # check
- "\U000F0599" # weather-sunny
- "\U000F0594" # weather-night
- "\U000F0590" # weather-cloudy
- "\U000F0595" # weather-partly-cloudy
- "\U000F0597" # weather-rainy
- "\U000F0596" # weather-pouring
- "\U000F0598" # weather-snowy
- "\U000F067F" # weather-snowy-rainy
- "\U000F0591" # weather-fog
- "\U000F0592" # weather-hail
- "\U000F0593" # weather-lightning
- "\U000F067E" # weather-lightning-rainy
- "\U000F059D" # weather-windy
- "\U000F05D6" # alert-circle-outline
- "\U000F059B" # weather-sunset-down
- "\U000F0150" # clock-outline
- "\U000F051B" # timer-outline
- "\U000F0004" # account
glyphs: *tile_icons
# Geheugen zichtbaar maken: dit bord heeft geen PSRAM, dus de vrije heap
# bepaalt hoeveel UI erbij kan. Verschijnt ook als entity in Home Assistant.
+168 -120
View File
@@ -22,8 +22,8 @@
#
# How to customize
# 1) Create device.yaml with tools/new_device.py; edit its TILE1..TILE10 overrides.
# 2) If you change icons, update the FONT GLYPHS block in the substitutions below.
# Each glyph must appear exactly once — same icon on multiple tiles is fine, just list it once.
# 2) Icons come from the tile icon set in screen_manager/app/tile_icons.py; the
# icon fonts already contain all of them (tools/generate_icons.py).
# 3) The background is a gradient (BG_TOP_COLOR / BG_BOTTOM_COLOR), not an image.
# Required fonts are bundled in fonts/ (Roboto and Material Design Icons).
#
@@ -59,7 +59,7 @@
############################################################
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.17"
SCREEN_FIRMWARE_VERSION: "0.2.18"
DYNAMIC_TILES: "true"
DASHBOARD_INBOX_INTERNAL: "false"
DEVICE_NAME: "cyd-new"
@@ -113,21 +113,9 @@ substitutions:
# ============================================================
# FONT GLYPHS
# List every unique MDI icon used across your tiles — each value must appear exactly once.
# Reusing the same icon on multiple tiles is fine: just list that glyph once here.
# If you use fewer than 6 unique icons, leave the extra slots with their default values
# (unused glyphs add a few bytes to the font but cause no errors).
# The icon fonts carry the full tile icon set from screen_manager/app/tile_icons.py
# (tools/generate_icons.py). Pick TILEn_ICON from that set; no glyph list to maintain.
# ============================================================
MDI_GLYPH_1: "\U000F001B"
MDI_GLYPH_2: "\U000F0387" # mdi:music
MDI_GLYPH_3: "\U000F02E3"
MDI_GLYPH_4: "\U000F059C"
MDI_GLYPH_5: "\U000F0425"
MDI_GLYPH_6: "\U000F070D"
MDI_GLYPH_7: "\U000F06B5" # mdi:lamp
MDI_GLYPH_8: "\U000F06EE" # mdi:mailbox
MDI_GLYPH_9: "\U000F04B9" # mdi:sofa
MDI_GLYPH_10: "\U000F002A" # mdi:alarm-light
# ============================================================
# TILE 1
@@ -137,7 +125,7 @@ substitutions:
TILE1_ENTITY: "light.voorbeeld_lamp" # main Home Assistant entity for this tile
TILE1_STATE_ENTITY: "light.voorbeeld_lamp" # entity used for active/inactive state styling
TILE1_TITLE: "Lamp" # tile title shown in the UI
TILE1_ICON: "\U000F001B" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE1_ICON: "\U000F001B" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE1_TYPE: "light" # tile type: light | fan | switch | scene | script | cover | climate
TILE1_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE1_LONGPRESS: "slider" # long press mode: none | slider | action
@@ -186,7 +174,7 @@ substitutions:
TILE2_ENTITY: "switch.voorbeeld_stekker" # main Home Assistant entity for this tile
TILE2_STATE_ENTITY: "switch.voorbeeld_stekker" # entity used for active/inactive state styling
TILE2_TITLE: "Stekker" # tile title shown in the UI
TILE2_ICON: "\U000F0387" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE2_ICON: "\U000F0387" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE2_TYPE: "switch" # tile type: light | fan | switch | scene | script | cover | climate
TILE2_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE2_LONGPRESS: "none" # long press mode: none | slider | action
@@ -235,7 +223,7 @@ substitutions:
TILE3_ENTITY: "scene.voorbeeld_avond" # main Home Assistant entity for this tile
TILE3_STATE_ENTITY: "scene.voorbeeld_avond" # entity used for active/inactive state styling
TILE3_TITLE: "Avond" # tile title shown in the UI
TILE3_ICON: "\U000F02E3" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE3_ICON: "\U000F02E3" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE3_TYPE: "scene" # tile type: light | fan | switch | scene | script | cover | climate
TILE3_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE3_LONGPRESS: "none" # long press mode: none | slider | action
@@ -284,7 +272,7 @@ substitutions:
TILE4_ENTITY: "script.voorbeeld_uit" # main Home Assistant entity for this tile
TILE4_STATE_ENTITY: "script.voorbeeld_uit" # entity used for active/inactive state styling
TILE4_TITLE: "Alles uit" # tile title shown in the UI
TILE4_ICON: "\U000F059C" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE4_ICON: "\U000F059C" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE4_TYPE: "script" # tile type: light | fan | switch | scene | script | cover | climate
TILE4_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE4_LONGPRESS: "none" # long press mode: none | slider | action
@@ -333,7 +321,7 @@ substitutions:
TILE5_ENTITY: "climate.voorbeeld_airco" # main Home Assistant entity for this tile
TILE5_STATE_ENTITY: "climate.voorbeeld_airco" # entity used for active/inactive state styling
TILE5_TITLE: "Airco" # tile title shown in the UI
TILE5_ICON: "\U000F0425" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE5_ICON: "\U000F0425" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE5_TYPE: "climate" # tile type: light | fan | switch | scene | script | cover | climate
TILE5_TAP_ACTION: "toggle" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE5_LONGPRESS: "slider" # long press mode: none | slider | action
@@ -382,7 +370,7 @@ substitutions:
TILE6_ENTITY: "vacuum.voorbeeld_robot" # main Home Assistant entity for this tile
TILE6_STATE_ENTITY: "vacuum.voorbeeld_robot" # entity used for active/inactive state styling
TILE6_TITLE: "Stofzuiger" # tile title shown in the UI
TILE6_ICON: "\U000F070D" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE6_ICON: "\U000F070D" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE6_TYPE: "vacuum" # tile type: light | fan | switch | scene | script | cover | climate
TILE6_TAP_ACTION: "custom" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE6_LONGPRESS: "vacuum" # long press mode: none | slider | action
@@ -431,7 +419,7 @@ substitutions:
TILE7_ENTITY: "light.ongebruikt_7" # main Home Assistant entity for this tile
TILE7_STATE_ENTITY: "light.ongebruikt_7" # entity used for active/inactive state styling
TILE7_TITLE: "Tegel 7" # tile title shown in the UI
TILE7_ICON: "\U000F06B5" # mdi:lamp — also listed in MDI_GLYPH_7 above
TILE7_ICON: "\U000F06B5" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE7_TYPE: "light" # tile type: light | fan | switch | scene | script | cover | climate
TILE7_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset
TILE7_LONGPRESS: "none" # long press mode: none | slider | action | vacuum
@@ -475,7 +463,7 @@ substitutions:
TILE8_ENTITY: "light.ongebruikt_8" # main Home Assistant entity for this tile
TILE8_STATE_ENTITY: "light.ongebruikt_8" # entity used for active/inactive state styling
TILE8_TITLE: "Tegel 8" # tile title shown in the UI
TILE8_ICON: "\U000F06EE" # mdi:mailbox — also listed in MDI_GLYPH_8 above
TILE8_ICON: "\U000F06EE" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE8_TYPE: "light" # generiek type; het echte gedrag komt uit TAP_SERVICE hieronder
TILE8_TAP_ACTION: "auto" # activate + TAP_SERVICE = losse service call
TILE8_LONGPRESS: "none" # long press stuurt alleen het event naar HA
@@ -519,7 +507,7 @@ substitutions:
TILE9_ENTITY: "light.ongebruikt_9" # main Home Assistant entity for this tile
TILE9_STATE_ENTITY: "light.ongebruikt_9" # entity used for active/inactive state styling
TILE9_TITLE: "Tegel 9" # tile title shown in the UI
TILE9_ICON: "\U000F04B9" # mdi:sofa — also listed in MDI_GLYPH_9 above
TILE9_ICON: "\U000F04B9" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE9_TYPE: "light" # tile type: light | fan | switch | scene | script | cover | climate
TILE9_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset
TILE9_LONGPRESS: "none" # long press mode: none | slider | action | vacuum
@@ -562,7 +550,7 @@ substitutions:
TILE10_ENTITY: "light.ongebruikt_10" # main Home Assistant entity for this tile
TILE10_STATE_ENTITY: "light.ongebruikt_10" # entity used for active/inactive state styling
TILE10_TITLE: "Tegel 10" # tile title shown in the UI
TILE10_ICON: "\U000F002A" # mdi:alarm-light — also listed in MDI_GLYPH_10 above
TILE10_ICON: "\U000F002A" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE10_TYPE: "light" # generiek aan/uit-type; de service hieronder doet het echte werk
TILE10_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset
TILE10_LONGPRESS: "none" # long press stuurt alleen het event naar HA
@@ -1245,116 +1233,176 @@ font:
- file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/materialdesignicons-webfont.ttf"
id: materialdesign_icons
size: 28
glyphs:
- "\U000F0335"
- "\U000F0210"
- "\U000F111C"
- "\U000F029A"
- "${MDI_GLYPH_1}"
- "${MDI_GLYPH_2}"
- "${MDI_GLYPH_3}"
- "${MDI_GLYPH_4}"
- "${MDI_GLYPH_5}"
- "${MDI_GLYPH_6}"
- "${MDI_GLYPH_7}"
- "${MDI_GLYPH_8}"
- "${MDI_GLYPH_9}"
- "${MDI_GLYPH_10}"
- "\U000F012C" # check
glyphs: &tile_icons # GENERATED by tools/generate_icons.py from screen_manager/app/tile_icons.py
- "\U000F0335" # lightbulb
- "\U000F1253" # lightbulb-group
- "\U000F0769" # ceiling-light
- "\U000F1793" # chandelier
- "\U000F06B5" # lamp
- "\U000F08DD" # floor-lamp
- "\U000F095F" # desk-lamp
- "\U000F091D" # wall-sconce-flat
- "\U000F179B" # light-recessed
- "\U000F04C9" # spotlight-beam
- "\U000F1051" # led-strip-variant
- "\U000F1054" # outdoor-lamp
- "\U000F02DC" # home
- "\U000F04B9" # sofa
- "\U000F02E3" # bed
- "\U000F0A70" # silverware-fork-knife
- "\U000F0B7C" # chef-hat
- "\U000F09A0" # shower
- "\U000F1818" # bathtub
- "\U000F09AB" # toilet
- "\U000F1239" # desk
- "\U000F06D9" # garage
- "\U000F0E2E" # fireplace
- "\U000F050F" # thermometer
- "\U000F0393" # thermostat
- "\U000F001B" # air-conditioner
- "\U000F0438" # radiator
- "\U000F1A43" # heat-pump
- "\U000F0238" # fire
- "\U000F0717" # snowflake
- "\U000F058E" # water-percent
- "\U000F0210" # fan
- "\U000F1797" # ceiling-fan
- "\U000F0D44" # air-purifier
- "\U000F07E4" # molecule-co2
- "\U000F0599" # weather-sunny
- "\U000F0594" # weather-night
- "\U000F0590" # weather-cloudy
- "\U000F0595" # weather-partly-cloudy
- "\U000F0590" # weather-cloudy
- "\U000F0597" # weather-rainy
- "\U000F0596" # weather-pouring
- "\U000F0598" # weather-snowy
- "\U000F067F" # weather-snowy-rainy
- "\U000F059D" # weather-windy
- "\U000F0594" # weather-night
- "\U000F059C" # weather-sunset-up
- "\U000F0387" # music-note
- "\U000F075A" # music
- "\U000F0CB8" # playlist-music
- "\U000F0025" # album
- "\U000F099A" # record-player
- "\U000F04C7" # spotify
- "\U000F0439" # radio
- "\U000F0994" # podcast
- "\U000F04C3" # speaker
- "\U000F0D38" # speaker-multiple
- "\U000F17DB" # soundbar
- "\U000F02CB" # headphones
- "\U000F067D" # piano
- "\U000F0771" # guitar-acoustic
- "\U000F0502" # television
- "\U000F042E" # projector
- "\U000F0FCE" # movie-open
- "\U000F0118" # cast
- "\U000F0297" # gamepad-variant
- "\U000F0EC5" # remote-tv
- "\U000F033E" # lock
- "\U000F0FC6" # lock-open-variant
- "\U000F068A" # shield-home
- "\U000F078F" # alarm-light
- "\U000F009A" # bell
- "\U000F12E6" # doorbell
- "\U000F07AE" # cctv
- "\U000F0D91" # motion-sensor
- "\U000F081B" # door-closed
- "\U000F081C" # door-open
- "\U000F11DB" # window-closed-variant
- "\U000F0392" # smoke-detector
- "\U000F1502" # water-alert
- "\U000F072A" # washing-machine
- "\U000F0917" # tumble-dryer
- "\U000F0AAC" # dishwasher
- "\U000F0290" # fridge
- "\U000F04DE" # stove
- "\U000F0C99" # microwave
- "\U000F0CD3" # toaster-oven
- "\U000F109F" # coffee-maker
- "\U000F05FA" # kettle
- "\U000F070D" # robot-vacuum
- "\U000F11F7" # robot-mower
- "\U000F042B" # printer-3d
- "\U000F06A5" # power-plug
- "\U000F07E7" # power-socket-eu
- "\U000F0322" # laptop
- "\U000F0469" # router-wireless
- "\U000F0241" # flash
- "\U000F0A72" # solar-power
- "\U000F0D9B" # solar-panel
- "\U000F1901" # home-battery
- "\U000F12A3" # battery-high
- "\U000F05F1" # ev-station
- "\U000F0B6C" # car-electric
- "\U000F1A57" # meter-electric
- "\U000F1A59" # meter-gas
- "\U000F058C" # water
- "\U000F0F92" # water-boiler
- "\U000F029A" # gauge
- "\U000F111C" # window-shutter
- "\U000F111E" # window-shutter-open
- "\U000F00AC" # blinds
- "\U000F1011" # blinds-open
- "\U000F1A6C" # roller-shade-closed
- "\U000F1846" # curtains
- "\U000F1847" # curtains-closed
- "\U000F1B88" # awning-outline
- "\U000F024A" # flower
- "\U000F0531" # tree
- "\U000F0E66" # sprout
- "\U000F1510" # grass
- "\U000F105F" # sprinkler
- "\U000F0606" # pool
- "\U000F0E45" # grill
- "\U000F06EE" # mailbox
- "\U000F0A79" # trash-can
- "\U000F044C" # recycle
- "\U000F0A43" # dog
- "\U000F011B" # cat
- "\U000F0004" # account
- "\U000F0849" # account-group
- "\U000F0A89" # account-child
- "\U000F0826" # home-account
- "\U000F04B2" # sleep
- "\U000F070E" # run
- "\U000F00D6" # briefcase
- "\U000F010B" # car
- "\U000F00A3" # bike
- "\U000F001D" # airplane
- "\U000F034E" # map-marker
- "\U000F0425" # power
- "\U000F0521" # toggle-switch
- "\U000F12A8" # gesture-tap-button
- "\U000F040A" # play
- "\U000F0BC2" # script-text
- "\U000F03D8" # palette
- "\U000F0020" # alarm
- "\U000F0150" # clock-outline
- "\U000F051B" # timer-outline
- "\U000F00ED" # calendar
- "\U000F009E" # bell-ring
- "\U000F02D1" # heart
- "\U000F002A" # alert-outline
- "\U000F0493" # cog
- "\U000F00E2" # broom
- "\U000F05D6" # alert-circle-outline
- "\U000F012C" # check
- "\U000F0591" # weather-fog
- "\U000F0592" # weather-hail
- "\U000F0593" # weather-lightning
- "\U000F067E" # weather-lightning-rainy
- "\U000F059D" # weather-windy
- "\U000F05D6" # alert-circle-outline
- "\U000F0596" # weather-pouring
- "\U000F067F" # weather-snowy-rainy
- "\U000F059B" # weather-sunset-down
- "\U000F0150" # clock-outline
- "\U000F051B" # timer-outline
- "\U000F0004" # account
- file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/materialdesignicons-webfont.ttf"
id: materialdesign_icons_mini
size: 18
glyphs:
- "\U000F0335"
- "\U000F0210"
- "\U000F111C"
- "\U000F029A"
- "${MDI_GLYPH_1}"
- "${MDI_GLYPH_2}"
- "${MDI_GLYPH_3}"
- "${MDI_GLYPH_4}"
- "${MDI_GLYPH_5}"
- "${MDI_GLYPH_6}"
- "${MDI_GLYPH_7}"
- "${MDI_GLYPH_8}"
- "${MDI_GLYPH_9}"
- "${MDI_GLYPH_10}"
- "\U000F012C" # check
- "\U000F0599" # weather-sunny
- "\U000F0594" # weather-night
- "\U000F0590" # weather-cloudy
- "\U000F0595" # weather-partly-cloudy
- "\U000F0597" # weather-rainy
- "\U000F0596" # weather-pouring
- "\U000F0598" # weather-snowy
- "\U000F067F" # weather-snowy-rainy
- "\U000F0591" # weather-fog
- "\U000F0592" # weather-hail
- "\U000F0593" # weather-lightning
- "\U000F067E" # weather-lightning-rainy
- "\U000F059D" # weather-windy
- "\U000F05D6" # alert-circle-outline
- "\U000F059B" # weather-sunset-down
- "\U000F0150" # clock-outline
- "\U000F051B" # timer-outline
- "\U000F0004" # account
glyphs: *tile_icons
- file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/materialdesignicons-webfont.ttf"
id: watch_icon
size: 12
glyphs:
- "\U000F0335"
- "\U000F0210"
- "\U000F111C"
- "\U000F029A"
- "${MDI_GLYPH_1}"
- "${MDI_GLYPH_2}"
- "${MDI_GLYPH_3}"
- "${MDI_GLYPH_4}"
- "${MDI_GLYPH_5}"
- "${MDI_GLYPH_6}"
- "${MDI_GLYPH_7}"
- "${MDI_GLYPH_8}"
- "${MDI_GLYPH_9}"
- "${MDI_GLYPH_10}"
- "\U000F012C" # check
- "\U000F0599" # weather-sunny
- "\U000F0594" # weather-night
- "\U000F0590" # weather-cloudy
- "\U000F0595" # weather-partly-cloudy
- "\U000F0597" # weather-rainy
- "\U000F0596" # weather-pouring
- "\U000F0598" # weather-snowy
- "\U000F067F" # weather-snowy-rainy
- "\U000F0591" # weather-fog
- "\U000F0592" # weather-hail
- "\U000F0593" # weather-lightning
- "\U000F067E" # weather-lightning-rainy
- "\U000F059D" # weather-windy
- "\U000F05D6" # alert-circle-outline
- "\U000F059B" # weather-sunset-down
- "\U000F0150" # clock-outline
- "\U000F051B" # timer-outline
- "\U000F0004" # account
glyphs: *tile_icons
# Geheugen zichtbaar maken: dit bord heeft geen PSRAM, dus de vrije heap
# bepaalt hoeveel UI erbij kan. Verschijnt ook als entity in Home Assistant.
+166 -118
View File
@@ -2,7 +2,7 @@
# Guition ESP32-S3-4848S040 / 4-inch EU wallbox, 480x480 IPS + GT911.
# Use guition-device.yaml; see docs/GUITION.md. CYD hardware is a separate profile.
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.17"
SCREEN_FIRMWARE_VERSION: "0.2.18"
DYNAMIC_TILES: "true"
DASHBOARD_INBOX_INTERNAL: "false"
DEVICE_NAME: "guition-new"
@@ -50,21 +50,9 @@ substitutions:
# ============================================================
# FONT GLYPHS
# List every unique MDI icon used across your tiles — each value must appear exactly once.
# Reusing the same icon on multiple tiles is fine: just list that glyph once here.
# If you use fewer than 6 unique icons, leave the extra slots with their default values
# (unused glyphs add a few bytes to the font but cause no errors).
# The icon fonts carry the full tile icon set from screen_manager/app/tile_icons.py
# (tools/generate_icons.py). Pick TILEn_ICON from that set; no glyph list to maintain.
# ============================================================
MDI_GLYPH_1: "\U000F001B"
MDI_GLYPH_2: "\U000F0387" # mdi:music
MDI_GLYPH_3: "\U000F02E3"
MDI_GLYPH_4: "\U000F059C"
MDI_GLYPH_5: "\U000F0425"
MDI_GLYPH_6: "\U000F070D"
MDI_GLYPH_7: "\U000F06B5" # mdi:lamp
MDI_GLYPH_8: "\U000F06EE" # mdi:mailbox
MDI_GLYPH_9: "\U000F04B9" # mdi:sofa
MDI_GLYPH_10: "\U000F002A" # mdi:alarm-light
# ============================================================
# TILE 1
@@ -74,7 +62,7 @@ substitutions:
TILE1_ENTITY: "light.voorbeeld_lamp" # main Home Assistant entity for this tile
TILE1_STATE_ENTITY: "light.voorbeeld_lamp" # entity used for active/inactive state styling
TILE1_TITLE: "Lamp" # tile title shown in the UI
TILE1_ICON: "\U000F001B" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE1_ICON: "\U000F001B" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE1_TYPE: "light" # tile type: light | fan | switch | scene | script | cover | climate
TILE1_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE1_LONGPRESS: "slider" # long press mode: none | slider | action
@@ -123,7 +111,7 @@ substitutions:
TILE2_ENTITY: "switch.voorbeeld_stekker" # main Home Assistant entity for this tile
TILE2_STATE_ENTITY: "switch.voorbeeld_stekker" # entity used for active/inactive state styling
TILE2_TITLE: "Stekker" # tile title shown in the UI
TILE2_ICON: "\U000F0387" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE2_ICON: "\U000F0387" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE2_TYPE: "switch" # tile type: light | fan | switch | scene | script | cover | climate
TILE2_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE2_LONGPRESS: "none" # long press mode: none | slider | action
@@ -172,7 +160,7 @@ substitutions:
TILE3_ENTITY: "scene.voorbeeld_avond" # main Home Assistant entity for this tile
TILE3_STATE_ENTITY: "scene.voorbeeld_avond" # entity used for active/inactive state styling
TILE3_TITLE: "Avond" # tile title shown in the UI
TILE3_ICON: "\U000F02E3" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE3_ICON: "\U000F02E3" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE3_TYPE: "scene" # tile type: light | fan | switch | scene | script | cover | climate
TILE3_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE3_LONGPRESS: "none" # long press mode: none | slider | action
@@ -221,7 +209,7 @@ substitutions:
TILE4_ENTITY: "script.voorbeeld_uit" # main Home Assistant entity for this tile
TILE4_STATE_ENTITY: "script.voorbeeld_uit" # entity used for active/inactive state styling
TILE4_TITLE: "Alles uit" # tile title shown in the UI
TILE4_ICON: "\U000F059C" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE4_ICON: "\U000F059C" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE4_TYPE: "script" # tile type: light | fan | switch | scene | script | cover | climate
TILE4_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE4_LONGPRESS: "none" # long press mode: none | slider | action
@@ -270,7 +258,7 @@ substitutions:
TILE5_ENTITY: "climate.voorbeeld_airco" # main Home Assistant entity for this tile
TILE5_STATE_ENTITY: "climate.voorbeeld_airco" # entity used for active/inactive state styling
TILE5_TITLE: "Airco" # tile title shown in the UI
TILE5_ICON: "\U000F0425" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE5_ICON: "\U000F0425" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE5_TYPE: "climate" # tile type: light | fan | switch | scene | script | cover | climate
TILE5_TAP_ACTION: "toggle" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE5_LONGPRESS: "slider" # long press mode: none | slider | action
@@ -319,7 +307,7 @@ substitutions:
TILE6_ENTITY: "vacuum.voorbeeld_robot" # main Home Assistant entity for this tile
TILE6_STATE_ENTITY: "vacuum.voorbeeld_robot" # entity used for active/inactive state styling
TILE6_TITLE: "Stofzuiger" # tile title shown in the UI
TILE6_ICON: "\U000F070D" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE6_ICON: "\U000F070D" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE6_TYPE: "vacuum" # tile type: light | fan | switch | scene | script | cover | climate
TILE6_TAP_ACTION: "custom" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE6_LONGPRESS: "vacuum" # long press mode: none | slider | action
@@ -368,7 +356,7 @@ substitutions:
TILE7_ENTITY: "light.ongebruikt_7" # main Home Assistant entity for this tile
TILE7_STATE_ENTITY: "light.ongebruikt_7" # entity used for active/inactive state styling
TILE7_TITLE: "Tegel 7" # tile title shown in the UI
TILE7_ICON: "\U000F06B5" # mdi:lamp — also listed in MDI_GLYPH_7 above
TILE7_ICON: "\U000F06B5" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE7_TYPE: "light" # tile type: light | fan | switch | scene | script | cover | climate
TILE7_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset
TILE7_LONGPRESS: "none" # long press mode: none | slider | action | vacuum
@@ -412,7 +400,7 @@ substitutions:
TILE8_ENTITY: "light.ongebruikt_8" # main Home Assistant entity for this tile
TILE8_STATE_ENTITY: "light.ongebruikt_8" # entity used for active/inactive state styling
TILE8_TITLE: "Tegel 8" # tile title shown in the UI
TILE8_ICON: "\U000F06EE" # mdi:mailbox — also listed in MDI_GLYPH_8 above
TILE8_ICON: "\U000F06EE" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE8_TYPE: "light" # generiek type; het echte gedrag komt uit TAP_SERVICE hieronder
TILE8_TAP_ACTION: "auto" # activate + TAP_SERVICE = losse service call
TILE8_LONGPRESS: "none" # long press stuurt alleen het event naar HA
@@ -456,7 +444,7 @@ substitutions:
TILE9_ENTITY: "light.ongebruikt_9" # main Home Assistant entity for this tile
TILE9_STATE_ENTITY: "light.ongebruikt_9" # entity used for active/inactive state styling
TILE9_TITLE: "Tegel 9" # tile title shown in the UI
TILE9_ICON: "\U000F04B9" # mdi:sofa — also listed in MDI_GLYPH_9 above
TILE9_ICON: "\U000F04B9" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE9_TYPE: "light" # tile type: light | fan | switch | scene | script | cover | climate
TILE9_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset
TILE9_LONGPRESS: "none" # long press mode: none | slider | action | vacuum
@@ -499,7 +487,7 @@ substitutions:
TILE10_ENTITY: "light.ongebruikt_10" # main Home Assistant entity for this tile
TILE10_STATE_ENTITY: "light.ongebruikt_10" # entity used for active/inactive state styling
TILE10_TITLE: "Tegel 10" # tile title shown in the UI
TILE10_ICON: "\U000F002A" # mdi:alarm-light — also listed in MDI_GLYPH_10 above
TILE10_ICON: "\U000F002A" # MDI glyph from the tile icon set (screen_manager/app/tile_icons.py)
TILE10_TYPE: "light" # generiek aan/uit-type; de service hieronder doet het echte werk
TILE10_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset
TILE10_LONGPRESS: "none" # long press stuurt alleen het event naar HA
@@ -1055,116 +1043,176 @@ font:
- file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/materialdesignicons-webfont.ttf"
id: materialdesign_icons
size: 42
glyphs:
- "\U000F0335"
- "\U000F0210"
- "\U000F111C"
- "\U000F029A"
- "${MDI_GLYPH_1}"
- "${MDI_GLYPH_2}"
- "${MDI_GLYPH_3}"
- "${MDI_GLYPH_4}"
- "${MDI_GLYPH_5}"
- "${MDI_GLYPH_6}"
- "${MDI_GLYPH_7}"
- "${MDI_GLYPH_8}"
- "${MDI_GLYPH_9}"
- "${MDI_GLYPH_10}"
- "\U000F012C" # check
glyphs: &tile_icons # GENERATED by tools/generate_icons.py from screen_manager/app/tile_icons.py
- "\U000F0335" # lightbulb
- "\U000F1253" # lightbulb-group
- "\U000F0769" # ceiling-light
- "\U000F1793" # chandelier
- "\U000F06B5" # lamp
- "\U000F08DD" # floor-lamp
- "\U000F095F" # desk-lamp
- "\U000F091D" # wall-sconce-flat
- "\U000F179B" # light-recessed
- "\U000F04C9" # spotlight-beam
- "\U000F1051" # led-strip-variant
- "\U000F1054" # outdoor-lamp
- "\U000F02DC" # home
- "\U000F04B9" # sofa
- "\U000F02E3" # bed
- "\U000F0A70" # silverware-fork-knife
- "\U000F0B7C" # chef-hat
- "\U000F09A0" # shower
- "\U000F1818" # bathtub
- "\U000F09AB" # toilet
- "\U000F1239" # desk
- "\U000F06D9" # garage
- "\U000F0E2E" # fireplace
- "\U000F050F" # thermometer
- "\U000F0393" # thermostat
- "\U000F001B" # air-conditioner
- "\U000F0438" # radiator
- "\U000F1A43" # heat-pump
- "\U000F0238" # fire
- "\U000F0717" # snowflake
- "\U000F058E" # water-percent
- "\U000F0210" # fan
- "\U000F1797" # ceiling-fan
- "\U000F0D44" # air-purifier
- "\U000F07E4" # molecule-co2
- "\U000F0599" # weather-sunny
- "\U000F0594" # weather-night
- "\U000F0590" # weather-cloudy
- "\U000F0595" # weather-partly-cloudy
- "\U000F0590" # weather-cloudy
- "\U000F0597" # weather-rainy
- "\U000F0596" # weather-pouring
- "\U000F0598" # weather-snowy
- "\U000F067F" # weather-snowy-rainy
- "\U000F059D" # weather-windy
- "\U000F0594" # weather-night
- "\U000F059C" # weather-sunset-up
- "\U000F0387" # music-note
- "\U000F075A" # music
- "\U000F0CB8" # playlist-music
- "\U000F0025" # album
- "\U000F099A" # record-player
- "\U000F04C7" # spotify
- "\U000F0439" # radio
- "\U000F0994" # podcast
- "\U000F04C3" # speaker
- "\U000F0D38" # speaker-multiple
- "\U000F17DB" # soundbar
- "\U000F02CB" # headphones
- "\U000F067D" # piano
- "\U000F0771" # guitar-acoustic
- "\U000F0502" # television
- "\U000F042E" # projector
- "\U000F0FCE" # movie-open
- "\U000F0118" # cast
- "\U000F0297" # gamepad-variant
- "\U000F0EC5" # remote-tv
- "\U000F033E" # lock
- "\U000F0FC6" # lock-open-variant
- "\U000F068A" # shield-home
- "\U000F078F" # alarm-light
- "\U000F009A" # bell
- "\U000F12E6" # doorbell
- "\U000F07AE" # cctv
- "\U000F0D91" # motion-sensor
- "\U000F081B" # door-closed
- "\U000F081C" # door-open
- "\U000F11DB" # window-closed-variant
- "\U000F0392" # smoke-detector
- "\U000F1502" # water-alert
- "\U000F072A" # washing-machine
- "\U000F0917" # tumble-dryer
- "\U000F0AAC" # dishwasher
- "\U000F0290" # fridge
- "\U000F04DE" # stove
- "\U000F0C99" # microwave
- "\U000F0CD3" # toaster-oven
- "\U000F109F" # coffee-maker
- "\U000F05FA" # kettle
- "\U000F070D" # robot-vacuum
- "\U000F11F7" # robot-mower
- "\U000F042B" # printer-3d
- "\U000F06A5" # power-plug
- "\U000F07E7" # power-socket-eu
- "\U000F0322" # laptop
- "\U000F0469" # router-wireless
- "\U000F0241" # flash
- "\U000F0A72" # solar-power
- "\U000F0D9B" # solar-panel
- "\U000F1901" # home-battery
- "\U000F12A3" # battery-high
- "\U000F05F1" # ev-station
- "\U000F0B6C" # car-electric
- "\U000F1A57" # meter-electric
- "\U000F1A59" # meter-gas
- "\U000F058C" # water
- "\U000F0F92" # water-boiler
- "\U000F029A" # gauge
- "\U000F111C" # window-shutter
- "\U000F111E" # window-shutter-open
- "\U000F00AC" # blinds
- "\U000F1011" # blinds-open
- "\U000F1A6C" # roller-shade-closed
- "\U000F1846" # curtains
- "\U000F1847" # curtains-closed
- "\U000F1B88" # awning-outline
- "\U000F024A" # flower
- "\U000F0531" # tree
- "\U000F0E66" # sprout
- "\U000F1510" # grass
- "\U000F105F" # sprinkler
- "\U000F0606" # pool
- "\U000F0E45" # grill
- "\U000F06EE" # mailbox
- "\U000F0A79" # trash-can
- "\U000F044C" # recycle
- "\U000F0A43" # dog
- "\U000F011B" # cat
- "\U000F0004" # account
- "\U000F0849" # account-group
- "\U000F0A89" # account-child
- "\U000F0826" # home-account
- "\U000F04B2" # sleep
- "\U000F070E" # run
- "\U000F00D6" # briefcase
- "\U000F010B" # car
- "\U000F00A3" # bike
- "\U000F001D" # airplane
- "\U000F034E" # map-marker
- "\U000F0425" # power
- "\U000F0521" # toggle-switch
- "\U000F12A8" # gesture-tap-button
- "\U000F040A" # play
- "\U000F0BC2" # script-text
- "\U000F03D8" # palette
- "\U000F0020" # alarm
- "\U000F0150" # clock-outline
- "\U000F051B" # timer-outline
- "\U000F00ED" # calendar
- "\U000F009E" # bell-ring
- "\U000F02D1" # heart
- "\U000F002A" # alert-outline
- "\U000F0493" # cog
- "\U000F00E2" # broom
- "\U000F05D6" # alert-circle-outline
- "\U000F012C" # check
- "\U000F0591" # weather-fog
- "\U000F0592" # weather-hail
- "\U000F0593" # weather-lightning
- "\U000F067E" # weather-lightning-rainy
- "\U000F059D" # weather-windy
- "\U000F05D6" # alert-circle-outline
- "\U000F0596" # weather-pouring
- "\U000F067F" # weather-snowy-rainy
- "\U000F059B" # weather-sunset-down
- "\U000F0150" # clock-outline
- "\U000F051B" # timer-outline
- "\U000F0004" # account
- file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/materialdesignicons-webfont.ttf"
id: materialdesign_icons_mini
size: 26
glyphs:
- "\U000F0335"
- "\U000F0210"
- "\U000F111C"
- "\U000F029A"
- "${MDI_GLYPH_1}"
- "${MDI_GLYPH_2}"
- "${MDI_GLYPH_3}"
- "${MDI_GLYPH_4}"
- "${MDI_GLYPH_5}"
- "${MDI_GLYPH_6}"
- "${MDI_GLYPH_7}"
- "${MDI_GLYPH_8}"
- "${MDI_GLYPH_9}"
- "${MDI_GLYPH_10}"
- "\U000F012C" # check
- "\U000F0599" # weather-sunny
- "\U000F0594" # weather-night
- "\U000F0590" # weather-cloudy
- "\U000F0595" # weather-partly-cloudy
- "\U000F0597" # weather-rainy
- "\U000F0596" # weather-pouring
- "\U000F0598" # weather-snowy
- "\U000F067F" # weather-snowy-rainy
- "\U000F0591" # weather-fog
- "\U000F0592" # weather-hail
- "\U000F0593" # weather-lightning
- "\U000F067E" # weather-lightning-rainy
- "\U000F059D" # weather-windy
- "\U000F05D6" # alert-circle-outline
- "\U000F059B" # weather-sunset-down
- "\U000F0150" # clock-outline
- "\U000F051B" # timer-outline
- "\U000F0004" # account
glyphs: *tile_icons
- file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/materialdesignicons-webfont.ttf"
id: watch_icon
size: 18
glyphs:
- "\U000F0335"
- "\U000F0210"
- "\U000F111C"
- "\U000F029A"
- "${MDI_GLYPH_1}"
- "${MDI_GLYPH_2}"
- "${MDI_GLYPH_3}"
- "${MDI_GLYPH_4}"
- "${MDI_GLYPH_5}"
- "${MDI_GLYPH_6}"
- "${MDI_GLYPH_7}"
- "${MDI_GLYPH_8}"
- "${MDI_GLYPH_9}"
- "${MDI_GLYPH_10}"
- "\U000F012C" # check
- "\U000F0599" # weather-sunny
- "\U000F0594" # weather-night
- "\U000F0590" # weather-cloudy
- "\U000F0595" # weather-partly-cloudy
- "\U000F0597" # weather-rainy
- "\U000F0596" # weather-pouring
- "\U000F0598" # weather-snowy
- "\U000F067F" # weather-snowy-rainy
- "\U000F0591" # weather-fog
- "\U000F0592" # weather-hail
- "\U000F0593" # weather-lightning
- "\U000F067E" # weather-lightning-rainy
- "\U000F059D" # weather-windy
- "\U000F05D6" # alert-circle-outline
- "\U000F059B" # weather-sunset-down
- "\U000F0150" # clock-outline
- "\U000F051B" # timer-outline
- "\U000F0004" # account
glyphs: *tile_icons
# Geheugen zichtbaar maken: dit bord heeft geen PSRAM, dus de vrije heap
# bepaalt hoeveel UI erbij kan. Verschijnt ook als entity in Home Assistant.
+7
View File
@@ -1,3 +1,10 @@
## 0.2.18 (firmware 0.2.18)
- **Eigen icoon per tegel**: in het tegelpaneel staat onder de naam het veld **Icoon**. Kies uit 150 iconen in twaalf groepen (verlichting, ruimtes, klimaat, weer, media en muziek, beveiliging, apparaten, energie, zonwering, tuin en huisdieren, mensen en onderweg, overig), met zoeken op Nederlandse naam, Engelse MDI-naam of groep. De mockup en de kop van het paneel tonen het gekozen icoon direct.
- **Automatisch** gebruikt het icoon dat je in Home Assistant aan de entiteit gaf (`mdi:…`), als het in de set zit; anders het standaardicoon zoals voorheen. De mockup toont nu overal het icoon dat het scherm werkelijk tekent.
- Firmware 0.2.18 bevat alle 150 iconen in de drie icoonfonts van beide borden (CYD +22 KB, Guition +36 KB). Oudere firmware negeert de keuze en houdt het standaardicoon; het paneel meldt dat een update nodig is.
- De weersvoorspelling blijft het icoon van het actuele weer tonen; klok, voorspelling en zonnebaan hebben geen icoonkeuze.
## 0.2.17 (firmware 0.2.17 blijft actueel)
- Het vinkje **Elke nacht automatisch bijwerken** kreeg de algemene invoerstijl (100% breed met padding), waardoor de tekst buiten de zijkolom onder het editorpaneel viel. Het checkboxje heeft nu een vaste maat. Alleen de app verandert.
+24 -5
View File
@@ -6,6 +6,8 @@ import math
import re
import secrets
import tile_icons
DOMAINS = frozenset('light switch input_boolean scene script climate vacuum fan cover sensor binary_sensor input_select select number input_number weather media_player button input_button sun timer person screen'.split())
# Built-in cards without a Home Assistant entity; firmware 0.2.14+ renders them.
BUILTIN = {'screen.clock': 'Klok'}
@@ -14,7 +16,7 @@ WEEKDAYS = ['ma', 'di', 'wo', 'do', 'vr', 'za', 'zo']
REPO = 'https://github.com/MaxGramser/homeassistant_espscreen'
REFS = {'cyd': 'main', 'guition': 'main'}
# Firmware shipped with this app release; screens below it get an update offer.
FIRMWARE_VERSION = '0.2.17'
FIRMWARE_VERSION = '0.2.18'
ATTRS = frozenset('brightness percentage current_position current_temperature temperature current_humidity min_temp max_temp target_temp_step supported_color_modes hvac_modes hs_color color_temp_kelvin min_color_temp_kelvin max_color_temp_kelvin fan_speed_list unit_of_measurement battery_level fan_speed volume_level media_title options min max step temperature_unit supported_features next_rising next_setting finishes_at duration remaining'.split())
@@ -112,10 +114,12 @@ def validate_layout(data):
item = {'entity': tile['entity'], 'name': name.strip()}
if 'options' in tile:
options = tile['options']
if not isinstance(options, dict) or set(options) - {'tap', 'display', 'inline', 'history_hours', 'background', 'size'}:
if not isinstance(options, dict) or set(options) - {'tap', 'display', 'inline', 'history_hours', 'background', 'size', 'icon'}:
raise ValueError('Onbekende tegelinstellingen.')
if 'background' in options and (not isinstance(options['background'],str) or options['background'] not in TILE_BACKGROUNDS):
raise ValueError('Kies een pastel achtergrondkleur uit het palet.')
if 'icon' in options and not (options['icon'] == 'auto' or isinstance(options['icon'], str) and options['icon'] in tile_icons.ICONS):
raise ValueError('Kies een icoon uit de lijst.')
domain = tile['entity'].split('.')[0]
displays = DISPLAYS.get(domain, ('standard', 'watch'))
choices = {'tap': ('auto', 'detail', 'toggle', 'none'), 'display': displays, 'inline': ('none', 'slider'), 'size': ('single', 'wide')}
@@ -186,6 +190,19 @@ def extras(tile, states, forecast=None, tz=None):
return result or None
return None
def tile_icon(tile, attrs):
"""Codepoint the screen shows: the chosen icon, else HA's own mdi icon; None keeps the firmware default."""
choice = tile.get('options', {}).get('icon', 'auto')
return tile_icons.ICONS[choice][0] if choice in tile_icons.ICONS else tile_icons.ha_icon(attrs)
def screen_options(tile, attrs):
"""Stored options on the wire; `icon` travels as the resolved codepoint (firmware 0.2.18+, ignored before)."""
options = {k: v for k, v in tile.get('options', {}).items() if k != 'icon'}
icon = tile_icon(tile, attrs)
if icon:
options['icon'] = icon
return options if options or 'options' in tile else None
def state_message(index, tile, states, extra=None):
if tile['entity'] in BUILTIN:
message = {'v': 1, 'op': 'state', 'i': index, 'entity': tile['entity'],
@@ -210,10 +227,11 @@ def state_message(index, tile, states, extra=None):
limit = 2 if key == 'hs_color' else 4 if key == 'fan_speed_list' else 8
bounded[key] = [short(v, 48) if isinstance(v, str) else v for v in value[:limit]
if isinstance(v, str) or isinstance(v, (float, int)) and math.isfinite(v) and abs(v) <= 1000000]
options = screen_options(tile, attrs)
return {'v': 1, 'op': 'state', 'i': index, 'entity': tile['entity'],
'name': short(tile['name'] or attrs.get('friendly_name') or tile['entity'], 80),
'state': short(state.get('state', 'unavailable'), 160), 'a': bounded,
**({'o': tile['options']} if 'options' in tile else {}), **({'x': extra} if extra else {})}
**({'o': options} if options is not None else {}), **({'x': extra} if extra else {})}
def packets(message, token=None):
raw = json.dumps(message, ensure_ascii=False, separators=(',', ':'), allow_nan=False).encode()
@@ -259,12 +277,13 @@ def discover(registry, states, devices, areas):
continue
entities.append({'id': eid, 'name': state.get('attributes', {}).get('friendly_name') or item.get('name') or item.get('original_name') or eid,
'device': device.get('name_by_user') or device.get('name') or '', 'area': area,
'state': state.get('state', 'unavailable')})
'state': state.get('state', 'unavailable'), 'icon': tile_icons.ha_icon(state.get('attributes'))})
# YAML entities may not have an entity-registry entry.
registered = {e['id'] for e in entities}
for eid, state in states.items():
if entity_id(eid) and eid not in registered and not any(r['entity_id'] == eid for r in registry):
entities.append({'id': eid, 'name': state.get('attributes', {}).get('friendly_name', eid), 'device': '', 'area': '', 'state': state['state']})
entities.append({'id': eid, 'name': state.get('attributes', {}).get('friendly_name', eid), 'device': '', 'area': '', 'state': state['state'],
'icon': tile_icons.ha_icon(state.get('attributes'))})
return screens, sorted(entities, key=lambda e: e['name'].casefold())
def installation_yaml(data):
+5 -3
View File
@@ -10,6 +10,7 @@ import time
from datetime import datetime, timedelta, timezone
import math
from firmware import Firmware
import tile_icons
from updates import Updater
from aiohttp import ClientSession, ClientTimeout, WSMsgType, web
@@ -220,8 +221,9 @@ class Manager:
old_tiles = {t['entity']:t for t in self.layouts.get(inbox,{}).get('tiles',[])}
for tile in layout['tiles']:
old_options=old_tiles.get(tile['entity'],{}).get('options',{})
if 'options' in tile and 'background' not in tile['options'] and 'background' in old_options:
tile['options']['background']=old_options['background']
for key in ('background', 'icon'):
if 'options' in tile and key not in tile['options'] and key in old_options:
tile['options'][key]=old_options[key]
if 'options' not in tile and 'options' in old_tiles.get(tile['entity'],{}):
tile['options'] = old_tiles[tile['entity']]['options'].copy()
known = {e['id'] for e in self.inventory()[1]} | set(BUILTIN)
@@ -361,7 +363,7 @@ def create_app(manager, development=False):
screen['update'] = manager.updates.state_for(screen)
builtin = [{'id': key, 'name': name, 'device': 'Ingebouwd op het scherm', 'area': '', 'state': 'ok'} for key, name in BUILTIN.items()]
return web.json_response({'csrf': csrf, 'connected': manager.ha.online, 'screens': screens, 'entities': entities,
'backgrounds': TILE_BACKGROUNDS, 'builtin': builtin, 'updates': manager.updates.summary()})
'backgrounds': TILE_BACKGROUNDS, 'icons': tile_icons.editor(), 'builtin': builtin, 'updates': manager.updates.summary()})
async def save(request):
manager.save(request.match_info['inbox'], await request.json())
return web.json_response({'saved': True})
+114 -3
View File
@@ -405,6 +405,29 @@ function domainBadge(id) {
badge.style.background = background;
return badge;
}
// The screen draws Material Design Icons; static/tile-icons.woff holds the same glyphs.
const glyph = (cp) => String.fromCodePoint(parseInt(cp, 16));
let iconIndex = { source: null, byName: {} };
function iconNamed(name) {
if (iconIndex.source !== inventory.icons)
iconIndex = { source: inventory.icons, byName: Object.fromEntries((inventory.icons?.groups || []).flatMap((g) => g.icons.map((i) => [i.name, i]))) };
return iconIndex.byName[name];
}
// What the firmware draws without a choice: Home Assistant's own icon, else the domain icon.
function automaticIcon(id) {
const icons = inventory.icons, entity = inventory.entities.find((e) => e.id === id), domain = id.split(".")[0];
if (entity?.icon) return entity.icon;
if (domain === "weather") return icons.weather[entity?.state] || icons.weather.partlycloudy;
if (domain === "sun") return icons.sun[entity?.state] || icons.sun.below_horizon;
return icons.defaults[domain] || icons.fallback;
}
function tileBadge(tile) {
const badge = domainBadge(tile.entity);
if (!inventory.icons) return badge;
badge.textContent = glyph(iconNamed(tile.options?.icon)?.cp || automaticIcon(tile.entity));
badge.classList.add("mdi");
return badge;
}
function tileLimit() {
const version=inventory.screens.find(s=>s.id===selected)?.firmware || "";
const match=/^(\d+)\.(\d+)\.(\d+)$/.exec(version);
@@ -437,7 +460,7 @@ function renderPreview() {
// "Geen": no card on the screen; the mockup keeps a dashed outline as drop target.
if (tile.options?.background === "none") card.classList.add("bare");
if (place.wide) card.classList.add("wide");
card.append(domainBadge(tile.entity), node("strong", name));
card.append(tileBadge(tile), node("strong", name));
card.dataset.index = index;
enableDrag(card, { kind: "tile", index });
card.classList.toggle("chosen", selectedTile === tile.entity);
@@ -487,8 +510,9 @@ function removeTile(index) {
}
// Tile settings open in a sheet above the mockup; every change applies live,
// so the card behind it shows the result while you pick.
let sheetIndex = -1;
let sheetIndex = -1, iconPickerOpen = false;
function openTileSheet(index) {
if (index !== sheetIndex) iconPickerOpen = false;
sheetIndex = index;
selectedTile = layout.tiles[index].entity;
renderTileSheet();
@@ -520,6 +544,89 @@ function segmented(choices, value, onChange) {
}
return group;
}
// A choice applies live like the palette: only pressed states, the summary and the
// badges update, so search text and scroll position survive.
function iconField(tile, onChange) {
const wrap = node("div", undefined, "sheet-field");
wrap.append(node("span", "Icoon"));
const fromHA = Boolean(inventory.entities.find((e) => e.id === tile.entity)?.icon);
const autoLabel = `Automatisch (${fromHA ? "uit Home Assistant" : "standaard"})`;
const summary = node("button", undefined, "icon-current");
summary.type = "button";
summary.setAttribute("aria-expanded", String(iconPickerOpen));
const current = node("span", undefined, "mdi"), text = node("span"), action = node("small", iconPickerOpen ? "Sluiten" : "Wijzigen");
summary.append(current, text, action);
const describe = () => {
const chosen = iconNamed(tile.options?.icon);
current.textContent = glyph(chosen?.cp || automaticIcon(tile.entity));
text.textContent = chosen?.label || autoLabel;
};
describe();
const panel = node("div", undefined, "icon-picker");
panel.hidden = !iconPickerOpen;
summary.onclick = () => {
iconPickerOpen = panel.hidden;
panel.hidden = !iconPickerOpen;
summary.setAttribute("aria-expanded", String(iconPickerOpen));
action.textContent = iconPickerOpen ? "Sluiten" : "Wijzigen";
};
const choice = (name, cp, label, cls = "") => {
const b = node("button", undefined, `icon-choice ${cls}`);
b.type = "button";
b.dataset.icon = name;
b.dataset.search = label.toLocaleLowerCase();
b.title = label;
b.setAttribute("aria-label", `Icoon: ${label}`);
b.setAttribute("aria-pressed", String((tile.options?.icon || "auto") === name));
b.append(node("span", glyph(cp), "mdi"));
b.onclick = () => {
tile.options = { ...tile.options, icon: name };
for (const other of panel.querySelectorAll(".icon-choice")) other.setAttribute("aria-pressed", String(other === b));
describe();
markDirty();
renderPreview();
onChange();
};
return b;
};
const search = node("input");
search.type = "search";
search.placeholder = "Zoek, bijvoorbeeld lamp, muziek of deur";
search.setAttribute("aria-label", "Zoek een icoon");
const auto = choice("auto", automaticIcon(tile.entity), autoLabel, "icon-auto");
auto.append(node("span", autoLabel));
const list = node("div", undefined, "icon-list"), empty = node("p", "Geen icoon gevonden.", "hint");
empty.hidden = true;
const sections = inventory.icons.groups.map((group) => {
const section = node("section"), grid = node("div", undefined, "icon-grid");
for (const icon of group.icons) {
const b = choice(icon.name, icon.cp, icon.label);
b.dataset.search += ` ${icon.name.replaceAll("-", " ")} ${group.label.toLocaleLowerCase()}`;
grid.append(b);
}
section.append(node("small", group.label), grid);
list.append(section);
return section;
});
search.oninput = () => {
const query = search.value.trim().toLocaleLowerCase();
let found = 0;
for (const section of sections) {
let visible = 0;
for (const b of section.querySelectorAll(".icon-choice")) {
b.hidden = !b.dataset.search.includes(query);
if (!b.hidden) visible++;
}
section.hidden = !visible;
found += visible;
}
empty.hidden = found > 0;
};
panel.append(search, auto, list, empty);
wrap.append(summary, panel);
if (!supportsFirmware(0, 2, 18)) wrap.append(node("small", "Het scherm toont een gekozen icoon vanaf firmware 0.2.18.", "icon-hint"));
return wrap;
}
function renderTileSheet() {
const sheet = $("#tile-sheet"), tile = layout.tiles[sheetIndex];
if (!tile) return;
@@ -531,7 +638,8 @@ function renderTileSheet() {
close.type = "button";
close.setAttribute("aria-label", "Sluiten");
close.onclick = closeTileSheet;
head.append(domainBadge(tile.entity), titles, close);
let badge = tileBadge(tile);
head.append(badge, titles, close);
const body = node("div", undefined, "sheet-body");
const nameInput = node("input");
nameInput.value = tile.name;
@@ -539,6 +647,9 @@ function renderTileSheet() {
nameInput.maxLength = 60;
nameInput.oninput = () => { tile.name = nameInput.value; markDirty(); renderPreview(); };
body.append(field("Naam op het scherm", nameInput));
// The clock, forecast and sun path cards draw no tile icon.
if (inventory.icons && domain !== "screen" && !["forecast", "sunpath"].includes(tile.options?.display))
body.append(iconField(tile, () => { const next = tileBadge(tile); badge.replaceWith(next); badge = next; }));
const displays = domain === "screen"
? [["digital", "Digitale klok"], ["analog", "Analoge klok"]]
: [["standard", "Naam en status"], ["watch", "Grote waarde"]];
+22
View File
@@ -957,3 +957,25 @@ pre {
.tile-sheet { width: 100%; max-width: 100%; margin: auto 0 0; border-radius: 20px 20px 0 0; max-height: 90vh; }
.sheet-body { max-height: 52vh; }
}
/* Tile icons: the same Material Design Icons subset the screen's fonts carry. */
@font-face { font-family: "Tile Icons"; src: url("tile-icons.woff") format("woff"); font-display: block; }
.mdi { font-family: "Tile Icons"; font-style: normal; font-weight: normal; line-height: 1; }
.domain-icon.mdi { font-size: 22px; }
.preview-tile .domain-icon.mdi { font-size: 19px; }
.icon-current { display: flex; align-items: center; gap: 10px; width: 100%; padding: 7px 12px 7px 7px; background: white; color: var(--ink); border: 1px solid #cdd5df; border-radius: 12px; font-size: 14px; font-weight: 500; text-align: left; }
.icon-current .mdi { display: grid; place-items: center; width: 36px; height: 36px; flex: none; border-radius: 9px; background: #f3f5f8; font-size: 24px; }
.icon-current > span:nth-child(2) { flex: 1; min-width: 0; }
.icon-current small { color: var(--accent); font-weight: 600; }
.icon-picker { display: grid; gap: 10px; padding: 10px; border: 1px solid var(--line); border-radius: 12px; background: #fafbfc; }
.icon-picker input { font-size: 14px; font-weight: 400; }
.icon-list { display: grid; gap: 12px; max-height: 300px; overflow: auto; padding-right: 2px; }
.icon-list small { display: block; margin-bottom: 6px; }
.icon-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(44px, 1fr)); gap: 6px; }
.icon-choice { display: grid; place-items: center; height: 44px; padding: 0; background: white; color: var(--ink); border: 1px solid var(--line); border-radius: 10px; }
.icon-choice .mdi { font-size: 25px; }
.icon-choice[aria-pressed="true"] { background: var(--soft); color: var(--accent); border-color: #9dbcf5; box-shadow: inset 0 0 0 1px #9dbcf5; }
.icon-choice.icon-auto { display: flex; justify-content: flex-start; gap: 10px; padding: 0 12px; font-size: 13px; font-weight: 500; }
.icon-choice.icon-auto .mdi { font-size: 22px; }
.icon-picker .hint { margin: 0; }
.icon-hint { font-weight: 400; }
Binary file not shown.
+226
View File
@@ -0,0 +1,226 @@
"""Curated Material Design Icons for tiles, shared by the editor and both firmware boards.
This is the only list. `python3 tools/generate_icons.py` writes these glyphs into
the three icon fonts of both board profiles and builds the editor font
`static/tile-icons.woff`; `--check` verifies names and codepoints against
fonts/materialdesignicons-webfont.ttf. Firmware 0.2.18+ carries every glyph here.
"""
# (group, ((MDI name, codepoint, label), ...)) in picker order.
GROUPS = (
('Verlichting', (
('lightbulb', 'F0335', 'Lamp'),
('lightbulb-group', 'F1253', 'Lampen'),
('ceiling-light', 'F0769', 'Plafondlamp'),
('chandelier', 'F1793', 'Kroonluchter'),
('lamp', 'F06B5', 'Tafellamp'),
('floor-lamp', 'F08DD', 'Vloerlamp'),
('desk-lamp', 'F095F', 'Bureaulamp'),
('wall-sconce-flat', 'F091D', 'Wandlamp'),
('light-recessed', 'F179B', 'Inbouwspot'),
('spotlight-beam', 'F04C9', 'Spot'),
('led-strip-variant', 'F1051', 'Ledstrip'),
('outdoor-lamp', 'F1054', 'Buitenlamp'),
)),
('Ruimtes', (
('home', 'F02DC', 'Huis'),
('sofa', 'F04B9', 'Woonkamer'),
('bed', 'F02E3', 'Slaapkamer'),
('silverware-fork-knife', 'F0A70', 'Eetkamer'),
('chef-hat', 'F0B7C', 'Keuken'),
('shower', 'F09A0', 'Douche'),
('bathtub', 'F1818', 'Badkamer'),
('toilet', 'F09AB', 'Toilet'),
('desk', 'F1239', 'Werkkamer'),
('garage', 'F06D9', 'Garage'),
('fireplace', 'F0E2E', 'Open haard'),
)),
('Klimaat', (
('thermometer', 'F050F', 'Temperatuur'),
('thermostat', 'F0393', 'Thermostaat'),
('air-conditioner', 'F001B', 'Airco'),
('radiator', 'F0438', 'Radiator'),
('heat-pump', 'F1A43', 'Warmtepomp'),
('fire', 'F0238', 'Verwarming'),
('snowflake', 'F0717', 'Koelen'),
('water-percent', 'F058E', 'Luchtvochtigheid'),
('fan', 'F0210', 'Ventilator'),
('ceiling-fan', 'F1797', 'Plafondventilator'),
('air-purifier', 'F0D44', 'Luchtreiniger'),
('molecule-co2', 'F07E4', 'CO₂'),
)),
('Weer', (
('weather-sunny', 'F0599', 'Zon'),
('weather-partly-cloudy', 'F0595', 'Half bewolkt'),
('weather-cloudy', 'F0590', 'Bewolkt'),
('weather-rainy', 'F0597', 'Regen'),
('weather-snowy', 'F0598', 'Sneeuw'),
('weather-windy', 'F059D', 'Wind'),
('weather-night', 'F0594', 'Nacht'),
('weather-sunset-up', 'F059C', 'Zonsopkomst'),
)),
('Media en muziek', (
('music-note', 'F0387', 'Muziek'),
('music', 'F075A', 'Muzieknoten'),
('playlist-music', 'F0CB8', 'Afspeellijst'),
('album', 'F0025', 'Album'),
('record-player', 'F099A', 'Platenspeler'),
('spotify', 'F04C7', 'Spotify'),
('radio', 'F0439', 'Radio'),
('podcast', 'F0994', 'Podcast'),
('speaker', 'F04C3', 'Speaker'),
('speaker-multiple', 'F0D38', 'Speakers'),
('soundbar', 'F17DB', 'Soundbar'),
('headphones', 'F02CB', 'Koptelefoon'),
('piano', 'F067D', 'Piano'),
('guitar-acoustic', 'F0771', 'Gitaar'),
('television', 'F0502', 'Televisie'),
('projector', 'F042E', 'Beamer'),
('movie-open', 'F0FCE', 'Film'),
('cast', 'F0118', 'Casten'),
('gamepad-variant', 'F0297', 'Gameconsole'),
('remote-tv', 'F0EC5', 'Afstandsbediening'),
)),
('Beveiliging', (
('lock', 'F033E', 'Slot'),
('lock-open-variant', 'F0FC6', 'Slot open'),
('shield-home', 'F068A', 'Alarm'),
('alarm-light', 'F078F', 'Alarmlicht'),
('bell', 'F009A', 'Bel'),
('doorbell', 'F12E6', 'Deurbel'),
('cctv', 'F07AE', 'Camera'),
('motion-sensor', 'F0D91', 'Bewegingsmelder'),
('door-closed', 'F081B', 'Deur'),
('door-open', 'F081C', 'Deur open'),
('window-closed-variant', 'F11DB', 'Raam'),
('smoke-detector', 'F0392', 'Rookmelder'),
('water-alert', 'F1502', 'Waterlek'),
)),
('Apparaten', (
('washing-machine', 'F072A', 'Wasmachine'),
('tumble-dryer', 'F0917', 'Droger'),
('dishwasher', 'F0AAC', 'Vaatwasser'),
('fridge', 'F0290', 'Koelkast'),
('stove', 'F04DE', 'Fornuis'),
('microwave', 'F0C99', 'Magnetron'),
('toaster-oven', 'F0CD3', 'Oven'),
('coffee-maker', 'F109F', 'Koffie'),
('kettle', 'F05FA', 'Waterkoker'),
('robot-vacuum', 'F070D', 'Robotstofzuiger'),
('robot-mower', 'F11F7', 'Robotmaaier'),
('printer-3d', 'F042B', '3D-printer'),
('power-plug', 'F06A5', 'Stekker'),
('power-socket-eu', 'F07E7', 'Stopcontact'),
('laptop', 'F0322', 'Laptop'),
('router-wireless', 'F0469', 'Router'),
)),
('Energie', (
('flash', 'F0241', 'Stroom'),
('solar-power', 'F0A72', 'Zonne-energie'),
('solar-panel', 'F0D9B', 'Zonnepanelen'),
('home-battery', 'F1901', 'Thuisbatterij'),
('battery-high', 'F12A3', 'Batterij'),
('ev-station', 'F05F1', 'Laadpaal'),
('car-electric', 'F0B6C', 'Elektrische auto'),
('meter-electric', 'F1A57', 'Stroommeter'),
('meter-gas', 'F1A59', 'Gasmeter'),
('water', 'F058C', 'Water'),
('water-boiler', 'F0F92', 'Boiler'),
('gauge', 'F029A', 'Meter'),
)),
('Zonwering', (
('window-shutter', 'F111C', 'Rolluik'),
('window-shutter-open', 'F111E', 'Rolluik open'),
('blinds', 'F00AC', 'Jaloezie'),
('blinds-open', 'F1011', 'Jaloezie open'),
('roller-shade-closed', 'F1A6C', 'Rolgordijn'),
('curtains', 'F1846', 'Gordijnen'),
('curtains-closed', 'F1847', 'Gordijnen dicht'),
('awning-outline', 'F1B88', 'Zonnescherm'),
)),
('Tuin en huisdieren', (
('flower', 'F024A', 'Bloem'),
('tree', 'F0531', 'Boom'),
('sprout', 'F0E66', 'Plant'),
('grass', 'F1510', 'Gazon'),
('sprinkler', 'F105F', 'Sproeier'),
('pool', 'F0606', 'Zwembad'),
('grill', 'F0E45', 'Barbecue'),
('mailbox', 'F06EE', 'Brievenbus'),
('trash-can', 'F0A79', 'Afval'),
('recycle', 'F044C', 'Recycling'),
('dog', 'F0A43', 'Hond'),
('cat', 'F011B', 'Kat'),
)),
('Mensen en onderweg', (
('account', 'F0004', 'Persoon'),
('account-group', 'F0849', 'Gezin'),
('account-child', 'F0A89', 'Kind'),
('home-account', 'F0826', 'Thuis'),
('sleep', 'F04B2', 'Slapen'),
('run', 'F070E', 'Sporten'),
('briefcase', 'F00D6', 'Werk'),
('car', 'F010B', 'Auto'),
('bike', 'F00A3', 'Fiets'),
('airplane', 'F001D', 'Vakantie'),
('map-marker', 'F034E', 'Locatie'),
)),
('Overig', (
('power', 'F0425', 'Aan/uit'),
('toggle-switch', 'F0521', 'Schakelaar'),
('gesture-tap-button', 'F12A8', 'Knop'),
('play', 'F040A', 'Start'),
('script-text', 'F0BC2', 'Script'),
('palette', 'F03D8', 'Scène'),
('alarm', 'F0020', 'Wekker'),
('clock-outline', 'F0150', 'Klok'),
('timer-outline', 'F051B', 'Timer'),
('calendar', 'F00ED', 'Agenda'),
('bell-ring', 'F009E', 'Melding'),
('heart', 'F02D1', 'Favoriet'),
('alert-outline', 'F002A', 'Waarschuwing'),
('cog', 'F0493', 'Instellingen'),
('broom', 'F00E2', 'Schoonmaken'),
)),
)
# Glyphs the firmware draws itself (weather conditions, sun, checkmark) that the picker does not offer.
FIXED = (
('alert-circle-outline', 'F05D6'),
('check', 'F012C'),
('weather-fog', 'F0591'),
('weather-hail', 'F0592'),
('weather-lightning', 'F0593'),
('weather-lightning-rainy', 'F067E'),
('weather-pouring', 'F0596'),
('weather-snowy-rainy', 'F067F'),
('weather-sunset-down', 'F059B'),
)
# Pickable icons by name, and every glyph the firmware fonts contain.
ICONS = {name: (codepoint, label) for _, icons in GROUPS for name, codepoint, label in icons}
GLYPHS = {**{name: codepoint for name, (codepoint, _) in ICONS.items()}, **dict(FIXED)}
# Mirrors runtime_tiles::icon_for() so the editor mockup shows what the screen draws.
DEFAULTS = {'light': 'lightbulb', 'climate': 'air-conditioner', 'vacuum': 'robot-vacuum', 'fan': 'fan',
'cover': 'window-shutter', 'scene': 'sofa', 'script': 'sofa', 'sensor': 'gauge', 'binary_sensor': 'gauge',
'timer': 'timer-outline', 'person': 'account', 'screen': 'clock-outline'}
FALLBACK = 'power'
WEATHER = {'sunny': 'weather-sunny', 'clear-night': 'weather-night', 'cloudy': 'weather-cloudy',
'partlycloudy': 'weather-partly-cloudy', 'rainy': 'weather-rainy', 'pouring': 'weather-pouring',
'snowy': 'weather-snowy', 'snowy-rainy': 'weather-snowy-rainy', 'fog': 'weather-fog', 'hail': 'weather-hail',
'lightning': 'weather-lightning', 'lightning-rainy': 'weather-lightning-rainy', 'windy': 'weather-windy',
'windy-variant': 'weather-windy', 'exceptional': 'alert-circle-outline'}
def ha_icon(attributes):
"""Codepoint of Home Assistant's own `mdi:` icon when the firmware carries it, else None."""
icon = attributes.get('icon') if isinstance(attributes, dict) else None
return GLYPHS.get(icon[4:]) if isinstance(icon, str) and icon.startswith('mdi:') else None
def editor():
"""Picker groups plus what the mockup needs to predict the screen's own icons."""
return {'groups': [{'label': group, 'icons': [{'name': name, 'cp': codepoint, 'label': label} for name, codepoint, label in icons]}
for group, icons in GROUPS],
'defaults': {domain: GLYPHS[name] for domain, name in DEFAULTS.items()}, 'fallback': GLYPHS[FALLBACK],
'weather': {state: GLYPHS[name] for state, name in WEATHER.items()},
'sun': {'above_horizon': GLYPHS['weather-sunset-down'], 'below_horizon': GLYPHS['weather-sunset-up']}}
+1 -1
View File
@@ -1,5 +1,5 @@
name: ESP Screen Manager
version: "0.2.17"
version: "0.2.18"
slug: esp_screen_manager
description: Kies tegels voor je CYD en Guition touchscreen, zonder opnieuw flashen.
url: https://github.com/MaxGramser/homeassistant_espscreen
+12
View File
@@ -0,0 +1,12 @@
#include "components/smart_display/tile_icon.h"
#include <cassert>
int main() {
assert(tile_icon::codepoint("F06B5")==0xF06B5);
assert(tile_icon::codepoint("f1d17")==0xF1D17);
// Names, short or long hex, non-MDI ranges and junk keep the domain icon.
for (const char *bad : {"", "lamp", "auto", "F06B", "F06B50", "006B5", "F06G5", " F06B", "-F06B"})
assert(tile_icon::codepoint(bad)==0);
assert(tile_icon::utf8(0xF06B5)=="\U000F06B5");
assert(tile_icon::utf8(0xF0335)=="\U000F0335");
assert(tile_icon::utf8(0).empty() && tile_icon::utf8(0x41).empty());
}
+93
View File
@@ -0,0 +1,93 @@
import re
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'screen_manager/app'))
import test_portal
import tile_icons
from core import discover, packets, state_message, validate_layout
HEADER = (ROOT / 'components/smart_display/runtime_tiles.h').read_text()
class IconSetTests(unittest.TestCase):
def test_set_is_complete_and_carried_by_every_icon_font(self):
self.assertEqual(len(tile_icons.ICONS), 150)
self.assertTrue(any(group == 'Media en muziek' and len(icons) >= 20 for group, icons in tile_icons.GROUPS))
self.assertEqual(len(set(tile_icons.GLYPHS.values())), len(tile_icons.GLYPHS))
for code in tile_icons.GLYPHS.values():
self.assertRegex(code, r'^F[0-9A-F]{4}$')
wanted = [f'\\U000{code}' for code in tile_icons.GLYPHS.values()]
for name in ('home-like-2432s028.yaml', 'guition-4848s040.yaml', 'packages/cyd.yaml', 'packages/guition.yaml'):
text = (ROOT / name).read_text()
fonts = re.findall(r'materialdesignicons-webfont\.ttf["\']\n id: (\w+)\n size: \d+\n glyphs: (.*)\n', text)
self.assertEqual([font for font, _ in fonts], ['materialdesign_icons', 'materialdesign_icons_mini', 'watch_icon'], name)
self.assertTrue(fonts[0][1].startswith('&tile_icons') and all(g == '*tile_icons' for _, g in fonts[1:]), name)
block = text.split('glyphs: &tile_icons', 1)[1].split('\n\n', 1)[0]
self.assertEqual(re.findall(r'- "(\\U000F[0-9A-F]{4})"', block), wanted, name)
self.assertNotIn('MDI_GLYPH', text, name)
self.assertTrue((ROOT / 'screen_manager/app/static/tile-icons.woff').exists())
def test_every_glyph_the_firmware_draws_is_in_the_set(self):
sources = [HEADER] + [(ROOT / name).read_text().split('glyphs: &tile_icons', 1)[0] for name in ('home-like-2432s028.yaml', 'guition-4848s040.yaml')]
used = {code.upper() for source in sources for code in re.findall(r'\\U000(F[0-9A-Fa-f]{4})', source)}
self.assertLessEqual(used, set(tile_icons.GLYPHS.values()))
def test_editor_predictions_mirror_the_firmware(self):
body = HEADER.split('inline const char *icon_for(const Tile &tile) {', 1)[1].split('\n}', 1)[0]
native = {}
for domains, code in re.findall(r'if \((d == "\w+"(?: \|\| d == "\w+")*)\) return "\\U000(F[0-9A-F]{4})";', body):
for domain in re.findall(r'"(\w+)"', domains):
native[domain] = code
self.assertEqual(native, {domain: tile_icons.GLYPHS[name] for domain, name in tile_icons.DEFAULTS.items()})
self.assertIn(f'return "\\U000{tile_icons.GLYPHS[tile_icons.FALLBACK]}";', body)
weather = HEADER.split('inline const char *weather_icon(const std::string &condition) {', 1)[1].split('\n}', 1)[0]
for state, name in tile_icons.WEATHER.items():
self.assertRegex(weather, rf'condition == "{re.escape(state)}"[^\n]*return "\\U000{tile_icons.GLYPHS[name]}";')
editor = tile_icons.editor()
self.assertEqual(sum(len(group['icons']) for group in editor['groups']), 150)
self.assertIn(f'"\\U000{editor["sun"]["above_horizon"]}" : "\\U000{editor["sun"]["below_horizon"]}"', HEADER)
class IconOptionTests(unittest.IsolatedAsyncioTestCase):
def test_validation_accepts_the_set_and_auto_only(self):
for value in ('auto', 'lamp', 'spotify', 'speaker'):
layout = validate_layout({'title': 'Thuis', 'tiles': [{'entity': 'media_player.a', 'options': {'icon': value}}]})
self.assertEqual(layout['tiles'][0]['options']['icon'], value)
for value in ('mdi:lamp', 'F06B5', 'weather-fog', 'unknown', '', None, 42, ['lamp']):
with self.assertRaises(ValueError):
validate_layout({'title': 'Thuis', 'tiles': [{'entity': 'light.a', 'options': {'icon': value}}]})
def test_wire_carries_the_codepoint_chosen_or_from_home_assistant(self):
states = {'light.a': {'state': 'on', 'attributes': {'icon': 'mdi:floor-lamp'}},
'light.b': {'state': 'on', 'attributes': {'icon': 'mdi:not-in-the-set'}},
'light.c': {'state': 'on', 'attributes': {}}}
chosen = state_message(0, {'entity': 'light.a', 'name': '', 'options': {'icon': 'lamp', 'tap': 'toggle'}}, states)
self.assertEqual(chosen['o'], {'tap': 'toggle', 'icon': 'F06B5'})
automatic = state_message(0, {'entity': 'light.a', 'name': '', 'options': {'icon': 'auto'}}, states)
self.assertEqual(automatic['o'], {'icon': 'F08DD'})
self.assertEqual(state_message(0, {'entity': 'light.a', 'name': ''}, states)['o'], {'icon': 'F08DD'})
self.assertNotIn('o', state_message(0, {'entity': 'light.b', 'name': ''}, states))
self.assertNotIn('o', state_message(0, {'entity': 'light.c', 'name': ''}, states))
self.assertEqual(state_message(0, {'entity': 'light.c', 'name': '', 'options': {'icon': 'auto'}}, states)['o'], {})
packets(chosen)
def test_discovery_reports_home_assistant_icons_the_screen_can_draw(self):
states = {'light.a': {'state': 'on', 'attributes': {'icon': 'mdi:chandelier'}}, 'light.b': {'state': 'off', 'attributes': {'icon': 'hass:x'}}}
_, entities = discover([{'entity_id': 'light.a'}], states, [], [])
self.assertEqual({e['id']: e['icon'] for e in entities}, {'light.a': 'F1793', 'light.b': None})
async def test_old_editor_preserves_icon_and_explicit_auto_clears(self):
with tempfile.TemporaryDirectory() as tmp:
m = test_portal.ManagerTests().setup_manager(Path(tmp) / 'screens.json')
m.save('text.screen', {'title': 'Thuis', 'tiles': [{'entity': 'light.a', 'options': {'icon': 'spotify'}}]})
m.save('text.screen', {'title': 'Thuis', 'tiles': [{'entity': 'light.a', 'options': {'inline': 'none'}}]})
self.assertEqual(m.layouts['text.screen']['tiles'][0]['options'], {'inline': 'none', 'icon': 'spotify'})
await m.sync_one('text.screen', m.layouts['text.screen'])
self.assertEqual(m.ha.messages[1][1]['o']['icon'], 'F04C7')
m.save('text.screen', {'title': 'Thuis', 'tiles': [{'entity': 'light.a', 'options': {'icon': 'auto'}}]})
self.assertEqual(m.layouts['text.screen']['tiles'][0]['options']['icon'], 'auto')
if __name__ == '__main__':
unittest.main()
+102
View File
@@ -0,0 +1,102 @@
"""Write the tile icon set into both board profiles and build the editor's icon font.
The list lives in screen_manager/app/tile_icons.py. Every Material Design Icons
font of a board profile gets exactly those glyphs (the first font carries the
list as a YAML anchor, the smaller sizes reuse it), and the editor gets a subset
of the same TTF so its mockup shows the glyphs the screen draws. Run
tools/generate_packages.py afterwards; --check changes nothing and fails when
anything is out of date.
"""
import argparse
import io
import re
import sys
from pathlib import Path
from fontTools import subset
from fontTools.ttLib import TTFont
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'screen_manager/app'))
import tile_icons # noqa: E402
TTF = ROOT / 'fonts/materialdesignicons-webfont.ttf'
PROFILES = [ROOT / 'home-like-2432s028.yaml', ROOT / 'guition-4848s040.yaml']
WEB_FONT = ROOT / 'screen_manager/app/static/tile-icons.woff'
FONT_BLOCK = re.compile(r"( - file: 'fonts/materialdesignicons-webfont\.ttf'\n id: \w+\n size: \d+\n) glyphs:.*\n(?: .*\n)*")
def verify():
"""Names and codepoints must match the TTF the firmware is built from."""
codepoints = {}
for code, name in TTFont(TTF).getBestCmap().items():
codepoints.setdefault(name, code)
codepoints[name] = min(codepoints[name], code)
seen = set()
for name, code in tile_icons.GLYPHS.items():
if codepoints.get(name) != int(code, 16):
raise SystemExit(f'{name}: codepoint {code} does not match {TTF.name}')
if code in seen:
raise SystemExit(f'Duplicate glyph {code}')
seen.add(code)
if len(tile_icons.ICONS) != sum(len(icons) for _, icons in tile_icons.GROUPS):
raise SystemExit('An icon appears in more than one group')
def glyph_list():
lines = [' glyphs: &tile_icons # GENERATED by tools/generate_icons.py from screen_manager/app/tile_icons.py']
lines += [f' - "\\U000{code}" # {name}' for name, code in tile_icons.GLYPHS.items()]
return '\n'.join(lines) + '\n'
def profile(text):
blocks = list(FONT_BLOCK.finditer(text))
if len(blocks) != 3:
raise SystemExit(f'Expected three icon fonts, found {len(blocks)}')
out, last = [], 0
for index, block in enumerate(blocks):
out += [text[last:block.start()], block[1], glyph_list() if index == 0 else ' glyphs: *tile_icons\n']
last = block.end()
return ''.join(out) + text[last:]
def web_font():
font = TTFont(TTF)
options = subset.Options()
options.flavor = 'woff'
options.layout_features = []
options.name_IDs = []
options.notdef_outline = False
subsetter = subset.Subsetter(options)
subsetter.populate(unicodes=[int(code, 16) for code in tile_icons.GLYPHS.values()])
subsetter.subset(font)
# The source stores xMin 0 with left bearing 0; saving recalculates xMin, and browsers
# then shift each glyph left by the difference. Keep the bearing equal to the real xMin.
glyf, hmtx = font['glyf'], font['hmtx']
for name in font.getGlyphOrder():
glyph = glyf[name]
glyph.recalcBounds(glyf)
hmtx[name] = (hmtx[name][0], getattr(glyph, 'xMin', 0))
buffer = io.BytesIO()
font.flavor = 'woff'
font.save(buffer)
return buffer.getvalue()
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--check', action='store_true')
args = parser.parse_args()
verify()
for path in PROFILES:
expected = profile(path.read_text())
if args.check:
if path.read_text() != expected: raise SystemExit(f'Outdated icon fonts: {path.name}')
else:
path.write_text(expected)
if args.check:
# Compare glyph coverage, not bytes: font output differs between fontTools versions.
wanted = {int(code, 16) for code in tile_icons.GLYPHS.values()}
if not WEB_FONT.exists() or set(TTFont(WEB_FONT).getBestCmap()) != wanted:
raise SystemExit(f'Outdated: {WEB_FONT.name}')
else:
WEB_FONT.write_bytes(web_font())
print(f'{len(tile_icons.ICONS)} pickable icons, {len(tile_icons.GLYPHS)} glyphs: ' + ('verified' if args.check else 'generated'))
if __name__ == '__main__':
main()