ROBOT SOCCER WEB SERVER VER 1
ROBOT SOCCER WEB SERVER VER 1
// Arduino IDE versi 2.2.1
// BOARD : LOLIN(WEMOS) D1 mini Lite (esp8266:esp8266:d1_mini_lite)
// ROBOT SOCKER NODEMCU - ANDROID LANDSCAPE MODE
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ArduinoOTA.h>
// connections for drive Motors
int PWM_A = D6;
int PWM_B = D7;
int DIR_A = D1;
int DIR_B = D2;
int DIR_C = D3;
int DIR_D = D4;
const int buzPin = D5;
const int ledPin = D8;
const int wifiLedPin = D0;
String command;
int SPEED = 1023;
ESP8266WebServer server(80);
unsigned long previousMillis = 0;
const char* sta_ssid = "www.asun86.com";
const char* sta_password = "";
// HTML Page untuk Android Landscape
const char* htmlPage = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Robot Soccer - Android Mode</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: 'Roboto', Arial, sans-serif;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
margin: 0;
padding: 0;
height: 100vh;
overflow: hidden;
touch-action: manipulation;
}
.container {
display: flex;
height: 100vh;
padding: 10px;
gap: 10px;
}
/* Panel Kiri - Kontrol Depan/Belakang */
.left-panel {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
gap: 15px;
}
/* Panel Kanan - Kontrol Kiri/Kanan */
.right-panel {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
gap: 15px;
}
/* Panel Tengah - Info dan Kontrol Tambahan */
.center-panel {
flex: 0.8;
display: flex;
flex-direction: column;
justify-content: space-between;
background: rgba(255, 255, 255, 0.1);
border-radius: 20px;
padding: 15px;
backdrop-filter: blur(10px);
}
.control-btn {
border: none;
border-radius: 20px;
background: linear-gradient(145deg, #667eea, #764ba2);
color: white;
font-size: 24px;
font-weight: bold;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 8px 25px rgba(0,0,0,0.3);
display: flex;
align-items: center;
justify-content: center;
}
.control-btn:active {
transform: scale(0.95);
box-shadow: 0 4px 15px rgba(0,0,0,0.4);
}
/* Tombol Maju dan Mundur - lebih kecil */
.btn-forward, .btn-backward {
height: 120px;
background: linear-gradient(145deg, #27ae60, #2ecc71);
}
/* Tombol Kiri dan Kanan - lebih besar */
.btn-left, .btn-right {
height: 150px;
width: 150px;
background: linear-gradient(145deg, #e67e22, #f39c12);
}
.btn-horn {
background: linear-gradient(145deg, #9b59b6, #8e44ad);
}
.btn-light {
background: linear-gradient(145deg, #f1c40f, #f39c12);
color: #2c3e50;
}
.info-section {
text-align: center;
color: white;
}
.info-title {
font-size: 14px;
opacity: 0.8;
margin-bottom: 5px;
}
.info-value {
font-size: 18px;
font-weight: bold;
}
.speed-control {
background: rgba(255, 255, 255, 0.9);
padding: 15px;
border-radius: 15px;
margin: 10px 0;
}
.speed-slider {
width: 100%;
height: 10px;
border-radius: 5px;
background: #ddd;
outline: none;
-webkit-appearance: none;
}
.speed-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 25px;
height: 25px;
border-radius: 50%;
background: #3498db;
cursor: pointer;
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
}
.status-indicator {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
background: #2ecc71;
margin-right: 8px;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.action-buttons {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin: 10px 0;
}
.action-btn {
padding: 15px 10px;
border: none;
border-radius: 15px;
background: linear-gradient(145deg, #34495e, #2c3e50);
color: white;
font-size: 14px;
font-weight: bold;
cursor: pointer;
transition: all 0.2s ease;
}
.action-btn:active {
transform: scale(0.95);
}
.connection-status {
background: rgba(255, 255, 255, 0.9);
padding: 10px;
border-radius: 10px;
margin-top: 10px;
font-size: 12px;
text-align: center;
}
.icon {
font-size: 24px;
margin-bottom: 5px;
}
/* Responsive untuk landscape orientation */
@media (max-height: 500px) {
.btn-forward, .btn-backward {
height: 80px;
font-size: 18px;
}
.btn-left, .btn-right {
height: 120px;
width: 120px;
font-size: 18px;
}
.center-panel {
padding: 10px;
}
}
/* Highlight untuk tombol aktif */
.active-btn {
transform: scale(0.92);
box-shadow: 0 2px 10px rgba(0,0,0,0.5);
filter: brightness(1.2);
}
/* Layout khusus untuk tombol kiri/kanan */
.turn-controls {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
height: 100%;
}
</style>
</head>
<body>
<div class="container">
<!-- Panel Kiri - Kontrol Depan/Belakang -->
<div class="left-panel">
<button class="control-btn btn-forward" ontouchstart="sendCommand('F')" ontouchend="sendCommand('S')">
<div>
<div class="icon">↑</div>
<div>MAJU</div>
</div>
</button>
<button class="control-btn btn-backward" ontouchstart="sendCommand('B')" ontouchend="sendCommand('S')">
<div>
<div class="icon">↓</div>
<div>MUNDUR</div>
</div>
</button>
</div>
<!-- Panel Tengah - Info dan Kontrol Tambahan -->
<div class="center-panel">
<div class="info-section">
<h3>🤖 ROBOT SOCCER</h3>
<div style="margin: 15px 0;">
<div class="info-title">KEADAAN</div>
<div class="info-value"><span class="status-indicator"></span> ONLINE</div>
</div>
<div class="speed-control">
<div class="info-title">KECEPATAN</div>
<div class="info-value" id="speed-value">1023</div>
<input type="range" class="speed-slider" id="speed" min="330" max="1023" value="1023" step="10">
</div>
</div>
<div class="action-buttons">
<button class="action-btn btn-horn" ontouchstart="sendCommand('V')">
<div class="icon">🚨</div>
<div>HORN</div>
</button>
<button class="action-btn btn-light" onclick="toggleLight()" id="btn-light">
<div class="icon">💡</div>
<div>LAMPU</div>
</button>
</div>
<div class="connection-status">
<div>IP: <span id="ip-address">192.168.11.86</span></div>
<div>Status: <span id="connection-status">SIAP</span></div>
</div>
</div>
<!-- Panel Kanan - Kontrol Kiri/Kanan -->
<div class="right-panel">
<div class="turn-controls">
<button class="control-btn btn-left" ontouchstart="sendCommand('L')" ontouchend="sendCommand('S')">
<div>
<div class="icon">←</div>
<div>KIRI</div>
</div>
</button>
<button class="control-btn btn-right" ontouchstart="sendCommand('R')" ontouchend="sendCommand('S')">
<div>
<div class="icon">→</div>
<div>KANAN</div>
</div>
</button>
</div>
</div>
</div>
<script>
let lightOn = false;
let activeCommand = '';
// Update IP address
document.getElementById('ip-address').textContent = window.location.hostname || '192.168.11.86';
function sendCommand(cmd) {
if (cmd === activeCommand) return;
// Remove active class from all buttons
document.querySelectorAll('.control-btn').forEach(btn => {
btn.classList.remove('active-btn');
});
// Add active class to pressed button
if (['F', 'B', 'L', 'R'].includes(cmd)) {
const activeBtn = event.target.closest('.control-btn');
if (activeBtn) {
activeBtn.classList.add('active-btn');
}
}
fetch('/?State=' + cmd)
.then(response => response.text())
.then(data => {
console.log('Command sent:', cmd);
activeCommand = cmd;
updateStatus('Aktif: ' + getCommandName(cmd));
})
.catch(error => {
console.error('Error:', error);
updateStatus('Error!');
});
}
function getCommandName(cmd) {
const commands = {
'F': 'MAJU', 'B': 'MUNDUR', 'L': 'KIRI', 'R': 'KANAN',
'S': 'STOP', 'V': 'HORN', 'W': 'LAMPU ON', 'w': 'LAMPU OFF'
};
return commands[cmd] || cmd;
}
function toggleLight() {
let cmd = lightOn ? 'w' : 'W';
sendCommand(cmd);
lightOn = !lightOn;
const btnLight = document.getElementById('btn-light');
btnLight.innerHTML = lightOn ?
'<div class="icon">💡</div><div>LAMPU ON</div>' :
'<div class="icon">💡</div><div>LAMPU OFF</div>';
btnLight.style.background = lightOn ?
'linear-gradient(145deg, #f39c12, #e67e22)' :
'linear-gradient(145deg, #f1c40f, #f39c12)';
}
function updateStatus(message) {
const statusElement = document.getElementById('connection-status');
statusElement.textContent = message;
statusElement.style.color = '#27ae60';
setTimeout(() => {
statusElement.textContent = 'SIAP';
statusElement.style.color = '#2c3e50';
}, 2000);
}
// Speed control
document.getElementById('speed').addEventListener('input', function() {
let speed = this.value;
document.getElementById('speed-value').textContent = speed;
sendCommand(speed);
});
// Keyboard control untuk testing di desktop
document.addEventListener('keydown', function(event) {
event.preventDefault();
switch(event.key) {
case 'ArrowUp':
case 'w':
sendCommand('F');
break;
case 'ArrowDown':
case 's':
sendCommand('B');
break;
case 'ArrowLeft':
case 'a':
sendCommand('L');
break;
case 'ArrowRight':
case 'd':
sendCommand('R');
break;
case ' ':
sendCommand('S');
break;
case 'h':
sendCommand('V');
break;
case 'l':
toggleLight();
break;
}
});
document.addEventListener('keyup', function(event) {
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'w', 'a', 's', 'd'].includes(event.key)) {
sendCommand('S');
}
});
// Prevent context menu on long press
document.addEventListener('contextmenu', function(event) {
event.preventDefault();
return false;
});
// Lock orientation (hint untuk browser)
if (screen.orientation && screen.orientation.lock) {
screen.orientation.lock('landscape').catch(function(error) {
console.log('Orientation lock failed: ', error);
});
}
// Initial focus
window.focus();
</script>
</body>
</html>
)rawliteral";
// Deklarasi fungsi
void HTTP_handleRoot(void);
void Forward();
void Backward();
void TurnRight();
void TurnLeft();
void Stop();
void BeepHorn();
void TurnLightOn();
void TurnLightOff();
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println("🤖 Robot Soccer - Android Landscape Mode");
Serial.println("--------------------------------------");
pinMode(buzPin, OUTPUT);
pinMode(ledPin, OUTPUT);
pinMode(wifiLedPin, OUTPUT);
digitalWrite(buzPin, LOW);
digitalWrite(ledPin, LOW);
digitalWrite(wifiLedPin, HIGH);
// Set all the motor control pins to outputs
pinMode(PWM_A, OUTPUT);
pinMode(PWM_B, OUTPUT);
pinMode(DIR_A, OUTPUT);
pinMode(DIR_B, OUTPUT);
pinMode(DIR_C, OUTPUT);
pinMode(DIR_D, OUTPUT);
// Turn off motors - Initial state
digitalWrite(DIR_A, LOW);
digitalWrite(DIR_B, LOW);
digitalWrite(DIR_C, LOW);
digitalWrite(DIR_D, LOW);
analogWrite(PWM_A, 0);
analogWrite(PWM_B, 0);
// WiFi connection
WiFi.mode(WIFI_STA);
WiFi.begin(sta_ssid, sta_password);
Serial.println("");
Serial.print("Connecting to: ");
Serial.println(sta_ssid);
unsigned long currentMillis = millis();
previousMillis = currentMillis;
while (WiFi.status() != WL_CONNECTED && currentMillis - previousMillis <= 10000) {
delay(500);
Serial.print(".");
currentMillis = millis();
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("");
Serial.println("✅ WiFi Connected");
Serial.print("📱 IP: ");
Serial.println(WiFi.localIP());
digitalWrite(wifiLedPin, LOW);
} else {
WiFi.mode(WIFI_AP);
String hostname = "soccer-bot-" + String(ESP.getChipId(), HEX);
WiFi.softAP(hostname.c_str());
IPAddress myIP = WiFi.softAPIP();
Serial.println("");
Serial.println("📱 AP Mode Activated");
Serial.print("📍 AP IP: ");
Serial.println(myIP);
digitalWrite(wifiLedPin, HIGH);
}
server.on("/", HTTP_handleRoot);
server.onNotFound(HTTP_handleRoot);
server.begin();
ArduinoOTA.begin();
Serial.println("🚀 Server started - Ready for Android control");
}
void loop() {
ArduinoOTA.handle();
server.handleClient();
command = server.arg("State");
if (command == "F") {
Forward();
Serial.println("📱 MAJU");
analogWrite(PWM_A, SPEED);
analogWrite(PWM_B, SPEED);
}
else if (command == "B") {
Backward();
Serial.println("📱 MUNDUR");
analogWrite(PWM_A, SPEED);
analogWrite(PWM_B, SPEED);
}
else if (command == "R") {
TurnRight();
Serial.println("📱 KANAN");
analogWrite(PWM_A, SPEED);
analogWrite(PWM_B, SPEED);
}
else if (command == "L") {
TurnLeft();
Serial.println("📱 KIRI");
analogWrite(PWM_A, SPEED);
analogWrite(PWM_B, SPEED);
}
else if (command == "S") {
Stop();
Serial.println("📱 STOP");
analogWrite(PWM_A, 0);
analogWrite(PWM_B, 0);
}
else if (command == "V") {
BeepHorn();
Serial.println("📱 HORN");
}
else if (command == "W") {
TurnLightOn();
Serial.println("📱 LAMPU ON");
}
else if (command == "w") {
TurnLightOff();
Serial.println("📱 LAMPU OFF");
}
else {
int speedValue = command.toInt();
if (speedValue >= 330 && speedValue <= 1023) {
SPEED = speedValue;
Serial.println("🎚️ Speed: " + String(SPEED));
}
}
}
void HTTP_handleRoot(void) {
server.send(200, "text/html", htmlPage);
}
// Movement functions
void Forward() {
digitalWrite(DIR_A, HIGH);
digitalWrite(DIR_B, LOW);
digitalWrite(DIR_C, HIGH);
digitalWrite(DIR_D, LOW);
}
void Backward() {
digitalWrite(DIR_A, LOW);
digitalWrite(DIR_B, HIGH);
digitalWrite(DIR_C, LOW);
digitalWrite(DIR_D, HIGH);
}
void TurnRight() {
digitalWrite(DIR_A, LOW);
digitalWrite(DIR_B, LOW);
digitalWrite(DIR_C, HIGH);
digitalWrite(DIR_D, LOW);
}
void TurnLeft() {
digitalWrite(DIR_A, HIGH);
digitalWrite(DIR_B, LOW);
digitalWrite(DIR_C, LOW);
digitalWrite(DIR_D, LOW);
}
void Stop() {
digitalWrite(DIR_A, LOW);
digitalWrite(DIR_B, LOW);
digitalWrite(DIR_C, LOW);
digitalWrite(DIR_D, LOW);
}
void BeepHorn() {
digitalWrite(buzPin, HIGH);
delay(150);
digitalWrite(buzPin, LOW);
delay(80);
}
void TurnLightOn() {
digitalWrite(ledPin, HIGH);
}
void TurnLightOff() {
digitalWrite(ledPin, LOW);
}
1. KONEKSI WIFI
Robot akan mencoba connect ke WiFi: www.asun86.com (192.168.88.42) / lihat pada serial monitor 115200
Jika gagal, robot membuat Hotspot AP sendiri
Nama hotspot: soccer-bot-xxxxxx (ID unik ESP8266)
2. CARA MENEMUKAN IP ROBOT
Via Serial Monitor:
Buka Serial Monitor di Arduino IDE, akan muncul IP address seperti:
✅ WiFi Connected
📱 IP: 192.168.1.100 // Catat IP ini
Via AP Mode:
Connect ke hotspot soccer-bot-xxxxxx
Buka browser, ketik: 192.168.4.1
3. AKSES CONTROL PANEL
Buka browser di HP/PC
Ketik IP address robot (contoh: 192.168.88.42)
Tekan Enter
KONTROL MOVEMENT:
↑ atau W = MAJU (Robot bergerak maju)
↓ atau S = MUNDUR (Robot bergerak mundur)
← atau A = KIRI (Belok kiri di tempat)
→ atau D = KANAN (Belok kanan di tempat)
SPACE = STOP (Berhenti seketika)
KONTROL FITUR:
H = HORN (Bunyi buzzer/klakson)
L = LAMPU (Nyala/mati lampu LED)
FITUR OTOMATIS:
Auto-stop: Lepas tombol arrow/WASD → robot otomatis stop
Responsif: Tombol langsung merespons tanpa delay
Pastikan koneksi stabil antara device dan robot
Gunakan landscape mode untuk tampilan optimal
Untuk kontrol halus: Tekan tombol movement secara singkat
Speed control: Gunakan slider di web interface untuk atur kecepatan (330-1023)
Tidak bisa konek?
Cek IP di Serial Monitor
Pastikan device dan robot dalam jaringan WiFi yang sama
Jika AP mode, connect ke hotspot robot
Tombol tidak responsif?
Refresh halaman web
Cek koneksi internet
Pastikan robot masih menyala
Control Panel tidak loading?
Clear cache browser
Gunakan browser Chrome/Firefox
Nonaktifkan ad blocker
Robot siap dikendalikan!