/      日本語

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.");
    }
}