diff --git a/test/README.md b/test/README.md index 10db0d9a8..8f9e1a412 100644 --- a/test/README.md +++ b/test/README.md @@ -13,6 +13,7 @@ require integration or target testing; see "Local testing without hardware" in pio test -e native # all suites except KISS modem pio test -e native_kiss_modem # KISS modem suite pio test -e native -f test_webconfig_keys # a single suite +python3 test/test_indicator_display_profile.py # Indicator RAM/scale contract ``` A green `[PASSED]` per suite means GoogleTest returned 0 (all assertions diff --git a/test/test_indicator_display_profile.py b/test/test_indicator_display_profile.py new file mode 100644 index 000000000..a706fcec9 --- /dev/null +++ b/test/test_indicator_display_profile.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +import re +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" + + +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_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()) + + +if __name__ == "__main__": + unittest.main() diff --git a/variants/sensecap_indicator-espnow/README.md b/variants/sensecap_indicator-espnow/README.md index 001b04f42..32df5674b 100644 --- a/variants/sensecap_indicator-espnow/README.md +++ b/variants/sensecap_indicator-espnow/README.md @@ -1,11 +1,11 @@ # SenseCAP Indicator controls -The Indicator uses its full 480x480 panel resolution while preserving the -existing 160x160 UI coordinate system. A 4-bit internal canvas avoids PSRAM -contention with RGB scanout; text and icons are rendered directly at 3x detail -instead of enlarging a completed 160x160 frame. The screen blanks after five -minutes on USB power; the hardware button or a touch wakes it without -selecting anything. +The Indicator preserves the existing 160x160 UI coordinate system in a +320x320, 4-bit internal canvas and scales it 1.5x onto the 480x480 panel. The +canvas avoids PSRAM contention with RGB scanout while reserving enough internal +RAM for Bluetooth and WiFi to operate together. The screen blanks after five +minutes on USB power; the hardware button or a touch wakes it without selecting +anything. ## Touch navigation diff --git a/variants/sensecap_indicator-espnow/platformio.ini b/variants/sensecap_indicator-espnow/platformio.ini index ec3abfce9..ae6a76ee9 100644 --- a/variants/sensecap_indicator-espnow/platformio.ini +++ b/variants/sensecap_indicator-espnow/platformio.ini @@ -20,11 +20,12 @@ build_flags = -D IO_EXPANDER=0x40 -D IO_EXPANDER_IRQ=42 -D DISPLAY_ROTATION=1 - ; Render at the panel's native 480x480 resolution while preserving the - ; established 160x160 UI coordinate system. A 4-bit internal canvas avoids - ; competing with the RGB scanout framebuffer in PSRAM. - -D UI_ZOOM=1 - -D UI_COORD_SCALE=3 + ; Render the established 160x160 UI into a 320x320 internal canvas, then + ; scale it 1.5x onto the 480x480 panel. The 4-bit canvas stays out of PSRAM + ; to protect RGB scanout, but now consumes 51,200 instead of 115,200 bytes + ; of scarce internal RAM so BLE and WiFi can run together. + -D UI_ZOOM=1.5f + -D UI_COORD_SCALE=2 -D UI_BUFFER_COLOR_DEPTH=4 -D AUTO_OFF_MILLIS=300000UL -D USB_MESSAGE_PREVIEW_MILLIS=300000UL