Пример #1
0
    def _init_(self, **kwargs):
        """
        Construct a new voice_cmds Instance

        items is an dictionary to copy items from (optional)
        limiter is the match ratio below which mathes should not be considered
        """
        self.voice_command_strings = FuzzySearch(None, .80)
        self.voice_command_data = {}

        self.commandsByVoice = self._Commands.get_commands_by_voice()
Пример #2
0
 def _init_(self, **kwargs):
     """
     Setups up the basic framework.
     
     """
     self.load_deferred = None  # Prevents loader from moving on past _start_ until we are done.
     self.commands = {}
     self.__yombocommandsByVoice = FuzzySearch(None, .92)
     self.command_search_attributes = [
         'command_id', 'label', 'machine_label', 'description',
         'always_load', 'voice_cmd', 'cmd', 'status'
     ]
Пример #3
0
    def __init__(self, testing=False, loop=None):
        self._operating_mode = "system_init"
        self._run_phase = "system_init"
        self.unittest = testing
        self._moduleLibrary = None
        YomboLibrary.__init__(self)

        self.loadedComponents = FuzzySearch({self._FullName.lower(): self},
                                            .95)
        self.loadedLibraries = FuzzySearch({self._Name.lower(): self}, .95)
        self.libraryNames = {}
        self._moduleLibrary = None
        self._invoke_list_cache = {
        }  # Store a list of hooks that exist or not. A cache.
        self._operating_mode = None  # One of: first_run, config, run
        self.sigint = False  # will be set to true if SIGINT is received
        self.hook_counts = OrderedDict(
        )  # keep track of hook names, and how many times it's called.
        reactor.addSystemEventTrigger("before", "shutdown", self.shutdown)
Пример #4
0
    def _init_(self):
        """
        Construct a new voice_cmds Instance

        items is an dictionary to copy items from (optional)
        limiter is the match ratio below which mathes should not be considered
        """
        self.voice_command_strings = FuzzySearch(None, .80)
        self.voice_command_data = {}

        self.commandsByVoice = self._Libraries['commands'].get_commands_by_voice()
Пример #5
0
    def __init__(self, testing=False):
        self.unittest = testing
        self._moduleLibrary = None
        YomboLibrary.__init__(self)

        self.loadedComponents = FuzzySearch({self._FullName.lower(): self}, .95)
        self.loadedLibraries = FuzzySearch({self._Name.lower(): self}, .95)
        self.libraryNames = {}
        self.__localModuleVars = {}
        self._moduleLibrary = None
        self._invoke_list_cache = {}  # Store a list of hooks that exist or not. A cache.
        self._operation_mode = None  # One of: firstrun, config, run
        self.sigint = False  # will be set to true if SIGINT is received
        self.hook_counts = OrderedDict()  # keep track of hook names, and how many times it's called.
        reactor.addSystemEventTrigger("before", "shutdown", self.shutdown)
