Ver código fonte

Merge pull request #280 from kalliope-project/dev

Kalliope v0.4.4
Monf 7 anos atrás
pai
commit
622d5eef76
100 arquivos alterados com 4705 adições e 1729 exclusões
  1. 23 2
      .travis.yml
  2. 17 0
      CHANGELOG.md
  3. 28 0
      Docs/installation.md
  4. 260 0
      Docs/installation/create_raspbian_image.md
  5. 1 1
      Docs/installation/debian_jessie.md
  6. 3 2
      Docs/installation/raspbian_jessie.md
  7. 1 1
      Docs/installation/ubuntu_14.04.md
  8. 1 1
      Docs/installation/ubuntu_16.04.md
  9. 39 29
      Docs/neurons.md
  10. 66 0
      Docs/player.md
  11. 20 0
      Docs/player_list.md
  12. 79 46
      Docs/rest_api.md
  13. 103 0
      Docs/settings.md
  14. 1 1
      Docs/stt.md
  15. 1 1
      Docs/stt_list.md
  16. 2 0
      Docs/trigger.md
  17. 10 3
      README.md
  18. 10 11
      Tests/__init__.py
  19. 587 0
      Tests/backup_test_order_analyser.py
  20. 7 0
      Tests/brains/brain_test.yml
  21. 63 0
      Tests/brains/lifo_buffer_test_brain.yml
  22. 17 0
      Tests/settings/settings_test.yml
  23. 63 23
      Tests/test_brain_loader.py
  24. 2 2
      Tests/test_dynamic_loading.py
  25. 14 4
      Tests/test_file_manager.py
  26. 111 0
      Tests/test_init.py
  27. 0 140
      Tests/test_launchers.py
  28. 358 0
      Tests/test_lifo_buffer.py
  29. 473 0
      Tests/test_models.py
  30. 227 0
      Tests/test_neuron_launcher.py
  31. 0 40
      Tests/test_neuron_model.py
  32. 50 89
      Tests/test_neuron_module.py
  33. 175 0
      Tests/test_neuron_parameter_loader.py
  34. 35 495
      Tests/test_order_analyser.py
  35. 3 3
      Tests/test_order_listener.py
  36. 44 0
      Tests/test_player_launcher.py
  37. 84 92
      Tests/test_rest_api.py
  38. 17 2
      Tests/test_settings_loader.py
  39. 3 3
      Tests/test_singleton.py
  40. 125 0
      Tests/test_synapse_launcher.py
  41. 47 0
      Tests/test_trigger_launcher.py
  42. 6 6
      Tests/test_tts_module.py
  43. 34 20
      Tests/test_utils.py
  44. 8 1
      Tests/test_yaml_loader.py
  45. 1 2
      brain_examples/neurotransmitter.yml
  46. 1 2
      brain_examples/openweathermap.yml
  47. 0 2
      brain_examples/twitter.yml
  48. 1 2
      brain_examples/wikipedia.yml
  49. 36 0
      docker/clone_and_test.sh
  50. 41 0
      docker/debian8.dockerfile
  51. 51 0
      docker/docker.md
  52. 41 0
      docker/ubuntu_16_04.dockerfile
  53. BIN
      images/led_kalliope_circuit.png
  54. 1 0
      install/files/deb-packages_requirements.txt
  55. 6 1
      install/files/python_requirements.txt
  56. 0 78
      install/install.yml
  57. 0 15
      install/install_kalliope.sh
  58. 47 0
      install/rpi_install_kalliope.sh
  59. 70 34
      kalliope/__init__.py
  60. 1 1
      kalliope/_version.py
  61. 19 21
      kalliope/core/ConfigurationManager/BrainLoader.py
  62. 1 1
      kalliope/core/ConfigurationManager/ConfigurationChecker.py
  63. 115 20
      kalliope/core/ConfigurationManager/SettingLoader.py
  64. 4 2
      kalliope/core/ConfigurationManager/YAMLLoader.py
  65. 3 3
      kalliope/core/ConfigurationManager/__init__.py
  66. 1 1
      kalliope/core/EventManager.py
  67. 196 0
      kalliope/core/LIFOBuffer.py
  68. 76 54
      kalliope/core/MainController.py
  69. 26 0
      kalliope/core/Models/APIResponse.py
  70. 1 5
      kalliope/core/Models/Dna.py
  71. 1 3
      kalliope/core/Models/Event.py
  72. 55 0
      kalliope/core/Models/MatchedSynapse.py
  73. 9 5
      kalliope/core/Models/Neuron.py
  74. 1 1
      kalliope/core/Models/Order.py
  75. 27 0
      kalliope/core/Models/Player.py
  76. 1 5
      kalliope/core/Models/Resources.py
  77. 24 9
      kalliope/core/Models/RestAPI.py
  78. 35 0
      kalliope/core/Models/RpiSettings.py
  79. 42 3
      kalliope/core/Models/Settings.py
  80. 7 1
      kalliope/core/Models/Stt.py
  81. 1 8
      kalliope/core/Models/Synapse.py
  82. 7 1
      kalliope/core/Models/Trigger.py
  83. 7 1
      kalliope/core/Models/Tts.py
  84. 8 8
      kalliope/core/Models/__init__.py
  85. 90 1
      kalliope/core/NeuronLauncher.py
  86. 120 68
      kalliope/core/NeuronModule.py
  87. 65 0
      kalliope/core/NeuronParameterLoader.py
  88. 46 247
      kalliope/core/OrderAnalyser.py
  89. 2 0
      kalliope/core/OrderListener.py
  90. 30 0
      kalliope/core/PlayerLauncher.py
  91. 41 0
      kalliope/core/PlayerModule.py
  92. 0 1
      kalliope/core/Players/__init__.py
  93. 33 33
      kalliope/core/RestAPI/FlaskAPI.py
  94. 1 14
      kalliope/core/ShellGui.py
  95. 58 13
      kalliope/core/SynapseLauncher.py
  96. 0 27
      kalliope/core/TTS/TTSLauncher.py
  97. 11 5
      kalliope/core/TTS/TTSModule.py
  98. 1 0
      kalliope/core/TTS/__init__.py
  99. 17 11
      kalliope/core/TriggerLauncher.py
  100. 8 2
      kalliope/core/Utils/FileManager.py

+ 23 - 2
.travis.yml

@@ -1,8 +1,16 @@
 language: python
+
+services:
+  - docker
+
 python:
   - "2.7"
+  - "3.4"
+
 # command to install dependencies
 before_install:
+- docker build --force-rm=true --build-arg TRAVIS_BRANCH=${TRAVIS_BRANCH} --build-arg TRAVIS_EVENT_TYPE=${TRAVIS_EVENT_TYPE} --build-arg TRAVIS_PULL_REQUEST_SLUG=${TRAVIS_PULL_REQUEST_SLUG} --build-arg TRAVIS_PULL_REQUEST_BRANCH=${TRAVIS_PULL_REQUEST_BRANCH} -t kalliope-ubuntu1604 -f docker/ubuntu_16_04.dockerfile .
+- docker build --force-rm=true --build-arg TRAVIS_BRANCH=${TRAVIS_BRANCH} --build-arg TRAVIS_EVENT_TYPE=${TRAVIS_EVENT_TYPE} --build-arg TRAVIS_PULL_REQUEST_SLUG=${TRAVIS_PULL_REQUEST_SLUG} --build-arg TRAVIS_PULL_REQUEST_BRANCH=${TRAVIS_PULL_REQUEST_BRANCH} -t kalliope-debian8 -f docker/debian8.dockerfile .
 - sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu trusty main restricted universe multiverse"
 - sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu trusty-updates main restricted universe multiverse"
 - sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse"
@@ -11,6 +19,19 @@ before_install:
 - sudo apt-get install libstdc++6
 - wget https://bootstrap.pypa.io/get-pip.py
 - sudo python get-pip.py
-install: "pip install -r install/files/python_requirements.txt"
+
+install:
+  - pip install -r install/files/python_requirements.txt
+  - pip install python2-pythondialog || pip install pythondialog
+  - pip install coveralls
+
 # command to run tests
-script: pytest
+script:
+#  - pytest
+  - docker run -it --rm kalliope-ubuntu1604
+  - docker run -it --rm kalliope-debian8
+  - coverage run --source=kalliope -m unittest discover
+
+after_success:
+  coveralls
+

+ 17 - 0
CHANGELOG.md

@@ -1,3 +1,20 @@
+v0.4.4 / 2017-05-20
+===================
+- Fix: Uppercase in order/parameters/global variables are now handled correctly
+- Fix: usage of integer in neuron parameters
+- Fix: encoding with special character
+- Refactor main controller. Use a LIFO to allow full usage of kalliope via API (even with neurotransmitter)
+- Add a systemd script to start kalliope automatically
+- docker testing
+- python 3 support 3.4, 3.5, 3.6
+- Increase testing code coverage
+- Fix: Raspberry performance. CPU usage from 120% to 15%
+- Input value refactoring. "args" parameter replaced by jinja templating
+- Review TTS overriding config in neuron declaration
+- Fix: accapela TTS
+- LED and mute button support for Raspberry Pi
+- Player modularity
+
 v0.4.3 / 2017-03-11
 ===================
 - Update Documentation

+ 28 - 0
Docs/installation.md

