/      æ—¥æœ¬èªž

Using the REST API

mwings-v1.1.3
This is an explanation of the sample sketch spot-httpbin, which acts as a Wi-Fi child device and sends received packet data to the mock server httpbin.org.

Getting the Source Code

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

System Overview

spot-httpbin sends part of the data received by the TWELITE parent device along with the current time obtained via NTP as an HTTP GET request to a mock server, and displays the response on the serial monitor.

Requirements for Development

Environment Setup

Installing IDE and Toolchain

See Setting up the development environment using Arduino IDE 1.x.

Installing Libraries

This sample includes required libraries from the beginning.

Obtaining the Project Files

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

Modifying User Settings

Open config.h from the tabs at the top of the Arduino IDE and set your Wi-Fi SSID and password. WPA2-PSK network is assumed. Also, register the root certificate. The root certificate can be obtained from the security page for each website using Chrome or another browser.

Writing the Project File

See How to upload a sketch to the ESP32.

Sketch

This section explains the Arduino sketch spot-httpbin.ino and config.h.

Including Libraries

Official Arduino and ESP32 Libraries

Lines 4–6 include official Arduino and ESP32 libraries.

#include <Arduino.h>
#include <WiFiClientSecure.h>
#include <WiFiUdp.h>
Header FileDescriptionRemarks
Arduino.hBasic Arduino libraryCan sometimes be omitted, but included for safety
WiFiClientSecure.hEnables SSL communication on ESP32
WiFiUdp.hHandles UDP communicationRequired for NTP

Third-Party Libraries

Lines 9–10 include bundled third-party libraries.

#include "src/NTPClient/NTPClient.h"
#include "src/Time/TimeLib.h"
Header FileDescriptionRemarks
NTPClient.hAccesses NTP servers
TimeLib.hConverts epoch time

MWings Library

Line 13 includes the MWings library.

#include <MWings.h>

Defining User Settings

Line 16 includes the configuration file.

#include "config.h"

Definition of Data Types

Lines 19–26 define the struct type used 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 (Link Quality Index)
temp100xTemperature ×100
humid100xHumidity ×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 installed in TWELITE SPOT.

const uint8_t TWE_CH = 18;
const uint32_t TWE_APPID = 0x67720102;
NameDescription
TWE_CHTWELITE 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 installed in 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

Root Certificate

Lines 14–16 of config.h provide a template for describing the contents of the root certificate.

const char *CA_CERT =
    "-----BEGIN CERTIFICATE-----\n"
    "-----END CERTIFICATE-----\n";

Obtain the root certificate from the security screen for each website using Chrome or another web browser. All lines must be enclosed in double quotes, and a newline character \n must be added before the ending double quote.

Host Settings

Lines 18–19 of config.h define the host settings.

const char *SERVER_HOST = "www.httpbin.org";
const uint16_t SERVER_PORT = 443;
NameDescription
SERVER_HOSTServer host name
SERVER_PORTServer port number

Definition of Various Constants

From line 21 of config.h, various constants are defined.

const uint32_t NTP_UPDATE_INTERVAL = 10000; // ms

const int QUERIES_MAX_LENGTH = 128;         // bytes (without \0)
const int32_t CONNECT_TIMEOUT = 10;     // seconds
const uint32_t RECONNECT_MIN_INTERVAL = 5; // seconds
// SEND_MIN_INTERVAL must be longer than NTP_UPDATE_INTERVAL
const uint32_t SEND_MIN_INTERVAL = 10; // seconds
const uint32_t REQUEST_TIMEOUT = 10;   // seconds
NameDescription
NTP_UPDATE_INTERVALInterval for obtaining NTP time
QUERIES_MAX_LENGTHMaximum length of query string (excluding null character)
CONNECT_TIMEOUTTimeout when connecting to the server
RECONNECT_MIN_INTERVALMinimum interval when reconnecting to Wi-Fi access point
SEND_MIN_INTERVALMinimum interval between requests
REQUEST_TIMEOUTTimeout from request to response

