Переглянути джерело

Merge pull request #71 from kalliope-project/dev

Dev
Nicolas Marcq 8 роки тому
батько
коміт
a909dbb7ff

+ 6 - 1
Docs/contributing.md

@@ -57,6 +57,11 @@ The constructor has a __**kwargs argument__ which is corresponding to the Dict o
         super(Say, self).__init__(**kwargs)
     ```
 
+1. You must run unit tests with success before sending a pull request. Add new tests that cover the code you want to publish.
+```
+cd /path/to/kalliope
+python -m unittest discover
+```
 
 1. (*optionnal-> good practice*) The Neuron can implement a __private method _is_parameters_ok(self)__ which checks if entries are ok. *return: true if parameters are ok, raise an exception otherwise*
 1. (*optionnal-> good practice*) The Neuron can __import and raise exceptions__ coming from NeuronModule:
@@ -99,4 +104,4 @@ They are managed like Neurons, you can follow the same process to develop your o
 
 *Incoming*
 
-We are maintening a list of all the Neurons available from the community, let us know
+We are maintening a list of all the Neurons available from the community, let us know

+ 37 - 42
core/ConfigurationManager/BrainLoader.py

@@ -4,6 +4,7 @@ import os
 
 from YAMLLoader import YAMLLoader
 from core.ConfigurationManager.ConfigurationChecker import ConfigurationChecker
+from core.Models import Singleton
 from core.Models.Brain import Brain
 from core.Models.Event import Event
 from core.Models.Neuron import Neuron
@@ -14,20 +15,21 @@ logging.basicConfig()
 logger = logging.getLogger("kalliope")
 
 
+@Singleton
 class BrainLoader(object):
     """
     This Class is used to get the brain YAML and the Brain as an object
     """
 
-    def __init__(self):
-        pass
+    def __init__(self, file_path=None):
+        logger.debug("Loading brain with file path: %s" % file_path)
+        self.file_path = file_path
+        self.yaml_config = self.get_yaml_config()
+        self.brain = self.get_brain()
 
-    @classmethod
-    def get_yaml_config(cls, file_path=None):
+    def get_yaml_config(self):
         """
         Class Methods which loads default or the provided YAML file and return it as a String
-        :param file_path: the brain file path to load
-        :type file_path: String
         :return: The loaded brain YAML
         :rtype: String
 
@@ -36,19 +38,15 @@ class BrainLoader(object):
 
         .. warnings:: Class Method
         """
-        if file_path is None:
-            brain_file_path = cls._get_root_brain_path()
+        if self.file_path is None:
+            brain_file_path = self._get_root_brain_path()
         else:
-            brain_file_path = file_path
+            brain_file_path = self.file_path
         return YAMLLoader.get_config(brain_file_path)
 
-    @classmethod
-    def get_brain(cls, file_path=None):
+    def get_brain(self):
         """
         Class Methods which loads default or the provided YAML file and return a Brain
-
-        :param file_path: the brain file path to load
-        :type file_path: String
         :return: The loaded Brain
         :rtype: Brain
 
@@ -61,35 +59,32 @@ class BrainLoader(object):
         """
 
         # Instantiate a brain
-        brain = Brain.Instance()
-        logger.debug("Is brain already loaded ? %r" % brain.is_loaded)
-        if brain.is_loaded is False:
-            # get the brain with dict
-            dict_brain = cls.get_yaml_config(file_path)
-
-            brain.brain_yaml = dict_brain
-            # create list of Synapse
-            synapses = list()
-            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 = cls._get_neurons(synapses_dict["neurons"])
-                        signals = cls._get_signals(synapses_dict["signals"])
-                        new_synapse = Synapse(name=name, neurons=neurons, signals=signals)
-                        synapses.append(new_synapse)
-            brain.synapses = synapses
-            if file_path is None:
-                brain.brain_file = cls._get_root_brain_path()
-            else:
-                brain.brain_file = file_path
-            # check that no synapse have the same name than another
-            if not ConfigurationChecker().check_synapes(synapses):
-                brain = None
+        brain = Brain()
+
+        # get the brain with dict
+        dict_brain = self.get_yaml_config()
+
+        brain.brain_yaml = dict_brain
+        # create list of Synapse
+        synapses = list()
+        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"])
+                    signals = self._get_signals(synapses_dict["signals"])
+                    new_synapse = Synapse(name=name, neurons=neurons, signals=signals)
+                    synapses.append(new_synapse)
+        brain.synapses = synapses
+        if self.file_path is None:
+            brain.brain_file = self._get_root_brain_path()
+        else:
+            brain.brain_file = self.file_path
+        # check that no synapse have the same name than another
+        if not ConfigurationChecker().check_synapes(synapses):
+            brain = None
 
-            # The Brain Singleton is loaded
-            brain.is_loaded = True
         return brain
 
     @staticmethod

+ 8 - 6
core/ConfigurationManager/ConfigurationChecker.py

@@ -62,6 +62,10 @@ class MultipleSameSynapseName(Exception):
     pass
 
 
+class NoValidOrder(Exception):
+    pass
+
+
 class ConfigurationChecker:
     """
     This Class provides all method to Check the configuration files are properly set up.
@@ -192,10 +196,8 @@ class ConfigurationChecker:
         .. raises:: NoEventPeriod
         .. warnings:: Static and Public
         """
-
-        if event_dict is None:
+        if event_dict is None or event_dict == "":
             raise NoEventPeriod("Event must contain a period: %s" % event_dict)
-
         return True
 
     @staticmethod
@@ -215,10 +217,10 @@ class ConfigurationChecker:
         .. seealso::  Order
         .. warnings:: Static and Public
         """
+        if order_dict is None or order_dict == "":
+            raise NoValidOrder("An order cannot be null or empty")
 
-        if order_dict is not None:
-            return True
-        return False
+        return True
 
     @staticmethod
     def check_synapes(synapses_list):

+ 53 - 58
core/ConfigurationManager/SettingLoader.py

@@ -2,6 +2,7 @@ import logging
 
 from YAMLLoader import YAMLLoader
 from core.FileManager import FileManager
+from core.Models import Singleton
 from core.Models.RestAPI import RestAPI
 from core.Models.Settings import Settings
 from core.Models.Stt import Stt
@@ -41,21 +42,25 @@ class SettingNotFound(Exception):
     pass
 
 
+@Singleton
 class SettingLoader(object):
     """
     This Class is used to get the Settings YAML and the Settings as an object
     """
 
-    def __init__(self):
-        pass
+    def __init__(self, file_path=None):
+        logger.debug("Loading settings with file path: %s" % file_path)
+        self.file_path = file_path
+        if self.file_path is None:
+            # use default file if not provided
+            self.file_path = FILE_NAME
+        self.yaml_config = self._get_yaml_config()
+        self.settings = self._get_settings()
 
-    @classmethod
-    def get_yaml_config(cls, file_path=None):
+    def _get_yaml_config(self):
         """
         Class Methods which loads default or the provided YAML file and return it as a String
 
-        :param file_path: the setting file path to load if None takes default
-        :type file_path: str
         :return: The loaded settings YAML
         :rtype: dict
 
@@ -64,18 +69,12 @@ class SettingLoader(object):
 
         .. warnings:: Class Method
         """
+        return YAMLLoader.get_config(self.file_path)
 
-        if file_path is None:
-            file_path = FILE_NAME
-        return YAMLLoader.get_config(file_path)
-
-    @classmethod
-    def get_settings(cls, file_path=None):
+    def _get_settings(self):
         """
         Class Methods which loads default or the provided YAML file and return a Settings Object
 
-        :param file_path: the setting file path to load
-        :type file_path: str
         :return: The loaded Settings
         :rtype: Settings
 
@@ -88,36 +87,32 @@ class SettingLoader(object):
         """
 
         # create a new setting
-        setting_object = Settings.Instance()
-        logger.debug("Is Settings already loaded ? %r" % setting_object.is_loaded)
-        if setting_object.is_loaded is False:
-
-            # Get the setting parameters
-            settings = cls.get_yaml_config(file_path)
-            default_stt_name = cls._get_default_speech_to_text(settings)
-            default_tts_name = cls._get_default_text_to_speech(settings)
-            default_trigger_name = cls._get_default_trigger(settings)
-            stts = cls._get_stts(settings)
-            ttss = cls._get_ttss(settings)
-            triggers = cls._get_triggers(settings)
-            random_wake_up_answers = cls._get_random_wake_up_answers(settings)
-            random_wake_up_sounds = cls._get_random_wake_up_sounds(settings)
-            rest_api = cls._get_rest_api(settings)
-            cache_path = cls._get_cache_path(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.stts = stts
-            setting_object.ttss = ttss
-            setting_object.triggers = triggers
-            setting_object.random_wake_up_answers = random_wake_up_answers
-            setting_object.random_wake_up_sounds = random_wake_up_sounds
-            setting_object.rest_api = rest_api
-            setting_object.cache_path = cache_path
-            # The Settings Singleton is loaded
-            setting_object.is_loaded = True
+        setting_object = Settings()
+
+        # Get the setting parameters
+        settings = self._get_yaml_config()
+        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)
+        stts = self._get_stts(settings)
+        ttss = self._get_ttss(settings)
+        triggers = self._get_triggers(settings)
+        random_wake_up_answers = self._get_random_wake_up_answers(settings)
+        random_wake_up_sounds = self._get_random_wake_up_sounds(settings)
+        rest_api = self._get_rest_api(settings)
+        cache_path = self._get_cache_path(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.stts = stts
+        setting_object.ttss = ttss
+        setting_object.triggers = triggers
+        setting_object.random_wake_up_answers = random_wake_up_answers
+        setting_object.random_wake_up_sounds = random_wake_up_sounds
+        setting_object.rest_api = rest_api
+        setting_object.cache_path = cache_path
 
         return setting_object
 
@@ -204,8 +199,8 @@ class SettingLoader(object):
         except KeyError, e:
             raise SettingNotFound("%s setting not found" % e)
 
-    @classmethod
-    def _get_stts(cls, settings):
+    @staticmethod
+    def _get_stts(settings):
         """
         Return a list of stt object
 
@@ -243,8 +238,8 @@ class SettingLoader(object):
                 stts.append(new_stt)
         return stts
 
-    @classmethod
-    def _get_ttss(cls, settings):
+    @staticmethod
+    def _get_ttss(settings):
         """
 
         Return a list of stt object
@@ -283,8 +278,8 @@ class SettingLoader(object):
                 ttss.append(new_tts)
         return ttss
 
-    @classmethod
-    def _get_triggers(cls, settings):
+    @staticmethod
+    def _get_triggers(settings):
         """
         Return a list of Trigger object
 
@@ -322,8 +317,8 @@ class SettingLoader(object):
                 triggers.append(new_trigger)
         return triggers
 
-    @classmethod
-    def _get_random_wake_up_answers(cls, settings):
+    @staticmethod
+    def _get_random_wake_up_answers(settings):
         """
         Return a list of the wake up answers set up on the settings.yml file
 
@@ -353,8 +348,8 @@ class SettingLoader(object):
 
         return random_wake_up_answers_list
 
-    @classmethod
-    def _get_random_wake_up_sounds(cls, settings):
+    @staticmethod
+    def _get_random_wake_up_sounds(settings):
         """
         Return a list of the wake up sounds set up on the settings.yml file
 
@@ -384,8 +379,8 @@ class SettingLoader(object):
 
         return random_wake_up_sounds_list
 
-    @classmethod
-    def _get_rest_api(cls, settings):
+    @staticmethod
+    def _get_rest_api(settings):
         """
         Return the settings of the RestApi
 
@@ -446,8 +441,8 @@ class SettingLoader(object):
         else:
             raise NullSettingException("rest_api settings cannot be null")
 
-    @classmethod
-    def _get_cache_path(cls, settings):
+    @staticmethod
+    def _get_cache_path(settings):
         """
         Return the path where to store the cache
 

+ 2 - 1
core/MainController.py

@@ -24,7 +24,8 @@ class MainController:
     def __init__(self, brain=None):
         self.brain = brain
         # get global configuration
-        self.settings = SettingLoader.get_settings()
+        sl = SettingLoader.Instance()
+        self.settings = sl.settings
 
         # run the api if the user want it
         if self.settings.rest_api.active:

+ 8 - 5
core/Models/Brain.py

@@ -1,9 +1,5 @@
-from core.Models import Singleton
 
-
-@Singleton
 class Brain:
-    # TODO review the Singleton, should be Instantiate at the BrainLoader level
     """
     This Class is a Singleton Representing the Brain.yml file with synapse
     .. note:: the is_loaded Boolean is True when the Brain has been properly loaded.
@@ -13,4 +9,11 @@ class Brain:
         self.synapses = synapses
         self.brain_file = brain_file
         self.brain_yaml = brain_yaml
-        self.is_loaded = False
+
+    def __eq__(self, other):
+        """
+        This is used to compare 2 objects
+        :param other:
+        :return:
+        """
+        return self.__dict__ == other.__dict__

+ 8 - 0
core/Models/Event.py

@@ -23,3 +23,11 @@ class Event(object):
         return {
             'event': self.period
         }
+
+    def __eq__(self, other):
+        """
+        This is used to compare 2 objects
+        :param other:
+        :return:
+        """
+        return self.__dict__ == other.__dict__

+ 11 - 0
core/Models/Neuron.py

@@ -20,3 +20,14 @@ class Neuron(object):
             'name': self.name,
             'parameters': str(self.parameters)
         }
+
+    def __str__(self):
+        return "Neuron: name: %s, parameters: %s" % (self.name, self.parameters)
+
+    def __eq__(self, other):
+        """
+        This is used to compare 2 objects
+        :param other:
+        :return:
+        """
+        return self.__dict__ == other.__dict__

+ 8 - 0
core/Models/Order.py

@@ -22,3 +22,11 @@ class Order(object):
         return {
             'order': self.sentence
         }
+
+    def __eq__(self, other):
+        """
+        This is used to compare 2 objects
+        :param other:
+        :return:
+        """
+        return self.__dict__ == other.__dict__

+ 8 - 0
core/Models/RestAPI.py

@@ -25,3 +25,11 @@ class RestAPI(object):
                                                        self.password,
                                                        self.active,
                                                        self.port)
+
+    def __eq__(self, other):
+        """
+        This is used to compare 2 objects
+        :param other:
+        :return:
+        """
+        return self.__dict__ == other.__dict__

+ 8 - 4
core/Models/Settings.py

@@ -1,10 +1,7 @@
-from core.Models import Singleton
 import platform
 
 
-@Singleton
 class Settings(object):
-    # TODO review the Singleton, should be Instantiate at the BrainLoader level
     """
     This Class is a Singleton Representing the settings.yml file with synapse
 
@@ -33,5 +30,12 @@ class Settings(object):
         self.triggers = triggers
         self.rest_api = rest_api
         self.cache_path = cache_path
-        self.is_loaded = False
         self.machine = platform.machine()   # can be x86_64 or armv7l
+
+    def __eq__(self, other):
+        """
+        This is used to compare 2 objects
+        :param other:
+        :return:
+        """
+        return self.__dict__ == other.__dict__

+ 2 - 2
core/Models/Singleton.py

@@ -20,7 +20,7 @@ class Singleton:
     def __init__(self, decorated):
         self._decorated = decorated
 
-    def Instance(self):
+    def Instance(self, **kwargs):
         """
         Returns the singleton instance. Upon its first call, it creates a
         new instance of the decorated class and calls its `__init__` method.
@@ -30,7 +30,7 @@ class Singleton:
         try:
             return self._instance
         except AttributeError:
-            self._instance = self._decorated()
+            self._instance = self._decorated(**kwargs)
             return self._instance
 
     def __call__(self):

+ 9 - 1
core/Models/Stt.py

@@ -10,4 +10,12 @@ class Stt(object):
         self.parameters = parameters
 
     def __str__(self):
-        return "Stt name: %s, parameters: %s" % (str(self.name), str(self.parameters))
+        return "Stt name: %s, parameters: %s" % (str(self.name), str(self.parameters))
+
+    def __eq__(self, other):
+        """
+        This is used to compare 2 objects
+        :param other:
+        :return:
+        """
+        return self.__dict__ == other.__dict__

+ 18 - 0
core/Models/Synapse.py

@@ -23,3 +23,21 @@ class Synapse(object):
             'neurons': [e.serialize() for e in self.neurons],
             'signals': [e.serialize() for e in self.signals]
         }
+
+    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
+
+    def __eq__(self, other):
+        """
+        This is used to compare 2 objects
+        :param other:
+        :return:
+        """
+        return self.__dict__ == other.__dict__

+ 8 - 0
core/Models/Trigger.py

@@ -11,3 +11,11 @@ class Trigger(object):
 
     def __str__(self):
         return "Trigger name: %s, parameters: %s" % (str(self.name), str(self.parameters))
+
+    def __eq__(self, other):
+        """
+        This is used to compare 2 objects
+        :param other:
+        :return:
+        """
+        return self.__dict__ == other.__dict__

+ 7 - 0
core/Models/Tts.py

@@ -12,3 +12,10 @@ class Tts(object):
     def __str__(self):
         return "Tts name: %s, parameters: %s" % (str(self.name), str(self.parameters))
 
+    def __eq__(self, other):
+        """
+        This is used to compare 2 objects
+        :param other:
+        :return:
+        """
+        return self.__dict__ == other.__dict__

+ 4 - 2
core/NeuronModule.py

@@ -65,8 +65,10 @@ class NeuronModule(object):
         child_name = self.__class__.__name__
         logger.debug("NeuronModule called from class %s with parameters: %s" % (child_name, str(kwargs)))
 
-        self.settings = SettingLoader.get_settings()
-        self.brain = BrainLoader.get_brain()
+        sl = SettingLoader.Instance()
+        self.settings = sl.settings
+        brain_loader = BrainLoader.Instance()
+        self.brain = brain_loader.brain
 
         # check if the user has overrider the TTS
         tts = kwargs.get('tts', None)

+ 2 - 1
core/OrderListener.py

@@ -39,7 +39,8 @@ class OrderListener(Thread):
         self._ignore_stderr()
         self.stt_module_name = stt
         self.callback = callback
-        self.settings = SettingLoader.get_settings()
+        sl = SettingLoader.Instance()
+        self.settings = sl.settings
 
     def run(self):
         """

+ 4 - 2
core/RestAPI/utils.py

@@ -8,7 +8,8 @@ def check_auth(username, password):
     """This function is called to check if a username /
     password combination is valid.
     """
-    settings = SettingLoader.get_settings()
+    sl = SettingLoader.Instance()
+    settings = sl.settings
     return username == settings.rest_api.login and password == settings.rest_api.password
 
 
@@ -23,7 +24,8 @@ def authenticate():
 def requires_auth(f):
     @wraps(f)
     def decorated(*args, **kwargs):
-        settings = SettingLoader.get_settings()
+        sl = SettingLoader.Instance()
+        settings = sl.settings
         if settings.rest_api.password_protected:
             auth = request.authorization
             if not auth or not check_auth(auth.username, auth.password):

+ 2 - 1
core/ShellGui.py

@@ -43,7 +43,8 @@ class ShellGui:
         self.brain = brain
 
         # get settings
-        self.settings = SettingLoader.get_settings()
+        sl = SettingLoader.Instance()
+        self.settings = sl.settings
         locale.setlocale(locale.LC_ALL, '')
 
         self.d = Dialog(dialog="dialog")

+ 2 - 1
core/TTS/TTSModule.py

@@ -56,7 +56,8 @@ class TTSModule(object):
         self.base_cache_path = None
 
         # load settings
-        self.settings = SettingLoader.get_settings()
+        sl = SettingLoader.Instance()
+        self.settings = sl.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)

+ 19 - 0
core/Tests/brains/brain_test.yml

@@ -0,0 +1,19 @@
+---
+  - name: "test"
+    signals:
+      - order: "test_order"
+    neurons:
+      - say:
+          message:
+            - "test message"
+
+  - name: "test2"
+    signals:
+      - order: "test_order_2"
+    neurons:
+      - say:
+          message:
+            - "test message"
+
+  - includes:
+    - included_brain_test.yml

+ 8 - 0
core/Tests/brains/included_brain_test.yml

@@ -0,0 +1,8 @@
+---
+  - name: "test3"
+    signals:
+      - order: "test_order_3"
+    neurons:
+      - say:
+          message:
+            - "test message"

+ 73 - 0
core/Tests/settings/settings_test.yml

@@ -0,0 +1,73 @@
+---
+# ---------------------------
+# Kalliope default settings
+# Use YAML syntax
+# ---------------------------
+
+# ---------------------------
+# Trigger
+# ---------------------------
+# This is the trigger that will catch your magic work to wake up Kalliope
+default_trigger: "snowboy"
+
+# Trigger engine configuration
+# Available engine are:
+# - snowboy
+triggers:
+  - snowboy:
+      pmdl_file: "trigger/snowboy/resources/kalliope-FR-6samples.pmdl"
+
+
+# ---------------------------
+# Speech to text
+# ---------------------------
+
+# This is the STT that will be used by default
+default_speech_to_text: "google"
+
+speech_to_text:
+  - google:
+      language: "fr-FR"
+
+
+# ---------------------------
+# Text to speech
+# ---------------------------
+# This is the default TTS that will be used by Kalliope to talk.
+default_text_to_speech: "pico2wave"
+# where we store generated audio files from TTS engine to reuse them
+cache_path: "/tmp/kalliope_tts_cache"
+
+text_to_speech:
+  - pico2wave:
+      language: "fr-FR"
+      cache: True
+  - voxygen:
+      voice: "Agnes"
+      cache: True
+
+# ---------------------------
+# Wake up answers
+# ---------------------------
+# When Kalliope detect the hotword/trigger, he will select randomly a phrase in the following list
+# to notify the user that he's listening for orders
+random_wake_up_answers:
+  - "Oui monsieur?"
+
+# You can play a sound when Kalliope detect the hotword/trigger instead of saying something from
+# the `random_wake_up_answers`.
+# Place here the full path of the sound file or just the name of the file in /usr/lib/kalliope/sounds
+# The file must be .wav or .mp3 format. By default two file are provided: ding.wav and dong.wav
+random_wake_up_sounds:
+  - "ding.wav"
+  - "dong.wav"
+
+# ---------------------------
+# Rest API
+# ---------------------------
+rest_api:
+  active: True
+  port: 5000
+  password_protected: True
+  login: admin
+  password: secret

+ 108 - 0
core/Tests/test_brain_loader.py

@@ -0,0 +1,108 @@
+import unittest
+
+from core.ConfigurationManager import BrainLoader
+from core.Models import Brain
+from core.Models import Event
+from core.Models import Neuron
+from core.Models import Order
+from core.Models import Synapse
+
+
+class TestBrainLoader(unittest.TestCase):
+
+    def setUp(self):
+        self.brain_to_test = "core/Tests/brains/brain_test.yml"
+        self.expected_result = [
+            {'signals': [{'order': 'test_order'}],
+             'neurons': [{'say': {'message': ['test message']}}],
+             'name': 'test'},
+            {'signals': [{'order': 'test_order_2'}],
+             'neurons': [{'say': {'message': ['test message']}}],
+             'name': 'test2'},
+            {'includes': ['included_brain_test.yml']},
+            {'signals': [{'order': 'test_order_3'}],
+             'neurons': [{'say': {'message': ['test message']}}],
+             'name': 'test3'}
+        ]
+
+    def test_get_yaml_config(self):
+        """
+        Test we can get a yaml config from the path
+        """
+        brain_loader = BrainLoader.Instance(file_path=self.brain_to_test)
+        self.assertEqual(brain_loader.yaml_config, self.expected_result)
+        del brain_loader
+
+    def test_get_brain(self):
+        """
+        Test the class return a valid brain object
+        """
+
+        neuron = Neuron(name='say', parameters={'message': ['test message']})
+
+        signal1 = Order(sentence="test_order")
+        signal2 = Order(sentence="test_order_2")
+        signal3 = Order(sentence="test_order_3")
+
+        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]
+
+        brain = Brain()
+        brain.synapses = synapses
+        brain.brain_file = self.brain_to_test
+        brain.brain_yaml = self.expected_result
+
+        brain_loader = BrainLoader.Instance(file_path=self.brain_to_test)
+        self.assertEqual(brain, brain_loader.brain)
+        del brain_loader
+
+    def test_get_neurons(self):
+        neuron_list = [{'say': {'message': ['test message']}}]
+
+        neuron = Neuron(name='say', parameters={'message': ['test message']})
+
+        bl = BrainLoader.Instance(file_path=self.brain_to_test)
+        neurons_from_brain_loader = bl._get_neurons(neuron_list)
+
+        self.assertEqual([neuron], neurons_from_brain_loader)
+        del bl
+
+    def test_get_signals(self):
+        signals = [{'order': 'test_order'}]
+
+        signal = Order(sentence='test_order')
+
+        bl = BrainLoader.Instance(file_path=self.brain_to_test)
+        signals_from_brain_loader = bl._get_signals(signals)
+
+        self.assertEqual([signal], signals_from_brain_loader)
+        del bl
+
+    def test_get_event_or_order_from_dict(self):
+
+        order_object = Order(sentence="test_order")
+        event_object = Event(period="0 7 * * *")
+
+        dict_order = {'order': 'test_order'}
+        dict_event = {'event': '0 7 * * *'}
+
+        bl = BrainLoader.Instance(file_path=self.brain_to_test)
+        order_from_bl = bl._get_event_or_order_from_dict(dict_order)
+        event_from_bl = bl._get_event_or_order_from_dict(dict_event)
+
+        self.assertEqual(order_from_bl, order_object)
+        self.assertEqual(event_from_bl, event_object)
+        del bl
+
+    def test_singleton(self):
+        bl1 = BrainLoader.Instance(file_path=self.brain_to_test)
+        bl2 = BrainLoader.Instance(file_path=self.brain_to_test)
+
+        self.assertTrue(bl1.brain is bl2.brain)
+        del bl1
+        del bl2
+
+if __name__ == '__main__':
+    unittest.main()

+ 109 - 0
core/Tests/test_configuration_checker.py

@@ -0,0 +1,109 @@
+import unittest
+
+from core.ConfigurationManager.ConfigurationChecker import ConfigurationChecker, NoSynapeName, NoSynapeNeurons, \
+    NoSynapeSignals, NoValidSignal, NoEventPeriod, NoValidOrder, MultipleSameSynapseName
+from core.Models import Synapse
+from core.Utils import ModuleNotFoundError
+
+
+class TestConfigurationChecker(unittest.TestCase):
+    """
+    Class used to test the ConfigurationChecker class
+    """
+
+    def setUp(self):
+        pass
+
+    def test_check_synape_dict(self):
+        valid_synapse_dict = {
+            'signals': [{'order': 'test_order'}],
+            'neurons': [{'say': {'message': ['test message']}}],
+            'name': 'test'
+        }
+
+        synapse_dict_without_name = {
+            'signals': [{'order': 'test_order'}],
+            'neurons': [{'say': {'message': ['test message']}}]
+        }
+
+        synapse_dict_without_neurons = {
+            'signals': [{'order': 'test_order'}],
+            'name': 'test'
+        }
+
+        synapse_dict_without_signals = {
+            'neurons': [{'say': {'message': ['test message']}}],
+            'name': 'test'
+        }
+
+        self.assertTrue(ConfigurationChecker.check_synape_dict(valid_synapse_dict))
+
+        with self.assertRaises(NoSynapeName):
+            ConfigurationChecker.check_synape_dict(synapse_dict_without_name)
+
+        with self.assertRaises(NoSynapeNeurons):
+            ConfigurationChecker.check_synape_dict(synapse_dict_without_neurons)
+
+        with self.assertRaises(NoSynapeSignals):
+            ConfigurationChecker.check_synape_dict(synapse_dict_without_signals)
+
+    def test_check_neuron_dict(self):
+        valid_neuron = {'say': {'message': ['test message']}}
+        invalid_neuron = {'not_existing_neuron': {'message': ['test message']}}
+
+        self.assertTrue(ConfigurationChecker.check_neuron_dict(valid_neuron))
+
+        with self.assertRaises(ModuleNotFoundError):
+            ConfigurationChecker.check_neuron_dict(invalid_neuron)
+
+    def test_check_signal_dict(self):
+        valid_signal_with_order = {'order': 'test_order'}
+        valid_signal_with_event = {'event': '0 * * * *'}
+        invalid_signal = {'invalid_option': 'test_order'}
+
+        self.assertTrue(ConfigurationChecker.check_signal_dict(valid_signal_with_order))
+        self.assertTrue(ConfigurationChecker.check_signal_dict(valid_signal_with_event))
+
+        with self.assertRaises(NoValidSignal):
+            ConfigurationChecker.check_signal_dict(invalid_signal)
+
+    def test_check_event_dict(self):
+        valid_event = '0 * * * *'
+        invalid_event = None
+        invalid_event2 = ""
+
+        self.assertTrue(ConfigurationChecker.check_event_dict(valid_event))
+
+        with self.assertRaises(NoEventPeriod):
+            ConfigurationChecker.check_event_dict(invalid_event)
+        with self.assertRaises(NoEventPeriod):
+            ConfigurationChecker.check_event_dict(invalid_event2)
+
+    def test_check_order_dict(self):
+        valid_order = 'test_order'
+        invalid_order = ''
+        invalid_order2 = None
+
+        self.assertTrue(ConfigurationChecker.check_order_dict(valid_order))
+
+        with self.assertRaises(NoValidOrder):
+            ConfigurationChecker.check_order_dict(invalid_order)
+        with self.assertRaises(NoValidOrder):
+            ConfigurationChecker.check_order_dict(invalid_order2)
+
+    def test_check_synapes(self):
+        synapse_1 = Synapse(name="test")
+        synapse_2 = Synapse(name="test2")
+        synapse_3 = Synapse(name="test")
+
+        valid_synapse_list = [synapse_1, synapse_2]
+        invalid_synapse_list = [synapse_1, synapse_3]
+
+        self.assertTrue(ConfigurationChecker.check_synapes(valid_synapse_list))
+
+        with self.assertRaises(MultipleSameSynapseName):
+            ConfigurationChecker.check_synapes(invalid_synapse_list)
+
+
+if __name__ == '__main__':
+    unittest.main()

+ 3 - 3
core/Tests/test_dynamic_loading.py

@@ -54,7 +54,7 @@ class TestDynamicLoading(unittest.TestCase):
 
     def test_03_can_import_stt(self):
         """
-        Try to import each neurons that are present in the neurons package
+        Try to import each stt that are present in the stt package
         :return:
         """
         stts = self.get_package_in_folder(self.stt_dir)
@@ -65,7 +65,7 @@ class TestDynamicLoading(unittest.TestCase):
 
     def test_04_can_import_tts(self):
         """
-        Try to import each neurons that are present in the neurons package
+        Try to import each tts that are present in the tts package
         :return:
         """
         ttss = self.get_package_in_folder(self.tts_dir)
@@ -76,7 +76,7 @@ class TestDynamicLoading(unittest.TestCase):
 
     def test_05_can_import_trigger(self):
         """
-        Try to import each neurons that are present in the neurons package
+        Try to import each trigger that are present in the trigger package
         :return:
         """
         triggers = self.get_package_in_folder(self.trigger_dir)

+ 7 - 4
core/Tests/test_order_analyser.py

@@ -11,6 +11,9 @@ class TestOrderAnalyser(unittest.TestCase):
 
     """Test case for the OrderAnalyser Class"""
 
+    def setUp(self):
+        pass
+
     def test_is_containing_bracket(self):
         #  Success
         order_to_test = "This test contains {{ bracket }}"
@@ -205,9 +208,9 @@ class TestOrderAnalyser(unittest.TestCase):
         signal2 = Order(sentence="this is the second sentence")
         signal3 = Order(sentence="this is 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})
+        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,
@@ -231,7 +234,7 @@ class TestOrderAnalyser(unittest.TestCase):
 
         signal1 = Order(sentence="this is the {{ sentence }}")
 
-        synapse1 = Synapse(name="Synapse1", neurons={neuron1, neuron2}, signals={signal1})
+        synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1])
 
         order_to_check = "this is the value"
         expected_result = {'sentence': 'value'}

+ 139 - 0
core/Tests/test_settings_loader.py

@@ -0,0 +1,139 @@
+import platform
+import unittest
+
+from core.ConfigurationManager import SettingLoader
+from core.Models.RestAPI import RestAPI
+from core.Models.Settings import Settings
+from core.Models.Stt import Stt
+from core.Models.Trigger import Trigger
+from core.Models.Tts import Tts
+
+
+class TestSettingLoader(unittest.TestCase):
+
+    def setUp(self):
+
+        self.settings_file_to_test = "core/Tests/settings/settings_test.yml"
+
+        self.settings_dict = {
+            'rest_api':
+                {'active': True,
+                 'login': 'admin',
+                 'password': 'secret',
+                 'password_protected': True,
+                 'port': 5000},
+            'default_trigger': 'snowboy',
+            'triggers': [{'snowboy': {'pmdl_file': 'trigger/snowboy/resources/kalliope-FR-6samples.pmdl'}}],
+            'speech_to_text': [{'google': {'language': 'fr-FR'}}],
+            'cache_path': '/tmp/kalliope_tts_cache',
+            'random_wake_up_answers': ['Oui monsieur?'],
+            'default_text_to_speech': 'pico2wave',
+            'default_speech_to_text': 'google',
+            'random_wake_up_sounds': ['ding.wav', 'dong.wav'],
+            'text_to_speech': [
+                {'pico2wave': {'cache': True, 'language': 'fr-FR'}},
+                {'voxygen': {'voice': 'Agnes', 'cache': True}}
+            ]
+        }
+
+    def test_singleton(self):
+        s1 = SettingLoader.Instance(file_path=self.settings_file_to_test)
+        s2 = SettingLoader.Instance(file_path=self.settings_file_to_test)
+
+        self.assertTrue(s1.settings is s2.settings)
+
+        del s1
+        del s2
+
+    # def test_get_yaml_config(self):
+    #
+    #     sl = SettingLoader.Instance(file_path=self.settings_file_to_test)
+    #     self.assertEqual(sl.yaml_config, self.settings_dict)
+    #     del sl
+    #
+    # def test_get_settings(self):
+    #     settings_object = Settings()
+    #     settings_object.default_tts_name = "pico2wave"
+    #     settings_object.default_stt_name = "google"
+    #     settings_object.default_trigger_name = "snowboy"
+    #     tts1 = Tts(name="pico2wave", parameters={'cache': True, 'language': 'fr-FR'})
+    #     tts2 = Tts(name="voxygen", parameters={'voice': 'Agnes', 'cache': True})
+    #     settings_object.ttss = [tts1, tts2]
+    #     stt = Stt(name="google", parameters={'language': 'fr-FR'})
+    #     settings_object.stts = [stt]
+    #     settings_object.random_wake_up_answers = ['Oui monsieur?']
+    #     settings_object.random_wake_up_sounds = ['ding.wav', 'dong.wav']
+    #     trigger1 = Trigger(name="snowboy",
+    #                        parameters={'pmdl_file': 'trigger/snowboy/resources/kalliope-FR-6samples.pmdl'})
+    #     settings_object.triggers = [trigger1]
+    #     settings_object.rest_api = RestAPI(password_protected=True, active=True,
+    #                                        login="admin", password="secret", port=5000)
+    #     settings_object.cache_path = '/tmp/kalliope_tts_cache'
+    #     settings_object.machine = platform.machine()
+    #
+    #     sl = SettingLoader.Instance(file_path=self.settings_file_to_test)
+    #
+    #     self.assertEqual(settings_object, sl.settings)
+    #     del sl
+
+    def test_get_default_speech_to_text(self):
+        expected_default_speech_to_text = "google"
+        sl = SettingLoader.Instance(file_path=self.settings_file_to_test)
+
+        self.assertEqual(expected_default_speech_to_text, sl._get_default_speech_to_text(self.settings_dict))
+        del sl
+
+    def test_get_default_text_to_speech(self):
+        expected_default_text_to_speech = "pico2wave"
+        sl = SettingLoader.Instance(file_path=self.settings_file_to_test)
+        self.assertEqual(expected_default_text_to_speech, sl._get_default_text_to_speech(self.settings_dict))
+        del sl
+
+    def test_get_default_trigger(self):
+        expected_default_trigger = "snowboy"
+        sl = SettingLoader.Instance(file_path=self.settings_file_to_test)
+        self.assertEqual(expected_default_trigger, sl._get_default_trigger(self.settings_dict))
+        del sl
+
+    def test_get_stts(self):
+        stt = Stt(name="google", parameters={'language': 'fr-FR'})
+        sl = SettingLoader.Instance(file_path=self.settings_file_to_test)
+        self.assertEqual([stt], sl._get_stts(self.settings_dict))
+        del sl
+
+    def test_get_ttss(self):
+        tts1 = Tts(name="pico2wave", parameters={'cache': True, 'language': 'fr-FR'})
+        tts2 = Tts(name="voxygen", parameters={'voice': 'Agnes', 'cache': True})
+        sl = SettingLoader.Instance(file_path=self.settings_file_to_test)
+        self.assertEqual([tts1, tts2], sl._get_ttss(self.settings_dict))
+        del sl
+
+    def test_get_triggers(self):
+        trigger1 = Trigger(name="snowboy",
+                           parameters={'pmdl_file': 'trigger/snowboy/resources/kalliope-FR-6samples.pmdl'})
+        sl = SettingLoader.Instance(file_path=self.settings_file_to_test)
+        self.assertEqual([trigger1], sl._get_triggers(self.settings_dict))
+        del sl
+
+    def test_get_random_wake_up_answers(self):
+        expected_random_wake_up_answers = ['Oui monsieur?']
+        sl = SettingLoader.Instance(file_path=self.settings_file_to_test)
+        self.assertEqual(expected_random_wake_up_answers, sl._get_random_wake_up_answers(self.settings_dict))
+        del sl
+
+    def test_get_rest_api(self):
+        expected_rest_api = RestAPI(password_protected=True, active=True,
+                                    login="admin", password="secret", port=5000)
+
+        sl = SettingLoader.Instance(file_path=self.settings_file_to_test)
+        self.assertEqual(expected_rest_api, sl._get_rest_api(self.settings_dict))
+        del sl
+
+    def test_get_cache_path(self):
+        expected_cache_path = '/tmp/kalliope_tts_cache'
+        sl = SettingLoader.Instance(file_path=self.settings_file_to_test)
+        self.assertEqual(expected_cache_path, sl._get_cache_path(self.settings_dict))
+        del sl
+
+if __name__ == '__main__':
+    unittest.main()

+ 38 - 0
core/Tests/test_yaml_loader.py

@@ -0,0 +1,38 @@
+import unittest
+
+from core.ConfigurationManager.YAMLLoader import YAMLFileNotFound, YAMLLoader
+
+
+class TestYAMLLoader(unittest.TestCase):
+    """
+    Class to test YAMLLoader
+    """
+
+    def setUp(self):
+        pass
+
+    def test_get_config(self):
+
+        valid_file_path_to_test = "core/Tests/brains/brain_test.yml"
+        invalid_file_path = "brains/non_existing_brain.yml"
+        expected_result = [
+            {'signals': [{'order': 'test_order'}],
+             'neurons': [{'say': {'message': ['test message']}}],
+             'name': 'test'},
+            {'signals': [{'order': 'test_order_2'}],
+             'neurons': [{'say': {'message': ['test message']}}],
+             'name': 'test2'},
+            {'includes': ['included_brain_test.yml']},
+            {'signals': [{'order': 'test_order_3'}],
+             'neurons': [{'say': {'message': ['test message']}}],
+             'name': 'test3'}
+        ]
+
+        with self.assertRaises(YAMLFileNotFound):
+            YAMLLoader.get_config(invalid_file_path)
+
+        self.assertEqual(YAMLLoader.get_config(valid_file_path_to_test), expected_result)
+
+
+if __name__ == '__main__':
+    unittest.main()

+ 2 - 1
kalliope.py

@@ -61,7 +61,8 @@ def main():
     if args.brain_file:
         brain_file = args.brain_file
     # load the brain once
-    brain = BrainLoader.get_brain(file_path=brain_file)
+    brain_loader = BrainLoader.Instance(file_path=brain_file)
+    brain = brain_loader.brain
 
     # check the user provide a valid action
     if args.action not in ACTION_LIST:

+ 22 - 0
manual_testing.py

@@ -0,0 +1,22 @@
+# coding: utf8
+import logging
+
+from core.ConfigurationManager.BrainLoader import BrainLoader
+from core.ConfigurationManager.SettingLoader import SettingLoader
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+logger.setLevel(logging.DEBUG)
+
+# file_path = "core/Tests/brains/brain_test.yml"
+#
+# brainloader1 = BrainLoader.Instance(file_path=file_path)
+#
+# brainloader2 = BrainLoader.Instance(file_path=file_path)
+
+sl = SettingLoader.Instance(file_path="core/Tests/settings/settings_test.yml")
+print sl.yaml_config
+
+
+
+

+ 0 - 17
test.py

@@ -1,17 +0,0 @@
-# coding: utf8
-import logging
-
-from core.ConfigurationManager.BrainLoader import BrainLoader
-from core.ConfigurationManager.SettingLoader import SettingLoader
-
-logging.basicConfig()
-logger = logging.getLogger("kalliope")
-logger.setLevel(logging.DEBUG)
-
-
-brain = BrainLoader.get_brain()
-print brain.brain_yaml
-for synapse in brain.synapses:
-    print "test"
-    print synapse.name
-

+ 0 - 5
trigger/snowboy/snowboydecoder.py

@@ -4,13 +4,8 @@ import collections
 from threading import Thread
 
 import pyaudio
-import sys
-
-import signal
-
 import snowboydetect
 import time
-import wave
 import os
 import logging
 

+ 2 - 1
trigger/snowboy/snowboydetect.py

@@ -6,7 +6,8 @@
 from core.ConfigurationManager import SettingLoader
 from sys import version_info
 
-settings = SettingLoader.get_settings()
+sl = SettingLoader.Instance()
+settings = sl.settings
 module_file_path = "%s/_snowboydetect" % settings.machine