Авто-калибровка PA и потока на QIDI Q2 — гайд

Инструкция, как на стоковой прошивке QIDI Q2 сделать себе автоматическую калибровку Pressure Advance и замер максимального объёмного потока штатным тензодатчиком-зондом. Итог: в панели Macros три кнопки — CALIBRATE_PA, CALIBRATE_FLOW, CALIBRATE_HELP; нажал → через ~5 минут число для слайсера в консоли и во всплывашке на экране. Ничего не сохраняется на принтере — только замер и вывод.

Теория и математика вынесены отдельно: метод свипа PA, метод лесенки потока. Здесь — только реализация.

Что нужно

  • QIDI Q2 или Q2C на стоковой прошивке 01.01.02.01+ (в 01.01.02.03 сенсорный стек байт-в-байт тот же — я сверял распаковкой образа; Plus4 не подойдёт, там другой датчик).
  • SSH-доступ root к принтеру и Moonraker http://<ip>:7125 из локалки.
  • numpy в klippy-env (на Q2 стоит 2.0.1; scipy 1.13.1 тоже есть — я проверял).
  • Запрет: не шить мейнлайн Klipper (патчсеты MisterSheikh/n3oney) — Combo Box работает только на закрытых box_*.so форка QIDI.

Почему это работает (кратко)

В голове Q2 тензодатчик с 24-битным АЦП CS1237 (пины THR:PB3/PB4), уже сконфигурированный на 1280 Гц и gain 128 — штатно им принтер только щупает стол. Закрытый модуль air.so — переименованный мейнлайновый probe_eddy_current, его объекты доступны из любого стороннего extra: printer.lookup_object('probe_air').sensor_helper.read_origin_data() возвращает сырое значение датчика (int). Синхронный опрос из reactor-greenlet даёт ~39–40 Гц — этого хватает: сигнал экструзии в 120+ раз больше шума. Подробности вскрытия закрытого API — QIDI Q2 — закрытый API тензодатчика.

Состав решения: какие файлы куда кладутся

