In diesem Beitrag zeige ich dir, wie du auf deinem ESP32 eine Schnittstelle zur künstlichen Intelligenz ChatGPT herstellst und mit dieser kommunizierst.
Für diesen Beitrag verwende ich den ESP32-C3 von der Firma Seeed Studio, dieser kleine Mikrocontroller verfügt über eine WiFi-Schnittstelle und ein paar GPIO Pins (welche aber für diesen Beitrag nicht benötigt werden).
Den ESP32-C3 habe ich dir bereits im Beitrag Mikrocontroller ESP32C3 von Seeed Studio vorgestellt und gezeigt, wie dieser in der Arduino IDE 2.0 programmiert wird. An diesen Beitrag möchte ich nun anknüpfen. Ich gehe daher davon aus, dass der Mikrocontroller bereits auf deinem Computer eingerichtet und in der Arduino IDE 2.0 lauffähig ist.
Im letzten Beitrag Absenden eines Requests an ChatGPT per Postman hatte ich dir bereits erläutert, wie du mithilfe von der kostenfreien Anwendung Postman eine Anfrage absendest. Dort wird die Anfrage per JSON-Request gesendet und dieses möchte ich hier ebenso nutzen um die Anfrage vom ESP32 an ChatGPT zu senden.
Aufbau einer WiFi Verbindung zum lokalen Netzwerk
Damit wir mit ChatGPT kommunizieren können, benötigen wir zunächst eine WiFi Verbindung zum lokalen Netzwerk oder zu einem Hotspot.
//Einbinden der Bibliotheken //für die WiFi Verbindung des ESP32 #include <WiFi.h> #include <WebServer.h> //SSID & Passwort für das lokale WiFi #define WIFI_SSID "" #define WIFI_PASSWORD "" //Initialisieren eines Webservers mit dem Port 80 (default http) WebServer server(80); void setup() { //begin der seriellen Kommunikation mit 115200 baud Serial.begin(115200); //Aufbau einer WiFi Verbindung mit den Daten WiFi.begin(WIFI_SSID, WIFI_PASSWORD); //maximal 10 Versuche zum aufbau der WiFi Verbindung int maxConnectionAttempts = 10; int counter = 0; //sollange die Verbindung nicht aufgebaut ist UND //der Zähler der Verbindungsversuche kleiner als den Wert der Variable //maxConnectionAttempts ist, dann... while (WiFi.status() != WL_CONNECTED && counter < maxConnectionAttempts) { //schreiben eines Punktes auf der seriellen Schnittstelle Serial.print("."); //den Zähler um eins erhöhen counter++; //eine kleine Pause von 250 Millisekunden delay(250); } //Wenn die Verbindungsversuche "verbraucht" sind oder eine //Verbindung zum WiFi Netzwerk aufgebaut wurde, dann soll //dieses ausgegeben werden. Serial.println(""); Serial.print("Zum WiFi Netzwerk "); Serial.print(WIFI_SSID); Serial.println(WiFi.isConnected() ? " verbunden" : " nicht verbunden"); //Wenn die Verbindung erfolgreich aufgebaut wurde, //dann soll die IP-Adresse auf der seriellen Schnittstelle //ausgegeben werden. if (WiFi.isConnected()) { Serial.print("IP-Adresse: "); Serial.println(WiFi.localIP()); Serial.println(); server.begin(); server.on("/", callWebsite); } else { //Wenn die Verbindung nicht aufgebaut wurde, dann soll //eine entsprechende Meldung ausgegeben werden. Serial.print("Fehler beim Aufbau der Verbindung zu "); Serial.println(WIFI_SSID); Serial.println(); } } //Callback Funktion welche aufgerufen wird wenn der Benutzer //im Browser die IP-Adresse eingibt. void callWebsite() { //eine kleine einfache Webseite mit dem Text "Hallo Welt!" String htmlpage = "<html><body><h1>Hallo Welt!</h1></body></html>"; //Absenden der Seite //mit HTTP-Code 200 "OK", //als MimeType "text/html" server.send(200, "text/html", htmlpage); } /** * Funktion wird fortlaufen ausgeführt. **/ void loop() { server.handleClient(); }
Wenn der Code erfolgreich hochgeladen wurde, muss die Taste Reboot “R” auf dem Mikrocontroller betätigt werden.
Es sollte dann die nachfolgende Ausgabe im seriellen Monitor erscheinen und wir können nun bei erfolgreicher Verbindung die IP-Adresse ablesen.
ESP-ROM:esp32c3-api1-20210207
Build:Feb 7 2021
rst:0x15 (USB_UART_CHIP_RESET),boot:0x8 (SPI_FAST_FLASH_BOOT)
Saved PC:0x4203675c
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fcd5810,len:0x438
load:0x403cc710,len:0x91c
load:0x403ce710,len:0x25b0
entry 0x403cc710
.
Zum WiFi Netzwerk FRITZBox7590GI24 verbunden
IP-Adresse: 192.168.178.131
Wenn man nun die IP-Adresse 192.168.178.131 im Browser eingibt, erhält man den Text “Hallo Welt!”.
Nachfolgend das kleine Programm als ZIP-Datei zum download.
Erstellen einer HTML-Seite mit Eingabe-/Ausgabefeld
Für das Absenden der Anfrage an ChatGPT benötigen wir ein Eingabefeld auf der Webseite und für die Antwort ein Ausgabefeld (in diesem Fall ein einfacher DIV-Container).
Die CSS & JavaScriptdateien für diese Seite lagere ich auf die Adresse http://progs.ressourcen-draeger-it.de/chatgptesp32 aus.
<html lang='de'> <head> <meta charset='utf-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <title>ChatGPT & ESP32</title> <script type='text/javascript' src='http://progs.ressourcen-draeger-it.de/chatgptesp32/js/jquery-3.6.3.min.js'> </script> <script type='text/javascript' src='http://progs.ressourcen-draeger-it.de/chatgptesp32/js/functions.js'></script> <link rel='stylesheet' href='http://progs.ressourcen-draeger-it.de/chatgptesp32/css/styles.css'/> </head> <body> <div class='outer'> <h1>ChatGPT & ESP32</h1> <form id='chatgptmessages' name='chatgptmessages' action='/' method='get'> <center> <input type='text' placeholder='press return to submit' value='%inputFieldValue%' class='inputField' id='inputMsg' name='inputMsg'/></br></br> <div class='resultTxt'>%resultTxt%</div> <div class='errorMsg' %errorMsgStyle%>%errorMsg%</div> </center> </form> </div> </body> </html>
Diese Seite wollen wir in einen String im Code speichern, daher muss diese noch komprimiert werden. Zum Komprimieren von HTML Seiten kann man zum Beispiel das Onlinetool https://www.textfixer.de/html/html-komprimieren.php nutzen. Dieses Tool entfernt unnötige Zeilenumbrüche und andere Formatierungen.
String page = "<html lang='de'> <head> <meta charset='utf-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <title>ChatGPT & ESP32</title> <script type='text/javascript' src='http://progs.ressourcen-draeger-it.de/chatgptesp32/js/jquery-3.6.3.min.js'> </script> <script type='text/javascript' src='http://progs.ressourcen-draeger-it.de/chatgptesp32/js/functions.js'></script> <link rel='stylesheet' href='http://progs.ressourcen-draeger-it.de/chatgptesp32/css/styles.css'/> </head> <body><div class='outer'><h1>ChatGPT & ESP32</h1><form id='chatgptmessages' name='chatgptmessages' action='/' method='get'><center><input type='text' placeholder='press return to submit' value='%inputFieldValue%' class='inputField' id='inputMsg' name='inputMsg'/></br></br><div class='resultTxt'>%resultTxt%</div><div class='errorMsg' %errorMsgStyle%>%errorMsg%</div></center></form></div> </body></html>";
Platzhalter für Anfrage, Antwort und ggf. Fehlermeldungen
In den HTML-Code betten wir noch 3 Platzhalter für die Kommunikation mit ChatGPT und den später gezeigten JSON-Parser ein. Des Weiteren noch einen Platzhalter zum Steuern, ob die Fehlermeldung angezeigt werden soll oder nicht.
- %inputFieldValue%
- %resultTxt%,
- %errorMsgStyle%,
- %errorMsg%
Diese Platzhalter werden später mit einer Funktion durch den Text ersetzt (String.replace)
Aufbau eines JSON-Request an ChatGPT
Nachdem wir nun eine Verbindung zu einem lokalen WiFi Netzwerk erfolgreich aufgebaut und eine kleine Webseite erstellt haben, können wir einen Request / eine Anfrage an ChatGPT stellen.
Dazu benötigen wir einen API Key welchen man unter https://platform.openai.com/account/api-keys anlegen können.
Für die JSON Verarbeitung nutze ich die Bibliothek ArduinoJson.h welche über den internen Bibliotheksverwalter installiert werden kann.
Dazu öffnen wir den Bibliotheksverwalter (1) und suchen nach ArduinoJson (2) danach klicken wir auf die Schaltfläche INSTALL (3). Es werden dann die Dateien geladen und installiert, wenn dieser Vorgang abgeschlossen ist, wird dieses mit dem Label INSTALLED (4) angezeigt.
Zunächst müssen wir unseren Request an die KI aufbauen. Wie erwähnt, senden wir ein JSON an das System ab.
//definieren das wir Daten vom Typ "application/json" senden https.addHeader("Content-Type", "application/json"); //den API Key mit String token_key = "Bearer " + chatgptApiKey; https.addHeader("Authorization", token_key); StaticJsonDocument<200> data; //das verwendete Model data["model"] = "text-davinci-003"; //die Anfrage an das System data["prompt"] = chatgptQuestion; //die Komplexität data["temperature"] = 0; //die gewünschte länge der Antwort von ChatGPT //Dieser Wert darf nicht zu groß gewählt werden, //der ESP32 hat nur begrenzten Speicherplatz. data["max_tokens"] = 300; String requestBody; //serialisieren des JSON in einen String serializeJson(data, requestBody); //absenden des Requests https.POST(requestBody);
Auswerten des HTTP Codes
Die Methode “POST” liefert als Rückgabewert einen ganzzahligen Integerwert als HTTP-Code. Wenn es funktioniert hat, d.h. der Service hat die Anfrage angenommen und verarbeitet, dann erhalten wir ein HTTP-200 Code.
Nachfolgend habe ich einen kleinen Auszug von den möglichen HTTP-Codes entnommen.
//absenden des Requests int httpCode = https.POST(requestBody); if (httpCode != 200) { errorMsg = "HTTP-Code: " + String(httpCode); switch (httpCode) { case 401: errorMsg += " Unauthorized"; break; case 403: errorMsg += " Forbidden"; break; case 404: errorMsg += " Not Found"; break; case 405: errorMsg += " Method Not Allowed"; break; case 408: errorMsg += " Timeout Error"; break; case 500: errorMsg += " Internal Server Error"; break; case 501: errorMsg += " Not Implemented"; break; case 502: errorMsg += " Bad Gateway"; break; case 504: errorMsg += " Gateway Timeout"; break; default: errorMsg += ""; } }
Eine Fehlermeldung vom System wird auf der Seite in einem kleinen, roten Fenster angezeigt. In diesem Fall habe ich den API Key falsch eingegeben.
Auswerten der Antwort von ChatGPT
In jedem Fall (Fehler oder Erfolg) erhalten wir eine Antwort im JSON Format vom System.
Diese Antwort engl. Response müssen wir nun deserialisieren und in einen String ablegen.
Wenn der empfange, JSON-String zu groß für die Verarbeitung auf dem Mikrocontroller ist, erhalten wir eine Fehlermeldung.
String response = https.getString(); if (response.length() > 0) { StaticJsonDocument<1024> doc; DeserializationError error = deserializeJson(doc, response); if (error) { errorMsg = error.f_str(); Serial.print("error: "); Serial.println(errorMsg); return; } ... }
Wenn die Deserializierung erfolgreich war, dann prüfen wir, ob wir einen HTTP-Code größer als 200 erhalten haben (also einen Fehler). Dann lesen wir die Fehlermeldung aus dem JSON, andernfalls lesen wir die Antwort und schreiben diese in den jeweiligen Platzhalter auf die Webseite.
if (httpCode > 200) { errorMsg += "<br/>"; String errorMessage = doc["error"]["message"]; errorMsg += errorMessage; htmlpage.replace("%resultTxt%", ""); } else { String chatGPTAnswer = doc["choices"][0]["text"]; Serial.print("answer: "); Serial.println(chatGPTAnswer); htmlpage.replace("%resultTxt%", chatGPTAnswer); }
Zum Schluss befüllen wir noch das Feld mit der eventuellen Fehlermeldung und senden die modifizierte Webseite ab.
htmlpage.replace("%errorMsg%", errorMsg); String errorMsgStyle = " style='display:none;' "; if (errorMsg.length() > 0) { errorMsgStyle = " style='display:block;' "; } htmlpage.replace("%errorMsgStyle%", errorMsgStyle); server.send(200, "text/html", htmlpage);
Kompletter Code zum Download als ZIP-Datei
Den kompletten Code als ZIP-Datei biete ich dir hier als Download an.
Externe Ressourcen für die Webseite Stylesheets, & JavaScript.