Skip to content

Latest commit

 

History

History
344 lines (247 loc) · 10.8 KB

File metadata and controls

344 lines (247 loc) · 10.8 KB

Anleitung

Ausführliche Fassung. Für den Schnelleinstieg reicht die README.


1. Wie Klipper baut

Klippers Makefile beginnt so:

OUT=out/
export KCONFIG_CONFIG := $(CURDIR)/.config

Beide Zuweisungen lassen sich auf der Kommandozeile überschreiben — in Make gewinnt ein Kommandozeilen-Argument gegen eine Zuweisung im Makefile. Also:

cd ~/klipper
make menuconfig KCONFIG_CONFIG=~/klipper_boards/octopus.config \
                OUT=~/klipper_builds/octopus/
make            KCONFIG_CONFIG=~/klipper_boards/octopus.config \
                OUT=~/klipper_builds/octopus/ -j4

Mehr steckt im Kern nicht dahinter. Das Script nimmt dir nur das Tippen ab und kümmert sich um die Details, über die man sonst stolpert.

OUT muss auf / enden. Make hängt den Dateinamen direkt an, aus OUT=out-octopus wird sonst out-octopusklipper.elf.

Ein zweiter wichtiger Baustein ist olddefconfig:

make olddefconfig KCONFIG_CONFIG=~/klipper_boards/octopus.config

Das übernimmt neue Kconfig-Optionen aus einem Klipper-Update mit ihren Standardwerten in deine gespeicherte Config — ohne interaktive Rückfrage. Genau das macht den unbeaufsichtigten Update-Lauf möglich.

Warum außerhalb von ~/klipper?

Weil git pull sonst irgendwann meckert und weil make distclean alles mitnimmt. Configs und Builds außerhalb des Repos bleiben davon unberührt.

Nach der Umstellung darf die alte Datei weg:

cd ~/klipper && make distclean    # entfernt .config und out/

2. Einrichtung

git clone https://github.com/<dein-user>/klipper-multiboard.git ~/klipper-multiboard
chmod +x ~/klipper-multiboard/klipper-build.sh

Es ist sinnvoll, die Configs dort abzulegen, wo dein restliches Drucker-Setup liegt — dann werden sie von deinem bestehenden Backup miterfasst:

mkdir -p ~/printer_data/klipper_boards
echo 'export CONFIG_DIR=~/printer_data/klipper_boards' >> ~/.bashrc

Alternativ pro Aufruf: CONFIG_DIR=~/printer_data/klipper_boards ./klipper-build.sh …


3. Boards anlegen

Zuerst schauen, was überhaupt am Host hängt:

./klipper-build.sh usb
==> Angeschlossene USB-MCUs (/dev/serial/by-id):
    /dev/serial/by-id/usb-Klipper_stm32f446xx_45002C001050535556323420-if00
    /dev/serial/by-id/usb-Klipper_rp2040_323232323619413F-if00
    /dev/serial/by-id/usb-Klipper_rp2040_3232323236598300-if00

Dann pro Board-Typ eine Config anlegen:

./klipper-build.sh new octopus

Das öffnet menuconfig. Nach dem Speichern liegt die Auswahl in $CONFIG_DIR/octopus.config und wird nie wieder überschrieben.

Zuletzt die Geräte eintragen — eine Zeile pro physischem Board, Kommentare mit # sind erlaubt:

cat > ~/printer_data/klipper_boards/octopus.devices <<'EOF'
/dev/serial/by-id/usb-Klipper_stm32f446xx_45002C001050535556323420-if00
EOF

Vorlagen liegen unter examples/.

Immer by-id, nie /dev/ttyACM0. Die Nummerierung der ttyACM-Geräte hängt an der Einschaltreihenfolge. Der by-id-Pfad enthält die Chip-ID und ist dauerhaft stabil — dieselbe ID, die auch in deiner printer.cfg steht.


4. Der Update-Lauf

cd ~/klipper && git pull
cd ~/klipper-multiboard
./klipper-build.sh flash all

Was dabei pro Board passiert:

  1. olddefconfig — neue Kconfig-Optionen übernehmen
  2. Build nach $BUILD_DIR/<board>/
  3. Klipper-Dienst anhalten (sonst hält klippy die seriellen Ports offen)
  4. Für jedes Gerät: Bootloader anfordern, warten, flashen
  5. Klipper-Dienst starten

