亿迅智能制造网
工业4.0先进制造技术信息网站!
首页 | 制造技术 | 制造设备 | 工业物联网 | 工业材料 | 设备保养维修 | 工业编程 |
home  MfgRobots >> 亿迅智能制造网 >  >> Manufacturing Technology >> 制造工艺

物联网压力传感器:MKR GSM + Arduino Cloud + Google Sheets

组件和用品

Arduino UNO
× 1
Arduino MKR GSM 1400
× 1
压力传感器 (0-150 psi)
× 1
Adafruit DHT22 温湿度传感器
× 1
DS3231 RTC 模块
× 1
Adafruit 逻辑电平转换器 - BSS138 - 4 通道 I2C 安全双向
× 1
Adafruit 3.7V LiPo 电池(2000 mAh 或更高)
× 2
电压升压转换器
× 1
带有各种 LED 和电阻器的基本入门套件
× 1
面包板(通用)
× 1

应用和在线服务

Google 表格
Arduino IoT Cloud
Arduino 网页编辑器
Arduino IDE

关于这个项目

目标

该项目的目标是为低成本设备创建原型,以使用蜂窝数据监测偏远地区工业设备的压力。

项目演练

以下是该项目的分步演练,遵循从压力传感器到由附加到 Google 表格的脚本生成的电子邮件通知的数据流。

步骤 1:Uno 的压力传感器

压力传感器将压力转换为模拟电信号。

Arduino Uno 将来自压力传感器的模拟信号转换为压力 (psi)。

第 2 步:通过串口将 Uno 转换为 MKR GSM 1400

通过串口在两个 Arduino 设备之间进行通信时:

  • 将设备 #1 上的 RX 连接到设备 #2 上的 TX
  • 将设备 #1 上的 TX 连接到设备 #2 上的 RX
  • 设备必须有一个共同点

数据传输频率(Uno to MKR GSM 1400)

  • 正常: 每 30 分钟(传输频率),Uno 会将数据串行打印到 MKR GSM 1400,后者将数据传输到云端。
  • 高/低触发: 如果压力超过 40 psi (highTrigger) 或低于 20 psi (lowTrigger) 并在那里停留超过 2 分钟 (dtLastTriggerLimit),Uno 会将数据串行打印到 MKR GSM 1400,后者会将数据传输到云端。
  • 需求调查: 如果 Uno 上的引脚 A1 被推高,它将串行打印数据到 MKR GSM 1400,后者将数据传输到云。注意:引脚 A1 在 Uno 的草图中命名为“buttonPin”。有两种方法可以将 Uno 上的引脚 A1 推高。 (1) 面包板上有一个按钮。 (2) 如果 MKR GSM 1400 上的引脚 A3 为高电平,则会将引脚 A1 推高。由于引脚 A3 由 Arduino Cloud 中的输入控制,因此可以随时远程获取当前压力,而无需等待定期安排的数据传输。

备注

  • 可以修改 Uno 的草图,以便除了当前版本的压力外,还可以使用高低设置点监控温度、湿度、电池电压等多个输入。
  • 用于将来自压力传感器的模拟信号转换为压力 (psi) 的代码基于以下 YouTube 视频中的说明:https://www.youtube.com/watch?v=AB7zgnfkEi4
  • Arduino 论坛上关于“串行输入基础”的以下帖子在编写代码以使用串行数据从一个设备到另一个设备时非常有用:https://forum.arduino.cc/index。 php?topic=288234.0

本项目中用于 Arduino Uno 的代码附有解释关键信息的注释。

文件名:“InstrumentReader”

第 3 步:通过蜂窝网络将 MKR GSM 1400 连接到 Arduino 云

MKR GSM 1400 处理来自 Arduino Uno 的串行数据,并使用蜂窝数据将数据传输到 Arduino Cloud。

值得注意的是,在 MKR GSM 1400 的代码中,您会看到 Serial1.read 而不是 Serial.read。 Arduino 网站上的参考资料给出了很好的解释。下图中的表格显示 MKR 板上的 TX/RX 引脚是通过 Serial1 访问的。

https://www.arduino.cc/reference/en/language/functions/communication/serial/

Arduino 云

该项目在 Arduino Cloud 中设置了 2 个变量。下图显示了这些变量如何显示在 Arduino Cloud 的仪表板中。

第一个名为“dataStringCloud”的变量实际上是来自设备的所有数据的包。采取这种方法而不是每个值一个变量来简化在 Google 表格中处理数据的过程。对于单独的变量名称,很难区分保持不变的值和未更新的值之间的区别。来自此包的数据在 Google Sheets 中解析。

