diff --git a/mxcubeweb/core/adapter/adapter_manager.py b/mxcubeweb/core/adapter/adapter_manager.py index 764060e3b..ff0135eae 100644 --- a/mxcubeweb/core/adapter/adapter_manager.py +++ b/mxcubeweb/core/adapter/adapter_manager.py @@ -8,7 +8,6 @@ from mxcubecore.utils.conversion import make_table from mxcubeweb.core.adapter.adapter_base import AdapterBase -from mxcubeweb.core.adapter.beamline_adapter import BeamlineAdapter class HardwareObjectAdapterManager: @@ -44,7 +43,6 @@ def init(self) -> None: done when one wishes. """ try: - self.beamline = BeamlineAdapter(HWR.beamline, self.app) self.adapt_hardware_objects() except Exception: # noqa: BLE001 msg = ( @@ -124,32 +122,44 @@ def find_best_adapter(self, ho): logging.getLogger("MX3.HWR").error(msg) raise RuntimeError(msg) + def adapt_hardware_object(self, ho, _id): + adapter_cls = self.find_best_adapter(ho) + + if adapter_cls: + try: + adapter_instance = adapter_cls(ho, _id, self.app) + msg = f"Added adapter for {_id}" + logging.getLogger("MX3.HWR").info(msg) + except Exception: + msg = f"Could not add adapter for {_id}" + logging.getLogger("MX3.HWR").exception(msg) + adapter_cls = AdapterBase + adapter_instance = AdapterBase(ho, _id, self.app) + + self._add_adapter(_id, adapter_cls, ho, adapter_instance) + else: + msg = f"No adapter for {_id}" + logging.getLogger("MX3.HWR").info(msg) + def adapt_hardware_objects(self): _hwr = HWR.get_hardware_repository() + # Beamline is not added to the list of hardware objects + # returned by _hwr.hardware_objects as its considered a + # special object root object for all other hardware objects + # so we add it manually here. We give it the id 'beamline' + # so that it can be retrieved by the adapter manager but + # in reality has no id. + self.adapt_hardware_object(HWR.beamline, "beamline") + for ho_name in _hwr.hardware_objects: ho = _hwr.get_hardware_object(ho_name) if not ho: continue - _id = ho.id - adapter_cls = self.find_best_adapter(ho) - - if adapter_cls: - try: - adapter_instance = adapter_cls(ho, _id, self.app) - msg = f"Added adapter for {_id}" - logging.getLogger("MX3.HWR").info(msg) - except Exception: - msg = f"Could not add adapter for {_id}" - logging.getLogger("MX3.HWR").exception(msg) - adapter_cls = AdapterBase - adapter_instance = AdapterBase(ho, _id, self.app) - - self._add_adapter(_id, adapter_cls, ho, adapter_instance) - else: - msg = f"No adapter for {_id}" - logging.getLogger("MX3.HWR").info(msg) + _id = ho.id or "beamline" + + self.adapt_hardware_object(ho, _id) self._print_adapter_table() diff --git a/mxcubeweb/core/adapter/beamline_adapter.py b/mxcubeweb/core/adapter/beamline_adapter.py index 57588306a..ad774fc94 100644 --- a/mxcubeweb/core/adapter/beamline_adapter.py +++ b/mxcubeweb/core/adapter/beamline_adapter.py @@ -1,65 +1,72 @@ import logging +from typing import ClassVar import pydantic +from mxcubecore import HardwareRepository as HWR +from mxcubecore.HardwareObjects.Beamline import Beamline -BEAMLINE_ADAPTER = None +from mxcubeweb.core.adapter.adapter_base import ActuatorAdapterBase +from mxcubeweb.core.models.configmodels import ResourceHandlerConfigModel -# Singleton like interface is needed to keep the same reference to the -# adapter object and its corresponding hardware objects, so that the signal -# system won't clean up signal handlers. (PyDispatcher removes signal handlers -# when an object is garbage collected) +resource_handler_config = ResourceHandlerConfigModel( + commands=["prepare_beamline_for_sample"], + attributes=["data", "get_value"], +) -def BeamlineAdapter(*args): - global BEAMLINE_ADAPTER - - if BEAMLINE_ADAPTER is None: - BEAMLINE_ADAPTER = _BeamlineAdapter(*args) - - return BEAMLINE_ADAPTER - - -class _BeamlineAdapter: +class BeamlineAdapter(ActuatorAdapterBase): """ Adapter between Beamline route and Beamline hardware object. """ - def __init__(self, beamline_hwobj, app): + SUPPORTED_TYPES: ClassVar[list[object]] = [Beamline] + + def __init__(self, ho, role, app): + super().__init__(ho, role, app, resource_handler_config) self.app = app - self._bl = beamline_hwobj self.adapter_dict = {} - workflow = self._bl.workflow + workflow = self._ho.workflow if workflow: - workflow.connect("parametersNeeded", self.wf_parameters_needed) + workflow.connect("parametersNeeded", self._wf_parameters_needed) - gphl_workflow = self._bl.gphl_workflow + gphl_workflow = self._ho.gphl_workflow if gphl_workflow: gphl_workflow.connect( - "GphlJsonParametersNeeded", self.gphl_json_wf_parameters_needed + "GphlJsonParametersNeeded", self._gphl_json_wf_parameters_needed ) gphl_workflow.connect( - "GphlUpdateUiParameters", self.gphl_json_wf_update_ui_parameters + "GphlUpdateUiParameters", self._gphl_json_wf_update_ui_parameters ) - def wf_parameters_needed(self, params): + from mxcubeweb.routes import signals + + if HWR.beamline.xrf_spectrum: + HWR.beamline.xrf_spectrum.connect( + HWR.beamline.xrf_spectrum, + "xrf_task_progress", + signals.xrf_task_progress, + ) + + def _wf_parameters_needed(self, params): self.app.server.emit("workflowParametersDialog", params, namespace="/hwr") - def gphl_json_wf_parameters_needed(self, schema, ui_schema): + def _gphl_json_wf_parameters_needed(self, schema, ui_schema): params = {} params["schema"] = schema params["ui_schema"] = ui_schema self.app.server.emit("gphlWorkflowParametersDialog", params, namespace="/hwr") - def gphl_json_wf_update_ui_parameters(self, update_dict): + def _gphl_json_wf_update_ui_parameters(self, update_dict): self.app.server.emit( "gphlWorkflowUpdateUiParametersDialog", update_dict, namespace="/hwr" ) - def get_object(self, name): - return self._ho.get_hardware_object(name) + def _get_available_elements(self): + escan = self._ho.energy_scan + return escan.get_elements() if escan else [] - def dict(self): + def get_value(self) -> dict: """ Build dictionary value-representation for each beamline attribute Returns: @@ -68,6 +75,10 @@ def dict(self): attributes = {} for attr_name in self.app.mxcubecore.adapter_dict: + # We skip the beamline attribute to avoid endless recursion + if attr_name == "beamline": + continue + try: _d = self.app.mxcubecore.get_adapter(attr_name).data().dict() except pydantic.ValidationError: @@ -76,13 +87,15 @@ def dict(self): attributes.update({attr_name: _d}) - return {"hardwareObjects": attributes} - - def get_available_elements(self): - escan = self._bl.energy_scan - elements = [] + return { + "energyScanElements": self._get_available_elements(), + "path": HWR.beamline.session.get_base_image_directory(), + "actionsList": [], + "hardwareObjects": attributes, + } - if escan: - elements = escan.get_elements() + def prepare_beamline_for_sample(self) -> dict: + if hasattr(HWR.beamline.collect, "prepare_for_new_sample"): + HWR.beamline.collect.prepare_for_new_sample() - return {"elements": elements} + return {} diff --git a/mxcubeweb/core/components/beamline.py b/mxcubeweb/core/components/beamline.py index 28e780ed6..2eb7da660 100644 --- a/mxcubeweb/core/components/beamline.py +++ b/mxcubeweb/core/components/beamline.py @@ -1,6 +1,5 @@ from mxcubecore import HardwareRepository as HWR -from mxcubeweb.core.adapter.beamline_adapter import BeamlineAdapter from mxcubeweb.core.components.component_base import ComponentBase @@ -9,28 +8,7 @@ def __init__(self, app, config): super().__init__(app, config) def init_signals(self): - from mxcubeweb.routes import signals - - if HWR.beamline.xrf_spectrum: - HWR.beamline.xrf_spectrum.connect( - HWR.beamline.xrf_spectrum, - "xrf_task_progress", - signals.xrf_task_progress, - ) - - def get_aperture(self): - """ - Returns list of apertures and the one currently used. - - :return: Tuple, (list of apertures, current aperture) - :rtype: tuple - """ - beam = HWR.beamline.beam - - aperture_list = beam.get_available_size()["values"] - current_aperture = beam.get_value()[-1] - - return aperture_list, current_aperture + pass def get_viewport_info(self): """ @@ -76,25 +54,3 @@ def get_viewport_info(self): "videoHash": HWR.beamline.sample_view.camera.stream_hash, "videoURL": self.app.CONFIG.app.VIDEO_STREAM_URL, } - - def beamline_get_all_attributes(self): - ho = BeamlineAdapter(HWR.beamline) - data = ho.dict() - actions = [] - - data.update( - { - "path": HWR.beamline.session.get_base_image_directory(), - "actionsList": actions, - } - ) - - data.update( - {"energyScanElements": ho.get_available_elements().get("elements", [])} - ) - - return data - - def prepare_beamline_for_sample(self): - if hasattr(HWR.beamline.collect, "prepare_for_new_sample"): - HWR.beamline.collect.prepare_for_new_sample() diff --git a/mxcubeweb/routes/beamline.py b/mxcubeweb/routes/beamline.py deleted file mode 100644 index b05a19ea5..000000000 --- a/mxcubeweb/routes/beamline.py +++ /dev/null @@ -1,33 +0,0 @@ -import logging - -from flask import ( - Blueprint, - Response, - jsonify, -) - - -def init_route(app, server, url_prefix): - bp = Blueprint("beamline", __name__, url_prefix=url_prefix) - - @bp.route("/", methods=["GET"]) - @server.restrict - def beamline_get_all_attributes(): - return jsonify(app.beamline.beamline_get_all_attributes()) - - @bp.route("/prepare_beamline", methods=["PUT"]) - @server.require_control - @server.restrict - def prepare_beamline_for_sample(): - """ - Prepare the beamline for a new sample. - """ - try: - app.beamline.prepare_beamline_for_sample() - except Exception: - msg = "Cannot prepare the Beamline for a new sample" - logging.getLogger("HWR").exception(msg) - return Response(status=200) - return Response(status=200) - - return bp diff --git a/mxcubeweb/server.py b/mxcubeweb/server.py index 2a30f73dd..763b2c07f 100644 --- a/mxcubeweb/server.py +++ b/mxcubeweb/server.py @@ -117,7 +117,6 @@ def register_routes(mxcube): Server.flask, Server.user_datastore, register_blueprint=False ) - from mxcubeweb.routes.beamline import init_route as init_beamline_route from mxcubeweb.routes.csp_report import init_route as init_csp_route from mxcubeweb.routes.harvester import init_route as init_harvester_route from mxcubeweb.routes.lims import init_route as init_lims_route @@ -133,9 +132,6 @@ def register_routes(mxcube): url_root_prefix = "/mxcube/api/v0.1" - Server._register_route( - init_beamline_route, mxcube, f"{url_root_prefix}/beamline" - ) Server._register_route(init_csp_route, mxcube, f"{url_root_prefix}/csp") Server._register_route(init_lims_route, mxcube, f"{url_root_prefix}/lims") diff --git a/test/test_beamline_routes.py b/test/test_beamline_routes.py index 662c803f4..b36a8fd7a 100644 --- a/test/test_beamline_routes.py +++ b/test/test_beamline_routes.py @@ -6,7 +6,9 @@ def test_beamline_get_all_attribute(client): Checks that the data returned has the right structure and if "all" beamline attributes are at least present """ - resp = client.get("/mxcube/api/v0.1/beamline/") + resp = client.get( + "/mxcube/api/v0.1/hwobj/beamline/beamline/get_value", + ) data = json.loads(resp.data) actual = list(data.get("hardwareObjects").keys()) diff --git a/ui/src/actions/beamline.js b/ui/src/actions/beamline.js index f58a77bbb..1c0ab1648 100644 --- a/ui/src/actions/beamline.js +++ b/ui/src/actions/beamline.js @@ -1,4 +1,3 @@ -import { sendPrepareBeamlineForNewSample } from '../api/beamline'; import { sendExecuteCommand, sendSetValue } from '../api/hardware-object'; import { sendLogFrontEndTraceBack } from '../api/log'; @@ -51,7 +50,13 @@ export function executeCommand(object_type, object_id, name, args) { } export function prepareBeamlineForNewSample() { - return () => sendPrepareBeamlineForNewSample(); + return () => + sendExecuteCommand( + 'beamline', + 'beamline', + 'prepare_beamline_for_sample', + {}, + ); } export function logFrontEndTraceBack(stack) { diff --git a/ui/src/actions/login.js b/ui/src/actions/login.js index f08f6311a..49abaeaf9 100644 --- a/ui/src/actions/login.js +++ b/ui/src/actions/login.js @@ -1,5 +1,4 @@ /* eslint-disable promise/prefer-await-to-then */ -import { fetchBeamlineSetup } from '../api/beamline'; import { fetchValue } from '../api/hardware-object'; import { fetchHarvesterInitialState } from '../api/harvester'; import { sendSelectProposal } from '../api/lims'; @@ -96,7 +95,7 @@ export function getInitialState() { fetchValue('beam', 'beam') .then((beamInfo) => ({ beamInfo: beamInfo.value })) .catch(notify), - fetchBeamlineSetup() + fetchValue('beamline', 'beamline') .then((beamlineSetup) => ({ beamlineSetup, datapath: beamlineSetup.path, diff --git a/ui/src/api/beamline.js b/ui/src/api/beamline.js deleted file mode 100644 index ab3a0bdf7..000000000 --- a/ui/src/api/beamline.js +++ /dev/null @@ -1,11 +0,0 @@ -import api from './api'; - -const endpoint = api.url('/beamline'); - -export function fetchBeamlineSetup() { - return endpoint.get('/').safeJson(); -} - -export function sendPrepareBeamlineForNewSample() { - return endpoint.put(undefined, '/prepare_beamline').res(); -}