Пример #6
0
class Commands(YomboLibrary):
    """
    Manages all commands available for devices.

    All modules already have a predefined reference to this library as
    `self._Commands`. All documentation will reference this use case.
    """
    def __contains__(self, command_requested):
        """
        .. note:: The command must be enabled to be found using this method.

        Checks to if a provided command id, label, or machine_label exists.

            >>> if '137ab129da9318' in self._Commands:

        or:

            >>> if 'living room light' in self._Commands:

        :raises YomboWarning: Raised when request is malformed.
        :param command_requested: The command ID, label, or machine_label to search for.
        :type command_requested: string
        :return: Returns true if exists, otherwise false.
        :rtype: bool
        """
        try:
            self.get(command_requested)
            return True
        except:
            return False

    def __getitem__(self, command_requested):
        """
        .. note:: The command must be enabled to be found using this method. An alternative,
        but equal function is: :py:meth:`get() <Commands.get>`
        
        Attempts to find the device requested using a couple of methods.

            >>> off_cmd = self._Commands['137ab129da9318']  #by id

        or:

            >>> off_cmd = self._Commands['Off']  #by label & machine_label

        :raises YomboWarning: Raised when request is malformed.
        :raises KeyError: Raised when request is not found.
        :param command_requested: The command ID, label, or machine_label to search for.
        :type command_requested: string
        :return: A pointer to the command instance.
        :rtype: instance
        """
        return self.get(command_requested)

    def __setitem__(self, command_requested, value):
        """
        Sets are not allowed. Raises exception.

        :raises Exception: Always raised.
        """
        raise Exception("Not allowed.")

    def __delitem__(self, command_requested):
        """
        Deletes are not allowed. Raises exception.

        :raises Exception: Always raised.
        """
        raise Exception("Not allowed.")

    def __iter__(self):
        """ iter commands. """
        return self.commands.__iter__()

    def __len__(self):
        """
        Returns an int of the number of commands configured.

        :return: The number of commands configured.
        :rtype: int
        """
        return len(self.commands)

    def __str__(self):
        """
        Returns the name of the library.
        :return: Name of the library
        :rtype: string
        """
        return "Yombo commands library"

    def keys(self):
        """
        Returns the keys (command ID's) that are configured.

        :return: A list of command IDs. 
        :rtype: list
        """
        return list(self.commands.keys())

    def items(self):
        """
        Gets a list of tuples representing the commands configured.

        :return: A list of tuples.
        :rtype: list
        """
        return list(self.commands.items())

    def iteritems(self):
        return iter(self.commands.items())

    def iterkeys(self):
        return iter(self.commands.keys())

    def itervalues(self):
        return iter(self.commands.values())

    def values(self):
        return list(self.commands.values())

    def _init_(self, **kwargs):
        """
        Setups up the basic framework.
        
        """
        self.load_deferred = None  # Prevents loader from moving on past _start_ until we are done.
        self.commands = {}
        self.__yombocommandsByVoice = FuzzySearch(None, .92)
        self.command_search_attributes = [
            'command_id', 'label', 'machine_label', 'description',
            'always_load', 'voice_cmd', 'cmd', 'status'
        ]

    def _load_(self, **kwargs):
        """
        Loads commands from the database and imports them.
        """
        self._load_commands_from_database()
        self.load_deferred = Deferred()
        return self.load_deferred

    def _stop_(self, **kwargs):
        """
        Cleans up any pending deferreds.
        """
        if self.load_deferred is not None and self.load_deferred.called is False:
            self.load_deferred.callback(
                1)  # if we don't check for this, we can't stop!

    def _clear_(self, **kwargs):
        """
        Clear all devices. Should only be called by the loader module
        during a reconfiguration event. B{Do not call this function!}
        """
        self.__yombocommandsByVoice.clear()

    def _reload_(self):
        self._load_()

    @inlineCallbacks
    def _load_commands_from_database(self):
        """
        Loads commands from database and sends them to :py:meth:`import_command() <Commands.import_device>`
        
        This can be triggered either on system startup or when new/updated commands have been saved to the
        database and we need to refresh existing commands.
        """
        commands = yield self._LocalDB.get_commands()
        logger.debug("commands: {commands}", commands=commands)
        for command in commands:
            yield self.import_command(command)
        self.load_deferred.callback(10)

    @inlineCallbacks
    def import_command(self, command, test_command=False):
        """
        Add a new command to memory or update an existing command.

        **Hooks called**:

        * _command_before_load_ : If added, sends command dictionary as 'command'
        * _command_before_update_ : If updated, sends command dictionary as 'command'
        * _command_loaded_ : If added, send the command instance as 'command'
        * _command_updated_ : If updated, send the command instance as 'command'

        :param device: A dictionary of items required to either setup a new command or update an existing one.
        :type device: dict
        :param test_command: Used for unit testing.
        :type test_command: bool
        :returns: Pointer to new device. Only used during unittest
        """
        logger.debug("command: {command}", command=command)
        command_id = command["id"]

        yield global_invoke_all(
            '_command_before_import_',
            called_by=self,
            command_id=command_id,
            command=command,
        )
        if command_id not in self.commands:
            try:
                yield global_invoke_all(
                    '_command_before_load_',
                    called_by=self,
                    command_id=command_id,
                    command=command,
                )
            except Exception as e:
                pass
            self.commands[command_id] = Command(command)
            try:
                yield global_invoke_all(
                    '_command_loaded_',
                    called_by=self,
                    command_id=command_id,
                    command=command,
                )
            except Exception as e:
                pass
        elif command_id not in self.commands:
            try:
                yield global_invoke_all(
                    '_command_before_update_',
                    called_by=self,
                    command_id=command_id,
                    command=command,
                )
            except Exception as e:
                pass

            self.commands[command_id].update_attributes(command)
            try:
                yield global_invoke_all(
                    '_command_updated_',
                    called_by=self,
                    command_id=command_id,
                    command=command,
                )
            except Exception as e:
                pass

        if command['voice_cmd'] is not None:
            self.__yombocommandsByVoice[
                command['voice_cmd']] = self.commands[command_id]

        # if test_command:
        #     return self.commands[command_id]

    def get(self,
            command_requested,
            limiter=None,
            status=None,
            command_list=None):
        """
        Looks for commands by it's id, label, and machine_label.

        .. note::

           Modules shouldn't use this function. Use the built in reference to
           find commands:
           
            >>> self._Commands['sz45q3423']
        
        or:
        
            >>> self._Commands['on']

        :raises YomboWarning: For invalid requests.
        :raises KeyError: When item requested cannot be found.
        :raises ValueError: When input value is invalid.
        :param command_requested: The command ID, label, and machine_label to search for.
        :type command_requested: string
        :param limiter_override: Default: .89 - A value between .5 and .99. Sets how close of a match it the search should be.
        :type limiter_override: float
        :param status: Deafult: 1 - The status of the command to check for.
        :type status: int
        :return: Pointer to requested command.
        :rtype: dict
        """
        if inspect.isclass(command_requested):
            if isinstance(command_requested, Command):
                return command_requested
            else:
                raise ValueError("Passed in an unknown object")

        if limiter is None:
            limiter = .89

        if limiter > .99999999:
            limiter = .99
        elif limiter < .10:
            limiter = .10

        if command_requested in self.commands:
            item = self.commands[command_requested]
            if status is not None and item.status != status:
                raise KeyError(
                    "Requested command found, but has invalid status: %s" %
                    item.status)
            return item
        else:
            attrs = [{
                'field': 'command_id',
                'value': command_requested,
                'limiter': limiter,
            }, {
                'field': 'label',
                'value': command_requested,
                'limiter': limiter,
            }, {
                'field': 'machine_label',
                'value': command_requested,
                'limiter': limiter,
            }]
            try:
                if command_list is not None:
                    commands = command_list
                else:
                    commands = self.commands
                logger.debug("Get is about to call search...: %s" %
                             command_requested)
                found, key, item, ratio, others = do_search_instance(
                    attrs,
                    commands,
                    self.command_search_attributes,
                    limiter=limiter,
                    operation="highest")
                logger.debug("found command by search: {command_id}",
                             command_id=key)
                if found:
                    return item
                else:
                    raise KeyError("Command not found: %s" % command_requested)
            except YomboWarning as e:
                raise KeyError('Searched for %s, but had problems: %s' %
                               (command_requested, e))

    def search(self, _limiter=None, _operation=None, **kwargs):
        """
        Advanced search, typically should use the :py:meth:`get <yombo.lib.commands.Commands.get>` method. 

        :param limiter_override: Default: .89 - A value between .5 and .99. Sets how close of a match it the search should be.
        :type limiter_override: float
        :param status: Deafult: 1 - The status of the command to check for.
        :return: 
        """
        return search_instance(kwargs, self.commands,
                               self.command_search_attributes, _limiter,
                               _operation)

    def get_commands_by_voice(self):
        """
        This function shouldn't be used by modules. Internal use only. For modules,
        use: `self._Commands['on']` to search by name.

        :return: Pointer to array of all devices.
        :rtype: dict
        """
        return self.__yombocommandsByVoice

    def get_local_commands(self):
        """
        Return a dictionary with all the public commands.

        :return: Returns any commands that are not publicly available.
        :rtype: list of objects
        """
        results = {}
        for command_id, command in self.commands.items():
            if command.public <= 1:
                results[command_id] = command
        return results

    def get_public_commands(self):
        """
        Return a dictionary with all the public commands.

        :return:
        """
        results = {}
        for command_id, command in self.commands.items():
            if command.public == 2:
                results[command_id] = command
        return results

    @inlineCallbacks
    def dev_command_add(self, data, **kwargs):
        """
        Used to interact with the Yombo API to add a new command. This doesn't add a new command
        to the local gateway.

        :param data: Fields to send to the Yombo API. See https://yg2.in/api for details.
        :type data: dict
        :param kwargs: Currently unused.
        :return: Results on the success/fail of the add request.
        :rtype: dict
        """
        try:
            command_results = yield self._YomboAPI.request(
                'POST', '/v1/command', data)
        except YomboWarning as e:
            results = {
                'status': 'failed',
                'msg': "Couldn't add command: %s" % e.message,
                'apimsg': "Couldn't add command: %s" % e.message,
                'apimsghtml': "Couldn't add command: %s" % e.html_message,
            }
            return results

        # print("command edit results: %s" % command_results)

        results = {
            'status': 'success',
            'msg': "Command added.",
            'command_id': command_results['data']['id'],
        }
        return results

    @inlineCallbacks
    def dev_command_edit(self, command_id, data, **kwargs):
        """
        Used to interact with the Yombo API to edit a command. This doesn't edit the command
        on the local gateway.

        :param data: Fields to send to the Yombo API. See https://yombo.net/API:Commands for details.
        :type data: dict
        :param kwargs: Currently unused.
        :return: Results on the success/fail of the request.
        :rtype: dict
        """
        try:
            command_results = yield self._YomboAPI.request(
                'PATCH', '/v1/command/%s' % (command_id), data)
        except YomboWarning as e:
            results = {
                'status': 'failed',
                'msg': "Couldn't edit command: %s" % e.message,
                'apimsg': "Couldn't edit command: %s" % e.message,
                'apimsghtml': "Couldn't edit command: %s" % e.html_message,
            }
            return results

        # print("command edit results: %s" % command_results)

        results = {
            'status': 'success',
            'msg': "Command edited.",
            'command_id': command_id,
        }
        return results

    @inlineCallbacks
    def dev_command_delete(self, command_id, **kwargs):
        """
        Used to interact with the Yombo API to delete a command. This doesn't delete the command
        on the local gateway.

        :param data: Fields to send to the Yombo API. See https://yombo.net/API:Commands for details.
        :type data: dict
        :param kwargs: Currently unused.
        :return: Results on the success/fail of the request.
        :rtype: dict
        """
        try:
            command_results = yield self._YomboAPI.request(
                'DELETE', '/v1/command/%s' % command_id)
        except YomboWarning as e:
            results = {
                'status': 'failed',
                'msg': "Couldn't delete command: %s" % e.message,
                'apimsg': "Couldn't delete command: %s" % e.message,
                'apimsghtml': "Couldn't delete command: %s" % e.html_message,
            }
            return results

        results = {
            'status': 'success',
            'msg': "Command deleted.",
            'command_id': command_id,
        }
        return results

    @inlineCallbacks
    def dev_command_enable(self, command_id, **kwargs):
        """
        Used to interact with the Yombo API to enable a command. This doesn't enable the command
        on the local gateway.

        :param data: Fields to send to the Yombo API. See https://yombo.net/API:Commands for details.
        :type data: dict
        :param kwargs: Currently unused.
        :return: Results on the success/fail of the request.
        :rtype: dict
        """
        api_data = {
            'status': 1,
        }

        try:
            command_results = yield self._YomboAPI.request(
                'PATCH', '/v1/command/%s' % command_id, api_data)
        except YomboWarning as e:
            results = {
                'status': 'failed',
                'msg': "Couldn't enable command: %s" % e.message,
                'apimsg': "Couldn't enable command: %s" % e.message,
                'apimsghtml': "Couldn't enable command: %s" % e.html_message,
            }
            return results

        results = {
            'status': 'success',
            'msg': "Command enabled.",
            'command_id': command_id,
        }
        return results

    @inlineCallbacks
    def dev_command_disable(self, command_id, **kwargs):
        """
        Used to interact with the Yombo API to disable a command. This doesn't diable the command
        on the local gateway.

        :param data: Fields to send to the Yombo API. See https://yombo.net/API:Commands for details.
        :type data: dict
        :param kwargs: Currently unused.
        :return: Results on the success/fail of the request.
        :rtype: dict
        """
        #        print "disabling command: %s" % command_id
        api_data = {
            'status': 0,
        }

        try:
            command_results = yield self._YomboAPI.request(
                'PATCH', '/v1/command/%s' % command_id, api_data)
        except YomboWarning as e:
            results = {
                'status': 'failed',
                'msg': "Couldn't disable command: %s" % e.message,
                'apimsg': "Couldn't disable command: %s" % e.message,
                'apimsghtml': "Couldn't disable command: %s" % e.html_message,
            }
            return results

        results = {
            'status': 'success',
            'msg': "Command disabled.",
            'command_id': command_id,
        }
        return results

    def full_list_commands(self):
        """
        Return a list of dictionaries representing all known commands to this gateway.
        :return:
        """
        items = []
        for command_id, command in self.commands.items():
            items.append(command.asdict())
        return items
