WiFi Remote Control RC Car Code For ESP32
#include <WiFi.h>
#include <WebServer.h>
#include <esp_wifi.h>
#define RELAY_FORWARD 26
#define RELAY_LEFT 27
#define ACTIVE_MODE LOW
WebServer server(80);
const char* ap_ssid = "CarRemote";
const char* ap_password = "123456789";
int lastRssi = -100;
unsigned long lastRssiUpdate = 0;
void setup() {
Serial.begin(115200);
pinMode(RELAY_FORWARD, OUTPUT);
pinMode(RELAY_LEFT, OUTPUT);
digitalWrite(RELAY_FORWARD, !ACTIVE_MODE);
digitalWrite(RELAY_LEFT, !ACTIVE_MODE);
WiFi.softAP(ap_ssid, ap_password);
server.on("/", handleRoot);
server.on("/forward", []{ controlRelay(RELAY_FORWARD, ACTIVE_MODE); });
server.on("/left", []{ controlRelay(RELAY_LEFT, ACTIVE_MODE); });
server.on("/stop", handleStop);
server.on("/status", handleStatus);
server.begin();
Serial.println("Car Remote Controller Ready!");
Serial.print("AP IP: "); Serial.println(WiFi.softAPIP());
}
void loop() {
server.handleClient();
if(millis() - lastRssiUpdate > 2000) {
lastRssi = getRssi();
lastRssiUpdate = millis();
}
}
void controlRelay(int pin, int state) {
digitalWrite(pin, state);
server.send(200, "text/plain", "OK");
}
void handleStop() {
digitalWrite(RELAY_FORWARD, !ACTIVE_MODE);
digitalWrite(RELAY_LEFT, !ACTIVE_MODE);
server.send(200, "text/plain", "STOPPED");
}
void handleStatus() {
String json = "{";
json += "\"forward\":";
json += (digitalRead(RELAY_FORWARD) == ACTIVE_MODE) ? "1" : "0";
json += ",\"left\":";
json += (digitalRead(RELAY_LEFT) == ACTIVE_MODE) ? "1" : "0";
json += ",\"rssi\":";
json += String(lastRssi);
json += "}";
server.send(200, "application/json", json);
}
int getRssi() {
wifi_sta_list_t station_list;
esp_wifi_ap_get_sta_list(&station_list);
if(station_list.num > 0) {
return station_list.sta[0].rssi;
}
return -100;
}
void handleRoot() {
String html = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Animated Car Controller</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
-webkit-tap-highlight-color: transparent;
}
body {
background: linear-gradient(135deg, #1a1a2e, #16213e);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
overflow: hidden;
}
.container {
width: 100%;
max-width: 420px;
background: rgba(0, 0, 0, 0.75);
border-radius: 25px;
padding: 30px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6);
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.15);
position: relative;
overflow: hidden;
}
/* Animated background elements */
.bg-elements {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
overflow: hidden;
}
.circle {
position: absolute;
border-radius: 50%;
background: radial-gradient(circle, rgba(144,224,239,0.1) 0%, transparent 70%);
animation: float 15s infinite linear;
}
.circle:nth-child(1) {
width: 120px;
height: 120px;
top: -30px;
left: -30px;
animation-delay: 0s;
}
.circle:nth-child(2) {
width: 80px;
height: 80px;
bottom: -20px;
right: -20px;
animation-delay: -5s;
animation-direction: reverse;
}
.circle:nth-child(3) {
width: 60px;
height: 60px;
top: 40%;
right: -30px;
animation-delay: -10s;
}
@keyframes float {
0% { transform: translate(0, 0) rotate(0deg); }
25% { transform: translate(10px, 10px) rotate(90deg); }
50% { transform: translate(0, 20px) rotate(180deg); }
75% { transform: translate(-10px, 10px) rotate(270deg); }
100% { transform: translate(0, 0) rotate(360deg); }
}
header {
text-align: center;
margin-bottom: 30px;
position: relative;
z-index: 1;
transform: translateY(0);
transition: transform 0.3s ease;
}
header:hover {
transform: translateY(-5px);
}
h1 {
color: #fff;
font-size: 32px;
text-transform: uppercase;
letter-spacing: 2px;
margin-bottom: 8px;
text-shadow: 0 3px 6px rgba(0,0,0,0.6);
background: linear-gradient(to right, #00b4d8, #90e0ef);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
position: relative;
display: inline-block;
}
h1::after {
content: '';
position: absolute;
bottom: -8px;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(to right, #00b4d8, #90e0ef);
border-radius: 3px;
transform: scaleX(0);
transform-origin: right;
transition: transform 0.5s ease;
}
h1:hover::after {
transform: scaleX(1);
transform-origin: left;
}
h2 {
color: #90e0ef;
font-size: 18px;
font-weight: 400;
letter-spacing: 1px;
opacity: 0.8;
}
.wifi-indicator {
display: flex;
align-items: center;
justify-content: center;
margin: 25px 0 30px;
padding: 15px;
background: rgba(30, 30, 50, 0.7);
border-radius: 15px;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
position: relative;
overflow: hidden;
z-index: 1;
}
.wifi-indicator::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(144, 224, 239, 0.1), transparent);
transition: left 0.6s ease;
}
.wifi-indicator:hover::before {
left: 100%;
}
.wifi-icon {
font-size: 32px;
margin-right: 15px;
color: #90e0ef;
display: flex;
align-items: center;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
.wifi-bar {
width: 160px;
height: 22px;
background: #1e1e32;
border-radius: 12px;
overflow: hidden;
position: relative;
box-shadow: inset 0 0 8px rgba(0,0,0,0.4);
}
.wifi-level {
height: 100%;
background: linear-gradient(90deg, #00b4d8, #90e0ef);
border-radius: 12px;
width: 75%;
transition: width 0.8s ease;
}
.wifi-percentage {
color: white;
margin-left: 12px;
font-weight: bold;
font-size: 20px;
min-width: 50px;
text-shadow: 0 1px 3px rgba(0,0,0,0.5);
}
.controls {
display: grid;
grid-template-columns: 1fr;
gap: 25px;
margin-top: 20px;
position: relative;
z-index: 1;
}
.control-row {
display: flex;
justify-content: center;
}
.control-btn {
width: 200px;
height: 200px;
border-radius: 50%;
background: linear-gradient(145deg, #1a1a1a, #000000);
color: white;
border: none;
font-size: 24px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1.5px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
cursor: pointer;
margin: 10px;
box-shadow:
0 10px 20px rgba(0,0,0,0.4),
inset 0 0 15px rgba(0, 180, 216, 0.2);
transition: all 0.2s ease;
position: relative;
overflow: hidden;
border: 4px solid #2a2a40;
transform: translateY(0) scale(1);
z-index: 1;
}
.control-btn:hover {
transform: translateY(-5px) scale(1.03);
box-shadow:
0 15px 25px rgba(0,0,0,0.5),
inset 0 0 20px rgba(0, 180, 216, 0.3);
}
.control-btn:active {
transform: translateY(2px) scale(0.98);
box-shadow:
0 5px 12px rgba(0,0,0,0.3),
inset 0 0 10px rgba(0, 180, 216, 0.4);
}
.control-btn::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle at center, transparent 10%, rgba(0, 180, 216, 0.1) 100%);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.control-btn:hover::before {
opacity: 1;
}
.control-btn::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 300px;
height: 300px;
background: radial-gradient(circle, rgba(255,255,255,0.15) 0%, transparent 70%);
transform: translate(-50%, -50%) scale(0);
opacity: 0;
transition:
transform 0.5s ease,
opacity 0.5s ease;
pointer-events: none;
}
.control-btn:active::after {
transform: translate(-50%, -50%) scale(1);
opacity: 1;
transition:
transform 0.3s ease,
opacity 0.3s ease;
}
.btn-icon {
width: 80px;
height: 80px;
margin-bottom: 18px;
display: flex;
justify-content: center;
align-items: center;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.5));
transition: transform 0.3s ease;
}
.control-btn:hover .btn-icon {
transform: scale(1.1);
}
.forward-btn {
background: linear-gradient(145deg, #1a1a1a, #000000);
}
.forward-btn.active {
background: linear-gradient(145deg, #00a300, #007500);
box-shadow:
0 0 30px rgba(0, 255, 100, 0.6),
inset 0 0 20px rgba(0, 255, 150, 0.4);
animation: glow 1.5s infinite alternate;
}
.left-btn {
background: linear-gradient(145deg, #1a1a1a, #000000);
}
.left-btn.active {
background: linear-gradient(145deg, #ff8c00, #cc7000);
box-shadow:
0 0 30px rgba(255, 165, 0, 0.6),
inset 0 0 20px rgba(255, 200, 0, 0.4);
animation: glow 1.5s infinite alternate;
}
@keyframes glow {
from { box-shadow:
0 0 20px rgba(0, 255, 100, 0.6),
inset 0 0 15px rgba(0, 255, 150, 0.4); }
to { box-shadow:
0 0 40px rgba(0, 255, 100, 0.8),
inset 0 0 25px rgba(0, 255, 150, 0.6); }
}
.stop-btn {
width: 160px;
height: 160px;
background: linear-gradient(145deg, #1a1a1a, #000000);
font-size: 20px;
}
.stop-btn.active {
background: linear-gradient(145deg, #ff0000, #cc0000);
box-shadow:
0 0 30px rgba(255, 0, 0, 0.6),
inset 0 0 20px rgba(255, 100, 100, 0.4);
animation: pulse 0.8s infinite alternate;
}
.status-indicators {
display: flex;
justify-content: space-around;
margin-top: 25px;
padding: 15px;
background: rgba(30, 30, 50, 0.5);
border-radius: 15px;
position: relative;
overflow: hidden;
z-index: 1;
}
.status-indicators::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(to right, #00b4d8, #90e0ef);
transform: scaleX(0);
transform-origin: left;
transition: transform 0.5s ease;
}
.status-indicators:hover::before {
transform: scaleX(1);
}
.status-item {
display: flex;
flex-direction: column;
align-items: center;
}
.status-label {
color: #90e0ef;
font-size: 16px;
margin-bottom: 8px;
opacity: 0.8;
}
.status-value {
color: white;
font-size: 20px;
font-weight: bold;
text-shadow: 0 1px 3px rgba(0,0,0,0.5);
padding: 5px 15px;
border-radius: 20px;
transition: all 0.3s ease;
}
.status-on {
background: linear-gradient(to right, #00c853, #64dd17);
box-shadow: 0 0 15px rgba(0, 200, 83, 0.4);
transform: scale(1.1);
}
.status-off {
background: linear-gradient(to right, #ff5252, #ff867f);
box-shadow: 0 0 15px rgba(255, 82, 82, 0.4);
}
.connection-info {
text-align: center;
margin-top: 25px;
padding: 15px;
background: rgba(30, 30, 50, 0.5);
border-radius: 15px;
position: relative;
z-index: 1;
}
.connection-info::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(to right, #00b4d8, #90e0ef);
border-radius: 3px;
transform: scaleX(0);
transform-origin: right;
transition: transform 0.5s ease;
}
.connection-info:hover::after {
transform: scaleX(1);
transform-origin: left;
}
.connection-label {
color: #90e0ef;
font-size: 16px;
margin-bottom: 8px;
}
.connection-value {
color: white;
font-size: 18px;
font-weight: bold;
text-shadow: 0 1px 3px rgba(0,0,0,0.5);
word-break: break-all;
}
/* Button press animation */
@keyframes press {
0% { transform: translateY(0) scale(1); }
50% { transform: translateY(4px) scale(0.98); }
100% { transform: translateY(0) scale(1); }
}
.press-animation {
animation: press 0.3s ease;
}
</style>
</head>
<body>
<div class="container">
<div class="bg-elements">
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
</div>
<header>
<h1>CAR REMOTE CONTROLLER</h1>
<h2>Animated Touch Interface</h2>
</header>
<div class="wifi-indicator">
<div class="wifi-icon">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 21C13.1046 21 14 20.1046 14 19C14 17.8954 13.1046 17 12 17C10.8954 17 10 17.8954 10 19C10 20.1046 10.8954 21 12 21Z" fill="#90e0ef"/>
<path d="M8.00004 16C8.00004 16 9.00004 15 12 15C15 15 16 16 16 16" stroke="#90e0ef" stroke-width="2" stroke-linecap="round"/>
<path d="M4.00004 12C4.00004 12 6.00004 10 12 10C18 10 20 12 20 12" stroke="#90e0ef" stroke-width="2" stroke-linecap="round"/>
<path d="M1.00004 8C1.00004 8 4.00004 5 12 5C20 5 23 8 23 8" stroke="#90e0ef" stroke-width="2" stroke-linecap="round"/>
</svg>
</div>
<div class="wifi-bar">
<div class="wifi-level" id="wifiLevel"></div>
</div>
<div class="wifi-percentage" id="wifiPercent">75%</div>
</div>
<div class="controls">
<div class="control-row">
<button class="control-btn forward-btn" id="forwardBtn"
ontouchstart="startForward(event)"
ontouchend="stopRelays()"
onmousedown="startForward(event)"
onmouseup="stopRelays()"
onmouseleave="stopRelays()">
<div class="btn-icon">
<svg width="80" height="80" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 13L6.5 10.5L8 13H5Z" fill="white"/>
<path d="M19 13H16L17.5 10.5L19 13Z" fill="white"/>
<path d="M10 13H14V9H10V13Z" fill="white"/>
<path d="M5 16H19V18C19 18.5523 18.5523 19 18 19H6C5.44772 19 5 18.5523 5 18V16Z" fill="white"/>
<path d="M18 6H6C5.44772 6 5 6.44772 5 7V13H19V7C19 6.44772 18.5523 6 18 6Z" fill="white"/>
<rect x="8" y="9" width="2" height="2" fill="#1a1a1a"/>
<rect x="14" y="9" width="2" height="2" fill="#1a1a1a"/>
</svg>
</div>
FORWARD
</button>
</div>
<div class="control-row">
<button class="control-btn left-btn" id="leftBtn"
ontouchstart="startLeft(event)"
ontouchend="stopRelays()"
onmousedown="startLeft(event)"
onmouseup="stopRelays()"
onmouseleave="stopRelays()">
<div class="btn-icon">
<svg width="80" height="80" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 16.5L7 13L10.5 9.5" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 13H16C17.6569 13 19 11.6569 19 10V10C19 8.34315 17.6569 7 16 7H7" stroke="white" stroke-width="2" stroke-linecap="round"/>
</svg>
</div>
LEFT TURN
</button>
</div>
<div class="control-row">
<button class="control-btn stop-btn" id="stopBtn"
ontouchstart="stopRelays()"
onmousedown="stopRelays()">
<div class="btn-icon">
<svg width="60" height="60" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="5" y="5" width="14" height="14" rx="2" fill="white"/>
</svg>
</div>
EMERGENCY STOP
</button>
</div>
</div>
<div class="status-indicators">
<div class="status-item">
<div class="status-label">FORWARD STATUS</div>
<div class="status-value status-off" id="forwardStatus">OFF</div>
</div>
<div class="status-item">
<div class="status-label">LEFT STATUS</div>
<div class="status-value status-off" id="leftStatus">OFF</div>
</div>
</div>
<div class="connection-info">
<div class="connection-label">CONNECTED TO</div>
<div class="connection-value" id="connectionValue">CarRemote (192.168.4.1)</div>
</div>
</div>
<script>
let activeBtn = null;
let holdTimer = null;
let isActive = false;
// Button press animation
function animateButton(btn) {
btn.classList.add('press-animation');
setTimeout(() => {
btn.classList.remove('press-animation');
}, 300);
}
function startForward(e) {
e.preventDefault();
if(activeBtn) activeBtn.classList.remove('active');
activeBtn = document.getElementById('forwardBtn');
activeBtn.classList.add('active');
document.getElementById('forwardStatus').textContent = 'ON';
document.getElementById('forwardStatus').className = 'status-value status-on';
animateButton(activeBtn);
// Send command to ESP32
fetch('/forward');
// Continuous hold
holdTimer = setInterval(() => {
// Keep relay active while holding
fetch('/forward');
}, 200);
}
function startLeft(e) {
e.preventDefault();
if(activeBtn) activeBtn.classList.remove('active');
activeBtn = document.getElementById('leftBtn');
activeBtn.classList.add('active');
document.getElementById('leftStatus').textContent = 'ON';
document.getElementById('leftStatus').className = 'status-value status-on';
animateButton(activeBtn);
// Send command to ESP32
fetch('/left');
// Continuous hold
holdTimer = setInterval(() => {
// Keep relay active while holding
fetch('/left');
}, 200);
}
function stopRelays() {
if(activeBtn) {
activeBtn.classList.remove('active');
activeBtn = null;
}
if(holdTimer) {
clearInterval(holdTimer);
holdTimer = null;
}
document.getElementById('forwardStatus').textContent = 'OFF';
document.getElementById('forwardStatus').className = 'status-value status-off';
document.getElementById('leftStatus').textContent = 'OFF';
document.getElementById('leftStatus').className = 'status-value status-off';
// Send stop command to ESP32
fetch('/stop');
animateButton(document.getElementById('stopBtn'));
}
function updateStatus() {
fetch('/status')
.then(response => response.json())
.then(data => {
// Update relay status
document.getElementById('forwardStatus').textContent =
data.forward ? 'ON' : 'OFF';
document.getElementById('forwardStatus').className =
'status-value ' + (data.forward ? 'status-on' : 'status-off');
document.getElementById('leftStatus').textContent =
data.left ? 'ON' : 'OFF';
document.getElementById('leftStatus').className =
'status-value ' + (data.left ? 'status-on' : 'status-off');
// Update button active state
document.getElementById('forwardBtn').classList.toggle('active', data.forward);
document.getElementById('leftBtn').classList.toggle('active', data.left);
// Update WiFi signal
let percent = 0;
if(data.rssi > -50) percent = 100;
else if(data.rssi > -60) percent = 80;
else if(data.rssi > -70) percent = 60;
else if(data.rssi > -80) percent = 40;
else if(data.rssi > -90) percent = 20;
else percent = 5;
document.getElementById('wifiLevel').style.width = percent + '%';
document.getElementById('wifiPercent').innerText = percent + '%';
});
}
// Update status every second
setInterval(updateStatus, 1000);
window.onload = updateStatus;
</script>
</body>
</html>
)rawliteral";
server.send(200, "text/html", html);
}
Comments
Post a Comment