Ejemplo n.º 1
0
    def __init__(self):

        ### Option parser
        parser = ArgumentParser()
        parser.add_argument("-i",
                          action="store_true",
                          dest="interactive",
                          default=False, \
                          help="Butler interactive mode (must be used WITH -f).")

        Plugin.__init__(self, name='butler', parser=parser)

        ### MQ
        # MQ publisher
        #self._mq_name = "interface-{0}.{1}".format(self._name, self.get_sanitized_hostname())
        self._mq_name = "butler"
        #self.zmq = zmq.Context()
        self.pub = MQPub(self.zmq, self._mq_name)

        # subscribe the MQ for interfaces inputs
        MQAsyncSub.__init__(self, self.zmq, self._name, ['interface.input'])

        ### Configuration elements
        try:
            cfg = Loader('butler')
            config = cfg.load()
            conf = dict(config[1])

            self.lang = conf['lang']
            self.butler_name = conf['name']
            self.butler_sex = conf['sex']
            self.butler_mood = None
            if self.butler_sex not in SEX_ALLOWED:
                self.log.error(
                    u"Exiting : the butler sex configured is not valid : '{0}'. Expecting : {1}"
                    .format(self.butler_sex, SEX_ALLOWED))
                self.force_leave()
                return

        except:
            self.log.error(
                u"Exiting : error while reading the configuration file '{0}' : {1}"
                .format(CONFIG_FILE, traceback.format_exc()))
            self.force_leave()
            return
        # user name (default is 'localuser')
        # this is not used for now on Domogik side
        self.user_name = "localuser"

        ### Prepare the brain
        # - validate packages

        # Start the brain :)
        self.brain = RiveScript(utf8=True)

        # set rivescript variables

        # Configure bot variables
        # all must be lower case....
        self.log.info("Configuring name and sex : {0}, {1}".format(
            self.butler_name.lower(), self.butler_sex.lower()))
        self.brain.set_variable("name", self.butler_name.lower())
        self.brain.set_variable("fullname", self.butler_name.lower())
        self.brain.set_variable("sex", self.butler_sex.lower())

        # set the PYTHONPATH
        sys.path.append(self.get_libraries_directory())

        # load the brain
        self.brain_content = None
        self.learn_content = None
        self.not_understood_content = None
        self.load_all_brain()

        # shortcut to allow the core brain package to reload the brain for learning
        self.brain.reload_butler = self.reload

        # history
        self.history = []

        print(u"*** Welcome in {0} world, your digital assistant! ***".format(
            self.butler_name))
        print(u"You may type /quit to let {0} have a break".format(
            self.butler_name))

        ### Interactive mode
        if self.options.interactive:
            self.log.info(u"Launched in interactive mode : running the chat!")
            # TODO : run as a thread
            #self.run_chat()
            thr_run_chat = Thread(None, self.run_chat, "run_chat", (), {})
            thr_run_chat.start()
        else:
            self.log.info(u"Not launched in interactive mode")

        ### TODO
        #self.add_stop_cb(self.shutdown)

        self.log.info(u"Butler initialized")
        self.ready()
Ejemplo n.º 2
0
    def __init__(self):

        ### Option parser
        parser = ArgumentParser()
        parser.add_argument("-i", 
                          action="store_true", 
                          dest="interactive", 
                          default=False, \
                          help="Butler interactive mode (must be used WITH -f).")

        Plugin.__init__(self, name = 'butler', parser = parser)

        ### MQ
        # MQ publisher
        #self._mq_name = "interface-{0}.{1}".format(self._name, self.get_sanitized_hostname())
        self._mq_name = "butler"
        #self.zmq = zmq.Context()
        self.pub = MQPub(self.zmq, self._mq_name)

        # subscribe the MQ for interfaces inputs
        MQAsyncSub.__init__(self, self.zmq, self._name, ['interface.input'])


        ### Configuration elements
        try:
            cfg = Loader('butler')
            config = cfg.load()
            conf = dict(config[1])

            self.lang = conf['lang']
            self.butler_name = conf['name']
            self.butler_sex = conf['sex']
            self.butler_mood = None
            if self.butler_sex not in SEX_ALLOWED:
                self.log.error(u"Exiting : the butler sex configured is not valid : '{0}'. Expecting : {1}".format(self.butler_sex, SEX_ALLOWED))
                self.force_leave()
                return
       
        except:
            self.log.error(u"Exiting : error while reading the configuration file '{0}' : {1}".format(CONFIG_FILE, traceback.format_exc()))
            self.force_leave()
            return
        # user name (default is 'localuser')
        # this is not used for now on Domogik side
        self.user_name = "localuser"

        ### Prepare the brain
        # - validate packages

        # Start the brain :)
        self.brain = RiveScript(utf8=True)

        # set rivescript variables

        # Configure bot variables
        # all must be lower case....
        self.log.info("Configuring name and sex : {0}, {1}".format(self.butler_name.lower(), self.butler_sex.lower()))
        self.brain.set_variable("name", self.butler_name.lower())
        self.brain.set_variable("fullname", self.butler_name.lower())
        self.brain.set_variable("sex", self.butler_sex.lower())

        # set the PYTHONPATH
        sys.path.append(self.get_libraries_directory())

        # load the brain
        self.brain_content = None
        self.learn_content = None
        self.not_understood_content = None
        self.load_all_brain()

        # shortcut to allow the core brain package to reload the brain for learning
        self.brain.reload_butler = self.reload


        # history
        self.history = []

        print(u"*** Welcome in {0} world, your digital assistant! ***".format(self.butler_name))
        print(u"You may type /quit to let {0} have a break".format(self.butler_name))


        ### Interactive mode
        if self.options.interactive:
            self.log.info(u"Launched in interactive mode : running the chat!")
            # TODO : run as a thread
            #self.run_chat()
            thr_run_chat = Thread(None,
                                  self.run_chat,
                                  "run_chat",
                                  (),
                                  {})
            thr_run_chat.start()
        else:
            self.log.info(u"Not launched in interactive mode")
        

        ### TODO
        #self.add_stop_cb(self.shutdown)

        self.log.info(u"Butler initialized")
        self.ready()
Ejemplo n.º 3
0
class Butler(Plugin, MQAsyncSub):
    """ Butler component

        TODO : 
        * /quit /reload commands
        * interact with domogik : commands
    """
    def __init__(self):

        ### Option parser
        parser = ArgumentParser()
        parser.add_argument("-i",
                          action="store_true",
                          dest="interactive",
                          default=False, \
                          help="Butler interactive mode (must be used WITH -f).")

        Plugin.__init__(self, name='butler', parser=parser)

        ### MQ
        # MQ publisher
        #self._mq_name = "interface-{0}.{1}".format(self._name, self.get_sanitized_hostname())
        self._mq_name = "butler"
        #self.zmq = zmq.Context()
        self.pub = MQPub(self.zmq, self._mq_name)

        # subscribe the MQ for interfaces inputs
        MQAsyncSub.__init__(self, self.zmq, self._name, ['interface.input'])

        ### Configuration elements
        try:
            cfg = Loader('butler')
            config = cfg.load()
            conf = dict(config[1])

            self.lang = conf['lang']
            self.butler_name = conf['name']
            self.butler_sex = conf['sex']
            self.butler_mood = None
            if self.butler_sex not in SEX_ALLOWED:
                self.log.error(
                    u"Exiting : the butler sex configured is not valid : '{0}'. Expecting : {1}"
                    .format(self.butler_sex, SEX_ALLOWED))
                self.force_leave()
                return

        except:
            self.log.error(
                u"Exiting : error while reading the configuration file '{0}' : {1}"
                .format(CONFIG_FILE, traceback.format_exc()))
            self.force_leave()
            return
        # user name (default is 'localuser')
        # this is not used for now on Domogik side
        self.user_name = "localuser"

        ### Prepare the brain
        # - validate packages

        # Start the brain :)
        self.brain = RiveScript(utf8=True)

        # set rivescript variables

        # Configure bot variables
        # all must be lower case....
        self.log.info("Configuring name and sex : {0}, {1}".format(
            self.butler_name.lower(), self.butler_sex.lower()))
        self.brain.set_variable("name", self.butler_name.lower())
        self.brain.set_variable("fullname", self.butler_name.lower())
        self.brain.set_variable("sex", self.butler_sex.lower())

        # set the PYTHONPATH
        sys.path.append(self.get_libraries_directory())

        # load the brain
        self.brain_content = None
        self.learn_content = None
        self.not_understood_content = None
        self.load_all_brain()

        # shortcut to allow the core brain package to reload the brain for learning
        self.brain.reload_butler = self.reload

        # history
        self.history = []

        print(u"*** Welcome in {0} world, your digital assistant! ***".format(
            self.butler_name))
        print(u"You may type /quit to let {0} have a break".format(
            self.butler_name))

        ### Interactive mode
        if self.options.interactive:
            self.log.info(u"Launched in interactive mode : running the chat!")
            # TODO : run as a thread
            #self.run_chat()
            thr_run_chat = Thread(None, self.run_chat, "run_chat", (), {})
            thr_run_chat.start()
        else:
            self.log.info(u"Not launched in interactive mode")

        ### TODO
        #self.add_stop_cb(self.shutdown)

        self.log.info(u"Butler initialized")
        self.ready()

    def on_mdp_request(self, msg):
        """ Handle Requests over MQ 
            @param msg : MQ req message
        """
        try:
            ### rivescript files detail
            if msg.get_action() == "butler.scripts.get":
                self.log.info(u"Scripts request : {0}".format(msg))
                self._mdp_reply_butler_scripts(msg)
            ### rivescript files detail
            elif msg.get_action() == "butler.reload.do":
                self.log.info(u"Reload brain request : {0}".format(msg))
                self._mdp_reply_butler_reload(msg)
            ### history
            elif msg.get_action() == "butler.history.get":
                self.log.info(u"Get butler history : {0}".format(msg))
                self._mdp_reply_butler_history(msg)
            ### features
            elif msg.get_action() == "butler.features.get":
                self.log.info(u"Get butler features : {0}".format(msg))
                self._mdp_reply_butler_features(msg)
        except:
            self.log.error(
                u"Error while processing MQ message : '{0}'. Error is : {1}".
                format(msg, traceback.format_exc()))

    def _mdp_reply_butler_scripts(self, message):
        """ Send the raw content for the brain parts over the MQ
        """

        # TODO : handle choice of the client in the req message

        # load not understood queries data
        self.read_not_understood_file()

        msg = MQMessage()
        msg.set_action('butler.scripts.result')
        msg.add_data("learn", self.learn_content)
        msg.add_data("not_understood", self.not_understood_content)
        for client_id in self.brain_content:
            msg.add_data(client_id, self.brain_content[client_id])
        self.reply(msg.get())

    def _mdp_reply_butler_reload(self, message):
        """ Reload the brain 
        """
        msg = MQMessage()
        msg.set_action('butler.reload.result')
        try:
            self.reload()
            msg.add_data(u"status", True)
            msg.add_data(u"reason", "")
        except:
            msg.add_data(u"status", False)
            msg.add_data(
                u"reason", "Error while reloading brain parts : {0}".format(
                    traceback.format_exc()))
        self.reply(msg.get())

    def _mdp_reply_butler_history(self, message):
        """ Butler history
        """
        msg = MQMessage()
        msg.set_action('butler.history.result')
        msg.add_data("history", self.history)
        self.reply(msg.get())

    def _mdp_reply_butler_features(self, message):
        """ Butler features
        """
        msg = MQMessage()
        msg.set_action('butler.features.result')
        msg.add_data("features", self.butler_features)
        self.reply(msg.get())

    def reload(self):
        self.load_all_brain()

    def load_all_brain(self):
        """ Load all the brain parts (included in domogik or in packages)
            and do any other related actions
        """
        try:
            # load the minimal brain
            self.brain_content = {
            }  # the brain raw content for display in the admin (transmitted over MQ)
            self.log.info(u"Load minimal brain : {0}".format(MINIMAL_BRAIN))
            self.brain.load_file(MINIMAL_BRAIN)

            # load packages for the brain
            self.load_brain_parts()

            # sort replies
            self.brain.sort_replies()
        except:
            self.log.error(u"Error while loading brain : {0}".format(
                traceback.format_exc()))

    def load_brain_parts(self):
        """ Load the parts of the brain from /var/lib/domogik/domogik_packages/brain_*
            and also plugin_* because some plugins may need dedicated brain parts :
            - weather forecast
            - anything less generic than a datatype basic usage
        """
        try:
            list = []
            # first load the packages parts
            for a_file in os.listdir(self.get_packages_directory()):
                try:
                    pkg_type, name = a_file.split("_")
                except ValueError:
                    # not a foo_bar file : skip it
                    continue
                #if a_file[0:len(BRAIN_PKG_TYPE)] == BRAIN_PKG_TYPE:
                if pkg_type in BRAIN_PKG_TYPES:
                    client_id = "{0}-{1}.{2}".format(
                        pkg_type,
                        a_file.split("_")[1], self.get_sanitized_hostname())
                    self.brain_content[client_id] = {}
                    rs_dir = os.path.join(self.get_packages_directory(),
                                          a_file, RIVESCRIPT_DIR)
                    if os.path.isdir(rs_dir):
                        self.log.info(u"Brain part found : {0}".format(a_file))
                        #self.log.debug(u"The brain part contains a rivescript folder ({0})".format(RIVESCRIPT_DIR))
                        lang_dir = os.path.join(rs_dir, self.lang)
                        if os.path.isdir(lang_dir):
                            self.log.info(u"- Language found : {0}".format(
                                self.lang))
                            # add the brain part to rivescript
                            self.brain.load_directory(lang_dir)
                            # add the files raw data to brain content (to be sent over MQ to the admin)
                            self.brain_content[client_id][self.lang] = {}
                            for a_rs_file in os.listdir(lang_dir):
                                a_rs_file_path = os.path.join(
                                    lang_dir, a_rs_file)
                                if os.path.isfile(
                                        a_rs_file_path) and a_rs_file[
                                            -len(RIVESCRIPT_EXTENSION
                                                 ):] == RIVESCRIPT_EXTENSION:
                                    try:
                                        import codecs
                                        file = codecs.open(
                                            a_rs_file_path, 'r', 'utf-8')
                                        file_content = file.read()
                                        content = u"{0}".format(file_content)
                                    except:
                                        content = u"Error while reading file '{0}'. Error is : {1}".format(
                                            a_rs_file_path,
                                            traceback.format_exc())
                                        self.log.error(content)
                                    self.brain_content[client_id][
                                        self.lang][a_rs_file] = content

            # and finally, load the learning file
            # this file is generated over the time when the domogik.butler.brain  learn() function is called
            learn_file = LEARN_FILE
            if os.path.isfile(learn_file):
                self.log.info(u"Learn file found : {0}".format(learn_file))

                # add the brain part to rivescript
                self.brain.load_file(learn_file)
                try:
                    import codecs
                    file = codecs.open(learn_file, 'r', 'utf-8')
                    file_content = file.read()
                    file_header = "// File : {0}".format(learn_file)
                    self.learn_content = u"{0}\n\n{1}".format(
                        file_header, file_content)
                except:
                    self.learn_content = u"Error while reading file '{0}'. Error is : {1}".format(
                        learn_file, traceback.format_exc())
                    self.log.error(self.learn_content)
            else:
                self.learn_content = u""
                self.log.info(
                    u"Learn file NOT found : {0}. This is not an error. You just have learn nothing to your butler ;)"
                    .format(learn_file))

            # to finish, find all the tagged features
            # and all the tagged suggestions
            self.get_brain_features_and_suggestions()

        except:
            msg = "Error accessing packages directory : {0}. You should create it".format(
                str(traceback.format_exc()))
            self.log.error(msg)

    def get_brain_features_and_suggestions(self):
        """ Extract brain features and suggestions  from the rivescript files :
            // ##feature## a feature
            + feature trigger
            - feature response

            /* ##suggest##
            ? ...
            @ ...
            */
        """
        self.butler_features = []
        self.butler_suggestions = []
        try:
            self.log.info(
                u"Extract tagged features (##feature##) and suggestions (##suggest##) from the rivescript files"
            )
            for client in self.brain_content:
                for lang in self.brain_content[client]:
                    for fic in self.brain_content[client][lang]:
                        the_suggests = re.findall(
                            SUGGEST_REGEXP,
                            self.brain_content[client][lang][fic])
                        if the_suggests != []:
                            self.butler_suggestions.extend(the_suggests)
                        for line in self.brain_content[client][lang][
                                fic].split("\n"):
                            if re.search(FEATURE_TAG, line):
                                self.butler_features.append(
                                    line.split(FEATURE_TAG)[1])
            self.log.info(u"{0} feature(s) found".format(
                len(self.butler_features)))
            self.log.info(u"{0} suggestion(s) found".format(
                len(self.butler_suggestions)))

            # store in the Rivescript object the features and suggestions to be able to grab them from the core brain package
            self.brain.the_features = '.\n'.join(self.butler_features)
            self.brain.the_suggestions = self.butler_suggestions
        except:
            self.log.error(u"Error while extracting the features : {0}".format(
                traceback.format_exc()))

    def process(self, query):
        """ Process the input query by calling rivescript brain
            @param query : the text query
        """
        try:
            self.log.debug(u"Before transforming query : {0}".format(query))
            self.brain.raw_query = query

            query = clean_input(query)

            self.log.debug(u"After transforming query : {0}".format(query))
            self.brain.query = query

            # process the query
            self.log.debug(
                u"Before calling Rivescript brain for processing : {0} (type={1})"
                .format(query, type(query)))
            reply = self.brain.reply(self.user_name, query)
            self.log.debug(
                u"Processing finished. The reply is : {0}".format(reply))
            return reply
        except:
            self.log.error(
                u"Error while processing query '{0}'. Error is : {1}".format(
                    query, traceback.format_exc()))
            self.log.error(reply)
            self.log.error(type(reply))
            return "Error"

    def shutdown(self):
        """ Shutdown the butler
        """
        pass

    def add_to_history(self, msgid, data):
        self.history.append({"msgid": msgid, "context": data})

    def on_message(self, msgid, content):
        """ When a message is received from the MQ (pub/sub)
        """
        if msgid == "interface.input":
            self.log.info(u"Received message : {0}".format(content))

            ### Get response from the brain
            # TODO : do it in a thread and if this last too long, do :
            # 3s : reply "humm..."
            # 10s : reply "I am checking..."
            # 20s : reply "It takes already 20s for processing, I cancel the request" and kill the thread
            #reply = self.brain.reply(self.user_name, content['text'])

            self.add_to_history("interface.input", content)
            reply = self.process(content['text'])

            ### Prepare response for the MQ
            # All elements that may be added in the request sent over MQ for interface.output
            # * media (irc, audio, sms, ...)
            # * text (from voice recognition)
            # * location (the input element location : this is configured on the input element : kitchen, garden, bedroom, ...)
            # * reply_to (from face recognition)

            #self.context = {"media" : "irc",
            #                "location" : "internet",
            #                "reply_to" : content['identity']
            #               }
            #self.context['text'] = reply

            # fill empty data
            for elt in [
                    'identity', 'media', 'location', 'sex', 'mood', 'reply_to'
            ]:
                if elt not in content:
                    content[elt] = None

            # publish over MQ
            data = {
                "media": content['media'],
                "location": content['location'],
                "sex": self.butler_sex,
                "mood": self.butler_mood,
                "reply_to": content['source'],
                "identity": self.butler_name,
                "text": reply
            }
            self.log.info(u"Send response over MQ : {0}".format(data))
            self.pub.send_event('interface.output', data)
            self.add_to_history("interface.output", data)

    def run_chat(self):
        """ Nestor starts to serve his master over the chat
            the chat is really usefull for debugging

            TODO : allow Nestor to connect over irc on demand for test purpose
        """
        # just wait for 2s to have a cleaner output
        #time.sleep(2)
        # start serving for an entire life
        while True:
            msg = raw_input(u"You > ")

            # first, let python handle some system messages
            if msg == '/quit':
                quit()

            # then, let Nestor do his work!!!
            #reply = self.brain.reply(self.user_name, msg)
            reply = self.process(msg)

            # let Nestor answer in the chat
            print(u"{0} > {1}".format(self.butler_name, reply))

            # let Nestor speak
            #tts = u"espeak -p 40 -s 140 -v mb/mb-fr1 \"{0}\" | mbrola /usr/share/mbrola/fr1/fr1 - -.au | aplay".format(reply)
            #tts = u"espeak -p 40 -s 140 -v mb/mb-fr1 \"{0}\" | mbrola /usr/share/mbrola/fr1 - -.au | aplay".format(reply)
            #subp = Popen(tts, shell=True)
            #pid = subp.pid
            #subp.communicate()

    def read_not_understood_file(self):
        """ Get the content of the non understood queries file
        """
        if os.path.isfile(STAR_FILE):
            self.log.info(
                u"Not understood queries file found : {0}".format(STAR_FILE))

            try:
                import codecs
                file = codecs.open(STAR_FILE, 'r', 'utf-8')
                file_content = file.read()
                file_header = "// File : {0}".format(STAR_FILE)
                self.not_understood_content = u"{0}\n\n{1}".format(
                    file_header, file_content)
            except:
                self.not_understood_content = u"Error while reading file '{0}'. Error is : {1}".format(
                    STAR_FILE, traceback.format_exc())
                self.log.error(self.not_understood_content)
        else:
            self.not_understood_content = u""
            self.log.info(
                u"Not understood queries file NOT found : {0}. This is not an error. Your butler is just awesome (or unused) ;)"
                .format(STAR_FILE))
