Simple REST API for Raspberry Pi with FreeBSD

My previous web application for control GPIO was monolithic.
This mean that server-side and client-side was in one script.
But modern Web application going to be dynamic with Representational State Transfer (REST).

Here is a description of a simple REST API service for Raspberry Pi with FreeBSD.

Python 3

First, I moved to Python 3.
It is simple to install Python 3 on Raspberry Pi with FreeBSD. There is a result:

py7

Next step is updating freebsd-gpio module.
For Python 3 we need to update _bsdgpiomodule.c in the python directory.

Here is a patch for this file:

200a201,208
> static struct PyModuleDef _bsdgpiomodule = {
>     PyModuleDef_HEAD_INIT,
>     "_bsdgpio",
>     "BSD GPIO Module",
>     -1,
>     GpioMethods
> };
> 
202c210
< init_bsdgpio(void)
---
> PyInit__bsdgpio(void)
206c214
<     m = Py_InitModule("_bsdgpio", GpioMethods);
---
>     m = PyModule_Create(&_bsdgpiomodule);
208c216
<         return;
---
>         return NULL;
212a221
>     return m;

Nginx

Config-file for nginx web-server now looks like this:

nginx
I use location /api as start point for REST service.

uWSGI

Config-file for uWSGI now look like this:

uwsgi

The Script

At this time my new REST API script works with local time and GPIO. It returns JSON objects.

It reacts on the following requests:


  • GET /api/localtime – returns current system time

  • GET /api/gpio – list of all GPIO pins with its name, value and configs

  • GET /api/gpio/%d – name, value and configs of pin %d

  • PUT /api/gpio/%d?value=v – setup value of pin %d to v

  • PUT /api/gpio/%d?config=c – setup config of pin %d to c

  • GET /api/gpio/%d/name – get name of pin %d

  • GET /api/gpio/%d/value – get value of pin %d

  • GET /api/gpio/%d/config – get config of pin %d

  • GET /api/gpio/%d/caps – get caps of pin %d


Here is a source code of it:

#!/usr/bin/env python

import cgi
import re
import time
import gpio
import json


def notfound ( env, start_response ):
    start_response( '404 Not Found', [('Content-type', 'text/plain')] )
    return [b'Not Found']

def badrequest ( env, start_response ):
    start_response ( '400 Bad Request', [('Content-type', 'text/plain')] )
    return [b'Bad Request']


