#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <Adafruit_FT6206.h>
// WiFi-Konfiguration
String ssid = "Wokwi-GUEST";
String password = "";
// OpenAI API-Konfiguration
const String openai_api_key = "sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
const String openai_url = "https://api.openai.com/v1/chat/completions";
// Hardware-Pins
#define TFT_DC 2
#define TFT_CS 15
#define TFT_MOSI 23
#define TFT_SCLK 18
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
Adafruit_FT6206 ts = Adafruit_FT6206();
// Status in Aktion
int inAction = 0;
// 0 = init state
// 1 = connect wifi
// 2 = reconnect wifi
// 3 = chatGPT (keyboard touch input)
// 4 = chatGPT (serial input)
// 99 = idle mode (facial expressions)
// Eingabeaufforderung
String userPrompt = "";
// Funktion zum Abrufen der Antwort von ChatGPT
String getChatGPTResponse(String prompt) {
HTTPClient http;
http.begin(openai_url);
// Bereite die Daten für die Anfrage vor
String jsonData = "{\"model\":\"gpt-4o-mini\", \"messages\":[{\"role\":\"developer\", \"content\":\""+ prompt +"\"}], \"temperature\":0.5, \"max_tokens\":1048, \"top_p\":1, \"frequency_penalty\":0, \"presence_penalty\":0}";
// Header hinzufügen
http.addHeader("Content-Type", "application/json");
http.addHeader("Authorization", "Bearer " + openai_api_key);
// HTTP POST ausführen
int httpResponseCode = http.POST(jsonData);
if (httpResponseCode != 200) {
Serial.print("HTTP Error: ");
Serial.println(httpResponseCode);
http.end();
return "Error connecting to OpenAI!";
}
String response = http.getString();
http.end();
// Antwort parsen
DynamicJsonDocument responseDoc(2048);
DeserializationError error = deserializeJson(responseDoc, response);
if (error) {
Serial.print("deserializeJson() failed: ");
Serial.println(error.c_str());
return "Error parsing response!";
}
// Extrahiere den Inhalt der Antwort des Assistenten
String chatResponse = responseDoc["choices"][0]["message"]["content"].as<String>();
return chatResponse;
}
// Funktion zum schrittweisen Anzeigen der Antwort auf dem TFT-Display
void displayChatGPTResponseStepwise(String response) {
const int maxY = 320; // Höhe des Displays
const int textHeight = 16; // Höhe einer Textzeile (bei Textgröße 2)
const int maxLines = maxY / textHeight; // Maximale Zeilenanzahl, die auf das Display passt
// Feste Zeilen für die Überschrift
int headerLines = 3; // "Prompt:", der Prompt selbst, und "Response:"
int availableLines = maxLines - headerLines; // Verfügbare Zeilen für den Antworttext
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 0);
tft.setTextSize(2);
// Überschriften anzeigen
tft.setTextColor(ILI9341_WHITE);
tft.println("Prompt:");
tft.setTextColor(ILI9341_GREEN);
tft.println(userPrompt);
tft.setTextColor(ILI9341_WHITE);
tft.println("\nResponse:");
// Cursorposition speichern, um Text unterhalb der Überschrift zu beginnen
int cursorX = 0;
int cursorY = tft.getCursorY();
tft.setTextColor(ILI9341_CYAN);
// Zeichen-für-Zeichen-Anzeige
for (int i = 0; i < response.length(); i++) {
char currentChar = response[i];
// Zeilenumbruch, wenn der Text das Display horizontal überschreitet
if (cursorX >= tft.width() - 8 * 1.5) { // 8 Pixel pro Zeichen * Breite der Textgröße 2
cursorX = 0;
cursorY += textHeight;
}
// Zeilenumbruch, wenn das Display vertikal voll ist
if (cursorY >= maxY) {
delay(5000); // 5 Sekunden warten
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 0);
tft.setTextSize(2);
// Überschriften erneut anzeigen
tft.setTextColor(ILI9341_WHITE);
tft.println("Prompt:");
tft.setTextColor(ILI9341_GREEN);
tft.println(userPrompt);
tft.setTextColor(ILI9341_WHITE);
tft.println("\nResponse:");
// Cursorposition zurücksetzen
cursorY = tft.getCursorY();
}
// Zeichen zeichnen
tft.setCursor(cursorX, cursorY);
tft.setTextColor(ILI9341_CYAN);
tft.print(currentChar);
// Nächste Zeichenposition berechnen
cursorX += 8 * 1.5; // 8 Pixel pro Zeichen * Breite der Textgröße 2
// Kurze Verzögerung für "Aufbau-Effekt"
delay(50); // 50ms pro Zeichen
}
}
void resetMyDisplay(){
tft.fillScreen(ILI9341_BLACK);
tft.setTextColor(ILI9341_CYAN);
tft.setCursor(0, 0);
//tft.println("listening...");
inAction = 99;
}
// Funktionen für die verschiedenen Gesichtsausdrücke
void drawEyes(int x, int y, int eyeSize, int eyeYPos, int expressionType) {
tft.fillScreen(ILI9341_BLACK);
// Augenposition in der Mitte
int leftEyeX = x - 50;
int rightEyeX = x + 50;
int eyeY = y + eyeYPos;
// Hintergrundfarbe für Augen entfernen (falls nötig)
tft.fillRect(leftEyeX - eyeSize, eyeY - eyeSize, 2 * eyeSize + 20, 2 * eyeSize + eyeSize / 2, ILI9341_BLACK);
tft.fillRect(rightEyeX - eyeSize, eyeY - eyeSize, 2 * eyeSize + 20, 2 * eyeSize + eyeSize / 2, ILI9341_BLACK);
// Augen zeichnen (wird je nach Ausdruck variieren)
if (expressionType == 0) { // Normale Augen (runde Form)
tft.fillCircle(leftEyeX, eyeY, eyeSize, ILI9341_CYAN);
tft.fillCircle(rightEyeX, eyeY, eyeSize, ILI9341_CYAN);
} else if (expressionType == 1) { // Augen zusammengekniffen (z.B. Lächeln)
tft.fillCircle(leftEyeX, eyeY, eyeSize * 1.2, ILI9341_CYAN);
tft.fillCircle(rightEyeX, eyeY, eyeSize * 1.2, ILI9341_CYAN);
tft.fillCircle(leftEyeX, eyeY + 40, eyeSize * 1.2, ILI9341_BLACK);
tft.fillCircle(rightEyeX, eyeY + 40, eyeSize * 1.2, ILI9341_BLACK);
} else if (expressionType == 2) { // Normale Augen (runde Form) nach links
tft.fillCircle(leftEyeX - 20, eyeY, eyeSize * 1.2, ILI9341_CYAN);
tft.fillCircle(rightEyeX - 20, eyeY + 20, eyeSize, ILI9341_CYAN);
} else if (expressionType == 3) { // Augen weit aufgerissen (z.B. überrascht)
tft.fillCircle(leftEyeX, eyeY, eyeSize * 1.8, ILI9341_CYAN);
tft.fillCircle(rightEyeX, eyeY, eyeSize * 1.8, ILI9341_CYAN);
} else if (expressionType == 4) { // Traurige Augen (mit Rechtecken)
tft.fillRoundRect(leftEyeX - eyeSize, eyeY - eyeSize / 2, 2 * eyeSize, eyeSize / 2, 10, ILI9341_CYAN);
tft.fillRoundRect(rightEyeX - eyeSize, eyeY - eyeSize / 2, 2 * eyeSize, eyeSize / 2, 10, ILI9341_CYAN);
} else if (expressionType == 5) { // Normale Augen (runde Form) nach rechts
tft.fillCircle(leftEyeX + 20, eyeY + 20, eyeSize, ILI9341_CYAN);
tft.fillCircle(rightEyeX + 20, eyeY, eyeSize * 1.2, ILI9341_CYAN);
} else if (expressionType == 6) { // Normale Augen (runde Form) nach unten
tft.fillCircle(leftEyeX, eyeY - 40, eyeSize, ILI9341_CYAN);
tft.fillCircle(rightEyeX, eyeY - 40, eyeSize, ILI9341_CYAN);
} else if (expressionType == 7) { // Normale Augen (runde Form) nach oben
tft.fillCircle(leftEyeX, eyeY + 40, eyeSize, ILI9341_CYAN);
tft.fillCircle(rightEyeX, eyeY + 40, eyeSize, ILI9341_CYAN);
} else if (expressionType == 8) { // Zwinkernde Augen (Kreis + Rechteck)
tft.fillCircle(leftEyeX, eyeY, eyeSize, ILI9341_CYAN); // Kreis für das linke Auge
tft.fillCircle(leftEyeX, eyeY + 40, eyeSize, ILI9341_BLACK);
tft.fillRoundRect(rightEyeX - eyeSize, eyeY, 2 * eyeSize, eyeSize / 2, 10, ILI9341_CYAN);
} else if (expressionType == 9) { // Wütende Augen (mit schmaleren Rechtecken)
tft.fillTriangle(leftEyeX - eyeSize, eyeY - eyeSize / 3, leftEyeX + eyeSize, eyeY - eyeSize / 3,
leftEyeX - eyeSize, eyeY - eyeSize * 2, ILI9341_RED);
tft.fillTriangle(rightEyeX - eyeSize, eyeY - eyeSize / 3 , rightEyeX + eyeSize, eyeY - eyeSize / 3,
rightEyeX + eyeSize, eyeY - eyeSize * 2, ILI9341_RED);
} else if (expressionType == 10) { // Verwirrte Augen (Kreis + Rechteck)
tft.fillCircle(leftEyeX, eyeY, eyeSize, ILI9341_YELLOW);
tft.fillCircle(rightEyeX, eyeY, eyeSize * 1.5, ILI9341_YELLOW);
} else{
}
}
// Tastatur Layout
int keyOption = 0;
const char* keyboard0[3][10] = {
{"Q", "W", "E", "R", "T", "Z", "U", "I", "O", "P"},
{"A", "S", "D", "F", "G", "H", "J", "K", "L", "dl"},
{"Y", "X", "C", "V", "B", "N", "M", " ", "..","en"}
};
const char* keyboard1[3][10] = {
{"q", "w", "e", "r", "t", "z", "u", "i", "o", "p"},
{"a", "s", "d", "f", "g", "h", "j", "k", "l", "dl"},
{"y", "x", "c", "v", "b", "n", "m", " ", "..", "en"}
};
const char* keyboard2[3][10] = {
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"},
{"!", "§", "$", "%", "&", "/", "@", "()", "=", "*"},
{"?", ",", ";", ".", ":", "-", "_", "+", "..","en"}
};
// Tastenkoordinaten
int keyWidth = 24;
int keyHeight = 24;
int startX = 0;
int startY = 248; // Untere Hälfte des Displays
// Variable für die Eingabe
String userInput = "";
// Funktion zum Zeichnen der Tastatur
void drawKeyboard() {
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 10; col++) {
// Berechne Position der Taste
int x = startX + col * keyWidth;
int y = startY + row * keyHeight;
// Zeichne die Taste
tft.fillRect(x, y, keyWidth, keyHeight, ILI9341_BLACK);
tft.drawRect(x, y, keyWidth, keyHeight, ILI9341_GREEN);
tft.setCursor(x + 7, y + 5); // Zentrierte Position
tft.setTextColor(ILI9341_GREEN);
tft.setTextSize(2);
if(keyOption == 0){
tft.print(keyboard0[row][col]);
} else if (keyOption == 1){
tft.print(keyboard1[row][col]);
} else if (keyOption == 2){
tft.print(keyboard2[row][col]);
}
}
}
if(keyOption == 0){
keyOption = 1;
} else if (keyOption == 1){
keyOption = 2;
} else if (keyOption == 2){
keyOption = 0;
}else{
}
}
TS_Point rotateTouch(TS_Point point, uint8_t rotation, uint16_t screenWidth, uint16_t screenHeight) {
TS_Point rotatedPoint;
switch (rotation) {
case 0: // Keine Rotation
rotatedPoint.x = point.x;
rotatedPoint.y = point.y;
break;
case 1: // 90 Grad
rotatedPoint.x = screenHeight - point.y;
rotatedPoint.y = point.x;
break;
case 2: // 180 Grad
rotatedPoint.x = screenWidth - point.x;
rotatedPoint.y = screenHeight - point.y;
break;
case 3: // 270 Grad
rotatedPoint.x = point.y;
rotatedPoint.y = screenWidth - point.x;
break;
default: // Standard: Keine Rotation
rotatedPoint.x = point.x;
rotatedPoint.y = point.y;
break;
}
return rotatedPoint;
}
// Funktion zum Überprüfen der Berührung und Aktualisieren der Eingabe
void checkTouch() {
static unsigned long lastTouchTime = 0; // Zeit der letzten Berührung
const unsigned long debounceDelay = 200; // Entprell-Zeit in Millisekunden
if (ts.touched()) {
unsigned long currentTime = millis(); // Aktuelle Zeit abrufen
// Entprellung: Ignoriere Berührung, wenn sie zu schnell hintereinander erfolgt
if (currentTime - lastTouchTime < debounceDelay) {
return;
}
lastTouchTime = currentTime;
TS_Point p = ts.getPoint();
// Zurück zur "Startseite" (Display löschen), wenn in den Bereich links oben gedrückt wird
if (p.x >= 220 && p.y >= 300) {
resetMyDisplay();
return; // Verlasse die Funktion, um weitere Aktionen zu verhindern
}
// Eingabeaufforderung öffnen
if (p.x <= 20 && p.y >= 300) {
if(inAction == 99){
inAction = 3;
}
tft.fillScreen(ILI9341_BLACK);
drawKeyboard();
userInput = ""; // Eingabe zurücksetzen
return; // Verlasse die Funktion, um weitere Aktionen zu verhindern
}
// Rotation anwenden
uint8_t rotation = 2; // Beispiel: 180 Grad Rotation
p = rotateTouch(p, rotation, 240, 320);
// Überprüfen, ob die Berührung innerhalb der Tasten liegt
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 10; col++) {
int x = startX + col * keyWidth;
int y = startY + row * keyHeight;
if (p.x >= x && p.x <= x + keyWidth && p.y >= y && p.y <= y + keyHeight) {
String character = "";
if(keyOption == 1){
character = keyboard0[row][col];
} else if (keyOption == 2) {
character = keyboard1[row][col];
} else if (keyOption == 0) {
character = keyboard2[row][col];
}
if (character == "en") {
// ENTER-Taste gedrückt -----------------------------------------------------------------------------
tft.fillScreen(ILI9341_BLACK);
if(inAction == 3){
userPrompt = userInput;
userInput = "";
String response = getChatGPTResponse(userPrompt);
displayChatGPTResponseStepwise(response);
} else if (inAction == 2){
// Index des Slashes finden
int slashIndex = userInput.indexOf('/');
if (slashIndex != -1) { // Überprüfen, ob der Slash existiert
ssid = userInput.substring(0, slashIndex).c_str(); // Teilstring vor dem Slash (ssid)
password = userInput.substring(slashIndex + 1).c_str(); // Teilstring nach dem Slash (password)
connectWifi();
}else {
tft.println("wrong ssid/password...");
}
}
} else if (character == "dl") {
// Rücktaste gedrückt
if (userInput.length() > 0) {
userInput.remove(userInput.length() - 1); // Letztes Zeichen löschen
tft.fillRect(0, 0, 240, 248, ILI9341_BLACK); // Eingabebereich leeren
tft.setCursor(0, 0);
tft.setTextColor(ILI9341_GREEN);
tft.setTextSize(2);
tft.print(userInput);
}
} else if (character == "..") {
drawKeyboard();
} else {
userInput += character; // Füge den Buchstaben hinzu
// Nur den Eingabebereich aktualisieren
tft.fillRect(0, 0, 240, 30, ILI9341_BLACK); // Eingabebereich leeren
tft.setCursor(0, 0);
tft.setTextColor(ILI9341_GREEN);
tft.setTextSize(2);
tft.print(userInput);
}
return; // Verlasse die Funktion, um weitere Aktionen zu verhindern
}
}
}
}
}
int connectWifi(){
inAction = 1;
Serial.println("SSID: " + ssid);
Serial.println("Password: " + password);
// WiFi verbinden
WiFi.begin(ssid, password, 6);
tft.begin();
tft.setRotation(0);
tft.setTextColor(ILI9341_CYAN);
tft.setTextSize(2);
tft.setCursor(0, 0);
tft.print("Connecting to WiFi");
unsigned long startTime = millis(); // Startzeit speichern
unsigned long timeout = 18000; // 18 Sekunden Timeout
while (WiFi.status() != WL_CONNECTED) {
if (millis() - startTime > timeout) {
// Wenn 10 Sekunden vergangen sind, beenden und Fehler anzeigen
tft.fillScreen(ILI9341_BLACK); // Bildschirm löschen
tft.setCursor(0, 0);
tft.setTextColor(ILI9341_RED); // Fehlerfarbe
tft.println("WiFi connection failed!");
Serial.println("WiFi connection failed!");
WiFi.mode(WIFI_OFF); // Setzt den Wi-Fi-Modus aus
delay(1000); // Warten
WiFi.mode(WIFI_STA); // Setze den Wi-Fi-Modus wieder auf Station
WiFi.disconnect();
return 0; // Verlasse die Funktion (kein weiteres Setup)
}
delay(100);
tft.print(".");
}
delay(1000); // Warten
// Wenn WiFi verbunden ist
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 0);
tft.setTextColor(ILI9341_GREEN); // Erfolgsfarbe
tft.println("WiFi Connected!");
tft.println("IP: " + WiFi.localIP().toString());
delay(1000); // Warten
// Warten auf Benutzereingabe
resetMyDisplay();
return 1;
}
void setup() {
Serial.begin(115200);
randomSeed(analogRead(0)); // Zufallsquelle initialisieren
if (!ts.begin(40)) { // Sensitivität kann angepasst werden
Serial.println("FT6206 Touchscreen konnte nicht initialisiert werden!");
while (1); // Endlosschleife bei Fehler
} else {
Serial.println("FT6206 Touchscreen erfolgreich initialisiert.");
}
if(connectWifi() == 1){
// Wenn WiFi verbunden ist
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 0);
tft.setTextColor(ILI9341_GREEN); // Erfolgsfarbe
tft.println("WiFi Connected!");
tft.println("IP: " + WiFi.localIP().toString());
// Warten auf Benutzereingabe
resetMyDisplay();
} else{
inAction = 2;
tft.println("enter ssid/password...");
}
}
// Globale Variablen für den Timer
unsigned long lastActionTime = 0; // Letzte Ausführungszeit des Ausdrucks
const unsigned long interval = 3000; // Intervall von 3 Sekunden
void loop() {
checkTouch(); // Überprüfen, ob eine Taste berührt wurde
// Prüfe auf Eingabe im Seriellen Monitor
if (Serial.available() > 0) {
inAction = 4;
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 0);
tft.setTextSize(2);
// Benutzerprompt lesen
userPrompt = Serial.readStringUntil('\n');
userPrompt.trim(); // Leerzeichen und Zeilenumbrüche entfernen
// Anfrage senden
String response = getChatGPTResponse(userPrompt);
displayChatGPTResponseStepwise(response);
}
// Zufällige Ausdrücke anzeigen (alle 3 Sekunden)
if (inAction == 99) {
unsigned long currentTime = millis(); // Aktuelle Zeit abfragen
// Prüfe, ob 3 Sekunden vergangen sind
if (currentTime - lastActionTime >= interval) {
lastActionTime = currentTime; // Zeit aktualisieren
int expressionType = random(0, 8);
drawEyes(120, 160, 30, 0, expressionType);
}
}
}