#!/usr/bin/env python3 import re import struct from pathlib import Path import unittest ROOT = Path(__file__).resolve().parents[1] PROFILE = ROOT / "variants" / "sensecap_indicator-espnow" / "platformio.ini" DISPLAY = ROOT / "src" / "helpers" / "ui" / "LGFXDisplay.cpp" INDICATOR_DISPLAY = ( ROOT / "variants" / "sensecap_indicator-espnow" / "SCIndicatorDisplay.h" ) FONT = ROOT / "variants" / "sensecap_indicator-espnow" / "sd" / "ui-font.vlw" def base_profile() -> str: text = PROFILE.read_text() match = re.search( r"^\[SenseCapIndicator-ESPNow\]\n(?P
.*?)(?=^\[)", text, re.MULTILINE | re.DOTALL, ) if match is None: raise AssertionError("SenseCap Indicator base profile is missing") return match.group("body") def numeric_flag(name: str) -> float: match = re.search( rf"^\s*-D\s+{re.escape(name)}=(\S+)\s*$", base_profile(), re.MULTILINE, ) if match is None: raise AssertionError(f"{name} is missing from the Indicator profile") return float(match.group(1).removesuffix("f")) class IndicatorDisplayProfileTest(unittest.TestCase): def test_panel_cs_release_cannot_discard_initialized_display(self): display = INDICATOR_DISPLAY.read_text() self.assertIn("releasePanelChipSelectWithRetry()", display) self.assertIn("if (!initialized) {", display) self.assertIn("begin_status = 0x82;", display) self.assertNotIn("if (!initialized || !released) return false;", display) self.assertIn("panel_chip_select_released =", display) self.assertIn("void startFrame(ColorVal bkg", display) self.assertIn("const bool controllers_prepared = prepareControllers()", display) self.assertIn("begin_status = controllers_prepared ? 4 : 0x84", display) self.assertIn("if (expander_address == 0 && prepareControllers())", display) self.assertIn("uint8_t beginStatus() const", display) def test_preserves_original_indicator_memory_profile(self): profile = base_profile() self.assertIn("board_build.arduino.memory_type = qio_opi", profile) self.assertIn("board_build.psram_type = opi", profile) self.assertIn("board_upload.flash_size = 8MB", profile) def test_n16r2_profile_uses_quad_psram_and_full_flash(self): profile = PROFILE.read_text() start = profile.index( "[env:SenseCapIndicator-LoRa-N16R2_comp_radio_usb_wifi]" ) n16r2 = profile[start:] self.assertIn("board_build.arduino.memory_type = qio_qspi", n16r2) self.assertIn("board_build.psram_type = qspi", n16r2) self.assertIn("board_upload.flash_size = 16MB", n16r2) self.assertIn("board_upload.maximum_size = 16777216", n16r2) self.assertIn("dual_ota_6400k_preserve_spiffs.csv", n16r2) def test_preserves_logical_ui_and_fills_panel(self): panel_size = 480 zoom = numeric_flag("UI_ZOOM") coordinate_scale = numeric_flag("UI_COORD_SCALE") logical_size = panel_size / (zoom * coordinate_scale) sprite_size = logical_size * coordinate_scale self.assertEqual(logical_size, 160) self.assertEqual(sprite_size, 320) self.assertEqual(sprite_size * zoom, panel_size) def test_internal_canvas_leaves_interface_headroom(self): panel_size = 480 color_depth = numeric_flag("UI_BUFFER_COLOR_DEPTH") zoom = numeric_flag("UI_ZOOM") coordinate_scale = numeric_flag("UI_COORD_SCALE") logical_size = panel_size / (zoom * coordinate_scale) sprite_size = int(logical_size * coordinate_scale) old_canvas_bytes = panel_size * panel_size * int(color_depth) // 8 canvas_bytes = sprite_size * sprite_size * int(color_depth) // 8 self.assertEqual(canvas_bytes, 51_200) self.assertGreaterEqual(old_canvas_bytes - canvas_bytes, 64_000) self.assertIn("buffer.setPsram(false);", DISPLAY.read_text()) def test_uses_dedicated_pairing_block(self): self.assertIn("-D UI_DEDICATED_PAIRING_BLOCK=1", base_profile()) logical_height = 160 block_height = 48 block_center = logical_height * 4 // 5 block_top = block_center - block_height // 2 info_top = 40 info_bottom = block_top - 12 self.assertEqual(block_center, 128) self.assertEqual(block_top, 104) self.assertLess(info_top, info_bottom) self.assertLessEqual(info_bottom + 12, block_top) def test_recovered_font_pairing_values_fit_reserved_block(self): data = FONT.read_bytes() glyph_count = struct.unpack_from(">I", data, 0)[0] advances = {} heights = {} for index in range(glyph_count): offset = 24 + index * 28 codepoint, height, _width, advance = struct.unpack_from( ">IIII", data, offset ) advances[codepoint] = advance heights[codepoint] = height footer = data[-40:] self.assertEqual(b"MCEMAP2\0", footer[:8]) atlas_offset = struct.unpack_from("