Files
Xandrossian 6910f30d4c
CI / checks (push) Has been cancelled
CI / firmware (add-on) (push) Has been cancelled
CI / firmware (min_version) (push) Has been cancelled
Add touch_probe and touch_reset actions to the Elecrow for a diagnosis over the API (0.2.97, firmware 0.2.83)
2026-09-21 14:04:25 -07:00

160 lines
9.8 KiB
Python

"""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: true', TEXT)
self.assertIn('address: 0x5D', TEXT)
self.assertIn('pin: GPIO2', TEXT)
# The USB pins are the touch I2C: the header that frees them.
self.assertIn('<esphome/components/smart_display/elecrow_usb_phy.h>', TEXT)
# The V3.0's PCA9557 brings the GT911 out of reset before the driver runs (firmware 0.2.82).
self.assertIn(' elecrow_touch_reset:\n id: touch_reset\n i2c_id: touch_bus\n address: 0x18', TEXT)
code = '\n'.join(line for line in TEXT.split('\n') if not line.lstrip().startswith('#'))
self.assertNotIn('reset_pin', code)
self.assertNotIn('interrupt_pin', code)
header = (ROOT / 'components/smart_display/elecrow_touch_reset.h').read_text()
# Elecrow's own values at setup, and the sequence in that order.
self.assertIn('this->reset(0x00, 0x00, 0x01, 20, 100, 0x02)', header)
order = [header.index(step) for step in ("write_reg_(0x03, config)", "write_reg_(0x01, low)", "delay(hold_ms)",
"write_reg_(0x01, high)", "delay(wait_ms)", "write_reg_(0x03, final_config)")]
self.assertEqual(order, sorted(order))
self.assertIn('return 700.0f', header)
for action in ('touch_probe', 'touch_reset'):
self.assertIn(f'- action: {action}', TEXT)
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()