Пример #7
0
class Loader(YomboLibrary, object):
    """
    Responsible for loading libraries, and then delegating loading modules to
    the modules library.

    Libraries are never reloaded, however, during a reconfiguration,
    modules are unloaded, and then reloaded after configurations are done
    being downloaded.
    """
    @property
    def operating_mode(self):
        return self._operating_mode

    @operating_mode.setter
    def operating_mode(self, val):
        if RUN_PHASE[self._run_phase] > 2:
            self.loadedLibraries['states']['loader.operating_mode'] = val
        self._operating_mode = val

    @property
    def run_phase(self):
        return (self._run_phase, RUN_PHASE[self._run_phase])

    @operating_mode.setter
    def run_phase(self, val):
        if RUN_PHASE[val] > 2:
            self.loadedLibraries['states']['loader.run_phase'] = val
        self._run_phase = val

    def __getitem__(self, component_requested):
        """
        """
        logger.debug("looking for: {component_requested}",
                     component_requested=component_requested)
        if component_requested in self.loadedComponents:
            logger.debug("found by loadedComponents! {component_requested}",
                         component_requested=component_requested)
            return self.loadedComponents[component_requested]
        elif component_requested in self.loadedLibraries:
            logger.debug("found by loadedLibraries! {component_requested}",
                         component_requested=component_requested)
            return self.loadedLibraries[component_requested]
        elif component_requested in self._moduleLibrary:
            logger.debug("found by self._moduleLibrary! {component_requested}",
                         component_requested=self._moduleLibrary)
            return self._moduleLibrary[component_requested]
        else:
            raise YomboWarning(
                "Loader could not find requested component: {%s}" %
                component_requested, '101', '__getitem__', 'loader')

    def __init__(self, testing=False, loop=None):
        self._operating_mode = "system_init"
        self._run_phase = "system_init"
        self.unittest = testing
        self._moduleLibrary = None
        YomboLibrary.__init__(self)

        self.loadedComponents = FuzzySearch({self._FullName.lower(): self},
                                            .95)
        self.loadedLibraries = FuzzySearch({self._Name.lower(): self}, .95)
        self.libraryNames = {}
        self._moduleLibrary = None
        self._invoke_list_cache = {
        }  # Store a list of hooks that exist or not. A cache.
        self._operating_mode = None  # One of: first_run, config, run
        self.sigint = False  # will be set to true if SIGINT is received
        self.hook_counts = OrderedDict(
        )  # keep track of hook names, and how many times it's called.
        reactor.addSystemEventTrigger("before", "shutdown", self.shutdown)

    def shutdown(self):
        """
        This is called if SIGINT (ctrl-c) was caught. Very useful incase it was called during startup.
        :return:
        """
        self.run_phase = "shutdown"
        self.sigint = True

    @inlineCallbacks
    def start(self):  #on startup, load libraried, then modules
        """
        This is effectively the main start function.

        This function is called when the gateway is to startup. In turn,
        this function will load all the components and modules of the gateway.
        """
        logger.info("Importing libraries, this can take a few moments.")

        # Get a reference to the asyncio event loop.
        yield yombo.utils.sleep(0.01)  # kick the asyncio event loop
        self.event_loop = asyncio.get_event_loop()

        yield self.import_libraries()  # import and init all libraries

        if self.sigint:
            return
        logger.debug("Calling load functions of libraries.")

        self.run_phase = "modules_import"
        self.operating_mode = self.operating_mode  # so we can update the State!
        yield self._moduleLibrary.import_modules()
        for name, config in HARD_LOAD.items():
            if self.sigint:
                return
            self._log_loader('debug', name, 'library', 'modules_imported',
                             'About to call _modules_imported_.')
            if self.check_operating_mode(config['operating_mode']):
                libraryName = name.lower()
                yield self.library_invoke(libraryName,
                                          "_modules_imported_",
                                          called_by=self)
                HARD_LOAD[name]['_modules_imported_'] = True
            else:
                HARD_LOAD[name]['_modules_imported_'] = False
            self._log_loader('debug', name, 'library', 'modules_imported',
                             'Finished call to _modules_imported_.')

        self.run_phase = "libraries_load"
        for name, config in HARD_LOAD.items():
            # print "sigint: %s" % self.sigint
            if self.sigint:
                return
            # self._log_loader('debug', name, 'library', 'load', 'About to call _load_.')
            if self.check_operating_mode(config['operating_mode']):
                HARD_LOAD[name]['_load_'] = 'Starting'
                libraryName = name.lower()
                yield self.library_invoke(libraryName,
                                          "_load_",
                                          called_by=self)
                HARD_LOAD[name]['_load_'] = True
            else:
                HARD_LOAD[name]['_load_'] = False
            self._log_loader('debug', name, 'library', 'load',
                             'Finished call to _load_.')

        self._moduleLibrary = self.loadedLibraries['modules']

        self.run_phase = "modules_init"
        yield self._moduleLibrary.init_modules()

        #        logger.debug("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1Calling start function of libraries.")
        self.run_phase = "libraries_start"
        for name, config in HARD_LOAD.items():
            if self.sigint:
                return
            self._log_loader('debug', name, 'library', 'start',
                             'About to call _start_.')
            if self.check_operating_mode(config['operating_mode']):
                libraryName = name.lower()
                yield self.library_invoke(libraryName,
                                          "_start_",
                                          called_by=self)
                HARD_LOAD[name]['_start_'] = True
            else:
                HARD_LOAD[name]['_start_'] = False
            self._log_loader('debug', name, 'library', '_start_',
                             'Finished call to _start_.')

        self.run_phase = "modules_start"
        yield self._moduleLibrary.load_modules()  #includes load & start
        self.run_phase = "modules_started"

        self.run_phase = "libraries_started"
        for name, config in HARD_LOAD.items():
            if self.sigint:
                return
            self._log_loader('debug', name, 'library', 'started',
                             'About to call _started_.')
            if self.check_operating_mode(config['operating_mode']):
                libraryName = name.lower()
                yield self.library_invoke(libraryName,
                                          "_started_",
                                          called_by=self)
                HARD_LOAD[name]['_started_'] = True
            else:
                HARD_LOAD[name]['_started_'] = False

        for name, config in HARD_LOAD.items():
            if self.sigint:
                return
            self._log_loader('debug', name, 'library', '_modules_started_',
                             'About to call _modules_started_.')
            if self.check_operating_mode(config['operating_mode']):
                libraryName = name.lower()
                yield self.library_invoke(libraryName,
                                          "_modules_started_",
                                          called_by=self)
                HARD_LOAD[name]['_started_'] = True
            else:
                HARD_LOAD[name]['_started_'] = False

        self.loadedLibraries['notifications'].add({
            'title': 'System started',
            'message': 'System successfully started.',
            'timeout': 300,
            'source': 'Yombo Gateway System',
            'persist': False,
            'always_show': False,
        })

        logger.info("Yombo Gateway started.")

    @inlineCallbacks
    def unload(self):
        """
        Called when the gateway should stop. This will gracefully stop the gateway.

        First, unload all modules, then unload all components.
        """
        self.sigint = True  # it's 99.999% true - usually only shutdown due to this.
        if self._moduleLibrary is not None:
            yield self._moduleLibrary.unload_modules()
        yield self.unload_libraries()
        # self.loop.close()

    def Loader_i18n_atoms(self, **kwargs):
        return [
            {
                'loader.operating_mode': {
                    'en': 'One of: first_run, run, config',
                },
            },
        ]

    def check_component_status(self, name, function):
        if name in HARD_LOAD:
            if function in HARD_LOAD[name]:
                return HARD_LOAD[name][function]
        return None

    def _log_loader(self, level, label, type, method, msg=""):
        """
        A common log format for loading/unloading libraries and modules.

        :param level: Log level - debug, info, warn...
        :param label: Module label "x10", "messages"
        :param type: Type of item being loaded: library, module
        :param method: Method being called.
        :param msg: Optional message to include.
        :return:
        """
        logit = func = getattr(logger, level)
        logit("Loader: {label}({type})::{method} - {msg}",
              label=label,
              type=type,
              method=method,
              msg=msg)

    def import_libraries_failure(self, failure):
        logger.error("Got failure during import of library: {failure}",
                     failure=failure)

    @inlineCallbacks
    def import_libraries(self):
        """
        Import then "init" all libraries. Call "loadLibraries" when done.
        """
        logger.debug("Importing server libraries.")
        self._run_phase = "libraries_import"
        for name, config in HARD_LOAD.items():
            if self.sigint:
                return
            HARD_LOAD[name]['__init__'] = 'Starting'
            pathName = "yombo.lib.%s" % name
            self.import_component(pathName, name, 'library')
            HARD_LOAD[name]['__init__'] = True

        logger.debug("Calling init functions of libraries.")
        self._run_phase = "libraries_init"
        for name, config in HARD_LOAD.items():
            if self.sigint:
                return
            if self.check_operating_mode(config['operating_mode']) is False:
                HARD_LOAD[name]['_init_'] = False
                continue
            HARD_LOAD[name]['_init_'] = 'Starting'
            # self._log_loader('debug', name, 'library', 'init', 'About to call _init_.')

            component = name.lower()
            library = self.loadedLibraries[component]
            library._event_loop = self.event_loop
            library._AMQP = self.loadedLibraries['amqp']
            library._AMQPYombo = self.loadedLibraries['amqpyombo']
            library._Atoms = self.loadedLibraries['atoms']
            library._Automation = self.loadedLibraries['automation']
            library._Commands = self.loadedLibraries['commands']
            library._Configs = self.loadedLibraries['configuration']
            library._Devices = self.loadedLibraries['devices']
            library._Locations = self.loadedLibraries['locations']
            library._DeviceTypes = self.loadedLibraries['devicetypes']
            library._Gateways = self.loadedLibraries['gateways']
            library._GPG = self.loadedLibraries['gpg']
            library._InputTypes = self.loadedLibraries['inputtypes']
            library._Libraries = self.loadedLibraries
            library._Loader = self
            library._LocalDB = self.loadedLibraries['localdb']
            library._Modules = self._moduleLibrary
            library._Nodes = self.loadedLibraries['nodes']
            library._Notifications = self.loadedLibraries['notifications']
            library._Localize = self.loadedLibraries['localize']
            library._MQTT = self.loadedLibraries['mqtt']
            library._Queue = self.loadedLibraries['queue']
            library._SQLDict = self.loadedLibraries['sqldict']
            library._SSLCerts = self.loadedLibraries['sslcerts']
            library._States = self.loadedLibraries['states']
            library._Statistics = self.loadedLibraries['statistics']
            library._Tasks = self.loadedLibraries['tasks']
            library._Times = self.loadedLibraries['times']
            library._YomboAPI = self.loadedLibraries['yomboapi']
            library._Variables = self.loadedLibraries['variables']
            library._Validate = self.loadedLibraries['validate']
            if hasattr(library, '_init_') and isinstance(library._init_, Callable) \
                    and yombo.utils.get_method_definition_level(library._init_) != 'yombo.core.module.YomboModule':
                d = Deferred()
                d.addCallback(lambda ignored: self._log_loader(
                    'debug', name, 'library', 'init', 'About to call _init_.'))
                d.addCallback(lambda ignored: maybeDeferred(library._init_))
                d.addErrback(self.import_libraries_failure)
                # d.addCallback(lambda ignored: self._log_loader('debug', name, 'library', 'init', 'Done with call _init_.'))
                d.callback(1)
                yield d
                # d.addCallback(maybeDeferred, library._init_)
                # self._log_loader('debug', name, 'library', 'init', 'Finished to call _init_.')
                # try:
                #     d = yield maybeDeferred(library._init_, self)
                # except YomboCritical, e:
                #     logger.error("---==(Critical Server Error in init function for library: {name})==----", name=name)
                #     logger.error("--------------------------------------------------------")
                #     logger.error("Error message: {e}", e=e)
                #     logger.error("--------------------------------------------------------")
                #     e.exit()
                # except:
                #     logger.error("-------==(Error in init function for library: {name})==---------", name=name)
                #     logger.error("1:: {e}", e=sys.exc_info())
                #     logger.error("---------------==(Traceback)==--------------------------")
                #     logger.error("{e}", e=traceback.print_exc(file=sys.stdout))
                #     logger.error("--------------------------------------------------------")
                HARD_LOAD[name]['_init_'] = True
            else:
                logger.error(
                    "----==(Library doesn't have init function: {name})==-----",
                    name=name)

    def check_operating_mode(self, allowed):
        """
        Checks if something should be run based on the current operating_mode.
        :param config: Either string or list or posible operating_modes
        :return: True/False
        """
        operating_mode = self.operating_mode

        if operating_mode is None:
            return True

        def check_operating_mode_inside(mode, operating_mode):
            if mode == 'all':
                return True
            elif mode == operating_mode:
                return True
            return False

        if isinstance(allowed, str):  # we have a string
            return check_operating_mode_inside(allowed, operating_mode)
        else:  # we have something else
            for item in allowed:
                if check_operating_mode_inside(item, operating_mode):
                    return True

    def library_invoke_failure(self, failure, requested_library, hook_name):
        logger.error(
            "Got failure during library invoke for hook ({requested_library}::{hook_name}): {failure}",
            requested_library=requested_library,
            hook_name=hook_name,
            failure=failure)

    @inlineCallbacks
    def library_invoke(self, requested_library, hook, **kwargs):
        """
        Invokes a hook for a a given library. Passes kwargs in, returns the results to caller.
        """
        requested_library = requested_library.lower()
        if requested_library not in self.loadedLibraries:
            raise YomboWarning('Requested library is missing: %s' %
                               requested_library)

        if 'called_by' not in kwargs:
            raise YomboWarning(
                "Unable to call hook '%s:%s', missing 'called_by' named argument."
                % (requested_library, hook))
        calling_component = kwargs['called_by']

        cache_key = requested_library + hook
        if cache_key in self._invoke_list_cache:
            if self._invoke_list_cache[cache_key] is False:
                # logger.warn("Cache hook ({cache_key})...SKIPPED", cache_key=cache_key)
                returnValue(
                    None)  # skip. We already know function doesn't exist.
        library = self.loadedLibraries[requested_library]
        if requested_library == 'Loader':
            returnValue(None)
        if not (hook.startswith("_") and hook.endswith("_")):
            hook = library._Name.lower() + "_" + hook
        if hasattr(library, hook):
            method = getattr(library, hook)
            if isinstance(method, Callable):
                if library._Name not in self.hook_counts:
                    self.hook_counts[library._Name] = {}
                if hook not in self.hook_counts:
                    self.hook_counts[library._Name][hook] = {
                        'Total Count': {
                            'count': 0
                        }
                    }
                # print "hook counts: %s" % self.hook_counts
                # print "hook counts: %s" % self.hook_counts[library._Name][hook]
                if calling_component not in self.hook_counts[
                        library._Name][hook]:
                    self.hook_counts[
                        library._Name][hook][calling_component] = {
                            'count': 0
                        }
                self.hook_counts[library._Name][hook][calling_component][
                    'count'] = self.hook_counts[
                        library._Name][hook][calling_component]['count'] + 1
                self.hook_counts[library._Name][hook]['Total Count'][
                    'count'] = self.hook_counts[
                        library._Name][hook]['Total Count']['count'] + 1
                self._invoke_list_cache[cache_key] = True

                try:
                    d = Deferred()
                    d.addCallback(lambda ignored: self._log_loader(
                        'debug', library._Name, 'library', hook,
                        'About to call %s' % hook))
                    # print("calling %s:%s" % (library._Name, hook))
                    d.addCallback(
                        lambda ignored: maybeDeferred(method, **kwargs))
                    d.addErrback(self.library_invoke_failure,
                                 requested_library, hook)
                    d.callback(1)
                    results = yield d
                    return results
                except RuntimeWarning as e:
                    pass
            else:
                logger.debug(
                    "Cache library hook ({library}:{hook})...setting false",
                    library=library._FullName,
                    hook=hook)
                logger.debug(
                    "----==(Library {library} doesn't have a callable function: {function})==-----",
                    library=library._FullName,
                    function=hook)
                raise YomboWarning("Hook is not callable: %s" % hook)
        else:
            #            logger.debug("Cache hook ({library}:{hook})...setting false", library=library._FullName, hook=hook)
            self._invoke_list_cache[cache_key] = False

    @inlineCallbacks
    def library_invoke_all(self, hook, fullName=False, **kwargs):
        """
        Calls library_invoke for all loaded libraries.
        """
        results = {}
        to_process = {}
        if 'components' in kwargs:
            to_process = kwargs['components']
        else:
            for library_name, library in self.loadedLibraries.items():
                #                print "library %s" % library
                label = library._FullName.lower(
                ) if fullName else library._Name.lower()
                to_process[library_name] = label
        if 'stoponerror' in kwargs:
            stoponerror = kwargs['stoponerror']
        else:
            kwargs['stoponerror'] = True
            stoponerror = True

        for library_name, library in self.loadedLibraries.items():
            # logger.debug("invoke all:{libraryName} -> {hook}", libraryName=library_name, hook=hook )
            try:
                result = yield self.library_invoke(library_name, hook,
                                                   **kwargs)
                if result is None:
                    continue
                results[library._FullName] = result
            except YomboWarning:
                pass
            except YomboHookStopProcessing as e:
                if stoponerror is not True:
                    pass
                e.collected = results
                e.by_who = label
                raise

        returnValue(results)

    def import_component(self,
                         pathName,
                         componentName,
                         componentType,
                         componentUUID=None):
        """
        Load component of given name. Can be a core library, or a module.
        """
        pymodulename = pathName.lower()
        self._log_loader('debug', componentName, componentType, 'import',
                         'About to import.')
        try:
            pyclassname = ReSearch("(?<=\.)([^.]+)$", pathName).group(1)
        except AttributeError:
            self._log_loader('error', componentName, componentType, 'import',
                             'Not found. Path: %s' % pathName)
            logger.error("Library or Module not found: {pathName}",
                         pathName=pathName)
            raise YomboCritical("Library or Module not found: %s", pathName)
        try:
            module_root = __import__(pymodulename, globals(), locals(), [], 0)
        except ImportError as detail:
            self._log_loader('error', componentName, componentType, 'import',
                             'Not found. Path: %s' % pathName)
            logger.error(
                "--------==(Error: Library or Module not found)==--------")
            logger.error("----Name: {pathName},  Details: {detail}",
                         pathName=pathName,
                         detail=detail)
            logger.error(
                "---------------==(Traceback)==--------------------------")
            logger.error("{trace}", trace=traceback.format_exc())
            logger.error(
                "--------------------------------------------------------")
            raise ImportError("Cannot import module, not found.")
        except Exception as e:
            logger.error(
                "---------------==(Traceback)==--------------------------")
            logger.error("{trace}", trace=traceback.format_exc())
            logger.error(
                "--------------------------------------------------------")
            logger.warn(
                "An exception of type {etype} occurred in yombo.lib.nodes:import_component. Message: {msg}",
                etype=type(e),
                msg=e)
            raise ImportError(e)
        module_tail = reduce(lambda p1, p2: getattr(p1, p2), [
            module_root,
        ] + pymodulename.split('.')[1:])
        # print "module_tail: %s   pyclassname: %s" % (module_tail, pyclassname)
        klass = getattr(module_tail, pyclassname)
        # print "klass: %s  " % klass

        # Put the component into various lists for mgmt
        if not isinstance(klass, Callable):
            logger.error(
                "Unable to start class '{classname}', it's not callable.",
                classname=pyclassname)
            raise ImportError(
                "Unable to start class '%s', it's not callable." % pyclassname)

        try:
            # Instantiate the class
            # logger.debug("Instantiate class: {pyclassname}", pyclassname=pyclassname)
            moduleinst = klass(
            )  # start the class, only libraries get the loader
            if componentType == 'library':
                if componentName.lower() == 'modules':
                    self._moduleLibrary = moduleinst

                self.loadedComponents["yombo.gateway.lib." +
                                      str(componentName.lower())] = moduleinst
                self.loadedLibraries[str(componentName.lower())] = moduleinst
                # this is mostly for manhole module, but maybe useful elsewhere?
                temp = componentName.split(".")
                self.libraryNames[temp[-1]] = moduleinst
            else:
                self.loadedComponents["yombo.gateway.modules." +
                                      str(componentName.lower())] = moduleinst
                return moduleinst, componentName.lower()

        except YomboCritical as e:
            logger.debug(
                "@!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
            )
            logger.debug("{e}", e=e)
            logger.debug(
                "@!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
            )
            e.exit()
            raise

    @inlineCallbacks
    def unload_libraries(self):
        """
        Only called when server is doing shutdown. Stops controller, server control and server data..
        """
        logger.debug("Stopping libraries: {stuff}", stuff=HARD_UNLOAD)
        for name, config in HARD_UNLOAD.items():
            if self.check_operating_mode(config['operating_mode']):
                logger.debug("stopping: {name}", name=name)
                yield self.library_invoke(name, "_stop_", called_by=self)

        for name, config in HARD_UNLOAD.items():
            if self.check_operating_mode(config['operating_mode']):
                logger.debug("_unload_: {name}", name=name)
                yield self.library_invoke(name, "_unload_", called_by=self)

    def _handleError(self, err):
        #        logger.error("Error caught: %s", err.getErrorMessage())
        #        logger.error("Error type: %s  %s", err.type, err.value)
        err.raiseException()

    def get_loaded_component(self, name):
        """
        Returns loaded module object by name. Module must be loaded.
        """
        return self.loadedComponents[name.lower()]

    def get_all_loaded_components(self):
        """
        Returns loaded module object by name. Module must be loaded.
        """
        return self.loadedComponents

    def find_function(self, component_type, component_name,
                      component_function):
        """
        Finds a function within the system by namme. This is useful for when you need to
        save a pointer to a callback to sql or a dictionary, but cannot save pointers to
        a function because the system may restart. This offers another method to reach
        various functions within the system.

        :param component_type: Either 'module' or 'library'.
        :param component_name: Module or libary name.
        :param component_function: Name of the function. A string for direct access to the function or a list
            can be provided and it will search the a dictionary of items for a callback.
        :return:
        """

        if component_type == 'library':
            if component_name not in self.loadedLibraries:
                logger.info("Library not found: {loadedLibraries}",
                            loadedLibraries=loadedLibraries)
                raise YomboWarning("Cannot library name.")

            if isinstance(component_function, list):
                if hasattr(self.loadedLibraries[component_name],
                           component_function[0]):
                    remote_attribute = getattr(
                        self.loadedLibraries[component_name],
                        component_function[0])  # the dictionary
                    if component_function[1] in remote_attribute:
                        if not isinstance(
                                remote_attribute[component_function[1]],
                                Callable):  # the key should be callable.
                            logger.info(
                                "Could not find callable library function by name: '{component_type} :: {component_name} :: (list) {component_function}'",
                                component_type=component_type,
                                component_name=component_name,
                                component_function=component_function)
                            raise YomboWarning("Cannot find callable")
                        else:
                            logger.info(
                                "Look ma, I found a cool function here.")
                            return remote_attribute[component_function[1]]
            else:
                if hasattr(self.loadedLibraries[component_name],
                           component_function):
                    method = getattr(self.loadedLibraries[component_name],
                                     component_function)
                    if not isinstance(method, Callable):
                        logger.info(
                            "Could not find callable modoule function by name: '{component_type} :: {component_name} :: {component_function}'",
                            component_type=component_type,
                            component_name=component_name,
                            component_function=component_function)
                        raise YomboWarning("Cannot find callable")
                    else:
                        return method
        elif component_type == 'module':
            modules = self._moduleLibrary
            if component_name not in modules._modulesByName:
                raise YomboWarning("Cannot module name.")

            if hasattr(modules._modulesByName[component_name],
                       component_function[0]):
                remote_attribute = getattr(
                    modules._modulesByName[component_name],
                    component_function[0])
                if component_function[1] in remote_attribute:
                    if not isinstance(remote_attribute[component_function[1]],
                                      Callable):  # the key should be callable.
                        logger.info(
                            "Could not find callable module function by name: '{component_type} :: {component_name} :: (list){component_function}'",
                            component_type=component_type,
                            component_name=component_name,
                            component_function=component_function)
                        raise YomboWarning("Cannot find callable")
                    else:
                        logger.info("Look ma, I found a cool function here.")
                        return remote_attribute[component_function[1]]
            else:
                if hasattr(modules._modulesByName[component_name],
                           component_function):
                    method = getattr(modules._modulesByName[component_name],
                                     component_function)
                    if not isinstance(method, Callable):
                        logger.info(
                            "Could not find callable module function by name: '{component_type} :: {component_name} :: {component_function}'",
                            component_type=component_type,
                            component_name=component_name,
                            component_function=component_function)
                        raise YomboWarning("Cannot find callable")
                    else:
                        return method
        else:
            logger.warn("Not a valid component_type: {component_type}",
                        component_type=component_type)
            raise YomboWarning("Invalid component_type.")
