Problem aspirating with 50 uL tips

I’m testing a very simple method that uses the 50 uL tips, which I haven’t yet used with PLR. I don’t know that the 50 uL tips is the issue, but it is the only thing new I’m doing relative to any other actions I’ve done without similar issues. I can also change the method to use 1000 uL tips and it works fine.

Here is a compressed set of relevant commands I’m using:

tip_car = TIP_CAR_480_A00(name='tip_carrier')
tip_car[0] = TIP_50ul_L(name='tips_01')
lh.deck.assign_child_resource(tip_car, rails=1)

premix_car = PLT_CAR_L5AC_A00(name='premix_carrier')
premix_car[0] = Cos_96_PCR(name='premix_plate')
lh.deck.assign_child_resource(premix_car, rails=7)

await lh.pick_up_tips96(lh.get_resource('tips_01'))
await lh.aspirate_plate(lh.get_resource('premix_plate'), volume=15)


It will pick up tips just fine, but when it goes to aspirate, I get a parameter out of range error. The only parameter I’m passing is the volume, but the volumes I’m using should work fine with the 50 uL tip. I’ll note that I tried several different volumes between 5 and 25 uL with no effect:

STARFirmwareError                         Traceback (most recent call last)
Cell In[15], line 2
      1 #await lh.aspirate_plate(lh.get_resource('premix_plate'), volume=premix_vol, flow_rates=aspirate_speed)
----> 2 await lh.aspirate_plate(lh.get_resource('premix_plate'), volume=15)

File ~/Documents/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/, in LiquidHandler.aspirate_plate(self, plate, volume, flow_rate, end_delay, blow_out_air_volume, **backend_kwargs)
   1257       well.tracker.rollback()
   1258     channel.get_tip().tracker.rollback()
-> 1259   self._trigger_callback(
   1260     "aspirate_plate",
   1261     liquid_handler=self,
   1262     aspiration=aspiration_plate,
   1263     error=error,
   1264     **backend_kwargs,
   1265   )
   1266 else:
   1267   for channel, well in zip(self.head96.values(), plate.get_all_items()):

File ~/Documents/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/, in LiquidHandler._trigger_callback(self, method_name, error, *args, **kwargs)
   1652   callback(self, *args, error=error, **kwargs)
   1653 elif error is not None:
-> 1654   raise error

File ~/Documents/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/, in LiquidHandler.aspirate_plate(self, plate, volume, flow_rate, end_delay, blow_out_air_volume, **backend_kwargs)
   1241 aspiration_plate = AspirationPlate(
   1242   resource=plate,
   1243   volume=volume,
   1249   liquids=cast(List[List[Tuple[Optional[Liquid], float]]], all_liquids) # stupid
   1250 )
   1252 try:
-> 1253   await self.backend.aspirate96(aspiration=aspiration_plate, **backend_kwargs)
   1254 except Exception as error:  # pylint: disable=broad-except
   1255   for channel, well in zip(self.head96.values(), plate.get_all_items()):

File ~/Documents/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/, in need_iswap_parked.<locals>.wrapper(self, *args, **kwargs)
     58 if self.iswap_installed and not self.iswap_parked:
     59   await self.park_iswap()
---> 61 result = await method(self, *args, **kwargs) # pylint: disable=not-callable
     63 return result

File ~/Documents/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/, in STAR.aspirate96(self, aspiration, jet, blow_out, use_lld, liquid_height, air_transport_retract_dist, hlc, aspiration_type, minimum_traverse_height_at_beginning_of_a_command, minimal_end_height, lld_search_height, maximum_immersion_depth, tube_2nd_section_height_measured_from_zm, tube_2nd_section_ratio, immersion_depth, immersion_depth_direction, liquid_surface_sink_distance_at_the_end_of_aspiration, transport_air_volume, pre_wetting_volume, gamma_lld_sensitivity, swap_speed, settling_time, homogenization_volume, homogenization_cycles, homogenization_position_from_liquid_surface, surface_following_distance_during_homogenization, speed_of_homogenization, limit_curve_index)
   2134 pull_out_distance_to_take_transport_air_in_function_without_lld = \
   2135   int(air_transport_retract_dist * 10)
   2137 # Was this ever true? Just copied it over from pyhamilton. Could have something to do with
   2138 # the liquid classes and whether blow_out mode is enabled.
   2139 # # Unfortunately, `blow_out_air_volume` does not work correctly, so instead we aspirate air
   2147 #     aspiration_volumes=int(blow_out_air_volume * 10)
   2148 #   )
