วันจันทร์ที่ 25 กรกฎาคม พ.ศ. 2559

ตัวอย่างการควบคุมอุปกรณ์ผ่าน Python HTTPServer กับ Raspberry Pi

Raspberry Pi กับ HTTPServer อยู่ด้วยกันมาตลอด เพราะทั้ง Python2 และ Python3 ต่างก็มี standard library ที่ช่วยให้ Raspberry Pi กลายเป็น Web Server ได้ทั้งคู่ ในบทความนี้จะใช้ HTTPServer ซึ่งอยู่ใน Python 2 มาใช้ทำเป็นตัวอย่าง และเพืี่อให้เห็นว่าเราสามารถทำให้มันเป็นมากกว่า HTTP Server ก็จะมีการนำเอาไปควบคุมการหมุน Servo เป็นตัวอย่างครับ

การสร้าง HTTP Server (Python 2)


ส่วนทีี่ 1 : Libraries 


from gadgets.motors.servos import Servo5V
from BaseHTTPServer import BaseHTTPRequestHandler
import cgi
import time
import atexit
from os import curdir, sep
from urlparse import urlparse


บรรทัดทีี่ 1 : นำเข้า library จาก RPI.GPIO.TH เพื่อใช้ในการควบคุม Servo ชนิดใช้แรงดันไฟฟ้า 5 V
บรรทัดที่ 2 : นำ BaseHTTPRequestHandler  เข้ามาเพื่อใช้ในการจัดการความต้องการที่ส่งมาจาก client มายัง Raspberry Pi
บรรทัดที่ 3 : cgi เป็น library ที่ช่วยให้เราแยกข้อมูลที่ส่งมาจาก client ด้วยวิธี POST ออกเป็นส่วน ๆ
บรรทัดที่ 5 :  atexit  นำเข้ามาเพื่อช่วยในการ clean up การใช้งาน GPIO
บรรทัดที่ 6 :  curdir, sep ช่วยในการจัดทำ path ของ static file ให้ถูกต้อง
บรรทัดที่ 7 : urlparse  เช่นเดียวกับ cgi แต่ใช้ในกรณีที่ทาง client ส่งข้อมูลมาด้วยวิธี GET



ส่วนที่ 2 : Request Handler Class


สร้าง Request Handler Class ซึ่งจะเป็นตัวทำหน้าที่หลักในการจัดการกับข้อมูล (HTTP requests) ที่ส่งมายัง HTTP Server  โดยเราจะเริ่มต้นด้วยการ overwrite  2 methods คือ do_GET และ do_POST  โดยยังมี code ภายใน

class RequestHandler(BaseHTTPRequestHandler):
   def do_GET(self):
      return

   def do_POST(self):
      return
โดยทั่วไปเราเข้าใจกันแล้วว่าหน้าแรกที่เราได้เห็นสำหรับเว็บไซต์ทั่วไปมักมีชื่อว่า index.html ซึ่งเราจะสร้างไว้เป็น static file แยกไว้ต่างหาก เมื่อ client ส่งความต้องการมาด้วยรูปแบบของ URL ที่ปิดท้ายด้วยสัญญลักษณ์ "/" จะถูกตีความว่าให้ส่งเนื้อหาใน index.html ไปยัง browser

def do_GET(self):
    query_str = urlparse(self.path)
    if query_str.path == '/':
        self.path = './index.html'
        with open(curdir + sep + self.path) as f:
     self.send_response(200)
     self.send_header('Content-type',"text/html")
            self.end_headers()
     self.wfile.write(f.read()) 
   
    return

Code ที่เพิ่มเข้าไปใน do_GET  เริ่มต้นด้วยการแยก request ออกด้วยคำสั่ง urlparse(self.path) ซึ่งทำให้เราทราบว่ามี query string เป็นอย่างไร ในกรณีที่ query string เป็น "/" ก็จะทำการ อ่านข้อมูลจาก index.html ขึ้นมาไว้ใน self.wfile (ที่สำหรับเขียนข้อมูลของ BaseHTTPRequestHandler ก่อนส่งออกไปยัง client) สำหรับเนื้อหาใน index.html เป็นดังนี้



บนหน้าจอของ browser จะแสดงให้เป็น slider ที่แสดงค่าตั้งแต่ 0 - 180 ซึ่งตรงกับค่าช่วงกว้างของ Servo นั่นเอง ค่าของ slider จะถูกส่งไปยัง function write_servo(angle) เพื่อทำการส่งค่ามุมกลับไปยัง HTTP Server ด้วยวิธีการ POST (หรือการ submit form ในกรณีที่ใช้ form บนเว็บเพจ)


