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:
| 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:
objectRepresents 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
IvyHandlerobject.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: Sending messages: 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.statushas already been set toINITIALIZED)
-
get_next_ping_delta()¶
-
get_regexps()¶
-
remove_regexp(regexp_id)¶ Removes a regexp
Returns: the regexp that has been removed
Raises: - IvyIllegalStateError – if the client has not been fully
initialized yet (see
start_init()) - KeyError – if no such subscription exists
- IvyIllegalStateError – if the client has not been fully
initialized yet (see
-
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_idandparams==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 inivy.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: Trueif 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; returnsFalseotherwise.
-
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_INITand ending with a typeEND_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.StreamRequestHandlerAn 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: Falseif the connection should be terminated,Trueotherwise
-
-
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.ThreadingTCPServerAn 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-safeGroup 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()anddie_callback()must be prepared to be called concurrentlyParameters: - 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_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
- the regexp
- regexp: (string) a regular expression
Returns: the binding’s id, which can be used to unregister the binding with
unbind_msg()- on_msg_fct: a function accepting as many parameters as there is
groups in the regexp. For example:
-
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_callbackReturns: 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
IvyClienttriggering 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; ifFalsethe method exits after the first message has been sent.
Returns: Trueif 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 untilstop()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.ThreadAn IvyTimer object is responsible for calling a function regularly. It is bound to an
IvyServerand 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
aborttoTrue, the timer will stop at the next tick (the callback won’t be called)
- Each timer gets a unique id, stored in the attribute
- 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
IvyServerrelated 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 Noneor empty, defaults to environment variableIVYBUSReturns: 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