Fabrication industrielle
Internet des objets industriel | Matériaux industriels | Entretien et réparation d'équipement | Programmation industrielle |
home  MfgRobots >> Fabrication industrielle >  >> Manufacturing Technology >> Processus de fabrication

Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets

Composants et Fournitures

ComposantQuantitéNotes
Arduino UNO1
Arduino MKR GSM 14001
Transducteur de pression (0-150 psi)1
Capteur Température/Humidité Adafruit DHT221
Module RTC DS32311
Convertisseur de niveau logique Adafruit BSS138 (4 canaux I2C bidir)1
Batterie LiPo 3.7V 2000 mAh+2
Convertisseur step-up tension1
Kit de base LEDs & résistances1
Plaque d'essai (breadboard)1

Applications et Services en Ligne

À 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.

Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets

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) :

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).

Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets

Arduino Cloud : 2 variables configurées :

Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets

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) :

  1. Créer nouveau fichier Google Sheets
  2. Outils → Éditeur de script
  3. Écrire code avec fonction doPost (voir GoogleSheetsScript.js joint)
  4. Publier → Déployer en tant qu'application web...
  5. 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
  6. 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

Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets

Étape 6 : Google Sheets - Notifications par email

Deux fonctions dans GoogleSheetsScript.js :

Configuration déclencheur pour sendEmail :

  1. Ouvrir fichier Sheets → Outils → Éditeur de script
  2. Édition → Déclencheurs du projet actuel
  3. Ajouter déclencheur (bas droite) → fonction sendEmail → basé sur le temps (toutes les minutes)
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets

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 $

Matériel / Outils

Tous achetés chez : Arduino Store, Adafruit, Amazon, Harbor Freight, Home Depot.

Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets
Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets

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

Surveillance Pression IoT Temps Réel : MKR GSM, Arduino Cloud et Google Sheets

Processus de fabrication

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