Skip to content

Commit

Permalink
feat(hardware): pass move ack id as part of the move complete info (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
ahiuchingau authored Sep 29, 2023
1 parent c46b394 commit 12af8ab
Show file tree
Hide file tree
Showing 22 changed files with 223 additions and 177 deletions.
30 changes: 18 additions & 12 deletions api/src/opentrons/hardware_control/backends/ot3controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,11 @@
MoveStopCondition,
MoveGroup,
)
from opentrons_hardware.hardware_control.types import NodeMap
from opentrons_hardware.hardware_control.types import (
NodeMap,
MotorPositionStatus,
MoveCompleteAck,
)
from opentrons_hardware.hardware_control.tools import types as ohc_tool_types

from opentrons_hardware.hardware_control.tool_sensors import (
Expand Down Expand Up @@ -487,11 +491,11 @@ async def update_encoder_position(self) -> OT3AxisMap[float]:

def _handle_motor_status_response(
self,
response: NodeMap[Tuple[float, float, bool, bool]],
response: NodeMap[MotorPositionStatus],
) -> None:
for axis, pos in response.items():
self._position.update({axis: pos[0]})
self._encoder_position.update({axis: pos[1]})
self._position.update({axis: pos.motor_position})
self._encoder_position.update({axis: pos.encoder_position})
# TODO (FPS 6-01-2023): Remove this once the Feature Flag to ignore stall detection is removed.
# This check will latch the motor status for an axis at "true" if it was ever set to true.
# To account for the case where a motor axis has its power reset, we also depend on the
Expand All @@ -505,7 +509,8 @@ def _handle_motor_status_response(
self._motor_status.update(
{
axis: MotorStatus(
motor_ok=(pos[2] or motor_ok_latch), encoder_ok=pos[3]
motor_ok=(pos.motor_ok or motor_ok_latch),
encoder_ok=pos.encoder_ok,
)
}
)
Expand Down Expand Up @@ -687,7 +692,7 @@ async def home_tip_motors(
positions = await runner.run(can_messenger=self._messenger)
if NodeId.pipette_left in positions:
self._gear_motor_position = {
NodeId.pipette_left: positions[NodeId.pipette_left][0]
NodeId.pipette_left: positions[NodeId.pipette_left].motor_position
}
else:
log.debug("no position returned from NodeId.pipette_left")
Expand All @@ -705,7 +710,7 @@ async def tip_action(
positions = await runner.run(can_messenger=self._messenger)
if NodeId.pipette_left in positions:
self._gear_motor_position = {
NodeId.pipette_left: positions[NodeId.pipette_left][0]
NodeId.pipette_left: positions[NodeId.pipette_left].motor_position
}
else:
log.debug("no position returned from NodeId.pipette_left")
Expand Down Expand Up @@ -1168,8 +1173,8 @@ async def liquid_probe(
sensor_id_for_instrument(probe),
)
for node, point in positions.items():
self._position.update({node: point[0]})
self._encoder_position.update({node: point[1]})
self._position.update({node: point.motor_position})
self._encoder_position.update({node: point.encoder_position})
return self._position

async def capacitive_probe(
Expand All @@ -1180,8 +1185,8 @@ async def capacitive_probe(
speed_mm_per_s: float,
sensor_threshold_pf: float,
probe: InstrumentProbeType,
) -> None:
pos, _ = await capacitive_probe(
) -> bool:
status = await capacitive_probe(
self._messenger,
sensor_node_for_mount(mount),
axis_to_node(moving),
Expand All @@ -1191,7 +1196,8 @@ async def capacitive_probe(
relative_threshold_pf=sensor_threshold_pf,
)

self._position[axis_to_node(moving)] = pos
self._position[axis_to_node(moving)] = status.motor_position
return status.move_ack == MoveCompleteAck.stopped_by_condition

async def capacitive_pass(
self,
Expand Down
3 changes: 2 additions & 1 deletion api/src/opentrons/hardware_control/backends/ot3simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,8 +697,9 @@ async def capacitive_probe(
speed_mm_per_s: float,
sensor_threshold_pf: float,
probe: InstrumentProbeType,
) -> None:
) -> bool:
self._position[axis_to_node(moving)] += distance_mm
return True

@ensure_yield
async def capacitive_pass(
Expand Down
32 changes: 14 additions & 18 deletions api/src/opentrons/hardware_control/ot3_calibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import datetime
import numpy as np
from enum import Enum
from math import floor, copysign, isclose
from math import floor, copysign
from logging import getLogger
from opentrons.util.linal import solve_attitude, SolvePoints

Expand Down Expand Up @@ -112,18 +112,14 @@ class AlignmentShift(Enum):
}


def _deck_hit(
def _verify_height(
found_pos: float, expected_pos: float, settings: EdgeSenseSettings
) -> bool:
) -> None:
"""
Evaluate the height found by capacitive probe against search settings
to determine whether or not it had hit the deck.
Evaluate the height found by capacitive probe against search settings.
"""
if found_pos > expected_pos + settings.early_sense_tolerance_mm:
raise EarlyCapacitiveSenseTrigger(found_pos, expected_pos)
return (
True if found_pos >= (expected_pos - settings.overrun_tolerance_mm) else False
)


async def _verify_edge_pos(
Expand Down Expand Up @@ -153,10 +149,10 @@ async def _verify_edge_pos(
LOG.info(
f"Checking {edge_name_str} in {dir} direction at {checking_pos}, stride_size: {check_stride}"
)
height = await _probe_deck_at(
height, hit_deck = await _probe_deck_at(
hcapi, mount, checking_pos, edge_settings.pass_settings, probe=probe
)
hit_deck = _deck_hit(height, found_edge.z, edge_settings)
_verify_height(height, found_edge.z, edge_settings)
LOG.info(f"Deck {'hit' if hit_deck else 'miss'} at check pos: {checking_pos}")
if last_result is not None and hit_deck != last_result:
LOG.info(
Expand Down Expand Up @@ -222,10 +218,10 @@ async def find_edge_binary(
final_z_height_found = slot_edge_nominal.z
for _ in range(edge_settings.search_iteration_limit):
LOG.info(f"Checking position {checking_pos}")
interaction_pos = await _probe_deck_at(
interaction_pos, hit_deck = await _probe_deck_at(
hcapi, mount, checking_pos, edge_settings.pass_settings, probe=probe
)
hit_deck = _deck_hit(interaction_pos, checking_pos.z, edge_settings)
_verify_height(interaction_pos, checking_pos.z, edge_settings)
if hit_deck:
# In this block, we've hit the deck
LOG.info(f"hit at {interaction_pos}, stride size: {stride}")
Expand Down Expand Up @@ -358,11 +354,11 @@ async def find_calibration_structure_height(
"""
z_pass_settings = hcapi.config.calibration.z_offset.pass_settings
z_prep_point = nominal_center + PREP_OFFSET_DEPTH
structure_z = await _probe_deck_at(
z_limit = nominal_center.z - z_pass_settings.max_overrun_distance_mm
structure_z, hit_deck = await _probe_deck_at(
hcapi, mount, z_prep_point, z_pass_settings, probe=probe
)
z_limit = nominal_center.z - z_pass_settings.max_overrun_distance_mm
if (structure_z < z_limit) or isclose(z_limit, structure_z, abs_tol=0.001):
if not hit_deck:
raise CalibrationStructureNotFoundError(structure_z, z_limit)
LOG.info(f"autocalibration: found structure at {structure_z}")
return structure_z
Expand All @@ -375,7 +371,7 @@ async def _probe_deck_at(
settings: CapacitivePassSettings,
speed: float = 50,
probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,
) -> float:
) -> Tuple[float, bool]:
here = await api.gantry_position(mount)
abs_transit_height = max(
target.z + LINEAR_TRANSIT_HEIGHT, target.z + settings.prep_distance_mm
Expand All @@ -384,13 +380,13 @@ async def _probe_deck_at(
await api.move_to(mount, here._replace(z=safe_height))
await api.move_to(mount, target._replace(z=safe_height), speed=speed)
await api.move_to(mount, target._replace(z=abs_transit_height))
_found_pos = await api.capacitive_probe(
_found_pos, contact = await api.capacitive_probe(
mount, Axis.by_mount(mount), target.z, settings, probe=probe
)
# don't use found Z position to calculate an updated transit height
# because the probe may have gone through the hole
await api.move_to(mount, target._replace(z=abs_transit_height))
return _found_pos
return _found_pos, contact


async def find_axis_center(
Expand Down
6 changes: 3 additions & 3 deletions api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2357,7 +2357,7 @@ async def capacitive_probe(
pass_settings: CapacitivePassSettings,
retract_after: bool = True,
probe: Optional[InstrumentProbeType] = None,
) -> float:
) -> Tuple[float, bool]:
"""Determine the position of something using the capacitive sensor.
This function orchestrates detecting the position of a collision between the
Expand Down Expand Up @@ -2418,7 +2418,7 @@ async def capacitive_probe(
else:
# default to primary (rear) probe
probe = InstrumentProbeType.PRIMARY
await self._backend.capacitive_probe(
contact = await self._backend.capacitive_probe(
mount,
moving_axis,
machine_pass_distance,
Expand All @@ -2429,7 +2429,7 @@ async def capacitive_probe(
end_pos = await self.gantry_position(mount, refresh=True)
if retract_after:
await self.move_to(mount, pass_start_pos)
return moving_axis.of_point(end_pos)
return moving_axis.of_point(end_pos), contact

async def capacitive_sweep(
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@
MoveStopCondition,
MoveGroupSingleAxisStep,
)
from opentrons_hardware.hardware_control.types import PCBARevision
from opentrons_hardware.hardware_control.types import (
PCBARevision,
MotorPositionStatus,
MoveCompleteAck,
)
from opentrons_hardware.hardware_control import current_settings
from opentrons_hardware.hardware_control.network import DeviceInfoCache
from opentrons_hardware.hardware_control.tools.types import (
Expand Down Expand Up @@ -310,19 +314,19 @@ def fw_node_info() -> Dict[NodeId, DeviceInfoCache]:

def move_group_run_side_effect(
controller: OT3Controller, axes_to_home: List[Axis]
) -> Iterator[Dict[NodeId, Tuple[float, float, bool, bool]]]:
) -> Iterator[Dict[NodeId, MotorPositionStatus]]:
"""Return homed position for axis that is present and was commanded to home."""
motor_nodes = controller._motor_nodes()
gantry_homes = {
axis_to_node(ax): (0.0, 0.0, True, True)
axis_to_node(ax): MotorPositionStatus(0.0, 0.0, True, True, MoveCompleteAck(1))
for ax in Axis.gantry_axes()
if ax in axes_to_home and axis_to_node(ax) in motor_nodes
}
if gantry_homes:
yield gantry_homes

pipette_homes = {
axis_to_node(ax): (0.0, 0.0, True, True)
axis_to_node(ax): MotorPositionStatus(0.0, 0.0, True, True, MoveCompleteAck(1))
for ax in Axis.pipette_axes()
if ax in axes_to_home and axis_to_node(ax) in motor_nodes
}
Expand Down Expand Up @@ -734,8 +738,10 @@ async def test_update_motor_status(
) -> None:
async def fake_gmp(
can_messenger: CanMessenger, nodes: Set[NodeId], timeout: float = 1.0
) -> Dict[NodeId, Tuple[float, float, bool, bool]]:
return {node: (0.223, 0.323, False, True) for node in nodes}
) -> Dict[NodeId, MotorPositionStatus]:
return {
node: MotorPositionStatus(0.223, 0.323, False, True, None) for node in nodes
}

with mock.patch(
"opentrons.hardware_control.backends.ot3controller.get_motor_position", fake_gmp
Expand All @@ -757,8 +763,10 @@ async def test_update_motor_estimation(
) -> None:
async def fake_umpe(
can_messenger: CanMessenger, nodes: Set[NodeId], timeout: float = 1.0
) -> Dict[NodeId, Tuple[float, float, bool, bool]]:
return {node: (0.223, 0.323, False, True) for node in nodes}
) -> Dict[NodeId, MotorPositionStatus]:
return {
node: MotorPositionStatus(0.223, 0.323, False, True, None) for node in nodes
}

with mock.patch(
"opentrons.hardware_control.backends.ot3controller.update_motor_position_estimation",
Expand Down
2 changes: 1 addition & 1 deletion api/tests/opentrons/hardware_control/test_ot3_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,7 @@ async def test_capacitive_probe(
) -> None:
await ot3_hardware.home()
here = await ot3_hardware.gantry_position(mount)
res = await ot3_hardware.capacitive_probe(mount, moving, 2, fake_settings)
res, _ = await ot3_hardware.capacitive_probe(mount, moving, 2, fake_settings)
# in reality, this value would be the previous position + the value
# updated in ot3controller.capacitive_probe, and it kind of is here, but that
# previous position is always 0. This is a test of ot3api though and checking
Expand Down
27 changes: 16 additions & 11 deletions api/tests/opentrons/hardware_control/test_ot3_calibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,19 +142,24 @@ def _other_axis_val(point: Tuple[float, float, float], main_axis: Axis) -> float
raise KeyError(main_axis)


# Mock Capacitive Probe Result (found_position[float], hit_deck[bool])
_HIT = (1, True)
_MISS = (-1, False)


@pytest.mark.parametrize(
"search_axis,direction_if_hit,probe_results,search_result",
[
# For each axis and direction, test
# 1. hit-miss-miss
# 2. miss-hit-hit
# 3. miss-hit-miss
(Axis.X, -1, (1, -1, -1), -1),
(Axis.X, -1, (-1, 1, 1), 1),
(Axis.X, -1, (-1, 1, -1), 3),
(Axis.X, 1, (1, -1, -1), 1),
(Axis.X, 1, (-1, 1, 1), -1),
(Axis.X, 1, (-1, 1, -1), -3),
(Axis.X, -1, (_HIT, _MISS, _MISS), -1),
(Axis.X, -1, (_MISS, _HIT, _HIT), 1),
(Axis.X, -1, (_MISS, _HIT, _MISS), 3),
(Axis.X, 1, (_HIT, _MISS, _MISS), 1),
(Axis.X, 1, (_MISS, _HIT, _HIT), -1),
(Axis.X, 1, (_MISS, _HIT, _MISS), -3),
],
)
async def test_find_edge(
Expand Down Expand Up @@ -192,8 +197,8 @@ async def test_find_edge(
@pytest.mark.parametrize(
"search_axis,direction_if_hit,probe_results",
[
(Axis.X, -1, (1, 1)),
(Axis.Y, -1, (-1, -1)),
(Axis.X, -1, (_HIT, _HIT)),
(Axis.Y, -1, (_MISS, _MISS)),
],
)
async def test_edge_not_found(
Expand Down Expand Up @@ -224,7 +229,7 @@ async def test_find_edge_early_trigger(
override_cal_config: None,
) -> None:
await ot3_hardware.home()
mock_capacitive_probe.side_effect = (3,)
mock_capacitive_probe.side_effect = ((3, True), ())
with pytest.raises(EarlyCapacitiveSenseTrigger):
await find_edge_binary(
ot3_hardware,
Expand All @@ -241,7 +246,7 @@ async def test_deck_not_found(
override_cal_config: None,
) -> None:
await ot3_hardware.home()
mock_capacitive_probe.side_effect = (-25,)
mock_capacitive_probe.side_effect = ((-25, False), ())
with pytest.raises(CalibrationStructureNotFoundError):
await find_calibration_structure_height(
ot3_hardware,
Expand All @@ -263,7 +268,7 @@ async def test_find_deck_checks_z_only(
) -> None:
await ot3_hardware.home()
here = await ot3_hardware.gantry_position(mount)
mock_capacitive_probe.side_effect = (-1.8,)
mock_capacitive_probe.side_effect = ((-1.8, True),)
await find_calibration_structure_height(ot3_hardware, mount, target)

z_prep_loc = target + PREP_OFFSET_DEPTH
Expand Down
Loading

0 comments on commit 12af8ab

Please sign in to comment.