3D Printering: Listen To Klipper

Art of 3D printer in the middle of printing a Hackaday Jolly Wrencher logo

I recently wrote about using Klipper to drive my 3D printers, and one natural question is: Why use Klipper instead of Marlin? To some degree that’s like asking why write in one programming language instead of another. However, Klipper does offer some opportunities to extend the environment more easily. Klipper runs on a Linux host, so you can do all of the normal Linux things.

What if you wanted to create a custom G-code that would play a wave file on a speaker? That would let you have custom sounds for starting a print, aborting a print, or even finishing a print.

If you recall, I mentioned that the Klipper system is really two parts. Well, actually more than two parts, but two important parts at the core. Klipper is, technically, just the small software stub that runs on your 3D printer. It does almost nothing. The real work is in Klippy, which is mostly Python software that runs on a host computer like a Raspberry Pi or, in my case, an old laptop.

Because it is Python and quite modular, it is very simple to write your own extensions without having to major surgery or even fork Klipper. At least in theory. Most of the time, you wind up just writing G-code macros. That’s fine, but there are some limitations. This time, I’m going to show you how easy it can be using the sound player as an example.

Macros All the Way Down

Normally, you think of gcode as something like: G1 X50 Y50. Some of the newer codes don’t start with G, but they look similar. But with Klipper, G1, M205, and MeltdownExtruder are all legitimate tokens that could be “G-code.”

For example, suppose you wanted to implement a new command called G_PURGE to create a purge line (case doesn’t matter, by the way). That’s easy. You just need to put in your configuration file:

[gcode_macro g_purge]
gcode:
# do your purge code here

The only restriction is that numbers have to occur at the end of the name, if at all. You can create a macro called “Hackaday2024,” but you can’t create one called “Hackaday2024_Test.” At least, the documentation says so. We haven’t tried it.

There’s more to macros. You can add descriptions, for example. You can also override an existing macro and even call it from within your new macro. Suppose you want to do something special before and after a G28 homing command:

[gcode_macro g28]
description: Macro to do homing (no arguments)
rename_existing: g28_original
gcode:
M117 Homing...
g28_original
M117 Home done....

Not Enough!

By default, your G-code macros can’t call shell commands. There are some ways to add that feature, but letting a file just run any old command seems like an unnecessary invitation for mayhem. Instead, we’ll write a Klippy “extra.” This is a Python class that resides in your klipper/klippy/extra directory.

Your code will run with a config object that lets you learn about the system in different ways. Suppose you are writing code to set up one single item, and it doesn’t make sense that you might have more than one. For example, consider an extra that raises the print speed for all printing. Then, you’d provide an entry point, load_config, and it would receive the config object.

However, it is more common to write code to handle things that could — at least in theory — have multiple instances. For example, if you wanted to control a fan, you might imagine that a printer could have more than one of these fans. In that case, you use load_config_prefix. That allows someone who writes a configuration file to specify multiple copies of the thing you define:

[hackaday_fan fan1]
pin: 8
[hackaday_fan_fan2]
pin: 9

The Sounds

In this case, we do want to allow for different sounds to play, so we’ll use load_config_prefix. Here’s the short bit of code that does the trick:


<h1>Play a sound from gcode</h1>

#

<h1>Copyright (C) 2023 Al Williams</h1>

<h1> </h1>

#

<h1>This file may be distributed under the terms of the GNU GPLv3 license.</h1>

import os
import shlex
import subprocess
import logging
class AplayCommand:
   def <strong>init</strong>(self, config):
   self.name = config.get_name().split()[-1] # get our name
   self.printer = config.get_printer()
   self.gcode = self.printer.lookup_object(&#039;gcode&#039;) # get wave and path
   wav = config.get(&#039;wave&#039;)
   path = config.get(&#039;path&#039;,None)
   if path!=None:
      wav = &quot;aplay &quot;+path+&#039;/&#039;+wav
   else:
      wav = &quot;aplay &quot; + wav
   self.wav = shlex.split(wav) # build command line
   self.gcode.register_mux_command( # register new command for gcode_macro
       &quot;APLAY&quot;, &quot;SOUND&quot;, self.name,
       self.cmd_APLAY_COMMAND, # worker for new command
   desc=self.cmd_APLAY_COMMAND_help) # help text

cmd_APLAY_COMMAND_help = &quot;Play a sound&quot;

def cmd_APLAY_COMMAND(self, params):
      try:
         proc = subprocess.run(self.wav) # run aplay
      except Exception:
         logging.exception(
         &quot;aplay: Wave {%s} failed&quot; % (self.name))
      raise self.gcode.error(&quot;Error playing {%s}&quot; % (self.name))

<h1>main entry point</h1>

def load_config_prefix(config):
   return AplayCommand(config)

Note that the AplayCommand object does all the actual configuration when you initialize it with a config object. So, to create an “aplay object” in your config files:

[aplay startup]
wave: powerup.wav
path: /home/klipper/sounds

[aplay magic]
wave: /home/klipper/sounds/wand.wav

Then, to use that sound in a macro, you only need to use:

[gcode_macro get_ready]
gcode:
   aplay sound=startup

You can make as many different sounds as you like, and if you provide an entire path for the wave parameter, you can omit the path. Optional parameters like this require a default in your code:

 path = config.get('path',None)

Obviously, this assumes your Klipper computer has aplay installed and the wave files you want to play. Or, switch players and use whatever format you want.

You can read more about options and other things you can do in the “Adding a host module” section of the code overview documentation. Another good resource is the source code for the stock extras, many of which aren’t really things you’d probably consider as extra.

So next time you want to add some features to your printer, you can do it in Python with less work than you probably thought. Haven’t tried Klipper? You can learn more and get set up fairly quickly.

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.