Commands

Commands#

You are going to need your coffee machines to make coffee on demand, so you’ll need a way to tell the Tango device to do something. In Tango, an action is triggered using a command.

For starters, here are some very simple commands. Commands have a name, an optional input parameter, and an optional return value. Multiple parameters are not supported, and neither are complex types.

main.py#
from tango.server import Device, command


class MegaCoffee3k(Device):

    @command
    def Brew(self):
        print("brewing coffee! (but nobody knows)")

    @command
    def BrewNoName(self) -> str:
        return "brewing coffee for someone!"

    @command
    def BrewName(self, name: str) -> str:
        return f"brewing coffee for {name}!"

    @command
    def BrewNames(self, names: list[str]) -> list[str]:
        return [f"brewing coffee for {name}!" for name in names]

    @command(doc_in="Name of coffee drinker", doc_out="Order response")
    def BrewNameDoc(self, name: str) -> str:
        return f"brewing coffee for {name}!"

    @command(
        dtype_in=str,
        doc_in="Name of coffee drinker",
        dtype_out=str,
        doc_out="Order response",
    )
    def BrewNameDocDtype(self, name):
        return f"brewing coffee for {name}!"


if __name__ == "__main__":
    MegaCoffee3k.run_server()

Sorry, still TODO!

Sorry, still TODO!

In Python, you need to import command() and then use that to decorate a method on the Device. In other languages, it is a little more complicated.

You have the following commands:

  • Brew has no input or output parameters.

  • BrewNoName has no input, but returns a string.

  • BrewName accepts an input string and returns a string.

  • BrewNames accepts a list of strings and returns a list of strings.

  • BrewNameDoc shows how the input and output parameters can be documented. There isn’t a way to document the command itself.

  • BrewNameDocDtype is the same as the previous, but shows an alternative (older) way of declaring the types.

The command names use capitalisation as per the Tango Naming Rules.

Tip

Some names are bad choice for commands:

  • Init, State, and Status commands already exist

  • Methods that already exist on the DeviceProxy class, including: alias, connect, description, info, lock, name, ping, reconnect, unlock, get_..., set_..., is_..., put_, read_..., write_..., etc.

  • Anything starting with an underscore, _.

Run this example, and in a second terminal, use the device proxy client to check if it is working:

>>> dp.Brew()  # nothing on client, but server will print a message
>>> dp.BrewNoName()
'brewing coffee for someone!'
>>> dp.BrewName("Java01")
'brewing coffee for Java01!'
>>> dp.BrewNames(["I", "need", "coffee"])
['brewing coffee for I!', 'brewing coffee for need!', 'brewing coffee for coffee!']

Calling the command as a function is a convenience provided by the DeviceProxy object. You can also use the more low-level command_inout() method:

>>> dp.command_inout("BrewNoName")
'brewing coffee for someone!'
>>> dp.command_inout("BrewName", "Java01")
'brewing coffee for Java01!'

Tango is case insensitive when accessing commands by name, so all of the following calls access the same command:

>>> dp.BrewNoName()
'brewing coffee for someone!'
>>> dp.brewnoname()
'brewing coffee for someone!'
>>> dp.command_inout("brewNONAME")
'brewing coffee for someone!'

You can also see how the documentation is available to the client:

>>> help(dp.BrewNameDoc)
# shows:

Help on function f in module tango.device_proxy:

f(*args, **kwds)
    BrewNameDoc(DevString) -> DevString

    -  in (DevString): Name of coffee drinker
    - out (DevString): Order response

>>> print(dp.get_command_config("BrewNameDoc"))
CommandInfo[
    cmd_name = "BrewNameDoc"
    cmd_tag = 0
    disp_level = tango._tango.DispLevel.OPERATOR
    in_type = tango._tango.CmdArgType.DevString
    in_type_desc = "Name of coffee drinker"
    out_type = tango._tango.CmdArgType.DevString
    out_type_desc = "Order response"
]

>>> print(dp.get_command_config("BrewNameDocDtype"))
CommandInfo[
    cmd_name = "BrewNameDocDtype"
    cmd_tag = 0
    disp_level = tango._tango.DispLevel.OPERATOR
    in_type = tango._tango.CmdArgType.DevString
    in_type_desc = "Name of coffee drinker"
    out_type = tango._tango.CmdArgType.DevString
    out_type_desc = "Order response"
]

>>> print(dp.get_command_config("BrewName"))
CommandInfo[
    cmd_name = "BrewName"
    cmd_tag = 0
    disp_level = tango._tango.DispLevel.OPERATOR
    in_type = tango._tango.CmdArgType.DevString
    in_type_desc = "Uninitialised"
    out_type = tango._tango.CmdArgType.DevString
    out_type_desc = "Uninitialised"
]

The get_command_config() method provides all the details about a command.

To simplify the implementation of all clients and servers, the data types available to commands are limited:

  • simple types: integer, float, string, boolean

  • lists of simple types

  • special structures:

    • a list of numbers combined with a list of strings: DevVarDoubleStringArray and DevVarLongStringArray

    • an encoded byte array, with string indicating the format: DevEncoded

Tip

For more complicated input and output data structures, it is common to use a string that is serialised and de-serialised using JSON. This allows structures like dicts to be passed between client and server. The downside is that the schema of those dicts is not obvious.

Tip

You can easily get a list of all the commands a Tango device offers:

>>> dp.get_command_list()
['Brew', 'BrewName', 'BrewNameDoc', 'BrewNameDocDtype', 'BrewNames', 'BrewNoName', 'Init', 'State', 'Status']

State and Status are special, and show up as commands and attributes. Normally we access them as commands. Init is a built-in command.

Note

For PyTango, here is the full list of the data types.

Tango does not support 2-D arrays (images) for commands. PyTango does not support enumerated types (DevEnum) for commands.

So far, so good. Commands were easy, next up: attributes.