da28d19961
A unit's touch never answered: "touchscreen is marked FAILED: Calibration error" at setup, the GT911 still in reset behind the PCA9557 (I2C 0x18) of the V3.0 board. The board file declares the expander as ESPHome's pca9554 (the same register map) and hands the GT911 driver its reset (IO0) and interrupt (IO1) pins on it, so the driver runs Elecrow's own sequence: reset low with the interrupt line low, reset high, 56 ms, address 0x5D. A V2.0 without the expander boots its GT911 by the pull-up as before. The bus is scanned at boot for the log.
150 lines
9.0 KiB
Python
150 lines
9.0 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 (as ESPHome's pca9554, the same registers) brings the GT911 out of reset (firmware 0.2.81).
|
|
self.assertIn('address: 0x18', TEXT)
|
|
for pin, number in (('reset_pin', 0), ('interrupt_pin', 1)):
|
|
self.assertIn(f' {pin}:\n pca9554: touch_expander\n number: {number}', 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()
|