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.
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
, andStatus
commands already existMethods 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
andDevVarLongStringArray
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.