// Simple project using Arduino UNO with Pimoroni 5x5 RGB LED Matrix Display to display current gear for the manual transmission cars
// however, since the Pimoroni display is not supported on WOKWI, I´m simulating this display with NeoPixel canvas
// please watch all 3 youtube videos to understand all details

// created by upir, 2024
// youtube channel: https://www.youtube.com/upir_upir

// YouTube video: https://youtu.be/84bn_OpuyCQ
// Source files: https://github.com/upiir/pimoroni_5x5rgb_led_matrix_display/

// YouTube video part 1: https://youtu.be/QixtxaAda18
// YouTube video part 2: https://youtu.be/sZZFgSmYJjc
// YouTube video part 3: https://youtu.be/HcP48uCBzDQ

// Links from the video:
// - do you like video? Please consider buying me coffee, thanks! https://www.buymeacoffee.com/upir
// - Bambu Lab A1 mini 3D Printer: https://shareasale.com/r.cfm?b=2420414&u=3422904&m=138211&urllink=&afftrack=
// - MOMO shifter knob: https://s.click.aliexpress.com/e/_DnKeTPb
// - Pimoroni 5x5 RGB LED Matrix Display: https://shop.pimoroni.com/products/5x5-rgb-matrix-breakout?variant=21375941279827
// - Breadboard wires: https://s.click.aliexpress.com/e/_Dkbngin
// - Arduino UNO R3: https://s.click.aliexpress.com/e/_AXDw1h
// - Arduino breadboard prototyping shield: https://s.click.aliexpress.com/e/_DlxEfPX
// - Photopea (online graphics editor like Photoshop): https://www.photopea.com/
// - Car shifter keyring: https://s.click.aliexpress.com/e/_DkjX6eL
// - Image Converter: https://lvgl.io/tools/imageconverter_v9


#include <Wire.h> // wire library is required for IIC/I2C communication
#include <Adafruit_GFX.h> // adafruit GFX is required for the IS31FL3731 chip
#include <Adafruit_IS31FL3731.h> // library for Pimoroni 11x7px LED matrix display (and Adafruit 16x9 LED matrix display)
#include <Adafruit_NeoPixel.h> // library for NeoPixels - only used for testing on WOKWI 

Adafruit_IS31FL3731 ledmatrix = Adafruit_IS31FL3731(); // initialization of the Pimoroni 11x7px display

#define PIN_NEO_PIXEL 6  // Arduino pin that connects to NeoPixel
#define NUM_PIXELS 25 // The number of LEDs (pixels) on NeoPixel

Adafruit_NeoPixel NeoPixel(NUM_PIXELS, PIN_NEO_PIXEL, NEO_GRB + NEO_KHZ800); // set the NeoPixel (canvas) initialization, only used for WOKWI simulation

byte indexes[] = { // indexes of RGB channels of individual RGB LEDs compared to Afafruit 16x9 display
  118, 69, 85, 117, 68, 101, 116, 84, 100, 115, 83, 99, 114, 82, 98,
  132, 19, 35, 133, 20, 36, 134, 21, 37, 112, 80, 96, 113, 81, 97,
  131, 18, 34, 130, 17, 50, 129, 33, 49, 128, 32, 48, 127, 47, 63,
  125, 28, 44, 124, 27, 43, 123, 26, 42, 122, 25, 58, 121, 41, 57,
  126, 29, 45, 15, 95, 111, 8, 89, 105, 9, 90, 106, 10, 91, 107
};

byte canvas_5x5rgb[] = { // 5x5 RGB canvas for drawing stuff - this is the content that will be displayed
  255, 0, 0,    0, 255, 0,    0, 0, 255,    255, 255, 0,     0, 255, 255,
  255, 0, 0,    0, 255, 0,    0, 0, 255,    255, 255, 0,     0, 255, 255,
  255, 0, 0,    0, 255, 0,    0, 0, 255,    255, 255, 0,     0, 255, 255,
  255, 0, 0,    0, 255, 0,    0, 0, 255,    255, 255, 0,     0, 255, 255,
  255, 0, 0,    0, 255, 0,    0, 0, 255,    255, 255, 0,     0, 255, 255
};

byte rainbow_5x5px_map[] = { // rainbox image exported from Photopea
  0x00, 0x00, 0xff, 0xb7, 0x00, 0xff, 0xff, 0x00, 0xbb, 0xff, 0x00, 0x00, 0xff, 0xcb, 0x00,
  0xbe, 0x00, 0xff, 0xff, 0x00, 0xb5, 0xff, 0x00, 0x00, 0xff, 0xd0, 0x00, 0xa9, 0xff, 0x00,
  0xff, 0x00, 0xaf, 0xff, 0x00, 0x00, 0xff, 0xd4, 0x00, 0xa5, 0xff, 0x00, 0x00, 0xff, 0x01,
  0xff, 0x00, 0x00, 0xff, 0xd9, 0x00, 0x9f, 0xff, 0x00, 0x00, 0xff, 0x02, 0x00, 0xff, 0xd6,
  0xff, 0xde, 0x00, 0x99, 0xff, 0x00, 0x00, 0xff, 0x04, 0x00, 0xff, 0xda, 0x00, 0x90, 0xff,
};

