Browse Source

Merge pull request #28 from kalliope-project/dev

Dev
Monf 8 years ago
parent
commit
1b0e4ef24b

+ 62 - 9
Docs/rest_api.md

@@ -4,17 +4,18 @@ Kalliope provides the REST API to manage the synapses. For configuring the API r
 
 
 ## Synapse API
 ## Synapse API
 
 
-| Method | URL                      | Action               |
-|--------|--------------------------|----------------------|
-| GET    | /synapses                | List synapses        |
-| GET    | /synapses/<synapse_name> | Show synapse details |
-| POST   | /synapses/<synapse_name> | Run a synapse        |
+| Method | URL                      | Action                      |
+|--------|--------------------------|-----------------------------|
+| GET    | /synapses                | List synapses               |
+| GET    | /synapses/<synapse_name> | Show synapse details        |
+| POST   | /synapses/<synapse_name> | Run a synapse by its name   |
+| POST   | /order                   | Run a synapse from an order |
 
 
 ## Curl examples
 ## Curl examples
 
 
 >**Note:** --user is only needed if `password_protected` is True
 >**Note:** --user is only needed if `password_protected` is True
 
 
-### Get all synapse
+### List synapses
 
 
 Normal response codes: 200
 Normal response codes: 200
 Error response codes: unauthorized(401), itemNotFound(404)
 Error response codes: unauthorized(401), itemNotFound(404)
@@ -68,7 +69,7 @@ Output example:
 }
 }
 ```
 ```
 
 
-### Get one synapse's detail by its name. 
+### Show synapse details 
 
 
 Normal response codes: 200
 Normal response codes: 200
 Error response codes: unauthorized(401), itemNotFound(404)
 Error response codes: unauthorized(401), itemNotFound(404)
@@ -100,7 +101,7 @@ Output example:
 }
 }
 ```
 ```
 
 
-### Run a synapse by its name. 
+### Run a synapse by its name
 
 
 Normal response codes: 201
 Normal response codes: 201
 Error response codes: unauthorized(401), itemNotFound(404)
 Error response codes: unauthorized(401), itemNotFound(404)
@@ -130,4 +131,56 @@ Output example:
     ]
     ]
   }
   }
 }
 }
-```
+```
+
+
+Run a synapse from an order
+
+Normal response codes: 201
+Error response codes: unauthorized(401), itemNotFound(404)
+
+Curl command:
+```
+curl -i --user admin:secret -H "Content-Type: application/json" -X POST -d '{"order":"my order"}' http://localhost:5000/order
+```
+
+If the order contains accent or quotes, use a file for testing with curl
+```
+cat post.json 
+{"order":"j'aime"}
+```
+Then
+```
+curl -i --user admin:secret -H "Content-Type: application/json" -X POST --data @post.json http://localhost:5000/order/
+```
+
+Output example if the order have matched and so launched synapses:
+```
+{
+  "synapses": [
+    {
+      "name": "Say-hello", 
+      "neurons": [
+        {
+          "name": "say", 
+          "parameters": "{'message': ['Hello sir']}"
+        }
+      ], 
+      "signals": [
+        {
+          "order": "hello"
+        }
+      ]
+    }
+  ]
+}
+```
+
+If the order haven't match ny synapses:
+```
+{
+  "error": {
+    "error": "The given order doesn't match any synapses"
+  }
+}
+```

+ 1 - 1
README.md

@@ -43,7 +43,7 @@ Kalliope is easy-peasy to use, see the hello world
 | [STT](Docs/stt.md)                 | Speech to text configuration                                                                |
 | [STT](Docs/stt.md)                 | Speech to text configuration                                                                |
 | [TTS](Docs/tts.md)                 | Text to speech configuration                                                                |
 | [TTS](Docs/tts.md)                 | Text to speech configuration                                                                |
 | [Triggers](Docs/trigger.md)        | Magic hotword engine used to make Kalliope listtening for an order                          |
 | [Triggers](Docs/trigger.md)        | Magic hotword engine used to make Kalliope listtening for an order                          |
-
+| [REST API](Docs/rest_api.md)       | Integrated REST API. Can be used to send an order                                           |
 
 
 ## Contributing
 ## Contributing
 
 

