Tutorial

Preparation

ivy-python is a message bus, so to demonstrate its usage we need at least two agents on the bus for them to exchange messages.

Fortunately, ivy-python ships with an already full-featured, generic agent called ivyprobe.py.
To follow this tutorial, you need to open two terminals next to one another.

Let’s open a new terminal, and launch ivyprobe.py:

$ ivyprobe.py -s hi 'Hello (.*)'
Broadcasting on ivydefault
Go ahead! (type .help for help on commands)
Changes in applications' bindings are now shown

What this command does is:

  • it creates a new agent, name “pyivyprobe”, on the standard ivy bus.

  • this agent registers two subscriptions: one for messages being exactly “hi”, and the other one for messages corresponding to the regular expression “Hello (.*)”. We will explain these concepts below.

This gives us an agent to discuss with from ivy-python.
Note that this generic agent, shipped with ivy-python, can be very handy to look at what happens on an Ivy bus.

Also note that ivyprobe indifferently uses the terms “subscriptions” or “bindings” when speaking about subscriptions to messages. This is explained in the section Subscribing to messages, below.

Creating a new agent

Now open a new terminal and launch a new Python interpreter, then type:

>>> from ivy.std_api import *
>>> IvyInit("my_python_agent")
>>> IvyStart()

We have just created out first agent, named my_python_agent and we connected it to the bus. Looking at the terminal where ivyprobe is launched, you see it appeared on the bus:

ivyprobe
Ivy application 127.0.0.1:xxxxx (my_python_agent) has connected
Ivy applications currently on the bus: my_python_agent

(Note: you will see a specific number instead of xxxxx: it is the port on which the agent can be reached. Unless you are involved in developing the library itself, you don’t need to worry about it: from a user point of view, agents are identified by name.)

Subscribing to messages

Our first subscription

Agents only receive messages they subscribed for. The subscription mechanism is based on regular expressions. It can be:

  • a simple text, like “hi!”: when someone on the bus sends this exact string, the agent receives it,

  • a regular expression, like “hello .*”: any message sent on the bus and matching the regular expression is received by our agent.

Let’s register our first subscription:

1>>> def on_hi(agent):
2...     print("Agent %r sent hi!"%agent)
3...
4>>> IvyBindMsg(on_hi, "hi!")
50

There are several things to note here.

  • The lines 1-2 declare a callback: this is the function that will be called when the message is received. Callbacks are an important concept: almost everything happening on the bus triggers a callback that was previously bound to a particular event.

  • On line 4, we bind this callback to the definition of the message, here the simple string “hi!”.

  • Line 5: IvyBindMsg() returns an integer (0), which is the index of the new registered binding (or subscription). This index can be used to remove a subscription later, using IvyUnbindMsg().

Now if you look at the terminal where ivyprobe.py is running, it displays something like this:

ivyprobe
127.0.0.1:xxxxx (my_python_agent) added regexp id=0: hi!

showing that ivyprobe registered the new subscription for our Python agent.

Now you can use ivyprobe to send message on the bus. In ivyprobe, everything you type is directly sent on the bus (except for recognized commands, type help for details).

So, in the terminal where ivyprobe runs, type: hi! and hit return. It displays:

ivyprobe
hi!
Sent to 1 peers

ivyprobe indicates the number of agents (peers) the message was sent to. Plus, on the terminal where Python runs, the following message was displayed:

Agent 127.0.0.1:xxxxx (pyivyprobe) sent hi!

Our callback function, on_hi() was called and it printed the message. We have successfully registered a subscription and received our first message!

Before going further, you can try to send other messages from ivyprobe, such as hello, hi or Hi!: ivyprobe always says:

Sent to 0 peers

and indeed, our python agent does not receive any of these messages. This is because currently its only subscription is for the exact string hi!. In the next section, we see how to register more flexible subscriptions.

Subscriptions and bindings To subscribe to a message, an agent

binds a callback function to a regular expression a function. This is why in “Ivy speaking” both terms “subscriptions” and “bindings” are synonyms, and we use them indifferently.

Using regular expressions

In Ivy, a subscription is declared using a regular expression, or regexp.

Note

We suppose that the reader is already familiar with his concept. If not, you will find numerous introductory materials on the internet. We suggest the Regular Expression HOWTO on python.org.

Let’s subscribe to all messages starting with “Hello “:

>>> def on_hello(agent):
...     print("Agent %r said hello"%agent)
...
>>> IvyBindMsg(on_hello, "Hello .*")
1

Looking at the terminal where ivyprobe runs, you can see that the new subscriptions was registered as well. From ivyprobe, send the string Hello World!:

