104 lines
4.5 KiB
Python
104 lines
4.5 KiB
Python
# Measure lost XY steps on a CoreXY printer by re-homing.
|
|||
|
|
#
|
||
|
|
# Homing resets Klipper's idea of the position, so the toolhead position
|
||
|
|
# after G28 is always the endstop position and cannot show lost steps. The
|
||
|
|
# stepper drivers' step counters (MCU positions) are not reset by homing:
|
||
|
|
# when no steps were lost, the endstop triggers at the same step count as
|
||
|
|
# last time; every lost step shows up as a difference. This module records
|
||
|
|
# the step counts at each X and Y endstop trigger and reports the drift in
|
||
|
|
# mm against a stored reference.
|
||
|
|
#
|
||
|
|
# Install: copy to ~/klipper/klippy/extras/xy_drift.py and add [xy_drift]
|
||
|
|
#
|
||
|
|
# Commands:
|
||
|
|
# XY_DRIFT_STORE use the last X and Y homing as the reference
|
||
|
|
# XY_DRIFT_QUERY print the drift of the last homing against the reference
|
||
|
|
#
|
||
|
|
# Status (printer.xy_drift):
|
||
|
|
# ref_set a reference is stored (cleared by a Klipper restart,
|
||
|
|
# which also resets the step counters)
|
||
|
|
# homed_x, homed_y the axis was homed since the reference was stored
|
||
|
|
# x, y drift in mm of the last homing against the reference
|
||
|
|
# (positive = Klipper had moved further than the head did)
|
||
|
|
|
||
|
|
import logging
|
||
|
|
|
||
|
|
|
||
|
|
class XYDrift:
|
||
|
|
def __init__(self, config):
|
||
|
|
self.printer = config.get_printer()
|
||
|
|
self.sx = self.sy = None
|
||
|
|
self.last = {} # axis -> kinematic position (mm) at the trigger
|
||
|
|
self.ref = {}
|
||
|
|
self.since_ref = set()
|
||
|
|
self.printer.register_event_handler('klippy:connect',
|
||
|
|
self._handle_connect)
|
||
|
|
self.printer.register_event_handler('homing:home_rails_end',
|
||
|
|
self._handle_home_rails_end)
|
||
|
|
gcode = self.printer.lookup_object('gcode')
|
||
|
|
gcode.register_command('XY_DRIFT_STORE', self.cmd_STORE,
|
||
|
|
desc="Store the last XY homing as drift reference")
|
||
|
|
gcode.register_command('XY_DRIFT_QUERY', self.cmd_QUERY,
|
||
|
|
desc="Report XY drift against the reference")
|
||
|
|
|
||
|
|
def _handle_connect(self):
|
||
|
|
kin = self.printer.lookup_object('toolhead').get_kinematics()
|
||
|
|
steppers = {s.get_name(): s for s in kin.get_steppers()}
|
||
|
|
self.sx = steppers.get('stepper_x')
|
||
|
|
self.sy = steppers.get('stepper_y')
|
||
|
|
if self.sx is None or self.sy is None:
|
||
|
|
raise self.printer.config_error(
|
||
|
|
"xy_drift needs stepper_x and stepper_y (CoreXY)")
|
||
|
|
|
||
|
|
def _handle_home_rails_end(self, homing_state, rails):
|
||
|
|
# This event also comes from other modules (Beacon sends it with no
|
||
|
|
# rails and a state that raises on get_trigger_position), so only
|
||
|
|
# handle X/Y homing and never let an error escape: an exception
|
||
|
|
# here shuts Klipper down.
|
||
|
|
try:
|
||
|
|
names = [rail.get_name() for rail in rails]
|
||
|
|
axes = [axis for axis, st in (('x', self.sx), ('y', self.sy))
|
||
|
|
if st is not None and st.get_name() in names]
|
||
|
|
if not axes:
|
||
|
|
return
|
||
|
|
a = (homing_state.get_trigger_position(self.sx.get_name())
|
||
|
|
* self.sx.get_step_dist())
|
||
|
|
b = (homing_state.get_trigger_position(self.sy.get_name())
|
||
|
|
* self.sy.get_step_dist())
|
||
|
|
except Exception:
|
||
|
|
logging.exception("xy_drift: could not read trigger positions")
|
||
|
|
return
|
||
|
|
# CoreXY: x = (a + b) / 2, y = (a - b) / 2
|
||
|
|
pos = {'x': 0.5 * (a + b), 'y': 0.5 * (a - b)}
|
||
|
|
for axis in axes:
|
||
|
|
self.last[axis] = pos[axis]
|
||
|
|
self.since_ref.add(axis)
|
||
|
|
|
||
|
|
def _drift(self, axis):
|
||
|
|
if axis in self.ref and axis in self.last:
|
||
|
|
return round(self.last[axis] - self.ref[axis], 4)
|
||
|
|
return 0.
|
||
|
|
|
||
|
|
def cmd_STORE(self, gcmd):
|
||
|
|
if 'x' not in self.last or 'y' not in self.last:
|
||
|
|
raise gcmd.error("XY_DRIFT_STORE: home X and Y first")
|
||
|
|
self.ref = dict(self.last)
|
||
|
|
self.since_ref = set()
|
||
|
|
gcmd.respond_info("XY_DRIFT: reference stored")
|
||
|
|
|
||
|
|
def cmd_QUERY(self, gcmd):
|
||
|
|
if not self.ref:
|
||
|
|
gcmd.respond_info("XY_DRIFT: no reference stored")
|
||
|
|
return
|
||
|
|
gcmd.respond_info("XY_DRIFT: X=%.3f mm Y=%.3f mm" % (
|
||
|
|
self._drift('x'), self._drift('y')))
|
||
|
|
|
||
|
|
def get_status(self, eventtime):
|
||
|
|
return {'ref_set': bool(self.ref),
|
||
|
|
'homed_x': 'x' in self.since_ref,
|
||
|
|
'homed_y': 'y' in self.since_ref,
|
||
|
|
'x': self._drift('x'), 'y': self._drift('y')}
|
||
|
|
|
||
|
|
|
||
|
|
def load_config(config):
|
||
|
|
return XYDrift(config)
|