From a89634a231a052a8f9d660b674b3e436a65673a7 Mon Sep 17 00:00:00 2001 From: tjiho Date: Sun, 27 Oct 2024 15:55:20 +0100 Subject: [PATCH 01/18] Replace alexa neural network by simple fuzzy search --- main.py | 19 +++++++++++-------- requirements.txt | 5 +++-- src/fuzzy.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/ratatouille.py | 45 +++++++++++++++++++++++++++++---------------- 4 files changed, 87 insertions(+), 26 deletions(-) create mode 100644 src/fuzzy.py diff --git a/main.py b/main.py index 3c9e802..2e250d7 100644 --- a/main.py +++ b/main.py @@ -10,7 +10,8 @@ from src.ratatouille import Ratatouille from src.mpd import Mpd from src.hass import Hass from src.httpServer import get_server -from src.intent import AlexaIntent +from src.fuzzy import fuzz_predict +# from src.intent import AlexaIntent logging.basicConfig( level=10, @@ -21,17 +22,19 @@ IP = "10.10.10.11" PORT = 5555 walle = Hass(config.hass_url, config.hass_token) -yoda = None #Rhasspy(config.rhasspy_url) -mopidy = Mpd('10.10.10.10', yoda) +yoda = None # Rhasspy(config.rhasspy_url) +# mopidy = Mpd('10.10.10.10', yoda) ratatouille = Ratatouille(yoda, walle, mopidy, schedule) -alexa = AlexaIntent() # we are not doing any request to the evil amazon but we are using one of its dataset +# alexa = AlexaIntent() # we are not doing any request to the evil amazon but we are using one of its dataset + def answer(sentence): - return ratatouille.parseAlexa(alexa.predict(sentence)) - #return "42" + # return ratatouille.parse_alexa(alexa.predict(sentence)) + return ratatouille.parse_fuzzy(fuzz_predict(sentence)) + # return "42" -server = get_server(IP,PORT,answer) + +server = get_server(IP, PORT, answer) logging.info('Running server on '+IP+':'+str(PORT)) server.serve_forever() - diff --git a/requirements.txt b/requirements.txt index 938ef97..9366e26 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,5 +2,6 @@ paho-mqtt<1.6 requests<2.26 schedule<1.1.0 python-mpd2<3.1 -transformers<4.28.0 -torch<2.1.0 \ No newline at end of file +#transformers<4.28.0 +#torch<2.1.0 +rapidfuzz<4.0.0 \ No newline at end of file diff --git a/src/fuzzy.py b/src/fuzzy.py new file mode 100644 index 0000000..6b946b9 --- /dev/null +++ b/src/fuzzy.py @@ -0,0 +1,44 @@ +from rapidfuzz import process, fuzz, utils + + +ROOMS = ["cuisine", "salon", "chambre", "bureau"] + + +def compute_sentences_lamp_on(): + return ["allume la lumière de la" + room for room in ROOMS] + + +def compute_sentences_lamp_off(): + return ["éteins la lumière de la" + room for room in ROOMS] + + +SENTENCES_HOUR = ["quel heure est-il ?"] +SENTENCES_LAMP_ON = compute_sentences_lamp_on() +SENTENCES_LAMP_OFF = compute_sentences_lamp_off() +# SENTENCES_MUSIQUE = computes_sentences + + +def fuzz_predict(text): + choices = SENTENCES_HOUR + SENTENCES_LAMP_ON + SENTENCES_LAMP_OFF + 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)]} + + return {'intentName': 'Unknown'} + + +def find_matching(list_str, text): + for search in list_str: + if search in text: + return search + + return None diff --git a/src/ratatouille.py b/src/ratatouille.py index cb0e09a..0a12e09 100644 --- a/src/ratatouille.py +++ b/src/ratatouille.py @@ -11,9 +11,10 @@ from src.tools.parse_entities import parse_entities from src.const.temperature_keyword import TEMPERATURE_KEYWORD + class Ratatouille(): - def __init__(self,yoda, walle, mopidy, schedule): + def __init__(self, yoda, walle, mopidy, schedule): self.yoda = yoda self.walle = walle self.mopidy = mopidy @@ -24,7 +25,7 @@ class Ratatouille(): # yoda.say('Ratatouille a bien démmaré') logging.info('loaded') - def parse_rhasspy_command(self,payload): + def parse_rhasspy_command(self, payload): command = payload['intent']['intentName'] print(command) response = '' @@ -40,11 +41,11 @@ class Ratatouille(): response = self.pause_music() elif command == 'StartMusic': response = self.start_music() - + self.yoda.say(response) return - def parseAlexa(self,payload): + def parse_alexa(self, payload): print(payload) intent = payload['intents'][0]['label'] entities = payload['entities'] @@ -59,12 +60,21 @@ class Ratatouille(): return self.weather_query(parse_entities(entities)) return '42' - def weather_query(self,entities): + def parse_fuzzy(self, payload): + command = payload['intentName'] + if command == 'GetTime': + return self.send_hour() + elif command == "LightOn": + self.walle.light_on(payload['intentArg'][0]) + elif command == "LightOff": + self.walle.light_off(payload['intentArg'][0]) + + def weather_query(self, entities): if any(a in entities['B-weather_descriptor'] for a in TEMPERATURE_KEYWORD): res = self.send_temperature() return res - def play_genre(self,genre): + def play_genre(self, genre): try: self.mopidy.play_genre(genre) except Exception as e: @@ -99,10 +109,11 @@ class Ratatouille(): def send_temperature(self): logging.info('Send temperature') 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' - def light_off(self,entities): + def light_off(self, entities): number_error = 0 for lamp in entities: try: @@ -115,9 +126,9 @@ class Ratatouille(): if number_error == 0: return 'Et voila !' 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 for lamp in entities: try: @@ -130,19 +141,19 @@ class Ratatouille(): if number_error == 0: return 'Et voila !' 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 -- -- + def can_hibernate(self): lamp_desk = self.walle.get('switch.prise_bureau_switch') 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') if lamp_bathroom: - 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 = lamp_bathroom.get('state', 'OFF') == '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 def hibernate(self): @@ -153,11 +164,13 @@ class Ratatouille(): time.sleep(5) else: 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') return + def clear_hibernate(self): self.schedule.clear('hourly-hibernate') -- 2.48.1 From 33ee285d887a4b27057fb82bc2117d33efef5742 Mon Sep 17 00:00:00 2001 From: tjiho Date: Sun, 27 Oct 2024 16:09:51 +0100 Subject: [PATCH 02/18] fix light on off --- src/ratatouille.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/ratatouille.py b/src/ratatouille.py index 0a12e09..612dd23 100644 --- a/src/ratatouille.py +++ b/src/ratatouille.py @@ -65,9 +65,9 @@ class Ratatouille(): if command == 'GetTime': return self.send_hour() elif command == "LightOn": - self.walle.light_on(payload['intentArg'][0]) + self.light_on_single(payload['intentArg'][0]) elif command == "LightOff": - self.walle.light_off(payload['intentArg'][0]) + self.light_off_single(payload['intentArg'][0]) def weather_query(self, entities): if any(a in entities['B-weather_descriptor'] for a in TEMPERATURE_KEYWORD): @@ -113,6 +113,26 @@ class Ratatouille(): ).replace('.', ' virgule ') return 'il fait '+temperature+' degrés' + 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 for lamp in entities: -- 2.48.1 From dc0200f00c69f04192fa96c4d29a3ef6de655ca3 Mon Sep 17 00:00:00 2001 From: tjiho Date: Sun, 27 Oct 2024 16:11:11 +0100 Subject: [PATCH 03/18] fix lamps --- src/ratatouille.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ratatouille.py b/src/ratatouille.py index 612dd23..d90930a 100644 --- a/src/ratatouille.py +++ b/src/ratatouille.py @@ -65,9 +65,9 @@ class Ratatouille(): if command == 'GetTime': return self.send_hour() elif command == "LightOn": - self.light_on_single(payload['intentArg'][0]) + return self.light_on_single(payload['intentArg'][0]) elif command == "LightOff": - self.light_off_single(payload['intentArg'][0]) + return self.light_off_single(payload['intentArg'][0]) def weather_query(self, entities): if any(a in entities['B-weather_descriptor'] for a in TEMPERATURE_KEYWORD): -- 2.48.1 From 051f42cf1762aaa91816c9ab43d983e96fbec5f2 Mon Sep 17 00:00:00 2001 From: tjiho Date: Sun, 27 Oct 2024 16:22:46 +0100 Subject: [PATCH 04/18] Add utf-8 charset to api --- src/httpServer.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/httpServer.py b/src/httpServer.py index 105154e..6f73eab 100644 --- a/src/httpServer.py +++ b/src/httpServer.py @@ -2,24 +2,24 @@ import http.server import json import logging + def get_server(ip, port, answer_function): class Server(http.server.BaseHTTPRequestHandler): - + def do_POST(self): length = int(self.headers.get('content-length')) field_data = self.rfile.read(length) fields = json.loads(field_data) text = fields['text'].strip() - + res = answer_function(text) logging.info('Get request:' + text) print(text) 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.wfile.write(res.encode()) - return http.server.HTTPServer((ip, port), Server) \ No newline at end of file + return http.server.HTTPServer((ip, port), Server) -- 2.48.1 From ef1e10810676da9e1178b71744102602e98e2019 Mon Sep 17 00:00:00 2001 From: tjiho Date: Sun, 10 Nov 2024 20:08:20 +0100 Subject: [PATCH 05/18] Add musique genre in fuzzy --- src/fuzzy.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/fuzzy.py b/src/fuzzy.py index 6b946b9..b3e13e4 100644 --- a/src/fuzzy.py +++ b/src/fuzzy.py @@ -4,6 +4,13 @@ from rapidfuzz import process, fuzz, utils ROOMS = ["cuisine", "salon", "chambre", "bureau"] +MUSIQUE_GENRE_SHORT = ["synthpop", "jazz classique", "jazz manouche", "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", "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] @@ -12,10 +19,19 @@ def compute_sentences_lamp_off(): return ["éteins la lumière de 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 = computes_sentences +SENTENCES_MUSIQUE_GENRE_LONG = compute_sentences_musique_genre_long() +SENTENCES_MUSIQUE_GENRE_SHORT = compute_sentences_musique_genre_short() def fuzz_predict(text): @@ -33,6 +49,12 @@ def fuzz_predict(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': 'LightOff', 'intentArg': [find_matching(MUSIQUE_GENRE_SHORT, text)]} + + if choosen_sentence in SENTENCES_MUSIQUE_GENRE_SHORT: + return {'intentName': 'LightOff', 'intentArg': [find_matching(MUSIQUE_GENRE_SHORT, text)]} + return {'intentName': 'Unknown'} -- 2.48.1 From f3afb19ae5041dda7223e97e413dd61dd39a328f Mon Sep 17 00:00:00 2001 From: tjiho Date: Sun, 10 Nov 2024 20:13:21 +0100 Subject: [PATCH 06/18] Fix fuzzy for musique --- src/fuzzy.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/fuzzy.py b/src/fuzzy.py index b3e13e4..9a21c3d 100644 --- a/src/fuzzy.py +++ b/src/fuzzy.py @@ -35,7 +35,8 @@ SENTENCES_MUSIQUE_GENRE_SHORT = compute_sentences_musique_genre_short() def fuzz_predict(text): - choices = SENTENCES_HOUR + SENTENCES_LAMP_ON + SENTENCES_LAMP_OFF + 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) @@ -50,10 +51,10 @@ def fuzz_predict(text): return {'intentName': 'LightOff', 'intentArg': [find_matching(ROOMS, text)]} if choosen_sentence in SENTENCES_MUSIQUE_GENRE_LONG: - return {'intentName': 'LightOff', 'intentArg': [find_matching(MUSIQUE_GENRE_SHORT, text)]} + return {'intentName': 'PlayMusicGenre', 'intentArg': [find_matching(MUSIQUE_GENRE_SHORT, text)]} if choosen_sentence in SENTENCES_MUSIQUE_GENRE_SHORT: - return {'intentName': 'LightOff', 'intentArg': [find_matching(MUSIQUE_GENRE_SHORT, text)]} + return {'intentName': 'PlayMusicGenre', 'intentArg': [find_matching(MUSIQUE_GENRE_SHORT, text)]} return {'intentName': 'Unknown'} -- 2.48.1 From aa512535aaeb4015d51cc63e03781bc787f8ab2e Mon Sep 17 00:00:00 2001 From: tjiho Date: Sun, 10 Nov 2024 20:29:48 +0100 Subject: [PATCH 07/18] Add play genre action --- src/fuzzy.py | 6 ++++-- src/mpd.py | 54 ++++++++++++++++++++++++++++++---------------- src/ratatouille.py | 3 +++ 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/fuzzy.py b/src/fuzzy.py index 9a21c3d..c7cb788 100644 --- a/src/fuzzy.py +++ b/src/fuzzy.py @@ -4,10 +4,12 @@ from rapidfuzz import process, fuzz, utils ROOMS = ["cuisine", "salon", "chambre", "bureau"] -MUSIQUE_GENRE_SHORT = ["synthpop", "jazz classique", "jazz manouche", "classique", "rock", "jazz", "blues", "film", "francaise", "pop", "reggae", "folk", +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", "classique", "rock", "jazz", "blues", "musique de film", "chanson francaise", "pop", "reggae", "folk", +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"] diff --git a/src/mpd.py b/src/mpd.py index efedc81..630e423 100644 --- a/src/mpd.py +++ b/src/mpd.py @@ -2,17 +2,18 @@ import logging import random from mpd import MPDClient + class Mpd(): - def __init__(self,ip, yoda): + def __init__(self, ip, yoda): self.ip = ip self.port = 6600 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.yoda = yoda - self.client.connect(ip, self.port) logging.debug(self.client.mpd_version) self.client.close() @@ -36,11 +37,12 @@ class Mpd(): logging.debug('Listing '+self.normalize_genre(genre)) try: # 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)) except: list_album = [] - if(list_album): + if (list_album): random_album = random.choice(list_album)["directory"] self.play_album(random_album) else: @@ -49,7 +51,7 @@ class Mpd(): self.client.close() self.client.disconnect() - def play_album(self,directory): + def play_album(self, directory): self.client.stop() self.yoda.say("Lancement de "+directory.split('/')[-1]) logging.debug('Playing '+directory) @@ -57,21 +59,35 @@ class Mpd(): self.client.add(directory) self.client.play(0) - def normalize_genre(self,genre): + def normalize_genre(self, genre): return NORMALIZED_GENRE[genre.lower()] - NORMALIZED_GENRE = { - 'classique': 'classique', - 'musique classique': 'classique', - 'jazz': 'jazz', - 'chanson française': 'chanson francaise', - 'chanson anglaise': 'chanson anglaise', - 'musique de film': 'musique de film', - 'électro': 'electro', - 'musique électronique': 'electro', - 'rock': 'rock', - 'pop': 'pop', - 'chanson latine': 'chanson latine', + 'classique': 'Classique', + 'musique classique': 'Classique', + 'jazz': 'Jazz', + 'chanson française': 'Chanson Francaise', + 'francaise': 'Chanson Francaise', + 'chanson anglaise': 'Chanson anglaise', + 'reggae': 'Reggae', + 'folk': 'Folk', + 'électro': 'Electro', + 'musique électronique': 'Electro', + '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', } diff --git a/src/ratatouille.py b/src/ratatouille.py index d90930a..08c85c1 100644 --- a/src/ratatouille.py +++ b/src/ratatouille.py @@ -68,6 +68,9 @@ class Ratatouille(): return self.light_on_single(payload['intentArg'][0]) elif command == "LightOff": return self.light_off_single(payload['intentArg'][0]) + elif command == "PlayMusicGenre": + return self.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): -- 2.48.1 From faadd1f4e06bfc6cda1a6a8fca98ab7ea2ed761b Mon Sep 17 00:00:00 2001 From: tjiho Date: Sun, 10 Nov 2024 20:35:43 +0100 Subject: [PATCH 08/18] Fix mpd --- main.py | 4 ++-- src/mpd.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/main.py b/main.py index 2e250d7..f01f980 100644 --- a/main.py +++ b/main.py @@ -18,12 +18,12 @@ logging.basicConfig( format="%(asctime)s %(filename)s:%(lineno)s %(levelname)s %(message)s" ) -IP = "10.10.10.11" +IP = "10.10.10.8" PORT = 5555 walle = Hass(config.hass_url, config.hass_token) yoda = None # Rhasspy(config.rhasspy_url) -# mopidy = Mpd('10.10.10.10', yoda) +mopidy = Mpd('10.10.10.8') ratatouille = Ratatouille(yoda, walle, mopidy, schedule) # alexa = AlexaIntent() # we are not doing any request to the evil amazon but we are using one of its dataset diff --git a/src/mpd.py b/src/mpd.py index 630e423..dd312b8 100644 --- a/src/mpd.py +++ b/src/mpd.py @@ -5,14 +5,13 @@ from mpd import MPDClient class Mpd(): - def __init__(self, ip, yoda): + def __init__(self, ip): self.ip = ip self.port = 6600 self.client = MPDClient() # create client object # network timeout in seconds (floats allowed), default: None self.client.timeout = 10 self.client.idletimeout = None - self.yoda = yoda self.client.connect(ip, self.port) logging.debug(self.client.mpd_version) @@ -45,15 +44,16 @@ class Mpd(): if (list_album): random_album = random.choice(list_album)["directory"] self.play_album(random_album) + return "C'est parti !" else: - self.yoda.say("Je n\'ai rien trouvé") + return "Je n\'ai rien trouvé." self.client.close() self.client.disconnect() def play_album(self, directory): self.client.stop() - self.yoda.say("Lancement de "+directory.split('/')[-1]) + # self.yoda.say("Lancement de "+directory.split('/')[-1]) logging.debug('Playing '+directory) self.client.clear() self.client.add(directory) -- 2.48.1 From 1eba121bec1baeb4da6f533d86b1f3792586163e Mon Sep 17 00:00:00 2001 From: tjiho Date: Sun, 10 Nov 2024 20:40:34 +0100 Subject: [PATCH 09/18] Fix musique genre --- src/mpd.py | 5 +++-- src/ratatouille.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/mpd.py b/src/mpd.py index dd312b8..fb448ee 100644 --- a/src/mpd.py +++ b/src/mpd.py @@ -44,12 +44,13 @@ class Mpd(): if (list_album): random_album = random.choice(list_album)["directory"] self.play_album(random_album) - return "C'est parti !" + res = "C'est parti !" else: - return "Je n\'ai rien trouvé." + res = "Je n\'ai rien trouvé." self.client.close() self.client.disconnect() + return res def play_album(self, directory): self.client.stop() diff --git a/src/ratatouille.py b/src/ratatouille.py index 08c85c1..5ff2d62 100644 --- a/src/ratatouille.py +++ b/src/ratatouille.py @@ -69,7 +69,8 @@ class Ratatouille(): elif command == "LightOff": return self.light_off_single(payload['intentArg'][0]) elif command == "PlayMusicGenre": - return self.play_genre(payload['intentArg'][0]) + self.play_genre(payload['intentArg'][0]) + return 'Lancement de la musique...' return '42' def weather_query(self, entities): -- 2.48.1 From 8ef51b29675498ca93dd5cfa2f0625489ee3ee25 Mon Sep 17 00:00:00 2001 From: tjiho Date: Tue, 12 Nov 2024 19:14:35 +0100 Subject: [PATCH 10/18] Improve mpd logic --- src/mpd.py | 23 +++++++++++++---------- src/ratatouille.py | 3 +-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/mpd.py b/src/mpd.py index fb448ee..2972345 100644 --- a/src/mpd.py +++ b/src/mpd.py @@ -32,24 +32,27 @@ class Mpd(): self.client.disconnect() def play_genre(self, genre): + res = "" self.client.connect(self.ip, self.port) logging.debug('Listing '+self.normalize_genre(genre)) + list_album = [] try: # todo: select only one album instead of the artist 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: - 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): - random_album = random.choice(list_album)["directory"] - self.play_album(random_album) - res = "C'est parti !" - else: - res = "Je n\'ai rien trouvé." - - self.client.close() - self.client.disconnect() return res def play_album(self, directory): diff --git a/src/ratatouille.py b/src/ratatouille.py index 5ff2d62..1ac82ea 100644 --- a/src/ratatouille.py +++ b/src/ratatouille.py @@ -69,8 +69,7 @@ class Ratatouille(): elif command == "LightOff": return self.light_off_single(payload['intentArg'][0]) elif command == "PlayMusicGenre": - self.play_genre(payload['intentArg'][0]) - return 'Lancement de la musique...' + return self.mopidy.play_genre(payload['intentArg'][0]) return '42' def weather_query(self, entities): -- 2.48.1 From 413abe2ef2c5e34fe144e519466e04bb77c129c9 Mon Sep 17 00:00:00 2001 From: tjiho Date: Sat, 23 Nov 2024 02:53:13 +0100 Subject: [PATCH 11/18] improve fuzzy lamp --- src/fuzzy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fuzzy.py b/src/fuzzy.py index c7cb788..ffa735a 100644 --- a/src/fuzzy.py +++ b/src/fuzzy.py @@ -14,11 +14,11 @@ MUSIQUE_GENRE_LONG = ["synthpop", "jazz classique", "jazz manouche", "chanson la def compute_sentences_lamp_on(): - return ["allume la lumière de la" + room for room in ROOMS] + 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] + 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(): -- 2.48.1 From 7b2cff5ff21f4d086d2129e891495f7df43f4c08 Mon Sep 17 00:00:00 2001 From: tjiho Date: Tue, 26 Nov 2024 18:46:06 +0100 Subject: [PATCH 12/18] add await script --- await.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100755 await.sh diff --git a/await.sh b/await.sh new file mode 100755 index 0000000..062f227 --- /dev/null +++ b/await.sh @@ -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 + -- 2.48.1 From 07c5184f13b1a96cea4700de7634b9d689b433cc Mon Sep 17 00:00:00 2001 From: tjiho Date: Fri, 31 Jan 2025 23:38:14 +0100 Subject: [PATCH 13/18] Add new intent classifier, and add args parser to main --- main.py | 65 +++++++++++++++++++----- src/intent.py | 68 +++++++++++++++++++++----- src/tools/simple_sentence_corrector.py | 4 ++ 3 files changed, 115 insertions(+), 22 deletions(-) create mode 100644 src/tools/simple_sentence_corrector.py diff --git a/main.py b/main.py index f01f980..6fbaa75 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,9 @@ import logging -import time -import schedule +import argparse + +# import schedule import config - from src.rhasspy import rhasspy_mqtt as yoda_listener from src.rhasspy import Rhasspy from src.ratatouille import Ratatouille @@ -11,30 +11,73 @@ from src.mpd import Mpd from src.hass import Hass from src.httpServer import get_server from src.fuzzy import fuzz_predict -# from src.intent import AlexaIntent +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) + +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( level=10, format="%(asctime)s %(filename)s:%(lineno)s %(levelname)s %(message)s" ) -IP = "10.10.10.8" -PORT = 5555 +logging.info("Loading ratatouilles modules") + + +# ---------- other --------------- walle = Hass(config.hass_url, config.hass_token) yoda = None # Rhasspy(config.rhasspy_url) mopidy = Mpd('10.10.10.8') -ratatouille = Ratatouille(yoda, walle, mopidy, schedule) +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): # return ratatouille.parse_alexa(alexa.predict(sentence)) - return ratatouille.parse_fuzzy(fuzz_predict(sentence)) + sentence_corrected = simple_sentence_corrector(sentence) + prediction = bert.predict(sentence_corrected) + return ratatouille.parse_fuzzy(prediction) # return "42" -server = get_server(IP, PORT, answer) +def run_server(ip, port): + server = get_server(ip, port, answer) + logging.info('Running server on '+ip+':'+str(port)) + server.serve_forever() -logging.info('Running server on '+IP+':'+str(PORT)) -server.serve_forever() + +def run_prompt(): + question = "empty" + while question != "stop": + question = input("?") + print(answer(question)) + + +logging.info("Ratatouille is ready !") + +# run_server() +if args.mode == "server": + run_server(str(args.ip), args.port) +else: + run_prompt() diff --git a/src/intent.py b/src/intent.py index b99b374..3bd1614 100644 --- a/src/intent.py +++ b/src/intent.py @@ -1,12 +1,14 @@ import unittest from transformers import AutoTokenizer, AutoModelForTokenClassification, TokenClassificationPipeline from transformers import AutoModelForSequenceClassification, TextClassificationPipeline +from transformers import pipeline DOMOTIQUE_OBJ_ON = ['iot_wemo_on'] DOMOTIQUE_OBJ_OFF = ['iot_wemo_off'] + class AlexaIntent(): - + def __init__(self): self.get_intents = self.init_intent_classification() self.get_entities = self.init_entities_classification() @@ -15,21 +17,24 @@ class AlexaIntent(): model_name = 'qanastek/XLMRoberta-Alexa-Intents-Classification' tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained(model_name) - classifier = TextClassificationPipeline(model=model, tokenizer=tokenizer) + classifier = TextClassificationPipeline( + model=model, tokenizer=tokenizer) return classifier def init_entities_classification(self): - tokenizer = AutoTokenizer.from_pretrained('qanastek/XLMRoberta-Alexa-Intents-NER-NLU') - model = AutoModelForTokenClassification.from_pretrained('qanastek/XLMRoberta-Alexa-Intents-NER-NLU') + tokenizer = AutoTokenizer.from_pretrained( + 'qanastek/XLMRoberta-Alexa-Intents-NER-NLU') + model = AutoModelForTokenClassification.from_pretrained( + 'qanastek/XLMRoberta-Alexa-Intents-NER-NLU') predict = TokenClassificationPipeline(model=model, tokenizer=tokenizer) return predict - def simple_sentence_corrector(self,sentence): - sentence = sentence.replace('étant','éteins') - sentence = sentence.replace('dépeint','éteins') + def simple_sentence_corrector(self, sentence): + sentence = sentence.replace('étant', 'éteins') + sentence = sentence.replace('dépeint', 'éteins') return sentence - def intent_corrector(self,intents): + def intent_corrector(self, intents): for intent in intents: if intent['label'] in DOMOTIQUE_OBJ_ON: intent['label'] = 'iot_hue_lighton' @@ -37,7 +42,7 @@ class AlexaIntent(): intent['label'] = 'iot_hue_lightoff' return intents - def predict(self,sentence): + def predict(self, sentence): sentence = self.simple_sentence_corrector(sentence) return { '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 + + label = classification[0]["label"] + + if label == "HEURE": + return {'intentName': 'GetTime'} + elif label == "DATE": + return {'intentName': 'GetDate'} + elif label == "ETEINDRE_CUISINE": + return {'intentName': 'GetTime', '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): @classmethod @@ -60,11 +106,11 @@ class TestAlexa(unittest.TestCase): self.assertEqual(res['intents'][0]['label'], 'iot_hue_lighton') self.assertEqual(res['entities'][0]['word'], '▁cuisine') - def test_bad_transcribe(self): res = self.alexa.predict("dépeint la cuisine") self.assertEqual(res['intents'][0]['label'], 'iot_hue_lightoff') self.assertEqual(res['entities'][0]['word'], '▁cuisine') + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/src/tools/simple_sentence_corrector.py b/src/tools/simple_sentence_corrector.py new file mode 100644 index 0000000..bfa90c0 --- /dev/null +++ b/src/tools/simple_sentence_corrector.py @@ -0,0 +1,4 @@ +def simple_sentence_corrector(sentence): + sentence = sentence.replace('étant', 'éteins') + sentence = sentence.replace('dépeint', 'éteins') + return sentence -- 2.48.1 From 86500c3164eac9496d04d68e54f7524986379762 Mon Sep 17 00:00:00 2001 From: tjiho Date: Sat, 1 Feb 2025 00:41:36 +0100 Subject: [PATCH 14/18] Add mpd ip as argument --- main.py | 10 +++++++++- src/ratatouille.py | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 6fbaa75..5b06378 100644 --- a/main.py +++ b/main.py @@ -24,6 +24,7 @@ parser = argparse.ArgumentParser( 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() @@ -47,7 +48,14 @@ logging.info("Loading ratatouilles modules") walle = Hass(config.hass_url, config.hass_token) yoda = None # Rhasspy(config.rhasspy_url) -mopidy = Mpd('10.10.10.8') + +mopidy = None + +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() diff --git a/src/ratatouille.py b/src/ratatouille.py index 1ac82ea..ae50f0c 100644 --- a/src/ratatouille.py +++ b/src/ratatouille.py @@ -23,7 +23,7 @@ class Ratatouille(): # schedule.every().day.at(config.hibernate_time).do(self.hibernate) # schedule.every().day.at(config.wakeup_time).do(self.clear_hibernate) # yoda.say('Ratatouille a bien démmaré') - logging.info('loaded') + # logging.info('loaded') def parse_rhasspy_command(self, payload): command = payload['intent']['intentName'] -- 2.48.1 From a44ea35b7dbd48bda86d7b91c5ad01a7bce494bc Mon Sep 17 00:00:00 2001 From: tjiho Date: Sat, 1 Feb 2025 01:10:51 +0100 Subject: [PATCH 15/18] Add date --- src/ratatouille.py | 2 ++ src/tools/date.py | 15 +++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/ratatouille.py b/src/ratatouille.py index ae50f0c..e0cd0a1 100644 --- a/src/ratatouille.py +++ b/src/ratatouille.py @@ -64,6 +64,8 @@ class Ratatouille(): 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": diff --git a/src/tools/date.py b/src/tools/date.py index ae929b2..ce75375 100644 --- a/src/tools/date.py +++ b/src/tools/date.py @@ -1,7 +1,10 @@ from src.tools.str import int_to_str -MONTHS = ['janvier','février','mars','avril','mai','juin','juillet','aout','septembre','octobre','novembre','decembre'] -WEEKDAY = ['lundi','mardi','mercredi','jeudi','vendredi','samedi','dimanche'] +MONTHS = ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', + 'juillet', 'aout', 'septembre', 'octobre', 'novembre', 'decembre'] +WEEKDAY = ['lundi', 'mardi', 'mercredi', + 'jeudi', 'vendredi', 'samedi', 'dimanche'] + def get_time(date): hour = date.hour @@ -13,18 +16,20 @@ def get_time(date): elif minute == 45: return 'il est '+format_hour(hour+1)+' moins le quart' elif minute == 15: - return 'il est '+format_hour(hour+1)+' et quart' + return 'il est '+format_hour(hour)+' et quart' else: return 'il est '+format_hour(hour)+' '+str(minute) return + def get_date(date): week_day = date.weekday() day = date.day month = date.month year = date.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): if hour == 12: @@ -34,8 +39,10 @@ def format_hour(hour): return int_to_str(hour, 'f') + ' heure' + def format_month(month): return MONTHS[month - 1] + def format_weekday(week_day): return WEEKDAY[week_day] -- 2.48.1 From 7a756137dcb6a3414ef1d51260c45f163faf9c3d Mon Sep 17 00:00:00 2001 From: tjiho Date: Sat, 1 Feb 2025 01:19:28 +0100 Subject: [PATCH 16/18] Fixes --- main.py | 3 ++- src/intent.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index 5b06378..b2562da 100644 --- a/main.py +++ b/main.py @@ -79,7 +79,8 @@ def run_prompt(): question = "empty" while question != "stop": question = input("?") - print(answer(question)) + if question != "stop": + print(answer(question)) logging.info("Ratatouille is ready !") diff --git a/src/intent.py b/src/intent.py index 3bd1614..5938bd1 100644 --- a/src/intent.py +++ b/src/intent.py @@ -60,7 +60,7 @@ class BertIntent(): classification = self.classifier(sentence) if classification[0]["score"] < 0.7: # score too low - return + return {'intentName': ''} label = classification[0]["label"] @@ -69,7 +69,7 @@ class BertIntent(): elif label == "DATE": return {'intentName': 'GetDate'} elif label == "ETEINDRE_CUISINE": - return {'intentName': 'GetTime', 'intentArg': ['cuisine']} + return {'intentName': 'LightOff', 'intentArg': ['cuisine']} elif label == "ETEINDRE_BUREAU": return {'intentName': 'LightOff', 'intentArg': ['bureau']} elif label == "ETEINDRE_SALON": -- 2.48.1 From 44861e8c994dc69541423f42a206d97f80d940a0 Mon Sep 17 00:00:00 2001 From: tjiho Date: Sat, 1 Feb 2025 01:26:24 +0100 Subject: [PATCH 17/18] Improve corrector --- src/tools/simple_sentence_corrector.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/simple_sentence_corrector.py b/src/tools/simple_sentence_corrector.py index bfa90c0..fec3de6 100644 --- a/src/tools/simple_sentence_corrector.py +++ b/src/tools/simple_sentence_corrector.py @@ -1,4 +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 -- 2.48.1 From 40a0db1aaa214b4ffcd0ea262575556c88548ee2 Mon Sep 17 00:00:00 2001 From: tjiho Date: Sat, 1 Feb 2025 01:34:16 +0100 Subject: [PATCH 18/18] readme --- README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d1f4b08..de5bd9e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,19 @@ # ratatouille -Projet ratatouille: interface tampon entre Rhasspy, Hass, Mpd, Bookstack, Systemd et autre \ No newline at end of file +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 +``` -- 2.48.1