+ 1 - 0
core/ConfigurationManager/BrainLoader.py

@@ -38,6 +38,7 @@ class BrainLoader(object):
         dict_brain = cls.get_yaml_config(file_path)
         dict_brain = cls.get_yaml_config(file_path)
         # create a new brain
         # create a new brain
         brain = Brain()
         brain = Brain()
+        brain.brain_yaml = dict_brain
         # create list of Synapse
         # create list of Synapse
         synapses = list()
         synapses = list()
         for synapses_dict in dict_brain:
         for synapses_dict in dict_brain:

+ 2 - 2
core/CrontabManager.py

@@ -19,9 +19,9 @@ KALLIOPE_ENTRY_POINT_SCRIPT = "kalliope.py"
 
 
 class CrontabManager:
 class CrontabManager:
 
 
-    def __init__(self, brain_file=None):
+    def __init__(self, brain=None):
         self.my_user_cron = CronTab(user=True)
         self.my_user_cron = CronTab(user=True)
-        self.brain = BrainLoader.get_brain(file_path=brain_file)
+        self.brain = brain
         self.base_command = self._get_base_command()
         self.base_command = self._get_base_command()
 
 
     def load_events_in_crontab(self):
     def load_events_in_crontab(self):

+ 3 - 9
core/MainController.py

@@ -18,22 +18,16 @@ logger = logging.getLogger("kalliope")
 
 
 
 
 class MainController:
 class MainController:
-    def __init__(self, brain_file=None):
-        self.brain_file = brain_file
+    def __init__(self, brain=None):
+        self.brain = brain
         # get global configuration
         # get global configuration
         self.settings = SettingLoader.get_settings()
         self.settings = SettingLoader.get_settings()
 
 
-        # load the brain
-        if brain_file is None:
-            self.brain = BrainLoader.get_brain()
-        else:
-            self.brain = BrainLoader.get_brain(file_path=brain_file)
-
         # run the api if the user want it
         # run the api if the user want it
         if self.settings.rest_api.active:
         if self.settings.rest_api.active:
             Utils.print_info("Starting REST API Listening port: %s" % self.settings.rest_api.port)
             Utils.print_info("Starting REST API Listening port: %s" % self.settings.rest_api.port)
             app = Flask(__name__)
             app = Flask(__name__)
-            flask_api = FlaskAPI(app, port=self.settings.rest_api.port, brain_file=brain_file)
+            flask_api = FlaskAPI(app, port=self.settings.rest_api.port, brain=self.brain)
             flask_api.start()
             flask_api.start()
 
 
         # create an order listener object. This last will the trigger callback before starting
         # create an order listener object. This last will the trigger callback before starting

+ 2 - 1
core/Models/Brain.py

@@ -1,5 +1,6 @@
 
 
 class Brain(object):
 class Brain(object):
-    def __init__(self, synapses=None, brain_file=None):
+    def __init__(self, synapses=None, brain_file=None, brain_yaml=None):
         self.synapses = synapses
         self.synapses = synapses
         self.brain_file = brain_file
         self.brain_file = brain_file
+        self.brain_yaml = brain_yaml

+ 5 - 0
core/Models/Event.py

@@ -5,3 +5,8 @@ class Event(object):
     def __str__(self):
     def __str__(self):
         return "%s: period: %s" % (self.__class__.__name__,
         return "%s: period: %s" % (self.__class__.__name__,
                                    self.period)
                                    self.period)
+
+    def serialize(self):
+        return {
+            'event': self.period
+        }

+ 6 - 0
core/Models/Neuron.py

@@ -4,3 +4,9 @@ class Neuron(object):
     def __init__(self, name=None, parameters=None):
     def __init__(self, name=None, parameters=None):
         self.name = name
         self.name = name
         self.parameters = parameters
         self.parameters = parameters
+
+    def serialize(self):
+        return {
+            'name': self.name,
+            'parameters': str(self.parameters)
+        }

+ 5 - 0
core/Models/Order.py

@@ -4,3 +4,8 @@ class Order(object):
 
 
     def __str__(self):
     def __str__(self):
         return "%s: Sentence: %s" % (self.__class__.__name__, self.sentence)
         return "%s: Sentence: %s" % (self.__class__.__name__, self.sentence)
+
+    def serialize(self):
+        return {
+            'order': self.sentence
+        }

+ 7 - 0
core/Models/Synapse.py

@@ -3,3 +3,10 @@ class Synapse(object):
         self.name = name
         self.name = name
         self.neurons = neurons
         self.neurons = neurons
         self.signals = signals
         self.signals = signals
+
+    def serialize(self):
+        return {
+            'name': self.name,
+            'neurons': [e.serialize() for e in self.neurons],
+            'signals': [e.serialize() for e in self.signals]
+        }

+ 6 - 0
core/OrderAnalyser.py

@@ -30,10 +30,13 @@ class OrderAnalyser:
     def start(self):
     def start(self):
         synapses_found = False
         synapses_found = False
         problem_in_neuron_found = False
         problem_in_neuron_found = False
+        # create a dict of synapses that have benn launched
+        launched_synapses = list()
         for synapse in self.brain.synapses:
         for synapse in self.brain.synapses:
             for signal in synapse.signals:
             for signal in synapse.signals:
                 if type(signal) == Order:
                 if type(signal) == Order:
                     if self._spelt_order_match_brain_order_via_table(signal.sentence, self.order):
                     if self._spelt_order_match_brain_order_via_table(signal.sentence, self.order):
+                        launched_synapses.append(synapse)
                         synapses_found = True
                         synapses_found = True
                         logger.debug("Order found! Run neurons: %s" % synapse.neurons)
                         logger.debug("Order found! Run neurons: %s" % synapse.neurons)
                         Utils.print_success("Order matched in the brain. Running synapse \"%s\"" % synapse.name)
                         Utils.print_success("Order matched in the brain. Running synapse \"%s\"" % synapse.name)
@@ -77,6 +80,9 @@ class OrderAnalyser:
         if not synapses_found:
         if not synapses_found:
             Utils.print_info("No synapse match the captured order: %s" % self.order)
             Utils.print_info("No synapse match the captured order: %s" % self.order)
 
 
+        # return the list of launched synapse
+        return launched_synapses
+
     def _associate_order_params_to_values(self, order_to_check):
     def _associate_order_params_to_values(self, order_to_check):
         """
         """
         Associate the variables from the order to the incoming user order
         Associate the variables from the order to the incoming user order

+ 54 - 7
core/RestAPI/FlaskAPI.py

@@ -1,23 +1,32 @@
 import threading
 import threading
 
 
 from flask import jsonify
 from flask import jsonify
+from flask import request
+from flask_restful import abort
 
 
-from core.ConfigurationManager.BrainLoader import BrainLoader
+from core import OrderAnalyser
 from core.RestAPI.utils import requires_auth
 from core.RestAPI.utils import requires_auth
 from core.SynapseLauncher import SynapseLauncher
 from core.SynapseLauncher import SynapseLauncher
 
 
 
 
 class FlaskAPI(threading.Thread):
 class FlaskAPI(threading.Thread):
-    def __init__(self, app, port=5000, brain_file=None):
+    def __init__(self, app, port=5000, brain=None):
+        """
+
+        :param app: Flask API
+        :param port: Port to listen
+        :param brain: Brain object
+        :type brain: Brain
+        """
         super(FlaskAPI, self).__init__()
         super(FlaskAPI, self).__init__()
         self.app = app
         self.app = app
         self.port = port
         self.port = port
-        self.brain_file = brain_file
-        self.brain_yaml = BrainLoader.get_yaml_config(file_path=self.brain_file)
+        self.brain = brain
 
 
         self.app.add_url_rule('/synapses/', view_func=self.get_synapses, methods=['GET'])
         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.get_synapse, methods=['GET'])
         self.app.add_url_rule('/synapses/<synapse_name>', view_func=self.run_synapse, methods=['POST'])
         self.app.add_url_rule('/synapses/<synapse_name>', view_func=self.run_synapse, methods=['POST'])