-> 2150 return await self.aspirate_core_96(
   2151   x_position=int(position.x * 10),
   2152   x_direction=0,
   2153   y_positions=int(position.y * 10),
   2154   aspiration_type=aspiration_type,
   2156   minimum_traverse_height_at_beginning_of_a_command=
   2157    minimum_traverse_height_at_beginning_of_a_command,
   2158   minimal_end_height=minimal_end_height,
   2159   lld_search_height=lld_search_height,
   2160   liquid_surface_at_function_without_lld=liquid_surface_at_function_without_lld,
   2161   pull_out_distance_to_take_transport_air_in_function_without_lld=
   2162    pull_out_distance_to_take_transport_air_in_function_without_lld,
   2163   maximum_immersion_depth=maximum_immersion_depth,
   2164   tube_2nd_section_height_measured_from_zm=tube_2nd_section_height_measured_from_zm,
   2165   tube_2nd_section_ratio=tube_2nd_section_ratio,
   2166   immersion_depth=immersion_depth,
   2167   immersion_depth_direction=immersion_depth_direction,
   2168   liquid_surface_sink_distance_at_the_end_of_aspiration=
   2169    liquid_surface_sink_distance_at_the_end_of_aspiration,
   2170   aspiration_volumes=aspiration_volumes,
   2171   aspiration_speed=flow_rate,
   2172   transport_air_volume=transport_air_volume,
   2173   blow_out_air_volume=blow_out_air_volume,
   2174   pre_wetting_volume=pre_wetting_volume,
   2175   lld_mode=int(use_lld),
   2176   gamma_lld_sensitivity=gamma_lld_sensitivity,
   2177   swap_speed=swap_speed,
   2178   settling_time=settling_time,
   2179   homogenization_volume=homogenization_volume,
   2180   homogenization_cycles=homogenization_cycles,
   2181   homogenization_position_from_liquid_surface=
   2182    homogenization_position_from_liquid_surface,
   2183   surface_following_distance_during_homogenization=
   2184    surface_following_distance_during_homogenization,
   2185   speed_of_homogenization=speed_of_homogenization,
   2186   channel_pattern=channel_pattern,
   2187   limit_curve_index=limit_curve_index,
   2188   tadm_algorithm=False,
   2189   recording_mode=0,
   2190 )

File ~/Documents/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/, in STAR.aspirate_core_96(self, aspiration_type, x_position, x_direction, y_positions, minimum_traverse_height_at_beginning_of_a_command, minimal_end_height, lld_search_height, liquid_surface_at_function_without_lld, pull_out_distance_to_take_transport_air_in_function_without_lld, maximum_immersion_depth, tube_2nd_section_height_measured_from_zm, tube_2nd_section_ratio, immersion_depth, immersion_depth_direction, liquid_surface_sink_distance_at_the_end_of_aspiration, aspiration_volumes, aspiration_speed, transport_air_volume, blow_out_air_volume, pre_wetting_volume, lld_mode, gamma_lld_sensitivity, swap_speed, settling_time, homogenization_volume, homogenization_cycles, homogenization_position_from_liquid_surface, surface_following_distance_during_homogenization, speed_of_homogenization, channel_pattern, limit_curve_index, tadm_algorithm, recording_mode)
   5023 channel_pattern_bin_str = reversed(["1" if x else "0" for x in channel_pattern])
   5024 channel_pattern_hex = hex(int("".join(channel_pattern_bin_str), 2)).upper()[2:]
-> 5026 return await self.send_command(
   5027   module="C0",
   5028   command="EA",
   5029   aa=aspiration_type,
   5030   xs=f"{x_position:05}",
   5031   xd=x_direction,
   5032   yh=f"{y_positions:04}",
   5033   zh=f"{minimum_traverse_height_at_beginning_of_a_command:04}",
   5034   ze=f"{minimal_end_height:04}",
   5035   lz=f"{lld_search_height:04}",
   5036   zt=f"{liquid_surface_at_function_without_lld:04}",
   5037   pp=f"{pull_out_distance_to_take_transport_air_in_function_without_lld:04}",
   5038   zm=f"{maximum_immersion_depth:04}",
   5039   zv=f"{tube_2nd_section_height_measured_from_zm:04}",
   5040   zq=f"{tube_2nd_section_ratio:05}",
   5041   iw=f"{immersion_depth:03}",
   5042   ix=immersion_depth_direction,
   5043   fh=f"{liquid_surface_sink_distance_at_the_end_of_aspiration:03}",
   5044   af=f"{aspiration_volumes:05}",
   5045   ag=f"{aspiration_speed:04}",
   5046   vt=f"{transport_air_volume:03}",
   5047   bv=f"{blow_out_air_volume:05}",
   5048   wv=f"{pre_wetting_volume:05}",
   5049   cm=lld_mode,
   5050   cs=gamma_lld_sensitivity,
   5051   bs=f"{swap_speed:04}",
   5052   wh=f"{settling_time:02}",
   5053   hv=f"{homogenization_volume:05}",
   5054   hc=f"{homogenization_cycles:02}",
   5055   hp=f"{homogenization_position_from_liquid_surface:03}",
   5056   mj=f"{surface_following_distance_during_homogenization:03}",
   5057   hs=f"{speed_of_homogenization:04}",
   5058   cw=channel_pattern_hex,
   5059   cr=f"{limit_curve_index:03}",
   5060   cj=tadm_algorithm,
   5061   cx=recording_mode,
   5062 )

