Skip to content

Commit

Permalink
chore(merge): internal-release 0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sfoster1 committed Apr 3, 2023
2 parents 68d80dc + 430ed52 commit 3fd9292
Show file tree
Hide file tree
Showing 25 changed files with 466 additions and 189 deletions.
10 changes: 5 additions & 5 deletions api/src/opentrons/config/defaults_ot3.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
prep_distance_mm=4.0,
max_overrun_distance_mm=2.0,
speed_mm_per_s=1.0,
sensor_threshold_pf=4.0,
sensor_threshold_pf=3.0,
),
),
edge_sense=EdgeSenseSettings(
Expand All @@ -51,7 +51,7 @@
prep_distance_mm=1,
max_overrun_distance_mm=1,
speed_mm_per_s=0.5,
sensor_threshold_pf=4.0,
sensor_threshold_pf=3.0,
),
search_initial_tolerance_mm=8.0,
search_iteration_limit=9,
Expand All @@ -71,7 +71,7 @@
DEFAULT_RIGHT_MOUNT_OFFSET: Final[Offset] = (40.5, -60.5, 255.675)
DEFAULT_GRIPPER_MOUNT_OFFSET: Final[Offset] = (84.55, -12.75, 93.85)
DEFAULT_Z_RETRACT_DISTANCE: Final = 2
DEFAULT_GRIPPER_JAW_HOME_DUTY_CYCLE: Final = 25
DEFAULT_SAFE_HOME_DISTANCE: Final = 5

