diff --git a/README.md b/README.md index c5d2b89..3884337 100644 --- a/README.md +++ b/README.md @@ -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/`) diff --git a/extras/bdpressure_check.py b/extras/bdpressure_check.py index ca5d350..146d125 100644 --- a/extras/bdpressure_check.py +++ b/extras/bdpressure_check.py @@ -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): diff --git a/macros/pressure_advance/bd_pa_spoolman.cfg b/macros/pressure_advance/bd_pa_spoolman.cfg index 48543da..00443b3 100644 --- a/macros/pressure_advance/bd_pa_spoolman.cfg +++ b/macros/pressure_advance/bd_pa_spoolman.cfg @@ -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 %}