Bonjour !

Aujourd’hui nous allons parler de la partie conception du petit projet. Après avoir passé commande et reçu les capteurs et l’Arduino Nano, il est temps de passer à la conception de celui-ci. Petite note ! J’ai changé de plante car celle que je voulais utiliser prend tout l’espace niveau racine. Impossible de planter le capteur d’humidité du sol ^^’.

La (nouvelle) plante de test

Tout d’abord, nous allons mettre en place l’électronique. Voici le schéma entier que je vais expliquer.

Schéma électrique (sans la partie pompe à eau)

Il y a 6 grandes parties ici :

  • L’alimentation par pile 9V :
    • Elle est composée d’un régulateur de tension L7805CV de chez STMicroelectronics. Il suffit de mettre le +9V sur le Vin, puis de prendre le +5V sur le Vout. Les communs se rejoignent sur le pin du milieu.
  • L’unité centrale (Arduino Nano)
    • Une Arduino Nano tout ce qu’il y a de plus classique, alimentée non en USB mais via le Pin Vin
  • Communication sans fil (Bluetooth)
    • Ici du Bluetooth 4.0 LE (Le LE est très important car pas tous les appareils en sont dotés.)
  • Capteur d’humidité / température
    • Câblé à l’aide de la docs, mais aucune idée des propriétés utilisées ^^’
  • Capteur de luminosité
    • C’est une photorésistance qui est mis en temps que pont diviseur de tension, nous lisons la tension en sortie que nous convertirons en % à partir de point (0 – 1024)
  • Capteur d’humidité du sol
    • Ici nous récupérons une valeur de tension que nous convertissons en point
Le tout câblé et prêt à l’emploi

Étalonnage

Avant d’utiliser notre capteur d’humidité du sol, nous allons devoir l’étalonné. Il faut se rappeler que nous passerons d’une tension à une valeur en point de 0 à 1023. Voici les valeurs que j’ai relevées pour le mien :

Air ambiant : 266 – Plongé dans l’eau : 643

Il suffit d’établir un pourcentage entre ces valeurs pour avoir quelque chose de plutôt cohérent.

Source de l’image : AZ Delivery

Programmation

Je vais mettre chaque code source et vous expliquer le fonctionnement ^^

Pour l’arduino, rien de bien compliqué. Je setup les ports ainsi que le module Bluetooth (qui communique en UART). Je lui définis simplement son type d’objet.

Je lis ensuite chaque entrée du Bluetooth. Si je reçois un ‘G’. Je fait une mesure et renvois les données dans une structure.

#include <Adafruit_Sensor.h>

#include <DHT.h>
#include <DHT_U.h>

#include <SoftwareSerial.h>

SoftwareSerial BluetoothSerial(4, 3); // RX, TX
DHT TempHumSensor(2, DHT22);

int done = 0;

typedef struct {
  float humidity;
  float temp;
  float   light;
  float   humidity_sol;
} ValeurCapteur;

ValeurCapteur sensor_getValues() {
  ValeurCapteur valeur;
  valeur.humidity     = TempHumSensor.readHumidity(); // Humidité de l'air (%)
  valeur.temp         = TempHumSensor.readTemperature(); // Température de l'air (C°)
  valeur.light        = float(map(analogRead(0), 0, 1023, 0, 100)); // Luminosité photorésustance (%)
  valeur.humidity_sol = float(map(analogRead(1), 266, 643, 100, 0)); // Humidité du sol (%)
  if (valeur.humidity_sol < 0)
    valeur.humidity_sol = 0;

  // D'après la docs arduino, il se pourrais que se soit pas des floats (????) donc je vérifie quand même.
  if (isnan(valeur.humidity) || isnan(valeur.temp)) {
    valeur.humidity = 0;
    valeur.temp = 0;
  }

  return valeur;
}

void bluetooth_setDefaultValue() {
   delay(1500);
   BluetoothSerial.print("AT+PASS0");
   delay(1500);
   BluetoothSerial.print("AT+NAMEBPlante");
   delay(1500);
   BluetoothSerial.print("AT+UUID0x1815");
   delay(1500);
   BluetoothSerial.print("AT+CHAR0x2A4A");
}

void setup() {
 pinMode(5, OUTPUT); // Sortie du relais
 digitalWrite(5, HIGH);

 Serial.begin(9600);
 //Serial.println("En attente du module bluetooth ...");

 BluetoothSerial.begin(9600);
 while (!BluetoothSerial) {}
 
 //Serial.println("Parametrage du capteur de temperature / humiditer  ...");
 TempHumSensor.begin();

 //Serial.println("Demarrage du Bluetooth ...");
 delay(7000);
 BluetoothSerial.print("AT+START");

 //Serial.println("La communication est prête !");
}

void loop() {
  char cmd;
  if (BluetoothSerial.available()) {
    cmd = BluetoothSerial.read();
    switch (cmd) {
      case 'G': {
          //Serial.println("Bluetooth master request for data !");
          ValeurCapteur valeur = sensor_getValues();

          /*
          Serial.print("Humidite: ");
          Serial.print(valeur.humidity);
          Serial.print(" % Temperature: ");
          Serial.print(valeur.temp);
          Serial.print(" °C Lumiere: ");
          Serial.print(valeur.light);
          Serial.print(" % Humidite(Sol): ");
          Serial.print(valeur.humidity_sol);
          Serial.println(" %");
          */
          
          // SoftwareSerian ne dispose pas de la fonction [Write(buffer, len)] donc je vais faire sans
          char* buff = (char*)&valeur;


          for (int i = 0; i < sizeof(ValeurCapteur); i++) {
              BluetoothSerial.write( buff[i] );
          }

          //Serial.println("Donnee envoyer !");
      }
    }
  }

  delay(10000);
}

Du coté Raspberry, j’ai un script Python qui se connecte à la base de donnée et au module Bluetooth. Toute les 60 secondes (changeable dans le script, beaucoup plus pratique que de reprogrammer l’Arduino), nous allons envoyer la commande. Ensuite nous allons simplement attendre la réponse, « Unpack » la structure et écrire les valeurs dans la table ^^.

Note : J’ai volontairement utilisé des float et non des int car les int sur Arduino font 2 bytes. Ce qui n’est pas compris par Struct.Unpack(…) 😛

from bluepy.btle import Scanner, Peripheral, DefaultDelegate
import mysql.connector as mysql
import time
from datetime import datetime
import struct

p = None
svc = None
ch = None
db = None

class planteDelegate(DefaultDelegate):

	def __init__(self):
		DefaultDelegate.__init__(self)
	
	def handleNotification(self, cHandle, data):
		global db

		try:
			data = struct.unpack("ffff", data)
			timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
			print "Humidite: {:2.1f} %".format(data[0])
			print "Temperature: {:2.1f} C".format(data[1])
			print "Lumiere: {:2.1f} %".format(data[2])
			print "Humidite(Sol): {:2.1f} %".format(data[3])

			try:
				query = "INSERT INTO sensor_data (date, temp, humidity, humidity_sol, luminosity) VALUES (%s, %s, %s, %s, %s)"
				values = (timestamp, str(data[1]), str(data[0]), str(data[3]), str(data[2]))
				db.cursor().execute(query, values)
				db.commit()
				print "Donnee enregister dans la base de donnee"
			except:
				print "Erreur de base de donnee"
		except:
			print "Mauvaise notification !"

def FindAndConnect():
	global p
	global svc
	global ch
	scanner = Scanner()
	devices = scanner.scan(10.0)

	for dev in devices:
		if dev.addr == "f8:30:02:29:67:fe":
			print "La plante est trouver ! %s (%s), RSSI=%d dB" % (dev.addr, dev.addrType, dev.rssi)
			print "Recuperation du peripherique ..."
			p = Peripheral( dev.addr )
			p.setDelegate(planteDelegate())
			print "Recuperation du service ..."
			svc = p.getServiceByUUID( 0x1815 )
			print "Recuperation de la characteristique ..."
			ch = svc.getCharacteristics( 0x2A4A )[0]
			print "Connecter a la Plante !"

print "Connexion a la base de donnee ..."
try:
	db = mysql.connect(
	    host = "localhost",
	    user = "theorywrong_plt",
	    passwd = "jaifaillitloublier:')",
	    database = "theorywrong_plt"
	)
except:
	print "Connexion a la base de donnee impossible !"
	exit()

