// simple project using Arduino UNO and 16x2 character display to display smooth gauge, 
// created by upir, 2022
// youtube channel: https://www.youtube.com/upir_upir
// FULL TUTORIAL: https://youtu.be/ZzIGHiHObYw 
// GAUGE IN 11 MINUTES TUTORIAL: https://youtu.be/upE17NHrdPc
// Links related to this project:
// Arduino UNO - https://s.click.aliexpress.com/e/_AXDw1h
// Arduino breadboard prototyping shield - https://s.click.aliexpress.com/e/_ApbCwx
// 16x2 displays with IIC - https://s.click.aliexpress.com/e/_9Hl3JV
// 16x2 display with RGB backlight - https://s.click.aliexpress.com/e/_9wgpeb
// original sketch from YWROBOT - https://wokwi.com/arduino/libraries/LiquidCrystal_I2C/HelloWorld
// character creator - https://tusindfryd.github.io/screenduino/
// another character creator - https://maxpromer.github.io/LCD-Character-Creator/
// sprintf explanation - https://www.programmingelectronics.com/sprintf-arduino/
// custom characters simplest project - https://wokwi.com/projects/294395602645549578
// Arduino I2C scanner - https://playground.arduino.cc/Main/I2cScanner/
// 16x2 available characters - https://docs.wokwi.com/parts/wokwi-lcd1602#font
// Bitwise Operators in GIFs - https://blog.wokwi.com/bitwise-operators-in-gifs/
// Bitwise operators Arduino documentation - https://www.arduino.cc/reference/en/language/structure/bitwise-operators/bitshiftleft/
//Lcd Display
#include <LiquidCrystal_I2C.h> // if you don´t have I2C version of the display, use LiquidCrystal.h library instead
#include <IRremote.h>
#define PIN_RECEIVER 4   // Signal Pin of IR receiver
#define led_rot 12
#define led_buildin 13
#define Def_LcdChar 20
#define Def_LcdLine 4 
#define Def_LcdAddr  0x27
char zeil_str[4][50] = {"0zeile0", "1zeile1","2zeile2","3zeile3"};
bool str_change=false;
uint16_t menue_state=5;
uint16_t menue_state_old=0;
IRrecv receiver(PIN_RECEIVER);
LiquidCrystal_I2C lcd(Def_LcdAddr,Def_LcdChar,Def_LcdLine); // set the LCD address to 0x27 for a 16 chars and 2 line display
void menue (){
  if(menue_state_old != menue_state){
     // Takes command based on IR code received
  switch (menue_state) {
    case 5:   //welcome
      sprintf(zeil_str[0], "      Welcome");
      sprintf(zeil_str[1], "Press Menu for info");
      sprintf(zeil_str[2], "");
      sprintf(zeil_str[3], "");     
      str_change=true;      
      break;
    case 7:   //menue
      sprintf(zeil_str[0], " 1. Slider");
      sprintf(zeil_str[1], " 2. Goug cpu/ Pow");
      sprintf(zeil_str[2], " 3. Display IR");
      sprintf(zeil_str[3], " 4. 4x Text");
      str_change=true;
      break;
    case 10:   //welcome
      sprintf(zeil_str[0], "   Analog Read");
      sprintf(zeil_str[1], "");
      sprintf(zeil_str[2], "");
      sprintf(zeil_str[3], "");     
      str_change=true;
      break;
    case 20:
      sprintf(zeil_str[0], "");
      sprintf(zeil_str[1], "");
      sprintf(zeil_str[2], "");
      sprintf(zeil_str[3], "");     
      str_change=true; 
      str_change=true; 
      break;
    case 30:
      sprintf(zeil_str[0], "Display IR");
      sprintf(zeil_str[1], "");
      sprintf(zeil_str[2], "");
      sprintf(zeil_str[3], "");     
      str_change=true; 
      break;
    case 40:
//      lcdPrint("PLUS");
      sprintf(zeil_str[0], "Some Test Z0");
      sprintf(zeil_str[1], "Some Test Z1");
      sprintf(zeil_str[2], "Some Test Z2");
      sprintf(zeil_str[3], "Some Test Z3");     
      str_change=true; 
      break;
    case 150:
      sprintf(zeil_str[0], " 1. Slider");
      sprintf(zeil_str[1], " 2. Goug cpu/ Pow");
      sprintf(zeil_str[2], " 3. Display IR");
      sprintf(zeil_str[3], " 4. 4x Text");
      str_change=true;
//      break;
//    case 60:
//      lcdPrint("PREV.");
//      break;
    default:
      sprintf(zeil_str[0], " Switch");
      sprintf(zeil_str[1], " case");
      sprintf(zeil_str[2], " error");
      sprintf(zeil_str[3], " in menue");
      str_change=true;
  }
  menue_state_old = menue_state;
  }
}
void lcd_out(){
  if(str_change){
    char buffer[21]; // helper buffer to store C-style strings (generated with sprintf function)
    int buff_size=sizeof(buffer);
    for(int i=0;i<4;i++){
       int len = strlen(zeil_str[i]);
       if(len >buff_size){
         lcd.setCursor(0,i); // move the cursor to the next line
         sprintf(buffer, "Error overload"); // set a string as CPU: XX%, with the number always taking at least 3 character
       }
       else{
         sprintf(buffer, "%s",zeil_str[i]); // set a string as CPU: XX%, with the number always taking at least 3 character
       }
       len = strlen(buffer);
       for(int i=len;i<buff_size;i++){
         buffer[i] = ' ';
       }
       buffer[buff_size-1]='\0';
       lcd.setCursor(0,i); // move the cursor to the next line
       lcd.print(buffer); // print the string on the display

    }
    digitalWrite(led_rot, ! digitalRead(led_rot));
    str_change=false;
  }
//  int len1 = strlen(buffer);
//  sprintf(buffer, "buf: %2d buf1: %2d",len, len1); // set a string as CPU: XX%, with the number always taking at least 3 character
//  lcd.setCursor(0,3); // move the cursor to the next line
//  lcd.print(buffer); // print the string on the display
}

