// simple project using Arduino UNO 128x64 SSD1306 OLED display to show a charging indicator
// this is meant to run on the Seeed XIAO board, but it also runs on the Arduino UNO
// created by upir, 2022
// youtube channel: https://www.youtube.com/upir_upir

// FULL TUTORIAL: https://youtu.be/4GfPQoIRqW8

// Links from the video:
// Seeed XIAO: https://s.click.aliexpress.com/e/_DnOVFHH
// Seeed XIAO expansion board: https://s.click.aliexpress.com/e/_DmZeeQ3
// Seeed XIAO documentation: https://wiki.seeedstudio.com/XIAO_BLE/
// Arduino UNO: https://s.click.aliexpress.com/e/_AXDw1h
// Arduino breadboard prototyping shield: https://s.click.aliexpress.com/e/_ApbCwx
// 128x64 SSD1306 OLED Display: https://s.click.aliexpress.com/e/_DCKdvnh
// Transparent OLED display: https://s.click.aliexpress.com/e/_Dns6eLz
// XBMP online generator: https://xbm.jazzychad.net/

// Related videos:
// Turbo pressure gauge with Arduino and OLED display - https://youtu.be/JXmw1xOlBdk
// Arduino Car Cluster with OLED Display - https://youtu.be/El5SJelwV_0
// Knob over OLED Display - https://youtu.be/SmbcNx7tbX8
// Arduino + OLED = 3D ? - https://youtu.be/kBAcaA7NAlA


#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); // initalize the display

static const unsigned char bitmap_upir_logo[] PROGMEM = { // upir logo bitmap, created using the XMBP online generator (link above)
 0xa8,0xeb,0xa8,0xa2,0xa8,0x69,0x98,0xa8};


// This is the battery bitmap
static const unsigned char battery_bitmap[] PROGMEM = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
   0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
   0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
   0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
   0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00,
   0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00,
   0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00,
   0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00,
   0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00,
   0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00,
   0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
   0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
   0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0x00, 0x00,
   0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
   0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00 };

const int NUM_PARTICLES = 20; // number of particles - more than 20 particles will not fit into Arduino UNO RAM

float particle_x[NUM_PARTICLES]; // particle X position
float particle_y[NUM_PARTICLES]; // particle Y position
int particle_speed_x[NUM_PARTICLES]; // particle X speed
int particle_speed_y[NUM_PARTICLES]; // particle Y speed
float particle_size[NUM_PARTICLES]; // particle size (radius)
int init_spread = 15; // how much could be new particle away from the center

int time_value = 0; // time value to animate some elements and update the charge value slower

int charge_value = 0; // charging value displayed in the middle of the screen
char charge_value_buffer[10]; // charging integer value converted to C style string

unsigned long millis_time;       // fps
unsigned long millis_time_last;  // fps
int fps;                         // actual FPS value
char fps_buffer[10];             // buffer for converting FPS to string


// This is the area inside the battery bitmap, in pixels
#define CHARGE_AREA_START_X     20
#define CHARGE_AREA_START_Y     18
#define CHARGE_AREA_WIDTH       83
#define CHARGE_AREA_HEIGHT      28

const uint8_t BATTERY_BOX_START_X = 100;
const uint8_t BATTERY_BOX_START_Y = 2;
const uint8_t BATTERY_BOX_WIDTH   = 23;
const uint8_t BATTERY_BOX_HEIGHT  = 6;
const uint8_t BATTERY_CHG_OFFSET  = 5;

// A variable to test our code.
uint8_t percent = 100;

// This is the battery fill mode
// BATTERY_MODE_SOLID = 1 means the battery bitmap will be filled with
// a solid rectangle according to the battery charge.
// BATTERY_MODE_SOLID = 0 means the battery will be filled with a
// rectangle subdivided in 3. If the battery level is 66% or more, it will
// draw 3 rectangles. If it's 33% or more, it will draw 2 rectangles. It will
// draw 1 rectagle if the battery is less than 33%.

#define BATTERY_MODE_SOLID 1

// This is the main function, and the one you will want to transplant to your
// code. Pass the battery charge percent and it will draw the battery with
// the corresponding charge level.
void showBatteryLevel(uint8_t percent)
{
    uint8_t width;

    if (percent > 100) {
        percent = 100;
    }

    //u8g2.clearBuffer();
    //u8g2.drawXBM(0, 0, 128, 64, battery_bitmap);

    if (BATTERY_MODE_SOLID)
    {
        width = (percent * BATTERY_BOX_WIDTH) / 100;
        u8g2.drawBox(BATTERY_BOX_START_X, BATTERY_BOX_START_Y, width, BATTERY_BOX_HEIGHT);
        u8g2.drawBox(126,2,2,5);
        u8g2.drawFrame(BATTERY_BOX_START_X-2,0,27,10);
    } 
    else 
    {
        uint8_t bars;
        
        if (percent >= 66)
        {
            // Show three bars
            bars = 3;
        } else if (percent >= 33)
        {
            // Show two bars
            bars = 2;
        } else if (percent > 0)
        {
            // Show one bar
            bars = 1;
        } else {
            // Show nothing
            bars = 0;
        }

        uint8_t offset = BATTERY_BOX_START_X;
        for (uint8_t i = 0; i < bars; i++)
        {
            u8g2.drawBox(offset, BATTERY_BOX_START_Y, 15, BATTERY_BOX_HEIGHT);
            offset += BATTERY_CHG_OFFSET;
        }
    }

    //u8g2.sendBuffer();
}

/**
*@brief 
*          (BattVoltage-BattMin)/(BattMax - BattMin) * 100 = BatteryPercentage
           Since we know the battery percentage(lets say 50%, for example) and 
           battery bar will be 30 pixels long, we can figure out how many pixels 
           the battery level should be at 50%.
           (batteryPercent*MaxBatteryBar)/100 = 15(battery bars pixels)

          (50*30)/100=15 pixels. After the calculation, we can draw the 
          correct length of the battery bar to the display .
*/

void draw(void) 
{
  // graphic commands to redraw the complete screen should be placed here  
  u8g2.setFont(u8g_font_unifont);
  //u8g2.setFont(u8g_font_osb21);
  //u8g2.drawFrame(99,0,27,10);
  u8g2.drawBox(101,2,23,6);
  //u8g2.drawBox(126,3,2,4);
}


void setup(void) 
{
  u8g2.begin(); // initialize u8g2 drawing
  u8g2.setFont(u8g2_font_chargen_92_tr); // set U8G font

  for (int i=0; i < NUM_PARTICLES; i++) 
  { // initialize all the particles
    particle_x[i] = random(64-init_spread,64+init_spread); // set the position to the center of the screen +- spread
    particle_y[i] = random(32-init_spread,32+init_spread); // set the position to the center of the screen +- spread
    particle_speed_x[i] = random(-5, 6) * 3; // set some random speed for X
    particle_speed_y[i] = random(-5, 6) * 3; // set some random speed for Y
    particle_size[i] = 8; // set a big enough radius
  }
}

void loop(void) 
{

  time_value = time_value+1; // increment the time value

  if (time_value % 10 == 0) { // only perform some calculations every Nth frame
    // increment the charge value
    charge_value = charge_value + 1;
    if (charge_value > 100) {
      charge_value = 0; // set charge value back to 0
    }

    sprintf(charge_value_buffer, "%d%%", charge_value); // convert charge value to string with the % symbol
  }

  
  u8g2.clearBuffer(); // clear U8G2 drawing buffer
  u8g2.setDrawColor(1); // white color

  // Some test code to scan all the battery levels
  showBatteryLevel(percent);
    
  if (percent)
      percent--;
  else
      percent = 100;

  // draw particles --------------

  for (int i=0; i < NUM_PARTICLES; i++) { // go over all the particles
    particle_x[i] = particle_x[i] + particle_speed_x[i]/10.0; // update the X position based on the speed
    particle_y[i] = particle_y[i] + particle_speed_y[i]/10.0; // update the Y position based on the speed
    particle_size[i] = particle_size[i] * 0.95; // make the size slightly smaller

    u8g2.drawDisc(particle_x[i], particle_y[i], particle_size[i]); // draw the particle

    // if the particle is outside the screen or small enough, re-initialize it again
    if ((particle_x[i] > 128) || (particle_x[i] < 0) || (particle_y[i] > 64) || (particle_y[i] < 0) || (particle_size[i] < 0.5)) {
      particle_x[i] = random(64-init_spread,64+init_spread); // set the position to the center of the screen +- spread
      particle_y[i] = random(32-init_spread,32+init_spread); // set the position to the center of the screen +- spread
      particle_speed_x[i] = random(-5, 6) * 3; // set some random speed for X
      particle_speed_y[i] = random(-5, 6) * 3; // set some random speed for Y
      particle_size[i] = 8; // set a big enough radius
    }
  }

  // draw big circle in the middle --------------

  byte radius = round(25.0 + sin(time_value / 10.0)*2.0); // radius for the big circle in the middle
  u8g2.drawDisc(64, 32, radius); // draw the big circle

  // draw upir logo --------------

  u8g2.drawXBMP(128-16, 64-4, 16, 4, bitmap_upir_logo); // draw upir logo in the corner of the screen

  // draw FPS --------------
  u8g2.setFont(u8g2_font_6x10_tf);   // set very small font
  itoa(fps, fps_buffer, 10);         // convert FPS number to string
  u8g2.drawStr(0, 10, fps_buffer);   // draw the FPS number

  // draw big value in the center --------------

  u8g2.setDrawColor(0); // black color
  u8g2.setFont(u8g2_font_chargen_92_tr); // set U8G font
  byte string_width = u8g2.getStrWidth(charge_value_buffer); // calculate the string width
  u8g2.drawStr(64 - (string_width / 2), 40, charge_value_buffer); // draw the charge value string in the middle of the screen

  u8g2.sendBuffer(); // send U8G2 buffer to the display

  // calculate the FPS
  millis_time_last = millis_time;                                  // store last millisecond value
  millis_time = millis();                                          // get millisecond value from the start of the program
  fps = round(1000.0/ (millis_time*1.0-millis_time_last));         // calculate FPS (frames per second) value
}