Skip to content

Commit fb4ef8b

Browse files
authored
Merge pull request #138 from openUC2/mergemaster
add direction to moster settings
2 parents a441d0a + 367780e commit fb4ef8b

1 file changed

Lines changed: 80 additions & 17 deletions

File tree

uc2rest/motor.py

Lines changed: 80 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ def __init__(self, parent=None):
2424
self.currentDirection = np.zeros((self.nMotors))
2525
self.currentPosition = np.zeros((self.nMotors))
2626

27+
# Per-axis hardware-direction sign (+1 or -1), indexed as [A, X, Y, Z]
28+
# Applied internally at the firmware boundary: outbound commands are
29+
# multiplied by this sign before being sent, and inbound positions are
30+
# multiplied by this sign before being reported. This lets callers keep
31+
# a consistent physical/user coordinate frame while motor.py absorbs
32+
# any wiring polarity flips. Configured via setup_motor() (either via
33+
# an explicit `direction` argument or by passing a negative stepSize).
34+
self.direction = np.ones((self.nMotors), dtype=np.int8)
35+
2736
self.minPosX = -np.inf
2837
self.minPosY = -np.inf
2938
self.minPosZ = -np.inf
@@ -83,8 +92,14 @@ def _callback_motor_status(self, data):
8392
stepSizes = np.array((self.stepSizeA, self.stepSizeX, self.stepSizeY, self.stepSizeZ))
8493
for iMotor in range(nSteppers):
8594
stepperID = data["steppers"][iMotor]["stepperid"]
86-
# Hardware returns steps, convert to physical units: (steps * stepSize)
87-
self.currentPosition[stepperID] = data["steppers"][iMotor]["position"] * stepSizes[stepperID]
95+
# Hardware returns raw steps in firmware frame; convert to physical units
96+
# in user frame: phys = hw_steps * stepSize * direction. The direction sign
97+
# hides any wiring polarity flip from the caller.
98+
self.currentPosition[stepperID] = (
99+
data["steppers"][iMotor]["position"]
100+
* stepSizes[stepperID]
101+
* self.direction[stepperID]
102+
)
88103
if callable(self._callbackPerKey[0]):
89104
self._callbackPerKey[0](self.currentPosition) # we call the function with the value
90105
except Exception as e:
@@ -254,7 +269,42 @@ def stopFocusScanning(self):
254269
'''################################################################################################################################################
255270
HIGH-LEVEL Functions that rely on basic REST-API functions
256271
################################################################################################################################################'''
257-
def setup_motor(self, axis, minPos, maxPos, stepSize, backlash):
272+
def setup_motor(self, axis, minPos, maxPos, stepSize, backlash, direction=None):
273+
"""Configure one motor axis.
274+
275+
The ``stepSize`` is stored internally as a positive magnitude (physical
276+
units per step). The wiring polarity is tracked separately in
277+
``self.direction[axis]`` so the user/physical coordinate frame stays
278+
consistent in both directions of communication.
279+
280+
Parameters
281+
----------
282+
axis : str
283+
One of "X", "Y", "Z", "A".
284+
minPos, maxPos : float
285+
Soft limits in physical (user-frame) units.
286+
stepSize : float
287+
Physical units per step. A negative value is interpreted as a
288+
request to flip the hardware direction for this axis (equivalent
289+
to passing ``direction=-1``) and is split into magnitude + sign.
290+
backlash : float
291+
Backlash in hardware steps (sign handled internally).
292+
direction : int or None, optional
293+
Explicit hardware-direction sign (+1 or -1). If ``None`` it is
294+
derived from the sign of ``stepSize`` (default +1).
295+
"""
296+
# Split sign from magnitude. An explicit `direction` always wins;
297+
# otherwise the sign of stepSize is consumed and stepSize becomes
298+
# a positive scale factor.
299+
if direction is None:
300+
sign = -1 if stepSize < 0 else 1
301+
else:
302+
sign = -1 if direction < 0 else 1
303+
stepSize = abs(stepSize)
304+
305+
axisIdx = self.xyztTo1230(axis)
306+
self.direction[axisIdx] = sign
307+
258308
if axis == "X":
259309
self.minPosX = minPos
260310
self.maxPosX = maxPos
@@ -271,7 +321,7 @@ def setup_motor(self, axis, minPos, maxPos, stepSize, backlash):
271321
self.minPosA = minPos
272322
self.maxPosA = maxPos
273323
self.stepSizeA = stepSize
274-
self.backlash[self.xyztTo1230(axis)] = backlash
324+
self.backlash[axisIdx] = backlash
275325

