class MainController: """ This Class is the global controller of the application. """ def __init__(self, brain=None): self.brain = brain # get global configuration sl = SettingLoader.Instance() self.settings = sl.settings # run the api if the user want it if self.settings.rest_api.active: Utils.print_info("Starting REST API Listening port: %s" % self.settings.rest_api.port) app = Flask(__name__) flask_api = FlaskAPI(app, port=self.settings.rest_api.port, brain=self.brain) flask_api.daemon = True flask_api.start() # create an order listener object. This last will the trigger callback before starting self.order_listener = OrderListener(self.analyse_order) # Wait that the kalliope trigger is pronounced by the user self.trigger_instance = self._get_default_trigger() self.trigger_instance.start() Utils.print_info("Waiting for trigger detection") def callback(self): """ we have detected the hotword, we can now pause the Trigger for a while The user can speak out loud his order during this time. """ # pause the trigger process self.trigger_instance.pause() # start listening for an order self.order_listener.start() # if random wake answer sentence are present, we play this if self.settings.random_wake_up_answers is not None: Say(message=self.settings.random_wake_up_answers) else: random_sound_to_play = self._get_random_sound( self.settings.random_wake_up_sounds) Mplayer.play(random_sound_to_play) def analyse_order(self, order): """ Receive an order, try to retrieve it in the brain.yml to launch to attached plugins :param order: the sentence received :type order: str """ if order is not None: # maybe we have received a null audio from STT engine order_analyser = OrderAnalyser(order, main_controller=self, brain=self.brain) order_analyser.start() # restart the trigger when the order analyser has finish his job Utils.print_info("Waiting for trigger detection") self.trigger_instance.unpause() # create a new order listener that will wait for start self.order_listener = OrderListener(self.analyse_order) def _get_default_trigger(self): """ Return an instance of the default trigger :return: Trigger """ for trigger in self.settings.triggers: if trigger.name == self.settings.default_trigger_name: return TriggerLauncher.get_trigger(trigger, callback=self.callback) @staticmethod def _get_random_sound(random_wake_up_sounds): """ Return a path of a sound to play If the path is absolute, test if file exist If the path is relative, we check if the file exist in the sound folder :param random_wake_up_sounds: List of wake_up sounds :return: path of a sound to play """ # take first randomly a path random_path = random.choice(random_wake_up_sounds) logger.debug("Selected sound: %s" % random_path) if os.path.isabs(random_path): logger.debug("Path of file %s is absolute" % random_path) return random_path else: logger.debug("Path of file %s is relative" % random_path) return "sounds" + os.sep + random_path
class MainController: """ This Class is the global controller of the application. """ states = ['init', 'starting_trigger', 'unpausing_trigger', 'playing_ready_sound', 'waiting_for_trigger_callback', 'start_order_listener', 'playing_wake_up_answer', 'waiting_for_order_listener_callback', 'analysing_order'] def __init__(self, brain=None): self.brain = brain # get global configuration sl = SettingLoader() self.settings = sl.settings # keep in memory the order to process self.order_to_process = None # Starting the rest API self._start_rest_api() # save an instance of the trigger self.trigger_instance = None self.trigger_callback_called = False # save the current order listener self.order_listener = None self.order_listener_callback_called = False # boolean used to know id we played the on ready notification at least one time self.on_ready_notification_played_once = False # Initialize the state machine self.machine = Machine(model=self, states=MainController.states, initial='init', queued=True) # define transitions self.machine.add_transition('start_trigger', 'init', 'starting_trigger') self.machine.add_transition('unpause_trigger', ['starting_trigger', 'analysing_order'], 'unpausing_trigger') self.machine.add_transition('play_ready_sound', 'unpausing_trigger', 'playing_ready_sound') self.machine.add_transition('wait_trigger_callback', 'playing_ready_sound', 'waiting_for_trigger_callback') self.machine.add_transition('play_wake_up_answer', 'waiting_for_trigger_callback', 'playing_wake_up_answer') self.machine.add_transition('wait_for_order', 'playing_wake_up_answer', 'waiting_for_order_listener_callback') self.machine.add_transition('analyse_order', 'waiting_for_order_listener_callback', 'analysing_order') self.machine.add_ordered_transitions() # add method which are called when changing state self.machine.on_enter_starting_trigger('start_trigger_process') self.machine.on_enter_playing_ready_sound('play_ready_sound_process') self.machine.on_enter_waiting_for_trigger_callback('waiting_for_trigger_callback_thread') self.machine.on_enter_playing_wake_up_answer('play_wake_up_answer_thread') self.machine.on_enter_start_order_listener('start_order_listener_thread') self.machine.on_enter_waiting_for_order_listener_callback('waiting_for_order_listener_callback_thread') self.machine.on_enter_analysing_order('analysing_order_thread') self.machine.on_enter_unpausing_trigger('unpausing_trigger_process') self.start_trigger() def start_trigger_process(self): """ This function will start the trigger thread that listen for the hotword """ logger.debug("Entering state: %s" % self.state) self.trigger_instance = self._get_default_trigger() self.trigger_instance.daemon = True # Wait that the kalliope trigger is pronounced by the user self.trigger_instance.start() self.next_state() def unpausing_trigger_process(self): """ If the trigger was in pause, this method will unpause it to listen again for the hotword """ logger.debug("Entering state: %s" % self.state) self.trigger_instance.unpause() self.trigger_callback_called = False Utils.print_info("Waiting for trigger detection") self.next_state() def play_ready_sound_process(self): """ Play a sound when Kalliope is ready to be awaken at the first start """ logger.debug("Entering state: %s" % self.state) if (not self.on_ready_notification_played_once and self.settings.play_on_ready_notification == "once") or \ self.settings.play_on_ready_notification == "always": # we remember that we played the notification one time self.on_ready_notification_played_once = True # here we tell the user that we are listening if self.settings.on_ready_answers is not None: Say(message=self.settings.on_ready_answers) elif self.settings.on_ready_sounds is not None: random_sound_to_play = self._get_random_sound(self.settings.on_ready_sounds) Mplayer.play(random_sound_to_play) self.next_state() def waiting_for_trigger_callback_thread(self): """ Method to print in debug that the main process is waiting for a trigger detection """ logger.debug("Entering state: %s" % self.state) # this loop is used to keep the main thread alive while not self.trigger_callback_called: sleep(0.1) self.next_state() def waiting_for_order_listener_callback_thread(self): """ Method to print in debug that the main process is waiting for an order to analyse """ logger.debug("Entering state: %s" % self.state) # this loop is used to keep the main thread alive while not self.order_listener_callback_called: sleep(0.1) self.next_state() def trigger_callback(self): """ we have detected the hotword, we can now pause the Trigger for a while The user can speak out loud his order during this time. """ logger.debug("Trigger callback called, switching to the next state") # self.next_state() self.trigger_callback_called = True def start_order_listener_thread(self): """ Start the STT engine thread """ logger.debug("Entering state: %s" % self.state) # pause the trigger process self.trigger_instance.pause() # start listening for an order self.order_listener_callback_called = False self.order_listener = OrderListener(callback=self.order_listener_callback) self.order_listener.daemon = True self.order_listener.start() self.next_state() def play_wake_up_answer_thread(self): """ Play a sound or make Kalliope say something to notify the user that she has been awaken and now waiting for order """ logger.debug("Entering state: %s" % self.state) # if random wake answer sentence are present, we play this if self.settings.random_wake_up_answers is not None: Say(message=self.settings.random_wake_up_answers) else: random_sound_to_play = self._get_random_sound(self.settings.random_wake_up_sounds) Mplayer.play(random_sound_to_play) self.next_state() def order_listener_callback(self, order): """ Receive an order, try to retrieve it in the brain.yml to launch to attached plugins :param order: the sentence received :type order: str """ logger.debug("order listener callback called. Order to process: %s" % order) self.order_to_process = order self.order_listener_callback_called = True def analysing_order_thread(self): """ Start the order analyser with the caught order to process """ logger.debug("order in analysing_order_thread %s" % self.order_to_process) if self.order_to_process is not None: # maybe we have received a null audio from STT engine order_analyser = OrderAnalyser(self.order_to_process, brain=self.brain) order_analyser.start() else: if self.settings.default_synapse is not None: SynapseLauncher.start_synapse(name=self.settings.default_synapse, brain=self.brain) # return to the state "unpausing_trigger" self.unpause_trigger() def _get_default_trigger(self): """ Return an instance of the default trigger :return: Trigger """ for trigger in self.settings.triggers: if trigger.name == self.settings.default_trigger_name: return TriggerLauncher.get_trigger(trigger, callback=self.trigger_callback) @staticmethod def _get_random_sound(random_wake_up_sounds): """ Return a path of a sound to play If the path is absolute, test if file exist If the path is relative, we check if the file exist in the sound folder :param random_wake_up_sounds: List of wake_up sounds :return: path of a sound to play """ # take first randomly a path random_path = random.choice(random_wake_up_sounds) logger.debug("Selected sound: %s" % random_path) return Utils.get_real_file_path(random_path) def _start_rest_api(self): """ Start the Rest API if asked in the user settings """ # run the api if the user want it if self.settings.rest_api.active: Utils.print_info("Starting REST API Listening port: %s" % self.settings.rest_api.port) app = Flask(__name__) flask_api = FlaskAPI(app=app, port=self.settings.rest_api.port, brain=self.brain, allowed_cors_origin=self.settings.rest_api.allowed_cors_origin) flask_api.daemon = True flask_api.start()
class Order(SignalModule, Thread): states = [ 'init', 'starting_trigger', 'waiting_for_trigger_callback', 'stopping_trigger', 'start_order_listener', 'waiting_for_order_listener_callback', 'analysing_order' ] def __init__(self): super(SignalModule, self).__init__() Thread.__init__(self, name=Order) Utils.print_info('Starting order signal') # load settings and brain from singleton sl = SettingLoader() self.settings = sl.settings self.brain = BrainLoader().brain # keep in memory the order to process self.order_to_process = None # get the player instance self.player_instance = PlayerLauncher.get_player( settings=self.settings) # save an instance of the trigger self.trigger_instance = None self.trigger_callback_called = False self.skip_trigger = False # keep the status of the trigger, if true we can skip it in the statue machine # save the current order listener self.order_listener = None self.order_listener_callback_called = False # Initialize the state machine self.machine = Machine(model=self, states=Order.states, initial='init', queued=True) # define transitions self.machine.add_transition('start_trigger', ['init', 'analysing_order'], 'starting_trigger') self.machine.add_transition('wait_trigger_callback', 'starting_trigger', 'waiting_for_trigger_callback') self.machine.add_transition('stop_trigger', 'waiting_for_trigger_callback', 'stopping_trigger') self.machine.add_transition('wait_for_order', 'stopping_trigger', 'waiting_for_order_listener_callback') self.machine.add_transition('analyse_order', 'waiting_for_order_listener_callback', 'analysing_order') self.machine.add_transition('start_order_listener', 'analysing_order', 'start_order_listener') self.machine.add_ordered_transitions() # add method which are called when changing state self.machine.on_enter_starting_trigger('start_trigger_process') self.machine.on_enter_waiting_for_trigger_callback( 'waiting_for_trigger_callback_thread') self.machine.on_enter_stopping_trigger('stop_trigger_process') self.machine.on_enter_start_order_listener( 'start_order_listener_thread') self.machine.on_enter_waiting_for_order_listener_callback( 'waiting_for_order_listener_callback_thread') self.machine.on_enter_analysing_order('analysing_order_thread') def run(self): # run hook on_start HookManager.on_start() self.start_trigger() def start_trigger_process(self): """ This function will start the trigger thread that listen for the hotword """ logger.debug("[Order] Entering state: %s" % self.state) HookManager.on_waiting_for_trigger() self.trigger_instance = TriggerLauncher.get_trigger( settings=self.settings, callback=self.trigger_callback) self.trigger_callback_called = False self.trigger_instance.daemon = True # Wait that the kalliope trigger is pronounced by the user self.trigger_instance.start() self.next_state() def waiting_for_trigger_callback_thread(self): """ Method to print in debug that the main process is waiting for a trigger detection """ logger.debug("[Order] Entering state: %s" % self.state) if self.settings.options.deaf: # the user asked to deaf inside the deaf neuron Utils.print_info("Kalliope is deaf") self.trigger_instance.pause() else: Utils.print_info("Waiting for trigger detection") # this loop is used to keep the main thread alive while not self.trigger_callback_called: sleep(0.1) # if here, then the trigger has been called HookManager.on_triggered() self.next_state() def waiting_for_order_listener_callback_thread(self): """ Method to print in debug that the main process is waiting for an order to analyse """ logger.debug("[Order] Entering state: %s" % self.state) # this loop is used to keep the main thread alive while not self.order_listener_callback_called: sleep(0.1) # TODO on end listening here self.next_state() def trigger_callback(self): """ we have detected the hotword, we can now pause the Trigger for a while The user can speak out loud his order during this time. """ logger.debug( "[Order] Trigger callback called, switching to the next state") self.trigger_callback_called = True def stop_trigger_process(self): """ The trigger has been awaken, we don't needed it anymore :return: """ logger.debug("[Order] Entering state: %s" % self.state) self.trigger_instance.stop() self.next_state() def start_order_listener_thread(self): """ Start the STT engine thread """ logger.debug("[Order] Entering state: %s" % self.state) HookManager.on_start_listening() # start listening for an order self.order_listener_callback_called = False self.order_listener = OrderListener( callback=self.order_listener_callback) self.order_listener.daemon = True self.order_listener.start() self.next_state() def order_listener_callback(self, order): """ Receive an order, try to retrieve it in the brain.yml to launch to attached plugins :param order: the sentence received :type order: str """ logger.debug( "[Order] Order listener callback called. Order to process: %s" % order) HookManager.on_stop_listening() self.order_to_process = order self.order_listener_callback_called = True # save in kalliope memory the last order Cortex.save('kalliope_last_order', order) def analysing_order_thread(self): """ Start the order analyser with the caught order to process """ if self.order_to_process is None or self.order_to_process == "": logger.debug("[Order] No audio caught from analysing_order_thread") HookManager.on_stt_error() else: logger.debug("[Order] order in analysing_order_thread %s" % self.order_to_process) SynapseLauncher.run_matching_synapse_from_order( self.order_to_process, self.brain, self.settings, is_api_call=False) if self.skip_trigger: self.start_order_listener() else: self.start_trigger() def on_notification_received(self, notification=None, payload=None): logger.debug( "[Order] received notification, notification: %s, payload: %s" % (notification, payload)) if notification == "skip_trigger": if "status" in payload: if payload["status"] == "True": logger.debug("[Order] switch signals to True") self.skip_trigger = True if payload["status"] == "False": logger.debug("[Order] switch signals to False") self.skip_trigger = False
class Order(Thread): states = ['init', 'starting_trigger', 'playing_ready_sound', 'waiting_for_trigger_callback', 'stopping_trigger', 'playing_wake_up_answer', 'start_order_listener', 'waiting_for_order_listener_callback', 'analysing_order'] def __init__(self): super(Order, self).__init__() Utils.print_info('Starting voice order manager') # load settings and brain from singleton sl = SettingLoader() self.settings = sl.settings self.brain = BrainLoader().get_brain() # keep in memory the order to process self.order_to_process = None # get the player instance self.player_instance = PlayerLauncher.get_player(settings=self.settings) # save an instance of the trigger self.trigger_instance = None self.trigger_callback_called = False self.is_trigger_muted = False # save the current order listener self.order_listener = None self.order_listener_callback_called = False # boolean used to know id we played the on ready notification at least one time self.on_ready_notification_played_once = False # rpi setting for led and mute button self.init_rpi_utils() # Initialize the state machine self.machine = Machine(model=self, states=Order.states, initial='init', queued=True) # define transitions self.machine.add_transition('start_trigger', ['init', 'analysing_order'], 'starting_trigger') self.machine.add_transition('play_ready_sound', 'starting_trigger', 'playing_ready_sound') self.machine.add_transition('wait_trigger_callback', 'playing_ready_sound', 'waiting_for_trigger_callback') self.machine.add_transition('stop_trigger', 'waiting_for_trigger_callback', 'stopping_trigger') self.machine.add_transition('play_wake_up_answer', 'stopping_trigger', 'playing_wake_up_answer') self.machine.add_transition('wait_for_order', 'playing_wake_up_answer', 'waiting_for_order_listener_callback') self.machine.add_transition('analyse_order', 'playing_wake_up_answer', 'analysing_order') self.machine.add_ordered_transitions() # add method which are called when changing state self.machine.on_enter_starting_trigger('start_trigger_process') self.machine.on_enter_playing_ready_sound('play_ready_sound_process') self.machine.on_enter_waiting_for_trigger_callback('waiting_for_trigger_callback_thread') self.machine.on_enter_playing_wake_up_answer('play_wake_up_answer_thread') self.machine.on_enter_stopping_trigger('stop_trigger_process') self.machine.on_enter_start_order_listener('start_order_listener_thread') self.machine.on_enter_waiting_for_order_listener_callback('waiting_for_order_listener_callback_thread') self.machine.on_enter_analysing_order('analysing_order_thread') def run(self): self.start_trigger() def start_trigger_process(self): """ This function will start the trigger thread that listen for the hotword """ logger.debug("[MainController] Entering state: %s" % self.state) self.trigger_instance = TriggerLauncher.get_trigger(settings=self.settings, callback=self.trigger_callback) self.trigger_callback_called = False self.trigger_instance.daemon = True # Wait that the kalliope trigger is pronounced by the user self.trigger_instance.start() self.next_state() def play_ready_sound_process(self): """ Play a sound when Kalliope is ready to be awaken at the first start """ logger.debug("[MainController] Entering state: %s" % self.state) if (not self.on_ready_notification_played_once and self.settings.play_on_ready_notification == "once") or \ self.settings.play_on_ready_notification == "always": # we remember that we played the notification one time self.on_ready_notification_played_once = True # here we tell the user that we are listening if self.settings.on_ready_answers is not None: Say(message=self.settings.on_ready_answers) elif self.settings.on_ready_sounds is not None: random_sound_to_play = self._get_random_sound(self.settings.on_ready_sounds) self.player_instance.play(random_sound_to_play) self.next_state() def waiting_for_trigger_callback_thread(self): """ Method to print in debug that the main process is waiting for a trigger detection """ logger.debug("[MainController] Entering state: %s" % self.state) if self.is_trigger_muted: # the user asked to mute inside the mute neuron Utils.print_info("Kalliope is muted") self.trigger_instance.pause() else: Utils.print_info("Waiting for trigger detection") # this loop is used to keep the main thread alive while not self.trigger_callback_called: sleep(0.1) self.next_state() def waiting_for_order_listener_callback_thread(self): """ Method to print in debug that the main process is waiting for an order to analyse """ logger.debug("[MainController] Entering state: %s" % self.state) # this loop is used to keep the main thread alive while not self.order_listener_callback_called: sleep(0.1) if self.settings.rpi_settings: if self.settings.rpi_settings.pin_led_listening: RpiUtils.switch_pin_to_off(self.settings.rpi_settings.pin_led_listening) self.next_state() def trigger_callback(self): """ we have detected the hotword, we can now pause the Trigger for a while The user can speak out loud his order during this time. """ logger.debug("[MainController] Trigger callback called, switching to the next state") self.trigger_callback_called = True def stop_trigger_process(self): """ The trigger has been awaken, we don't needed it anymore :return: """ logger.debug("[MainController] Entering state: %s" % self.state) self.trigger_instance.stop() self.next_state() def start_order_listener_thread(self): """ Start the STT engine thread """ logger.debug("[MainController] Entering state: %s" % self.state) # start listening for an order self.order_listener_callback_called = False self.order_listener = OrderListener(callback=self.order_listener_callback) self.order_listener.daemon = True self.order_listener.start() self.next_state() def play_wake_up_answer_thread(self): """ Play a sound or make Kalliope say something to notify the user that she has been awaken and now waiting for order """ logger.debug("[MainController] Entering state: %s" % self.state) # if random wake answer sentence are present, we play this if self.settings.random_wake_up_answers is not None: Say(message=self.settings.random_wake_up_answers) else: random_sound_to_play = self._get_random_sound(self.settings.random_wake_up_sounds) self.player_instance.play(random_sound_to_play) self.next_state() def order_listener_callback(self, order): """ Receive an order, try to retrieve it in the brain.yml to launch to attached plugins :param order: the sentence received :type order: str """ logger.debug("[MainController] Order listener callback called. Order to process: %s" % order) self.order_to_process = order self.order_listener_callback_called = True def analysing_order_thread(self): """ Start the order analyser with the caught order to process """ logger.debug("[MainController] order in analysing_order_thread %s" % self.order_to_process) SynapseLauncher.run_matching_synapse_from_order(self.order_to_process, self.brain, self.settings, is_api_call=False) # return to the state "unpausing_trigger" self.start_trigger() @staticmethod def _get_random_sound(random_wake_up_sounds): """ Return a path of a sound to play If the path is absolute, test if file exist If the path is relative, we check if the file exist in the sound folder :param random_wake_up_sounds: List of wake_up sounds :return: path of a sound to play """ # take first randomly a path random_path = random.choice(random_wake_up_sounds) logger.debug("[MainController] Selected sound: %s" % random_path) return Utils.get_real_file_path(random_path) def set_mute_status(self, muted=False): """ Define is the trigger is listening or not :param muted: Boolean. If true, kalliope is muted """ logger.debug("[MainController] Mute button pressed. Switch trigger process to muted: %s" % muted) if muted: self.trigger_instance.pause() self.is_trigger_muted = True Utils.print_info("Kalliope now muted") else: self.trigger_instance.unpause() self.is_trigger_muted = False Utils.print_info("Kalliope now listening for trigger detection") def get_mute_status(self): """ return the current state of the trigger (muted or not) :return: Boolean """ return self.is_trigger_muted def init_rpi_utils(self): """ Start listening on GPIO if defined in settings """ if self.settings.rpi_settings: # the user set GPIO pin, we need to instantiate the RpiUtils class in order to setup GPIO rpi_utils = RpiUtils(self.settings.rpi_settings, self.set_mute_status) if self.settings.rpi_settings.pin_mute_button: # start the listening for button pressed thread only if the user set a pin rpi_utils.daemon = True rpi_utils.start() # switch high the start led, as kalliope is started. Only if the setting exist if self.settings.rpi_settings: if self.settings.rpi_settings.pin_led_started: logger.debug("[MainController] Switching pin_led_started to ON") RpiUtils.switch_pin_to_on(self.settings.rpi_settings.pin_led_started)
class Order(Thread): states = [ 'init', 'starting_trigger', 'waiting_for_trigger_callback', 'stopping_trigger', 'start_order_listener', 'waiting_for_order_listener_callback', 'analysing_order' ] def __init__(self): super(Order, self).__init__() Utils.print_info('Starting order signal') # load settings and brain from singleton sl = SettingLoader() self.settings = sl.settings self.brain = BrainLoader().brain # keep in memory the order to process self.order_to_process = None # get the player instance self.player_instance = PlayerLauncher.get_player( settings=self.settings) # save an instance of the trigger self.trigger_instance = None self.trigger_callback_called = False self.is_trigger_muted = False # If kalliope is asked to start muted if self.settings.start_options['muted'] is True: self.is_trigger_muted = True # save the current order listener self.order_listener = None self.order_listener_callback_called = False # Initialize the state machine self.machine = Machine(model=self, states=Order.states, initial='init', queued=True) # define transitions self.machine.add_transition('start_trigger', ['init', 'analysing_order'], 'starting_trigger') self.machine.add_transition('wait_trigger_callback', 'starting_trigger', 'waiting_for_trigger_callback') self.machine.add_transition('stop_trigger', 'waiting_for_trigger_callback', 'stopping_trigger') self.machine.add_transition('wait_for_order', 'stopping_trigger', 'waiting_for_order_listener_callback') self.machine.add_transition('analyse_order', 'waiting_for_order_listener_callback', 'analysing_order') self.machine.add_ordered_transitions() # add method which are called when changing state self.machine.on_enter_starting_trigger('start_trigger_process') self.machine.on_enter_waiting_for_trigger_callback( 'waiting_for_trigger_callback_thread') self.machine.on_enter_stopping_trigger('stop_trigger_process') self.machine.on_enter_start_order_listener( 'start_order_listener_thread') self.machine.on_enter_waiting_for_order_listener_callback( 'waiting_for_order_listener_callback_thread') self.machine.on_enter_analysing_order('analysing_order_thread') def run(self): # run hook on_start HookManager.on_start() self.start_trigger() def start_trigger_process(self): """ This function will start the trigger thread that listen for the hotword """ logger.debug("[MainController] Entering state: %s" % self.state) HookManager.on_waiting_for_trigger() self.trigger_instance = TriggerLauncher.get_trigger( settings=self.settings, callback=self.trigger_callback) self.trigger_callback_called = False self.trigger_instance.daemon = True # Wait that the kalliope trigger is pronounced by the user self.trigger_instance.start() self.next_state() def waiting_for_trigger_callback_thread(self): """ Method to print in debug that the main process is waiting for a trigger detection """ logger.debug("[MainController] Entering state: %s" % self.state) if self.is_trigger_muted: # the user asked to mute inside the mute neuron Utils.print_info("Kalliope is muted") self.trigger_instance.pause() else: Utils.print_info("Waiting for trigger detection") # this loop is used to keep the main thread alive while not self.trigger_callback_called: sleep(0.1) # if here, then the trigger has been called HookManager.on_triggered() self.next_state() def waiting_for_order_listener_callback_thread(self): """ Method to print in debug that the main process is waiting for an order to analyse """ logger.debug("[MainController] Entering state: %s" % self.state) # this loop is used to keep the main thread alive while not self.order_listener_callback_called: sleep(0.1) # TODO on end listening here self.next_state() def trigger_callback(self): """ we have detected the hotword, we can now pause the Trigger for a while The user can speak out loud his order during this time. """ logger.debug( "[MainController] Trigger callback called, switching to the next state" ) self.trigger_callback_called = True def stop_trigger_process(self): """ The trigger has been awaken, we don't needed it anymore :return: """ logger.debug("[MainController] Entering state: %s" % self.state) self.trigger_instance.stop() self.next_state() def start_order_listener_thread(self): """ Start the STT engine thread """ logger.debug("[MainController] Entering state: %s" % self.state) HookManager.on_start_listening() # start listening for an order self.order_listener_callback_called = False self.order_listener = OrderListener( callback=self.order_listener_callback) self.order_listener.daemon = True self.order_listener.start() self.next_state() def order_listener_callback(self, order): """ Receive an order, try to retrieve it in the brain.yml to launch to attached plugins :param order: the sentence received :type order: str """ logger.debug( "[MainController] Order listener callback called. Order to process: %s" % order) HookManager.on_stop_listening() self.order_to_process = order self.order_listener_callback_called = True def analysing_order_thread(self): """ Start the order analyser with the caught order to process """ logger.debug("[MainController] order in analysing_order_thread %s" % self.order_to_process) SynapseLauncher.run_matching_synapse_from_order(self.order_to_process, self.brain, self.settings, is_api_call=False) # return to the state "unpausing_trigger" self.start_trigger() def set_mute_status(self, muted=False): """ Define is the trigger is listening or not :param muted: Boolean. If true, kalliope is muted """ logger.debug( "[MainController] Mute button pressed. Switch trigger process to muted: %s" % muted) if muted: self.trigger_instance.pause() self.is_trigger_muted = True Utils.print_info("Kalliope now muted") HookManager.on_mute() else: self.trigger_instance.unpause() self.is_trigger_muted = False Utils.print_info("Kalliope now listening for trigger detection") HookManager.on_unmute() def get_mute_status(self): """ return the current state of the trigger (muted or not) :return: Boolean """ return self.is_trigger_muted
def run_synapse_by_audio(self): """ Give an order to Kalliope with an audio file Test with curl curl -i --user admin:secret -X POST http://localhost:5000/synapses/start/audio -F "file=@/path/to/input.wav" With mute flag curl -i --user admin:secret -X POST http://localhost:5000/synapses/start/audio -F "file=@path/to/file.wav" -F mute="true" :return: """ # check if the post request has the file part if 'file' not in request.files: data = { "error": "No file provided" } return jsonify(error=data), 400 uploaded_file = request.files['file'] # if user does not select file, browser also # submit a empty part without filename if uploaded_file.filename == '': data = { "error": "No file provided" } return jsonify(error=data), 400 # Store the mute value, then apply depending of the request parameters old_mute_value = self.settings.options.mute if request.form.get("mute"): SettingEditor.set_mute_status(mute=Utils.str_to_bool(request.form.get("mute"))) # save the file filename = secure_filename(uploaded_file.filename) base_path = os.path.join(self.app.config['UPLOAD_FOLDER']) uploaded_file.save(os.path.join(base_path, filename)) # now start analyse the audio with STT engine audio_path = base_path + os.sep + filename logger.debug("[FlaskAPI] run_synapse_by_audio: with file path %s" % audio_path) if not self.allowed_file(audio_path): audio_path = self._convert_to_wav(audio_file_path=audio_path) ol = OrderListener(callback=self.audio_analyser_callback, audio_file_path=audio_path) ol.start() ol.join() # wait the Order Analyser processing. We need to wait in this thread to keep the context while not self.order_analyser_return: time.sleep(0.1) self.order_analyser_return = False if self.api_response is not None and self.api_response: data = jsonify(self.api_response) self.api_response = None logger.debug("[FlaskAPI] run_synapse_by_audio: data %s" % data) if request.form.get("mute"): SettingEditor.set_mute_status(mute=old_mute_value) return data, 201 else: data = { "error": "The given order doesn't match any synapses" } if request.form.get("mute"): SettingEditor.set_mute_status(mute=old_mute_value) return jsonify(error=data), 400
class Order(SignalModule, Thread): states = [ 'init', 'starting_trigger', 'unpausing_trigger', 'waiting_for_trigger_callback', 'pausing_trigger', 'start_order_listener', 'waiting_for_order_listener_callback', 'analysing_order' ] def __init__(self): super(SignalModule, self).__init__() Thread.__init__(self, name=Order) Utils.print_info('Starting order signal') # load settings and brain from singleton sl = SettingLoader() self.settings = sl.settings self.brain = BrainLoader().brain # keep in memory the order to process self.order_to_process = None # get the player instance self.player_instance = PlayerLauncher.get_player( settings=self.settings) # save an instance of the trigger self.trigger_instance = None self.trigger_callback_called = False # variable from notifications self.skip_trigger = False # keep the status of the trigger, if true we can skip it in the statue machine self.counter_max_retry = 0 # 0 means disabled # save the current order listener self.order_listener = None self.order_listener_callback_called = False # Initialize the state machine self.machine = Machine(model=self, states=Order.states, initial='init', queued=True) # define transitions self.machine.add_transition('start_trigger', 'init', 'starting_trigger') self.machine.add_transition('unpause_trigger', 'analysing_order', 'unpausing_trigger') self.machine.add_transition('wait_trigger_callback', 'unpausing_trigger', 'waiting_for_trigger_callback') self.machine.add_transition('pause_trigger', 'waiting_for_trigger_callback', 'pausing_trigger') self.machine.add_transition('wait_for_order', 'pausing_trigger', 'waiting_for_order_listener_callback') self.machine.add_transition('analyse_order', 'waiting_for_order_listener_callback', 'analysing_order') self.machine.add_transition('start_order_listener', 'analysing_order', 'start_order_listener') self.machine.add_ordered_transitions() # add method which are called when changing state self.machine.on_enter_starting_trigger('start_trigger_process') self.machine.on_enter_unpausing_trigger('unpausing_trigger_process') self.machine.on_enter_waiting_for_trigger_callback( 'waiting_for_trigger_callback_thread') self.machine.on_enter_pausing_trigger('pause_trigger_process') self.machine.on_enter_start_order_listener( 'start_order_listener_thread') self.machine.on_enter_waiting_for_order_listener_callback( 'waiting_for_order_listener_callback_thread') self.machine.on_enter_analysing_order('analysing_order_thread') def run(self): # run hook on_start self.start_trigger() def start_trigger_process(self): """ This function will start the trigger thread that listen for the hotword """ logger.debug("[Order] Entering state: %s" % self.state) self.trigger_instance = TriggerLauncher.get_trigger( settings=self.settings, callback=self.trigger_callback) self.trigger_callback_called = False self.trigger_instance.daemon = True # Wait that the kalliope trigger is pronounced by the user self.trigger_instance.start() HookManager.on_start() self.next_state() def unpausing_trigger_process(self): """ If the trigger was in pause, this method will unpause it to listen again for the hotword """ logger.debug("Entering state: %s" % self.state) HookManager.on_waiting_for_trigger() self.trigger_instance.unpause() self.trigger_callback_called = False self.next_state() #self.waiting_for_trigger_callback_thread() def waiting_for_trigger_callback_thread(self): """ Method to print in debug that the main process is waiting for a trigger detection """ logger.debug("[Order] Entering state: %s" % self.state) if self.settings.options.deaf: # the user asked to deaf inside the deaf neuron Utils.print_info("Kalliope is deaf") self.trigger_instance.pause() else: Utils.print_info("Waiting for trigger detection") # this loop is used to keep the main thread alive while not self.trigger_callback_called: sleep(0.1) # if here, then the trigger has been called HookManager.on_triggered() self.next_state() def waiting_for_order_listener_callback_thread(self): """ Method to print in debug that the main process is waiting for an order to analyse """ logger.debug("[Order] Entering state: %s" % self.state) # this loop is used to keep the main thread alive while not self.order_listener_callback_called: sleep(0.1) # TODO on end listening here self.next_state() def trigger_callback(self): """ we have detected the hotword, we can now pause the Trigger for a while The user can speak out loud his order during this time. """ logger.debug( "[Order] Trigger callback called, switching to the next state") self.trigger_callback_called = True def pause_trigger_process(self): """ The trigger has been awaken, we pause it :return: """ logger.debug("[Order] Entering state: %s" % self.state) self.trigger_instance.pause() self.next_state() def start_order_listener_thread(self): """ Start the STT engine thread """ logger.debug("[Order] Entering state: %s" % self.state) # start listening for an order HookManager.on_start_listening() self.order_listener_callback_called = False self.order_listener = OrderListener( callback=self.order_listener_callback) self.order_listener.daemon = True self.order_listener.start() self.next_state() def order_listener_callback(self, order): """ Receive an order, try to retrieve it in the brain.yml to launch to attached plugins :param order: the sentence received :type order: str """ logger.debug( "[Order] Order listener callback called. Order to process: %s" % order) HookManager.on_stop_listening() self.order_to_process = order self.order_listener_callback_called = True # save in kalliope memory the last order Cortex.save('kalliope_last_order', order) def analysing_order_thread(self): """ Start the order analyser with the caught order to process """ if self.order_to_process is None or self.order_to_process == "": logger.debug("[Order] No audio caught from analysing_order_thread") HookManager.on_stt_error() else: logger.debug("[Order] order in analysing_order_thread %s" % self.order_to_process) SynapseLauncher.run_matching_synapse_from_order( self.order_to_process, self.brain, self.settings, is_api_call=False) if self.skip_trigger: self.start_order_listener() else: self.unpause_trigger() def on_notification_received(self, notification=None, payload=None): logger.debug( "[Order] received notification, notification: %s, payload: %s" % (notification, payload)) # skip_trigger: change the trigger status if notification == "skip_trigger": self.switch_trigger(payload) if notification == "skip_trigger_max_retry": self.set_counter_max_retry(payload) if notification == "skip_trigger_decrease_max_retry": self.decrease_max_retry() def set_counter_max_retry(self, payload): """ set a 'counter_max_retry' for max retry before switching automatically the skip_trigger flag to False :param payload: payload that contains the max_retry counter value to set """ if "max_retry" in payload: if payload["max_retry"] > 0: self.counter_max_retry = payload["max_retry"] logger.debug("[Order] max_retry set to %s" % self.counter_max_retry) def decrease_max_retry(self): """ will decrease the current value of 'counter_max_retry' if the current is > 0 If the new value == 0, then the skip_trigger flag is automatically switched to False """ logger.debug("[Order] current max_retry: %s" % self.counter_max_retry) if self.counter_max_retry > 0: self.counter_max_retry = self.counter_max_retry - 1 logger.debug("[Order] new max_retry value after decrease: %s" % self.counter_max_retry) if self.counter_max_retry == 0: logger.debug( "[Order] max_retry reached '0'. Set skip_trigger to False") # the counter raised 0, we can stop to skip the trigger self.skip_trigger = False else: logger.debug( "[Order] cannot decrease max_retry because current value is <= 0" ) def switch_trigger(self, payload): """ switch the skip_trigger flag :param payload: payload dict that contains the new status of the skip_trigger flag """ if "status" in payload: status = Utils.str_to_bool(payload["status"]) if status: logger.debug("[Order] switch signals to True") self.skip_trigger = True else: logger.debug("[Order] switch signals to False") self.skip_trigger = False
class Order(SignalModule, Thread): states = ['init', 'starting_trigger', 'waiting_for_trigger_callback', 'stopping_trigger', 'start_order_listener', 'waiting_for_order_listener_callback', 'analysing_order'] def __init__(self): super(SignalModule, self).__init__() Thread.__init__(self, name=Order) Utils.print_info('Starting order signal') # load settings and brain from singleton sl = SettingLoader() self.settings = sl.settings self.brain = BrainLoader().brain # keep in memory the order to process self.order_to_process = None # get the player instance self.player_instance = PlayerLauncher.get_player(settings=self.settings) # save an instance of the trigger self.trigger_instance = None self.trigger_callback_called = False self.skip_trigger = False # keep the status of the trigger, if true we can skip it in the statue machine # save the current order listener self.order_listener = None self.order_listener_callback_called = False # Initialize the state machine self.machine = Machine(model=self, states=Order.states, initial='init', queued=True) # define transitions self.machine.add_transition('start_trigger', ['init', 'analysing_order'], 'starting_trigger') self.machine.add_transition('wait_trigger_callback', 'starting_trigger', 'waiting_for_trigger_callback') self.machine.add_transition('stop_trigger', 'waiting_for_trigger_callback', 'stopping_trigger') self.machine.add_transition('wait_for_order', 'stopping_trigger', 'waiting_for_order_listener_callback') self.machine.add_transition('analyse_order', 'waiting_for_order_listener_callback', 'analysing_order') self.machine.add_transition('start_order_listener', 'analysing_order', 'start_order_listener') self.machine.add_ordered_transitions() # add method which are called when changing state self.machine.on_enter_starting_trigger('start_trigger_process') self.machine.on_enter_waiting_for_trigger_callback('waiting_for_trigger_callback_thread') self.machine.on_enter_stopping_trigger('stop_trigger_process') self.machine.on_enter_start_order_listener('start_order_listener_thread') self.machine.on_enter_waiting_for_order_listener_callback('waiting_for_order_listener_callback_thread') self.machine.on_enter_analysing_order('analysing_order_thread') def run(self): # run hook on_start HookManager.on_start() self.start_trigger() def start_trigger_process(self): """ This function will start the trigger thread that listen for the hotword """ logger.debug("[Order] Entering state: %s" % self.state) HookManager.on_waiting_for_trigger() self.trigger_instance = TriggerLauncher.get_trigger(settings=self.settings, callback=self.trigger_callback) self.trigger_callback_called = False self.trigger_instance.daemon = True # Wait that the kalliope trigger is pronounced by the user self.trigger_instance.start() self.next_state() def waiting_for_trigger_callback_thread(self): """ Method to print in debug that the main process is waiting for a trigger detection """ logger.debug("[Order] Entering state: %s" % self.state) if self.settings.options.deaf: # the user asked to deaf inside the deaf neuron Utils.print_info("Kalliope is deaf") self.trigger_instance.pause() else: Utils.print_info("Waiting for trigger detection") # this loop is used to keep the main thread alive while not self.trigger_callback_called: sleep(0.1) # if here, then the trigger has been called HookManager.on_triggered() self.next_state() def waiting_for_order_listener_callback_thread(self): """ Method to print in debug that the main process is waiting for an order to analyse """ logger.debug("[Order] Entering state: %s" % self.state) # this loop is used to keep the main thread alive while not self.order_listener_callback_called: sleep(0.1) # TODO on end listening here self.next_state() def trigger_callback(self): """ we have detected the hotword, we can now pause the Trigger for a while The user can speak out loud his order during this time. """ logger.debug("[Order] Trigger callback called, switching to the next state") self.trigger_callback_called = True def stop_trigger_process(self): """ The trigger has been awaken, we don't needed it anymore :return: """ logger.debug("[Order] Entering state: %s" % self.state) self.trigger_instance.stop() self.next_state() def start_order_listener_thread(self): """ Start the STT engine thread """ logger.debug("[Order] Entering state: %s" % self.state) HookManager.on_start_listening() # start listening for an order self.order_listener_callback_called = False self.order_listener = OrderListener(callback=self.order_listener_callback) self.order_listener.daemon = True self.order_listener.start() self.next_state() def order_listener_callback(self, order): """ Receive an order, try to retrieve it in the brain.yml to launch to attached plugins :param order: the sentence received :type order: str """ logger.debug("[Order] Order listener callback called. Order to process: %s" % order) HookManager.on_stop_listening() self.order_to_process = order self.order_listener_callback_called = True # save in kalliope memory the last order Cortex.save('kalliope_last_order', order) def analysing_order_thread(self): """ Start the order analyser with the caught order to process """ if self.order_to_process is None or self.order_to_process == "": logger.debug("[Order] No audio caught from analysing_order_thread") HookManager.on_stt_error() else: logger.debug("[Order] order in analysing_order_thread %s" % self.order_to_process) SynapseLauncher.run_matching_synapse_from_order(self.order_to_process, self.brain, self.settings, is_api_call=False) if self.skip_trigger: self.start_order_listener() else: self.start_trigger() def on_notification_received(self, notification=None, payload=None): logger.debug("[Order] received notification, notification: %s, payload: %s" % (notification, payload)) if notification == "skip_trigger": if "status" in payload: if payload["status"] == "True": logger.debug("[Order] switch signals to True") self.skip_trigger = True if payload["status"] == "False": logger.debug("[Order] switch signals to False") self.skip_trigger = False
def run_synapse_by_audio(self): """ Give an order to Kalliope with an audio file Test with curl curl -i --user admin:secret -X POST http://localhost:5000/synapses/start/audio -F "file=@/path/to/input.wav" With mute flag curl -i --user admin:secret -X POST http://localhost:5000/synapses/start/audio -F "file=@path/to/file.wav" -F mute="true" :return: """ # check if the post request has the file part if 'file' not in request.files: data = { "error": "No file provided" } return jsonify(error=data), 400 uploaded_file = request.files['file'] # if user does not select file, browser also # submit a empty part without filename if uploaded_file.filename == '': data = { "error": "No file provided" } return jsonify(error=data), 400 # Store the mute value, then apply depending of the request parameters old_mute_value = self.settings.options.mute if request.form.get("mute"): SettingEditor.set_mute_status(mute=Utils.str_to_bool(request.form.get("mute"))) # save the file filename = secure_filename(uploaded_file.filename) base_path = os.path.join(self.app.config['UPLOAD_FOLDER']) uploaded_file.save(os.path.join(base_path, filename)) # now start analyse the audio with STT engine audio_path = base_path + os.sep + filename logger.debug("[FlaskAPI] run_synapse_by_audio: with file path %s" % audio_path) if not self.allowed_file(audio_path): audio_path = self._convert_to_wav(audio_file_path=audio_path) ol = OrderListener(callback=self.audio_analyser_callback, audio_file_path=audio_path) ol.start() ol.join() # wait the Order Analyser processing. We need to wait in this thread to keep the context while not self.order_analyser_return: time.sleep(0.1) self.order_analyser_return = False if self.api_response is not None and self.api_response: data = jsonify(self.api_response) self.api_response = None logger.debug("[FlaskAPI] run_synapse_by_audio: data %s" % data) if request.form.get("mute"): SettingEditor.set_mute_status(mute=old_mute_value) return data, 201 else: data = { "error": "The given order doesn't match any synapses" } if request.form.get("mute"): SettingEditor.set_mute_status(mute=old_mute_value) return jsonify(error=data), 400