Browse Source

led and mute button support for Rpi

nico 8 years ago
parent
commit
473df782a6

+ 40 - 0
Docs/settings.md

@@ -323,5 +323,45 @@ And use variables in your neurons:
           password: "{{password}}"
           password: "{{password}}"
 ```
 ```
 
 
+## Raspberry LED and mute button
+LEDs connected to GPIO port of your Raspberry can be used to know current status of Kalliope.
+A button can also be added in order to pause the trigger process. Kalliope does not listen for the hotword anymore when pressed.
+
+A Dictionary called `rpi` can be declared which contains pin number to use following the mapping bellow
+
+| Value name        | Description                                                                                                |
+|-------------------|------------------------------------------------------------------------------------------------------------|
+| pin_mute_button   | Pin connected to a mute button. When pressed the trigger process of kalliope is paused                     |
+| pin_led_started   | Pin switched to "on" when Kalliope is running                                                              |
+| pin_led_muted     | Pin switched to "on" when the mute button is pressed                                                       |
+| pin_led_talking   | Pin switched to "on" when Kalliope is talking                                                              |
+| pin_led_listening | Pin switched to "on" when Kalliope is readu to listen an order after a trigger detection ("Say something") |
+
+**Example config**
+```yml
+rpi:
+  pin_mute_button: 6
+  pin_led_started: 5
+  pin_led_muted: 17
+  pin_led_talking: 27
+  pin_led_listening: 22
+```
+
+**Example circuit**
+
+You will be using one of the ‘ground’ (GND) pins to act like the ‘negative’ or 0 volt ends of a battery. 
+The ‘positive’ end of the battery will be provided by a GPIO pin.
+
+<p align="center">
+    <img style="width: 200px;" src="../images/led_kalliope_circuit.png">
+</p>
+
+
+>**Note:** You must ALWAYS use resistors to connect LEDs up to the GPIO pins of the Raspberry Pi. 
+The Raspberry Pi can only supply a small current (about 60mA). T
+he LEDs will want to draw more, and if allowed to they will burn out the Raspberry Pi. 
+Therefore putting the resistors in the circuit will ensure that only this small current will flow and the Pi will not be damaged.
+
+
 ## Next: configure the brain of Kalliope
 ## Next: configure the brain of Kalliope
 Now your settings are ok, you can start creating the [brain](brain.md) of your assistant.
 Now your settings are ok, you can start creating the [brain](brain.md) of your assistant.

+ 2 - 1
Tests/test_models.py

@@ -353,7 +353,8 @@ class TestModels(unittest.TestCase):
                 'ttss': ['ttts'],
                 'ttss': ['ttts'],
                 'variables': {'key1': 'val1'},
                 'variables': {'key1': 'val1'},
                 'resources': None,
                 'resources': None,
-                'triggers': ['snowboy']
+                'triggers': ['snowboy'],
+                'rpi_settings': None
             }
             }
 
 
             self.assertDictEqual(expected_result_serialize, setting1.serialize())
             self.assertDictEqual(expected_result_serialize, setting1.serialize())

BIN
images/led_kalliope_circuit.png


+ 15 - 2
kalliope/__init__.py

@@ -148,10 +148,23 @@ 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 state machine
             # start the state machine
-            MainController(brain=brain)
+            try:
+                MainController(brain=brain)
+            except (KeyboardInterrupt, SystemExit):
+                Utils.print_info("Ctrl+C pressed. Killing Kalliope")
+            finally:
+                # we need to switch GPIO pin to default status if we are using a Rpi
+                if settings.rpi_settings:
+                    logger.debug("Clean GPIO")
+                    import RPi.GPIO as GPIO
+                    GPIO.cleanup()
 
 
     if parser.action == "gui":
     if parser.action == "gui":
-        ShellGui(brain=brain)
+        try:
+            ShellGui(brain=brain)
+        except (KeyboardInterrupt, SystemExit):
+            Utils.print_info("Ctrl+C pressed. Killing Kalliope")
+            sys.exit(0)
 
 
 
 
 def configure_logging(debug=None):
 def configure_logging(debug=None):

+ 31 - 2
kalliope/core/ConfigurationManager/SettingLoader.py

@@ -2,6 +2,7 @@ import logging
 import os
 import os
 from six import with_metaclass
 from six import with_metaclass
 
 
+from kalliope.core.Models.RpiSettings import RpiSettings
 from .YAMLLoader import YAMLLoader
 from .YAMLLoader import YAMLLoader
 from kalliope.core.Models.Resources import Resources
 from kalliope.core.Models.Resources import Resources
 from kalliope.core.Utils.Utils import Utils
 from kalliope.core.Utils.Utils import Utils
@@ -113,6 +114,7 @@ class SettingLoader(with_metaclass(Singleton, object)):
         default_synapse = self._get_default_synapse(settings)
         default_synapse = self._get_default_synapse(settings)
         resources = self._get_resources(settings)
         resources = self._get_resources(settings)
         variables = self._get_variables(settings)
         variables = self._get_variables(settings)
+        rpi_settings = self._get_rpi_settings(settings)
 
 
         # Load the setting singleton with the parameters
         # Load the setting singleton with the parameters
         setting_object.default_tts_name = default_tts_name
         setting_object.default_tts_name = default_tts_name
@@ -131,6 +133,7 @@ class SettingLoader(with_metaclass(Singleton, object)):
         setting_object.default_synapse = default_synapse
         setting_object.default_synapse = default_synapse
         setting_object.resources = resources
         setting_object.resources = resources
         setting_object.variables = variables
         setting_object.variables = variables
+        setting_object.rpi_settings = rpi_settings
 
 
         return setting_object
         return setting_object
 
 
@@ -452,7 +455,7 @@ class SettingLoader(with_metaclass(Singleton, object)):
                 # check the CORS request settings
                 # check the CORS request settings
                 allowed_cors_origin = False
                 allowed_cors_origin = False
                 if "allowed_cors_origin" in rest_api:
                 if "allowed_cors_origin" in rest_api:
-                     allowed_cors_origin = rest_api["allowed_cors_origin"]
+                    allowed_cors_origin = rest_api["allowed_cors_origin"]
 
 
             except KeyError as e:
             except KeyError as e:
                 raise SettingNotFound("%s settings not found" % e)
                 raise SettingNotFound("%s settings not found" % e)
@@ -604,7 +607,7 @@ class SettingLoader(with_metaclass(Singleton, object)):
         return play_on_ready_notification
         return play_on_ready_notification
 
 
     @staticmethod
     @staticmethod
-    def _get_on_ready_answers( settings):
+    def _get_on_ready_answers(settings):
         """
         """
         Return the list of on_ready_answers string from the settings.
         Return the list of on_ready_answers string from the settings.
         :param settings: The YAML settings file
         :param settings: The YAML settings file
@@ -662,4 +665,30 @@ class SettingLoader(with_metaclass(Singleton, object)):
             # User does not provide this settings
             # User does not provide this settings
             return dict()
             return dict()
 
 
+    @staticmethod
+    def _get_rpi_settings(settings):
+        """
+        return RpiSettings object
+        :param settings: The loaded YAML settings file
+        :return: 
+        """
 
 
+        try:
+            rpi_settings_dict = settings["rpi"]
+            rpi_settings = RpiSettings()
+            # affect pin if there are declared
+            if "pin_mute_button" in rpi_settings_dict:
+                rpi_settings.pin_mute_button = rpi_settings_dict["pin_mute_button"]
+            if "pin_led_started" in rpi_settings_dict:
+                rpi_settings.pin_led_started = rpi_settings_dict["pin_led_started"]
+            if "pin_led_muted" in rpi_settings_dict:
+                rpi_settings.pin_led_muted = rpi_settings_dict["pin_led_muted"]
+            if "pin_led_talking" in rpi_settings_dict:
+                rpi_settings.pin_led_talking = rpi_settings_dict["pin_led_talking"]
+            if "pin_led_listening" in rpi_settings_dict:
+                rpi_settings.pin_led_listening = rpi_settings_dict["pin_led_listening"]
+
+            return rpi_settings
+        except KeyError:
+            logger.debug("[SettingsLoader] No Rpi config")
+            return None

+ 37 - 11
kalliope/core/MainController.py

@@ -12,6 +12,7 @@ from kalliope.core.Players import Mplayer
 from kalliope.core.RestAPI.FlaskAPI import FlaskAPI
 from kalliope.core.RestAPI.FlaskAPI import FlaskAPI
 from kalliope.core.SynapseLauncher import SynapseLauncher
 from kalliope.core.SynapseLauncher import SynapseLauncher
 from kalliope.core.TriggerLauncher import TriggerLauncher
 from kalliope.core.TriggerLauncher import TriggerLauncher
+from kalliope.core.Utils.RpiUtils import RpiUtils
 from kalliope.neurons.say.say import Say
 from kalliope.neurons.say.say import Say
 
 
 logging.basicConfig()
 logging.basicConfig()
@@ -43,6 +44,19 @@ class MainController:
         # Starting the rest API
         # Starting the rest API
         self._start_rest_api()
         self._start_rest_api()
 
 
+        self.rpi_utils = None
+        if self.settings.rpi_settings:
+            if self.settings.rpi_settings.pin_mute_button:
+                # start the listening for button pressed thread only if the user set a pin
+                self.rpi_utils = RpiUtils(self.settings.rpi_settings, self.muted_button_pressed)
+                self.rpi_utils.daemon = True
+                self.rpi_utils.start()
+        # switch high the start led, as kalliope is started. Only if the setting exist
+        if self.settings.rpi_settings:
+            if self.settings.rpi_settings.pin_led_started:
+                logger.debug("[MainController] Switching pin_led_started to ON")
+                RpiUtils.switch_pin_to_on(self.settings.rpi_settings.pin_led_started)
+
         # save an instance of the trigger
         # save an instance of the trigger
         self.trigger_instance = None
         self.trigger_instance = None
         self.trigger_callback_called = False
         self.trigger_callback_called = False
@@ -84,7 +98,7 @@ class MainController:
         """
         """
         This function will start the trigger thread that listen for the hotword
         This function will start the trigger thread that listen for the hotword
         """
         """
-        logger.debug("Entering state: %s" % self.state)
+        logger.debug("[MainController] Entering state: %s" % self.state)
         self.trigger_instance = self._get_default_trigger()
         self.trigger_instance = self._get_default_trigger()
         self.trigger_callback_called = False
         self.trigger_callback_called = False
         self.trigger_instance.daemon = True
         self.trigger_instance.daemon = True
@@ -96,7 +110,7 @@ class MainController:
         """
         """
         Play a sound when Kalliope is ready to be awaken at the first start
         Play a sound when Kalliope is ready to be awaken at the first start
         """
         """
-        logger.debug("Entering state: %s" % self.state)
+        logger.debug("[MainController] Entering state: %s" % self.state)
         if (not self.on_ready_notification_played_once and self.settings.play_on_ready_notification == "once") or \
         if (not self.on_ready_notification_played_once and self.settings.play_on_ready_notification == "once") or \
                         self.settings.play_on_ready_notification == "always":
                         self.settings.play_on_ready_notification == "always":
             # we remember that we played the notification one time
             # we remember that we played the notification one time
@@ -113,7 +127,7 @@ class MainController:
         """
         """
         Method to print in debug that the main process is waiting for a trigger detection
         Method to print in debug that the main process is waiting for a trigger detection
         """
         """
-        logger.debug("Entering state: %s" % self.state)
+        logger.debug("[MainController] Entering state: %s" % self.state)
         Utils.print_info("Waiting for trigger detection")
         Utils.print_info("Waiting for trigger detection")
         # this loop is used to keep the main thread alive
         # this loop is used to keep the main thread alive
         while not self.trigger_callback_called:
         while not self.trigger_callback_called:
@@ -124,10 +138,13 @@ class MainController:
         """
         """
         Method to print in debug that the main process is waiting for an order to analyse
         Method to print in debug that the main process is waiting for an order to analyse
         """
         """
-        logger.debug("Entering state: %s" % self.state)
+        logger.debug("[MainController] Entering state: %s" % self.state)
         # this loop is used to keep the main thread alive
         # this loop is used to keep the main thread alive
         while not self.order_listener_callback_called:
         while not self.order_listener_callback_called:
             sleep(0.1)
             sleep(0.1)
+        if self.settings.rpi_settings:
+            if self.settings.rpi_settings.pin_led_listening:
+                RpiUtils.switch_pin_to_off(self.settings.rpi_settings.pin_led_listening)
         self.next_state()
         self.next_state()
 
 
     def trigger_callback(self):
     def trigger_callback(self):
