From 0465e0ae3aaec257618d095f7e2b28ef677dbe4a Mon Sep 17 00:00:00 2001
From: Costas Basdekis <costas.basdekis@gmail.com>
Date: Wed, 11 Nov 2020 06:39:23 +0000
Subject: [PATCH] Distinct runout states (#19965)

Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
---
 Marlin/Configuration.h                        | 22 +++++-
 Marlin/src/HAL/DUE/fastio.h                   |  3 +
 Marlin/src/HAL/ESP32/fastio.h                 |  3 +
 Marlin/src/feature/mmu2/mmu2.cpp              |  2 +-
 Marlin/src/feature/runout.h                   | 67 ++++++++++++++-----
 Marlin/src/gcode/feature/pause/M600.cpp       |  2 +-
 Marlin/src/inc/Conditionals_LCD.h             | 67 +++++++++++++++++++
 Marlin/src/inc/SanityCheck.h                  | 30 ++++-----
 .../extui/lib/anycubic_chiron/chiron_tft.cpp  |  4 +-
 .../anycubic_i3mega/anycubic_i3mega_lcd.cpp   |  6 +-
 .../screens/endstop_state_screen.cpp          |  4 +-
 Marlin/src/module/endstops.cpp                |  7 +-
 buildroot/tests/BIGTREE_GTR_V1_0-tests        | 15 ++++-
 buildroot/tests/mega2560-tests                | 15 +++++
 14 files changed, 202 insertions(+), 45 deletions(-)

diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h
index ea77b4e429..0036f40c4a 100644
--- a/Marlin/Configuration.h
+++ b/Marlin/Configuration.h
@@ -1186,9 +1186,27 @@
 #if ENABLED(FILAMENT_RUNOUT_SENSOR)
   #define FIL_RUNOUT_ENABLED_DEFAULT true // Enable the sensor on startup. Override with M412 followed by M500.
   #define NUM_RUNOUT_SENSORS   1          // Number of sensors, up to one per extruder. Define a FIL_RUNOUT#_PIN for each.
+
   #define FIL_RUNOUT_STATE     LOW        // Pin state indicating that filament is NOT present.
-  #define FIL_RUNOUT_PULLUP               // Use internal pullup for filament runout pins.
-  //#define FIL_RUNOUT_PULLDOWN           // Use internal pulldown for filament runout pins.
+  #define FIL_RUNOUT_PULL                 // Use internal pullup / pulldown for filament runout pins.
+
+  // Override individually if the runout sensors vary
+  //#define FIL_RUNOUT1_STATE LOW
+  //#define FIL_RUNOUT1_PULL
+  //#define FIL_RUNOUT2_STATE LOW
+  //#define FIL_RUNOUT2_PULL
+  //#define FIL_RUNOUT3_STATE LOW
+  //#define FIL_RUNOUT3_PULL
+  //#define FIL_RUNOUT4_STATE LOW
+  //#define FIL_RUNOUT4_PULL
+  //#define FIL_RUNOUT5_STATE LOW
+  //#define FIL_RUNOUT5_PULL
+  //#define FIL_RUNOUT6_STATE LOW
+  //#define FIL_RUNOUT6_PULL
+  //#define FIL_RUNOUT7_STATE LOW
+  //#define FIL_RUNOUT7_PULL
+  //#define FIL_RUNOUT8_STATE LOW
+  //#define FIL_RUNOUT8_PULL
 
   // Set one or more commands to execute on filament runout.
   // (After 'M412 H' Marlin will ask the host to handle the process.)
diff --git a/Marlin/src/HAL/DUE/fastio.h b/Marlin/src/HAL/DUE/fastio.h
index 119d0005af..5fb8b4d015 100644
--- a/Marlin/src/HAL/DUE/fastio.h
+++ b/Marlin/src/HAL/DUE/fastio.h
@@ -163,6 +163,9 @@
 #define SET_INPUT(IO)        _SET_INPUT(IO)
 // Set pin as input with pullup (wrapper)
 #define SET_INPUT_PULLUP(IO) do{ _SET_INPUT(IO); _PULLUP(IO, HIGH); }while(0)
+// Set pin as input with pulldown (substitution)
+#define SET_INPUT_PULLDOWN   SET_INPUT
+
 // Set pin as output (wrapper) -  reads the pin and sets the output to that value
 #define SET_OUTPUT(IO)       _SET_OUTPUT(IO)
 // Set pin as PWM
diff --git a/Marlin/src/HAL/ESP32/fastio.h b/Marlin/src/HAL/ESP32/fastio.h
index 2ded3a5f62..8db89dca12 100644
--- a/Marlin/src/HAL/ESP32/fastio.h
+++ b/Marlin/src/HAL/ESP32/fastio.h
@@ -52,6 +52,9 @@
 // Set pin as input with pullup wrapper
 #define SET_INPUT_PULLUP(IO)    do{ _SET_INPUT(IO); _PULLUP(IO, HIGH); }while(0)
 
+// Set pin as input with pulldown (substitution)
+#define SET_INPUT_PULLDOWN      SET_INPUT
+
 // Set pin as output wrapper
 #define SET_OUTPUT(IO)          do{ _SET_OUTPUT(IO); }while(0)
 
diff --git a/Marlin/src/feature/mmu2/mmu2.cpp b/Marlin/src/feature/mmu2/mmu2.cpp
index c5cf485850..d76476e719 100644
--- a/Marlin/src/feature/mmu2/mmu2.cpp
+++ b/Marlin/src/feature/mmu2/mmu2.cpp
@@ -163,7 +163,7 @@ uint8_t MMU2::get_current_tool() {
 }
 
 #if EITHER(PRUSA_MMU2_S_MODE, MMU_EXTRUDER_SENSOR)
-  #define FILAMENT_PRESENT() (READ(FIL_RUNOUT_PIN) != FIL_RUNOUT_STATE)
+  #define FILAMENT_PRESENT() (READ(FIL_RUNOUT1_PIN) != FIL_RUNOUT1_STATE)
 #endif
 
 void MMU2::mmu_loop() {
diff --git a/Marlin/src/feature/runout.h b/Marlin/src/feature/runout.h
index a5abf057d1..aa1ccc9cc5 100644
--- a/Marlin/src/feature/runout.h
+++ b/Marlin/src/feature/runout.h
@@ -149,18 +149,34 @@ class FilamentSensorBase {
 
   public:
     static inline void setup() {
-      #if ENABLED(FIL_RUNOUT_PULLUP)
-        #define INIT_RUNOUT_PIN(P) SET_INPUT_PULLUP(P)
-      #elif ENABLED(FIL_RUNOUT_PULLDOWN)
-        #define INIT_RUNOUT_PIN(P) SET_INPUT_PULLDOWN(P)
-      #else
-        #define INIT_RUNOUT_PIN(P) SET_INPUT(P)
+      #define _INIT_RUNOUT_PIN(P,S,U) do{ if (DISABLED(U)) SET_INPUT(P); else if (S) SET_INPUT_PULLDOWN(P); else SET_INPUT_PULLUP(P); }while(0)
+      #define  INIT_RUNOUT_PIN(N) _INIT_RUNOUT_PIN(FIL_RUNOUT##N##_PIN, FIL_RUNOUT##N##_STATE, FIL_RUNOUT##N##_PULL)
+      #if NUM_RUNOUT_SENSORS >= 1
+        INIT_RUNOUT_PIN(1);
       #endif
-
-      #define _INIT_RUNOUT(N) INIT_RUNOUT_PIN(FIL_RUNOUT##N##_PIN);
-      REPEAT_S(1, INCREMENT(NUM_RUNOUT_SENSORS), _INIT_RUNOUT)
-      #undef _INIT_RUNOUT
-      #undef INIT_RUNOUT_PIN
+      #if NUM_RUNOUT_SENSORS >= 2
+        INIT_RUNOUT_PIN(2);
+      #endif
+      #if NUM_RUNOUT_SENSORS >= 3
+        INIT_RUNOUT_PIN(3);
+      #endif
+      #if NUM_RUNOUT_SENSORS >= 4
+        INIT_RUNOUT_PIN(4);
+      #endif
+      #if NUM_RUNOUT_SENSORS >= 5
+        INIT_RUNOUT_PIN(5);
+      #endif
+      #if NUM_RUNOUT_SENSORS >= 6
+        INIT_RUNOUT_PIN(6);
+      #endif
+      #if NUM_RUNOUT_SENSORS >= 7
+        INIT_RUNOUT_PIN(7);
+      #endif
+      #if NUM_RUNOUT_SENSORS >= 8
+        INIT_RUNOUT_PIN(8);
+      #endif
+      #undef _INIT_RUNOUT_PIN
+      #undef  INIT_RUNOUT_PIN
     }
 
     // Return a bitmask of runout pin states
@@ -172,11 +188,32 @@ class FilamentSensorBase {
 
     // Return a bitmask of runout flag states (1 bits always indicates runout)
     static inline uint8_t poll_runout_states() {
-      return poll_runout_pins()
-        #if FIL_RUNOUT_STATE == LOW
-          ^ uint8_t(_BV(NUM_RUNOUT_SENSORS) - 1)
+      return poll_runout_pins() ^ uint8_t(0
+        #if NUM_RUNOUT_SENSORS >= 1
+          | (FIL_RUNOUT1_STATE ? 0 : _BV(1 - 1))
         #endif
-      ;
+        #if NUM_RUNOUT_SENSORS >= 2
+          | (FIL_RUNOUT2_STATE ? 0 : _BV(2 - 1))
+        #endif
+        #if NUM_RUNOUT_SENSORS >= 3
+          | (FIL_RUNOUT3_STATE ? 0 : _BV(3 - 1))
+        #endif
+        #if NUM_RUNOUT_SENSORS >= 4
+          | (FIL_RUNOUT4_STATE ? 0 : _BV(4 - 1))
+        #endif
+        #if NUM_RUNOUT_SENSORS >= 5
+          | (FIL_RUNOUT5_STATE ? 0 : _BV(5 - 1))
+        #endif
+        #if NUM_RUNOUT_SENSORS >= 6
+          | (FIL_RUNOUT6_STATE ? 0 : _BV(6 - 1))
+        #endif
+        #if NUM_RUNOUT_SENSORS >= 7
+          | (FIL_RUNOUT7_STATE ? 0 : _BV(7 - 1))
+        #endif
+        #if NUM_RUNOUT_SENSORS >= 8
+          | (FIL_RUNOUT8_STATE ? 0 : _BV(8 - 1))
+        #endif
+      );
     }
 };
 
diff --git a/Marlin/src/gcode/feature/pause/M600.cpp b/Marlin/src/gcode/feature/pause/M600.cpp
index 0af68333ca..e676ed38a4 100644
--- a/Marlin/src/gcode/feature/pause/M600.cpp
+++ b/Marlin/src/gcode/feature/pause/M600.cpp
@@ -88,7 +88,7 @@ void GcodeSuite::M600() {
                               // In this case, for duplicating modes set DXC_ext to the extruder that ran out.
       #if HAS_FILAMENT_SENSOR && NUM_RUNOUT_SENSORS > 1
         if (idex_is_duplicating())
-          DXC_ext = (READ(FIL_RUNOUT2_PIN) == FIL_RUNOUT_STATE) ? 1 : 0;
+          DXC_ext = (READ(FIL_RUNOUT2_PIN) == FIL_RUNOUT2_STATE) ? 1 : 0;
       #else
         DXC_ext = active_extruder;
       #endif
diff --git a/Marlin/src/inc/Conditionals_LCD.h b/Marlin/src/inc/Conditionals_LCD.h
index 98aa4afb8c..865365c0ee 100644
--- a/Marlin/src/inc/Conditionals_LCD.h
+++ b/Marlin/src/inc/Conditionals_LCD.h
@@ -675,6 +675,73 @@
   #define HAS_BED_PROBE 1
 #endif
 
+#if ENABLED(FILAMENT_RUNOUT_SENSOR)
+  #if NUM_RUNOUT_SENSORS >= 1
+    #ifndef FIL_RUNOUT1_STATE
+      #define FIL_RUNOUT1_STATE FIL_RUNOUT_STATE
+    #endif
+    #ifndef FIL_RUNOUT1_PULL
+      #define FIL_RUNOUT1_PULL FIL_RUNOUT_PULL
+    #endif
+  #endif
+  #if NUM_RUNOUT_SENSORS >= 2
+    #ifndef FIL_RUNOUT2_STATE
+      #define FIL_RUNOUT2_STATE FIL_RUNOUT_STATE
+    #endif
+    #ifndef FIL_RUNOUT2_PULL
+      #define FIL_RUNOUT2_PULL FIL_RUNOUT_PULL
+    #endif
+  #endif
+  #if NUM_RUNOUT_SENSORS >= 3
+    #ifndef FIL_RUNOUT3_STATE
+      #define FIL_RUNOUT3_STATE FIL_RUNOUT_STATE
+    #endif
+    #ifndef FIL_RUNOUT3_PULL
+      #define FIL_RUNOUT3_PULL FIL_RUNOUT_PULL
+    #endif
+  #endif
+  #if NUM_RUNOUT_SENSORS >= 4
+    #ifndef FIL_RUNOUT4_STATE
+      #define FIL_RUNOUT4_STATE FIL_RUNOUT_STATE
+    #endif
+    #ifndef FIL_RUNOUT4_PULL
+      #define FIL_RUNOUT4_PULL FIL_RUNOUT_PULL
+    #endif
+  #endif
+  #if NUM_RUNOUT_SENSORS >= 5
+    #ifndef FIL_RUNOUT5_STATE
+      #define FIL_RUNOUT5_STATE FIL_RUNOUT_STATE
+    #endif
+    #ifndef FIL_RUNOUT5_PULL
+      #define FIL_RUNOUT5_PULL FIL_RUNOUT_PULL
+    #endif
+  #endif
+  #if NUM_RUNOUT_SENSORS >= 6
+    #ifndef FIL_RUNOUT6_STATE
+      #define FIL_RUNOUT6_STATE FIL_RUNOUT_STATE
+    #endif
+    #ifndef FIL_RUNOUT6_PULL
+      #define FIL_RUNOUT6_PULL FIL_RUNOUT_PULL
+    #endif
+  #endif
+  #if NUM_RUNOUT_SENSORS >= 7
+    #ifndef FIL_RUNOUT7_STATE
+      #define FIL_RUNOUT7_STATE FIL_RUNOUT_STATE
+    #endif
+    #ifndef FIL_RUNOUT7_PULL
+      #define FIL_RUNOUT7_PULL FIL_RUNOUT_PULL
+    #endif
+  #endif
+  #if NUM_RUNOUT_SENSORS >= 8
+    #ifndef FIL_RUNOUT8_STATE
+      #define FIL_RUNOUT8_STATE FIL_RUNOUT_STATE
+    #endif
+    #ifndef FIL_RUNOUT8_PULL
+      #define FIL_RUNOUT8_PULL FIL_RUNOUT_PULL
+    #endif
+  #endif
+#endif // FILAMENT_RUNOUT_SENSOR
+
 #if EITHER(MESH_BED_LEVELING, AUTO_BED_LEVELING_UBL)
   #undef PROBE_MANUALLY
 #endif
diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h
index 3762d40c53..e2a79cd5ea 100644
--- a/Marlin/src/inc/SanityCheck.h
+++ b/Marlin/src/inc/SanityCheck.h
@@ -111,7 +111,7 @@
 #elif defined(FILAMENT_SENSOR)
   #error "FILAMENT_SENSOR is now FILAMENT_WIDTH_SENSOR. Please update your configuration."
 #elif defined(ENDSTOPPULLUP_FIL_RUNOUT)
-  #error "ENDSTOPPULLUP_FIL_RUNOUT is now FIL_RUNOUT_PULLUP. Please update your configuration."
+  #error "ENDSTOPPULLUP_FIL_RUNOUT is now FIL_RUNOUT_PULL. Please update your configuration."
 #elif defined(DISABLE_MAX_ENDSTOPS) || defined(DISABLE_MIN_ENDSTOPS)
   #error "DISABLE_MAX_ENDSTOPS and DISABLE_MIN_ENDSTOPS deprecated. Use individual USE_*_PLUG options instead."
 #elif defined(LANGUAGE_INCLUDE)
@@ -660,8 +660,6 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
 
 #if BOTH(ENDSTOPPULLUPS, ENDSTOPPULLDOWNS)
   #error "Enable only one of ENDSTOPPULLUPS or ENDSTOPPULLDOWNS."
-#elif BOTH(FIL_RUNOUT_PULLUP, FIL_RUNOUT_PULLDOWN)
-  #error "Enable only one of FIL_RUNOUT_PULLUP or FIL_RUNOUT_PULLDOWN."
 #elif BOTH(ENDSTOPPULLUP_XMAX, ENDSTOPPULLDOWN_XMAX)
   #error "Enable only one of ENDSTOPPULLUP_X_MAX or ENDSTOPPULLDOWN_X_MAX."
 #elif BOTH(ENDSTOPPULLUP_YMAX, ENDSTOPPULLDOWN_YMAX)
@@ -814,18 +812,20 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
     #error "FILAMENT_RUNOUT_SENSOR requires FIL_RUNOUT_PIN."
   #elif NUM_RUNOUT_SENSORS > E_STEPPERS
     #error "NUM_RUNOUT_SENSORS cannot exceed the number of E steppers."
-  #elif NUM_RUNOUT_SENSORS > 1 && !PIN_EXISTS(FIL_RUNOUT2)
-    #error "FILAMENT_RUNOUT_SENSOR with NUM_RUNOUT_SENSORS > 1 requires FIL_RUNOUT2_PIN."
-  #elif NUM_RUNOUT_SENSORS > 2 && !PIN_EXISTS(FIL_RUNOUT3)
-    #error "FILAMENT_RUNOUT_SENSOR with NUM_RUNOUT_SENSORS > 2 requires FIL_RUNOUT3_PIN."
-  #elif NUM_RUNOUT_SENSORS > 3 && !PIN_EXISTS(FIL_RUNOUT4)
-    #error "FILAMENT_RUNOUT_SENSOR with NUM_RUNOUT_SENSORS > 3 requires FIL_RUNOUT4_PIN."
-  #elif NUM_RUNOUT_SENSORS > 4 && !PIN_EXISTS(FIL_RUNOUT5)
-    #error "FILAMENT_RUNOUT_SENSOR with NUM_RUNOUT_SENSORS > 4 requires FIL_RUNOUT5_PIN."
-  #elif NUM_RUNOUT_SENSORS > 5 && !PIN_EXISTS(FIL_RUNOUT6)
-    #error "FILAMENT_RUNOUT_SENSOR with NUM_RUNOUT_SENSORS > 5 requires FIL_RUNOUT6_PIN."
-  #elif NONE(SDSUPPORT, PRINTJOB_TIMER_AUTOSTART)
-    #error "FILAMENT_RUNOUT_SENSOR requires SDSUPPORT or PRINTJOB_TIMER_AUTOSTART."
+  #elif NUM_RUNOUT_SENSORS >= 2 && !PIN_EXISTS(FIL_RUNOUT2)
+    #error "FIL_RUNOUT2_PIN is required with NUM_RUNOUT_SENSORS >= 2."
+  #elif NUM_RUNOUT_SENSORS >= 3 && !PIN_EXISTS(FIL_RUNOUT3)
+    #error "FIL_RUNOUT3_PIN is required with NUM_RUNOUT_SENSORS >= 3."
+  #elif NUM_RUNOUT_SENSORS >= 4 && !PIN_EXISTS(FIL_RUNOUT4)
+    #error "FIL_RUNOUT4_PIN is required with NUM_RUNOUT_SENSORS >= 4."
+  #elif NUM_RUNOUT_SENSORS >= 5 && !PIN_EXISTS(FIL_RUNOUT5)
+    #error "FIL_RUNOUT5_PIN is required with NUM_RUNOUT_SENSORS >= 5."
+  #elif NUM_RUNOUT_SENSORS >= 6 && !PIN_EXISTS(FIL_RUNOUT6)
+    #error "FIL_RUNOUT6_PIN is required with NUM_RUNOUT_SENSORS >= 6."
+  #elif NUM_RUNOUT_SENSORS >= 7 && !PIN_EXISTS(FIL_RUNOUT7)
+    #error "FIL_RUNOUT7_PIN is required with NUM_RUNOUT_SENSORS >= 7."
+  #elif NUM_RUNOUT_SENSORS >= 8 && !PIN_EXISTS(FIL_RUNOUT8)
+    #error "FIL_RUNOUT8_PIN is required with NUM_RUNOUT_SENSORS >= 8."
   #elif FILAMENT_RUNOUT_DISTANCE_MM < 0
     #error "FILAMENT_RUNOUT_DISTANCE_MM must be greater than or equal to zero."
   #elif DISABLED(ADVANCED_PAUSE_FEATURE)
diff --git a/Marlin/src/lcd/extui/lib/anycubic_chiron/chiron_tft.cpp b/Marlin/src/lcd/extui/lib/anycubic_chiron/chiron_tft.cpp
index a1d1217d6e..4725afb8bc 100644
--- a/Marlin/src/lcd/extui/lib/anycubic_chiron/chiron_tft.cpp
+++ b/Marlin/src/lcd/extui/lib/anycubic_chiron/chiron_tft.cpp
@@ -76,8 +76,8 @@ namespace Anycubic {
     #endif
 
     // Filament runout is handled by Marlin settings in Configuration.h
-    // set FIL_RUNOUT_STATE HIGH  // Pin state indicating that filament is NOT present.
-    // enable FIL_RUNOUT_PULLUP
+    // opt_set    FIL_RUNOUT_STATE HIGH  // Pin state indicating that filament is NOT present.
+    // opt_enable FIL_RUNOUT_PULL
 
     TFTSer.begin(115200);
 
diff --git a/Marlin/src/lcd/extui/lib/anycubic_i3mega/anycubic_i3mega_lcd.cpp b/Marlin/src/lcd/extui/lib/anycubic_i3mega/anycubic_i3mega_lcd.cpp
index 2aaa0d0e33..6d15d937b9 100644
--- a/Marlin/src/lcd/extui/lib/anycubic_i3mega/anycubic_i3mega_lcd.cpp
+++ b/Marlin/src/lcd/extui/lib/anycubic_i3mega/anycubic_i3mega_lcd.cpp
@@ -87,7 +87,7 @@ void AnycubicTFTClass::OnSetup() {
     SET_INPUT_PULLUP(SD_DETECT_PIN);
   #endif
   #if ENABLED(FILAMENT_RUNOUT_SENSOR)
-    SET_INPUT_PULLUP(FIL_RUNOUT_PIN);
+    SET_INPUT_PULLUP(FIL_RUNOUT1_PIN);
   #endif
 
   mediaPrintingState = AMPRINTSTATE_NOT_PRINTING;
@@ -935,7 +935,7 @@ void AnycubicTFTClass::DoFilamentRunoutCheck() {
   #if ENABLED(FILAMENT_RUNOUT_SENSOR)
     // NOTE: ExtUI::getFilamentRunoutState() only returns the runout state if the job is printing
     // we want to actually check the status of the pin here, regardless of printstate
-    if (READ(FIL_RUNOUT_PIN)) {
+    if (READ(FIL_RUNOUT1_PIN)) {
       if (mediaPrintingState == AMPRINTSTATE_PRINTING || mediaPrintingState == AMPRINTSTATE_PAUSED || mediaPrintingState == AMPRINTSTATE_PAUSE_REQUESTED) {
         // play tone to indicate filament is out
         ExtUI::injectCommands_P(PSTR("\nM300 P200 S1567\nM300 P200 S1174\nM300 P200 S1567\nM300 P200 S1174\nM300 P2000 S1567"));
@@ -983,7 +983,7 @@ void AnycubicTFTClass::PausePrint() {
 void AnycubicTFTClass::ResumePrint() {
   #if ENABLED(SDSUPPORT)
     #if ENABLED(FILAMENT_RUNOUT_SENSOR)
-      if (READ(FIL_RUNOUT_PIN)) {
+      if (READ(FIL_RUNOUT1_PIN)) {
         #if ENABLED(ANYCUBIC_LCD_DEBUG)
           SERIAL_ECHOLNPGM("TFT Serial Debug: Resume Print with filament sensor still tripped... ");
         #endif
diff --git a/Marlin/src/lcd/extui/lib/ftdi_eve_touch_ui/screens/endstop_state_screen.cpp b/Marlin/src/lcd/extui/lib/ftdi_eve_touch_ui/screens/endstop_state_screen.cpp
index 41f72e8e53..09561edc02 100644
--- a/Marlin/src/lcd/extui/lib/ftdi_eve_touch_ui/screens/endstop_state_screen.cpp
+++ b/Marlin/src/lcd/extui/lib/ftdi_eve_touch_ui/screens/endstop_state_screen.cpp
@@ -92,12 +92,12 @@ void EndstopStatesScreen::onRedraw(draw_mode_t) {
     PIN_DISABLED(5, 3, PSTR(STR_Z_MIN), Z_MIN)
   #endif
   #if ENABLED(FILAMENT_RUNOUT_SENSOR) && PIN_EXISTS(FIL_RUNOUT)
-    PIN_ENABLED (1, 4, GET_TEXT_F(MSG_RUNOUT_1), FIL_RUNOUT, FIL_RUNOUT_STATE)
+    PIN_ENABLED (1, 4, GET_TEXT_F(MSG_RUNOUT_1), FIL_RUNOUT, FIL_RUNOUT1_STATE)
   #else
     PIN_DISABLED(1, 4, GET_TEXT_F(MSG_RUNOUT_1), FIL_RUNOUT)
   #endif
   #if BOTH(HAS_MULTI_EXTRUDER, FILAMENT_RUNOUT_SENSOR) && PIN_EXISTS(FIL_RUNOUT2)
-    PIN_ENABLED (3, 4, GET_TEXT_F(MSG_RUNOUT_2), FIL_RUNOUT2, FIL_RUNOUT_STATE)
+    PIN_ENABLED (3, 4, GET_TEXT_F(MSG_RUNOUT_2), FIL_RUNOUT2, FIL_RUNOUT2_STATE)
   #else
     PIN_DISABLED(3, 4, GET_TEXT_F(MSG_RUNOUT_2), FIL_RUNOUT2)
   #endif
diff --git a/Marlin/src/module/endstops.cpp b/Marlin/src/module/endstops.cpp
index 825194cf45..697ced7833 100644
--- a/Marlin/src/module/endstops.cpp
+++ b/Marlin/src/module/endstops.cpp
@@ -459,18 +459,19 @@ void _O2 Endstops::report_states() {
   #endif
   #if HAS_FILAMENT_SENSOR
     #if NUM_RUNOUT_SENSORS == 1
-      print_es_state(READ(FIL_RUNOUT_PIN) != FIL_RUNOUT_STATE, PSTR(STR_FILAMENT_RUNOUT_SENSOR));
+      print_es_state(READ(FIL_RUNOUT1_PIN) != FIL_RUNOUT1_STATE, PSTR(STR_FILAMENT_RUNOUT_SENSOR));
     #else
-      #define _CASE_RUNOUT(N) case N: pin = FIL_RUNOUT##N##_PIN; break;
+      #define _CASE_RUNOUT(N) case N: pin = FIL_RUNOUT##N##_PIN; state = FIL_RUNOUT##N##_STATE; break;
       LOOP_S_LE_N(i, 1, NUM_RUNOUT_SENSORS) {
         pin_t pin;
+        uint8_t state;
         switch (i) {
           default: continue;
           REPEAT_S(1, INCREMENT(NUM_RUNOUT_SENSORS), _CASE_RUNOUT)
         }
         SERIAL_ECHOPGM(STR_FILAMENT_RUNOUT_SENSOR);
         if (i > 1) SERIAL_CHAR(' ', '0' + i);
-        print_es_state(extDigitalRead(pin) != FIL_RUNOUT_STATE);
+        print_es_state(extDigitalRead(pin) != state);
       }
       #undef _CASE_RUNOUT
     #endif
diff --git a/buildroot/tests/BIGTREE_GTR_V1_0-tests b/buildroot/tests/BIGTREE_GTR_V1_0-tests
index e8d47562aa..1db0bcffd2 100644
--- a/buildroot/tests/BIGTREE_GTR_V1_0-tests
+++ b/buildroot/tests/BIGTREE_GTR_V1_0-tests
@@ -24,7 +24,20 @@ opt_set E2_AUTO_FAN_PIN PC12
 opt_set X_DRIVER_TYPE TMC2208
 opt_set Y_DRIVER_TYPE TMC2130
 opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER
-exec_test $1 $2 "BigTreeTech GTR 8 Extruders with Auto-Fan and Mixed TMC Drivers"
+opt_enable FILAMENT_RUNOUT_SENSOR NOZZLE_PARK_FEATURE ADVANCED_PAUSE_FEATURE
+opt_set FIL_RUNOUT_PIN 3
+opt_set FIL_RUNOUT2_PIN 4
+opt_set FIL_RUNOUT3_PIN 5
+opt_set FIL_RUNOUT4_PIN 6
+opt_set FIL_RUNOUT5_PIN 7
+opt_set FIL_RUNOUT6_PIN 8
+opt_set FIL_RUNOUT7_PIN 9
+opt_set FIL_RUNOUT8_PIN 10
+opt_set FIL_RUNOUT4_STATE HIGH
+opt_enable FIL_RUNOUT4_PULL
+opt_set FIL_RUNOUT8_STATE HIGH
+opt_enable FIL_RUNOUT8_PULL
+exec_test $1 $2 "BigTreeTech GTR 8 Extruders with Auto-Fan, Mixed TMC Drivers, and Runout Sensors with distinct states"
 
 restore_configs
 opt_set MOTHERBOARD BOARD_BTT_GTR_V1_0
diff --git a/buildroot/tests/mega2560-tests b/buildroot/tests/mega2560-tests
index a6902b9d14..9ae43dcdb5 100755
--- a/buildroot/tests/mega2560-tests
+++ b/buildroot/tests/mega2560-tests
@@ -75,6 +75,21 @@ opt_enable ZONESTAR_LCD Z_PROBE_SERVO_NR Z_SERVO_ANGLES DEACTIVATE_SERVOS_AFTER_
            FILAMENT_RUNOUT_SENSOR NOZZLE_PARK_FEATURE ADVANCED_PAUSE_FEATURE Z_SAFE_HOMING
 exec_test $1 $2 "RAMPS | ZONESTAR + Chinese | MMU2 | Servo | 3-Point + Debug | G38 ..."
 
+#
+# 5 runout sensors with distinct states
+#
+restore_configs
+opt_set EXTRUDERS 5
+opt_set NUM_SERVOS 1
+opt_enable ZONESTAR_LCD Z_PROBE_SERVO_NR Z_SERVO_ANGLES DEACTIVATE_SERVOS_AFTER_MOVE BOOT_MARLIN_LOGO_ANIMATED \
+           AUTO_BED_LEVELING_3POINT DEBUG_LEVELING_FEATURE EEPROM_SETTINGS EEPROM_CHITCHAT M114_DETAIL \
+           NO_VOLUMETRICS EXTENDED_CAPABILITIES_REPORT AUTO_REPORT_TEMPERATURES AUTOTEMP G38_PROBE_TARGET JOYSTICK \
+           PRUSA_MMU2 MMU2_MENUS PRUSA_MMU2_S_MODE DIRECT_STEPPING DETECT_BROKEN_ENDSTOP \
+           FILAMENT_RUNOUT_SENSOR NOZZLE_PARK_FEATURE ADVANCED_PAUSE_FEATURE Z_SAFE_HOMING FIL_RUNOUT3_PULL
+opt_set MIXING_STEPPERS 5
+opt_set FIL_RUNOUT3_STATE HIGH
+exec_test $1 $2 "Multiple runout sensors (x5) | Distinct runout states"
+
 #
 # Test MINIRAMBO with PWM_MOTOR_CURRENT and many features
 #