// ============================================
// KODE C - ESP8266 DISPLAY MONITOR
// Versi: 4.1 - Layout Baru (Icon Kiri, Rank Kanan)
// ============================================
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ArduinoJson.h>
// ============================================
// KONFIGURASI LCD
// ============================================
#define LCD_COLUMNS 16
#define LCD_ROWS 2
#define LCD_ADDRESS 0x27
LiquidCrystal_I2C lcd(LCD_ADDRESS, LCD_COLUMNS, LCD_ROWS);
// ============================================
// KONFIGURASI WIFI
// ============================================
const char* ssid = "Galapagos";
const char* password = "@Galapagos123";
// ============================================
// KONFIGURASI API
// ============================================
const char* baseUrl = "http://zidcreative.com/sishopint/api";
const char* getAllPoinUrl = "/get_all_peserta.php";
// ============================================
// KONFIGURASI UPDATE & SCROLL
// ============================================
const unsigned long UPDATE_INTERVAL = 300000; // 5 MENIT
const unsigned long SCROLL_INTERVAL = 2500; // 2.5 DETIK
// ============================================
// STRUKTUR DATA
// ============================================
#define MAX_PESERTA 150
struct Peserta {
int id;
String nama;
int totalPoin;
int poinSholat;
int poinKegiatan;
int poinHariIni;
};
Peserta daftarPeserta[MAX_PESERTA];
int jumlahPeserta = 0;
int currentDisplayIndex = 0;
int maxPoin = 0;
// ============================================
// VARIABEL GLOBAL
// ============================================
bool wifiConnected = false;
bool isUpdating = false;
unsigned long lastUpdate = 0;
unsigned long lastScroll = 0;
String statusMessage = "Siap";
unsigned long lastDisplayRefresh = 0;
// ============================================
// FUNGSI UTILITY LCD
// ============================================
void lcdPrintRight(int row, String text) {
int padding = 16 - text.length();
if (padding < 0) padding = 0;
lcd.setCursor(padding, row);
lcd.print(text);
}
String truncateText(String text, int maxLen = 10) {
if (text.length() <= maxLen) return text;
return text.substring(0, maxLen - 3) + "...";
}
// ============================================
// FUNGSI EKSPRESI BERDASARKAN PERSENTASE
// ============================================
String getExpressionIcon(int poin, int maxPoin) {
if (maxPoin == 0) return ":|";
float percentage = ((float)poin / maxPoin) * 100.0;
// ============================================
// KARAKTER ASCII UNTUK LCD 16x2
// ============================================
if (percentage >= 90.0) {
return ":)"; // Senyum lebar
} else if (percentage >= 70.0) {
return ":)"; // Senyum
} else if (percentage >= 40.0) {
return ":|"; // Muka datar
} else if (percentage >= 20.0) {
return ":("; // Sedih
} else {
return ":'("; // Menangis
}
}
// ============================================
// FUNGSI KONEKSI WIFI
// ============================================
void connectWiFi() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Menghubungkan...");
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
lcd.setCursor(0, 1);
lcd.print(".");
for(int i = 0; i < attempts % 10; i++) lcd.print(".");
attempts++;
Serial.print(".");
}
if (WiFi.status() == WL_CONNECTED) {
wifiConnected = true;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi OK!");
lcd.setCursor(0, 1);
lcd.print(WiFi.localIP().toString());
Serial.println("\nWiFi Connected!");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
delay(1500);
} else {
wifiConnected = false;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi GAGAL!");
lcd.setCursor(0, 1);
lcd.print("Mode Offline");
Serial.println("\nWiFi Failed!");
delay(2000);
}
}
void checkWiFi() {
if (WiFi.status() == WL_CONNECTED) {
if (!wifiConnected) {
wifiConnected = true;
Serial.println("WiFi Reconnected");
}
} else {
if (wifiConnected) {
wifiConnected = false;
Serial.println("WiFi Disconnected");
showErrorQuick("WiFi", "Putus");
}
}
}
// ============================================
// FUNGSI FETCH DATA
// ============================================
bool fetchData() {
if (WiFi.status() != WL_CONNECTED) {
return false;
}
if (isUpdating) {
Serial.println("Update already in progress...");
return false;
}
isUpdating = true;
Serial.println("=== START FETCH DATA ===");
String url = String(baseUrl) + getAllPoinUrl;
Serial.print("Fetching: ");
Serial.println(url);
WiFiClient client;
HTTPClient http;
http.begin(client, url);
http.addHeader("User-Agent", "ESP8266-HTTPClient/1.0");
http.addHeader("Accept", "application/json");
http.setTimeout(15000);
int httpCode = http.GET();
Serial.print("HTTP Code: ");
Serial.println(httpCode);
bool success = false;
if (httpCode == 200) {
String response = http.getString();
http.end();
if (response.length() > 0) {
success = parseDataV7(response);
}
} else {
Serial.print("HTTP Error: ");
Serial.println(httpCode);
http.end();
}
isUpdating = false;
Serial.println("=== FETCH COMPLETE ===");
return success;
}
// ============================================
// FUNGSI PARSE DATA
// ============================================
bool parseDataV7(String json) {
Serial.println("=== Parsing JSON ===");
JsonDocument doc;
DeserializationError error = deserializeJson(doc, json);
if (error) {
Serial.print("JSON Error: ");
Serial.println(error.c_str());
return false;
}
bool success = doc["success"].as<bool>();
if (!success) {
Serial.println("API Error: " + doc["message"].as<String>());
return false;
}
JsonArray dataArray = doc["data"].as<JsonArray>();
jumlahPeserta = 0;
maxPoin = 0;
for (JsonObject obj : dataArray) {
if (jumlahPeserta >= MAX_PESERTA) break;
const char* namaPtr = obj["nama"].as<const char*>();
String nama = namaPtr ? String(namaPtr) : "";
if (nama.length() > 0) {
daftarPeserta[jumlahPeserta].id = obj["peserta_id"].as<int>();
daftarPeserta[jumlahPeserta].nama = nama;
daftarPeserta[jumlahPeserta].totalPoin = obj["total_poin"].as<int>();
daftarPeserta[jumlahPeserta].poinSholat = obj["poin_sholat"].as<int>();
daftarPeserta[jumlahPeserta].poinKegiatan = obj["poin_kegiatan"].as<int>();
daftarPeserta[jumlahPeserta].poinHariIni = obj["poin_hari_ini"].as<int>();
if (daftarPeserta[jumlahPeserta].totalPoin > maxPoin) {
maxPoin = daftarPeserta[jumlahPeserta].totalPoin;
}
jumlahPeserta++;
}
}
Serial.print("Total peserta dimuat: ");
Serial.println(jumlahPeserta);
Serial.print("Max Poin: ");
Serial.println(maxPoin);
if (jumlahPeserta == 0) {
return false;
}
sortPesertaByPoin();
currentDisplayIndex = 0;
lastScroll = millis();
statusMessage = String(jumlahPeserta) + " Peserta";
return true;
}
// ============================================
// FUNGSI SORTING
// ============================================
void sortPesertaByPoin() {
for (int i = 0; i < jumlahPeserta - 1; i++) {
for (int j = 0; j < jumlahPeserta - i - 1; j++) {
if (daftarPeserta[j].totalPoin < daftarPeserta[j + 1].totalPoin) {
Peserta temp = daftarPeserta[j];
daftarPeserta[j] = daftarPeserta[j + 1];
daftarPeserta[j + 1] = temp;
}
}
}
}
// ============================================
// FUNGSI DISPLAY - LAYOUT BARU
// ============================================
void displayPeserta(int index) {
if (jumlahPeserta == 0) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Belum ada data");
lcd.setCursor(0, 1);
lcd.print("Tunggu update...");
return;
}
if (index >= jumlahPeserta) {
index = 0;
currentDisplayIndex = 0;
}
Peserta p = daftarPeserta[index];
lcd.clear();
// ============================================
// BARIS 1: Nama (kiri) + Total Poin (kanan)
// ============================================
String namaDisplay = truncateText(p.nama, 10);
lcd.setCursor(0, 0);
lcd.print(namaDisplay);
String poinText = String(p.totalPoin);
lcdPrintRight(0, poinText);
// ============================================
// BARIS 2: Icon (kiri) + Peringkat (kanan)
// ============================================
// Icon ekspresi di kiri (di bawah nama)
String icon = getExpressionIcon(p.totalPoin, maxPoin);
lcd.setCursor(0, 1);
lcd.print(icon);
// Peringkat di kanan (di bawah poin)
String rankText = String(index + 1) + "/" + String(jumlahPeserta);
lcdPrintRight(1, rankText);
}
// ============================================
// FUNGSI TAMPILAN LAINNYA
// ============================================
void showSplashScreen() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Monitor Poin");
lcd.setCursor(0, 1);
lcd.print("v4.1");
delay(1500);
}
void showDataLoaded(String info) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Data Dimuat!");
lcd.setCursor(0, 1);
lcd.print(info.substring(0, 16));
delay(1500);
}
void showErrorQuick(String title, String msg) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ERROR: " + title);
lcd.setCursor(0, 1);
lcd.print(msg.substring(0, 16));
delay(1000);
}
void showStatusUpdate() {
lcd.setCursor(0, 1);
lcd.print("Updating... ");
}
// ============================================
// SETUP
// ============================================
void setup() {
Serial.begin(115200);
Serial.println("\n=== Monitor Poin v4.1 ===");
Serial.print("ArduinoJson Version: ");
Serial.println(ARDUINOJSON_VERSION);
lcd.init();
lcd.backlight();
lcd.clear();
showSplashScreen();
connectWiFi();
if (wifiConnected) {
delay(1000);
if (fetchData()) {
showDataLoaded(String(jumlahPeserta) + " peserta");
displayPeserta(0);
} else {
showErrorQuick("Data", "Gagal load");
}
}
lastUpdate = millis();
lastScroll = millis();
lastDisplayRefresh = millis();
}
// ============================================
// LOOP
// ============================================
void loop() {
unsigned long now = millis();
// Cek WiFi
static unsigned long lastWifiCheck = 0;
if (now - lastWifiCheck >= 10000) {
checkWiFi();
lastWifiCheck = now;
}
// Update data
if (wifiConnected && !isUpdating && (now - lastUpdate >= UPDATE_INTERVAL)) {
Serial.println("=== SCHEDULED UPDATE ===");
lcd.setCursor(0, 1);
lcd.print("Updating... ");
if (fetchData()) {
currentDisplayIndex = 0;
lastScroll = now;
showDataLoaded(String(jumlahPeserta) + " peserta");
displayPeserta(0);
} else {
showErrorQuick("Update", "Gagal");
}
lastUpdate = now;
}
// ============================================
// SLIDESHOW
// ============================================
if (jumlahPeserta > 0) {
// Pindah slide setiap SCROLL_INTERVAL
if (now - lastScroll >= SCROLL_INTERVAL) {
currentDisplayIndex = (currentDisplayIndex + 1) % jumlahPeserta;
lastScroll = now;
displayPeserta(currentDisplayIndex);
} else {
// Refresh display untuk animasi
if (now - lastDisplayRefresh >= 500) {
displayPeserta(currentDisplayIndex);
lastDisplayRefresh = now;
}
}
} else {
if (now - lastUpdate > 10000) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Tidak ada data");
lcd.setCursor(0, 1);
lcd.print("Cek koneksi...");
}
}
delay(50);
}