Klipper — приёмы работы с вендорским форком QIDI

Вендоры (QIDI и др.) поставляют свой Klipper с закрытыми модулями и урезанным Moonraker без [update_manager]. Приёмы отладки такого форка через Moonraker API и SSH.

Гоча: после FIRMWARE_RESTART состояние startup — это НЕ ошибка

FIRMWARE_RESTART перезапускает Klipper + все MCU. Сразу после printer/info отдаёт:

{"state":"startup","state_message":"Printer is not ready ... The klippy host software is attempting to connect."}

Это штатная фаза переподключения MCU (на боксе с боксом-MCU ~12 с). Реальный промах: проверил состояние ОДИН раз через 16 с, увидел startup, счёл сбоем и откатил свои макросы. Перезапустил с поллингом — реальные тайминги: t=3s startup, t=6s startup, t=9s startup, t=12s ready. Правильно — опрашивать в цикле до ready, отличая startup (ждать) от error/shutdown (реальный сбой, читать state_message):

for i in $(seq 1 20); do sleep 3
  S=$(curl -s http://IP:7125/printer/info | python3 -c "import sys,json;print(json.load(sys.stdin)['result']['state'])")
  [ "$S" = ready ] && break
  case "$S" in error|shutdown) echo FAIL; break;; esac
done

Проверка регистрации макроса после: curl .../printer/gcode/help | ... 'MY_MACRO' in help.

Что QIDI вырезала/сломала в Moonraker (и гоча 404 vs 405)

  • /machine/system_info500 (падает на отсутствующем /dev_info.txt);
  • /machine/update/status404 (нет [update_manager] — Klipper/Moonraker штатно не обновить, только перепрошивкой);
  • НЕ вырезано, хоть и кажется: /machine/services/restart, /machine/reboot, /machine/shutdown — POST-only. GET по ним даёт 405 (не 404!), что легко прочитать как «функции нет». Различать 404 (нет маршрута) и 405 (не тот метод), пробовать целевым методом. Рестарт Moonraker: POST /machine/services/restart с {"service":"moonraker"}.
  • Гоча загрузки: POST /server/files/upload трактует поле path как каталог, имя берётся из файла; path=foo.cfg создаст foo.cfg/foo.cfg.

Правка конфига и порядок include

Новые секции — отдельным файлом ([include extras.cfg]), строку include ставить до автосейв-блока #*# (SAVE_CONFIG в конце printer.cfg), иначе include попадёт внутрь автосейва и сломает его. Реальный пример вставки — [include klipperscreen-extras.cfg] после [include box.cfg] через sed -i '/^\[include box.cfg\]/a ...'. Владельца/права — под klipper-юзера (chown qidi:netdev, chmod 644). Применение — FIRMWARE_RESTART. Держать бэкап и авто-откат при error.

Бинарные (скомпилированные) модули

QIDI часть extras отгружает как .so (напр. multi_color_controller.so в klippy/extras/) — исходника нет. Что делать:

  • список командcurl http://IP:7125/printer/gcode/help (в т.ч. вендорские MULTI_COLOR_*);
  • имена параметровstrings module.so | grep -iE 'TEMP|SLOT|time|state|cmd_': выдаёт токены gcode-параметров (SLOT, TEMP, minutes, state) и имена cmd-функций (cmd_multi_color_dry);
  • безопасный пробинг — вызвать команду без аргументов и прочитать ответ: если параметр обязателен, Klipper вернёт ошибку с его именем, НЕ выполнив действие. НО если параметр опционален, команда сработает с дефолтом → держать сеть безопасности (для нагрева — *_DISABLE_HEATER) и проверять эффект по объекту (printer/objects/query?<module>). Пример: SET_FILAMENT_DRY/MULTI_COLOR_DRY без аргументов → {"result":"ok"}, нагрев НЕ запущен (drying.box1.dry_state:0).

Запуск shell из макроса

gcode_shell_command (сторонний extra) есть не во всех форках — проверить ls klippy/extras/gcode_shell_command.py. Если есть — из макроса можно дёргать shell (напр. переключать сервисы, если у klipper-юзера беспарольный sudo):

[gcode_shell_command my_cmd]
command: bash -c "..."
timeout: 30.
verbose: True
[gcode_macro MY_MACRO]
gcode:
  RUN_SHELL_COMMAND CMD=my_cmd

gcode_shell_command исполняется от klipper-юзера — для sudo внутри нужен его NOPASSWD.

