From 615c6763f68a889fd22c67e913a29f0b8934d307 Mon Sep 17 00:00:00 2001
From: s-hadinger <49731213+s-hadinger@users.noreply.github.com>
Date: Sat, 14 Dec 2024 22:39:45 +0100
Subject: [PATCH 1/5] Tls ecdsa (#22649)

* TLS add support for ECDSA on ESP32

* Reduce size for ESP8266
---
 CHANGELOG.md                                  |  1 +
 .../src/WiFiClientSecureLightBearSSL.cpp      | 49 +++++++++++++++----
 2 files changed, 41 insertions(+), 9 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index dc5e3931b..e14afd0d6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
 ## [14.4.0.1] 
 ### Added
 - MCP23XXX_DRV control register IOCON in template (#22622)
+- TLS add support for ECDSA on ESP32
 
 ### Breaking Changed
 
diff --git a/lib/lib_ssl/tls_mini/src/WiFiClientSecureLightBearSSL.cpp b/lib/lib_ssl/tls_mini/src/WiFiClientSecureLightBearSSL.cpp
index a11b04f03..4259023e5 100755
--- a/lib/lib_ssl/tls_mini/src/WiFiClientSecureLightBearSSL.cpp
+++ b/lib/lib_ssl/tls_mini/src/WiFiClientSecureLightBearSSL.cpp
@@ -788,19 +788,39 @@ extern "C" {
   // created with more than two primes, and most numbers, even large ones, can
   // be easily factored.
   static void pubkeyfingerprint_pubkey_fingerprint(br_x509_pubkeyfingerprint_context *xc) {
-    br_rsa_public_key rsakey = xc->ctx.pkey.key.rsa;
+    if (xc->ctx.pkey.key_type == BR_KEYTYPE_RSA) {
+      br_rsa_public_key rsakey = xc->ctx.pkey.key.rsa;
 
-    br_sha1_context shactx;
+      br_sha1_context shactx;
 
-    br_sha1_init(&shactx);
+      br_sha1_init(&shactx);
 
-    // The tag string doesn't really matter, but it should differ depending on
-    // key type. Since we only support RSA for now, it's a fixed string.
-    sha1_update_len(&shactx, "ssh-rsa", 7);          // tag
-    sha1_update_len(&shactx, rsakey.e, rsakey.elen); // exponent
-    sha1_update_len(&shactx, rsakey.n, rsakey.nlen); // modulus
+      // The tag string doesn't really matter, but it should differ depending on
+      // key type. For RSA it's a fixed string.
+      sha1_update_len(&shactx, "ssh-rsa", 7);          // tag
+      sha1_update_len(&shactx, rsakey.e, rsakey.elen); // exponent
+      sha1_update_len(&shactx, rsakey.n, rsakey.nlen); // modulus
 
-    br_sha1_out(&shactx, xc->pubkey_recv_fingerprint); // copy to fingerprint
+      br_sha1_out(&shactx, xc->pubkey_recv_fingerprint); // copy to fingerprint
+    }
+  #ifndef ESP8266
+    else if (xc->ctx.pkey.key_type == BR_KEYTYPE_EC) {
+      br_ec_public_key eckey = xc->ctx.pkey.key.ec;
+
+      br_sha1_context shactx;
+
+      br_sha1_init(&shactx);
+
+      // The tag string doesn't really matter, but it should differ depending on
+      // key type. For ECDSA it's a fixed string.
+      sha1_update_len(&shactx, "ecdsa-sha2-nistp256", 19); // tag
+      sha1_update_len(&shactx, eckey.q, eckey.qlen);       // exponent
+    }
+  #endif
+    else {
+      // We don't support anything else, so just set the fingerprint to all zeros.
+      memset(xc->pubkey_recv_fingerprint, 0, 20);
+    }
   }
 
   // Callback when complete chain has been parsed.
@@ -856,11 +876,19 @@ extern "C" {
     ctx->fingerprint_all = fingerprint_all;
   }
 
+#ifdef ESP8266
   // We limit to a single cipher to reduce footprint
   // we reference it, don't put in PROGMEM
   static const uint16_t suites[] = {
     BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
   };
+#else
+  // add more flexibility on ESP32
+  static const uint16_t suites[] = {
+    BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+    BR_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+  };
+#endif
 
   // Default initializion for our SSL clients
   static void br_ssl_client_base_init(br_ssl_client_context *cc) {
@@ -884,6 +912,9 @@ extern "C" {
 
     // we support only P256 EC curve for AWS IoT, no EC curve for Letsencrypt unless forced
     br_ssl_engine_set_ec(&cc->eng, &br_ec_p256_m15); // TODO
+#ifndef ESP8266
+    br_ssl_engine_set_ecdsa(&cc->eng, &br_ecdsa_i15_vrfy_asn1);
+#endif
   }
 }
 

From b3b969978283df8da352e9d7580642caaeddbb28 Mon Sep 17 00:00:00 2001
From: Theo Arends <11044339+arendst@users.noreply.github.com>
Date: Sun, 15 Dec 2024 00:32:51 +0100
Subject: [PATCH 2/5] Display related fixes

- CHange Display removed PWM control of backlight GPIO for universal display regression from v14.1.0
- Fix Display DisplayMode adds a display device while not configured
- Fix GUI intermittent exception on screen updates due to flash access
---
 CHANGELOG.md                                  |  5 +-
 RELEASENOTES.md                               |  5 +-
 tasmota/tasmota_support/support.ino           |  3 +
 .../xdrv_01_9_webserver.ino                   | 20 ++++---
 .../tasmota_xdrv_driver/xdrv_13_display.ino   | 56 ++++++++++---------
 5 files changed, 52 insertions(+), 37 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e14afd0d6..e705f0533 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,18 +6,21 @@ All notable changes to this project will be documented in this file.
 ## [14.4.0.1] 
 ### Added
 - MCP23XXX_DRV control register IOCON in template (#22622)
-- TLS add support for ECDSA on ESP32
+- ESP32 support for TLS ECDSA (#22649)
 
 ### Breaking Changed
 
 ### Changed
 - Berry make Leds animate calls reentrant (#22643)
 - SSL clean up remnants of old fingerprint algorithm (#22645)
+- Display removed PWM control of backlight GPIO for universal display regression from v14.1.0
 
 ### Fixed
 - ESP32 rules operation priority regression from v13.3.0.4 (#22636)
 - GUI display power button regression from v14.3.0.5 (#15788)
 - MCP23xxx, PCF8574 and Shift595 power control when a display is configured regression from v14.3.0.7
+- Display DisplayMode adds a display device while not configured
+- GUI intermittent exception on screen updates due to flash access
 
 ### Removed
 
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index d2433f585..89ec06e2f 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -117,14 +117,17 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
 ## Changelog v14.4.0.1
 ### Added
 - MCP23XXX_DRV control register IOCON in template [#22622](https://github.com/arendst/Tasmota/issues/22622)
+- ESP32 support for TLS ECDSA [#22649](https://github.com/arendst/Tasmota/issues/22649)
 
 ### Breaking Changed
 
 ### Changed
+- Display removed PWM control of backlight GPIO for universal display regression from v14.1.0
+- SSL clean up remnants of old fingerprint algorithm [#22645](https://github.com/arendst/Tasmota/issues/22645)
 - Berry make Leds animate calls reentrant [#22643](https://github.com/arendst/Tasmota/issues/22643)
-- SSL clean up remnants of old fingerprint algorithm (#22645)[#22645](https://github.com/arendst/Tasmota/issues/22645)
 
 ### Fixed
+- Display DisplayMode adds a display device while not configured
 - GUI display power button regression from v14.3.0.5 [#15788](https://github.com/arendst/Tasmota/issues/15788)
 - MCP23xxx, PCF8574 and Shift595 power control when a display is configured regression from v14.3.0.7
 - ESP32 rules operation priority regression from v13.3.0.4 [#22636](https://github.com/arendst/Tasmota/issues/22636)
diff --git a/tasmota/tasmota_support/support.ino b/tasmota/tasmota_support/support.ino
index 7d34b6c51..4caa4bdf4 100755
--- a/tasmota/tasmota_support/support.ino
+++ b/tasmota/tasmota_support/support.ino
@@ -835,6 +835,9 @@ int32_t UpdateDevicesPresent(int32_t change) {
 //    AddLog(LOG_LEVEL_DEBUG, PSTR("APP: Max 32 devices supported"));
   }
   TasmotaGlobal.devices_present = devices_present;
+
+//  AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("DVC: DevicesPresent %d, Change %d"), TasmotaGlobal.devices_present, change);
+
   return difference;
 }
 
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino b/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino
index db4e9540c..5c9f55e5a 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino
@@ -1289,7 +1289,7 @@ void WebGetDeviceCounts(void) {
   }
 #endif  // USE_SHUTTER
 
-//  AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HTP: DP %d, BNLNS %d, SB %08X"), TasmotaGlobal.devices_present, Web.buttons_non_light_non_shutter, Web.light_shutter_button_mask);
+//  AddLog(LOG_LEVEL_DEBUG, PSTR("HTP: DP %d, BNLNS %d, SB %08X"), TasmotaGlobal.devices_present, Web.buttons_non_light_non_shutter, Web.light_shutter_button_mask);
 }
 
 #ifdef USE_LIGHT
@@ -1374,7 +1374,6 @@ void HandleRoot(void) {
   if (TasmotaGlobal.devices_present) {
     WebGetDeviceCounts();
 
-    uint32_t button_idx = 1;
     if (Web.buttons_non_light_non_shutter) {   // Any non light AND non shutter button - Show toggle buttons
       WSContentSend_P(HTTP_TABLE100);      // "<table style='width:100%%'>"
       WSContentSend_P(PSTR("<tr>"));
@@ -1400,7 +1399,7 @@ void HandleRoot(void) {
         if (Web.buttons_non_light_non_shutter % rows) { cols++; }
 
         uint32_t button_ptr = 0;
-        for (button_idx = 1; button_idx <= TasmotaGlobal.devices_present; button_idx++) {
+        for (uint32_t button_idx = 1; button_idx <= TasmotaGlobal.devices_present; button_idx++) {
           if (bitRead(Web.light_shutter_button_mask, button_idx -1)) { continue; }  // Skip non-sequential light and/or shutter button
           bool set_button = ((button_idx <= MAX_BUTTON_TEXT) && strlen(GetWebButton(button_idx -1)));
           snprintf_P(stemp, sizeof(stemp), PSTR(" %d"), button_idx);
@@ -1457,7 +1456,7 @@ void HandleRoot(void) {
     if (TasmotaGlobal.light_type) {        // Any light - Show light button and slider(s)
       uint32_t light_device = LightDevice();
       uint32_t light_devices = LightDevices();
-      button_idx = light_device;
+      uint32_t button_idx = light_device;
 
       WSContentSend_P(HTTP_TABLE100);      // "<table style='width:100%%'>"
 
@@ -1904,11 +1903,14 @@ bool HandleRootStatusRefresh(void) {
         WSContentSend_P(PSTR("{t}<tr>"));
         uint32_t cols = Web.buttons_non_light_non_shutter;
         uint32_t fontsize = (cols < 5) ? 70 - (cols * 8) : 32;
-        for (uint32_t idx = 1; idx <= Web.buttons_non_light_non_shutter; idx++) {
-          if (bitRead(Web.light_shutter_button_mask, idx -1)) { continue; }  // Skip non-sequential shutter button
-          snprintf_P(svalue, sizeof(svalue), PSTR("%d"), bitRead(TasmotaGlobal.power, idx -1));
-          WSContentSend_P(HTTP_DEVICE_STATE, 100 / cols, (bitRead(TasmotaGlobal.power, idx -1)) ? PSTR("bold") : PSTR("normal"), fontsize,
-            (cols < 5) ? GetStateText(bitRead(TasmotaGlobal.power, idx -1)) : svalue);
+        uint32_t button_ptr = 0;
+        for (uint32_t button_idx = 1; button_idx <= TasmotaGlobal.devices_present; button_idx++) {
+          if (bitRead(Web.light_shutter_button_mask, button_idx -1)) { continue; }  // Skip non-sequential shutter button
+          bool power_state = bitRead(TasmotaGlobal.power, button_idx -1);
+          snprintf_P(svalue, sizeof(svalue), PSTR("%d"), power_state);
+          WSContentSend_P(HTTP_DEVICE_STATE, 100 / cols, (power_state) ? "bold" : "normal", fontsize, (cols < 5) ? GetStateText(power_state) : svalue);
+          button_ptr++;
+          if (button_ptr == Web.buttons_non_light_non_shutter) { break; }
         }
         WSContentSend_P(PSTR("</tr></table>"));
       }
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino b/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino
index 9d2381a61..a6ebf1e6f 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino
@@ -1852,57 +1852,61 @@ void DisplayLocalSensor(void)
 \*********************************************************************************************/
 
 void DisplayInitDriver(void) {
+  Settings->display_model = 0;
   XdspCall(FUNC_DISPLAY_INIT_DRIVER);
 
 //  AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Display model %d"), Settings->display_model);
 
-  if (Settings->display_model) {
-//    ApplyDisplayDimmer();  // Not allowed here. Way too early in init sequence. Global power state has not been set at this point in time
+  if (!Settings->display_model) { return; }
+
+//  AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("DSP: Model %d"), Settings->display_model);
+
+//  ApplyDisplayDimmer();  // Not allowed here. Way too early in init sequence. Global power state has not been set at this point in time
 
 #ifdef USE_MULTI_DISPLAY
-    Set_display(0);
+  Set_display(0);
 #endif // USE_MULTI_DISPLAY
 
-    if (renderer) {
-      renderer->setTextFont(Settings->display_font);
-      renderer->setTextSize(Settings->display_size);
-      // force opaque mode
-      renderer->setDrawMode(0);
+  if (renderer) {
+    renderer->setTextFont(Settings->display_font);
+    renderer->setTextSize(Settings->display_size);
+    // force opaque mode
+    renderer->setDrawMode(0);
 
-      for (uint32_t cnt = 0; cnt < (MAX_INDEXCOLORS - PREDEF_INDEXCOLORS); cnt++) {
-        index_colors[cnt] = 0;
-      }
+    for (uint32_t cnt = 0; cnt < (MAX_INDEXCOLORS - PREDEF_INDEXCOLORS); cnt++) {
+      index_colors[cnt] = 0;
     }
+  }
 
 #ifdef USE_DT_VARS
-    free_dt_vars();
+  free_dt_vars();
 #endif
 
 #ifdef USE_UFILESYS
-    Display_Text_From_File(DISP_BATCH_FILE);
+  Display_Text_From_File(DISP_BATCH_FILE);
 #endif
 
 #ifdef USE_GRAPH
-    for (uint8_t count = 0; count < NUM_GRAPHS; count++) { graph[count] = 0; }
+  for (uint8_t count = 0; count < NUM_GRAPHS; count++) { graph[count] = 0; }
 #endif
 
-    UpdateDevicesPresent(1);
-    if (!PinUsed(GPIO_BACKLIGHT)) {
-      if ((LT_PWM1 == TasmotaGlobal.light_type) &&  // Single PWM light channel
-          ((4 == Settings->display_model) ||        // ILI9341 legacy
-           (17 == Settings->display_model))         // Universal
-         ) {
-        UpdateDevicesPresent(-1);                   // Assume PWM channel is used for backlight
-      }
+  UpdateDevicesPresent(1);
+  if (!PinUsed(GPIO_BACKLIGHT)) {
+    if ((LT_PWM1 == TasmotaGlobal.light_type) &&  // Single PWM light channel
+        (4 == Settings->display_model)            // ILI9341 legacy
+//          ((4 == Settings->display_model) ||        // ILI9341 legacy
+//           (17 == Settings->display_model))         // Universal - Too invasive in case displays have no backlight pin
+        ) {
+      UpdateDevicesPresent(-1);                   // Assume PWM channel is used for backlight
     }
-    disp_device = TasmotaGlobal.devices_present;
+  }
+  disp_device = TasmotaGlobal.devices_present;
 
 #ifndef USE_DISPLAY_MODES1TO5
-    Settings->display_mode = 0;
+  Settings->display_mode = 0;
 #else
-    DisplayLogBufferInit();
+  DisplayLogBufferInit();
 #endif  // USE_DISPLAY_MODES1TO5
-  }
 }
 
 void DisplaySetPower(void) {

From 6fbf8c58f72ed22bfe46d42505f2eb7da4cb0866 Mon Sep 17 00:00:00 2001
From: Theo Arends <11044339+arendst@users.noreply.github.com>
Date: Sun, 15 Dec 2024 12:33:43 +0100
Subject: [PATCH 3/5] Fix GUI timing related divide by zero exception

---
 CHANGELOG.md                                  |  1 +
 .../xdrv_01_9_webserver.ino                   | 36 +++++++++----------
 .../tasmota_xdrv_driver/xdrv_13_display.ino   | 18 +++-------
 3 files changed, 24 insertions(+), 31 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e705f0533..f2885de68 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,7 @@ All notable changes to this project will be documented in this file.
 - MCP23xxx, PCF8574 and Shift595 power control when a display is configured regression from v14.3.0.7
 - Display DisplayMode adds a display device while not configured
 - GUI intermittent exception on screen updates due to flash access
+- GUI timing related divide by zero exception
 
 ### Removed
 
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino b/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino
index 5c9f55e5a..cf32f4e76 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino
@@ -1885,21 +1885,23 @@ bool HandleRootStatusRefresh(void) {
   XsnsXdrvCall(FUNC_WEB_SENSOR);
   WSContentSend_P(PSTR("</table>"));
 
-  if (!Settings->flag6.gui_no_state_text &&  // SetOption161 - (GUI) Disable display of state text (1)
-      TasmotaGlobal.devices_present) {
+  if (!Settings->flag6.gui_no_state_text) {          // SetOption161 - (GUI) Disable display of state text (1)
+    if (!Web.buttons_non_light_non_shutter) {        // Might still be zero on restart so chk if we have at least one 
+      WebGetDeviceCounts();
+    }
+    if ((Web.buttons_non_light_non_shutter > 0) &&
+       ( Web.buttons_non_light_non_shutter <= 8)) {  // We need at least one non light AND non shutter button
 
 #ifdef USE_SONOFF_IFAN
-    if (IsModuleIfan()) {
-      WSContentSend_P(PSTR("{t}<tr>"));
-      WSContentSend_P(HTTP_DEVICE_STATE, 36, (bitRead(TasmotaGlobal.power, 0)) ? PSTR("bold") : PSTR("normal"), 54, GetStateText(bitRead(TasmotaGlobal.power, 0)));
-      uint32_t fanspeed = GetFanspeed();
-      snprintf_P(svalue, sizeof(svalue), PSTR("%d"), fanspeed);
-      WSContentSend_P(HTTP_DEVICE_STATE, 64, (fanspeed) ? PSTR("bold") : PSTR("normal"), 54, (fanspeed) ? svalue : GetStateText(0));
-      WSContentSend_P(PSTR("</tr></table>"));
-    } else {
+      if (IsModuleIfan()) {
+        WSContentSend_P(PSTR("{t}<tr>"));
+        WSContentSend_P(HTTP_DEVICE_STATE, 36, (bitRead(TasmotaGlobal.power, 0)) ? PSTR("bold") : PSTR("normal"), 54, GetStateText(bitRead(TasmotaGlobal.power, 0)));
+        uint32_t fanspeed = GetFanspeed();
+        snprintf_P(svalue, sizeof(svalue), PSTR("%d"), fanspeed);
+        WSContentSend_P(HTTP_DEVICE_STATE, 64, (fanspeed) ? PSTR("bold") : PSTR("normal"), 54, (fanspeed) ? svalue : GetStateText(0));
+        WSContentSend_P(PSTR("</tr></table>"));
+      } else {
 #endif  // USE_SONOFF_IFAN
-
-      if (Web.buttons_non_light_non_shutter <= 8) {   // Any non light AND non shutter button
         WSContentSend_P(PSTR("{t}<tr>"));
         uint32_t cols = Web.buttons_non_light_non_shutter;
         uint32_t fontsize = (cols < 5) ? 70 - (cols * 8) : 32;
@@ -1908,17 +1910,15 @@ bool HandleRootStatusRefresh(void) {
           if (bitRead(Web.light_shutter_button_mask, button_idx -1)) { continue; }  // Skip non-sequential shutter button
           bool power_state = bitRead(TasmotaGlobal.power, button_idx -1);
           snprintf_P(svalue, sizeof(svalue), PSTR("%d"), power_state);
-          WSContentSend_P(HTTP_DEVICE_STATE, 100 / cols, (power_state) ? "bold" : "normal", fontsize, (cols < 5) ? GetStateText(power_state) : svalue);
+          WSContentSend_P(HTTP_DEVICE_STATE, 100 / cols, (power_state) ? PSTR("bold") : PSTR("normal"), fontsize, (cols < 5) ? GetStateText(power_state) : svalue);
           button_ptr++;
-          if (button_ptr == Web.buttons_non_light_non_shutter) { break; }
+          if (button_ptr >= Web.buttons_non_light_non_shutter) { break; }
         }
         WSContentSend_P(PSTR("</tr></table>"));
-      }
-
 #ifdef USE_SONOFF_IFAN
-    }
+      }
 #endif  // USE_SONOFF_IFAN
-
+    }
   }
 
   if (1 == Web.slider_update_time) {
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino b/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino
index a6ebf1e6f..732586d65 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino
@@ -1852,11 +1852,12 @@ void DisplayLocalSensor(void)
 \*********************************************************************************************/
 
 void DisplayInitDriver(void) {
-  Settings->display_model = 0;
+  uint32_t display_model = Settings->display_model;
+  Settings->display_model = 0;                    // Test if any display_model is available
   XdspCall(FUNC_DISPLAY_INIT_DRIVER);
-
-//  AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Display model %d"), Settings->display_model);
-
+  if (Settings->display_model) {                  // If model found keep using user configured one for backward compatibility
+    Settings->display_model = display_model;
+  }
   if (!Settings->display_model) { return; }
 
 //  AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("DSP: Model %d"), Settings->display_model);
@@ -1891,15 +1892,6 @@ void DisplayInitDriver(void) {
 #endif
 
   UpdateDevicesPresent(1);
-  if (!PinUsed(GPIO_BACKLIGHT)) {
-    if ((LT_PWM1 == TasmotaGlobal.light_type) &&  // Single PWM light channel
-        (4 == Settings->display_model)            // ILI9341 legacy
-//          ((4 == Settings->display_model) ||        // ILI9341 legacy
-//           (17 == Settings->display_model))         // Universal - Too invasive in case displays have no backlight pin
-        ) {
-      UpdateDevicesPresent(-1);                   // Assume PWM channel is used for backlight
-    }
-  }
   disp_device = TasmotaGlobal.devices_present;
 
 #ifndef USE_DISPLAY_MODES1TO5

From b0c505d17175974ea06567ccd2dae8866dbdfd7d Mon Sep 17 00:00:00 2001
From: Theo Arends <11044339+arendst@users.noreply.github.com>
Date: Sun, 15 Dec 2024 12:38:26 +0100
Subject: [PATCH 4/5] Fix display model

---
 tasmota/tasmota_xdrv_driver/xdrv_13_display.ino | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino b/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino
index 732586d65..5ba88cefa 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino
@@ -1853,9 +1853,9 @@ void DisplayLocalSensor(void)
 
 void DisplayInitDriver(void) {
   uint32_t display_model = Settings->display_model;
-  Settings->display_model = 0;                    // Test if any display_model is available
+  Settings->display_model = 0;                     // Test if any display_model is available
   XdspCall(FUNC_DISPLAY_INIT_DRIVER);
-  if (Settings->display_model) {                  // If model found keep using user configured one for backward compatibility
+  if (Settings->display_model && display_model) {  // If any model found keep using user configured one for backward compatibility
     Settings->display_model = display_model;
   }
   if (!Settings->display_model) { return; }

From da7473e07c39056421382f1cc29fe56d5ea0c671 Mon Sep 17 00:00:00 2001
From: Theo Arends <11044339+arendst@users.noreply.github.com>
Date: Sun, 15 Dec 2024 14:10:06 +0100
Subject: [PATCH 5/5] save a few bytes

---
 tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino b/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino
index cf32f4e76..1e4dfbf52 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino
@@ -1891,18 +1891,15 @@ bool HandleRootStatusRefresh(void) {
     }
     if ((Web.buttons_non_light_non_shutter > 0) &&
        ( Web.buttons_non_light_non_shutter <= 8)) {  // We need at least one non light AND non shutter button
-
+      WSContentSend_P(PSTR("{t}<tr>"));
 #ifdef USE_SONOFF_IFAN
       if (IsModuleIfan()) {
-        WSContentSend_P(PSTR("{t}<tr>"));
         WSContentSend_P(HTTP_DEVICE_STATE, 36, (bitRead(TasmotaGlobal.power, 0)) ? PSTR("bold") : PSTR("normal"), 54, GetStateText(bitRead(TasmotaGlobal.power, 0)));
         uint32_t fanspeed = GetFanspeed();
         snprintf_P(svalue, sizeof(svalue), PSTR("%d"), fanspeed);
         WSContentSend_P(HTTP_DEVICE_STATE, 64, (fanspeed) ? PSTR("bold") : PSTR("normal"), 54, (fanspeed) ? svalue : GetStateText(0));
-        WSContentSend_P(PSTR("</tr></table>"));
       } else {
 #endif  // USE_SONOFF_IFAN
-        WSContentSend_P(PSTR("{t}<tr>"));
         uint32_t cols = Web.buttons_non_light_non_shutter;
         uint32_t fontsize = (cols < 5) ? 70 - (cols * 8) : 32;
         uint32_t button_ptr = 0;
@@ -1914,10 +1911,10 @@ bool HandleRootStatusRefresh(void) {
           button_ptr++;
           if (button_ptr >= Web.buttons_non_light_non_shutter) { break; }
         }
-        WSContentSend_P(PSTR("</tr></table>"));
 #ifdef USE_SONOFF_IFAN
       }
 #endif  // USE_SONOFF_IFAN
+      WSContentSend_P(PSTR("</tr></table>"));
     }
   }