+ +
## License diff --git a/Tests/backup_test_order_analyser.py b/Tests/backup_test_order_analyser.py deleted file mode 100644 index 7b59e0cf..00000000 --- a/Tests/backup_test_order_analyser.py +++ /dev/null @@ -1,587 +0,0 @@ -import unittest -import mock - -from kalliope.core.OrderAnalyser import OrderAnalyser -from kalliope.core.Models.Neuron import Neuron -from kalliope.core.Models.Synapse import Synapse -from kalliope.core.Models.Brain import Brain -from kalliope.core.Models.Settings import Settings -from kalliope.core.Models.Order import Order - - -class TestOrderAnalyser(unittest.TestCase): - - """Test case for the OrderAnalyser Class""" - - def setUp(self): - pass - - def test_start(self): - """ - Testing if the matches from the incoming messages and the signals/order sentences. - Scenarii : - - Order matchs a synapse and the synapse has been launched. - - Order does not match but have a default synapse. - - Order does not match and does not have default synapse. - - Provide synapse without any external orders - - Provide synapse with any external orders - """ - # 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'}) - - signal1 = Order(sentence="this is the sentence") - signal2 = Order(sentence="this is the second sentence") - signal3 = Order(sentence="that is part of the third sentence") - - synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1]) - synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2]) - synapse3 = Synapse(name="Synapse3", neurons=[neuron2, neuron4], signals=[signal3]) - - all_synapse_list = [synapse1, - synapse2, - synapse3] - - br = Brain(synapses=all_synapse_list) - - with mock.patch("kalliope.core.OrderAnalyser._start_neuron") as mock_start_neuron_method: - # assert synapses have been launched - order_to_match = "this is the sentence" - oa = OrderAnalyser(order=order_to_match, - brain=br) - expected_result = [synapse1] - - self.assertEquals(oa.start(), - expected_result, - "Fail to run the expected Synapse matching the order") - - calls = [mock.call(neuron1, {}), mock.call(neuron2, {})] - mock_start_neuron_method.assert_has_calls(calls=calls) - mock_start_neuron_method.reset_mock() - - # No order matching Default Synapse to run - order_to_match = "random sentence" - oa = OrderAnalyser(order=order_to_match, - brain=br) - oa.settings = mock.MagicMock(default_synapse="Synapse3") - expected_result = [synapse3] - self.assertEquals(oa.start(), - expected_result, - "Fail to run the default Synapse because no other synapses match the order") - - # No order matching no Default Synapse - order_to_match = "random sentence" - oa = OrderAnalyser(order=order_to_match, - brain=br) - oa.settings = mock.MagicMock() - expected_result = [] - self.assertEquals(oa.start(), - expected_result, - "Fail to no synapse because no synapse matchs and no default defined") - - # Provide synapse to run - order_to_match = "this is the sentence" - oa = OrderAnalyser(order=order_to_match, - brain=br) - expected_result = [synapse1] - synapses_to_run = [synapse1] - - self.assertEquals(oa.start(synapses_to_run=synapses_to_run), - expected_result, - "Fail to run the provided synapse to run") - calls = [mock.call(neuron1, {}), mock.call(neuron2, {})] - mock_start_neuron_method.assert_has_calls(calls=calls) - mock_start_neuron_method.reset_mock() - - # Provide synapse and external orders - order_to_match = "this is an external sentence" - oa = OrderAnalyser(order=order_to_match, - brain=br) - external_orders = "this is an external {{ order }}" - synapses_to_run = [synapse2] - expected_result = [synapse2] - - self.assertEquals(oa.start(synapses_to_run=synapses_to_run, external_order=external_orders), - expected_result, - "Fail to run a provided synapse with external order") - calls = [mock.call(neuron3, {"order":u"sentence"}), mock.call(neuron4, {"order":u"sentence"})] - mock_start_neuron_method.assert_has_calls(calls=calls) - mock_start_neuron_method.reset_mock() - - def test_start_neuron(self): - """ - Testing params association and starting a Neuron - """ - - neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'}) - - with mock.patch("kalliope.core.NeuronLauncher.NeuronLauncher.start_neuron") as mock_start_neuron_method: - # Assert to the neuron is launched - neuron1 = Neuron(name='neurone1', parameters={'var1': 'val1'}) - params = { - 'param1':'parval1' - } - OrderAnalyser._start_neuron(neuron=neuron1,params=params) - mock_start_neuron_method.assert_called_with(neuron1) - mock_start_neuron_method.reset_mock() - - # Assert the params are well passed to the neuron - neuron2 = Neuron(name='neurone2', parameters={'var2': 'val2', 'args': ['arg1', 'arg2']}) - params = { - 'arg1':'argval1', - 'arg2':'argval2' - } - OrderAnalyser._start_neuron(neuron=neuron2, params=params) - neuron2_params = Neuron(name='neurone2', - parameters={'var2': 'val2', - 'args': ['arg1', 'arg2'], - 'arg1':'argval1', - 'arg2':'argval2'} - ) - mock_start_neuron_method.assert_called_with(neuron2_params) - mock_start_neuron_method.reset_mock() - - # Assert the Neuron is not started when missing args - neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3', 'args': ['arg3', 'arg4']}) - params = { - 'arg1': 'argval1', - 'arg2': 'argval2' - } - OrderAnalyser._start_neuron(neuron=neuron3, params=params) - mock_start_neuron_method.assert_not_called() - mock_start_neuron_method.reset_mock() - - # Assert no neuron is launched when waiting for args and none are given - neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4', 'args': ['arg5', 'arg6']}) - params = {} - OrderAnalyser._start_neuron(neuron=neuron4, params=params) - mock_start_neuron_method.assert_not_called() - mock_start_neuron_method.reset_mock() - - def test_spelt_order_match_brain_order_via_table(self): - order_to_test = "this is the order" - sentence_to_test = "this is the order" - - # Success - self.assertTrue(OrderAnalyser.spelt_order_match_brain_order_via_table(order_to_test, sentence_to_test), - "Fail matching order with the expected sentence") - - # Failure - sentence_to_test = "unexpected sentence" - self.assertFalse(OrderAnalyser.spelt_order_match_brain_order_via_table(order_to_test, sentence_to_test), - "Fail to ensure the expected sentence is not matching the order") - - # Upper/lower cases - sentence_to_test = "THIS is THE order" - self.assertTrue(OrderAnalyser.spelt_order_match_brain_order_via_table(order_to_test, sentence_to_test), - "Fail matching Upper/lower cases") - - def test_format_sentences_to_analyse(self): - # First capital in sentence - order_to_test = "this is the order" - sentence_to_test = "This is the order" - expected_result = "this is the order", "this is the order" - self.assertEqual(OrderAnalyser._format_sentences_to_analyse(order_to_analyse=order_to_test, - user_said=sentence_to_test), - expected_result, - "Fails formatting the sentences with first capital in sentence") - - # random uppercase in sentence - order_to_test = "this is the order" - sentence_to_test = "This IS the ordeR" - expected_result = "this is the order", "this is the order" - self.assertEqual(OrderAnalyser._format_sentences_to_analyse(order_to_analyse=order_to_test, - user_said=sentence_to_test), - expected_result, - "Fails formatting the sentences with random in sentence") - - # random uppercase in order - order_to_test = "thiS is THE orDer" - sentence_to_test = "this is the order" - expected_result = "this is the order", "this is the order" - self.assertEqual(OrderAnalyser._format_sentences_to_analyse(order_to_analyse=order_to_test, - user_said=sentence_to_test), - expected_result, - "Fails formatting the sentences with random in order") - - # random uppercase in both order and sentence - order_to_test = "thiS is THE orDer" - sentence_to_test = "THIS is the Order" - expected_result = "this is the order", "this is the order" - self.assertEqual(OrderAnalyser._format_sentences_to_analyse(order_to_analyse=order_to_test, - user_said=sentence_to_test), - expected_result, - "Fails formatting the sentences with random in both order and sentence") - - def test_get_split_order_without_bracket(self): - # Success - order_to_test = "this is the order" - expected_result = ["this", "is", "the", "order"] - self.assertEqual(OrderAnalyser._get_split_order_without_bracket(order_to_test), expected_result, - "No brackets Fails to return the expected list") - - order_to_test = "this is the {{ order }}" - expected_result = ["this", "is", "the"] - self.assertEqual(OrderAnalyser._get_split_order_without_bracket(order_to_test), expected_result, - "With spaced brackets Fails to return the expected list") - - order_to_test = "this is the {{order }}" # left bracket without space - expected_result = ["this", "is", "the"] - self.assertEqual(OrderAnalyser._get_split_order_without_bracket(order_to_test), expected_result, - "Left brackets Fails to return the expected list") - - order_to_test = "this is the {{ order}}" # right bracket without space - expected_result = ["this", "is", "the"] - self.assertEqual(OrderAnalyser._get_split_order_without_bracket(order_to_test), expected_result, - "Right brackets Fails to return the expected list") - - order_to_test = "this is the {{order}}" # bracket without space - expected_result = ["this", "is", "the"] - self.assertEqual(OrderAnalyser._get_split_order_without_bracket(order_to_test), expected_result, - "No space brackets Fails to return the expected list") - - def test_associate_order_params_to_values(self): - ## - # Testing the brackets position behaviour - ## - - # Success - order_brain = "This is the {{ variable }}" - order_user = "This is the value" - expected_result = {'variable': 'value'} - self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result, - "Fail to match the order_brain {{ variable }} to the 'value'") - - # Success - order_brain = "This is the {{variable }}" - order_user = "This is the value" - expected_result = {'variable': 'value'} - self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result, - "Fail to match the order_brain {{variable }} to the 'value'") - - # Success - order_brain = "This is the {{ variable}}" - order_user = "This is the value" - expected_result = {'variable': 'value'} - self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result, - "Fail to match the order_brain {{ variable}} to the 'value'") - - # Success - order_brain = "This is the {{variable}}" - order_user = "This is the value" - expected_result = {'variable': 'value'} - self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result, - "Fail to match the order_brain {{variable}} to the 'value'") - - # Fail - order_brain = "This is the {variable}" - order_user = "This is the value" - expected_result = {'variable': 'value'} - self.assertNotEquals(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result, - "Should not match the order_brain {variable} to the 'value'") - - # Fail - order_brain = "This is the { variable}}" - order_user = "This is the value" - expected_result = {'variable': 'value'} - self.assertNotEquals(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result, - "Should not match the order_brain { variable}} to the 'value'") - - ## - # Testing the brackets position in the sentence - ## - - # Success - order_brain = "{{ variable }} This is the" - order_user = "value This is the" - expected_result = {'variable': 'value'} - self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result, - "Fail to match the order_brain {{ variable }} in first position " - "ins the sentence to the 'value'") - - # Success - order_brain = "This is {{ variable }} the" - order_user = " This is value the" - expected_result = {'variable': 'value'} - self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result, - "Fail to match the order_brain {{ variable }} in middle position ins " - "the sentence to the 'value'") - - ## - # Testing multi variables - ## - - # Success - order_brain = "This is {{ variable }} the {{ variable2 }}" - order_user = "This is value the value2" - expected_result = {'variable': 'value', - 'variable2': 'value2'} - self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result, - "Fail to match the order_brain multi variable to the multi values") - - ## - # Testing multi words in variable - ## - - # Success - order_brain = "This is the {{ variable }}" - order_user = "This is the value with multiple words" - expected_result = {'variable': 'value with multiple words'} - self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result, - "Fail to match the order_brain {{ variable }} to the 'value with multiple words'") - - # Success - order_brain = "This is the {{ variable }} and {{ variable2 }}" - order_user = "This is the value with multiple words and second value multiple" - expected_result = {'variable': 'value with multiple words', - 'variable2': 'second value multiple'} - self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result, - "Fail to match the order_brain multiple variables with multiple words as values'") - - ## - # Specific Behaviour - ## - - # Upper/Lower case - order_brain = "This Is The {{ variable }}" - order_user = "ThiS is tHe VAlue" - expected_result = {'variable': 'VAlue'} - self.assertEqual(OrderAnalyser._associate_order_params_to_values(order_user, order_brain), expected_result, - "Fail to match the order_brain when using Upper/Lower cases") - - def test_get_matching_synapse_list(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'}) - - signal1 = Order(sentence="this is the sentence") - signal2 = Order(sentence="this is the second sentence") - signal3 = Order(sentence="that is part of the third sentence") - - synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1]) - synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2]) - synapse3 = Synapse(name="Synapse3", neurons=[neuron2, neuron4], signals=[signal3]) - - order_to_match = "this is the sentence" - all_synapse_list = [synapse1, - synapse2, - synapse3] - - - - # Success - expected_result = synapse1 - oa_tuple_list = OrderAnalyser._get_matching_synapse_list(all_synapses_list=all_synapse_list, - order_to_match=order_to_match) - self.assertEquals(oa_tuple_list[0].synapse, - expected_result, - "Fail matching 'the expected synapse' from the complete synapse list and the order") - - # Multiple Matching synapses - signal2 = Order(sentence="this is the sentence") - - synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2]) - order_to_match = "this is the sentence" - - all_synapse_list = [synapse1, - synapse2, - synapse3] - - expected_result = [synapse1, - synapse2] - oa_tuple_list = OrderAnalyser._get_matching_synapse_list(all_synapses_list=all_synapse_list, - order_to_match=order_to_match) - self.assertEquals(oa_tuple_list[0].synapse, - expected_result[0], - "Fail 'Multiple Matching synapses' from the complete synapse list and the order (first element)") - self.assertEquals(oa_tuple_list[1].synapse, - expected_result[1], - "Fail 'Multiple Matching synapses' from the complete synapse list and the order (second element)") - - # matching no synapses - order_to_match = "this is not the correct word" - - all_synapse_list = [synapse1, - synapse2, - synapse3] - - expected_result = [] - - self.assertEquals(OrderAnalyser._get_matching_synapse_list(all_synapses_list=all_synapse_list, - order_to_match=order_to_match), - expected_result, - "Fail matching 'no synapses' from the complete synapse list and the order") - - # matching synapse with all key worlds - # /!\ Some words in the order are matching all words in synapses signals ! - order_to_match = "this is not the correct sentence" - all_synapse_list = [synapse1, - synapse2, - synapse3] - - expected_result = [synapse1, - synapse2] - - oa_tuple_list = OrderAnalyser._get_matching_synapse_list(all_synapses_list=all_synapse_list, - order_to_match=order_to_match) - - self.assertEquals(oa_tuple_list[0].synapse, - expected_result[0], - "Fail matching 'synapse with all key worlds' from the complete synapse list and the order (first element)") - self.assertEquals(oa_tuple_list[1].synapse, - expected_result[1], - "Fail matching 'synapse with all key worlds' from the complete synapse list and the order (second element)") - - def test_get_params_from_order(self): - - string_order = "this is the {{ sentence }}" - order_to_check = "this is the value" - expected_result = {'sentence': 'value'} - - self.assertEquals(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check), - expected_result, - "Fail to retrieve 'the params' of the string_order from the order") - - # Multiple match - string_order = "this is the {{ sentence }}" - - order_to_check = "this is the value with multiple words" - expected_result = {'sentence': 'value with multiple words'} - - self.assertEqual(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check), - expected_result, - "Fail to retrieve the 'multiple words params' of the string_order from the order") - - # Multiple params - string_order = "this is the {{ sentence }} with multiple {{ params }}" - - order_to_check = "this is the value with multiple words" - expected_result = {'sentence': 'value', - 'params':'words'} - - self.assertEqual(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check), - expected_result, - "Fail to retrieve the 'multiple params' of the string_order from the order") - - # Multiple params with multiple words - string_order = "this is the {{ sentence }} with multiple {{ params }}" - - order_to_check = "this is the multiple values with multiple values as words" - expected_result = {'sentence': 'multiple values', - 'params': 'values as words'} - - self.assertEqual(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check), - expected_result, - "Fail to retrieve the 'multiple params with multiple words' of the string_order from the order") - - # params at the begining of the sentence - string_order = "{{ sentence }} this is the sentence" - - order_to_check = "hello world this is the multiple values with multiple values as words" - expected_result = {'sentence': 'hello world'} - - self.assertEqual(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check), - expected_result, - "Fail to retrieve the 'params at the begining of the sentence' of the string_order from the order") - - # all of the sentence is a variable - string_order = "{{ sentence }}" - - order_to_check = "this is the all sentence is a variable" - expected_result = {'sentence': 'this is the all sentence is a variable'} - - self.assertEqual(OrderAnalyser._get_params_from_order(string_order=string_order, order_to_check=order_to_check), - expected_result, - "Fail to retrieve the 'all of the sentence is a variable' of the string_order from the order") - - def test_get_default_synapse_from_sysnapses_list(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'}) - - signal1 = Order(sentence="this is the sentence") - signal2 = Order(sentence="this is the second sentence") - signal3 = Order(sentence="that is part of the third sentence") - - synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1]) - synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2]) - synapse3 = Synapse(name="Synapse3", neurons=[neuron2, neuron4], signals=[signal3]) - - default_synapse_name = "Synapse2" - all_synapse_list = [synapse1, - synapse2, - synapse3] - expected_result = synapse2 - - # Assert equals - self.assertEquals(OrderAnalyser._get_default_synapse_from_sysnapses_list(all_synapses_list=all_synapse_list, - default_synapse_name=default_synapse_name), - expected_result, - "Fail to match the expected default Synapse") - - def test_find_synapse_to_run(self): - """ - Test to find the good synapse to run - Scenarii: - - 1/ Find the synapse - - 2/ No synpase found, no default synapse - - 3/ No synapse found, run the default synapse - """ - # 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'}) - - signal1 = Order(sentence="this is the sentence") - signal2 = Order(sentence="this is the second sentence") - signal3 = Order(sentence="that is part of the third sentence") - - synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1]) - synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2]) - synapse3 = Synapse(name="Synapse3", neurons=[neuron2, neuron4], signals=[signal3]) - - all_synapse_list = [synapse1, - synapse2, - synapse3] - - br = Brain(synapses=all_synapse_list) - st = Settings() - # 1/ Find synapse - order = "this is the sentence" - expected_result = synapse1 - oa_tuple_list = OrderAnalyser._find_synapse_to_run(brain=br,settings=st, order=order) - self.assertEquals(oa_tuple_list[0].synapse, - expected_result, - "Fail to run the proper synapse matching the order") - - expected_result = signal1.sentence - self.assertEquals(oa_tuple_list[0].order, - expected_result, - "Fail to run the proper synapse matching the order") - - # 2/ No Default synapse - order = "No default synapse" - expected_result = [] - self.assertEquals(OrderAnalyser._find_synapse_to_run(brain=br,settings=st, order=order), - expected_result, - "Fail to run no synapse, when no default is defined") - - # 3/ Default synapse - st = Settings(default_synapse="Synapse2") - order = "default synapse" - expected_result = synapse2 - oa_tuple_list = OrderAnalyser._find_synapse_to_run(brain=br, settings=st, order=order) - self.assertEquals(oa_tuple_list[0].synapse, - expected_result, - "Fail to run the default synapse") - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_brain_loader.py b/Tests/test_brain_loader.py index f2b80edc..11cb89bb 100644 --- a/Tests/test_brain_loader.py +++ b/Tests/test_brain_loader.py @@ -2,13 +2,11 @@ import os import unittest -from kalliope.core.Models import Singleton +from kalliope.core.Models import Singleton, Signal from kalliope.core.ConfigurationManager import BrainLoader -from kalliope.core.Models import Event from kalliope.core.Models import Neuron from kalliope.core.Models import Synapse -from kalliope.core.Models import Order from kalliope.core.Models.Brain import Brain from kalliope.core.Models.Settings import Settings @@ -57,10 +55,10 @@ def test_get_brain(self): neuron = Neuron(name='say', parameters={'message': ['test message']}) neuron2 = Neuron(name='sleep', parameters={'seconds': 60}) - signal1 = Order(sentence="test_order") - signal2 = Order(sentence="test_order_2") - signal3 = Order(sentence="test_order_3") - signal4 = Order(sentence="order_for_int") + signal1 = Signal(name="order", parameters="test_order") + signal2 = Signal(name="order", parameters="test_order_2") + signal3 = Signal(name="order", parameters="test_order_3") + signal4 = Signal(name="order", parameters="order_for_int") synapse1 = Synapse(name="test", neurons=[neuron], signals=[signal1]) synapse2 = Synapse(name="test2", neurons=[neuron], signals=[signal2]) @@ -127,28 +125,13 @@ def test_get_neurons(self): def test_get_signals(self): signals = [{'order': 'test_order'}] - signal = Order(sentence='test_order') + signal = Signal(name="order", parameters="test_order") bl = BrainLoader(file_path=self.brain_to_test) signals_from_brain_loader = bl._get_signals(signals) self.assertEqual([signal], signals_from_brain_loader) - def test_get_event_or_order_from_dict(self): - - order_object = Order(sentence="test_order") - event_object = Event(hour="7") - - dict_order = {'order': 'test_order'} - dict_event = {'event': {'hour': '7'}} - - bl = BrainLoader(file_path=self.brain_to_test) - order_from_bl = bl._get_event_or_order_from_dict(dict_order) - event_from_bl = bl._get_event_or_order_from_dict(dict_event) - - self.assertEqual(order_from_bl, order_object) - self.assertEqual(event_from_bl, event_object) - def test_singleton(self): bl1 = BrainLoader(file_path=self.brain_to_test) bl2 = BrainLoader(file_path=self.brain_to_test) @@ -184,7 +167,7 @@ def test_replace_global_variables(self): } self.assertEqual(BrainLoader._replace_global_variables(parameter=parameters, - settings=st), + settings=st), expected_parameters, "Fail to assign a single global variable to parameters") @@ -203,7 +186,7 @@ def test_replace_global_variables(self): } self.assertEqual(BrainLoader._replace_global_variables(parameter=parameters, - settings=st), + settings=st), expected_parameters, "Fail to assign a global variable with string after to parameters") @@ -222,7 +205,7 @@ def test_replace_global_variables(self): } self.assertEqual(BrainLoader._replace_global_variables(parameter=parameters, - settings=st), + settings=st), expected_parameters, "Fail to assign global variable with int after to parameters") @@ -241,7 +224,7 @@ def test_replace_global_variables(self): } self.assertEqual(BrainLoader._replace_global_variables(parameter=parameters, - settings=st), + settings=st), expected_parameters, "Fail to assign multiple global variables to parameters") @@ -260,7 +243,7 @@ def test_replace_global_variables(self): } self.assertEqual(BrainLoader._replace_global_variables(parameter=parameters, - settings=st), + settings=st), expected_parameters, "Fail to assign a single global when parameter value is a list to neuron") @@ -280,7 +263,7 @@ def test_replace_global_variables(self): } self.assertEqual(BrainLoader._replace_global_variables(parameter=parameters, - settings=st), + settings=st), expected_parameters, "Fail to assign a single global when parameter value is a list to neuron") @@ -300,7 +283,7 @@ def test_get_global_variable(self): expected_result = "i am kalliope" self.assertEqual(BrainLoader._get_global_variable(sentence=sentence, - settings=st), + settings=st), expected_result) # test with accent @@ -308,7 +291,7 @@ def test_get_global_variable(self): expected_result = u"i am kalliopé" self.assertEqual(BrainLoader._get_global_variable(sentence=sentence, - settings=st), + settings=st), expected_result) # test with int @@ -316,7 +299,7 @@ def test_get_global_variable(self): expected_result = "i am 1" self.assertEqual(BrainLoader._get_global_variable(sentence=sentence, - settings=st), + settings=st), expected_result) diff --git a/Tests/test_configuration_checker.py b/Tests/test_configuration_checker.py index 79f3bd21..2a3d47b3 100644 --- a/Tests/test_configuration_checker.py +++ b/Tests/test_configuration_checker.py @@ -1,7 +1,7 @@ import unittest -from kalliope.core.ConfigurationManager.ConfigurationChecker import ConfigurationChecker, NoSynapeName, NoSynapeNeurons, \ - NoSynapeSignals, NoValidSignal, NoEventPeriod, NoValidOrder, MultipleSameSynapseName +from kalliope.core.ConfigurationManager.ConfigurationChecker import ConfigurationChecker, NoSynapeName, \ + NoSynapeNeurons, NoSynapeSignals, NoValidSignal, MultipleSameSynapseName from kalliope.core.Models import Synapse from kalliope.core.Utils.Utils import ModuleNotFoundError @@ -57,48 +57,14 @@ def test_check_neuron_dict(self): ConfigurationChecker.check_neuron_dict(invalid_neuron) def test_check_signal_dict(self): - valid_signal_with_order = {'order': 'test_order'} - valid_signal_with_event = {'event': '0 * * * *'} - invalid_signal = {'invalid_option': 'test_order'} + valid_signal = {'event': {'parameter_1': ['value1']}} + invalid_signal = {'non_existing_signal_name': {'parameter_2': ['value2']}} - self.assertTrue(ConfigurationChecker.check_signal_dict(valid_signal_with_order)) - self.assertTrue(ConfigurationChecker.check_signal_dict(valid_signal_with_event)) + self.assertTrue(ConfigurationChecker.check_signal_dict(valid_signal)) - with self.assertRaises(NoValidSignal): + with self.assertRaises(ModuleNotFoundError): ConfigurationChecker.check_signal_dict(invalid_signal) - def test_check_event_dict(self): - valid_event = { - "hour": "18", - "minute": "16" - } - invalid_event = None - invalid_event2 = "" - invalid_event3 = { - "notexisting": "12" - } - - self.assertTrue(ConfigurationChecker.check_event_dict(valid_event)) - - with self.assertRaises(NoEventPeriod): - ConfigurationChecker.check_event_dict(invalid_event) - with self.assertRaises(NoEventPeriod): - ConfigurationChecker.check_event_dict(invalid_event2) - with self.assertRaises(NoEventPeriod): - ConfigurationChecker.check_event_dict(invalid_event3) - - def test_check_order_dict(self): - valid_order = 'test_order' - invalid_order = '' - invalid_order2 = None - - self.assertTrue(ConfigurationChecker.check_order_dict(valid_order)) - - with self.assertRaises(NoValidOrder): - ConfigurationChecker.check_order_dict(invalid_order) - with self.assertRaises(NoValidOrder): - ConfigurationChecker.check_order_dict(invalid_order2) - def test_check_synapes(self): synapse_1 = Synapse(name="test") synapse_2 = Synapse(name="test2") diff --git a/Tests/test_cortex.py b/Tests/test_cortex.py new file mode 100644 index 00000000..9f58b563 --- /dev/null +++ b/Tests/test_cortex.py @@ -0,0 +1,163 @@ +import unittest + +from kalliope.core.Cortex import Cortex + + +class TestCortex(unittest.TestCase): + + def setUp(self): + # cleanup the cortex memory + Cortex.memory = dict() + Cortex.temp = dict() + + def test_get_memory(self): + test_memory = { + "key1": "value1", + "key2": "value2" + } + + Cortex.memory = test_memory + self.assertDictEqual(test_memory, Cortex.get_memory()) + + def test_save(self): + key_to_save = "key1" + value_to_save = "value1" + + expected_memory = { + "key1": "value1" + } + + Cortex.save(key=key_to_save, value=value_to_save) + self.assertDictEqual(expected_memory, Cortex.memory) + + def test_get_from_key(self): + test_memory = { + "key1": "value1", + "key2": "value2" + } + + Cortex.memory = test_memory + expected_value = "value2" + self.assertEqual(expected_value, Cortex.get_from_key("key2")) + + def test_add_parameters_from_order(self): + + order_parameters = { + "key1": "value1", + "key2": "value2" + } + + expected_temp_dict = { + "key1": "value1", + "key2": "value2" + } + + Cortex.add_parameters_from_order(order_parameters) + self.assertDictEqual(Cortex.temp, expected_temp_dict) + + def test_clean_parameter_from_order(self): + Cortex.temp = { + "key1": "value1", + "key2": "value2" + } + + Cortex.clean_parameter_from_order() + expected_temp_dict = dict() + self.assertDictEqual(expected_temp_dict, Cortex.memory) + + def test_save_neuron_parameter_in_memory(self): + + # test with a list of parameter with bracket + + neuron1_parameters = { + "key1": "value1", + "key2": "value2" + } + + dict_val_to_save = {"my_key_in_memory": "{{key1}}"} + + expected_dict = {"my_key_in_memory": "value1"} + + Cortex.save_neuron_parameter_in_memory(kalliope_memory_dict=dict_val_to_save, + neuron_parameters=neuron1_parameters) + + self.assertDictEqual(expected_dict, Cortex.memory) + + # test with a list of parameter with brackets and string + self.setUp() # clean + neuron1_parameters = { + "key1": "value1", + "key2": "value2" + } + + dict_val_to_save = {"my_key_in_memory": "string {{key1}}"} + + expected_dict = {"my_key_in_memory": "string value1"} + + Cortex.save_neuron_parameter_in_memory(kalliope_memory_dict=dict_val_to_save, + neuron_parameters=neuron1_parameters) + + self.assertDictEqual(expected_dict, Cortex.memory) + + # test with a list of parameter with only a string. Neuron parameters are not used + self.setUp() # clean + neuron1_parameters = { + "key1": "value1", + "key2": "value2" + } + + dict_val_to_save = {"my_key_in_memory": "string"} + + expected_dict = {"my_key_in_memory": "string"} + + Cortex.save_neuron_parameter_in_memory(kalliope_memory_dict=dict_val_to_save, + neuron_parameters=neuron1_parameters) + + self.assertDictEqual(expected_dict, Cortex.memory) + + # test with an empty list of parameter to save (no kalliope_memory set) + self.setUp() # clean + + neuron1_parameters = { + "key1": "value1", + "key2": "value2" + } + + dict_val_to_save = None + + Cortex.save_neuron_parameter_in_memory(kalliope_memory_dict=dict_val_to_save, + neuron_parameters=neuron1_parameters) + + self.assertDictEqual(dict(), Cortex.memory) + + def test_save_parameter_from_order_in_memory(self): + # Test with a value that exist in the temp memory + order_parameters = { + "key1": "value1", + "key2": "value2" + } + + Cortex.temp = order_parameters + + dict_val_to_save = {"my_key_in_memory": "{{key1}}"} + + expected_dict = {"my_key_in_memory": "value1"} + + Cortex.save_parameter_from_order_in_memory(dict_val_to_save) + + self.assertDictEqual(expected_dict, Cortex.memory) + + # test with a value that does not exsit + order_parameters = { + "key1": "value1", + "key2": "value2" + } + + Cortex.temp = order_parameters + dict_val_to_save = {"my_key_in_memory": "{{key3}}"} + + self.assertFalse(Cortex.save_parameter_from_order_in_memory(dict_val_to_save)) + + +if __name__ == '__main__': + unittest.main() diff --git a/Tests/test_init.py b/Tests/test_init.py index 137699c1..10ba710c 100644 --- a/Tests/test_init.py +++ b/Tests/test_init.py @@ -43,10 +43,12 @@ def test_configure_logging(self): def test_main(self): # test start kalliope sys.argv = ['kalliope.py', 'start'] - with mock.patch('kalliope.core.MainController.__init__') as mock_maincontroller: - mock_maincontroller.return_value = None - main() - mock_maincontroller.assert_called() + with mock.patch('kalliope.start_rest_api') as mock_rest_api: + with mock.patch('kalliope.start_kalliope') as mock_start_kalliope: + mock_rest_api.return_value = None + main() + mock_rest_api.assert_called() + mock_start_kalliope.assert_called() # test start gui sys.argv = ['kalliope.py', 'gui'] diff --git a/Tests/test_lifo_buffer.py b/Tests/test_lifo_buffer.py index 5fd3ef80..957064b4 100644 --- a/Tests/test_lifo_buffer.py +++ b/Tests/test_lifo_buffer.py @@ -82,7 +82,7 @@ def test_execute(self): 'neuron_module_list': [ { 'neuron_name': 'Say', - 'generated_message': 'question in synapse 1' + 'generated_message': 'question in synapse 1' }, { 'neuron_name': 'Neurotransmitter', @@ -99,7 +99,7 @@ def test_execute(self): 'generated_message': 'enter synapse 2' } ], - 'synapse_name': 'synapse2' + 'synapse_name': 'synapse2' } ], 'user_order': None @@ -139,7 +139,7 @@ def test_execute(self): }, { 'matched_order': 'enter in synapse 1', - 'neuron_module_list':[ + 'neuron_module_list': [ { 'neuron_name': 'Say', 'generated_message': 'question in synapse 1' @@ -345,8 +345,8 @@ def test_process_neuron_list(self): self.lifo_buffer.set_api_call(True) self.lifo_buffer.set_answer("synapse 6 answer") with mock.patch("kalliope.core.TTS.TTSModule.generate_and_play"): - with self.assertRaises(SynapseListAddedToLIFO): - self.lifo_buffer._process_neuron_list(matched_synapse=matched_synapse) + self.assertRaises(SynapseListAddedToLIFO, + self.lifo_buffer._process_neuron_list(matched_synapse=matched_synapse)) if __name__ == '__main__': @@ -355,4 +355,4 @@ def test_process_neuron_list(self): # suite = unittest.TestSuite() # suite.addTest(TestLIFOBuffer("test_process_neuron_list")) # runner = unittest.TextTestRunner() - # runner.run(suite) \ No newline at end of file + # runner.run(suite) diff --git a/Tests/test_models.py b/Tests/test_models.py index 7fc70d72..29af9797 100644 --- a/Tests/test_models.py +++ b/Tests/test_models.py @@ -3,6 +3,8 @@ import mock from kalliope.core.Models.Player import Player +from kalliope.core.Models.Signal import Signal +from kalliope.core.Models.RecognitionOptions import RecognitionOptions from kalliope.core.Models.Tts import Tts from kalliope.core.Models.Trigger import Trigger @@ -16,7 +18,7 @@ from kalliope.core import LIFOBuffer from kalliope.core.Models.Settings import Settings -from kalliope.core.Models import Neuron, Order, Synapse, Brain, Event, Resources, Singleton +from kalliope.core.Models import Neuron, Synapse, Brain, Resources, Singleton from kalliope.core.Models.APIResponse import APIResponse from kalliope.core.Models.MatchedSynapse import MatchedSynapse @@ -34,9 +36,9 @@ def setUp(self): neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'}) neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'}) - signal1 = Order(sentence="this is the sentence") - signal2 = Order(sentence="this is the second sentence") - signal3 = Order(sentence="that is part of the third sentence") + signal1 = Signal(name="order", parameters="this is the sentence") + signal2 = Signal(name="order", parameters="this is the second sentence") + signal3 = Signal(name="order", parameters="that is part of the third sentence") self.synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1]) self.synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2]) @@ -119,35 +121,6 @@ def test_Dna(self): self.assertTrue(dna1.__eq__(dna3)) self.assertFalse(dna1.__eq__(dna2)) - def test_Event(self): - event1 = Event(year=2017, month=12, day=31, week=53, day_of_week=2, - hour=8, minute=30, second=0) - - event2 = Event(year=2018, month=11, day=30, week=25, day_of_week=4, - hour=9, minute=40, second=0) - - # same as the event1 - event3 = Event(year=2017, month=12, day=31, week=53, day_of_week=2, - hour=8, minute=30, second=0) - - expected_result_serialize = { - 'event': { - 'week': 53, - 'second': 0, - 'minute': 30, - 'hour': 8, - 'year': 2017, - 'day': 31, - 'day_of_week': 2, - 'month': 12 - } - } - - self.assertDictEqual(expected_result_serialize, event1.serialize()) - - self.assertTrue(event1.__eq__(event3)) - self.assertFalse(event1.__eq__(event2)) - def test_MatchedSynapse(self): user_order = "user order" matched_synapse1 = MatchedSynapse(matched_synapse=self.synapse1, matched_order=user_order) @@ -215,20 +188,6 @@ def test_Neuron(self): self.assertDictEqual(ast.literal_eval(neuron.__str__()), ast.literal_eval(expected_result_str)) - def test_Order(self): - order1 = Order(sentence="this is an order") - order2 = Order(sentence="this is an other order") - order3 = Order(sentence="this is an order") - - expected_result_serialize = {'order': 'this is an order'} - expected_result_str = "{'order': 'this is an order'}" - - self.assertEqual(expected_result_serialize, order1.serialize()) - self.assertEqual(expected_result_str, order1.__str__()) - - self.assertTrue(order1.__eq__(order3)) - self.assertFalse(order1.__eq__(order2)) - def test_Resources(self): resource1 = Resources(neuron_folder="/path/neuron", stt_folder="/path/stt", tts_folder="/path/tts", trigger_folder="/path/trigger") @@ -243,7 +202,8 @@ def test_Resources(self): 'tts_folder': '/path/tts', 'neuron_folder': '/path/neuron', 'stt_folder': '/path/stt', - 'trigger_folder': '/path/trigger' + 'trigger_folder': '/path/trigger', + 'signal_folder': None } self.assertDictEqual(expected_result_serialize, resource1.serialize()) @@ -284,6 +244,8 @@ def test_Settings(self): active=True, port=5000, allowed_cors_origin="*") + recognition_options = RecognitionOptions() + setting1 = Settings(default_tts_name="pico2wav", default_stt_name="google", default_trigger_name="swoyboy", @@ -301,7 +263,8 @@ def test_Settings(self): cache_path="/tmp/kalliope", default_synapse="default_synapse", resources=None, - variables={"key1": "val1"}) + variables={"key1": "val1"}, + recognition_options=recognition_options) setting1.kalliope_version = "0.4.5" setting2 = Settings(default_tts_name="accapela", @@ -320,7 +283,8 @@ def test_Settings(self): cache_path="/tmp/kalliope_tmp", default_synapse="my_default_synapse", resources=None, - variables={"key1": "val1"}) + variables={"key1": "val1"}, + recognition_options=recognition_options) setting2.kalliope_version = "0.4.5" setting3 = Settings(default_tts_name="pico2wav", @@ -340,7 +304,8 @@ def test_Settings(self): cache_path="/tmp/kalliope", default_synapse="default_synapse", resources=None, - variables={"key1": "val1"}) + variables={"key1": "val1"}, + recognition_options=recognition_options) setting3.kalliope_version = "0.4.5" expected_result_serialize = { @@ -372,7 +337,8 @@ def test_Settings(self): 'resources': None, 'triggers': ['snowboy'], 'rpi_settings': None, - 'players': ['mplayer'] + 'players': ['mplayer'], + 'recognition_options': {'energy_threshold': 4000, 'adjust_for_ambient_noise_second': 0} } self.assertDictEqual(expected_result_serialize, setting1.serialize()) @@ -398,8 +364,8 @@ def test_Synapse(self): neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'}) neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'}) - signal1 = Order(sentence="this is the sentence") - signal2 = Order(sentence="this is the second sentence") + signal1 = Signal(name="order", parameters="this is the sentence") + 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]) @@ -408,13 +374,14 @@ def test_Synapse(self): expected_result_serialize = { 'signals': [ { - 'order': 'this is the sentence' + 'name': 'order', + 'parameters': 'this is the sentence' } ], 'neurons': [ { 'name': 'neurone1', - 'parameters': { + 'parameters': { 'var1': 'val1' } }, @@ -471,3 +438,10 @@ def test_Tts(self): self.assertFalse(tts1.__eq__(tts2)) +if __name__ == '__main__': + unittest.main() + + # suite = unittest.TestSuite() + # suite.addTest(TestLIFOBuffer("test_process_neuron_list")) + # runner = unittest.TextTestRunner() + # runner.run(suite) diff --git a/Tests/test_neuron_launcher.py b/Tests/test_neuron_launcher.py index 48dd769e..434712d1 100644 --- a/Tests/test_neuron_launcher.py +++ b/Tests/test_neuron_launcher.py @@ -2,6 +2,7 @@ import unittest import mock +from kalliope.core.Models import Singleton from kalliope.core.Models.Resources import Resources from kalliope.core.NeuronLauncher import NeuronLauncher, NeuronParameterNotAvailable @@ -16,7 +17,11 @@ class TestNeuronLauncher(unittest.TestCase): """ def setUp(self): - pass + # clean settings + Singleton._instances = dict() + + def tearDown(self): + Singleton._instances = dict() #### # Neurons Launcher @@ -181,47 +186,87 @@ def test_replace_brackets_by_loaded_parameter(self): self.assertEqual(expected_result, NeuronLauncher._replace_brackets_by_loaded_parameter(neuron_parameters, loaded_parameters)) + # replacing with variable + sl = SettingLoader() + sl.settings.variables = { + "replaced": { + "name": u'replaced successfully' + } + } + + neuron_parameters = { + "param1": "this is a value {{ replaced['name'] }}" + } + + loaded_parameters = { + "name": "replaced successfully" + } + + expected_result = { + "param1": "this is a value replaced successfully" + } + + self.assertEqual(expected_result, NeuronLauncher._replace_brackets_by_loaded_parameter(neuron_parameters, + loaded_parameters)) + + # the parameter is a reserved key. for example from_answer_link from the neurotransmitter + list_reserved_keys = ["say_template", "file_template", "kalliope_memory", "from_answer_link"] + + for reserved_key in list_reserved_keys: + neuron_parameters = { + reserved_key: "this is a value with {{ 'brackets '}}" + } + + loaded_parameters = dict() + + expected_result = { + reserved_key: "this is a value with {{ 'brackets '}}" + } + + self.assertEqual(expected_result, NeuronLauncher._replace_brackets_by_loaded_parameter(neuron_parameters, + loaded_parameters)) + def test_parameters_are_available_in_loaded_parameters(self): # the parameter in bracket is available in the dict string_parameters = "this is a {{ parameter1 }}" loaded_parameters = {"parameter1": "value"} self.assertTrue(NeuronLauncher._neuron_parameters_are_available_in_loaded_parameters(string_parameters, - loaded_parameters)) + loaded_parameters)) # the parameter in bracket is NOT available in the dict string_parameters = "this is a {{ parameter1 }}" loaded_parameters = {"parameter2": "value"} self.assertFalse(NeuronLauncher._neuron_parameters_are_available_in_loaded_parameters(string_parameters, - loaded_parameters)) + loaded_parameters)) # the string_parameters doesn't contains bracket in bracket is available in the dict string_parameters = "this is a {{ parameter1 }}" loaded_parameters = {"parameter1": "value"} self.assertTrue(NeuronLauncher._neuron_parameters_are_available_in_loaded_parameters(string_parameters, - loaded_parameters)) + loaded_parameters)) # the string_parameters contains 2 parameters available in the dict string_parameters = "this is a {{ parameter1 }} and this is {{ parameter2 }}" loaded_parameters = {"parameter1": "value", "parameter2": "other value"} self.assertTrue(NeuronLauncher._neuron_parameters_are_available_in_loaded_parameters(string_parameters, - loaded_parameters)) + loaded_parameters)) # the string_parameters contains 2 parameters and one of them is not available in the dict string_parameters = "this is a {{ parameter1 }} and this is {{ parameter2 }}" loaded_parameters = {"parameter1": "value", "parameter3": "other value"} self.assertFalse(NeuronLauncher._neuron_parameters_are_available_in_loaded_parameters(string_parameters, - loaded_parameters)) + loaded_parameters)) if __name__ == '__main__': unittest.main() # suite = unittest.TestSuite() - # suite.addTest(TestNeuronLauncher("test_start_neuron")) + # suite.addTest(TestNeuronLauncher("test_replace_brackets_by_loaded_parameter")) # runner = unittest.TextTestRunner() # runner.run(suite) diff --git a/Tests/test_neuron_parameter_loader.py b/Tests/test_neuron_parameter_loader.py index bc060332..73c83fd1 100644 --- a/Tests/test_neuron_parameter_loader.py +++ b/Tests/test_neuron_parameter_loader.py @@ -171,5 +171,29 @@ def test_associate_order_params_to_values(self): self.assertEqual(NeuronParameterLoader._associate_order_params_to_values(order_user, order_brain), expected_result) + # Upper/Lower case between multiple variables + order_brain = "This Is The {{ variable }} And The {{ variable2 }}" + order_user = "ThiS is tHe VAlue aND tHE vAlUe2" + expected_result = {'variable': 'VAlue', + 'variable2':'vAlUe2'} + self.assertEqual(NeuronParameterLoader._associate_order_params_to_values(order_user, order_brain), + expected_result) + + # Upper/Lower case between multiple variables and at the End + order_brain = "This Is The {{ variable }} And The {{ variable2 }} And Again" + order_user = "ThiS is tHe VAlue aND tHE vAlUe2 and aGAIN" + expected_result = {'variable': 'VAlue', + 'variable2': 'vAlUe2'} + self.assertEqual(NeuronParameterLoader._associate_order_params_to_values(order_user, order_brain), + expected_result) + + # integers variables + order_brain = "This Is The {{ variable }} And The {{ variable2 }}" + order_user = "ThiS is tHe 1 aND tHE 2" + expected_result = {'variable': '1', + 'variable2': '2'} + self.assertEqual(NeuronParameterLoader._associate_order_params_to_values(order_user, order_brain), + expected_result) + if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/Tests/test_order_analyser.py b/Tests/test_order_analyser.py index 80edd412..3ffe19b7 100644 --- a/Tests/test_order_analyser.py +++ b/Tests/test_order_analyser.py @@ -3,9 +3,9 @@ from kalliope.core.Models import Brain from kalliope.core.Models import Neuron -from kalliope.core.Models import Order from kalliope.core.Models import Synapse from kalliope.core.Models.MatchedSynapse import MatchedSynapse +from kalliope.core.Models.Signal import Signal from kalliope.core.OrderAnalyser import OrderAnalyser @@ -23,9 +23,9 @@ def test_get_matching_synapse(self): neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'}) neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'}) - signal1 = Order(sentence="this is the sentence") - signal2 = Order(sentence="this is the second sentence") - signal3 = Order(sentence="that is part of the third sentence") + signal1 = Signal(name="order", parameters="this is the sentence") + signal2 = Signal(name="order", parameters="this is the second sentence") + signal3 = Signal(name="order", parameters="that is part of the third sentence") synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1]) synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2]) diff --git a/Tests/test_resources_manager.py b/Tests/test_resources_manager.py index 9af6f84f..3c5ee1f9 100644 --- a/Tests/test_resources_manager.py +++ b/Tests/test_resources_manager.py @@ -47,6 +47,13 @@ def test_is_settings_ok(self): dna.module_type = "trigger" self.assertTrue(ResourcesManager.is_settings_ok(valid_resource, dna)) + # valid signal + valid_resource = Resources() + valid_resource.signal_folder = "/path" + dna = Dna() + dna.module_type = "signal" + self.assertTrue(ResourcesManager.is_settings_ok(valid_resource, dna)) + # ----------------- # invalid resource # ----------------- @@ -78,6 +85,13 @@ def test_is_settings_ok(self): dna.module_type = "trigger" self.assertFalse(ResourcesManager.is_settings_ok(valid_resource, dna)) + # valid signal + valid_resource = Resources() + valid_resource.signal_folder = None + dna = Dna() + dna.module_type = "signal" + self.assertFalse(ResourcesManager.is_settings_ok(valid_resource, dna)) + def test_is_repo_ok(self): # valid repo if "/Tests" in os.getcwd(): @@ -127,6 +141,11 @@ def test_get_target_folder(self): resources.trigger_folder = '/var/tmp/test/resources' self.assertEqual(ResourcesManager._get_target_folder(resources, "trigger"), "/var/tmp/test/resources") + # test get signal folder + resources = Resources() + resources.signal_folder = '/var/tmp/test/resources' + self.assertEqual(ResourcesManager._get_target_folder(resources, "signal"), "/var/tmp/test/resources") + # test get non existing resource resources = Resources() self.assertIsNone(ResourcesManager._get_target_folder(resources, "not_existing")) diff --git a/Tests/test_rest_api.py b/Tests/test_rest_api.py index dd8f5b6e..b986dab5 100644 --- a/Tests/test_rest_api.py +++ b/Tests/test_rest_api.py @@ -73,51 +73,84 @@ def test_get_all_synapses(self): response = self.client.get(url) expected_content = { - "synapses": [{ - "name": "test", - "neurons": [{ - "name": "say", - "parameters": { - "message": ["test message"] - } - }], - "signals": [{ - "order": "test_order" - }] - }, { - "name": "test2", - "neurons": [{ - "name": "say", - "parameters": { - "message": ["test message"] - } - }], - "signals": [{ - "order": "bonjour" - }] - }, { - "name": "test4", - "neurons": [{ - "name": "say", - "parameters": { - "message": ["test message {{parameter1}}"] - } - }], - "signals": [{ - "order": "test_order_with_parameter" - }] - }, { - "name": "test3", - "neurons": [{ - "name": "say", - "parameters": { - "message": ["test message"] - } - }], - "signals": [{ - "order": "test_order_3" - }] - }] + "synapses": [ + { + "name": "test", + "neurons": [ + { + "name": "say", + "parameters": { + "message": [ + "test message" + ] + } + } + ], + "signals": [ + { + "name": "order", + "parameters": "test_order" + } + ] + }, + { + "name": "test2", + "neurons": [ + { + "name": "say", + "parameters": { + "message": [ + "test message" + ] + } + } + ], + "signals": [ + { + "name": "order", + "parameters": "bonjour" + } + ] + }, + { + "name": "test4", + "neurons": [ + { + "name": "say", + "parameters": { + "message": [ + "test message {{parameter1}}" + ] + } + } + ], + "signals": [ + { + "name": "order", + "parameters": "test_order_with_parameter" + } + ] + }, + { + "name": "test3", + "neurons": [ + { + "name": "say", + "parameters": { + "message": [ + "test message" + ] + } + } + ], + "signals": [ + { + "name": "order", + "parameters": "test_order_3" + } + ] + } + ] } # a lot of char ti process self.maxDiff = None @@ -129,25 +162,26 @@ def test_get_one_synapse(self): url = self.get_server_url() + "/synapses/test" response = self.client.get(url) - expected_content = { - "synapses": { - "name": "test", - "neurons": [ - { - "name": "say", - "parameters": { - "message": [ - "test message" - ] - } - } - ], - "signals": [ - { - "order": "test_order" - } - ] - } + expected_content ={ + "synapses": { + "name": "test", + "neurons": [ + { + "name": "say", + "parameters": { + "message": [ + "test message" + ] + } + } + ], + "signals": [ + { + "name": "order", + "parameters": "test_order" + } + ] + } } self.assertEqual(json.dumps(expected_content, sort_keys=True), json.dumps(json.loads(response.get_data().decode('utf-8')), sort_keys=True)) diff --git a/Tests/test_settings_loader.py b/Tests/test_settings_loader.py index e8096db3..4494da45 100644 --- a/Tests/test_settings_loader.py +++ b/Tests/test_settings_loader.py @@ -11,6 +11,7 @@ from kalliope.core.Models.RestAPI import RestAPI from kalliope.core.Models.Settings import Settings from kalliope.core.Models.Stt import Stt +from kalliope.core.Models.RecognitionOptions import RecognitionOptions from kalliope.core.Models.Trigger import Trigger from kalliope.core.Models.Tts import Tts @@ -119,6 +120,7 @@ def test_get_settings(self): "test": "kalliope" } settings_object.machine = platform.machine() + settings_object.recognition_options = RecognitionOptions() sl = SettingLoader(file_path=self.settings_file_to_test) diff --git a/Tests/test_synapse_launcher.py b/Tests/test_synapse_launcher.py index 9d327826..fa482835 100644 --- a/Tests/test_synapse_launcher.py +++ b/Tests/test_synapse_launcher.py @@ -3,13 +3,12 @@ import mock from kalliope.core import LIFOBuffer -from kalliope.core.Models import Brain +from kalliope.core.Models import Brain, Signal, Singleton from kalliope.core.Models.MatchedSynapse import MatchedSynapse from kalliope.core.Models.Settings import Settings from kalliope.core.SynapseLauncher import SynapseLauncher, SynapseNameNotFound from kalliope.core.Models import Neuron -from kalliope.core.Models import Order from kalliope.core.Models import Synapse @@ -25,9 +24,9 @@ def setUp(self): neuron3 = Neuron(name='neurone3', parameters={'var3': 'val3'}) neuron4 = Neuron(name='neurone4', parameters={'var4': 'val4'}) - signal1 = Order(sentence="this is the sentence") - signal2 = Order(sentence="this is the second sentence") - signal3 = Order(sentence="that is part of the third sentence") + signal1 = Signal(name="order", parameters="this is the sentence") + signal2 = Signal(name="order", parameters="this is the second sentence") + signal3 = Signal(name="order", parameters="that is part of the third sentence") self.synapse1 = Synapse(name="Synapse1", neurons=[neuron1, neuron2], signals=[signal1]) self.synapse2 = Synapse(name="Synapse2", neurons=[neuron3, neuron4], signals=[signal2]) @@ -41,7 +40,7 @@ def setUp(self): self.settings_test = Settings(default_synapse="Synapse3") # clean the LiFO - LIFOBuffer.lifo_list = list() + Singleton._instances = dict() def test_start_synapse_by_name(self): # existing synapse in the brain @@ -50,7 +49,22 @@ def test_start_synapse_by_name(self): SynapseLauncher.start_synapse_by_name("Synapse1", brain=self.brain_test) # we expect that the lifo has been loaded with the synapse to run expected_result = [[should_be_created_matched_synapse]] - self.assertEqual(expected_result, LIFOBuffer.lifo_list) + lifo_buffer = LIFOBuffer() + self.assertEqual(expected_result, lifo_buffer.lifo_list) + + # we expect that the lifo has been loaded with the synapse to run and overwritten parameters + Singleton._instances = dict() + lifo_buffer = LIFOBuffer() + overriding_param = { + "val1": "val" + } + SynapseLauncher.start_synapse_by_name("Synapse1", brain=self.brain_test, + overriding_parameter_dict=overriding_param) + should_be_created_matched_synapse = MatchedSynapse(matched_synapse=self.synapse1, + overriding_parameter=overriding_param) + # we expect that the lifo has been loaded with the synapse to run + expected_result = [[should_be_created_matched_synapse]] + self.assertEqual(expected_result, lifo_buffer.lifo_list) # non existing synapse in the brain with self.assertRaises(SynapseNameNotFound): @@ -71,13 +85,14 @@ def test_run_matching_synapse_from_order(self): brain=self.brain_test, settings=self.settings_test) - self.assertEqual(expected_result, LIFOBuffer.lifo_list) + lifo_buffer = LIFOBuffer() + self.assertEqual(expected_result, lifo_buffer.lifo_list) # ------------------------- # test_match_synapse1_and_2 # ------------------------- # clean LIFO - LIFOBuffer.lifo_list = list() + Singleton._instances = dict() with mock.patch("kalliope.core.LIFOBuffer.execute"): order_to_match = "this is the second sentence" should_be_created_matched_synapse1 = MatchedSynapse(matched_synapse=self.synapse1, @@ -91,13 +106,14 @@ def test_run_matching_synapse_from_order(self): SynapseLauncher.run_matching_synapse_from_order(order_to_match, brain=self.brain_test, settings=self.settings_test) - self.assertEqual(expected_result, LIFOBuffer.lifo_list) + lifo_buffer = LIFOBuffer() + self.assertEqual(expected_result, lifo_buffer.lifo_list) # ------------------------- # test_match_default_synapse # ------------------------- # clean LIFO - LIFOBuffer.lifo_list = list() + Singleton._instances = dict() with mock.patch("kalliope.core.LIFOBuffer.execute"): order_to_match = "not existing sentence" should_be_created_matched_synapse = MatchedSynapse(matched_synapse=self.synapse3, @@ -108,13 +124,14 @@ def test_run_matching_synapse_from_order(self): SynapseLauncher.run_matching_synapse_from_order(order_to_match, brain=self.brain_test, settings=self.settings_test) - self.assertEqual(expected_result, LIFOBuffer.lifo_list) + lifo_buffer = LIFOBuffer() + self.assertEqual(expected_result, lifo_buffer.lifo_list) # ------------------------- # test_no_match_and_no_default_synapse # ------------------------- # clean LIFO - LIFOBuffer.lifo_list = list() + Singleton._instances = dict() with mock.patch("kalliope.core.LIFOBuffer.execute"): order_to_match = "not existing sentence" new_settings = Settings() @@ -122,4 +139,14 @@ def test_run_matching_synapse_from_order(self): SynapseLauncher.run_matching_synapse_from_order(order_to_match, brain=self.brain_test, settings=new_settings) - self.assertEqual(expected_result, LIFOBuffer.lifo_list) + lifo_buffer = LIFOBuffer() + self.assertEqual(expected_result, lifo_buffer.lifo_list) + + +if __name__ == '__main__': + unittest.main() + + # suite = unittest.TestSuite() + # suite.addTest(TestSynapseLauncher("test_run_matching_synapse_from_order")) + # runner = unittest.TextTestRunner() + # runner.run(suite) diff --git a/brain_examples/kalliope_memory.yml b/brain_examples/kalliope_memory.yml new file mode 100644 index 00000000..55c089ac --- /dev/null +++ b/brain_examples/kalliope_memory.yml @@ -0,0 +1,33 @@ +# test with a value to save from the order +- name: "cortex-1" + signals: + - order: "dis bonjour à {{ friend }}" + neurons: + - say: + message: + - "Bonjour {{ friend }}" + kalliope_memory: + friend: "{{ friend }}" + + +- name: "remember-synapse222" + signals: + - order: "remind me {{ remember }} in {{ time }} seconds" + neurons: + - neurotimer: + seconds: "{{ time }}" + synapse: "remember-todo22" + kalliope_memory: + remember: "{{ remember }}" + minutes: "{{ time }}" + - say: + message: + - "Ok {{ time }} seconds" + +- name: "remember-todo22" + signals: + - order: "no-order-for-this-synapse111" + neurons: + - say: + message: + - "does this work? {{ kalliope_memory['remember'] }} and the time? {{ kalliope_memory['minutes'] }}" diff --git a/brain_examples/mute.yml b/brain_examples/mute.yml new file mode 100644 index 00000000..ea35c6ce --- /dev/null +++ b/brain_examples/mute.yml @@ -0,0 +1,10 @@ + +- name: "mute-synapse" + signals: + - order: "stop listening" + neurons: + - say: + message: + - "I stop hearing you, sir" + - mute: + status: True diff --git a/brain_examples/neurotimer.yml b/brain_examples/neurotimer.yml new file mode 100644 index 00000000..bc84e944 --- /dev/null +++ b/brain_examples/neurotimer.yml @@ -0,0 +1,54 @@ + - name: "timer" + signals: + - order: "je lance un thé" + - order: "je lance un T" + neurons: + - neurotimer: + seconds: 10 + synapse: "time-over" + - say: + message: + - "je vous préviens dans 5 secondes" + + - name: "time-over" + signals: + - order: "time-is-over" + neurons: + - say: + message: + - "c'est terminé" + + - name: "timer2" + signals: + - order: "préviens moi dans {{ time }} secondes" + - order: "préviens-moi dans {{ time }} secondes" + neurons: + - neurotimer: + seconds: "{{ time }}" + synapse: "time-over" + - say: + message: + - "je vous préviens dans {{ time }} secondes" + + - name: "remember-synapse" + signals: + - order: "rappel-moi de {{ remember }} dans {{ time }} secondes" + - order: "rappel moi de {{ remember }} dans {{ time }} secondes" + - order: "rappelle-moi de {{ remember }} dans {{ time }} secondes" + neurons: + - neurotimer: + seconds: "{{ time }}" + synapse: "remember-todo" + forwarded_parameters: + remember: "{{ remember }}" + time: "{{ time }}" + - say: + message: + - "je vous le rappel dans {{ time }} secondes" + + - name: "remember-todo" + signals: {} + neurons: + - say: + message: + - "Vous m'avez demandé de vous rappeler de {{ remember }} il y a {{ time }} secondes" \ No newline at end of file diff --git a/brain_examples/test_lifo.yml b/brain_examples/test_lifo.yml new file mode 100644 index 00000000..362b9817 --- /dev/null +++ b/brain_examples/test_lifo.yml @@ -0,0 +1,32 @@ +- name: "remember-synapse" + signals: + - order: "rappel moi de {{ remember }} dans {{ time }} secondes" + neurons: + - neurotimer: + seconds: "{{ time }}" + synapse: "remember-todo" + forwarded_parameters: + remember: "{{ remember }}" + time: "{{ time }}" + - say: + message: + - "je vous le rappel dans {{ time }} secondes" + +- name: "remember-todo" + signals: {} + neurons: + - say: + message: + - "vous m'avez demandé de vous rappeler de {{ remember }} il a {{ time }} secondes" + - neurotransmitter: + from_answer_link: + - synapse: "your-welcome" + answers: + - "merci" + default: "synapse3" + +- name: "your-welcome" + signals: {} + neurons: + - say: + message: "de rien" diff --git a/docker/clone_and_test_python3.sh b/docker/clone_and_test_python3.sh new file mode 100755 index 00000000..51b5320e --- /dev/null +++ b/docker/clone_and_test_python3.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# install dev version +git clone https://github.com/kalliope-project/kalliope.git kalliope; +cd kalliope; +git checkout dev; + +# install +sudo python3 setup.py install + +# tests +export LANG=C.UTF-8 +python3 -m unittest discover diff --git a/docker/compile_snowboy.dockerfile b/docker/compile_snowboy.dockerfile new file mode 100644 index 00000000..13176294 --- /dev/null +++ b/docker/compile_snowboy.dockerfile @@ -0,0 +1,39 @@ +# build last version +# docker build --force-rm=true -t compile-snowboy-python3 -f compile_snowboy.dockerfile . + +# build specified version +# docker build --force-rm=true --build-arg SNOWBOY_VERSION=1.1.1 -t compile-snowboy-python3 -f compile_snowboy.dockerfile . + +# compile into local /tmp/snowboy +# docker run -it --rm --mount type=bind,source=/tmp/snowboy,target=/data compile-snowboy-python3 + +FROM ubuntu:xenial + +RUN apt-get update +RUN apt-get install -y git make g++ python3-dev libatlas3-base libblas-dev gfortran vim wget libpcre3-dev + +# get the last version of swig +RUN wget https://downloads.sourceforge.net/swig/swig-3.0.12.tar.gz && tar xzf swig-3.0.12.tar.gz +RUN cd swig-3.0.12 && \ + ./configure --prefix=/usr \ + --without-clisp \ + --without-maximum-compile-warnings && \ + make + +RUN cd swig-3.0.12 && \ + make install && \ + install -v -m755 -d /usr/share/doc/swig-3.0.12 && \ + cp -v -R Doc/* /usr/share/doc/swig-3.0.12 + +# version can be 1.2.0, 1.1.1, 1.1.0, 1.0.4 +ARG SNOWBOY_VERSION="1.2.0" + +RUN wget https://github.com/Kitt-AI/snowboy/archive/v${SNOWBOY_VERSION}.tar.gz && tar xzf v${SNOWBOY_VERSION}.tar.gz + +RUN sed -i "s|python-config|python3-config|g" snowboy-${SNOWBOY_VERSION}/swig/Python/Makefile +RUN sed -i "s|-lf77blas -lcblas -llapack_atlas -latlas|-lquadmath -lgfortran -lblas /usr/lib/libcblas.so.3|g" snowboy-${SNOWBOY_VERSION}/swig/Python/Makefile +RUN cd /snowboy-${SNOWBOY_VERSION}/swig/Python && make +RUN cd /snowboy-${SNOWBOY_VERSION}/swig/Python && python3 -c "import _snowboydetect; print('OK')" +# compiled binary will be placed into data folder +RUN mkdir /data +CMD cp /snowboy-*/swig/Python/*.so /data diff --git a/docker/compile_snowboy_python34.dockerfile b/docker/compile_snowboy_python34.dockerfile new file mode 100644 index 00000000..de206589 --- /dev/null +++ b/docker/compile_snowboy_python34.dockerfile @@ -0,0 +1,39 @@ +# build last version +# docker build --force-rm=true -t compile-snowboy-python34 -f compile_snowboy_python34.dockerfile . + +# build specified version +# docker build --force-rm=true --build-arg SNOWBOY_VERSION=1.1.1 -t compile-snowboy-python34 -f compile_snowboy_python34.dockerfile . + +# compile into local /tmp/snowboy +# docker run -it --rm --mount type=bind,source=/tmp/snowboy,target=/data compile-snowboy-python34 + +FROM ubuntu:trusty + +RUN apt-get update +RUN apt-get install -y git make g++ python3-dev libatlas3-base libblas-dev gfortran vim wget libpcre3-dev + +# get the last version of swig +RUN wget https://downloads.sourceforge.net/swig/swig-3.0.12.tar.gz && tar xzf swig-3.0.12.tar.gz +RUN cd swig-3.0.12 && \ + ./configure --prefix=/usr \ + --without-clisp \ + --without-maximum-compile-warnings && \ + make + +RUN cd swig-3.0.12 && \ + make install && \ + install -v -m755 -d /usr/share/doc/swig-3.0.12 && \ + cp -v -R Doc/* /usr/share/doc/swig-3.0.12 + +# version can be 1.2.0, 1.1.1, 1.1.0, 1.0.4 +ARG SNOWBOY_VERSION="1.2.0" + +RUN wget https://github.com/Kitt-AI/snowboy/archive/v${SNOWBOY_VERSION}.tar.gz && tar xzf v${SNOWBOY_VERSION}.tar.gz + +RUN sed -i "s|python-config|python3-config|g" snowboy-${SNOWBOY_VERSION}/swig/Python/Makefile +RUN sed -i "s|-lf77blas -lcblas -llapack_atlas -latlas|-lquadmath -lgfortran -lblas /usr/lib/libcblas.so.3|g" snowboy-${SNOWBOY_VERSION}/swig/Python/Makefile +RUN cd /snowboy-${SNOWBOY_VERSION}/swig/Python && make +RUN cd /snowboy-${SNOWBOY_VERSION}/swig/Python && python3 -c "import _snowboydetect; print('OK')" +# compiled binary will be placed into data folder +RUN mkdir /data +CMD cp /snowboy-*/swig/Python/*.so /data diff --git a/docker/ubuntu_16_04.dockerfile b/docker/ubuntu_16_04.dockerfile index 87286da0..6faec658 100644 --- a/docker/ubuntu_16_04.dockerfile +++ b/docker/ubuntu_16_04.dockerfile @@ -21,6 +21,7 @@ RUN pip install --upgrade pip six RUN pip install --upgrade pip pyyaml RUN pip install --upgrade pip SpeechRecognition RUN pip install --upgrade pip Werkzeug +RUN pip install --upgrade pip gitdb # add a standart user. tests must not be ran as root RUN useradd -m -u 1000 tester diff --git a/docker/ubuntu_16_04_python3.dockerfile b/docker/ubuntu_16_04_python3.dockerfile new file mode 100644 index 00000000..172b1bf7 --- /dev/null +++ b/docker/ubuntu_16_04_python3.dockerfile @@ -0,0 +1,38 @@ +# used to build the last kalliope dev version with python 3 +# docker build --force-rm=true -t kalliope-ubuntu1604-python3 -f docker/ubuntu_16_04_python3.dockerfile . +# docker run -it --rm kalliope-ubuntu1604-python3 + +FROM ubuntu:16.04 + +ENV no_proxy="127.0.0.1,localhost,kalliope.fr" + +# set UTF-8 to the terminal +ENV LANG en_US.UTF-8 + +# pico2wav is a multiverse package +RUN echo "deb http://us.archive.ubuntu.com/ubuntu/ xenial multiverse" >> /etc/apt/sources.list + +# install packages +RUN apt-get update && apt-get install -y \ + git python3-dev libsmpeg0 libttspico-utils libsmpeg0 flac dialog \ + libffi-dev libffi-dev libssl-dev portaudio19-dev build-essential \ + sox libatlas3-base mplayer wget vim sudo\ + && rm -rf /var/lib/apt/lists/* + +# Install the last PIP +RUN wget https://bootstrap.pypa.io/get-pip.py +RUN python3 get-pip.py + +# add a standart user. tests must not be ran as root +RUN useradd -m -u 1000 tester +RUN usermod -aG sudo tester +RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers + +ADD docker/clone_and_test_python3.sh /home/tester/clone_and_test_python3.sh +RUN chown tester /home/tester/clone_and_test_python3.sh + +USER tester +WORKDIR /home/tester + +# run tests +CMD ./clone_and_test_python3.sh \ No newline at end of file diff --git a/images/kalliope_app.png b/images/kalliope_app.png new file mode 100755 index 00000000..5081ace9 Binary files /dev/null and b/images/kalliope_app.png differ diff --git a/install/files/python_requirements.txt b/install/files/python_requirements.txt index 00b0c748..679663b7 100644 --- a/install/files/python_requirements.txt +++ b/install/files/python_requirements.txt @@ -3,7 +3,7 @@ SpeechRecognition>=3.7.1 markupsafe==0.23 pyaudio==0.2.11 pyasn1>=0.1.8 -ansible==2.2.0.0 +ansible==2.3.0.0 #python2-pythondialog==3.4.0 jinja2==2.8 cffi==1.9.1 @@ -24,3 +24,4 @@ SoundFile>=0.9.0 pyalsaaudio>=0.8.4 RPi.GPIO>=0.6.3 sox>=1.3.0 +paho-mqtt>=1.3.0 diff --git a/kalliope/__init__.py b/kalliope/__init__.py index 8a821dca..36b63f8d 100644 --- a/kalliope/__init__.py +++ b/kalliope/__init__.py @@ -3,13 +3,16 @@ import argparse import logging +import time + from kalliope.core import ShellGui from kalliope.core import Utils from kalliope.core.ConfigurationManager import SettingLoader from kalliope.core.ConfigurationManager.BrainLoader import BrainLoader -from kalliope.core.EventManager import EventManager -from kalliope.core.MainController import MainController +from kalliope.core.SignalLauncher import SignalLauncher from kalliope.core.Utils.RpiUtils import RpiUtils +from flask import Flask +from kalliope.core.RestAPI.FlaskAPI import FlaskAPI from ._version import version_str import signal @@ -59,6 +62,7 @@ def parse_args(args): parser.add_argument("--stt-name", help="STT name to uninstall") parser.add_argument("--tts-name", help="TTS name to uninstall") parser.add_argument("--trigger-name", help="Trigger name to uninstall") + parser.add_argument("--signal-name", help="Signal name to uninstall") parser.add_argument('-v', '--version', action='version', version='Kalliope ' + version_str) @@ -107,14 +111,25 @@ def main(): # uninstall modules if parser.action == "uninstall": - if not parser.neuron_name and not parser.stt_name and not parser.tts_name and not parser.trigger_name: - Utils.print_danger("You must specify a module name with --neuron-name or --stt-name or --tts-name " - "or --trigger-name") + if not parser.neuron_name \ + and not parser.stt_name \ + and not parser.tts_name \ + and not parser.trigger_name\ + and not parser.signal_name: + Utils.print_danger("You must specify a module name with " + "--neuron-name " + "or --stt-name " + "or --tts-name " + "or --trigger-name " + "or --signal-name") sys.exit(1) else: res_manager = ResourcesManager() - res_manager.uninstall(neuron_name=parser.neuron_name, stt_name=parser.stt_name, - tts_name=parser.tts_name, trigger_name=parser.trigger_name) + res_manager.uninstall(neuron_name=parser.neuron_name, + stt_name=parser.stt_name, + tts_name=parser.tts_name, + trigger_name=parser.trigger_name, + signal_name=parser.signal_name) return # load the brain once @@ -128,10 +143,6 @@ def main(): if parser.action == "start": - if settings.rpi_settings: - # init GPIO once - RpiUtils(settings.rpi_settings) - # user set a synapse to start if parser.run_synapse is not None: SynapseLauncher.start_synapse_by_name(parser.run_synapse, @@ -144,25 +155,9 @@ def main(): is_api_call=False) if (parser.run_synapse is None) and (parser.run_order is None): - # first, load events in event manager - EventManager(brain.synapses) - Utils.print_success("Events loaded") - # then start kalliope - Utils.print_success("Starting Kalliope") - Utils.print_info("Press Ctrl+C for stopping") - # catch signal for killing on Ctrl+C pressed - signal.signal(signal.SIGINT, signal_handler) - # start the state machine - 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() + # start rest api + start_rest_api(settings, brain) + start_kalliope(settings, brain) if parser.action == "gui": try: @@ -172,6 +167,15 @@ def main(): sys.exit(0) +class AppFilter(logging.Filter): + """ + Class used to add a custom entry into the logger + """ + def filter(self, record): + record.app_version = "kalliope-%s" % version_str + return True + + def configure_logging(debug=None): """ Prepare log folder in current home directory. @@ -180,18 +184,90 @@ def configure_logging(debug=None): """ logger = logging.getLogger("kalliope") + logger.addFilter(AppFilter()) logger.propagate = False - ch = logging.StreamHandler() - ch.setLevel(logging.DEBUG) - formatter = logging.Formatter('%(asctime)s :: %(levelname)s :: %(message)s') - ch.setFormatter(formatter) + syslog = logging.StreamHandler() + syslog .setLevel(logging.DEBUG) - # add the handlers to logger - logger.addHandler(ch) + formatter = logging.Formatter('%(asctime)s :: %(app_version)s :: %(message)s', "%Y-%m-%d %H:%M:%S") + syslog .setFormatter(formatter) if debug: logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) + # add the handlers to logger + logger.addHandler(syslog) + logger.debug("Logger ready") + + +def get_list_signal_class_to_load(brain): + """ + Return a list of signal class name + For all synapse, each signal type is added to a list only if the signal is not yet present in the list + :param brain: Brain object + :type brain: Brain + :return: set of signal class + """ + list_signal_class_name = set() + + for synapse in brain.synapses: + for signal_object in synapse.signals: + list_signal_class_name.add(signal_object.name) + logger.debug("[Kalliope entrypoint] List of signal class to load: %s" % list_signal_class_name) + return list_signal_class_name + + +def start_rest_api(settings, brain): + """ + Start the Rest API if asked in the user settings + """ + # run the api if the user want it + if settings.rest_api.active: + Utils.print_info("Starting REST API Listening port: %s" % settings.rest_api.port) + app = Flask(__name__) + flask_api = FlaskAPI(app=app, + port=settings.rest_api.port, + brain=brain, + allowed_cors_origin=settings.rest_api.allowed_cors_origin) + flask_api.daemon = True + flask_api.start() + + +def start_kalliope(settings, brain): + """ + Start all signals declared in the brain + """ + # start kalliope + Utils.print_success("Starting Kalliope") + Utils.print_info("Press Ctrl+C for stopping") + # catch signal for killing on Ctrl+C pressed + signal.signal(signal.SIGINT, signal_handler) + + # get a list of signal class to load from declared synapse in the brain + # this list will contain string of signal class type. + # For example, if the brain contains multiple time the signal type "order", the list will be ["order"] + # If the brain contains some synapse with "order" and "event", the list will be ["order", "event"] + list_signals_class_to_load = get_list_signal_class_to_load(brain) + + # start each class name + try: + for signal_class_name in list_signals_class_to_load: + signal_instance = SignalLauncher.launch_signal_class_by_name(signal_name=signal_class_name, + settings=settings) + if signal_instance is not None: + signal_instance.daemon = True + signal_instance.start() + + while True: # keep main thread alive + time.sleep(0.1) + + except (KeyboardInterrupt, SystemExit): + # we need to switch GPIO pin to default status if we are using a Rpi + if settings.rpi_settings: + Utils.print_info("GPIO cleaned") + logger.debug("Clean GPIO") + import RPi.GPIO as GPIO + GPIO.cleanup() diff --git a/kalliope/_version.py b/kalliope/_version.py index 3a8156bb..233f4985 100644 --- a/kalliope/_version.py +++ b/kalliope/_version.py @@ -1,2 +1,2 @@ # https://www.python.org/dev/peps/pep-0440/ -version_str = "0.4.5" +version_str = "0.4.6" diff --git a/kalliope/core/ConfigurationManager/BrainLoader.py b/kalliope/core/ConfigurationManager/BrainLoader.py index 9d3477d4..4eacab5f 100644 --- a/kalliope/core/ConfigurationManager/BrainLoader.py +++ b/kalliope/core/ConfigurationManager/BrainLoader.py @@ -4,15 +4,14 @@ from six import with_metaclass import six +from kalliope.core.Models.Signal import Signal from .YAMLLoader import YAMLLoader from kalliope.core.Utils import Utils from kalliope.core.ConfigurationManager import SettingLoader from kalliope.core.ConfigurationManager.ConfigurationChecker import ConfigurationChecker from kalliope.core.Models import Singleton from kalliope.core.Models.Brain import Brain -from kalliope.core.Models.Event import Event from kalliope.core.Models.Neuron import Neuron -from kalliope.core.Models.Order import Order from kalliope.core.Models.Synapse import Synapse logging.basicConfig() @@ -166,38 +165,12 @@ def _get_signals(cls, signals_dict): signals = list() for signal_dict in signals_dict: if ConfigurationChecker().check_signal_dict(signal_dict): - event_or_order = cls._get_event_or_order_from_dict(signal_dict) - signals.append(event_or_order) + for signal_name in signal_dict: + new_signal = Signal(name=signal_name, parameters=signal_dict[signal_name]) + signals.append(new_signal) return signals - @classmethod - def _get_event_or_order_from_dict(cls, signal_or_event_dict): - """ - The signal is either an Event or an Order - - :param signal_or_event_dict: A dict of event or signal - :type signal_or_event_dict: dict - :return: The object corresponding to An Order or an Event - :rtype: An Order or an Event - - :Example: - - event_or_order = cls._get_event_or_order_from_dict(signal_dict) - - .. seealso:: Event, Order - .. warnings:: Static method and Private - """ - if 'event' in signal_or_event_dict: - event = signal_or_event_dict["event"] - if ConfigurationChecker.check_event_dict(event): - return cls._get_event_object(event) - - if 'order' in signal_or_event_dict: - order = signal_or_event_dict["order"] - if ConfigurationChecker.check_order_dict(order): - return Order(sentence=order) - @staticmethod def _get_root_brain_path(): """ @@ -221,26 +194,6 @@ def _get_root_brain_path(): return brain_path raise IOError("Default brain.yml file not found") - @classmethod - def _get_event_object(cls, event_dict): - def get_key(key_name): - try: - return event_dict[key_name] - except KeyError: - return None - - year = get_key("year") - month = get_key("month") - day = get_key("day") - week = get_key("week") - day_of_week = get_key("day_of_week") - hour = get_key("hour") - minute = get_key("minute") - second = get_key("second") - - return Event(year=year, month=month, day=day, week=week, - day_of_week=day_of_week, hour=hour, minute=minute, second=second) - @classmethod def _replace_global_variables(cls, parameter, settings): """ diff --git a/kalliope/core/ConfigurationManager/ConfigurationChecker.py b/kalliope/core/ConfigurationManager/ConfigurationChecker.py index 7e3eb81a..ebd8ae7b 100644 --- a/kalliope/core/ConfigurationManager/ConfigurationChecker.py +++ b/kalliope/core/ConfigurationManager/ConfigurationChecker.py @@ -5,6 +5,7 @@ from kalliope.core.Utils.Utils import ModuleNotFoundError from kalliope.core.ConfigurationManager.SettingLoader import SettingLoader + class InvalidSynapeName(Exception): """ The name of the synapse is not correct. It should only contains alphanumerics at the beginning and the end of @@ -48,15 +49,6 @@ class NoValidSignal(Exception): pass -class NoEventPeriod(Exception): - """ - An Event must contains a period corresponding to its execution - - .. seealso:: Event - """ - pass - - class MultipleSameSynapseName(Exception): """ A synapse name must be unique @@ -144,7 +136,7 @@ def check_neuron_exist(neuron_module_name): sl = SettingLoader() settings = sl.settings package_name = "kalliope.neurons" + "." + neuron_module_name.lower() + "." + neuron_module_name.lower() - if settings.resources is not None: + if settings.resources.neuron_folder is not None: neuron_resource_path = settings.resources.neuron_folder + \ os.sep + neuron_module_name.lower() + os.sep + \ neuron_module_name.lower()+".py" @@ -172,72 +164,43 @@ def check_neuron_exist(neuron_module_name): @staticmethod def check_signal_dict(signal_dict): - """ - Check received signal dictionary is valid: - - :param signal_dict: The signal Dictionary - :type signal_dict: Dict - :return: True if signal are ok - :rtype: Boolean - :Example: - - ConfigurationChecker().check_signal_dict(signal_dict): - - .. seealso:: Order, Event - .. raises:: NoValidSignal - .. warnings:: Static and Public - """ - - if ('event' not in signal_dict) and ('order' not in signal_dict): - raise NoValidSignal("The signal is not an event or an order %s" % signal_dict) - return True - - @staticmethod - def check_event_dict(event_dict): - """ - Check received event dictionary is valid: - - :param event_dict: The event Dictionary - :type event_dict: Dict - :return: True if event are ok - :rtype: Boolean - - :Example: - - ConfigurationChecker().check_event_dict(event_dict): + def check_signal_exist(signal_name): + """ + Return True if the signal_name python Class exist in signals package + :param signal_name: Name of the neuron module to check + :type signal_name: str + :return: + """ + sl = SettingLoader() + settings = sl.settings + package_name = "kalliope.signals" + "." + signal_name.lower() + "." + signal_name.lower() + if settings.resources.signal_folder is not None: + neuron_resource_path = settings.resources.neuron_folder + \ + os.sep + signal_name.lower() + os.sep + \ + signal_name.lower() + ".py" + if os.path.exists(neuron_resource_path): + imp.load_source(signal_name.capitalize(), neuron_resource_path) + package_name = signal_name.capitalize() - .. seealso:: Event - .. raises:: NoEventPeriod - .. warnings:: Static and Public - """ - def get_key(key_name): try: - return event_dict[key_name] - except KeyError: - return None - - if event_dict is None or event_dict == "": - raise NoEventPeriod("Event must contain at least one of those elements: " - "year, month, day, week, day_of_week, hour, minute, second") - - # check content as at least on key - year = get_key("year") - month = get_key("month") - day = get_key("day") - week = get_key("week") - day_of_week = get_key("day_of_week") - hour = get_key("hour") - minute = get_key("minute") - second = get_key("second") - - list_to_check = [year, month, day, week, day_of_week, hour, minute, second] - number_of_none_object = list_to_check.count(None) - list_size = len(list_to_check) - if number_of_none_object >= list_size: - raise NoEventPeriod("Event must contain at least one of those elements: " - "year, month, day, week, day_of_week, hour, minute, second") + mod = __import__(package_name, fromlist=[signal_name.capitalize()]) + getattr(mod, signal_name.capitalize()) + except AttributeError: + raise ModuleNotFoundError( + "[AttributeError] The module %s does not exist in the package %s " % (signal_name.capitalize(), + package_name)) + except ImportError: + raise ModuleNotFoundError( + "[ImportError] The module %s does not exist in the package %s " % (signal_name.capitalize(), + package_name)) + return True + if isinstance(signal_dict, dict): + for signal_name in signal_dict: + check_signal_exist(signal_name) + else: + check_signal_exist(signal_dict) return True @staticmethod diff --git a/kalliope/core/ConfigurationManager/SettingLoader.py b/kalliope/core/ConfigurationManager/SettingLoader.py index 35dc1eaa..d09a74ae 100644 --- a/kalliope/core/ConfigurationManager/SettingLoader.py +++ b/kalliope/core/ConfigurationManager/SettingLoader.py @@ -3,6 +3,7 @@ from six import with_metaclass from kalliope.core.Models.RpiSettings import RpiSettings +from kalliope.core.Models.RecognitionOptions import RecognitionOptions from .YAMLLoader import YAMLLoader from kalliope.core.Models.Resources import Resources from kalliope.core.Utils.Utils import Utils @@ -118,6 +119,7 @@ def _get_settings(self): resources = self._get_resources(settings) variables = self._get_variables(settings) rpi_settings = self._get_rpi_settings(settings) + recognition_options = self._get_recognition_options(settings) # Load the setting singleton with the parameters setting_object.default_tts_name = default_tts_name @@ -139,6 +141,7 @@ def _get_settings(self): setting_object.resources = resources setting_object.variables = variables setting_object.rpi_settings = rpi_settings + setting_object.recognition_options = recognition_options return setting_object @@ -615,6 +618,8 @@ def _get_resources(settings): .. raises:: SettingNotFound, NullSettingException, SettingInvalidException .. warnings:: Class Method and Private """ + # return an empty resource object anyway + resource_object = Resources() try: resource_dir = settings["resource_directory"] logger.debug("Resource directory synapse: %s" % resource_dir) @@ -623,40 +628,60 @@ def _get_resources(settings): stt_folder = None tts_folder = None trigger_folder = None + signal_folder = None + if "neuron" in resource_dir: neuron_folder = resource_dir["neuron"] - if not os.path.exists(neuron_folder): + if os.path.exists(neuron_folder): + logger.debug("[SettingLoader] Neuron resource folder path loaded: %s" % neuron_folder) + resource_object.neuron_folder = neuron_folder + else: raise SettingInvalidException("The path %s does not exist on the system" % neuron_folder) if "stt" in resource_dir: stt_folder = resource_dir["stt"] - if not os.path.exists(stt_folder): + if os.path.exists(stt_folder): + logger.debug("[SettingLoader] STT resource folder path loaded: %s" % stt_folder) + resource_object.stt_folder = stt_folder + else: raise SettingInvalidException("The path %s does not exist on the system" % stt_folder) if "tts" in resource_dir: tts_folder = resource_dir["tts"] - if not os.path.exists(tts_folder): + if os.path.exists(tts_folder): + logger.debug("[SettingLoader] TTS resource folder path loaded: %s" % tts_folder) + resource_object.tts_folder = tts_folder + else: raise SettingInvalidException("The path %s does not exist on the system" % tts_folder) if "trigger" in resource_dir: trigger_folder = resource_dir["trigger"] - if not os.path.exists(trigger_folder): + if os.path.exists(trigger_folder): + logger.debug("[SettingLoader] Trigger resource folder path loaded: %s" % trigger_folder) + resource_object.trigger_folder = trigger_folder + else: raise SettingInvalidException("The path %s does not exist on the system" % trigger_folder) + if "signal" in resource_dir: + signal_folder = resource_dir["signal"] + if os.path.exists(signal_folder): + logger.debug("[SettingLoader] Signal resource folder path loaded: %s" % signal_folder) + resource_object.signal_folder = signal_folder + else: + raise SettingInvalidException("The path %s does not exist on the system" % signal_folder) + if neuron_folder is None \ and stt_folder is None \ and tts_folder is None \ - and trigger_folder is None: + and trigger_folder is None \ + and signal_folder is None: raise SettingInvalidException("No required folder has been provided in the setting resource_directory. " - "Define : \'neuron\' or/and \'stt\' or/and \'tts\' or/and \'trigger\'") + "Define : \'neuron\' or/and \'stt\' or/and \'tts\' or/and \'trigger\' " + "or/and \'signal\'") - resource_object = Resources(neuron_folder=neuron_folder, - stt_folder=stt_folder, - tts_folder=tts_folder, - trigger_folder=trigger_folder) except KeyError: logger.debug("Resource directory not found in settings") - resource_object = None + return resource_object return resource_object @@ -762,3 +787,30 @@ def _get_rpi_settings(settings): except KeyError: logger.debug("[SettingsLoader] No Rpi config") return None + + @staticmethod + def _get_recognition_options(settings): + """ + return the value of stt_threshold + :param settings: The loaded YAML settings file + :return: integer or 1200 by default if not set + """ + recognition_options = RecognitionOptions() + + try: + recognition_options_dict = settings["RecognitionOptions"] + + if "energy_threshold" in recognition_options_dict: + recognition_options.energy_threshold = recognition_options_dict["energy_threshold"] + logger.debug("[SettingsLoader] energy_threshold set to %s" % recognition_options.energy_threshold) + if "adjust_for_ambient_noise_second" in recognition_options_dict: + recognition_options.adjust_for_ambient_noise_second = recognition_options_dict["adjust_for_ambient_noise_second"] + logger.debug("[SettingsLoader] adjust_for_ambient_noise_second set to %s" + % recognition_options.adjust_for_ambient_noise_second) + return recognition_options + + except KeyError: + logger.debug("[SettingsLoader] no recognition_options defined. Set to default") + + logger.debug("[SettingsLoader] recognition_options: %s" % str(recognition_options)) + return recognition_options diff --git a/kalliope/core/Cortex.py b/kalliope/core/Cortex.py new file mode 100644 index 00000000..133eb80d --- /dev/null +++ b/kalliope/core/Cortex.py @@ -0,0 +1,120 @@ +import logging + +import jinja2 +from kalliope.core.Utils.Utils import Utils + +from kalliope.core.Models import Singleton +from six import with_metaclass + +logging.basicConfig() +logger = logging.getLogger("kalliope") + + +class Cortex(with_metaclass(Singleton, object)): + """ + short-term memories of kalliope. Used to store object with a "key" "value" + """ + # this dict contains the short term memory of kalliope. + # all keys present in this dict has been saved from a user demand + memory = dict() + # this is a temp dict that allow us to store temporary parameters that as been loaded from the user order + # if the user want to a key from this dict, the key and its value will be added o the memory dict + temp = dict() + + def __init__(self): + logger.debug("[Cortex] New memory created") + + @classmethod + def get_memory(cls): + """ + Get the current dict of parameters saved in memory + :return: dict memory + """ + return cls.memory + + @classmethod + def save(cls, key, value): + """ + Save a new value in the memory + :param key: key to save + :param value: value to save into the key + """ + if key in cls.memory: + logger.debug("[Cortex] key %s already present in memory with value %s. Will be overridden" + % (key, cls.memory[key])) + logger.debug("[Cortex] key saved in memory. key: %s, value: %s" % (key, value)) + cls.memory[key] = value + + @classmethod + def get_from_key(cls, key): + try: + return cls.memory[key] + except KeyError: + logger.debug("[Cortex] key %s does not exist in memory" % key) + return None + + @classmethod + def add_parameters_from_order(cls, dict_parameter): + logger.debug("[Cortex] place parameters in temp list: %s" % dict_parameter) + cls.temp.update(dict_parameter) + + @classmethod + def clean_parameter_from_order(cls): + """ + Clean the temps memory that store parameters loaded from vocal order + """ + logger.debug("[Cortex] Clean temp memory") + cls.temp = dict() + + @classmethod + def save_neuron_parameter_in_memory(cls, kalliope_memory_dict, neuron_parameters): + """ + receive a dict of value send by the child neuron + save in kalliope memory all value + + E.g + dict_parameter_to_save = {"my_key_to_save_in_memory": "{{ output_val_from_neuron }}"} + neuron_parameter = {"output_val_from_neuron": "this_is_a_value" } + + then the cortex will save in memory the key "my_key_to_save_in_memory" and attach the value "this_is_a_value" + + :param neuron_parameters: dict of parameter the neuron has processed and send to the neurone module to + be processed by the TTS engine + :param kalliope_memory_dict: a dict of key value the user want to save from the dict_neuron_parameter + """ + + if kalliope_memory_dict is not None: + logger.debug("[Cortex] save_memory - User want to save: %s" % kalliope_memory_dict) + logger.debug("[Cortex] save_memory - Available parameters in the neuron: %s" % neuron_parameters) + + for key, value in kalliope_memory_dict.items(): + # ask the cortex to save in memory the target "key" if it was in parameters of the neuron + if isinstance(neuron_parameters, dict): + if Utils.is_containing_bracket(value): + value = jinja2.Template(value).render(neuron_parameters) + Cortex.save(key, value) + + @classmethod + def save_parameter_from_order_in_memory(cls, order_parameters): + """ + Save key from the temp dict (where parameters loaded from the voice order where placed temporary) + into the memory dict + :param order_parameters: dict of key to save. {'key_name_in_memory': 'key_name_in_temp_dict'} + :return True if a value has been saved in the kalliope memory + """ + order_saved = False + if order_parameters is not None: + logger.debug("[Cortex] save_parameter_from_order_in_memory - User want to save: %s" % order_parameters) + logger.debug("[Cortex] save_parameter_from_order_in_memory - Available parameters in orders: %s" + % cls.temp) + + for key, value in order_parameters.items(): + # ask the cortex to save in memory the target "key" if it was in the order + if Utils.is_containing_bracket(value): + # if the key exist in the temp dict we can load it with jinja + value = jinja2.Template(value).render(Cortex.temp) + if value: + Cortex.save(key, value) + order_saved = True + + return order_saved diff --git a/kalliope/core/EventManager.py b/kalliope/core/EventManager.py deleted file mode 100644 index 6521a65a..00000000 --- a/kalliope/core/EventManager.py +++ /dev/null @@ -1,49 +0,0 @@ -from apscheduler.schedulers.background import BackgroundScheduler -from apscheduler.triggers.cron import CronTrigger - -from kalliope.core.ConfigurationManager import BrainLoader -from kalliope.core.SynapseLauncher import SynapseLauncher -from kalliope.core import Utils -from kalliope.core.Models import Event - - -class EventManager(object): - - def __init__(self, synapses): - Utils.print_info('Starting event manager') - self.scheduler = BackgroundScheduler() - self.synapses = synapses - self.load_events() - self.scheduler.start() - - def load_events(self): - """ - For each received synapse that have an event as signal, we add a new job scheduled - to launch the synapse - :return: - """ - for synapse in self.synapses: - for signal in synapse.signals: - # if the signal is an event we add it to the task list - if type(signal) == Event: - my_cron = CronTrigger(year=signal.year, - month=signal.month, - day=signal.day, - week=signal.week, - day_of_week=signal.day_of_week, - hour=signal.hour, - minute=signal.minute, - second=signal.second) - Utils.print_info("Add synapse name \"%s\" to the scheduler: %s" % (synapse.name, my_cron)) - self.scheduler.add_job(self.run_synapse_by_name, my_cron, args=[synapse.name]) - - @staticmethod - def run_synapse_by_name(synapse_name): - """ - This method will run the synapse - """ - Utils.print_info("Event triggered, running synapse: %s" % synapse_name) - # get a brain - brain_loader = BrainLoader() - brain = brain_loader.brain - SynapseLauncher.start_synapse_by_name(synapse_name, brain=brain) diff --git a/kalliope/core/LIFOBuffer.py b/kalliope/core/LIFOBuffer.py index 5f5080c9..2d72500d 100644 --- a/kalliope/core/LIFOBuffer.py +++ b/kalliope/core/LIFOBuffer.py @@ -1,5 +1,7 @@ import logging +from six import with_metaclass +from kalliope.core.Cortex import Cortex from kalliope.core.NeuronLauncher import NeuronLauncher from kalliope.core.Models import Singleton from kalliope.core.Models.APIResponse import APIResponse @@ -23,7 +25,7 @@ class SynapseListAddedToLIFO(Exception): pass -class LIFOBuffer(object): +class LIFOBuffer(with_metaclass(Singleton, object)): """ This class is a LIFO list of synapse to process where the last synapse list to enter will be the first synapse list to be processed. @@ -32,56 +34,55 @@ class LIFOBuffer(object): like with the Neurotransmitter neuron. """ - __metaclass__ = Singleton - api_response = APIResponse() - lifo_list = list() - logger.debug("[LIFOBuffer] LIFO buffer created") - answer = None - is_api_call = False - no_voice = False + def __init__(self): + logger.debug("[LIFOBuffer] LIFO buffer created") + self.api_response = APIResponse() + self.lifo_list = list() + self.answer = None + self.is_api_call = False + self.no_voice = False + self.is_running = False + self.reset_lifo = False - @classmethod - def set_answer(cls, value): - cls.answer = value + def set_answer(self, value): + self.answer = value - @classmethod - def set_api_call(cls, value): - cls.is_api_call = value + def set_api_call(self, value): + self.is_api_call = value - @classmethod - def add_synapse_list_to_lifo(cls, matched_synapse_list): + def add_synapse_list_to_lifo(self, matched_synapse_list, high_priority=False): """ Add a synapse list to process to the lifo :param matched_synapse_list: List of Matched Synapse + :param high_priority: If True, the synapse list added is executed directly :return: """ logger.debug("[LIFOBuffer] Add a new synapse list to process to the LIFO") - cls.lifo_list.append(matched_synapse_list) + self.lifo_list.append(matched_synapse_list) + if high_priority: + self.reset_lifo = True - @classmethod - def clean(cls): + def clean(self): """ Clean the LIFO by creating a new list """ - cls.lifo_list = list() - cls.api_response = APIResponse() + self.lifo_list = list() + self.api_response = APIResponse() - @classmethod - def _return_serialized_api_response(cls): + def _return_serialized_api_response(self): """ Serialize Exception has been raised by the execute process somewhere, return the serialized API response to the caller. Clean up the APIResponse object for the next call :return: """ # we prepare a json response - returned_api_response = cls.api_response.serialize() + returned_api_response = self.api_response.serialize() # we clean up the API response object for the next call - cls.api_response = APIResponse() + self.api_response = APIResponse() return returned_api_response - @classmethod - def execute(cls, answer=None, is_api_call=False, no_voice=False): + def execute(self, answer=None, is_api_call=False, no_voice=False): """ Process the LIFO list. @@ -97,29 +98,34 @@ def execute(cls, answer=None, is_api_call=False, no_voice=False): :return: serialized APIResponse object """ # store the answer if present - cls.answer = answer - cls.is_api_call = is_api_call - cls.no_voice = no_voice - - try: - # we keep looping over the LIFO til we have synapse list to process in it - while cls.lifo_list: - logger.debug("[LIFOBuffer] number of synapse list to process: %s" % len(cls.lifo_list)) - try: - # get the last list of matched synapse in the LIFO - last_synapse_fifo_list = cls.lifo_list[-1] - cls._process_synapse_list(last_synapse_fifo_list) - except SynapseListAddedToLIFO: - continue - # remove the synapse list from the LIFO - cls.lifo_list.remove(last_synapse_fifo_list) - raise Serialize - - except Serialize: - return cls._return_serialized_api_response() - - @classmethod - def _process_synapse_list(cls, synapse_list): + self.answer = answer + self.is_api_call = is_api_call + self.no_voice = no_voice + + if not self.is_running: + self.is_running = True + + try: + # we keep looping over the LIFO til we have synapse list to process in it + while self.lifo_list: + logger.debug("[LIFOBuffer] number of synapse list to process: %s" % len(self.lifo_list)) + try: + # get the last list of matched synapse in the LIFO + last_synapse_fifo_list = self.lifo_list[-1] + self._process_synapse_list(last_synapse_fifo_list) + except SynapseListAddedToLIFO: + continue + # remove the synapse list from the LIFO + self.lifo_list.remove(last_synapse_fifo_list) + # clean the cortex from value loaded from order as all synapses have been processed + Cortex.clean_parameter_from_order() + self.is_running = False + raise Serialize + + except Serialize: + return self._return_serialized_api_response() + + def _process_synapse_list(self, synapse_list): """ Process a list of matched synapse. Execute each neuron list for each synapse. @@ -133,16 +139,15 @@ def _process_synapse_list(cls, synapse_list): matched_synapse = synapse_list[0] # add the synapse to the API response so the user will get the status if the synapse was not already # in the response - if matched_synapse not in cls.api_response.list_processed_matched_synapse: - cls.api_response.list_processed_matched_synapse.append(matched_synapse) + if matched_synapse not in self.api_response.list_processed_matched_synapse: + self.api_response.list_processed_matched_synapse.append(matched_synapse) - cls._process_neuron_list(matched_synapse=matched_synapse) + self._process_neuron_list(matched_synapse=matched_synapse) # The synapse has been processed we can remove it from the list. synapse_list.remove(matched_synapse) - @classmethod - def _process_neuron_list(cls, matched_synapse): + def _process_neuron_list(self, matched_synapse): """ Process the neuron list of the matched_synapse Execute the Neuron @@ -162,25 +167,26 @@ def _process_neuron_list(cls, matched_synapse): # get the first neuron in the FIFO neuron list neuron = matched_synapse.neuron_fifo_list[0] # from here, we are back into the last neuron we were processing. - if cls.answer is not None: # we give the answer if exist to the first neuron - neuron.parameters["answer"] = cls.answer + if self.answer is not None: # we give the answer if exist to the first neuron + neuron.parameters["answer"] = self.answer # the next neuron should not get this answer - cls.answer = None + self.answer = None # todo fix this when we have a full client/server call. The client would be the voice or api call - neuron.parameters["is_api_call"] = cls.is_api_call - neuron.parameters["no_voice"] = cls.no_voice - logger.debug("[LIFOBuffer] process_neuron_list: is_api_call: %s, no_voice: %s" % (cls.is_api_call, - cls.no_voice)) + neuron.parameters["is_api_call"] = self.is_api_call + neuron.parameters["no_voice"] = self.no_voice + logger.debug("[LIFOBuffer] process_neuron_list: is_api_call: %s, no_voice: %s" % (self.is_api_call, + self.no_voice)) # execute the neuron instantiated_neuron = NeuronLauncher.start_neuron(neuron=neuron, parameters_dict=matched_synapse.parameters) # the status of an execution is "complete" if no neuron are waiting for an answer - cls.api_response.status = "complete" + self.api_response.status = "complete" if instantiated_neuron is not None: if instantiated_neuron.is_waiting_for_answer: # the neuron is waiting for an answer logger.debug("[LIFOBuffer] Wait for answer mode") - cls.api_response.status = "waiting_for_answer" + self.api_response.status = "waiting_for_answer" + self.is_running = False raise Serialize else: logger.debug("[LIFOBuffer] complete mode") @@ -190,12 +196,11 @@ def _process_neuron_list(cls, matched_synapse): # the neuron is fully processed we can remove it from the list matched_synapse.neuron_fifo_list.remove(neuron) - if instantiated_neuron.pending_synapse: # the last executed neuron want to run a synapse + if self.reset_lifo: # the last executed neuron want to run a synapse logger.debug("[LIFOBuffer] Last executed neuron want to run a synapse. Restart the LIFO") - # add the synapse to the lifo (inside a list as expected by the lifo) - cls.add_synapse_list_to_lifo([instantiated_neuron.pending_synapse]) # we have added a list of synapse to the LIFO ! this one must start over. # break all while loop until the execution is back to the LIFO loop + self.reset_lifo = False raise SynapseListAddedToLIFO else: raise Serialize diff --git a/kalliope/core/Models/Event.py b/kalliope/core/Models/Event.py deleted file mode 100644 index b482dd1c..00000000 --- a/kalliope/core/Models/Event.py +++ /dev/null @@ -1,49 +0,0 @@ -class Event(object): - """ - This Class is representing an Event which is raised by when the System at some defined time. - - .. note:: Events are based on the system crontab - """ - - def __init__(self, year=None, month=None, day=None, week=None, day_of_week=None, - hour=None, minute=None, second=None): - self.year = year - self.month = month - self.day = day - self.week = week - self.day_of_week = day_of_week - self.hour = hour - self.minute = minute - self.second = second - - def __str__(self): - return str(self.serialize()) - - def serialize(self): - """ - This method allows to serialize in a proper way this object - - :return: A dict of name / period - :rtype: Dict - """ - - return { - 'event': { - "year": self.year, - "month": self.month, - "day": self.day, - "week": self.week, - "day_of_week": self.day_of_week, - "hour": self.hour, - "minute": self.minute, - "second": self.second, - } - } - - def __eq__(self, other): - """ - This is used to compare 2 objects - :param other: - :return: - """ - return self.__dict__ == other.__dict__ diff --git a/kalliope/core/Models/MatchedSynapse.py b/kalliope/core/Models/MatchedSynapse.py index 9980c2c0..bbdc1fb2 100644 --- a/kalliope/core/Models/MatchedSynapse.py +++ b/kalliope/core/Models/MatchedSynapse.py @@ -28,9 +28,8 @@ def __init__(self, matched_synapse=None, matched_order=None, user_order=None, ov self.parameters = NeuronParameterLoader.get_parameters(synapse_order=self.matched_order, user_order=user_order) if overriding_parameter is not None: - # we suppose that we don't have any parameters. - # We replace the current parameter object with the received one - self.parameters = overriding_parameter + # merge dict of parameters with overriding + self.parameters.update(overriding_parameter) # list of Neuron Module self.neuron_module_list = list() diff --git a/kalliope/core/Models/Order.py b/kalliope/core/Models/Order.py deleted file mode 100644 index 353768b7..00000000 --- a/kalliope/core/Models/Order.py +++ /dev/null @@ -1,32 +0,0 @@ -class Order(object): - """ - This Class is representing an Order which is raised by when an entry (Vocal/REST/ anything ...) is matching it. - - .. note:: Order are defined in the brain file for each synapse. - """ - - def __init__(self, sentence): - self.sentence = sentence - - def __str__(self): - return str(self.serialize()) - - def serialize(self): - """ - This method allows to serialize in a proper way this object - - :return: A dict of order - :rtype: Dict - """ - - return { - 'order': self.sentence - } - - def __eq__(self, other): - """ - This is used to compare 2 objects - :param other: - :return: - """ - return self.__dict__ == other.__dict__ diff --git a/kalliope/core/Models/RecognitionOptions.py b/kalliope/core/Models/RecognitionOptions.py new file mode 100644 index 00000000..452e9f71 --- /dev/null +++ b/kalliope/core/Models/RecognitionOptions.py @@ -0,0 +1,27 @@ +class RecognitionOptions(object): + """ + This Class is representing a Speech To Text (STT) Recognition elements with name and parameters + + .. note:: must be defined in the settings.yml + """ + + def __init__(self, energy_threshold=4000, adjust_for_ambient_noise_second=0): + self.energy_threshold = energy_threshold + self.adjust_for_ambient_noise_second = adjust_for_ambient_noise_second + + def __str__(self): + return str(self.serialize()) + + def serialize(self): + return { + 'energy_threshold': self.energy_threshold, + 'adjust_for_ambient_noise_second': self.adjust_for_ambient_noise_second + } + + def __eq__(self, other): + """ + This is used to compare 2 objects + :param other: + :return: + """ + return self.__dict__ == other.__dict__ diff --git a/kalliope/core/Models/Resources.py b/kalliope/core/Models/Resources.py index c16e0451..da5f8e1e 100644 --- a/kalliope/core/Models/Resources.py +++ b/kalliope/core/Models/Resources.py @@ -4,11 +4,12 @@ class Resources(object): """ """ - def __init__(self, neuron_folder=None, stt_folder=None, tts_folder=None, trigger_folder=None): + def __init__(self, neuron_folder=None, stt_folder=None, tts_folder=None, trigger_folder=None, signal_folder=None): self.neuron_folder = neuron_folder self.stt_folder = stt_folder self.tts_folder = tts_folder self.trigger_folder = trigger_folder + self.signal_folder = signal_folder def __str__(self): return str(self.serialize()) @@ -25,7 +26,8 @@ def serialize(self): 'neuron_folder': self.neuron_folder, 'stt_folder': self.stt_folder, 'tts_folder': self.tts_folder, - 'trigger_folder': self.trigger_folder + 'trigger_folder': self.trigger_folder, + 'signal_folder': self.signal_folder } def __eq__(self, other): diff --git a/kalliope/core/Models/Settings.py b/kalliope/core/Models/Settings.py index 84bfce14..33d93721 100644 --- a/kalliope/core/Models/Settings.py +++ b/kalliope/core/Models/Settings.py @@ -27,7 +27,8 @@ def __init__(self, default_synapse=None, resources=None, variables=None, - rpi_settings=None): + rpi_settings=None, + recognition_options=None): self.default_tts_name = default_tts_name self.default_stt_name = default_stt_name @@ -50,6 +51,7 @@ def __init__(self, self.machine = platform.machine() # can be x86_64 or armv7l self.kalliope_version = current_kalliope_version self.rpi_settings = rpi_settings + self.recognition_options = recognition_options def serialize(self): """ @@ -80,7 +82,8 @@ def serialize(self): 'variables': self.variables, 'machine': self.machine, 'kalliope_version': self.kalliope_version, - 'rpi_settings': self.rpi_settings.serialize() if self.rpi_settings is not None else None + 'rpi_settings': self.rpi_settings.serialize() if self.rpi_settings is not None else None, + 'recognition_options': self.recognition_options.serialize() if self.recognition_options is not None else None, } def __str__(self): diff --git a/kalliope/core/Models/Signal.py b/kalliope/core/Models/Signal.py new file mode 100644 index 00000000..6e9cadea --- /dev/null +++ b/kalliope/core/Models/Signal.py @@ -0,0 +1,51 @@ +class Signal(object): + """ + This Class is representing a Signal which is corresponding to an input action that should start executing neuron + list when triggered + """ + + def __init__(self, name=None, parameters=None): + self.name = name + self.parameters = parameters + + def serialize(self): + """ + This method allows to serialize in a proper way this object + + :return: A dict of name and parameters + :rtype: Dict + """ + return { + 'name': self.name, + 'parameters': self.parameters + } + + def __str__(self): + """ + Return a string that describe the signal. If a parameter contains the word "password", + the output of this parameter will be masked in order to not appears in clean in the console + :return: string description of the neuron + """ + returned_dict = { + 'name': self.name, + 'parameters': self.parameters + } + + cleaned_parameters = dict() + if isinstance(self.parameters, dict): + for key, value in self.parameters.items(): + if "password" in key: + cleaned_parameters[key] = "*****" + else: + cleaned_parameters[key] = value + returned_dict["parameters"] = cleaned_parameters + + return str(returned_dict) + + def __eq__(self, other): + """ + This is used to compare 2 objects + :param other: + :return: + """ + return self.__dict__ == other.__dict__ diff --git a/kalliope/core/Models/__init__.py b/kalliope/core/Models/__init__.py index 0c1ea5b4..fde6b7db 100644 --- a/kalliope/core/Models/__init__.py +++ b/kalliope/core/Models/__init__.py @@ -1,8 +1,7 @@ from .Singleton import Singleton -from .Event import Event from .Resources import Resources from .Brain import Brain -from .Order import Order from .Synapse import Synapse from .Neuron import Neuron from .RpiSettings import RpiSettings +from .Signal import Signal diff --git a/kalliope/core/NeuronExceptions.py b/kalliope/core/NeuronExceptions.py new file mode 100644 index 00000000..7ed094d5 --- /dev/null +++ b/kalliope/core/NeuronExceptions.py @@ -0,0 +1,2 @@ +class NeuronExceptions(Exception): + pass diff --git a/kalliope/core/NeuronLauncher.py b/kalliope/core/NeuronLauncher.py index ce0d9663..947e9336 100644 --- a/kalliope/core/NeuronLauncher.py +++ b/kalliope/core/NeuronLauncher.py @@ -1,10 +1,12 @@ import logging -import six + import jinja2 -import sys +import six -from kalliope.core.Utils.Utils import Utils from kalliope.core.ConfigurationManager.SettingLoader import SettingLoader +from kalliope.core.Cortex import Cortex +from kalliope.core.NeuronExceptions import NeuronExceptions +from kalliope.core.Utils.Utils import Utils logging.basicConfig() logger = logging.getLogger("kalliope") @@ -28,8 +30,7 @@ def launch_neuron(cls, neuron): :return: """ logger.debug("Run neuron: \"%s\"" % (neuron.__str__())) - sl = SettingLoader() - settings = sl.settings + settings = cls.load_settings() neuron_folder = None if settings.resources: neuron_folder = settings.resources.neuron_folder @@ -52,9 +53,14 @@ def start_neuron(cls, neuron, parameters_dict=None): try: neuron.parameters = cls._replace_brackets_by_loaded_parameter(neuron.parameters, parameters_dict) except NeuronParameterNotAvailable: - Utils.print_danger("The neuron %s cannot be launched" % neuron.name) + Utils.print_danger("Missing parameter in neuron %s. Execution skipped" % neuron.name) return None - instantiated_neuron = NeuronLauncher.launch_neuron(neuron) + try: + instantiated_neuron = NeuronLauncher.launch_neuron(neuron) + except NeuronExceptions as e: + Utils.print_danger("ERROR: Fail to execute neuron '%s'. " + '%s' ". -> Execution skipped" % (neuron.name, e.message)) + return None return instantiated_neuron @classmethod @@ -67,11 +73,21 @@ def _replace_brackets_by_loaded_parameter(cls, neuron_parameters, loaded_paramet :param loaded_parameters: dict of parameters """ logger.debug("[NeuronLauncher] replacing brackets from %s, using %s" % (neuron_parameters, loaded_parameters)) + # add variables from the short term memory to the list of loaded parameters that can be used in a template + # the final dict is added into a key "kalliope_memory" to not override existing keys loaded form the order + memory_dict = dict() + memory_dict["kalliope_memory"] = Cortex.get_memory() + if loaded_parameters is None: + loaded_parameters = dict() # instantiate an empty dict in order to be able to add memory in it + loaded_parameters.update(memory_dict) if isinstance(neuron_parameters, str) or isinstance(neuron_parameters, six.text_type): # replace bracket parameter only if the str contains brackets if Utils.is_containing_bracket(neuron_parameters): # check that the parameter to replace is available in the loaded_parameters dict if cls._neuron_parameters_are_available_in_loaded_parameters(neuron_parameters, loaded_parameters): + # add parameters from global variable into the final loaded parameter dict + settings = cls.load_settings() + loaded_parameters.update(settings.variables) neuron_parameters = jinja2.Template(neuron_parameters).render(loaded_parameters) neuron_parameters = Utils.encode_text_utf8(neuron_parameters) return str(neuron_parameters) @@ -82,7 +98,9 @@ def _replace_brackets_by_loaded_parameter(cls, neuron_parameters, loaded_paramet if isinstance(neuron_parameters, dict): returned_dict = dict() for key, value in neuron_parameters.items(): - if key in "say_template" or key in "file_template": # those keys are reserved for the TTS. + # following keys are reserved by kalliope core + if key in "say_template" or key in "file_template" or key in "kalliope_memory" \ + or key in "from_answer_link": returned_dict[key] = value else: returned_dict[key] = cls._replace_brackets_by_loaded_parameter(value, loaded_parameters) @@ -121,3 +139,12 @@ def _neuron_parameters_are_available_in_loaded_parameters(string_parameters, loa Utils.print_danger("The parameter %s is not available in the order" % str(parameter)) return False return True + + @staticmethod + def load_settings(): + """ + Return loaded kalliope settings + :return: setting object + """ + sl = SettingLoader() + return sl.settings diff --git a/kalliope/core/NeuronModule.py b/kalliope/core/NeuronModule.py index 4a3a60d8..5ced5a36 100644 --- a/kalliope/core/NeuronModule.py +++ b/kalliope/core/NeuronModule.py @@ -2,13 +2,16 @@ import logging import random import sys -import six +import six from jinja2 import Template from kalliope.core import OrderListener from kalliope.core.ConfigurationManager import SettingLoader, BrainLoader +from kalliope.core.Cortex import Cortex +from kalliope.core.LIFOBuffer import LIFOBuffer from kalliope.core.Models.MatchedSynapse import MatchedSynapse +from kalliope.core.NeuronExceptions import NeuronExceptions from kalliope.core.OrderAnalyser import OrderAnalyser from kalliope.core.Utils.RpiUtils import RpiUtils from kalliope.core.Utils.Utils import Utils @@ -17,18 +20,23 @@ logger = logging.getLogger("kalliope") -class InvalidParameterException(Exception): +class InvalidParameterException(NeuronExceptions): """ Some Neuron parameters are invalid. """ - pass + def __init__(self, message): + # Call the base class constructor with the parameters it needs + super(InvalidParameterException, self).__init__(message) -class MissingParameterException(Exception): +class MissingParameterException(NeuronExceptions): """ Some Neuron parameters are missing. """ - pass + + def __init__(self, message): + # Call the base class constructor with the parameters it needs + super(MissingParameterException, self).__init__(message) class NoTemplateException(Exception): @@ -103,6 +111,10 @@ def __init__(self, **kwargs): self.is_waiting_for_answer = False # the synapse name to add the the buffer self.pending_synapse = None + # a dict of parameters the user ask to save in short term memory + self.kalliope_memory = kwargs.get('kalliope_memory', None) + # parameters loaded from the order can be save now + Cortex.save_parameter_from_order_in_memory(self.kalliope_memory) def __str__(self): retuned_string = "" @@ -137,6 +149,9 @@ def say(self, message): tts_message = None + # we can save parameters from the neuron in memory + Cortex.save_neuron_parameter_in_memory(self.kalliope_memory, message) + if isinstance(message, str) or isinstance(message, six.text_type): logger.debug("[NeuronModule] message is string") tts_message = message @@ -185,7 +200,6 @@ def _get_message_from_dict(self, message_dict): .. raises:: TemplateFileNotFoundException """ returned_message = None - # the user chooses a say_template option if self.say_template is not None: returned_message = self._get_say_template(self.say_template, message_dict) @@ -222,18 +236,30 @@ def _get_file_template(cls, file_template, message_dict): return returned_message - def run_synapse_by_name(self, synapse_name, user_order=None, synapse_order=None): + @staticmethod + def run_synapse_by_name(synapse_name, user_order=None, synapse_order=None, high_priority=False, + is_api_call=False, overriding_parameter_dict=None): """ call the lifo for adding a synapse to execute in the list of synapse list to process :param synapse_name: The name of the synapse to run :param user_order: The user order :param synapse_order: The synapse order + :param high_priority: If True, the synapse is executed before the end of the current synapse list + :param is_api_call: If true, the current call comes from the api + :param overriding_parameter_dict: dict of value to add to neuron parameters """ synapse = BrainLoader().get_brain().get_synapse_by_name(synapse_name) matched_synapse = MatchedSynapse(matched_synapse=synapse, matched_order=synapse_order, - user_order=user_order) - self.pending_synapse = matched_synapse + user_order=user_order, + overriding_parameter=overriding_parameter_dict) + + list_synapse_to_process = list() + list_synapse_to_process.append(matched_synapse) + # get the singleton + lifo_buffer = LIFOBuffer() + lifo_buffer.add_synapse_list_to_lifo(list_synapse_to_process, high_priority=high_priority) + lifo_buffer.execute(is_api_call=is_api_call) @staticmethod def is_order_matching(order_said, order_match): diff --git a/kalliope/core/NeuronParameterLoader.py b/kalliope/core/NeuronParameterLoader.py index ae526e42..999b905e 100644 --- a/kalliope/core/NeuronParameterLoader.py +++ b/kalliope/core/NeuronParameterLoader.py @@ -1,3 +1,4 @@ +from kalliope.core.Cortex import Cortex from kalliope.core.Utils import Utils import logging @@ -16,7 +17,9 @@ def get_parameters(cls, synapse_order, user_order): params = dict() if Utils.is_containing_bracket(synapse_order): params = cls._associate_order_params_to_values(user_order, synapse_order) - logger.debug("Parameters for order: %s" % params) + logger.debug("[NeuronParameterLoader.get_parameters]Parameters for order: %s" % params) + # we place the dict of parameters load from order into a cache in Cortex so the user can save it later + Cortex.add_parameters_from_order(params) return params @classmethod @@ -29,7 +32,7 @@ def _associate_order_params_to_values(cls, order, order_to_check): :type order: str :return: the dict corresponding to the key / value of the params """ - logger.debug("[OrderAnalyser._associate_order_params_to_values] user order: %s, " + logger.debug("[NeuronParameterLoader._associate_order_params_to_values] user order: %s, " "order from synapse: %s" % (order, order_to_check)) list_word_in_order = Utils.remove_spaces_in_brackets(order_to_check).split() @@ -54,7 +57,7 @@ def _associate_order_params_to_values(cls, order, order_to_check): dict_var[var_name] = " ".join(truncate_list_word_said) break for word_said in truncate_list_word_said: - if word_said == stop_value: + if word_said.lower() == stop_value.lower(): # Do not consider the case break if var_name in dict_var: dict_var[var_name] += " " + word_said diff --git a/kalliope/core/OrderAnalyser.py b/kalliope/core/OrderAnalyser.py index b54ed526..91b56fc3 100644 --- a/kalliope/core/OrderAnalyser.py +++ b/kalliope/core/OrderAnalyser.py @@ -1,13 +1,11 @@ # coding: utf8 import collections from collections import Counter -import sys import six from kalliope.core.Models.MatchedSynapse import MatchedSynapse from kalliope.core.Utils.Utils import Utils from kalliope.core.ConfigurationManager import SettingLoader -from kalliope.core.Models import Order import logging @@ -54,12 +52,13 @@ def get_matching_synapse(cls, order, brain=None): for synapse in cls.brain.synapses: # we are only concerned by synapse with a order type of signal for signal in synapse.signals: - if type(signal) == Order: - if cls.spelt_order_match_brain_order_via_table(signal.sentence, order): + + if signal.name == "order": + if cls.spelt_order_match_brain_order_via_table(signal.parameters, order): # the order match the synapse, we add it to the returned list logger.debug("Order found! Run synapse name: %s" % synapse.name) Utils.print_success("Order matched in the brain. Running synapse \"%s\"" % synapse.name) - list_match_synapse.append(synapse_order_tuple(synapse=synapse, order=signal.sentence)) + list_match_synapse.append(synapse_order_tuple(synapse=synapse, order=signal.parameters)) # create a list of MatchedSynapse from the tuple list list_synapse_to_process = list() diff --git a/kalliope/core/ResourcesManager.py b/kalliope/core/ResourcesManager.py index c72d0c59..9caef22a 100644 --- a/kalliope/core/ResourcesManager.py +++ b/kalliope/core/ResourcesManager.py @@ -32,7 +32,7 @@ TYPE_TTS = "tts" TYPE_STT = "stt" TYPE_TRIGGER = "trigger" - +TYPE_SIGNAL = "signal" class ResourcesManagerException(Exception): pass @@ -103,7 +103,11 @@ def install(self): % str(self.tmp_path)) shutil.rmtree(self.tmp_path) - def uninstall(self, neuron_name=None, tts_name=None, stt_name=None, trigger_name=None): + def uninstall(self, neuron_name=None, + tts_name=None, + stt_name=None, + trigger_name=None, + signal_name= None): """ Uninstall a community resource """ @@ -111,21 +115,26 @@ def uninstall(self, neuron_name=None, tts_name=None, stt_name=None, trigger_name module_name = "" if neuron_name is not None: target_path_to_delete = self._get_target_folder(resources=self.settings.resources, - module_type="neuron") + module_type=TYPE_NEURON) module_name = neuron_name if tts_name is not None: target_path_to_delete = self._get_target_folder(resources=self.settings.resources, - module_type="neuron") + module_type=TYPE_TTS) module_name = tts_name if stt_name is not None: target_path_to_delete = self._get_target_folder(resources=self.settings.resources, - module_type="neuron") + module_type=TYPE_STT) module_name = stt_name if trigger_name is not None: target_path_to_delete = self._get_target_folder(resources=self.settings.resources, - module_type="neuron") + module_type=TYPE_TRIGGER) module_name = trigger_name + if signal_name is not None: + target_path_to_delete = self._get_target_folder(resources=self.settings.resources, + module_type=TYPE_SIGNAL) + module_name = signal_name + if target_path_to_delete is not None: try: shutil.rmtree(target_path_to_delete + os.sep + module_name.lower()) @@ -173,6 +182,11 @@ def is_settings_ok(resources, dna): logger.debug(message) Utils.print_danger(message) settings_ok = False + if dna.module_type == "signal" and resources.signal_folder is None: + message = "Resources folder for signal installation not set in settings, cannot install." + logger.debug(message) + Utils.print_danger(message) + settings_ok = False return settings_ok @@ -201,7 +215,7 @@ def _get_target_folder(resources, module_type): Return the folder from the resources and given a module type :param resources: Resource object :type resources: Resources - :param module_type: type of the module (TYPE_NEURON, TYPE_STT, TYPE_TTS, TYPE_TRIGGER) + :param module_type: type of the module (TYPE_NEURON, TYPE_STT, TYPE_TTS, TYPE_TRIGGER, TYPE_SIGNAL) :return: path of the folder """ module_type_converter = dict() @@ -211,7 +225,8 @@ def _get_target_folder(resources, module_type): TYPE_NEURON: resources.neuron_folder, TYPE_STT: resources.stt_folder, TYPE_TTS: resources.tts_folder, - TYPE_TRIGGER: resources.trigger_folder + TYPE_TRIGGER: resources.trigger_folder, + TYPE_SIGNAL: resources.signal_folder } except AttributeError: # will be raised if the resource folder is not set in settings diff --git a/kalliope/core/RestAPI/FlaskAPI.py b/kalliope/core/RestAPI/FlaskAPI.py index 49ae0318..2cba79b3 100644 --- a/kalliope/core/RestAPI/FlaskAPI.py +++ b/kalliope/core/RestAPI/FlaskAPI.py @@ -9,6 +9,7 @@ from flask_restful import abort from werkzeug.utils import secure_filename +from kalliope import SignalLauncher from kalliope._version import version_str from kalliope.core.ConfigurationManager import SettingLoader, BrainLoader from kalliope.core.LIFOBuffer import LIFOBuffer @@ -17,6 +18,7 @@ from kalliope.core.RestAPI.utils import requires_auth from kalliope.core.SynapseLauncher import SynapseLauncher from kalliope.core.Utils.FileManager import FileManager +from kalliope.signals.order import Order logging.basicConfig() logger = logging.getLogger("kalliope") @@ -72,6 +74,8 @@ def __init__(self, app, port=5000, brain=None, allowed_cors_origin=False): self.app.add_url_rule('/synapses/start/order', view_func=self.run_synapse_by_order, methods=['POST']) self.app.add_url_rule('/synapses/start/audio', view_func=self.run_synapse_by_audio, methods=['POST']) self.app.add_url_rule('/shutdown/', view_func=self.shutdown_server, methods=['POST']) + self.app.add_url_rule('/mute/', view_func=self.get_mute, methods=['GET']) + self.app.add_url_rule('/mute/', view_func=self.set_mute, 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) @@ -157,7 +161,7 @@ def run_synapse_by_name(self, synapse_name): synapse_target = BrainLoader().get_brain().get_synapse_by_name(synapse_name=synapse_name) # get no_voice_flag if present - no_voice = self.get_no_voice_flag_from_request(request) + no_voice = self.get_boolean_flag_from_request(request, boolean_flag_to_find="no_voice") # get parameters parameters = self.get_parameters_from_request(request) @@ -204,7 +208,7 @@ def run_synapse_by_order(self): order = request.get_json('order') # get no_voice_flag if present - no_voice = self.get_no_voice_flag_from_request(request) + no_voice = self.get_boolean_flag_from_request(request, boolean_flag_to_find="no_voice") if order is not None: # get the order order_to_run = order["order"] @@ -307,6 +311,59 @@ def shutdown_server(self): func() return "Shutting down..." + @requires_auth + def get_mute(self): + """ + Return the current trigger status + + Curl test + curl -i --user admin:secret -X GET http://127.0.0.1:5000/mute + """ + + # find the order signal and call the mute method + signal_order = SignalLauncher.get_order_instance() + if signal_order is not None: + data = { + "mute": signal_order.get_mute_status() + } + return jsonify(data), 200 + + # if no Order instance + data = { + "error": "Mute status unknow" + } + return jsonify(error=data), 400 + + @requires_auth + def set_mute(self): + """ + Set the trigger status (muted or not) + + Curl test: + curl -i -H "Content-Type: application/json" --user admin:secret -X POST \ + -d '{"mute": "True"}' http://127.0.0.1:5000/mute + """ + + if not request.get_json() or 'mute' not in request.get_json(): + abort(400) + + # get mute if present + mute = self.get_boolean_flag_from_request(request, boolean_flag_to_find="mute") + + # find the order signal and call the mute method + signal_order = SignalLauncher.get_order_instance() + if signal_order is not None: + signal_order.set_mute_status(mute) + data = { + "mute": signal_order.get_mute_status() + } + return jsonify(data), 200 + + data = { + "error": "Cannot switch mute status" + } + return jsonify(error=data), 400 + def audio_analyser_callback(self, order): """ Callback of the OrderListener. Called after the processing of the audio file @@ -329,23 +386,23 @@ def audio_analyser_callback(self, order): # this boolean will notify the main process that the order have been processed self.order_analyser_return = True - def get_no_voice_flag_from_request(self, http_request): + def get_boolean_flag_from_request(self, http_request, boolean_flag_to_find): """ - Get the no_voice flag from the request if exist + Get the boolean flag from the request if exist :param http_request: - :return: + :param boolean_flag_to_find: json flag to find in the http_request + :return: True or False if the boolean flag has been found in the request """ - - no_voice = False + boolean_flag = False try: received_json = http_request.get_json(force=True, silent=True, cache=True) - if 'no_voice' in received_json: - no_voice = self.str_to_bool(received_json['no_voice']) + if boolean_flag_to_find in received_json: + boolean_flag = self.str_to_bool(received_json[boolean_flag_to_find]) except TypeError: # no json received pass - logger.debug("[FlaskAPI] no_voice: %s" % no_voice) - return no_voice + logger.debug("[FlaskAPI] Boolean %s : %s" % (boolean_flag_to_find, boolean_flag)) + return boolean_flag @staticmethod def str_to_bool(s): diff --git a/kalliope/core/ShellGui.py b/kalliope/core/ShellGui.py index 2719dcf2..696b17ed 100644 --- a/kalliope/core/ShellGui.py +++ b/kalliope/core/ShellGui.py @@ -2,15 +2,12 @@ import locale import logging -import signal -import sys from dialog import Dialog from kalliope.core import OrderListener from kalliope.core.ConfigurationManager import SettingLoader from kalliope.core.SynapseLauncher import SynapseLauncher -from kalliope.core.Utils.Utils import Utils from kalliope.neurons.say.say import Say logging.basicConfig() diff --git a/kalliope/core/SignalLauncher.py b/kalliope/core/SignalLauncher.py new file mode 100644 index 00000000..3484cfbb --- /dev/null +++ b/kalliope/core/SignalLauncher.py @@ -0,0 +1,54 @@ +import logging + +from kalliope import Utils +from kalliope.signals.order import Order + +logging.basicConfig() +logger = logging.getLogger("kalliope") + + +class SignalLauncher: + + # keep a list of instantiated signals + list_launched_signals = list() + + def __init__(self): + pass + + @classmethod + def launch_signal_class_by_name(cls, signal_name, settings=None): + """ + load the signal class from the given name, pass the brain and settings to the signal + :param signal_name: name of the signal class to load + :param settings: Settings Object + """ + signal_folder = None + if settings.resources: + signal_folder = settings.resources.signal_folder + + launched_signal = Utils.get_dynamic_class_instantiation(package_name="signals", + module_name=signal_name, + resources_dir=signal_folder) + + cls.add_launched_signals_to_list(launched_signal) + + return launched_signal + + @classmethod + def add_launched_signals_to_list(cls, signal): + cls.list_launched_signals.append(signal) + + @classmethod + def get_launched_signals_list(cls): + return cls.list_launched_signals + + @classmethod + def get_order_instance(cls): + """ + Return the Order instance from the list of launched signals if exist + :return: + """ + for signal in cls.list_launched_signals: + if isinstance(signal, Order): + return signal + return None diff --git a/kalliope/core/SynapseLauncher.py b/kalliope/core/SynapseLauncher.py index c208470a..0557da57 100644 --- a/kalliope/core/SynapseLauncher.py +++ b/kalliope/core/SynapseLauncher.py @@ -23,11 +23,12 @@ class SynapseNameNotFound(Exception): class SynapseLauncher(object): @classmethod - def start_synapse_by_name(cls, name, brain=None): + def start_synapse_by_name(cls, name, brain=None, overriding_parameter_dict=None): """ Start a synapse by it's name :param name: Name (Unique ID) of the synapse to launch :param brain: Brain instance + :param overriding_parameter_dict: parameter to pass to neurons """ logger.debug("[SynapseLauncher] start_synapse_by_name called with synapse name: %s " % name) # check if we have found and launched the synapse @@ -41,7 +42,8 @@ def start_synapse_by_name(cls, name, brain=None): list_synapse_to_process = list() new_matching_synapse = MatchedSynapse(matched_synapse=synapse, matched_order=None, - user_order=None) + user_order=None, + overriding_parameter=overriding_parameter_dict) list_synapse_to_process.append(new_matching_synapse) lifo_buffer.add_synapse_list_to_lifo(list_synapse_to_process) return lifo_buffer.execute(is_api_call=True) diff --git a/kalliope/core/Utils/Utils.py b/kalliope/core/Utils/Utils.py index 916a5a95..ac582257 100644 --- a/kalliope/core/Utils/Utils.py +++ b/kalliope/core/Utils/Utils.py @@ -44,42 +44,42 @@ class Utils(object): @classmethod def print_info(cls, text_to_print): pipe_print(cls.color_list["BLUE"] + text_to_print + cls.color_list["ENDLINE"]) - logger.info(text_to_print) + logger.debug(text_to_print) @classmethod def print_success(cls, text_to_print): pipe_print(cls.color_list["GREEN"] + text_to_print + cls.color_list["ENDLINE"]) - logger.info(text_to_print) + logger.debug(text_to_print) @classmethod def print_warning(cls, text_to_print): pipe_print(cls.color_list["YELLOW"] + text_to_print + cls.color_list["ENDLINE"]) - logger.info(text_to_print) + logger.debug(text_to_print) @classmethod def print_danger(cls, text_to_print): pipe_print(cls.color_list["RED"] + text_to_print + cls.color_list["ENDLINE"]) - logger.info(text_to_print) + logger.debug(text_to_print) @classmethod def print_header(cls, text_to_print): pipe_print(cls.color_list["HEADER"] + text_to_print + cls.color_list["ENDLINE"]) - logger.info(text_to_print) + logger.debug(text_to_print) @classmethod def print_purple(cls, text_to_print): pipe_print(cls.color_list["PURPLE"] + text_to_print + cls.color_list["ENDLINE"]) - logger.info(text_to_print) + logger.debug(text_to_print) @classmethod def print_bold(cls, text_to_print): pipe_print(cls.color_list["BOLD"] + text_to_print + cls.color_list["ENDLINE"]) - logger.info(text_to_print) + logger.debug(text_to_print) @classmethod def print_underline(cls, text_to_print): pipe_print(cls.color_list["UNDERLINE"] + text_to_print + cls.color_list["ENDLINE"]) - logger.info(text_to_print) + logger.debug(text_to_print) @staticmethod def print_yaml_nicely(to_print): diff --git a/kalliope/core/__init__.py b/kalliope/core/__init__.py index 4b6b7b0c..50f0bb09 100755 --- a/kalliope/core/__init__.py +++ b/kalliope/core/__init__.py @@ -10,5 +10,3 @@ from kalliope.core.NeuronParameterLoader import NeuronParameterLoader from kalliope.core.NeuronModule import NeuronModule from kalliope.core.PlayerModule import PlayerModule -from kalliope.core.MainController import MainController -from kalliope.core.EventManager import EventManager diff --git a/kalliope/neurons/mqtt_publisher/README.md b/kalliope/neurons/mqtt_publisher/README.md new file mode 100644 index 00000000..b5a122b5 --- /dev/null +++ b/kalliope/neurons/mqtt_publisher/README.md @@ -0,0 +1,145 @@ +# MQTT Publisher + +## Synopsis + +Publish a message to a MQTT broker server + +## Installation + +This is a core neuron, no installation required. + +## Options + +| parameter | required | type | default | choices | comment | +|--------------|----------|---------|----------|---------------------|--------------------------------------------------------------------------------------------------| +| broker_ip | YES | string | | | IP address of the MQTT broker server | +| port | NO | int | 1883 | | Port of the broker. By default 1883. 8883 when TLS is activated. | +| topic | YES | string | | | Topic name where the message will be published | +| payload | YES | string | | | Message to publish on the topic | +| qos | NO | int | 0 | 0 or 1 or 2 | The quality of service level to use | +| retain | NO | Boolean | FALSE | True, False | if set to True, the message will be set as the “last known good”/retained message for the topic. | +| client_id | NO | string | kalliope | | The MQTT client id to use. If not set, the name will be set to "kalliope" | +| keepalive | NO | int | 60 | | The keepalive timeout value for the client | +| username | NO | string | | | username for authenticating the client | +| password | NO | string | | | password for authenticating the client | +| ca_cert | NO | string | | | Path to the remote server CA certificate used for securing the transport | +| certfile | NO | string | | | Path to the client certificate file used for authentication | +| keyfile | NO | string | | | Path to the client key file attached to the client certificate | +| protocol | NO | string | MQTTv311 | MQTTv31 or MQTTv311 | Can be either MQTTv31 or MQTTv311 | +| tls_insecure | NO | string | FALSE | | Set the verification of the server hostname in the server certificate | + +## Return Values + +No returned values + +## Synapses example + +Publish a message to the topic "my/topic" with minimal configuration +```yml +- name: "mqtt-publisher-1" + signals: + - order: "this is my order" + neurons: + - mqtt_publisher: + broker_ip: "127.0.0.1" + topic: "my/topic" + payload: "my message" +``` + +Publish a json formatted message. Note that anti-slashes must be escaped. +```yml +- name: "mqtt-publisher-2" + signals: + - order: "this is my order" + neurons: + - mqtt_publisher: + broker_ip: "127.0.0.1" + topic: "mytopic" + payload: "{\"mykey\": \"myvalue\"}" +``` + +The broker require authentication +```yml +- name: "mqtt-publisher-3" + signals: + - order: "this is my order" + neurons: + - mqtt_publisher: + broker_ip: "127.0.0.1" + topic: "my/topic" + payload: "my message" + username: "guest" + password: "guest" +``` + +The broker require a secure TLS connection +```yml +- name: "mqtt-publisher-4" + signals: + - order: "this is my order" + neurons: + - mqtt_publisher: + broker_ip: "127.0.0.1" + topic: "my/topic" + payload: "my message" + ca_cert: "/path/to/ca.cert" +``` + +The broker require a secure TLS connection and authentication based on client certificate +```yml +- name: "mqtt-publisher-5" + signals: + - order: "this is my order" + neurons: + - mqtt_publisher: + broker_ip: "127.0.0.1" + topic: "my/topic" + payload: "my message" + ca_cert: "/path/to/ca.cert" + certfile: "path/to/client.crt" + keyfile: "path/to/client.key" +``` + +The broker require a secure TLS connection, an authentication based on client certificate and the CA is a self signed certificate +```yml +- name: "mqtt-publisher-6" + signals: + - order: "this is my order" + neurons: + - mqtt_publisher: + broker_ip: "127.0.0.1" + topic: "my/topic" + payload: "my message" + ca_cert: "/path/to/ca.cert" + certfile: "path/to/client.crt" + keyfile: "path/to/client.key" + tls_insecure: True +``` + + +## Test with CLI + +The following part of the documentation can help you to configure your synapse with right options. +From here we suppose that you have already a running broker server on your local machine. If it's not the case, please refer to the documentation of the [signal mqtt_subscriber](../../signals/mqtt_subscriber) to install a testing broker server. + +Install a CLI mqtt client +```bash +sudo apt-get install mosquitto-clients +``` + +Run a subscriber +```bash +mosquitto_sub -t 'this/is/a/topic' +``` + +Then use your neuron. E.g +```yml +- name: "test-mqtt-publisher" + signals: + - order: "this is my order" + neurons: + - mqtt_publisher: + broker_ip: "127.0.0.1" + topic: "this/is/a/topic" + payload: "info" +``` diff --git a/kalliope/neurons/mqtt_publisher/__init__.py b/kalliope/neurons/mqtt_publisher/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kalliope/neurons/mqtt_publisher/mqtt_publisher.py b/kalliope/neurons/mqtt_publisher/mqtt_publisher.py new file mode 100644 index 00000000..04bcbffc --- /dev/null +++ b/kalliope/neurons/mqtt_publisher/mqtt_publisher.py @@ -0,0 +1,143 @@ +import logging +import socket + +import paho +import paho.mqtt.client as mqtt + +from kalliope.core.NeuronModule import NeuronModule + +logging.basicConfig() +logger = logging.getLogger("kalliope") + + +class Mqtt_publisher(NeuronModule): + def __init__(self, **kwargs): + super(Mqtt_publisher, self).__init__(**kwargs) + + logger.debug("[mqtt_publisher] neuron called with parameters: %s" % kwargs) + + # get parameters + self.broker_ip = kwargs.get('broker_ip', None) + self.port = kwargs.get('port', 1883) + self.topic = kwargs.get('topic', None) + self.payload = kwargs.get('payload', None) + self.qos = kwargs.get('qos', 0) + self.retain = kwargs.get('retain', False) + self.client_id = kwargs.get('client_id', 'kalliope') + self.keepalive = kwargs.get('keepalive', 60) + self.username = kwargs.get('username', None) + self.password = kwargs.get('password', None) + self.ca_cert = kwargs.get('ca_cert', None) + self.certfile = kwargs.get('certfile', None) + self.keyfile = kwargs.get('keyfile', None) + self.protocol = kwargs.get('protocol', 'MQTTv311') + self.tls_insecure = kwargs.get('tls_insecure', False) + + if not self._is_parameters_ok(): + logger.debug("[mqtt_publisher] One or more invalid parameters, neuron will not be launched") + else: + # string must be converted + self.protocol = self._get_protocol(self.protocol) + + self.client = mqtt.Client(client_id=self.broker_ip, protocol=self.protocol) + + if self.username is not None and self.password is not None: + logger.debug("[mqtt_publisher] Username and password are set") + self.client.username_pw_set(self.username, self.password) + + if self.ca_cert is not None and self.certfile is not None and self.keyfile is not None: + logger.debug("[mqtt_publisher] Active TLS with client certificate authentication") + self.client.tls_set(ca_certs=self.ca_cert, + certfile=self.certfile, + keyfile=self.keyfile) + self.client.tls_insecure_set(self.tls_insecure) + + elif self.ca_cert is not None: + logger.debug("[mqtt_publisher] Active TLS with server CA certificate only") + self.client.tls_set(ca_certs=self.ca_cert) + self.client.tls_insecure_set(self.tls_insecure) + + try: + self.client.connect(self.broker_ip, port=self.port, keepalive=self.keepalive) + self.client.publish(topic=self.topic, payload=self.payload, qos=int(self.qos), retain=self.retain) + logger.debug("[mqtt_publisher] Message published to topic %s: %s" % (self.topic, self.payload)) + self.client.disconnect() + except socket.error: + logger.debug("[mqtt_publisher] Unable to connect to broker %s" % self.broker_ip) + + def _is_parameters_ok(self): + if self.broker_ip is None: + print("[mqtt_publisher] ERROR: broker_ip is not set") + return False + + if self.port is not None: + if not isinstance(self.port, int): + try: + self.port = int(self.port) + except ValueError: + print("[mqtt_publisher] ERROR: port must be an integer") + return False + + if self.topic is None: + print("[mqtt_publisher] ERROR: topic is not set") + return False + + if self.payload is None: + print("[mqtt_publisher] ERROR: payload is not set") + return False + + if self.qos: + if not isinstance(self.qos, int): + try: + self.qos = int(self.qos) + except ValueError: + print("[mqtt_publisher] ERROR: qos must be an integer") + return False + if self.qos not in [0, 1, 2]: + print("[mqtt_publisher] ERROR: qos must be 0,1 or 2") + return False + + if self.keepalive: + if not isinstance(self.keepalive, int): + try: + self.keepalive = int(self.keepalive) + except ValueError: + print("[mqtt_publisher] ERROR: keepalive must be an integer") + return False + + if self.username is not None and self.password is None: + print("[mqtt_publisher] ERROR: password must be set when using username") + return False + if self.username is None and self.password is not None: + print("[mqtt_publisher] ERROR: username must be set when using password") + return False + + if self.protocol: + if self.protocol not in ["MQTTv31", "MQTTv311"]: + print("[mqtt_publisher] Invalid protocol value, fallback to MQTTv311") + self.protocol = "MQTTv311" + + # if the user set a certfile, the key and ca cert must be set to + if self.certfile is not None and self.keyfile is None: + print("[mqtt_publisher] ERROR: keyfile must be set when using certfile") + return False + if self.certfile is None and self.keyfile is not None: + print("[mqtt_publisher] ERROR: certfile must be set when using keyfile") + return False + + if self.certfile is not None and self.keyfile is not None: + if self.ca_cert is None: + print("[mqtt_publisher] ERROR: ca_cert must be set when using keyfile and certfile") + return False + + return True + + def _get_protocol(self, protocol): + """ + Return the right code depending on the given string protocol name + :param protocol: string name of the protocol to use. + :return: integer + """ + if protocol == "MQTTv31": + return paho.mqtt.client.MQTTv31 + return paho.mqtt.client.MQTTv311 diff --git a/kalliope/neurons/mute/README.md b/kalliope/neurons/mute/README.md new file mode 100644 index 00000000..3744952b --- /dev/null +++ b/kalliope/neurons/mute/README.md @@ -0,0 +1,52 @@ +# Mute + +## Synopsis + +Mute control of kalliope. If set to True the trigger process will be stopped. + +Once this neuron is used, and Kalliope muted, the hotword is deactivated. Only ways to unmute are: +- by calling the API (see [mute section](../../../Docs/rest_api.md#switch-mute-status)) +- If running on Raspberry, by using the unmute button. (See the section [Raspberry LED and mute button](../../../Docs/settings.md#raspberry-led-and-mute-button)) +- by using another signals than a "vocal order" that call back this neuron with a status set to "False" +- Restarting Kalliope + +## Options + +| parameter | required | type | default | choices | comment | +|-----------|----------|---------|---------|-------------|---------------------------------------------------| +| status | YES | Boolean | | True, False | If "True" Kalliope will stop the hotword process | + + +## Return Values + +Not returned values + +## Synapses example + +Mute Kalliope from a vocal order +```yml +- name: "mute-synapse" + signals: + - order: "stop listening" + neurons: + - say: + message: + - "I stop hearing you, sir" + - mute: + status: True +``` + +Unmute Kalliope from another signals. In the following example, a MQTT message is received +```yml +- name: "unmute-synapse" + signals: + - mqtt_subscriber: + broker_ip: "127.0.0.1" + topic: "/my/sensor" + neurons: + - mute: + status: False + - say: + message: + - "Waiting for orders, sir" +``` diff --git a/kalliope/neurons/mute/__init__.py b/kalliope/neurons/mute/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kalliope/neurons/mute/mute.py b/kalliope/neurons/mute/mute.py new file mode 100644 index 00000000..8c30b260 --- /dev/null +++ b/kalliope/neurons/mute/mute.py @@ -0,0 +1,33 @@ +import logging + +from kalliope import SignalLauncher +from kalliope.core.NeuronModule import NeuronModule + +logging.basicConfig() +logger = logging.getLogger("kalliope") + + +class Mute(NeuronModule): + + def __init__(self, **kwargs): + super(Mute, self).__init__(**kwargs) + + self.status = kwargs.get('status', None) + + # check if parameters have been provided + if self._is_parameters_ok(): + signal_order = SignalLauncher.get_order_instance() + if signal_order is not None: + signal_order.set_mute_status(self.status) + + def _is_parameters_ok(self): + """ + Check if received parameters are ok to perform operations in the neuron + :return: true if parameters are ok, raise an exception otherwise + + .. raises:: MissingParameterException + """ + if self.status is None: + logger.debug("[Mute] You must specify a status with a boolean") + return False + return True diff --git a/kalliope/neurons/neurotimer/README.md b/kalliope/neurons/neurotimer/README.md new file mode 100644 index 00000000..6b83b03b --- /dev/null +++ b/kalliope/neurons/neurotimer/README.md @@ -0,0 +1,120 @@ +# Neurotimer + +## Synopsis + +Run a synapse after a delay. + +## Installation + +CORE NEURON : No installation needed. + +## Options + +| parameter | required | type | default | choices | comment | +|----------------------|----------|--------|---------|-----------|--------------------------------------------------------------| +| seconds | NO | int | | value > 0 | Number of second to wait before running the synapse | +| minutes | NO | int | | value > 0 | Number of minutes to wait before running the synapse | +| hours | NO | int | | value > 0 | Number of hours to wait before running the synapse | +| synapse | YES | string | | | Name of the synapse to run after the selected delay | +| forwarded_parameters | NO | dict | | | dict of parameters that will be passed to the called synapse | + +## Return Values + +None + +## Synapses example + + +**Scenario:** You are used to make a tea and want to know when it's time to remove the bag. +> **You:** remember me to remove the bag of my tea