while True:
	if ch is None:
		FindAndConnect()

	try:
		ch.write( 'G' )
		print "Demande de donnee envoyer"
		p.waitForNotifications(10.0)
	except:
		print "Impossible d envoyer les donnee"
		ch = None

	time.sleep(60)

Ici la page html est très simple, avec l’utilisation de JQuery et de ChartJS pour l’affichage dynamique des valeurs relevées.

<!doctype html>
<html lang="fr">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <!-- Open Iconic -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/open-iconic/1.1.1/font/css/open-iconic.min.css" crossorigin="anonymous">

    <title>Capteur - Plante</title>
  </head>
  <body>
	<nav class="navbar navbar-expand-lg navbar-light bg-light">
	  <a class="navbar-brand" href="#">Projet Plante</a>
	  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
	    <span class="navbar-toggler-icon"></span>
	  </button>

	  <div class="collapse navbar-collapse" id="navbarSupportedContent">
	    <ul class="navbar-nav mr-auto">
	      <li class="nav-item active">
	        <a class="nav-link" href="#"><span class="oi" data-glyph="dashboard"></span>  Capteur<span class="sr-only">(current)</span></a>
	      </li>

		  <li class="nav-item dropdown active">
				<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
					<span class="oi" data-glyph="compass"></span> Filtres
				</a>

				<div class="dropdown-menu" aria-labelledby="navbarDropdown">
				  <a class="dropdown-item" onclick="setMode('day'); updateData();">Aujourd'hui</a>
				  <a class="dropdown-item" onclick="setMode('week'); updateData();">Semaine actuel</a>
				  <a class="dropdown-item" onclick="setMode('month'); updateData();">Mois actuel</a>
				  <div class="dropdown-divider"></div>
				  <a class="dropdown-item" onclick="setMode('all'); updateData();">Depuis le début</a>
				</div>
		   </li>	
	    </ul>
	  </div>
	</nav>
	
	<br/>

	<div class="container">
	  <div class="row">
	    <div class="col-lg">
			<div class="card">
			  <div class="card-header">
			    <span class="oi" data-glyph="fire"></span> Température
			  </div>
			  <div class="card-body">
				<canvas id="temp_chart"></canvas>
			  </div>
			</div>
	    </div>
	    <div class="col-lg">
			<div class="card">
			  <div class="card-header">
			    <span class="oi" data-glyph="sun"></span> Luminosité
			  </div>
			  <div class="card-body">
				<canvas id="luminosity_chart"></canvas>
			  </div>
			</div>
	    </div>
	  </div>

	<br/>

	  <div class="row">
	    <div class="col-lg">
			<div class="card">
			  <div class="card-header">
			    <span class="oi" data-glyph="droplet"></span> Humidité
			  </div>
			  <div class="card-body">
				<canvas id="humidity_chart"></canvas>
			  </div>
			</div>
	    </div>
	    <div class="col-lg">
			<div class="card">
			  <div class="card-header">
			    <span class="oi" data-glyph="droplet"></span> Humidité du Sol
			  </div>
			  <div class="card-body">
				<canvas id="humidity_sol_chart"></canvas>
			  </div>
			</div>
	    </div>
	  </div>
	</div>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS, then ChartJS -->
	<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
    <script>
    var mode = "day";
	var temp_ctx = document.getElementById('temp_chart').getContext('2d');
	var luminosity_ctx = document.getElementById('luminosity_chart').getContext('2d');
	var humidity_ctx = document.getElementById('humidity_chart').getContext('2d');
	var humidity_sol_ctx = document.getElementById('humidity_sol_chart').getContext('2d');

	function setMode(new_mode) {
		mode = new_mode;
	}

	var temp_chart = new Chart(temp_ctx, {
	    // The type of chart we want to create
	    type: 'line',

	    // The data for our dataset
	    data: {
	        labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
	        datasets: [{
	            label: 'Température (°C)',
	            backgroundColor: 'rgb(255, 99, 132)',
	            borderColor: 'rgb(255, 0, 0)',
	            data: [0, 10, 5, 2, 20, 30, 45]
	        }]
	    },

	    // Configuration options go here
	    options: {}
	});

	var luminosity_chart = new Chart(luminosity_ctx, {
	    // The type of chart we want to create
	    type: 'line',

	    // The data for our dataset
	    data: {
	        labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
	        datasets: [{
	            label: 'Luminosité (%)',
	            backgroundColor: 'rgb(252, 233, 83)',
	            borderColor: 'rgb(250, 204, 0)',
	            data: [0, 10, 5, 2, 20, 30, 45]
	        }]
	    },

	    // Configuration options go here
	    options: {}
	});

	var humidity_chart = new Chart(humidity_ctx, {
	    // The type of chart we want to create
	    type: 'line',

	    // The data for our dataset
	    data: {
	        labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
	        datasets: [{
	            label: 'Humidité (%)',
	            backgroundColor: 'rgb(99, 161, 255)',
	            borderColor: 'rgb(23, 115, 255)',
	            data: [0, 10, 5, 2, 20, 30, 45]
	        }]
	    },

	    // Configuration options go here
	    options: {}
	});

	var humidity_sol_chart = new Chart(humidity_sol_ctx, {
	    // The type of chart we want to create
	    type: 'line',

	    // The data for our dataset
	    data: {
	        labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
	        datasets: [{
	            label: 'Humidité du Sol (%)',
	            backgroundColor: 'rgb(99, 161, 255)',
	            borderColor: 'rgb(23, 115, 255)',
	            data: [0, 10, 5, 2, 20, 30, 45]
	        }]
	    },

	    // Configuration options go here
	    options: {}
	});

	function updateData() {
		console.log("Data updated.");

		    $.ajax({
		       url : 'sensor_data.php?mode='+mode,
		       type : 'GET',
		       dataType : 'html', // On désire recevoir du HTML
		       success : function(data, statut){
		       		data = JSON.parse(data);

		       		var labels = [];
		       		var temp_data = [];
					var luminosity_data = [];
					var humidity_data = [];
					var humidity_sol_data = [];

		       		data.forEach(obj => {
		       			labels.push(obj.date);
		       			temp_data.push(obj.temp);
		       			luminosity_data.push(obj.luminosity);
		       			humidity_data.push(obj.humidity);
		       			humidity_sol_data.push(obj.humidity_sol);
		       		});

			        temp_chart.data.datasets[0].data = temp_data; 
			    	temp_chart.data.labels = labels;

			        luminosity_chart.data.datasets[0].data = luminosity_data; 
			    	luminosity_chart.data.labels = labels;

			        humidity_chart.data.datasets[0].data = humidity_data; 
			    	humidity_chart.data.labels = labels;

			        humidity_sol_chart.data.datasets[0].data = humidity_sol_data; 
			    	humidity_sol_chart.data.labels = labels;

			    	// Update the chart
			        temp_chart.update();
			        luminosity_chart.update();
			        humidity_chart.update();
			        humidity_sol_chart.update();
		       }
		    });
	}

	(function update() {
	    	updateData();
	    	setTimeout(update, 30000);  // function refers to itself
	})();

    </script>
  </body>
