Can you list an example of the jinja format to check the status of endstops? I have a remote probe and I want to know if the probe is connected to use boolean in a macro.
Here it is:
[gcode_macro A_PRBDROP]
gcode:
QUERY_PROBE
M400
QUERY_PROBE
M400
_probedropstatus
[gcode_macro _probedropstatus]
gcode:
{% if "xy" not in printer.toolhead.homed_axes %}
G28 YX
{% endif %}
{% if printer.probe.last_query %}
RESPOND TYPE=echo MSG="Probe dropped"
P_Good
G4 P1000
M400
{% else %}
RESPOND TYPE=echo MSG="Dropping off probe!"
A_ProbeDrop
{% endif %}
[gcode_macro A_PRBPICK]
gcode:
QUERY_PROBE
M400
QUERY_PROBE
M400
_probepickstatus
[gcode_macro _probepickstatus]
gcode:
{% if "xy" not in printer.toolhead.homed_axes %}
G28 YX
{% endif %}
{% if printer.probe.last_query %}
RESPOND TYPE=echo MSG="Picking up the Probe!!"
A_ProbePick
{% else %}
RESPOND TYPE=echo MSG="Probe connected!!"
P_Good
G4 P1000
M400
{% endif %}
I had to add 2 query_probe as I was getting miss-fires! (thought probe was attached and was not!) No problems since adding.
Thank you for taking the time to write this tutorial.
-Beau
I use delayed_gcode macros in some cases, I can’t find information anywhere :-(. Is there any way to know if delayed_gcode is now running with a specific ID? I.e. some query which will return me True or False depending on whether delayed_gcode is running now or not.
i dont think (but thats assumption at my point) that there can be 2 macro’s active at ones. also i dont think interrups are a thing (might also be wrong here). the reason because it’s based arround gcode instructions.
something to add to the tutorial:
The SET_GCODE_VARIABLE
and SAVE_VARIABLE
commands in klipper require that you pass them Python literals. Simple strings and numbers are pretty easy but what about complex types like arrays or objects? This has been a stumbling block for more ambitious macros. If you have tried the tojson
filter you have probably not had a lot of luck. Here is a way to do it using the pprint
filter:
[gcode_macro TEST_STUFF]
variable_hello: None
gcode:
{action_respond_info(printer['gcode_macro TEST_STUFF'].hello | pprint)}
{% set my_array = [1, 2.0, "heater_bed", None, 3] %}
{% set my_object = {"name": "Hello World it's \"Fred\" calling", "some_data": my_array} %}
{action_respond_info("my_array: %s" % (my_array | pprint | replace("\n", "") | replace("\"", "\\\"")))}
{action_respond_info("my_object: %s" % (my_object | pprint | replace("\n", "") | replace("\"", "\\\"")))}
SET_GCODE_VARIABLE MACRO=TEST_STUFF VARIABLE=hello VALUE="{my_object | pprint | replace("\n", "") | replace("\"", "\\\"")}"
Output of 2 calls:
$ TEST_STUFF
// None
// my_array: [1, 2.0, 'heater_bed', None, 3]
// my_object: {'name': 'Hello World it\'s \"Fred\" calling', 'some_data': [1, 2.0, 'heater_bed', None, 3]}
$ TEST_STUFF
// {'name': 'Hello World it\'s "Fred" calling',
// 'some_data': [1, 2.0, 'heater_bed', None, 3]}
// my_array: [1, 2.0, 'heater_bed', None, 3]
// my_object: {'name': 'Hello World it\'s \"Fred\" calling', 'some_data': [1, 2.0, 'heater_bed', None, 3]}
-
pprint
converts Python objects to their Python Literal format but it adds newlines when the object is too large to comfortably read. -
replace("\n", "")
deletes the newlines -
replace("\"", "\\\"")
escapes double quotes in the string so it can be passed to a klipper command wrapped in double quotes.
The end result is a valid Python literal that you can pass to the macros:
SET_GCODE_VARIABLE MACRO=TEST_STUFF VARIABLE=hello VALUE="{my_object | pprint | replace("\n", "") | replace("\"", "\\\"")}"
SAVE_VARIABLE VARIABLE="my_saved_variable" VALUE="{my_object | pprint | replace("\n", "") | replace("\"", "\\\"")}"
Is it somehow possible to use bitwise operators in Klipper macros?
Is it possible to test for 2 if statements in a macro, like you would use elif in python?
So for instance:
{% if ( printer.toolhead.position.z + 10 ) > 280 %}
G90
G0 X150 Y150 Z280 F24000 #Wipe out
{% elif printer.toolhead.position.z < 40 %}
G91
G0 Z1 F24000
G90
G0 X150 Y150 Z50 F24000 #Wipe out
{% else %}
G91
G0 Z1 F24000
G90
G0 X150 Y150 F24000 #Wipe out
G91
G0 Z9 F24000
{% endif %}
Lets test this idea:
[gcode_macro TEST]
variable_total : 0
gcode:
{% set points = range(1,4) %}
{% for point in points %}
{ action_respond_info("Calling probe on: %i" % (point)) }
TEST_PROBE POINT={point}
{% endfor %}
{ action_respond_info("Total is: %s" % (printer['gcode_macro TEST'].total)) }
[gcode_macro TEST_PROBE]
gcode:
{ action_respond_info("Probing: %i" % (params.POINT | int)) }
SET_GCODE_VARIABLE MACRO=TEST VARIABLE=total VALUE={printer['gcode_macro TEST'].total + 1}
will output:
$ TEST
// Calling probe on: 1
// Calling probe on: 2
// Calling probe on: 3
// Total is: 0
// Probing: 1
// Probing: 2
// Probing: 3
Sad times. Normal intuition about how program flow works does not work in Jinja/klipper. Macros execute completely before the sub-routines called from inside the macro are invoked. Then those are invoked in-order and so on. But where there is a will there is a way… a deep dark way
We can pause the print and use delayed_gcode
to monitor the probing until its completed, then resume the print to make use of the result:
[gcode_macro TEST]
variable_total : 0
gcode:
{% set points = range(1,4) %}
{% for point in points %}
{ action_respond_info("Calling probe on: %i" % (point)) }
TEST_PROBE POINT={point}
{% endfor %}
PAUSE
UPDATE_DELAYED_GCODE ID=_TEST_MONITOR DURATION=1
[gcode_macro TEST_PROBE]
gcode:
{ action_respond_info("Probing: %i" % (params.POINT | int)) }
SET_GCODE_VARIABLE MACRO=TEST VARIABLE=total VALUE={printer['gcode_macro TEST'].total + 1}
[delayed_gcode _TEST_MONITOR]
gcode:
{% set test_macro = printer['gcode_macro TEST']%}
{% if test_macro.total >= 3 %}
UPDATE_DELAYED_GCODE ID=_TEST_MONITOR DURATION=0 # cancel monitor
{ action_respond_info("Total is: %s" % (test_macro.total)) }
RESUME
{% endif %}
which outputs:
$ TEST
// Calling probe on: 1
// Calling probe on: 2
// Calling probe on: 3
// Probing: 1
// Probing: 2
// Probing: 3
// action:paused
// Total is: 3
// action:resumed
This has pros and cons. One of the major cons is that PAUSE
doesn’t stop macro execution, it only stops execution of the GCode file being printed. So you would have to call TEST directly from a line in the gcode file for this to work as expected during a print. The other major downside is it gets very complicated to reason about when coding.
I’ve used this technique this in my heatsoaking code: klipper-voron2.4-config/heatsoak.cfg at mainline · garethky/klipper-voron2.4-config · GitHub
There is a lot more complexity around detecting if you hit the resume or print cancel buttons while paused. The good news is that you can respond to both of those events in the delayed_gcode
which makes long running processes cancel-able without a printer restart. More in the readme: klipper-voron2.4-config/heatsoak.readme.md at mainline · garethky/klipper-voron2.4-config · GitHub
Thanks so much for replying to my question about if the SET_GCODE_VARIABLE
and SAVE_VARIABLE
commands in your example above could be used as global scope variables. I deleted my question shortly after writing it (before you responded), because I found another possible way to do this from another of your macro examples using the save_variables command. I will try out that command to see how well it would work. As you say, it is a shame that a macro is not able to do a bed probe and pick up the result in the same macro. That would have made my life a lot simpler.
You just cant get around the execution order problem. Templates are evaluated completely before any sub-routines are run. If a macro is going to provide data you are basically out of luck. You might be better off doing it in Python as a Klippy extension.
Your gcode macro test above is fantastic! It contains all the tricky stuff that I would not have been able to figure out on my own from this tutorial. Using it as a template (since the syntax is greek to me), I was able to create a set of macros that, combined with a gcode print file, will do what I need to create a bed mesh generator with backlash compensation averaging. I average 4 probes, approaching the probe point from 4 directions. Then I will assemble a 15 x 15 point output to the console to cut and past into the printer.cfg. There is still a bit of hand work to process it, but it is manageable. The proof of principle is now done. I will share the code (clunky as it is) once I have it running perfectly. I only have one error that I can not figure out and have yet to find an example. I am trying to concatenate a series of strings to a single string of 15 probe results to print to the console. You can see what I am doing just from the example gcode of one probe point:
G28 Home
G90 ;absolute
G1 Z2 F3000 ;get close to bed
;
GOTO_PROBE_POINT X_Pos=0.001 Y_Pos=134.959
;Probe sequence
PROBE_BED X_Dir=1 Y_Dir=1
SAVE_Z
PROBE_BED X_Dir=-1 Y_Dir=-1
SUM_Z
PROBE_BED X_Dir=1 Y_Dir=-1
SUM_Z
PROBE_BED X_Dir=-1 Y_Dir=1
AVG_Z Probe_Count=4
SAVE_TO_LINE Repeat=15
PRINT_LINE
I get this error: !! Error evaluating ‘gcode_macro SAVE_TO_LINE:gcode’: TypeError: ‘str’ object cannot be interpreted as an integer
Here are all the relative macros that make it work. The error is just in the SAVE_TO_LINE macro. Everything else is working.
[gcode_macro SAVE_Z]
variable_zavg : 0
gcode:
# Call after 1st PROBE
SET_GCODE_VARIABLE MACRO=SAVE_Z VARIABLE=zavg VALUE={ printer.probe.last_z_result|float }
[gcode_macro SUM_Z]
gcode:
# Call after 2nd PROBE to n-1th PROBE
SET_GCODE_VARIABLE MACRO=SAVE_Z VARIABLE=zavg VALUE={printer['gcode_macro SAVE_Z'].zavg + printer.probe.last_z_result|float }
[gcode_macro AVG_Z]
gcode:
# Call after last PROBE with Probe_Count=2-8
SUM_Z
SET_GCODE_VARIABLE MACRO=SAVE_Z VARIABLE=zavg VALUE={printer['gcode_macro SAVE_Z'].zavg / params.PROBE_COUNT|float }
[gcode_macro CLR_LINE]
variable_row_line : ""
gcode:
SET_GCODE_VARIABLE MACRO=CLR_LINE VARIABLE=row_line VALUE=""
[gcode_macro PRINT_LINE]
gcode:
M118 {printer['gcode_macro CLR_LINE'].row_line}
CLR_LINE
[gcode_macro SAVE_TO_LINE]
gcode:
{% set count = range(1,params.REPEAT) %}
{% for point in count %}
SET_GCODE_VARIABLE MACRO=CLR_LINE VARIABLE=row_line VALUE={printer['gcode_macro CLR_LINE'].row_line|string + ", " + printer['gcode_macro SAVE_Z'].zavg|round(5)|string }
{% endfor %}
Thanks again for the help you gave that is making this possible. I appreciate any insites on the correct syntax to get rid of this last error.
In your SAVE_TO_LINE macro, try changing the first line to:
{% set count = range(1,params.REPEAT|int) %}
Thank you. That was a stupid error on my part. I was looking for the error on the wrong line. I took a more systematic debug process now. After dozens of attempts at still trying to append text to a string variable, I have made no progress in getting past this error message:
!! Malformed command 'SET_GCODE_VARIABLE MACRO=CLR_LINE VARIABLE=row_line VALUE=, 0.08487'
The same command can save numbers, but seems to choke on strings. Perhaps I do not understand some syntax difference for strings. It is the only thing now keeping me from printing.
Here is the stripped down and expanded debug code for the macros in question:
[gcode_macro CLR_LINE]
variable_row_line : ""
gcode:
#SET_GCODE_VARIABLE MACRO=CLR_LINE VARIABLE=row_line VALUE="" # this blows up with internal error unexpected EOF
[gcode_macro SAVE_TO_LINE]
gcode:
{% set TEMPA = printer['gcode_macro CLR_LINE'].row_line %}
{% set TEMPB = printer['gcode_macro SAVE_Z'].zavg|round(5)|string %}
{% set TEMPC = TEMPA + ", " + TEMPB %}
M118 { TEMPC } #debug -- good to here
SET_GCODE_VARIABLE MACRO=CLR_LINE VARIABLE=row_line VALUE={ TEMPC } # this errors with malformed command
I really appreciate your help with learning how to do this.
The value of your VALUE parameter contains a comma and a space so you need to wrap it in quotes. Try this:
SET_GCODE_VARIABLE MACRO=CLR_LINE VARIABLE=row_line VALUE='"{ TEMPC }"'
Thank you so much! That was the trick and everything is working now. Now to flush out the gcode and complete the bed leveling experiments.
Ok… question…
Say I have a macro definition and I want to pass a parameter to it. I imagine it something like this:
TEST X
X IS the parameter and value you could say…
I played around with this:
Function:
[gcode_macro TEST]
gcode:
{ action_respond_info(“OUTPUT: {}”.format(params)) }
Call made: TEST XYZ=1
and the output is: ‘OUTPUT: {‘XYZ’: ‘1’}’ … the array… again… not what im looking for.
I suppose one way you could look at it as I want to pass a parameter name only to TEST and not a value along with it… so in sudo code something like this:
[gcode_macro TEST]
gcode:
{ action_respond_info(“OUTPUT: {}”.format(params[0].name)) }
and get the output: ‘OUTPUT: X’
How can I accomplish this?
Thanks so much for taking the time to create this resource…looking forward to more.
Ps. I found Chat gpt to be somewhat knowledgeable about klipper macros, though often uses python rather than jinja. You might find it useful in developing new sections quickly.
It’s funny you mention that. I did that too out of pure curiosity. My impression was that ChatGPT sounds like it’s knowledgeable about Klipper macros, but it is actually not. I could not get it to produce even a simple functional macro even after multiple attempts to coach it. I’ve found that with tentpole programming languages like Python and Perl, ChatGPT is capable of producing working code, though will often have bugs and errors. As long as you know the language well enough to recognize them, or at least to formulate prompts that will get ChatGPT to recognize them, you can usually run its generated code through multiple iterations to end up with something functional that does what you intended. Not so with Klipper macros, at least not in my admittedly superficial experience. In addition to mixing python and jinja like you mentioned, it will also create “pseudo-jinja” by recasting python expressions in a jinja-like construction, even though jinja has no corresponding syntax or function. On top of that, Klipper uses a somewhat non-standard implementation of jinja and ChatGPT seems completely unaware of that.
So, all that to say, for all the lurkers out there, please do not try to generate Klipper macros with ChatGPT and then come in here wondering why they don’t work.
Last week I had questions from somebody who wanted to make a 15 foot (4.5m) high statue that consisted of interlocking 3D printed PLA segments that had a poured concrete core (which would mass 6 tonnes). He worked it all out with ChatGPT but he wanted people’s opinions about his assumptions. He got angry when told to talk to somebody who made concrete statues/columns for a living and argued that what came out of ChatGPT seemed right.
I haven’t used ChatGPT myself but it seems to produce very confidence inspiring answers.