Components and supplies
DHT22 Temperature & Humidity Sensor
DS3231 RTC Module
3.7V LiPo Battery (2000 mAh or greater)
Arduino UNO
Basic Starter Kit with Assorted LEDs & Resistors
Logic Level Converter - BSS138 - 4-channel I2C-safe Bi-directional
Voltage Step-up Converter
Breadboard (generic)
Pressure Transducer (0-150 psi)
Arduino MKR GSM 1400
Apps and platforms
Google Sheets
Arduino IoT Cloud
Arduino IDE
Arduino Web Editor
Project description
Code
InstrumentReader - Sketch for Arduino Uno
arduino
1// Sketch 1 of 2 2// Arduino Uno 3 // Data is collected by this device and transmitted to the MKR 1400 via serial 4 5// The DHT22 Sensor Requires 2 libraries, but only one is called in the code. 6// (1): DHT Sensor Library: https://github.com/adafruit/DHT-sensor-library 7// (2): Adafruit Unified Sensor Lib: https://github.com/adafruit/Adafruit_Sensor 8 9#include <SPI.h> 10#include <Wire.h> 11#include <DS3231.h> // RTC module 12#include <DHT.h> // DHT22 13#include "U8glib.h" // Velleman 128 x 64 OLED SPI Display 14 // Note: Another standard U8glib library did not work when I attempted to use it for this display. It did work when I used the library recommended by the manufacturer. 15 // Library: https://www.velleman.eu/support/downloads/?code=VMA437 16 // Syntax: https://github.com/olikraus/u8glib/wiki/userreference & https://github.com/olikraus/u8glib/wiki/thelloworld 17//#include <SD.h> // Option to save to SD card in Ethernet Shield for Arduino Uno 18 19RTClib RTC; 20 21#define DHTPIN 11 // Digital pin connected to the DHT sensor 22#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321 23DHT dht(DHTPIN, DHTTYPE); 24 25U8GLIB_SH1106_128X64 u8g(3, 4, 6, 7); // (CLK/SCK: 3, MOSI: 4, CS: 6, DC(A0): 7) // u8g(sck, mosi, cs, a0 [, reset]) 26 27int y_pos = 0; // global variable 28 29//const int chipSelect = 10; // Option to save to SD card in Ethernet Shield for Arduino Uno 30//float fileSizeSD = 0.0; // Option to save to SD card in Ethernet Shield for Arduino Uno 31 32int i = 0; // Count # of readings taken by the Uno (same as # loops in program) 33 34const int ledPin = 9; // transmit indicator (flashes when a data transmission occurs) 35const int ledPin2 = 8; // push transmit indicator (pushed high by manual button on breadboard or output from MKR 1400 activated from the cloud) 36const int buttonPin = A1; 37 38int buttonState = 0; 39 40int transmitFrequency = 30; // Frequency of Serial Print dataString for sending data to the second device (minutes) 41String pTransmitDateTime = ""; 42int transmitCounter = 0; 43int pTransmitMinute = 0; 44int ptriggerTransmitAlertIndicator; 45 46float cRuntimeAtTriggerStart = 0.0; 47float dtLastTrigger = 0.0; 48int triggerCounter = 0.0; 49int triggerTransmitAlertCounter = 0; 50 51// Input Variables to Control Trigger 52float lowTrigger = 20.0; 53float highTrigger = 40.0; 54float dtLastTriggerLimit = 2.0; // If the condition is met for this length of time, an alert will be generated 55 56void setup (void) { 57 Serial.begin(9600); 58 Wire.begin(); 59 dht.begin(); 60 pinMode(ledPin, OUTPUT); 61 pinMode(ledPin2, OUTPUT); 62 pinMode(buttonPin, INPUT); 63 u8g.setRot180(); // flip screen, if required (add/remove comments on this line to rotate) 64 65 // Option to save to SD card in Ethernet Shield for Arduino Uno 66 // Serial.print("Initializing SD card..."); 67 // if (!SD.begin(chipSelect)) // see if the card is present and can be initialized 68 // { 69 // Serial.println("Card failed, or not present"); 70 // while (1); // don't do anything more 71 // } 72 // Serial.println("card initialized."); 73} 74 75void loop (void) { 76 delay(5000); 77 DateTime now = RTC.now(); 78 float cRuntime = millis()/60000; 79 float p = getPressure(); 80// Serial.println(p); 81 82 buttonState = digitalRead(buttonPin); 83// Serial.print("Button: "); 84// Serial.println(buttonState); 85 if(buttonState == 1) 86 { 87 digitalWrite(ledPin2, HIGH); 88 delay(30000); // delay to allow MKR1400 to get ready to recieve data if Uno:buttonPin is pushed HIGH by MKR1400:pingPin 89 } 90 else 91 { 92 digitalWrite(ledPin2, LOW); 93 } 94 95 float h = dht.readHumidity(); 96 float t = dht.readTemperature(true); // t = dht.readTemperature(true) --> temp if degrees F & t = dht.readTemperature() --> temp if degrees C 97 98 int transmitIndicator = 0; 99 if(now.minute() % transmitFrequency == 0 && now.minute() != pTransmitMinute) 100 { 101 transmitIndicator = 1; 102 pTransmitMinute = now.minute(); 103 pTransmitDateTime = String(now.hour())+String(":")+String(now.minute())+String(":")+String(now.second()); 104 } 105 106 int triggerStatus = 0; 107 if(p <= lowTrigger || p >= highTrigger) 108 { 109 // Note: the variable referenced in the condition for this if statement is evaulated against high & low setpoints 110 // It is quick to change which variable is evaluated - this is the only location where the variable is specified 111 112 triggerStatus = 1; 113 triggerCounter++; 114 } 115 else 116 { 117 triggerCounter = 0; 118 } 119 120 if(triggerStatus == 1 && triggerCounter == 1) 121 { 122 cRuntimeAtTriggerStart = cRuntime; 123 } 124 125 dtLastTrigger = cRuntime - cRuntimeAtTriggerStart; 126 127 int triggerTransmitAlertIndicator = 0; 128 if((dtLastTrigger > dtLastTriggerLimit) && triggerStatus == 1) 129 { 130 triggerTransmitAlertIndicator = 1; 131 triggerTransmitAlertCounter++; 132 } 133 else 134 { 135 triggerTransmitAlertCounter = 0; 136 } 137 138 if(triggerTransmitAlertCounter > 0 && triggerTransmitAlertCounter % 10 == 0) 139 { 140 flashLED(2,500); 141 } 142 143 int triggerPushTransmitAlertIndicator = 0; 144 if((triggerTransmitAlertIndicator == 1 && triggerTransmitAlertCounter == 1) || ptriggerTransmitAlertIndicator != triggerTransmitAlertIndicator) 145 // if(TriggerStatus existed for min specified time for Alert & Count = 1 meaining that this is the first loop where the time exceeded the min specified time 146 // OR the triggerAlert status changes -- this will generate a push Alert if the TriggerStatus goes back to 0 meaning the Trigger conditions are no longer met.) 147 { 148 triggerPushTransmitAlertIndicator = 1; 149 flashLED(5,500); 150 delay(5000); 151 } 152 153 ptriggerTransmitAlertIndicator = triggerTransmitAlertIndicator; // current indicator stored to previous indicator. On the next loop the value transferred here will be compared to the value generated based on new values. 154 155 // Create strings 156 String dataString = ""; 157 String cDateTime = ""; 158 String cHumTemp = ""; 159 String cP = ""; 160 161 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)+">"; 162 cDateTime += String(now.month())+"/"+String(now.day())+"/"+String(now.year())+" "+String(now.hour())+":"+String(now.minute())+":"+String(now.second()); 163 cHumTemp += "H:"+String(h)+"% T:"+String(t)+"degF"; 164 cP += "P:"+String(p)+"psi"; 165 166 if(transmitIndicator == 1 || triggerPushTransmitAlertIndicator == 1 || buttonState == 1) 167 { 168 char dataArray[100]; 169 dataString.toCharArray(dataArray, 100); 170 Serial.println(dataArray); 171 flashLED(10,500); 172 transmitCounter++; 173 } 174 175// Serial.print("T:"); 176// Serial.println(triggerStatus); 177 178 delay(100); // wait a bit for the entire message to arrive 179 // picture loop 180 u8g.firstPage(); 181 do 182 { 183 draw(cDateTime,cHumTemp, cP,i,transmitCounter,now.minute(),transmitFrequency,pTransmitMinute); 184 } 185 while(u8g.nextPage()); 186 delay(1000); 187 // writeToSD(dataString); // Option to save to SD card in Ethernet Shield for Arduino Uno 188 i++; 189} 190 191void draw(String DcDateTime,String DcHumTemp, String DcP, int Di, int DtransmitCounter,int DnowMinute,int DtransmitFrequency, int DpTransmitMinute) 192{ 193 u8g.begin(); 194 u8g.setFont(u8g_font_5x7); //u8g_font_micro //u8g_font_5x7 //u8g_font_5x8 //u8g_font_6x10 195 u8g.setFontPosTop(); 196 u8g.setPrintPos(0,0); 197 u8g.print(DcDateTime); 198 u8g.setPrintPos(0,8); 199 u8g.print(2); 200 u8g.setPrintPos(10,8); 201 u8g.print(DcHumTemp); 202 u8g.setPrintPos(0,16); 203 u8g.print("3 #:"); 204 u8g.setPrintPos(30,16); 205 u8g.print(Di); 206 u8g.setPrintPos(50,16); 207 u8g.print(DcP); 208 u8g.setPrintPos(0,24); 209 u8g.print("4 #t:"); 210 u8g.setPrintPos(30,24); 211 u8g.print(DtransmitCounter); 212 u8g.setPrintPos(50,24); 213 u8g.print("tFreq: "); 214 u8g.setPrintPos(83,24); 215 u8g.print(DtransmitFrequency); 216 u8g.setPrintPos(0,32); 217 u8g.print(5); 218 u8g.setPrintPos(10,32); 219 u8g.print("nowMinute:"); 220 u8g.setPrintPos(70,32); 221 u8g.print(DnowMinute); 222 u8g.setPrintPos(0,40); 223 u8g.print(6); 224 u8g.setPrintPos(10,40); 225 u8g.print("pTransmitMinute:"); 226 u8g.setPrintPos(95,40); 227 u8g.print(DpTransmitMinute); 228 u8g.setPrintPos(0,48); 229 u8g.print(7); 230 u8g.setPrintPos(10,48); 231 u8g.print("Remainder:"); 232 u8g.setPrintPos(70,48); 233 u8g.print(DnowMinute % DtransmitFrequency); 234} 235 236float getPressure() 237{ 238 int sensorVal=analogRead(A2); 239// Serial.print("Sensor Value: "); 240// Serial.print(sensorVal); 241 float voltage = (sensorVal*5.0)/1023.0; 242// Serial.print(" Volts: "); 243// Serial.print(voltage); 244 // When Pressure = 0, Analog Input = 100 245 // Conversion of Analog Input to Voltage: Analog Input = 100 -> Voltage = 100*(5/1023) = 0.4889 246 float m = ((150-0)/(4.5-0.4889)); 247 float b = 150 - (m*4.5); 248// Serial.print(" m = "); 249// Serial.print(m); 250// Serial.print(" b = "); 251// Serial.print(b); 252 float pressure_psi = ((m*voltage)+ b); 253// Serial.print(" Pressure = "); 254// Serial.print(pressure_psi); 255// Serial.println(" psi"); 256// delay(200); 257 return pressure_psi; 258} 259 260void flashLED(int num, int t) 261{ 262 for (int z = 1; z <= num; z++) 263 { 264 digitalWrite(ledPin, HIGH); 265 delay(t); 266 digitalWrite(ledPin, LOW); 267 delay(t); 268 } 269} 270 271 272// Option to save to SD card in Ethernet Shield for Arduino Uno 273 //void writeToSD(String dataToWrite) 274 //{ 275 // // open the file. note that only one file can be open at a time, 276 // // so you have to close this one before opening another. 277 // File dataFile = SD.open("datalog4.txt", FILE_WRITE); 278 // fileSizeSD = dataFile.size(); // Reurns file size in bytes 279 // fileSizeSD = fileSizeSD / 1000000 ; // Converts bytes to MB. 1 MB = 1e6 bytes 280 // // if the file is available, write to it: 281 // if (dataFile) 282 // { 283 // dataFile.println(dataToWrite); 284 // dataFile.close(); 285 // // print to the serial port too: 286 // // Serial.println(dataToWrite); 287 // } 288 // // if the file isn't open, pop up an error: 289 // else 290 // { 291 // Serial.println("error opening datalog1.txt"); 292 // } 293 //} 294
InstrumentReader - Sketch for Arduino Uno
arduino
1// Sketch 1 of 2 2// Arduino Uno 3 // Data is collected by this device and transmitted to the MKR 1400 via serial 4 5// The DHT22 Sensor Requires 2 libraries, but only one is called in the code. 6// (1): DHT Sensor Library: https://github.com/adafruit/DHT-sensor-library 7// (2): Adafruit Unified Sensor Lib: https://github.com/adafruit/Adafruit_Sensor 8 9#include <SPI.h> 10#include <Wire.h> 11#include <DS3231.h> // RTC module 12#include <DHT.h> // DHT22 13#include "U8glib.h" // Velleman 128 x 64 OLED SPI Display 14 // Note: Another standard U8glib library did not work when I attempted to use it for this display. It did work when I used the library recommended by the manufacturer. 15 // Library: https://www.velleman.eu/support/downloads/?code=VMA437 16 // Syntax: https://github.com/olikraus/u8glib/wiki/userreference & https://github.com/olikraus/u8glib/wiki/thelloworld 17//#include <SD.h> // Option to save to SD card in Ethernet Shield for Arduino Uno 18 19RTClib RTC; 20 21#define DHTPIN 11 // Digital pin connected to the DHT sensor 22#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321 23DHT dht(DHTPIN, DHTTYPE); 24 25U8GLIB_SH1106_128X64 u8g(3, 4, 6, 7); // (CLK/SCK: 3, MOSI: 4, CS: 6, DC(A0): 7) // u8g(sck, mosi, cs, a0 [, reset]) 26 27int y_pos = 0; // global variable 28 29//const int chipSelect = 10; // Option to save to SD card in Ethernet Shield for Arduino Uno 30//float fileSizeSD = 0.0; // Option to save to SD card in Ethernet Shield for Arduino Uno 31 32int i = 0; // Count # of readings taken by the Uno (same as # loops in program) 33 34const int ledPin = 9; // transmit indicator (flashes when a data transmission occurs) 35const int ledPin2 = 8; // push transmit indicator (pushed high by manual button on breadboard or output from MKR 1400 activated from the cloud) 36const int buttonPin = A1; 37 38int buttonState = 0; 39 40int transmitFrequency = 30; // Frequency of Serial Print dataString for sending data to the second device (minutes) 41String pTransmitDateTime = ""; 42int transmitCounter = 0; 43int pTransmitMinute = 0; 44int ptriggerTransmitAlertIndicator; 45 46float cRuntimeAtTriggerStart = 0.0; 47float dtLastTrigger = 0.0; 48int triggerCounter = 0.0; 49int triggerTransmitAlertCounter = 0; 50 51// Input Variables to Control Trigger 52float lowTrigger = 20.0; 53float highTrigger = 40.0; 54float dtLastTriggerLimit = 2.0; // If the condition is met for this length of time, an alert will be generated 55 56void setup (void) { 57 Serial.begin(9600); 58 Wire.begin(); 59 dht.begin(); 60 pinMode(ledPin, OUTPUT); 61 pinMode(ledPin2, OUTPUT); 62 pinMode(buttonPin, INPUT); 63 u8g.setRot180(); // flip screen, if required (add/remove comments on this line to rotate) 64 65 // Option to save to SD card in Ethernet Shield for Arduino Uno 66 // Serial.print("Initializing SD card..."); 67 // if (!SD.begin(chipSelect)) // see if the card is present and can be initialized 68 // { 69 // Serial.println("Card failed, or not present"); 70 // while (1); // don't do anything more 71 // } 72 // Serial.println("card initialized."); 73} 74 75void loop (void) { 76 delay(5000); 77 DateTime now = RTC.now(); 78 float cRuntime = millis()/60000; 79 float p = getPressure(); 80// Serial.println(p); 81 82 buttonState = digitalRead(buttonPin); 83// Serial.print("Button: "); 84// Serial.println(buttonState); 85 if(buttonState == 1) 86 { 87 digitalWrite(ledPin2, HIGH); 88 delay(30000); // delay to allow MKR1400 to get ready to recieve data if Uno:buttonPin is pushed HIGH by MKR1400:pingPin 89 } 90 else 91 { 92 digitalWrite(ledPin2, LOW); 93 } 94 95 float h = dht.readHumidity(); 96 float t = dht.readTemperature(true); // t = dht.readTemperature(true) --> temp if degrees F & t = dht.readTemperature() --> temp if degrees C 97 98 int transmitIndicator = 0; 99 if(now.minute() % transmitFrequency == 0 && now.minute() != pTransmitMinute) 100 { 101 transmitIndicator = 1; 102 pTransmitMinute = now.minute(); 103 pTransmitDateTime = String(now.hour())+String(":")+String(now.minute())+String(":")+String(now.second()); 104 } 105 106 int triggerStatus = 0; 107 if(p <= lowTrigger || p >= highTrigger) 108 { 109 // Note: the variable referenced in the condition for this if statement is evaulated against high & low setpoints 110 // It is quick to change which variable is evaluated - this is the only location where the variable is specified 111 112 triggerStatus = 1; 113 triggerCounter++; 114 } 115 else 116 { 117 triggerCounter = 0; 118 } 119 120 if(triggerStatus == 1 && triggerCounter == 1) 121 { 122 cRuntimeAtTriggerStart = cRuntime; 123 } 124 125 dtLastTrigger = cRuntime - cRuntimeAtTriggerStart; 126 127 int triggerTransmitAlertIndicator = 0; 128 if((dtLastTrigger > dtLastTriggerLimit) && triggerStatus == 1) 129 { 130 triggerTransmitAlertIndicator = 1; 131 triggerTransmitAlertCounter++; 132 } 133 else 134 { 135 triggerTransmitAlertCounter = 0; 136 } 137 138 if(triggerTransmitAlertCounter > 0 && triggerTransmitAlertCounter % 10 == 0) 139 { 140 flashLED(2,500); 141 } 142 143 int triggerPushTransmitAlertIndicator = 0; 144 if((triggerTransmitAlertIndicator == 1 && triggerTransmitAlertCounter == 1) || ptriggerTransmitAlertIndicator != triggerTransmitAlertIndicator) 145 // if(TriggerStatus existed for min specified time for Alert & Count = 1 meaining that this is the first loop where the time exceeded the min specified time 146 // OR the triggerAlert status changes -- this will generate a push Alert if the TriggerStatus goes back to 0 meaning the Trigger conditions are no longer met.) 147 { 148 triggerPushTransmitAlertIndicator = 1; 149 flashLED(5,500); 150 delay(5000); 151 } 152 153 ptriggerTransmitAlertIndicator = triggerTransmitAlertIndicator; // current indicator stored to previous indicator. On the next loop the value transferred here will be compared to the value generated based on new values. 154 155 // Create strings 156 String dataString = ""; 157 String cDateTime = ""; 158 String cHumTemp = ""; 159 String cP = ""; 160 161 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)+">"; 162 cDateTime += String(now.month())+"/"+String(now.day())+"/"+String(now.year())+" "+String(now.hour())+":"+String(now.minute())+":"+String(now.second()); 163 cHumTemp += "H:"+String(h)+"% T:"+String(t)+"degF"; 164 cP += "P:"+String(p)+"psi"; 165 166 if(transmitIndicator == 1 || triggerPushTransmitAlertIndicator == 1 || buttonState == 1) 167 { 168 char dataArray[100]; 169 dataString.toCharArray(dataArray, 100); 170 Serial.println(dataArray); 171 flashLED(10,500); 172 transmitCounter++; 173 } 174 175// Serial.print("T:"); 176// Serial.println(triggerStatus); 177 178 delay(100); // wait a bit for the entire message to arrive 179 // picture loop 180 u8g.firstPage(); 181 do 182 { 183 draw(cDateTime,cHumTemp, cP,i,transmitCounter,now.minute(),transmitFrequency,pTransmitMinute); 184 } 185 while(u8g.nextPage()); 186 delay(1000); 187 // writeToSD(dataString); // Option to save to SD card in Ethernet Shield for Arduino Uno 188 i++; 189} 190 191void draw(String DcDateTime,String DcHumTemp, String DcP, int Di, int DtransmitCounter,int DnowMinute,int DtransmitFrequency, int DpTransmitMinute) 192{ 193 u8g.begin(); 194 u8g.setFont(u8g_font_5x7); //u8g_font_micro //u8g_font_5x7 //u8g_font_5x8 //u8g_font_6x10 195 u8g.setFontPosTop(); 196 u8g.setPrintPos(0,0); 197 u8g.print(DcDateTime); 198 u8g.setPrintPos(0,8); 199 u8g.print(2); 200 u8g.setPrintPos(10,8); 201 u8g.print(DcHumTemp); 202 u8g.setPrintPos(0,16); 203 u8g.print("3 #:"); 204 u8g.setPrintPos(30,16); 205 u8g.print(Di); 206 u8g.setPrintPos(50,16); 207 u8g.print(DcP); 208 u8g.setPrintPos(0,24); 209 u8g.print("4 #t:"); 210 u8g.setPrintPos(30,24); 211 u8g.print(DtransmitCounter); 212 u8g.setPrintPos(50,24); 213 u8g.print("tFreq: "); 214 u8g.setPrintPos(83,24); 215 u8g.print(DtransmitFrequency); 216 u8g.setPrintPos(0,32); 217 u8g.print(5); 218 u8g.setPrintPos(10,32); 219 u8g.print("nowMinute:"); 220 u8g.setPrintPos(70,32); 221 u8g.print(DnowMinute); 222 u8g.setPrintPos(0,40); 223 u8g.print(6); 224 u8g.setPrintPos(10,40); 225 u8g.print("pTransmitMinute:"); 226 u8g.setPrintPos(95,40); 227 u8g.print(DpTransmitMinute); 228 u8g.setPrintPos(0,48); 229 u8g.print(7); 230 u8g.setPrintPos(10,48); 231 u8g.print("Remainder:"); 232 u8g.setPrintPos(70,48); 233 u8g.print(DnowMinute % DtransmitFrequency); 234} 235 236float getPressure() 237{ 238 int sensorVal=analogRead(A2); 239// Serial.print("Sensor Value: "); 240// Serial.print(sensorVal); 241 float voltage = (sensorVal*5.0)/1023.0; 242// Serial.print(" Volts: "); 243// Serial.print(voltage); 244 // When Pressure = 0, Analog Input = 100 245 // Conversion of Analog Input to Voltage: Analog Input = 100 -> Voltage = 100*(5/1023) = 0.4889 246 float m = ((150-0)/(4.5-0.4889)); 247 float b = 150 - (m*4.5); 248// Serial.print(" m = "); 249// Serial.print(m); 250// Serial.print(" b = "); 251// Serial.print(b); 252 float pressure_psi = ((m*voltage)+ b); 253// Serial.print(" Pressure = "); 254// Serial.print(pressure_psi); 255// Serial.println(" psi"); 256// delay(200); 257 return pressure_psi; 258} 259 260void flashLED(int num, int t) 261{ 262 for (int z = 1; z <= num; z++) 263 { 264 digitalWrite(ledPin, HIGH); 265 delay(t); 266 digitalWrite(ledPin, LOW); 267 delay(t); 268 } 269} 270 271 272// Option to save to SD card in Ethernet Shield for Arduino Uno 273 //void writeToSD(String dataToWrite) 274 //{ 275 // // open the file. note that only one file can be open at a time, 276 // // so you have to close this one before opening another. 277 // File dataFile = SD.open("datalog4.txt", FILE_WRITE); 278 // fileSizeSD = dataFile.size(); // Reurns file size in bytes 279 // fileSizeSD = fileSizeSD / 1000000 ; // Converts bytes to MB. 1 MB = 1e6 bytes 280 // // if the file is available, write to it: 281 // if (dataFile) 282 // { 283 // dataFile.println(dataToWrite); 284 // dataFile.close(); 285 // // print to the serial port too: 286 // // Serial.println(dataToWrite); 287 // } 288 // // if the file isn't open, pop up an error: 289 // else 290 // { 291 // Serial.println("error opening datalog1.txt"); 292 // } 293 //} 294
GoogleSheetsScript.js
javascript
1// There are 2 functions in this code 2// doPost function - Transmits data from the Arduino Cloud Webhook. It runs when there is new data on the Arduino Cloud. 3// sendEmail function - Sends emails based on values extracted from the sheet named "Data" in the Google Sheets file for the project. It runs once every minute based on the settings in the trigger setup. For instructions, refer to the sendEmail function portion of the project hub post. 4 5// The majority of this code (aside from the sendEmail function) is modeled after the following project on the Arduino Project Hub 6// https://create.arduino.cc/projecthub/Arduino_Genuino/arduino-iot-cloud-google-sheets-integration-71b6bc?f=1 7 8// This a link to a GitHub repository with the Google Script used for the above mentioned project on the Project Hub. 9// This link was copied from the desription of the project on the Project Hub. 10// https://github.com/arduino/arduino-iot-google-sheet-script/blob/master/Code.gs 11 12 13// get active spreasheet 14var ss = SpreadsheetApp.getActiveSpreadsheet(); 15 16// get sheet named RawData 17var sheet = ss.getSheetByName("RawData"); 18var sd = ss.getSheetByName("Data"); 19var sref = ss.getSheetByName("References"); 20 21var MAX_ROWS = 1440; // max number of data rows to display 22// 3600s / cloud_int(30s) * num_ore(12h) = (60*60*12)/30 = (3600*12)/30 = 1440 readings in 12 hours at 30 second update interval 23// (60*24)/15 = 96 readings in a 24 hour period at 15 minute update interval 24// 15 days * 96 readings/day = 1440 readings 25// 90 days * 96 readings/day = 8640 readings 26// 365 days * 96 readings/day = 35040 readings 27var HEADER_ROW = 1; // row index of header 28var TIMESTAMP_COL = 1; // column index of the timestamp column 29 30function doPost(e) { 31 var cloudData = JSON.parse(e.postData.contents); // this is a json object containing all info coming from IoT Cloud 32 console.log(cloudData); 33 //var webhook_id = cloudData.webhook_id; // really not using these three 34 //var device_id = cloudData.device_id; 35 //var thing_id = cloudData.thing_id; 36 var values = cloudData.values; // this is an array of json objects 37 console.log(values); 38 39 // Store names and values from the values array 40 // Each incoming property has a: 41 // name which will become the column names 42 // value which will be written into the rows below the column header 43 var incLength = values.length; 44 var incNames = []; 45 var incValues = []; 46 for (var i = 0; i < incLength; i++) { 47 incNames[i] = values[i].name; 48 incValues[i] = values[i].value; 49 } 50 51 console.log(incNames); 52 console.log(incValues); 53 54 // read timestamp of incoming message 55 var timestamp = values[0].updated_at; // format: yyyy-MM-ddTHH:mm:ss.mmmZ 56 var date = new Date(Date.parse(timestamp)); 57 58 /* 59 This if statement is due to the fact that duplicate messages arrive from the cloud! 60 If that occurs, the timestamp is not read correctly and date variable gets compromised. 61 Hence, execute the rest of the script if the year of the date is well defined and it is greater 62 then 2018 (or any other year before) 63 */ 64 if (date.getYear() > 2018) { 65 66 // discard all messages that arrive 'late' 67 if (sheet.getRange(HEADER_ROW+1, 1).getValue() != '') { 68 // HEADER_ROW + 1 = Row #2 & Column #1 --> this is the location of the most recent timestamp in the sheet 69 // If the most recent timestamp is not blank = (''), then compare the current time to the timestamp of the incoming data 70 // If the most recent timestamp is blank, it is most likely the first time the script is run. 71 // In this case skip this if statement and proceed to write in the column headers and data. 72 // It doesnt matter if the data is arriving late (current time vs timestamp of incoming data). 73 var now = new Date(); // now 74 var COMM_TIME = 120; // Note: changed to 120 to allow more messages to come through, previously set at 5 seconds & worked ok // rough overestimate of communication time between cloud and app 75 if (now.getTime() - date.getTime() > COMM_TIME * 1000) { 76 // If the difference between the current time and the timestamp is greater than 5 seconds, discard the data. 77 // When the condition in this If statement evaluates true, the funtion will stop due to the return statement. 78 return; // "The return statement stops the execution of a function and returns a value from that function." 79 } 80 } 81 82 // This section writes values to the header row based on the names of the incoming properties 83 // In other words, this section creates the column names 84 85 // Assign a name to the cell that is in the header row & in the first column = timestamp 86 sheet.getRange(HEADER_ROW, 1).setValue('timestamp'); 87 88 for (var i = 0; i < incLength; i++) { 89 var lastCol = sheet.getLastColumn(); // at the very beginning this should return 1 // second cycle -> it is 2 90 // column 1 is the timestamp column. 91 if (lastCol == 1) { 92 // write the column names beginning with the column after lastCol == 1 which is the timestamp column 93 // incNames is an array containing the names of all of the incoming properties 94 // If lastCol == 1, write the value from 'i'th location in the incNames array to the header row in column #2 = lastCol + 1 95 sheet.getRange(HEADER_ROW, lastCol + 1).setValue(incNames[i]); 96 } else { 97 // evaluated if the lastCol != 1 98 // check if the name is already in header 99 var found = 0; 100 for (var col = 2; col <= lastCol; col++) { 101 // starting with column 2, iterate through all of the columns up to the lastCol evaluating the if statement enclosed 102 if (sheet.getRange(HEADER_ROW, col).getValue() == incNames[i]) { 103 // the condition of this If statement compares the value in the header row & column # = col to the 'i'th value in the array of incoming property names 104 // This if statement is evaulated for each iteration of the for loop that it is enclosed in. 105 // The condition is evaluated for all of the columns from column #2 to the last column. 106 // It is checking to see if the 'i'th value in the incNames array exists in any of the columns in the header row. 107 // If the 'i'th value in the incNames array finds a match to any of the values in the header row, set found = 1 & exit the for loop with the break statment. 108 found = 1; 109 break; // "The break statement breaks the loop and continues executing the code after the loop" 110 } // close if statement evaluated for each iteration of the for loop that it is enclosed in. 111 } // close for loop to check the 'i'th value in the incNames array to the values in the header row. 112 if (found == 0) { 113 // This If statemnt will be evaluated after the preceeding for loop has completed. 114 // If found == 0 it means that the 'i'th value in the incNames array did not match any of the existing values in the header row. 115 // If found == 0, write the 'i'th value in the incNames array to the column after the last column. 116 // If new properties are added to the incoming data over time, the existing columns will not be impacted. The new property will be added to the column after the last column. 117 sheet.getRange(HEADER_ROW, lastCol+1).setValue(incNames[i]); 118 } // close if statement 119 } // close else, since this is the end of the code block inside the main for loop, the next i will be evaluated up to i = incLength 120 // The block of code inside this for loop is evaluated for each value at location i in the incNames array. 121 // The values of i range from 0 to incLength (the number of values in the names array) 122 // In JavaScript the index in arrays starts at 0. In other words, the 1st value in the array is at location = 0. 123 } // close main for loop used to write column names (assigning values from names array to header row) 124 125 // redefine last coloumn and last row since new names could have been added 126 var lastCol = sheet.getLastColumn(); 127 var lastRow = sheet.getLastRow(); 128 129 // delete last row to maintain constant the total number of rows 130 if (lastRow > MAX_ROWS + HEADER_ROW - 1) { 131 sheet.deleteRow(lastRow); 132 } 133 134 // insert new row after deleting the last one 135 sheet.insertRowAfter(HEADER_ROW); 136 137 // reset style of the new row, otherwise it will inherit the style of the header row 138 var range = sheet.getRange('A2:Z2'); 139 //range.setBackground('#ffffff'); 140 range.setFontColor('#000000'); 141 range.setFontSize(10); 142 range.setFontWeight('normal'); 143 144 // write the timestamp 145 sheet.getRange(HEADER_ROW+1, TIMESTAMP_COL).setValue(date).setNumberFormat("yyyy-MM-dd HH:mm:ss"); 146 147 // write values in the respective columns 148 for (var col = 1+TIMESTAMP_COL; col <= lastCol; col++) { 149 // for loop to assign the value from incValues to the approrpriate column 150 151 // This block of code was replaced by an if statement checking for blank values after the incoming data is populated. 152 // Copy previous values 153 // This is to avoid empty cells if not all properties are updated at the same time 154 // sheet.getRange(HEADER_ROW+1, col).setValue(sheet.getRange(HEADER_ROW+2, col).getValue()); 155 156 for (var i = 0; i < incLength; i++) { 157 // for loop to identify the appropriate column to assign the value 158 159 // get the value in the header row in column = col 160 var currentName = sheet.getRange(HEADER_ROW, col).getValue(); 161 162 if (currentName == incNames[i]) { 163 // When the condition of this If statement evaluates as true, the 'i'th value in the incValues array will be assigned to row #2 (HEADER_ROW + 1) in column = col 164 165 // turn boolean values into 0/1, otherwise google sheets interprets them as labels in the graph 166 if (incValues[i] == true) { 167 incValues[i] = 1; 168 } else if (incValues[i] == false) { 169 incValues[i] = 0; 170 } // close else if to convert boolean values to 0/1 171 172 sheet.getRange(HEADER_ROW+1, col).setValue(incValues[i]); 173 } // close if checking for a match of the value in the header row for column = col to the 'i'th value in the incNames array 174 } // close for loop run for each value in the incNames array 175 176 if(sheet.getRange(HEADER_ROW+1, col).getValue() == ''){ 177 // If statement to check for blank values. 178 // If the condition is true the previous value will be copied. 179 // Option 1: copy the previous value if the incoming value is blank. This typicaly works, but sometimes the change in the value will not be captured in the transfer due to filtering of incoming messages to avoid duplicates. This can result in an old value getting carried forward 180 // sheet.getRange(HEADER_ROW+1, col).setValue(sheet.getRange(HEADER_ROW+2, col).getValue()); 181 // Option 2: Insert a dash if the incoming value is blank. 182 sheet.getRange(HEADER_ROW+1, col).setValue("-"); 183 } // close If statment used to copy the previous value if the value is blank. Since the values are only sent when they change, this carries forward the values that didn't change. 184 185 } // close for loop run for each column from col #2 (TIMESTAMP_COL + 1) to the lastCol 186 187 } // close if statment with the condtion (date.getYear() > 2018), used to eliminate dupicate messages from the Arduino Cloud 188} // close doPost function 189 190 191function sendEmail (){ 192 var emailAddress = sd.getRange("V3").getValue(); 193 194 var lastPressure = sd.getRange("K3").getValue(); // pressure at last update 195 var lastUpdate = sd.getRange("A3").getValue(); // datetime of last update 196 var ssLink = "https://docs.google.com/spreadsheets/d/1XwCir2Llw8RvGPGgZI3Yk6U5a3LeIfUACNuO1Gr_LFQ/edit#gid=1123486497"; 197 198 var triggerAlertCount = sd.getRange("L6").getValue(); 199 var triggerMonitor = sd.getRange("M3").getValue(); 200 var dtLastStatusChange = sd.getRange("P3").getValue(); 201 202 var dtLastDeviceUpdate1 = sd.getRange("S3").getValue(); 203 var dtLastDeviceUpdate2 = sd.getRange("S4").getValue(); 204 205 var emailSentNoUpdate = sd.getRange("T3").getValue(); 206 var emailSentStartedReading = sd.getRange("U3").getValue(); 207 208 var message = "Last Device Update: " + "\ 209" + lastUpdate + "\ 210\ 211" + " Last Pressure: " + "\ 212\ " + lastPressure.toFixed(2) + " psi" + "\ 213\ 214" + " Link to Spreadsheet: " + "\ 215\ " + ssLink; 216 217 if(triggerMonitor == 0){ 218 sd.getRange("L5").setValue(0); 219 sd.getRange("L6").setValue(0); 220 } 221 222 if(triggerMonitor == -1 && triggerAlertCount <= 4){ 223 sd.getRange("L3").setValue(lastUpdate); // emailSent 224 sd.getRange("L5").setValue(-1); 225 sd.getRange("L6").setValue(triggerAlertCount + 1); 226 var subject = "Status Change Alert - Outside Setpoints"; 227 MailApp.sendEmail(emailAddress, subject, message); 228 } 229 230 if(triggerMonitor == 1 && triggerAlertCount <= 4){ 231 sd.getRange("L3").setValue(lastUpdate); // emailSent 232 sd.getRange("L5").setValue(1); 233 sd.getRange("L6").setValue(triggerAlertCount + 1); 234 var subject = "Status Change Alert - Normal"; 235 MailApp.sendEmail(emailAddress, subject, message); 236 } 237 238 if(emailSentNoUpdate == 0 && dtLastDeviceUpdate1 > 60 && dtLastDeviceUpdate2 > 60){ 239 sd.getRange("T3").setValue(1); // emailSentNoUpdate 240 sd.getRange("U3").setValue(0); // emailSentStartedReading 241 sd.getRange("T4").setValue(now.getTime()); // emailSentNoUpdate 242 var subject = "Alert - Over 60 minutes Since Last Device Update"; 243 MailApp.sendEmail(emailAddress, subject, message); 244 } 245 246 if(emailSentNoUpdate == 1 && dtLastDeviceUpdate1 < 60){ // removed the following from the condition: && dtLastDeviceUpdate2 > 60 247 sd.getRange("T3").setValue(0); // emailSentNoUpdate 248 sd.getRange("U3").setValue(1); // emailSentStartedReading 249 sd.getRange("U4").setValue(now.getTime()); // emailSentStartedReading 250 var subject = "Alert - Device Started Updating"; 251 MailApp.sendEmail(emailAddress, subject, message); 252 } 253 254} 255
Downloadable files
Schematic
Schematic
Schematic
Schematic
Comments
Only logged in users can leave comments