class Agent(object): def __init__(self, jid, password, verify_security=False, loop=None): """ Creates an agent Args: jid (str): The identifier of the agent in the form username@server password (str): The password to connect to the server verify_security (bool): Wether to verify or not the SSL certificates loop (an asyncio event loop): the event loop if it was already created (optional) """ self.jid = aioxmpp.JID.fromstr(jid) self.password = password self.verify_security = verify_security self.behaviours = [] self._values = {} self.traces = TraceStore(size=1000) if loop: self.loop = loop self.external_loop = True else: self.loop = asyncio.new_event_loop() self.external_loop = False self.aiothread = AioThread(self, self.loop) self._alive = Event() # obtain an instance of the service self.message_dispatcher = self.client.summon(SimpleMessageDispatcher) # Presence service self.presence = PresenceManager(self) # Web service self.web = WebApp(agent=self) def start(self, auto_register=True): """ Starts the agent. This fires some actions: * if auto_register: register the agent in the server * runs the event loop * connects the agent to the server * runs the registered behaviours Args: auto_register (bool, optional): register the agent in the server (Default value = True) """ if auto_register: self.register() self.aiothread.connect() self._start() async def async_start(self, auto_register=True): """ Starts the agent from a coroutine. This fires some actions: * if auto_register: register the agent in the server * runs the event loop * connects the agent to the server * runs the registered behaviours Args: auto_register (bool, optional): register the agent in the server (Default value = True) """ if auto_register: await self.async_register() await self.aiothread.async_connect() self._start() def _start(self): self.aiothread.start() self._alive.set() # register a message callback here self.message_dispatcher.register_callback( aioxmpp.MessageType.CHAT, None, self._message_received, ) self.setup() def register(self): # pragma: no cover """ Register the agent in the XMPP server. """ metadata = aioxmpp.make_security_layer( None, no_verify=not self.verify_security) query = ibr.Query(self.jid.localpart, self.password) _, stream, features = self.loop.run_until_complete( aioxmpp.node.connect_xmlstream(self.jid, metadata)) self.loop.run_until_complete(ibr.register(stream, query)) async def async_register(self): # pragma: no cover """ Register the agent in the XMPP server from a coroutine. """ metadata = aioxmpp.make_security_layer( None, no_verify=not self.verify_security) query = ibr.Query(self.jid.localpart, self.password) _, stream, features = await aioxmpp.node.connect_xmlstream( self.jid, metadata) await ibr.register(stream, query) def setup(self): """ Setup agent before startup. This method may be overloaded. """ pass @property def name(self): """ Returns the name of the agent (the string before the '@') """ return self.jid.localpart @property def client(self): """ Returns the client that is connected to the xmpp server """ return self.aiothread.client @property def stream(self): """ Returns the stream of the connection """ return self.aiothread.stream @property def avatar(self): """ Generates a unique avatar for the agent based on its JID. Uses Gravatar service with MonsterID option. Returns: str: the url of the agent's avatar """ return self.build_avatar_url(self.jid.bare()) @staticmethod def build_avatar_url(jid): """ Static method to build a gravatar url with the agent's JID Args: jid (aioxmpp.JID): an XMPP identifier Returns: str: an URL for the gravatar """ digest = md5(str(jid).encode("utf-8")).hexdigest() return "http://www.gravatar.com/avatar/{md5}?d=monsterid".format( md5=digest) def submit(self, coro): """ Runs a coroutine in the event loop of the agent. this call is not blocking. Args: coro (coroutine): the coroutine to be run Returns: asyncio.Future: the future of the coroutine execution """ return asyncio.run_coroutine_threadsafe(coro, loop=self.loop) def add_behaviour(self, behaviour, template=None): """ Adds and starts a behaviour to the agent. If template is not None it is used to match new messages and deliver them to the behaviour. Args: behaviour (spade.behaviour.CyclicBehaviour): the behaviour to be started template (spade.template.Template, optional): the template to match messages with (Default value = None) """ behaviour.set_agent(self) behaviour.set_template(template) self.behaviours.append(behaviour) behaviour.start() def remove_behaviour(self, behaviour): """ Removes a behaviour from the agent. The behaviour is first killed. Args: behaviour (spade.behaviour.CyclicBehaviour): the behaviour instance to be removed """ if not self.has_behaviour(behaviour): raise ValueError("This behaviour is not registered") index = self.behaviours.index(behaviour) self.behaviours[index].kill() self.behaviours.pop(index) def has_behaviour(self, behaviour): """ Checks if a behaviour is added to an agent. Args: behaviour (spade.behaviour.CyclicBehaviour): the behaviour instance to check Returns: bool: a boolean that indicates wether the behaviour is inside the agent. """ return behaviour in self.behaviours def stop(self): """ Stops an agent and kills all its behaviours. """ self.presence.set_unavailable() for behav in self.behaviours: behav.kill() if self.web.server: self.web.server.close() self.submit(self.web.app.shutdown()) self.submit(self.web.handler.shutdown(60.0)) self.submit(self.web.app.cleanup()) self.aiothread.finalize() self._alive.clear() def is_alive(self): """ Checks if the agent is alive. Returns: bool: wheter the agent is alive or not """ return self._alive.is_set() def set(self, name, value): """ Stores a knowledge item in the agent knowledge base. Args: name (str): name of the item value (object): value of the item """ self._values[name] = value def get(self, name): """ Recovers a knowledge item from the agent's knowledge base. Args: name(str): name of the item Returns: object: the object retrieved or None """ if name in self._values: return self._values[name] else: return None def _message_received(self, msg): """ Callback run when an XMPP Message is reveived. This callback delivers the message to every behaviour that is waiting for it using their templates match. the aioxmpp.Message is converted to spade.message.Message Args: msg (aioxmpp.Messagge): the message just received. Returns: list(asyncio.Future): a list of futures of the append of the message at each matched behaviour. """ logger.debug(f"Got message: {msg}") msg = Message.from_node(msg) futures = [] matched = False for behaviour in (x for x in self.behaviours if x.match(msg)): futures.append(self.submit(behaviour.enqueue(msg))) logger.debug(f"Message enqueued to behaviour: {behaviour}") self.traces.append(msg, category=str(behaviour)) matched = True if not matched: logger.warning(f"No behaviour matched for message: {msg}") self.traces.append(msg) return futures
class Agent(object): def __init__(self, jid, password, verify_security=False): """ Creates an agent Args: jid (str): The identifier of the agent in the form username@server password (str): The password to connect to the server verify_security (bool): Wether to verify or not the SSL certificates """ self.jid = aioxmpp.JID.fromstr(jid) self.password = password self.verify_security = verify_security self.behaviours = [] self._values = {} self.conn_coro = None self.stream = None self.client = None self.message_dispatcher = None self.presence = None self.loop = None self.container = Container() self.container.register(self) self.loop = self.container.loop # Web service self.web = WebApp(agent=self) self.traces = TraceStore(size=1000) self._alive = Event() def set_loop(self, loop): self.loop = loop def set_container(self, container): """ Sets the container to which the agent is attached Args: container (spade.container.Container): the container to be attached to """ self.container = container def start(self, auto_register=True): """ Tells the container to start this agent. It returns a coroutine or a future depending on whether it is called from a coroutine or a synchronous method. Args: auto_register (bool): register the agent in the server (Default value = True) """ return self.container.start_agent(agent=self, auto_register=auto_register) async def _async_start(self, auto_register=True): """ Starts the agent from a coroutine. This fires some actions: * if auto_register: register the agent in the server * runs the event loop * connects the agent to the server * runs the registered behaviours Args: auto_register (bool, optional): register the agent in the server (Default value = True) """ if auto_register: await self._async_register() self.client = aioxmpp.PresenceManagedClient( self.jid, aioxmpp.make_security_layer(self.password, no_verify=not self.verify_security), loop=self.loop, logger=logging.getLogger(self.jid.localpart)) # obtain an instance of the service self.message_dispatcher = self.client.summon(SimpleMessageDispatcher) # Presence service self.presence = PresenceManager(self) await self._async_connect() # register a message callback here self.message_dispatcher.register_callback( aioxmpp.MessageType.CHAT, None, self._message_received, ) await self.setup() self._alive.set() for behaviour in self.behaviours: if not behaviour.is_running: behaviour.start() async def _async_connect(self): # pragma: no cover """ connect and authenticate to the XMPP server. Async mode. """ try: self.conn_coro = self.client.connected() aenter = type(self.conn_coro).__aenter__(self.conn_coro) self.stream = await aenter logger.info(f"Agent {str(self.jid)} connected and authenticated.") except aiosasl.AuthenticationFailure: raise AuthenticationFailure( "Could not authenticate the agent. Check user and password or use auto_register=True" ) async def _async_register(self): # pragma: no cover """ Register the agent in the XMPP server from a coroutine. """ metadata = aioxmpp.make_security_layer( None, no_verify=not self.verify_security) query = ibr.Query(self.jid.localpart, self.password) _, stream, features = await aioxmpp.node.connect_xmlstream( self.jid, metadata, loop=self.loop) await ibr.register(stream, query) async def setup(self): """ Setup agent before startup. This coroutine may be overloaded. """ await asyncio.sleep(0) @property def name(self): """ Returns the name of the agent (the string before the '@') """ return self.jid.localpart @property def avatar(self): """ Generates a unique avatar for the agent based on its JID. Uses Gravatar service with MonsterID option. Returns: str: the url of the agent's avatar """ return self.build_avatar_url(self.jid.bare()) @staticmethod def build_avatar_url(jid): """ Static method to build a gravatar url with the agent's JID Args: jid (aioxmpp.JID): an XMPP identifier Returns: str: an URL for the gravatar """ digest = md5(str(jid).encode("utf-8")).hexdigest() return "http://www.gravatar.com/avatar/{md5}?d=monsterid".format( md5=digest) def submit(self, coro): """ Runs a coroutine in the event loop of the agent. this call is not blocking. Args: coro (coroutine): the coroutine to be run Returns: asyncio.Future: the future of the coroutine execution """ return asyncio.run_coroutine_threadsafe(coro, loop=self.loop) def add_behaviour(self, behaviour, template=None): """ Adds and starts a behaviour to the agent. If template is not None it is used to match new messages and deliver them to the behaviour. Args: behaviour (spade.behaviour.CyclicBehaviour): the behaviour to be started template (spade.template.Template, optional): the template to match messages with (Default value = None) """ behaviour.set_agent(self) if issubclass(type(behaviour), FSMBehaviour): for _, state in behaviour.get_states().items(): state.set_agent(self) behaviour.set_template(template) self.behaviours.append(behaviour) if self.is_alive(): behaviour.start() def remove_behaviour(self, behaviour): """ Removes a behaviour from the agent. The behaviour is first killed. Args: behaviour (spade.behaviour.CyclicBehaviour): the behaviour instance to be removed """ if not self.has_behaviour(behaviour): raise ValueError("This behaviour is not registered") index = self.behaviours.index(behaviour) self.behaviours[index].kill() self.behaviours.pop(index) def has_behaviour(self, behaviour): """ Checks if a behaviour is added to an agent. Args: behaviour (spade.behaviour.CyclicBehaviour): the behaviour instance to check Returns: bool: a boolean that indicates wether the behaviour is inside the agent. """ return behaviour in self.behaviours def stop(self): """ Tells the container to start this agent. It returns a coroutine or a future depending on whether it is called from a coroutine or a synchronous method. """ return self.container.stop_agent(self) async def _async_stop(self): """ Stops an agent and kills all its behaviours. """ if self.presence: self.presence.set_unavailable() for behav in self.behaviours: behav.kill() if self.web.is_started(): await self.web.runner.cleanup() """ Discconnect from XMPP server. """ if self.is_alive(): # Disconnect from XMPP server self.client.stop() aexit = self.conn_coro.__aexit__(*sys.exc_info()) await aexit logger.info("Client disconnected.") self._alive.clear() def is_alive(self): """ Checks if the agent is alive. Returns: bool: wheter the agent is alive or not """ return self._alive.is_set() def set(self, name, value): """ Stores a knowledge item in the agent knowledge base. Args: name (str): name of the item value (object): value of the item """ self._values[name] = value def get(self, name): """ Recovers a knowledge item from the agent's knowledge base. Args: name(str): name of the item Returns: object: the object retrieved or None """ if name in self._values: return self._values[name] else: return None def _message_received(self, msg): """ Callback run when an XMPP Message is reveived. This callback delivers the message to every behaviour that is waiting for it. First, the aioxmpp.Message is converted to spade.message.Message Args: msg (aioxmpp.Messagge): the message just received. Returns: list(asyncio.Future): a list of futures of the append of the message at each matched behaviour. """ msg = Message.from_node(msg) return self.dispatch(msg) def dispatch(self, msg): """ Dispatch the message to every behaviour that is waiting for it using their templates match. Args: msg (spade.message.Messagge): the message to dispatch. Returns: list(asyncio.Future): a list of futures of the append of the message at each matched behaviour. """ logger.debug(f"Got message: {msg}") futures = [] matched = False for behaviour in (x for x in self.behaviours if x.match(msg)): futures.append(self.submit(behaviour.enqueue(msg))) logger.debug(f"Message enqueued to behaviour: {behaviour}") self.traces.append(msg, category=str(behaviour)) matched = True if not matched: logger.warning(f"No behaviour matched for message: {msg}") self.traces.append(msg) return futures
class Artifact(PubSubMixin): def __init__(self, jid, password, pubsub_server=None, verify_security=False): """ Creates an artifact Args: jid (str): The identifier of the artifact in the form username@server password (str): The password to connect to the server verify_security (bool): Wether to verify or not the SSL certificates """ self.jid = aioxmpp.JID.fromstr(jid) self.password = password self.verify_security = verify_security self.pubsub_server = (pubsub_server if pubsub_server else f"pubsub.{self.jid.domain}") self._values = {} self.conn_coro = None self.stream = None self.client = None self.message_dispatcher = None self.presence = None self.container = Container() self.container.register(self) self.loop = self.container.loop # self.loop = None #asyncio.new_event_loop() self.queue = asyncio.Queue(loop=self.loop) self._alive = Event() def set_loop(self, loop): self.loop = loop def set_container(self, container): """ Sets the container to which the artifact is attached Args: container (spade.container.Container): the container to be attached to """ self.container = container def start(self, auto_register=True): """ Tells the container to start this agent. It returns a coroutine or a future depending on whether it is called from a coroutine or a synchronous method. Args: auto_register (bool): register the agent in the server (Default value = True) """ return self.container.start_agent(agent=self, auto_register=auto_register) async def _async_start(self, auto_register=True): """ Starts the agent from a coroutine. This fires some actions: * if auto_register: register the agent in the server * runs the event loop * connects the agent to the server * runs the registered behaviours Args: auto_register (bool, optional): register the agent in the server (Default value = True) """ await self._hook_plugin_before_connection() if auto_register: await self._async_register() self.client = aioxmpp.PresenceManagedClient( self.jid, aioxmpp.make_security_layer(self.password, no_verify=not self.verify_security), loop=self.loop, logger=logging.getLogger(self.jid.localpart), ) # obtain an instance of the service self.message_dispatcher = self.client.summon(SimpleMessageDispatcher) # Presence service self.presence = PresenceManager(self) await self._async_connect() # register a message callback here self.message_dispatcher.register_callback( aioxmpp.MessageType.CHAT, None, self._message_received, ) await self._hook_plugin_after_connection() # pubsub initialization try: self._node = str(self.jid.bare()) await self.pubsub.create(self.pubsub_server, f"{self._node}") except XMPPCancelError as e: logger.info(f"Node {self._node} already registered") except XMPPAuthError as e: logger.error( f"Artifact {self._node} is not allowed to publish properties.") raise e await self.setup() self._alive.set() asyncio.run_coroutine_threadsafe(self.run(), loop=self.loop) async def _hook_plugin_before_connection(self, *args, **kwargs): """ Overload this method to hook a plugin before connetion is done """ try: await super()._hook_plugin_before_connection(*args, **kwargs) except AttributeError: logger.debug("_hook_plugin_before_connection is undefined") async def _hook_plugin_after_connection(self, *args, **kwargs): """ Overload this method to hook a plugin after connetion is done """ try: await super()._hook_plugin_after_connection(*args, **kwargs) except AttributeError: logger.debug("_hook_plugin_after_connection is undefined") async def _async_connect(self): # pragma: no cover """ connect and authenticate to the XMPP server. Async mode. """ try: self.conn_coro = self.client.connected() aenter = type(self.conn_coro).__aenter__(self.conn_coro) self.stream = await aenter logger.info( f"Artifact {str(self.jid)} connected and authenticated.") except aiosasl.AuthenticationFailure: raise AuthenticationFailure( "Could not authenticate the artifact. Check user and password or use auto_register=True" ) async def _async_register(self): # pragma: no cover """ Register the artifact in the XMPP server from a coroutine. """ metadata = aioxmpp.make_security_layer( None, no_verify=not self.verify_security) query = ibr.Query(self.jid.localpart, self.password) _, stream, features = await aioxmpp.node.connect_xmlstream( self.jid, metadata, loop=self.loop) await ibr.register(stream, query) async def setup(self): """ Setup artifact before startup. This coroutine may be overloaded. """ await asyncio.sleep(0) def kill(self): self._alive.clear() async def run(self): """ Main body of the artifact. This coroutine SHOULD be overloaded. """ raise NotImplementedError @property def name(self): """ Returns the name of the artifact (the string before the '@') """ return self.jid.localpart def stop(self): """ Stop the artifact """ self.kill() return self.loop.run_until_complete(self._async_stop()) async def _async_stop(self): """ Stops an artifact and kills all its behaviours. """ if self.presence: self.presence.set_unavailable() """ Discconnect from XMPP server. """ if self.is_alive(): # Disconnect from XMPP server self.client.stop() aexit = self.conn_coro.__aexit__(*sys.exc_info()) await aexit logger.info("Client disconnected.") self._alive.clear() def is_alive(self): """ Checks if the artifact is alive. Returns: bool: wheter the artifact is alive or not """ return self._alive.is_set() def set(self, name, value): """ Stores a knowledge item in the artifact knowledge base. Args: name (str): name of the item value (object): value of the item """ self._values[name] = value def get(self, name): """ Recovers a knowledge item from the artifact's knowledge base. Args: name(str): name of the item Returns: object: the object retrieved or None """ if name in self._values: return self._values[name] else: return None def _message_received(self, msg): """ Callback run when an XMPP Message is reveived. The aioxmpp.Message is converted to spade.message.Message Args: msg (aioxmpp.Messagge): the message just received. Returns: asyncio.Future: a future of the append of the message. """ msg = Message.from_node(msg) logger.debug(f"Got message: {msg}") return asyncio.run_coroutine_threadsafe(self.queue.put(msg), self.loop) async def send(self, msg: Message): """ Sends a message. Args: msg (spade.message.Message): the message to be sent. """ if not msg.sender: msg.sender = str(self.jid) logger.debug(f"Adding artifact's jid as sender to message: {msg}") aioxmpp_msg = msg.prepare() await self.client.send(aioxmpp_msg) msg.sent = True async def receive(self, timeout: float = None) -> Union[Message, None]: """ Receives a message for this artifact. If timeout is not None it returns the message or "None" after timeout is done. Args: timeout (float): number of seconds until return Returns: spade.message.Message: a Message or None """ if timeout: coro = self.queue.get() try: msg = await asyncio.wait_for(coro, timeout=timeout) except asyncio.TimeoutError: msg = None else: try: msg = self.queue.get_nowait() except asyncio.QueueEmpty: msg = None return msg def mailbox_size(self) -> int: """ Checks if there is a message in the mailbox Returns: int: the number of messages in the mailbox """ return self.queue.qsize() def join(self, timeout=None): try: in_coroutine = asyncio.get_event_loop() == self.loop except RuntimeError: # pragma: no cover in_coroutine = False if not in_coroutine: t_start = time.time() while self.is_alive(): time.sleep(0.001) t = time.time() if timeout is not None and t - t_start > timeout: raise TimeoutError else: return self._async_join(timeout=timeout) async def _async_join(self, timeout): t_start = time.time() while self.is_alive(): await asyncio.sleep(0.001) t = time.time() if timeout is not None and t - t_start > timeout: raise TimeoutError async def publish(self, payload: str) -> None: await self.pubsub.publish(self.pubsub_server, self._node, payload)
class Agent(object): def __init__(self, jid, password, verify_security=False): """ Creates an agent Args: jid (str): The identifier of the agent in the form username@server password (str): The password to connect to the server verify_security (bool): Wether to verify or not the SSL certificates """ self.jid = aioxmpp.JID.fromstr(jid) self.password = password self.verify_security = verify_security self.behaviours = [] self._values = {} self.conn_coro = None self.stream = None self.client = None self.message_dispatcher = None self.presence = None self.loop = None self.container = Container() self.container.register(self) self.loop = self.container.loop # Web service self.web = WebApp(agent=self) self.traces = TraceStore(size=1000) self._alive = Event() def set_loop(self, loop): self.loop = loop def set_container(self, container): """ Sets the container to which the agent is attached Args: container (spade.container.Container): the container to be attached to """ self.container = container def start(self, auto_register=True): """ Tells the container to start this agent. It returns a coroutine or a future depending on whether it is called from a coroutine or a synchronous method. Args: auto_register (bool): register the agent in the server (Default value = True) """ return self.container.start_agent(agent=self, auto_register=auto_register) async def _async_start(self, auto_register=True): """ Starts the agent from a coroutine. This fires some actions: * if auto_register: register the agent in the server * runs the event loop * connects the agent to the server * runs the registered behaviours Args: auto_register (bool, optional): register the agent in the server (Default value = True) """ if auto_register: await self._async_register() self.client = aioxmpp.PresenceManagedClient(self.jid, aioxmpp.make_security_layer(self.password, no_verify=not self.verify_security), loop=self.loop, logger=logging.getLogger(self.jid.localpart)) # obtain an instance of the service self.message_dispatcher = self.client.summon(SimpleMessageDispatcher) # Presence service self.presence = PresenceManager(self) await self._async_connect() # register a message callback here self.message_dispatcher.register_callback( aioxmpp.MessageType.CHAT, None, self._message_received, ) await self.setup() self._alive.set() for behaviour in self.behaviours: if not behaviour.is_running: behaviour.start() async def _async_connect(self): # pragma: no cover """ connect and authenticate to the XMPP server. Async mode. """ try: self.conn_coro = self.client.connected() aenter = type(self.conn_coro).__aenter__(self.conn_coro) self.stream = await aenter logger.info(f"Agent {str(self.jid)} connected and authenticated.") except aiosasl.AuthenticationFailure: raise AuthenticationFailure( "Could not authenticate the agent. Check user and password or use auto_register=True") async def _async_register(self): # pragma: no cover """ Register the agent in the XMPP server from a coroutine. """ metadata = aioxmpp.make_security_layer(None, no_verify=not self.verify_security) query = ibr.Query(self.jid.localpart, self.password) _, stream, features = await aioxmpp.node.connect_xmlstream(self.jid, metadata, loop=self.loop) await ibr.register(stream, query) async def setup(self): """ Setup agent before startup. This coroutine may be overloaded. """ await asyncio.sleep(0) @property def name(self): """ Returns the name of the agent (the string before the '@') """ return self.jid.localpart @property def avatar(self): """ Generates a unique avatar for the agent based on its JID. Uses Gravatar service with MonsterID option. Returns: str: the url of the agent's avatar """ return self.build_avatar_url(self.jid.bare()) @staticmethod def build_avatar_url(jid): """ Static method to build a gravatar url with the agent's JID Args: jid (aioxmpp.JID): an XMPP identifier Returns: str: an URL for the gravatar """ digest = md5(str(jid).encode("utf-8")).hexdigest() return "http://www.gravatar.com/avatar/{md5}?d=monsterid".format(md5=digest) def submit(self, coro): """ Runs a coroutine in the event loop of the agent. this call is not blocking. Args: coro (coroutine): the coroutine to be run Returns: asyncio.Future: the future of the coroutine execution """ return asyncio.run_coroutine_threadsafe(coro, loop=self.loop) def add_behaviour(self, behaviour, template=None): """ Adds and starts a behaviour to the agent. If template is not None it is used to match new messages and deliver them to the behaviour. Args: behaviour (spade.behaviour.CyclicBehaviour): the behaviour to be started template (spade.template.Template, optional): the template to match messages with (Default value = None) """ behaviour.set_agent(self) if issubclass(type(behaviour), FSMBehaviour): for _, state in behaviour.get_states().items(): state.set_agent(self) behaviour.set_template(template) self.behaviours.append(behaviour) if self.is_alive(): behaviour.start() def remove_behaviour(self, behaviour): """ Removes a behaviour from the agent. The behaviour is first killed. Args: behaviour (spade.behaviour.CyclicBehaviour): the behaviour instance to be removed """ if not self.has_behaviour(behaviour): raise ValueError("This behaviour is not registered") index = self.behaviours.index(behaviour) self.behaviours[index].kill() self.behaviours.pop(index) def has_behaviour(self, behaviour): """ Checks if a behaviour is added to an agent. Args: behaviour (spade.behaviour.CyclicBehaviour): the behaviour instance to check Returns: bool: a boolean that indicates wether the behaviour is inside the agent. """ return behaviour in self.behaviours def stop(self): """ Tells the container to start this agent. It returns a coroutine or a future depending on whether it is called from a coroutine or a synchronous method. """ return self.container.stop_agent(self) async def _async_stop(self): """ Stops an agent and kills all its behaviours. """ if self.presence: self.presence.set_unavailable() for behav in self.behaviours: behav.kill() if self.web.is_started(): await self.web.runner.cleanup() """ Discconnect from XMPP server. """ if self.is_alive(): # Disconnect from XMPP server self.client.stop() aexit = self.conn_coro.__aexit__(*sys.exc_info()) await aexit logger.info("Client disconnected.") self._alive.clear() def is_alive(self): """ Checks if the agent is alive. Returns: bool: wheter the agent is alive or not """ return self._alive.is_set() def set(self, name, value): """ Stores a knowledge item in the agent knowledge base. Args: name (str): name of the item value (object): value of the item """ self._values[name] = value def get(self, name): """ Recovers a knowledge item from the agent's knowledge base. Args: name(str): name of the item Returns: object: the object retrieved or None """ if name in self._values: return self._values[name] else: return None def _message_received(self, msg): """ Callback run when an XMPP Message is reveived. This callback delivers the message to every behaviour that is waiting for it. First, the aioxmpp.Message is converted to spade.message.Message Args: msg (aioxmpp.Messagge): the message just received. Returns: list(asyncio.Future): a list of futures of the append of the message at each matched behaviour. """ msg = Message.from_node(msg) return self.dispatch(msg) def dispatch(self, msg): """ Dispatch the message to every behaviour that is waiting for it using their templates match. Args: msg (spade.message.Messagge): the message to dispatch. Returns: list(asyncio.Future): a list of futures of the append of the message at each matched behaviour. """ logger.debug(f"Got message: {msg}") futures = [] matched = False for behaviour in (x for x in self.behaviours if x.match(msg)): futures.append(self.submit(behaviour.enqueue(msg))) logger.debug(f"Message enqueued to behaviour: {behaviour}") self.traces.append(msg, category=str(behaviour)) matched = True if not matched: logger.warning(f"No behaviour matched for message: {msg}") self.traces.append(msg) return futures