Add the Elecrow CrowPanel 7.0" as a third board, and take the packages and the add-on from Gitea (0.2.93)
A board file for the Elecrow CrowPanel 7.0" (DIS08070H, ESP32-S3-WROOM-1-N4R8): 800x480 through mipi_rgb, GT911 on the ESP32-S3's USB pins (elecrow_usb_phy.h frees them, behind a flag only this board sets), the Guition's vertical sizes across a wider page, no rotation. Its 4 MB of flash take the CYD's budget: a partition table of the component's (smart_display: partitions: 4mb_wide_slots, 1,984 KB slots), the icon fonts at 2 bits a pixel and the TLS client only bring the firmware to 1,865,440 bytes, 91.8 % of a slot, with camera images kept. docs/ELECROW.md has the numbers and the trade. The add-on reads a large board's name from the type sensor's value, the editor offers the board under New screen and draws the wide preview, tools/check.sh builds it with the budget, render_topbar and capture_ui take the panel's size. Firmware 0.2.79; the CYD image is unchanged (1,633,760 bytes). This fork builds from gittea.trippedonmy.name/Xandrossian/homeassistant_espscreen: the packages, the fonts, the component and the add-on repository point there instead of GitHub.
This commit is contained in:
@@ -80,7 +80,7 @@ jobs:
|
||||
~/.cache/esphome
|
||||
~/.platformio
|
||||
.esphome/check
|
||||
key: firmware-${{ runner.os }}-esphome-${{ steps.esphome.outputs.version }}-${{ hashFiles('home-like-2432s028.yaml', 'guition-4848s040.yaml', 'packages/**', 'components/**') }}
|
||||
key: firmware-${{ runner.os }}-esphome-${{ steps.esphome.outputs.version }}-${{ hashFiles('home-like-2432s028.yaml', 'guition-4848s040.yaml', 'elecrow-crowpanel7.yaml', 'packages/**', 'components/**') }}
|
||||
restore-keys: |
|
||||
firmware-${{ runner.os }}-esphome-${{ steps.esphome.outputs.version }}-
|
||||
- name: tools/check.sh --firmware
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
The preferred route for new users is docs/EASY_SETUP.md: ESP Screen Manager
|
||||
plus remote ESPHome packages. No token or blueprint needed. Tiles live in the
|
||||
persistent add-on data; Wi-Fi/API/OTA stay in the device's own ESPHome YAML. Read
|
||||
docs/RELEASING.md before publishing updates. Main distributes both boards.
|
||||
docs/RELEASING.md before publishing updates. Main distributes every board.
|
||||
Every push to GitHub is a release: always also bump the add-on version in
|
||||
screen_manager/config.yaml (with a CHANGELOG line), otherwise HA won't see an update.
|
||||
A screen's YAML is packages/core.yaml (shared by every board) plus one file under packages/boards/;
|
||||
packages/<board>.yaml and the two profiles in the root only include them. Read docs/PROFILES.md before
|
||||
packages/<board>.yaml and the profiles in the root only include them. Read docs/PROFILES.md before
|
||||
touching them: a board-dependent number is a `${NAME}` in the board file, board-only code inside a shared
|
||||
lambda is a hook substitution there, and `tools/check_packages.py` (run by tools/check.sh) keeps the boards
|
||||
complete.
|
||||
@@ -26,10 +26,18 @@ docs/GUITION.md. Don't carry over the CYD layout or the XPT2046 calibration.
|
||||
Verify touch with tools/verify_gt911.py and keep the CYD regressions green.
|
||||
Don't configure wallbox relays as part of display support.
|
||||
|
||||
## Elecrow board
|
||||
|
||||
The Elecrow CrowPanel 7.0" (DIS08070H, ESP32-S3-WROOM-1-N4R8) has `packages/boards/elecrow-crowpanel7.yaml`:
|
||||
800×480 through `mipi_rgb`, GT911 on the ESP32-S3's USB pins (`elecrow_usb_phy.h` frees them; no interrupt pin, no
|
||||
PCA9557 writes, `scan: false`), the Guition's vertical sizes across a wider page, no rotation. It has 4 MB of flash: the
|
||||
CYD's budget applies (`tools/check.sh --firmware`), and the board file's partition table, icon fonts at 2 bits a pixel
|
||||
and the notes in docs/ELECROW.md are what keep it inside. Read docs/ELECROW.md before touching it.
|
||||
|
||||
# Working instructions for LLMs and developers
|
||||
|
||||
This project drives two boards: the CYD ESP32-2432S028 with ILI9341 + XPT2046 (320×240,
|
||||
LVGL 90°) and the Guition above. Read README.md, README_EXTENDED.md and docs/ before installing. The owner can
|
||||
This project drives three boards: the CYD ESP32-2432S028 with ILI9341 + XPT2046 (320×240,
|
||||
LVGL 90°), the Guition above and the Elecrow above. Read README.md, README_EXTENDED.md and docs/ before installing. The owner can
|
||||
physically tap; an agent cannot replace that with software coordinates.
|
||||
|
||||
## Installing a new screen
|
||||
@@ -44,7 +52,7 @@ physically tap; an agent cannot replace that with software coordinates.
|
||||
crosshairs. `tools/calibrate.py` with docs/CALIBRATING.md is the USB route for a
|
||||
panel that needs measuring; ask for physical taps per target, and wait for
|
||||
confirmation that the measurement screen is visible. A successful build is not a flash.
|
||||
Guition: GT911 reports pixels; verify with tools/verify_gt911.py, no ADC calibration.
|
||||
Guition and Elecrow: GT911 reports pixels; verify with tools/verify_gt911.py, no ADC calibration.
|
||||
3. Pair with the owner's own Home Assistant through the ESPHome integration.
|
||||
Read real entity IDs and supported attributes; don't make up entities.
|
||||
4. Choose the tiles in ESP Screens. Don't test real device actions without the
|
||||
@@ -83,15 +91,15 @@ and screens for lookups and tests. If that file is missing you are not on his ma
|
||||
and LVGL 90°. A changed orientation also requires a new projection/tests.
|
||||
- Run `tools/check.sh` on code changes (the Python tests, every C++ test, the package check, the icon
|
||||
generator's check, the editor's tests, types and build); on a firmware change also `tools/check.sh --firmware`, which
|
||||
compiles both boards and applies the CYD's flash budget (docs/RELEASING.md step 2). CI runs the
|
||||
compiles every board and applies the flash budget to the 4 MB boards, the CYD and the Elecrow (docs/RELEASING.md step 2). CI runs the
|
||||
same script. Firmware tests and hardware acceptance are different checks.
|
||||
- `diagnostics/run_ui_test.py` renders without HA actions; don't touch the screen
|
||||
during that test. Use `--name` for the expected device identity.
|
||||
`diagnostics/send_layout.py` pushes a demo layout with every card type to a
|
||||
screen via the API inbox (no HA actions); the manager restores the real
|
||||
layout within ~25 s. Guition: `capture_ui.py` saves the LVGL render as a PNG.
|
||||
Without a screen: `tools/render_topbar.py` renders the real top-bar code for both
|
||||
boards via the ESPHome host + SDL2 to `.esphome/render-topbar/out/sheet.png`.
|
||||
layout within ~25 s. Guition and Elecrow: `capture_ui.py` saves the LVGL render as a PNG.
|
||||
Without a screen: `tools/render_topbar.py` renders the real top-bar code for every
|
||||
board via the ESPHome host + SDL2 to `.esphome/render-topbar/out/sheet.png`.
|
||||
- Share through Git. Don't stage secrets, measurements, binaries, logs, build
|
||||
caches, or local device profiles.
|
||||
- No automatic firmware upload to an arbitrary connected port.
|
||||
|
||||
@@ -15,7 +15,7 @@ Thank you! I work on this project with a lot of love, and every bit of support h
|
||||
panel costs a fraction of that, sits on a table or in a wall box, and is always at hand for the
|
||||
lights, the heating or the vacuum.
|
||||
|
||||
**What.** Firmware for two affordable panels, the 2.8-inch CYD and the 4-inch Guition, with up to
|
||||
**What.** Firmware for three affordable panels, the 2.8-inch CYD, the 4-inch Guition and the 7-inch Elecrow CrowPanel, with up to
|
||||
48 tiles over eight pages: lights, climate, blinds and curtains, the vacuum, media, the weather,
|
||||
history graphs, clocks and timers, and on a Guition your cameras. A tile can take the whole page, one big switch you push without
|
||||
looking, and a tile can go to another page. An automation can put an alert on every screen when someone rings
|
||||
@@ -227,7 +227,7 @@ For Home Assistant OS with Apps/Add-ons on **aarch64 or amd64**:
|
||||
2. Install **ESP Screen Manager**, start the app, and open **ESP Screens**.
|
||||
ESPHome Device Builder is optional: the ESPHome CLI is already in this app.
|
||||
3. Connect the screen with a USB data cable to the **Home Assistant machine**
|
||||
and choose **New screen** in the sidebar: CYD or Guition, a name, the USB port, and
|
||||
and choose **New screen** in the sidebar: CYD, Guition or Elecrow CrowPanel 7.0", a name, the USB port, and
|
||||
**Install**. If Wi-Fi is missing from the ESPHome `secrets.yaml`, the window
|
||||
asks for it once and ESP Screens only adds the missing lines. The profile
|
||||
with unique API and OTA keys goes into the ESPHome folder; the build
|
||||
@@ -237,7 +237,7 @@ For Home Assistant OS with Apps/Add-ons on **aarch64 or amd64**:
|
||||
Choose **Download** under **Install via**: ESP Screens builds the firmware, and you put it
|
||||
on the screen from your own computer with [ESPHome Web](https://web.esphome.io) in Chrome
|
||||
or Edge. After that, updates go over Wi-Fi as usual.
|
||||
4. **CYD:** go through the calibration on the screen. **Guition:** uses GT911
|
||||
4. **CYD:** go through the calibration on the screen. **Guition and Elecrow:** use GT911
|
||||
without resistive calibration. Then pair the discovered ESPHome device in
|
||||
**Settings → Devices & services** using the API key the window
|
||||
shows after installation (copy button). Grant the device permission to
|
||||
|
||||
@@ -550,6 +550,7 @@ take it from, and the same `FIRMWARE_VERSION` in `screen_manager/app/core.py`.
|
||||
- [Complete installation from ESP Screens](docs/EASY_SETUP.md)
|
||||
- [ESP Screens with Home Assistant Container (Docker)](docs/DOCKER.md)
|
||||
- [Guition hardware, mounting, and rotation](docs/GUITION.md)
|
||||
- [Elecrow CrowPanel 7.0" hardware, touch and flash](docs/ELECROW.md)
|
||||
- [CYD calibration and USB diagnostics](docs/CALIBRATING.md)
|
||||
- [Camera images and album covers](docs/CAMERA.md)
|
||||
- [Troubleshooting](docs/TROUBLESHOOTING.md)
|
||||
|
||||
@@ -4,19 +4,26 @@
|
||||
screen_manager/translations/nl.json, with English for any key it lacks, becomes one table in main.cpp
|
||||
(screen_text_gen.py, screen_text.h). The shared core sets it from the LANGUAGE substitution, which ESP Screens writes
|
||||
into a screen's YAML.
|
||||
|
||||
`smart_display: partitions: 4mb_wide_slots` (firmware 0.2.79) builds with one of the partition tables of partitions.py
|
||||
instead of ESPHome's own: two update slots of 1,984 KB on a 4 MB ESP32-S3, which the Elecrow CrowPanel 7.0" needs.
|
||||
"""
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32
|
||||
import esphome.config_validation as cv
|
||||
from esphome.core import CORE
|
||||
from esphome.helpers import write_file_if_changed
|
||||
|
||||
from . import screen_text_gen
|
||||
from . import partitions, screen_text_gen
|
||||
|
||||
CODEOWNERS = []
|
||||
AUTO_LOAD = ["json"]
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_LANGUAGE = "language"
|
||||
CONF_PARTITIONS = "partitions"
|
||||
|
||||
|
||||
def _language(value):
|
||||
@@ -26,7 +33,10 @@ def _language(value):
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({cv.Optional(CONF_LANGUAGE, default="en"): _language})
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.Optional(CONF_LANGUAGE, default="en"): _language,
|
||||
cv.Optional(CONF_PARTITIONS): cv.one_of(*partitions.TABLES, lower=True),
|
||||
})
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
@@ -36,3 +46,8 @@ async def to_code(config):
|
||||
# ESP Screens only writes languages it has; a hand-written one it lacks builds in the nearest it does have.
|
||||
_LOGGER.warning("No translation for %s yet; the screen's texts are in %s", wanted, language)
|
||||
cg.add_global(cg.RawStatement(code))
|
||||
if CONF_PARTITIONS in config:
|
||||
# ESPHome writes no partitions.csv of its own when one is among the extra build files (esp32.copy_files).
|
||||
table = CORE.relative_build_path("smart_display_partitions.csv")
|
||||
write_file_if_changed(table, partitions.TABLES[config[CONF_PARTITIONS]])
|
||||
esp32.add_extra_build_file("partitions.csv", table)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
// The Elecrow CrowPanel 7.0" wires its GT911 touch I2C to GPIO19 and GPIO20, the ESP32-S3's USB D- and D+. The ROM
|
||||
// leaves the USB_SERIAL_JTAG PHY on those pads, and ESP-IDF's I2C driver cannot take them back: the bus reads as
|
||||
// "I2C software timeout" and the touch never answers (esphome/esphome#15356, ESPHome 2026.3+). This runs before any
|
||||
// ESPHome component (a static constructor at priority 101): the PHY lets the pads go, and both become plain GPIOs
|
||||
// with their pull-ups, as the I2C driver expects to find them. The board's USB-C port is a CH340 on UART0, so nothing
|
||||
// is lost.
|
||||
//
|
||||
// ESPHome includes every header of the component in every build, so the code is behind a flag only the Elecrow's board
|
||||
// file sets (`-DESP_SCREENS_FREE_USB_PADS=1`, packages/boards/elecrow-crowpanel7.yaml): the CYD has no such peripheral,
|
||||
// and the Guition, an ESP32-S3 too, keeps its USB.
|
||||
#if defined(ESP_SCREENS_FREE_USB_PADS) && ESP_SCREENS_FREE_USB_PADS
|
||||
#include "hal/usb_serial_jtag_ll.h"
|
||||
#include "soc/io_mux_reg.h"
|
||||
#include "soc/usb_serial_jtag_reg.h"
|
||||
|
||||
namespace elecrow {
|
||||
static __attribute__((constructor(101))) void release_usb_pads() {
|
||||
usb_serial_jtag_ll_phy_enable_pad(false);
|
||||
CLEAR_PERI_REG_MASK(USB_SERIAL_JTAG_CONF0_REG, USB_SERIAL_JTAG_USB_PAD_ENABLE);
|
||||
CLEAR_PERI_REG_MASK(USB_SERIAL_JTAG_CONF0_REG, USB_SERIAL_JTAG_DP_PULLUP);
|
||||
for (uint32_t reg : {IO_MUX_GPIO19_REG, IO_MUX_GPIO20_REG}) {
|
||||
PIN_FUNC_SELECT(reg, PIN_FUNC_GPIO);
|
||||
PIN_INPUT_ENABLE(reg);
|
||||
REG_SET_BIT(reg, FUN_PU);
|
||||
REG_CLR_BIT(reg, FUN_PD);
|
||||
}
|
||||
}
|
||||
} // namespace elecrow
|
||||
#endif
|
||||
@@ -1,18 +1,22 @@
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
|
||||
#include "lvgl.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
// On-demand 240x240 diagnostic image of LVGL's rendered screen. Nothing is
|
||||
// allocated or streamed during normal use. The RGB display signal itself
|
||||
// still needs a physical visual check.
|
||||
// On-demand diagnostic image of LVGL's rendered screen at half its resolution (240x240 on the Guition, 400x240 on
|
||||
// the Elecrow): every second pixel of every second row. Nothing is allocated or streamed during normal use. The RGB
|
||||
// display signal itself still needs a physical visual check. The geometry check takes the panel's own size.
|
||||
namespace guition {
|
||||
inline int screen_w() { return lv_display_get_horizontal_resolution(lv_display_get_default()); }
|
||||
inline int screen_h() { return lv_display_get_vertical_resolution(lv_display_get_default()); }
|
||||
inline bool check_geometry(lv_obj_t *obj) {
|
||||
if (lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN)) return true;
|
||||
bool ok = true;
|
||||
lv_area_t area;
|
||||
lv_obj_get_coords(obj, &area);
|
||||
if (lv_obj_has_flag(obj, LV_OBJ_FLAG_CLICKABLE) &&
|
||||
(area.x1 < 0 || area.y1 < 0 || area.x2 >= 480 || area.y2 >= 480)) {
|
||||
(area.x1 < 0 || area.y1 < 0 || area.x2 >= screen_w() || area.y2 >= screen_h())) {
|
||||
ESP_LOGE("ui_test", "GEOMETRY FAIL clickable=(%d,%d)-(%d,%d)",
|
||||
area.x1, area.y1, area.x2, area.y2);
|
||||
ok = false;
|
||||
@@ -32,7 +36,7 @@ inline void snapshot_begin() {
|
||||
overlay = nullptr;
|
||||
snapshot = lv_snapshot_take(lv_screen_active(), LV_COLOR_FORMAT_RGB565);
|
||||
snapshot_row = 0;
|
||||
if (!snapshot || snapshot->header.w != 480 || snapshot->header.h != 480) {
|
||||
if (!snapshot || (int) snapshot->header.w != screen_w() || (int) snapshot->header.h != screen_h()) {
|
||||
if (snapshot) lv_draw_buf_destroy(snapshot);
|
||||
snapshot = nullptr;
|
||||
ESP_LOGE("ui_image", "IMAGE FAIL allocation/dimensions");
|
||||
@@ -41,10 +45,10 @@ inline void snapshot_begin() {
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_count(lv_layer_top()); ++i)
|
||||
if (!lv_obj_has_flag(lv_obj_get_child(lv_layer_top(), i), LV_OBJ_FLAG_HIDDEN)) {
|
||||
overlay = lv_snapshot_take(lv_layer_top(), LV_COLOR_FORMAT_ARGB8888);
|
||||
if (overlay && (overlay->header.w != 480 || overlay->header.h != 480)) { lv_draw_buf_destroy(overlay); overlay = nullptr; }
|
||||
if (overlay && ((int) overlay->header.w != screen_w() || (int) overlay->header.h != screen_h())) { lv_draw_buf_destroy(overlay); overlay = nullptr; }
|
||||
break;
|
||||
}
|
||||
ESP_LOGI("ui_image", "IMAGE BEGIN 240 240%s", overlay ? " with top layer" : "");
|
||||
ESP_LOGI("ui_image", "IMAGE BEGIN %d %d%s", screen_w() / 2, screen_h() / 2, overlay ? " with top layer" : "");
|
||||
}
|
||||
// The top layer's pixel over the page's, as the panel shows them.
|
||||
inline uint16_t layered(uint16_t pixel, int x, int y) {
|
||||
@@ -62,15 +66,17 @@ inline void snapshot_next_row() {
|
||||
const auto *row = reinterpret_cast<const uint16_t *>(
|
||||
snapshot->data + snapshot_row * 2 * snapshot->header.stride);
|
||||
static constexpr char hex[] = "0123456789abcdef";
|
||||
char line[961];
|
||||
for (int x = 0; x < 240; ++x) {
|
||||
// Four hex digits a pixel; the widest panel is 800 px, 400 in the image.
|
||||
static char line[1601];
|
||||
const int columns = std::min(screen_w() / 2, 400);
|
||||
for (int x = 0; x < columns; ++x) {
|
||||
uint16_t pixel = layered(row[x * 2], x * 2, snapshot_row * 2);
|
||||
for (int digit = 0; digit < 4; ++digit)
|
||||
line[x * 4 + digit] = hex[(pixel >> (12 - digit * 4)) & 15];
|
||||
}
|
||||
line[960] = 0;
|
||||
line[columns * 4] = 0;
|
||||
ESP_LOGI("ui_image", "ROW %03d %s", snapshot_row, line);
|
||||
if (++snapshot_row == 240) {
|
||||
if (++snapshot_row == screen_h() / 2) {
|
||||
lv_draw_buf_destroy(snapshot);
|
||||
snapshot = nullptr;
|
||||
if (overlay) lv_draw_buf_destroy(overlay);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Partition tables a board file can ask for with `smart_display: partitions:` (firmware 0.2.79).
|
||||
|
||||
ESPHome's own table for 4 MB of flash keeps 448 KB for NVS and gives the two update slots 1,792 KB each. The screen's
|
||||
preferences take a few KB of NVS (ESP-IDF's own two-slot example keeps 16 KB), and the Elecrow CrowPanel 7.0" needs
|
||||
the room: its firmware is the Guition's, which does not fit 1,792 KB. The tables live here, as text, because a board
|
||||
file over GitHub cannot carry a file of its own; __init__.py hands the chosen one to ESPHome as the build's
|
||||
partitions.csv. A table never changes once a screen is flashed with it: an update over Wi-Fi writes into the other slot
|
||||
of the table the screen has, and only a USB install lays the flash out anew.
|
||||
"""
|
||||
|
||||
# 4 MB as two update slots of 0x1F0000 bytes, the largest 64 KB-aligned pair that fits, and 64 KB of NVS at the end;
|
||||
# otadata and phy_init where ESPHome puts them. Offsets are explicit so the table is the same on every build.
|
||||
TABLES = {
|
||||
'4mb_wide_slots': """\
|
||||
# ESP Screens: 4 MB ESP32-S3 with two update slots of 1,984 KB (components/smart_display/partitions.py).
|
||||
# Name, Type, SubType, Offset, Size
|
||||
otadata, data, ota, 0x9000, 0x2000
|
||||
phy_init, data, phy, 0xB000, 0x1000
|
||||
app0, app, ota_0, 0x10000, 0x1F0000
|
||||
app1, app, ota_1, 0x200000, 0x1F0000
|
||||
nvs, data, nvs, 0x3F0000, 0x10000
|
||||
""",
|
||||
}
|
||||
+13
-10
@@ -1,4 +1,4 @@
|
||||
"""Save a Guition LVGL diagnostic snapshot as PNG through its encrypted API."""
|
||||
"""Save a Guition or Elecrow LVGL diagnostic snapshot as PNG through its encrypted API (half the panel's size)."""
|
||||
import argparse
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
@@ -9,16 +9,16 @@ import yaml
|
||||
from aioesphomeapi import APIClient, LogLevel
|
||||
|
||||
|
||||
def png(rows):
|
||||
def png(rows,width,height):
|
||||
pixels=bytearray()
|
||||
for y in range(240):
|
||||
for y in range(height):
|
||||
pixels.append(0)
|
||||
for x in range(240):
|
||||
for x in range(width):
|
||||
p=int(rows[y][x*4:x*4+4],16)
|
||||
pixels.extend((((p>>11)&31)*255//31,((p>>5)&63)*255//63,(p&31)*255//31))
|
||||
def chunk(name,data):
|
||||
return struct.pack('>I',len(data))+name+data+struct.pack('>I',zlib.crc32(name+data)&0xffffffff)
|
||||
return b'\x89PNG\r\n\x1a\n'+chunk(b'IHDR',struct.pack('>2I5B',240,240,8,2,0,0,0))+chunk(b'IDAT',zlib.compress(pixels))+chunk(b'IEND',b'')
|
||||
return b'\x89PNG\r\n\x1a\n'+chunk(b'IHDR',struct.pack('>2I5B',width,height,8,2,0,0,0))+chunk(b'IDAT',zlib.compress(pixels))+chunk(b'IEND',b'')
|
||||
|
||||
|
||||
async def main():
|
||||
@@ -33,18 +33,21 @@ async def main():
|
||||
await client.connect(login=True)
|
||||
try:
|
||||
_,services=await client.list_entities_services()
|
||||
done=asyncio.Event();rows={};errors=[]
|
||||
done=asyncio.Event();rows={};errors=[];size=[240,240]
|
||||
def log(msg):
|
||||
text=msg.message.decode(errors='replace') if isinstance(msg.message,bytes) else msg.message
|
||||
m=re.search(r'ROW (\d{3}) ([0-9a-f]{960})',text)
|
||||
if m:rows[int(m[1])]=m[2]
|
||||
# The firmware says the image's size first: 240x240 on a Guition, 400x240 on an Elecrow.
|
||||
b=re.search(r'IMAGE BEGIN (\d+) (\d+)',text)
|
||||
if b:size[:]=[int(b[1]),int(b[2])]
|
||||
m=re.search(r'ROW (\d{3}) ([0-9a-f]{960,1600})',text)
|
||||
if m and len(m[2])==size[0]*4:rows[int(m[1])]=m[2]
|
||||
if 'IMAGE FAIL' in text:errors.append(text);done.set()
|
||||
if 'IMAGE COMPLETE' in text:done.set()
|
||||
client.subscribe_logs(log,log_level=LogLevel.LOG_LEVEL_INFO,dump_config=False)
|
||||
await client.execute_service(next(s for s in services if s.name=='ui_snapshot'),{})
|
||||
await asyncio.wait_for(done.wait(),timeout=60)
|
||||
if errors or set(rows)!=set(range(240)):raise ValueError(f'Incomplete image: {len(rows)}/240 rows; {errors}')
|
||||
with args.output.open('xb') as f:f.write(png(rows))
|
||||
if errors or set(rows)!=set(range(size[1])):raise ValueError(f'Incomplete image: {len(rows)}/{size[1]} rows; {errors}')
|
||||
with args.output.open('xb') as f:f.write(png(rows,*size))
|
||||
print(f'PASS: LVGL render saved to {args.output}')
|
||||
finally:await client.disconnect()
|
||||
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
# Camera images on a Guition
|
||||
|
||||
App 0.2.66 with firmware 0.2.57 shows camera images on a Guition 4848S040:
|
||||
App 0.2.66 with firmware 0.2.57 shows camera images on a Guition 4848S040 (and, from app 0.2.93 with firmware
|
||||
0.2.79, on an Elecrow CrowPanel 7.0" at its own 800×480 size, docs/ELECROW.md; "Guition" below reads for both):
|
||||
|
||||
- **A camera tile.** Add a `camera.*` or `image.*` entity as a tile. A tap opens the image full
|
||||
screen, with the round back key at the top left like every card, and a spinner until the first
|
||||
|
||||
+4
-3
@@ -15,6 +15,7 @@ ESPHome Device Builder is optional:
|
||||
| --- | --- |
|
||||
| CYD | ESP32-2432S028, 320×240, ILI9341 and XPT2046 |
|
||||
| Guition | ESP32-S3-4848S040, 480×480, ST7701S and GT911 |
|
||||
| Elecrow CrowPanel 7.0" | DIS08070H, ESP32-S3-WROOM-1-N4R8, 800×480 RGB and GT911 ([ELECROW.md](ELECROW.md)) |
|
||||
|
||||
Other screens with roughly the same name can have different pins. Use
|
||||
the board profile that matches the hardware. Use a USB cable that supports data.
|
||||
@@ -38,8 +39,8 @@ Use the GitHub version for updates; a local test add-on is a separate app.
|
||||
1. Connect the screen with a **USB data cable** to the machine running Home
|
||||
Assistant. With multiple boards: connect them one at a time for the first
|
||||
installation, or check which port belongs to this screen.
|
||||
2. Click **New screen** in the sidebar of ESP Screens, under your screens. Choose CYD
|
||||
or Guition and give the screen a name, for example `Kitchen`. The device name
|
||||
2. Click **New screen** in the sidebar of ESP Screens, under your screens. Choose CYD,
|
||||
Guition or Elecrow CrowPanel and give the screen a name, for example `Kitchen`. The device name
|
||||
(`kitchen`) follows from that; use **customize** to choose a different one.
|
||||
3. Wi-Fi: if `wifi_ssid` and `wifi_password` are already in the ESPHome `secrets.yaml`,
|
||||
the screen uses them automatically. If they're missing, or the file doesn't
|
||||
@@ -88,7 +89,7 @@ and survives OTA updates. Use the HA device button **Calibrate touch** to
|
||||
measure again later. For a panel that stays off, or for a measurement report over
|
||||
USB, see [CALIBRATING.md](CALIBRATING.md).
|
||||
|
||||
**Guition:** the GT911 touch mapping is baked into the board profile; there's no ADC calibration.
|
||||
**Guition and Elecrow:** the GT911 touch mapping is baked into the board profile; there's no ADC calibration.
|
||||
|
||||
## 3. Pair the screen with Home Assistant
|
||||
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
# Elecrow CrowPanel 7.0" — 800 × 480
|
||||
|
||||
The **Elecrow CrowPanel ESP32 HMI 7.0-inch** (product code **DIS08070H**, hardware V2.0 and V3.0) has a board file of
|
||||
its own, next to the CYD's and the Guition's: an 800 × 480 IPS panel on the ESP32-S3's own RGB bus, **GT911
|
||||
capacitive touch**, an **ESP32-S3-WROOM-1-N4R8** with 4 MB of flash and 8 MB of octal PSRAM, and a backlight on
|
||||
GPIO 2. It is the Guition's kind of screen, a big RGB panel with capacitive touch, in a wider, landscape form: every
|
||||
card, the camera images and album covers, the page swipe from the glass edge, the settings on the screen. Two things
|
||||
differ in kind, and both are below: the **4 MB of flash** (the CYD's budget, not the Guition's 16 MB) and a panel
|
||||
that is not square, so it **does not turn** (no Rotation entity).
|
||||
|
||||
Hardware source: [Elecrow's wiki](https://www.elecrow.com/wiki/esp32-display-702727-intelligent-touch-screen-wi-fi26ble-800480-hmi-display.html)
|
||||
and the [ESPHome devices page for the CrowPanel 7.0"](https://github.com/esphome/esphome-devices/pull/1494), whose
|
||||
panel timings, pins and pitfalls this board file follows. The **CrowPanel *Advance* 7.0"** is a different board with
|
||||
other pins (ESPHome's `CROWPANEL-ADVANCE-7` model); this file is for the plain CrowPanel 7.0".
|
||||
|
||||
## Files
|
||||
|
||||
- `packages/boards/elecrow-crowpanel7.yaml`: the board file (hardware, the 800 × 480 sizes, the flash measures); the
|
||||
interface itself is `packages/core.yaml`, shared with both other boards (docs/PROFILES.md).
|
||||
- `packages/elecrow.yaml`: what a screen builds from over GitHub; `elecrow-crowpanel7.yaml` in the root is the same for
|
||||
a build from a checkout; `installers/elecrow.yaml` the block for a hand-written ESPHome configuration.
|
||||
- `components/smart_display/elecrow_usb_phy.h`: frees the USB pins for the touch I2C (below).
|
||||
- `components/smart_display/partitions.py`: the partition table that makes the firmware fit (below).
|
||||
- `tools/verify_gt911.py`: physical pixel check, as on the Guition; no resistive calibration.
|
||||
|
||||
Choose **Elecrow CrowPanel · 7 inch** under **New screen** in ESP Screens (app 0.2.93+). The Guition's own parts (the
|
||||
Rotation choice) stay off for it; everything the Guition can show, it shows.
|
||||
|
||||
## Interface
|
||||
|
||||
Six tiles of **378 × 108 pixels** per page in two columns, the Guition's rows and fonts across a wider page: the
|
||||
vertical sizes are the Guition's to the pixel, the tiles and the cards take the extra width. The top bar, the tile
|
||||
labels, the alert card (560 wide, its camera picture 532 × 220), the colour and climate titles and the page keys are
|
||||
sized for 800 px; the cards that are a share of the page (the setpoint card, the mode keys) simply widen. A camera
|
||||
opens full screen at 800 × 480; ESP Screens serves the picture in that box (`camera_feed.BOXES['elecrow']`).
|
||||
|
||||
At 133 pixels an inch the Guition's 42 px icons are a shade larger on this panel than on the 4-inch (170 ppi), which
|
||||
suits a screen that stands further away.
|
||||
|
||||
### Touch
|
||||
|
||||
The GT911 is polled every 20 ms over I2C at 400 kHz, with no interrupt pin and no reset: the **V3.0** has a PCA9557
|
||||
I/O expander (I2C 0x18) on the GT911's reset and interrupt lines, and writing it during boot is what breaks the touch
|
||||
on that revision. It powers up with every pin as an input, a pull-up brings the GT911 out of reset before the firmware
|
||||
starts, and the driver finds it ready. A bus scan at boot upsets the GT911 too, so `scan: false`.
|
||||
|
||||
The GT911 takes I2C address **0x5D** when GPIO 38 reads low at power-on and **0x14** when it reads high; on this
|
||||
board it is 0x5D on most units, and a unit that never reports a touch may be a 0x14. Give that screen an Override YAML
|
||||
in ESP Screens (Screen settings → Override YAML) with:
|
||||
|
||||
```yaml
|
||||
touchscreen:
|
||||
address: 0x14
|
||||
```
|
||||
|
||||
**GPIO 19 and 20, the touch I2C, are the ESP32-S3's USB D- and D+.** The ROM leaves the USB PHY on those pads and
|
||||
ESP-IDF's I2C driver cannot take them back: the bus reads as `I2C software timeout` and the touch never answers
|
||||
(esphome/esphome#15356, ESPHome 2026.3+). `elecrow_usb_phy.h` runs before any component, as a static constructor,
|
||||
and lets the pads go; it is behind the build flag `ESP_SCREENS_FREE_USB_PADS`, which only this board file sets, because
|
||||
ESPHome includes every header of the component in every build and the Guition, an ESP32-S3 too, keeps its USB. The board's USB-C port is a CH340 on UART0, so flashing and the log are unaffected; the
|
||||
`GPIO19 is used by the USB-Serial-JTAG interface` warnings at build time are expected.
|
||||
|
||||
### Rotation
|
||||
|
||||
A landscape panel with a layout 800 px wide: it does not turn. `ROTATION_SUPPORTED` is `false`, the settings page
|
||||
shows no rotation row, and the app offers none for this board.
|
||||
|
||||
## Flash
|
||||
|
||||
The ESP32-S3-WROOM-1-**N4**R8 has **4 MB** of flash (the N4; 16 MB in the YAML boot-loops with no log). The firmware
|
||||
is the Guition's, with camera images; with the Guition's fonts it is **2,028 KB**, and ESPHome's own partition table for
|
||||
4 MB, two update slots of 1,792 KB with 448 KB kept for NVS, does not hold it. Three measures make it fit with room for
|
||||
updates, all in the board file, measured with ESPHome 2026.9.0 (the add-on's):
|
||||
|
||||
| Measure | Saves | Where |
|
||||
|---|---|---|
|
||||
| Two update slots of **1,984 KB** and 64 KB of NVS instead of 448 KB (the preferences take a few KB; ESP-IDF's own two-slot example keeps 16 KB) | 192 KB more room a slot | `smart_display: partitions: 4mb_wide_slots`, `components/smart_display/partitions.py` |
|
||||
| The four icon fonts (the 314 tile icons at 42, 26 and 18 px, the 31 big icons at 64 px) at **2 bits a pixel** instead of 4; the text fonts keep their 4 | 156 KB | `font:` in the board file (`!extend` on the core's fonts) |
|
||||
| The TLS client only; nothing serves TLS | 7 KB | `sdkconfig_options` in the board file |
|
||||
|
||||
That is **1,865,456 bytes, 91.8 %** of a slot: within the CYD's rules (docs/RELEASING.md step 2), in the *tight* band,
|
||||
and `tools/check.sh --firmware` applies the same budget to this board. Without camera images it is about 1,754 KB,
|
||||
86 %: drop the `online_image`, `http_request` and `preview_camera` sections, the alert's picture frame and the
|
||||
`camera_image_seed` image, and empty the camera hooks (`BOOT_CAMERA_HOOKS`, `BOARD_PAINT_FILL`, `CLOSE_CARDS_HOOK`,
|
||||
`MAY_OPEN_EXTRA`, `TICK_HOOK`, `AWAY_EXTRA`, `ALERT_SHOW_HOOK`) as the CYD's board file has them. The icons at 4 bits
|
||||
a pixel are the Guition's 156 KB back; they fit only without the camera images, and then at 94 %.
|
||||
|
||||
The partition table is part of the firmware and **never changes once a screen is flashed**: an update over Wi-Fi
|
||||
writes into the other slot of the table the screen has, and only a USB install (ESP Screens' New screen, or ESPHome
|
||||
Web with the factory image) lays the flash out anew. A screen flashed with this table keeps it through every update.
|
||||
|
||||
## Panel
|
||||
|
||||
ESPHome's `mipi_rgb` driver with the `RPI` model: the panel takes the pixels straight from the ESP32-S3's LCD
|
||||
peripheral and needs no initialisation, so the timings are the whole configuration: 15 MHz pixel clock, inverted
|
||||
(`pclk_inverted: true`, without it the picture tears), horizontal porches 40/48/13 and vertical 1/31/13, the 16 data
|
||||
pins of Elecrow's schematic. The frame buffer is in PSRAM; LVGL draws in a buffer of 15 % of the screen, the same
|
||||
number of pixels as the Guition's 25 % of 480 × 480.
|
||||
|
||||
## Checks
|
||||
|
||||
- `python3 tools/check_packages.py`, `esphome config elecrow-crowpanel7.yaml`, `tools/check.sh --firmware`.
|
||||
- The touch test page and `tools/verify_gt911.py` for the corners; the ghost-touch filter at (0, 0) of the Guition's
|
||||
GT911 applies here too.
|
||||
- `diagnostics/capture_ui.py` saves the LVGL render at 400 × 240 (half the panel) as a PNG; `ui_self_test` renders
|
||||
every card without touching a device.
|
||||
- A build is not a flash, and a render is not a touch: the owner checks the picture and taps the corners.
|
||||
+10
-6
@@ -10,8 +10,9 @@ already drifted apart in small ways.
|
||||
| `packages/core.yaml` | Everything every board shares: the LVGL tree (tiles, cards, overlays, the touch test page), the scripts, the API actions, the entities, the fonts, the globals, and Wi-Fi that never dozes (`power_save_mode: none`, firmware 0.2.74+; the network itself stays in the screen's own YAML). It contains no number that depends on the board: every such value is a `${NAME}` the board file defines. |
|
||||
| `packages/boards/cyd-2432s028.yaml` | The CYD: its hardware (ILI9341 over SPI, XPT2046 resistive touch, backlight, LED), its sizes and font sizes, the code the shared lambdas take from it (the *hooks*, below), and what only a CYD has: the calibration wizard, a page swipe by LVGL's gesture, `ui_self_test`, the two scripts that drive the backlight as a light action, its climate card layout. |
|
||||
| `packages/boards/guition-4848s040.yaml` | The Guition: its hardware (ST7701S RGB, GT911, PSRAM), its sizes, its hooks, and what only a Guition has: camera images (`online_image`, the alert's picture frame), the Rotation entity, the page swipe from the glass edge, the snapshot diagnostics, the climate card with its fan and swing card, the wider tile labels (`!extend` on each tile). |
|
||||
| `packages/cyd.yaml`, `packages/guition.yaml` | What a screen installed from ESP Screens builds from over GitHub, unchanged in name and place: ESP Screen Manager writes every screen's YAML with `files: [packages/<board>.yaml]`. Each is a few lines: where the fonts are, the components from GitHub, and `packages: {core: !include core.yaml, board: !include boards/<board>.yaml}`. |
|
||||
| `home-like-2432s028.yaml`, `guition-4848s040.yaml` | The same two files for a build from a checkout (the bench, the manual route of README_EXTENDED.md): the components of the checkout, the fonts from `fonts/`, and the secrets from `secrets.yaml`. |
|
||||
| `packages/boards/elecrow-crowpanel7.yaml` | The Elecrow CrowPanel 7.0" (app 0.2.93): the Guition's kind at 800×480 on 4 MB of flash. Its hardware (the RGB panel through `mipi_rgb`, GT911, the USB pins freed for the touch I2C), the Guition's vertical sizes across a wider page, the same hooks and sections as the Guition's file except the Rotation (a landscape panel does not turn), and what the flash asks for: a partition table of the component's (`smart_display: partitions`), the icon fonts at 2 bits a pixel (`font:` with `!extend`). docs/ELECROW.md has the numbers. |
|
||||
| `packages/cyd.yaml`, `packages/guition.yaml`, `packages/elecrow.yaml` | What a screen installed from ESP Screens builds from over GitHub, unchanged in name and place: ESP Screen Manager writes every screen's YAML with `files: [packages/<board>.yaml]`. Each is a few lines: where the fonts are, the components from GitHub, and `packages: {core: !include core.yaml, board: !include boards/<board>.yaml}`. |
|
||||
| `home-like-2432s028.yaml`, `guition-4848s040.yaml`, `elecrow-crowpanel7.yaml` | The same files for a build from a checkout (the bench, the manual route of README_EXTENDED.md): the components of the checkout, the fonts from `fonts/`, and the secrets from `secrets.yaml`. |
|
||||
|
||||
ESPHome merges them in this order: the core, then the board file on top of it, then the entry file on top of both.
|
||||
A mapping merges key by key, a list of components with ids merges by id (`!extend` adds to an existing widget or
|
||||
@@ -41,11 +42,14 @@ the board file replaces the core's. `esphome config home-like-2432s028.yaml` sho
|
||||
`assertion_level: SILENT` in its `esp32:` block (firmware 0.2.75+): every board builds its firmware the same way,
|
||||
and `tests/test_easy_package.py` checks it.
|
||||
2. Fill the sizes table for the new resolution, and go through the hooks: keep, change or empty each one.
|
||||
3. Add `packages/<board>.yaml` and `<board>.yaml` after the existing entries, `<board>` to `ENTRIES` in
|
||||
3. Add `packages/<board>.yaml` and `<board>.yaml` after the existing entries, `<board>` to `BOARDS` and `ENTRIES` in
|
||||
`tools/profiles.py` and to `REFS` in `screen_manager/app/core.py` (the boards `installation_yaml()` writes a
|
||||
profile for), and a `BANDS` entry to `tools/render_topbar.py`. The add-on and the editor still know two boards,
|
||||
a Guition or not: the board a screen is (`core.py`), `ALERT_LIMITS`, the camera sizes in `camera_feed.py`, and
|
||||
the editor's New screen choices and `BAR_METRICS` (`web/src`) need the new board too.
|
||||
profile for), and a `BANDS` entry to `tools/render_topbar.py`. The add-on and the editor know a board as small (the
|
||||
CYD) or large (`LARGE_BOARDS` in `core.py` and `web/src/store.ts`: a Guition or an Elecrow, the ones that draw
|
||||
images and take the large cards); a large board reports its name in the value of the "Guition screen type" sensor
|
||||
(`discover_screens`). `ALERT_LIMITS`, the camera sizes in `camera_feed.py`, the editor's New screen choices
|
||||
(`InstallerView.vue` and `board_<name>` in every translation) and `BAR_METRICS` need the new board too, and
|
||||
`tools/check.sh` builds it (with the flash budget when it has 4 MB). The Elecrow (app 0.2.93) is the worked example.
|
||||
4. `python3 tools/check_packages.py`, `esphome config <board>.yaml`, `tools/check.sh --firmware`.
|
||||
|
||||
The tiles per page (two columns of three) are still fixed in the firmware, the add-on and the editor
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# ESP Screens - an Elecrow CrowPanel 7.0" (DIS08070H, 7-inch 800x480) built from this checkout: the bench and the
|
||||
# manual route (README_EXTENDED.md, docs/ELECROW.md).
|
||||
#
|
||||
# The screen is two packages: packages/core.yaml, which every board shares (the LVGL tree, the cards, the
|
||||
# scripts, the entities), and packages/boards/elecrow-crowpanel7.yaml with this board's hardware and sizes.
|
||||
# docs/PROFILES.md says how the files fit together. A screen installed from ESP Screens builds the same two
|
||||
# files over GitHub through packages/elecrow.yaml; this file only adds what a build from a checkout needs:
|
||||
# the secrets (api_encryption_key, ota_password, Wi-Fi) from secrets.yaml and the components of this checkout.
|
||||
substitutions:
|
||||
FONT_DIR: "fonts" # the fonts next to this file
|
||||
|
||||
packages:
|
||||
core: !include packages/core.yaml
|
||||
board: !include packages/boards/elecrow-crowpanel7.yaml
|
||||
|
||||
# The components of this checkout; packages/elecrow.yaml takes them from GitHub.
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: components
|
||||
components: [smart_display]
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: !secret api_encryption_key
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ota_password
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
# Mains-powered panel: no modem sleep, so HA updates and OTA arrive without beacon latency.
|
||||
power_save_mode: none
|
||||
ap:
|
||||
ssid: "Smartdisplay Fallback Hotspot"
|
||||
password: !secret ap_password
|
||||
|
||||
captive_portal:
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
# The easy route generates a complete file of its own: ESP Screen Manager.
|
||||
packages:
|
||||
screen:
|
||||
url: https://github.com/MaxGramser/homeassistant_espscreen
|
||||
url: https://gittea.trippedonmy.name/Xandrossian/homeassistant_espscreen
|
||||
ref: main
|
||||
files: [packages/cyd.yaml]
|
||||
refresh: 0s
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Manual alternative: add this block to the ESPHome wizard configuration.
|
||||
# Remove the old esp32: block; keep your own esphome:, api:, ota: and wifi:.
|
||||
# The easy route generates a complete file of its own: ESP Screen Manager.
|
||||
packages:
|
||||
screen:
|
||||
url: https://gittea.trippedonmy.name/Xandrossian/homeassistant_espscreen
|
||||
ref: main
|
||||
files: [packages/elecrow.yaml]
|
||||
refresh: 0s
|
||||
@@ -3,7 +3,7 @@
|
||||
# The easy route generates a complete file of its own: ESP Screen Manager.
|
||||
packages:
|
||||
screen:
|
||||
url: https://github.com/MaxGramser/homeassistant_espscreen
|
||||
url: https://gittea.trippedonmy.name/Xandrossian/homeassistant_espscreen
|
||||
ref: main
|
||||
files: [packages/guition.yaml]
|
||||
refresh: 0s
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -14,7 +14,7 @@
|
||||
# fonts/materialdesignicons-webfont.ttf (Apache 2.0, github.com/Templarian/MaterialDesign-Webfont).
|
||||
############################################################
|
||||
substitutions:
|
||||
SCREEN_FIRMWARE_VERSION: "0.2.78"
|
||||
SCREEN_FIRMWARE_VERSION: "0.2.79"
|
||||
DASHBOARD_INBOX_INTERNAL: "false"
|
||||
CALIBRATION_ON_BOOT: "false" # a bench build: start on the touch test page
|
||||
ROOM_NAME: "" # top left until ESP Screens sends the screen name
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
# entities), and boards/cyd-2432s028.yaml with this board's hardware and sizes. docs/PROFILES.md says how the
|
||||
# files fit together. home-like-2432s028.yaml builds the same two files from a checkout.
|
||||
substitutions:
|
||||
FONT_DIR: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts"
|
||||
FONT_DIR: "https://gittea.trippedonmy.name/Xandrossian/homeassistant_espscreen/raw/branch/main/fonts"
|
||||
|
||||
packages:
|
||||
core: !include core.yaml
|
||||
@@ -15,7 +15,7 @@ packages:
|
||||
external_components:
|
||||
- source:
|
||||
type: git
|
||||
url: https://github.com/MaxGramser/homeassistant_espscreen.git
|
||||
url: https://gittea.trippedonmy.name/Xandrossian/homeassistant_espscreen.git
|
||||
ref: main
|
||||
path: components
|
||||
refresh: 0s
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# ESP Screens - the package an Elecrow CrowPanel 7.0" builds from over GitHub. ESP Screen Manager writes every
|
||||
# screen's own YAML with `packages: display: {url: <this repository>, files: [packages/elecrow.yaml]}` and adds
|
||||
# the name, the Wi-Fi reference and the unique keys (docs/EASY_SETUP.md); nothing of that lives here.
|
||||
#
|
||||
# The screen is two packages: core.yaml, which every board shares (the LVGL tree, the cards, the scripts, the
|
||||
# entities), and boards/elecrow-crowpanel7.yaml with this board's hardware and sizes. docs/PROFILES.md says how
|
||||
# the files fit together. elecrow-crowpanel7.yaml builds the same two files from a checkout.
|
||||
substitutions:
|
||||
FONT_DIR: "https://gittea.trippedonmy.name/Xandrossian/homeassistant_espscreen/raw/branch/main/fonts"
|
||||
|
||||
packages:
|
||||
core: !include core.yaml
|
||||
board: !include boards/elecrow-crowpanel7.yaml
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: git
|
||||
url: https://gittea.trippedonmy.name/Xandrossian/homeassistant_espscreen.git
|
||||
ref: main
|
||||
path: components
|
||||
refresh: 0s
|
||||
components: [smart_display]
|
||||
@@ -6,7 +6,7 @@
|
||||
# entities), and boards/guition-4848s040.yaml with this board's hardware and sizes. docs/PROFILES.md says how
|
||||
# the files fit together. guition-4848s040.yaml builds the same two files from a checkout.
|
||||
substitutions:
|
||||
FONT_DIR: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts"
|
||||
FONT_DIR: "https://gittea.trippedonmy.name/Xandrossian/homeassistant_espscreen/raw/branch/main/fonts"
|
||||
|
||||
packages:
|
||||
core: !include core.yaml
|
||||
@@ -15,7 +15,7 @@ packages:
|
||||
external_components:
|
||||
- source:
|
||||
type: git
|
||||
url: https://github.com/MaxGramser/homeassistant_espscreen.git
|
||||
url: https://gittea.trippedonmy.name/Xandrossian/homeassistant_espscreen.git
|
||||
ref: main
|
||||
path: components
|
||||
refresh: 0s
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
name: Home Assistant ESP Screens
|
||||
url: https://github.com/MaxGramser/homeassistant_espscreen
|
||||
maintainer: MaxGramser
|
||||
url: https://gittea.trippedonmy.name/Xandrossian/homeassistant_espscreen
|
||||
maintainer: Xandrossian (fork of MaxGramser/homeassistant_espscreen with the Elecrow CrowPanel 7.0")
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
## 0.2.93 (firmware 0.2.79)
|
||||
|
||||
A third screen: the 7-inch Elecrow CrowPanel, with everything the Guition shows across a wider page.
|
||||
|
||||
- **Elecrow CrowPanel 7.0" (DIS08070H).** Choose **Elecrow CrowPanel · 7 inch** under **New screen**. An 800 × 480 panel with GT911 touch on an ESP32-S3-WROOM-1-N4R8: every card, the camera images and album covers, the live pictures on camera tiles, the page swipe from the glass edge and the settings on the screen, the Guition's rows and fonts across six tiles of 378 × 108. It does not turn (a landscape panel), so it has no Rotation. docs/ELECROW.md has the hardware, the touch notes (the GT911 sits on the ESP32-S3's USB pins, which the firmware frees; a unit at I2C 0x14 gets one line of Override YAML) and the flash.
|
||||
- **4 MB of flash, like the CYD.** The firmware is the Guition's, which does not fit ESPHome's own partition table for 4 MB. Three measures in the board file make it fit with room for updates: two update slots of 1,984 KB instead of 1,792 (a partition table of the component's, `smart_display: partitions`), the icon fonts at 2 bits a pixel where the text keeps its 4, and the TLS client only. Elecrow firmware: 1,865,456 bytes, 91.8 % of its update slot (without camera images it would be 86 %; the trade is in docs/ELECROW.md). `tools/check.sh --firmware` applies the CYD's budget to this board too.
|
||||
- The app reads a large board's name from its "Guition screen type" sensor (an older Guition, which never publishes a value, stays a Guition); the editor's preview takes the panel's shape. The snapshot diagnostics (`ui_snapshot`, `capture_ui.py`) take the panel's own size, half of it, 400 × 240 on the Elecrow.
|
||||
- Needs firmware 0.2.79 for the Elecrow; a CYD or Guition on 0.2.78 loses nothing by waiting. Includes everything from 0.2.92.
|
||||
|
||||
## 0.2.92 (firmware 0.2.78)
|
||||
|
||||
The album cover on a media tile, the way Home Assistant's own tile shows it.
|
||||
|
||||
@@ -5,7 +5,7 @@ Thank you! I work on this project with a lot of love, and every bit of support h
|
||||
<a href="https://buymeacoffee.com/f5j9jnkmhpv"><img src="https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20coffee&emoji=&slug=f5j9jnkmhpv&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff" alt="Buy me a coffee" height="42"></a>
|
||||
|
||||
Find your Home Assistant entities, arrange up to 48 tiles, and push changes
|
||||
to a CYD or Guition screen without reflashing. Includes installation help,
|
||||
to a CYD, Guition or Elecrow CrowPanel screen without reflashing. Includes installation help,
|
||||
a unique per-device YAML, and support for multiple screens.
|
||||
|
||||
<p align="center">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Camera images on a Guition screen (app 0.2.66, firmware 0.2.57).
|
||||
"""Camera images on a Guition screen (app 0.2.66, firmware 0.2.57), and on an Elecrow CrowPanel 7.0" (app 0.2.93).
|
||||
|
||||
A screen never asks Home Assistant for a camera itself. This app fetches the snapshot with its own token (a
|
||||
camera's access token changes every five minutes), makes it exactly as large as the screen shows it and serves
|
||||
@@ -62,8 +62,8 @@ MAX_LINKS = 64
|
||||
# What the screens load (online_image `format: BMP`).
|
||||
CONTENT_TYPE = 'image/bmp'
|
||||
# The pixel box per board and view; the image keeps its proportions inside it. Equal to the CAMERA_*
|
||||
# substitutions of packages/guition.yaml (tests/test_camera.py).
|
||||
BOXES = {'guition': {'full': (480, 480), 'thumb': (392, 220)}}
|
||||
# substitutions of the board's file under packages/boards/ (tests/test_camera.py).
|
||||
BOXES = {'guition': {'full': (480, 480), 'thumb': (392, 220)}, 'elecrow': {'full': (800, 480), 'thumb': (532, 220)}}
|
||||
|
||||
|
||||
def supported(entity):
|
||||
|
||||
@@ -27,7 +27,7 @@ PAGE_TILE_REPEAT_VERSION = '.'.join(str(part) for part in PAGE_TILE_REPEAT_MIN_F
|
||||
|
||||
NAME = 'esp-screens'
|
||||
# claude.ai accepts at most 200 characters; Claude Code picks the skill by this sentence.
|
||||
DESCRIPTION = ('ESP Screens (CYD and Guition touchscreens run from Home Assistant): put a tile on a screen, move or '
|
||||
DESCRIPTION = ('ESP Screens (CYD, Guition and Elecrow screens run from Home Assistant): put a tile on a screen, move or '
|
||||
'order tiles, show an alert on one screen or all of them, and wake, sleep or keep a screen awake.')
|
||||
TYPES = {'string': 'text', 'int': 'number', 'bool': 'on/off'}
|
||||
|
||||
|
||||
@@ -48,10 +48,13 @@ FULL_PAGE_MIN_FIRMWARE = (0, 2, 62)
|
||||
# Twenty tiles from firmware 0.2.7, ten before.
|
||||
TWENTY_TILES_MIN_FIRMWARE = (0, 2, 7)
|
||||
FIRST_MAX_TILES = 10
|
||||
REPO = 'https://github.com/MaxGramser/homeassistant_espscreen'
|
||||
REFS = {'cyd': 'main', 'guition': 'main'}
|
||||
REPO = 'https://gittea.trippedonmy.name/Xandrossian/homeassistant_espscreen'
|
||||
REFS = {'cyd': 'main', 'guition': 'main', 'elecrow': 'main'}
|
||||
# The boards that draw camera images and album covers and take the large layouts (a big RGB panel with PSRAM); the CYD
|
||||
# is the small one. The Elecrow CrowPanel 7.0" (app 0.2.93) is the Guition's kind at 800x480, without the Rotation.
|
||||
LARGE_BOARDS = ('guition', 'elecrow')
|
||||
# Firmware shipped with this app release; screens below it get an update offer.
|
||||
FIRMWARE_VERSION = '0.2.78'
|
||||
FIRMWARE_VERSION = '0.2.79'
|
||||
# The Auto standby switch a screen offers Home Assistant automations.
|
||||
AUTO_STANDBY_MIN_FIRMWARE = '0.2.41'
|
||||
# The settings page the screen opens itself, and the screen.settings tile that opens it.
|
||||
@@ -202,6 +205,8 @@ def resolve_controls(tile):
|
||||
# forms are recognised until every board has been reflashed with an English `name:`.
|
||||
NAME_TILE_SETTINGS = ('Tile settings', 'Tegelinstellingen')
|
||||
NAME_SCREEN_FIRMWARE = ('Screen firmware', 'Schermfirmware')
|
||||
# Only the large boards have this sensor; its value names the board ("guition", or "elecrow" from firmware 0.2.79),
|
||||
# an older Guition never publishes one.
|
||||
NAME_GUITION_TYPE = ('Guition screen type', 'Guition schermtype')
|
||||
NAME_DEVICE_NAME = ('Device name', 'Apparaatnaam')
|
||||
NAME_IP_ADDRESS = ('IP address', 'IP-adres')
|
||||
@@ -1408,7 +1413,8 @@ ALERT_FIELDS = (
|
||||
# is a value to type as it is.
|
||||
ALERT_TEXT_EXAMPLES = frozenset(('title', 'subtitle', 'button_text'))
|
||||
# Bytes per field the firmware keeps (the profiles' ALERT_*_MAX); an accented letter takes two.
|
||||
ALERT_LIMITS = {'cyd': {'title': 48, 'subtitle': 160, 'button_text': 12}, 'guition': {'title': 64, 'subtitle': 240, 'button_text': 16}}
|
||||
ALERT_LIMITS = {'cyd': {'title': 48, 'subtitle': 160, 'button_text': 12}, 'guition': {'title': 64, 'subtitle': 240, 'button_text': 16},
|
||||
'elecrow': {'title': 64, 'subtitle': 240, 'button_text': 16}}
|
||||
ALERT_SUGGESTED_ICONS = ('doorbell', 'bell', 'bell-ring', 'alert-outline', 'alarm-light', 'lock', 'lock-open-variant', 'door-open',
|
||||
'window-closed-variant', 'motion-sensor', 'cctv', 'smoke-detector', 'water-alert', 'fire', 'mailbox', 'car',
|
||||
'account', 'account-group', 'washing-machine', 'robot-vacuum', 'timer-outline', 'check')
|
||||
@@ -1559,8 +1565,13 @@ def discover_screens(registry, states, devices, areas):
|
||||
area_map = {a['area_id']: a['name'] for a in areas}
|
||||
versions = {item.get('device_id'): states.get(item['entity_id'], {}).get('state', 'unknown') for item in registry
|
||||
if item.get('platform') == 'esphome' and item.get('original_name') in NAME_SCREEN_FIRMWARE}
|
||||
boards = {item.get("device_id"): "guition" for item in registry
|
||||
if item.get("platform") == "esphome" and item.get("original_name") in NAME_GUITION_TYPE}
|
||||
# A screen with the type sensor is a large board: the one its value names, a Guition when the value is not a board
|
||||
# (older firmware never publishes one, and a screen that is restarting reports "unavailable").
|
||||
boards = {}
|
||||
for item in registry:
|
||||
if item.get("platform") == "esphome" and item.get("original_name") in NAME_GUITION_TYPE:
|
||||
value = states.get(item['entity_id'], {}).get('state')
|
||||
boards[item.get("device_id")] = value if value in LARGE_BOARDS else "guition"
|
||||
def diagnostic(names, pattern):
|
||||
found = {}
|
||||
for item in registry:
|
||||
|
||||
@@ -171,10 +171,10 @@ def capabilities(entity_id, actions, state, services):
|
||||
elif domain == 'sun':
|
||||
displays.append('sunpath')
|
||||
elif domain in ('camera', 'image'):
|
||||
# A live picture on the tile (app 0.2.91): the editor offers it on a Guition, the only board that draws images.
|
||||
# A live picture on the tile (app 0.2.91): the editor offers it on a large board (a Guition, an Elecrow), the ones that draw images.
|
||||
displays.append('live')
|
||||
elif domain == 'media_player':
|
||||
# The album cover in the icon's place (app 0.2.92), a Guition again.
|
||||
# The album cover in the icon's place (app 0.2.92), a large board again.
|
||||
displays.append('cover')
|
||||
return {
|
||||
'toggle': TOGGLE.format(domain=domain) in actions,
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -5,8 +5,8 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<title>ESP Screens</title>
|
||||
<script type="module" crossorigin src="./assets/index-plSjhIie.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-DQxVdm_5.css">
|
||||
<script type="module" crossorigin src="./assets/index-C4Uqwn3z.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-DGMtqWTE.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
name: ESP Screen Manager
|
||||
version: "0.2.92"
|
||||
version: "0.2.93"
|
||||
slug: esp_screen_manager
|
||||
description: Choose tiles for your CYD and Guition touchscreen, without reflashing.
|
||||
url: https://github.com/MaxGramser/homeassistant_espscreen
|
||||
url: https://gittea.trippedonmy.name/Xandrossian/homeassistant_espscreen
|
||||
arch: [aarch64, amd64]
|
||||
startup: application
|
||||
boot: auto
|
||||
|
||||
@@ -1593,6 +1593,7 @@
|
||||
"board": "Welchen Bildschirm hast du?",
|
||||
"board_cyd": "CYD · 2,8 Zoll",
|
||||
"board_guition": "Guition · 4 Zoll",
|
||||
"board_elecrow": "Elecrow CrowPanel · 7 Zoll",
|
||||
"name": "Name",
|
||||
"name_placeholder": "Bildschirm Wohnzimmer",
|
||||
"device_name": "Gerätename",
|
||||
|
||||
@@ -1593,6 +1593,7 @@
|
||||
"board": "Which screen do you have?",
|
||||
"board_cyd": "CYD · 2.8 inch",
|
||||
"board_guition": "Guition · 4 inch",
|
||||
"board_elecrow": "Elecrow CrowPanel · 7 inch",
|
||||
"name": "Name",
|
||||
"name_placeholder": "Living room screen",
|
||||
"device_name": "Device name",
|
||||
|
||||
@@ -1593,6 +1593,7 @@
|
||||
"board": "¿Qué pantalla tienes?",
|
||||
"board_cyd": "CYD · 2,8 pulgadas",
|
||||
"board_guition": "Guition · 4 pulgadas",
|
||||
"board_elecrow": "Elecrow CrowPanel · 7 pulgadas",
|
||||
"name": "Nombre",
|
||||
"name_placeholder": "Pantalla del salón",
|
||||
"device_name": "Nombre del dispositivo",
|
||||
|
||||
@@ -1593,6 +1593,7 @@
|
||||
"board": "Quel écran avez-vous ?",
|
||||
"board_cyd": "CYD · 2,8 pouces",
|
||||
"board_guition": "Guition · 4 pouces",
|
||||
"board_elecrow": "Elecrow CrowPanel · 7 pouces",
|
||||
"name": "Nom",
|
||||
"name_placeholder": "Écran du salon",
|
||||
"device_name": "Nom de l'appareil",
|
||||
|
||||
@@ -1593,6 +1593,7 @@
|
||||
"board": "Quale schermo hai?",
|
||||
"board_cyd": "CYD · 2,8 pollici",
|
||||
"board_guition": "Guition · 4 pollici",
|
||||
"board_elecrow": "Elecrow CrowPanel · 7 pollici",
|
||||
"name": "Nome",
|
||||
"name_placeholder": "Schermo del soggiorno",
|
||||
"device_name": "Nome dispositivo",
|
||||
|
||||
@@ -1593,6 +1593,7 @@
|
||||
"board": "Welk scherm heb je?",
|
||||
"board_cyd": "CYD · 2,8 inch",
|
||||
"board_guition": "Guition · 4 inch",
|
||||
"board_elecrow": "Elecrow CrowPanel · 7 inch",
|
||||
"name": "Naam",
|
||||
"name_placeholder": "Scherm woonkamer",
|
||||
"device_name": "Apparaatnaam",
|
||||
|
||||
@@ -1593,6 +1593,7 @@
|
||||
"board": "Jaki masz ekran?",
|
||||
"board_cyd": "CYD · 2,8 cala",
|
||||
"board_guition": "Guition · 4 cale",
|
||||
"board_elecrow": "Elecrow CrowPanel · 7 cali",
|
||||
"name": "Nazwa",
|
||||
"name_placeholder": "Ekran w salonie",
|
||||
"device_name": "Nazwa urządzenia",
|
||||
|
||||
@@ -1593,6 +1593,7 @@
|
||||
"board": "Que ecrã tem?",
|
||||
"board_cyd": "CYD · 2,8 polegadas",
|
||||
"board_guition": "Guition · 4 polegadas",
|
||||
"board_elecrow": "Elecrow CrowPanel · 7 polegadas",
|
||||
"name": "Nome",
|
||||
"name_placeholder": "Ecrã da sala",
|
||||
"device_name": "Nome do dispositivo",
|
||||
|
||||
@@ -10,7 +10,7 @@ ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / 'tools'))
|
||||
import profiles # noqa: E402
|
||||
|
||||
BOARDS = {'cyd': 'home-like-2432s028.yaml', 'guition': 'guition-4848s040.yaml'}
|
||||
BOARDS = {'cyd': 'home-like-2432s028.yaml', 'guition': 'guition-4848s040.yaml', 'elecrow': 'elecrow-crowpanel7.yaml'}
|
||||
# The old manual profile bound tiles to fixed entities in the YAML. ESP Screens sends the tiles now, so
|
||||
# none of this may come back: the fixed subscriptions, its tap handlers and scripts, its own vacuum card.
|
||||
GONE = ['ha_state_tile1', 'tile1_brightness', 'tile6_climate_humidity', 'tile6_cover_state', 'smartdisplay_action',
|
||||
@@ -42,8 +42,8 @@ class PackageTests(unittest.TestCase):
|
||||
package = (ROOT / 'packages' / f'{board}.yaml').read_text()
|
||||
self.assertNotIn('!secret', package)
|
||||
self.assertNotIn('type: local', package)
|
||||
self.assertIn('url: https://github.com/MaxGramser/homeassistant_espscreen.git', package)
|
||||
self.assertIn('FONT_DIR: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts"', package)
|
||||
self.assertIn('url: https://gittea.trippedonmy.name/Xandrossian/homeassistant_espscreen.git', package)
|
||||
self.assertIn('FONT_DIR: "https://gittea.trippedonmy.name/Xandrossian/homeassistant_espscreen/raw/branch/main/fonts"', package)
|
||||
# The fonts of the shared core come from that place; a checkout takes them from fonts/.
|
||||
self.assertIn('file: "${FONT_DIR}/Roboto-500.ttf"', profiles.CORE.read_text())
|
||||
self.assertIn('FONT_DIR: "fonts"', (ROOT / BOARDS[board]).read_text())
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
"""The Elecrow CrowPanel 7.0" (app 0.2.93, firmware 0.2.79): a third board, the Guition's kind at 800x480 on 4 MB of flash."""
|
||||
import re
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / 'tools'))
|
||||
import profiles # noqa: E402
|
||||
sys.path.insert(0, str(ROOT / 'screen_manager/app'))
|
||||
import camera_feed # noqa: E402
|
||||
import core # noqa: E402
|
||||
sys.path.insert(0, str(ROOT / 'components/smart_display'))
|
||||
import partitions # noqa: E402
|
||||
|
||||
BOARD = profiles.BOARDS['elecrow']
|
||||
TEXT = BOARD.read_text()
|
||||
SUBS = profiles.substitutions_of(BOARD)
|
||||
|
||||
|
||||
class BoardFile(unittest.TestCase):
|
||||
def test_it_is_a_landscape_guition(self):
|
||||
self.assertEqual((SUBS['DISPLAY_W'], SUBS['DISPLAY_H']), ('800', '480'))
|
||||
guition = profiles.substitutions_of(profiles.BOARDS['guition'])
|
||||
# The vertical sizes are the Guition's: the rows, the tile height, the fonts.
|
||||
for name in ('GRID_ROW1_Y', 'GRID_ROW2_Y', 'GRID_ROW3_Y', 'TILE_H', 'SCROLL_Y', 'SCROLL_H', 'HEADER_Y',
|
||||
'FONT_LABEL_SIZE', 'FONT_ICON_SIZE', 'FONT_HEADLINE_SIZE', 'TILE_ICON_SIZE', 'PAGE_BAR_H'):
|
||||
self.assertEqual(SUBS[name], guition[name], name)
|
||||
# Two columns of tiles fill the width with the Guition's margins and gap.
|
||||
col1, col2, tile = int(SUBS['GRID_COL1_X']), int(SUBS['GRID_COL2_X']), int(SUBS['TILE_W'])
|
||||
self.assertEqual(col1, 16)
|
||||
self.assertEqual(col2, col1 + tile + 12)
|
||||
self.assertEqual(col2 + tile + 16, 800)
|
||||
self.assertEqual(int(SUBS['PAGE_KEY_W']), 400)
|
||||
self.assertEqual(int(SUBS['TOUCH_TEST_FAR_X']), 800 - 12 - 24 - 1)
|
||||
self.assertEqual(int(SUBS['TOUCH_TEST_MID_X']), 400 - 12)
|
||||
self.assertEqual(int(SUBS['SETTINGS_HOLD_W']), 800 - 2 * int(SUBS['EDGE_SWIPE_BAND_PX']))
|
||||
|
||||
def test_it_does_not_turn(self):
|
||||
self.assertEqual(SUBS['ROTATION_SUPPORTED'], 'false')
|
||||
self.assertEqual(SUBS['APPLY_ROTATION'], '')
|
||||
self.assertEqual(SUBS['APPLY_ROTATION_ENTITY'], '')
|
||||
self.assertNotIn('id: setting_rotation', TEXT)
|
||||
|
||||
def test_the_hardware_of_the_crowpanel(self):
|
||||
self.assertIn('flash_size: 4MB', TEXT)
|
||||
self.assertIn('platform: mipi_rgb', TEXT)
|
||||
self.assertIn('model: RPI', TEXT)
|
||||
self.assertIn('pclk_inverted: true', TEXT)
|
||||
self.assertIn('red: [14, 21, 47, 48, 45]', TEXT)
|
||||
self.assertIn('green: [9, 46, 3, 8, 16, 1]', TEXT)
|
||||
self.assertIn('blue: [15, 7, 6, 5, 4]', TEXT)
|
||||
self.assertIn('sda: GPIO19', TEXT)
|
||||
self.assertIn('scl: GPIO20', TEXT)
|
||||
self.assertIn('scan: false', TEXT)
|
||||
self.assertIn('address: 0x5D', TEXT)
|
||||
self.assertIn('pin: GPIO2', TEXT)
|
||||
# The USB pins are the touch I2C: the header that frees them, and no interrupt pin or I/O expander.
|
||||
self.assertIn('<esphome/components/smart_display/elecrow_usb_phy.h>', TEXT)
|
||||
code = '\n'.join(line for line in TEXT.split('\n') if not line.lstrip().startswith('#'))
|
||||
self.assertNotIn('interrupt_pin', code)
|
||||
self.assertNotIn('pca9557', code.lower())
|
||||
self.assertIn('assertion_level: SILENT', TEXT)
|
||||
|
||||
def test_the_flash_fits_by_three_measures(self):
|
||||
self.assertIn('partitions: 4mb_wide_slots', TEXT)
|
||||
self.assertIn('CONFIG_MBEDTLS_TLS_CLIENT_ONLY: y', TEXT)
|
||||
fonts = re.search(r'^font:\n(.*?)(?=^[a-z_]+:)', TEXT, re.M | re.S)[1]
|
||||
for font in ('materialdesign_icons', 'materialdesign_icons_mini', 'materialdesign_icons_big', 'watch_icon'):
|
||||
self.assertIn(f'- id: !extend {font}\n bpp: 2', fonts)
|
||||
self.assertNotIn('!extend headline', fonts)
|
||||
|
||||
def test_the_usb_phy_header_runs_before_every_component(self):
|
||||
header = (ROOT / 'components/smart_display/elecrow_usb_phy.h').read_text()
|
||||
self.assertIn('__attribute__((constructor(101)))', header)
|
||||
self.assertIn('usb_serial_jtag_ll_phy_enable_pad(false)', header)
|
||||
for reg in ('IO_MUX_GPIO19_REG', 'IO_MUX_GPIO20_REG'):
|
||||
self.assertIn(reg, header)
|
||||
# ESPHome includes every header of the component in every build: the code is behind a flag only this board sets.
|
||||
self.assertIn('#if defined(ESP_SCREENS_FREE_USB_PADS) && ESP_SCREENS_FREE_USB_PADS', header)
|
||||
for board, path in profiles.BOARDS.items():
|
||||
self.assertEqual('-DESP_SCREENS_FREE_USB_PADS=1' in path.read_text(), board == 'elecrow', board)
|
||||
|
||||
def test_the_partition_table(self):
|
||||
table = partitions.TABLES['4mb_wide_slots']
|
||||
rows = [[cell.strip() for cell in line.split(',')] for line in table.splitlines() if line.strip() and not line.startswith('#')]
|
||||
by_name = {row[0]: row for row in rows}
|
||||
self.assertEqual(by_name['app0'][1:], ['app', 'ota_0', '0x10000', '0x1F0000'])
|
||||
self.assertEqual(by_name['app1'][1:], ['app', 'ota_1', '0x200000', '0x1F0000'])
|
||||
self.assertEqual(int(by_name['nvs'][3], 16) + int(by_name['nvs'][4], 16), 4 * 1024 * 1024)
|
||||
# The slots touch neither each other nor the table before them.
|
||||
self.assertEqual(int(by_name['app0'][3], 16) + int(by_name['app0'][4], 16), int(by_name['app1'][3], 16))
|
||||
self.assertEqual(int(by_name['app1'][3], 16) + int(by_name['app1'][4], 16), int(by_name['nvs'][3], 16))
|
||||
self.assertGreaterEqual(int(by_name['app0'][3], 16), int(by_name['phy_init'][3], 16) + int(by_name['phy_init'][4], 16))
|
||||
for name in ('app0', 'app1'):
|
||||
self.assertEqual(int(by_name[name][3], 16) % 0x10000, 0, f'{name} is 64 KB aligned')
|
||||
|
||||
def test_the_entry_files(self):
|
||||
for name in ('elecrow-crowpanel7.yaml', 'packages/elecrow.yaml'):
|
||||
self.assertIn('boards/elecrow-crowpanel7.yaml', (ROOT / name).read_text(), name)
|
||||
self.assertIn('files: [packages/elecrow.yaml]', (ROOT / 'installers/elecrow.yaml').read_text())
|
||||
self.assertEqual(profiles.board_of('packages/elecrow.yaml'), 'elecrow')
|
||||
|
||||
|
||||
class TheApp(unittest.TestCase):
|
||||
def test_the_board_is_read_from_the_type_sensor(self):
|
||||
registry = []
|
||||
states = {}
|
||||
for device, value in (('d1', 'elecrow'), ('d2', 'guition'), ('d3', 'unavailable'), ('d4', None), ('d5', 'cyd')):
|
||||
registry += [{'entity_id': f'text.{device}_tiles', 'platform': 'esphome', 'original_name': 'Tile settings', 'device_id': device},
|
||||
{'entity_id': f'sensor.{device}_type', 'platform': 'esphome', 'original_name': 'Guition screen type', 'device_id': device}]
|
||||
states[f'text.{device}_tiles'] = {'state': 'Synced'}
|
||||
if value is not None:
|
||||
states[f'sensor.{device}_type'] = {'state': value}
|
||||
registry.append({'entity_id': 'text.d6_tiles', 'platform': 'esphome', 'original_name': 'Tile settings', 'device_id': 'd6'})
|
||||
states['text.d6_tiles'] = {'state': 'Synced'}
|
||||
screens = {s['device_id']: s['board'] for s in core.discover_screens(registry, states, [{'id': f'd{n}', 'name': f'D{n}'} for n in range(1, 7)], [])}
|
||||
# The value names the board; a sensor without a board's name (older firmware, a restart) is a Guition; no sensor is no large board.
|
||||
self.assertEqual(screens, {'d1': 'elecrow', 'd2': 'guition', 'd3': 'guition', 'd4': 'guition', 'd5': 'guition', 'd6': 'unknown'})
|
||||
|
||||
def test_the_app_knows_the_board(self):
|
||||
self.assertIn('elecrow', core.REFS)
|
||||
self.assertEqual(core.ALERT_LIMITS['elecrow'], {'title': int(SUBS['ALERT_TITLE_MAX']), 'subtitle': int(SUBS['ALERT_SUBTITLE_MAX']),
|
||||
'button_text': int(SUBS['ALERT_BUTTON_MAX'])})
|
||||
self.assertIn('files: [packages/elecrow.yaml]', core.installation_yaml({'board': 'elecrow', 'name': 'hall', 'friendly_name': 'Hall'}))
|
||||
|
||||
def test_the_camera_boxes_are_the_profile_sizes(self):
|
||||
self.assertEqual(camera_feed.BOXES['elecrow'], {'full': (int(SUBS['CAMERA_FULL_W']), int(SUBS['CAMERA_FULL_H'])),
|
||||
'thumb': (int(SUBS['CAMERA_THUMB_W']), int(SUBS['CAMERA_THUMB_H']))})
|
||||
self.assertTrue(camera_feed.can_show({'board': 'elecrow', 'firmware': '0.2.79'}))
|
||||
self.assertTrue(camera_feed.can_show_live({'board': 'elecrow', 'firmware': '0.2.79'}))
|
||||
self.assertTrue(camera_feed.can_show_cover({'board': 'elecrow', 'firmware': '0.2.79'}))
|
||||
# The alert's picture fits the wider card.
|
||||
self.assertEqual(int(SUBS['CAMERA_THUMB_W']), int(SUBS['ALERT_CARD_W']) - 2 * int(SUBS['ALERT_IMAGE_INSET']))
|
||||
|
||||
def test_the_editor_offers_the_board(self):
|
||||
installer = (ROOT / 'web/src/components/InstallerView.vue').read_text()
|
||||
self.assertIn('value="elecrow"', installer)
|
||||
for path in sorted((ROOT / 'screen_manager/translations').glob('*.json')):
|
||||
if path.stem != 'en-GB':
|
||||
self.assertIn('"board_elecrow"', path.read_text(), path.name)
|
||||
store = (ROOT / 'web/src/store.ts').read_text()
|
||||
self.assertIn('LARGE_BOARDS = ["guition", "elecrow"]', store)
|
||||
self.assertIn('elecrow: { width: 768', (ROOT / 'web/src/model/topbar.ts').read_text())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -20,7 +20,7 @@ CHANGELOG = ROOT / 'screen_manager/CHANGELOG.md'
|
||||
APP_HEADING = re.compile(r'^## (\d+)\.(\d+)\.(\d+)\b(.*)$', re.M)
|
||||
FIRMWARE_IN_HEADING = re.compile(r'\(firmware (\d+\.\d+\.\d+)\)')
|
||||
# The published entries (packages/<board>.yaml) point ${FONT_DIR} at the raw GitHub URL of fonts/ on main.
|
||||
FONT_URL = re.compile(r'https://raw\.githubusercontent\.com/MaxGramser/homeassistant_espscreen/[^/\s"\']+/(fonts/[^"\'\s]+)')
|
||||
FONT_URL = re.compile(r'https://gittea\.trippedonmy\.name/Xandrossian/homeassistant_espscreen/raw/branch/[^/\s"\']+/(fonts/[^"\'\s]+)')
|
||||
|
||||
|
||||
def app_headings():
|
||||
|
||||
+10
-7
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# tools/check.sh the Python tests, every tests/*.cpp, the package check, the icon generator's --check, the editor's
|
||||
# tests, types and build, and whether the editor bundle in Git equals that build
|
||||
# tools/check.sh --firmware compiles both board profiles and applies the CYD's flash budget
|
||||
# tools/check.sh --firmware compiles every board profile and applies the flash budget to the 4 MB boards (CYD, Elecrow)
|
||||
# tools/check.sh --all both
|
||||
# --baseline BYTES with --firmware: the CYD image of the last release, to print the growth
|
||||
#
|
||||
@@ -175,8 +175,8 @@ api_encryption_key: "Y2hlY2stYnVpbGQtcGxhY2Vob2xkZXIta2V5LTMyYnk="
|
||||
ota_password: "check-build-ota-password-0000000"
|
||||
ap_password: "check-ap-passwd0"
|
||||
EOF
|
||||
for board in cyd guition; do
|
||||
file=$([[ $board == cyd ]] && echo home-like-2432s028.yaml || echo guition-4848s040.yaml)
|
||||
for board in cyd guition elecrow; do
|
||||
case $board in cyd) file=home-like-2432s028.yaml ;; guition) file=guition-4848s040.yaml ;; elecrow) file=elecrow-crowpanel7.yaml ;; esac
|
||||
cp "$ROOT/$file" "$config/check-$board.yaml" || return 1
|
||||
if ! grep -q '^captive_portal:' "$config/check-$board.yaml" || ! grep -q '^ ap:' "$config/check-$board.yaml"; then
|
||||
echo "$file has no captive_portal: or wifi ap: any more, unlike the YAML users get; the flash figures would read low."
|
||||
@@ -186,16 +186,16 @@ EOF
|
||||
echo "Check profiles in $config"
|
||||
}
|
||||
|
||||
compile_board() { # compile_board cyd|guition
|
||||
compile_board() { # compile_board cyd|guition|elecrow
|
||||
local board=$1
|
||||
cd "$WORK/config" || return 1
|
||||
"${ESPHOME_CMD[@]}" -s DEVICE_NAME "check-$board" -s DEVICE_FRIENDLY_NAME "Check $board" compile "check-$board.yaml" || return 1
|
||||
flash_report "$board"
|
||||
}
|
||||
|
||||
# flash_report BOARD [budget]: the image against its update slot, both read from the build, and on the CYD the growth
|
||||
# against --baseline and the budget of docs/RELEASING.md step 2. Exit 3 means over 90 %: it passes, but the release has
|
||||
# to say why.
|
||||
# flash_report BOARD [budget]: the image against its update slot, both read from the build, and on the 4 MB boards (the
|
||||
# CYD, the Elecrow) the growth against --baseline (the CYD only) and the budget of docs/RELEASING.md step 2. Exit 3 means
|
||||
# over 90 %: it passes, but the release has to say why.
|
||||
flash_report() {
|
||||
local board=$1 mode=${2:-} status=0 out="$WORK/flash-$1.txt" against=""
|
||||
[[ $board == cyd ]] && against=$baseline
|
||||
@@ -251,6 +251,7 @@ EOF
|
||||
}
|
||||
|
||||
cyd_budget() { flash_report cyd budget; }
|
||||
elecrow_budget() { flash_report elecrow budget; }
|
||||
|
||||
# ---- Run ----
|
||||
|
||||
@@ -282,6 +283,8 @@ if ((want_firmware)); then
|
||||
run "Firmware: CYD" compile_board cyd
|
||||
if ((last_ok)); then run "CYD flash budget" cyd_budget; else skip "CYD flash budget" "no CYD build"; fi
|
||||
run "Firmware: Guition" compile_board guition
|
||||
run "Firmware: Elecrow" compile_board elecrow
|
||||
if ((last_ok)); then run "Elecrow flash budget" elecrow_budget; else skip "Elecrow flash budget" "no Elecrow build"; fi
|
||||
else
|
||||
skip "Firmware builds" "ESPHome or the check profiles are missing"
|
||||
fi
|
||||
|
||||
+1
-1
@@ -200,7 +200,7 @@ LINT_KEEP = {
|
||||
'None', 'Auto', 'Wh',
|
||||
# Placeholders of the YAML tree that the runtime tiles replace before a screen shows them, and profile defaults.
|
||||
'Lamp', 'Plug', 'Evening', 'All off', 'AC', 'Vacuum', 'Tile 7', 'Tile 8', 'Tile 9', 'Tile 10', 'Light', 'Light Color',
|
||||
'Climate', 'Example lamp', 'My CYD', 'My Guition',
|
||||
'Climate', 'Example lamp', 'My CYD', 'My Guition', 'My CrowPanel',
|
||||
}
|
||||
LINT_FILES = ('components/smart_display/*.h', 'packages/core.yaml', 'packages/boards/*.yaml')
|
||||
|
||||
|
||||
+9
-7
@@ -1,7 +1,8 @@
|
||||
"""The board profiles as the tests and tools read them (app 0.2.84+).
|
||||
|
||||
A screen is three files: an entry (`home-like-2432s028.yaml` or `packages/cyd.yaml` for a CYD, `guition-4848s040.yaml`
|
||||
or `packages/guition.yaml` for a Guition), `packages/core.yaml` that every board shares, and the board's own file under
|
||||
or `packages/guition.yaml` for a Guition, `elecrow-crowpanel7.yaml` or `packages/elecrow.yaml` for an Elecrow CrowPanel 7.0"),
|
||||
`packages/core.yaml` that every board shares, and the board's own file under
|
||||
`packages/boards/`. `text(name)` is the three of them one after the other, so a check that looks for a line finds it
|
||||
wherever it lives; `resolved(name)` also fills in the `${NAME}` substitutions from the three files (the sizes, the fonts
|
||||
and the hooks with the board's code), for a check that needs the numbers or the whole lambda. Neither is what ESPHome
|
||||
@@ -15,12 +16,13 @@ import yaml
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
CORE = ROOT / 'packages/core.yaml'
|
||||
BOARDS = {'cyd': ROOT / 'packages/boards/cyd-2432s028.yaml', 'guition': ROOT / 'packages/boards/guition-4848s040.yaml'}
|
||||
ENTRIES = {'home-like-2432s028.yaml': 'cyd', 'guition-4848s040.yaml': 'guition',
|
||||
'packages/cyd.yaml': 'cyd', 'packages/guition.yaml': 'guition'}
|
||||
# The names the four entry files are known by, in the order the older tests listed them.
|
||||
PROFILES = ('home-like-2432s028.yaml', 'guition-4848s040.yaml')
|
||||
PACKAGES = ('packages/cyd.yaml', 'packages/guition.yaml')
|
||||
BOARDS = {'cyd': ROOT / 'packages/boards/cyd-2432s028.yaml', 'guition': ROOT / 'packages/boards/guition-4848s040.yaml',
|
||||
'elecrow': ROOT / 'packages/boards/elecrow-crowpanel7.yaml'}
|
||||
ENTRIES = {'home-like-2432s028.yaml': 'cyd', 'guition-4848s040.yaml': 'guition', 'elecrow-crowpanel7.yaml': 'elecrow',
|
||||
'packages/cyd.yaml': 'cyd', 'packages/guition.yaml': 'guition', 'packages/elecrow.yaml': 'elecrow'}
|
||||
# The names the six entry files are known by, in the order the older tests listed them.
|
||||
PROFILES = ('home-like-2432s028.yaml', 'guition-4848s040.yaml', 'elecrow-crowpanel7.yaml')
|
||||
PACKAGES = ('packages/cyd.yaml', 'packages/guition.yaml', 'packages/elecrow.yaml')
|
||||
NAMES = PROFILES + PACKAGES
|
||||
|
||||
|
||||
|
||||
+18
-12
@@ -20,14 +20,16 @@ from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
WORK = ROOT / '.esphome' / 'render-topbar'
|
||||
PROFILES = {'guition': 'guition-4848s040.yaml', 'cyd': 'home-like-2432s028.yaml'}
|
||||
PROFILES = {'guition': 'guition-4848s040.yaml', 'cyd': 'home-like-2432s028.yaml', 'elecrow': 'elecrow-crowpanel7.yaml'}
|
||||
sys.path.insert(0, str(ROOT / 'tools'))
|
||||
import profiles # noqa: E402
|
||||
FONTS = ('headline', 'time_label', 'sublabel_big', 'label', 'materialdesign_icons', 'materialdesign_icons_mini')
|
||||
# Hardware headers the host cannot compile; the top bar needs none of them.
|
||||
SKIP = {'guition_diagnostics.h', '__pycache__'}
|
||||
# Header band per board: page width and height above the tiles.
|
||||
BANDS = {'guition': (480, 72), 'cyd': (320, 40)}
|
||||
# Header band per board: page width and height above the tiles. The Elecrow's bar is the Guition's across 800 px
|
||||
# (the same fonts and insets), so it renders with the Guition's fonts.
|
||||
BANDS = {'guition': (480, 72), 'cyd': (320, 40), 'elecrow': (800, 72)}
|
||||
LARGE = ('guition', 'elecrow')
|
||||
|
||||
# (slug, title, items) with items as C++ expressions of the render lambda's helpers.
|
||||
SCENARIOS = (
|
||||
@@ -56,8 +58,8 @@ def font_blocks(board):
|
||||
|
||||
def band(board, y):
|
||||
width, height = BANDS[board]
|
||||
margin, top = (16, 22) if board == 'guition' else (11, 14)
|
||||
tile_y, tile_w, tile_h, col2, radius = (72, 218, 108, 246, 22) if board == 'guition' else (40, 147, 52, 164, 18)
|
||||
margin, top = (16, 22) if board in LARGE else (11, 14)
|
||||
tile_y, tile_w, tile_h, col2, radius = {'guition': (72, 218, 108, 246, 22), 'elecrow': (72, 378, 108, 406, 22), 'cyd': (40, 147, 52, 164, 18)}[board]
|
||||
return f''' - obj:
|
||||
id: {board}_page
|
||||
x: 0
|
||||
@@ -85,7 +87,7 @@ def band(board, y):
|
||||
text: ""
|
||||
text_font: time_label_{board}
|
||||
- obj:
|
||||
x: {margin if board == 'guition' else 9}
|
||||
x: {margin if board in LARGE else 9}
|
||||
y: {tile_y}
|
||||
width: {tile_w}
|
||||
height: {tile_h}
|
||||
@@ -128,6 +130,7 @@ struct Board {{ const char *name; lv_obj_t *page, *room, *time; const lv_font_t
|
||||
std::vector<Board> boards = {{
|
||||
{{"guition", id(guition_page), id(guition_room), id(guition_time), id(sublabel_big_guition)->get_lv_font(), id(materialdesign_icons_mini_guition)->get_lv_font()}},
|
||||
{{"cyd", id(cyd_page), id(cyd_room), id(cyd_time), id(sublabel_big_cyd)->get_lv_font(), id(materialdesign_icons_mini_cyd)->get_lv_font()}},
|
||||
{{"elecrow", id(elecrow_page), id(elecrow_room), id(elecrow_time), id(sublabel_big_guition)->get_lv_font(), id(materialdesign_icons_mini_guition)->get_lv_font()}},
|
||||
}};
|
||||
for (auto &scenario : scenarios) {{
|
||||
for (auto &board : boards) {{
|
||||
@@ -200,8 +203,8 @@ display:
|
||||
- platform: sdl
|
||||
id: sdl_display
|
||||
dimensions:
|
||||
width: 480
|
||||
height: 480
|
||||
width: 800
|
||||
height: 540
|
||||
auto_clear_enabled: false
|
||||
update_interval: never
|
||||
|
||||
@@ -227,7 +230,7 @@ lvgl:
|
||||
points:
|
||||
- 0, 0
|
||||
- 1, 1
|
||||
{band('guition', 0)}{band('cyd', 220)}
|
||||
{band('guition', 0)}{band('cyd', 220)}{band('elecrow', 340).replace('headline_elecrow', 'headline_guition').replace('time_label_elecrow', 'time_label_guition')}
|
||||
script:
|
||||
- id: action_seed
|
||||
then:
|
||||
@@ -254,15 +257,18 @@ def crop_scale(image, width, height, scale):
|
||||
return bytes(out)
|
||||
|
||||
def sheet(out):
|
||||
"""Header bands of every scenario: Guition 2x above CYD 3x, 12 px white between."""
|
||||
"""Header bands of every scenario: Guition 2x above CYD 3x above the Elecrow 1x, 12 px white between."""
|
||||
width, gap = 960, 12
|
||||
rows = []
|
||||
for slug, _, _ in SCENARIOS:
|
||||
for board, scale in (('guition', 2), ('cyd', 3)):
|
||||
for board, scale in (('guition', 2), ('cyd', 3), ('elecrow', 1)):
|
||||
image = read_ppm(out / f'{slug}-{board}.ppm')
|
||||
band_w, band_h = BANDS[board]
|
||||
png(out / f'{slug}-{board}.png', image[0], image[1], image[2])
|
||||
rows.append((crop_scale(image, band_w, band_h, scale), band_h * scale))
|
||||
band = crop_scale(image, band_w, band_h, scale)
|
||||
# A band narrower than the sheet (the Elecrow's 800) gets white at its right.
|
||||
pad = bytes([255]) * (width - band_w * scale) * 3
|
||||
rows.append((b''.join(band[y * band_w * scale * 3:(y + 1) * band_w * scale * 3] + pad for y in range(band_h * scale)), band_h * scale))
|
||||
rows.append((bytes([255]) * width * 3 * gap * 2, gap * 2))
|
||||
height = sum(h for _, h in rows)
|
||||
png(out / 'sheet.png', width, height, b''.join(data for data, _ in rows))
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { computed } from "vue";
|
||||
import { t } from "../i18n";
|
||||
import { cellsOf, pageOf, sizeOf, SLOTS_PER_PAGE, spanOf } from "../model/layout";
|
||||
import { isGuition, openBar, removePage, state } from "../store";
|
||||
import { isGuition, isWideScreen, openBar, removePage, state } from "../store";
|
||||
import type { Tile } from "../types";
|
||||
import TileCard from "./TileCard.vue";
|
||||
import TopbarSvg from "./TopbarSvg.vue";
|
||||
@@ -29,7 +29,7 @@ function pickCell(slot: number) {
|
||||
<button v-if="empty" type="button" class="btn mini" :title="t('editor.page.remove_title')" @click="removePage(page)">{{ t("editor.page.remove") }}</button>
|
||||
<span v-else>{{ filled }} / {{ SLOTS_PER_PAGE }}</span>
|
||||
</div>
|
||||
<div class="device" :class="{ cyd: !isGuition }">
|
||||
<div class="device" :class="{ cyd: !isGuition, wide: isWideScreen }">
|
||||
<div class="bar-wrap" :class="{ selected: barSelected }" :title="t('editor.page.edit_bar')" role="button" tabindex="0"
|
||||
@click="openBar(0)" @keydown.enter.prevent="openBar(0)">
|
||||
<TopbarSvg />
|
||||
|
||||
@@ -189,6 +189,7 @@ onBeforeUnmount(() => clearInterval(poll));
|
||||
<div class="boards">
|
||||
<label class="board"><input type="radio" name="board" value="cyd" v-model="form.board" /><span><b>{{ t("editor.installer.board_cyd") }}</b><small>ESP32-2432S028 · 320 × 240</small></span></label>
|
||||
<label class="board"><input type="radio" name="board" value="guition" v-model="form.board" /><span><b>{{ t("editor.installer.board_guition") }}</b><small>ESP32-S3-4848S040 · 480 × 480 · GT911</small></span></label>
|
||||
<label class="board"><input type="radio" name="board" value="elecrow" v-model="form.board" /><span><b>{{ t("editor.installer.board_elecrow") }}</b><small>DIS08070H · ESP32-S3 N4R8 · 800 × 480 · GT911</small></span></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="field">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { computed } from "vue";
|
||||
import { t } from "../i18n";
|
||||
import { entriesOf, hasGaps, MAX_PAGES, pageCount } from "../model/layout";
|
||||
import { addPage, closeInspector, currentScreen, pageReachWarning, pagesShown, state, supports, tileLimit } from "../store";
|
||||
import { addPage, closeInspector, currentScreen, isGuition, isWideScreen, pageReachWarning, pagesShown, state, supports, tileLimit } from "../store";
|
||||
import DevicePage from "./DevicePage.vue";
|
||||
import Library from "./Library.vue";
|
||||
|
||||
@@ -37,7 +37,7 @@ function onCanvasClick(e: MouseEvent) {
|
||||
<DevicePage v-for="page in shown" :key="page" :page="page - 1" :entries="entries" :pages="pages" :moving="state.drag.moving" />
|
||||
<div class="page ghost" :class="{ disabled: !canAdd }">
|
||||
<div class="page-label"><span>{{ t("editor.page.label", { page: shown + 1 }) }}</span></div>
|
||||
<div class="device" :class="{ cyd: currentScreen?.board !== 'guition' }" id="add-page" role="button" :tabindex="canAdd ? 0 : -1" @click="canAdd && addPage()" @keydown.enter.prevent="canAdd && addPage()">
|
||||
<div class="device" :class="{ cyd: !isGuition, wide: isWideScreen }" id="add-page" role="button" :tabindex="canAdd ? 0 : -1" @click="canAdd && addPage()" @keydown.enter.prevent="canAdd && addPage()">
|
||||
{{ canAdd ? t("editor.layout.add_page") : t("editor.layout.max_pages", MAX_PAGES) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -50,6 +50,8 @@ export function agoText(then: number, now = Math.floor(Date.now() / 1000), local
|
||||
export type BarMetrics = { width: number; top: number; name: number; text: number; icon: number };
|
||||
export const BAR_METRICS: Record<string, BarMetrics> = {
|
||||
guition: { width: 448, top: 36, name: 27, text: 21, icon: 26 },
|
||||
// The Elecrow CrowPanel 7.0" (app 0.2.93): the Guition's bar across 800 px, the same insets and fonts.
|
||||
elecrow: { width: 768, top: 36, name: 27, text: 21, icon: 26 },
|
||||
cyd: { width: 298, top: 24, name: 18, text: 14, icon: 18 },
|
||||
};
|
||||
export type ItemView = { icon?: string | null; text?: string; color?: string | null; shown: boolean; analog?: boolean; loading?: boolean };
|
||||
|
||||
+6
-2
@@ -86,8 +86,12 @@ export const pageTilesRepeat = computed(() => {
|
||||
return typeof repeat === "boolean" ? repeat : supports(0, 2, 65);
|
||||
});
|
||||
export const repeatable = (id: string) => pageTilesRepeat.value && pageTarget(id) > 0;
|
||||
export const isGuition = computed(() => currentScreen.value?.board === "guition");
|
||||
export const barMetrics = computed(() => BAR_METRICS[isGuition.value ? "guition" : "cyd"]);
|
||||
// The large boards (app 0.2.93): a Guition or an Elecrow CrowPanel 7.0", the ones that draw camera images and album
|
||||
// covers and take the large cards; `isGuition` keeps its name where it means that. The CYD is the small one.
|
||||
export const LARGE_BOARDS = ["guition", "elecrow"];
|
||||
export const isGuition = computed(() => LARGE_BOARDS.includes(currentScreen.value?.board ?? ""));
|
||||
export const isWideScreen = computed(() => currentScreen.value?.board === "elecrow");
|
||||
export const barMetrics = computed(() => BAR_METRICS[currentScreen.value?.board ?? ""] ?? BAR_METRICS.cyd);
|
||||
export const currentTile = computed<Tile | undefined>(() =>
|
||||
state.selectedTile && state.layout?.tiles.includes(state.selectedTile) ? state.selectedTile : undefined);
|
||||
// The reactive copy and the plain object are the same tile.
|
||||
|
||||
@@ -137,6 +137,7 @@ a:hover { text-decoration: underline; }
|
||||
/* The device mock keeps the Guition light palette on purpose: it is the screen, not the page. */
|
||||
.device { background: #e7e7e7; border-radius: 18px; padding: 10px; display: grid; grid-template-rows: auto minmax(0, 1fr); gap: 8px; color: #1b1b1b; aspect-ratio: 1 / 1; }
|
||||
.device.cyd { aspect-ratio: 4 / 3; }
|
||||
.device.wide { aspect-ratio: 5 / 3; }
|
||||
.bar-wrap { cursor: pointer; border-radius: 8px; padding: 0 2px; }
|
||||
.bar-wrap:hover { background: #dedede; }
|
||||
.bar-wrap.selected { outline: 2px solid var(--accent); outline-offset: 1px; }
|
||||
|
||||
@@ -237,6 +237,11 @@ describe("TileInspector: a live picture on a camera tile (app 0.2.91)", () => {
|
||||
Object.assign(state.inventory.screens[0], { board: "guition" });
|
||||
tile.options = { size: "full" };
|
||||
expect(choices(mount(TileInspector, { props: { tile } }), "Display")).toEqual(["Name and status", "Large value"]);
|
||||
// An Elecrow CrowPanel 7.0" (app 0.2.93) is a large board like the Guition: the cover is on offer again.
|
||||
Object.assign(state.inventory.screens[0], { board: "elecrow" });
|
||||
tile.options = { size: "single" };
|
||||
expect(choices(mount(TileInspector, { props: { tile } }), "Display")).toContain("Album cover");
|
||||
Object.assign(state.inventory.screens[0], { board: "guition" });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user