Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
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
Модуль RTC DS3231 × 1
Adafruit Logic Level Converter BSS138 (4-канальный I2C) × 1
Adafruit 3.7В LiPo аккумулятор (2000 мАч и более) × 2
Повышающий DC-DC преобразователь × 1
Набор для начинающих (LED, резисторы) × 1
Макетная плата × 1

Приложения и онлайн-сервисы

Google Sheets
Arduino IoT Cloud
Arduino Web Editor
Arduino IDE

О проекте

Цель

Создать прототип недорогого устройства для мониторинга давления на промышленном оборудовании в удаленных локациях с использованием сотовой связи.

Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets

Пошаговое описание потока данных

Шаг 1: Датчик давления → Arduino Uno

Датчик преобразует давление в аналоговый электрический сигнал. Arduino Uno конвертирует аналоговый сигнал в давление (psi).

Шаг 2: Arduino Uno → MKR GSM 1400 по Serial

При последовательном соединении двух Arduino:

Частота передачи данных (Uno → MKR GSM 1400):

Скетч для Arduino Uno приложен с комментариями. Имя файла: "InstrumentReader".

Конвертация аналогового сигнала в psi основана на видео: YouTube. Полезен пост на форуме Arduino «Serial Input Basics»: forum.arduino.cc.

Шаг 3: MKR GSM 1400 → Arduino Cloud через сотовую сеть

MKR GSM 1400 обрабатывает Serial-данные от Uno и отправляет их в Arduino Cloud по сотовой связи.

В коде для MKR GSM 1400 используется Serial1.read вместо Serial.read. На MKR-платах пины TX/RX доступны через Serial1 (см. справку Arduino).

Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets

Arduino Cloud

Проект настроен с 2 переменными в Arduino Cloud. На дашборде отображаются:

Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets

Скетч для MKR GSM 1400 приложен. Имя файла: "CommunicationsDevice".

Шаг 4: Arduino Cloud → Google Sheets через Webhook

Данные передаются из Arduino Cloud в Google Sheets с помощью вебхука. Ядро — функция doPost в скрипте Google Sheets.

Краткая инструкция настройки вебхука (процесс начинается в Google Sheets, а не в Arduino Cloud):

  1. Создать новый файл Google Sheets.
  2. Инструменты → Редактор скриптов.
  3. Написать код с функцией doPost (см. прикрепленный GoogleSheetsScript.js).
  4. Опубликовать → Развернуть как веб-приложение.
  5. В диалоге: (1) Версия проекта — всегда выбирать «Новый»; (2) Выполнять от моего имени; (3) Доступ — «Любой, даже анонимный». Нажать «Развернуть».
  6. Появится «Current web app URL» — его копируем во вкладку вебхука в Arduino Cloud. URL остается неизменным при обновлениях версий.

Часть JavaScript кода основана на проекте «Arduino IoT Cloud Google Sheets Integration»: ссылка.

Шаг 5: Парсинг данных в Google Sheets

Google Sheets парсит отдельные значения из dataStringCloud и отображает уникальные записи.

Пример таблицы из недавнего теста (ячейки раскрашены по легенде на каждом листе): Google Sheets.

Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets

Шаг 6: Отправка уведомлений из Google Sheets

В JavaScript файле (GoogleSheetsScript.js) две функции:

Настройка триггера для sendEmail:

  1. Открыть Google Sheets → Инструменты → Редактор скриптов.
  2. Правка → Триггеры текущего проекта.
  3. В G Suite Developer Hub: Добавить триггер (правый нижний угол).
  4. Выбрать функцию sendEmail, тип — Time-Driven (например, каждую минуту). Это позволяет слать алерты, когда устройство перестает обновляться.
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets

Время работы от батареи

~24 часа. Можно оптимизировать: отключить/убрать дисплеи, убрать необязательные датчики (DHT22, DS3231).

Потребление данных

~0.7 МБ/день. Оптимизация: уменьшить размер или частоту передач (только давление вместо набора параметров, обновления раз в час вместо 30 минут).

