Table of Contents
Klipper Dynamic Macros
Never restart Klipper again for simple macros.
Klipper Dynamic Macros is an unofficial way to update macros without restarting Klipper, so you can update macros mid-print and see their results live. It also supports extra features that normal GCode Macros don't have.
Features
- Recursion
- Receiving Variables
- Utility Functions
- Variables
- Python
- Delayed GCode
- Macro Clusters
- Rendering Macros
How Normal Macros Work
Your macros are written in a .cfg
file, then included into your printer.cfg
. When Klipper restarts, it parses these files and saves the macros internally (you can't change them without restarting Klipper). When a macro is called, the cached code is interpreted and run.
How Dynamic Macros Work
Your macros are written in a .cfg
file, then the relative path to that file is configured in a [dynamicmacros]
config section. The config files are read and parsed every time you run the DYNAMIC_MACRO
command, allowing you to update macros without restarting Klipper.
Klippy Extras Tutorial
DynamicMacros also includes a tutorial on writing Klippy extras.
Get Started
Follow Setup to get started with Dynamic Macros.
Planned Features
See Development Status for the currently available features, and planned features.
Examples
See Example Macros for examples of Dynamic Macros.
More Projects
If you like this project, don't forget to give it a star! Also, check out the 3MS, a modular multimaterial system for Klipper!
Setup
Follow this guide to setup and confiure Dynamic Macros.
Install
Run in your terminal:
Add to your moonraker.conf
:
moonraker.conf | |
---|---|
Updating
First, update via Moonraker's update manager. Then run in your terminal:
Configuration
To configure Dynamic Macros, put in your printer.cfg
(or a file included in it):
Create a new file in the same folder as your printer.cfg
called dynamic.cfg
. In it, configure some macros like you normally would. Example:
dynamic.cfg | |
---|---|
Restart Klipper.
Info
Updating the [dynamicmacros]
config section requires a Klipper restart. The files listed in the macros:
parameter must be present when Klipper restarts.
Testing
If you run the command MYTEST
, the output should be:
If you runthe command HEAT_HOTEND TEMP=200
, the hotend should start heating up.
Next, edit the MYTEST
macro to output something else, like Test successful!
Save the file, but do not restart Klipper.
Run the DYNAMIC_MACRO
command to reload the macros.
Run MYTEST
again, and the output should be:
Features
Tutorial
Follow the Tutorial.
When to Restart Klipper or Reload Dynamic Macros
Dynamic Macros provides a utility DYNAMIC_MACRO
command to run macros manually, and to refresh the macros. Usage examples (assuming M900 is defined as a Dynamic Macro):
is the same as:
To refresh Dynamic Macros, just run DYNAMIC_MACRO
with no parameters.
A Klipper restart is required if:
- You changed the
description
- You changed the
initial_duration
- You changed the
repeat
A DYNAMIC_MACRO
refresh is required if:
- You created a new macro
- You renamed an existing macro
- You changed the contents of a macro
- You deleted an existing macro
Tutorial ↵
Table of Contents
Follow this tutorial to learn how to create Dynamic Macros and to use its powerful features.
Standard Macros
Before writing macros, it's a good idea to learn how to write standard gcode_macro
s. Here is an amazing guide on how to write standard macros.
Table of Contents
Converting to Dynamic Macros
Follow this tutorial to convert your standard gcode_macro
to a Dynamic Macro.
printer.cfg
First, remove the include
line in your printer.cfg
referencing the file your macros are in. For example, if your macros are in macros.cfg
, remove the line:
from your printer.cfg
.
Dynamic Macros Configuration
Next, if you don't already have one, create a [dynamicmacros]
config section in your printer.cfg
and add your macro configuration to it:
printer.cfg | |
---|---|
Fluidd and KlipperScreen
If your Fluidd macros list is empty or your dynamic macros don't appear in KlipperScreen's macros list, you can add the following parameter to your [dynamicmacros]
config section:
KlipperScreen
If you convert your LOAD_FILAMENT
and UNLOAD_FILAMENT
macros to be dynamic, KlipperScreen may not recognize them and report an error. To fix this, add blank macros to your printer.cfg
, before your [dynamicmacros]
section. Example:
Unknown config object 'gcode_macro'
If you are getting a "Unknown config object 'gcode_macro'" error after converting your macros to Dynamic Macros, move your [dynamicmacros]
section to be after your [virtual_sdcard]
section.
Nacro Names
Klipper has certain macro names reserved for core functionality. If you are experiencing errors, don't name your Dynamic Macros any of the following:
PAUSE
RESUME
CANCEL_PRINT
If you want to make those macros dynamic, first define them as a standard GCode macro:
Restart Klipper.
That's it. Your macros are now Dynamic Macros.
Using Dynamic Macros Features
Now that your macros are dynamic, you can use the powerful feature set of Dynamic Macros.
Recursion
Recursion allows a macro to call itself, something standard GCode macros can't do.
When using recursion, it's important to make sure your macro has an "end case". This is a case when the macro won't call itself again. Otherwise the macro will be stuck in an infinite loop and freeze Klipper.
Here are a few examples of recursion:
Counting to 10 | |
---|---|
Receiving Variable Updates
Receiving variable updates allows Dynamic Macros to update variables without rerunning the macro.
An example of this is getting the printer's position. A standard GCode macro will evaluate all the variables, then run the GCode. However, using three newlines (two blank lines) between code segments in Dynamic Macros will allow each segment to be evaluated at runtime, allowing for variable updates.
Utility Functions
See the link above (the subtitle) for more information on utility functions.
Python
Dynamic Macro's most powerful feature allows you to run Python code from within a macro. See the link above (the subtitle) for more information.
delayed_gcode
Dynamic Macros supports a feature similar to delayed_gcode
. See the link above (the subtitle) for more information.
Experimental Features
Warning
The features on this page are experimental, and untested or only lightly tested. Proceed at your own risk.
Ended: Tutorial
Features ↵
Recursion
Unlike normal gcode_macro
s, Dynamic Macros supports recursion (allowing a macro to call itself). For more examples, see Examples
Receiving Variable Updates
Unlike normal gcode_macro
s, Dynamic Macros supports receiving variable updates within the same macro. For example, the following macro will show the same output in both M117
s:
However, this macro will show different outputs based on the current Z position of the toolhead:
Notice the large whitespaces. Three newline characters (two blank lines) between code segments denotes a variable update. However, some variables won't be preserevd across the whitespace.
You can use the update()
function to preserve certain variables across the whitespaces:
See Examples for examples.
Utility Functions
Dynamic Macros provides a few utility functions to make Dynamic Macros easier to write.
update()
The update()
function allows to save variables across whitespaces (see Receiving Variables for more information). Example:
get_macro_variables()
The get_macro_variables()
function allows to retrieve all the variables from another macro in one line of code. Example:
update_from_dict()
The update_from_dict()
function allows for saving the output of get_macro_variables()
(or other dictionaries) across the whitespaces when Receiving Variables. Example:
Variables
Dynamic Macro variables work nearly the same way as standard gcode_macro
variables. This guide covers the differences.
SET_DYNAMIC_VARIABLE
Instead of using SET_GCODE_VARIABLE
, Dynamic Macros use SET_DYNAMIC_VARIABLE
to update Dynamic Macro variables. Example (assuming VAR_TEST
is defined from Utilities):
Python
Dynamic Macros's most powerful feature allows you to run Python code directly from within a macro.
Running Python from within a Macro
Disclaimer
This functionality allows Dynamic Macros to gain significant control over your printer and Klipper host. I am not responsible for whatever happens if you download a malicious macro.
There are three main reasons why this could be helpful:
- Allowing for deeper control of Klipper and the Klipper host
- A learning bridge for creating Klipper plugins/extras
- A tool to help develop Klipper plugins/extras without restarting Klipper
To run Python from within a Dynamic Macro, use either the python()
utility function, or the python_file()
utility function. The python()
function accepts python code as a multiline string, and the python_file()
function accepts a filename (relative to your printer.cfg
folder).
Tip
When using the python()
utiltiy function, Jinja2 (which converts the macro to GCode) may throw errors during parsing. If you are getting errors, it is recommended to switch to python_file()
.
Here are a few examples:
Python Math
macros.cfg | |
---|---|
Python File Running GCode
macros.cfg | |
---|---|
Delayed GCode
Dynamic Macros supports a feature similar to delayed_gcode
.
Configuration
To configure a DynamicMacro to be run repeatedly/after a delay, update your macro as follows:
Parameters
initial_duration
specifies the delay after Klipper starts that the macro will be run, and the duration between repeats, if repeat
is true.
repeat
specifies whether or not to repeat the macro.
Macro Clusters
Dynamic Macro clusters allow for multiple [dynamicmacros]
configuration sections, and allow for security features.
To configure a Dynamic Macro cluster, create a [dynamicmacros name]
section shown below:
This section is configured mostly the same as a standard [dynamicmacros]
config section. The differences between clusters and standard syntax are two parameters:
python_enabled
printer_enabled
By default, both of these are set to true
. If you want to disable either the Python functionality, or accessing the printer
object, you can change the configuration as shown below:
Dynamic Rendering
DynamicMacros includes a DYNAMIC_RENDER
command, useful for developing new macros.
How Macros are Run
All Klipper macros (including standard macros) are "rendered" before being run. For example, the following macro:
When the command test A=2
is run, 2
is displayed on the printer's screen.
When a macro is run, it is first rendered, then run. For example (click on the tabs):
Using the DYNAMIC_RENDER
command
The DYNAMIC_RENDER
command is used the same way as the DYNAMIC_MACRO
command. However, instead of running the macro, it will render the macro, as explained above, then print out the result to the Klipper console. Example:
will display
Ended: Features
Klippy Extras ↵
Start Writing Klippy Extras
While DynamicMacros makes Klipper macros much more powerful, sometimes a Klippy extra is required for more functionality. In this tutorial, you will learn how to develop a Klippy extra and test it using DynamicMacros.
Info
To write a Klipper extra, you should be fluent in Python.
Klipper vs Klippy
To limit confusion, I'm going to explain this here. "Klipper" is the name of the firmware running on your 3D printer. "Klippy" is the name of the software running on your Klipper Host (usually a Raspberry Pi). Klipper is written in C code, and Klippy is mostly written in Python.
Start writing your first Klippy extra:
Introduction to Writing Klippy Extras
This page contains many things that are useful to know before writing Klippy extras.
The config
object
Every Klippy extra is a class. Everything in a Klippy extra starts when the class is initialized, passing a config
object into the class. This config
object allows access to many parts of Klipper discussed next.
The printer
object
This is probably the most useful object you can use in a Klippy extra. This code block below shows how to get it:
It's that simple!
The printer object lets you access all sorts of useful parts of Klippy, which will be explained later in this page.
The gcode
object
The next object you will want to get familiar with is the gcode
object. This code block below shows how to get it:
This gcode
object is useful for many things. Here are a few of its functions:
run_script_from_command
Runs any GCode provided as a stringrespond_info
Displays text to the Klipper terminal; essentially the same as runningRESPOND MSG=
in a macroregister_command
Allows for defining custom GCode commandsregister_mux_command
A different way to definine custom GCode commands. This is explained later in the examples.
For example, if you wanted to run a G28
command (home all axes), you would run:
Configuration options
The config
object also allows reading of the configuration options present in the printer.cfg
file. Here's an example config:
This configuration shows four main option types:
int
float
str
list
To read them, you can use the corresponding functions of config
:
getint
getfloat
get
getlist
Here's how to use them in code:
Tip
To make a configuration option optional, pass another parameter to the get
function as a default value. Example:
Where do Klippy Extras go?
Before we go much further into this tutorial, it's good to know how Klippy reads the extras files. First, there's one last missing part of our Python file:
This is explained later in the examples.
For Klippy to read your extras file, you need to put it in ~/klipper/klippy/extras
. The filename, in this case myextra.py
, becomes the config name, in this case [myextra]
. After putting your file in the extras
folder, you can put a corresponding section into your printer.cfg
and restart Klipper. Your extra is installed!
However, every time you want to change something, you have to copy your file to the extras folder and restart Klipper. This is a very slow way to develop extras, as each Klipper restart turns off your heaters, loses your home position, and wastes too much time.
Fortunately, DynamicMacros provides a better solution.
DynamicMacros
Follow the installation instructions here.
DynamicMacros supports running Python code from a macro. This makes testing parts of Klippy extras much quicker. Here's the current example, as a Klippy extra, excluding the config reading:
Here it is as a DynamicMacro (use the tabs to navigate between the two files):
Now, you can change anything without restarting Klipper! Whenever you change the macro/Python code, simply run the DYNAMIC_MACRO
command to internally refresh DynamicMacros.
Note
This setup is intended for development, and not release, as this approach has a few limitations:
- This approach can't read configuration sections
- This approach isn't as structured as a full Klippy extra
For these reasons, this approach is only intended for testing parts of a Klippy extra, and isn't a replacement of extras.
This tutorial won't go in depth about testing extras with DynamicMacros. Instead, this tutorial focuses on writing traditional Klippy extras.
The reactor
object
Another useful object from the config
object is the reactor
. The code block below shows how to get it:
The reactor opens up a new possibility of Klippy extras: scheduling code to run/repeat at any time. This ability is explained in more detail in the third example.
Other Extras
This is one more good-to-know about Klippy extras before continuing to the examples:
You can access other extras from an extra. This allows for extended capabilities of extras. For example, this project, DynamicMacros invokes the gcode_macro
extra to run macros internally. The code blocks below shows two ways to access any other extra:
Examples
I hope this introduction was helpful. The next part of this tutorial consists of three examples. The first one is linked below:
Example 1: Greeter (Structure)
Before you start to write a Klippy extra, it is important to understand the structure of a Klippy extra.
In Python, a Klippy extra is defined as a class, then referenced in a function at the end of the file. Example:
There are two functions that can be used at the end of a file:
load_config_prefix
, allows for configurations like[greeter mygreeter]
load_config
, like in this example, allows for configurations like[greeter]
Initializer
In the last line of the above code block, you can see that a config
object is passed as a parameter to the Greeter
object. The initializer of the Greeter
class is shown below in sections:
Get printer objects and configuration options | |
---|---|
This section gets the printer
object with config.get_printer()
.
It then gets the gcode
object with self.printer.lookup_object('gcode')
.
Then, it gets the reactor
object with self.printer.get_reactor()
.
After that, it reads the configuration to get the message
parameter, specifying 'Welcome to Klipper!'
as the default, using config.get('message', 'Welcome to Klipper!')
.
The next part of the initializer:
Register GCode command and event handler | |
---|---|
The self.gcode
object is used here to register the GREET
command:
self.gcode.register_command('GREET', self.cmd_GREET, desc=self.cmd_GRRET_help)
The self.printer
object is then used to register an event handler to run when Klippy starts:
self.printer.register_event_handler('klippy:ready', self._ready_handler)
Event handlers
Klipper supports the following event handlers for extras to use:
"klippy:ready"
"klippy:firmware_restart"
"klippy:disconnect"
The combined initializer is:
Functions
The next part of this Klippy extra is the class functions. This class has three functions aside from the initializer, two of which were mentioned in the __init__
function:
cmd_GREET
_ready_handler
This example will start with the _ready_handler
function:
_ready_handler | |
---|---|
This event handler sets a timer for one second after Klippy starts to run _greet()
. self.reactor.monotonic()
represents the current time, and + 1
adds one second. self.reactor.register_timer
registers _greet()
to run when waketime
occurs.
_greet() | |
---|---|
This function uses the self.gcode
object declared in the initializer. Here, the respond_info
command is used (similar to running RESPOND MSG=""
) to display self.message
.
What is eventtime
?
You may have noticed that eventtime
is passed to the _greet()
function. This is because when Klippy runs _greet()
from the previous register_timer
, it passes eventtime
as a parameter. This is useful if you want a function to repeat itself after a specified interval. In this case, we only want it to run once, so eventtime
is unused.
The self.gcode
object
The self.gcode
object has a few useful functions:
register_command
(used in the initializer)register_mux_command
(explained later)respond_info
(used here)run_script_from_command
(runs the provided string as GCode. The provided string can be multiple lines long)
Finally, the cmd_GREET
function:
Here, you can see the cmd_GREET_help
is set to a string. Next, the cmd_GREET
function takes in a gcmd
parameter (unused). Finally, the cmd_GREET
function calls _greet()
.
The gcmd
parameter
The gcmd
parameter allows functions to receive parameters. For example, if GREET REPEAT=3
was called, the REPEAT
parameter could be read as follows:
There are a few get_
functions that can be used with the gcmd
parameter:
get
returns astr
getint
returns anint
getfloat
returns afloat
gcmd
also has a respond_info
function, similar to the self.gcode.respond_info
function.
The repeat
variable can then be used:
Install
To install a Klippy extra, it has to be placed in the ~/klipper/klippy/extras/
folder. Here's a simple command to install greeter.py
:
You can also create an install script that uses the ln
command to create a link to the file, rather than a copy:
install.sh | |
---|---|
Other Things
A few things that are good to know before moving on:
- If your Klippy extra uses
load_config_prefix
, instead ofload_config
, you can get the name of the configuration section (e.g.[greeter first]
is namedfirst
) by using: - If you want to learn more about additional capabilities of Klippy extras, check out the built-in Klippy extras.
- For further explanation of topics on this page, open the dropdowns.
Next example:
Example 2: BetterGreeter
For this tutorial, we are going to improve on the greeter code used in Structure.
For reference, the entire original greeter code is below:
Original Greeter
Goals
For the BetterGreeter
, we want the following features:
- Ability to have multiple different greetings
- Allow the user to choose if they want their greeting to display after Klipper starts, and if so, to set the delay
- Allow the user to set the message of the greeting
Here's an example configuration:
better_greeter.cfg | |
---|---|
Here's the desired behavior (anything on the line of a >
is a user-typed command):
- One second after
RESTART
- Two seconds after
RESTART
Creating the Base Class
The first step of creating a Klippy extra is to make the base class and the config function:
load_config_prefix
is used here instead ofload_config
because there will be multiplegreeter
configuration sections.
Reading the Configuration
The next step of our Klippy extra is to setup the class variables and read the parameters:
Quiz
If there was a configuration option (int) named repeats
, how would you get it in the initializer?
config.getint('repeats')
Here, the printer
, reactor
, gcode
, and message
variables are the same as in the previous Klippy extra. However, in this case, there are a couple new variables:
name
is explained in the last section of Structure.delay
is read as anint
from theconfig
object, with default value0
. The default value of0
indicates it will not be run when Klippy starts.
GCode Commands and Event Handler
After reading the configuration variables, we need to setup the GCode commands and event handler:
register_mux_command
is used here because there are multiplegreeting
configuration sections, and each should be called separately.
Here, a few parts are similar to the example in Structure, but not identical. Let's start with the GCode command.
In the Structure example, the GCode command was registered with:
self.gcode.register_command('GREET', self.cmd_GREET, desc=self.cmd_GREET_help)
In this new example, the GCode command is registered with:
The difference here is that there can be multiple greeting
configuration sections, and as a result, multiple Greeting
objects. To call each one separately, register_mux_command
is used, passing the following parameters:
- Macro name:
"GREETING"
- Parameter name:
"NAME"
- Parameter value:
self.name
- Function:
self.cmd_GREETING
- Description
self.cmd_GREETING_help
Next, the register_event_handler
is nearly identical to the Structure example, except in this case, it is run only if self.delay > 0
.
Functions
The next part to creating this Klippy extra is the functions.
There are three functions in the Greeting
class:
ready_handler
_greet
cmd_GREETING
Flowchart
graph TD
A[klippy:ready] --> B[_ready_handler]
B --> C[Wait self.delay seconds]
C --> D[_greet]
D --> E[Display self.message]
F[cmd_GREETING] --> E
The first function, _ready_handler
:
This function takes no parameters, and runs when Klippy reports "klippy:ready"
.
Inside, the waketime
variable is declared as self.reactor.monotonic() + self.delay
. Let's break this declaration down:
self.reactor.monotonic()
This is the current Klippy time+ self.delay
This addsself.delay
to the current time.
The result is that waketime
contains the Klippy time for self.delay
seconds in the future. Finally, we use self.reactor.register_timer
to register this time, telling it to run self._greet()
when the time occurs.
The next function, _greet
:
This function takes an optional eventtime
parameter (unused) (1), and prints out self.message
to the Klipper console, using self.gcode.respond_info
.
- This is passed by Klippy when it calls
_greet()
after the timer occurs.
Tip
If you want the timer function to repeat, you can return the provided eventtime
plus any number of seconds in the future, and it will repeat.
The final function in this class is the cmd_GREETING
function:
This function simply calls self._greet()
, which displays self.message
to the Klipper terminal.
Full Code
The full code of this Klippy extra is:
You can install it following these instructions, replacing greeter.py
with greeting.py
.
Last example (so far):
Example 3: KlipperMaintenance
This last example of the Klippy extra development tutorial is actually one of my other Klipper projects: KlipperMaintenance, a maintenance reminder system for Klipper.
This example will go through how the KlipperMaintenance code works to teach a few more important things about developing Klippy extras.
Full Code
For this tutorial, we'll start with the full code, then break it down and explain what each section does. If you can learn to read existing Klippy extras, you can read the builtin Klippy extras when developing your own extras.
Full Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
|
Structure
The class structure of this Klippy extra is shown in this flowchart:
classDiagram
class Maintain{
str name
str label
str trigger
int threshold
str message
fetch_history()
init_db()
fetch_db()
update_db(new)
get_remaining()
cmd_CHECK_MAINTENANCE(gcmd)
cmd_UPDATE_MAINTENANCE(gcmd)
}
note for Maintain "trigger must be one of:\n - print_time\n - filament\n - time"
class Maintenance{
int interval
_handle_ready()
_gcode_timer_event()
check_maintenance()
cmd_MAINTAIN_STATUS(gcmd)
}
Maintenance <|-- Maintain: Has-a
This diagram has a lot of information, but the key points are:
- Multiple
Maintain
objects are managed by oneMaintainence
object. - The
Maintain
class handles maintenance reminders, theCHECK_MAINTENANCE
command, and theUPDATE_MAINTENANCE
command. - The
Maintenance
class handles theMAINTAIN_STATUS
command. - A
[maintain name]
section corresponds to theMaintain
class (multiple). - A
[maintain]
section corresponds to theMaintenance
class (one).
The base code for this is:
- This function corresponds to a
[maintain]
config section. - This function corresponds to a
[maintain name]
config section.
The highlighted section in the code contains the classes, and the configuration loading, the parts relevant to this first section. The code above the highlighted section includes the relevant module imports and constant declarations (both explained later).
Much of the content in this example will be embedded in the code (usually the highlighted section) as a plus sign, like this: (1)
- Click some more plus marks in the code to learn more about the code.
Maintain
class
This next section of the example will focus on the Maintain
class.
Initializer
When load_config_prefix
creates a Maintain
object, it starts in the initializer.
Here's the full initializer, with explanations embedded inside with plus signs. Below is a more general breakdown of the initializer for clarity.
config.get_name()
returns the full config name, like"maintain name"
. The.split()[1]
splits the name by spaces and gets the last "word".config.getchoice()
allows you to only accept values in a certain list.- This part of the initializer chooses the units based on the
trigger
type:"print_time"
:"h"
"filament"
:"m"
"time"
:"h"
- This is explained later, in functions.
The major breakdown of this initializer is:
- First highlighted section: Load basic objects
- Second highlighted section: Read configuration options
- Third highlighted section: Register GCode commands
The line self.init_db()
will be explained later in functions.
Quiz
How would you add another trigger type (called "axes_distance"
with units "m"
)?
Functions
The next part of the Maintain
class is its functions. The functions in this class are, broken down into two sections:
Database:
fetch_history()
init_db()
fetch_db()
update_db(new)
GCode:
get_remaining()
cmd_CHECK_MAINTENANCE(gcmd)
cmd_UPDATE_MAINTENANCE(gcmd)
First, the database functions (again, plus signs throughout the code explain in more detail what each part does):
- In case of an error, empty placeholder data is returned.
- If history fetch was successful, return the data read from the Moonraker History API.
- The
path
is (if the username ispi
andself.name
is"lubricate"
)"/home/pi/maintain-db/lubricate"
. Even though it has no file extension, it stores JSON data. - The first time running this, the
maintain-db
folder won't exist.os.makedirs
creates the folder, andexist_ok=True
doesn't throw an error if it already exists.
The flow of information in this Klippy extra is:
init_db()
is called when Klippy startsfetch_db()
is called to read the stored database- If the data returned by
fetch_db()
isNone
(database is empty)fetch_history()
is called to fetch the history stored by Moonrakerupdate_db()
is called to update the database with the newly fetched data.
Calling update_db()
will erase the current maintenance state (resetting the timer/filament counter).
The next section of functions in the Maintain
class are the GCode commands. There are three functions in this section:
get_remaining()
cmd_CHECK_MAINTENANCE(gcmd)
cmd_UPDATE_MAINTENANCE(gcmd)
- This erases the current maintenance status, and is called when maintenance has been done.
self.fetch_history()
retrieves the current state of the printer history (print time, filament), and thenself.update_db()
saves that data to the JSON database.
The first function, get_remaining
, works as follows (assuming the trigger is print_time
and threshold is 250
):
- Read the last print time that maintenance occured at
- Read the current accumulated print time
- Get the difference between the two (how long has it been since last maintenance?)
- Subtract that from
threshold
(how much longer until maintenance should be done?) - Round to two decimal places and return
The next function, cmd_CHECK_MAINTENANCE
, corresponds to the GCode command CHECK_MAINTENANCE
, and outputs the Maintain
object's variables in a user-friendly format.
The final function, cmd_UPDATE_MAINTENANCE
, corresponds to the GCode command UPDATE_MAINTENANCE
, and erases the current maintenance state. Click the plus sign in the function for more information.
Maintenance Class
Now that we've gone through the Maintain
class, let's see how multiple Maintain
objects are managed in the Maintenance
class. This class does the following:
- Displays maintenance reminders
- Provides the
MAINTAIN_STATUS
command to overview the current maintenance state
Unlike the Maintain
class, which has multiple objects, there will be only one Maintenance
object. Let's start with the initializer.
Initializer
The initializer of the Maintenance
class is shown below:
This initializer may look similar to the BetterGreeter initializer in the previous example. This is because both the BetterGreeter
and Maintenance
classes use Klipper's timer system to schedule events.
There are four highlighted sections in the above code block. Let's go through each of them and explain what they do.
- This sets up the
reactor
object, which is important in scheduling events with Klipper. - This reads the interval from the configuration, defaulting to
60
if no value is provided. - This section is based off the
delayed_gcode
code, which is builtin to Klipper. Source code here. This section declares atimer_handler
,inside_timer
, andrepeat
variables, all of which will be used later. The last line of this section registers theself._handle_ready
function to run when Klippy is ready. - This final line registers the
"MAINTAIN_STATUS"
GCode command.
Functions
The next part of the Maintenance
class is its functions:
_handle_ready
_gcode_timer_event
check_maintenance
cmd_MAINTAIN_STATUS
Click on the plus symbols in the code below to learn more about specific parts.
-
Quiz
What doesself.reactor.monotonic()
return?self.reactor.monotonic()
returns the current Klippy time. - This is necessary for the timer to repeat. If you wanted to make this function not repeat, you can make it return
self.reactor.NEVER
. - Whenever you use
printer.lookup_objects
, it will return a list of tuples, where each tuple contains, in order, the configuration name of the object, then the actual Python object. - Because the
Maintenance
class is also configured with a[maintain]
config section (the difference being thatMaintenance
doesn't have a name, whileMaintain
does have a name, like[maintain lubricate]
), a check has to be made to ensure aMaintain
object has been found. if get_remaining() < 0
, the maintenance is expired and needs to be done.
There are general flows of information in these functions:
GCode Flow:
MAINTAIN_STATUS
is called from GCodecmd_MAINTAIN_STATUS
is called in Python- All
Maintain
objects are retrieved - If a
Maintain
object is expired, notify the user in the terminal - Display the status of all
Maintain
objects
Timer Flow:
- Klippy reports ready and calls
_handle_ready
_handle_ready
schedules an event to happen inself.interval
seconds_gcode_timer_event
is called by Klippy, and the maintenance is checked- Repeat step 3 until Klippy shuts down
The first flow, the GCode flow, runs when the user manually runs the MAINTAIN_STATUS
command. This makes it useful for checking how close certain maintenance objects are to being expired.
The second flow, the timer flow, runs behind the scenes as long as Klippy is running. This makes it useful for reminding the user if maintenance needs to be done without the need for manually checking.
Tip
Using a combination of GCode-initiated code, and timer-initiated code allows for Klippy extras to be more user-friendly.
Feedback
Was this tutorial helpful? Do you have any feedback for it? Are there any areas where you think this could be improved?
Let me know either on the Klipper Discourse or in a Documentation Issue on Github.
Thank you for your feedback!
Ended: Klippy Extras
Example Macros
This page will hold several different Dynamic Macro examples. Note that most of the examples here are specific to Dynamic Macros only.
M900
Normal Macro
In Marlin, M900 K is used to set pressure/linear advance. Now, you can use it in Klipper too:
Recursion
Dynamic Macro
These are a few example Dynamic Macros to demonstrate the recursive functionality of Dynamic Macros.
Counting to 10 | |
---|---|
Receiving Position Updates
Dynamic Macro
This is an example Dynamic Macro to demonstrate the ability to receive position updates from within the same macro.
Preserving Variables
Dynamic Macro
This is an example of how to preserve variables across triple-newlines in Dynamic Macros.
Development Status
Features
- Editing macros without restarting Klipper
- Accessing printer information from within Dynamic Macros
- Accessing parameters from within Dynamic Macros
- Adding new Dynamic Macros without restarting Klipper
- Removing existing Dynamic Macros without restarting Klipper
- Renaming Dynamic Macros without restarting Klipper
- Dynamic Macro descriptions
- Dynamic Macro variables
- Retrieving variables from other macros
- Support for
rename_existing
- Running Python from within a Dynamic Macro
- Dynamic
delayed_gcode
implementation - Traditional
delayed_gcode
syntax - Klippy extras tutorial
- Logging for easier debugging
- Configuring DynamicMacros clusters
- Internal logging
- Support for multiple Klipper instances
- Rendering a Dynamic Macro without running it
Planned Features
A checkmark indicates a feature is implemented and experimental.