#include <EEPROM.h> 
#include <OneWire.h>
#include <SoftwareSerial.h>

#define Ver F("Eperec  v1.1\n")

OneWire ds(10);                  // датчик температуры подключен к 10 пину, подтяжка 4.7кОм на +.
SoftwareSerial gsm(2, 3);       // модем подключен к 2 и 3 пинам.
#define LED 13             // пин 13. состояние системы.
#define R1 4  // реле 2 (желтый светодиод)
#define R2 5 // реле 1 (синий светодиод)
#define S 7 //ЗАПУСК МОДУЛЯ 300мс
#define on "on"     // вкл
#define off "off"   // выкл
int8_t Guard = 0;   
int8_t ModemID = 0;        //    тип модема
int8_t Firststart = 1;     // первый старт.
int8_t LEDState = LOW;    // Состояние светодиода.
int8_t sendsmscaller = 1;  // разрешение отправки смс.
int StateRelay1=0;
int StateRelay2=0;
uint32_t msAT = 0;
unsigned long rest;

char Bal[4];               // для временного хранения номера баланса.   
char Phone[14];            // для временного хранения номера телефона, номер до 14 цифр.      
String  AT[] = {"ATE0", "AT+CLIP=1", "AT+CMGF=1", "AT+CSCS=\"GSM\"", "AT+CNMI=2,2,0,0,0", "AT+CREG?" ,"ATI"}; // общие АТ команды
#define  AT_M590  F("AT+CUSD=1,\"*100#\"")            // баланс для М590
#define  AT_SIM800_A6  F("AT+CUSD=1,#")     // баланс для А6 и СИМ800

String RingPhone = "";          // номер звонившего.
String AlarmPhone[5];           // номера для отправки смс.
byte Adress[5] = {25, 40, 55, 70, 85}; // ячейки для пяти номеров в ЕЕПРОМ.

int p = 0;

int previousMillis_alarm = 0;
#define interval_led 1000      // для мигания LED, 1 сек.
int previousMillis_led = 0;

/* EEPROM Data:
  № ячейки - значение:
  0 - если записана не 1, то необходимо затереть eeprom и восстановить стандартную конфигурацию.
  3 - SendSMS = 1;     // отправка смс:          1 - вкл.
  10 - Firststars = 1; // первый запуск          1.
  20 - ячейка памяти баланса,
  25, 40, 55, 70, 85 - ячейки памяти телефонов.
  100 - ячейка памяти номера последнего звонившего RingPhone.
*/


void setup() { /// === настройка программы === ///
  Serial.begin(9600); 
  Serial.println(Ver); 
  pinMode(R1, OUTPUT);
  pinMode(S, OUTPUT);
  pinMode(R2, OUTPUT);
  pinMode(LED, OUTPUT);             // светодиод горит во время запуска.
  digitalWrite(S, HIGH);            //даем запуск модема
  delay(300);                       //держим оптрон 300мс
  digitalWrite(S, LOW);           //отключаем оптрон
  InitModem();                      // запускаем инициализация модема.
  eepromconfig();
  Serial.println(eepromtext()); 
  Serial.println(eepromphone()); 
  digitalWrite(LED, LOW);
  digitalWrite(R1, LOW);
  digitalWrite(R2, LOW);
  rest = millis();
}  
                                 