名为“pinCloud”的第二个变量用于从 Arduino Cloud 控制 MKR GSM 1400。 Sketch中有一个开关函数,根据pinCloud的值控制动作。当 pinCloud =1 时,引脚 A1 被推高,导致板上的 LED 亮起。当 pinCloud =2 时,引脚 A3 被推高,导致 Arduino Uno 如上所述发送当前数据。

本项目中用于 Arduino MKR GSM 1400 的代码附有解释关键信息的注释。

文件名:“通讯设备”

第 4 步:通过 Webhook 将 Arduino Cloud 连接到 Google Sheets

使用网络钩子将数据从 Arduino Cloud 传输到 Google Sheets。

webhook 的核心是用 Google Sheets 文件的脚本编写的 doPost 函数。

这是如何设置 webhook 的快速摘要。 请注意,该过程在 Google 表格中开始。直到最后,您才能访问 Arduino Cloud。从 A 到 B,从 B 开始。

  • 创建新的 Google 表格文件
  • 点击工具栏上的“工具”并在下拉菜单中选择“脚本编辑器”
  • 使用doPost 函数编写代码 (参考本项目附带的 GoogleSheetsScript.js)
  • 点击工具栏上的“发布”并在下拉菜单中选择“部署为网络应用...”
  • 一个对话框会出现 3 个字段。
  • (1) 项目版本: 始终使用下拉菜单选择“新建”。第一次更新后默认为当前版本号;如果您不使用下拉菜单选择“新建”,更改将不会生效。
  • (3) 谁有权访问应用程序: “任何人,甚至匿名”
  • 验证 3 个字段中的值后按部署
  • 第二个对话框将出现,其中包含“当前网络应用 URL”。这是您将复制并粘贴到 Arduino Cloud 上的 webhook 选项卡中的 URL。值得注意的是,无论项目版本如何,此 URL 都保持不变。
  • 点击确定,你就完成了!

该项目中使用的 JavaScript 代码的很大一部分是根据另一个名为“Arduino IoT Cloud Google Sheets Integration”的项目中使用的代码建模的。该项目的链接如下。我建议检查一下。

https://create.arduino.cc/projecthub/Arduino_Genuino/arduino-iot-cloud-google-sheets-integration-71b6bc?ref=part&ref_id=64347&offset=9

第 5 步:使用 Google 表格解析数据

使用 Google Sheets 解析 dataStringCloud 中的单个值并显示从 Arduino Cloud 传输的唯一记录

下面的链接指向用于设备最近测试的 Google 表格文件。此文件中的单元格根据它们的填充方式进行着色,如每张纸上的图例所示。

https://docs.google.com/spreadsheets/d/1XwCir2Llw8RvGPGgZI3Yk6U5a3LeIfUACNuO1Gr_LFQ/edit?usp=sharing

第 6 步:使用 Google 表格发送通知

您可能已经注意到,上面步骤 4 中引用的此项目的 JavaScript 文件 (GoogleSheetsScript.js) 中有两个函数。

  • doPost 函数 - 从 Arduino Cloud Webhook 传输数据。它在 Arduino 云上有新数据时运行。
  • sendEmail 函数 - 根据从项目的 Google 表格文件中名为“数据”的表格中提取的值发送电子邮件。它根据触发器设置中的设置每分钟运行一次。

为 sendEmail 函数设置触发器的步骤

  • 打开 Google 表格文件
  • 点击工具栏中的“工具”
  • 在下拉菜单中选择“脚本编辑器”
  • 从脚本编辑器窗口继续:
  • 点击工具栏中的“编辑”
  • 在下拉菜单中选择“当前项目的触发器”
  • 从 G Suite 开发者中心窗口继续:
  • 选择窗口右下角的“添加触发器”
  • 在出现的对话框中选择运行 sendEmail 函数。
  • 注意:在时间驱动的基础上运行触发器可以在设备停止更新时生成电子邮件通知。

电池寿命

~24小时

这可以通过关闭或移除显示器来优化。另一种选择是移除非必要的传感器,如 DHT22 和 DS3231。

数据使用

~0.7 兆字节/天

这可以通过减少数据传输的大小或频率来优化。例如:为了减小尺寸,只发送压力而不是压力、温度、湿度和时间。为了减少频率,仅每小时更新一次,而不是每 30 分钟一次。

项目成本

总计 =241 美元

  • Arduino MKR GSM 1400(70 美元)
  • Arduino Uno(22 美元)
  • 2 x 3.7V 锂聚合物电池(30 美元)
  • 2 个 LED 显示屏(29 美元)
  • 耐候塑料盒(22 美元)
  • 压力传感器(19 美元)
  • 温度/湿度传感器 - DHT22(10 美元)
  • RTC 模块 - DS3231(5 美元)
  • 升压转换器(5 美元)
  • 逻辑电平转换器(4 美元)
  • 杂项 - LED、电阻器、接线等(25 美元)

硬件/工具

本项目所使用的硬件和工具均购自以下机构:

  • Arduino 在线商店 (https://store.arduino.cc/)
  • Adafruit (https://www.adafruit.com/)
  • 亚马逊 (https://www.amazon.com/)
  • 海港货运
  • 家得宝

结论...

感谢您抽出宝贵时间审核此项目。

欢迎/赞赏任何和所有问题/反馈/意见/建议。

代码

  • InstrumentReader - Arduino Uno 的草图
  • GoogleSheetsScript.js
InstrumentReader - Arduino UnoArduino的草图
// Sketch 1 of 2// Arduino Uno // 数据由该设备收集并通过串行传输到 MKR 1400// DHT22 传感器需要 2 个库,但在代码中只调用了一个。 // (1):DHT 传感器库:https://github.com/adafruit/DHT-sensor-library // (2):Adafruit 统一传感器库:https://github.com/adafruit/Adafruit_Sensor #include #include #include  // RTC 模块#include  // DHT22#include "U8glib.h" // Velleman 128 x 64 OLED SPI 显示器 //注意:当我尝试将它用于此显示时,另一个标准 U8glib 库不起作用。当我使用制造商推荐的库时,它确实有效。 // 库:https://www.velleman.eu/support/downloads/?code=VMA437 // 语法:https://github.com/olikraus/u8glib/wiki/userreference &https://github.com/ olikraus/u8glib/wiki/thelloworld//#include  // 选择在以太网扩展板中保存到 Arduino UnoRTClib RTC 的 SD 卡;#define DHTPIN 11 // 连接到 DHT 传感器的数字引脚#define DHTTYPE DHT22 / / DHT 22 (AM2302), AM2321DHT dht(DHTPIN, DHTTYPE);U8GLIB_SH1106_128X64 u8g(3, 4, 6, 7); // (CLK/SCK:3, MOSI:4, CS:6, DC(A0):7) // u8g(sck, mosi, cs, a0 [, reset]) int y_pos =0; // 全局变量//const int chipSelect =10; // 选择在以太网扩展板中保存到 Arduino Uno 的 SD 卡//float fileSizeSD =0.0; // Arduino Unoint i =0 的以太网扩展板中保存到 SD 卡的选项; // 计算 Uno 读取的读数(与程序中的 # 循环相同) const int ledPin =9; // 传输指示器(发生数据传输时闪烁)const int ledPin2 =8; // 推动传输指标(通过面包板上的手动按钮或从云端激活的 MKR 1400 输出推高)const int buttonPin =A1;int buttonState =0;int transferFrequency =30; // 串行打印的频率 dataString 用于向第二个设备发送数据(分钟)String pTransmitDateTime ="";int transferCounter =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,输出); pinMode(ledPin2,输出); pinMode(buttonPin, INPUT); u8g.setRot180(); // 翻转屏幕,如果需要(添加/删除此行上的注释以进行旋转) // 保存到 Arduino Uno 以太网扩展板中的 SD 卡的选项 // Serial.print("Initializing SD card..."); // if (!SD.begin(chipSelect)) // 查看卡是否存在并且可以被初始化 // { // Serial.println("Card failed, or not present"); // 而 (1); // 不要再做任何事情 // } // Serial.println("card initialized.");}void loop (void) { delay(5000);现在日期时间 =RTC.now();浮动 cRuntime =毫秒()/60000; float p =getPressure();// Serial.println(p); buttonState =digitalRead(buttonPin);// Serial.print("Button:");// Serial.println(buttonState); if(buttonState ==1) { digitalWrite(ledPin2, HIGH);延迟(30000); // 如果 MKR1400:pingPin 将 Uno:buttonPin 推高,则延迟以允许 MKR1400 准备接收数据 } else { digitalWrite(ledPin2, LOW);浮动 h =dht.readHumidity();浮动 t =dht.readTemperature(true); // t =dht.readTemperature(true) --> temp if degree F &t =dht.readTemperature() --> temp if degree C int transferIndicator =0; if(now.minute() % transferFrequency ==0 &&now.minute() !=pTransmitMinute) {transmitIndicator =1; pTransmitMinute =now.minute(); pTransmitDateTime =String(now.hour())+String(":")+String(now.minute())+String(":")+String(now.second()); } int triggerStatus =0; if(p <=lowTrigger || p>
=highTrigger) { // 注意:此 if 语句的条件中引用的变量根据高和低设置点进行评估 // 可以快速更改评估哪个变量 - 这是仅指定变量的位置 triggerStatus =1;触发计数器++; } else { 触发器计数器 =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) // if(TriggerStatus 存在指定的最小时间为 Alert &Count =1 意味着这是时间超过指定的最小时间的第一个循环time // 或者 triggerAlert 状态改变——如果 TriggerStatus 回到 0 意味着不再满足触发条件,这将生成推送警报。){ triggerPushTransmitAlertIndicator =1;闪光灯(5,500);延迟(5000); } ptriggerTransmitAlertIndicator =triggerTransmitAlertIndicator; // 当前指标存储到前一个指标。在下一个循环中,此处传输的值将与基于新值生成的值进行比较。 // 创建字符串 String dataString =""; String 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);闪光灯(10,500);传输计数器++; } // Serial.print("T:");// Serial.println(triggerStatus);延迟(100); // 等待整个消息到达 // 图片循环 u8g.firstPage();做 { draw(cDateTime,cHumTemp, cP,i,transmitCounter,now.minute(),transmitFrequency,pTransmitMinute); } while(u8g.nextPage());延迟(1000); // writeToSD(dataString); // Arduino Uno i++ 以太网扩展板中保存到 SD 卡的选项;}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_font_micro //u8g_font_5x7 //u8g_font_5x8 //u8g_font_6x10 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("现在分钟:"); 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("余数:"); u8g.setPrintPos(70,48); u8g.print(DnowMinute % DtransmitFrequency);}float getPressure(){ int sensorVal=analogRead(A2);// Serial.print("Sensor Value:");// Serial.print(sensorVal);浮动电压 =(sensorVal*5.0)/1023.0;// Serial.print(" Volts:"); // Serial.print(电压); // 当压力 =0 时,模拟输入 =100 // 模拟输入到电压的转换:模拟输入 =100 -> 电压 =100*(5/1023) =0.4889 float m =((150-0)/(4.5- 0.4889)); float b =150 - (m*4.5);// Serial.print(" m =");// Serial.print(m);// Serial.print(" b =");// Serial.print( b); float pressure_psi =((m*电压)+ b);// Serial.print(" Pressure =");// Serial.print(pressure_psi);// Serial.println("psi"); // 延迟(200); return pressure_psi;}void flashLED(int num, int t){ for (int z =1; z <=num; z++) { digitalWrite(ledPin, HIGH);延迟(t);数字写入(ledPin,低);延迟(t); }}// Arduino Uno 以太网扩展板中保存到 SD 卡的选项 //void writeToSD(String dataToWrite) //{ // // 打开文件。请注意,一次只能打开一个文件,// // 因此您必须先关闭这个文件,然后再打开另一个文件。 // 文件 dataFile =SD.open("datalog4.txt", FILE_WRITE); // fileSizeSD =dataFile.size(); // 以字节为单位返回文件大小 // fileSizeSD =fileSizeSD / 1000000; // 将字节转换为 MB。 1 MB =1e6 字节 // // 如果文件可用,则写入它: // if (dataFile) // { // dataFile.println(dataToWrite); // dataFile.close(); // // 也打印到串口: // // Serial.println(dataToWrite); // } // // 如果文件没有打开,弹出一个错误: // else // { // Serial.println("error opening datalog1.txt"); // } //}
GoogleSheetsScript.jsJavaScript
// 此代码中有 2 个函数// doPost 函数 - 从 Arduino Cloud Webhook 传输数据。它在 Arduino 云上有新数据时运行。// sendEmail 函数 - 根据从项目的 Google 表格文件中名为“数据”的表格中提取的值发送电子邮件。它根据触发器设置中的设置每分钟运行一次。有关说明,请参阅项目中心帖子的 sendEmail 函数部分。// 此代码的大部分(除了 sendEmail 函数)都模仿了 Arduino 项目中心// https://create.arduino 上的以下项目。 cc/projecthub/Arduino_Genuino/arduino-iot-cloud-google-sheets-integration-71b6bc?f=1// 这是指向 GitHub 存储库的链接,其中包含用于 Project Hub 上上述项目的 Google 脚本。 // 此链接是从项目中心的项目描述中复制的。// https://github.com/arduino/arduino-iot-google-sheet-script/blob/master/Code.gs// 激活spreasheetvar ss =SpreadsheetApp.getActiveSpreadsheet();// 获取名为 RawData 的表单 sheet =ss.getSheetByName("RawData");var sd =ss.getSheetByName("Data");var sref =ss.getSheetByName("References"); var MAX_ROWS =1440; // 要显示的最大数据行数// 3600s / cloud_int(30s) * num_ore(12h) =(60*60*12)/30 =(3600*12)/30 =12 小时内 30 秒更新的 1440 个读数间隔// (60*24)/15 =24 小时内 96 个读数,更新间隔为 15 分钟// 15 天 * 96 个读数/天 =1440 个读数// 90 天 * 96 个读数/天 =8640 个读数// 365天 * 96 个读数/天 =35040 个读数var HEADER_ROW =1; // headervar 的行索引 TIMESTAMP_COL =1; // 时间戳的列索引 columnfunction doPost(e) { var cloudData =JSON.parse(e.postData.contents); // 这是一个 json 对象,包含来自 IoT Cloud console.log(cloudData); //var webhook_id =cloudData.webhook_id; // 真的不使用这三个 //var device_id =cloudData.device_id; //var thing_id =cloudData.thing_id; var 值 =cloudData.values; // 这是一个 json 对象数组 console.log(values); // 存储值数组中的名称和值 // 每个传入的属性都有一个: // 名称将成为列名称 // 值将写入列标题下方的行中 var incLength =values.length; var incNames =[]; var incValues =[]; for (var i =0; i  2018) { // 丢弃所有到达的消息'late' if (sheet.getRange(HEADER_ROW+1, 1).getValue() !='') { // HEADER_ROW + 1 =Row #2 &Column #1 --> 这是最近时间戳的位置在工作表中 // 如果最近的时间戳不为空 =(''),则将当前时间与传入数据的时间戳进行比较 // 如果最近的时间戳为空,则很可能是脚本的第一次正在运行。 // 在这种情况下,跳过此 if 语句并继续写入列标题和数据。 // 数据是否迟到并不重要(当前时间与传入数据的时间戳)。 var now =new Date(); // 现在 var COMM_TIME =120; // 注意:更改为 120 以允许更多消息通过,之前设置为 5 秒并且工作正常 // 粗略高估了云和应用程序之间的通信时间 if (now.getTime() - date.getTime()> COMM_TIME * 1000) { // 如果当前时间与时间戳相差大于5秒,则丢弃数据。 // 当此 If 语句中的条件计算为真时,该函数将因 return 语句而停止。返回; // “return 语句停止执行一个函数并从该函数返回一个值。” } } // 此部分根据传入属性的名称将值写入标题行 // 换句话说,此部分创建列名称 // 为位于标题行和第一个的单元格分配名称column =时间戳 sheet.getRange(HEADER_ROW, 1).setValue('timestamp'); for (var i =0; i  它是 2 // 第 1 列是时间戳列。 if (lastCol ==1) { // 写入以 lastCol ==1 之后的列开头的列名,这是时间戳列 // incNames 是一个包含所有传入属性名称的数组 // If lastCol ==1 , 将 incNames 数组中第 i 个位置的值写入列 #2 =lastCol + 1 sheet.getRange(HEADER_ROW, lastCol + 1).setValue(incNames[i]); } else { // evaluated if the lastCol !=1 // check if the name is already in header var found =0; for (var col =2; col <=lastCol; col++) { // starting with column 2, iterate through all of the columns up to the lastCol evaluating the if statement enclosed if (sheet.getRange(HEADER_ROW, col).getValue() ==incNames[i]) { // 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 // This if statement is evaulated for each iteration of the for loop that it is enclosed in. // The condition is evaluated for all of the columns from column #2 to the last column. // It is checking to see if the 'i'th value in the incNames array exists in any of the columns in the header row. // 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. found =1;休息; // "The break statement breaks the loop and continues executing the code after the loop" } // close if statement evaluated for each iteration of the for loop that it is enclosed in. } // close for loop to check the 'i'th value in the incNames array to the values in the header row. if (found ==0) { // This If statemnt will be evaluated after the preceeding for loop has completed. // 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. // If found ==0, write the 'i'th value in the incNames array to the column after the last column. // 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. sheet.getRange(HEADER_ROW, lastCol+1).setValue(incNames[i]); } // close if statement } // 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 // The block of code inside this for loop is evaluated for each value at location i in the incNames array. // The values of i range from 0 to incLength (the number of values in the names array) // In JavaScript the index in arrays starts at 0. In other words, the 1st value in the array is at location =0. } // close main for loop used to write column names (assigning values from names array to header row) // redefine last coloumn and last row since new names could have been added var lastCol =sheet.getLastColumn(); var lastRow =sheet.getLastRow(); // delete last row to maintain constant the total number of rows if (lastRow> MAX_ROWS + HEADER_ROW - 1) { sheet.deleteRow(lastRow); } // insert new row after deleting the last one sheet.insertRowAfter(HEADER_ROW); // reset style of the new row, otherwise it will inherit the style of the header row var range =sheet.getRange('A2:Z2'); //range.setBackground('#ffffff'); range.setFontColor('#000000'); range.setFontSize(10); range.setFontWeight('normal'); // write the timestamp sheet.getRange(HEADER_ROW+1, TIMESTAMP_COL).setValue(date).setNumberFormat("yyyy-MM-dd HH:mm:ss"); // write values in the respective columns for (var col =1+TIMESTAMP_COL; col <=lastCol; col++) { // for loop to assign the value from incValues to the approrpriate column // This block of code was replaced by an if statement checking for blank values after the incoming data is populated. // Copy previous values // This is to avoid empty cells if not all properties are updated at the same time // sheet.getRange(HEADER_ROW+1, col).setValue(sheet.getRange(HEADER_ROW+2, col).getValue()); for (var i =0; i  2018), used to eliminate dupicate messages from the Arduino Cloud} // close doPost functionfunction sendEmail (){ var emailAddress =sd.getRange("V3").getValue(); var lastPressure =sd.getRange("K3").getValue(); // pressure at last update var lastUpdate =sd.getRange("A3").getValue(); // datetime of last update 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); // emailSent sd.getRange("L5").setValue(-1); sd.getRange("L6").setValue(triggerAlertCount + 1); var subject ="Status Change Alert - Outside Setpoints"; MailApp.sendEmail(emailAddress, subject, message); } if(triggerMonitor ==1 &&triggerAlertCount <=4){ sd.getRange("L3").setValue(lastUpdate); // emailSent sd.getRange("L5").setValue(1); sd.getRange("L6").setValue(triggerAlertCount + 1); var subject ="Status Change Alert - Normal"; MailApp.sendEmail(emailAddress, subject, message); } if(emailSentNoUpdate ==0 &&dtLastDeviceUpdate1> 60 &&dtLastDeviceUpdate2> 60){ sd.getRange("T3").setValue(1); // emailSentNoUpdate sd.getRange("U3").setValue(0); // emailSentStartedReading sd.getRange("T4").setValue(now.getTime()); // emailSentNoUpdate var subject ="Alert - Over 60 minutes Since Last Device Update"; MailApp.sendEmail(emailAddress, subject, message); } if(emailSentNoUpdate ==1 &&dtLastDeviceUpdate1 <60){ // removed the following from the condition:&&dtLastDeviceUpdate2> 60 sd.getRange("T3").setValue(0); // emailSentNoUpdate sd.getRange("U3").setValue(1); // emailSentStartedReading sd.getRange("U4").setValue(now.getTime()); // emailSentStartedReading var subject ="Alert - Device Started Updating"; MailApp.sendEmail(emailAddress, subject, message); }} 
CommunicationsDevice - Sketch for MKR 1400

示意图


制造工艺

  1. 凌华科技与 Google Cloud 合作提供物联网就绪解决方案
  2. 基本物联网 – RaspberryPI HDC2010 如何
  3. BMP180 I2C 数字气压传感器
  4. Python/MicroPython Sensor Logger with Google Sheets
  5. Raspberry Pi 2 上的 Windows 10 IoT Core – Adafruit 传感器数据
  6. Windows 10 IoT Core 和 SHT15
  7. UnifiedWater v1
  8. IOT - 使用 ESP8266、Arduino 和超声波传感器的智能罐
  9. Arduino Cloud Sensor Tower
  10. Arduino Apple Watch
  11. 使用 Alexa 和 Arduino IoT Cloud 完全控制您的电视
  12. Arduino 指纹传感器教程