Skip to content

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
class Greeter:
    def __init__(self, config):
        self.printer = config.get_printer()
        self.reactor = self.printer.get_reactor()
        self.gcode = self.printer.lookup_object('gcode')

        self.message = config.get('message', 'Welcome to Klipper!')

        self.gcode.register_command(
            'GREET', self.cmd_GREET, desc=self.cmd_GREET_help)
        self.printer.register_event_handler(
            'klippy:ready', self._ready_handler)

    def _ready_handler(self):
        waketime = self.reactor.monotonic() + 1
        self.reactor.register_timer(self._greet, waketime)

    def _greet(self, eventtime=None):
        self.gcode.respond_info(self.message)
        return self.reactor.NEVER

    cmd_GREET_help = "Greet the user"
    def cmd_GREET(self, gcmd):
        self._greet()

def load_config(config):
    return Greeter(config)

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
[greeting welcome]
message: Welcome to Klipper!
delay: 1

[greeting gcode]
message: Upload some GCode!
delay: 2

[greeting print_done]
message: Print completed!

Here's the desired behavior (anything on the line of a > is a user-typed command):

1
2
3
4
5
> RESTART
Welcome to Klipper! # (1)!
Upload some GCode! # (2)!
> GREETING NAME=print_done
Print completed!
  1. One second after RESTART
  2. 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:

1
2
3
4
5
6
7
class Greeting:
    def __init__(self, config):
        pass

# (1)!
def load_config_prefix(config):
    return Greeting(config)
  1. load_config_prefix is used here instead of load_config because there will be multiple greeter configuration sections.

Reading the Configuration

The next step of our Klippy extra is to setup the class variables and read the parameters:

1
2
3
4
5
6
7
8
9
class Greeting:
    def __init__(self, config):
        self.printer = config.get_printer()
        self.reactor = self.printer.get_reactor()
        self.gcode = self.printer.lookup_object('gcode')
        self.name = config.get_name().split()[1]

        self.message = config.get('message', 'Welcome to Klipper!')
        self.delay = config.getint('delay', 0)

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 an int from the config object, with default value 0. The default value of 0 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:

class Greeting:
    def __init__(self, config):
        self.printer = config.get_printer()
        self.reactor = self.printer.get_reactor()
        self.gcode = self.printer.lookup_object('gcode')
        self.name = config.get_name().split()[1]

        self.message = config.get('message', 'Welcome to Klipper!')
        self.delay = config.getint('delay', 0)

        #(1)!
        self.gcode.register_mux_command(
            'GREETING',
            'NAME',
            self.name,
            self.cmd_GREETING,
            desc=self.cmd_GREETING_help
        )

        if self.delay > 0:
            self.printer.register_event_handler(
            'klippy:ready', self._ready_handler)
  1. register_mux_command is used here because there are multiple greeting 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:

1
2
3
4
5
6
7
self.gcode.register_mux_command(
    'GREETING',
    'NAME',
    self.name,
    self.cmd_GREETING,
    desc=self.cmd_GREETING_help
)

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:

greeting.py
class Greeting:
    def __init__(self, config):
        self.printer = config.get_printer()
        self.reactor = self.printer.get_reactor()
        self.gcode = self.printer.lookup_object('gcode')
        self.name = config.get_name().split()[1]

        self.message = config.get('message', 'Welcome to Klipper!')
        self.delay = config.getint('delay', 0)
        self.gcode.register_mux_command(
            'GREETING',
            'NAME',
            self.name,
            self.cmd_GREETING,
            desc=self.cmd_GREETING_help
        )

        if self.delay > 0:
            self.printer.register_event_handler(
            'klippy:ready', self._ready_handler)

    def _ready_handler(self):
        waketime = self.reactor.monotonic() + self.delay
        self.reactor.register_timer(self._greet, waketime)

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 adds self.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:

greeting.py
class Greeting:
    def __init__(self, config):
        self.printer = config.get_printer()
        self.reactor = self.printer.get_reactor()
        self.gcode = self.printer.lookup_object('gcode')
        self.name = config.get_name().split()[1]

        self.message = config.get('message', 'Welcome to Klipper!')
        self.delay = config.getint('delay', 0)
        self.gcode.register_mux_command(
            'GREETING',
            'NAME',
            self.name,
            self.cmd_GREETING,
            desc=self.cmd_GREETING_help
        )

        if self.delay > 0:
            self.printer.register_event_handler(
            'klippy:ready', self._ready_handler)

    def _ready_handler(self):
        waketime = self.reactor.monotonic() + self.delay
        self.reactor.register_timer(self._greet, waketime)

    def _greet(self, eventtime=None):
        self.gcode.respond_info(self.message)
        return self.reactor.NEVER

This function takes an optional eventtime parameter (unused) (1), and prints out self.message to the Klipper console, using self.gcode.respond_info.

  1. 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:

greeting.py
class Greeting:
    def __init__(self, config):
        self.printer = config.get_printer()
        self.reactor = self.printer.get_reactor()
        self.gcode = self.printer.lookup_object('gcode')
        self.name = config.get_name().split()[1]

        self.message = config.get('message', 'Welcome to Klipper!')
        self.delay = config.getint('delay', 0)
        self.gcode.register_mux_command(
            'GREETING',
            'NAME',
            self.name,
            self.cmd_GREETING,
            desc=self.cmd_GREETING_help
        )

        if self.delay > 0:
            self.printer.register_event_handler(
            'klippy:ready', self._ready_handler)

    def _ready_handler(self):
        waketime = self.reactor.monotonic() + self.delay
        self.reactor.register_timer(self._greet, waketime)

    def _greet(self, eventtime=None):
        self.gcode.respond_info(self.message)
        return self.reactor.NEVER

    cmd_GREETING_help = "Greet the user"
    def cmd_GREETING(self, gcmd):
        self._greet()

This function simply calls self._greet(), which displays self.message to the Klipper terminal.

Full Code

The full code of this Klippy extra is:

greeting.py
class Greeting:
    def __init__(self, config):
        self.printer = config.get_printer()
        self.reactor = self.printer.get_reactor()
        self.gcode = self.printer.lookup_object('gcode')
        self.name = config.get_name().split()[1]

        self.message = config.get('message', 'Welcome to Klipper!')
        self.delay = config.getint('delay', 0)

        self.gcode.register_mux_command(
            'GREETING',
            'NAME',
            self.name,
            self.cmd_GREETING,
            desc=self.cmd_GREETING_help
        )

        if self.delay > 0:
            self.printer.register_event_handler(
            'klippy:ready', self._ready_handler)

    def _ready_handler(self):
        waketime = self.reactor.monotonic() + self.delay
        self.reactor.register_timer(self._greet, waketime)

    def _greet(self, eventtime=None):
        self.gcode.respond_info(self.message)
        return self.reactor.NEVER

    cmd_GREETING_help = "Greet the user"
    def cmd_GREETING(self, gcmd):
        self._greet()

def load_config_prefix(config):
    return Greeting(config)

You can install it following these instructions, replacing greeter.py with greeting.py.


Last example (so far):

Example 3: KlipperMaintenance

Comments