Example #1
0
def main(arglist):
    logging.basicConfig(
        format='%(funcName)s:%(lineno)d %(levelname)s %(message)s',
        level=logging.INFO)
    parser = argparse.ArgumentParser()
    parser.add_argument('--mode',
                        dest='mode',
                        default=RUN_MODE.UPLOAD,
                        help='The mdoe to run Good drive uploader',
                        choices=RUN_MODE.CHOICES)
    parser.add_argument('--upload',
                        dest='upload',
                        default='',
                        help='Upload a file or directory')
    args = parser.parse_args(arglist[1:])

    if args.mode == RUN_MODE.UPLOAD:
        if not args.upload:
            raise ValueError('params --upload required')
        GoogleDriveUpload.upload_in_shared(args.upload)

    elif args.mode == RUN_MODE.LISTENER:
        PORT = 4242
        IP = '127.0.0.1'
        uploader = GoogleDriveUpload()
        listener = Listener(IP, PORT, uploader)
        listener.listen()
Example #2
0
def main():
    """
	main function
	Creates the main Listener and calls listen()
	Catches the last exception and exit upon that
	:return: Program returned value
	"""
    l = Listener()
    try:
        l.listen()
    except Exception as e:
        print "Server main() exception: {}".format(e)
        sys.exit()
Example #3
0
    def listen(self):
        print("Starting listen")
        while self.RUN:
            listener = Listener()
            listener.listen()

            if listener.last_key == "home":
                if self.TOGGLE:
                    print("Toggling OFF")
                    self.TOGGLE = False
                else:
                    print("Toggling ON")
                    self.TOGGLE = True

            if listener.last_key == "end":
                print("Quitting")
                self.RUN = False
Example #4
0
class Channel:
    def __init__(self):
        self.s = None
        self.l = Listener(self)
        global c
        c = self

    def connect(self):
        self.s = socket.socket()
        self.s.connect((cfg.HOST, cfg.PORT))
        self.s.send("PASS {}\r\n".format(cfg.PASS).encode())
        self.s.send("NICK {}\r\n".format(cfg.NAME).encode())
        self.s.send("JOIN #{}\r\n".format(cfg.CHAN).encode())

        connected = False
        while not connected:
            response = self.s.recv(1024).decode("utf-8")
            print(response)
            if "End of /NAMES list" in response:
                connected = True
        print("Connected to #" + cfg.CHAN)

    def chat(self, msg):
        global last_chat
        if self.can_chat():
            self.s.send("PRIVMSG #{} :{}\r\n".format(cfg.CHAN, msg).encode())
            last_chat = time.time()
        elif cfg.QUEUE:
            print("message added to queue")
            queue.append(msg)
        else:
            print("message discarded")

    def listen(self):
        self.l.listen()

    def do_queue(self):
        if cfg.QUEUE and len(queue) > 0:
            if self.can_chat():
                msg = queue.popleft()
                self.chat(msg)
                print("message sent from queue")

    def can_chat(self):
        return (time.time() - last_chat) > cfg.DELAY
Example #5
0
class Hal:
    """
    Hal is our speech assistant and he starts to record spoken words after someone said "Hello Hal".
    And the speech assistant "Hal" reacts with an answer.
    """
    def __init__(self):
        self.listener = Listener()
        self.spoken_words = ""
        self.interpreter = Interpreter()

    def activate(self):
        print("mock: Hal is now activated and listening")
        print("Hello Dave what can I do for you?")
        self.main_menu()

    def main_menu(self):
        while True:
            utilities.clear_screen()
            print("*" * 80)
            print("Hal main menu")
            print("*" * 80)
            print("\t press 1 to talk to Hal")
            print("\t press 2 to turn off Hal")
            inp = input("Please type 1 or 2 and press enter:")
            if inp == "1":
                self.start_listening()
            elif inp == "2":
                sys.exit()
            else:
                input(
                    "Wrong command please press enter to continue and repeat")

    def start_listening(self):
        self.spoken_words = self.listener.listen()
        self.interpreter.execute(self.spoken_words)
        self.spoken_words = self.listener.listen()

    @staticmethod
    def error(message=''):
        print(message)
        input('Invalid input. Please press enter to continue. ')
Example #6
0
class Server(object):
    def __init__(self, host: str = "0.0.0.0", port: int = 9494):
        self._processor = Processor()
        self._response_queue = ResponseQueue()
        self._listener = Listener(host, port)

        self._init()

    def _init(self):
        self._listener.add_subscriber(self._processor.enqueue)
        self._listener.add_subscriber(log_request)
        self._processor.add_subscriber(self._response_queue.enqueue)

    def start(self):
        self._processor.start()
        self._response_queue.start()
        self._listener.listen()

    def web(self, action: Callable):
        action_name = action.__name__
        self._processor.add_action(action_name, action)
Example #7
0
class Bot:
    """
    A class that represents the bot conducting the dialogue (SDS)

    Attributes
    ----------
    _name: str
        the name of the bot
    _speaker: Speaker
        speaker object used for text to speech
    _listener: Listener
        listener object used for speech to perform Automatic Speech Recognition
    _prompt: str
        colored name of the bot to appear in the terminal
    _verbose: bool
        whether the bot should print info about the command it receives
        (dependency tree, lemmas info)
    _silent: bool
        whether the bot should only print replies (no text to speech)
    _frame_stack: list
        stack where the bot holds the frames it still has not finished to process
    _current_frame: Frame
        current frame the bot is processing
    _is_over: bool
        whether the interaction is over
    """
    def __init__(self,
                 name,
                 color,
                 verbose=True,
                 silent=False,
                 menu_path=None):
        """
        Constructor
        :param name: the bot's name it will use in the dialogues
        :param color: the color the bot will use in the prompt
        :param verbose: whether the bot should print info about
        the command it receives (dependency tree, lemmas info)
        :param silent: if true, then only use print (no text to speech)
        :param menu_path: the path of the stored menu
        """

        self._name = name
        self._speaker = Speaker(rate=150, volume=1)
        self._listener = Listener(mic_index=0)
        self._prompt = colored(f'{self._name}: ', color)
        self._verbose = verbose
        self._silent = silent

        self._frame_stack = []
        self._current_frame = None

        self._is_over = False

        if menu_path is not None:
            self._load_menu(menu_path)
        else:
            self._menu = {"entries": []}

        # when finished setup, welcome user
        self._say(self._welcome())

    def _say(self, sentence):
        """
        Says the given sentence through the bot speaker object
        :param sentence: sentence
        :return: None
        """
        print(f"{self._prompt} {sentence}")
        if not self._silent:
            self._speaker.speak(sentence)

    def listen(self):
        """
        Tries to listen for commands.
        Loops until the sentence in understood properly or there is a connection error
        :return: transcribed sentence from voice
        """
        if self._verbose:
            print(f'{self._prompt} * listening *')
        res = self._listen()
        while not res["success"]:
            err = res["error"]
            if isinstance(err, sr.UnknownValueError):
                self._say(
                    "Sorry, I did not hear that, can you say that again?")
            elif isinstance(err, sr.RequestError):
                self._say("No connection with the server available")
                return None

            res = self._listen()

        return res["sentence"]

    def _listen(self):
        """
        A simple proxy to access bot own listener object and obtain
        the transcription of the voice command issued by the user
        :return: a response object (see Listener docs)
        """
        response = self._listener.listen()
        return response

    def process(self, command):
        """
        Processes the given command
        :param command: command
        :return: a proper reply (None if interaction is over)
        """

        # obtain spacy syntax dependency tree
        parsed = syntax_analysis(command)

        # if prompted to load last stored menu, or saved current one, do so
        if contains_text(parsed, "save"):
            self._save_menu()
            self._say("Menu saved")
            return
        if contains_text(parsed, "load"):
            self._load_menu()
            self._say("Menu loaded")
            return

        # print info if required
        if self._verbose:
            root = parsed.root.text.lower().strip()
            self._say(f"The root of the sentence is \'{root}\'")
            print(f"\n{'=' * 5} DEPENDENCIES OF SENTENCE {'=' * 5}")
            print_dependencies(parsed)
            print(f"\n{'=' * 5} TOKENS OF SENTENCE {'=' * 5}")
            print_tokens_info(parsed)

        # determine frame based on parsed command
        frame = self._determine_frame(parsed)

        # change current frame if necessary, storing old one
        if self._current_frame is None:
            self._current_frame = frame
        elif frame is not None and type(frame) != type(self._current_frame):
            self._frame_stack.insert(0, self._current_frame)
            # self._say("Ok we will come back to that later")
            self._current_frame = frame

        # obtain reply by handling parsed command based on the current frame
        if isinstance(self._current_frame, EndFrame):
            self._say(self._goodbye())
            self._current_frame = None
            self._frame_stack = []
            return
        elif isinstance(self._current_frame, AddInfoFrame):
            reply = self._handle_add_info_frame(parsed)
        elif isinstance(self._current_frame, AskInfoFrame):
            reply = self._handle_ask_info_frame(parsed)
        elif isinstance(self._current_frame, OrderFrame):
            reply = self._handle_order_frame(parsed)
        else:
            # if current frame is still None, could not determine user intention
            reply = "Sorry, I did not understand that, can you say that again?"

        self._say(reply)

        # if frame is not over yet, save current reply for later use
        if self._current_frame is not None:
            # self._current_frame.set_last_sentence(reply)
            self._set_last_sentence()

        # if older frame stored, restore it
        # and announce that bot is going back to older frame
        if self._current_frame is None and len(self._frame_stack) > 0:
            self._current_frame = self._frame_stack.pop(0)
            if isinstance(self._current_frame, AddInfoFrame):
                self._say("Ok now back to your statement")
            elif isinstance(self._current_frame, AskInfoFrame):
                self._say("Ok now back to your question")
            elif isinstance(self._current_frame, OrderFrame):
                self._say("Ok now back to your order")

            if self._current_frame.get_last_sentence() is not None:
                self._say(self._current_frame.get_last_sentence())

    def _determine_frame(self, parsed):
        """
        Determines the user intention based upon the parsed command and returns
        the appropriate frame to handle it
        :param parsed: parsed command (spacy tree)
        :return: appropriate frame
        """

        # if command is a question, then user is asking info
        if is_question(parsed):
            return AskInfoFrame()

        # otherwise, count the number of frame triggers for each frame
        triggers_counts = self._count_frame_triggers(parsed)

        # could not determine frame
        if all([v == 0 for v in triggers_counts.values()]):
            return None

        # return the frame with the majority of triggers
        # MIGHT NOT BE THE BEST METHOD
        frame_name = max(triggers_counts, key=triggers_counts.get)

        if frame_name == "EndFrame":
            return EndFrame()
        elif frame_name == "OrderFrame":
            return OrderFrame()
        elif frame_name == "AddInfoFrame":
            return AddInfoFrame()
        elif frame_name == "AskInfoFrame":
            return AskInfoFrame()

    def _count_frame_triggers(self, parsed):
        """
        Counts the number of keywords for each frame triggered by the given
        parsed command
        :param parsed: parsed command
        :return: a dictionary {frame_name: triggers_count}
        """

        # setup return object
        res = {
            "EndFrame": 0,
            "OrderFrame": 0,
            "AddInfoFrame": 0,
            "AskInfoFrame": 0
        }

        # visit tree breadth-first, adding up the triggers along the way
        nodes = [parsed.root]
        visited = []
        while nodes:
            node = nodes.pop(0)
            visited.append(node)
            if EndFrame.is_trigger(node.lemma_, node.dep_):
                res["EndFrame"] += 1
            if OrderFrame.is_trigger(node.lemma_, node.dep_):
                res["OrderFrame"] += 1
            if AddInfoFrame.is_trigger(node.lemma_, node.dep_):
                res["AddInfoFrame"] += 1
            if AskInfoFrame.is_trigger(node.lemma_, node.dep_):
                res["AskInfoFrame"] += 1

            for child in node.children:
                if not child in visited:
                    nodes.append(child)

        return res

    def _add_menu_entry(self, name, course=None):
        """
        Adds an entry to the bot's menu to choose from
        :param name: name of the entry (e.g. hamburger)
        :param course: name of the course (optional, e.g. main course)
        :return: None
        """

        # consistency checks
        if self._get_menu_entry(name) is not None:
            raise EntryAlreadyOnMenu()

        if course is not None and course not in courses_names:
            raise CourseNotValid()

        self._menu["entries"].append({"name": name, "course": course})

    def _update_menu_entry(self, name, course=None):
        """
        Updates a menu entry (mainly adding info about course, but can be extended
        later on if needed)
        :param name: entry name
        :param course: course of the entry (e.g. drink)
        :return: None
        """

        # consistency checks
        if course is not None and course not in courses_names:
            raise CourseNotValid()

        entry = self._get_menu_entry(name)

        if entry is None:
            raise EntryNotOnMenu()

        if entry["course"] is not None:
            raise EntryAttributeAlreadySet()

        # update entry
        for i, entry in enumerate(self._menu["entries"]):
            if entry["name"] == name:
                self._menu["entries"][i].update({
                    "name": name,
                    "course": course
                })

    def _get_menu_entry(self, name):
        """
        Searches the given entry in the bot menu
        :param name: entry name
        :return: entry, None if absent
        """
        for i, entry in enumerate(self._menu["entries"]):
            if entry["name"] == name:
                return self._menu["entries"][i]

        return None

    def _handle_add_info_frame(self, parsed):
        """
        Handles the current AddInfoFrame
        :param parsed: the parsed command
        :return: consistent reply
        """

        # consistency check
        assert isinstance(self._current_frame, AddInfoFrame)

        # fill current frame slots based on the parsed command
        reply = ""
        self._fill_add_info_frame_slots(parsed)

        # based on the frame slots, handle command
        if self._current_frame.get_slot("subj") == "menu":
            # add entry to menu
            try:
                entry = self._current_frame.get_slot("obj")
                self._add_menu_entry(entry)
                if self._current_frame.get_slot("info") is None:
                    reply = "Ok. What course is it?"
                    self._current_frame.set_waiting_answer(True)
                else:
                    course = self._current_frame.get_slot("info")
                    self._update_menu_entry(entry, course)
                    reply = "Ok"
                    self._current_frame = None
            except EntryAlreadyOnMenu:
                reply = "This entry is already in the menu"
                self._current_frame = None
        elif self._current_frame.get_slot("subj") == "course":
            # add info about course
            entry = self._current_frame.get_slot("obj")
            course = self._current_frame.get_slot("info")
            try:
                self._update_menu_entry(entry, course)
                reply = "Ok"
                self._current_frame = None
            except EntryAttributeAlreadySet:
                # TODO: add option to modify entry
                reply = "{} is already a {}".format(
                    entry,
                    self._get_menu_entry(entry)["course"])
            except EntryNotOnMenu:
                # TODO: add option to add entry
                reply = "I am sorry, {} is not on the menu".format(entry)
            except CourseNotValid:
                reply = "I am sorry, {} is not a course".format(course)

        return reply

    def _fill_add_info_frame_slots(self, parsed):
        """
        Fills slots of the current AddInfoFrame based on the information
        contained in the given parsed command
        :param parsed: parsed command
        :return: None
        """
        root_lemma = parsed.root.lemma_
        pobj_lemma = obtain_lemma(find_dep(parsed, "pobj"))

        if root_lemma == "add" or pobj_lemma == "menu":
            # user wants to add entry to menu
            # TODO: allow to add entry and specify course at the same time
            self._current_frame.fill_slot("subj", "menu")
            entry = obtain_text(find_dep(parsed, "dobj"))
            self._current_frame.fill_slot("obj", entry)
            if pobj_lemma in courses_names:
                # user has also specified course
                self._current_frame.fill_slot("info", pobj_lemma)

        elif root_lemma == "is":
            # user wants to add info about course
            self._current_frame.fill_slot("subj", "course")
            entry = obtain_text(find_dep(parsed, "nsubj"))
            self._current_frame.fill_slot("obj", entry)
            course = obtain_lemma(find_dep(parsed, "attr"))
            self._current_frame.fill_slot("info", course)
        elif self._current_frame.is_waiting_answer():
            # user has responded to a previous question from the bot
            # (up to now, that might be only to add info about course of added entry)
            root_lemma = obtain_lemma(find_dep(parsed, "ROOT"))
            attr = obtain_lemma(find_dep(parsed, "attr"))
            course = root_lemma if root_lemma in courses_names else attr
            self._current_frame.fill_slot("subj", "course")
            self._current_frame.fill_slot("info", course)

    def _handle_ask_info_frame(self, parsed):
        """
        Handles the current AskInfoFrame
        :param parsed: the parsed command
        :return: consistent reply
        """
        # consistency check
        assert isinstance(self._current_frame, AskInfoFrame)

        # if nothing to ask about...
        if len(self._menu["entries"]) == 0:
            reply = "I am sorry, there is nothing on menu today. " \
                    "Try and add something or load the stored menu first."
            self._current_frame = None
            return reply

        # perform slot filling of current frame
        self._fill_ask_info_frame_slots(parsed)

        subj = self._current_frame.get_slot("subj")
        if subj == "menu":
            # if asked to see the menu
            # tell menu (entry, course) for each entry in menu
            reply = "We have:"
            for course in courses_names:
                if len([
                        entry for entry in self._menu["entries"]
                        if entry["course"] == course
                ]) == 0:
                    continue

                for entry in self._menu["entries"]:
                    if entry["course"] == course:
                        reply = f"{reply} {entry['name']},"

                reply = reply[:-1]
                reply = f"{reply} for {course};"

            reply = reply[:-1]
        else:
            # if asked about a particular course
            # tell menu (entry, course) for each entry in menu if course == obj
            obj = self._current_frame.get_slot("obj")
            if obj in courses_names:
                reply = ""
                for entry in self._menu["entries"]:
                    if entry["course"] == obj:
                        reply = f"{reply} {entry['name']},"

                if len(reply) == "":
                    reply = f"I'm sorry, we don't have anything for {obj}"
                else:
                    reply = f"We have{reply[:-1]}"

            else:
                # the slot filling went wrong
                reply = "Sorry, I am not sure I understood your question"

        self._current_frame = None
        return reply

    def _fill_ask_info_frame_slots(self, parsed):
        """
        Fills slots of the current AskInfoFrame based on the information
        contained in the given parsed command
        :param parsed: parsed command
        :return: None
        """

        obj_lemma = obtain_lemma(find_dep(parsed, "dobj"))
        pobj_lemma = obtain_lemma(find_dep(parsed, "pobj"))

        if (obj_lemma is not None and obj_lemma == "menu") or \
                (pobj_lemma is not None and pobj_lemma == "menu"):
            # user has asked to know the menu
            self._current_frame.fill_slot("subj", "menu")
        else:
            # user has asked to know a course in particular
            self._current_frame.fill_slot("subj", "course")

            # to handle different possible phrasings of the question
            if obj_lemma is not None and obj_lemma in courses_names:
                self._current_frame.fill_slot("obj", obj_lemma)
            elif pobj_lemma is not None and pobj_lemma in courses_names:
                self._current_frame.fill_slot("obj", pobj_lemma)

    def _handle_order_frame(self, parsed):
        """
        Handles the current OrderFrame
        :param parsed: the parsed command
        :return: consistent reply
        """

        # consistency check
        assert isinstance(self._current_frame, OrderFrame)

        # if nothing to order...
        if len(self._menu["entries"]) == 0:
            reply = "I am sorry, there is nothing on menu today. " \
                    "Try and add something or load the stored menu first."
            self._current_frame = None
            return reply

        # try to perform slot filling
        try:
            self._fill_order_frame_slots(parsed)
        except EntryNotOnMenu:
            reply = "I am sorry, that is not on the menu"
            return reply

        # if first interaction and user did not specified anything
        if len(self._current_frame.filled_slots()) == 0:
            # if user asked for order recap
            if self._current_frame.get_asked_recap():
                reply = "You did not order anything yet, sir. " \
                        "I am ready to take your order"
            else:
                # user just said something like "i am ready to order"
                reply = "I am ready to take your order"
        else:
            # ongoing interaction
            if self._current_frame.get_asked_recap():
                reply = self._recap_order()
                self._current_frame.set_asked_recap(False)
                reply = f"{reply}. Would you like to add something else?"
                self._current_frame.set_waiting_confirmation(True)
            elif self._current_frame.is_waiting_confirmation() and \
                self._current_frame.get_user_answer() == "yes":
                # user said he wants something else
                reply = "Ok, please tell me"
                self._current_frame.set_waiting_confirmation(False)
            elif len(self._current_frame.unfilled_slots()) == 0 or \
                    (self._current_frame.is_waiting_confirmation() and
                    self._current_frame.get_user_answer() == "no"):
                # user has made a full order or said he does not want anything else
                reply = "Ok. Your order is complete. It will come right away. Enjoy!"
                self._current_frame.set_waiting_confirmation(False)
                self._current_frame = None
            else:
                # user has added an entry to the order
                reply = "Ok. Do you want anything else?"
                self._current_frame.set_waiting_confirmation(True)

        return reply

    def _fill_order_frame_slots(self, parsed):
        """
        Fills slots of the current OrderInfoFrame based on the information
        contained in the given parsed command
        :param parsed: parsed command
        :return: None
        """

        # get needed sentence parts to determine user intention
        root_lemma = parsed.root.lemma_
        xcomp_lemma = obtain_lemma(find_dep(parsed, "xcomp"))
        dobj_lemma = obtain_lemma(find_dep(parsed, "dobj"))
        dobj_text = obtain_text(find_dep(parsed,
                                         "dobj"))  # need text for entry names
        advmod_lemma = obtain_lemma(find_dep(parsed, "advmod"))
        intj_lemma = obtain_lemma(find_dep(parsed, "intj"))
        det_lemma = obtain_lemma(find_dep(parsed, "det"))

        if self._current_frame.is_waiting_confirmation():
            # if bot is waiting for a binary answer ("do you want anything else?")
            if (root_lemma == "no" or intj_lemma == "no" or det_lemma == "no") \
                    and \
                (root_lemma != "have" and root_lemma != "like" and root_lemma != "take"):
                # user does not want anything else
                self._current_frame.set_user_answer("no")
                return
            elif (root_lemma == "yes" or intj_lemma == "yes") \
                    and \
                (not OrderFrame.is_trigger(root_lemma, "ROOT")):
                # user wishes to keep on with his order
                self._current_frame.set_user_answer("yes")
                return
            else:
                # user did not give a straight answer
                self._current_frame.set_user_answer(None)

        elif xcomp_lemma is None and dobj_lemma is None:
            # user said gibberish (as far as the bot knows...)
            # best thing is to just say 'that is not on the menu'
            raise EntryNotOnMenu

        if dobj_lemma == "order" and advmod_lemma == "so far":
            # user has asked to recap his order so far
            self._current_frame.set_asked_recap(True)
            return

        if len(self._current_frame.filled_slots()) == 0 and \
                xcomp_lemma == "order" and dobj_text is None:
            # user has just stated he is ready to order,
            # but has not ordered anything yet
            return

        if dobj_text is not None:
            # user has made an order for a menu entry,
            # add that to the current order if entry is on menu
            entry = self._get_menu_entry(dobj_text)
            if entry is None:
                raise EntryNotOnMenu()

            self._current_frame.fill_slot(entry["course"], entry["name"])

    def _recap_order(self):
        """
        Recaps the order made by the user so far
        :return: reply with the recap of the order
        """

        # consistency check
        assert isinstance(self._current_frame, OrderFrame)
        reply = "You ordered:"

        for course in courses_names:
            order_entry = self._current_frame.get_slot(course)
            if order_entry is not None:
                reply = f"{reply} {order_entry} for {course},"

        reply = f"{reply[:-1]}"

        return reply

    def _set_last_sentence(self):
        if isinstance(self._current_frame, AskInfoFrame):
            return
        elif isinstance(self._current_frame, AddInfoFrame):
            return
        elif isinstance(self._current_frame, EndFrame):
            return
        elif isinstance(self._current_frame, OrderFrame):
            if len(self._current_frame.filled_slots()) == 0:
                self._current_frame.set_last_sentence(
                    "I am ready to take your order")
            else:
                self._current_frame.set_last_sentence(
                    "Would you like anything else?")
                self._current_frame.set_waiting_confirmation(True)

    def _welcome(self):
        """
        Welcom message upon bot startup
        :return: welcome message
        """
        self._is_over = False
        return "Hello, how may I help?"

    def _goodbye(self):
        """
        Goodbye message upon bot termination
        :return: goodby message
        """
        self._is_over = True
        return "Here's your bill. Goodbye!"

    def is_over(self):
        return self._is_over

    def _load_menu(self, path=None):
        """
        Loads a menu
        :param path: menu path
        :return: None
        """
        if path is None:
            menus = os.listdir("./menu")
            menus.sort()
            path = menus[-1]
        with open(f"./menu/{path}") as file:
            self._menu = json.load(file)

    def _save_menu(self):
        """
        Saves the current menu on disk as a json file, at path timestamp_menu.json
        :return: None
        """
        with open(
                f"./menu/{datetime.now().strftime('%Y%m%d-%H%M%S')}_menu.json",
                'x') as f:
            json.dump(self._menu, f)
