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

Pre-installed Sketch

ESP32 Arduino Core v2.x.x Version
    This is an explanation of the sample sketch spot-server, which acts as a wireless LAN access point and displays data from child devices on a web page.

    Obtaining the Source Code

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

    System Overview

    spot-server consists of an Arduino sketch (.ino) that receives and relays data from TWELITE, and a web page (.html / .css / .js) delivered to smartphones.

    Image diagram

    Image diagram

    The data sent by TWELITE child devices is received by the Arduino sketch, which then fires events to the published web page. The published web page dynamically rewrites its HTML content in response to these events.

    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

    First, if there is no libraries folder in your Arduino sketchbook location (as specified in Arduino IDE preferences, e.g., C:\Users\foo\Documents\Arduino), create it.

    Asynchronous TCP Communication Library

    1. Download the Zip file from GitHub (me-no-dev/AsyncTCP).
    2. Extract the Zip file and rename the folder from AsyncTCP-master to AsyncTCP.
    3. Place the AsyncTCP folder into your libraries folder.

    Asynchronous Web Server Library

    1. Download the Zip file from GitHub (me-no-dev/ESPAsyncWebServer).
    2. Extract the Zip file and rename the folder from AsyncWebServer-master to AsyncWebServer.
    3. Place the AsyncWebServer folder into your libraries folder.

    OLED Display Library

    1. Download the Zip file from GitHub (Seeed-Studio/OLED_Display_96X96).
    2. Extract the Zip file and rename the folder from OLED_Display_96X96-master to OLED_Display_96X96.
    3. Place the OLED_Display_96X96 folder into your libraries folder.

    JSON Library

    Open the Library Manager and install Arduino_JSON.

    Installing Plugins

    File System Writing Plugin

    To write files such as HTML to the ESP32’s flash area, you need an Arduino plugin.

    Here, we use lorol/arduino-esp32fs-plugin: Arduino plugin for uploading files to ESP32 file system.

    For installation instructions, see TWELITE SPOT Manual: How to Write Files to ESP32.

    Downloading Project Files

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

    How to Write Project Files

    Sketch

    See How to write a sketch to ESP32.

    Web Page

    See How to write files to ESP32.

    Sketch

    This section explains the Arduino sketch spot-server.ino.

    Including Libraries

    Official Arduino and ESP32 Libraries

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

    #include <Arduino.h>
    #include <Arduino_JSON.h>
    #include <ESPmDNS.h>
    #include <LittleFS.h>
    #include <WiFi.h>
    #include <Wire.h>
    Header FileDescriptionRemarks
    Arduino.hBasic Arduino libraryMay be omitted, but included just in case
    Arduino_JSON.hHandles JSON stringsDifferent from ArduinoJson
    ESPmDNS.hUses mDNSRequired for using hostnames
    LittleFS.hHandles LittleFS file systemNeeded for serving pages
    WiFi.hUses ESP32 WiFi
    Wire.hUses I2CFor OLED display

    Third-party Libraries

    Lines 12-14 include third-party libraries.

    #include <AsyncTCP.h>
    #include <ESPAsyncWebServer.h>
    #include <SeeedGrayOLED.h>
    Header FileDescriptionRemarks
    AsyncTCP.hPerforms asynchronous TCP communication
    ESPAsyncWebServer.hRuns asynchronous web serverDepends on AsyncTCP
    SeeedGrayOLED.hUses OLED display

    MWings Library

    Line 17 includes the MWings library.

    #include <MWings.h>

    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 onboard LED
    ESP_RXD1Pin number connected to the TX pin of TWELITE
    ESP_TXD1Pin number connected to the RX pin of TWELITE

    TWELITE Settings Definition

    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 (on transmission)
    TWE_POWERTWELITE transmission output

    Wireless LAN Settings Definition

    Lines 33-38 define the wireless LAN settings applied to the ESP32 mounted on TWELITE SPOT.

    const char* WIFI_SSID_BASE = "TWELITE SPOT";
    const char* WIFI_PASSWORD = "twelitespot";
    const uint8_t WIFI_CH = 13;
    const IPAddress WIFI_IP = IPAddress(192, 168, 1, 1);
    const IPAddress WIFI_MASK = IPAddress(255, 255, 255, 0);
    const char* HOSTNAME = "spot";    // spot.local
    
    NameDescription
    WIFI_SSID_BASECommon part of the SSID string
    WIFI_PASSWORDPassword
    WIFI_CHESP32 frequency channel
    WIFI_IPIP address
    WIFI_MASKSubnet mask
    HOSTNAMEHost name

    Declaration of Global Objects

    Lines 41-42 declare global objects.

    AsyncWebServer server(80);
    AsyncEventSource events("/events");
    NameDescription
    serverInterface for asynchronous web server on port 80
    eventsInterface for server-sent events at /events ?

    Declaration of Function Prototypes

    Lines 45-49 declare function prototypes.

    uint16_t createUidFromMac();
    String createJsonFrom(const ParsedAppTwelitePacket& packet);
    String createJsonFrom(const ParsedAppAriaPacket& packet);
    String createJsonFrom(const ParsedAppCuePacket& packet);
    String createJsonFrom(const BarePacket& packet);
    NameDescription
    createUidFromMac()Creates an identifier for SSID from MAC address
    createJsonFrom()<ParsedAppTwelitePacket&>Creates a JSON string from App_Twelite packet data
    createJsonFrom()<ParsedAppAriaPacket&>Creates a JSON string from App_ARIA packet data
    createJsonFrom()<ParsedAppCuePacket&>Creates a JSON string from App_CUE packet data
    createJsonFrom()<BarePacket&>Creates a JSON string from all packet data

    TWELITE Settings

    Lines 58-63 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 status LED
    TWE_RSTintPin number connected to TWELITE RST pin
    TWE_PRGintPin number connected to TWELITE PRG pin
    TWE_CHANNELuint8_tTWELITE frequency channel
    TWE_APP_IDuint32_tTWELITE application ID
    TWE_RETRYuint8_tTWELITE retransmission count (on transmission)
    TWE_POWERuint8_tTWELITE transmission output

    App_Twelite: Registering Event Handler

    Lines 65-72 call Twelite.on() <ParsedAppTwelitePacket> to register the process to execute when a packet is received from a child device using the super-easy standard app.

    Twelite.on([](const ParsedAppTwelitePacket& packet) {
        Serial.println("Received a packet from App_Twelite");
        String jsonStr = createJsonFrom(packet);
        if (not(jsonStr.length() <= 0)) {
            events.send(jsonStr.c_str(), "data_app_twelite", millis());
        }
        events.send("parsed_app_twelite", "data_parsing_result", millis());
    });

    Creating JSON String

    Line 67 generates a JSON string from the received data.

    String jsonStr = createJsonFrom(packet);

    To display the received data on the web page, it is necessary to send the data to client-side JavaScript, and string data is easier to handle, so it is converted to a JSON string.

    Sending Events to the Web Page

    Lines 68-70 send the generated JSON string to the “Signal Viewer” page.

    if (not(jsonStr.length() <= 0)) {
        events.send(jsonStr.c_str(), "data_app_twelite", millis());
    }

    The event name is data_app_twelite.

    Line 71 sends a notification to the “Serial Viewer” page that a packet has been received from App_Twelite.

    events.send("parsed_app_twelite", "data_parsing_result", millis());

    App_ARIA: Registering Event Handler

    Lines 74-84 call Twelite.on() <ParsedAppAriaPacket> to register the process to execute when a packet is received from a child device in ARIA app (TWELITE ARIA mode).

    Twelite.on([](const ParsedAppAriaPacket& packet) {
            Serial.println("Received a packet from App_ARIA");
            static uint32_t firstSourceSerialId = packet.u32SourceSerialId;
            if (packet.u32SourceSerialId == firstSourceSerialId) {
                String jsonStr = createJsonFrom(packet);
                if (not(jsonStr.length() <= 0)) {
                    events.send(jsonStr.c_str(), "data_app_aria_twelite_aria_mode", millis());
                }
            }
            events.send("parsed_app_aria_twelite_aria_mode", "data_parsing_result", millis());
        });

    Filtering the Target

    Lines 76-77 limit the processing to the first child device received.

    static uint32_t firstSourceSerialId = packet.u32SourceSerialId;
    if (packet.u32SourceSerialId == firstSourceSerialId) {

    This is done to maintain consistency in the graph when there are multiple child devices.

    Creating JSON String

    Line 78 generates a JSON string from the received data.

    String jsonStr = createJsonFrom(packet);

    Sending Events to the Web Page

    Lines 79-81 send the generated JSON string to the “ARIA Viewer” page.

    if (not(jsonStr.length() <= 0)) {
        events.send(jsonStr.c_str(), "data_app_aria_twelite_aria_mode", millis());
    }

    The event name is data_app_aria_twelite_aria_mode.

    Line 83 sends a notification to the “Serial Viewer” page that a packet has been received from App_Twelite.

    events.send("parsed_app_aria_twelite_aria_mode", "data_parsing_result", millis());

    App_CUE: Registering Event Handler

    Lines 86-96 call Twelite.on() <ParsedAppCuePacket> to register the process to execute when a packet is received from a child device in CUE app (TWELITE CUE mode).

    Twelite.on([](const ParsedAppCuePacket& packet) {
        Serial.println("Received a packet from App_CUE");
        static uint32_t firstSourceSerialId = packet.u32SourceSerialId;
        if (packet.u32SourceSerialId == firstSourceSerialId) {
            String jsonStr = createJsonFrom(packet);
            if (not(jsonStr.length() <= 0)) {
                events.send(jsonStr.c_str(), "data_app_cue_twelite_cue_mode", millis());
            }
        }
        events.send("parsed_app_cue_twelite_cue_mode", "data_parsing_result", millis());
    });

    Others: Registering Event Handlers

    Lines 98-126 register the process to execute when packets are received from child devices of other apps.

    As with the ARIA app, events are sent to the “Serial Viewer”.

    All: Registering Event Handlers

    Lines 128-134 register the process to execute when packets are received from child devices of any app.

    Twelite.on([](const BarePacket& packet) {
        String jsonStr = createJsonFrom(packet);
        if (not(jsonStr.length() <= 0)) {
            events.send(jsonStr.c_str(), "data_bare_packet", millis());
        }
        events.send("unparsed_bare_packet", "data_parsing_result", millis());
    });

    Here as well, the packet data string is sent to the “Serial Viewer”.

    OLED Display Settings

    Lines 137-142 configure the OLED display.

        Wire.begin();
        SeeedGrayOled.init(SSD1327);
        SeeedGrayOled.setNormalDisplay();
        SeeedGrayOled.setVerticalMode();
        SeeedGrayOled.setGrayLevel(0x0F);
        SeeedGrayOled.clearDisplay();

    Wireless LAN Settings

    Lines 146-154 configure the wireless LAN.

    WiFi.mode(WIFI_AP);
    char uidCString[8];
    sprintf(uidCString, " (%02X)", createUidFromMac());
    char ssidCString[20];
    sprintf(ssidCString, "%s%s", WIFI_SSID_BASE, uidCString);
    WiFi.softAP(ssidCString, WIFI_PASSWORD, WIFI_CH, false, 8);
    delay(100);    // IMPORTANT: Waiting for SYSTEM_EVENT_AP_START
    WiFi.softAPConfig(WIFI_IP, WIFI_IP, WIFI_MASK);
    MDNS.begin(HOSTNAME);

    File System Settings

    Line 187 configures the LittleFS file system.

    if (LittleFS.begin()) { Serial.println("Mounted file system."); }

    This allows you to retrieve files such as HTML written to the flash area as web pages.

    Web Server Settings

    Lines 190-217 configure the web server.

    Handling GET Requests

    For example, lines 195-199 return /signal-viewer.html from the LittleFS file system in response to a GET request to /signal-viewer.

    server.on("/signal-viewer", HTTP_GET,
              [](AsyncWebServerRequest* request) {
                  Serial.println("HTTP_GET: signal-viewer.html");
                  request->send(LittleFS, "/signal-viewer.html", "text/html");
              });

    Server Initialization

    Lines 215-217 set the root of the file system as the server root, register the event source, and start the server.

    server.serveStatic("/", LittleFS, "/");
    server.addHandler(&events);
    server.begin();

    Updating TWELITE Data

    Line 223 calls Twelite.update().

        Twelite.update();

    Twelite.update() is a function that sequentially reads packet data (in ModBus ASCII format) sent from the TWELITE parent device, one byte at a time.

    Web Page

    This section does not provide a detailed explanation of the web page. Only important points are explained.

    HTML: Grid System

    The HTML of this sample uses Flexbox Grid (the source file is data/css/flexboxgrid.min.css).

    It uses a 12-column grid system similar to Bootstrap, as shown below.

          <div class="col-xs-6 col-sm-6 col-md-5 col-lg-4">
            <div class="neumorphic inset dense row center-xs middle-xs">
              <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 npr npl">
                <img src="./images/logo-lands.svg" class="logo" />
              </div>
            </div>
          </div>
    
          <div class="col-xs-6 col-sm-6 col-md-7 col-lg-8">
            <div class="neumorphic inset dense row center-xs middle-xs">
              <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 nwp npr npl">
                <span class="medium bold">TWELITE SPOT</span>
              </div>
              <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 nwp npr npl">
                <span class="small bold">CUE Viewer</span>
              </div>
            </div>
          </div>

    Here, the element centered on the logo is set to 6/12 width, and the element centered on the text is also 6/12 width, so both are arranged in a row with equal width. The elements centered on the text TWELITE SPOT and CUE Viewer are both 12/12 width, meaning they are arranged in two rows, each occupying one full row.

    HTML: Data Display Section

    Elements displaying data received from TWELITE child devices are given unique IDs.

    Below is an excerpt showing the part that displays the X-axis acceleration received from TWELITE CUE.

    <div class="col-xs-4 nwp npr npl">
      <code class="medium"
            id="latest-accel-x">ยฑ--.--</code>
      <code class="small">G</code>
    </div>

    Here, the ID latest-accel-x is assigned. This ID is used to update the value from the script.

    JS: Global Variables

    Lines 4-8 declare global variables for storing the latest acceleration values.

    let latest_accel = {
        x: 0.0,
        y: 0.0,
        z: 0.0
    };

    These values are also used by the graph, so global variables are used to simplify the implementation.

    JS: Graph Settings

    Lines 11-133 configure the graph drawing library Chart.js | Chart.js and its plugin chartjs-plugin-streaming.

    JS: Updating Page Content

    The function processDataAppCueTweliteCueMode() on lines 136-235 updates the page content when the data_app_cue_twelite_cue_mode event is received from the sketch.

    For example, lines 184-208 update the voltage value and emoji according to the power supply voltage of TWELITE CUE.

    if (data.vcc >= 3000) {
        document.getElementById("latest-vcc-icon").innerHTML = "๐Ÿ”‹";
        document.getElementById("latest-vcc-data").innerHTML = `${(data.vcc / 1000.0).toFixed(2).toString().padStart(4)}`;
        document.getElementById("latest-vcc-data").classList.remove("red");
        document.getElementById("latest-vcc-data").classList.remove("yellow");
        document.getElementById("latest-vcc-data").classList.add("green");
    } else if (data.vcc >= 2700) {
        document.getElementById("latest-vcc-icon").innerHTML = "๐Ÿ”‹";
        document.getElementById("latest-vcc-data").innerHTML = `${(data.vcc / 1000.0).toFixed(2).toString().padStart(4)}`;
        document.getElementById("latest-vcc-data").classList.remove("red");
        document.getElementById("latest-vcc-data").classList.remove("yellow");
        document.getElementById("latest-vcc-data").classList.remove("green");
    } else if (data.vcc >= 2400) {
        document.getElementById("latest-vcc-icon").innerHTML = "๐Ÿชซ";
        document.getElementById("latest-vcc-data").innerHTML = `${(data.vcc / 1000.0).toFixed(2).toString().padStart(4)}`;
        document.getElementById("latest-vcc-data").classList.remove("red");
        document.getElementById("latest-vcc-data").classList.add("yellow");
        document.getElementById("latest-vcc-data").classList.remove("green");
    } else {
        document.getElementById("latest-vcc-icon").innerHTML = "๐Ÿชซ";
        document.getElementById("latest-vcc-data").innerHTML = `${(data.vcc / 1000.0).toFixed(2).toString().padStart(4)}`;
        document.getElementById("latest-vcc-data").classList.add("red");
        document.getElementById("latest-vcc-data").classList.remove("yellow");
        document.getElementById("latest-vcc-data").classList.remove("green");
    }

    Here, when the power supply voltage drops below 2700mV, the emoji changes from ๐Ÿ”‹ to ๐Ÿชซ, and as the voltage drops from 3000mV โ†’ 2700mV โ†’ 2400mV, the CSS class applied to the voltage value text color is switched accordingly.

    Registering Event Listeners

    Lines 254-257 register the process to execute when events are received from the sketch.

    source.addEventListener("data_app_cue_twelite_cue_mode", (e) => {
        console.log("data_app_cue_twelite_cue_mode", e.data);
        processDataAppCueTweliteCueMode(JSON.parse(e.data));
    }, false);

    Here, the event message received from the sketch is parsed as a JSON string, and the resulting data is passed to the function processDataAppCueTweliteCueMode().

    Arduino

    ESP32

    Community

    Libraries

    Plugins

    ECMAScript (JavaScript)

    Community