276326
def xyztTo1230(self, axis):
277327
axis = axis.upper()
@@ -504,12 +554,16 @@ def move_stepper(self, steps=(0,0,0,0), speed=(1000,1000,1000,1000), is_absolute
504554

505555
# Store the target position in physical units BEFORE conversion to hardware steps
506556
targetPositionPhysical = steps.copy()
507-
508-
# convert from physical units to steps
509-
steps[0] *= 1/self.stepSizeA
510-
steps[1] *= 1/self.stepSizeX
511-
steps[2] *= 1/self.stepSizeY
512-
steps[3] *= 1/self.stepSizeZ
557+
558+
# Convert from physical (user-frame) units to firmware (hardware-frame) steps.
559+
# Apply the per-axis direction sign so wiring polarity is hidden from callers:
560+
# hw_steps = phys / |stepSize| * direction
561+
# (For relative moves this flips the requested delta; for absolute targets it
562+
# flips the absolute position the firmware should drive to.)
563+
steps[0] = steps[0] * self.direction[0] / self.stepSizeA
564+
steps[1] = steps[1] * self.direction[1] / self.stepSizeX
565+
steps[2] = steps[2] * self.direction[2] / self.stepSizeY
566+
steps[3] = steps[3] * self.direction[3] / self.stepSizeZ
513567

514568
# detect change in direction and compute distances in HARDWARE STEPS for travel time calculation
515569
absoluteDistances_steps = np.zeros((4)) # Distance in hardware steps
@@ -617,16 +671,19 @@ def move_stepper(self, steps=(0,0,0,0), speed=(1000,1000,1000,1000), is_absolute
617671
}
618672
}
619673

620-
# Update currentPosition to track expected position in physical units
621-
# steps are now in hardware units, so we need to convert back to physical
674+
# Update currentPosition to track expected position in physical (user-frame) units.
675+
# ``steps`` is in hardware/firmware frame at this point, so we convert back with
676+
# phys = hw_steps * stepSize * direction
622677
stepSizes = np.array((self.stepSizeA, self.stepSizeX, self.stepSizeY, self.stepSizeZ))
623678
for iMotor in range(self.nMotors):
624679
if isAbsoluteArray[iMotor]:
625-
# For absolute: convert hardware steps back to physical units: (steps * stepSize)
626-
self.currentPosition[iMotor] = steps[iMotor] * stepSizes[iMotor]
680+
# For absolute: convert hardware steps back to physical (user) units.
681+
self.currentPosition[iMotor] = steps[iMotor] * stepSizes[iMotor] * self.direction[iMotor]
627682
else:
628-
# For relative: convert step delta to physical delta and add to current position
629-
self.currentPosition[iMotor] = self.currentPosition[iMotor] + (steps[iMotor] * stepSizes[iMotor])
683+
# For relative: convert hw step delta to physical delta and accumulate.
684+
self.currentPosition[iMotor] = self.currentPosition[iMotor] + (
685+
steps[iMotor] * stepSizes[iMotor] * self.direction[iMotor]
686+
)
630687

631688
# drive motor
632689
self.isRunning = True
@@ -757,7 +814,13 @@ def get_position(self, axis=None, timeout=1):
757814
if "motor" in r :
758815
for index, istepper in enumerate(r["motor"]["steppers"]):
759816
if index >3: break # TODO: We would need to handle other values too soon
760-
_position[istepper["stepperid"]]=istepper["position"]*_physicalStepSizes[self.motorAxisOrder[index]]
817+
stepperID = istepper["stepperid"]
818+
# phys = hw_steps * stepSize * direction (apply wiring sign at boundary)
819+
_position[stepperID] = (
820+
istepper["position"]
821+
* _physicalStepSizes[self.motorAxisOrder[index]]
822+
* self.direction[stepperID]
823+
)
761824

762825

763826
return _position

0 commit comments

Comments
 (0)