DEFAULT_MAX_SPEEDS: Final[ByGantryLoad[Dict[OT3AxisKind, float]]] = ByGantryLoad(
high_throughput={
Expand Down Expand Up @@ -379,8 +379,8 @@ def build_with_defaults(robot_settings: Dict[str, Any]) -> OT3Config:
z_retract_distance=robot_settings.get(
"z_retract_distance", DEFAULT_Z_RETRACT_DISTANCE
),
grip_jaw_home_duty_cycle=robot_settings.get(
"grip_jaw_home_duty_cycle", DEFAULT_GRIPPER_JAW_HOME_DUTY_CYCLE
safe_home_distance=robot_settings.get(
"safe_home_distance", DEFAULT_SAFE_HOME_DISTANCE
),
deck_transform=_build_default_transform(
robot_settings.get("deck_transform", []), DEFAULT_DECK_TRANSFORM
Expand Down
2 changes: 1 addition & 1 deletion api/src/opentrons/config/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ class OT3Config:
motion_settings: OT3MotionSettings
current_settings: OT3CurrentSettings
z_retract_distance: float
grip_jaw_home_duty_cycle: float
safe_home_distance: float
deck_transform: OT3Transform
carriage_offset: Offset
left_mount_offset: Offset
Expand Down
5 changes: 3 additions & 2 deletions api/src/opentrons/hardware_control/backends/ot3controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,9 @@ async def home(self, axes: Sequence[OT3Axis]) -> OT3AxisMap[float]:
A dictionary containing the new positions of each axis
"""
checked_axes = [axis for axis in axes if self._axis_is_present(axis)]
assert (
OT3Axis.G not in checked_axes
), "Please home G axis using gripper_home_jaw()"
if not checked_axes:
return {}

Expand All @@ -625,8 +628,6 @@ async def home(self, axes: Sequence[OT3Axis]) -> OT3AxisMap[float]:
if runner
]
positions = await asyncio.gather(*coros)
if OT3Axis.G in checked_axes:
await self.gripper_home_jaw(self._configuration.grip_jaw_home_duty_cycle)
if OT3Axis.Q in checked_axes:
await self.tip_action(
[OT3Axis.Q],
Expand Down
2 changes: 1 addition & 1 deletion api/src/opentrons/hardware_control/backends/ot3utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def node_id_to_subsystem(node_id: NodeId) -> "OT3SubSystem":
node_to_subsystem = {
node: subsystem for subsystem, node in SUBSYSTEM_NODEID.items()
}
return node_to_subsystem[node_id]
return node_to_subsystem[node_id.application_for()]


def get_current_settings(
Expand Down
57 changes: 23 additions & 34 deletions api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -728,12 +728,16 @@ async def home_gripper_jaw(self) -> None:
"""
Home the jaw of the gripper.
"""
gripper = self._gripper_handler.get_gripper()
dc = self._gripper_handler.get_duty_cycle_by_grip_force(
gripper.default_home_force
)
await self._ungrip(duty_cycle=dc)
gripper.state = GripperJawState.HOMED_READY
try:
gripper = self._gripper_handler.get_gripper()
self._log.info("Homing gripper jaw.")
dc = self._gripper_handler.get_duty_cycle_by_grip_force(
gripper.default_home_force
)
await self._ungrip(duty_cycle=dc)
gripper.state = GripperJawState.HOMED_READY
except GripperNotAttachedError:
pass

async def home_plunger(self, mount: Union[top_types.Mount, OT3Mount]) -> None:
"""
Expand Down Expand Up @@ -1166,28 +1170,22 @@ async def _retrieve_home_position() -> Tuple[
await self._update_position_estimation([axis])
origin, target_pos = await _retrieve_home_position()
if OT3Axis.to_kind(axis) in [OT3AxisKind.Z, OT3AxisKind.P]:
# we can move directly to the home position for accuracy axes
# move directly the home position if the stepper position is valid
axis_home_dist = self._config.safe_home_distance
else:
# FIXME: (AA 2/15/23) This is a temporary workaround because of
# XY encoder inaccuracy. Otherwise, we should be able to use
# 5.0 mm for all axes.
# Move to 20 mm away from the home position and then home
axis_home_dist = 20.0
if origin[axis] - target_pos[axis] > axis_home_dist:
target_pos[axis] += axis_home_dist
moves = self._build_moves(origin, target_pos)
await self._backend.move(
origin,
moves[0],
MoveStopCondition.none,
)
else:
if origin[axis] - target_pos[axis] > 20.0:
# FIXME: (AA 2/15/23) This is a temporary workaround because of
# XY encoder inaccuracy. We should remove this and move axes directly
# to the home position when we fix the encoder issues.
# Move to 20 mm away from the home position and then home
target_pos[axis] += 20.00
moves = self._build_moves(origin, target_pos)
await self._backend.move(
origin,
moves[0],
MoveStopCondition.none,
)
await self._backend.home([axis])
await self._backend.home([axis])
else:
# both stepper and encoder positions are invalid, must home
await self._backend.home([axis])
Expand All @@ -1197,8 +1195,9 @@ async def _home(self, axes: Sequence[OT3Axis]) -> None:
async with self._motion_lock:
for axis in axes:
try:
# let backend handle homing gripper jaw and pipette plunger
if axis in [OT3Axis.G, OT3Axis.Q]:
if axis == OT3Axis.G:
await self.home_gripper_jaw()
elif axis == OT3Axis.Q:
await self._backend.home([axis])
else:
await self._home_axis(axis)
Expand All @@ -1212,16 +1211,6 @@ async def _home(self, axes: Sequence[OT3Axis]) -> None:
else:
await self._cache_current_position()
await self._cache_encoder_position()
if axis == OT3Axis.G:
try:
self._gripper_handler.set_jaw_state(
GripperJawState.HOMED_READY
)
self._gripper_handler.set_jaw_displacement(
self._encoder_position[OT3Axis.G]
)
except GripperNotAttachedError:
pass

@ExecutionManagerProvider.wait_for_running
async def home(
Expand Down
2 changes: 1 addition & 1 deletion api/tests/opentrons/config/ot3_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
},
"log_level": "NADA",
"z_retract_distance": 10,
"grip_jaw_home_duty_cycle": 25,
"safe_home_distance": 5,
"deck_transform": [[-0.5, 0, 1], [0.1, -2, 4], [0, 0, -1]],
"carriage_offset": (1, 2, 3),
"right_mount_offset": (3, 2, 1),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,11 @@ def fw_update_info() -> Dict[NodeId, str]:

@pytest.fixture
def fw_node_info() -> Dict[NodeId, DeviceInfoCache]:
node_cache1 = DeviceInfoCache(NodeId.head, 1, "12345678", None, PCBARevision(None))
node_cache1 = DeviceInfoCache(
NodeId.head, 1, "12345678", None, PCBARevision(None), subidentifier=0
)
node_cache2 = DeviceInfoCache(
NodeId.gantry_x, 1, "12345678", None, PCBARevision(None)
NodeId.gantry_x, 1, "12345678", None, PCBARevision(None), subidentifier=0
)
return {NodeId.head: node_cache1, NodeId.gantry_x: node_cache2}

Expand Down
9 changes: 4 additions & 5 deletions api/tests/opentrons/hardware_control/test_moves.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,18 @@ def mock_home(ot3_hardware):


async def test_home(ot3_hardware, mock_home):
# this is only valid for Axis G, for other axes, check out test_ot3_api.py
with mock.patch("opentrons.hardware_control.ot3api.deck_from_machine") as dfm_mock:
dfm_mock.return_value = {OT3Axis.G: 20}
await ot3_hardware._home([OT3Axis.G])
dfm_mock.return_value = {OT3Axis.X: 20}
await ot3_hardware._home([OT3Axis.X])

mock_home.assert_called_once_with([OT3Axis.G])
mock_home.assert_called_once_with([OT3Axis.X])
assert dfm_mock.call_count == 2
dfm_mock.assert_called_with(
mock_home.return_value,
ot3_hardware._transforms.deck_calibration.attitude,
ot3_hardware._transforms.carriage_offset,
)
assert ot3_hardware._current_position[OT3Axis.G] == 20
assert ot3_hardware._current_position[OT3Axis.X] == 20


async def test_home_unmet(ot3_hardware, mock_home):
Expand Down
15 changes: 12 additions & 3 deletions api/tests/opentrons/hardware_control/test_ot3_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,10 +420,11 @@ async def test_liquid_probe(
fake_liquid_settings: LiquidProbeSettings,
mock_instrument_handlers: Tuple[Mock],
mock_current_position_ot3: AsyncMock,
mock_ungrip: AsyncMock,
mock_home_plunger: AsyncMock,
) -> None:
mock_ungrip.return_value = None
backend = ot3_hardware.managed_obj._backend

await ot3_hardware.home()
mock_move_to.return_value = None

Expand Down Expand Up @@ -490,9 +491,10 @@ async def test_liquid_sensing_errors(
mock_instrument_handlers: Tuple[Mock],
mock_current_position_ot3: AsyncMock,
mock_home_plunger: AsyncMock,
mock_ungrip: AsyncMock,
) -> None:
backend = ot3_hardware.managed_obj._backend

mock_ungrip.return_value = None
await ot3_hardware.home()
mock_move_to.return_value = None

Expand Down Expand Up @@ -787,6 +789,8 @@ async def test_gripper_action(
mock_ungrip.assert_called_once()
mock_ungrip.reset_mock()
await ot3_hardware.home([OT3Axis.G])
mock_ungrip.assert_called_once()
mock_ungrip.reset_mock()
await ot3_hardware.grip(5.0)
mock_grip.assert_called_once_with(
gc.duty_cycle_by_force(5.0, gripper_config.grip_force_profile),
Expand Down Expand Up @@ -956,7 +960,9 @@ async def test_save_instrument_offset(
async def test_pick_up_tip_full_tiprack(
ot3_hardware: ThreadManager[OT3API],
mock_instrument_handlers: Tuple[Mock],
mock_ungrip: AsyncMock,
) -> None:
mock_ungrip.return_value = None
await ot3_hardware.home()
_, pipette_handler = mock_instrument_handlers
backend = ot3_hardware.managed_obj._backend
Expand Down Expand Up @@ -1162,7 +1168,10 @@ async def test_home_axis(
if axis in [OT3Axis.Z_L, OT3Axis.P_L]:
# move is called
mock_backend_move.assert_awaited_once()
mock_backend_home.assert_not_awaited()
move = mock_backend_move.call_args_list[0][0][1][0]
assert move.distance == 95.0
# then home is called
mock_backend_home.assert_awaited_once()
else:
# we move to 20 mm away from home
mock_backend_move.assert_awaited_once()
Expand Down
29 changes: 29 additions & 0 deletions hardware/opentrons_hardware/firmware_bindings/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,27 @@ class NodeId(int, Enum):
head_bootloader = head | 0xF
gripper_bootloader = gripper | 0xF

def is_bootloader(self) -> bool:
"""Whether this node ID is a bootloader."""
return bool(self.value & 0xF == 0xF)

def bootloader_for(self) -> "NodeId":
"""The associated bootloader node ID for the node.
This is safe to call on any node id, including ones that are already bootloaders.
"""
return NodeId(self.value | 0xF)

def application_for(self) -> "NodeId":
"""The associated core node ID for the node (i.e. head, not head_l).
This is safe to call on any node ID, including non-core application node IDs like
head_l. It will always give the code node ID.
"""
# in c this would be & ~0xf but in python that gives 0x10 for some reason
# so let's write out the whole byte
return NodeId(self.value & 0xF0)


# make these negative numbers so there is no chance they overlap with NodeId
@unique
Expand All @@ -39,6 +60,14 @@ class USBTarget(int, Enum):

rear_panel = -1

def is_bootloader(self) -> bool:
"""Whether this is a bootloader id (always false)."""
return False

def application_for(self) -> "USBTarget":
"""The corresponding application id."""
return self


FirmwareTarget = Union[NodeId, USBTarget]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class DeviceInfoResponse(utils.BinarySerializable):
flags: VersionFlagsField = VersionFlagsField(0)
shortsha: FirmwareShortSHADataField = FirmwareShortSHADataField(bytes())
revision: OptionalRevisionField = OptionalRevisionField("", "", "")
subidentifier: utils.UInt8Field = utils.UInt8Field(0)

@classmethod
def build(cls, data: bytes) -> "DeviceInfoResponse":
Expand Down Expand Up @@ -102,8 +103,16 @@ def build(cls, data: bytes) -> "DeviceInfoResponse":

revision = OptionalRevisionField.build(data[data_iter:])

data_iter = data_iter + revision.NUM_BYTES
try:
subidentifier = utils.UInt8Field.build(
int.from_bytes(data[data_iter : data_iter + 1], "big")
)
except IndexError:
subidentifier = utils.UInt8Field.build(0)

return DeviceInfoResponse(
message_id, length, version, flags, shortsha, revision
message_id, length, version, flags, shortsha, revision, subidentifier
)


Expand Down
18 changes: 16 additions & 2 deletions hardware/opentrons_hardware/firmware_bindings/messages/payloads.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# from __future__ import annotations
from dataclasses import dataclass, field, asdict
from . import message_definitions
from typing import Iterator

from .fields import (
FirmwareShortSHADataField,
Expand Down Expand Up @@ -83,16 +84,29 @@ def build(cls, data: bytes) -> "DeviceInfoResponsePayload":
consumed_by_super = _DeviceInfoResponsePayloadBase.get_size()
superdict = asdict(_DeviceInfoResponsePayloadBase.build(data))
message_index = superdict.pop("message_index")

# we want to parse this by adding extra 0s that may not be necessary,
# which is annoying and complex, so let's wrap it in an iterator
def _data_for_optionals(consumed: int, buf: bytes) -> Iterator[bytes]:
extended = buf + b"\x00\x00\x00\x00"
yield extended[consumed:]
consumed += 4
extended = extended + b"\x00"
yield extended[consumed : consumed + 1]

optionals_yielder = _data_for_optionals(consumed_by_super, data)
inst = cls(
**superdict,
revision=OptionalRevisionField.build(
(data + b"\x00\x00\x00\x00")[consumed_by_super:]
revision=OptionalRevisionField.build(next(optionals_yielder)),
subidentifier=utils.UInt8Field.build(
int.from_bytes(next(optionals_yielder), "big")
),
)
inst.message_index = message_index
return inst

revision: OptionalRevisionField
subidentifier: utils.UInt8Field


@dataclass(eq=False)
Expand Down
4 changes: 2 additions & 2 deletions hardware/opentrons_hardware/firmware_update/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ async def _run_can_update(
initiator = FirmwareUpdateInitiator(messenger)
downloader = FirmwareUpdateDownloader(messenger)

target = Target(system_node=node_id)
target = Target.from_single_node(node_id)

logger.info(f"Initiating FW Update on {target}.")
await self._status_queue.put((node_id, (FirmwareUpdateStatus.updating, 0)))
Expand Down Expand Up @@ -297,7 +297,7 @@ async def _run_can_update(
else:
logger.info("Skipping erase step.")

logger.info(f"Downloading FW to {target.bootloader_node}.")
logger.info(f"Downloading {filepath} to {target.bootloader_node}.")
with open(filepath) as f:
hex_processor = HexRecordProcessor.from_file(f)
async for download_progress in downloader.run(
Expand Down
Loading

0 comments on commit 3fd9292

Please sign in to comment.