Skip to content
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

refactor(api): tiplength from tiprack #2637

Merged
merged 6 commits into from
Nov 7, 2018
Merged
Show file tree
Hide file tree
Changes from 5 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
72 changes: 40 additions & 32 deletions api/src/opentrons/hardware_control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,6 @@ class MustHomeError(RuntimeError):
pass


class PipetteNotAttachedError(KeyError):
pass


_Backend = Union[Controller, Simulator]
Instruments = Dict[top_types.Mount, Optional[Pipette]]
SHAKE_OFF_TIPS_SPEED = 50
Expand Down Expand Up @@ -192,7 +188,7 @@ async def cache_instruments(self,

@property
def attached_instruments(self):
configs = ['name', 'min_volume', 'max_volume',
configs = ['name', 'min_volume', 'max_volume', 'channels',
'aspirate_flow_rate', 'dispense_flow_rate',
'pipette_id', 'current_volume', 'display_name']
instruments = {top_types.Mount.LEFT: {},
Expand Down Expand Up @@ -233,6 +229,29 @@ async def home_z(self):
""" Home the two z-axes """
await self.home([Axis.Z, Axis.A])

@_log_call
async def home_plunger(self, mount: top_types.Mount):
btmorr marked this conversation as resolved.
Show resolved Hide resolved
"""
Home the plunger motor for a mount, and then return it to the 'bottom'
position.

:param mount: the mount associated with the target plunger
:type mount: :py:class:`.top_types.Mount`
"""
# incase plunger motor stalled while dropping a tip, add a
# safety margin of the distance between `bottom` and `drop_tip`
instr = self._attached_instruments[mount]
if instr:
b = instr.config.plunger_positions['bottom']
d = instr.config.plunger_positions['drop_tip']
safety_margin = abs(b-d)
self._backend.set_active_current(Axis.of_plunger(mount),
instr.config.plunger_current)
await self._move_plunger(mount, safety_margin)
await self.home([Axis.of_plunger(mount)])
await self._move_plunger(mount,
instr.config.plunger_positions['bottom'])

@_log_call
async def home(self, axes: List[Axis] = None):
""" Home the entire robot and initialize current position.
Expand Down Expand Up @@ -565,8 +584,8 @@ async def aspirate(self, mount: top_types.Mount, volume: float = None,
"""
this_pipette = self._attached_instruments[mount]
if not this_pipette:
raise PipetteNotAttachedError("No pipette attached to {} mount"
.format(mount.name))
raise top_types.PipetteNotAttachedError(
"No pipette attached to {} mount".format(mount.name))
if volume is None:
asp_vol = this_pipette.available_volume
mod_log.debug(
Expand Down Expand Up @@ -611,8 +630,8 @@ async def dispense(self, mount: top_types.Mount, volume: float = None,
"""
this_pipette = self._attached_instruments[mount]
if not this_pipette:
raise PipetteNotAttachedError("No pipette attached to {} mount"
.format(mount.name))
raise top_types.PipetteNotAttachedError(
"No pipette attached to {} mount".format(mount.name))
if volume is None:
disp_vol = this_pipette.current_volume
mod_log.debug("No dispense volume specified. Dispensing all "
Expand Down Expand Up @@ -656,8 +675,8 @@ async def blow_out(self, mount):
"""
this_pipette = self._attached_instruments[mount]
if not this_pipette:
raise PipetteNotAttachedError("No pipette attached to {} mount"
.format(mount.name))
raise top_types.PipetteNotAttachedError(
"No pipette attached to {} mount".format(mount.name))

self._backend.set_active_current(Axis.of_plunger(mount),
this_pipette.config.plunger_current)
Expand All @@ -671,7 +690,11 @@ async def blow_out(self, mount):
this_pipette.set_current_volume(0)

@_log_call
async def pick_up_tip(self, mount, presses: int = 3, increment: float = 1):
async def pick_up_tip(self,
mount,
tip_length: float,
presses: int = 3,
increment: float = 1):
"""
Pick up tip from current location
"""
Expand Down Expand Up @@ -700,7 +723,7 @@ async def pick_up_tip(self, mount, presses: int = 3, increment: float = 1):
# move nozzle back up
backup_pos = top_types.Point(0, 0, -dist)
await self.move_rel(mount, backup_pos)
instr.add_tip()
instr.add_tip(tip_length=tip_length)
instr.set_current_volume(0)

# neighboring tips tend to get stuck in the space between
Expand Down Expand Up @@ -731,7 +754,6 @@ async def drop_tip(self, mount):
await self._move_plunger(mount,
instr.config.plunger_positions['drop_tip'])
await self._shake_off_tips(mount)
await self._home_plunger_after_drop_tip(mount)
instr.set_current_volume(0)
instr.remove_tip()

Expand All @@ -752,20 +774,6 @@ async def _shake_off_tips(self, mount):
up_pos = top_types.Point(0, 0, DROP_TIP_RELEASE_DISTANCE)
await self.move_rel(mount, up_pos)

async def _home_plunger_after_drop_tip(self, mount):
# incase plunger motor stalled while dropping a tip, add a
# safety margin of the distance between `bottom` and `drop_tip`
instr = self._attached_instruments[mount]
b = instr.config.plunger_positions['bottom']
d = instr.config.plunger_positions['drop_tip']
safety_margin = abs(b-d)
self._backend.set_active_current(Axis.of_plunger(mount),
instr.config.plunger_current)
await self._move_plunger(mount, safety_margin)
await self.home([Axis.of_plunger(mount)])
await self._move_plunger(mount,
instr.config.plunger_positions['bottom'])

# Pipette config api
@_log_call
def calibrate_plunger(self,
Expand All @@ -785,8 +793,8 @@ def calibrate_plunger(self,
"""
instr = self._attached_instruments[mount]
if not instr:
raise PipetteNotAttachedError("No pipette attached to {} mount"
.format(mount.name))
raise top_types.PipetteNotAttachedError(
"No pipette attached to {} mount".format(mount.name))

pos_dict: Dict = instr.config.plunger_positions
if top is not None:
Expand All @@ -803,8 +811,8 @@ def calibrate_plunger(self,
def set_flow_rate(self, mount, aspirate=None, dispense=None):
this_pipette = self._attached_instruments[mount]
if not this_pipette:
raise PipetteNotAttachedError("No pipette attached to {} mount"
.format(mount))
raise top_types.PipetteNotAttachedError(
"No pipette attached to {} mount".format(mount))
if aspirate:
this_pipette.update_config_item('aspirate_flow_rate', aspirate)
if dispense:
Expand Down
45 changes: 32 additions & 13 deletions api/src/opentrons/hardware_control/pipette.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def __init__(self, model: str, pipette_id: str = None) -> None:
self._config = pipette_config.load(model)
self._name = model
self._current_volume = 0.0
self._has_tip = False
self._current_tip_length = 0.0
self._pipette_id = pipette_id

@property
Expand All @@ -37,19 +37,25 @@ def pipette_id(self) -> Optional[str]:

@property
def critical_point(self) -> Point:
""" The vector from the pipette's origin to its critical point """
if not self.has_tip:
return Point(*self.config.model_offset)
else:
return Point(self.config.model_offset[0],
self.config.model_offset[1],
self.config.model_offset[2] - self.config.tip_length)
"""
The vector from the pipette's origin to its critical point. The
critical point for a pipette is the end of the nozzle if no tip is
attached, or the end of the tip if a tip is attached.
"""
return Point(self.config.model_offset[0],
self.config.model_offset[1],
self.config.model_offset[2] - self.current_tip_length)

@property
def current_volume(self) -> float:
""" The amount of liquid currently aspirated """
return self._current_volume

@property
def current_tip_length(self) -> float:
""" The length of the current tip attached (0.0 if no tip) """
return self._current_tip_length

@property
def available_volume(self) -> float:
""" The amount of liquid possible to aspirate """
Expand All @@ -71,17 +77,30 @@ def remove_current_volume(self, volume_incr: float):
def ok_to_add_volume(self, volume_incr: float) -> bool:
return self.current_volume + volume_incr <= self.config.max_volume

def add_tip(self):
def add_tip(self, tip_length) -> None:
"""
Add a tip to the pipette for position tracking and validation
(effectively updates the pipette's critical point)

:param tip_length: a positive, non-zero float representing the distance
in Z from the end of the pipette nozzle to the end of the tip
:return:
"""
assert tip_length > 0.0, "tip_length must be greater than 0"
assert not self.has_tip
self._has_tip = True
self._current_tip_length = tip_length

def remove_tip(self):
def remove_tip(self) -> None:
"""
Remove the tip from the pipette (effectively updates the pipette's
critical point)
"""
assert self.has_tip
self._has_tip = False
self._current_tip_length = 0.0

@property
def has_tip(self) -> bool:
return self._has_tip
return self.current_tip_length != 0.0

def ul_per_mm(self, ul: float, action: str) -> float:
sequence = self._config.ul_per_mm[action]
Expand Down
Loading