ESP32 & ESP8266

OTA Update ESP32: Update Firmware Tanpa Kabel

TOKEN

Panduan lengkap mengupdate firmware ESP32 secara wireless menggunakan ArduinoOTA, Web-Based OTA, dan HTTP OTA — tanpa perlu kabel USB lagi

1. Apa Itu OTA Update?

OTA (Over-The-Air) adalah metode untuk mengupdate firmware atau software pada perangkat embedded secara wireless, tanpa perlu menghubungkan kabel USB atau mengakses fisik perangkat. Teknologi ini sangat penting dalam ekosistem IoT di mana perangkat sering dipasang di lokasi yang sulit dijangkau — seperti atap gedung, gudang, area pertanian, atau bahkan di dalam dinding.

Bayangkan Anda memiliki 50 unit ESP32 yang tersebar di berbagai lokasi untuk memantau suhu gudang. Tanpa OTA, setiap kali ada bug atau fitur baru, Anda harus mendatangi setiap perangkat satu per satu dengan laptop dan kabel USB. Dengan OTA, semua bisa diupdate dari satu lokasi pusat melalui jaringan WiFi.

💡 Tips

OTA bukan sekadar kenyamanan — untuk banyak proyek IoT produksi, OTA adalah kebutuhan wajib. Tanpa OTA, biaya maintenance perangkat yang tersebar bisa sangat mahal dan tidak praktis.

Jenis-Jenis OTA pada ESP32

ESP32 mendukung beberapa metode OTA yang bisa dipilih sesuai kebutuhan:

Metode OTA Cara Kerja Kompleksitas Cocok Untuk
ArduinoOTAUpdate via jaringan lokal menggunakan Arduino IDEMudahDevelopment & testing
Web-Based OTAUpload firmware via browser ke web server di ESP32SedangPrototyping & demo
HTTP OTAESP32 mengunduh firmware dari server HTTP/HTTPSMahirProduksi & fleet update
Perbandingan Arsitektur Metode OTA
💻
ArduinoOTA
Arduino IDE → WiFi → ESP32
🌐
Web OTA
Browser → WiFi → ESP32
☁️
HTTP OTA
Server → WiFi → ESP32
💾
Flash Memory
Firmware ditulis ke partisi OTA
🔄
Swap Partition
Boot dari partisi baru
Validasi
Rollback jika gagal boot

Skema Partisi OTA ESP32

Untuk mendukung OTA, ESP32 menggunakan skema partisi khusus yang membagi flash memory menjadi beberapa bagian. Paling tidak, diperlukan dua partisi aplikasi — satu yang sedang berjalan (aktif) dan satu lagi untuk menampung firmware baru yang diupload.

Diagram: Skema Partisi OTA pada Flash 4 MB
  ┌──────────────────────────────────────────────────┐
  │                Flash 4 MB                         │
  ├──────────────┬────────────────────────────────────┤
  │  nvs (20KB)  │  Penyimpanan data konfigurasi      │
  ├──────────────┼────────────────────────────────────┤
  │ otadata (8KB)│  Menandai partisi mana yang aktif   │
  ├──────────────┼────────────────────────────────────┤
  │ app0 (1.5MB) │  ← Partisi firmware A (aktif)       │
  ├──────────────┼────────────────────────────────────┤
  │ app1 (1.5MB) │  ← Partisi firmware B (OTA target)  │
  ├──────────────┼────────────────────────────────────┤
  │ spiffs (1MB) │  Filesystem untuk data tambahan     │
  └──────────────┴────────────────────────────────────┘

  Saat OTA: firmware baru ditulis ke partisi yang tidak aktif,
  lalu bootloader mengarahkan boot ke partisi baru setelah reset.
⚠️ Peringatan

Pastikan ukuran firmware Anda tidak melebihi kapasitas partisi. Dengan skema default "Default 4MB with spiffs", masing-masing partisi app0 dan app1 berukuran sekitar 1.5 MB. Jika firmware Anda terlalu besar, Anda perlu membuat custom partition scheme.

2. Setup ArduinoOTA

ArduinoOTA adalah metode OTA paling sederhana yang tersedia di ekosistem Arduino. Dengan ArduinoOTA, Anda bisa mengupload firmware baru langsung dari Arduino IDE tanpa perlu kabel USB, asalkan ESP32 dan komputer Anda berada di jaringan WiFi yang sama.

Langkah 1: Instal Library ArduinoOTA

