/*
   Управление лентой на WS2812 с компьютера + динамическая яркость
   добавил IR пульт и балансировку цветов Shaitan http://nettips.ru/ 2021
*/
//----------------------НАСТРОЙКИ-----------------------
#define NUM_LEDS 42          // число светодиодов в ленте
#define DI_PIN 13            // пин, к которому подключена лента
#define TYPE_LED_2811        // 2811 - WS2811 12V при использовании WS2812 5V закоментировать
#define OFF_TIME 10          // время (секунд), через которое лента выключится при пропадаании сигнала
#define CURRENT_LIMIT 2500   // лимит по току в миллиамперах, автоматически управляет яркостью (пожалей свой блок питания!) 0 - выключить лимит

#define START_FLASHES 1      // проверка цветов при запуске (1 - включить, 0 - выключить)

#define AUTO_BRIGHT 0        // автоматическая подстройка яркости от уровня внешнего освещения (1 - включить, 0 - выключить)
#define MIN_BRIGHT 10        // минимальная яркость (0 - 255)
#define BRIGHT_CONSTANT 400  // константа усиления от внешнего света (0 - 1023)
// чем МЕНЬШЕ константа, тем "резче" будет прибавляться яркость
#define COEF 0.9             // коэффициент фильтра (0.0 - 1.0), чем больше - тем медленнее меняется яркость

#include <avr/eeprom.h>

#include "IRremote.h"        // Билиотека для пульта
IRrecv irrecv(2); // указываем вывод, к которому подключен приемник
decode_results results;
uint32_t IR_KEY, IR_KEY_REP;

uint8_t MAX_BRIGHT=255;       // максимальная яркость (0 - 255)
uint8_t MAX_RED=255;
uint8_t MAX_GREEN=255;
uint8_t MAX_BLUE=255;

uint8_t Ada_step=0;
uint8_t Ada_led;

bool    save_eeprom=false;
uint32_t save_eeprom_timer;
uint8_t mode=1;               // 0-Выкл, 1-Auto, 2-Белый, 3-Красный, 4-Зеленый, 5-Синий, 6-Желтый
//----------------------НАСТРОЙКИ-----------------------

int new_bright, new_bright_f;
unsigned long bright_timer, off_timer;

#define serialRate 115200  // скорость связи с ПК
uint8_t prefix[] = {'A', 'd', 'a'}, hi, lo, chk, i;  // кодовое слово Ada для связи
#include <FastLED.h>
CRGB leds[NUM_LEDS];  // создаём ленту
boolean led_state = true;  // флаг состояния ленты


void setup()
{
#ifdef TYPE_LED_2811  
  FastLED.addLeds<WS2812, DI_PIN, BRG>(leds, NUM_LEDS);  // инициализация светодиодов (BRG - порядок цветов) для 12 вольтовых лент
#else
 FastLED.addLeds<WS2812, DI_PIN, RGB>(leds, NUM_LEDS);  // инициализация светодиодов (RGB - порядок цветов) для 5 вольтовых лент
#endif
  if (CURRENT_LIMIT > 0) FastLED.setMaxPowerInVoltsAndMilliamps(5, CURRENT_LIMIT);

  // Считываем с EEPROM значения яркости и гаммы
 if (eeprom_read_byte(0)!=55)
   { 
    save_eeprom=true;
    save_MAX_param();
    eeprom_update_byte(0,55); // Записать признак не первого запуска
   }

 //Счимтываем параметры яркости и цветовой балансировки
 MAX_BRIGHT=eeprom_read_byte(1);       // максимальная яркость (0 - 255)
 MAX_RED=eeprom_read_byte(2);
 MAX_GREEN=eeprom_read_byte(3);
 MAX_BLUE=eeprom_read_byte(4);
 save_eeprom_timer=millis();

  // вспышки красным синим и зелёным при запуске (можно отключить)
  if (START_FLASHES) {
    LEDS.showColor(CRGB(255, 0, 0));
    delay(500);
    LEDS.showColor(CRGB(0, 255, 0));
    delay(500);
    LEDS.showColor(CRGB(0, 0, 255));
    delay(500);
    LEDS.showColor(CRGB(0, 0, 0));
  }

  irrecv.enableIRIn(); // запускаем прием IR

  Serial.begin(serialRate);
  Serial.print("Ada\n");     // Связаться с компом
}

void save_MAX_param()
{
  if (save_eeprom)
   if (millis()-save_eeprom_timer>60000) //Сохранение не чаще, чем раз в минуту
   { 
    eeprom_update_byte(1,MAX_BRIGHT); // MAX_BRIGHT
    eeprom_update_byte(2,MAX_RED);    // MAX_RED
    eeprom_update_byte(3,MAX_GREEN);  // MAX_GREEN
    eeprom_update_byte(4,MAX_BLUE);   // MAX_BLUE
    save_eeprom=false;
   } 
}


void check_IR() {
   if (irrecv.decode(&results)) {

   IR_KEY=results.value;
   if (results.value==0xFFFFFFFF) IR_KEY=IR_KEY_REP; // на некоторых пультах повтор нажатой клавиши 0xFFFFFFFF
       //Выбор шага изменения яркости
       uint8_t delta = MAX_BRIGHT < 100U?1U:5U; 
        switch (mode)
         {
            case 3: delta = MAX_RED   < 100U?1U:5U;   break;  // Красный
            case 4: delta = MAX_GREEN < 100U?1U:5U;   break;  // Зеленый
            case 5: delta = MAX_BLUE  < 100U?1U:5U;   break;  // Синий
         }
       //При  передаче в модуль PaintPack тредуется 8 байт
       if (IR_KEY>0x00FFFFFF) Serial.print(IR_KEY, HEX);  
       else Serial.print(IR_KEY | 0x80000000 , HEX);  
       switch (IR_KEY)
          {
            //Перечень кнопок пультов, у которых повтор 0xFFFFFFFF для
             case 0xFFE01F:IR_KEY_REP=IR_KEY;break;  // Кнопка Vol- 
             case 0xFFA857:IR_KEY_REP=IR_KEY;break;  // Кнопка Vol- 

             case 0xE0E012ED: // FF Увеличить яркость/цвет
               IR_KEY_REP=IR_KEY;
               switch (mode)
               {
                 case 1:
                 case 6:
                 case 2: if (MAX_BRIGHT+delta<255) MAX_BRIGHT+=delta; break;  // Яркость
                 case 3: if (MAX_RED+delta<255)    MAX_RED+=delta;    break;  // Красный
                 case 4: if (MAX_GREEN+delta<255)  MAX_GREEN+=delta;  break;  // Зеленый
                 case 5: if (MAX_BLUE+delta<255)   MAX_BLUE+=delta;   break;  // Синий
               }
               save_eeprom=true;
               save_eeprom_timer=millis();
               break;  
             case 0xE0E0A25D:
               IR_KEY_REP=IR_KEY;
               switch (mode)  // RF Уменьшить яркость/цвет
               {
                 case 1:
                 case 6:
                 case 2: if (MAX_BRIGHT>MIN_BRIGHT) MAX_BRIGHT-=delta; break;  // Яркость
                 case 3: if (MAX_RED>delta)         MAX_RED-=delta;    break;  // Красный
                 case 4: if (MAX_GREEN>delta)       MAX_GREEN-=delta;  break;  // Зеленый
                 case 5: if (MAX_BLUE>delta)        MAX_BLUE-=delta;   break;  // Синий
               } 
               save_eeprom=true;
               save_eeprom_timer=millis();
               break;  
             case 0xE0E0629D: mode=0; break;  // Stop
             case 0xE0E0E21D: mode=1; break;  // Play, Режим авто
             case 0xE0E052AD: mode=2; break;  // Pause, Белый
             case 0xE0E036C9: mode=3; break;  // Красный
             case 0xE0E028D7: mode=4; break;  // Зеленый
             case 0xE0E06897: mode=5; break;  // Синий
             case 0xE0E0A857: mode=6; break;  // Желтый (для настройки не используется. просто есть кнопка на пульте)
             default:IR_KEY_REP=0xFFFFFFFF;break; 
          }   
       switch (mode) { //После нажатия кнопок перерисовать ленту
             case 0: LEDS.showColor(CRGB(0, 0, 0));                     break;
             case 2: LEDS.showColor(CRGB(MAX_RED, MAX_GREEN, MAX_BLUE));break;
             case 3: LEDS.showColor(CRGB(MAX_RED, 0, 0));               break;
             case 4: LEDS.showColor(CRGB(0, MAX_GREEN, 0));             break;
             case 5: LEDS.showColor(CRGB(0, 0, MAX_BLUE));              break;
             case 6: LEDS.showColor(CRGB(MAX_RED, MAX_GREEN/2, 0));     break;
       }
       save_MAX_param(); //Проверяем, надо ли сохранить параметры яркости в eeprom  
       led_state=true;
       irrecv.resume();  // принимаем следующую команду
      } 
}

void check_com() {
 if (Serial.available()) 
 switch (Ada_step) {
  case 0: // Зачитываем начальное значение "Ada"
  case 1:
  case 2: if (prefix[Ada_step] == Serial.read()) Ada_step++;else Ada_step=0;break;
          // Сверяемся с контрольной суммой
  case 3: hi = Serial.read();Ada_step++;break;
  case 4: lo = Serial.read();Ada_step++;break;
  case 5: chk = Serial.read();
   if (chk == (hi ^ lo ^ 0x55)) {
      Ada_step++;
      Ada_led=0;
    } else Ada_step=0;
          // Считываем значения в ленту 


  
#ifdef TYPE_LED_2811  
// поряток для 12 вотльтовых лент  
  case 6: leds[Ada_led].b = map(Serial.read(),0,255,0,MAX_BLUE);  Ada_step++;break;
  case 7: leds[Ada_led].r = map(Serial.read(),0,255,0,MAX_RED);   Ada_step++;break; 
  case 8: leds[Ada_led].g = map(Serial.read(),0,255,0,MAX_GREEN);
#else  
// поряток для 5 вотльтовых лент  
  case 6: leds[Ada_led].r = map(Serial.read(),0,255,0,MAX_RED);   Ada_step++;break; 
  case 7: leds[Ada_led].g = map(Serial.read(),0,255,0,MAX_GREEN); Ada_step++;break;
  case 8: leds[Ada_led].b = map(Serial.read(),0,255,0,MAX_BLUE);  
#endif  
    Ada_led++;
    Ada_step=6;
    if (Ada_led>=NUM_LEDS) {
      Ada_step=0;                   // начинаем сначала
      off_timer=millis();           // сбрасываем счетчик автоотключения
      if (mode==1) FastLED.show();  // записываем цвета в ленту в авторежиме
    }
   break; 
 }
}

void loop() {
  if (AUTO_BRIGHT) {                         // если включена адаптивная яркость
    if (millis() - bright_timer > 100) {     // каждые 100 мс
      bright_timer = millis();               // сброить таймер
      new_bright = map(analogRead(6), 0, BRIGHT_CONSTANT, MIN_BRIGHT, MAX_BRIGHT);   // считать показания с фоторезистора, перевести диапазон
      new_bright = constrain(new_bright, MIN_BRIGHT, MAX_BRIGHT);
      new_bright_f = new_bright_f * COEF + new_bright * (1 - COEF);
      LEDS.setBrightness(new_bright_f);      // установить новую яркость
    }
 }
  if (mode==1) // Гасим в автоматическом режиме при отсутствии активности OFF_TIME секунд
  if (led_state) {
    if (millis() - off_timer > (OFF_TIME * 1000)) {
      led_state = false;
      FastLED.clear();
      FastLED.show();
    }
  }

  check_IR();  // Проверить пульт
  check_com(); // Проверить порт

}  