void lcdPrint(char* text)
{   if(menue_state==30){
       sprintf(zeil_str[2], "%s code: %d",text, receiver.decodedIRData.command); // set a string as CPU: XX%, with the number always taking at least 3 character
       str_change=true;
    }
}


void lcdPrint1(char* text)
{
  char buffer[21]; // helper buffer to store C-style strings (generated with sprintf function)
  int buff_size=sizeof(buffer);
  int len = strlen(text);
  if((len >buff_size-10)||(receiver.decodedIRData.command>999)){
    lcd.setCursor(0,2); // move the cursor to the next line
    sprintf(buffer, "Error overload"); // set a string as CPU: XX%, with the number always taking at least 3 character
  }
  else{
    sprintf(buffer, "%s code: %d",text, receiver.decodedIRData.command); // set a string as CPU: XX%, with the number always taking at least 3 character
  }
  len = strlen(buffer);
  for(int i=len;i<buff_size;i++){
    buffer[i] = ' ';
  }
  buffer[buff_size-1]='\0';
  lcd.setCursor(0,2); // move the cursor to the next line
  lcd.print(buffer); // print the string on the display
  int len1 = strlen(buffer);
  sprintf(buffer, "buf: %2d buf1: %2d",len, len1); // set a string as CPU: XX%, with the number always taking at least 3 character
  lcd.setCursor(0,3); // move the cursor to the next line
  lcd.print(buffer); // print the string on the display
}
void translateIR()
{
  // Takes command based on IR code received
  switch (receiver.decodedIRData.command) {
    case 162:
      lcdPrint("POWERxxx10");
      break;
    case 226:
      //lcdPrint("MENU");
      menue_state=7;
      break;
    case 34:
      lcdPrint("TEST");
      break;
    case 2:
      lcdPrint("PLUS");
      break;
    case 194:
//      lcdPrint("BACK");
      menue_state=5;
      break;
    case 224:
      lcdPrint("PREV.");
      break;
    case 168:
      lcdPrint("PLAY");
      break;
    case 144:
      lcdPrint("NEXT");
      break;
    case 104:
      lcdPrint("num: 0");
      break;
    case 152:
      lcdPrint("MINUS");
      break;
    case 176:
      lcdPrint("key: C");
      break;
    case 48:  //1
      menue_state=10;
      break;
    case 24:
      //lcdPrint("num: 2");
      menue_state=20;
      break;
    case 122:
      //lcdPrint("num: 3");
      menue_state=30;
      break;
    case 16:
      //lcdPrint("num: 4");
      menue_state=40;
      break;
    case 56:
      lcdPrint("num: 5");
      break;
    case 90:
      lcdPrint("num: 6");
      break;
    case 66:
      lcdPrint("num: 7");
      break;
    case 74:
      lcdPrint("num: 8");
      break;
    case 82:
      lcdPrint("num: 9");
      break;
    default:
      sprintf(zeil_str[0], " Error IR");
      sprintf(zeil_str[1], " code");
      sprintf(zeil_str[2], " %d",receiver.decodedIRData.command);
      sprintf(zeil_str[3], "  other button");
      str_change=true;
  }
}

