Skip to content

Commit

Permalink
handle motor driver errors in move_group_runner
Browse files Browse the repository at this point in the history
  • Loading branch information
pmoegenburg committed Mar 20, 2024
1 parent 923a5e6 commit 6367691
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 3 deletions.
4 changes: 4 additions & 0 deletions hardware/opentrons_hardware/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
PipetteOverpressureError,
LabwareDroppedError,
PythonException,
MotorDriverError,
)

from opentrons_hardware.firmware_bindings.messages.message_definitions import (
Expand Down Expand Up @@ -133,6 +134,9 @@ def raise_from_error_message( # noqa: C901
raise RoboticsControlError(
message="Unexpected robotics error", detail=detail_dict
)

if error_code in (ErrorCode.motor_driver_error_detected,):
raise MotorDriverError(detail=detail_dict)

raise RoboticsControlError(message="Hardware error", detail=detail_dict)

Expand Down
9 changes: 9 additions & 0 deletions hardware/opentrons_hardware/firmware_bindings/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,15 @@ class ErrorCode(int, Enum):
motor_driver_error_detected = 0x0E


@unique
class MotorDriverErrorCode(int, Enum):
"""Motor driver error codes."""

over_temperature = 0x2000000
short_circuit = 0x18000000
open_circuit = 0x60000000


@unique
class ToolType(int, Enum):
"""Tool types detected on Head."""
Expand Down
45 changes: 42 additions & 3 deletions hardware/opentrons_hardware/hardware_control/move_group_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
EStopActivatedError,
MotionFailedError,
PythonException,
MotorDriverError,
)

from opentrons_hardware.firmware_bindings import ArbitrationId
Expand All @@ -22,6 +23,7 @@
ErrorSeverity,
GearMotorId,
MoveAckId,
MotorDriverErrorCode,
)
from opentrons_hardware.drivers.can_bus.can_messenger import CanMessenger
from opentrons_hardware.firmware_bindings.messages import MessageDefinition
Expand All @@ -38,6 +40,7 @@
TipActionResponse,
ErrorMessage,
StopRequest,
ReadMotorDriverErrorStatusResponse,
)
from opentrons_hardware.firmware_bindings.messages.payloads import (
AddLinearMoveRequestPayload,
Expand Down Expand Up @@ -497,6 +500,42 @@ def _handle_move_completed(
# pick up groups they don't care about, and need to not fail.
pass

def _handle_motor_driver_error(
self, message: ReadMotorDriverErrorStatusResponse, arbitration_id: ArbitrationId
) -> None:
node_id = arbitration_id.parts.originating_node_id
data = message.payload.data.value
if data & MotorDriverErrorCode.over_temperature.value:
log.error(f"Motor driver error from node {node_id}")
self._errors.append(
MotorDriverError(
detail={
"node": NodeId(node_id).name,
"error": "over temperature",
}
)
)
if data & MotorDriverErrorCode.short_circuit.value:
log.error(f"Motor driver error from node {node_id}")
self._errors.append(
MotorDriverError(
detail={
"node": NodeId(node_id).name,
"error": "short circuit",
}
)
)
if data & MotorDriverErrorCode.open_circuit.value:
log.error(f"Motor driver error from node {node_id}")
self._errors.append(
MotorDriverError(
detail={
"node": NodeId(node_id).name,
"error": "open circuit",
}
)
)

def __call__(
self, message: MessageDefinition, arbitration_id: ArbitrationId
) -> None:
Expand All @@ -510,6 +549,8 @@ def __call__(
self._handle_move_completed(message, arbitration_id)
elif isinstance(message, ErrorMessage):
self._handle_error(message, arbitration_id)
elif isinstance(message, ReadMotorDriverErrorStatusResponse):
self._handle_motor_driver_error(message, arbitration_id)

def _handle_tip_action_motors(self, message: TipActionResponse) -> bool:
gear_id = GearMotorId(message.payload.gear_motor_id.value)
Expand Down Expand Up @@ -571,7 +612,7 @@ async def _run_one_group(self, group_id: int, can_messenger: CanMessenger) -> No

log.debug(f"Executing move group {group_id}.")
self._current_group = group_id - self._start_at_index
error = await can_messenger.ensure_send( # catches Error response as opposed to ack response??
error = await can_messenger.ensure_send(
node_id=NodeId.broadcast,
message=ExecuteMoveGroupRequest(
payload=ExecuteMoveGroupRequestPayload(
Expand All @@ -587,8 +628,6 @@ async def _run_one_group(self, group_id: int, can_messenger: CanMessenger) -> No
if error != ErrorCode.ok:
log.error(f"received error trying to execute move group: {str(error)}")

# just report errors when move requested
# we want to report follow-up message! Just let logs gather it if error doesn't occur during a move? Do we collect non-response (error) messages??
expected_time = max(3.0, self._durations[group_id - self._start_at_index] * 1.1)
full_timeout = max(5.0, self._durations[group_id - self._start_at_index] * 2)
start_time = time.time()
Expand Down
4 changes: 4 additions & 0 deletions shared-data/errors/definitions/1/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@
"detail": "Gripper Pickup Failed",
"category": "roboticsControlError"
},
"2016": {
"detail": "Motor Driver Error",
"category": "roboticsControlError"
},
"3000": {
"detail": "A robotics interaction error occurred.",
"category": "roboticsInteractionError"
Expand Down
1 change: 1 addition & 0 deletions shared-data/python/opentrons_shared_data/errors/codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class ErrorCodes(Enum):
POSITION_UNKNOWN = _code_from_dict_entry("2013")
EXECUTION_CANCELLED = _code_from_dict_entry("2014")
FAILED_GRIPPER_PICKUP_ERROR = _code_from_dict_entry("2015")
MOTOR_DRIVER_ERROR = _code_from_dict_entry("2016")
ROBOTICS_INTERACTION_ERROR = _code_from_dict_entry("3000")
LABWARE_DROPPED = _code_from_dict_entry("3001")
LABWARE_NOT_PICKED_UP = _code_from_dict_entry("3002")
Expand Down
12 changes: 12 additions & 0 deletions shared-data/python/opentrons_shared_data/errors/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,18 @@ def __init__(
super().__init__(ErrorCodes.EXECUTION_CANCELLED, message, detail, wrapping)


class MotorDriverError(RoboticsControlError):
"""An error indicating that a motor driver is in error state."""
def __init__(
self,
message: Optional[str] = None,
detail: Optional[Dict[str, str]] = None,
wrapping: Optional[Sequence[EnumeratedError]] = None,
) -> None:
"""Build a MotorDriverError."""
super().__init__(ErrorCodes.MOTOR_DRIVER_ERROR, message, detail, wrapping)


class LabwareDroppedError(RoboticsInteractionError):
"""An error indicating that the gripper dropped labware it was holding."""

Expand Down

0 comments on commit 6367691

Please sign in to comment.