Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Composants et Fournitures
| Composant | Quantité | Notes |
|---|---|---|
| Arduino UNO | 1 | |
| Arduino MKR GSM 1400 | 1 | |
| Transducteur de pression (0-150 psi) | 1 | |
| Capteur Température/Humidité Adafruit DHT22 | 1 | |
| Module RTC DS3231 | 1 | |
| Convertisseur de niveau logique Adafruit BSS138 (4 canaux I2C bidir) | 1 | |
| Batterie LiPo 3.7V 2000 mAh+ | 2 | |
| Convertisseur step-up tension | 1 | |
| Kit de base LEDs & résistances | 1 | |
| Plaque d'essai (breadboard) | 1 |
Applications et Services en Ligne
- Google Sheets
- Arduino IoT Cloud
- Arduino Web Editor
- Arduino IDE
À propos du projet
Objectif
Créer un prototype de dispositif low-cost pour surveiller la pression sur équipements industriels en sites distants via données cellulaires.





Parcours du projet (flux de données)
Étape 1 : Transducteur → Uno
Le transducteur convertit la pression en signal électrique analogique. L'Arduino Uno convertit ce signal en pression (psi).
Étape 2 : Uno → MKR GSM 1400 via Série
Communication série entre deux Arduino : RX Uno → TX MKR, TX Uno → RX MKR, masse commune.
Fréquence transmission (Uno → MKR) :
- NORMALE : Toutes les 30 min (
transmitFrequency), l'Uno envoie les données au MKR qui les transmet au cloud. - DÉCLENCHEMENT HAUT/BAS : Si pression > 40 psi (
highTrigger) ou < 20 psi (lowTrigger) pendant > 2 min (dtLastTriggerLimit), envoi immédiat. - INTERROGATION À LA DEMANDE : Si broche A1 Uno passe HIGH (bouton poussoir sur breadboard OU broche A3 MKR mise HIGH via Arduino Cloud), envoi immédiat. Permet lecture à distance sans attendre l'envoi programmé.
Notes : le sketch Uno peut être étendu à d'autres entrées (température, humidité, tension batterie...). Conversion analogique → psi basée sur cette vidéo YouTube. Guide "Serial Input Basics" du forum Arduino très utile : forum.arduino.cc.
Code Uno : fichier "InstrumentReader" (avec commentaires).
Étape 3 : MKR GSM 1400 → Arduino Cloud via Cellulaire
Le MKR traite les données série de l'Uno et les transmet à l'Arduino Cloud via GSM.
Note : le code MKR utilise Serial1.read (pas Serial.read). Sur les cartes MKR, les broches TX/RX sont accessibles via Serial1 (voir référence Arduino).

Arduino Cloud : 2 variables configurées :

- dataStringCloud : paquet contenant toutes les données du device. Approche choisie pour simplifier le traitement dans Google Sheets (difficile de distinguer valeur inchangée vs non mise à jour avec variables séparées). Données parsées dans Sheets.
- pinCloud : contrôle le MKR depuis le Cloud. Switch dans le sketch :
pinCloud=1→ allume LED (broche A1 HIGH),pinCloud=2→ broche A3 HIGH → déclenche envoi données depuis Uno.
Code MKR : fichier "CommunicationsDevice" (avec commentaires).
Étape 4 : Arduino Cloud → Google Sheets via Webhook
Transfert via webhook. Cœur = fonction doPost en JavaScript dans le script du fichier Google Sheets.
Configuration webhook (départ depuis Google Sheets) :
- Créer nouveau fichier Google Sheets
- Outils → Éditeur de script
- Écrire code avec fonction doPost (voir
GoogleSheetsScript.jsjoint) - Publier → Déployer en tant qu'application web...
- Champs : (1) Version projet : Nouveau (toujours via dropdown), (2) Exécuter en tant que : moi (votre@email.com), (3) Accès : Tout le monde, même anonyme
- Déployer → récupérer "Current web app URL" → coller dans onglet Webhook Arduino Cloud (URL stable)
Code largement inspiré du projet "Arduino IoT Cloud Google Sheets Integration" : lien Project Hub
Étape 5 : Google Sheets - Parsing des données
Extraction des valeurs individuelles depuis dataStringCloud et affichage des enregistrements uniques.
Fichier Sheets de test récent (cellules colorées selon origine) : lien Google Sheets





