Web-Sumo Bot: การควบคุมหุ่นยนต์ซูโม่ผ่านเบราว์เซอร์ด้วย Web Interface
- Siwadon Heebkaew
- 18 มี.ค.
- ยาว 5 นาที
1. แนวคิดและการออกแบบ: เมื่อซอฟต์แวร์เป็นตัวกำหนดรูปร่าง
โปรเจกต์ Web-Sumo Bot เกิดจากไอเดียที่อยากทำหุ่นยนต์ที่ "ใครก็บังคับได้" โดยไม่ต้องมีรีโมทเฉพาะทาง แต่ใช้แค่สมาร์ทโฟนหรือ iPad ที่ทุกคนมีติดตัวอยู่แล้ว ผมจึงเลือกวางโครงสร้างทุกอย่างโดยให้ระบบ Web Interface เป็นตัวนำทาง
ในยุคที่เทคโนโลยี IoT (Internet of Things) เข้ามามีบทบาทมากขึ้น การพัฒนาหุ่นยนต์ไม่ได้จำกัดอยู่แค่การใช้รีโมทคอนโทรลแบบเดิมๆ อีกต่อไป บทความนี้จะพาไปทำความรู้จักกับ "Web-Sumo Bot" โปรเจกต์หุ่นยนต์ซูโม่จิ๋วที่รวมเอาศาสตร์ด้านวิศวกรรม การออกแบบ 3D และการพัฒนา Web Application เข้าไว้ด้วยกันใช้สำหรับแนวทางการสอนเด็กๆได้ครับ

2. โครงสร้างละการออกแบบตัวหุ่น (Hardware)
โครงสร้างหลักของ หุ่นยนต์ Web-Sumo Bot ถูกสร้างขึ้นด้วยเทคโนโลยี 3D Printing ซึ่งช่วยให้เราสามารถปรับเปลี่ยนอุปกรณ์เสริมตามกลยุทธ์การเล่นได้ทันทีและนำมาติดตั้งเข้ากับ บอร์ดFriendRobot Model HiFi ตัวหุ่นใช้ระบบขับเคลื่อน 2 ล้อหลัง โดยใช้ DC gear motor 3 - 6 V (1:48)พร้อมล้อหน้าแบบอิสระ ทำให้มีความคล่องตัวสูงในการหมุนตัวกลับลำในสนามวงกลม

3. ระบบการควบคุมและ Dashboard (Software & Control)
หัวใจสำคัญที่ทำให้หุ่นยนต์ตัวนี้ทำงานได้ คือการผสมผสานระหว่างซอฟต์แวร์ที่ฝังอยู่ในบอร์ด (Firmware) และหน้าเว็บควบคุม โดยแบ่งจุดเด่นออกเป็น 3 ส่วนหลัก:
การเชื่อมต่อไร้สาย (Wi-Fi Access Point): ตัวหุ่นยนต์จะทำหน้าที่เป็นแม่ข่ายปล่อยสัญญาณ Wi-Fi ออกมาเอง (ใช้คำสั่ง WiFi.softAP) ทำให้เราสามารถใช้ iPad หรือสมาร์ทโฟนเชื่อมต่อเข้ากับหุ่นยนต์ได้โดยตรง ไม่ต้องผ่านอินเทอร์เน็ตบ้าน เหมาะมากสำหรับการนำไปใช้สอนตามห้องเรียนหรือสนามแข่งที่ไม่มี Wi-Fi
อินเตอร์เฟซการควบคุม (Web Dashboard): หน้าจอควบคุมถูกออกแบบให้ใช้งานง่ายและตอบสนองเร็ว (Low Latency) มีปุ่มบังคับทิศทางที่ชัดเจน พร้อมแถบสไลเดอร์ปรับความเร็วได้แบบ Real-time ตั้งแต่ 0-60 และมีมาตรวัดดิจิทัลเพื่อบอกสถานะความเร็วปัจจุบัน ช่วยให้ผู้บังคับกะจังหวะการเคลื่อนที่ได้แม่นยำ
ฟังก์ชันเสริมและโหมดไม้ตาย (Action Features): เราได้เพิ่มฟังก์ชันที่ช่วยให้การแข่งซูโม่สนุกขึ้น เช่น NITRO

อินเตอร์เฟซการควบคุม (สามารถดูรายละเอียดโค้ดการทำงานทั้งหมดได้ที่ส่วนท้ายของบทความ)
4. ประสบการณ์การใช้งานจริง (Practical Application)
หลังจากที่พัฒนาทั้งตัวหุ่นและระบบ Dashboard เสร็จสิ้น ขั้นตอนที่สำคัญที่สุดคือการนำมาใช้งานจริงในสภาพแวดล้อมต่างๆ ซึ่งทำให้เราเห็นประสิทธิภาพของระบบในหลายมิติ:
การเชื่อมต่อที่เสถียรในพื้นที่เปิด: จากการใช้งานจริง ระบบ Wi-Fi Hotspot ของบอร์ด FriendRobot สามารถคลุมพื้นที่สนามแข่งซูโม่ได้อย่างทั่วถึง การควบคุมผ่าน iPad ให้ความรู้สึกไหลลื่น ไม่ต่างจากการใช้รีโมทวิทยุแบบเดิม แต่ได้เปรียบตรงที่สามารถดูสถานะความเร็วและปรับค่าได้ทันทีจากหน้าจอ
ความคล่องตัวในสนาม: เนื่องจากใช้มอเตอร์เกียร์ 1:48 คู่กับล้อหน้าแบบอิสระ ในการใช้งานจริงหุ่นยนต์สามารถหมุนตัวกลับลำ (U-Turn) ได้ในพื้นที่แคบๆ ซึ่งเป็นหัวใจสำคัญในการแข่งขันซูโม่เพื่อหลบหลีกการถูกผลักออกนอกสนาม
ความยืดหยุ่นของอุปกรณ์เสริม: ในการใช้งานจริงเราสามารถสลับ "โมดูลหน้า" ได้ตามคู่ต่อสู้ หากเจอหุ่นที่ตัวสูงเราจะใช้แขนคีบ (Gripper) เพื่อยกให้เสียสมดุล หรือหากเจอสายพุ่งชนเราจะใช้แผ่นผลัก (Shield) เพื่อรับแรงปะทะ ซึ่งการออกแบบด้วย 3D Print ทำให้การเปลี่ยนหน้างานทำได้รวดเร็วภายในเวลาไม่กี่นาที
สื่อการเรียนรู้ที่มีชีวิต: นอกเหนือจากการแข่งขัน เราได้ลองนำไปใช้เป็นสื่อการสอน พบว่าเด็กๆ ให้ความสนใจกับการเปลี่ยนสีหน้าจอ Dashboard และการสั่ง HORN หรือ DANCE มากกว่าการบังคับทิศทางเพียงอย่างเดียว ทำให้การเรียนรู้เรื่อง Coding กลายเป็นเรื่องที่สนุกและเข้าถึงง่ายสำหรับทุกคน

สรุปผลการพัฒนา
โปรเจกต์ Web-Sumo Bot นี้พิสูจน์ให้เห็นว่า การเรียนรู้เรื่องหุ่นยนต์ในปัจจุบันไม่จำเป็นต้องเริ่มจากสิ่งที่ยากเสมอไป การใช้บอร์ด FriendRobot Model HiFi ร่วมกับเทคโนโลยีเว็บเบราว์เซอร์ ช่วยลดกำแพงเรื่องอุปกรณ์ควบคุมลงได้มหาศาลและทุนในการสร้างหุ่นยนต์ไว้เล่นเอง ทำให้เด็กๆ หรือผู้ที่สนใจสามารถสนุกไปกับการเขียนโปรแกรม (Coding) และการออกแบบวิศวกรรม (Engineering) ได้พร้อมๆ กัน
สินค้าในบทความ
ดูเพิ่มเติมได้ที่ https://www.friendrobotshop.com/
สนใจสอบถามข้อมูลเพิ่มเติมเกี่ยวกับหุ่นยนต์เพื่อการศึกษา ติดต่อ LINE: [ID LINE @friendrobot]
NITRO Mode: ปุ่มกดเร่งพลังมอเตอร์ไปที่ 100% ชั่วขณะ เพื่อใช้พุ่งชนหรือเผด็จศึกคู่ต่อสู้
HORN & BEEP: ระบบส่งเสียงสัญญาณเพื่อสร้างสีสันในการแข่งขัน
AUTOPILOT: โหมดโปรแกรมอัตโนมัติที่ช่วยให้หุ่นยนต์ขยับเองได้ เพื่อเป็นแนวทางในการต่อยอดด้านวิทยาการหุ่นยนต์ (Robotics)
Full Source Code (Arduino)
#include <ModelHifi.h> // เรียกใช้คำสั่งพื้นฐานของบอร์ด FriendRobot
#include <WiFi.h> // เรียกใช้ระบบ Wi-Fi สำหรับสร้างจุดเชื่อมต่อ
#include <WebServer.h> // เรียกใช้ระบบจัดการหน้าเว็บควบคุม
// --- ตั้งชื่อ Wi-Fi และ รหัสผ่าน ---
const char* ssid = "My_Robot_iPad4";
const char* password = "12345678";
int mySpeed = 30;
unsigned long nitroStartTime = 0; // เก็บเวลาที่เริ่มกดไนตรัส
bool isNitroActive = false; // เช็คสถานะว่ากำลังใช้ไนตรัสอยู่ไหม
WebServer server(80); // กำหนดให้ Server ทำงานที่พอร์ต 80 (พอร์ตมาตรฐานเว็บ)
String carDirection = "STOP"; // ตัวแปรเก็บทิศทาง (STOP, FORWARD, BACKWARD, LEFT, RIGHT)
// --- ส่วนโครงสร้างหน้าเว็บ (HTML) ---
// ใช้ R"raw( ... )raw" เพื่อให้เขียน HTML ได้หลายบรรทัดโดยไม่ Error
String html = R"raw(
<!DOCTYPE html>
<html lang="th">
<head>
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, orientation=landscape'>
<style>
:root {
--bg-color: #1e2124;
--panel-color: #2f3136;
--accent-blue: #7289da;
--accent-mint: #43b581;
--accent-nitro: #faa61a; /* สีส้มทอง สบายตากว่าแดง */
--text-main: #ffffff;
--text-dim: #b9bbbe;
}
body {
background-color: var(--bg-color); color: var(--text-main); font-family: 'Segoe UI', Roboto, sans-serif;
margin: 0; padding: 0; height: 100vh; overflow: hidden;
display: flex; justify-content: space-between; align-items: center;
}
/* --- ฝั่งซ้าย: การควบคุมทิศทาง --- */
.left-controls { padding-left: 50px; display: flex; align-items: center; gap: 30px; }
.d-pad {
display: grid;
grid-template-areas: ". up ." "left stop right" ". down .";
gap: 12px;
}
.btn {
width: 70px; height: 70px; border: none; border-radius: 18px;
background: var(--panel-color); color: var(--text-main);
font-size: 16px; font-weight: 600;
box-shadow: 0 4px 0px #18191c; transition: all 0.1s;
cursor: pointer;
}
.btn:active { transform: translateY(3px); box-shadow: 0 1px 0px #18191c; background: var(--accent-blue); }
/* ปุ่มเลี้ยวซ้าย-ขวา ใช้สีที่ต่างกันเล็กน้อยเพื่อความชัดเจน */
.btn-dir { background: #36393f; }
/* --- กลาง: Dashboard --- */
.dashboard { text-align: center; flex-grow: 1; }
#val { font-size: 70px; font-weight: 200; color: var(--accent-mint); font-family: 'Courier New', monospace; }
.unit { font-size: 14px; letter-spacing: 2px; color: var(--text-dim); text-transform: uppercase; }
/* --- ฝั่งขวา: ปุ่ม Action --- */
.right-controls { padding-right: 50px; display: flex; align-items: center; gap: 25px; }
.nitro-btn {
width: 110px; height: 110px; border-radius: 50%; border: none;
background: var(--accent-nitro); color: white; font-size: 20px; font-weight: 800;
box-shadow: 0 6px 15px rgba(250, 166, 26, 0.3); transition: 0.1s;
}
.nitro-btn:active { transform: scale(0.95); box-shadow: 0 2px 5px rgba(250, 166, 26, 0.5); }
/* --- สไลเดอร์ความเร็ว --- */
.speed-container { display: flex; flex-direction: column; align-items: center; }
.vertical-slider {
-webkit-appearance: none; width: 180px; height: 8px; background: #4f545c;
border-radius: 10px; transform: rotate(-90deg); margin: 80px -80px;
}
.vertical-slider::-webkit-slider-thumb {
-webkit-appearance: none; width: 30px; height: 30px;
background: var(--text-main); border-radius: 50%; border: 4px solid var(--accent-blue);
}
/* --- ปุ่มล่าง --- */
.extra-btns {
position: absolute; bottom: 25px; left: 50%; transform: translateX(-50%);
display: flex; gap: 20px;
}
.small-btn {
padding: 10px 25px; background: rgba(255,255,255,0.05); color: var(--text-dim);
border: 1px solid #4f545c; border-radius: 20px; font-size: 12px;
}
</style>
</head>
<body>
<div class="left-controls">
<div class="speed-container">
<input type="range" min="0" max="60" value="30" class="vertical-slider" id="spd" oninput="updateVal(this.value)">
</div>
<div class="d-pad">
<button class="btn" style="grid-area: up" onmousedown="send('F')" onmouseup="send('S')" ontouchstart="send('F')" ontouchend="send('S')">UP</button>
<button class="btn btn-dir" style="grid-area: left" onmousedown="send('L')" onmouseup="send('S')" ontouchstart="send('L')" ontouchend="send('S')">Left</button>
<button class="btn" style="grid-area: stop; background: #f04747; color: white;" onclick="send('S')">STOP</button>
<button class="btn btn-dir" style="grid-area: right" onmousedown="send('R')" onmouseup="send('S')" ontouchstart="send('R')" ontouchend="send('S')">Right</button>
<button class="btn" style="grid-area: down" onmousedown="send('B')" onmouseup="send('S')" ontouchstart="send('B')" ontouchend="send('S')">DOWN</button>
</div>
</div>
<div class="dashboard">
<div id="val">30</div>
<div class="unit">Kilometers / H</div>
</div>
<div class="right-controls">
<div style="display:flex; flex-direction:column; gap:15px;">
<button class="btn" style="background:var(--accent-blue);" onclick="send('HORN')">HORN</button>
<button class="btn" style="background:var(--panel-color);" onclick="send('LIGHT')">LIGHT</button>
</div>
<button class="nitro-btn" onmousedown="send('NITRO')" onmouseup="send('F')" ontouchstart="send('NITRO')" ontouchend="send('F')">NITRO</button>
</div>
<div class="extra-btns">
<button class="small-btn" onclick="fetch('/DANCE')">AUTOPILOT</button>
<button class="small-btn" onclick="fetch('/BEEP')">BEEP MODE</button>
</div>
<script>
function updateVal(v) {
document.getElementById('val').innerHTML = v;
fetch('/set?v=' + v);
}
function send(dir) {
let s = document.getElementById('spd').value;
let speedValue = (dir === 'NITRO') ? 100 : s;
fetch('/' + dir + '?v=' + speedValue);
}
</script>
</body>
</html>
)raw";
void setup() {
XIO();
oled(0, 0, "Robot Starting...");
WiFi.softAP(ssid, password);
IPAddress IP = WiFi.softAPIP();
oled(0, 0, "Wi-Fi: %s", ssid);
oled(0, 20, "IP: %s", IP.toString().c_str());
oled(0, 40, "Status: Ready!");
server.on("/", []() { server.send(200, "text/html", html); });
// --- ปรับปรุงการรับค่าความเร็วให้ Real-time ---
// สั่งเดินหน้า
motor(1, 0); motor(2, 0);
server.on("/F", []() {
carDirection = "FORWARD"; // ตัวแปรเก็บทิศทาง (STOP, FORWARD, BACKWARD, LEFT, RIGHT)
if(server.hasArg("v")) mySpeed = server.arg("v").toInt();
motor(1, mySpeed); motor(2, mySpeed); // สั่งทั้ง 2 ล้อ
oled(0, 40, "Action: FORWARD");
server.send(200, "text/plain", "OK");
});
// สั่งถอยหลัง
server.on("/B", []() {
carDirection = "BACKWARD"; // ตัวแปรเก็บทิศทาง (STOP, FORWARD, BACKWARD, LEFT, RIGHT)
if(server.hasArg("v")) mySpeed = server.arg("v").toInt();
motor(1, -mySpeed); motor(2, -mySpeed); // ใส่เครื่องหมายติดลบเพื่อถอยหลัง
oled(0, 40, "Action: BACKWARD");
server.send(200, "text/plain", "OK");
});
// สั่งเลี้ยวซ้าย (ล้อ 1 ถอย, ล้อ 2 เดินหน้า)
server.on("/L", []() {
carDirection = "LEFT"; // ตัวแปรเก็บทิศทาง (STOP, FORWARD, BACKWARD, LEFT, RIGHT)
if(server.hasArg("v")) mySpeed = server.arg("v").toInt();
motor(1, -mySpeed); motor(2, mySpeed);
server.send(200, "text/plain", "OK");
});
// สั่งเลี้ยวขวา (ล้อ 1 เดินหน้า, ล้อ 2 ถอย)
server.on("/R", []() {
carDirection = "RIGHT"; // ตัวแปรเก็บทิศทาง (STOP, FORWARD, BACKWARD, LEFT, RIGHT)
if(server.hasArg("v")) mySpeed = server.arg("v").toInt();
motor(1, mySpeed); motor(2, -mySpeed);
server.send(200, "text/plain", "OK");
});
// สั่งหยุด
server.on("/S", []() {
motor(1, 0); motor(2, 0);
oled(0, 40, "Action: STOPPED ");
server.send(200, "text/plain", "OK");
});
// ปุ่ม NITRO (วิ่งเต็มกำลัง 100)
server.on("/NITRO", []() {
motor(1, 100); motor(2, 100);
nitroStartTime = millis(); // บันทึกเวลาที่เริ่มกด
isNitroActive = true; // เปิดโหมดไนตรัส
oled(0, 40, "!!! NITRO !!!");
server.send(200, "text/plain", "OK");
});
// ปุ่ม DANCE (สะบัดซ้ายขวา)
server.on("/DANCE", []() {
motor(1, -80); motor(2, 80); delay(200);
motor(1, 80); motor(2, -80); delay(200);
motor(1, 0); motor(2, 0);
server.send(200, "text/plain", "OK");
});
// 2. HORN: บีบแตร
server.on("/HORN", []() {
beep();
oled(0, 40, "Action: HORN");
server.send(200, "text/plain", "OK");
});
// 3. LIGHT: เปิดไฟ (สมมติว่าใช้ขา 2 หรือคำสั่งเฉพาะของบอร์ด)
server.on("/LIGHT", []() {
// หากบอร์ดมีคำสั่งเปิดไฟ เช่น led(1) ให้ใส่ตรงนี้
oled(0, 40, "Action: LIGHT");
server.send(200, "text/plain", "OK");
});
server.on("/BEEP", []() {
beep(); delay(100); beep();
server.send(200, "text/plain", "OK");
});
server.on("/set", []() {
mySpeed = server.arg("v").toInt();
server.send(200, "text/plain", "OK");
});
server.begin();
}
void loop() {
server.handleClient(); // วนลูปเช็คว่ามีการกดปุ่มบนหน้าเว็บเข้ามาหรือไม่
// --- ระบบนับเวลาไนตรัส 5 วินาที ---
if (isNitroActive) {
// ถ้าเวลาปัจจุบัน ลบ เวลาที่เริ่ม มากกว่า 5000 มิลลิวินาที (5 วินาที)
if (millis() - nitroStartTime >= 2000) {
isNitroActive = false; // ปิดโหมดไนตรัส
motor(1, 0); // เพื่อความปลอดภัย แนะนำให้สั่งหยุดก่อน หรือปรับเป็นความเร็วเดิม
motor(2, 0);
oled(0, 40, "Nitro Timeout! ");
}
}
}

ความคิดเห็น