element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet & Tria Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • About Us
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
In Reach! – Ultrasonic Sensor Sensing Challenge
  • Challenges & Projects
  • Design Challenges
  • In Reach! – Ultrasonic Sensor Sensing Challenge
  • More
  • Cancel
In Reach! – Ultrasonic Sensor Sensing Challenge
Forum Early Flood Warning and Monitoring System #3 - Code and Cloud Making the MKR Talk to Google Sheets
  • News
  • Forum
  • Projects
  • DC
  • Files
  • Members
  • More
  • Cancel
  • New
Join In Reach! – Ultrasonic Sensor Sensing Challenge to participate - click to join for free!
Actions
  • Share
  • More
  • Cancel
Forum Thread Details
  • Replies 0 replies
  • Subscribers 41 subscribers
  • Views 32 views
  • Users 0 members are here
  • design challenge
  • TDK waterproof sensors
  • Ultrasonic Sensor Sensing Challenge
  • flood warning system
Related

Early Flood Warning and Monitoring System #3 - Code and Cloud Making the MKR Talk to Google Sheets

sandeepdwivedi17
sandeepdwivedi17 8 days ago

Alright — time for the slightly nerdy, very satisfying bit.
This is where the sensors stop being lonely hardware and start sending real data to the cloud.
No smoke. No drama. Just packets, spreadsheets, and the occasional garbage value that makes you laugh and cry.

The simple goal — in one line

Read water level. Send it. Get an alert if things go wrong.

What I stitch together here

  • Arduino MKR WiFi 1010 — the brain and Wi-Fi radio.
  • TDK Eval board + USSM sensors — they do distance sensing.
  • Google Sheets — the datastore and quick dashboard.
  • Google Apps Script — the tiny web API that accepts posts and emails alerts.

Think: sensor → MKR → HTTPS POST → Apps Script → Sheet → emails.
That chain is the whole system.

Why Google Sheets? (Spoiler: Because it’s fast)

Sheets is free, easy, and everyone knows it.
You don’t need a server or a database yet.
For a prototype and a competition demo, it’s perfect.
Later we can swap it for a proper backend. But right now — speed wins.

Server-side: Apps Script doPost

On the Google side, doPost(e) checks:

  • JSON parse ok?
  • api_key matches?
  • value is numeric?
  • alert_requested true AND value compared vs THRESHOLD_VALUE in the sheet?

If conditions match, it sends email(s) and writes Alert Sent @ TIMESTAMP into the DataLog row.

Create a google sheet "TDK-MKR-Logger" with 3 tabs.
tab1 named "ConfigDetails"

image

Tab 2 "Email_list"

image

Tab 3 "DataLog"

image

Click on Extensions and select Apps Script
image
image

Copy and Paste my Code into it.

/**
 * Google Apps Script for "TDK-MKR-Logger" Google Sheet
 *
 * Sheets expected:
 * 1) "ConfigDetails" - columns: key | Value | Description
 *      - keys expected: THRESHOLD_VALUE, POST_INTERVAL_MS
 * 2) "Email_list"    - column A header "Email Addresses" followed by emails
 * 3) "DataLog"       - columns: Timestamp | Sensor Name | Value | Alert Sent
 *
 * doPost(e): Accepts JSON payload and appends row to DataLog
 *           Payload example:
 *           { "sensor_name": "MKR_UltraSim_01", "value": 12.3, "alert_requested": true }
 *
 * doGet(e): Returns JSON config: { "THRESHOLD_VALUE": 15, "POST_INTERVAL_MS": 10000 }
 *
 * Notes:
 * - This script uses MailApp to send emails (requires permission).
 * - Deploy as Web App and set "Execute as: Me" so MailApp runs under your account.
 */

const SHEET_NAME = 'TDK-MKR-Logger';
const TAB_CONFIG = 'ConfigDetails';
const TAB_EMAILS = 'Email_list';
const TAB_DATALOG = 'DataLog';

