Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pylabrobot/hamilton/star/driver/features/iswap.py
Original file line number Diff line number Diff line change
Expand Up @@ -3049,7 +3049,7 @@ async def request_pose(self) -> iSWAPPose:

# -- parking ---------------------------------------------------------------

async def request_parked(self, tolerance_increments: int = 2) -> bool:
async def request_is_parked(self, tolerance_increments: int = 2) -> bool:
"""Whether the arm is parked, judged from where its drives are.

Each drive is checked against the parking stop in its stored table, which setup reads:
Expand Down
22 changes: 11 additions & 11 deletions pylabrobot/hamilton/star/driver/features/iswap_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ async def test_the_arm_is_parked_when_every_drive_is_at_its_parking_position(sel
}
),
):
self.assertTrue(await iswap.request_parked())
self.assertTrue(await iswap.request_is_parked())

async def test_the_arm_is_not_parked_when_a_drive_is_moved(self):
iswap, _ = await gripper()
Expand All @@ -100,7 +100,7 @@ async def test_the_arm_is_not_parked_when_a_drive_is_moved(self):
c.elbow_predefined_y_positions_increments.parking - 10
)
with patch.object(iswap, "request_joint_state", new=AsyncMock(return_value=joints)):
self.assertFalse(await iswap.request_parked())
self.assertFalse(await iswap.request_is_parked())

async def test_z_above_the_parking_position_is_still_parked(self):
iswap, _ = await gripper()
Expand All @@ -112,7 +112,7 @@ async def test_z_above_the_parking_position_is_still_parked(self):
+ c.elbow_z_offset_above_finger
)
with patch.object(iswap, "request_joint_state", new=AsyncMock(return_value=joints)):
self.assertTrue(await iswap.request_parked())
self.assertTrue(await iswap.request_is_parked())


class TestJawMoves(unittest.IsolatedAsyncioTestCase):
Expand Down Expand Up @@ -385,7 +385,7 @@ async def test_it_never_asks_the_master_for_its_parked_flag(self):
"""`C0 RG` is answered wrongly by the firmware, so nothing may read it."""
iswap, sent = await gripper()

await iswap.request_parked()
await iswap.request_is_parked()

self.assertEqual([command for command in sent if command.startswith("C0RG")], [])

Expand All @@ -394,13 +394,13 @@ async def test_a_parked_arm_is_parked_and_a_moved_one_is_not(self):
c = iswap.configuration

await iswap.park()
self.assertTrue(await iswap.request_parked())
self.assertTrue(await iswap.request_is_parked())

assert c.elbow_predefined_y_positions_increments is not None
await iswap.elbow_move_to_y_position(
c.y_increments_to_mm(c.elbow_predefined_y_positions_increments.parking) - 50.0
)
self.assertFalse(await iswap.request_parked())
self.assertFalse(await iswap.request_is_parked())

async def test_the_jaws_are_part_of_it(self):
"""Parking closes them, and the gripper's table names that stop its home."""
Expand All @@ -412,7 +412,7 @@ async def test_the_jaws_are_part_of_it(self):
home = c.gripper_increments_to_mm(c.gripper_drive_predefined_increments.home)
await iswap.gripper_move_to_jaw_position(home + 5.0)

self.assertFalse(await iswap.request_parked())
self.assertFalse(await iswap.request_is_parked())

async def test_a_drive_within_the_tolerance_still_counts_as_parked(self):
iswap, _ = await gripper()
Expand All @@ -424,9 +424,9 @@ async def test_a_drive_within_the_tolerance_still_counts_as_parked(self):
assert c.elbow_predefined_y_positions_increments is not None
stop = c.elbow_predefined_y_positions_increments.parking
iswap.update_location_by_reference_point(y=c.y_increments_to_mm(stop + 2))
self.assertTrue(await iswap.request_parked())
self.assertTrue(await iswap.request_is_parked())
iswap.update_location_by_reference_point(y=c.y_increments_to_mm(stop + 20))
self.assertFalse(await iswap.request_parked())
self.assertFalse(await iswap.request_is_parked())

