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
Maintainobjects are managed by oneMaintainenceobject. - The
Maintainclass handles maintenance reminders, theCHECK_MAINTENANCEcommand, and theUPDATE_MAINTENANCEcommand. - The
Maintenanceclass handles theMAINTAIN_STATUScommand. - A
[maintain name]section corresponds to theMaintainclass (multiple). - A
[maintain]section corresponds to theMaintenanceclass (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
triggertype:"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
pathis (if the username ispiandself.nameis"lubricate")"/home/pi/maintain-db/lubricate". Even though it has no file extension, it stores JSON data. - The first time running this, the
maintain-dbfolder won't exist.os.makedirscreates the folder, andexist_ok=Truedoesn'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_STATUScommand 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
reactorobject, which is important in scheduling events with Klipper. - This reads the interval from the configuration, defaulting to
60if no value is provided. - This section is based off the
delayed_gcodecode, which is builtin to Klipper. Source code here. This section declares atimer_handler,inside_timer, andrepeatvariables, all of which will be used later. The last line of this section registers theself._handle_readyfunction 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_eventcheck_maintenancecmd_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
Maintenanceclass is also configured with a[maintain]config section (the difference being thatMaintenancedoesn't have a name, whileMaintaindoes have a name, like[maintain lubricate]), a check has to be made to ensure aMaintainobject 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_STATUSis called from GCodecmd_MAINTAIN_STATUSis called in Python- All
Maintainobjects are retrieved - If a
Maintainobject is expired, notify the user in the terminal - Display the status of all
Maintainobjects
Timer Flow:
- Klippy reports ready and calls
_handle_ready _handle_readyschedules an event to happen inself.intervalseconds_gcode_timer_eventis 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!