</html>

Enfin, le code PHP ici retourne simplement les données sous un format JSON selon la sélection de l’utilisateur (mois, jour, tout) …

<?php
header("Content-Type:application/json");

switch ($_GET['mode']) {
	case 'all':
		$sql = "SELECT * FROM sensor_data"; // Toute les donnée
		break;

	case 'month':
		$sql = "SELECT * FROM `sensor_data` WHERE date > DATE_SUB(NOW(), INTERVAL 1 MONTH);"; // Mois
		break;

	case 'week':
		$sql = "SELECT * FROM `sensor_data` WHERE date > DATE_SUB(NOW(), INTERVAL 1 WEEK);"; // Semaine
		break;

	case 'day':
		$sql = "SELECT * FROM `sensor_data` WHERE date > DATE_SUB(NOW(), INTERVAL 1 DAY);"; // Jour
		break;
	

	default:
		echo "Invalid request.";
		exit();
		break;
}


$pdo = new PDO('mysql:host=localhost;dbname=theorywrong_plt', 'theorywrong_plt', 'jaifaillitmetrompé!');
$req = $pdo->query($sql);
$req->execute();
$data = $req->fetchAll(PDO::FETCH_OBJ);

echo json_encode($data);
?>

Voilà ! J’ai pas trop détaillé j’en suis désolé, n’hésitez pas à me contacter si vous avez des questions ^^’

Ah j’oubliais ! Vous pouvez retrouver le tout fonctionnelle ici 😉

Bye !


0 commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée.