Projektbeschreibung
In diesem Projekt wurde ein Sprachassistent entwickelt, der das Licht eines LED-Strips einschaltet, die Temperatur misst, die Helligkeit erfasst und mittels ChatGPT Fragen beantworten konnte.
Verwendete Teile
- Arduino MKR WiFi 1010 [Sollte aber mit anderen Modellen ebenfalls funktionieren]
- Raspberry Pi 4
- Raspberry Pi 7 Touch Screen
- Lautsprecher
- Mikrofon
- Temperatursensor
- Sonnenlichtsensor
- LED-Strips
Download Whole Project
Nova Skript
Code erfolgreich kopiert!
//------------------------------------------------------------------------
// SGenerated by: Luka Petkovic - Tim Tobler
// Source File: .\Nova.py
//------------------------------------------------------------------------
import json
import socket
from vosk import Model, KaldiRecognizer
import pyaudio
import pyttsx3
import pygame
import sys
import time
import random
from flask import Flask, request
import threading
from openai import OpenAI
# Konfiguration der OpenAI GPT-API
client = OpenAI(api_key='API-KEY')
# Definition von Farbkonstanten für die grafische Darstellung
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (173, 216, 230)
PINK = (255, 192, 203)
LIGHT_GREY = (230, 230, 230)
# Variablen zur Steuerung der Mundanimation
mouth_opening = True
mouth_height_percent = 50
check_mouth_status = 0
# Variablen zur Steuerung des Augenblinkens
blink_counter = 0
blink_time = random.randint(60, 180)
blinking = False
blink_frames = 10
current_blink_frame = 0
# Position und Größe der Augen
left_eye_pos = (300, 200)
right_eye_pos = (500, 200)
eye_radius = 50
pupil_radius = int(eye_radius / 3)
# Text und Anzeigedauer für die Sprechblase
speech_bubble_text = ""
speech_bubble_display_time = 0
def animate_mouth_opening():
global mouth_height_percent, mouth_opening, check_mouth_status
# Animiert das Öffnen des Mundes von geschlossen bis vollständig offen
# Mundhöhe wird von 50% zu 0% und dann zu 100% verändert
# check_mouth_status wird verwendet, um den Animationsstatus zu überwachen
while mouth_height_percent > 0:
check_mouth_status = 0
mouth_height_percent -= 2
time.sleep(0.005)
while mouth_height_percent < 100:
check_mouth_status = 1
mouth_height_percent += 2
time.sleep(0.005)
mouth_opening = False
def animate_mouth_closing():
global mouth_height_percent, mouth_opening, check_mouth_status
# Animiert das Schließen des Mundes von vollständig offen bis halb geschlossen
# Mundhöhe wird von 100% zu 0% und dann zu 50% verändert
while mouth_height_percent > 0:
check_mouth_status = 1
mouth_height_percent -= 2
time.sleep(0.005)
while mouth_height_percent < 50:
check_mouth_status = 0
mouth_height_percent += 2
time.sleep(0.005)
mouth_opening = True
def speak(text):
global speech_bubble_text
global speech_bubble_display_time
# Startet die Mundöffnungsanimation in einem separaten Thread
mouth_open_thread = threading.Thread(target=animate_mouth_opening)
mouth_open_thread.start()
mouth_open_thread.join()
# Stoppt den Audio-Stream und zeigt den gesprochenen Text in einer Sprechblase an
stream.stop_stream()
speech_bubble_text = text
speech_bubble_display_time = 7.5
# Verwendet die pyttsx3-Bibliothek, um den Text auszusprechen
engine.say(text)
engine.runAndWait()
# Startet die Mundschließungsanimation in einem separaten Thread
mouth_close_thread = threading.Thread(target=animate_mouth_closing)
mouth_close_thread.start()
# Startet den Audio-Stream neu
stream.start_stream()
def change_led_color(color_name):
udp_ip = "192.168.46.140"
udp_port = 2390
message = "Farbe:" + color_name
try:
# Sendet einen UDP-Befehl, um die LED-Farbe zu ändern
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(message.encode(), (udp_ip, udp_port))
speak(f"Farbe zu {color_name} geändert.")
except Exception as e:
speak("Fehler bei der Kommunikation mit dem Arduino.")
print(e)
def shutdown():
# Beendet das Programm
speak("Ok")
time.sleep(1)
pygame.quit()
sys.exit()
def initialize():
global app, arduino_data, flask_thread, engine, model, rec, p, stream
# Initialisiert Flask für das Empfangen von Daten vom Arduino
app = Flask(__name__)
arduino_data = {"Temperatur": 0, "Feuchtigkeit": 0, "Helligkeit": 0}
@app.route('/api/data', methods=['POST'])
def receive_data():
# Nimmt Daten vom Arduino entgegen und aktualisiert die globalen Variablen
global arduino_data
data = request.json
arduino_data.update(data)
return {"message": "Daten empfangen"}, 200
def run_flask():
# Startet den Flask-Server
app.run(host='0.0.0.0', port=5000, debug=False)
# Startet den Flask-Server in einem separaten Thread
flask_thread = threading.Thread(target=run_flask)
flask_thread.start()
# Initialisiert pyttsx3 für Text-zu-Sprache und Vosk für Spracherkennung
engine = pyttsx3.init()
engine.setProperty('voice', 'german')
engine.setProperty('rate', 125)
engine.setProperty('volume', 1)
model_path = "./vosk-model-de-0.21/vosk-model-de-0.21"
model = Model(model_path)
rec = KaldiRecognizer(model, 16000)
p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=32768)
stream.start_stream()
def draw_mouth(mouth_height_percent):
global check_mouth_status
# Zeichnet den Mund basierend auf dem aktuellen Prozentsatz der Mundhöhe
mouth_width = 100
mouth_height = 5 + int(45 * (mouth_height_percent / 100))
mouth_rect = pygame.Rect(350, 300 + (50 - mouth_height) // 2, mouth_width, mouth_height)
# Zeichnet eine Zunge, wenn der Mund weit genug offen ist
if check_mouth_status == 1:
pygame.draw.ellipse(screen, WHITE, mouth_rect, 2)
tongue_rect = pygame.Rect(mouth_rect.left + 20, mouth_rect.top + mouth_height // 2, mouth_rect.width - 40, mouth_rect.height // 2)
pygame.draw.ellipse(screen, PINK, tongue_rect)
else:
pygame.draw.arc(screen, WHITE, mouth_rect, 3.14, 2 * 3.14, 2)
def draw_speech_bubble(text):
# Zeichnet eine Sprechblase mit dem angegebenen Text
text_surface = font.render(text, True, BLACK)
text_width, text_height = text_surface.get_size()
padding_horizontal = 30
padding_vertical = 20
bubble_width = text_width + padding_horizontal * 2
bubble_height = text_height + padding_vertical * 2
bubble_x = screen_width / 2 - bubble_width / 2
bubble_y = 50
bubble_rect = pygame.Rect(bubble_x, bubble_y, bubble_width, bubble_height)
pygame.draw.ellipse(screen, LIGHT_GREY, bubble_rect)
pygame.draw.ellipse(screen, BLACK, bubble_rect, 2)
tail_points = [(bubble_x + bubble_width / 2, bubble_y + bubble_height),
(bubble_x + bubble_width / 2 - 10, bubble_y + bubble_height + 20),
(bubble_x + bubble_width / 2 + 10, bubble_y + bubble_height + 20)]
pygame.draw.polygon(screen, LIGHT_GREY, tail_points)
pygame.draw.lines(screen, BLACK, False, tail_points, 2)
screen.blit(text_surface, (bubble_x + padding_horizontal, bubble_y + padding_vertical))
def draw_face(mouth_height_percent, blinking, current_blink_frame, speaking_text):
global speech_bubble_text
global speech_bubble_display_time
# Aktualisiert die Anzeige des Gesichts mit Mund, Augen und Sprechblase
screen.fill(BLACK)
draw_mouth(mouth_height_percent)
# Zeigt die Sprechblase an, wenn sie aktiv ist
if speech_bubble_display_time > 0:
draw_speech_bubble(speech_bubble_text)
speech_bubble_display_time -= 1/30
else:
speech_bubble_text = ""
# Steuert die Augenanimation für das Blinzeln
if blinking:
if current_blink_frame <= blink_frames / 2:
eye_scale = 1 - (current_blink_frame / (blink_frames / 2))
else:
eye_scale = (current_blink_frame - blink_frames / 2) / (blink_frames / 2)
else:
eye_scale = 1
eye_height = int(eye_radius * eye_scale)
for pos in [left_eye_pos, right_eye_pos]:
pygame.draw.ellipse(screen, BLUE, (pos[0] - eye_radius, pos[1] - eye_height, eye_radius * 2, max(eye_height * 2, 1)))
pygame.draw.circle(screen, BLACK, pos, pupil_radius)
def draw_loading_bar(screen, percentage, screen_width, screen_height):
# Zeichnet eine Ladeleiste mit dem gegebenen Füllstand
bar_width = screen_width * 0.6
bar_height = 30
bar_x = (screen_width - bar_width) / 2
bar_y = (screen_height - bar_height) / 2
pygame.draw.rect(screen, WHITE, [bar_x, bar_y, bar_width, bar_height], 2)
fill_width = percentage * bar_width
pygame.draw.rect(screen, BLUE, [bar_x, bar_y, fill_width, bar_height])
# Funktion zum Zeichnen der Ladebalken-Szene
def loading_scene(screen, screen_width, screen_height):
loading_duration = 90 # Gesamtdauer in Sekunden (1 Minute und 30 Sekunden)
frame_rate = 30 # Frames pro Sekunde
total_frames = loading_duration * frame_rate
font = pygame.font.SysFont("Arial", 24) # Sie können eine andere Schriftart und Größe wählen
base_text = "Initialisieren"
base_text_surface = font.render(base_text, True, WHITE)
text_x = (screen_width - base_text_surface.get_width()) / 2
dot_counter = 0
max_dots = 3
dot_update_interval = frame_rate * 1.5 # Aktualisieren alle 1.5 Sekunden
for frame in range(total_frames):
# Zeichnet die Ladebalken-Szene und zeigt den Fortschritt an
loading_percentage = frame / total_frames
screen.fill(BLACK)
if frame % dot_update_interval == 0:
dot_counter = (dot_counter % max_dots) + 1
dots = "." * dot_counter
full_text_surface = font.render(f"{base_text}{dots}", True, WHITE)
full_text_rect = full_text_surface.get_rect(left=text_x, centery=screen_height / 2 - 50)
screen.blit(full_text_surface, full_text_rect)
draw_loading_bar(screen, loading_percentage, screen_width, screen_height)
pygame.display.update()
time.sleep(1/frame_rate)
def ask_gpt(question):
# Stellt eine Frage an die OpenAI GPT-API und gibt die Antwort zurück
try:
prompt = f"Bitte geben Sie eine kurze Antwort in maximal 20 Token: {question}"
response = client.chat.completions.create(
model="gpt-3.5-turbo-1106",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": prompt}
],
max_tokens=30
)
return response.choices[0].message.content
except Exception as e:
return str(e)
def process_speech(data):
# Verarbeitet gesprochene Daten und gibt erkannten Text zurück
if rec.AcceptWaveform(data):
result_json = rec.Result()
result = json.loads(result_json)
if result is not None and 'text' in result:
return result['text'].lower()
return None
def handle_command(recognized_text):
global nova_activated
# Verarbeitet erkannten Text und führt entsprechende Befehle aus
if "nova" in recognized_text or "nora" in recognized_text:
nova_activated = True
if nova_activated:
if "bitte ausschalten" in recognized_text:
shutdown()
elif "wie geht es dir" in recognized_text:
speak("Mir geht es gut, danke!")
elif "zeige sensor werte" in recognized_text:
temp = arduino_data["Temperatur"]
humidity = arduino_data["Feuchtigkeit"]
light = arduino_data["Helligkeit"]
speak(f"Die Temperatur beträgt {temp} Grad, die Luftfeuchtigkeit {humidity} Prozent und die Helligkeit {light}.")
elif "wechsle farbe zu" in recognized_text:
color_name = recognized_text.split("zu ")[1]
change_led_color(color_name)
else:
answer = ask_gpt(recognized_text)
speak(answer)
nova_activated = False
def main_loop():
global handled
has_greeted = False
while True:
# Hauptprogrammschleife, in der Spracheingaben verarbeitet werden
if not has_greeted:
speak("Hallo, ich bin Nova, Ihr Sprachassistent.")
has_greeted = True
for event in pygame.event.get():
if event.type == pygame.QUIT:
shutdown()
# Verarbeitung der Spracheingaben
data = stream.read(4096, exception_on_overflow=False)
recognized_text = process_speech(data)
if recognized_text:
handle_command(recognized_text)
# Initialisierung und Start des Programms
initialization_thread = threading.Thread(target=initialize)
initialization_thread.start()
# Initialisierung von Pygame und Einstellung der Fenstergröße und des Titels
pygame.init()
screen_width = 800
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Sprachassistent")
# Anzeigen der Ladebalken-Szene während der Initialisierung
loading_scene(screen, screen_width, screen_height)
font = pygame.font.SysFont("Arial", 16)
initialization_thread.join()
# Start der Hauptprogrammschleife in einem separaten Thread
main_loop_thread = threading.Thread(target=main_loop)
has_main_loop_start = False
while True:
if not has_main_loop_start:
main_loop_thread.start()
has_main_loop_start = True
# Steuerung des Augenblinkens
if not blinking:
blink_counter += 1
if blink_counter >= blink_time:
blinking = True
current_blink_frame = 0
else:
if current_blink_frame < blink_frames:
current_blink_frame += 1
else:
blinking = False
current_blink_frame = 0
blink_counter = 0
blink_time = random.randint(60, 180)
# Aktualisieren des Gesichts und Anzeigen von Sprechtext
draw_face(mouth_height_percent, blinking, current_blink_frame, "Sprechtext")
pygame.display.update()
time.sleep(1/30)
# Beendet Pygame, wenn das Programm beendet wird
pygame.quit()