This is the multi-page printable view of this section. Click here to print...

Return to the regular view of this page

As of 2025-07-24

Using Google Sheets

Latest Edition
    This is an explanation of the sample sketch spot-google-sheets, which acts as a Wi-Fi client and uploads data received from TWELITE ARIA to Google Sheets in the cloud. This sketch uses FreeRTOS functions from the ESP32 Arduino environment.

    Getting the Source Code

    You can obtain it from GitHub (monowireless/spot-google-sheets).

    System Overview

    TWELITE SPOT automatically creates a spreadsheet using a pre-created service account and shares the file with a specified user account.

    By logging into the user account, the user can view and edit the spreadsheet created by TWELITE SPOT from the “Shared with me” page in Google Drive.

    Image of the created spreadsheet

    Image of the created spreadsheet

    TWELITE SPOT continuously adds data rows to the created spreadsheet.

    Requirements for Development

    Environment Setup

    Installing the IDE and Toolchain

    See How to set up the development environment using Arduino IDE 1.x.

    Installing Libraries

    ESP-Google-Sheet-Client Library

    Open the Library Manager and search for esp-google-sheet to install it. You can also obtain it from GitHub (mobizt/ESP-Google-Sheet-Client).

    Official NTP Library

    Open the Library Manager and search for ntpclient to install it.

    TimeLib Library

    Open the Library Manager and search for timelib to install it.

    Preliminary Setup: API Configuration

    Before using the API, you need to set up your environment. A Google account is required.

    The following steps will be performed here:

    • Create a Google Cloud project
    • Enable the Google Sheets API
    • Enable the Google Drive API
    • Create and configure a service account
    • Obtain authentication credentials for the service account

    Creating the Project

    To use the API, first create a Google Cloud project.

    A Google Cloud project encompasses the entire system. It’s recommended to name the project after the system you’re building. Here, we will use SPOT-DEV as an example.

    Visit the following link and create a project.

    https://console.cloud.google.com/projectcreate

    Example screen for creating a project (personal)

    Example screen for creating a project (personal)

    Enabling the Sheets API

    To operate spreadsheets from TWELITE SPOT, enable the Sheets API.

    Visit the following link and enable the API.

    https://console.cloud.google.com/apis/library/sheets.googleapis.com

    Example screen for enabling the Sheets API

    Example screen for enabling the Sheets API

    Enabling the Drive API

    To share spreadsheets from TWELITE SPOT, enable the Drive API.

    Visit the following link and enable the API.

    https://console.cloud.google.com/apis/library/drive.googleapis.com

    Example screen for enabling the Drive API

    Example screen for enabling the Drive API

    Creating and Configuring the Service Account

    To create spreadsheets from TWELITE SPOT, you need to create a service account.

    Visit the following link, select your project (here, SPOT-DEV), and display the list of service accounts. Then, use the button at the top of the page to begin creating a service account.

    https://console.cloud.google.com/iam-admin/serviceaccounts

    Example screen showing the list of service accounts

    Example screen showing the list of service accounts

    In “โ‘  Service account details”, enter the name of the service account.

    In the example below, the name is set as spot-dev-sa.

    Example screen for entering the service account name

    Example screen for entering the service account name

    After entering the name, click the “Create and continue” button to proceed.

    In “โ‘ก Grant this service account access to the project (optional)”, configure the permissions for the service account.

    Here, select “Owner” as shown in the example below.

    Example screen for entering service account permissions

    Example screen for entering service account permissions

    After selecting the role, click the “Continue” button to proceed.

    In “โ‘ข Grant users access to this service account (optional)”, do nothing and click “Done” to skip.

    Example screen to be skipped

    Example screen to be skipped

    Once the service account is created, you will return to the service account list. Verify that the newly created service account appears.

    Obtaining Service Account Credentials

    Once you have confirmed the created service account, click the link in the “Email” column to open the service account details page.

    Example screen after creating a service account

    Example screen after creating a service account

    Select the “Keys” tab at the top to navigate to the screen for managing the private key required for service account authentication.

    Example screen of the service account details page

    Example screen of the service account details page

    Click the “Add Key” button and select “Create new key” to begin creating a private key.

    Example of the key creation button

    Example of the key creation button

    On the next screen, leave “JSON” selected and click the “Create” button.

    Example screen for selecting the key type

    Example screen for selecting the key type

    Clicking the “Create” button will automatically download the private key file (.json).

    When you open the private key file in a text editor, it should look like the following.

    {
      "type": "service_account",
      "project_id": "???",
      "private_key_id": "???",
      "private_key": "-----BEGIN PRIVATE KEY-----\n???\n-----END PRIVATE KEY-----\n",
      "client_email": "???@???.iam.gserviceaccount.com",
      "client_id": "???",
      "auth_uri": "https://accounts.google.com/o/oauth2/auth",
      "token_uri": "https://oauth2.googleapis.com/token",
      "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
      "client_x509_cert_url": "???",
      "universe_domain": "googleapis.com"
    }

    Among the above, the values of project_id, private_key, and client_email will be used during the operation check.

    Operation Check

    Let’s start by checking the operation.

    Obtaining the Project Files

    1. Download the Zip file from GitHub (monowireless/spot-google-sheets)
    2. Extract the Zip file and rename the folder from spot-google-sheets-main to spot-google-sheets
    3. Place the spot-google-sheets folder in your Arduino sketchbook location (as specified in the Arduino IDE settings, e.g., C:\Users\foo\Documents\Arduino)

    Editing the Sketch Configuration File

    Open the Arduino sketch spot-google-sheets.ino, select the config.h tab at the top of the screen, and modify the values from lines 4 to 11.

    Lines 4โ€“5 are Wi-Fi related settings.

    const char* WIFI_SSID = "YOUR SSID";            // Modify it
    const char* WIFI_PASSWORD = "YOUR PASSWORD";    // Modify it
    

    These specify the SSID and password.

    On the other hand, lines 8โ€“11 are spreadsheet-related settings.

    const char* PROJECT_ID = "YOUR-PROJECT-ID";                                                                         // Modify it
    const char* SERVICE_ACCOUNT_EMAIL = "YOUR-SERVICE-ACCOUNT@YOUR-PROJECT-ID.iam.gserviceaccount.com";                 // Modify it
    const char PRIVATE_KEY[] PROGMEM = "-----BEGIN PRIVATE KEY-----\nYOUR-PRIVATE-KEY\n-----END PRIVATE KEY-----\n";    // Modify it
    const char* USER_ACCOUNT_EMAIL = "YOUR-ACCOUNT@EMAIL";                                                              // Modify it
    

    For the first three items, copy the values from the .json file, and for USER_ACCOUNT_EMAIL, enter your logged-in Google account email address.

    Uploading the Sketch

    Refer to How to upload a sketch to ESP32 to upload the sketch.

    Starting the Parent and Child Devices

    Press the reset button on TWELITE SPOT (ESP32 side).

    If you see the following output in the Arduino serial console, the startup was successful.

    Initializing queue...
    Completed.
    Started TWELITE.
    Connecting to WiFi ...!...
    Connected. IP: xxx.xxx.xxx.xxx
    Initializing NTP...Completed. UNIX time: xxxxxxxxxx
    Initializing sheets...
    Creating sheets...
    OAuth2.0 access token on initializing
    OAuth2.0 access token on signing
    OAuth2.0 access token on exchange request
    OAuth2.0 access token ready
    Requesting to create...
    Succeeded.
    Adding headers for ARIA...
    Requesting to add header...
    Succeeded.
    Formatting the sheet for ARIA...
    Requesting to format...
    Succeeded.
    Extending the sheet for ARIA...
    Requesting to extend...
    Succeeded.
    Completed.

    Insert the coin battery into TWELITE ARIA (with default settings) and power it on.

    Inserting the coin battery

    Inserting the coin battery

    When TWELITE SPOT successfully receives packets from TWELITE ARIA and adds the data rows, you will see the following output.

    Got a new packet from ARIA.
    Got a new packet from ARIA.
    Requesting to add data...
    Got a new packet from ARIA.
    Succeeded.

    Incidentally, in the example above, packets are being received during a request โ€” this indicates successful multitasking (see Task Registration).

    Accessing Google

    Access Shared with me on Google Drive and open the spreadsheet named SPOT Sheet (xxx).

    You should see a screen similar to the one below.

    Example of the spreadsheet screen

    Example of the spreadsheet screen

    Scroll down to find the data received from TWELITE ARIA.

    Sketch Explanation

    This section explains the Arduino sketch spot-google-sheets.ino.

    Including Libraries

    Official Arduino and ESP32 Libraries

    Lines 4-7 include the official Arduino and ESP32 libraries.

    #include <Arduino.h>
    #include <NTPClient.h>
    #include <WiFi.h>
    #include <WiFiUdp.h>
    Header FileDescriptionRemarks
    Arduino.hBasic Arduino library
    NTPClient.hUse NTPUsed for file name and receive time
    WiFi.hUse ESP32 WiFi
    WiFiUdp.hUse UDPRequired for NTPClient

    Third-Party Libraries

    Lines 10-11 include third-party libraries.

    #include <ESP_Google_Sheet_Client.h>
    #include <TimeLib.h>
    Header FileDescriptionRemarks
    ESP_Google_Sheet_Client.hAccess Google
    TimeLib.hFormat UNIX time

    MWings Library

    Line 14 includes the MWings library.

    #include <MWings.h>

    User Configuration Definitions

    Line 17 includes config.h.

    #include "config.h"

    Wi-Fi Configuration Definition

    Lines 4-5 of config.h define the Wi-Fi settings applied to the ESP32 on TWELITE SPOT.

    const char* WIFI_SSID = "YOUR SSID";            // Modify it
    const char* WIFI_PASSWORD = "YOUR PASSWORD";    // Modify it
    
    NameDescription
    WIFI_SSIDSSID of the network to connect to
    WIFI_PASSWORDPassword of the network to connect to

    API Configuration Definition

    Lines 8-11 of config.h define the API settings.

    const char* PROJECT_ID = "YOUR-PROJECT-ID";                                                                         // Modify it
    const char* SERVICE_ACCOUNT_EMAIL = "YOUR-SERVICE-ACCOUNT@YOUR-PROJECT-ID.iam.gserviceaccount.com";                 // Modify it
    const char PRIVATE_KEY[] PROGMEM = "-----BEGIN PRIVATE KEY-----\nYOUR-PRIVATE-KEY\n-----END PRIVATE KEY-----\n";    // Modify it
    const char* USER_ACCOUNT_EMAIL = "YOUR-ACCOUNT@EMAIL";                                                              // Modify it
    
    NameDescription
    PROJECT_IDProject ID
    SERVICE_ACCOUNT_EMAILEmail address of the service account
    PRIVATE_KEYContent of the private key
    USER_ACCOUNT_EMAILEmail address of the user account to share the spreadsheet with

    Definition of Pin Numbers

    Lines 20-24 define the pin numbers.

    const uint8_t TWE_RST = 5;
    const uint8_t TWE_PRG = 4;
    const uint8_t LED = 18;
    const uint8_t ESP_RXD1 = 16;
    const uint8_t ESP_TXD1 = 17;
    NameDescription
    TWE_RSTPin number connected to the RST pin of TWELITE
    TWE_PRGPin number connected to the PRG pin of TWELITE
    LEDPin number connected to the ESP32 LED on the board
    ESP_RXD1Pin number connected to the TX pin of TWELITE
    ESP_TXD1Pin number connected to the RX pin of TWELITE

    Definition of TWELITE Settings

    Lines 27-30 define the settings applied to the TWELITE parent device mounted on TWELITE SPOT.

    const uint8_t TWE_CH = 18;
    const uint32_t TWE_APPID = 0x67720102;
    const uint8_t TWE_RETRY = 2;
    const uint8_t TWE_POWER = 3;
    NameDescription
    TWE_CHTWELITE frequency channel
    TWE_APPIDTWELITE application ID
    TWE_RETRYTWELITE retransmission count (when transmitting)
    TWE_POWERTWELITE transmission output

    Lines 32-43 define information related to the sheet.

    const char* SPREADSHEET_TITLE_PREFIX = "SPOT Sheet";
    const char* SPREADSHEET_LOCALE = "ja_JP";
    const char* SPREADSHEET_TIME_ZONE = "Asia/Tokyo";
    
    const int MIN_REQUEST_INTERVAL = 1000;    // 60 requests per minute
    
    const int SHEETS_DEFAULT_ROWS = 1000;            // Default length is 1000 rows
    const int SHEETS_ROWS = 100000;                  // Max 1,000,000 rows at 10 columns
    
    const int ARIA_SHEET_ID = 1;
    const char* ARIA_SHEET_TITLE = "ARIA";
    constexpr int ARIA_BUFFER_PACKETS = 32;    // Maximum number of rows per addition request
    
    NameDescription
    SPREADSHEET_TITLE_PREFIXFixed part of the spreadsheet file name
    SPREADSHEET_LOCALESpreadsheet locale
    SPREADSHEET_TIME_ZONESpreadsheet time zone
    MIN_REQUEST_INTERVALMinimum interval between requests
    SHEETS_DEFAULT_ROWSDefault number of rows per sheet
    SHEETS_ROWSNumber of rows per sheet
    ARIA_SHEET_IDSheet ID for ARIA
    ARIA_SHEET_TITLESheet name for ARIA
    ARIA_BUFFER_PACKETSQueue length for storing packets from ARIA

    Type Declarations

    Lines 46-50 declare types.

    struct ParsedAppAriaPacketWithTime {
        ParsedAppAriaPacket packet;
        uint32_t elapsedMillis;
        uint32_t unixTime;
    };
    NameDescription
    ParsedAppAriaPacketWithTimeType for storing received packet data together with the reception time in the queue
    • elapsedMillis: Elapsed time since startup at the time the packet was received (milliseconds)
    • unixTime: UNIX time (seconds) when the packet was received

    Declaration of Global Objects

    Lines 53-61 declare global objects.

    WiFiUDP ntpUDP;
    NTPClient timeClient(ntpUDP, "ntp.nict.jp", 32400);
    
    String spreadsheetIdString;    // Identifier of newly created file
    bool readyForNewRequests = false;
    uint32_t lastTimeRequestWasSent = UINT32_MAX;
    
    QueueHandle_t ariaPacketQueue;       // Store received data from ARIA
    uint32_t rowToAddNewAriaData = 2;    // Starting with the Row 2
    
    NameDescription
    ntpUDPUDP interface for NTP
    timeClientNTP interface
    spreadsheetIdStringID of the created spreadsheet
    readyForNewRequestsBecomes true when ready to send new requests
    lastTimeRequestWasSentTime when the last request was sent
    ariaPacketQueueQueue to store packets and reception times received from ARIA
    rowToAddNewAriaDataRow number to add the next data received from ARIA

    Function Prototype Declarations

    Lines 64-71 declare function prototypes.

    void anotherLoop();
    
    void waitUntilNewRequestsReady();
    String createSpreadsheet();
    bool formatSheet(const String spreadsheetId, const int sheetId);
    bool extendSheet(const String spreadsheetId, const int sheetId, const int rows);
    bool addSheetAriaHeaderRow(const String spreadsheetId, const char* const sheetTitle);
    bool addSheetsDataRow(const String spreadsheetId);
    NameDescription
    anotherLoop()Another loop() for asynchronously processing TWELITE
    waitUntilNewRequestsReady()Wait until the next request can be sent
    createSpreadsheet()Create a new spreadsheet
    formatSheet()Format the specified sheet
    extendSheet()Increase the number of rows in the specified sheet and format it
    addSheetAriaHeaderRow()Add a header row for ARIA to the specified sheet
    addSheetsDataRow()Add a data row to the sheet

    Queue Initialization

    Lines 82-83 initialize the queue for storing received packet data along with the reception time.

    ariaPacketQueue = xQueueCreate(ARIA_BUFFER_PACKETS, sizeof(ParsedAppAriaPacketWithTime));
    if (ariaPacketQueue == 0) { Serial.println("Failed to init a queue."); }

    xQueueCreate() is a FreeRTOS function running inside the ESP32. It allows you to easily create queues that support multitasking.

    TWELITE Configuration

    Lines 88-92 call Twelite.begin() to configure and start the TWELITE parent device mounted on TWELITE SPOT.

    Serial2.begin(115200, SERIAL_8N1, ESP_RXD1, ESP_TXD1);
        if (Twelite.begin(Serial2,
                          LED, TWE_RST, TWE_PRG,
                          TWE_CH, TWE_APPID, TWE_RETRY, TWE_POWER)) {
            Serial.println("Started TWELITE.");
        }
    ArgumentTypeDescription
    Serial2HardwareSerial&Serial port used for communication with TWELITE
    LEDintPin number connected to the status LED
    TWE_RSTintPin number connected to the RST pin of TWELITE
    TWE_PRGintPin number connected to the PRG pin of TWELITE
    TWE_CHANNELuint8_tTWELITE frequency channel
    TWE_APP_IDuint32_tTWELITE application ID
    TWE_RETRYuint8_tTWELITE retransmission count (when transmitting)
    TWE_POWERuint8_tTWELITE transmission output

    Event Handler Registration

    Lines 94-103 register the process to be executed when a packet is received from TWELITE ARIA.

    Twelite.on([](const ParsedAppAriaPacket& packet) {
        Serial.println("Got a new packet from ARIA.");
        ParsedAppAriaPacketWithTime packetWithTime;
        packetWithTime.elapsedMillis = millis();
        packetWithTime.unixTime = timeClient.getEpochTime();
        packetWithTime.packet = packet;
        if (not(xQueueSend(ariaPacketQueue, &packetWithTime, 0) == pdPASS)) {
            Serial.println("Failed to add packet data to the queue.");
        }
    });

    Here, xQueueSend() is used to store the received packet data along with the reception time at the end of the queue.

    Wi-Fi Settings

    Lines 106-120 perform the Wi-Fi settings.

    WiFi.mode(WIFI_STA);
    WiFi.setAutoReconnect(true);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    Serial.print("Connecting to WiFi ..");
    while (WiFi.status() != WL_CONNECTED) {
        static int count = 0;
        Serial.print('.');
        delay(500);
        // Retry every 5 seconds
        if (count++ % 10 == 0) {
            WiFi.disconnect();
            WiFi.reconnect();
            Serial.print('!');
        }
    }

    Here, it is set as a Wi-Fi station and connects to the specified network.

    NTP Settings

    Lines 126-127 perform the NTP settings.

    timeClient.begin();
    timeClient.update();

    Google Spreadsheet Settings

    Lines 132-145 configure the Google Spreadsheet.

    GSheet.setTokenCallback([](TokenInfo info) {
        // Print token initialization states
        if (info.status == esp_signer_token_status_error) {
            Serial.print("Token error ");
            Serial.println(GSheet.getTokenError(info));
        }
        Serial.print(GSheet.getTokenType(info));
        Serial.print(" ");
        Serial.println(GSheet.getTokenStatus(info));
    });
    GSheet.setPrerefreshSeconds(60);    // Set refresh rate for auth token
    
    Serial.println("Initializing sheets...");
    GSheet.begin(SERVICE_ACCOUNT_EMAIL, PROJECT_ID, PRIVATE_KEY);

    In lines 132-141, it registers processing to display the status when obtaining a service account token, sets the token refresh interval in line 142, and initializes the service account in line 145.

    Additionally, lines 147-173 create the spreadsheet, add the header row for ARIA, format the cells, and extend the number of rows.

    Serial.println("Creating sheets...");
    waitUntilNewRequestsReady();    // Wait for token
    spreadsheetIdString = createSpreadsheet();
    if (not(spreadsheetIdString.length() > 0)) {
        Serial.println("Failed to create sheets.");
    }
    
    Serial.println("Adding headers for ARIA...");
    delay(MIN_REQUEST_INTERVAL);
    waitUntilNewRequestsReady();
    if (not addSheetAriaHeaderRow(spreadsheetIdString, ARIA_SHEET_TITLE)) {
        Serial.println("Failed to add headers.");
    }
    
    Serial.println("Formatting the sheet for ARIA...");
    delay(MIN_REQUEST_INTERVAL);
    waitUntilNewRequestsReady();
    if (not formatSheet(spreadsheetIdString, ARIA_SHEET_ID)) {
        Serial.println("Failed to format.");
    }
    
    Serial.println("Extending the sheet for ARIA...");
    delay(MIN_REQUEST_INTERVAL);
    waitUntilNewRequestsReady();
    if (not extendSheet(spreadsheetIdString, ARIA_SHEET_ID, SHEETS_ROWS - SHEETS_DEFAULT_ROWS)) {
        Serial.println("Failed to extend.");
    }

    Task Registration

    Lines 179-186 register a task to update TWELITE data asynchronously.

    xTaskCreatePinnedToCore(
        [](void* params) {
            while (true) {
                anotherLoop();
                vTaskDelay(1);
            }
        },
        "Task for anotherLoop()", 8192, nullptr, 18, nullptr, 0);

    xTaskCreatePinnedToCore() is a function provided by the FreeRTOS multitasking framework.

    Here, a lambda function (lines 180โ€“185) is passed to create a task that calls anotherLoop() repeatedly.

    [](void* params) {
        while (true) {
            anotherLoop();
            vTaskDelay(1);    // IMPORTANT for Watchdog
        }
    },

    The task is named Task for anotherLoop(), its stack size is 8192, it takes no parameters, its priority is 18 (the higher the number, the higher the priority; Wi-Fi-related processing is at 19), there is no interface to manipulate the task, and it runs on the same CPU core as Wi-Fi and other RF processing, Core 0 (loop() runs on Core 1).

    "Task for anotherLoop()", 8192, nullptr, 18, nullptr, 0);    // Priority is 18 (lower than WiFi)
    

    Updating TWELITE Data

    At line 203, Twelite.update() is called within anotherLoop().

    Twelite.update();

    Twelite.update() reads out the packet data (ModBus ASCII format) sent from the TWELITE parent device, one byte at a time.

    Updating the Spreadsheet

    Lines 192โ€“195 call the spreadsheet update process.

    if (millis() - lastTimeRequestWasSent > MIN_REQUEST_INTERVAL) {
        // Add any available data
        addSheetsDataRow(spreadsheetIdString);
    }

    The if statement on line 192 ensures that at least 1 second has passed since the last request was sent, in order to comply with the API limit of 60 requests per minute (similar to “throttle” in JavaScript’s Throttle/Debounce).

    Line 194 adds a new data row to the spreadsheet as needed.

    addSheetsDataRow(spreadsheetIdString);

    Updating the API Library

    Line 196 updates the Google API library and checks whether new requests can be sent.

    readyForNewRequests = GSheet.ready();

    Updating the NTP Library

    Line 197 updates the NTP library.

    timeClient.update();

    Spreadsheet Operations

    Starting from line 217, operations on the spreadsheet are performed using the Sheets API.

    For details, see the API reference for the library or Sheets API REST resources.

    Google

    Arduino

    ESP32

    Community

    Libraries

    Plugins