@@ -135,7 +152,7 @@ class MainController:
         we have detected the hotword, we can now pause the Trigger for a while
         we have detected the hotword, we can now pause the Trigger for a while
         The user can speak out loud his order during this time.
         The user can speak out loud his order during this time.
         """
         """
-        logger.debug("Trigger callback called, switching to the next state")
+        logger.debug("[MainController] Trigger callback called, switching to the next state")
         self.trigger_callback_called = True
         self.trigger_callback_called = True
 
 
     def stop_trigger_process(self):
     def stop_trigger_process(self):
@@ -143,7 +160,7 @@ class MainController:
         The trigger has been awaken, we don't needed it anymore
         The trigger has been awaken, we don't needed it anymore
         :return: 
         :return: 
         """
         """
-        logger.debug("Entering state: %s" % self.state)
+        logger.debug("[MainController] Entering state: %s" % self.state)
         self.trigger_instance.stop()
         self.trigger_instance.stop()
         self.next_state()
         self.next_state()
 
 
@@ -151,7 +168,7 @@ class MainController:
         """
         """
         Start the STT engine thread
         Start the STT engine thread
         """
         """
-        logger.debug("Entering state: %s" % self.state)
+        logger.debug("[MainController] Entering state: %s" % self.state)
         # start listening for an order
         # start listening for an order
         self.order_listener_callback_called = False
         self.order_listener_callback_called = False
         self.order_listener = OrderListener(callback=self.order_listener_callback)
         self.order_listener = OrderListener(callback=self.order_listener_callback)
@@ -164,7 +181,7 @@ class MainController:
         Play a sound or make Kalliope say something to notify the user that she has been awaken and now
         Play a sound or make Kalliope say something to notify the user that she has been awaken and now
         waiting for order
         waiting for order
         """
         """
-        logger.debug("Entering state: %s" % self.state)
+        logger.debug("[MainController] Entering state: %s" % self.state)
         # if random wake answer sentence are present, we play this
         # if random wake answer sentence are present, we play this
         if self.settings.random_wake_up_answers is not None:
         if self.settings.random_wake_up_answers is not None:
             Say(message=self.settings.random_wake_up_answers)
             Say(message=self.settings.random_wake_up_answers)
@@ -180,7 +197,7 @@ class MainController:
         :param order: the sentence received
         :param order: the sentence received
         :type order: str
         :type order: str
         """
         """
-        logger.debug("order listener callback called. Order to process: %s" % order)
+        logger.debug("[MainController] Order listener callback called. Order to process: %s" % order)
         self.order_to_process = order
         self.order_to_process = order
         self.order_listener_callback_called = True
         self.order_listener_callback_called = True
 
 
@@ -188,7 +205,7 @@ class MainController:
         """
         """
         Start the order analyser with the caught order to process
         Start the order analyser with the caught order to process
         """
         """
-        logger.debug("order in analysing_order_thread %s" % self.order_to_process)
+        logger.debug("[MainController] order in analysing_order_thread %s" % self.order_to_process)
         SynapseLauncher.run_matching_synapse_from_order(self.order_to_process,
         SynapseLauncher.run_matching_synapse_from_order(self.order_to_process,
                                                         self.brain,
                                                         self.brain,
                                                         self.settings,
                                                         self.settings,
@@ -217,7 +234,7 @@ class MainController:
         """
         """
         # take first randomly a path
         # take first randomly a path
         random_path = random.choice(random_wake_up_sounds)
         random_path = random.choice(random_wake_up_sounds)
-        logger.debug("Selected sound: %s" % random_path)
+        logger.debug("[MainController] Selected sound: %s" % random_path)
         return Utils.get_real_file_path(random_path)
         return Utils.get_real_file_path(random_path)
 
 
     def _start_rest_api(self):
     def _start_rest_api(self):
@@ -234,3 +251,12 @@ class MainController:
                                  allowed_cors_origin=self.settings.rest_api.allowed_cors_origin)
                                  allowed_cors_origin=self.settings.rest_api.allowed_cors_origin)
             flask_api.daemon = True
             flask_api.daemon = True
             flask_api.start()
             flask_api.start()
+
+    def muted_button_pressed(self, muted=False):
+        logger.debug("[MainController] Mute button pressed. Switch trigger process to muted: %s" % muted)
+        if muted:
+            self.trigger_instance.pause()
+            Utils.print_info("Kalliope now muted")
+        else:
+            self.trigger_instance.unpause()
+            Utils.print_info("Kalliope now listening for trigger detection")