async def test_a_parked_arm_above_its_z_stop_is_parked_and_below_it_is_not(self):
"""Parked from 284 mm, the device stands at `rz` 26660 against a stop of 25400."""
Expand All @@ -438,9 +438,9 @@ async def test_a_parked_arm_above_its_z_stop_is_parked_and_below_it_is_not(self)
offset = c.elbow_z_offset_above_finger

iswap.update_location_by_reference_point(z=c.z_increments_to_mm(stop + 1260) + offset)
self.assertTrue(await iswap.request_parked())
self.assertTrue(await iswap.request_is_parked())
iswap.update_location_by_reference_point(z=c.z_increments_to_mm(stop - 20) + offset)
self.assertFalse(await iswap.request_parked())
self.assertFalse(await iswap.request_is_parked())


class TestElbowXMoves(unittest.IsolatedAsyncioTestCase):
Expand Down
12 changes: 6 additions & 6 deletions pylabrobot/hamilton/star/driver/features/pipettes.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ async def _require_iswap_parked(self) -> None:
RuntimeError: If it is not parked.
"""
iswap = self.arm.iswap
if iswap is not None and not await iswap.request_parked():
if iswap is not None and not await iswap.request_is_parked():
raise RuntimeError(
"the iSWAP is not parked, and the channels move where it stands. "
"Call `await star.iswap.park()` first."
Expand Down Expand Up @@ -3661,15 +3661,15 @@ def _tip_command_positions(
ys.append(round(centre.y * 10))
placed[channel] = (centre.x, centre.y)

# As legacy checks them: each pair taking part by itself, not the channels between them. Where
# those leave the pair too little room, the firmware arranges the channels, as it does for
# legacy's commands.
# Each pair taking part by itself, as legacy checks them; the firmware arranges the channels
# between. Distances in tenths, as the command carries them: 9.0 mm is 9.0 mm, float or not.
for i, (xi, yi) in placed.items():
for j, (xj, yj) in placed.items():
# Channels in different columns are separate moves on the device.
if i < j and round(xi, 1) == round(xj, 1) and abs(yi - yj) < self._min_pair_spacing(i, j):
apart = round(abs(yi - yj), 1)
if i < j and round(xi, 1) == round(xj, 1) and apart < self._min_pair_spacing(i, j):
raise ValueError(
f"channels {i} and {j} would be {abs(yi - yj):.1f} mm apart in Y, closer than "
f"channels {i} and {j} would be {apart} mm apart in Y, closer than "
f"{self._min_pair_spacing(i, j)} mm"
)

Expand Down
10 changes: 9 additions & 1 deletion pylabrobot/hamilton/star/driver/features/pipettes_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ async def test_not_parked_refuses(self):
async def not_parked(*args: Any, **kwargs: Any) -> bool:
return False

iswap.request_parked = not_parked # type: ignore[method-assign]
iswap.request_is_parked = not_parked # type: ignore[method-assign]
with self.assertRaises(RuntimeError):
await self.pipettes._require_iswap_parked()

Expand Down Expand Up @@ -2144,6 +2144,14 @@ async def test_a_pickup_brings_the_other_channels_along_in_y(self):
f"channels {channel} and {channel + 1} stand too close: {order}",
)

async def test_spots_a_pitch_apart_in_floating_point_are_accepted(self):
# 130.7 - 121.7 is 8.999999999999986 in floating point; the command carries 9.0 mm.
pipettes, _, _ = await channels_over_a_rack()
_, ys, _ = pipettes._tip_command_positions(
{0: Coordinate(300.0, 130.7, 150.0), 1: Coordinate(300.0, 121.7, 150.0)}
)
self.assertEqual(ys[:2], [1307, 1217])


class TestTipsOfDifferentKinds(unittest.IsolatedAsyncioTestCase):
"""A command names one tip type, so spots holding different tips go out in separate commands."""
Expand Down
Loading