byte digit_3_map[] = { // digit 3 image exported from Photopea
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
};





byte digit_1[] = {
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
};


byte digit_2[] = {
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
};


byte digit_3[] = {
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
};

byte digit_4[] = {
  0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 
};


byte digit_5[] = {
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
};


byte digit_N[] = {
  0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 
};

byte digit_R[] = {
  0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 
  0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
};



const unsigned char* gear_bitmaps[] = {
  digit_R,
  digit_N,
  digit_1,
  digit_2,
  digit_3,
  digit_4,
  digit_5
};

int animation_gear = 0;








void setup() {

  NeoPixel.begin();  // initialize the NeoPixel object

  //Serial.begin(9600);
  //Serial.println("ISSI swirl test");

  if (! ledmatrix.begin(0x74)) {
    //Serial.println("IS31 not found");
    //while (1); // commented out for the WOKWI simulation, since the Pimoroni display is not connected/supported on WOKWI
  }
  //Serial.println("IS31 found!");
}

float animation = 0.0; // rainbow animation 
float animation_inc = 0.6; // rainbow animation increment

float anim_fade = 0.0;
float anim_fade_inc = 10.0;

void loop() { // main loop

  animation = animation + animation_inc; // increase the rainbow animation

  anim_fade = anim_fade + anim_fade_inc;
  if ((anim_fade > 400) && (anim_fade_inc > 0)) {
    anim_fade = 400;
    anim_fade_inc = -10.0;
  }
  if ((anim_fade < 0) && (anim_fade_inc < 0)) {
    anim_fade = 0;
    anim_fade_inc = 10.0;
    // switch gears
    animation_gear++;
    if (animation_gear > 5) {animation_gear = 0;}    
  }  

  float anim_fade_constrained = constrain(anim_fade, 0, 255);

  // rainbow animation
  for (int pixel = 0; pixel < 25; pixel++) {
    // rainbox colors
    float color_red = (sin(pixel / 3.0 + animation) + 1.0) * (255.0 / 2.0);
    float color_green = (sin(pixel / 3.0 + animation + 2.1) + 1.0) * (255.0 / 2.0);
    float color_blue = (sin(pixel / 3.0 + animation + 4.2) + 1.0) * (255.0 / 2.0);
    // set RGB channels for canvas
    canvas_5x5rgb[pixel * 3] = round(color_red);
    canvas_5x5rgb[pixel * 3 + 1] = round(color_green);
    canvas_5x5rgb[pixel * 3 + 2] = round(color_blue);
  }


  for (int i = 0; i < 25 * 3; i++) {
    // set the canvas to be the rainbow image from Photopea
    float rainbow_animated_digit = (float)canvas_5x5rgb[i]; // rainbow
    rainbow_animated_digit = rainbow_animated_digit + anim_fade_constrained; // brightening the rainbow
    //rainbow_animated_digit = rainbow_animated_digit * ((float)digit_3_map[i] / 255.0); // use digit image as mask

    rainbow_animated_digit = rainbow_animated_digit * (((float)gear_bitmaps[animation_gear][i]) / 255.0); // use digit GEAR image as mask

    rainbow_animated_digit = rainbow_animated_digit * anim_fade_constrained / 255.0; // fading animation
    rainbow_animated_digit = constrain(rainbow_animated_digit, 0, 255);
    canvas_5x5rgb[i] = round(rainbow_animated_digit);
  }


  /*for (int i = 0; i < 25 * 3; i++) {
    // set the canvas to be the rainbow image from Photopea
    canvas_5x5rgb[i] = rainbow_5x5px_map[i];
    }*/


  // draw pixels on the actual 5x5 RGB LED Matrix Display
  for (int pixel = 0; pixel < 25; pixel++) {
    ledmatrix.drawPixel(indexes[pixel * 3] % 16, indexes[pixel * 3] / 16, canvas_5x5rgb[pixel * 3 + 2]); // red
    ledmatrix.drawPixel(indexes[pixel * 3 + 1] % 16, indexes[pixel * 3 + 1] / 16, canvas_5x5rgb[pixel * 3 + 1]); // green
    ledmatrix.drawPixel(indexes[pixel * 3 + 2] % 16, indexes[pixel * 3 + 2] / 16, canvas_5x5rgb[pixel * 3]); // blue
  }



  // Neopixel code below
  // neopixels are only used to simulate the content of the Pimoroni 11x7px display inside WOKWI emulator

  // clear neopixels
  NeoPixel.clear();  // set all pixel colors to 'off'. It only takes effect if pixels.show() is called
  // transfer the content of the 11x7 canvas to neopixels
  for (uint8_t pixel = 0; pixel < 25; pixel++) {
    NeoPixel.setPixelColor(pixel, NeoPixel.Color(canvas_5x5rgb[pixel * 3 + 2], canvas_5x5rgb[pixel * 3 + 1], canvas_5x5rgb[pixel * 3]));
  }
  NeoPixel.show(); // show all the set pixels on neopixel canvas
}