Browse Source

Feature/hooks (#388)

* first draft hooks

* fix tests after refactoring

* remove rpi utils + doc hooks

* add tests on hookmanager

* use the brain singleton directly instead of loading again the brain.yml file

* add  lifo manager + debug neuron

* [Review] Fix typos

* fix afte review
Nicolas Marcq 7 years ago
parent
commit
c1f7a1fc27

+ 85 - 124
Docs/settings.md

@@ -212,94 +212,118 @@ text_to_speech:
 
 
 Some arguments are required, some other optional, please refer to the [TTS documentation](tts.md) to know available parameters for each supported TTS.
 Some arguments are required, some other optional, please refer to the [TTS documentation](tts.md) to know available parameters for each supported TTS.
 
 
-## Wake up answers configuration
+## Hooks
 
 
-### random_wake_up_answers
-When Kalliope detects your trigger/hotword/magic word, it lets you know that it's operational and now waiting for order. It's done by answering randomly
-one of the sentences provided in the variable random_wake_up_answers.
+Hooking allow to bind actions to events based on the lifecycle of Kalliope. 
+For example, it's useful to know when Kalliope has detected the hotword from the trigger engine and make her spell out loud that she's ready to listen your order.
 
 
-This variable must contain a list of strings as shown bellow
+To use a hook, attach the name of the hook to a synapse (or list of synapse) which exists in your brain.
+
+Syntax:
 ```yml
 ```yml
-random_wake_up_answers:
-  - "You sentence"
-  - "Another sentence"
+hooks:
+  hook_name1: synapse_name
+  hook_name2:
+    - synapse_name_1
+    - synapse_name_2
 ```
 ```
 
 
-E.g
+E.g.
 ```yml
 ```yml
-random_wake_up_answers:
-  - "Yes sir?"
-  - "I'm listening"
-  - "Sir?"
-  - "What can I do for you?"
-  - "Listening"
-  - "Yes?"
+hooks:
+  on_start: "on-start-synapse"
 ```
 ```
 
 
-### random_wake_up_sounds
-You can play a sound when Kalliope detects the hotword/trigger instead of saying something from
-the `random_wake_up_answers`.
-Place here a list of full paths of the sound files you want to use. Otherwise, you can use some default sounds provided by Kalliope which you can find in `/usr/lib/kalliope/sounds`.
-By default two file are provided: ding.wav and dong.wav. In all cases, the file must be in `.wav` or `.mp3` format. If more than on file is present in the list,
-Kalliope will select one randomly at each wake up.
+List of available hook
+
+| Hook name              | Description                                                     |
+|------------------------|-----------------------------------------------------------------|
+| on_start               | When kalliope is started. This hook will only be triggered once |
+| on_waiting_for_trigger | When Kalliope waits for the hotword detection                   |
+| on_triggered           | When the hotword has been detected                              |
+| on_start_listening     | When the Speech to Text engine is listening for an order        |
+| on_stop_listening      | When the Speech to Text engine stop listening for an order      |
+| on_order_found         | When the pronounced order has been found in the brain           |
+| on_order_not_found     | When the pronounced order has not been found in the brain       |
+| on_mute                | When Kalliope switches from non muted to muted                  |
+| on_unmute              | When Kalliope switches from muted to non muted                  |
+| on_start_speaking      | When Kalliope starts speaking via the text to speech engine     |
+| on_stop_speaking       | When Kalliope stops speaking                                    |
 
 
+Example: You want to hear a random answer when the hotword has been triggered
+
+**settings.yml**
 ```yml
 ```yml
-random_wake_up_sounds:
-  - "local_file_in_sounds_folder.wav"
-  - "/my/personal/full/path/my_file.mp3"
+hooks:
+  on_triggered: "on-triggered-synapse"
 ```
 ```
 
 
-E.g
+**brain.yml**
 ```yml
 ```yml
-random_wake_up_sounds:
-  - "ding.wav"
-  - "dong.wav"
-  - "/my/personal/full/path/my_file.mp3"
+- name: "on-triggered-synapse"
+  signals: []
+  neurons:
+    - say:
+        message:
+          - "yes sir?"
+          - "I'm listening"
+          - "I'm listening to you"
+          - "sir?"
+          - "what can i do for you?"
+          - "Speaking"
+          - "how can i help you?"
 ```
 ```
 
 
->**Note: ** If you want to use a wake up sound instead of a wake up answer you must comment out the `random_wake_up_answers` section.
-E.g: `# random_wake_up_answers:`
-
-
-## On ready notification
-This section is used to notify the user when Kalliope is waiting for a trigger detection by playing a sound or speak a sentence out loud
-
-### play_on_ready_notification
-This parameter define if you play the on ready notification:
- - `always`: every time Kalliope is ready to be awaken
- - `never`: never play a sound or sentences when kalliope is ready
- - `once`: at the first start of Kalliope
+Example: You want to know that your order has not been found
 
 
-E.g:
+**settings.yml**
 ```yml
 ```yml
-play_on_ready_notification: always
+hooks:
+  on_order_not_found: "order-not-found-synapse"
 ```
 ```
-### on_ready_answers
-The on ready notification can be a sentence. Place here a sentence or a list of sentence. If you set a list, one sentence will be picked up randomly
 
 
-E.g:
+**brain.yml**
 ```yml
 ```yml
-on_ready_answers:
-  - "I'm ready"
-  - "Waiting for order"
+- name: "order-not-found-synapse"
+    signals: []
+    neurons:
+      - say:
+          message:
+            - "I haven't understood"
+            - "I don't know this order"
+            - "Please renew your order"
+            - "Would you please reword your order"
+            - "Can ou please reformulate your order"
+            - "I don't recognize that order"
 ```
 ```
 
 
-### on_ready_sounds
-You can play a sound instead of a sentence.
-Remove the `on_ready_answers` parameters by commenting it out and use this one instead.
-Place here the path of the sound file. Files must be .wav or .mp3 format.
+Example: You are running Kalliope on a Rpi. You've made a script that turn on or off a led.
+You can call this script every time kalliope start or stop speaking
 
 
-E.g:
+**settings.yml**
 ```yml
 ```yml
-on_ready_sounds:
-  - "ding.wav"
-  - "dong.wav"
-  - "/my/personal/full/path/my_file.mp3"
+hooks:
+  on_start_speaking: "turn-on-led"
+  on_stop_speaking: "turn-off-led"
 ```
 ```
 
 
->**Note: ** If you want to use a on ready sound instead of a on ready you must comment out the `random_on_ready_answers` section.
-E.g: `# random_on_ready_answers:`
+**brain.yml**
+```yml
+- name: "turn-on-led"
+  signals: []   
+  neurons:
+    - script:
+        path: "/path/to/script.sh on" 
+        
+- name: "turn-off-led"
+  signals: []   
+  neurons:
+    - script:
+        path: "/path/to/script.sh off"  
+```
 
 
+>**Note:** You cannot use a neurotransmitter neuron inside a synapse called from a hook. 
+You cannot use the "say" neuron inside the "on_start_speaking" or "on_stop_speaking" or it will create an infinite loop
 
 
 ## Rest API
 ## Rest API
 
 
@@ -359,19 +383,6 @@ allowed_cors_origin:
 
 
 Remember that an origin is composed of the scheme (http(s)), the port (eg: 80, 4200,…) and the domain (mydomain.com, localhost).
 Remember that an origin is composed of the scheme (http(s)), the port (eg: 80, 4200,…) and the domain (mydomain.com, localhost).
 
 
-## Default synapse
-
-Run a default [synapse](brain.md) when Kalliope can't find the order in any synapse or if the SST engine haven't understood the order.
-
-```yml
-default_synapse: "synapse-name"
-```
-
-E.g
-```yml
-default_synapse: "Default-response"
-```
-
 ## Resources directory
 ## Resources directory
 
 
 The resources directory is the path where Kalliope will try to load community modules like Neurons, STTs or TTSs.
 The resources directory is the path where Kalliope will try to load community modules like Neurons, STTs or TTSs.
@@ -444,56 +455,6 @@ And a synapse that use this dict:
         - "the number is {{ contacts[contact_to_search] }}"
         - "the number is {{ contacts[contact_to_search] }}"
 ```
 ```
 
 
-## Raspberry LED and mute button
-LEDs connected to GPIO port of your Raspberry can be used to know current status of Kalliope.
-A button can also be added in order to pause the trigger process. Kalliope does not listen for the hotword anymore when pressed.
-
-A Dictionary called `rpi` can be declared which contains pin number to use following the mapping bellow
-
-| Value name        | Description                                                                                                |
-|-------------------|------------------------------------------------------------------------------------------------------------|
-| pin_mute_button   | Pin connected to a mute button. When pressed the trigger process of kalliope is paused                     |
-| pin_led_started   | Pin switched to "on" when Kalliope is running                                                              |
-| pin_led_muted     | Pin switched to "on" when the mute button is pressed                                                       |
-| pin_led_talking   | Pin switched to "on" when Kalliope is talking                                                              |
-| pin_led_listening | Pin switched to "on" when Kalliope is readu to listen an order after a trigger detection ("Say something") |
-
-**Example config**
-```yml
-rpi:
-  pin_mute_button: 6
-  pin_led_started: 5
-  pin_led_muted: 17
-  pin_led_talking: 27
-  pin_led_listening: 22
-```
-
-You can also define a couple led instead of all if you don't use them
-```yml
-rpi:
-  pin_mute_button: 6
-  pin_led_started: 5
-#  pin_led_muted: 17
-#  pin_led_talking: 27
-#  pin_led_listening: 22
-```
-
-**Example circuit**
-
-You will be using one of the ‘ground’ (GND) pins to act like the ‘negative’ or 0 volt ends of a battery. 
-The ‘positive’ end of the battery will be provided by a GPIO pin.
-
-<p align="center">
-    <img style="width: 200px;" src="../images/led_kalliope_circuit.png">
-</p>
-
-
->**Note:** You must ALWAYS use resistors to connect LEDs up to the GPIO pins of the Raspberry Pi. 
-The Raspberry Pi can only supply a small current (about 60mA). T
-he LEDs will want to draw more, and if allowed to they will burn out the Raspberry Pi. 
-Therefore putting the resistors in the circuit will ensure that only this small current will flow and the Pi will not be damaged.
-
-
 ## Start options
 ## Start options
 Options that can be defined when kalliope starts.
 Options that can be defined when kalliope starts.
 
 

+ 5 - 0
Tests/brains/brain_test_api.yml

@@ -26,3 +26,8 @@
           message:
           message:
             - "test message {{ parameter1 }}"
             - "test message {{ parameter1 }}"
 
 
+  - name: "order-not-found-synapse"
+    signals: []
+    neurons:
+      - say:
+          message: "order not found"

+ 18 - 41
Tests/settings/settings_test.yml

@@ -63,43 +63,6 @@ players:
   - pyalsaaudio:
   - pyalsaaudio:
      device: "default"
      device: "default"
 
 
-# ---------------------------
-# Wake up answers
-# ---------------------------
-# When Kalliope detect the hotword/trigger, he will select randomly a phrase in the following list
-# to notify the user that he's listening for orders
-random_wake_up_answers:
-  - "Oui monsieur?"
-
-# You can play a sound when Kalliope detect the hotword/trigger instead of saying something from
-# the `random_wake_up_answers`.
-# Place here the full path of the sound file or just the name of the file in /usr/lib/kalliope/sounds
-# The file must be .wav or .mp3 format. By default two file are provided: ding.wav and dong.wav
-random_wake_up_sounds:
-  - "sounds/ding.wav"
-  - "sounds/dong.wav"
-
-# ---------------------------
-# On ready notification
-# ---------------------------
-# This section is used to notify the user when Kalliope is waiting for a trigger detection by playing a sound or speak a sentence out loud
-
-# This parameter define if you play the on ready answer:
-# - always: every time Kalliope is ready to be awaken
-# - never: never play a sound or sentences when kalliope is ready
-# - once: at the first start of Kalliope
-play_on_ready_notification: never
-
-# The on ready notification can be a sentence. Place here a sentence or a list of sentence. If you set a list, one sentence will be picked up randomly
-on_ready_answers:
-  - "Kalliope is ready"
-
-# You can play a sound instead of a sentence.
-# Remove the `on_ready_answers` parameters by commenting it out and use this one instead.
-# Place here the path of the sound file. Files must be .wav or .mp3 format.
-on_ready_sounds:
-  - "sounds/ding.wav"
-  - "sounds/dong.wav"
 
 
 
 
 # ---------------------------
 # ---------------------------
@@ -114,10 +77,24 @@ rest_api:
   allowed_cors_origin: False
   allowed_cors_origin: False
 
 
 # ---------------------------
 # ---------------------------
-# Default Synapse
-# ---------------------------
-# Specify an optional default synapse response in case your order is not found.
-default_synapse: "Default-synapse"
+# Hooks
+# ---------------------------
+hooks:
+  on_start:
+    - "on-start-synapse"
+    - "bring-led-on"
+  on_waiting_for_trigger: "test"
+  on_triggered:
+    - "on-triggered-synapse"
+  on_start_listening:
+  on_stop_listening:
+  on_order_found:
+  on_order_not_found:
+    - "order-not-found-synapse"
+  on_mute: []
+  on_unmute: []
+  on_start_speaking:
+  on_stop_speaking:
 
 
 # ---------------------------
 # ---------------------------
 # resource directory path
 # resource directory path

+ 90 - 0
Tests/test_hook_manager.py

@@ -0,0 +1,90 @@
+import unittest
+import os
+import mock as mock
+import inspect
+import shutil
+
+from kalliope.core.Models import Singleton
+
+from kalliope.core.ConfigurationManager import SettingLoader
+
+from kalliope.core import HookManager
+from kalliope.core.Models.Settings import Settings
+
+
+class TestInit(unittest.TestCase):
+
+    def setUp(self):
+        # Init the folders, otherwise it raises an exceptions
+        os.makedirs("/tmp/kalliope/tests/kalliope_resources_dir/neurons")
+        os.makedirs("/tmp/kalliope/tests/kalliope_resources_dir/stt")
+        os.makedirs("/tmp/kalliope/tests/kalliope_resources_dir/tts")
+        os.makedirs("/tmp/kalliope/tests/kalliope_resources_dir/trigger")
+
+        # get current script directory path. We are in /an/unknown/path/kalliope/core/tests
+        cur_script_directory = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
+        # get parent dir. Now we are in /an/unknown/path/kalliope
+        root_dir = os.path.normpath(cur_script_directory + os.sep + os.pardir)
+
+        self.settings_file_to_test = root_dir + os.sep + "Tests/settings/settings_test.yml"
+        self.settings = SettingLoader(file_path=self.settings_file_to_test)
+
+    def tearDown(self):
+        # Cleanup
+        shutil.rmtree('/tmp/kalliope/tests/kalliope_resources_dir')
+
+        Singleton._instances = {}
+
+    def test_on_start(self):
+        """
+        test list of synapse
+        """
+        with mock.patch("kalliope.core.SynapseLauncher.start_synapse_by_list_name") as mock_synapse_launcher:
+            HookManager.on_start()
+            mock_synapse_launcher.assert_called_with(["on-start-synapse", "bring-led-on"], new_lifo=True)
+            mock_synapse_launcher.reset_mock()
+
+    def test_on_waiting_for_trigger(self):
+        """
+        test with single synapse 
+        """
+        with mock.patch("kalliope.core.SynapseLauncher.start_synapse_by_name") as mock_synapse_launcher:
+            HookManager.on_waiting_for_trigger()
+            mock_synapse_launcher.assert_called_with("test", new_lifo=True)
+            mock_synapse_launcher.reset_mock()
+
+    def test_on_triggered(self):
+        with mock.patch("kalliope.core.SynapseLauncher.start_synapse_by_list_name") as mock_synapse_launcher:
+            HookManager.on_triggered()
+            mock_synapse_launcher.assert_called_with(["on-triggered-synapse"], new_lifo=True)
+            mock_synapse_launcher.reset_mock()
+
+    def test_on_start_listening(self):
+        self.assertIsNone(HookManager.on_start_listening())
+
+    def test_on_stop_listening(self):
+        self.assertIsNone(HookManager.on_stop_listening())
+
+    def test_on_order_found(self):
+        self.assertIsNone(HookManager.on_order_found())
+
+    def test_on_order_not_found(self):
+        with mock.patch("kalliope.core.SynapseLauncher.start_synapse_by_list_name") as mock_synapse_launcher:
+            HookManager.on_order_not_found()
+            mock_synapse_launcher.assert_called_with(["order-not-found-synapse"], new_lifo=True)
+            mock_synapse_launcher.reset_mock()
+
+    def test_on_mute(self):
+        """
+        test that empty list of synapse return none
+        """
+        self.assertIsNone(HookManager.on_mute())
+
+
+if __name__ == '__main__':
+    unittest.main()
+
+    # suite = unittest.TestSuite()
+    # suite.addTest(TestInit("test_main"))
+    # runner = unittest.TextTestRunner()
+    # runner.run(suite)

+ 7 - 7
Tests/test_lifo_buffer.py

@@ -3,9 +3,9 @@ import unittest
 
 
 import mock
 import mock
 
 
-from kalliope.core import LIFOBuffer
+from kalliope.core import LifoManager
 from kalliope.core.ConfigurationManager import BrainLoader
 from kalliope.core.ConfigurationManager import BrainLoader
-from kalliope.core.LIFOBuffer import Serialize, SynapseListAddedToLIFO
+from kalliope.core.Lifo.LIFOBuffer import Serialize, SynapseListAddedToLIFO
 
 
 from kalliope.core.Models import Singleton
 from kalliope.core.Models import Singleton
 from kalliope.core.Models.MatchedSynapse import MatchedSynapse
 from kalliope.core.Models.MatchedSynapse import MatchedSynapse
@@ -24,7 +24,7 @@ class TestLIFOBuffer(unittest.TestCase):
 
 
         BrainLoader(file_path=self.brain_to_test)
         BrainLoader(file_path=self.brain_to_test)
         # create a new lifo buffer
         # create a new lifo buffer
-        self.lifo_buffer = LIFOBuffer()
+        self.lifo_buffer = LifoManager.get_singleton_lifo()
         self.lifo_buffer.clean()
         self.lifo_buffer.clean()
 
 
     def test_execute(self):
     def test_execute(self):
@@ -292,7 +292,7 @@ class TestLIFOBuffer(unittest.TestCase):
         list_matched_synapse = list()
         list_matched_synapse = list()
         list_matched_synapse.append(matched_synapse)
         list_matched_synapse.append(matched_synapse)
 
 
-        with mock.patch("kalliope.core.LIFOBuffer._process_neuron_list"):
+        with mock.patch("kalliope.core.Lifo.LIFOBuffer._process_neuron_list"):
             self.lifo_buffer._process_synapse_list(list_matched_synapse)
             self.lifo_buffer._process_synapse_list(list_matched_synapse)
             expected_response = {
             expected_response = {
                 'status': None,
                 'status': None,
@@ -322,7 +322,7 @@ class TestLIFOBuffer(unittest.TestCase):
             self.assertEqual("complete", self.lifo_buffer.api_response.status)
             self.assertEqual("complete", self.lifo_buffer.api_response.status)
 
 
         # test with neuron that wait for an answer
         # test with neuron that wait for an answer
-        self.lifo_buffer.clean()
+        LifoManager.clean_saved_lifo()
         synapse = BrainLoader().brain.get_synapse_by_name("synapse6")
         synapse = BrainLoader().brain.get_synapse_by_name("synapse6")
         order = "synapse6"
         order = "synapse6"
         matched_synapse = MatchedSynapse(matched_synapse=synapse,
         matched_synapse = MatchedSynapse(matched_synapse=synapse,
@@ -335,7 +335,7 @@ class TestLIFOBuffer(unittest.TestCase):
                 self.lifo_buffer._process_neuron_list(matched_synapse=matched_synapse)
                 self.lifo_buffer._process_neuron_list(matched_synapse=matched_synapse)
 
 
         # test with a neuron that want to add a synapse list to the LIFO
         # test with a neuron that want to add a synapse list to the LIFO
-        self.lifo_buffer.clean()
+        LifoManager.clean_saved_lifo()
         synapse = BrainLoader().brain.get_synapse_by_name("synapse6")
         synapse = BrainLoader().brain.get_synapse_by_name("synapse6")
         order = "synapse6"
         order = "synapse6"
         matched_synapse = MatchedSynapse(matched_synapse=synapse,
         matched_synapse = MatchedSynapse(matched_synapse=synapse,
@@ -353,6 +353,6 @@ if __name__ == '__main__':
     unittest.main()
     unittest.main()
 
 
     # suite = unittest.TestSuite()
     # suite = unittest.TestSuite()
-    # suite.addTest(TestLIFOBuffer("test_process_neuron_list"))
+    # suite.addTest(TestLIFOBuffer("test_execute"))
     # runner = unittest.TextTestRunner()
     # runner = unittest.TextTestRunner()
     # runner.run(suite)
     # runner.run(suite)

+ 3 - 26
Tests/test_models.py

@@ -56,7 +56,7 @@ class TestModels(unittest.TestCase):
         # this brain is the same as the first one
         # this brain is the same as the first one
         self.brain_test3 = Brain(synapses=self.all_synapse_list1)
         self.brain_test3 = Brain(synapses=self.all_synapse_list1)
 
 
-        self.settings_test = Settings(default_synapse="Synapse3")
+        self.settings_test = Settings()
 
 
         # clean the LiFO
         # clean the LiFO
         LIFOBuffer.lifo_list = list()
         LIFOBuffer.lifo_list = list()
@@ -252,16 +252,10 @@ class TestModels(unittest.TestCase):
                                 default_player_name="mplayer",
                                 default_player_name="mplayer",
                                 ttss=["ttts"],
                                 ttss=["ttts"],
                                 stts=["stts"],
                                 stts=["stts"],
-                                random_wake_up_answers=["yes"],
-                                random_wake_up_sounds=None,
-                                play_on_ready_notification=False,
-                                on_ready_answers=None,
-                                on_ready_sounds=None,
                                 triggers=["snowboy"],
                                 triggers=["snowboy"],
                                 players=["mplayer"],
                                 players=["mplayer"],
                                 rest_api=rest_api1,
                                 rest_api=rest_api1,
                                 cache_path="/tmp/kalliope",
                                 cache_path="/tmp/kalliope",
-                                default_synapse="default_synapse",
                                 resources=None,
                                 resources=None,
                                 variables={"key1": "val1"},
                                 variables={"key1": "val1"},
                                 recognition_options=recognition_options,
                                 recognition_options=recognition_options,
@@ -274,15 +268,9 @@ class TestModels(unittest.TestCase):
                                 default_player_name="mplayer",
                                 default_player_name="mplayer",
                                 ttss=["ttts"],
                                 ttss=["ttts"],
                                 stts=["stts"],
                                 stts=["stts"],
-                                random_wake_up_answers=["no"],
-                                random_wake_up_sounds=None,
-                                play_on_ready_notification=False,
-                                on_ready_answers=None,
-                                on_ready_sounds=None,
                                 triggers=["snowboy"],
                                 triggers=["snowboy"],
                                 rest_api=rest_api1,
                                 rest_api=rest_api1,
                                 cache_path="/tmp/kalliope_tmp",
                                 cache_path="/tmp/kalliope_tmp",
-                                default_synapse="my_default_synapse",
                                 resources=None,
                                 resources=None,
                                 variables={"key1": "val1"},
                                 variables={"key1": "val1"},
                                 recognition_options=recognition_options,
                                 recognition_options=recognition_options,
@@ -295,16 +283,10 @@ class TestModels(unittest.TestCase):
                                 default_player_name="mplayer",
                                 default_player_name="mplayer",
                                 ttss=["ttts"],
                                 ttss=["ttts"],
                                 stts=["stts"],
                                 stts=["stts"],
-                                random_wake_up_answers=["yes"],
-                                random_wake_up_sounds=None,
-                                play_on_ready_notification=False,
-                                on_ready_answers=None,
-                                on_ready_sounds=None,
                                 triggers=["snowboy"],
                                 triggers=["snowboy"],
                                 players=["mplayer"],
                                 players=["mplayer"],
                                 rest_api=rest_api1,
                                 rest_api=rest_api1,
                                 cache_path="/tmp/kalliope",
                                 cache_path="/tmp/kalliope",
-                                default_synapse="default_synapse",
                                 resources=None,
                                 resources=None,
                                 variables={"key1": "val1"},
                                 variables={"key1": "val1"},
                                 recognition_options=recognition_options,
                                 recognition_options=recognition_options,
@@ -312,8 +294,8 @@ class TestModels(unittest.TestCase):
             setting3.kalliope_version = "0.4.5"
             setting3.kalliope_version = "0.4.5"
 
 
             expected_result_serialize = {
             expected_result_serialize = {
-                'default_synapse': 'default_synapse',
                 'default_tts_name': 'pico2wav',
                 'default_tts_name': 'pico2wav',
+                'hooks': None,
                 'rest_api':
                 'rest_api':
                     {
                     {
                         'password_protected': True,
                         'password_protected': True,
@@ -323,28 +305,23 @@ class TestModels(unittest.TestCase):
                         'password': 'password',
                         'password': 'password',
                         'login': 'admin'
                         'login': 'admin'
                     },
                     },
-                'play_on_ready_notification': False,
                 'default_stt_name': 'google',
                 'default_stt_name': 'google',
                 'kalliope_version': '0.4.5',
                 'kalliope_version': '0.4.5',
-                'random_wake_up_sounds': None,
-                'on_ready_answers': None,
                 'default_trigger_name': 'swoyboy',
                 'default_trigger_name': 'swoyboy',
                 'default_player_name': 'mplayer',
                 'default_player_name': 'mplayer',
                 'cache_path': '/tmp/kalliope',
                 'cache_path': '/tmp/kalliope',
                 'stts': ['stts'],
                 'stts': ['stts'],
                 'machine': 'pumpkins',
                 'machine': 'pumpkins',
-                'random_wake_up_answers': ['yes'],
-                'on_ready_sounds': None,
                 'ttss': ['ttts'],
                 'ttss': ['ttts'],
                 'variables': {'key1': 'val1'},
                 'variables': {'key1': 'val1'},
                 'resources': None,
                 'resources': None,
                 'triggers': ['snowboy'],
                 'triggers': ['snowboy'],
-                'rpi_settings': None,
                 'players': ['mplayer'],
                 'players': ['mplayer'],
                 'recognition_options': {'energy_threshold': 4000, 'adjust_for_ambient_noise_second': 0},
                 'recognition_options': {'energy_threshold': 4000, 'adjust_for_ambient_noise_second': 0},
                 'start_options': {'muted': False}
                 'start_options': {'muted': False}
             }
             }
 
 
+            self.maxDiff = None
             self.assertDictEqual(expected_result_serialize, setting1.serialize())
             self.assertDictEqual(expected_result_serialize, setting1.serialize())
 
 
             self.assertTrue(setting1.__eq__(setting3))
             self.assertTrue(setting1.__eq__(setting3))

+ 62 - 115
Tests/test_rest_api.py

@@ -7,7 +7,7 @@ from flask_testing import LiveServerTestCase
 from mock import mock
 from mock import mock
 
 
 from kalliope._version import version_str
 from kalliope._version import version_str
-from kalliope.core import LIFOBuffer
+from kalliope.core import LIFOBuffer, LifoManager
 from kalliope.core.ConfigurationManager import BrainLoader
 from kalliope.core.ConfigurationManager import BrainLoader
 from kalliope.core.ConfigurationManager import SettingLoader
 from kalliope.core.ConfigurationManager import SettingLoader
 from kalliope.core.Models import Singleton
 from kalliope.core.Models import Singleton
@@ -19,7 +19,7 @@ class TestRestAPI(LiveServerTestCase):
     def tearDown(self):
     def tearDown(self):
         Singleton._instances = {}
         Singleton._instances = {}
         # clean the lifo
         # clean the lifo
-        LIFOBuffer.lifo_list = list()
+        LifoManager.clean_saved_lifo()
 
 
     def create_app(self):
     def create_app(self):
         """
         """
@@ -42,6 +42,7 @@ class TestRestAPI(LiveServerTestCase):
         sl.settings.port = 5000
         sl.settings.port = 5000
         sl.settings.allowed_cors_origin = "*"
         sl.settings.allowed_cors_origin = "*"
         sl.settings.default_synapse = None
         sl.settings.default_synapse = None
+        sl.settings.hooks["on_order_not_found"] = "order-not-found-synapse"
 
 
         # prepare a test brain
         # prepare a test brain
         brain_to_test = full_path_brain_to_test
         brain_to_test = full_path_brain_to_test
@@ -73,85 +74,23 @@ class TestRestAPI(LiveServerTestCase):
 
 
         response = self.client.get(url)
         response = self.client.get(url)
         expected_content = {
         expected_content = {
-          "synapses": [
-            {
-              "name": "test",
-              "neurons": [
-                {
-                  "name": "say",
-                  "parameters": {
-                    "message": [
-                      "test message"
-                    ]
-                  }
-                }
-              ],
-              "signals": [
-                {
-                  "name": "order",
-                  "parameters": "test_order"
-                }
-              ]
-            },
-            {
-              "name": "test2",
-              "neurons": [
-                {
-                  "name": "say",
-                  "parameters": {
-                    "message": [
-                      "test message"
-                    ]
-                  }
-                }
-              ],
-              "signals": [
-                {
-                  "name": "order",
-                  "parameters": "bonjour"
-                }
-              ]
-            },
-            {
-              "name": "test4",
-              "neurons": [
-                {
-                  "name": "say",
-                  "parameters": {
-                    "message": [
-                      "test message {{parameter1}}"
-                    ]
-                  }
-                }
-              ],
-              "signals": [
-                {
-                  "name": "order",
-                  "parameters": "test_order_with_parameter"
-                }
-              ]
-            },
-            {
-              "name": "test3",
-              "neurons": [
-                {
-                  "name": "say",
-                  "parameters": {
-                    "message": [
-                      "test message"
-                    ]
-                  }
-                }
-              ],
-              "signals": [
-                {
-                  "name": "order",
-                  "parameters": "test_order_3"
-                }
-              ]
-            }
-          ]
-        }
+            "synapses": [
+                {"signals": [{"name": "order", "parameters": "test_order"}],
+                 "neurons": [{"name": "say", "parameters": {"message": ["test message"]}}],
+                 "name": "test"},
+                {"signals": [{"name": "order", "parameters": "bonjour"}],
+                 "neurons": [{"name": "say", "parameters": {"message": ["test message"]}}],
+                 "name": "test2"},
+                {"signals": [{"name": "order", "parameters": "test_order_with_parameter"}],
+                 "neurons": [{"name": "say", "parameters": {"message": ["test message {{parameter1}}"]}}],
+                 "name": "test4"},
+                {"signals": [],
+                 "neurons": [{"name": "say", "parameters": {"message": "order not found"}}],
+                 "name": "order-not-found-synapse"},
+                {"signals": [{"name": "order", "parameters": "test_order_3"}],
+                 "neurons": [{"name": "say", "parameters": {"message": ["test message"]}}],
+                 "name": "test3"}]}
+
         # a lot of char ti process
         # a lot of char ti process
         self.maxDiff = None
         self.maxDiff = None
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -162,26 +101,26 @@ class TestRestAPI(LiveServerTestCase):
         url = self.get_server_url() + "/synapses/test"
         url = self.get_server_url() + "/synapses/test"
         response = self.client.get(url)
         response = self.client.get(url)
 
 
-        expected_content ={
-          "synapses": {
-            "name": "test",
-            "neurons": [
-              {
-                "name": "say",
-                "parameters": {
-                  "message": [
-                    "test message"
-                  ]
-                }
-              }
-            ],
-            "signals": [
-              {
-                "name": "order",
-                "parameters": "test_order"
-              }
-            ]
-          }
+        expected_content = {
+            "synapses": {
+                "name": "test",
+                "neurons": [
+                    {
+                        "name": "say",
+                        "parameters": {
+                            "message": [
+                                "test message"
+                            ]
+                        }
+                    }
+                ],
+                "signals": [
+                    {
+                        "name": "order",
+                        "parameters": "test_order"
+                    }
+                ]
+            }
         }
         }
         self.assertEqual(json.dumps(expected_content, sort_keys=True),
         self.assertEqual(json.dumps(expected_content, sort_keys=True),
                          json.dumps(json.loads(response.get_data().decode('utf-8')), sort_keys=True))
                          json.dumps(json.loads(response.get_data().decode('utf-8')), sort_keys=True))
@@ -222,20 +161,20 @@ class TestRestAPI(LiveServerTestCase):
         result = self.client.post(url, headers=headers, data=json.dumps(data))
         result = self.client.post(url, headers=headers, data=json.dumps(data))
 
 
         expected_content = {
         expected_content = {
-          "matched_synapses": [
-            {
-              "matched_order": None,
-              "neuron_module_list": [
+            "matched_synapses": [
                 {
                 {
-                  "generated_message": "test message replaced_value",
-                  "neuron_name": "Say"
+                    "matched_order": None,
+                    "neuron_module_list": [
+                        {
+                            "generated_message": "test message replaced_value",
+                            "neuron_name": "Say"
+                        }
+                    ],
+                    "synapse_name": "test4"
                 }
                 }
-              ],
-              "synapse_name": "test4"
-            }
-          ],
-          "status": "complete",
-          "user_order": None
+            ],
+            "status": "complete",
+            "user_order": None
         }
         }
 
 
         self.assertEqual(json.dumps(expected_content, sort_keys=True),
         self.assertEqual(json.dumps(expected_content, sort_keys=True),
@@ -290,7 +229,14 @@ class TestRestAPI(LiveServerTestCase):
                                   headers=headers,
                                   headers=headers,
                                   data=json.dumps(data))
                                   data=json.dumps(data))
 
 
-        expected_content = {'status': None, 'matched_synapses': [], 'user_order': u'non existing order'}
+        expected_content = {"matched_synapses": [{"matched_order": None,
+                                                  "neuron_module_list": [
+                                                      {"generated_message": "order not found",
+                                                       "neuron_name": "Say"}
+                                                  ],
+                                                  "synapse_name": "order-not-found-synapse"}],
+                            "status": "complete",
+                            "user_order": None}
 
 
         self.assertEqual(json.dumps(expected_content, sort_keys=True),
         self.assertEqual(json.dumps(expected_content, sort_keys=True),
                          json.dumps(json.loads(result.get_data().decode('utf-8')), sort_keys=True))
                          json.dumps(json.loads(result.get_data().decode('utf-8')), sort_keys=True))
@@ -351,10 +297,11 @@ class TestRestAPI(LiveServerTestCase):
             self.assertEqual(expected_result, result_file)
             self.assertEqual(expected_result, result_file)
             mock_os_system.assert_called_once_with("avconv -y -i " + temp_file + " " + expected_result)
             mock_os_system.assert_called_once_with("avconv -y -i " + temp_file + " " + expected_result)
 
 
+
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()
 
 
     # suite = unittest.TestSuite()
     # suite = unittest.TestSuite()
-    # suite.addTest(TestRestAPI("test_run_synapse_by_name"))
+    # suite.addTest(TestRestAPI("test_get_all_synapses"))
     # runner = unittest.TextTestRunner()
     # runner = unittest.TextTestRunner()
     # runner.run(suite)
     # runner.run(suite)

+ 83 - 38
Tests/test_settings_loader.py

@@ -27,7 +27,6 @@ class TestSettingLoader(unittest.TestCase):
         self.settings_file_to_test = root_dir + os.sep + "Tests/settings/settings_test.yml"
         self.settings_file_to_test = root_dir + os.sep + "Tests/settings/settings_test.yml"
 
 
         self.settings_dict = {
         self.settings_dict = {
-            'default_synapse': 'Default-synapse',
             'rest_api':
             'rest_api':
                 {'allowed_cors_origin': False,
                 {'allowed_cors_origin': False,
                  'active': True,
                  'active': True,
@@ -35,15 +34,11 @@ class TestSettingLoader(unittest.TestCase):
                  'password_protected': True,
                  'password_protected': True,
                  'password': 'secret', 'port': 5000},
                  'password': 'secret', 'port': 5000},
             'default_trigger': 'snowboy',
             'default_trigger': 'snowboy',
-            'default_player': 'mplayer',
-            'play_on_ready_notification': 'never',
             'triggers': [{'snowboy': {'pmdl_file': 'trigger/snowboy/resources/kalliope-FR-6samples.pmdl'}}],
             'triggers': [{'snowboy': {'pmdl_file': 'trigger/snowboy/resources/kalliope-FR-6samples.pmdl'}}],
+            'default_player': 'mplayer',
             'players': [{'mplayer': {}}, {'pyalsaaudio': {"device": "default"}}],
             'players': [{'mplayer': {}}, {'pyalsaaudio': {"device": "default"}}],
             'speech_to_text': [{'google': {'language': 'fr-FR'}}],
             'speech_to_text': [{'google': {'language': 'fr-FR'}}],
-            'on_ready_answers': ['Kalliope is ready'],
             'cache_path': '/tmp/kalliope_tts_cache',
             'cache_path': '/tmp/kalliope_tts_cache',
-            'random_wake_up_answers': ['Oui monsieur?'],
-            'on_ready_sounds': ['sounds/ding.wav', 'sounds/dong.wav'],
             'resource_directory': {
             'resource_directory': {
                 'stt': '/tmp/kalliope/tests/kalliope_resources_dir/stt',
                 'stt': '/tmp/kalliope/tests/kalliope_resources_dir/stt',
                 'tts': '/tmp/kalliope/tests/kalliope_resources_dir/tts',
                 'tts': '/tmp/kalliope/tests/kalliope_resources_dir/tts',
@@ -51,15 +46,25 @@ class TestSettingLoader(unittest.TestCase):
                 'trigger': '/tmp/kalliope/tests/kalliope_resources_dir/trigger'},
                 'trigger': '/tmp/kalliope/tests/kalliope_resources_dir/trigger'},
             'default_text_to_speech': 'pico2wave',
             'default_text_to_speech': 'pico2wave',
             'default_speech_to_text': 'google',
             'default_speech_to_text': 'google',
-            'random_wake_up_sounds': ['sounds/ding.wav', 'sounds/dong.wav'],
             'text_to_speech': [
             'text_to_speech': [
                 {'pico2wave': {'cache': True, 'language': 'fr-FR'}},
                 {'pico2wave': {'cache': True, 'language': 'fr-FR'}},
                 {'voxygen': {'voice': 'Agnes', 'cache': True}}
                 {'voxygen': {'voice': 'Agnes', 'cache': True}}
-            ],
+                ],
             'var_files': ["../Tests/settings/variables.yml"],
             'var_files': ["../Tests/settings/variables.yml"],
-            'start_options': {
-                'muted': True
-            }
+            'start_options': {'muted': True},
+            'hooks': {'on_waiting_for_trigger': 'test',
+                      'on_stop_listening': None,
+                      'on_start_listening': None,
+                      'on_order_found': None,
+                      'on_start': ['on-start-synapse', 'bring-led-on'],
+                      'on_unmute': [],
+                      'on_triggered': ['on-triggered-synapse'],
+                      'on_mute': [],
+                      'on_order_not_found': [
+                          'order-not-found-synapse'],
+                      'on_start_speaking': None,
+                      'on_stop_speaking': None
+                      }
         }
         }
 
 
         # Init the folders, otherwise it raises an exceptions
         # Init the folders, otherwise it raises an exceptions
@@ -83,7 +88,8 @@ class TestSettingLoader(unittest.TestCase):
     def test_get_yaml_config(self):
     def test_get_yaml_config(self):
 
 
         sl = SettingLoader(file_path=self.settings_file_to_test)
         sl = SettingLoader(file_path=self.settings_file_to_test)
-        self.assertEqual(sl.yaml_config, self.settings_dict)
+        self.maxDiff = None
+        self.assertDictEqual(sl.yaml_config, self.settings_dict)
 
 
     def test_get_settings(self):
     def test_get_settings(self):
         settings_object = Settings()
         settings_object = Settings()
@@ -96,11 +102,6 @@ class TestSettingLoader(unittest.TestCase):
         settings_object.ttss = [tts1, tts2]
         settings_object.ttss = [tts1, tts2]
         stt = Stt(name="google", parameters={'language': 'fr-FR'})
         stt = Stt(name="google", parameters={'language': 'fr-FR'})
         settings_object.stts = [stt]
         settings_object.stts = [stt]
-        settings_object.random_wake_up_answers = ['Oui monsieur?']
-        settings_object.random_wake_up_sounds = ['sounds/ding.wav', 'sounds/dong.wav']
-        settings_object.play_on_ready_notification = "never"
-        settings_object.on_ready_answers = ['Kalliope is ready']
-        settings_object.on_ready_sounds = ['sounds/ding.wav', 'sounds/dong.wav']
         trigger1 = Trigger(name="snowboy",
         trigger1 = Trigger(name="snowboy",
                            parameters={'pmdl_file': 'trigger/snowboy/resources/kalliope-FR-6samples.pmdl'})
                            parameters={'pmdl_file': 'trigger/snowboy/resources/kalliope-FR-6samples.pmdl'})
         settings_object.triggers = [trigger1]
         settings_object.triggers = [trigger1]
@@ -111,7 +112,6 @@ class TestSettingLoader(unittest.TestCase):
                                            login="admin", password="secret", port=5000,
                                            login="admin", password="secret", port=5000,
                                            allowed_cors_origin=False)
                                            allowed_cors_origin=False)
         settings_object.cache_path = '/tmp/kalliope_tts_cache'
         settings_object.cache_path = '/tmp/kalliope_tts_cache'
-        settings_object.default_synapse = 'Default-synapse'
         resources = Resources(neuron_folder="/tmp/kalliope/tests/kalliope_resources_dir/neurons",
         resources = Resources(neuron_folder="/tmp/kalliope/tests/kalliope_resources_dir/neurons",
                               stt_folder="/tmp/kalliope/tests/kalliope_resources_dir/stt",
                               stt_folder="/tmp/kalliope/tests/kalliope_resources_dir/stt",
                               tts_folder="/tmp/kalliope/tests/kalliope_resources_dir/tts",
                               tts_folder="/tmp/kalliope/tests/kalliope_resources_dir/tts",
@@ -127,6 +127,19 @@ class TestSettingLoader(unittest.TestCase):
         }
         }
         settings_object.machine = platform.machine()
         settings_object.machine = platform.machine()
         settings_object.recognition_options = RecognitionOptions()
         settings_object.recognition_options = RecognitionOptions()
+        settings_object.hooks = {'on_waiting_for_trigger': 'test',
+                                 'on_stop_listening': None,
+                                 'on_start_listening': None,
+                                 'on_order_found': None,
+                                 'on_start': ['on-start-synapse', 'bring-led-on'],
+                                 'on_unmute': [],
+                                 'on_triggered': ['on-triggered-synapse'],
+                                 'on_mute': [],
+                                 'on_order_not_found': [
+                                     'order-not-found-synapse'],
+                                 'on_start_speaking': None,
+                                 'on_stop_speaking': None,
+                                 }
 
 
         sl = SettingLoader(file_path=self.settings_file_to_test)
         sl = SettingLoader(file_path=self.settings_file_to_test)
 
 
@@ -173,21 +186,6 @@ class TestSettingLoader(unittest.TestCase):
         sl = SettingLoader(file_path=self.settings_file_to_test)
         sl = SettingLoader(file_path=self.settings_file_to_test)
         self.assertEqual([player1, player2], sl._get_players(self.settings_dict))
         self.assertEqual([player1, player2], sl._get_players(self.settings_dict))
 
 
-    def test_get_random_wake_up_answers(self):
-        expected_random_wake_up_answers = ['Oui monsieur?']
-        sl = SettingLoader(file_path=self.settings_file_to_test)
-        self.assertEqual(expected_random_wake_up_answers, sl._get_random_wake_up_answers(self.settings_dict))
-
-    def test_get_on_ready_answers(self):
-        expected_on_ready_answers = ['Kalliope is ready']
-        sl = SettingLoader(file_path=self.settings_file_to_test)
-        self.assertEqual(expected_on_ready_answers, sl._get_on_ready_answers(self.settings_dict))
-
-    def test_get_on_ready_sounds(self):
-        expected_on_ready_sounds = ['sounds/ding.wav', 'sounds/dong.wav']
-        sl = SettingLoader(file_path=self.settings_file_to_test)
-        self.assertEqual(expected_on_ready_sounds, sl._get_on_ready_sounds(self.settings_dict))
-
     def test_get_rest_api(self):
     def test_get_rest_api(self):
         expected_rest_api = RestAPI(password_protected=True, active=True,
         expected_rest_api = RestAPI(password_protected=True, active=True,
                                     login="admin", password="secret", port=5000,
                                     login="admin", password="secret", port=5000,
@@ -201,11 +199,6 @@ class TestSettingLoader(unittest.TestCase):
         sl = SettingLoader(file_path=self.settings_file_to_test)
         sl = SettingLoader(file_path=self.settings_file_to_test)
         self.assertEqual(expected_cache_path, sl._get_cache_path(self.settings_dict))
         self.assertEqual(expected_cache_path, sl._get_cache_path(self.settings_dict))
 
 
-    def test_get_default_synapse(self):
-        expected_default_synapse = 'Default-synapse'
-        sl = SettingLoader(file_path=self.settings_file_to_test)
-        self.assertEqual(expected_default_synapse, sl._get_default_synapse(self.settings_dict))
-
     def test_get_resources(self):
     def test_get_resources(self):
 
 
         resources = Resources(neuron_folder="/tmp/kalliope/tests/kalliope_resources_dir/neurons",
         resources = Resources(neuron_folder="/tmp/kalliope/tests/kalliope_resources_dir/neurons",
@@ -234,6 +227,58 @@ class TestSettingLoader(unittest.TestCase):
         self.assertEqual(expected_result,
         self.assertEqual(expected_result,
                          sl._get_start_options(self.settings_dict))
                          sl._get_start_options(self.settings_dict))
 
 
+    def test_get_hooks(self):
+
+        # test with only one hook set
+        settings = dict()
+        settings["hooks"] = {
+            "on_start": "test_synapse"
+        }
+
+        expected_dict = {
+            "on_start": "test_synapse",
+            "on_waiting_for_trigger": None,
+            "on_triggered": None,
+            "on_start_listening": None,
+            "on_stop_listening": None,
+            "on_order_found": None,
+            "on_order_not_found": None,
+            "on_mute": None,
+            "on_unmute": None,
+            "on_start_speaking": None,
+            "on_stop_speaking": None
+        }
+
+        returned_dict = SettingLoader._get_hooks(settings)
+
+        self.assertEqual(returned_dict, expected_dict)
+
+        # test with no hook set
+        settings = dict()
+
+        expected_dict = {
+            "on_start": None,
+            "on_waiting_for_trigger": None,
+            "on_triggered": None,
+            "on_start_listening": None,
+            "on_stop_listening": None,
+            "on_order_found": None,
+            "on_order_not_found": None,
+            "on_mute": None,
+            "on_unmute": None,
+            "on_start_speaking": None,
+            "on_stop_speaking": None
+        }
+
+        returned_dict = SettingLoader._get_hooks(settings)
+
+        self.assertEqual(returned_dict, expected_dict)
+
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()
+
+    # suite = unittest.TestSuite()
+    # suite.addTest(TestSettingLoader("test_get_hooks"))
+    # runner = unittest.TextTestRunner()
+    # runner.run(suite)

+ 86 - 28
Tests/test_synapse_launcher.py

@@ -2,7 +2,7 @@ import unittest
 
 
 import mock
 import mock
 
 
-from kalliope.core import LIFOBuffer
+from kalliope.core import LIFOBuffer, LifoManager
 from kalliope.core.Models import Brain, Signal, Singleton
 from kalliope.core.Models import Brain, Signal, Singleton
 from kalliope.core.Models.MatchedSynapse import MatchedSynapse
 from kalliope.core.Models.MatchedSynapse import MatchedSynapse
 from kalliope.core.Models.Settings import Settings
 from kalliope.core.Models.Settings import Settings
@@ -37,24 +37,26 @@ class TestSynapseLauncher(unittest.TestCase):
                                  self.synapse3]
                                  self.synapse3]
 
 
         self.brain_test = Brain(synapses=self.all_synapse_list)
         self.brain_test = Brain(synapses=self.all_synapse_list)
-        self.settings_test = Settings(default_synapse="Synapse3")
+        self.settings_test = Settings()
 
 
         # clean the LiFO
         # clean the LiFO
         Singleton._instances = dict()
         Singleton._instances = dict()
+        LifoManager.clean_saved_lifo()
 
 
     def test_start_synapse_by_name(self):
     def test_start_synapse_by_name(self):
         # existing synapse in the brain
         # existing synapse in the brain
-        with mock.patch("kalliope.core.LIFOBuffer.execute"):
+        with mock.patch("kalliope.core.Lifo.LIFOBuffer.execute"):
             should_be_created_matched_synapse = MatchedSynapse(matched_synapse=self.synapse1)
             should_be_created_matched_synapse = MatchedSynapse(matched_synapse=self.synapse1)
             SynapseLauncher.start_synapse_by_name("Synapse1", brain=self.brain_test)
             SynapseLauncher.start_synapse_by_name("Synapse1", brain=self.brain_test)
             # we expect that the lifo has been loaded with the synapse to run
             # we expect that the lifo has been loaded with the synapse to run
             expected_result = [[should_be_created_matched_synapse]]
             expected_result = [[should_be_created_matched_synapse]]
-            lifo_buffer = LIFOBuffer()
+            lifo_buffer = LifoManager.get_singleton_lifo()
             self.assertEqual(expected_result, lifo_buffer.lifo_list)
             self.assertEqual(expected_result, lifo_buffer.lifo_list)
 
 
             # we expect that the lifo has been loaded with the synapse to run and overwritten parameters
             # we expect that the lifo has been loaded with the synapse to run and overwritten parameters
             Singleton._instances = dict()
             Singleton._instances = dict()
-            lifo_buffer = LIFOBuffer()
+            LifoManager.clean_saved_lifo()
+            lifo_buffer = LifoManager.get_singleton_lifo()
             overriding_param = {
             overriding_param = {
                 "val1": "val"
                 "val1": "val"
             }
             }
@@ -70,11 +72,67 @@ class TestSynapseLauncher(unittest.TestCase):
         with self.assertRaises(SynapseNameNotFound):
         with self.assertRaises(SynapseNameNotFound):
             SynapseLauncher.start_synapse_by_name("not_existing", brain=self.brain_test)
             SynapseLauncher.start_synapse_by_name("not_existing", brain=self.brain_test)
 
 
+    def test_start_synapse_by_list_name(self):
+        # test to start a list of synapse
+        with mock.patch("kalliope.core.Lifo.LIFOBuffer.execute"):
+            created_matched_synapse1 = MatchedSynapse(matched_synapse=self.synapse1)
+            created_matched_synapse2 = MatchedSynapse(matched_synapse=self.synapse2)
+
+            expected_list_matched_synapse = [created_matched_synapse1, created_matched_synapse2]
+
+            SynapseLauncher.start_synapse_by_list_name(["Synapse1", "Synapse2"], brain=self.brain_test)
+            # we expect that the lifo has been loaded with the synapse to run
+            expected_result = [expected_list_matched_synapse]
+            lifo_buffer = LifoManager.get_singleton_lifo()
+            self.maxDiff = None
+            self.assertEqual(expected_result, lifo_buffer.lifo_list)
+
+        # empty list should return none
+        empty_list = list()
+        self.assertIsNone(SynapseLauncher.start_synapse_by_list_name(empty_list))
+
+        # test to start a synapse list with a new lifo
+        # we create a Lifo that is the current singleton
+        Singleton._instances = dict()
+        LifoManager.clean_saved_lifo()
+        lifo_buffer = LifoManager.get_singleton_lifo()
+        created_matched_synapse1 = MatchedSynapse(matched_synapse=self.synapse1)
+
+        lifo_buffer.lifo_list = [created_matched_synapse1]
+        # the current status of the singleton lifo should not move even after the call of SynapseLauncher
+        expected_result = [created_matched_synapse1]
+
+        # create a new call
+        with mock.patch("kalliope.core.Lifo.LIFOBuffer.execute"):
+            SynapseLauncher.start_synapse_by_list_name(["Synapse2", "Synapse3"],
+                                                       brain=self.brain_test,
+                                                       new_lifo=True)
+            # the current singleton should be the same
+            self.assertEqual(expected_result, lifo_buffer.lifo_list)
+
+        # test to start a synapse list with the singleton lifo
+        Singleton._instances = dict()
+        LifoManager.clean_saved_lifo()
+        lifo_buffer = LifoManager.get_singleton_lifo()
+        created_matched_synapse1 = MatchedSynapse(matched_synapse=self.synapse1)
+        # place a synapse in the singleton
+        lifo_buffer.lifo_list = [created_matched_synapse1]
+        # the current status of the singleton lifo should contain synapse launched in the next call
+        created_matched_synapse2 = MatchedSynapse(matched_synapse=self.synapse2)
+        created_matched_synapse3 = MatchedSynapse(matched_synapse=self.synapse3)
+        expected_result = [created_matched_synapse1, [created_matched_synapse2, created_matched_synapse3]]
+
+        with mock.patch("kalliope.core.Lifo.LIFOBuffer.execute"):
+            SynapseLauncher.start_synapse_by_list_name(["Synapse2", "Synapse3"],
+                                                       brain=self.brain_test)
+            # the singleton should now contains the synapse that was already there and the 2 other synapses
+            self.assertEqual(expected_result, lifo_buffer.lifo_list)
+
     def test_run_matching_synapse_from_order(self):
     def test_run_matching_synapse_from_order(self):
         # ------------------
         # ------------------
         # test_match_synapse1
         # test_match_synapse1
         # ------------------
         # ------------------
-        with mock.patch("kalliope.core.LIFOBuffer.execute"):
+        with mock.patch("kalliope.core.Lifo.LIFOBuffer.execute"):
             order_to_match = "this is the sentence"
             order_to_match = "this is the sentence"
 
 
             should_be_created_matched_synapse = MatchedSynapse(matched_synapse=self.synapse1,
             should_be_created_matched_synapse = MatchedSynapse(matched_synapse=self.synapse1,
@@ -85,7 +143,7 @@ class TestSynapseLauncher(unittest.TestCase):
                                                             brain=self.brain_test,
                                                             brain=self.brain_test,
                                                             settings=self.settings_test)
                                                             settings=self.settings_test)
 
 
-            lifo_buffer = LIFOBuffer()
+            lifo_buffer = LifoManager.get_singleton_lifo()
             self.assertEqual(expected_result, lifo_buffer.lifo_list)
             self.assertEqual(expected_result, lifo_buffer.lifo_list)
 
 
         # -------------------------
         # -------------------------
@@ -93,7 +151,8 @@ class TestSynapseLauncher(unittest.TestCase):
         # -------------------------
         # -------------------------
         # clean LIFO
         # clean LIFO
         Singleton._instances = dict()
         Singleton._instances = dict()
-        with mock.patch("kalliope.core.LIFOBuffer.execute"):
+        LifoManager.clean_saved_lifo()
+        with mock.patch("kalliope.core.Lifo.LIFOBuffer.execute"):
             order_to_match = "this is the second sentence"
             order_to_match = "this is the second sentence"
             should_be_created_matched_synapse1 = MatchedSynapse(matched_synapse=self.synapse1,
             should_be_created_matched_synapse1 = MatchedSynapse(matched_synapse=self.synapse1,
                                                                 user_order=order_to_match,
                                                                 user_order=order_to_match,
@@ -106,47 +165,46 @@ class TestSynapseLauncher(unittest.TestCase):
             SynapseLauncher.run_matching_synapse_from_order(order_to_match,
             SynapseLauncher.run_matching_synapse_from_order(order_to_match,
                                                             brain=self.brain_test,
                                                             brain=self.brain_test,
                                                             settings=self.settings_test)
                                                             settings=self.settings_test)
-            lifo_buffer = LIFOBuffer()
+            lifo_buffer = LifoManager.get_singleton_lifo()
             self.assertEqual(expected_result, lifo_buffer.lifo_list)
             self.assertEqual(expected_result, lifo_buffer.lifo_list)
 
 
         # -------------------------
         # -------------------------
-        # test_match_default_synapse
+        # test_call_hook_order_not_found
         # -------------------------
         # -------------------------
         # clean LIFO
         # clean LIFO
         Singleton._instances = dict()
         Singleton._instances = dict()
-        with mock.patch("kalliope.core.LIFOBuffer.execute"):
+        LifoManager.clean_saved_lifo()
+        with mock.patch("kalliope.core.HookManager.on_order_not_found") as mock_hook:
             order_to_match = "not existing sentence"
             order_to_match = "not existing sentence"
-            should_be_created_matched_synapse = MatchedSynapse(matched_synapse=self.synapse3,
-                                                               user_order=order_to_match,
-                                                               matched_order=None)
 
 
-            expected_result = [[should_be_created_matched_synapse]]
             SynapseLauncher.run_matching_synapse_from_order(order_to_match,
             SynapseLauncher.run_matching_synapse_from_order(order_to_match,
                                                             brain=self.brain_test,
                                                             brain=self.brain_test,
                                                             settings=self.settings_test)
                                                             settings=self.settings_test)
-            lifo_buffer = LIFOBuffer()
-            self.assertEqual(expected_result, lifo_buffer.lifo_list)
+            mock_hook.assert_called_with()
+
+        mock_hook.reset_mock()
 
 
         # -------------------------
         # -------------------------
-        # test_no_match_and_no_default_synapse
+        # test_call_hook_order_found
         # -------------------------
         # -------------------------
         # clean LIFO
         # clean LIFO
         Singleton._instances = dict()
         Singleton._instances = dict()
-        with mock.patch("kalliope.core.LIFOBuffer.execute"):
-            order_to_match = "not existing sentence"
-            new_settings = Settings()
-            expected_result = [[]]
-            SynapseLauncher.run_matching_synapse_from_order(order_to_match,
-                                                            brain=self.brain_test,
-                                                            settings=new_settings)
-            lifo_buffer = LIFOBuffer()
-            self.assertEqual(expected_result, lifo_buffer.lifo_list)
+        with mock.patch("kalliope.core.Lifo.LIFOBuffer.execute"):
+            with mock.patch("kalliope.core.HookManager.on_order_found") as mock_hook:
+                order_to_match = "this is the second sentence"
+                new_settings = Settings()
+                SynapseLauncher.run_matching_synapse_from_order(order_to_match,
+                                                                brain=self.brain_test,
+                                                                settings=new_settings)
+                mock_hook.assert_called_with()
+
+        mock_hook.reset_mock()
 
 
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()
 
 
     # suite = unittest.TestSuite()
     # suite = unittest.TestSuite()
-    # suite.addTest(TestSynapseLauncher("test_run_matching_synapse_from_order"))
+    # suite.addTest(TestSynapseLauncher("test_start_synapse_by_list_name"))
     # runner = unittest.TextTestRunner()
     # runner = unittest.TextTestRunner()
     # runner.run(suite)
     # runner.run(suite)

+ 0 - 1
install/files/python_requirements.txt

@@ -22,7 +22,6 @@ transitions>=0.4.3
 sounddevice>=0.3.7
 sounddevice>=0.3.7
 SoundFile>=0.9.0
 SoundFile>=0.9.0
 pyalsaaudio>=0.8.4
 pyalsaaudio>=0.8.4
-RPi.GPIO>=0.6.3
 sox>=1.3.0
 sox>=1.3.0
 paho-mqtt>=1.3.0
 paho-mqtt>=1.3.0
 voicerss_tts>=1.0.3
 voicerss_tts>=1.0.3

+ 12 - 19
kalliope/__init__.py

@@ -10,7 +10,6 @@ from kalliope.core import Utils
 from kalliope.core.ConfigurationManager import SettingLoader
 from kalliope.core.ConfigurationManager import SettingLoader
 from kalliope.core.ConfigurationManager.BrainLoader import BrainLoader
 from kalliope.core.ConfigurationManager.BrainLoader import BrainLoader
 from kalliope.core.SignalLauncher import SignalLauncher
 from kalliope.core.SignalLauncher import SignalLauncher
-from kalliope.core.Utils.RpiUtils import RpiUtils
 from flask import Flask
 from flask import Flask
 from kalliope.core.RestAPI.FlaskAPI import FlaskAPI
 from kalliope.core.RestAPI.FlaskAPI import FlaskAPI
 
 
@@ -258,21 +257,15 @@ def start_kalliope(settings, brain):
     list_signals_class_to_load = get_list_signal_class_to_load(brain)
     list_signals_class_to_load = get_list_signal_class_to_load(brain)
 
 
     # start each class name
     # start each class name
-    try:
-        for signal_class_name in list_signals_class_to_load:
-            signal_instance = SignalLauncher.launch_signal_class_by_name(signal_name=signal_class_name,
-                                                                         settings=settings)
-            if signal_instance is not None:
-                signal_instance.daemon = True
-                signal_instance.start()
-
-        while True:  # keep main thread alive
-            time.sleep(0.1)
-
-    except (KeyboardInterrupt, SystemExit):
-        # we need to switch GPIO pin to default status if we are using a Rpi
-        if settings.rpi_settings:
-            Utils.print_info("GPIO cleaned")
-            logger.debug("Clean GPIO")
-            import RPi.GPIO as GPIO
-            GPIO.cleanup()
+
+    for signal_class_name in list_signals_class_to_load:
+        signal_instance = SignalLauncher.launch_signal_class_by_name(signal_name=signal_class_name,
+                                                                     settings=settings)
+        if signal_instance is not None:
+            signal_instance.daemon = True
+            signal_instance.start()
+
+    while True:  # keep main thread alive
+        time.sleep(0.1)
+
+

+ 1 - 1
kalliope/_version.py

@@ -1,2 +1,2 @@
 # https://www.python.org/dev/peps/pep-0440/
 # https://www.python.org/dev/peps/pep-0440/
-version_str = "0.4.6"
+version_str = "0.5.0b"

+ 21 - 5
kalliope/brain.yml

@@ -4,8 +4,7 @@
       - order: "Bonjour"
       - order: "Bonjour"
     neurons:
     neurons:
       - say:
       - say:
-          message:
-            - "Bonjour monsieur"
+          message: "Bonjour monsieur"
 
 
   - name: "say-hello-en"
   - name: "say-hello-en"
     signals:
     signals:
@@ -15,9 +14,8 @@
           message:
           message:
             - "Hello sir"
             - "Hello sir"
 
 
-  - name: "default-synapse"
-    signals:
-      - order: "default-synapse-order"
+  - name: "order-not-found-synapse"
+    signals: []
     neurons:
     neurons:
       - say:
       - say:
           message:
           message:
@@ -26,3 +24,21 @@
             - "Veuillez renouveller votre ordre"
             - "Veuillez renouveller votre ordre"
             - "Veuillez reformuller s'il vous plait"
             - "Veuillez reformuller s'il vous plait"
             - "Je n'ai pas saisi cet ordre"
             - "Je n'ai pas saisi cet ordre"
+
+  - name: "on-triggered-synapse"
+    signals: []
+    neurons:
+      - say:
+          message:
+            - "Oui monsieur?"
+            - "Je vous écoute"
+            - "Monsieur?"
+            - "Que puis-je faire pour vous?"
+            - "J'écoute"
+            - "Oui?"
+
+  - name: "on-start-synapse"
+    signals: []
+    neurons:
+      - say:
+          message: "je suis prête"

+ 38 - 188
kalliope/core/ConfigurationManager/SettingLoader.py

@@ -2,7 +2,6 @@ import logging
 import os
 import os
 from six import with_metaclass
 from six import with_metaclass
 
 
-from kalliope.core.Models.RpiSettings import RpiSettings
 from kalliope.core.Models.RecognitionOptions import RecognitionOptions
 from kalliope.core.Models.RecognitionOptions import RecognitionOptions
 from .YAMLLoader import YAMLLoader
 from .YAMLLoader import YAMLLoader
 from kalliope.core.Models.Resources import Resources
 from kalliope.core.Models.Resources import Resources
@@ -108,19 +107,13 @@ class SettingLoader(with_metaclass(Singleton, object)):
         ttss = self._get_ttss(settings)
         ttss = self._get_ttss(settings)
         triggers = self._get_triggers(settings)
         triggers = self._get_triggers(settings)
         players = self._get_players(settings)
         players = self._get_players(settings)
-        random_wake_up_answers = self._get_random_wake_up_answers(settings)
-        random_wake_up_sound = self._get_random_wake_up_sounds(settings)
-        play_on_ready_notification = self._get_play_on_ready_notification(settings)
-        on_ready_answers = self._get_on_ready_answers(settings)
-        on_ready_sounds = self._get_on_ready_sounds(settings)
         rest_api = self._get_rest_api(settings)
         rest_api = self._get_rest_api(settings)
         cache_path = self._get_cache_path(settings)
         cache_path = self._get_cache_path(settings)
-        default_synapse = self._get_default_synapse(settings)
         resources = self._get_resources(settings)
         resources = self._get_resources(settings)
         variables = self._get_variables(settings)
         variables = self._get_variables(settings)
-        rpi_settings = self._get_rpi_settings(settings)
         recognition_options = self._get_recognition_options(settings)
         recognition_options = self._get_recognition_options(settings)
         start_options = self._get_start_options(settings)
         start_options = self._get_start_options(settings)
+        hooks = self._get_hooks(settings)
 
 
         # Load the setting singleton with the parameters
         # Load the setting singleton with the parameters
         setting_object.default_tts_name = default_tts_name
         setting_object.default_tts_name = default_tts_name
@@ -131,19 +124,13 @@ class SettingLoader(with_metaclass(Singleton, object)):
         setting_object.ttss = ttss
         setting_object.ttss = ttss
         setting_object.triggers = triggers
         setting_object.triggers = triggers
         setting_object.players = players
         setting_object.players = players
-        setting_object.random_wake_up_answers = random_wake_up_answers
-        setting_object.random_wake_up_sounds = random_wake_up_sound
-        setting_object.play_on_ready_notification = play_on_ready_notification
-        setting_object.on_ready_answers = on_ready_answers
-        setting_object.on_ready_sounds = on_ready_sounds
         setting_object.rest_api = rest_api
         setting_object.rest_api = rest_api
         setting_object.cache_path = cache_path
         setting_object.cache_path = cache_path
-        setting_object.default_synapse = default_synapse
         setting_object.resources = resources
         setting_object.resources = resources
         setting_object.variables = variables
         setting_object.variables = variables
-        setting_object.rpi_settings = rpi_settings
         setting_object.recognition_options = recognition_options
         setting_object.recognition_options = recognition_options
         setting_object.start_options = start_options
         setting_object.start_options = start_options
+        setting_object.hooks = hooks
 
 
         return setting_object
         return setting_object
 
 
@@ -410,72 +397,6 @@ class SettingLoader(with_metaclass(Singleton, object)):
                 players.append(new_player)
                 players.append(new_player)
         return players
         return players
 
 
-    @staticmethod
-    def _get_random_wake_up_answers(settings):
-        """
-        Return a list of the wake up answers set up on the settings.yml file
-
-        :param settings: The YAML settings file
-        :type settings: dict
-        :return: List of wake up answers
-        :rtype: list of str
-
-        :Example:
-
-            wakeup = cls._get_random_wake_up_answers(settings)
-
-        .. seealso::
-        .. raises:: NullSettingException
-        .. warnings:: Class Method and Private
-        """
-
-        try:
-            random_wake_up_answers_list = settings["random_wake_up_answers"]
-        except KeyError:
-            # User does not provide this settings
-            return None
-
-        # The list cannot be empty
-        if random_wake_up_answers_list is None:
-            raise NullSettingException("random_wake_up_answers settings is null")
-
-        return random_wake_up_answers_list
-
-    @staticmethod
-    def _get_random_wake_up_sounds(settings):
-        """
-        Return a list of the wake up sounds set up on the settings.yml file
-
-        :param settings: The YAML settings file
-        :type settings: dict
-        :return: list of wake up sounds
-        :rtype: list of str
-
-        :Example:
-
-            wakeup_sounds = cls._get_random_wake_up_sounds(settings)
-
-        .. seealso::
-        .. raises:: NullSettingException
-        .. warnings:: Class Method and Private
-        """
-
-        try:
-            random_wake_up_sounds_list = settings["random_wake_up_sounds"]
-            # In case files are declared in settings.yml, make sure kalliope can access them.
-            for sound in random_wake_up_sounds_list:
-                if Utils.get_real_file_path(sound) is None:
-                    raise SettingInvalidException("sound file %s not found" % sound)
-        except KeyError:
-            # User does not provide this settings
-            return None
-
-        # The the setting is present, the list cannot be empty
-        if random_wake_up_sounds_list is None:
-            raise NullSettingException("random_wake_up_sounds settings is empty")
-
-        return random_wake_up_sounds_list
-
     @staticmethod
     @staticmethod
     def _get_rest_api(settings):
     def _get_rest_api(settings):
         """
         """
@@ -575,33 +496,6 @@ class SettingLoader(with_metaclass(Singleton, object)):
         else:
         else:
             raise SettingInvalidException("The cache_path seems to be invalid: %s" % cache_path)
             raise SettingInvalidException("The cache_path seems to be invalid: %s" % cache_path)
 
 
-    @staticmethod
-    def _get_default_synapse(settings):
-        """
-        Return the name of the default synapse
-
-        :param settings: The YAML settings file
-        :type settings: dict
-        :return: the default synapse name
-        :rtype: String
-
-        :Example:
-
-            default_synapse = cls._get_default_synapse(settings)
-
-        .. seealso::
-        .. raises:: SettingNotFound, NullSettingException, SettingInvalidException
-        .. warnings:: Class Method and Private
-        """
-
-        try:
-            default_synapse = settings["default_synapse"]
-            logger.debug("Default synapse: %s" % default_synapse)
-        except KeyError:
-            default_synapse = None
-
-        return default_synapse
-
     @staticmethod
     @staticmethod
     def _get_resources(settings):
     def _get_resources(settings):
         """
         """
@@ -687,58 +581,6 @@ class SettingLoader(with_metaclass(Singleton, object)):
 
 
         return resource_object
         return resource_object
 
 
-    @staticmethod
-    def _get_play_on_ready_notification(settings):
-        """
-        Return the on_ready_notification setting. If the user didn't provided it the default is never
-        :param settings: The YAML settings file
-        :type settings: dict
-        :return:
-        """
-        try:
-            play_on_ready_notification = settings["play_on_ready_notification"]
-        except KeyError:
-            # User does not provide this settings, by default we set it to never
-            play_on_ready_notification = "never"
-            return play_on_ready_notification
-        return play_on_ready_notification
-
-    @staticmethod
-    def _get_on_ready_answers(settings):
-        """
-        Return the list of on_ready_answers string from the settings.
-        :param settings: The YAML settings file
-        :type settings: dict
-        :return: String parameter on_ready_answers
-        """
-        try:
-            on_ready_answers = settings["on_ready_answers"]
-        except KeyError:
-            # User does not provide this settings
-            return None
-
-        return on_ready_answers
-
-    @staticmethod
-    def _get_on_ready_sounds(settings):
-        """
-        Return the list of on_ready_sounds string from the settings.
-        :param settings: The YAML settings file
-        :type settings: dict
-        :return: String parameter on_ready_sounds
-        """
-        try:
-            on_ready_sounds = settings["on_ready_sounds"]
-            # In case files are declared in settings.yml, make sure kalliope can access them.
-            for sound in on_ready_sounds:
-                if Utils.get_real_file_path(sound) is None:
-                    raise SettingInvalidException("sound file %s not found" % sound)
-        except KeyError:
-            # User does not provide this settings
-            return None
-
-        return on_ready_sounds
-
     @staticmethod
     @staticmethod
     def _get_variables(settings):
     def _get_variables(settings):
         """
         """
@@ -762,34 +604,6 @@ class SettingLoader(with_metaclass(Singleton, object)):
             # User does not provide this settings
             # User does not provide this settings
             return dict()
             return dict()
 
 
-    @staticmethod
-    def _get_rpi_settings(settings):
-        """
-        return RpiSettings object
-        :param settings: The loaded YAML settings file
-        :return:
-        """
-
-        try:
-            rpi_settings_dict = settings["rpi"]
-            rpi_settings = RpiSettings()
-            # affect pin if there are declared
-            if "pin_mute_button" in rpi_settings_dict:
-                rpi_settings.pin_mute_button = rpi_settings_dict["pin_mute_button"]
-            if "pin_led_started" in rpi_settings_dict:
-                rpi_settings.pin_led_started = rpi_settings_dict["pin_led_started"]
-            if "pin_led_muted" in rpi_settings_dict:
-                rpi_settings.pin_led_muted = rpi_settings_dict["pin_led_muted"]
-            if "pin_led_talking" in rpi_settings_dict:
-                rpi_settings.pin_led_talking = rpi_settings_dict["pin_led_talking"]
-            if "pin_led_listening" in rpi_settings_dict:
-                rpi_settings.pin_led_listening = rpi_settings_dict["pin_led_listening"]
-
-            return rpi_settings
-        except KeyError:
-            logger.debug("[SettingsLoader] No Rpi config")
-            return None
-
     @staticmethod
     @staticmethod
     def _get_recognition_options(settings):
     def _get_recognition_options(settings):
         """
         """
@@ -845,3 +659,39 @@ class SettingLoader(with_metaclass(Singleton, object)):
 
 
         logger.debug("Start options: %s" % options)
         logger.debug("Start options: %s" % options)
         return options
         return options
+
+    @staticmethod
+    def _get_hooks(settings):
+        """
+        Return hooks settings
+        :param settings: The YAML settings file
+        :return: A dict containing hooks
+        :rtype: dict
+        """
+
+        try:
+            hooks = settings["hooks"]
+
+        except KeyError:
+            # if the user haven't set any hooks we define an empty dict
+            hooks = dict()
+
+        all_hook = [
+            "on_start",
+            "on_waiting_for_trigger",
+            "on_triggered",
+            "on_start_listening",
+            "on_stop_listening",
+            "on_order_found",
+            "on_order_not_found",
+            "on_mute",
+            "on_unmute",
+            "on_start_speaking",
+            "on_stop_speaking"
+        ]
+
+        for key in all_hook:
+            if key not in hooks:
+                hooks[key] = None
+
+        return hooks

+ 73 - 0
kalliope/core/HookManager.py

@@ -0,0 +1,73 @@
+from kalliope.core.ConfigurationManager import SettingLoader
+import logging
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+
+class HookManager(object):
+
+    @classmethod
+    def on_start(cls):
+        return cls.execute_synapses_in_hook_name("on_start")
+
+    @classmethod
+    def on_waiting_for_trigger(cls):
+        return cls.execute_synapses_in_hook_name("on_waiting_for_trigger")
+
+    @classmethod
+    def on_triggered(cls):
+        return cls.execute_synapses_in_hook_name("on_triggered")
+
+    @classmethod
+    def on_start_listening(cls):
+        return cls.execute_synapses_in_hook_name("on_start_listening")
+
+    @classmethod
+    def on_stop_listening(cls):
+        return cls.execute_synapses_in_hook_name("on_stop_listening")
+
+    @classmethod
+    def on_order_found(cls):
+        return cls.execute_synapses_in_hook_name("on_order_found")
+
+    @classmethod
+    def on_order_not_found(cls):
+        return cls.execute_synapses_in_hook_name("on_order_not_found")
+
+    @classmethod
+    def on_mute(cls):
+        return cls.execute_synapses_in_hook_name("on_mute")
+
+    @classmethod
+    def on_unmute(cls):
+        return cls.execute_synapses_in_hook_name("on_unmute")
+
+    @classmethod
+    def on_start_speaking(cls):
+        return cls.execute_synapses_in_hook_name("on_start_speaking")
+
+    @classmethod
+    def on_stop_speaking(cls):
+        return cls.execute_synapses_in_hook_name("on_stop_speaking")
+
+    @classmethod
+    def execute_synapses_in_hook_name(cls, hook_name):
+        # need to import SynapseLauncher from here to avoid cross import
+        from kalliope.core.SynapseLauncher import SynapseLauncher
+
+        logger.debug("[HookManager] calling synapses in hook name: %s" % hook_name)
+
+        settings = SettingLoader().settings
+
+        # list of synapse to execute
+        list_synapse = settings.hooks[hook_name]
+        logger.debug("[HookManager] hook: %s , type: %s" % (hook_name, type(list_synapse)))
+
+        if isinstance(list_synapse, list):
+            return SynapseLauncher.start_synapse_by_list_name(list_synapse, new_lifo=True)
+
+        if isinstance(list_synapse, str):
+            return SynapseLauncher.start_synapse_by_name(list_synapse, new_lifo=True)
+
+        return None

+ 1 - 1
kalliope/core/LIFOBuffer.py → kalliope/core/Lifo/LIFOBuffer.py

@@ -25,7 +25,7 @@ class SynapseListAddedToLIFO(Exception):
     pass
     pass
 
 
 
 
-class LIFOBuffer(with_metaclass(Singleton, object)):
+class LIFOBuffer(object):
     """
     """
     This class is a LIFO list of synapse to process where the last synapse list to enter will be the first synapse
     This class is a LIFO list of synapse to process where the last synapse list to enter will be the first synapse
     list to be processed.
     list to be processed.

+ 30 - 0
kalliope/core/Lifo/LifoManager.py

@@ -0,0 +1,30 @@
+import logging
+
+from kalliope.core.Lifo.LIFOBuffer import LIFOBuffer
+from six import with_metaclass
+from kalliope.core.Models import Singleton
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+
+class LifoManager(with_metaclass(Singleton, object)):
+
+    lifo_buffer = LIFOBuffer()
+
+    @classmethod
+    def get_singleton_lifo(cls):
+        return cls.lifo_buffer
+
+    @classmethod
+    def get_new_lifo(cls):
+        """
+        This class is used to manage hooks "on_start_speaking" and "on_stop_speaking".
+        :return:
+        """
+        return LIFOBuffer()
+
+    @classmethod
+    def clean_saved_lifo(cls):
+        cls.lifo_buffer = LIFOBuffer()
+

+ 2 - 0
kalliope/core/Lifo/__init__.py

@@ -0,0 +1,2 @@
+from kalliope.core.Lifo.LIFOBuffer import LIFOBuffer
+from kalliope.core.Lifo.LifoManager import LifoManager

+ 0 - 35
kalliope/core/Models/RpiSettings.py

@@ -1,35 +0,0 @@
-
-
-class RpiSettings(object):
-
-    def __init__(self, pin_mute_button=None, pin_led_started=None, pin_led_muted=None,
-                 pin_led_talking=None, pin_led_listening=None):
-        self.pin_mute_button = pin_mute_button
-        self.pin_led_started = pin_led_started
-        self.pin_led_muted = pin_led_muted
-        self.pin_led_talking = pin_led_talking
-        self.pin_led_listening = pin_led_listening
-
-    def __str__(self):
-        return str(self.serialize())
-
-    def serialize(self):
-        """
-        This method allows to serialize in a proper way this object        
-        """
-
-        return {
-            'pin_mute_button': self.pin_mute_button,
-            'pin_led_started': self.pin_led_started,
-            'pin_led_muted': self.pin_led_muted,
-            'pin_led_talking': self.pin_led_talking,
-            'pin_led_listening': self.pin_led_listening,
-        }
-
-    def __eq__(self, other):
-        """
-        This is used to compare 2 objects
-        :param other:
-        :return:
-        """
-        return self.__dict__ == other.__dict__

+ 4 - 22
kalliope/core/Models/Settings.py

@@ -15,21 +15,15 @@ class Settings(object):
                  default_player_name=None,
                  default_player_name=None,
                  ttss=None,
                  ttss=None,
                  stts=None,
                  stts=None,
-                 random_wake_up_answers=None,
-                 random_wake_up_sounds=None,
-                 play_on_ready_notification=None,
-                 on_ready_answers=None,
-                 on_ready_sounds=None,
                  triggers=None,
                  triggers=None,
                  players=None,
                  players=None,
                  rest_api=None,
                  rest_api=None,
                  cache_path=None,
                  cache_path=None,
-                 default_synapse=None,
                  resources=None,
                  resources=None,
                  variables=None,
                  variables=None,
-                 rpi_settings=None,
                  recognition_options=None,
                  recognition_options=None,
-                 start_options=None):
+                 start_options=None,
+                 hooks=None):
 
 
         self.default_tts_name = default_tts_name
         self.default_tts_name = default_tts_name
         self.default_stt_name = default_stt_name
         self.default_stt_name = default_stt_name
@@ -37,23 +31,17 @@ class Settings(object):
         self.default_player_name = default_player_name
         self.default_player_name = default_player_name
         self.ttss = ttss
         self.ttss = ttss
         self.stts = stts
         self.stts = stts
-        self.random_wake_up_answers = random_wake_up_answers
-        self.random_wake_up_sounds = random_wake_up_sounds
-        self.play_on_ready_notification = play_on_ready_notification
-        self.on_ready_answers = on_ready_answers
-        self.on_ready_sounds = on_ready_sounds
         self.triggers = triggers
         self.triggers = triggers
         self.players = players
         self.players = players
         self.rest_api = rest_api
         self.rest_api = rest_api
         self.cache_path = cache_path
         self.cache_path = cache_path
-        self.default_synapse = default_synapse
         self.resources = resources
         self.resources = resources
         self.variables = variables
         self.variables = variables
         self.machine = platform.machine()   # can be x86_64 or armv7l
         self.machine = platform.machine()   # can be x86_64 or armv7l
         self.kalliope_version = current_kalliope_version
         self.kalliope_version = current_kalliope_version
-        self.rpi_settings = rpi_settings
         self.recognition_options = recognition_options
         self.recognition_options = recognition_options
         self.start_options = start_options
         self.start_options = start_options
+        self.hooks = hooks
 
 
     def serialize(self):
     def serialize(self):
         """
         """
@@ -70,23 +58,17 @@ class Settings(object):
             'default_player_name': self.default_player_name,
             'default_player_name': self.default_player_name,
             'ttss': self.ttss,
             'ttss': self.ttss,
             'stts': self.stts,
             'stts': self.stts,
-            'random_wake_up_answers': self.random_wake_up_answers,
-            'random_wake_up_sounds': self.random_wake_up_sounds,
-            'play_on_ready_notification': self.play_on_ready_notification,
-            'on_ready_answers': self.on_ready_answers,
-            'on_ready_sounds': self.on_ready_sounds,
             'triggers': self.triggers,
             'triggers': self.triggers,
             'players': self.players,
             'players': self.players,
             'rest_api': self.rest_api.serialize(),
             'rest_api': self.rest_api.serialize(),
             'cache_path': self.cache_path,
             'cache_path': self.cache_path,
-            'default_synapse': self.default_synapse,
             'resources': self.resources,
             'resources': self.resources,
             'variables': self.variables,
             'variables': self.variables,
             'machine': self.machine,
             'machine': self.machine,
             'kalliope_version': self.kalliope_version,
             'kalliope_version': self.kalliope_version,
-            'rpi_settings': self.rpi_settings.serialize() if self.rpi_settings is not None else None,
             'recognition_options': self.recognition_options.serialize() if self.recognition_options is not None else None,
             'recognition_options': self.recognition_options.serialize() if self.recognition_options is not None else None,
             'start_options': self.start_options,
             'start_options': self.start_options,
+            'hooks': self.hooks
         }
         }
 
 
     def __str__(self):
     def __str__(self):

+ 0 - 1
kalliope/core/Models/__init__.py

@@ -3,5 +3,4 @@ from .Resources import Resources
 from .Brain import Brain
 from .Brain import Brain
 from .Synapse import Synapse
 from .Synapse import Synapse
 from .Neuron import Neuron
 from .Neuron import Neuron
-from .RpiSettings import RpiSettings
 from .Signal import Signal
 from .Signal import Signal

+ 5 - 22
kalliope/core/NeuronModule.py

@@ -7,13 +7,13 @@ import six
 from jinja2 import Template
 from jinja2 import Template
 
 
 from kalliope.core import OrderListener
 from kalliope.core import OrderListener
+from kalliope.core.HookManager import HookManager
 from kalliope.core.ConfigurationManager import SettingLoader, BrainLoader
 from kalliope.core.ConfigurationManager import SettingLoader, BrainLoader
 from kalliope.core.Cortex import Cortex
 from kalliope.core.Cortex import Cortex
-from kalliope.core.LIFOBuffer import LIFOBuffer
+from kalliope.core.Lifo.LifoManager import LifoManager
 from kalliope.core.Models.MatchedSynapse import MatchedSynapse
 from kalliope.core.Models.MatchedSynapse import MatchedSynapse
 from kalliope.core.NeuronExceptions import NeuronExceptions
 from kalliope.core.NeuronExceptions import NeuronExceptions
 from kalliope.core.OrderAnalyser import OrderAnalyser
 from kalliope.core.OrderAnalyser import OrderAnalyser
-from kalliope.core.Utils.RpiUtils import RpiUtils
 from kalliope.core.Utils.Utils import Utils
 from kalliope.core.Utils.Utils import Utils
 
 
 logging.basicConfig()
 logging.basicConfig()
@@ -174,6 +174,7 @@ class NeuronModule(object):
                 logger.debug("[NeuronModule] no_voice is True, Kalliope is muted")
                 logger.debug("[NeuronModule] no_voice is True, Kalliope is muted")
             else:
             else:
                 logger.debug("[NeuronModule] no_voice is False, make Kalliope speaking")
                 logger.debug("[NeuronModule] no_voice is False, make Kalliope speaking")
+                HookManager.on_start_speaking()
                 # get the instance of the TTS module
                 # get the instance of the TTS module
                 tts_folder = None
                 tts_folder = None
                 if self.settings.resources:
                 if self.settings.resources:
@@ -182,14 +183,10 @@ class NeuronModule(object):
                                                                             module_name=self.tts.name,
                                                                             module_name=self.tts.name,
                                                                             parameters=self.tts.parameters,
                                                                             parameters=self.tts.parameters,
                                                                             resources_dir=tts_folder)
                                                                             resources_dir=tts_folder)
-                # Kalliope will talk, turn on the LED
-                self.switch_on_led_talking(rpi_settings=self.settings.rpi_settings, on=True)
 
 
                 # generate the audio file and play it
                 # generate the audio file and play it
                 tts_module_instance.say(tts_message)
                 tts_module_instance.say(tts_message)
-
-                # Kalliope has finished to talk, turn off the LED
-                self.switch_on_led_talking(rpi_settings=self.settings.rpi_settings, on=False)
+                HookManager.on_stop_speaking()
 
 
     def _get_message_from_dict(self, message_dict):
     def _get_message_from_dict(self, message_dict):
         """
         """
@@ -257,7 +254,7 @@ class NeuronModule(object):
         list_synapse_to_process = list()
         list_synapse_to_process = list()
         list_synapse_to_process.append(matched_synapse)
         list_synapse_to_process.append(matched_synapse)
         # get the singleton
         # get the singleton
-        lifo_buffer = LIFOBuffer()
+        lifo_buffer = LifoManager.get_singleton_lifo()
         lifo_buffer.add_synapse_list_to_lifo(list_synapse_to_process, high_priority=high_priority)
         lifo_buffer.add_synapse_list_to_lifo(list_synapse_to_process, high_priority=high_priority)
         lifo_buffer.execute(is_api_call=is_api_call, no_voice=no_voice)
         lifo_buffer.execute(is_api_call=is_api_call, no_voice=no_voice)
 
 
@@ -327,17 +324,3 @@ class NeuronModule(object):
 
 
         logger.debug("[NeuronModule] TTS args: %s" % tts_object)
         logger.debug("[NeuronModule] TTS args: %s" % tts_object)
         return tts_object
         return tts_object
-
-    @staticmethod
-    def switch_on_led_talking(rpi_settings, on):
-        """
-        Call the Rpi utils class to switch the led talking if the setting has been specified by the user
-        :param rpi_settings: Rpi
-        :param on: True if the led need to be switched to on
-        """
-        if rpi_settings:
-            if rpi_settings.pin_led_talking:
-                if on:
-                    RpiUtils.switch_pin_to_on(rpi_settings.pin_led_talking)
-                else:
-                    RpiUtils.switch_pin_to_off(rpi_settings.pin_led_talking)

+ 3 - 4
kalliope/core/RestAPI/FlaskAPI.py

@@ -12,13 +12,12 @@ from werkzeug.utils import secure_filename
 from kalliope import SignalLauncher
 from kalliope import SignalLauncher
 from kalliope._version import version_str
 from kalliope._version import version_str
 from kalliope.core.ConfigurationManager import SettingLoader, BrainLoader
 from kalliope.core.ConfigurationManager import SettingLoader, BrainLoader
-from kalliope.core.LIFOBuffer import LIFOBuffer
+from kalliope.core.Lifo.LifoManager import LifoManager
 from kalliope.core.Models.MatchedSynapse import MatchedSynapse
 from kalliope.core.Models.MatchedSynapse import MatchedSynapse
 from kalliope.core.OrderListener import OrderListener
 from kalliope.core.OrderListener import OrderListener
 from kalliope.core.RestAPI.utils import requires_auth
 from kalliope.core.RestAPI.utils import requires_auth
 from kalliope.core.SynapseLauncher import SynapseLauncher
 from kalliope.core.SynapseLauncher import SynapseLauncher
 from kalliope.core.Utils.FileManager import FileManager
 from kalliope.core.Utils.FileManager import FileManager
-from kalliope.signals.order import Order
 
 
 logging.basicConfig()
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
 logger = logging.getLogger("kalliope")
@@ -174,8 +173,8 @@ class FlaskAPI(threading.Thread):
         else:
         else:
             # generate a MatchedSynapse from the synapse
             # generate a MatchedSynapse from the synapse
             matched_synapse = MatchedSynapse(matched_synapse=synapse_target, overriding_parameter=parameters)
             matched_synapse = MatchedSynapse(matched_synapse=synapse_target, overriding_parameter=parameters)
-            # get the current LIFO buffer
-            lifo_buffer = LIFOBuffer()
+            # get the current LIFO buffer from the singleton
+            lifo_buffer = LifoManager.get_singleton_lifo()
             # this is a new call we clean up the LIFO
             # this is a new call we clean up the LIFO
             lifo_buffer.clean()
             lifo_buffer.clean()
             lifo_buffer.add_synapse_list_to_lifo([matched_synapse])
             lifo_buffer.add_synapse_list_to_lifo([matched_synapse])

+ 55 - 17
kalliope/core/SynapseLauncher.py

@@ -1,9 +1,9 @@
 import logging
 import logging
 
 
 from kalliope.core.ConfigurationManager import BrainLoader
 from kalliope.core.ConfigurationManager import BrainLoader
-from kalliope.core.LIFOBuffer import LIFOBuffer
+from kalliope.core.HookManager import HookManager
+from kalliope.core.Lifo.LifoManager import LifoManager
 from kalliope.core.Models.MatchedSynapse import MatchedSynapse
 from kalliope.core.Models.MatchedSynapse import MatchedSynapse
-from kalliope.core.NeuronLauncher import NeuronLauncher
 from kalliope.core.OrderAnalyser import OrderAnalyser
 from kalliope.core.OrderAnalyser import OrderAnalyser
 
 
 
 
@@ -23,22 +23,29 @@ class SynapseNameNotFound(Exception):
 class SynapseLauncher(object):
 class SynapseLauncher(object):
 
 
     @classmethod
     @classmethod
-    def start_synapse_by_name(cls, name, brain=None, overriding_parameter_dict=None):
+    def start_synapse_by_name(cls, name, brain=None, overriding_parameter_dict=None, new_lifo=False):
         """
         """
         Start a synapse by it's name
         Start a synapse by it's name
         :param name: Name (Unique ID) of the synapse to launch
         :param name: Name (Unique ID) of the synapse to launch
         :param brain: Brain instance
         :param brain: Brain instance
         :param overriding_parameter_dict: parameter to pass to neurons
         :param overriding_parameter_dict: parameter to pass to neurons
+        :param new_lifo: If True, ask the HookManager to return a new lifo and not the singleton
         """
         """
         logger.debug("[SynapseLauncher] start_synapse_by_name called with synapse name: %s " % name)
         logger.debug("[SynapseLauncher] start_synapse_by_name called with synapse name: %s " % name)
+
+        if brain is None:
+            brain = BrainLoader().brain
+
         # check if we have found and launched the synapse
         # check if we have found and launched the synapse
         synapse = brain.get_synapse_by_name(synapse_name=name)
         synapse = brain.get_synapse_by_name(synapse_name=name)
 
 
         if not synapse:
         if not synapse:
             raise SynapseNameNotFound("The synapse name \"%s\" does not exist in the brain file" % name)
             raise SynapseNameNotFound("The synapse name \"%s\" does not exist in the brain file" % name)
         else:
         else:
-            # get our singleton LIFO
-            lifo_buffer = LIFOBuffer()
+            if new_lifo:
+                lifo_buffer = LifoManager.get_new_lifo()
+            else:
+                lifo_buffer = LifoManager.get_singleton_lifo()
             list_synapse_to_process = list()
             list_synapse_to_process = list()
             new_matching_synapse = MatchedSynapse(matched_synapse=synapse,
             new_matching_synapse = MatchedSynapse(matched_synapse=synapse,
                                                   matched_order=None,
                                                   matched_order=None,
@@ -48,6 +55,45 @@ class SynapseLauncher(object):
             lifo_buffer.add_synapse_list_to_lifo(list_synapse_to_process)
             lifo_buffer.add_synapse_list_to_lifo(list_synapse_to_process)
             return lifo_buffer.execute(is_api_call=True)
             return lifo_buffer.execute(is_api_call=True)
 
 
+    @classmethod
+    def start_synapse_by_list_name(cls, list_name, brain=None, overriding_parameter_dict=None, new_lifo=False):
+        """
+        Start synapses by their name
+        :param list_name: List of name of the synapse to launch
+        :param brain: Brain instance
+        :param overriding_parameter_dict: parameter to pass to neurons
+        :param new_lifo: If True, ask the LifoManager to return a new lifo and not the singleton
+        """
+        logger.debug("[SynapseLauncher] start_synapse_by_list_name called with synapse list: %s " % list_name)
+
+        if list_name:
+            if brain is None:
+                brain = BrainLoader().brain
+
+            # get all synapse object
+            list_synapse_object_to_start = list()
+            for name in list_name:
+                synapse_to_start = brain.get_synapse_by_name(synapse_name=name)
+                list_synapse_object_to_start.append(synapse_to_start)
+
+            # run the LIFO with all synapse
+            if new_lifo:
+                lifo_buffer = LifoManager.get_new_lifo()
+            else:
+                lifo_buffer = LifoManager.get_singleton_lifo()
+            list_synapse_to_process = list()
+            for synapse in list_synapse_object_to_start:
+                if synapse is not None:
+                    new_matching_synapse = MatchedSynapse(matched_synapse=synapse,
+                                                          matched_order=None,
+                                                          user_order=None,
+                                                          overriding_parameter=overriding_parameter_dict)
+                    list_synapse_to_process.append(new_matching_synapse)
+
+            lifo_buffer.add_synapse_list_to_lifo(list_synapse_to_process)
+            return lifo_buffer.execute(is_api_call=True)
+        return None
+
     @classmethod
     @classmethod
     def run_matching_synapse_from_order(cls, order_to_process, brain, settings, is_api_call=False, no_voice=False):
     def run_matching_synapse_from_order(cls, order_to_process, brain, settings, is_api_call=False, no_voice=False):
         """
         """
@@ -61,7 +107,7 @@ class SynapseLauncher(object):
         """
         """
 
 
         # get our singleton LIFO
         # get our singleton LIFO
-        lifo_buffer = LIFOBuffer()
+        lifo_buffer = LifoManager.get_singleton_lifo()
 
 
         # if the LIFO is not empty, so, the current order is passed to the current processing synapse as an answer
         # if the LIFO is not empty, so, the current order is passed to the current processing synapse as an answer
         if len(lifo_buffer.lifo_list) > 0:
         if len(lifo_buffer.lifo_list) > 0:
@@ -73,17 +119,9 @@ class SynapseLauncher(object):
             list_synapse_to_process = OrderAnalyser.get_matching_synapse(order=order_to_process, brain=brain)
             list_synapse_to_process = OrderAnalyser.get_matching_synapse(order=order_to_process, brain=brain)
 
 
             if not list_synapse_to_process:  # the order analyser returned us an empty list
             if not list_synapse_to_process:  # the order analyser returned us an empty list
-                # add the default synapse if exist into the lifo
-                if settings.default_synapse:
-                    logger.debug("[SynapseLauncher] No matching Synapse-> running default synapse ")
-                    # get the default synapse
-                    default_synapse = brain.get_synapse_by_name(settings.default_synapse)
-                    new_matching_synapse = MatchedSynapse(matched_synapse=default_synapse,
-                                                          matched_order=None,
-                                                          user_order=order_to_process)
-                    list_synapse_to_process.append(new_matching_synapse)
-                else:
-                    logger.debug("[SynapseLauncher] No matching Synapse and no default synapse ")
+                return HookManager.on_order_not_found()
+            else:
+                HookManager.on_order_found()
 
 
             lifo_buffer.add_synapse_list_to_lifo(list_synapse_to_process)
             lifo_buffer.add_synapse_list_to_lifo(list_synapse_to_process)
             lifo_buffer.api_response.user_order = order_to_process
             lifo_buffer.api_response.user_order = order_to_process

+ 0 - 116
kalliope/core/Utils/RpiUtils.py

@@ -1,116 +0,0 @@
-from threading import Thread
-
-try:
-    # only import if we are on a Rpi
-    import RPi.GPIO as GPIO
-except RuntimeError:
-    pass
-import time
-
-import logging
-
-from kalliope.core.Models.RpiSettings import RpiSettings
-
-logging.basicConfig()
-logger = logging.getLogger("kalliope")
-
-
-class RpiUtils(Thread):
-
-    def __init__(self, rpi_settings=None, callback=None):
-        """
-        Class used to:
-        - manage RPI GPIO
-        - thread to catch mute button signal
-        The object receive a rpi settings object which contains pin number to use on the Rpi
-        When a signal is caught form the mute button, the callback method from the main controller is called
-        :param rpi_settings: Settings object with GPIO pin number to use
-        :type rpi_settings: RpiSettings
-        :param callback: Callback function from the main controller to call when the mute button is pressed
-        """
-        super(RpiUtils, self).__init__()
-        GPIO.setmode(GPIO.BCM)  # Use GPIO name
-        GPIO.setwarnings(False)
-        self.rpi_settings = rpi_settings
-        self.callback = callback
-        self.init_gpio(self.rpi_settings)
-
-    def run(self):
-        """
-        Start the thread to make kalliope waiting for an input GPIO signal
-        """
-        # run the main thread
-        try:
-            while True:  # keep the thread alive
-                time.sleep(0.1)
-        except (KeyboardInterrupt, SystemExit):
-            self.destroy()
-        self.destroy()
-
-    def switch_kalliope_mute_led(self, event):
-        """
-        Switch the state of the MUTE LED
-        :param event: not used
-        """
-        logger.debug("[RpiUtils] Event button caught. Switching mute led")
-        # get led status
-        led_mute_kalliope = GPIO.input(self.rpi_settings.pin_led_muted)
-        # switch state
-        if led_mute_kalliope == GPIO.HIGH:
-            logger.debug("[RpiUtils] Switching pin_led_muted to OFF")
-            self.switch_pin_to_off(self.rpi_settings.pin_led_muted)
-            self.callback(muted=False)
-        else:
-            logger.debug("[RpiUtils] Switching pin_led_muted to ON")
-            self.switch_pin_to_on(self.rpi_settings.pin_led_muted)
-            self.callback(muted=True)
-
-    @staticmethod
-    def destroy():
-        """
-        Cleanup GPIO to not keep a pin to HIGH status
-        :return: 
-        """
-        logger.debug("[RpiUtils] Cleanup GPIO configuration")
-        GPIO.cleanup()
-
-    def init_gpio(self, rpi_settings):
-        """
-        Initialize GPIO pin to a default value. Leds are off by default
-        Mute button is set as an input
-        :param rpi_settings: RpiSettings object
-        """
-        # All led are off by default
-        if self.rpi_settings.pin_led_muted:
-            GPIO.setup(rpi_settings.pin_led_muted, GPIO.OUT, initial=GPIO.LOW)
-        if self.rpi_settings.pin_led_started:
-            GPIO.setup(rpi_settings.pin_led_started, GPIO.OUT, initial=GPIO.LOW)
-        if self.rpi_settings.pin_led_listening:
-            GPIO.setup(rpi_settings.pin_led_listening, GPIO.OUT, initial=GPIO.LOW)
-        if self.rpi_settings.pin_led_talking:
-            GPIO.setup(rpi_settings.pin_led_talking, GPIO.OUT, initial=GPIO.LOW)
-
-        # MUTE button
-        if self.rpi_settings.pin_mute_button:
-            GPIO.setup(rpi_settings.pin_mute_button, GPIO.IN, pull_up_down=GPIO.PUD_UP)
-            GPIO.add_event_detect(rpi_settings.pin_mute_button, GPIO.FALLING,
-                                  callback=self.switch_kalliope_mute_led,
-                                  bouncetime=500)
-
-    @classmethod
-    def switch_pin_to_on(cls, pin_number):
-        """
-        Switch the pin_number of the RPI GPIO board to HIGH status
-        :param pin_number: integer pin number to switch HIGH
-        """
-        logger.debug("[RpiUtils] Switching pin number %s to ON" % pin_number)
-        GPIO.output(pin_number, GPIO.HIGH)
-
-    @classmethod
-    def switch_pin_to_off(cls, pin_number):
-        """
-        Switch the pin_number of the RPI GPIO board to LOW status
-        :param pin_number: integer pin number to switch LOW
-        """
-        logger.debug("[RpiUtils] Switching pin number %s to OFF" % pin_number)
-        GPIO.output(pin_number, GPIO.LOW)

+ 4 - 1
kalliope/core/__init__.py

@@ -6,8 +6,11 @@ from kalliope.core.Utils import FileManager
 from kalliope.core.ResourcesManager import ResourcesManager
 from kalliope.core.ResourcesManager import ResourcesManager
 from kalliope.core.NeuronLauncher import NeuronLauncher
 from kalliope.core.NeuronLauncher import NeuronLauncher
 from kalliope.core.SynapseLauncher import SynapseLauncher
 from kalliope.core.SynapseLauncher import SynapseLauncher
-from kalliope.core.LIFOBuffer import LIFOBuffer
+from kalliope.core.Lifo.LIFOBuffer import LIFOBuffer
 from kalliope.core.NeuronParameterLoader import NeuronParameterLoader
 from kalliope.core.NeuronParameterLoader import NeuronParameterLoader
 from kalliope.core.NeuronModule import NeuronModule
 from kalliope.core.NeuronModule import NeuronModule
 from kalliope.core.SignalModule import SignalModule, MissingParameter
 from kalliope.core.SignalModule import SignalModule, MissingParameter
 from kalliope.core.PlayerModule import PlayerModule
 from kalliope.core.PlayerModule import PlayerModule
+from kalliope.core.HookManager import HookManager
+from kalliope.core.Lifo.LIFOBuffer import LIFOBuffer
+from kalliope.core.Lifo.LifoManager import LifoManager

+ 63 - 0
kalliope/neurons/debug/README.md

@@ -0,0 +1,63 @@
+# Debug
+
+## Synopsis
+
+Print a message in the console. This neuron can be used to check your [captured variable from an order](../../Docs/neurons.md#input-values) or check the content of variable placed 
+in [Kalliope memory](../../Docs/neurons.md#kalliope_memory-store-in-memory-a-variable-from-an-order-or-generated-from-a-neuron).
+
+## Installation
+
+CORE NEURON : No installation needed.  
+
+## Options
+
+| parameter | required | default | choices | comment                                  |
+|-----------|----------|---------|---------|------------------------------------------|
+| message   | YES      |         |         | Message to print in the console output   |
+
+## Return Values
+
+No returned values
+
+## Synapses example
+
+Simple example : 
+```yml
+- name: "debug"
+  signals:
+    - order: "print a debug"
+  neurons:
+    - debug:
+        message: "this is a debug line"     
+```
+
+Output example:
+```
+[Debug neuron, 2017-12-17 17:30:53] this is a debug line
+```
+
+Show the content of captured variables from the spoken order
+```yml
+- name: "debug"
+  signals:
+    - order: "tell me what I say {{ here }}"
+  neurons:
+    - debug:
+        message: "{{ here }}"     
+```
+
+Show the content of a variable placed in Kalliope memory
+```yml
+- name: "debug"
+  signals:
+    - order: "what time is it?"
+  neurons:
+    - systemdate:
+        say_template:
+          - "It' {{ hours }} hours and {{ minutes }} minutes"
+        kalliope_memory:
+          hours_when_asked: "{{ hours }}"
+          minutes_when_asked: "{{ minutes }}"   
+    - debug:
+        message: "hours: {{ kalliope_memory['hours_when_asked']}}, minutes: {{ kalliope_memory['minutes_when_asked']}}"
+```

+ 1 - 0
kalliope/neurons/debug/__init__.py

@@ -0,0 +1 @@
+from .debug import Debug

+ 26 - 0
kalliope/neurons/debug/debug.py

@@ -0,0 +1,26 @@
+import datetime
+
+from kalliope import Utils
+from kalliope.core.NeuronModule import NeuronModule, MissingParameterException
+
+
+class Debug(NeuronModule):
+    def __init__(self, **kwargs):
+        super(Debug, self).__init__(**kwargs)
+        self.message = kwargs.get('message', None)
+
+        # check if parameters have been provided
+        if self._is_parameters_ok():
+            Utils.print_warning("[Debug neuron, %s] %s\n" % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+                                                             self.message))
+
+    def _is_parameters_ok(self):
+        """
+        Check if received parameters are ok to perform operations in the neuron
+        :return: true if parameters are ok, raise an exception otherwise
+
+        .. raises:: MissingParameterException
+        """
+        if self.message is None:
+            raise MissingParameterException("You must specify a message string or a list of messages as parameter")
+        return True

+ 5 - 7
kalliope/neurons/say/README.md

@@ -10,9 +10,9 @@ CORE NEURON : No installation needed.
 
 
 ## Options
 ## Options
 
 
-| parameter | required | default | choices | comment                                |
-|-----------|----------|---------|---------|----------------------------------------|
-| message   | YES      |         |         | A list of messages Kalliope could say  |
+| parameter | required | default | choices | comment                                                    |
+|-----------|----------|---------|---------|------------------------------------------------------------|
+| message   | YES      |         |         | A single message or a list of messages Kalliope could say  |
 
 
 ## Return Values
 ## Return Values
 
 
@@ -28,8 +28,7 @@ Simple example :
     - order: "hello"
     - order: "hello"
   neurons:
   neurons:
     - say:
     - say:
-        message:
-          - "Hello Sir"     
+        message: "Hello Sir"     
 ```
 ```
 
 
 With a multiple choice list, Kalliope will pick one randomly:
 With a multiple choice list, Kalliope will pick one randomly:
@@ -53,8 +52,7 @@ With an input value
     - order: "say hello to {{ friend_name }}"
     - order: "say hello to {{ friend_name }}"
   neurons:
   neurons:
     - say:
     - say:
-        message:
-          - "Hello {{ friend_name }}"     
+        message: "Hello {{ friend_name }}"
 ```
 ```
 
 
 ## Notes
 ## Notes

+ 13 - 58
kalliope/settings.yml

@@ -106,52 +106,22 @@ players:
   - sounddeviceplayer:
   - sounddeviceplayer:
      convert_to_wav: True
      convert_to_wav: True
 
 
-# ---------------------------
-# Wake up answers
-# ---------------------------
-# When Kalliope detect the hotword/trigger, he will select randomly a phrase in the following list
-# to notify the user that he's listening for orders
-random_wake_up_answers:
-  - "Oui monsieur?"
-  - "Je vous écoute"
-  - "Monsieur?"
-  - "Que puis-je faire pour vous?"
-  - "J'écoute"
-  - "Oui?"
-
-# You can play a sound when Kalliope detect the hotword/trigger instead of saying something from
-# the `random_wake_up_answers`.
-# Place here the full path of the sound file or just the name of the file in /usr/lib/kalliope/sounds
-# The file must be .wav or .mp3 format. By default two file are provided: ding.wav and dong.wav
-#random_wake_up_sounds:
-#  - "sounds/ding.wav"
-#  - "sounds/dong.wav"
-  # - "/my/personal/full/path/my_file.mp3"
-
 
 
 # ---------------------------
 # ---------------------------
-# On ready notification
+# Hooks
 # ---------------------------
 # ---------------------------
-# This section is used to notify the user when Kalliope is waiting for a trigger detection by playing a sound or speak a sentence out loud
-
-# This parameter define if you play the on ready answer:
-# - always: every time Kalliope is ready to be awaken
-# - never: never play a sound or sentences when kalliope is ready
-# - once: at the first start of Kalliope
-play_on_ready_notification: never
-
-# The on ready notification can be a sentence. Place here a sentence or a list of sentence. If you set a list, one sentence will be picked up randomly
-on_ready_answers:
-  - "Kalliope is ready"
-  - "Waiting for order"
-
-# You can play a sound instead of a sentence.
-# Remove the `on_ready_answers` parameters by commenting it out and use this one instead.
-# Place here the path of the sound file. Files must be .wav or .mp3 format.
-on_ready_sounds:
-  - "sounds/ding.wav"
-  - "sounds/dong.wav"
-
+hooks:
+  on_start: "on-start-synapse"
+  on_waiting_for_trigger:
+  on_triggered: "on-triggered-synapse"
+  on_start_listening:
+  on_stop_listening:
+  on_order_found:
+  on_order_not_found: "order-not-found-synapse"
+  on_mute:
+  on_unmute:
+  on_start_speaking:
+  on_stop_speaking:
 
 
 # ---------------------------
 # ---------------------------
 # Rest API
 # Rest API
@@ -164,11 +134,6 @@ rest_api:
   password: secret
   password: secret
   allowed_cors_origin: False
   allowed_cors_origin: False
 
 
-# ---------------------------
-# Default Synapse
-# ---------------------------
-# Specify an optional default synapse response in case your order is not found.
-default_synapse: "default-synapse"
 
 
 # ---------------------------
 # ---------------------------
 # Resource directory path
 # Resource directory path
@@ -195,16 +160,6 @@ default_synapse: "default-synapse"
 #  - variables.yml
 #  - variables.yml
 #  - variables2.yml
 #  - variables2.yml
 
 
-# ---------------------------
-# Raspberry Pi GPIO settings
-# ---------------------------
-#rpi:
-#  pin_mute_button: 24
-#  pin_led_started: 23
-#  pin_led_muted: 17
-#  pin_led_talking: 27
-#  pin_led_listening: 22
-
 # -------------
 # -------------
 # Start options
 # Start options
 # -------------
 # -------------

+ 16 - 86
kalliope/signals/order/order.py

@@ -1,16 +1,12 @@
 import logging
 import logging
-import random
 from threading import Thread
 from threading import Thread
 from time import sleep
 from time import sleep
 
 
-from kalliope.core.Utils.RpiUtils import RpiUtils
-
 from kalliope.core.SynapseLauncher import SynapseLauncher
 from kalliope.core.SynapseLauncher import SynapseLauncher
 
 
 from kalliope.core.OrderListener import OrderListener
 from kalliope.core.OrderListener import OrderListener
 
 
 from kalliope import Utils, BrainLoader
 from kalliope import Utils, BrainLoader
-from kalliope.neurons.say import Say
 
 
 from kalliope.core.TriggerLauncher import TriggerLauncher
 from kalliope.core.TriggerLauncher import TriggerLauncher
 from transitions import Machine
 from transitions import Machine
@@ -19,6 +15,8 @@ from kalliope.core.PlayerLauncher import PlayerLauncher
 
 
 from kalliope.core.ConfigurationManager import SettingLoader
 from kalliope.core.ConfigurationManager import SettingLoader
 
 
+from kalliope.core.HookManager import HookManager
+
 logging.basicConfig()
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
 logger = logging.getLogger("kalliope")
 
 
@@ -26,17 +24,15 @@ logger = logging.getLogger("kalliope")
 class Order(Thread):
 class Order(Thread):
     states = ['init',
     states = ['init',
               'starting_trigger',
               'starting_trigger',
-              'playing_ready_sound',
               'waiting_for_trigger_callback',
               'waiting_for_trigger_callback',
               'stopping_trigger',
               'stopping_trigger',
-              'playing_wake_up_answer',
               'start_order_listener',
               'start_order_listener',
               'waiting_for_order_listener_callback',
               'waiting_for_order_listener_callback',
               'analysing_order']
               'analysing_order']
 
 
     def __init__(self):
     def __init__(self):
         super(Order, self).__init__()
         super(Order, self).__init__()
-        Utils.print_info('Starting voice order manager')
+        Utils.print_info('Starting order signal')
         # load settings and brain from singleton
         # load settings and brain from singleton
         sl = SettingLoader()
         sl = SettingLoader()
         self.settings = sl.settings
         self.settings = sl.settings
@@ -54,7 +50,6 @@ class Order(Thread):
         self.is_trigger_muted = False
         self.is_trigger_muted = False
 
 
         # If kalliope is asked to start muted
         # If kalliope is asked to start muted
-        #self.set_mute_status(self.settings.start_muted)
         if self.settings.start_options['muted'] is True:
         if self.settings.start_options['muted'] is True:
             self.is_trigger_muted = True
             self.is_trigger_muted = True
 
 
@@ -62,37 +57,29 @@ class Order(Thread):
         self.order_listener = None
         self.order_listener = None
         self.order_listener_callback_called = False
         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
         # Initialize the state machine
         self.machine = Machine(model=self, states=Order.states, initial='init', queued=True)
         self.machine = Machine(model=self, states=Order.states, initial='init', queued=True)
 
 
         # define transitions
         # define transitions
         self.machine.add_transition('start_trigger', ['init', 'analysing_order'], 'starting_trigger')
         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('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('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_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()
         self.machine.add_ordered_transitions()
 
 
         # add method which are called when changing state
         # add method which are called when changing state
         self.machine.on_enter_starting_trigger('start_trigger_process')
         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_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_stopping_trigger('stop_trigger_process')
         self.machine.on_enter_start_order_listener('start_order_listener_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_waiting_for_order_listener_callback('waiting_for_order_listener_callback_thread')
         self.machine.on_enter_analysing_order('analysing_order_thread')
         self.machine.on_enter_analysing_order('analysing_order_thread')
 
 
     def run(self):
     def run(self):
+        # run hook on_start
+        HookManager.on_start()
         self.start_trigger()
         self.start_trigger()
 
 
     def start_trigger_process(self):
     def start_trigger_process(self):
@@ -100,6 +87,7 @@ class Order(Thread):
         This function will start the trigger thread that listen for the hotword
         This function will start the trigger thread that listen for the hotword
         """
         """
         logger.debug("[MainController] Entering state: %s" % self.state)
         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_instance = TriggerLauncher.get_trigger(settings=self.settings, callback=self.trigger_callback)
         self.trigger_callback_called = False
         self.trigger_callback_called = False
         self.trigger_instance.daemon = True
         self.trigger_instance.daemon = True
@@ -107,23 +95,6 @@ class Order(Thread):
         self.trigger_instance.start()
         self.trigger_instance.start()
         self.next_state()
         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):
     def waiting_for_trigger_callback_thread(self):
         """
         """
         Method to print in debug that the main process is waiting for a trigger detection
         Method to print in debug that the main process is waiting for a trigger detection
@@ -137,6 +108,8 @@ class Order(Thread):
         # this loop is used to keep the main thread alive
         # this loop is used to keep the main thread alive
         while not self.trigger_callback_called:
         while not self.trigger_callback_called:
             sleep(0.1)
             sleep(0.1)
+        # if here, then the trigger has been called
+        HookManager.on_triggered()
         self.next_state()
         self.next_state()
 
 
     def waiting_for_order_listener_callback_thread(self):
     def waiting_for_order_listener_callback_thread(self):
@@ -147,9 +120,7 @@ class Order(Thread):
         # this loop is used to keep the main thread alive
         # this loop is used to keep the main thread alive
         while not self.order_listener_callback_called:
         while not self.order_listener_callback_called:
             sleep(0.1)
             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)
+        # TODO on end listening here
         self.next_state()
         self.next_state()
 
 
     def trigger_callback(self):
     def trigger_callback(self):
@@ -174,6 +145,7 @@ class Order(Thread):
         Start the STT engine thread
         Start the STT engine thread
         """
         """
         logger.debug("[MainController] Entering state: %s" % self.state)
         logger.debug("[MainController] Entering state: %s" % self.state)
+        HookManager.on_start_listening()
         # start listening for an order
         # start listening for an order
         self.order_listener_callback_called = False
         self.order_listener_callback_called = False
         self.order_listener = OrderListener(callback=self.order_listener_callback)
         self.order_listener = OrderListener(callback=self.order_listener_callback)
@@ -181,20 +153,6 @@ class Order(Thread):
         self.order_listener.start()
         self.order_listener.start()
         self.next_state()
         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):
     def order_listener_callback(self, order):
         """
         """
         Receive an order, try to retrieve it in the brain.yml to launch to attached plugins
         Receive an order, try to retrieve it in the brain.yml to launch to attached plugins
@@ -202,6 +160,7 @@ class Order(Thread):
         :type order: str
         :type order: str
         """
         """
         logger.debug("[MainController] Order listener callback called. Order to process: %s" % order)
         logger.debug("[MainController] Order listener callback called. Order to process: %s" % order)
+        HookManager.on_stop_listening()
         self.order_to_process = order
         self.order_to_process = order
         self.order_listener_callback_called = True
         self.order_listener_callback_called = True
 
 
@@ -218,20 +177,6 @@ class Order(Thread):
         # return to the state "unpausing_trigger"
         # return to the state "unpausing_trigger"
         self.start_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):
     def set_mute_status(self, muted=False):
         """
         """
         Define is the trigger is listening or not
         Define is the trigger is listening or not
@@ -242,10 +187,12 @@ class Order(Thread):
             self.trigger_instance.pause()
             self.trigger_instance.pause()
             self.is_trigger_muted = True
             self.is_trigger_muted = True
             Utils.print_info("Kalliope now muted")
             Utils.print_info("Kalliope now muted")
+            HookManager.on_mute()
         else:
         else:
             self.trigger_instance.unpause()
             self.trigger_instance.unpause()
             self.is_trigger_muted = False
             self.is_trigger_muted = False
             Utils.print_info("Kalliope now listening for trigger detection")
             Utils.print_info("Kalliope now listening for trigger detection")
+            HookManager.on_unmute()
 
 
     def get_mute_status(self):
     def get_mute_status(self):
         """
         """
@@ -253,20 +200,3 @@ class Order(Thread):
         :return: Boolean
         :return: Boolean
         """
         """
         return self.is_trigger_muted
         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)

+ 0 - 5
kalliope/stt/Utils.py

@@ -5,7 +5,6 @@ import logging
 import speech_recognition as sr
 import speech_recognition as sr
 
 
 from kalliope import Utils, SettingLoader
 from kalliope import Utils, SettingLoader
-from kalliope.core.Utils.RpiUtils import RpiUtils
 
 
 logging.basicConfig()
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
 logger = logging.getLogger("kalliope")
@@ -60,10 +59,6 @@ class SpeechRecognition(Thread):
         """
         """
         if self.audio_stream is None:
         if self.audio_stream is None:
             Utils.print_info("Say something!")
             Utils.print_info("Say something!")
-            # Turn on the listening led if we are on a Raspberry
-            if self.settings.rpi_settings:
-                if self.settings.rpi_settings.pin_led_listening:
-                    RpiUtils.switch_pin_to_on(self.settings.rpi_settings.pin_led_listening)
             self.stop_thread = self.recognizer.listen_in_background(self.microphone, self.callback)
             self.stop_thread = self.recognizer.listen_in_background(self.microphone, self.callback)
             while not self.kill_yourself:
             while not self.kill_yourself:
                 sleep(0.1)
                 sleep(0.1)

+ 3 - 0
kalliope/trigger/snowboy/snowboydecoder.py

@@ -151,6 +151,9 @@ class HotwordDetector(Thread):
                     callback = self.detected_callback[ans-1]
                     callback = self.detected_callback[ans-1]
                     if callback is not None:
                     if callback is not None:
                         callback()
                         callback()
+            else:
+                # take a little break
+                time.sleep(self.sleep_time)
 
 
         logger.debug("[Snowboy] process finished.")
         logger.debug("[Snowboy] process finished.")
 
 

+ 1 - 1
kalliope/tts/voicerss/voicerss.py

@@ -13,7 +13,7 @@ logger = logging.getLogger("kalliope")
 # https://bitbucket.org/daycoder/cachingutil/pull-requests/1/fix-python3-packages-paths/diff
 # https://bitbucket.org/daycoder/cachingutil/pull-requests/1/fix-python3-packages-paths/diff
 if sys.version_info[0] == 3:
 if sys.version_info[0] == 3:
     logger.error("[Voicerss] WARNING : VOICERSS is not working for python3 yet !")
     logger.error("[Voicerss] WARNING : VOICERSS is not working for python3 yet !")
-else :
+else:
     from voicerss_tts.voicerss_tts import TextToSpeech
     from voicerss_tts.voicerss_tts import TextToSpeech
 
 
 TTS_URL = "http://www.voicerss.org/controls/speech.ashx"
 TTS_URL = "http://www.voicerss.org/controls/speech.ashx"

+ 0 - 1
setup.py

@@ -90,7 +90,6 @@ setup(
         'sounddevice>=0.3.7',
         'sounddevice>=0.3.7',
         'SoundFile>=0.9.0',
         'SoundFile>=0.9.0',
         'pyalsaaudio>=0.8.4',
         'pyalsaaudio>=0.8.4',
-        'RPi.GPIO>=0.6.3',
         'sox>=1.3.0',
         'sox>=1.3.0',
         'paho-mqtt>=1.3.0',
         'paho-mqtt>=1.3.0',
         'voicerss_tts>=1.0.3'
         'voicerss_tts>=1.0.3'