ivy.ivy module

Using an IvyServer

The following code is a typical example of use:

from ivy.ivy import IvyServer

class MyAgent(IvyServer):
  def __init__(self, agent_name):
    IvyServer.__init__(self,agent_name)
    self.start('127.255.255.255:2010')
    self.bind_msg(self.handle_hello, 'hello .*')
    self.bind_msg(self.handle_button, 'BTN ([a-fA-F0-9]+)')

  def handle_hello(self, agent):
    print('[Agent %s] GOT hello from %r'%(self.agent_name, agent))

  def handle_button(self, agent, btn_id):
    print('[Agent %s] GOT BTN button_id=%s from %r'%(self.agent_name, btn_id, agent))
    # let's answer!
    self.send_msg('BTN_ACK %s'%btn_id)

a=MyAgent('007')

Implementation details

An Ivy agent is made of several threads:

  • an IvyServer instance
  • a UDP server, launched by the Ivy server, listening to incoming UDP broadcast messages
  • IvyTimer objects
group Messages types:
 BYE, ADD_REGEXP, MSG, ERROR, DEL_REGEXP, END_REGEXP, END_INIT, START_REGEXP, START_INIT, DIRECT_MSG, DIE
group Separators:
 ARG_START, ARG_END
group Misc. constants:
 DEFAULT_IVYBUS, PROTOCOL_VERSION, IVY_SHOULD_NOT_DIE IvyApplicationConnected, IvyApplicationDisconnected, DEFAULT_TTL
group Objects and functions related to logging:
 ivylogger, debug, log, warn, error, ivy_loghdlr, ivy_logformatter

Copyright (c) 2005-2021 Sebastien Bigaret <sebastien.bigaret@telecom-bretagne.eu>

class ivy.ivy.IvyClient(ip, port, client_socket, agent_id=None, agent_name=None)

Bases: object

Represents a client connected to the bus. Every callback methods registered by an agent receive an object of this type as their first parameter, so that they know which agent on the bus is the cause of the event which triggered the callback.

An IvyClient is responsible for:

  • managing the remote agent’s subscriptions,
  • sending messages to the remote agent.

It is not responsible for receiving messages from the client: another object is in charge of that, namely an IvyHandler object.

The local IvyServer creates one IvyClient per agent on the bus.

See the discussion in regexps.

Protocol-related methods:
 

start_init(), end_init(), send_new_subscription(), remove_subscription(), wave_bye()

Manipulating the remote agent’s subscriptions:
 

add_regexp(), remove_regexp()

Sending messages:
 

send_msg(), send_direct_message(), send_die_message()

Variables:
  • regexps – a dictionary mapping subscriptions’ ids (as delivered by add_regexp) to the corresponding regular expressions. Precisely, it maps ids to tuples being (regexp_as_string, compiled_regexp). You shouldn’t directly access or manipulate this variable, use add_regexp and remove_regexp instead; however if you really need/want to, you must acquire the regexp_lock before accessing/modifying it.
  • regexp_lock – a non-reentrant lock protecting the variable regexps. Used by methods add_regexp, remove_regexp and send_msg.
add_regexp(regexp_id, regexp)
Raises:IvyIllegalStateError – if the client has not been fully initialized yet (see start_init())
end_init()

Should be called when the initialization process ends.

Raises:IvyIllegalStateError – if the method has already been called (and self.status has already been set to INITIALIZED)
get_next_ping_delta()
get_regexps()
remove_regexp(regexp_id)

Removes a regexp

Returns:

the regexp that has been removed

Raises:
remove_subscription(idx)

Notifies the remote agent that we (the local agent) are not interested in a given subscription.

Parameters:
  • idx: the index/id of a subscription previously registered with send_new_subscription.
send_die_message(num_id=0, msg='')

Sends a die message

send_direct_message(num_id, msg)

Sends a direct message

Note: the message will be encoded by encode_message with numerical_id=num_id and params==msg; this means that if msg is not a string but a list or a tuple, the direct message will contain more than one parameter. This is an extension of the original Ivy design, supported by python, but if you want to inter-operate with applications using the standard Ivy API the message you send must be a string. See in particular in ivy.h:

typedef void (*MsgDirectCallback)( IvyClientPtr app, void *user_data, int id, char *msg ) ;
send_error(num_id, msg)

Sends an error message

send_msg(msg)

Sends the message to the client. The client will receive one message for each of its subscriptions (regexps) that the message matches.

Returns:True if the message was actually sent to the client, that is: if there is one or more regexps matching the message in the client’s subscriptions; returns False otherwise.
send_new_subscription(idx, regexp)

Notifies the remote agent that we (the local agent) subscribe to a new type of messages

Parameters:
  • idx: the index/id of the new subscription. It is the responsibility of the local agent to make sure that every subscription gets a unique id.
  • regexp: a regular expression. The subscription consists in receiving messages matching the regexp.
send_ping()
start_init(agent_name)

Finalizes the initialization process by setting the client’s agent_name. This is a Ivy protocol requirement that an application sends its agent-name only once during the initial handshake (beginning with message of type START_INIT and ending with a type END_INIT). After this method is called, we expect to receive the initial subscriptions for that client (or none); the initialization process completes after end_init is called.

