Browse Source

Merge pull request #11 from kalliope-project/yaml_include

Yaml include
Nicolas Marcq 8 years ago
parent
commit
9e40a0355b

+ 22 - 0
Docs/brain.md

@@ -97,3 +97,25 @@ signals:
   - order: "say"
 ```
 When you will pronounce "say hello", it will trigger both synapses. 
+
+
+## Split the brain
+
+If you want a better visibly, or simply sort your actions with different file, you can split the main brain file into multiple one.
+
+To do that, use the import statement in the entry brain.yml file with the following syntax:
+
+```
+---
+  - !include path/to/sub_brain.yml
+  - !include path/to/another_sub_brain.yml
+```
+
+E.g:
+```
+---
+  - !include brains/rolling_shutter_commands.yml
+  - !include brains/find_my_phone.yml
+```
+
+>**Note:** You can only use the `!include` statement in the main brain file. 

+ 1 - 1
Docs/neuron_list.md

@@ -5,12 +5,12 @@ A neuron is a module you can use in your synapses. See the [complete neuron docu
 | Name                                               | Description                                                                             |
 |----------------------------------------------------|-----------------------------------------------------------------------------------------|
 | [ansible_task](../neurons/ansible_task/)           | Run an ansible playbook                                                                 |
-| [command](../neurons/command/)                     | Run a shell command                                                                     |
 | [gmail_checker](../neurons/gmail_checker/)         | Get the number of unread email and their subjects from a gmail account                  |
 | [kill_switch](../neurons/kill_switch/)             | Stop Jarvis process                                                                     |
 | [push_message](../neurons/push_message/)           | Send a push message to a remote device like Android/iOS/Windows Phone or Chrome browser |
 | [say](../neurons/say/)                             | Make Jarvis talk by using TTS                                                           |
 | [script](../neurons/script/)                       | Run an executable script                                                                |
+| [shell](../neurons/command/)                       | Run a shell command                                                                     |
 | [sleep](../neurons/sleep/)                         | Make Jarvis sleep for a while before continuing                                         |
 | [systemdate](../neurons/systemdate/)               | Give the local system date and time                                                     |
 | [tasker_autoremote](../neurons/tasker_autoremote/) | Send a message to Android tasker app                                                    |

+ 11 - 169
brain.yml

@@ -1,171 +1,13 @@
 ---
-  - name: "Send push message"
-    neurons:
-      - push_message:
-           message: "Message to send"
-           api_key: "my_token_key"
-           channel_name: "my_chanel_name"
-    signals:
-      - order: "push message"
 
-  - name: "get the weather"
-    neurons:
-      - openweathermap:
-          api_key: "fdfba4097c318aed7836b2a85a6a05ef"
-          lang: "fr"
-          temp_unit: "celsius"
-          location : "grenoble"
-          country: "FR"
-          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"
-    signals:
-      - order: "quel temps fait-il "
-
-  - name: "say hello"
-    neurons:
-      - say:
-          message:
-            - "Bonjour monsieur"
-    signals:
-      - order: "dis bonjour"
-
-  - name: "Meaning of life"
-    neurons:
-      - say:
-          message:
-            - "42"
-          tts: "voxygen"
-    signals:
-      - order: "sens de la vie"
-
-  - name: "Run a simple script"
-    neurons:
-      - script:
-          path: "/home/nico/test.sh"
-      - say:
-          message: "Script lancé, monsieur"
-    signals:
-      - order: "lance le script"
-
-  - name: "Say local date"
-    neurons:
-      - systemdate:
-          tts: "voxygen"
-          say_template:
-            - "il est {{ hours }} heure et {{ minutes }} minute"
-    signals:
-      - order: "quelle heure est-il"
-
-  - name: "Say local date from template"
-    neurons:
-      - systemdate:
-          file_template: en_systemdate_template_example.j2
-    signals:
-      - order: "test heure"
-
-  - name: "Close rolling shutter"
-    neurons:
-      - command: "curl http://192.168.0.22:5000/fermeture -d \"password=monpass\" -X POST"
-      - say:
-          message: "Fermeture en cours, monsieur"
-    signals:
-      - order: "ferme les volets"
-
-  - name: "Open rolling shutter"
-    neurons:
-      - command: "curl http://192.168.0.22:5000/ouverture -d \"password=monpass\" -X POST"
-      - say:
-          message: "Ouverture en cours, monsieur"
-    signals:
-      - order: "ouvre les volets"
-
-  - name: "Start steam"
-    neurons:
-      - command: "curl -i --user admin:secret -H \"Content-Type: application/json\" -X POST -d '{\"app_name\":\"kodi\",\"state\":\"stop\"}' http://192.168.0.17:8000/app"
-      - command: "curl -i --user admin:secret -H \"Content-Type: application/json\" -X POST -d '{\"app_name\":\"steam\",\"state\":\"start\"}' http://192.168.0.17:8000/app"
-      - say:
-          message: "Lancement en cours, monsieur"
-    signals:
-      - order: "lance Steam"
-
-  - name: "Start Kodi"
-    neurons:
-      - command: "curl -i --user admin:secret -H \"Content-Type: application/json\" -X POST -d '{\"app_name\":\"steam\",\"state\":\"stop\"}' http://192.168.0.17:8000/app"
-      - command: "curl -i --user admin:secret -H \"Content-Type: application/json\" -X POST -d '{\"app_name\":\"kodi\",\"state\":\"start\"}' http://192.168.0.17:8000/app"
-      - say:
-          message: "Lancement en cours, monsieur"
-    signals:
-      - order: "lance Cody"
-
-  - name: "Start music"
-    neurons:
-      - command: "curl -i --user admin:secret -H \"Content-Type: application/json\" -X POST -d '{\"app_name\":\"music\",\"state\":\"start\"}' http://192.168.0.17:8000/app"
-      - say:
-          message: "Musique lance, monsieur"
-    signals:
-      - order: "mais nous de la musique"
-      - order: "musique rock"
-
-  - name: "Stop music"
-    neurons:
-      - command: "curl -i --user admin:secret -H \"Content-Type: application/json\" -X POST -d '{\"app_name\":\"music\",\"state\":\"stop\"}' http://192.168.0.17:8000/app"
-      - say:
-          message: "musique stoppé, monsieur"
-    signals:
-      - order: "arrête la musique"
-      - order: "stop la musique"
-      - order: "éteins la musique"
-
-  - name: "Ansible test"
-    neurons:
-      - ansible_tasks: "tasks.yml"
-      - say:
-          message: "Tache terminée"
-    signals:
-      - order: "playbook"
-
-  - name: "Stop kalliope"
-    neurons:
-      - say:
-          message: "Aurevoir"
-      - kill_switch
-    signals:
-      - order: "ferme-toi"
-
-  - name: "check email"
-    neurons:
-      - gmail_checker:
-          username: "me@gmail.com"
-          password: "my_password"
-          file_template: fr_gmail.j2
-    signals:
-      - order: "est-ce que j'ai des emails"
-
-  - name: "run soundcloud"
-    neurons:
-      - soundcloud:
-          username: "me@gmail.com"
-          password: "my_password"
-          args:
-            - artist_name
-            - music_name
-    signals:
-      - order: "I would like to hear the song {{ music_name }}"
-      - order: "I would like to hear {{ artist_name }}"
-
-  - name: "find my phone"
-    neurons:
-      - say:
-          message: "Je fais sonner le téléphone, monsieur"
-      - tasker_autoremote:
-          key: "APA91bqmY"
-          message: "lost"
-    signals:
-      - order: "où est mon téléphone"
-
-  - name: "wake my PC"
-    neurons:
-      - wake_on_lan:
-          mac_address: "00-00-00-00-00-00"
-    signals:
-      - order: "réveil mon PC"
+  - !include brains/ansible_playbook.yml
+  - !include brains/gmail_checker.yml
+  - !include brains/kill_switch.yml
+  - !include brains/openweathermap.yml
+  - !include brains/push_message.yml
+  - !include brains/say.yml
+  - !include brains/script.yml
+  - !include brains/shell.yml
+  - !include brains/systemdate.yml
+  - !include brains/tasker_autoremote.yml
+  - !include brains/wake_on_lan.yml

+ 9 - 0
brains/ansible_playbook.yml

@@ -0,0 +1,9 @@
+---
+
+  - name: "Ansible test"
+    neurons:
+      - ansible_playbook: "tasks.yml"
+      - say:
+          message: "Tache terminée"
+    signals:
+      - order: "playbook"

+ 10 - 0
brains/gmail_checker.yml

@@ -0,0 +1,10 @@
+---
+
+  - name: "check email"
+    neurons:
+      - gmail_checker:
+          username: "me@gmail.com"
+          password: "my_password"
+          file_template: fr_gmail.j2
+    signals:
+      - order: "est-ce que j'ai des emails"

+ 9 - 0
brains/kill_switch.yml

@@ -0,0 +1,9 @@
+---
+
+  - name: "Stop kalliope"
+    neurons:
+      - say:
+          message: "Aurevoir"
+      - kill_switch
+    signals:
+      - order: "ferme-toi"

+ 13 - 0
brains/openweathermap.yml

@@ -0,0 +1,13 @@
+---
+  - name: "get the weather"
+    neurons:
+      - openweathermap:
+          api_key: "fdfba4097c318aed7836b2a85a6a05ef"
+          lang: "fr"
+          temp_unit: "celsius"
+          location : "grenoble"
+          country: "FR"
+          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"
+    signals:
+      - order: "quel temps fait-il "

+ 9 - 0
brains/push_message.yml

@@ -0,0 +1,9 @@
+---
+  - name: "Send push message"
+    neurons:
+      - push_message:
+           message: "Message to send"
+           api_key: "my_token_key"
+           channel_name: "my_chanel_name"
+    signals:
+      - order: "push message"

+ 8 - 0
brains/say.yml

@@ -0,0 +1,8 @@
+---
+  - name: "say hello"
+    neurons:
+      - say:
+          message:
+            - "Bonjour monsieur"
+    signals:
+      - order: "bonjour"

+ 9 - 0
brains/script.yml

@@ -0,0 +1,9 @@
+---
+  - name: "Run a simple script"
+    neurons:
+      - script:
+          path: "/home/nico/test.sh"
+      - say:
+          message: "Script lancé, monsieur"
+    signals:
+      - order: "lance le script"

+ 63 - 0
brains/shell.yml

@@ -0,0 +1,63 @@
+---
+
+  - name: "Close rolling shutter"
+    neurons:
+      - shell:
+          cmd: "curl http://192.168.0.22:5000/fermeture -d \"password=monpass\" -X POST"
+      - say:
+          message: "Fermeture en cours, monsieur"
+    signals:
+      - order: "ferme les volets"
+
+  - name: "Open rolling shutter"
+    neurons:
+      - shell:
+          cmd: "curl http://192.168.0.22:5000/ouverture -d \"password=monpass\" -X POST"
+      - say:
+          message: "Ouverture en cours, monsieur"
+    signals:
+      - order: "ouvre les volets"
+
+
+  - name: "Start steam"
+    neurons:
+      - shell:
+          cmd: "curl -i --user admin:secret -H \"Content-Type: application/json\" -X POST -d '{\"app_name\":\"kodi\",\"state\":\"stop\"}' http://192.168.0.17:8000/app"
+      - shell:
+          cmd: "curl -i --user admin:secret -H \"Content-Type: application/json\" -X POST -d '{\"app_name\":\"steam\",\"state\":\"start\"}' http://192.168.0.17:8000/app"
+      - say:
+          message: "Lancement en cours, monsieur"
+    signals:
+      - order: "lance Steam"
+
+  - name: "Start Kodi"
+    neurons:
+      - shell:
+          cmd: "curl -i --user admin:secret -H \"Content-Type: application/json\" -X POST -d '{\"app_name\":\"steam\",\"state\":\"stop\"}' http://192.168.0.17:8000/app"
+      - shell:
+          cmd: "curl -i --user admin:secret -H \"Content-Type: application/json\" -X POST -d '{\"app_name\":\"kodi\",\"state\":\"start\"}' http://192.168.0.17:8000/app"
+      - say:
+          message: "Lancement en cours, monsieur"
+    signals:
+      - order: "lance Cody"
+
+  - name: "Start music"
+    neurons:
+      - shell:
+          cmd: "curl -i --user admin:secret -H \"Content-Type: application/json\" -X POST -d '{\"app_name\":\"music\",\"state\":\"start\"}' http://192.168.0.17:8000/app"
+      - say:
+          message: "Musique lance, monsieur"
+    signals:
+      - order: "mais nous de la musique"
+      - order: "musique rock"
+
+  - name: "Stop music"
+    neurons:
+      - shell:
+          cmd: "curl -i --user admin:secret -H \"Content-Type: application/json\" -X POST -d '{\"app_name\":\"music\",\"state\":\"stop\"}' http://192.168.0.17:8000/app"
+      - say:
+          message: "musique stoppé, monsieur"
+    signals:
+      - order: "arrête la musique"
+      - order: "stop la musique"
+      - order: "éteins la musique"

+ 17 - 0
brains/systemdate.yml

@@ -0,0 +1,17 @@
+---
+
+  - name: "Say local date"
+    neurons:
+      - systemdate:
+          tts: "voxygen"
+          say_template:
+            - "il est {{ hours }} heure et {{ minutes }} minute"
+    signals:
+      - order: "quelle heure est-il"
+
+  - name: "Say local date from template"
+    neurons:
+      - systemdate:
+          file_template: en_systemdate_template_example.j2
+    signals:
+      - order: "test heure"

+ 11 - 0
brains/tasker_autoremote.yml

@@ -0,0 +1,11 @@
+---
+
+  - name: "find my phone"
+    neurons:
+      - say:
+          message: "Je fais sonner le téléphone, monsieur"
+      - tasker_autoremote:
+          key: "APA91bqmY"
+          message: "lost"
+    signals:
+      - order: "où est mon téléphone"

+ 8 - 0
brains/wake_on_lan.yml

@@ -0,0 +1,8 @@
+---
+
+  - name: "wake my PC"
+    neurons:
+      - wake_on_lan:
+          mac_address: "00-00-00-00-00-00"
+    signals:
+      - order: "wake my PC"

+ 4 - 1
core/ConfigurationManager/BrainLoader.py

@@ -41,7 +41,10 @@ class BrainLoader(object):
         # create list of Synapse
         synapses = list()
         for synapses_dict in dict_brain:
-            # print synapses_dict
+            print synapses_dict
+            # if the synapse is a list, it come from an include
+            if isinstance(synapses_dict, list):
+                synapses_dict = synapses_dict[0]
             if ConfigurationChecker().check_synape_dict(synapses_dict):
                 # print "synapses_dict ok"
                 name = synapses_dict["name"]

+ 1 - 1
core/ConfigurationManager/SettingLoader.py

@@ -28,7 +28,7 @@ class SettingLoader(object):
     @classmethod
     def get_yaml_config(cls, file_path=None):
         if file_path is None:
-            file_path = "../../" + FILE_NAME
+            file_path = FILE_NAME
         return YAMLLoader.get_config(file_path)
 
     @classmethod

+ 48 - 8
core/ConfigurationManager/YAMLLoader.py

@@ -1,6 +1,12 @@
+import logging
 import os
 import yaml
 
+from core.Utils import Utils
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
 
 class YAMLFileNotFound(Exception):
     pass
@@ -17,11 +23,45 @@ class YAMLLoader:
         Load settings file
         :return: cfg : the configuration file
         """
-        # Load settings.
-        __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
-        try:
-            with open(os.path.join(__location__, yaml_file)) as ymlfile:
-                cfg = yaml.load(ymlfile)
-            return cfg
-        except IOError:
-            raise YAMLFileNotFound("The file path %s does not exist" % yaml_file)
+        # # Load settings.
+        # __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
+        # try:
+        #     with open(os.path.join(__location__, yaml_file)) as ymlfile:
+        #         cfg = yaml.load(ymlfile)
+        #     return cfg
+        # except IOError:
+        #     raise YAMLFileNotFound("The file path %s does not exist" % yaml_file)
+        current_dir = os.path.dirname(os.path.abspath(__file__))
+        logger.debug("Current dir: %s " % current_dir)
+        root_dir = os.path.join(current_dir, "../../")
+        root_dir = os.path.normpath(root_dir)
+        logger.debug("Root dir: %s " % root_dir)
+        file_path_to_load = os.path.join(root_dir, yaml_file)
+        logger.debug("File path to load: %s " % file_path_to_load)
+        if os.path.isfile(yaml_file):
+            data = IncludeLoader(open(file_path_to_load, 'r')).get_data()
+            # print Utils.print_yaml_nicely(data)
+            return data
+        else:
+            raise YAMLFileNotFound("File %s not found" % file_path_to_load)
+
+
+class IncludeLoader(yaml.Loader):
+
+    def __init__(self, *args, **kwargs):
+        super(IncludeLoader, self).__init__(*args, **kwargs)
+        self.add_constructor('!include', self._include)
+        if 'root' in kwargs:
+            self.root = kwargs['root']
+        elif isinstance(self.stream, file):
+            self.root = os.path.dirname(self.stream.name)
+        else:
+            self.root = os.path.curdir
+
+    def _include(self, loader, node):
+        oldRoot = self.root
+        filename = os.path.join(self.root, loader.construct_scalar(node))
+        self.root = os.path.dirname(filename)
+        data = yaml.load(open(filename, 'r'))
+        self.root = oldRoot
+        return data

+ 4 - 3
core/NeuronModule.py

@@ -97,7 +97,7 @@ class NeuronModule(object):
             logger.debug("message is dict")
             tts_message = self._get_message_from_dict(message)
 
-        if message is not None:
+        if tts_message is not None:
             # get an instance of the target TTS
             tts_instance = self._get_tts_instance(self.tts)
             tts_args = None
@@ -152,8 +152,9 @@ class NeuronModule(object):
                                                         % real_file_template_path)
             return returned_message
 
-        else:
-            raise NoTemplateException("You must specify a say_template or a file_template")
+        # we don't force the usage of a template. The user can choose to do nothing with returned value
+        # else:
+        #     raise NoTemplateException("You must specify a say_template or a file_template")
 
     @staticmethod
     def _get_content_of_file(real_file_template_path):

+ 10 - 0
core/Utils.py

@@ -73,3 +73,13 @@ class Utils(object):
             else:
                 return klass(parameters)
         return None
+
+    @classmethod
+    def print_yaml_nicely(cls, to_print):
+        """
+        Used for debug
+        :param to_print: Dict to print nicely
+        :return:
+        """
+        import json
+        print json.dumps(to_print, indent=2)

+ 2 - 2
neurons/__init__.py

@@ -1,5 +1,5 @@
-from ansible_tasks import Ansible_tasks
-from command import Command
+from ansible_playbook import Ansible_playbook
+from shell import Shell
 from kill_switch import Kill_switch
 from say import Say
 from script import Script

+ 1 - 0
neurons/ansible_playbook/__init__.py

@@ -0,0 +1 @@
+from ansible_playbook import Ansible_playbook

+ 2 - 2
neurons/ansible_tasks/ansible_tasks.py → neurons/ansible_playbook/ansible_playbook.py

@@ -7,9 +7,9 @@ from ansible.executor.playbook_executor import PlaybookExecutor
 from core.NeuronModule import NeuronModule
 
 
-class Ansible_tasks(NeuronModule):
+class Ansible_playbook(NeuronModule):
     def __init__(self, task_file, **kwargs):
-        super(Ansible_tasks, self).__init__(**kwargs)
+        super(Ansible_playbook, self).__init__(**kwargs)
         Options = namedtuple('Options',
                              ['connection', 'forks', 'become', 'become_method', 'become_user', 'check', 'listhosts',
                               'listtasks', 'listtags', 'syntax', 'module_path'])

+ 0 - 1
neurons/ansible_tasks/__init__.py

@@ -1 +0,0 @@
-from ansible_tasks import Ansible_tasks

+ 0 - 1
neurons/command/__init__.py

@@ -1 +0,0 @@
-from command import Command

+ 0 - 12
neurons/command/command.py

@@ -1,12 +0,0 @@
-import subprocess
-
-from core.NeuronModule import NeuronModule
-
-
-class Command(NeuronModule):
-    def __init__(self, command, **kwargs):
-        super(Command, self).__init__(**kwargs)
-        p = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)
-        (output, err) = p.communicate()
-
-

+ 1 - 5
neurons/gmail_checker/gmail_checker.py

@@ -3,16 +3,12 @@ import logging
 
 from gmail import Gmail
 from email.header import decode_header
-from core.NeuronModule import NeuronModule
+from core.NeuronModule import NeuronModule, MissingParameterException
 
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
 
 
-class MissingParameterException(Exception):
-    pass
-
-
 class Gmail_checker(NeuronModule):
     def __init__(self, **kwargs):
         super(Gmail_checker, self).__init__(**kwargs)

+ 1 - 0
neurons/openweathermap/README.md

@@ -93,3 +93,4 @@ You also can define the "location" args directly in neuron argument list.
 
 ## Notes
 
+> **Note:** You need to create a free account on [openweathermap.org](http://openweathermap.org/) to get your API key.

+ 105 - 0
neurons/shell/Readme.md

@@ -0,0 +1,105 @@
+# shell
+
+## Synopsis
+
+Run a shell command on the local system where Kalliope is installed.
+
+
+## Options
+
+| parameter | required | default | choices  | comment                                                                     |
+|-----------|----------|---------|----------|-----------------------------------------------------------------------------|
+| cmd       | yes      |         |          | The shell command to run                                                    |
+| async     | no       | False   |          | If True, Kalliope will not wait for the end of the execution of the command |
+
+
+## Return Values
+
+Values are only returned by the neuron if the async mode is set to `False`.
+
+| Name       | Description                                                                                           | Type   | sample                        |
+|------------|-------------------------------------------------------------------------------------------------------|--------|-------------------------------|
+| output     | The shell output of the command if any. The command "date" will retun "Sun Oct 16 15:50:45 CEST 2016" | string | Sun Oct 16 15:50:45 CEST 2016 |
+| returncode | The returned code of the command. Return 0 if the command was succesfuly exectued, else 1             | int    | 0                             |
+
+
+## Synapses example
+
+Simple that will create a file locally
+```
+  - name: "create a local file"
+    neurons:
+      - shell:
+          cmd: "touch ~/test.txt"
+    signals:
+      - order: "touch"
+```
+
+We want to launch our favorite web radio. This command, which it call mplayer, will block the entire Kalliope process if we 
+wait for the result unless the mplayer process is killed. So we add async parameter. 
+``` 
+  - name: "run web radio"
+    neurons:
+      - shell:
+          cmd: "mplayer http://192.99.17.12:6410/"
+          async: True
+      - say:
+          message: "web radio lanched"
+    signals:
+      - order: "run web radio"
+```
+If the parameter `async` is set to True, the neuron will not return any values.
+
+
+Then, we can kill the player process with another synapse
+```
+  - name: "stop web radio"
+    neurons:
+      - shell:
+          cmd: "pkill mplayer"
+      - say:
+          message: "web radio stopped"
+    signals:
+      - order: "stop web radio"
+```
+
+Make Kalliope add two number and speak out loud the result. Here you should hear "4".
+```
+  - name: "get the result of the addition: 1 + 3"
+    neurons:
+      - shell:
+          cmd: "echo $(expr \"1\" + \"3\")"
+          say_template: "{{ output }}"
+    signals:
+      - order: "addition"
+```
+
+Let's use a file template. We try to remove the file `~/test.txt` and make Kalliope gice us the result depending of the 
+returned error code.
+If the file is present on the system, you will hear "The command has succeeded" and so the file has been deleted. 
+If you run it a second time, the command will fail as the file is not anymore present and so you should hear 
+"The command has failed". See the template example bellow.
+```
+  - name: "remove a file"
+    neurons:
+      - shell:
+          cmd: "rm ~/test.txt"
+          file_template: remove_file.j2
+    signals:
+      - order: "rm file"
+```
+
+## Templates example 
+
+Template `remove_file.j2` used in the remove file example remove_file.j2
+```
+{% if returncode == 0 %}
+    The command succeeded
+{% else %}
+    The command failled
+{% endif %}
+```
+
+## Notes
+
+> **Note:** If the parameter `async` is set to True, the neuron will not return any values.

+ 1 - 0
neurons/shell/__init__.py

@@ -0,0 +1 @@
+from shell import Shell

+ 54 - 0
neurons/shell/shell.py

@@ -0,0 +1,54 @@
+import logging
+import subprocess
+import threading
+from core.NeuronModule import NeuronModule, MissingParameterException
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+
+class AsyncShell(threading.Thread):
+    def __init__(self, cmd):
+        self.stdout = None
+        self.stderr = None
+        self.cmd = cmd
+        threading.Thread.__init__(self)
+
+    def run(self):
+        p = subprocess.Popen(self.cmd,
+                             shell=True,
+                             stdout=subprocess.PIPE,
+                             stderr=subprocess.PIPE)
+
+        self.stdout, self.stderr = p.communicate()
+
+
+class Shell(NeuronModule):
+    def __init__(self, **kwargs):
+        super(Shell, self).__init__(**kwargs)
+
+        # get the command
+        cmd = kwargs.get('cmd', None)
+        # get if the user select a blocking command or not
+        async = kwargs.get('async', False)
+
+        if cmd is None:
+            raise MissingParameterException("cmd parameter required")
+
+        # run the command
+        if not async:
+            p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
+            (output, err) = p.communicate()
+            message = {
+                "output": output,
+                "returncode": p.returncode
+            }
+            self.say(message)
+
+        else:
+            async_shell = AsyncShell(cmd=cmd)
+            async_shell.start()
+
+
+
+

+ 5 - 0
templates/remove_file.j2

@@ -0,0 +1,5 @@
+{% if returncode == 0 %}
+    The command has succeeded
+{% else %}
+    The command has failed
+{% endif %}

+ 4 - 4
test.py

@@ -4,6 +4,7 @@ import re
 from collections import Counter
 
 from core import OrderAnalyser
+from core.ConfigurationManager import YAMLLoader
 from neurons import Systemdate
 from neurons.tasker_autoremote.tasker_autoremote import Tasker_autoremote
 
@@ -11,10 +12,8 @@ logging.basicConfig()
 logger = logging.getLogger("kalliope")
 logger.setLevel(logging.DEBUG)
 
-
-
-order = "réveil mon PC"
-oa = OrderAnalyser(order)
+order = "bonjour"
+oa = OrderAnalyser(order=order)
 oa.start()
 
 
@@ -23,3 +22,4 @@ oa.start()
 
 
 
+

+ 0 - 12
test.yml

@@ -1,12 +0,0 @@
----
-  - name: "wake up"
-    neurons:
-      - say:
-          message:
-            - "Bonjour monsieur"
-      - systemdate:
-          say_template:
-            - "Il est {{ hours }} heures et {{ minutes }} minutes"
-      - command: "mplayer http://192.99.17.12:6410/"
-    signals:
-      - event: "20 12 * * *"