+ 35 - 0
kalliope/core/Models/RpiSettings.py

@@ -0,0 +1,35 @@
+
+
+class RpiSettings(object):
+
+    def __init__(self, pin_mute_button=None, pin_led_started=None, pin_led_muted=None,
+                 pin_led_talking=None, pin_led_listening=None):
+        self.pin_mute_button = pin_mute_button
+        self.pin_led_started = pin_led_started
+        self.pin_led_muted = pin_led_muted
+        self.pin_led_talking = pin_led_talking
+        self.pin_led_listening = pin_led_listening
+
+    def __str__(self):
+        return str(self.serialize())
+
+    def serialize(self):
+        """
+        This method allows to serialize in a proper way this object        
+        """
+
+        return {
+            'pin_mute_button': self.pin_mute_button,
+            'pin_led_started': self.pin_led_started,
+            'pin_led_muted': self.pin_led_muted,
+            'pin_led_talking': self.pin_led_talking,
+            'pin_led_listening': self.pin_led_listening,
+        }
+
+    def __eq__(self, other):
+        """
+        This is used to compare 2 objects
+        :param other:
+        :return:
+        """
+        return self.__dict__ == other.__dict__

+ 5 - 2
kalliope/core/Models/Settings.py

@@ -24,7 +24,8 @@ class Settings(object):
                  cache_path=None,
                  cache_path=None,
                  default_synapse=None,
                  default_synapse=None,
                  resources=None,
                  resources=None,
-                 variables=None):
+                 variables=None,
+                 rpi_settings=None):
 
 
         self.default_tts_name = default_tts_name
         self.default_tts_name = default_tts_name
         self.default_stt_name = default_stt_name
         self.default_stt_name = default_stt_name
@@ -44,6 +45,7 @@ class Settings(object):
         self.variables = variables
         self.variables = variables
         self.machine = platform.machine()   # can be x86_64 or armv7l
         self.machine = platform.machine()   # can be x86_64 or armv7l
         self.kalliope_version = current_kalliope_version
         self.kalliope_version = current_kalliope_version
+        self.rpi_settings = rpi_settings
 
 
     def serialize(self):
     def serialize(self):
         """
         """
@@ -71,7 +73,8 @@ class Settings(object):
             'resources': self.resources,
             'resources': self.resources,
             'variables': self.variables,
             'variables': self.variables,
             'machine': self.machine,
             'machine': self.machine,
-            'kalliope_version': self.kalliope_version
+            'kalliope_version': self.kalliope_version,
+            'rpi_settings': self.rpi_settings.serialize() if self.rpi_settings is not None else None
         }
         }
 
 
     def __str__(self):
     def __str__(self):

+ 1 - 1
kalliope/core/Models/__init__.py

@@ -5,4 +5,4 @@ from .Brain import Brain
 from .Order import Order
 from .Order import Order
 from .Synapse import Synapse
 from .Synapse import Synapse
 from .Neuron import Neuron
 from .Neuron import Neuron
-
+from .RpiSettings import RpiSettings

+ 21 - 0
kalliope/core/NeuronModule.py

@@ -10,6 +10,7 @@ from kalliope.core import OrderListener
 from kalliope.core.ConfigurationManager import SettingLoader, BrainLoader
 from kalliope.core.ConfigurationManager import SettingLoader, BrainLoader
 from kalliope.core.Models.MatchedSynapse import MatchedSynapse
 from kalliope.core.Models.MatchedSynapse import MatchedSynapse
 from kalliope.core.OrderAnalyser import OrderAnalyser
 from kalliope.core.OrderAnalyser import OrderAnalyser
+from kalliope.core.Utils.RpiUtils import RpiUtils
 from kalliope.core.Utils.Utils import Utils
 from kalliope.core.Utils.Utils import Utils
 
 
 logging.basicConfig()
 logging.basicConfig()
@@ -159,9 +160,15 @@ class NeuronModule(object):
                                                                         module_name=self.tts.name,
                                                                         module_name=self.tts.name,
                                                                         parameters=self.tts.parameters,
                                                                         parameters=self.tts.parameters,
                                                                         resources_dir=tts_folder)
                                                                         resources_dir=tts_folder)
