// ************************************
// **   Кодовый замок  08.10.2017    **
// ************************************
//Дисплей TM1637
#include "SevenSegmentTM1637.h"
#include "SevenSegmentExtended.h"
#include "SevenSegmentFun.h"

//Определяем пины для подключения к плате Arduino
#define TONE_PIN		2		//Пишалка
#define CLK_PIN			3		//Дисплей CLK
#define DIO_PIN			4		//Дисплей DIO
#define MIN5V_PIN		A2		//Управляемый минус 5V (для вкл/вык питания сервы)
#define ZAMOK_PIN		5		//Управление серво
#define LEDRED_PIN		A0		//Красный светодиод
#define LEDGREEN_PIN	A1		//Зеленый светодиод

SevenSegmentFun  display(CLK_PIN, DIO_PIN);

//Ячейки памяти
#include  
#define  EEPROM_Z	0	//Состояние замка
#define  EEPROM_S	1	//Режим звука
#define  EEPROM_LO  2	//Положение сервы в режиме открыто (две ячейки)
#define  EEPROM_LC  4	//Положение сервы в режиме закрыто (две ячейки)
#define  EEPROM_P	6	//Храним пинкод

//Клавиатура
#include  
const byte ROWS = 4; // 4 строки
const byte COLS = 4; // 4 столбца


#include  //Библиотека для управления сервоприводом (сервой)
Servo myServo;

bool sound; //Режим звука
bool zamok; //Состояние замка 0 - открыт
bool dostup = false; // Состояние доступа. Когда истина, доступны функции смены кода и установки положения сервы
#define NMAX 4 //Максимальное количество цифр в коде (при желании можно увеличить)
int  latch_op, latch_cl; //Значение угла для открытой и закрытой защелки 

char pin0[NMAX]; //Правильный код
char pin[NMAX];  //Введенный код
char newpin1[NMAX];  //Новый код, введенный первый раз
char newpin2[NMAX];  //Новый код, введенный второй раз
byte newpin = 0;  //Режим 0 = обычный ввод, 1 и 2 ввод нового пароля, 3 - настройка сервы
byte n; //Сколько цифр уже введено
unsigned long currentMillis; //Текущее значение внутреннего времени
unsigned long previousMillis; //Запомненное значение внутреннего времени
bool flag = true; //Флаг. устанавливается когда начали ввод... и сбрасывается через 5 секунд (если ничего не вводили)
char keys[ROWS][COLS] = { //Коды клавиш
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'}
}; 
byte rowPins[ROWS] = {6, 7, 8, 9}; 
//byte colPins[COLS] = {13, 12, 11, 10};  //Arduino Mini
byte colPins[COLS] = {15, 14, 16, 10}; //Arduino Micro
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS );




void setup(){
  //Инициализация модуля дисплея
  display.begin();            // initializes the display
  display.setBacklight(100);  // set the brightness to 100 %
  
  //Определяем назначение выводов
  pinMode(MIN5V_PIN, OUTPUT);     //Управляемая земля -5V (Когда низкий уровень, питание на сервоприводе нет!
  pinMode(LEDRED_PIN, OUTPUT);    //Красный светодиод (закрыто)
  pinMode(LEDGREEN_PIN, OUTPUT);  //Зеленый светодиод (открыто)
  pinMode(TONE_PIN, OUTPUT);    // Пьеопишалка (звку нажатия клавишь и проигрывание мелодии)
  
  digitalWrite(LEDRED_PIN, HIGH); // включаем светодиоды (просто для контроля)
  digitalWrite(LEDGREEN_PIN, HIGH);

  Serial.begin(9600); // Порт нужен только для контроля с PC
  display.snake(3);
  myServo.attach(ZAMOK_PIN); //Сервопривод замка    // Определяем сервопривод

  //Считываем настройки из памяти

    if (EEPROM.read(EEPROM_S) > 1) // Начальная настройка режима звука
		{
			sound = 1; 
			EEPROM.write(EEPROM_S, sound);
		}
        else sound = EEPROM.read(EEPROM_S);


    //первоначальные настройки сервы  55 зак 105 открыт. Опытным путем. При первой загрузке записать в память, а потом нужно закоментить и перегрузить снова
    // EEPROM.write(EEPROM_LO, lowByte(55));      //Записываем младший байт 
    //EEPROM.write(EEPROM_LO + 1, highByte(55)); //Записываем старший байт 
    //EEPROM.write(EEPROM_LC, lowByte(105));    //Записываем младший байт 
    //EEPROM.write(EEPROM_LC + 1, highByte(105)); //Записываем старший байт 

    // Настройки сервы
    latch_op = (EEPROM.read(EEPROM_LO + 1) << 8 | EEPROM.read(EEPROM_LO));  //Считываем значение открытого положения (два байта, старший и младший)
    latch_cl = (EEPROM.read(EEPROM_LC + 1) << 8 | EEPROM.read(EEPROM_LC));  // Считываем значение закрытого положения (два байта, старший и младший)
	if (EEPROM.read(EEPROM_Z) > 1) { // Начальная настройка состояния замка. По умолчанию - закрыт
		zamok = 1;
		EEPROM.write(EEPROM_Z, zamok);
	}
	else zamok = EEPROM.read(EEPROM_Z); // Если уже есть допустимое значение, то его и используем
	uprzamkom(zamok); //Запускаем процедуру управления замком. На случай сбоев питиания

    Serial.println("Настройки сервы: ");
    Serial.print("Открыто: ");
    Serial.println(latch_op);
    Serial.print("Закрыто: ");
    Serial.println(latch_cl);


    Serial.print("Текущий код: ");    
    for (int i = 0; i < NMAX;i++)  {					// Вынимаем контрольный код из памяти
		pin0[i] = (char)EEPROM.read(EEPROM_P+i);		// Достаем код
		if (((byte)pin0[i] < 47) || ((byte)pin0[i] > 57))  // Начальное заполнение (при первом включении заполняется нулями и код получается "0000"
			{
			pin0[i] = '0';  
			EEPROM.write(EEPROM_P+i, pin0[i]);
			}
		Serial.print(pin0[i]);            // Выводим каждую цифру на экран. Для отладки 
    }
    Serial.println(); 
    play(1);  
}


void loop(){ //Основной цикл
  currentMillis = millis(); // Метка времени (от начала работы контроллера 
  char key = keypad.getKey(); // Считываем код клавиши
  if (key) 
  {
	  flag = true; //Установили флаг (пометили, что начался ввод)
	  Serial.print(key); // Выводим клавишу в порт
    previousMillis = currentMillis; // Запоминаем метку времен 
    switch (key) { 
	//***********************************************************
	case '*': //Если нажата *
      if (sound) sound =  false;// если звук был включен, то выключим 
        else sound = true;  // или наоборот
      EEPROM.write(1, sound); //Запомним в памяти
      Serial.print("sound="); // Выводим на экран PC  (для отладки)
      Serial.println(sound);
      play(3);
      break;
    
    //***********************************************************
	case '#': //Завершен ввод ввод
	  //Если был режим корректировки положения сервы (угол поворота замка), то записываем то, что есть (что было после настройки)
      if (newpin == 3) 
	  { 
        Serial.println();
        Serial.println("Настройки сервы: ");
		display.blink(); //Помигать записанным значением!
        if (zamok) { //Если режим настройки был нажат во время закрытого замка, то настраиваем угол в закрытом состоянии
          Serial.print("Закрыто: ");
          Serial.println(latch_cl);
          EEPROM.write(EEPROM_LC, lowByte(latch_cl));   //Записываем младший байт 
          EEPROM.write(EEPROM_LC + 1, highByte(latch_cl));  //Записываем старший байт 
        }
        else { //записываем угол сервы в открытом состоянии
          Serial.print("Открыто: ");
          Serial.println(latch_op);
          EEPROM.write(EEPROM_LO, lowByte(latch_op));      //Записываем младший байт 
          EEPROM.write(EEPROM_LO + 1, highByte(latch_op)); //Записываем старший байт 
        }
        newpin = 0; 
        n = 0;
        dostup = false; //Закрываем доступ к настройкам
        play(1); // звуковой сигнал
        digitalWrite(LEDGREEN_PIN, LOW);
        digitalWrite(LEDRED_PIN, LOW);
		break; // Выходим из подпрограммы
      }

      if (n == NMAX) { // Если нужное количество символов 

        switch (newpin) { //Если режим смены кода
        case 1: // Если код один раз уже вводился
          newpin = 2; // значит помечаем, как ввод второго раза
          play(2);
          Serial.println("Введите код еще один раз");
          previousMillis = millis(); //Запомнить отметку времени (чтобы ждать следующее нажатие не более 5 с
          n = 0; // Начинаем вводить с начала
          break;

        case 2: // Если код в режиме смены кода введен уже второй раз
			for (int i = 0; i < NMAX;++i) { // Последовательно проверяем
            if (newpin1[i] == newpin2[i]) dostup = true;
            else { 
              dostup = false; 
              break; } //Если хоть одна ошибка - выходим
          }
          if (dostup) { // Если проверка прошла успешно
			  display.blink(); //Помигать записанным значением!
			  play(1); // проигрываем красивую мелодию
            for (int i = 0; i  5000) { // Если прошло 5сек с момента последнего нажатия
    if (flag) { // Если отметка установлена то (это просто для того, чтобы лишний раз не выполнять действия по обнулению
      flag = false;     //Слишком большая пауза. Сбрасываем 
      n = 0;        // Обнуляем число уже введенных цифр
      newpin = 0;     // Отменяем режим ввода нового кода
      dostup = false;
      display.clear();
      digitalWrite(MIN5V_PIN, LOW); //Выключаем питание на сервопривод
      // Далее выключаем оба светодиода... (они включаются в разных случаях, а выключаются через 5 с)
      digitalWrite(LEDGREEN_PIN, LOW); 
      digitalWrite(LEDRED_PIN, LOW);
    }
  }
  else flag = true;


}

//Управление замком
void uprzamkom(bool zamok) {
  if (zamok) {
    if (newpin==0) Serial.println("Замок закрыт!");
    digitalWrite(LEDRED_PIN, HIGH); //Включаем светодиод 
    digitalWrite(MIN5V_PIN, HIGH);  // Включаем питание на сервопривод
    display.clear();
    display.print(latch_cl);
    delay(150);                     //Задержка, чтоб наприжение успело востановится
    myServo.write(latch_cl);        //Поварачиваем на нужный угол (подбирается опытным путем, в зависимости от установки сервы)
  }
  else {
	if (newpin == 0) Serial.println("Замок открыт!");  //Аналогично
    digitalWrite(LEDGREEN_PIN, HIGH);
    digitalWrite(MIN5V_PIN, HIGH);
    display.clear();
    display.print(latch_op);
    delay(150);
    myServo.write(latch_op);
  }

}

//Озвучка событий
void play(byte r) {
  /*
  0 - ошибка
  1 - успех
  2 - настройка сервы
  3 - нейтральное предупреждение
  */

  if (sound) {
    switch (r) {
    case 0:
      tone(TONE_PIN, 500, 1000);
      display.bombTimer(3, 8, 10000, "");
      break;

    case 1:
      tone(TONE_PIN, 700, 150);
      delay(300);
      tone(TONE_PIN, 700, 150);
      delay(300);
      tone(TONE_PIN, 780, 75);
      delay(150);
      tone(TONE_PIN, 700, 75);
      delay(150);
      tone(TONE_PIN, 625, 225);
      delay(300);
      tone(TONE_PIN, 590, 75);
      delay(150);
      tone(TONE_PIN, 520, 75);
      delay(150);
      tone(TONE_PIN, 460, 225);
      delay(300);
      tone(TONE_PIN, 350, 225);
      delay(600);
      break;
    case 2:
      tone(TONE_PIN, 300, 150);
      delay(300);
      tone(TONE_PIN, 300, 150);
      delay(300);
      break;

    default:
      tone(TONE_PIN, 500, 200);
      delay(50);

    }
  }
}
 
void disp(char Disp[]) {
   display.clear();
  for (int i = 0; i