From 42531a79bf92b35d3cfe5489a1d24ae6c9ac432c Mon Sep 17 00:00:00 2001 From: Bobbe Date: Sun, 3 May 2026 15:57:01 +0200 Subject: [PATCH 1/3] klipper-flash: support can bus flashing via katapult --- nixos/modules/services/misc/klipper.nix | 28 ++++++++++-- pkgs/servers/klipper/default.nix | 4 +- pkgs/servers/klipper/klipper-firmware.nix | 8 +++- pkgs/servers/klipper/klipper-flash.nix | 52 ++++++++++++++--------- pkgs/top-level/all-packages.nix | 2 +- 5 files changed, 66 insertions(+), 28 deletions(-) diff --git a/nixos/modules/services/misc/klipper.nix b/nixos/modules/services/misc/klipper.nix index 78bd208ca1b6..6c51e0b96915 100644 --- a/nixos/modules/services/misc/klipper.nix +++ b/nixos/modules/services/misc/klipper.nix @@ -33,11 +33,27 @@ let ''; serial = lib.mkOption { type = lib.types.nullOr lib.types.path; - description = "Path to serial port this printer is connected to. Derived from `service.klipper.settings` by default."; + description = "Path to serial port this mcu is connected to. Derived from `service.klipper.settings` by default."; defaultText = lib.literalExpression "config.services.klipper.settings..serial"; default = if lib.hasAttrByPath [ "${mcu}" "serial" ] cfg.settings then cfg.settings."${mcu}".serial else null; }; + canbus_uuid = lib.mkOption { + type = lib.types.nullOr lib.types.str; + description = "CAN bus uuid of this mcu. Derived from `service.klipper.settings` by default."; + defaultText = lib.literalExpression "config.services.klipper.settings..canbus_uuid"; + default = + if lib.hasAttrByPath [ "${mcu}" "canbus_uuid" ] cfg.settings then + cfg.settings."${mcu}".canbus_uuid + else + null; + }; + canbusNetwork = lib.mkOption { + type = lib.types.nullOr lib.types.str; + description = "CAN bus network this mcu is connected to. Defaults to can0 if canbus_uuid is set."; + defaultText = lib.literalExpression ''if canbus_uuid != null then "can0" else null''; + default = if subcfg.canbus_uuid != null then "can0" else null; + }; configFile = lib.mkOption { type = lib.types.path; description = "Path to firmware config which is generated using `klipper-genconf`"; @@ -71,6 +87,8 @@ let klipper-firmware = subcfg.package; mcu = lib.strings.sanitizeDerivationName mcu; flashDevice = subcfg.serial; + canbusDevice = subcfg.canbus_uuid; + canbusNetwork = subcfg.canbusNetwork; firmwareConfig = subcfg.configFile; } else @@ -217,12 +235,15 @@ in } ] ++ lib.mapAttrsToList (mcu: firmware: { - assertion = firmware.enableKlipperFlash -> firmware.serial != null; + assertion = + firmware.enableKlipperFlash -> (firmware.serial != null || firmware.canbus_uuid != null); message = '' - Unable to determine the serial connection for services.klipper.firmwares."${mcu}". Please set one of the following: + Unable to determine the serial or canbus connection for services.klipper.firmwares."${mcu}". Please set one of the following: - services.klipper.firmwares."${mcu}".serial + - services.klipper.firmwares."${mcu}".canbus_uuid - services.klipper.settings."${mcu}".serial + - services.klipper.settings."${mcu}".canbus_uuid ''; }) cfg.firmwares; @@ -301,7 +322,6 @@ in environment.systemPackages = let - default = a: b: if a != null then a else b; genconf = pkgs.klipper-genconf.override { klipper = cfg.package; }; diff --git a/pkgs/servers/klipper/default.nix b/pkgs/servers/klipper/default.nix index 559eb44f74f3..7705d40a9e75 100644 --- a/pkgs/servers/klipper/default.nix +++ b/pkgs/servers/klipper/default.nix @@ -109,8 +109,10 @@ stdenv.mkDerivation rec { # under `klipper_path` cp -r $src/docs $out/lib/docs cp -r $src/config $out/lib/config - cp -r $src/scripts $out/lib/scripts cp -r $src/klippy $out/lib/klippy + mkdir -p $out/lib/scripts + cp -r $src/scripts/* $out/lib/scripts + cp $src/lib/katapult/flashtool.py $out/lib/scripts/flash_can.py # Add version information. For the normal procedure see https://www.klipper3d.org/Packaging.html#versioning # This is done like this because scripts/make_version.py is not available when sourceRoot is set to "${src.name}/klippy" diff --git a/pkgs/servers/klipper/klipper-firmware.nix b/pkgs/servers/klipper/klipper-firmware.nix index 90b29f33d987..16c673aca132 100644 --- a/pkgs/servers/klipper/klipper-firmware.nix +++ b/pkgs/servers/klipper/klipper-firmware.nix @@ -95,7 +95,11 @@ stdenv.mkDerivation { passthru = { makeFlasher = - { flashDevice }: + { + flashDevice ? null, + canbusNetwork ? null, + canbusDevice ? null, + }: klipper-flash.override { klipper-firmware = klipper-firmware.override args; inherit @@ -103,6 +107,8 @@ stdenv.mkDerivation { firmwareConfig mcu flashDevice + canbusNetwork + canbusDevice ; }; }; diff --git a/pkgs/servers/klipper/klipper-flash.nix b/pkgs/servers/klipper/klipper-flash.nix index 78ba90218953..7aa8d0c39b02 100644 --- a/pkgs/servers/klipper/klipper-flash.nix +++ b/pkgs/servers/klipper/klipper-flash.nix @@ -7,7 +7,9 @@ dfu-util, stm32flash, mcu ? "mcu", - flashDevice ? "/dev/null", + flashDevice ? null, + canbusNetwork ? null, + canbusDevice ? null, firmwareConfig ? ./simulator.cfg, }: let @@ -21,33 +23,41 @@ let matchPlatform = getConfigField "CONFIG_BOARD_DIRECTORY"; matchBoard = getConfigField "CONFIG_MCU"; in +assert lib.assertMsg ( + (flashDevice != null) != (canbusNetwork != null && canbusDevice != null) + && ((canbusNetwork != null) == (canbusDevice != null)) +) "Either set flashDevice or both canbusNetwork and canbusDevice"; writeShellApplication { name = "klipper-flash-${mcu}"; runtimeInputs = [ ] - ++ lib.optionals (matchPlatform == "avr") [ avrdude ] - ++ lib.optionals (matchPlatform == "stm32") [ - stm32flash - dfu-util - ] - ++ lib.optionals (matchPlatform == "lpc176x") [ dfu-util ] - # bossac, hid-flash and RP2040 flash binaries are built by klipper-firmware - ; + ++ lib.optionals (flashDevice != null) ( + lib.optionals (matchPlatform == "avr") [ avrdude ] + ++ lib.optionals (matchPlatform == "stm32") [ + stm32flash + dfu-util + ] + ++ lib.optionals (matchPlatform == "lpc176x") [ dfu-util ] + # bossac, hid-flash and RP2040 flash binaries are built by klipper-firmware + ); text = # generic USB script for most things with serial and bootloader (see MCU_TYPES in scripts/flash_usb.py) - if matchBoard != null && matchPlatform != null then - '' - pushd ${klipper-firmware} - ${klipper}/lib/scripts/flash_usb.py -t ${matchBoard} -d ${flashDevice} ${klipper-firmware}/klipper.bin "$@" - popd - '' + if flashDevice != null then + if matchBoard != null && matchPlatform != null then + '' + ${klipper}/lib/scripts/flash_usb.py -t ${matchBoard} -d ${flashDevice} ${klipper-firmware}/klipper.bin "$@" + '' + else + '' + cat < Date: Tue, 5 May 2026 16:25:54 +0200 Subject: [PATCH 2/3] klipper-flash: support flashing avr mcus via avrdude --- pkgs/servers/klipper/klipper-firmware.nix | 1 + pkgs/servers/klipper/klipper-flash.nix | 26 ++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/pkgs/servers/klipper/klipper-firmware.nix b/pkgs/servers/klipper/klipper-firmware.nix index 16c673aca132..6ab6beac9ac3 100644 --- a/pkgs/servers/klipper/klipper-firmware.nix +++ b/pkgs/servers/klipper/klipper-firmware.nix @@ -72,6 +72,7 @@ stdenv.mkDerivation { cp ./.config $out/config cp out/klipper.bin $out/ || true cp out/klipper.elf $out/ || true + cp out/klipper.elf.hex $out/ || true cp out/klipper.uf2 $out/ || true mkdir -p $out/lib/ diff --git a/pkgs/servers/klipper/klipper-flash.nix b/pkgs/servers/klipper/klipper-flash.nix index 7aa8d0c39b02..14379c4b3156 100644 --- a/pkgs/servers/klipper/klipper-flash.nix +++ b/pkgs/servers/klipper/klipper-flash.nix @@ -22,6 +22,26 @@ let if matches != null then head matches else null; matchPlatform = getConfigField "CONFIG_BOARD_DIRECTORY"; matchBoard = getConfigField "CONFIG_MCU"; + matchAvrdudeProtocol = getConfigField "CONFIG_AVRDUDE_PROTOCOL"; + flashUsbSupportedBoards = [ + "sam3" + "sam4" + "same70" + "samd" + "same5" + "lpc176" + "stm32f103" + "stm32f4" + "stm32f042" + "stm32f070" + "stm32f072" + "stm32g0b1" + "stm32f7" + "stm32h7" + "stm32l4" + "stm32g4" + "rp2" + ]; in assert lib.assertMsg ( (flashDevice != null) != (canbusNetwork != null && canbusDevice != null) @@ -43,10 +63,14 @@ writeShellApplication { text = # generic USB script for most things with serial and bootloader (see MCU_TYPES in scripts/flash_usb.py) if flashDevice != null then - if matchBoard != null && matchPlatform != null then + if (builtins.elem matchBoard flashUsbSupportedBoards) && matchPlatform != null then '' ${klipper}/lib/scripts/flash_usb.py -t ${matchBoard} -d ${flashDevice} ${klipper-firmware}/klipper.bin "$@" '' + else if matchPlatform == "avr" && matchAvrdudeProtocol != null && matchBoard != null then + '' + avrdude -p${matchBoard} -c${matchAvrdudeProtocol} -P"${flashDevice}" -D -U"flash:w:${klipper-firmware}/klipper.elf.hex:i" + '' else '' cat < Date: Fri, 29 May 2026 17:06:53 +0200 Subject: [PATCH 3/3] klipper: add klipper-canbus-query wrapper binary --- pkgs/servers/klipper/default.nix | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkgs/servers/klipper/default.nix b/pkgs/servers/klipper/default.nix index 7705d40a9e75..6a71af6de33a 100644 --- a/pkgs/servers/klipper/default.nix +++ b/pkgs/servers/klipper/default.nix @@ -90,6 +90,7 @@ stdenv.mkDerivation rec { p: with p; [ numpy matplotlib + python-can ] )).interpreter; @@ -127,6 +128,11 @@ stdenv.mkDerivation rec { --subst-var-by "script" "calibrate_shaper.py" chmod 755 "$out/bin/klipper-calibrate-shaper" + substitute "$pythonScriptWrapper" "$out/bin/klipper-canbus-query" \ + --subst-var "out" \ + --subst-var-by "script" "canbus_query.py" + chmod 755 "$out/bin/klipper-canbus-query" + runHook postInstall '';