Created
September 26, 2025 00:58
-
-
Save cdracars/0d343ae438029f93f1d0f2c69857a514 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ============================================================================ | |
| # KLIPPER HEAT SOAK MACRO (SMART: TIME OR CHAMBER TEMP) | |
| # ============================================================================ | |
| # Author: Cody Dracars | |
| # Date: 2025-04-05 (smart mode added 2025-09-25) | |
| # License: MIT | |
| # | |
| # Heats bed to target and performs a soak using either: | |
| # - Time-based countdown, or | |
| # - Chamber-temperature target with hold requirement (if a chamber sensor exists) | |
| # | |
| # Features | |
| # - Configurable bed temp, soak duration, fan speed | |
| # - Auto-detects chamber sensor (temperature_sensor/heater_generic/temperature_fan) | |
| # - MODE switch: time | chamber | auto (default auto) | |
| # - Chamber hold timer to ensure temperature stability | |
| # - 1s tick loop via delayed_gcode (robust against long G4 timing issues) | |
| # - Gentle toolhead motion for air circulation | |
| # - Progress updates (M117/M118) and optional beeps on completion | |
| # - Cancel macro: HEAT_SOAK_CANCEL | |
| # | |
| # Usage Examples | |
| # HEAT_SOAK ; default time mode if no chamber target | |
| # HEAT_SOAK BED_TEMP=110 SOAK_TIME=1800 ; 30 min soak (time) | |
| # HEAT_SOAK MODE=chamber CHAMBER_TEMP=45 ; wait for chamber 45°C + hold | |
| # HEAT_SOAK CHAMBER_TEMP=45 ; auto: uses chamber if found, else time | |
| # HEAT_SOAK CHAMBER_TEMP=45 SENSOR="heater_generic chamber" ; explicit sensor | |
| # HEAT_SOAK BED_TEMP=110 FAN_SPEED=192 ; ~75% fan | |
| # | |
| # Installation | |
| # - Save as heat_soak_smart.cfg and [include heat_soak_smart.cfg] in printer.cfg | |
| # - FIRMWARE_RESTART | |
| # - Optional beeper (see bottom); macro auto-detects BEEP | |
| # | |
| # ============================================================================ | |
| [gcode_macro HEAT_SOAK] | |
| description: Heat bed and soak chamber (time or chamber sensor) | |
| # Tunables with sane defaults | |
| variable_mode: 0 # 0=time, 1=chamber | |
| variable_target_bed: 100 | |
| variable_target_chamber: 0 | |
| variable_soak_time: 900 | |
| variable_fan_speed: 128 | |
| variable_update_interval: 30 # seconds between UI updates/motions | |
| variable_move_distance: 10 # mm sweep distance (X) | |
| variable_elapsed: 0 | |
| variable_hold: 0 | |
| variable_hold_required: 120 # seconds at/above chamber target | |
| variable_timeout: 3600 # max seconds to wait for chamber | |
| variable_sensor_key: "" | |
| variable_running: 0 | |
| gcode: | |
| {% set bed_temp = params.BED_TEMP|default(100)|int %} | |
| {% set soak_time = params.SOAK_TIME|default(900)|int %} | |
| {% set fan_speed = params.FAN_SPEED|default(128)|int %} | |
| {% set chamber_temp = params.CHAMBER_TEMP|default(0)|int %} | |
| {% set timeout = params.CHAMBER_TIMEOUT|default(3600)|int %} | |
| {% set hold_req = params.CHAMBER_HOLD|default(120)|int %} | |
| {% set req_mode = params.MODE|default("auto")|lower %} | |
| {% set sensor = params.SENSOR|default("") %} | |
| {% if bed_temp < 0 or bed_temp > 150 %}{action_raise_error("BED_TEMP 0-150")}{% endif %} | |
| {% if soak_time < 60 or soak_time > 7200 %}{action_raise_error("SOAK_TIME 60-7200s")}{% endif %} | |
| {% if fan_speed < 0 or fan_speed > 255 %}{action_raise_error("FAN_SPEED 0-255")}{% endif %} | |
| {% if chamber_temp < 0 or chamber_temp > 90 %}{action_raise_error("CHAMBER_TEMP 0-90")}{% endif %} | |
| # Resolve chamber sensor | |
| {% set ns = namespace(key=sensor) %} | |
| {% if not ns.key or ns.key not in printer %} | |
| {% for k in ["temperature_sensor chamber","temperature_sensor enclosure", | |
| "heater_generic chamber","heater_generic enclosure", | |
| "temperature_fan chamber","temperature_fan enclosure"] %} | |
| {% if k in printer %}{% set ns.key = k %}{% break %}{% endif %} | |
| {% endfor %} | |
| {% endif %} | |
| {% set has_chamber = ns.key and (ns.key in printer) %} | |
| # Decide mode | |
| {% if req_mode == "chamber" and not has_chamber %} | |
| {action_raise_error("MODE=chamber requires a chamber SENSOR; pass SENSOR=... or define a chamber sensor")} | |
| {% endif %} | |
| {% if req_mode == "chamber" and chamber_temp <= 0 %} | |
| {action_raise_error("MODE=chamber requires CHAMBER_TEMP>0")} | |
| {% endif %} | |
| {% set mode_num = 1 if (req_mode=="chamber" or (req_mode=="auto" and has_chamber and chamber_temp>0)) else 0 %} | |
| M117 Heat Soak: Heating bed to {bed_temp}°C | |
| M118 Heat Soak: Mode={("chamber" if mode_num==1 else "time")}; Sensor='{{ns.key|default("none")}}' | |
| M118 Heat Soak: Targets → bed={bed_temp}°C; {("chamber="+chamber_temp|string+"°C" if mode_num==1 else "soak="+soak_time|string+"s")} | |
| # Heat bed and wait | |
| M190 S{bed_temp} | |
| # Home if needed, center toolhead, set fan | |
| {% if printer.toolhead.homed_axes != "xyz" %} G28 {% endif %} | |
| G90 | |
| {% set cx = printer.toolhead.axis_maximum.x/2 %} | |
| {% set cy = printer.toolhead.axis_maximum.y/2 %} | |
| {% set safe_z = 10 %} | |
| G1 X{cx} Y{cy} Z{safe_z} F6000 | |
| {% if fan_speed>0 %} M106 S{fan_speed} {% endif %} | |
| # Seed state for ticker | |
| SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=mode VALUE={mode_num} | |
| SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=target_bed VALUE={bed_temp} | |
| SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=target_chamber VALUE={chamber_temp} | |
| SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=soak_time VALUE={soak_time} | |
| SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=fan_speed VALUE={fan_speed} | |
| SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=update_interval VALUE={printer["gcode_macro HEAT_SOAK"].update_interval} | |
| SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=move_distance VALUE={printer["gcode_macro HEAT_SOAK"].move_distance} | |
| SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=hold_required VALUE={hold_req} | |
| SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=timeout VALUE={timeout} | |
| SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=sensor_key VALUE="'{{ ns.key|default("") }}'" | |
| SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=elapsed VALUE=0 | |
| SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=hold VALUE=0 | |
| SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=running VALUE=1 | |
| M118 Heat Soak: Started. Use HEAT_SOAK_CANCEL to abort. | |
| UPDATE_DELAYED_GCODE ID=_HEAT_SOAK_TICK DURATION=1 | |
| # Periodic tick driving countdown or chamber hold | |
| [delayed_gcode _HEAT_SOAK_TICK] | |
| initial_duration: 0 | |
| gcode: | |
| {% set m = printer["gcode_macro HEAT_SOAK"] %} | |
| {% if not m.running %} | |
| UPDATE_DELAYED_GCODE ID=_HEAT_SOAK_TICK DURATION=0 | |
| {% endif %} | |
| {% set e = m.elapsed|int %} | |
| {% set iv = m.update_interval|int %} | |
| {% set mode = m.mode|int %} | |
| {% set cx = printer.toolhead.axis_maximum.x/2 %} | |
| {% set move = m.move_distance|float %} | |
| {% set ready = 0 %} | |
| {% set chamber = None %} | |
| {% if m.sensor_key and m.sensor_key in printer %} | |
| {% set chamber = printer[m.sensor_key].temperature|float %} | |
| {% endif %} | |
| {% if mode==1 %} | |
| {% set hold = m.hold|int %} | |
| {% if chamber is not none and chamber >= m.target_chamber|float %} | |
| {% set hold = hold + 1 %} | |
| {% else %} | |
| {% set hold = 0 %} | |
| {% endif %} | |
| SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=hold VALUE={hold} | |
| {% if hold >= m.hold_required|int or e >= m.timeout|int %} | |
| {% set ready = 1 %} | |
| {% endif %} | |
| {% else %} | |
| {% if e >= m.soak_time|int %} | |
| {% set ready = 1 %} | |
| {% endif %} | |
| {% endif %} | |
| # UI + gentle motion every iv seconds | |
| {% if e % iv == 0 %} | |
| {% if mode==1 %} | |
| M117 Heat Soak: Chamber {chamber|default(-1)|round(1)} / {m.target_chamber}°C | |
| M118 Heat Soak: chamber={chamber|default(-1)|round(1)}°C, hold={m.hold}/{m.hold_required}s | |
| {% else %} | |
| M117 Heat Soak: {m.soak_time - e}s left | |
| M118 Heat Soak: elapsed={e}s / {m.soak_time}s | |
| {% endif %} | |
| {% set dir = -1 if ((e // iv) % 2 == 0) else 1 %} | |
| {% set x = cx + dir * (move/2) %} | |
| G1 X{x} F3000 | |
| {% endif %} | |
| {% if ready %} | |
| M107 | |
| G1 X{cx} Y{printer.toolhead.axis_minimum.y + 10} Z10 F6000 | |
| M117 Heat Soak: Complete! | |
| M118 Heat Soak: Complete. | |
| {% if 'gcode_macro BEEP' in printer %} BEEP ; G4 P300 ; BEEP ; G4 P600 ; BEEP {% endif %} | |
| SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=running VALUE=0 | |
| UPDATE_DELAYED_GCODE ID=_HEAT_SOAK_TICK DURATION=0 | |
| {% else %} | |
| SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=elapsed VALUE={e+1} | |
| UPDATE_DELAYED_GCODE ID=_HEAT_SOAK_TICK DURATION=1 | |
| {% endif %} | |
| [gcode_macro HEAT_SOAK_CANCEL] | |
| gcode: | |
| UPDATE_DELAYED_GCODE ID=_HEAT_SOAK_TICK DURATION=0 | |
| SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=running VALUE=0 | |
| M107 | |
| M117 Heat Soak: Canceled | |
| M118 Heat Soak: Canceled | |
| # Optional beeper | |
| # [output_pin beeper] | |
| # pin: YOUR_BEEPER_PIN | |
| # pwm: True | |
| # cycle_time: 0.0005 | |
| # | |
| # [gcode_macro BEEP] | |
| # gcode: | |
| # SET_PIN PIN=beeper VALUE=0.8 | |
| # G4 P100 | |
| # SET_PIN PIN=beeper VALUE=0 | |
| # Troubleshooting | |
| # - Instant finish: Your config had G4 issues; this macro uses a 1s ticker. | |
| # - Unknown command BEEP: define BEEP or remove beeps. | |
| # - No fan: ensure [fan] is configured. | |
| # - MODE=chamber errors: provide SENSOR=... and CHAMBER_TEMP>0, or use MODE=time. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment