การควบคุม Servo และ LED ด้วย ESP8266 ผ่าน Web Browser
เมื่อต้นเดือนที่ผ่านมา มีภารกิจเล็กๆน้อยๆ โดยต้องการควบคุมกลไกง่ายๆ โดยใช้เซอร์โว 2 ตัว กับหลอด LED 1 ชุดและที่สำคัญคือมันจะต้องเป็นอะไรที่ Portable มีขนาดไม่ใหญ่มาก และเคลื่อนย้ายอุปกรณ์ได้ง่าย สามารถควบคุมผ่าน Android หรือ Iphone ได้
มองไปมองมา เห็นบอร์ด Nodemcu V2 (ESP-12E) อยู่ เลยคิดว่าจะเอามาใช้เป็น Access Point และทำเป็น Web Server เพื่อให้อุปกรณ์ เช่นโทรศัพท์ หรือคอมพิวเตอร์สามารถเข้ามาควบคุมได้โดยผ่าน เครือข่าย WIFI ของตัว Nodemcu เอง ทำให้ไม่จำเป็นต้องพัฒนาแอพพลิเคชั่นเพื่อรองรับอุปกรณ์ทุกตัว ซึ่งลดงานไปได้เยอะมาก
การทำงาน
จากในรูปเราทำการเชื่อมต่อ wireless ของมือถือเข้ากับ Access Point ของ Nodemcu แล้วเปิดเบราว์เซอร์เข้าไปที่ url 192.168.4.1 เมื่อเราทำการปรับเปลี่ยนค่าต่างๆในหน้าเว็บก็จะถูกส่งค่าไปให้ Nodemcu และสั่งงานควบคุมอุปกรณ์ปลายทาง
วิดีโอแสดงการใช้งาน
การเขียนหน้าเว็บ
หน้าเว็บที่เราสร้างประกอบไปด้วย 3 ภาษาคือ HTML, JavaScript และ CSS
HTML ใช้กำหนดโครงสร้างของเว็บ CSS ใช้กำหนดหน้าตาความสวยงามของส่วนต่างๆ และ JavaScript ใช้ในการรับค่าจากการเปลี่ยนแปลงของ Tag Element ต่างๆ แล้วส่งไปยัง Server Side ผ่าน URL
เรานำ HTML Input element มาใช้ 2 ประเภทด้วยกันคือ checkbox กับ range
Input ประเภท checkbox ใช้เพื่อควบคุม LED เพราะ check box มีค่าตั้งต้นเพียงสองค่าคือ True กับ False และสามารถนำ CSS มาใช้ปรับแต่งให้เป็นปุ่มรูปแบบ Toggle Switch ดูสวยงามใช้งานง่าย
Input ประเภท range ใช้เพื่อควบคุมตำแหน่งของเซอร์โว 2 ตัว เพราะมีลักษณะเป็น slider control เราสามารถกำหนดค่าสูงสุด-ต่ำสุดของ range เองได้ตามต้องการ
Checkbox จะทำงานผ่าน event แบบ onchange จะเรียกฟังก์ชัน ajax ที่ชื่อ sendVal() เมื่อสถานะของ checkbox เปลี่ยนไปเท่านั้น
Slider bar จะทำงานผ่าน event แบบ oninput เมื่อทำการเลื่อน slider จะเรียกฟังก์ชั่น sendVal() รวมถึงแสดงค่าปัจจุบันที่ output ด้วย
event แบบ onchange กับ oninput การทำงานคล้ายๆกันคือทำงานเมื่อมีการเปลี่ยนแปลงของ element แต่ที่ต่างคือ onchange นั้นจะเริ่มทำงานเมื่อเราปล่อยมือออกจากปุ่ม ในขณะที่ oninput จะทำงานแม้ว่าเรายังกดอยู่หรือเลื่อนปุ่มอยู่ก็ตาม จึงเลือก oninput ใช้กับ input แบบ range เพื่อให้ servo สามารถทำงานตามตำแหน่งการเลื่อนได้อย่างนุ่มนวลตลอดเวลา
1 2 3 4 5 6 7 8 9 | <h4>LED ON/OFF</h4> <input type="checkbox" class="cmn-toggle cmn-toggle-round" id="led1" onchange="sendVal()"> <label for="led1"></label> <h4>SERVOS</h4> <input type="range" id="servo1" min="0" max="180" value="90" oninput="sendVal();rangeVal1.value=value;"> <output id="rangeVal1">90</output> <input type="range" id="servo2" min="0" max="180" value="90" oninput="sendVal();rangeVal2.value=value;"> <output id="rangeVal2">90</output> |
เพื่อความสะดวกในการค้นหา และแปลงข้อความให้กลายเป็นข้อมูลที่นำไปใช้ในการทำงานของ ESP8266
เราจึงกำหนดรูปแบบของข้อมูลที่ง่ายต่อการแยกแยะด้วย ESP8266 ดังนี้ url/led1=true$servo1=123$servo2=65$
โดยสิ่งที่เราสนใจคือตัวแปร และค่า ที่ส่งมากับ url นั้น อย่างในตัวอย่างนี้คือ
ตัวแปล led1 มีค่าเป็น true, servo1 มีค่าเป็น 123 และ servo2 มีค่าเป็น 65
ส่วนสัญลักษณ์ที่เพิ่มเข้ามาคั่นระหว่างตัวแปรนั้นเพื่อเป็นเครื่องหมายอ้างอิงเมื่อสิ่งสุดข้อความของตัวแปรแต่ละชุด ซึ่งทำให้ง่ายในการแยกแยะในกรณีที่ค่าที่ใช้ในการส่งนั้นมีความยาวไม่คงที่
1 2 3 4 5 6 7 8 9 | function sendVal() { var x = document.getElementById("led1").checked; var y = document.getElementById("servo1").value; var z = document.getElementById("servo2").value; var URL = "/led1="+ x +"$servo1="+ y +"$servo2="+ z + "$"; ajax.open("GET", URL); ajax.send(null); } |
เมื่อเรากำหนดรูปแบบการใช้งาน และโปรโตคอลที่จะใช้สื่อสารกันแล้ว ก็เริ่มลงมือเขียนโค้ดกันได้
ไม่นานก็ได้ไฟล์ html สำหรับตัว ESP8266 แล้ว ความยาวแค่สองร้อยกว่าบรรทัดเอง….
html File สามารถดาวน์โหลดได้ที่นี่ครับ “index.html”
ด้านล่างเป็นหน้าตาภาพรวมของไฟล์ index.html ( ขอย่อ tag <style> ที่เป็นส่วนของ CSS ไปดูเต็มๆในลิงก์นะ )
การแปลงหน้าเว็บให้เป็นรูปแบบตัวแปรข้อความใน C
แล้วถ้าต้องแก้แต่ละครั้ง และอ้างอิงกลับไปกลับมาบ่อยๆ คงไม่เสร็จง่ายๆแน่
สุดท้ายทางออกคือทำเพิ่ม โดยการเขียน python script ขึ้นมา โดยสคริปต์นี้ จะรับไฟล์ html ที่สร้างขึ้น
จากนั้นก็แปลงเป็นโค้ด C รูปแบบของตัวแปลข้อความไว้สำหรับแสดงบน Browser ของ Client
และเพื่อป้องกันปัญหาจำนวนตัวอักษรเกินที่ตัวแปรจะรองรับได้ จึงได้แบ่งในแต่ละส่วนไว้ในรูปของ Array เพื่อให้สามารถแสดงผลต่อกันได้อย่างง่ายดาย
python script สามารถดาวน์โหลดได้ที่นี่ครับ “html2Ccode.py”
การใช้งาน python script เพื่อสร้าง C code ไฟล์
>python html2Ccode.py -i <inputfile> -o <outputfile> -p <parameter>
<inputfile> – ไฟล์ html ที่เขียนไว้ ในตัวอย่างนี้คือ index.html
<outputfile> – ไฟล์ C code ที่ได้จากการสร้างจากสคริปต์
<parameter> – ชื่อตัวแปรที่ใช้ใน C code
ตัวอย่าง
1 | > python html2Ccode.py -i index.html -o index2.h -p htmlCode |
ใน C code ไฟล์จะประกอบไปด้วย Array ของ ชื่อตัวแปร และจำนวนของ Array ที่มีผ่าน ตัวแปร pageCount
ซึ่งถูกกำหนดไว้ผ่านการ define ในตอนต้นของโค้ด และกำหนดค่าของ Array ของแต่ละตัวผ่านฟังค์ชั่น initial (<parameter>_init())
การเขียนโค้ดรับค่าจาก URL และควบคุมอุปกรณ์
วิธีการใช้งาน C code นี้ทำได้โดย เพิ่มไฟล์ลงใน arduino Project ผ่านเมนู sketch -> add file
เลือกไฟล์ C code ที่ generate ออกมาเข้าไปใน project
จากนั้น เพิ่มบรรทัด #include “<outputfile>” ลงในส่วนของ Source file ของ Arduino
เพิ่มบรรทัด <parameter>_init() ลงใน setup() ฟังค์ชั่น เพื่อเรียกใช้ฟังค์ชั่น <parameter>_init()
และเมื่อต้องการส่ง html code ตามคำขอของ Browser ทำได้โดย วนส่งค่าตั้งแต่ตัวแปรแรกไปจนตัวแปรสุดท้าย
ตัวอย่าง C code ที่ได้สามารถดาวน์โหลดได้ที่นี่ครับ “index2.h”
Ex.
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include "index2.h" #define pageCount 27 //แปลว่ามี Array จำนวน 27 ตัว void setup(){ htmlCode_init(); //เตรียมข้อมูลผ่านฟังค์ชั่น initial } void loop(){ for (count = 0 ; count < pageCount ; count++){ client.print(<parameter>[count]) } } |
การแบ่ง html Code เป็นส่วนๆแบบนี้ ทำให้มีความสะดวกในการเขียนโปรแกรม เพื่อแก้ไขข้อความที่แสดงบน Browser เนื่องจากจำนวนตัวอักษรของแต่ละ Array มีจำนวนไม่มาก แต่การที่จะทำให้ตัวอักษรที่เก็บในตัวแปลแบ่งได้ง่ายนั้น จำเปนที่จะต้องเขียน html Code ให้แบ่งแยกได้ง่ายเช่นเดียวกัน
ตัวอย่าง Arduino Code สามารถดาวน์โหลดได้ที่นี่ครับ “WiFiAP_ServoLEDServer.ino”
เงื่อนไขในการแบ่ง ข้อความที่จะนำไปเก็บไว้บน Array ใช้เงื่อนไข 2 ประการ คือ
ประการแรกคือ ใช้จำนวนตัวอักษร เมื่อในตัวแปรนั้นมีตัวอักษรเกิน 1000 ตัว ส่วนเกินนั้นจะถูกนำไปเก็บไว้ในตัวแปรถัดไป และถัดไปเรื่อยๆ เนื่องจากตัวแปร String นั้น มีการจำกัดขนาดไว้ที่ 2000 ตัวอักษร เราจึงใช้จำนวนตัวอักษรในการแบ่ง Array
Ex.
abcd
…
123
xyz
ถ้าหาก ข้อความนี้ ยาว 1003 ตัวอักษร ตัวแปรแรกจะมีข้อความตั้งแต่ “abcd….123” และตัวแปรถัดไปจะเก็บ “xyz”
s[0] = {‘a’,’b’,’c’,’d’,’\n’,…,’1′,’2′,’3′,’\n’};
s[1] = {‘x’,’y’,’z’,’\n’};
ประการที่สองคือ บรรทัดว่าง โดยข้อความที่อยู่ข้างหลังบรรทัดว่าง จะถูกนำไปเก็บไว้บนตัวแปรถัดไป โดยข้อความนั้นไม่ต้องมีขนาดใหญ่เกิน 1000 ตัวอักษร
Ex.
abcd
……
123
xyz
ในกรณีนี้ตัวแปรแรกจะเก็บข้อความ
abcd
…..
123
และตัวแปรที่สองจะเก็บข้อความ
xyz
s[0] = {‘a’,’b’,’c’,’d’,’\n’,….,’1′,’2′,’3′,’\n’};
s[1] = {‘x’,’y’,’z’,’\n’};
ดังนั้น ในส่วนข้องข้อความที่เราจำเป็นต้องเขียนทับจาก code เราสามารถทำให้ข้อความส่วนนั้นเป็นหนึ่งในตัวแปร Array ซึ่งเราสามารถเขียนทับได้ในภายหลังได้โดยง่าย
วงจรที่ใช้ในงานนี้
การรับค่าต่างๆผ่าน url โดยโดยเขียนบน Arduino IDE
1 2 3 | String req = client.readStringUntil('\r'); //อ่านข้อความจนกระทั่งสิ้นสุดบรรทัด Serial.println(req); //แสดงข้อความที่รับมา client.flush(); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | //ตรวจสอบข้อความที่อ่านได้ว่ามีค่าที่ต้องการหรือไม่ ในที่นี้คือ 'led1' ซึ่งเป็นค่าตัวแปรตัวแรก if(req.indexOf("led1=") != -1) { iled1 = req.indexOf("led1="); //เก็บค่าตำแหน่งของข้อความ 'led=' endindex = req.indexOf('$'); //เก็บตำแหน่งของ '$' ที่ใช้คั่นข้อความ startValindex = req.indexOf('='); //เก็บตำแหน่ง '=' เพื่อใช้ระบุตำแหน่งค่าตัวแปร Serial.print(iled1); //แสดงตำแหน่งข้อความ 'led=' cled1 = req .substring(startValindex+1,endindex); //เลือกเฉพาะข้อความที่อยู่หลัง '=' และก่อน '$' Serial.println(cled1); //แสดงข้อความ ซึ่งควรจะเป็น 'true' หรือ 'false' if (cled1.indexOf("true") != -1){ //ถ้าข้อความนั้นเป็น 'true' digitalWrite ( led1, 1 ); //สั่งให้ไฟติด digitalWrite ( led2, 1 ); Serial.println("LED1ON"); } else if (cled1.indexOf("false") != -1){ //ถ้าข้อความเป็น 'false' digitalWrite ( led1, 0 ); //สั่งให้ไฟดับ digitalWrite ( led2, 0 ); Serial.println("LED1OFF"); }else{ Serial.println("LED1ERR"); //ถ้าไม่ใช่ทั้งสองแบบ จะแสดง 'LED1ERR' } iservo1 = req.indexOf("servo1="); //เก็บค่าตำแหน่งของข้อความ 'servo1=' endindex = req.indexOf('$',endindex+1); //เก็บตำแหน่งของ '$' โดยเริ่มนับจากตำแหน่ง endindex อันที่แล้ว startValindex = req.indexOf('=',startValindex+1); //เก็บตำแหน่งของ '=' โดยเริ่มนับจากตำแหน่ง startValindex อันที่แล้ว Serial.print(iservo1); //แสดงตำแหน่งข้อความ 'iservo1' cservo1 = req .substring(startValindex+1,endindex); //เลือกเฉพาะข้อความที่อยู่หลัง '=' และก่อน '$' Serial.println(cservo1); //แสดงข้อความที่คัดไว้ ควรจะเป็นตัวเลข servo1Drv=cservo1.toInt(); //แปลงข้อความเป็นตัวเลข myservo1.write(servo1Drv); //กำหนดค่า Servo ตัวที่ 1 ตามค่าที่ได้จาก url Serial.println(servo1Drv,DEC); //แสดงค่าตัวเลข iservo2 = req.indexOf("servo2="); //ทำเช่นเดียวกันกับ Servo ตัวที่ 1 endindex = req.indexOf('$',endindex+2); startValindex = req.indexOf('=',startValindex+2); Serial.print(iservo1); cservo2 = req .substring(startValindex+1,endindex); Serial.println(cservo2); servo2Drv=cservo2.toInt(); myservo2.write(servo2Drv); Serial.println(servo2Drv,DEC); delay(10); } else if(req.indexOf("/") != -1) //if(req.indexOf("/") != -1) { for (htmlCount = 0; htmlCount<pageCount;htmlCount++){ client.print(htmlCode[htmlCount]); //ส่ง html code ขึ้นบน Browser } client.flush(); } |
เมื่อเราได้ไฟล์ครบแล้ว เราจะสามารถคอมไพล์ Arduino Project และโปรแกรมลงบน NodeMCU ได้
ถ้าหากทุกอย่างเรียบร้อยดี NodeMCU ก็จะทำงานตามภาพ
ลองใช้งาน
และเมื่อลองตรวจสอบ wifi เราก็จะพบ เครือข่ายที่ NodeMCU สร้างขึ้น
โดยเราสามารถเชื่อมต่อเพื่อควบคุม Servo กับ LED ได้ผ่านเครือข่ายนี้
SSID คือ “ESPap”
Password คือ “thereisnospoon”
เมื่อเชื่อมต่อสำเร็จแล้ว ให้เปิด Browser ขึ้นมา และพิมพ์ url เป็น 192.168.4.1
ฺBrowser ก็จะเปิดหน้า webpage ที่เราเขียนไว้บน NodeMCU
ซึ่งเราสามารถควบคุมตำแหน่งของ Servo ทั้งสองตัวได้ผ่าน Slide Bar สองอัน
และเปิด – ปิด หลอด LED ได้ผ่าน สวิตซ์ด้านบน
บอร์ดก็จะทำงานตามที่เราสั่งได้
สำหรับไฟล์ทั้งหมดสามารถดาวน์โหลดได้ที่นี่ครับ playelek/ESP8266GPIOandServo
นอกจากจะใช้กับ Arduino Project แล้ว วิธีนี้ยังสามารถใช้กับ คอนโทรลเลอร์ตัวอื่นที่จำเป็นต้องทำงานบนระบบ Web Based ได้อีกด้วยครับ
References
Toggle Switch ใช้ CSS จาก callmenick.com : CSS Toggle Switch Examples
Slider Control ใช้ CSS จาก CSS Portal : Style Input Range