Web Control of Raspberry Pi GPIO in FreeBSD

We know how to control GPIO in FreeBSD from command line and C programs.
But it’s better to use Raspberry Pi as a standalone server and control it through web interface.

Here is a description how to control GPIO through web in Raspberry Pi with FreeBSD.

We can use C promgram for CGI, but this is “the old way”.
Modern Web applications are dynamic and are built on a framework.
We need to control GPIO in languages such as Perl, Python and Ruby.

Python

We need to build Python from ports.
First, I build python27:

py2

And then build the “meta-port” for version 2:

py1

GPIO in Python

There is a Python module RPi.GPIO.
But it is Linux’s oriented. It uses memory mapping to access GPIO. So we can’t use it in FreeBSD.

To control GPIO in FreeBSD from C program we need to use ioctl for GPIO device.
I am not an expert in Python and not sure that we can do it natively from Python.
Fortunately Oleksandr Tymoshenko wrote simple wrappers around FreeBSD’s GPIO ioctl for Per, Python, and Ruby.
It is available here: https://github.com/gonzoua/freebsd-gpio

Let’s download and extract it:

py3

py4

To build this python module we need to do this command:

# python setup.py build

py5

This module comes with demo program.
It outputs information about all known pins and then blink one pin.
I have connected one LED to pin 25 and changed pin number for the LED in this demo program.

LED25

Now we can run demo program:

py6.1py6.2

It’s work!
To install this module in system we need to run this command:

# python setup.py install

Python in Web

There is a Web Server Gateway Interface originally specified in PEP 333.
This is a specification for simple and universal interface between web servers and web applications or frameworks for the Python.

There are some application servers realisation for python.
I decide to use uWSGI as compact and performance solution.

uWSGI

We can build uWSGI from ports:

uwsgi1

I used the demo program from the freebsd-gpio module as a basis for creating an web application.
It outputs table with information about all known pins.

#!/usr/bin/env python

import gpio

html1 = """\
<!doctype html>
<html>
<head><title>GPIO Configuration</title></head>
<body>
 <h1>GPIO Configuration</h1>
 <table border=1>
  <tr><th>Name</th><th>Value</th><th>Config</th><th>Caps</th></tr>
"""

html2 = """\
 </table>
</body>
</html>
"""

row_template = "  <tr><td>%s</td><td>%d</td><td>%s</td><td>%s</td></tr>"

def flags2str(flags):
    strs = []

    if (flags & gpio.PIN_INPUT):
        strs.append('INPUT')
    if (flags & gpio.PIN_OUTPUT):
        strs.append('OUTPUT')
    if (flags & gpio.PIN_OPENDRAIN):
        strs.append('OPENDRAIN')
    if (flags & gpio.PIN_PUSHPULL):
        strs.append('PUSHPULL')
    if (flags & gpio.PIN_TRISTATE):
        strs.append('TRISTATE')
    if (flags & gpio.PIN_PULLUP):
        strs.append('PULLUP')
    if (flags & gpio.PIN_PULLDOWN):
        strs.append('PULLDOWN')
    if (flags & gpio.PIN_INVIN):
        strs.append('INVIN')
    if (flags & gpio.PIN_INVOUT):
        strs.append('INVOUT')
    if (flags & gpio.PIN_PULSATE):
        strs.append('PULSATE')

    return ','.join(strs)


def application(env, start_response):

    status = "200 OK"
    headers = [('Content-type', 'text/html'), ]
    start_response(status, headers)

    gpioc = gpio.controller()
    max_pin = gpioc.max_pin
    gpios = ""

    content = html1

# Dump information about all known pins
    for p in range(0, max_pin + 1):
        pin = gpioc.pin(p)
        try:
            value = pin.value
        except gpio.GpioError as e:
            # pin with this number is unknown
            continue

        caps = flags2str(pin.caps)
        config = flags2str(pin.config)

        name = pin.name

        content += row_template % (name, value, config, caps)

    content += html2

    return [content]

Firts deploy it on port 9090 with this command:

# uwsgi --http :9090 --wsgi-file gpioweb.py

uwsgi2

Now we can try to access it:

uwsgi3

We can adding concurrency and monitoring to our application:

# uwsgi --http :9090 --wsgi-file gpioweb.py --master --processes 4 --threads 2 --stats 127.0.0.1:9191

uwsgi4

We can use “uwsgitop” to see a top-like monitoring of the uWSGI:

uwsgi5

Before using uwsgitop we need to install py-pip:

# cd /usr/ports/devel/py-pip
# make
# make install

And than to install uwsgitop:

# pip install uwsgitop

uWSGI behind a full webserver

Next step is using a “normal” webserver.
FreeBSD Handbook advises to use Apache HTTP Server but I decide to use Nginx.
It is compact and performance. And OpenBSD uses it as a default webserver.

We can build and install nginx from ports:

# cd /usr/ports/www/nginx
# make
# make install

ng1

Then we need to change configuration file for nginx: /usr/local/etc/nginx/nginx.conf.
Nginx will use Unix sockets to communicate with uWSGI.
And our application will be available at location /gpio.

We need to add this strings in section “http” in the file /usr/local/etc/nginx/nginx.conf:

upstream pi {
    server unix:///tmp/uwsgi.sock;
}

And this strings in section “server”:

location /gpio {
    include uwsgi_params;
    uwsgi_pass pi;
}

ng2

We will use this file /usr/local/etc/uwsgi.ini as ini-file for uWSGI:

[uwsgi]

chdir = /usr/local/www/pi
wsgi-file = gpioweb.py

master = true
processes = 4
stats = 127.0.0.1:9191

vacuum = true

uwsgi6

To run Nginx and uWSGI at startup we need to add this strings to /etc/rc.conf:

nginx_enable="YES"
uwsgi_enable="YES"
uwsgi_uid=root
uwsgi_flags="--ini /usr/local/etc/uwsgi.ini"

nginx-uwsgi

I run uWSGI from root account to have access to GPIO device.
This isn’t secure of course, but at this time we can do this.

Let’s check our Nginx server first:

ng3

It’s works!

To check uWSGI server we can look into its log file:

uwsgi7

Interactive application

It is not very interesting to see only status of GPIO pins. Let’s add some interactivity.
I have changed first application and add a possibility to change GPIO value and configuration.

#!/usr/bin/env python

import cgi
import gpio

html1 = """\
<!doctype html>
<html>
<head><title>GPIO Configuration</title></head>
<body>
 <script>
  function formSubmit(fname) {
    document.forms[fname].submit();
  }
 </script>
 <h1>GPIO Configuration</h1>
 <form name="freload" method="get">
  <input type="button" value="reload" onClick="formSubmit(this.form.name)">
 </form>
 <p><table border=1 cellpadding=3>
  <tr><th>Name</th><th colspan=2>Value</th><th colspan=2>Config</th></tr>
"""

form1 = """\
  <tr>
   <form name="form%d" method="post">
    <input type="hidden" name="pin" value="%d">
     <td>%s</td>
     <td>%d</td>
     <td><select name="value" onChange="formSubmit(this.form.name)">
"""

option0_selected = "          <option value=\"0\" selected>0</option>"
option0_nonselected = "          <option value=\"0\">0</option>"
option1_selected = "          <option value=\"1\" selected>1</option>"
option1_nonselected = "          <option value=\"1\">1</option>"

form2 = """\
         </select>
     </td>
     <td>%s</td> 
     <td><select name="config"  onChange="formSubmit(this.form.name)">
"""

option_selected = "          <option  value=\"%s\" selected>%s</option>"
option_nonselected = "          <option  value=\"%s\">%s</option>"

form3 = """\
         </select>
     </td>
   </form>
  </tr>
"""

html2 = """\
 </table>
"""
 
html3 = """\
</body>
</html>
"""

def flags2list(flags):
    lists = []

    if (flags & gpio.PIN_INPUT):
        lists.append('INPUT')
    if (flags & gpio.PIN_OUTPUT):
        lists.append('OUTPUT')
    if (flags & gpio.PIN_OPENDRAIN):
        lists.append('OPENDRAIN')
    if (flags & gpio.PIN_PUSHPULL):
        lists.append('PUSHPULL')
    if (flags & gpio.PIN_TRISTATE):
        lists.append('TRISTATE')
    if (flags & gpio.PIN_PULLUP):
        lists.append('PULLUP')
    if (flags & gpio.PIN_PULLDOWN):
        lists.append('PULLDOWN')
    if (flags & gpio.PIN_INVIN):
        lists.append('INVIN')
    if (flags & gpio.PIN_INVOUT):
        lists.append('INVOUT')
    if (flags & gpio.PIN_PULSATE):
        lists.append('PULSATE')

    return lists

def flags2str(flags):
    return ','.join(flags2list(flags))


def application(env, start_response):

    configs = { 'INPUT' : gpio.PIN_INPUT,
                'OUTPUT' : gpio.PIN_OUTPUT,
                'OPENDRAIN' : gpio.PIN_OPENDRAIN,
                'PUSHPULL' : gpio.PIN_PUSHPULL,
                'TRISTATE' : gpio.PIN_TRISTATE,
                'PULLUP' : gpio.PIN_PULLUP,
                'PULLDOWN' : gpio.PIN_PULLDOWN,
                'INVIN' : gpio.PIN_INVIN,
                'INVOUT' : gpio.PIN_INVOUT,
                'PULSATE' : gpio.PIN_PULSATE }

    gpioc = gpio.controller()
    max_pin = gpioc.max_pin

# Parse Post request

    try:
       request_body_size = int(env.get('CONTENT_LENGTH', 0))
    except (ValueError):
       request_body_size = 0
    
    request_body = env['wsgi.input'].read(request_body_size)
    d = cgi.parse_qs(request_body)

    rpin  = d.get('pin',[''])[0]
    rvalue  = d.get('value',[''])[0]
    rconfig = d.get('config',[''])[0]

    rpin = cgi.escape(rpin)
    rvalue = cgi.escape(rvalue)
    rconfig = cgi.escape(rconfig)

    if request_body_size > 0:
        cpin = gpioc.pin(int(rpin))
        if int(rvalue) == 1:
            cpin.value = gpio.HIGH
        else:
            cpin.value = gpio.LOW
        cpin.config = configs[ rconfig ]

# Forming answer: creating a list with all known pins

    content = html1

    for p in range(0, max_pin + 1):
        pin = gpioc.pin(p)

        try:
            value = pin.value
        except gpio.GpioError as e:
            # pin with this number is unknown
            continue

        name = pin.name
        config = flags2str(pin.config)
        caps = flags2list(pin.caps)

        content += form1 % (p, p, name, value)

        if value == 0:
            content += option0_selected
            content += option1_nonselected
        else:
            content += option0_nonselected
            content += option1_selected

        content += form2 % (config)

        for cap in caps:
            if cap in config:
                content += option_selected % (cap, cap)
            else:
                content += option_nonselected % (cap, cap)

        content += form3

    content += html2

# Print request for debug

#    if request_body_size > 0:
#        content += "<p>Post request: pin = %d, value = %d, config = %s" % (int(rpin), int(rvalue), rconfig)

    content += html3

    status = "200 OK"
    headers = [('Content-type', 'text/html'),('Content-Length', str(len(content)))]
    start_response(status, headers)

    return [content]

Let’s check it:

gpioweb1

We can see here pin name, it current value, selection to change pin value, it current config and selection to change config.

Changing value for pin 25 we can turn on and turn off our LED.

gpioweb2

To see how changes value of pin configured as input we can use button “Reload” at the top of the page.

This is simple application: ugly interface, no computing on client side, no REST API and so on.
But this a good first step on controlling Raspberry Pi with FreeBSD through web.

Advertisements

5 thoughts on “Web Control of Raspberry Pi GPIO in FreeBSD

  1. Hallo and a big THX for your work.
    There must be a typo in the second script – I got an error in line 150 – you know – boys form IT are the lazy ones 🙂

    Once again many thanks for your blog.

    armin

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s