#include <WiFi.h>
#include <time.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// ===== KONFIGURASI JARINGAN =====
// WiFi.begin("Wokwi-GUEST", "", 6);
const char* ssid = "Wokwi-GUEST";
const char* password = "";
char buffer[50];
// ===== KONFIGURASI NTP =====
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 7 * 3600; // GMT+7 (WIB)
const int daylightOffset_sec = 0; // Tidak menggunakan daylight saving
// ===== KONFIGURASI LCD I2C =====
// Alamat I2C LCD 1602 biasanya 0x27 atau 0x3F
#define I2C_ADDR 0x27
#define LCD_COLUMNS 20
#define LCD_ROWS 4
// Inisialisasi objek LCD
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_ROWS);
// ===== VARIABEL UNTUK FORMAT WAKTU =====
struct tm timeinfo;
char timeString[9]; // Untuk menyimpan format HH:MM:SS
char dateString[11]; // Untuk menyimpan format DD/MM/YYYY
char dayString[10]; // Untuk menyimpan nama hari
// ===== DAFTAR NAMA HARI =====
const char* dayNames[] = {
"Minggu", "Senin", "Selasa", "Rabu",
"Kamis", "Jumat", "Sabtu"
};
// ===== DAFTAR TIMEZONE =====
// Anda bisa menambahkan timezone lain sesuai kebutuhan
typedef struct {
const char* name;
long offset_sec;
const char* ntpServer;
} TimeZone;
TimeZone timeZones[] = {
{"WIB (GMT+7)", 7 * 3600, "id.pool.ntp.org"},
{"WITA (GMT+8)", 8 * 3600, "id.pool.ntp.org"},
{"WIT (GMT+9)", 9 * 3600, "id.pool.ntp.org"},
{"Tokyo (GMT+9)", 9 * 3600, "jp.pool.ntp.org"},
{"London (GMT+0)", 0, "uk.pool.ntp.org"},
{"NYC (GMT-5)", -5 * 3600, "us.pool.ntp.org"}
};
int currentTimezone = 0; // Indeks timezone awal (WIB)
// ===== VARIABEL UNTUK DEBOUNCE BUTTON =====
const int buttonPin = 13; // Pin untuk tombol ganti timezone
unsigned long lastButtonPress = 0;
const unsigned long debounceDelay = 300; // ms
class KalenderJawa {
private:
// Tanggal referensi
struct Referensi {
int year = 2023;
int month = 1;
int day = 1;
int pasaranIndex = 0; // Legi
} ref;
// Nama-nama
const char* pasaranNames[5] = { "Pahing", "Pon", "Wage", "Kliwon","Legi"};
const char* hariNames[7] = {"Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu"};
// Neptu
int neptuPasaran[5] = {5, 9, 7, 4, 8};
int neptuHari[7] = {5, 4, 3, 7, 8, 6, 9};
public:
// Constructor dengan referensi kustom
KalenderJawa(int refYear = 2023, int refMonth = 1, int refDay = 1, int refPasaran = 0) {
ref.year = refYear;
ref.month = refMonth;
ref.day = refDay;
ref.pasaranIndex = refPasaran;
}
/**
* Metode utama: Hitung pasaran Jawa
*/
String hitungPasaran(int year, int month, int day) {
long selisih = hitungSelisihHari(year, month, day, ref.year, ref.month, ref.day);
int index = (ref.pasaranIndex + selisih) % 5;
if (index < 0) index += 5;
return String(pasaranNames[index]);
}
/**
* Metode: Dapatkan informasi lengkap
*/
void getInfoLengkap(int year, int month, int day,
String &pasaran, int &neptuPasaran,
String &hari, int &neptuHari, int &totalNeptu) {
// Hitung pasaran
pasaran = hitungPasaran(year, month, day);
// Hitung indeks pasaran
int idxPasaran = getIndexPasaran(pasaran);
neptuPasaran = this->neptuPasaran[idxPasaran];
// Hitung hari Masehi
hari = getHariMasehi(year, month, day);
// Hitung indeks hari
int idxHari = getIndexHari(hari);
neptuHari = this->neptuHari[idxHari];
// Total neptu
totalNeptu = neptuPasaran + neptuHari;
}
/**
* Metode: Validasi pasaran untuk tanggal tertentu
* Contoh: 17 Agustus 1945 = Jumat Legi
*/
bool validasiPasaran(int year, int month, int day, String pasaranYangDiharapkan) {
String hasil = hitungPasaran(year, month, day);
return (hasil == pasaranYangDiharapkan);
}
private:
/**
* Fungsi internal: Hitung selisih hari
*/
long hitungSelisihHari(int y1, int m1, int d1, int y2, int m2, int d2) {
return dateToJDN(y1, m1, d1) - dateToJDN(y2, m2, d2);
}
/**
* Fungsi internal: Julian Day Number
*/
long dateToJDN(int year, int month, int day) {
if (month <= 2) {
year--;
month += 12;
}
long a = year / 100;
long b = a / 4;
long c = 2 - a + b;
long e = 365.25 * (year + 4716);
long f = 30.6001 * (month + 1);
return c + day + e + f - 1524;
}
/**
* Fungsi internal: Dapatkan hari Masehi
*/
String getHariMasehi(int year, int month, int day) {
if (month < 3) {
month += 12;
year--;
}
int k = year % 100;
int j = year / 100;
int h = (day + (13 * (month + 1)) / 5 + k + (k / 4) + (j / 4) + 5 * j) % 7;
return String(hariNames[h]);
}
/**
* Fungsi internal: Dapatkan indeks pasaran
*/
int getIndexPasaran(String pasaran) {
for (int i = 0; i < 5; i++) {
if (pasaran == pasaranNames[i]) return i;
}
return -1;
}
/**
* Fungsi internal: Dapatkan indeks hari
*/
int getIndexHari(String hari) {
for (int i = 0; i < 7; i++) {
if (hari == hariNames[i]) return i;
}
return -1;
}
};
KalenderJawa tglJawa;
void setup() {
Serial.begin(115200);
// Inisialisasi pin button
pinMode(buttonPin, INPUT_PULLUP);
// Inisialisasi LCD
Wire.begin();
lcd.init();
lcd.backlight();
lcd.clear();
// Tampilkan pesan awal
lcd.setCursor(0, 0);
lcd.print("Initializing...");
// Koneksi ke WiFi
connectToWiFi();
// Konfigurasi waktu dengan timezone default
configTime(timeZones[currentTimezone].offset_sec,
daylightOffset_sec,
timeZones[currentTimezone].ntpServer);
// Tunggu hingga waktu tersinkronisasi
waitForTimeSync();
// Tampilkan pesan berhasil
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("NTP Sync OK!");
delay(2000);
}
void loop() {
// Cek jika tombol ditekan untuk ganti timezone
if (digitalRead(buttonPin) == LOW &&
millis() - lastButtonPress > debounceDelay) {
changeTimezone();
lastButtonPress = millis();
}
// Ambil waktu saat ini
if (getLocalTime(&timeinfo)) {
// Format waktu menjadi string
formatTime();
// Tampilkan pada LCD
updateDisplay();
} else {
Serial.println("Gagal mendapatkan waktu");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Time Error!");
}
delay(500); // Update setiap 500ms
}
// ===== FUNGSI KONEKSI WIFI =====
void connectToWiFi() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Connecting to");
lcd.setCursor(0, 1);
lcd.print(ssid);
WiFi.begin(ssid, password);
Serial.print("Menghubungkan ke WiFi");
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nTerhubung ke WiFi!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi Connected");
lcd.setCursor(0, 1);
lcd.print("IP: ");
lcd.print(WiFi.localIP().toString().substring(0, 12));
delay(2000);
} else {
Serial.println("\nGagal terhubung ke WiFi!");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi Failed!");
lcd.setCursor(0, 1);
lcd.print("Check SSID/PW");
// Jika gagal, coba lagi setelah 10 detik
delay(10000);
ESP.restart();
}
}
// ===== FUNGSI TUNGGU SINKRONISASI WAKTU =====
void waitForTimeSync() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Syncing time...");
Serial.print("Menunggu sinkronisasi waktu");
int attempts = 0;
while (!getLocalTime(&timeinfo) && attempts < 20) {
Serial.print(".");
lcd.setCursor(attempts % 16, 1);
lcd.print(".");
attempts++;
delay(500);
}
if (attempts >= 20) {
Serial.println("\nGagal sinkronisasi waktu!");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Time sync fail!");
} else {
Serial.println("\nWaktu tersinkronisasi!");
}
}
// ===== FUNGSI FORMAT WAKTU =====
void formatTime() {
// Format jam: HH:MM:SS
strftime(timeString, sizeof(timeString), "%H:%M:%S", &timeinfo);
// Format tanggal: DD/MM/YYYY
strftime(dateString, sizeof(dateString), "%d/%m/%Y", &timeinfo);
// Format nama hari
int dayOfWeek = timeinfo.tm_wday;
strcpy(dayString, dayNames[dayOfWeek]);
}
// ===== FUNGSI UPDATE TAMPILAN LCD =====
void updateDisplay() {
static unsigned long lastUpdate = 0;
// Update LCD setiap 1 detik (untuk efek kedip pada detik)
if (millis() - lastUpdate >= 1000) {
// Baris 1: Nama hari dan tanggal
lcd.setCursor(0, 0);
lcd.print(dayString);
//Serial.println(dayString);
// Hitung posisi untuk tanggal (rata kanan)
int dateLength = strlen(dateString);
int datePos = LCD_COLUMNS - dateLength;
lcd.setCursor(datePos, 0);
lcd.print(dateString);
//Serial.println(dateString );
//String Tulisan = str
// Baris 2: Waktu dan timezone
lcd.setCursor(0, 1);
lcd.print(timeString);
//Serial.println(timeString);
// Tampilkan singkatan timezone
lcd.setCursor(11, 1);
lcd.print(timeZones[currentTimezone].name);
//Serial.println(timeZones[currentTimezone].name);
int tahun = timeinfo.tm_year + 1900;
int bulan = timeinfo.tm_mon + 1; // 0-11 → 1-12
int hari = timeinfo.tm_mday; // 1-31
// int jam = timeinfo.tm_hour; // 0-23
// int menit = timeinfo.tm_min; // 0-59
// int detik = timeinfo.tm_sec; // 0-59
String pasarannya = tglJawa.hitungPasaran(tahun, bulan, hari);
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, "Tanggal : %s, Hari : %s %s, Jam : %s, TimeZone : %s",
dateString,dayString,pasarannya,timeString,timeZones[currentTimezone].name);
Serial.println(buffer);
// Hapus sisa karakter di baris 2
for (int i = strlen(timeString) + strlen(timeZones[currentTimezone].name); i < LCD_COLUMNS; i++) {
lcd.print(" ");
}
lastUpdate = millis();
}
}
// ===== FUNGSI GANTI TIMEZONE =====
void changeTimezone() {
// Pindah ke timezone berikutnya
currentTimezone = (currentTimezone + 1) % (sizeof(timeZones) / sizeof(timeZones[0]));
// Konfigurasi ulang waktu dengan timezone baru
configTime(timeZones[currentTimezone].offset_sec,
daylightOffset_sec,
timeZones[currentTimezone].ntpServer);
// Tunggu sinkronisasi
waitForTimeSync();
// Tampilkan notifikasi
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Timezone changed");
lcd.setCursor(0, 1);
lcd.print(timeZones[currentTimezone].name);
delay(1500);
}
// ===== FUNGSI TAMPILKAN INFO TIMEZONE DI SERIAL =====
void printTimezoneInfo() {
Serial.println("\n=== Daftar Timezone ===");
for (int i = 0; i < sizeof(timeZones) / sizeof(timeZones[0]); i++) {
Serial.printf("%d. %s (Offset: %ld sec)\n",
i,
timeZones[i].name,
timeZones[i].offset_sec);
}
Serial.println("=====================\n");
}