This tutorial covers how to communicate with a YARP Port without using the YARP library.
You might want to do this when trying to hook up some on your non-YARP-using code with a YARP robot. Or you may just want to know that users of your robot will have this option in future.
YARP Ports can be communicated with in a choice of protocols. Some of those protocols are very simple, simple enough to use without any code or to implement yourself in your own code.
The tutorial has two parts. For the first part, you will need the "telnet" program or an equivalent for this tutorial. It is used just as a way to create a socket from the command-line; we don't rely on any special parts of the telnet protocol. For the second part, we use a few lines of python to repeat the steps done manually with telnet. There are examples of interoperating with YARP from other languages in the example/external
subdirectory of the YARP source code (YARP can also be used directly from many languages via SWIG, but that is a different topic).
For concreteness, let's set up a very simple YARP system to interact with. In separate terminals, run:
yarp server yarp read /read yarp write /write yarpdev --device controlboard --subdevice fakeMotor --name /motor --axes 5
The yarp server
command is the yarp name server, which creates a Port called /root
. We can communicate with that Port in order to find out how to communicate with all other Ports. The yarp read /read
command creates a Port called /read
, and prints anything sent to it onto standard output. The yarp write /write
command creates a Port called /write
, and sends anything typed on standard input to that Port. The yarpdev
command creates a fake motor controlboard with 5 controllable axes.
As a reference, if we type yarp name list
we can see a list of all ports created here:
registration name /motor/command:i ip 127.0.0.1 port 10032 type tcp registration name /motor/quit ip 127.0.0.1 port 10052 type quit registration name /motor/rpc:i ip 127.0.0.1 port 10022 type tcp registration name /motor/state:o ip 127.0.0.1 port 10042 type tcp registration name /read ip 127.0.0.1 port 10002 type tcp registration name /root ip 127.0.0.1 port 10000 type tcp registration name /write ip 127.0.0.1 port 10012 type tcp registration name fallback ip 224.2.1.1 port 10001 type mcast *** end of message
We can see /root
, the name server, /read
and /write
, and four ports related to the controlboard. We're not concerned here with the details of what you can do with motors, you can find out about that elsewhere.
The yarp read /read
program is waiting for data to be sent to the port /read
, which it will then print out. Let's make it happy.
From the output of yarp name list
", we see that /read
is at address 127.0.0.1
with port number 10002
. Let's do:
(Replace these numbers for whatever they are on your system).
We do this, and nothing happens, telnet just sits there doing nothing. That's normal. YARP ports don't respond immediately, they are waiting for a header that tells them what protocol you want to use (there are several).
Type carefully into telnet (on Windows you'll have to get this right first time without any visual feedback):
CONNECT foo
(you can substitute "foo" with anything you like). Hit return after you type this. Finally, we get a response:
Welcome foo
Hit return again. You should see something like:
Port command not understood. Type d to send data to the port's owner. Type ? for help.
Type:
?
and hit return. You should see something like:
This is a YARP port. Here are the commands it responds to: * Gives a description of this port d Signals the beginning of input for the port's owner do The same as "d" except replies should be suppressed ("data-only") q Disconnects i Interrupt parent process (unix only) r Reverse connection type to be a reader /port Requests to send output to /port !/port Requests to stop sending output to /port ~/port Requests to stop receiving input from /port a Signals the beginning of an administrative message ? Gives this help
Good, we are clearly talking to a YARP port (it says so). Let's use the first command listed to get some information about the port. Type:
*
and hit return. You should see something like:
This is /read at tcp://127.0.0.1:10002 There are no outgoing connections There is an input connection from foo to /read using text
We are indeed connected to the /read
port, and our connection is listed. The using text
part means we are using a text-mode protocol, chosen by starting the communication with CONNECT
(other choices include more efficient binary protocols, protocols that switch to using udp, multi-cast, shared memory, etc).
Every single YARP port responds to the kinds of commands we've used so far. We could connect to /write
or /root
or /motor/...
and do the same thing.
So far we have chatted with the /read
Port and found out some information about it that we already knew (its name is /read
and we are connected to it).
Now let's send some data to the owner of the /read
Port, which is the yarp read /read
program. We expect that this data will get printed out on the terminal where that program is running.
Hit return without a command. You should see something like:
Port command not understood. Type d to send data to the port's owner. Type ? for help.
Okay, so "d" sends data to the port's owner. Let's do it. Type:
d
and hit return. Nothing happens, but now you are talking to the owner of the Port, rather than the Port itself. What you can type now depends on what the owner is expecting. There are several possible data-types in YARP, and users can easily define their own. All well-behaved data-types should follow the YARP Standard data representation format, which is particularly easy to enter in text-mode - it is just a list of space-separated numbers, strings, and nested structures.
Let's see what happens - type:
hello yarp
(And hit return as always).
Nothing happens in telnet, but if you look on the terminal running yarp read /read
, you should see your text appear.
(If you find it disconcerting that you got no acknowledgement in telnet, next time you connect to a port, start with CONNACK foo
rather than CONNECT foo
, this is a small variant of the protocol that always supplies acknowledgements).
Okay, we're done for now - type:
q
(and hit return) and the connection will close.
Let's try talking to another Port. For the controlboard, /motor/rpc:i
is a useful Port for sending commands and getting responses. Refer back to the yarp name list
output we got earlier, or type on the command line:
yarp name query /motor/rpc:i
This gives us the machine and socket port number we need to contact this Port, in my case machine 127.0.0.1
and socket port number 10042
. So do:
telnet 127.0.0.1 10042
(Replace these numbers for whatever they are on your system).
Then type:
CONNECT bar
(you can substitute bar
with anything you like). Hit return after you type this. We get a response:
Welcome bar
Let's send some data. Type:
d help
Some helpful YARP Port owners respond to help
with a brief list of the kinds of data the expect. In this case, you should see something like:
"[help]" "[help] [more]" "[get] [axes]" "[get] [name] $iAxisNumber" "[set] [pos] $iAxisNumber $fPosition" "[set] [rel] $iAxisNumber $fPosition" "[set] [vmo] $iAxisNumber $fVelocity" "[get] [enc] $iAxisNumber" "[set] [poss] ($f0 $f1 $f2 $f3 $f4)" "[set] [rels] ($f0 $f1 $f2 $f3 $f4)" "[set] [vmos] ($f0 $f1 $f2 $f3 $f4)" "[set] [aen] $iAxisNumber" "[set] [adi] $iAxisNumber" "[get] [acu] $iAxisNumber" "[get] [acus]" [ok]
You'd need to consult with other tutorials to see what these do. Let's send a simple command to get the number of axes in the controlboard. Type:
d [get] [axes]
(As a shorthand, you can omit the "[" and "]" characters and everything will still work).
You should see a response like:
[is] [axes] 5 [ok]
Checking back, 5 is indeed the number of axes we specified that our test controlboard should have.
If this were a real robot, we could go on to turn on power and control the motors. As it is, we can do it in simulation. Type:
d [set] [poss] (10 10 -10 -10 5) d [get] [enc] 2
This sets the position of the 5 motors, and then checks the position of the third one (index 2). The responses are something like:
[ok] [is] [enc] -10.0 [ok]
Here is a python program for sending a command to a YARP port and receiving a response. The program does not use the YARP library (of course it is perfectly possible to use YARP from python/perl/ruby/tcl/... directly, this is just to show that you don't have to). First it sends a query to the name server to find the address of the port whose name is passed as the first argument to the program. Then it sends the string specified as the second argument to the program to that address, and prints the response.
The program assumes that the name server is running on the local machine, at socket port number 10000
. If not, it is best to add the following find_name_server.py
module for reading the address of the server from YARP configuration files:
Here's an example of using the program assuming it is called yarprpc.py
:
# check number of axes on simulated motor controlboard python yarprpc.py /motor/rpc:i "[get] [axes]" [is] [axes] 5 [ok] # move joint 0 on simulated motor controlboard python yarprpc.py /motor/rpc:i "[set] [pos] 0 45.0" [ok]
So far, we have been the ones to initiate all communication. We send data to a Port's owner, and possibly get a reply back, but we are in charge.
Often, the initiative should be in the other direction. For example, the yarp write /write
program takes data typed on the terminal and sends it out to anyone listening. How can we listen to it?
The usual answer within YARP is just to connect the /write
Port to another Port /foo with the yarp connect /write /foo
. But in this tutorial, we are trying to read from YARP without using the YARP libraries. It would take some work to create a full Port, and we certainly couldn't do it with just telnet.
There's an easier way. The first step is to connect just as we did before. We check where /write
is with yarp name query /write
, for my system it is:
registration name /write ip 127.0.0.1 port 10012 type tcp *** end of message
This gives us the machine and socket port number we need to contact this Port, in my case machine 127.0.0.1
and socket port number 10012
. So do:
telnet 127.0.0.1 10012
(Replace these numbers for whatever they are on your system).
Then type:
CONNECT foo
(you can substitute "foo" with anything you like). Hit return after you type this. We get a response:
Welcome foo
So far things are just the same. Now, we don't want to send data to this Port's owner, we want to read it. Just as a check, type something on the yarp write /write
terminal and hit return - it will NOT show up on the telnet terminal. You never receive data in YARP unless you are expecting it. So let's tell the /write
Port that we are expecting data. Type:
?
and hit return. You should see something like:
This is a YARP port. Here are the commands it responds to: * Gives a description of this port d Signals the beginning of input for the port's owner do The same as "d" except replies should be suppressed ("data-only") q Disconnects i Interrupt parent process (unix only) r Reverse connection type to be a reader /port Requests to send output to /port !/port Requests to stop sending output to /port ~/port Requests to stop receiving input from /port a Signals the beginning of an administrative message ? Gives this help
Look at the listing for r
- "Reverse connection type to be a reader". This is what we want. Type:
r
and hit return. Nothing happens. But if you go to the yarp write /write
terminal and type something, such as:
hello yarp
Then on the telnet terminal you will see:
do hello yarp
So we got the message. What is do
? It is a variant of d
that tells the listener that a reply is not expected.
Once we convert to being a reader, there is no going back. The only safe way to take control again is to cut the connection (<control>-] q <return>
in telnet) and connect again.
Let's try another Port, the /motor/state:o
Port. Make the appropriate telnet connection based on yarp name query /motor/state:o
, then type:
CONNECT foo r
You should see a stream of data, something like:
... do 2654 1215723086.182938 10.0 10.0 -10.0 -10.0 5.0 do 2655 1215723086.203243 10.0 10.0 -10.0 -10.0 5.0 do 2656 1215723086.226119 10.0 10.0 -10.0 -10.0 5.0 do 2657 1215723086.246381 10.0 10.0 -10.0 -10.0 5.0 do 2658 1215723086.266672 10.0 10.0 -10.0 -10.0 5.0 do 2659 1215723086.286925 10.0 10.0 -10.0 -10.0 5.0 do 2660 1215723086.307388 10.0 10.0 -10.0 -10.0 5.0 ....
The line starting with d
signals data; do
means we're not expected to reply. The do
is followed by some numbers - this is a message index and timestamp, an optional part of the protocol (important for motor information though). Then the actual positions of the motors are listed, exactly as we set them earlier.
We've been using yarp name list
or yarp name query /PORT
to find out where ports are. What if we wanted to do this step too from telnet or our own program?
No problem. All we need is the address of the name server Port (which is just like any other Port), and then we can get all other addresses from it.
By default, the YARP name server runs on socket port number 10000
, so then you just need to find what machine it is running on. You could choose to always run it on a standard machine at your lab.
Suppose we know the server is on 127.0.0.1
with socket port number 10000
. Now if we want to find out the address of the /read
Port, here is how we do it. Connect to the name server with telnet, and type:
CONNECT foo d help
The response is something like:
Welcome foo Here are some ways to use the name server: + help + list + register $portname + register $portname $carrier $ipAddress $portNumber (if you want a field set automatically, write '...') + unregister $portname + query $portname + set $portname $property $value + get $portname $property + check $portname $property + match $portname $property $prefix + route $port1 $port2 + gc *** end of message
In the list above, we can see query $portname
. Sounds good, let's try it. Type:
d query /read
and the response should be something like:
registration name /read ip 127.0.0.1 port 10002 type tcp *** end of message
So we have our replacement for the yarp name query /PORT
command.
Text-mode communication is fine for small messages. It isn't very useful for transmitting images or sound. For that, we need a binary protocol; at this point there's no avoiding reading hairy documentation like Port and connection protocols and writing some code.
There is sample C code for reading binary data (images in particular) in the directory example/external/c
- there are also examples in C that translate the python examples above.