Library ArduinoOTA sudah termasuk dalam package ESP32 untuk Arduino IDE, jadi jika Anda sudah menginstal board ESP32 (seperti di tutorial ESP32 Fundamentals), Anda tidak perlu menginstal library tambahan.

Langkah 2: Program ESP32 dengan ArduinoOTA

Berikut adalah kode dasar yang mengaktifkan ArduinoOTA pada ESP32:

C++ — arduino_ota_basic.ino
// ============================================
// OTA Basic - ESP32 ArduinoOTA
// BeebaneLabs - https://beebanelabs.pages.dev
// ============================================

#include <WiFi.h>
#include <ArduinoOTA.h>

// Konfigurasi WiFi
const char* ssid = "WIFI_KAMU";
const char* password = "PASSWORD_WIFI";

void setup() {
  Serial.begin(115200);
  Serial.println("Memulai ESP32 dengan ArduinoOTA...");

  // Koneksi ke WiFi
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.print("Terhubung ke WiFi! IP: ");
  Serial.println(WiFi.localIP());

  // Inisialisasi ArduinoOTA
  ArduinoOTA.setHostname("esp32-ota-demo");

  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH) {
      type = "firmware";
    } else {
      type = "filesystem";
    }
    Serial.println("Mulai update: " + type);
  });

  ArduinoOTA.onEnd([]() {
    Serial.println("\nUpdate selesai!");
  });

  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });

  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });

  ArduinoOTA.begin();
  Serial.println("OTA siap! Upload dari Arduino IDE via Network Port.");
}

void loop() {
  // Handle OTA request — WAJIB dipanggil di loop()
  ArduinoOTA.handle();

  // Program utama Anda di sini
  // ...
}

Langkah 3: Upload Firmware via OTA

  1. Pertama-tama, upload kode di atas ke ESP32 via kabel USB (ini upload terakhir via kabel!)
  2. Buka Tools → Port, Anda akan melihat port baru bertuliskan "esp32-ota-demo at 192.168.x.x (ESP32)"
  3. Pilih port network tersebut
  4. Buat perubahan pada kode Anda (misalnya ubah pesan serial)
  5. Klik → Upload (Ctrl+U) seperti biasa
  6. Arduino IDE akan mengupload firmware melalui WiFi tanpa kabel!
💡 Tips

Jika port network ESP32 tidak muncul di Arduino IDE, pastikan komputer dan ESP32 berada di jaringan yang sama dan firewall tidak memblokir port 3232 (port default ArduinoOTA). Coba matikan firewall sementara untuk testing.

Menambahkan Password Keamanan

Secara default, ArduinoOTA tidak memerlukan password — siapa saja di jaringan yang sama bisa mengupload firmware ke ESP32 Anda. Untuk keamanan, tambahkan password:

C++ — Tambahkan setelah ArduinoOTA.setHostname()
// Atur password untuk OTA
ArduinoOTA.setPassword("passwordOTA123");

// Atau gunakan hash MD5 (lebih aman):
// ArduinoOTA.setPasswordHash("d8578edf8458ce06fbc5bb76a58c5ca4");
⚠️ Peringatan

Saat password OTA diaktifkan, Arduino IDE akan meminta password saat pertama kali upload via network. Masukkan password yang sama dengan yang Anda atur di kode. Password ini dikirim dalam bentuk MD5 hash, bukan plain text, tetapi bukan enkripsi end-to-end. Jangan gunakan password sensitif.

3. Web-Based OTA Update

Web-Based OTA memungkinkan Anda mengupdate firmware ESP32 melalui web browser. ESP32 menjalankan web server kecil yang menyediakan halaman HTML tempat Anda bisa memilih dan mengupload file firmware (.bin). Metode ini sangat fleksibel karena tidak memerlukan Arduino IDE — cukup buka browser dari perangkat manapun.

Mengapa Web-Based OTA?

Program Web-Based OTA

C++ — web_ota_server.ino
// ============================================
// Web-Based OTA Update - ESP32
// BeebaneLabs - https://beebanelabs.pages.dev
// ============================================

#include <WiFi.h>
#include <WebServer.h>
#include <Update.h>

const char* ssid = "WIFI_KAMU";
const char* password = "PASSWORD_WIFI";

WebServer server(80);

