Smart Watering

Aus HRW FabLab MediaWiki
Wechseln zu: Navigation, Suche
Smart Watering

SmartWatering.jpg

Entwickler

Matthias Hinz, Jonas Stratmann, Pierre Kirschner

Projektdateien

Android App auf Github Mikrocontroller Code auf Github

Software

Android Studio Arduiono IDE

Smart Watering ist ein System, welches das fast automatische Gießen von Pflanzen ermöglicht. Das Gießverhalten soll hierbei über eine App steuerbar sein.

Motivation[Bearbeiten]

Aufgrund des fortgeschrittenen Alters meiner Großeltern und ihrer Probleme beim Heben von Objekten fällt es ihnen schwer ihre noch verbleibenden Pflanzen zu gießen, wollen diese aber gerne noch erhalten. Daher kam die Idee infolge der Projektvorbereitung für das Modul Eingebettete Systeme, eine (halb-) automatisierte Bewässerungsanlage zu installieren, um ein Bewässern der Pflanzen zu erleichtern.

Es gibt zahlreiche Möglichkeiten dies zu realisieren und wir haben uns in unserer Gruppe anhand kleiner Vorgaben dazu entschieden ein System zu bauen, welches die Möglichkeit hat, sowohl nach wenigen Einstellungen automatisiert zu laufen als auch manuell betrieben zu werden. Es kursieren einige Anregungen und Ideen, mit denen wir uns eine grobe Übersicht machen konnten inwieweit wir uns von anderen absetzen können und welche Ideen es noch nicht gibt.

Projektbeschreibung[Bearbeiten]

Unser Projekt „Smart Watering“ ist möglichst klein gehalten um platzsparend in jeder Ecke, Fensterbank oder ähnlichem effizient arbeiten zu können. Es dient dazu Pflanzen automatisiert zu gießen. Dadurch das es über eine Bluetooth – Schnittstelle angesteuert wird, ist das eigenständige Gießen mittels Gießkanne oder ähnlichen nicht mehr nötig. Benötigt wird dazu ein auf Android basiertes Smartphone, dass mit unserer gleichnamigen App „Smart Watering“ ausgestattet ist. Dort können über ein Menü Einstellungen bezüglich der Pflanzenart ausgewählt werden, die Pflanzenart bestimmt den Sollwert für die Bodenfeuchtigkeit im Topf der Pflanze. Die Pumpe, die durch das System angesteuert wird und das Wasser über einen Schlauch ansaugen und weitergeben kann, kann z.B. in der Nähe einer Wassertonne oder ähnlichen Zugängen installiert werden und dort seine Wasservorräte beziehen. Ein Sensor der die Feuchtigkeit der Erde misst und an das System weitergibt, muss lediglich in die Erde der Pflanze gesteckt werden.

Hardware[Bearbeiten]

Bauteile Anzahl Preis
ESP32-WROOM-Rev.4 1 8€
Mini-Breadboard 1 2€
Korrosionsbeständiger Feuchtigkeitssensor 1 8€
Mini-Wasserpumpe 1 7-10€
Kabelset 1 7€
PVC-Schlauch 1 ca. 2€
Netzteil 1 8€
Schaltreglermodul 1 6€
Relay 10A – 230V 1 5€
Box/3D-Druck 1 ca. 5€
Total Kosten ca. 58€

Schaltplan[Bearbeiten]

Schaltplan

Entwicklungsphase[Bearbeiten]

Prototyp 1[Bearbeiten]

Der erste Entwickelte Prototyp bestand anstelle eines ESP32 noch aus einem Arduino Nano und ohne Bluetooth und WLAN Schnittstelle. Ziel ist zunächst einen automatisierten Algorithmus zu erstellen, damit Pflanzen mittels einer Pumpe und entsprechender Verkabelung bewässert werden können. Der Fokus lag zunächst auf der Funktionalität, das Design wurde vernachlässigt.
Erster Prototyp im Test

Prototyp 2[Bearbeiten]

Nachdem die ersten Tests mit dem Prototyp 1 erfolgreich verlaufen sind, wurde ein zweiter Prototyp gebaut mit einigen Hardware Änderungen. Der Arduino Nano wurde durch den ESP32-WROOM V4 ersetzt. Durch die Bluetooth Schnittstelle des ESP 32, ist es nun möglich das System über eine App auf einem Android Gerät anzusteuern.
Zweiter Prototyp im Test

Programmierung[Bearbeiten]

Im folgenden gehen wir auf wichtige Teile aus der Mikrocontroller und App Programmierung ein. Da wir zwischen zwei Geräten über eine Bluetooth Verbindung kommunizieren, müssen wir uns auf ein Protokoll zur Kommunikation einigen.