File ~/Documents/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/, in HamiltonLiquidHandler.send_command(self, module, command, tip_pattern, write_timeout, read_timeout, wait, fmt, **kwargs)
    171 """ Send a firmware command to the Hamilton machine.
    173 Args:
    188   A dictionary containing the parsed response, or None if no response was read within `timeout`.
    189 """
    191 cmd, id_ = self._assemble_command(module=module, command=command, tip_pattern=tip_pattern,
    192   **kwargs)
--> 193 resp = await self._write_and_read_command(id_=id_, cmd=cmd, write_timeout=write_timeout,
    194                 read_timeout=read_timeout, wait=wait)
    195 if resp is not None and fmt is not None:
    196   return self._parse_response(resp, fmt)

File ~/Documents/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/, in HamiltonLiquidHandler._write_and_read_command(self, id_, cmd, write_timeout, read_timeout, wait)
    218 fut = loop.create_future()
    219 self._start_reading(id_, loop, fut, cmd, read_timeout)
--> 220 result = await fut
    221 return cast(str, result)

File ~/Documents/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/, in HamiltonLiquidHandler._continuously_read(self)
    291 if response_id == id_:
    292   try:
--> 293     self.check_fw_string_error(resp)
    294   except Exception as e: # pylint: disable=broad-exception-caught
    295     loop.call_soon_threadsafe(fut.set_exception, e)

File ~/Documents/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/, in STAR.check_fw_string_error(self, resp)
   1172   if error.message == "Unknown parameter":
   1173     # temp. disabled until we figure out how to handle async in parse response (the
   1174     # background thread does not have an event loop, and I'm not sure if it should.)
   1178     # pylint: disable=unnecessary-dict-index-lookup
   1179     he[module_name].message += " (call lh.backend.request_name_of_last_faulty_parameter)"
-> 1181 raise he

STARFirmwareError: STARFirmwareError(errors={'CoRe 96 Head': CommandSyntaxError('Parameter out of range')}, raw_response=C0EAid0007er99/00 H001/32)

Are you actually using lh.get_resource or is that just in the code above? I removed this a while ago (in favor of lh.deck.get_resource), so that would indicate you’re using a slightly outdated version. Could you confirm you’re running the latest version of PLR?

I am actually using that code, and it has been working. I last pulled the latest version like 5-7 days ago or so.

could you try updating? lh.get_resource should not work on the newest version.

I just did an update (I’m doing git pull -v to do that) and lh.get_resource and lh.deck.get_resource both work, at least to pick up tips.

They also work with each other, so I can do pick_up_tips with lh.get_resource and then drop_tips with lh.deck.get_resource and it works without issue.

FWIW, I also tried the above aspiration with lh.deck.get_resource and I get the same error.

are you pulling from a fork that is not synced?

what is git rev-parse HEAD

Here. you go: 7491b9556af1587f7c4ce9825b229c37648a6df1

With git pull -v I definitely see a bunch of changes each time I update, fwiw.

huh that is the latest commit rn. If you open do you still see def get_resource??

Please double check it’s imported in Python from where you expect:

import pylabrobot.liquid_handling.liquid_handler

That import gives me:

Which is what I expect (though it’s, not

And for the first question, no I do not find def get_resource in

so why does the code run with lh.get_resource?

I have no answer for that, unfortunately. Is there anything else I can look for or try that will help, or just explain more about my environment? Happy to do it, though I left the lab for the day and will be back at it tomorrow.

Here are my actual imports in the script (which are just straight copied from the example in the docs):

from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import STAR
from pylabrobot.resources.hamilton import STARLetDeck

and if I run your import command like this instead, I get the same answer:

from pylabrobot.liquid_handling import LiquidHandler

can you call with screen share tomorrow?

Yep, I can do that. Can be open most of the day.

I have a meeting 1-2pm ET. Wanna do 6p ET?

That works for me. I’ll send an invite.

1 Like

Actually, we obviously expect lh.get_resource exist now that it’s a Resource, so Jon’s environment was not outdated as I had thought.

The problem was that a firmware parameter on STAR.aspirate96 was wrong. Fixed with fix maximum_immersion_depth on STAR.aspirate96 · PyLabRobot/pylabrobot@2735e6b · GitHub.