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:
[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):
> RESTART
Welcome to Klipper! # (1)!
Upload some GCode! # (2)!
> GREETING NAME=print_done
Print completed!
- 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:
class Greeting:
def __init__(self, config):
pass
# (1)!
def load_config_prefix(config):
return Greeting(config)
load_config_prefixis used here instead ofload_configbecause there will be multiplegreeterconfiguration sections.
Reading the Configuration
The next step of our Klippy extra is to setup the class variables and read the parameters:
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:
nameis explained in the last section of Structure.delayis read as anintfrom theconfigobject, with default value0. The default value of0indicates 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)
register_mux_commandis used here because there are multiplegreetingconfiguration 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:
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_greetcmd_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:
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.delayThis addsself.delayto 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:
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.
- 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:
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:
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):