App zu Esp Erklärung
p Aktiviere die Pumpe manuell für 5 Sekunden
e Ermögliche das die Pumpe automatisch, bei zu trockener Erde, Wasser pumpen kann
d Verhindert das die Pumpe automatisch, bei zu trockener Erde, Wasser pumpen kann
s<Wert zwischen 20 und 70> Verändere den Sollwert ab dem wider gegossen werden soll
? Fordere die aktuelle Konfiguration im Format s<Sollwert><e/d> an

Mikrocontroller[Bearbeiten]

Zur Programmierung des Mikrokontroller haben wir die Arduino IDE benutzt. Ein Problem welches uns aufgefallen ist, dass wenn man den pump_pin auf LOW setzt die Pumpe AN geht und bei HIGH AUS.

Bluetouth Verbindung[Bearbeiten]

#include "BluetoothSerial.h"
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enabled it
#endif
BluetoothSerial SerialBT;
// Length of the Byte array for the Commands over Bluetouth
const int commandLength = 6;
byte command[commandLength];

in setup

SerialBT.begin("Gruppe 17 - Bewässerung");

in loop

// Bluetooth Connection from Phone to Chip to configure the Programm
 if(SerialBT.available()){
   // Read Command
   for(int i = 0; i < commandLength; i++){
     if(SerialBT.available()){
       command[i] = SerialBT.read();
     } else {
       command[i] = '-';
     }
   }
--- Reaktion auf die Übertragung ---
}

Pin Belegung und Steuerung[Bearbeiten]

// Other variables
int output_value = 0;
int targetValue = 50;
bool enabled;
unsigned long delayTimer = 0;
// Determin the Pins
int sensor_pin = 34;
int pump_pin = 23;

in setup

enabled = false; 
// Configure Pins  
pinMode(pump_pin, OUTPUT);
digitalWrite(pump_pin, HIGH);

in loop

// Read value of Sensor and Map map the Values to values between 0 and 100 for easier handling
output_value = analogRead(sensor_pin);
output_value = map(output_value, 3300, 1640, 0, 100);
if(enabled == true && delayTimer > 100){
 delayTimer = 0;
   // Check Sensor Value against Configured Value
   if(output_value < targetValue){
     digitalWrite(pump_pin, LOW);
     delay(10000);
     digitalWrite(pump_pin, HIGH);
   }
 }
--- Configuration über Bluetooth ---
delayTimer++;

Speicherung vom Sollwert[Bearbeiten]

// EEPROM config
#include <EEPROM.h>
#define EEPROM_SIZE 1

in setup

// EEPROM - Configure the memory and read the targetValue
EEPROM.begin(EEPROM_SIZE);
targetValue = EEPROM.read(1);

in loop bei änderung des Sollwertes über Bluetooth

// Change the target value
   if(command[0] == 's'){      
     String inString = "";
     for(int i = 2; i < commandLength; i++){
       if(command[i] != '-'){
         inString += (char)command[i];  
       } else {
         break;
       }
     }
     // Override the current targetValue and save it
     targetValue = inString.toInt();
     EEPROM.write(1, targetValue);
     EEPROM.commit();
   }

App Programmierung[Bearbeiten]

Wir haben uns für eine App auf Android Basis entschieden, somit haben wir die IDE Android Studio genutzt um diese zu entwickeln. Wichtige Abschnitte des Source Codes werden hiernach gezeigt.

Bluetooth Verbindung aufbauen[Bearbeiten]

Um eine Bluetooth Verbindung aufzubauen habe ich ein Gerüst von einen Bluetooth Terminals angepasst. Link zum Bluetooth Terminals: Gitub

Nachrichten an den Esp senden[Bearbeiten]

//Schicke eine Nachricht über die aufgebaute Verbindung ab 
//Rückgabewert signalisiert das die Nachricht verschickt werden konnte
public boolean sendMessage(String message) {
    // Überprüfe die Verbindung
    if (bluetoothService.getState() != Constants.STATE_CONNECTED) {
        //Es konnte keine Verbindung hergestellt werden
        return false;
    } else {
        //Falls eine Verbindung hergestellt werden konnte, schicke die übergebene Nachricht über die aufgebaute Verbindung ab
        byte[] send = message.getBytes();
        bluetoothService.write(send);
        return true;
    }
}

Nachricht vom Esp empfangen[Bearbeiten]

