diff --git a/hardware/opentrons_hardware/hardware_control/sensor_utils.py b/hardware/opentrons_hardware/hardware_control/sensor_utils.py new file mode 100644 index 00000000000..f3aec6c6ad0 --- /dev/null +++ b/hardware/opentrons_hardware/hardware_control/sensor_utils.py @@ -0,0 +1,30 @@ +"""Functions aiding in Liquid Level Detection.""" + +from opentrons_shared_data.errors.exceptions import ( + TipHitWellBottomError, + LiquidNotFoundError, +) + + +import numpy as np + + +def did_tip_hit_liquid( + pressure_readings: list[float], liquid_solid_threshold: float +) -> bool: + """Detects if tip has hit liquid or solid based on given pressure data.""" + if len(pressure_readings) < 5: + raise LiquidNotFoundError( + "Liquid not found. Not enough data to calculate pressure change", + ) + pressure_difference = np.gradient(pressure_readings[-5:], 1) + end_difference = pressure_difference[-1] + if end_difference > liquid_solid_threshold: + # Hit Bottom of Well + raise TipHitWellBottomError( + "Tip hit the bottom of the well.", + {"Pressure change detected: ": str(end_difference)}, + ) + else: + # Hit Liquid + return True diff --git a/shared-data/errors/definitions/1/errors.json b/shared-data/errors/definitions/1/errors.json index 71cfb1ed764..a06d19083d5 100644 --- a/shared-data/errors/definitions/1/errors.json +++ b/shared-data/errors/definitions/1/errors.json @@ -126,6 +126,10 @@ "detail": "Liquid Not Found", "category": "roboticsControlError" }, + "2018": { + "detail": "Tip Hit Well Bottom", + "category": "roboticsControlError" + }, "3000": { "detail": "A robotics interaction error occurred.", "category": "roboticsInteractionError" diff --git a/shared-data/python/opentrons_shared_data/errors/codes.py b/shared-data/python/opentrons_shared_data/errors/codes.py index 59a6a6e3697..53dec3d5c62 100644 --- a/shared-data/python/opentrons_shared_data/errors/codes.py +++ b/shared-data/python/opentrons_shared_data/errors/codes.py @@ -61,6 +61,7 @@ class ErrorCodes(Enum): FAILED_GRIPPER_PICKUP_ERROR = _code_from_dict_entry("2015") MOTOR_DRIVER_ERROR = _code_from_dict_entry("2016") LIQUID_NOT_FOUND = _code_from_dict_entry("2017") + TIP_HIT_WELL_BOTTOM = _code_from_dict_entry("2018") ROBOTICS_INTERACTION_ERROR = _code_from_dict_entry("3000") LABWARE_DROPPED = _code_from_dict_entry("3001") LABWARE_NOT_PICKED_UP = _code_from_dict_entry("3002") diff --git a/shared-data/python/opentrons_shared_data/errors/exceptions.py b/shared-data/python/opentrons_shared_data/errors/exceptions.py index 144bb963285..83529b3741b 100644 --- a/shared-data/python/opentrons_shared_data/errors/exceptions.py +++ b/shared-data/python/opentrons_shared_data/errors/exceptions.py @@ -629,6 +629,24 @@ def __init__( ) +class TipHitWellBottomError(RoboticsControlError): + """Error raised if tip hits bottom of well while trying to detect liquid level.""" + + def __init__( + self, + message: Optional[str] = None, + detail: Optional[Dict[str, str]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + """Initialize TipHitWellBottomError.""" + super().__init__( + ErrorCodes.TIP_HIT_WELL_BOTTOM, + message, + detail, + wrapping, + ) + + class LabwareDroppedError(RoboticsInteractionError): """An error indicating that the gripper dropped labware it was holding."""