Learning ay_trick
- ay_trick is a useful framework to build integrated robotic applications. It provides a CUI interface to execute scripts and commands where you are released from the bothering ROS setup; once they have established, you can keep using the connection.
Installation †
Refer to Installation to install ay_trick. Relative packages are also needed according to the purposes.
Overview [Basic] †
ay_trick consists of the following components:
- CUITool
- Provides CUI to run scripts and commands. You can execute any Python commands, and Scripts of ay_trick.
- Script
- Python scripts to control robots, sensors, simulators, etc. Each script has a specific interface (Help and Run functions) so that they can be executed from CUITool and DirectRun.
- CoreTool
- CoreTool is an object shared among Scripts that maintains the ROS connections (publishers, subscribers, service clients and servers, actionlib, etc.). Through CoreTool, Scripts can access to the robots and sensors. CoreTool also stores global variables shared among Scripts.
- DirectRun
- While CUITool provides CUI to run Scripts, DirectRun gives a way to run Scripts without user interface.
File System [Basic] †
ay_tools/ +---ay_trick/ | +---ay_trick/ #ROS package of ay_trick | | +---scripts/ | | core_tool.py, cui_tool.py, direct_run.py | +---ay_skill/ #Directory to store Scripts +---ay_skill_extra/ #Directory to store extra (e.g. your) Scripts
CUITool [Basic] †
For running CUITool simply (without robots, sensors, simulations),
$ roscore (In another terminal) $ rosrun ay_trick cui_tool.py
Then you will see CUI like:
trick or quit>
By typing Script name or Python command and pressing enter, you can execute Scripts and Python commands.
Let's see an example: Type hello and press enter.
trick or quit> hello Hello World! This is a script. We have a CoreTool object: <core_tool.TCoreTool object at 0x7f9fc6176710> Bye trick or quit>
This executed hello.py in the ay_tools/ay_trick/ay_skill/ directory.
Executing a script with -help option, you can see the help message:
trick or quit> hello -help Hello-world script. Usage: hello
Script [Basic] †
Writing a Script †
Each script is written in Python with two specific functions: Help and Run. Help is a function that returns a help message including a brief explanation of the script and its usage. Run is the main function of the script that has an argument of CoreTool object.
Where to Put a Script? †
ay_tools/ +---ay_trick/ | +---ay_skill/ #<-- You can put here, but not recommended! +---ay_skill_extra/ #<-- Put here!
A recommended way is: Create your directory in ay_tools/ay_skill_extra/, and put your scripts there.
How to Write a Script? †
Let's see a template of scripts.
#!/usr/bin/python
from core_tool import *
def Help():
return '''Template of script.
Usage: template'''
def Run(ct,*args):
print ct
Help is a function that returns a string.
Run is a function with arguments: ct: a CoreTool object, and args: a variable number of arguments. CoreTool is explained later. args is user-defined, i.e. you can define and use args as you wish.
You may return values at the end of Run.
A brief explanation of a variable number of arguments. This is a functionality of Python. When you want to write a function where the number of arguments varies, you can do that with an argument variable with asterisk (*).
For example,
def Run(ct,*args):
print 'type of args:', type(args)
print 'length of args:', len(args)
print 'content of args:', args
where args is a variable number of arguments. You can call this function with different number of arguments, for example:
ct= None
Run(ct, 'aaa')
Run(ct, 1, 2.5, 'bbb', [1,2,3])
In the function, args is treated as a tuple. The results of the above example are:
type of args: <type 'tuple'> length of args: 1 content of args: ('aaa',)
type of args: <type 'tuple'> length of args: 4 content of args: (1, 2.5, 'bbb', [1, 2, 3])
How to Add a Search Path? [Intermediate] †
Add a path to the Python module search path list (sys.path).
For example, following code adds ${HOME}/myskill to the search path (${HOME} denotes your home directory).
sys.path.insert(0, os.environ['HOME']+'/myskill')
You may need to import sys and os to use above code. You may need to put a blank file ${HOME}/myskill/__init__.py for telling Python the directory is a Python directory.
Executing Script †
There are two ways to run a script.
Here we assume that your script is created in your_dir directory (in ay_skill_extra), with the name your_script.py.
From CUITool †
In CUITool, type:
> your_dir.your_script A, B, C
where A, B, C are parameters.
Then, the Run function defined in your_dir/your_script.py is executed as:
Run(ct, A, B, C)
where CoreTool object ct is automatically supplied.
If the Run function returns some values, you will see the values in CUITool.
From Other Script (ct.Run) †
In other scripts, you can execute the Script by:
ct.Run('your_dir.your_script', A, B, C)
where ct is CoreTool object. It imports the Run function in your script, and execute it with the given parameters.
Note that in every execution of ct.Run, the file your_dir.your_script is reloaded.
From Other Script (ct.Load) [Intermediate] †
Another way is loading your_dir.your_script as a module. "Loading module" is done in the standard Python module import manner.
Importing your_dir.your_script is coded as follows:
m_your_script= ct.Load('your_dir.your_script')
The Run function in your_script can be executed by:
m_your_script.Run(ct, A, B, C)
Note that in every execution of ct.Load, the file your_dir.your_script is reloaded.
The benefits to use ct.Load are:
- You can access to all objects defined in the Script, such as functions other than Run and classes.
- More efficient when repeating the execution of Run. Both ct.Run and ct.Load has an overhead (such as searching the file in the path list, and reloading module). When repeating ct.Run, this overhead is costed every time, while executing ct.Load once and repeating m_your_script.Run, the overhead is costed at once.
CoreTool [Intermediate] †
CoreTool is a shared object among Scripts. It provides:
- ROS connection helpers (publishers, subscribers, service servers, service clients, actionlib, etc.).
- ROS connections with robots.
- Interface of Executing Other Scripts.
- Sharing variables.
- etc.
ROS Connection Helpers †
CoreTool provides methods to support establishing ROS connections. Of course you can use standard ROS connection methods, but there are some benefits to use the helpers of CoreTool:
- Connection objects (e.g. publisher) can be shared among Scripts.
- Some common codes are unified (e.g. using rospy.wait_for_service before rospy.ServiceProxy).
- The connections are automatically released when destroying CoreTool.
ct.AddSub †
ct.AddSub(name, port_name, port_type, call_back, callback_args=None, queue_size=None, buff_size=65536, tcp_nodelay=False)
Making a subscriber with rospy.Subscriber. The parameters except for name are fed to rospy.Subscriber directly.
The parameter name is used for maintaining the subscriber object in CoreTool. You can use ct.sub.name to access the subscribe object.
It does nothing if name already exists. It returns True in success.
Example:
ct.AddSub('joy', 'joy', sensor_msgs.msg.Joy, lambda msg: CallbackJS(param, msg))
CallbackJS is a callback function.
You can stop and remove the subscriber by:
ct.DelSub(name)
ct.AddPub †
ct.AddPub(name, port_name, port_type, subscriber_listener=None, tcp_nodelay=False, latch=False, headers=None, queue_size=10)
Making a publisher with rospy.Publisher. The parameters except for name are fed to rospy.Publisher directly.
The parameter name is used for maintaining the publisher object in CoreTool. You can use ct.pub.name to access the publisher object.
It does nothing if name already exists. It returns True in success.
Example:
# Defining a publisher:
ct.AddPub('js_rate', 'robot/joint_state_publish_rate', std_msgs.msg.UInt16)
# Using the publisher:
ct.pub.js_rate.publish(rate)
You can remove the publisher by:
ct.DelPub(name)
ct.AddSrvP †
ct.AddSrvP(name, port_name, port_type, persistent=False, time_out=None)
Adding a service proxy with rospy.ServiceProxy. The parameter time_out is used in rospy.wait_for_service, the parameters port_name, port_type, persistent are fed to rospy.ServiceProxy, and the parameter name is used for maintaining the service proxy object in CoreTool. You can use ct.srvp.name to access the service proxy object.
It does nothing if name already exists. It returns True in success and False in failure.
Example:
ct.AddSrvP('clear_obj', '/fingervision/fv_3_l/clear_obj', std_srvs.srv.Empty, persistent=False, time_out=3.0)
You can remove the service proxy by:
ct.DelSrvP(name)
ct.AddActC †
ct.AddActC(name, port_name, port_type, time_out=None, num_wait=1)
Adding an instance of actionlib.SimpleActionClient. The action_client.wait_for_server(time_out) is executed num_wait times until the connection is established. The parameters port_name, port_type are used to create SimpleActionClient. The parameter name is used for maintaining the SimpleActionClient object in CoreTool. You can use ct.actc.name to access the object.
It does nothing if name already exists. It returns True in success and False in failure.
Example:
ct.AddActC('traj', '/follow_joint_trajectory', control_msgs.msg.FollowJointTrajectoryAction, time_out=3.0)
You can remove the SimpleActionClient by:
ct.DelActC(name)
ct.AddDynConfig †
ct.AddDynConfig(name, node_name, time_out=None)
Setup dynamic_reconfigure.client.Client.
You can remove the dynamic_reconfigure client by:
ct.DelDynConfig(name)
ROS Connection with Robots †
CoreTool provides a framework to establish the ROS connection with robots, and operating the robot using ct.robot variable.
Refer to Step-by-step Robot Control with ay_trick for the details.
Interface of Executing Other Scripts †
See above: Executing Script.
Sharing Variables: Attribute †
The CoreTool object provides a way to share variables among Scripts (e.g. ct.robot). Here we describe a way to share data among Scripts, named Attribute.
It is a multidimensional dictionary of Python (dict). CoreTool provides utility to get and set the elements. Note that all keys are string.
Multidimensional Dictionary †
Multidimensional dictionary is a dictionary of dictionary of ..., for example:
{'A': {'a': 100} }
which is suite for representing structured knowledge.
For example, let's think an object obj1:
{'obj1': {'position': [1,2,3], 'color': [255,0,0]} }
CoreTool has such a member variable as ct.attributes, and provides methods to manipulate it.
Let's define the terminology:
- Keys: A sequence of keys to access the value. Each key is a string type. In above examples, ('A','a'), ('obj1', 'position'), and ('obj1', 'color') are Keys.
- Value: The value accessed by the Keys.
SetAttr †
ct.SetAttr(key1,key2,...,value)
Set value to the Keys of (key1,key2,...).
Note that it automatically creates sub-dictionaries if they do not exist.
For example,
ct.SetAttr('obj1','position', [1,2,3])
GetAttr †
ct.GetAttr(key1,key2,...)
Get the Value of the Keys (key1,key2,...).
If one of Keys does not exist, the KeyError exception is raised.
For example,
ct.GetAttr('obj1','position')
which returns [1,2,3] in the above example.
GetAttrOr †
ct.GetAttrOr(default, key1,key2,...)
Similar to ct.GetAttr(key1,key2,...), but if the Keys do not exist, it returns default.
For example,
ct.GetAttrOr(None, 'obj1','orientation')
which returns None in the above example.
HasAttr †
ct.HasAttr(key1,key2,...)
Returns the existence (True/False) of the Keys (key1,key2,...).
For example,
ct.HasAttr('obj1','position')
ct.HasAttr('obj1','orientation')
They return True, False respectively in the above example.
DelAttr †
ct.DelAttr(key1,key2,...)
Delete the Keys Keys (key1,key2,...).
For example,
ct.DelAttr('obj1')
AddDictAttr †
ct.AddDictAttr(key1,key2,..., dict_value)
Merge multidimensional dictionary dict_value to the Keys of (key1,key2,...).
The key difference from SetAttr is the merge operation of dictionary. For example,
ct.SetAttr(key1,key2, {key3: value})
replaces the Value of Keys (key1,key2) by the dictionary {key3: value}.
ct.AddDictAttr(key1,key2, {key3: value})
just modifies the Value of Keys (key1,key2,key3) by value.
For example, when we have:
{'obj1': {'color': [255, 0, 0], 'position': [1, 2, 3]} }
SetAttr behaves like:
> ct.SetAttr('obj1',{'orientation': [0,0,0,1]})
> ct.GetAttr()
{'obj1': {'orientation': [0, 0, 0, 1]} }
On the other hand, AddDictAttr behaves like:
> ct.AddDictAttr('obj1',{'orientation': [0,0,0,1]})
> ct.GetAttr()
{'obj1': {'color': [255, 0, 0], 'position': [1, 2, 3], 'orientation': [0, 0, 0, 1]} }
Temporary Attribute †
A special key TMP is used for temporary attributes such as planning results.
Note that TMP=='*'.
For example,
ct.SetAttr(TMP,'plan', [1,1,1])
DirectRun [Intermediate] †
DirectRun is a tool to execute Scripts directly, i.e. without CUITool.
Exampes:
[TODO: Write about rosrun ay_trick direct_run.py]