ESP8266: Hello World!

Hal besar selalu berawal dari sebuah hello world

Bayangkan jika tidak pernah terjadi hello world; tidak ada bigbang, tidak ada ikan yang melompat ke darat, tidak ada dinosaurus yang punah, tidak ada monyet yang berjalan tegap, tidak ada peradaban, tidak ada tulisan yang tidak ada kaitannya dengan judul ini. Maaf cuman ngetest GitHub Copilot, kan enak ngeblog cuma pencet tab ehehe..

Oke,..

Saya akan mencoba untuk menjalankan hello world pada beberapa protokol yang sering digunakan dalam pengembangan IoT saya tahu dengan board ESP8266.

  • Serial
  • Web Server (HTTP, SSE, WebSocket)
  • MQTT

*Saya menggunakan PlatformIO di VS Code dan framework Arduino

Serial

Serial akan paling sering digunakan, karena ini seperti halnya console di JavaScript yang sangat berguna untuk tahap development dan debugging, dan juga untuk upload firmware/data ke MCU.

Bedanya di Serial tidak ada fitur seperti console.time(), console.table() atau hal fancy JavaScript lainnya karena di Serial hanya bisa output plain text aja.

Ada beberapa board yang tidak support Serial dikarenakan beberapa faktor, misal ESP-01 yang tidak ada interface USB; untuk menggunakan Serial pada board tersebut harus menggunakan modul adapter UART seperti ini. Kapan-kapan saya akan tulis hal tersebut.

Untuk menggukanannya bisa langsung dengan fn berikut pada object Serial:

  • print() atau
  • println() untuk ouput dengan line break
  • printf() untuk output dengan specifier format
void setup(){
  Serial.begin(74880);

  Serial.println("Hello World!");
}

void main(){
  Serial.printf("Seconds since boot: %lu", millis() / 1000);
  delay(1000)
}

Hal yang perlu diperhatikan adalah bahwa Serial butuh Serial.begin() pada block setup dengan parameter baudrate berupa angka yang harus disamakan dengan settingan IDE, Jika mismatch maka IDE akan mengeluarkan karakter non-utf-8 atau disebut gibberish text.

Web Server

Saya menggunakan ESPAsyncWebServer karena library ini menerapkan konsep asynchronous, jadi tidak membebani main loop dan juga karena sudah support SSE dan WebSocket.

Disini saya hanya fokus pada Web Server, untuk hal lain seperti WiFi akan saya tulis di lain waktu.

Install Library

# platformio.ini

[env:development]
platform = espressif8266
board = nodemcuv2
framework = arduino
+lib_deps = ottowinter/ESPAsyncWebServer-esphome@^2.1.0

*ESPAsyncWebServer-esphome merupakan mirror fork dari repo asli, hanya saja package tersebut lebih update di PlatformIO.

Usage

Pastikan ESP dapat connect ke jaringan WiFi dan mendapatkan IP address. Oiya, contoh lengkap sudah ada pada halaman repository ESPAsyncWebServer.

Basic HTTP Web Server

Web Server ini akan menjadi dasar untuk SSE dan WebSocket.

#include <ESPAsyncWebServer.h>

#include "config.h"

AsyncWebServer server(80);

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

    // Connect ke WiFi
    WiFi.mode(WIFI_STA);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
        Serial.printf("WiFi Failed!\n");
        return;
    }

    server.on("/hello", HTTP_GET, [](AsyncWebServerRequest *request) {
        request->send(200, "text/plain", "Hello World!");
    });

    server.begin();
}

void loop() {
    // server tidak berjalan pada main loop
}

Akan saya sedikit jelaskan kode di atas,..

Pertama include library-nya, lalu buat instance AsyncWebServer baru dengan parameter port yang akan digunakan, disini saya buat bernama server menggunakan port default HTTP: 80.

Lalu pada block setup(), register handler untuk:

  • URL /hello
  • method HTTP_GET

dengan response:

  • mimetype text/plain
  • content Hello World!

Terakhir kita jalankan server dengan invoke server.begin().

Karena library ini full asynchronous, maka tidak perlu menggunakan loop() untuk menjalankan server. Semua event berjalan berdasarkan callback yang sudah disediakan oleh library.

Setelah upload program tersebut, coba buka browser dan akses URL http://<ip_esp>/hello maka akan muncul response text: Hello World!

Static File Serving

Jika ingin menjalankan server dengan file statis, seperti HTML, CSS, JavaScript, atau image bisa menggunakan AsyncWebServer::serveStatic().

buat file index.html:

<!-- {projectRoot}/data/www/index.html -->

<!DOCTYPE html>
<html>
  <head>
    <title>ESP8266</title>
  </head>
  <body>
    <h1>Hello World!</h1>
  </body>
</html>

Ada 2 pilihan filesystem yang bisa digunakan; SPIFFS dan LittleFS. Gunakan LittleFS karena SPIFFS sudah tidak didukung lagi, hanya tinggal menunggu waktu.

# platformio.ini

[env:development]
platform = espressif8266
board = nodemcuv2
framework = arduino
lib_deps = ottowinter/ESPAsyncWebServer-esphome@^2.1.0
+board_build.filesystem = littlefs

upload file yang ada di folder data ke MCU:

$ pio run -t uploadfs

Ada sedikit tambahan untuk menjalankan server dengan file statis.

#include <LittleFS.h>
...
setup() {
  LittleFS.begin();
  ...
  server.serveStatic("/", LittleFS, "/www/").setDefaultFile("index.html");
  ...
  server.begin()
}

SSE (Server Sent Events)

SSE adalah protokol satu arah dimana server dapat mengirimkan data dalam sebuah event ke client kapanpun server mau, tanpa harus menunggu client mengirimkan request. SSE cocok untuk mengirimkan data secara realtime, seperti data dari sensor.

Untuk SSE hanya perlu attach instance AsyncEventSource ke AsyncWebServer sebagai handler.

...
AsyncWebServer server(80);
AsyncEventSource events("/events");

setup() {
  ...
  server.addHandler(&events);
  ...
  server.begin()
}

Untuk mengirim pesan, AsyncEventSource memiliki method send() yang membutuhkan 3 parameter:

  • message : const char *
  • event : const char *
  • id: uint32_t

Misal kita ingin mengirimkan event "hello" dengan pesan "Hello World!" dan id generated dari millis():

events.send("Hello World!", "hello", millis());

Pada sisi client kita harus buat instance EventSource dengan endpoint yang sama dengan AsyncEventSource tadi. Contoh minimal JavaScript di sisi client:

if (!!window.EventSource) {
  const source = new EventSource('/events')

  source.addEventListener(
    'open',
    function (e) {
      console.log('Events Connected')
    },
    false
  )

  source.addEventListener(
    'error',
    function (e) {
      if (e.target.readyState != EventSource.OPEN) {
        console.log('Events Disconnected')
      }
    },
    false
  )

  source.addEventListener(
    'hello',
    function (e) {
      console.log('message:', e.data)
    },
    false
  )
}

contoh lengkap SSE dengan event waktu booting ESP:

// src/main.cpp

#include <ESPAsyncWebServer.h>
#include <LittleFS.h>

#include "config.h"

AsyncWebServer server(80);
AsyncEventSource events("/events");

void setup() {
    Serial.begin(74880);
    // start file system
    LittleFS.begin();

    // connect to WiFi
    WiFi.mode(WIFI_STA);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
        Serial.printf("WiFi Failed!\n");
        return;
    }

    // attach SSE handler
    server.addHandler(&events);

    // serve static files
    server.serveStatic("/", LittleFS, "/www/").setDefaultFile("index.html");

    // start the server
    server.begin();
}

void loop() {
    // send SSE event every second
    static char temp[128];
    sprintf(temp, "Seconds since boot: %lu", millis() / 1000);

    events.send(temp, "time", millis() / 1000);
    delay(1000);
}
<!-- data/www/index.html -->

<!DOCTYPE html>

<html>
  <head>
    <title>SSE Test ESP8266</title>
  </head>
  <body>
    <p>press F12 to open devtool.</p>
    <script>
      if (!!window.EventSource) {
        const source = new EventSource('/events')

        source.addEventListener(
          'open',
          function (e) {
            console.log('Events Connected')
          },
          false
        )

        source.addEventListener(
          'error',
          function (e) {
            if (e.target.readyState != EventSource.OPEN) {
              console.log('Events Disconnected')
            }
          },
          false
        )

        source.addEventListener(
          'time',
          function (e) {
            console.log({ timeMessage: e.data })
          },
          false
        )
      }
    </script>
  </body>
</html>

masih males ngelanjutin, panjang juga ternyata..