Files
homeassistant_espscreen/tests/test_easy_package.py
T
Max Gramser fcdea12c44 Keep every screen's Wi-Fi awake from the shared core, so Home Assistant's messages no longer wait for the router's next beacon (0.2.88)
packages/core.yaml sets wifi: power_save_mode: none. ESPHome merges it
under the screen's own wifi: block, which keeps the network, the password
and the hotspot, and the screen's own YAML still wins on the mode. The
YAML ESP Screens writes has had the line since app 0.2.24; screens with
an older or hand-written YAML ran ESPHome's ESP32 default light, and
their messages from Home Assistant waited 76-117 ms on the bench.

esphome config on the bench screens' Device Builder YAMLs: NONE for both
without a line of their own, LIGHT when the YAML says light. The bench
Guition, flashed from its Device Builder YAML: ping 15 ms average instead
of 117 ms. tools/check.sh: 474 Python tests (a new one keeps the core's
wifi: block to this one key), 19/19 C++, editor; firmware unchanged in
size (CYD 1,678,816 B, 91.5 %; Guition 2,055,280 B), since the checkout
profiles already had the line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 09:06:53 +02:00

113 lines
6.7 KiB
Python

"""The packages a screen builds from: one shared core plus a board file, runtime tiles only (app 0.2.84+)."""
import importlib.util
import re
import subprocess
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
BOARDS = {'cyd': 'home-like-2432s028.yaml', 'guition': 'guition-4848s040.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',
'publish_action', 'do_tile_action', 'open_vacuum_overlay', 'vacuum_action', 'vacuum_fan_speed', 'vacuum_overlay',
'vacspd_quiet', 'vacuum_actions', 'DYNAMIC_TILES', 'TILE_COUNT', 'DIRECT_ACTIONS', 'TILE1_ENTITY',
'light_controls::subscribe']
def load_checker():
spec = importlib.util.spec_from_file_location('check_packages', ROOT / 'tools/check_packages.py')
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
class PackageTests(unittest.TestCase):
def test_the_packages_fit_together(self):
result = subprocess.run([sys.executable, str(ROOT / 'tools/check_packages.py')], capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
def test_every_entry_is_the_shared_core_plus_its_board(self):
checker = load_checker()
for board, profile in BOARDS.items():
for name in (profile, f'packages/{board}.yaml'):
self.assertEqual(sorted(checker.included(ROOT / name)), sorted(['packages/core.yaml', str(profiles.BOARDS[board].relative_to(ROOT))]), name)
def test_the_published_entry_takes_everything_from_github(self):
for board in BOARDS:
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)
# 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())
def test_no_secret_or_local_path_under_packages(self):
for path in sorted((ROOT / 'packages').rglob('*.yaml')):
text = '\n'.join(line for line in path.read_text().split('\n') if not line.lstrip().startswith('#'))
self.assertNotIn('!secret', text, path.name)
self.assertNotIn('type: local', text, path.name)
for section in ('sensor', 'binary_sensor', 'text_sensor'):
block = re.search(r'^' + section + r':\n.*?(?=^[a-zA-Z_]+:|\Z)', text, re.M | re.S)
self.assertFalse(block and re.search(r'^ - platform: homeassistant$', block[0], re.M), f'{path.name}: a {section} subscribes to Home Assistant')
def test_the_wifi_never_dozes_but_stays_the_owners(self):
# Firmware 0.2.74+: the core keeps Wi-Fi awake for every screen, and nothing else of Wi-Fi lives in a package;
# the network, its password and the fallback hotspot stay in the screen's own YAML, which also wins on the mode.
for path in sorted((ROOT / 'packages').rglob('*.yaml')):
text = path.read_text()
block = re.search(r'^wifi:\n(.*?)(?=^[a-zA-Z_]+:|\Z)', text, re.M | re.S)
if path == profiles.CORE:
self.assertIsNotNone(block, 'packages/core.yaml has no wifi: block')
lines = [line for line in block[1].split('\n') if line.strip() and not line.lstrip().startswith('#')]
self.assertEqual(lines, [' power_save_mode: none'])
else:
self.assertIsNone(block, f'{path.name} has a wifi: block')
# The YAML ESP Screens writes says so too (since app 0.2.24), so an older package changes nothing for those.
self.assertIn(' power_save_mode: none\n', (ROOT / 'screen_manager/app/core.py').read_text())
def test_the_manual_profile_is_gone_from_profiles_and_packages(self):
for board, name in BOARDS.items():
for entry in (name, f'packages/{board}.yaml'):
text = '\n'.join(line for line in profiles.text(entry).split('\n') if not line.lstrip().startswith('#'))
for key in GONE:
self.assertNotIn(key, text, f'{entry} still carries {key}')
def test_runtime_tiles_keep_what_they_bind_and_open(self):
for board in BOARDS:
package = profiles.text(f'packages/{board}.yaml')
for key in ['runtime_tiles::bind(9, id(tile10)', 'runtime_tiles::enabled = true;', 'id: open_value_overlay',
'id: ui_refresh', 'runtime_tiles::render(id(lbl_room));', 'id: climate_detail_overlay',
'id: color_detail_overlay']:
self.assertIn(key, package, board)
# The CYD keeps its resistive calibration on the screen itself; the Guition's GT911 needs none.
self.assertIn('screen_calibration::setup(', profiles.resolved('packages/cyd.yaml'))
self.assertNotIn('screen_calibration::setup(', profiles.resolved('packages/guition.yaml'))
def test_the_core_carries_no_board_number_and_every_board_the_same_names(self):
core = profiles.CORE.read_text()
for needle in ('esp32:', 'platform: xpt2046', 'platform: gt911', 'platform: st7701s', 'platform: mipi_spi', 'psram:',
'set_raw_correction', 'camera_image', 'GPIO'):
self.assertNotIn(needle, core, f'{needle} is a board\'s, not the core\'s')
names = {board: set(profiles.substitutions_of(path)) for board, path in profiles.BOARDS.items()}
shared = set.intersection(*names.values())
self.assertGreater(len(shared), 100)
for board, defined in names.items():
own = defined - shared
self.assertTrue(all(re.match(r'(TOUCH_AFFINE_|TOUCH_CAL_|EDGE_SWIPE_|ALERT_\w*IMAGE|CAMERA_)', n) for n in own), f'{board}: {sorted(own)}')
def test_the_checker_refuses_a_fixed_home_assistant_subscription(self):
source = (ROOT / 'tools/check_packages.py').read_text()
self.assertIn("re.search(r'^ - platform: homeassistant$', match[0], re.M)", source)
self.assertIn("for section in ('sensor', 'binary_sensor', 'text_sensor'):", source)
if __name__ == '__main__':
unittest.main()