diff --git a/firmware/host_linux.config b/firmware/host_linux.config new file mode 100644 index 0000000..8ab6fa2 --- /dev/null +++ b/firmware/host_linux.config @@ -0,0 +1,50 @@ +# CONFIG_LOW_LEVEL_OPTIONS is not set +# CONFIG_MACH_AVR is not set +# CONFIG_MACH_ATSAM is not set +# CONFIG_MACH_ATSAMD is not set +# CONFIG_MACH_LPC176X is not set +# CONFIG_MACH_STM32 is not set +# CONFIG_MACH_HC32F460 is not set +# CONFIG_MACH_RPXXXX is not set +# CONFIG_MACH_PRU is not set +# CONFIG_MACH_AR100 is not set +CONFIG_MACH_LINUX=y +# CONFIG_MACH_SIMU is not set +CONFIG_BOARD_DIRECTORY="linux" +CONFIG_CLOCK_FREQ=50000000 +CONFIG_LINUX_SELECT=y +CONFIG_USB_VENDOR_ID=0x1d50 +CONFIG_USB_DEVICE_ID=0x614e +CONFIG_USB_SERIAL_NUMBER="12345" +CONFIG_WANT_ADC=y +CONFIG_WANT_SPI=y +CONFIG_WANT_SOFTWARE_SPI=y +CONFIG_WANT_I2C=y +CONFIG_WANT_SOFTWARE_I2C=y +CONFIG_WANT_HARD_PWM=y +CONFIG_WANT_BUTTONS=y +CONFIG_WANT_TMCUART=y +CONFIG_WANT_NEOPIXEL=y +CONFIG_WANT_PULSE_COUNTER=y +CONFIG_WANT_ST7920=y +CONFIG_WANT_HD44780=y +CONFIG_WANT_ADXL345=y +CONFIG_WANT_LIS2DW=y +CONFIG_WANT_BMI160=y +CONFIG_WANT_MPU9250=y +CONFIG_WANT_ICM20948=y +CONFIG_WANT_THERMOCOUPLE=y +CONFIG_WANT_HX71X=y +CONFIG_WANT_ADS1220=y +CONFIG_WANT_LDC1612=y +CONFIG_WANT_SENSOR_ANGLE=y +CONFIG_NEED_SENSOR_BULK=y +CONFIG_WANT_TRIGGER_ANALOG=y +CONFIG_NEED_SOS_FILTER=y +CONFIG_CANBUS_FREQUENCY=1000000 +CONFIG_INLINE_STEPPER_HACK=y +CONFIG_HAVE_GPIO=y +CONFIG_HAVE_GPIO_ADC=y +CONFIG_HAVE_GPIO_SPI=y +CONFIG_HAVE_GPIO_I2C=y +CONFIG_HAVE_GPIO_HARD_PWM=y diff --git a/firmware/update_fw.sh b/firmware/update_fw.sh index 96c8437..c760df7 100755 --- a/firmware/update_fw.sh +++ b/firmware/update_fw.sh @@ -1,52 +1,264 @@ -#!/bin/bash +#!/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] [--force] [host|mcu|toolhead ...] +# без целей — все три +# --build-only — только собрать прошивки в ~/firmware, ничего не прошивать +# --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 -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 -#Полезные комманды -#make menuconfig KCONFIG_CONFIG=/home/biqu/printer_data/config/firmware/config.manta723.CAN -#make flash FLASH_DEVICE=0483:df11 -#python3 flash_can.py -i can0 -u 6c3c86ee0b29 -f ~/firmware/octopus_klipper.bin -#python3 ~/katapult/scripts/flashtool.py -i can0 -q -#make flash FLASH_DEVICE=/dev/serial/by-id/usb-Klipper_rp2040_45474E621A87A43A-if00 KCONFIG_CONFIG=/home/biqu/printer_data/config/firmware/hbb.cfg +set -euo pipefail -#Toolheads -#rp_sb2209=350f10596216 -#sht36_v2=8ce295109ef0 +# ---------- Константы ---------- +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 -toolhead_name=ebb_rp2240 -toolhead_uuid=d7e70bd38059 -mcu_uuid=afcf80281237 -mcu_path=/dev/serial/by-id/usb-katapult_stm32h723xx_320006001751313431393536-if00 -config_path=/home/biqu/printer_data/config/firmware/ +MCU_UUID="afcf80281237" +MCU_CFG="octopus-pro.config" +KATAPULT_SERIAL_GLOB='/dev/serial/by-id/usb-katapult_stm32h723xx_*' -mkdir -p ~/firmware +TOOLHEAD_UUID="d7e70bd38059" +TOOLHEAD_CFG="ebb_rp2240.CAN" -sudo service klipper stop +HOST_CFG="host_linux.config" +HOST_BIN="/usr/local/bin/klipper_mcu" -cd ~/klipper -make clean -make -j4 KCONFIG_CONFIG="${config_path}octopus-pro.config" -mv ~/klipper/out/klipper.bin ~/firmware/octopus_klipper.bin +ALL_TARGETS=(host mcu toolhead) -make clean -toolhead_config="${config_path}/${toolhead_name}.CAN" -make -j4 KCONFIG_CONFIG=$toolhead_config -mv ~/klipper/out/klipper.bin ~/firmware/toolhead_klipper.bin +# ---------- Разбор аргументов ---------- +BUILD_ONLY=0 +FORCE=0 +TARGETS=() +for arg in "$@"; do + case "$arg" in + -h|--help) sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + --build-only) BUILD_ONLY=1 ;; + --force) FORCE=1 ;; + host|mcu|toolhead) TARGETS+=("$arg") ;; + *) echo "Неизвестный аргумент: $arg (см. -h)" >&2; exit 2 ;; + esac +done +[ ${#TARGETS[@]} -eq 0 ] && TARGETS=("${ALL_TARGETS[@]}") +want() { local t; for t in "${TARGETS[@]}"; do [ "$t" = "$1" ] && return 0; done; return 1; } -cd ~/katapult/scripts -# Update MCU Octopus Pro -echo "Start update MCU Octopus Pro" -python3 ~/katapult/scripts/flashtool.py -i can0 -u $mcu_uuid -r -python3 flash_can.py -f ~/firmware/octopus_klipper.bin -d $mcu_path -sleep 2 -#read -p "MCU Manta M5P firmware flashed, please check above for any errors. Press [Enter] to continue, or [Ctrl+C] to abort" -#echo "Finish update MCU octopus" +# ---------- Лог и трапы ---------- +mkdir -p "$OUT_DIR" +LOG="$OUT_DIR/update_fw-$(date +%Y%m%d-%H%M).log" +exec > >(tee -a "$LOG") 2>&1 +START_TS=$(date +%s) +STAGE="инициализация" +KLIPPER_STOPPED=0 -# Update Toolhead MCU -echo "Start update Toolhead MCU" -python3 flash_can.py -i can0 -u $toolhead_uuid -f ~/firmware/toolhead_klipper.bin -sleep 2 -#read -p "MCU EBB36 firmware flashed, please check above for any errors. Press [Enter] to continue, or [Ctrl+C] to abort" -#echo "Finish update MCU EBB36" +on_err() { + echo + echo "!!! ОШИБКА на этапе: $STAGE (лог: $LOG)" >&2 +} +on_exit() { + local rc=$? + 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 -python3 ~/katapult/scripts/flashtool.py -i can0 -q +section() { echo; echo "=== $* ==="; } +die() { echo "!!! $*" >&2; exit 1; } -sudo service klipper start +# Ждать, пока команда-условие не вернёт 0 (таймаут в секундах) +wait_for() { + local timeout=$1 msg=$2; shift 2 + local i + for ((i = 0; i < timeout; i++)); do + if "$@" >/dev/null 2>&1; then return 0; fi + sleep 1 + done + echo "!!! Таймаут ($timeout с): $msg" >&2 + return 1 +} +can_has_uuid() { python3 "$KATAPULT" -i "$CAN_IF" -q 2>/dev/null | grep -q "$1"; } +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 лог: $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" + +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 + + # Печать не должна идти + 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 "$PRINT_STATE" ]; then + if [ "$FORCE" = 1 ]; then + echo "Moonraker недоступен, проверка печати пропущена (--force)" + else + die "Moonraker недоступен — не могу проверить, идёт ли печать (--force чтобы пропустить)" + fi + 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() { # build <цель> <конфиг> <артефакт: bin|elf> + local name=$1 cfg=$CONFIG_DIR/$2 ext=$3 + STAGE="сборка $name" + section "Сборка $name ($2)" + 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")" "$OUT_DIR/${name}_klipper.$ext" + echo "-> $dst ($(stat -c %s "$dst") байт)" +} + +# host собирается последним: его out/ нужен для make flash +want mcu && build mcu "$MCU_CFG" bin +want toolhead && build toolhead "$TOOLHEAD_CFG" bin +want host && build host "$HOST_CFG" elf + +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 копирует out/klipper.elf в /usr/local/bin/klipper_mcu и перезапускает klipper-mcu + make -C "$KLIPPER" flash KCONFIG_CONFIG="$CONFIG_DIR/$HOST_CFG" + [ "$(stat -c %Y "$HOST_BIN")" -ge "$START_TS" ] || die "$HOST_BIN не обновился" + wait_for 10 "klipper-mcu не запустился" systemctl is-active --quiet klipper-mcu + echo "klipper-mcu: $(systemctl is-active klipper-mcu), $HOST_BIN обновлён" +fi + +if want mcu; then + STAGE="прошивка Octopus Pro" + section "Прошивка mcu (Octopus Pro, uuid $MCU_UUID)" + can_has_uuid "$MCU_UUID" || die "Octopus ($MCU_UUID) не виден на $CAN_IF" + 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" + python3 "$KATAPULT" -d "$DEV" -f "$OUT_DIR/mcu_klipper.bin" + # После прошивки плата перезагружается в 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)" + can_has_uuid "$TOOLHEAD_UUID" || die "Тулхед ($TOOLHEAD_UUID) не виден на $CAN_IF" + python3 "$KATAPULT" -i "$CAN_IF" -u "$TOOLHEAD_UUID" -f "$OUT_DIR/toolhead_klipper.bin" + 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" +AFTER=$(mcu_versions) +printf '%-12s %-32s %s\n' "MCU" "было" "стало" +MISMATCH=0 +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" + +if tail -n 200 "$KLIPPY_LOG" 2>/dev/null | 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