+            # Kalliope will talk, turn on the LED
+            self.switch_on_led_talking(rpi_settings=self.settings.rpi_settings, on=True)
+
             # generate the audio file and play it
             # generate the audio file and play it
             tts_module_instance.say(tts_message)
             tts_module_instance.say(tts_message)
 
 
+            # Kalliope has finished to talk, turn off the LED
+            self.switch_on_led_talking(rpi_settings=self.settings.rpi_settings, on=False)
+
     def _get_message_from_dict(self, message_dict):
     def _get_message_from_dict(self, message_dict):
         """
         """
         Generate a message that can be played by a TTS engine from a dict of variable and the jinja template
         Generate a message that can be played by a TTS engine from a dict of variable and the jinja template
@@ -287,3 +294,17 @@ class NeuronModule(object):
 
 
         logger.debug("NeuroneModule: TTS args: %s" % tts_object)
         logger.debug("NeuroneModule: TTS args: %s" % tts_object)
         return tts_object
         return tts_object
+
+    @staticmethod
+    def switch_on_led_talking(rpi_settings, on):
+        """
+        Call the Rpi utils class to switch the led talking if the setting has been specified by the user
+        :param rpi_settings: Rpi
+        :param on: True if the led need to be switched to on
+        """
+        if rpi_settings:
+            if rpi_settings.pin_led_talking:
+                if on:
+                    RpiUtils.switch_pin_to_on(rpi_settings.pin_led_talking)
+                else:
+                    RpiUtils.switch_pin_to_off(rpi_settings.pin_led_talking)

+ 0 - 13
kalliope/core/ShellGui.py

@@ -17,19 +17,6 @@ logging.basicConfig()
 logger = logging.getLogger("kalliope")
 logger = logging.getLogger("kalliope")
 
 
 
 
-def signal_handler(signal, frame):
-    """
-    Used to catch a keyboard signal like Ctrl+C in order to kill the kalliope program
-    :param signal: signal handler
-    :param frame: execution frame
-    """
-    print("\n")
-    Utils.print_info("Ctrl+C pressed. Killing Kalliope")
-    sys.exit(0)
-
-signal.signal(signal.SIGINT, signal_handler)
-
-
 class ShellGui:
 class ShellGui:
     def __init__(self, brain=None):
     def __init__(self, brain=None):
         """
         """

+ 91 - 0
kalliope/core/Utils/RpiUtils.py

@@ -0,0 +1,91 @@
+from threading import Thread
+
+try:
+    # only import if we are on a Rpi
+    import RPi.GPIO as GPIO
+except RuntimeError:
+    pass
+import time
+
+import logging
+
+from kalliope.core.Models.RpiSettings import RpiSettings
+
+logging.basicConfig()
+logger = logging.getLogger("kalliope")
+
+
+class RpiUtils(Thread):
+
+    def __init__(self, rpi_settings=None, callback=None):
+        """
+        
+        :param rpi_settings: Settings object with GPIO pin number to use
+        :type rpi_settings: RpiSettings
+        :param callback: Callback function from the main controller to call when the mute button is pressed
+        """
+        super(RpiUtils, self).__init__()
+        GPIO.setmode(GPIO.BCM)  # Use GPIO name
+        GPIO.setwarnings(False)
+        self.rpi_settings = rpi_settings
+        self.callback = callback
+        self.init_gpio(self.rpi_settings)
+
+    def run(self):
+        # run the main thread
+        try:
+            while True:  # keep the thread alive
+                time.sleep(0.1)
+        except (KeyboardInterrupt, SystemExit):
+            self.destroy()
+        self.destroy()
+
+    def switch_kalliope_mute_led(self, event):
+        """
+        Switch the state of the MUTE LED
+        :param event: 
+        :return: 
+        """
+        logger.debug("[RpiUtils] Event button caught. Switching mute led")
+        # get led status
+        led_mute_kalliope = GPIO.input(self.rpi_settings.pin_led_muted)
+        # switch state
+        if led_mute_kalliope == GPIO.HIGH:
+            logger.debug("[RpiUtils] Switching pin_led_muted to OFF")
+            self.switch_pin_to_off(self.rpi_settings.pin_led_muted)
+            self.callback(muted=False)
+        else:
+            logger.debug("[RpiUtils] Switching pin_led_muted to ON")
+            self.switch_pin_to_on(self.rpi_settings.pin_led_muted)
+            self.callback(muted=True)
+
+    def destroy(self):
+        logger.debug("[RpiUtils] Cleanup GPIO configuration")
+        GPIO.cleanup()
+
+    def init_gpio(self, rpi_settings):
+        # All led are off by default
+        if self.rpi_settings.pin_led_muted:
+            GPIO.setup(rpi_settings.pin_led_muted, GPIO.OUT, initial=GPIO.LOW)
+        if self.rpi_settings.pin_led_started:
+            GPIO.setup(rpi_settings.pin_led_started, GPIO.OUT, initial=GPIO.LOW)
+        if self.rpi_settings.pin_led_listening:
+            GPIO.setup(rpi_settings.pin_led_listening, GPIO.OUT, initial=GPIO.LOW)
+        if self.rpi_settings.pin_led_talking:
+            GPIO.setup(rpi_settings.pin_led_talking, GPIO.OUT, initial=GPIO.LOW)
+
+        # MUTE button
+        GPIO.setup(rpi_settings.pin_mute_button, GPIO.IN, pull_up_down=GPIO.PUD_UP)
+        GPIO.add_event_detect(rpi_settings.pin_mute_button, GPIO.FALLING,
+                              callback=self.switch_kalliope_mute_led,
+                              bouncetime=500)
+
+    @classmethod
+    def switch_pin_to_on(cls, pin_number):
+        logger.debug("[RpiUtils] Switching pin number %s to ON" % pin_number)
+        GPIO.output(pin_number, GPIO.HIGH)
+
+    @classmethod
+    def switch_pin_to_off(cls, pin_number):
+        logger.debug("[RpiUtils] Switching pin number %s to OFF" % pin_number)
+        GPIO.output(pin_number, GPIO.LOW)