Ejemplo n.º 4
0
class Butler(Plugin, MQAsyncSub):
    """ Butler component

        TODO : 
        * /quit /reload commands
        * interact with domogik : commands
    """

    def __init__(self):

        ### Option parser
        parser = ArgumentParser()
        parser.add_argument("-i", 
                          action="store_true", 
                          dest="interactive", 
                          default=False, \
                          help="Butler interactive mode (must be used WITH -f).")

        Plugin.__init__(self, name = 'butler', parser = parser)

        ### MQ
        # MQ publisher
        #self._mq_name = "interface-{0}.{1}".format(self._name, self.get_sanitized_hostname())
        self._mq_name = "butler"
        #self.zmq = zmq.Context()
        self.pub = MQPub(self.zmq, self._mq_name)

        # subscribe the MQ for interfaces inputs
        MQAsyncSub.__init__(self, self.zmq, self._name, ['interface.input'])


        ### Configuration elements
        try:
            cfg = Loader('butler')
            config = cfg.load()
            conf = dict(config[1])

            self.lang = conf['lang']
            self.butler_name = conf['name']
            self.butler_sex = conf['sex']
            self.butler_mood = None
            if self.butler_sex not in SEX_ALLOWED:
                self.log.error(u"Exiting : the butler sex configured is not valid : '{0}'. Expecting : {1}".format(self.butler_sex, SEX_ALLOWED))
                self.force_leave()
                return
       
        except:
            self.log.error(u"Exiting : error while reading the configuration file '{0}' : {1}".format(CONFIG_FILE, traceback.format_exc()))
            self.force_leave()
            return
        # user name (default is 'localuser')
        # this is not used for now on Domogik side
        self.user_name = "localuser"

        ### Prepare the brain
        # - validate packages

        # Start the brain :)
        self.brain = RiveScript(utf8=True)

        # set rivescript variables

        # Configure bot variables
        # all must be lower case....
        self.log.info("Configuring name and sex : {0}, {1}".format(self.butler_name.lower(), self.butler_sex.lower()))
        self.brain.set_variable("name", self.butler_name.lower())
        self.brain.set_variable("fullname", self.butler_name.lower())
        self.brain.set_variable("sex", self.butler_sex.lower())

        # set the PYTHONPATH
        sys.path.append(self.get_libraries_directory())

        # load the brain
        self.brain_content = None
        self.learn_content = None
        self.not_understood_content = None
        self.load_all_brain()

        # shortcut to allow the core brain package to reload the brain for learning
        self.brain.reload_butler = self.reload


        # history
        self.history = []

        print(u"*** Welcome in {0} world, your digital assistant! ***".format(self.butler_name))
        print(u"You may type /quit to let {0} have a break".format(self.butler_name))


        ### Interactive mode
        if self.options.interactive:
            self.log.info(u"Launched in interactive mode : running the chat!")
            # TODO : run as a thread
            #self.run_chat()
            thr_run_chat = Thread(None,
                                  self.run_chat,
                                  "run_chat",
                                  (),
                                  {})
            thr_run_chat.start()
        else:
            self.log.info(u"Not launched in interactive mode")
        

        ### TODO
        #self.add_stop_cb(self.shutdown)

        self.log.info(u"Butler initialized")
        self.ready()


    def on_mdp_request(self, msg):
        """ Handle Requests over MQ 
            @param msg : MQ req message
        """
        try:
            ### rivescript files detail
            if msg.get_action() == "butler.scripts.get":
                self.log.info(u"Scripts request : {0}".format(msg))
                self._mdp_reply_butler_scripts(msg)
            ### rivescript files detail
            elif msg.get_action() == "butler.reload.do":
                self.log.info(u"Reload brain request : {0}".format(msg))
                self._mdp_reply_butler_reload(msg)
            ### history
            elif msg.get_action() == "butler.history.get":
                self.log.info(u"Get butler history : {0}".format(msg))
                self._mdp_reply_butler_history(msg)
            ### features
            elif msg.get_action() == "butler.features.get":
                self.log.info(u"Get butler features : {0}".format(msg))
                self._mdp_reply_butler_features(msg)
        except:
            self.log.error(u"Error while processing MQ message : '{0}'. Error is : {1}".format(msg, traceback.format_exc()))
   

    def _mdp_reply_butler_scripts(self, message):
        """ Send the raw content for the brain parts over the MQ
        """

        # TODO : handle choice of the client in the req message

        # load not understood queries data
        self.read_not_understood_file()

        msg = MQMessage()
        msg.set_action('butler.scripts.result')
        msg.add_data("learn", self.learn_content)
        msg.add_data("not_understood", self.not_understood_content)
        for client_id in self.brain_content:
            msg.add_data(client_id, self.brain_content[client_id])
        self.reply(msg.get())


    def _mdp_reply_butler_reload(self, message):
        """ Reload the brain 
        """
        msg = MQMessage()
        msg.set_action('butler.reload.result')
        try:
            self.reload()
            msg.add_data(u"status", True)
            msg.add_data(u"reason", "")
        except:
            msg.add_data(u"status", False)
            msg.add_data(u"reason", "Error while reloading brain parts : {0}".format(traceback.format_exc()))
        self.reply(msg.get())


    def _mdp_reply_butler_history(self, message):
        """ Butler history
        """
        msg = MQMessage()
        msg.set_action('butler.history.result')
        msg.add_data("history", self.history)
        self.reply(msg.get())


    def _mdp_reply_butler_features(self, message):
        """ Butler features
        """
        msg = MQMessage()
        msg.set_action('butler.features.result')
        msg.add_data("features", self.butler_features)
        self.reply(msg.get())


    def reload(self):
        self.load_all_brain()


    def load_all_brain(self):
        """ Load all the brain parts (included in domogik or in packages)
            and do any other related actions
        """
        try:
            # load the minimal brain
            self.brain_content = {}   # the brain raw content for display in the admin (transmitted over MQ)
            self.log.info(u"Load minimal brain : {0}".format(MINIMAL_BRAIN))
            self.brain.load_file(MINIMAL_BRAIN)

            # load packages for the brain
            self.load_brain_parts()

            # sort replies
            self.brain.sort_replies()
        except:
            self.log.error(u"Error while loading brain : {0}".format(traceback.format_exc()))

    def load_brain_parts(self):
        """ Load the parts of the brain from /var/lib/domogik/domogik_packages/brain_*
            and also plugin_* because some plugins may need dedicated brain parts :
            - weather forecast
            - anything less generic than a datatype basic usage
        """
        try:
            list = []
            # first load the packages parts
            for a_file in os.listdir(self.get_packages_directory()):
                try:
                    pkg_type, name = a_file.split("_")
                except ValueError:
                    # not a foo_bar file : skip it
                    continue
                #if a_file[0:len(BRAIN_PKG_TYPE)] == BRAIN_PKG_TYPE:
                if pkg_type in BRAIN_PKG_TYPES:
                    client_id = "{0}-{1}.{2}".format(pkg_type, a_file.split("_")[1], self.get_sanitized_hostname())
                    self.brain_content[client_id] = {}
                    rs_dir = os.path.join(self.get_packages_directory(), a_file, RIVESCRIPT_DIR)
                    if os.path.isdir(rs_dir):
                        self.log.info(u"Brain part found : {0}".format(a_file))
                        #self.log.debug(u"The brain part contains a rivescript folder ({0})".format(RIVESCRIPT_DIR))
                        lang_dir = os.path.join(rs_dir, self.lang)
                        if os.path.isdir(lang_dir):
                            self.log.info(u"- Language found : {0}".format(self.lang))
                            # add the brain part to rivescript
                            self.brain.load_directory(lang_dir)
                            # add the files raw data to brain content (to be sent over MQ to the admin)
                            self.brain_content[client_id][self.lang] = {}
                            for a_rs_file in os.listdir(lang_dir):
                                a_rs_file_path = os.path.join(lang_dir, a_rs_file)
                                if os.path.isfile(a_rs_file_path) and a_rs_file[-len(RIVESCRIPT_EXTENSION):] == RIVESCRIPT_EXTENSION:
                                    try:
                                        import codecs
                                        file = codecs.open(a_rs_file_path, 'r', 'utf-8')
                                        file_content = file.read()
                                        content = u"{0}".format(file_content)
                                    except:
                                        content = u"Error while reading file '{0}'. Error is : {1}".format(a_rs_file_path, traceback.format_exc())
                                        self.log.error(content)
                                    self.brain_content[client_id][self.lang][a_rs_file] = content

            # and finally, load the learning file
            # this file is generated over the time when the domogik.butler.brain  learn() function is called
            learn_file = LEARN_FILE
            if os.path.isfile(learn_file):
                self.log.info(u"Learn file found : {0}".format(learn_file))

                # add the brain part to rivescript
                self.brain.load_file(learn_file)
                try:
                    import codecs
                    file = codecs.open(learn_file, 'r', 'utf-8')
                    file_content = file.read()
                    file_header = "// File : {0}".format(learn_file)
                    self.learn_content = u"{0}\n\n{1}".format(file_header, file_content)
                except:
                    self.learn_content = u"Error while reading file '{0}'. Error is : {1}".format(learn_file, traceback.format_exc())
                    self.log.error(self.learn_content)
            else:
                self.learn_content = u""
                self.log.info(u"Learn file NOT found : {0}. This is not an error. You just have learn nothing to your butler ;)".format(learn_file))
            
                              
            # to finish, find all the tagged features
            # and all the tagged suggestions
            self.get_brain_features_and_suggestions()

        except:
            msg = "Error accessing packages directory : {0}. You should create it".format(str(traceback.format_exc()))
            self.log.error(msg)

    def get_brain_features_and_suggestions(self):
        """ Extract brain features and suggestions  from the rivescript files :
            // ##feature## a feature
            + feature trigger
            - feature response

            /* ##suggest##
            ? ...
            @ ...
            */
        """
        self.butler_features = []
        self.butler_suggestions = []
        try:
            self.log.info(u"Extract tagged features (##feature##) and suggestions (##suggest##) from the rivescript files")
            for client in self.brain_content:
                for lang in self.brain_content[client]:
                    for fic in self.brain_content[client][lang]:
                        the_suggests = re.findall(SUGGEST_REGEXP, self.brain_content[client][lang][fic])
                        if the_suggests != []:
                            self.butler_suggestions.extend(the_suggests)
                        for line in self.brain_content[client][lang][fic].split("\n"):
                            if re.search(FEATURE_TAG, line):
                                self.butler_features.append(line.split(FEATURE_TAG)[1])
            self.log.info(u"{0} feature(s) found".format(len(self.butler_features)))
            self.log.info(u"{0} suggestion(s) found".format(len(self.butler_suggestions)))

            # store in the Rivescript object the features and suggestions to be able to grab them from the core brain package
            self.brain.the_features = '.\n'.join(self.butler_features)
            self.brain.the_suggestions = self.butler_suggestions
        except:
            self.log.error(u"Error while extracting the features : {0}".format(traceback.format_exc()))
                 


    def process(self, query):
        """ Process the input query by calling rivescript brain
            @param query : the text query
        """
        try:
            self.log.debug(u"Before transforming query : {0}".format(query))
            self.brain.raw_query = query

            query = clean_input(query)

            self.log.debug(u"After transforming query : {0}".format(query))
            self.brain.query = query

            # process the query
            self.log.debug(u"Before calling Rivescript brain for processing : {0} (type={1})".format(query, type(query)))
            reply = self.brain.reply(self.user_name, query)
            self.log.debug(u"Processing finished. The reply is : {0}".format(reply))
            return reply
        except:
            self.log.error(u"Error while processing query '{0}'. Error is : {1}".format(query, traceback.format_exc()))
            self.log.error(reply)
            self.log.error(type(reply))
            return "Error"


    def shutdown(self):
        """ Shutdown the butler
        """
        pass

    def add_to_history(self, msgid, data):
        self.history.append({"msgid" : msgid, "context" : data})

    def on_message(self, msgid, content):
        """ When a message is received from the MQ (pub/sub)
        """
        if msgid == "interface.input":
            self.log.info(u"Received message : {0}".format(content))

            ### Get response from the brain
            # TODO : do it in a thread and if this last too long, do :
            # 3s : reply "humm..."
            # 10s : reply "I am checking..."
            # 20s : reply "It takes already 20s for processing, I cancel the request" and kill the thread
            #reply = self.brain.reply(self.user_name, content['text'])

            self.add_to_history("interface.input", content)
            reply = self.process(content['text'])

            ### Prepare response for the MQ
            # All elements that may be added in the request sent over MQ for interface.output
            # * media (irc, audio, sms, ...)
            # * text (from voice recognition)
            # * location (the input element location : this is configured on the input element : kitchen, garden, bedroom, ...)
            # * reply_to (from face recognition)
            
            #self.context = {"media" : "irc",
            #                "location" : "internet",
            #                "reply_to" : content['identity']
            #               }
            #self.context['text'] = reply

            # fill empty data
            for elt in ['identity', 'media', 'location', 'sex', 'mood', 'reply_to']:
                if elt not in content:
                    content[elt] = None

            # publish over MQ
            data =              {"media" : content['media'],
                                 "location" : content['location'],
                                 "sex" : self.butler_sex,
                                 "mood" : self.butler_mood,
                                 "reply_to" : content['source'],
                                 "identity" : self.butler_name,
                                 "text" : reply}
            self.log.info(u"Send response over MQ : {0}".format(data))
            self.pub.send_event('interface.output',
                                data)
            self.add_to_history("interface.output", data)


    def run_chat(self):
        """ Nestor starts to serve his master over the chat
            the chat is really usefull for debugging

            TODO : allow Nestor to connect over irc on demand for test purpose
        """
        # just wait for 2s to have a cleaner output
        #time.sleep(2)
        # start serving for an entire life
        while True:
            msg = raw_input(u"You > ")

            # first, let python handle some system messages
            if msg == '/quit':
                quit()

            # then, let Nestor do his work!!!
            #reply = self.brain.reply(self.user_name, msg)
            reply = self.process(msg)

            # let Nestor answer in the chat
            print(u"{0} > {1}".format(self.butler_name, reply))

            # let Nestor speak
            #tts = u"espeak -p 40 -s 140 -v mb/mb-fr1 \"{0}\" | mbrola /usr/share/mbrola/fr1/fr1 - -.au | aplay".format(reply)
            #tts = u"espeak -p 40 -s 140 -v mb/mb-fr1 \"{0}\" | mbrola /usr/share/mbrola/fr1 - -.au | aplay".format(reply)
            #subp = Popen(tts, shell=True)
            #pid = subp.pid
            #subp.communicate()

    def read_not_understood_file(self):
        """ Get the content of the non understood queries file
        """
        if os.path.isfile(STAR_FILE):
            self.log.info(u"Not understood queries file found : {0}".format(STAR_FILE))

            try:
                import codecs
                file = codecs.open(STAR_FILE, 'r', 'utf-8')
                file_content = file.read()
                file_header = "// File : {0}".format(STAR_FILE)
                self.not_understood_content = u"{0}\n\n{1}".format(file_header, file_content)
            except:
                self.not_understood_content = u"Error while reading file '{0}'. Error is : {1}".format(STAR_FILE, traceback.format_exc())
                self.log.error(self.not_understood_content)
        else:
            self.not_understood_content = u""
            self.log.info(u"Not understood queries file NOT found : {0}. This is not an error. Your butler is just awesome (or unused) ;)".format(STAR_FILE))