diff --git a/Marlin/src/feature/pause.cpp b/Marlin/src/feature/pause.cpp
index 10a1ba6a96f63f16217420f3413e67bdcf8c73e4..6a2f148ea39412672eb94e77e59cd2e72baa1149 100644
--- a/Marlin/src/feature/pause.cpp
+++ b/Marlin/src/feature/pause.cpp
@@ -564,7 +564,7 @@ void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep
       #endif
 
       // Re-enable the heaters if they timed out
-      HOTEND_LOOP() thermalManager.reset_heater_idle_timer(e);
+      HOTEND_LOOP() thermalManager.reset_hotend_idle_timer(e);
 
       // Wait for the heaters to reach the target temperatures
       ensure_safe_temperature();
@@ -633,7 +633,7 @@ void resume_print(const float &slow_load_length/*=0*/, const float &fast_load_le
   bool nozzle_timed_out = false;
   HOTEND_LOOP() {
     nozzle_timed_out |= thermalManager.hotend_idle[e].timed_out;
-    thermalManager.reset_heater_idle_timer(e);
+    thermalManager.reset_hotend_idle_timer(e);
   }
 
   if (nozzle_timed_out || thermalManager.hotEnoughToExtrude(active_extruder)) // Load the new filament
diff --git a/Marlin/src/gcode/temperature/M104_M109.cpp b/Marlin/src/gcode/temperature/M104_M109.cpp
index 8c5827e83b172be6a5c4832fbda2830155e8e0f1..8dbb3af235b1a9e29c11b788a837ea65e866c86e 100644
--- a/Marlin/src/gcode/temperature/M104_M109.cpp
+++ b/Marlin/src/gcode/temperature/M104_M109.cpp
@@ -20,6 +20,12 @@
  *
  */
 
+/**
+ * gcode/temperature/M104_M109.cpp
+ *
+ * Hotend target temperature control
+ */
+
 #include "../../inc/MarlinConfigPre.h"
 
 #if EXTRUDERS
@@ -73,14 +79,11 @@ void GcodeSuite::M104() {
     #if ENABLED(PRINTJOB_TIMER_AUTOSTART)
       /**
        * Stop the timer at the end of print. Start is managed by 'heat and wait' M109.
-       * We use half EXTRUDE_MINTEMP here to allow nozzles to be put into hot
-       * standby mode, for instance in a dual extruder setup, without affecting
-       * the running print timer.
+       * Hotends use EXTRUDE_MINTEMP / 2 to allow nozzles to be put into hot standby
+       * mode, for instance in a dual extruder setup, without affecting the running
+       * print timer.
        */
-      if (temp <= (EXTRUDE_MINTEMP) / 2) {
-        print_job_timer.stop();
-        ui.reset_status();
-      }
+      thermalManager.check_timer_autostart(false, true);
     #endif
   }
 
@@ -90,8 +93,10 @@ void GcodeSuite::M104() {
 }
 
 /**
- * M109: Sxxx Wait for extruder(s) to reach temperature. Waits only when heating.
- *       Rxxx Wait for extruder(s) to reach temperature. Waits when heating and cooling.
+ * M109: Sxxx Wait for hotend(s) to reach temperature. Waits only when heating.
+ *       Rxxx Wait for hotend(s) to reach temperature. Waits when heating and cooling.
+ *
+ * With PRINTJOB_TIMER_AUTOSTART also start the job timer on heating and stop it if turned off.
  */
 void GcodeSuite::M109() {
 
@@ -125,12 +130,7 @@ void GcodeSuite::M109() {
        * standby mode, (e.g., in a dual extruder setup) without affecting
        * the running print timer.
        */
-      if (parser.value_celsius() <= (EXTRUDE_MINTEMP) / 2) {
-        print_job_timer.stop();
-        ui.reset_status();
-      }
-      else
-        startOrResumeJob();
+      thermalManager.check_timer_autostart(true, true);
     #endif
 
     #if HAS_DISPLAY
diff --git a/Marlin/src/gcode/temperature/M140_M190.cpp b/Marlin/src/gcode/temperature/M140_M190.cpp
index d6386cef506a77343afcda7c9f06f8e005eef191..f5c3d3683299f1afc35e6dfab91b0fe1c5065352 100644
--- a/Marlin/src/gcode/temperature/M140_M190.cpp
+++ b/Marlin/src/gcode/temperature/M140_M190.cpp
@@ -20,6 +20,12 @@
  *
  */
 
+/**
+ * gcode/temperature/M140_M190.cpp
+ *
+ * Bed target temperature control
+ */
+
 #include "../../inc/MarlinConfig.h"
 
 #if HAS_HEATED_BED
@@ -50,6 +56,8 @@ void GcodeSuite::M140() {
 /**
  * M190: Sxxx Wait for bed current temp to reach target temp. Waits only when heating
  *       Rxxx Wait for bed current temp to reach target temp. Waits when heating and cooling
+ *
+ * With PRINTJOB_TIMER_AUTOSTART also start the job timer on heating.
  */
 void GcodeSuite::M190() {
   if (DEBUGGING(DRYRUN)) return;
@@ -58,8 +66,7 @@ void GcodeSuite::M190() {
   if (no_wait_for_cooling || parser.seenval('R')) {
     thermalManager.setTargetBed(parser.value_celsius());
     #if ENABLED(PRINTJOB_TIMER_AUTOSTART)
-      if (parser.value_celsius() > BED_MINTEMP)
-        startOrResumeJob();
+      thermalManager.check_timer_autostart(true, false);
     #endif
   }
   else return;
diff --git a/Marlin/src/gcode/temperature/M141_M191.cpp b/Marlin/src/gcode/temperature/M141_M191.cpp
index 12eaa24bf921e5bcee7bb071c11893f93559a3bb..3c9934659940230df20bdaa92711d48c84249f74 100644
--- a/Marlin/src/gcode/temperature/M141_M191.cpp
+++ b/Marlin/src/gcode/temperature/M141_M191.cpp
@@ -20,6 +20,12 @@
  *
  */
 
+/**
+ * gcode/temperature/M141_M191.cpp
+ *
+ * Chamber target temperature control
+ */
+
 #include "../../inc/MarlinConfig.h"
 
 #if HAS_HEATED_CHAMBER
@@ -59,8 +65,7 @@ void GcodeSuite::M191() {
   if (no_wait_for_cooling || parser.seenval('R')) {
     thermalManager.setTargetChamber(parser.value_celsius());
     #if ENABLED(PRINTJOB_TIMER_AUTOSTART)
-      if (parser.value_celsius() > CHAMBER_MINTEMP)
-        startOrResumeJob();
+      thermalManager.check_timer_autostart(true, false);
     #endif
   }
   else return;
diff --git a/Marlin/src/lcd/extensible_ui/ui_api.cpp b/Marlin/src/lcd/extensible_ui/ui_api.cpp
index 8c8133f22b52e64e38a9d4956167473f7eb31274..5e053b670a73f2e642c23ed18a516ae2a9c6ecdf 100644
--- a/Marlin/src/lcd/extensible_ui/ui_api.cpp
+++ b/Marlin/src/lcd/extensible_ui/ui_api.cpp
@@ -171,7 +171,7 @@ namespace ExtUI {
 
   void enableHeater(const extruder_t extruder) {
     #if HOTENDS && HEATER_IDLE_HANDLER
-      thermalManager.reset_heater_idle_timer(extruder - E0);
+      thermalManager.reset_hotend_idle_timer(extruder - E0);
     #else
       UNUSED(extruder);
     #endif
@@ -190,7 +190,7 @@ namespace ExtUI {
         #endif
         default:
           #if HOTENDS
-            thermalManager.reset_heater_idle_timer(heater - H0);
+            thermalManager.reset_hotend_idle_timer(heater - H0);
           #endif
           break;
       }
diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp
index 559b3d5a1ee77d0cfb9976df9a0da0ffc791500e..61ed04aaea460194824339cc9797b2e7e8f1b4df 100644
--- a/Marlin/src/module/temperature.cpp
+++ b/Marlin/src/module/temperature.cpp
@@ -220,10 +220,10 @@ Temperature thermalManager;
 #endif // FAN_COUNT > 0
 
 #if WATCH_HOTENDS
-  heater_watch_t Temperature::watch_hotend[HOTENDS]; // = { { 0 } }
+  hotend_watch_t Temperature::watch_hotend[HOTENDS]; // = { { 0 } }
 #endif
 #if HEATER_IDLE_HANDLER
-  heater_idle_t Temperature::hotend_idle[HOTENDS]; // = { { 0 } }
+  hotend_idle_t Temperature::hotend_idle[HOTENDS]; // = { { 0 } }
 #endif
 
 #if HAS_HEATED_BED
@@ -236,13 +236,13 @@ Temperature thermalManager;
     int16_t Temperature::maxtemp_raw_BED = HEATER_BED_RAW_HI_TEMP;
   #endif
   #if WATCH_BED
-    heater_watch_t Temperature::watch_bed; // = { 0 }
+    bed_watch_t Temperature::watch_bed; // = { 0 }
   #endif
   #if DISABLED(PIDTEMPBED)
     millis_t Temperature::next_bed_check_ms;
   #endif
   #if HEATER_IDLE_HANDLER
-    heater_idle_t Temperature::bed_idle; // = { 0 }
+    hotend_idle_t Temperature::bed_idle; // = { 0 }
   #endif
 #endif // HAS_HEATED_BED
 
@@ -256,7 +256,7 @@ Temperature thermalManager;
       int16_t Temperature::maxtemp_raw_CHAMBER = HEATER_CHAMBER_RAW_HI_TEMP;
     #endif
     #if WATCH_CHAMBER
-      heater_watch_t Temperature::watch_chamber{0};
+      chamber_watch_t Temperature::watch_chamber{0};
     #endif
     millis_t Temperature::next_chamber_check_ms;
   #endif // HAS_HEATED_CHAMBER
@@ -1974,12 +1974,7 @@ void Temperature::init() {
    */
   void Temperature::start_watching_hotend(const uint8_t E_NAME) {
     const uint8_t ee = HOTEND_INDEX;
-    if (degTargetHotend(ee) && degHotend(ee) < degTargetHotend(ee) - (WATCH_TEMP_INCREASE + TEMP_HYSTERESIS + 1)) {
-      watch_hotend[ee].target = degHotend(ee) + WATCH_TEMP_INCREASE;
-      watch_hotend[ee].next_ms = millis() + (WATCH_TEMP_PERIOD) * 1000UL;
-    }
-    else
-      watch_hotend[ee].next_ms = 0;
+    watch_hotend[ee].restart(degHotend(ee), degTargetHotend(ee));
   }
 #endif
 
@@ -1990,12 +1985,7 @@ void Temperature::init() {
    * This is called when the temperature is set. (M140, M190)
    */
   void Temperature::start_watching_bed() {
-    if (degTargetBed() && degBed() < degTargetBed() - (WATCH_BED_TEMP_INCREASE + TEMP_BED_HYSTERESIS + 1)) {
-      watch_bed.target = degBed() + WATCH_BED_TEMP_INCREASE;
-      watch_bed.next_ms = millis() + (WATCH_BED_TEMP_PERIOD) * 1000UL;
-    }
-    else
-      watch_bed.next_ms = 0;
+    watch_bed.restart(degBed(), degTargetBed());
   }
 #endif
 
@@ -2006,12 +1996,7 @@ void Temperature::init() {
    * This is called when the temperature is set. (M141, M191)
    */
   void Temperature::start_watching_chamber() {
-    if (degChamber() < degTargetChamber() - (WATCH_CHAMBER_TEMP_INCREASE + TEMP_CHAMBER_HYSTERESIS + 1)) {
-      watch_chamber.target = degChamber() + WATCH_CHAMBER_TEMP_INCREASE;
-      watch_chamber.next_ms = millis() + (WATCH_CHAMBER_TEMP_PERIOD) * 1000UL;
-    }
-    else
-      watch_chamber.next_ms = 0;
+    watch_chamber.restart(degChamber(), degTargetChamber());
   }
 #endif
 
@@ -2154,6 +2139,34 @@ void Temperature::disable_all_heaters() {
   #endif
 }
 
+#if ENABLED(PRINTJOB_TIMER_AUTOSTART)
+
+  bool Temperature::over_autostart_threshold() {
+    #if HOTENDS
+      HOTEND_LOOP() if (degTargetHotend(e) < (EXTRUDE_MINTEMP) / 2) return true;
+    #endif
+    #if HAS_HEATED_BED
+      if (degTargetBed() > BED_MINTEMP) return true;
+    #endif
+    #if HAS_HEATED_CHAMBER
+      if (degTargetChamber() > CHAMBER_MINTEMP) return true;
+    #endif
+    return false;
+  }
+
+  void Temperature::check_timer_autostart(const bool can_start, const bool can_stop) {
+    if (over_autostart_threshold()) {
+      if (can_start) startOrResumeJob();
+    }
+    else if (can_stop) {
+      print_job_timer.stop();
+      ui.reset_status();
+    }
+  }
+
+#endif
+
+
 #if ENABLED(PROBING_HEATERS_OFF)
 
   void Temperature::pause(const bool p) {
@@ -2166,7 +2179,7 @@ void Temperature::disable_all_heaters() {
         #endif
       }
       else {
-        HOTEND_LOOP() reset_heater_idle_timer(e);
+        HOTEND_LOOP() reset_hotend_idle_timer(e);
         #if HAS_HEATED_BED
           reset_bed_idle_timer();
         #endif
diff --git a/Marlin/src/module/temperature.h b/Marlin/src/module/temperature.h
index da73ed37a4b97066e8074c4435665737d9efd9ad..a360394f24e69a2e4ec5131081f25419e4ffad8f 100644
--- a/Marlin/src/module/temperature.h
+++ b/Marlin/src/module/temperature.h
@@ -228,15 +228,38 @@ typedef struct {
   inline void start(const millis_t &ms) { timeout_ms = millis() + ms; timed_out = false; }
   inline void reset() { timeout_ms = 0; timed_out = false; }
   inline void expire() { start(0); }
-} heater_idle_t;
+} hotend_idle_t;
 
 // Heater watch handling
-typedef struct {
+template <int INCREASE, int HYSTERESIS, millis_t PERIOD>
+struct HeaterWatch {
   uint16_t target;
   millis_t next_ms;
   inline bool elapsed(const millis_t &ms) { return next_ms && ELAPSED(ms, next_ms); }
   inline bool elapsed() { return elapsed(millis()); }
-} heater_watch_t;
+
+  inline void restart(const int16_t curr, const int16_t tgt) {
+    if (tgt) {
+      const int16_t newtarget = curr + INCREASE;
+      if (newtarget < tgt - HYSTERESIS - 1) {
+        target = newtarget;
+        next_ms = millis() + PERIOD * 1000UL;
+        return;
+      }
+    }
+    next_ms = 0;
+  }
+};
+
+#if WATCH_HOTENDS
+  typedef struct HeaterWatch<WATCH_TEMP_INCREASE, TEMP_HYSTERESIS, WATCH_TEMP_PERIOD> hotend_watch_t;
+#endif
+#if WATCH_BED
+  typedef struct HeaterWatch<WATCH_BED_TEMP_INCREASE, TEMP_BED_HYSTERESIS, WATCH_BED_TEMP_PERIOD> bed_watch_t;
+#endif
+#if WATCH_CHAMBER
+  typedef struct HeaterWatch<WATCH_CHAMBER_TEMP_INCREASE, TEMP_CHAMBER_HYSTERESIS, WATCH_CHAMBER_TEMP_PERIOD> chamber_watch_t;
+#endif
 
 // Temperature sensor read value ranges
 typedef struct { int16_t raw_min, raw_max; } raw_range_t;
@@ -345,12 +368,12 @@ class Temperature {
     FORCE_INLINE static bool targetHotEnoughToExtrude(const uint8_t e) { return !targetTooColdToExtrude(e); }
 
     #if HEATER_IDLE_HANDLER
-      static heater_idle_t hotend_idle[HOTENDS];
+      static hotend_idle_t hotend_idle[HOTENDS];
       #if HAS_HEATED_BED
-        static heater_idle_t bed_idle;
+        static hotend_idle_t bed_idle;
       #endif
       #if HAS_HEATED_CHAMBER
-        static heater_idle_t chamber_idle;
+        static hotend_idle_t chamber_idle;
       #endif
     #endif
 
@@ -363,7 +386,7 @@ class Temperature {
     static volatile bool raw_temps_ready;
 
     #if WATCH_HOTENDS
-      static heater_watch_t watch_hotend[HOTENDS];
+      static hotend_watch_t watch_hotend[HOTENDS];
     #endif
 
     #if ENABLED(TEMP_SENSOR_1_AS_REDUNDANT)
@@ -382,7 +405,7 @@ class Temperature {
 
     #if HAS_HEATED_BED
       #if WATCH_BED
-        static heater_watch_t watch_bed;
+        static bed_watch_t watch_bed;
       #endif
       #if DISABLED(PIDTEMPBED)
         static millis_t next_bed_check_ms;
@@ -397,7 +420,7 @@ class Temperature {
 
     #if HAS_HEATED_CHAMBER
       #if WATCH_CHAMBER
-        static heater_watch_t watch_chamber;
+        static chamber_watch_t watch_chamber;
       #endif
       static millis_t next_chamber_check_ms;
       #ifdef CHAMBER_MINTEMP
@@ -736,6 +759,14 @@ class Temperature {
      */
     static void disable_all_heaters();
 
+    #if ENABLED(PRINTJOB_TIMER_AUTOSTART)
+      /**
+       * Methods to check if heaters are enabled, indicating an active job
+       */
+      static bool over_autostart_threshold();
+      static void check_timer_autostart(const bool can_start, const bool can_stop);
+    #endif
+
     /**
      * Perform auto-tuning for hotend or bed in response to M303
      */
@@ -768,7 +799,7 @@ class Temperature {
 
     #if HEATER_IDLE_HANDLER
 
-      static void reset_heater_idle_timer(const uint8_t E_NAME) {
+      static void reset_hotend_idle_timer(const uint8_t E_NAME) {
         hotend_idle[HOTEND_INDEX].reset();
         start_watching_hotend(HOTEND_INDEX);
       }