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 plainprint()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.