4ec8c7a935
The 2 ms reset of ESPHome's expander support (0.2.81) left the chip silent: "Calibration error" with the PCA9557 answering at 0x18. A component of smart_display's (elecrow_touch_reset.h, setup priority 700: after the I2C bus, before the GT911 driver) writes the expander as Elecrow's V3.0 ESPHome example does: every pin an output and low, 20 ms, the reset released, 100 ms, the interrupt line back to an input. The driver's reset and interrupt pins are gone.
155 lines
9.4 KiB
Python
155 lines
9.4 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 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()
|
|
for step in ('write(0x03, 0x00)', 'write(0x01, 0x00)', 'delay(20)', 'write(0x01, 0x01)', 'delay(100)', 'write(0x03, 0x02)'):
|
|
self.assertIn(step, header)
|
|
self.assertIn('return 700.0f', header)
|
|
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()
|