Пример #8
0
class Loader(YomboLibrary, object):
    """
    Responsible for loading libraries, and then delegating loading modules to
    the modules library.

    Libraries are never reloaded, however, during a reconfiguration,
    modules are unloaded, and then reloaded after configurations are done
    being downloaded.
    """
    @property
    def operation_mode(self):
        return self._operation_mode

    @operation_mode.setter
    def operation_mode(self, val):
        self.loadedLibraries['atoms']['loader.operation_mode'] = val
        self._operation_mode = val

    def __getitem__(self, component_requested):
        """
        """
        logger.debug("looking for: {component_requested}", component_requested=component_requested)
        if component_requested in self.loadedComponents:
            logger.debug("found by loadedComponents! {component_requested}", component_requested=component_requested)
            return self.loadedComponents[component_requested]
        elif component_requested in self.loadedLibraries:
            logger.debug("found by loadedLibraries! {component_requested}", component_requested=component_requested)
            return self.loadedLibraries[component_requested]
        elif component_requested in self._moduleLibrary:
            logger.debug("found by self._moduleLibrary! {component_requested}", component_requested=self._moduleLibrary)
            return self._moduleLibrary[component_requested]
        else:
            raise YomboWarning("Loader could not find requested component: {%s}"
                               % component_requested, '101', '__getitem__', 'loader')

    def __init__(self, testing=False):
        self.unittest = testing
        self._moduleLibrary = None
        YomboLibrary.__init__(self)

        self.loadedComponents = FuzzySearch({self._FullName.lower(): self}, .95)
        self.loadedLibraries = FuzzySearch({self._Name.lower(): self}, .95)
        self.libraryNames = {}
        self.__localModuleVars = {}
        self._moduleLibrary = None
        self._invoke_list_cache = {}  # Store a list of hooks that exist or not. A cache.
        self._operation_mode = None  # One of: firstrun, config, run
        self.sigint = False  # will be set to true if SIGINT is received
        self.hook_counts = OrderedDict()  # keep track of hook names, and how many times it's called.
        reactor.addSystemEventTrigger("before", "shutdown", self.shutdown)
        # signal(SIGINT, self.shutdown2)

    def shutdown(self):
        """
        This is called if SIGINT (ctrl-c) was caught. Very useful incase it was called during startup.
        :return:
        """
        print "SIGINT received - twisted"
        self.sigint = True

    # def shutdown2(self, signum, frame):
    #     """
    #     This is called if SIGINT (ctrl-c) was caught. Very useful incase it was called during startup.
    #     :return:
    #     """
    #     print 'Signal handler called with signal %s' % signum
    #     print "WHAT!  I was called - signal"
    #     self.sigint = True
    #     reactor.stop()

    @inlineCallbacks
    def start(self):  #on startup, load libraried, then modules
        """
        This is effectively the main start function.

        This function is called when the gateway is to startup. In turn,
        this function will load all the components and modules of the gateway.
        """
        logger.info("Importing libraries, this can take a few moments.")
        yield self.import_libraries() # import and init all libraries
        # returnValue(None)

        # if self.sigint:
        #     return
        logger.debug("Calling load functions of libraries.")
        for name, config in HARD_LOAD.iteritems():