Definition of Pin Numbers

Lines 29–31 define the pin numbers.

static const int RST_PIN = 5;
static const int PRG_PIN = 4;
static const int LED_PIN = 18;
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

Declaration of Global Objects

Lines 34–37 declare global objects.

static WiFiClientSecure client;
static WiFiUDP ntpUDP;
static NTPClient timeClient(ntpUDP, "ntp.nict.jp",
                            32400, NTP_UPDATE_INTERVAL); // JST(UTC+9)
NameDescription
clientInterface for HTTPS communication
ntpUDPInterface for UDP communication for NTP
timeClientInterface for NTP

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 has been received from TWELITE ARIA

Declaration of Function Prototypes

Lines 44–56 declare function prototypes.

void anotherLoopForTWELITE();
void anotherLoopForNTP();
NameDescription
anotherLoopForTWELITELoop function for processing TWELITE data
anotherLoopForNTPLoop function for obtaining time via NTP
void initTWELITE();
void initWiFi();
void initNTP();
NameDescription
initTWELITEFunction to initialize TWELITE
initWiFiFunction to initialize Wi-Fi
initNTPFunction to initialize NTP
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 59–87 perform overall initialization.

void setup() {
    Serial.begin(115200);

    initTWELITE();
    initWiFi();
    initNTP();

    // 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)
    // Attach another loop function for NTP
    xTaskCreatePinnedToCore(
        [](void *params) {
            while (true) {
                anotherLoopForNTP();
                vTaskDelay(1); // IMPORTANT for Watchdog
            }
        },
        "Task for anotherLoopForNTP()", 8192, nullptr, 17, nullptr,
        0); // Priority is 17 (lower than WiFi and TWELITE)
}

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

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

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

loop()

Lines 90–111 are the main loop process.

This handles HTTP requests, reconnection to Wi-Fi when 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 114–116 are the loop process for TWELITE.

To sequentially receive and interpret data, this is run as a separate task from the (potentially blocking) loop().

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

anotherLoopForNTP()

Lines 117–120 are the loop process for NTP.

Since this involves UDP communication, it is also run as a separate task from the (potentially blocking) loop().

void anotherLoopForNTP() {
    timeClient.update();
    setTime(timeClient.getEpochTime());
}

initTWELITE()

Lines 123–130 perform the initialization process for TWELITE.

This starts the TWELITE installed in TWELITE SPOT with the specified settings and registers a callback function 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 133–157 perform Wi-Fi initialization.

If not connected, reconnection is attempted 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) x
        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);
}

initNTP()

Lines 160–164 perform NTP initialization.

void initNTP() {
    timeClient.begin();
    timeClient.update();
    setTime(timeClient.getEpochTime());
}

onAppAriaPacket()

Lines 167–177 describe the process performed 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 is 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 180–237 define a function that sets TWELITE ARIA data into the query string of an HTTP GET request and sends it.

To avoid excessive load on the server, transmission 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
            char queries[QUERIES_MAX_LENGTH+1];
            snprintf(queries, sizeof(queries),
                     "datetime=%04d%02d%02d%02d%02d%02d&sid=%X&lid=%d&temp=%d&humid=%d&bat=%d&lqi=%d",
                     // Note that NTP_UPDATE_INTERVAL is set for 10000ms by default; second() delays up to 10s.
                     // To prevent duplication of datetime, SEND_MIN_INTERVAL is set for 10s.
                     year(), month(), day(), hour(), minute(), second(),
                     data.serialId,
                     data.logicalId,
                     data.temp100x,
                     data.humid100x,
                     data.supplyVoltage,
                     data.linkQuality);

            // Send a request
            client.println(String("GET https://") +
                           SERVER_HOST +
                           String("/get?") +
                           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.println("Headers received");
                    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();
        }
        lastTimeRequested = millis();
    } else {
        Serial.println("Requests are too frequently; skip.");
    }
}