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!