// MAIN - POST handler
function doPost(e) {
  try {
    if (!e || !e.postData || !e.postData.contents) {
      return jsonResponse({ error: 'No POST data received' }, 400);
    }

    // parse JSON body
    let payload;
    try {
      payload = JSON.parse(e.postData.contents);
    } catch (err) {
      return jsonResponse({ error: 'Invalid JSON', details: err.toString() }, 400);
    }

    const sensorName = (payload.sensor_name || payload.sensor || 'UnknownSensor').toString();
    // coerce numeric
    const value = parseFloat(payload.value);
    const alertRequested = Boolean(payload.alert_requested || payload.alert || false);

    if (isNaN(value)) {
      return jsonResponse({ error: 'Value must be a number' }, 400);
    }

    const ss = SpreadsheetApp.getActiveSpreadsheet();
    if (!ss) {
      return jsonResponse({ error: 'Spreadsheet not found or not opened' }, 500);
    }
    const configSheet = ss.getSheetByName(TAB_CONFIG);
    const emailSheet = ss.getSheetByName(TAB_EMAILS);
    const dataSheet  = ss.getSheetByName(TAB_DATALOG);

    if (!configSheet || !emailSheet || !dataSheet) {
      return jsonResponse({ error: 'One or more expected tabs not found', requiredTabs: [TAB_CONFIG, TAB_EMAILS, TAB_DATALOG] }, 500);
    }

    // read config values
    const config = readConfig(configSheet);
    const threshold = Number(config.THRESHOLD_VALUE);
    // fallback if config missing or invalid
    const THRESHOLD_VALUE = isFinite(threshold) ? threshold : 15;
    const nowIso = (new Date()).toISOString();

    // prepare row: Timestamp | Sensor Name | Value | Alert Sent
    let alertSentText = '';
    const newRow = [ nowIso, sensorName, value, '' ];
    dataSheet.appendRow(newRow);
    const lastRow = dataSheet.getLastRow();

    // If alert requested AND value is below threshold (per your spec "lesser than threshold triggers alert")
    if (alertRequested && value < THRESHOLD_VALUE) {
      // read emails
      const emails = readEmails(emailSheet);
      if (emails.length > 0) {
        const subject = `ALERT: ${sensorName} reading ${value} < ${THRESHOLD_VALUE}`;
        const body = [
          `Sensor: ${sensorName}`,
          `Value: ${value}`,
          `Threshold: ${THRESHOLD_VALUE}`,
          `Timestamp: ${nowIso}`,
          '',
          'This alert was generated by the TDK-MKR-Logger Google Apps Script.'
        ].join('\n');

        // send to each email (MailApp.sendEmail handles comma-separated list too)
        try {
          MailApp.sendEmail(emails.join(','), subject, body);
          alertSentText = `Alert Sent @ ${nowIso}`;
          // write alert info into the Alert Sent column (4th column)
          dataSheet.getRange(lastRow, 4).setValue(alertSentText);
        } catch (mailErr) {
          // email failed - log error into sheet in Alert Sent column
          const errText = `Alert FAILED @ ${nowIso} -- ${mailErr.toString()}`;
          dataSheet.getRange(lastRow, 4).setValue(errText);
          // return success with warning about email failure
          return jsonResponse({ status: 'ok', message: 'row appended, email failed', error: mailErr.toString() }, 200);
        }
      } else {
        // no emails found - put note in Alert Sent
        const note = `Alert requested but no email addresses found @ ${nowIso}`;
        dataSheet.getRange(lastRow, 4).setValue(note);
      }
    }

    // Successful append (and maybe alert)
    return jsonResponse({ status: 'ok', row: lastRow, alert: alertSentText || null, config: config }, 200);

  } catch (err) {
    return jsonResponse({ error: 'Unhandled exception', details: err.toString() }, 500);
  }
}

// GET handler - returns config as JSON
function doGet(e) {
  try {
    const ss = SpreadsheetApp.getActiveSpreadsheet();
    const configSheet = ss.getSheetByName(TAB_CONFIG);
    if (!configSheet) {
      return jsonResponse({ error: 'ConfigDetails sheet not found' }, 500);
    }
    const config = readConfig(configSheet);
    // ensure numeric where appropriate
    if (config.POST_INTERVAL_MS) config.POST_INTERVAL_MS = Number(config.POST_INTERVAL_MS);
    if (config.THRESHOLD_VALUE) config.THRESHOLD_VALUE = Number(config.THRESHOLD_VALUE);
    return jsonResponse(config, 200);
  } catch (err) {
    return jsonResponse({ error: 'Unhandled exception', details: err.toString() }, 500);
  }
}

/* ================= Helper functions ================= */

function readConfig(sheet) {
  // Expecting two columns: key (A) | Value (B)
  // Read all data and build a simple key->value object
  const values = sheet.getDataRange().getValues();
  const config = {};
  for (let i = 1; i < values.length; i++) { // skip header row assumed at row 1
    const row = values[i];
    const key = (row[0] || '').toString().trim();
    const val = (row[1] !== undefined && row[1] !== null) ? row[1] : '';
    if (key) config[key] = val;
  }
  return config;
}

function readEmails(sheet) {
  // Read column A (skip header); filter empty and validate basic email format
  const col = sheet.getRange(2, 1, Math.max(0, sheet.getLastRow() - 1), 1).getValues();
  const emails = [];
  for (let i = 0; i < col.length; i++) {
    const v = (col[i][0] || '').toString().trim();
    if (v) {
      // Allow comma-separated addresses in single cell too; split and trim
      const parts = v.split(',');
      parts.forEach(p => {
        const e = p.trim();
        if (e && e.indexOf('@') > -1) emails.push(e);
      });
    }
  }
  // dedupe simple
  return Array.from(new Set(emails));
}

function jsonResponse(obj, statusCode) {
  // Apps Script ContentService doesn't allow setting HTTP status codes directly to callers,
  // but when deployed as Web App it will return 200 unless an exception is thrown.
  // We'll return a JSON wrapper with a code field so the device can inspect success/failure.
  const wrapper = Object.assign({}, obj, { _code: statusCode || 200 });
  const json = JSON.stringify(wrapper);
  return ContentService.createTextOutput(json).setMimeType(ContentService.MimeType.JSON);
}

 Click on Deploy
image

Select New Deployment> in description type any thing you want >in Who has access select anyone >Click Deploy  > from there a web URL will be created. Copy it and save for next part of arduino mkr 1010 Programming.
In next Blog we will discuss the hardware part.

  • Sign in to reply
  • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube