Преглед на файлове

Merge branch 'dev' into neuron-twitter

Conflicts:
	install/files/python_requirements.txt
	test.py
monf преди 8 години
родител
ревизия
a074b9abcb

+ 6 - 6
Docs/brain.md

@@ -18,7 +18,7 @@ Let's take a look on a basic synapse in our brain:
 
 ```
 ---
-  - name: "Say hello"
+  - name: "Say-hello"
     neurons:      
       - say:
           message: "Hello, sir"
@@ -36,9 +36,9 @@ This is a requirement for YAML to interpret the file as a proper document.
 
 Items that begin with a ```-``` are considered as list items. Items have the format of ```key: value``` where value can be a simple string or a sequence of other items.
 
-At the top level we have a "name" tag. This is the **unique identifier** of the synapse. It must be unique to each synapse and should not contain any accent.
+At the top level we have a "name" tag. This is the **unique identifier** of the synapse. It must be an unique word with the only accepted values : alphanumerics and dash. ([a - zA - Z0 - 9\-])
 ```
-- name: "Say hello"
+- name: "Say-hello"
 ```
 
 Then we have the neurons declaration. Neurons are modules that will be executed when the input action is triggered. You can define as many neurons as you want to the same input action (for example: say somethning, then do something etc...). This declaration contains a list (because it starts with a "-") of neurons
@@ -67,7 +67,7 @@ The last part, called **signals** is a list of input actions. This works exactly
 In the following example, we use just one signal, an order. See the complete list of [available signals](signals.md) here.
 ```
 signals:
-  - order: "say hello"
+  - order: "say-hello"
 ```
 
 You can add as many orders as you want for the signals. Even if literally they do not mean the same thing (For example order "say hello" and order "adventure" or whatever) as long they are in the same synaps, they will trigger the same action defined in neurons. 
@@ -84,14 +84,14 @@ To know if your order will be triggered by Kalliope, we recommend you to [use th
 >**Note:**
 You must pay attention to define the orders as precise as possible. As Kalliope is based on matching, if you define your orders in different synapses too similiary, Kalliope risks to trigger more actions that you were expecting. For exemple, if you define two different synapses as shown below:
 ```
-- name: "Say hello"
+- name: "Say-hello"
 ...
 signals:
   - order: "say hello"
 ```
 and 
 ```
-- name: "Say something"
+- name: "Say-something"
 ...
 signals:
   - order: "say"

+ 1 - 0
Docs/dev_env_install.md

@@ -32,6 +32,7 @@ pip install pushetta
 pip install wakeonlan
 pip install ipaddress
 pip install pyowm
+pip install flask
 ```
 
 ### Test your env

+ 35 - 0
Docs/settings.md

@@ -163,3 +163,38 @@ random_wake_up_sounds:
 
 >**Note: ** If you want to use a wake up sound instead of a wake up answer you must comment out the `random_wake_up_answers` section.
 E.g: `# random_wake_up_answers:`
+
+
+## Rest API
+
+A Rest API can be activated in order to:
+- List synapses
+- Get synapse's detail
+- Run a synapse
+
+For the complete API ref see the [signals documentation](signals.md)
+
+Settings examples:
+```
+rest_api:
+  active: True
+  port: 5000
+  password_protected: True
+  login: admin
+  password: secret
+```
+
+#### active
+To enable the rest api server.
+
+#### port
+The listening port of the web server. Must be an integer in range 1024-65535.
+
+#### password_protected
+If `True`, the whole api will be password protected.
+
+#### Login
+Login used by the basic HTTP authentication. Must be provided if `password_protected` is `True`
+
+#### Password
+Password used by the basic HTTP authentication. Must be provided if `password_protected` is `True`

+ 136 - 2
Docs/signals.md

@@ -74,7 +74,7 @@ Let make a complete example. We want Kalliope to wake us up each morning of work
 
 The synapse in the brain would be
 ```
-  - name: "wake up"
+  - name: "wake-up"
     neurons:
       - say:
           message:
@@ -98,4 +98,138 @@ Synapse "wake up" added to the crontab
 Event loaded in crontab
 ```
 
-That's it, the synapse is now scheduled and will be started automatically.
+That's it, the synapse is now scheduled and will be started automatically.
+
+## Rest API
+
+Each synapse can be started from the REST API. For configuring the API see the [settings documentation](settings.md).
+
+### Synapse API
+
+| Method | URL                      | Action               |
+|--------|--------------------------|----------------------|
+| GET    | /synapses                | List synapses        |
+| GET    | /synapses/<synapse_name> | Show synapse details |
+| POST   | /synapses/<synapse_name> | Run a synapse        |
+
+### Curl examples
+
+>**Note:** --user is only needed if `password_protected` is True
+
+#### Get all synapse
+
+Normal response codes: 200
+Error response codes: unauthorized(401), itemNotFound(404)
+Curl command:
+```
+curl -i --user admin:secret -X GET  http://localhost:5000/synapses/
+```
+
+Output example:
+```
+{
+  "synapses": [
+    [
+      {
+        "name": "stop-kalliope",
+        "neurons": [
+          {
+            "say": {
+              "message": "Good bye"
+            }
+          },
+          "kill_switch"
+        ],
+        "signals": [
+          {
+            "order": "close"
+          }
+        ]
+      }
+    ],
+    [
+      {
+        "name": "say-hello",
+        "neurons": [
+          {
+            "say": {
+              "message": [
+                "Bonjour monsieur"
+              ]
+            }
+          }
+        ],
+        "signals": [
+          {
+            "order": "bonjour"
+          }
+        ]
+      }
+    ]
+  ]
+}
+```
+
+#### Get synapse's detail. 
+
+Normal response codes: 200
+Error response codes: unauthorized(401), itemNotFound(404)
+Curl command:
+```
+curl -i --user admin:secret -X GET  http://localhost:5000/synapses/say-hello
+```
+
+Output example:
+```
+{
+  "synapses": {
+    "name": "say-hello",
+    "neurons": [
+      {
+        "say": {
+          "message": [
+            "Bonjour monsieur"
+          ]
+        }
+      }
+    ],
+    "signals": [
+      {
+        "order": "bonjour"
+      }
+    ]
+  }
+}
+```
+
+#### Run a synapse by its name. 
+
+Normal response codes: 201
+Error response codes: unauthorized(401), itemNotFound(404)
+Curl command:
+```
+curl -i --user admin:secret -X POST  http://localhost:5000/synapses/say-hello
+```
+
+Output example:
+```
+{
+  "synapses": {
+    "name": "say-hello",
+    "neurons": [
+      {
+        "say": {
+          "message": [
+            "Bonjour monsieur"
+          ]
+        }
+      }
+    ],
+    "signals": [
+      {
+        "order": "bonjour"
+      }
+    ]
+  }
+}
+```

+ 13 - 6
README.md

@@ -30,12 +30,19 @@ Kalliope is easy-peasy to use, see the hello world
 - [Create the brain of your Kalliope](Docs/brain.md)
 - [Run Kalliope with CLI](Docs/kalliope_cli.md)
 
-## Neurons
-
-A neuron is a plugin that can be used from your **brain.yml**. 
-
-- See the list of [available neurons](Docs/neuron_list.md).
-- See how to [create your own neuron](Docs/contributing.md).
+## Documentation summary
+
+| Link                               | Detail                                                                                      |
+|------------------------------------|---------------------------------------------------------------------------------------------|
+| [Settings](Docs/settings.md)       | The main Kalliope configuration                                                             |
+| [Brain](Docs/brain.md)             | What is the brain and how to create your own bot                                            |
+| [neuron](Docs/neurons.md)          | What is a neuron and how to use it                                                          |
+| [neuron list](Docs/neuron_list.md) | List of availlable neurons                                                                  |
+| [CLI](Docs/kalliope_cli.md)        | How to use Kalliope from the command line interface                                         |
+| [Signals](Docs/signals.md)         | Signals are input event that can wake up kalliope (spoken order, scheduled event, REST API) |
+| [STT](Docs/stt.md)                 | Speech to text configuration                                                                |
+| [TTS](Docs/tts.md)                 | Text to speech configuration                                                                |
+| [Triggers](Docs/tts.md)            | Magic hotword engine used to make Kalliope listtening for an order                          |
 
 
 ## Contributing

+ 1 - 1
brains/ansible_playbook.yml

@@ -1,6 +1,6 @@
 ---
 
-  - name: "Ansible test"
+  - name: "Ansible-test"
     neurons:
       - ansible_playbook: "tasks.yml"
       - say:

+ 1 - 1
brains/gmail_checker.yml

@@ -1,6 +1,6 @@
 ---
 
-  - name: "check email"
+  - name: "check-email"
     neurons:
       - gmail_checker:
           username: "me@gmail.com"

+ 1 - 1
brains/kill_switch.yml

@@ -1,6 +1,6 @@
 ---
 
-  - name: "Stop kalliope"
+  - name: "stop-kalliope"
     neurons:
       - say:
           message: "Aurevoir"

+ 1 - 1
brains/openweathermap.yml

@@ -1,5 +1,5 @@
 ---
-  - name: "get the weather"
+  - name: "get-the-weather"
     neurons:
       - openweathermap:
           api_key: "your-api"

+ 1 - 1
brains/push_message.yml

@@ -1,5 +1,5 @@
 ---
-  - name: "Send push message"
+  - name: "send-push-message"
     neurons:
       - push_message:
            message: "Message to send"

+ 1 - 1
brains/say.yml

@@ -1,5 +1,5 @@
 ---
-  - name: "say hello"
+  - name: "Say-hello"
     neurons:
       - say:
           message:

+ 1 - 1
brains/script.yml

@@ -1,5 +1,5 @@
 ---
-  - name: "Run a simple script"
+  - name: "run-simple-script"
     neurons:
       - script:
           path: "/home/nico/test.sh"

+ 6 - 6
brains/shell.yml

@@ -1,6 +1,6 @@
 ---
 
-  - name: "Close rolling shutter"
+  - name: "close-rolling-shutter"
     neurons:
       - shell:
           cmd: "curl http://192.168.0.22:5000/fermeture -d \"password=monpass\" -X POST"
@@ -9,7 +9,7 @@
     signals:
       - order: "ferme les volets"
 
-  - name: "Open rolling shutter"
+  - name: "open-rolling-shutter"
     neurons:
       - shell:
           cmd: "curl http://192.168.0.22:5000/ouverture -d \"password=monpass\" -X POST"
@@ -19,7 +19,7 @@
       - order: "ouvre les volets"
 
 
-  - name: "Start steam"
+  - 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"
@@ -30,7 +30,7 @@
     signals:
       - order: "lance Steam"
 
-  - name: "Start Kodi"
+  - 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"
@@ -41,7 +41,7 @@
     signals:
       - order: "lance Cody"
 
-  - name: "Start music"
+  - 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"
@@ -51,7 +51,7 @@
       - order: "mais nous de la musique"
       - order: "musique rock"
 
-  - name: "Stop music"
+  - 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"

+ 2 - 2
brains/systemdate.yml

@@ -1,6 +1,6 @@
 ---
 
-  - name: "Say local date"
+  - name: "say-local-date"
     neurons:
       - systemdate:
           tts: "voxygen"
@@ -9,7 +9,7 @@
     signals:
       - order: "quelle heure est-il"
 
-  - name: "Say local date from template"
+  - name: "say-local-date-from-template"
     neurons:
       - systemdate:
           file_template: en_systemdate_template_example.j2

+ 1 - 1
brains/tasker_autoremote.yml

@@ -1,6 +1,6 @@
 ---
 
-  - name: "find my phone"
+  - name: "find-my-phone"
     neurons:
       - say:
           message: "Je fais sonner le téléphone, monsieur"

+ 1 - 1
brains/wake_on_lan.yml

@@ -1,6 +1,6 @@
 ---
 
-  - name: "wake my PC"
+  - name: "wake-my-PC"
     neurons:
       - wake_on_lan:
           mac_address: "00-00-00-00-00-00"

+ 13 - 3
core/ConfigurationManager/ConfigurationChecker.py

@@ -1,6 +1,10 @@
 import re
 
 
+class InvalidSynapeName(Exception):
+    pass
+
+
 class NoSynapeName(Exception):
     pass
 
@@ -44,6 +48,15 @@ class ConfigurationChecker:
         if 'name' not in synape_dict:
             raise NoSynapeName("The Synapse does not have a name: %s" % synape_dict)
 
+        # check that the name is conform
+        # Regex for [a - zA - Z0 - 9\-] with dashes allowed in between but not at the start or end
+        pattern = r'(?=[a-zA-Z0-9\-]{4,25}$)^[a-zA-Z0-9]+(\-[a-zA-Z0-9]+)*$'
+        prog = re.compile(pattern)
+        result = prog.match(synape_dict["name"])
+        if result is None:
+            raise InvalidSynapeName("Error with synapse name \"%s\".Valid syntax: [a - zA - Z0 - 9\-] with dashes "
+                                    "allowed in between but not at the start or end" % synape_dict["name"])
+
         if 'neurons' not in synape_dict:
             raise NoSynapeNeurons("The Synapse does not have neurons: %s" % synape_dict)
 
