Smartes Domotik-Gewächshaus: Automatische Klimasteuerung mit Arduino
Komponenten und Zubehör
| Komponente | Menge |
|---|---|
| Arduino MKR1000 | 1 |
| Arduino UNO | 1 |
| DHT22 Temperatursensor | 1 |
| Arduino WiFi Shield 101 | 1 |
| Android-Gerät | 1 |
| DC Motor Control Shield mit BTN8982 für Arduino (Infineon) | 1 |
Apps und Online-Dienste
- Arduino IDE
- Eclipse
Projektübersicht
Die Umgebungsbedingungen in Gewächshäusern ändern sich kontinuierlich, weshalb eine effiziente Überwachung unerlässlich ist. Ein automatisches System bietet zahlreiche Vorteile: weniger manuelle Arbeit, die Möglichkeit, Investitionen per PC oder Smartphone aus der Ferne zu überwachen, sowie die Speicherung von Daten in einer Datenbank – ein entscheidender Faktor für wirtschaftlichen Erfolg. Durch Echtzeit-Steuerung kann sofort eingegriffen werden, um Ernteausfälle zu verhindern.
Das automatische Überwachungssystem besteht aus Sensoren, die Umweltdaten erfassen, und Aktoren (sogenannte „Slaves“). Diese kommunizieren drahtlos mit einer zentralen Einheit („Master“). Der Master sendet Schwellenwert-Änderungen an die Slaves und leitet Daten per WiFi an einen Webserver weiter.
1. Funktionsweise
Das Projekt gliedert sich in drei Hauptbereiche:
- Master (Arduino MKR1000)
- Slave (Arduino UNO)
- Webserver
Der Master (MKR1000) kommuniziert per integriertem WINC1500-WiFi mit dem Webserver. Der Slave (UNO) erfasst Temperatur und Luftfeuchtigkeit über den DHT22-Sensor und sendet diese Daten per WiFi (WiFi Shield WINC1500) an den Master.
2. Master
Der Master arbeitet im „Access Point Mode“ und wartet auf die Verbindung des Slaves, um Temperatur- und Feuchtigkeitsdaten zu empfangen. Diese werden verarbeitet und an den Webserver gesendet. Der Master prüft auf eingehende Daten und erstellt ein UDP-Paket mit neuen Schwellenwerten und CRC. Der CRC wird vom Slave zur Validierung der empfangenen Einstellungen verwendet. Nach dem Verlassen des AP-Modus verbindet sich der Master mit dem WLAN, um Daten an den Webserver zu übertragen, wo sie grafisch dargestellt werden.
3. Slave
Der Slave liest alle 5 Minuten Temperatur und Luftfeuchtigkeit vom DHT22-Sensor, erstellt ein UDP-Paket, verbindet sich mit dem Master und sendet die Daten. Anschließend wartet er auf mögliche neue Schwellenwerte vom Master. Empfängt er solche, berechnet er den CRC nach Dallas-Maxim-Algorithmus und vergleicht ihn mit dem empfangenen CRC. Stimmen beide überein, speichert der Slave die neuen Einstellungen im EEPROM.
4. Webserver
Der Webserver speichert alle Daten für die Historie. Ein PHP-Skript verbindet sich mit der Datenbank und stellt die Daten auf zwei Arten dar:
- Als Grafik (mittels eines weiteren PHP-Skripts)
- In einer Android-App im JSON-Format (über ein drittes PHP-Skript)
5. Android-App
Die App ermöglicht den Zugriff auf die Datenbank. Auf dem ersten Bildschirm wählt man den gewünschten Zeitbereich und ruft per „GRAPHIC“-Button die Daten vom Webservice ab. Die App visualisiert die Daten grafisch.
Code
Grafik-Erstellung (PHP)
<?php
define('__ROOT__', dirname(dirname(__FILE__)));
require_once ('../jpgraph.php');
require_once ('../jpgraph_line.php');
require_once ('../jpgraph_error.php');
$parameter1 = array(); $parameter2 = array(); $time_axis = array(); $i = 0;
$con = mysqli_connect("localhost","user","pass","my_db");
if (mysqli_connect_errno()) {
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
$result = mysqli_query($con,"SELECT your_parameter FROM your_raw");
while($row = mysqli_fetch_array($result)) {
$parameter1[$i] = $row["parameter1"];
$parameter2[$i] = $row["parameter2"];
$time_axis[$i] = $row["date"];
$i++;
}
mysqli_close($con);
$graph = new Graph(300,250);
$graph->SetScale("textlin");
$theme_class = new UniversalTheme;
$graph->SetTheme($theme_class);
$graph->img->SetAntiAliasing(false);
$graph->title->Set('Titel');
$graph->SetBox(false);
$graph->img->SetAntiAliasing();
$graph->yaxis->HideZeroLabel();
$graph->yaxis->HideLine(false);
$graph->yaxis->HideTicks(false,false);
$graph->xgrid->Show();
$graph->xgrid->SetLineStyle("solid");
$graph->xaxis->SetTickLabels($time_axis);
$graph->xgrid->SetColor('#E3E3E3');
$graph->xaxis->SetLabelAngle(90);
$graph->legend->SetPos(0.5,0.08,'center','top');
$p1 = new LinePlot($parameter1);
$graph->Add($p1);
$p1->SetColor("#6495ED");
$p1->SetLegend('Parameter1');
$graph->yscale->SetGrace(0);
$p2 = new LinePlot($parameter2);
$graph->Add($p2);
$p2->SetColor("#B22222");
$p2->SetLegend('Parameter2');
$graph->yscale->SetGrace(0);
$graph->legend->SetFrameWeight(1);
$graph->Stroke();
?>
Webserver-Verbindung & Datenspeicherung (PHP)
<?php
$servername = "localhost"; $username = "user"; $password = "pass"; $dbname = "my_db";
$id = $_GET["name"]; $temp = $_GET["parameter1"]; $umid = $_GET["parameter2"];
$conn = mysqli_connect($servername, $username, $password, $dbname);
if (!$conn) { die("Connection failed: " . mysqli_connect_error()); }
$sql = "INSERT INTO greenhouse (name, parameter1, parameter2) VALUES ('".$name."', '".$parameter1."', '".$parameter2."')";
if (mysqli_query($conn, $sql)) { echo "New record created successfully"; }
else { echo "Error: " . $sql . "
" . mysqli_error($conn); }
mysqli_close($conn);
?>
Tabellen-Erstellung (SQL)
CREATE TABLE IF NOT EXISTS `greenhouse` ( `id` int(11) NOT NULL, `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `temp` float NOT NULL, `umid` float NOT NULL ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Master-Code (C/C++)
/* Automation Greenhouse Project */ /* Autoren: Antonio La Mura, Umberto Festa */ /* Master: Arduino MKR1000 */ #include#include #include #include char ssid[] = "SSID"; char pass[] = "pass"; int keyIndex = 0; char ssid_ap[] = "Arduino"; char pass_ap[] = ""; int status = WL_IDLE_STATUS; unsigned int localPort = 2390; char server[] = "www.cormaz.altervista.org"; WiFiServer server_ap(80); WiFiClient client_ap; WiFiUDP Udp; RTCZero rtc; WiFiClient client; char packetBuffer[255]; char ReplyBuffer[255]; float humax = 0, humin = 0, tumax = 0, tumin = 0; boolean flag = false; boolean threeshold_available = false; void setup() { Serial.begin(9600); while (!Serial); Serial.println(); } void loop() { int packetSize; double temp, hum; int id; byte crc; String strURL; if (WiFi.status() == WL_NO_SHIELD) { Serial.println("WiFi shield not present"); while (true); } while (status != WL_CONNECTED) { Serial.print("Creating Network named: "); Serial.println(ssid_ap); status = WiFi.beginAP(ssid_ap); delay(10000); server_ap.begin(); } Serial.println("Connected to wifi"); Udp.begin(localPort); printWifiStatus(); client_ap = server_ap.available(); if (client_ap) { Serial.println("new client"); packetSize = Udp.parsePacket(); if (packetSize) { int len = Udp.read(packetBuffer, 255); if (len > 0) packetBuffer[len] = 0; char* command = strtok((char *)packetBuffer, ";"); int count = 0; while (command != 0) { switch (count) { case 0: id = atoi(command); break; case 1: temp = atof(command) / 10; break; case 2: hum = atof(command) / 10; break; } command = strtok(0, ";"); count++; } byte bhmax = (byte)humax, bhmin = (byte)humin, btmax = (byte)tumax, btmin = (byte)tumin; byte crc32_str[4] = {bhmax, bhmin, btmax, btmin}; crc = CRC8(crc32_str); if (threeshold_available == true) { snprintf(ReplyBuffer, sizeof(ReplyBuffer), "%d;%d;%d;%d;%d;%d", id, (int)humax, (int)humin, (int)tumax, (int)tumin, (int)crc); Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); Udp.write(ReplyBuffer); Udp.endPacket(); } } client_ap.stop(); Serial.println("client disconnected"); flag = true; } if (flag == true) { while (status != WL_CONNECTED) { Serial.print("Attempting to connect to SSID: "); Serial.println(ssid); status = WiFi.begin(ssid, pass); delay(10000); } printWifiStatus(); strURL = "GET /YourAddress.php?id="; strURL += id; strURL += "¶meter1="; strURL += temp; strURL += "¶meter2="; strURL += hum; strURL += " HTTP/1.1"; if (client.connect(server, 80)) { client.println(strURL); client.println("Host: www.cormaz.altervista.org"); client.println("Connection: close"); client.println(); client.stop(); } if (!client.connected()) { client.stop(); while (true); } flag = false; } } byte CRC8(const byte * data) { byte crc = 0x00; while (*data) { byte extract = *data++; for (byte tempI = 8; tempI; tempI--) { byte sum = (crc ^ extract) & 0x01; crc >>= 1; if (sum) crc ^= 0x8C; extract >>= 1; } } return crc; } void printWifiStatus() { Serial.print("SSID: "); Serial.println(WiFi.SSID()); IPAddress ip = WiFi.localIP(); Serial.print("IP Address: "); Serial.println(ip); long rssi = WiFi.RSSI(); Serial.print("signal strength (RSSI):"); Serial.print(rssi); Serial.println(" dBm"); }
Slave-Code (C/C++)
/* Slave: Arduino UNO mit DHT22, WiFi Shield, Motorsteuerung */ #include#include #include #include #include #define HUMIDIFIER A4 #define HEATER A5 #define DHT22_PIN 4 #define IS_1 0 #define IS_2 1 #define IN_1 3 #define IN_2 11 #define INH_1 12 #define INH_2 13 #define TCONST 100 extern unsigned long timer0_millis; DHT22 myDHT22(DHT22_PIN); WiFiUDP Udp; float hmin=0, hmax=0, tmin=0, tmax=0; int duty_motor=0; float humidity, temperature; unsigned long interval = 600000; char ssid[] = "Arduino"; char pass[] = ""; int status = WL_IDLE_STATUS; void setup() { Serial.begin(9600); while (!Serial); pinMode(HEATER, OUTPUT); digitalWrite(HEATER, LOW); pinMode(HUMIDIFIER, OUTPUT); digitalWrite(HUMIDIFIER, LOW); pinMode(IN_1, OUTPUT); pinMode(IN_2, OUTPUT); pinMode(INH_1, OUTPUT); pinMode(INH_2, OUTPUT); reset_ports(); digitalWrite(INH_1, 1); digitalWrite(INH_2, 1); Serial.println("Beginning ..."); delay(2000); } void loop() { int packetSize = Udp.parsePacket(); unsigned long time = millis(); humidity = myDHT22.getHumidity() * 10; temperature = myDHT22.getTemperatureC() * 10; DHT22_ERROR_t errorCode = myDHT22.readData(); byte crc; int crc_ric; char toSend[32]; char name[] = "clie1"; if (millis() > interval) { if (WiFi.status() == WL_NO_SHIELD) { Serial.println("WiFi shield not present"); while (true); } while (status != WL_CONNECTED) { status = WiFi.begin(ssid); delay(10000); } Udp.begin(localPort); snprintf(toSend, sizeof(toSend), "%s;%d;%d", name, (int)humidity, (int)temperature); Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); Udp.write(toSend); Udp.endPacket(); resetMillis(); while (millis() < 10000) { packetSize = Udp.parsePacket(); if (packetSize) { int len = Udp.read(packetBuffer, 255); if (len > 0) packetBuffer[len] = 0; char* command = strtok((char *)packetBuffer, ";"); int count = 0; while (command != 0) { switch (count) { case 1: hmax = atof(command)/10; break; case 2: hmin = atof(command)/10; break; case 3: tmax = atof(command)/10; break; case 4: tmin = atof(command)/10; break; case 5: crc_ric = atoi(command); break; } command = strtok(0, ";"); count++; } byte bhmax = (byte)hmax, bhmin = (byte)hmin, btmax = (byte)tmax, btmin = (byte)tmin; byte crc32_str[4] = {bhmax, bhmin, btmax, btmin}; crc = CRC8(crc32_str); if (crc_ric == (int)crc) { EEPROM_writeDouble(0, tmax); EEPROM_writeDouble(4, tmin); EEPROM_writeInt(8, hmax); EEPROM_writeInt(10, hmin); } } delay(10); } } WiFi.disconnect(); // Aktoren steuern if (myDHT22.getTemperatureC() >= tmax) digitalWrite(HEATER, HIGH); if (myDHT22.getTemperatureC() <= tmin + 1) digitalWrite(HEATER, LOW); if ((int)myDHT22.getHumidity() >= hmax) digitalWrite(HUMIDIFIER, HIGH); if ((int)myDHT22.getHumidity() <= hmin + 1) digitalWrite(HUMIDIFIER, LOW); // Lüftersteuerung if (myDHT22.getTemperatureC() >= tmax + 4) { duty_motor = map(myDHT22.getTemperatureC(), tmax + 4, tmax + 10, 0, 100); if (myDHT22.getTemperatureC() > tmax + 10) duty_motor = 100; analogWrite(IN_2, duty_motor); delay(TCONST); } if (myDHT22.getTemperatureC() <= (tmax + tmin) / 2) { reset_ports(); duty_motor = 0; analogWrite(IN_2, duty_motor); delay(TCONST); } delay(1000); } // EEPROM-Hilfsfunktionen, CRC8, resetMillis() etc. folgen...
App-Datenabruf (PHP)
<?php
$username = "root"; $password = ""; $hostname = "localhost";
$dbhandle = mysql_connect($hostname, $username, $password) or die("Unable to connect to MySQL");
$selected = mysql_select_db("serra",$dbhandle) or die("Could not select examples");
$datada = $_GET["datada"]; $dataa = $_GET["dataa"];
$result = mysql_query("SELECT hum,temp,date FROM record WHERE date >= '".$datada."' and date <='".$dataa."'");
$records = array();
while ($row = mysql_fetch_array($result)) {
$tmp = array("hum"=>$row{'hum'}, "temp"=>$row{'temp'}, "date"=>$row{'date'});
array_push($records,$tmp);
}
echo json_encode($records); mysql_close($dbhandle);
?>
Herstellungsprozess
- Professionelle Bitmap-Animation auf dem ILI9341 2,4″ TFT Touchscreen Shield mit Visuino
- Web-gesteuertes DMX-Lichtsystem – Arduino Master Controller
- Bluetooth-gesteuerter Arduino-Spybot bauen – Überwachungsroboter mit IP-Kamera
- FlickMote: Gestensteuerung per Ultraschall & IR für Smart Home (Arduino 101)
- DIY Arduino TV-B-Gone: Universelle TV-Aus-Fernbedienung
- Einen eigenen LED-Master-Clock mit Alarm bauen – aus 12 Metern Entfernung ablesbar
- HSV-Uhr: Lebendige, anpassbare Zeitanzeige für Arduino-Enthusiasten
- Android-gesteuertes Roboterfahrzeug mit Raspberry Pi Motor Shield
- Find Me: Smarter Gegenstands-Finder mit Arduino und Bluetooth
- Optimierte Stromversorgungslösungen für Arduino-Projekte