Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hotend Idle Timeout Menu #25015

Merged
merged 11 commits into from
Jun 25, 2023
29 changes: 17 additions & 12 deletions Marlin/src/feature/hotend_idle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,28 +37,33 @@
#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) {
static float old_e_position = 0;
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
}
}

Expand All @@ -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
}

Expand Down
17 changes: 15 additions & 2 deletions Marlin/src/feature/hotend_idle.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 7 additions & 0 deletions Marlin/src/gcode/gcode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -658,8 +658,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: M115(); break; // M115: Report capabilities
Expand Down
7 changes: 7 additions & 0 deletions Marlin/src/gcode/gcode.h
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,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);

Expand Down
79 changes: 79 additions & 0 deletions Marlin/src/gcode/temp/M86-M87.cpp
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*
*/

/**
* 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<seconds> : Idle timeout. Set to 0 to disable.
* E<temp> : Extruder idle temperature to set on timeout
* B<temp> : Bed idle temperature to set on timeout
* T<temp> : 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
2 changes: 1 addition & 1 deletion Marlin/src/lcd/language/language_de.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand Down
5 changes: 4 additions & 1 deletion Marlin/src/lcd/language/language_en.h
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ namespace Language_en {
LSTR MSG_PID_AUTOTUNE_FAILED = _UxGT("PID 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.");
Expand Down Expand Up @@ -670,6 +670,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");
Expand Down
2 changes: 1 addition & 1 deletion Marlin/src/lcd/language/language_it.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand Down
2 changes: 1 addition & 1 deletion Marlin/src/lcd/language/language_sk.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand Down
26 changes: 26 additions & 0 deletions Marlin/src/lcd/menu/menu_configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,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
Expand Down Expand Up @@ -277,6 +281,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() {
Expand Down Expand Up @@ -610,6 +632,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
//
Expand Down
4 changes: 4 additions & 0 deletions Marlin/src/lcd/menu/menu_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,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
Expand Down
37 changes: 36 additions & 1 deletion Marlin/src/module/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,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
Expand Down Expand Up @@ -623,6 +627,13 @@ typedef struct SettingsDataStruct {
shaping_y_zeta; // M593 Y D
#endif

//
// HOTEND_IDLE_TIMEOUT
//
#if ENABLED(HOTEND_IDLE_TIMEOUT)
hotend_idle_settings_t hotend_idle_config; // M86 S T E B
#endif

} SettingsData;

//static_assert(sizeof(SettingsData) <= MARLIN_EEPROM_SIZE, "EEPROM too small to contain SettingsData!");
Expand Down Expand Up @@ -1702,7 +1713,7 @@ void MarlinSettings::postprocess() {

//
// Input Shaping
///
//
#if HAS_ZV_SHAPING
#if ENABLED(INPUT_SHAPING_X)
EEPROM_WRITE(stepper.get_shaping_frequency(X_AXIS));
Expand All @@ -1714,6 +1725,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
//
Expand Down Expand Up @@ -2781,6 +2799,13 @@ void MarlinSettings::postprocess() {
}
#endif

//
// HOTEND_IDLE_TIMEOUT
//
#if ENABLED(HOTEND_IDLE_TIMEOUT)
EEPROM_READ(hotend_idle.cfg);
#endif

//
// Validate Final Size and CRC
//
Expand Down Expand Up @@ -3590,6 +3615,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)
Expand Down Expand Up @@ -3845,6 +3875,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
//
Expand Down
2 changes: 1 addition & 1 deletion ini/features.ini
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ HAS_FANMUX = build_src_filter=+<src/feature/fanmux.c
FILAMENT_WIDTH_SENSOR = build_src_filter=+<src/feature/filwidth.cpp> +<src/gcode/feature/filwidth>
FWRETRACT = build_src_filter=+<src/feature/fwretract.cpp> +<src/gcode/feature/fwretract>
HOST_ACTION_COMMANDS = build_src_filter=+<src/feature/host_actions.cpp>
HOTEND_IDLE_TIMEOUT = build_src_filter=+<src/feature/hotend_idle.cpp>
HOTEND_IDLE_TIMEOUT = build_src_filter=+<src/feature/hotend_idle.cpp> +<src/gcode/temp/M86-M87.cpp>
JOYSTICK = build_src_filter=+<src/feature/joystick.cpp>
BLINKM = build_src_filter=+<src/feature/leds/blinkm.cpp>
HAS_COLOR_LEDS = build_src_filter=+<src/feature/leds/leds.cpp> +<src/gcode/feature/leds/M150.cpp>
Expand Down