Reject PA calibrations with no result or bad sensor data

bdpressure.py leaves the extruder at the last PA_CALIBRATE test value
(0.098) when it finds no result, and _PA_AFTER_CALIBRATE saved that as
the calibrated PA (lane1 after an AFC pause, lane5 after the sensor went
silent). A run is now rejected, with nothing cached or written to
Spoolman, when the PA is still at the end of the scan, the printer
paused during the test, or bd_pressure gave too few or too few different
readings. The previous valid PA (or fallback_pa) is applied instead.

bdpressure_check reports pa_points and pa_distinct from the bdpressure
module's last PA_CALIBRATE data for this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-12 21:10:06 +02:00
1 parent 82f9486160
commit 25891640b8
3 files changed
+60 -3

No files matched your search

+2 -2
View File
@@ -37,7 +37,7 @@ everything below, grouped by topic. Comment out what you do not use there.
| `purge_check.cfg` | `BLOBIFIER_CHECKED`: checks every AFC purge with bd_pressure (and optionally bdwidth motion) | Blobifier, AFC, `extras/bdpressure_check.py` + bd_pressure load-check firmware, `extras/bdwidth_status.py` |
| `purge_until_empty.cfg` | `PURGE_UNTIL_EMPTY`: purges a snapped filament out until there is no more nozzle pressure | same as `purge_check.cfg` |
| **pressure_advance/** | | |
| `bd_pa_spoolman.cfg` + `bd_pa_*.sh` | `LANE_CALIBRATE_PA`, `PA_CALIBRATE_ALL_LANES`, `LANE_RECAL_PA`, `SET_SPOOL_ID`, `PA_STATUS`: pressure advance per lane/spool, cached locally and in Spoolman | bd_pressure, AFC, Spoolman, `gcode_shell_command` |
| `bd_pa_spoolman.cfg` + `bd_pa_*.sh` | `LANE_CALIBRATE_PA`, `PA_CALIBRATE_ALL_LANES`, `LANE_RECAL_PA`, `SET_SPOOL_ID`, `PA_STATUS`: pressure advance per lane/spool, cached locally and in Spoolman; a calibration with no result, a pause during the test or too few/stuck sensor readings is rejected and not saved | bd_pressure, AFC, Spoolman, `gcode_shell_command` |
| **spoolman/** | | |
| `active_spool.cfg` | `SET_ACTIVE_SPOOL`, `CLEAR_ACTIVE_SPOOL` | Spoolman in Moonraker |
| **leds/** | | |
@@ -51,7 +51,7 @@ everything below, grouped by topic. Comment out what you do not use there.
|---|---|
| `xy_drift.py` | `AFC_XY_SAFETY_CHECK`: measures lost XY steps from the stepper step counts at the endstop triggers (CoreXY). The toolhead position after homing cannot show lost steps. |
| `bdwidth_status.py` | exposes bdwidth's motion counter and width to macros (`present: False` with neutral values when no bdwidth is configured, so the macros also work without it), and keeps a USB dropout of bdwidth (port: usb) from shutting down Klipper: the read is retried every 10 s and the port reopened |
| `bdpressure_check.py` | bd_pressure load check (`BDP_CHECK_START/STOP/QUERY`). Needs the load-check firmware from the bd_pressure `load-check-extension` branch; on stock firmware it reports "not available" and does nothing. |
| `bdpressure_check.py` | bd_pressure load check (`BDP_CHECK_START/STOP/QUERY`). Needs the load-check firmware from the bd_pressure `load-check-extension` branch; on stock firmware it reports "not available" and does nothing. Also reports how many readings the last `PA_CALIBRATE` got (`pa_points`, `pa_distinct`), used to reject runs with a silent or stuck sensor. |
### Scripts (`scripts/`)
+16 -1
View File
@@ -19,6 +19,11 @@
#
# Status (printer.bdpressure_check), updated by the commands above:
# available, running, peak, above_ms, longest_ms, delta
# pa_points, pa_distinct - readings the [bdpressure] module collected in
# its last PA_CALIBRATE, and how many of them differ (read
# from that module, not from the sensor). A sensor that
# stops answering gives few points; one that hangs repeats
# the same reading. LANE_CALIBRATE_PA rejects such runs.
REG_EXT = 54 # first byte of the extension
REG_CTRL = 56
@@ -31,6 +36,7 @@ class BDPressureCheck:
self.printer = config.get_printer()
self.sensor_name = 'bdpressure ' + config.get('sensor', 'bd_pa')
self.i2c = None
self.sensor = None
self.status = {'available': False, 'running': False, 'peak': 0,
'above_ms': 0, 'longest_ms': 0, 'delta': 0}
self.printer.register_event_handler('klippy:connect',
@@ -45,6 +51,7 @@ class BDPressureCheck:
def _handle_connect(self):
sensor = self.printer.lookup_object(self.sensor_name)
self.sensor = sensor
self.i2c = getattr(sensor, 'i2c', None)
def _read(self):
@@ -100,7 +107,15 @@ class BDPressureCheck:
self._respond(gcmd, "BDP_CHECK_QUERY")
def get_status(self, eventtime):
return dict(self.status)
status = dict(self.status)
# [val, res, lk, rk, Hk, Ha] per reading, see bdpressure.py
try:
data = list(getattr(self.sensor, 'PA_data', None) or [])
status['pa_points'] = len(data)
status['pa_distinct'] = len(set(tuple(d[1:]) for d in data))
except Exception:
status['pa_points'] = status['pa_distinct'] = -1
return status
def load_config(config):
@@ -196,6 +196,19 @@ variable_travel_speed: 300
variable_acc_to_decel_factor: 50
# Minimum PA delta before we bother re-saving the cached value
variable_write_threshold: 0.002
# A calibration is rejected (nothing cached or written to Spoolman, the
# previous PA is kept) when:
# - its result is the last PA_CALIBRATE test value: bdpressure.py found no
# result and left the extruder at the end of its scan (0..49 x 0.002),
# - the printer paused during the test (e.g. an AFC filament fault),
# - bd_pressure gave fewer than min_points readings or fewer than
# min_distinct different ones (sensor silent or hung; needs
# bdpressure_check.py, otherwise this check is skipped).
variable_sweep_end_pa: 0.098
variable_min_points: 10
variable_min_distinct: 8
# PA applied when a rejected lane has no earlier cached value
variable_fallback_pa: 0.04
# Spoolman URL and the custom "extra" field name used to push calibrated
# PA values back to the spool record (so the value travels with the
# spool, not just with this printer's local save_variables cache).
@@ -469,6 +482,34 @@ gcode:
# triggered -- which happens after PA_CALIBRATE (queued earlier by
# the caller) has fully completed. So this read is fresh and correct.
{% set new_pa = printer.extruder.pressure_advance|default(0.0)|float %}
# Reject runs where the sensor or the printer had a problem
{% set problems = [] %}
{% if new_pa >= cfg.sweep_end_pa|float - 0.0001 %}
{% set _ = problems.append("no result (PA left at the end of the test range, %.3f)" % new_pa) %}
{% endif %}
{% if printer.pause_resume.is_paused %}
{% set _ = problems.append("the printer paused during the test") %}
{% endif %}
{% if 'bdpressure_check' in printer %}
{% set bdp = printer.bdpressure_check %}
{% set points = bdp.pa_points|default(-1)|int %}
{% set distinct = bdp.pa_distinct|default(-1)|int %}
{% if points >= 0 and points < cfg.min_points|int %}
{% set _ = problems.append("only %d readings from bd_pressure (sensor not answering?)" % points) %}
{% elif distinct >= 0 and distinct < cfg.min_distinct|int %}
{% set _ = problems.append("only %d different readings out of %d (sensor stuck?)" % (distinct, points)) %}
{% endif %}
{% endif %}
{% if problems %}
# A cached value at the end of the scan is an earlier failed run, not a PA
{% set use_cache = cache_found and cached_pa < cfg.sweep_end_pa|float - 0.0001 %}
{% set keep = cached_pa if use_cache else cfg.fallback_pa|float %}
RESPOND TYPE=error MSG="PA calibration for {lane} (spool {spool_id}) rejected: {problems|join('; ')}. Nothing saved; using PA={'%.4f' % keep} ({'cached' if use_cache else 'fallback'}). Fix the cause, then LANE_CALIBRATE_PA LANE={lane} FORCE=1"
SET_PRESSURE_ADVANCE ADVANCE={keep}
M117 PA cal rejected: {lane}
{% else %}
{action_respond_info("Calibration complete for %s: PA=%.4f" % (lane, new_pa))}
M117 PA cal done: {new_pa|round(4)}
@@ -480,6 +521,7 @@ gcode:
{% else %}
{action_respond_info("PA change below threshold (%.4f), skipping cache/Spoolman write" % cfg.write_threshold|float)}
{% endif %}
{% endif %}