Der Dienst wird über ein trap auch dann wieder gestartet, wenn ein Board scheitert oder du mit Strg-C abbrichst. Fehlgeschlagene Boards brechen den Lauf nicht ab, sondern werden am Ende gesammelt gemeldet und setzen den Exit-Code.

Vorher trocken üben:

DRY_RUN=1 ./klipper-build.sh flash all

Zeigt jeden Schritt inklusive der konkreten flashtool.py-Aufrufe, fasst aber kein Board an.


5. Mehrere baugleiche Boards

Ein „Board" ist in diesem Script ein Board-Typ, kein physisches Gerät. Sechs identische Toolhead-Boards brauchen genau eine .config und genau eine Firmware — unterschieden werden sie erst beim Flashen und in der printer.cfg.

nhk36.config      <- einmal
nhk36.devices     <- sechs Zeilen

Das funktioniert, weil die USB-Seriennummer bei RP2040 und STM32 aus der eindeutigen Chip-ID stammt. Jedes Board hat damit dauerhaft seinen eigenen by-id-Pfad, obwohl alle dieselbe Firmware tragen.

Das Script nutzt genau diese Chip-ID, um beim Bootloader-Wechsel das richtige Board wiederzufinden:

/dev/serial/by-id/usb-Klipper_rp2040_3232323236598300-if00
                                     ^^^^^^^^^^^^^^^^ Chip-ID
/dev/serial/by-id/usb-katapult_rp2040_3232323236598300-if00

Ohne diesen Abgleich würde bei sechs gleichen Boards schlicht das erstbeste Katapult-Gerät erwischt.


6. Versionsstand im Blick behalten

./klipper-build.sh status
Klipper-Repo (Host): v0.13.0-743-gac2a7f8b

BOARD / CHIP-ID            VERSION                    STAND
nhk36                      v0.13.0-743-gac2a7f8b      (gebaut)
  323232323619413F         v0.13.0-743-gac2a7f8b      2026-08-24 09:15
  3232323236598300         v0.13.0-743-gac2a7f8b      2026-08-24 09:16
octopus                    v0.13.0-743-gac2a7f8b      (gebaut)
  45002C001050535556323420 v0.13.0-700-gaaaaaaa       2026-07-02 21:40

Nicht von diesem Script verwaltete USB-MCUs:
    usb-Klipper_stm32f042x6_1A2B3C4D5E6F-if00

Die Version kommt aus klipper.dict des jeweiligen Builds — genau der String, den der MCU später auch meldet. Nach jedem erfolgreichen Flash wird sie pro Chip-ID in $BUILD_DIR/<board>/.flashed festgehalten.

Der Block „nicht verwaltet" ist bewusst dabei. Dort tauchen MCUs auf, die eigene Firmware haben — ein Cartographer oder Beacon etwa — und die nicht über dieses Script laufen sollen. Sichtbar, aber außen vor.

Wie schlimm sind unterschiedliche Versionen?

Weniger schlimm, als oft behauptet. Klippers Versionsvergleich sitzt in klippy/extras/error_mcu.py und läuft erst, nachdem ein Protokollfehler aufgetreten ist — er erklärt den Fehler, er löst ihn nicht aus. Eine reine Versionsdifferenz ist unkritisch, solange sich das Message-Protokoll nicht geändert hat. Deshalb laufen Boards mit eigener Firmware problemlos mit.

Wenn ein Update aber das Protokoll anfasst, fällt jedes zurückgebliebene Board aus — und dann willst du wissen, welches. Dafür ist status da.


7. Moonraker update_manager

Damit Updates des Scripts über Mainsail/Fluidd kommen, in moonraker.conf:

[update_manager klipper-multiboard]
type: git_repo
channel: dev
path: ~/klipper-multiboard
origin: https://github.com/<dein-user>/klipper-multiboard.git
primary_branch: main
managed_services: klipper
is_system_service: False

Danach Moonraker neu starten:

sudo systemctl restart moonraker

