Serre Domotique Intelligente : Contrôle Climatique Automatisé avec Arduino
Composants et fournitures
| Composant | Quantité |
|---|---|
| Arduino MKR1000 | 1 |
| Arduino UNO | 1 |
| Capteur de température DHT22 | 1 |
| Shield WiFi Arduino 101 | 1 |
| Appareil Android | 1 |
| Shield de contrôle moteur DC avec BTN8982 pour Arduino Infineon | 1 |
Applications et services en ligne
- Arduino IDE
- Eclipse
À propos de ce projet
Vue d'ensemble
Les conditions environnementales d'une serre évoluent en permanence, d'où la nécessité d'une surveillance efficace. Un système automatisé offre de nombreux avantages : réduction de la charge de travail, suivi à distance des cultures depuis un ordinateur ou un smartphone, et stockage des données en base de données pour une analyse historique — un atout décisif pour la rentabilité. Grâce à un contrôle en temps réel, il est possible d'intervenir immédiatement pour prévenir tout problème de culture.
Le système de surveillance automatique se compose de capteurs (esclaves) qui lisent les données environnementales et d'actionneurs. Ils communiquent sans fil avec un dispositif central (maître). Ce dernier envoie les modifications de seuils aux esclaves et transmet les données via WiFi au serveur web.
1. Fonctionnement
Le projet se divise en trois parties :
- Maître (Arduino MKR1000)
- Esclave (Arduino UNO)
- Serveur Web
Le maître communique avec le serveur web via WiFi (module WINC1500 intégré). L'esclave acquiert la température et l'humidité via le capteur DHT22, puis envoie ces données au maître via WiFi (shield WiFi WINC1500).
2. Maître
Le maître fonctionne en mode « Point d'accès » et attend la connexion de l'esclave pour recevoir les mesures de température et d'humidité. Il les traite et les transmet au serveur web. Le maître vérifie la disponibilité des données, crée un paquet UDP contenant les nouveaux seuils et un CRC (Cyclic Redundancy Check) calculé selon l'algorithme Dallas-Maxim, que l'esclave utilisera pour valider les paramètres reçus. Une fois sorti du mode point d'accès, le maître se connecte au WiFi pour envoyer les données au serveur web où elles seront affichées sous forme de graphiques.
3. Esclave
L'esclave acquiert la température et l'humidité via le capteur DHT22 et envoie les données au maître toutes les 5 minutes. Il crée un paquet UDP, se connecte au maître et transmet les données. Il attend ensuite d'éventuelles nouvelles valeurs de seuils. Dans ce cas, l'esclave reçoit les nouveaux paramètres, calcule le CRC selon la formule Dallas-Maxim et le compare au CRC reçu du maître. Si les deux CRC correspondent, l'esclave enregistre les nouveaux paramètres dans l'EEPROM.
4. Serveur Web
Le serveur web enregistre toutes les données pour constituer un historique. Un script PHP se connecte à la base de données et présente les données de deux manières : sous forme de graphique (via un autre script PHP) et dans une application Android au format JSON (via un troisième script PHP).
5. Application Mobile
L'application permet de consulter les données stockées en base. Sur le premier écran, l'utilisateur sélectionne la plage temporelle à afficher. En appuyant sur le bouton « GRAPHIQUE », l'application contacte le service web et récupère les données pour les afficher sous forme graphique.
Code
Création du graphique (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 "Échec de la connexion à 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('Titre');
$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('paramètre1');
$graph->yscale->SetGrace(0);
$p2 = new LinePlot($parameter2);
$graph->Add($p2);
$p2->SetColor("#B22222");
$p2->SetLegend('paramètre2');
$graph->yscale->SetGrace(0);
$graph->legend->SetFrameWeight(1);
$graph->Stroke();
?>
Script de connexion au site web (PHP)
<?php
$servername = "localhost";
$username = "user";
$password = "pass";
$dbname = "my_db";
$id = $_GET["name"];
$temp = $_GET["parameter1"];
$umid = $_GET["parameter2"];
echo "name " . $_GET['name'] . "
";
echo "parameter1 " . $_GET['parameter1'] . "
";
echo "parameter2 " . $_GET['parameter2'] . "
";
$conn = mysqli_connect($servername, $username, $password, $dbname);
if (!$conn) {
die("Échec de la connexion : " . mysqli_connect_error());
}
$sql = "INSERT INTO greenhouse (name, parameter1, parameter2) VALUES ('".$id."', '".$temp."', '".$umid."')";
if (mysqli_query($conn, $sql)) {
echo "Nouvel enregistrement créé avec succès";
} else {
echo "Erreur : " . $sql . "
" . mysqli_error($conn);
}
mysqli_close($conn);
?>
Création de la table (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;
Code du Maître (C/C++)
/* Projet d'automatisation de serre */
/* Auteurs : Antonio La Mura, Umberto Festa */
/* Date : 03/03/2016 */
#include <SPI.h>
#include <WiFi101.h>
#include <RTCZero.h>
#include <WiFiUDP.h>
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("Shield WiFi non présent");
while (true);
}
while (status != WL_CONNECTED) {
Serial.print("Création du réseau : ");
Serial.println(ssid_ap);
status = WiFi.beginAP(ssid_ap);
delay(10000);
server_ap.begin();
}
Serial.println("Connecté au WiFi");
Udp.begin(localPort);
printWifiStatus();
client_ap = server_ap.available();
if (client_ap) {
Serial.println("Nouveau client");
packetSize = Udp.parsePacket();
if (packetSize) {
Serial.print("Paquet reçu de taille ");
Serial.println(packetSize);
IPAddress remoteIp = Udp.remoteIP();
Serial.print("De ");
Serial.print(remoteIp);
Serial.print(", port ");
Serial.println(Udp.remotePort());
int len = Udp.read(packetBuffer, 255);
if (len > 0) packetBuffer[len] = 0;
Serial.println("Contenu :");
Serial.println(packetBuffer);
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++;
}
Serial.print("Paquet reçu de ");
Serial.print(id);
Serial.print(" T: ");
Serial.print(temp, 1);
Serial.print(" H: ");
Serial.println(hum, 1);
delay(20);
byte bhmax = (byte)humax;
byte bhmin = (byte)humin;
byte btmax = (byte)tumax;
byte btmin = (byte)tumin;
byte crc32_str[4] = { bhmax, bhmin, btmax, btmin };
crc = CRC8(crc32_str);
Serial.println("CRC : ");
Serial.println(crc);
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 déconnecté");
flag = true;
}
if (flag == true) {
while (status != WL_CONNECTED) {
Serial.print("Tentative de connexion au SSID : ");
Serial.println(ssid);
status = WiFi.begin(ssid, pass);
delay(10000);
}
Serial.println("Connecté au WiFi");
printWifiStatus();
strURL = "GET /YourAddress.php?id=";
strURL += id;
strURL += "¶meter1=";
strURL += temp;
strURL += "¶meter2=";
strURL += hum;
strURL += " HTTP/1.1";
Serial.println("\nDémarrage de la connexion au serveur...");
if (client.connect(server, 80)) {
Serial.println("Connecté au serveur");
client.println(strURL);
client.println("Host: www.cormaz.altervista.org");
client.println("Connection: close");
client.println();
client.stop();
Serial.println("OK !");
}
if (!client.connected()) {
Serial.println();
Serial.println("Déconnexion du serveur.");
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("Adresse IP : ");
Serial.println(ip);
long rssi = WiFi.RSSI();
Serial.print("Force du signal (RSSI) : ");
Serial.print(rssi);
Serial.println(" dBm");
}
Code de l'Esclave (C/C++)
/* Projet d'automatisation de serre */
/* Auteurs : Antonio La Mura, Umberto Festa */
/* Date : 21/03/2016 */
#include <SPI.h>
#include <WiFi101.h>
#include <WiFiUdp.h>
#include <EEPROM.h>
#include <DHT22.h>
#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;
int status = WL_IDLE_STATUS;
char ssid[] = "Arduino";
char pass[] = "";
int keyIndex = 0;
unsigned int localPort = 2390;
char packetBuffer[255];
WiFiUDP Udp;
DHT22 myDHT22(DHT22_PIN);
float hmin = 0, hmax = 0, tmin = 0, tmax = 0;
int duty_motor = 0;
float humidity, temperature;
unsigned long interval = 600000;
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("Démarrage ... ");
delay(2000);
}
void loop() {
int packetSize = Udp.parsePacket();
unsigned long time = millis();
unsigned long currentMillis = millis();
DHT22_ERROR_t errorCode;
int i = 0;
char name[] = "clie1";
humidity = myDHT22.getHumidity() * 10;
temperature = myDHT22.getTemperatureC() * 10;
char toSend[32];
errorCode = myDHT22.readData();
byte crc;
int crc_ric;
switch (errorCode) {
case DHT_ERROR_NONE:
char buf[128];
sprintf(buf, "Lecture entière : Température %hi.%01hi C, Humidité %i.%01i %% RH",
myDHT22.getTemperatureCInt() / 10, abs(myDHT22.getTemperatureCInt() % 10),
myDHT22.getHumidityInt() / 10, myDHT22.getHumidityInt() % 10);
break;
case DHT_ERROR_CHECKSUM: break;
case DHT_BUS_HUNG: break;
case DHT_ERROR_NOT_PRESENT: break;
case DHT_ERROR_ACK_TOO_LONG: break;
case DHT_ERROR_SYNC_TIMEOUT: break;
case DHT_ERROR_DATA_TIMEOUT: break;
case DHT_ERROR_TOOQUICK: break;
}
if (humidity != myDHT22.getHumidity() * 10 || temperature != myDHT22.getTemperatureC() * 10) {
Serial.print("T : ");
Serial.print(myDHT22.getTemperatureC(), 1);
Serial.print("C H : ");
Serial.print(myDHT22.getHumidity(), 1);
Serial.println("%");
}
if (millis() > interval) {
if (WiFi.status() == WL_NO_SHIELD) {
Serial.println("Shield WiFi non présent");
while (true);
}
while (status != WL_CONNECTED) {
Serial.print("Tentative de connexion au SSID : ");
Serial.println(ssid);
status = WiFi.begin(ssid);
delay(10000);
}
Serial.println("Connecté au WiFi");
Serial.println("\nDémarrage de la connexion au serveur...");
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();
Serial.println("Envoi terminé");
resetMillis();
while (millis() < 10000) {
packetSize = Udp.parsePacket();
if (packetSize) {
int len = Udp.read(packetBuffer, 255);
if (len > 0) packetBuffer[len] = 0;
Serial.println("Contenu :");
Serial.println(packetBuffer);
char* command = strtok((char*)packetBuffer, ";");
int count = 0;
while (command != 0) {
switch (count) {
case 0: snprintf(name, sizeof(name), "%s", command); break;
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++;
}
Serial.print("Réponse : ");
Serial.print(name);
Serial.print(";");
Serial.print(hmax, 1);
Serial.print(";");
Serial.print(hmin, 1);
Serial.print(";");
Serial.print(tmax, 1);
Serial.print(";");
Serial.println(tmin, 1);
Serial.print("CRC reçu : ");
Serial.println(crc_ric);
byte bhmax = (byte)hmax;
byte bhmin = (byte)hmin;
byte btmax = (byte)tmax;
byte btmin = (byte)tmin;
byte crc32_str[4] = { bhmax, bhmin, btmax, btmin };
crc = CRC8(crc32_str);
Serial.println("CRC : ");
Serial.println(crc);
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();
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);
}
if (myDHT22.getTemperatureC() >= tmax + 4) {
duty_motor = map(i, tmax + 4, tmax + 10, 0, 100);
if (tmax > 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);
}
void EEPROM_writeDouble(int ee, double value) {
byte* p = (byte*)(void*)&value;
for (int i = 0; i < sizeof(value); i++) EEPROM.write(ee++, *p++);
}
void EEPROM_writeInt(int ee, int value) {
byte* p = (byte*)(void*)&value;
for (int i = 0; i < sizeof(value); i++) EEPROM.write(ee++, *p++);
}
double EEPROM_readDouble(int ee) {
double value = 0.0;
byte* p = (byte*)(void*)&value;
for (int i = 0; i < 4; i++) *p++ = EEPROM.read(ee++);
return value;
}
int EEPROM_readInt(int ee) {
int value = 0;
byte* p = (byte*)(void*)&value;
for (int i = 0; i < 2; i++) *p++ = EEPROM.read(ee++);
return value;
}
void reset_ports() {
digitalWrite(IN_1, 0);
digitalWrite(IN_2, 0);
}
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 resetMillis() {
cli();
timer0_millis = 0;
sei();
}
Script getdati (PHP)
<?php
$username = "root";
$password = "";
$hostname = "localhost";
$dbhandle = mysql_connect($hostname, $username, $password) or die("Impossible de se connecter à MySQL");
$selected = mysql_select_db("serra", $dbhandle) or die("Impossible de sélectionner la base");
$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);
?>
Application Greenhouse
Sur la première page, l'utilisateur définit la plage de dates à l'aide de deux sélecteurs de date. En appuyant sur le bouton « GRAPHIQUE », l'application effectue un appel HTTP GET vers le script PHP « getdati » en envoyant les paramètres et dates choisis. Elle reçoit alors le JSON, le décode et génère le graphique à l'aide de la bibliothèque GraphView.
Schémas
Connexion : Arduino UNO - DHT22 - Relais
Processus de fabrication
- Guide professionnel : Animation bitmap sur écran tactile ILI9341 2,4″ avec Visuino
- Système d'éclairage DMX piloté par web – Contrôleur maître Arduino
- Construire un Spybot Arduino piloté par Bluetooth
- FlickMote : Télécommande IR à gestes pour maison connectée
- TV-B-Gone DIY Arduino : Télécommande universelle pour toutes les TV
- Construire une horloge maître LED personnalisée avec alarme – Visible à 12 mètres
- Horloge HSV : Un Garde-Temps Vibrant et Personnalisable pour Passionnés d'Arduino
- Véhicule Télécommandé par Android avec Raspberry Pi et Shield Moteur
- Find Me : Localisateur d'objets intelligent avec Arduino et Bluetooth