//////////////////////////////////////
///  Timer Handling--Global-----------
  bool DiagRuntime=true;
  bool RunTimeError=false;
  bool ton10000=0;  //10 sekunden Blinker
  bool ton5000=0;   //sekunden Blinker 1s
  bool ton1000=0;   //sekunden Blinker 1s
  bool ton100=0;    //sekunden Blinker 1/100s
  bool ton50=0;    //sekunden Blinker 1/10s
  bool fton10000=0;  //Flanke fuer Blinker
  bool fton5000=0;  //Flanke fuer Blinker
  bool fton1000=0;  //Flanke fuer Blinker
  bool fton100=0;   //Flanke fuer Blinker
  bool fton50=0;   //Flanke fuer Blinker
  unsigned long prev10000Millis = 0;        // Zaehler feur blinken 
  unsigned long prev5000Millis = 0;        // Zaehler feur blinken 
  unsigned long prev1000Millis = 0;        // Zaehler feur blinken 
  unsigned long prev100Millis = 0;         // Zaehler feur blinken 
  unsigned long prev50Millis = 0;         // Zaehler feur blinken 
  unsigned long UptimeSec = 0;        // Zaehler für Uhr
  unsigned int MillisOverrun =0;
  unsigned int WatchMoreThen50Millis =0;
  unsigned int WatchMoreThen100Millis =0;
  unsigned int LoopTime =0;
  unsigned long currentMillis = millis();
  String GS_StartTime = "";
  bool   GB_StartTime=false;
  unsigned long GI_TimeToStart=0;

int Timer(bool Begin);       //Zeitensteuerung
//---END Timer-------------------------------------------------------------------
//////////////////////////////////////
///  Timer Handling-------------
//////////////////////////////////////
////////////////////////////////////////////
int Timer(bool Begin) {
    if(Begin){
      currentMillis = millis();
    
      if(currentMillis < prev50Millis){
        prev10000Millis = 0; prev5000Millis = 0; prev1000Millis = 0; prev100Millis = 0; prev50Millis = 0; MillisOverrun ++;// Zaehler Nullen da milis ueberlauf 
      }
      UptimeSec = (MillisOverrun * 4294967) + (currentMillis / 1000);
      if((currentMillis >= prev50Millis + 50) /* &! (fton100 || fton1000 || fton5000 || fton10000)*/){
          prev50Millis = currentMillis;
          if(ton50==0){
             ton50=1;fton50=1;
          }else{ton50=0;fton50=1;}
      }
      else if((currentMillis >= prev100Millis + 100)/* &! (fton50 || fton1000 || fton5000 || fton10000)*/) {
          prev100Millis = currentMillis;
          if(ton100==0){
             ton100=1;fton100=1;
          }else{ton100=0;fton100=1;}
      }
      else if((currentMillis >= prev1000Millis + 1000)/* &! (fton50 || fton100 || fton5000 || fton10000)*/) {
          prev1000Millis = currentMillis;
              if(ton1000==0){ton1000=1;fton1000=1;
              }else{ ton1000=0;fton1000=1;}
      }       
      else if((currentMillis >= prev5000Millis + 5000)/* &! (fton50 || fton100 || fton1000 || fton10000)*/) {
          prev5000Millis = currentMillis;
              if(ton5000==0){ton5000=1;fton5000=1;
              }else{ ton5000=0;fton5000=1;}
      }       
      else if((currentMillis >= prev10000Millis + 10000)/* &! (fton50 || fton100 || fton1000 || fton5000)*/) {
          prev10000Millis = currentMillis;
              if(ton10000==0){ton10000=1;fton10000=1;
              }else{ ton10000=0;fton10000=1;}
      }
      return 0;
    }
    else{
        if(DiagRuntime){
            if(millis()>currentMillis +50)
                WatchMoreThen50Millis ++;
            if(millis()>currentMillis +100)
                WatchMoreThen100Millis ++;
            LoopTime=millis()- currentMillis;
        }
        fton10000=0; fton5000=0; fton1000=0; fton100=0; fton50=0;      
        if(millis()> currentMillis +100)
          return -1;
        else
          return 0;
    }
}
///////////////////////////////////////////////////
/// --END--- Timer Handling-------------
///////////////////////////////////////////////////7
///////////-----Zeit----------------

//LiquidCrystal_I2C lcd(0x3f,16,2); // set the LCD address to 0x3f for a 16 chars and 2 line display

   byte gauge_empty[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B11111}; // empty middle piece
   byte gauge_fill_1[8] = {B11111, B10000, B10000, B10000, B10000, B10000, B10000, B11111}; // filled gauge - 1 column
   byte gauge_fill_2[8] = {B11111, B11000, B11000, B11000, B11000, B11000, B11000, B11111}; // filled gauge - 2 columns
   byte gauge_fill_3[8] = {B11111, B11100, B11100, B11100, B11100, B11100, B11100, B11111}; // filled gauge - 3 columns
   byte gauge_fill_4[8] = {B11111, B11110, B11110, B11110, B11110, B11110, B11110, B11111}; // filled gauge - 4 columns
   byte gauge_fill_5[8] = {B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111}; // filled gauge - 5 columns
//   byte gauge_left[8] = {B11111, B10000, B10000, B10000, B10000, B10000, B10000, B11111}; // left part of gauge - empty
   byte gauge_left[8] = {B01111, B11111, B11111, B11111, B11111, B11111, B11111, B01111}; // left part of gauge - empty
   byte gauge_right[8] = {B11110, B00001, B00001, B00001, B00001, B00001, B00001, B11110}; // right part of gauge - empty
   byte gauge_mask_left[8] = {B01111, B11111, B11111, B11111, B11111, B11111, B11111, B01111}; // mask for rounded corners for leftmost character
   byte gauge_mask_right[8] = {B11110, B11111, B11111, B11111, B11111, B11111, B11111, B11110}; // mask for rounded corners for rightmost character
//   byte gauge_left_dynamic[8]; // left part of gauge dynamic - will be set in the loop function
//   byte gauge_right_dynamic[8]; // right part of gauge dynamic - will be set in the loop function

byte warning_icon[8] = {B00100, B00100, B01110, B01010, B11011, B11111, B11011, B11111}; // warning icon - just because we still have one custom character left


void setup()
{
   pinMode(led_rot, OUTPUT);
   pinMode(led_buildin, OUTPUT);
   receiver.enableIRIn(); // Start the receiver
   lcd.init(); // initialize the 16x2 lcd module
   lcd.createChar(7, gauge_empty); // middle empty gauge
   lcd.createChar(1, gauge_fill_1); // filled gauge - 1 column
   lcd.createChar(2, gauge_fill_2); // filled gauge - 2 columns
   lcd.createChar(3, gauge_fill_3); // filled gauge - 3 columns
   lcd.createChar(4, gauge_fill_4); // filled gauge - 4 columns 
   lcd.createChar(5, gauge_left); // create custom character for the left part of the gauge
   lcd.createChar(6, gauge_right); // create custom character for the right part of the gauge
   lcd.createChar(0, warning_icon); // warning icon - just because we have one more custom character that we could use  
   lcd.backlight(); // enable backlight for the LCD module
   
}
// define custom characters/arrays - every character is 5x8 "pixels"


