Powered by Drupal, an open source content management system

การประยุกต์ใช้งาน Embedded Linux Computer (ตอนที่ 14 - CGI)

ความสามารถเด่นอันนึงของ WRT54G คือ มีโปรแกรมWebServerวิ่งอยู่ภายใน ซึ่งทำให้เราสามารถเชื่อมต่อและควบคุมโปรแกรมที่วิ่งบนมันด้วยเวบบราวเซอร์ โดยการใช้งานสามารถทำได้จากตั้งแต่พีซีไปจนถึงมือถือ และหากทำการเซ็ทอัพบน Router หลักให้เชื่อมต่อกับอินเตอร์เน็ท เราก็จะสามารถควบคุมใช้งานได้จากที่ใดก็ตามในโลกที่อินเตอร์เน็ทไปถึง ดังนั้นตอนนี้จะว่าด้วยเรื่องหลักๆดังนี้
1. การเชื่อมต่อโปรแกรมที่วิ่งอยู่บนเครื่องผ่านWebserver
2. การใช้งานโปรแกรมผ่านอินเตอร์เน็ท
 
การเชื่อมต่อโปรแกรมที่วิ่งอยู่บนเครื่องผ่านWebserver
 
การเชื่อมต่อโปรแกรมกับเวบเซิฟเวอร์ทำได้โดยอาศัย Protocol ตัวนึงที่เรียกว่า CGI (Common Gateway Interface) หลักการก็คือโดยตามปกติ Application Program ทั่วไปจะส่งค่าที่พิมพ์ไปออก Standard Output ของระบบ (อาจจะเป็น serial port หรือ  tty) แต่ถ้าหากโปรแกรมนั้นถูกเรียกใช้ผ่าน CGI ค่าที่ถูกส่งออกไปยัง Standard Output ก็จะไปออกที่เวบบราวเซอร์แทน
 
มาลองดูตัวอย่างกันนะครับ ความเดิมตอนที่แล้วเรามีโปรแกรม hellomips ที่วิ่งแล้วจะพิมพ์ค่าดังนี้
 
 
เวบเซิร์ฟเวอร์ที่วิ่งบน ELC จะจอง directory พิเศษอันนึงสำหรับ CGI โดยเรียกว่า cgi-bin ซึ่งหากเรียกไฟล์ที่อยู่ภายใต้ directory นี้ เวบเซิร์ฟเวอร์ก็จะทำการรันโปรแกรมตาม Protocol ที่ได้ตกลงกันไว้ ดังนั้นให้เราทำคำสั่งดังนี้
 
# cd /www               ไปยัง directory หลักของ web server
# mkdir cgi-bin          สร้าง directory cgi-bin
# cd cgi-bin               
# cp /root/hellomips .  คัดลอกโปรแกรมมายัง cgi-bin
 
จากนั้นลองเปิดเวบบราวเซอร์และไปยัง URL ดังนี้ http://192.168.1.111/cgi-bin/hellomips
(จำได้ใช่ไม๊ครับ IP Address ของ ELC เราคือ 192.168.1.111)
     
 
และแล้วสิ่งมหัศจรรย์ก็เกิดขึ้น โปรแกรม hellomips จะถูก execute และส่งค่ากลับมายังเวบบราวเซอร์!!! ง่ายใช่ไม๊ครับ :)
 
เริ่มมีคำถามแล้วใช่ไม๊ครับว่าโปรแกรมที่ CGI รองรับนี่ต้องพัฒนาโดย C เท่านั้นหรือ คำตอบคือไม่ครับ โปรแกรมที่รองรับ CGI มีมากมายเลยครับ C, perl , python, ruby , bash เยอะมากครับ ไม่เชื่อลองพิมพ์คำว่า cgi with บน google สิครับ
มาลองดูอีกสักตัวอย่างที่ใช้ shell script นะครับ
 
ภายใต้ directory cgi-bin ให้ทำคำสั่งนี้เพื่อสร้างไฟล์ checki2c
 
# echo "#!/bin/sh" >> checki2c
# echo i2cdetect -y 0 >> checki2c
# chmod +x checki2c       เปลี่ยนให้ไฟล์สามารถ execute ได้
 
โปรแกรมจะประกอบด้วยสองบรรทัดดังนี้
#!/bin/sh               ให้ Process file นี้ด้วย shell
i2cdetect -y 0         ตรวจสอบไอซีที่ต่ออยู่บน I2C BUS
 
จากนั้นลองเปิดเวบบราวเซอร์และไปยัง URL ดังนี้ http://192.168.1.111/cgi-bin/checki2c
 
 
จะเห็นว่าโปรแกรมที่เราเคยวิ่งบน Console สามารถเรียกใช้งานผ่านเวบบราวเซอร์ตามต้องการ
 
คำถามต่อไปคือ แล้วการรับค่าอินพุทจากบราวเซอร์เพื่อส่งต่อไปยังโปรแกรมจะทำยังไง
ก่อนอื่นเราต้องทำความเข้าใจวิธีรับอินพุทของบราวเซอร์ก่อนครับ ซึ่งจะแบ่งเป็นสองประเภทคือ GET และ POST
 
การรับค่าแบบ GET
ลองมาดูตัวอย่าง html page ที่ชื่อว่า helloget.htm ครับ
<html>
<body>
<h3>Test GET Method</h3><br>
<form method="get" action="cgi-bin/helloget">
Enter number of counter: <input name="count" value="5"><br><br>
<input type="submit" value = "Execute">
</form>
</body>
</html>
 
และเมื่อแสดงผลบนบราวเซอร์จะมีหน้าตาดังนี้
 
 
เมื่อปุ่ม execute ถูกกด จะเกิดปฏิบัติการดังนี้
1. Text String ในลักษณะ name=value จะถูกสร้างขึ้น โดย value คือค่าที่รับมาจากผู้ใช้ ดังนั้นในกรณีตัวอย่างนี้ Text String ที่ถูกสร้างขึ้นคือ count=5 (ในกรณีที่อินพุทมีมากกว่าหนึ่งค่า แต่ละค่าจะถูกคั่นด้วยเครื่องหมาย & โดยมีลักษณะดังนี้ name1=value1&name2=value2&...)
2. Text String ในข้อแรกจะเอาไปต่อท้าย action และคั่นด้วยเครื่องหมาย ?
3. สุดท้ายจะประกอบเป็น URL ที่จะถูกเรียกคือ http://192.168.1.111/cgi-bin/helloget?count=5
4. CGI จะทำการตัดค่าที่ส่งมาหลังเครื่องหมาย ? และเอาไปฝากไว้ใน Enviroment variable ของ shell ที่ชื่อว่า QUERY_STRING
5. CGI จะทำการรันโปรแกรม helloget 
6. helloget จะเป็น C output file ที่ compile มาจากโปรแกรม helloget.c โดยจะรับอินพุทจาก  Enviroment variable QUERY_STRING
 
โปรแกรม helloget.c จะมีหน้าตาดังนี้
 
#include <stdio.h>
int main(void)
{
 char *Data;
 char *valuePtr;
 int i,count;
 /* get input data */
 Data = (char *)getenv("QUERY_STRING");
 /* looking for the value */
 valuePtr = strstr(Data,"=");
 if (valuePtr != NULL)
 {
   /* found = move to next char */
   valuePtr++;
   /* get counter */
   count = atoi(valuePtr);
   printf("Content-type: text/html\n\n");
   printf("<html>\n<body>\n");
   for(i=0; i< count ;i++)
      printf("Hello!! %d<br>\n",i+1);
   printf("<a href='../helloget.htm'>Go Back</a>");
   printf("</body>\n</html>\n");
 }
}
 
ในขั้นตอนของการพัฒนาโปรแกรม เราสามารถทดสอบโปรแกรมใน console ด้วยการกำหนด environment variable QUERY_STRING และเรียกใช้โปรแกรม helloget ดังนี้
 
 
ผลการใช้งานจริงจะเป็นดังนี้
 

ข้อดีของการส่งค่าแบบ GET คือเราสามารถเรียกใช้ helloget และใส่ค่าอินพุทโดยอัตโนมัติในขั้นตอนเดียวไม่ต้องผ่านการใส่ค่าจากหน้า helloget.htm ซึ่งสามารถทำได้โดยแก้ไข URL โดยตรง ตัวอย่างเช่น ถ้าต้องการเรียกใช้ helloget ด้วย count=10 ก็สามารถกระทำโดยการเรียกใช้ URL ดังนี้ http://192.168.1.111/cgi-bin/helloget?count=10
ปัญหาของการส่งแบบ GET คือ ความยาวของ URL จะถูกจำกัดไว้ที่ 2048 bytes ซึ่งหากส่งข้อมูลที่ยาวมากกว่านั้นจึงจำเป็นต้องใช้อีกวิธี ซึ่งก็คือ POST นั่นเอง
 
การรับค่าแบบ POST
 
การส่งค่าแบบ POST ทำได้โดยการเปลี่ยน method เป็น POST
 
<html>
<body>
<h3>Test POST Method</h3><br>
<form method="post" action="cgi-bin/hellopost">
Enter number of counter: <input name="count" value="5"><br><br>
<input type="submit" value = "Execute">
</form>
</body>
</html>
 
