Add the Elecrow CrowPanel 7.0" as a third board, and take the packages and the add-on from Gitea (0.2.93)
CI / checks (push) Successful in 12m50s
CI / firmware (add-on) (push) Successful in 18m19s
CI / firmware (min_version) (push) Successful in 17m55s

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:
Xandrossian
2026-09-20 23:18:01 -07:00
parent efbab04b39
commit 9857cc3bda
64 changed files with 1664 additions and 145 deletions
+3 -3
View File
@@ -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):
+1 -1
View File
@@ -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'}
+17 -6
View File
@@ -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:
+2 -2
View File
@@ -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,
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
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
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -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>