ivyprobe
Hello World!
Sent to 1 peers
Our python agent displays Agent 127.0.0.1:xxxxx (pyivyprobe) said hello: the message was successfully delivered.
You can try to send other messages from ivyprobe, for example:
  • the following message will be received by our python agent: “Hello you”, “Hello you and all”, even “Hello “ (with a trailing whitespace);

  • but these messages won’t be sent to our python agent: “Hello” (no trailing whitespace), or “I say Hello World”

Using groups in regexps

We now know how to subscribe to messages with regexps, but it would be more interesting if we could get back the interesting parts of a message. In the previous section, the callback never gets the name after the initial string “Hello “ in the message.

Suppose we want to monitor the change of status of a set of doors. You have an equipment monitoring all doors, issuing message like status change: door <DOOR_ID>: <open|close>.
We want to listen to such message, so we take advantage of groups within regexps, i.e. parts of a regexps that are surrounded by the ( and ) metacharacters.
An example being worth a thousand words, let’s write it:
>>> def on_door_status(agent, door, status):
...     print("Agent %s: door %s status is: %s"%(agent, door, status)
...
>>> IvyBindMsg(on_door_status, "status change: door ([^ ]*): (open|close)")
2

Now if you send such a message from ivyprobe:

status change: door MAIN_ENTRANCE: open

our python agent’s callback on_door_status() is called and prints:

Agent pyivyprobe@localhost: door MAIN_ENTRANCE status is: open

Please note that:

  • the callback always receives the sending agent as its first parameter,

  • it also receives as many additional parameters as groups in the regexp supplied to IvyBindMsg, supplied to the callback in the same order as in the subscription regexp.

In fact, it is completely possible to supply a generic callback such as this one:

>>> def generic_callback(*args):
...   agent = args[0]
...   args = args[1:]
...   print(("Agent %s sent a matching message " +
...          "with %i arguments: %s")%(agent,len(args),str(args)))

and you’ll probably write a variation of such a callback if you want to bind the same callback function to different subscriptions.

Sending messages

Remember: at the very beginning of this tutorial ivyprobe was launched with this command:

$ ivyprobe.py -s hi 'ivyprobe (.*)'

By doing this, we already registered two subscriptions for it. We can ask it to print them:

ivyprobe
.regexps
Our subscriptions: 0:'hi', 1:'ivyprobe (.*)'

Back to our Python interpreter: to send a message, one uses IvySendMsg(), so let’s send 3 messages:

1 >>> IvySendMsg('hi there')
2 0
3 >>> IvySendMsg('hi')
4 1
5 >>> IvySendMsg('ivyprobe are you here?')
6 1
Each call returns the number of agents to which a message was sent.
As expected:
  • the first message was sent to no-one (lines 1-2),

  • the two subsequent ones were sent to 1 agent (lines 3-4 and 5-6), namely ivyprobe

And ivyprobe acknowledges these messages by printing:

ivyprobe
Received from 127.0.0.1:xxxxx (my_python_agent): <no args>
Received from 127.0.0.1:xxxxx (my_python_agent): ('are you here?',)

Examining the bus

Here is how you can get the list of agents registered on the bus, and how to get informations on them:

 >>> IvyGetApplicationList()  # list of connected agents (not including us)
 ['pyivyprobe']
 >>> ivyprobe=IvyGetApplication('pyivyprobe')  # Get a specific agent by name
 >>> IvyGetApplicationHost(ivyprobe)  # where is it running?
 'localhost'
 >>> IvyGetApplicationName(ivyprobe) # what's is name? This may be useful in a callback
 'pyivyprobe'
 >>> IvyGetApplicationMessages(ivyprobe)  # Get its current subscriptions
 [(0, 'hi'), (1, 'ivyprobe (.*)')]

Note

the ‘agent’ object that each callback receives as its first parameter is a ivy.ivy.IvyClient object, just like the one returned by ivy.std_api.IvyGetApplication() used above.

Terminating an agent

While terminating an agent abruptly will be detected by the other agents on the bus, it is recommended that an agent call ivy.std_api.IvyStop() before exiting. This notifies the other participants on the bus that it signs off, and it also properly terminates the python threads that were started when joining the bus (ivy.std_api.IvyStart()).

Summary

We have seen here how to create and connect on agent to the default Ivy bus, how to choose the messages to be received, how those message are handled upon reception, and how to send messages to others.

Going further

The standard API allows you to send direct messages to application, and offers a facility to manipulate timers. It is fully documented in the ivy.std_api module.

Other source of informations:

  • The ivyprobe utility source code can be found in example/ivyprobe.py, it is built using the std_api.

  • The document ivy package also contains useful informations

  • The documentation found at the Ivy Home Page will provide more context and technical informations, including the original whitepaper. And there is a mailing-list where you can find help and experience from authors and other users