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

Graph Display with ThingSpeak

Latest version
    This is an explanation of the sample sketch spot-thingspeak, which acts as a Wi-Fi client and uses MathWorks’ service ThingSpeak to graph temperature and humidity data.

    Getting the Source Code

    You can obtain it from the GitHub repository monowireless/spot-thingspeak.

    System Overview

    The spot-thingspeak sample receives data from TWELITE ARIA and sends it as an HTTP GET request to the ThingSpeak server, making it possible to display the data as a graph.

    Display Example

    Display Example

    What You Need for Development

    Environment Setup

    ThingSpeak Setup

    1. Access ThingSpeak’s website and create a MathWorks account.
    2. Create a “Channel” and set it as shown below.
    Example Channel Settings

    Example Channel Settings

    1. On the “API Keys” tab, make a note of the 16-character “Write API key”.

    Installing the IDE and Toolchain

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

    Obtaining the Project Files

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

    Changing User Settings

    Open config.h from the tab at the top of the Arduino IDE and set the Wi-Fi SSID and password. WPA2-PSK networks are assumed.

    Also, set the 16-character “Write API key” you noted earlier. If necessary, rewrite the root certificate as well.

    Writing the Project Files

    See How to upload sketches to ESP32.

    Setting up and Starting TWELITE ARIA

    1. Change the settings for TWELITE ARIA and set the Transmission Interval in TWELITE ARIA Mode to 30 seconds or more (e.g., 60 seconds) to avoid overloading the server.
    2. Insert a CR2032 battery and start TWELITE ARIA.

    Sketch

    Explanation of the Arduino sketch spot-thingspeak.ino and config.h.

    Including Libraries

    Official Arduino and ESP32 Libraries

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

    #include <Arduino.h>
    #include <WiFiClientSecure.h>
    #include <WiFi.h>
    Header FileDescriptionNotes
    Arduino.hBasic Arduino librarySometimes can be omitted, but included just in case
    WiFiClientSecure.hSSL communication on ESP32
    WiFi.hHandles Wi-FiSometimes can be omitted, but included just in case

    MWings Library

    Line 13 includes the MWings library.

    #include <MWings.h>

    Definition of User Settings

    Line 16 includes config.h.

    #include "config.h"

    Definition of Data Types

    Lines 19-26 define a struct type to store data received from the child device.

    struct DataFromAria {
        uint32_t serialId;
        uint8_t logicalId;
        uint16_t supplyVoltage;
        uint8_t linkQuality;
        int16_t temp100x;
        uint16_t humid100x;
    };
    NameDescription
    serialIdSerial ID
    logicalIdLogical Device ID
    supplyVoltageSupply Voltage
    linkQualityLQI
    temp100xTemperature multiplied by 100
    humid100xHumidity multiplied by 100

    Here, TWELITE ARIA is used.

    config.h

    Definition of Reboot Interval

    Line 4 of config.h specifies the reboot interval for the ESP32.

    const uint32_t REBOOT_INTERVAL = 21600; // seconds
    

    Here, 21600 seconds = 6 hours.

    Definition of TWELITE Settings

    Lines 7-8 of config.h 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;
    NameDescription
    TWE_CHTWELITE frequency channel
    TWE_APPIDTWELITE application ID

    Definition of Wi-Fi Settings

    Lines 11-12 of config.h define the Wi-Fi settings applied to the ESP32 mounted on TWELITE SPOT.

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

    ThingSpeak Write API key

    Line 15 of config.h defines the “Write API key” required to add data to a “Field” in a ThingSpeak “Channel”.

    Root Certificate

    Lines 18-41 of config.h contain the root certificate obtained from api.thingspeak.com using Google Chrome. Rewrite as necessary.

    const char *CA_CERT = R"(
    -----BEGIN CERTIFICATE-----
    ...
    -----END CERTIFICATE-----
    )";

    Host Settings

    Lines 43-44 of config.h define the host settings.

    const char *SERVER_HOST = "api.thingspeak.com";
    const uint16_t SERVER_PORT = 443;
    NameDescription
    SERVER_HOSTServer host name
    SERVER_PORTServer port number

    Definition of Various Constants

    From line 46 in config.h, various constants are defined.

    const int QUERIES_MAX_LENGTH = 128;         // bytes (without \0)
    const int32_t CONNECT_TIMEOUT = 10;     // seconds
    const uint32_t RECONNECT_MIN_INTERVAL = 5; // seconds
    // According to thingspeck free limitations, SEND_MIN_INTERVAL is set for 20s.
    const uint32_t SEND_MIN_INTERVAL = 20; // seconds
    const uint32_t REQUEST_TIMEOUT = 10;   // seconds
    
    NameDescription
    QUERIES_MAX_LENGTHMaximum length of the query string (excluding null terminator)
    CONNECT_TIMEOUTTimeout when connecting to the server
    RECONNECT_MIN_INTERVALMinimum interval for reconnecting to Wi-Fi access point
    SEND_MIN_INTERVALMinimum interval between requests
    REQUEST_TIMEOUTTimeout from request to response

    Definition of Pin Numbers

    Lines 29-34 of spot-thingspeak.ino define the pin numbers.

    static const int RST_PIN = 5;
    static const int PRG_PIN = 4;
    static const int LED_PIN = 18;
    
    static const int8_t RX1_PIN = 16;
    static const int8_t TX1_PIN = 17;
    NameDescription
    RST_PINPin number connected to TWELITE’s RST pin
    PRG_PINPin number connected to TWELITE’s PRG pin
    LED_PINPin number connected to the ESP32 LED on the board
    RX1_PINPin number connected to TWELITE’s RX1 pin
    TX1_PINPin number connected to TWELITE’s TX1 pin

    Declaration of Global Objects

    Line 37 declares a global object.

    static WiFiClientSecure client;
    NameDescription
    clientInterface for HTTPS communication

    Declaration of Global Variables

    Lines 40-41 declare global variables.

    static DataFromAria LatestDataFromAria;
    static bool IsThereNewDataFromAria;
    NameDescription
    LatestDataFromAriaLatest data received from TWELITE ARIA
    IsThereNewDataFromAriaFlag indicating new data received from TWELITE ARIA

    Declaration of Function Prototypes

    Lines 44-54 declare function prototypes.

    void anotherLoopForTWELITE();
    NameDescription
    anotherLoopForTWELITELoop function for processing TWELITE data
    void initTWELITE();
    void initWiFi();
    NameDescription
    initTWELITETWELITE initialization function
    initWiFiWi-Fi initialization function
    void onAppAriaPacket(const ParsedAppAriaPacket& packet);
    NameDescription
    onAppAriaPacketCallback function when data is received from TWELITE ARIA
    void sendAriaData(const DataFromAria& data)
    NameDescription
    sendAriaDataFunction to send TWELITE ARIA data via HTTP GET request

    setup()

    Lines 57-74 perform overall initialization.

    void setup() {
        Serial.begin(115200);
    
        initTWELITE();
        initWiFi();
    
        // Attach another loop function for TWELITE
        // Note: Core 0 is also used for the WiFi task, which priority is 19 (ESP_TASKD_EVENT_PRIO - 1)
        xTaskCreatePinnedToCore(
            [](void *params) {
                while (true) {
                    anotherLoopForTWELITE();
                    vTaskDelay(1); // IMPORTANT for Watchdog
                }
            },
            "Task for anotherLoopForTWELITE()", 8192, nullptr, 18, nullptr,
            0); // Priority is 18 (lower than WiFi)
    }

    xTaskCreatePinnedToCore() is used to register a task separate from loop().

    The following part is an anonymous function without a capture. This avoids unnecessary pollution of the global namespace.

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

    loop()

    Lines 77-99 are the main loop process.

    It handles HTTP requests, Wi-Fi reconnection if disconnected, and periodic resets.

    void loop() {
        static uint32_t lastTimeReconnected = 0;
        if (WiFi.status() == WL_CONNECTED) {
            // Regular operations
            // Check for new data
            if (IsThereNewDataFromAria) {
                IsThereNewDataFromAria = false; // Clear first; data is updated on another thread
                DataFromAria data = LatestDataFromAria; // Now, the buffer is open for incoming data
                sendAriaData(data);
            }
        } else if (millis() - lastTimeReconnected > RECONNECT_MIN_INTERVAL * 1000) {
            // Lost connection, reconnect periodically
            Serial.println("Disconnected. Reconnecting to WiFi...");
            WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
            lastTimeReconnected = millis();
        }
    
        // Reboot every x interval
        if (millis() > REBOOT_INTERVAL * 1000) {
            Serial.println("Rebooting...");
            ESP.restart();
        }
    }

    anotherLoopForTWELITE()

    Lines 102-104 are the loop process for TWELITE.

    To sequentially receive and interpret data, this is handled in a separate task from the blocking loop().

    void anotherLoopForTWELITE() {
        Twelite.update();
    }

    initTWELITE()

    Lines 107-114 handle initialization for TWELITE.

    The TWELITE mounted on TWELITE SPOT is started with the specified settings, and a callback function is registered for packet reception.

    void initTWELITE() {
        Serial2.begin(115200);
        if (Twelite.begin(Serial2, LED_PIN, RST_PIN, PRG_PIN, TWE_CHANNEL, TWE_APP_ID)) {
            Serial.println("Started TWELITE.");
        }
        // Attach event handlers to process packets
        Twelite.on(onAppAriaPacket);
    }

    initWiFi()

    Lines 117-144 handle Wi-Fi initialization.

    If connection fails, it will retry every 5 seconds.

    void initWiFi() {
        Serial.print("\nConnecting to the WiFi network ");
        Serial.print(WIFI_SSID);
        Serial.println("...");
    
        // Begin
        WiFi.mode(WIFI_STA);
        WiFi.setAutoReconnect(true);
        WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    
        // Wait for connection
        Serial.print("Connecting.");
        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('!');
            }
        }
        Serial.println("\nConnected!");
    
        // Set Root CA certificate
        client.setCACert(CA_CERT);
    }

    onAppAriaPacket()

    Lines 147-157 describe the processing when data is received from TWELITE ARIA.

    Here, HTTP transmission is not performed; instead, the data is set to a global variable. The data set in the global variable will be processed by sendAriaData() in a separate task.

    void onAppAriaPacket(const ParsedAppAriaPacket& packet)
    {
        // Store data
        LatestDataFromAria.serialId = packet.u32SourceSerialId;
        LatestDataFromAria.logicalId = packet.u8SourceLogicalId;
        LatestDataFromAria.supplyVoltage = packet.u16SupplyVoltage;
        LatestDataFromAria.linkQuality = packet.u8Lqi;
        LatestDataFromAria.temp100x = packet.i16Temp100x;
        LatestDataFromAria.humid100x = packet.u16Humid100x;
        IsThereNewDataFromAria = true;
    }

    sendAriaData()

    Lines 160-220 define a function that sets TWELITE ARIA data in the query string of an HTTP GET request and sends it.

    To prevent excessive load on the server, sending is skipped if packets arrive too frequently.

    void sendAriaData(const DataFromAria& data)
    {
        static uint32_t lastTimeRequested = 0;
        if (millis() - lastTimeRequested > SEND_MIN_INTERVAL * 1000 or lastTimeRequested == 0) {
            Serial.println("Connecting to the server...");
            if (not client.connect(SERVER_HOST, SERVER_PORT, CONNECT_TIMEOUT * 1000)) {
                Serial.println("Connection failed!");
            } else {
                Serial.println("Connected to the server!");
    
                // Make a query string for the Channel on the ThingSpeak
                char queries[QUERIES_MAX_LENGTH+1];
                snprintf(queries, sizeof(queries),
                         "api_key=%s&field1=%s&field2=%s&field3=%s&field4=%d",
                         // Write API key for the Channel
                         WRITE_API_KEY,
                         // Field 1: Temperature
                         String(data.temp100x / 100.0f, 2),
                         // Field 2: Humidity
                         String(data.humid100x / 100.0f, 2),
                         // Field 3: Supply Voltage
                         String(data.supplyVoltage / 1000.0f, 2),
                         // Field 4: Link Quality
                         data.linkQuality);
    
                // Send a request
                client.println(String("GET https://") +
                               SERVER_HOST +
                               String("/update?") +
                               queries +
                               String(" HTTP/1.1"));
                client.println("Accept: */*");
                client.println(String("Host: ") + SERVER_HOST);
                client.println("Connection: close");
                client.println();
                uint32_t timeSentRequest = millis();
    
                // Handle a response
                while (client.connected()) {
                    String line = client.readStringUntil('\n');
                    if (line == "\r") {
                        Serial.print("Index (if succeeded): ");
                        break;
                    }
                    if (millis() - timeSentRequest > REQUEST_TIMEOUT * 1000) {
                        Serial.println("Request was timed out");
                        break;
                    }
                }
                while (client.available()) {
                    char c = client.read();
                    Serial.write(c);
                }
                client.stop();
                Serial.println("");
            }
            lastTimeRequested = millis();
        } else {
            Serial.println("Requests are too frequently; skip.");
        }
    }