Add fuzzy and text-classification light model #1
					 11 changed files with 353 additions and 84 deletions
				
			
		
							
								
								
									
										18
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										18
									
								
								README.md
									
										
									
									
									
								
							|  | @ -1,3 +1,19 @@ | ||||||
| # ratatouille | # ratatouille | ||||||
| 
 | 
 | ||||||
| Projet ratatouille: interface tampon entre Rhasspy, Hass, Mpd, Bookstack, Systemd et autre | Projet ratatouille: interface tampon entre Rhasspy, Hass, Mpd, Bookstack, Systemd et autre. | ||||||
|  | 
 | ||||||
|  | To work with models (intent.py), transformers and pytorch is needed. | ||||||
|  | 
 | ||||||
|  | See https://pytorch.org/get-started/locally/ | ||||||
|  | 
 | ||||||
|  | ## Run | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | python main.py server -i 127.0.0.1 -p 7777 --mpd 10.10.10.10 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ou | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | python main.py prompt | ||||||
|  | ``` | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								await.sh
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										12
									
								
								await.sh
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,12 @@ | ||||||
|  | while [[ 1 == 1 ]]; do | ||||||
|  | 	/bin/sleep 10 | ||||||
|  | 	code=$(curl -o /dev/null -s -w "%{http_code}\n" 10.10.10.8) | ||||||
|  | 	echo "10.10.10.8 return code $code" | ||||||
|  | 
 | ||||||
|  | 	if [ $code = '200' ]; then  | ||||||
|  | 		exit 0;  | ||||||
|  | 	else  | ||||||
|  | 		echo "retrying in 10s";  | ||||||
|  | 	fi | ||||||
|  | done | ||||||
|  | 
 | ||||||
							
								
								
									
										85
									
								
								main.py
									
										
									
									
									
								
							
							
						
						
									
										85
									
								
								main.py
									
										
									
									
									
								
							|  | @ -1,37 +1,92 @@ | ||||||
| import logging | import logging | ||||||
| import time | import argparse | ||||||
| import schedule | 
 | ||||||
|  | # import schedule | ||||||
| 
 | 
 | ||||||
| import config | import config | ||||||
| 
 |  | ||||||
| from src.rhasspy import rhasspy_mqtt as yoda_listener | from src.rhasspy import rhasspy_mqtt as yoda_listener | ||||||
| from src.rhasspy import Rhasspy | from src.rhasspy import Rhasspy | ||||||
| from src.ratatouille import Ratatouille | from src.ratatouille import Ratatouille | ||||||
| from src.mpd import Mpd | from src.mpd import Mpd | ||||||
| from src.hass import Hass | from src.hass import Hass | ||||||
| from src.httpServer import get_server | from src.httpServer import get_server | ||||||
| from src.intent import AlexaIntent | from src.fuzzy import fuzz_predict | ||||||
|  | from src.intent import BertIntent | ||||||
|  | from src.tools.simple_sentence_corrector import simple_sentence_corrector | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # --------- setup args ------------- | ||||||
|  | 
 | ||||||
|  | parser = argparse.ArgumentParser( | ||||||
|  |     prog='Ratatouille', | ||||||
|  |     description='Ratatouille le cerveau domotique !') | ||||||
|  | 
 | ||||||
|  | parser.add_argument('mode') | ||||||
|  | parser.add_argument('-i', '--ip', required=False) | ||||||
|  | parser.add_argument('-p', '--port', required=False, type=int) | ||||||
|  | parser.add_argument('-m', '--mpd', required=False) | ||||||
|  | 
 | ||||||
|  | args = parser.parse_args() | ||||||
|  | 
 | ||||||
|  | if args.mode == "server": | ||||||
|  |     if args.ip is None or args.port is None: | ||||||
|  |         logging.error(" --ip or --port argument missing") | ||||||
|  |         exit() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # -------- setup logging ------------ | ||||||
| 
 | 
 | ||||||
| logging.basicConfig( | logging.basicConfig( | ||||||
|     level=10, |     level=10, | ||||||
|     format="%(asctime)s %(filename)s:%(lineno)s %(levelname)s %(message)s" |     format="%(asctime)s %(filename)s:%(lineno)s %(levelname)s %(message)s" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| IP = "10.10.10.11" | logging.info("Loading ratatouilles modules") | ||||||
| PORT = 5555 | 
 | ||||||
|  | 
 | ||||||
|  | # ---------- other --------------- | ||||||
| 
 | 
 | ||||||
| walle = Hass(config.hass_url, config.hass_token) | walle = Hass(config.hass_url, config.hass_token) | ||||||
| yoda = None #Rhasspy(config.rhasspy_url) | yoda = None  # Rhasspy(config.rhasspy_url) | ||||||
| mopidy = Mpd('10.10.10.10', yoda) | 
 | ||||||
| ratatouille = Ratatouille(yoda, walle, mopidy, schedule) | mopidy = None | ||||||
| alexa = AlexaIntent() # we are not doing any request to the evil amazon but we are using one of its dataset | 
 | ||||||
|  | if args.mpd is not None: | ||||||
|  |     mopidy = Mpd(args.mpd) | ||||||
|  | else: | ||||||
|  |     logging.warning('Starting without MPD connection') | ||||||
|  | 
 | ||||||
|  | ratatouille = Ratatouille(yoda, walle, mopidy, None) | ||||||
|  | # alexa = AlexaIntent() # we are not doing any request to the evil amazon but we are using one of its dataset | ||||||
|  | bert = BertIntent() | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def answer(sentence): | def answer(sentence): | ||||||
|     return ratatouille.parseAlexa(alexa.predict(sentence)) |     # return ratatouille.parse_alexa(alexa.predict(sentence)) | ||||||
|     #return "42" |     sentence_corrected = simple_sentence_corrector(sentence) | ||||||
|  |     prediction = bert.predict(sentence_corrected) | ||||||
|  |     return ratatouille.parse_fuzzy(prediction) | ||||||
|  |     # return "42" | ||||||
| 
 | 
 | ||||||
| server = get_server(IP,PORT,answer) |  | ||||||
| 
 | 
 | ||||||
| logging.info('Running server on '+IP+':'+str(PORT)) | def run_server(ip, port): | ||||||
| server.serve_forever() |     server = get_server(ip, port, answer) | ||||||
|  |     logging.info('Running server on '+ip+':'+str(port)) | ||||||
|  |     server.serve_forever() | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | def run_prompt(): | ||||||
|  |     question = "empty" | ||||||
|  |     while question != "stop": | ||||||
|  |         question = input("?") | ||||||
|  |         if question != "stop": | ||||||
|  |             print(answer(question)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | logging.info("Ratatouille is ready !") | ||||||
|  | 
 | ||||||
|  | # run_server() | ||||||
|  | if args.mode == "server": | ||||||
|  |     run_server(str(args.ip), args.port) | ||||||
|  | else: | ||||||
|  |     run_prompt() | ||||||
|  |  | ||||||
|  | @ -2,5 +2,6 @@ paho-mqtt<1.6 | ||||||
| requests<2.26 | requests<2.26 | ||||||
| schedule<1.1.0 | schedule<1.1.0 | ||||||
| python-mpd2<3.1 | python-mpd2<3.1 | ||||||
| transformers<4.28.0 | #transformers<4.28.0 | ||||||
| torch<2.1.0 | #torch<2.1.0 | ||||||
|  | rapidfuzz<4.0.0 | ||||||
							
								
								
									
										69
									
								
								src/fuzzy.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/fuzzy.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,69 @@ | ||||||
|  | from rapidfuzz import process, fuzz, utils | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ROOMS = ["cuisine", "salon", "chambre", "bureau"] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | MUSIQUE_GENRE_SHORT = ["synthpop", "jazz classique", "jazz manouche", "latine", "classique", "rock", "jazz", | ||||||
|  |                        "blues", "film", "francaise", "pop",  "reggae", "folk", | ||||||
|  |                        "électro", "punk", "corse", "arabe", "persane",  "piano", "rap", "slam"] | ||||||
|  | 
 | ||||||
|  | MUSIQUE_GENRE_LONG = ["synthpop", "jazz classique", "jazz manouche", "chanson latine", "classique", "rock", "jazz", | ||||||
|  |                       "blues", "musique de film", "chanson francaise", "pop",  "reggae", "folk", | ||||||
|  |                       "électro", "punk", "chanson corse", "chanson arabe", "chanson persane",  "piano", "rap", "slam", "chanson classique"] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def compute_sentences_lamp_on(): | ||||||
|  |     return ["allume la lumière de la" + room for room in ROOMS] + ["allume la" + room for room in ROOMS] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def compute_sentences_lamp_off(): | ||||||
|  |     return ["éteins la lumière de la" + room for room in ROOMS] + ["éteins la" + room for room in ROOMS] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def compute_sentences_musique_genre_long(): | ||||||
|  |     return ["met du" + genre for genre in MUSIQUE_GENRE_LONG] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def compute_sentences_musique_genre_short(): | ||||||
|  |     return ["met de la musique" + genre for genre in MUSIQUE_GENRE_SHORT] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | SENTENCES_HOUR = ["quel heure est-il ?"] | ||||||
|  | SENTENCES_LAMP_ON = compute_sentences_lamp_on() | ||||||
|  | SENTENCES_LAMP_OFF = compute_sentences_lamp_off() | ||||||
|  | SENTENCES_MUSIQUE_GENRE_LONG = compute_sentences_musique_genre_long() | ||||||
|  | SENTENCES_MUSIQUE_GENRE_SHORT = compute_sentences_musique_genre_short() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def fuzz_predict(text): | ||||||
|  |     choices = SENTENCES_HOUR + SENTENCES_LAMP_ON + SENTENCES_LAMP_OFF + \ | ||||||
|  |         SENTENCES_MUSIQUE_GENRE_LONG + SENTENCES_MUSIQUE_GENRE_SHORT | ||||||
|  |     result = process.extractOne( | ||||||
|  |         text, choices, scorer=fuzz.WRatio, processor=utils.default_process) | ||||||
|  | 
 | ||||||
|  |     choosen_sentence = result[0] | ||||||
|  |     if choosen_sentence in SENTENCES_HOUR: | ||||||
|  |         return {'intentName': 'GetTime'} | ||||||
|  | 
 | ||||||
|  |     if choosen_sentence in SENTENCES_LAMP_ON: | ||||||
|  |         return {'intentName': 'LightOn', 'intentArg': [find_matching(ROOMS, text)]} | ||||||
|  | 
 | ||||||
|  |     if choosen_sentence in SENTENCES_LAMP_OFF: | ||||||
|  |         return {'intentName': 'LightOff', 'intentArg': [find_matching(ROOMS, text)]} | ||||||
|  | 
 | ||||||
|  |     if choosen_sentence in SENTENCES_MUSIQUE_GENRE_LONG: | ||||||
|  |         return {'intentName': 'PlayMusicGenre', 'intentArg': [find_matching(MUSIQUE_GENRE_SHORT, text)]} | ||||||
|  | 
 | ||||||
|  |     if choosen_sentence in SENTENCES_MUSIQUE_GENRE_SHORT: | ||||||
|  |         return {'intentName': 'PlayMusicGenre', 'intentArg': [find_matching(MUSIQUE_GENRE_SHORT, text)]} | ||||||
|  | 
 | ||||||
|  |     return {'intentName': 'Unknown'} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def find_matching(list_str, text): | ||||||
|  |     for search in list_str: | ||||||
|  |         if search in text: | ||||||
|  |             return search | ||||||
|  | 
 | ||||||
|  |     return None | ||||||
|  | @ -2,6 +2,7 @@ import http.server | ||||||
| import json | import json | ||||||
| import logging | import logging | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| def get_server(ip, port, answer_function): | def get_server(ip, port, answer_function): | ||||||
|     class Server(http.server.BaseHTTPRequestHandler): |     class Server(http.server.BaseHTTPRequestHandler): | ||||||
| 
 | 
 | ||||||
|  | @ -16,10 +17,9 @@ def get_server(ip, port, answer_function): | ||||||
|             logging.info('Get request:' + text) |             logging.info('Get request:' + text) | ||||||
|             print(text) |             print(text) | ||||||
|             self.send_response(200) |             self.send_response(200) | ||||||
|             self.send_header('Content-type','text/plain') |             self.send_header('Content-type', 'text/plain; charset=utf-8') | ||||||
|             self.end_headers() |             self.end_headers() | ||||||
| 
 | 
 | ||||||
|              |  | ||||||
|             self.wfile.write(res.encode()) |             self.wfile.write(res.encode()) | ||||||
| 
 | 
 | ||||||
|     return http.server.HTTPServer((ip, port), Server) |     return http.server.HTTPServer((ip, port), Server) | ||||||
|  | @ -1,10 +1,12 @@ | ||||||
| import unittest | import unittest | ||||||
| from transformers import AutoTokenizer, AutoModelForTokenClassification, TokenClassificationPipeline | from transformers import AutoTokenizer, AutoModelForTokenClassification, TokenClassificationPipeline | ||||||
| from transformers import AutoModelForSequenceClassification, TextClassificationPipeline | from transformers import AutoModelForSequenceClassification, TextClassificationPipeline | ||||||
|  | from transformers import pipeline | ||||||
| 
 | 
 | ||||||
| DOMOTIQUE_OBJ_ON = ['iot_wemo_on'] | DOMOTIQUE_OBJ_ON = ['iot_wemo_on'] | ||||||
| DOMOTIQUE_OBJ_OFF = ['iot_wemo_off'] | DOMOTIQUE_OBJ_OFF = ['iot_wemo_off'] | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class AlexaIntent(): | class AlexaIntent(): | ||||||
| 
 | 
 | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|  | @ -15,21 +17,24 @@ class AlexaIntent(): | ||||||
|         model_name = 'qanastek/XLMRoberta-Alexa-Intents-Classification' |         model_name = 'qanastek/XLMRoberta-Alexa-Intents-Classification' | ||||||
|         tokenizer = AutoTokenizer.from_pretrained(model_name) |         tokenizer = AutoTokenizer.from_pretrained(model_name) | ||||||
|         model = AutoModelForSequenceClassification.from_pretrained(model_name) |         model = AutoModelForSequenceClassification.from_pretrained(model_name) | ||||||
|         classifier = TextClassificationPipeline(model=model, tokenizer=tokenizer) |         classifier = TextClassificationPipeline( | ||||||
|  |             model=model, tokenizer=tokenizer) | ||||||
|         return classifier |         return classifier | ||||||
| 
 | 
 | ||||||
|     def init_entities_classification(self): |     def init_entities_classification(self): | ||||||
|         tokenizer = AutoTokenizer.from_pretrained('qanastek/XLMRoberta-Alexa-Intents-NER-NLU') |         tokenizer = AutoTokenizer.from_pretrained( | ||||||
|         model = AutoModelForTokenClassification.from_pretrained('qanastek/XLMRoberta-Alexa-Intents-NER-NLU') |             'qanastek/XLMRoberta-Alexa-Intents-NER-NLU') | ||||||
|  |         model = AutoModelForTokenClassification.from_pretrained( | ||||||
|  |             'qanastek/XLMRoberta-Alexa-Intents-NER-NLU') | ||||||
|         predict = TokenClassificationPipeline(model=model, tokenizer=tokenizer) |         predict = TokenClassificationPipeline(model=model, tokenizer=tokenizer) | ||||||
|         return predict |         return predict | ||||||
| 
 | 
 | ||||||
|     def simple_sentence_corrector(self,sentence):     |     def simple_sentence_corrector(self, sentence): | ||||||
|         sentence = sentence.replace('étant','éteins') |         sentence = sentence.replace('étant', 'éteins') | ||||||
|         sentence = sentence.replace('dépeint','éteins') |         sentence = sentence.replace('dépeint', 'éteins') | ||||||
|         return sentence |         return sentence | ||||||
| 
 | 
 | ||||||
|     def intent_corrector(self,intents): |     def intent_corrector(self, intents): | ||||||
|         for intent in intents: |         for intent in intents: | ||||||
|             if intent['label'] in DOMOTIQUE_OBJ_ON: |             if intent['label'] in DOMOTIQUE_OBJ_ON: | ||||||
|                 intent['label'] = 'iot_hue_lighton' |                 intent['label'] = 'iot_hue_lighton' | ||||||
|  | @ -37,7 +42,7 @@ class AlexaIntent(): | ||||||
|                 intent['label'] = 'iot_hue_lightoff' |                 intent['label'] = 'iot_hue_lightoff' | ||||||
|         return intents |         return intents | ||||||
| 
 | 
 | ||||||
|     def predict(self,sentence): |     def predict(self, sentence): | ||||||
|         sentence = self.simple_sentence_corrector(sentence) |         sentence = self.simple_sentence_corrector(sentence) | ||||||
|         return { |         return { | ||||||
|             'intents': self.intent_corrector(self.get_intents(sentence)), |             'intents': self.intent_corrector(self.get_intents(sentence)), | ||||||
|  | @ -45,6 +50,47 @@ class AlexaIntent(): | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class BertIntent(): | ||||||
|  |     def __init__(self): | ||||||
|  |         self.classifier = pipeline("text-classification", | ||||||
|  |                                    model="Tjiho/french-intents-classificaton") | ||||||
|  | 
 | ||||||
|  |     def predict(self, sentence): | ||||||
|  |         # sentence = self.simple_sentence_corrector(sentence) | ||||||
|  |         classification = self.classifier(sentence) | ||||||
|  | 
 | ||||||
|  |         if classification[0]["score"] < 0.7:  # score too low | ||||||
|  |             return {'intentName': ''} | ||||||
|  | 
 | ||||||
|  |         label = classification[0]["label"] | ||||||
|  | 
 | ||||||
|  |         if label == "HEURE": | ||||||
|  |             return {'intentName': 'GetTime'} | ||||||
|  |         elif label == "DATE": | ||||||
|  |             return {'intentName': 'GetDate'} | ||||||
|  |         elif label == "ETEINDRE_CUISINE": | ||||||
|  |             return {'intentName': 'LightOff', 'intentArg': ['cuisine']} | ||||||
|  |         elif label == "ETEINDRE_BUREAU": | ||||||
|  |             return {'intentName': 'LightOff', 'intentArg': ['bureau']} | ||||||
|  |         elif label == "ETEINDRE_SALON": | ||||||
|  |             return {'intentName': 'LightOff', 'intentArg': ['salon']} | ||||||
|  |         elif label == "ETEINDRE_CHAMBRE": | ||||||
|  |             return {'intentName': 'LightOff', 'intentArg': ['chambre']} | ||||||
|  |         elif label == "ALLUMER_CUISINE": | ||||||
|  |             return {'intentName': 'LightOn', 'intentArg': ['cuisine']} | ||||||
|  |         elif label == "ALLUMER_SALON": | ||||||
|  |             return {'intentName': 'LightOn', 'intentArg': ['salon']} | ||||||
|  |         elif label == "ALLUMER_BUREAU": | ||||||
|  |             return {'intentName': 'LightOn', 'intentArg': ['bureau']} | ||||||
|  |         elif label == "ALLUMER_CHAMBRE": | ||||||
|  |             return {'intentName': 'LightOn', 'intentArg': ['chambre']} | ||||||
|  |         elif label == "METEO": | ||||||
|  |             return {'intentName': 'Meteo'} | ||||||
|  |         elif label == "TEMPERATURE_EXTERIEUR": | ||||||
|  |             return {'intentName': 'Temperature_ext'} | ||||||
|  |         elif label == "TEMPERATURE_INTERIEUR": | ||||||
|  |             return {'intentName': 'Temperature_int'} | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class TestAlexa(unittest.TestCase): | class TestAlexa(unittest.TestCase): | ||||||
|     @classmethod |     @classmethod | ||||||
|  | @ -60,11 +106,11 @@ class TestAlexa(unittest.TestCase): | ||||||
|         self.assertEqual(res['intents'][0]['label'], 'iot_hue_lighton') |         self.assertEqual(res['intents'][0]['label'], 'iot_hue_lighton') | ||||||
|         self.assertEqual(res['entities'][0]['word'], '▁cuisine') |         self.assertEqual(res['entities'][0]['word'], '▁cuisine') | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     def test_bad_transcribe(self): |     def test_bad_transcribe(self): | ||||||
|         res = self.alexa.predict("dépeint la cuisine") |         res = self.alexa.predict("dépeint la cuisine") | ||||||
|         self.assertEqual(res['intents'][0]['label'], 'iot_hue_lightoff') |         self.assertEqual(res['intents'][0]['label'], 'iot_hue_lightoff') | ||||||
|         self.assertEqual(res['entities'][0]['word'], '▁cuisine') |         self.assertEqual(res['entities'][0]['word'], '▁cuisine') | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|     unittest.main() |     unittest.main() | ||||||
							
								
								
									
										78
									
								
								src/mpd.py
									
										
									
									
									
								
							
							
						
						
									
										78
									
								
								src/mpd.py
									
										
									
									
									
								
							|  | @ -2,16 +2,16 @@ import logging | ||||||
| import random | import random | ||||||
| from mpd import MPDClient | from mpd import MPDClient | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class Mpd(): | class Mpd(): | ||||||
| 
 | 
 | ||||||
|     def __init__(self,ip, yoda): |     def __init__(self, ip): | ||||||
|         self.ip = ip |         self.ip = ip | ||||||
|         self.port = 6600 |         self.port = 6600 | ||||||
|         self.client = MPDClient()               # create client object |         self.client = MPDClient()               # create client object | ||||||
|         self.client.timeout = 10                # network timeout in seconds (floats allowed), default: None |         # network timeout in seconds (floats allowed), default: None | ||||||
|  |         self.client.timeout = 10 | ||||||
|         self.client.idletimeout = None |         self.client.idletimeout = None | ||||||
|         self.yoda = yoda |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
|         self.client.connect(ip, self.port) |         self.client.connect(ip, self.port) | ||||||
|         logging.debug(self.client.mpd_version) |         logging.debug(self.client.mpd_version) | ||||||
|  | @ -32,46 +32,66 @@ class Mpd(): | ||||||
|         self.client.disconnect() |         self.client.disconnect() | ||||||
| 
 | 
 | ||||||
|     def play_genre(self, genre): |     def play_genre(self, genre): | ||||||
|  |         res = "" | ||||||
|         self.client.connect(self.ip, self.port) |         self.client.connect(self.ip, self.port) | ||||||
|         logging.debug('Listing '+self.normalize_genre(genre)) |         logging.debug('Listing '+self.normalize_genre(genre)) | ||||||
|  |         list_album = [] | ||||||
|         try: |         try: | ||||||
|             # todo: select only one album instead of the artist |             # todo: select only one album instead of the artist | ||||||
|             list_album = self.client.lsinfo("Files/Genres/"+self.normalize_genre(genre)) |             list_album = self.client.lsinfo( | ||||||
|  |                 "Subsonic/Genre/"+self.normalize_genre(genre)) | ||||||
|  | 
 | ||||||
|  |             if (list_album): | ||||||
|  |                 random_album = random.choice(list_album)["directory"] | ||||||
|  |                 self.play_album(random_album) | ||||||
|  |                 res = "C'est parti !" | ||||||
|  |             else: | ||||||
|  |                 res = "Je n\'ai rien trouvé." | ||||||
|         except: |         except: | ||||||
|             list_album = [] |             res = "Il y a eu une erreur durant le lancement de la musique" | ||||||
|  |         finally: | ||||||
|  |             self.client.close() | ||||||
|  |             self.client.disconnect() | ||||||
| 
 | 
 | ||||||
|         if(list_album): |         return res | ||||||
|             random_album = random.choice(list_album)["directory"] |  | ||||||
|             self.play_album(random_album) |  | ||||||
|         else: |  | ||||||
|             self.yoda.say("Je n\'ai rien trouvé") |  | ||||||
| 
 | 
 | ||||||
|         self.client.close() |     def play_album(self, directory): | ||||||
|         self.client.disconnect() |  | ||||||
| 
 |  | ||||||
|     def play_album(self,directory): |  | ||||||
|         self.client.stop() |         self.client.stop() | ||||||
|         self.yoda.say("Lancement de "+directory.split('/')[-1]) |         # self.yoda.say("Lancement de "+directory.split('/')[-1]) | ||||||
|         logging.debug('Playing '+directory) |         logging.debug('Playing '+directory) | ||||||
|         self.client.clear() |         self.client.clear() | ||||||
|         self.client.add(directory) |         self.client.add(directory) | ||||||
|         self.client.play(0) |         self.client.play(0) | ||||||
| 
 | 
 | ||||||
|     def normalize_genre(self,genre): |     def normalize_genre(self, genre): | ||||||
|         return NORMALIZED_GENRE[genre.lower()] |         return NORMALIZED_GENRE[genre.lower()] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| NORMALIZED_GENRE = { | NORMALIZED_GENRE = { | ||||||
|     'classique': 'classique', |     'classique': 'Classique', | ||||||
|     'musique classique': 'classique', |     'musique classique': 'Classique', | ||||||
|     'jazz': 'jazz', |     'jazz': 'Jazz', | ||||||
|     'chanson française': 'chanson francaise', |     'chanson française': 'Chanson Francaise', | ||||||
|     'chanson anglaise': 'chanson anglaise', |     'francaise': 'Chanson Francaise', | ||||||
|     'musique de film': 'musique de film', |     'chanson anglaise': 'Chanson anglaise', | ||||||
|     'électro': 'electro', |     'reggae': 'Reggae', | ||||||
|     'musique électronique': 'electro', |     'folk': 'Folk', | ||||||
|     'rock': 'rock', |     'électro': 'Electro', | ||||||
|     'pop': 'pop', |     'musique électronique': 'Electro', | ||||||
|     'chanson latine': 'chanson latine', |     'punk': 'Punk', | ||||||
|  |     'rock': 'Rock', | ||||||
|  |     'pop': 'Pop', | ||||||
|  |     'latine': 'Latin', | ||||||
|  |     'arabe': 'Arabic', | ||||||
|  |     'corse': 'Chants Corse', | ||||||
|  |     'persane': 'Chanson Persane', | ||||||
|  |     'piano': 'Piano', | ||||||
|  |     'rap': 'Rap', | ||||||
|  |     'slam': 'Slam', | ||||||
|  |     'synthpop': 'Synthpop', | ||||||
|  |     'jazz classique': 'Classique Jazz', | ||||||
|  |     'jazz manouche': 'Jazz Manouche', | ||||||
|  |     'blues': 'Blues', | ||||||
|  |     'film': 'Soundtrack', | ||||||
|  |     'musique de film': 'Soundtrack', | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -11,9 +11,10 @@ from src.tools.parse_entities import parse_entities | ||||||
| 
 | 
 | ||||||
| from src.const.temperature_keyword import TEMPERATURE_KEYWORD | from src.const.temperature_keyword import TEMPERATURE_KEYWORD | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class Ratatouille(): | class Ratatouille(): | ||||||
| 
 | 
 | ||||||
|     def __init__(self,yoda, walle, mopidy, schedule): |     def __init__(self, yoda, walle, mopidy, schedule): | ||||||
|         self.yoda = yoda |         self.yoda = yoda | ||||||
|         self.walle = walle |         self.walle = walle | ||||||
|         self.mopidy = mopidy |         self.mopidy = mopidy | ||||||
|  | @ -22,9 +23,9 @@ class Ratatouille(): | ||||||
|         # schedule.every().day.at(config.hibernate_time).do(self.hibernate) |         # schedule.every().day.at(config.hibernate_time).do(self.hibernate) | ||||||
|         # schedule.every().day.at(config.wakeup_time).do(self.clear_hibernate) |         # schedule.every().day.at(config.wakeup_time).do(self.clear_hibernate) | ||||||
|         # yoda.say('Ratatouille a bien démmaré') |         # yoda.say('Ratatouille a bien démmaré') | ||||||
|         logging.info('loaded') |         # logging.info('loaded') | ||||||
| 
 | 
 | ||||||
|     def parse_rhasspy_command(self,payload): |     def parse_rhasspy_command(self, payload): | ||||||
|         command = payload['intent']['intentName'] |         command = payload['intent']['intentName'] | ||||||
|         print(command) |         print(command) | ||||||
|         response = '' |         response = '' | ||||||
|  | @ -44,7 +45,7 @@ class Ratatouille(): | ||||||
|         self.yoda.say(response) |         self.yoda.say(response) | ||||||
|         return |         return | ||||||
| 
 | 
 | ||||||
|     def parseAlexa(self,payload): |     def parse_alexa(self, payload): | ||||||
|         print(payload) |         print(payload) | ||||||
|         intent = payload['intents'][0]['label'] |         intent = payload['intents'][0]['label'] | ||||||
|         entities = payload['entities'] |         entities = payload['entities'] | ||||||
|  | @ -59,12 +60,26 @@ class Ratatouille(): | ||||||
|             return self.weather_query(parse_entities(entities)) |             return self.weather_query(parse_entities(entities)) | ||||||
|         return '42' |         return '42' | ||||||
| 
 | 
 | ||||||
|     def weather_query(self,entities): |     def parse_fuzzy(self, payload): | ||||||
|  |         command = payload['intentName'] | ||||||
|  |         if command == 'GetTime': | ||||||
|  |             return self.send_hour() | ||||||
|  |         elif command == 'GetDate': | ||||||
|  |             return self.send_date() | ||||||
|  |         elif command == "LightOn": | ||||||
|  |             return self.light_on_single(payload['intentArg'][0]) | ||||||
|  |         elif command == "LightOff": | ||||||
|  |             return self.light_off_single(payload['intentArg'][0]) | ||||||
|  |         elif command == "PlayMusicGenre": | ||||||
|  |             return self.mopidy.play_genre(payload['intentArg'][0]) | ||||||
|  |         return '42' | ||||||
|  | 
 | ||||||
|  |     def weather_query(self, entities): | ||||||
|         if any(a in entities['B-weather_descriptor'] for a in TEMPERATURE_KEYWORD): |         if any(a in entities['B-weather_descriptor'] for a in TEMPERATURE_KEYWORD): | ||||||
|             res = self.send_temperature() |             res = self.send_temperature() | ||||||
|             return res |             return res | ||||||
| 
 | 
 | ||||||
|     def play_genre(self,genre): |     def play_genre(self, genre): | ||||||
|         try: |         try: | ||||||
|             self.mopidy.play_genre(genre) |             self.mopidy.play_genre(genre) | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|  | @ -99,10 +114,31 @@ class Ratatouille(): | ||||||
|     def send_temperature(self): |     def send_temperature(self): | ||||||
|         logging.info('Send temperature') |         logging.info('Send temperature') | ||||||
|         data = self.walle.get('weather.toulouse') |         data = self.walle.get('weather.toulouse') | ||||||
|         temperature = str(data['attributes']['temperature']).replace('.',' virgule ') |         temperature = str(data['attributes']['temperature'] | ||||||
|  |                           ).replace('.', ' virgule ') | ||||||
|         return 'il fait '+temperature+' degrés' |         return 'il fait '+temperature+' degrés' | ||||||
| 
 | 
 | ||||||
|     def light_off(self,entities): |     def light_off_single(self, lamp): | ||||||
|  |         try: | ||||||
|  |             self.walle.light_off(lamp) | ||||||
|  |         except Exception as e: | ||||||
|  |             logging.warning("Error light off:") | ||||||
|  |             logging.warning(e) | ||||||
|  |             return "J'ai pas pu éteindre la lampe '" + lamp + "'." | ||||||
|  | 
 | ||||||
|  |         return "J'ai éteint la lampe '" + lamp + "'." | ||||||
|  | 
 | ||||||
|  |     def light_on_single(self, lamp): | ||||||
|  |         try: | ||||||
|  |             self.walle.light_on(lamp) | ||||||
|  |         except Exception as e: | ||||||
|  |             logging.warning("Error light on:") | ||||||
|  |             logging.warning(e) | ||||||
|  |             return "J'ai pas pu allumer la lampe '" + lamp + "'." | ||||||
|  | 
 | ||||||
|  |         return "J'ai allumé la lampe '" + lamp + "'." | ||||||
|  | 
 | ||||||
|  |     def light_off(self, entities): | ||||||
|         number_error = 0 |         number_error = 0 | ||||||
|         for lamp in entities: |         for lamp in entities: | ||||||
|             try: |             try: | ||||||
|  | @ -115,9 +151,9 @@ class Ratatouille(): | ||||||
|         if number_error == 0: |         if number_error == 0: | ||||||
|             return 'Et voila !' |             return 'Et voila !' | ||||||
|         else: |         else: | ||||||
|             return 'J\'ai pas pu eteindre ' + int_to_str(number_error,'f') + ' lampes' |             return 'J\'ai pas pu eteindre ' + int_to_str(number_error, 'f') + ' lampes' | ||||||
| 
 | 
 | ||||||
|     def light_on(self,entities): |     def light_on(self, entities): | ||||||
|         number_error = 0 |         number_error = 0 | ||||||
|         for lamp in entities: |         for lamp in entities: | ||||||
|             try: |             try: | ||||||
|  | @ -130,19 +166,19 @@ class Ratatouille(): | ||||||
|         if number_error == 0: |         if number_error == 0: | ||||||
|             return 'Et voila !' |             return 'Et voila !' | ||||||
|         else: |         else: | ||||||
|             return 'J\'ai pas pu allumer ' + int_to_str(number_error,'f') + ' lampes' |             return 'J\'ai pas pu allumer ' + int_to_str(number_error, 'f') + ' lampes' | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
|     # -- -- hibernate -- -- |     # -- -- hibernate -- -- | ||||||
|  | 
 | ||||||
|     def can_hibernate(self): |     def can_hibernate(self): | ||||||
|         lamp_desk = self.walle.get('switch.prise_bureau_switch') |         lamp_desk = self.walle.get('switch.prise_bureau_switch') | ||||||
|         if lamp_desk: |         if lamp_desk: | ||||||
|             desk_is_on = lamp_desk['attributes'].get('state','OFF') == 'ON' |             desk_is_on = lamp_desk['attributes'].get('state', 'OFF') == 'ON' | ||||||
| 
 | 
 | ||||||
|         lamp_bathroom = self.walle.get('light.chihiro_chihiro') |         lamp_bathroom = self.walle.get('light.chihiro_chihiro') | ||||||
|         if lamp_bathroom: |         if lamp_bathroom: | ||||||
|             bathroom_is_on = lamp_bathroom.get('state','OFF') == 'on' |             bathroom_is_on = lamp_bathroom.get('state', 'OFF') == 'on' | ||||||
|         #bathroom_is_on = self.walle.get('switch.prise_bureau_switch')['attributes']['state'] == 'ON' |         # bathroom_is_on = self.walle.get('switch.prise_bureau_switch')['attributes']['state'] == 'ON' | ||||||
|         return lamp_desk and lamp_bathroom and not desk_is_on and not bathroom_is_on |         return lamp_desk and lamp_bathroom and not desk_is_on and not bathroom_is_on | ||||||
| 
 | 
 | ||||||
|     def hibernate(self): |     def hibernate(self): | ||||||
|  | @ -153,11 +189,13 @@ class Ratatouille(): | ||||||
|             time.sleep(5) |             time.sleep(5) | ||||||
|         else: |         else: | ||||||
|             self.schedule.clear('hourly-hibernate') |             self.schedule.clear('hourly-hibernate') | ||||||
|             self.schedule.every(3).minutes.do(self.hibernate).tag('hourly-hibernate') |             self.schedule.every(3).minutes.do( | ||||||
|  |                 self.hibernate).tag('hourly-hibernate') | ||||||
| 
 | 
 | ||||||
|             logging.info('retry to hibernate in 3 minutes') |             logging.info('retry to hibernate in 3 minutes') | ||||||
| 
 | 
 | ||||||
|         return |         return | ||||||
|  | 
 | ||||||
|     def clear_hibernate(self): |     def clear_hibernate(self): | ||||||
|         self.schedule.clear('hourly-hibernate') |         self.schedule.clear('hourly-hibernate') | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,7 +1,10 @@ | ||||||
| from src.tools.str import int_to_str | from src.tools.str import int_to_str | ||||||
| 
 | 
 | ||||||
| MONTHS = ['janvier','février','mars','avril','mai','juin','juillet','aout','septembre','octobre','novembre','decembre'] | MONTHS = ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', | ||||||
| WEEKDAY = ['lundi','mardi','mercredi','jeudi','vendredi','samedi','dimanche'] |           'juillet', 'aout', 'septembre', 'octobre', 'novembre', 'decembre'] | ||||||
|  | WEEKDAY = ['lundi', 'mardi', 'mercredi', | ||||||
|  |            'jeudi', 'vendredi', 'samedi', 'dimanche'] | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def get_time(date): | def get_time(date): | ||||||
|     hour = date.hour |     hour = date.hour | ||||||
|  | @ -13,18 +16,20 @@ def get_time(date): | ||||||
|     elif minute == 45: |     elif minute == 45: | ||||||
|         return 'il est '+format_hour(hour+1)+' moins le quart' |         return 'il est '+format_hour(hour+1)+' moins le quart' | ||||||
|     elif minute == 15: |     elif minute == 15: | ||||||
|         return 'il est '+format_hour(hour+1)+' et quart' |         return 'il est '+format_hour(hour)+' et quart' | ||||||
|     else: |     else: | ||||||
|         return 'il est '+format_hour(hour)+' '+str(minute) |         return 'il est '+format_hour(hour)+' '+str(minute) | ||||||
|     return |     return | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| def get_date(date): | def get_date(date): | ||||||
|     week_day = date.weekday() |     week_day = date.weekday() | ||||||
|     day = date.day |     day = date.day | ||||||
|     month = date.month |     month = date.month | ||||||
|     year = date.year |     year = date.year | ||||||
|     return 'Nous somme le '+format_weekday(week_day)+' '+str(day)+' '+format_month(month)+' '+str(year) |     return 'Nous somme le '+format_weekday(week_day)+' '+str(day)+' '+format_month(month)+' '+str(year) | ||||||
|     #self.yoda.say('Nous somme le '+str(week_day)+' '+str(day)+' '+str(month)+' '+str(year)) |     # self.yoda.say('Nous somme le '+str(week_day)+' '+str(day)+' '+str(month)+' '+str(year)) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def format_hour(hour): | def format_hour(hour): | ||||||
|     if hour == 12: |     if hour == 12: | ||||||
|  | @ -34,8 +39,10 @@ def format_hour(hour): | ||||||
| 
 | 
 | ||||||
|     return int_to_str(hour, 'f') + ' heure' |     return int_to_str(hour, 'f') + ' heure' | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| def format_month(month): | def format_month(month): | ||||||
|     return MONTHS[month - 1] |     return MONTHS[month - 1] | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| def format_weekday(week_day): | def format_weekday(week_day): | ||||||
|     return WEEKDAY[week_day] |     return WEEKDAY[week_day] | ||||||
|  |  | ||||||
							
								
								
									
										5
									
								
								src/tools/simple_sentence_corrector.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/tools/simple_sentence_corrector.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | def simple_sentence_corrector(sentence): | ||||||
|  |     sentence = sentence.replace('étant', 'éteins') | ||||||
|  |     sentence = sentence.replace('dépeint', 'éteins') | ||||||
|  |     sentence = sentence.replace('mais', 'mets') | ||||||
|  |     return sentence | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue