#include <WiFi.h>
#include <WebServer.h>
#include <FastLED.h>
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
// --- KONFIGURASI NEOPIXEL ---
#define LED_PIN_1 5
#define NUM_LEDS_1 300 // (12*4) + 40
#define LED_PIN_2 15
#define NUM_LEDS_2 300 // 40 * 2
CRGB leds1[NUM_LEDS_1];
CRGB leds2[NUM_LEDS_2];
// --- KONFIGURASI LED MATRIX (MD_PAROLA) ---
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW // Jenis hardware matrix standar di Wokwi
#define MAX_DEVICES 4 // Jumlah modul matrix (4-in-1)
#define CLK_PIN 18 // GPIO 18 SCK
#define DATA_PIN 23 // GPIO 23 MOSI (Sesuai request)
#define CS_PIN 15 // Pin CS (Gunakan GPIO 4 agar tidak bentrok dengan GPIO 5)
MD_Parola P = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
// --- KONFIGURASI WEB SERVER ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";
WebServer server(80);
// Status Kontrol
bool meteorActive = true; // Default aktif saat dinyalakan
CRGB color1 = CRGB::Red;
CRGB color2 = CRGB::Blue;
// Variabel Efek Meteor
int meteorPos = 0;
// --- HTML ANTARMUKA WEB ---
const char HTML_PAGE[] PROGMEM = R"=====(
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ESP32 Control Panel</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; background-color: #111; color: white; }
.btn { padding: 15px 25px; font-size: 18px; margin: 10px; cursor: pointer; border: none; border-radius: 5px; font-weight: bold; }
.on { background-color: #4CAF50; color: white; }
.off { background-color: #f44336; color: white; }
input[type="color"] { width: 80px; height: 40px; border: none; vertical-align: middle; cursor: pointer; }
.container { max-width: 500px; margin: auto; padding: 20px; background: #222; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.5); }
</style>
</head>
<body>
<div class="container">
<h2>AORUS CONTROLLER HUB</h2>
<hr style="border: 1px solid #444;">
<h3>Efek Meteor (2 Warna)</h3>
<button class="btn on" onclick="toggleMeteor(1)">AKTIFKAN</button>
<button class="btn off" onclick="toggleMeteor(0)">MATIKAN</button>
<br><br>
<p>
<label>Warna 1: </label><input type="color" id="c1" value="#ff0000" onchange="updateColors()">
<label>Warna 2: </label><input type="color" id="c2" value="#0000ff" onchange="updateColors()">
</p>
</div>
<script>
function toggleMeteor(status) {
fetch('/meteor?status=' + status);
}
function updateColors() {
let c1 = document.getElementById('c1').value.substring(1);
let c2 = document.getElementById('c2').value.substring(1);
fetch('/setcolor?c1=' + c1 + '&c2=' + c2);
}
</script>
</body>
</html>
)=====";
// --- HANDLER WEB SERVER ---
void handleRoot() {
server.send(200, "text/html", HTML_PAGE);
}
void handleMeteor() {
if (server.hasArg("status")) {
meteorActive = server.arg("status").toInt() == 1;
}
server.send(200, "text/plain", "OK");
}
void handleSetColor() {
if (server.hasArg("c1") && server.hasArg("c2")) {
long hex1 = strtol(server.arg("c1").c_str(), NULL, 16);
long hex2 = strtol(server.arg("c2").c_str(), NULL, 16);
color1 = CRGB(hex1 >> 16, (hex1 >> 8) & 0xFF, hex1 & 0xFF);
color2 = CRGB(hex2 >> 16, (hex2 >> 8) & 0xFF, hex2 & 0xFF);
}
server.send(200, "text/plain", "OK");
}
// --- SETUP ---
void setup() {
Serial.begin(115200);
// Init Neopixel
FastLED.addLeds<WS2812B, LED_PIN_1, GRB>(leds1, NUM_LEDS_1).setCorrection(TypicalLEDStrip);
FastLED.addLeds<WS2812B, LED_PIN_2, GRB>(leds2, NUM_LEDS_2).setCorrection(TypicalLEDStrip);
FastLED.setBrightness(100);
FastLED.clear();
FastLED.show();
// Init LED Matrix dengan MD_Parola
P.begin();
P.setIntensity(5); // Kecerahan (0-15)
/* Setting Animasi Parola:
- Teks: "AORUS"
- Posisi Akhir Berhenti: PA_CENTER (Di Tengah)
- Kecepatan Gerak: 60 (Makin kecil makin cepat)
- Waktu Berhenti (Pause): 2000 ms (2 Detik)
- Efek Masuk: PA_SCROLL_LEFT (Dari Kanan ke Kiri)
- Efek Keluar: PA_SCROLL_LEFT (Lanjut ke Kiri)
*/
P.displayText("AORUS", PA_CENTER, 60, 2000, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
// Konek WiFi Wokwi
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
// Routing Web Server
server.on("/", handleRoot);
server.on("/meteor", handleMeteor);
server.on("/setcolor", handleSetColor);
server.onNotFound([]() { server.send(404, "text/plain", "Not Found"); });
server.begin();
}
// --- LOOP UTAMA ---
void loop() {
server.handleClient();
// Jalankan Efek Meteor jika diaktifkan dari Web
updateMeteorEffect();
// Animasi LED Matrix ditangani otomatis oleh MD_Parola
if (P.displayAnimate()) {
P.displayReset(); // Mengulang animasi dari kanan setelah teks benar-benar keluar ke kiri
}
FastLED.show();
delay(15); // Memberi jeda waktu kecil untuk stabilitas jaringan ESP32
}
// --- LOGIK EFEK METEOR 2 WARNA ---
void updateMeteorEffect() {
static unsigned long lastMeteorTime = 0;
if (millis() - lastMeteorTime < 35) return;
lastMeteorTime = millis();
if (!meteorActive) {
fadeToBlackBy(leds1, NUM_LEDS_1, 50);
fadeToBlackBy(leds2, NUM_LEDS_2, 50);
return;
}
// Efek memudar untuk ekor meteor
fadeToBlackBy(leds1, NUM_LEDS_1, 40);
fadeToBlackBy(leds2, NUM_LEDS_2, 40);
// Render kepala meteor (panjang 6 LED dengan gradasi Warna 1 ke Warna 2)
for (int i = 0; i < 6; i++) {
int pos1 = (meteorPos + i) % NUM_LEDS_1;
int pos2 = (meteorPos + i) % NUM_LEDS_2;
// Mencampur warna 1 dan warna 2 secara bertahap
CRGB blendedColor = blend(color1, color2, i * 42);
leds1[pos1] = blendedColor;
leds2[pos2] = blendedColor;
}
meteorPos++;
}