Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -462,3 +462,7 @@ async def set_hepa_uv_state(self, light_on: bool, uv_duration_s: int) -> bool:

async def get_hepa_uv_state(self) -> Optional[HepaUVState]:
...

async def increase_evo_disp_count(self, mount: OT3Mount) -> None:
"""Tell a pipette to increase it's evo-tip-dispense-count in eeprom."""
...
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@
from .flex_protocol import FlexBackend
from .status_bar_state import StatusBarStateController
from opentrons_hardware.sensors.types import SensorDataType
from opentrons_hardware.sensors.utils import send_evo_dispense_count_increase

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -1828,3 +1829,9 @@ def _update_tip_state(self, mount: OT3Mount, status: bool) -> None:
"""This is something we only use in the simulator.
It is required so that PE simulations using ot3api don't break."""
pass

async def increase_evo_disp_count(self, mount: OT3Mount) -> None:
"""Tell a pipette to increase it's evo-tip-dispense-count in eeprom."""
await send_evo_dispense_count_increase(
self._messenger, sensor_node_for_pipette(OT3Mount(mount.value))
)
3 changes: 3 additions & 0 deletions api/src/opentrons/hardware_control/backends/ot3simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -871,3 +871,6 @@ def _update_tip_state(self, mount: OT3Mount, status: bool) -> None:
"""This is something we only use in the simulator.
It is required so that PE simulations using ot3api don't break."""
self._sim_tip_state[mount] = status

async def increase_evo_disp_count(self, mount: OT3Mount) -> None:
pass
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Coordinate subsystem detection and updates.
"""

import asyncio
from contextlib import contextmanager, ExitStack
from dataclasses import dataclass
Expand Down Expand Up @@ -390,11 +391,12 @@ async def _tool_detection_task_protected(self) -> None:
tool_nodes.add(NodeId.pipette_right)
if update.gripper != ToolType.nothing_attached:
tool_nodes.add(NodeId.gripper)
base_nodes: Set[FirmwareTarget] = {
base_can_nodes: List[NodeId] = [
NodeId.pipette_left,
NodeId.pipette_right,
NodeId.gripper,
}
]
base_nodes: Set[FirmwareTarget] = {n for n in base_can_nodes}
try:
self._network_info.mark_absent(base_nodes - tool_nodes)
await self._probe_network_and_cache_fw_updates(
Expand All @@ -412,6 +414,9 @@ async def _tool_detection_task_protected(self) -> None:
)
self._present_tools = await self._tool_detector.resolve(to_resolve, 10.0)
log.info(f"Present tools are now {self._present_tools}")
await network.log_motor_usage_data(
self._can_messenger, list(set(base_can_nodes + [t for t in tool_nodes]))
)
async with self._tool_task_condition:
self._tool_task_state = True
self._tool_task_condition.notify_all()
Expand Down
16 changes: 13 additions & 3 deletions api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,9 +452,11 @@ async def build_hardware_simulator(
checked_config = config

backend = await OT3Simulator.build(
{OT3Mount.from_mount(k): v for k, v in attached_instruments.items()}
if attached_instruments
else {},
(
{OT3Mount.from_mount(k): v for k, v in attached_instruments.items()}
if attached_instruments
else {}
),
checked_modules,
checked_config,
checked_loop,
Expand Down Expand Up @@ -3130,3 +3132,11 @@ async def set_hepa_uv_state(

async def get_hepa_uv_state(self) -> Optional[HepaUVState]:
return await self._backend.get_hepa_uv_state()

async def increase_evo_disp_count(
self,
mount: Union[top_types.Mount, OT3Mount],
) -> None:
"""Tell a pipette to increase its evo-tip-dispense-count in eeprom."""
realmount = OT3Mount.from_mount(mount)
await self._backend.increase_evo_disp_count(realmount)
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,7 @@ async def liquid_probe(
max_z_dist : maximum depth to probe for liquid
"""
...