channel: dev ist hier richtig, weil das Repo keine getaggten Releases verwendet. Wer mit Tags arbeitet, kann auf channel: stable wechseln.


8. Troubleshooting

No DFU capable USB device available — obwohl Katapult installiert ist

Das ist der Fehler, der zu diesem Script geführt hat. Typischer Log:

Entering bootloader on /dev/serial/by-id/usb-Klipper_stm32f446xx_...-if00
Device reconnect on /sys/devices/.../1-6.2/1-6.2:1.0
sudo dfu-util -p 1-6.2 -R -a 0 -s 0x8008000:leave -D .../klipper.bin
dfu-util: No DFU capable USB device available
Failed to flash: Error running dfu-util

Beim zweiten Anlauf klappt es dann oft. Was tatsächlich passiert, in Klippers scripts/flash_usb.py:

  1. Der 1200-Baud-Reset geht raus, das Board springt in den Bootloader.
  2. wait_path() wartet 100 ms und prüft, ob der sysfs-Pfad existiert. Das ist aber noch der Pfad des alten Klipper-Geräts. Hat sich das Board noch nicht abgemeldet, ist er noch da — und wait_path() meldet fälschlich „Device reconnect".
  3. detect_canboot() liest idVendor/idProduct aus genau diesem noch alten sysfs-Eintrag und liest 1d50:614e (Klipper) statt 1d50:6177 (Katapult). Ergebnis: „kein Katapult".
  4. Fallback auf dfu-util — das findet natürlich kein DFU-Gerät, weil das Board in Katapult sitzt und nicht in DFU.

Die Fehlermeldung zeigt also auf DFU, obwohl es eine Fehlerkennung ist. Das Zeitfenster von 100 ms reicht bei direkt angestecktem Board meist; über einen USB-Hub oder an einem gut belegten Host kippt es.

Was dieses Script dagegen tut: es umgeht flash_usb.py beim Flashen. Es sucht das Katapult-Gerät über die Chip-ID, fordert bei Bedarf den Bootloader per flashtool.py -r an, pollt aktiv bis zu BOOTLOADER_WAIT Sekunden und flasht dann direkt mit flashtool.py. Kein 100-ms-Fenster, kein DFU-Umweg.

Von Hand geht dasselbe so:

python3 ~/klipper/lib/katapult/flashtool.py \
  -d /dev/serial/by-id/usb-katapult_stm32f446xx_<CHIPID>-if00 \
  -f ~/klipper_builds/octopus/klipper.bin

Ein Board hängt im Bootloader fest

Kein Drama. Prüfen mit:

lsusb | grep -Ei "1d50|0483"
ls /dev/serial/by-id/

1d50:6177 heißt Katapult, 0483:df11 heißt STM32-ROM-DFU. In beiden Fällen erkennt das Script beim nächsten flash-Lauf, dass das Board schon im Bootloader steht, und bedient es direkt.

make flash scheitert mit belegtem Port

klippy hält die seriellen Ports offen. Das Script stoppt den Dienst selbst; läuft bei dir ein anderer Dienstname, setze KLIPPER_SERVICE:

KLIPPER_SERVICE=klipper-drucker2 ./klipper-build.sh flash all

Der Katapult-Weg soll gar nicht benutzt werden

FLASH_METHOD=make ./klipper-build.sh flash all

Dann läuft alles über make flash, mit Warteschleife und zweitem Anlauf über den Bootloader als Absicherung. Umgekehrt erzwingt FLASH_METHOD=katapult den direkten Weg ohne Fallback.

out-octopusklipper.elf und ähnliche Kuriositäten

OUT ohne Schrägstrich am Ende. Make hängt den Dateinamen direkt an.

Ein Board taucht nach dem Flashen nicht wieder auf

Katapult springt nach dem Schreiben in die Anwendung; die Neu-Enumeration dauert einen Moment. Das Script wartet zwischen zwei Boards drei Sekunden. Bei langsamen Hubs hilft eine höhere BOOTLOADER_WAIT:

BOOTLOADER_WAIT=40 ./klipper-build.sh flash all

Bleibt es dabei, ist meist die USB-Verkabelung dran — Hub-Kette, Kabellänge, Stromversorgung. Ein Board testweise direkt an den Host stecken.