Skip to content

Adding core_check_resource_exists_at_location_center & first PLR audiofeedback #78

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 19, 2024
Merged
1 change: 1 addition & 0 deletions pylabrobot/audio/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .audio import *
38 changes: 38 additions & 0 deletions pylabrobot/audio/audio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
""" Defines audio that machines can generate.

Source of audio: https://simpleguics2pygame.readthedocs.io/en/latest/_static/links/snd_links.html
"""

import functools


try:
from IPython.display import display, Audio
USE_AUDIO = True
except ImportError:
USE_AUDIO = False


def _audio_check(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if not USE_AUDIO:
return
return func(*args, **kwargs)
return wrapper


# ====== 1. Identifying items on deck (e.g. through LLD or Z-drive engagement) ======

@_audio_check
def play_not_found():
display(Audio(
url="https://codeskulptor-demos.commondatastorage.googleapis.com/pang/arrow.mp3",
autoplay=True))


@_audio_check
def play_got_item():
display(Audio(
url="https://codeskulptor-demos.commondatastorage.googleapis.com/descent/gotitem.mp3",
autoplay=True))
102 changes: 101 additions & 1 deletion pylabrobot/liquid_handling/backends/hamilton/STAR.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
)
from pylabrobot.resources.liquid import Liquid
from pylabrobot.resources.ml_star import HamiltonTip, TipDropMethod, TipPickupMethod, TipSize

from pylabrobot import audio

T = TypeVar("T")

Expand Down Expand Up @@ -4394,6 +4394,106 @@ async def core_move_plate_to_position(

return command_output

async def core_check_resource_exists_at_location_center(
self,
location: Coordinate,
resource: Resource,
gripper_y_margin: float = 5,
offset: Coordinate = Coordinate.zero(),
minimum_traverse_height_at_beginning_of_a_command: int = 2750,
z_position_at_the_command_end: int = 2750,
enable_recovery: bool = True,
audio_feedback: bool = True
) -> bool:
""" Check existence of resource with CoRe gripper tool
a "Get plate using CO-RE gripper" + error handling
Which channels are used for resource check is dependent on which channels have been used for
`STAR.get_core(p1: int, p2: int)` which is a prerequisite for this check function.

Args:
location: Location to check for resource
resource: Resource to check for.
gripper_y_margin = Distance between the front / back wall of the resource
and the grippers during "bumping" / checking
offset: Offset from resource position in mm.
minimum_traverse_height_at_beginning_of_a_command: Minimum traverse height at beginning of
a command [0.1mm] (refers to all channels independent of tip pattern parameter 'tm').
Must be between 0 and 3600. Default 3600.
z_position_at_the_command_end: Minimum z-Position at end of a command [0.1 mm] (refers to
all channels independent of tip pattern parameter 'tm'). Must be between 0 and 3600.
Default 3600.
enable_recovery: if True will ask for user input if resource was not found
audio_feedback: enable controlling computer to emit different sounds when
finding/not finding the resource

Returns:
bool: True if resource was found, False if resource was not found
"""

center = location + resource.centers()[0] + offset
y_width_to_gripper_bump = resource.get_size_y() - gripper_y_margin*2
assert 9 <= y_width_to_gripper_bump <= int(resource.get_size_y()), \
f"width between channels must be between 9 and {resource.get_size_y()} mm" \
" (i.e. the minimal distance between channels and the max y size of the resource"
Comment on lines +4433 to +4437
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how do the users move the core grippers arms? Just call star.move_channel_y?


# Check if CoRe gripper currently in use
cores_used = not self._core_parked
if not cores_used:
raise ValueError("CoRe grippers not yet picked up.")

# Enable recovery of failed checks
resource_found = False
try_counter = 0
while not resource_found:
try:
await self.core_get_plate(
x_position=int(center.x * 10),
y_position=int(center.y * 10),
z_position=int(center.z * 10),
open_gripper_position=int(y_width_to_gripper_bump * 10),
plate_width=int(y_width_to_gripper_bump * 10),
# Set default values based on VENUS check_plate commands
y_gripping_speed=50,
x_direction=0,
z_speed=600,
grip_strength = 20,
# Enable mods of channel z position for check acceleration
minimum_traverse_height_at_beginning_of_a_command = \
minimum_traverse_height_at_beginning_of_a_command,
minimum_z_position_at_the_command_end = z_position_at_the_command_end,
)
except STARFirmwareError as exc:
for module_error in exc.errors.values():
if module_error.trace_information == 62:
resource_found = True
else:
raise ValueError(f"Unexpected error encountered: {exc}") from exc
else:
if audio_feedback:
audio.play_not_found()
if enable_recovery:
print(f"\nWARNING: Resource '{resource.name}' not found at center" \
f" location {(center.x, center.y, center.z)} during check no {try_counter}.")
user_prompt = input("Have you checked resource is present?" \
"\n [ yes ] -> machine will check location again" \
"\n [ abort ] -> machine will abort run\n Answer:"
)
if user_prompt == "yes":
try_counter += 1
elif user_prompt == "abort":
raise ValueError(f"Resource '{resource.name}' not found at center" \
f" location {(center.x,center.y,center.z)}" \
" & error not resolved -> aborted resource movement.")
else:
# Resource was not found
return False

# Resource was found
if audio_feedback:
audio.play_got_item()
return True


# TODO:(command:ZB)

# -------------- 3.5.6 Adjustment & movement commands --------------
Expand Down