diff --git a/src/hyperion/experiment_plans/robot_load_then_centre_plan.py b/src/hyperion/experiment_plans/robot_load_then_centre_plan.py index 7c08899fb..15d0f4ec5 100644 --- a/src/hyperion/experiment_plans/robot_load_then_centre_plan.py +++ b/src/hyperion/experiment_plans/robot_load_then_centre_plan.py @@ -174,6 +174,10 @@ def robot_load(): yield from bps.wait("robot_load") + yield from bps.create(name=CONST.PLAN.ROBOT_LOAD) + yield from bps.read(composite.robot.barcode) + yield from bps.save() + yield from wait_for_smargon_not_disabled(composite.smargon) yield from robot_load() diff --git a/src/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py b/src/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py index 83b780309..17c36dd0d 100644 --- a/src/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +++ b/src/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py @@ -2,6 +2,8 @@ from typing import TYPE_CHECKING, Dict, Optional +from event_model.documents import EventDescriptor + from hyperion.external_interaction.callbacks.common.ispyb_mapping import ( get_proposal_and_session_from_visit_string, get_visit_string_from_path, @@ -17,7 +19,7 @@ from hyperion.parameters.constants import CONST if TYPE_CHECKING: - from event_model.documents import EventDescriptor, RunStart, RunStop + from event_model.documents import Event, EventDescriptor, RunStart, RunStop class RobotLoadISPyBCallback(PlanReactiveCallback): @@ -30,7 +32,7 @@ def __init__(self) -> None: self.expeye = ExpeyeInteraction() def activity_gated_start(self, doc: RunStart): - ISPYB_LOGGER.debug("ISPyB robot load handler received start document.") + ISPYB_LOGGER.debug("ISPyB robot load callback received start document.") if doc.get("subplan_name") == CONST.PLAN.ROBOT_LOAD: self.run_uid = doc.get("uid") assert isinstance(metadata := doc.get("metadata"), Dict) @@ -47,12 +49,26 @@ def activity_gated_start(self, doc: RunStart): ) return super().activity_gated_start(doc) + def activity_gated_descriptor(self, doc: EventDescriptor) -> EventDescriptor | None: + self.descriptors[doc["uid"]] = doc + + def activity_gated_event(self, doc: Event) -> Event | None: + event_descriptor = self.descriptors.get(doc["descriptor"]) + if event_descriptor and event_descriptor.get("name") == CONST.PLAN.ROBOT_LOAD: + assert ( + self.action_id is not None + ), "ISPyB Robot load callback event called unexpectedly" + barcode = doc["data"]["robot-barcode"] + self.expeye.update_barcode(self.action_id, barcode) + + return super().activity_gated_event(doc) + def activity_gated_stop(self, doc: RunStop) -> RunStop | None: - ISPYB_LOGGER.debug("ISPyB robot load handler received stop document.") + ISPYB_LOGGER.debug("ISPyB robot load callback received stop document.") if doc.get("run_start") == self.run_uid: assert ( self.action_id is not None - ), "ISPyB Robot load handler stop called unexpectedly" + ), "ISPyB Robot load callback stop called unexpectedly" exit_status = ( doc.get("exit_status") or "Exit status not available in stop document!" ) diff --git a/src/hyperion/external_interaction/ispyb/exp_eye_store.py b/src/hyperion/external_interaction/ispyb/exp_eye_store.py index 27aa90eaf..885b68946 100644 --- a/src/hyperion/external_interaction/ispyb/exp_eye_store.py +++ b/src/hyperion/external_interaction/ispyb/exp_eye_store.py @@ -67,6 +67,12 @@ def start_load( response = self._send_and_get_response(url, data, post) return response["robotActionId"] + def update_barcode(self, action_id: RobotActionID, barcode: str): + url = self.base_url + self.UPDATE_ROBOT_ACTION.format(action_id=action_id) + + data = {"sampleBarcode": barcode} + self._send_and_get_response(url, data, patch) + def end_load(self, action_id: RobotActionID, status: str, reason: str): url = self.base_url + self.UPDATE_ROBOT_ACTION.format(action_id=action_id) diff --git a/tests/system_tests/external_interaction/test_exp_eye_dev.py b/tests/system_tests/external_interaction/test_exp_eye_dev.py index 0d079ef34..dffe6985f 100644 --- a/tests/system_tests/external_interaction/test_exp_eye_dev.py +++ b/tests/system_tests/external_interaction/test_exp_eye_dev.py @@ -15,7 +15,11 @@ def test_start_and_end_robot_load(): robot_action_id = expeye.start_load("cm37235", 2, 5289780, 40, 3) - sleep(1) + sleep(0.5) + + expeye.update_barcode(robot_action_id, "test_barcode") + + sleep(0.5) expeye.end_load(robot_action_id, "fail", "Oh no!") diff --git a/tests/unit_tests/experiment_plans/test_wait_for_robot_load_then_centre.py b/tests/unit_tests/experiment_plans/test_wait_for_robot_load_then_centre.py index dc5815369..df08fefd5 100644 --- a/tests/unit_tests/experiment_plans/test_wait_for_robot_load_then_centre.py +++ b/tests/unit_tests/experiment_plans/test_wait_for_robot_load_then_centre.py @@ -306,6 +306,9 @@ def test_when_prepare_for_robot_load_called_then_moves_as_expected( @patch( "hyperion.external_interaction.callbacks.robot_load.ispyb_callback.ExpeyeInteraction.end_load" ) +@patch( + "hyperion.external_interaction.callbacks.robot_load.ispyb_callback.ExpeyeInteraction.update_barcode" +) @patch( "hyperion.external_interaction.callbacks.robot_load.ispyb_callback.ExpeyeInteraction.start_load" ) @@ -319,6 +322,7 @@ def test_when_prepare_for_robot_load_called_then_moves_as_expected( def test_given_ispyb_callback_attached_when_robot_load_then_centre_plan_called_then_ispyb_deposited( mock_centring_plan: MagicMock, start_load: MagicMock, + update_barcode: MagicMock, end_load: MagicMock, robot_load_composite: RobotLoadThenCentreComposite, robot_load_then_centre_params: RobotLoadThenCentreInternalParameters, @@ -332,4 +336,5 @@ def test_given_ispyb_callback_attached_when_robot_load_then_centre_plan_called_t RE(robot_load_then_centre(robot_load_composite, robot_load_then_centre_params)) start_load.assert_called_once_with("cm31105", 4, "12345", 40, 3) + update_barcode.assert_called_once_with(action_id, "BARCODE") end_load.assert_called_once_with(action_id, "success", "") diff --git a/tests/unit_tests/external_interaction/callbacks/robot_load/test_robot_load_ispyb_callback.py b/tests/unit_tests/external_interaction/callbacks/robot_load/test_robot_load_ispyb_callback.py index 5d1071182..335ef58bc 100644 --- a/tests/unit_tests/external_interaction/callbacks/robot_load/test_robot_load_ispyb_callback.py +++ b/tests/unit_tests/external_interaction/callbacks/robot_load/test_robot_load_ispyb_callback.py @@ -4,6 +4,7 @@ import bluesky.preprocessors as bpp import pytest from bluesky.run_engine import RunEngine +from dodal.devices.robot import BartRobot from hyperion.external_interaction.callbacks.robot_load.ispyb_callback import ( RobotLoadISPyBCallback, @@ -90,3 +91,35 @@ def test_given_end_called_but_no_start_then_exception_raised(end_load): with pytest.raises(AssertionError): callback.activity_gated_stop({"run_uid": None}) # type: ignore end_load.assert_not_called() + + +@patch( + "hyperion.external_interaction.callbacks.robot_load.ispyb_callback.ExpeyeInteraction.end_load" +) +@patch( + "hyperion.external_interaction.callbacks.robot_load.ispyb_callback.ExpeyeInteraction.start_load" +) +@patch( + "hyperion.external_interaction.callbacks.robot_load.ispyb_callback.ExpeyeInteraction.update_barcode" +) +def test_given_plan_reads_barcode_then_data_put_in_ispyb( + update_barcode: MagicMock, + start_load: MagicMock, + end_load: MagicMock, + robot: BartRobot, +): + RE = RunEngine() + RE.subscribe(RobotLoadISPyBCallback()) + start_load.return_value = ACTION_ID + + @bpp.run_decorator(md=metadata) + def my_plan(): + yield from bps.create(name=CONST.PLAN.ROBOT_LOAD) + yield from bps.read(robot.barcode) + yield from bps.save() + + RE(my_plan()) + + start_load.assert_called_once_with("cm31105", 4, SAMPLE_ID, SAMPLE_PUCK, SAMPLE_PIN) + update_barcode.assert_called_once_with(ACTION_ID, "BARCODE") + end_load.assert_called_once_with(ACTION_ID, "success", "") diff --git a/tests/unit_tests/external_interaction/ispyb/test_expeye_interaction.py b/tests/unit_tests/external_interaction/ispyb/test_expeye_interaction.py index 4ba406eb7..2f2fe9e2a 100644 --- a/tests/unit_tests/external_interaction/ispyb/test_expeye_interaction.py +++ b/tests/unit_tests/external_interaction/ispyb/test_expeye_interaction.py @@ -118,3 +118,18 @@ def test_given_server_does_not_respond_when_end_load_called_then_error(mock_patc expeye_interactor = ExpeyeInteraction() with pytest.raises(ISPyBDepositionNotMade): expeye_interactor.end_load(1, "", "") + + +@patch("hyperion.external_interaction.ispyb.exp_eye_store.patch") +def test_when_update_barcode_called_with_success_then_correct_expected_url_posted_to_with_expected_data( + mock_patch, +): + expeye_interactor = ExpeyeInteraction() + expeye_interactor.update_barcode(3, "test") + + mock_patch.assert_called_once() + assert mock_patch.call_args.args[0] == "http://blah/core/robot-actions/3" + expected_data = { + "sampleBarcode": "test", + } + assert mock_patch.call_args.kwargs["json"] == expected_data