Стоимость проекта

Итого = $241

Инструменты и покупки

Все приобретено в:

Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets
Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets

Заключение

Спасибо за внимание к проекту. Любые вопросы, отзывы, комментарии, предложения приветствуются и ценится.

Код

InstrumentReader — Скетч для Arduino Uno

// Sketch 1 of 2 // Arduino Uno
// Данные собираются этим устройством и передаются на MKR 1400 по Serial
// DHT22 требует 2 библиотеки, но в коде вызывается одна:
// (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 модуль
#include <DHT.h> // DHT22
#include "U8glib.h" // Velleman 128x64 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 <SD.h> // Опция сохранения на SD карту в Ethernet Shield для Uno
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 option
//float fileSizeSD = 0.0; // SD card option
int i = 0; // счетчик измерений
const int ledPin = 9; // индикатор передачи (мигает при отправке)
const int ledPin2 = 8; // индикатор принудительной отправки (кнопка на плате или сигнал от MKR 1400 из облака)
const int buttonPin = A1;
int buttonState = 0;
int transmitFrequency = 30; // частота передачи данных (минуты)
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(); // переворот экрана при необходимости
  // SD card init option commented out
}

void loop(void) {
  delay(5000);
  DateTime now = RTC.now();
  float cRuntime = millis() / 60000.0;
  float p = getPressure();
  buttonState = digitalRead(buttonPin);
  if (buttonState == 1) {
    digitalWrite(ledPin2, HIGH);
    delay(30000); // задержка для готовности MKR1400 к приему
  } else {
    digitalWrite(ledPin2, LOW);
  }
  float h = dht.readHumidity();
  float t = dht.readTemperature(true); // true = Фаренгейт, false = Цельсий
  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);

  // Отрисовка на OLED
  u8g.firstPage();
  do {
    draw(cDateTime, cHumTemp, cP, i, transmitCounter, now.minute(), transmitFrequency, pTransmitMinute);
  } while (u8g.nextPage());
  delay(1000);
  // writeToSD(dataString); // SD card option
  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;
  // При давлении 0: Analog Input = 100 -> Voltage = 100 * (5/1023) = 0.4889
  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);
  }
}

// SD card option commented out

GoogleSheetsScript.js (JavaScript)

// Две функции:
// doPost — принимает данные от вебхука Arduino Cloud. Запускается при новых данных.
// sendEmail — шлет email на основе значений из листа "Data". Запускается каждую минуту по триггеру.
// Большая часть кода (кроме sendEmail) основана на проекте Arduino IoT Cloud Google Sheets Integration
// https://create.arduino.cc/projecthub/Arduino_Genuino/arduino-iot-cloud-google-sheets-integration-71b6bc?f=1
// GitHub репозиторий с Google Script: 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; // формат: yyyy-MM-ddTHH:mm:ss.mmmZ
  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;
  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 — Скетч для MKR 1400

(Код не показан в превью, доступен для скачивания)

Схемы

Мониторинг давления в реальном времени с MKR GSM, Arduino Cloud и Google Sheets

Производственный процесс

  1. ADLINK и Google Cloud сотрудничают для поставки бесшовных IoT-решений
  2. Raspberry Pi и HDC2010: полное руководство по настройке I2C датчика температуры и влажности
  3. Интеграция барометрического датчика BMP180 с Raspberry Pi по I2C
  4. Python/MicroPython логгер датчиков: бесшовное сохранение данных в Google Sheets
  5. Передача данных Adafruit 10DOF IMU с Raspberry Pi 2 в Azure Event Hubs на Windows 10 IoT Core
  6. Точный мониторинг температуры и влажности с SHT15 на Windows 10 IoT Core
  7. UnifiedWater v1: полный набор датчиков качества воды на Arduino MKR GSM
  8. Умная банка на ESP8266 + Arduino: мониторинг наполненности в реальном времени
  9. Сенсорная башня на Arduino Cloud: Мониторинг температуры, движения и газа
  10. DIY Arduino Smartwatch: самодельные умные часы в стиле Apple Watch