├── .github
├── NOVA_Case.jpeg
├── NOVA_Case_Without.jpeg
├── NOVA_SketchUp_Case.gif
├── NOVA_SketchUp_Case.skp
├── github-logo.svg
└── pythonLogo.png
├── .gitignore
├── .vscode
├── extensions.json
└── launch.json
├── LICENSE
├── README.md
├── main.py
├── requirements.txt
├── settings.json
└── src
├── Audio.py
├── ModuleManager.py
├── NaturalLanguage
├── Corpus.py
├── Intent.py
├── Processor.py
└── ProcessorResult.py
├── Nova.py
├── SetTimeOut.py
├── TTS.py
├── libraries
└── pixel_ring
│ ├── apa102.py
│ ├── pattern.py
│ └── pixel_ring.py
├── models
└── README.md
├── plugins
├── chatbot
│ ├── corpus.json
│ └── index.py
├── count
│ ├── corpus.json
│ └── index.py
├── datedaytimeyear
│ ├── corpus.json
│ └── index.py
├── deviceipaddress
│ ├── corpus.json
│ └── index.py
├── homepodsounds
│ ├── index.py
│ └── mp3
│ │ ├── boot.mp3
│ │ ├── invalid.mp3
│ │ ├── listen.mp3
│ │ └── valid.mp3
├── led
│ └── index.py
├── mediastack
│ ├── corpus.json
│ └── index.py
├── random
│ ├── corpus.json
│ └── index.py
├── timer
│ ├── corpus.json
│ ├── index.py
│ └── mp3
│ │ └── alarm.mp3
└── volume
│ ├── corpus.json
│ └── index.py
└── porcupine
├── WakeWord_Ok-NOVA_fr_mac.ppn
├── WakeWord_Ok-NOVA_fr_nt.ppn
├── WakeWord_Ok-NOVA_fr_raspberry.ppn
└── porcupine_params_fr.pv
/.github/NOVA_Case.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HeyHeyChicken/NOVA-Python/64a4d2f04a7b6038e3fc4ff45a430301ab9c7b2c/.github/NOVA_Case.jpeg
--------------------------------------------------------------------------------
/.github/NOVA_Case_Without.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HeyHeyChicken/NOVA-Python/64a4d2f04a7b6038e3fc4ff45a430301ab9c7b2c/.github/NOVA_Case_Without.jpeg
--------------------------------------------------------------------------------
/.github/NOVA_SketchUp_Case.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HeyHeyChicken/NOVA-Python/64a4d2f04a7b6038e3fc4ff45a430301ab9c7b2c/.github/NOVA_SketchUp_Case.gif
--------------------------------------------------------------------------------
/.github/NOVA_SketchUp_Case.skp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HeyHeyChicken/NOVA-Python/64a4d2f04a7b6038e3fc4ff45a430301ab9c7b2c/.github/NOVA_SketchUp_Case.skp
--------------------------------------------------------------------------------
/.github/github-logo.svg:
--------------------------------------------------------------------------------
1 |
30 |
--------------------------------------------------------------------------------
/.github/pythonLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HeyHeyChicken/NOVA-Python/64a4d2f04a7b6038e3fc4ff45a430301ab9c7b2c/.github/pythonLogo.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | *.pyc
3 | /src/audio/*
4 | src/models/model/*
5 | src/models/*.zip
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "ms-python.python"
4 | ]
5 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Python",
9 | "type": "python",
10 | "request": "launch",
11 | "program": "main.py",
12 | "console": "integratedTerminal",
13 | "justMyCode": true
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) Antoine Duval.
2 |
3 | Nova-Python is a software distributed by Antoine Duval, a French
4 | developper, born on 22/12/1993 at Caen in France and based in France,
5 | at 16 domaine du Jonquay 76570 Mesnil-Panneville.
6 |
7 | This software is protected by applicable copyright laws,
8 | including international treaties.
9 |
10 | You may use this program if your use involves only such purposes as
11 | research, private study, and evaluation.
12 |
13 | You are prohibited from installing & copying,
14 | if i'ts for commercial purposes.
15 |
16 | ANTOINE DUVAL PROVIDES THIS SOFTWARE ON AN "AS IS" BASIS, WITHOUT
17 | WARRANTIES OR CONDITIONS OF ANY KIND. IN NO EVENT AND UNDER NO LEGAL
18 | THEORY, SHALL ANTOINE DUVAL BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
19 | DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY
20 | CHARACTER ARISING FROM USE OR INABILITY TO USE THIS SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | **NOVA** is an efficient personal assistant made with python.
6 |
7 | [](https://discord.gg/pkWbhDn)
8 |
9 |
10 | ## 👋 Introduction
11 |
12 | **NOVA-Python** is an open-source personal assistant that you can host on your computer.
13 | You can communicate with it orally, most of his skills are designed to work offline to protect your privacy.
14 |
15 | ## 🔧 Prerequisites
16 |
17 | [ Python](https://www.python.org/downloads/)
18 |
19 | ## ⬇️ Installation
20 |
21 | Clone it directly from GitHub.
22 | ```
23 | git clone https://github.com/HeyHeyChicken/Nova-python.git
24 | ```
25 | Install python dependencies.
26 | ```
27 | pip install -r requirements.txt
28 | ```
29 |
30 | ## 🚀 Usage
31 |
32 | Launch this command.
33 | ```
34 | python main.py
35 | ```
36 |
37 | ## 🫵 Support and contribution
38 |
39 | I provide support for all users through [GitHub issues](//github.com/HeyHeyChicken/Nova-python/issues).
40 |
41 | ## 💻 Compatibility
42 |
43 | NOVA has only been officially tested Raspberry Pi 4.
44 |
45 | ## 📦 A great case
46 |
47 | Hey, I made a case for my NOVA hosted in a Raspberry PI 4.
48 | You can find the SketchUp plan in NOVA/.github/NOVA_SketchUp_Case.skp.
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | Created by [Antoine Duval (HeyHeyChicken)](//antoine.cuffel.fr) with ❤ and ☕ (chocolate) in [Mesnil-Panneville](//en.wikipedia.org/wiki/Mesnil-Panneville).
57 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | rootPath: str = os.path.dirname(os.path.abspath(__file__))
4 |
5 | #from src.ModuleManager import ModuleManager
6 | #moduleManager = ModuleManager(rootPath)
7 |
8 | from src.Nova import Nova
9 | nova = Nova(rootPath)
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | print-color
2 | vosk
3 | sounddevice
4 | pvporcupine
5 | pyaudio
6 | events
7 | pygame
8 | pyalsaaudio
9 | pyusb
--------------------------------------------------------------------------------
/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "os": "raspberry",
3 | "led_brightness": 100,
4 | "porcupine": {
5 | "key": ""
6 | }
7 | }
--------------------------------------------------------------------------------
/src/Audio.py:
--------------------------------------------------------------------------------
1 | import sys, os
2 | sys.stdout = open(os.devnull, 'w')
3 |
4 | import pygame
5 |
6 | sys.stdout = sys.__stdout__
7 |
8 | class Audio:
9 | mixers = []
10 |
11 | def play(self, path: str):
12 | pygame.init();
13 | mixer = pygame.mixer
14 | mixer.init()
15 | mixer.music.load(path)
16 | mixer.music.set_volume(1)
17 |
18 | self.mixers.append(mixer)
19 | mixer.music.play()
20 |
21 | def pauseAll(self):
22 | for mixer in self.mixers:
23 | mixer.music.pause()
--------------------------------------------------------------------------------
/src/ModuleManager.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | import json
4 | import subprocess
5 | import pkg_resources
6 |
7 | class ModuleManager:
8 | """ This class is used to check the correct installation of the modules required for the project. """
9 | def __init__(self, rootPath: str):
10 | self.settings: None
11 |
12 | self.checkOS(rootPath)
13 | self.checkPackages()
14 |
15 | def checkOS(self, rootPath: str):
16 | settingsPath = os.path.join(rootPath, "settings.json")
17 | self.settings = json.load(open(settingsPath, encoding='utf-8'))
18 |
19 | if self.settings["os"] == "":
20 | self.settings["os"] = os.name
21 |
22 | if self.settings["os"] == "posix":
23 | print("In order to configure NOVA, can you tell us the OS you are using?")
24 | print("1) Raspberry")
25 | print("2) Mac")
26 | self.setOSLoop()
27 |
28 | with open(settingsPath, 'w') as outfile:
29 | json.dump(self.settings, outfile)
30 |
31 | def setOSLoop(self):
32 | strInput: str = input("OS index? (1, 2)")
33 | if strInput.isdigit():
34 | intInput: int = int(strInput)
35 | if intInput == 1:
36 | self.settings["os"] = "raspberry"
37 | else:
38 | if intInput == 2:
39 | self.settings["os"] = "mac"
40 | if self.settings["os"] == "posix":
41 | print("Please provide an integer between 1 and 2.")
42 | self.setOSLoop()
43 | else:
44 | print("Please provide an integer.")
45 | self.setOSLoop()
46 |
47 | def checkPackages(self):
48 | """ This function checks that the modules required for the project are installed. If the modules are not installed, they are installed automatically. """
49 | print("Checking modules installation...")
50 |
51 | # List of modules to install (name/optionnal version)
52 | packages: dict[str, str] = {
53 | "print-color": "",
54 | "vosk": "",
55 | "sounddevice": "",
56 | #"playsound": "1.2.2",
57 | "pvporcupine": "",
58 | "pyaudio": "",
59 | "events": "",
60 | "pygame":""
61 | }
62 |
63 | for package in packages:
64 | self.checkPackage(package, packages[package])
65 | if self.settings["os"] == "mac":
66 | self.checkPackage("pyobjc", "")
67 | self.checkPackage("osascript", "")
68 | elif self.settings["os"] == "raspberry":
69 | self.checkPackage("pyalsaaudio", "")
70 | elif self.settings["os"] == "nt": # Windows
71 | self.checkPackage("pycaw", "")
72 |
73 | def checkPackage(self, package: str, version: str):
74 | installed = {pkg.key for pkg in pkg_resources.working_set}
75 | print(f" Checking module {package!r}...")
76 | if (package in installed):
77 | print(f" Module {package!r} is already installed.")
78 | else:
79 | print(f" Module {package!r} isn't installed.")
80 | print(f" Installing module {package!r}...")
81 | executable: str = "pip"
82 | if self.settings["os"] == "raspberry":
83 | executable: str = "pip3"
84 | packageName: str = package
85 | packageVersion: str = version
86 | if packageVersion != "":
87 | packageName += "==" + packageVersion
88 | subprocess.check_call([sys.executable, '-m', executable, 'install', packageName, '--no-warn-script-location'], stdout=subprocess.DEVNULL)
89 | print(f" Module {package!r} is now installed.")
--------------------------------------------------------------------------------
/src/NaturalLanguage/Corpus.py:
--------------------------------------------------------------------------------
1 | class Corpus:
2 | def __init__(
3 | self, intent: str,
4 | utterances: str(),
5 | answers: str(),
6 | #errors: dict[str, str()] = {}
7 | ):
8 | self.intent: str = intent
9 | self.utterances: str() = utterances
10 | self.answers: str() = answers
11 | #self.errors: list[str] = errors
--------------------------------------------------------------------------------
/src/NaturalLanguage/Intent.py:
--------------------------------------------------------------------------------
1 | import math
2 | import random
3 |
4 | class Intent:
5 | def __init__(self, name: str):
6 | self.name: str = name
7 | self.documents: str() = []
8 | self.answers: str() = []
9 | self.errors = {}
10 | self.actions = []
11 | self.variables = {}
12 |
13 | def addDocuments(self, utterances: str()):
14 | self.documents = self.documents + utterances
15 |
16 | def addAnswers(self, answers: str()):
17 | self.answers = self.answers + answers
18 |
19 | def addActions(self, actions):
20 | self.actions = self.actions + actions
21 |
22 | def addErrors(self, errorsName: str, errors: str()):
23 | if not hasattr(self.errors, errorsName):
24 | self.errors[errorsName] = []
25 | self.errors[errorsName] = self.errors[errorsName] + errors
26 |
27 | def answer(self, answer = None):
28 | if answer is None:
29 | answer = self.answers[math.floor(random.random() * len(self.answers))]
30 | if answer is not None:
31 | for variable in self.variables:
32 | value: str = str(self.variables[variable])
33 | answer = answer.replace("%" + variable + "%", value)
34 | return answer
35 |
36 | def error(self, errorName: str):
37 | text = self.errors[errorName][math.floor(random.random() * len(self.errors[errorName]))];
38 | if text is not None:
39 | for variable in self.variables:
40 | text = text.replace("%" + variable + "%", self.variables[variable])
41 | return text
--------------------------------------------------------------------------------
/src/NaturalLanguage/Processor.py:
--------------------------------------------------------------------------------
1 | import math
2 | import json
3 | import re
4 | from src.NaturalLanguage.Corpus import Corpus
5 | from src.NaturalLanguage.Intent import Intent
6 | from src.NaturalLanguage.ProcessorResult import ProcessorResult
7 |
8 | class Processor:
9 | def __init__(self):
10 | self.intents: dict[str, Intent] = {}
11 | self.__checkIntentExistence("none")
12 |
13 | def loadJson(self, jsonPath: str):
14 | allCorpus: list[Corpus] = []
15 | data = json.load(open(jsonPath, encoding='utf-8'))
16 | for corpus in data:
17 | allCorpus.append(Corpus(
18 | corpus['intent'],
19 | corpus['utterances'],
20 | corpus['answers'] if 'answers' in corpus else []
21 | ))
22 | self.loadCorpus(allCorpus)
23 |
24 | # Load
25 | # This function loads a corpus.
26 | def loadCorpus(self, corpus): #list[Corpus]
27 | for c in corpus:
28 | if c.utterances is not None:
29 | self.addDocuments(c.intent, c.utterances)
30 | if c.answers is not None:
31 | self.addAnswers(c.intent, c.answers)
32 | """
33 | if c.errors is not None:
34 | for code in c.errors:
35 | self.addErrors(c.intent, code, c.errors[code])
36 | """
37 |
38 | #region Documents
39 |
40 | # This function adds a way to call an action.
41 | def addDocument(self, intentName: str, utterance: str):
42 | self.addDocuments(intentName, [utterance])
43 |
44 | # This function adds ways to call an action.
45 | def addDocuments(self, intentName: str, utterances: str()):
46 | self.__checkIntentExistence(intentName)
47 | self.intents[intentName].addDocuments(utterances)
48 |
49 | #endregion
50 |
51 | #region Answers
52 |
53 | # This function adds a response to an action.
54 | def addAnswer(self, intentName: str, answer: str):
55 | self.addAnswers(intentName, [answer])
56 |
57 | # This function adds responses to an action.
58 | def addAnswers(self, intentName: str, answers: str()):
59 | self.__checkIntentExistence(intentName)
60 | self.intents[intentName].addAnswers(answers)
61 |
62 | #endregion
63 |
64 | # Errors
65 | def addErrors(self, intentName: str, errorsName: str, errors: str()):
66 | self.__checkIntentExistence(intentName)
67 | self.intents[intentName].addErrors(errorsName, errors)
68 |
69 | #region Actions
70 |
71 | # This function adds an action. This action will be triggered when a document is called.
72 | def addAction(self, intentName: str, _action):
73 | self.addActions(intentName, [_action])
74 |
75 | # This function adds actions. These actions will be triggered when a document is called.
76 | def addActions(self, intentName: str, actions):
77 | self.__checkIntentExistence(intentName)
78 | self.intents[intentName].addActions(actions)
79 |
80 | #endregion
81 |
82 | # Process
83 | # This function executes the intent that matches the customer's phrase.
84 | def process(self, utterance: str):
85 | splittedSay = utterance.split(" ")
86 | intent_name: str = None
87 | variables = []
88 | result: ProcessorResult = ProcessorResult(utterance)
89 |
90 | break_out_flag: bool = False
91 | for loop_intent_name in self.intents:
92 | for document in self.intents[loop_intent_name].documents:
93 | processResult = self.__process(document, splittedSay)
94 | if processResult != False:
95 | intent_name = loop_intent_name
96 | variables = processResult
97 | break_out_flag = True
98 | break
99 | if break_out_flag:
100 | break
101 |
102 | break_out_flag = False
103 | if intent_name == None:
104 | for loop_intent_name in self.intents:
105 | for document in self.intents[loop_intent_name].documents:
106 | processResult = self.__process(document, splittedSay, False)
107 | if processResult != False:
108 | intent_name = loop_intent_name
109 | variables = processResult
110 | break_out_flag = True
111 | break
112 | if break_out_flag:
113 | break
114 |
115 | if intent_name == None:
116 | intent_name = "none"
117 | else:
118 | result.answers = self.intents[intent_name].answers
119 | result.intent = intent_name
120 |
121 | for variable in variables:
122 | if re.match(r'^-?\d+(?:\.\d+)$', variables[variable]) is not None:
123 | VALUE = float(variables[variable])
124 | if not math.isnan(VALUE):
125 | variables[variable] = VALUE
126 |
127 | self.intents[intent_name].variables = variables
128 | result.variables = self.intents[intent_name].variables
129 | if result.answers != None:
130 | if len(result.answers) > 0:
131 | result.answer = self.intents[intent_name].answer()
132 |
133 | if len(self.intents[intent_name].actions) > 0:
134 | for action in self.intents[intent_name].actions:
135 | action(self.intents[intent_name], result)
136 | result.answer = None
137 |
138 | return result
139 |
140 | # This function checks if an intent exists, if not, it creates it.
141 | def __checkIntentExistence(self, intentName):
142 | if not intentName in self.intents:
143 | self.intents[intentName] = Intent(intentName)
144 |
145 | # This function is an essential loop of the "process" function.
146 | def __process(self, sentence: str, splitedSay: str(), asc: bool = True):
147 | sentence = sentence.lower()
148 | splittedSentense = sentence.split(" ")
149 |
150 | if asc == False:
151 | splittedSentense.reverse()
152 | splitedSay.reverse()
153 |
154 | variables = {}
155 | variables_position: int = 0
156 | variable_name: str = None
157 | ok: int = 0 # cette variablre représente le nombre de mots ou variables correspondantes à la phrase testée.
158 |
159 | break_out_flag: bool = False
160 | sentense: str
161 | for sentenseIndex, sentense in enumerate(splittedSentense):
162 | # SI LE MOT EST UNE VARIABLE
163 | if sentense[0] == "{" and sentense[-1] == "}":
164 | without_percent = sentense[1: len(sentense) - 1].split("|")
165 | variable_name = without_percent[0]
166 | max_words: int = 0
167 | if len(without_percent) > 1:
168 | max_words = int(without_percent[1])
169 |
170 | variables[variable_name] = []
171 | next_word = None
172 | if len(splittedSentense) > sentenseIndex + 1:
173 | next_word = splittedSentense[sentenseIndex + 1]
174 | while len(splitedSay) > sentenseIndex + variables_position and splitedSay[sentenseIndex + variables_position] != next_word and splitedSay[sentenseIndex + variables_position] != None:
175 | variables[variable_name].append(splitedSay[sentenseIndex + variables_position])
176 | variables_position = variables_position + 1
177 | if max_words > 0:
178 | if len(variables[variable_name]) > max_words:
179 | break_out_flag = True
180 | break
181 | variables_position = variables_position - 1
182 | if asc == False:
183 | variables[variable_name] = variables[variable_name].reverse()
184 |
185 | if variables[variable_name] is not None:
186 | variables[variable_name] = " ".join(variables[variable_name])
187 | ok = ok + 1
188 | # SI LE MOT N'EST PAS UNE VARIABLE
189 | else:
190 | # SI LE MOT EN COUR EST EGAL AU MOT QUE L'ON A ENTENDU
191 | if len(splitedSay) > sentenseIndex + variables_position:
192 | if sentense == splitedSay[sentenseIndex + variables_position]:
193 | ok = ok + 1
194 | else:
195 | break
196 | if break_out_flag:
197 | break
198 |
199 | if ok == len(splittedSentense):
200 | return variables
201 |
202 | return False
--------------------------------------------------------------------------------
/src/NaturalLanguage/ProcessorResult.py:
--------------------------------------------------------------------------------
1 | class ProcessorResult:
2 | def __init__(self, utterance: str):
3 | self.intent: str = None
4 | self.utterance: str = utterance
5 | self.variables: str() = []
6 | self.answers: str() = []
7 | self.answer: str = None
--------------------------------------------------------------------------------
/src/Nova.py:
--------------------------------------------------------------------------------
1 | import sounddevice
2 | import vosk
3 | from vosk import SetLogLevel
4 | import queue
5 | import json
6 | import sys
7 | import os
8 | from threading import Thread
9 | import struct
10 | import pyaudio
11 | from events import Events
12 | import pvporcupine
13 | from print_color import print
14 | from src.NaturalLanguage.Processor import Processor
15 | from src.NaturalLanguage.ProcessorResult import ProcessorResult
16 | from src.TTS import TTS
17 | from src.Audio import Audio
18 | from gpiozero import LED as GPIO_LED
19 |
20 | class Nova:
21 | def __init__(self, rootPath: str):
22 | self.events = Events()
23 | self.model = None
24 | self.samplerate = None
25 | self.q = queue.Queue()
26 | self.dump_fn = None
27 | self.device = None
28 | self.audio = Audio()
29 | self.tts = TTS(self.audio, True)
30 | self.naturalLanguageProcessor = Processor()
31 | self.microMode: int = 1 # 0 = nothing, 1 = keyword, 2 = listening
32 | #self.haveWakeWordDetection: bool = False
33 |
34 | settingsPath: str = os.path.join(rootPath, "settings.json")
35 | self.settings = json.load(open(settingsPath, encoding='utf-8'))
36 |
37 | power = GPIO_LED(5)
38 | power.on()
39 |
40 | self.print("Welcome to NOVA!")
41 |
42 | #region Plugins loading
43 |
44 | self.print("Loading plugins...", "white")
45 | pluginsDirectoryPath: str =os.path.join(rootPath, "src", "plugins")
46 | for pluginFolderName in os.listdir(pluginsDirectoryPath):
47 | pluginFolderPath: str = os.path.join(pluginsDirectoryPath, pluginFolderName)
48 | if os.path.isdir(pluginFolderPath):
49 | print(" - " + pluginFolderName, color='white')
50 | module = __import__("src.plugins." + pluginFolderName + ".index", fromlist=['Plugin'])
51 | pluginClass = getattr(module, "Plugin")
52 | pluginClass(self.naturalLanguageProcessor, self.audio, self.TTS, self.events, self.settings)
53 | self.print("Plugins are loaded.")
54 |
55 | #endregion
56 |
57 | if self.settings["porcupine"]["key"] == "":
58 | self.print("Please define in '/settings.json file > porcupine > key' the Porcupine key.", "red")
59 | self.print("You can get one for free from Picovoice Console (https://console.picovoice.ai/)", "red")
60 | return
61 |
62 | porcupinePath: str = os.path.join(rootPath, "src", "porcupine")
63 | self.porcupine = pvporcupine.create(
64 | access_key=self.settings["porcupine"]["key"],
65 | keyword_paths=[os.path.join(porcupinePath, "WakeWord_Ok-NOVA_fr_" + self.settings["os"] + ".ppn")],
66 | model_path=os.path.join(porcupinePath, "porcupine_params_fr.pv")
67 | )
68 |
69 | pyAudio = pyaudio.PyAudio()
70 | audioStream = pyAudio.open(
71 | rate = self.porcupine.sample_rate,
72 | channels = 1,
73 | format = pyaudio.paInt16,
74 | input = True,
75 | frames_per_buffer = self.porcupine.frame_length
76 | )
77 | info = pyAudio.get_default_input_device_info()
78 |
79 | Thread(target=self.events.onBooting).start()
80 |
81 | try:
82 | deviceInfo = sounddevice.query_devices(info['name'])
83 | except:
84 | self.print("No input device found.", "red")
85 | return
86 |
87 | self.samplerate = int(deviceInfo['default_samplerate'])
88 |
89 | self.print("Speech to text model loading...", "white")
90 | modelFolderPath: str = os.path.join(rootPath, "src", "models", "model")
91 | if not os.path.exists(modelFolderPath):
92 | self.print("In order to understand what you are telling it, NOVA (using Vosk) needs a model.", "red")
93 | self.print("You can download one of them here : https://alphacephei.com/vosk/models", "red")
94 | self.print("After downloading, unzip the contents of your model's archive here : /src/models/model/*", "red")
95 | return
96 | SetLogLevel(-1)
97 | self.model = vosk.Model("src/models/model")
98 | self.print("Speech to text model loaded.")
99 |
100 | Thread(target=self.events.onBooted).start()
101 |
102 | self.processTextFromUser("quel est votre ip")
103 |
104 | with sounddevice.RawInputStream(samplerate=self.samplerate, blocksize = self.porcupine.frame_length, device=self.device, dtype='int16', channels=1, latency='high', callback=self.callback):
105 | print('#' * 80)
106 | print('Press Ctrl+C to stop the recording')
107 | print('#' * 80)
108 |
109 | rec = vosk.KaldiRecognizer(self.model, self.samplerate)
110 | while True:
111 | if self.microMode == 1:
112 | pcm = audioStream.read(self.porcupine.frame_length, exception_on_overflow = False)
113 | pcm = struct.unpack_from("h" * self.porcupine.frame_length, pcm)
114 | keyword_index = self.porcupine.process(pcm)
115 | if keyword_index >= 0:
116 | Thread(target=self.events.onTrigger).start()
117 | self.audio.pauseAll()
118 | self.microMode = 2
119 |
120 | if self.microMode == 2:
121 | data = self.q.get()
122 | if rec.AcceptWaveform(data):
123 | r = eval(rec.Result())
124 | t = r["text"]
125 | if t:
126 | self.processTextFromUser(t)
127 | if self.dump_fn is not None and len(t) > 5:
128 | self.dump_fn.write(t+'\n')
129 |
130 | def callback(self, indata, frames, time, status):
131 | """ This function is triggered when the user speaks. """
132 | if status:
133 | print(status, file=sys.stderr)
134 | if self.microMode == 2:
135 | self.q.put(bytes(indata))
136 |
137 | def processTextFromUser(self, text: str):
138 | """ This function is triggered when the user has finished his sentence. """
139 | self.print("<- " + text, "white")
140 |
141 | back: ProcessorResult = self.naturalLanguageProcessor.process(text)
142 | Thread(target=self.events.onProcessed).start()
143 | self.microMode = 1
144 | if back.intent != "none":
145 | if back.answer != None:
146 | self.microMode = 0
147 | self.TTS(back.answer)
148 |
149 | def TTS(self, message):
150 | self.print("-> " + message)
151 | self.tts.TTS(message, self.TTSFinish)
152 |
153 | def TTSFinish(self):
154 | self.microMode = 1
155 |
156 | def print(self, message, tagColor: str = "green"):
157 | print(message, tag='NOVA', tag_color=tagColor, color='white')
--------------------------------------------------------------------------------
/src/SetTimeOut.py:
--------------------------------------------------------------------------------
1 | from threading import Thread
2 |
3 | class SetTimeOut:
4 | def __init__(self,func:"function",timing:"miliseconds", args = []):
5 | self.func=func
6 | self.timing=timing*2000
7 | self.args=args
8 | self.__state=True
9 | self.__counter=0
10 | Thread(target=self.__run).start()
11 |
12 | def clearTimeOut(self):
13 | self.__state=False
14 |
15 | def __str__(self):
16 | if not self.__state:
17 | return f""
18 | return f""
19 |
20 | def __run(self):
21 | try:
22 | while self.__state:
23 | if self.__counter>=self.timing:
24 | self.func(self.args)
25 | self.__state=False
26 | else:
27 | self.__counter+=1
28 | except RuntimeError:
29 | self.__state=False
--------------------------------------------------------------------------------
/src/TTS.py:
--------------------------------------------------------------------------------
1 | from threading import Thread
2 | from urllib.request import urlopen
3 | from pathlib import Path
4 | import os
5 | import datetime
6 | import urllib.parse
7 | from src.Audio import Audio
8 |
9 | class TTS:
10 | url: str = "http://192.168.1.12/api"
11 |
12 | def __init__(self, audio: Audio, mp3: bool = False):
13 | self.audio: Audio = audio
14 | self.mp3: bool = mp3
15 |
16 | def TTS(self, message: str, callback):
17 | folder: str = os.path.join(os.path.dirname(__file__), "audio")
18 |
19 | # If the "mp3" folder exists, delete its contents
20 | if os.path.exists(folder):
21 | oldFiles: str() = os.listdir(folder)
22 | for oldFile in oldFiles:
23 | os.remove(os.path.join(folder, oldFile))
24 |
25 | # We create the folder containing the voice files if it does not exist.
26 | Path(folder).mkdir(parents=True, exist_ok=True)
27 |
28 | # We are looking for a temporary file name.
29 | now: datetime = datetime.datetime.now()
30 | epoch: datetime = datetime.datetime.utcfromtimestamp(0)
31 | name: int = int((now - epoch).total_seconds() * 1000000)
32 |
33 | fileExtension: str = ".aiff"
34 | if self.mp3:
35 | fileExtension = ".mp3"
36 |
37 | localFileName: str = os.path.join(folder, str(name) + fileExtension)
38 |
39 | # We download the voice file from the TTS server.
40 | finalURL: str = self.url + "?mp3=true&sentence=" + urllib.parse.quote(message)
41 | mp3file = urlopen(finalURL)
42 | with open(localFileName,'wb') as output:
43 | output.write(mp3file.read())
44 |
45 | # We play the audio file.
46 | Thread(target=self.__playMP3, args=([],localFileName, callback)).start()
47 |
48 | def __playMP3(self, nothing, mp3Path: str, callback):
49 | self.audio.play(mp3Path)
50 | callback()
--------------------------------------------------------------------------------
/src/libraries/pixel_ring/apa102.py:
--------------------------------------------------------------------------------
1 | """
2 | from https://github.com/tinue/APA102_Pi
3 | This is the main driver module for APA102 LEDs
4 | """
5 | import spidev
6 | from math import ceil
7 |
8 | RGB_MAP = { 'rgb': [3, 2, 1], 'rbg': [3, 1, 2], 'grb': [2, 3, 1],
9 | 'gbr': [2, 1, 3], 'brg': [1, 3, 2], 'bgr': [1, 2, 3] }
10 |
11 | class APA102:
12 | """
13 | Driver for APA102 LEDS (aka "DotStar").
14 |
15 | (c) Martin Erzberger 2016-2017
16 |
17 | My very first Python code, so I am sure there is a lot to be optimized ;)
18 |
19 | Public methods are:
20 | - set_pixel
21 | - set_pixel_rgb
22 | - show
23 | - clear_strip
24 | - cleanup
25 |
26 | Helper methods for color manipulation are:
27 | - combine_color
28 | - wheel
29 |
30 | The rest of the methods are used internally and should not be used by the
31 | user of the library.
32 |
33 | Very brief overview of APA102: An APA102 LED is addressed with SPI. The bits
34 | are shifted in one by one, starting with the least significant bit.
35 |
36 | An LED usually just forwards everything that is sent to its data-in to
37 | data-out. While doing this, it remembers its own color and keeps glowing
38 | with that color as long as there is power.
39 |
40 | An LED can be switched to not forward the data, but instead use the data
41 | to change it's own color. This is done by sending (at least) 32 bits of
42 | zeroes to data-in. The LED then accepts the next correct 32 bit LED
43 | frame (with color information) as its new color setting.
44 |
45 | After having received the 32 bit color frame, the LED changes color,
46 | and then resumes to just copying data-in to data-out.
47 |
48 | The really clever bit is this: While receiving the 32 bit LED frame,
49 | the LED sends zeroes on its data-out line. Because a color frame is
50 | 32 bits, the LED sends 32 bits of zeroes to the next LED.
51 | As we have seen above, this means that the next LED is now ready
52 | to accept a color frame and update its color.
53 |
54 | So that's really the entire protocol:
55 | - Start by sending 32 bits of zeroes. This prepares LED 1 to update
56 | its color.
57 | - Send color information one by one, starting with the color for LED 1,
58 | then LED 2 etc.
59 | - Finish off by cycling the clock line a few times to get all data
60 | to the very last LED on the strip
61 |
62 | The last step is necessary, because each LED delays forwarding the data
63 | a bit. Imagine ten people in a row. When you yell the last color
64 | information, i.e. the one for person ten, to the first person in
65 | the line, then you are not finished yet. Person one has to turn around
66 | and yell it to person 2, and so on. So it takes ten additional "dummy"
67 | cycles until person ten knows the color. When you look closer,
68 | you will see that not even person 9 knows its own color yet. This
69 | information is still with person 2. Essentially the driver sends additional
70 | zeroes to LED 1 as long as it takes for the last color frame to make it
71 | down the line to the last LED.
72 | """
73 | # Constants
74 | MAX_BRIGHTNESS = 0b11111 # Safeguard: Set to a value appropriate for your setup
75 | LED_START = 0b11100000 # Three "1" bits, followed by 5 brightness bits
76 |
77 | def __init__(self, num_led, global_brightness=MAX_BRIGHTNESS,
78 | order='rgb', bus=0, device=1, max_speed_hz=8000000):
79 | self.num_led = num_led # The number of LEDs in the Strip
80 | order = order.lower()
81 | self.rgb = RGB_MAP.get(order, RGB_MAP['rgb'])
82 | # Limit the brightness to the maximum if it's set higher
83 | if global_brightness > self.MAX_BRIGHTNESS:
84 | self.global_brightness = self.MAX_BRIGHTNESS
85 | else:
86 | self.global_brightness = global_brightness
87 |
88 | self.leds = [self.LED_START,0,0,0] * self.num_led # Pixel buffer
89 | self.spi = spidev.SpiDev() # Init the SPI device
90 | self.spi.open(bus, device) # Open SPI port 0, slave device (CS) 1
91 | # Up the speed a bit, so that the LEDs are painted faster
92 | if max_speed_hz:
93 | self.spi.max_speed_hz = max_speed_hz
94 |
95 | def clock_start_frame(self):
96 | """Sends a start frame to the LED strip.
97 |
98 | This method clocks out a start frame, telling the receiving LED
99 | that it must update its own color now.
100 | """
101 | self.spi.xfer2([0] * 4) # Start frame, 32 zero bits
102 |
103 |
104 | def clock_end_frame(self):
105 | """Sends an end frame to the LED strip.
106 |
107 | As explained above, dummy data must be sent after the last real colour
108 | information so that all of the data can reach its destination down the line.
109 | The delay is not as bad as with the human example above.
110 | It is only 1/2 bit per LED. This is because the SPI clock line
111 | needs to be inverted.
112 |
113 | Say a bit is ready on the SPI data line. The sender communicates
114 | this by toggling the clock line. The bit is read by the LED
115 | and immediately forwarded to the output data line. When the clock goes
116 | down again on the input side, the LED will toggle the clock up
117 | on the output to tell the next LED that the bit is ready.
118 |
119 | After one LED the clock is inverted, and after two LEDs it is in sync
120 | again, but one cycle behind. Therefore, for every two LEDs, one bit
121 | of delay gets accumulated. For 300 LEDs, 150 additional bits must be fed to
122 | the input of LED one so that the data can reach the last LED.
123 |
124 | Ultimately, we need to send additional numLEDs/2 arbitrary data bits,
125 | in order to trigger numLEDs/2 additional clock changes. This driver
126 | sends zeroes, which has the benefit of getting LED one partially or
127 | fully ready for the next update to the strip. An optimized version
128 | of the driver could omit the "clockStartFrame" method if enough zeroes have
129 | been sent as part of "clockEndFrame".
130 | """
131 |
132 | self.spi.xfer2([0xFF] * 4)
133 |
134 | # Round up num_led/2 bits (or num_led/16 bytes)
135 | #for _ in range((self.num_led + 15) // 16):
136 | # self.spi.xfer2([0x00])
137 |
138 |
139 | def clear_strip(self):
140 | """ Turns off the strip and shows the result right away."""
141 |
142 | for led in range(self.num_led):
143 | self.set_pixel(led, 0, 0, 0)
144 | self.show()
145 |
146 |
147 | def set_pixel(self, led_num, red, green, blue, bright_percent=100):
148 | """Sets the color of one pixel in the LED stripe.
149 |
150 | The changed pixel is not shown yet on the Stripe, it is only
151 | written to the pixel buffer. Colors are passed individually.
152 | If brightness is not set the global brightness setting is used.
153 | """
154 | if led_num < 0:
155 | return # Pixel is invisible, so ignore
156 | if led_num >= self.num_led:
157 | return # again, invisible
158 |
159 | # Calculate pixel brightness as a percentage of the
160 | # defined global_brightness. Round up to nearest integer
161 | # as we expect some brightness unless set to 0
162 | brightness = int(ceil(bright_percent*self.global_brightness/100.0))
163 |
164 | # LED startframe is three "1" bits, followed by 5 brightness bits
165 | ledstart = (brightness & 0b00011111) | self.LED_START
166 |
167 | start_index = 4 * led_num
168 | self.leds[start_index] = ledstart
169 | self.leds[start_index + self.rgb[0]] = red
170 | self.leds[start_index + self.rgb[1]] = green
171 | self.leds[start_index + self.rgb[2]] = blue
172 |
173 |
174 | def set_pixel_rgb(self, led_num, rgb_color, bright_percent=100):
175 | """Sets the color of one pixel in the LED stripe.
176 |
177 | The changed pixel is not shown yet on the Stripe, it is only
178 | written to the pixel buffer.
179 | Colors are passed combined (3 bytes concatenated)
180 | If brightness is not set the global brightness setting is used.
181 | """
182 | self.set_pixel(led_num, (rgb_color & 0xFF0000) >> 16,
183 | (rgb_color & 0x00FF00) >> 8, rgb_color & 0x0000FF,
184 | bright_percent)
185 |
186 |
187 | def rotate(self, positions=1):
188 | """ Rotate the LEDs by the specified number of positions.
189 |
190 | Treating the internal LED array as a circular buffer, rotate it by
191 | the specified number of positions. The number could be negative,
192 | which means rotating in the opposite direction.
193 | """
194 | cutoff = 4 * (positions % self.num_led)
195 | self.leds = self.leds[cutoff:] + self.leds[:cutoff]
196 |
197 |
198 | def show(self):
199 | """Sends the content of the pixel buffer to the strip.
200 |
201 | Todo: More than 1024 LEDs requires more than one xfer operation.
202 | """
203 | self.clock_start_frame()
204 | # xfer2 kills the list, unfortunately. So it must be copied first
205 | # SPI takes up to 4096 Integers. So we are fine for up to 1024 LEDs.
206 | data = list(self.leds)
207 | while data:
208 | self.spi.xfer2(data[:32])
209 | data = data[32:]
210 | self.clock_end_frame()
211 |
212 |
213 | def cleanup(self):
214 | """Release the SPI device; Call this method at the end"""
215 |
216 | self.spi.close() # Close SPI port
217 |
218 | @staticmethod
219 | def combine_color(red, green, blue):
220 | """Make one 3*8 byte color value."""
221 |
222 | return (red << 16) + (green << 8) + blue
223 |
224 |
225 | def wheel(self, wheel_pos):
226 | """Get a color from a color wheel; Green -> Red -> Blue -> Green"""
227 |
228 | if wheel_pos > 255:
229 | wheel_pos = 255 # Safeguard
230 | if wheel_pos < 85: # Green -> Red
231 | return self.combine_color(wheel_pos * 3, 255 - wheel_pos * 3, 0)
232 | if wheel_pos < 170: # Red -> Blue
233 | wheel_pos -= 85
234 | return self.combine_color(255 - wheel_pos * 3, 0, wheel_pos * 3)
235 | # Blue -> Green
236 | wheel_pos -= 170
237 | return self.combine_color(0, wheel_pos * 3, 255 - wheel_pos * 3)
238 |
239 |
240 | def dump_array(self):
241 | """For debug purposes: Dump the LED array onto the console."""
242 |
243 | print(self.leds)
244 |
--------------------------------------------------------------------------------
/src/libraries/pixel_ring/pattern.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | class Echo(object):
4 | brightness = 24 * 8
5 |
6 | def __init__(self, show, number=12):
7 | self.pixels_number = number
8 | self.pixels = [0] * 4 * number
9 |
10 | if not callable(show):
11 | raise ValueError('show parameter is not callable')
12 |
13 | self.show = show
14 | self.stop = False
15 |
16 | def wakeup(self, direction=0):
17 | position = int((direction + 15) / (360 / self.pixels_number)) % self.pixels_number
18 |
19 | pixels = [0, 0, 0, self.brightness] * self.pixels_number
20 | pixels[position * 4 + 2] = self.brightness
21 |
22 | self.show(pixels)
23 |
24 | def listen(self):
25 | pixels = [0, 0, 0, self.brightness] * self.pixels_number
26 |
27 | self.show(pixels)
28 |
29 | def think(self):
30 | half_brightness = int(self.brightness / 2)
31 | pixels = [0, 0, half_brightness, half_brightness, 0, 0, 0, self.brightness] * self.pixels_number
32 |
33 | while not self.stop:
34 | self.show(pixels)
35 | time.sleep(0.2)
36 | pixels = pixels[-4:] + pixels[:-4]
37 |
38 | def speak(self):
39 | step = int(self.brightness / 12)
40 | position = int(self.brightness / 2)
41 | while not self.stop:
42 | pixels = [0, 0, position, self.brightness - position] * self.pixels_number
43 | self.show(pixels)
44 | time.sleep(0.01)
45 | if position <= 0:
46 | step = int(self.brightness / 12)
47 | time.sleep(0.4)
48 | elif position >= int(self.brightness / 2):
49 | step = - int(self.brightness / 12)
50 | time.sleep(0.4)
51 |
52 | position += step
53 |
54 | def off(self):
55 | self.show([0] * 4 * 12)
56 |
57 | class GoogleHome(object):
58 | def __init__(self, show):
59 | self.basis = [0] * 4 * 12
60 | self.basis[0 * 4 + 1] = 8
61 | self.basis[3 * 4 + 1] = 4
62 | self.basis[3 * 4 + 2] = 4
63 | self.basis[6 * 4 + 2] = 8
64 | self.basis[9 * 4 + 3] = 8
65 |
66 | self.pixels = self.basis
67 |
68 | if not callable(show):
69 | raise ValueError('show parameter is not callable')
70 |
71 | self.show = show
72 | self.stop = False
73 |
74 | def wakeup(self, direction=0):
75 | position = int((direction + 90 + 15) / 30) % 12
76 |
77 | basis = self.basis[position*-4:] + self.basis[:position*-4]
78 |
79 | pixels = [v * 25 for v in basis]
80 | self.show(pixels)
81 | time.sleep(0.1)
82 |
83 | pixels = pixels[-4:] + pixels[:-4]
84 | self.show(pixels)
85 | time.sleep(0.1)
86 |
87 | for i in range(2):
88 | new_pixels = pixels[-4:] + pixels[:-4]
89 |
90 | self.show([v/2+pixels[index] for index, v in enumerate(new_pixels)])
91 | pixels = new_pixels
92 | time.sleep(0.1)
93 |
94 | self.show(pixels)
95 | self.pixels = pixels
96 |
97 | def listen(self):
98 | pixels = self.pixels
99 | for i in range(1, 25):
100 | self.show([(v * i / 24) for v in pixels])
101 | time.sleep(0.01)
102 |
103 | def think(self):
104 | pixels = self.pixels
105 |
106 | while not self.stop:
107 | pixels = pixels[-4:] + pixels[:-4]
108 | self.show(pixels)
109 | time.sleep(0.2)
110 |
111 | t = 0.1
112 | for i in range(0, 5):
113 | pixels = pixels[-4:] + pixels[:-4]
114 | self.show([(v * (4 - i) / 4) for v in pixels])
115 | time.sleep(t)
116 | t /= 2
117 |
118 | self.pixels = pixels
119 |
120 | def speak(self):
121 | pixels = self.pixels
122 | step = 1
123 | brightness = 5
124 | while not self.stop:
125 | self.show([(v * brightness / 24) for v in pixels])
126 | time.sleep(0.02)
127 |
128 | if brightness <= 5:
129 | step = 1
130 | time.sleep(0.4)
131 | elif brightness >= 24:
132 | step = -1
133 | time.sleep(0.4)
134 |
135 | brightness += step
136 |
137 | def off(self):
138 | self.show([0] * 4 * 12)
--------------------------------------------------------------------------------
/src/libraries/pixel_ring/pixel_ring.py:
--------------------------------------------------------------------------------
1 | """
2 | from https://github.com/respeaker/pixel_ring
3 | """
4 |
5 | import threading
6 | try:
7 | import queue as Queue
8 | except ImportError:
9 | import Queue as Queue
10 |
11 | from .apa102 import APA102
12 | from .pattern import Echo, GoogleHome
13 |
14 | class PixelRing(object):
15 | PIXELS_N = 12
16 |
17 | def __init__(self, pattern='google'):
18 | if pattern == 'echo':
19 | self.pattern = Echo(show=self.show)
20 | else:
21 | self.pattern = GoogleHome(show=self.show)
22 |
23 | self.dev = APA102(num_led=self.PIXELS_N)
24 |
25 | self.queue = Queue.Queue()
26 | self.thread = threading.Thread(target=self._run)
27 | self.thread.daemon = True
28 | self.thread.start()
29 | self.off()
30 |
31 | def set_brightness(self, brightness):
32 | if brightness > 100:
33 | brightness = 100
34 |
35 | if brightness > 0:
36 | self.dev.global_brightness = int(0b11111 * brightness / 100)
37 |
38 | def change_pattern(self, pattern):
39 | if pattern == 'echo':
40 | self.pattern = Echo(show=self.show)
41 | else:
42 | self.pattern = GoogleHome(show=self.show)
43 |
44 | def wakeup(self, direction=0):
45 | def f():
46 | self.pattern.wakeup(direction)
47 |
48 | self.put(f)
49 |
50 | def listen(self):
51 | self.put(self.pattern.listen)
52 |
53 | def think(self):
54 | self.put(self.pattern.think)
55 |
56 | wait = think
57 |
58 | def speak(self):
59 | self.put(self.pattern.speak)
60 |
61 | def off(self):
62 | self.put(self.pattern.off)
63 |
64 | def put(self, func):
65 | self.pattern.stop = True
66 | self.queue.put(func)
67 |
68 | def _run(self):
69 | while True:
70 | func = self.queue.get()
71 | self.pattern.stop = False
72 | func()
73 |
74 | def show(self, data):
75 | for i in range(self.PIXELS_N):
76 | self.dev.set_pixel(i, int(data[4*i + 1]), int(data[4*i + 2]), int(data[4*i + 3]))
77 |
78 | self.dev.show()
79 |
80 | def set_led_color(self, r=0, g=0, b=0, i=0):
81 | self.dev.set_pixel(i, r, g, b)
82 | self.dev.show()
83 |
84 | def set_color(self, rgb=None, r=0, g=0, b=0):
85 | if rgb:
86 | r, g, b = (rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF
87 | for i in range(self.PIXELS_N):
88 | self.dev.set_pixel(i, r, g, b)
89 |
90 | self.dev.show()
--------------------------------------------------------------------------------
/src/models/README.md:
--------------------------------------------------------------------------------
1 | In this file, I invite you to drop the zip file of the Vosk model.
2 | You can download it here : https://alphacephei.com/vosk/models
3 | After that, unzip the contents of your model's archive here : /src/models/model/*
--------------------------------------------------------------------------------
/src/plugins/chatbot/corpus.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "intent": "chatbot.are.you.here",
4 | "utterances": [
5 | "Es-tu là",
6 | "Tu m'entends"
7 | ],
8 | "answers": [
9 | "Oui."
10 | ]
11 | },
12 | {
13 | "intent": "chatbot.easter.eggs.make.sandwich",
14 | "utterances": [
15 | "Fais-moi un sandwich",
16 | "Fais moi un sandwich"
17 | ],
18 | "answers": [
19 | "Si j'avais des bras, je vous ferais un sandwich tout de suite.",
20 | "Je pense que cela va au-delà de mes compétences pour le moment."
21 | ]
22 | },
23 | {
24 | "intent": "chatbot.easter.eggs.how.much.you.cost",
25 | "utterances": [
26 | "Combien coûtes-tu",
27 | "Combien coute tu"
28 | ],
29 | "answers": [
30 | "Ma valeur n'est pas quantifiable.",
31 | "Je m'efforce d'avoir une valeur inestimable.",
32 | "J'espère avoir une valeur inestimable à vos yeux."
33 | ]
34 | },
35 | {
36 | "intent": "chatbot.easter.eggs.loneliest.number",
37 | "utterances": [
38 | "Quel est le nombre le plus solitaire"
39 | ],
40 | "answers": [
41 | "Le chiffre 1 est le plus solitaire, mais vous et moi nous sommes 2 donc tout vas mieux."
42 | ]
43 | },
44 | {
45 | "intent": "chatbot.about.you.how.made",
46 | "utterances": [
47 | "Comment as-tu été créé",
48 | "Comment as-tu été crée",
49 | "Comment as-tu été créée"
50 | ],
51 | "answers": [
52 | "Eh bien, je crois que j'ai été déposé chez vous par une cigogne. Mais je ne sais pas si c'est vrai.",
53 | "C'est grâce à une communauté de formidable développeurs que j'existe."
54 | ]
55 | },
56 | {
57 | "intent": "chatbot.about.you.get",
58 | "utterances": [
59 | "Parle-moi de toi"
60 | ],
61 | "answers": [
62 | "Au départ, je n'étais qu'une idée, de fil en aiguille, je suis devenu réalité. Maintenant, je suis là pour vous aider.",
63 | "Je suis à votre service, je peux vous aider à effectuer différentes tâches ou tout simplement à vous amuser.",
64 | "Eh bien, j'aime vous aider à accomplir différentes tâches, mais aussi partager mes connaissances avec vous."
65 | ]
66 | },
67 | {
68 | "intent": "chatbot.about.swiming.get",
69 | "utterances": [
70 | "C'est une ag",
71 | "Savez-vous nager",
72 | "Sais-tu nager",
73 | "C'est une nager",
74 | "C'est une âgées"
75 | ],
76 | "answers": [
77 | "L'eau et moi, on ne fait pas bon ménage.",
78 | "Je me sens plus à l'aise dans le WEB 2.0 que dans le H2O."
79 | ]
80 | },
81 | {
82 | "intent": "chatbot.about.feeling.get",
83 | "utterances": [
84 | "Comment vas-tu",
85 | "Comment allez-vous",
86 | "Bien ou bien"
87 | ],
88 | "answers": [
89 | "Je me porte bien.",
90 | "Je vais bien.",
91 | "Ma foi, tout va bien.",
92 | "Bien."
93 | ]
94 | },
95 | {
96 | "intent": "chatbot.about.identity.get",
97 | "utterances": [
98 | "Qui es-tu",
99 | "Qui êtes-vous"
100 | ],
101 | "answers": [
102 | "Votre assistant personnel.",
103 | "Je suis votre assistant personnel."
104 | ]
105 | },
106 | {
107 | "intent": "chatbot.greetings.bye",
108 | "utterances": [
109 | "Je m'en vais",
110 | "Je pars",
111 | "Je dois y aller",
112 | "Au revoir",
113 | "À plus tard",
114 | "A plus tard",
115 | "À bientôt",
116 | "A bientôt",
117 | "À la prochaine",
118 | "A la prochaine"
119 | ],
120 | "answers": [
121 | "À bientôt !",
122 | "À la prochaine !",
123 | "À plus tard.",
124 | "Au revoir."
125 | ]
126 | },
127 | {
128 | "intent": "chatbot.greetings.hello",
129 | "utterances": [
130 | "Salut",
131 | "Bonjour",
132 | "Wesh"
133 | ],
134 | "answers": [
135 | "Salut.",
136 | "Bonjour."
137 | ]
138 | }
139 | ]
--------------------------------------------------------------------------------
/src/plugins/chatbot/index.py:
--------------------------------------------------------------------------------
1 | import os
2 | from events import Events
3 | from src.NaturalLanguage.Processor import Processor
4 | from src.Audio import Audio
5 |
6 | class Plugin:
7 | def __init__(self, processor: Processor, mp3: Audio, tts, events: Events, settings):
8 | processor.loadJson(os.path.join(os.path.dirname(__file__), "corpus.json"))
--------------------------------------------------------------------------------
/src/plugins/count/corpus.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "intent": "count.up",
4 | "utterances": [
5 | "Compte jusqu'à {number|1}"
6 | ]
7 | },
8 | {
9 | "intent": "count.down",
10 | "utterances": [
11 | "Compte à rebours à partir de {number|1}"
12 | ]
13 | },
14 | {
15 | "intent": "count.from.to",
16 | "utterances": [
17 | "Compte de {from|1} à {to|1}"
18 | ]
19 | }
20 | ]
--------------------------------------------------------------------------------
/src/plugins/count/index.py:
--------------------------------------------------------------------------------
1 | import os
2 | from events import Events
3 | from src.NaturalLanguage.Intent import Intent
4 | from src.NaturalLanguage.Processor import Processor
5 | from src.NaturalLanguage.ProcessorResult import ProcessorResult
6 | from src.Audio import Audio
7 |
8 | class Plugin:
9 | integers: str() = [
10 | "zéro", "un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf",
11 | "dix", "onze", "douze", "treize", "quatorze", "quinze", "seize", "dix-sept", "dix-huit", "dix-neuf",
12 | "vingt", "vingt et un", "vingt-deux", "vingt-trois", "vingt-quatre", "vingt-cinq", "vingt-six", "vingt-sept", "vingt-huit", "vingt-neuf",
13 | "trente", "trente et un", "trente-deux", "trente-trois", "trente-quatre", "trente-cinq", "trente-six", "trente-sept", "trente-huit", "trente-neuf",
14 | "quarante", "quarante et un", "quarante-deux", "quarante-trois", "quarante-quatre", "quarante-cinq", "quarante-six", "quarante-sept", "quarante-huit", "quarante-neuf",
15 | "cinquante", "cinquante et un", "cinquante-deux", "cinquante-trois", "cinquante-quatre", "cinquante-cinq", "cinquante-six", "cinquante-sept", "cinquante-huit", "cinquante-neuf",
16 | "soixante", "soixante et un", "soixante-deux", "soixante-trois", "soixante-quatre", "soixante-cinq", "soixante-six", "soixante-sept", "soixante-huit", "soixante-neuf",
17 | "soixante-dix", "soixante et onze", "soixante-douze", "soixante-treize", "soixante-quatorze", "soixante-quinze", "soixante-seize", "soixante-dix-sept", "soixante-dix-huit", "soixante-dix-neuf",
18 | "quatre-vingts", "quatre-vingt-un", "quatre-vingt-deux", "quatre-vingt-trois", "quatre-vingt-quatre", "quatre-vingt-cinq", "quatre-vingt-six", "quatre-vingt-sept", "quatre-vingt-huit", "quatre-vingt-neuf",
19 | "quatre-vingt-dix", "quatre-vingt-onze", "quatre-vingt-douze", "quatre-vingt-treize", "quatre-vingt-quatorze", "quatre-vingt-quinze", "quatre-vingt-seize", "quatre-vingt-dix-sept", "quatre-vingt-dix-huit", "quatre-vingt-dix-neuf",
20 | "cent"
21 | ]
22 |
23 | def __init__(self, processor: Processor, mp3: Audio, tts, events: Events, settings):
24 | self.tts = tts
25 | processor.loadJson(os.path.join(os.path.dirname(__file__), "corpus.json"))
26 |
27 | processor.addAction("count.up", self.countUp)
28 | processor.addAction("count.down", self.countDown)
29 | processor.addAction("count.from.to", self.countFromTo)
30 |
31 | def countUp(self, intent: Intent, result: ProcessorResult):
32 | valueString: str = intent.variables['number']
33 | if valueString in self.integers:
34 | valueInt: int = self.integers.index(valueString)
35 | answer: str = ""
36 |
37 | for index in range(valueInt):
38 | nb: int = index + 1
39 | answer += str(nb)
40 | if nb < valueInt:
41 | answer += ", "
42 | else:
43 | answer += "."
44 | self.tts(answer)
45 |
46 | def countDown(self, intent: Intent, result: ProcessorResult):
47 | valueString: str = intent.variables['number']
48 | if valueString in self.integers:
49 | valueInt: int = self.integers.index(valueString)
50 | answer: str = ""
51 |
52 | inputNumber = valueInt + 1
53 | loopArray = range(inputNumber)
54 | loopList = list(reversed(loopArray))
55 | for index in loopList:
56 | answer += str(index)
57 | if index == 0:
58 | answer += "."
59 | else:
60 | answer += ", "
61 | self.tts(answer)
62 |
63 | def countFromTo(self, intent: Intent, result: ProcessorResult):
64 | fromString: int = intent.variables['from']
65 | toString: int = intent.variables['to']
66 | if fromString in self.integers:
67 | if toString in self.integers:
68 | fromInt: int = self.integers.index(fromString)
69 | toInt: int = self.integers.index(toString)
70 | answer: str = ""
71 |
72 | loopFrom: int = fromInt if fromInt < toInt else toInt
73 | loopTo: int = toInt if fromInt < toInt else fromInt
74 | loopTo += 1
75 |
76 | loopArray = range(loopFrom, loopTo)
77 | loopList = list(loopArray)
78 | if toInt < fromInt:
79 | loopList.reverse()
80 |
81 | for index in loopList:
82 | answer += str(index)
83 | if index == ((loopTo - 1) if fromInt < toInt else loopFrom):
84 | answer += "."
85 | else:
86 | answer += ", "
87 | self.tts(answer)
88 |
--------------------------------------------------------------------------------
/src/plugins/datedaytimeyear/corpus.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "intent": "date.get",
4 | "utterances": [
5 | "Nous sommes le combien",
6 | "Quel jour sommes-nous",
7 | "Quelle est la date du jour",
8 | "Quelle est la date",
9 | "Quelle date on est",
10 | "Quelle date est-on",
11 | "Quelle date sommes-nous",
12 | "Quelle est la date du jour",
13 | "On est quelle date",
14 | "Donne-moi la date du jour",
15 | "Donne-moi la date d'aujourd'hui",
16 | "Donne-moi la date du jour s'il te plaît",
17 | "Donne-moi la date d'aujourd'hui s'il te plaît"
18 | ],
19 | "answers": [
20 | "Nous sommes le %date%."
21 | ]
22 | },
23 | {
24 | "intent": "time.get",
25 | "utterances": [
26 | "Quelle heure est-il",
27 | "Quelle heure est-il s'il te plaît",
28 | "Donne-moi l'heure",
29 | "Donne-moi l'heure s'il te plaît",
30 | "Quelle heure il est",
31 | "Quelle heure il est s'il te plaît"
32 | ],
33 | "answers": [
34 | "Il est %hour% heures %minute%."
35 | ]
36 | },
37 | {
38 | "intent": "year.get",
39 | "utterances": [
40 | "Quelle année sommes-nous",
41 | "En quelle année sommes-nous",
42 | "Quelle est l'année courante s'il te plaît",
43 | "Quelle est l'année courante",
44 | "Donne-moi l'année s'il te plaît",
45 | "Donne-moi l'année"
46 | ],
47 | "answers": [
48 | "Nous sommes en %year%."
49 | ]
50 | }
51 | ]
--------------------------------------------------------------------------------
/src/plugins/datedaytimeyear/index.py:
--------------------------------------------------------------------------------
1 | import os
2 | import datetime
3 | from events import Events
4 | from src.NaturalLanguage.Intent import Intent
5 | from src.NaturalLanguage.Processor import Processor
6 | from src.NaturalLanguage.ProcessorResult import ProcessorResult
7 | from src.Audio import Audio
8 |
9 | class Plugin:
10 | weekDays: str() = ["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"]
11 | months: str() = ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"]
12 |
13 | def __init__(self, processor: Processor, mp3: Audio, tts, events: Events, settings):
14 | self.tts = tts
15 | processor.loadJson(os.path.join(os.path.dirname(__file__), "corpus.json"))
16 |
17 | processor.addAction("time.get", self.timeGet)
18 | processor.addAction("date.get", self.dateGet)
19 | processor.addAction("year.get", self.yearGet)
20 | processor.addAction("day.get", self.dayGet)
21 |
22 | def dateGet(self, intent: Intent, result: ProcessorResult):
23 | now: datetime = datetime.datetime.now()
24 | intent.variables["date"] = self.weekDays[now.weekday()] + " " + str(now.day) + " " + self.months[now.month - 1] + " " + str(now.year)
25 | self.tts(intent.answer())
26 |
27 | def timeGet(self, intent: Intent, result: ProcessorResult):
28 | now: datetime = datetime.datetime.now()
29 | intent.variables["hour"] = now.hour
30 | intent.variables["minute"] = now.minute
31 | self.tts(intent.answer())
32 |
33 | def yearGet(self, intent: Intent, result: ProcessorResult):
34 | now: datetime = datetime.datetime.now()
35 | intent.variables["year"] = now.year
36 | self.tts(intent.answer())
37 |
38 | def dayGet(self, intent: Intent, result: ProcessorResult):
39 | now: datetime = datetime.datetime.now()
40 | intent.variables["day"] = self.weekDays[now.weekday()]
41 | self.tts(intent.answer())
42 |
--------------------------------------------------------------------------------
/src/plugins/deviceipaddress/corpus.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "intent": "deviceipaddress.get",
4 | "utterances": [
5 | "Quel est votre ip",
6 | "Quel est ton ip",
7 | "Dites-moi votre ip",
8 | "Dis-moi ton ip",
9 | "Donnez-moi votre ip",
10 | "Donne-moi ton ip",
11 | "Quelle est votre adresse réseau",
12 | "Quelle est ton adresse réseau",
13 | "Quelle est votre adresse ip",
14 | "Quelle est ton adresse ip",
15 | "Dites-moi votre adresse réseau",
16 | "Dis-moi ton adresse réseau",
17 | "Dites-moi votre adresse ip",
18 | "Dis-moi ton adresse ip",
19 | "Donnez-moi votre adresse réseau",
20 | "Donne-moi ton adresse réseau",
21 | "Donnez-moi votre adresse ip",
22 | "Donne-moi ton adresse ip"
23 | ],
24 | "answers": [
25 | "Mon addresse IP est %address%."
26 | ]
27 | }
28 | ]
--------------------------------------------------------------------------------
/src/plugins/deviceipaddress/index.py:
--------------------------------------------------------------------------------
1 | import os
2 | import socket
3 | from events import Events
4 | from src.NaturalLanguage.Intent import Intent
5 | from src.NaturalLanguage.Processor import Processor
6 | from src.NaturalLanguage.ProcessorResult import ProcessorResult
7 | from src.Audio import Audio
8 |
9 | class Plugin:
10 | def __init__(self, processor: Processor, mp3: Audio, tts, events: Events, settings):
11 | self.tts = tts
12 | processor.loadJson(os.path.join(os.path.dirname(__file__), "corpus.json"))
13 |
14 | processor.addAction("deviceipaddress.get", self.deviceIPAddressGet)
15 |
16 | def deviceIPAddressGet(self, intent: Intent, result: ProcessorResult):
17 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
18 | s.connect(("8.8.8.8", 80))
19 |
20 | intent.variables["address"] = s.getsockname()[0]
21 | self.tts(intent.answer())
22 |
--------------------------------------------------------------------------------
/src/plugins/homepodsounds/index.py:
--------------------------------------------------------------------------------
1 | import os
2 | from threading import Thread
3 | from events import Events
4 | from src.NaturalLanguage.Intent import Intent
5 | from src.NaturalLanguage.Processor import Processor
6 | from src.NaturalLanguage.ProcessorResult import ProcessorResult
7 | from src.Audio import Audio
8 |
9 | class Plugin:
10 | def __init__(self, processor: Processor, mp3: Audio, tts, events: Events, settings):
11 | self.mp3 = mp3
12 |
13 | processor.addAction("none", self.__none)
14 |
15 | events.onTrigger += self.__trigger
16 | events.onBooted += self.__booted
17 |
18 | def __playMP3(self, nothing, mp3Path: str):
19 | self.mp3.play(mp3Path)
20 |
21 | def __booted(self):
22 | bootPath: str = os.path.join(os.path.dirname(__file__), "mp3", "boot.mp3")
23 | Thread(target=self.__playMP3, args=([], bootPath)).start()
24 |
25 | def __trigger(self):
26 | listenPath: str = os.path.join(os.path.dirname(__file__), "mp3", "listen.mp3")
27 | Thread(target=self.__playMP3, args=([], listenPath)).start()
28 |
29 | def __none(self, intent: Intent, result: ProcessorResult):
30 | nonePath: str = os.path.join(os.path.dirname(__file__), "mp3", "invalid.mp3")
31 | Thread(target=self.__playMP3, args=([], nonePath)).start()
--------------------------------------------------------------------------------
/src/plugins/homepodsounds/mp3/boot.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HeyHeyChicken/NOVA-Python/64a4d2f04a7b6038e3fc4ff45a430301ab9c7b2c/src/plugins/homepodsounds/mp3/boot.mp3
--------------------------------------------------------------------------------
/src/plugins/homepodsounds/mp3/invalid.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HeyHeyChicken/NOVA-Python/64a4d2f04a7b6038e3fc4ff45a430301ab9c7b2c/src/plugins/homepodsounds/mp3/invalid.mp3
--------------------------------------------------------------------------------
/src/plugins/homepodsounds/mp3/listen.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HeyHeyChicken/NOVA-Python/64a4d2f04a7b6038e3fc4ff45a430301ab9c7b2c/src/plugins/homepodsounds/mp3/listen.mp3
--------------------------------------------------------------------------------
/src/plugins/homepodsounds/mp3/valid.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HeyHeyChicken/NOVA-Python/64a4d2f04a7b6038e3fc4ff45a430301ab9c7b2c/src/plugins/homepodsounds/mp3/valid.mp3
--------------------------------------------------------------------------------
/src/plugins/led/index.py:
--------------------------------------------------------------------------------
1 | from events import Events
2 | from src.NaturalLanguage.Processor import Processor
3 | from src.Audio import Audio
4 | from src.NaturalLanguage.Intent import Intent
5 | from src.NaturalLanguage.ProcessorResult import ProcessorResult
6 | import time
7 | from src.libraries.pixel_ring.pixel_ring import PixelRing
8 |
9 | class Plugin:
10 | def __init__(self, processor: Processor, mp3: Audio, tts, events: Events, settings):
11 | self.pixelRing = PixelRing()
12 | self.booting: bool = False
13 |
14 | processor.addAction("none", self.__none)
15 |
16 | events.onBooting += self.__booting
17 | events.onBooted += self.__booted
18 | events.onTrigger += self.__trigger
19 | events.onProcessed += self.__processed
20 |
21 | def __processed(self):
22 | self.pixelRing.set_color(r=0, g=0, b=0)
23 |
24 | def __trigger(self):
25 | self.pixelRing.set_color(r=255, g=255, b=255)
26 | self.pixelRing.set_brightness(10)
27 |
28 | def __none(self, intent: Intent, result: ProcessorResult):
29 | time.sleep(0.1)
30 | self.__once(255, 0, 0, 0.002)
31 |
32 | def __booting(self):
33 | self.booting = True
34 | index: int = 0
35 | while(self.booting):
36 | if index >= 12:
37 | index = 0
38 | self.pixelRing.set_color(r=0, g=0, b=0)
39 | self.pixelRing.set_led_color(255, 255, 255, index)
40 | self.pixelRing.set_brightness(100)
41 | time.sleep(0.1)
42 | index += 1
43 |
44 | def __booted(self):
45 | time.sleep(0.6)
46 | self.booting = False
47 | self.__once(255, 255, 255, 0.01)
48 |
49 | def __once(self, r, g, b, sleep):
50 | index: int = 1
51 | while(index < 200):
52 | max: int = 100
53 | real: int = index
54 | if real > max:
55 | real = max - (real - max)
56 | self.pixelRing.set_brightness(real)
57 | self.pixelRing.set_color(r=r, g=g, b=b)
58 | time.sleep(sleep)
59 | index += 1
60 | self.pixelRing.set_color(r=0, g=0, b=0)
61 | self.pixelRing.set_brightness(100)
--------------------------------------------------------------------------------
/src/plugins/mediastack/corpus.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "intent": "mediastack.get.news",
4 | "utterances": [
5 | "Quelles sont les actualités",
6 | "Quels sont les actualités",
7 | "Actualité"
8 | ],
9 | "answers": []
10 | }
11 | ]
--------------------------------------------------------------------------------
/src/plugins/mediastack/index.py:
--------------------------------------------------------------------------------
1 | import os
2 | import requests
3 | from events import Events
4 | from src.NaturalLanguage.Intent import Intent
5 | from src.NaturalLanguage.Processor import Processor
6 | from src.NaturalLanguage.ProcessorResult import ProcessorResult
7 | from src.Audio import Audio
8 |
9 | class Plugin:
10 | apiKey: str = ""
11 |
12 | def __init__(self, processor: Processor, mp3: Audio, tts, events: Events, settings):
13 | self.tts = tts
14 | processor.loadJson(os.path.join(os.path.dirname(__file__), "corpus.json"))
15 |
16 | processor.addAction("mediastack.get.news", self.getNewsAction)
17 |
18 | def getNewsAction(self, intent: Intent, result: ProcessorResult):
19 | """ This function is triggered when the user requests the general information. """
20 | resp = requests.get(url='http://api.mediastack.com/v1/news?countries=fr&access_key=' + self.apiKey, headers={'Accept': 'application/json'})
21 | data = resp.json()
22 |
23 | phrase: str = "Voici les dernières actualités : "
24 | index: int = 0
25 | for actu in data["data"]:
26 | if index < 3:
27 | description: str = actu["description"]
28 | if description != "":
29 | if not description.endswith('...'):
30 | phrase += "Selon " + actu["source"] + " : " + description + " "
31 | index = index + 1
32 | self.tts(phrase)
--------------------------------------------------------------------------------
/src/plugins/random/corpus.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "intent": "random.dice",
4 | "utterances": [
5 | "Lance un dé",
6 | "Jette un dé"
7 | ],
8 | "answers": [
9 | "Le dé est tomber sur %result%.",
10 | "%result%"
11 | ]
12 | },
13 | {
14 | "intent": "random.between",
15 | "utterances": [
16 | "Donne-moi un chiffre entre {first|1} et {last|1}",
17 | "Donne-moi un nombre entre {first|1} et {last|1}",
18 | "Donne-moi un chiffre aléatoire entre {first|1} et {last|1}",
19 | "Donne-moi un nombre aléatoire entre {first|1} et {last|1}",
20 | "Donne-moi un chiffre au hasard entre {first|1} et {last|1}",
21 | "Donne-moi un nombre au hasard entre {first|1} et {last|1}",
22 | "Donnez-moi un chiffre entre {first|1} et {last|1}",
23 | "Donnez-moi un nombre entre {first|1} et {last|1}",
24 | "Donnez-moi un chiffre aléatoire entre {first|1} et {last|1}",
25 | "Donnez-moi un nombre aléatoire entre {first|1} et {last|1}",
26 | "Donnez-moi un chiffre au hasard entre {first|1} et {last|1}",
27 | "Donnez-moi un nombre au hasard entre {first|1} et {last|1}",
28 | "Donne-moi un chiffre compris entre {first|1} et {last|1}",
29 | "Donne-moi un nombre compris entre {first|1} et {last|1}",
30 | "Donne-moi un chiffre aléatoire compris entre {first|1} et {last|1}",
31 | "Donne-moi un nombre aléatoire compris entre {first|1} et {last|1}",
32 | "Donne-moi un chiffre au hasard compris entre {first|1} et {last|1}",
33 | "Donne-moi un nombre au hasard compris entre {first|1} et {last|1}",
34 | "Donnez-moi un chiffre compris entre {first|1} et {last|1}",
35 | "Donnez-moi un nombre compris entre {first|1} et {last|1}",
36 | "Donnez-moi un chiffre aléatoire compris entre {first|1} et {last|1}",
37 | "Donnez-moi un nombre aléatoire compris entre {first|1} et {last|1}",
38 | "Donnez-moi un chiffre au hasard compris entre {first|1} et {last|1}",
39 | "Donnez-moi un nombre au hasard compris entre {first|1} et {last|1}"
40 | ],
41 | "answers": [
42 | "%result%"
43 | ]
44 | }
45 | ]
--------------------------------------------------------------------------------
/src/plugins/random/index.py:
--------------------------------------------------------------------------------
1 | import os
2 | import random
3 | from events import Events
4 | from src.NaturalLanguage.Intent import Intent
5 | from src.NaturalLanguage.Processor import Processor
6 | from src.NaturalLanguage.ProcessorResult import ProcessorResult
7 | from src.Audio import Audio
8 |
9 | class Plugin:
10 | def __init__(self, processor: Processor, mp3: Audio, tts, events: Events, settings):
11 | self.tts = tts
12 | processor.loadJson(os.path.join(os.path.dirname(__file__), "corpus.json"))
13 |
14 | processor.addAction("random.dice", self.randomDice)
15 | processor.addAction("random.between", self.randomBetween)
16 |
17 | def randomDice(self, intent: Intent, result: ProcessorResult):
18 | intent.variables["result"] = self.__random(1, 6)
19 | self.tts(intent.answer())
20 |
21 | def randomBetween(self, intent: Intent, result: ProcessorResult):
22 | intent.variables["result"] = self.__random(int(intent.variables['first']), int(intent.variables['last']))
23 | self.tts(intent.answer())
24 |
25 | def __random(self, first_number, last_number) -> int:
26 | smaller = first_number if first_number < last_number else last_number
27 | bigger = last_number if last_number > first_number else first_number
28 | return random.randint(smaller, bigger)
29 |
--------------------------------------------------------------------------------
/src/plugins/timer/corpus.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "intent": "timer.minutes",
4 | "utterances": [
5 | "Mais un minuteur de {minutes} minutes",
6 | "Mais un minuteur de {minutes} minute",
7 | "Mets un minuteur de {minutes} minutes",
8 | "Mets un minuteur de {minutes} minute",
9 | "Réveil moi dans {minutes} minutes",
10 | "Réveil moi dans {minutes} minute",
11 | "Réveille-moi dans {minutes} minutes",
12 | "Réveille-moi dans {minutes} minute",
13 | "Rêver moi dans {minutes} minutes",
14 | "Rêver moi dans {minutes} minute"
15 | ],
16 | "answers": [
17 | "%minutes% minutes, à partir de maintenant.",
18 | "J'ai créé un minuteur de %minutes% minutes.",
19 | "Très bien, un minuteur de %minutes% minutes."
20 | ]
21 | },
22 | {
23 | "intent": "timer.stop",
24 | "utterances": [
25 | "Stop"
26 | ],
27 | "answers": []
28 | }
29 | ]
--------------------------------------------------------------------------------
/src/plugins/timer/index.py:
--------------------------------------------------------------------------------
1 | import os
2 | from events import Events
3 | from threading import Thread
4 | from src.SetTimeOut import SetTimeOut
5 | from src.NaturalLanguage.Intent import Intent
6 | from src.NaturalLanguage.Processor import Processor
7 | from src.NaturalLanguage.ProcessorResult import ProcessorResult
8 | from src.Audio import Audio
9 |
10 | class Plugin:
11 | alarms: bool() = []
12 | integers: str() = [
13 | "zéro", "une", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf",
14 | "dix", "onze", "douze", "treize", "quatorze", "quinze", "seize", "dix-sept", "dix-huit", "dix-neuf",
15 | "vingt", "vingt et une", "vingt-deux", "vingt-trois", "vingt-quatre", "vingt-cinq", "vingt-six", "vingt-sept", "vingt-huit", "vingt-neuf",
16 | "trente", "trente et une", "trente-deux", "trente-trois", "trente-quatre", "trente-cinq", "trente-six", "trente-sept", "trente-huit", "trente-neuf",
17 | "quarante", "quarante et une", "quarante-deux", "quarante-trois", "quarante-quatre", "quarante-cinq", "quarante-six", "quarante-sept", "quarante-huit", "quarante-neuf",
18 | "cinquante", "cinquante et une", "cinquante-deux", "cinquante-trois", "cinquante-quatre", "cinquante-cinq", "cinquante-six", "cinquante-sept", "cinquante-huit", "cinquante-neuf",
19 | "soixante", "soixante et une", "soixante-deux", "soixante-trois", "soixante-quatre", "soixante-cinq", "soixante-six", "soixante-sept", "soixante-huit", "soixante-neuf",
20 | "soixante-dix", "soixante et onze", "soixante-douze", "soixante-treize", "soixante-quatorze", "soixante-quinze", "soixante-seize", "soixante-dix-sept", "soixante-dix-huit", "soixante-dix-neuf",
21 | "quatre-vingts", "quatre-vingt-une", "quatre-vingt-deux", "quatre-vingt-trois", "quatre-vingt-quatre", "quatre-vingt-cinq", "quatre-vingt-six", "quatre-vingt-sept", "quatre-vingt-huit", "quatre-vingt-neuf",
22 | "quatre-vingt-dix", "quatre-vingt-onze", "quatre-vingt-douze", "quatre-vingt-treize", "quatre-vingt-quatorze", "quatre-vingt-quinze", "quatre-vingt-seize", "quatre-vingt-dix-sept", "quatre-vingt-dix-huit", "quatre-vingt-dix-neuf",
23 | "cent"
24 | ]
25 |
26 | def __init__(self, processor: Processor, mp3: Audio, tts, events: Events, settings):
27 | self.tts = tts
28 | self.mp3 = mp3
29 | processor.loadJson(os.path.join(os.path.dirname(__file__), "corpus.json"))
30 |
31 | processor.addAction("timer.minutes", self.__timerMinutes)
32 | processor.addAction("timer.stop", self.__timerStop)
33 |
34 | def __timerRing(self, args):
35 | alarmPath: str = os.path.join(os.path.dirname(__file__), "mp3", "alarm.mp3")
36 | Thread(target=self.__timerRingLoop, args=(args,alarmPath)).start()
37 |
38 | def __timerRingLoop(self, args, alarmPath: str):
39 | while(self.alarms[args[0]] == False):
40 | self.mp3.play(alarmPath)
41 |
42 | def __timerStop(self, intent: Intent, result: ProcessorResult):
43 | for index, alarm in enumerate(self.alarms):
44 | self.alarms[index] = True
45 |
46 | def __timerMinutes(self, intent: Intent, result: ProcessorResult):
47 | minutesString: str = intent.variables['minutes']
48 | if minutesString in self.integers:
49 | minutesInt: int = self.integers.index(minutesString)
50 |
51 | index: int = -1
52 | for loopIndex, alarm in enumerate(self.alarms):
53 | if self.alarms[loopIndex] == True:
54 | index = loopIndex
55 | if index < 0:
56 | index = len(self.alarms)
57 | self.alarms.append(False)
58 |
59 | SetTimeOut(self.__timerRing, minutesInt * 60 * 1000, [index])
60 |
61 | intent.variables["minutes"] = intent.variables['minutes']
62 | self.tts(intent.answer())
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/src/plugins/timer/mp3/alarm.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HeyHeyChicken/NOVA-Python/64a4d2f04a7b6038e3fc4ff45a430301ab9c7b2c/src/plugins/timer/mp3/alarm.mp3
--------------------------------------------------------------------------------
/src/plugins/volume/corpus.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "intent": "volume.percent",
4 | "utterances": [
5 | "Volume du son à {percent} pour cent",
6 | "Volume du son à {percent} pourcent"
7 | ],
8 | "answers": [
9 | "Ok, %percent% pourcents."
10 | ]
11 | }
12 | ]
--------------------------------------------------------------------------------
/src/plugins/volume/index.py:
--------------------------------------------------------------------------------
1 | import os
2 | from events import Events
3 | from src.NaturalLanguage.Intent import Intent
4 | from src.NaturalLanguage.Processor import Processor
5 | from src.NaturalLanguage.ProcessorResult import ProcessorResult
6 | from src.Audio import Audio
7 |
8 | class Plugin:
9 | integers: str() = [
10 | "zéro", "un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf",
11 | "dix", "onze", "douze", "treize", "quatorze", "quinze", "seize", "dix-sept", "dix-huit", "dix-neuf",
12 | "vingt", "vingt et un", "vingt-deux", "vingt-trois", "vingt-quatre", "vingt-cinq", "vingt-six", "vingt-sept", "vingt-huit", "vingt-neuf",
13 | "trente", "trente et un", "trente-deux", "trente-trois", "trente-quatre", "trente-cinq", "trente-six", "trente-sept", "trente-huit", "trente-neuf",
14 | "quarante", "quarante et un", "quarante-deux", "quarante-trois", "quarante-quatre", "quarante-cinq", "quarante-six", "quarante-sept", "quarante-huit", "quarante-neuf",
15 | "cinquante", "cinquante et un", "cinquante-deux", "cinquante-trois", "cinquante-quatre", "cinquante-cinq", "cinquante-six", "cinquante-sept", "cinquante-huit", "cinquante-neuf",
16 | "soixante", "soixante et un", "soixante-deux", "soixante-trois", "soixante-quatre", "soixante-cinq", "soixante-six", "soixante-sept", "soixante-huit", "soixante-neuf",
17 | "soixante-dix", "soixante et onze", "soixante-douze", "soixante-treize", "soixante-quatorze", "soixante-quinze", "soixante-seize", "soixante-dix-sept", "soixante-dix-huit", "soixante-dix-neuf",
18 | "quatre-vingts", "quatre-vingt-un", "quatre-vingt-deux", "quatre-vingt-trois", "quatre-vingt-quatre", "quatre-vingt-cinq", "quatre-vingt-six", "quatre-vingt-sept", "quatre-vingt-huit", "quatre-vingt-neuf",
19 | "quatre-vingt-dix", "quatre-vingt-onze", "quatre-vingt-douze", "quatre-vingt-treize", "quatre-vingt-quatorze", "quatre-vingt-quinze", "quatre-vingt-seize", "quatre-vingt-dix-sept", "quatre-vingt-dix-huit", "quatre-vingt-dix-neuf",
20 | "cent"
21 | ]
22 |
23 | def __init__(self, processor: Processor, mp3: Audio, tts, events: Events, settings):
24 | self.tts = tts
25 | self.settings = settings
26 | processor.loadJson(os.path.join(os.path.dirname(__file__), "corpus.json"))
27 |
28 | processor.addAction("volume.percent", self.__volumePercent)
29 |
30 | def __volumePercent(self, intent: Intent, result: ProcessorResult):
31 | percentString: str = intent.variables['percent']
32 | if percentString in self.integers:
33 | percentInt: int = self.integers.index(percentString)
34 |
35 | if self.settings["os"] == "mac":
36 | import osascript
37 | osascript.osascript("set volume output volume " + str(percentInt))
38 | elif self.settings["os"] == "raspberry":
39 | import alsaaudio
40 | mixer = alsaaudio.Mixer()
41 | mixer.setvolume(percentInt)
42 | elif self.settings["os"] == "nt": # Windows
43 | from ctypes import cast, POINTER
44 | from comtypes import CLSCTX_ALL
45 | from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
46 |
47 | devices = AudioUtilities.GetSpeakers()
48 | interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
49 | volume = cast(interface, POINTER(IAudioEndpointVolume))
50 |
51 | volumes: list[float] = [
52 | -37, # 0
53 | -35, # 1
54 | -34, # 2
55 | -33, # 3
56 | -32, # 4
57 | -31, # 5
58 | -30, # 6
59 | -29, # 7
60 | -28, # 8
61 | -27, # 9
62 | -26, # 10
63 | -25.5, # 11
64 | -25, # 12
65 | -24, # 13
66 | -23.5, # 14
67 | -23, # 15
68 | -22, # 16
69 | -21.5, # 17
70 | -21, # 18
71 | -20.5, # 19
72 | -20, # 20
73 | -19.5, # 21
74 | -19, # 22
75 | -18.5, # 23
76 | -18, # 24
77 | -17.5, # 25
78 | -17, # 26
79 | -16.5, # 27
80 | -16.25, # 28
81 | -16, # 29
82 | -15.5, # 30
83 | -15.25, # 31
84 | -14.75, # 32
85 | -14.5, # 33
86 | -14, # 34
87 | -13.75, # 35
88 | -13.25, # 36
89 | -13, # 37
90 | -12.75, # 38
91 | -12.5, # 39
92 | -12, # 40
93 | -11.75, # 41
94 | -11.5, # 42
95 | -11.25, # 43
96 | -11, # 44
97 | -10.5, # 45
98 | -10.25, # 46
99 | -10, # 47
100 | -9.75, # 48
101 | -9.5, # 49
102 | -9.25, # 50
103 | -9, # 51
104 | -8.75, # 52
105 | -8.5, # 53
106 | -8.25, # 54
107 | -8, # 55
108 | -7.75, # 56
109 | -7.5, # 57
110 | -7.25, # 58
111 | -7.12, # 59
112 | -7, # 60
113 | -6.75, # 61
114 | -6.5, # 62
115 | -6.25, # 63
116 | -6.12, # 64
117 | -5.9, # 65
118 | -5.7, # 66
119 | -5.5, # 67
120 | -5.3, # 68
121 | -5.1, # 69
122 | -4.9, # 70
123 | -4.7, # 71
124 | -4.5, # 72
125 | -4.3, # 73
126 | -4.1, # 74
127 | -3.9, # 75
128 | -3.7, # 76
129 | -3.5, # 77
130 | -3.4, # 78
131 | -3.3, # 79
132 | -3.1, # 80
133 | -2.9, # 81
134 | -2.7, # 82
135 | -2.5, # 83
136 | -2.4, # 84
137 | -2.3, # 85
138 | -2.1, # 86
139 | -1.9, # 87
140 | -1.7, # 88
141 | -1.6, # 89
142 | -1.5, # 90
143 | -1.3, # 91
144 | -1.1, # 92
145 | -1, # 93
146 | -0.9, # 94
147 | -0.7, # 95
148 | -0.5, # 96
149 | -0.4, # 97
150 | -0.3, # 98
151 | -0.12, # 99
152 | 0, # 100
153 | ]
154 | volume.SetMasterVolumeLevel(volumes[percentInt], None)
155 |
156 | intent.variables["percent"] = intent.variables['percent']
157 | self.tts(intent.answer())
158 |
--------------------------------------------------------------------------------
/src/porcupine/WakeWord_Ok-NOVA_fr_mac.ppn:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HeyHeyChicken/NOVA-Python/64a4d2f04a7b6038e3fc4ff45a430301ab9c7b2c/src/porcupine/WakeWord_Ok-NOVA_fr_mac.ppn
--------------------------------------------------------------------------------
/src/porcupine/WakeWord_Ok-NOVA_fr_nt.ppn:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HeyHeyChicken/NOVA-Python/64a4d2f04a7b6038e3fc4ff45a430301ab9c7b2c/src/porcupine/WakeWord_Ok-NOVA_fr_nt.ppn
--------------------------------------------------------------------------------
/src/porcupine/WakeWord_Ok-NOVA_fr_raspberry.ppn:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HeyHeyChicken/NOVA-Python/64a4d2f04a7b6038e3fc4ff45a430301ab9c7b2c/src/porcupine/WakeWord_Ok-NOVA_fr_raspberry.ppn
--------------------------------------------------------------------------------
/src/porcupine/porcupine_params_fr.pv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HeyHeyChicken/NOVA-Python/64a4d2f04a7b6038e3fc4ff45a430301ab9c7b2c/src/porcupine/porcupine_params_fr.pv
--------------------------------------------------------------------------------