// определения для работы, настраиваются пользователем
#define GO_UP_BTN_PIN 11    // нижний датчик прохода
#define GO_DOWN_BTN_PIN 10  // верхний датчик прохода
#define LED_PIN 6           // пин ленты
#define WORK_BRIGHTNESS 100  // яркость подсветки лестнецы для рабочего режима
#define DUTY_BRIGHTNESS 40  // яркость подсветки лестнецы для дежурного режима
#define NUM_STEP 20         // количество ступеней лестницы
//#define BRI_STEP 20       // количество ступеней яркости, для медленного и плавного включения/выключения.
#define BRI_STEP 10         // количество ступеней яркости
#define NUM_LEDS 516        //количество светодиодов ленты, нужно вычислить самостоятельно
#define ON_TIME 7           // время полного загорания/затухания подсветки. Время сильно примерное для малых величин (меньше 5), т.к. не учитываются доп операции и реально будет больше
#define ON_DELAY 3          // время задержки после полного влючения. Время выключения отсчитается от последнего срабатывания датчика, равно ON_TIME+ON_DELAY
#define DUTY_TIME 500       // время прохода полного цикла огней, в мс. 500 близко к минимуму. Минимальное значение зависит от кол-ва ступеней яркости и общего числа ступеней.
#define K_OUT 0.94          // коэффициент затухания дежурных огней при переходе на рабочий режим
#define DUTY_LEN 3          // количество одновременных дежурных огней
#define DUTY_DELAY 3        // задержка между циклами дежурных огней
#include <FastLED.h>
#define COLOR_ORDER GRB
#define CHIPSET     WS2811
#include "GyverButton.h"    // работа с датчиками прохода как с кнопками
// если датчики работают как то "наоборот", то поставить последний параметр NORM_CLOSE
GButton up_Btn(GO_UP_BTN_PIN , HIGH_PULL, NORM_CLOSE);
GButton down_Btn(GO_DOWN_BTN_PIN , HIGH_PULL, NORM_CLOSE);
// кол-во светодиодов на каждую ступень, настраивается пользователем
uint8_t stairs_step_leds[NUM_STEP] =
{
  25, 25, 25, 25, 25,
  25, 25, 25, 25, 25, 
  26, 26, 26, 28, 28,
  28, 28, 25, 25, 25 
};
// рабочие цвета подсветки, настраивается пользователем
CRGB work_Color = CRGB(255, 255, 255);
CRGB fly_Color = CRGB(20, 15, 255);
// глобальные переменные не требующие изменений
// массив светодиодов
CRGB leds[NUM_LEDS];
// задержка между изменениями цвета
uint8_t step_delay = 20;
uint8_t duty_delay = 20;
// градации яркости для шагов. всего BRI_STEP шагов.
// для длительных загораний более плавное включение
/*
  uint8_t led_brightness[BRI_STEP] =
  {
  0, 5, 10, 15, 20, 25, 30, 35, 40, 52,
  65, 77, 90, 102, 115, 125, 135, 145, 180, 255
  };*/
// для коротких загораний - быстрое включение
uint8_t led_brightness[BRI_STEP] =
{
  0, 10, 20, 40, 60,
  90, 130, 170, 210, 255
};
// текущие ступени, снизу вверх, сверху вниз, и на выключение, дежурный
int8_t cur_step[4] =
{
  0, NUM_STEP, NUM_STEP, 0
};
// текушая яркость ступени, от  0 до 9
uint8_t br_all[NUM_STEP];
bool goDown = false;        // огни загораются сверху вниз
bool goUp = false;          // огни загораются снизу вверх
bool lights_out = false;    // огни выключаются
bool go_direction = false;  // направление выключения false сверху вниз, true снизу вверх
bool goDuty = true;         // работает режим дежурных онней
bool needClear = false;     // надо почистить таблицу яркости!
bool ignor_top = false;     // надо игнорировать одно срабатывание верхнего датчика
bool ignor_bottom = false;  // надо игнорировать одно срабатывание нижнего датчика
uint32_t off_tmr = 0;
void setup() {
  Serial.begin(115200);
  delay(1000);
  FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalSMD5050);
  FastLED.setBrightness(DUTY_BRIGHTNESS);
  ClearBRI();
  ClearLed();
  FastLED.show();
  step_delay = get_step_delay();
  Serial.println(step_delay);
  duty_delay = get_duty_delay();
  Serial.println(duty_delay);
}
void loop() {
  down_Btn.tick();
  if (down_Btn.isPress())
  {
    if (!ignor_top)
    {
      Serial.println("Идем вниз!");
      goDown = true;
      cur_step[2] = NUM_STEP;
      lights_out = false;
      go_direction = false;
      off_tmr = millis();
      if (goDuty)
      {
        ClearBRI();
        FastLED.setBrightness(WORK_BRIGHTNESS);
        needClear = true;
      }
      goDuty = false;
    }
    else
    {
      ignor_top = false;
      Serial.println("Игнор верхнего датчика!");
    }
  }
  if (goDown)
  {
    lights_out = false;
    goDown = stairs_fire_on_down();// если есть что зажигать свверху вниз, то зажигаем
    if (goDown) ClearMidle();
    else
    {
      ignor_bottom = true;
      Serial.println("Нужен игнор нижнего датчика!");
    }
  }
  up_Btn.tick();
  if (up_Btn.isPress())
  {
    if (!ignor_bottom)
    {
      Serial.println("Идем вверх!");
      goUp = true;
      cur_step[2] = 0;
      lights_out = false;
      go_direction = true;
      off_tmr = millis();
      if (goDuty)
      {
        ClearBRI();
        FastLED.setBrightness(WORK_BRIGHTNESS);
        needClear = true;
      }
      goDuty = false;
    }
    else
    {
      ignor_bottom = false;
      Serial.println("Игнор верхнего датчика!");
    }
  }
  if (goUp)
  {
    lights_out = false;
    goUp = stairs_fire_on_up(); // если есть что зажигать снизу вверх, то зажигаем
    if (goUp) ClearMidle();
    else
    {
      ignor_top = true;
      Serial.println("Нужен игнор верхнего датчика!");
    }
  }
  if ((!goDown) && (!goUp) && (!goDuty)) lights_out = true; 
  if (lights_out)
  {
    if (millis() - off_tmr > 1000 * (ON_TIME + ON_DELAY))
    {
      ignor_bottom = false;
      ignor_top = false;
      if (go_direction) lights_out = stairs_fire_off_up();
      else lights_out = stairs_fire_off_down();
      goDuty = !lights_out;
      if (goDuty)
      {
        FastLED.setBrightness(DUTY_BRIGHTNESS);
        cur_step[3] = 0;
      }
    }
  }
  if (goDuty)
  {
    stairs_duty_fire();
    ignor_bottom = false;
    ignor_top = false;
  }
  redraw_leds();
  //ShowFPS();
}
// функции
void ClearLed()
{
  for (uint16_t i = 0; i < NUM_LEDS; i++)   leds[i] = 0;
}
uint16_t first_led_on_step(uint8_t n) // номер 1-го светодиода в ступени n
{
  uint16_t result = 0;
  for (uint8_t i = 0; i < n; i++) result = result + stairs_step_leds[i];
  return result;
}
void set_step_color(uint8_t n, CRGB c)
{
  uint16_t ns = first_led_on_step(n);
  for (uint16_t i = ns; i < (ns + stairs_step_leds[n]); i++)
    leds[i] = c;
}
CRGB remapColor(CRGB c, uint8_t br)
{
  return CRGB(map(c.r, 0, 255, 0, led_brightness[br]), map(c.g, 0, 255, 0, led_brightness[br]), map(c.b, 0, 255, 0, led_brightness[br]));
}
bool fire_on_step(uint8_t n, CRGB c)
{
  if (br_all[n] == (BRI_STEP - 1)) return true;
  br_all[n]++;
  set_step_color(n, remapColor(c, br_all[n]));
  return false;
}
bool fire_off_step(uint8_t n, CRGB c)
{
  if (br_all[n] == 0) return true;
  br_all[n]--;
  set_step_color(n, remapColor(c, br_all[n]));
  return false;
}
bool stairs_fire_on_up()
{
  static uint32_t f_tmr = millis();
  if (millis() - f_tmr < step_delay) return true;
  f_tmr = millis();
  if (fire_on_step(cur_step[0], work_Color)) cur_step[0]++;
  if (cur_step[0] == NUM_STEP)
  {
    cur_step[0] = 0;
    return false;
  }
  return true;
}
bool stairs_fire_off_up()
{
  static uint32_t f_tmr = millis();
  if (millis() - f_tmr < step_delay) return true;
  f_tmr = millis();
  if (fire_off_step(cur_step[2], work_Color)) cur_step[2]++;
  if (cur_step[2] == NUM_STEP)
  {
    cur_step[2] = 0;
    return false;
  }
  return true;
}
bool stairs_fire_on_down()
{
  static uint32_t f_tmr = millis();
  if (millis() - f_tmr < step_delay) return true;
  f_tmr = millis();
  if (fire_on_step(cur_step[1] - 1, work_Color)) cur_step[1]--;
  if (cur_step[1] == 0)
  {
    cur_step[1] = NUM_STEP;
    return false;
  }
  return true;
}
bool stairs_fire_off_down()
{
  static uint32_t f_tmr = millis();
  if (millis() - f_tmr < step_delay) return true;
  f_tmr = millis();
  if (fire_off_step(cur_step[2] - 1, work_Color)) cur_step[2]--;
  if (cur_step[2] == 0)
  {
    cur_step[2] = NUM_STEP;
    return false;
  }
  return true;
}
void redraw_leds()
{
  static uint32_t r_tmr = millis();
  if (millis() - r_tmr < (step_delay)) return;
  r_tmr = millis();
  FastLED.show();
}
uint8_t get_duty_delay()
{
  return (DUTY_TIME) / NUM_STEP / BRI_STEP;
}
uint8_t get_step_delay()
{
  return (1000.0 * ON_TIME) / NUM_STEP / BRI_STEP;
}
void ShowFPS()
{
  static uint32_t tm_m = 0;
  static uint32_t cnt_m = 0;
  cnt_m++;
  if ((millis() - tm_m) > 1000)
  {
    Serial.print("loop per sec: "); Serial.println(cnt_m);
    cnt_m = 0;
    tm_m = millis();
  }
}
void set_duty_step_color(uint8_t n, CRGB c)
{
  uint16_t ns = first_led_on_step(n);
  leds[ns] = c;
  for (uint16_t i = ns + 1; i < ns + stairs_step_leds[n] - 1; i++)
    leds[i] = CRGB(0, 0, 0);
  leds[ns + stairs_step_leds[n] - 1] = c;
}
bool duty_fire_on_step(uint8_t n, CRGB c)
{ 
  if (br_all[n] == (BRI_STEP - 1)) return true;
  br_all[n]++;
  set_duty_step_color(n, remapColor(c, br_all[n])); 
  return false;
}
bool duty_fire_off_step(uint8_t n, CRGB c)
{
  if (br_all[n] == 0) return true;
  br_all[n]--;
  set_duty_step_color(n, remapColor(c, br_all[n]));
  return false;
}
void stairs_duty_fire()
{
  static uint32_t f_tmr = millis();
  static uint32_t delay_tmr = millis();
  if (millis() - f_tmr < duty_delay) return true;
  f_tmr = millis();
  int16_t on_step = cur_step[3];
  int16_t off_step = cur_step[3] - (DUTY_LEN - 1); 
  bool f_off = false;
  bool f_on = false;   
  if (off_step > -1 && off_step < NUM_STEP) f_off = duty_fire_off_step(off_step, fly_Color);
  else f_off = true; 
  if (on_step > -1 && on_step < NUM_STEP) f_on = duty_fire_on_step(on_step, fly_Color);
  else f_on = true; 
  if (cur_step[3] == (NUM_STEP + DUTY_LEN - 1)) delay_tmr = millis();
  if ((cur_step[3] == (NUM_STEP + DUTY_LEN)) && (millis() - delay_tmr < (1000 * DUTY_DELAY))) f_on = false; 
  if (f_off && f_on) cur_step[3] = (cur_step[3] + 1) % (NUM_STEP + DUTY_LEN + 1); 
}
void ClearBRI()
{
  for (uint8_t i = 0; i < NUM_STEP; i++)br_all[i] = 0;
}
void ClearMidle()
{
  if (!needClear) return;
  static uint32_t f_tmr = millis();
  if (millis() - f_tmr < step_delay) return;
  f_tmr = millis();
  needClear = false;
  int16_t on_step = cur_step[3];
  int16_t off_step = cur_step[3] - (DUTY_LEN - 1);
  if (off_step < 0) off_step = off_step + NUM_STEP;
  if (off_step > on_step) on_step = on_step + NUM_STEP;
 
  for (int8_t i = off_step; i < on_step + 1; i++)
  {
    int8_t pz = i % NUM_STEP;
    if (pz >= cur_step[0] && pz  <= cur_step[1])
    {
      uint16_t ns = first_led_on_step(pz);
      if (CRGB(0, 0, 0) != leds[ns])
      {
        needClear = true;
        leds[ns] = CRGB(K_OUT * leds[ns].r , K_OUT * leds[ns].g , K_OUT * leds[ns].b );
      }
      ns = ns + stairs_step_leds[pz] - 1;
      if (CRGB(0, 0, 0) != leds[ns])
      {
        needClear = true;
        leds[ns] = CRGB(K_OUT * leds[ns].r , K_OUT * leds[ns].g , K_OUT * leds[ns].b );
      }
    }
  }
}