Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #include <WiFi.h>
- #include <WebServer.h>
- #include <DNSServer.h>
- #include <ESPmDNS.h>
- // ===== WiFi / AP Config =====
- const char* ssid = "STUG";
- const char* password = ""; // open
- IPAddress apIP(10, 10, 0, 1);
- IPAddress netM(255, 255, 255, 0);
- WebServer server(80);
- DNSServer dnsServer;
- // ===== Motor Pins =====
- const int LEFT_POS = 1;
- const int LEFT_NEG = 0;
- const int RIGHT_POS = 20;
- const int RIGHT_NEG = 21;
- // ===== State =====
- String currentCommand = "";
- String activeButton = "";
- unsigned long lastSend = 0;
- const unsigned long sendInterval = 100; // ms
- // ===== Web Page (controller) =====
- const char webpage[] PROGMEM = R"rawliteral(
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate, max-age=0">
- <title>STUG Controller</title>
- <style>
- html,body { height:100%; }
- body { margin:0; display:flex; height:100vh; justify-content:center; align-items:center; background:#111; }
- .grid { display:grid; grid-template-rows: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr; gap:10px; width:100%; max-width:480px; padding:16px; box-sizing:border-box; }
- button { font-size:2.5rem; width:100%; padding:24px; background:#444; color:#fff; border:none; border-radius:12px; touch-action:none; -webkit-tap-highlight-color: transparent; }
- button:active { background:#777; }
- </style>
- </head>
- <body>
- <div class="grid">
- <div></div>
- <button ontouchstart="press('forward')" ontouchend="release('forward')" onmousedown="press('forward')" onmouseup="release('forward')" onmouseleave="release('forward')">▲</button>
- <div></div>
- <button ontouchstart="press('left')" ontouchend="release('left')" onmousedown="press('left')" onmouseup="release('left')" onmouseleave="release('left')">◀</button>
- <div></div>
- <button ontouchstart="press('right')" ontouchend="release('right')" onmousedown="press('right')" onmouseup="release('right')" onmouseleave="release('right')">▶</button>
- <div></div>
- <button ontouchstart="press('backward')" ontouchend="release('backward')" onmousedown="press('backward')" onmouseup="release('backward')" onmouseleave="release('backward')">▼</button>
- <div></div>
- </div>
- <script>
- // Use keep-alive OFF to avoid caching/probe weirdness
- const params = { cache: 'no-store', headers: { 'Cache-Control': 'no-store', 'Pragma': 'no-cache', 'Connection': 'close' } };
- function press(cmd) { fetch("/cmd?c=" + cmd + "&a=down", params); }
- function release(cmd) { fetch("/cmd?c=" + cmd + "&a=up", params); }
- // Safety: stop if page is hidden (user switches apps)
- document.addEventListener('visibilitychange', () => {
- if (document.hidden) fetch('/cmd?c=safe&a=up', params);
- });
- </script>
- </body>
- </html>
- )rawliteral";
- // ===== Util: common headers =====
- void addNoCacheClose() {
- server.sendHeader("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");
- server.sendHeader("Pragma", "no-cache");
- server.sendHeader("Connection", "close");
- }
- // ===== Serve controller (with host redirect) =====
- void serveControllerPage() {
- addNoCacheClose();
- String host = server.hostHeader();
- String ipStr = apIP.toString();
- // If the Host header isn't our IP or mDNS name, force a redirect to our root.
- if (host != ipStr && host != "stug.local") {
- server.sendHeader("Location", String("http://") + ipStr + "/", true);
- server.send(302, "text/plain", "");
- return;
- }
- server.send(200, "text/html", webpage);
- }
- // ===== Handle button commands =====
- void handleCommand() {
- if (server.hasArg("c") && server.hasArg("a")) {
- String c = server.arg("c");
- String a = server.arg("a");
- if (a == "down") {
- activeButton = c;
- currentCommand = c;
- } else if (a == "up") {
- if (activeButton == c) {
- activeButton = "";
- currentCommand = "";
- }
- }
- }
- addNoCacheClose();
- server.send(200, "text/plain", "OK");
- }
- // ===== Motor Control =====
- void setMotors(const String& cmd) {
- if (cmd == "forward") {
- digitalWrite(LEFT_POS, HIGH); digitalWrite(LEFT_NEG, LOW);
- digitalWrite(RIGHT_POS, HIGH); digitalWrite(RIGHT_NEG, LOW);
- } else if (cmd == "backward") {
- digitalWrite(LEFT_POS, LOW); digitalWrite(LEFT_NEG, HIGH);
- digitalWrite(RIGHT_POS, LOW); digitalWrite(RIGHT_NEG, HIGH);
- } else if (cmd == "left") {
- digitalWrite(LEFT_POS, LOW); digitalWrite(LEFT_NEG, HIGH);
- digitalWrite(RIGHT_POS, HIGH); digitalWrite(RIGHT_NEG, LOW);
- } else if (cmd == "right") {
- digitalWrite(LEFT_POS, HIGH); digitalWrite(LEFT_NEG, LOW);
- digitalWrite(RIGHT_POS, LOW); digitalWrite(RIGHT_NEG, HIGH);
- } else {
- digitalWrite(LEFT_POS, LOW); digitalWrite(LEFT_NEG, LOW);
- digitalWrite(RIGHT_POS, LOW); digitalWrite(RIGHT_NEG, LOW);
- }
- }
- // ===== Captive Portal helpers =====
- // For all probe endpoints, answer with 200 and small HTML (not 204), to trigger portal.
- void captiveOK() {
- addNoCacheClose();
- server.send(200, "text/html",
- "<!doctype html><meta name='viewport' content='width=device-width, initial-scale=1'>"
- "<title>Sign-in</title><p>Redirecting...</p>"
- "<script>location.replace('/');</script>");
- }
- // For everything else, 302 to root so the address bar is nice.
- void redirectToRoot() {
- addNoCacheClose();
- server.sendHeader("Location", String("http://") + apIP.toString() + "/", true);
- server.send(302, "text/plain", "");
- }
- void setup() {
- Serial.begin(115200);
- delay(400);
- Serial.println("\nBooting...");
- // Motor pin setup
- pinMode(LEFT_POS, OUTPUT);
- pinMode(LEFT_NEG, OUTPUT);
- pinMode(RIGHT_POS, OUTPUT);
- pinMode(RIGHT_NEG, OUTPUT);
- setMotors(""); // stop at startup
- // Wi-Fi AP setup (more deterministic for picky phones)
- WiFi.persistent(false);
- WiFi.mode(WIFI_MODE_AP);
- WiFi.setSleep(false); // reliability
- WiFi.softAPConfig(apIP, apIP, netM);
- WiFi.softAP(ssid, password, 1 /*chan*/, 0 /*hidden*/, 4 /*max clients*/);
- delay(150);
- Serial.print("AP IP: "); Serial.println(WiFi.softAPIP());
- // DNS catch-all (lower TTL, no error replies to avoid weird caching)
- dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
- dnsServer.setTTL(0);
- dnsServer.start(53, "*", apIP);
- // mDNS for convenience: http://stug.local
- if (MDNS.begin("stug")) {
- MDNS.addService("http", "tcp", 80);
- Serial.println("mDNS started: http://stug.local");
- } else {
- Serial.println("mDNS start failed (continuing).");
- }
- // Web routes
- server.on("/", HTTP_ANY, serveControllerPage);
- server.on("/cmd", HTTP_ANY, handleCommand);
- // --- Captive portal probe endpoints (serve HTML 200 + JS redirect) ---
- // Google/Android
- server.on("/generate_204", HTTP_ANY, captiveOK);
- server.on("/gen_204", HTTP_ANY, captiveOK);
- // Microsoft/Windows
- server.on("/connecttest.txt", HTTP_ANY, captiveOK);
- server.on("/ncsi.txt", HTTP_ANY, captiveOK);
- // Apple
- server.on("/hotspot-detect.html", HTTP_ANY, captiveOK);
- server.on("/captive.apple.com", HTTP_ANY, captiveOK);
- // Samsung variants (HTTP)
- server.on("/connectivitycheck.txt", HTTP_ANY, captiveOK);
- server.on("/mobile/success.html", HTTP_ANY, captiveOK);
- // Some carriers poke simple roots like "/"
- server.on("/success.txt", HTTP_ANY, captiveOK);
- // Favicon etc. → just redirect to root
- server.on("/favicon.ico", HTTP_ANY, redirectToRoot);
- // Catch-all → 302 to "/"
- server.onNotFound(redirectToRoot);
- server.begin();
- Serial.println("ESP32-C3 Controller Ready!");
- }
- void loop() {
- dnsServer.processNextRequest();
- server.handleClient();
- // Stream the active command every 100ms
- if (!currentCommand.isEmpty()) {
- unsigned long now = millis();
- if (now - lastSend >= sendInterval) {
- lastSend = now;
- Serial.println(currentCommand);
- }
- }
- // Update motors
- setMotors(currentCommand);
- // Yield to Wi-Fi/stack
- delay(1);
- }
Advertisement
Add Comment
Please, Sign In to add comment