// Halaman HTML untuk upload
const char* loginIndex = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ESP32 OTA Update</title>
  <style>
    body { font-family: Arial; background: #1a1a2e; color: #eee;
           display: flex; justify-content: center; align-items: center;
           min-height: 100vh; margin: 0; }
    .card { background: #16213e; padding: 40px; border-radius: 12px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.3); max-width: 450px; width: 90%; }
    h1 { text-align: center; color: #00d2ff; margin-bottom: 24px; }
    input[type=file] { margin: 16px 0; width: 100%; }
    button { background: #00d2ff; color: #000; border: none; padding: 12px 24px;
             border-radius: 8px; width: 100%; font-size: 16px; cursor: pointer;
             font-weight: bold; }
    button:hover { background: #00b4d8; }
    #progress { display: none; margin-top: 16px; }
    .bar { background: #0f3460; border-radius: 8px; overflow: hidden; height: 24px; }
    .bar-fill { background: #00d2ff; height: 100%; width: 0%;
                transition: width 0.3s; text-align: center; line-height: 24px;
                font-size: 12px; color: #000; font-weight: bold; }
    #status { margin-top: 12px; text-align: center; }
  </style>
</head>
<body>
  <div class="card">
    <h1>🔄 ESP32 OTA Update</h1>
    <form id="uploadForm">
      <input type="file" name="firmware" accept=".bin" id="fileInput">
      <button type="submit">Upload Firmware</button>
    </form>
    <div id="progress">
      <div class="bar"><div class="bar-fill" id="barFill">0%</div></div>
    </div>
    <div id="status"></div>
  </div>
  <script>
    document.getElementById('uploadForm')
      .addEventListener('submit', function(e) {
      e.preventDefault();
      var file = document.getElementById('fileInput').files[0];
      if (!file) { alert('Pilih file firmware!'); return; }
      var data = new FormData();
      data.append('firmware', file);
      var xhr = new XMLHttpRequest();
      xhr.open('POST', '/update', true);
      document.getElementById('progress').style.display = 'block';
      xhr.upload.addEventListener('progress', function(e) {
        if (e.lengthComputable) {
          var pct = Math.round((e.loaded / e.total) * 100);
          document.getElementById('barFill').style.width = pct + '%';
          document.getElementById('barFill').innerText = pct + '%';
        }
      });
      xhr.onload = function() {
        if (xhr.status === 200) {
          document.getElementById('status').innerHTML =
            '<p style="color:#00d2ff">✅ Update berhasil! ESP32 akan restart...</p>';
        } else {
          document.getElementById('status').innerHTML =
            '<p style="color:#ff6b6b">❌ Update gagal!</p>';
        }
      };
      xhr.send(data);
    });
  </script>
</body>
</html>
)rawliteral";

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

  // Koneksi WiFi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected! IP: " + WiFi.localIP().toString());

  // Route: halaman utama
  server.on("/", HTTP_GET, []() {
    server.send(200, "text/html", loginIndex);
  });

  // Route: handle upload firmware
  server.on("/update", HTTP_POST, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
    delay(1000);
    ESP.restart();
  }, []() {
    HTTPUpload& upload = server.upload();

    if (upload.status == UPLOAD_FILE_START) {
      Serial.printf("Mulai upload: %s\n", upload.filename.c_str());
      if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_WRITE) {
      if (Update.write(upload.buf, upload.currentSize)
          != upload.currentSize) {
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_END) {
      if (Update.end(true)) {
        Serial.printf("Update selesai: %u bytes\n",
                      upload.totalSize);
      } else {
        Update.printError(Serial);
      }
    }
  });

  server.begin();
  Serial.println("Web OTA server berjalan!");
  Serial.println("Buka http://" + WiFi.localIP().toString()
                 + " di browser");
}

void loop() {
  server.handleClient();
}
ℹ️ Cara Menggunakan Web OTA

1. Upload kode di atas via kabel USB untuk pertama kali.
2. Buka Serial Monitor, catat IP address ESP32.
3. Buka browser, masukkan IP tersebut (contoh: http://192.168.1.105).
4. Pilih file .bin firmware baru, klik Upload.
5. Tunggu progress bar hingga 100%, ESP32 akan restart otomatis.
6. File .bin bisa ditemukan di C:\Users\<user>\AppData\Local\Temp\arduino_build_<id>\ setelah Verify/Compile di Arduino IDE.

4. HTTP OTA Update

HTTP OTA adalah metode yang paling powerful untuk update firmware di lingkungan produksi. Dengan HTTP OTA, ESP32 secara aktif mengunduh firmware dari server web (cloud atau lokal) dan menginstalnya sendiri. Ini memungkinkan Anda mengupdate ratusan perangkat sekaligus hanya dengan mengupload firmware ke satu server.

Arsitektur HTTP OTA

Diagram: Arsitektur HTTP OTA
  ┌──────────┐    HTTPS GET     ┌──────────────┐
  │  ESP32   │ ───────────────→ │  Web Server  │
  │  Device  │ ←─────────────── │  (GitHub/    │
  │          │   firmware.bin   │   S3/custom) │
  └────┬─────┘                  └──────────────┘
       │                              ↑
       │  1. Cek versi terbaru        │
       │     dari server              │
       │  2. Jika ada update,         │
       │     download firmware.bin    │
       │  3. Tulis ke partisi OTA     │
       │  4. Set flag & restart       │
       ▼
  ┌──────────┐
  │  Boot    │  → bootloader memilih
  │  Loader  │    partisi baru
  └──────────┘

Program HTTP OTA dengan Version Check

C++ — http_ota_update.ino
// ============================================
// HTTP OTA Update - ESP32
// BeebaneLabs - https://beebanelabs.pages.dev
// ============================================

#include <WiFi.h>
#include <HTTPClient.h>
#include <HTTPUpdate.h>
#include <WiFiClientSecure.h>

const char* ssid = "WIFI_KAMU";
const char* password = "PASSWORD_WIFI";

// URL firmware di server (bisa GitHub raw, S3, server sendiri)
const char* firmwareURL =
  "http://yourserver.com/esp32/firmware.bin";

// URL file versi (untuk cek update terbaru)
const char* versionURL =
  "http://yourserver.com/esp32/version.txt";

// Versi firmware saat ini — naikkan setiap kali ada update
#define FIRMWARE_VERSION "1.0.0"

void setup() {
  Serial.begin(115200);
  Serial.println("Firmware v" + String(FIRMWARE_VERSION));

  // Koneksi WiFi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected!");

  // Cek dan jalankan update jika tersedia
  checkForUpdate();
}

void loop() {
  // Cek update setiap 60 detik
  static unsigned long lastCheck = 0;
  if (millis() - lastCheck > 60000) {
    checkForUpdate();
    lastCheck = millis();
  }
  // Program utama...
}

void checkForUpdate() {
  if (WiFi.status() != WL_CONNECTED) return;

  HTTPClient http;
  http.begin(versionURL);
  int httpCode = http.GET();

  if (httpCode == 200) {
    String newVersion = http.getString();
    newVersion.trim();

    Serial.println("Versi server: " + newVersion);
    Serial.println("Versi lokal: " + String(FIRMWARE_VERSION));

    if (newVersion != String(FIRMWARE_VERSION)) {
      Serial.println("Update tersedia! Memulai download...");
      performOTA(firmwareURL);
    } else {
      Serial.println("Firmware sudah versi terbaru.");
    }
  } else {
    Serial.println("Gagal cek versi: " + String(httpCode));
  }

  http.end();
}

void performOTA(const char* url) {
  WiFiClient client;
  Serial.println("Downloading firmware dari: " + String(url));

  t_httpUpdate_return ret = httpUpdate.update(client, url);

  switch (ret) {
    case HTTP_UPDATE_FAILED:
      Serial.printf("Update GAGAL! Error(%d): %s\n",
                    httpUpdate.getLastError(),
                    httpUpdate.getLastErrorString().c_str());
      break;

    case HTTP_UPDATE_NO_UPDATES:
      Serial.println("Tidak ada update.");
      break;

    case HTTP_UPDATE_OK:
      Serial.println("Update berhasil! Restart...");
      // ESP akan restart otomatis
      break;
  }
}

Menyimpan Firmware di GitHub

Salah satu cara termudah untuk mendistribusikan firmware adalah menggunakan GitHub Releases. Upload file .bin sebagai release asset, lalu gunakan URL raw dari file tersebut di kode ESP32:

Contoh URL GitHub
// URL firmware dari GitHub Release
const char* firmwareURL =
  "https://github.com/user/repo/releases/latest/"
  "download/firmware.bin";

// Untuk HTTPS, gunakan WiFiClientSecure:
WiFiClientSecure client;
client.setInsecure(); // Skip SSL verification (dev only!)
httpUpdate.update(client, firmwareURL);
⚠️ Peringatan HTTPS

Untuk koneksi HTTPS yang aman, gunakan sertifikat CA server yang valid, bukan setInsecure(). setInsecure() hanya untuk development — di produksi, ini rentan terhadap man-in-the-middle attack. Simpan root CA certificate di SPIFFS dan muat saat inisialisasi.

5. Pertimbangan Keamanan OTA

OTA adalah pedang bermata dua — memudahkan update, tetapi juga membuka potensi keamanan. Jika seseorang bisa mengintersep proses OTA dan mengirim firmware berbahaya, mereka bisa mengambil alih seluruh jaringan perangkat IoT Anda. Berikut adalah langkah-langkah keamanan yang wajib diterapkan:

🔴
Risiko Tanpa Keamanan
  • Firmware injection oleh attacker
  • Intercept & modifikasi firmware
  • Upload malware ke semua device
  • Brick perangkat dengan firmware corrupt
  • Pencurian data konfigurasi WiFi
🟢
Dengan Keamanan OTA
  • Firmware terenkripsi (AES-256)
  • Signature verification (RSA/ECDSA)
  • HTTPS dengan certificate pinning
  • Rollback otomatis jika boot gagal
  • Version downgrade protection

Best Practices Keamanan OTA

Langkah Deskripsi Tingkat
Gunakan HTTPS Enkripsi channel komunikasi untuk mencegah intercept Wajib
Verifikasi Signature Tanda tangani firmware dengan private key, verifikasi di ESP32 Sangat Disarankan
Firmware Encryption Enkripsi file firmware agar tidak bisa dibaca/dimodifikasi Disarankan
Version Check Pastikan versi baru > versi lama (cegah downgrade) Wajib
Secure Boot ESP32 hanya menjalankan firmware yang ditandatangani Espressif Produksi
Flash Encryption Enkripsi seluruh flash memory, firmware tidak bisa dibaca via JTAG Produksi
🔴 Keamanan Kritis

Jangan pernah menyimpan password WiFi atau API key secara plain text di firmware yang dikirim via OTA. Gunakan NVS (Non-Volatile Storage) ESP32 untuk menyimpan kredensial secara terpisah dari firmware, sehingga update firmware tidak menghapus atau mengekspos data sensitif.

Implementasi Rollback Otomatis

ESP32 memiliki fitur OTA rollback built-in yang mencegah boot loop jika firmware baru bermasalah. Jika ESP32 tidak bisa boot dengan firmware baru setelah N kali restart, bootloader akan otomatis kembali ke firmware lama:

C++ — Rollback pada loop() pertama
#include <esp_ota_ops.h>

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

  // Cek apakah ini boot pertama setelah OTA
  const esp_partition_t* running =
    esp_ota_get_running_partition();
  esp_ota_img_states_t state;

  if (esp_ota_get_state_partition(running, &state) == ESP_OK) {
    if (state == ESP_OTA_IMG_PENDING_VERIFY) {
      // Firmware baru berjalan pertama kali
      // Lakukan self-test di sini:
      bool selfTestOK = runSelfTest();

      if (selfTestOK) {
        // Konfirmasi firmware valid
        esp_ota_mark_app_valid_cancel_rollback();
        Serial.println("Firmware baru dikonfirmasi!");
      } else {
        // Firmware bermasalah, rollback ke versi lama
        Serial.println("Self-test gagal! Rollback...");
        esp_ota_mark_app_invalid_and_rollback();
        ESP.restart();
      }
    }
  }
}

bool runSelfTest() {
  // Test koneksi WiFi
  // Test koneksi server
  // Test sensor aktif
  // Return true jika semua OK
  return true;
}

6. Proyek Praktis: Remote Update Sensor Node

Sekarang kita akan menggabungkan semua yang telah dipelajari ke dalam satu proyek lengkap: sensor node yang bisa di-update dari jarak jauh. Proyek ini menggunakan ESP32 yang membaca sensor suhu DHT11, mengirim data ke MQTT broker, dan mendukung update firmware via HTTP OTA dari server.

Fitur Proyek

Diagram: Arsitektur Proyek Sensor Node dengan OTA
  ┌────────────┐     WiFi      ┌──────────────┐
  │  ESP32     │──────────────→│  MQTT Broker │
  │  Sensor    │  publish:     │  (Mosquitto) │
  │  Node      │  sensor/suhu  └──────────────┘
  │            │
  │  ┌──────┐  │     WiFi      ┌──────────────┐
  │  │DHT11│  │←──────────────│  OTA Server  │
  │  └──────┘  │  HTTP GET     │  (version +  │
  │  ┌──────┐  │  firmware.bin │   firmware)  │
  │  │ LED  │  │──────────────→│              │
  │  └──────┘  │               └──────────────┘
  └────────────┘

Kode Lengkap Proyek

C++ — ota_sensor_node.ino (klik untuk expand)
// ============================================
// Proyek: Remote Update Sensor Node
// ESP32 + DHT11 + MQTT + HTTP OTA
// BeebaneLabs - https://beebanelabs.pages.dev
// ============================================

#include <WiFi.h>
#include <PubSubClient.h>
#include <HTTPClient.h>
#include <HTTPUpdate.h>
#include <DHT.h>
#include <esp_ota_ops.h>

// ====== KONFIGURASI ======
#define FIRMWARE_VERSION    "1.2.0"
#define LED_STATUS_PIN      2
#define DHT_PIN             4
#define DHT_TYPE            DHT11
#define OTA_CHECK_INTERVAL  300000  // 5 menit

const char* ssid          = "WIFI_KAMU";
const char* password      = "PASSWORD_WIFI";
const char* mqtt_server   = "192.168.1.100";
const int   mqtt_port     = 1883;
const char* mqtt_topic    = "iot/sensornode/data";
const char* mqtt_cmd_topic= "iot/sensornode/cmd";
const char* ota_version_url =
  "http://yourserver.com/ota/version.txt";
const char* ota_firmware_url =
  "http://yourserver.com/ota/firmware.bin";

// ====== OBJEK GLOBAL ======
WiFiClient espClient;
PubSubClient mqtt(espClient);
DHT dht(DHT_PIN, DHT_TYPE);

unsigned long lastOTA = 0;
unsigned long lastRead = 0;

// ====== LED STATUS ======
void setLedStatus(const char* mode) {
  // Dipanggil dengan "ok", "ota", "error"
  // Implementasi LED blink pattern bisa ditambah
  Serial.printf("Status: %s\n", mode);
}

// ====== WIFI ======
void setupWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("Menghubungkan WiFi");

  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 30) {
    delay(500);
    Serial.print(".");
    attempts++;
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\nWiFi OK! IP: " + WiFi.localIP().toString());
  } else {
    Serial.println("\nWiFi gagal! Restart...");
    ESP.restart();
  }
}

// ====== MQTT ======
void mqttCallback(char* topic, byte* payload, unsigned int length) {
  String msg;
  for (int i = 0; i < length; i++) {
    msg += (char)payload[i];
  }
  Serial.printf("MQTT [%s]: %s\n", topic, msg.c_str());

  if (msg == "check_update") {
    checkForOTAUpdate();
  } else if (msg == "restart") {
    ESP.restart();
  } else if (msg == "status") {
    publishStatus();
  }
}

void connectMQTT() {
  while (!mqtt.connected()) {
    Serial.print("MQTT connecting...");
    if (mqtt.connect("esp32-sensor-node")) {
      Serial.println("connected!");
      mqtt.subscribe(mqtt_cmd_topic);
    } else {
      Serial.printf("failed (%d). Retry...\n", mqtt.state());
      delay(3000);
    }
  }
}

void publishStatus() {
  String status = "{";
  status += "\"version\":\"" + String(FIRMWARE_VERSION) + "\",";
  status += "\"ip\":\"" + WiFi.localIP().toString() + "\",";
  status += "\"rssi\":" + String(WiFi.RSSI()) + ",";
  status += "\"uptime\":" + String(millis() / 1000);
  status += "}";
  mqtt.publish(mqtt_topic, status.c_str());
}

// ====== OTA UPDATE ======
void checkForOTAUpdate() {
  if (WiFi.status() != WL_CONNECTED) return;

  Serial.println("Mengecek update OTA...");
  setLedStatus("ota");

  HTTPClient http;
  http.begin(ota_version_url);
  int code = http.GET();

  if (code == 200) {
    String serverVersion = http.getString();
    serverVersion.trim();

    if (serverVersion != String(FIRMWARE_VERSION)) {
      Serial.println("Update tersedia: " + serverVersion);
      performOTAUpdate();
    } else {
      Serial.println("Sudah versi terbaru.");
      setLedStatus("ok");
    }
  } else {
    Serial.println("Gagal cek versi: " + String(code));
    setLedStatus("error");
  }
  http.end();
}

void performOTAUpdate() {
  WiFiClient client;
  t_httpUpdate_return ret =
    httpUpdate.update(client, ota_firmware_url);

  switch (ret) {
    case HTTP_UPDATE_OK:
      Serial.println("OTA berhasil! Restart...");
      break;
    case HTTP_UPDATE_FAILED:
      Serial.printf("OTA gagal: %s\n",
        httpUpdate.getLastErrorString().c_str());
      setLedStatus("error");
      break;
    default:
      break;
  }
}

// ====== SETUP ======
void setup() {
  Serial.begin(115200);
  pinMode(LED_STATUS_PIN, OUTPUT);

  dht.begin();
  setupWiFi();

  mqtt.setServer(mqtt_server, mqtt_port);
  mqtt.setCallback(mqttCallback);
  connectMQTT();

  Serial.println("Sensor Node siap! v" + String(FIRMWARE_VERSION));
  setLedStatus("ok");
}

// ====== LOOP ======
void loop() {
  // Maintain connections
  if (WiFi.status() != WL_CONNECTED) setupWiFi();
  if (!mqtt.connected()) connectMQTT();
  mqtt.loop();

  // Baca sensor setiap 15 detik
  if (millis() - lastRead > 15000) {
    float suhu = dht.readTemperature();
    float hum = dht.readHumidity();

    if (!isnan(suhu) && !isnan(hum)) {
      String payload = "{";
      payload += "\"suhu\":" + String(suhu, 1) + ",";
      payload += "\"kelembaban\":" + String(hum, 1) + ",";
      payload += "\"versi\":\"" + String(FIRMWARE_VERSION) + "\"";
      payload += "}";
      mqtt.publish(mqtt_topic, payload.c_str());
      Serial.printf("Suhu: %.1f°C, Humidity: %.1f%%\n",
                    suhu, hum);
    }
    lastRead = millis();
  }

  // Cek OTA setiap interval
  if (millis() - lastOTA > OTA_CHECK_INTERVAL) {
    checkForOTAUpdate();
    lastOTA = millis();
  }
}
ℹ️ Cara Menggunakan Proyek

1. Upload kode pertama kali via kabel USB.
2. Sensor suhu mulai dikirim ke MQTT setiap 15 detik.
3. Untuk update remote: naikkan versi di FIRMWARE_VERSION, compile, upload file .bin ke server, dan buat file version.txt berisi versi baru.
4. ESP32 akan otomatis mendeteksi update baru setiap 5 menit.
5. Atau kirim perintah check_update ke topik MQTT iot/sensornode/cmd untuk trigger manual.

7. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang OTA Update pada ESP32:

Pertanyaan 1: Apa kepanjangan dari OTA dalam konteks update firmware?

a) On The Application
b) Over-The-Air
c) Open Terminal Access
d) One Time Authentication

