Skip to content
fabcor edited this page Jan 16, 2024 · 2 revisions

Warning

This page is now deprecated.

The up-to-date page can be found at:

https://mxcubeweb.readthedocs.io/en/latest/dev/tutorial.html


Exercise 1. Setup environment

Launch web ui, login and play...

Useful links:

swagger

Exercise 2. Create a new beamline action, and trigger it from the ui

For procedures that are frequently used and involves more than a simple command. Try it out, play with multiple hardware objects

Solution

Edit the xml so mxcube is aware of the new one:

      {"type": "controller", "name": "SuperAction", "command": "HardwareObjects.mockup.BeamlineActionsMockup.SuperAction"},

and do the actual action, in mxcubecore/mxcubecore/HardwareObjects/mockup/BeamlineActionsMockup.py

class SuperAction:
    def __call__(self, *args, **kw):
        logging.getLogger('HWR').info("Running super action")

        from mxcubecore import HardwareRepository as HWR
        logging.getLogger('HWR').info("Setting diff to transfer")

        HWR.beamline.diffractometer.set_phase('Transfer')
        gevent.sleep(3)
        logging.getLogger('HWR').info("Restarting detector")

        HWR.beamline.detector.restart()
        logging.getLogger('HWR').info("Running super action")

Exercise 3. Add a custom action for the detector and make it appear in the ui (custom equipment tab)

For not so often used or temporary instrumentation commands

trigger

Solution

We add the method in the hardware object

    def trigger(self, exp_time: float ) -> None:
        self.update_state(HardwareObjectState.BUSY)
        time.sleep(exp_time)
        self.update_state(HardwareObjectState.READY)

and define as exportable in the detector.xml

  <exports>["restart", "trigger"]</exports>

Exercise 4. Humidity controller

  • Create a new hardware object that emulates a humidity controller; it emits periodically an updated value and the reference value can be changed
  • Display this value in the sample view area
  • The ui can control the reference
humidity_ui

Solution

First we need to write the hardwareobject

"""
[Name] Humidity Controller

[Description]
Humidity Controller Mockup

[Emited signals]
valueChanged

"""

import random
import gevent
from mxcubecore.HardwareObjects.abstract import AbstractActuator


class HumidityControllerMockup(AbstractActuator.AbstractActuator):
    """HumidityControllerMockup implementation"""

    def __init__(self, name):
        super().__init__(name)

    def init(self):
        """Initialisation method"""
        super().init()
        self._nominal_value = 33.3
        # self.humidity_list = self.get_property('humidity_list', None)
        self.update_state(self.STATES.READY)
        self._run()

    def _run(self):
        """Spawn update routine."""
        gevent.spawn(self._update)

    def get_value(self):
        """Read the humidity.
        Returns:
            float: humidity value.
        """
        # _nominal_value comes from AbstractActuator
        noise = random.random()
        return self._nominal_value + noise

    def set_value(self, value, timeout=0):
        """Set humidity reference.
        Args:
            value: target value
            timeout (float): optional - timeout [s],
                             If timeout == 0: return at once and do not wait
                                              (default);
                             if timeout is None: wait forever.
        Raises:
            ValueError: Invalid value or attemp to set read only actuator.
            RuntimeError: Timeout waiting for status ready  # From wait_ready
        """
        print('set_value')
        self._nominal_value = value
        return self.get_value()

    def get_humidity_list(self):
        return self.humidity_list

    def _update(self):
        while True:
            gevent.sleep(5)
            value = self.get_value()
            print('new humidity value {}'.format(value))
            self.emit("valueChanged", (value,))

with the humidity.xml

<object class="HumidityControllerMockup">
</object>

and extra line in mxcubeweb/test/HardwareObjectsMockup.xml/beamline_config.yml

    - humidity: humidity.xml

And we need to let the Beamline know about it:

Beamline.py

    @property
    def humidity(self):
        """Humidity control Hardware object

        Returns:
            Optional[AbstractMachineInfo]:
        """
        return self._objects.get("humidity")

    __content_roles.append("humidity")

And finally, let the UI know that there is a new hardware object that needs rendering:

in ui.yaml

    - label: Humidity
      attribute: humidity
      precision: 2
      suffix: "%"
      format: expo

This made the ui provide the functionality out of the box, but we dit not really play with web development. The extra dropdown menu gives us some extra knowledge.

First we need to create the react component, create new file in mxcubeweb/ui/src/components/SampleView/HumidityInput.js

import React from 'react';
import '../MotorInput/motor.css';
import '../input.css';
import cx from 'classnames';

export default class HumidityInput extends React.Component {
  constructor(props) {
    super(props);
    this.sendHumidity = this.sendHumidity.bind(this);
    this.humidityList = [20, 30, 40];
  }

  sendHumidity(event) {
    if (event.target.value !== 'Unknown') {
      this.props.sendHumidity(event.target.value);
    }
  }

  render() {
    const inputCSS = cx('form-control input-sm', {
      'input-bg-moving': this.props.state !== 'READY',
      'input-bg-ready': this.props.state === 'READY',
    });

    console.log('render humidity', this.props);
    return (
      <div className="motor-input-container">
        <select
          className={inputCSS}
          onChange={this.sendHumidity}
          value={this.props.humidity}
        >
          {this.humidityList.map((option) => (
            <option key={option} value={option}>
              {option}
            </option>
          ))}
        </select>
      </div>
    );
  }
}

You can see that we are hardcoding the options for the humidity. Now we need to call this component in mxcubeweb/ui/src/containers/SampleViewContainer.js

import HumidityInput from '../components/SampleView/HumidityInput';

// and
    const humidityInput = (
      <div>
        <p className="motor-name">Humidity Control:</p>
        <HumidityInput
          humidityList={this.props.sampleViewState.humidityList}
          sendHumidity={this.props.sampleViewActions.sendHumidity}
          state={diffractometerHo.state}
        />
      </div>
    );
    
// and
              {process.env.REACT_APP_PHASECONTROL === 'true'
                ? phaseControl
                : null}
              {humidityInput}
          {apertureControl}

(just find your way through the code ;)) But we still need to define what happens when we select an option, sendHumidity would be called ... if you have created it first in mxcubeweb/ui/src/actions/sampleview.js

export function sendHumidity(humidity) {
  return function () {
    fetch('/mxcube/api/v0.1/beamline/actuator/value/humidity', {
      method: 'PUT',
      credentials: 'include',
      headers: {
        Accept: 'application/json',
        'Content-type': 'application/json',
      },
      body: JSON.stringify({ name: 'humidity', value: humidity }),
    });
  };
}

Exercise 5. New React component from scratch

This shows how to create and use a minimal component, don't pay too much attention to the functionality.

import React from 'react';
import { Button } from 'react-bootstrap';

function NewComponent(props) {
  return (
    <div>
      <Button
        variant="danger"
        onClick={() => props.sendCurrentPhase('Transfer')}
      >
        boo
      </Button>
      <h1>Hello, {props.name}</h1>
    </div>
  );
}

export default NewComponent;

and in BeamlineSetupContainer.jsx add the following lines:

import NewComponent from '../components/NewComponent/NewComponent';
.
.

// in the render method:

          <Nav className="me-3">
            <NewComponent
              name={
                this.props.beamline.hardwareObjects['diffractometer.phi'].value
              }
              sendCurrentPhase={this.props.sampleViewActions.sendCurrentPhase}
            />