From 0f5150936886c8db83ad2510e86634581b0d7de4 Mon Sep 17 00:00:00 2001 From: beniroquai Date: Fri, 26 Jun 2026 11:01:44 +0200 Subject: [PATCH 1/2] Update motor.py --- uc2rest/motor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uc2rest/motor.py b/uc2rest/motor.py index a02c88d..a46139e 100644 --- a/uc2rest/motor.py +++ b/uc2rest/motor.py @@ -705,7 +705,7 @@ def move_stepper(self, steps=(0,0,0,0), speed=(1000,1000,1000,1000), is_absolute motorProp["acceleration"] = int(acceleration[iMotor]) else: motorProp["accel"] = self.DEFAULT_ACCELERATION - motorProp["acceleleration"] = self.DEFAULT_ACCELERATION + motorProp["acceleration"] = self.DEFAULT_ACCELERATION motorPropList.append(motorProp) if len(motorPropList)==0: return "{'return':-1}" From 2a4b110194a39e6521dc91843b246c268006dc21 Mon Sep 17 00:00:00 2001 From: beniroquai Date: Sun, 28 Jun 2026 19:13:48 +0200 Subject: [PATCH 2/2] Skip partial stepper frames and add backlash API Avoid KeyError and stale positions when intermediate status frames arrive that only contain {stepperid, isDone}: inspect each stepper dict and continue if "position" is missing, and use a local `stepper` variable for clarity. Also add `set_backlash(axis, backlash)` and `get_backlash(axis)` helpers (with docstrings) to read/set per-axis backlash in hardware steps; they accept axis name or index and return the stored value. These changes improve robustness of position callbacks and provide a small public API for backlash handling. --- uc2rest/motor.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/uc2rest/motor.py b/uc2rest/motor.py index a46139e..d60c6ff 100644 --- a/uc2rest/motor.py +++ b/uc2rest/motor.py @@ -91,12 +91,19 @@ def _callback_motor_status(self, data): nSteppers = len(data["steppers"]) stepSizes = np.array((self.stepSizeA, self.stepSizeX, self.stepSizeY, self.stepSizeZ)) for iMotor in range(nSteppers): - stepperID = data["steppers"][iMotor]["stepperid"] + stepper = data["steppers"][iMotor] + # Intermediate status frames during a move carry only + # {stepperid, isDone} with no "position" key — skip them instead + # of raising KeyError 'position' (which previously aborted the + # whole callback and left currentPosition stale). + if "position" not in stepper: + continue + stepperID = stepper["stepperid"] # Hardware returns raw steps in firmware frame; convert to physical units # in user frame: phys = hw_steps * stepSize * direction. The direction sign # hides any wiring polarity flip from the caller. self.currentPosition[stepperID] = ( - data["steppers"][iMotor]["position"] + stepper["position"] * stepSizes[stepperID] * self.direction[stepperID] ) @@ -333,6 +340,26 @@ def setup_motor(self, axis, minPos, maxPos, stepSize, backlash, direction=None): self.stepSizeA = stepSize self.backlash[axisIdx] = backlash + def set_backlash(self, axis, backlash): + """Set the per-axis backlash (in hardware steps) used as a reversal overshoot. + + ``axis`` may be a name (\"X\"/\"Y\"/\"Z\"/\"A\") or a hardware index (0-3). + The value is stored in :attr:`backlash` and consumed by + :meth:`move_stepper`, which adds ``direction * backlash`` extra steps + whenever the axis changes direction, taking up the mechanical slack so the + stage still reaches the commanded target. Pass ``0`` to disable + compensation for that axis. Typically fed from a camera-based backlash + measurement (microns) after converting to steps with the axis step size. + """ + idx = self.xyztTo1230(axis) if isinstance(axis, str) else int(axis) + self.backlash[idx] = backlash + return float(self.backlash[idx]) + + def get_backlash(self, axis): + """Return the per-axis backlash (in hardware steps).""" + idx = self.xyztTo1230(axis) if isinstance(axis, str) else int(axis) + return float(self.backlash[idx]) + def xyztTo1230(self, axis): axis = axis.upper() if axis == "X":