@@ -81,7 +94,6 @@ class ConfigurationChecker:
         """
         Check the synapse list is ok:
          - No double same name
-         - No accent of special character
         :param synapses_list:
         :type synapses_list: list of Synapse
         :return:
@@ -93,7 +105,5 @@ class ConfigurationChecker:
             if synapse_name in seen:
                 raise MultipleSameSynapseName("Multiple synapse found with the same name: %s" % synapse_name)
             seen.add(synapse.name)
-            if not re.match("^[a-zA-Z0-9_\s]*$", synapse.name):
-                raise NotValidSynapseName("Synapse's name %s not valid." % synapse_name)
 
         return True

+ 62 - 11
core/ConfigurationManager/SettingLoader.py

@@ -1,6 +1,7 @@
 from YAMLLoader import YAMLLoader
 import logging
 
+from core.Models.RestAPI import RestAPI
 from core.Models.Settings import Settings
 from core.Models.Stt import Stt
 from core.Models.Trigger import Trigger
@@ -12,6 +13,10 @@ logging.basicConfig()
 logger = logging.getLogger("kalliope")
 
 
+class SettingInvalidException(Exception):
+    pass
+
+
 class NullSettingException(Exception):
     pass
 
@@ -46,6 +51,7 @@ class SettingLoader(object):
         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)
 
         # create a setting object
         setting_object = Settings(default_stt_name=default_stt_name,
@@ -55,7 +61,8 @@ class SettingLoader(object):
                                   ttss=ttss,
                                   triggers=triggers,
                                   random_wake_up_answers=random_wake_up_answers,
-                                  random_wake_up_sounds=random_wake_up_sounds)
+                                  random_wake_up_sounds=random_wake_up_sounds,
+                                  rest_api=rest_api)
         return setting_object
 
     @staticmethod
@@ -67,8 +74,8 @@ 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:
-            raise SettingNotFound("Attribute default_speech_to_text not found in settings")
+        except KeyError, e:
+            raise SettingNotFound("%s setting not found" % e)
 
     @staticmethod
     def _get_default_text_to_speech(settings):
@@ -78,8 +85,8 @@ 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:
-            raise SettingNotFound("Attribute default_text_to_speech not found in settings")
+        except KeyError, e:
+            raise SettingNotFound("%s setting not found" % e)
 
     @staticmethod
     def _get_default_trigger(settings):
@@ -89,8 +96,8 @@ class SettingLoader(object):
                 raise NullSettingException("Attribute default_trigger is null")
             logger.debug("Default Trigger name: %s" % default_trigger)
             return default_trigger
-        except KeyError:
-            raise SettingNotFound("Attribute default_trigger not found in settings")
+        except KeyError, e:
+            raise SettingNotFound("%s setting not found" % e)
 
     @classmethod
     def _get_stts(cls, settings):
@@ -128,8 +135,8 @@ class SettingLoader(object):
         """
         try:
             text_to_speech_list = settings["text_to_speech"]
-        except KeyError:
-            raise SettingNotFound("text_to_speech settings not found")
+        except KeyError, e:
+            raise SettingNotFound("%s setting not found" % e)
 
         ttss = list()
         for text_to_speech_el in text_to_speech_list:
@@ -155,8 +162,8 @@ class SettingLoader(object):
         """
         try:
             triggers_list = settings["triggers"]
-        except KeyError:
-            raise SettingNotFound("text_to_speech settings not found")
+        except KeyError, e:
+            raise SettingNotFound("%s setting not found" % e)
 
         triggers = list()
         for trigger_el in triggers_list:
@@ -210,3 +217,47 @@ class SettingLoader(object):
             raise NullSettingException("random_wake_up_sounds settings is empty")
 
         return random_wake_up_sounds_list
+
+    @classmethod
+    def _get_rest_api(cls, settings):
+        try:
+            rest_api = settings["rest_api"]
+        except KeyError, e:
+            raise SettingNotFound("%s setting not found" % e)
+
+        if rest_api is not None:
+            try:
+                password_protected = rest_api["password_protected"]
+                if password_protected is None:
+                    raise NullSettingException("password_protected setting cannot be null")
+                login = rest_api["login"]
+                password = rest_api["password"]
+                if password_protected:
+                    if login is None:
+                        raise NullSettingException("login setting cannot be null if password_protected is True")
+                    if login is None:
+                        raise NullSettingException("password setting cannot be null if password_protected is True")
+                active = rest_api["active"]
+                if active is None:
+                    raise NullSettingException("active setting cannot be null")
+                port = rest_api["port"]
+                if port is None:
+                    raise NullSettingException("port setting cannot be null")
+                # check that the port in an integer
+                try:
+                    port = int(port)
+                except ValueError:
+                    raise SettingInvalidException("port must be an integer")
+                # check the port is a valid port number
+                if not 1024 <= port <= 65535:
+                    raise SettingInvalidException("port must be in range 1024-65535")
+
+            except KeyError, e:
+                print e
+                raise SettingNotFound("%s settings not found" % e)
+
+            # config ok, we can return the rest api object
+            rest_api_obj = RestAPI(password_protected=password_protected, login=login, password=password, active=active, port=port)
+            return rest_api_obj
+        else:
+            raise NullSettingException("rest_api settings cannot be null")

+ 9 - 1
core/MainController.py

@@ -8,7 +8,8 @@ from core.ConfigurationManager import SettingLoader
 from core.OrderAnalyser import OrderAnalyser
 from core.OrderListener import OrderListener
 from core.TriggerLauncher import TriggerLauncher
-
+from flask import Flask
+from core.RestAPI.FlaskAPI import FlaskAPI
 from neurons import Say
 
 logging.basicConfig()
@@ -21,6 +22,13 @@ class MainController:
         # get global configuration
         self.settings = SettingLoader.get_settings()
 
+        # run the api if the user want it
+        if self.settings.rest_api.active:
+            Utils.print_info("Starting REST API Listening port: %s" % self.settings.rest_api.port)
+            app = Flask(__name__)
+            flask_api = FlaskAPI(app, port=self.settings.rest_api.port, brain_file=brain_file)
+            flask_api.start()
+
         # create an order listener object. This last will the trigger callback before starting
         self.order_listener = OrderListener(self.analyse_order)
         # Wait that the kalliope trigger is pronounced by the user

+ 23 - 0
core/Models/RestAPI.py

@@ -0,0 +1,23 @@
+class RestAPI(object):
+    def __init__(self, password_protected=None, login=None, password=None, active=None, port=None):
+        """
+
+        :param password_protected: If true, the rest api will ask for an authentication
+        :param login: login used if auth is activated
+        :param password: password used if auth is activated
+        :param active: specify if the rest api is loaded on start with Kalliope
+        """
+        self.password_protected = password_protected
+        self.login = login
+        self.password = password
+        self.active = active
+        self.port = port
+
+    def __str__(self):
+        return "%s: RestAPI: password_protected: %s, login: %s, " \
+               "password: %s, active: %s, port: %s" % (self.__class__.__name__,
+                                                       self.password_protected,
+                                                       self.login,
+                                                       self.password,
+                                                       self.active,
+                                                       self.port)

+ 2 - 1
core/Models/Settings.py

@@ -3,7 +3,7 @@
 class Settings(object):
     def __init__(self, default_tts_name=None, default_stt_name=None,
                  default_trigger_name=None, ttss=None, stts=None,
-                 random_wake_up_answers=None, random_wake_up_sounds=None, triggers=None):
+                 random_wake_up_answers=None, random_wake_up_sounds=None, triggers=None, rest_api=None):
         self.default_tts_name = default_tts_name
         self.default_stt_name = default_stt_name
         self.default_trigger_name = default_trigger_name
@@ -12,3 +12,4 @@ class Settings(object):
         self.random_wake_up_answers = random_wake_up_answers
         self.random_wake_up_sounds = random_wake_up_sounds
         self.triggers = triggers
+        self.rest_api = rest_api

+ 81 - 0
core/RestAPI/FlaskAPI.py

@@ -0,0 +1,81 @@
+import threading
+
+from flask import jsonify
+
+from core.ConfigurationManager.BrainLoader import BrainLoader
+from core.RestAPI.utils import requires_auth
+from core.SynapseLauncher import SynapseLauncher
+
+
+class FlaskAPI(threading.Thread):
+    def __init__(self, app, port=5000, brain_file=None):
+        super(FlaskAPI, self).__init__()
+        self.app = app
+        self.port = port
+        self.brain_file = brain_file
+        self.brain_yaml = BrainLoader.get_yaml_config(file_path=self.brain_file)
+
+        self.app.add_url_rule('/synapses/', view_func=self.get_synapses, methods=['GET'])
+        self.app.add_url_rule('/synapses/<synapse_name>', view_func=self.get_synapse, methods=['GET'])
+        self.app.add_url_rule('/synapses/<synapse_name>', view_func=self.run_synapse, methods=['POST'])
+
+    def run(self):
+        self.app.run(host='0.0.0.0', port="%s" % int(self.port), debug=True, threaded=True, use_reloader=False)
+
+    def _get_synapse_by_name(self, synapse_name):
+        """
+        Find a synapse in the brain by its name
+        :param synapse_name:
+        :return:
+        """
+        all_synapse = self.brain_yaml
+        for el in all_synapse:
+            print el
+            if el[0]["name"] in synapse_name:
+                return el[0]
+        return None
+
+    @requires_auth
+    def get_synapses(self):
+        """
+        get all synapse
+        """
+        data = jsonify(synapses=self.brain_yaml)
+        return data, 200
+
+    @requires_auth
+    def get_synapse(self, synapse_name):
+        """
+        get a synapse by its name
+        """
+        synapse_target = self._get_synapse_by_name(synapse_name)
+        if synapse_target is not None:
+            data = jsonify(synapses=synapse_target)
+            return data, 200
+
+        data = {
+            "synapse name not found": "%s" % synapse_name
+        }
+        return jsonify(error=data), 404
+
+    @requires_auth
+    def run_synapse(self, synapse_name):
+        """
+        Run a synapse by its name
+        test with curl:
+        curl -i --user admin:secret -X POST  http://localhost:5000/synapses/say-hello
+        :param synapse_name:
+        :return:
+        """
+        synapse_target = self._get_synapse_by_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_file=self.brain_file)
+        data = jsonify(synapses=synapse_target)
+        return data, 201

+ 0 - 0
core/RestAPI/__init__.py


+ 32 - 0
core/RestAPI/utils.py

@@ -0,0 +1,32 @@
+from functools import wraps
+from flask import request, Response
+
+from core.ConfigurationManager import SettingLoader
+
+
+def check_auth(username, password):
+    """This function is called to check if a username /
+    password combination is valid.
+    """
+    settings = SettingLoader.get_settings()
+    return username == settings.rest_api.login and password == settings.rest_api.password
+
+
+def authenticate():
+    """Sends a 401 response that enables basic auth"""
+    return Response(
+        'Could not verify your access level for that URL.\n'
+        'You have to login with proper credentials', 401,
+        {'WWW-Authenticate': 'Basic realm="Login Required"'})
+
+
+def requires_auth(f):
+    @wraps(f)
+    def decorated(*args, **kwargs):
+        settings = SettingLoader.get_settings()
+        if settings.rest_api.password_protected:
+            auth = request.authorization
+            if not auth or not check_auth(auth.username, auth.password):
+                return authenticate()
+        return f(*args, **kwargs)
+    return decorated

+ 1 - 0
install/files/python_requirements.txt

@@ -12,3 +12,4 @@ wakeonlan==0.2.2
 ipaddress==1.0.16
 pyowm==2.5.0
 python-twitter==3.1
+flask==0.11.1

+ 11 - 0
settings.yml

@@ -95,3 +95,14 @@ random_wake_up_sounds:
   - "ding.wav"
   - "dong.wav"
   # - "/my/personal/full/path/my_file.mp3"
+
+
+# ---------------------------
+# Rest API
+# ---------------------------
+rest_api:
+  active: True
+  port: 5000
+  password_protected: True
+  login: admin
+  password: secret

+ 29 - 1
test.py

@@ -1,5 +1,33 @@
 # coding: utf8
-import twitter
+import logging
+import re
+from collections import Counter
+
+from flask import Flask
+from core.RestAPI.FlaskAPI import FlaskAPI
+from core import OrderAnalyser
+from core.ConfigurationManager import SettingLoader
+from core.ConfigurationManager import YAMLLoader
+from core.ConfigurationManager.BrainLoader import BrainLoader
+
+from neurons import Systemdate
+from neurons.tasker_autoremote.tasker_autoremote import Tasker_autoremote
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+logger.setLevel(logging.DEBUG)
+
+# order = "quelle heure est-il"
+# oa = OrderAnalyser(order=order)
+# oa.start()
+
+SettingLoader.get_settings()
+
+
+# app = Flask(__name__)
+# flask_api = FlaskAPI(app)
+# flask_api.start()
+