Wettbewerbsbeitrag: Blumenwächter

Pflanze

Beitrag zum Kalenderwettbewerb 2015 von Leander Hackmann:

Jeder hat Topfpflanzen. Sie verschönern unseren Alltag, benötigen jedoch auch häufig unsere Aufmerksamkeit in Form von Pflege. Um den besten Standort herauszufinden und das regelmäßige Giessen nicht zu vergessen, habe ich folgende Anwendung mit dem Iot-Board entworfen und umgesetzt: ein Datenlogger für Licht und Feuchtigkeit.

Download des Programms

Das Programm

//Blumenwaechter für das NanoESP-Board
//2016 von Leander H.
//Misst alle 30min. Feuchtigkeit und Helligkeit
//Mit Druck auf Taster kann ein Nullpunkt in der Messung als markanter Punkt gesetzt werden#
//Es werden zwei Channels auf Thingspeak.com verwendet (API-Keys in "LightKEY und "MoistureKEY")
//Über "threshold" kann der Pegel zur Warnung eingestellt werden
//Code und Idee von Leander H. -- Funktionen von Fabian Kainka

#include <SoftwareSerial.h>
#include <Time.h>

//Setup-Bereich
#define SSID "[SSID]"
#define PASSWORD "[Passwort]"
#define LightKEY "[API-KEY]"
#define MoistureKEY "[API-KEY]"
#define threshold 400 //<--- "threshold" muss ggf. veraendert werden
#define DEBUG true
#define LED_WLAN 13

SoftwareSerial esp8266(11, 12); // RX, TX

int ledGreen = 5;
int ledYellow = 4;
int ledRed = 3;
int sensorPower = 2;
int moistSensor = 2;
String lastminute;
  
const byte thingPost[] PROGMEM = {
  80, 79, 83, 84, 32, 42, 85, 82, 76, 42, 32, 72, 84, 84, 80, 47, 49, 46, 49, 10, 72, 111, 115, 116, 58, 32, 97, 112, 105, 46, 116, 104, 105, 110, 103, 115, 112, 101, 97, 107, 46, 99, 111, 109, 10, 67, 111, 110, 110, 101, 99, 116, 105, 111, 110, 58, 32, 99, 108, 111, 115, 101, 10, 67, 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 58, 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 120, 45, 119, 119, 119, 45, 102, 111, 114, 109, 45, 117, 114, 108, 101, 110, 99, 111, 100, 101, 100, 10, 67, 111, 110, 116, 101, 110, 116, 45, 76, 101, 110, 103, 116, 104, 58, 32, 42, 76, 69, 78, 42, 10, 10, 42, 65, 80, 80, 69, 78, 68, 42, 10
};

void setup() {
  pinMode(ledGreen, OUTPUT);
  pinMode(ledYellow, OUTPUT);
  pinMode(ledRed, OUTPUT);
  pinMode(sensorPower, OUTPUT);
  
  Serial.begin(19200);
  esp8266.begin(19200);

  while(!espConfig()){ //Failsafe für WLAN
    delay(200);
  }
  digitalWrite(LED_WLAN, HIGH);

  while(!getTime("chronic.herokuapp.com", "/utc/in+one+hours") == false){ //Failsafe für Zeitsynchro
    delay(200);
  }
  
  sendThingPost(LightKEY, analogRead(A0)); //Erste Übermittlung
  digitalWrite(sensorPower, HIGH);
  delay(200);
  int tmp = analogRead(A2);
  sendThingPost(MoistureKEY, tmp);
  digitalWrite(sensorPower, LOW);
  if(tmp < threshold){
     digitalWrite(ledRed, HIGH);
     digitalWrite(ledGreen, LOW);
  }else{
     digitalWrite(ledRed, LOW);
     digitalWrite(ledGreen, HIGH);
  }
}

void loop() {
  String shour, sminute, ssecond;

  if (hour() <= 9) shour = "0" + String(hour()); else shour = String(hour()); // adjust for 0-9
  if (minute() <= 9) sminute = "0" + String(minute());  else sminute = String(minute());
  if (second() <= 9) ssecond = "0" + String(second());  else ssecond = String(second());

  //debug(shour + ":" + sminute + ":" + ssecond);
  
  if(sminute == "0" || sminute == "30"){ //Wird zur halben und vollen Stunde ausgeführt
    if(sminute != lastminute){
          digitalWrite(ledYellow, HIGH);
          if(sendThingPost(LightKEY, analogRead(A0) == false)){
            while(sendThingPost(LightKEY, analogRead(A0) == false)){
              sendThingPost(LightKEY, analogRead(A0));
              delay(200);
            }
          }
          digitalWrite(ledYellow, LOW);
          delay(500);
          digitalWrite(ledYellow, HIGH);
          digitalWrite(sensorPower, HIGH);
          delay(100);
          if(sendThingPost(MoistureKEY, analogRead(A2) == false)){
            while(sendThingPost(MoistureKEY, analogRead(A2) == false)){
              sendThingPost(MoistureKEY, analogRead(A2));
              delay(200);
            }
          }
          digitalWrite(sensorPower, LOW);
          digitalWrite(ledYellow, LOW);
          
          digitalWrite(sensorPower, HIGH);
          delay(100);
          int tmp = analogRead(A2);
          digitalWrite(sensorPower, LOW);
          
          if(tmp < threshold){
            digitalWrite(ledRed, HIGH);
            digitalWrite(ledGreen, LOW);
          }else{
            digitalWrite(ledRed, LOW);
            digitalWrite(ledGreen, HIGH);
          }
          
          lastminute = sminute;
    }
  }
  
  if(analogRead(A1) > 500){ //Button zum Setzen eines Nullpunktes
    digitalWrite(ledYellow, HIGH);
    sendThingPost(LightKEY, 0);
    digitalWrite(ledYellow, LOW);
    delay(500);
    digitalWrite(ledYellow, HIGH);
    sendThingPost(MoistureKEY, 0);
    digitalWrite(ledYellow, LOW);
    delay(1000);
  }
}



//Verwendete Funktionen von Fabian Kainka folgen ab hier:
//-----------------------------------------ThingsSpeak Functions------------------------------------

boolean sendThingPost(String key, int value)
{
  boolean succes = true;
  String  Host = "api.thingspeak.com";
  String msg = "field1=" + String(value);
  succes &= sendCom("AT+CIPSTART=\"TCP\",\"" + Host + "\",80", "OK");

  String postRequest = createThingPost("/update", key, msg);

  if (sendCom("AT+CIPSEND=" + String(postRequest.length()), ">"))
  {
    esp8266.print(postRequest);
    esp8266.find("SEND OK");
    if (!esp8266.find("CLOSED")) succes &= sendCom("AT+CIPCLOSE", "OK");
  }
  else
  {
    succes = false;
  }
  return succes;
}  

String createThingPost(String url, String key, String msg)
{
  String xBuffer;

  for (int i = 0; i <= sizeof(thingPost); i++)
  {
    char myChar = pgm_read_byte_near(thingPost + i);
    xBuffer += myChar;
  }

  String append = "api_key=" + key + "&" + msg;
  xBuffer.replace("*URL*", url);
  xBuffer.replace("*LEN*", String( append.length()));
  xBuffer.replace("*APPEND*", append);

  return xBuffer;
}

String createThingGet(String url, String key)
{
  String xBuffer;

  for (int i = 0; i <= sizeof(thingPost); i++)
  {
    char myChar = pgm_read_byte_near(thingPost + i);
    xBuffer += myChar;
  }

  String append = "api_key=" + key;
  xBuffer.replace("POST", "GET");
  xBuffer.replace("*URL*", url);
  xBuffer.replace("*LEN*", String( append.length()));
  xBuffer.replace("*APPEND*", append);

  return xBuffer;
}

String createThingGet(String url, String key, String msg)
{
  String xBuffer;

  for (int i = 0; i <= sizeof(thingPost); i++)
  {
    char myChar = pgm_read_byte_near(thingPost + i);
    xBuffer += myChar;
  }

  String append = "api_key=" + key + "&" + msg;

  xBuffer.replace("POST", "GET");
  xBuffer.replace("*URL*", url);
  xBuffer.replace("*LEN*", String( append.length()));
  xBuffer.replace("*APPEND*", append);

  return xBuffer;
}




String getTCP(String Host, String Subpage)
{
  boolean succes = true;

  succes &= sendCom("AT+CIPSTART=\"TCP\",\"" + Host + "\",80", "OK");
  String getRequest = "GET " + Subpage + " HTTP/1.1\r\nHost:" + Host + "\r\n\r\n";
  succes &= sendCom("AT+CIPSEND=" + String(getRequest.length() + 2), ">");

  return sendCom(getRequest);
}

boolean getTime(String Host, String Subpage)
{
  boolean succes = true;
  int xyear, xmonth, xday, xhour, xminute, xsecond;  //lokal variables

  succes &= sendCom("AT+CIPSTART=\"TCP\",\"" + Host + "\",80", "OK");
  String getRequest = "GET " + Subpage + " HTTP/1.1\r\nHost:" + Host + "\r\n";
  succes &= sendCom("AT+CIPSEND=" + String(getRequest.length() + 2), ">");

  esp8266.println(getRequest);

  if (esp8266.find("+IPD"))
  {
    if (esp8266.find("\r\n\r\n"))
    {
      xyear = esp8266.parseInt();
      xmonth = esp8266.parseInt();
      xday = esp8266.parseInt();
      xhour = esp8266.parseInt();
      xminute = esp8266.parseInt();
      xsecond = esp8266.parseInt();

      if (xday < 0) xday *= -1;          //Because of date seperator - parseInt detects negativ integer
      if (xmonth < 0) xmonth *= -1;    //Because of date seperator - parseInt detects negativ integer


      setTime(xhour, xminute, xsecond, xday, xmonth, xyear);
      sendCom("AT+CIPCLOSE", "OK");
      return true;
    }
    else return false;
  }
  else return false;
}

