commit 97d64e3bfe98f544c79411b6e04f0470110be1c7 Author: kaki Date: Sat Jan 18 16:20:15 2025 +0100 2025_ErikaCode hinzugefügt diff --git a/2025_ErikaCode b/2025_ErikaCode new file mode 100644 index 0000000..b5f52fd --- /dev/null +++ b/2025_ErikaCode @@ -0,0 +1,555 @@ +#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 ########################################################################## + } +} \ No newline at end of file