Real-Time IoT Drukmonitoring met MKR GSM, Arduino Cloud en Google Sheets
Onderdelen en benodigdheden
| Onderdeel | Aantal |
|---|---|
| Arduino UNO | 1 |
| Arduino MKR GSM 1400 | 1 |
| Druksensor (0-150 psi) | 1 |
| Adafruit DHT22 Temperatuur & Vochtigheid Sensor | 1 |
| DS3231 RTC Module | 1 |
| Adafruit Logic Level Converter - BSS138 - 4-kanaals I2C-safe Bidirectioneel | 1 |
| Adafruit 3.7V LiPo Batterij (2000 mAh of meer) | 2 |
| Spanningsopwaartsconverter | 1 |
| Basis Starter Kit met diverse LEDs & Weerstanden | 1 |
| Breadboard (generiek) | 1 |
Apps en online diensten
- Google Sheets
- Arduino IoT Cloud
- Arduino Web Editor
- Arduino IDE
Over dit project
Doelstelling
Het doel was een prototype te maken van een laagprijs apparaat om druk op industriële apparatuur op afgelegen locaties te monitoren via cellulair netwerk.
Project Walkthrough
Stap-voor-stap doorloop van de datastroom van de druksensor naar de e-mailnotificatie gegenereerd door het script in Google Sheets.
Stap 1: Druksensor naar Uno
De druksensor converteert druk naar een analoog elektrisch signaal. De Arduino Uno converteert dit analoog signaal naar druk in psi.
Stap 2: Uno naar MKR GSM 1400 via Serial
Bij seriële communicatie tussen twee Arduino's:
- RX op Apparaat #1 verbinden met TX op Apparaat #2
- TX op Apparaat #1 verbinden met RX op Apparaat #2
- Beide apparaten moeten een gemeenschappelijke ground hebben
Data-overdrachtsfrequentie (Uno naar MKR GSM 1400)
- NORMAAL: Elke 30 minuten (transmitFrequency) stuurt de Uno seriële data naar de MKR GSM 1400 die het naar de cloud stuurt.
- HOOG/LAAG TRIGGER: Als druk boven 40 psi (highTrigger) of onder 20 psi (lowTrigger) blijft voor meer dan 2 minuten (dtLastTriggerLimit), stuurt de Uno data naar de MKR GSM 1400.
- DEMAND POLL: Als pin A1 op de Uno hoog wordt getrokken, stuurt de Uno data naar de MKR GSM 1400. Pin A1 heet "buttonPin" in de sketch. Twee manieren: (1) drukknop op breadboard, (2) als pin A3 op MKR GSM 1400 hoog is, duwt het pin A1 hoog. Pin A3 wordt bestuurd door een input in Arduino Cloud, zodat de huidige druk op afstand opgevraagd kan worden zonder te wachten op geplande transmissie.
Opmerkingen: De Uno-sketch kan uitgebreid worden om meerdere inputs (temperatuur, vochtigheid, batterijspanning) te monitoren met drempelwaarden. De conversiecode voor de druksensor is gebaseerd op deze YouTube-video. Het Arduino Forum-bericht over Serial Input Basics was heel nuttig.
Bestandsnaam: "InstrumentReader"
Stap 3: MKR GSM 1400 naar Arduino Cloud via Cellulair
De MKR GSM 1400 verwerkt seriële data van de Uno en stuurt het via cellulair naar Arduino Cloud. Let op: in de code zie je Serial1.read in plaats van Serial.read. Op MKR-boards zijn TX/RX-pinnen bereikbaar via Serial1. Zie Arduino referentie.
Arduino Cloud
Project heeft 2 variabelen in Arduino Cloud. Het dashboard toont:
- dataStringCloud: een pakket met alle data van het apparaat. Deze aanpak vereenvoudigt verwerking in Google Sheets. Individuele variabelen maakten het moeilijk om onderscheid te maken tussen ongewijzigde waarden en niet-geüpdate waarden. Data wordt in Google Sheets geparsed.
- pinCloud: bestuurt de MKR GSM 1400 vanuit Arduino Cloud. Switch-functie in sketch: pinCloud=1 zet pin A1 hoog (LED brandt), pinCloud=2 zet pin A3 hoog wat leidt tot dataverzending van de Uno.
Bestandsnaam: "CommunicationsDevice"
Stap 4: Arduino Cloud naar Google Sheets via Webhook
Dataoverdracht via webhook. De kern is de doPost functie in het script van een Google Sheets-bestand.
Webhook setup (begin in Google Sheets!):
- Nieuw Google Sheets-bestand aanmaken
- Tools > Script editor
- Code met doPost functie schrijven (zie GoogleSheetsScript.js)
- Publish > Deploy as web app...
- Dialoogvelden: (1) Project Version: "New" selecteren, (2) Execute as: "me (jouw email)", (3) Who has access: "Anyone, even anonymous"
- Deploy klikken, URL kopiëren naar webhook-tab in Arduino Cloud. URL blijft constant.
Gedeelte van de JavaScript-code is gemodelleerd na Arduino IoT Cloud Google Sheets Integration.
Stap 5: Google Sheets Data Parsen
Google Sheets parseert individuele waarden uit dataStringCloud en toont unieke records.
Testbestand: Google Sheets testbestand. Cellen gekleurd op basis van vulling (zie legenda per sheet).
Stap 6: Google Sheets Notificaties Sturen
Twee functies in GoogleSheetsScript.js:
doPost: ontvangt data van Arduino Cloud webhook. Draait bij nieuwe data.sendEmail: stuurt e-mails op basis van waarden uit sheet "Data". Draait elke minuut via trigger.
Trigger setup voor sendEmail:
- Google Sheets openen > Tools > Script Editor
- Edit > Current project's triggers
- Add Trigger > selecteer sendEmail functie
- Time-Driven basis maakt notificaties mogelijk wanneer apparaat stopt met updaten
Batterijduur
~24 uur. Optimaliseer door displays uit te schakelen/verwijderen of non-essentiële sensoren (DHT22, DS3231) weg te laten.
Dataverbruik
~0.7 MB/dag. Optimaliseer door grootte/frequentie van transmissies te verminderen (bijv. alleen druk sturen, of elk uur ipv 30 min).
Projectkosten
Totaal = $241
- Arduino MKR GSM 1400 ($70)
- Arduino Uno ($22)
- 2x 3.7V LiPo Batterijen ($30)
- 2x LED Displays ($29)
- Weerbestendige Kunststof Doos ($22)
- Druksensor ($19)
- DHT22 Temperatuur/Vochtigheid Sensor ($10)
- DS3231 RTC Module ($5)
- Spanningsopwaartsconverter ($5)
- Logic Level Converter ($4)
- Diverse - LEDs, Weerstanden, Bedrading, etc. ($25)
Hardware / Tools
Alle hardware gekocht bij:
- Arduino Online Store (store.arduino.cc)
- Adafruit (adafruit.com)
- Amazon (amazon.com)
- Harbor Freight
- Home Depot
Conclusie
Dank voor het bekijken van dit project. Alle vragen, feedback, opmerkingen en suggesties zijn welkom en gewaardeerd.
Code
InstrumentReader - Sketch voor Arduino Uno
// Sketch 1 of 2 // Arduino Uno
// Data wordt verzameld en via serial naar MKR 1400 gestuurd
// DHT22 Sensor vereist 2 libraries, maar alleen 1 wordt aangeroepen in code.
// (1): DHT Sensor Library: https://github.com/adafruit/DHT-sensor-library
// (2): Adafruit Unified Sensor Lib: https://github.com/adafruit/Adafruit_Sensor
#include <SPI.h>
#include <Wire.h>
#include <DS3231.h> // RTC module
#include <DHT.h> // DHT22
#include "U8glib.h" // Velleman 128x64 OLED SPI Display
// Note: Een andere standaard U8glib library werkte niet voor dit display. Werkte wel met de door fabrikant aanbevolen library.
// Library: https://www.velleman.eu/support/downloads/?code=VMA437
// Syntax: https://github.com/olikraus/u8glib/wiki/userreference & https://github.com/olikraus/u8glib/wiki/thelloworld
//#include <SD.h> // Optie om op SD-kaart in Ethernet Shield op te slaan
RTClib RTC;
#define DHTPIN 11
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
U8GLIB_SH1106_128X64 u8g(3, 4, 6, 7); // (CLK/SCK: 3, MOSI: 4, CS: 6, DC(A0): 7)
int y_pos = 0;
//const int chipSelect = 10; // SD card optie
//float fileSizeSD = 0.0; // SD card optie
int i = 0; // Aantal metingen
const int ledPin = 9; // transmit indicator
const int ledPin2 = 8; // push transmit indicator
const int buttonPin = A1;
int buttonState = 0;
int transmitFrequency = 30; // minuten
String pTransmitDateTime = "";
int transmitCounter = 0;
int pTransmitMinute = 0;
int ptriggerTransmitAlertIndicator;
float cRuntimeAtTriggerStart = 0.0;
float dtLastTrigger = 0.0;
int triggerCounter = 0.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();
// SD card init code (optioneel)
}
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);
} 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 = "";
String cDateTime = "";
String cHumTemp = "";
String 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);
// writeToSD(dataString); // SD card optie
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;
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
// 2 functies: doPost (webhook ontvangst) en sendEmail (notificaties)
// Gemodelleerd na: https://create.arduino.cc/projecthub/Arduino_Genuino/arduino-iot-cloud-google-sheets-integration-71b6bc?f=1
// GitHub repo: 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;
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;
console.log(values);
var incLength = values.length;
var incNames = [];
var 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;
var date = new Date(Date.parse(timestamp));
if (date.getYear() > 2018) {
if (sheet.getRange(HEADER_ROW+1, 1).getValue() != '') {
var now = new Date();
var COMM_TIME = 120;
if (now.getTime() - date.getTime() > COMM_TIME * 1000) return;
}
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'); range.setFontSize(10); range.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("-");
}
}
}
}
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;
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(new Date().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(new Date().getTime());
MailApp.sendEmail(emailAddress, "Alert - Device Started Updating", message);
}
}
CommunicationsDevice - Sketch voor MKR 1400
Code niet beschikbaar in preview (download only).
Schema's
Productieproces
- Raspberry Pi & HDC2010: Complete I2C Temperatuur & Vochtigheid Sensor Setup Gids
- BMP180 I2C Barometrische Druksensor – Raspberry Pi Integratiehandleiding
- Python/MicroPython Sensor Logger: Naadloos Sensordata Opslaan in Google Sheets
- Adafruit 10DOF IMU-data van Raspberry Pi 2 naar Azure Event Hubs Sturen met Windows 10 IoT Core
- Nauwkeurige Temperatuur- & Vochtigheidsmonitoring met SHT15 op Windows 10 IoT Core
- UnifiedWater v1 – Complete Arduino Waterkwaliteit Sensor Kit
- Bouw een Slimme IoT Pot met ESP8266, Arduino & Ultrasonische Sensor – Real-time Monitoring
- Bouw een Sensor Toren met Arduino Cloud: Temperatuur, Beweging & Gas Monitoring
- DIY Arduino Smartwatch: Bouw Je Eigen Apple Watch-stijl Tijdwijzer
- Je TV bedienen met Alexa via Arduino IoT Cloud – stapsgewijze handleiding
- ADLINK & Google Cloud collaboreren voor naadloze IoT-oplossingen