//-----------------------------------------Config ESP8266------------------------------------

boolean espConfig()
{
  boolean succes = true;
  esp8266.setTimeout(5000);
  succes &= sendCom("AT+RST", "ready");
  esp8266.setTimeout(1000);
  if (configStation(SSID, PASSWORD)) {
    succes &= true;
    debug("WLAN Connected");
    debug("My IP is:");
    debug(sendCom("AT+CIFSR"));
  }
  else
  {
    succes &= false;
  }
  //shorter Timeout for faster wrong UPD-Comands handling
  succes &= sendCom("AT+CIPMODE=0", "OK");  //So rum scheit wichtig!
  succes &= sendCom("AT+CIPMUX=0", "OK");

  return succes;
}

boolean configTCPServer()
{
  boolean succes = true;

  succes &= (sendCom("AT+CIPMUX=1", "OK"));
  succes &= (sendCom("AT+CIPSERVER=1,80", "OK"));

  return succes;

}

boolean configTCPClient()
{
  boolean succes = true;

  succes &= (sendCom("AT+CIPMUX=0", "OK"));
  //succes &= (sendCom("AT+CIPSERVER=1,80", "OK"));

  return succes;

}


boolean configStation(String vSSID, String vPASSWORT)
{
  boolean succes = true;
  succes &= (sendCom("AT+CWMODE=1", "OK"));
  esp8266.setTimeout(20000);
  succes &= (sendCom("AT+CWJAP=\"" + String(vSSID) + "\",\"" + String(vPASSWORT) + "\"", "OK"));
  esp8266.setTimeout(1000);
  return succes;
}

boolean configAP()
{
  boolean succes = true;

  succes &= (sendCom("AT+CWMODE=2", "OK"));
  succes &= (sendCom("AT+CWSAP=\"NanoESP\",\"\",5,0", "OK"));

  return succes;
}

boolean configUDP()
{
  boolean succes = true;

  succes &= (sendCom("AT+CIPMODE=0", "OK"));
  succes &= (sendCom("AT+CIPMUX=0", "OK"));
  succes &= sendCom("AT+CIPSTART=\"UDP\",\"192.168.255.255\",90,91,2", "OK"); //Importand Boradcast...Reconnect IP
  return succes;
}

//-----------------------------------------------Controll ESP-----------------------------------------------------

boolean sendUDP(String Msg)
{
  boolean succes = true;

  succes &= sendCom("AT+CIPSEND=" + String(Msg.length() + 2), ">");    //+",\"192.168.4.2\",90", ">");
  if (succes)
  {
    succes &= sendCom(Msg, "OK");
  }
  return succes;
}


boolean sendCom(String command, char respond[])
{
  esp8266.println(command);
  if (esp8266.findUntil(respond, "ERROR"))
  {
    return true;
  }
  else
  {
    debug("ESP SEND ERROR: " + command);
    return false;
  }
}

String sendCom(String command)
{
  esp8266.println(command);
  return esp8266.readString();
}



//-------------------------------------------------Debug Functions------------------------------------------------------
void serialDebug() {
  while (true)
  {
    if (esp8266.available())
      Serial.write(esp8266.read());
    if (Serial.available())
      esp8266.write(Serial.read());
  }
}

void debug(String Msg)
{
  if (DEBUG)
  {
    Serial.println(Msg);
  }
}

[collapse]

Das Programm misst zur vollen und halben Stunde (Zeit wird aus dem Internet bezogen) Helligkeit und Feuchtigkeit, um diese dann in zwei Graphen auf Thingspeak.com zu schreiben. Bei zu trockener Erde (der Wert „threshold“ muss individuell angepasst werden, bei mir lag der Wert für feuchte Erde bei rund 400) leuchtet dann eine rote LED und ansonsten eine grüne. Die Stromzufuhr für den Feuchtigkeitssensor (ein Spannungsteiler), wird nur periodisch eingeschaltet, um Elektrolyse in der Erde zu vermeiden. Außerdem müssen Graphitelektroden (z.B. Bleistiftminen) verwendet werden, um die Pflanze nicht durch Ionen einer Elektrolyse zu vergiften (besonders bei Kupfer besteht Gefahr).

Auf Knopfdruck wird in beide Graphen ein Nullwert geschrieben, beispielsweise um Anfang und Ende einer Messung auf dem Graphen festzuhalten. Während der Übermittlung von Daten und bei Messungen wird zur Kontrolle die gelbe LED eingeschaltet.

sch-001Das Bild zeigt einen experimentellen Aufbau (es wurden nur andersfarbige LEDs mit eingebautem Vorwiderstand verwendet, der Aufbau ist aber nach der Fritzing-Abbildung mit Bauteilen aus den Kalendern realisierbar).