//Statischer String da es immer nur eine Konfigurations Nachricht geben kann
//Außerdem kann man von überall in der App auf diese Nachricht zugreifen
static String message;
//Ein Handler der Reagiert wenn er über die Bluetooth Verbindung eine Nachricht empfängt
private static class myHandler extends Handler {
   private final WeakReference<BluetoothActivity> mActivity;
   public myHandler(BluetoothActivity activity) {
       mActivity = new WeakReference<>(activity);
   }
   @Override
   public void handleMessage(Message msg) {
       final BluetoothActivity activity = mActivity.get();
       switch (msg.what) {
           case Constants.MESSAGE_READ:
               
              String readMessage = (String) msg.obj;
               //Weise die empfangende Nachricht unserer statischen Variable zu. 
              //In diesem Fall ist das möglich da von dem Esp nur diese Nachricht kommt, ansonsten ist die Kommunikation einseitig von App->Esp
               message=readMessage;
               break;
       }
   }
}

Nach Verbindungsaufbau[Bearbeiten]

//Wird bei dem erstellen dieser Activity aufgerufen 
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_bluetooth);
   ButterKnife.bind(this);
   getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   assert getSupportActionBar() != null;
   myHandler handler = new myHandler(BluetoothActivity.this);
   device = getIntent().getExtras().getParcelable(Constants.EXTRA_DEVICE);
   bluetoothService = new BluetoothService(handler, device);
   setTitle(device.getName());
   //Erstelle einen (Warte) AlertDialog der signalisiert das momentan Daten empfangen werden/ auf Daten gewartet werden
   AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
   alertDialogBuilder.setMessage("Empfange Daten von Smart Watering, bitte warten");
   final AlertDialog alertDialog = alertDialogBuilder.create();
   alertDialog.show();
  //Warte 3 Sekunden um sicher zu stellen das die Bluetooth Verbindung aufgebaut ist 
  new Handler().postDelayed(new Runnable() {
       @Override
       public void run() {
           //Sende ein "?" an den Esp
           if(sendMessage("?")==true){
               //Falls die Nachricht am Esp angekommen ist 
              // Warte 2 Sekunden um sicher zu stellen das Daten empfangen wurden 
              new Handler().postDelayed(new Runnable() {
                   @Override
                   public void run() {
                       //Schließe den Warte Dialog
                       alertDialog.cancel();
                       //Zeige eine Nachricht an damit der User weiß das Daten empfangen wurden
                       Toast.makeText(getApplicationContext(),"Daten erfolgreich empfangen", Toast.LENGTH_LONG).show();
                       //Setze die Empfangene Konfiguration
                       setConfig();
                   }
               }, 2000 );//time in milisecond
           }
           else{
               Toast.makeText(getApplicationContext(),"Keine Daten empfangen, überprüfen sie die Verbindung", Toast.LENGTH_LONG).show();
               alertDialog.cancel();
           }
       }
   }, 3000 );//time in milisecond
}

Empfangende Daten in der App abbilden[Bearbeiten]

public void setConfig()
{
   //Teile message in zwei Substrings auf 
  //Der 1. gibt den Slot an und den Sollwert
   //Der 2. ob dieser Slot dis/enabled werden soll 
  String config = message.substring(0,5);
   String cngA =message.substring(5,6);
   Switch s =(Switch) findViewById(R.id.switch1);
   TextView t = (TextView) findViewById(R.id.textView10);
   //Setze die Einstellungen zu dis/enable
   if(cngA=="d") {
       s.setChecked(false);
   }
   if (cngA=="e"){
       s.setChecked(true);
   }
   switch(cngA){
       case "d":
           s.setChecked(false);
           break;
       case "e":
           s.setChecked(true);
           break;
   }
   //Setze die Einstellungen zum Sollwert
   switch(config) {
       case "s0=10":
               //Setze Text
               t.setText("Kaktus");
               //Setze Icon
               t.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.mipmap.i9, 0);
               t.setGravity(Gravity.CENTER);
           break;
       case "s0=50":
           t.setText("Blume");
           t.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.mipmap.i6, 0);
           t.setGravity(Gravity.CENTER);
           break;
---Restlichen Cases---
}

Slot am ESP dis/enablen[Bearbeiten]

//Wird aufgerufen falls der Switch betätigt wird  
@OnCheckedChanged(R.id.switch1) void disEnable(Switch s)
{ 
   //falls er nach dem betätigen true ist:
   if(s.isChecked()==true)
   {
       //Sende den Befehl "e0" an den ESP, diese Befehl aktiviert die Pumpe 
      if(sendMessage("e0")==true)
       {
           //falls die Nachricht abgeschickt werden konnte zeige dem User eine Nachricht
           Toast.makeText(getApplicationContext(),"Slot wurde aktiviert", Toast.LENGTH_LONG).show();
       }
       else
       {
           //falls die Nachricht nicht abgeschickt werden zeige Fehlermeldung
           Toast.makeText(getApplicationContext(),"Verbindungsfehler - Bitte versuchen sie es erneut", Toast.LENGTH_LONG).show();
       }
   }
   //Sende den Befehl "d0" an den ESP, diese Befehl deaktiviert die Pumpe 
  else
   {
       if(sendMessage("d0")==true)
       {
           //falls die Nachricht abgeschickt werden konnte zeige dem User eine Nachricht
           Toast.makeText(getApplicationContext(),"Slot wurde deaktiviert", Toast.LENGTH_LONG).show();
       }
       else
       {
           //falls die Nachricht nicht abgeschickt werden zeige Fehlermeldung
           Toast.makeText(getApplicationContext(),"Verbindungsfehler - Bitte versuchen sie es erneut", Toast.LENGTH_LONG).show();
       }
   }
}

Einen Slot am ESP gießen[Bearbeiten]

//Schicke eine Nachricht zum ESP die dazu führt einen Slot zu gießen
@OnClick(R.id.iconGiessen) void giessen() {
 //"p0e" ist der Befehl zum bewässern
   if(sendMessage("p0e")==true)
   {
       //falls die Nachricht abgeschickt werden konnte zeige dem User eine Nachricht
       Toast.makeText(getApplicationContext(),"Slot wurde manuell bewässert", Toast.LENGTH_LONG).show();
   }
   else
   {
       //falls die Nachricht nicht abgeschickt werden zeige Fehlermeldung
       Toast.makeText(getApplicationContext(),"Verbindungsfehler - Bitte versuchen sie es erneut", Toast.LENGTH_LONG).show();
   }
}

Einen Sollwert für einen Slot am ESP festlegen[Bearbeiten]

Um einen Sollwert für einen Slot auszuwählen habe ich eine neue Activity erstellt. In dieser Activity gibt es eine 3x3 Matrix aus Buttons. Jeder Button repräsentiert eine Pflanze mit voreingestelltem Sollwert. Code zu Auswahl Activity:

public class Auswahl extends AppCompatActivity {
   //Wird ausgelöst beim Klick auf den Kaktus Button
   @OnClick(R.id.bt1) void BttnKaktus() {
       //Die message die zurück an unsere MainActivity(BluetoothActivity) geht
       String message="Kaktus";
       //es muss ein Intent erstellt werden den wir zwischen den Activitys senden können 
      Intent intent=new Intent();
       //füge die message an den Intent an 
      intent.putExtra("MESSAGE",message);
       // Rückgabenachricht an die vorherige Activity
       setResult(2,intent);
       //Diese activity schließen
       finish();//finishing activity
   }
...Restliche Buttons zu den anderen Pflanzenarten

Code in unserer MainActivity:

//CallBack Funktion um eine Nachricht von einer anderen Activity zu empfangen
@Override
protected void onActivityResult(final int requestCode,final int resultCode, final Intent data)
{
   super.onActivityResult(requestCode, resultCode, data);
   //Warte 500 Millisekunden da die Bluetooth Verbindung etwas Zeit brauch um wieder aufgebaut zu werden 
  //Wenn man nicht warten würde, würde man die Nachricht aus dem Callback versuchen an den Esp zu schicken bevor die Bluetooth verbindung wieder da ist
   new Handler().postDelayed(new Runnable() {
       @Override
       public void run() {
           // überprüfe ob der resultCode ==2 
          if(requestCode==2)
           {
               TextView t = (TextView) findViewById(R.id.textView10);
               //Überprüfe die Nachricht 
              switch(data.getStringExtra("MESSAGE")) {
                   case "Kaktus":
                       //Send den Befehl "s=10" an den ESP
                       //Damit der Sollwert 10 eingestellt wird
                       if(sendMessage("s=10")==true)
                       {
                           //Falls die Nachricht verschickt werden konnte:
                           //Zeige dem User eine Nachricht an 
                          Toast.makeText(getApplicationContext(),data.getStringExtra("MESSAGE")+ " wurde ausgewählt", Toast.LENGTH_LONG).show();
                           //Setzte den Text 
                          t.setText(data.getStringExtra("MESSAGE"));
                           //Setze das Icon
                           t.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.mipmap.i9, 0);
                           t.setGravity(Gravity.CENTER);
                       }
                       else{
                           //falls die Nachricht nicht abgeschickt werden zeige Fehlermeldung
                           Toast.makeText(getApplicationContext(),"Verbindungsfehler - Bitte versuchen sie es erneut", Toast.LENGTH_LONG).show();
                       }
                       break;
             -----Restliche Cases-----
                   default:
                       break;
               }
           }
       }
   }, 500 );//time in milisecond
}