Ver Fonte

Merge pull request #387 from kalliope-project/feature/signal_geoloc

Feature/signal geoloc
Nicolas Marcq há 7 anos atrás
pai
commit
b5bbf8841e

+ 1 - 0
Docs/signals.md

@@ -27,3 +27,4 @@ Here is a list of core signal that are installed natively with Kalliope
 | [event](../kalliope/signals/event)                     | Launch synapses periodically at fixed times, dates, or intervals. |
 | [mqtt_subscriber](../kalliope/signals/mqtt_subscriber) | Launch synapse from when receive a message from a MQTT broker     |
 | [order](../kalliope/signals/order)                     | Launch synapses from captured vocal order from the microphone     |
+| [geolocation](../kalliope/signals/geolocation)         | Define synapses to be triggered by clients handling geolocation   |

+ 76 - 0
kalliope/signals/geolocation/README.md

@@ -0,0 +1,76 @@
+# Geolocalisation
+
+## Synopsis
+
+**Geolocation** is a way to launch a synapse when ENTERING a geolocated zone.
+
+As Kalliope does not manage its own geolocation, this signal has been designed in view to be implemented from external clients (smartphones, watches, embedded devices, etc).
+
+The syntax of a geolocation declaration in a synapse is the following.
+```yml
+signals:
+    - geolocation:
+          latitude: "46.204391"
+          longitude: "6.143158"
+          radius: "10000"
+```
+
+For example, if we want Kalliope to run the synapse when entering in Geneva
+```yml
+- geolocation:
+      latitude: "46.204391"
+      longitude: "6.143158"
+      radius: "1000"
+```
+
+## Options
+
+Parameters are keyword you can use to build your geolocation
+
+List of available parameter:
+
+| parameter   | required | default | choices                                                         | comment   |
+|-------------|----------|---------|-----------------------------------------------------------------|-----------|
+| latitude    | yes      |         | 46.204391                                                         | E.g: 2016 |
+| longitude   | yes      |         | 6.143158                                                    |           |
+| radius      | yes      |         | 1 (meters)                                               |           |
+
+## Synapses example
+
+### Web clock radio
+
+Let's make a complete example. 
+We want to Kalliope to :
+- welcome when coming back home
+- Play our favourite web radio
+
+The synapse in the brain would be:
+```yml
+  - name: "geolocation-welcome-radio"
+    signals:
+      - geolocation:
+            latitude: "46.204391"
+            longitude: "6.143158"
+            radius: "10"
+    neurons:
+      - say:
+          message:
+            - "Welcome Home!"
+      - shell: 
+          cmd: "mplayer http://192.99.17.12:6410/"
+          async: True
+```
+
+After setting up a geolocation signal, you must restart Kalliope
+```bash
+python kalliope.py start
+```
+
+If the syntax is NOT ok, Kalliope will raise an error and log a message:
+```
+[Geolocation] The signal is missing mandatory parameters, check documentation
+```
+
+### Note
+
+/!\ this feature is supported by the Kalliope official smartphone application.

+ 1 - 0
kalliope/signals/geolocation/__init__.py

@@ -0,0 +1 @@
+from .geolocation import Geolocation

+ 59 - 0
kalliope/signals/geolocation/geolocation.py

@@ -0,0 +1,59 @@
+import logging
+from threading import Thread
+
+from kalliope.core import Utils
+from kalliope.core.ConfigurationManager import BrainLoader
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+
+class MissingParameter(Exception):
+    """
+    A geolocation must contain latitude, longitude, radius.
+
+    .. seealso:: Geolocation
+    """
+    pass
+
+
+class Geolocation(Thread):
+
+    def __init__(self):
+        super(Geolocation, self).__init__()
+        Utils.print_info('Init Geolocation')
+        self.brain = BrainLoader().get_brain()
+
+    def run(self):
+        logger.debug("[Geolocalisation] Loading ...")
+        self.list_synapses_with_geolocalion = self._get_list_synapse_with_geolocation(brain=self.brain)
+
+    @classmethod
+    def _get_list_synapse_with_geolocation(cls, brain):
+        """
+        return the list of synapse that use geolocation as signal in the provided brain
+        :param brain: Brain object that contain all synapses loaded
+        :type brain: Brain
+        :return: generator of synapse that use geolocation as signal
+        """
+        for synapse in brain.synapses:
+            for signal in synapse.signals:
+                # if the signal is an event we add it to the task list
+                if signal.name == "geolocation":
+                    if not cls._check_geolocation(parameters=signal.parameters):
+                        logger.debug("[Geolocation] The signal is missing mandatory parameters, check documentation")
+                        raise MissingParameter()
+                    else:
+                        yield synapse
+
+
+    @staticmethod
+    def _check_geolocation(parameters):
+        """
+        receive a dict of parameter from a geolocation signal and them
+        :param parameters: dict of parameters
+        :return: True if parameters are valid
+        """
+        # check mandatory parameters
+        mandatory_parameters = ["latitude", "longitude", "radius"]
+        return all(key in parameters for key in mandatory_parameters)