Весь код я держу в локальном репозитории (каталог autopa-q2/), на принтер он раскатывается скриптом. Обновление прошивки QIDI перезаписывает printer.cfg, но НЕ трогает klippy/extras/*.py — после прошивки достаточно перезапустить установку.

Файл в репоКуда на принтереЧто делает
q2_loadcell.py/home/qidi/klipper/klippy/extras/адаптер датчика: поллер-greenlet ~40 Гц поверх read_origin_data(), тарирование медианой 2 с, print_time-таймстампы, Collector-API мейнлайнового load_cell
vendor/autopa-repo/autopa/ (форк G0BL1N/autopa, AGPL)/home/qidi/klipper/klippy/extras/autopa/движок свипа PA: AUTOPA_SWEEP, bd-анализ на numpy; мои правки помечены # Q2: — гейты частоты вынесены в конфиг, сдвиг точки по X, команды профилей не регистрируются
q2_flow.py (порт flow-модуля PrusaPATuner)/home/qidi/klipper/klippy/extras/движок лесенки потока: QFLOW_MEASURE, фит + три детектора предела
q2_calibrate.py/home/qidi/klipper/klippy/extras/оркестратор: QPA_CALIBRATE/QPA_FLOW — прогрев, продувка, позиция над столом, замер, уборка, вердикт, всплывашка; QPA_ABORT, QPA_HELP
install.sh / uninstall.shзапускаются с компараскатка: scp модулей, вставка секций в printer.cfg (перед маркером SAVE_CONFIG), рестарт сервиса klipper, смок-тест датчика
klipperscreen-macros.cfg.snippetдописывается в свой include-файл конфигатри gcode_macro для панели
klipperscreen-calib.conf.snippetKlipperScreen.conf (в пользовательскую зону!)скрытие мусорных макросов из панели

Установка

  1. Запустить ./install.sh — он сам: проверит, что принтер не печатает (print_stats.state == standby — именно print_stats, /printer/info врёт ready и во время печати); зальёт модули; вставит секции конфига; перезапустит сервис klipper; дождётся Moonraker и прогонит смок QPA_SENSOR_TEST. Успех выглядит так: q2_loadcell: 200 samples, 40.0 Hz, tare=..., sigma=96 counts, read_errors=0.
  2. Секции, которые install.sh добавляет в printer.cfg (перед строкой-маркером #*# <---------------------- SAVE_CONFIG ---------------------->):
[q2_loadcell]
 
[autopa]
min_segment_rate: 35
min_segment_samples: 12
sweep_tfast: 0.5
sweep_cycles: 10
sweep_wobble_axis: X
sweep_x_shift: 3
 
[q2_flow]
 
[q2_calibrate]

Смысл значений: стоковые гейты autopa (сегмент < 40 Гц или < 20 сэмплов → брак) на фактических 38.7 Гц бракуют 100% данных, поэтому пороги 35/12; ступени свипа удлинены (tfast 0.5 с, 10 циклов) — вдвое больше сэмплов на сегмент; вобл по X, потому что жёлоб стоит на пределе Y; сдвиг точки на 3 мм на каждый шаг сетки K размазывает кучку пластика дорожкой.

  1. Макросы панели — дописать в свой include-файл (у меня klipperscreen-extras.cfg, подключённый из printer.cfg) и перезапустить сервис klipper:
[gcode_macro CALIBRATE_PA]
description: Авто-подбор Pressure Advance (число вписать в слайсер). TEMP=температура сопла, FULL=1 широкая сетка K для незнакомого материала
gcode:
  {% set temp = params.TEMP|default(220)|int %}
  {% set full = params.FULL|default(0)|int %}
  QPA_CALIBRATE TEMP={temp} FULL={full}
 
[gcode_macro CALIBRATE_FLOW]
description: Замер максимального объёмного потока (безопасное число вписать в слайсер в Max volumetric speed). TEMP=температура, FMIN/FMAX=границы лесенки мм3/с
gcode:
  {% set temp = params.TEMP|default(220)|int %}
  {% set fmin = params.FMIN|default(5)|int %}
  {% set fmax = params.FMAX|default(30)|int %}
  QPA_FLOW TEMP={temp} FMIN={fmin} FMAX={fmax}
 
[gcode_macro CALIBRATE_HELP]
description: Шпаргалка по калибровщику PA/потока
gcode:
  QPA_HELP

Параметры обязаны быть в стиле {% set x = params.X|default(N)|int %} — только такие KlipperScreen показывает редактируемыми полями в панели Macros. Однострочная подстановка {params.X|default(...)} полей не даёт — я на это наступил.

  1. Скрыть чужую свалку из панели Macros (у Q2 в ней 107 стоковых макросов): секция [displayed_macros Printer] со строками ИМЯ: False. ⚠️ Вставлять в KlipperScreen.conf выше строки-маркера #~# --- Do not edit below this line... — всё, что ниже маркера, KlipperScreen игнорирует и стирает при первой своей записи. Макросы при этом не удаляются, галки возвращаются в Настройки → Macros.

Разведка датчика перед постройкой (проверь на своей машине)

Все команды безопасны на простое, ответы читаются из консоли Fluidd или GET /server/gcode_store?count=30:

CS_WEIGHT_READ_CONFIG    → // 传感器配置:0x3c --- 正确应该是0x3c
   0x3C = SPEED 11 (1280 Гц) | PGA 11 (gain 128) | канал A — чип уже на максимуме
WEIGHTING_DEBUG_QUERY    → // 传感器数据(-): 15858769 / -918447 / -2.138426(mV)
   сырое_беззнаковое / знаковое (raw−2²⁴) / милливольты моста
WEIGHTING_DEBUG_ZERO_DATA → // 零点数据: 15858361 / -2.139376(mV)   (тарирование зонда)

Мои опорные числа: шум покоя σ=323 отсчёта (моторы выключены) / ~787 (включены); порог срабатывания зонда delta_v: 0.08 мВ ≈ 34 400 отсчётов ≈ 190 г (пересчёт по counts_per_gram≈179.4 с чужого Q2 на мейнлайне); базлайн уезжает на ~50k отсчётов между холодной и горячей головой — поэтому тарирование на каждый запуск. Подписка probe_air.add_client(cb) в форке НЕ работает (0 колбэков даже во время настоящего G28-промера — проверено), единственный путь данных — опрос read_origin_data().

Контракт адаптера (если пишешь свой)

autopa потребляет от датчика ровно это — реализуешь пять вещей и его код работает без единой правки:

class Q2LoadCell:               # объект секции [q2_loadcell]
    name = 'q2_loadcell'
    sensor                      # .get_samples_per_second() -> ~38.7 (замерить!),
                                # .get_range() -> (-0x800000, 0x7FFFFF)
    def get_collector(self) -> Collector
 
class Collector:
    is_started                  # property! сеттер False обязан ГАСИТЬ поллер:
                                # аварийный путь autopa делает только is_started=False
    def start_collecting(self, min_time=None)      # min_time — отсечка print_time
    def collect_until(self, print_time)            # -> ([[t, grams, counts, tare],...], errs)
                                                   # блокирует через reactor.pause, реактор жив
 
# обнаружение без патчей autopa — событие на klippy:ready:
self.printer.send_event('load_cell:tare', self)
# знак: давление гонит counts ВНИЗ; анализ сам считает force = -(counts - tare)
# таймстампы: mcu.estimated_print_time(reactor.monotonic()), НИКОГДА time.time()

Грабли адаптера, которые у меня поймали тесты (12 регрессов + мутационные канарейки):

  • Дубль поллера: погашенный greenlet замечает флаг только проснувшись; новый сбор в этом окне запускал второго — частота отчёта удваивалась (80 Гц вместо 40) и уезжала прямо в гейт анализа. Лечение: штамповать greenlet поколением при регистрации, устаревший выходит сам.
  • Тарирование медианой, не средним: спайк +250 000 в тихом окне сдвигает среднее на 25 000, медиану — никак.
  • Исключение чтения не должно убивать поллер: считать в read_errors, строку пропустить, жить дальше.
  • Кэп буфера (у меня 60 000 строк ≈ 26 мин ≈ 12 МБ) — коробка с 498 МБ ОЗУ.

Последовательность цикла обёртки (порядок выстрадан)

  1. Отказ, если print_stats.state = printing/paused; отказ, если бокс говорит «филамент не заряжен» (подсказка BOX_LOAD TN=<n> T=<temp>).
  2. SAVE_GCODE_STATEG28 при нехоумленных осях (⚠️ G28 сбрасывает уставку камеры — прочитать до, вернуть M141 S<v> после).
  3. Штатный CLEAR_NOZZLE HOTEND=<temp> — продувает прежний материал из расплава, чистит сопло, сбрасывает лоток. Без этого хвост PETG в расплаве завысил мне K PLA до 0.0366 против чистых 0.032.
  4. Стол вниз G1 Z200, голова на центр X135 Y135, M109 S<temp> → замер (свип или лесенка).
  5. Финал БЕЗ продувки: хвост стокового макроса — M106 S255 + M104 S140 + стол вверх сразу (G1 Z2 едет, пока сопло остывает) → TEMPERATURE_WAIT MAXIMUM=190 → щётка (5×X103↔90, 7×X114↔100, 5×X103↔90 на Y287.5, F5000) → скребок кружками на Z−0.2 (после остывания ≤150) → рычаг сброса лотка X164↔180 на Z−1.5 → M107, M104 S0, RESTORE_GCODE_STATE MOVE=0.
  6. Вердикт «числом вперёд» в консоль + всплывашка на экран через action-prompts: respond_info('action:prompt_begin Калибровка готова'), ...prompt_text <текст>, ...prompt_footer_button Закрыть|RESPOND TYPE=command MSG=action:prompt_end, ...prompt_show.

Механика install.sh (что стоит скопировать)

  • Пре-чек занятости — ТОЛЬКО print_stats.state == "standby".
  • Пакет autopa передавать tar-пайпом через ssh во временный каталог и подменять mv (rsync на принтере может отсутствовать, COPYFILE_DISABLE=1 спасает от AppleDouble-мусора macOS).
  • Секции в printer.cfg вставлять python-скриптом ПЕРЕД маркером #*# <---------------------- SAVE_CONFIG ---------------------->, идемпотентно (повторный запуск не дублирует), с бэкапом и атомарной записью с восстановлением владельца файла (chmod+chown до os.replace — пишешь под root, файл принадлежит qidi).
  • После рестарта сервиса ждать /printer/info == ready, затем пинговать POST gcode/script?script=M115 до первого {"result": "ok"} (Moonraker-лаг 30–60 с).
  • Смок: QPA_SENSOR_TEST DURATION=5 и парс частоты из ответа; порог ≥35 Гц.

Как пользоваться

  • CALIBRATE_PA TEMP=220 — весь цикл сам: проверки (не печатает ли; заряжен ли филамент — если нет, подскажет BOX_LOAD), хоуминг при необходимости, штатный CLEAR_NOZZLE (продувает прежний материал из расплава — иначе хвост PETG завышает K PLA, у меня было 0.0366 вместо 0.032), стол вниз на Z200, голова на центр, свип, протирка без продувки, вердикт. Узкая сетка K 0.02–0.05 (~0.6 г пластика, ~5 мин); для незнакомого материала FULL=1 — сетка 0.01–0.08 (~1.2 г). Ничего не применяется автоматически — K вписывается в поле Pressure Advance профиля филамента в слайсере.
  • CALIBRATE_FLOW TEMP=240 — лесенка потока 5→30 мм³/с (~1.15 г, ~2 мин), вердикт: реальный предел и безопасное число (предел × 0.85) — безопасное вписывается в Max volumetric speed профиля филамента.
  • CALIBRATE_HELP — шпаргалка в консоль.
  • Аварийно: QPA_ABORT (уборка: гасит поллер, возвращает PA, выключает нагрев; ⚠️ команда, набранная во время идущего свипа, исполнится только ПОСЛЕ него — Klipper на лету переисполняет лишь M112). Диагностика датчика: QPA_SENSOR_TEST.
  • После любого замера снять спагетти со стола — оно лежит на холодной плите и снимается одним движением.

Мои результаты для ориентира: PLA Rapido @220 — K 0.032 (три прогона 0.0328/0.0311/0.0331, разброс 6.2%, совпало с ручным методом линий), поток 18.7 безопасно / ~22 предел; Elegoo Rapid PETG @240 — K 0.059, поток 19.6 / 23. Вендорские профили потоку ставят 12–17 — замер дал честные +20–30% скорости.

Траблшутинг

СимптомПричина и лечение
K_opt: none, все сегменты забракованыстоковые гейты на 40 Гц; проверить min_segment_rate: 35, min_segment_samples: 12 в [autopa]
метрики не зависят от K (flat table)PA-гейт не взводится: вобл 0 или ось не хоумлена. Проверка: мини-свипы K=0 и K=0.05 обязаны отличаться на порядки (у меня 0/223 против 23142/27028)
K уехал вверх после смены материалахвост прежнего пластика в расплаве — цикл обязан начинаться с CLEAR_NOZZLE (в моей обёртке уже так)
σ полок пляшет, guard ругается на baseline shiftкуча пластика достала до сопла — мерить только над опущенным столом
своя команда зарегистрирована, но молча не выполняетсяцифра в имени: парсер Klipper режет CS1237_FOO до CS1237. Имена — только [A-Z_]
после install.sh команды «висят»Moonraker после рестарта сервиса klipper ~30–60 с молча теряет G-code — пинговать M115 до первого ответа
MCU тулхеда в shutdown после аварийной остановкижёсткий стоп посреди движения; лечится FIRMWARE_RESTART
секции в KlipperScreen.conf исчезливставлены ниже маркера #~# — переносить в пользовательскую зону

Проверка, что зонд не пострадал (я делал до и после): G28, PROBE_ACCURACY — σ должна остаться на уровне базовой (у меня 0.00163 → 0.00166 мм).

Ссылки