+        self.app.add_url_rule('/order/', view_func=self.run_order, methods=['POST'])
 
 
     def run(self):
     def run(self):
         self.app.run(host='0.0.0.0', port="%s" % int(self.port), debug=True, threaded=True, use_reloader=False)
         self.app.run(host='0.0.0.0', port="%s" % int(self.port), debug=True, threaded=True, use_reloader=False)
@@ -28,7 +37,7 @@ class FlaskAPI(threading.Thread):
         :param synapse_name:
         :param synapse_name:
         :return:
         :return:
         """
         """
-        all_synapse = self.brain_yaml
+        all_synapse = self.brain.brain_yaml
         for el in all_synapse:
         for el in all_synapse:
             print el
             print el
             if el[0]["name"] in synapse_name:
             if el[0]["name"] in synapse_name:
@@ -40,7 +49,7 @@ class FlaskAPI(threading.Thread):
         """
         """
         get all synapse
         get all synapse
         """
         """
-        data = jsonify(synapses=self.brain_yaml)
+        data = jsonify(synapses=self.brain.brain_yaml)
         return data, 200
         return data, 200
 
 
     @requires_auth
     @requires_auth
@@ -76,6 +85,44 @@ class FlaskAPI(threading.Thread):
             return jsonify(error=data), 404
             return jsonify(error=data), 404
 
 
         # run the synapse
         # run the synapse
-        SynapseLauncher.start_synapse(synapse_name, brain_file=self.brain_file)
+        SynapseLauncher.start_synapse(synapse_name, brain=self.brain)
         data = jsonify(synapses=synapse_target)
         data = jsonify(synapses=synapse_target)
         return data, 201
         return data, 201
+
+    @requires_auth
+    def run_order(self):
+        """
+        Give an order to Kalliope via API like it was from a spoken one
+        Test with curl
+        curl -i --user admin:secret -H "Content-Type: application/json" -X POST -d '{"order":"my order"}' http://localhost:5000/order
+        In case of quotes in the order or accents, use a file
+        cat post.json:
+        {"order":"j'aime"}
+        curl -i --user admin:secret -H "Content-Type: application/json" -X POST --data @post.json http://localhost:5000/order/
+        :return:
+        """
+        if not request.get_json() or 'order' not in request.get_json():
+            abort(400)
+
+        order = request.get_json('order')
+        if order is not None:
+            # get the order
+            order_to_run = order["order"]
+            oa = OrderAnalyser(order=order_to_run, brain=self.brain)
+            launched_synapses = oa.start()
+
+            if launched_synapses:
+                # if the list is not empty, we have launched one or more synapses
+                data = jsonify(synapses=[e.serialize() for e in launched_synapses])
+                return data, 201
+            else:
+                data = {
+                    "error": "The given order doesn't match any synapses"
+                }
+                return jsonify(error=data), 400
+
+        else:
+            data = {
+                "error": "order cannot be null"
+            }
+            return jsonify(error=data), 400

+ 8 - 11
core/ShellGui.py

@@ -1,18 +1,16 @@
 # coding: utf8
 # coding: utf8
 
 
+import locale
 import logging
 import logging
-
+import signal
 import sys
 import sys
 
 
-import signal
 from dialog import Dialog
 from dialog import Dialog
-import locale
 
 
 from core import OrderListener
 from core import OrderListener
-from core.ConfigurationManager.BrainLoader import BrainLoader
+from core.ConfigurationManager import SettingLoader
 from core.SynapseLauncher import SynapseLauncher
 from core.SynapseLauncher import SynapseLauncher
 from core.Utils import Utils
 from core.Utils import Utils
-from core.ConfigurationManager import SettingLoader
 from neurons import Say
 from neurons import Say
 
 
 logging.basicConfig()
 logging.basicConfig()
@@ -28,9 +26,10 @@ signal.signal(signal.SIGINT, signal_handler)
 
 
 
 
 class ShellGui:
 class ShellGui:
-    def __init__(self, brain_file=None):
+    def __init__(self, brain=None):
         # override brain
         # override brain
-        self.brain_file = brain_file
+        self.brain = brain
+
         # get settings
         # get settings
         self.settings = SettingLoader.get_settings()
         self.settings = SettingLoader.get_settings()
         locale.setlocale(locale.LC_ALL, '')
         locale.setlocale(locale.LC_ALL, '')
@@ -155,13 +154,11 @@ class ShellGui:
         Show a list of available synapse in the brain to run it directly
         Show a list of available synapse in the brain to run it directly
         :return:
         :return:
         """
         """
-        # get the list of synapse from the brain
-        brain = BrainLoader.get_brain(file_path=self.brain_file)
 
 
         # create a tuple for the list menu
         # create a tuple for the list menu
         choices = list()
         choices = list()
         x = 0
         x = 0
-        for el in brain.synapses:
+        for el in self.brain.synapses:
             tup = (str(el.name), str(x))
             tup = (str(el.name), str(x))
             choices.append(tup)
             choices.append(tup)
             x += 1
             x += 1
@@ -173,5 +170,5 @@ class ShellGui:
             self.show_main_menu()
             self.show_main_menu()
         if code == self.d.OK:
         if code == self.d.OK:
             logger.debug("Run synapse from GUI: %s" % tag)
             logger.debug("Run synapse from GUI: %s" % tag)
-            SynapseLauncher.start_synapse(tag, brain_file=self.brain_file)
+            SynapseLauncher.start_synapse(tag, brain=self.brain)
             self.show_synapses_test_menu()
             self.show_synapses_test_menu()

+ 4 - 7
core/SynapseLauncher.py

@@ -12,22 +12,19 @@ class SynapseLauncher(object):
         pass
         pass
 
 
     @classmethod
     @classmethod
-    def start_synapse(cls, name, brain_file=None):
+    def start_synapse(cls, name, brain=None):
         """
         """
         Start a synapse by it's name
         Start a synapse by it's name
         :param name: Name (Unique ID) of the synapse to launch
         :param name: Name (Unique ID) of the synapse to launch
-        :param brain_file: Brain file path to load instead of the default one
+        :param brain: Brain instance
         """
         """
         synapse_name_launch = name
         synapse_name_launch = name
         # get the brain
         # get the brain
-        if brain_file is None:
-            brain = BrainLoader.get_brain()
-        else:
-            brain = BrainLoader.get_brain(file_path=brain_file)
+        cls.brain = brain
 
 
         # check if we have found and launched the synapse
         # check if we have found and launched the synapse
         synapse_launched = False
         synapse_launched = False
-        for synapse in brain.synapses:
+        for synapse in cls.brain.synapses:
             if synapse.name == synapse_name_launch:
             if synapse.name == synapse_name_launch:
                 cls._run_synapse(synapse)
                 cls._run_synapse(synapse)
                 synapse_launched = True
                 synapse_launched = True

+ 1 - 0
install/files/python_requirements.txt

@@ -13,4 +13,5 @@ ipaddress==1.0.16
 pyowm==2.5.0
 pyowm==2.5.0
 python-twitter==3.1
 python-twitter==3.1
 flask==0.11.1
 flask==0.11.1
+Flask-Restful==0.3.5
 wikipedia==1.4.0
 wikipedia==1.4.0

+ 10 - 7
kalliope.py

@@ -5,6 +5,7 @@ import logging
 
 
 from core import ShellGui
 from core import ShellGui
 from core import Utils
 from core import Utils
+from core.ConfigurationManager.BrainLoader import BrainLoader
 from core.CrontabManager import CrontabManager
 from core.CrontabManager import CrontabManager
 from core.MainController import MainController
 from core.MainController import MainController
 import signal
 import signal
@@ -49,6 +50,11 @@ def main():
 
 
     # by default, no brain file is set. Use the default one: brain.yml in the root path
     # by default, no brain file is set. Use the default one: brain.yml in the root path
     brain_file = None
     brain_file = None
+    # check if user set a brain.yml file
+    if args.brain_file:
+        brain_file = args.brain_file
+    # load the brain once
+    brain = BrainLoader.get_brain(file_path=brain_file)
 
 
     # check the user provide a valid action
     # check the user provide a valid action
     if args.action not in ACTION_LIST:
     if args.action not in ACTION_LIST:
@@ -56,17 +62,14 @@ def main():
         parser.print_help()
         parser.print_help()
 
 
     if args.action == "start":
     if args.action == "start":
-        # check if user set a brain.yml file
-        if args.brain_file:
-            brain_file = args.brain_file
 
 
         # user set a synapse to start
         # user set a synapse to start
         if args.run_synapse is not None:
         if args.run_synapse is not None:
-            SynapseLauncher.start_synapse(args.run_synapse, brain_file=brain_file)
+            SynapseLauncher.start_synapse(args.run_synapse, brain=brain)
 
 
         if args.run_synapse is None:
         if args.run_synapse is None:
             # first, load events in crontab
             # first, load events in crontab
-            crontab_manager = CrontabManager(brain_file=brain_file)
+            crontab_manager = CrontabManager(brain=brain)
             crontab_manager.load_events_in_crontab()
             crontab_manager.load_events_in_crontab()
             Utils.print_success("Events loaded in crontab")
             Utils.print_success("Events loaded in crontab")
             # then start kalliope
             # then start kalliope
@@ -75,10 +78,10 @@ def main():
             # catch signal for killing on Ctrl+C pressed
             # catch signal for killing on Ctrl+C pressed
             signal.signal(signal.SIGINT, signal_handler)
             signal.signal(signal.SIGINT, signal_handler)
             # start the main controller
             # start the main controller
-            MainController(brain_file=brain_file)
+            MainController(brain=brain)
 
 
     if args.action == "gui":
     if args.action == "gui":
-        ShellGui(args.brain_file)
+        ShellGui(brain=brain)
 
 
 
 
 def configure_logging(debug=None):
 def configure_logging(debug=None):

+ 10 - 34
test.py

@@ -1,55 +1,31 @@
 # coding: utf8
 # coding: utf8
 import logging
 import logging
 
 
+from flask import Flask
+
 from core import OrderAnalyser
 from core import OrderAnalyser
 from core import Utils
 from core import Utils
 from core.ConfigurationManager import SettingLoader
 from core.ConfigurationManager import SettingLoader
 from core.ConfigurationManager.BrainLoader import BrainLoader
 from core.ConfigurationManager.BrainLoader import BrainLoader
 from core.Players import Mplayer
 from core.Players import Mplayer
+from core.RestAPI.FlaskAPI import FlaskAPI
 
 
 logging.basicConfig()
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
 logger = logging.getLogger("kalliope")
 logger.setLevel(logging.DEBUG)
 logger.setLevel(logging.DEBUG)
 
 
-# order = "quelle heure est-il"
-# oa = OrderAnalyser(order=order)
-# oa.start()
-
-
 
 
 brain = BrainLoader.get_brain()
 brain = BrainLoader.get_brain()
+#
+# order = "bonjour"
+# oa = OrderAnalyser(order=order, brain=brain)
+# oa.start()
 
 
-order = "bonjour"
-
-oa = OrderAnalyser(order=order, brain=brain)
-
-oa.start()
 
 
 
 
-# settings = SettingLoader.get_settings()
-#
-# tts_name_to_use = "pico2wave"
-# sentence_to_say = "bonjour monsieur, je m'appelle Kalliopé"
-#
-#
-# def _get_tts_object_from_name(tts_name_to_use):
-#     """
-#     Return a Tts object from the nae of the Tss. Get parameters in settings
-#     :param tts_name_to_use:
-#     :return:
-#     """
-#     return next((x for x in settings.ttss if x.name == tts_name_to_use), None)
-#
-#
-# # create a tts object from the tts the user want to user
-# tts_object = _get_tts_object_from_name(tts_name_to_use)
-#
-# if tts_object is None:
-#     print "TTS module name %s not found in settings" % tts_name_to_use
-#
-# else:
-#     tts_module_instance = Utils.get_dynamic_class_instantiation("tts", tts_object.name.capitalize(), tts_object.parameters)
-#     tts_module_instance.say(sentence_to_say)
+app = Flask(__name__)
+flask_api = FlaskAPI(app, port=5000, brain=brain)
+flask_api.start()