Pertanyaan 2: Fungsi apa yang WAJIB dipanggil di loop() agar ArduinoOTA berfungsi?

a) ArduinoOTA.start()
b) ArduinoOTA.handle()
c) ArduinoOTA.update()
d) ArduinoOTA.listen()

Pertanyaan 3: Mengapa ESP32 memerlukan skema partisi khusus untuk OTA?

a) Agar firmware lebih cepat dijalankan
b) Agar ada dua partisi: satu aktif dan satu untuk menampung firmware baru
c) Untuk menghemat penggunaan RAM
d) Untuk memisahkan kode WiFi dari kode sensor

Pertanyaan 4: Keamanan mana yang SANGAT DISARANKAN untuk OTA di lingkungan produksi?

a) Menggunakan password sederhana di ArduinoOTA
b) Menggunakan HTTPS dengan verifikasi signature firmware
c) Mengganti nama SSID WiFi
d) Menggunakan kabel USB yang lebih panjang

Pertanyaan 5: Apa yang terjadi jika firmware baru gagal boot saat rollback diaktifkan?

a) ESP32 akan mati permanen dan harus diganti
b) ESP32 akan otomatis kembali ke firmware versi lama
c) ESP32 akan masuk mode AP dan menunggu konfigurasi
d) ESP32 akan menghapus semua data di flash
← Sebelumnya Panduan Lengkap ESP32 Selanjutnya → Artikel Lainnya