While upgrading my 4-wheel autonomous wall-following robot to add vision enhancement, Wall-E managed to dive off my bench onto the floor – twice! No major damage except to my ego, but one wheel did get dinged up to the point where it needed to be replaced.
The original wheels were designed in OSCad many years ago, so the print files were no longer available. I designed a replacement wheel using my new best friend OnShape, as shown in the screenshot below:
After getting the design dialed in (version 6), I printed out the remaining three wheels and transferred the tires from the old wheels to the new ones. Since the tires were already red, I chose to print the wheels in gray PETG to produce an Ohio State University themed robot ;). Here’s a short video showing off the new wheels:
In a prior post, Grok help me figure out why I was having so much trouble trying to change my Voron 2.4 printer from ‘dock’ to ‘no_dock’ configuration. Solving this problem exposed a pretty significant flaw in the way that config_switch.py managed the process of changing the printer.cfg file to reflect the new configuration. After getting everything to work, I suggested we generate a pull request to Vin’s Klipper-toolchanger repo to make the config swap process a bit more dependable. This post is intended to document this effort.
We started out by making sure we had all the preparatory work done.
I copied yet another set of config files to my PC
1. Update my clone of Vin-y’s klipper-toolchanger repo on my printer, so all I had to do was update it with: cd ~/klipper-toolchanger, git pull. Now my local repo is the same as Vin-y’s main branch
cd ~/klipper-toolchanger git status # just to see where you are git pull origin main
Created a new branch so we can make our changes without touching the main branch.Just type this one command and show me the result:bash
git checkout -b fix-config-switch-in-place
In VS Code on my PC, I navigated to /home/pi/klipper-toolchanger/klipper/extras/config_switch.py and replaced its contents with the file generated by Grok.
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
# Copyright (C) 2024 Chinh Nhan Vo <nhanvo29@proton.me>
# Improved 2026 by Frank Paynter / Grok – fix section replacement, marker handling,
# and prevent progressive corruption of printer.cfg
#
# This file may be distributed under the terms of the GNU GPLv3 license.
Create a new branch to make the changes without changing the ‘main’ branch:
1
git checkout-bfix-config-switch-in-place
Then in VS Code we overwrote the existing klipper-toolchanger / klipper / extras / config_switch.py with the new version (see above), saved the file and then restarted Klipper with “sudo systemctl restart klipper”
Testing:
At the start of the test program, ‘printer.cfg’ contains the ‘no_dock’ configuration. Then I ran SAVE_CONFIG_MODE fro the mainsail console, and got the following output:
To go the other way, we edited the ‘config_wt_dock.cfg’ file to add the closing marker ‘#;>’ to the end of the file, and then ran TOGGLE_CONFIG_MODE again to swap out the ‘no_dock’ “Session Variables” with teh ‘with_dock’ ones and then checked to make sure the Voron would reboot successfully (it did – yay!) and the “session variables” and ‘SAVE_CONFIG’ sections were correct.
Pull Request:
First we committed all the changes (principally the revised ‘config_switch.py’ script) to the local repo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cd~/klipper-toolchanger
git status
git add klipper/extras/config_switch.py
git commit-m"Fix CONFIG_TOGGLE: replace section in-place instead of appending
Previously the toggle stripped the marked block and appended the new
config at the end of printer.cfg. This caused progressive corruption,
lost the SAVE_CONFIG position, and dropped the closing #;> marker.
The new implementation:
- Strictly replaces only the text between (and including) the #;< / #;> markers
- Leaves all other content (including SAVE_CONFIG) untouched and in order
- Always writes both markers into the config_*.cfg files
- Creates a timestamped backup before every toggle
- Adds clear error checking for missing/duplicate markers
Tested with multiple dock / no-dock round-trips on a Voron 2.4 + MissChanger."
Then before we do anything else, we need to fork Vin-y’s klipper-toolchanger repo to my Github account with
branch'fix-config-switch-in-place'set up totrack'myfork/fix-config-switch-in-place'.
pi@mainsailos:~/klipper-toolchanger$
Here’s what the pull request looks like on Github, just before clicking the ‘Create Pull Request button:
And here’s what it looks like after creating the pull request:
It feels good to be able to contribute something of worth to the MISSChanger community, as I am certain that others have run into similar problems with TOGGLE_CONFIG_MODE.
In my old 4-wheel robot setup, I had a 5V regulator built around an LF50CV low-voltage dropout 3-pin regulator. Unfortunately, with the added current drain of the Pi5 and OAKD-Lite Camera, the LF50CV started dropping the output voltage to the point where the Pi5 started to get flaky. In discussing this with Grok, he suggested The Pololu D24V50F5 step-down regulator shown below:
I did some initial testing on the module:
At 9.1V at the ‘Switched Battery input, the Vbatt monitor voltage was 3.3V – the upper limit for inputs to a Teensy 4.1. Since 9.1 is well above even the high charging limit (~8.4V), this seems pretty safe.
At a measured Irun = 0.2A, Irun_out measured 90mV
At a measured Irun = 1.5A, Irun_out measured 0.4V
at a measured run = 1.14A, Irun_out measured 0.255V
Then attached a USB Type C male pigtail to the regulator output and then plugged it directly into the Raspberry Pi5. The Pi5 booted right up and I was able to access it from my PC using VS Code. Regulator output voltage with Pi5 connected was 4.99V at a measured input current of about 0.55A.
Here’s a photo showing the current sense/regulator module already hot-glued to its 3D-printed mounting plate.
30 July 2026 Update:
Yesterday the plan was for the existing LF50CV-based LDO 5V voltage regulator to be replaced with the above D24V50F5 DC-DC step-down regulator. However, as I was waiting for sleep last night, I was thinking about the increased 5V current level required by the Raspberry Pi5 and the OAKD-Lite camera and the fact that in the current (no pun intended) design, that 5V power comes from a module on the lower deck. So even with the higher current capacity (nominally 5A max) provided by the D24V50F5 regulator, there is still going to be a significant voltage drop in the approximately 2 meters of 24-guage stranded hookup wire and the TE Connectivity ‘MATE-N-LOK inter-deck connector. As Grok reminded me, there is not only the 1m line TO the Pi5/Camera, there is also the ground wire coming BACK for a total of approx 2m and two times through the connector. Grok noted that the total round-trip IR drop at 3A would be about 0.5V, which would place the input voltage at the Pi5 at about 4.5V, way too low for reliable operation.
That made me realize that even if I replaced the LF50CV-based LDO 5V voltage regulator with the D24V50F5 5A module, it *still* wouldn’t provide a clean 5V to the Pi5. It was at that point that the way to skin this cat was to leave the LF50CV-based LDO 5V voltage regulator in place on the bottom deck and move the D24V50F5 5A module to the top deck where the wire length to the Pi5 would be more like 0.1m instead of 2m – problem solved!
Well, not entirely, as I still have to get +Vbatt (nominally 8V) up to (and back from) the second deck, with the same 2m + 2x through the connector as before. But now that same 0.5V IR drop is applied to Vbatt instead of +5V, and that makes the problem go away entirely – yay!
Looking at a recent system schematic:
I see that +5V from the LF50CV goes through the interdeck connector on pin 1 (red), and the return path is on pin 2 (black). I believe all I would have to do is move the red wire from the output to the input of the LF50CV module. No, not that simple – the LF50CV output goes directly to the Teensy main controller 5V buss, and a separate wire goes from that 5V buss up through the interdeck connector to a terminal strip. That wire will have to be disconnected from the Teensy 5V buss and re-connected to Vbatt at the input to the LF50CV module. At the other end, the wire will have to be disconnected from the terminal strip and routed instead to the ‘Batt’ terminal of the D24V50F0 module. The output (both +5 and GND) from the D24V50F0 module must be routed back to the terminal strip. At this point, the two 5V regulator modules will be tied to the same (GND) reference, but supply entirely different circuits. Here’s a photo of the D24V50F0 module installed on the upper deck.
With this setup I was able to run the Pi5 for several hours before the +5V supply dropped to low to continue.
I need at least four additional lines from the Teensy 4.1 to the 2nd deck. Two lines for Serial1-Pi5 comms and two for Vbatt/Irun sensors on the 2nd deck 5V regulator board. The existing Brn/Wht wire pair has already been designated on the schematic for Serial1-Pi5 comms, and the existing but unassigned Blu/Gry pair can be used for the sensor lines. Note that the robot now sports *three* 1NA169 current sensor modules (the third one is in the base with the battery pack), and two of those also provide (Vbatt – 6V) voltage outputs. The pre-existing gray wire on the second deck was used to reset the Teensy 3.5 used to control the VL531LX LIDAR sensors but since the Teensy 3.5 (and all but one of the VL53L1X sensors) was removed, the Gry wire is no longer used. Here’s the updated schematic:
I’m still having problems (see also this post) with the Neopixel LED string on my T1 (red) Stealthburner toolhead. It stops working, then I wiggle some wires on the toolhead and it starts working again – temporarily.
To start the troubleshooting process, I swapped just the front part of my two stealthburner toolheads. The T0 (grey) Neopixel installation works fine on the T1 (red) body, but the T1 (red) Neopixel installation does not work on the T0 (grey) body, indicating that the problem is isolated to the T1 (red) Neopixel installation.
In a previous post I described connecting the string to a Teensy 4.1 using an AdaFruit Neopixel test program, so I dug out the Teensy 4.1 and the 3-pin 2.0mm male JST plug adaptor from this post, and connected the Teensy to the T1 (red) Neopixel string by disconnecting the Neopixel JST connector from the ‘SBurner Fan Adaptor V1’ and then connecting it to the Teensy via the 3-pin male JST, as shown in the following photo:
With this setup, the Neopixel string worked perfectly, so the problem is either in the Fan Adaptor board connector, or something else upstream from that. When I swapped just the front parts, I essentially eliminated everything upstream of the Fan Adaptor boar, and by exercising the NeoPixel string via the 3-pin JST connector I eliminated everything downstream of the Fan Adaptor module, leaving just the module itself. Plugged T1 (red) back into its toolhead 8-pin connector and everything worked! Came back this morning and turned on the printer; now the T1 LED’s are dead again.
It looks like this is one of those intermittent issues that just don’t go away. At this point I suspect that the 8-pin inter-board connector is the real issue, and I’m not sure there is anything to be done about it. If this is in fact the case, then it is the female half on the Fan Adaptor board that is faulty, as the T0 (gray) toolhead front module works fine when plugged into the T1 (red) main body.
22 March 2026 Update:
Still having problems with my T1 toolhead LED string; sometimes it will turn on when the printer powers up, but mostly not. As before, the T0 toolhead LED string works fine on the T1 toolhead, but the T0 string won’t work on either one. After consulting with Grok some more, I began to suspect the problem was in the female connector half of the two-PCB board arrangement that connects the toolhead body to the part that contains the two fans and the LED string, so I ordered a new set from West 3D.
I was a more than a little miffed when I realized I had ordered the wrong part – this set of boards is for the CANbus umbilical configuration, but fortunately the ‘Fan Adaptor’ PCB (left-hand PCB in the above image) is identical in both versions. Unfortunately when I replaced the existing PCB with this one, the problem didn’t go away – bummer! At this point I had pretty much eliminated everything except possibly the LED string itself (although I have replaced it twice with no change in the symptoms. To reiterate the current situation:
I have two Stealthburner toolheads each consisting of a body containing the extruder mechanics and the RP2040 driver PCB, and a ‘front half’ containing two cooling fans, the LED string and a daughter board that connects to the RP2040 PCB via an 8-pin push-on connector. The T0 ‘front half’ is colored gray, and the T1 ‘front half’ is colored red.
The T0 (gray) front half LED string works fine when installed on either the T0 or T1 body, while the T1 (red) front half LED string doesn’t work when connected to either the T0 or T1 body. This eliminates the T1 body as the source of the problem
Replacing the T1 (red) front half ‘Fan Adaptor’ PCB with the above brand-new item from West 3D did not change the symptoms. T1(gray) front half still works on both toolhead bodies but the T1 (red) front half doesn’t work on either. This eliminates the PCB connector as the culprit, leaving only the T1 LED string.
As noted in previous posts on this subject, there have been some issues in the past with intermittent LED strings, caused by some combination of using 3.3V signals from the RP2040 on LED strings meant for 5V and potential ringing on the data line, and Ton/Toff timing variations among different makes of Neopixel chips.
After some more consultation with Grok, I decided I needed a way to determine how well (or poorly) a particular LED string operated over a range of Ton/Toff values. I asked Grok to produce an Arduino program targeting a 600MHz Teensy 4.1 microcontroller to determine the actual operable timing range for a connected Neopixel LED string. Here’s the program Grok came up with (including some tweaks I made to allow for user comment capture during ‘sweep’ mode):
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
153
154
155
156
/*
Name: NeoPixelTimingTester.ino
Created: 3/21/2026 4:51:43 PM
Author: FRANK_XPS_9530\Frank
*/
#include <Arduino.h>
#define DATA_PIN 8
#define NUM_LEDS 3
// Timing (ns) — starting tolerant for your current string
uint32_t t0h_ns=400;
uint32_t t1h_ns=850;
uint32_t reset_us=80;
voidsetup()
{
pinMode(DATA_PIN,OUTPUT);
digitalWriteFast(DATA_PIN,LOW);
// Long reset pulse on power-up (matches Adafruit best practices)
delayMicroseconds(200);
Serial.begin(115200);
delay(500);
Serial.println("=== Stealthburner NeoPixel Timing Test Jig v2 (fixed timing) ===");
printHelp();
}
voidprintHelp()
{
Serial.println("Commands:");
Serial.println(" T0H <value> e.g. T0H 320");
Serial.println(" T1H <value> e.g. T1H 780");
Serial.println(" RESET <value> e.g. RESET 100");
Serial.println(" WHITE / RED / GREEN / BLUE / RAINBOW / CHECKER");
When I performed a ‘sweep’ of the above LED string, I got the following output:
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
Opening port
Port open
===Stealthburner NeoPixel Timing Test Jig v2(fixed timing)===
Commands:
T0H<value>e.g.T0H320
T1H<value>e.g.T1H780
RESET<value>e.g.RESET100
WHITE/RED/GREEN/BLUE/RAINBOW/CHECKER
SWEEP
Current:T0H=400ns|T1H=850ns|Reset=80µs
→Rainbow pattern
Commands:
T0H<value>e.g.T0H320
T1H<value>e.g.T1H780
RESET<value>e.g.RESET100
WHITE/RED/GREEN/BLUE/RAINBOW/CHECKER
SWEEP
Current:T0H=400ns|T1H=850ns|Reset=80µs
Starting T1H sweep(T0H fixed@400ns)...
T1H=450ns
You entered:LED0 off,LED1 off,LED2 bright white
T1H=475ns
You entered:LED0 off,LED1 off,LED2 bright white
T1H=500ns
You entered:LED0 off,LED1 off,LED2 bright white
T1H=525ns
You entered:LED0 faint white,LED1 off,LED2 bright white
T1H=550ns
You entered:LED0 bright white,LED1 bright white,LED2 bright white
T1H=575ns
You entered:LED0 bright green,LED1 bright green,LED2 bright green
T1H=600ns
You entered:LED0 bright green/white,LED1 bright green,LED2 bright green/white
T1H=625ns
You entered:LED0 bright green/white,LED1 bright green/white,LED2 bright green/white
T1H=650ns
You entered:LED0 bright green/white,LED1 bright green/white,LED2 bright green/white
T1H=675ns
You entered:LED0 bright green/white,LED1 bright green/white,LED2 bright green/white
T1H=700ns
You entered:LED0 bright green/white,LED1 bright green/white,LED2 bright green/white
T1H=725ns
You entered:LED0 bright green/white,LED1 bright green/white,LED2 bright green/white
T1H=750ns
You entered:LED0 bright green/white,LED1 bright green/white,LED2 bright green/white
T1H=775ns
You entered:LED0 bright green/white,LED1 bright green/white,LED2 bright green/white
T1H=800ns
You entered:LED0 bright green/white,LED1 bright green/white,LED2 bright green/white
T1H=825ns
You entered:LED0 bright green/white,LED1 bright green/white,LED2 bright green/white
T1H=850ns
You entered:LED0 bright green/white,LED1 bright green/white,LED2 bright green/white
T1H=875ns
You entered:LED0 bright green/white,LED1 bright green/white,LED2 bright green/white
T1H=900ns
You entered:LED0 bright green/white,LED1 bright green/white,LED2 bright green/white
T1H=925ns
You entered:LED0 bright green/white,LED1 bright green/white,LED2 bright green/white
T1H=950ns
You entered:LED0 bright green/white,LED1 bright green/white,LED2 bright green/white
Sweep finished
Commands:
T0H<value>e.g.T0H320
T1H<value>e.g.T1H780
RESET<value>e.g.RESET100
WHITE/RED/GREEN/BLUE/RAINBOW/CHECKER
SWEEP
Current:T0H=400ns|T1H=950ns|Reset=80µs
According to Grok, this output shows a ‘good’ LED string, and sure ’nuff, when I re-installed this string into my T1(red) toolhead, darned if it didn’t work!
27 March 2026 Update:
It’s been several days now since I got my recalcitrant toolhead LED string working, and so far, so good. I have turned the printer on and off several times, and even made several prints (with T0 rather than T1, but both toolhead LED strings always light up when power is applied to the printer, and I have been able to exercise the T1 LED string using the printer control panel). In the meantime, based on Grok’s recommendation I ordered two more LED strings from Fabreeko, as they seem to have the gold standard reputation for Stealthburner LED strings. However, when they arrived, they looked identical to one I had gotten some time ago from Amazon as shown below
Cheap knock-offs from Amazon
and in fact had the same defect as the Amazon knockoffs – the GND and Data_In wires were reversed on the 3-pin JST. Not only that, but even after correcting the pin-reversal issue, they did not perform well (or even at all) when tested using Grok’s Tstart/Tstop sweep program. It was hard to avoid the suspicion that Fabreeko was simply buying their strings from Amazon and reselling them as their own. Needless to say I returned them for credit.
So, currently I have working NeoPixel strings on both T0 & T1 toolheads, but no backup if one fails (again), and no known-good ordering source. Perhaps Grok can guide me on this (although it was Grok that recommended Fabreeko).
Last night I was able to verify that ‘klippy.log’ contains the entire in-lined configuration, after all includes and before parsing at the top of each entry. This should go a long way toward effective troubleshooting of the ‘No LEDs’ problem. However, when I started the process of implementing the Grok-suggested changes, I discovered that my printer will no longer connect to nhk0 or nhk1. I get the dreaded “mcu ‘nhk1’: Unable to connect” and/or “mcu ‘nhk0’: Unable to connect” error(s). Since I haven’t made any hardware changes this is kinda hard to believe, and at least so far I’m convinced this is yet another software-induced issue.
So, I plan to go back to my 251025 configuration (251025_wt_dock_printer.cfg & 251003_T0-Nitehawk-Revo-LDO.cfg). Yep! this configuration setup worked perfectly – no errors, and the restart was very rapid (maybe 2-5 seconds). LEDs on T0 also worked.
01 November 2025 Update:
After spending way too much time chasing the issue with LEDs not working on the T1(red) toolhead, I think I’m back going again. Again I’m starting with the following single-toolhead configuration:
251025_wt_dock_printer.cfg (printer.cfg with ‘with dock’ contents below the SAVE_CONFIG line)
251003_T0-Nitehawk-Revo-LDO.cfg
I modified printer.cfg in MainSail to change the max_y value for QGL from 225 to 275, and then ran SAVE_CONFIG_MODE to copy the changes to the ‘config_wt_dock.cfg’. This gives QGL a wider range of positions and should result in better gantry levelling in the ‘with-dock’ configuration.
I rechecked the x & y parking values for T0 and then ran TEST_DOCKING. The docking test went very well – very smooth, no hitches at all. An interesting note about TEST_DOCKING; if a recent HOME_ALL/QGL hasn’t been accomplished, it does another set before actually doing the docking moves.
Per recommendation from Grok I also added ‘[gcode_macro STATUS_MSG]’ to the top of printer.cfg to implement dual-destination logging – both to the status line and to the console log so I can see the full range of activity. The full macro is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[respond]
default_type:echo
#11/01/25 added to direct output to both the status line and the console
STATUS_MSG MSG="Heating T1"TYPE=echo# Green console
STATUS_MSG MSG="ERROR: Timeout!"TYPE=error# Red `!!
Note that to make outputs from a STATUS_MSG command immediately visible in the console window in, the standard console filter “Hide Temperatures” must be disabled, and a new filter added to suppress the status messages that are emitted by the ‘STATUS’ line at the end of the STATUS_MSG macro (the STATUS line is required to force a console window update). Interestingly, the ‘Hide Temperatures’ filter is supposed to suppress the 1-2 second period temp reports, but disabling this filter still doesn’t result in a blizzard of temp reports. From Grok:
1
2
3
4
Why No Automatic Temps inConsole?Klipper Design:Klipper doesn't "spam" real-time temperature updates to the console tab (which is primarily for G-code commands/responses and explicit messages like RESPOND/M118). Instead:Periodic stats (including temps) are logged to klippy.log (every ~1 second when active, like during heating).
Temps only appear in the console when explicitly queried (e.g., via M105) or during certain events (e.g., heating errors).
UI Role: The Console tab in Fluidd/Mainsail shows interactive G-code output, not the full backend log. The "Hide Temperatures" filter only affects M105-like responses (hiding them to reduce clutter), but since no automatic reports are sent, disabling it won't"unlock"them.
So I’m happy with the current setup; ‘Hide Temperatures’ filter OFF, ‘Hide Klipper State’ OFF, ‘STATUS’ line removed from STATUS_MSG macro.
02 November 2025 Update:
I saved the three files relevant to the STATUS_MGS macro adventure to a folder named
‘20251102-141756 STATUS_MSG Macro Files’ in the MissChanger/Config Files folder, so now I can start moving forward again toward dual-toolhead nirvana. I plan to do this as follows:
Add [include 251029_T1-Nitehawk-Revo-LDO.cfg] to printer.cfg just before the SAVE_CONFIG section. this file in turn includes 251002_T1_stealthburner_leds.cfg
OK, this came up OK, And now both ‘T0’ and ‘T1’ are available in the ‘Extruder’ tab in the dashboard – yay! From Klippy.log I can see that the T0/T1 include files have been properly in-lined into printer.cfg, along with the T0/T1 neopixel LED files, with NO errors. It’s really nice being able to see the entire config chain in one file.
Looking at klippy.log, I see the following ‘parking’ positions for T0 & T1:
These values should be pretty close to correct for tool changes. I have already verified that T0 can be docked and undocked using TEST_DOCKING, but I still need to re-verify the values for T1.
03 November 2025 Update:
At the moment, neither toolhead has been selected, so I’m going to manually swap T0 out (docked), and T1 in (place it on the carrier), and then command a HOME_ALL (G28). This *should* cause T1 to be detected as the active toolhead. Yes!! T1 was detected as the active toolhead. Wow! I actually knew something!
OK, now I’m going to re-verify the parking positions using manual moves, and then I’m going to try TEST_DOCKING. WooHoo! Test Docking on T1 worked, and now I can even change toolheads under program control, as shown in the following short videos:
Docking test with the TEST_DOCKING macroDemonstrating the ability to change back and forth between T0 & T1
Now that I have tool changing working, I think we are about done with the hardware configuration aspects of the MissChanger mod. I think the next step is to get Prusa Slicer configured to handle dual extruders, and get some test prints going – woo hoo!
Went back through MissChanger’s Prusa Slicer settings, so I think I have this correct. Tried a T1 test print with ‘251103_T1_Strap tensioner optimized_v003.gcode’, but it failed with the error ‘Error evaluating ‘gcode_macro PRINT_START:gcode’: jinja2.exceptions.UndefinedError: ‘dict object’ has no attribute ‘BED‘
I’m thinking that this error is yet another instance of my old single-toolhead PRINT_START macro not being able to handle multiple toolheads.
Grok agreed, and proposed the following PRINT_START macro upgrade:
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
[gcode_macro PRINT_START]
description:Multi-tool start(heat active extruder+bed/chamber)
gcode:
{%set TOOL=printer.toolchanger.tool_number|int%}# Active tool
{%set EXTRUDER='extruder'+(TOOL ifTOOL>0else'')%}# 'extruder' (T0) or 'extruder1' (T1)
{%set BED=params.BED|default(60)|int%}# Default 60 if missing
However, before implementing this change, I saved off the current config file set (printer.cfg, 251003_T0_Nitehawk-Revo-LDO.cfg, 250922_TO_stealthburner_leds.cfg, 251029_T1_Nitehawk-Revo-LDO.cfg, 251002_T1_stealthburner_leds.cfg) to config-20251103-113415.zip and put it in C:\Users\Frank\Documents\3D Projects\Voron Printer\MissChanger\Dock.
Then I replaced my PRINT_START with Grok’s version, saved, and restarted. I also reset (rolled-over) Klippy.log, which caused another save/restart. Then I tried the print again.
Side note: After starting the print, the console window started getting ‘spammed’ by temperature messages – the behavior that resulted in having a ‘hide temperatures’ filter. I activated the filter and the spam stopped (not sure if that affected my STATUS_MSG behavior or not).
Hmm, activating the ‘hide temperatures’ filter didn’t stop the temps span – uh oh!
Well, that went about as well as could be expected, meaning ‘not very well at all’ :(.
but the result was “T1: PRINT_START: T{TOOL} Bed={BED} Extruder={EXTRUDER_TEMP} Chamber={CHAMBER}”. In other words, none of the {} parameters were expanded properly. And to add insult to injury, even that much didn’t show up until I hit F5 (or disabled the ‘hide temperatures’ filter).
The extruder temperature went up to 200C and then stayed there, until eventually resulting in a “Extrude below min temperature”. I’m actually not even sure the T1 extruder ever got hot AT ALL – have to check.
Also got another error – “The value ‘sb_leds’ is not valid for LED”. This sounds like an issue with naming conventions in printer.cfg
The reason the ‘Extruder below temps’ error occurred was because the T0 extruder was being heated, but the desired T1 extruder was not. I confirmed I could manually set T1 to a temp at the printer KlipperScreen and T1’s extruder actually got hot. So, the number mixup may well be in PRINT_START or in the Gcode
07 November 2025 Update:
These last few days I have been working on getting the various stealthburner LED color/intensity combinations working on both T0 & T1 toolheads, and wound up with a totally different configuration than the ‘stock’ MissChanger. Instead of two complete sets of ‘STATUS_xxx’ LED-setting macros, Grok talked me through creating a single set of macro files called ‘251103_shared_stealthburner_leds.cfg’ containing all the macro code, and separate very small ‘251103_T0/T1_stealthburner_leds.cfg. All the T0/T1 files to is define the mcu pin number to be used. When a ‘STATUS_XXX’ macro is called, it first figures out which toolhead is active, and then passes a new ‘LED’ parameter to the appropriate led-setting macro (typically ‘_set_sb_leds_by_name’). This took a long while to work through all the hardware and software problems, but eventually (with a LOT of help from Grok), I got it figured out. All the status codes work now, with both T0 & T1. The related config files were all saved to the ‘C:\Users\Frank\Documents\3D Projects\Voron Printer\MissChanger\Config Files\251107_stealthburner_LED_config’ folder.
In part I of this saga, I described my motivations for starting this project as follows: Last May (2025) I started the process of building a Voron 2.4 300x300mm 3D printer, with the ultimate goal of constructing a dual extruder system capable (hopefully) of complex prints requiring soluble supports. Now that I have the printer running with a single ‘Stealthburner’ toolhead, it is time to move on toward my ultimate goal of a dual extruder system.
After a LOT of research into the many available toolchanger mods, I chose the MISSChanger (the ‘MISS’ stands for ‘Make It Simple, Stupid!’) mod because unlike all the other mods, this one does not require a ‘top hat’ addition to make room for the top-rail-mounted docking system, room that I do not have in my office. Instead, it uses up about 130 mm of print surface (almost half of my available Y-dimension space on my 300×300 plate at the front of the printer for the docking system. If necessary, however, the docking system can be easily removed to recover the print space, but only at the cost of going back to a single-extruder configuration.
08 November 2025 Update:
I now have tool changing working, and I was actually able to make a two-color print, as shown in the following short video and a photo of the 10mm two-color calibration cube.
Smooth toolchanges – who would have thought I would ever get this far!My very first dual-color print on my Voron 2.4/MissChanger 3D Printer
Lots of work still to do. The above print isn’t very high quality, and the zero offset isn’t dialed in yet. In addition I noticed the toolhead logo/nozzle colors were off; the nozzle LEDs were a dark red during print instead of white – which made seeing print details very difficult. Either the STATUS_PRINTING nozzle color isn’t white like I thought, or PRINT_START isn’t changing to STATUS_PRINTING when needed.
I manually checked, and STATUS_PRINTING nozzle LEDs are full white – so that’s not the problem. Looking at PRINT_START I don’t see where the STATUS_xxx macros are used except at the end when STATUS_READY is called.
Side Note:When I started checking where (if anywhere) the stealthburner LED colors were being set at the beginning of a print, I found that they weren’t being set at all, which explains why I wasn’t seeing anything. Also in this process, I discovered that Vin’s MissChanger files contain their own, much more complex PRINT_START macro (among others). This macro is found in ‘misschanger_macros/print_time_default.cfg’, which is included at the very start of Vin’s example ‘printer.cfg’ file. I had blindly copied these include lines into my ‘printer.cfg’ file without ever realizing what was in them, as they didn’t seem to affect much. In particular I never realized there was another PRINT_START macro due to the way that Klipper parses includes – subsequent copies of any section or macro override any previous ones, so my PRINT_START macro always survived the process. I’m not going to worry about this right now – I’ll get to it when I get to it.
I went into PRINT_START and placed some STATUS_XXX macro calls and some STATUS_MSG debug prints, and will re-do the test print. I also took photos of each defined STATUS_XXX color sets so I can match what I’m seeing with a STATUS_XX call in PRINT_START. See below for the photos:
Well, I’m not much smarter than I was yesterday regarding two-color prints, but I think I may have made some progress today. I couldn’t (and still can’t) figure out Vin’s instructions for setting up toolhead Z offsets for tools other than T0. Finally I remembered that Vin’s instructions had me add the following to printer.cfg in the SAVE_CONFIG section:
1
2
3
4
5
#*# [tool_probe T1]
#*# z_offset = -1.750000
#*#
#*# [extruder1]
#*#
This led me to believe that I could probably just mount T1, tell Klipper to send the toolhead to Z-=0, and use the paper test to set the offset. As far as I can tell, this trick actually worked! So, now I am wondering what all the hooforah is for the Lube_Ball calibration tools and associated config files? Here’s a photo of my two-color calibration block, with no fine tuning done for either toolhead.
Two color print (T1 green, T0 white) with both z-offsets set manually, and no fine-tuning
In the above photo, it looks like the T1 z-offset of -1.750 is just a little too much, as the brim lines aren’t squeezed enough to connect to each other. I reduced the offset to -1.700 and ran the print again.
Reduced T1 z-offset from -1.750 to -1.700. Much nicer ‘squeeze on brim lines.
As the above photo shows, this offset produced a much nicer ‘squeeze’ on the brim lines. Now it remains to be seen if this method will hold up over time.
I decided to try Voxel3D_Nederland’s XYZ tuning print, shown here. It will be interesting to see if it confirms the z-offsets I have so far.
When I tried to print this job, I got an error message that said the extruder was too cool. Then I noticed that the T1 extruder temp was where it should be (240C), but the gcode was trying to print with T0 – oops. I hope this is actually a problem with the slicer, as my test prints with the two-color 10mm cal cube have worked correctly.
11 November 2025 Update:
I ran into yet another odd problem with the offsets. After getting the T1 z-offset nailed down I tried another print, and this on failed miserably, with the extruder starting out way too high. I ‘fine-tuned’ it back down again, but it took almost all (and then some) of the original -1.700mm offset back out again – which can’t be right. I re-ran PROBE_CALIBRATE on T1 and got close to the original -1.700mm, and when I tried the print again, it worked fine. However, when I tried to print the same job a second time without any changes, the same thing (extruder tip way too high) happened again. After thinking about this for a while, I began to think that the culprit was the ‘trigger_to_bottom = -1.896’ entry in the ‘[tools_calibrate]’ section of misschanger_settings.cfg. Maybe somehow this was being added to the T1 z-offset?
So, I set the ‘trigger_to_bottom’ value to zero and tried again, and saw that the 10mm two-color print was now consistently perfect. I am now in the process of making several prints in a row to verify that the z-offset is now consistently correct.
Not quite. I got a couple of prints where the extruder started out about 0.05mm too low. Not quite sure that’s all that bad, so I’m going to keep the offset at -1.700 for a few more prints.
I added 0.05mm to the z-offset to arrive at -1.750mm and restarted the firmware, and this time the extruder tip was too high by about 0.900mm – I have no idea why. The ‘trigger_to_bottom_z’ value in misschanger_settings.cfg is still zero. I restarted again and tried another print. This time the extruder tip was too high by about 0.6mm (could be the same as the 0.9 from before). Tried another print without accepting the 0.6 offset recommendation and without restarting. Same thing – extruder tip too high
Chose ‘apply offset’ Klipper restarted, and z_offset in [tool_probe T1 changed from -1.75 to -1.10mm. Did another print. Job went OK. Trying another print without changing anything. Good print.
Did a full Klipper reboot, selected test job without any other changes. This time the extruder tip was WAY too high; almost -2.0mm. At this point the printer.cfg tool_probe T1 offset is -1.10, so to get the extruder down another 1.9mm would put the extruder into positive territory – wow!.
Did a Home-All, then sent extruder to z=0; Had to RAISE the extruder by 0.60mm in order to get the proper ‘paper’ thickness. Did another PROBE_CALIBRATE; the result was _0.477. Accepted the result, and the T1 offset changed from -1.100 to … -1.100 – NO CHANGE! Did a save & restart from MainSail, and checked printer.cfg again. T1 offset is still -1.100 – unchanged. Did a HOME ALL, then checked extruder zero; had to raise the tip 0.5mm for a good ‘paper drag’.
Did another print without changing anything (T1 z_offset still at -1.1). Had to ‘fine-tune’ up 0.5mm, so this time I accepted the change. This time the T1 z_offset did not change with the restart – had to manually revise it – to -1.6000.
Did a ‘save & restart, HOME ALL, and sent extruder to z=0. This time I had to LOWER the extruder tip by 0.5mm for good paper drag. So I adjusted T1 z_offset by 0.5mm UP (minus), but this caused me to have to LOWER it by 0.5 after a HOME ALL.
Alright – there is certainly something rotten in Denmark. I changed to T0, sent the extruder to 150,200,0 and the paper check was fine – within 0.1mm. I did this because of this sentence in Vin’s instructions; “test the z-offset with the paper test. – If it is good then you set up the trigger_to_bottom_z value correctly. However, I had long since manually set the ‘trigger_to_bottom_z’ value to 0, so I really don’t undersdand how this works.
Next, I switched from T0 to T1, ran HOME ALL & QGL. Then tested offset by driving extruder to zero. This time it was pretty much spot-on – within 0.1mm. Tried another test print; good print.
Do ten test prints:
Print 1: OK.
Print 2: OK; a skosh low (brim was hard to scrape off, but not impossible)
Print 3: OK;
Print 4: Failed – extruder started out too high
Print 5: Failed – extruder started out too high
Print 6: Failed – extruder started out too high
I inspected the T1 mount on the carrier, and it seemed pretty tight. Also the screw-in extruder tip seemed secure, so those are probably not the culprit. At this point I noticed that starting when starting a print job, the printer skipped both the HOME ALL & QGL steps (not in PRINT_START?). Maybe that is what is causing the wandering z_offset?
Print 1: After HOME ALL & QGL. looks good – almost perfect first layer.
Print 2: After HOME ALL & QGL. looks good – almost perfect first layer.
Print 3: After HOME ALL & QGL. Good print, but extruder a little high.
Print 4: After HOME ALL & QGL. Poor print. Extruder started out too high
Print 5 After HA & QGL. Poor print. Extruder started out too high
Print 6 After HA & QGL. Poor print. Extruder started out too high
Print 7 After HA & QGL. Excellent print – almost perfect first layer
At this point, it looks like I can consistently get two-color prints that have a functional (if not pretty) first layer with T1, without requiring any ‘fine-tune’ adjustments (although smaller ‘fine tune’ adjustments might be advisable). And this is with the ‘trigger_to_bottom_z’ parameter in the ‘tools_calibrate’ section of misschanger_settings.cfg set to zero.
literally dozens of test prints of my 20x20x10mm high dual-color test print. Note that many successful prints were terminated early, as I was mostly interested in first layer quality.
12 November 2025 Update:
At this point, I think I can successfully do two-color/material prints, with maybe the need for some minor ‘fine-tuning’. However, it is clear that the PRINT_START macro needs to make certain that the HOME_ALL and QGL operations are performed before each print actually starts. Looking at the PRINT_START macro, I see:
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
[gcode_macro PRINT_START]
description:Multi-tool start(heat active extruder+bed/chamber)
So, at least in theory the PRINT_START macro does the HOME_ALL (G28) and QGL if it hasn’t already been done. However, I’m not certain it *ever* does this. I looked to see if there were any status messages emitted, but couldn’t find anything. In fact, there don’t appear to be *any* status messages in the Klippy log. I asked Grok about this, and apparently the only way to get user outputs to the Klippy.log is to put in RESPOND lines. So I put a RESPOND MSG=”In PRINT_START” in the PRINT_START macro. Also, I commented out the ‘if’ statement guarding the HOME_ALL (G28) and QGL commands in PRINT_START to see if that would actually happen, and started a new test print. Console output confirmed that the G28 & QGL commands were actually performed, so we’ll see. Yep, the print started OK (with the extruder tip maybe just a bit too far from the bed, but still very nice!).
The RESPOND message did not appear in Klippy.log – bummer.
I think I’m going to declare victory at this point, and start working on the status LED problem. The problem is that the STATUS_XX commands seem to be going to T0 instead of T1. From the console log I see:
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
Voron24_30
Send code...
12:58PM
Done printing file
12:55PM
Klipper state:Ready
12:55PM
// T0: _set_sb_leds_by_name. led = t0_sb_leds
12:55PM
Klipper state:Ready
12:55PM
// T0: _set_sb_leds_by_name. led = t0_sb_leds
12:55PM
Klipper state:Ready
12:55PM
// T0: Status Ready. led = t0_sb_leds
12:55PM
Selected tool0(tool T0)
12:55PM
Found active tool probe:tool_probe T0
12:55PM
Activating extruder extruder
12:55PM
All probes triggered
12:55PM
Toolhead T0 Select Macro
12:52PM
Klipper state:Ready
12:52PM
// T1: _set_sb_leds_by_name. led = t1_sb_leds
12:52PM
Klipper state:Ready
12:52PM
// T1: _set_sb_leds_by_name. led = t1_sb_leds
12:52PM
Klipper state:Ready
12:52PM
// T1: Status Ready. led = t1_sb_leds
12:52PM
Tool tool T1 already selected
12:52PM
Klipper state:Ready
12:52PM
// T1: _set_sb_leds_by_name. led = t0_sb_leds
12:52PM
Klipper state:Ready
12:52PM
// T1: _set_sb_leds_by_name. led = t0_sb_leds
12:50PM
Klipper state:Ready
12:50PM
// T1: _set_sb_leds_by_name. led = t0_sb_leds
12:50PM
Klipper state:Ready
12:50PM
// T1: _set_sb_leds_by_name. led = t0_sb_leds
12:50PM
...
12:48PM
TOOL_BED_MESH_CALIBRATE...
12:48PM
Klipper state:Ready
12:48PM
// T1: _set_sb_leds_by_name. led = t0_sb_leds
12:48PM
Klipper state:Ready
12:48PM
// T1: _set_sb_leds_by_name. led = t0_sb_leds
12:48PM
Klipper state:Ready
12:48PM
// T1: STATUS_MESHING
12:48PM
Tool tool T1 already selected
From the above output, I can see that the ‘STATUS_MESHING’ command was executed at 12:48, but the subsequent call to _set_sb_leds_by_name was called with ‘led = t0_sb_leds’ instead of ‘t1_sb_leds’, but I didn’t see any actual status change messages other than the one ‘STATUS_MESHING’ output. I think I need to edit ALL the STATUS_XX messages to include a STATUS_MSG MSG=”[current status call]” so I can track progress.
Saved all relevant config files (and printer.cfg backups) to ‘config-20251112-131701.zip’, and then deleted all the backup files.
Opened 251103_shared_stealthburner_leds.cfg and edited all ‘gcode_macro status_xxx’ macros to include a STATUS_MSG MSG = “status_xxx” line. Then I restarted Klipper, cleared the console and started another test print.
I noted that the printer started out with T0 initialized and it did the initial HOME_ALL & QGL with T0. Then changed tools and did STATUS_MESHING (with led = t0_sb_leds). Then a full mesh calibration, then // T1: status_heating (led = t0_sb_leds). I noted that the T0 leds did, in fact, change to the ‘STATUS_HEATING’ color set.
I got some Grok help in debugging this situation, and now the LED action seems to be following the correct toolhead. The affected files were 251103_shared_stealthburner_leds.cfg and 251103_paynter_toolchanger.cfg.
I saved the entire configuration set in ‘config-20251112-143625.zip’ in the MissChanger/Config Files folder.
Not so fast! There were a few more bugs to work out, but with Grok’s help we seem to have exterminated them – at least so far. I again saved the configuration set in MissChanger/Config Files/config-20251112-173325.zip
At this point I can print a two-color model reasonably consistently, with toolhead LED colors changing through ‘status_cal_z’, ‘status_meshing’, ‘status_heating’, then ‘status_printing’ and finally to ‘status_ready’ after the print concludes – yay! Here is a photo of a two-color print I just did from a design I created some time ago with my old Flashforge Pro IDEX printer (before I threw it in the trash!)
Final exam: Two color print of a desk nameplate for my wife
30 November 2025 Update:
Well, it has certainly been an interesting adventure so far, but I think I may have actually gotten to the point where I can do dual-material prints with my Voron printer. The last set of hurdles to surmount were a series of infinite hangs during tool changes, caused by poorly formed macros that were way too picky about extruder temperatures. The macros wanted to wait until the extruder temperature was just right, and sometimes the extruder in question just didn’t want to be that exact. This caused long waits (on the order of 30 seconds) before picking up the next tool, and eventually the printer wouldn’t pick up the next tool at all. I got a lot of help from Grok running this down, and I am now a real fan of this AI. Its superpower is a combination of an in-depth understanding of Klipper (by reading the manual at about 10000x human speed, with perfect recall) combined with the ability to grind through huge mind-numbing klippy.log files and then use that information to pinpoint the place(s) in my macros where things weren’t working correctly. For instance, here is one of several macros that regulate the process of changing from one toolhead to another:
This is very tough stuff to get my head around due to all the variable referencing, but basically it controls the carriage movement that drops off the current tool into its docking station before the printer moves on to picking up the next one. The Grok realized that part of the problem may have been that the extruder temps weren’t being set at their ‘standby’ temps when they were dropped off, which allowed the extruder to cool all the way down to ambient, which then caused a long delay on pickup as the printer waited for the extruder temp to come back up (and as I said before, sometimes the wait became infinite). In any case, Grok wrote out a completely valid bit of Klipper code to add to the end of this macro to make sure the dropped-off extruder temp was set to ‘standby’ properly, as follows:
1
2
3
4
5
6
7
#11/30/25 gfp: added per Grok to eliminate pause for temperature on next pickup
Then it went on to look at the companion ‘TOOL PICKUP’ macro and identified the real culprit, which was the use of the ‘M109’ gcode command, which sets the extruder temperature, and then waits for the extruder temperature to fall within a very narrow range. This can take 30 seconds or more – and sometimes the ‘more’ is ‘infinity. Grok’s solution was to use the M104 command, which also sets the temperature, but doesn’t wait – it is non-blocking. Then he followed this with a TEMPERATURE_WAIT command that does block – but only until the extruder temp rises to within 5C of the setpoint. Here’s the applicable part of this macro, with the original M109-based code commented out and the Grok code substituted.
These modifications worked extremely well. My test print went from an average of about 30-45sec between the time one tool finished printing to the time the next tool started printing (and as I noted before, it almost always died entirely at some point in the print) to an average of about 15sec inter-tool delay, with complete reliability – YAY!
Here’s a couple of photos of my test print. It is a box with cutouts for chassis-mounted USB-C female sockets; the body was printed in green PETG, and the supports were printed in black PLA.
Of course, my ultimate goal is still to be able to do prints that require water-soluble support material, but the above project at least demonstrated that the two-toolhead MissChanger mod – or at least my version of it, can handle dual-material prints.
03 December 2025 Update:
Made my first water-soluble support print today – yay! I printed the bottom part of one of my caplet dispensers, as shown in the screenshot below:
And here is the result:
07 December 2025 Update:
After running into yet more problems with getting a good ‘squish’ on T1 (and spending WAY too much time working with Grok to no real result), today I went back to Vin’s instructions and went through the entire thing again, carefully.
In preparation for this I changed out the PVA soluble filament for PETG to eliminate any issues associated with the rather flexible and sometimes cantankerous PVA. Then, starting with 4.3. Calibrate offsets .
The first step is PROBE_CALIBRATE. This is the normal ‘paper test’ z-offset calibration, a step I have become very familiar with.
4.3.2. Calibration probe setup: This describes the process of setting up the LUBE_BALLS calibration probe, which I had already done. The description is a bit confusing because Vin also talks about another type of physical offset probe called a ‘Nudge Probe’, so I had to keep working around that issue. Another confusing factor is the need to keep removing and replacing the calibration probe. It can’t be on the bed when doing a QGL, and a QGL is required before doing most steps – ugh!
The steps for validating the calibration probe and calibrating T0, which is then used as the reference for all other toolheads:
Go to printer.cfg and record the z-offset for [tool_probe T0], which should be at (or near) the bottom of the file
Mount tool-head T0 and make sure it’s nozzle is clean
!!! MAKE SURE THE CALIBRATION PROBE IS NOT MOUNTED !!!
Run G28 and QUAD_GANTRY_LEVEL
Mount the calibration probe to the bed.
Run CALIBRATE_TRIGGER_BOTTOM. If it is a fresh calibration probe, it is worth redoing this step until the suggested value is consistent at +/- 0.005mm.
Copy the proposed offset on the console to the trigger_to_bottom_z: in [tools_calibrate].
Save and restart
Run CALIBRATE_OFFSETS TOOL=0 – But, DON’T RESTART
Check if proposed z-offset to be +-0.01mm of the recorded z_offset in step 1.
Run CALIBRATE_OFFSETS TOOL=0 2-3 more times to make sure the measured value is consistent.
The above procedure made a lot more sense today than it did in the past. Maybe I had to struggle for a while and then come back to this from a fresh perspective, kinda like letting go of that NYT Sunday Crossword at midnight, getting some sleep, and coming back to it the next day.
4.3.3. Other tool-head(s):
This is where I got lost before, as I just didn’t understand the strategy being pursued. The idea is to make sure the LUBE_BALLS calibration assembly (probe) is working by using it to calibrate the offsets for T0, and then use the T0 results as the reference for all other toolheads. The magic that makes this work is a macro called CALIBRATE_OFFSETS, which starts by running T0 against the LUBE_BALLS cal probe, and then automatically running each subsequent toolhead T1 through TN against the same probe, thereby establishing the X, Y & Z offsets of all the other toolheads relative to T0. These offsets are then stored in the gcode_x/y/z_offset variables in the [tool T(n)] section in the SAVE_CONFIG area of printer.cfg. If CALIBRATE_OFFSETS is called with a ‘TOOL=N’ argument, then only T0 and TN are calibrated. If CALIBRATE_OFFSETS TOOL=0 is used, only T0 is calibrated as is done in step 9 above.
After doing all this, I wound up with the following offsets for T1:
1
2
3
4
#*# [tool T1]
#*# gcode_x_offset: -0.101562
#*# gcode_y_offset: -0.195312
#*# gcode_z_offset: -0.120000
Amazingly enough, this setup resulted in a ‘perfect’ (or at least ‘damned good’) first layer, using my first-layer adjustment test print, shown below:
After this was successful, I replaced the PETG on T1 with my brand-new roll of SainSmart PVA, and tried my partial pill dispenser test print (shown above) again. This time the first PVA layer was as good as I have ever seen for a PVA layer, and the entire print continued without fail (The PVA supports inside the open cavity of the slide part did get loose about halfway through the print, but that wasn’t due to any calibration problem – just PVA not sticking completely to PVA. See the following gallery of print photos:
So at this point I believe I have essentially completed the journey to add a second toolhead to my Voron 2.4/300 3D printer – what a ride!
I’ve been having a heck of a time getting the neopixel LED string working on one of my two Stealthburner toolheads (MissChanger mod). Sometimes it would work, sometimes it would not, and I wasn’t able to correlate the two conditions to anything I was doing. On several occasions it seemed to be correlated with whether or not the front piece (the one containing the LEDs) was securely fastened to the main toolhead body with the two (very) long and two short(er) screws. It seemed it worked when the screws were not in, and didn’t work when they were in and tightened down.
At first I was convinced that one or more LED wires was getting pinched between the two toolhead halves, and when I tightened down the mating screws, the circuit was interrupted. I even found evidence for this and fixed it – several times – but the intermittent behavior continued.
Some research by Grok turned up a LOT of talking about SB LED issues, which were mostly divided into two categories – one thread earlier this year talked about waveform timing issues, and the other thread talked about the fact that the rp2040 outputs 3.3V waveforms while the nominal voltage for the LED string is 5V.
In desperation, I decided to attach a short cable to the LED string connector pads on the Nitehawk PCB on the front half of the SB, thereby allowing me to directly monitor the actual waveform being transmitted to the LED string.
I was able to observe the ‘not working’ waveform once and then my troublesome SB refused to not work again, stubbornly continuing to light up no matter what I did. The waveform in the ‘not working’ condition looked more like triangular waves rather than rectangular indicating an output loading condition of some kind. Unfortunately, I was unable to capture the waveform on my DSO, so I can’t display it here. The ‘working’ waveform is shown below:
Toolhead mounted on the Voron printer, LED string working properly
In an earlier post I described the test jig I had created using an Arduino UNO. I could use it to connect directly to the LED string on the front half of the SB, and my troublesome LED string worked fine on the test jig. As Grok pointed out however, the Arduino output waveform went to 5V, not the 3.3V the actual hardware was providing, so the test jig might not be valid. So, I replaced the Arduino UNO with a Teensy 4.1, which does use 3.3V outputs, as shown here:
I added a potentiometer into the output circuit so I could slowly drop the output waveform down from 3.3V to see where the LED string stopped working. At 3.3V output it was solid, and this particular string still worked fine at 2.7V peak, as shown in following trace-grabs:
Teensy 4.1 3.3V output, LED string workingTeensy 4.1 2.7V output, LED string working
So, it appears that that at least my particular LED string works fine all the way down to 2.7V, so I don’t think the intermittent issues other users have been experiencing are due to the rp2040 having a 3.3V instead of 5V output. My current thinking is that the problems are more likely to be the timing issues discussed in this Klipper Github PR
01 November 2025 Update:
When all was said and done, I could no longer get the LED string on my troublesome toolhead to fail, so I wound up putting it back together and mounting it back on the printer. However, as insurance against a future Murphy attack, I left the external LED data line monitor cable intact, and hot-glued it into the intake cavity for the part cooling fan, as shone in the following photo:
Earlier this month I was able to run a successful docking sequence with one toolhead, as shown below:
Unfortunately, I haven’t been able to replicate this successfully, and after several failures I started looking for the root cause. Eventually I figured out that at least part of the issue was that the middle ‘retainer’ foot that keeps the sliding ‘claw’ from falling off is rotated slightly by the claw when it comes out the ‘docked’ position during an undocking procedure, and this rotation keeps the claw from freely sliding down the ramp and subsequently connecting to the toolhead carrier. See the following two photos for an example of this issue:
Before the dock claw comes off the pinsAfter the dock claw comes off the pins
At first, I thought I just hadn’t tightened the retainer foot down enough, but doing so actually made the problem worse; now when the toolhead is pulled out of the pins by the toolhead carrier, there is enough force to still rotate the foot, but now the additional screw tension makes it even harder for the claw to slide down to the release position – yikes!
As an experiment, I modified the foot by removing 1mm from the bottom to form a ledge to mate with the front of the base, figuring this would stop the foot from rotating. Unfortunately this did not work – there was still enough play in even a very tightly matched ledge to allow the foot to rotate. Next I decided to create a second point of alignment to the foot by way of a post on the underside of the foot that mates to a hole on the upper surface of the base, as shown in the following photos.
The combination of the post/hole and ledge features seemed to do the trick. Now the claw disengages cleanly from the ‘docked’ position and slides freely down to the ‘undocked’ position as shown in the following video:
In an interesting side note, I discovered that I could easily adjust the rubber band tension by making a loop around either (or both) screw/washer combinations. As it turned out though, even the weakest arrangement (no additional loops) with the new foot/claw configuration was plenty to pull the claw all the way down the slide. Hopefully this will result in much better dock/undock performance – we’ll see.
24 October 2025 Update:
I wound up going through a few minor iterations on the design of the dock body/base and the retainer piece. On the dock body, I opened up all four pin docking holes by 0.5mm, and slightly enlarged the holes for the 2mm self-tapping screws, as these were very difficult to get all the way in, especially the two on the bottom. On the retainer piece, I wound up removing 1mm from the side that the claw slides on, as this seemed to provide a better sliding action.
After getting all this done, I remounted the dock assembly on my printer, and again (for the upteenth time) went through section 4.1. ‘Park position calibration’. When I thought I had everything lined up, I ran TEST_DOCKING only to have it fail – again. The claw wasn’t sliding over to the docked position, so I must not have gotten the Y-position correct and the pins didn’t slide into their respective holes. Went through the whole process again, but this time when I got to the part where the claw was ‘all the way up the ramp’ I commanded an X-minus move of 5mm, and made sure the claw moved smoothly into the docked position. Because I was working with the left-most dock , I was able to actually look through the left side transparency of the printer and see whether the pins on that side were lined up with the holes. Turns out that the docking failure was caused by the Y position being off (1mm too positive). I was only 1mm, but that made a huge difference. Here’s a short video of a successful dock/undock sequence.
My wife wants a low-intensity lamp she can turn on/off using any common TV remote from a distance of about 20ft. I found this set of four pairs of IR receiver and transmitter breakout boards on Amazon, and discovered the receiver can indeed detect the signal from a TF remote at well more than 20ft, so the project looks feasible.
Wiring is simplicity itself; just +5V, GND, and output pins.
Here are some representative scope grabs:
20ms/div, so each block of data is about 80-90msec longCloseup of one data block
The idle output voltage is +5V, so I’m thinking that I can probably just low-pass the output and set a threshold.
The low-pass idea didn’t work – there wasn’t enough difference between the HIGH level and the average to comfortably use, so I wound up just counting the number of LOW occurrences in the receiver output, and that worked well (and eliminated two components as well). Here’s the finished prototype:
16 October 2025 Update:
This project is now finished, sort-of. The TV Remote Lamp works according to the wife, but there are some issues with the remote. If she uses the TV remote as we intended, and presses the ON/OFF button at the top – that will also turn the TV itself back on – an unintended consequence, but one that can be resolved by NOT using the ON/OFF button on the remote – any button will do.
Here’s a photo of the finished product and the accompanying enclosure, compliments of my Prusa Core ONE 3D printer:
And here is a photo of the ‘installation’ in our master bathroom:
As can be seen from the above photo, the sensor and LED are actually facing away from the bedroom and toward the mirror, but the sensor is perfectly happy with the reflected signal from the TV remote.
And here is the finished Arduino code for the Teensy 3.2:
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/*
Name: TVRemoteLamp.ino
Created: 10/12/2025 8:52:39 AM
Author: FRANK_XPS_9530\Frank
*/
#include <EEPROM.h>
#define INPUT_PIN 23
#define LAMP_PIN 3
#define TV_REMOTE_SIGNAL_COUNT 1000
#define TV_REMOTE_LOW_COUNT_THRESHOLD 100 //20%
#define REPEAT_TRIGGER_DELAY_MSEC 1000
//From NiteLight project
#define LED_MAX 250
#define LED_MIN 0
#define UP_BUTTON_PIN 14
#define DOWN_BUTTON_PIN 22
#define EEPROM_ADDRESS 0
#define PWM_INCREMENT 5
//#define OFFLINE
uint8_t LED_Intensity=0;
elapsedMillis sinceLastOutput;
boolbDone=false;
uint16_t samp=0;
uint16_t samples[1000];
uint32_t Msec[1000];
uint16_t low_count=0;
boolbLampStatus=false;
voidsetup()
{
#ifndef OFFLINE
Serial.begin(115200);
delay(2000);//needed for teensy
#endif
pinMode(INPUT_PIN,INPUT);
//pinMode(LED_BUILTIN, OUTPUT);
pinMode(LAMP_PIN,OUTPUT);
//digitalWrite(LED_BUILTIN, HIGH);
digitalWrite(LAMP_PIN,LOW);
//From NiteLight project
pinMode(UP_BUTTON_PIN,INPUT_PULLUP);
pinMode(DOWN_BUTTON_PIN,INPUT_PULLUP);
LED_Intensity=EEPROM.read(EEPROM_ADDRESS);
Serial.printf("Got = %d from EEPROM address %d\n",LED_Intensity,EEPROM_ADDRESS);
I have decided to make the Klipper-related changes for my MissChanger adventure into its own post, so readers won’t have to wade through all my other non-klipper related blatherings.
The current project status is:
My single SB configuration with single-extruder config file 250830_OrigToolhead_printer.cfg & 250920_Original_stealthburner_leds.cfg working fine.
Currently using my new SB (due to original SB’s neopixel intermittency) with original SB’s NH BCB (due to new SB,s PCB failure). This combo works fine
Next step is to reload 250928_MC_T0_printer.cfg, 250922_T0-Nitehawk-Revo-LDO_Ex.cfg and 250922_T0_stealthburner_leds.cfg. this config file set loaded and started up fine.
Now it is time to go through Vim’s ‘Calibration’ (Section 4 in the TOC) instructions to see if I can get prints to work
02 October 2025 Update:
The first part of the ‘Calibration’ section is intended to shake the toolhead around on the Tap & Change mount to make sure it will slide onto and off the mount during tool changes.
Ran ‘G28’ (Home All) and confirmed the toolhead Z position of +10mm at the center of the bed.
Set the heated bed temp to 60C. According to Vin, this is to get the chamber temp up to 40C or so before starting the ‘shake & bake’ routine.
Tried to run ‘SHAPER_CALIBRATE’ but got a ‘Unknown Command’ error. Researched this a bit, and decided to skip this step for the moment.
So, I moved on to Section 4.1 – Park Position Calibrate.
First, I mounted the docking hardware I had previously constructed. This in itself required a bit of work to get everything assembled and mounted.
Then I ran the Home-All & QGL and STOP_TOOL_PROBE_CRASH_DETECTION.
I temporarily replaced the ‘100’ with ‘0.1’ in the toolhead movement options for X/Y movement. Then I used these movement options to optimize the ‘X’ position of the toolhead in front of the leftmost dock, as shown.
This process turned out to be a LOT more complicated than I thought, due to the irregular shape of the stealthburner toolhead. Not only did the X position have to be correct, but the dock module rotation around the 10mm rod mattered as well. Eventually I got what I think is a good lineup at X = 68.7.
After getting the X value down, and getting a good dock module orientation, I tried some dock/undock movements as shown in the following short video:
Tool 0 Dock/Undock testing
The above test was performed by moving the toolhead laterally to the optimum X position (68.7), and forward to just in front of the dock (Y ~ 110). Then I commanded a 50mm Y travel to fully engage the toolhead in the dock without forcing the docking collar up the locking ramp, followed by backing the toolhead out 50mm & repeating the process.
At this point I have one toolhead docking station calibrated with the leftmost docking station.
As a side note, my replacement NH USB PC and replacement RGB LED string came in today, so I have two working toolheads again. My original (Red) SB is now labelled as ‘T1’ and my second (Gray) SB is now labelled as ‘T0’. the USB serial ID’s are”
1
2
3
usb-Klipper_rp2040_4E363334320B42B6-if00--Orig(Red)toolhead with newUSB PC
usb-Klipper_rp2040_4E363334324B5803-if00--Second(Gray)toolhead with orig USB PC
The next step is to create a second toolhead-specific config with its companion stealthburner_leds.cfg. Since the two stealthburners are identical save for their USB IDSs, this should be relatively easy.
I copied 250922_T0-Nitehawk-Revo-LDO_Ex.cfg to 251002_T1-Nitehawk-Revo-LDO_Ex.cfg, replaced the T0 USB ID with the new T1 USB ID, replaced include 250922_T0_stealthburner_leds.cfg with include 251002_T1_stealthburner_leds.cfg, and changed all 20 instances of ‘nhk0:’ with ‘nhk1:’.
I copied 250922_T0_stealthburner_leds.cfg to 251002_T1_stealthburner_leds.cfg and changed ‘nhk0:gpio7’ to ‘nhk1:gpio7’
Then I saved 250928_MC_T0_printer.cfg to 251002_MC_T1_printer.cfg and (on line 826) changed include ./250922_T0-Nitehawk-Revo-LDO_Ex.cfg to include ./251002_T1-Nitehawk-Revo-LDO_Ex.cfg.
Then using SCP in a Windows command window, I loaded all the above to the raspberry, changing the name of 251002_MC_T1_printer.cfg to just printer.cfg.
I disconnected the USB/Power connector onT0 and restarted restarted the printer. Got ‘mcu nhk0’ Unable to connect.
03 October 2025 Update:
I was a bit ahead of myself with getting the second toolhead up and running. For one thing, I discovered the problem I was having with not being able to successfully do a firmware restart instead having to cycle power was a known problem with the LDO USB adaptor board, and the fix was to physically remove two resistor chips from the PCB. I did this for both the USB adaptor boards.
Then I realized I hadn’t really completed the ‘park position calibrate’ step – I had just done the X position step, so I reverted the firmware to the T0 configuration and started over with this procedure. This time I wound up with X = 69.7 (probably due to some lateral movement of the entire docking bar).
05 October 2025 Update:
Finally got all the way through the dock park calibrate step for T0, and was able to run several successful TEST_DOCK cycles
08 October 2025 Update:
The last couple of days have been marked by one disaster after another. First, I have been unable to get the neopixel string on my new(er) Stealthburner to work, even though I would have sworn on a stack of bibles that it was working fine a few days ago. Second, I tried to ‘unload filament’, forgetting that that particular macro (and a few others I suspect) move the toolhead to the front of the printer, where it promptly collided with the docking assembly, and third, I tried a test print with my new(er) toolhead forgetting that I needed to recalibrate the z-axis offset, so my extruder tip was dragging along the print bed while I frantically banged on the fine-tuning Z+ button. I thought I was doing OK until I watched in horror as the entire conical tip of the extruder fell onto the print bed, along with a huge glop of molten filament! YIKES!!!
Neopixel LED Woes:
By swapping the front shell of my known-good original toolhead with the one from my new(er) toolhead, I was able to determine that the neopixel problem followed the front shell – so not a NH PCB, umbilical or USB adaptor problem. I removed the neopixel strings from both shells and swapped just the neopixel string – and the problem followed the suspect string. Visual inspection of the string was hampered by the fact that whoever assembled the string used all black wires for the intra-pixel connections, and then glopped what looks like black RTV on the connections.
While I did find and repair a broken connection, the string still didn’t work. At this point I decided to get a replacement string on order, so I jumped up on Amazon and ordered this:
When it arrived, I plugged it into my new(er) toolhead (thankfully without going to the trouble of installing all the LEDs) whereupon I discovered that it didn’t work either! I did the same thing with my original shell – no joy there either. Now I feel like I’m in an episode of “The Twilight Zone” (for those of you old enough to have watched them); I have three strings, one of which is brand-new, but only my original toolhead string actually works! After sleeping on this, I decided to set up a test jig to test all the strings independently of the printer. I grabbed an ancient Arduino UNO and a 3-pin 2.0mm male JST plug from my parts bin, downloaded Adafruit’s wonderful NeoPixel library and used their ‘strandtest.ino’ example program to drive the string under test. After getting through all the normal stupid programmer issues, I was able to validate the test jig against my known-good neopixel string – Yay! Then I tested the suspect string from my new(er) toolhead – yep still bad – and then the brand-new string from Amazon – bad. I was really shocked that the new string was bad, as it looked perfect physically, and since the pixels are wired in parallel, I would certainly have expected at least one to work. So, I went looking on Amazon for reviews and found one that said the blue (data) and black (ground) wires were reversed in the connector – yikes! I reversed the pins, and voila` the string lit up – YAY!!
Re-pinned Amazon Neopixel string
Now I have two working strings and one that’s still dead. Maybe it has the same mis-wiring issue? I grabbed my trusty Hanmatek DOS1102 digital O’Scope (a must-have for a well-equipped lab) and used it on my brand-new working string to verify that each pixel chip on the brand-new string had good, solid data (see trace-grab below).
However, when I looked at the ‘bad’ string, I can see the proper waveform on the ‘DO’ solder pad on the ‘input’ side of the first pixel, but not on the ‘output’ side (I didn’t realize until just now that there was an ‘input’ and ‘output’ side – I thought the data line was just like the +V and GND – straight passthrough). So, I bypassed the first pixel by physically connecting the ‘output’ wire to the ‘input’ pad and sure ’nuff, the remaining two pixels lit right up!
‘Bad’ neopixel string test with first pixel bypassed
I installed the new Neopixel string into my ‘T0’ toolhead (with the gray shell). Now that I have my nifty Arduino test program, I was able to test the string at each stage of the installation process to make sure all the required wire contortions didn’t fatally pinch a wire or break a solder connection. Here it is after the string was fully installed (and yes, I did check that the front-facing LED was working too).
Toolhead T0 shell with Neopixel string installed
If you happen to have an Arduino-compatible board (Ardiono, Sparkfun, Teensy, etc) you can easily build the same test jig. I have provided the Arduino code below:
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
/*
Name: NeopixelTest2.ino
Created: 10/7/2025 5:35:59 PM
Author: FRANK_XPS_9530\Frank
*/
// A basic everyday NeoPixel strip test program.
// NEOPIXEL BEST PRACTICES for most reliable operation:
// - Add 1000 uF CAPACITOR between NeoPixel strip's + and - connections.
// - MINIMIZE WIRING LENGTH between microcontroller board and first pixel.
// - NeoPixel strip's DATA-IN should pass through a 300-500 OHM RESISTOR.
// - AVOID connecting NeoPixels on a LIVE CIRCUIT. If you must, ALWAYS
// connect GROUND (-) first, then +, then data.
// - When using a 3.3V microcontroller with a 5V-powered NeoPixel strip,
// a LOGIC-LEVEL CONVERTER on the data line is STRONGLY RECOMMENDED.
// (Skipping these may work OK on your workbench but can fail in the field)
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif
// Which pin on the Arduino is connected to the NeoPixels?
// On a Trinket or Gemma we suggest changing this to 1:
#define LED_PIN 6
// How many NeoPixels are attached to the Arduino?
strip.setPixelColor(c,color);// Set pixel 'c' to value 'color'
}
strip.show();// Update strip with new contents
delay(wait);// Pause for a moment
firstPixelHue+=65536/90;// One cycle of color wheel over 90 frames
}
}
}
Filament Load/Unload macros – and others?
When I tried to do a filament unload, the toolhead promptly drove itself right into the dock – oops! It was at this point that I realized I really didn’t know what I was doing, and had to get A LOT smarter. The filament unload macro (and surely many others) uses print bed size information from printer.cfg. gcode_macro UNLOAD_FILAMENT calls a macro called ‘FRONT’ to move the toolhead to the front center of the print bed, as shown below:
As can be seen in the above code, the line “G1 X{x_center} Y10 F7800” uses the variable ‘x_center’, but goes to Y = 10, which is why it drives into the dock. This macro should really use something like “Y{printer.position_max – 10}, where ‘printer.position_max’ changes depending on whether or not the dock is present (i.e. on the value of the ‘variable_dock’ variable in ‘gcode_macro _home’)
The above issue ran me down a rabbit hole from which I have just emerged – dirty but victorious. Although this crash problem could have been fixed easily by just changing ‘Y10’ to ‘Y110’, I was worried about other places where ‘magic numbers’ might be lurking. And then this got me thinking about all those places in the code where ‘printer.toolhead.axis_maximum.y’ is used, as this value doesn’t currently get changed when CONFIG_TOGGLE is executed.
So, I posed this problem to Grok as follows:
I have the MissChanger toolchanger mod installed. This mod uses a macro called CONFIG_TOGGLE to modify some printer.cfg parameters to account for the approximately 130mm at the front of the build plate occluded by the toolhead docking assembly, The python function ‘klipper-toolchanger/klipper/extras/config_switch.py’ manages this by replacing a section of printer.cfg with either the contents of ‘printer_data/config/config/config_no_dock.cfg’ or ‘printer_data/config/config/config_wt_dock.cfg’ I have attached copies of ‘printer.cfg’, ‘config_wt_dock.cfg’ and ‘config_no_dock.cfg’. It looks like the switch code should modify the printer object’s minimum_y value (derived from the stepper_y section’s position_max) instead of ( or maybe in addition to) the ‘variable_xh’ value in printer.cfg, as the printer object’s values are used everywhere in the code. What do you think?
Amazingly, Grok not only agreed with the idea in principal, but went on to digest the files I had provided and then produce a complete step-by-step procedure for implementing the idea, including complete ‘config_switch files. Grok even analyzed ‘klipper-toolchanger/klipper/extras/config_switch.py’ and figured out that the [stepper_y] and [tmc5160 stepper_y] sections would have to be moved below the ‘;< # Section Variable marker’ line in printer.cfg so that ‘config_switch.py’ could modify the ‘position_min:’ value appropriately. And Grok did the whole thing in just a few minutes! To say that I was blown away by this would be to hugely understate the impact this had on me – wow!
Armed with Grok’s step-by-step plan, I was able (after more ‘running around in circles’ than I care to admit) come up with a complete working set of files to support ‘with_dock’ and ‘without_dock’ configuration switching.
At this point I can switch from the ‘dock module installed’ to the ‘dock module removed’ condition freely. It remains to be seen whether I can now build on that to get to a fully functional dual-toolhead system.
16 October 2025 Update:
I was away for a while, but I’m back now. I read through my notes above, and starting from there will try to make a bit more progress. After getting the ‘config_toggle’ to work, I’m going to try to get the current toolhead (gray shell) working to the point of doing a test print. DON’T FORGET TO MAKE SURE THE ‘CONFIG_TOGGLE’ state matches the actual physical state (docking assembly present or not)!
Note: If you get the error “failed to detect active tool” after a head-banging episode, it is probably because the power was cycled with the toolhead resting on the print bed with the z_axis sensor in the ‘triggered’ state – apparently this prevents the toolhead from being ‘active’. Manually raise the gantry sufficiently to UNtrigger the sensor (blue LED changes to RED). Don’t worry about keeping the gantry level as this will be done later.
In the ‘Dock Installed’ configuraion, I got the Home_all to work, but the gantry-level failed with an ‘out of bounds’ error. This was due to the config_wt_dock.cfg file having ‘110’ as the min Y, but ‘position_min’ in [stepper_y] was set to 130 – oops. I fixed this by setting both numbers to 120 and did ‘Save&Restart’. Then I did *two* ‘CONFIG_TOGGLES’ and re-checked. OOPS! no change to ‘printer.cfg’ – how can this be?
I went back into config_wt_dock.cfg and saw that the numbers had not changed – so what happenec? Checked config_no_dock.cfg and those numbers are OK (25 for min quad level move in Y, position_min = 0 in [stepper_y]).
Did ONE Config_Toggle, and I see the value changed to the ‘no_dock’ values. Did a second one, and now the values are ‘110’ and ‘130’ again, even though I changed them to 120 & 120 in config_wt_dock.cfg. No, they are still 110 & 130 in config_wt_dock.cfg. I edited them again, verified that they were still correct after a save, then did a Save & Restart and checked again. This time they are correct. Oooh, I have a headache! I did a CONFIG_TOGGLE and verified the ‘no_dock’ numbers were correct, but when I did another CONFIG_TOGGLE, not only were the ‘wt_dock’ numbers wrong in printer.cfg, they were also wrong in /config/config_wt_dock.cfg! Something is editing (or reverting?) the wt_dock file
OK, the problem was that CONFIG_TOGGLE calls CONFIG_SAVE before anything else, and CONFIG_SAVE, writes the dock-related contents of printer.cfg to ‘config_wt_dock’ or ‘config_no_dock’ depending on the value (True or False) of the ‘variable_dock:’ parameter. Now fixed.
So now HOME_ALL and QUAD_GANTRY_LEVEL both worked as expected in the ‘with dock’ configuration. So now doing the ‘Calibrate reference z-offset’ step. *should* use the value ‘130’ (min_y = 120 + 10 = 130). YES! It did – and I’m getting a successful print – YAY!
The first test print required a ‘fine-tune’ adjustment of +0.350 to get the right amount of ‘squish’. When the print completed, I accepted the change, and this modified the z-offset to -2.000 (was -1.650). The second test print was OK without any fine-tuning.
I would like to have the toolhead go to Z = 225 after the print has finished but, if possible, do this only for the ‘with_dock’ configuration. I modified printer.cfg to put the entire PRINT_END macro just below the ‘variables’ marker, and then changed the finish z-axis position to 225. Then I ran a test print and this worked – the toolhead climbed to 225, pulling the dock assembly with it so I could easily access the finished print. Then I did a ‘SAVE_CONFIG_MODE’ from the console to write the new printer.cfg contents to the ‘config_wt_dock.cfg’ file. I checked this file and verified that the PRINT_END macro had indeed been saved to it. Then I switched to the ‘no-dock’ configuration, again moved the PRINT_END macro below the variable marker, changed the ending z position to Z25, and ran SAVE_CONFIG_MODE to write it out to ‘config_no_dock’.
Then I did a ‘test print, to verify that the PRINT_END macro works as intended in this config.
18 October 2025 Update:
I couldn’t get the TEST_DOCK operation to work. I kept getting ‘move out of bounds’ errors even though I had executed the STOP_TOOL_PROBE_CRASH_DETECTION macro. This worked before, so I was more than a little bit confused. When I asked Grok about this, he maintained that the STOP_TOOL_PROBE_CRASH_DETECTION has only a ‘local’ (whatever that means) effect – the hardware bounds listed in the config file are still checked and honored. Just now it came to me that I caused this problem when I modified the configuration toggle mechanism to dynamically modify the ‘position_min’ variable in [stepper_y]
1
2
3
4
5
6
7
8
9
[stepper_y]
step_pin:PF15
dir_pin:!PF14
enable_pin:!PE9
rotation_distance:40
microsteps:32
full_steps_per_rotation:400#set to 200 for 1.8 degree stepper
endstop_pin:PC2
position_min:120#Dock installed
I thought I was being very clever, but apparently not clever enough for klipper!
19 October 2025 Update:
When I asked Grok about this issue, it gave me a pretty detailed explanation why the previous ‘clever idea’ didn’t (and won’t) work. Here is Grok’s response:
To address your issue with position_min blocking docking operations while still leveraging printer.toolhead.axis_minimum.y (and similar) for general macro consistency, the core challenge is that position_min enforces a hard, system-wide limit on all moves—it’s static after startup and can’t be dynamically toggled or selectively bypassed for specific macros without workarounds. Based on Klipper’s documentation and community discussions, there are no built-in ways to change it at runtime or ignore it per-macro safely. However, here’s a viable strategy that balances your goals, with options from safe (recommended) to more advanced/risky.Recommended Approach: Use Soft Limits with Full Hardware RangeRevert position_min to 0 in both toggle files (config_no_dock.cfg and config_wt_dock.cfg) to allow full Y-axis access at the hardware level—this ensures docking macros can always succeed without errors, even mid-print. Then, enforce an “effective” minimum Y (your occlusion value, e.g., 130) as a soft limit via macro variables and conditionals. This propagates dynamically without magic numbers by referencing a single variable, while still using the printer object’s actual axis_minimum.y (now always 0) as a base.
Step 1: Update Toggle Files for Full Range
In both config_no_dock.cfg and config_wt_dock.cfg, set [stepper_y] position_min: 0 (keep other values like position_max: 310).
This makes printer.toolhead.axis_minimum.y always 0 after restart, avoiding any hard bounds errors during docking.
Step 2: Define an Effective Min Y Variable. Add or update a global variable in your toggle files (e.g., in [gcode_macro _static_variable] or a new [gcode_macro _limits]). Use variable_dock to set an “effective_min_y”:
[gcode_macro _limits]
variable_effective_min_y: {% if printer["gcode_macro _home"].dock %} 130.0 {% else %} printer.toolhead.axis_minimum.y {% endif %} # Your occlusion value; use 110 if that's exact
gcode: # Empty, just for variables
Place this in the swappable section of both toggle files (after the marker).
Now, printer[“_limits”].effective_min_y acts as your dynamic, dock-aware min Y—it’s 130 when docked (soft-enforcing occlusion for print ops) and 0 otherwise.
After toggling/restarting, this propagates to all macros without hardcoding 130 everywhere.
Step 3: Update Macros to Use the Effective MinReplace direct references to printer.toolhead.axis_minimum.y or hard Y values with {printer[“_limits”].effective_min_y} (or add offsets as needed). Examples:
Prime Line (e.g., in PRINT_START or a prime macro):
{% set prime_y = printer["_limits"].effective_min_y + 5 %} # Safe offset from effective min
G1 X{printer.toolhead.axis_maximum.x / 2} Y{prime_y} F7800 # Prime at front if no dock, or just above occlusion if docked
Parking (e.g., in PAUSE or M600):G1 X{printer.toolhead.axis_maximum.x / 2} Y{printer["_limits"].effective_min_y + 5} F6000 # Parks safely above effective min
Other Macros: For any that position near front (e.g., nozzle clean, unload), use similar: Y{printer[“_limits”].effective_min_y + offset}.
Keep [bed_mesh] and [quad_gantry_level] toggling as-is—they already use high min Y (e.g., 110) when docked to avoid probing occluded areas.
Benefits and Trade-offs
Keeps Using Printer Object: You still reference printer.toolhead.axis_minimum.y as a base (it’s now reliably 0), but layer the effective value on top for dock awareness—no magic numbers scattered around.
Docking Succeeds: Full range allows low-Y moves without errors.
Safety: Soft limits prevent accidental print/probe in occluded areas via macros, but won’t block invalid custom G-code (e.g., manual G0 Y10 would still work—add warnings if needed).
No Restart Mid-Print: Toggle only when switching dock install state (e.g., hardware changes).
Update your slicer profiles to match effective build volume (Y min 130 when docked) to avoid sliced violations.
This is the safest, most maintainable way without risking hardware.
It occurred to me that rather than modifying the dock & no-dock toggle files per Step1, I could simply move the [stepper_y] & [tmc5160 stepper_y] sections back above the variable marker where they started and remove the sections from both the dock & no-dock config files. I checked this with Grok, and he was OK with that. So, Step 1 – complete.
Step2: Grok said the [gcode_macro _limits] section could go anywhere ABOVE the toggle marker, so I put it JUST above the marker, with a comment. I also changed ‘130’ to ‘120’ as I know it works.
Other macros that needed to be modified:
Prime line code in [gcode_macro PRINT_START]
Front position in [gcode_macro FRONT]: I discovered that past-Frank had shifted this macro into the ‘Session Variables’ section (presumably so the y-value could be changed to reflect the ‘dock/no-dock’ configurations), but the corresponding macros in ‘config_wt_dock’ and ‘config_no_dock’ weren’t any different than the original version – oops!. Moved the CHOME & FRONT macros back to their original positions above the ‘Session Variables’ marker and changed the y value in ‘FRONT’ from {% set y_center = printer.toolhead.axis_maximum.y|float / 2.0 %} to {% set y_front = printer[“_limits”].effective_min_y + 5 %} and ‘G1 X{x_center} Y{y_center} F7800’ to ‘G1 X{x_center} Y{y_front} F7800’. Also added a statement to send the FRONT x,y pos to the display.
Removed the CHOME & FRONT macros from ‘dock’ & ‘no-dock config files (and moved the ‘#include'[include 251003_T0-Nitehawk-Revo-LDO.cfg]’ to the top)
Now I will just have to play with the printer for a while and see what happens. Fortunately by this time I have a pretty good understanding of how all the bells and whistles work. As one more safety measure, I have included printer.cfg, config_wt_dock, and config_no_dock here:
config_wt_dock.cfg:
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
#;< # Section Variable marker
[gcode_macro _limits]
variable_effective_min_y:120.0#this value gets changed to '0.0' in 'config_no_dock'
gcode:# Empty, just for variables
#10/17/25 gfp: PRINT_END moved here to see if I can modify end Z position for only the 'with_dock' configuration
[gcode_macro PRINT_END]
# Use PRINT_END for the slicer ending script - please customise for your slicer of choice
Back to testing after yesterday’s marathon sessions with Grok. Started a test print with the dock in place (“variable_dock: True” in printer.cfg). Hopefully the prime line will get laid down just in front of the dock exclusion area. Hurrah – it worked! The prime line was laid down at Y=125, as expected, and the print itself turned out as well as any in the past.
28 October 2025 Update:
I spent the entire day yesterday trying to get the ‘LOAD_FILAMENT’ macro to work with toolhead T1, with no success. Every time I tried the macro, it would heat the extruder to 230C and then hang forever ‘waiting for temperature’, even though I could plainly see that extruder 1 was at 230C. For a long time I thought maybe this was happening because I hadn’t yet calibrated the PID for the extruder heater, so the temp was varying too much. After I finally figured out how (with a lot of help from Grok) to calibrate the PID, I *still* couldn’t get LOAD_FILAMENT to work. Eventually, with more help from Grok, I figured out the problem. It turns out that the LOAD_FILAMENT macro definition in printer.cfg only works for a single extruder setup, and it was blocking the execution of the multiple-extruder version of LOAD_FILAMENT in the ‘macro_general.cfg’ file that came with the MissChanger installation – oops!
Once I figured this out, I deleted the LOAD_FILAMENT (and UNLOAD_FILAMENT) versions in printer.cfg, and voila! everything started working again!
So now I understand why the example printer.cfg that came with the MissChanger installation is so simple compared to the one I had developed over time since I first got my Voron 2.4 going; it’s because many of the standard operations in my printer.cfg assume a single extruder (named ‘extruder’). This still works OK with my dual extruder setup when extruder T0 is active, as it is also named ‘extruder’. However, when T1 is active, its extruder is named ‘extruder1’, which isn’t recognized at all by the single-extruder macro definitions. The corresponding macros in ‘macro_general.cfg’ have been generalized to deal with any number of extruders, named ‘extruder’ (for T0) or ‘extruder[x]’ (where ‘x’ ranges from 0-N).
After finally figuring this out, I went back through all the macros in printer.cfg to see what other single-toolhead limited code has to be removed to avoid overriding multiple-toolhead versions in macro_general.cfg, but didn’t find any more. Apparently, the LOAD/UNLOAD_FILAMENT were the only ones.
OK, now I’m trying to figure out why my SB LEDs aren’t working, and my assumption is it has something to do with the T0/T1 issue above. I instrumented 251002_T1_stealthburner_leds.cfg to display on the status screen in the ‘Dashboard’ display, and from that I can tell that when I run the ‘status_ready’ (and others), that the correct macro is called (in this case, the one in 251002_T1_stealthburner_leds.cfg). However, nothing happens on the toolhead itself.
251029 Update:
Well, I am truly down the rabbit-hole without a crumb trail on this one. After a full day of working on the issue with Grok, I’m more lost now than when I started. Grok seems completely convinced that the issue is a hardware problem caused by trying to run the nominally 5V neopixel string from the nominally 3.3V RP2040 MCU on the NH PCB. Grok’s cure was to install a WS2812B 3.3-5V Shifter Board. I’m more than a little suspicious of this result, as I remember having the LED strings on both toolheads working not long ago. Here’s a short movie of a dock/undock cycle from 24 October and the neopixel string was working just fine then.
So, my plan is to revert my Klipper config setup to that previous one and see if the LEDs work again. If so, then it obviously is a software, not hardware, problem.
Looking through my saved config files, I found this complete set of config files:
I replaced the contents of printer.cfg with the contents of 251025_wt_dock_printer.cfg (this file uses the 251003_T0-Nitehawk-Revo-LDO.cfg tool-specific config file, which is already available on the printer), and voila! the LEDs on the T0 (gray) toolhead are back, and all the ‘STATUS_xxx’ macros work as well! So, my old “Hardware never fails – it is ALWAYS a software issue” mantra wins again.
When I told this to Grok, he(it?) congratulated me on eliminating hardware as the problem, and then decided the problem was caused when I moved the T0/T1 include files to the top of printer.cfg. This caused the T0/T1 code to be inserted in the wrong place, thereby causing the LED problems I experienced. Grok then guided me through fixing the issues with the dual toolhead config files.
30 October 2025 Update:
Well, Grok and I have been back and forth a lot over the last few days, and I’m sure by now Grok (he/she/it?) is getting frustrated with the inability of this old human to ‘grok’ (and yes, I *am* old enough to have read “Stranger in a Strange Land” when it first came out) the finer points of Klipper configuration file construction.
The basic problem is that whenever I try to implement a dual-toolhead configuration, the stealthburner LEDs are disabled. Grok has explained this is due to ‘included’ files overwriting needed LED control information (like pin numbers?), and has provided several ideas for fixing the problem via different config file edits. Unfortunately my Klipper ignorance is showing (and Grok has some real blind spots as well), so I haven’t been able to get anything to work.
While researching why this LED thing is giving me so much trouble, I came to realize that the real problem is that I started with the single-toolhead config file developed over time since I first got the Voron going, and part of that was the use of the ‘stealthburner_leds.cfg’ add-on that contains all the cool ‘STATUSxxx’ macros. However, vin’s MISSChanger project starts with just a very plain-vanilla printer.cfg file without any of the bells/whistles and adds the multiple-toolhead stuff from there. This was a situation ripe for problems and boy did I have them. I’m going to give Grok’s ideas a good try, but if I can’t make them work, I’m going to go back to Vin’s basic printer.cfg and build things up from there.
31 October 2025:
Last night I was able to verify that ‘klippy.log’ contains the entire in-lined configuration, after all includes and before parsing at the top of each entry. This should go a long way toward effective troubleshooting of the ‘No LEDs’ problem. However, when I started the process of implementing the Grok-suggested changes, I discovered that my printer will no longer connect to nhk0 or nhk1. I get the dreaded “mcu ‘nhk1’: Unable to connect” and/or “mcu ‘nhk0’: Unable to connect” error(s). Since I haven’t made any hardware changes this is kinda hard to believe, and at least so far I’m convinced this is yet another software-induced issue.
So, I plan to go back to my 251025 configuration (251025_wt_dock_printer.cfg & 251003_T0-Nitehawk-Revo-LDO.cfg). Yep! this configuration setup worked perfectly – no errors, and the restart was very rapid (maybe 2-5 seconds).
I’m going to start a new post – “MissChanger Klipper Configuration – Part 2” – as this one is getting a bit cumbersome.
24 July 2026 Update:
I started a project to replace my current under-riser desk tray with something a bit nicer. I originally thought I could just order a nicer one on Amazon, but quickly realized that wasn’t going to happen. So, I decided to try 3D printing something like the following screenshot:
I immediately ran into a ‘big’ (pun intended) problem, as the above design won’t fit onto the ‘with_dock MISSCHANGER configuration, as shown below:
but it will fit in the ‘without_dock’ configuration, if I could just remember how to change from the ‘with_dock’ to ‘without_dock’ configurations. Fortunately for me, ‘past-Frank’ left me a bread-crumb trail in the form of this post. Now all I have to do is follow it – we’ll see!
25 July 2026 Update:
Well, as usual, things weren’t quite as easy as I’d hoped, but with a lot of help from Grok I “gotter done”. In the process, we discovered some problems left over from my last run at this
The configuration switching magic swaps a defined section of printer.cfg with the contents of ‘config_no_dock.cfg’ or ‘config_wt_dock.cfg’ depending on whether the value of ‘variable_dock:’ is ‘true’ (dock present) or ‘false’ (no dock). The section to be swapped out is defined by the symbols ‘#;<‘ (start) and ‘#;>’ (stop). As it turns out the ‘stop’ symbol (‘#;>’) wasn’t present, so instead of swapping just the relevant section, the relevant section plus the rest of the file to EOF was swapped. This miraculously didn’t cause any noticeable problem because ‘the rest of the file’ was just the SAVE_CONFIG section which identical for both ‘dock’ and ‘non-dock’ configurations.
Well, the above turned out to be ‘not quite how it actually works’. In actuality, the switching magic deletes everything between the ‘start’ and ‘end’ marks and then appends (rather than inserts) the entire contents of the opposite configuration *_dock.cfg file at the bottom of ‘printer.cfg’. Not only that, but the new appended section doesn’t include the terminating ‘#;>’ marker because it was never written to the *_dock.cfg file by SAVE_CONFIG_MODE. The result of all this is a gradual (or not-so-gradual) corruption of printer.cfg that will eventually cause the printer firmware to refuse to boot, with error messages that don’t make any sense, and with printer.cfg contents that don’t make sense anymore. I was only able to avoid complete disaster because I had the foresight (read ‘pure luck’) to save a clean printer.cfg copy before starting the process of changing from the ‘dock’ to ‘no_dock’ configuration – whew!
At this point I have a clean ‘no_dock’ printer.cfg and a corresponding clean ‘config_no_dock.cfg’ file. I downloaded both to “C:\Users\Frank\Documents\3D Projects\Voron Printer\MissChanger\Config Files” so when I screw things up in the future I can recover. Here are the related files.
‘no_dock’ printer.cfg (note: Urvanov Syntax Highlighter doesn’t work for very long files).