มาลองดูข้อมูลที่ถูกส่งจากเวบบราวเซอร์ไปยังเวบเซิฟเวอร์เทียบกันระหว่างการส่งแบบ GET และ POST (โดยใช้ Tool ที่ชื่อ fiddler http://www.fiddler2.com/fiddler2/)
 
 
 
จะสังเกตุเห็นว่า เมื่อรับข้อมูลแบบ POST ตัว name/value จะอยู่ในส่วนข้อความของrequestและความยาวของข้อความถูกกำหนดไว้ใน header content-length ซึ่งก่อนที่ CGI จะรัน hellopost จะทำขั้นตอนดังนี้
1. สร้าง Environment Variable ชื่อ CONTENT_LENGTH เพื่อเก็บค่า content-length
2. ข้อความของ request จะถูกส่งให้กับโปรแกรมผ่าน stdin (ถูกสร้างขึ้นใน stdio.h) ของระบบ
ดังนั้นโปรแกรม hellopost.c จึงมีหน้าตาดังนี้
 
#include <stdio.h>
int main(void)
{
 char Data[80];
 char *valuePtr;
 int i,cl,count;
 /* check data length comming in */
 cl = atoi(getenv("CONTENT_LENGTH"));
 /* get input data */
 fgets(Data,cl+1,stdin);
 /* looking for the value */
 valuePtr = strstr(Data,"=");
 if (valuePtr != NULL)
 {
   /* found = move to next char */
   valuePtr++;
   /* get counter */
   count = atoi(valuePtr);
   printf("Content-type: text/html\n\n");
   printf("<html>\n<body>\n");
   for(i=0; i< count ;i++)
      printf("Hello!! %d<br>\n",i+1);
   printf("<a href='../hellopost.htm'>Go Back</a>");
   printf("</body>\n</html>\n");
 }
}
 
ทดสอบโปรแกรมใน console โดยใช้คำสั่ง
CONTENT_LENGTH=7 ./hellopost
โปรแกรมจะหยุดรอค่าจาก stdin ให้เราค่า content=9 แล้ว enter
 
 
ลองใช้งานจริงผ่าน cgi
 
 
โปรดสังเกตุว่า จะไม่มีข้อความต่อท้าย http://192.168.1.111/cgi-bin/hellopost  ในกรณีการส่งข้อความแบบ POST
 
 
มาถึงจุดนี้เราก็พร้อมที่จะต่อระบบของเราเพื่อให้สามารถสั่งการได้จากอินเตอร์เน็ทแล้วล่ะครับ
 
เชื่อมระบบภายในกับอินเตอร์เน็ท
Router หลักที่ผมใช้อยู่วิ่ง Firmware ของ DD-WRT (http://www.dd-wrt.com) ซึ่งสามารถเข้าถึงผ่านอินเตอร์เน็ทได้ด้วย domain name สมมติให้เป็น http://www.xyz.com (ในกรณีที่ท่านไม่มี fix ip และต้องการให้เข้าถึง Router ที่บ้านท่านผ่านอินเตอร์เน็ท ให้ลองศึกษาได้จากที่นี่ครับ http://www.dyndns.org/
วิธีการเซ็ทอัพให้ติดต่อกับ elc ผ่านอิเตอร์เน็ทให้ทำดังนี้
1. เข้าไปยัง router หลักของท่านผ่านบราวเซอร์
2. ไปยังหน้า NAT/Qos
3. เลือก Port Forwarding และเพิ่มบรรทัดเข้าไปดังรูปนี้
 
เราทำการเซ็ทอัพให้อะไรก็ตามที่เข้ามายัง port หมายเลข 5555 ให้วิ่งไปยัง ELC ของเราที่ Port 80 ซึ่งก็คือ เวบเซิฟเวอร์นั่นเอง
ลองทดสอบโดยการใช้มือถือ BB เปิด Browser และไปยัง URL
 
 
 
Ipod Touch ไปยัง http://www.xyz.com:5555/cgi-bin/checki2c
 
 
จะเห็นว่า ELC ของเราเป็นเครื่องมืออย่างดีในการเชื่อมต่อการควบคุมฮาร์แวร์จากระยะทางไกลไร้สายผ่านอินเตอร์เน็ท เราสามารถนำไปใช้งานต่างๆได้มากมายขึ้นอยู่กับว่าเราจะเขียนโปรแกรม cgi รองรับอย่างไร อาทิเช่น
- เราอาจจะเขียน cgi program ให้ติดต่อกับ MCU ผ่าน RS232 ของตัว ELC เองซึ่งมีอยู่สอง port
- เพิ่ม i2c temperature sensor ให้สามารถวัดอุณหภูมิระยะไกลผ่านอินเตอร์เน็ท
- AC Thermostat ผ่านอินเตอร์เน็ท
- ๆลๆ อีกมากมายสุดแต่จินตนการของท่านจะไปถึง ดังนั้นตอนนี้ขอจบบทด้วยคำกล่าวของอัลเบิร์ต ไอน์สไตน์ที่ว่า
"Imagination is more important than knowledge."