Example #8
0
def main():
    """
    Accept user voice input and translate it into desired language until
    user says exit or turn off.
    """

    os.system("flite -voice rms -t 'Booting up computer now'")

    # See if user has an API key to Google Cloud. If not, just set API_KEY
    # to None. This tells the program to use Google Web API for speech-to-
    # text which presumably logs user voice. Google Cloud API requires an
    # account and credit card but gives the user the option to opt out of
    # logging.
    try:
        with open("./.api_key") as f:
            API_KEY = validate_api(f.read())

    except FileNotFoundError as e:
        API_KEY = ""

    # Have LEDs pulse when program is starting up and beep to confirm device is ready
    listener = Listener(api=API_KEY)
    listener.breathe()
    listener.confirmation()

    # Verbal prompt
    os.system("flite -voice rms -t 'Device ready'")
    listener.set_brightness(0.10, True, 0.001)
    translator = Translator()

    # Loop for feedback
    while True:

        os.system("aplay -q 'beep.wav'")

        # Prompt user
        text = listener.listen()

        # If exit or turn off, break and turn off device.
        if (translator.translate(text, dest="en").text == "exit"
                or translator.translate(text, dest="en").text == "turn off"
                # text == translator.translate("exit", dest="en").text
                # or text == translator.translate("turn off", dest="en").text
            ):
            break

        # Change language to translate into
        if translator.translate(text,
                                dest="en").text.startswith("Set target to"):
            self.set_target(text.split("")[-1])
            continue

        # Change input language (translate 'set source to')
        if translator.translate(text,
                                dest="en").text.startswith("Set source to"):
            self.set_source(text.split("")[-1])
            continue

        # Now take the perceived audio and do something with it
        try:
            print(f"Google thinks you said:\n{text}")
        except sr.UnknownValueError:
            print(f"Google Speech could not understand audio")
        except sr.RequestError as e:
            print(
                f"Could not request results from Google Speech Recognition services: {e}"
            )

        # Speak the translated text if user does not want to exit
        text = translator.translate(text=text,
                                    src=listener.src,
                                    dest=listener.target).text

        # Now sanitize the text
        text = "".join(
            [char for char in text if char.isalnum() or char.isspace()])
        print(text)

        os.system(f"flite -voice rms -t {text.__repr__()}")

        sleep(2)

        listener.confirmation()

        print("Continue through the loop")

    # Pulsate LED once more, hold it, and then turn off device
    listener.breathe(iterations=1, rest=3)
    os.system("shutdown 0")
Example #9
0
class PahoListener(PahoAwsIot, object):
    def __init__(self, **kargs):
        super(PahoListener, self).__init__(
            topic_sub=kargs['paho']['MQTT_TOPIC_SUB'],
            ca=kargs['paho']['CA_ROOT_CERT_FILE'],
            cert=kargs['paho']['THING_CERT_FILE'],
            key=kargs['paho']['THING_PRIVATE_KEY'],
            host=kargs['paho']['MQTT_HOST'],
            port=kargs['paho']['MQTT_PORT'],
            keepalive=kargs['paho']['MQTT_KEEPALIVE_INTERVAL'],
            logging=kargs['logging'])

        self.listener = Listener(pyaudio=kargs['pyaudio'],
                                 path_temp=kargs['path_temp'],
                                 azure=kargs['azure'],
                                 logging=kargs['logging'])

        self.topic_pub = kargs['paho']['MQTT_TOPIC_PUB']

        self.is_debug = kargs['is_debug']

    def _on_subscribe(self, mosq, obj, mid, granted_qos):
        self.logger.info("Subscribed to Topic with QoS: " + str(granted_qos))

        # test code
        if self.is_debug:
            self.publish(
                'raspberrypi/request/listen',
                request_id=str(uuid.uuid4()),
            )

    def _on_message(self, mosq, obj, msg):
        """
		:param dict msg: dictionary converted from json
		 str  topic : raspberrypi/request/{action}
		 int  qos :
		 json payload : {"param1": "...", "param2": "..."}

		  action :
		   listen -- payload : null
		"""
        try:
            self.logger.info("Topic: " + str(msg.topic))
            self.logger.info("QoS: " + str(msg.qos))
            self.logger.info("Payload: " + str(msg.payload))

            ack = {}
            ack['sender'] = 'raspberrypi'

            # topic 確認
            # Level1:既定文字列のチェック
            levels_pub = msg.topic.split('/', 2)
            levels_sub = self.topic_sub.split('/')
            if levels_pub[0] != levels_sub[0]:
                raise ParamError("invalid topic.")

            # Level2:typeのチェック
            if levels_pub[1] != levels_sub[1]:
                raise ParamError("invalid type.")

            # Level3:actionのチェックと取得
            if len(levels_pub) < 3:
                raise ParamError("can't find action.")
            action = levels_pub[2]

            # action毎の処理
            if action == 'listen':
                ack['action'] = 'listened'

                # 音声録音とテキスト変換
                result = self.listener.listen()
                ack['result'] = result
            else:
                raise ParamError("can't find action.")

            # 処理終了
            self.logger.info('on message success.')

        except ParamError as e:
            # 他のメッセージを処理しない
            self.logger.info(e.description)
            return

        except RecognitionError as e:
            # 通常変換失敗時
            self.logger.error(e.description)
            ack['result'] = "音声の聞き取りに失敗しました。"

        except Error as e:
            # その他ユーザエラー全般
            self.logger.error(e.description)
            ack['result'] = "録音処理中にエラーが発生しました。"

        except Exception as e:
            # その他エラー全般
            self.logger.critical(e)
            self.logger.critical(util.trace())
            ack['result'] = "録音処理中にエラーが発生しました。"

        # 応答返却
        self.publish(self.topic_pub, **ack)
        self.logger.info('on message complete.')

    def run(self):
        self.mqttc.loop_forever()
        pass
Example #10
0
    else:
        print 'Usage : python nxpush.py [--prompt]'
        sys.exit(0)

    # getting a session
    try:
        connector = NxConnector(url, user, password)
        connector.openSession()
    except:
        logger.error('%s \n >>> ERROR: Invalid username or password ' % url)
        sys.exit(0)

    # setting up ocr wrapper
    try:
        logger.debug('ocrscript location is %s' % ocrScript)
        ocr = OcrWrapper(ocrScript)
    except FileNotFoundError, e:
        logger.error('Failed to wrap ocr script: ' + str(e))

    # listener
    handler = EventHandler(connector, ocr, mapper)
    listener = Listener(mapper.keys(), handler)

    # start
    listener.listen()
    logger.debug('nxpush service is running')


if __name__ == '__main__':
    main()
