diff --git a/Marlin/src/feature/hotend_idle.cpp b/Marlin/src/feature/hotend_idle.cpp
index 4b137f42da7b..f64fe7e3cf67 100644
--- a/Marlin/src/feature/hotend_idle.cpp
+++ b/Marlin/src/feature/hotend_idle.cpp
@@ -37,20 +37,25 @@
#include "../module/planner.h"
#include "../lcd/marlinui.h"
-extern HotendIdleProtection hotend_idle;
+HotendIdleProtection hotend_idle;
millis_t HotendIdleProtection::next_protect_ms = 0;
+hotend_idle_settings_t HotendIdleProtection::cfg; // Initialized by settings.load()
void HotendIdleProtection::check_hotends(const millis_t &ms) {
+ const bool busy = (TERN0(HAS_RESUME_CONTINUE, wait_for_user) || planner.has_blocks_queued());
bool do_prot = false;
- HOTEND_LOOP() {
- const bool busy = (TERN0(HAS_RESUME_CONTINUE, wait_for_user) || planner.has_blocks_queued());
- if (thermalManager.degHotend(e) >= (HOTEND_IDLE_MIN_TRIGGER) && !busy) {
- do_prot = true; break;
+ if (!busy && cfg.timeout != 0) {
+ HOTEND_LOOP() {
+ if (thermalManager.degHotend(e) >= cfg.trigger) {
+ do_prot = true; break;
+ }
}
}
- if (bool(next_protect_ms) != do_prot)
- next_protect_ms = do_prot ? ms + hp_interval : 0;
+ if (!do_prot)
+ next_protect_ms = 0; // No hotends are hot so cancel timeout
+ else if (!next_protect_ms) // Timeout is possible?
+ next_protect_ms = ms + cfg.timeout * 1000; // Start timeout if not already set
}
void HotendIdleProtection::check_e_motion(const millis_t &ms) {
@@ -58,7 +63,7 @@ void HotendIdleProtection::check_e_motion(const millis_t &ms) {
if (old_e_position != current_position.e) {
old_e_position = current_position.e; // Track filament motion
if (next_protect_ms) // If some heater is on then...
- next_protect_ms = ms + hp_interval; // ...delay the timeout till later
+ next_protect_ms = ms + cfg.timeout * 1000; // ...delay the timeout till later
}
}
@@ -79,12 +84,12 @@ void HotendIdleProtection::timed_out() {
SERIAL_ECHOLNPGM("Hotend Idle Timeout");
LCD_MESSAGE(MSG_HOTEND_IDLE_TIMEOUT);
HOTEND_LOOP() {
- if ((HOTEND_IDLE_NOZZLE_TARGET) < thermalManager.degTargetHotend(e))
- thermalManager.setTargetHotend(HOTEND_IDLE_NOZZLE_TARGET, e);
+ if (cfg.nozzle_target < thermalManager.degTargetHotend(e))
+ thermalManager.setTargetHotend(cfg.nozzle_target, e);
}
#if HAS_HEATED_BED
- if ((HOTEND_IDLE_BED_TARGET) < thermalManager.degTargetBed())
- thermalManager.setTargetBed(HOTEND_IDLE_BED_TARGET);
+ if (cfg.bed_target < thermalManager.degTargetBed())
+ thermalManager.setTargetBed(cfg.bed_target);
#endif
}
diff --git a/Marlin/src/feature/hotend_idle.h b/Marlin/src/feature/hotend_idle.h
index 40f557d5ed4c..a4229153c966 100644
--- a/Marlin/src/feature/hotend_idle.h
+++ b/Marlin/src/feature/hotend_idle.h
@@ -21,13 +21,26 @@
*/
#pragma once
-#include "../core/millis_t.h"
+#include "../inc/MarlinConfig.h"
+
+typedef struct {
+ int16_t timeout, trigger, nozzle_target;
+ #if HAS_HEATED_BED
+ int16_t bed_target;
+ #endif
+ void set_defaults() {
+ timeout = HOTEND_IDLE_TIMEOUT_SEC;
+ trigger = HOTEND_IDLE_MIN_TRIGGER;
+ nozzle_target = HOTEND_IDLE_NOZZLE_TARGET;
+ bed_target = HOTEND_IDLE_BED_TARGET;
+ }
+} hotend_idle_settings_t;
class HotendIdleProtection {
public:
static void check();
+ static hotend_idle_settings_t cfg;
private:
- static constexpr millis_t hp_interval = SEC_TO_MS(HOTEND_IDLE_TIMEOUT_SEC);
static millis_t next_protect_ms;
static void check_hotends(const millis_t &ms);
static void check_e_motion(const millis_t &ms);
diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp
index 83761a95b1fc..1701d7b86e10 100644
--- a/Marlin/src/gcode/gcode.cpp
+++ b/Marlin/src/gcode/gcode.cpp
@@ -711,8 +711,15 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
case 82: M82(); break; // M82: Set E axis normal mode (same as other axes)
case 83: M83(); break; // M83: Set E axis relative mode
#endif
+
case 18: case 84: M18_M84(); break; // M18/M84: Disable Steppers / Set Timeout
case 85: M85(); break; // M85: Set inactivity stepper shutdown timeout
+
+ #if ENABLED(HOTEND_IDLE_TIMEOUT)
+ case 86: M86(); break; // M86: Set Hotend Idle Timeout
+ case 87: M87(); break; // M87: Cancel Hotend Idle Timeout
+ #endif
+
case 92: M92(); break; // M92: Set the steps-per-unit for one or more axes
case 114: M114(); break; // M114: Report current position
case 115:
diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h
index cf4a82339562..44b7643d5529 100644
--- a/Marlin/src/gcode/gcode.h
+++ b/Marlin/src/gcode/gcode.h
@@ -716,6 +716,13 @@ class GcodeSuite {
#endif
static void M85();
+
+ #if ENABLED(HOTEND_IDLE_TIMEOUT)
+ static void M86();
+ static void M86_report(const bool forReplay=true);
+ static void M87();
+ #endif
+
static void M92();
static void M92_report(const bool forReplay=true, const int8_t e=-1);
diff --git a/Marlin/src/gcode/temp/M86-M87.cpp b/Marlin/src/gcode/temp/M86-M87.cpp
new file mode 100644
index 000000000000..7fb446196123
--- /dev/null
+++ b/Marlin/src/gcode/temp/M86-M87.cpp
@@ -0,0 +1,79 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2023 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+/**
+ * gcode/temp/M86-M87.cpp
+ *
+ * Hotend Idle Timeout
+ */
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if ENABLED(HOTEND_IDLE_TIMEOUT)
+
+#include "../gcode.h"
+#include "../../feature/hotend_idle.h"
+
+void GcodeSuite::M86_report(const bool forReplay/*=true*/) {
+ hotend_idle_settings_t &c = hotend_idle.cfg;
+ report_heading(forReplay, F("Hotend Idle Timeout"));
+ SERIAL_ECHOLNPGM(" M86"
+ #if HAS_HEATED_BED
+ " B", c.bed_target,
+ #endif
+ " E", c.nozzle_target,
+ " S", c.timeout,
+ " T", c.trigger
+ );
+}
+
+/**
+ * M86: Set / Report Hotend Idle Timeout
+ *
+ * Parameters
+ * S : Idle timeout. Set to 0 to disable.
+ * E : Extruder idle temperature to set on timeout
+ * B : Bed idle temperature to set on timeout
+ * T : Minimum extruder temperature to consider for timeout (> idle temperature)
+ */
+void GcodeSuite::M86() {
+ if (!parser.seen_any()) return M86_report();
+ hotend_idle_settings_t &c = hotend_idle.cfg;
+ if (parser.seenval('S')) c.timeout = parser.value_ushort();
+ if (parser.seenval('T')) c.trigger = parser.value_celsius();
+ if (parser.seenval('E')) c.nozzle_target = parser.value_celsius();
+ #if HAS_HEATED_BED
+ if (parser.seenval('B')) c.bed_target = parser.value_celsius();
+ #endif
+ const celsius_t min_trigger = c.nozzle_target + TEMP_HYSTERESIS;
+ if (c.trigger <= min_trigger)
+ SERIAL_ECHOLNPGM("?Idle Timeout (T) trigger temperature should be over ", min_trigger, "C.");
+}
+
+/**
+ * M86: Cancel Hotend Idle Timeout (by setting the timeout period to 0)
+ */
+void GcodeSuite::M87() {
+ hotend_idle.cfg.timeout = 0;
+}
+
+#endif // HOTEND_IDLE_TIMEOUT
diff --git a/Marlin/src/lcd/language/language_de.h b/Marlin/src/lcd/language/language_de.h
index c78942bff68e..33abdbee2351 100644
--- a/Marlin/src/lcd/language/language_de.h
+++ b/Marlin/src/lcd/language/language_de.h
@@ -343,7 +343,7 @@ namespace Language_de {
LSTR MSG_PID_AUTOTUNE_FAILED = _UxGT("PID Autotune fehlge.!");
LSTR MSG_BAD_HEATER_ID = _UxGT("ungültiger Extruder.");
LSTR MSG_TEMP_TOO_HIGH = _UxGT("Temperatur zu hoch.");
- LSTR MSG_TIMEOUT = _UxGT("Timeout.");
+ LSTR MSG_TIMEOUT = _UxGT("Timeout");
LSTR MSG_PID_BAD_HEATER_ID = _UxGT("Autotune fehlge.! Ungültiger Extruder");
LSTR MSG_PID_TEMP_TOO_HIGH = _UxGT("Autotune fehlge.! Temperatur zu hoch.");
LSTR MSG_PID_TIMEOUT = _UxGT("Autotune fehlge.! Timeout.");
diff --git a/Marlin/src/lcd/language/language_en.h b/Marlin/src/lcd/language/language_en.h
index 116e018c00f6..041a3e979920 100644
--- a/Marlin/src/lcd/language/language_en.h
+++ b/Marlin/src/lcd/language/language_en.h
@@ -371,7 +371,7 @@ namespace Language_en {
LSTR MSG_PID_AUTOTUNE_FAILED = _UxGT("Autotune failed!");
LSTR MSG_BAD_HEATER_ID = _UxGT("Bad extruder.");
LSTR MSG_TEMP_TOO_HIGH = _UxGT("Temperature too high.");
- LSTR MSG_TIMEOUT = _UxGT("Timeout.");
+ LSTR MSG_TIMEOUT = _UxGT("Timeout");
LSTR MSG_PID_BAD_HEATER_ID = _UxGT("Autotune failed! Bad extruder.");
LSTR MSG_PID_TEMP_TOO_HIGH = _UxGT("Autotune failed! Temperature too high.");
LSTR MSG_PID_TIMEOUT = _UxGT("Autotune failed! Timeout.");
@@ -683,6 +683,9 @@ namespace Language_en {
LSTR MSG_INFO_RUNAWAY_OFF = _UxGT("Runaway Watch: OFF");
LSTR MSG_INFO_RUNAWAY_ON = _UxGT("Runaway Watch: ON");
LSTR MSG_HOTEND_IDLE_TIMEOUT = _UxGT("Hotend Idle Timeout");
+ LSTR MSG_HOTEND_IDLE_DISABLE = _UxGT("Disable Timeout");
+ LSTR MSG_HOTEND_IDLE_NOZZLE_TARGET = _UxGT("Nozzle Idle Temp");
+ LSTR MSG_HOTEND_IDLE_BED_TARGET = _UxGT("Bed Idle Temp");
LSTR MSG_FAN_SPEED_FAULT = _UxGT("Fan speed fault");
LSTR MSG_CASE_LIGHT = _UxGT("Case Light");
diff --git a/Marlin/src/lcd/language/language_it.h b/Marlin/src/lcd/language/language_it.h
index 054a71d6c069..cbdd308f9b6e 100644
--- a/Marlin/src/lcd/language/language_it.h
+++ b/Marlin/src/lcd/language/language_it.h
@@ -364,7 +364,7 @@ namespace Language_it {
LSTR MSG_PID_AUTOTUNE_FAILED = _UxGT("Calibr.PID fallito!");
LSTR MSG_BAD_HEATER_ID = _UxGT("Estrusore invalido.");
LSTR MSG_TEMP_TOO_HIGH = _UxGT("Temp.troppo alta.");
- LSTR MSG_TIMEOUT = _UxGT("Tempo scaduto.");
+ LSTR MSG_TIMEOUT = _UxGT("Tempo scaduto");
LSTR MSG_PID_BAD_HEATER_ID = _UxGT("Calibrazione fallita! Estrusore errato.");
LSTR MSG_PID_TEMP_TOO_HIGH = _UxGT("Calibrazione fallita! Temperatura troppo alta.");
LSTR MSG_PID_TIMEOUT = _UxGT("Calibrazione fallita! Tempo scaduto.");
diff --git a/Marlin/src/lcd/language/language_sk.h b/Marlin/src/lcd/language/language_sk.h
index 5499a2494203..7dc1c5357e39 100644
--- a/Marlin/src/lcd/language/language_sk.h
+++ b/Marlin/src/lcd/language/language_sk.h
@@ -363,7 +363,7 @@ namespace Language_sk {
LSTR MSG_PID_AUTOTUNE_FAILED = _UxGT("Kal. PID zlyhala!");
LSTR MSG_BAD_HEATER_ID = _UxGT("Zlý extrudér");
LSTR MSG_TEMP_TOO_HIGH = _UxGT("Príliš vysoká tepl.");
- LSTR MSG_TIMEOUT = _UxGT("Čas vypršal.");
+ LSTR MSG_TIMEOUT = _UxGT("Čas vypršal");
LSTR MSG_PID_BAD_HEATER_ID = _UxGT("Auto-kal. zlyhala! Zlý extrúder.");
LSTR MSG_PID_TEMP_TOO_HIGH = _UxGT("Auto-kal. zlyhala! Príliš vysoká tepl.");
LSTR MSG_PID_TIMEOUT = _UxGT("Auto-kal. zlyhala! Čas vypršal.");
diff --git a/Marlin/src/lcd/menu/menu_configuration.cpp b/Marlin/src/lcd/menu/menu_configuration.cpp
index 0435983f9d57..51bc64965585 100644
--- a/Marlin/src/lcd/menu/menu_configuration.cpp
+++ b/Marlin/src/lcd/menu/menu_configuration.cpp
@@ -60,6 +60,10 @@
#include "../../libs/buzzer.h"
#endif
+#if ENABLED(HOTEND_IDLE_TIMEOUT)
+ #include "../../feature/hotend_idle.h"
+#endif
+
#if ANY(LCD_PROGRESS_BAR_TEST, LCD_ENDSTOP_TEST)
#include "../lcdprint.h"
#define HAS_DEBUG_MENU 1
@@ -278,6 +282,24 @@ void menu_advanced_settings();
}
#endif
+#if ENABLED(HOTEND_IDLE_TIMEOUT)
+
+ void menu_hotend_idle() {
+ hotend_idle_settings_t &c = hotend_idle.cfg;
+ START_MENU();
+ BACK_ITEM(MSG_BACK);
+
+ if (c.timeout) GCODES_ITEM(MSG_HOTEND_IDLE_DISABLE, F("M87"));
+ EDIT_ITEM(int3, MSG_TIMEOUT, &c.timeout, 0, 999);
+ EDIT_ITEM(int3, MSG_TEMPERATURE, &c.trigger, 0, HEATER_0_MAXTEMP);
+ EDIT_ITEM(int3, MSG_HOTEND_IDLE_NOZZLE_TARGET, &c.nozzle_target, 0, HEATER_0_MAXTEMP);
+ EDIT_ITEM(int3, MSG_HOTEND_IDLE_BED_TARGET, &c.bed_target, 0, BED_MAXTEMP);
+
+ END_MENU();
+ }
+
+#endif
+
#if ENABLED(DUAL_X_CARRIAGE)
void menu_idex() {
@@ -588,6 +610,10 @@ void menu_configuration() {
#endif
}
+ #if ENABLED(HOTEND_IDLE_TIMEOUT)
+ SUBMENU(MSG_HOTEND_IDLE_TIMEOUT, menu_hotend_idle);
+ #endif
+
//
// Set single nozzle filament retract and prime length
//
diff --git a/Marlin/src/lcd/menu/menu_main.cpp b/Marlin/src/lcd/menu/menu_main.cpp
index 6807cc74df1f..ea9f7e61ddb5 100644
--- a/Marlin/src/lcd/menu/menu_main.cpp
+++ b/Marlin/src/lcd/menu/menu_main.cpp
@@ -101,6 +101,10 @@ void menu_configuration();
void menu_preheat_only();
#endif
+#if ENABLED(HOTEND_IDLE_TIMEOUT)
+ void menu_hotend_idle();
+#endif
+
#if HAS_MULTI_LANGUAGE
void menu_language();
#endif
diff --git a/Marlin/src/module/settings.cpp b/Marlin/src/module/settings.cpp
index cd6802aa0d2a..ae8b14434bfa 100644
--- a/Marlin/src/module/settings.cpp
+++ b/Marlin/src/module/settings.cpp
@@ -183,6 +183,10 @@
#include "../lcd/extui/dgus/DGUSDisplayDef.h"
#endif
+#if ENABLED(HOTEND_IDLE_TIMEOUT)
+ #include "../feature/hotend_idle.h"
+#endif
+
#pragma pack(push, 1) // No padding between variables
#if HAS_ETHERNET
@@ -637,6 +641,14 @@ typedef struct SettingsDataStruct {
moving_settings_t moving_settings;
} SettingsData;
+ //
+ // HOTEND_IDLE_TIMEOUT
+ //
+ #if ENABLED(HOTEND_IDLE_TIMEOUT)
+ hotend_idle_settings_t hotend_idle_config; // M86 S T E B
+ #endif
+
+
autooff_settings_t autooff_settings;
psu_settings_t psu_settings;
bedlevel_settings_t bedlevel_settings;
@@ -1740,7 +1752,7 @@ void MarlinSettings::postprocess() {
//
// Input Shaping
- ///
+ //
#if HAS_ZV_SHAPING
#if ENABLED(INPUT_SHAPING_X)
EEPROM_WRITE(stepper.get_shaping_frequency(X_AXIS));
@@ -1752,6 +1764,13 @@ void MarlinSettings::postprocess() {
#endif
#endif
+ //
+ // HOTEND_IDLE_TIMEOUT
+ //
+ #if ENABLED(HOTEND_IDLE_TIMEOUT)
+ EEPROM_WRITE(hotend_idle.cfg);
+ #endif
+
//
// Report final CRC and Data Size
//
@@ -2852,6 +2871,13 @@ void MarlinSettings::postprocess() {
}
#endif
+ //
+ // HOTEND_IDLE_TIMEOUT
+ //
+ #if ENABLED(HOTEND_IDLE_TIMEOUT)
+ EEPROM_READ(hotend_idle.cfg);
+ #endif
+
//
// Validate Final Size and CRC
//
@@ -3769,6 +3795,11 @@ void MarlinSettings::reset() {
#endif
#endif
+ //
+ // Hotend Idle Timeout
+ //
+ TERN_(HOTEND_IDLE_TIMEOUT, hotend_idle.cfg.set_defaults());
+
postprocess();
#if ANY(EEPROM_CHITCHAT, DEBUG_LEVELING_FEATURE)
@@ -4027,6 +4058,11 @@ void MarlinSettings::reset() {
//
TERN_(HAS_ZV_SHAPING, gcode.M593_report(forReplay));
+ //
+ // Hotend Idle Timeout
+ //
+ TERN_(HOTEND_IDLE_TIMEOUT, gcode.M86_report(forReplay));
+
//
// Linear Advance
//