@@ -95,5 +95,33 @@ Those repositories provide you a basic structure to start playing with kalliope.
 - [English starter config](https://github.com/kalliope-project/kalliope_starter_en)
 - [German starter config](https://github.com/kalliope-project/kalliope_starter_de)
 
+
+## Start Kalliope automatically after a reboot
+
+If you want to start Kalliope automatically Place the script bellow in `/etc/systemd/system/kalliope.service`.
+Update the path to your folder where you've placed your `brain.yml` and `settings.yml`.
+```bash
+[Unit]
+Description=Kalliope
+
+[Service]
+WorkingDirectory=/path/to/kalliope_brain_folder
+
+Environment='STDOUT=/var/log/kalliope.log'
+Environment='STDERR=/var/log/kalliope.err.log'
+ExecStart=/bin/bash -c "/usr/local/bin/kalliope start > ${STDOUT} 2> ${STDERR}"
+User=%i
+
+[Install]
+WantedBy=multi-user.target
+```
+
+Then, reload systemctl, start the service and enable it at startup
+```bash
+sudo systemctl daemon-reload
+sudo systemctl start kalliope
+sudo systemctl enable kalliope
+```
+
 ## Next: 
 If everything is ok, you can start playing with Kalliope. First, take a look to the [default settings](settings.md).

+ 260 - 0
Docs/installation/create_raspbian_image.md

@@ -0,0 +1,260 @@
+# Create a Raspbian image with already installed Kalliope
+
+This documentation aims at explaining how to creta a Raspbian image with pre installed Kalliope.
+
+Install a fresh [image of Raspbian](http://downloads.raspberrypi.org/raspbian/images/) as usual on your raspberry Pi.
+Once deployed, follow manual steps bellow.
+
+>**Note:** From here I suppose that the Rpi has received a valid IP from your LAN DHCP server and can access to the internet.
+
+## Prepare the image
+Login to your Rpi.
+
+Enable SSH
+```bash
+sudo systemctl enable ssh
+sudo systemctl start ssh
+```
+
+You now have a SSH connection, you can connect remotely to your Pi to perform next steps from a console.
+
+>**Note:** The SSH server is listening on the default SSH port with the default Rasbpian credentials. This can be a security issue.
+It is recommended  to check that the Rpi is not directly accessible from the internet.
+
+Install Kalliope from the script
+```bash
+curl -s https://raw.githubusercontent.com/kalliope-project/kalliope/master/install/rpi_install_kalliope.sh | bash
+```
+
+If you want to install a particular branch you can specify it with an argument following the syntax bellow
+```
+curl -s https://raw.githubusercontent.com/kalliope-project/kalliope/master/install/rpi_install_kalliope.sh | bash -s <branch_name>
+```
+
+E.g
+```bash
+curl -s https://raw.githubusercontent.com/kalliope-project/kalliope/master/install/rpi_install_kalliope.sh | bash -s dev
+```
+
+Check Kalliope is installed
+```bash
+kalliope --version
+```
+
+Cleanup installation files
+```bash
+rm -rf get-pip.py
+sudo rm -rf kalliope
+```
+
+Clone some starter kit
+```bash
+git clone https://github.com/kalliope-project/kalliope_starter_fr.git
+git clone https://github.com/kalliope-project/kalliope_starter_en.git
+git clone https://github.com/kalliope-project/kalliope_starter_de.git
+```
+
+Change the hostname
+```bash
+sudo hostnamectl set-hostname kalliope
+sudo sed -i 's/raspberrypi/kalliope/g' /etc/hosts
+```
+
+Clear the command line history
+```bash
+history -c
+```
+
+Shutdown the Rpi
+```bash
+sudo shutdown -h now
+```
+
+## Create the image
+
+Next commands have been tested on Ubuntu 16.04.
+
+In the next part we create an image an shrink it in order to take less storage space.
+>**Note:** Raspbian operating system comes with a tool to resize the filesystem to the largest size the SD card will support (sudo raspi-config, then select Expend Filesystem). You wont lose space by shrinking the image because you can expand it back again.
+
+>**Note:** Be sure of what you doing in next steps. Writing disk image on the wrong disk will destroy all your computer data. 
+
+Remove the SD card from your Rpi and connect it into a Linux distrib via an external USB card reader.
+
+Check where the card is mounted
+```bash
+df -h
+```
+
+The output should looks like this
+```bash
+df -h
+Filesystem      Size  Used Avail Use% Mounted on
+--- TRUNCKATED ---
+/dev/sdb2        15G  1.3G   13G  10% /media/nico/f2100b2f-ed84-4647-b5ae-089280112716
+/dev/sdb1        41M   21M   21M  51% /media/nico/boot
+```
+
+The SD card is on **/dev/sdb device**. It has two partition, **/dev/sdb1** and **/dev/sdb2**.
+
+>**Note:** Your system might mount the card somewhere else depending on the number of disk you already have like /dev/sdc or /dev/sde. 
+Note down the path where your SD is.
+
+Unmount the two partitions. Keep the SD card in the reader and connected to the system.
+```bash
+sudo umount /dev/sdb1 /dev/sdb2
+```
+
+Make the image with **dcfldd**. This program is a replacement for the old dd disk utility program that show the progression of a copy.
+
+Install the tool
+```bash
+sudo apt-get install dcfldd
+```
+
+Create the image following this syntax.
+```
+sudo dcfldd if=<my_sd_card_disk_path> of=<target_path>/kalliope.img
+```
+
+E.g
+```bash
+sudo dcfldd if=/dev/sdb of=kalliope.img
+```
+>**Note:** Be sure you have enough space available in the target path
+
+It will take a couple minutes to create the image depending of the size of your SD card.
+
+Once it's done, give the ownership back to your current user. (the image belong to root as we created it with sudo)
+```bash
+sudo chown ${USER}:${USER} kalliope.img
+```
+
+Now we have a file that can already be used to instantiate a Rpi. But the file is big as the SD card itself.
+To reduce the size of the image we need `gparted`. Install it
+```bash
+sudo apt-get install gparted
+```
+
+Gparted is only able to edit physical device, so we need to create a virtual device from the image before using it.
+As we saw when we have identified our disk, Raspbian has two partitions. The fist one, boot, is already tiny and does not need to be shrank.
+The second partition is where everything is stored. It contains a lot of free space.
+
+Show partition info from the image
+```bash
+sudo fdisk -l kalliope.img
+```
+
+The output should looks like this
+```
+Device        Boot Start      End  Sectors  Size Id Type
+kalliope.img1       8192    92159    83968   41M  c W95 FAT32 (LBA)
+kalliope.img2      92160 31116287 31024128 14.8G 83 Linux
+```
+
+Export the START sector of the second partition. The variable will be used in next commands.
+```bash
+export START=92160
+```
+
+Check the env variable is set correctly
+```bash
+echo ${START}
+```
+
+Create the virtual drive with only the second patition
+```bash
+sudo losetup /dev/loop0 kalliope.img -o $((START*512))
+```
+
+Now read the loopback device with gparted
+```bash
+sudo gparted /dev/loop0
+```
+
+Gparted will show you the state of the partition. Click the `/dev/loop0` partition and select **Resize/Move** from the menu.
+change the value of "New Size" so that it is slighty abose the "Minimum Size". 
+Note down the new size! In this example the new size is **2000 MB**.
+Then apply the resizing and exit gparted.
+
+Remove the loopback device and create a new one with the whole image this time.
+```bash
+sudo losetup -d /dev/loop0
+sudo losetup /dev/loop0 kalliope.img
+```
+
+Now, we use **fdisk** to edit the partition table in order to resize it to the new size.
+```bash
+sudo fdisk /dev/loop0
+```
+
+You should now see the **fdisk** prompt.
+- Enter **d 2** to delete the table entry for the second partition
+- Enter n p 2 to create a new second partition entry
+- Enter the START sector number that you used earlier.
+- Enter `+NEWSIZE` as the new size. Don't forget the "+" at the start. For example `+2000M`
+- Enter w to write the new partition
+
+
+Output example
+```
+Command (m for help): d
+Partition number (1,2, default 2): 2
+
+Partition 2 has been deleted.
+
+Command (m for help): n
+Partition type
+   p   primary (1 primary, 0 extended, 3 free)
+   e   extended (container for logical partitions)
+Select (default p): p
+Partition number (2-4, default 2): 2
+First sector (2048-31116287, default 2048): 92160
+Last sector, +sectors or +size{K,M,G,T,P} (92160-31116287, default 31116287): +2000M
+
+Created a new partition 2 of type 'Linux' and of size 2 GiB.
+
+Command (m for help): w
+The partition table has been altered.
+Calling ioctl() to re-read partition table.
+```
+Let's take a look to the partition table again
+```
+sudo fdisk -l /dev/loop0
+
+Device       Boot Start     End Sectors Size Id Type
+/dev/loop0p1       8192   92159   83968  41M  c W95 FAT32 (LBA)
+/dev/loop0p2      92160 4188159 4096000   2G 83 Linux
+```
+
+Note down the ED sector of the second partition
+```bash
+export END=4188159
+```
+
+Destroy the loopback
+```bash
+sudo losetup -d /dev/loop0
+```
+
+Now, trim the file to the new length.
+```
+truncate -s $(((END+1)*512)) kalliope.img
+```
+
+Check the new size of the image
+```bash
+du -hs kalliope.img 
+2.0G	kalliope.img
+```
+
+You can now compress it to reduce a little more the size
+```bash
+zip kalliope.img.zip kalliope.img
+```
+
+Final size
+```bash
+du -hs kalliope.img.zip 
+727M	kalliope.img.zip
+```

+ 1 - 1
Docs/installation/debian_jessie.md

@@ -12,7 +12,7 @@ Install some required system libraries and softwares:
 
 ```bash
 sudo apt-get update
-sudo apt-get install git python-dev libsmpeg0 libttspico-utils libsmpeg0 flac dialog libffi-dev libffi-dev libssl-dev portaudio19-dev build-essential libssl-dev libffi-dev sox libatlas3-base mplayer
+sudo apt-get install git python-dev libsmpeg0 libttspico-utils libsmpeg0 flac dialog libffi-dev libffi-dev libssl-dev portaudio19-dev build-essential libssl-dev libffi-dev sox libatlas3-base mplayer libav-tools
 ```
 
 Let's install the last release of python-pip

+ 3 - 2
Docs/installation/raspbian_jessie.md

@@ -9,7 +9,7 @@ Download the last image [from the release page](https://github.com/kalliope-proj
 
 Once installed, use the `raspi-config` command to expend the file system and fill the whole available space on your SD card.
 The SSH server is already active. You only need to get the ip of your Rpi via the command `ip a` and then connect via your favourite SSH client.
-We placed in `/home/pi` the two starter config we made for [French](https://github.com/kalliope-project/kalliope_starter_fr) and [English](https://github.com/kalliope-project/kalliope_starter_en). 
+We placed in `/home/pi` the two starter config we made for [French](https://github.com/kalliope-project/kalliope_starter_fr), [English](https://github.com/kalliope-project/kalliope_starter_en) and [German](https://github.com/kalliope-project/kalliope_starter_de).
 
 ## Manual installation
 
@@ -17,6 +17,7 @@ Supported Raspbian images:
 [raspbian-2016-09-28](http://downloads.raspberrypi.org/raspbian/images/raspbian-2016-09-28/)
 [raspbian-2016-11-29](http://downloads.raspberrypi.org/raspbian/images/raspbian-2016-11-29/)
 [raspbian-2017-01-10](http://downloads.raspberrypi.org/raspbian/images/raspbian-2017-01-10/)
+[raspbian-2017-04-10](http://downloads.raspberrypi.org/raspbian/images/raspbian-2017-04-10/)
 
 > **Note:** We recommend to use a **lite** installation of Raspbian without any graphical interface for a better experience. 
 
@@ -28,7 +29,7 @@ Install some required system libraries and software:
 
 ```bash
 sudo apt-get update
-sudo apt-get install git python-dev libsmpeg0 libttspico-utils libsmpeg0 flac dialog libffi-dev libffi-dev libssl-dev portaudio19-dev build-essential libssl-dev libffi-dev sox libatlas3-base mplayer libyaml-dev libpython2.7-dev
+sudo apt-get install git python-dev libsmpeg0 libttspico-utils libsmpeg0 flac dialog libffi-dev libffi-dev libssl-dev portaudio19-dev build-essential libssl-dev libffi-dev sox libatlas3-base mplayer libyaml-dev libpython2.7-dev libav-tools
 ```
 
 Let's install the last release of python-pip

+ 1 - 1
Docs/installation/ubuntu_14.04.md

@@ -6,7 +6,7 @@ Install some required system libraries and softwares:
 
 ```bash
 sudo apt-get update
-sudo apt-get install git python-dev libsmpeg0 libttspico-utils libsmpeg0 flac dialog libffi-dev libffi-dev libssl-dev libjack0 libjack-dev portaudio19-dev build-essential libssl-dev libffi-dev sox libatlas3-base mplayer
+sudo apt-get install git python-dev libsmpeg0 libttspico-utils libsmpeg0 flac dialog libffi-dev libffi-dev libssl-dev libjack0 libjack-dev portaudio19-dev build-essential libssl-dev libffi-dev sox libatlas3-base mplayer libav-tools
 ```
 
 Let's install the last release of python-pip

+ 1 - 1
Docs/installation/ubuntu_16.04.md

@@ -6,7 +6,7 @@ Install some required system libraries and softwares:
 
 ```bash
 sudo apt-get update
-sudo apt-get install git python-dev libsmpeg0 libttspico-utils libsmpeg0 flac dialog libffi-dev libffi-dev libssl-dev portaudio19-dev build-essential libssl-dev libffi-dev sox libatlas3-base mplayer
+sudo apt-get install git python-dev libsmpeg0 libttspico-utils libsmpeg0 flac dialog libffi-dev libffi-dev libssl-dev portaudio19-dev build-essential libssl-dev libffi-dev sox libatlas3-base mplayer libav-tools
 ```
 Let's install the last release of python-pip
 ```bash

+ 39 - 29
Docs/neurons.md

@@ -55,22 +55,20 @@ From global variables: (cf: [settings.md](settings.md))
 
 From the captured order:
 ```yml
-  - name: "run-neuron-with-parameter-in-order"
+  - name: "say-hello"
     signals:
-      - order: "this is an order with the parameter {{ parameter3 }}"
+      - order: "say hello to {{ name }}"
     neurons:
-      - neuron_name:
-          parameter1: "value1"
-          parameter2: "value2"
-          args:
-          - parameter3
+      - say:
+          message:
+            - "Hello {{ name }}"
 ```
 
-Here, the spoken value captured by the TTS engine will be passed as an argument to the neuron in the variable named `parameter3`.
+Here, the spoken value captured by the TTS engine will be passed as an argument to the neuron in every parameters that want use it.
 
-Example, with the synapse declaration above, if you say "this is an order with the parameter Amy Winehouse". The neuron will receive a parameter named `parameter3` with "Amy Winehouse" as a value of this parameter.
+Example, with the synapse declaration above, if you say "say hello to Bob". The parameter parameter message is instantiated and all `{{ name }}` are replaced by "bob".
 We recommend the reading of the [signals documentation](signals.md) for a complete understanding of how arguments in a neuron work.
-
+> **Note:** If a parameter of a neuron is waiting for a variable from the order and this variable haven't been found in the spoken order, then the neuron is not launched.
 
 ## Output values
 
@@ -124,12 +122,12 @@ As this is multi-lines, we can put the content in a file and use a `file_templat
 
 ## Overridable parameters
 
-For each neuron, you can override some parameters to use a specific configuration of TTS instead of the default one 
-set in [settings.yml](settings.md) file.
+For each neuron, you can override some parameters to use a specific configuration instead of the default one set in [settings.yml](settings.md) file.
 
-### Cache
+### TTS
 
-You can override the default cache configuration. By default Kalliope uses a cache to save a generated audio from a TTS engine.
+You can override the default tts configuration like the TTS engine to use and its parameters like the language, api key or the cache.
+By default Kalliope uses a cache to save a generated audio from a TTS engine.
 This cache is useful to manage sentences that are not suppose to be changed very often. For example, the following sentence will not change in time, so it's more optimized to generate it once and to keep it in cash:
 ```yml
 - say:
@@ -138,28 +136,40 @@ This cache is useful to manage sentences that are not suppose to be changed very
 ```
 
 In some cases, especially when the neuron is based on a template, the generated audio will change on each new call of the neuron and so the usage of a cache is not necessary. The best example of the case like this is the `systemdate` neuron. As the time changes every minute, the generated audio will change too and so, saving the generated audio in the cache is useless. In this case, you can override the cache usage for this neuron:
+Here are my default TTS settings in my `settings.yml` file
+```yml
+default_text_to_speech: "pico2wave"
+
+text_to_speech:
+  - pico2wave:
+      language: "fr-FR"
+      cache: True
+```
+
+And this is how I override only the cache parameter. 
 ```yml
 - systemdate:
     say_template:
       - "It's {{ hours }} hour and {{ minutes }} minute"
-    cache: False
+    tts:
+      pico2wave:
+        cache: False
 ```
 
-### tts
-
-You can override the default tts for each neurons. Just add the "tts" parameter to the neuron like in the example bellow
+You can override all parameter for each neurons like in the example bellow
 ```yml
-neurons:
-  - say:
-    message:
-      - "Hello, sir"
-  - say:
-    message:
-      - "My name is Kalliope"
-    tts: "acapela"
+- name: "say-something-in-spanish"
+    signals:
+      - order: "Say hello in spanish"
+    neurons:
+      - say:
+          message:
+            - "Buenos dias"
+          tts:             
+            pico2wave:
+              language: "es-ES"
+              cache: False
 ```
 
-Here, the first neuron will use the default tts as set in the settings.yml file. The second neuron will use the tts "acapela".
-
->**Note:** The TTS must has been configured with its required parameters in the settings.yml file. See [TTS documentation](tts.md).
+>**Note:** The TTS must has been configured with its required parameters in the settings.yml file to be overridden. See [TTS documentation](tts.md).
 

+ 66 - 0
Docs/player.md

@@ -0,0 +1,66 @@
+# Players                                                                                                                                         # Players
+
+The player is the library/software used to make Kalliope talk.
+With Kalliope project, you can set whatever sound player you want to use.
+
+## Settings
+
+Define the player you want to use by default in the [setting.yml](settings.md) file.
+```yml
+default_player: "player_name"
+```
+
+E.g
+```yml
+default_player: "mplayer"
+```
+
+Then, still in the [setting.yml](settings.md) file, each player must set up its configuration following the 'players' tag :
+```yml
+players:
+   - player1:
+      player1parameter1: "value option1"
+      player1parameter2: "value option2"
+   - player2:
+      player2parameter1: "value option1"
+```
+
+E.g
+```yml
+players:
+  - mplayer: {}
+  - pyalsaaudio:
+     device: "default"
+     convert_to_wav: True
+  - pyaudioplayer:
+     convert_to_wav: True
+  - sounddeviceplayer:
+     convert_to_wav: True
+```
+
+>**Note:** Sometime, parameters will be necessary to use an engine. 
+Click on a Player engine link in the `Current CORE Available Players` section to know which parameter are required.
+
+>**Note:** A player which does not ask for input parameters need to be declared as an empty dict. E.g: ```- player_name: {}```
+
+## Current CORE Available Players
+
+Core players are already packaged with the installation of Kalliope an can be used out of the box. See the [complete list here](player_list.md).
+
+## Full Example
+
+In the settings.yml file :
+
+```yml
+default_player: "mplayer"
+
+players:
+  - mplayer: {}
+  - pyalsaaudio:
+     device: "default"
+     convert_to_wav: True
+  - pyaudioplayer:
+     convert_to_wav: True
+  - sounddeviceplayer:
+     convert_to_wav: True
+```

+ 20 - 0
Docs/player_list.md

@@ -0,0 +1,20 @@
+# List of available Player
+
+A player is a module that Kalliope will use when playing a sound generated from a TTS engine.
+You can define them in your [settings.yml file](settings.md). 
+
+## Core Players
+Core Players are already packaged with the installation of Kalliope an can be used out of the box.
+
+| Name              | Description                                                          | Note                                                                                    |
+|-------------------|----------------------------------------------------------------------|-----------------------------------------------------------------------------------------|
+| mplayer           | [mplayer](../kalliope/players/mplayer/README.md)                     | Based on [mplayer software](http://www.mplayerhq.hu/design7/news.html)                  |
+| pyalsaaudio       | [AlsaAudioPlayer](../kalliope/players/pyalsaaudio/README.md)         | Based on [pyalsaaudio](https://larsimmisch.github.io/pyalsaaudio/libalsaaudio.html) lib |
+| pyaudioplayer     | [PyAudioPlayer](../kalliope/players/pyaudioplayer/README.md)         | Based on [pyaudio](https://people.csail.mit.edu/hubert/pyaudio/docs/) lib               |
+| sounddeviceplayer | [SoundDevicePlayer](../kalliope/players/sounddeviceplayer/README.md) | Based on [sounddevice](https://pypi.python.org/pypi/sounddevice) lib                    |
+
+## Community Players
+Community Players need to be installed manually.
+
+Wanna add your Player in the list? Open [an issue](../../issues) and send a pull request to update the list directly.
+

+ 79 - 46
Docs/rest_api.md

@@ -130,23 +130,20 @@ curl -i --user admin:secret -X POST  http://localhost:5000/synapses/start/id/say
 Output example:
 ```JSON
 {
-  "synapses": {
-    "name": "say-hello",
-    "neurons": [
-      {
-        "say": {
-          "message": [
-            "Bonjour monsieur"
-          ]
+  "matched_synapses": [
+    {
+      "matched_order": null,
+      "neuron_module_list": [
+        {
+          "generated_message": "Bonjour monsieur",
+          "neuron_name": "Say"
         }
-      }
-    ],
-    "signals": [
-      {
-        "order": "bonjour"
-      }
-    ]
-  }
+      ],
+      "synapse_name": "say-hello-fr"
+    }
+  ],
+  "status": "complete",
+  "user_order": null
 }
 ```
 
@@ -174,31 +171,49 @@ curl -i --user admin:secret -H "Content-Type: application/json" -X POST --data @
 Output example if the order have matched and so launched synapses:
 ```JSON
 {
-  "synapses": [
+  "matched_synapses": [
     {
-      "name": "Say-hello", 
-      "neurons": [
+      "matched_order": "Bonjour",
+      "neuron_module_list": [
         {
-          "name": "say", 
-          "parameters": "{'message': ['Hello sir']}"
+          "generated_message": "Bonjour monsieur",
+          "neuron_name": "Say"
         }
-      ], 
-      "signals": [
+      ],
+      "synapse_name": "say-hello-fr"
+    }
+  ],
+  "status": "complete",
+  "user_order": "bonjour"
+}
+```
+
+If the order haven't match any synapses it will try to run the default synapse if it exists in your settings:
+```JSON
+{
+  "matched_synapses": [
+    {
+      "matched_order": null,
+      "neuron_module_list": [
         {
-          "order": "hello"
+          "generated_message": "Je n'ai pas compris votre ordre",
+          "neuron_name": "Say"
         }
-      ]
+      ],
+      "synapse_name": "default-synapse"
     }
-  ]
+  ],
+  "status": "complete",
+  "user_order": "not existing order"
 }
 ```
 
-If the order haven't match any synapses:
-```JSON
+Or return an empty list of matched synapse
+```
 {
-  "error": {
-    "error": "The given order doesn't match any synapses"
-  }
+  "matched_synapses": [],
+  "status": null,
+  "user_order": "not existing order"
 }
 ```
 
@@ -217,30 +232,48 @@ curl -i --user admin:secret -X POST  http://localhost:5000/synapses/start/audio
 Output example if the order inside the audio have matched and so launched synapses:
 ```JSON
 {
-  "synapses": [
+  "matched_synapses": [
     {
-      "name": "Say-hello", 
-      "neurons": [
+      "matched_order": "Bonjour",
+      "neuron_module_list": [
         {
-          "name": "say", 
-          "parameters": "{'message': ['Hello sir']}"
+          "generated_message": "Bonjour monsieur",
+          "neuron_name": "Say"
         }
-      ], 
-      "signals": [
+      ],
+      "synapse_name": "say-hello-fr"
+    }
+  ],
+  "status": "complete",
+  "user_order": "bonjour"
+}
+```
+
+If the order haven't match any synapses it will try to run the default synapse if it exists in your settings:
+```JSON
+{
+  "matched_synapses": [
+    {
+      "matched_order": null,
+      "neuron_module_list": [
         {
-          "order": "hello"
+          "generated_message": "Je n'ai pas compris votre ordre",
+          "neuron_name": "Say"
         }
-      ]
+      ],
+      "synapse_name": "default-synapse"
     }
-  ]
+  ],
+  "status": "complete",
+  "user_order": "not existing order"
 }
 ```
 
-If the order haven't match any synapses:
-```JSON
+Or return an empty list of matched synapse
+```
 {
-  "error": {
-    "error": "The given order doesn't match any synapses"
-  }
+  "matched_synapses": [],
+  "status": null,
+  "user_order": "not existing order"
 }
 ```

+ 103 - 0
Docs/settings.md

@@ -36,6 +36,59 @@ triggers:
 
 See the complete list of [available triggers here](trigger.md).
 
+## Players configuration
+
+#### default_player
+
+The player is the module managing the sound in Kalliope.
+
+Specify the name of the player module you want to use.
+```yml
+default_player: "player_name"
+```
+E.g
+```yml
+default_player: "mplayer"
+```
+
+#### players
+The player is the engine in charge of running sounds in Kalliope.
+
+Each Players has it own configuration. 
+This configuration is passed as argument following the syntax bellow
+```yml
+players:
+  - player_name:
+      parameter_name: "value"
+```
+
+E.g
+```yml
+players:
+  - mplayer: {}
+  - pyalsaaudio:
+     device: "default"
+     convert_to_wav: True
+  - pyaudioplayer:
+     convert_to_wav: True
+  - sounddeviceplayer:
+     convert_to_wav: True
+```
+
+See the complete list of [available players here](player_list.md).
+
+When no parameters are required set an empty object:
+```yml
+players:
+  - mplayer: {}
+```
+
+Sometime, parameters will be necessary to use an engine. See the [complete list here](player_list.md) to know which parameter are required.
+Core players are already packaged with the installation of Kalliope an can be used out of the box. 
+
+>**Note:** Most cloud based TTS generate a file in MP3 format. Some players are not able to read this format and then a conversion to wav is needed.
+
+
 ## Speech to text configuration
 
 #### default_speech_to_text
@@ -323,5 +376,55 @@ And use variables in your neurons:
           password: "{{password}}"
 ```
 
+## 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.
+
+
 ## Next: configure the brain of Kalliope
 Now your settings are ok, you can start creating the [brain](brain.md) of your assistant.

+ 1 - 1
Docs/stt.md

@@ -23,7 +23,7 @@ speech_to_text:
    - STT2:
       STT2parameter1: "value option1"
 ```
-Sometime, an API key will be necessary to use an engine. Click on a TTS engine link in the `Current Available STT` section to know which parameter are required.
+Sometime, an API key will be necessary to use an engine. Click on a TTS engine link in the `Current CORE Available STT` section to know which parameter are required.
 
 ## Current CORE Available STT
 

+ 1 - 1
Docs/stt_list.md

@@ -13,7 +13,7 @@ Core STTs are already packaged with the installation of Kalliope an can be used
 | CMUSphinx | [CMUSphinx](../kalliope/stt/cmusphinx/README.md) | Self hosted |
 | Google    | [google](../kalliope/stt/google/README.md)       | Cloud based |
 | Houndify  | [houndify](../kalliope/stt/houndify/README.md)   | Cloud based |
-| wit.ai    | [wit.ai](../kalliope/stt/wit/README.md)          | Cloud based |
+| wit.ai    | [wit](../kalliope/stt/wit/README.md)             | Cloud based |
 
 
 ## Community STT

+ 2 - 0
Docs/trigger.md

@@ -23,6 +23,7 @@ E.g
 kalliope-FR
 kalliope-EN
 kalliope-RU
+kalliope-DE
 ```
 Then, open an issue or create a pull request to add the model to the list bellow.
 
@@ -35,3 +36,4 @@ Then, open an issue or create a pull request to add the model to the list bellow
 | [kalliope-FR](https://snowboy.kitt.ai/hotword/1363) | French   | Ka-lio-pé      |
 | [kalliope-EN](https://snowboy.kitt.ai/hotword/2540) | English  | kə-LIE-ə-pee   |
 | [kalliope-RU](https://snowboy.kitt.ai/hotword/2964) | Russian  | каллиопа       |
+| [kalliope-DE](https://snowboy.kitt.ai/hotword/4324) | German  | Ka-lio-pe   |

+ 10 - 3
README.md

@@ -1,11 +1,15 @@
-# Kalliope
+<p align="center">
+    <img src="images/Kalliope_logo_large.png">
+</p>
 
+# Kalliope
 [![Build Status](https://travis-ci.org/kalliope-project/kalliope.svg)](https://travis-ci.org/kalliope-project/kalliope)
+[![Coverage Status](https://coveralls.io/repos/github/kalliope-project/kalliope/badge.svg)](https://coveralls.io/github/kalliope-project/kalliope)
 [![Gitter](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/kalliope-project/Lobby)
+[![PyPI version](https://badge.fury.io/py/kalliope.svg)](https://badge.fury.io/py/kalliope)
+[![PyPI](https://img.shields.io/pypi/pyversions/kalliope.svg)](https://pypi.python.org/pypi/kalliope/)
 
 
-![logo](images/Kalliope_logo_large.png)
-
 Kalliope is a modular always-on voice controlled personal assistant designed for home automation.
 Kalliope is based on audio input recognition, so it recognize an order pronounced by the user and then execute actions attached to the order.
 
@@ -53,6 +57,7 @@ Once installed, you can start learning basics of Kalliope from a [quick start co
 | [TTS](Docs/tts.md)                 | Text to speech configuration                                                                |
 | [Triggers](Docs/trigger.md)        | Magic hotword engine used to make Kalliope listening for an order                           |
 | [REST API](Docs/rest_api.md)       | Integrated REST API. Can be used to send an order                                           |
+| [Players](Docs/player.md)          | Player is the software used to make kalliope talk                                           |
 
 ## Contributing
 
@@ -62,6 +67,8 @@ Reading through this guide before writing any code is recommended.
 - Read the [contributing guide](Docs/contributing.md)
 - Add [issues and feature requests](../../issues)
 
+You liked kalliope? **Star us!**
+
 ## Credits
 
 > **Meaning of Kalliope** Kalliope means "beautiful voice" from Greek καλλος (kallos) "beauty" and οψ (ops) "voice". 

+ 10 - 11
Tests/__init__.py

@@ -1,11 +1,10 @@
-from test_brain_loader import TestBrainLoader
-from test_configuration_checker import TestConfigurationChecker
-from test_dynamic_loading import TestDynamicLoading
-from test_file_manager import TestFileManager
-from test_order_analyser import TestOrderAnalyser
-from test_rest_api import TestRestAPI
-from test_settings_loader import TestSettingLoader
-from test_singleton import TestSingleton
-from test_tts_module import TestTTSModule
-from test_yaml_loader import TestYAMLLoader
-from test_neuron_module import TestNeuronModule
+from .test_brain_loader import TestBrainLoader
+from .test_configuration_checker import TestConfigurationChecker
+from .test_dynamic_loading import TestDynamicLoading
+from .test_file_manager import TestFileManager
+from .test_rest_api import TestRestAPI
+from .test_settings_loader import TestSettingLoader
+from .test_singleton import TestSingleton
+from .test_tts_module import TestTTSModule
+from .test_yaml_loader import TestYAMLLoader
+from .test_neuron_module import TestNeuronModule

+ 587 - 0
Tests/backup_test_order_analyser.py

@@ -0,0 +1,587 @@
+import unittest
+import mock
+
+from kalliope.core.OrderAnalyser import OrderAnalyser
+from kalliope.core.Models.Neuron import Neuron
+from kalliope.core.Models.Synapse import Synapse
+from kalliope.core.Models.Brain import Brain
+from kalliope.core.Models.Settings import Settings
+from kalliope.core.Models.Order import Order
+
+
+class TestOrderAnalyser(unittest.TestCase):
+
+    """Test case for the OrderAnalyser Class"""
+
+    def setUp(self):
+        pass
+
+    def test_start(self):
+        """
+        Testing if the matches from the incoming messages and the signals/order sentences.
+        Scenarii :
+            - Order matchs a synapse and the synapse has been launched.
+            - Order does not match but have a default synapse.
+            - Order does not match and does not have default synapse.
+            - Provide synapse without any external orders
+            - Provide synapse with any external orders
+        """
+        # Init
+        neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
+        neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2'})
+        neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'})
+        neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'})
+
+        signal1 = Order(sentence="this is the sentence")
+        signal2 = Order(sentence="this is the second sentence")
+        signal3 = Order(sentence="that is part of the third sentence")
+
+        synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1])
+        synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2])
+        synapse3 = Synapse(name="Synapse3", neurons=[neuron2, neuron4], signals=[signal3])
+
+        all_synapse_list = [synapse1,
+                            synapse2,
+                            synapse3]
+
+        br = Brain(synapses=all_synapse_list)
+
+        with mock.patch("kalliope.core.OrderAnalyser._start_neuron") as mock_start_neuron_method:
+            # assert synapses have been launched
+            order_to_match = "this is the sentence"
+            oa = OrderAnalyser(order=order_to_match,
+                               brain=br)
+            expected_result = [synapse1]
+
+            self.assertEquals(oa.start(),
+                              expected_result,
+                              "Fail to run the expected Synapse matching the order")
+
+            calls = [mock.call(neuron1, {}), mock.call(neuron2, {})]
+            mock_start_neuron_method.assert_has_calls(calls=calls)
+            mock_start_neuron_method.reset_mock()
+
+            # No order matching Default Synapse to run
+            order_to_match = "random sentence"
+            oa = OrderAnalyser(order=order_to_match,
+                               brain=br)
+            oa.settings = mock.MagicMock(default_synapse="Synapse3")
+            expected_result = [synapse3]
+            self.assertEquals(oa.start(),
+                              expected_result,
+                              "Fail to run the default Synapse because no other synapses match the order")
+
+            # No order matching no Default Synapse
+            order_to_match = "random sentence"
+            oa = OrderAnalyser(order=order_to_match,
+                               brain=br)
+            oa.settings = mock.MagicMock()
+            expected_result = []
+            self.assertEquals(oa.start(),
+                              expected_result,
+                              "Fail to no synapse because no synapse matchs and no default defined")
+
+            # Provide synapse to run
+            order_to_match = "this is the sentence"
+            oa = OrderAnalyser(order=order_to_match,
+                               brain=br)
+            expected_result = [synapse1]
+            synapses_to_run = [synapse1]
+
+            self.assertEquals(oa.start(synapses_to_run=synapses_to_run),
+                              expected_result,
+                              "Fail to run the provided synapse to run")
+            calls = [mock.call(neuron1, {}), mock.call(neuron2, {})]
+            mock_start_neuron_method.assert_has_calls(calls=calls)
+            mock_start_neuron_method.reset_mock()
+
+            # Provide synapse and external orders
+            order_to_match = "this is an external sentence"
+            oa = OrderAnalyser(order=order_to_match,
+                               brain=br)
+            external_orders = "this is an external {{ order }}"
+            synapses_to_run = [synapse2]
+            expected_result = [synapse2]
+
+            self.assertEquals(oa.start(synapses_to_run=synapses_to_run, external_order=external_orders),
+                              expected_result,
+                              "Fail to run a provided synapse with external order")
+            calls = [mock.call(neuron3, {"order":u"sentence"}), mock.call(neuron4, {"order":u"sentence"})]
+            mock_start_neuron_method.assert_has_calls(calls=calls)
+            mock_start_neuron_method.reset_mock()
+
+    def test_start_neuron(self):
+        """
+        Testing params association and starting a Neuron
+        """
+
+        neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'})
+
+        with mock.patch("kalliope.core.NeuronLauncher.NeuronLauncher.start_neuron") as mock_start_neuron_method:
+            # Assert to the neuron is launched
+            neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
+            params = {
+                'param1':'parval1'
+            }
+            OrderAnalyser._start_neuron(neuron=neuron1,params=params)
+            mock_start_neuron_method.assert_called_with(neuron1)
+            mock_start_neuron_method.reset_mock()
+
+            # Assert the params are well passed to the neuron
+            neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2', 'args': ['arg1', 'arg2']})
+            params = {
+                'arg1':'argval1',
+                'arg2':'argval2'
+            }
+            OrderAnalyser._start_neuron(neuron=neuron2, params=params)
+            neuron2_params = Neuron(name='neurone2',
+                                    parameters={'var2': 'val2',
+                                                'args': ['arg1', 'arg2'],
+                                                'arg1':'argval1',
+                                                'arg2':'argval2'}
+                                    )
+            mock_start_neuron_method.assert_called_with(neuron2_params)
+            mock_start_neuron_method.reset_mock()
+
+            # Assert the Neuron is not started when missing args
+            neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3', 'args': ['arg3', 'arg4']})
+            params = {
+                'arg1': 'argval1',
+                'arg2': 'argval2'
+            }
+            OrderAnalyser._start_neuron(neuron=neuron3, params=params)
+            mock_start_neuron_method.assert_not_called()
+            mock_start_neuron_method.reset_mock()
+
+            # Assert no neuron is launched when waiting for args and none are given
+            neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4', 'args': ['arg5', 'arg6']})
+            params = {}
+            OrderAnalyser._start_neuron(neuron=neuron4, params=params)
+            mock_start_neuron_method.assert_not_called()
+            mock_start_neuron_method.reset_mock()
+
+    def test_spelt_order_match_brain_order_via_table(self):
+        order_to_test = "this is the order"
+        sentence_to_test = "this is the order"
+
+        # Success
+        self.assertTrue(OrderAnalyser.spelt_order_match_brain_order_via_table(order_to_test, sentence_to_test),
+                        "Fail matching order with the expected sentence")
+
+        # Failure
+        sentence_to_test = "unexpected sentence"
+        self.assertFalse(OrderAnalyser.spelt_order_match_brain_order_via_table(order_to_test, sentence_to_test),
+                         "Fail to ensure the expected sentence is not matching the order")
+
+        # Upper/lower cases
+        sentence_to_test = "THIS is THE order"
+        self.assertTrue(OrderAnalyser.spelt_order_match_brain_order_via_table(order_to_test, sentence_to_test),
+                        "Fail matching Upper/lower cases")
+
+    def test_format_sentences_to_analyse(self):
+        # First capital in sentence
+        order_to_test = "this is the order"
+        sentence_to_test = "This is the order"
+        expected_result = "this is the order", "this is the order"
+        self.assertEqual(OrderAnalyser._format_sentences_to_analyse(order_to_analyse=order_to_test,
+                                                                    user_said=sentence_to_test),
+                         expected_result,
+                         "Fails formatting the sentences with first capital in sentence")
+
+        # random uppercase in sentence
+        order_to_test = "this is the order"
+        sentence_to_test = "This IS the ordeR"
+        expected_result = "this is the order", "this is the order"
+        self.assertEqual(OrderAnalyser._format_sentences_to_analyse(order_to_analyse=order_to_test,
+                                                                    user_said=sentence_to_test),
+                         expected_result,
+                         "Fails formatting the sentences with random in sentence")
+
+        # random uppercase in order
+        order_to_test = "thiS is THE orDer"
+        sentence_to_test = "this is the order"
+        expected_result = "this is the order", "this is the order"
+        self.assertEqual(OrderAnalyser._format_sentences_to_analyse(order_to_analyse=order_to_test,
+                                                                    user_said=sentence_to_test),
+                         expected_result,
+                         "Fails formatting the sentences with random in order")
+
+        # random uppercase in both order and sentence
+        order_to_test = "thiS is THE orDer"
+        sentence_to_test = "THIS is the Order"
+        expected_result = "this is the order", "this is the order"
+        self.assertEqual(OrderAnalyser._format_sentences_to_analyse(order_to_analyse=order_to_test,
+                                                                    user_said=sentence_to_test),
+                         expected_result,
+                         "Fails formatting the sentences with random in both order and sentence")
+
+    def test_get_split_order_without_bracket(self):
+        # Success
+        order_to_test = "this is the order"
+        expected_result = ["this", "is", "the", "order"]
+        self.assertEqual(OrderAnalyser._get_split_order_without_bracket(order_to_test), expected_result,
+                         "No brackets Fails to return the expected list")
+
+        order_to_test = "this is the {{ order }}"
+        expected_result = ["this", "is", "the"]
+        self.assertEqual(OrderAnalyser._get_split_order_without_bracket(order_to_test), expected_result,
+                         "With spaced brackets Fails to return the expected list")
+
+        order_to_test = "this is the {{order }}"    # left bracket without space
+        expected_result = ["this", "is", "the"]
+        self.assertEqual(OrderAnalyser._get_split_order_without_bracket(order_to_test), expected_result,
+                         "Left brackets Fails to return the expected list")
+
+        order_to_test = "this is the {{ order}}"    # right bracket without space
+        expected_result = ["this", "is", "the"]
+        self.assertEqual(OrderAnalyser._get_split_order_without_bracket(order_to_test), expected_result,
+                         "Right brackets Fails to return the expected list")
+
+        order_to_test = "this is the {{order}}"  # bracket without space
+        expected_result = ["this", "is", "the"]
+        self.assertEqual(OrderAnalyser._get_split_order_without_bracket(order_to_test), expected_result,
+                         "No space brackets Fails to return the expected list")
+
+    def test_associate_order_params_to_values(self):
+        ##
+        # Testing the brackets position behaviour
+        ##
+
+        # Success
+        order_brain = "This is the {{ variable }}"
+        order_user = "This is the value"
+        expected_result = {'variable': 'value'}
+        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
+                         "Fail to match the order_brain {{ variable }} to the 'value'")
+
+        # Success
+        order_brain = "This is the {{variable }}"
+        order_user = "This is the value"
+        expected_result = {'variable': 'value'}
+        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
+                         "Fail to match the order_brain {{variable }} to the 'value'")
+
+        # Success
+        order_brain = "This is the {{ variable}}"
+        order_user = "This is the value"
+        expected_result = {'variable': 'value'}
+        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
+                         "Fail to match the order_brain {{ variable}} to the 'value'")
+
+        # Success
+        order_brain = "This is the {{variable}}"
+        order_user = "This is the value"
+        expected_result = {'variable': 'value'}
+        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
+                         "Fail to match the order_brain {{variable}} to the 'value'")
+
+        # Fail
+        order_brain = "This is the {variable}"
+        order_user = "This is the value"
+        expected_result = {'variable': 'value'}
+        self.assertNotEquals(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
+                             "Should not match the order_brain {variable} to the 'value'")
+
+        # Fail
+        order_brain = "This is the { variable}}"
+        order_user = "This is the value"
+        expected_result = {'variable': 'value'}
+        self.assertNotEquals(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
+                             "Should not match the order_brain { variable}} to the 'value'")
+
+        ##
+        # Testing the brackets position in the sentence
+        ##
+
+        # Success
+        order_brain = "{{ variable }} This is the"
+        order_user = "value This is the"
+        expected_result = {'variable': 'value'}
+        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
+                         "Fail to match the order_brain {{ variable }} in first position "
+                         "ins the sentence to the 'value'")
+
+        # Success
+        order_brain = "This is {{ variable }} the"
+        order_user = " This is value the"
+        expected_result = {'variable': 'value'}
+        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
+                         "Fail to match the order_brain {{ variable }} in middle position ins "
+                         "the sentence to the 'value'")
+
+        ##
+        # Testing multi variables
+        ##
+
+        # Success
+        order_brain = "This is {{ variable }} the {{ variable2 }}"
+        order_user = "This is value the value2"
+        expected_result = {'variable': 'value',
+                           'variable2': 'value2'}
+        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
+                         "Fail to match the order_brain multi variable to the multi values")
+
+        ##
+        # Testing multi words in variable
+        ##
+
+        # Success
+        order_brain = "This is the {{ variable }}"
+        order_user = "This is the value with multiple words"
+        expected_result = {'variable': 'value with multiple words'}
+        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
+                         "Fail to match the order_brain {{ variable }} to the 'value with multiple words'")
+
+        # Success
+        order_brain = "This is the {{ variable }} and  {{ variable2 }}"
+        order_user = "This is the value with multiple words and second value multiple"
+        expected_result = {'variable': 'value with multiple words',
+                           'variable2': 'second value multiple'}
+        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
+                         "Fail to match the order_brain multiple variables with multiple words as values'")
+
+        ##
+        #  Specific Behaviour
+        ##
+
+        # Upper/Lower case
+        order_brain = "This Is The {{ variable }}"
+        order_user = "ThiS is tHe VAlue"
+        expected_result = {'variable': 'VAlue'}
+        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
+                         "Fail to match the order_brain when using Upper/Lower cases")
+
+    def test_get_matching_synapse_list(self):
+        # Init
+        neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
+        neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2'})
+        neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'})
+        neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'})
+
+        signal1 = Order(sentence="this is the sentence")
+        signal2 = Order(sentence="this is the second sentence")
+        signal3 = Order(sentence="that is part of the third sentence")
+
+        synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1])
+        synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2])
+        synapse3 = Synapse(name="Synapse3", neurons=[neuron2, neuron4], signals=[signal3])
+
+        order_to_match = "this is the sentence"
+        all_synapse_list = [synapse1,
+                            synapse2,
+                            synapse3]
+
+
+
+        # Success
+        expected_result = synapse1
+        oa_tuple_list = OrderAnalyser._get_matching_synapse_list(all_synapses_list=all_synapse_list,
+                                                                   order_to_match=order_to_match)
+        self.assertEquals(oa_tuple_list[0].synapse,
+                          expected_result,
+                          "Fail matching 'the expected synapse' from the complete synapse list and the order")
+
+        # Multiple Matching synapses
+        signal2 = Order(sentence="this is the sentence")
+
+        synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2])
+        order_to_match = "this is the sentence"
+
+        all_synapse_list = [synapse1,
+                            synapse2,
+                            synapse3]
+
+        expected_result = [synapse1,
+                           synapse2]
+        oa_tuple_list = OrderAnalyser._get_matching_synapse_list(all_synapses_list=all_synapse_list,
+                                                                 order_to_match=order_to_match)
+        self.assertEquals(oa_tuple_list[0].synapse,
+                          expected_result[0],
+                          "Fail 'Multiple Matching synapses' from the complete synapse list and the order (first element)")
+        self.assertEquals(oa_tuple_list[1].synapse,
+                          expected_result[1],
+                          "Fail 'Multiple Matching synapses' from the complete synapse list and the order (second element)")
+
+        # matching no synapses
+        order_to_match = "this is not the correct word"
+
+        all_synapse_list = [synapse1,
+                            synapse2,
+                            synapse3]
+
+        expected_result = []
+
+        self.assertEquals(OrderAnalyser._get_matching_synapse_list(all_synapses_list=all_synapse_list,
+                                                                   order_to_match=order_to_match),
+                          expected_result,
+                          "Fail matching 'no synapses' from the complete synapse list and the order")
+
+        # matching synapse with all key worlds
+        # /!\ Some words in the order are matching all words in synapses signals !
+        order_to_match = "this is not the correct sentence"
+        all_synapse_list = [synapse1,
+                            synapse2,
+                            synapse3]
+
+        expected_result = [synapse1,
+                           synapse2]
+
+        oa_tuple_list = OrderAnalyser._get_matching_synapse_list(all_synapses_list=all_synapse_list,
+                                                                 order_to_match=order_to_match)
+
+        self.assertEquals(oa_tuple_list[0].synapse,
+                          expected_result[0],
+                          "Fail matching 'synapse with all key worlds' from the complete synapse list and the order (first element)")
+        self.assertEquals(oa_tuple_list[1].synapse,
+                          expected_result[1],
+                          "Fail matching 'synapse with all key worlds' from the complete synapse list and the order (second element)")
+
+    def test_get_params_from_order(self):
+
+        string_order = "this is the {{ sentence }}"
+        order_to_check = "this is the value"
+        expected_result = {'sentence': 'value'}
+
+        self.assertEquals(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check),
+                          expected_result,
+                          "Fail to retrieve 'the params' of the string_order from the order")
+
+        # Multiple match
+        string_order = "this is the {{ sentence }}"
+
+        order_to_check = "this is the value with multiple words"
+        expected_result = {'sentence': 'value with multiple words'}
+
+        self.assertEqual(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check),
+                         expected_result,
+                         "Fail to retrieve the 'multiple words params' of the string_order from the order")
+
+        # Multiple params
+        string_order = "this is the {{ sentence }} with multiple {{ params }}"
+
+        order_to_check = "this is the value with multiple words"
+        expected_result = {'sentence': 'value',
+                            'params':'words'}
+
+        self.assertEqual(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check),
+                         expected_result,
+                         "Fail to retrieve the 'multiple params' of the string_order from the order")
+
+        # Multiple params with multiple words
+        string_order = "this is the {{ sentence }} with multiple {{ params }}"
+
+        order_to_check = "this is the multiple values with multiple values as words"
+        expected_result = {'sentence': 'multiple values',
+                           'params': 'values as words'}
+
+        self.assertEqual(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check),
+                         expected_result,
+                         "Fail to retrieve the 'multiple params with multiple words' of the string_order from the order")
+
+        # params at the begining of the sentence
+        string_order = "{{ sentence }} this is the sentence"
+
+        order_to_check = "hello world this is the multiple values with multiple values as words"
+        expected_result = {'sentence': 'hello world'}
+
+        self.assertEqual(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check),
+                         expected_result,
+                         "Fail to retrieve the 'params at the begining of the sentence' of the string_order from the order")
+
+        # all of the sentence is a variable
+        string_order = "{{ sentence }}"
+
+        order_to_check = "this is the all sentence is a variable"
+        expected_result = {'sentence': 'this is the all sentence is a variable'}
+
+        self.assertEqual(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check),
+                         expected_result,
+                         "Fail to retrieve the 'all of the sentence is a variable' of the string_order from the order")
+
+    def test_get_default_synapse_from_sysnapses_list(self):
+        # Init
+        neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
+        neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2'})
+        neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'})
+        neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'})
+
+        signal1 = Order(sentence="this is the sentence")
+        signal2 = Order(sentence="this is the second sentence")
+        signal3 = Order(sentence="that is part of the third sentence")
+
+        synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1])
+        synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2])
+        synapse3 = Synapse(name="Synapse3", neurons=[neuron2, neuron4], signals=[signal3])
+
+        default_synapse_name = "Synapse2"
+        all_synapse_list = [synapse1,
+                            synapse2,
+                            synapse3]
+        expected_result = synapse2
+
+        # Assert equals
+        self.assertEquals(OrderAnalyser._get_default_synapse_from_sysnapses_list(all_synapses_list=all_synapse_list,
+                                                                                 default_synapse_name=default_synapse_name),
+                          expected_result,
+                          "Fail to match the expected default Synapse")
+
+    def test_find_synapse_to_run(self):
+        """
+        Test to find the good synapse to run
+        Scenarii:
+            - 1/ Find the synapse
+            - 2/ No synpase found, no default synapse
+            - 3/ No synapse found, run the default synapse
+        """
+        # Init
+        neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
+        neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2'})
+        neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'})
+        neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'})
+
+        signal1 = Order(sentence="this is the sentence")
+        signal2 = Order(sentence="this is the second sentence")
+        signal3 = Order(sentence="that is part of the third sentence")
+
+        synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1])
+        synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2])
+        synapse3 = Synapse(name="Synapse3", neurons=[neuron2, neuron4], signals=[signal3])
+
+        all_synapse_list = [synapse1,
+                            synapse2,
+                            synapse3]
+
+        br = Brain(synapses=all_synapse_list)
+        st = Settings()
+        # 1/ Find synapse
+        order = "this is the sentence"
+        expected_result = synapse1
+        oa_tuple_list = OrderAnalyser._find_synapse_to_run(brain=br,settings=st, order=order)
+        self.assertEquals(oa_tuple_list[0].synapse,
+                          expected_result,
+                          "Fail to run the proper synapse matching the order")
+
+        expected_result = signal1.sentence
+        self.assertEquals(oa_tuple_list[0].order,
+                          expected_result,
+                          "Fail to run the proper synapse matching the order")
+
+        # 2/ No Default synapse
+        order = "No default synapse"
+        expected_result = []
+        self.assertEquals(OrderAnalyser._find_synapse_to_run(brain=br,settings=st, order=order),
+                          expected_result,
+                          "Fail to run no synapse, when no default is defined")
+
+        # 3/ Default synapse
+        st = Settings(default_synapse="Synapse2")
+        order = "default synapse"
+        expected_result = synapse2
+        oa_tuple_list = OrderAnalyser._find_synapse_to_run(brain=br, settings=st, order=order)
+        self.assertEquals(oa_tuple_list[0].synapse,
+                          expected_result,
+                          "Fail to run the default synapse")
+
+
+if __name__ == '__main__':
+    unittest.main()

+ 7 - 0
Tests/brains/brain_test.yml

@@ -15,5 +15,12 @@
           message:
             - "test message"
 
+  - name: "testint"
+    signals:
+      - order: "order_for_int"
+    neurons:
+      - sleep:
+          seconds: 60
+
   - includes:
     - included_brain_test.yml

+ 63 - 0
Tests/brains/lifo_buffer_test_brain.yml

@@ -0,0 +1,63 @@
+- name: "synapse1"
+  signals:
+    - order: "enter in synapse 1"
+  neurons:
+    - say:
+        message: "question in synapse 1"
+    - neurotransmitter:
+        from_answer_link:
+          - synapse: "synapse2"
+            answers:
+              - "answer synapse1"
+          - synapse: "synapse3"
+            answers:
+              - "other answer synapse1"
+        default: "synapse4"
+    - say:
+        message: "last neuron in synapse 1"
+
+- name: "synapse2"
+  signals:
+    - order: "synapse2"
+  neurons:
+    - say:
+        message: "enter synapse 2"
+    - neurotransmitter:
+        from_answer_link:
+          - synapse: "synapse5"
+            answers:
+              - "synapse5"
+        default: "synapse4"
+
+- name: "synapse3"
+  signals:
+    - order: "synapse3"
+  neurons:
+    - say:
+        message: "I enter in synapse 3"
+
+- name: "synapse4"
+  signals:
+    - order: "synapse4"
+  neurons:
+    - say:
+        message: "not understood"
+
+
+- name: "synapse5"
+  signals:
+    - order: "synapse5"
+  neurons:
+    - say:
+        message: "execution of synapse 5"
+
+- name: "synapse6"
+  signals:
+    - order: "synapse6"
+  neurons:
+    - neurotransmitter:
+        from_answer_link:
+          - synapse: "synapse5"
+            answers:
+              - "synapse 6 answer"
+        default: "synapse4"

+ 17 - 0
Tests/settings/settings_test.yml

@@ -46,6 +46,23 @@ text_to_speech:
       voice: "Agnes"
       cache: True
 
+# ---------------------------
+# players
+# ---------------------------
+# This is the sound player that will be used by default
+default_player: "mplayer"
+
+# players configuration
+# Available engine are:
+# - mplayer
+# - pyalsaaudio
+# - pyaudioplayer
+# - sounddeviceplayer
+players:
+  - mplayer: {}
+  - pyalsaaudio:
+     device: "default"
+
 # ---------------------------
 # Wake up answers
 # ---------------------------

+ 63 - 23
Tests/test_brain_loader.py

@@ -1,3 +1,4 @@
+# coding: utf8
 import os
 import unittest
 
@@ -15,6 +16,8 @@ from kalliope.core.Models.Settings import Settings
 class TestBrainLoader(unittest.TestCase):
 
     def setUp(self):
+        # be sure the brain haven't been instantiated before
+        Singleton._instances = dict()
         if "/Tests" in os.getcwd():
             self.brain_to_test = os.getcwd() + os.sep + "brains/brain_test.yml"
         else:
@@ -27,6 +30,9 @@ class TestBrainLoader(unittest.TestCase):
             {'signals': [{'order': 'test_order_2'}],
              'neurons': [{'say': {'message': ['test message']}}],
              'name': 'test2'},
+            {'signals': [{'order': 'order_for_int'}],
+             'neurons': [{'sleep': {'seconds': 60}}],
+             'name': 'testint'},
             {'includes': ['included_brain_test.yml']},
             {'signals': [{'order': 'test_order_3'}],
              'neurons': [{'say': {'message': ['test message']}}],
@@ -34,7 +40,7 @@ class TestBrainLoader(unittest.TestCase):
         ]
 
     def tearDown(self):
-        Singleton._instances = {}
+        Singleton._instances = dict()
 
     def test_get_yaml_config(self):
         """
@@ -49,15 +55,18 @@ class TestBrainLoader(unittest.TestCase):
         """
 
         neuron = Neuron(name='say', parameters={'message': ['test message']})
+        neuron2 = Neuron(name='sleep', parameters={'seconds': 60})
 
         signal1 = Order(sentence="test_order")
         signal2 = Order(sentence="test_order_2")
         signal3 = Order(sentence="test_order_3")
+        signal4 = Order(sentence="order_for_int")
 
         synapse1 = Synapse(name="test", neurons=[neuron], signals=[signal1])
         synapse2 = Synapse(name="test2", neurons=[neuron], signals=[signal2])
         synapse3 = Synapse(name="test3", neurons=[neuron], signals=[signal3])
-        synapses = [synapse1, synapse2, synapse3]
+        synapse4 = Synapse(name="testint", neurons=[neuron2], signals=[signal4])
+        synapses = [synapse1, synapse2, synapse4, synapse3]
 
         brain = Brain()
         brain.synapses = synapses
@@ -73,6 +82,7 @@ class TestBrainLoader(unittest.TestCase):
         scenarii:
             - 1/ get a simple neuron from the brainloader
             - 2/ get a neuron with global variables as parameters
+            - 3/ get a neuron with int as parameters
         """
         # 1/ get a simple neuron from the brainloader
         st = Settings()
@@ -102,6 +112,18 @@ class TestBrainLoader(unittest.TestCase):
 
         self.assertEqual([neuron], neurons_from_brain_loader)
 
+        # 3/ get a neuron with int as parameters
+        st = Settings()
+        neuron_list = [{'sleep': {'seconds': 60}}]
+
+        neuron = Neuron(name='sleep', parameters={'seconds': 60})
+
+        bl = BrainLoader(file_path=self.brain_to_test)
+        neurons_from_brain_loader = bl._get_neurons(neuron_list,
+                                                    settings=st)
+
+        self.assertEqual([neuron], neurons_from_brain_loader)
+
     def test_get_signals(self):
         signals = [{'order': 'test_order'}]
 
@@ -161,10 +183,10 @@ class TestBrainLoader(unittest.TestCase):
             'var1': 'test'
         }
 
-        self.assertEquals(BrainLoader._replace_global_variables(parameter=parameters,
+        self.assertEqual(BrainLoader._replace_global_variables(parameter=parameters,
                                                                 settings=st),
-                          expected_parameters,
-                          "Fail to assign a single global variable to parameters")
+                         expected_parameters,
+                         "Fail to assign a single global variable to parameters")
 
         # 2/ global variable with string after
         parameters = {
@@ -180,10 +202,10 @@ class TestBrainLoader(unittest.TestCase):
             'var1': 'test Sispheor'
         }
 
-        self.assertEquals(BrainLoader._replace_global_variables(parameter=parameters,
+        self.assertEqual(BrainLoader._replace_global_variables(parameter=parameters,
                                                                 settings=st),
-                          expected_parameters,
-                          "Fail to assign a global variable with string after to parameters")
+                         expected_parameters,
+                         "Fail to assign a global variable with string after to parameters")
 
         # 3/ global variable with int after
         parameters = {
@@ -199,10 +221,10 @@ class TestBrainLoader(unittest.TestCase):
             'var1': '600'
         }
 
-        self.assertEquals(BrainLoader._replace_global_variables(parameter=parameters,
+        self.assertEqual(BrainLoader._replace_global_variables(parameter=parameters,
                                                                 settings=st),
-                          expected_parameters,
-                          "Fail to assign global variable with int after to parameters")
+                         expected_parameters,
+                         "Fail to assign global variable with int after to parameters")
 
         # 4/ multiple global variables
         parameters = {
@@ -218,10 +240,10 @@ class TestBrainLoader(unittest.TestCase):
             'var1': 'hello LaMonf'
         }
 
-        self.assertEquals(BrainLoader._replace_global_variables(parameter=parameters,
+        self.assertEqual(BrainLoader._replace_global_variables(parameter=parameters,
                                                                 settings=st),
-                          expected_parameters,
-                          "Fail to assign multiple global variables to parameters")
+                         expected_parameters,
+                         "Fail to assign multiple global variables to parameters")
 
         # 5/ parameter value is a list
         parameters = {
@@ -237,10 +259,10 @@ class TestBrainLoader(unittest.TestCase):
             'var1': '[hello LaMonf, bonjour LaMonf]'
         }
 
-        self.assertEquals(BrainLoader._replace_global_variables(parameter=parameters,
+        self.assertEqual(BrainLoader._replace_global_variables(parameter=parameters,
                                                                 settings=st),
-                          expected_parameters,
-                          "Fail to assign a single global when parameter value is a list to neuron")
+                         expected_parameters,
+                         "Fail to assign a single global when parameter value is a list to neuron")
 
         # 6/ parameter is a dict
         parameters = {'from_answer_link': [{'synapse': 'synapse2', 'answers': ['absolument', '{{ name }}']},
@@ -257,10 +279,10 @@ class TestBrainLoader(unittest.TestCase):
                 {'synapse': 'synapse3', 'answers': ['nico']}], 'default': 'synapse4'
         }
 
-        self.assertEquals(BrainLoader._replace_global_variables(parameter=parameters,
+        self.assertEqual(BrainLoader._replace_global_variables(parameter=parameters,
                                                                 settings=st),
-                          expected_parameters,
-                          "Fail to assign a single global when parameter value is a list to neuron")
+                         expected_parameters,
+                         "Fail to assign a single global when parameter value is a list to neuron")
 
     def test_get_global_variable(self):
         """
@@ -270,15 +292,33 @@ class TestBrainLoader(unittest.TestCase):
         variables = {
             "name": "LaMonf",
             "name2": "kalliope",
+            "name3": u"kalliopé",
+            "name4": 1
         }
         st = Settings(variables=variables)
 
         expected_result = "i am kalliope"
 
-        self.assertEquals(BrainLoader._get_global_variable(sentence=sentence,
+        self.assertEqual(BrainLoader._get_global_variable(sentence=sentence,
+                                                           settings=st),
+                         expected_result)
+
+        # test with accent
+        sentence = "i am {{name3}}"
+        expected_result = u"i am kalliopé"
+
+        self.assertEqual(BrainLoader._get_global_variable(sentence=sentence,
                                                            settings=st),
-                          expected_result,
-                          "Fail to get the global variable from the sentence")
+                         expected_result)
+
+        # test with int
+        sentence = "i am {{name4}}"
+        expected_result = "i am 1"
+
+        self.assertEqual(BrainLoader._get_global_variable(sentence=sentence,
+                                                           settings=st),
+                         expected_result)
+
 
 if __name__ == '__main__':
     unittest.main()

+ 2 - 2
Tests/test_dynamic_loading.py

@@ -15,7 +15,7 @@ class TestDynamicLoading(unittest.TestCase):
     """
 
     def setUp(self):
-        # get current script directory path. We are in /an/unknown/path/kalliope/core/Tests
+        # 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)
@@ -98,7 +98,7 @@ class TestDynamicLoading(unittest.TestCase):
         # we keep only package. Because we have _init_.py or other stuff in what listdir returned
         packages_in_folder = list()
         for el in el_folder:
-            if os.path.isdir(folder + os.sep + el):
+            if os.path.isdir(folder + os.sep + el) and not '__pycache__' in el:
                 packages_in_folder.append(el)
         return packages_in_folder
 

+ 14 - 4
Tests/test_file_manager.py

@@ -12,6 +12,10 @@ class TestFileManager(unittest.TestCase):
     def setUp(self):
         pass
 
+    def create_file_manager(self):
+        file_manager = FileManager()
+        self.assertIsInstance(FileManager, file_manager)
+
     def test_create_directory(self):
         """
         Test to create a new directory.
@@ -55,6 +59,12 @@ class TestFileManager(unittest.TestCase):
         if os.path.exists(file_path):
             os.remove(file_path)
 
+        # run into IOError by trying to write something in root
+        dir_path = "/root/"
+        file_name = "test_FileManager_writeInFile"
+        file_path = os.path.join(dir_path, file_name)
+        self.assertFalse(FileManager.write_in_file(file_path=file_path, content=in_file_text))
+
     def test_file_is_empty(self):
         """
         Test that the file is empty
@@ -71,7 +81,7 @@ class TestFileManager(unittest.TestCase):
 
         # Test FileManager.file_is_empty
         with open(file_path, "wb") as file_open:
-            file_open.write("")
+            file_open.write(b"")
             file_open.close()
         self.assertTrue(FileManager.file_is_empty(file_path=file_path),
                         "Fail matching to verify that file is empty ")
@@ -97,7 +107,7 @@ class TestFileManager(unittest.TestCase):
         # Test to remove the file
         # FileManager.remove_file
         with open(file_path, "wb") as file_open:
-            file_open.write("")
+            file_open.write(b"")
             file_open.close()
         FileManager.remove_file(file_path=file_path)
         self.assertFalse(os.path.exists(file_path),
@@ -147,7 +157,7 @@ class TestFileManager(unittest.TestCase):
 
         # Test the file exist and creatable : return True
         with open(file_path, "wb") as file_open:
-            file_open.write("[Kalliope] Test Running the test_is_path_exists_or_creatable method")
+            file_open.write(b"[Kalliope] Test Running the test_is_path_exists_or_creatable method")
             file_open.close()
         self.assertTrue(FileManager.is_path_exists_or_creatable(file_path),
                         "Fail to assert the file exist ")
@@ -168,4 +178,4 @@ class TestFileManager(unittest.TestCase):
                          "Fail to assert not accessing this path ")
 
 if __name__ == '__main__':
-    unittest.main()
+    unittest.main()

+ 111 - 0
Tests/test_init.py

@@ -0,0 +1,111 @@
+import unittest
+
+import logging
+
+import sys
+
+import mock
+
+from kalliope import parse_args, configure_logging, main
+
+
+class TestInit(unittest.TestCase):
+
+    def test_parse_args(self):
+        # start option
+        parser = parse_args(['value'])
+        self.assertEqual(parser.action, "value")
+
+        # no option
+        with self.assertRaises(SystemExit):
+            parse_args([])
+
+        parser = parse_args(['start', '--run-synapse', 'run_synapse'])
+        self.assertEqual(parser.run_synapse, 'run_synapse')
+
+        parser = parse_args(['start', '--run-order', 'my order'])
+        self.assertEqual(parser.run_order, 'my order')
+
+    def test_configure_logging(self):
+        logger = logging.getLogger("kalliope")
+        # Level 10 = DEBUG
+        configure_logging(debug=True)
+        self.assertEqual(logger.getEffectiveLevel(), 10)
+        logger.propagate = False
+
+        # Level 20 = INFO
+        configure_logging(debug=False)
+        self.assertEqual(logger.getEffectiveLevel(), 20)
+
+        # disable after testing
+        logger.disabled = True
+
+    def test_main(self):
+        # test start kalliope
+        sys.argv = ['kalliope.py', 'start']
+        with mock.patch('kalliope.core.MainController.__init__') as mock_maincontroller:
+            mock_maincontroller.return_value = None
+            main()
+            mock_maincontroller.assert_called()
+
+        # test start gui
+        sys.argv = ['kalliope.py', 'gui']
+        with mock.patch('kalliope.core.ShellGui.__init__') as mock_shell_gui:
+            mock_shell_gui.return_value = None
+            main()
+            mock_shell_gui.assert_called()
+
+        # test run_synapse
+        sys.argv = ['kalliope.py', 'start', '--run-synapse', 'synapse_name']
+        with mock.patch('kalliope.core.SynapseLauncher.start_synapse_by_name') as mock_synapse_launcher:
+            mock_synapse_launcher.return_value = None
+            main()
+            mock_synapse_launcher.assert_called()
+
+        # test run order
+        sys.argv = ['kalliope.py', 'start', '--run-order', 'my order']
+        with mock.patch('kalliope.core.SynapseLauncher.run_matching_synapse_from_order') as mock_synapse_launcher:
+            mock_synapse_launcher.return_value = None
+            main()
+            mock_synapse_launcher.assert_called()
+
+        # action doesn't exist
+        sys.argv = ['kalliope.py', 'non_existing_action']
+        with self.assertRaises(SystemExit):
+            main()
+
+        # install
+        sys.argv = ['kalliope.py', 'install', '--git-url', 'https://my_url']
+        with mock.patch('kalliope.core.ResourcesManager.install') as mock_resource_manager:
+            mock_resource_manager.return_value = None
+            main()
+            mock_resource_manager.assert_called()
+
+        # install, no URL
+        sys.argv = ['kalliope.py', 'install']
+        with self.assertRaises(SystemExit):
+            main()
+
+        sys.argv = ['kalliope.py', 'install', '--git-url']
+        with self.assertRaises(SystemExit):
+            main()
+
+        # uninstall
+        sys.argv = ['kalliope.py', 'uninstall', '--neuron-name', 'neuron_name']
+        with mock.patch('kalliope.core.ResourcesManager.uninstall') as mock_resource_manager:
+            mock_resource_manager.return_value = None
+            main()
+            mock_resource_manager.assert_called()
+
+        sys.argv = ['kalliope.py', 'uninstall']
+        with self.assertRaises(SystemExit):
+            main()
+
+
+if __name__ == '__main__':
+    unittest.main()
+
+    # suite = unittest.TestSuite()
+    # suite.addTest(TestInit("test_main"))
+    # runner = unittest.TextTestRunner()
+    # runner.run(suite)

+ 0 - 140
Tests/test_launchers.py

@@ -1,140 +0,0 @@
-import unittest
-import mock
-
-from kalliope.core.Models.Resources import Resources
-from kalliope.core.NeuronLauncher import NeuronLauncher
-from kalliope.core.SynapseLauncher import SynapseLauncher, SynapseNameNotFound
-from kalliope.core.TriggerLauncher import TriggerLauncher
-from kalliope.core.ConfigurationManager import SettingLoader
-from kalliope.core.Models.Settings import Settings
-
-from kalliope.core.Models.Trigger import Trigger
-from kalliope.core.Models.Neuron import Neuron
-from kalliope.core.Models.Order import Order
-from kalliope.core.Models.Brain import Brain
-from kalliope.core.Models.Synapse import Synapse
-
-
-class TestLaunchers(unittest.TestCase):
-    """
-    Class to test Launchers Classes (TriggerLauncher, SynapseLauncher, NeuronLauncher) and methods
-    """
-
-    def setUp(self):
-        pass
-
-    ####
-    # Trigger Launcher
-    def test_get_trigger(self):
-        """
-        Test the Trigger Launcher trying to run the trigger
-        """
-        trigger = Trigger("Trigger", {})
-        with mock.patch("kalliope.core.Utils.get_dynamic_class_instantiation") as mock_get_class_instantiation:
-            TriggerLauncher.get_trigger(trigger=trigger,
-                                        callback=None)
-
-            mock_get_class_instantiation.assert_called_once_with(package_name="trigger",
-                                                                 module_name=trigger.name,
-                                                                 parameters=trigger.parameters)
-            mock_get_class_instantiation.reset_mock()
-
-    ####
-    # Synapse Launcher
-    def test_start_synapse(self):
-        """
-        Test the Synapse launcher trying to start synapse
-        """
-        # Init
-        neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
-        neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2'})
-        neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'})
-        neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'})
-
-        signal1 = Order(sentence="this is the sentence")
-        signal2 = Order(sentence="this is the second sentence")
-        signal3 = Order(sentence="that is part of the third sentence")
-
-        synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1])
-        synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2])
-        synapse3 = Synapse(name="Synapse3", neurons=[neuron2, neuron4], signals=[signal3])
-
-        all_synapse_list = [synapse1,
-                            synapse2,
-                            synapse3]
-
-        br = Brain(synapses=all_synapse_list)
-
-        sl = SettingLoader()
-        r = Resources(neuron_folder="/var/tmp/test/resources")
-        sl.settings.resources = r
-        with mock.patch("kalliope.core.Utils.get_dynamic_class_instantiation") as mock_get_class_instantiation:
-            # Success
-            SynapseLauncher.start_synapse("Synapse1", brain=br)
-
-            calls = [mock.call(package_name="neurons",
-                               module_name=neuron1.name,
-                               parameters=neuron1.parameters,
-                               resources_dir='/var/tmp/test/resources'),
-                     mock.call(package_name="neurons",
-                               module_name=neuron2.name,
-                               parameters=neuron2.parameters,
-                               resources_dir='/var/tmp/test/resources')]
-            mock_get_class_instantiation.assert_has_calls(calls=calls)
-            mock_get_class_instantiation.reset_mock()
-
-            # Fail
-            with self.assertRaises(SynapseNameNotFound):
-                SynapseLauncher.start_synapse("Synapse4", brain=br)
-
-    def test_run_synapse(self):
-        """
-        Test to run a Synapse
-        """
-        neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
-        neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2'})
-        signal1 = Order(sentence="this is the sentence")
-        synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1])
-        synapse_empty = Synapse(name="Synapse_empty", neurons=[], signals=[signal1])
-        sl = SettingLoader()
-        resources = Resources(neuron_folder='/var/tmp/test/resources')
-        sl.settings.resources = resources
-        with mock.patch("kalliope.core.Utils.get_dynamic_class_instantiation") as mock_get_class_instantiation:
-            SynapseLauncher._run_synapse(synapse=synapse1)
-
-            calls = [mock.call(package_name="neurons",
-                               module_name=neuron1.name,
-                               parameters=neuron1.parameters,
-                               resources_dir="/var/tmp/test/resources"),
-                     mock.call(package_name="neurons",
-                               module_name=neuron2.name,
-                               parameters=neuron2.parameters,
-                               resources_dir="/var/tmp/test/resources")]
-            mock_get_class_instantiation.assert_has_calls(calls=calls)
-            mock_get_class_instantiation.reset_mock()
-
-            # Do not any Neurons
-            SynapseLauncher._run_synapse(synapse=synapse_empty)
-            mock_get_class_instantiation.assert_not_called()
-            mock_get_class_instantiation.reset_mock()
-
-    ####
-    # Neurons Launcher
-    def test_start_neuron(self):
-        """
-        Test the Neuron Launcher trying to start a Neuron
-        """
-        neuron = Neuron(name='neurone1', parameters={'var1': 'val1'})
-        sl = SettingLoader()
-        resources = Resources(neuron_folder='/var/tmp/test/resources')
-        sl.settings.resources = resources
-        with mock.patch("kalliope.core.Utils.get_dynamic_class_instantiation") as mock_get_class_instantiation:
-            NeuronLauncher.start_neuron(neuron=neuron)
-
-            mock_get_class_instantiation.assert_called_once_with(package_name="neurons",
-                                                                 module_name=neuron.name,
-                                                                 parameters=neuron.parameters,
-                                                                 resources_dir=sl.settings.resources.neuron_folder)
-            mock_get_class_instantiation.reset_mock()
-
-

+ 358 - 0
Tests/test_lifo_buffer.py

@@ -0,0 +1,358 @@
+import os
+import unittest
+
+import mock
+
+from kalliope.core import LIFOBuffer
+from kalliope.core.ConfigurationManager import BrainLoader
+from kalliope.core.LIFOBuffer import Serialize, SynapseListAddedToLIFO
+
+from kalliope.core.Models import Singleton
+from kalliope.core.Models.MatchedSynapse import MatchedSynapse
+
+
+class TestLIFOBuffer(unittest.TestCase):
+
+    def setUp(self):
+        # be sure the brain haven't been instantiated before
+        Singleton._instances = dict()
+
+        if "/Tests" in os.getcwd():
+            self.brain_to_test = os.getcwd() + os.sep + "brains/lifo_buffer_test_brain.yml"
+        else:
+            self.brain_to_test = os.getcwd() + os.sep + "Tests/brains/lifo_buffer_test_brain.yml"
+
+        BrainLoader(file_path=self.brain_to_test)
+        # create a new lifo buffer
+        self.lifo_buffer = LIFOBuffer()
+        self.lifo_buffer.clean()
+
+    def test_execute(self):
+        """
+        In this test the brain contains a neurotransmitter
+        """
+        # --------------------------------------
+        # Test 1. The user answers correctly to all neurotransmitter
+        # --------------------------------------
+
+        # we suppose that the first synapse has matched the first synapse
+        synapse = BrainLoader().brain.get_synapse_by_name("synapse1")
+        order = "enter in synapse 1"
+        matched_synapse = MatchedSynapse(matched_synapse=synapse,
+                                         user_order=order,
+                                         matched_order=order)
+        list_matched_synapse = list()
+        list_matched_synapse.append(matched_synapse)
+        self.lifo_buffer.add_synapse_list_to_lifo(list_matched_synapse)
+        self.lifo_buffer.api_response.user_order = order
+
+        with mock.patch("kalliope.core.TTS.TTSModule.generate_and_play"):
+
+            response = self.lifo_buffer.execute(is_api_call=True)
+
+            expected_result = {
+                'status': 'waiting_for_answer',
+                'matched_synapses': [
+                    {
+                        'matched_order': 'enter in synapse 1',
+                        'neuron_module_list':
+                            [
+                                {
+                                    'neuron_name': 'Say',
+                                    'generated_message': 'question in synapse 1'
+                                }
+                            ],
+                        'synapse_name': 'synapse1'
+                    }
+                ],
+                'user_order': 'enter in synapse 1'
+            }
+
+            self.assertEqual(response, expected_result)
+
+            # give an answer
+            answer = "answer synapse1"
+            response = self.lifo_buffer.execute(answer=answer,
+                                                is_api_call=True)
+            expected_result = {
+                'status': 'waiting_for_answer',
+                'matched_synapses': [
+                    {
+                        'matched_order': 'enter in synapse 1',
+                        'neuron_module_list': [
+                            {
+                                'neuron_name': 'Say',
+                                 'generated_message': 'question in synapse 1'
+                            },
+                            {
+                                'neuron_name': 'Neurotransmitter',
+                                'generated_message': None
+                            }
+                        ],
+                        'synapse_name': 'synapse1'
+                    },
+                    {
+                        'matched_order': 'answer synapse1',
+                        'neuron_module_list': [
+                            {
+                                'neuron_name': 'Say',
+                                'generated_message': 'enter synapse 2'
+                            }
+                        ],
+                    'synapse_name': 'synapse2'
+                    }
+                ],
+                'user_order': None
+            }
+            self.assertEqual(response, expected_result)
+
+            # give the last answer
+            answer = "synapse5"
+            response = self.lifo_buffer.execute(answer=answer,
+                                                is_api_call=True)
+            expected_result = {
+                'status': 'complete',
+                'matched_synapses': [
+                    {
+                        'matched_order': 'answer synapse1',
+                        'neuron_module_list': [
+                            {
+                                'neuron_name': 'Say',
+                                'generated_message': 'enter synapse 2'
+                            },
+                            {
+                                'neuron_name': 'Neurotransmitter',
+                                'generated_message': None
+                            }
+                        ],
+                        'synapse_name': 'synapse2'
+                    },
+                    {
+                        'matched_order': 'synapse5',
+                        'neuron_module_list': [
+                            {
+                                'neuron_name': 'Say',
+                                'generated_message': 'execution of synapse 5'
+                            }
+                        ],
+                        'synapse_name': 'synapse5'
+                    },
+                    {
+                        'matched_order': 'enter in synapse 1',
+                        'neuron_module_list':[
+                            {
+                                'neuron_name': 'Say',
+                                'generated_message': 'question in synapse 1'
+                            },
+                            {
+                                'neuron_name': 'Neurotransmitter',
+                                'generated_message': None
+                            },
+                            {
+                                'neuron_name': 'Say',
+                                'generated_message': 'last neuron in synapse 1'
+                            }
+                        ],
+                        'synapse_name': 'synapse1'
+                    }
+                ],
+                'user_order': None
+            }
+
+            self.assertEqual(response, expected_result)
+
+        # --------------------------------------
+        # Test 2. The user doesn't answered correctly to the first neurotransmitter
+        # --------------------------------------
+
+        # we suppose that the first synapse has matched the first synapse
+        synapse = BrainLoader().brain.get_synapse_by_name("synapse1")
+        order = "enter in synapse 1"
+        matched_synapse = MatchedSynapse(matched_synapse=synapse,
+                                         user_order=order,
+                                         matched_order=order)
+        list_matched_synapse = list()
+        list_matched_synapse.append(matched_synapse)
+        self.lifo_buffer.add_synapse_list_to_lifo(list_matched_synapse)
+        self.lifo_buffer.api_response.user_order = order
+
+        with mock.patch("kalliope.core.TTS.TTSModule.generate_and_play"):
+            # fist call to enter in the neurotransmitter
+            self.lifo_buffer.execute(is_api_call=True)
+
+            wrong_answer = "wrong answer"
+            response = self.lifo_buffer.execute(answer=wrong_answer, is_api_call=True)
+
+            expected_result = {
+                'status': 'complete',
+                'matched_synapses': [
+                    {
+                        'matched_order': 'enter in synapse 1',
+                        'neuron_module_list': [
+                            {
+                                'neuron_name': 'Say',
+                                'generated_message': 'question in synapse 1'
+                            },
+                            {
+                                'neuron_name': 'Neurotransmitter',
+                                'generated_message': None
+                            },
+                            {
+                                'neuron_name': 'Say',
+                                'generated_message': 'last neuron in synapse 1'
+                            }
+                        ],
+                        'synapse_name': 'synapse1'
+                    },
+                    {
+                        'matched_order': None,
+                        'neuron_module_list': [
+                            {
+                                'neuron_name': 'Say',
+                                'generated_message': 'not understood'
+                            }
+                        ],
+                        'synapse_name': 'synapse4'
+                    }
+                ],
+                'user_order': None
+            }
+
+            self.assertEqual(response, expected_result)
+
+        # --------------------------------------
+        # Test 3. No synapse matched, we still execute the list
+        # --------------------------------------
+        list_matched_synapse = list()
+        self.lifo_buffer.add_synapse_list_to_lifo(list_matched_synapse)
+        self.lifo_buffer.api_response.user_order = "this is an order"
+
+        with mock.patch("kalliope.core.TTS.TTSModule.generate_and_play"):
+            # fist call to enter in the neurotransmitter
+            response = self.lifo_buffer.execute(is_api_call=True)
+
+            expected_result = {
+                'status': None,
+                'matched_synapses': [],
+                'user_order': 'this is an order'
+            }
+
+            self.assertEqual(response, expected_result)
+
+    def test_add_synapse_list_to_lifo(self):
+        """
+        Testing to add a synapse to the lifo
+        """
+        synapse = BrainLoader().brain.get_synapse_by_name("synapse1")
+        order = "enter in synapse 1"
+        matched_synapse = MatchedSynapse(matched_synapse=synapse,
+                                         user_order=order,
+                                         matched_order=order)
+        list_matched_synapse = list()
+        list_matched_synapse.append(matched_synapse)
+        self.lifo_buffer.add_synapse_list_to_lifo(list_matched_synapse)
+
+        self.assertEqual(self.lifo_buffer.lifo_list, [list_matched_synapse])
+
+    def test_clean(self):
+        """
+        Test the Cleaning of the matched synapses list
+        """
+        synapse = BrainLoader().brain.get_synapse_by_name("synapse1")
+        order = "enter in synapse 1"
+        matched_synapse = MatchedSynapse(matched_synapse=synapse,
+                                         user_order=order,
+                                         matched_order=order)
+        list_matched_synapse = list()
+        list_matched_synapse.append(matched_synapse)
+        self.lifo_buffer.add_synapse_list_to_lifo(list_matched_synapse)
+
+        self.lifo_buffer.clean()
+        self.assertEqual(0, len(self.lifo_buffer.lifo_list))
+
+    def test_return_serialized_api_response(self):
+        """
+        Test the serialization
+        """
+        self.lifo_buffer.clean()
+        self.lifo_buffer.execute(is_api_call=True)
+        expected_result = {'status': None, 'matched_synapses': [], 'user_order': None}
+        response = self.lifo_buffer._return_serialized_api_response()
+        self.assertEqual(expected_result, response)
+
+    def test_process_synapse_list(self):
+        """
+        Testing the neuron list from a synapse
+        """
+        synapse = BrainLoader().brain.get_synapse_by_name("synapse1")
+        order = "enter in synapse 1"
+        matched_synapse = MatchedSynapse(matched_synapse=synapse,
+                                         user_order=order,
+                                         matched_order=order)
+        list_matched_synapse = list()
+        list_matched_synapse.append(matched_synapse)
+
+        with mock.patch("kalliope.core.LIFOBuffer._process_neuron_list"):
+            self.lifo_buffer._process_synapse_list(list_matched_synapse)
+            expected_response = {
+                'status': None,
+                'matched_synapses': [
+                    {
+                        'matched_order': 'enter in synapse 1',
+                        'neuron_module_list': [],
+                        'synapse_name': 'synapse1'
+                    }
+                ],
+                'user_order': None
+            }
+            self.assertEqual(expected_response, self.lifo_buffer.api_response.serialize())
+            self.assertEqual(0, len(self.lifo_buffer.lifo_list))
+
+    def test_process_neuron_list(self):
+        # Test with a neuron that doesn't wait for an answer
+        synapse = BrainLoader().brain.get_synapse_by_name("synapse5")
+        order = "synapse5"
+        matched_synapse = MatchedSynapse(matched_synapse=synapse,
+                                         user_order=order,
+                                         matched_order=order)
+
+        with mock.patch("kalliope.core.TTS.TTSModule.generate_and_play"):
+            self.lifo_buffer.set_api_call(True)
+            self.lifo_buffer._process_neuron_list(matched_synapse=matched_synapse)
+            self.assertEqual("complete", self.lifo_buffer.api_response.status)
+
+        # test with neuron that wait for an answer
+        self.lifo_buffer.clean()
+        synapse = BrainLoader().brain.get_synapse_by_name("synapse6")
+        order = "synapse6"
+        matched_synapse = MatchedSynapse(matched_synapse=synapse,
+                                         user_order=order,
+                                         matched_order=order)
+
+        self.lifo_buffer.set_api_call(True)
+        with mock.patch("kalliope.core.TTS.TTSModule.generate_and_play"):
+            with self.assertRaises(Serialize):
+                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()
+        synapse = BrainLoader().brain.get_synapse_by_name("synapse6")
+        order = "synapse6"
+        matched_synapse = MatchedSynapse(matched_synapse=synapse,
+                                         user_order=order,
+                                         matched_order=order)
+
+        self.lifo_buffer.set_api_call(True)
+        self.lifo_buffer.set_answer("synapse 6 answer")
+        with mock.patch("kalliope.core.TTS.TTSModule.generate_and_play"):
+            with self.assertRaises(SynapseListAddedToLIFO):
+                self.lifo_buffer._process_neuron_list(matched_synapse=matched_synapse)
+
+
+if __name__ == '__main__':
+    unittest.main()
+
+    # suite = unittest.TestSuite()
+    # suite.addTest(TestLIFOBuffer("test_process_neuron_list"))
+    # runner = unittest.TextTestRunner()
+    # runner.run(suite)

+ 473 - 0
Tests/test_models.py

@@ -0,0 +1,473 @@
+import unittest
+import ast
+import mock
+
+from kalliope.core.Models.Player import Player
+from kalliope.core.Models.Tts import Tts
+
+from kalliope.core.Models.Trigger import Trigger
+
+from kalliope.core.Models.Stt import Stt
+
+from kalliope.core.Models.RestAPI import RestAPI
+
+from kalliope.core.Models.Dna import Dna
+
+from kalliope.core import LIFOBuffer
+from kalliope.core.Models.Settings import Settings
+
+from kalliope.core.Models import Neuron, Order, Synapse, Brain, Event, Resources, Singleton
+
+from kalliope.core.Models.APIResponse import APIResponse
+from kalliope.core.Models.MatchedSynapse import MatchedSynapse
+
+
+class TestModels(unittest.TestCase):
+
+    def setUp(self):
+        # Kill the singleton
+        Singleton._instances = dict()
+
+        # Init
+        neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
+        neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2'})
+        neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'})
+        neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'})
+
+        signal1 = Order(sentence="this is the sentence")
+        signal2 = Order(sentence="this is the second sentence")
+        signal3 = Order(sentence="that is part of the third sentence")
+
+        self.synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1])
+        self.synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2])
+        self.synapse3 = Synapse(name="Synapse3", neurons=[neuron2, neuron4], signals=[signal3])
+
+        self.all_synapse_list1 = [self.synapse1,
+                                  self.synapse2,
+                                  self.synapse3]
+
+        self.all_synapse_list2 = [self.synapse2,
+                                  self.synapse3]
+
+        self.brain_test1 = Brain(synapses=self.all_synapse_list1)
+        self.brain_test2 = Brain(synapses=self.all_synapse_list2)
+        # 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")
+
+        # clean the LiFO
+        LIFOBuffer.lifo_list = list()
+
+    def test_APIResponse(self):
+        user_order = "user order"
+        self.matched_synapse = MatchedSynapse(matched_synapse=self.synapse1, matched_order=user_order)
+
+        api_response = APIResponse()
+        api_response.user_order = user_order
+        api_response.list_processed_matched_synapse = [self.matched_synapse]
+
+        expected_result_serialize = {
+            'status': None,
+            'matched_synapses':
+                [
+                    {
+                        'matched_order': 'user order',
+                        'neuron_module_list': [],
+                        'synapse_name': 'Synapse1'
+                    }
+                ],
+                'user_order': 'user order'
+        }
+
+        self.assertDictEqual(expected_result_serialize, api_response.serialize())
+
+    def test_Brain(self):
+        # test get synapse by name
+        expect_result = self.synapse1
+        synapse_name = "Synapse1"
+        self.assertEqual(self.brain_test1.get_synapse_by_name(synapse_name), expect_result)
+
+        # test equals
+        self.assertTrue(self.brain_test1.__eq__(self.brain_test3))
+
+        # test not equals
+        self.assertFalse(self.brain_test1.__eq__(self.brain_test2))
+
+    def test_Dna(self):
+        # create DNA object
+        dna1 = Dna(name="dna1", module_type="neuron", author="kalliope",
+                   kalliope_supported_version="0.4.4", tags="test")
+
+        dna2 = Dna(name="dna2", module_type="neuron", author="community",
+                   kalliope_supported_version="0.4.2", tags="other")
+
+        # this dna is exactly the same as the first one
+        dna3 = Dna(name="dna1", module_type="neuron", author="kalliope",
+                   kalliope_supported_version="0.4.4", tags="test")
+
+        expected_result_serialize = {
+            'kalliope_supported_version': '0.4.4',
+            'tags': 'test',
+            'type': 'neuron',
+            'name': 'dna1',
+            'author': 'kalliope'
+        }
+
+        self.assertDictEqual(expected_result_serialize, dna1.serialize())
+
+        self.assertTrue(dna1.__eq__(dna3))
+        self.assertFalse(dna1.__eq__(dna2))
+
+    def test_Event(self):
+        event1 = Event(year=2017, month=12, day=31, week=53, day_of_week=2,
+                       hour=8, minute=30, second=0)
+
+        event2 = Event(year=2018, month=11, day=30, week=25, day_of_week=4,
+                       hour=9, minute=40, second=0)
+
+        # same as the event1
+        event3 = Event(year=2017, month=12, day=31, week=53, day_of_week=2,
+                       hour=8, minute=30, second=0)
+
+        expected_result_serialize = {
+            'event': {
+                'week': 53,
+                'second': 0,
+                'minute': 30,
+                'hour': 8,
+                'year': 2017,
+                'day': 31,
+                'day_of_week': 2,
+                'month': 12
+            }
+        }
+
+        self.assertDictEqual(expected_result_serialize, event1.serialize())
+
+        self.assertTrue(event1.__eq__(event3))
+        self.assertFalse(event1.__eq__(event2))
+
+    def test_MatchedSynapse(self):
+        user_order = "user order"
+        matched_synapse1 = MatchedSynapse(matched_synapse=self.synapse1, matched_order=user_order)
+        matched_synapse2 = MatchedSynapse(matched_synapse=self.synapse2, matched_order=user_order)
+        matched_synapse3 = MatchedSynapse(matched_synapse=self.synapse1, matched_order=user_order)
+
+        expected_result_serialize = {
+            'matched_order': 'user order',
+            'neuron_module_list': [],
+            'synapse_name': 'Synapse1'
+        }
+
+        self.assertDictEqual(expected_result_serialize, matched_synapse1.serialize())
+
+        self.assertTrue(matched_synapse1.__eq__(matched_synapse3))
+        self.assertFalse(matched_synapse1.__eq__(matched_synapse2))
+
+        # test neuron parameter loader is called
+        with mock.patch("kalliope.core.NeuronParameterLoader.get_parameters") as mock_get_parameters:
+
+            MatchedSynapse(matched_synapse=self.synapse1, matched_order=user_order, user_order=user_order)
+            mock_get_parameters.assert_called_once_with(synapse_order=user_order,
+                                                        user_order=user_order)
+            mock_get_parameters.reset_mock()
+
+    def test_Neuron(self):
+
+        neuron1 = Neuron(name="test", parameters={"key1": "val1", "key2": "val2"})
+        neuron2 = Neuron(name="test", parameters={"key3": "val3", "key4": "val4"})
+        neuron3 = Neuron(name="test", parameters={"key1": "val1", "key2": "val2"})
+
+        expected_result_serialize = {'name': 'test', 'parameters': {'key2': 'val2', 'key1': 'val1'}}
+
+        self.assertDictEqual(expected_result_serialize, neuron1.serialize())
+
+        self.assertTrue(neuron1.__eq__(neuron3))
+        self.assertFalse(neuron1.__eq__(neuron2))
+
+        # test password
+        neuron_name = "test"
+        neuron_parameters = {
+            "password": "my secret",
+            "parameter": "test"
+        }
+
+        neuron = Neuron()
+        neuron.name = neuron_name
+        neuron.parameters = neuron_parameters
+
+        expected_result_str = "{'name': 'test', 'parameters': {'password': '*****', 'parameter': 'test'}}"
+
+        self.assertDictEqual(ast.literal_eval(neuron.__str__()), ast.literal_eval(expected_result_str))
+
+        neuron_name = "test"
+        neuron_parameters = {
+            "password_parameter": "my secret",
+            "parameter": "test"
+        }
+
+        neuron = Neuron()
+        neuron.name = neuron_name
+        neuron.parameters = neuron_parameters
+
+        expected_result_str = "{'name': 'test', 'parameters': {'parameter': 'test', 'password_parameter': '*****'}}"
+
+        self.assertDictEqual(ast.literal_eval(neuron.__str__()), ast.literal_eval(expected_result_str))
+
+    def test_Order(self):
+        order1 = Order(sentence="this is an order")
+        order2 = Order(sentence="this is an other order")
+        order3 = Order(sentence="this is an order")
+
+        expected_result_serialize = {'order': 'this is an order'}
+        expected_result_str = "{'order': 'this is an order'}"
+
+        self.assertEqual(expected_result_serialize, order1.serialize())
+        self.assertEqual(expected_result_str, order1.__str__())
+
+        self.assertTrue(order1.__eq__(order3))
+        self.assertFalse(order1.__eq__(order2))
+
+    def test_Resources(self):
+        resource1 = Resources(neuron_folder="/path/neuron", stt_folder="/path/stt",
+                              tts_folder="/path/tts", trigger_folder="/path/trigger")
+
+        resource2 = Resources(neuron_folder="/other_path/neuron", stt_folder="/other_path/stt",
+                              tts_folder="/other_path/tts", trigger_folder="/other_path/trigger")
+
+        resource3 = Resources(neuron_folder="/path/neuron", stt_folder="/path/stt",
+                              tts_folder="/path/tts", trigger_folder="/path/trigger")
+
+        expected_result_serialize = {
+            'tts_folder': '/path/tts',
+            'neuron_folder': '/path/neuron',
+            'stt_folder': '/path/stt',
+            'trigger_folder': '/path/trigger'
+        }
+
+        self.assertDictEqual(expected_result_serialize, resource1.serialize())
+
+        self.assertTrue(resource1.__eq__(resource3))
+        self.assertFalse(resource1.__eq__(resource2))
+
+    def test_RestAPI(self):
+
+        rest_api1 = RestAPI(password_protected=True, login="admin", password="password", active=True,
+                            port=5000, allowed_cors_origin="*")
+
+        rest_api2 = RestAPI(password_protected=False, active=False,
+                            port=5000, allowed_cors_origin=None)
+
+        rest_api3 = RestAPI(password_protected=True, login="admin", password="password", active=True,
+                            port=5000, allowed_cors_origin="*")
+
+        expected_result_serialize = {
+            'password_protected': True,
+            'port': 5000,
+            'active': True,
+            'allowed_cors_origin': '*',
+            'password': 'password',
+            'login': 'admin'
+        }
+
+        self.assertDictEqual(expected_result_serialize, rest_api1.serialize())
+
+        self.assertTrue(rest_api1.__eq__(rest_api3))
+        self.assertFalse(rest_api1.__eq__(rest_api2))
+
+    def test_Settings(self):
+        with mock.patch('platform.machine', return_value='pumpkins'):
+            rest_api1 = RestAPI(password_protected=True,
+                                login="admin",
+                                password="password",
+                                active=True,
+                                port=5000, allowed_cors_origin="*")
+
+            setting1 = Settings(default_tts_name="pico2wav",
+                                default_stt_name="google",
+                                default_trigger_name="swoyboy",
+                                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"})
+            setting1.kalliope_version = "0.4.5"
+
+            setting2 = Settings(default_tts_name="accapela",
+                                default_stt_name="bing",
+                                default_trigger_name="swoyboy",
+                                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"})
+            setting2.kalliope_version = "0.4.5"
+
+            setting3 = Settings(default_tts_name="pico2wav",
+                                default_stt_name="google",
+                                default_trigger_name="swoyboy",
+                                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"})
+            setting3.kalliope_version = "0.4.5"
+
+            expected_result_serialize = {
+                'default_synapse': 'default_synapse',
+                'default_tts_name': 'pico2wav',
+                'rest_api':
+                    {
+                        'password_protected': True,
+                        'port': 5000,
+                        'active': True,
+                        'allowed_cors_origin': '*',
+                        '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']
+            }
+
+            self.assertDictEqual(expected_result_serialize, setting1.serialize())
+
+            self.assertTrue(setting1.__eq__(setting3))
+            self.assertFalse(setting1.__eq__(setting2))
+
+    def test_Stt(self):
+        stt1 = Stt(name="stt1", parameters={"key1": "val1"})
+        stt2 = Stt(name="stt2", parameters={"key2": "val2"})
+        stt3 = Stt(name="stt1", parameters={"key1": "val1"})
+
+        expected_result_serialize = {'name': 'stt1', 'parameters': {'key1': 'val1'}}
+
+        self.assertDictEqual(expected_result_serialize, stt1.serialize())
+
+        self.assertTrue(stt1.__eq__(stt3))
+        self.assertFalse(stt1.__eq__(stt2))
+
+    def test_Synapse(self):
+        neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
+        neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2'})
+        neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'})
+        neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'})
+
+        signal1 = Order(sentence="this is the sentence")
+        signal2 = Order(sentence="this is the second sentence")
+
+        synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1])
+        synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2])
+        synapse3 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1])
+
+        expected_result_serialize = {
+            'signals': [
+                {
+                    'order': 'this is the sentence'
+                }
+            ],
+            'neurons': [
+                {
+                    'name': 'neurone1',
+                     'parameters': {
+                         'var1': 'val1'
+                     }
+                },
+                {
+                    'name': 'neurone2',
+                    'parameters':
+                        {
+                            'var2': 'val2'
+                        }
+                }
+            ],
+            'name': 'Synapse1'
+        }
+
+        self.assertDictEqual(expected_result_serialize, synapse1.serialize())
+
+        self.assertTrue(synapse1.__eq__(synapse3))
+        self.assertFalse(synapse1.__eq__(synapse2))
+
+    def test_Trigger(self):
+        trigger1 = Trigger(name="trigger1", parameters={"key1": "val1"})
+        trigger2 = Trigger(name="trigger2", parameters={"key2": "val2"})
+        trigger3 = Trigger(name="trigger1", parameters={"key1": "val1"})
+
+        expected_result_serialize = {'name': 'trigger1', 'parameters': {'key1': 'val1'}}
+
+        self.assertDictEqual(expected_result_serialize, trigger1.serialize())
+
+        self.assertTrue(trigger1.__eq__(trigger3))
+        self.assertFalse(trigger1.__eq__(trigger2))
+
+    def test_Player(self):
+        player1 = Player(name="player1", parameters={"key1": "val1"})
+        player2 = Player(name="player2", parameters={"key2": "val2"})
+        player3 = Player(name="player1", parameters={"key1": "val1"})
+
+        expected_result_serialize = {'name': 'player1', 'parameters': {'key1': 'val1'}}
+
+        self.assertDictEqual(expected_result_serialize, player1.serialize())
+
+        self.assertTrue(player1.__eq__(player3))
+        self.assertFalse(player1.__eq__(player2))
+
+    def test_Tts(self):
+        tts1 = Tts(name="tts1", parameters={"key1": "val1"})
+        tts2 = Tts(name="tts2", parameters={"key2": "val2"})
+        tts3 = Tts(name="tts1", parameters={"key1": "val1"})
+
+        expected_result_serialize = {'name': 'tts1', 'parameters': {'key1': 'val1'}}
+
+        self.assertDictEqual(expected_result_serialize, tts1.serialize())
+
+        self.assertTrue(tts1.__eq__(tts3))
+        self.assertFalse(tts1.__eq__(tts2))
+
+

+ 227 - 0
Tests/test_neuron_launcher.py

@@ -0,0 +1,227 @@
+# coding: utf-8
+
+import unittest
+import mock
+
+from kalliope.core.Models.Resources import Resources
+from kalliope.core.NeuronLauncher import NeuronLauncher, NeuronParameterNotAvailable
+from kalliope.core.ConfigurationManager import SettingLoader
+
+from kalliope.core.Models.Neuron import Neuron
+
+
+class TestNeuronLauncher(unittest.TestCase):
+    """
+    Class to test Launchers Classes (TriggerLauncher, SynapseLauncher, NeuronLauncher) and methods
+    """
+
+    def setUp(self):
+        pass
+
+    ####
+    # Neurons Launcher
+    def test_launch_neuron(self):
+        """
+        Test the Neuron Launcher trying to start a Neuron
+        """
+        neuron = Neuron(name='neurone1', parameters={'var1': 'val1'})
+        sl = SettingLoader()
+        resources = Resources(neuron_folder='/var/tmp/test/resources')
+        sl.settings.resources = resources
+        with mock.patch("kalliope.core.Utils.get_dynamic_class_instantiation") as mock_get_class_instantiation:
+            NeuronLauncher.launch_neuron(neuron=neuron)
+
+            mock_get_class_instantiation.assert_called_once_with(package_name="neurons",
+                                                                 module_name=neuron.name,
+                                                                 parameters=neuron.parameters,
+                                                                 resources_dir=sl.settings.resources.neuron_folder)
+            mock_get_class_instantiation.reset_mock()
+
+    def test_start_neuron(self):
+        """
+        Testing params association and starting a Neuron
+        """
+
+        with mock.patch("kalliope.core.NeuronLauncher.launch_neuron") as mock_launch_neuron_method:
+            # Assert to the neuron is launched with not parameter from order
+            neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
+
+            NeuronLauncher.start_neuron(neuron=neuron1)
+            mock_launch_neuron_method.assert_called_with(neuron1)
+            mock_launch_neuron_method.reset_mock()
+
+            # Assert the params are well passed to the neuron
+            neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2', 'var3': "{{ var3 }}"})
+            params = {
+                'var3': 'value3'
+            }
+            NeuronLauncher.start_neuron(neuron=neuron2,
+                                        parameters_dict=params)
+            neuron2_params = Neuron(name='neurone2', parameters={'var2': 'val2', 'var3': 'value3'})
+
+            mock_launch_neuron_method.assert_called_with(neuron2_params)
+            mock_launch_neuron_method.reset_mock()
+
+            # Assert the Neuron is not started when missing args
+            neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3', 'var4': '{{val4}}'})
+            params = {
+                'not_exist': 'test'
+            }
+            NeuronLauncher.start_neuron(neuron=neuron3,
+                                        parameters_dict=params)
+            mock_launch_neuron_method.assert_not_called()
+            mock_launch_neuron_method.reset_mock()
+
+            # Assert no neuron is launched when waiting for args and none are given
+            neuron4 = Neuron(name='neurone4', parameters={'var5': 'val5', 'var6': '{{val6}}'})
+
+            NeuronLauncher.start_neuron(neuron=neuron4)
+            mock_launch_neuron_method.assert_not_called()
+            mock_launch_neuron_method.reset_mock()
+
+    def test_replace_brackets_by_loaded_parameter(self):
+        # -------------------
+        # test with string
+        # -------------------
+        # the target value to replace is present in the loaded parameter dict
+        neuron_parameters = {
+            "param1": "this is a value {{ replaced }}"
+        }
+
+        loaded_parameters = {
+            "replaced": "replaced successfully"
+        }
+
+        expected_result = {
+            "param1": "this is a value replaced successfully"
+        }
+
+        self.assertEqual(expected_result, NeuronLauncher._replace_brackets_by_loaded_parameter(neuron_parameters,
+                                                                                               loaded_parameters))
+
+        # the target value with unicode to replace is present in the loaded parameter dict
+        neuron_parameters = {
+            "param1": "this is a value {{ replaced }}"
+        }
+
+        loaded_parameters = {
+            "replaced": u"rêmpläcée successfülly"
+        }
+
+        expected_result = {
+            "param1": "this is a value rêmpläcée successfülly"
+        }
+
+        self.assertEqual(expected_result, NeuronLauncher._replace_brackets_by_loaded_parameter(neuron_parameters,
+                                                                                               loaded_parameters))
+
+        # the target value to replace is NOT present in the loaded parameter dict
+        neuron_parameters = {
+            "param1": "this is a value {{ replaced }}"
+        }
+
+        loaded_parameters = {
+            "not_exist": "replaced successfully"
+        }
+
+        with self.assertRaises(NeuronParameterNotAvailable):
+            NeuronLauncher._replace_brackets_by_loaded_parameter(neuron_parameters, loaded_parameters)
+
+        # one parameter doesn't contains bracket, the other one do
+        neuron_parameters = {
+            "param1": "this is a value {{ replaced }}",
+            "param2": "value"
+        }
+
+        loaded_parameters = {
+            "replaced": "replaced successfully"
+        }
+
+        expected_result = {
+            "param1": "this is a value replaced successfully",
+            "param2": "value"
+        }
+
+        self.assertEqual(expected_result, NeuronLauncher._replace_brackets_by_loaded_parameter(neuron_parameters,
+                                                                                               loaded_parameters))
+
+        # parameters are integer or boolean
+        neuron_parameters = {
+            "param1": 1,
+            "param2": True
+        }
+
+        loaded_parameters = {
+            "replaced": "replaced successfully"
+        }
+
+        expected_result = {
+            "param1": 1,
+            "param2": True
+        }
+
+        self.assertEqual(expected_result, NeuronLauncher._replace_brackets_by_loaded_parameter(neuron_parameters,
+                                                                                               loaded_parameters))
+
+        # parameters are say_template or file template. Should not be altered by the loader
+        neuron_parameters = {
+            "say_template": "{{output}}",
+            "file_template": "here is a file"
+        }
+
+        loaded_parameters = {
+            "output": "should not be used"
+        }
+
+        expected_result = {
+            "say_template": "{{output}}",
+            "file_template": "here is a file"
+        }
+
+        self.assertEqual(expected_result, NeuronLauncher._replace_brackets_by_loaded_parameter(neuron_parameters,
+                                                                                               loaded_parameters))
+
+    def test_parameters_are_available_in_loaded_parameters(self):
+        # the parameter in bracket is available in the dict
+        string_parameters = "this is a {{ parameter1 }}"
+        loaded_parameters = {"parameter1": "value"}
+
+        self.assertTrue(NeuronLauncher._neuron_parameters_are_available_in_loaded_parameters(string_parameters,
+                                                                                            loaded_parameters))
+
+        # the parameter in bracket is NOT available in the dict
+        string_parameters = "this is a {{ parameter1 }}"
+        loaded_parameters = {"parameter2": "value"}
+
+        self.assertFalse(NeuronLauncher._neuron_parameters_are_available_in_loaded_parameters(string_parameters,
+                                                                                             loaded_parameters))
+
+        # the string_parameters doesn't contains bracket in bracket is available in the dict
+        string_parameters = "this is a {{ parameter1 }}"
+        loaded_parameters = {"parameter1": "value"}
+
+        self.assertTrue(NeuronLauncher._neuron_parameters_are_available_in_loaded_parameters(string_parameters,
+                                                                                            loaded_parameters))
+
+        # the string_parameters contains 2 parameters available in the dict
+        string_parameters = "this is a {{ parameter1 }} and this is {{ parameter2 }}"
+        loaded_parameters = {"parameter1": "value", "parameter2": "other value"}
+
+        self.assertTrue(NeuronLauncher._neuron_parameters_are_available_in_loaded_parameters(string_parameters,
+                                                                                            loaded_parameters))
+
+        # the string_parameters contains 2 parameters and one of them is not available in the dict
+        string_parameters = "this is a {{ parameter1 }} and this is {{ parameter2 }}"
+        loaded_parameters = {"parameter1": "value", "parameter3": "other value"}
+
+        self.assertFalse(NeuronLauncher._neuron_parameters_are_available_in_loaded_parameters(string_parameters,
+                                                                                             loaded_parameters))
+
+
+if __name__ == '__main__':
+    unittest.main()
+
+    # suite = unittest.TestSuite()
+    # suite.addTest(TestNeuronLauncher("test_start_neuron"))
+    # runner = unittest.TextTestRunner()
+    # runner.run(suite)

+ 0 - 40
Tests/test_neuron_model.py

@@ -1,40 +0,0 @@
-import os
-import unittest
-
-from kalliope.core.Models import Neuron
-
-
-class TestNeuronModule(unittest.TestCase):
-
-    def test_password_parameter(self):
-        neuron_name = "test"
-        neuron_parameters = {
-            "password": "my secret",
-            "parameter": "test"
-        }
-
-        neuron = Neuron()
-        neuron.name = neuron_name
-        neuron.parameters = neuron_parameters
-
-        expected_result = "Neuron: name: test, parameters: {'password': '*****', 'parameter': 'test'}"
-
-        self.assertEqual(neuron.__str__(), expected_result)
-
-    def test_password_in_parameter(self):
-        neuron_name = "test"
-        neuron_parameters = {
-            "password_parameter": "my secret",
-            "parameter": "test"
-        }
-
-        neuron = Neuron()
-        neuron.name = neuron_name
-        neuron.parameters = neuron_parameters
-
-        expected_result = "Neuron: name: test, parameters: {'parameter': 'test', 'password_parameter': '*****'}"
-
-        self.assertEqual(neuron.__str__(), expected_result)
-
-if __name__ == '__main__':
-    unittest.main()

+ 50 - 89
Tests/test_neuron_module.py

@@ -2,18 +2,21 @@ import os
 import unittest
 import mock
 
-from kalliope.core.NeuronModule import NeuronModule, TemplateFileNotFoundException
-from kalliope.core.Models.Neuron import Neuron
-from kalliope.core.Models.Synapse import Synapse
-from kalliope.core.Models.Brain import Brain
-from kalliope.core.Models.Order import Order
+from kalliope.core.Models import Singleton
+from kalliope.core.Models.Tts import Tts
+
+from kalliope import SettingLoader
+from kalliope.core.NeuronModule import NeuronModule, TemplateFileNotFoundException, TTSModuleNotFound
 
 
 class TestNeuronModule(unittest.TestCase):
 
     def setUp(self):
+        # kill singleton
+        Singleton._instances = dict()
+
         self.expected_result = "hello, this is a replaced word"
-        # this allow us to run the test from an IDE and from the root with python -m unittest Tests.TestNeuronModule
+        # this allow us to run the test from an IDE and from the root with python -m unittest tests.TestNeuronModule
         if "/Tests" in os.getcwd():
             self.file_template = "templates/template_test.j2"
         else:
@@ -24,6 +27,12 @@ class TestNeuronModule(unittest.TestCase):
         }
         self.neuron_module_test = NeuronModule()
 
+        if "/Tests" in os.getcwd():
+            self.file_settings = "settings/settings_test.yml"
+        else:
+            self.file_settings = "Tests/settings/settings_test.yml"
+        self.settings = SettingLoader(file_path=self.file_settings).settings
+
     def tearDown(self):
         del self.neuron_module_test
 
@@ -33,7 +42,7 @@ class TestNeuronModule(unittest.TestCase):
         """
 
         with mock.patch("kalliope.core.OrderListener.start") as mock_orderListener_start:
-            with mock.patch("kalliope.core.OrderListener.join") as mock_orderListener_join:
+            with mock.patch("kalliope.core.OrderListener.join"):
                 def callback():
                     pass
 
@@ -41,35 +50,28 @@ class TestNeuronModule(unittest.TestCase):
                 mock_orderListener_start.assert_called_once_with()
                 mock_orderListener_start.reset_mock()
 
-    def test_update_cache_var(self):
-        """
-        Test Update the value of the cache in the provided arg list
-        """
-
-        # True -> False
-        args_dict = {
-            "cache": True
-        }
-        expected_dict = {
-            "cache": False
-        }
-        self.assertEquals(NeuronModule._update_cache_var(False, args_dict=args_dict),
-                          expected_dict,
-                          "Fail to update the cache value from True to False")
-        self.assertFalse(args_dict["cache"])
-
-        # False -> True
-        args_dict = {
-            "cache": False
-        }
-        expected_dict = {
-            "cache": True
-        }
-        self.assertEquals(NeuronModule._update_cache_var(True, args_dict=args_dict),
-                          expected_dict,
-                          "Fail to update the cache value from False to True")
-
-        self.assertTrue(args_dict["cache"])
+    def test_get_tts_object(self):
+
+        # no TTS name provided. should return the default tts
+        expected_tts = Tts(name="pico2wave", parameters={"language": "fr-FR", "cache": True})
+        self.assertEqual(NeuronModule._get_tts_object(settings=self.settings), expected_tts)
+
+        # TTS provided, only cache parameter updated
+        expected_tts = Tts(name="pico2wave", parameters={"language": "fr-FR", "cache": False})
+        self.assertEqual(NeuronModule._get_tts_object(tts_name="pico2wave",
+                                                      override_parameter={"cache": False},
+                                                      settings=self.settings), expected_tts)
+
+        # TTS provided, all parameters updated
+        expected_tts = Tts(name="pico2wave", parameters={"language": "es-ES", "cache": False})
+        self.assertEqual(NeuronModule._get_tts_object(tts_name="pico2wave",
+                                                      override_parameter={"language": "es-ES", "cache": False},
+                                                      settings=self.settings), expected_tts)
+        # TTS not existing in settings
+        with self.assertRaises(TTSModuleNotFound):
+            NeuronModule._get_tts_object(tts_name="no_existing_tts",
+                                         override_parameter={"cache": False},
+                                         settings=self.settings)
 
     def test_get_message_from_dict(self):
 
@@ -114,62 +116,21 @@ class TestNeuronModule(unittest.TestCase):
         expected_result = "hello, this is a {{ test }}"
         self.assertEqual(NeuronModule._get_content_of_file(self.file_template), expected_result)
 
-    def test_run_synapse_by_name_with_order(self):
+    def test_serialize(self):
         """
-        Test to start a synapse with a specific given order
-        Scenarii :
-            - Neuron has been found and launched
-            - Neuron has not been found
+        Test the serialisation of the neuron module
         """
+        neuron_module = NeuronModule()
+        neuron_module.neuron_name = "kalliope"
+        neuron_module.tts_message = "I am french"
 
-        # Init
-        neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
-        neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2'})
-        neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'})
-        neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'})
-
-        signal1 = Order(sentence="the sentence")
-        signal2 = Order(sentence="the second sentence")
-        signal3 = Order(sentence="part of the third sentence")
-
-        synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1])
-        synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2])
-        synapse3 = Synapse(name="Synapse3", neurons=[neuron2, neuron4], signals=[signal3])
-
-        all_synapse_list = [synapse1,
-                            synapse2,
-                            synapse3]
-
-        br = Brain(synapses=all_synapse_list)
-
-        order = "This is the order"
-        synapse_name = "Synapse2"
-        answer = "This is the {{ answer }}"
-
-        with mock.patch("kalliope.core.OrderAnalyser.start") as mock_orderAnalyser_start:
-            neuron_mod = NeuronModule()
-            neuron_mod.brain = br
-
-            # Success
-            self.assertTrue(neuron_mod.run_synapse_by_name_with_order(order=order,
-                                                                        synapse_name=synapse_name,
-                                                                        order_template=answer),
-                              "fail to find the proper synapse")
-
-            # mock_orderAnalyser_start.assert_called_once()
-            mock_orderAnalyser_start.assert_called_once_with(synapses_to_run=[synapse2],
-                                                             external_order=answer)
-            mock_orderAnalyser_start.reset_mock()
-
-            # Fail
-            synapse_name = "Synapse5"
-            self.assertFalse(neuron_mod.run_synapse_by_name_with_order(order=order,
-                                                                      synapse_name=synapse_name,
-                                                                       order_template=answer),
-                            "fail to NOT find the synapse")
-
-            mock_orderAnalyser_start.assert_not_called()
-            mock_orderAnalyser_start.reset_mock()
+        expected_result = {
+            'neuron_name': "kalliope",
+            'generated_message': "I am french"
+        }
 
+        self.assertEqual(expected_result, neuron_module.serialize())
 
 
+if __name__ == '__main__':
+    unittest.main()

+ 175 - 0
Tests/test_neuron_parameter_loader.py

@@ -0,0 +1,175 @@
+import unittest
+
+from kalliope.core.NeuronParameterLoader import NeuronParameterLoader
+
+
+class TestNeuronParameterLoader(unittest.TestCase):
+
+    def test_get_parameters(self):
+
+        synapse_order = "this is the {{ sentence }}"
+        user_order = "this is the value"
+        expected_result = {'sentence': 'value'}
+
+        self.assertEqual(NeuronParameterLoader.get_parameters(synapse_order=synapse_order, user_order=user_order),
+                         expected_result,
+                         "Fail to retrieve 'the params' of the synapse_order from the order")
+
+        # Multiple match
+        synapse_order = "this is the {{ sentence }}"
+
+        user_order = "this is the value with multiple words"
+        expected_result = {'sentence': 'value with multiple words'}
+
+        self.assertEqual(NeuronParameterLoader.get_parameters(synapse_order=synapse_order, user_order=user_order),
+                         expected_result,
+                         "Fail to retrieve the 'multiple words params' of the synapse_order from the order")
+
+        # Multiple params
+        synapse_order = "this is the {{ sentence }} with multiple {{ params }}"
+
+        user_order = "this is the value with multiple words"
+        expected_result = {'sentence': 'value',
+                           'params':'words'}
+
+        self.assertEqual(NeuronParameterLoader.get_parameters(synapse_order=synapse_order, user_order=user_order),
+                         expected_result,
+                         "Fail to retrieve the 'multiple params' of the synapse_order from the order")
+
+        # Multiple params with multiple words
+        synapse_order = "this is the {{ sentence }} with multiple {{ params }}"
+
+        user_order = "this is the multiple values with multiple values as words"
+        expected_result = {'sentence': 'multiple values',
+                           'params': 'values as words'}
+
+        self.assertEqual(NeuronParameterLoader.get_parameters(synapse_order=synapse_order, user_order=user_order),
+                         expected_result)
+
+        # params at the begining of the sentence
+        synapse_order = "{{ sentence }} this is the sentence"
+
+        user_order = "hello world this is the multiple values with multiple values as words"
+        expected_result = {'sentence': 'hello world'}
+
+        self.assertEqual(NeuronParameterLoader.get_parameters(synapse_order=synapse_order, user_order=user_order),
+                         expected_result)
+
+        # all of the sentence is a variable
+        synapse_order = "{{ sentence }}"
+
+        user_order = "this is the all sentence is a variable"
+        expected_result = {'sentence': 'this is the all sentence is a variable'}
+
+        self.assertEqual(NeuronParameterLoader.get_parameters(synapse_order=synapse_order, user_order=user_order),
+                         expected_result)
+
+    def test_associate_order_params_to_values(self):
+        ##
+        # Testing the brackets position behaviour
+        ##
+
+        # Success
+        order_brain = "This is the {{ variable }}"
+        order_user = "This is the value"
+        expected_result = {'variable': 'value'}
+        self.assertEqual(NeuronParameterLoader._associate_order_params_to_values(order_user, order_brain),
+                         expected_result)
+
+        # Success
+        order_brain = "This is the {{variable }}"
+        order_user = "This is the value"
+        expected_result = {'variable': 'value'}
+        self.assertEqual(NeuronParameterLoader._associate_order_params_to_values(order_user, order_brain),
+                         expected_result)
+
+        # Success
+        order_brain = "This is the {{ variable}}"
+        order_user = "This is the value"
+        expected_result = {'variable': 'value'}
+        self.assertEqual(NeuronParameterLoader._associate_order_params_to_values(order_user, order_brain),
+                         expected_result)
+
+        # Success
+        order_brain = "This is the {{variable}}"
+        order_user = "This is the value"
+        expected_result = {'variable': 'value'}
+        self.assertEqual(NeuronParameterLoader._associate_order_params_to_values(order_user, order_brain),
+                         expected_result)
+
+        # Fail
+        order_brain = "This is the {variable}"
+        order_user = "This is the value"
+        expected_result = {'variable': 'value'}
+        self.assertNotEqual(NeuronParameterLoader._associate_order_params_to_values(order_user, order_brain),
+                             expected_result)
+
+        # Fail
+        order_brain = "This is the { variable}}"
+        order_user = "This is the value"
+        expected_result = {'variable': 'value'}
+        self.assertNotEqual(NeuronParameterLoader._associate_order_params_to_values(order_user, order_brain),
+                             expected_result)
+
+        ##
+        # Testing the brackets position in the sentence
+        ##
+
+        # Success
+        order_brain = "{{ variable }} This is the"
+        order_user = "value This is the"
+        expected_result = {'variable': 'value'}
+        self.assertEqual(NeuronParameterLoader._associate_order_params_to_values(order_user, order_brain),
+                         expected_result)
+
+        # Success
+        order_brain = "This is {{ variable }} the"
+        order_user = " This is value the"
+        expected_result = {'variable': 'value'}
+        self.assertEqual(NeuronParameterLoader._associate_order_params_to_values(order_user, order_brain),
+                         expected_result)
+
+        ##
+        # Testing multi variables
+        ##
+
+        # Success
+        order_brain = "This is {{ variable }} the {{ variable2 }}"
+        order_user = "This is value the value2"
+        expected_result = {'variable': 'value',
+                           'variable2': 'value2'}
+        self.assertEqual(NeuronParameterLoader._associate_order_params_to_values(order_user, order_brain),
+                         expected_result)
+
+        ##
+        # Testing multi words in variable
+        ##
+
+        # Success
+        order_brain = "This is the {{ variable }}"
+        order_user = "This is the value with multiple words"
+        expected_result = {'variable': 'value with multiple words'}
+        self.assertEqual(NeuronParameterLoader._associate_order_params_to_values(order_user, order_brain),
+                         expected_result)
+
+        # Success
+        order_brain = "This is the {{ variable }} and  {{ variable2 }}"
+        order_user = "This is the value with multiple words and second value multiple"
+        expected_result = {'variable': 'value with multiple words',
+                           'variable2': 'second value multiple'}
+        self.assertEqual(NeuronParameterLoader._associate_order_params_to_values(order_user, order_brain),
+                         expected_result)
+
+        ##
+        #  Specific Behaviour
+        ##
+
+        # Upper/Lower case
+        order_brain = "This Is The {{ variable }}"
+        order_user = "ThiS is tHe VAlue"
+        expected_result = {'variable': 'VAlue'}
+        self.assertEqual(NeuronParameterLoader._associate_order_params_to_values(order_user, order_brain),
+                         expected_result)
+
+if __name__ == '__main__':
+    unittest.main()

+ 35 - 495
Tests/test_order_analyser.py

@@ -1,12 +1,12 @@
 import unittest
-import mock
 
+
+from kalliope.core.Models import Brain
+from kalliope.core.Models import Neuron
+from kalliope.core.Models import Order
+from kalliope.core.Models import Synapse
+from kalliope.core.Models.MatchedSynapse import MatchedSynapse
 from kalliope.core.OrderAnalyser import OrderAnalyser
-from kalliope.core.Models.Neuron import Neuron
-from kalliope.core.Models.Synapse import Synapse
-from kalliope.core.Models.Brain import Brain
-from kalliope.core.Models.Settings import Settings
-from kalliope.core.Models.Order import Order
 
 
 class TestOrderAnalyser(unittest.TestCase):
@@ -16,16 +16,7 @@ class TestOrderAnalyser(unittest.TestCase):
     def setUp(self):
         pass
 
-    def test_start(self):
-        """
-        Testing if the matches from the incoming messages and the signals/order sentences.
-        Scenarii :
-            - Order matchs a synapse and the synapse has been launched.
-            - Order does not match but have a default synapse.
-            - Order does not match and does not have default synapse.
-            - Provide synapse without any external orders
-            - Provide synapse with any external orders
-        """
+    def test_get_matching_synapse(self):
         # Init
         neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
         neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2'})
@@ -46,174 +37,43 @@ class TestOrderAnalyser(unittest.TestCase):
 
         br = Brain(synapses=all_synapse_list)
 
-        with mock.patch("kalliope.core.OrderAnalyser._start_neuron") as mock_start_neuron_method:
-            # assert synapses have been launched
-            order_to_match = "this is the sentence"
-            oa = OrderAnalyser(order=order_to_match,
-                               brain=br)
-            expected_result = [synapse1]
-
-            self.assertEquals(oa.start(),
-                              expected_result,
-                              "Fail to run the expected Synapse matching the order")
-
-            calls = [mock.call(neuron1, {}), mock.call(neuron2, {})]
-            mock_start_neuron_method.assert_has_calls(calls=calls)
-            mock_start_neuron_method.reset_mock()
-
-            # No order matching Default Synapse to run
-            order_to_match = "random sentence"
-            oa = OrderAnalyser(order=order_to_match,
-                               brain=br)
-            oa.settings = mock.MagicMock(default_synapse="Synapse3")
-            expected_result = [synapse3]
-            self.assertEquals(oa.start(),
-                              expected_result,
-                              "Fail to run the default Synapse because no other synapses match the order")
-
-            # No order matching no Default Synapse
-            order_to_match = "random sentence"
-            oa = OrderAnalyser(order=order_to_match,
-                               brain=br)
-            oa.settings = mock.MagicMock()
-            expected_result = []
-            self.assertEquals(oa.start(),
-                              expected_result,
-                              "Fail to no synapse because no synapse matchs and no default defined")
+        # TEST1: should return synapse1
+        spoken_order = "this is the sentence"
 
-            # Provide synapse to run
-            order_to_match = "this is the sentence"
-            oa = OrderAnalyser(order=order_to_match,
-                               brain=br)
-            expected_result = [synapse1]
-            synapses_to_run = [synapse1]
+        # Create the matched synapse
+        matched_synapse_1 = MatchedSynapse(matched_synapse=synapse1,
+                                           matched_order=spoken_order,
+                                           user_order=spoken_order)
 
-            self.assertEquals(oa.start(synapses_to_run=synapses_to_run),
-                              expected_result,
-                              "Fail to run the provided synapse to run")
-            calls = [mock.call(neuron1, {}), mock.call(neuron2, {})]
-            mock_start_neuron_method.assert_has_calls(calls=calls)
-            mock_start_neuron_method.reset_mock()
-
-            # Provide synapse and external orders
-            order_to_match = "this is an external sentence"
-            oa = OrderAnalyser(order=order_to_match,
-                               brain=br)
-            external_orders = "this is an external {{ order }}"
-            synapses_to_run = [synapse2]
-            expected_result = [synapse2]
-
-            self.assertEquals(oa.start(synapses_to_run=synapses_to_run, external_order=external_orders),
-                              expected_result,
-                              "Fail to run a provided synapse with external order")
-            calls = [mock.call(neuron3, {"order":u"sentence"}), mock.call(neuron4, {"order":u"sentence"})]
-            mock_start_neuron_method.assert_has_calls(calls=calls)
-            mock_start_neuron_method.reset_mock()
-
-    def test_start_neuron(self):
-        """
-        Testing params association and starting a Neuron
-        """
-
-        neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'})
+        matched_synapses = OrderAnalyser.get_matching_synapse(order=spoken_order, brain=br)
+        self.assertEqual(len(matched_synapses), 1)
+        self.assertTrue(matched_synapse_1 in matched_synapses)
 
-        with mock.patch("kalliope.core.NeuronLauncher.NeuronLauncher.start_neuron") as mock_start_neuron_method:
-            # Assert to the neuron is launched
-            neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
-            params = {
-                'param1':'parval1'
-            }
-            OrderAnalyser._start_neuron(neuron=neuron1,params=params)
-            mock_start_neuron_method.assert_called_with(neuron1)
-            mock_start_neuron_method.reset_mock()
+        # TEST2: should return synapse1 and 2
+        spoken_order = "this is the second sentence"
+        matched_synapses = OrderAnalyser.get_matching_synapse(order=spoken_order, brain=br)
+        self.assertEqual(len(matched_synapses), 2)
+        self.assertTrue(synapse1, synapse2 in matched_synapses)
 
-            # Assert the params are well passed to the neuron
-            neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2', 'args': ['arg1', 'arg2']})
-            params = {
-                'arg1':'argval1',
-                'arg2':'argval2'
-            }
-            OrderAnalyser._start_neuron(neuron=neuron2, params=params)
-            neuron2_params = Neuron(name='neurone2',
-                                    parameters={'var2': 'val2',
-                                                'args': ['arg1', 'arg2'],
-                                                'arg1':'argval1',
-                                                'arg2':'argval2'}
-                                    )
-            mock_start_neuron_method.assert_called_with(neuron2_params)
-            mock_start_neuron_method.reset_mock()
-
-            # Assert the Neuron is not started when missing args
-            neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3', 'args': ['arg3', 'arg4']})
-            params = {
-                'arg1': 'argval1',
-                'arg2': 'argval2'
-            }
-            OrderAnalyser._start_neuron(neuron=neuron3, params=params)
-            mock_start_neuron_method.assert_not_called()
-            mock_start_neuron_method.reset_mock()
-
-            # Assert no neuron is launched when waiting for args and none are given
-            neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4', 'args': ['arg5', 'arg6']})
-            params = {}
-            OrderAnalyser._start_neuron(neuron=neuron4, params=params)
-            mock_start_neuron_method.assert_not_called()
-            mock_start_neuron_method.reset_mock()
+        # TEST3: should empty
+        spoken_order = "not a valid order"
+        matched_synapses = OrderAnalyser.get_matching_synapse(order=spoken_order, brain=br)
+        self.assertFalse(matched_synapses)
 
     def test_spelt_order_match_brain_order_via_table(self):
         order_to_test = "this is the order"
         sentence_to_test = "this is the order"
 
         # Success
-        self.assertTrue(OrderAnalyser.spelt_order_match_brain_order_via_table(order_to_test, sentence_to_test),
-                        "Fail matching order with the expected sentence")
+        self.assertTrue(OrderAnalyser.spelt_order_match_brain_order_via_table(order_to_test, sentence_to_test))
 
         # Failure
         sentence_to_test = "unexpected sentence"
-        self.assertFalse(OrderAnalyser.spelt_order_match_brain_order_via_table(order_to_test, sentence_to_test),
-                         "Fail to ensure the expected sentence is not matching the order")
+        self.assertFalse(OrderAnalyser.spelt_order_match_brain_order_via_table(order_to_test, sentence_to_test))
 
         # Upper/lower cases
         sentence_to_test = "THIS is THE order"
-        self.assertTrue(OrderAnalyser.spelt_order_match_brain_order_via_table(order_to_test, sentence_to_test),
-                        "Fail matching Upper/lower cases")
-
-    def test_format_sentences_to_analyse(self):
-        # First capital in sentence
-        order_to_test = "this is the order"
-        sentence_to_test = "This is the order"
-        expected_result = "this is the order", "this is the order"
-        self.assertEqual(OrderAnalyser._format_sentences_to_analyse(order_to_analyse=order_to_test,
-                                                                    user_said=sentence_to_test),
-                         expected_result,
-                         "Fails formatting the sentences with first capital in sentence")
-
-        # random uppercase in sentence
-        order_to_test = "this is the order"
-        sentence_to_test = "This IS the ordeR"
-        expected_result = "this is the order", "this is the order"
-        self.assertEqual(OrderAnalyser._format_sentences_to_analyse(order_to_analyse=order_to_test,
-                                                                    user_said=sentence_to_test),
-                         expected_result,
-                         "Fails formatting the sentences with random in sentence")
-
-        # random uppercase in order
-        order_to_test = "thiS is THE orDer"
-        sentence_to_test = "this is the order"
-        expected_result = "this is the order", "this is the order"
-        self.assertEqual(OrderAnalyser._format_sentences_to_analyse(order_to_analyse=order_to_test,
-                                                                    user_said=sentence_to_test),
-                         expected_result,
-                         "Fails formatting the sentences with random in order")
-
-        # random uppercase in both order and sentence
-        order_to_test = "thiS is THE orDer"
-        sentence_to_test = "THIS is the Order"
-        expected_result = "this is the order", "this is the order"
-        self.assertEqual(OrderAnalyser._format_sentences_to_analyse(order_to_analyse=order_to_test,
-                                                                    user_said=sentence_to_test),
-                         expected_result,
-                         "Fails formatting the sentences with random in both order and sentence")
+        self.assertTrue(OrderAnalyser.spelt_order_match_brain_order_via_table(order_to_test, sentence_to_test))
 
     def test_get_split_order_without_bracket(self):
         # Success
@@ -242,334 +102,14 @@ class TestOrderAnalyser(unittest.TestCase):
         self.assertEqual(OrderAnalyser._get_split_order_without_bracket(order_to_test), expected_result,
                          "No space brackets Fails to return the expected list")
 
-    def test_associate_order_params_to_values(self):
-        ##
-        # Testing the brackets position behaviour
-        ##
-
-        # Success
-        order_brain = "This is the {{ variable }}"
-        order_user = "This is the value"
-        expected_result = {'variable': 'value'}
-        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
-                         "Fail to match the order_brain {{ variable }} to the 'value'")
-
-        # Success
-        order_brain = "This is the {{variable }}"
-        order_user = "This is the value"
-        expected_result = {'variable': 'value'}
-        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
-                         "Fail to match the order_brain {{variable }} to the 'value'")
-
-        # Success
-        order_brain = "This is the {{ variable}}"
-        order_user = "This is the value"
-        expected_result = {'variable': 'value'}
-        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
-                         "Fail to match the order_brain {{ variable}} to the 'value'")
-
-        # Success
-        order_brain = "This is the {{variable}}"
-        order_user = "This is the value"
-        expected_result = {'variable': 'value'}
-        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
-                         "Fail to match the order_brain {{variable}} to the 'value'")
-
-        # Fail
-        order_brain = "This is the {variable}"
-        order_user = "This is the value"
-        expected_result = {'variable': 'value'}
-        self.assertNotEquals(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
-                             "Should not match the order_brain {variable} to the 'value'")
-
-        # Fail
-        order_brain = "This is the { variable}}"
-        order_user = "This is the value"
-        expected_result = {'variable': 'value'}
-        self.assertNotEquals(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
-                             "Should not match the order_brain { variable}} to the 'value'")
-
-        ##
-        # Testing the brackets position in the sentence
-        ##
-
-        # Success
-        order_brain = "{{ variable }} This is the"
-        order_user = "value This is the"
-        expected_result = {'variable': 'value'}
-        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
-                         "Fail to match the order_brain {{ variable }} in first position "
-                         "ins the sentence to the 'value'")
-
-        # Success
-        order_brain = "This is {{ variable }} the"
-        order_user = " This is value the"
-        expected_result = {'variable': 'value'}
-        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
-                         "Fail to match the order_brain {{ variable }} in middle position ins "
-                         "the sentence to the 'value'")
-
-        ##
-        # Testing multi variables
-        ##
-
-        # Success
-        order_brain = "This is {{ variable }} the {{ variable2 }}"
-        order_user = "This is value the value2"
-        expected_result = {'variable': 'value',
-                           'variable2': 'value2'}
-        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
-                         "Fail to match the order_brain multi variable to the multi values")
-
-        ##
-        # Testing multi words in variable
-        ##
-
-        # Success
-        order_brain = "This is the {{ variable }}"
-        order_user = "This is the value with multiple words"
-        expected_result = {'variable': 'value with multiple words'}
-        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
-                         "Fail to match the order_brain {{ variable }} to the 'value with multiple words'")
-
-        # Success
-        order_brain = "This is the {{ variable }} and  {{ variable2 }}"
-        order_user = "This is the value with multiple words and second value multiple"
-        expected_result = {'variable': 'value with multiple words',
-                           'variable2': 'second value multiple'}
-        self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result,
-                         "Fail to match the order_brain multiple variables with multiple words as values'")
-
-    def test_get_matching_synapse_list(self):
-        # Init
-        neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
-        neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2'})
-        neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'})
-        neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'})
-
-        signal1 = Order(sentence="this is the sentence")
-        signal2 = Order(sentence="this is the second sentence")
-        signal3 = Order(sentence="that is part of the third sentence")
-
-        synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1])
-        synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2])
-        synapse3 = Synapse(name="Synapse3", neurons=[neuron2, neuron4], signals=[signal3])
-
-        order_to_match = "this is the sentence"
-        all_synapse_list = [synapse1,
-                            synapse2,
-                            synapse3]
-
-
-
-        # Success
-        expected_result = synapse1
-        oa_tuple_list = OrderAnalyser._get_matching_synapse_list(all_synapses_list=all_synapse_list,
-                                                                   order_to_match=order_to_match)
-        self.assertEquals(oa_tuple_list[0].synapse,
-                          expected_result,
-                          "Fail matching 'the expected synapse' from the complete synapse list and the order")
-
-        # Multiple Matching synapses
-        signal2 = Order(sentence="this is the sentence")
-
-        synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2])
-        order_to_match = "this is the sentence"
-
-        all_synapse_list = [synapse1,
-                            synapse2,
-                            synapse3]
-
-        expected_result = [synapse1,
-                           synapse2]
-        oa_tuple_list = OrderAnalyser._get_matching_synapse_list(all_synapses_list=all_synapse_list,
-                                                                 order_to_match=order_to_match)
-        self.assertEquals(oa_tuple_list[0].synapse,
-                          expected_result[0],
-                          "Fail 'Multiple Matching synapses' from the complete synapse list and the order (first element)")
-        self.assertEquals(oa_tuple_list[1].synapse,
-                          expected_result[1],
-                          "Fail 'Multiple Matching synapses' from the complete synapse list and the order (second element)")
-
-        # matching no synapses
-        order_to_match = "this is not the correct word"
-
-        all_synapse_list = [synapse1,
-                            synapse2,
-                            synapse3]
-
-        expected_result = []
-
-        self.assertEquals(OrderAnalyser._get_matching_synapse_list(all_synapses_list=all_synapse_list,
-                                                                   order_to_match=order_to_match),
-                          expected_result,
-                          "Fail matching 'no synapses' from the complete synapse list and the order")
-
-        # matching synapse with all key worlds
-        # /!\ Some words in the order are matching all words in synapses signals !
-        order_to_match = "this is not the correct sentence"
-        all_synapse_list = [synapse1,
-                            synapse2,
-                            synapse3]
-
-        expected_result = [synapse1,
-                           synapse2]
-
-        oa_tuple_list = OrderAnalyser._get_matching_synapse_list(all_synapses_list=all_synapse_list,
-                                                                 order_to_match=order_to_match)
-
-        self.assertEquals(oa_tuple_list[0].synapse,
-                          expected_result[0],
-                          "Fail matching 'synapse with all key worlds' from the complete synapse list and the order (first element)")
-        self.assertEquals(oa_tuple_list[1].synapse,
-                          expected_result[1],
-                          "Fail matching 'synapse with all key worlds' from the complete synapse list and the order (second element)")
-
-    def test_get_params_from_order(self):
-
-        string_order = "this is the {{ sentence }}"
-        order_to_check = "this is the value"
-        expected_result = {'sentence': 'value'}
-
-        self.assertEquals(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check),
-                          expected_result,
-                          "Fail to retrieve 'the params' of the string_order from the order")
-
-        # Multiple match
-        string_order = "this is the {{ sentence }}"
-
-        order_to_check = "this is the value with multiple words"
-        expected_result = {'sentence': 'value with multiple words'}
-
-        self.assertEqual(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check),
-                         expected_result,
-                         "Fail to retrieve the 'multiple words params' of the string_order from the order")
-
-        # Multiple params
-        string_order = "this is the {{ sentence }} with multiple {{ params }}"
-
-        order_to_check = "this is the value with multiple words"
-        expected_result = {'sentence': 'value',
-                            'params':'words'}
-
-        self.assertEqual(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check),
-                         expected_result,
-                         "Fail to retrieve the 'multiple params' of the string_order from the order")
-
-        # Multiple params with multiple words
-        string_order = "this is the {{ sentence }} with multiple {{ params }}"
-
-        order_to_check = "this is the multiple values with multiple values as words"
-        expected_result = {'sentence': 'multiple values',
-                           'params': 'values as words'}
-
-        self.assertEqual(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check),
-                         expected_result,
-                         "Fail to retrieve the 'multiple params with multiple words' of the string_order from the order")
-
-        # params at the begining of the sentence
-        string_order = "{{ sentence }} this is the sentence"
-
-        order_to_check = "hello world this is the multiple values with multiple values as words"
-        expected_result = {'sentence': 'hello world'}
-
-        self.assertEqual(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check),
-                         expected_result,
-                         "Fail to retrieve the 'params at the begining of the sentence' of the string_order from the order")
-
-        # all of the sentence is a variable
-        string_order = "{{ sentence }}"
-
-        order_to_check = "this is the all sentence is a variable"
-        expected_result = {'sentence': 'this is the all sentence is a variable'}
-
-        self.assertEqual(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check),
-                         expected_result,
-                         "Fail to retrieve the 'all of the sentence is a variable' of the string_order from the order")
-
-    def test_get_default_synapse_from_sysnapses_list(self):
-        # Init
-        neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
-        neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2'})
-        neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'})
-        neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'})
-
-        signal1 = Order(sentence="this is the sentence")
-        signal2 = Order(sentence="this is the second sentence")
-        signal3 = Order(sentence="that is part of the third sentence")
-
-        synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1])
-        synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2])
-        synapse3 = Synapse(name="Synapse3", neurons=[neuron2, neuron4], signals=[signal3])
-
-        default_synapse_name = "Synapse2"
-        all_synapse_list = [synapse1,
-                            synapse2,
-                            synapse3]
-        expected_result = synapse2
-
-        # Assert equals
-        self.assertEquals(OrderAnalyser._get_default_synapse_from_sysnapses_list(all_synapses_list=all_synapse_list,
-                                                                                 default_synapse_name=default_synapse_name),
-                          expected_result,
-                          "Fail to match the expected default Synapse")
-
-    def test_find_synapse_to_run(self):
-        """
-        Test to find the good synapse to run
-        Scenarii:
-            - 1/ Find the synapse
-            - 2/ No synpase found, no default synapse
-            - 3/ No synapse found, run the default synapse
-        """
-        # Init
-        neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
-        neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2'})
-        neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'})
-        neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'})
-
-        signal1 = Order(sentence="this is the sentence")
-        signal2 = Order(sentence="this is the second sentence")
-        signal3 = Order(sentence="that is part of the third sentence")
-
-        synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1])
-        synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2])
-        synapse3 = Synapse(name="Synapse3", neurons=[neuron2, neuron4], signals=[signal3])
-
-        all_synapse_list = [synapse1,
-                            synapse2,
-                            synapse3]
-
-        br = Brain(synapses=all_synapse_list)
-        st = Settings()
-        # 1/ Find synapse
-        order = "this is the sentence"
-        expected_result = synapse1
-        oa_tuple_list = OrderAnalyser._find_synapse_to_run(brain=br,settings=st, order=order)
-        self.assertEquals(oa_tuple_list[0].synapse,
-                          expected_result,
-                          "Fail to run the proper synapse matching the order")
-
-        expected_result = signal1.sentence
-        self.assertEquals(oa_tuple_list[0].order,
-                          expected_result,
-                          "Fail to run the proper synapse matching the order")
-
-        # 2/ No Default synapse
-        order = "No default synapse"
-        expected_result = []
-        self.assertEquals(OrderAnalyser._find_synapse_to_run(brain=br,settings=st, order=order),
-                          expected_result,
-                          "Fail to run no synapse, when no default is defined")
+    def test_counter_subset(self):
+        list1 = ("word1", "word2")
+        list2 = ("word3", "word4")
+        list3 = ("word1", "word2", "word3", "word4")
 
-        # 3/ Default synapse
-        st = Settings(default_synapse="Synapse2")
-        order = "default synapse"
-        expected_result = synapse2
-        oa_tuple_list = OrderAnalyser._find_synapse_to_run(brain=br, settings=st, order=order)
-        self.assertEquals(oa_tuple_list[0].synapse,
-                          expected_result,
-                          "Fail to run the default synapse")
+        self.assertFalse(OrderAnalyser._counter_subset(list1, list2))
+        self.assertTrue(OrderAnalyser._counter_subset(list1, list3))
+        self.assertTrue(OrderAnalyser._counter_subset(list2, list3))
 
 
 if __name__ == '__main__':

+ 3 - 3
Tests/test_order_listener.py

@@ -39,9 +39,9 @@ class TestOrderListener(unittest.TestCase):
 
         with mock.patch("kalliope.core.Utils.get_dynamic_class_instantiation") as mock_get_dynamic_class_instantiation:
             mock_get_dynamic_class_instantiation.return_value = 'class_instance'
-            self.assertEquals(ol.load_stt_plugin(),
-                              "class_instance",
-                              "Fail getting the proper value")
+            self.assertEqual(ol.load_stt_plugin(),
+                             "class_instance",
+                             "Fail getting the proper value")
 
             mock_get_dynamic_class_instantiation.assert_called_once_with(package_name= "stt",
                                                                          module_name= "Default-stt",

+ 44 - 0
Tests/test_player_launcher.py

@@ -0,0 +1,44 @@
+import unittest
+import mock
+
+from kalliope.core.Models.Player import Player
+from kalliope.core.Models.Settings import Settings
+from kalliope.core.PlayerLauncher import PlayerLauncher
+
+
+class TestPlayerLauncher(unittest.TestCase):
+    """
+    Class to test Launchers Classes (PlayerLauncher) and methods
+    """
+
+    def setUp(self):
+        pass
+
+    ####
+    # Player Launcher
+    def test_get_player(self):
+        """
+        Test the PlayerLauncher trying to run the Player
+        """
+        player1 = Player("Player", {})
+        player2 = Player("Player2", {'test': "hitheparamtest"})
+        settings = Settings()
+        settings.players = [player1, player2]
+        with mock.patch("kalliope.core.Utils.get_dynamic_class_instantiation") as mock_get_class_instantiation:
+            # Get the player1
+            settings.default_player_name = "Player"
+            PlayerLauncher.get_player(settings=settings)
+
+            mock_get_class_instantiation.assert_called_once_with(package_name="players",
+                                                                 module_name=player1.name,
+                                                                 parameters=player1.parameters)
+            mock_get_class_instantiation.reset_mock()
+
+            # Get the player 2
+            settings.default_player_name = "Player2"
+            PlayerLauncher.get_player(settings=settings)
+
+            mock_get_class_instantiation.assert_called_once_with(package_name="players",
+                                                                 module_name=player2.name,
+                                                                 parameters=player2.parameters)
+            mock_get_class_instantiation.reset_mock()

+ 84 - 92
Tests/test_rest_api.py

@@ -1,12 +1,12 @@
 import json
 import os
 import unittest
-
-from werkzeug.datastructures import FileStorage
+import ast
 
 from flask import Flask
 from flask_testing import LiveServerTestCase
 
+from kalliope.core import LIFOBuffer
 from kalliope.core.Models import Singleton
 from kalliope._version import version_str
 from kalliope.core.ConfigurationManager import BrainLoader
@@ -16,6 +16,11 @@ from kalliope.core.RestAPI.FlaskAPI import FlaskAPI
 
 class TestRestAPI(LiveServerTestCase):
 
+    def tearDown(self):
+        Singleton._instances = {}
+        # clean the lifo
+        LIFOBuffer.lifo_list = list()
+
     def create_app(self):
         """
         executed once at the beginning of the test
@@ -36,6 +41,7 @@ class TestRestAPI(LiveServerTestCase):
         sl.settings.active = True
         sl.settings.port = 5000
         sl.settings.allowed_cors_origin = "*"
+        sl.settings.default_synapse = None
 
         # prepare a test brain
         brain_to_test = full_path_brain_to_test
@@ -59,7 +65,7 @@ class TestRestAPI(LiveServerTestCase):
         expected_content = {
             "Kalliope version": "%s" % version_str
         }
-        self.assertEqual(json.dumps(expected_content), json.dumps(json.loads(response.get_data())))
+        self.assertEqual(json.dumps(expected_content, sort_keys=True), json.dumps(json.loads(response.get_data().decode('utf-8')), sort_keys=True))
 
     def test_get_all_synapses(self):
         url = self.get_server_url()+"/synapses"
@@ -126,9 +132,7 @@ class TestRestAPI(LiveServerTestCase):
         # a lot of char ti process
         self.maxDiff = None
         self.assertEqual(response.status_code, 200)
-        # print response.get_data()
-        # print json.dumps(expected_content)
-        self.assertEqual(json.dumps(expected_content), json.dumps(json.loads(response.get_data())))
+        self.assertEqual(json.dumps(expected_content, sort_keys=True), json.dumps(json.loads(response.get_data().decode('utf-8')), sort_keys=True))
 
     def test_get_one_synapse(self):
         url = self.get_server_url() + "/synapses/test"
@@ -154,7 +158,7 @@ class TestRestAPI(LiveServerTestCase):
                 ]
             }
         }
-        self.assertEqual(json.dumps(expected_content), json.dumps(json.loads(response.get_data())))
+        self.assertEqual(json.dumps(expected_content, sort_keys=True), json.dumps(json.loads(response.get_data().decode('utf-8')), sort_keys=True))
 
     def test_get_synapse_not_found(self):
         url = self.get_server_url() + "/synapses/test-none"
@@ -166,34 +170,22 @@ class TestRestAPI(LiveServerTestCase):
             }
         }
 
-        self.assertEqual(expected_content, json.loads(result.get_data()))
+        self.assertEqual(expected_content, json.loads(result.get_data().decode('utf-8')))
         self.assertEqual(result.status_code, 404)
 
     def test_run_synapse_by_name(self):
         url = self.get_server_url() + "/synapses/start/id/test"
         result = self.client.post(url)
 
-        expected_content = {
-            "synapses": {
-                "name": "test",
-                "neurons": [
-                    {
-                        "name": "say",
-                        "parameters": {
-                            "message": [
-                                "test message"
-                            ]
-                        }
-                    }
-                ],
-                "signals": [
-                    {
-                        "order": "test_order"
-                    }
-                ]
-            }
-        }
-        self.assertEqual(json.dumps(expected_content), json.dumps(json.loads(result.get_data())))
+        expected_content = {'status': 'complete',
+                            'matched_synapses':
+                                [{'matched_order': None,
+                                  'neuron_module_list':
+                                      [{'generated_message': 'test message', 'neuron_name': 'Say'}],
+                                  'synapse_name': 'test'}],
+                            '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))
         self.assertEqual(result.status_code, 201)
 
     def test_post_synapse_not_found(self):
@@ -206,7 +198,7 @@ class TestRestAPI(LiveServerTestCase):
             }
         }
 
-        self.assertEqual(json.dumps(expected_content), json.dumps(json.loads(result.get_data())))
+        self.assertEqual(json.dumps(expected_content, sort_keys=True), json.dumps(json.loads(result.get_data().decode('utf-8')), sort_keys=True))
         self.assertEqual(result.status_code, 404)
 
     def test_run_synapse_with_order(self):
@@ -215,78 +207,78 @@ class TestRestAPI(LiveServerTestCase):
         data = {"order": "test_order"}
         result = self.client.post(url, headers=headers, data=json.dumps(data))
 
-        expected_content = {
-            "synapses": [
-                {
-                    "name": "test",
-                    "neurons": [
-                        {
-                            "name": "say",
-                            "parameters": {
-                                "message": [
-                                    "test message"
-                                ]
+        expected_content = {'status': 'complete',
+                            'matched_synapses':
+                                [
+                                    {
+                                        'matched_order': "test_order",
+                                        'neuron_module_list':
+                                            [
+                                                {
+                                                    'generated_message': 'test message', 'neuron_name': 'Say'
+                                                }
+                                            ],
+                                  'synapse_name': 'test'
+                                    }
+                                ],
+                            'user_order': "test_order"
                             }
-                        }
-                    ],
-                    "signals": [
-                        {
-                            "order": "test_order"
-                        }
-                    ]
-                }
-            ]
-        }
-        print result.get_data()
-        self.assertEqual(json.dumps(expected_content), json.dumps(json.loads(result.get_data())))
+        self.assertEqual(json.dumps(expected_content, sort_keys=True), json.dumps(json.loads(result.get_data().decode('utf-8')), sort_keys=True))
         self.assertEqual(result.status_code, 201)
 
     def test_post_synapse_by_order_not_found(self):
         url = self.get_server_url() + "/synapses/start/order"
         data = {"order": "non existing order"}
         headers = {"Content-Type": "application/json"}
-        result = self.client.post(url, headers=headers, data=json.dumps(data))
+        result = self.client.post(url,
+                                  headers=headers,
+                                  data=json.dumps(data))
 
-        expected_content = {'error': {'error': "The given order doesn't match any synapses"}}
+        expected_content = {'status': None, 'matched_synapses': [], 'user_order': u'non existing order'}
 
-        self.assertEqual(json.dumps(expected_content), json.dumps(json.loads(result.get_data())))
-        self.assertEqual(result.status_code, 400)
+        self.assertEqual(json.dumps(expected_content, sort_keys=True), json.dumps(json.loads(result.get_data().decode('utf-8')), sort_keys=True))
+        self.assertEqual(result.status_code, 201)
 
-    # TODO this doesn't work on travis but works locally with python -m unittest discover
-    # def test_post_synapse_by_audio(self):
-    #     url = self.get_server_url() + "/synapses/start/audio"
-    #     with open(os.path.join(self.audio_file), 'rb') as fp:
-    #         file = FileStorage(fp)
-    #         data = {
-    #             'file': file
-    #         }
-    #         result = self.client.post(url, data=data, content_type='multipart/form-data')
-    #
-    #         expected_content = {
-    #             "synapses": [
-    #                 {
-    #                     "name": "test2",
-    #                     "neurons": [
-    #                         {
-    #                             "name": "say",
-    #                             "parameters": {
-    #                                 "message": [
-    #                                     "test message"
-    #                                 ]
-    #                             }
-    #                         }
-    #                     ],
-    #                     "signals": [
-    #                         {
-    #                             "order": "bonjour"
-    #                         }
-    #                     ]
-    #                 }
-    #             ]
-    #         }
-    #
-    #         self.assertEqual(json.dumps(expected_content), json.dumps(json.loads(result.get_data())))
-    #         self.assertEqual(result.status_code, 201)
+        # TODO this doesn't work on travis but works locally with python -m unittest discover
+        # def test_post_synapse_by_audio(self):
+        #     url = self.get_server_url() + "/synapses/start/audio"
+        #     with open(os.path.join(self.audio_file), 'rb') as fp:
+        #         file = FileStorage(fp)
+        #         data = {
+        #             'file': file
+        #         }
+        #         result = self.client.post(url, data=data, content_type='multipart/form-data')
+        #
+        #         expected_content = {
+        #             "synapses": [
+        #                 {
+        #                     "name": "test2",
+        #                     "neurons": [
+        #                         {
+        #                             "name": "say",
+        #                             "parameters": {
+        #                                 "message": [
+        #                                     "test message"
+        #                                 ]
+        #                             }
+        #                         }
+        #                     ],
+        #                     "signals": [
+        #                         {
+        #                             "order": "bonjour"
+        #                         }
+        #                     ]
+        #                 }
+        #             ]
+        #         }
+        #
+        #         self.assertEqual(json.dumps(expected_content), json.dumps(json.loads(result.get_data())))
+        #         self.assertEqual(result.status_code, 201)
 
 if __name__ == '__main__':
     unittest.main()
+
+    # suite = unittest.TestSuite()
+    # suite.addTest(TestRestAPI("test_post_synapse_by_order_not_found"))
+    # runner = unittest.TextTestRunner()
+    # runner.run(suite)

+ 17 - 2
Tests/test_settings_loader.py

@@ -7,6 +7,7 @@ import unittest
 from kalliope.core.ConfigurationManager import SettingLoader
 from kalliope.core.Models import Singleton
 from kalliope.core.Models import Resources
+from kalliope.core.Models.Player import Player
 from kalliope.core.Models.RestAPI import RestAPI
 from kalliope.core.Models.Settings import Settings
 from kalliope.core.Models.Stt import Stt
@@ -17,7 +18,7 @@ from kalliope.core.Models.Tts import Tts
 class TestSettingLoader(unittest.TestCase):
 
     def setUp(self):
-        # get current script directory path. We are in /an/unknown/path/kalliope/core/Tests
+        # 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)
@@ -33,8 +34,10 @@ 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'}}],
+            'players': [{'mplayer': {}}, {'pyalsaaudio': {"device": "default"}}],
             'speech_to_text': [{'google': {'language': 'fr-FR'}}],
             'on_ready_answers': ['Kalliope is ready'],
             'cache_path': '/tmp/kalliope_tts_cache',
@@ -83,6 +86,7 @@ class TestSettingLoader(unittest.TestCase):
         settings_object.default_tts_name = "pico2wave"
         settings_object.default_stt_name = "google"
         settings_object.default_trigger_name = "snowboy"
+        settings_object.default_player_name = "mplayer"
         tts1 = Tts(name="pico2wave", parameters={'cache': True, 'language': 'fr-FR'})
         tts2 = Tts(name="voxygen", parameters={'voice': 'Agnes', 'cache': True})
         settings_object.ttss = [tts1, tts2]
@@ -96,6 +100,9 @@ class TestSettingLoader(unittest.TestCase):
         trigger1 = Trigger(name="snowboy",
                            parameters={'pmdl_file': 'trigger/snowboy/resources/kalliope-FR-6samples.pmdl'})
         settings_object.triggers = [trigger1]
+        player1 = Player(name="mplayer", parameters={})
+        player2 = Player(name="pyalsaaudio", parameters={"device": "default"})
+        settings_object.players = [player1, player2]
         settings_object.rest_api = RestAPI(password_protected=True, active=True,
                                            login="admin", password="secret", port=5000,
                                            allowed_cors_origin=False)
@@ -150,6 +157,14 @@ class TestSettingLoader(unittest.TestCase):
         sl = SettingLoader(file_path=self.settings_file_to_test)
         self.assertEqual([trigger1], sl._get_triggers(self.settings_dict))
 
+    def test_get_players(self):
+        player1 = Player(name="mplayer",
+                         parameters={})
+        player2 = Player(name="pyalsaaudio",
+                         parameters={'device': 'default'})
+        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)
@@ -191,7 +206,7 @@ class TestSettingLoader(unittest.TestCase):
                               trigger_folder="/tmp/kalliope/tests/kalliope_resources_dir/trigger")
         expected_resource = resources
         sl = SettingLoader(file_path=self.settings_file_to_test)
-        self.assertEquals(expected_resource, sl._get_resources(self.settings_dict))
+        self.assertEqual(expected_resource, sl._get_resources(self.settings_dict))
 
     def test_get_variables(self):
         expected_result = {

+ 3 - 3
Tests/test_singleton.py

@@ -1,15 +1,15 @@
 import unittest
+import sys
+from six import with_metaclass
 
 from kalliope.core.Models import Singleton
 
 
-class MyClass(object):
-    __metaclass__ = Singleton
+class MyClass(with_metaclass(Singleton, object)):
 
     def __init__(self):
         self.value = "test"
 
-
 class TestSingleton(unittest.TestCase):
 
     def setUp(self):

+ 125 - 0
Tests/test_synapse_launcher.py

@@ -0,0 +1,125 @@
+import unittest
+
+import mock
+
+from kalliope.core import LIFOBuffer
+from kalliope.core.Models import Brain
+from kalliope.core.Models.MatchedSynapse import MatchedSynapse
+from kalliope.core.Models.Settings import Settings
+from kalliope.core.SynapseLauncher import SynapseLauncher, SynapseNameNotFound
+
+from kalliope.core.Models import Neuron
+from kalliope.core.Models import Order
+from kalliope.core.Models import Synapse
+
+
+class TestSynapseLauncher(unittest.TestCase):
+    """
+    Test the class SynapseLauncher
+    """
+
+    def setUp(self):
+        # Init
+        neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
+        neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2'})
+        neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'})
+        neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'})
+
+        signal1 = Order(sentence="this is the sentence")
+        signal2 = Order(sentence="this is the second sentence")
+        signal3 = Order(sentence="that is part of the third sentence")
+
+        self.synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1])
+        self.synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2])
+        self.synapse3 = Synapse(name="Synapse3", neurons=[neuron2, neuron4], signals=[signal3])
+
+        self.all_synapse_list = [self.synapse1,
+                                 self.synapse2,
+                                 self.synapse3]
+
+        self.brain_test = Brain(synapses=self.all_synapse_list)
+        self.settings_test = Settings(default_synapse="Synapse3")
+
+        # clean the LiFO
+        LIFOBuffer.lifo_list = list()
+
+    def test_start_synapse_by_name(self):
+        # existing synapse in the brain
+        with mock.patch("kalliope.core.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]]
+            self.assertEqual(expected_result, LIFOBuffer.lifo_list)
+
+        # non existing synapse in the brain
+        with self.assertRaises(SynapseNameNotFound):
+            SynapseLauncher.start_synapse_by_name("not_existing", brain=self.brain_test)
+
+    def test_run_matching_synapse_from_order(self):
+        # ------------------
+        # test_match_synapse1
+        # ------------------
+        with mock.patch("kalliope.core.LIFOBuffer.execute"):
+            order_to_match = "this is the sentence"
+
+            should_be_created_matched_synapse = MatchedSynapse(matched_synapse=self.synapse1,
+                                                               user_order=order_to_match,
+                                                               matched_order="this is the sentence")
+            expected_result = [[should_be_created_matched_synapse]]
+            SynapseLauncher.run_matching_synapse_from_order(order_to_match,
+                                                            brain=self.brain_test,
+                                                            settings=self.settings_test)
+
+            self.assertEqual(expected_result, LIFOBuffer.lifo_list)
+
+        # -------------------------
+        # test_match_synapse1_and_2
+        # -------------------------
+        # clean LIFO
+        LIFOBuffer.lifo_list = list()
+        with mock.patch("kalliope.core.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,
+                                                                matched_order="this is the sentence")
+            should_be_created_matched_synapse2 = MatchedSynapse(matched_synapse=self.synapse2,
+                                                                user_order=order_to_match,
+                                                                matched_order="this is the second sentence")
+
+            expected_result = [[should_be_created_matched_synapse1, should_be_created_matched_synapse2]]
+            SynapseLauncher.run_matching_synapse_from_order(order_to_match,
+                                                            brain=self.brain_test,
+                                                            settings=self.settings_test)
+            self.assertEqual(expected_result, LIFOBuffer.lifo_list)
+
+        # -------------------------
+        # test_match_default_synapse
+        # -------------------------
+        # clean LIFO
+        LIFOBuffer.lifo_list = list()
+        with mock.patch("kalliope.core.LIFOBuffer.execute"):
+            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)
+            self.assertEqual(expected_result, LIFOBuffer.lifo_list)
+
+        # -------------------------
+        # test_no_match_and_no_default_synapse
+        # -------------------------
+        # clean LIFO
+        LIFOBuffer.lifo_list = list()
+        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)
+            self.assertEqual(expected_result, LIFOBuffer.lifo_list)

+ 47 - 0
Tests/test_trigger_launcher.py

@@ -0,0 +1,47 @@
+import unittest
+import mock
+
+from kalliope.core.Models.Settings import Settings
+from kalliope.core.TriggerLauncher import TriggerLauncher
+
+from kalliope.core.Models.Trigger import Trigger
+
+
+class TestTriggerLauncher(unittest.TestCase):
+    """
+    Class to test Launchers Classes (TriggerLauncher) and methods
+    """
+
+    def setUp(self):
+        pass
+
+    ####
+    # Trigger Launcher
+    def test_get_trigger(self):
+        """
+        Test the Trigger Launcher trying to run the trigger
+        """
+        trigger1 = Trigger("Trigger", {})
+        trigger2 = Trigger("Trigger2", {'pmdl_file': "trigger/snowboy/resources/kalliope-FR-6samples.pmdl"})
+        settings = Settings()
+        settings.triggers = [trigger1, trigger2]
+        with mock.patch("kalliope.core.Utils.get_dynamic_class_instantiation") as mock_get_class_instantiation:
+            # Get the trigger 1
+            settings.default_trigger_name = "Trigger"
+            TriggerLauncher.get_trigger(settings=settings,
+                                        callback=None)
+
+            mock_get_class_instantiation.assert_called_once_with(package_name="trigger",
+                                                                 module_name=trigger1.name,
+                                                                 parameters=trigger1.parameters)
+            mock_get_class_instantiation.reset_mock()
+
+            # Get the trigger 2
+            settings.default_trigger_name = "Trigger2"
+            TriggerLauncher.get_trigger(settings=settings,
+                                        callback=None)
+
+            mock_get_class_instantiation.assert_called_once_with(package_name="trigger",
+                                                                 module_name=trigger2.name,
+                                                                 parameters=trigger2.parameters)
+            mock_get_class_instantiation.reset_mock()

+ 6 - 6
Tests/test_tts_module.py

@@ -25,9 +25,9 @@ class TestTTSModule(unittest.TestCase):
         word = "kalliope"
         expected_result = "5c186d1e123be2667fb5fd54640e4fd0"
 
-        self.assertEquals(TTSModule.generate_md5_from_words(words=word),
-                          expected_result,
-                          "Fail md5")
+        self.assertEqual(TTSModule.generate_md5_from_words(words=word),
+                         expected_result,
+                         "Fail md5")
 
     def test_get_path_to_store_audio(self):
         """
@@ -40,9 +40,9 @@ class TestTTSModule(unittest.TestCase):
 
         expected_result = "/tmp/kalliope/tests/TTSModule/tests/default/5c186d1e123be2667fb5fd54640e4fd0.tts"
 
-        self.assertEquals(self.TTSMod._get_path_to_store_audio(),
-                          expected_result,
-                          "fail test_get_path_to_store_audio, expected path not corresponding to result")
+        self.assertEqual(self.TTSMod._get_path_to_store_audio(),
+                         expected_result,
+                         "fail test_get_path_to_store_audio, expected path not corresponding to result")
 
     def test_generate_and_play(self):
         """

+ 34 - 20
Tests/test_utils.py

@@ -4,6 +4,8 @@
 import unittest
 import os
 
+import sys
+
 from kalliope.core.Models.Neuron import Neuron
 from kalliope.neurons.say.say import Say
 from kalliope.core.Utils.Utils import Utils
@@ -26,9 +28,9 @@ class TestUtils(unittest.TestCase):
         path_to_test = "../kalliope/core/Utils"
         expected_result = os.path.normpath("../kalliope/core")
 
-        self.assertEquals(Utils.get_current_file_parent_path(path_to_test),
-                          expected_result,
-                          "fail getting the parent parent path from the given path")
+        self.assertEqual(Utils.get_current_file_parent_path(path_to_test),
+                         expected_result,
+                         "fail getting the parent parent path from the given path")
 
     def test_get_current_file_parent_parent_path(self):
         """
@@ -37,9 +39,9 @@ class TestUtils(unittest.TestCase):
         path_to_test = "../kalliope/core/Utils"
         expected_result = os.path.normpath("../kalliope")
 
-        self.assertEquals(Utils.get_current_file_parent_parent_path(path_to_test),
-                          expected_result,
-                          "fail getting the parent parent path from the given path")
+        self.assertEqual(Utils.get_current_file_parent_parent_path(path_to_test),
+                         expected_result,
+                         "fail getting the parent parent path from the given path")
 
     def test_get_real_file_path(self):
         """
@@ -53,7 +55,7 @@ class TestUtils(unittest.TestCase):
         # Test the absolute path
         dir_path = "/tmp/kalliope/tests/"
         file_name = "test_real_file_path"
-        absolute_path_to_test = os.path.join(dir_path,file_name)
+        absolute_path_to_test = os.path.join(dir_path, file_name)
         expected_result = absolute_path_to_test
         if not os.path.exists(dir_path):
             os.makedirs(dir_path)
@@ -61,9 +63,9 @@ class TestUtils(unittest.TestCase):
         # touch the file
         open(absolute_path_to_test, 'a').close()
 
-        self.assertEquals(Utils.get_real_file_path(absolute_path_to_test),
-                          expected_result,
-                          "Fail to match the given absolute path ")
+        self.assertEqual(Utils.get_real_file_path(absolute_path_to_test),
+                         expected_result,
+                         "Fail to match the given absolute path ")
         # Clean up
         if os.path.exists(absolute_path_to_test):
             os.remove(absolute_path_to_test)
@@ -76,9 +78,9 @@ class TestUtils(unittest.TestCase):
         # touch the file
         open(file_name, 'a').close()
 
-        self.assertEquals(Utils.get_real_file_path(file_name),
-                          expected_result,
-                          "Fail to match the Current path ")
+        self.assertEqual(Utils.get_real_file_path(file_name),
+                         expected_result,
+                         "Fail to match the Current path ")
         # Clean up
         if os.path.exists(file_name):
             os.remove(file_name)
@@ -108,19 +110,19 @@ class TestUtils(unittest.TestCase):
         dir_path = "../kalliope/"
         file_name = "test_real_file_path"
         path_to_test = os.path.join(dir_path, file_name)
-        expected_result = os.path.normpath(os.getcwd() + os.sep + os.pardir + os.sep +"kalliope" + os.sep + file_name)
+        expected_result = os.path.normpath(os.getcwd() + os.sep + os.pardir + os.sep + "kalliope" + os.sep + file_name)
         if not os.path.exists(dir_path):
             os.makedirs(dir_path)
 
         # touch the file
         open(path_to_test, 'a').close()
 
-        self.assertEquals(Utils.get_real_file_path(file_name),
-                          expected_result,
-                          "Fail to match the /an/unknown/path/kalliope path")
+        self.assertEqual(Utils.get_real_file_path(file_name),
+                         expected_result,
+                         "Fail to match the /an/unknown/path/kalliope path")
         # Clean up
-        if os.path.exists(file_name):
-            os.remove(file_name)
+        if os.path.exists(expected_result):
+            os.remove(expected_result)
 
     def test_get_dynamic_class_instantiation(self):
         """
@@ -222,4 +224,16 @@ class TestUtils(unittest.TestCase):
         expected_result = "This is the {{bracket}} {{second}}"
         self.assertEqual(Utils.remove_spaces_in_brackets(sentence=sentence),
                          expected_result,
-                         "Fail to remove spaces in two brackets")
+                         "Fail to remove spaces in two brackets")
+
+    def test_encode_text_utf8(self):
+        """
+        Test encoding the text in utf8
+        """
+        sentence = "kâllìöpé"
+        if sys.version_info[0] < 3:
+            sentence = sentence.decode('utf8')
+        expected_sentence = "kâllìöpé"
+
+        self.assertEqual(Utils.encode_text_utf8(text=sentence),
+                         expected_sentence)

+ 8 - 1
Tests/test_yaml_loader.py

@@ -14,7 +14,11 @@ class TestYAMLLoader(unittest.TestCase):
 
     def test_get_config(self):
 
-        valid_file_path_to_test = os.getcwd() + os.sep + "Tests/brains/brain_test.yml"
+        if "/Tests" in os.getcwd():
+            valid_file_path_to_test = os.getcwd() + os.sep + "brains/brain_test.yml"
+        else:
+            valid_file_path_to_test = os.getcwd() + os.sep + "Tests/brains/brain_test.yml"
+
         invalid_file_path = "brains/non_existing_brain.yml"
         expected_result = [
             {'signals': [{'order': 'test_order'}],
@@ -23,6 +27,9 @@ class TestYAMLLoader(unittest.TestCase):
             {'signals': [{'order': 'test_order_2'}],
              'neurons': [{'say': {'message': ['test message']}}],
              'name': 'test2'},
+            {'signals': [{'order': 'order_for_int'}],
+             'neurons': [{'sleep': {'seconds': 60}}],
+             'name': 'testint'},
             {'includes': ['included_brain_test.yml']},
             {'signals': [{'order': 'test_order_3'}],
              'neurons': [{'say': {'message': ['test message']}}],

+ 1 - 2
brain_examples/neurotransmitter.yml

@@ -77,8 +77,7 @@
           lang: "fr"
           temp_unit: "celsius"
           country: "FR"
-          args:
-            - location
+          location: "{{location}}"
           say_template:
           - "Aujourd'hui a {{ location }} le temps est {{ weather_today }} avec une température de {{ temp_today_temp }} degrés et demain le temps sera {{ weather_tomorrow }} avec une température de {{ temp_tomorrow_temp }} degrés"
 

+ 1 - 2
brain_examples/openweathermap.yml

@@ -8,8 +8,7 @@
           lang: "fr"
           temp_unit: "celsius"
           country: "FR"
-          args:
-            - location
+          location: "{{location}}"
           say_template:
           - "Aujourd'hui a {{ location }} le temps est {{ weather_today }} avec une température de {{ temp_today_temp }} degrés et demain le temps sera {{ weather_tomorrow }} avec une température de {{ temp_tomorrow_temp }} degrés"
 

+ 0 - 2
brain_examples/twitter.yml

@@ -11,5 +11,3 @@
           access_token_secret: ""
           say_template:
           - "voici le tweet {{ tweet }}"
-          args:
-            - tweet

+ 1 - 2
brain_examples/wikipedia.yml

@@ -7,6 +7,5 @@
       - wikipedia_searcher:
           language: "fr"
           sentences: 1
-          args:
-            - query
+          query: "{{query}}"
           file_template: "templates/wikipedia_returned_value.j2"

+ 36 - 0
docker/clone_and_test.sh

@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+
+# get travis env
+echo "TRAVIS_EVENT_TYPE: ${TRAVIS_EVENT_TYPE}"
+echo "TRAVIS_BRANCH: ${TRAVIS_BRANCH}"
+echo "TRAVIS_PULL_REQUEST_SLUG: ${TRAVIS_PULL_REQUEST_SLUG}"
+echo "TRAVIS_PULL_REQUEST_BRANCH: ${TRAVIS_PULL_REQUEST_BRANCH}"
+
+# set default variable if not in env
+if [ -z ${TRAVIS_EVENT_TYPE} ]; then
+TRAVIS_EVENT_TYPE="push";
+echo "TRAVIS_EVENT_TYPE set to push";
+fi
+
+if [ -z ${TRAVIS_BRANCH} ]; then
+TRAVIS_BRANCH="master";
+echo "TRAVIS_BRANCH set to master";
+fi
+
+# check if this is a pull request or a push
+if [ ${TRAVIS_EVENT_TYPE} == "pull_request" ]; then
+    git clone https://github.com/${TRAVIS_PULL_REQUEST_SLUG} kalliope;
+    cd kalliope;
+    git checkout ${TRAVIS_PULL_REQUEST_BRANCH};
+else
+    # it's a push
+    git clone https://github.com/kalliope-project/kalliope.git kalliope;
+    cd kalliope;
+    git checkout ${TRAVIS_BRANCH};
+fi
+
+# install
+sudo python setup.py install
+
+# tests
+python -m unittest discover

+ 41 - 0
docker/debian8.dockerfile

@@ -0,0 +1,41 @@
+FROM debian:8
+
+ENV no_proxy="127.0.0.1,localhost,kalliope.fr"
+
+# Need some packages in non-free repo
+RUN sed -i -- 's/jessie main/jessie main contrib non-free/g' /etc/apt/sources.list
+
+# install packages
+RUN apt-get update && apt-get install -y \
+    git python-dev libsmpeg0 libttspico-utils libsmpeg0 \
+    flac dialog portaudio19-dev build-essential libssl-dev \
+    libffi-dev sox libatlas3-base mplayer wget vim sudo\
+    && rm -rf /var/lib/apt/lists/*
+
+# Install the last PIP
+RUN wget https://bootstrap.pypa.io/get-pip.py
+RUN python get-pip.py
+
+# add a standart user. tests must not be ran as root
+RUN useradd -m -u 1000 tester
+RUN usermod -aG sudo tester
+RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
+
+# get TRAVIS environment variables
+ARG TRAVIS_BRANCH
+ARG TRAVIS_EVENT_TYPE
+ARG TRAVIS_PULL_REQUEST_SLUG
+ARG TRAVIS_PULL_REQUEST_BRANCH
+ENV TRAVIS_BRANCH=${TRAVIS_BRANCH}
+ENV TRAVIS_EVENT_TYPE=${TRAVIS_EVENT_TYPE}
+ENV TRAVIS_PULL_REQUEST_SLUG=${TRAVIS_PULL_REQUEST_SLUG}
+ENV TRAVIS_PULL_REQUEST_BRANCH=${TRAVIS_PULL_REQUEST_BRANCH}
+
+ADD docker/clone_and_test.sh /home/tester/clone_and_test.sh
+RUN chown tester /home/tester/clone_and_test.sh
+
+USER tester
+WORKDIR /home/tester
+
+# run tests
+CMD ./clone_and_test.sh

+ 51 - 0
docker/docker.md

@@ -0,0 +1,51 @@
+# Docker deployment and tests
+
+## Create images
+
+### Ubuntu 16.04
+Build the image for Ubuntu 16.04. By default, the master branch of the Kalliope project will be cloned.
+```
+docker build --force-rm=true -t kalliope-ubuntu1604 -f docker/ubuntu_16_04.dockerfile .
+```
+
+To build with TRAVIS env we need to send global variables
+```
+docker build \
+--force-rm=true \
+--build-arg TRAVIS_BRANCH=${TRAVIS_BRANCH} \
+--build-arg TRAVIS_EVENT_TYPE=${TRAVIS_EVENT_TYPE} \
+--build-arg TRAVIS_PULL_REQUEST_SLUG=${TRAVIS_PULL_REQUEST_SLUG} \
+--build-arg TRAVIS_PULL_REQUEST_BRANCH=${TRAVIS_PULL_REQUEST_BRANCH} \
+-t kalliope-ubuntu1604 \
+-f docker/ubuntu_16_04.dockerfile .
+```
+
+### Debian Jessie
+Build the image for Debian Jessie. By default, the master branch of the Kalliope project will be cloned.
+```
+docker build --force-rm=true -t kalliope-debian8 -f docker/debian8.dockerfile .
+```
+
+To build with TRAVIS env we need to send global variables
+```
+docker build \
+--force-rm=true \
+--build-arg TRAVIS_BRANCH=${TRAVIS_BRANCH} \
+--build-arg TRAVIS_EVENT_TYPE=${TRAVIS_EVENT_TYPE} \
+--build-arg TRAVIS_PULL_REQUEST_SLUG=${TRAVIS_PULL_REQUEST_SLUG} \
+--build-arg TRAVIS_PULL_REQUEST_BRANCH=${TRAVIS_PULL_REQUEST_BRANCH} \
+-t kalliope-debian8 \
+-f docker/debian8.dockerfile .
+```
+
+## Run the test
+
+Ubuntu image
+```
+docker run -it --rm kalliope-ubuntu1604
+```
+
+Debian image
+```
+docker run -it --rm kalliope-debian8
+```

+ 41 - 0
docker/ubuntu_16_04.dockerfile

@@ -0,0 +1,41 @@
+FROM ubuntu:16.04
+
+ENV no_proxy="127.0.0.1,localhost,kalliope.fr"
+
+# pico2wav is a multiverse package
+RUN echo "deb http://us.archive.ubuntu.com/ubuntu/ xenial multiverse" >> /etc/apt/sources.list
+
+# install packages
+RUN apt-get update && apt-get install -y \
+    git python-dev libsmpeg0 libttspico-utils libsmpeg0 flac dialog \
+    libffi-dev libffi-dev libssl-dev portaudio19-dev build-essential \
+    sox libatlas3-base mplayer wget vim sudo\
+    && rm -rf /var/lib/apt/lists/*
+
+# Install the last PIP
+RUN wget https://bootstrap.pypa.io/get-pip.py
+RUN python get-pip.py
+
+# add a standart user. tests must not be ran as root
+RUN useradd -m -u 1000 tester
+RUN usermod -aG sudo tester
+RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
+
+# get TRAVIS environment variables
+ARG TRAVIS_BRANCH
+ARG TRAVIS_EVENT_TYPE
+ARG TRAVIS_PULL_REQUEST_SLUG
+ARG TRAVIS_PULL_REQUEST_BRANCH
+ENV TRAVIS_BRANCH=${TRAVIS_BRANCH}
+ENV TRAVIS_EVENT_TYPE=${TRAVIS_EVENT_TYPE}
+ENV TRAVIS_PULL_REQUEST_SLUG=${TRAVIS_PULL_REQUEST_SLUG}
+ENV TRAVIS_PULL_REQUEST_BRANCH=${TRAVIS_PULL_REQUEST_BRANCH}
+
+ADD docker/clone_and_test.sh /home/tester/clone_and_test.sh
+RUN chown tester /home/tester/clone_and_test.sh
+
+USER tester
+WORKDIR /home/tester
+
+# run tests
+CMD ./clone_and_test.sh

BIN
images/led_kalliope_circuit.png


+ 1 - 0
install/files/deb-packages_requirements.txt

@@ -20,3 +20,4 @@ build-essential
 sox
 libatlas3-base
 mplayer
+libav-tools

+ 6 - 1
install/files/python_requirements.txt

@@ -4,7 +4,7 @@ markupsafe==0.23
 pyaudio==0.2.9
 pyasn1>=0.1.8
 ansible==2.2.0.0
-python2-pythondialog==3.4.0
+#python2-pythondialog==3.4.0
 jinja2==2.8
 cffi==1.9.1
 ipaddress==1.0.17
@@ -19,3 +19,8 @@ apscheduler==3.3.0
 GitPython==2.1.1
 packaging>=16.8
 transitions>=0.4.3
+sounddevice>=0.3.7
+SoundFile>=0.9.0
+pyalsaaudio>=0.8.4
+RPi.GPIO>=0.6.3
+

+ 0 - 78
install/install.yml

@@ -1,78 +0,0 @@
----
-#- hosts: localhost
-#  gather_facts: no
-#  connection: local
-#  vars:
-#    - git_url: "git@github.com:kalliope-project/kalliope.git"
-#    - temp_local_repo: "/tmp/kalliope_repo"
-#
-#  tasks:
-#    - name: clone project in /tmp as standart user
-#      git:
-#        repo: "{{ git_url }}"
-#        dest: /tmp/kalliope_repo
-#        accept_hostkey: yes
-
-
-- hosts: localhost
-  gather_facts: no
-  connection: local
-  become: True
-  vars:
-    - git_url: "git@github.com:kalliope-project/kalliope.git"
-    - temp_local_repo: "/tmp/kalliope_repo"
-    - install_path: "/usr/lib/kalliope"
-  tasks:
-    - name: Install packages
-      apt: name={{ item }} update_cache=yes
-      with_items:
-        - python-pip
-        - python-dev
-        - libsmpeg0
-        - libttspico-utils
-        - libsmpeg0
-        - flac
-        - dialog
-        - libffi-dev
-        - libssl-dev
-        - portaudio19-dev
-        - build-essential
-        - sox
-        - libatlas3-base
-        - mplayer
-
-    - name: Copy requirement
-      copy:
-        src: python_requirements.txt
-        dest: /tmp/requirements.txt
-
-    - name: Upgrade pip
-      pip:
-        name: pip
-        state: latest
-
-    - name: Install python libs
-      pip:
-        requirements=/tmp/requirements.txt
-
-#    - name: Check that the repo has been cloned
-#      stat:
-#        path: "{{ temp_local_repo }}"
-#      register: git_repo_stat
-#
-#    - name: Remove old version if exist
-#      file:
-#        path: "{{ install_path }}"
-#        state: absent
-#
-#    - name: Move repo in /usr/lib
-#      command: mv "{{ temp_local_repo }}" "{{ install_path }}"
-#      when: git_repo_stat.stat.exists
-#
-#    - name: Add entry in /usr/bin
-#      file:
-#        src: "{{ install_path }}/kalliope.py"
-#        dest: /usr/bin/kalliope
-#        state: link
-#        force: True
-

+ 0 - 15
install/install_kalliope.sh

@@ -1,15 +0,0 @@
-#!/usr/bin/env bash
-
-# install packages
-sudo apt-get install -y python-pip libssl-dev
-
-# this is used to help the RPI
-sudo apt-get install -y libportaudio0 libportaudio2 libportaudiocpp0 portaudio19-dev
-sudo apt-get install -y libffi-dev python-yaml python-pycparser python-paramiko python-markupsafe apt-transport-https
-
-# install ansible
-sudo pip install ansible==2.1.1.0
-
-# Install the project
-cd kalliope/install
-ansible-playbook install.yml -K

+ 47 - 0
install/rpi_install_kalliope.sh

@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+
+# This script will install automatically everything needed for Kalliope
+# usage: ./rpi_install_kalliope.sh [<branch_name>]
+# E.g: ./rpi_install_kalliope.sh dev
+# If no branch are set, the master branch will be installed
+
+# name of the branch to install
+branch="master"
+
+# get the branch name to install from passed arguments if exist
+if [ $# -eq 0 ]
+  then
+    echo "No arguments supplied. Master branch will be installed"
+else
+    branch=$1
+    echo "Selected branch name to install: ${branch}"
+fi
+
+echo "Installing python pip..."
+wget https://bootstrap.pypa.io/get-pip.py
+sudo python get-pip.py
+echo "Installing python pip... [OK]"
+
+# install packages
+echo "Installing system packages..."
+sudo apt-get update
+sudo apt-get install -y git python-dev libsmpeg0 libttspico-utils libsmpeg0 \
+flac dialog libffi-dev libffi-dev libssl-dev portaudio19-dev build-essential \
+libssl-dev libffi-dev sox libatlas3-base mplayer libyaml-dev libpython2.7-dev pulseaudio pulseaudio-utils libav-tools
+
+# this is used to help the RPI
+sudo apt-get install -y libportaudio0 libportaudio2 libportaudiocpp0 portaudio19-dev
+sudo apt-get install -y libffi-dev python-yaml python-pycparser python-paramiko python-markupsafe apt-transport-https
+echo "Installing system packages...[OK]"
+
+echo "Cloning the project"
+# clone the project
+git clone https://github.com/kalliope-project/kalliope.git
+echo "Cloning the project...[OK]"
+
+# Install the project
+echo "Installing Kalliope..."
+cd kalliope
+git checkout ${branch}
+sudo python setup.py install
+echo "Installing Kalliope...[OK]"

+ 70 - 34
kalliope/__init__.py

@@ -5,11 +5,13 @@ import logging
 
 from kalliope.core import ShellGui
 from kalliope.core import Utils
+from kalliope.core.ConfigurationManager import SettingLoader
 from kalliope.core.ConfigurationManager.BrainLoader import BrainLoader
 from kalliope.core.EventManager import EventManager
 from kalliope.core.MainController import MainController
+from kalliope.core.Utils.RpiUtils import RpiUtils
 
-from _version import version_str
+from ._version import version_str
 import signal
 import sys
 
@@ -29,7 +31,7 @@ def signal_handler(signal, frame):
     :param frame: execution frame
 
     """
-    print "\n"
+    print("\n")
     Utils.print_info("Ctrl+C pressed. Killing Kalliope")
     sys.exit(0)
 
@@ -37,9 +39,12 @@ def signal_handler(signal, frame):
 ACTION_LIST = ["start", "gui", "install", "uninstall"]
 
 
-def main():
-    """Entry point of Kalliope program."""
-
+def parse_args(args):
+    """
+    Parsing function
+    :param args: arguments passed from the command line
+    :return: return parser
+    """
     # create arguments
     parser = argparse.ArgumentParser(description='Kalliope')
     parser.add_argument("action", help="[start|gui|install|uninstall]")
@@ -58,69 +63,87 @@ def main():
                         version='Kalliope ' + version_str)
 
     # parse arguments from script parameters
-    args = parser.parse_args()
+    return parser.parse_args(args)
+
 
-    # require at least one parameter, the action
-    if len(sys.argv[1:]) == 0:
-        parser.print_usage()
+def main():
+    """Entry point of Kalliope program."""
+    # parse argument. the script name is removed
+    try:
+        parser = parse_args(sys.argv[1:])
+    except SystemExit:
         sys.exit(1)
 
     # check if we want debug
-    configure_logging(debug=args.debug)
+    configure_logging(debug=parser.debug)
 
-    logger.debug("kalliope args: %s" % args)
+    logger.debug("kalliope args: %s" % parser)
 
     # by default, no brain file is set.
     # Use the default one: brain.yml in the root path
     brain_file = None
 
     # check if user set a brain.yml file
-    if args.brain_file:
-        brain_file = args.brain_file
+    if parser.brain_file:
+        brain_file = parser.brain_file
 
     # check the user provide a valid action
-    if args.action not in ACTION_LIST:
-        Utils.print_warning("%s is not a recognised action\n" % args.action)
-        parser.print_help()
+    if parser.action not in ACTION_LIST:
+        Utils.print_warning("%s is not a recognised action\n" % parser.action)
+        sys.exit(1)
 
     # install modules
-    if args.action == "install":
-        if not args.git_url:
+    if parser.action == "install":
+        if not parser.git_url:
             Utils.print_danger("You must specify the git url")
+            sys.exit(1)
         else:
             parameters = {
-                "git_url": args.git_url
+                "git_url": parser.git_url
             }
             res_manager = ResourcesManager(**parameters)
             res_manager.install()
         return
 
     # uninstall modules
-    if args.action == "uninstall":
-        if not args.neuron_name and not args.stt_name and not args.tts_name and not args.trigger_name:
+    if parser.action == "uninstall":
+        if not parser.neuron_name and not parser.stt_name and not parser.tts_name and not parser.trigger_name:
             Utils.print_danger("You must specify a module name with --neuron-name or --stt-name or --tts-name "
                                "or --trigger-name")
+            sys.exit(1)
         else:
             res_manager = ResourcesManager()
-            res_manager.uninstall(neuron_name=args.neuron_name, stt_name=args.stt_name,
-                                  tts_name=args.tts_name, trigger_name=args.trigger_name)
+            res_manager.uninstall(neuron_name=parser.neuron_name, stt_name=parser.stt_name,
+                                  tts_name=parser.tts_name, trigger_name=parser.trigger_name)
         return
 
     # load the brain once
     brain_loader = BrainLoader(file_path=brain_file)
     brain = brain_loader.brain
 
-    if args.action == "start":
+    # load settings
+    # get global configuration once
+    settings_loader = SettingLoader()
+    settings = settings_loader.settings
+
+    if parser.action == "start":
+
+        if settings.rpi_settings:
+            # init GPIO once
+            RpiUtils(settings.rpi_settings)
 
         # user set a synapse to start
-        if args.run_synapse is not None:
-            SynapseLauncher.start_synapse(args.run_synapse, brain=brain)
+        if parser.run_synapse is not None:
+            SynapseLauncher.start_synapse_by_name(parser.run_synapse,
+                                                  brain=brain)
 
-        if args.run_order is not None:
-            order_analyser = OrderAnalyser(args.run_order, brain=brain)
-            order_analyser.start()
+        if parser.run_order is not None:
+            SynapseLauncher.run_matching_synapse_from_order(parser.run_order,
+                                                            brain=brain,
+                                                            settings=settings,
+                                                            is_api_call=False)
 
-        if (args.run_synapse is None) and (args.run_order is None):
+        if (parser.run_synapse is None) and (parser.run_order is None):
             # first, load events in event manager
             EventManager(brain.synapses)
             Utils.print_success("Events loaded")
@@ -130,10 +153,23 @@ def main():
             # catch signal for killing on Ctrl+C pressed
             signal.signal(signal.SIGINT, signal_handler)
             # start the state machine
-            MainController(brain=brain)
-
-    if args.action == "gui":
-        ShellGui(brain=brain)
+            try:
+                MainController(brain=brain)
+            except (KeyboardInterrupt, SystemExit):
+                Utils.print_info("Ctrl+C pressed. Killing Kalliope")
+            finally:
+                # we need to switch GPIO pin to default status if we are using a Rpi
+                if settings.rpi_settings:
+                    logger.debug("Clean GPIO")
+                    import RPi.GPIO as GPIO
+                    GPIO.cleanup()
+
+    if parser.action == "gui":
+        try:
+            ShellGui(brain=brain)
+        except (KeyboardInterrupt, SystemExit):
+            Utils.print_info("Ctrl+C pressed. Killing Kalliope")
+            sys.exit(0)
 
 
 def configure_logging(debug=None):

+ 1 - 1
kalliope/_version.py

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

+ 19 - 21
kalliope/core/ConfigurationManager/BrainLoader.py

@@ -1,8 +1,10 @@
 import inspect
 import logging
 import os
+from six import with_metaclass
+import six
 
-from YAMLLoader import YAMLLoader
+from .YAMLLoader import YAMLLoader
 from kalliope.core.Utils import Utils
 from kalliope.core.ConfigurationManager import SettingLoader
 from kalliope.core.ConfigurationManager.ConfigurationChecker import ConfigurationChecker
@@ -23,11 +25,10 @@ class BrainNotFound(Exception):
     pass
 
 
-class BrainLoader(object):
+class BrainLoader(with_metaclass(Singleton, object)):
     """
     This Class is used to get the brain YAML and the Brain as an object
     """
-    __metaclass__ = Singleton
 
     def __init__(self, file_path=None):
         sl = SettingLoader()
@@ -87,7 +88,6 @@ class BrainLoader(object):
         for synapses_dict in dict_brain:
             if "includes" not in synapses_dict:     # we don't need to check includes as it's not a synapse
                 if ConfigurationChecker().check_synape_dict(synapses_dict):
-                    # print "synapses_dict ok"
                     name = synapses_dict["name"]
                     neurons = self._get_neurons(synapses_dict["neurons"], self.settings)
                     signals = self._get_signals(synapses_dict["signals"])
@@ -127,7 +127,6 @@ class BrainLoader(object):
         for neuron_dict in neurons_dict:
             if isinstance(neuron_dict, dict):
                 if ConfigurationChecker().check_neuron_dict(neuron_dict):
-                    # print "Neurons dict ok"
                     for neuron_name in neuron_dict:
 
                         name = neuron_name
@@ -164,11 +163,9 @@ class BrainLoader(object):
         .. seealso:: Event, Order
         .. warnings:: Class method and Private
         """
-        # print signals_dict
         signals = list()
         for signal_dict in signals_dict:
             if ConfigurationChecker().check_signal_dict(signal_dict):
-                # print "Signals dict ok"
                 event_or_order = cls._get_event_or_order_from_dict(signal_dict)
                 signals.append(event_or_order)
 
@@ -192,7 +189,6 @@ class BrainLoader(object):
         .. warnings:: Static method and Private
         """
         if 'event' in signal_or_event_dict:
-            # print "is event"
             event = signal_or_event_dict["event"]
             if ConfigurationChecker.check_event_dict(event):
                 return cls._get_event_object(event)
@@ -255,23 +251,20 @@ class BrainLoader(object):
         :param settings: the settings
         :return: the parameter dict
         """
-
-        if isinstance(parameter, dict):
-            # print "parameter is dict %s" % str(parameter)
-            for key, value in parameter.iteritems():
-                parameter[key] = cls._replace_global_variables(value, settings=settings)
-            return parameter
+        if isinstance(parameter, str) \
+                or isinstance(parameter, six.text_type) \
+                or isinstance(parameter, int):
+            if Utils.is_containing_bracket(parameter):
+                return cls._get_global_variable(sentence=parameter, settings=settings)
         if isinstance(parameter, list):
-            # print "parameter is list %s" % str(parameter)
             new_parameter_list = list()
             for el in parameter:
                 new_parameter_list.append(cls._replace_global_variables(el, settings=settings))
             return new_parameter_list
-        if isinstance(parameter, str) or isinstance(parameter, unicode):
-            # print "parameter is string %s" % parameter
-            if Utils.is_containing_bracket(parameter):
-                return cls._get_global_variable(sentence=parameter, settings=settings)
-            return parameter
+        if isinstance(parameter, dict):
+            for key, value in parameter.items():
+                parameter[key] = cls._replace_global_variables(value, settings=settings)
+        return parameter
 
     @staticmethod
     def _get_global_variable(sentence, settings):
@@ -287,6 +280,11 @@ class BrainLoader(object):
             if param_no_brackets in settings.variables:
                 logger.debug("Replacing variable %s with  %s" % (param_with_bracket,
                                                                  settings.variables[param_no_brackets]))
+
+                # need to check if the variable is an integer
+                variable = settings.variables[param_no_brackets]
+                if isinstance(variable, int):
+                    variable = str(variable)
                 sentence_no_spaces = sentence_no_spaces.replace(param_with_bracket,
-                                                                str(settings.variables[param_no_brackets]))
+                                                                variable)
         return sentence_no_spaces

+ 1 - 1
kalliope/core/ConfigurationManager/ConfigurationChecker.py

@@ -288,6 +288,6 @@ class ConfigurationChecker:
             synapse_name = synapse.name.encode('utf-8')
             if synapse_name in seen:
                 raise MultipleSameSynapseName("Multiple synapse found with the same name: %s" % synapse_name)
-            seen.add(synapse.name)
+            seen.add(synapse_name)
 
         return True

+ 115 - 20
kalliope/core/ConfigurationManager/SettingLoader.py

@@ -1,7 +1,9 @@
 import logging
 import os
+from six import with_metaclass
 
-from YAMLLoader import YAMLLoader
+from kalliope.core.Models.RpiSettings import RpiSettings
+from .YAMLLoader import YAMLLoader
 from kalliope.core.Models.Resources import Resources
 from kalliope.core.Utils.Utils import Utils
 from kalliope.core.Models import Singleton
@@ -9,6 +11,7 @@ from kalliope.core.Models.RestAPI import RestAPI
 from kalliope.core.Models.Settings import Settings
 from kalliope.core.Models.Stt import Stt
 from kalliope.core.Models.Trigger import Trigger
+from kalliope.core.Models.Player import Player
 from kalliope.core.Models.Tts import Tts
 from kalliope.core.Utils.FileManager import FileManager
 
@@ -45,11 +48,10 @@ class SettingNotFound(Exception):
     pass
 
 
-class SettingLoader(object):
+class SettingLoader(with_metaclass(Singleton, object)):
     """
     This Class is used to get the Settings YAML and the Settings as an object
     """
-    __metaclass__ = Singleton
 
     def __init__(self, file_path=None):
         self.file_path = file_path
@@ -100,9 +102,11 @@ class SettingLoader(object):
         default_stt_name = self._get_default_speech_to_text(settings)
         default_tts_name = self._get_default_text_to_speech(settings)
         default_trigger_name = self._get_default_trigger(settings)
+        default_player_name = self._get_default_player(settings)
         stts = self._get_stts(settings)
         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)
@@ -113,14 +117,17 @@ class SettingLoader(object):
         default_synapse = self._get_default_synapse(settings)
         resources = self._get_resources(settings)
         variables = self._get_variables(settings)
+        rpi_settings = self._get_rpi_settings(settings)
 
         # Load the setting singleton with the parameters
         setting_object.default_tts_name = default_tts_name
         setting_object.default_stt_name = default_stt_name
         setting_object.default_trigger_name = default_trigger_name
+        setting_object.default_player_name = default_player_name
         setting_object.stts = stts
         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
@@ -131,6 +138,7 @@ class SettingLoader(object):
         setting_object.default_synapse = default_synapse
         setting_object.resources = resources
         setting_object.variables = variables
+        setting_object.rpi_settings = rpi_settings
 
         return setting_object
 
@@ -159,7 +167,7 @@ class SettingLoader(object):
                 raise NullSettingException("Attribute default_speech_to_text is null")
             logger.debug("Default STT: %s" % default_speech_to_text)
             return default_speech_to_text
-        except KeyError, e:
+        except KeyError as e:
             raise SettingNotFound("%s setting not found" % e)
 
     @staticmethod
@@ -187,7 +195,7 @@ class SettingLoader(object):
                 raise NullSettingException("Attribute default_text_to_speech is null")
             logger.debug("Default TTS: %s" % default_text_to_speech)
             return default_text_to_speech
-        except KeyError, e:
+        except KeyError as e:
             raise SettingNotFound("%s setting not found" % e)
 
     @staticmethod
@@ -214,7 +222,34 @@ class SettingLoader(object):
                 raise NullSettingException("Attribute default_trigger is null")
             logger.debug("Default Trigger name: %s" % default_trigger)
             return default_trigger
-        except KeyError, e:
+        except KeyError as e:
+            raise SettingNotFound("%s setting not found" % e)
+
+    @staticmethod
+    def _get_default_player(settings):
+        """
+        Get the default player defined in the settings.yml file
+        :param settings: The YAML settings file
+        :type settings: dict
+        :return: the default player
+        :rtype: str
+
+        :Example:
+
+            default_player_name = cls._get_default_player(settings)
+
+        .. seealso:: Player
+        .. raises:: NullSettingException, SettingNotFound
+        .. warnings:: Static and Private
+        """
+
+        try:
+            default_player = settings["default_player"]
+            if default_player is None:
+                raise NullSettingException("Attribute default_player is null")
+            logger.debug("Default Player name: %s" % default_player)
+            return default_player
+        except KeyError as e:
             raise SettingNotFound("%s setting not found" % e)
 
     @staticmethod
@@ -233,7 +268,7 @@ class SettingLoader(object):
 
         .. seealso:: Stt
         .. raises:: SettingNotFound
-        .. warnings:: Class Method and Private
+        .. warnings:: Static Method and Private
         """
 
         try:
@@ -244,7 +279,6 @@ class SettingLoader(object):
         stts = list()
         for speechs_to_text_el in speechs_to_text_list:
             if isinstance(speechs_to_text_el, dict):
-                # print "Neurons dict ok"
                 for stt_name in speechs_to_text_el:
                     name = stt_name
                     parameters = speechs_to_text_el[name]
@@ -273,18 +307,17 @@ class SettingLoader(object):
 
         .. seealso:: Tts
         .. raises:: SettingNotFound
-        .. warnings:: Class Method and Private
+        .. warnings:: Static Method and Private
         """
 
         try:
             text_to_speech_list = settings["text_to_speech"]
-        except KeyError, e:
+        except KeyError as e:
             raise SettingNotFound("%s setting not found" % e)
 
         ttss = list()
         for text_to_speech_el in text_to_speech_list:
             if isinstance(text_to_speech_el, dict):
-                # print "Neurons dict ok"
                 for tts_name in text_to_speech_el:
                     name = tts_name
                     parameters = text_to_speech_el[name]
@@ -312,18 +345,17 @@ class SettingLoader(object):
 
         .. seealso:: Trigger
         .. raises:: SettingNotFound
-        .. warnings:: Class Method and Private
+        .. warnings:: Static Method and Private
         """
 
         try:
             triggers_list = settings["triggers"]
-        except KeyError, e:
+        except KeyError as e:
             raise SettingNotFound("%s setting not found" % e)
 
         triggers = list()
         for trigger_el in triggers_list:
             if isinstance(trigger_el, dict):
-                # print "Neurons dict ok"
                 for trigger_name in trigger_el:
                     name = trigger_name
                     parameters = trigger_el[name]
@@ -335,6 +367,44 @@ class SettingLoader(object):
                 triggers.append(new_trigger)
         return triggers
 
+    @staticmethod
+    def _get_players(settings):
+        """
+        Return a list of Player object
+
+        :param settings: The YAML settings file
+        :type settings: dict
+        :return: List of Player
+        :rtype: list
+
+        :Example:
+
+            players = cls._get_players(settings)
+
+        .. seealso:: players
+        .. raises:: SettingNotFound
+        .. warnings:: Static Method and Private
+        """
+
+        try:
+            players_list = settings["players"]
+        except KeyError as e:
+            raise SettingNotFound("%s setting not found" % e)
+
+        players = list()
+        for player_el in players_list:
+            if isinstance(player_el, dict):
+                for player_name in player_el:
+                    name = player_name
+                    parameters = player_el[name]
+                    new_player = Player(name=name, parameters=parameters)
+                    players.append(new_player)
+            else:
+                # the player does not have parameters
+                new_player = Player(name=player_el)
+                players.append(new_player)
+        return players
+
     @staticmethod
     def _get_random_wake_up_answers(settings):
         """
@@ -422,7 +492,7 @@ class SettingLoader(object):
 
         try:
             rest_api = settings["rest_api"]
-        except KeyError, e:
+        except KeyError as e:
             raise SettingNotFound("%s setting not found" % e)
 
         if rest_api is not None:
@@ -455,10 +525,9 @@ class SettingLoader(object):
                 # check the CORS request settings
                 allowed_cors_origin = False
                 if "allowed_cors_origin" in rest_api:
-                     allowed_cors_origin = rest_api["allowed_cors_origin"]
+                    allowed_cors_origin = rest_api["allowed_cors_origin"]
 
-            except KeyError, e:
-                # print e
+            except KeyError as e:
                 raise SettingNotFound("%s settings not found" % e)
 
             # config ok, we can return the rest api object
@@ -489,7 +558,7 @@ class SettingLoader(object):
 
         try:
             cache_path = settings["cache_path"]
-        except KeyError, e:
+        except KeyError as e:
             raise SettingNotFound("%s setting not found" % e)
 
         if cache_path is None:
@@ -608,7 +677,7 @@ class SettingLoader(object):
         return play_on_ready_notification
 
     @staticmethod
-    def _get_on_ready_answers( settings):
+    def _get_on_ready_answers(settings):
         """
         Return the list of on_ready_answers string from the settings.
         :param settings: The YAML settings file
@@ -666,4 +735,30 @@ class SettingLoader(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

+ 4 - 2
kalliope/core/ConfigurationManager/YAMLLoader.py

@@ -72,7 +72,8 @@ class IncludeImport(object):
         parent_dir = os.path.normpath(file_path + os.sep + os.pardir)
 
         # load the yaml file
-        self.data = yaml.load(open(file_path, 'r'))
+        with open(file_path, "r") as f:
+            self.data = yaml.load(f)
 
         if self.data is None:
             raise YAMLFileEmpty("File %s is empty" % file_path)
@@ -86,7 +87,8 @@ class IncludeImport(object):
                             # logger.debug("File path %s is relative, adding the root path" % inc)
                             inc = os.path.join(parent_dir, inc)
                             # logger.debug("New path: %s" % inc)
-                        self.update(yaml.load(open(inc)))
+                        with open(inc, "r") as f:
+                            self.update(yaml.load(f))
 
     def get_data(self):
         """

+ 3 - 3
kalliope/core/ConfigurationManager/__init__.py

@@ -1,3 +1,3 @@
-from YAMLLoader import YAMLLoader
-from SettingLoader import SettingLoader
-from BrainLoader import BrainLoader
+from .YAMLLoader import YAMLLoader
+from .SettingLoader import SettingLoader
+from .BrainLoader import BrainLoader

+ 1 - 1
kalliope/core/EventManager.py

@@ -46,4 +46,4 @@ class EventManager(object):
         # get a brain
         brain_loader = BrainLoader()
         brain = brain_loader.brain
-        SynapseLauncher.start_synapse(synapse_name, brain=brain)
+        SynapseLauncher.start_synapse_by_name(synapse_name, brain=brain)

+ 196 - 0
kalliope/core/LIFOBuffer.py

@@ -0,0 +1,196 @@
+import logging
+
+from kalliope.core.NeuronLauncher import NeuronLauncher
+from kalliope.core.Models import Singleton
+from kalliope.core.Models.APIResponse import APIResponse
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+
+class Serialize(Exception):
+    """
+    When raised, the LIFO class return the current API response to the caller
+    """
+    pass
+
+
+class SynapseListAddedToLIFO(Exception):
+    """
+    When raised, a synapse list to process has been added to the LIFO list. 
+    The LIFO must start over and process the last synapse list added
+    """
+    pass
+
+
+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.
+    This design is needed in order to use Kalliope from the API. 
+    Because we want to return an information when a Neuron is still processing and waiting for an answer from the user
+    like with the Neurotransmitter neuron.
+    
+    """
+    __metaclass__ = Singleton
+
+    api_response = APIResponse()
+    lifo_list = list()
+    logger.debug("[LIFOBuffer] LIFO buffer created")
+    answer = None
+    is_api_call = False
+
+    @classmethod
+    def set_answer(cls, value):
+        cls.answer = value
+
+    @classmethod
+    def set_api_call(cls, value):
+        cls.is_api_call = value
+
+    @classmethod
+    def add_synapse_list_to_lifo(cls, matched_synapse_list):
+        """
+        Add a synapse list to process to the lifo
+        :param matched_synapse_list: List of Matched Synapse
+        :return: 
+        """
+        logger.debug("[LIFOBuffer] Add a new synapse list to process to the LIFO")
+        cls.lifo_list.append(matched_synapse_list)
+
+    @classmethod
+    def clean(cls):
+        """
+        Clean the LIFO by creating a new list
+        """
+        cls.lifo_list = list()
+        cls.api_response = APIResponse()
+
+    @classmethod
+    def _return_serialized_api_response(cls):
+        """
+        Serialize Exception has been raised by the execute process somewhere, return the serialized API response
+        to the caller. Clean up the APIResponse object for the next call
+        :return: 
+        """
+        # we prepare a json response
+        returned_api_response = cls.api_response.serialize()
+        # we clean up the API response object for the next call
+        cls.api_response = APIResponse()
+        return returned_api_response
+
+    @classmethod
+    def execute(cls, answer=None, is_api_call=False):
+        """
+        Process the LIFO list.
+        
+        The LIFO list contains multiple list of matched synapses.        
+        For each list of matched synapse we process synapses inside        
+        For each synapses we process neurons.        
+        If a neuron add a Synapse list to the lifo, this synapse list is processed before executing the first list 
+        in which we were in.
+        
+        :param answer: String answer to give the the last neuron which whas waiting for an answer
+        :param is_api_call: Boolean passed to all neuron in order to let them know if the current call comes from API
+        :return: serialized APIResponse object
+        """
+        # store the answer if present
+        cls.answer = answer
+        cls.is_api_call = is_api_call
+
+        try:
+            # we keep looping over the LIFO til we have synapse list to process in it
+            while cls.lifo_list:
+                logger.debug("[LIFOBuffer] number of synapse list to process: %s" % len(cls.lifo_list))
+                try:
+                    # get the last list of matched synapse in the LIFO
+                    last_synapse_fifo_list = cls.lifo_list[-1]
+                    cls._process_synapse_list(last_synapse_fifo_list)
+                except SynapseListAddedToLIFO:
+                    continue
+                # remove the synapse list from the LIFO
+                cls.lifo_list.remove(last_synapse_fifo_list)
+            raise Serialize
+
+        except Serialize:
+            return cls._return_serialized_api_response()
+
+    @classmethod
+    def _process_synapse_list(cls, synapse_list):
+        """
+        Process a list of matched synapse.
+        Execute each neuron list for each synapse.
+        Add info in the API response object after each processed synapse
+        Remove the synapse from the synapse_list when it has been fully executed
+        :param synapse_list: List of MatchedSynapse
+        """
+        # we keep processing til we have synapse in the FIFO to process
+        while synapse_list:
+            # get the first matched synapse in the list
+            matched_synapse = synapse_list[0]
+            # add the synapse to the API response so the user will get the status if the synapse was not already
+            # in the response
+            if matched_synapse not in cls.api_response.list_processed_matched_synapse:
+                cls.api_response.list_processed_matched_synapse.append(matched_synapse)
+
+            cls._process_neuron_list(matched_synapse=matched_synapse)
+
+            # The synapse has been processed we can remove it from the list.
+            synapse_list.remove(matched_synapse)
+
+    @classmethod
+    def _process_neuron_list(cls, matched_synapse):
+        """
+        Process the neuron list of the matched_synapse
+        Execute the Neuron
+        Executing a Neuron creates a NeuronModule object. This one can have 3 status:
+        - waiting for an answer: The neuron wait for an answer from the caller. The api response object is returned.
+                                 The neuron is not removed from the matched synapse to be executed again
+        - want to execute a synapse: The neuron add a list of synapse to execute to the lifo. 
+                                     The LIFO restart over to process it.The neuron is removed from the matched synapse
+        - complete: The neuron has been executed and its not waiting for an answer and doesn't want to start a synapse
+                    The neuron is removed from the matched synapse
+        :param matched_synapse: MatchedSynapse object to process
+        """
+
+        logger.debug("[LIFOBuffer] number of neuron to process: %s" % len(matched_synapse.neuron_fifo_list))
+        # while we have synapse to process in the list of synapse
+        while matched_synapse.neuron_fifo_list:
+            # get the first neuron in the FIFO neuron list
+            neuron = matched_synapse.neuron_fifo_list[0]
+            # from here, we are back into the last neuron we were processing.
+            if cls.answer is not None:  # we give the answer if exist to the first neuron
+                neuron.parameters["answer"] = cls.answer
+                # the next neuron should not get this answer
+                cls.answer = None
+            # todo fix this when we have a full client/server call. The client would be the voice or api call
+            neuron.parameters["is_api_call"] = cls.is_api_call
+            logger.debug("[LIFOBuffer] process_neuron_list: is_api_call: %s" % cls.is_api_call)
+            # execute the neuron
+            instantiated_neuron = NeuronLauncher.start_neuron(neuron=neuron,
+                                                              parameters_dict=matched_synapse.parameters)
+
+            # the status of an execution is "complete" if no neuron are waiting for an answer
+            cls.api_response.status = "complete"
+            if instantiated_neuron is not None:
+                if instantiated_neuron.is_waiting_for_answer:  # the neuron is waiting for an answer
+                    logger.debug("[LIFOBuffer] Wait for answer mode")
+                    cls.api_response.status = "waiting_for_answer"
+                    raise Serialize
+                else:
+                    logger.debug("[LIFOBuffer] complete mode")
+                    # we add the instantiated neuron to the neuron_module_list.
+                    # This one contains info about generated text
+                    matched_synapse.neuron_module_list.append(instantiated_neuron)
+                    # the neuron is fully processed we can remove it from the list
+                    matched_synapse.neuron_fifo_list.remove(neuron)
+
+                if instantiated_neuron.pending_synapse:  # the last executed neuron want to run a synapse
+                    logger.debug("[LIFOBuffer] Last executed neuron want to run a synapse. Restart the LIFO")
+                    # add the synapse to the lifo (inside a list as expected by the lifo)
+                    cls.add_synapse_list_to_lifo([instantiated_neuron.pending_synapse])
+                    # we have added a list of synapse to the LIFO ! this one must start over.
+                    # break all while loop until the execution is back to the LIFO loop
+                    raise SynapseListAddedToLIFO
+            else:
+                raise Serialize

+ 76 - 54
kalliope/core/MainController.py

@@ -2,17 +2,22 @@ import logging
 import random
 from time import sleep
 
-from flask import Flask
-from kalliope.core.SynapseLauncher import SynapseLauncher
 from transitions import Machine
-
 from kalliope.core import Utils
 from kalliope.core.ConfigurationManager import SettingLoader
-from kalliope.core.OrderAnalyser import OrderAnalyser
 from kalliope.core.OrderListener import OrderListener
-from kalliope.core.Players import Mplayer
+
+# API
+from flask import Flask
 from kalliope.core.RestAPI.FlaskAPI import FlaskAPI
+
+# Launchers
+from kalliope.core.SynapseLauncher import SynapseLauncher
 from kalliope.core.TriggerLauncher import TriggerLauncher
+from kalliope.core.Utils.RpiUtils import RpiUtils
+from kalliope.core.PlayerLauncher import PlayerLauncher
+
+# Neurons
 from kalliope.neurons.say.say import Say
 
 logging.basicConfig()
@@ -25,9 +30,9 @@ class MainController:
     """
     states = ['init',
               'starting_trigger',
-              'unpausing_trigger',
               'playing_ready_sound',
               'waiting_for_trigger_callback',
+              'stopping_trigger',
               'start_order_listener',
               'playing_wake_up_answer',
               'waiting_for_order_listener_callback',
@@ -44,6 +49,24 @@ class MainController:
         # Starting the rest API
         self._start_rest_api()
 
+        # rpi setting for led and mute button
+        self.rpi_utils = None
+        if self.settings.rpi_settings:
+            # the useer set GPIO pin, we need to instantiate the RpiUtils class in order to setup GPIO
+            self.rpi_utils = RpiUtils(self.settings.rpi_settings, self.muted_button_pressed)
+            if self.settings.rpi_settings.pin_mute_button:
+                # start the listening for button pressed thread only if the user set a pin
+                self.rpi_utils.daemon = True
+                self.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)
+
+        # get the player instance
+        self.player_instance = PlayerLauncher.get_player(settings=self.settings)
+
         # save an instance of the trigger
         self.trigger_instance = None
         self.trigger_callback_called = False
@@ -59,10 +82,10 @@ class MainController:
         self.machine = Machine(model=self, states=MainController.states, initial='init', queued=True)
 
         # define transitions
-        self.machine.add_transition('start_trigger', 'init', 'starting_trigger')
-        self.machine.add_transition('unpause_trigger', ['starting_trigger', 'analysing_order'], 'unpausing_trigger')
-        self.machine.add_transition('play_ready_sound', 'unpausing_trigger', 'playing_ready_sound')
+        self.machine.add_transition('start_trigger', ['init', 'analysing_order'], 'starting_trigger')
+        self.machine.add_transition('play_ready_sound', 'starting_trigger', 'playing_ready_sound')
         self.machine.add_transition('wait_trigger_callback', 'playing_ready_sound', 'waiting_for_trigger_callback')
+        self.machine.add_transition('stop_trigger', 'waiting_for_trigger_callback', 'stopping_trigger')
         self.machine.add_transition('play_wake_up_answer', 'waiting_for_trigger_callback', 'playing_wake_up_answer')
         self.machine.add_transition('wait_for_order', 'playing_wake_up_answer', 'waiting_for_order_listener_callback')
         self.machine.add_transition('analyse_order', 'waiting_for_order_listener_callback', 'analysing_order')
@@ -74,10 +97,10 @@ class MainController:
         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')
-        self.machine.on_enter_unpausing_trigger('unpausing_trigger_process')
 
         self.start_trigger()
 
@@ -85,30 +108,21 @@ class MainController:
         """
         This function will start the trigger thread that listen for the hotword
         """
-        logger.debug("Entering state: %s" % self.state)
-        self.trigger_instance = self._get_default_trigger()
+        logger.debug("[MainController] Entering state: %s" % self.state)
+        self.trigger_instance = TriggerLauncher.get_trigger(settings=self.settings, callback=self.trigger_callback)
+        self.trigger_callback_called = False
         self.trigger_instance.daemon = True
         # Wait that the kalliope trigger is pronounced by the user
         self.trigger_instance.start()
         self.next_state()
 
-    def unpausing_trigger_process(self):
-        """
-        If the trigger was in pause, this method will unpause it to listen again for the hotword
-        """
-        logger.debug("Entering state: %s" % self.state)
-        self.trigger_instance.unpause()
-        self.trigger_callback_called = False
-        Utils.print_info("Waiting for trigger detection")
-        self.next_state()
-
     def play_ready_sound_process(self):
         """
         Play a sound when Kalliope is ready to be awaken at the first start
         """
-        logger.debug("Entering state: %s" % self.state)
+        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":
+                        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
@@ -116,14 +130,15 @@ class MainController:
                 Say(message=self.settings.on_ready_answers)
             elif self.settings.on_ready_sounds is not None:
                 random_sound_to_play = self._get_random_sound(self.settings.on_ready_sounds)
-                Mplayer.play(random_sound_to_play)
+                self.player_instance.play(random_sound_to_play)
         self.next_state()
 
     def waiting_for_trigger_callback_thread(self):
         """
         Method to print in debug that the main process is waiting for a trigger detection
         """
-        logger.debug("Entering state: %s" % self.state)
+        logger.debug("[MainController] Entering state: %s" % self.state)
+        Utils.print_info("Waiting for trigger detection")
         # this loop is used to keep the main thread alive
         while not self.trigger_callback_called:
             sleep(0.1)
@@ -133,10 +148,13 @@ class MainController:
         """
         Method to print in debug that the main process is waiting for an order to analyse
         """
-        logger.debug("Entering state: %s" % self.state)
+        logger.debug("[MainController] Entering state: %s" % self.state)
         # this loop is used to keep the main thread alive
         while not self.order_listener_callback_called:
             sleep(0.1)
+        if self.settings.rpi_settings:
+            if self.settings.rpi_settings.pin_led_listening:
+                RpiUtils.switch_pin_to_off(self.settings.rpi_settings.pin_led_listening)
         self.next_state()
 
     def trigger_callback(self):
@@ -144,17 +162,23 @@ class MainController:
         we have detected the hotword, we can now pause the Trigger for a while
         The user can speak out loud his order during this time.
         """
-        logger.debug("Trigger callback called, switching to the next state")
-        # self.next_state()
+        logger.debug("[MainController] Trigger callback called, switching to the next state")
         self.trigger_callback_called = True
 
+    def stop_trigger_process(self):
+        """
+        The trigger has been awaken, we don't needed it anymore
+        :return: 
+        """
+        logger.debug("[MainController] Entering state: %s" % self.state)
+        self.trigger_instance.stop()
+        self.next_state()
+
     def start_order_listener_thread(self):
         """
         Start the STT engine thread
         """
-        logger.debug("Entering state: %s" % self.state)
-        # pause the trigger process
-        self.trigger_instance.pause()
+        logger.debug("[MainController] Entering state: %s" % self.state)
         # start listening for an order
         self.order_listener_callback_called = False
         self.order_listener = OrderListener(callback=self.order_listener_callback)
@@ -167,14 +191,13 @@ class MainController:
         Play a sound or make Kalliope say something to notify the user that she has been awaken and now
         waiting for order
         """
-        logger.debug("Entering state: %s" % self.state)
+        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)
-            Mplayer.play(random_sound_to_play)
-
+            self.player_instance.play(random_sound_to_play)
         self.next_state()
 
     def order_listener_callback(self, order):
@@ -183,7 +206,7 @@ class MainController:
         :param order: the sentence received
         :type order: str
         """
-        logger.debug("order listener callback called. Order to process: %s" % order)
+        logger.debug("[MainController] Order listener callback called. Order to process: %s" % order)
         self.order_to_process = order
         self.order_listener_callback_called = True
 
@@ -191,24 +214,14 @@ class MainController:
         """
         Start the order analyser with the caught order to process
         """
-        logger.debug("order in analysing_order_thread %s" % self.order_to_process)
-        if self.order_to_process is not None:   # maybe we have received a null audio from STT engine
-            order_analyser = OrderAnalyser(self.order_to_process, brain=self.brain)
-            order_analyser.start()
-        else:
-            if self.settings.default_synapse is not None:
-                SynapseLauncher.start_synapse(name=self.settings.default_synapse, brain=self.brain)
-        # return to the state "unpausing_trigger"
-        self.unpause_trigger()
+        logger.debug("[MainController] order in analysing_order_thread %s" % self.order_to_process)
+        SynapseLauncher.run_matching_synapse_from_order(self.order_to_process,
+                                                        self.brain,
+                                                        self.settings,
+                                                        is_api_call=False)
 
-    def _get_default_trigger(self):
-        """
-        Return an instance of the default trigger
-        :return: Trigger
-        """
-        for trigger in self.settings.triggers:
-            if trigger.name == self.settings.default_trigger_name:
-                return TriggerLauncher.get_trigger(trigger, callback=self.trigger_callback)
+        # return to the state "unpausing_trigger"
+        self.start_trigger()
 
     @staticmethod
     def _get_random_sound(random_wake_up_sounds):
@@ -221,7 +234,7 @@ class MainController:
         """
         # take first randomly a path
         random_path = random.choice(random_wake_up_sounds)
-        logger.debug("Selected sound: %s" % random_path)
+        logger.debug("[MainController] Selected sound: %s" % random_path)
         return Utils.get_real_file_path(random_path)
 
     def _start_rest_api(self):
@@ -238,3 +251,12 @@ class MainController:
                                  allowed_cors_origin=self.settings.rest_api.allowed_cors_origin)
             flask_api.daemon = True
             flask_api.start()
+
+    def muted_button_pressed(self, muted=False):
+        logger.debug("[MainController] Mute button pressed. Switch trigger process to muted: %s" % muted)
+        if muted:
+            self.trigger_instance.pause()
+            Utils.print_info("Kalliope now muted")
+        else:
+            self.trigger_instance.unpause()
+            Utils.print_info("Kalliope now listening for trigger detection")

+ 26 - 0
kalliope/core/Models/APIResponse.py

@@ -0,0 +1,26 @@
+from kalliope.core.Utils import Utils
+
+
+class APIResponse(object):
+
+    def __init__(self):
+        self.user_order = None
+        self.list_processed_matched_synapse = list()
+        self.status = None
+
+    def __str__(self):
+        return str(self.serialize())
+
+    def serialize(self):
+        """
+        This method allows to serialize in a proper way this object
+        :return: A dict of name and parameters
+        :rtype: Dict
+        """
+
+        self.user_order = Utils.encode_text_utf8(self.user_order)
+        return {
+            'user_order': self.user_order,
+            'matched_synapses': [e.serialize() for e in self.list_processed_matched_synapse],
+            'status': self.status
+        }

+ 1 - 5
kalliope/core/Models/Dna.py

@@ -25,11 +25,7 @@ class Dna(object):
         }
 
     def __str__(self):
-        return "Dna: name: %s, " \
-               "type: %s, " \
-               "author: %s, " \
-               "kalliope_supported_version: %s, " \
-               "tags: %s" % (self.name, self.module_type, self.author, self.kalliope_supported_version, self.tags)
+        return str(self.serialize())
 
     def __eq__(self, other):
         """

+ 1 - 3
kalliope/core/Models/Event.py

@@ -17,9 +17,7 @@ class Event(object):
         self.second = second
 
     def __str__(self):
-        return "%s:  year: %s, month: %s, day: %s, week: %s, day_of_week: %s, hour: %s, minute: %s, second: %s" \
-               % (self.__class__.__name__, self.year, self.month, self.day, self.week,
-                  self.day_of_week, self.hour, self.minute, self.second)
+        return str(self.serialize())
 
     def serialize(self):
         """

+ 55 - 0
kalliope/core/Models/MatchedSynapse.py

@@ -0,0 +1,55 @@
+import copy
+
+from kalliope.core.NeuronParameterLoader import NeuronParameterLoader
+
+
+class MatchedSynapse(object):
+    """
+    This class represent a synapse that has matched an order send by an User.
+    """
+
+    def __init__(self, matched_synapse=None, matched_order=None, user_order=None):
+        """
+        :param matched_synapse: The synapse that has matched in the brain.
+        :param matched_order: The order from the synapse that have matched.
+        :param user_order: The order said by the user.
+        """
+
+        # create a copy of the synapse. the received synapse come from the brain.
+        self.synapse = matched_synapse
+        # create a fifo list that contains all neurons to process.
+        # Create a copy to be sure when we remove a neuron from this list it will not be removed from the synapse's
+        # neuron list
+        self.neuron_fifo_list = copy.deepcopy(self.synapse.neurons)
+        self.matched_order = matched_order
+        self.parameters = dict()
+        if matched_order is not None:
+            self.parameters = NeuronParameterLoader.get_parameters(synapse_order=self.matched_order,
+                                                                   user_order=user_order)
+
+        # list of Neuron Module
+        self.neuron_module_list = list()
+
+    def __str__(self):
+        return str(self.serialize())
+
+    def serialize(self):
+        """
+        This method allows to serialize in a proper way this object
+
+        :return: A dict of name and parameters
+        :rtype: Dict
+        """
+        return {
+            'synapse_name': self.synapse.name,
+            'matched_order': self.matched_order,
+            'neuron_module_list': [e.serialize() for e in self.neuron_module_list]
+        }
+
+    def __eq__(self, other):
+        """
+        This is used to compare 2 objects
+        :param other:
+        :return:
+        """
+        return self.__dict__ == other.__dict__

+ 9 - 5
kalliope/core/Models/Neuron.py

@@ -27,16 +27,20 @@ class Neuron(object):
         the output of this parameter will be masked in order to not appears in clean in the console
         :return: string description of the neuron
         """
-        returned_string = str()
-        returned_string += "Neuron: name: %s" % self.name
+        returned_dict = {
+            'name': self.name,
+            'parameters': self.parameters
+        }
+
         cleaned_parameters = dict()
-        for key, value in self.parameters.iteritems():
+        for key, value in self.parameters.items():
             if "password" in key:
                 cleaned_parameters[key] = "*****"
             else:
                 cleaned_parameters[key] = value
-        returned_string += ", parameters: %s" % cleaned_parameters
-        return returned_string
+        returned_dict["parameters"] = cleaned_parameters
+
+        return str(returned_dict)
 
     def __eq__(self, other):
         """

+ 1 - 1
kalliope/core/Models/Order.py

@@ -9,7 +9,7 @@ class Order(object):
         self.sentence = sentence
 
     def __str__(self):
-        return "%s: Sentence: %s" % (self.__class__.__name__, self.sentence)
+        return str(self.serialize())
 
     def serialize(self):
         """

+ 27 - 0
kalliope/core/Models/Player.py

@@ -0,0 +1,27 @@
+class Player(object):
+    """
+    This Class is representing a Player with its name and parameters
+
+    .. note:: must be defined in the settings.yml
+    """
+
+    def __init__(self, name=None, parameters=None):
+        self.name = name
+        self.parameters = parameters
+
+    def __str__(self):
+        return str(self.serialize())
+
+    def serialize(self):
+        return {
+            'name': self.name,
+            'parameters': self.parameters
+        }
+
+    def __eq__(self, other):
+        """
+        This is used to compare 2 Player objects
+        :param other: the Player to compare
+        :return: True if both players are similar, False otherwise
+        """
+        return self.__dict__ == other.__dict__

+ 1 - 5
kalliope/core/Models/Resources.py

@@ -11,11 +11,7 @@ class Resources(object):
         self.trigger_folder = trigger_folder
 
     def __str__(self):
-        return "%s: neuron_folder: %s, stt_folder: %s, tts_folder: %s, trigger_folder: %s" % (self.__class__.__name__,
-                                                                                              self.neuron_folder,
-                                                                                              self.stt_folder,
-                                                                                              self.tts_folder,
-                                                                                              self.trigger_folder)
+        return str(self.serialize())
 
     def serialize(self):
         """

+ 24 - 9
kalliope/core/Models/RestAPI.py

@@ -3,7 +3,12 @@ class RestAPI(object):
     This Class is representing the rest API with all its configuration.
     """
 
-    def __init__(self, password_protected=None, login=None, password=None, active=None, port=None, allowed_cors_origin=None):
+    def __init__(self,
+                 password_protected=None,
+                 login=None, password=None,
+                 active=None,
+                 port=None,
+                 allowed_cors_origin=None):
         """
         :param password_protected: If true, the rest api will ask for an authentication
         :param login: login used if auth is activated
@@ -19,14 +24,24 @@ class RestAPI(object):
         self.allowed_cors_origin = allowed_cors_origin
 
     def __str__(self):
-        return "%s: RestAPI: password_protected: %s, login: %s, " \
-              "password: %s, active: %s, port: %s, allow_cors_request: %s" % (self.__class__.__name__,
-                                                       self.password_protected,
-                                                       self.login,
-                                                       self.password,
-                                                       self.active,
-                                                       self.port,
-                                                       self.allowed_cors_origin)
+        return str(self.serialize())
+
+    def serialize(self):
+        """
+        This method allows to serialize in a proper way this object
+
+        :return: A dict of order
+        :rtype: Dict
+        """
+
+        return {
+            'password_protected': self.password_protected,
+            'login': self.login,
+            'password': self.password,
+            'active': self.active,
+            'port': self.port,
+            'allowed_cors_origin': self.allowed_cors_origin
+        }
 
     def __eq__(self, other):
         """

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

@@ -0,0 +1,35 @@
+
+
+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__

+ 42 - 3
kalliope/core/Models/Settings.py

@@ -12,6 +12,7 @@ class Settings(object):
                  default_tts_name=None,
                  default_stt_name=None,
                  default_trigger_name=None,
+                 default_player_name=None,
                  ttss=None,
                  stts=None,
                  random_wake_up_answers=None,
@@ -20,17 +21,18 @@ class Settings(object):
                  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, # dict()
-                 machine=None,
-                 kalliope_version=None):
+                 variables=None,
+                 rpi_settings=None):
 
         self.default_tts_name = default_tts_name
         self.default_stt_name = default_stt_name
         self.default_trigger_name = default_trigger_name
+        self.default_player_name = default_player_name
         self.ttss = ttss
         self.stts = stts
         self.random_wake_up_answers = random_wake_up_answers
@@ -39,6 +41,7 @@ class Settings(object):
         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
@@ -46,6 +49,42 @@ class Settings(object):
         self.variables = variables
         self.machine = platform.machine()   # can be x86_64 or armv7l
         self.kalliope_version = current_kalliope_version
+        self.rpi_settings = rpi_settings
+
+    def serialize(self):
+        """
+        This method allows to serialize in a proper way this object
+
+        :return: A dict of order
+        :rtype: Dict
+        """
+
+        return {
+            'default_tts_name': self.default_tts_name,
+            'default_stt_name': self.default_stt_name,
+            'default_trigger_name': self.default_trigger_name,
+            '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
+        }
+
+    def __str__(self):
+        return str(self.serialize())
 
     def __eq__(self, other):
         """

+ 7 - 1
kalliope/core/Models/Stt.py

@@ -10,7 +10,13 @@ class Stt(object):
         self.parameters = parameters
 
     def __str__(self):
-        return "Stt name: %s, parameters: %s" % (str(self.name), str(self.parameters))
+        return str(self.serialize())
+
+    def serialize(self):
+        return {
+            'name': self.name,
+            'parameters': self.parameters
+        }
 
     def __eq__(self, other):
         """

+ 1 - 8
kalliope/core/Models/Synapse.py

@@ -25,14 +25,7 @@ class Synapse(object):
         }
 
     def __str__(self):
-        return_val = "Synapse name: %s" % self.name
-        return_val += "\nneurons:"
-        for el in self.neurons:
-            return_val += str(el)
-        return_val += "\nsignals:"
-        for el in self.signals:
-            return_val += str(el)
-        return return_val
+        return str(self.serialize())
 
     def __eq__(self, other):
         """

+ 7 - 1
kalliope/core/Models/Trigger.py

@@ -10,7 +10,13 @@ class Trigger(object):
         self.parameters = parameters
 
     def __str__(self):
-        return "Trigger name: %s, parameters: %s" % (str(self.name), str(self.parameters))
+        return str(self.serialize())
+
+    def serialize(self):
+        return {
+            'name': self.name,
+            'parameters': self.parameters
+        }
 
     def __eq__(self, other):
         """

+ 7 - 1
kalliope/core/Models/Tts.py

@@ -10,7 +10,13 @@ class Tts(object):
         self.parameters = parameters
 
     def __str__(self):
-        return "Tts name: %s, parameters: %s" % (str(self.name), str(self.parameters))
+        return str(self.serialize())
+
+    def serialize(self):
+        return {
+            'name': self.name,
+            'parameters': self.parameters
+        }
 
     def __eq__(self, other):
         """

+ 8 - 8
kalliope/core/Models/__init__.py

@@ -1,8 +1,8 @@
-from Singleton import Singleton
-from Event import Event
-from Resources import Resources
-from Brain import Brain
-from Order import Order
-from Synapse import Synapse
-from Neuron import Neuron
-
+from .Singleton import Singleton
+from .Event import Event
+from .Resources import Resources
+from .Brain import Brain
+from .Order import Order
+from .Synapse import Synapse
+from .Neuron import Neuron
+from .RpiSettings import RpiSettings

+ 90 - 1
kalliope/core/NeuronLauncher.py

@@ -1,4 +1,7 @@
 import logging
+import six
+import jinja2
+import sys
 
 from kalliope.core.Utils.Utils import Utils
 from kalliope.core.ConfigurationManager.SettingLoader import SettingLoader
@@ -7,13 +10,17 @@ logging.basicConfig()
 logger = logging.getLogger("kalliope")
 
 
+class NeuronParameterNotAvailable(Exception):
+    pass
+
+
 class NeuronLauncher:
 
     def __init__(self):
         pass
 
     @classmethod
-    def start_neuron(cls, neuron):
+    def launch_neuron(cls, neuron):
         """
         Start a neuron plugin
         :param neuron: neuron object
@@ -32,3 +39,85 @@ class NeuronLauncher:
                                                      parameters=neuron.parameters,
                                                      resources_dir=neuron_folder)
 
+    @classmethod
+    def start_neuron(cls, neuron, parameters_dict=None):
+        """
+        Execute each neuron from the received neuron_list.
+        Replace parameter if exist in the received dict of parameters_dict
+        :param neuron: Neuron object to run
+        :param parameters_dict: dict of parameter to load in each neuron if expecting a parameter
+        :return: List of the instantiated neurons (no errors detected)
+        """
+        if neuron.parameters is not None:
+            try:
+                neuron.parameters = cls._replace_brackets_by_loaded_parameter(neuron.parameters, parameters_dict)
+            except NeuronParameterNotAvailable:
+                Utils.print_danger("The neuron %s cannot be launched" % neuron.name)
+                return None
+        instantiated_neuron = NeuronLauncher.launch_neuron(neuron)
+        return instantiated_neuron
+
+    @classmethod
+    def _replace_brackets_by_loaded_parameter(cls, neuron_parameters, loaded_parameters):
+        """
+        Receive a value (which can be a str or dict or list) and instantiate value in double brace bracket
+        by the value specified in the loaded_parameters dict.
+        This method will call itself until all values has been instantiated
+        :param neuron_parameters: value to instantiate. Str or dict or list
+        :param loaded_parameters: dict of parameters
+        """
+        logger.debug("[NeuronLauncher] replacing brackets from %s, using %s" % (neuron_parameters, loaded_parameters))
+        if isinstance(neuron_parameters, str) or isinstance(neuron_parameters, six.text_type):
+            # replace bracket parameter only if the str contains brackets
+            if Utils.is_containing_bracket(neuron_parameters):
+                # check that the parameter to replace is available in the loaded_parameters dict
+                if cls._neuron_parameters_are_available_in_loaded_parameters(neuron_parameters, loaded_parameters):
+                    neuron_parameters = jinja2.Template(neuron_parameters).render(loaded_parameters)
+                    neuron_parameters = Utils.encode_text_utf8(neuron_parameters)
+                    return str(neuron_parameters)
+                else:
+                    raise NeuronParameterNotAvailable
+            return neuron_parameters
+
+        if isinstance(neuron_parameters, dict):
+            returned_dict = dict()
+            for key, value in neuron_parameters.items():
+                if key in "say_template" or key in "file_template":  # those keys are reserved for the TTS.
+                    returned_dict[key] = value
+                else:
+                    returned_dict[key] = cls._replace_brackets_by_loaded_parameter(value, loaded_parameters)
+            return returned_dict
+
+        if isinstance(neuron_parameters, list):
+            returned_list = list()
+            for el in neuron_parameters:
+                templated_value = cls._replace_brackets_by_loaded_parameter(el, loaded_parameters)
+                returned_list.append(templated_value)
+            return returned_list
+        # in all other case (boolean or int for example) we return the value as it
+        return neuron_parameters
+
+    @staticmethod
+    def _neuron_parameters_are_available_in_loaded_parameters(string_parameters, loaded_parameters):
+        """
+        Check that all parameters in brackets are available in the loaded_parameters dict
+        
+        E.g:
+        string_parameters = "this is a {{ parameter1 }}"
+        
+        Will return true if the loaded_parameters looks like the following
+        loaded_parameters { "parameter1": "a value"}        
+        
+        :param string_parameters: The string that contains one or more parameters in brace brackets
+        :param loaded_parameters: Dict of parameter
+        :return: True if all parameters in brackets have an existing key in loaded_parameters dict
+        """
+        list_parameters_with_brackets = Utils.find_all_matching_brackets(string_parameters)
+        # remove brackets to keep only the parameter name
+        for parameter_with_brackets in list_parameters_with_brackets:
+            parameter = Utils.remove_spaces_in_brackets(parameter_with_brackets)
+            parameter = parameter.replace("{{", "").replace("}}", "")
+            if loaded_parameters is None or parameter not in loaded_parameters:
+                Utils.print_danger("The parameter %s is not available in the order" % str(parameter))
+                return False
+        return True

+ 120 - 68
kalliope/core/NeuronModule.py

@@ -1,15 +1,17 @@
 # coding: utf8
 import logging
 import random
-
 import sys
+import six
+
 from jinja2 import Template
 
 from kalliope.core import OrderListener
-from kalliope.core import OrderAnalyser
-from kalliope.core.SynapseLauncher import SynapseLauncher
-from kalliope.core.Utils.Utils import Utils
 from kalliope.core.ConfigurationManager import SettingLoader, BrainLoader
+from kalliope.core.Models.MatchedSynapse import MatchedSynapse
+from kalliope.core.OrderAnalyser import OrderAnalyser
+from kalliope.core.Utils.RpiUtils import RpiUtils
+from kalliope.core.Utils.Utils import Utils
 
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
@@ -70,22 +72,53 @@ class NeuronModule(object):
         brain_loader = BrainLoader()
         self.brain = brain_loader.brain
 
-        # check if the user has overrider the TTS
-        tts = kwargs.get('tts', None)
-        if tts is None:
-            # No tts provided,  we load the default one
-            self.tts = self.settings.default_tts_name
-        else:
-            self.tts = tts
+        # a dict of overridden TTS parameters if provided by the user
+        self.override_tts_parameters = kwargs.get('tts', None)
 
-        # get if the cache settings is present
-        self.override_cache = kwargs.get('cache', None)
+        # create the TTS instance
+        self.tts = None
+        if self.override_tts_parameters is None or not isinstance(self.override_tts_parameters, dict):
+            # we get the default TTS
+            self.tts = self._get_tts_object(settings=self.settings)
+        else:
+            for key, value in self.override_tts_parameters.items():
+                tts_name = key
+                tts_parameters = value
+                self.tts = self._get_tts_object(tts_name=tts_name,
+                                                override_parameter=tts_parameters,
+                                                settings=self.settings)
 
         # get templates if provided
         # Check if there is a template associate to the output message
         self.say_template = kwargs.get('say_template', None)
         # check if there is a template file associate to the output message
         self.file_template = kwargs.get('file_template', None)
+        # keep the generated message
+        self.tts_message = None
+        # if the current call is api one
+        self.is_api_call = kwargs.get('is_api_call', False)
+        # boolean to know id the synapse is waiting for an answer
+        self.is_waiting_for_answer = False
+        # the synapse name to add the the buffer
+        self.pending_synapse = None
+
+    def __str__(self):
+        retuned_string = ""
+        retuned_string += self.tts_message
+        return retuned_string
+
+    def serialize(self):
+        """
+        This method allows to serialize in a proper way this object
+
+        :return: A dict of name and parameters
+        :rtype: Dict
+        """
+        self.tts_message = Utils.encode_text_utf8(self.tts_message)
+        return {
+            'neuron_name': self.neuron_name,
+            'generated_message': self.tts_message
+        }
 
     def say(self, message):
         """
@@ -102,7 +135,7 @@ class NeuronModule(object):
 
         tts_message = None
 
-        if isinstance(message, str) or isinstance(message, unicode):
+        if isinstance(message, str) or isinstance(message, six.text_type):
             logger.debug("message is string")
             tts_message = message
 
@@ -116,28 +149,26 @@ class NeuronModule(object):
 
         if tts_message is not None:
             logger.debug("tts_message to say: %s" % tts_message)
-
-            # create a tts object from the tts the user want to use
-            tts_object = next((x for x in self.settings.ttss if x.name == self.tts), None)
-            if tts_object is None:
-                raise TTSModuleNotFound("The tts module name %s does not exist in settings file" % self.tts)
-            # change the cache settings with the one precised for the current neuron
-            if self.override_cache is not None:
-                tts_object.parameters = self._update_cache_var(self.override_cache, tts_object.parameters)
-
-            logger.debug("NeuroneModule: TTS args: %s" % tts_object)
+            self.tts_message = tts_message
+            Utils.print_success(tts_message)
 
             # get the instance of the TTS module
             tts_folder = None
             if self.settings.resources:
                 tts_folder = self.settings.resources.tts_folder
             tts_module_instance = Utils.get_dynamic_class_instantiation(package_name="tts",
-                                                                        module_name=tts_object.name,
-                                                                        parameters=tts_object.parameters,
+                                                                        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)
+
     def _get_message_from_dict(self, message_dict):
         """
         Generate a message that can be played by a TTS engine from a dict of variable and the jinja template
@@ -153,8 +184,9 @@ class NeuronModule(object):
             returned_message = self._get_say_template(self.say_template, message_dict)
 
         # trick to remove unicode problem when loading jinja template with non ascii char
-        reload(sys)
-        sys.setdefaultencoding('utf-8')
+        if sys.version_info[0] == 2:
+            reload(sys)
+            sys.setdefaultencoding('utf-8')
 
         # the user chooses a file_template option
         if self.file_template is not None:  # the user choose a file_template option
@@ -183,35 +215,23 @@ class NeuronModule(object):
 
         return returned_message
 
-    def run_synapse_by_name(self, name):
-        SynapseLauncher.start_synapse(name=name, brain=self.brain)
-
-    def is_order_matching(self, order_said, order_match):
-        oa = OrderAnalyser(order=order_said, brain=self.brain)
-        return oa.spelt_order_match_brain_order_via_table(order_to_analyse=order_match, user_said=order_said)
-
-    def run_synapse_by_name_with_order(self, order, synapse_name, order_template):
+    def run_synapse_by_name(self, synapse_name, user_order=None, synapse_order=None):
         """
-        Run a synapse using its name, and giving an order so it can retrieve its params.
-        Useful for neurotransmitters.
-        :param order: the order to match
-        :param synapse_name: the name of the synapse
-        :param order_template: order_template coming from the neurotransmitter
-        :return: True if a synapse as been found and started using its params
+        call the lifo for adding a synapse to execute in the list of synapse list to process
+        :param synapse_name: The name of the synapse to run
+        :param user_order: The user order
+        :param synapse_order: The synapse order
         """
-        synapse_to_run = self.brain.get_synapse_by_name(synapse_name=synapse_name)
-        if synapse_to_run:
-            # Make a list with the synapse
-            logger.debug("[run_synapse_by_name_with_order]-> a synapse has been found  %s" % synapse_to_run.name)
-            list_to_run = list()
-            list_to_run.append(synapse_to_run)
-
-            oa = OrderAnalyser(order=order, brain=self.brain)
-            oa.start(synapses_to_run=list_to_run, external_order=order_template)
-        else:
-            logger.debug("[NeuronModule]-> run_synapse_by_name_with_order, the synapse has not been found : %s"
-                         % synapse_name)
-        return synapse_to_run is not None
+        synapse = BrainLoader().get_brain().get_synapse_by_name(synapse_name)
+        matched_synapse = MatchedSynapse(matched_synapse=synapse,
+                                         matched_order=synapse_order,
+                                         user_order=user_order)
+        self.pending_synapse = matched_synapse
+
+    @staticmethod
+    def is_order_matching(order_said, order_match):
+        return OrderAnalyser().spelt_order_match_brain_order_via_table(order_to_analyse=order_match,
+                                                                       user_said=order_said)
 
     @staticmethod
     def _get_content_of_file(real_file_template_path):
@@ -223,19 +243,6 @@ class NeuronModule(object):
         with open(real_file_template_path, 'r') as content_file:
             return content_file.read()
 
-    @staticmethod
-    def _update_cache_var(new_override_cache, args_dict):
-        """
-        update the value for the key "cache" in the dict args_list
-        :param new_override_cache: cache boolean to set in place of the current one in args_list
-        :param args_dict: arg list that contain "cache" to update
-        :return:
-        """
-        logger.debug("args for TTS plugin before update: %s" % str(args_dict))
-        args_dict["cache"] = new_override_cache
-        logger.debug("args for TTS plugin after update: %s" % str(args_dict))
-        return args_dict
-
     @staticmethod
     def get_audio_from_stt(callback):
         """
@@ -256,3 +263,48 @@ class NeuronModule(object):
         :return:
         """
         return self.neuron_name
+
+    @staticmethod
+    def _get_tts_object(tts_name=None, override_parameter=None, settings=None):
+        """
+        Return a TTS model object
+        If no tts name provided, return the default TTS defined in the settings
+        If the TTS name is provided, get the default configuration for this TTS in settings and override each parameters
+        with parameters provided in override_parameter
+        :param tts_name: name of the TTS to load
+        :param override_parameter: dict of parameter to override the default configuration of the TTS
+        :param settings: current settings
+        :return: Tts model object
+        """
+
+        # if the tts_name is not provided, we get the default tts from settings
+        if tts_name is None:
+            tts_name = settings.default_tts_name
+
+        # create a tts object from the tts the user want to use
+        tts_object = next((x for x in settings.ttss if x.name == tts_name), None)
+        if tts_object is None:
+            raise TTSModuleNotFound("The tts module name %s does not exist in settings file" % tts_name)
+
+        if override_parameter is not None:  # the user want to override the default TTS configuration
+            logger.debug("args for TTS plugin before update: %s" % str(tts_object.parameters))
+            for key, value in override_parameter.items():
+                tts_object.parameters[key] = value
+            logger.debug("args for TTS plugin after update: %s" % str(tts_object.parameters))
+
+        logger.debug("NeuroneModule: 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)

+ 65 - 0
kalliope/core/NeuronParameterLoader.py

@@ -0,0 +1,65 @@
+from kalliope.core.Utils import Utils
+
+import logging
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+
+class NeuronParameterLoader(object):
+
+    @classmethod
+    def get_parameters(cls, synapse_order, user_order):
+        """
+        Class method to get all params coming from a string order. Returns a dict of key/value.
+        """
+        params = dict()
+        if Utils.is_containing_bracket(synapse_order):
+            params = cls._associate_order_params_to_values(user_order, synapse_order)
+            logger.debug("Parameters for order: %s" % params)
+        return params
+
+    @classmethod
+    def _associate_order_params_to_values(cls, order, order_to_check):
+        """
+        Associate the variables from the order to the incoming user order
+        :param order_to_check: the order to check incoming from the brain
+        :type order_to_check: str
+        :param order: the order from user
+        :type order: str
+        :return: the dict corresponding to the key / value of the params
+        """
+        logger.debug("[OrderAnalyser._associate_order_params_to_values] user order: %s, "
+                     "order from synapse: %s" % (order, order_to_check))
+
+        list_word_in_order = Utils.remove_spaces_in_brackets(order_to_check).split()
+
+        # get the order, defined by the first words before {{
+        # /!\ Could be empty if order starts with double brace
+        the_order = order_to_check[:order_to_check.find('{{')]
+
+        # remove sentence before order which are sentences not matching anyway
+        # Manage Upper/Lower case
+        truncate_user_sentence = order[order.lower().find(the_order.lower()):]
+        truncate_list_word_said = truncate_user_sentence.split()
+
+        # make dict var:value
+        dict_var = dict()
+        for idx, ow in enumerate(list_word_in_order):
+            if Utils.is_containing_bracket(ow):
+                # remove bracket and grab the next value / stop value
+                var_name = ow.replace("{{", "").replace("}}", "")
+                stop_value = Utils.get_next_value_list(list_word_in_order[idx:])
+                if stop_value is None:
+                    dict_var[var_name] = " ".join(truncate_list_word_said)
+                    break
+                for word_said in truncate_list_word_said:
+                    if word_said == stop_value:
+                        break
+                    if var_name in dict_var:
+                        dict_var[var_name] += " " + word_said
+                        truncate_list_word_said = truncate_list_word_said[1:]
+                    else:
+                        dict_var[var_name] = word_said
+            truncate_list_word_said = truncate_list_word_said[1:]
+        return dict_var

+ 46 - 247
kalliope/core/OrderAnalyser.py

@@ -1,12 +1,13 @@
 # coding: utf8
-import re
 import collections
 from collections import Counter
+import sys
+import six
 
+from kalliope.core.Models.MatchedSynapse import MatchedSynapse
 from kalliope.core.Utils.Utils import Utils
 from kalliope.core.ConfigurationManager import SettingLoader
 from kalliope.core.Models import Order
-from kalliope.core.NeuronLauncher import NeuronLauncher
 
 import logging
 
@@ -16,225 +17,59 @@ logger = logging.getLogger("kalliope")
 
 class OrderAnalyser:
     """
-    This Class is used to compare the incoming message to the Signal/Order sentences.
+    This Class is used to get a list of synapses that match a given Spoken order
     """
 
-    def __init__(self, order, brain=None):
-        """
-        Class used to load brain and run neuron attached to the received order
-        :param order: spelt order
-        :param brain: loaded brain
-        """
-        sl = SettingLoader()
-        self.settings = sl.settings
-        self.order = order
-        if isinstance(self.order, str):
-            self.order = order.decode('utf-8')
-        self.brain = brain
-        logger.debug("OrderAnalyser, Received order: %s" % self.order)
-
-    def start(self, synapses_to_run=None, external_order=None):
-        """
-        This method matches the incoming messages to the signals/order sentences provided in the Brain.
-
-        Note: we use named tuples:
-        tuple_synapse_order = collections.namedtuple('tuple_synapse_matchingOrder',['synapse', 'order'])
-        """
-
-        synapse_order_tuple = collections.namedtuple('tuple_synapse_matchingOrder', ['synapse', 'order'])
-        synapses_order_tuple_list = list()
-
-        if synapses_to_run is not None and external_order is not None:
-            for synapse in synapses_to_run:
-                synapses_order_tuple_list.append(synapse_order_tuple(synapse=synapse,
-                                                                     order=external_order))
-
-        # if list of synapse is not provided, let's find one
-        else:  # synapses_to_run is None or external_order is None:
-            # create a dict of synapses that have been launched
-            logger.debug("[orderAnalyser.start]-> No Synapse provided, let's find one")
-            synapses_order_tuple_list = self._find_synapse_to_run(brain=self.brain,
-                                                                  settings=self.settings,
-                                                                  order=self.order)
+    brain = None
+    settings = None
 
-        # retrieve params
-        synapses_launched = list()
-        for tuple in synapses_order_tuple_list:
-            logger.debug("[orderAnalyser.start]-> Grab the params")
-            params = self._get_params_from_order(tuple.order, self.order)
-
-            # Start a neuron list with params
-            self._start_list_neurons(list_neurons=tuple.synapse.neurons,
-                                     params=params,
-                                     settings=self.settings)
-            synapses_launched.append(tuple.synapse)
-
-        # return the list of launched synapse
-        return synapses_launched
+    @classmethod
+    def __init__(cls):
+        cls.settings = SettingLoader().settings
 
     @classmethod
-    def _find_synapse_to_run(cls, brain, settings, order):
+    def get_matching_synapse(cls, order, brain=None):
         """
-        Find the list of the synapse matching the order.
-
-        Note: we use named tuples:
-        tuple_synapse_order = collections.namedtuple('tuple_synapse_matchingOrder',['synapse', 'order'])
-
-        :param brain: the brain
-        :param settings: the settings
-        :param order: the provided order to match
-        :return: the list of synapses launched (named tuples)
+        Return the list of matching synapses from the given order
+        :param order: The user order
+        :param brain: The loaded brain
+        :return: The List of synapses matching the given order
         """
+        cls.brain = brain
+        logger.debug("[OrderAnalyser] Received order: %s" % order)
+        if isinstance(order, six.binary_type):
+            order = order.decode('utf-8')
 
-        synapse_to_run = cls._get_matching_synapse_list(brain.synapses, order)
-        if not synapse_to_run:
-            Utils.print_info("No synapse match the captured order: %s" % order)
-
-            if settings.default_synapse is not None:
-                default_synapse = cls._get_default_synapse_from_sysnapses_list(brain.synapses,
-                                                                               settings.default_synapse)
+        # We use a namedtuple to associate the synapse and the signal of the synapse
+        synapse_order_tuple = collections.namedtuple('tuple_synapse_matchingOrder',
+                                                     ['synapse', 'order'])
 
-                if default_synapse is not None:
-                    logger.debug("Default synapse found %s" % default_synapse)
-                    Utils.print_info("Default synapse found: %s, running it" % default_synapse.name)
-                    tuple_synapse_order = collections.namedtuple('tuple_synapse_matchingOrder',
-                                                                         ['synapse', 'order'])
-                    synapse_to_run.append(tuple_synapse_order(synapse=default_synapse,
-                                                                      order=""))
+        list_match_synapse = list()
 
-        return synapse_to_run
+        # if the received order is None we can stop the process immediately
+        if order is None:
+            return list_match_synapse
 
-    @classmethod
-    def _get_matching_synapse_list(cls, all_synapses_list, order_to_match):
-        """
-        Class method to return all the matching synapses with the order from the complete of synapses.
-
-        Note: we use named tuples:
-        tuple_synapse_matchingOrder = collections.namedtuple('tuple_synapse_matchingOrder',['synapse', 'order'])
-
-        :param all_synapses_list: the complete list of all synapses
-        :param order_to_match: the order to match
-        :type order_to_match: str
-        :return: the list of matching synapses (named tuples)
-        """
-        tuple_synapse_order = collections.namedtuple('tuple_synapse_matchingOrder', ['synapse', 'order'])
-        matching_synapses_list = list()
-        for synapse in all_synapses_list:
+        # test each synapse from the brain
+        for synapse in cls.brain.synapses:
+            # we are only concerned by synapse with a order type of signal
             for signal in synapse.signals:
                 if type(signal) == Order:
-                    if cls.spelt_order_match_brain_order_via_table(signal.sentence, order_to_match):
-                        matching_synapses_list.append(tuple_synapse_order(synapse=synapse,
-                                                                                  order=signal.sentence))
-                        logger.debug("Order found! Run neurons: %s" % synapse.neurons)
+                    if cls.spelt_order_match_brain_order_via_table(signal.sentence, order):
+                        # the order match the synapse, we add it to the returned list
+                        logger.debug("Order found! Run synapse name: %s" % synapse.name)
                         Utils.print_success("Order matched in the brain. Running synapse \"%s\"" % synapse.name)
-        return matching_synapses_list
+                        list_match_synapse.append(synapse_order_tuple(synapse=synapse, order=signal.sentence))
 
-    @classmethod
-    def _get_params_from_order(cls, string_order, order_to_check):
-        """
-        Class method to get all params coming from a string order. Returns a dict of key/value.
-
-        :param string_order: the  string_order to check
-        :param order_to_check: the order to match
-        :type order_to_check: str
-        :return: the dict key/value
-        """
-        params = dict()
-        if Utils.is_containing_bracket(string_order):
-            params = cls._associate_order_params_to_values(order_to_check, string_order)
-            logger.debug("Parameters for order: %s" % params)
-        return params
-
-    @classmethod
-    def _start_list_neurons(cls, list_neurons, params, settings):
-        # start neurons
-        for neuron in list_neurons:
-            cls._start_neuron(neuron, params)
-
-    @staticmethod
-    def _start_neuron(neuron, params):
-        """
-        Associate params and Starts a neuron.
-
-        :param neuron: the neuron to start
-        :param params: the params to check and associate to the neuron args.
-        """
-
-        problem_in_neuron_found = False
-        if isinstance(neuron.parameters, dict):
-            # print neuron.parameters
-            if "args" in neuron.parameters:
-                logger.debug("The neuron waits for parameter")
-                # check that the user added parameters to his order
-                if params is None:
-                    # we don't raise an error and break the program but we don't run the neuron
-                    problem_in_neuron_found = True
-                    Utils.print_danger("Error: The neuron %s is waiting for argument. "
-                                       "Argument found in bracket in the given order" % neuron.name)
-                else:
-                    # we add wanted arguments the existing neuron parameter dict
-                    for arg in neuron.parameters["args"]:
-                        if arg in params:
-                            logger.debug("Parameter %s added to the current parameter "
-                                         "of the neuron: %s" % (arg, neuron.name))
-                            neuron.parameters[arg] = params[arg]
-                        else:
-                            # we don't raise an error and break the program but
-                            # we don't run the neuron
-                            problem_in_neuron_found = True
-                            Utils.print_danger("Error: Argument \"%s\" not found in the"
-                                               " order" % arg)
-
-        # if no error detected, we run the neuron
-        if not problem_in_neuron_found:
-            NeuronLauncher.start_neuron(neuron)
-        else:
-            Utils.print_danger("A problem has been found in the Synapse.")
-
-    @staticmethod
-    def _associate_order_params_to_values(order, order_to_check):
-        """
-        Associate the variables from the order to the incoming user order
-        :param order_to_check: the order to check incoming from the brain
-        :type order_to_check: str
-        :param order: the order from user
-        :type order: str
-        :return: the dict corresponding to the key / value of the params
-        """
-        logger.debug("[OrderAnalyser._associate_order_params_to_values] user order: %s, "
-                     "order to check: %s" % (order, order_to_check))
-
-        list_word_in_order = Utils.remove_spaces_in_brackets(order_to_check).split()
-
-        # get the order, defined by the first words before {{
-        # /!\ Could be empty if order starts with double brace
-        the_order = order_to_check[:order_to_check.find('{{')]
-
-        # remove sentence before order which are sentences not matching anyway
-        truncate_user_sentence = order[order.find(the_order):]
-        truncate_list_word_said = truncate_user_sentence.split()
-
-        # make dict var:value
-        dict_var = dict()
-        for idx, ow in enumerate(list_word_in_order):
-            if Utils.is_containing_bracket(ow):
-                # remove bracket and grab the next value / stop value
-                var_name = ow.replace("{{", "").replace("}}", "")
-                stop_value = Utils.get_next_value_list(list_word_in_order[idx:])
-                if stop_value is None:
-                    dict_var[var_name] = " ".join(truncate_list_word_said)
-                    break
-                for word_said in truncate_list_word_said:
-                    if word_said == stop_value:
-                        break
-                    if var_name in dict_var:
-                        dict_var[var_name] += " " + word_said
-                        truncate_list_word_said = truncate_list_word_said[1:]
-                    else:
-                        dict_var[var_name] = word_said
-            truncate_list_word_said = truncate_list_word_said[1:]
-        return dict_var
+        # create a list of MatchedSynapse from the tuple list
+        list_synapse_to_process = list()
+        for tuple_el in list_match_synapse:
+            new_matching_synapse = MatchedSynapse(matched_synapse=tuple_el.synapse,
+                                                  matched_order=tuple_el.order,
+                                                  user_order=order)
+            list_synapse_to_process.append(new_matching_synapse)
 
+        return list_synapse_to_process
 
     @classmethod
     def spelt_order_match_brain_order_via_table(cls, order_to_analyse, user_said):
@@ -245,36 +80,20 @@ class OrderAnalyser:
         :param user_said: String to compare to the order
         :return: True if all string are present in the order
         """
-        logger.debug("[spelt_order_match_brain_order_via_table] before formatting, "
-                     "order to analyse: %s, "
-                     "user sentence: %s"
-                     % (order_to_analyse, user_said))
-        order_to_analyse, user_said = cls._format_sentences_to_analyse(order_to_analyse=order_to_analyse,
-                                                                       user_said=user_said)
-        list_word_user_said = user_said.split()
-        split_order_without_bracket = cls._get_split_order_without_bracket(order_to_analyse)
-
-        # if all words in the list of what the user said in in the list of word in the order
-        return cls._counter_subset(split_order_without_bracket, list_word_user_said)
-
-    @staticmethod
-    def _format_sentences_to_analyse(order_to_analyse, user_said):
-        """
-        return formatted tuple of the order_to_analyse, user_said
-        :param order_to_analyse: String order to test
-        :param user_said: String to compare to the order
-        :return: tuple of the order_to_analyse, user_said
-        """
         # Lowercase all incoming
         order_to_analyse = order_to_analyse.lower()
         user_said = user_said.lower()
 
-        logger.debug("[_format_sentences_to_analyse] formatted, "
+        logger.debug("[spelt_order_match_brain_order_via_table] "
                      "order to analyse: %s, "
                      "user sentence: %s"
                      % (order_to_analyse, user_said))
 
-        return order_to_analyse, user_said
+        list_word_user_said = user_said.split()
+        split_order_without_bracket = cls._get_split_order_without_bracket(order_to_analyse)
+
+        # if all words in the list of what the user said in in the list of word in the order
+        return cls._counter_subset(split_order_without_bracket, list_word_user_said)
 
     @staticmethod
     def _get_split_order_without_bracket(order):
@@ -305,23 +124,3 @@ class OrderAnalyser:
             if n > c2[k]:
                 return False
         return True
-
-    @staticmethod
-    def _get_default_synapse_from_sysnapses_list(all_synapses_list, default_synapse_name):
-        """
-        Static method to get the default synapse if it exists.
-
-        :param all_synapses_list: the complete list of all synapses
-        :param default_synapse_name: the synapse to find
-        :return: the Synapse
-        """
-        default_synapse = None
-        for synapse in all_synapses_list:
-            if synapse.name == default_synapse_name:
-                logger.debug("Default synapse found: %s" % synapse.name)
-                default_synapse = synapse
-                break
-        if default_synapse is None:
-            logger.debug("Default synapse not found")
-            Utils.print_warning("Default synapse not found")
-        return default_synapse

+ 2 - 0
kalliope/core/OrderListener.py

@@ -48,12 +48,14 @@ class OrderListener(Thread):
         """
         Start thread
         """
+        logger.debug("[OrderListener] running ...")
         self.stt_instance = self.load_stt_plugin()
 
     def load_stt_plugin(self):
         if self.stt is None:
             self.stt_module_name = self.settings.default_stt_name
 
+        logger.debug("[OrderListener] stt module name : %s" % self.stt_module_name)
         for stt_object in self.settings.stts:
             if stt_object.name == self.stt_module_name:
                 stt_object.parameters["callback"] = self.callback

+ 30 - 0
kalliope/core/PlayerLauncher.py

@@ -0,0 +1,30 @@
+import logging
+
+from kalliope.core import Utils
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+
+class PlayerLauncher(object):
+    def __init__(self):
+        pass
+
+    @staticmethod
+    def get_player(settings):
+        """
+        Instantiate a Player
+        :param settings: setting object
+        :type settings: Settings
+        :return: the Player instance
+        :rtype: Player
+        """
+        player_instance = None
+        for player in settings.players:
+            if player.name == settings.default_player_name:
+                logger.debug("PlayerLauncher: Start player %s with parameters: %s" % (player.name, player.parameters))
+                player_instance = Utils.get_dynamic_class_instantiation(package_name="players",
+                                                                        module_name=player.name,
+                                                                        parameters=player.parameters)
+                break
+        return player_instance

+ 41 - 0
kalliope/core/PlayerModule.py

@@ -0,0 +1,41 @@
+# coding: utf8
+import logging
+import os
+import subprocess
+
+from kalliope.core.Utils.FileManager import FileManager
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+
+class PlayerModule(object):
+    """
+    Mother class of Players. 
+    Ability to convert mp3 to wave format.
+    """
+
+    def __init__(self, **kwargs):
+
+        # set parameter from what we receive from the settings
+        self.convert = kwargs.get('convert_to_wav', True)
+
+    @staticmethod
+    def convert_mp3_to_wav(file_path_mp3):
+        """ 
+        PyAudio, AlsaPlayer, sounddevices  do not support mp3 files 
+        MP3 files must be converted to a wave in order to be played
+        This function assumes ffmpeg is available on the system
+        :param file_path_mp3: the file path to convert from mp3 to wav
+        """
+        logger.debug("Converting mp3 file to wav file: %s" % file_path_mp3)
+        fnull = open(os.devnull, 'w')
+        # temp file
+        tmp_file_wav = file_path_mp3 + ".wav"
+        # Convert mp3 to wave
+        subprocess.call(['avconv', '-y', '-i', file_path_mp3, tmp_file_wav],
+                        stdout=fnull, stderr=fnull)
+        # remove the original file
+        FileManager.remove_file(file_path_mp3)
+        # rename the temp file with the same name as the original file
+        os.rename(tmp_file_wav, file_path_mp3)

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

@@ -1 +0,0 @@
-from Mplayer import Mplayer

+ 33 - 33
kalliope/core/RestAPI/FlaskAPI.py

@@ -4,18 +4,19 @@ import threading
 
 import time
 
+from kalliope.core.LIFOBuffer import LIFOBuffer
+from kalliope.core.Models.MatchedSynapse import MatchedSynapse
 from kalliope.core.Utils.FileManager import FileManager
 
-from kalliope.core.ConfigurationManager import SettingLoader
+from kalliope.core.ConfigurationManager import SettingLoader, BrainLoader
 from kalliope.core.OrderListener import OrderListener
 from werkzeug.utils import secure_filename
 
 from flask import jsonify
 from flask import request
 from flask_restful import abort
-from flask_cors import CORS, cross_origin
+from flask_cors import CORS
 
-from kalliope.core import OrderAnalyser
 from kalliope.core.RestAPI.utils import requires_auth
 from kalliope.core.SynapseLauncher import SynapseLauncher
 from kalliope._version import version_str
@@ -46,8 +47,8 @@ class FlaskAPI(threading.Thread):
         sl = SettingLoader()
         self.settings = sl.settings
 
-        # list of launched synapse by the Order Analyser when using the /synapses/start/audio URL
-        self.launched_synapses = None
+        # api_response sent by the Order Analyser when using the /synapses/start/audio URL
+        self.api_response = None
         # boolean used to notify the main process that we get the list of returned synapse
         self.order_analyser_return = False
 
@@ -137,18 +138,25 @@ class FlaskAPI(threading.Thread):
         :param synapse_name:
         :return:
         """
-        synapse_target = self._get_synapse_by_name(synapse_name)
+        # get a synapse object from the name
+        synapse_target = BrainLoader().get_brain().get_synapse_by_name(synapse_name=synapse_name)
 
         if synapse_target is None:
             data = {
                 "synapse name not found": "%s" % synapse_name
             }
             return jsonify(error=data), 404
-
-        # run the synapse
-        SynapseLauncher.start_synapse(synapse_name, brain=self.brain)
-        data = jsonify(synapses=synapse_target.serialize())
-        return data, 201
+        else:
+            # generate a MatchedSynapse from the synapse
+            matched_synapse = MatchedSynapse(matched_synapse=synapse_target)
+            # get the current LIFO buffer
+            lifo_buffer = LIFOBuffer()
+            # this is a new call we clean up the LIFO
+            lifo_buffer.clean()
+            lifo_buffer.add_synapse_list_to_lifo([matched_synapse])
+            response = lifo_buffer.execute(is_api_call=True)
+            data = jsonify(response)
+            return data, 201
 
     @requires_auth
     def run_synapse_by_order(self):
@@ -169,19 +177,14 @@ class FlaskAPI(threading.Thread):
         if order is not None:
             # get the order
             order_to_run = order["order"]
-            oa = OrderAnalyser(order=order_to_run, brain=self.brain)
-            launched_synapses = oa.start()
 
-            if launched_synapses:
-                # if the list is not empty, we have launched one or more synapses
-                data = jsonify(synapses=[e.serialize() for e in launched_synapses])
-                return data, 201
-            else:
-                data = {
-                    "error": "The given order doesn't match any synapses"
-                }
-                return jsonify(error=data), 400
+            api_response = SynapseLauncher.run_matching_synapse_from_order(order_to_run,
+                                                                           self.brain,
+                                                                           self.settings,
+                                                                           is_api_call=True)
 
+            data = jsonify(api_response)
+            return data, 201
         else:
             data = {
                 "error": "order cannot be null"
@@ -225,9 +228,9 @@ class FlaskAPI(threading.Thread):
             while not self.order_analyser_return:
                 time.sleep(0.1)
             self.order_analyser_return = False
-            if self.launched_synapses is not None and self.launched_synapses:
-                data = jsonify(synapses=[e.serialize() for e in self.launched_synapses])
-                self.launched_synapses = None
+            if self.api_response is not None and self.api_response:
+                data = jsonify(self.api_response)
+                self.api_response = None
                 return data, 201
             else:
                 data = {
@@ -255,14 +258,11 @@ class FlaskAPI(threading.Thread):
         :return:
         """
         logger.debug("order to process %s" % order)
-        if order is not None:  # maybe we have received a null audio from STT engine
-            order_analyser = OrderAnalyser(order, brain=self.brain)
-            synapses_launched = order_analyser.start()
-            self.launched_synapses = synapses_launched
-        else:
-            if self.settings.default_synapse is not None:
-                SynapseLauncher.start_synapse(name=self.settings.default_synapse, brain=self.brain)
-                self.launched_synapses = self.brain.get_synapse_by_name(synapse_name=self.settings.default_synapse)
+        api_response = SynapseLauncher.run_matching_synapse_from_order(order,
+                                                                       self.brain,
+                                                                       self.settings,
+                                                                       is_api_call=True)
+        self.api_response = api_response
 
         # this boolean will notify the main process that the order have been processed
         self.order_analyser_return = True

+ 1 - 14
kalliope/core/ShellGui.py

@@ -17,19 +17,6 @@ logging.basicConfig()
 logger = logging.getLogger("kalliope")
 
 
-def signal_handler(signal, frame):
-    """
-    Used to catch a keyboard signal like Ctrl+C in order to kill the kalliope program
-    :param signal: signal handler
-    :param frame: execution frame
-    """
-    print "\n"
-    Utils.print_info("Ctrl+C pressed. Killing Kalliope")
-    sys.exit(0)
-
-signal.signal(signal.SIGINT, signal_handler)
-
-
 class ShellGui:
     def __init__(self, brain=None):
         """
@@ -193,5 +180,5 @@ class ShellGui:
             self.show_main_menu()
         if code == self.d.OK:
             logger.debug("Run synapse from GUI: %s" % tag)
-            SynapseLauncher.start_synapse(tag, brain=self.brain)
+            SynapseLauncher.start_synapse_by_name(tag, brain=self.brain)
             self.show_synapses_test_menu()

+ 58 - 13
kalliope/core/SynapseLauncher.py

@@ -1,4 +1,14 @@
+import logging
+
+from kalliope.core.ConfigurationManager import BrainLoader
+from kalliope.core.LIFOBuffer import LIFOBuffer
+from kalliope.core.Models.MatchedSynapse import MatchedSynapse
 from kalliope.core.NeuronLauncher import NeuronLauncher
+from kalliope.core.OrderAnalyser import OrderAnalyser
+
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
 
 
 class SynapseNameNotFound(Exception):
@@ -12,32 +22,67 @@ class SynapseNameNotFound(Exception):
 
 class SynapseLauncher(object):
 
-    def __init__(self):
-        pass
-
     @classmethod
-    def start_synapse(cls, name, brain=None):
+    def start_synapse_by_name(cls, name, brain=None):
         """
         Start a synapse by it's name
         :param name: Name (Unique ID) of the synapse to launch
         :param brain: Brain instance
         """
-
+        logger.debug("[SynapseLauncher] start_synapse_by_name called with synapse name: %s " % name)
         # 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:
-            cls._run_synapse(synapse=synapse)
+            # get our singleton LIFO
+            lifo_buffer = LIFOBuffer()
+            list_synapse_to_process = list()
+            new_matching_synapse = MatchedSynapse(matched_synapse=synapse,
+                                                  matched_order=None,
+                                                  user_order=None)
+            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)
 
     @classmethod
-    def _run_synapse(cls, synapse):
+    def run_matching_synapse_from_order(cls, order_to_process, brain, settings, is_api_call=False):
         """
-        Start all neurons in the synapse
-        :param synapse: Synapse for which we run neurons
-        :return:
+        
+        :param order_to_process: the spoken order sent by the user
+        :param brain: Brain object
+        :param settings: Settings object
+        :param is_api_call: if True, the current call come from the API. This info must be known by launched Neuron
+        :return: list of matched synapse
         """
-        for neuron in synapse.neurons:
-            NeuronLauncher.start_neuron(neuron)
-        return True
+
+        # get our singleton LIFO
+        lifo_buffer = LIFOBuffer()
+
+        # 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:
+            # the LIFO is not empty, this is an answer to a previous call
+            return lifo_buffer.execute(answer=order_to_process, is_api_call=is_api_call)
+
+        else:  # the LIFO is empty, this is a new call
+            # get a list of matched synapse from the order
+            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 ")
+
+            lifo_buffer.add_synapse_list_to_lifo(list_synapse_to_process)
+            lifo_buffer.api_response.user_order = order_to_process
+
+            return lifo_buffer.execute(is_api_call=is_api_call)

+ 0 - 27
kalliope/core/TTS/TTSLauncher.py

@@ -1,27 +0,0 @@
-import logging
-
-from kalliope.core import Utils
-
-logging.basicConfig()
-logger = logging.getLogger("kalliope")
-
-
-class TTSLauncher(object):
-    def __init__(self):
-        pass
-
-    @classmethod
-    def get_tts(cls, tts):
-        """
-        Return an instance of a TTS module from the name of this module
-        :param tts: TTS model
-        :type tts: Tts
-        :return: TTS module instance
-
-        .. seealso::  TTS
-        .. warnings:: Class Method and Public
-        """
-        logger.debug("get TTS module \"%s\" with parameters %s" % (tts.name, tts.parameters))
-        return Utils.get_dynamic_class_instantiation(package_name="tts",
-                                                     module_name=tts.name,
-                                                     parameters=tts.parameters)

+ 11 - 5
kalliope/core/TTS/TTSModule.py

@@ -2,10 +2,14 @@
 import hashlib
 import logging
 import os
+import subprocess
+
+import six
 
 from kalliope.core.ConfigurationManager import SettingLoader
-from kalliope.core.Players import Mplayer
+from kalliope.core.PlayerLauncher import PlayerLauncher
 from kalliope.core.Utils.FileManager import FileManager
+from kalliope.core import Utils
 
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
@@ -45,7 +49,7 @@ class TTSModule(object):
 
         # set parameter from what we receive from the settings
         self.cache = kwargs.get('cache', False)
-        self.language = kwargs.get('language', None)
+        self.language = kwargs.get('language', "default")
         self.voice = kwargs.get('voice', "default")
         # the name of the TSS is the name of the Tss module that have instantiated TTSModule
         self.tts_caller_name = self.__class__.__name__
@@ -58,6 +62,7 @@ class TTSModule(object):
         # load settings
         sl = SettingLoader()
         self.settings = sl.settings
+        self.player = PlayerLauncher.get_player(settings=self.settings)
 
         # create the path in the tmp folder
         base_path = os.path.join(self.settings.cache_path, self.tts_caller_name, self.language, self.voice)
@@ -72,7 +77,8 @@ class TTSModule(object):
         """
         Play the audio file
         """
-        Mplayer.play(self.file_path)
+        # Mplayer.play(self.file_path)
+        self.player.play(self.file_path)
 
     def generate_and_play(self, words, generate_audio_function_from_child=None):
         """
@@ -132,7 +138,7 @@ class TTSModule(object):
         :param words: Text to convert into md5 hash
         :return: String md5 hash from the received words
         """
-        if isinstance(words, unicode):
+        if isinstance(words, six.text_type):
             words = words.encode('utf-8')
         return hashlib.md5(words).hexdigest()
 
@@ -151,4 +157,4 @@ class TTSModule(object):
             logger.debug("TTSModule, File already in cache: %s" % file_path)
         else:
             logger.debug("TTSModule, File not yet in cache: %s" % file_path)
-        return exist_in_cache
+        return exist_in_cache

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

@@ -0,0 +1 @@
+from .TTSModule import TTSModule

+ 17 - 11
kalliope/core/TriggerLauncher.py

@@ -10,19 +10,25 @@ class TriggerLauncher(object):
     def __init__(self):
         pass
 
-    @classmethod
-    def get_trigger(cls, trigger, callback):
+    @staticmethod
+    def get_trigger(settings, callback):
         """
         Start a trigger module
         :param trigger: trigger object to instantiate
         :type trigger: Trigger
-        :param callback: Callback function to call when the trigger
-        catch the magic word
-        :return:
+        :param callback: Callback function to call when the trigger catch the magic word
+        :return: The instance of Trigger 
+        :rtype: Trigger
         """
-        # add the callback method to parameters
-        trigger.parameters["callback"] = callback
-        logger.debug("TriggerLauncher: Start trigger %s with parameters: %s" % (trigger.name, trigger.parameters))
-        return Utils.get_dynamic_class_instantiation(package_name="trigger",
-                                                     module_name=trigger.name,
-                                                     parameters=trigger.parameters)
+        trigger_instance = None
+        for trigger in settings.triggers:
+            if trigger.name == settings.default_trigger_name:
+                # add the callback method to parameters
+                trigger.parameters["callback"] = callback
+                logger.debug(
+                    "TriggerLauncher: Start trigger %s with parameters: %s" % (trigger.name, trigger.parameters))
+                trigger_instance = Utils.get_dynamic_class_instantiation(package_name="trigger",
+                                                                         module_name=trigger.name,
+                                                                         parameters=trigger.parameters)
+                break
+        return trigger_instance

+ 8 - 2
kalliope/core/Utils/FileManager.py

@@ -1,6 +1,7 @@
 import logging
 import os
 
+import sys
 
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
@@ -36,11 +37,15 @@ class FileManager:
         """
         try:
             with open(file_path, "wb") as file_open:
-                file_open.write(content)
+                if sys.version_info[0] == 2:
+                    file_open.write(content)
+                else:
+                    file_open.write(content.encode())
                 file_open.close()
             return not FileManager.file_is_empty(file_path)
         except IOError as e:
             logger.error("I/O error(%s): %s", e.errno, e.strerror)
+            return False
 
     @staticmethod
     def file_is_empty(file_path):
@@ -82,5 +87,6 @@ class FileManager:
         """
         try:
             return os.path.exists(pathname) or FileManager.is_path_creatable(pathname)
-        except OSError:
+        except OSError as e:
+            logger.error("OSError(%s): %s", e.errno, e.strerror)
             return False

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff