#include #include #include #include #include "utf8coder.hpp" #include "ddr2unicode.h" #include "unicode2ddr.h" // hardware specific includes #include //#include "esp32wroom_board.h" #include "M5StampS3_board.h" // in the EFS26 connector #include "neopixel.hpp" #define CHATAI 1 #define KOBOLD 2 #define OPENAI 3 #define HOST CHATAI #include "aimodelconfig.h" #include "credentials.h" #define PC_BAUD 115200 class ErikaSchreibmaschine { private: int _txPin, _rxPin, _rtsPin, _baud; bool _wait = false; #ifdef SWSERIAL EspSoftwareSerial::UART serconn; #else #define serconn HWSERIAL #endif size_t num_spalten; size_t spalte; public: ErikaSchreibmaschine(int rxPin, int txPin, int rtsPin, int baud) #ifdef SWSERIAL :spalte(0), num_spalten(75), serconn(_rxPin, _txPin) // RX, TX #else :spalte(0), num_spalten(75) // end of line "beep" here #endif { _rtsPin=rtsPin; _txPin=txPin; _rxPin=rxPin; _baud=baud; } bool Init() { pinMode(_rtsPin, INPUT_PULLUP); #ifdef SWSERIAL serconn.begin(_baud, SWSERIAL_8N1, _rxPin, _txPin, false); #else serconn.begin(_baud); #endif //serconn.setTimeout(0); //serconn.begin(_baud); //return ClearBuf(); return true; } bool ClearBuf() { // alles weglesen - puffer leeren bool r =false; while (serconn.read() > 0) r=true; if(r) { Serial.println("Tastaturpuffer geleert."); } return r; } bool CharAvailable() { return serconn.available(); } char16_t ReadC() { if(CharAvailable()) { int znummer = serconn.read(); Serial.print(znummer,16); char zeichen = ddr2uni[znummer]; Serial.print(zeichen,16); return zeichen; } return 0; } // Liest eine ganze Zeile bis \n und liefert das als utf-8 kodierten string String ReadLine() { String eingabestring; std::vector uniZeichenfolge; while(true) { if(CharAvailable()) { int ddrZeichen = serconn.read(); // breaker for while loop if(ddrZeichen == 119) // newline { break; } switch (ddrZeichen) { case 114: // Backtab { if(uniZeichenfolge.size() > 0) //this does not work as expected { uniZeichenfolge.pop_back(); } break; } case 117: { break; } // Einzug ignorieren case 118: { break; } // Papier zurück ignorieren case 119: { break; } // Newline hier ignorieren case 121: { break; } // Tab ignorieren default: { // Druckbare Zeichen anhängen if (ddrZeichen > 127) {} // nothing - kann nicht sein // it can be ;-) it can be a command code! else // gutes Zeichen auf der Erikatastatur { char16_t uniZeichen = ddr2uni[ddrZeichen]; // a bit dangerous before where it was (8 bit in for 7 bits lookup) uniZeichenfolge.push_back(uniZeichen); } break; } }; } else { // däumchen drehen delay(100); } } // Tastatur in unicode eingaben sind im vector // codepoints in utf-8 bytefolge verwandeln. for (size_t i = 0; i < uniZeichenfolge.size(); i++) { std::vector utfbytes = numberToUtf8(uniZeichenfolge[i]); String s(utfbytes.data(), utfbytes.size()); eingabestring.concat(s); } return eingabestring; } /*void WriteC(char zeichen) //char16_t { Serial.printf("WriteC: %c\n", zeichen); if(zeichen=='@') { serconn.write((char)uni2ddr['O']); serconn.write(114); // Backtab serconn.write((char)uni2ddr['a']); spalte++; return; } else { if (zeichen < 128) { serconn.write(isolatin12ddr[zeichen]); spalte++; } else { Serial.print(zeichen); if (zeichen == 0xc39f ) {serconn.write(0x47); }//ß else if (zeichen == 0xc3a4 ) {serconn.write(0x65); }//ä else if (zeichen == 0xc3b6 ) {serconn.write(0x66); }//ö else if (zeichen == 0xc3bc ) {serconn.write(0x67); }//ü else if (zeichen == 0xc384 ) {serconn.write(0x3F); }//Ä else if (zeichen == 0xc396 ) {serconn.write(0x3C); }//Ö else if (zeichen == 0xc39c ) {serconn.write(0x3A); }//Ü else if (zeichen == 0xc2b0 ) {serconn.write(0x39); }//° else if (zeichen == 0xc2a7 ) {serconn.write(0x3D); }//§ else {serconn.write(27);} } spalte++; } //char z = (char)uni2ddr[c]; //serconn.write(z); //spalte++; }*/ void Write(String wstring) { int i = 0; int length = wstring.length(); SetDuplexMode(); while (i <= length) { if (IsReady()) { int zeichen = wstring[i]; int charsToSpace = wstring.indexOf(' ', i) - i; //how far is the next space away? if (charsToSpace > 0) { if ((spalte + charsToSpace) >= num_spalten) { //next word will not fit on the remaining space of the line NewLine(); } } if (spalte >= num_spalten) { NewLine(); } //TODO: avoid "\n " what would create a space as first character on new line if(zeichen=='@') { serconn.write(uni2ddr['O']); serconn.write(114); // Backtab serconn.write(uni2ddr['a']); } else { if (zeichen < 128) { if (zeichen == '\n') { NewLine(); } else { serconn.write(uni2ddr[zeichen]); } } else { i++; uint8_t zeichen2 = wstring[i];//utf8chars[i]; zeichen = (zeichen<<8) | zeichen2; if (zeichen == 0xc39f ) {serconn.write(0x47); }//ß else if (zeichen == 0xc3a4 ) {serconn.write(0x65); }//ä else if (zeichen == 0xc3b6 ) {serconn.write(0x66); }//ö else if (zeichen == 0xc3bc ) {serconn.write(0x67); }//ü else if (zeichen == 0xc384 ) {serconn.write(0x3F); }//Ä else if (zeichen == 0xc396 ) {serconn.write(0x3C); }//Ö else if (zeichen == 0xc39c ) {serconn.write(0x3A); }//Ü else if (zeichen == 0xc2b0 ) {serconn.write(0x39); }//° else if (zeichen == 0xc2a7 ) {serconn.write(0x3D); }//§ else {serconn.write(27);} // write an asteriks * for any unsupported UTF char } } spalte++; i++; _wait = true; delay(50); // RTS will not change stange instantly } else { delay(10); } } SetSimplexMode(); } void WriteLn(String wstring) { Write(wstring); NewLine(); } void NewLine() { serconn.write(uni2ddr['\n']); //serconn.write(ascii2ddr['\r']); spalte = 0; } // Maschine bereit für nächstes Zeichen bool IsReady() { int rtsState = digitalRead(_rtsPin); if (rtsState == LOW) { neopixelShowReady(); // I know, bad style... } else { neopixelShowBusy(); // I know, bad style... } return rtsState == LOW; } void writeCommand(int commandCode) { if(IsReady()) { serconn.write(commandCode); delay(100); } } /* Trennmode aktivieren (Trennung von Tastatur und Druckwerk) Duplexbetrieb: Alle Tasteninformationen werden nur nach TxD ausgegeben und nur die von RxD kommenden gelangen zum Druck (Korr, CREL und CRL sind nicht wirksam!) */ void SetDuplexMode(){ delay(500); writeCommand(0x91); delay(100); } /* Trennmode aufheben Simplexbetrieb: Alle Tasteninformationen werden gedruckt */ void SetSimplexMode(){ delay(1000); writeCommand(0x92); delay(100); } }; //int max_tokens = 356; // Für längere Antworten erhöhen //char Nachricht_array[6096]; // Bei größeren max_tokens das Nachricht_array auch vergrößern // Instantiiere die Schreibmaschine ErikaSchreibmaschine erika(ERIKA_RX, ERIKA_TX, RTS_PIN, ERIKA_BAUD); void WiFiEvent(WiFiEvent_t event) { Serial.printf("[WiFi-event] event: %d\n", event); switch (event) { case ARDUINO_EVENT_WIFI_READY: Serial.println("WiFi interface ready"); break; case ARDUINO_EVENT_WIFI_SCAN_DONE: Serial.println("Completed scan for access points"); break; case ARDUINO_EVENT_WIFI_STA_START: Serial.println("WiFi client started"); Serial.println("Connected to access point"); neopixel.setPixelColor(0, neopixel.Color(0, 0, 255)); neopixel.show(); break; case ARDUINO_EVENT_WIFI_STA_STOP: Serial.println("WiFi clients stopped"); break; case ARDUINO_EVENT_WIFI_STA_CONNECTED: Serial.println("Connected to access point"); neopixel.setPixelColor(0, neopixel.Color(0, 255, 0)); neopixel.show(); break; case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: Serial.println("Disconnected from WiFi access point"); neopixel.setPixelColor(0, neopixel.Color(255, 0, 0)); neopixel.show(); break; case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: Serial.println("Authentication mode of access point has changed"); break; case ARDUINO_EVENT_WIFI_STA_GOT_IP: Serial.print("Obtained IP address: "); Serial.println(WiFi.localIP()); break; case ARDUINO_EVENT_WIFI_STA_LOST_IP: Serial.println("Lost IP address and IP address is reset to 0"); break; case ARDUINO_EVENT_WIFI_AP_STACONNECTED: Serial.println("Client connected"); break; case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: Serial.println("Client disconnected"); break; case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: Serial.println("Assigned IP address to client"); break; case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: Serial.println("Received probe request"); break; case ARDUINO_EVENT_WIFI_AP_GOT_IP6: Serial.println("AP IPv6 is preferred"); break; case ARDUINO_EVENT_WIFI_STA_GOT_IP6: Serial.println("STA IPv6 is preferred"); break; default: break; } } void setup() { delay(1000); Serial.begin(115200); delay(1000); Serial.println("geht los ..."); neopixelInit(); erika.Init(); // Mit dem WLan verbinden WiFi.mode(WIFI_STA); WiFi.onEvent(WiFiEvent); if (password != "") { WiFi.begin(ssid, password); } else { WiFi.begin(ssid); } WiFi.setAutoReconnect(true); delay(3000); erika.NewLine(); erika.WriteLn(helloMsg); erika.NewLine(); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(500); } } void loop() { // EINGABE ############################################################################### String eingabe= erika.ReadLine(); //waits until \n Serial.println(eingabe); // EINGABE ENDE ########################################################################## // AUSGABE ############################################################################### if (eingabe.length() == 0) { Serial.println("leere Zeile"); } else { // Request Serial.println(); Serial.print("Sende API Request an: "); Serial.println(url); WiFiClientSecure client; client.setInsecure(); // we do not check certificates or checksums JsonDocument jsonaiapi; jsonaiapi["model"] = model; jsonaiapi["messages"][0]["role"] = "system"; jsonaiapi["messages"][0]["content"] = systemPrompt; jsonaiapi["messages"][1]["role"] = "user"; jsonaiapi["messages"][1]["content"] = eingabe; jsonaiapi["temperature"] = 0.7; String request; serializeJson(jsonaiapi, request); //eingabe.replace("\"", "\\\""); // sanitize hyphens before we encode the input in JSON //request = "{\"model\":\"" + model + "\",\"messages\": [{\"role\":\"system\",\"content\":\"You are a helpful assistant\"},{\"role\":\"user\",\"content\":\"" + eingabe + "\"}], \"temperature\":0.7}"; //request = "{\"model\":\"" + model + "\",\"messages\": [{\"role\":\"system\",\"content\":\"" + systemPrompt + "\"},{\"role\":\"user\",\"content\":\"" + eingabe + "\"}], \"temperature\":0.7}"; Serial.print("request: "); Serial.println(request); HTTPClient http; http.setTimeout(60000); //server response might take a while http.addHeader("Content-Type", "application/json; charset=utf-8"); http.addHeader("Authorization", authMode + " " + authToken); http.addHeader("Accept", "application/json"); neopixelShowConnect(); if (http.begin(client, url)) { // HTTPS Serial.print("[HTTPS] POST...\n"); unsigned long millisStart = millis(); int httpCode = http.POST(request); // httpCode will be negative on error if (httpCode > 0) { // HTTP header has been send and Server response header has been handled Serial.printf("[HTTPS] POST... code: %d\n", httpCode); // file found at server if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { unsigned long millisDuration = millis()-millisStart; neopixelShowReady(); String payload = http.getString(); Serial.println(payload); JsonDocument jsondoc; deserializeJson(jsondoc, payload); payload.clear(); Serial.println("geparst: "); http.end(); String antwort = jsondoc["choices"][0]["message"]["content"]; Serial.println(antwort); erika.NewLine(); erika.WriteLn(antwort); WiFiClientSecure clientlog; clientlog.setInsecure(); HTTPClient httplog; httplog.setTimeout(10000); httplog.addHeader("Content-Type", "application/json; charset=utf-8"); //httplog.addHeader("Accept", "application/json; charset=UTF-8"); if (httplog.begin(clientlog, logURL)) { // HTTPS Serial.print("[HTTPS] POST Logging ...\n"); JsonDocument jsonlog; jsonlog["request"] = eingabe; jsonlog["response"] = antwort; jsonlog["systemprompt"] = systemPrompt; jsonlog["model"] = model; jsonlog["duration"] = millisDuration; //String logString = "{\"request\":\"" + eingabe + "\", \"response\":\"" + antwort + "\", \"systemprompt\":\"" + systemPrompt + "\", \"model\":\"" + model + "\", \"duration\":" + millisDuration + "}"; String logString; serializeJson(jsonlog, logString); Serial.println(logString); int httpCodeLog = httplog.POST(logString); // httpCode will be negative on error if (httpCodeLog > 0) { Serial.println("geloggt"); httplog.end(); } else { Serial.printf("[HTTPS] POST Logging... failed, error: %s\n", http.errorToString(httpCodeLog).c_str()); httplog.end(); } } else { Serial.printf("[HTTPS] Unable to connect to log\n"); } } } else { Serial.printf("[HTTPS] POST... failed, error: %s\n", http.errorToString(httpCode).c_str()); http.end(); neopixelShowFail(); delay(1000); } } else { Serial.printf("[HTTPS] Unable to connect\n"); neopixelShowFail(); delay(1000); } Serial.println("Request+response fertig"); erika.WriteLn("======"); erika.NewLine(); // AUSGABE ENDE ########################################################################## } }