order.py 7.9 KB

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