Interruptible heat soak

This was working great for me until I updated Klipper the other day. Now the “delayed gcode” portion is not working properly and throws the following error in klippy.log:

Error evaluating ‘delayed_gcode heat_soaker:gcode’: jinja2.exceptions.UndefinedError: ‘params’ is undefined

I’ve been tinkering with it for a few days and have been stumped.

Full error:

Error evaluating 'delayed_gcode heat_soaker:gcode': jinja2.exceptions.UndefinedError: 'params' is undefined
Traceback (most recent call last):
  File "/home/pi/klipper/klippy/extras/gcode_macro.py", line 61, in render
    return str(self.template.render(context))
  File "/home/pi/klippy-env/lib/python3.9/site-packages/jinja2/environment.py", line 1090, in render
    self.environment.handle_exception()
  File "/home/pi/klippy-env/lib/python3.9/site-packages/jinja2/environment.py", line 832, in handle_exception
    reraise(*rewrite_traceback_stack(source=source))
  File "/home/pi/klippy-env/lib/python3.9/site-packages/jinja2/_compat.py", line 28, in reraise
    raise value.with_traceback(tb)
  File "<template>", line 2, in top-level template code
  File "/home/pi/klippy-env/lib/python3.9/site-packages/jinja2/environment.py", line 471, in getattr
    return getattr(obj, attribute)
jinja2.exceptions.UndefinedError: 'params' is undefined
Script running error
Traceback (most recent call last):
  File "/home/pi/klipper/klippy/extras/gcode_macro.py", line 61, in render
    return str(self.template.render(context))
  File "/home/pi/klippy-env/lib/python3.9/site-packages/jinja2/environment.py", line 1090, in render
    self.environment.handle_exception()
  File "/home/pi/klippy-env/lib/python3.9/site-packages/jinja2/environment.py", line 832, in handle_exception
    reraise(*rewrite_traceback_stack(source=source))
  File "/home/pi/klippy-env/lib/python3.9/site-packages/jinja2/_compat.py", line 28, in reraise
    raise value.with_traceback(tb)
  File "<template>", line 2, in top-level template code
  File "/home/pi/klippy-env/lib/python3.9/site-packages/jinja2/environment.py", line 471, in getattr
    return getattr(obj, attribute)
jinja2.exceptions.UndefinedError: 'params' is undefined

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/pi/klipper/klippy/extras/delayed_gcode.py", line 34, in _gcode_timer_event
    self.gcode.run_script(self.timer_gcode.render())
  File "/home/pi/klipper/klippy/extras/gcode_macro.py", line 66, in render
    raise self.gcode.error(msg)
gcode.CommandError: Error evaluating 'delayed_gcode heat_soaker:gcode': jinja2.exceptions.UndefinedError: 'params' is undefined

Full set of macros:

[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]
gcode:   
    ## debug
    ;{ action_respond_info( printer['gcode_macro HEAT_SOAK'] | tojson )}
    
    {% 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 %}

[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

Hey! thanks for creating this wonderful macro. I’ve been using it lately and it works really well.
I’m wondering if there is a way to set default parameter like the bed heater to by default be 40. that way I can call the macros with one click instead using the full sentence.

1 Like

Set it in the ‘Presets’ interface in Mainsail (I assume Fluid has the same option)

1 Like

If you have one setting that you always use you can write your own macro to wrap it. If you want a version where the only parameter is the temp to heat to, then its much the same, just add 1 parameter:

[gcode_macro GET_HOT]
variable_parameter_TARGET : 60
gcode:
    {% set TARGET = params.TARGET|default(60)|float %}
    HEAT_SOAK HEATER='heater_bed' TARGET={TARGET} SOAKER='temperature_sensor top_bed' CANCEL='CANCEL_PRINT'
```
1 Like