555 lines
No EOL
16 KiB
Text
555 lines
No EOL
16 KiB
Text
#include <WiFi.h>
|
|
#include <WiFiClientSecure.h>
|
|
#include <HTTPClient.h>
|
|
#include <ArduinoJson.h>
|
|
|
|
#include "utf8coder.hpp"
|
|
#include "ddr2unicode.h"
|
|
#include "unicode2ddr.h"
|
|
|
|
// hardware specific includes
|
|
#include <Adafruit_NeoPixel.h>
|
|
//#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<char16_t> 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<uint8_t> 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 ##########################################################################
|
|
}
|
|
} |