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')
- Messages types:
BYE, ADD_REGEXP, MSG, ERROR, DEL_REGEXP, END_REGEXP, END_INIT, START_REGEXP, START_INIT, DIRECT_MSG, DIE
- Separators:
ARG_START, ARG_END
- Misc. constants:
DEFAULT_IVYBUS, PROTOCOL_VERSION, IVY_SHOULD_NOT_DIE, IvyApplicationConnected, IvyApplicationDisconnected, DEFAULT_TTL
- Objects and functions related to logging:
ivylogger, debug, log, warn, error, ivy_loghdlr, ivy_logformatter
Implementation details¶
An Ivy agent is made of several threads:
Copyright (c) 2005-2023 Sebastien Bigaret <sebastien.bigaret@telecom-bretagne.eu>
- class ivy.ivy.ClientsBinding(pattern: Pattern, clients: List[Tuple[IvyClient, int]])¶
Bases:
object
Holds agents on the bus which are subscribed (bound) to a given regexp
- pattern: Pattern¶
- regexp() str ¶
- class ivy.ivy.IvyClient(ip: str, port: int, client_socket: socket, agent_id: str | None = None, agent_name: str | None = 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:
performing the initialization required by the Ivy protocol,
sending the various types of messages specified in the protocol.
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.
- Protocol-related methods:
- Announcing changes in our subscriptions:
- Sending messages:
send_message()
,send_direct_message()
,send_die_message()
,send_error()
,send_ping()
,send_pong()
,wave_bye()
MT-safety¶
MT-safe.
- end_init() None ¶
Should be called when the initialization process ends.
- Raises:
IvyIllegalStateError – if the method has already been called (and
self.status
has already been set toINITIALIZED
)
- get_next_ping_delta() float | None ¶
Returns the time (in seconds) elapsed since the oldest ping request. See:
get_next_ping_delta_ns()
for details.- Returns:
the time (seconds) elapsed since the oldest ping request, or None if there is not such request.
- get_next_ping_delta_ns() int | None ¶
Returns the time elapsed since the oldest ping request, in nanoseconds. This oldest request is then discarded from the internal LIFO stack (see
send_ping()
).- Returns:
the time (in nanoseconds) elapsed since the oldest ping request. Returns
None
if there is no such request –i.e. either no ping was sent or the method has been called at least as many times assend_ping()
).
- remove_subscription(idx: int) None ¶
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: int = 0, msg: str = '') None ¶
Sends a die message
- send_direct_message(num_id: int, msg: str) None ¶
Sends a direct message
Note: the message will be encoded by encode_message with
numerical_id=num_id
andparams==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: int, msg: str) None ¶
Sends an error message
- send_message(num_id: int, captures: Sequence) None ¶
Sends a message to the client.
- Parameters:
num_id: index of the client’s subscription matched by the message
captures: the capturing groups found when the regexp of that subscription match the message
- send_new_subscription(idx: int, regexp: str) None ¶
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() None ¶
Sends a PING request to the client. The time at which the request is sent is pushed into an internal LIFO stack, which is then used by
get_next_ping_delta()
.
- send_pong(num_id: int) None ¶
Sends a PONG message to a client. This is intended to be sent after this client sent us a PING message.
- start_init(agent_name: str) None ¶
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 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: int = 0) None ¶
Notifies the remote agent that we are about to quit
- class ivy.ivy.IvyHandler(request, client_address, server)¶
Bases:
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() None ¶
- process_ivymessage(msg: str, client: IvyClient) bool ¶
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)
client: the agent sending the message
- Returns:
False
if the connection should be terminated,True
otherwise
- exception ivy.ivy.IvyIllegalStateError¶
Bases:
RuntimeError
Raised when a method is called in an incorrect context, for example when attemptin to send a message whilst the server isn’t initialised.
- exception ivy.ivy.IvyMalformedMessage¶
Bases:
Exception
Raised when a received message is incorrectly formed
- exception ivy.ivy.IvyProtocolError¶
Bases:
Exception
Raised when a received (well-formed) message is not recognized as a valid message
- class ivy.ivy.IvyServer(agent_name: str, ready_msg: str | None = '', app_callback: ~typing.Callable = <function void_function>, die_callback: ~typing.Callable = <function void_function>, usesDaemons: bool = False)¶
Bases:
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
andserver_termination
.- Communication on the ivybus:
start()
,send_msg()
,send_direct_message()
,send_ready_message()
,handle_msg()
,stop()
- Inspecting the ivybus:
get_clients()
,_get_client()
,get_client_with_name()
- Our own subscriptions:
get_subscriptions()
,bind_msg()
,unbind_msg()
,_add_subscription()
,_remove_subscription()
,_get_fct_for_subscription()
- Other clients’ subscriptions:
add_client_binding()
,get_client_bindings()
,remove_client_binding()
- Handling callbacks:
Callbacks can be registered either by assigning a Callable to a class attribute or by calling the corresponding method:
app_callback
regexp_change_callback
,bind_regexp_change()
die_callback
,bind_die_change()
direct_callback
,bind_direct_msg()
pong_callback
,bind_pong()
MT-safety¶
All public methods (not starting with an underscore
_
) are MT-safeBuilds 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 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_direct_msg(on_direct_msg_fct: Callable) None ¶
Registers a callback to be called when a direct message is received.
- bind_msg(on_msg_fct: Callable, regexp: str) int ¶
Registers a new subscription, 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: Callable) None ¶
Registers a callback to be called when receiving a “pong” message.
- bind_regexp_change(on_regexp_change_callback: Callable) None ¶
Registers a callback to be called when a client on the bus adds or removes one of its subscriptions.
- get_client(ip: str, port: int) IvyClient ¶
Returns the corresponding client, and create a new one if needed.
If agent_id is not None, the method checks whether a client with the same id is already registered; if it exists, the method exits by returning None.
You should not need to call this, use
get_client_with_name()
instead
- get_client_with_name(name: str) List[IvyClient] ¶
Returns the list of the clients registered with a given agent-name
- See:
get_clients
- get_clients() List[str] ¶
Returns the list of the agent names of all connected clients
- See:
get_client_with_name
- get_subscriptions() List[Tuple[int, str]] ¶
- handle_direct_msg(client: IvyClient, num_id: int, msg: str) None ¶
- Parameters:
client –
num_id –
msg –
- Returns:
- handle_msg(client: IvyClient, idx: int, *params: Any) None ¶
Simply call the function bound to the subscription id idx with the supplied parameters.
- isAlive() bool ¶
- ivybus: Tuple[str, int] | None¶
Holds
(broadcast_addr, port)
of the Ivy bus after the server is started
- port¶
The port on which the TCP server awaits connection
- register_client(ip: str, port: int, client_socket: socket, agent_id: str | None = None, agent_name: str | None = None) IvyClient ¶
Creates a new IvyClient.
If agent_id is not None, the method checks whether a client with the same id is already registered; if it exists, the method exits by returning None.
You should not need to call this, use
get_client_with_name()
instead
- remove_client(ip: str, port: int, trigger_application_callback: bool = True) IvyClient | None ¶
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: Callable, callback_description: str, agent: IvyClient, *args: Any, **kw: Any) Any ¶
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 callbackon_exc: the returned value in case an exception was raised by the callback.
args: other positional arguments are passed as-is to the callback.
kw: other keyword arguments are passed as-is to the callback.
- Returns:
the value returned by the callback, or on_exc if an exception was raised
- send_direct_message(agent_name: str, num_id: int, msg: str, stop_on_first: bool = True) bool ¶
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; ifFalse
the method exits after the first message has been sent.
- Returns:
True
if at least one direct message was sent
- send_msg(message: str, *, to: IvyClient | None = None) int ¶
Send a message to the clients which subscribed to such a message (or to a specific client if parameter to is set). Specifically, a message is sent to a given client each time the message matches one of its subscriptions ; as a consequence, this can result in more than one (ivy) message being sent to a client, depending on its subscriptions.
- Parameters:
message – the message to send
to – the client to which the message is to be sent. When
None
(the default) the message is sent to all connected clients.
- Returns:
the number of times a subscription matched the message
- serve_forever(poll_interval: float = 0.5) None ¶
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.
- 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 callstop()
to terminate the server.
- start(ivybus: str | None = None) 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() None ¶
Disconnects the server from the ivybus. It also sets the
server_termination
event.- Raises:
IvyIllegalStateError – if the server is not running
- unbind_msg(num_id: int) str ¶
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
- 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
__init__()
time and should not be modified afterwards.
- class ivy.ivy.IvyTimer(server: IvyServer, nbticks: int, delay_ms: int, callback: Callable)¶
Bases:
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
toTrue
, 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.).
Creates and activates a new timer.
- 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 loopdelay: the delay, in milliseconds, between two ticks
callback: a function called at each tick. This function is called with one parameter, the timer itself
- callback¶
The function to be called at each tick.
- delay¶
The delay between two ticks, in milliseconds
- id¶
The timer unique id
- nbticks¶
The number of ticks after which the timer stops. Zero (0) means infinity.
- ivy.ivy.UDP_init_and_listen(broadcast_addr: str, port: int, socket_server: IvyServer) None ¶
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: str) Sequence[str] ¶
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: str | None = None) Tuple[str, int] ¶
Transforms the supplied string into the corresponding broadcast address and port
- Parameters:
ivybus – if
None
or empty, defaults to environment variableIVYBUS
, orDEFAULT_IVYBUS
if no such env.var. exists- 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(message: str) Tuple[int, int, Sequence[str]] ¶
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: int, numerical_id: int, params: str | Sequence = '') bytes ¶
params is string -> added as-is params is list -> concatenated, separated by ARG_END
- ivy.ivy.is_multicast(ip: str) bool ¶
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: Any, **kw: Any) Any ¶
A function that accepts any number of parameters and does nothing