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:

# Play a sound from gcode
#
# Copyright (C) 2023 Al Williams
# 
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import os
import shlex
import subprocess
import logging
class AplayCommand:
   def __init__(self, config):
   self.name = config.get_name().split()[-1] # get our name
   self.printer = config.get_printer()
   self.gcode = self.printer.lookup_object('gcode') # get wave and path
   wav = config.get('wave')
   path = config.get('path',None)
   if path!=None:
      wav = "aplay "+path+'/'+wav
   else:
      wav = "aplay " + wav
   self.wav = shlex.split(wav) # build command line
   self.gcode.register_mux_command( # register new command for gcode_macro
       "APLAY", "SOUND", self.name,
       self.cmd_APLAY_COMMAND, # worker for new command
   desc=self.cmd_APLAY_COMMAND_help) # help text

   cmd_APLAY_COMMAND_help = "Play a sound"

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

# main entry point
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.

10 thoughts on “3D Printering: Listen To Klipper

    1. I’m getting tired of all the Klipper posts tbh.. Feels like there’s a fair amount of pressure going out into the community to use it, but there’s been no decent write up here that takes an impartial look at Klipper vs. Other stand-alone solutions.
      It’s not simply a ‘programming language choice’, and is more along the lines of using a USB-Harddrive, vs. using a NAS. They both do the ‘same’ thing in different ways and they both have some very important pros and cons that need to be understood.

      1. they’re not telling anyone to use klipper, they’re showing you how to use it if you want to because there will be plenty of people out there that are curious and in-depth, easily digestible tutorials like this are pretty useful.

      2. Smoothieware and similar are plenty good for regular, run-of-the-mill 3D printer. If you have some more special needs or just like hacking, Klipper will be easier to adapt.

        Remora is a similar solution that uses LinuxCNC as the host, but it is not nearly as polished as Klipper yet.

  1. Just want to check something.
    “The only restriction is that numbers have to occur at the end of the name, if at all.” … “At least, the documentation says so. We haven’t tried it.”
    But then in the very next code snippet you rename the g28 command to g28_original and promptly use it. Does that not count as a gcode macro with numbers in the middle? Or did you not try it?

    1. DHCP reservations exist. SD cards are dirt cheap and don’t wear out as fast as people seem to think they do. Klipper + Pi2 has been running multiple printers simultaneously on a table across the room from me for 6+ years and so far the only issue has been a couple of power outages.

      Let’s say it actually was as risky as you are implying…so what? It’s not being bundled onto a deep space probe and launched into the void. It’s cheap, readily available parts that could, in an absolute worst case, be completely reassembled from scratch in less than 20 minutes. It’s the opposite of a serious problem.

    2. i am absolutely astonished at the continuing scourge of “SD wear out” on pi.

      the first thing i did when setting up raspbian was to move the logfiles and the rest of var into tmpfs (it’s already halfway there iirc), and “mount -o remount,ro /” so that it wouldn’t wear out the flash. when i need to, i remount it read-write but it’s been mounted read-write for only about 5 hours out of the 3 years i’ve owned the thing. doesn’t everyone do that?!

      but apparently not. my buddy had a helium mining thing at my house, of roughly the same age, and it died from wearing out its SD card. insane, with how much he paid for it, that they didn’t bother to figure out how to minimize writes before shipping a zilion copies of it.

      i know my 3d printer is definitely way older (a decade) than the typical lifetime of an sd card on pi.

      anyways as far as klipper, i figure i’d run the pc side of it on my workbench laptop. not on a raspi. everyone has a workbench laptop don’t they? :)

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.