Example #11
0
class Macro:
    def __init__(self):
        pyautogui.PAUSE = round(uniform(1.5, 2.3), 2)
        self.listener = Listener()
        self.search = Search()
        self.bglistener = None
        self.waypoints = []
        self.end = False

    def __repr__(self):
        return f"{len(self.waypoints)} waypoint(s) in macro"

    def _on_release(self, key):
        if key == keyboard.Key.esc:
            self.end = True
            self.bglistener.stop()
            print("Ending macro ...")

            return False

    def start(self):
        self.end = False
        self.bglistener = keyboard.Listener(on_release=self._on_release)
        self.bglistener.start()

        print("Macro started ...")
        while not self.end:
            for waypoint in self.waypoints:
                while True:
                    is_success = self.moveTo(waypoint)
                    if is_success:
                        break

        print("Macro ended ...")

    def show_waypoints(self):
        print(f"Current waypoints:")
        print(f"---------------------------------")
        for i in range(0, len(self.waypoints)):
            print(f"{i} : {self.waypoints[i]}")
        print(f"---------------------------------")

    def delete_waypoint(self, index):
        self.waypoints.pop(index)
        print(f"---------------------------------")
        for i in range(0, len(self.waypoints)):
            print(f"{i} : {self.waypoints[i]}")
        print(f"---------------------------------")

    def add_waypoint(self):
        region = self.listener.listen()
        if not None in region:
            image = self.search.find_image(region=region)
            if image:
                self.waypoints.append({
                    "position": (region[0], region[1]),
                    "box": (region[2], region[3]),
                    "image": image,
                })

    def moveTo(self, waypoint, random=True):
        try:
            pyautogui.PAUSE = round(uniform(1.5, 3.0), 2)
            print(pyautogui.PAUSE)
            if not random:
                pyautogui.moveTo(waypoint["position"], duration=0.2)

            if self.search.find_image(image=waypoint["image"]):
                randX = randint(
                    waypoint["position"][0],
                    waypoint["position"][0] + waypoint["box"][0],
                )
                randY = randint(
                    waypoint["position"][1],
                    waypoint["position"][1] + waypoint["box"][1],
                )

                randPosition = (randX, randY)
                pyautogui.moveTo(randPosition, duration=0.2)
                return True
            else:
                print("\033[91mWaypoint image not found ...\033[0m")
                return False
        except:
            pass
Example #12
0
from configurator import Configurator
from listener import Listener

inputdevice = "/dev/input/event0"
configfolder = "./config"

Configurator(configfolder, inputdevice).configure()
listener = Listener(configfolder, inputdevice)

try:
    listener.listen()
except KeyboardInterrupt:
    listener.stop()
Example #13
0
class BotBase:
    plugins = []

    def __init__(self, is_debug):
        self.__init_base_services(is_debug)
        self.__init_plugins()
        self.__init_apps()
        self.last_msg = time.time()
        self.__monitoring_thread = threading.Thread(target=self.monitor_apps,
                                                    daemon=True)
        self.__monitoring_thread.start()
        self.__listener = Listener(self)

    def __init_base_services(self, is_debug):
        logger = Logger(is_debug, './../logs/')
        self.log = logger.log
        self.log('Program is launching')
        self.keys = KeyManager('./../resources/data.json', logger.log)
        self.api = ApiManager(self.keys, logger.log)
        self.sender = Sender('./../resources/', self.keys, self.api,
                             logger.log)
        self.start_time = datetime.datetime.utcnow()
        self.BOT_PORT = self.keys.bot_port

        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.sock.bind(('localhost', self.BOT_PORT))
        self.__thread_listening_port = threading.Thread(
            target=self.__listen_port, daemon=True)
        self.__is_listen_port_sleeping = False
        self.__is_listen_port_listening = False
        self.__go_on_sleep = False
        self.__thread_listening_port.start()

        self.log('Base services are loaded')

    def __listen_port(self):
        self.__is_listen_port_sleeping = False
        self.log('Listening port %d...' % self.BOT_PORT)
        sock = self.sock
        #skip stuff

        sock.settimeout(0.1)
        while True:
            try:
                sock.recv(8192)
            except:
                break

        while True:
            sock.settimeout(60 * 60 * 6)
            try:
                try:
                    self.__is_listen_port_listening = True
                    msg, addr = sock.recvfrom(1024)
                    time.sleep(0.01)
                    self.__is_listen_port_listening = False
                    msg = msg.decode('utf-8')
                except socket.timeout:
                    continue
                self.log(f'Received msg in ports listener from {addr}: {msg}')
                if msg == 'ping':
                    self.log(f'"pong" sending to {addr[1]}')
                    sock.sendto(b'pong', addr)
                elif msg == 'update':
                    threading.Thread(target=self.update, daemon=True).start()
                elif msg == 'exited':
                    for app in self.apps:
                        if app.port == addr[1]:
                            app.is_enabled = False
                            break
                elif msg == 'stop' and addr[1] == self.BOT_PORT:
                    self.__is_listen_port_sleeping = True
                    while self.__go_on_sleep:
                        time.sleep(0.1)
                    self.__is_listen_port_sleeping = False
                else:
                    self.log('unknown message')
            except Exception as e:
                self.sock.close()
                self.sender.report(
                    f'Exception in ports listener:\n{str(type(e))}\n{str(e)}')
                self.log(
                    f'!!ERROR!!\nException in ports listener:\n{str(type(e))}\n{str(e)}'
                )
                break

    def __init_plugins(self):
        modules = [
            x[:-3] for x in os.listdir('./../plugins') if x.endswith('.py')
        ]
        imports = []
        for x in modules:
            try:
                imports.append(__import__(x))
            except Exception as e:
                self.sender.report(
                    f'Exception in the import module({x}):\n{str(type(e))}\n{str(e)}'
                )
        self.log(f'Imported modules: {str(modules)}')
        all_classes = []
        for x in imports:
            for cls in inspect.getmembers(x, inspect.isclass):
                if SuperPlugin in cls[1].__bases__:
                    all_classes.append(cls[1])
        classes = []
        for x in all_classes:
            if x not in classes:
                classes.append(x)
        self.log(f'Loaded classes: {str(classes)}')
        for x in classes:
            self.plugins.append(x(self))
        self.log('Plugins are loaded')

    def update(self):
        self.__init_apps()
        self.__listener.apps = self.apps

    def __init_apps(self):
        class App:
            def __init__(self, name, port, cmds, prefixes, folder, is_enabled,
                         launch_time):
                self.name = name
                self.port = port
                self.cmds = cmds
                self.prefixes = prefixes
                self.folder = folder
                self.is_enabled = is_enabled
                self.is_port_busy = True
                self.last_act = 0
                self.last_check = 0
                self.launch_time = launch_time
                self.check_state = 0

        apps = []
        path_shit = './../apps/'
        dirs = os.listdir(path_shit)
        for dirr in dirs:
            config_path = path_shit + dirr + '/config'
            if not os.path.exists(config_path):
                continue
            try:
                with open(config_path, 'r', encoding='utf-8') as file:
                    s1 = file.readline()
                    assert (s1.startswith('port=')
                            ), 'Config file is uncorrect (port)'
                    port = int(s1[5:-1])
                    s2 = file.readline()
                    assert (s2.startswith('commands=')
                            ), 'Config file is uncorrect (commands)'
                    cmds_str = s2[9:-1]
                    cmds_dict = ast.literal_eval(cmds_str)
                    s3 = file.readline()
                    assert (s3.startswith('prefixes=')
                            ), 'Config file is uncorrect (prefixes)'
                    prefixes_str = s3[9:-1]
                    prefixes_dict = ast.literal_eval(prefixes_str)
                    s4 = file.readline()
                    assert (s4.startswith('name=')
                            ), 'Config file is uncorrect (name)'
                    name = s4[5:-1]
                    s5 = file.readline()
                    assert (s5.startswith('launch_time=')
                            ), 'Config file is uncorrect (launch_time)'
                    launch_time = float(s5[12:-1])
                    one_app = App(name, port, cmds_dict, prefixes_dict, dirr,
                                  False, launch_time)
                    apps.append(one_app)
                self.log(f'Loaded app "{name}" with port {port}')

            except Exception as e:
                self.log(
                    f'!!ERROR!!\nAttempt to load app is unsuccessfully:\n{str(type(e))}\n{str(e)}'
                )
        self.apps = apps

        for app in apps:
            self.check_connection(app)

    def send_to_port(self, msg, app, timeout=1, max_ans_size=1024):
        app.is_port_busy = True
        sock = self.sock
        while not self.__is_listen_port_listening:
            pass
        self.__go_on_sleep = True
        sock.sendto(b'stop', ('localhost', self.BOT_PORT))
        while not self.__is_listen_port_sleeping:
            pass
        sock.settimeout(timeout)
        self.log(f'"{msg}" sending to app"{app.name}":{app.port}')
        sock.sendto(msg.encode('utf-8'), ('localhost', app.port))
        try:
            ans, addr = sock.recvfrom(max_ans_size)
            self.__go_on_sleep = False
            app.is_port_busy = False
            ans = ans.decode('utf-8')
            if addr[1] != app.port:
                shit = app.name
                self.log(f'Answer came from {shit}: {ans}')
                self.sender.report(
                    'Произошла коллизия. Ожидался запрос от одного порта, получился другой'
                )
                return None
            self.log(f'App "{app.name}":{app.port} responded: {ans}')
            return ans
        except (ConnectionResetError, socket.timeout) as e:
            self.__go_on_sleep = False
            self.log(f'App "{app.name}":{app.port} doesn\'t respond')
            app.is_enabled = False
            return None

    def __update_states(self):
        for app in self.apps:
            lnc = time.time() - app.launch_time
            cha = time.time() - app.last_act
            if lnc < 5.0 * 60.0 or cha < 1.0 * 60.0:
                app.check_state = 1
            elif lnc < 1.0 * 60.0 * 60.0 or cha < 5.0 * 60.0:
                app.check_state = 2
            elif lnc < 4.0 * 60.0 * 60.0 or cha < 20.0 * 60.0:
                app.check_state = 3
            elif lnc < 24.0 * 60.0 * 60.0 or cha < 60.0 * 60.0:
                app.check_state = 4
            elif lnc > 24.0 * 60.0 * 60.0:
                app.check_state = 5

    def __collect_check_list(self):
        for i in range(len(self.apps)):
            app = self.apps[i]
            if app.is_enabled == False or app.is_port_busy:
                continue
            if app.check_state == 1:
                yield i
            elif app.check_state == 2 and time.time() - app.last_check > 60.0:
                yield i
            elif app.check_state == 3 and time.time(
            ) - app.last_check > 5.0 * 60.0:
                yield i
            elif app.check_state == 4 and time.time(
            ) - app.last_check > 30.0 * 60.0:
                yield i
            elif app.check_state == 5 and time.time(
            ) - app.last_check > 60.0 * 60.0:
                yield i

    def monitor_apps(self):
        time.sleep(10)
        try:
            while True:
                self.__update_states()
                check_list = list(self.__collect_check_list())
                if len(check_list) == 0:
                    time.sleep(11)
                    continue
                for i in check_list:
                    app = self.apps[i]
                    if app.is_enabled == True and not app.is_port_busy and time.time(
                    ) - self.last_msg > 3.0:
                        app.last_check = time.time()
                        ans = self.send_to_port('is_all_right', app, 0.6, 8)
                        if ans == None:
                            self.sender.report(
                                f'Приложение {app.name} внезапно перестало отвечать'
                            )
                            self.log(
                                f'!!ERROR!!\nApp {app.name} suddenly stopped responding'
                            )
                            app.is_enabled = False
                        elif ans == '1':
                            self.sender.report(
                                f'Процесс приложения {app.name} неожиданно завершил работу'
                            )
                            self.log(
                                f'!!ERROR!!\nThread of app {app.name} crashed')
                        elif ans != '2' and ans != '0':
                            self.sender.report(
                                'Неожиданный ответ в функции BotBase.monitor_apps. Посмотри логи'
                            )
                            self.log(
                                f'!!ERROR!!\nIt\'s strange answer: "{ans}"')
                    time.sleep(11.0 / len(check_list))
        except Exception as e:
            self.log(
                '!! ERROR !!\nException in the monitor_apps. It stopped\n' +
                str(type(e)) + '\n' + str(e))
            self.sender.report(
                '!! ERROR !!\nException in the monitor_apps. It stopped\n' +
                str(type(e)) + '\n' + str(e))
            return

    def check_connection(self, app):
        if self.send_to_port('ping', app, 0.1, 32) == 'pong':
            self.log(f'App "{app.name}":{app.port} responded Pong')
            app.is_enabled = True
            return True
        else:
            if app.is_enabled == True:
                self.log(f'App "{app.name}":{app.port} is disabled')
                app.is_enabled = False
            return False

    def _start_(self, mode=0):
        if mode == 0:
            pass  #quiet start
        elif mode == 1:
            self.sender.report('Бот запущен!')
            self.log('Bot is started. mode %d' % mode)
        elif mode == 2:
            self.sender.report('Бот перезапущен!')
            self.log('Bot is restarted %d' % mode)
        elif mode == 3:
            self.log('Bot is restarted %d' % mode)

        self.__listen__()

    def __listen__(self):
        try:
            self.__listener.listen()
        except requests.exceptions.ConnectionError:
            self.log('Connection ERROR in botbase')
            time.sleep(60)
            self._start_(3)

        except requests.exceptions.ReadTimeout:
            self.log('ReadTimeout (night reboot)')
            time.sleep(60)
            self._start_(3)

        except Exception as e:
            self.sender.report('Exception in Botbase:\n' + str(type(e)) +
                               '\n' + str(e))
            self.sender.report('Бот запустится через 5 секунд')
            self.log('!!ERROR!!\nException in Botbase:\n' + str(type(e)) +
                     '\n' + str(e))
            time.sleep(5)
            self._start_(2)
Example #14
0
class Loader(Owner):
    def __init__(self, init_cfg: dict, init_state: dict, path: dict,
                 sig: SignalHandlerDummy):
        self._sig = sig
        self.reload = False
        self._restore_filename = None
        self._lock = threading.Lock()
        self._stts_lock = threading.Lock()
        self._join_lock = threading.Lock()

        self._pub = PubSub()
        self._sig.set_wakeup_callback(
            lambda: self.sub_call('default', 'terminal_stop', True))

        self._logger = logger.Logger(self)
        proxies.add_logger(self._logger.add('Proxy'))

        self._cfg = ConfigHandler(cfg=init_cfg,
                                  state=init_state,
                                  path=path,
                                  log=self._logger.add('CFG'),
                                  owner=self)
        self._logger.init(cfg=self._cfg, owner=self)
        self._log = self._logger.add('SYSTEM')

        self._listen = Listener(cfg=self._cfg,
                                log=self._logger.add('REC'),
                                owner=self)
        self._notifier = MajordomoNotifier(cfg=self._cfg,
                                           log=self._logger.add('Notifier'),
                                           owner=self)
        self._tts = stts.TextToSpeech(cfg=self._cfg,
                                      log=self._logger.add('TTS'))
        self._play = Player(cfg=self._cfg,
                            log=self._logger.add('Player'),
                            owner=self)
        self._music = music_constructor(cfg=self._cfg,
                                        logger=self._logger,
                                        owner=self)
        self._stt = stts.SpeechToText(cfg=self._cfg,
                                      log=self._logger.add('STT'),
                                      owner=self)
        self._mm = ModuleManager(cfg=self._cfg,
                                 log=self._logger.add('MM'),
                                 owner=self)
        self._updater = Updater(cfg=self._cfg,
                                log=self._logger.add('Updater'),
                                owner=self)
        self._backup = Backup(cfg=self._cfg,
                              log=self._logger.add('Backup'),
                              owner=self)
        self._terminal = MDTerminal(cfg=self._cfg,
                                    log=self._logger.add('Terminal'),
                                    owner=self)
        self._server = server_constructor(cfg=self._cfg,
                                          logger=self._logger,
                                          owner=self)
        self._plugins = Plugins(cfg=self._cfg,
                                log=self._logger.add('Plugins'),
                                owner=self)
        self._duplex_pool = DuplexPool(cfg=self._cfg,
                                       log=self._logger.add('DP'),
                                       owner=self)

        self._discovery = DiscoveryServer(cfg=self._cfg,
                                          log=self._logger.add('Discovery'))

    def start_all_systems(self):
        self._music.start()
        self._play.start()
        self._play.say_info(F(
            'Приветствую. Голосовой терминал настраивается, три... два... один...'
        ),
                            0,
                            wait=0.5)
        self._stt.start()
        self._cfg.start()
        self._notifier.start()
        self._mm.start()
        self._updater.start()
        self._terminal.start()
        self._server.start()
        self._discovery.start()
        self._backup.start()
        self._plugins.start()

        self.messenger(
            lambda: self.log(available_version_msg(self._cfg.version_info),
                             logger.INFO), None)
        self.sub_call('default', 'version', self._cfg.version_str)
        self.volume_callback(self.get_volume())

    def stop_all_systems(self):
        self._cfg.config_save(final=True)
        self.join_thread(self._plugins)
        self._mm.stop()
        self.join_thread(self._discovery)
        self.join_thread(self._server)
        self.join_thread(self._terminal)
        self.join_thread(self._backup)
        self.join_thread(self._updater)
        self.join_thread(self._notifier)
        self.join_thread(self._duplex_pool)

        self._play.quiet()
        self._play.kill_popen()
        self._play.say_info(F('Голосовой терминал завершает свою работу.'))

        self._stt.stop()
        self._play.stop()
        self.join_thread(self._music)

        if self._restore_filename:
            self._backup.restore(self._restore_filename)
            self._restore_filename = ''

        self.join_thread(self._logger.remote_log)
        self._pub.stopping = True
        self._logger.join()
        self._pub.join()

        self._pub.report()

    def log(self, msg: str, lvl=logger.DEBUG):
        self._log(msg, lvl)

    def join_thread(self, obj):
        def obj_log(msg_: str, lvl=logger.DEBUG):
            if log_present:
                obj.log(msg_, lvl)

        def diagnostic_msg() -> str:
            _call = getattr(obj, 'diagnostic_msg', None)
            return ' {}'.format(_call()) if callable(_call) else ''

        with self._join_lock:
            close_signal = getattr(obj, 'close_signal', None)
            if close_signal:
                close_signal()
            if not obj.work:
                return
            log_present = callable(getattr(obj, 'log', None))
            obj.work = False
            obj_log('stopping...')
            stop_time = time.time()
            obj.join()
            stop_time = time.time() - stop_time
            if not obj.is_alive():
                obj_log('stop.', logger.INFO)
            else:
                obj_log('stopping error.', logger.ERROR)
                name_ = '.'.join(getattr(obj.log, 'name',
                                         [''])) if log_present else None
                name_ = name_ or str(obj)
                msg = 'Thread \'{}\' stuck and not stopping in {}!{}'.format(
                    name_, pretty_time(stop_time), diagnostic_msg())
                self.log(msg, logger.ERROR)

    def subscribe(self, event, callback, channel='default') -> bool:
        return self._pub.subscribe(event, callback, channel)

    def unsubscribe(self, event, callback, channel='default') -> bool:
        return self._pub.unsubscribe(event, callback, channel)

    def registration(self, event: str, channel='default'):
        return self._pub.registration(event, channel)

    def has_subscribers(self, event: str, channel='default') -> bool:
        return self._pub.has_subscribers(event, channel)

    def events_list(self, channel='default') -> list:
        return self._pub.events_list(channel)

    def send_notify(self, event: str, *args, **kwargs):
        return self._pub.sub_call('default', event, *args, **kwargs)

    def sub_call(self, channel: str, event: str, *args, **kwargs):
        return self._pub.sub_call(channel, event, *args, **kwargs)

    @staticmethod
    def messenger(call, callback, *args, **kwargs) -> bool:
        return Messenger(call, callback, *args, **kwargs)()

    def insert_module(self, module) -> bool:
        return self._mm.insert_module(module)

    def extract_module(self, callback) -> bool:
        return self._mm.extract_module(callback)

    def insert_detectors(self, detector):
        detector = prepare_detectors(detector)
        if not detector:
            return

        def callback():
            with self._lock:
                detectors.DETECTORS.update(detector)
                self.__reconfigure_terminal(detector)

        # noinspection PyTypeChecker
        self.terminal_call('callme', callback, save_time=False)

    def extract_detectors(self, detector):
        detector = prepare_detectors(detector, True)
        if not detector:
            return

        def callback():
            with self._lock:
                [detectors.DETECTORS.pop(x, None) for x in detector]
                self.__reconfigure_terminal(detector)

        # noinspection PyTypeChecker
        self.terminal_call('callme', callback, save_time=False)

    def add_stt_provider(self, name: str, entrypoint) -> bool:
        with self._stts_lock:
            if name not in STT.PROVIDERS:
                STT.PROVIDERS[name] = entrypoint
                return True
            return False

    def remove_stt_provider(self, name: str):
        with self._stts_lock:
            try:
                return STT.PROVIDERS.pop(name)
            except KeyError:
                return None

    def add_tts_provider(self, name: str, entrypoint) -> bool:
        with self._stts_lock:
            if name not in TTS.PROVIDERS:
                TTS.PROVIDERS[name] = entrypoint
                return True
            return False

    def remove_tts_provider(self, name: str):
        with self._stts_lock:
            try:
                return TTS.PROVIDERS.pop(name)
            except KeyError:
                return None

    def tts_providers(self) -> list:
        with self._stts_lock:
            return list(TTS.PROVIDERS.keys())

    def stt_providers(self) -> list:
        with self._stts_lock:
            return list(STT.PROVIDERS.keys())

    def is_tts_provider(self, name: str) -> bool:
        return name in TTS.PROVIDERS

    def is_stt_provider(self, name: str) -> bool:
        return name in STT.PROVIDERS

    def plugins_status(self, state: str) -> dict:
        return self._plugins.status(state)

    def get_plugin(self, name: str) -> object:
        try:
            return self._plugins.modules[name]
        except KeyError:
            raise RuntimeError('Plugin \'{}\' not found'.format(name))
        except Exception as e:
            raise RuntimeError('Error accessing to plugin \'{}\': {}'.format(
                name, e))

    def list_notifications(self) -> list:
        return self._notifier.list_notifications()

    def add_notifications(self, events: list, is_self=False) -> list:
        return self._notifier.add_notifications(events, is_self)

    def remove_notifications(self, events: list) -> list:
        return self._notifier.remove_notifications(events)

    def say(self,
            msg: str,
            lvl: int = 2,
            alarm=None,
            wait=0,
            is_file: bool = False,
            blocking: int = 0):
        self._play.say(msg, lvl, alarm, wait, is_file, blocking)

    def play(self, file, lvl: int = 2, wait=0, blocking: int = 0):
        self._play.play(file, lvl, wait, blocking)

    def say_info(self,
                 msg: str,
                 lvl: int = 2,
                 alarm=None,
                 wait=0,
                 is_file: bool = False):
        self._play.say_info(msg, lvl, alarm, wait, is_file)

    def set_lvl(self, lvl: int) -> bool:
        return self._play.set_lvl(lvl)

    def clear_lvl(self):
        self._play.clear_lvl()

    def quiet(self):
        self._play.quiet()

    def full_quiet(self):
        self._play.full_quiet()

    def really_busy(self) -> bool:
        return self._play.really_busy()

    @state_cache(interval=0.008)
    def noising(self) -> bool:
        return self._play.noising()

    def kill_popen(self):
        self._play.kill_popen()

    def listen(self,
               hello: str = '',
               deaf: bool = True,
               voice: bool = False) -> tuple:
        return self._stt.listen(hello, deaf, voice)

    def voice_record(self,
                     hello: str or None,
                     save_to: str,
                     convert_rate=None,
                     convert_width=None,
                     limit=8):
        return self._stt.voice_record(hello, save_to, convert_rate,
                                      convert_width, limit)

    def voice_recognition(self,
                          audio,
                          quiet: bool = False,
                          fusion=None) -> str:
        return self._stt.voice_recognition(audio, quiet, fusion)

    @property
    def max_mic_index(self) -> int:
        return self._stt.max_mic_index

    @max_mic_index.setter
    def max_mic_index(self, val: int):
        self._stt.max_mic_index = val

    @property
    def mic_index(self) -> int:
        return self._stt.get_mic_index()

    def phrase_from_files(self, files: list) -> tuple:
        return self._stt.phrase_from_files(files)

    def multiple_recognition(self, file_or_adata, providers: list) -> list:
        return self._stt.multiple_recognition(file_or_adata, providers)

    @property
    def sys_say_chance(self) -> bool:
        return self._stt.sys_say.chance

    def music_state(self) -> str:
        return self._music.state()

    def music_play(self, uri):
        self._music.play(uri)

    def music_pause(self, paused=None):
        self._music.pause(paused)

    @property
    def music_plays(self) -> bool:
        return self._music.plays

    @property
    def music_volume(self):
        return self._music.volume

    @music_volume.setter
    def music_volume(self, vol):
        self._music.volume = vol

    @property
    def music_real_volume(self):
        return self._music.real_volume

    @music_real_volume.setter
    def music_real_volume(self, vol):
        self._music.real_volume = vol

    @property
    def music_track_name(self) -> str or None:
        return self._music.get_track_name()

    def tts(self, msg, realtime: bool = True):
        return self._tts.tts(msg, realtime)

    def ask_again_callback(self):
        self._pub.call('ask_again')

    def voice_activated_callback(self):
        self._pub.call('voice_activated')

    def speech_recognized_callback(self, status: bool):
        if status and self._cfg.gts('alarm_recognized'):
            self.play(self._cfg.path['bimp'])
        self._pub.call(
            'speech_recognized_{}success'.format('' if status else 'un'))

    def record_callback(self, start_stop: bool):
        self._pub.call('start_record' if start_stop else 'stop_record')

    def say_callback(self, start_stop: bool):
        self._pub.call('start_talking' if start_stop else 'stop_talking')

    def speech_recognized(self, start_stop: bool):
        self._pub.call('start_recognized' if start_stop else 'stop_recognized')

    def music_status_callback(self, status: str):
        self._pub.call('music_status',
                       status if status is not None else 'error')

    def music_volume_callback(self, volume: int):
        self._pub.call('music_volume', volume if volume is not None else -1)

    def volume_callback(self, volume: int):
        self._pub.call('volume', volume)

    @property
    def srv_ip(self) -> str:
        return self._cfg['smarthome']['ip']

    def update(self):
        self._updater.update()

    def manual_rollback(self):
        self._updater.manual_rollback()

    def backup_manual(self):
        self._backup.manual_backup()

    def backup_restore(self, filename: str):
        if not self._restore_filename and filename:
            self._restore_filename = filename
            self.die_in(3, reload=True)

    def backup_list(self) -> list:
        return self._backup.backup_list()

    def modules_tester(self, phrase: str, call_me=None, rms=None, model=None):
        return self._mm.tester(phrase, call_me, rms, model)

    def die_in(self, wait, reload=False):
        self.reload = reload
        self._sig.die_in(wait)

    @property
    def get_volume_status(self) -> dict:
        music_volume = self._music.real_volume
        return {
            'volume': self.get_volume(),
            'music_volume': music_volume if music_volume is not None else -1
        }

    def terminal_call(self,
                      cmd: str,
                      data='',
                      lvl: int = 0,
                      save_time: bool = True):
        self._terminal.call(cmd, data, lvl, save_time)

    def terminal_listen(self) -> bool:
        return self._terminal.listening

    def recognition_forever(self, interrupt_check: callable,
                            callback: callable):
        return self._listen.recognition_forever(interrupt_check, callback)

    def get_vad_detector(self,
                         source_or_mic,
                         vad_mode=None,
                         vad_lvl=None,
                         energy_lvl=None,
                         energy_dynamic=None):
        return self._listen.get_vad_detector(source_or_mic, vad_mode, vad_lvl,
                                             energy_lvl, energy_dynamic)

    def detected_fake(self, text: str, rms=None, model=None, cb=None):
        self._listen.detected_fake(text, rms, model, cb)

    def listener_listen(self, r=None, mic=None, vad=None):
        return self._listen.listen(r, mic, vad)

    def background_listen(self):
        return self._listen.background_listen()

    def get_volume(self) -> int:
        control = self._cfg.gt('volume', 'line_out', '')
        card = self._cfg.gt('volume', 'card', 0)
        if not control or control == volume_.UNDEFINED:
            return -2
        return volume_.get_volume(control, card)

    def set_volume(self, vol) -> int:
        control = self._cfg.gt('volume', 'line_out', '')
        card = self._cfg.gt('volume', 'card', 0)
        if not control or control == volume_.UNDEFINED:
            return -2
        try:
            return volume_.set_volume(vol, control, card)
        except RuntimeError as e:
            self.log('set_volume({}): {}'.format(vol, e), logger.WARN)
            return -1

    def settings_from_inside(self, cfg: dict) -> bool:
        with self._lock:
            return self._cfg.update_from_dict(cfg)

    def settings_from_srv(self, cfg: str or dict) -> dict:
        # Reload modules if their settings could be changes
        with self._lock:
            diff = self._cfg.update_from_external(cfg)
            reload_terminal = False
            detector_reconfigure = False
            vad_reconfigure = False
            if diff is None:
                self._cfg.print_cfg_no_change()
                return {}
            lang, lang_check = None, None
            if is_sub_dict('settings',
                           diff) and ('lang' in diff['settings']
                                      or 'lang_check' in diff['settings']):
                # re-init lang
                lang = diff['settings'].pop('lang', None)
                lang_check = diff['settings'].pop('lang_check', None)
                self._cfg.lang_init()
                if lang:
                    # reload phrases
                    self._stt.reload()
                    # reload modules
                    self._mm.reload()
            if is_sub_dict('models', diff):
                # reload models. Reload terminal - later
                self._cfg.models_load()
                reload_terminal = True
            if is_sub_dict('log', diff):
                # reload logger
                self._logger.reload()
            if is_sub_dict('cache', diff):
                # re-check tts cache
                self._cfg.tts_cache_check()
            if is_sub_dict('proxy', diff):
                # re-init proxy
                self._cfg.proxies_init()
            if is_sub_dict('music', diff):
                # reconfigure music server
                self.music_reload()
            if is_sub_dict('update', diff):
                # update 'update' interval
                self._updater.reload()
            if is_sub_dict('backup', diff):
                # update backup interval
                self._backup.reload()
            if is_sub_dict('smarthome', diff):
                if 'allow_addresses' in diff['smarthome']:
                    # re-init allow ip addresses
                    self._cfg.allow_addresses_init()
                if 'disable_server' in diff['smarthome']:
                    # handle [smarthome] disable_server
                    self.messenger(self.server_reload, None)
                # resubscribe
                self._notifier.reload(diff)
                self._duplex_pool.reload()
            if is_sub_dict('noise_suppression', diff):
                # reconfigure APM. Reload terminal - later
                self._cfg.apm_configure()
                reload_terminal = True
            if is_sub_dict('listener', diff):
                reload_terminal = True
                detector_reconfigure = 'detector' in diff['listener']
                vad_reconfigure = bool([
                    key for key in ('vad_mode', 'vad_chrome')
                    if key in diff['listener']
                ])
            if is_sub_dict('settings', diff) or reload_terminal:
                # reload terminal
                # noinspection PyTypeChecker
                self.terminal_call('reload',
                                   (detector_reconfigure, vad_reconfigure),
                                   save_time=False)

            # restore lang's
            if lang is not None:
                diff['settings']['lang'] = lang
            if lang_check is not None:
                diff['settings']['lang_check'] = lang_check

            # check and reload plugins
            self._plugins.reload(diff)
            self._cfg.print_cfg_change()
            self._cfg.config_save()
            return diff

    def music_reload(self):
        self._music = music_constructor(self._cfg, self._logger, self,
                                        self._music)

    def server_reload(self):
        self._server = server_constructor(self._cfg, self._logger, self,
                                          self._server)

    def __reconfigure_terminal(self, detector: set or dict):
        if self._cfg['listener'][
                'detector'] in detector or self._cfg.detector.NAME in detector:
            # noinspection PyProtectedMember
            self._terminal._reload((True, False))