Write your first macro

Macros are the procedures that the Sardana MacroServer executes: every scan, every motion command you typed so far is a macro. In this lesson you will write a macro of your own, run it from Spock, give it parameters and make it return a result.

You need the simulated beamline from Set up a simulated beamline up and running.

Macros live in macro libraries

A macro is plain Python code. A Python file containing macros is called a macro library. The MacroServer discovers macro libraries in a list of directories configured through the MacroPath property of the MacroServer Tango device.

First, create a directory for your macro libraries, for example:

mkdir -p /home/homer/sardana/macros

Then add it to the MacroPath of your MacroServer. From any Python or itango console:

import tango
ms = tango.DeviceProxy("MacroServer/demo1/1")
ms.put_property({"MacroPath": ["/home/homer/sardana/macros"]})

Finally restart the Sardana demo1 server (stop it with Ctrl+C and start it again) so that the new property takes effect. This configuration only has to be done once.

Hello, world!

Create the file /home/homer/sardana/macros/salute.py with this content:

from sardana.macroserver.macro import macro

@macro()
def hello_world(self):
    """This is a hello world macro"""
    self.output("Hello, World!")

That is a complete macro:

  • The @macro() decorator labels the function as a macro – without it, Sardana ignores the function.

  • The first (and here only) parameter, conventionally called self, is the macro execution context. Through it you can send output, ask for motors, run other macros, and much more.

  • The docstring documents the macro; always write one, it is shown by the command line and graphical interfaces.

  • self.output(...) sends text to the client. A plain print() would print in the server terminal – remember your code runs in the MacroServer, possibly on another computer.

Load and run it

If you just (re)started the server, the new library was discovered automatically. If the server was already running when you created the file, load it with the addmaclib macro:

Door_demo1_1 [1]: addmaclib salute
salute successfully loaded (found 1 macros)

Now run your macro like any other:

Door_demo1_1 [2]: hello_world
Hello, World!

Whenever you edit the file afterwards, reload it with relmaclib:

Door_demo1_1 [3]: relmaclib salute
salute successfully (re)loaded (found 1 macros)

Hint

Spock can also create and edit macros for you: edmac hello_world salute opens your favorite editor (the one in the EDITOR environment variable) on a macro template and loads the result into the server when you are done.

Adding parameters

Macro parameters must be declared: each one has a name, a type, a default value (None meaning no default) and a description. In exchange, Sardana rejects calls with wrong types, Spock offers tab-completion of parameter values, and GUIs can build forms automatically. Add this to salute.py:

from sardana.macroserver.macro import macro, Type

@macro([["moveable", Type.Moveable, None, "moveable to move"],
        ["position", Type.Float, None, "absolute position"]])
def move(self, moveable, position):
    """This macro moves a moveable to the specified position"""
    moveable.move(position)
    self.output("%s is now at %s", moveable.getName(), moveable.getPosition())

Note that the moveable parameter arrives as a full-blown motor object you can operate on directly. Reload and run it on a demo motor:

Door_demo1_1 [4]: relmaclib salute
salute successfully (re)loaded (found 2 macros)

Door_demo1_1 [5]: move mot01 3
mot01 is now at 3.0

Common parameter types include Integer, Float, Boolean, String, Moveable and Motor.

Returning a result

A macro can also return one or more results, declared in the same fashion with result_def. Macros come in two equivalent forms and both of them support results. The first one is the class form: instead of decorating a function, you inherit from Macro and implement the run method:

from sardana.macroserver.macro import Macro, Type

class twice(Macro):
    """A macro that returns twice its input."""

    param_def = [["value", Type.Float, 23, "value to be doubled"]]
    result_def = [["result", Type.Float, None, "the double of the given value"]]

    def run(self, value):
        return 2 * value

The second one is the decorator form you have been using so far – the @macro decorator takes result_def as its second argument and the value returned by the function is the result:

from sardana.macroserver.macro import macro, Type

@macro([["value", Type.Float, 23, "value to be doubled"]],
       [["result", Type.Float, None, "the double of the given value"]])
def twice(self, value):
    """A macro that returns twice its input."""
    return 2 * value

Pick whichever reads better for the macro at hand. Either way, other macros can now call twice and read its result, e.g. with self.execMacro("twice 21").getResult().

Next steps

You can now extend Sardana with your own procedures. Continue with Write your first controller to learn how Sardana talks to hardware. When you need more – repeat parameters, calling macros from macros, environment, hooks, plotting – the complete guide is Writing macros.