From 9a194826e3e424e404e95443976f1bdfb1d2b245 Mon Sep 17 00:00:00 2001
From: John Lagonikas <39417467+zeleps@users.noreply.github.com>
Date: Mon, 3 Jan 2022 18:11:39 +0200
Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20M81=20D=20/=20S=20-=20Power-off=20D?=
 =?UTF-8?q?elay=20(#23396)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Marlin/Configuration.h                | 11 +++--
 Marlin/src/MarlinCore.cpp             |  4 ++
 Marlin/src/feature/power.cpp          | 70 +++++++++++++++++++++------
 Marlin/src/feature/power.h            | 40 ++++++++++-----
 Marlin/src/gcode/control/M80_M81.cpp  | 30 +++++++++---
 Marlin/src/inc/SanityCheck.h          |  2 +
 Marlin/src/lcd/e3v2/enhanced/dwin.cpp |  4 +-
 Marlin/src/module/temperature.h       |  7 ++-
 buildroot/tests/rambo                 |  4 +-
 9 files changed, 130 insertions(+), 42 deletions(-)

diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h
index 236c6a8a546..c66c8eff95c 100644
--- a/Marlin/Configuration.h
+++ b/Marlin/Configuration.h
@@ -374,6 +374,9 @@
   //#define PSU_DEFAULT_OFF         // Keep power off until enabled directly with M80
   //#define PSU_POWERUP_DELAY 250   // (ms) Delay for the PSU to warm up to full power
 
+  //#define POWER_OFF_TIMER               // Enable M81 D<seconds> to power off after a delay
+  //#define POWER_OFF_WAIT_FOR_COOLDOWN   // Enable M81 S to power off only after cooldown
+
   //#define PSU_POWERUP_GCODE  "M355 S1"  // G-code to run after power-on (e.g., case light on)
   //#define PSU_POWEROFF_GCODE "M355 S0"  // G-code to run before power-off (e.g., case light off)
 
@@ -384,12 +387,14 @@
     #define AUTO_POWER_CONTROLLERFAN
     #define AUTO_POWER_CHAMBER_FAN
     #define AUTO_POWER_COOLER_FAN
-    //#define AUTO_POWER_E_TEMP        50 // (°C) Turn on PSU if any extruder is over this temperature
-    //#define AUTO_POWER_CHAMBER_TEMP  30 // (°C) Turn on PSU if the chamber is over this temperature
-    //#define AUTO_POWER_COOLER_TEMP   26 // (°C) Turn on PSU if the cooler is over this temperature
     #define POWER_TIMEOUT              30 // (s) Turn off power if the machine is idle for this duration
     //#define POWER_OFF_DELAY          60 // (s) Delay of poweroff after M81 command. Useful to let fans run for extra time.
   #endif
+  #if EITHER(AUTO_POWER_CONTROL, POWER_OFF_WAIT_FOR_COOLDOWN) 
+    //#define AUTO_POWER_E_TEMP        50 // (°C) PSU on if any extruder is over this temperature
+    //#define AUTO_POWER_CHAMBER_TEMP  30 // (°C) PSU on if the chamber is over this temperature
+    //#define AUTO_POWER_COOLER_TEMP   26 // (°C) PSU on if the cooler is over this temperature
+  #endif
 #endif
 
 //===========================================================================
diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp
index 16cd43443c1..2c290306693 100644
--- a/Marlin/src/MarlinCore.cpp
+++ b/Marlin/src/MarlinCore.cpp
@@ -1652,6 +1652,10 @@ void loop() {
 
     queue.advance();
 
+    #if EITHER(POWER_OFF_TIMER, POWER_OFF_WAIT_FOR_COOLDOWN)
+      powerManager.checkAutoPowerOff();
+    #endif
+
     endstops.event_handler();
 
     TERN_(HAS_TFT_LVGL_UI, printer_state_polling());
diff --git a/Marlin/src/feature/power.cpp b/Marlin/src/feature/power.cpp
index fabe35b989c..480cb91a918 100644
--- a/Marlin/src/feature/power.cpp
+++ b/Marlin/src/feature/power.cpp
@@ -27,7 +27,9 @@
 #include "../inc/MarlinConfig.h"
 
 #include "power.h"
+#include "../module/planner.h"
 #include "../module/stepper.h"
+#include "../module/temperature.h"
 #include "../MarlinCore.h"
 
 #if ENABLED(PS_OFF_SOUND)
@@ -75,6 +77,10 @@ void Power::power_on() {
 
   if (psu_on) return;
 
+  #if EITHER(POWER_OFF_TIMER, POWER_OFF_WAIT_FOR_COOLDOWN)
+    cancelAutoPowerOff();
+  #endif
+
   OUT_WRITE(PS_ON_PIN, PSU_ACTIVE_STATE);
   psu_on = true;
   safe_delay(PSU_POWERUP_DELAY);
@@ -89,7 +95,6 @@ void Power::power_on() {
 /**
  * Power off if the power is currently on.
  * Processes any PSU_POWEROFF_GCODE and makes a PS_OFF_SOUND if enabled.
- *
  */
 void Power::power_off() {
   if (!psu_on) return;
@@ -104,8 +109,56 @@ void Power::power_off() {
 
   OUT_WRITE(PS_ON_PIN, !PSU_ACTIVE_STATE);
   psu_on = false;
+
+  #if EITHER(POWER_OFF_TIMER, POWER_OFF_WAIT_FOR_COOLDOWN)
+    cancelAutoPowerOff();
+  #endif
 }
 
+#if EITHER(AUTO_POWER_CONTROL, POWER_OFF_WAIT_FOR_COOLDOWN)
+
+  bool Power::is_cooling_needed() {
+    #if HAS_HOTEND && AUTO_POWER_E_TEMP
+      HOTEND_LOOP() if (thermalManager.degHotend(e) >= (AUTO_POWER_E_TEMP)) return true;
+    #endif
+
+    #if HAS_HEATED_CHAMBER && AUTO_POWER_CHAMBER_TEMP
+      if (thermalManager.degChamber() >= (AUTO_POWER_CHAMBER_TEMP)) return true;
+    #endif
+
+    #if HAS_COOLER && AUTO_POWER_COOLER_TEMP
+      if (thermalManager.degCooler() >= (AUTO_POWER_COOLER_TEMP)) return true;
+    #endif
+
+    return false;
+  }
+
+#endif
+
+#if EITHER(POWER_OFF_TIMER, POWER_OFF_WAIT_FOR_COOLDOWN)
+
+  #if ENABLED(POWER_OFF_TIMER)
+    millis_t Power::power_off_time = 0;
+    void Power::setPowerOffTimer(const millis_t delay_ms) { power_off_time = millis() + delay_ms; }
+  #endif
+
+  #if ENABLED(POWER_OFF_WAIT_FOR_COOLDOWN)
+    bool Power::power_off_on_cooldown = false;
+    void Power::setPowerOffOnCooldown(const bool ena) { power_off_on_cooldown = ena; }
+  #endif
+
+  void Power::cancelAutoPowerOff() {
+    TERN_(POWER_OFF_TIMER, power_off_time = 0);
+    TERN_(POWER_OFF_WAIT_FOR_COOLDOWN, power_off_on_cooldown = false);
+  }
+
+  void Power::checkAutoPowerOff() {
+    if (TERN0(POWER_OFF_WAIT_FOR_COOLDOWN, power_off_on_cooldown && is_cooling_needed())) return;
+    if (TERN0(POWER_OFF_TIMER, power_off_time && PENDING(millis(), power_off_time))) return;
+    power_off();
+  }
+
+#endif // POWER_OFF_TIMER || POWER_OFF_WAIT_FOR_COOLDOWN
 
 #if ENABLED(AUTO_POWER_CONTROL)
 
@@ -149,19 +202,7 @@ void Power::power_off() {
 
     if (TERN0(HAS_HEATED_BED, thermalManager.degTargetBed() > 0 || thermalManager.temp_bed.soft_pwm_amount > 0)) return true;
 
-    #if HAS_HOTEND && AUTO_POWER_E_TEMP
-      HOTEND_LOOP() if (thermalManager.degHotend(e) >= (AUTO_POWER_E_TEMP)) return true;
-    #endif
-
-    #if HAS_HEATED_CHAMBER && AUTO_POWER_CHAMBER_TEMP
-      if (thermalManager.degChamber() >= (AUTO_POWER_CHAMBER_TEMP)) return true;
-    #endif
-
-    #if HAS_COOLER && AUTO_POWER_COOLER_TEMP
-      if (thermalManager.degCooler() >= (AUTO_POWER_COOLER_TEMP)) return true;
-    #endif
-
-    return false;
+    return is_cooling_needed();
   }
 
   /**
@@ -193,7 +234,6 @@ void Power::power_off() {
 
     /**
      * Power off with a delay. Power off is triggered by check() after the delay.
-     *
      */
     void Power::power_off_soon() {
       lastPowerOn = millis() - SEC_TO_MS(POWER_TIMEOUT) + SEC_TO_MS(POWER_OFF_DELAY);
diff --git a/Marlin/src/feature/power.h b/Marlin/src/feature/power.h
index 42c2c849428..38f7ed6ce7c 100644
--- a/Marlin/src/feature/power.h
+++ b/Marlin/src/feature/power.h
@@ -36,21 +36,37 @@ class Power {
     static void init();
     static void power_on();
     static void power_off();
+    
+    #if EITHER(POWER_OFF_TIMER, POWER_OFF_WAIT_FOR_COOLDOWN)
+      #if ENABLED(POWER_OFF_TIMER)
+        static millis_t power_off_time;
+        static void setPowerOffTimer(const millis_t delay_ms);
+      #endif
+      #if ENABLED(POWER_OFF_WAIT_FOR_COOLDOWN)
+        static bool power_off_on_cooldown;
+        static void setPowerOffOnCooldown(const bool ena);
+      #endif
+      static void cancelAutoPowerOff();
+      static void checkAutoPowerOff();
+    #endif
 
-  #if ENABLED(AUTO_POWER_CONTROL) && POWER_OFF_DELAY > 0
-    static void power_off_soon();
-  #else
-    static void power_off_soon() { power_off(); }
-  #endif
+    #if ENABLED(AUTO_POWER_CONTROL) && POWER_OFF_DELAY > 0
+      static void power_off_soon();
+    #else
+      static void power_off_soon() { power_off(); }
+    #endif
 
-  #if ENABLED(AUTO_POWER_CONTROL)
-    static void check(const bool pause);
+    #if ENABLED(AUTO_POWER_CONTROL)
+      static void check(const bool pause);
 
-    private:
-      static millis_t lastPowerOn;
-      static bool is_power_needed();
-
-  #endif
+      private:
+        static millis_t lastPowerOn;
+        static bool is_power_needed();
+        static bool is_cooling_needed();
+    #elif ENABLED(POWER_OFF_WAIT_FOR_COOLDOWN)
+      private:
+        static bool is_cooling_needed();
+    #endif
 };
 
 extern Power powerManager;
diff --git a/Marlin/src/gcode/control/M80_M81.cpp b/Marlin/src/gcode/control/M80_M81.cpp
index b8be9daa401..dc9b09c4e1b 100644
--- a/Marlin/src/gcode/control/M80_M81.cpp
+++ b/Marlin/src/gcode/control/M80_M81.cpp
@@ -79,13 +79,31 @@ void GcodeSuite::M81() {
 
   print_job_timer.stop();
 
-  #if HAS_FAN
-    #if ENABLED(PROBING_FANS_OFF)
-      thermalManager.fans_paused = false;
-      ZERO(thermalManager.saved_fan_speed);
-    #endif
+  #if BOTH(HAS_FAN, PROBING_FANS_OFF)
+    thermalManager.fans_paused = false;
+    ZERO(thermalManager.saved_fan_speed);
   #endif
 
+  LCD_MESSAGE_F(MACHINE_NAME " " STR_OFF ".");
+
+  bool delayed_power_off = false;
+
+  #if ENABLED(POWER_OFF_TIMER)
+    if (parser.seenval('D')) {
+      delayed_power_off = true;
+      powerManager.setPowerOffTimer(SEC_TO_MS(parser.value_ushort()));
+    }
+  #endif
+
+  #if ENABLED(POWER_OFF_WAIT_FOR_COOLDOWN)
+    if (parser.boolval('S')) {
+      delayed_power_off = true;
+      powerManager.setPowerOffOnCooldown(true);
+    }
+  #endif
+
+  if (delayed_power_off) return;
+
   safe_delay(1000); // Wait 1 second before switching off
 
   #if HAS_SUICIDE
@@ -93,6 +111,4 @@ void GcodeSuite::M81() {
   #elif ENABLED(PSU_CONTROL)
     powerManager.power_off_soon();
   #endif
-
-  LCD_MESSAGE_F(MACHINE_NAME " " STR_OFF ".");
 }
diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h
index f884046a96d..06e0dfbb426 100644
--- a/Marlin/src/inc/SanityCheck.h
+++ b/Marlin/src/inc/SanityCheck.h
@@ -3577,6 +3577,8 @@ static_assert(_PLUS_TEST(4), "HOMING_FEEDRATE_MM_M values must be positive.");
     #error "PSU_CONTROL requires PS_ON_PIN."
   #elif POWER_OFF_DELAY < 0
     #error "POWER_OFF_DELAY must be a positive value."
+  #elif ENABLED(POWER_OFF_WAIT_FOR_COOLDOWN) && !(defined(AUTO_POWER_E_TEMP) || defined(AUTO_POWER_CHAMBER_TEMP) || defined(AUTO_POWER_COOLER_TEMP))
+    #error "POWER_OFF_WAIT_FOR_COOLDOWN requires AUTO_POWER_E_TEMP, AUTO_POWER_CHAMBER_TEMP, and/or AUTO_POWER_COOLER_TEMP."
   #endif
 #endif
 
diff --git a/Marlin/src/lcd/e3v2/enhanced/dwin.cpp b/Marlin/src/lcd/e3v2/enhanced/dwin.cpp
index 4e730d04e72..3612fb6bd19 100644
--- a/Marlin/src/lcd/e3v2/enhanced/dwin.cpp
+++ b/Marlin/src/lcd/e3v2/enhanced/dwin.cpp
@@ -481,7 +481,7 @@ void Popup_window_PauseOrStop() {
     Draw_Select_Highlight(true);
   DWIN_UpdateLCD();
   }
-  else 
+  else
     DWIN_Popup_ConfirmCancel(ICON_BLTouch, select_print.now == PRINT_PAUSE_RESUME ? GET_TEXT_F(MSG_PAUSE_PRINT) : GET_TEXT_F(MSG_STOP_PRINT));
 }
 
@@ -2015,7 +2015,7 @@ void HMI_LockScreen() {
 #endif
 
 //=============================================================================
-// NEW MENU SUBSYSTEM 
+// NEW MENU SUBSYSTEM
 //=============================================================================
 
 // On click functions
diff --git a/Marlin/src/module/temperature.h b/Marlin/src/module/temperature.h
index e3515f0db87..089694ab4ad 100644
--- a/Marlin/src/module/temperature.h
+++ b/Marlin/src/module/temperature.h
@@ -993,7 +993,12 @@ class Temperature {
       static int16_t read_max_tc(TERN_(HAS_MULTI_MAX_TC, const uint8_t hindex=0));
     #endif
 
-    static void update_autofans();
+    #if HAS_AUTO_FAN
+      #if ENABLED(POWER_OFF_WAIT_FOR_COOLDOWN)
+        static bool autofans_on;
+      #endif
+      static void update_autofans();
+    #endif
 
     #if HAS_HOTEND
       static float get_pid_output_hotend(const uint8_t e);
diff --git a/buildroot/tests/rambo b/buildroot/tests/rambo
index a54c04eeb21..92e8bc2b5f4 100755
--- a/buildroot/tests/rambo
+++ b/buildroot/tests/rambo
@@ -14,7 +14,7 @@ opt_set MOTHERBOARD BOARD_RAMBO \
         EXTRUDERS 2 TEMP_SENSOR_0 -2 TEMP_SENSOR_1 1 TEMP_SENSOR_BED 2 \
         TEMP_SENSOR_PROBE 1 TEMP_PROBE_PIN 12 \
         TEMP_SENSOR_CHAMBER 3 TEMP_CHAMBER_PIN 3 HEATER_CHAMBER_PIN 45 \
-        GRID_MAX_POINTS_X 16 \
+        GRID_MAX_POINTS_X 16 AUTO_POWER_E_TEMP 80 \
         FANMUX0_PIN 53
 opt_disable Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN USE_WATCHDOG
 opt_enable USE_ZMAX_PLUG REPRAP_DISCOUNT_SMART_CONTROLLER LCD_PROGRESS_BAR LCD_PROGRESS_BAR_TEST \
@@ -32,7 +32,7 @@ opt_enable USE_ZMAX_PLUG REPRAP_DISCOUNT_SMART_CONTROLLER LCD_PROGRESS_BAR LCD_P
            SKEW_CORRECTION SKEW_CORRECTION_FOR_Z SKEW_CORRECTION_GCODE \
            BACKLASH_COMPENSATION BACKLASH_GCODE BAUD_RATE_GCODE BEZIER_CURVE_SUPPORT \
            FWRETRACT ARC_P_CIRCLES CNC_WORKSPACE_PLANES CNC_COORDINATE_SYSTEMS \
-           PSU_CONTROL PS_OFF_CONFIRM PS_OFF_SOUND AUTO_POWER_CONTROL \
+           PSU_CONTROL PS_OFF_CONFIRM PS_OFF_SOUND POWER_OFF_WAIT_FOR_COOLDOWN \
            POWER_LOSS_RECOVERY POWER_LOSS_PIN POWER_LOSS_STATE POWER_LOSS_RECOVER_ZHOME POWER_LOSS_ZHOME_POS \
            SLOW_PWM_HEATERS THERMAL_PROTECTION_CHAMBER LIN_ADVANCE EXTRA_LIN_ADVANCE_K \
            HOST_ACTION_COMMANDS HOST_PROMPT_SUPPORT PINS_DEBUGGING MAX7219_DEBUG M114_DETAIL