Étape 6 : Google Sheets - Notifications par email
Deux fonctions dans GoogleSheetsScript.js :
doPost: reçoit données du webhook Arduino Cloud (exécution à chaque nouvelle donnée)sendEmail: envoie emails selon valeurs extraites de la feuille "Data". Exécution chaque minute via déclencheur.
Configuration déclencheur pour sendEmail :
- Ouvrir fichier Sheets → Outils → Éditeur de script
- Édition → Déclencheurs du projet actuel
- Ajouter déclencheur (bas droite) → fonction
sendEmail→ basé sur le temps (toutes les minutes)

Le déclencheur temporel permet d'alerter quand le device cesse d'envoyer des données.
Autonomie Batterie
~24 heures. Optimisable : éteindre/retirer les afficheurs, supprimer capteurs non essentiels (DHT22, DS3231).
Consommation Données
~0.7 Mo/jour. Optimisable : réduire taille/fréquence transmissions (ex: envoyer seulement pression, ou passer à 1h au lieu de 30 min).
Coût du Projet
Total = 241 $
- Arduino MKR GSM 1400 (70 $)
- Arduino Uno (22 $)
- 2 × Batteries LiPo 3.7V (30 $)
- 2 × Afficheurs LED (29 $)
- Boîtier plastique résistant intempéries (22 $)
- Capteur pression (19 $)
- Capteur T°/Humidité DHT22 (10 $)
- Module RTC DS3231 (5 $)
- Convertisseur step-up (5 $)
- Convertisseur niveau logique (4 $)
- Divers - LEDs, résistances, câblage (25 $)
Matériel / Outils
Tous achetés chez : Arduino Store, Adafruit, Amazon, Harbor Freight, Home Depot.










Conclusion
Merci d'avoir lu ce projet. Questions, retours, commentaires, suggestions bienvenus !
Code
InstrumentReader - Sketch pour Arduino Uno
// Sketch 1 sur 2 // Arduino Uno
// Données collectées ici, transmises au MKR 1400 via série
// DHT22 nécessite 2 bibliothèques : (1) DHT Sensor Library, (2) Adafruit Unified Sensor
#include <SPI.h>
#include <Wire.h>
#include <DS3231.h> // Module RTC
#include <DHT.h> // DHT22
#include "U8glib.h" // Afficheur OLED SPI Velleman 128x64
// Note: bibliothèque U8glib standard ne marchait pas, utiliser celle du fabricant
// Lib: https://www.velleman.eu/support/downloads/?code=VMA437
// Syntaxe: https://github.com/olikraus/u8glib/wiki/userreference
RTClib RTC;
#define DHTPIN 11
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
U8GLIB_SH1106_128X64 u8g(3, 4, 6, 7); // CLK:3, MOSI:4, CS:6, DC:7
int y_pos = 0;
const int ledPin = 9; // indicateur transmission
const int ledPin2 = 8; // indicateur transmission forcée
const int buttonPin = A1;
int buttonState = 0;
int transmitFrequency = 30; // minutes
String pTransmitDateTime = "";
int transmitCounter = 0;
int pTransmitMinute = 0;
int ptriggerTransmitAlertIndicator;
float cRuntimeAtTriggerStart = 0.0;
float dtLastTrigger = 0.0;
int triggerCounter = 0;
int triggerTransmitAlertCounter = 0;
float lowTrigger = 20.0;
float highTrigger = 40.0;
float dtLastTriggerLimit = 2.0;
void setup(void) {
Serial.begin(9600);
Wire.begin();
dht.begin();
pinMode(ledPin, OUTPUT);
pinMode(ledPin2, OUTPUT);
pinMode(buttonPin, INPUT);
u8g.setRot180(); // retourner écran si nécessaire
}
void loop(void) {
delay(5000);
DateTime now = RTC.now();
float cRuntime = millis() / 60000;
float p = getPressure();
buttonState = digitalRead(buttonPin);
if (buttonState == 1) {
digitalWrite(ledPin2, HIGH);
delay(30000); // délai pour que MKR1400 soit prêt
} else {
digitalWrite(ledPin2, LOW);
}
float h = dht.readHumidity();
float t = dht.readTemperature(true); // true = Fahrenheit
int transmitIndicator = 0;
if (now.minute() % transmitFrequency == 0 && now.minute() != pTransmitMinute) {
transmitIndicator = 1;
pTransmitMinute = now.minute();
pTransmitDateTime = String(now.hour()) + ":" + String(now.minute()) + ":" + String(now.second());
}
int triggerStatus = 0;
if (p <= lowTrigger || p >= highTrigger) {
triggerStatus = 1;
triggerCounter++;
} else {
triggerCounter = 0;
}
if (triggerStatus == 1 && triggerCounter == 1) {
cRuntimeAtTriggerStart = cRuntime;
}
dtLastTrigger = cRuntime - cRuntimeAtTriggerStart;
int triggerTransmitAlertIndicator = 0;
if ((dtLastTrigger > dtLastTriggerLimit) && triggerStatus == 1) {
triggerTransmitAlertIndicator = 1;
triggerTransmitAlertCounter++;
} else {
triggerTransmitAlertCounter = 0;
}
if (triggerTransmitAlertCounter > 0 && triggerTransmitAlertCounter % 10 == 0) {
flashLED(2, 500);
}
int triggerPushTransmitAlertIndicator = 0;
if ((triggerTransmitAlertIndicator == 1 && triggerTransmitAlertCounter == 1) ||
ptriggerTransmitAlertIndicator != triggerTransmitAlertIndicator) {
triggerPushTransmitAlertIndicator = 1;
flashLED(5, 500);
delay(5000);
}
ptriggerTransmitAlertIndicator = triggerTransmitAlertIndicator;
String dataString = "", cDateTime = "", cHumTemp = "", cP = "";
dataString += "<" + String(i) + "," + String(triggerTransmitAlertIndicator) + "," +
String(dtLastTrigger, 0) + "," + String(buttonState) + ", " +
String(now.month()) + "," + String(now.day()) + "," + String(now.year()) + ", " +
String(now.hour()) + "," + String(now.minute()) + "," + String(now.second()) + ", " +
String(h) + "," + String(t) + "," + String(p) + ">";
cDateTime += String(now.month()) + "/" + String(now.day()) + "/" + String(now.year()) + " " +
String(now.hour()) + ":" + String(now.minute()) + ":" + String(now.second());
cHumTemp += "H:" + String(h) + "% T:" + String(t) + "degF";
cP += "P:" + String(p) + "psi";
if (transmitIndicator == 1 || triggerPushTransmitAlertIndicator == 1 || buttonState == 1) {
char dataArray[100];
dataString.toCharArray(dataArray, 100);
Serial.println(dataArray);
flashLED(10, 500);
transmitCounter++;
}
delay(100);
u8g.firstPage();
do {
draw(cDateTime, cHumTemp, cP, i, transmitCounter, now.minute(), transmitFrequency, pTransmitMinute);
} while (u8g.nextPage());
delay(1000);
i++;
}
void draw(String DcDateTime, String DcHumTemp, String DcP, int Di, int DtransmitCounter,
int DnowMinute, int DtransmitFrequency, int DpTransmitMinute) {
u8g.begin();
u8g.setFont(u8g_font_5x7);
u8g.setFontPosTop();
u8g.setPrintPos(0, 0); u8g.print(DcDateTime);
u8g.setPrintPos(0, 8); u8g.print(2);
u8g.setPrintPos(10, 8); u8g.print(DcHumTemp);
u8g.setPrintPos(0, 16); u8g.print("3 #:"); u8g.setPrintPos(30, 16); u8g.print(Di);
u8g.setPrintPos(50, 16); u8g.print(DcP);
u8g.setPrintPos(0, 24); u8g.print("4 #t:"); u8g.setPrintPos(30, 24); u8g.print(DtransmitCounter);
u8g.setPrintPos(50, 24); u8g.print("tFreq: "); u8g.setPrintPos(83, 24); u8g.print(DtransmitFrequency);
u8g.setPrintPos(0, 32); u8g.print(5); u8g.setPrintPos(10, 32); u8g.print("nowMinute:");
u8g.setPrintPos(70, 32); u8g.print(DnowMinute);
u8g.setPrintPos(0, 40); u8g.print(6); u8g.setPrintPos(10, 40); u8g.print("pTransmitMinute:");
u8g.setPrintPos(95, 40); u8g.print(DpTransmitMinute);
u8g.setPrintPos(0, 48); u8g.print(7); u8g.setPrintPos(10, 48); u8g.print("Remainder:");
u8g.setPrintPos(70, 48); u8g.print(DnowMinute % DtransmitFrequency);
}
float getPressure() {
int sensorVal = analogRead(A2);
float voltage = (sensorVal * 5.0) / 1023.0;
// Pression 0 → Analog 100 → Voltage 0.4889V
float m = ((150 - 0) / (4.5 - 0.4889));
float b = 150 - (m * 4.5);
float pressure_psi = (m * voltage) + b;
return pressure_psi;
}
void flashLED(int num, int t) {
for (int z = 1; z <= num; z++) {
digitalWrite(ledPin, HIGH); delay(t);
digitalWrite(ledPin, LOW); delay(t);
}
}
GoogleSheetsScript.js (JavaScript)
// 2 fonctions : doPost (webhook Arduino Cloud) et sendEmail (alertes email, 1/min via déclencheur)
// Majoritairement inspiré de : https://github.com/arduino/arduino-iot-google-sheet-script/blob/master/Code.gs
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("RawData");
var sd = ss.getSheetByName("Data");
var sref = ss.getSheetByName("References");
var MAX_ROWS = 1440; // max lignes affichées (ex: 12h @ 30s = 1440)
var HEADER_ROW = 1;
var TIMESTAMP_COL = 1;
function doPost(e) {
var cloudData = JSON.parse(e.postData.contents);
console.log(cloudData);
var values = cloudData.values; // array d'objets JSON
console.log(values);
var incLength = values.length;
var incNames = [], incValues = [];
for (var i = 0; i < incLength; i++) {
incNames[i] = values[i].name;
incValues[i] = values[i].value;
}
console.log(incNames); console.log(incValues);
var timestamp = values[0].updated_at; // format: yyyy-MM-ddTHH:mm:ss.mmmZ
var date = new Date(Date.parse(timestamp));
if (date.getYear() > 2018) { // filtre messages dupliqués
if (sheet.getRange(HEADER_ROW + 1, 1).getValue() != '') {
var now = new Date();
var COMM_TIME = 120; // secondes (augmenté de 5 à 120)
if (now.getTime() - date.getTime() > COMM_TIME * 1000) return;
}
// Écriture en-têtes
sheet.getRange(HEADER_ROW, 1).setValue('timestamp');
for (var i = 0; i < incLength; i++) {
var lastCol = sheet.getLastColumn();
if (lastCol == 1) {
sheet.getRange(HEADER_ROW, lastCol + 1).setValue(incNames[i]);
} else {
var found = 0;
for (var col = 2; col <= lastCol; col++) {
if (sheet.getRange(HEADER_ROW, col).getValue() == incNames[i]) { found = 1; break; }
}
if (found == 0) sheet.getRange(HEADER_ROW, lastCol + 1).setValue(incNames[i]);
}
}
var lastCol = sheet.getLastColumn();
var lastRow = sheet.getLastRow();
if (lastRow > MAX_ROWS + HEADER_ROW - 1) sheet.deleteRow(lastRow);
sheet.insertRowAfter(HEADER_ROW);
var range = sheet.getRange('A2:Z2');
range.setFontColor('#000000').setFontSize(10).setFontWeight('normal');
sheet.getRange(HEADER_ROW + 1, TIMESTAMP_COL).setValue(date).setNumberFormat("yyyy-MM-dd HH:mm:ss");
for (var col = 1 + TIMESTAMP_COL; col <= lastCol; col++) {
for (var i = 0; i < incLength; i++) {
var currentName = sheet.getRange(HEADER_ROW, col).getValue();
if (currentName == incNames[i]) {
if (incValues[i] === true) incValues[i] = 1;
else if (incValues[i] === false) incValues[i] = 0;
sheet.getRange(HEADER_ROW + 1, col).setValue(incValues[i]);
}
}
if (sheet.getRange(HEADER_ROW + 1, col).getValue() == '') {
sheet.getRange(HEADER_ROW + 1, col).setValue("-"); // tiret si vide
}
}
}
}
function sendEmail() {
var emailAddress = sd.getRange("V3").getValue();
var lastPressure = sd.getRange("K3").getValue();
var lastUpdate = sd.getRange("A3").getValue();
var ssLink = "https://docs.google.com/spreadsheets/d/1XwCir2Llw8RvGPGgZI3Yk6U5a3LeIfUACNuO1Gr_LFQ/edit#gid=1123486497";
var triggerAlertCount = sd.getRange("L6").getValue();
var triggerMonitor = sd.getRange("M3").getValue();
var dtLastStatusChange = sd.getRange("P3").getValue();
var dtLastDeviceUpdate1 = sd.getRange("S3").getValue();
var dtLastDeviceUpdate2 = sd.getRange("S4").getValue();
var emailSentNoUpdate = sd.getRange("T3").getValue();
var emailSentStartedReading = sd.getRange("U3").getValue();
var message = "Last Device Update: \n" + lastUpdate + "\n\n Last Pressure: \n\t" +
lastPressure.toFixed(2) + " psi\n\n Link to Spreadsheet: \n\t" + ssLink;
var now = new Date();
if (triggerMonitor == 0) {
sd.getRange("L5").setValue(0); sd.getRange("L6").setValue(0);
}
if (triggerMonitor == -1 && triggerAlertCount <= 4) {
sd.getRange("L3").setValue(lastUpdate);
sd.getRange("L5").setValue(-1);
sd.getRange("L6").setValue(triggerAlertCount + 1);
MailApp.sendEmail(emailAddress, "Status Change Alert - Outside Setpoints", message);
}
if (triggerMonitor == 1 && triggerAlertCount <= 4) {
sd.getRange("L3").setValue(lastUpdate);
sd.getRange("L5").setValue(1);
sd.getRange("L6").setValue(triggerAlertCount + 1);
MailApp.sendEmail(emailAddress, "Status Change Alert - Normal", message);
}
if (emailSentNoUpdate == 0 && dtLastDeviceUpdate1 > 60 && dtLastDeviceUpdate2 > 60) {
sd.getRange("T3").setValue(1);
sd.getRange("U3").setValue(0);
sd.getRange("T4").setValue(now.getTime());
MailApp.sendEmail(emailAddress, "Alert - Over 60 minutes Since Last Device Update", message);
}
if (emailSentNoUpdate == 1 && dtLastDeviceUpdate1 < 60) {
sd.getRange("T3").setValue(0);
sd.getRange("U3").setValue(1);
sd.getRange("U4").setValue(now.getTime());
MailApp.sendEmail(emailAddress, "Alert - Device Started Updating", message);
}
}
CommunicationsDevice - Sketch pour MKR 1400
(Code non affiché dans l'original, disponible en téléchargement)
Schémas

Processus de fabrication
- ADLINK et Google Cloud collaborent pour des solutions IoT transparentes
- Raspberry Pi et HDC2010 : guide complet de configuration du capteur I2C température et humidité
- Capteur de pression barométrique BMP180 I2C – Guide d'intégration Raspberry Pi
- Enregistreur de capteurs Python/MicroPython : Stocker les données de capteurs dans Google Sheets
- Envoyer les données IMU 10DOF Adafruit du Raspberry Pi 2 vers Azure Event Hubs avec Windows 10 IoT Core
- Surveillance Précise de Température et Humidité avec SHT15 sur Windows 10 IoT Core
- UnifiedWater v1 – Kit complet de capteur de qualité d'eau Arduino
- Bocal Intelligent IoT avec ESP8266, Arduino et Capteur Ultrasonique – Surveillance Temps Réel
- Tour de Capteurs avec Arduino Cloud : Température, Mouvement et Gaz