Bläddra i källkod

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 år sedan
förälder
incheckning
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.
 
-## 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
-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
-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
-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
-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
-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
-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
-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
 
@@ -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).
 
-## 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
 
 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] }}"
 ```
 
-## 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
 Options that can be defined when kalliope starts.
 

+ 5 - 0
Tests/brains/brain_test_api.yml

@@ -26,3 +26,8 @@
           message:
             - "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:
      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
 
 # ---------------------------
-# 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

+ 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
 
-from kalliope.core import LIFOBuffer
+from kalliope.core import LifoManager
 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.MatchedSynapse import MatchedSynapse
@@ -24,7 +24,7 @@ class TestLIFOBuffer(unittest.TestCase):
 
         BrainLoader(file_path=self.brain_to_test)
         # create a new lifo buffer
-        self.lifo_buffer = LIFOBuffer()
+        self.lifo_buffer = LifoManager.get_singleton_lifo()
         self.lifo_buffer.clean()
 
     def test_execute(self):
@@ -292,7 +292,7 @@ class TestLIFOBuffer(unittest.TestCase):
         list_matched_synapse = list()
         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)
             expected_response = {
                 'status': None,
@@ -322,7 +322,7 @@ class TestLIFOBuffer(unittest.TestCase):
             self.assertEqual("complete", self.lifo_buffer.api_response.status)
 
         # test with neuron that wait for an answer
-        self.lifo_buffer.clean()
+        LifoManager.clean_saved_lifo()
         synapse = BrainLoader().brain.get_synapse_by_name("synapse6")
         order = "synapse6"
         matched_synapse = MatchedSynapse(matched_synapse=synapse,
@@ -335,7 +335,7 @@ class TestLIFOBuffer(unittest.TestCase):
                 self.lifo_buffer._process_neuron_list(matched_synapse=matched_synapse)
 
         # 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")
         order = "synapse6"
         matched_synapse = MatchedSynapse(matched_synapse=synapse,
@@ -353,6 +353,6 @@ if __name__ == '__main__':
     unittest.main()
 
     # suite = unittest.TestSuite()
-    # suite.addTest(TestLIFOBuffer("test_process_neuron_list"))
+    # suite.addTest(TestLIFOBuffer("test_execute"))
     # runner = unittest.TextTestRunner()
     # 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
         self.brain_test3 = Brain(synapses=self.all_synapse_list1)
 
-        self.settings_test = Settings(default_synapse="Synapse3")
+        self.settings_test = Settings()
 
         # clean the LiFO
         LIFOBuffer.lifo_list = list()
@@ -252,16 +252,10 @@ class TestModels(unittest.TestCase):
                                 default_player_name="mplayer",
                                 ttss=["ttts"],
                                 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"],
                                 players=["mplayer"],
                                 rest_api=rest_api1,
                                 cache_path="/tmp/kalliope",
-                                default_synapse="default_synapse",
                                 resources=None,
                                 variables={"key1": "val1"},
                                 recognition_options=recognition_options,
@@ -274,15 +268,9 @@ class TestModels(unittest.TestCase):
                                 default_player_name="mplayer",
                                 ttss=["ttts"],
                                 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"],
                                 rest_api=rest_api1,
                                 cache_path="/tmp/kalliope_tmp",
-                                default_synapse="my_default_synapse",
                                 resources=None,
                                 variables={"key1": "val1"},
                                 recognition_options=recognition_options,
@@ -295,16 +283,10 @@ class TestModels(unittest.TestCase):
                                 default_player_name="mplayer",
                                 ttss=["ttts"],
                                 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"],
                                 players=["mplayer"],
                                 rest_api=rest_api1,
                                 cache_path="/tmp/kalliope",
-                                default_synapse="default_synapse",
                                 resources=None,
                                 variables={"key1": "val1"},
                                 recognition_options=recognition_options,
@@ -312,8 +294,8 @@ class TestModels(unittest.TestCase):
             setting3.kalliope_version = "0.4.5"
 
             expected_result_serialize = {
-                'default_synapse': 'default_synapse',
                 'default_tts_name': 'pico2wav',
+                'hooks': None,
                 'rest_api':
                     {
                         'password_protected': True,
@@ -323,28 +305,23 @@ class TestModels(unittest.TestCase):
                         'password': 'password',
                         'login': 'admin'
                     },
-                'play_on_ready_notification': False,
                 'default_stt_name': 'google',
                 'kalliope_version': '0.4.5',
-                'random_wake_up_sounds': None,
-                'on_ready_answers': None,
                 'default_trigger_name': 'swoyboy',
                 'default_player_name': 'mplayer',
                 'cache_path': '/tmp/kalliope',
                 'stts': ['stts'],
                 'machine': 'pumpkins',
-                'random_wake_up_answers': ['yes'],
-                'on_ready_sounds': None,
                 'ttss': ['ttts'],
                 'variables': {'key1': 'val1'},
                 'resources': None,
                 'triggers': ['snowboy'],
-                'rpi_settings': None,
                 'players': ['mplayer'],
                 'recognition_options': {'energy_threshold': 4000, 'adjust_for_ambient_noise_second': 0},
                 'start_options': {'muted': False}
             }
 
+            self.maxDiff = None
             self.assertDictEqual(expected_result_serialize, setting1.serialize())
 
             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 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 SettingLoader
 from kalliope.core.Models import Singleton
@@ -19,7 +19,7 @@ class TestRestAPI(LiveServerTestCase):
     def tearDown(self):
         Singleton._instances = {}
         # clean the lifo
-        LIFOBuffer.lifo_list = list()
+        LifoManager.clean_saved_lifo()
 
     def create_app(self):
         """
@@ -42,6 +42,7 @@ class TestRestAPI(LiveServerTestCase):
         sl.settings.port = 5000
         sl.settings.allowed_cors_origin = "*"
         sl.settings.default_synapse = None
+        sl.settings.hooks["on_order_not_found"] = "order-not-found-synapse"
 
         # prepare a test brain
         brain_to_test = full_path_brain_to_test
@@ -73,85 +74,23 @@ class TestRestAPI(LiveServerTestCase):
 
         response = self.client.get(url)
         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
         self.maxDiff = None
         self.assertEqual(response.status_code, 200)
@@ -162,26 +101,26 @@ class TestRestAPI(LiveServerTestCase):
         url = self.get_server_url() + "/synapses/test"
         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),
                          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))
 
         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),
@@ -290,7 +229,14 @@ class TestRestAPI(LiveServerTestCase):
                                   headers=headers,
                                   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),
                          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)
             mock_os_system.assert_called_once_with("avconv -y -i " + temp_file + " " + expected_result)
 
+
 if __name__ == '__main__':
     unittest.main()
 
     # suite = unittest.TestSuite()
-    # suite.addTest(TestRestAPI("test_run_synapse_by_name"))
+    # suite.addTest(TestRestAPI("test_get_all_synapses"))
     # runner = unittest.TextTestRunner()
     # 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_dict = {
-            'default_synapse': 'Default-synapse',
             'rest_api':
                 {'allowed_cors_origin': False,
                  'active': True,
@@ -35,15 +34,11 @@ class TestSettingLoader(unittest.TestCase):
                  'password_protected': True,
                  'password': 'secret', 'port': 5000},
             'default_trigger': 'snowboy',
-            'default_player': 'mplayer',
-            'play_on_ready_notification': 'never',
             'triggers': [{'snowboy': {'pmdl_file': 'trigger/snowboy/resources/kalliope-FR-6samples.pmdl'}}],
+            'default_player': 'mplayer',
             'players': [{'mplayer': {}}, {'pyalsaaudio': {"device": "default"}}],
             'speech_to_text': [{'google': {'language': 'fr-FR'}}],
-            'on_ready_answers': ['Kalliope is ready'],
             'cache_path': '/tmp/kalliope_tts_cache',
-            'random_wake_up_answers': ['Oui monsieur?'],
-            'on_ready_sounds': ['sounds/ding.wav', 'sounds/dong.wav'],
             'resource_directory': {
                 'stt': '/tmp/kalliope/tests/kalliope_resources_dir/stt',
                 'tts': '/tmp/kalliope/tests/kalliope_resources_dir/tts',
@@ -51,15 +46,25 @@ class TestSettingLoader(unittest.TestCase):
                 'trigger': '/tmp/kalliope/tests/kalliope_resources_dir/trigger'},
             'default_text_to_speech': 'pico2wave',
             'default_speech_to_text': 'google',
-            'random_wake_up_sounds': ['sounds/ding.wav', 'sounds/dong.wav'],
             'text_to_speech': [
                 {'pico2wave': {'cache': True, 'language': 'fr-FR'}},
                 {'voxygen': {'voice': 'Agnes', 'cache': True}}
-            ],
+                ],
             '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
@@ -83,7 +88,8 @@ class TestSettingLoader(unittest.TestCase):
     def test_get_yaml_config(self):
 
         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):
         settings_object = Settings()
@@ -96,11 +102,6 @@ class TestSettingLoader(unittest.TestCase):
         settings_object.ttss = [tts1, tts2]
         stt = Stt(name="google", parameters={'language': 'fr-FR'})
         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",
                            parameters={'pmdl_file': 'trigger/snowboy/resources/kalliope-FR-6samples.pmdl'})
         settings_object.triggers = [trigger1]
@@ -111,7 +112,6 @@ class TestSettingLoader(unittest.TestCase):
                                            login="admin", password="secret", port=5000,
                                            allowed_cors_origin=False)
         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",
                               stt_folder="/tmp/kalliope/tests/kalliope_resources_dir/stt",
                               tts_folder="/tmp/kalliope/tests/kalliope_resources_dir/tts",
@@ -127,6 +127,19 @@ class TestSettingLoader(unittest.TestCase):
         }
         settings_object.machine = platform.machine()
         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)
 
@@ -173,21 +186,6 @@ class TestSettingLoader(unittest.TestCase):
         sl = SettingLoader(file_path=self.settings_file_to_test)
         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):
         expected_rest_api = RestAPI(password_protected=True, active=True,
                                     login="admin", password="secret", port=5000,
@@ -201,11 +199,6 @@ class TestSettingLoader(unittest.TestCase):
         sl = SettingLoader(file_path=self.settings_file_to_test)
         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):
 
         resources = Resources(neuron_folder="/tmp/kalliope/tests/kalliope_resources_dir/neurons",
@@ -234,6 +227,58 @@ class TestSettingLoader(unittest.TestCase):
         self.assertEqual(expected_result,
                          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__':
     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
 
-from kalliope.core import LIFOBuffer
+from kalliope.core import LIFOBuffer, LifoManager
 from kalliope.core.Models import Brain, Signal, Singleton
 from kalliope.core.Models.MatchedSynapse import MatchedSynapse
 from kalliope.core.Models.Settings import Settings
@@ -37,24 +37,26 @@ class TestSynapseLauncher(unittest.TestCase):
                                  self.synapse3]
 
         self.brain_test = Brain(synapses=self.all_synapse_list)
-        self.settings_test = Settings(default_synapse="Synapse3")
+        self.settings_test = Settings()
 
         # clean the LiFO
         Singleton._instances = dict()
+        LifoManager.clean_saved_lifo()
 
     def test_start_synapse_by_name(self):
         # 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)
             SynapseLauncher.start_synapse_by_name("Synapse1", brain=self.brain_test)
             # we expect that the lifo has been loaded with the synapse to run
             expected_result = [[should_be_created_matched_synapse]]
-            lifo_buffer = LIFOBuffer()
+            lifo_buffer = LifoManager.get_singleton_lifo()
             self.assertEqual(expected_result, lifo_buffer.lifo_list)
 
             # we expect that the lifo has been loaded with the synapse to run and overwritten parameters
             Singleton._instances = dict()
-            lifo_buffer = LIFOBuffer()
+            LifoManager.clean_saved_lifo()
+            lifo_buffer = LifoManager.get_singleton_lifo()
             overriding_param = {
                 "val1": "val"
             }
@@ -70,11 +72,67 @@ class TestSynapseLauncher(unittest.TestCase):
         with self.assertRaises(SynapseNameNotFound):
             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):
         # ------------------
         # 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"
 
             should_be_created_matched_synapse = MatchedSynapse(matched_synapse=self.synapse1,
@@ -85,7 +143,7 @@ class TestSynapseLauncher(unittest.TestCase):
                                                             brain=self.brain_test,
                                                             settings=self.settings_test)
 
-            lifo_buffer = LIFOBuffer()
+            lifo_buffer = LifoManager.get_singleton_lifo()
             self.assertEqual(expected_result, lifo_buffer.lifo_list)
 
         # -------------------------
@@ -93,7 +151,8 @@ class TestSynapseLauncher(unittest.TestCase):
         # -------------------------
         # clean LIFO
         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"
             should_be_created_matched_synapse1 = MatchedSynapse(matched_synapse=self.synapse1,
                                                                 user_order=order_to_match,
@@ -106,47 +165,46 @@ class TestSynapseLauncher(unittest.TestCase):
             SynapseLauncher.run_matching_synapse_from_order(order_to_match,
                                                             brain=self.brain_test,
                                                             settings=self.settings_test)
-            lifo_buffer = LIFOBuffer()
+            lifo_buffer = LifoManager.get_singleton_lifo()
             self.assertEqual(expected_result, lifo_buffer.lifo_list)
 
         # -------------------------
-        # test_match_default_synapse
+        # test_call_hook_order_not_found
         # -------------------------
         # clean LIFO
         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"
-            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,
                                                             brain=self.brain_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
         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__':
     unittest.main()
 
     # 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.run(suite)

+ 0 - 1
install/files/python_requirements.txt

@@ -22,7 +22,6 @@ transitions>=0.4.3
 sounddevice>=0.3.7
 SoundFile>=0.9.0
 pyalsaaudio>=0.8.4
-RPi.GPIO>=0.6.3
 sox>=1.3.0
 paho-mqtt>=1.3.0
 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.BrainLoader import BrainLoader
 from kalliope.core.SignalLauncher import SignalLauncher
-from kalliope.core.Utils.RpiUtils import RpiUtils
 from flask import Flask
 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)
 
     # 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/
-version_str = "0.4.6"
+version_str = "0.5.0b"

+ 21 - 5
kalliope/brain.yml

@@ -4,8 +4,7 @@
       - order: "Bonjour"
     neurons:
       - say:
-          message:
-            - "Bonjour monsieur"
+          message: "Bonjour monsieur"
 
   - name: "say-hello-en"
     signals:
@@ -15,9 +14,8 @@
           message:
             - "Hello sir"
 
-  - name: "default-synapse"
-    signals:
-      - order: "default-synapse-order"
+  - name: "order-not-found-synapse"
+    signals: []
     neurons:
       - say:
           message:
@@ -26,3 +24,21 @@
             - "Veuillez renouveller votre ordre"
             - "Veuillez reformuller s'il vous plait"
             - "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
 from six import with_metaclass
 
-from kalliope.core.Models.RpiSettings import RpiSettings
 from kalliope.core.Models.RecognitionOptions import RecognitionOptions
 from .YAMLLoader import YAMLLoader
 from kalliope.core.Models.Resources import Resources
@@ -108,19 +107,13 @@ class SettingLoader(with_metaclass(Singleton, object)):
         ttss = self._get_ttss(settings)
         triggers = self._get_triggers(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)
         cache_path = self._get_cache_path(settings)
-        default_synapse = self._get_default_synapse(settings)
         resources = self._get_resources(settings)
         variables = self._get_variables(settings)
-        rpi_settings = self._get_rpi_settings(settings)
         recognition_options = self._get_recognition_options(settings)
         start_options = self._get_start_options(settings)
+        hooks = self._get_hooks(settings)
 
         # Load the setting singleton with the parameters
         setting_object.default_tts_name = default_tts_name
@@ -131,19 +124,13 @@ class SettingLoader(with_metaclass(Singleton, object)):
         setting_object.ttss = ttss
         setting_object.triggers = triggers
         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.cache_path = cache_path
-        setting_object.default_synapse = default_synapse
         setting_object.resources = resources
         setting_object.variables = variables
-        setting_object.rpi_settings = rpi_settings
         setting_object.recognition_options = recognition_options
         setting_object.start_options = start_options
+        setting_object.hooks = hooks
 
         return setting_object
 
@@ -410,72 +397,6 @@ class SettingLoader(with_metaclass(Singleton, object)):
                 players.append(new_player)
         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
     def _get_rest_api(settings):
         """
@@ -575,33 +496,6 @@ class SettingLoader(with_metaclass(Singleton, object)):
         else:
             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
     def _get_resources(settings):
         """
@@ -687,58 +581,6 @@ class SettingLoader(with_metaclass(Singleton, 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
     def _get_variables(settings):
         """
@@ -762,34 +604,6 @@ class SettingLoader(with_metaclass(Singleton, object)):
             # User does not provide this settings
             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
     def _get_recognition_options(settings):
         """
@@ -845,3 +659,39 @@ class SettingLoader(with_metaclass(Singleton, object)):
 
         logger.debug("Start options: %s" % 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
 
 
-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
     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,
                  ttss=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,
                  players=None,
                  rest_api=None,
                  cache_path=None,
-                 default_synapse=None,
                  resources=None,
                  variables=None,
-                 rpi_settings=None,
                  recognition_options=None,
-                 start_options=None):
+                 start_options=None,
+                 hooks=None):
 
         self.default_tts_name = default_tts_name
         self.default_stt_name = default_stt_name
@@ -37,23 +31,17 @@ class Settings(object):
         self.default_player_name = default_player_name
         self.ttss = ttss
         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.players = players
         self.rest_api = rest_api
         self.cache_path = cache_path
-        self.default_synapse = default_synapse
         self.resources = resources
         self.variables = variables
         self.machine = platform.machine()   # can be x86_64 or armv7l
         self.kalliope_version = current_kalliope_version
-        self.rpi_settings = rpi_settings
         self.recognition_options = recognition_options
         self.start_options = start_options
+        self.hooks = hooks
 
     def serialize(self):
         """
@@ -70,23 +58,17 @@ class Settings(object):
             'default_player_name': self.default_player_name,
             'ttss': self.ttss,
             '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,
             'players': self.players,
             'rest_api': self.rest_api.serialize(),
             'cache_path': self.cache_path,
-            'default_synapse': self.default_synapse,
             'resources': self.resources,
             'variables': self.variables,
             'machine': self.machine,
             '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,
             'start_options': self.start_options,
+            'hooks': self.hooks
         }
 
     def __str__(self):

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

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

+ 5 - 22
kalliope/core/NeuronModule.py

@@ -7,13 +7,13 @@ import six
 from jinja2 import Template
 
 from kalliope.core import OrderListener
+from kalliope.core.HookManager import HookManager
 from kalliope.core.ConfigurationManager import SettingLoader, BrainLoader
 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.NeuronExceptions import NeuronExceptions
 from kalliope.core.OrderAnalyser import OrderAnalyser
-from kalliope.core.Utils.RpiUtils import RpiUtils
 from kalliope.core.Utils.Utils import Utils
 
 logging.basicConfig()
@@ -174,6 +174,7 @@ class NeuronModule(object):
                 logger.debug("[NeuronModule] no_voice is True, Kalliope is muted")
             else:
                 logger.debug("[NeuronModule] no_voice is False, make Kalliope speaking")
+                HookManager.on_start_speaking()
                 # get the instance of the TTS module
                 tts_folder = None
                 if self.settings.resources:
@@ -182,14 +183,10 @@ class NeuronModule(object):
                                                                             module_name=self.tts.name,
                                                                             parameters=self.tts.parameters,
                                                                             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
                 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):
         """
@@ -257,7 +254,7 @@ class NeuronModule(object):
         list_synapse_to_process = list()
         list_synapse_to_process.append(matched_synapse)
         # 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.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)
         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._version import version_str
 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.OrderListener import OrderListener
 from kalliope.core.RestAPI.utils import requires_auth
 from kalliope.core.SynapseLauncher import SynapseLauncher
 from kalliope.core.Utils.FileManager import FileManager
-from kalliope.signals.order import Order
 
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
@@ -174,8 +173,8 @@ class FlaskAPI(threading.Thread):
         else:
             # generate a MatchedSynapse from the synapse
             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
             lifo_buffer.clean()
             lifo_buffer.add_synapse_list_to_lifo([matched_synapse])

+ 55 - 17
kalliope/core/SynapseLauncher.py

@@ -1,9 +1,9 @@
 import logging
 
 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.NeuronLauncher import NeuronLauncher
 from kalliope.core.OrderAnalyser import OrderAnalyser
 
 
@@ -23,22 +23,29 @@ class SynapseNameNotFound(Exception):
 class SynapseLauncher(object):
 
     @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
         :param name: Name (Unique ID) 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 HookManager to return a new lifo and not the singleton
         """
         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
         synapse = brain.get_synapse_by_name(synapse_name=name)
 
         if not synapse:
             raise SynapseNameNotFound("The synapse name \"%s\" does not exist in the brain file" % name)
         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()
             new_matching_synapse = MatchedSynapse(matched_synapse=synapse,
                                                   matched_order=None,
@@ -48,6 +55,45 @@ class SynapseLauncher(object):
             lifo_buffer.add_synapse_list_to_lifo(list_synapse_to_process)
             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
     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
-        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 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)
 
             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.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.NeuronLauncher import NeuronLauncher
 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.NeuronModule import NeuronModule
 from kalliope.core.SignalModule import SignalModule, MissingParameter
 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
 
-| 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
 
@@ -28,8 +28,7 @@ Simple example :
     - order: "hello"
   neurons:
     - say:
-        message:
-          - "Hello Sir"     
+        message: "Hello Sir"     
 ```
 
 With a multiple choice list, Kalliope will pick one randomly:
