Source code for iotilegateway.gateway

"""An IOTile gateway-in-a-box that will connect to devices using device adapters and serve them using agents."""

import logging
import threading
import pkg_resources
import tornado.ioloop
import iotilegateway.device as device
from iotile.core.exceptions import ArgumentError


def find_entry_point(group, name):
    """Find an entry point by name.

    Args:
        group (string): The entry point group like iotile.gateway_agent
        name (string): The name of the entry point to find
    """

    for entry in pkg_resources.iter_entry_points(group, name):
        item = entry.load()
        return item

    raise ArgumentError("Could not find installed plugin by name and group", group=group, name=name)


[docs]class IOTileGateway(threading.Thread): """A gateway that finds IOTile devices using device adapters and serves them using agents. The gateway runs in separate thread in a tornado IOLoop and you can call the synchronous wait function to wait for it to quit. It will loop forever unless you stop it by calling the stop() or stop_from_signal() methods. These functions add a task to the gateway's event loop and implicitly call wait to synchronously wait until the gateway loop actually stops. IOTileGateway should be thought of as a turn-key gateway object that translates requests for IOTile Device access received from one or more GatewayAgents into commands sent to one or more DeviceAdapters. It is a multi-device, multi-user, multi-protocol system that can have many connections in flight at the same time, limited only by the available resources on the computer that hosts it. The arguments dictionary to IOTileGateway class has the same format as the json parameters passed to the iotile-gateway script that is just a thin wrapper around this class. Args: config (dict): The configuration of the gateway. There should be two keys set: agents (list): a list of dictionaries with the name of the agent and any arguments that should be passed to create it. adapters (list): a list of dictionaries with the device adapters to add into the gateway and any arguments that should be use to create each one. """ def __init__(self, config): self.loop = None self.device_manager = None self.agents = [] self.loaded = threading.Event() self._config = config self._logger = logging.getLogger(__name__) if 'agents' not in config: self._config['agents'] = [] self._logger.warning("No agents defined in arguments to iotile-gateway, " "this is likely not what you want") elif 'adapters' not in config: self._config['adapters'] = [] self._logger.warning("No device adapters defined in arguments to iotile-gateway, " "this is likely not what you want") super(IOTileGateway, self).__init__() def run(self): """Start the gateway and run it to completion in another thread.""" self.loop = tornado.ioloop.IOLoop(make_current=True) # To create a loop for each thread self.device_manager = device.DeviceManager(self.loop) # If we have an initialization error, stop trying to initialize more things and # just shut down cleanly should_close = False # Load in all of the gateway agents that are supposed to provide access to # the devices in this gateway for agent_info in self._config['agents']: if 'name' not in agent_info: self._logger.error("Invalid agent information in gateway config, info=%s, missing_key=%s", str(agent_info), 'name') should_close = True break agent_name = agent_info['name'] agent_args = agent_info.get('args', {}) self._logger.info("Loading agent by name '%s'", agent_name) agent_class = find_entry_point('iotile.gateway_agent', agent_name) try: agent = agent_class(agent_args, self.device_manager, self.loop) agent.start() self.agents.append(agent) except Exception: # pylint: disable=W0703 self._logger.exception("Could not load gateway agent %s, quitting", agent_name) should_close = True break # Load in all of the device adapters that provide access to actual devices if not should_close: for adapter_info in self._config['adapters']: if 'name' not in adapter_info: self._logger.error("Invalid adapter information in gateway config, info=%s, missing_key=%s", str(adapter_info), 'name') should_close = True break adapter_name = adapter_info['name'] port_string = adapter_info.get('port', None) self._logger.info("Loading device adapter by name '%s' and port '%s'", adapter_name, port_string) try: adapter_class = find_entry_point('iotile.device_adapter', adapter_name) adapter = adapter_class(port_string) self.device_manager.add_adapter(adapter) except Exception: # pylint: disable=W0703 self._logger.exception("Could not load device adapter %s, quitting", adapter_name) should_close = True if should_close: self.loop.add_callback(self._stop_loop) else: # Notify that we have now loaded all plugins and are starting operation (once the loop starts) self.loop.add_callback(lambda: self.loaded.set()) self.loop.start() # The loop has been closed, finish and quit self._logger.critical("Done stopping loop") def _stop_loop(self): """Cleanly stop the gateway and close down the IOLoop. This function must be called only by being added to our event loop using add_callback. """ self._logger.critical("Stopping gateway") self._logger.info("Stopping gateway agents") for agent in self.agents: try: agent.stop() except Exception: # pylint: disable=W0703 self._logger.exception("Error stopping gateway agent") self._logger.critical('Stopping device adapters') try: self.device_manager.stop() except Exception: # pylint: disable=W0703 self._logger.exception("Error stopping device adapters") self.loop.stop() self._logger.critical('Stopping event loop and shutting down') def stop(self): """Stop the gateway manager and synchronously wait for it to stop.""" self.loop.add_callback(self._stop_loop) self.wait() def wait(self): """Wait for this gateway to shut down. We need this special function because waiting inside join will cause signals to not get handled. """ while self.is_alive(): try: self.join(timeout=0.1) except IOError: pass # IOError comes when this call is interrupted in a signal handler def stop_from_signal(self): """Stop the gateway from a signal handler, not waiting for it to stop.""" self.loop.add_callback_from_signal(self._stop_loop)