#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
// Konfigurasi WiFi
const char* ssid = "smrthome 4sun86";
const char* password = "12345678";
// WiFi eksternal yang diprioritaskan
const char* externalSSID = "www.asun86.com";
const char* externalPassword = "";
// Pin untuk lampu
const int LAMPU_KORIDOR = D1;
const int LAMPU_TAMU = D2;
const int LAMPU_TENGAH = D3;
const int LAMPU_KAMAR1 = D4;
const int LAMPU_KAMAR2 = D5;
const int LAMPU_DAPUR = D6;
const int LAMPU_KAMAR_MANDI = D7;
const int BELL_RUMAH_PIN = D8; // Pin untuk Bell Rumah
// Status lampu
bool statusLampu[7] = {false, false, false, false, false, false, false};
bool statusBellRumah = false;
String namaLampu[7] = {
"1. Koridor", "2. Tamu", "3. Tengah",
"4. Kamar 1", "5. Kamar 2",
"6. Dapur", "7. Kamar Mandi"
};
// Struktur untuk riwayat
struct HistoryEntry {
String timestamp;
String message;
};
// Struktur untuk chat
struct ChatEntry {
String timestamp;
String sender;
String message;
};
const int MAX_HISTORY = 18;
const int MAX_CHAT = 15;
HistoryEntry history[MAX_HISTORY];
ChatEntry chat[MAX_CHAT];
int historyIndex = 0;
int historyCount = 0;
int chatIndex = 0;
int chatCount = 0;
ESP8266WebServer server(80);
// Deklarasi fungsi
int getPinFromIndex(int index);
void printStatus();
void addHistory(String message);
void addChat(String sender, String message);
String getTimeStamp();
bool connectToWiFi(const char* ssid, const char* password, int timeout = 15);
// HTML untuk tampilan web dengan tema hacker
const char* htmlContent = R"rawliteral(
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SMART HOME TERMINAL</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Courier New', monospace;
background: #0a0a0a;
color: #00ff00;
min-height: 100vh;
padding: 0;
overflow-x: hidden;
}
.terminal-container {
width: 100vw;
height: 100vh;
margin: 0;
border: none;
border-radius: 0;
background: #000000;
box-shadow: none;
display: flex;
flex-direction: column;
}
.terminal-header {
background: #001100;
padding: 15px 20px;
border-bottom: 1px solid #00ff00;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
.terminal-title {
font-size: 1.2em;
font-weight: bold;
text-shadow: 0 0 10px #00ff00;
}
.terminal-status {
font-size: 0.9em;
color: #00ff00;
}
.blink {
animation: blink 1s infinite;
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
.content-area {
padding: 20px;
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.tab-container {
margin-bottom: 20px;
flex: 1;
display: flex;
flex-direction: column;
}
.tabs {
display: flex;
border-bottom: 1px solid #00ff00;
margin-bottom: 20px;
flex-wrap: wrap;
flex-shrink: 0;
}
.tab {
padding: 12px 25px;
background: #001100;
border: 1px solid #00ff00;
border-bottom: none;
cursor: pointer;
margin-right: 5px;
border-radius: 5px 5px 0 0;
transition: all 0.3s ease;
flex: 1;
min-width: 120px;
text-align: center;
}
.tab.active {
background: #00ff00;
color: #000000;
font-weight: bold;
}
.tab-content {
display: none;
flex: 1;
overflow: hidden;
}
.tab-content.active {
display: flex;
flex-direction: column;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 25px;
}
.lamp-card {
background: #001a00;
border: 1px solid #00ff00;
border-radius: 8px;
padding: 15px;
text-align: center;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.lamp-card::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(0, 255, 0, 0.1), transparent);
transition: left 0.5s;
}
.lamp-card:hover::before {
left: 100%;
}
.lamp-card:hover {
box-shadow: 0 0 15px rgba(0, 255, 0, 0.5);
transform: translateY(-2px);
}
.lamp-name {
font-size: 1em;
font-weight: bold;
margin-bottom: 12px;
color: #00ff00;
text-transform: uppercase;
letter-spacing: 1px;
}
.toggle-btn {
padding: 10px 20px;
border: 1px solid #00ff00;
border-radius: 5px;
font-size: 0.9em;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
font-family: 'Courier New', monospace;
position: relative;
overflow: hidden;
width: 100%;
margin-bottom: 8px;
}
.btn-on {
background: #003300;
color: #00ff00;
}
.btn-on:hover {
background: #00ff00;
color: #000000;
box-shadow: 0 0 15px rgba(0, 255, 0, 0.7);
}
.btn-off {
background: #330000;
color: #ff4444;
border-color: #ff4444;
}
.btn-off:hover {
background: #ff4444;
color: #000000;
box-shadow: 0 0 15px rgba(255, 68, 68, 0.7);
}
.push-btn {
background: #000033;
color: #0088ff;
border-color: #0088ff;
}
.push-btn.active {
background: #0088ff;
color: #000000;
box-shadow: 0 0 15px rgba(0, 136, 255, 0.7);
}
.push-btn:hover {
background: #0088ff;
color: #000000;
box-shadow: 0 0 15px rgba(0, 136, 255, 0.7);
}
.status-indicator {
margin-top: 8px;
font-size: 0.8em;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.led {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
}
.led-on {
background: #00ff00;
box-shadow: 0 0 8px #00ff00;
}
.led-off {
background: #ff4444;
box-shadow: 0 0 8px #ff4444;
}
.led-push {
background: #0088ff;
box-shadow: 0 0 8px #0088ff;
}
.status-text {
font-size: 0.75em;
}
.control-panel {
background: #001100;
border: 1px solid #00ff00;
border-radius: 8px;
padding: 20px;
margin-top: 20px;
}
.control-title {
text-align: center;
margin-bottom: 15px;
font-size: 1.1em;
font-weight: bold;
color: #00ff00;
}
.all-controls {
display: flex;
justify-content: center;
gap: 15px;
flex-wrap: wrap;
}
.all-btn {
padding: 12px 20px;
border: 1px solid #00ff00;
border-radius: 5px;
font-size: 0.9em;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
font-family: 'Courier New', monospace;
}
.all-on {
background: #003300;
color: #00ff00;
}
.all-on:hover {
background: #00ff00;
color: #000000;
box-shadow: 0 0 15px rgba(0, 255, 0, 0.7);
}
.all-off {
background: #330000;
color: #ff4444;
border-color: #ff4444;
}
.all-off:hover {
background: #ff4444;
color: #000000;
box-shadow: 0 0 15px rgba(255, 68, 68, 0.7);
}
.history-container {
background: #001100;
border: 1px solid #00ff00;
border-radius: 8px;
padding: 20px;
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.history-title {
text-align: center;
margin-bottom: 15px;
font-size: 1.1em;
font-weight: bold;
color: #00ff00;
flex-shrink: 0;
}
.history-list {
list-style: none;
flex: 1;
overflow-y: auto;
}
.history-item {
padding: 8px 12px;
border-bottom: 1px solid #003300;
font-size: 0.85em;
display: flex;
justify-content: space-between;
}
.history-item:last-child {
border-bottom: none;
}
.history-time {
color: #0088ff;
font-weight: bold;
min-width: 70px;
}
.history-message {
color: #00ff00;
flex: 1;
margin-left: 15px;
}
.chat-container {
background: #001100;
border: 1px solid #00ff00;
border-radius: 8px;
padding: 20px;
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.chat-title {
text-align: center;
margin-bottom: 15px;
font-size: 1.1em;
font-weight: bold;
color: #00ff00;
flex-shrink: 0;
}
.chat-messages {
flex: 1;
overflow-y: auto;
margin-bottom: 15px;
}
.chat-list {
list-style: none;
}
.chat-item {
padding: 10px;
border-bottom: 1px solid #003300;
font-size: 0.9em;
}
.chat-item:last-child {
border-bottom: none;
}
.chat-header {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
}
.chat-sender {
color: #0088ff;
font-weight: bold;
}
.chat-time {
color: #00cc00;
}
.chat-message {
color: #00ff00;
}
.chat-input-container {
display: flex;
gap: 10px;
flex-shrink: 0;
}
.chat-input {
flex: 1;
padding: 10px;
background: #000000;
border: 1px solid #00ff00;
border-radius: 5px;
color: #00ff00;
font-family: 'Courier New', monospace;
font-size: 0.9em;
}
.chat-input::placeholder {
color: #008800;
}
.chat-send-btn {
padding: 10px 20px;
background: #003300;
border: 1px solid #00ff00;
border-radius: 5px;
color: #00ff00;
cursor: pointer;
font-family: 'Courier New', monospace;
font-weight: bold;
transition: all 0.3s ease;
}
.chat-send-btn:hover {
background: #00ff00;
color: #000000;
}
.system-info {
margin-top: 20px;
text-align: center;
font-size: 0.8em;
color: #008800;
border-top: 1px solid #00ff00;
padding-top: 15px;
flex-shrink: 0;
}
/* Scrollbar styling */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #001100;
}
::-webkit-scrollbar-thumb {
background: #00ff00;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #00cc00;
}
/* Responsive Design */
@media (max-width: 600px) {
.grid {
grid-template-columns: 1fr;
}
.all-controls {
flex-direction: column;
}
.terminal-header {
flex-direction: column;
gap: 10px;
text-align: center;
}
.tabs {
flex-direction: column;
}
.tab {
margin-right: 0;
margin-bottom: 5px;
border-radius: 5px;
border-bottom: 1px solid #00ff00;
}
}
@media (min-width: 601px) and (max-width: 900px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 901px) {
.grid {
grid-template-columns: repeat(4, 1fr);
}
}
</style>
</head>
<body>
<div class="terminal-container">
<div class="terminal-header">
<div class="terminal-title">🚀 SMART HOME TERMINAL v3.0</div>
<div class="terminal-status">SYSTEM: <span class="blink">ONLINE</span> | AUTO-REFRESH: 1m</div>
</div>
<div class="content-area">
<div class="tab-container">
<div class="tabs">
<div class="tab active" onclick="switchTab('control')">CONTROL PANEL</div>
<div class="tab" onclick="switchTab('history')">SYSTEM HISTORY</div>
<div class="tab" onclick="switchTab('chat')">FAMILY CHAT</div>
</div>
<div id="control-tab" class="tab-content active">
<div class="grid" id="lampGrid">
<!-- Lampu cards akan diisi oleh JavaScript -->
</div>
<div class="control-panel">
<div class="control-title">MASTER CONTROL</div>
<div class="all-controls">
<button class="all-btn all-on" onclick="controlAll('on')">ACTIVATE ALL</button>
<button class="all-btn all-off" onclick="controlAll('off')">SHUTDOWN ALL</button>
</div>
</div>
</div>
<div id="history-tab" class="tab-content">
<div class="history-container">
<div class="history-title">SYSTEM ACTIVITY LOG (Last 18 Events)</div>
<ul class="history-list" id="historyList">
<!-- Riwayat akan diisi oleh JavaScript -->
</ul>
</div>
</div>
<div id="chat-tab" class="tab-content">
<div class="chat-container">
<div class="chat-title">FAMILY CHAT ROOM</div>
<div class="chat-messages">
<ul class="chat-list" id="chatList">
<!-- Chat messages akan diisi oleh JavaScript -->
</ul>
</div>
<div class="chat-input-container">
<input type="text" class="chat-input" id="chatMessage" placeholder="Type your message..." maxlength="100">
<button class="chat-send-btn" onclick="sendMessage()">SEND</button>
</div>
</div>
</div>
</div>
<div class="system-info">
SYSTEM IP: <span id="systemIP">192.168.11.86</span> | NODE: SMARTHOME_4SUN86 | LAST UPDATE: <span id="lastUpdate"></span>
</div>
</div>
</div>
<script>
const lamps = [
{id: 0, name: "1. KORIDOR"},
{id: 1, name: "2. TAMU"},
{id: 2, name: "3. TENGAH"},
{id: 3, name: "4. KAMAR 1"},
{id: 4, name: "5. KAMAR 2"},
{id: 5, name: "6. DAPUR"},
{id: 6, name: "7. K.MANDI"},
{id: 7, name: "8. BELL RUMAH"}
];
let bellRumahState = false;
let lampCardsCreated = false;
function switchTab(tabName) {
// Sembunyikan semua tab content
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
// Hapus active class dari semua tab
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
// Tampilkan tab yang dipilih
document.getElementById(tabName + '-tab').classList.add('active');
event.currentTarget.classList.add('active');
// Jika pindah ke chat tab, fokus ke input
if (tabName === 'chat') {
setTimeout(() => {
document.getElementById('chatMessage').focus();
}, 100);
}
}
function updateDisplay() {
updateLampsDisplay();
updateHistoryDisplay();
updateChatDisplay();
updateLastUpdateTime();
}
function updateLampsDisplay() {
const grid = document.getElementById('lampGrid');
// Reset flag jika grid kosong
if (grid.children.length === 0) {
lampCardsCreated = false;
}
// Hanya buat cards sekali saja
if (!lampCardsCreated) {
grid.innerHTML = '';
// Buat cards untuk 7 lampu
for (let i = 0; i < 7; i++) {
const card = document.createElement('div');
card.className = 'lamp-card';
card.id = 'lampCard-' + i;
card.innerHTML = `
<div class="lamp-name">${lamps[i].name}</div>
<button class="toggle-btn btn-on" id="btn-${i}" onclick="toggleLamp(${i})">ON</button>
<div class="status-indicator">
<span class="led led-off" id="led-${i}"></span>
<span class="status-text" id="status-${i}">INACTIVE</span>
</div>
`;
grid.appendChild(card);
}
// Buat card untuk bell rumah
const bellCard = document.createElement('div');
bellCard.className = 'lamp-card';
bellCard.id = 'bellCard';
bellCard.innerHTML = `
<div class="lamp-name">${lamps[7].name}</div>
<button class="toggle-btn push-btn" id="bell-btn" onclick="toggleBellRumah()">ACTIVATE</button>
<div class="status-indicator">
<span class="led led-off" id="bell-led"></span>
<span class="status-text" id="bell-status">INACTIVE</span>
</div>
`;
grid.appendChild(bellCard);
lampCardsCreated = true;
}
// Update status untuk semua lampu
for (let i = 0; i < 7; i++) {
fetch('/status?lamp=' + i)
.then(response => response.text())
.then(status => {
const isOn = status === 'ON';
const btn = document.getElementById('btn-' + i);
const led = document.getElementById('led-' + i);
const statusText = document.getElementById('status-' + i);
if (btn && led && statusText) {
btn.className = isOn ? 'toggle-btn btn-off' : 'toggle-btn btn-on';
btn.textContent = isOn ? 'OFF' : 'ON';
led.className = isOn ? 'led led-on' : 'led led-off';
statusText.textContent = isOn ? 'ACTIVE' : 'INACTIVE';
}
})
.catch(error => {
console.error('Error fetching lamp status:', error);
});
}
// Update status untuk bell rumah
fetch('/bellStatus')
.then(response => response.text())
.then(status => {
bellRumahState = status === 'ON';
const btn = document.getElementById('bell-btn');
const led = document.getElementById('bell-led');
const statusText = document.getElementById('bell-status');
if (btn && led && statusText) {
btn.className = bellRumahState ? 'toggle-btn push-btn active' : 'toggle-btn push-btn';
btn.textContent = bellRumahState ? 'DEACTIVATE' : 'ACTIVATE';
led.className = bellRumahState ? 'led led-push' : 'led led-off';
statusText.textContent = bellRumahState ? 'ACTIVE' : 'INACTIVE';
}
})
.catch(error => {
console.error('Error fetching bell status:', error);
});
}
function updateHistoryDisplay() {
fetch('/getHistory')
.then(response => response.json())
.then(history => {
const historyList = document.getElementById('historyList');
historyList.innerHTML = '';
history.forEach(entry => {
const li = document.createElement('li');
li.className = 'history-item';
li.innerHTML = `
<span class="history-time">[${entry.timestamp}]</span>
<span class="history-message">${entry.message}</span>
`;
historyList.appendChild(li);
});
})
.catch(error => {
console.error('Error fetching history:', error);
});
}
function updateChatDisplay() {
fetch('/getChat')
.then(response => response.json())
.then(chat => {
const chatList = document.getElementById('chatList');
chatList.innerHTML = '';
chat.forEach(entry => {
const li = document.createElement('li');
li.className = 'chat-item';
li.innerHTML = `
<div class="chat-header">
<span class="chat-sender">${entry.sender}</span>
<span class="chat-time">[${entry.timestamp}]</span>
</div>
<div class="chat-message">${entry.message}</div>
`;
chatList.appendChild(li);
});
// Scroll ke bawah
chatList.scrollTop = chatList.scrollHeight;
})
.catch(error => {
console.error('Error fetching chat:', error);
});
}
function updateLastUpdateTime() {
const now = new Date();
const timeString = now.toLocaleTimeString();
document.getElementById('lastUpdate').textContent = timeString;
}
function toggleLamp(lampId) {
fetch('/toggle?lamp=' + lampId)
.then(response => {
if (response.ok) {
setTimeout(updateLampsDisplay, 300);
}
})
.catch(error => {
console.error('Error toggling lamp:', error);
});
}
function toggleBellRumah() {
fetch('/toggleBell')
.then(response => {
if (response.ok) {
setTimeout(updateLampsDisplay, 300);
}
})
.catch(error => {
console.error('Error toggling bell rumah:', error);
});
}
function controlAll(action) {
fetch('/all?action=' + action)
.then(response => {
if (response.ok) {
setTimeout(updateLampsDisplay, 500);
}
})
.catch(error => {
console.error('Error controlling all lamps:', error);
});
}
function sendMessage() {
const messageInput = document.getElementById('chatMessage');
const message = messageInput.value.trim();
if (message) {
fetch('/sendChat?message=' + encodeURIComponent(message))
.then(response => {
if (response.ok) {
messageInput.value = '';
setTimeout(updateChatDisplay, 300);
}
})
.catch(error => {
console.error('Error sending message:', error);
});
}
}
// Enter key untuk send message
document.getElementById('chatMessage').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});
// Auto-refresh setiap 1 menit (60000 ms)
setInterval(updateDisplay, 60000);
// Initial load
updateDisplay();
</script>
</body>
</html>
)rawliteral";
// Fungsi untuk koneksi WiFi
bool connectToWiFi(const char* ssid, const char* password, int timeout) {
Serial.print(">>> Mencoba koneksi ke: ");
Serial.println(ssid);
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < timeout * 2) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\n>>> KONEKSI BERHASIL");
Serial.print(">>> IP ADDRESS: ");
Serial.println(WiFi.localIP());
return true;
} else {
Serial.println("\n>>> KONEKSI GAGAL");
return false;
}
}
// Fungsi untuk mendapatkan timestamp
String getTimeStamp() {
unsigned long currentMillis = millis();
unsigned long seconds = currentMillis / 1000;
unsigned long minutes = seconds / 60;
unsigned long hours = minutes / 60;
seconds = seconds % 60;
minutes = minutes % 60;
hours = hours % 24;
char timestamp[20];
sprintf(timestamp, "%02lu:%02lu:%02lu", hours, minutes, seconds);
return String(timestamp);
}
// Fungsi untuk menambah riwayat
void addHistory(String message) {
HistoryEntry entry;
entry.timestamp = getTimeStamp();
entry.message = message;
history[historyIndex] = entry;
historyIndex = (historyIndex + 1) % MAX_HISTORY;
if (historyCount < MAX_HISTORY) {
historyCount++;
}
}
// Fungsi untuk menambah chat
void addChat(String sender, String message) {
ChatEntry entry;
entry.timestamp = getTimeStamp();
entry.sender = sender;
entry.message = message;
chat[chatIndex] = entry;
chatIndex = (chatIndex + 1) % MAX_CHAT;
if (chatCount < MAX_CHAT) {
chatCount++;
}
}
// Fungsi untuk mendapatkan pin dari index
int getPinFromIndex(int index) {
switch(index) {
case 0: return LAMPU_KORIDOR;
case 1: return LAMPU_TAMU;
case 2: return LAMPU_TENGAH;
case 3: return LAMPU_KAMAR1;
case 4: return LAMPU_KAMAR2;
case 5: return LAMPU_DAPUR;
case 6: return LAMPU_KAMAR_MANDI;
default: return -1;
}
}
// Fungsi untuk menampilkan status di Serial Monitor
void printStatus() {
Serial.println("\n=== SYSTEM STATUS ===");
for (int i = 0; i < 7; i++) {
Serial.print(">>> ");
Serial.print(namaLampu[i]);
Serial.print(": ");
Serial.println(statusLampu[i] ? "[ACTIVE]" : "[INACTIVE]");
}
Serial.print(">>> BELL RUMAH: ");
Serial.println(statusBellRumah ? "[ACTIVE]" : "[INACTIVE]");
Serial.println("=====================");
}
void setup(void) {
Serial.begin(115200);
// Setup pin mode
pinMode(LAMPU_KORIDOR, OUTPUT);
pinMode(LAMPU_TAMU, OUTPUT);
pinMode(LAMPU_TENGAH, OUTPUT);
pinMode(LAMPU_KAMAR1, OUTPUT);
pinMode(LAMPU_KAMAR2, OUTPUT);
pinMode(LAMPU_DAPUR, OUTPUT);
pinMode(LAMPU_KAMAR_MANDI, OUTPUT);
pinMode(BELL_RUMAH_PIN, OUTPUT); // Setup pin Bell Rumah
// Matikan semua lampu awal
digitalWrite(LAMPU_KORIDOR, LOW);
digitalWrite(LAMPU_TAMU, LOW);
digitalWrite(LAMPU_TENGAH, LOW);
digitalWrite(LAMPU_KAMAR1, LOW);
digitalWrite(LAMPU_KAMAR2, LOW);
digitalWrite(LAMPU_DAPUR, LOW);
digitalWrite(LAMPU_KAMAR_MANDI, LOW);
digitalWrite(BELL_RUMAH_PIN, LOW); // Matikan Bell Rumah awal
// Koneksi WiFi dengan prioritas
Serial.println();
Serial.println(">>> SMART HOME TERMINAL BOOTED");
Serial.println(">>> SYSTEM INITIALIZATION COMPLETE");
Serial.println(">>> MENCARI JARINGAN WIFI...");
// Prioritas 1: Coba konek ke WiFi eksternal terlebih dahulu
if (connectToWiFi(externalSSID, externalPassword)) {
Serial.println(">>> MODE: CLIENT (WiFi Eksternal)");
addHistory("CONNECTED TO EXTERNAL WIFI: " + String(externalSSID));
}
// Prioritas 2: Jika tidak ada WiFi eksternal, buat Access Point
else {
Serial.println(">>> MEMBUAT ACCESS POINT...");
WiFi.softAP(ssid, password);
IPAddress myIP = WiFi.softAPIP();
Serial.print(">>> SSID: ");
Serial.println(ssid);
Serial.print(">>> IP ADDRESS: ");
Serial.println(myIP);
addHistory("ACCESS POINT CREATED: " + String(ssid));
}
Serial.println(">>> BELL RUMAH CONNECTED");
Serial.println(">>> READY FOR COMMANDS");
Serial.println("===============================");
// Tambah riwayat startup dan chat welcome
addHistory("SYSTEM BOOTED - READY FOR COMMANDS");
addHistory("BELL RUMAH CONNECTED");
addChat("SYSTEM", "Smart Home Terminal v3.0 activated");
addChat("SYSTEM", "Family chat room is ready for use");
addChat("SYSTEM", "Bell Rumah connected");
// Setup server routes
server.on("/", []() {
server.send(200, "text/html", htmlContent);
});
server.on("/toggle", []() {
if (server.hasArg("lamp")) {
int lamp = server.arg("lamp").toInt();
if (lamp >= 0 && lamp <= 6) {
statusLampu[lamp] = !statusLampu[lamp];
digitalWrite(getPinFromIndex(lamp), statusLampu[lamp] ? HIGH : LOW);
String message = "WEB: " + namaLampu[lamp] + (statusLampu[lamp] ? " [ACTIVATED]" : " [DEACTIVATED]");
Serial.println(">>> " + message);
addHistory(message);
printStatus();
server.send(200, "text/plain", "OK");
return;
}
}
server.send(400, "text/plain", "ERROR");
});
server.on("/toggleBell", []() {
statusBellRumah = !statusBellRumah;
digitalWrite(BELL_RUMAH_PIN, statusBellRumah ? HIGH : LOW);
String message = "WEB: BELL RUMAH " + String(statusBellRumah ? "[ACTIVATED]" : "[DEACTIVATED]");
Serial.println(">>> " + message);
addHistory(message);
server.send(200, "text/plain", "OK");
});
server.on("/bellStatus", []() {
server.send(200, "text/plain", statusBellRumah ? "ON" : "OFF");
});
server.on("/status", []() {
if (server.hasArg("lamp")) {
int lamp = server.arg("lamp").toInt();
if (lamp >= 0 && lamp <= 6) {
server.send(200, "text/plain", statusLampu[lamp] ? "ON" : "OFF");
return;
}
}
server.send(400, "text/plain", "ERROR");
});
server.on("/all", []() {
if (server.hasArg("action")) {
String action = server.arg("action");
String message;
if (action == "on") {
for (int i = 0; i < 7; i++) {
statusLampu[i] = true;
digitalWrite(getPinFromIndex(i), HIGH);
}
// Jangan aktifkan Bell Rumah saat ALL ON
message = "WEB: ALL LAMPS ACTIVATED (Bell Rumah unchanged)";
} else if (action == "off") {
for (int i = 0; i < 7; i++) {
statusLampu[i] = false;
digitalWrite(getPinFromIndex(i), LOW);
}
// Jangan matikan Bell Rumah saat ALL OFF
message = "WEB: ALL LAMPS DEACTIVATED (Bell Rumah unchanged)";
}
Serial.println(">>> " + message);
addHistory(message);
printStatus();
server.send(200, "text/plain", "OK");
return;
}
server.send(400, "text/plain", "ERROR");
});
server.on("/getHistory", []() {
String json = "[";
for (int i = 0; i < historyCount; i++) {
int index = (historyIndex - historyCount + i + MAX_HISTORY) % MAX_HISTORY;
if (i > 0) json += ",";
json += "{\"timestamp\":\"" + history[index].timestamp + "\",\"message\":\"" + history[index].message + "\"}";
}
json += "]";
server.send(200, "application/json", json);
});
server.on("/getChat", []() {
String json = "[";
for (int i = 0; i < chatCount; i++) {
int index = (chatIndex - chatCount + i + MAX_CHAT) % MAX_CHAT;
if (i > 0) json += ",";
json += "{\"timestamp\":\"" + chat[index].timestamp + "\",\"sender\":\"" + chat[index].sender + "\",\"message\":\"" + chat[index].message + "\"}";
}
json += "]";
server.send(200, "application/json", json);
});
server.on("/sendChat", []() {
if (server.hasArg("message")) {
String message = server.arg("message");
if (message.length() > 0) {
addChat("USER", message);
Serial.println(">>> CHAT: USER - " + message);
server.send(200, "text/plain", "OK");
return;
}
}
server.send(400, "text/plain", "ERROR");
});
server.begin();
Serial.println(">>> HTTP SERVER: ACTIVE");
// Tampilkan informasi koneksi
if (WiFi.status() == WL_CONNECTED) {
Serial.println(">>> MODE: CLIENT - Terhubung ke WiFi eksternal");
Serial.print(">>> IP Address: ");
Serial.println(WiFi.localIP());
} else {
Serial.println(">>> MODE: ACCESS POINT - Menggunakan WiFi internal");
Serial.print(">>> IP Address: ");
Serial.println(WiFi.softAPIP());
}
printStatus();
}
void loop(void) {
server.handleClient();
// Optional: Cek status WiFi secara periodik (setiap 30 detik)
static unsigned long lastWiFiCheck = 0;
if (millis() - lastWiFiCheck > 30000) {
lastWiFiCheck = millis();
// Jika sedang dalam mode AP, coba reconnect ke WiFi eksternal
if (WiFi.status() != WL_CONNECTED) {
Serial.println(">>> Mencoba reconnect ke WiFi eksternal...");
if (connectToWiFi(externalSSID, externalPassword, 10)) {
Serial.println(">>> Berhasil reconnect ke WiFi eksternal");
addHistory("RECONNECTED TO EXTERNAL WIFI");
}
}
}
delay(100);
}
CARA PENGGUNAAN:
Koneksi WiFi Otomatis:
Sistem akan otomatis mencoba terhubung ke WiFi "www.asun86.com" dengan password "1324576890"
Jika WiFi eksternal tidak tersedia, sistem akan membuat Access Point "smrthome 4sun86" dengan password "12345678"
Hubungkan perangkat Anda ke jaringan yang sama
Akses Web Interface:
Buka browser web di perangkat yang terhubung
Akses alamat IP yang ditampilkan di Serial Monitor
Interface akan terbuka dengan tema hacker terminal
Kontrol Perangkat:
Klik tombol ON/OFF untuk masing-masing lampu
Gunakan tombol ACTIVATE/DEACTIVATE untuk Bell Rumah
Tombol "ACTIVATE ALL" untuk menyalakan semua lampu sekaligus
Tombol "SHUTDOWN ALL" untuk mematikan semua lampu sekaligus
PENJELASAN SISTEM:
Sistem Smart Home Terminal v3.0 adalah kontroler rumah pintar berbasis NodeMCU ESP8266 yang dapat mengontrol 7 lampu dan 1 bell rumah melalui interface web. Sistem dirancang dengan prioritas koneksi WiFi - pertama akan mencoba terhubung ke jaringan WiFi eksternal, jika tidak tersedia akan berfungsi sebagai Access Point sendiri.
Antarmuka web menggunakan tema terminal hacker dengan warna hijau neon pada latar hitam, memberikan pengalaman yang futuristic. Terdapat tiga tab utama: Control Panel untuk mengontrol perangkat, System History untuk melihat riwayat aktivitas, dan Family Chat untuk komunikasi internal.
FITUR UTAMA:
Kontrol Lampu:
7 channel lampu independen (Koridor, Tamu, Tengah, Kamar 1, Kamar 2, Dapur, Kamar Mandi)
Kontrol individual ON/OFF untuk setiap lampu
Kontrol master ACTIVATE ALL dan SHUTDOWN ALL
Status real-time dengan indikator LED visual
Bell Rumah:
Kontrol bell rumah terpisah dari sistem lampu
Fungsi ACTIVATE/DEACTIVATE independen
Tidak terpengaruh oleh kontrol master lampu
System Monitoring:
Riwayat aktivitas sistem (18 event terakhir)
Timestamp untuk setiap aksi yang dilakukan
Auto-refresh setiap 1 menit
Status koneksi real-time
Family Chat:
Ruang chat internal untuk komunikasi keluarga
Penyimpanan 15 pesan terakhir
Interface chat yang user-friendly
Kirim pesan dengan Enter atau tombol SEND
Manajemen Koneksi:
Prioritas koneksi WiFi otomatis
Fallback ke Access Point mode
Auto-reconnect setiap 30 detik
Deteksi jaringan otomatis
Tampilan Visual:
Tema terminal hacker dengan efek neon
Responsive design untuk semua perangkat
Animasi hover dan transisi halus
Indikator status dengan LED warna
Sistem ini cocok untuk implementasi smart home sederhana dengan kontrol yang intuitif melalui browser web tanpa perlu aplikasi tambahan.