Skip to content

Commit 12f31d0

Browse files
authored
fix(api): update flex stacker motion params for DVT (#17697)
1 parent 6716cd6 commit 12f31d0

File tree

12 files changed

+199
-161
lines changed

12 files changed

+199
-161
lines changed

api/src/opentrons/drivers/flex_stacker/driver.py

Lines changed: 55 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
StackerInfo,
2121
HardwareRevision,
2222
MoveParams,
23+
AxisParams,
2324
LimitSwitchStatus,
2425
LEDColor,
2526
StallGuardParams,
@@ -57,56 +58,67 @@
5758
STALLGUARD_CONFIG = {
5859
StackerAxis.X: StallGuardParams(StackerAxis.X, True, 2),
5960
StackerAxis.Z: StallGuardParams(StackerAxis.Z, True, 2),
60-
StackerAxis.L: StallGuardParams(StackerAxis.L, True, 2),
6161
}
6262

6363
STACKER_MOTION_CONFIG = {
6464
StackerAxis.X: {
65-
"home": MoveParams(
66-
StackerAxis.X,
67-
max_speed=10.0, # mm/s
68-
acceleration=100.0, # mm/s^2
69-
max_speed_discont=40, # mm/s
70-
current=1.5, # mAmps
65+
"home": AxisParams(
66+
run_current=1.5, # mAmps
67+
hold_current=0.75,
68+
move_params=MoveParams(
69+
max_speed=10.0, # mm/s
70+
acceleration=100.0, # mm/s^2
71+
max_speed_discont=40.0, # mm/s
72+
),
7173
),
72-
"move": MoveParams(
73-
StackerAxis.X,
74-
max_speed=200.0,
75-
acceleration=1500.0,
76-
max_speed_discont=40,
77-
current=1.0,
74+
"move": AxisParams(
75+
run_current=1.0,
76+
hold_current=0.75,
77+
move_params=MoveParams(
78+
max_speed=200.0,
79+
acceleration=1500.0,
80+
max_speed_discont=40.0,
81+
),
7882
),
7983
},
8084
StackerAxis.Z: {
81-
"home": MoveParams(
82-
StackerAxis.Z,
83-
max_speed=10.0,
84-
acceleration=100.0,
85-
max_speed_discont=40,
86-
current=1.5,
85+
"home": AxisParams(
86+
run_current=1.5,
87+
hold_current=1.8,
88+
move_params=MoveParams(
89+
max_speed=10.0,
90+
acceleration=100.0,
91+
max_speed_discont=25.0,
92+
),
8793
),
88-
"move": MoveParams(
89-
StackerAxis.Z,
90-
max_speed=200.0,
91-
acceleration=500.0,
92-
max_speed_discont=40,
93-
current=1.5,
94+
"move": AxisParams(
95+
run_current=1.5,
96+
hold_current=0.5,
97+
move_params=MoveParams(
98+
max_speed=150.0,
99+
acceleration=500.0,
100+
max_speed_discont=25.0,
101+
),
94102
),
95103
},
96104
StackerAxis.L: {
97-
"home": MoveParams(
98-
StackerAxis.L,
99-
max_speed=100.0,
100-
acceleration=800.0,
101-
max_speed_discont=40,
102-
current=0.8,
105+
"home": AxisParams(
106+
run_current=0.8,
107+
hold_current=0.15,
108+
move_params=MoveParams(
109+
max_speed=100.0,
110+
acceleration=800.0,
111+
max_speed_discont=40.0,
112+
),
103113
),
104-
"move": MoveParams(
105-
StackerAxis.L,
106-
max_speed=100.0,
107-
acceleration=800.0,
108-
max_speed_discont=40,
109-
current=0.6,
114+
"move": AxisParams(
115+
run_current=0.6,
116+
hold_current=0.15,
117+
move_params=MoveParams(
118+
max_speed=100.0,
119+
acceleration=800.0,
120+
max_speed_discont=40.0,
121+
),
110122
),
111123
},
112124
}
@@ -183,18 +195,12 @@ def parse_installation_detected(cls, response: str) -> bool:
183195
def parse_move_params(cls, response: str) -> MoveParams:
184196
"""Parse move params."""
185197
field_names = MoveParams.get_fields()
186-
pattern = r"\s".join(
187-
[
188-
rf"{f}:(?P<{f}>(\d*\.)?\d+)" if f != "M" else rf"{f}:(?P<{f}>[XZL])"
189-
for f in field_names
190-
]
191-
)
192-
_RE = re.compile(f"^{GCODE.GET_MOVE_PARAMS} {pattern}$")
198+
pattern = r"\s".join(rf"{f}:(?P<{f}>(\d*\.)?\d+)" for f in field_names)
199+
_RE = re.compile(rf"^{GCODE.GET_MOVE_PARAMS} M:([XZL]) {pattern}$")
193200
m = _RE.match(response)
194201
if not m:
195202
raise ValueError(f"Incorrect Response for move params: {response}")
196203
return MoveParams(
197-
axis=StackerAxis(m.group("M")),
198204
max_speed=float(m.group("V")),
199205
acceleration=float(m.group("A")),
200206
max_speed_discont=float(m.group("D")),
@@ -294,14 +300,9 @@ def append_move_params(
294300
) -> CommandBuilder:
295301
"""Append move params."""
296302
if params is not None:
297-
if params.max_speed is not None:
298-
command.add_float("V", params.max_speed, GCODE_ROUNDING_PRECISION)
299-
if params.acceleration is not None:
300-
command.add_float("A", params.acceleration, GCODE_ROUNDING_PRECISION)
301-
if params.max_speed_discont is not None:
302-
command.add_float(
303-
"D", params.max_speed_discont, GCODE_ROUNDING_PRECISION
304-
)
303+
command.add_float("V", params.max_speed, GCODE_ROUNDING_PRECISION)
304+
command.add_float("A", params.acceleration, GCODE_ROUNDING_PRECISION)
305+
command.add_float("D", params.max_speed_discont, GCODE_ROUNDING_PRECISION)
305306
return command
306307

307308
@classmethod
@@ -410,6 +411,7 @@ async def set_stallguard_threshold(
410411
self, axis: StackerAxis, enable: bool, threshold: int
411412
) -> bool:
412413
"""Enables and sets the stallguard threshold for the given axis motor."""
414+
assert axis != StackerAxis.L, "Stallguard not supported for L axis"
413415
if not -64 < threshold < 63:
414416
raise ValueError(
415417
f"Threshold value ({threshold}) should be between -64 and 63."

api/src/opentrons/drivers/flex_stacker/errors.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
)
88

99

10+
class EStopTriggered(ErrorResponse):
11+
"""Raised when the estop is triggered during a move."""
12+
13+
def __init__(self, port: str, response: str, command: str) -> None:
14+
super().__init__(port, response, command)
15+
16+
1017
class MotorStallDetected(ErrorResponse):
1118
"""Raised when a motor stall is detected."""
1219

@@ -47,6 +54,7 @@ class StackerErrorCodes(BaseErrorCode):
4754
"""Stacker-specific error codes."""
4855

4956
UNHANDLED_GCODE = ("ERR003", UnhandledGcode)
57+
ESTOP_TRIGGERED = ("ERR006", EStopTriggered)
5058
MOTOR_STALL_DETECTED = ("ERR403", MotorStallDetected)
5159
MOTOR_QUEUE_FULL = ("ERR404", MotorQueueFull)
5260
UNEXPECTED_LIMIT_SWITCH = ("ERR405", UnexpectedLimitSwitch)

api/src/opentrons/drivers/flex_stacker/simulator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ async def get_tof_sensor_status(self, sensor: TOFSensor) -> TOFSensorStatus:
184184
@ensure_yield
185185
async def get_motion_params(self, axis: StackerAxis) -> MoveParams:
186186
"""Get the motion parameters used by the given axis motor."""
187-
return MoveParams(axis, 1, 1, 1)
187+
return MoveParams(1, 1, 1)
188188

189189
@ensure_yield
190190
async def get_stallguard_threshold(self, axis: StackerAxis) -> StallGuardParams:

api/src/opentrons/drivers/flex_stacker/types.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -207,16 +207,40 @@ class TOFSensorStatus:
207207
class MoveParams:
208208
"""Move Parameters."""
209209

210-
axis: Optional[StackerAxis] = None
211-
max_speed: Optional[float] = None
212-
acceleration: Optional[float] = None
213-
max_speed_discont: Optional[float] = None
214-
current: Optional[float] = 0
210+
max_speed: float
211+
acceleration: float
212+
max_speed_discont: float
215213

216214
@classmethod
217215
def get_fields(cls) -> List[str]:
218216
"""Get parsing fields."""
219-
return ["M", "V", "A", "D"]
217+
return ["V", "A", "D"]
218+
219+
def update(
220+
self,
221+
max_speed: Optional[float] = None,
222+
acceleration: Optional[float] = None,
223+
max_speed_discont: Optional[float] = None,
224+
) -> "MoveParams":
225+
"""Update the move parameters and return a new object."""
226+
return MoveParams(
227+
max_speed=max_speed if max_speed is not None else self.max_speed,
228+
acceleration=acceleration
229+
if acceleration is not None
230+
else self.acceleration,
231+
max_speed_discont=max_speed_discont
232+
if max_speed_discont is not None
233+
else self.max_speed_discont,
234+
)
235+
236+
237+
@dataclass
238+
class AxisParams:
239+
"""Axis Parameters."""
240+
241+
run_current: float
242+
hold_current: float
243+
move_params: MoveParams
220244

221245

222246
@dataclass

api/src/opentrons/hardware_control/modules/flex_stacker.py

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@
4747

4848
# Maximum distance in mm the axis can travel.
4949
MAX_TRAVEL = {
50-
StackerAxis.X: 192.5,
51-
StackerAxis.Z: 136.0,
52-
StackerAxis.L: 23.0,
50+
StackerAxis.X: 194.0,
51+
StackerAxis.Z: 139.5,
52+
StackerAxis.L: 22.0,
5353
}
5454

5555
# The offset in mm to subtract from MAX_TRAVEL when moving an axis before we home.
@@ -122,8 +122,7 @@ async def build(
122122
)
123123

124124
# Enable stallguard
125-
for axis in StackerAxis:
126-
config = STALLGUARD_CONFIG[axis]
125+
for axis, config in STALLGUARD_CONFIG.items():
127126
await driver.set_stallguard_threshold(
128127
axis, config.enabled, config.threshold
129128
)
@@ -272,13 +271,14 @@ async def move_axis(
272271
) -> bool:
273272
"""Move the axis in a direction by the given distance in mm."""
274273
await self.reset_stall_detected()
275-
motion_params = STACKER_MOTION_CONFIG[axis]["move"]
276-
await self._driver.set_run_current(axis, current or motion_params.current or 0)
277-
if any([speed, acceleration, current]):
278-
motion_params = self._reader.motion_params[axis]
279-
motion_params.current = current or motion_params.current
280-
motion_params.max_speed = speed or motion_params.max_speed
281-
motion_params.acceleration = acceleration or motion_params.acceleration
274+
default = STACKER_MOTION_CONFIG[axis]["move"]
275+
await self._driver.set_run_current(
276+
axis, current if current is not None else default.run_current
277+
)
278+
await self._driver.set_ihold_current(axis, default.hold_current)
279+
motion_params = default.move_params.update(
280+
max_speed=speed, acceleration=acceleration
281+
)
282282
distance = direction.distance(distance)
283283
res = await self._driver.move_in_mm(axis, distance, params=motion_params)
284284
if res == MoveResult.STALL_ERROR:
@@ -295,14 +295,14 @@ async def home_axis(
295295
current: Optional[float] = None,
296296
) -> bool:
297297
await self.reset_stall_detected()
298-
motion_params = STACKER_MOTION_CONFIG[axis]["home"]
299-
await self._driver.set_run_current(axis, current or motion_params.current or 0)
300-
# Set the max hold current for the Z axis
301-
if axis == StackerAxis.Z:
302-
await self._driver.set_ihold_current(axis, 1.8)
303-
if any([speed, acceleration]):
304-
motion_params.max_speed = speed or motion_params.max_speed
305-
motion_params.acceleration = acceleration or motion_params.acceleration
298+
default = STACKER_MOTION_CONFIG[axis]["home"]
299+
await self._driver.set_run_current(
300+
axis, current if current is not None else default.run_current
301+
)
302+
await self._driver.set_ihold_current(axis, default.hold_current)
303+
motion_params = default.move_params.update(
304+
max_speed=speed, acceleration=acceleration
305+
)
306306
success = await self._driver.move_to_limit_switch(
307307
axis=axis, direction=direction, params=motion_params
308308
)
@@ -320,14 +320,11 @@ async def close_latch(
320320
# Dont move the latch if its already closed.
321321
if self.limit_switch_status[StackerAxis.L] == StackerAxisState.EXTENDED:
322322
return True
323-
motion_params = STACKER_MOTION_CONFIG[StackerAxis.L]["move"]
324-
speed = velocity or motion_params.max_speed
325-
accel = acceleration or motion_params.acceleration
326323
success = await self.home_axis(
327324
StackerAxis.L,
328325
Direction.RETRACT,
329-
speed=speed,
330-
acceleration=accel,
326+
speed=velocity,
327+
acceleration=acceleration,
331328
)
332329
# Check that the latch is closed.
333330
await self._reader.get_limit_switch_status()
@@ -345,18 +342,14 @@ async def open_latch(
345342
# Dont move the latch if its already opened.
346343
if self.limit_switch_status[StackerAxis.L] == StackerAxisState.RETRACTED:
347344
return True
348-
motion_params = STACKER_MOTION_CONFIG[StackerAxis.L]["move"]
349-
speed = velocity or motion_params.max_speed
350-
accel = acceleration or motion_params.acceleration
351-
distance = MAX_TRAVEL[StackerAxis.L]
352345
# The latch only has one limit switch, so we have to travel a fixed distance
353346
# to open the latch.
354347
success = await self.move_axis(
355348
StackerAxis.L,
356349
Direction.EXTEND,
357-
distance=distance,
358-
speed=speed,
359-
acceleration=accel,
350+
distance=MAX_TRAVEL[StackerAxis.L],
351+
speed=velocity,
352+
acceleration=acceleration,
360353
)
361354
# Check that the latch is opened.
362355
await self._reader.get_limit_switch_status()
@@ -394,7 +387,9 @@ async def store_labware(self, labware_height: float) -> bool:
394387

395388
# Transfer
396389
await self.open_latch()
397-
z_speed = (STACKER_MOTION_CONFIG[StackerAxis.Z]["move"].max_speed or 0) / 2
390+
z_speed = (
391+
STACKER_MOTION_CONFIG[StackerAxis.Z]["move"].move_params.max_speed or 0
392+
) / 2
398393
await self.move_axis(
399394
StackerAxis.Z, Direction.EXTEND, (labware_height / 2), z_speed
400395
)
@@ -433,7 +428,9 @@ def __init__(self, driver: AbstractFlexStackerDriver) -> None:
433428
}
434429
self.platform_state = PlatformState.UNKNOWN
435430
self.hopper_door_closed = False
436-
self.motion_params = {axis: MoveParams(axis=axis) for axis in StackerAxis}
431+
self.motion_params: Dict[StackerAxis, Optional[MoveParams]] = {
432+
axis: None for axis in StackerAxis
433+
}
437434
self.get_config = True
438435

439436
async def read(self) -> None:

api/tests/opentrons/drivers/flex_stacker/test_driver.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ async def test_get_motion_params(
8989
connection.send_command.return_value = "M120 M:X V:200.000 A:1500.000 D:5.000"
9090
response = await subject.get_motion_params(types.StackerAxis.X)
9191
assert response == types.MoveParams(
92-
axis=types.StackerAxis.X,
9392
acceleration=1500.0,
9493
max_speed=200.0,
9594
max_speed_discont=5.0,

0 commit comments

Comments
 (0)