void loop() {  /// === основной цикл программы === ///
  if (millis() - rest > 60000) {
    gsm.println("AT");
  }
  if (rest > millis()) {
    rest = millis();
  }
  if (gsm.available()) {                      // Если модем, что-то отправил...
      sendsmscaller = 1;
      String gval = "";                      // Переменная для хранения ответа модуля
      rest = millis();
      gval = waitgsm();                 // Получаем ответ от модема для анализа
//  Serial.println("gval:" + gval);                  // Если нужно выводим в монитор порта
      if (gval.indexOf(F("+PBREADY")) > -1) InitModem();
      else if (gval.indexOf(F("+CLIP")) > -1) {        // если обнаружен вызов.
        gsm.println(F("ATH"));                 // сбрасываем вызов.
  Serial.println(F("Process RING"));                              
        int ind = gval.indexOf(F("+CLIP: \""));          // обрезаем номер 
//        if (ind >= 0) {                     // Если информация была найдена
        ind += 8;                         // Парсим строку и
        String ring = gval.substring(ind, gval.indexOf("\"", ind));
//        }
  Serial.println(String(F("RingPhone->")) + ring);                            
        if (CheckPhone(ring) == 1) {                 // проверяем номер, если наш.
  Serial.println(F("Master Ring OK!"));
                                
        } else  Serial.println(F("No Master Ring!"));  
      } else if (gval.indexOf(F("+CMT")) > -1) {  // если обнаруженa СМС
  Serial.println(F("Process CMT"));                            
        parsesms(gval);
      } else if(gval.indexOf(F("+CUSD:")) > -1) { // если пришел баланс 
        String bal = "";
        if (ModemID == 3) {
        // команда проверки баланса  AT+CUSD=1,#105#,15
        // анализируем строку 
          int p1 = gval.indexOf("\"");         // начало строки
          int p2 = gval.lastIndexOf("\"");     // конец строки
          gval = gval.substring(p1+1,p2);
        Serial.println(F("Input string:"));    // пишем в порт пришедшую строку
        Serial.println(gval);
  Decode7bit(gval, bal);
        Serial.println(F("Decode string:"));   // пишем в порт конвертированную строку
        } else if (ModemID == 1 || 2) {
          bal = String(gval.substring(gval.indexOf(",\"") + 2,gval.indexOf("\",")));  //
        }
        Serial.println(bal);
sms("SIM " + bal + "\n", 0);    // смс на последний звонивший
        gsm.println(F("AT+CUSD=0"));
         } 
  } else if (Serial.available()) {           // если в мониторе порта что-то ввели
      String sval = "";
      while (Serial.available()) {         // сохраняем входную строку в переменную val.
        char ch = Serial.read();
        sval += char(ch);                   // собираем принятые символы в строку
        delay(3);
        } 
        sendsmscaller = 0;
  Komand(sval);                        // запускаем консольную программу
        sendsmscaller = 1;
    }
}

String waitgsm() {                         // Функция ожидания ответа и возврата полученного результата
  String gsmval = "";                            // Переменная для хранения результата
  uint32_t _timeout = millis() + 10000;             // Переменная для отслеживания таймаута (10 секунд)
  while (!gsm.available() && (millis() < _timeout))  {}; // Ждем ответа 5 секунд, если пришел ответ или наступил таймаут, то...
  if (gsm.available()) {     // если GSM модем что-то послал.
    while (gsm.available()) {  // сохраняем входную строку в переменную val.
      char ch = gsm.read();
      gsmval += char(ch);              // собираем принятые символы в строку
      delay(3);
    } 
  } else {                                      // Если пришел таймаут, то...
    Serial.println(F("Timeout..."));               // ... оповещаем об этом и...
  }
  return gsmval;                                 // ... возвращаем результат. Пусто, если проблема
}

void parsesms(String msg) {
  String msgheader = "";
  String msgsms    = "";
  String msgphone  = "";
  msg = msg.substring(msg.indexOf(F("+CMT: ")));
  msgheader = msg.substring(0,msg.indexOf("\r"));            // Выдергиваем телефон
  msgsms = msg.substring(msgheader.length() + 2);
  msgsms = msgsms.substring(0, msgsms.lastIndexOf("OK"));  // Выдергиваем текст SMS
  msgsms.trim();
  int Index = msgheader.indexOf(F("+CMT:"));
  Index += 8;                         // Парсим строку и
  msgphone = msgheader.substring(Index, msgheader.indexOf("\"", Index));
 
  Serial.println(String(F("Phone: ")) + msgphone);    // Выводим номер телефона
  Serial.println(String(F("Message: ")) + msgsms);    // Выводим текст SMS
     if (CheckPhone(msgphone) == 1) {      // если СМС от хозяина
  Serial.println(F("Master SMS OK!"));
  Komand(msgsms);
     } else  Serial.println(F("No Master SMS!"));
}

int CheckPhone(String phone) { /// === проверка телефона === ///
  if (Firststart == 0) {                        // если не первый старт
    for (int i = 0; i < 5; i++) {
      if (phone == AlarmPhone[i] && phone != 0) { // если есть вызов, и он наш
        phone = AlarmPhone[i];              // запоминаем его.
        RingPhone = AlarmPhone[i];              // запоминаем его.
        RingPhone.toCharArray(Phone, 14);   
        EEPROM.put(100, Phone);                 // записываем RingPhone в память. 
//  Serial.println(String(F("RingPhone ")) + RingPhone);          
        return 1;                               // возвращаем 1 - номер наш!
      } 
    } return 0;                                 // возвращаем 0 - номер не наш!
  } else if (Firststart == 1) {                 // иначе (если первый старт)
// первый звонивший добавляется как основной мастер номер для управления сигнализацией
        phone.toCharArray(Phone, 14);
        EEPROM.put(25, Phone);                    // пишем номер в EEPROM
        EEPROM.put(100, Phone);                   // записываем RingPhone в память. 
        AlarmPhone[0] = Phone;                    // запишем
        RingPhone = Phone;                        // запоминаем его.
        EEPROM.update(10, 0);                     // обновляем Firststart
        Firststart = 0;                           // пишем 0
  Serial.println(String(F("Firststart:")) + Firststart);
sms(String(F("Master0:")) + phone + " Ok\n", 0);     // смс на звонивший
//  приходит смс в виде: " Master0:7914******* Ok "
    } return 0;                               
}

