/*Ereignismelder.ino, Stand 15.12.2017

Parameter der Alarmanlagen und Haustürklingel aufs Telefon
Aufbau config.txt Datei auf der SD Karte:
1.) Rufnummer bei Haustürklingel:                          <**9>        ( 1) rufalarm[0]
    2.) Dauerklingeln in Sekunden (dreistellig):           <008>        ( 3) t_eingang[0]
    3.) wav-Datei für Ansage                               <klingel.wav>( 5) ansage[0]
4.) Rufnummer bei Eingang_C:                               <**9>        ( 7) rufalarm[1]
   5.) Dauerklingeln in Sekunden (dreistellig):            <008>        ( 9) t_eingang[1]
   6.) wav-Datei für Ansage                                <klingel.wav>(11) ansage[1]
7.) Rufnummer bei Eingang_D:                               <**9>        (13) rufalarm[2]
   8.) Dauerklingeln in Sekunden (dreistellig):            <008>        (15) t_eingang[2]
   9.) wav-Datei für Ansage                                <klingel.wav>(17) ansage[2]
10.) Verzögerung Alarm beim Einschalten (dreistellig):     <200>        (19) t_verzoegerung
11.) Dauer Alarm auf dem Piezosummer in Sekunden(3-stellig):<100>       (21) t_piezo
12.) Verzögerung Alarm Haustür (3-stellig in Sekunden):    <015>        (23) t_haustuer
13.) Dauer des Alarm Anrufs (dreistellig in Sekunden):     <100>        (25) t_anrufdauer
14.) 1. RufNr bei Alarm (max. 10-stellig):                 <0178xyz>    (27) rufalarm[3][11]
15.) 2. RufNr bei Alarm (max. 10-stellig):                 <0178abc>    (29) rufalarm[4][11]
16.) 3. RufNr bei Alarm (max. 10-stellig):                 <0171defgh>  (31) rufalarm[5][11]*/
#define Alarmkreis1  17   //A3 verzögerter Alarm (Haustür)
#define Alarmkreis2  14   //A0 unverzögerter Alarm 
#define PIR          16   //A2 PIR Sensor
#define Taster       15   //A1 Alarmanlage ein- und ausschalten
#define Klingel       4   //D4 Haustürklingel über Optokoppler
#define LED           7   //D7 Kontroll LED
#define Kameras       2   //D2 optional: Impuls schaltet Videocameras an und aus
#define Piezo         3   //D3 Piezoalrmgeber
#define Relais        8   //D8 "abnehmen Telefonhörer"
#define Ton           9   //D9 wav-Tonausgabe
#define chipSelect   10   //D10 für die SD Karte notwendig
#define Eingang_C     5   //D5 Eingang C
#define Eingang_D     6   //D6 Eingang C
#define DEBUG        true //false: Debug ist ausgeschaltet, true: ist eingeschaltet
#include <avr/pgmspace.h> // konstante Ausdrücke in den ROM, nicht in den RAM Funktion F
#include <SPI.h>
#include <SD.h>
#include <TMRpcm.h>       //  für wav-Datei Wiedergabe
TMRpcm tmrpcm;            // create an object for use in this sketch
#include <SPI.h>
//#include <LiquidCrystal.h>
//LiquidCrystal lcd(2,3,4,5,6,7); //2=RS; 3=Enable, 4..7=D4..D7
//*****************************************************************************************************
unsigned long t_eingang[3],   // Dauer des Anrufs "Haustuer", Eingang C+D
              t_verzoegerung, // Einschaltverzoegerung Alarmanlage
              t_piezo,        // Dauer des Alarms der Alarmanlage
              t_haustuer,     // Totzeit, die Zeit die bleibt die Alarmanlage zu deaktivieren
              t_anrufdauer,   //Dauer des Anrufs bei Alarm
              t_einverz_ti, 
              led_blink_ti, 
              t_alarmkreis1,
              t_rufdauer, 
              alarm_ti, 
              millissav[5];
int     flag1         =0,
        led_blink_cnt =0,
        lfd_rufnr     =3,
        eingang[]     ={Klingel, Eingang_C, Eingang_D}; 
char    zeichen,
        rufhaustuer[5],
        rufalarm[6][14],                         // Speicher für 6 Telefonnummern
        ansage[3][13];                           // Speicher für 3 Dateinamen mit Ansagetexten
boolean eingang_fl[]      ={false,false,false},  // jede Menge Flags um Stati zu speichern,
        eingang_fl1[]     ={false,false,false},  //...
        ta_gedrueckt      =false,                //...
        ta_fl             =false,                //...
        alarm_fl          =false,                //...
        t_einverz_fl      =false,                //...
        led_blink_fl      =false,                //...
        alarmkreis1_fl    =false,                //...
        t_alarmkreis1_fl  =false,                //...
        t_alarmverz_fl    =false,                //...
        rufdauer_fl       =false,                //...
        alarmti_fl        =false,                //...
        sd_fault_fl       =false,                //...
        blink_on_fl       =false,                //...
        piezo_fl          =false,                // da das Programm im "Polling"-Modus laeuft.
        portwert[7];
//*****************************************************************************************************
void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.println(F("Ereignismelder, Stand 15.12.2017"));
  tmrpcm.speakerPin = Ton; //5,6,11 or 46 on Mega, 9 on Uno, Nano, etc
  pinMode(Alarmkreis1, INPUT_PULLUP); // Pullup aktiviert den internen Pullup-Widerstand des
  pinMode(Alarmkreis2, INPUT_PULLUP); // Atmega 328, die Beschaltung mit den externen 
  pinMode(PIR,         INPUT_PULLUP); // 10K Widerstaenden ist somit eigentlich nicht noetig,
  pinMode(Taster,      INPUT_PULLUP); // schadet aber auch nicht
  pinMode(Klingel,     INPUT_PULLUP);
  pinMode(Eingang_C,   INPUT_PULLUP);
  pinMode(Eingang_D,   INPUT_PULLUP);
  pinMode(LED,         OUTPUT);
  pinMode(Kameras,     OUTPUT);
  pinMode(Piezo,       OUTPUT);
  pinMode(Relais,      OUTPUT);
  digitalWrite(LED,0);
  digitalWrite(Kameras,0);
  digitalWrite(Piezo,0);
  digitalWrite(Relais,0);
  if(config_von_sd()==false) sd_fault_fl=true;
  //lcd.begin(8,2);
  //lcd.home();
  //lcd.print(F("Version "));
  //lcd.setCursor(0,1);
  //lcd.print(F("15.12.17"));
  }
//*****************************************************************************************************
void loop() {
  poll();
}
//*****************************************************************************************************
void poll() {
  int a1, a2, pp;
  if(sd_fault_fl==true) blinken(4); //wenn die SD-Karte nicht funktioniert keine weiteren Aktionen
  else {
    if(taster_alarm_ein_aus()==true) {  //Alarmanlage ist eingeschaltet
      if(t_einverz()==false) {  // Alarmanlage ist scharf
        digitalWrite(Kameras,1);
        if(alarmkr1()==true||entstoerread(Alarmkreis2)==HIGH||entstoerread(PIR)==HIGH) alarm_fl=true;
        if(alarm_fl==true) alarm();
      }
    }
    for(int e=0;e<=2;e++) {             //abfragen der Eingaenge "Haustürklingel, Eingang_C, EIngang_D
      if(entstoerread(eingang[e])==LOW&&alarm_fl==false) {//bei ausgelöstem Alarm keine Haustürklingel
        if(eingang_fl1[e]==false) {
          eingang_fl[e]=eingang_fl1[e]=true;
          delay(100);
        }
      }
      else eingang_fl1[e]=false; //um mehr als ein Haustürklingeln auszulösen, muss der Klingelknopf 
                                //zwischenzeitlich auf off sein
      if(eingang_fl[e]==true) if(wahl(rufalarm[e],t_eingang[e],ansage[e])==true) eingang_fl[e]=false;
    }
  }  
}
//*****************************************************************************************************
boolean taster_alarm_ein_aus() {
  if(entstoerread(Taster)==LOW && ta_gedrueckt==false) {
    ta_gedrueckt=true;
    if(ta_fl==false) {                            // in ta_fl wird gespeichert ob ein oder aus Staus,
      ta_fl=true;                                 // 1x drücken=ein, nochmal druecken=aus usw.
      if (DEBUG) Serial.println(F("Alarm ein "));
      return(ta_fl);
    }
    else {                         //Alarmanlage wird ausgeschaltet und alles moegliche rueckgesetzt
      ta_fl=false;
      t_einverz_fl=false;
      alarm_fl=false;
      alarmti_fl=false;
      alarmkreis1_fl=false;
      t_alarmkreis1_fl=false;
      lfd_rufnr=3;
      piezo_fl=false;
      digitalWrite(Piezo,0);
      digitalWrite(Relais,0);
      digitalWrite(Kameras,0);
      blinken(0);
      blink_on_fl=false;
      tmrpcm.stopPlayback();
      rufdauer_fl=false;
      if (DEBUG) Serial.println(F("Alarm aus")); 
      return(ta_fl);
    }
  }
  if(entstoerread(Taster)==HIGH && ta_gedrueckt==true) { //wenn der Taster wieder losgelassen wird ...
    ta_gedrueckt=false;
  }
  return(ta_fl);
}
//*****************************************************************************************************
boolean t_einverz() {
  if(t_einverz_fl==false) {
    t_einverz_fl=true;
    millisstart(0);
    t_einverz_ti=millisdauer(0);
  }
  if(millisdauer(0)-t_einverz_ti >t_verzoegerung) { 
    blinken(1);
    return false;
  }
  else {
    blinken(2);
    return true;
  }
}
//*****************************************************************************************************
boolean alarmkr1() {
  if(entstoerread(Alarmkreis1)==HIGH) alarmkreis1_fl=true;
  if(alarmkreis1_fl==true) {
    if(t_alarmkreis1_fl==false) {
      t_alarmkreis1_fl=true;
      millisstart(1);
      t_alarmkreis1=millisdauer(1);
    }
    if(millisdauer(1)-t_alarmkreis1 >t_haustuer) return true;
  }
  return false;
}
//*****************************************************************************************************
void blinken(int w){ //LED bei: w=0 aus, w=1 an, w=2 blinkt, w=3 blinkt schnell/lanmgsam, w=4 blitzt
  int tempo_on=650; //Blinkrhytmus in ms
  switch(w) {
  case 0: digitalWrite(LED,0); 
          return;
  case 1: if(blink_on_fl==false) {
            blink_on_fl=true;
            digitalWrite(LED,1); 
         }
         return;
  case 2: if(entstoerread(Alarmkreis1)==HIGH||entstoerread(Alarmkreis2)==HIGH||entstoerread(PIR)==HIGH) 
              tempo_on=100;           // wenn Alarmkreise O.K. blinkt 650ms on, 130ms off
          led_blink(tempo_on,130);    // wenn Alarmkreise nicht O.K.  100ms on, 130ms off
          return;
  case 3: if(millis()% 4000 < 800) led_blink(100,120); // blinkt unregelmaessig, = Alarmausloesung
          else led_blink(80,650); 
          return;
  case 4: led_blink(10,1000);           // LED "blitz" nur kurz auf, verwendet bei SD-Karten Fehler
          return;
  }
}
//*****************************************************************************************************
void led_blink(int tempo, int tempo_off) {
  if(led_blink_fl==false) {
    led_blink_fl=true;
    millisstart(2);
    led_blink_ti=millisdauer(2);
  }
  if(digitalRead(LED)==0) tempo=tempo_off;
  if(millisdauer(2)-led_blink_ti>tempo) {
    digitalWrite(LED,!digitalRead(LED));
    led_blink_fl=false;
  }
}
//*****************************************************************************************************
void alarm() { //Funktion löst für die definierte Zeit Alarm ausund wählt die Telefonnummern
  if(alarmti_fl==false) {
    alarmti_fl=true;
    millisstart(3);
    alarm_ti=millisdauer(3);
  }
  if(millisdauer(3)-alarm_ti < t_piezo) { piezo_fl=true; digitalWrite(Piezo,1);}
  else {
    piezo_fl=false;
    digitalWrite(Piezo,0);
  }
  blinken(3);
  for(int e=0; e<=2; e++) {
    if(eingang_fl[e]==true) {    //weiterer Eingang aktiv bei Alarmauslösung?
      tmrpcm.stopPlayback();
      digitalWrite(Relais,0); //dann mögliches Telefongespräch beenden ...
      delay(1000);
      rufdauer_fl=false;
      eingang_fl[e]=false;
   }
  }
  if(lfd_rufnr<=5) {
     if(wahl(rufalarm[lfd_rufnr], t_anrufdauer, "alarm.wav")==true) {
      lfd_rufnr++;
      delay(1000);
     }
  }
}
//*****************************************************************************************************
boolean wahl(char nr[], unsigned long rufdauer, char nachricht[20]) { //wählt die Telefonnummer in nr
  int n=0; char ziffer[]="DTMF_X.wav";//X ist Platzhalter und wird in der while-Schleife ersetzt
  if(rufdauer_fl==false) {
    if (DEBUG) {Serial.print(lfd_rufnr+1);Serial.print(F(". Rufnummer wird gewaehlt: "));
              Serial.print(nr);Serial.print(F(" Dauer: "));Serial.println(rufdauer/1000);}
    rufdauer_fl=true;
    millisstart(4);
    t_rufdauer=millisdauer(4); 
    digitalWrite(Relais,1);                //Hoerer abheben
    delay(800);                            //800 ms warten, jetzt sollte der Waehlton zu hören sein
    while(nr[n]!='\0') {
      if(nr[n]=='*')   ziffer[5]='S';
      else {
        if(nr[n]=='#') ziffer[5]='P';
        else           ziffer[5]=nr[n];
      }
      if(piezo_fl==true) digitalWrite(Piezo,0);//wenn Piezosummer an,gibt es beim Waehlen Störungen
      //if (DEBUG) {Serial.print(ziffer);Serial.println(F(" wav-Datei wird wiedergegeben"));}
      tmrpcm.play(ziffer);         //Datei deren Name in Ziffer steht wird als wav-Datei ausgegeben
      if(!tmrpcm.isPlaying()) {
        sd_fault_fl=true;                           // keine Wiedergabe von SD-Karte, FEHLER!!! 
        digitalWrite(Relais,0);                     // Hoerer auflegen
      }
      delay(130);                                  // 130 milliSekunden Wiedergabe
      tmrpcm.stopPlayback();
      if(piezo_fl==true) digitalWrite(Piezo,1);
      delay(80);                                   // 80 milliSekunden Pause zwischen den Waehltoenen
      n++;
    }
    tmrpcm.play(nachricht);
    if(!tmrpcm.isPlaying()) {
        sd_fault_fl=true;                          // keine Wiedergabe von SD-Karte, FEHLER!!! 
        digitalWrite(Relais,0);                    // Hoerer auflegen
    }
  }
  if(millisdauer(4)-t_rufdauer > rufdauer) {
    tmrpcm.stopPlayback();
    digitalWrite(Relais,0);                        // Hoerer auflegen
    delay(2000);
    rufdauer_fl=false;
    return true;
  }
  else return false;
}
//*****************************************************************************************************
boolean config_von_sd() { //liest die Konfiguration von der SD-Karte aus der Datei "config.txt" ein
  int j;
  if (DEBUG) Serial.print(F("Initializing SD card..."));
  if (!SD.begin(chipSelect)) {
    if (DEBUG) Serial.println(F("Card failed, or not present"));
    return false;
  }
  if (DEBUG) Serial.println(F("card initialized."));
  File config_txt = SD.open("config.txt");
  if (config_txt) {
    while (config_txt.available()) {
      zeichen = config_txt.read();
      if (zeichen == '<' ) flag1++;
      if (flag1 % 2 != 0) {
        switch (flag1) {
          case 1: //+++++++++++++++++++++++++ Eingang Klingel +++++++++++++
            for (j = 0; j < 11; j++) if ((rufalarm[0][j] = config_txt.read()) == '>') break;
            rufalarm[0][j] = '\0';
            if (DEBUG) {Serial.print(F("rufalarm[0]:  ")); Serial.println(rufalarm[0]);}
            break;
          case 3:
            t_eingang[0] = long ((config_txt.read() & 0x0F) * 100 + (config_txt.read() & 0x0F) * 10 + (config_txt.read() & 0x0F) * 1)* 1000;
            if (DEBUG) {Serial.print(F("t_eingang[0]= ")); Serial.println(t_eingang[0]);}
            break;
          case 5:
            for (j = 0; j <= 12; j++) if ((ansage[0][j] = config_txt.read()) == '>') break;
            ansage[0][j] = '\0';
            if (DEBUG) {Serial.print(F("ansage[0]:  ")); Serial.println(ansage[0]);}
            break;
          case 7: //+++++++++++++++++++++++++ Eingang_C++++++++++++++++++++
            for (j = 0; j < 11; j++) if ((rufalarm[1][j] = config_txt.read()) == '>') break;
            rufalarm[1][j] = '\0';
            if (DEBUG) {Serial.print(F("rufalarm[1]:  ")); Serial.println(rufalarm[1]);}
            break;
          case 9:
            t_eingang[1] = long ((config_txt.read() & 0x0F) * 100 + (config_txt.read() & 0x0F) * 10 + (config_txt.read() & 0x0F) * 1)* 1000;
            if (DEBUG) {Serial.print(F("t_eingang[1]= ")); Serial.println(t_eingang[1]);}
            break;
          case 11:
            for (j = 0; j <= 12; j++) if ((ansage[1][j] = config_txt.read()) == '>') break;
            ansage[1][j] = '\0';
            if (DEBUG) {Serial.print(F("ansage[1]:  ")); Serial.println(ansage[1]);}
            break;
          case 13: //+++++++++++++++++++++++++ Eingang_D++++++++++++++++++++
            for (j = 0; j < 11; j++) if ((rufalarm[2][j] = config_txt.read()) == '>') break;
            rufalarm[2][j] = '\0';
            if (DEBUG) {Serial.print(F("rufalarm[2]:  ")); Serial.println(rufalarm[2]);}
            break;
          case 15:
            t_eingang[2] = long ((config_txt.read() & 0x0F) * 100 + (config_txt.read() & 0x0F) * 10 + (config_txt.read() & 0x0F) * 1)* 1000;
            if (DEBUG) {Serial.print(F("t_eingang[2]= ")); Serial.println(t_eingang[2]);}
            break;
          case 17:
            for (j = 0; j <= 12; j++) if ((ansage[2][j] = config_txt.read()) == '>') break;
            ansage[2][j] = '\0';
            if (DEBUG) {Serial.print(F("ansage[2]:  ")); Serial.println(ansage[2]);}
            break;
          case 19:   //+++++++++++++++++++ Verzoegerung Alarm beim Einschalten ++++++++
            t_verzoegerung = long ((config_txt.read() & 0x0F) * 100 + (config_txt.read() & 0x0F) * 10 + (config_txt.read() & 0x0F) * 1)* 1000;
            if (DEBUG) {Serial.print(F("t_verzoegerung= ")); Serial.println(t_verzoegerung);}
            break;
          case 21: //++++++++++++++++++++ Dauer des Alarms ++++++++++++++++++
            t_piezo = long ((config_txt.read() & 0x0F) * 100 + (config_txt.read() & 0x0F) * 10 + (config_txt.read() & 0x0F) * 1)* 1000;
            if (DEBUG) {Serial.print(F("t_piezo= ")); Serial.println(t_piezo);}
            break;
          case 23: //+++++++++++++++++++ Zeit der verzögerten Alarmleitung bis auch die auslöst +++++++++
             t_haustuer = long ((config_txt.read() & 0x0F) * 100 + (config_txt.read() & 0x0F) * 10 + (config_txt.read() & 0x0F) * 1)* 1000;
            if (DEBUG) {Serial.print(F("t_haustuer= ")); Serial.println(t_haustuer);}
            break;
          case 25: //+++++++++++++++++++ Dauer des Anrufs und damit der Ansage +++++++++++++++++
            t_anrufdauer = long ((config_txt.read() & 0x0F) * 100 + (config_txt.read() & 0x0F) * 10 + (config_txt.read() & 0x0F) * 1)* 1000;
            if (DEBUG) {Serial.print(F("t_anrufdauer= ")); Serial.println(t_anrufdauer);}
            break;
          case 27: //+++++++++++++++++++ erste Alarmnummer ++++++++++++++++++
            for (j = 0; j < 13 ; j++) if ( (rufalarm[3][j] = config_txt.read()) == '>') break;
            rufalarm[3][j] = '\0';
            if (DEBUG) {Serial.print(F("rufalarm 3.Nr:  ")); Serial.println(rufalarm[3]);}
            break;
          case 29: //+++++++++++++++++++ zweite Alarmnummer ++++++++++++++++++
            for (j = 0; j < 13 ; j++) if ( (rufalarm[4][j] = config_txt.read()) == '>') break;
            rufalarm[4][j] = '\0';
            if (DEBUG) {Serial.print(F("rufalarm 4.Nr:  ")); Serial.println(rufalarm[4]);}
            break;
          case 31: //+++++++++++++++++++ dritte Alarmnummer ++++++++++++++++++
            for (j = 0; j < 13 ; j++) if ( (rufalarm[5][j] = config_txt.read()) == '>') break;
            rufalarm[5][j] = '\0';
            if (DEBUG) {Serial.print(F("rufalarm 5.Nr:  ")); Serial.println(rufalarm[5]);}
            break;
          default:
            if (DEBUG) Serial.println(F("*************  hier D E F A U L T  ***************"));
        }
        flag1++;
      }
    }
    config_txt.close();
    return true;
  }
  else {
    if (DEBUG) Serial.println(F("error opening config.txt"));
    sd_fault_fl=true;
    return false;
  }
}
//*****************************************************************************************************
boolean entstoerread(int pin) { // Da sich lange Leitungen ggf. Stoerimpulse einfangen können,
  int pnr, cnt=0;               // wird jeder Eingang im Abstand von einer Millisekunde 200x abgefragt.
  switch (pin) {
    case Alarmkreis1: pnr=0; break;
    case Alarmkreis2: pnr=1; break;
    case PIR        : pnr=2; break;
    case Taster     : pnr=3; cnt=140; break; //Abfrage des Tasterdrucks wird auf 60 ms verkuerzt
    case Klingel    : pnr=4; cnt=140; break; //Abfrage des Klingelknopfs wird auf 60 ms verkuerzt
    case Eingang_C  : pnr=5; break;
    case Eingang_D  : pnr=6; break;
  }
  while(digitalRead(pin)!=portwert[pnr]) {
    delay(1);
    if(cnt++ >200) {
      if(portwert[pnr]==true) portwert[pnr]=false;
      else                    portwert[pnr]=true;
      break;
    }
  }
  return portwert[pnr];
}
//*****************************************************************************************************
void millisstart(int n) {   //verhindert gemeinsam mit millisdauer() das millisekunden ueberlaufen
  millissav[n]=millis();
}
//*****************************************************************************************************
unsigned long millisdauer(int n) {
  unsigned long millistemp=millis();
  if(millistemp<millissav[n]) return(millistemp+(4294967295-millissav[n])); //Überlauf ist da
  return(millistemp-millissav[n]);
}
//*****************************************************************************************************
