// 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};

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



void setup(void) {
  u8g2.setBusClock(800000);
  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

  // 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
}