# Interruptible heat soak

**URL:** <https://klipper.discourse.group/t/interruptible-heat-soak/1552>\
**Category:** Macros\
**Created:** [December 17, 2021, 12:44am UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552 "2021-12-17T00:44:52Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![blalor](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/blalor/32/175_2.png) [@blalor](https://klipper.discourse.group/u/blalor)\
**Post date:** [December 17, 2021, 12:44am UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/1 "2021-12-17T00:44:52Z")

</div>

I wanted a macro for heating the bed of my printer and allowing the bed, chamber, and frame to come up to temp for a while. My first solution worked, but `M190` and `G4` are both blocking commands: it was impossible to interrupt the macro without using the emergency stop. And sometimes I wanted to be able to move the toolhead or do minor maintenance while the printer is warming up. This is the first macro I used:

```auto
[gcode_macro HEAT_SOAK]
description: heats the bed for a while
gcode:
    {% set TARGET = params.TARGET | default(0) %}
    {% set PERIOD = (params.PERIOD | default(60) | int) %} ## minutes

    ;; put the bed and nozzle where they're a safe distance apart
    G28
    CENTER

    M84 ;; turn off steppers

    ;; run the fan to circulate air
    _SET_FAN_SPEED PERCENT=50

    M190 S{ TARGET } ; wait for bed temp

    RESPOND TYPE=command MSG="will soak for { PERIOD }m"
    G4 P{ PERIOD * 1000 * 60 }

```

After noodling on it for a while I realized I could do a timer-based macro with `delayed_gcode` that would keep the heater on and the printer from becoming idle while still allowing me to cancel it cleanly or to do other stuff like move the head around. This is that solution:

```auto
[gcode_macro HEAT_SOAK]
description: heats the bed for a while

variable_target_temp: 0
variable_stage: None ## heating -> soaking -> done -> None

## in seconds
variable_check_interval: 10
variable_soak_time_remaining: 0
variable_total_time_elapsed: 0

gcode:
    {% set TARGET = params.TARGET | default(0) | float %}
    {% set DURATION = (params.DURATION | default(5) | int) * 60 %} ## minutes to seconds

    SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=target_temp VALUE={ TARGET }
    SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=stage VALUE="'heating'"
    SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=soak_time_remaining VALUE={ DURATION }
    SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=total_time_elapsed VALUE=0

    ;; fire up the heater
    SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET={ TARGET }

    ;; run the fan to circulate air
    _SET_FAN_SPEED PERCENT=50

    ;; put the bed and nozzle where they're a safe distance apart
    G28
    CENTER

    M84 ;; turn off steppers

    UPDATE_DELAYED_GCODE ID=heat_soaker DURATION={ check_interval }

[gcode_macro CANCEL_HEAT_SOAK]
description: cancels an in-progress HEAT_SOAK cycle
gcode:
    SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=stage VALUE="'cancel'"
    UPDATE_DELAYED_GCODE ID=heat_soaker DURATION=1

[delayed_gcode heat_soaker]
; ## debug
; { action_respond_info( printer['gcode_macro HEAT_SOAK'] | tojson )}
gcode:
    {% set heat_soak = printer['gcode_macro HEAT_SOAK'] %}

    ## update total time elapsed
    {% set total_time_elapsed = heat_soak.total_time_elapsed + heat_soak.check_interval %}
    SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=total_time_elapsed VALUE={ total_time_elapsed }

    {% set stage = heat_soak.stage %}
    {% if stage == "heating" and printer.heater_bed.temperature >= heat_soak.target_temp %}
        {% set stage = "soaking" %}
    {% endif %}

    {% if stage == "soaking" %}
        ## update soak countdown
        {% set soak_time_remaining = [heat_soak.soak_time_remaining - heat_soak.check_interval, 0] | max %}
        SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=soak_time_remaining VALUE={ soak_time_remaining }

        {% if soak_time_remaining == 0 %}
            {% set stage = "done" %}
        {% endif %}
    {% endif %}

    SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=stage VALUE="'{ stage }'"

    {% if stage in ("done", "cancel") %}

        {% if stage == "cancel" %}
            {% set stage = "done" %}
            TURN_OFF_HEATERS
            M107 ; turn off fan

            M117 { "soak cancelled after ~%.1fm" | format(total_time_elapsed / 60.0) }
        {% else %}
            M117 { "soak complete after %.1fm" | format(total_time_elapsed / 60.0) }
        {% endif %}

        ## reset all state vars, except stage, which may be queried via the api
        SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=target_temp VALUE=0
        SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=soak_time_remaining VALUE=0
        SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=total_time_elapsed VALUE=0

    {% else %}

        {% if total_time_elapsed % 90 == 0 %}
            ## output status periodically
            {% if stage == "heating" %}
                M117 { "heating -- %.1fm elapsed" | format(total_time_elapsed / 60.0) }
            {% elif stage == "soaking" %}
                M117 { "soaking -- %.1fm remaining" | format(soak_time_remaining / 60.0) }
            {% endif %}
        {% endif %}

        ## trigger ourselves again
        UPDATE_DELAYED_GCODE ID=heat_soaker DURATION={ heat_soak.check_interval }

        ## dwell for 1ms to prevent from going idle
        G4 P1

    {% endif %}

```

There are actually 3 macros in here:

- `HEAT_SOAK` – turns on the heater and starts a timer that calls itself until the soak duration has elapsed
- `CANCEL_HEAT_SOAK` – causes the timer to cancel the soak process and turn off the heater
- `heat_soaker` (delayed g-code macro) – called periodically (every 10s) to update state and prevent the idle timeout from triggering.

It’s worth noting that after the heat soak process completes, the heater is left _on_. The idle timeout will kick in and shut everything down later on. This gives me time to start a print after soaking’s finished, without things cooling down too much. `CANCEL_HEAT_SOAK` causes the heater to be turned off immediately.

The implementation feels a bit clunky but overall I’m happy with it, I think. I welcome any suggestions or critiques.

A fun bonus: this will also allow me to use Home Assistant to send me a notification when the soak process is complete! Moonraker allows you to query a `gcode_macro` and return its variables. The `stage` variable will be what I trigger my automation on.

---

<div class="post-metadata">

**Author:** ![Dvantuyl](https://avatars.discourse-cdn.com/v4/letter/d/9d8465/32.png) [@Dvantuyl](https://klipper.discourse.group/u/Dvantuyl)\
**Post date:** [December 23, 2021, 11:26am UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/2 "2021-12-23T11:26:14Z")

</div>

Wow, it seems you have a real good grasp of macro’s. I am impressed. I am a newbie on klipper and just finished the construction of my first printer (a 350mm Voron 2.4) so I am still tinkering with settings and have a lot to learn. Thanks for providing the macro’s. They are a good example and I will probably use them. As a retired programmer I know that for any task there are a million ways to accomplish it. Klunky isn’t a bad thing as long as it works.

---

<div class="post-metadata">

**Author:** ![elpeter](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/elpeter/32/1398_2.png) [@elpeter](https://klipper.discourse.group/u/elpeter)\
**Post date:** [May 11, 2022, 10:31am UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/3 "2022-05-11T10:31:02Z")

</div>

(I know it’s an old topic but just found it on google)  
This looks awesome!! Thanks for sharing this 🙂  
I’m trying to use it and missing some other macros you call from within this one. i.e.:  
Unknown command:"\_SET\_FAN\_SPEED"  
Unknown command:“CENTER”

Is there any github you have where you have all your macros and configurations by any chance?

Thanks in advance!!

---

<div class="post-metadata">

**Author:** ![blalor](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/blalor/32/175_2.png) [@blalor](https://klipper.discourse.group/u/blalor)\
**Post date:** [May 11, 2022, 10:56am UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/4 "2022-05-11T10:56:00Z")

</div>

Sorry about that! This was supposed to be a stand-alone example. I cannot edit the first post, but what I use is below.

```auto
[gcode_macro _SET_FAN_SPEED]
gcode:
    M106 S{ (params.PERCENT | float) * 255 / 100 }

[gcode_macro CENTER]
gcode:
    G90
    G0 X{ printer.toolhead.axis_maximum.x/2 } Y{ printer.toolhead.axis_maximum.y/2 } Z{ printer.toolhead.axis_maximum.z/2 } F7200

```

---

<div class="post-metadata">

**Author:** ![elpeter](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/elpeter/32/1398_2.png) [@elpeter](https://klipper.discourse.group/u/elpeter)\
**Post date:** [May 11, 2022, 11:27am UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/5 "2022-05-11T11:27:19Z")

</div>

Awesome! Thanks so much!!

---

<div class="post-metadata">

**Author:** ![hergtoler](https://avatars.discourse-cdn.com/v4/letter/h/49beb7/32.png) [@hergtoler](https://klipper.discourse.group/u/hergtoler)\
**Post date:** [June 9, 2022, 12:29am UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/6 "2022-06-09T00:29:06Z")

</div>

Thanks for the macro. It is working well for me (standalone), and I’ve been able modify it to use my chamber sensor for the target.

What I haven’t been able to figure out is how to use this as part of my PRINT\_START macro. It seems like HEAT\_SOAK doesn’t block, so PRINT\_START continues while “heat\_soaker” is still being ran. Reading your comment, it sounds like you are using using your notification to let you know when you can start your print, so it’s different than what I’m trying.

---

<div class="post-metadata">

**Author:** ![blalor](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/blalor/32/175_2.png) [@blalor](https://klipper.discourse.group/u/blalor)\
**Post date:** [June 9, 2022, 1:48am UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/7 "2022-06-09T01:48:06Z")

</div>

I’m glad it’s useful! Yeah, I only use it as a standalone process. I don’t think there’s any way to “join” to a background task in Klipper, although that’d be useful!

So the way I use this is to start the heat soak – say 15m at 110°C – when I’m getting ready to start a job. When I get the notification it’s done, then I actually start the job. If I’m doing multiple small prints I only need to soak once.

---

<div class="post-metadata">

**Author:** ![miklschmidt](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/miklschmidt/32/974_2.png) [@miklschmidt](https://klipper.discourse.group/u/miklschmidt)\
**Post date:** [June 13, 2022, 11:04am UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/8 "2022-06-13T11:04:40Z")

</div>

Add a chamber thermistor, add the macro call in START\_PRINT/PRINT\_START, add HEAT\_SOAK and TEMPERATURE\_WAIT. Done? It won’t wait for the specified time but it will wait for the chamber to reach the temperature you want.

---

<div class="post-metadata">

**Author:** ![mwu](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/mwu/32/1585_2.png) [@mwu](https://klipper.discourse.group/u/mwu)\
**Post date:** [June 13, 2022, 1:33pm UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/9 "2022-06-13T13:33:28Z")

</div>

If you want to use this during a print start script and wait for the heat soak, here are the changes I made:

- Put a `PAUSE` after the safe distance apart code in the HEAT\_SOAK macro
- Put a `CANCEL_PRINT` in the `if stage == "cancel"` condition
- Put a `RESUME` in the `else` case for the above cancel condition check
- Optional: If you have additional gcode to run after the heat soak completes and before the print resumes, add a `SOAK_COMPLETE` call right before `RESUME` and create the `SOAK_COMPLETE` macro to do whatever else you need to do

Note that if you have customized `PAUSE`, ‘CANCEL\_PRINT’, or `RESUME` macros, and you used `rename_existing`, you can call the original instead of the customized macros if you desire. Here is my alteration of the `HEAT_SOAK` macro:

```auto
[gcode_macro HEAT_SOAK]
description: heats the bed for a while

variable_target_temp: 0
variable_stage: None ## heating -> soaking -> done -> None

## in seconds
variable_check_interval: 10
variable_soak_time_remaining: 0
variable_total_time_elapsed: 0

gcode:
    {% set TARGET = params.TARGET | default(0) | float %}
    {% set DURATION = (params.DURATION | default(15) | int) * 60 %} ## minutes to seconds

    SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=target_temp VALUE={ TARGET }
    SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=stage VALUE="'heating'"
    SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=soak_time_remaining VALUE={ DURATION }
    SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=total_time_elapsed VALUE=0

    ;; fire up the heater
    SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET={ TARGET }

    ;; run the fan to circulate air
    _SET_FAN_SPEED PERCENT=50
    ;; put the bed and nozzle where they're a safe distance apart
    G28
    CENTER

    M84 ;; turn off steppers

    UPDATE_DELAYED_GCODE ID=heat_soaker DURATION={ check_interval }

    ;; pause the print during heat soak
    BASE_PAUSE

[gcode_macro CANCEL_HEAT_SOAK]
description: cancels an in-progress HEAT_SOAK cycle
gcode:
    SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=stage VALUE="'cancel'"
    UPDATE_DELAYED_GCODE ID=heat_soaker DURATION=1

[delayed_gcode heat_soaker]
; ## debug
; { action_respond_info( printer['gcode_macro HEAT_SOAK'] | tojson )}
gcode:
    {% set heat_soak = printer['gcode_macro HEAT_SOAK'] %}

    ## update total time elapsed
    {% set total_time_elapsed = heat_soak.total_time_elapsed + heat_soak.check_interval %}
    SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=total_time_elapsed VALUE={ total_time_elapsed }

    {% set stage = heat_soak.stage %}
    {% if stage == "heating" and printer.heater_bed.temperature >= heat_soak.target_temp %}
        {% set stage = "soaking" %}
    {% endif %}

    {% if stage == "soaking" %}
        ## update soak countdown
        {% set soak_time_remaining = [heat_soak.soak_time_remaining - heat_soak.check_interval, 0] | max %}
        SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=soak_time_remaining VALUE={ soak_time_remaining }

        {% if soak_time_remaining == 0 %}
            {% set stage = "done" %}
        {% endif %}
    {% endif %}

    SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=stage VALUE="'{ stage }'"

    {% if stage in ("done", "cancel") %}

        {% if stage == "cancel" %}
            {% set stage = "done" %}
            TURN_OFF_HEATERS
            M107 ; turn off fan

            M117 { "soak cancelled after ~%.1fm" | format(total_time_elapsed / 60.0) }

            ;; heat soak was cancelled -- also cancel the print
            CANCEL_PRINT
        {% else %}
            M117 { "soak complete after %.1fm" | format(total_time_elapsed / 60.0) }

            ;; heat soak is complete. Call the complete macro prior to starting the print
            SOAK_COMPLETE
            ;; resume / start the print
            BASE_RESUME
        {% endif %}

        ## reset all state vars, except stage, which may be queried via the api
        SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=target_temp VALUE=0
        SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=soak_time_remaining VALUE=0
        SET_GCODE_VARIABLE MACRO=HEAT_SOAK VARIABLE=total_time_elapsed VALUE=0

    {% else %}

        {% if total_time_elapsed % 90 == 0 %}
            ## output status periodically
            {% if stage == "heating" %}
                M117 { "heating -- %.1fm elapsed" | format(total_time_elapsed / 60.0) }
            {% elif stage == "soaking" %}
                M117 { "soaking -- %.1fm remaining" | format(soak_time_remaining / 60.0) }
            {% endif %}
        {% endif %}

        ## trigger ourselves again
        UPDATE_DELAYED_GCODE ID=heat_soaker DURATION={ heat_soak.check_interval }

        ## dwell for 1ms to prevent from going idle
        G4 P1

    {% endif %}

```

---

<div class="post-metadata">

**Author:** ![blalor](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/blalor/32/175_2.png) [@blalor](https://klipper.discourse.group/u/blalor)\
**Post date:** [June 13, 2022, 5:43pm UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/10 "2022-06-13T17:43:03Z")

</div>

ooh, that’s really clever!

---

<div class="post-metadata">

**Author:** ![akashrpatel](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/akashrpatel/32/1914_2.png) [@akashrpatel](https://klipper.discourse.group/u/akashrpatel)\
**Post date:** [August 2, 2022, 7:22am UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/11 "2022-08-02T07:22:33Z")

</div>

hello, just built a voron 2.4r2, i’m having problems with parts staying stuck when initially printing, so I thought heat soak would be a good idea. I ran across your post. I added the macro to my printer.cfg.

I run heat\_soak, my toolhead homes and moves to the center, but the heater\_bed never turn on.

I’m assuming I need to add the temperature I want to the variable\_target\_temp: 0. I set it to 110, but it does not turn on, any ideas?

thank you!

---

<div class="post-metadata">

**Author:** ![blalor](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/blalor/32/175_2.png) [@blalor](https://klipper.discourse.group/u/blalor)\
**Post date:** [August 2, 2022, 11:13am UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/12 "2022-08-02T11:13:33Z")

</div>

> [@akashrpatel](#):
>
> I run heat\_soak, my toolhead homes and moves to the center, but the heater\_bed never turn on.
> 
> I’m assuming I need to add the temperature I want to the variable\_target\_temp: 0. I set it to 110, but it does not turn on, any ideas?

It’s a parameterized macro. You need to specify the temperature and duration, like `HEAT_SOAK TARGET=110 DURATION=30` to soak for 30m at 110°C.

---

<div class="post-metadata">

**Author:** ![garethky](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/garethky/32/956_2.png) [@garethky](https://klipper.discourse.group/u/garethky)\
**Post date:** [August 18, 2022, 4:53am UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/13 "2022-08-18T04:53:59Z")

</div>

Thanks @blalor and @mwu for this script! I’ve wanted a way to do this so badly I hacked up klippy to do it: [Add MAX\_SLOPE capability to TEMPERATURE\_WAIT by garethky · Pull Request #4796 · Klipper3d/klipper · GitHub](https://github.com/Klipper3d/klipper/pull/4796) But I gave up on getting the PR merged because Kevin has not so much time for this kind of thing. Bit I still wanted this functionality!

My version of the script is here: [klipper-voron2.4-config/printer\_data/config/heatsoak.cfg at mainline · garethky/klipper-voron2.4-config · GitHub](https://github.com/garethky/klipper-voron2.4-config/blob/mainline/printer_data/config/heatsoak.cfg)  
It uses a second temperature sensor and stops the heat soak when the rate of temperature change on that sensor drops below a target. I have the default set to a rate of change of less than 0.75 degrees C per minute. This means that if the bed is already hot the soak time will be shorter. You call it like this:

```auto
HEAT_SOAK HEATER='heater_bed' TARGET=100 SOAKER='temperature_sensor top_bed' RATE=0.75

```

You also have to split your print start script where you call HEAT\_SOAK. The call to PAUSE will not stop the print start script from running, it just stops the next line of GCode in the file being printed from running. So now I have:

```auto
PRINT_PREPARE [...] ; ends with a call to HEAT_SOAK
PRINT_START [...] ; does mesh bed leveling after heat soak

```

The only real gap now is that we cant configure a `smooth_time` on temperature sensors so the data we get from the sensor is noisy. I might do something about that but honestly this is working fine for my needs.

---

<div class="post-metadata">

**Author:** ![SinisterRj](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/sinisterrj/32/6252_2.png) [@SinisterRj](https://klipper.discourse.group/u/SinisterRj)\
**Post date:** [August 18, 2022, 12:30pm UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/14 "2022-08-18T12:30:57Z")

</div>

Hello guys,

I’m trying to use your macro @garethky but klipper is giving me this erro os starting:

Error loading template ‘delayed\_gcode heat\_soaker:gcode’: TemplateSyntaxError: unexpected ‘end of print statement’

Any idea?

---

<div class="post-metadata">

**Author:** ![SinisterRj](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/sinisterrj/32/6252_2.png) [@SinisterRj](https://klipper.discourse.group/u/SinisterRj)\
**Post date:** [August 18, 2022, 1:47pm UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/15 "2022-08-18T13:47:16Z")

</div>

I found the problem:

Must change line 98 from

```auto
{action_respond_info("Heat soak timed out after ~%.1fm" | format(total_time_elapsed / 60.0)) %}

```

to

```auto
{action_respond_info("Heat soak timed out after ~%.1fm" % (format(total_time_elapsed / 60.0)))}

```

---

<div class="post-metadata">

**Author:** ![garethky](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/garethky/32/956_2.png) [@garethky](https://klipper.discourse.group/u/garethky)\
**Post date:** [August 18, 2022, 4:35pm UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/16 "2022-08-18T16:35:07Z")

</div>

Ahh, sorry about that, updated on github.

Is there a way to do a callback in a macro? I could add an argument to HEAT\_SOAK that calls another macro when it reaches the done condition.

---

<div class="post-metadata">

**Author:** ![garethky](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/garethky/32/956_2.png) [@garethky](https://klipper.discourse.group/u/garethky)\
**Post date:** [August 18, 2022, 4:39pm UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/17 "2022-08-18T16:39:22Z")

</div>

Oh yeah, callbacks work:

```auto
[gcode_macro MACRO_A]
gcode:
    {action_respond_info("Macro A")}
    {% set CALLBACK = (params.CALLBACK | string) %}
    {CALLBACK}

[gcode_macro MACRO_B]
gcode:
    {action_respond_info("Macro B")}

```

```auto
09:38:19 $ macro_a CALLBACK=MACRO_B
09:38:19 // Macro A
09:38:19 // Macro B

```

---

<div class="post-metadata">

**Author:** ![garethky](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/garethky/32/956_2.png) [@garethky](https://klipper.discourse.group/u/garethky)\
**Post date:** [August 19, 2022, 6:53am UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/18 "2022-08-19T06:53:16Z")

</div>

Ok I had yet another bug with that `action_respond_info` statement that I fixed. No more `format()` calls in the code now.

I implemented a callback in `heat_soak`. My `print_start` passes a callback to `heat_soak`. `heat_soak` then calls that macro when it reaches the done state. This means the 2 parts of the print start process are chained together and I only have to call `print_start` in my slicer.

I also added a callback to `print_start` itself, so it can start running a test print macro after the entire startup process finishes. Running the pressure advance test macro after print start looks like this:

```auto
{% set PA_CAL_CALL -%}
    PA_CAL BED={BED_TEMP} EXTRUDER={EXTRUDER_TEMP} EXTRUSION_FACTOR={ER}
{%- endset %}
PRINT_START BED_TEMP={BED_TEMP} EXTRUDER_TEMP={EXTRUDER_TEMP} CALLBACK='{PA_CAL_CALL}'

```

The `-%}` and `{%-` are how you remove whitespace around the value, so the result is 1 line, this is required for this trick to work.

I didn’t find a way to nest the callbacks, with the way jinja and klipper handle string escaping I don’t see how to keep the quotes in the strings intact. I can think of more devious ways of achieving 🙊 this but you probably wont like them.

Programming macros with callbacks destroys the intended simplicity of the language. Some sort of barrier statement that blocks further evaluation of a macro is probably a better solution. Like the `await` keyword.

---

<div class="post-metadata">

**Author:** ![SinisterRj](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/sinisterrj/32/6252_2.png) [@SinisterRj](https://klipper.discourse.group/u/SinisterRj)\
**Post date:** [August 21, 2022, 3:39am UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/19 "2022-08-21T03:39:49Z")

</div>

Hy @garethky , I made some changes in your macro. The most important are:  
mintemp variable: my chamber temp sensor takes too long to react upon the bed heating, so I need to define a minimum temperature to finish the heatsoak, otherwise it finishes in the beginning;  
check\_interval: defined two check\_interval, one for heating stage, which is fast and need more frequent update and another to soak stage, where longer updates helps with delta temperature calculations.

I hope you enjoy the suggestions.

---

<div class="post-metadata">

**Author:** ![garethky](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/garethky/32/956_2.png) [@garethky](https://klipper.discourse.group/u/garethky)\
**Post date:** [August 28, 2022, 5:58pm UTC](https://klipper.discourse.group/t/interruptible-heat-soak/1552/20 "2022-08-28T17:58:54Z")

</div>

I like it!

The MIN\_TEMP idea makes sense. I could also see calling the macro with a formula, like `MIN_TEMP=(TARGET_TEMP * 0.85)` so it scales with the target temp. I can also make it so that its optional, so if you don’t provide a value it is not checked.

Would smoothing the temperature reading have made a difference? I’ve been thinking about making the interval 1 second, gathering a reading in an array and then computing an average over 5 seconds. Basically doing the smoothing in the macro. I’m not exactly sure there are enough array operators to do this in the language though, you would ideally want something like `push()` and `pop()` or `slice()`.

This would uncouple the interval that the temp gets polled and the interval that the messages are posted, so that would add the same kind of interval variables that you added.

[Next page](https://klipper.discourse.group/t/interruptible-heat-soak/1552.md?page=2)
