автополив на ардуине

anon
Offline
Зарегистрирован: 23.05.2013

добрый день. есть ардуина, есть часы реального времени и релюха. хочу сделать автополив цикличный раз в день, два, три (как выбиру). можно было бы запилить какой-нибудь delay или millis, но это не спасет меня от выключения искричества. для этого и приобрел часы 1307. вот, собственно и сам вопрос скорее по логике, нежели чем по коду: как увязать цикл полива в определенное время (через сутки, например) и текущую дату и, в случае отключения тока, не столкнуться с выполнением цикла заново? пока только пришел к подсчету дней с начала года и вычисления сл. дня полива. но это не элегантно, кажется. помогите

anon
Offline
Зарегистрирован: 23.05.2013

выберу, конечно же

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

а поиском не ищется?

полив в нужные дни недели в нужное время на нужное количество минут:

// street pump for Arduino pro mini 5v

#include <TM1637.h>
#include <RTClib.h>
#include <EEPROM.h>
#include <avr/wdt.h>

uint8_t mcusr_mirror __attribute__ ((section (".noinit")));
void get_mcusr(void) \
__attribute__((naked)) \
__attribute__((used)) \
__attribute__((section(".init3")));
void get_mcusr(void)
{
  mcusr_mirror = MCUSR;
  MCUSR = 0;
  wdt_disable();
}

#define count_keys 5
#define start_pin_keys 2

#define pos_eeprom_min_pump 19
#define pos_eeprom_hour_time 20
#define pos_eeprom_min_time 21
#define pos_eeprom_stat_days 22 // 7 bytes 0-off 1=on Monday....Sunday

#define period_read_key 100UL
#define min_count_key_down 2

#define max_time_options 10000UL // 10 sec

#define period_flash_led_days 50UL

#define period_get_real_time 60000UL // 1 min

byte tmPoint = 0;

unsigned long timer_get_real_time = 0;
unsigned long timer_read_key = 0;
unsigned long timer_every_sec = 0;
unsigned long timer_flash_led_days;
unsigned long timer_time_options;
unsigned long timer_pumping;

byte keys_down[count_keys];
byte keys_begin[count_keys];
byte keys_click[count_keys];

boolean startFlashLedDays = false;
byte currentFlashLedDays;
byte nextFlashLedDays;

TM1637 mytm(1, 0); // TM1637 0-CLK 1-DIO
RTC_DS1307 myrtc; // DS1307 A4-SDA A5-SCL

byte daysWeek[7]; // 1-day enable 0-day disable
byte minPump; // period for pumping by min
byte hourTime; // hour for pump
byte minTime; // min for pump

byte deviceMode = 0;
// 0 - main mode
// 1 - set day pump
// 2 - set period min pumping
// 3 - set real time on DS1307
// 4 - pumping

byte currentDay; // current day of week from 0 = monday
byte currentHour; // current hour of day
byte currentMin; // current min of hour

void setup() {
  // put your setup code here, to run once:
  for (byte i = 17; i >= 11; --i) { // init led Monday....Sunday, A3...A0 D13...D11 and OFF
    pinMode(i, OUTPUT); digitalWrite(i, HIGH);
  }
  pinMode(0, OUTPUT); digitalWrite(0, LOW); // CLK TM1637 display
  pinMode(1, OUTPUT); digitalWrite(1, LOW); // DIO TM1637 display
  pinMode(2, INPUT); digitalWrite(2, HIGH); // key select day pump / set day real time
  pinMode(3, INPUT); digitalWrite(3, HIGH); // key set day pump / set month real time
  pinMode(4, INPUT); digitalWrite(4, HIGH); // key select time hour pump / set hour real time
  pinMode(5, INPUT); digitalWrite(5, HIGH); // key select time min pump / set min real time
  pinMode(6, INPUT); digitalWrite(6, HIGH); // key select period min pump / set year real time
  pinMode(7, OUTPUT); digitalWrite(7, LOW); // OFF relay pump
  pinMode(8, OUTPUT); digitalWrite(8, LOW); // OFF reserv relay
  pinMode(9, OUTPUT); digitalWrite(9, LOW); // off unused pin
  pinMode(10, OUTPUT); digitalWrite(10, LOW); // off unused pin
  pinMode(A6, OUTPUT); digitalWrite(A6, LOW); // off unused pin
  pinMode(A7, OUTPUT); digitalWrite(A7, LOW); // off unused pin
  if (! myrtc.begin())  while (1); // init DS1307
  mytm.init(); // init TM1637
  mytm.set(5); // BRIGHT TM1637 MAX=7
  mytm.point(0); mytm.display(0, 0x0A); mytm.point(1); mytm.display(1, 0x0B); mytm.point(0); mytm.display(2, 0x0C); mytm.point(1); mytm.display(3, 0x0D);
  unsigned long delp = millis(); while ((millis() - delp) <= 5000); // delay 5 sec for RTC
  mytm.point(1); mytm.display(0, 0x0E); mytm.point(0); mytm.display(1, 0x0F); mytm.point(1); mytm.display(2, 0x00); mytm.point(0); mytm.display(3, 0x09);
  delp = millis(); while ((millis() - delp) <= 10000); // delay 10 sec for RTC
  //myrtc.adjust(DateTime(2018, 3, 1, 12, 24, 10)); // test
  getCurrentTime(); // load day and time from DS1307
  showCurrentTimeAndDay();
  loadDataFromEeprom();
  for (byte i = 0; i < count_keys; ++i) keys_begin[i] = 0; // reset keys
  wdt_enable(WDTO_4S);
  delp = millis(); while ((millis() - delp) <= 1000); // delay 1 sec fo test
  // flash led - start and test wdt
  for (byte i = 0; i < 10; ++i) {
    digitalWrite(LED_BUILTIN, HIGH); delay(50); digitalWrite(LED_BUILTIN, LOW); delay(50);
  }
}

void loop() {
  wdt_reset();
  // put your main code here, to run repeatedly:
  //delay(5000); // test wdt
  unsigned long current_millis = millis();
  // get current time every 1 min
  if ((current_millis - timer_get_real_time) >= period_get_real_time) {
    timer_get_real_time = current_millis;
    getCurrentTime();
    // compare time and day for start pumping
    if ((currentHour == hourTime) && (daysWeek[currentDay] == 1) && ((currentMin - minTime) >= 0) && ((currentMin - minTime) <= 4) && (deviceMode == 0)) {
      timer_pumping = current_millis;
      deviceMode = 4;
    }
  }
  // end timer options
  if ((deviceMode > 0) && (deviceMode < 4)) {
    if ((current_millis - timer_time_options) >= max_time_options) {
      deviceMode = 0;
    }
  }
  // operate keys
  if ((current_millis - timer_read_key) >= period_read_key) {
    timer_read_key = current_millis;
    for (byte i = 0; i < count_keys; ++i) {
      if (digitalRead(i + start_pin_keys) == 0) {
        // key id down
        if (keys_begin[i] == 1) {
          ++keys_down[i];
        } else {
          keys_begin[i] = 1;
          keys_down[i] = 0;
          keys_click[i] = 0;
        }
      } else {
        // key is up
        if (keys_begin[i] == 1) {
          if (keys_down[i] >= min_count_key_down) {
            // short click
            keys_begin[i] = 0;
            keys_down[i] = 0;
            keys_click[i] = 1;
          } else {
            // no click
            keys_begin[i] = 0;
            keys_down[i] = 0;
            keys_click[i] = 0;
          }
        }
      }
    }
    // analize keys
    if (keys_click[0] == 1) {
      if (deviceMode == 0) {
        timer_time_options = current_millis;
        deviceMode = 1;
        currentFlashLedDays = 0;
      } else if (deviceMode == 1) {
        timer_time_options = current_millis;
        if ((++currentFlashLedDays) > 6) currentFlashLedDays = 0;
      }
    }
    if (keys_click[1] == 1) {
      if (deviceMode == 1) {
        if (daysWeek[currentFlashLedDays] == 1) daysWeek[currentFlashLedDays] = 0; else daysWeek[currentFlashLedDays] = 1;
        EEPROM.update((currentFlashLedDays + pos_eeprom_stat_days), daysWeek[currentFlashLedDays]);
      }  else if (deviceMode == 0) {
        showCurrentTimeAndDay();
      }
    }
    if ((keys_click[2] == 1) && (deviceMode == 0)) {
      if ((++hourTime) > 23) hourTime = 5;
      EEPROM.update(pos_eeprom_hour_time, hourTime);
      showStartTimePumpAndPoint();
    }
    if ((keys_click[3] == 1) && (deviceMode == 0)) {
      if ((minTime += 5) > 55) minTime = 0;
      EEPROM.update(pos_eeprom_min_time, minTime);
      showStartTimePumpAndPoint();
    }
    if (keys_click[4] == 1) {
      if (deviceMode == 0) {
        timer_time_options = current_millis;
        deviceMode = 2;
        mytm.clearDisplay();
        showPeriodTimePump();
      } else if (deviceMode == 2) {
        timer_time_options = current_millis;
        if ((minPump += 5) > 30) minPump = 5;
        EEPROM.update(pos_eeprom_min_pump, minPump);
        showPeriodTimePump();
      }
    }
    // end analize keys
  }
  // flash days led every 1 sec and othen action every sec
  if ((current_millis - timer_every_sec) >= 1000UL) {
    timer_every_sec = current_millis;
    timer_flash_led_days = current_millis;
    startFlashLedDays = true;
    nextFlashLedDays = 0;
    // other actions every sec
    if (deviceMode == 0) {
      // show start pump time and flash point
      showStartTimePumpAndPoint();
    }
    if (deviceMode == 4) {
      if (((current_millis - timer_pumping) <= (minPump * 60000UL)) && (minPump < 40)) {
        digitalWrite(8, HIGH); // reserv ON
        unsigned long delp = millis(); while ((millis() - delp) <= 300);
        digitalWrite(7, HIGH); // pump ON
        showTimePumping((current_millis - timer_pumping) / 1000);
      } else {
        digitalWrite(7, LOW); // pump OFF
        unsigned long delp = millis(); while ((millis() - delp) <= 300);
        digitalWrite(8, LOW); // reserv OFF
        deviceMode = 0;
      }
    } else {
      digitalWrite(7, LOW); // pump OFF
      digitalWrite(8, LOW); // reserv OFF
    }
    if (deviceMode != 1) {
      // ON led active days
      for (byte i = 0; i <= 6; ++i) if (daysWeek[i] == 1) digitalWrite((17 - i), LOW); // led on
    }
    // other actions every sec
  }
  // flash days leds
  if ((current_millis - timer_flash_led_days) >= period_flash_led_days) {
    timer_flash_led_days = current_millis;
    if (startFlashLedDays) {
      if (deviceMode != 1) {
        byte workled = 99;
        if (nextFlashLedDays == 0) {
          // begin loop
          // nothing off led
          // find first free led
          for (byte i = 0; i <= 6; ++i) {
            if (daysWeek[i] == 0) {
              workled = i;
              break;
            }
          }
          if (workled <= 6) {
            digitalWrite((17 - workled), LOW); // led on
          } else {
            startFlashLedDays = false;
          }
          currentFlashLedDays = nextFlashLedDays;
          ++nextFlashLedDays;
        } else {
          // off current led
          if ((currentFlashLedDays > 0) || ((daysWeek[0] == 0) && (currentFlashLedDays == 0)))
            digitalWrite((17 - currentFlashLedDays), HIGH); // led off
          if (nextFlashLedDays > 6) {
            startFlashLedDays = false;
          } else {
            // find first free led
            for (byte i = nextFlashLedDays; i <= 6; ++i) {
              if (daysWeek[i] == 0) {
                workled = i;
                break;
              }
            }
            if (workled <= 6) {
              digitalWrite((17 - workled), LOW); // led on
              currentFlashLedDays = workled;
              nextFlashLedDays = (workled + 1);
            } else {
              startFlashLedDays = false;
            }
          }
        }
      } else {
        // flash for set day pump mode
        // currentFlashLedDays - is current select day by user
        for (byte i = 0; i <= 6; ++i) {
          if (i == currentFlashLedDays) {
            if (nextFlashLedDays == 0) {
              if (daysWeek[i] == 1) digitalWrite((17 - i), HIGH); else digitalWrite((17 - i), LOW);
              ++nextFlashLedDays;
            } else {
              if (daysWeek[i] == 1) digitalWrite((17 - i), LOW); else digitalWrite((17 - i), HIGH);
              startFlashLedDays = false;
            }
          } else {
            if (daysWeek[i] == 1) digitalWrite((17 - i), LOW); else digitalWrite((17 - i), HIGH);
          }
        }
      }
    }
  }
  // end loop
  // clear keys
  for (byte i = 0; i < count_keys; ++i) keys_click[i] = 0;
  // --
}

void getCurrentTime() {
  DateTime now = myrtc.now();
  currentHour = now.hour();
  currentMin = now.minute();
  currentDay = now.dayOfTheWeek();
  if (currentDay == 0) currentDay = 6; else --currentDay;
}

void loadDataFromEeprom() {
  minPump = EEPROM.read(pos_eeprom_min_pump);
  if ((minPump < 5) || (minPump > 30)) minPump = 5;
  hourTime = EEPROM.read(pos_eeprom_hour_time);
  if ((hourTime < 5) || (hourTime > 23)) hourTime = 5;
  minTime = EEPROM.read(pos_eeprom_min_time);
  if (minTime > 55) minTime = 0;
  for (byte i = 0; i <= 7; ++i)
      if (EEPROM.read(i + pos_eeprom_stat_days) == 1) daysWeek[i] = 1; else daysWeek[i] = 0;
}

void showStartTimePumpAndPoint() {
  mytm.point(0);
  mytm.display(0, (hourTime / 10));
  tmPoint = !tmPoint;
  mytm.point(tmPoint);
  mytm.display(1, (hourTime % 10));
  mytm.point(0);
  mytm.display(2, (minTime / 10));
  mytm.point(0);
  mytm.display(3, (minTime % 10));
}

void showPeriodTimePump() {
  mytm.point(0);
  tmPoint = !tmPoint;
  mytm.point(tmPoint);
  mytm.point(0);
  mytm.display(2, (minPump / 10));
  mytm.point(0);
  mytm.display(3, (minPump % 10));
}

void showTimePumping(word unTimePump) {
  mytm.point(0);
  mytm.display(0, (unTimePump / 60) / 10);
  tmPoint = !tmPoint;
  mytm.point(tmPoint);
  mytm.display(1, (unTimePump / 60) % 10);
  mytm.point(0);
  mytm.display(2, (unTimePump % 60) / 10);
  mytm.point(0);
  mytm.display(3, (unTimePump % 60) % 10);
}

void showCurrentTimeAndDay() {
  if ((currentDay > 0) && (currentDay < 4)) mytm.point(0); else mytm.point(1);
  mytm.display(0, (currentHour / 10));
  if ((currentDay == 0) || (currentDay == 2) || (currentDay == 3)) mytm.point(0); else mytm.point(1);
  mytm.display(1, (currentHour % 10));
  if ((currentDay == 2) || (currentDay == 5) || (currentDay == 6)) mytm.point(1); else mytm.point(0);
  mytm.display(2, (currentMin / 10));
  if ((currentDay == 3) || (currentDay == 6)) mytm.point(1); else mytm.point(0);
  mytm.display(3, (currentMin % 10));
}

 

anon
Offline
Зарегистрирован: 23.05.2013

спасибо.  с циклами в каждый день я разобрался, но, может быть, вы подскажите. как ссорудить цикл через поределенное количество суток? 

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

anon пишет:

спасибо.  с циклами в каждый день я разобрался, но, может быть, вы подскажите. как ссорудить цикл через поределенное количество суток? 

ну например хранить в переменной количество суток и каждый millis() деленный на количество миллисекунд в сутки уменьшать эту переменную - как будет равно нулем - значит прошло нужное количество дней.

вот пример подсчет часов и потом дней

  // timer every hour
  if ((work_mode == 0) || (work_mode == 3)) {
    if ((current_millis - timer_hour) >= 3600000UL) { // for test = 1000UL // by real 1 hour = 3600000UL ms
      timer_hour = current_millis;
      if ((++hour_counting) >= 24) {
        ++day_counting;
        hour_counting = 0;
        if (day_counting >= day_period) {
          // start pump
          time_pumping_mode = current_millis;
          TM1637_clear();
          work_mode = 2;
          day_counting = 0;
        }
        EEPROM.write(pos_eeprom_current_days, day_counting);
      }
    }
    // end hour timer
  }

 

anon
Offline
Зарегистрирован: 23.05.2013

ну это не решает вопрос с отключением света на, скажем, 12 часов. тогда ведь это время прибавится к заданному интервалу

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

anon пишет:

ну это не решает вопрос с отключением света на, скажем, 12 часов. тогда ведь это время прибавится к заданному интервалу

все решаемо, например если есть RTC то в EEPROM сохранить время включения полива и когда до не го дойдет счетчик - полить, или сохранять каждый час счетчик в EEPROM и лет через несколько МК выкинуть.

у меня в деревне свет часто отключают, но +- сутки ничего не рашает для полива домашнего растения (согласно установленного женой ТЗ), потому сохраняю в EEPROM только счетчик суток.

b707
Offline
Зарегистрирован: 26.05.2017

можно и без RTC - соорудить ардуине простейшее аварийное питание из батарейки 2032 и диода и она будет сохранять свой счетчик не хуже RTC

anon
Offline
Зарегистрирован: 23.05.2013

вот и мне хотелось бы без насилия над eeprom. вот только как это изобразить я ума ни приложу

anon
Offline
Зарегистрирован: 23.05.2013

есть у меня 18650 ворох (правда без защиты и контроллера) и, как вариант это решение. только тут зависимость от самой батарейки - рано или поздно она помрет, а я и знать не знаю. 

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

все зависит от задачи/желания/стоимости,

я тут прикинул - минимум 5 лет проживет EEPROM при записи каждый час, вы хорошо с электроникой дружите? нужно оно вам паять дополнительную электронику?

мне например лишняя деталь - уже проблема, со школы толком ничего не паял, проще программно решить вопрос.

сдохнет attiny85 - проще другую поставить, чем городить огород с батарейкой/аккумулятором/RTC

anon
Offline
Зарегистрирован: 23.05.2013

паяю норм, с этим проблем нет, а вот с программированием хуже) я именно по этому и не хочу городить аккум, тк думаю что есть более элегантное решение, прост не могу продумать логику.
100 000 циклов это 11 лет.
 

 

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

anon пишет:

паяю норм, с этим проблем нет, а вот с программированием хуже) я именно по этому и не хочу городить аккум, тк думаю что есть более элегантное решение, прост не могу продумать логику.
100 000 циклов это 11 лет.

я предполагаю худший вариант китайского чипа - 50 % ресурса, + еще можно логику придумать равномерной записи по всей EEPROM, тогда ресурс в разы увеличивается - быстрее насос умрет, или как у меня недавно в молнию на БП конденсатор взорвался, благо остальное все живо осталось.

sadman41
Offline
Зарегистрирован: 19.10.2016

Подтверждаю - циклическая запись в EEPROM дает просто адский ресурс. Однако, думаю, что топикстартеру проще взять FRAM и писать туда хоть каждую минуту, если ему паяльник ближе математики.

anon
Offline
Зарегистрирован: 23.05.2013

с кодом я разбирусь, верен заветам Ильича (учиться учиться и еще раз учиться) 
прост меня пнуть нужно в правильную сторону)

anon
Offline
Зарегистрирован: 23.05.2013

элегантное решение может быть не в цикле, а в произвольном выборе дней недели для полива, тк часы возвращают их как цифру. сравниваем и поливаем.

b707
Offline
Зарегистрирован: 26.05.2017

anon пишет:

элегантное решение может быть не в цикле, а в произвольном выборе дней недели для полива, тк часы возвращают их как цифру. сравниваем и поливаем.

элегентное оно только на бумаге. Цветы ничего не знают про дни недели, их нужно поливать каждые 2-3 дня, так что циклом задавать проще.

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

anon пишет:

элегантное решение может быть не в цикле, а в произвольном выборе дней недели для полива, тк часы возвращают их как цифру. сравниваем и поливаем.

угу, произвольно выберется понедельник 22:00 и вторник 07:00 потом через воскресенье 21:00 и получится за две недели цветок засохнет.

повторюсь - все зависит от задачи, под нее уже и решение придумывается.

anon
Offline
Зарегистрирован: 23.05.2013

в идеале мне нужно поливать кустик раз в два- пять дней не опасаясь выключения света и как можно меньше лазия к зеленому другу.  есть уно, мини, нано, часики 1307, релюхи-помпы все дела)

anon
Offline
Зарегистрирован: 23.05.2013

это и понятно, что нужно циклом делать, только как учесть вероятные отключения искричества?

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

anon пишет:

это и понятно, что нужно циклом делать, только как учесть вероятные отключения искричества?

э....так выше же уже обмусолили тему и варианты - нет?

вот полный код (выше кусок) поливалки каждые несколько суток с ежесуточным сохранением в EEPROM, и RTC нафиг не надо.

#include <avr/io.h>
#include <util/delay.h>
#include "EEPROM.h"
#include "tm1637.h"

#define pos_eeprom_pump_sec 0x25
#define pos_eeprom_count_days 0x27
#define pos_eeprom_current_days 0x29

#define period_read_key 100UL
#define min_count_key_down 2 // 0.2 sec 
#define min_count_key_long 20 // 2 sec
#define max_count_key_short 10 // 1 sec
#define max_time_show_options 20000UL // 20 sec

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

// PB0 pin 5 - TM1637_DIO_PIN
#define PB0_OUT sbi(DDRB,PB0)
#define PB0_IN cbi(DDRB,PB0)
#define PB0_HIGH sbi(PORTB,PB0)
#define PB0_LOW cbi(PORTB,PB0)
// PB1 pin 6 - TM1637_CLK_PIN
#define PB1_OUT sbi(DDRB,PB1)
#define PB1_IN cbi(DDRB,PB1)
#define PB1_HIGH sbi(PORTB,PB1)
#define PB1_LOW cbi(PORTB,PB1)
// PB2 pin 7 - relay pump
#define PB2_OUT sbi(DDRB,PB2)
#define PB2_IN cbi(DDRB,PB2)
#define PB2_HIGH sbi(PORTB,PB2)
#define PB2_LOW cbi(PORTB,PB2)
// PB3 pin 2 - key 1
#define PB3_OUT sbi(DDRB,PB3)
#define PB3_IN cbi(DDRB,PB3)
#define PB3_HIGH sbi(PORTB,PB3)
#define PB3_LOW cbi(PORTB,PB3)
#define PB3_READ bitRead(PINB,PB3)
// PB4 pin 3 - key 2
#define PB4_OUT sbi(DDRB,PB4)
#define PB4_IN cbi(DDRB,PB4)
#define PB4_HIGH sbi(PORTB,PB4)
#define PB4_LOW cbi(PORTB,PB4)
#define PB4_READ bitRead(PINB,PB4)

unsigned long key_time;

byte day_counting; // current counting
byte hour_counting = 0; // current counting
byte sec_pumping; // default/minimum 1 sec pump
byte day_period; // default/minimum 1 day pump

byte count_key1_down = 0;
byte mode_key1 = 0; // 0-none 1-short 2-long
boolean key1_is_begin = false;
boolean key1_next = true;

byte count_key2_down = 0;
byte mode_key2 = 0; // 0-none 1-short 2-long
boolean key2_is_begin = false;
boolean key2_next = true;

byte work_mode = 0; // 0-counting 1-options 2-pumping 3-test pump
unsigned long time_options_mode;
unsigned long time_pumping_mode;

byte point_togl = 0; // display point

unsigned long timer_sec = 0; // show display timer
unsigned long timer_hour = 0; // every hour - main timer

void setup() {
  // put your setup code here, to run once:
  PB3_IN; PB3_HIGH; // key 1
  PB4_IN; PB4_HIGH; // key 2
  PB2_OUT; PB2_LOW; // relay OFF
  // load conf from eeprom
  sec_pumping = EEPROM.read(pos_eeprom_pump_sec);
  day_period = EEPROM.read(pos_eeprom_count_days);
  day_counting = EEPROM.read(pos_eeprom_current_days);
  TM1637_init();
  TM1637_set_brightness(6);
}

void loop() {
  // put your main code here, to run repeatedly:
  unsigned long current_millis = millis();
  if ((current_millis - key_time) >= period_read_key) {
    key_time = current_millis;
    // operate key1
    if (PB3_READ == 0) {
      // key1 is down
      if (key1_is_begin) {
        if ((++count_key1_down) >= min_count_key_long) {
          // long click
          key1_is_begin = false;
          count_key1_down = 0;
          mode_key1 = 2;
        }
      } else {
        if (key1_next) {
          key1_is_begin = true;
          key1_next = false;
          count_key1_down = 0;
          mode_key1 = 0;
        }
      }
    } else {
      // key1 is up
      key1_next = true;
      if (key1_is_begin) {
        if  ((count_key1_down >= min_count_key_down) && (count_key1_down <= max_count_key_short)) {
          // short click
          key1_is_begin = false;
          count_key1_down = 0;
          mode_key1 = 1;
        } else {
          // no click
          key1_is_begin = false;
          count_key1_down = 0;
          mode_key1 = 0;
        }
      }
    }
    // end key1
    // operate key2
    if (PB4_READ == 0) {
      // key2 is down
      if (key2_is_begin) {
        if ((++count_key2_down) >= min_count_key_long) {
          // long click
          key2_is_begin = false;
          count_key2_down = 0;
          mode_key2 = 2;
        }
      } else {
        if (key2_next) {
          key2_is_begin = true;
          key2_next = false;
          count_key2_down = 0;
          mode_key2 = 0;
        }
      }
    } else {
      // key2 is up
      key2_next = true;
      if (key2_is_begin) {
        if  ((count_key2_down >= min_count_key_down) && (count_key2_down <= max_count_key_short)) {
          // short click
          key2_is_begin = false;
          count_key2_down = 0;
          mode_key2 = 1;
        } else {
          // no click
          key2_is_begin = false;
          count_key2_down = 0;
          mode_key2 = 0;
        }
      }
    }
    // end key2
  }
  // analize keys
  if ((mode_key1 == 2) && (mode_key2 == 2)) {
    if (work_mode == 0) {
      // enter to options mode
      time_options_mode = current_millis;
      TM1637_clear();
      ShowSecDay();
      work_mode = 1;
    } else {
      if (work_mode == 1) {
        // clear counting
        hour_counting = 0;
        day_counting = 0;
        // save options
        EEPROM.write(pos_eeprom_pump_sec, sec_pumping);
        EEPROM.write(pos_eeprom_count_days, day_period);
        EEPROM.write(pos_eeprom_current_days, day_counting);
        // exit options mode
        TM1637_clear();
        work_mode = 0;
      }
    }
  } else {
    if (mode_key1 == 2) {
      if (work_mode == 1) {
        // save options
        EEPROM.write(pos_eeprom_pump_sec, sec_pumping);
        EEPROM.write(pos_eeprom_count_days, day_period);
        // test pump
        time_pumping_mode = current_millis;
        TM1637_clear();
        work_mode = 3;
      }
    } else {
      if (mode_key1 == 1) {
        if (work_mode == 1) {
          time_options_mode = current_millis;
          // change pump sec
          if ((++sec_pumping) > 9) sec_pumping = 1;
          // show
          ShowSecDay();
        }
      } else {
        if (mode_key2 == 1) {
          if (work_mode == 1) {
            time_options_mode = current_millis;
            // change period pump days
            if ((++day_period) > 9) day_period = 1;
            // show
            ShowSecDay();
          }
        } else {
          // no keys
          if (work_mode == 1) {
            if ((current_millis - time_options_mode) >= max_time_show_options) {
              // save options
              EEPROM.write(pos_eeprom_pump_sec, sec_pumping);
              EEPROM.write(pos_eeprom_count_days, day_period);
              // exit options mode
              TM1637_clear();
              work_mode = 0;
            }
          }
        }
      }
    }
  }
  // end analize keys
  // analize pump mode
  if ((work_mode == 2) || (work_mode == 3)) {
    PB2_HIGH;
    if ((current_millis - time_pumping_mode) >= (sec_pumping * 1000)) {
      time_pumping_mode = current_millis;
      // end pumping
      PB2_LOW;
      TM1637_clear();
      work_mode = 0;
    }
  }
  // end pump mode
  // loop show display
  if ((current_millis - timer_sec) >= 1000UL) {
    timer_sec = current_millis;
    if (point_togl == 0) point_togl = 1; else point_togl = 0;
    TM1637_display_colon(point_togl);
    switch (work_mode) {
      case 0: {
          ShowTimer();
          break;
        }
      case 1: {
          ShowSecDay();
          break;
        }
      case 2: {
          ShowPump();
          break;
        }
      case 3: {
          ShowPump();
          break;
        }
    }
  }
  // timer every hour
  if ((work_mode == 0) || (work_mode == 3)) {
    if ((current_millis - timer_hour) >= 3600000UL) { // for test = 1000UL // by real 1 hour = 3600000UL ms
      timer_hour = current_millis;
      if ((++hour_counting) >= 24) {
        ++day_counting;
        hour_counting = 0;
        if (day_counting >= day_period) {
          // start pump
          time_pumping_mode = current_millis;
          TM1637_clear();
          work_mode = 2;
          day_counting = 0;
        }
        EEPROM.write(pos_eeprom_current_days, day_counting);
      }
    }
    // end hour timer
  }
  // end main loop
  mode_key1 = 0; mode_key2 = 0;
}

void ShowTimer() {
  TM1637_display_digit(TM1637_SET_ADR_00H, day_counting);
  TM1637_display_segments(TM1637_SET_ADR_01H, 0b01000000);
  TM1637_display_digit(TM1637_SET_ADR_02H, (hour_counting / 10));
  TM1637_display_digit(TM1637_SET_ADR_03H, (hour_counting % 10));
}

void ShowSecDay() {
  TM1637_display_segments(TM1637_SET_ADR_00H, 0b01000000);
  TM1637_display_digit(TM1637_SET_ADR_01H, sec_pumping);
  TM1637_display_segments(TM1637_SET_ADR_02H, 0b01000000);
  TM1637_display_digit(TM1637_SET_ADR_03H, day_period);
}

void ShowPump() {
  TM1637_display_segments(TM1637_SET_ADR_00H, 0b01000000);
  TM1637_display_segments(TM1637_SET_ADR_01H, 0b01000000);
  TM1637_display_digit(TM1637_SET_ADR_02H, ((millis() - time_pumping_mode) / 1000) / 10);
  TM1637_display_digit(TM1637_SET_ADR_03H, ((millis() - time_pumping_mode) / 1000) % 10);
}