void eepromconfig() { // == конфигурирование == //
    if (EEPROM.read(0) != 1) {           // если записана не 1
      for (int i = 0 ; i < 512 ; i++) {  // переписываем все ячейки
        EEPROM.write(i, 0);
      }                      // и записываем:
     
      EEPROM.update(3, 1);   // SendSMS     смс вкл.
      EEPROM.update(10, 1);  // Firststart  первый старт!.
      EEPROM.update(20, 0);  // баланс, записываем массив в EEPROM 
      EEPROM.update(0, 1);   // 
  Serial.println(F("EEPROM != 1/ RESET!")); 
    } if (EEPROM.read(0) == 1) {
     
      Firststart = EEPROM.read(10);  //
    }
}

String eepromtext() { /// === состояние системы === ///
   String RELAY1 = "";         // текущее состояние системы.
   String RELAY2 = "";         // текущее состояние вызов.

    if (StateRelay1 == 1) RELAY1 = F("Relay1=ON\n"); else RELAY1 = F("Relay1=OFF\n");
    if (StateRelay2 == 1) RELAY2 = F("Relay2=ON\n"); else RELAY2 = F("Relay2=OFF\n");
    String LastEvent = RELAY1 + RELAY2;             // полное состояние системы и датчиков.
    return LastEvent;
}

String eepromphone() { /// === чтение мастер-номеров из EEPROM === ///
    for (int i = 0; i < 5 ; i++) {
      EEPROM.get(Adress[i], Phone);        // считываем мастер-номера 
      AlarmPhone[i] = Phone;
    }
    String numbers = "0 " + AlarmPhone[0] + "\n1 " + AlarmPhone[1] + "\n2 " + AlarmPhone[2] + "\n3 " + AlarmPhone[3] + "\n4 " + AlarmPhone[4] + "\n";
    EEPROM.get(100, Phone);                // считываем номер последнего звонившего
    RingPhone = Phone;
    String ring = String(F("Ring ")) + RingPhone + "\n";
    EEPROM.get(20, Bal);                   // считываем номер баланса
    String bal = String(F("Balance *")) + Bal + "#\n"; 
    String EEPhone = numbers + ring + bal;
    return EEPhone;
}

void Komand(String kom) { /// === мастер смс === ///
  Serial.println("Kom->" + kom);
  kom.toLowerCase();
  if (RingPhone == AlarmPhone[0] || sendsmscaller == 0) {        // команды выполняются только с 0 мастер номера
    if (kom.indexOf(F("Reset")) > -1)    { // полный сброс ЕЕПРОМ
      sms(F("System Reset!\n"), 0); // смс на последний номер
      EEPROM.write(0, 0);
  eepromconfig();
  Serial.println(eepromtext());
  Serial.println(eepromphone());
    } else if ((p = kom.indexOf(F("master"))) > -1)  { // добавление мастер-номера
      master(kom.substring(p + 6, p + 7).toInt(), kom.substring(p + 8, kom.indexOf("\n", p + 8)));
///// sms типа: " master1:7914*******" - ячейка + номер
    } else if ((p = kom.indexOf(F("balance:"))) > -1) { // добавление номера баланса
      balnum(kom.substring(p + 8, p + 11));
///// sms типа: " balance:100 " - номер баланса
    } else if ((p = kom.indexOf(F("dell:"))) > -1) { // удаление номера из ячейки     
      dellphone(kom.substring(p + 5, p + 6).toInt());
///// sms типа: " dell:1 " - удалить номер из ячейки 1
    } else if (kom.indexOf(F("phone")) > -1)     { // инфо о номерах системы
sms(eepromphone(), 0); // смс на последний номер
    }
  }  
  if ((kom.indexOf(F("info")) > -1) || (kom.indexOf(F("?")) > -1)) {     // инфо о состоянии системы
  eepromconfig();
sms(eepromtext(), 0); // смс на последний номер
  } else if (kom.indexOf(F("money")) > -1) {   // запрос баланса
  balance();
  } 

  else if (kom.indexOf(F("1on")) > -1) {   // включение реле 1
  digitalWrite(R1, HIGH);
  StateRelay1 =1;
  smsRelay(String(F("Relay1: On"))  + F(" \n"), 0); // смс на 0 номер
 
 
  } 

  else if (kom.indexOf(F("1off")) > -1) {   // выключение реле 1
    digitalWrite(R1, LOW);
    StateRelay1 =0;
  smsRelay(String(F("Relay1: Off"))  + F(" \n"), 0); // смс на 0 номер
 
  } 

  else if (kom.indexOf(F("2on")) > -1) {   // включение реле 2
  digitalWrite(R2, HIGH);
  StateRelay2 =1;
  smsRelay(String(F("Relay2: On"))  + F(" \n"), 0); // смс на 0 номер
  
  } 

  else if (kom.indexOf(F("2off")) > -1) {   // выключение реле 2
  digitalWrite(R2, LOW);
  StateRelay2 =0;
  smsRelay(String(F("Relay2: Off"))  + F(" \n"), 0); // смс на 0 номер
  
  } 

  else if (kom.indexOf(F("on")) > -1) {   // включение 1 и 2 реле
  digitalWrite(R1, HIGH);
  digitalWrite(R2, HIGH);
  StateRelay1 =1;
   StateRelay2 =1;
  smsRelay(String(F("Relay1 Relay2: ON")) + F(" \n"), 0); // смс на 0 номер
  
  } 

  else if (kom.indexOf(F("off")) > -1) {   // выключение 1 и 2 реле
  digitalWrite(R1, LOW);
  digitalWrite(R2, LOW);
  StateRelay1 =0;
   StateRelay2 =0;
  smsRelay(String(F("Relay1 Relay2: OFF")) + F(" \n"), 0); // смс на 0 номер
  
  } 
 
  gsm.println(F("AT+CMGD=1,4")); // стираем смс
}





void balance() { /// === проверка баланса сим-карты === ///
  EEPROM.get(20, Bal);        // считываем массив символов
  Serial.println(String("Modem ") + ModemID);
  delay(1000);                // ждем секунду, т.к. вылетает с ошибкой
  if (ModemID == 1) {
    gsm.println(AT_M590);
    delay(100);
  gsm.println(String(F("ATD#")) + Bal + "#;");
  } else if (ModemID == 2 || ModemID == 3) gsm.println(String(AT_SIM800_A6) + Bal + "#,15");
  Serial.println(String(F("Balance#")) + Bal + "#");
}


int master (int i, String num) { /// === добавление мастер-номеров === ///
    if (i < 5 && i != 0) {
      num.toCharArray(Phone, 14); // конвертируем
      AlarmPhone[i] = num;        // пишем номер  
      EEPROM.put(Adress[i], Phone);           // записываем массив в EEPROM 
sms(String(F("Master")) + i + ":" + num + " add\n", 0); // смс на 0 номер.
    }// else Serial.println(F("MAX number 4!"));             // 4 номера максимум! 
    // 0 номер можно удалить только через RESET!!!
    return 1;                             
}                              



int dellphone (int i) { /// === удаление мастар-номеров === ///
     if (i < 5 && i != 0) {
sms(String(F("Master")) + i + ":" + AlarmPhone[i] + " dell\n", 0); // смс на 0 номер
       EEPROM.put(Adress[i], 0);
       AlarmPhone[i] = "";   // обнулим номер
  } 
  return 1;                          
}



int balnum (String num) { /// === добавляем номер баланса === ///
    num.toCharArray(Bal, 4);   // конвертируем
    EEPROM.put(20, Bal);           // записываем массив в EEPROM 
sms(String(F("Balance:*")) + num + F("# Ok\n"), 0); // смс на 0 номер
    return 1;                             
}

String TempC() {      /// === измеряем температуру === ///
  String temp;
    byte data[2];
    ds.reset();
    ds.write(0xCC);
    ds.write(0x44);
    delay(750);
    ds.reset();
    ds.write(0xCC);
    ds.write(0xBE);
    data[0] = ds.read();
    data[1] = ds.read();
    int Temp = (data[1] << 8) + data[0];
    Temp = Temp >> 4;
    temp = String(F("Temp=")) + Temp;
  return temp;
}





void Decode7bit(String &instr, String &outstr) { /// === декодирование баланса === ///
  byte reminder = 0;
  int bitstate = 7;
  for (unsigned int i = 0; i < instr.length(); i++) {
    byte b = instr[i];
    byte bb = b << (7 - bitstate);
    char c = (bb + reminder) & 0x7F;
    outstr += c;
    reminder = b >> bitstate;
    bitstate--;
    if (bitstate == 0) {
      char c = reminder;
      outstr += c;
      reminder = 0;
      bitstate = 7;
    }
  }
}







void InitModem() { /// === инициализация модема === ///
  gsm.begin(57600);  //Скорость порта для связи Arduino с GSM модулем
  waitConnect();
  String val = sendATCommand(F("ATI"), true);
    if (val.indexOf(F("M590")) > -1) ModemID = 1;
    else if (val.indexOf(F("SIM800")) > -1) ModemID = 2;
    else if (val.indexOf("A6") > -1) ModemID = 3;
    Serial.println(String(F("ModemID ")) + ModemID + "\n");
    for (int i = 0; i < 6; i++) {
       sendATCommand(AT[i], true);         // включить АОН
    }
}

String sendATCommand(String cmd, bool waiting) {
  String atval = "";                            // Переменная для хранения результата
  Serial.println(cmd);                          // Дублируем команду в монитор порта
  gsm.println(cmd);                             // Отправляем команду модулю
//  if (waiting) {                                // Если необходимо дождаться ответа...
    atval = waitgsm();                          // ... ждем, когда будет передан ответ
    Serial.println(atval);                      // Дублируем ответ в монитор порта
//  }
  return atval;                                 // Возвращаем результат. Пусто, если проблема
}

void waitConnect() {  // запуск модема
  int countok = 0;
  uint32_t msAT = 0;
  String str; 
  while(countok < 5) {                      //ждём пять ОК  
    uint32_t ms = millis();
    if(( ms - msAT ) > 500 || ms < msAT ) { // Событие срабатывающее каждые 500 мс 
      msAT = ms;
      Serial.println(">>AT");
      gsm.println("AT");                    // посылаем команду
      str = "";
      delay(100);                           // ждём ответа
      str = waitgsm();
      if (str.indexOf("OK") > -1) {
        Serial.println("OK"); 
        countok++;
      } else countok = 0;
    }
  }  
}


void sms(String text, byte sendnum) { /// === отправка СМС === ///
    String phone = "";
    text = text + TempC();
    if (sendsmscaller == 1 && sendnum == 0) {                  // отправка смс на номер звонившего
      phone = RingPhone;
//      delay(500);
  sendATCommand("AT+CMGS=\"" + phone + "\"", true);             // Переходим в режим ввода текстового сообщения
  sendATCommand(text + (String)((char)26), true);   // После текста отправляем перенос строки и Ctrl+Z      Serial.println(String(F("SMS send=>")) + phone); 
  Serial.println(String(F("SMS sended=>")) + phone + "\n" + text + "\n"); 
    } else if (sendsmscaller == 1 && sendnum == 1) {                  // отправка смс на все номера
      for (int i = 0; i < 4 ; i++) {        
        phone = AlarmPhone[i];
        if (phone != 0) {
//      delay(500);
  sendATCommand("AT+CMGS=\"" + phone + "\"", true);             // Переходим в режим ввода текстового сообщения
  sendATCommand(text + (String)((char)26), true);   // После текста отправляем перенос строки и Ctrl+Z      Serial.println(String(F("SMS send=>")) + phone); 
  Serial.println(String(F("SMS sended=>")) + phone + "\n" + text + "\n"); 
//      delay(2000);
        }
      }
    } else if (sendsmscaller == 0) Serial.println("text:\n" + text + "\n");
}


void smsRelay(String text, byte sendnum) { /// === отправка СМС === ///
    String phone = "";
    text = text;
    if (sendsmscaller == 1 && sendnum == 0) {                  // отправка смс на номер звонившего
      phone = RingPhone;
//      delay(500);
  sendATCommand("AT+CMGS=\"" + phone + "\"", true);             // Переходим в режим ввода текстового сообщения
  sendATCommand(text + (String)((char)26), true);   // После текста отправляем перенос строки и Ctrl+Z      Serial.println(String(F("SMS send=>")) + phone); 
  Serial.println(String(F("SMS sended=>")) + phone + "\n" + text + "\n"); 
    } else if (sendsmscaller == 1 && sendnum == 1) {                  // отправка смс на все номера
      for (int i = 0; i < 4 ; i++) {        
        phone = AlarmPhone[i];
        if (phone != 0) {
//      delay(500);
  sendATCommand("AT+CMGS=\"" + phone + "\"", true);             // Переходим в режим ввода текстового сообщения
  sendATCommand(text + (String)((char)26), true);   // После текста отправляем перенос строки и Ctrl+Z      Serial.println(String(F("SMS send=>")) + phone); 
  Serial.println(String(F("SMS sended=>")) + phone + "\n" + text + "\n"); 
//      delay(2000);
        }
      }
    } else if (sendsmscaller == 0) Serial.println("text:\n" + text + "\n");
}