#            print "sigint: %s" % self.sigint
#             if self.sigint:
#                 return
            self._log_loader('debug', name, 'library', 'load', 'About to call _load_.')
            if self.check_operation_mode(config['operation_mode']):
                HARD_LOAD[name]['_load_'] = 'Starting'
                libraryName = name.lower()
                yield self.library_invoke(libraryName, "_load_", self)
                HARD_LOAD[name]['_load_'] = True
            else:
                HARD_LOAD[name]['_load_'] = False
            self._log_loader('debug', name, 'library', 'load', 'Finished call to _load_.')

        self._moduleLibrary = self.loadedLibraries['modules']
#        logger.debug("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1Calling start function of libraries.")
        for name, config in HARD_LOAD.iteritems():
            if self.sigint:
                return
            self._log_loader('debug', name, 'library', 'start', 'About to call _start_.')
            if self.check_operation_mode(config['operation_mode']):
                libraryName =  name.lower()
                yield self.library_invoke(libraryName, "_start_", self)
                HARD_LOAD[name]['_start_'] = True
            else:
                HARD_LOAD[name]['_start_'] = False

        for name, config in HARD_LOAD.iteritems():
            if self.sigint:
                return
            self._log_loader('debug', name, 'library', 'started', 'About to call _started_.')
            if self.check_operation_mode(config['operation_mode']):
                libraryName =  name.lower()
                yield self.library_invoke(libraryName, "_started_", self)
                HARD_LOAD[name]['_started_'] = True
            else:
                HARD_LOAD[name]['_started_'] = False

        yield self._moduleLibrary.load_modules()
        self.loadedLibraries['notifications'].add({'title': 'System started',
            'message': 'System successfully started.', 'timeout': 300, 'source': 'Yombo Gateway System',
            'persist': False,
            'always_show': False,
        })

    @inlineCallbacks
    def unload(self):
        """
        Called when the gateway should stop. This will gracefully stop the gateway.

        First, unload all modules, then unload all components.
        """
        self.sigint = True  # it's 99.999% true - usually only shutdown due to this.
        if self._moduleLibrary is not None:
            yield self._moduleLibrary.unload_modules()
        yield self.unload_components()

    def Times_i18n_atoms(self, **kwargs):
       return [
           {'loader.operation_mode': {
               'en': 'One of: firstrun, run, config',
               },
           },
       ]

    def check_component_status(self, name, function):
        if name in HARD_LOAD:
            if function in HARD_LOAD[name]:
                return HARD_LOAD[name][function]
        return None

    def _log_loader(self, level, label, type, method, msg=""):
        """
        A common log format for loading/unloading libraries and modules.

        :param level: Log level - debug, info, warn...
        :param label: Module label "x10", "messages"
        :param type: Type of item being loaded: library, module
        :param method: Method being called.
        :param msg: Optional message to include.
        :return:
        """
        logit = func = getattr(logger, level)
        logit("Loader: {label}({type})::{method} - {msg}", label=label, type=type, method=method, msg=msg)

    @inlineCallbacks
    def import_libraries(self):
        """
        Import then "init" all libraries. Call "loadLibraries" when done.
        """
        logger.debug("Importing server libraries.")
        for name, config in HARD_LOAD.iteritems():
            if self.sigint:
                return
            HARD_LOAD[name]['__init__'] = 'Starting'
            pathName = "yombo.lib.%s" % name
            self.import_component(pathName, name, 'library')
            HARD_LOAD[name]['__init__'] = True

        logger.debug("Calling init functions of libraries.")
        for name, config in HARD_LOAD.iteritems():
            if self.sigint:
                return
            if self.check_operation_mode(config['operation_mode']) is False:
                HARD_LOAD[name]['_init_'] = False
                continue
            HARD_LOAD[name]['_init_'] = 'Starting'
            self._log_loader('debug', name, 'library', 'init', 'About to call _init_.')

            component = name.lower()
            library = self.loadedLibraries[component]
            library._AMQP = self.loadedLibraries['amqp']
            library._Atoms = self.loadedLibraries['atoms']
            library._Commands = self.loadedLibraries['commands']
            library._Configs = self.loadedLibraries['configuration']
            library._Devices = self.loadedLibraries['devices']
            library._DeviceTypes = self.loadedLibraries['devicetypes']
            library._InputTypes = self.loadedLibraries['inputtypes']
            library._Libraries = self.loadedLibraries
            library._Loader = self
            library._Modules = self._moduleLibrary
            library._Notifications = self.loadedLibraries['notifications']
            library._Localize = self.loadedLibraries['localize']
            library._MQTT = self.loadedLibraries['mqtt']
            library._SQLDict = self.loadedLibraries['sqldict']
            library._States = self.loadedLibraries['states']
            library._Statistics = self.loadedLibraries['statistics']
            library._YomboAPI = self.loadedLibraries['yomboapi']
            library._Variables = self.loadedLibraries['variables']
            if hasattr(library, '_init_') and callable(library._init_) \
                    and yombo.utils.get_method_definition_level(library._init_) != 'yombo.core.module.YomboModule':
                d = yield maybeDeferred(library._init_)
                self._log_loader('debug', name, 'library', 'init', 'Finished to call _init_.')
                # try:
                #     d = yield maybeDeferred(library._init_, self)
                # except YomboCritical, e:
                #     logger.error("---==(Critical Server Error in init function for library: {name})==----", name=name)
                #     logger.error("--------------------------------------------------------")
                #     logger.error("Error message: {e}", e=e)
                #     logger.error("--------------------------------------------------------")
                #     e.exit()
                # except:
                #     logger.error("-------==(Error in init function for library: {name})==---------", name=name)
                #     logger.error("1:: {e}", e=sys.exc_info())
                #     logger.error("---------------==(Traceback)==--------------------------")
                #     logger.error("{e}", e=traceback.print_exc(file=sys.stdout))
                #     logger.error("--------------------------------------------------------")
                HARD_LOAD[name]['_init_'] = True
            else:
                logger.error("----==(Library doesn't have init function: {name})==-----", name=name)

    def check_operation_mode(self, allowed):
        """
        Checks if something should be run based on the current operation_mode.
        :param config: Either string or list or posible operation_modes
        :return: True/False
        """
        op_mode = self.operation_mode

        if op_mode is None:
            return True

        def check_operation_mode_inside(mode, op_mode):
            if mode == 'all':
                return True
            elif mode == op_mode:
                return True
            return False

        if isinstance(allowed, basestring):  # we have a string
            return check_operation_mode_inside(allowed, op_mode)
        else: # we have something else
            for item in allowed:
                if check_operation_mode_inside(item, op_mode):
                    return True

    @inlineCallbacks
    def library_invoke(self, requested_library, hook, called_by=None, **kwargs):
        """
        Invokes a hook for a a given library. Passes kwargs in, returns the results to caller.
        """
        if requested_library not in self.loadedLibraries:
            returnValue(None)
        if called_by is not None:
            called_by = called_by._FullName
        else:
            called_by = 'Unknown'

        cache_key = requested_library + hook
        if cache_key in self._invoke_list_cache:
            if self._invoke_list_cache[cache_key] is False:
                # logger.warn("Cache hook ({cache_key})...SKIPPED", cache_key=cache_key)
                returnValue(None) # skip. We already know function doesn't exist.
        library = self.loadedLibraries[requested_library]
        if requested_library == 'Loader':
            returnValue(None)
        if not (hook.startswith("_") and hook.endswith("_")):
            hook = library._Name.lower() + "_" + hook
        if hasattr(library, hook):
            method = getattr(library, hook)
            self._log_loader('debug', requested_library, 'library', 'library_invoke', 'About to call: %s' % hook)
            if callable(method):
                if library._Name not in self.hook_counts:
                    self.hook_counts[library._Name] = {}
                if hook not in self.hook_counts:
                    self.hook_counts[library._Name][hook] = {'Total Count': {'count': 0}}
                # print "hook counts: %s" % self.hook_counts
                # print "hook counts: %s" % self.hook_counts[library._Name][hook]
                if called_by not in self.hook_counts[library._Name][hook]:
                    self.hook_counts[library._Name][hook][called_by] = {'count': 0}
                self.hook_counts[library._Name][hook][called_by]['count'] = self.hook_counts[library._Name][hook][called_by]['count'] + 1
                self.hook_counts[library._Name][hook]['Total Count']['count'] = self.hook_counts[library._Name][hook]['Total Count']['count'] + 1
                self._invoke_list_cache[cache_key] = True
                results = yield maybeDeferred(method, **kwargs)
                self._log_loader('debug', requested_library, 'library', 'library_invoke', 'Finished with call: %s' % hook)
                returnValue(results)
            else:
                logger.warn("Cache library hook ({library}:{hook})...setting false", library=library._FullName, hook=hook)
                logger.debug("----==(Library {library} doesn't have a callable function: {function})==-----", library=library._FullName, function=hook)
                raise YomboWarning("Hook is not callable: %s" % hook)
        else:
#            logger.debug("Cache hook ({library}:{hook})...setting false", library=library._FullName, hook=hook)
            self._invoke_list_cache[cache_key] = False

    def library_invoke_all(self, hook, fullName=False, called_by=None, **kwargs):
        """
        Calls library_invoke for all loaded libraries.
        """
        results = {}
        to_process = {}
        if 'components' in kwargs:
            to_process = kwargs['components']
        else:
            for library_name, library in self.loadedLibraries.iteritems():
#                print "library %s" % library
                label = library._FullName.lower() if fullName else library._Name.lower()
                to_process[library_name] = label

        for library_name, library in self.loadedLibraries.iteritems():
            # logger.debug("invoke all:{libraryName} -> {hook}", libraryName=library_name, hook=hook )
            try:
                d = self.library_invoke(library_name, hook, called_by=called_by, **kwargs)
                if isinstance(d, Deferred):
                    result = getattr(d, 'result', None)
                    if result is not None:
#                      logger.warn("1111aaa:: {libraryName} {hook} {result}", libraryName=libraryName, hook=hook, result=result)
                      results[library._FullName] = result
            except YomboWarning:
                pass
            except YomboHookStopProcessing as e:
                e.collected = results
                e.by_who =  label
                raise

        return results

    def import_component(self, pathName, componentName, componentType, componentUUID=None):
        """
        Load component of given name. Can be a core library, or a module.
        """
        pymodulename = pathName.lower()
        self._log_loader('debug', componentName, componentType, 'import', 'About to import.')
        try:
            pyclassname = ReSearch("(?<=\.)([^.]+)$", pathName).group(1)
        except AttributeError:
            self._log_loader('error', componentName, componentType, 'import', 'Not found. Path: %s' % pathName)
            logger.error("Library or Module not found: {pathName}", pathName=pathName)
            raise YomboCritical("Library or Module not found: %s", pathName)
        try:
#            print "pymodulename: %s" % pymodulename
            module_root = __import__(pymodulename, globals(), locals(), [], 0)
            pass
        except ImportError as detail:
            self._log_loader('error', componentName, componentType, 'import', 'Not found. Path: %s' % pathName)
            logger.error("--------==(Error: Library or Module not found)==--------")
            logger.error("----Name: {pathName},  Details: {detail}", pathName=pathName, detail=detail)
            logger.error("--------------------------------------------------------")
            logger.error("{error}", error=sys.exc_info())
            logger.error("---------------==(Traceback)==--------------------------")
            logger.error("{trace}", trace=traceback.print_exc(file=sys.stdout))
            logger.error("--------------------------------------------------------")
            return

        module_tail = reduce(lambda p1, p2: getattr(p1, p2), [module_root, ]+pymodulename.split('.')[1:])
        # print "module_tail: %s   pyclassname: %s" % (module_tail, pyclassname)
        klass = getattr(module_tail, pyclassname)
        # print "klass: %s  " % klass

        # Put the component into various lists for mgmt
        if not callable(klass):
            logger.warn("Unable to start class '{classname}', it's not callable.", classname=pyclassname)
            return

        try:
            # Instantiate the class
            moduleinst = klass()  # start the class, only libraries get the loader
            if componentType == 'library':
                if componentName.lower() == 'modules':
                    self._moduleLibrary = moduleinst

                self.loadedComponents["yombo.gateway.lib." + str(componentName.lower())] = moduleinst
                self.loadedLibraries[str(componentName.lower())] = moduleinst
                # this is mostly for manhole module, but maybe useful elsewhere?
                temp = componentName.split(".")
                self.libraryNames[temp[-1]] = moduleinst
            else:
                self.loadedComponents["yombo.gateway.modules." + str(componentName.lower())] = moduleinst
                self._moduleLibrary.add_imported_module(componentUUID, str(componentName.lower()), moduleinst)

        except YomboCritical, e:
            logger.debug("@!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
            logger.debug("{e}", e=e)
            logger.debug("@!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
            e.exit()
            raise
Пример #9
0
class VoiceCmds(YomboLibrary):
    """
    Store all voice commands here and perfrom searches.

    The purpose of this class is two fold: it provides a single repository for
    all possible noun/verb combinations, and provides an ability to *not* have
    to be 100% accurate when looking up noun/verb pairs.

    Convert "voice_cmd" strings into voice commands.

    Also, provides searching for voice commands.
    """
    def __contains__(self, voice_command_requested):
        """
        Checks to if a provided voice command exists.

            >>> if 'cpu.count' in self._VoiceCmds:

        :raises YomboWarning: Raised when request is malformed.
        :param voice_command_requested: The voice command key to search for.
        :type voice_command_requested: string
        :return: Returns true if exists, otherwise false.
        :rtype: bool
        """
        if voice_command_requested in self._VoiceCmds:
            return True
        else:
            return False

    def __getitem__(self, voice_command_requested):
        """
        Search for a voice command. this will return a dictionary: id, cmd object, device object.

            >>> voice_command = self._VoiceCmds['living room light on']

        :raises YomboWarning: Raised when request is malformed.
        :raises KeyError: Raised when request is not found.
        :param voice_command_requested: The voice command key to search for.
        :type voice_command_requested: string
        :return: dict containing: 'id', 'cmd', 'device'
        :rtype: dict
        """
        return self.get(voice_command_requested)

    def __setitem__(self, voice_command_requested, value):
        """
        Sets are not allowed. Raises exception.

        :raises Exception: Always raised.
        """
        raise Exception("Not allowed.")

    def __delitem__(self, voice_command_requested):
        """
        Deletes are not allowed. Raises exception.

        :raises Exception: Always raised.
        """
        raise Exception("Not allowed.")

    def __iter__(self):
        """ iter voice commands. """
        return self.__yombocommands.__iter__()

    def __len__(self):
        """
        Returns an int of the number of voice commands defined.

        :return: The number of voice commands defined.
        :rtype: int
        """
        return len(self.__yombocommands)

    def __str__(self):
        """
        Returns the name of the library.
        :return: Name of the library
        :rtype: string
        """
        return "Yombo voice commands library"

    def keys(self):
        """
        Returns the keys of the voice commands that are defined.

        :return: A list of voice commands defined. 
        :rtype: list
        """
        return list(self.__yombocommands.keys())

    def items(self):
        """
        Gets a list of tuples representing the voice commands defined.

        :return: A list of tuples.
        :rtype: list
        """
        return list(self.__yombocommands.items())

    def iteritems(self):
        return iter(self.__yombocommands.items())

    def iterkeys(self):
        return iter(self.__yombocommands.keys())

    def itervalues(self):
        return iter(self.__yombocommands.values())

    def values(self):
        return list(self.__yombocommands.values())

    def _init_(self, **kwargs):
        """
        Construct a new voice_cmds Instance

        items is an dictionary to copy items from (optional)
        limiter is the match ratio below which mathes should not be considered
        """
        self.voice_command_strings = FuzzySearch(None, .80)
        self.voice_command_data = {}

        self.commandsByVoice = self._Commands.get_commands_by_voice()

    @inlineCallbacks
    def _modules_loaded_(self, **kwargs):
        """
        Implements the _modules_loaded_ and is called after _load_ is called for all the modules.

        Expects a list of events to subscribe to.

        **Hooks called**:

        * _voicecmds_add_ : Expects a list of message subscription events to subscrib to.

        **Usage**:

        .. code-block:: python

           def ModuleName_voice_cmds_load(self, **kwargs):
               return ['status']
        """
        voicecommands_to_add = yield global_invoke_all('_voicecmds_add_',
                                                       called_by=self)
        #        logger.info("voicecommands_to_add: {voice_cmds}", voice_cmds=voicecommands_to_add)

        for componentName, voice_cmds in voicecommands_to_add.items():
            if voice_cmds is None:
                continue
            for list in voice_cmds:
                logger.debug(
                    "For module '{fullName}', adding voice_cmd: {voice_cmd}, order: {order}",
                    voice_cmd=list['voice_cmd'],
                    fullName=componentName,
                    order=list['order'])
                self.add_by_string(list['voice_cmd'], list['call_back'],
                                   list['device'], list['order'])

    def get_all(self):
        """
        Returns a list of voice commands.
        :return:
        """
        results = {}
        for voice, voice_id in self.voice_command_strings.items():
            results[voice] = self.voice_command_data[voice_id]
        return results

    def get(self, voice_command_requested, limiter_override=None):
        """
        Search for a voice command. this will return a dictionary: id, cmd object, device object.

        You can use the device and command objects as desired.

            >>> self._VoiceCmds['living room light on']  #by name

        :param voicecmd_requested: Search all available voice commands for a phrase.
        :type voicecmd_requested: string
        :return: dict containing: 'id', 'cmd', 'device'
        :rtype: dict
        """
        try:
            result = self.voice_commands.search2(
                voice_command_requested, limiter_override=limiter_override)
            self._Statistics.increment("lib.voicecmds.search.found",
                                       bucket_size=30,
                                       anon=True)
            return self.voice_command_data[result]
        except:
            self._Statistics.increment("lib.voicecmds.search.not_found",
                                       bucket_size=30,
                                       anon=True)
            raise KeyError("Searched for voice command, none found: %s" %
                           voice_command_requested)

    def add_by_string(self,
                      voice_string,
                      call_back=None,
                      device=None,
                      order='devicecmd'):
        """
        Adds a voice command by using a string like "desk lamp [on, off]". This will add the following voice commands
        based on the value of 'order'.  If both is provided, the following will be added:

        * desklamp on
        * desklamp off
        * off desklamp
        * on desklamp

        You can specify 'order' as: both, devicecmd, cmddevice. This determines what ordering the voice commands
        are added. In the above example, specifying 'devicecmd', then only the first two items are added.

        Either a callback function must be provided or a device must be provided. Otherwise, the voice command
        will not be added and a YomboWarning will be raised if either or both are defined.

        :raises YomboWarning: If voiceString or destination is invalid.
        :param voice_string: Voice command string to process: "desklamp [on, off]"
        :type voice_string: string
        :param call_back: A function to send the voice command id, device, and command objects to.
        :type call_back: pointer to function
        :param device: A device id or device label to search for a matching device.
        :type device: string
        :param order: The ordering in which to add voice command text lookup. Default: devicecmd
        :type order: string
        """
        logger.debug("Adding voice command: {voice_string}",
                     voice_string=voice_string)
        if call_back is None and device is None:
            raise YomboWarning("'call_back' and 'device' are mising.", 1000,
                               'add_by_string', 'voicecmds')

        if call_back is not None and device is not None:
            raise YomboWarning(
                "Either specifiy 'call_back' or 'device', not both.", 1001,
                'add_by_string', 'voicecmds')

        try:
            tag_re = re.compile('(%s.*?%s)' % (re.escape('['), re.escape(']')))
            string_parts = tag_re.split(voice_string)
            string_parts = [_f for _f in string_parts
                            if _f]  # remove empty bits

            device_obj = None
            if device is not None:
                if isclass(device):
                    device_obj = device
                else:
                    device_obj = self._Devices[device]

            commands = []
        except:
            raise YomboWarning("Invalid format for 'voice_string'", 1010,
                               'add_by_string', 'voicecmds')

        if len(string_parts) > 2:
            raise YomboWarning("Invalid format for 'voice_string'", 1003,
                               'add_by_string', 'voicecmds')

        for part in range(len(string_parts)):
            string_parts[part] = string_parts[part].strip()
            if string_parts[part].startswith(
                    "[") and string_parts[part].endswith("]"):
                temp_commands = string_parts[part][1:-1].split(',')
                for cmd in range(len(temp_commands)):
                    cmd_temp = temp_commands[cmd].strip()
                    if len(cmd_temp) > 0:
                        commands.append(cmd_temp)
            else:
                device_label = string_parts[part]

        if len(commands) == 0:
            raise YomboWarning("No commands found in voice_string.", 1003,
                               'add_by_string', 'voicecmds')

        # logger.debug("commands by voice: {commandsByVoice}:{verb}", commandsByVoice=self.commandsByVoice, verb=verb)
        for cmd in commands:
            if cmd not in self.commandsByVoice:
                continue
            command = self.commandsByVoice[cmd]
            vc_id = random_string(length=12)

            if order == 'both':
                self.voice_command_strings["%s %s" %
                                           (device_label, cmd)] = vc_id
                self.voice_command_strings["%s %s" %
                                           (cmd, device_label)] = vc_id

            elif order == 'devicecmd':
                self.voice_command_strings["%s %s" %
                                           (device_label, cmd)] = vc_id

            else:
                self.voice_command_strings["%s %s" %
                                           (cmd, device_label)] = vc_id

            self.voice_command_data[vc_id] = {
                'id': vc_id,
                'cmd': command,
                'device': device_obj,
                'call_back': call_back
            }
Пример #10
0
class VoiceCmds(YomboLibrary):
    """
    Store all voice commands here and perfrom searches.

    The purpose of this class is two fold: it provides a single repository for
    all possible noun/verb combinations, and provides an ability to *not* have
    to be 100% accurate when looking up noun/verb pairs.

    Convert "voice_cmd" strings into voice commands.

    Also, provides searching for voice commands.
    """
    def __getitem__(self, voice_command_requested):
        """
        Search for a voice command. this will return a dictionary: id, cmd object, device object.

        You can use the device and command objects as desired.

            >>> self._VoiceCmds['living room light on']  #by name

        :param voicecmd_requested: Search all available voice commands for a phrase.
        :type voicecmd_requested: string
        :return: dict containing: 'id', 'cmd', 'device'
        :rtype: dict
        """
        return self.get(voice_command_requested)

    def __len__(self):
        return len(self.__yombocommands)

    def __contains__(self, command_requested):
        try:
            self.get_command(command_requested)
            return True
        except:
            return False


    def _init_(self):
        """
        Construct a new voice_cmds Instance

        items is an dictionary to copy items from (optional)
        limiter is the match ratio below which mathes should not be considered
        """
        self.voice_command_strings = FuzzySearch(None, .80)
        self.voice_command_data = {}

        self.commandsByVoice = self._Libraries['commands'].get_commands_by_voice()

    def _module_prestart_(self, **kwargs):
        """
        Implements the _module_prestart_ and is called after _load_ is called for all the modules.

        Expects a list of events to subscribe to.

        **Hooks called**:

        * _voicecmds_add_ : Expects a list of message subscription events to subscrib to.

        **Usage**:

        .. code-block:: python

           def ModuleName_voice_cmds_load(self, **kwargs):
               return ['status']
        """
        voicecommands_to_add = global_invoke_all('_voicecmds_add_')
#        logger.info("voicecommands_to_add: {voice_cmds}", voice_cmds=voicecommands_to_add)

        for componentName, voice_cmds in voicecommands_to_add.iteritems():
            if voice_cmds is None:
                continue
            for list in voice_cmds:
                logger.debug("For module '{fullName}', adding voice_cmd: {voice_cmd}, order: {order}", voice_cmd=list['voice_cmd'], fullName=componentName, order=list['order'])
                self.add_by_string(list['voice_cmd'], list['call_back'], list['device'], list['order'])

    def get_all(self):
        """
        Returns a list of voice commands.
        :return:
        """
        results = {}
        for voice, voice_id in self.voice_command_strings.iteritems():
            results[voice] = self.voice_command_data[voice_id]
        return results

    def get(self, voice_command_requested, limiter_override=None):
        """
        Search for a voice command. this will return a dictionary: id, cmd object, device object.

        You can use the device and command objects as desired.

            >>> self._VoiceCmds['living room light on']  #by name

        :param voicecmd_requested: Search all available voice commands for a phrase.
        :type voicecmd_requested: string
        :return: dict containing: 'id', 'cmd', 'device'
        :rtype: dict
        """
        try:
            result = self.voice_commands.search2(voice_command_requested, limiter_override=limiter_override)
            self._Statistics.increment("lib.voicecmds.search.found", bucket_time=30, anon=True)
            return self.voice_command_data[result]
        except:
            self._Statistics.increment("lib.voicecmds.search.not_found", bucket_time=30, anon=True)
            return None

    def add_by_string(self, voice_string, call_back = None, device = None, order = 'devicecmd'):
        """
        Adds a voice command by using a string like "desk lamp [on, off]". This will add the following voice commands
        based on the value of 'order'.  If both is provided, the following will be added:

        * desklamp on
        * desklamp off
        * off desklamp
        * on desklamp

        You can specify 'order' as: both, devicecmd, cmddevice. This determines what ordering the voice commands
        are added. In the above example, specifying 'devicecmd', then only the first two items are added.

        Either a callback function must be provided or a device must be provided. Otherwise, the voice command
        will not be added and a YomboWarning will be raised if either or both are defined.

        :raises YomboWarning: If voiceString or destination is invalid.
        :param voice_string: Voice command string to process: "desklamp [on, off]"
        :type voice_string: string
        :param call_back: A function to send the voice command id, device, and command objects to.
        :type call_back: pointer to function
        :param device: A device id or device label to search for a matching device.
        :type device: string
        :param order: The ordering in which to add voice command text lookup. Default: devicecmd
        :type order: string
        """
        logger.debug("Adding voice command: {voice_string}", voice_string=voice_string)
        if call_back is None and device is None:
            raise YomboWarning("'call_back' and 'device' are mising.", 1000, 'add_by_string', 'voicecmds')

        if call_back is not None and device is not None:
            raise YomboWarning("Either specifiy 'call_back' or 'device', not both.", 1001, 'add_by_string', 'voicecmds')

        try:
            tag_re = re.compile('(%s.*?%s)' % (re.escape('['), re.escape(']')))
            string_parts = tag_re.split(voice_string)
            string_parts = filter(None, string_parts) # remove empty bits

            device_obj = None
            if device is not None:
                if isclass(device):
                    device_obj = device
                else:
                    device_obj = self._Devices[device]

            commands = []
        except:
            raise YomboWarning("Invalid format for 'voice_string'", 1010, 'add_by_string', 'voicecmds')


        if len(string_parts) > 2:
            raise YomboWarning("Invalid format for 'voice_string'", 1003, 'add_by_string', 'voicecmds')

        for part in range(len(string_parts)):
            string_parts[part] = string_parts[part].strip()
            if string_parts[part].startswith("[") and string_parts[part].endswith("]"):
                temp_commands = string_parts[part][1:-1].split(',')
                for cmd in range(len(temp_commands)):
                    cmd_temp = temp_commands[cmd].strip()
                    if len(cmd_temp) > 0:
                        commands.append(cmd_temp)
            else:
                device_label = string_parts[part]

        if len(commands) == 0:
            raise YomboWarning("No commands found in voice_string.", 1003, 'add_by_string', 'voicecmds')

        # logger.debug("commands by voice: {commandsByVoice}:{verb}", commandsByVoice=self.commandsByVoice, verb=verb)
        for cmd in commands:
            if cmd not in self.commandsByVoice:
                continue
            command = self.commandsByVoice[cmd]
            vc_id = random_string(length=12)

            if order == 'both':
                self.voice_command_strings["%s %s" % (device_label, cmd)] = vc_id
                self.voice_command_strings["%s %s" % (cmd, device_label)] = vc_id

            elif order == 'devicecmd':
                self.voice_command_strings["%s %s" % (device_label, cmd)] = vc_id

            else:
                self.voice_command_strings["%s %s" % (cmd, device_label)] = vc_id

            self.voice_command_data[vc_id] = {
                'id': vc_id,
                'cmd': command,
                'device': device_obj,
                'call_back': call_back
            }