@@ -53,8 +52,7 @@ With an input value
     - order: "say hello to {{ friend_name }}"
   neurons:
     - say:
-        message:
-          - "Hello {{ friend_name }}"     
+        message: "Hello {{ friend_name }}"
 ```
 
 ## Notes

+ 13 - 58
kalliope/settings.yml

@@ -106,52 +106,22 @@ players:
   - sounddeviceplayer:
      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
@@ -164,11 +134,6 @@ rest_api:
   password: secret
   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
@@ -195,16 +160,6 @@ default_synapse: "default-synapse"
 #  - variables.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
 # -------------

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

@@ -1,16 +1,12 @@
 import logging
-import random
 from threading import Thread
 from time import sleep
 
-from kalliope.core.Utils.RpiUtils import RpiUtils
-
 from kalliope.core.SynapseLauncher import SynapseLauncher
 
 from kalliope.core.OrderListener import OrderListener
 
 from kalliope import Utils, BrainLoader
-from kalliope.neurons.say import Say
 
 from kalliope.core.TriggerLauncher import TriggerLauncher
 from transitions import Machine
@@ -19,6 +15,8 @@ from kalliope.core.PlayerLauncher import PlayerLauncher
 
 from kalliope.core.ConfigurationManager import SettingLoader
 
+from kalliope.core.HookManager import HookManager
+
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
 
@@ -26,17 +24,15 @@ logger = logging.getLogger("kalliope")
 class Order(Thread):
     states = ['init',
               'starting_trigger',
-              'playing_ready_sound',
               'waiting_for_trigger_callback',
               'stopping_trigger',
-              'playing_wake_up_answer',
               'start_order_listener',
               'waiting_for_order_listener_callback',
               'analysing_order']
 
     def __init__(self):
         super(Order, self).__init__()
-        Utils.print_info('Starting voice order manager')
+        Utils.print_info('Starting order signal')
         # load settings and brain from singleton
         sl = SettingLoader()
         self.settings = sl.settings
@@ -54,7 +50,6 @@ class Order(Thread):
         self.is_trigger_muted = False
 
         # If kalliope is asked to start muted
-        #self.set_mute_status(self.settings.start_muted)
         if self.settings.start_options['muted'] is True:
             self.is_trigger_muted = True
 
@@ -62,37 +57,29 @@ class Order(Thread):
         self.order_listener = None
         self.order_listener_callback_called = False
 
-        # boolean used to know id we played the on ready notification at least one time
-        self.on_ready_notification_played_once = False
-
-        # rpi setting for led and mute button
-        self.init_rpi_utils()
-
         # Initialize the state machine
         self.machine = Machine(model=self, states=Order.states, initial='init', queued=True)
 
         # define transitions
         self.machine.add_transition('start_trigger', ['init', 'analysing_order'], 'starting_trigger')
-        self.machine.add_transition('play_ready_sound', 'starting_trigger', 'playing_ready_sound')
-        self.machine.add_transition('wait_trigger_callback', 'playing_ready_sound', 'waiting_for_trigger_callback')
+        self.machine.add_transition('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('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()
 
         # add method which are called when changing state
         self.machine.on_enter_starting_trigger('start_trigger_process')
-        self.machine.on_enter_playing_ready_sound('play_ready_sound_process')
         self.machine.on_enter_waiting_for_trigger_callback('waiting_for_trigger_callback_thread')
-        self.machine.on_enter_playing_wake_up_answer('play_wake_up_answer_thread')
         self.machine.on_enter_stopping_trigger('stop_trigger_process')
         self.machine.on_enter_start_order_listener('start_order_listener_thread')
         self.machine.on_enter_waiting_for_order_listener_callback('waiting_for_order_listener_callback_thread')
         self.machine.on_enter_analysing_order('analysing_order_thread')
 
     def run(self):
+        # run hook on_start
+        HookManager.on_start()
         self.start_trigger()
 
     def start_trigger_process(self):
@@ -100,6 +87,7 @@ class Order(Thread):
         This function will start the trigger thread that listen for the hotword
         """
         logger.debug("[MainController] Entering state: %s" % self.state)
+        HookManager.on_waiting_for_trigger()
         self.trigger_instance = TriggerLauncher.get_trigger(settings=self.settings, callback=self.trigger_callback)
         self.trigger_callback_called = False
         self.trigger_instance.daemon = True
@@ -107,23 +95,6 @@ class Order(Thread):
         self.trigger_instance.start()
         self.next_state()
 
-    def play_ready_sound_process(self):
-        """
-        Play a sound when Kalliope is ready to be awaken at the first start
-        """
-        logger.debug("[MainController] Entering state: %s" % self.state)
-        if (not self.on_ready_notification_played_once and self.settings.play_on_ready_notification == "once") or \
-                        self.settings.play_on_ready_notification == "always":
-            # we remember that we played the notification one time
-            self.on_ready_notification_played_once = True
-            # here we tell the user that we are listening
-            if self.settings.on_ready_answers is not None:
-                Say(message=self.settings.on_ready_answers)
-            elif self.settings.on_ready_sounds is not None:
-                random_sound_to_play = self._get_random_sound(self.settings.on_ready_sounds)
-                self.player_instance.play(random_sound_to_play)
-        self.next_state()
-
     def waiting_for_trigger_callback_thread(self):
         """
         Method to print in debug that the main process is waiting for a trigger detection
@@ -137,6 +108,8 @@ class Order(Thread):
         # this loop is used to keep the main thread alive
         while not self.trigger_callback_called:
             sleep(0.1)
+        # if here, then the trigger has been called
+        HookManager.on_triggered()
         self.next_state()
 
     def waiting_for_order_listener_callback_thread(self):
@@ -147,9 +120,7 @@ class Order(Thread):
         # this loop is used to keep the main thread alive
         while not self.order_listener_callback_called:
             sleep(0.1)
-        if self.settings.rpi_settings:
-            if self.settings.rpi_settings.pin_led_listening:
-                RpiUtils.switch_pin_to_off(self.settings.rpi_settings.pin_led_listening)
+        # TODO on end listening here
         self.next_state()
 
     def trigger_callback(self):
@@ -174,6 +145,7 @@ class Order(Thread):
         Start the STT engine thread
         """
         logger.debug("[MainController] Entering state: %s" % self.state)
+        HookManager.on_start_listening()
         # start listening for an order
         self.order_listener_callback_called = False
         self.order_listener = OrderListener(callback=self.order_listener_callback)
@@ -181,20 +153,6 @@ class Order(Thread):
         self.order_listener.start()
         self.next_state()
 
-    def play_wake_up_answer_thread(self):
-        """
-        Play a sound or make Kalliope say something to notify the user that she has been awaken and now
-        waiting for order
-        """
-        logger.debug("[MainController] Entering state: %s" % self.state)
-        # if random wake answer sentence are present, we play this
-        if self.settings.random_wake_up_answers is not None:
-            Say(message=self.settings.random_wake_up_answers)
-        else:
-            random_sound_to_play = self._get_random_sound(self.settings.random_wake_up_sounds)
-            self.player_instance.play(random_sound_to_play)
-        self.next_state()
-
     def order_listener_callback(self, order):
         """
         Receive an order, try to retrieve it in the brain.yml to launch to attached plugins
@@ -202,6 +160,7 @@ class Order(Thread):
         :type order: str
         """
         logger.debug("[MainController] Order listener callback called. Order to process: %s" % order)
+        HookManager.on_stop_listening()
         self.order_to_process = order
         self.order_listener_callback_called = True
 
@@ -218,20 +177,6 @@ class Order(Thread):
         # return to the state "unpausing_trigger"
         self.start_trigger()
 
-    @staticmethod
-    def _get_random_sound(random_wake_up_sounds):
-        """
-        Return a path of a sound to play
-        If the path is absolute, test if file exist
-        If the path is relative, we check if the file exist in the sound folder
-        :param random_wake_up_sounds: List of wake_up sounds
-        :return: path of a sound to play
-        """
-        # take first randomly a path
-        random_path = random.choice(random_wake_up_sounds)
-        logger.debug("[MainController] Selected sound: %s" % random_path)
-        return Utils.get_real_file_path(random_path)
-
     def set_mute_status(self, muted=False):
         """
         Define is the trigger is listening or not
@@ -242,10 +187,12 @@ class Order(Thread):
             self.trigger_instance.pause()
             self.is_trigger_muted = True
             Utils.print_info("Kalliope now muted")
+            HookManager.on_mute()
         else:
             self.trigger_instance.unpause()
             self.is_trigger_muted = False
             Utils.print_info("Kalliope now listening for trigger detection")
+            HookManager.on_unmute()
 
     def get_mute_status(self):
         """
@@ -253,20 +200,3 @@ class Order(Thread):
         :return: Boolean
         """
         return self.is_trigger_muted
-
-    def init_rpi_utils(self):
-        """
-        Start listening on GPIO if defined in settings
-        """
-        if self.settings.rpi_settings:
-            # the user set GPIO pin, we need to instantiate the RpiUtils class in order to setup GPIO
-            rpi_utils = RpiUtils(self.settings.rpi_settings, self.set_mute_status)
-            if self.settings.rpi_settings.pin_mute_button:
-                # start the listening for button pressed thread only if the user set a pin
-                rpi_utils.daemon = True
-                rpi_utils.start()
-        # switch high the start led, as kalliope is started. Only if the setting exist
-        if self.settings.rpi_settings:
-            if self.settings.rpi_settings.pin_led_started:
-                logger.debug("[MainController] Switching pin_led_started to ON")
-                RpiUtils.switch_pin_to_on(self.settings.rpi_settings.pin_led_started)

+ 0 - 5
kalliope/stt/Utils.py

@@ -5,7 +5,6 @@ import logging
 import speech_recognition as sr
 
 from kalliope import Utils, SettingLoader
-from kalliope.core.Utils.RpiUtils import RpiUtils
 
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
@@ -60,10 +59,6 @@ class SpeechRecognition(Thread):
         """
         if self.audio_stream is None:
             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)
             while not self.kill_yourself:
                 sleep(0.1)

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

@@ -151,6 +151,9 @@ class HotwordDetector(Thread):
                     callback = self.detected_callback[ans-1]
                     if callback is not None:
                         callback()
+            else:
+                # take a little break
+                time.sleep(self.sleep_time)
 
         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
 if sys.version_info[0] == 3:
     logger.error("[Voicerss] WARNING : VOICERSS is not working for python3 yet !")
-else :
+else:
     from voicerss_tts.voicerss_tts import TextToSpeech
 
 TTS_URL = "http://www.voicerss.org/controls/speech.ashx"

+ 0 - 1
setup.py

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