+ 10 - 0
kalliope/settings.yml

@@ -162,3 +162,13 @@ default_synapse: "default-synapse"
 #var_files:
 #var_files:
 #  - variables.yml
 #  - variables.yml
 #  - variables2.yml
 #  - variables2.yml
+
+# ---------------------------
+# Raspberry Pi GPIO settings
+# ---------------------------
+#rpi:
+#  pin_mute_button: 24
+#  pin_led_started: 23
+#  pin_led_muted: 17
+#  pin_led_talking: 27
+#  pin_led_listening: 22

+ 10 - 1
kalliope/stt/Utils.py

@@ -4,7 +4,8 @@ from time import sleep
 import logging
 import logging
 import speech_recognition as sr
 import speech_recognition as sr
 
 
-from kalliope import Utils
+from kalliope import Utils, SettingLoader
+from kalliope.core.Utils.RpiUtils import RpiUtils
 
 
 logging.basicConfig()
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
 logger = logging.getLogger("kalliope")
@@ -24,6 +25,10 @@ class SpeechRecognition(Thread):
         self.kill_yourself = False
         self.kill_yourself = False
         self.audio_stream = None
         self.audio_stream = None
 
 
+        # get global configuration
+        sl = SettingLoader()
+        self.settings = sl.settings
+
         if audio_file is None:
         if audio_file is None:
             # audio file not set, we need to capture a sample from the microphone
             # audio file not set, we need to capture a sample from the microphone
             with self.microphone as source:
             with self.microphone as source:
@@ -40,6 +45,10 @@ class SpeechRecognition(Thread):
         """
         """
         if self.audio_stream is None:
         if self.audio_stream is None:
             Utils.print_info("Say something!")
             Utils.print_info("Say something!")
+            # Turn on the listening led if we are on a Raspberry
+            if self.settings.rpi_settings:
+                if self.settings.rpi_settings.pin_led_listening:
+                    RpiUtils.switch_pin_to_on(self.settings.rpi_settings.pin_led_listening)
             self.stop_thread = self.recognizer.listen_in_background(self.microphone, self.callback)
             self.stop_thread = self.recognizer.listen_in_background(self.microphone, self.callback)
             while not self.kill_yourself:
             while not self.kill_yourself:
                 sleep(0.1)
                 sleep(0.1)

+ 1 - 0
setup.py

@@ -83,6 +83,7 @@ setup(
         'GitPython>=2.1.3',
         'GitPython>=2.1.3',
         'packaging>=16.8',
         'packaging>=16.8',
         'transitions>=0.4.3',
         'transitions>=0.4.3',
+        'RPi.GPIO>=0.6.3'
     ],
     ],