#include <Arduino.h>
#include <LiquidCrystal_I2C.h>

// LCD-Codes Aufzählung 
const unsigned char myue=0x01;
const unsigned char EURO=0x02; 
const unsigned char GRAD=0xDF; 
const unsigned char ae=0xE1; 
const unsigned char sz=0xE2; 
const unsigned char oe=0xEF; 
const unsigned char ue=0xF5;

//
// Forwarddeklaration der verwendeten Funktionen
//
void print_lcd(const unsigned char*);
unsigned char sonderzeichen(const unsigned char);

// 
// Bitmuster für eigene Zeichen 5x8 Bit Matrix (5 Spalten, 8 Zeilen)
//
// Version mit PROGMEM. Funktioniert in dieser WokWi Simulation nicht aber in RL mit 
// der Version LiquidCrystal_I2C 1.1.4
//
//const char uuml[8] PROGMEM =  { 0x0A, 0x00, 0x11, 0x11, 0x11, 0x13, 0x0D, 0x00 }; // Umlaut ü
//const char euro[8] PROGMEM =  { 0x06, 0x09, 0x1C, 0x08, 0x1C, 0x09, 0x06, 0x00 }; // Euro Zeichen

// Version ohne PROGMEM
unsigned char uuml[8] =  { 0x0A, 0x00, 0x11, 0x11, 0x11, 0x13, 0x0D, 0x00 }; // Umlaut ü
unsigned char euro[8] =  { 0x06, 0x09, 0x1C, 0x08, 0x1C, 0x09, 0x06, 0x00 }; // Euro Zeichen

// String direkt mit den zum LCD passenden ASCII-Codes definieren.
const unsigned char text[] = {ae,oe,ue,myue,sz,'\0'};

// Folgender String "wort" wird automatisch auf die für das Display passenden Zeichen 
// umgewandelt. Dafür wird eine "Umsetzfunktion" sonderzeichen() und eine Funktion 
// für die Zeichenausgabe print_lcd() verwendet.

// Achtung: 
// Werden C-Strings wie folgt definiert, so werden nur die Zeichen mit einem 
// ASCII Wert < 128 mit einem Byte gespeichert. Alle anderen Zeichen werden 
// im UTF-8 Code, der bis zu 4Byte lang sein kann, vorgehalten!
//
// Die in diesem Beispiel verwendeten Sonderzeichen werden als 
// zwei Byte UTF-8 Zeichen gespeichert. Das Euro-Zeichen hat sogar einen 
// drei Byte UTF-8 Code (0xE2 0x82 0xAC).
//
// Strings sind demzufolge also länger als es im Programmcode den Anschein hat!!!
//
unsigned char wort[]  = "Bödläülß 6€ 42°C";  // <- Nicht 17 sondern 24 Byte inkl. Stringende (\0)!

LiquidCrystal_I2C lcd(0x27,16,2);   // LCD I2C Adresse 0x27 für 16 Zeichen und 2 Zeilen Darstellung
//LiquidCrystal_I2C lcd(0x3F,20,4);   // LCD I2C Adresse 0x27 für 20 Zeichen und 4 Zeilen Darstellung
void setup()
{
  Serial.begin(115200);
  Serial.print("Stringlänge von \"wort\" = ");
  Serial.print(strlen((char*)wort));
  Serial.println(" Byte + 1 Byte fuer Stringterminierung (\\0)");

  lcd.init();              
  lcd.backlight();

  lcd.createChar(myue, uuml);          // Umlaut in den Displayspeicher schreiben
  lcd.createChar(EURO, euro);
  
  lcd.setCursor(5,0);
  lcd.print((char*)text);              // Bei Liquid Crystal I2C Bibliothek cast auf (char*) notwendig
  lcd.setCursor(0,1);
  print_lcd(wort);
}

void loop()
{
}

//
// Gib einen String auf dem Display aus.
//
void print_lcd(const unsigned char* text) {
  while (*text) {
    // Wenn sonderzeichen() eine 0 zurückliefert ist ein UTF-8 Zeichen auszugeben. 
    unsigned char zeichen = sonderzeichen(*text++);
    if (zeichen) {
      lcd.write(zeichen);
    }
  }
}

//
// Wandle UTF-8 Sonderzeichen in ASCII Codes für das LCD-Display um.
// Die Codes 0x01 und 0x02 sind die selbst definierte Zeichen uuml und euro.
// 
unsigned char sonderzeichen(const unsigned char ascii) {
  static unsigned char istUTF8;           // Speichert das "vorletze" Zeichen.
  unsigned char zeichen = '\0';
  if (ascii < 0x7F) {
    istUTF8 = '\0';
    return ascii;                         // Return wenn KEIN UTF-8 Zeichen
  } 

  // Wenn UTF-8 Zeichen
  switch (istUTF8) { 
    case 0xC2:
      switch (ascii) {
        case 0xB0: zeichen = GRAD; break; // ° Grad UTF-8 = 0xC3 0XB0
        default: zeichen = ascii;  break;
      }
      break;
    case 0xC3: 
      switch (ascii) {
        case 0xA4: zeichen = ae; break;   // ä UTF-8 = 0xC3 0xA4
        case 0xB6: zeichen = oe; break;   // ö UTF-8 = 0xC3 0xB6
        case 0xBC: zeichen = myue; break; // ü UTF-8 = 0xC3 0xBC
        case 0x9F: zeichen = sz; break;   // ß UTF-8 = 0xC3 0x9F
        default:   zeichen = ascii | 0xC0; break;
      }
      break;
    // Spezialfall: Euro Zeichen (3 Byte Code -> 0xE2 0x82 0xAC)
    case 0xE2: if (ascii == 0x82) zeichen = '\0'; break; 
    case 0x82: if (ascii == 0xAC) zeichen = EURO; break;
    default: break;
  }
  istUTF8 = ascii;
  return zeichen;
}