Updating Macros without Restarting Klipper

I recently created DynamicMacros, a way to update simple macros without restarting Klipper. It works by having the “dynamic” macros in a separate file, not included in your printer.cfg. Then, a [dynamicmacros] config section references that file. Whenever a dynamic macro is reloaded with the DYNAMIC_MACRO command, it reads the provided configuration files and executes the macro. This way, you can edit your macros without restarting Klipper.

Currently, dynamic macros support the following:

  • Updating without restarting Klipper
  • Standard [gcode_macro] syntax (if statements, for loops, etc)
  • Receiving parameters
  • Accessing the printer object
  • Storing variables in the macro
  • Recursion
  • Receiving variable updates
  • Running Python code from within a macro
  • Dynamic delayed_gcode syntax

The following features are in development:

  • “Sandboxed” macros for security (preventing running Python and/or accessing the printer object)
  • Standard delayed_gcode syntax
  • Tutorial for creating Klippy extras and using DynamicMacros to help development

The Dynamic Macros code is based off the normal macros code to provide most of these features. I would really appreciate any feedback.

Github link

Documentation link

4 Likes

Nice, thanks. Moved into the Developers section

2 Likes

Can I ask what is the problem you’re trying to solve here?

I don’t understand why the need to do a Klipper restart is an issue after an update. I’m guessing that this is to keep the printer’s home position, temperature setting with the toolhead at a specific location but I can’t think of a situation, with a macro, where this is a hard requirement.

1 Like

Where I can definitively see an advantage is during macro development.
At least, the way I do it: Produce 42 Klipper restarts until all bugs are halfway gone in 5 lines of macro.

5 Likes

I agree with @Sineos. The main use case for this would be developing a new macro or modifying one mid-print. Just now, I had to modify my attempt at a M900 macro that was accepting incorrect values, and it was nice not to have to restart, as I already had my printer preheated. While restarting Klipper in an idle state just wastes time, restarting Klipper while printing wastes filament and aborts the print.

2 Likes

UPDATE:

Now Dynamic Macros supports the following new features:

  • Adding Dynamic Macros without restart
  • Renaming macros without restart
  • Deleting macros without restart
  • Macro descriptions (with restart)
1 Like

This looks like an awesome option for developing macros.
Not having to restart between tweaks would be a major time and patience saver :clap:

This looks great. Thanks!

Would you think its possible to enable dinamic rendering of a macro that uses jinja templating? That is, to render the commands in a macro only just before they are needed.

The case is that a user wanted to do a probe move, and then use the toolhead’s halt position in the next command of the macro. It seemed tricky because all lines of their were rendered before running any of them.

Could be optional by setting a new ‘dynamic’ parameter:

[gcode_macro foobar]
dynamic: true
gcode:
    RESPOND PREFIX='info' MSG='toolhead: {printer.toolhead.position}'
    G38.2 Z=0 F=10
    RESPOND PREFIX='info' MSG='toolhead: {printer.toolhead.position}'

At the moment, both messages print the same position even if the machine moves (because all rendering happens before sending anything).

Just a thought. Thanks again!

That is a good idea. I’m thinking about getting dynamic variables by creating a dynamic printer object by default inside a dynamic macro:

[gcode_macro foobar]
gcode:
    RESPOND PREFIX='info' MSG='toolhead: {dynamic_printer.toolhead.position}'
    G38.2 Z=0 F=10
    RESPOND PREFIX='info' MSG='toolhead: {dynamic_printer.toolhead.position}'

I’ll add that to the planned features list. Thank you for your feedback!

Unfortunately, the way Jinja2 works (Jinja2 converts the macro into G-Codes), all the variables are evaluated before running the macro. However, this can mostly be worked around by using recursive macros, which Dynamic Macros supports. See here for an example.

Right, but is that a limitation from Jinja2, or from the way Klipper uses it to render the macros? Line-by-line rendering does not seem so hard conceptually, but I really don’t know that part of the codebase.

If the limitation is on Klipper’s side, do you know what part of klippy’s code would need to be modified for this? I would add the changes to a klipper fork where I added some additional CNC-related things.

Both. Jinja has multiline constructs and updates to variables that are persistent for the entire template, and Klipper has always allowed these constructs so you can’t decide to start treating macros as a list of single-line templates without breaking a ton of things.

This is valid templating (and not doing anything particularly fancy):

{% set foo = bar | default(baz) %}
{% if foo %}
  { foo }
  M107
  { foo }
{% else %}
  M115
{% endif %}
{ foo | default('M114', true) }

In order to render this “line by line” you would have to basically rearchitect Jinja so that it could return each line as it was rendered, then wait for Klipper to ask for the next line in case the state changed.

You could do something like redefine a macro as a manually separated list of individual templates that are each rendered after the previous one runs, but you can achieve the same thing without code changes by making each element of that list a separate macro.

[gcode_macro foobar]
gcode:
    _foobar_respond
    G1 X0 Y0 Z0 F10
    _foobar_respond

[gcode_macro _foobar_respond]
gcode:
    RESPOND PREFIX='info' MSG='toolhead: {printer.toolhead.position}'

1 Like

Excellent thanks a lot for the explanation!

You could do something like redefine a macro as a manually separated list of individual templates that are each rendered after the previous one runs

Thank you for the idea. I just implemented that into Dynamic Macros, and some examples of it are available in the Examples, and how to use it are in the Tutorial.

DynamicMacros v1.0 is released!

This isn’t the end of development for DynamicMacros, but I have tested it enough to consider it stable enough for developing new macros, and using its additional feature set.

Additional features that standard GCode macros don’t have:

  • Recursion
  • Receiving Variable Updates (Thank you @naikymen and @flowerysong )
  • Utility Functions
  • Editing, adding, removing, and renaming Dynamic Macros without restarting Klipper.
4 Likes

Marvellous :rainbow: with a really neat documentation too!

Would you consider adding a free-software license to the repo?

I think that MIT and GPL are compatible with klipper’s GPL license.

1 Like

Thank you!

I completely forgot about licensing the project. I just added the GPLv3 license to it (the same license as Klipper). Thank you for pointing that out.

1 Like

DynamicMacros v1.1 is released!

This release contains the most powerful feature of Dynamic Macros (so far), the ability to run Python code from within a macro.

Features:

Also, thank you @naikymen for catching the typo in the README.

3 Likes

Quick update on the status of DynamicMacros:

DynamicMacros v1.1.1 is a bugfix release, allowing users to configure where their config path is, which is a problem if multiple Klipper instances are being run.

DynamicMacros v1.2-beta contains the following:

  • Ability to sandbox macros to control whether or not Python and/or accessing the printer object is enabled
  • Traditional delayed_gcode syntax for easier migration from standard GCode macros

I also just finished the first three examples of my Klippy extra tutorial. I would really appreciate any feedback, especially on the tutorial. If anything is unclear or you think needs more explaining, please let me know here or by opening a Documentation Issue on Github.

1 Like

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.