Чтение консоли (что видит экран)

Вывод команд (respond_info, //-строки) идёт в консоль, не попапом. Забрать: curl -s "http://IP:7125/server/gcode_store?count=30" (JSON, result.gcode_store[].message). ⚠️ парсить питоном через heredoc, а не python3 -c "...['result']..." внутри ssh '...' — вложенные одинарные кавычки ломают ключ, получаешь NameError: name 'result' is not defined (это про кавычки, не про данные). Родственное — KlipperScreen на слабом SBC.

Переопределение вендорских макросов (rename_existing и слияние секций)

Чтобы обернуть штатный макрос (например T0, добавив своё действие), в Klipper есть
rename_existing. С ним две ловушки:

  1. Тип имени. Цель переименования обязана быть того же класса, что исходное имя. T0
    «традиционный» g-code (буква+цифра), поэтому и цель — буква+цифра. rename_existing: BOX_T0
    падает: G-Code macro rename of different types ('T0' vs 'BOX_T0'). Валидно только вроде T90.
  2. Слияние секций. Klipper сливает одноимённые [gcode_macro T0] из разных include
    (опции более позднего файла побеждают). Если и вендорский box.cfg, и твой файл объявляют
    [gcode_macro T0], отдельного «оригинала» для переименования не остаётся → на connect падает
    Existing command 'T0' not found in gcode_macro rename.

Вывод: переопределять вендорский макрос из своего include надо полным переопределением, а
не rename_existing: в своём файле (подключённом ПОСЛЕ вендорского) объявить тот же
[gcode_macro T0], скопировав тело вендора и добавив своё — более поздняя опция gcode: побеждает
при слиянии. Чьё тело победило — видно по description в printer/gcode/help. Обе ошибки валят
Klipper в error, чинится следующим FIRMWARE_RESTART.

Урезанный Moonraker: чего ещё может не быть

Кроме [update_manager], на старом форке нередко отсутствует компонент [analysis] (нет
analysis.py, среди компонентов не грузится) — тогда серверная точная оценка времени недоступна,
остаётся только слайсерный post-process (см.
klipper_estimator — точная оценка времени печати).
Веб-морда Fluidd при этом может быть стоковой, но с вендорскими правками настроек и скрытыми
карточками — см. Fluidd — настройки в БД Moonraker.

Цифра в имени G-code команды = команда молча теряется

Парсер G-code Klipper режет имя команды по первой цифре: моя CS1237_FIND_SENSOR регистрировалась (видна в printer/gcode/help с моим description), но диспатчилась как несуществующая CS1237 — без ошибки, просто ничего не происходило. Поэтому вендорские команды называются CS_WEIGHT_*, а не CS1237_* (и между версиями прошивки их переименовывали — декомпилированные имена сверять с живым gcode/help). Свои команды и макросы — только [A-Z_] в имени.

Extras перечитываются ТОЛЬКО рестартом сервиса (и Moonraker после него глохнет)

RESTART/FIRMWARE_RESTART не перечитывают новые/изменённые файлы в klippy/extras/ — только systemctl restart klipper (удалённо: POST /machine/services/restart с {"service":"klipper"}). Две гочи следом:

  • Moonraker после рестарта сервиса 30–60 с молча теряет G-code: /printer/info уже ready, а POST gcode/script висит или возвращается пустым. Лечение — пинговать M115 в цикле до первого {"result": "ok"}, только потом слать команды.
  • Обходные каналы мимо Moonraker, когда он глохнет: pty ~/printer_data/comms/klippy.serial (printf "CMD\n" > <pty>, ответы cat оттуда же) и unix-сокет klippy.sock (JSON {"id":1,"method":"gcode/script","params":{"script":"CMD"}} + терминатор 0x03). Работают мгновенно и сразу после старта klippy.

Жёсткая остановка движения = THR MCU в shutdown

Рестарт сервиса klipper посреди активного движения роняет MCU тулхеда: klippy при переподключении падает в error с Can not update MCU 'THR' config as it is shutdown. Это штатно чинится FIRMWARE_RESTART (~15 с) — не паниковать и не искать поломку железа. Хоуминг после этого потерян — G28 перед следующим движением. Runtime-вскрытие закрытых модулей — Runtime-интроспекция закрытого Klipper-модуля.

Источники

  • Moonraker API: printer/info, printer/gcode/help, printer/gcode/script, server/gcode_store, printer/objects/query, machine/services, server/files/upload
  • github.com/qidi-community/q2-wiki