Raises:IvyIllegalStateError – if the method has already been called once
wave_bye(num_id=0)

Notifies the remote agent that we are about to quit

class ivy.ivy.IvyHandler(request, client_address, server)

Bases: socketserver.StreamRequestHandler

An IvyHandler is associated to one IvyClient connected to our server.

It runs into a dedicate thread as long as the remote client is connected to us.

It is in charge of examining all messages that are received and to take any appropriate actions.

Implementation note: the IvyServer is accessible in self.server

handle()
process_ivymessage(msg, client)

Examines the message (after passing it through the decode_msg() filter) and takes the appropriate actions depending on the message types. Please refer to the document The Ivy Architecture and Protocol and to the python code for further details.

Parameters:
  • msg: (should not include a newline at end)
Returns:

False if the connection should be terminated, True otherwise

exception ivy.ivy.IvyIllegalStateError

Bases: RuntimeError

exception ivy.ivy.IvyMalformedMessage

Bases: Exception

exception ivy.ivy.IvyProtocolError

Bases: Exception

class ivy.ivy.IvyServer(agent_name, ready_msg='', app_callback=<function void_function>, die_callback=<function void_function>, usesDaemons=False)

Bases: socketserver.ThreadingTCPServer

An Ivy server is responsible for receiving and handling the messages that other clients send on an Ivy bus to a given agent.

An IvyServer has two important attributes: usesDaemons and server_termination.

Variables:
  • usesDaemons – whether the threads are daemonic or not. Daemonic threads do not prevent python from exiting when the main thread stop, while non-daemonic ones do. Default is False. This attribute should be set through at __init__() time and should not be modified afterwards.
  • server_termination – a threading.Event object that is set on server shutdown. It can be used either to test whether the server has been stopped (server_termination.isSet()) or to wait until it is stopped (server_termination.wait()). Application code should not try to set the Event directly, rather it will call stop() to terminate the server.
  • port – tells on which port the TCP server awaits connection

All public methods (not starting with an underscore _) are MT-safe

Group Communication on the ivybus:
 start(), send_msg(), send_direct_message(), send_ready_message(), handle_msg(), stop()
Group Inspecting the ivybus:
 get_clients(), _get_client(), get_client_with_name()
Group Our own subscriptions:
 get_subscriptions(), bind_msg(), unbind_msg(), _add_subscription(), _remove_subscription(), _get_fct_for_subscription()

Builds a new IvyServer. A client only needs to call start() on the newly created instances to connect to the corresponding Ivy bus and to start communicating with other applications.

MT-safety: both functions app_callback() and die_callback() must be prepared to be called concurrently

Parameters:
  • agent_name: the client’s agent name
  • ready_msg: a message to send to clients when they connect
  • app_callback: a function called each time a client connects or disconnects. This function is called with a single parameter indicating which event occurred: IvyApplicationConnected or IvyApplicationDisconnected.
  • die_callback: called when the IvyServer receives a DIE message
  • usesDaemons: see above.

See also

bind_msg(), start()

bind_direct_msg(on_direct_msg_fct)
bind_msg(on_msg_fct, regexp)

Registers a new subscriptions, by binding a regexp to a function, so that this function is called whenever a message matching the regexp is received.

Parameters:
  • on_msg_fct: a function accepting as many parameters as there is groups in the regexp. For example:
    • the regexp '^hello .*' corresponds to a function called w/ no parameter,
    • '^hello (.*)': one parameter,
    • '^hello=([^ ]*) from=([^ ]*)': two parameters
  • regexp: (string) a regular expression
Returns:

the binding’s id, which can be used to unregister the binding with unbind_msg()

bind_pong(on_pong_callback)
bind_regexp_change(on_regexp_change_callback)
get_client_with_name(name)

Returns the list of the clients registered with a given agent-name

See:get_clients
get_clients()

Returns the list of the agent names of all connected clients

See:get_client_with_name
get_subscriptions()
handle_die_message(msg_id, from_client=None)
handle_direct_msg(client, num_id, msg)
Parameters:
  • client
  • num_id
  • msg
Returns:

handle_msg(client, idx, *params)

Simply call the function bound to the subscription id idx with the supplied parameters.

handle_new_client(client)

Completes connection with the client TODO: maybe add a flag (while connecting) on the client, that would prevent sending msg. etc.. as CNX. not confirmed

handle_pong(client, delta)
handle_regexp_change(client, event, num_id, regexp)
isAlive()
remove_client(ip, port, trigger_application_callback=True)

Removes a registered client

This method is responsible for calling server.app_callback

Returns:the removed client, or None if no such client was found

Note

NO NETWORK CLEANUP IS DONE

static run_callback(callback, callback_description, agent, *args, **kw)

Runs a callback, catching any exception it may raise.

Parameters:
  • callback: the function to be called
  • callback_description: the description to use in the error message, when an exception is raised.
  • agent: the IvyClient triggering the callback, which is passed as the first argument to the callback
  • on_exc: the returned value in case an exception was raised by the callback.
  • All other arguments are passed as-is to the callback.