+ 17 - 0
kalliope/signals/geolocation/model.py

@@ -0,0 +1,17 @@
+class Geolocation(object):
+
+    def __init__(self, latitude, longitude, radius):
+        self.latitude = latitude
+        self.longitude = longitude
+        self.radius = radius
+
+    def __str__(self):
+        return str(self.serialize())
+
+    def __eq__(self, other):
+        """
+        This is used to compare 2 objects
+        :param other:
+        :return:
+        """
+        return self.__dict__ == other.__dict__

+ 0 - 0
kalliope/signals/geolocation/tests/__init__.py


+ 84 - 0
kalliope/signals/geolocation/tests/test_geolocalisation.py

@@ -0,0 +1,84 @@
+import unittest
+
+
+from kalliope.core.Models import Brain
+from kalliope.core.Models import Neuron
+from kalliope.core.Models import Synapse
+from kalliope.core.Models.Signal import Signal
+
+from kalliope.signals.geolocation.geolocation import Geolocation, MissingParameter
+
+
+class Test_Geolocation(unittest.TestCase):
+
+    def test_check_geolocation_valid(self):
+        expected_parameters = ["latitude", "longitude", "radius"]
+        self.assertTrue(Geolocation._check_geolocation(expected_parameters))
+
+    def test_check_geolocation_valid_with_other(self):
+        expected_parameters = ["latitude", "longitude", "radius", "kalliope", "random"]
+        self.assertTrue(Geolocation._check_geolocation(expected_parameters))
+
+    def test_check_geolocation_no_radius(self):
+        expected_parameters = ["latitude", "longitude", "kalliope", "random"]
+        self.assertFalse(Geolocation._check_geolocation(expected_parameters))
+
+    def test_check_geolocation_no_latitude(self):
+        expected_parameters = ["longitude", "radius", "kalliope", "random"]
+        self.assertFalse(Geolocation._check_geolocation(expected_parameters))
+
+    def test_check_geolocation_no_longitude(self):
+        expected_parameters = ["latitude", "radius", "kalliope", "random"]
+        self.assertFalse(Geolocation._check_geolocation(expected_parameters))
+
+    def test_get_list_synapse_with_geolocation(self):
+        # Init
+        neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
+        neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2'})
+        neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'})
+        neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'})
+
+        fake_geolocation_parameters = {
+            "latitude": 66,
+            "longitude": 66,
+            "radius": 66,
+        }
+        signal1 = Signal(name="geolocation", parameters=fake_geolocation_parameters)
+        signal2 = Signal(name="order", parameters="this is the second sentence")
+
+        synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1])
+        synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2])
+
+        synapses_list = [synapse1, synapse2]
+        br = Brain(synapses=synapses_list)
+
+        expected_list = [synapse1]
+        self.assertEqual(expected_list, list(Geolocation._get_list_synapse_with_geolocation(brain=br)))
+
+    def test_get_list_synapse_with_raise_missing_parameters(self):
+        # Init
+        neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'})
+        neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2'})
+        neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'})
+        neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'})
+
+        fake_geolocation_parameters = {
+            "longitude": 66,
+            "radius": 66,
+        }
+        signal1 = Signal(name="geolocation", parameters=fake_geolocation_parameters)
+        signal2 = Signal(name="order", parameters="this is the second sentence")
+
+        synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1])
+        synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2])
+
+        synapses_list = [synapse1, synapse2]
+        br = Brain(synapses=synapses_list)
+
+        with self.assertRaises(MissingParameter):
+            # /!\ Note: impossible to call a generator method directly ! need to use a list !
+            list(Geolocation._get_list_synapse_with_geolocation(brain=br))
+
+
+if __name__ == '__main__':
+    unittest.main()