.. _sardana-tutorial-first-controller: ============================ Write your first controller ============================ A *controller* is a small Python plugin that translates between the Sardana API and the API of a specific piece of hardware. By writing one class with a handful of methods you make a whole family of devices -- motors, counters, detectors -- available to every macro, scan and GUI in the system. In this lesson you will write a minimal *motor* controller that simulates its hardware in pure software, load it into the Pool, create a motor from it and move that motor from Spock. You need the simulated beamline from :ref:`sardana-tutorial-demo-beamline` up and running. The controller class -------------------- A motor controller must inherit from ``MotorController`` (other element types have their own base classes: ``CounterTimerController``, ``TwoDController``, ...). Each controller instance manages a set of *axes* -- in our case, motors -- identified by a number. Create a directory for your controllers, for example:: mkdir -p /home/homer/sardana/controllers and create the file ``/home/homer/sardana/controllers/tutmotctrl.py``: .. code-block:: python import time from sardana import State from sardana.pool.controller import MotorController class TutorialMotorController(MotorController): """A minimal motor controller for the Sardana tutorial. Instead of talking to real hardware it simulates motors that "arrive" at the target position after a constant-velocity delay. """ MaxDevice = 4 # this controller supports up to 4 axes VELOCITY = 10.0 # simulated velocity in units/s def __init__(self, inst, props, *args, **kwargs): super().__init__(inst, props, *args, **kwargs) self._motors = {} def AddDevice(self, axis): self._motors[axis] = {"position": 0.0, "end_time": 0.0} def DeleteDevice(self, axis): del self._motors[axis] def StateOne(self, axis): if time.time() < self._motors[axis]["end_time"]: return State.Moving, "Motor is moving" return State.On, "Motor is ready" def ReadOne(self, axis): return self._motors[axis]["position"] def StartOne(self, axis, position): motor = self._motors[axis] travel_time = abs(position - motor["position"]) / self.VELOCITY motor["end_time"] = time.time() + travel_time motor["position"] = position def StopOne(self, axis): self._motors[axis]["end_time"] = 0.0 def AbortOne(self, axis): self._motors[axis]["end_time"] = 0.0 The methods map directly onto what Sardana needs from a motor: - ``AddDevice`` / ``DeleteDevice`` are called when an axis is created or removed (a real controller would open/close the hardware connection here). - ``StateOne`` returns the state of one axis. While you report ``State.Moving``, Sardana keeps polling; anything else ends the motion. - ``ReadOne`` returns the current position of one axis. - ``StartOne`` receives the target position and must trigger the motion in the hardware -- it must *not* wait for it to finish. - ``StopOne`` / ``AbortOne`` stop a motion gracefully / as fast as possible. .. note:: To keep the example short, our simulated motor "jumps" to the target position immediately and merely *reports* Moving for the travel time. A real controller would read back the actual instantaneous position from the hardware in ``ReadOne``. Tell the Pool where to find it ------------------------------ Just like macros, controller plugins are discovered through a list of directories: the ``PoolPath`` property of the Pool Tango device. Configure it once, from any Python or itango console: .. code-block:: python import tango pool = tango.DeviceProxy("Pool/demo1/1") pool.put_property({"PoolPath": ["/home/homer/sardana/controllers"]}) and restart the ``Sardana demo1`` server. After the restart you can verify that your class was found: .. sourcecode:: spock Door_demo1_1 [1]: lsctrllib Tutorial.* Name Type Library Family -------------------------- ------------------ ------------ --------- TutorialMotorController Motor tutmotctrl Generic .. hint:: After editing the controller file, reload it without restarting the server using the ``relctrllib tutmotctrl`` macro (or ``addctrllib`` for a library added after the server started). Create a controller and a motor ------------------------------- A controller *class* becomes usable hardware when you create a controller *instance* and then *elements* (axes) on it. From Spock, use ``defctrl``: .. sourcecode:: spock Door_demo1_1 [2]: defctrl TutorialMotorController tutctrl01 Created tutctrl01 in Pool_demo1_1 and then create a motor on axis 1 with ``defm`` (``defelem`` is the generic equivalent for any element type): .. sourcecode:: spock Door_demo1_1 [3]: defm tutmot01 tutctrl01 1 Created tutmot01 in Pool_demo1_1 Move it! -------- Your home-made motor is now a first-class Sardana citizen. It shows up in ``lsm`` and ``wa`` next to the demo motors, and every standard macro works with it: .. sourcecode:: spock Door_demo1_1 [4]: umv tutmot01 5 tutmot01 5.0000 Door_demo1_1 [5]: wm tutmot01 tutmot01 User High Not specified Current 5.0000 Low Not specified When you are done experimenting, you can remove the motor and the controller with ``udefelem tutmot01`` followed by ``udefctrl tutctrl01``. Next steps ---------- You have closed the loop: hardware plugin, server, macro, client. Continue with :ref:`sardana-tutorial-first-scan` to combine motors and counters in scans. To write controllers for real hardware -- extra attributes and properties, error handling, other element types like counters or 2D detectors -- read the general :ref:`controller overview ` and the full :ref:`motor controller how-to `, and check the `Sardana plugins catalogue `_ first: a controller for your hardware may already exist.