YARP  2.3.68+283-20170530.4+git10f9853
Yet Another Robot Platform
YARP without YARP
Author
Paul Fitzpatrick

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

Test scenario

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 test_motor --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:

$ yarp name list
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.

Talking to a Port

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:

telnet 127.0.0.1 10002

(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

Ah-hah! 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.

Talking to a Port's owner

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]

Writing commands from code

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.

#!/usr/bin/python
# Copyright: (C) 2010 RobotCub Consortium
# Author: Paul Fitzpatrick
# CopyPolicy: Released under the terms of the LGPLv2.1 or later, see LGPL.TXT
import socket
import re
import sys
if len(sys.argv)!=3:
print 'Call as:\n %s /port/to/write/to \"message to send\"'%sys.argv[0]
exit(1)
try:
import find_name_server
name_server = find_name_server.find_name_server()
print "Nameserver is here:", name_server
except:
name_server = ('localhost',10000)
print "Assuming nameserver is here:", name_server
port_name = sys.argv[1]
message = sys.argv[2]
def get_addr(s):
m = re.match("registration name [^ ]+ ip ([^ ]+) port ([0-9]+) type tcp",s)
return (m.group(1),int(m.group(2))) if m else None
# get a single line of text from a socket
def getline(sock):
result = ""
while result.find('\n')==-1:
result = result + sock.recv(1024)
result = re.sub('[\r\n].*','',result)
return result
# send a message and expect a reply
def comm(addr,message):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(addr)
sock.send('CONNACK extern\n')
getline(sock)
sock.send('d\n%s\n' % message)
result = getline(sock)
sock.close()
return result
# ask name server for location
query = get_addr(comm(name_server,"query %s"%port_name))
print "Talking to", port_name, "here:", query
print comm(query,message)

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:

#!/usr/bin/python
# Copyright: (C) 2010 RobotCub Consortium
# Author: Paul Fitzpatrick
# CopyPolicy: Released under the terms of the LGPLv2.1 or later, see LGPL.TXT
import os
def find_name_server():
if "YARP_CONF" in os.environ.keys():
base = os.environ["YARP_CONF"]
elif "HOMEDIR" in os.environ.keys():
base = os.path.join(os.environ["HOMEDIR"],"yarp","conf")
elif "HOME" in os.environ.keys():
base = os.path.join(os.environ["HOME"],".yarp","conf")
else:
print "Please set YARP_CONF to the location reported by this command:"
print " yarp conf"
return None
print "Config files should be in", base
namespace = "/root"
server_filename = "yarp.conf"
namespace_filename = os.path.join(base,"yarp_namespace.conf")
if os.path.exists(namespace_filename):
namespace = open(namespace_filename).read().strip()
namespace = namespace.replace("\"","")
print "Namespace set to", namespace
server_filename = namespace.replace("/","_") + ".conf"
else:
print "Using default namespace,", namespace
print "Expect name server information in", server_filename
server_filename = os.path.join(base,server_filename)
if os.path.exists(server_filename):
info = open(server_filename).readline().strip().split()
info = (info[0], int(info[1]))
print "server contact information:", info
else:
print "cannot find", server_filename
return info

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]

Listening to a Port's owner

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.

Talking to the name server

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

  1. 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.

Binary messages

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.