Returns:

the value returned by the callback, or exc_none if an exception was raised

send_direct_message(agent_name, num_id, msg, stop_on_first=True)

Sends a direct message to the agent named agent_name. If there is more than one agent with that name on the bus, parameter stop_on_first determines the behaviour.

Parameters:
  • agent_name: the name of the agent(s) to which the direct message should be sent.
  • num_id: a numerical identifier attached to the direct message
  • msg: the direct message itself
  • stop_on_first: if True, the message to all agents having the same name will receive the message; if False the method exits after the first message has been sent.
Returns:

True if at least one direct message was sent

send_msg(message)

Examine the message and choose to send a message to the clients that subscribed to such a msg

Returns:the number of clients to which the message was sent
send_ready_message(client)
serve_forever()

Handle requests (calling handle_request()) until doomsday… or until stop() is called.

This method is registered as the target method for the thread. It is also responsible for launching the UDP server in a separate thread, see UDP_init_and_listen() for details.

You should not need to call this method, use start() instead.

start(ivybus=None)

Binds the server to the ivybus. The server remains connected until stop() is called, or until it receives and accepts a ‘die’ message.

Raises:IvyIllegalStateError – if the server has already been started
stop()

Disconnects the server from the ivybus. It also sets the server_termination() event.

Raises:IvyIllegalStateError – if the server is not running started
unbind_msg(num_id)

Unbinds a subscription

Parameters:num_id – the binding’s id, as returned by bind_msg()
Returns:the regexp corresponding to the unsubscribed binding
Raises:KeyError – if no such subscription can be found
class ivy.ivy.IvyTimer(server, nbticks, delay, callback)

Bases: threading.Thread

An IvyTimer object is responsible for calling a function regularly. It is bound to an IvyServer and stops when its server stops.

Interacting with a timer object:
  • Each timer gets a unique id, stored in the attribute id. Note that a dead timer’s id can be reassigned to another one (a dead timer is a timer that has been stopped)
  • To start a timer, simply call its method start()
  • To modify a timer’s delay: simply assign delay, the modification will be taken into account after the next tick. The delay should be given in milliseconds.
  • to stop a timer, assign abort to True, the timer will stop at the next tick (the callback won’t be called)
MT-safety:
Please note: start() starts a new thread; if the same function is given as the callback to different timers, that function should be prepared to be called concurrently. Specifically, if the callback accesses shared variables, they should be protected against concurrency problems (using locks e.g.).
abort

Stops the timer at the next tick.

Creates a new timer. After creation, call the timer’s start() method to activate it.

Parameters:
  • server: the IvyServer related to this timer –when the server stops, so does the timer.
  • nbticks: the number of repetition to make. 0 (zero) means: endless loop
  • delay: the delay, in milliseconds, between two ticks
  • callback: a function called at each tick. This function is called with one parameter, the timer itself
id = None

The timer unique id

run()

Method representing the thread’s activity.

You may override this method in a subclass. The standard run() method invokes the callable object passed to the object’s constructor as the target argument, if any, with sequential and keyword arguments taken from the args and kwargs arguments, respectively.

ivy.ivy.UDP_init_and_listen(broadcast_addr, port, socket_server)

Called by an IvyServer at startup; the method is responsible for:

  • sending the initial UDP broadcast message,
  • waiting for incoming UDP broadcast messages being sent by new clients connecting on the bus. When it receives such a message, a connection is established to that client and that connection (a socket) is then passed to the IvyServer instance.
Parameters:
  • broadcast_addr: the broadcast address used on the Ivy bus
  • port: the port dedicated to the Ivy bus
  • socket_server: instance of an IvyServer handling communications for our client.
ivy.ivy.decode_MSG_params(params)

Implements the special treatment of parameters in text messages (message type: MSG). The purpose here is to make sure that, when their last parameter is not ETX-terminated, they are processed the same way as in the reference ivy-c library.

ivy.ivy.decode_ivybus(ivybus=None)

Transforms the supplied string into the corresponding broadcast address and port

Parameters:ivybus – if None or empty, defaults to environment variable IVYBUS
Returns:a tuple made of (broadcast address, port number). For example:
>>> print decode_ivybus('192.168.12:2010')
('192.168.12.255', 2010)
ivy.ivy.decode_msg(msg)

Extracts from an ivybus message its message type, the conveyed numerical identifier and its parameters.

Returns:msg_type, numerical_id, parameters
Raises:IvyMalformedMessage – if the message’s type or numerical identifier are not integers
ivy.ivy.encode_message(msg_type, numerical_id, params='')

params is string -> added as-is params is list -> concatenated, separated by ARG_END

ivy.ivy.is_multicast(ip)

Tells whether the specified ip is a multicast address or not

Parameters:ip – an IPv4 address in dotted-quad string format, for example 192.168.2.3
ivy.ivy.trace(*args, **kw)
ivy.ivy.void_function(*arg, **kw)

A function that accepts any number of parameters and does nothing