ขั้นตอนต่อไปเป็นการเพิ่ม code เข้าไปใน do_POST ที่วางไว้ในตอนแรก ดังนี้

def do_POST(self):
    form = cgi.FieldStorage(
                 fp=self.rfile, 
   headers=self.headers,
   environ={'REQUEST_METHOD':'POST',
   'CONTENT_TYPE':self.headers['Content-Type'],
  })
    servo.write(int(form["write_servo"].value))
    self.send_response(200)
    self.send_header('Content-type','text/plain')
    self.end_headers()
    self.wfile.write(form["write_servo"].value)
    return
ข้อมูลที่ผ่านการ submit form หรือการส่งด้วยวิธี POST เราจะทำการ dump ออกมาเก็บไว้ในตัวแปร form ซึ่งเป็น instance ของ cgi.FieldStorage class ก่อนแล้วจึงจะสามารถนำค่าที่เก็บไว้มาใช้งานได้ โดยค่าทีีส่งมานั้นเก็บไว้ใน key ชืี่อ 'write_servo' (ดูใน index.html)  ซึ่งจะถูกส่งต่อไปให้ servo ใช้งานต่อไป

ส่วนที่ 3


def cleanup():
 servo.cleanup()
  
if __name__ == '__main__':
 from BaseHTTPServer import HTTPServer
 global servo
 servo = Servo5V(pin_number=12,freq=100)
 atexit.register(cleanup)
 server = HTTPServer(('', 8080), RequestHandler)
 print ('Starting server, use  to stop')
 server.serve_forever()

งานที่เหลือได้แก่ การ cleanup GPIO หลังการใช้งาน และการประกาศตัวแปรที่จำเป็น ได้แก่
servo = Servo5V()
เพื่อใช้ควบคุม Server ผ่าน GPIO

server = HTTPServer(('', 8080), RequestHandler)
เพื่อใช้เป็น HTTP Server ที่ให้บริการผ่าน port 8080

ฉบับสมบูรณ์

RaspiHTTPServer.py :

from gadgets.motors.servos import Servo5V
from BaseHTTPServer import BaseHTTPRequestHandler
import cgi
import time
import atexit
from os import curdir, sep
import os

from urlparse import urlparse

class RequestHandler(BaseHTTPRequestHandler):
 def get_mimetype(self):
  if self.path.endswith(".html"):
   mimetype='text/html'
  if self.path.endswith(".jpg"):
   mimetype='image/jpg'
  if self.path.endswith(".gif"):
   mimetype='image/gif'
  if self.path.endswith(".js"):
   mimetype='application/javascript'
  if self.path.endswith(".css"):
   mimetype='text/css'
  return mimetype

 def response_static_file(self):
  with open(curdir + sep + self.path) as f:
   mimetype = self.get_mimetype()
   self.send_response(200)
   self.send_header('Content-type',mimetype)
   self.end_headers()
   self.wfile.write(f.read()) 
  
 def do_GET(self):
  query_str = urlparse(self.path)
  if query_str.path == '/':
   self.path = './index.html'
   with open(curdir + sep + self.path) as f:
    mimetype = self.get_mimetype()
    self.send_response(200)
    self.send_header('Content-type',mimetype)
    self.end_headers()
    self.wfile.write(f.read()) 
   
  return
  
 def do_POST(self):
  #dump POST request onto variable
  form = cgi.FieldStorage(
   fp=self.rfile, 
   headers=self.headers,
   environ={'REQUEST_METHOD':'POST',
     'CONTENT_TYPE':self.headers['Content-Type'],
     })
  servo.write(int(form["write_servo"].value))
  self.send_response(200)
  self.send_header('Content-type','text/plain')
  self.end_headers()
  self.wfile.write(form["write_servo"].value)
 
  return

def cleanup():
 servo.cleanup()
  
if __name__ == '__main__':
 from BaseHTTPServer import HTTPServer
 global servo
 servo = Servo5V(pin_number=12,freq=100)
 atexit.register(cleanup)
 server = HTTPServer(('', 8080), RequestHandler)
 print ('Starting server, use  to stop')
 server.serve_forever()

ผลการทำงาน


$ python RaspiHTTPServer.py

แล้วเปิดเว็บบราวเซอร์ด้วย URL : http://[your Raspberry Pi address]:8080/


ไม่มีความคิดเห็น:

แสดงความคิดเห็น