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

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()