Pada artikel sebelumnya, kita sudah berhasil menghubungkan ESP32 ke internet melalui MQTT dan mengirimkan data sensor ke Node-RED Dashboard. Data yang ditampilkan di sana selalu real-time — kita hanya bisa melihat kondisi saat ini, bukan riwayat sebelumnya.
Di artikel ini, kita akan melangkah lebih jauh ke wilayah data visualization — bagaimana cara menampilkan data sensor secara lebih kaya dan bermakna, termasuk dengan dimensi waktu. Kita akan membahas tiga topik yang saling berkaitan:
- NTP Client — mengambil tanggal dan waktu dari internet untuk memberi timestamp pada setiap data
- Chart Web Server — menampilkan grafik pembacaan sensor secara real-time langsung dari browser
- Google Sheets Logger — mencatat data sensor ke Google Sheets secara otomatis untuk keperluan analisis jangka panjang
Ketiga topik ini dibangun di atas fondasi yang sama: ESP32 terhubung ke WiFi, membaca sensor BMP280, dan menggunakan konektivitas internet untuk tujuan yang berbeda-beda.
Bagian 1: NTP Client — Mengambil Waktu dari Internet
Mengapa Timestamp Penting?
Data sensor tanpa informasi waktu ibarat catatan harian tanpa tanggal — kita tidak tahu kapan data itu diambil, tidak bisa membandingkan tren dari hari ke hari, dan tidak bisa menganalisis pola berdasarkan waktu.
Sayangnya, ESP32 tidak memiliki jam bawaan yang tetap berjalan saat perangkat mati. Solusinya adalah menggunakan NTP (Network Time Protocol) — protokol internet yang memungkinkan perangkat mendapatkan waktu yang akurat dari server waktu global.
Selama ESP32 terhubung ke internet, ia bisa menanyakan waktu saat ini ke server NTP kapanpun dibutuhkan, tanpa memerlukan modul RTC (Real-Time Clock) tambahan.
Cara Kerja NTP
NTP bekerja dengan model client-server. ESP32 kita berperan sebagai NTP Client yang mengirimkan permintaan ke NTP Server di internet. Server merespons dengan waktu UTC (Coordinated Universal Time) yang akurat. ESP32 kemudian menyimpan waktu tersebut dan menghitung waktu lokal berdasarkan selisih zona waktu (offset).
Beberapa NTP server publik yang umum digunakan:
| Server | Keterangan |
|---|---|
pool.ntp.org | Pool NTP global yang paling populer |
time.nist.gov | Server NTP milik NIST Amerika |
id.pool.ntp.org | Pool NTP regional Indonesia |
Instalasi Library NTPClient
Untuk tutorial ini kita menggunakan library NTPClient yang di-fork oleh Taranais — bukan library NTPClient default yang sudah ada di Library Manager, karena versi fork ini menyediakan fungsi getFormattedDate() yang sangat berguna.
Langkah instalasi:
- Download library dari: github.com/taranais/NTPClient/archive/master.zip
- Di Arduino IDE, pilih Sketch → Include Library → Add .ZIP Library
- Pilih file
.zipyang baru didownload - Restart Arduino IDE
Latihan 1: Menampilkan Tanggal dan Waktu di Serial Monitor
#include <WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
const char* ssid = "NAMA_WIFI_ANDA";
const char* password = "PASSWORD_WIFI_ANDA";
// Offset timezone dalam detik
// WIB (GMT+7) = 7 * 3600 = 25200
// WITA (GMT+8) = 8 * 3600 = 28800
// WIT (GMT+9) = 9 * 3600 = 32400
const long utcOffsetInSeconds = 25200; // WIB
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds);
String formattedDate;
String dayStamp;
String timeStamp;
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
Serial.print("Menghubungkan ke WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi terhubung. IP: " + WiFi.localIP().toString());
timeClient.begin();
}
void loop() {
// Pastikan waktu selalu terbarui
while (!timeClient.update()) {
timeClient.forceUpdate();
}
// getFormattedDate() mengembalikan format: 2026-05-03T08:30:45Z
formattedDate = timeClient.getFormattedDate();
// Pisahkan tanggal dan waktu berdasarkan karakter "T"
int splitT = formattedDate.indexOf("T");
dayStamp = formattedDate.substring(0, splitT);
timeStamp = formattedDate.substring(splitT + 1, formattedDate.length() - 1);
Serial.print("Tanggal: ");
Serial.print(dayStamp);
Serial.print(" | Waktu: ");
Serial.println(timeStamp);
delay(1000);
}
Output di Serial Monitor:
WiFi terhubung. IP: 192.168.1.105
Tanggal: 2026-05-03 | Waktu: 08:30:45
Tanggal: 2026-05-03 | Waktu: 08:30:46
Tanggal: 2026-05-03 | Waktu: 08:30:47Penjelasan Kode
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds) — membuat NTP client yang terhubung ke server pool.ntp.org dengan offset zona waktu yang sudah ditentukan.
timeClient.begin() — memulai koneksi NTP client.
timeClient.update() — memperbarui waktu dari server NTP. Mengembalikan true jika berhasil. Loop while (!timeClient.update()) dengan forceUpdate() memastikan kita selalu mendapatkan waktu yang valid, karena kadang request pertama bisa mengembalikan tahun 1970 (epoch time) jika koneksi belum stabil.
getFormattedDate() — mengembalikan timestamp dalam format ISO 8601: 2026-05-03T08:30:45Z. Kita memisahkan tanggal dan waktu menggunakan indexOf("T") sebagai titik pemisah.
Latihan 2: NTP + BMP280 — Data Sensor dengan Timestamp
Inilah penggunaan NTP yang paling praktis: menambahkan timestamp ke setiap pembacaan sensor.
#include <WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP280.h>
const char* ssid = "NAMA_WIFI_ANDA";
const char* password = "PASSWORD_WIFI_ANDA";
const long utcOffsetInSeconds = 25200; // WIB GMT+7
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds);
Adafruit_BMP280 bmp;
void setup() {
Serial.begin(115200);
if (!bmp.begin(0x76)) {
Serial.println("BMP280 tidak ditemukan!");
while (1);
}
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi OK.");
timeClient.begin();
}
void loop() {
while (!timeClient.update()) {
timeClient.forceUpdate();
}
String formattedDate = timeClient.getFormattedDate();
int splitT = formattedDate.indexOf("T");
String tanggal = formattedDate.substring(0, splitT);
String waktu = formattedDate.substring(splitT + 1, formattedDate.length() - 1);
float suhu = bmp.readTemperature();
float tekanan = bmp.readPressure() / 100.0F;
// Cetak dalam format CSV agar mudah disalin ke spreadsheet
Serial.print(tanggal);
Serial.print(",");
Serial.print(waktu);
Serial.print(",");
Serial.print(suhu, 2);
Serial.print(",");
Serial.println(tekanan, 2);
delay(10000); // Catat setiap 10 detik
}
Output Serial Monitor akan berbentuk CSV:
2026-05-03,08:30:00,28.45,1013.25
2026-05-03,08:30:10,28.47,1013.22
2026-05-03,08:30:20,28.50,1013.20Format CSV ini bisa langsung disalin ke Excel atau Google Sheets jika Anda hanya ingin logging sederhana melalui Serial Monitor. Untuk logging otomatis ke Google Sheets, kita akan bahas di Bagian 3.
Bagian 2: Chart Web Server — Grafik Real-Time di Browser
Ide Dasarnya
Pada artikel Belajar ESP32: Web Server, kita sudah membuat web server yang menampilkan angka sensor secara statis. Kali ini kita akan meningkatkannya: menampilkan grafik garis yang bergerak secara real-time langsung di browser, tanpa perlu aplikasi tambahan.
Teknik yang digunakan adalah AJAX polling — browser secara berkala mengirim request kecil ke ESP32 untuk meminta data terbaru, lalu menambahkannya ke grafik tanpa me-refresh seluruh halaman.
Library yang Digunakan
Kita menggunakan dua library penting di sisi ESP32:
- ESPAsyncWebServer — web server asinkron yang lebih efisien dari
WiFiServerbiasa. Mampu menangani beberapa request secara bersamaan tanpa blocking. - AsyncTCP — library TCP asinkron yang menjadi dependensi ESPAsyncWebServer.
Dan satu library JavaScript di sisi browser:
- Highcharts — library grafik JavaScript yang powerful dan mudah dikonfigurasi. Dimuat langsung dari CDN, jadi ESP32 tidak perlu menyimpannya.
Instalasi library:
- Buka Arduino IDE → Sketch → Include Library → Manage Libraries
- Cari dan install ESPAsyncWebServer (by ESP32Async)
- Install juga AsyncTCP (by ESP32Async) yang otomatis muncul sebagai dependensi
Struktur File Proyek
Berbeda dari artikel web server sebelumnya yang mengirim HTML langsung dari kode Arduino, kali ini kita menyimpan file HTML di LittleFS — filesystem internal ESP32. Ini membuat kode lebih rapi karena HTML tidak bercampur dengan logika program.
Struktur folder proyek:
ESP32_Chart_WebServer/
├── ESP32_Chart_WebServer.ino ← kode Arduino
└── data/
└── index.html ← file HTML dengan grafik HighchartsPenting: Folder
dataharus berada satu level dengan file.ino, persis seperti di atas.
Instalasi LittleFS Uploader
Untuk mengunggah folder data ke memori ESP32, kita membutuhkan plugin Arduino IDE:
- Download plugin dari: github.com/lorol/arduino-esp32fs-plugin
- Ekstrak dan letakkan file
.jardi foldertoolsdalam direktori Arduino IDE Anda - Restart Arduino IDE
File HTML: data/index.html
Buat file index.html di dalam folder data dengan konten berikut:
<!DOCTYPE HTML>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://code.highcharts.com/highcharts.js"></script>
<style>
body {
min-width: 320px;
max-width: 800px;
margin: 0 auto;
font-family: Arial, sans-serif;
background: #f5f5f5;
padding: 10px;
}
h2 {
text-align: center;
color: #333;
}
.container {
background: white;
border-radius: 8px;
padding: 10px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
</style>
</head>
<body>
<h2>🌦 Monitor Ruangan - ESP32</h2>
<div id="chart-temperature" class="container"></div>
<div id="chart-pressure" class="container"></div>
</body>
<script>
// ===========================
// Grafik Suhu
// ===========================
var chartSuhu = new Highcharts.Chart({
chart: { renderTo: 'chart-temperature' },
title: { text: 'Suhu Ruangan' },
series: [{
showInLegend: false,
data: []
}],
plotOptions: {
line: {
animation: false,
dataLabels: { enabled: true }
},
series: { color: '#e74c3c' }
},
xAxis: {
type: 'datetime',
dateTimeLabelFormats: { second: '%H:%M:%S' }
},
yAxis: {
title: { text: 'Suhu (°C)' }
},
credits: { enabled: false }
});
// Request data suhu setiap 10 detik
setInterval(function() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var x = (new Date()).getTime();
var y = parseFloat(this.responseText);
// Jaga maksimal 40 titik data di grafik
if (chartSuhu.series[0].data.length > 40) {
chartSuhu.series[0].addPoint([x, y], true, true, true);
} else {
chartSuhu.series[0].addPoint([x, y], true, false, true);
}
}
};
xhttp.open("GET", "/temperature", true);
xhttp.send();
}, 10000);
// ===========================
// Grafik Tekanan
// ===========================
var chartTekanan = new Highcharts.Chart({
chart: { renderTo: 'chart-pressure' },
title: { text: 'Tekanan Udara' },
series: [{
showInLegend: false,
data: []
}],
plotOptions: {
line: {
animation: false,
dataLabels: { enabled: true }
},
series: { color: '#3498db' }
},
xAxis: {
type: 'datetime',
dateTimeLabelFormats: { second: '%H:%M:%S' }
},
yAxis: {
title: { text: 'Tekanan (hPa)' }
},
credits: { enabled: false }
});
setInterval(function() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var x = (new Date()).getTime();
var y = parseFloat(this.responseText);
if (chartTekanan.series[0].data.length > 40) {
chartTekanan.series[0].addPoint([x, y], true, true, true);
} else {
chartTekanan.series[0].addPoint([x, y], true, false, true);
}
}
};
xhttp.open("GET", "/pressure", true);
xhttp.send();
}, 10000);
</script>
</html>
Kode Arduino: ESP32_Chart_WebServer.ino
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP280.h>
const char* ssid = "NAMA_WIFI_ANDA";
const char* password = "PASSWORD_WIFI_ANDA";
Adafruit_BMP280 bmp;
AsyncWebServer server(80);
String bacaSuhu() {
return String(bmp.readTemperature(), 2);
}
String bacaTekanan() {
return String(bmp.readPressure() / 100.0F, 2);
}
void setup() {
Serial.begin(115200);
// Inisialisasi LittleFS
if (!LittleFS.begin()) {
Serial.println("LittleFS gagal! Jalankan 'ESP32 Sketch Data Upload' terlebih dahulu.");
return;
}
Serial.println("LittleFS OK.");
// Inisialisasi BMP280
if (!bmp.begin(0x76)) {
Serial.println("BMP280 tidak ditemukan!");
while (1);
}
Serial.println("BMP280 OK.");
// Koneksi WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi terhubung. Buka browser: http://" + WiFi.localIP().toString());
// Route: halaman utama — kirim index.html dari LittleFS
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(LittleFS, "/index.html", "text/html");
});
// Route: data suhu (dipanggil oleh JavaScript di browser setiap 10 detik)
server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send_P(200, "text/plain", bacaSuhu().c_str());
});
// Route: data tekanan
server.on("/pressure", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send_P(200, "text/plain", bacaTekanan().c_str());
});
server.begin();
}
void loop() {
// ESPAsyncWebServer berjalan di background — loop() bisa kosong
}
Cara Upload dan Menjalankan
Langkah 1 — Upload HTML ke LittleFS:
- Pastikan folder
data/index.htmlsudah ada - Download file
.vsixdari github.com/earlephilhower/arduino-littlefs-upload/releases - Buka File Explorer, navigasi ke folder:
C:\Users\NAMA_USER_ANDA\.arduinoIDE\plugins\
Jika folderpluginsbelum ada, buat manual. - Copy file
.vsixlangsung ke folderpluginstersebut — tidak perlu diekstrak, biarkan tetap.vsix - Restart Arduino IDE sepenuhnya (tutup lalu buka lagi)
- Setelah restart, coba tekan
Ctrl+Shift+P→ ketik “Upload LittleFS” → seharusnya sudah muncul opsi “Upload LittleFS to Pico/ESP8266/ESP32” - Tunggu hingga proses selesai (ada output di console bawah)
LittleFS Filesystem Uploader v1.6.3 -- https://github.com/earlephilhower/arduino-littlefs-upload
Sketch Path: C:\Users\USER\Documents\Arduino\ESP32_Chart_WebServer
Data Path: C:\Users\USER\Documents\Arduino\ESP32_Chart_WebServer\data
Device: ESP32 series, model esp32
Using partition: default
Partitions: C:\Users\USER\AppData\Local\Arduino15\packages\esp32\hardware\esp32\3.3.8\tools\partitions\default.csv
Start: 0x290000
End: 0x3f0000
Serial Port: COM4
Building LittleFS filesystem
Command Line: C:\Users\USER\AppData\Local\Arduino15\packages\esp32\tools\mklittlefs\4.0.2-db0513a\mklittlefs.exe -c C:\Users\USER\Documents\Arduino\ESP32_Chart_WebServer\data -p 256 -b 4096 -s 1441792 C:\Users\USER\AppData\Local\Temp\tmp-22844-sEPs4l1Dyyif-.littlefs.bin
/index.html
Uploading LittleFS filesystem
Command Line: C:\Users\USER\AppData\Local\Arduino15\packages\esp32\tools\esptool_py\5.2.0\esptool.exe --chip esp32 --port COM4 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect 2686976 C:\Users\USER\AppData\Local\Temp\tmp-22844-sEPs4l1Dyyif-.littlefs.bin
Warning: Deprecated: Option '--flash_mode' is deprecated. Use '--flash-mode' instead.
Warning: Deprecated: Option '--flash_freq' is deprecated. Use '--flash-freq' instead.
Warning: Deprecated: Option '--flash_size' is deprecated. Use '--flash-size' instead.
Warning: Deprecated: Choice 'default_reset' for option '--before' is deprecated. Use 'default-reset' instead.
Warning: Deprecated: Choice 'hard_reset' for option '--after' is deprecated. Use 'hard-reset' instead.
Warning: Deprecated: Command 'write_flash' is deprecated. Use 'write-flash' instead.
esptool v5.2.0
Serial port COM4:
Connecting.....
Connected to ESP32 on COM4:
Chip type: ESP32-D0WDQ6 (revision v1.0)
Features: Wi-Fi, BT, Dual Core + LP Core, 240MHz, Vref calibration in eFuse, Coding Scheme None
Crystal frequency: 40MHz
MAC: 7c:9e:bd:f9:e3:c0
Uploading stub flasher...
Running stub flasher...
Stub flasher running.
Changing baud rate to 921600...
Changed.
Configuring flash size...
Auto-detected flash size: 4MB
Flash will be erased from 0x00290000 to 0x003effff...
Compressed 1441792 bytes to 2889...
Writing at 0x00290000 [ ] 0.0% 0/2889 bytes...
Writing at 0x003f0000 [==============================] 100.0% 2889/2889 bytes...
Wrote 1441792 bytes (2889 compressed) at 0x00290000 in 3.6 seconds (3170.9 kbit/s).
Verifying written data...
Hash of data verified.
Hard resetting via RTS pin...
Completed upload.Langkah 2 — Upload kode Arduino:
- Upload sketch
.inoseperti biasa (tombol Upload) - Buka Serial Monitor untuk melihat alamat IP
Langkah 3 — Buka di browser:
- Ketik alamat IP ESP32 di browser
- Grafik akan muncul kosong, lalu mulai terisi setiap 10 detik seiring data baru masuk

Penjelasan Mekanisme AJAX
Highcharts membuat objek grafik di browser. Kemudian setInterval() JavaScript menjalankan fungsi setiap 10.000ms (10 detik):
xhttp.open("GET", "/temperature", true);
xhttp.send();Browser mengirim request GET ke URL /temperature pada ESP32. ESP32 merespons hanya dengan angka mentah, misalnya 28.45. Browser menerima angka ini dan menambahkannya sebagai titik data baru ke grafik menggunakan addPoint().
Parameter addPoint([x, y], redraw, shift, animation):
x— timestamp saat ini dalam millisecondsy— nilai sensorredraw—trueagar grafik langsung digambar ulangshift—truejika jumlah titik sudah lebih dari 40, agar titik tertua dihapus (grafik bergeser seperti scrolling)
ESPAsyncWebServer vs WiFiServer
Mengapa kita beralih ke ESPAsyncWebServer? Di artikel Web Server sebelumnya, kita menggunakan WiFiServer yang bersifat synchronous — ia memblokir eksekusi program saat melayani request. ESPAsyncWebServer bersifat asynchronous — request dilayani di background menggunakan interrupt dan callback, sehingga loop() tetap bebas menjalankan logika lain.
Ini sangat penting untuk chart server karena browser akan mengirim banyak request secara bersamaan (satu untuk suhu, satu untuk tekanan), dan kita perlu melayani semuanya tanpa ada yang tertunda.
Bagian 3: Google Sheets Logger — Catat Data Sensor Secara Otomatis
Mengapa Google Sheets?
Grafik real-time di bagian sebelumnya menampilkan data yang hilang begitu browser ditutup atau ESP32 di-restart — tidak ada riwayat yang tersimpan. Untuk analisis jangka panjang, kita perlu menyimpan data ke media yang persisten.
Google Sheets adalah pilihan yang sangat praktis karena:
- Gratis dan bisa diakses dari mana saja
- Mendukung pembuatan grafik dan pivot table
- Mudah dibagikan dengan anggota tim
- Data bisa dianalisis lebih lanjut dengan Google Looker Studio
Untuk menghubungkan ESP32 ke Google Sheets, kita menggunakan Google Apps Script — layanan scripting bawaan Google yang sepenuhnya gratis. Cara kerjanya sederhana: kita tulis sebuah script di Google yang berfungsi sebagai “pintu masuk” web, lalu ESP32 mengirimkan data ke pintu tersebut melalui HTTPS request.
Alur Sistem
ESP32 (baca sensor + timestamp NTP)
│
│ HTTPS GET request
▼
Google Apps Script Web App
│
│ appendRow()
▼
Google Sheets
│ (baris baru: Timestamp | Suhu | Tekanan)
▼
Tersimpan permanen di Google Drive AndaKeunggulan pendekatan ini dibanding layanan pihak ketiga: tidak ada perantara, tidak ada batasan request, tidak ada biaya berlangganan — semua infrastruktur berjalan di dalam ekosistem Google sendiri.
Langkah 1: Buat Google Spreadsheet
- Buka sheets.google.com dan buat spreadsheet baru
- Beri nama spreadsheet:
BMP280_DataLogger - Isi baris pertama sebagai header kolom:
- A1:
Timestamp - B1:
Suhu (°C) - C1:
Tekanan (hPa)
- A1:
- Catat nama sheet di tab bawah (biasanya
Sheet1) & Spreadsheet ID — kita akan menggunakannya di script
Langkah 2: Buat Google Apps Script
- Di spreadsheet yang baru dibuat, klik menu Extensions → Apps Script
- Tab baru akan terbuka dengan editor script. Jika ada error bisa melakukannya secara manual dengan mengakses alamat https://script.google.com/ lalu klik CREATE APPS SCRIPT.
- Hapus semua kode yang ada, lalu copy-paste script berikut dan ganti bagian SPREADSHEET_ID:
function doGet(e) {
try {
var spreadsheet = SpreadsheetApp.openById("SPREADSHEET_ID");
var sheet = spreadsheet.getSheetByName('Monitoring_Ruangan');
// Ambil parameter dari URL request
var timestamp = e.parameter.timestamp || new Date().toISOString();
var suhu = e.parameter.suhu || "";
var tekanan = e.parameter.tekanan || "";
// Tambahkan baris baru ke bawah data yang ada
sheet.appendRow([timestamp, suhu, tekanan]);
// Kembalikan respons sukses ke ESP32
return ContentService
.createTextOutput("OK: Data berhasil disimpan")
.setMimeType(ContentService.MimeType.TEXT);
} catch (error) {
// Kembalikan pesan error jika terjadi masalah
return ContentService
.createTextOutput("ERROR: " + error.toString())
.setMimeType(ContentService.MimeType.TEXT);
}
}- Klik ikon Save (atau
Ctrl+S), beri nama project:ESP32Logger
Langkah 3: Deploy Script sebagai Web App
Ini adalah langkah terpenting — kita mempublikasikan script agar bisa diakses melalui URL dari luar.
- Klik tombol Deploy (pojok kanan atas) → New deployment
- Klik ikon gear ⚙ di sebelah “Select type” → pilih Web app
- Isi konfigurasi:
- Description:
ESP32 Data Logger - Execute as:
Me(script berjalan dengan hak akses akun Google Anda) - Who has access:
Anyone(agar ESP32 bisa mengaksesnya tanpa login)
- Description:
- Klik Deploy
- Jika diminta, klik Authorize access → pilih akun Google Anda → klik Allow
- Setelah selesai, Anda akan mendapatkan Web App URL yang terlihat seperti ini:
https://script.google.com/macros/s/AKfycbxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/execSalin URL ini — inilah “alamat” yang akan dipanggil oleh ESP32.

Penting: Setiap kali Anda mengubah kode script, Anda harus membuat deployment baru (Deploy → New deployment) agar perubahan berlaku. Deployment lama tetap menggunakan versi kode yang lama.
Langkah 4: Uji Coba Script
Sebelum menghubungkan ESP32, pastikan script bekerja dengan mengujinya dari browser terlebih dahulu. Buka URL berikut di browser (ganti URL dengan milik Anda):
https://script.google.com/macros/s/AKfycbXXXXXXXXX/exec?timestamp=2026-05-03+10:00:00&suhu=28.5&tekanan=1013.25Jika berhasil, browser menampilkan teks OK: Data berhasil disimpan dan spreadsheet Anda akan terisi satu baris baru:
| Timestamp | Suhu (°C) | Tekanan (hPa) |
|---|---|---|
| 2026-05-03 10:00:00 | 28.5 | 1013.25 |
Langkah 5: Kode ESP32 — Logger ke Google Sheets
Karena Google Apps Script menggunakan HTTPS (bukan HTTP biasa), kita memerlukan WiFiClientSecure untuk koneksi terenkripsi.
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP280.h>
const char* ssid = "NAMA_WIFI_ANDA";
const char* password = "PASSWORD_WIFI_ANDA";
// ============================================
// Konfigurasi Google Apps Script
// ============================================
const char* gasHost = "script.google.com";
// Ganti dengan Web App URL milik Anda (bagian setelah https://script.google.com)
const String gasPath = "/macros/s/AKfycbXXXXXXXXXXXXXXXXXX/exec";
// ============================================
// Konfigurasi NTP (WIB = GMT+7)
// ============================================
const long utcOffset = 25200;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffset);
Adafruit_BMP280 bmp;
// Interval logging — ubah sesuai kebutuhan
// 60000 = 1 menit (untuk pengujian)
// 1800000 = 30 menit (untuk produksi)
const unsigned long LOG_INTERVAL = 60000;
unsigned long lastLog = 0;
// ============================================
// Fungsi: URL encode spasi menjadi %20
// ============================================
String urlEncode(String str) {
str.replace(" ", "%20");
str.replace(":", "%3A");
return str;
}
// ============================================
// Fungsi: Kirim data ke Google Sheets via GAS
// ============================================
void kirimKeGoogleSheets(String timestamp, float suhu, float tekanan) {
WiFiClientSecure client;
client.setInsecure(); // Skip verifikasi sertifikat (cukup untuk pengembangan)
Serial.print("Menghubungi Google Apps Script...");
if (!client.connect(gasHost, 443)) {
Serial.println("Gagal terhubung!");
return;
}
// Bangun query string
String url = gasPath;
url += "?timestamp=" + urlEncode(timestamp);
url += "&suhu=" + String(suhu, 2);
url += "&tekanan=" + String(tekanan, 2);
// Kirim HTTP GET request
client.println("GET " + url + " HTTP/1.1");
client.println("Host: " + String(gasHost));
client.println("Connection: close");
client.println();
// Baca respons — GAS sering melakukan redirect (302), kita cukup baca status awalnya
String statusLine = client.readStringUntil('\n');
client.stop();
Serial.println("Selesai.");
Serial.print("[LOG] ");
Serial.print(timestamp);
Serial.print(" | Suhu: ");
Serial.print(suhu, 2);
Serial.print(" °C | Tekanan: ");
Serial.print(tekanan, 2);
Serial.println(" hPa");
}
// ============================================
// SETUP
// ============================================
void setup() {
Serial.begin(115200);
if (!bmp.begin(0x76)) {
Serial.println("BMP280 tidak ditemukan!");
while (1);
}
Serial.println("BMP280 OK.");
WiFi.begin(ssid, password);
Serial.print("Menghubungkan ke WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi terhubung. IP: " + WiFi.localIP().toString());
timeClient.begin();
// Mulai langsung saat boot
lastLog = millis() - LOG_INTERVAL;
}
// ============================================
// LOOP
// ============================================
void loop() {
while (!timeClient.update()) {
timeClient.forceUpdate();
}
if (millis() - lastLog >= LOG_INTERVAL) {
lastLog = millis();
// Ambil timestamp dari NTP
String formattedDate = timeClient.getFormattedDate();
int splitT = formattedDate.indexOf("T");
String tanggal = formattedDate.substring(0, splitT);
String waktu = formattedDate.substring(splitT + 1, formattedDate.length() - 1);
String timestamp = tanggal + " " + waktu;
// Baca sensor
float suhu = bmp.readTemperature();
float tekanan = bmp.readPressure() / 100.0F;
// Kirim ke Google Sheets
kirimKeGoogleSheets(timestamp, suhu, tekanan);
}
}10:40:32.485 -> Menghubungi Google Apps Script...Selesai.
10:40:34.346 -> [LOG] 2026-05-03 10:40:32 | Suhu: 31.90 °C | Tekanan: 937.83 hPa
10:41:32.594 -> Menghubungi Google Apps Script...Selesai.
10:41:34.475 -> [LOG] 2026-05-03 10:41:32 | Suhu: 31.90 °C | Tekanan: 937.81 hPa
10:42:32.570 -> Menghubungi Google Apps Script...Selesai.
10:42:34.440 -> [LOG] 2026-05-03 10:42:32 | Suhu: 31.95 °C | Tekanan: 937.80 hPaCatatan tentang Redirect GAS
Google Apps Script secara default melakukan redirect HTTP 302 saat Web App dipanggil — artinya respons pertama adalah “pergi ke URL ini”, bukan langsung isi data. Ini adalah perilaku normal dari Google. Data tetap tertulis ke Sheets karena script sudah dieksekusi sebelum redirect dikirim. Anda bisa mengabaikan status respons 302 — yang penting data muncul di spreadsheet.
Hasil di Google Sheets
Setiap interval logging, satu baris baru akan ditambahkan secara otomatis:

Visualisasi Data di Google Sheets
Setelah data terkumpul beberapa baris, Anda bisa membuat grafik langsung:
- Pilih kolom Timestamp dan Suhu (°C) serta Timestamp dan Tekanan (hPa)
- Klik Insert → Chart
- Pilih Line chart
- Di Chart Editor, pastikan sumbu X diatur ke kolom Timestamp

Google Sheets juga mendukung Looker Studio (dulu Google Data Studio) untuk visualisasi yang lebih advanced seperti gauge, scorecard, dan grafik interaktif — cukup sambungkan ke spreadsheet sebagai data source secara gratis.
Proyek Gabungan: Logger + Chart + Timestamp
Sebagai penutup, berikut adalah gambaran bagaimana ketiga bagian ini bisa dikombinasikan menjadi satu sistem yang lengkap:
| Fitur | Teknologi | Keterangan |
|---|---|---|
| Timestamping data | NTP Client | Waktu otomatis dari internet, tidak perlu RTC |
| Monitoring real-time | Chart Web Server + Highcharts | Grafik langsung di browser, update setiap 10 detik |
| Penyimpanan historis | Google Apps Script + Google Sheets | Data tersimpan permanen, bisa dianalisis kapanpun |
Dalam sistem nyata, ketiga fitur ini bisa dijalankan bersamaan di satu ESP32: web server melayani grafik real-time, sementara setiap 30 menit data dikirim ke Google Sheets dengan timestamp yang akurat dari NTP.
Kesimpulan
Di artikel ini kita telah membahas tiga dimensi berbeda dari visualisasi dan pencatatan data IoT:
NTP Client memberikan konteks waktu yang akurat pada setiap data sensor — fondasi penting untuk semua bentuk logging dan analisis historis. Selama ESP32 terhubung ke internet, ia bisa mendapatkan waktu yang presisi tanpa hardware RTC tambahan.
Chart Web Server mengubah data sensor mentah menjadi grafik visual yang mengalir secara real-time di browser. Dengan kombinasi ESPAsyncWebServer di sisi ESP32 dan Highcharts di sisi browser, grafik bisa diakses dari perangkat apapun di jaringan lokal tanpa instalasi apapun.
Google Sheets Logger menjadi solusi pencatatan data jangka panjang yang gratis, mudah diakses, dan siap untuk analisis lebih lanjut. Melalui Google Apps Script sebagai perantara, ESP32 cukup mengirimkan HTTPS request ke Web App URL untuk menambah baris data baru ke spreadsheet — tanpa layanan pihak ketiga, tanpa biaya berlangganan.
Referensi
- https://randomnerdtutorials.com/esp32-ntp-client-date-time-arduino-ide/
- https://randomnerdtutorials.com/esp32-esp8266-plot-chart-web-server/
- https://randomnerdtutorials.com/esp32-esp8266-publish-sensor-readings-to-google-sheets/
- https://github.com/taranais/NTPClient
- https://www.highcharts.com/docs/
- https://developers.google.com/apps-script/guides/web

