HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Web server to switch GPIO pin

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
gpiowebserverswitchpin

Problem

I am running a simple Python webserver to toggle a GPIO pin
on a Raspberry Pi. All seems to work well, but I have a few
questions about my code (see below).

I am using the urlparse module to obtain the URL arguments.

The HTML file just sends out Ajax requests using buttons when clicked.

  • I've commented out the send_response lines, and it seems to work


both ways. Is it mandatory to send a response?

  • Is the code below easy to exploit? Are there improvements I can make to make it a bit more robust?



Python code:

import time
import SimpleHTTPServer
import SocketServer
from urlparse import *

ledpin=18
myport=8123
# Turn LED on  => http://ip_of_pi:port/control.html?led=on
# Turn LED off => http://ip_of_pi:port/control.html?led=off
class MyRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    def do_GET(self):
        urlcomp=urlparse(self.path) # Split URL in components
        query = parse_qs(urlcomp.query) # Get args as dictionary
        if len(query)==0 or query.has_key("led")==False:
            self.path=urlcomp.path
            # self.send_response(200)
        elif query["led"] == ["on"] :
            GPIO.output(ledpin,True)
            # self.send_response(200)
            return
        elif query["led"] == ["off"] :
            GPIO.output(ledpin,False)
            # self.send_response(200)
            return
        SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(ledpin,GPIO.OUT)

Handler = MyRequestHandler
httpd = SocketServer.TCPServer(('0.0.0.0', myport), Handler)
httpd.serve_forever()

Solution

Import mess

You fail to bring GPIO into the namespace by using something along the lines of from RPi import GPIO. You also import time but don't use it. And you import everything from urlparse using from urlparse import *. This form is frowned upon as it polutes your namespace and may override something else. Better use import urlparse or from urlparse import urlparse, parse_qs.
Global layout mess

It is good practice to put top-level code into functions and call them in a if __name__ == '__main__' clause. That way you can start an interactive session and import your script for testing purposes without having code running as a side effect of your import:

def init_GPIO(ledpin=18):
    GPIO.setwarnings(False)
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(ledpin,GPIO.OUT)

def init_webserver(port=8123):
    httpd = SocketServer.TCPServer(('0.0.0.0', port), MyRequestHandler)
    httpd.serve_forever()

if __name__ == "__main__":
    init_GPIO()
    init_webserver()


Note that I also turned your constants into parameters with default values. In this case it can make testing much more easier.

You also don't need your alias (Handler = MyRequestHandler) and can use the class directly.
Request handler

There is a more idiomatic way of checking if 'led' is in the query string; by using EAFP:

class MyRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    def do_GET(self):
        urlcomp = urlparse(self.path) # split url in components
        query = parse_qs(urlcomp.query) # Get args as dictionary
        try:
            led_state = query['led']
        except KeyError:
            pass
        else:
            turn_on = led_state == ["on"]
            GPIO.output(ledpin, turn_on)
        self.send_response(200)


It is recommended that you call the send_response method since, even if the raspberry-pi handled the request and turned the led on or off, your browser is still waiting for a reply of your webserver. Not calling self.send_response(200) should result in a timeout of your browser at some point.

If you want to send back data to the browser on top of the status code, you can write whatever content you want into self.wfile which will stream is to your browser. You can use it to make it simpler to control your raspi. You can also cusomize the message based on how you handled the action.

class MyRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    def do_GET(self):
        urlcomp = urlparse(self.path) # split url in components
        query = parse_qs(urlcomp.query) # Get args as dictionary
        try:
            led_state = query['led']
        except KeyError:
            message = "No commands processed"
        else:
            if led_state in (["on"], ["off"]):
                GPIO.output(ledpin, led_state == ["on"])
                message = "Led turned {}".format(led_state[0])
            else:
                message = "Unknown action {}".format(led_state)
        # Build links whatever the action was
        message += """
                      Turn on the led
                      
                      Turn off the led
                      """
        self.send_response(200)
        # Custom headers, if need be
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        # Custom body
        self.wfile.write(message)


If you want to read the body from a file, just use regular python file manipulation:

with open('control.html') as html:
    self.wfile.write(html.read())

Code Snippets

def init_GPIO(ledpin=18):
    GPIO.setwarnings(False)
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(ledpin,GPIO.OUT)

def init_webserver(port=8123):
    httpd = SocketServer.TCPServer(('0.0.0.0', port), MyRequestHandler)
    httpd.serve_forever()

if __name__ == "__main__":
    init_GPIO()
    init_webserver()
class MyRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    def do_GET(self):
        urlcomp = urlparse(self.path) # split url in components
        query = parse_qs(urlcomp.query) # Get args as dictionary
        try:
            led_state = query['led']
        except KeyError:
            pass
        else:
            turn_on = led_state == ["on"]
            GPIO.output(ledpin, turn_on)
        self.send_response(200)
class MyRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    def do_GET(self):
        urlcomp = urlparse(self.path) # split url in components
        query = parse_qs(urlcomp.query) # Get args as dictionary
        try:
            led_state = query['led']
        except KeyError:
            message = "<p>No commands processed</p>"
        else:
            if led_state in (["on"], ["off"]):
                GPIO.output(ledpin, led_state == ["on"])
                message = "<p>Led turned {}</p>".format(led_state[0])
            else:
                message = "<p>Unknown action {}</p>".format(led_state)
        # Build links whatever the action was
        message += """<p>
                      <a href="/control.html?led=on">Turn on the led</a>
                      </p><p>
                      <a href="/control.html?led=off">Turn off the led</a>
                      </p>"""
        self.send_response(200)
        # Custom headers, if need be
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        # Custom body
        self.wfile.write(message)
with open('control.html') as html:
    self.wfile.write(html.read())

Context

StackExchange Code Review Q#112222, answer score: 2

Revisions (0)

No revisions yet.