diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index f23a4e0d1..515d04ff5 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -826,6 +826,7 @@ void halt() { CompanionWiFiDisplayState::NotRendered; static uint32_t companion_wifi_display_frames = 0; static bool companion_display_available = false; + static uint8_t companion_display_begin_status = 0; #if defined(COMPANION_EXCLUSIVE_WIFI_BLE) static CompanionTransportMode companion_transport_boot_mode = @@ -1174,10 +1175,11 @@ void halt() { const bool idf_associated = esp_wifi_sta_get_ap_info(&access_point) == ESP_OK; snprintf(reply, reply_size, - "> state=%s, frames=%lu, display=%u, requested=%u, services=%u, " + "> state=%s, frames=%lu, display=%u/%02X, requested=%u, services=%u, " "wl=%d, idf=%u, IP=%s", names[state], (unsigned long)companion_wifi_display_frames, companion_display_available ? 1U : 0U, + (unsigned int)companion_display_begin_status, companion_wifi_requested ? 1U : 0U, companion_wifi_active ? 1U : 0U, (int)WiFi.status(), idf_associated ? 1U : 0U, @@ -1737,6 +1739,7 @@ void setup() { } #if defined(ESP32) && defined(WIFI_SSID) companion_display_available = disp != NULL; + companion_display_begin_status = display.beginStatus(); #endif #endif diff --git a/test/test_indicator_display_profile.py b/test/test_indicator_display_profile.py index a553f4f26..d4652a93c 100644 --- a/test/test_indicator_display_profile.py +++ b/test/test_indicator_display_profile.py @@ -42,10 +42,15 @@ 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) return false;", 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() diff --git a/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h b/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h index 07c784a67..5300bca2c 100644 --- a/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h +++ b/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h @@ -130,6 +130,7 @@ class SCIndicatorDisplay : public LGFXDisplay { uint8_t expander_address = 0; uint16_t expander_output = 0; bool panel_chip_select_released = false; + uint8_t begin_status = 0; static constexpr gpio_num_t BACKLIGHT_PIN = GPIO_NUM_45; static constexpr uint8_t I2C_PORT = 0; @@ -218,10 +219,14 @@ class SCIndicatorDisplay : public LGFXDisplay { } bool prepareControllers() { - if (!lgfx::i2c::init(I2C_PORT, PIN_BOARD_SDA, PIN_BOARD_SCL).has_value()) { - return false; + for (uint8_t attempt = 0; attempt < 10; ++attempt) { + if (lgfx::i2c::init(I2C_PORT, PIN_BOARD_SDA, PIN_BOARD_SCL).has_value() + && (prepareControllersAt(0x20) || prepareControllersAt(0x39))) { + return true; + } + delay(20); } - return prepareControllersAt(0x20) || prepareControllersAt(0x39); + return false; } bool releasePanelChipSelect() { @@ -258,15 +263,23 @@ public: #endif bool begin() { - if (!prepareControllers()) return false; + begin_status = 1; + const bool controllers_prepared = prepareControllers(); + begin_status = controllers_prepared ? 2 : 0x81; const bool initialized = LGFXDisplay::begin(); - if (!initialized) return false; + if (!initialized) { + begin_status = 0x82; + return false; + } + begin_status = controllers_prepared ? 3 : 0x83; // A failed CS release must not discard an otherwise valid 115 KiB render // buffer and leave the LCD showing its pre-reset scanout forever. Keep the // UI alive and retry from startFrame(); the common case succeeds here // before the external LoRa radio is initialized. - panel_chip_select_released = releasePanelChipSelectWithRetry(); + if (controllers_prepared) { + panel_chip_select_released = releasePanelChipSelectWithRetry(); + } setBacklight(true); size_t fontSize; uint8_t* fontData = IndicatorFontClient::load(fontSize); @@ -280,16 +293,28 @@ public: } #endif } + begin_status = controllers_prepared ? 4 : 0x84; return true; } void startFrame(ColorVal bkg = UIColor::window_bkg) override { + if (expander_address == 0 && prepareControllers()) { + // The RGB scanout can start while the shared expander is temporarily + // unavailable. Once it recovers, re-run the panel command sequence and + // release CS before presenting the next complete frame. + if (disp.init()) { + panel_chip_select_released = releasePanelChipSelectWithRetry(); + begin_status = 5; + } + } if (!panel_chip_select_released) { panel_chip_select_released = releasePanelChipSelectWithRetry(); } LGFXDisplay::startFrame(bkg); } + uint8_t beginStatus() const { return begin_status; } + #ifdef INDICATOR_WIFI_FONT_RECOVERY void serviceFontRecovery() { size_t fontSize = 0;