//======================================================================
//= Beschreibung                                                       =
//======================================================================

/* Projekt: Temperaturlogger
 *
 * Ziele:
 * Sensoren:        Dallas DS18B20
 * RTC:             Version 1.0 zunächst noch mit Softwareuhr
 * Datenspeicher:   SD-Karte
 * IP:              65
 * Konfiguration:   über Konfigurationsdatei auf der SD-Karte
 * Einstellung Uhr: Nach einem Reset (Startwert aus Konfigurationsdatei)
 * Versorgung:      Batterie (4 Stck. NimH.Akkus AA)
 *
 * SD-Karte an SPI-Bus:
 * MOSI - Pin 11    MasterOutSlaveIn
 * MISO - Pin 12    MasterInSlaveOut
 * CLK  - Pin 13    Clock
 * CS   - PIN 4     (beim Ethernet-Shield)
 *
 * Step 1:
 * Diverse Werte aus einer config-Datei auf der SD Karte auslesen.
 * Unter Verwendung der parseInt() Methode.
 * Siehe hierzu:
 * Kochbuch Seite 105, 113
 * oder auch: http//:arduino.cc/en/Tutorial/ReadASCIIString
 *
 * Step 2:
 * Schreiben auf die SD-Karte in eine csv-Datei
 * Achtung: der Dateiname darf 8 Zeichen nicht übersteigen
 * siehe: http://arduino.cc/en/Reference/SD
 * "The library supports FAT16 and FAT32 file systems on standard
 *  SD cards and SDHC cards. It uses short 8.3 names for files."
 *
 * Step 3:
 * Anbindung der Sensoren
 * - Test mit zwei Sensoren: erfolgreich
 *
 * Step 4:
 * Feinabstimmung
 * - Von der Zykluszeit die Programmlaufzeit abziehen (höhere Präzision)
 * - Messdauer in Sekunden einstellbar: erledigt
 * - Auflösung einstellbar: erledigt
 * - Test mit drei Sensoren: erfolgreich
 *
 * Step 5:
 * Optimierungen
 * - Tabellenkopf erzeugen (zur Identifikation der Sensoren):
 *   erledigt
 * - Startverzögerungsoption einbauen:
 *   erledigt
 * - F("") Option der aktuellen IDE verwenden um RAM zu sparen:
 *   erledigt
 * - Umstellung auf sdfat wegen "file.rename" Möglichkeit:
 *
 * - file = SD.open("test.txt", O_CREAT | O_WRITE); testen:
 *
 * - Strombedarf reduzieren
 *
 * Step 6:
 * Umstellung auf Nano mit SD-Card Modul
 * - Aufbau auf Steckbrett: erledigt
 * - SD-Modul via SPI-Bus anbinden: erledigt
 * - Strombedarf aus Akkublock messen
 * - Schaltplan erstellen:
 *
 * Step 7:
 * Dokumentation
 *
 *
 * Step 8:
 * Anpassung an die neue IDE mind. ab 1.6.0
 * u. A. neue Time.h Library
 *
 * Version: 13_Final
 *
 */

//======================================================================
//= Bibliotheken einbinden                                             =
//======================================================================
#include <SPI.h>                 // mindestens ab IDE 1.6.0 erforderlich
#include <SD.h>
#include <Time.h>
#include <OneWire.h>
#include <DallasTemperature.h>

//======================================================================
// variables created by the build process when compiling the sketch    =
//======================================================================
extern int __bss_end;                // notwendig für die Berechnung
extern void *__brkval;               // des Speicherverbrauchs (RAM)

//======================================================================
//= diverse Konstanten                                                 =
//======================================================================
#define ONE_WIRE_BUS 7               // Hier ist der Sensorbus angeschl.
#define chipSelect 4                 // beim Ethernet Shield CS = pin 4
#define hardwareSS 10                // hardware SS pin

//======================================================================
//= diverse Deklarationen                                              =
//======================================================================

// für die SD-Karte
File myFile;                         // Objekte vom Typ File erzeugen
char zeichen;                        // das zuletzt gelesene Zeichen
char dateiName[20];                  // Dateiname der csv-Datei
char buffer[20];                     // Puffer zur int-Umwandlung

// allgemeine Daten
unsigned long zyklusZeit;            // Messzyklus in Millisekunden
unsigned long initUnixZeit;          // Initialisierung der Uhr
unsigned long verzoegMessung;        // Verzögerung der Messung
unsigned long startMessung;          // Start der Messung
unsigned long endeMessung;           // Ende der Messung
unsigned long dauerMessung;          // Messdauer in Sekunden
int genauigkeitMessung;              // Auflösung der Messung

long dauer;                          // Laufzeit von loop

// für die Temperatursensoren
OneWire oneWire(ONE_WIRE_BUS);       // Objekte vom Typ oneWire erzeugen
DallasTemperature sensors(&oneWire); // Anbindung an die Dallas-Lib
DeviceAddress tempDeviceAddress;     // Adressspeicher
int numberOfDevices;                 // Anzahl gefundener Sensoren

//======================================================================
//= Setup-Funktion                                                     =
//======================================================================
void setup() {
  // einen Kanal zur seriellen Kommunikation öffnen------------------
  Serial.begin(9600);

  // SD-Karte initialisieren-----------------------------------------
  Serial.print(F("Initialisierung der SD Karte..."));
  pinMode(hardwareSSOUTPUT);
  // erfolgreich?
  if (!SD.begin(chipSelect)) {
    Serial.println(F(" fehlgeschlagen!"));
    return;
  }
  Serial.println(F(" erledigt."));

  // Konfigurationsdatei zum lesen öffnen----------------------------
  myFile SD.open("config.txt");
  if (myFile) {
    Serial.println(F("Einlesen der Startwerte:"));
    // Die Konfigurationswerte einlesen
    genauigkeitMessung myFile.parseInt();
    initUnixZeit       myFile.parseInt();
    zyklusZeit         myFile.parseInt();
    verzoegMessung     myFile.parseInt();
    dauerMessung       myFile.parseInt();
    // Datenkanal wieder schließen
    myFile.close();
    // Zeit (UTC) laut config.txt setzen
    setTime(initUnixZeit);
    // Info zur Signalauflösung zum Terminal
    Serial.println("");
    Serial.print(F("Messgenauigkeit = "));
    Serial.print(genauigkeitMessung);
    Serial.println(" Bit");
    // Info zur Zeitinitialisierung zum Terminal
    Serial.println("");
    Serial.print(F("Startwert der Uhr = "));
    Serial.print(initUnixZeit);
    Serial.println(F(" im Unixzeitformat"));
    // Info über den Messzyklus zum Terminal
    Serial.println("");
    Serial.print(F("Messzyklus = "));
    Serial.print(zyklusZeit);
    Serial.println(F(" Millisekunden"));
    // Info zur Verzögerung zum Terminal
    Serial.println("");
    Serial.print(F("Startverzoegerung = "));
    Serial.print(verzoegMessung);
    Serial.println(F(" Sekunden"));
    // Info zur Messdauer zum Terminal
    Serial.println("");
    Serial.print(F("Messdauer = "));
    Serial.print(dauerMessung);
    Serial.println(F(" Sekunden"));
    Serial.println("");
    // Startzeit der Messung berechnen
    startMessung initUnixZeit verzoegMessung;
    // Zeit für Ende der Messung berechnen
    endeMessung startMessung dauerMessung;
    // den Namen der Ausgabedatei erzeugen
    erzeugeDateiNamen();
    // Info zum Dateinamen zum Terminal
    Serial.print(F("Dateiname = "));
    Serial.println(dateiName);
    Serial.println("");
    // Wenn es die Datei schon gibt, dann löschen !!!!!!!!!
    // hier muss noch optimiert werden. Löschen ist nicht
    // wirklich gut. Datenverlust droht bei unbedachten RESETS !!!
    if (SD.exists(dateiName)) {
      SD.remove(dateiName);
      Serial.println(F("Altdatei geloescht."));
      Serial.println("");
    }
  }
  else {
    // Wenn das Öffnen der Konfigurationsdatei gescheitert ist
    Serial.println(F("Fehler beim Öffnen der Konfigurationsdatei!"));
  }
  // Temperatursensoren ---------------------------------------------
  // Die Dallas Temperature IC Control Library starten
  sensors.begin();
  // Anzahl der Sensoren am Bus ermitteln
  numberOfDevices sensors.getDeviceCount();
  // Anzahl der Sensoren am Bus am Terminal ausgeben
  Serial.println(F("Sensorsuche..."));
  Serial.print(F("gefunden: "));
  Serial.print(numberOfDevicesDEC);
  Serial.println(F(" Sensoren."));
  // Spannungsversorgungsart am terminal anzeigen
  Serial.print(F("Parasitaere Versorgung ist: "));
  if (sensors.isParasitePowerMode()) Serial.println(F("aktiv"));
  else Serial.println(F("inaktiv"));
  // Adressen aller Sensoren am Terminal anzeigen und in Tabellenkopf schreiben
  myFile SD.open(dateiNameFILE_WRITE);   // für den Tabellenkopf
  if (myFile) {                              // die Datendatei öffnen
    myFile.print(F("Datum"));             // Spalte 1
    myFile.print(";");                    // Spaltentrenner
    myFile.print(F("Uhrzeit"));           // Spalte 2
    for (int 0numberOfDevicesi++) { // Sensoradressen durchl.
      // Adresssuche und Ausgabe zum Terminal und in die Datei
      if (sensors.getAddress(tempDeviceAddressi)) {
        Serial.print(F("\nSensor "));
        Serial.print(iDEC);
        Serial.print(F(" mit der Adresse: "));
        printAddress(tempDeviceAddress);     // Terminal und csv-Datei
        Serial.println();
        // Die Messwertauflösung auf genauigkeitMessung bit setzen
        // Bei jedem Dallas/Maxim Sensor kann, unabhängig von einander,
        // die Auflösung eingestellt werden.
        // Bei diesem Projekt werden alle auf die gleiche Auflösung
        // parametriert.
        sensors.setResolution(tempDeviceAddressgenauigkeitMessung);
        // Genauigkeit aller Sensoren am terminal anzeigen
        Serial.print(F("Die Aktuelle Genauigkeit ist eingestellt auf: "));
        Serial.print(sensors.getResolution(tempDeviceAddress), DEC);
        Serial.println();
      }
      else {
        Serial.print(F("Geisterbauteil gefunden bei "));
        Serial.print(iDEC);
        Serial.print(F(" die Adresse kann nicht ermittelt werden. Aufbau kontrollieren!"));
      }
    }
    myFile.println("");    // Zeilenvorschub für den Tabellenkopf
    // die csv-Datei wieder schließen
    myFile.close();
  }
  else {
    // Wenn das Öffnen der csv-Datei gescheitert ist
    Serial.println(F("Fehler beim schreiben in die CSV-Datei!"));
  }
  Serial.println(F("==================================================="));
  Serial.println("");
}// Ende: Setup-Funktion

//======================================================================
//= diverse Funktionen                                                 =
//======================================================================
void erzeugeDateiNamen() {
  // den Namen der Ausgabedatei erzeugen
  // ACHTUNG: Namenslänge der Datei maximal 8.3 !!!
  strcpy(dateiName"");        // alten Namen löschen
  itoa(year(), buffer10);     // Jahr
  strcat(dateiNamebuffer);
  itoa(month(), buffer10);    // Monat
  strcat(dateiNamebuffer);
  itoa(day(), buffer10);      // Tag
  strcat(dateiNamebuffer);
  strcat(dateiName".csv");    // .csv
}// Ende: erzeugeDateiNamen

void printDigits(int digits) {
  // Zusatzfunktion zur Uhrzeitanzeige
  // Doppelpunkt und führende 0 ausgeben
  Serial.print(":");
  if (digits 10)
    Serial.print('0');
  Serial.print(digits);
}// Ende: printDigits

void printDigitsOnSD(int digits) {
  // Zusatzfunktion zur Uhrzeitausgabe in die csv-datei
  // Doppelpunkt und führende 0 ausgeben
  myFile.print(":");
  if (digits 10)
    myFile.print('0');
  myFile.print(digits);
}// Ende: printDigitsOnSD

void printAddress(DeviceAddress deviceAddress) {
  // Adresse eines Temperatursensors ausgeben (Terminal und Datei)
  myFile.print(";");                         // Spaltentrenner
  for (uint8_t 08i++)
  {
    if (deviceAddress[i] < 16Serial.print("0");
    Serial.print(deviceAddress[i], HEX);  // Terminal
    myFile.print(deviceAddress[i], HEX);  // Tabellenkopf
  }
}// Ende: printAddress

int memoryFree() {
  // function to return the amount of free RAM
  int freeValue;
  if ((int)__brkval == 0)
    freeValue = ((int)&freeValue) - ((int)&__bss_end);
  else
    freeValue = ((int)&freeValue) - ((int)__brkval);
  return freeValue;
}// Ende: memoryFree

//======================================================================
//= Loop-Funktion                                                      =
//======================================================================
void loop()
{
  if (now() >= startMessung)
  // bei Freigabe der Messung
    // Datenausgabe in die csv-Datei auf der SD-Karte------------------
    // die csv-Datei zur Datenaufnahme öffnen und beschreiben
    myFile SD.open(dateiNameFILE_WRITE);
    // wenn erfolgreich, dann Start der Ausgabe------------------------
    if (myFile) {
      myFile.print(day());      // Tag
      myFile.print(".");
      myFile.print(month());    // Monat
      myFile.print(".");
      myFile.print(year());     // Jahr
      myFile.print(";");        // neue Spalte----------------------
      myFile.print(hour());     // Stunde
      printDigitsOnSD(minute());// Minute
      printDigitsOnSD(second());// Sekunde
      // jetzt kommen endlich die Daten
      // Aufruf von: sensors.requestTemperatures() um eine Antwort von
      // allen Geräten am Bus zu erhalten.
      // Hierdurch werden die Temperaturen bereitgestellt.
      sensors.requestTemperatures();
      // Alle Geräte durchlaufen und Messwerte ausgeben
      for (int 0numberOfDevicesi++)
      // den Bus nach Adressen absuchen
        if (sensors.getAddress(tempDeviceAddressi))
        // Die Antwort kommt fast unmittelbar. Sofortige Datenausgabe
          float tempC sensors.getTempC(tempDeviceAddress);
          myFile.print(";");    // neue Spalte--------------
          myFile.print(tempC);  // Messwert vom angesprochenen Sensor
        }
        //else ghost device! Check your power requirements and cabling
      }    // Ende von for
      myFile.println(""); // Zeilenvorschub am Ende der Tabellenzeile
      // die csv-Datei wieder schließen
      myFile.close();
    }    // Ende von if myFile
    else
    // Wenn das Öffnen der csv-Datei scheitert
      Serial.println(F("Fehler beim schreiben in die CSV-Datei!"));
    }    // Ende von else
  }        // Ende von if freigabe
  // Messzeitkorrektur und Terminaltestausgaben
  // Zykluszeit des Programms bestimmen
  dauer millis() - dauer;
  // mit korrigiertem Wert auf den nächsten Messzyklus warten
  delay(zyklusZeit dauer);
  // neuen Zählerstand der Millisekunden merken
  dauer millis();
  // Kontrolle ob das Messzeitende erreicht ist
  while (now() > endeMessung) {
  // am Ende der Messung, ab in die Dauerschleife
}// Ende: Loop-Funktion

//======================================================================
//= Ende                                                               =
//======================================================================