LeoVoron2.4 config (history squashed 2026-10-11)

This commit is contained in:
2026-10-11 22:03:03 +03:00
commit d24a996275
67 changed files with 8815 additions and 0 deletions
+320
View File
@@ -0,0 +1,320 @@
#!/usr/bin/env bash
# Сборка и прошивка Klipper на всех MCU принтера leo-voron:
# host — linux-процесс klipper_mcu на CB2 (/usr/local/bin/klipper_mcu, сервис klipper-mcu)
# mcu — Octopus Pro 1.1 (stm32h723, USB-CAN bridge; прошивается по USB через katapult)
# toolhead — EBB SB2209 (rp2040; прошивается по CAN через katapult)
#
# Использование:
# update_fw.sh [-h] [--build-only|--no-build] [--force] [host|mcu|toolhead ...]
# без целей — все три
# --build-only — только собрать прошивки в ~/firmware, ничего не прошивать
# --no-build — не собирать, прошить уже собранные ~/firmware/<цель>_klipper.{bin,elf}
# --force — не останавливаться, если Moonraker недоступен для проверки печати
#
# Исходники Klipper НЕ обновляются (это делает Moonraker Update Manager).
# Скрипт интерактивный: пароль sudo спрашивается один раз в начале.
#
# Полезные команды:
# make menuconfig KCONFIG_CONFIG=/home/biqu/printer_data/config/firmware/octopus-pro.config
# make flash FLASH_DEVICE=0483:df11 # stm32 в DFU
# python3 ~/katapult/scripts/flashtool.py -i can0 -q # список узлов на CAN
# python3 ~/katapult/scripts/flashtool.py -i can0 -u <uuid> -r # запрос бутлоадера
# make flash FLASH_DEVICE=/dev/serial/by-id/usb-Klipper_rp2040_45474E621A87A43A-if00 KCONFIG_CONFIG=.../hbb.cfg
#
# Старые тулхеды (UUID):
# rp_sb2209 = 350f10596216
# sht36_v2 = 8ce295109ef0
set -Eeuo pipefail
# ---------- Константы ----------
MOONRAKER="http://127.0.0.1:7125"
KLIPPER="$HOME/klipper"
KATAPULT="$HOME/katapult/scripts/flashtool.py"
CONFIG_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUT_DIR="$HOME/firmware"
KLIPPY_LOG="$HOME/printer_data/logs/klippy.log"
CAN_IF="can0"
JOBS=4
MCU_UUID="afcf80281237"
MCU_CFG="octopus-pro.config"
KATAPULT_SERIAL_GLOB='/dev/serial/by-id/usb-katapult_stm32h723xx_*'
TOOLHEAD_UUID="d7e70bd38059"
TOOLHEAD_CFG="ebb_rp2240.CAN"
HOST_CFG="host_linux.config"
HOST_BIN="/usr/local/bin/klipper_mcu"
ALL_TARGETS=(host mcu toolhead)
# ---------- Разбор аргументов ----------
BUILD_ONLY=0
NO_BUILD=0
FORCE=0
TARGETS=()
for arg in "$@"; do
case "$arg" in
-h|--help) sed -n '2,16p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
--build-only) BUILD_ONLY=1 ;;
--no-build) NO_BUILD=1 ;;
--force) FORCE=1 ;;
host|mcu|toolhead) TARGETS+=("$arg") ;;
*) echo "Неизвестный аргумент: $arg (см. -h)" >&2; exit 2 ;;
esac
done
[ ${#TARGETS[@]} -eq 0 ] && TARGETS=("${ALL_TARGETS[@]}")
[ "$BUILD_ONLY" = 1 ] && [ "$NO_BUILD" = 1 ] && { echo "--build-only и --no-build несовместимы" >&2; exit 2; }
want() { local t; for t in "${TARGETS[@]}"; do [ "$t" = "$1" ] && return 0; done; return 1; }
# ---------- Лог и трапы ----------
mkdir -p "$OUT_DIR"
LOG="$OUT_DIR/update_fw-$(date +%Y%m%d-%H%M).log"
exec > >(trap '' INT; exec tee -a "$LOG") 2>&1
START_TS=$(date +%s)
STAGE="инициализация"
KLIPPER_STOPPED=0
on_err() {
echo
echo "!!! ОШИБКА на этапе: $STAGE (лог: $LOG)" >&2
}
on_exit() {
local rc=$?
rm -rf "${BUILD_TMP:-}"
if [ "$KLIPPER_STOPPED" = 1 ]; then
echo; echo "=== Запуск klipper ==="
sudo systemctl start klipper || echo "!!! Не удалось запустить klipper — запустите вручную: sudo systemctl start klipper" >&2
fi
[ $rc -ne 0 ] && echo "Завершено с ошибкой (код $rc)."
exit $rc
}
trap on_err ERR
trap on_exit EXIT
trap 'exit 130' INT
section() { echo; echo "=== $* ==="; }
die() { echo "!!! [$STAGE] $*" >&2; exit 1; }
# Ждать, пока команда-условие не вернёт 0 (таймаут в секундах)
wait_for() {
local timeout=$1 msg=$2; shift 2
local deadline=$((SECONDS + timeout))
while [ "$SECONDS" -lt "$deadline" ]; do
if "$@" >/dev/null 2>&1; then return 0; fi
sleep 1
done
echo "!!! Таймаут ($timeout с): $msg" >&2
return 1
}
# Klipper отвечает на запрос UUID только пока ему не назначен node id (canserial.c),
# поэтому -q годится лишь для узлов, только что перезагруженных после прошивки.
can_has_uuid() { local out; out=$(python3 "$KATAPULT" -i "$CAN_IF" -q 2>/dev/null) || return 1; grep -q "$1" <<< "$out"; }
katapult_serial() { local d; for d in $KATAPULT_SERIAL_GLOB; do [ -e "$d" ] && { echo "$d"; return 0; }; done; return 1; }
klippy_state() { curl -s --max-time 5 "$MOONRAKER/printer/info" | jq -r '.result.state // empty' 2>/dev/null; }
klippy_ready() { [ "$(klippy_state)" = "ready" ]; }
mcu_versions() {
curl -s --max-time 5 "$MOONRAKER/printer/objects/query?mcu&mcu%20EBBCan&mcu%20host" \
| jq -r '.result.status | to_entries[] | "\(.key)\t\(.value.mcu_version // "?")\t\(.value.mcu_constants.MCU // "?")"' 2>/dev/null \
|| echo "недоступно"
}
# ---------- Preflight ----------
STAGE="предварительные проверки"
section "Preflight"
echo "Цели: ${TARGETS[*]} build-only=$BUILD_ONLY no-build=$NO_BUILD лог: $LOG"
[ -d "$KLIPPER" ] || die "Нет исходников Klipper: $KLIPPER"
[ -f "$KATAPULT" ] || die "Нет katapult flashtool: $KATAPULT"
want host && { [ -f "$CONFIG_DIR/$HOST_CFG" ] || die "Нет конфига $CONFIG_DIR/$HOST_CFG"; }
want mcu && { [ -f "$CONFIG_DIR/$MCU_CFG" ] || die "Нет конфига $CONFIG_DIR/$MCU_CFG"; }
want toolhead && { [ -f "$CONFIG_DIR/$TOOLHEAD_CFG" ] || die "Нет конфига $CONFIG_DIR/$TOOLHEAD_CFG"; }
KLIPPER_VER=$(git -C "$KLIPPER" describe --always --tags --long --dirty 2>/dev/null || echo unknown)
echo "Klipper в $KLIPPER: $KLIPPER_VER"
# Артефакт цели: ~/firmware/<цель>_klipper.<bin|elf> (симлинк на <цель>-<версия>.<ext>)
artifact() { case "$1" in host) echo "$OUT_DIR/host_klipper.elf" ;; *) echo "$OUT_DIR/$1_klipper.bin" ;; esac; }
if [ "$NO_BUILD" = 1 ]; then
echo "Без сборки — используются готовые прошивки:"
for t in "${TARGETS[@]}"; do
f=$(artifact "$t")
[ -s "$f" ] || die "Нет готовой прошивки $f (соберите: $0 --build-only $t)"
real=$(readlink -f "$f")
echo " $t: $(basename "$real") ($(stat -c %s "$real") байт)"
case "$(basename "$real")" in
*"-${KLIPPER_VER}."*) ;;
*) echo " !!! $t: прошивка собрана не из текущего $KLIPPER_VER" ;;
esac
done
fi
BEFORE=""
if [ "$BUILD_ONLY" = 0 ]; then
if want mcu || want toolhead; then
ip -br link show "$CAN_IF" 2>/dev/null | grep -q " UP " || die "$CAN_IF не поднят"
fi
# Печать не должна идти
KSTATE=$(klippy_state || true)
PRINT_STATE=$(curl -s --max-time 5 "$MOONRAKER/printer/objects/query?print_stats" \
| jq -r '.result.status.print_stats.state // empty' 2>/dev/null || true)
if [ -z "$KSTATE" ]; then
if [ "$FORCE" = 1 ]; then
echo "Moonraker недоступен, проверка печати пропущена (--force)"
else
die "Moonraker недоступен — не могу проверить, идёт ли печать (--force чтобы пропустить)"
fi
elif [ -z "$PRINT_STATE" ]; then
echo "klippy в состоянии '$KSTATE' (не ready) — печать не идёт, продолжаю"
else
echo "print_stats.state = $PRINT_STATE"
case "$PRINT_STATE" in
printing|paused) die "Идёт печать ($PRINT_STATE) — прошивка отменена" ;;
esac
fi
echo "Версии MCU до обновления:"
BEFORE=$(mcu_versions); echo "$BEFORE" | sed 's/^/ /'
# Пароль sudo — один раз, до остановки сервисов
sudo -v || die "Нужны права sudo"
fi
# ---------- Сборка ----------
BUILD_TMP=$(mktemp -d)
build() { # build <цель> <конфиг> <артефакт: bin|elf>
local name=$1 ext=$3
STAGE="сборка $name"
section "Сборка $name ($2)"
# Собираем из копии: olddefconfig переписывает конфиг и создаёт <конфиг>.old,
# а файлы в репозитории должны оставаться нетронутыми
local cfg="$BUILD_TMP/$2"
cp "$CONFIG_DIR/$2" "$cfg"
make -C "$KLIPPER" clean KCONFIG_CONFIG="$cfg" >/dev/null
make -C "$KLIPPER" olddefconfig KCONFIG_CONFIG="$cfg"
make -C "$KLIPPER" -j"$JOBS" KCONFIG_CONFIG="$cfg"
local src="$KLIPPER/out/klipper.$ext"
[ -s "$src" ] || die "Сборка $name не дала $src"
local dst="$OUT_DIR/${name}-${KLIPPER_VER}.$ext"
cp "$src" "$dst"
ln -sfn "$(basename "$dst")" "$(artifact "$name")"
echo "-> $dst ($(stat -c %s "$dst") байт)"
}
if [ "$NO_BUILD" = 0 ]; then
want mcu && build mcu "$MCU_CFG" bin
want toolhead && build toolhead "$TOOLHEAD_CFG" bin
want host && build host "$HOST_CFG" elf
fi
if [ "$BUILD_ONLY" = 1 ]; then
section "Готово (только сборка)"
ls -l "$OUT_DIR"/*_klipper.* 2>/dev/null
exit 0
fi
# ---------- Прошивка ----------
STAGE="остановка klipper"
section "Остановка klipper"
sudo systemctl stop klipper
KLIPPER_STOPPED=1
if want host; then
STAGE="прошивка host MCU"
section "Прошивка host (klipper-mcu)"
# flash-linux.sh <dir> копирует <dir>/klipper.elf в /usr/local/bin/klipper_mcu и перезапускает klipper-mcu.
# Подсовываем ему каталог с артефактом из ~/firmware, а не out/ (там может быть сборка другой цели)
mkdir -p "$BUILD_TMP/host"
cp "$(artifact host)" "$BUILD_TMP/host/klipper.elf"
HOST_PID_OLD=$(systemctl show -p ExecMainPID --value klipper-mcu)
sudo "$KLIPPER/scripts/flash-linux.sh" "$BUILD_TMP/host"
[ "$(stat -c %Y "$HOST_BIN")" -ge "$START_TS" ] || die "$HOST_BIN не обновился"
host_restarted() {
[ "$(systemctl show -p ExecMainPID --value klipper-mcu)" != "$HOST_PID_OLD" ] \
&& [ "$(systemctl show -p SubState --value klipper-mcu)" = "running" ] && [ -e /tmp/klipper_host_mcu ]
}
if ! wait_for 10 "klipper-mcu не перезапустился (PID $HOST_PID_OLD)" host_restarted; then
journalctl -u klipper-mcu -n 8 --no-pager -o cat || true
die "klipper-mcu не работает после прошивки (см. journalctl -u klipper-mcu)"
fi
# Убедиться, что процесс не падает в цикле перезапуска (Restart=always)
sleep 3
[ "$(systemctl show -p SubState --value klipper-mcu)" = "running" ] \
|| { journalctl -u klipper-mcu -n 8 --no-pager -o cat || true; die "klipper-mcu перезапускается в цикле"; }
echo "klipper-mcu перезапущен (PID $HOST_PID_OLD -> $(systemctl show -p ExecMainPID --value klipper-mcu)), $HOST_BIN обновлён"
fi
if want mcu; then
STAGE="прошивка Octopus Pro"
section "Прошивка mcu (Octopus Pro, uuid $MCU_UUID)"
python3 "$KATAPULT" -i "$CAN_IF" -u "$MCU_UUID" -r
wait_for 20 "не появился katapult serial ($KATAPULT_SERIAL_GLOB). Проверьте USB или перезагрузите плату" katapult_serial
DEV=$(katapult_serial)
echo "katapult serial: $DEV"
sleep 1 # порт может быть ещё не готов сразу после появления симлинка
python3 "$KATAPULT" -d "$DEV" -f "$(artifact mcu)"
# После прошивки плата перезагружается в Klipper и снова поднимает USB-CAN мост
wait_for 30 "Octopus не вернулся на $CAN_IF после прошивки" can_has_uuid "$MCU_UUID"
echo "Octopus снова на $CAN_IF"
fi
if want toolhead; then
STAGE="прошивка тулхеда"
section "Прошивка toolhead (EBB SB2209, uuid $TOOLHEAD_UUID)"
# flashtool сам отправляет запрос бутлоадера по UUID и заливает прошивку
python3 "$KATAPULT" -i "$CAN_IF" -u "$TOOLHEAD_UUID" -f "$(artifact toolhead)"
wait_for 30 "тулхед не вернулся на $CAN_IF после прошивки" can_has_uuid "$TOOLHEAD_UUID"
echo "Тулхед снова на $CAN_IF"
fi
section "Узлы на $CAN_IF"
python3 "$KATAPULT" -i "$CAN_IF" -q || true
# ---------- Запуск и проверка ----------
STAGE="запуск klipper"
section "Запуск klipper"
sudo systemctl start klipper
KLIPPER_STOPPED=0
STAGE="проверка после прошивки"
if wait_for 90 "klippy не перешёл в ready" klippy_ready; then
echo "klippy: ready"
else
echo "klippy state: $(klippy_state)"
curl -s --max-time 5 "$MOONRAKER/printer/info" | jq -r '.result.state_message // empty' 2>/dev/null || true
echo "--- хвост $KLIPPY_LOG ---"
tail -n 40 "$KLIPPY_LOG" 2>/dev/null || true
fi
section "Версии"
KLIPPY_VER=$(curl -s --max-time 5 "$MOONRAKER/printer/info" | jq -r '.result.software_version // "?"' 2>/dev/null || echo "?")
echo "klippy (хост): $KLIPPY_VER"
# klippy считает untracked-файлы в klippy/extras (autotune, Happy-Hare) как -dirty, прошивки — нет
KLIPPY_VER=${KLIPPY_VER%-dirty}
AFTER=$(mcu_versions)
MISMATCH=0
if [ -z "$AFTER" ] || [ "$AFTER" = "недоступно" ]; then
echo "!!! Версии MCU недоступны (klippy не ready)"; MISMATCH=1
else
printf '%-12s %-32s %s\n' "MCU" "было" "стало"
while IFS=$'\t' read -r name ver _; do
old=$(printf '%s\n' "$BEFORE" | awk -F'\t' -v n="$name" '$1==n{print $2}')
printf '%-12s %-32s %s\n' "$name" "${old:-?}" "$ver"
[ "$ver" = "$KLIPPY_VER" ] || { echo "!!! $name: версия $ver != klippy $KLIPPY_VER"; MISMATCH=1; }
done <<< "$AFTER"
fi
# Только строки после последнего старта klippy, чтобы не ловить старые ошибки
if tac "$KLIPPY_LOG" 2>/dev/null | sed '/^Start printer at/q' | grep -E "Command format mismatch|Protocol error|Lost communication" | tail -5 | grep .; then
echo "!!! В klippy.log есть ошибки протокола (см. выше)"; MISMATCH=1
fi
if [ "$MISMATCH" = 0 ]; then
echo; echo "Готово: все MCU на $KLIPPY_VER. Дальше: QUAD_GANTRY_LEVEL и проверить TAP/z_offset."
else
echo; echo "Есть расхождения — проверьте вывод выше; при необходимости FIRMWARE_RESTART или повторить: $0 <цель>"
exit 1
fi