void calc_gauge(int x_pos, int y_pos, int size, int val_gauge){

   const int gauge_size_chars = size; // width of the gauge in number of characters
   char gauge_string[gauge_size_chars+1]; // string that will include all the gauge character to be printed
   float units_per_pixel = (gauge_size_chars*5.0)/100.0; // every character is 5px wide, we want to count from 0-100
   int value_in_pixels = round(val_gauge * units_per_pixel); // cpu_gauge value converted to pixel width
   int move_offset = 0; // used to shift bits for the custom characters
   int tip_position = 0; // 0= not set, 1=tip in first char, 2=tip in middle, 3=tip in last char
      
      if (value_in_pixels < 5) {tip_position = 1;} // tip is inside the first character
      else if (value_in_pixels > gauge_size_chars*5.0-5) {tip_position = 3;} // tip is inside the last character
      else {tip_position = 2;} // tip is somewhere in the middle
      move_offset = 4 - ((value_in_pixels-1) % 5); // value for offseting the pixels for the smooth filling
// Auskommentiert da es nur geht wenn ein gouge pro display verwendet wird, da sonst falsche zeichen ausgegeben werden.
//      for (int i=0; i<8; i++) { // dynamically create left part of the gauge
//         if (tip_position == 1) {gauge_left_dynamic[i] = (gauge_fill_5[i] << move_offset) | gauge_left[i];} // tip on the first character
//         else {gauge_left_dynamic[i] = gauge_fill_5[i];} // tip not on the first character
//         gauge_left_dynamic[i] = gauge_left_dynamic[i] & gauge_mask_left[i]; // apply mask for rounded corners
//      }
//      for (int i=0; i<8; i++) { // dynamically create right part of the gauge
//         if (tip_position == 3) {gauge_right_dynamic[i] = (gauge_fill_5[i] << move_offset) | gauge_right[i];} // tip on the last character
//         else {gauge_right_dynamic[i] = gauge_right[i];} // tip not on the last character
//         gauge_right_dynamic[i] = gauge_right_dynamic[i] & gauge_mask_right[i]; // apply mask for rounded corners
//      } 
      for (int i=0; i<gauge_size_chars; i++) { // set all the characters for the gauge
         if (i==0) {gauge_string[i] = byte(5);} // first character = custom left piece
         else if (i==gauge_size_chars-1) {
           if(tip_position==3){
             gauge_string[i] =  byte(0);}
            else{gauge_string[i] =  byte(6);}
         } 
         else { // character in the middle, could be empty, tip or fill
            if (value_in_pixels <= i*5) {gauge_string[i] = byte(7);} // empty character
            else if (value_in_pixels > i*5 && value_in_pixels < (i+1)*5) {gauge_string[i] = byte(5-move_offset);} // tip
            else {gauge_string[i] = byte(255);} // filled character
         }
        

      }
     gauge_string[gauge_size_chars] = '\0';
//   lcd.createChar(5, gauge_left_dynamic); // create custom character for the left part of the gauge
//   lcd.createChar(6, gauge_right_dynamic); // create custom character for the right part of the gauge
   lcd.setCursor(x_pos,y_pos); 
   //delay(100); // wait for a while - 100ms = update the screen 10x in a second
   lcd.print(gauge_string); // display the gauge
   if(tip_position==3){lcd.write(byte(0));}    // print warning character auf letzten zeichen     
}


void loop()
{ 
  Timer(true); 
  static int cpu_gauge = 0; // value for the CPU gauge
  static bool cpu_gauge_up = true;
  char buffer[21]; // helper buffer to store C-style strings (generated with sprintf function)
  // Checks received an IR signal
  if (receiver.decode()) {
    translateIR();
    receiver.resume();  // Receive the next value
  }
  menue ();
  if(menue_state==10){
    lcd.setCursor(0,1); // move the cursor to the next line
    sprintf(buffer, "Read :%4d", analogRead(A1) ); // set a string as CPU: XX%, with the number always taking at least 3 character
    lcd.print(buffer); // print the string on the display
    calc_gauge(10,1,10,analogRead(A1)/11);
  }
  if(fton100){
//    digitalWrite(led_rot, ! digitalRead(led_rot));
    if(menue_state==20){
      if (cpu_gauge_up){
         cpu_gauge = cpu_gauge +1;
         if (cpu_gauge > 99) {cpu_gauge_up = false;}
      }
      else{
         cpu_gauge = cpu_gauge -1;
         if (cpu_gauge < 1) {cpu_gauge_up = true;}
      }
      calc_gauge(10,0,10,cpu_gauge);
      lcd.setCursor(0,0); // move cursor to top left
      sprintf(buffer, "CPU:%3d%% ", cpu_gauge); // set a string as CPU: XX%, with the number always taking at least 3 character
      lcd.print(buffer); // print the string on the display
      int rand_no = random(0, 99);
      calc_gauge(10,1,10,rand_no);
      lcd.setCursor(0,1); // move the cursor to the next line
      sprintf(buffer, "-WP:%3dW ", rand_no); // set a string as CPU: XX%, with the number always taking at least 3 character
      lcd.print(buffer); // print the string on the display
    }
  
  }
  lcd_out();
  Timer(false);
}