order.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. import logging
  2. from threading import Thread
  3. from time import sleep
  4. from kalliope.core.SynapseLauncher import SynapseLauncher
  5. from kalliope.core.OrderListener import OrderListener
  6. from kalliope import Utils, BrainLoader
  7. from kalliope.core.TriggerLauncher import TriggerLauncher
  8. from transitions import Machine
  9. from kalliope.core.PlayerLauncher import PlayerLauncher
  10. from kalliope.core.ConfigurationManager import SettingLoader
  11. from kalliope.core.HookManager import HookManager
  12. logging.basicConfig()
  13. logger = logging.getLogger("kalliope")
  14. class Order(Thread):
  15. states = ['init',
  16. 'starting_trigger',
  17. 'waiting_for_trigger_callback',
  18. 'stopping_trigger',
  19. 'start_order_listener',
  20. 'waiting_for_order_listener_callback',
  21. 'analysing_order']
  22. def __init__(self):
  23. super(Order, self).__init__()
  24. Utils.print_info('Starting order signal')
  25. # load settings and brain from singleton
  26. sl = SettingLoader()
  27. self.settings = sl.settings
  28. self.brain = BrainLoader().brain
  29. # keep in memory the order to process
  30. self.order_to_process = None
  31. # get the player instance
  32. self.player_instance = PlayerLauncher.get_player(settings=self.settings)
  33. # save an instance of the trigger
  34. self.trigger_instance = None
  35. self.trigger_callback_called = False
  36. self.is_trigger_muted = False
  37. # If kalliope is asked to start muted
  38. if self.settings.start_options['muted'] is True:
  39. self.is_trigger_muted = True
  40. # save the current order listener
  41. self.order_listener = None
  42. self.order_listener_callback_called = False
  43. # Initialize the state machine
  44. self.machine = Machine(model=self, states=Order.states, initial='init', queued=True)
  45. # define transitions
  46. self.machine.add_transition('start_trigger', ['init', 'analysing_order'], 'starting_trigger')
  47. self.machine.add_transition('wait_trigger_callback', 'starting_trigger', 'waiting_for_trigger_callback')
  48. self.machine.add_transition('stop_trigger', 'waiting_for_trigger_callback', 'stopping_trigger')
  49. self.machine.add_transition('wait_for_order', 'stopping_trigger', 'waiting_for_order_listener_callback')
  50. self.machine.add_transition('analyse_order', 'waiting_for_order_listener_callback', 'analysing_order')
  51. self.machine.add_ordered_transitions()
  52. # add method which are called when changing state
  53. self.machine.on_enter_starting_trigger('start_trigger_process')
  54. self.machine.on_enter_waiting_for_trigger_callback('waiting_for_trigger_callback_thread')
  55. self.machine.on_enter_stopping_trigger('stop_trigger_process')
  56. self.machine.on_enter_start_order_listener('start_order_listener_thread')
  57. self.machine.on_enter_waiting_for_order_listener_callback('waiting_for_order_listener_callback_thread')
  58. self.machine.on_enter_analysing_order('analysing_order_thread')
  59. def run(self):
  60. # run hook on_start
  61. HookManager.on_start()
  62. self.start_trigger()
  63. def start_trigger_process(self):
  64. """
  65. This function will start the trigger thread that listen for the hotword
  66. """
  67. logger.debug("[MainController] Entering state: %s" % self.state)
  68. HookManager.on_waiting_for_trigger()
  69. self.trigger_instance = TriggerLauncher.get_trigger(settings=self.settings, callback=self.trigger_callback)
  70. self.trigger_callback_called = False
  71. self.trigger_instance.daemon = True
  72. # Wait that the kalliope trigger is pronounced by the user
  73. self.trigger_instance.start()
  74. self.next_state()
  75. def waiting_for_trigger_callback_thread(self):
  76. """
  77. Method to print in debug that the main process is waiting for a trigger detection
  78. """
  79. logger.debug("[MainController] Entering state: %s" % self.state)
  80. if self.is_trigger_muted: # the user asked to mute inside the mute neuron
  81. Utils.print_info("Kalliope is muted")
  82. self.trigger_instance.pause()
  83. else:
  84. Utils.print_info("Waiting for trigger detection")
  85. # this loop is used to keep the main thread alive
  86. while not self.trigger_callback_called:
  87. sleep(0.1)
  88. # if here, then the trigger has been called
  89. HookManager.on_triggered()
  90. self.next_state()
  91. def waiting_for_order_listener_callback_thread(self):
  92. """
  93. Method to print in debug that the main process is waiting for an order to analyse
  94. """
  95. logger.debug("[MainController] Entering state: %s" % self.state)
  96. # this loop is used to keep the main thread alive
  97. while not self.order_listener_callback_called:
  98. sleep(0.1)
  99. # TODO on end listening here
  100. self.next_state()
  101. def trigger_callback(self):
  102. """
  103. we have detected the hotword, we can now pause the Trigger for a while
  104. The user can speak out loud his order during this time.
  105. """
  106. logger.debug("[MainController] Trigger callback called, switching to the next state")
  107. self.trigger_callback_called = True
  108. def stop_trigger_process(self):
  109. """
  110. The trigger has been awaken, we don't needed it anymore
  111. :return:
  112. """
  113. logger.debug("[MainController] Entering state: %s" % self.state)
  114. self.trigger_instance.stop()
  115. self.next_state()
  116. def start_order_listener_thread(self):
  117. """
  118. Start the STT engine thread
  119. """
  120. logger.debug("[MainController] Entering state: %s" % self.state)
  121. HookManager.on_start_listening()
  122. # start listening for an order
  123. self.order_listener_callback_called = False
  124. self.order_listener = OrderListener(callback=self.order_listener_callback)
  125. self.order_listener.daemon = True
  126. self.order_listener.start()
  127. self.next_state()
  128. def order_listener_callback(self, order):
  129. """
  130. Receive an order, try to retrieve it in the brain.yml to launch to attached plugins
  131. :param order: the sentence received
  132. :type order: str
  133. """
  134. logger.debug("[MainController] Order listener callback called. Order to process: %s" % order)
  135. HookManager.on_stop_listening()
  136. self.order_to_process = order
  137. self.order_listener_callback_called = True
  138. def analysing_order_thread(self):
  139. """
  140. Start the order analyser with the caught order to process
  141. """
  142. logger.debug("[MainController] order in analysing_order_thread %s" % self.order_to_process)
  143. SynapseLauncher.run_matching_synapse_from_order(self.order_to_process,
  144. self.brain,
  145. self.settings,
  146. is_api_call=False)
  147. # return to the state "unpausing_trigger"
  148. self.start_trigger()
  149. def set_mute_status(self, muted=False):
  150. """
  151. Define is the trigger is listening or not
  152. :param muted: Boolean. If true, kalliope is muted
  153. """
  154. logger.debug("[MainController] Mute button pressed. Switch trigger process to muted: %s" % muted)
  155. if muted:
  156. self.trigger_instance.pause()
  157. self.is_trigger_muted = True
  158. Utils.print_info("Kalliope now muted")
  159. HookManager.on_mute()
  160. else:
  161. self.trigger_instance.unpause()
  162. self.is_trigger_muted = False
  163. Utils.print_info("Kalliope now listening for trigger detection")
  164. HookManager.on_unmute()
  165. def get_mute_status(self):
  166. """
  167. return the current state of the trigger (muted or not)
  168. :return: Boolean
  169. """
  170. return self.is_trigger_muted