async def increase_evo_disp_count(self, mount: MountArgType) -> None:
"""Tell a pipette to increase it's evo-tip-dispense-count in eeprom."""
...
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
DispenseVolumeMixin,
BaseLiquidHandlingResult,
dispense_in_place,
increase_evo_disp_count,
DEFAULT_CORRECTION_VOLUME,
)
from .movement_common import (
Expand Down Expand Up @@ -102,6 +103,9 @@ async def execute(self, params: EvotipDispenseParams) -> _ExecuteReturn:
return move_result

current_position = await self._gantry_mover.get_position(params.pipetteId)
await increase_evo_disp_count(
pipette_id=params.pipetteId, pipetting=self._pipetting
)
result = await dispense_in_place(
pipette_id=params.pipetteId,
volume=params.volume,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -432,3 +432,8 @@ async def blow_out_in_place(
public=EmptyResult(),
state_update=StateUpdate().set_fluid_empty(pipette_id=pipette_id),
)


async def increase_evo_disp_count(pipette_id: str, pipetting: PipettingHandler) -> None:
"""Tell a pipette to increase it's evo-tip-dispense-count in eeprom."""
await pipetting.increase_evo_disp_count(pipette_id)
15 changes: 15 additions & 0 deletions api/src/opentrons/protocol_engine/execution/pipetting.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ async def liquid_probe_in_place(
) -> LiquidTrackingType:
"""Detect liquid level."""

async def increase_evo_disp_count(self, pipette_id: str) -> None:
"""Increase evo tip dispense action count."""


class HardwarePipettingHandler(PipettingHandler):
"""Liquid handling, using the Hardware API."""
Expand Down Expand Up @@ -366,6 +369,14 @@ def _set_flow_rate(
blow_out=original_blow_out_rate,
)

async def increase_evo_disp_count(self, pipette_id: str) -> None:
"""Increase evo tip dispense action count."""
hw_pipette = self._state_view.pipettes.get_hardware_pipette(
pipette_id=pipette_id,
attached_pipettes=self._hardware_api.attached_instruments,
)
await self._hardware_api.increase_evo_disp_count(mount=hw_pipette.mount)


class VirtualPipettingHandler(PipettingHandler):
"""Liquid handling, using the virtual pipettes.""" ""
Expand Down Expand Up @@ -495,6 +506,10 @@ async def dispense_while_tracking(
state_view=self._state_view, pipette_id=pipette_id, dispense_volume=volume
)

async def increase_evo_disp_count(self, pipette_id: str) -> None:
"""Increase evo tip dispense action count."""
pass


def create_pipetting_handler(
state_view: StateView, hardware_api: HardwareControlAPI
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import asyncio
from typing import Dict, Set, Optional, AsyncIterator, Tuple
from typing import Dict, Set, Optional, AsyncIterator, Tuple, Callable, Any, cast
from itertools import chain

import pytest
Expand All @@ -13,9 +13,23 @@
FirmwareTarget,
PipetteName,
)
from opentrons_hardware.firmware_bindings.utils import UInt8Field
from opentrons_hardware.hardware_control import network, tools, types
from opentrons_hardware.firmware_update import FirmwareUpdate, RunUpdate
from opentrons_hardware.firmware_update.types import StatusElement, FirmwareUpdateStatus
from opentrons_hardware.firmware_bindings.messages.message_definitions import (
GetMotorUsageRequest,
GetMotorUsageResponse,
)
from opentrons_hardware.firmware_bindings.messages.messages import MessageDefinition
from opentrons_hardware.firmware_bindings.messages.payloads import (
GetMotorUsageResponsePayload,
)
from opentrons_hardware.firmware_bindings import (
ArbitrationId,
ArbitrationIdParts,
MessageId,
)
from opentrons_hardware.drivers import can_bus, binary_usb

from opentrons.hardware_control.backends.subsystem_manager import SubsystemManager
Expand Down Expand Up @@ -109,9 +123,73 @@ def default_network_info_for(


@pytest.fixture
def can_messenger(decoy: Decoy) -> can_bus.CanMessenger:
async def can_messenger(decoy: Decoy) -> can_bus.CanMessenger:
"""Build a decoyed can messenger."""
return decoy.mock(cls=can_bus.CanMessenger)
mock_messenger = decoy.mock(cls=can_bus.CanMessenger)
listeners: list[Callable[[MessageDefinition, ArbitrationId], None]] = []

def _add(
listener: Callable[[MessageDefinition, ArbitrationId], None], _: Any
) -> None:
listeners.append(listener)

decoy.when(
mock_messenger.add_listener(
cast(Any, matchers.Anything()), cast(Any, matchers.Anything())
)
).then_do(_add)

async def _send_response(node_id: NodeId, message: MessageDefinition) -> None:
for listener in listeners:
listener(
GetMotorUsageResponse(
payload=GetMotorUsageResponsePayload(
usage_elements=[], num_elements=UInt8Field(0)
)
),
ArbitrationId(
parts=ArbitrationIdParts(
message_id=MessageId.get_motor_usage_response,
node_id=NodeId.host,
originating_node_id=NodeId.pipette_left,
)
),
)
listener(
GetMotorUsageResponse(
payload=GetMotorUsageResponsePayload(
usage_elements=[], num_elements=UInt8Field(0)
)
),
ArbitrationId(
parts=ArbitrationIdParts(
message_id=MessageId.get_motor_usage_response,
node_id=NodeId.host,
originating_node_id=NodeId.pipette_right,
)
),
)
listener(
GetMotorUsageResponse(
payload=GetMotorUsageResponsePayload(
usage_elements=[], num_elements=UInt8Field(0)
)
),
ArbitrationId(
parts=ArbitrationIdParts(
message_id=MessageId.get_motor_usage_response,
node_id=NodeId.host,
originating_node_id=NodeId.gripper,
)
),
)

decoy.when(
await mock_messenger.send(
node_id=NodeId.broadcast, message=GetMotorUsageRequest()
)
).then_do(_send_response)
return mock_messenger


@pytest.fixture
Expand Down Expand Up @@ -243,9 +321,11 @@ def _auto_tool_summary(
right=self._pipette_info_from_network(
devices.get(NodeId.pipette_right), PipetteName.p50_multi
),
gripper=tools.types.GripperInformation(model="1", serial="12131231")
if (NodeId.gripper in devices and devices[NodeId.gripper].ok)
else None,
gripper=(
tools.types.GripperInformation(model="1", serial="12131231")
if (NodeId.gripper in devices and devices[NodeId.gripper].ok)
else None
),
)

async def add_resolution(
Expand All @@ -257,15 +337,23 @@ async def add_resolution(
summary = specific or self._auto_tool_summary(devices)

arg = tools.types.ToolDetectionResult(
left=on.left
if (NodeId.pipette_left in devices and devices[NodeId.pipette_left].ok)
else ToolType.nothing_attached,
right=on.right
if (NodeId.pipette_right in devices and devices[NodeId.pipette_right].ok)
else ToolType.nothing_attached,
gripper=on.gripper
if (NodeId.gripper in devices and devices[NodeId.gripper].ok)
else ToolType.nothing_attached,
left=(
on.left
if (NodeId.pipette_left in devices and devices[NodeId.pipette_left].ok)
else ToolType.nothing_attached
),
right=(
on.right
if (
NodeId.pipette_right in devices and devices[NodeId.pipette_right].ok
)
else ToolType.nothing_attached
),
gripper=(
on.gripper
if (NodeId.gripper in devices and devices[NodeId.gripper].ok)
else ToolType.nothing_attached
),
)

self._decoy.when(await self._tool_detector.resolve(arg, 10.0)).then_return(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,4 @@ async def test_evotip_dispense_implementation(
),
),
)
decoy.verify(await pipetting.increase_evo_disp_count("pipette-id-abc123"), times=1)
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Pipetting execution handler."""

from typing import cast
from unittest.mock import sentinel

import pytest
from decoy import Decoy
Expand Down Expand Up @@ -752,3 +754,31 @@ async def test_dispense_volume_validation(
push_out=7,
is_full_dispense=True,
)


async def test_hw_increase_evo_disp_count(
decoy: Decoy,
mock_state_view: StateView,
mock_hardware_api: HardwareAPI,
hardware_subject: HardwarePipettingHandler,
) -> None:
"""Should set flow_rate and call hardware_api aspirate."""
decoy.when(mock_hardware_api.attached_instruments).then_return(
sentinel.attached_instruments
)
decoy.when(
mock_state_view.pipettes.get_hardware_pipette(
pipette_id="pipette-id", attached_pipettes=sentinel.attached_instruments
)
).then_return(
HardwarePipette(
mount=Mount.LEFT,
config=cast(
PipetteDict,
{},
),
)
)
await hardware_subject.increase_evo_disp_count(pipette_id="pipette-id")

decoy.verify(await mock_hardware_api.increase_evo_disp_count(mount=Mount.LEFT))
2 changes: 2 additions & 0 deletions hardware/opentrons_hardware/firmware_bindings/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ class MessageId(int, Enum):
max_sensor_value_request = 0x70
max_sensor_value_response = 0x71

increase_evo_disp_count_request = 0x80
batch_read_sensor_response = 0x81
read_sensor_request = 0x82
write_sensor_request = 0x83
Expand Down Expand Up @@ -446,6 +447,7 @@ class MotorUsageValueType(int, Enum):
force_application_time = 0x3
total_error_count = 0x4
overpressure_error_count = 0x5
resin_tip_dispense_count = 0x6


class MoveAckId(int, Enum):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1067,3 +1067,12 @@ class SendAccumulatedSensorDataRequest(BaseMessage):
message_id: Literal[
MessageId.send_accumulated_sensor_data
] = MessageId.send_accumulated_sensor_data


@dataclass
class IncreaseEvoTipDispenseCountRequestRequest(EmptyPayloadMessage):
"""Send all the saved sensor data."""

message_id: Literal[
MessageId.increase_evo_disp_count_request
] = MessageId.increase_evo_disp_count_request
Loading
Loading