fcdea12c44
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>
113 lines
6.7 KiB
Python
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()
|