def localtime ( env, start_response ):
    t = time.localtime()
    lt = { 'time': { 'year': t.tm_year,
                     'month': t.tm_mon,
                     'day': t.tm_mday,
                     'hour': t.tm_hour,
                     'minute': t.tm_min,
                     'second': t.tm_sec
                   }
         }
    jlt = json.dumps(lt)
    resp = jlt.encode('utf-8')
    start_response( '200 OK', [('Content-type', 'application/json')] )
    return [resp]


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 gpioapi ( 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

    request = env['REQUEST_METHOD']
    uri = re.split('/',re.sub('/api/','',env['PATH_INFO']))

    if ( (len(uri) == 1) or ((len(uri) == 2) and (uri[1] == '')) ):
        if ( request != 'GET' ):
            badrequest( env, start_response )
        else:
            gpiol = {}
            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
                 gpiol[p] = { 'name': pin.name, 'value': value, 'config': flags2str(pin.config), 'caps': flags2list(pin.caps) }
            jgpiol = json.dumps(gpiol)
            resp = jgpiol.encode('utf-8')
            start_response( '200 OK', [('Content-type', 'application/json')] )
            return [resp]

    if ( (len(uri) == 2) or ((len(uri) == 3) and (uri[2] == '')) ):
        gpiol = {}
        p = uri[1]
        pin = gpioc.pin(int(p))
        try:
            value = pin.value
        except gpio.GpioError as e:
            notfound ( env, start_response )
        if ( request == 'PUT' ):
            query=re.split('=',env['QUERY_STRING'])
            if ( len(query) == 2 ):
                op = query[0]
                val = query[1]
                if ( op == 'value' ):
                    if ( int(val) == 1 ):
                        pin.value = gpio.HIGH
                    else:
                        pin.value = gpio.LOW
                elif ( op == 'config' ):
                    if ( val.upper() in configs ):
                        pin.config = configs[ val.upper() ]
        if ( (request == 'PUT') or (request == 'GET') ):
            gpiol[p] = { 'name': pin.name, 'value': value, 'config': flags2str(pin.config), 'caps': flags2list(pin.caps) }
            jgpiol = json.dumps(gpiol)
            resp = jgpiol.encode('utf-8')
            start_response( '200 OK', [('Content-type', 'application/json')] )
            return [resp]
           

    if ( len(uri) > 2 ):
        p = uri[1]
        pin = gpioc.pin(int(p))
        try:
            value = pin.value
        except gpio.GpioError as e:
            notfound ( env, start_response )
        op = uri[2].lower()
        if ( request == 'GET' ):
            if ( op == 'name' ):
                gpiol = { 'name': pin.name }
                jgpiol = json.dumps(gpiol)
                resp = jgpiol.encode('utf-8')
                start_response( '200 OK', [('Content-type', 'application/json')] )
                return [resp]
            elif ( op == 'value' ):
                gpiol = { 'value': value }
                jgpiol = json.dumps(gpiol)
                resp = jgpiol.encode('utf-8')
                start_response( '200 OK', [('Content-type', 'application/json')] )
                return [resp]
            elif ( op == 'config' ):
                gpiol = { 'config': flags2str(pin.config) }
                jgpiol = json.dumps(gpiol)
                resp = jgpiol.encode('utf-8')
                start_response( '200 OK', [('Content-type', 'application/json')] )
                return [resp]
            elif ( op == 'caps' ):
                gpiol = { 'caps': flags2list(pin.caps) }
                jgpiol = json.dumps(gpiol)
                resp = jgpiol.encode('utf-8')
                start_response( '200 OK', [('Content-type', 'application/json')] )
                return [resp]

    badrequest( env, start_response )


pathmap = { 'gpio': gpioapi,
            'localtime': localtime,
          }


def application ( env, start_response ):
    path = re.split('/',re.sub('/api/','',env['PATH_INFO']))[0]
    handler = pathmap.get( path, notfound )
    return handler( env, start_response )

This source code also available here: https://github.com/vzaigrin/BSD-REST.git

Demonstration

Let’s show how it works.

Local time:

localtime

List of all GPIO pins:

gpio

Information about one pin:

gpio.info

Changing pin

Let’s try to change a pin. I still have a LED connected to pin 25. Let’s try to play with it.

gpio.pin25

And here’s the result:

SDIM0001sm

Conclusion

This is a simple script.
The next steps will be adding more functionality, separating to the some files and adding a client.

Advertisements

4 thoughts on “Simple REST API for Raspberry Pi with FreeBSD

  1. Hi there, I was wondering if you have ever tried to set up a hotspot with raspberry pi using tl-wn725n (rlt8818eu) wifi adaptor in freeBSD? I read your post and it seems that the wifi adaptor has to support hostap, mine doesnt (ifconfig wlan0 list caps retunrs ), is there any way around it?

    • Hi

      To get HostAp mode we need to find an adapter with HostAP mode 🙂

      In the FreeBSD Manual Pages I found three chipsets for USB WiFi adapters which support HostAP mode.
      This are rum(4), run(4) and ural(4) adapters.
      Each manual page has a list of adapters from different manufacturers.

      But be careful with this list.
      For example we can see ASUS USB-N13 ver. A1 in the list of adapters for driver RUN(4).
      But ASUS USB-N13 ver. A2 doesn’t support HostAP mode.

      • Thanks for the reply, can you recommend any chipset that works both in hostap and ibss mode in freebsd over rpi?

      • I can only confirm that Ralink Technology RT3900E chip (MAC/BBP RT5392, RF RT5372) works and support HostAP mode.
        I didn’t check other variants.

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