Creating a REST web service with Python for serving TinkerForge sensor data in JSON

Creating a REST web service with Python for serving TinkerForge sensor data in JSON

This tutorial is demonstrating one solution to serve data read from TinkerForge sensors via a REST web service. It is implemented utilizing Python on a Fedora Unix (for ARM) running on a Cubietruck. It is not handling the basics of connecting to TinkerForge hardware, as this is covered quite well on their homepage.

About REST

REST is an acronym for “Representational State Transfer” and stands for a programmers paradigm mainly used in web applications. The main idea behind REST states, that one URL should present exactly one type of site content (independent where this content was generated from; e.g. a search on database). This behavior for static content (such as permalinks) is written down in the HTTP Internet standard. REST is based on five principles:

  1. Client – Server separation; server and client may be implemented independently, even in varying programming languages, when the interface between them is well-matched.
  2. Cacheable – All responses must be flagged as cacheable or not; when using cache headers in the responses with cache expired times this helps improving performance and scalability, as the client can respect this for further connections to the service.
  3. Stateless Transfer – this means that each individual request from a client to the service holds all necessary information to establish the connection (e.g. user data for authentication)
  4. Layered System – for a client it is not clear whether it is already connected to the end server or everywhere in-between (for instance: a load balancing system). This is also connected with the REST caching.
  5. Uniform interface – this means a decoupling of the architecture. An individual resource is identified in a request for instance using URIs. And the resources are conceptual separated from the representation that is send to the client. This means, a REST service may connect to a database based on a query that is invoked by the client connection, but will return not the database query back, but rather XML or JSON. For the client it is not visible how exactly the service is “working” in the background.

It is important to note, that REST itself is not a standard, like SOAP-based web services are. REST is meant to be an architectural style, whereas SOAP is a protocol. But REST is resting upon common Internet standards, like URI, HTTP or XML.

REST HTTP methods

URL (or resource) GET PUT POST DELETE
Collction URI:
http://myserver/weather/
List all URIs and maybe more details of collection members Replace the collection by another one Create a new collection member. The service will return the URI of the new member. Delete the full collection
Element URI, such as
http://myserver/weather/temperature
Retrieve a representation of this member including the data connected with the member in a format like JSON or XML. Create a new member or when it exists already, replace it. Most likely not used / implemented. Delete the specific member in the collection

Prerequisites

After some theory, let´s get started with the implementation. I assume, that you already installed python and „pip”, as well as the whole Tinkerforge SDK on your system.

If not already, install them:

sudo yum install python-setuptools python-setuptools-devel python

To install the Tinkerforge SDK, please visit the webpage about their SDK for Python installation.

Let´s get started

There are multiple frameworks, like Django REST framework etc. available for Python, but I used Flask with mimerender as this combination seemed most flexible in their set-up for me. As just basic functionality regarding GET requests is needed for the implementation I decided against a complex framework like Django is.

To install Flask and mimerender, just call the following command from the command line:

sudo pip install Flask
sudo pip install mimerender

After installation,  create a Python file that will include the source code for setting up a.) a rest web service b.) the connection to the Tinkerforge hardware.

[seiler@localhost]# touch myService.py [seiler@localhost]# nano myService.py

First we need our parameters about the Tinkerforge hardware, like the UID of your Wireless Lan brick and the values for your HOST  and PORT. I prefer to use the Brickviewer to access those values.

#!/usr/bin/env python 
# -*- coding: utf-8 -*-
HOST = "192.168.1.151"
PORT = 4223
UID = "6kqfG9" # Change to your UID
#U1 = "maz" #Ambient light Bricklet
#U2 = "kqh" #Temperature IR Bricklet 
U3 = "kFL" #Humidity Bricklet
U4 = "ncz" #Temperature Bricklet

In case you are ready to implement more than just the temperature service, you can also set-up and connect further devices. But this is not covered here.

To get the connection and the REST service working we need some imports, like the just intalled Flask and mimerender, but also JSON libs and the Tinkerforge libraries.

from tinkerforge.ip_connection import IPConnection 
from tinkerforge.bricklet_temperature import Temperature
from flask import Flask
import json
import mimerender

To set-up the app, we are defining special routes for specific URLs, like the root („/“) or our temperature REST service.

For instance, our service for serving the current temperature shall be available at /rest/v1.0/temperature/, a route for this URL is defined as follows:

@app.route('/rest/v1.0/temperature/')
@mimerender(
    default = 'json',
    html = render_html,
    xml  = render_xml,
    json = render_json,
    txt  = render_txt
)
def getTemp():
  return{'rtn': str(currentTemperature)}

The global variable currentTemperature is returned through the mimerender in the formats HTML, XML, JSON or as plain text, where you can define the output by yourself.  In this example above, the standard return will be in JSON.

The current value of currentTemperature is set by a callback function every 100ms:

# Callback function for temperature callback (parameter has unit °C/100)
def cb_temperature(temperature):
  global currentTemperature 
  currentTemperature = temperature/100.0

As just explained, you have several channels than can be used to output the content. In my case, they are defined as follows:

mimerender = mimerender.FlaskMimeRender()

render_xml = lambda rtn: '<?xml version="1.0" encoding="UTF-8"?><weather><description>Current Temperature in Bochum, Germany</description<currentTemp>%s</currentTemp></weather>'%rtn
render_json = lambda **args: json.dumps(args)
render_html = lambda rtn: '<html><body><p>Current Temperature in Bochum, Germany: <strong>%s</strong></p></body></html>'%rtn
render_txt = lambda rtn: rtn

The main function is capable of starting the IP connection to the Tinkerforge hardware, setting up the callback for reading the temperature value and for starting your REST service.

The IP connection to Tinkerforge hardware is established first with the HOST and PORT parameters set-up in the beginning of your script. After that the device object „t“ is created, calling the Temperature for the specific UID „U4“  through your IP connection.

With t.set_temperature_callback_period(time in ms) you can actual set the period for writing the temperature into the current temperature variable. In this case we call it every 100ms.

app.run(host=’192.168.1.144′, port=8080) will make your service available at the specific IP and Port. In my case a „0.0.0.0“ was not working as IP address, but it might be working for you.

if __name__ == "__main__":
  ipcon = IPConnection() # Create IP connection 
  ipcon.connect(HOST, PORT)  
  t=Temperature(U4, ipcon) #Create device object
  # Set Period for temperature callback to 100ms
  # Note: The callback is only called every second if the 
  #       temperature has changed since the last call!
  t.set_temperature_callback_period(100)

  # Register temperature callback to function cb_temperature
  t.register_callback(t.CALLBACK_TEMPERATURE, cb_temperature)
  #app.debug = True
  app.run(host='192.168.1.144', port=8080)
  ipcon.disconnect()

Starting and testing your service

The service can be started from console and will list the full URL where it is available from. In the example below, the first connection to our designed URL returned the HTTP status code 200 and was delivering the content as defined before. As this connection was invoked from a web browser, the requested feedback was HTML.

[seiler@localhost]# python myService.py
 * Running on http://192.168.1.144:8080/
 * Restarting with reloader
192.168.1.146 - - [29/May/2014 19:40:14] "GET /rest/v1.0/temperature/ HTTP/1.1" 200 -

In the following examples we are using curl to get the data back in different formats:

XML:

[root@localhost ~]# curl -i -H "Accept: application/xml"  http://192.168.1.144:8080/rest/v1.0/temperature/
HTTP/1.0 200 OK
Vary: Accept
Content-Type: application/xml
Content-Length: 153
Server: Werkzeug/0.9.4 Python/2.7.5
Date: Thu, 29 May 2014 17:44:04 GMT

<?xml version="1.0" encoding="UTF-8"?><weather><description>Current Temperature in Bochum, Germany</description<currentTemp>21.62</currentTemp><

 JSON:

[root@localhost ~]# curl -i -H "Accept: application/json"  http://192.168.1.144:8080/rest/v1.0/temperature/
HTTP/1.0 200 OK
Vary: Accept
Content-Type: application/json
Content-Length: 16
Server: Werkzeug/0.9.4 Python/2.7.5
Date: Thu, 29 May 2014 17:45:27 GMT

{"rtn": "21.62"}

 HTML:

[root@localhost ~]# curl -i -H "Accept: application/json"  http://192.168.1.144:8080/rest/v1.0/temperature/
HTTP/1.0 200 OK
Vary: Accept
Content-Type: application/html
Content-Length: 16
Server: Werkzeug/0.9.4 Python/2.7.5
Date: Thu, 29 May 2014 17:45:27 GMT

<html><body><p>Current Temperature in Bochum, Germany: <strong>22.52</strong></p></body></html>

 

 

Full source code

Here is the full code example I implemented to get the whole service working:

#!/usr/bin/env python 
# -*- coding: utf-8 -*-
HOST = "192.168.10.151"
PORT = 1213
UID = "61qfG9" # Change to your UID
U3 = "k2L" #Humidity Bricklet
U4 = "1cz" #Temperature Bricklet


from tinkerforge.ip_connection import IPConnection 
from tinkerforge.bricklet_temperature import Temperature
from flask import Flask
import json
import mimerender

# Callback function for temperature callback (parameter has unit °C/100)
def cb_temperature(temperature):
  global currentTemperature 
  currentTemperature = temperature/100.0

mimerender = mimerender.FlaskMimeRender()

render_xml = lambda rtn: '<?xml version="1.0" encoding="UTF-8"?><weather><description>Current Temperature in Bochum, Germany</description<currentTemp>%s</currentTemp></weather>'%rtn
render_json = lambda **args: json.dumps(args)
render_html = lambda rtn: '<html><body><p>Current Temperature in Bochum, Germany: <strong>%s</strong></p></body></html>'%rtn
render_txt = lambda rtn: rtn

app = Flask(__name__)

@app.route('/')
@mimerender(
    default = 'json',
    html = render_html,
    xml  = render_xml,
    json = render_json,
    txt  = render_txt
)
def index():
	return {'rtn': 'Please call: <a href="/rest/v1.0/temperature/">/rest/v1.0/temperature/</a> to get the current temperature'}

@app.route('/rest/v1.0/temperature/')
@mimerender(
    default = 'json',
    html = render_html,
    xml  = render_xml,
    json = render_json,
    txt  = render_txt
)
def getTemp():
  #print('Temperature: ' + str(temp))
  return{'rtn': str(currentTemperature)}
    #return{temp}	

	
if __name__ == "__main__":
  ipcon = IPConnection() # Create IP connection 
  ipcon.connect(HOST, PORT)  
  t=Temperature(U4, ipcon) #Create device object
  # Set Period for temperature callback to 100ms
  # Note: The callback is only called every second if the 
  #       temperature has changed since the last call!
  t.set_temperature_callback_period(100)

  # Register temperature callback to function cb_temperature
  t.register_callback(t.CALLBACK_TEMPERATURE, cb_temperature)
  app.debug = True
  app.run(host='192.168.1.144', port=8080)
  ipcon.disconnect()

 

 

 

2019-01-03T16:47:21+02:00Tags: , , , , , |

Share This Story, Choose Your Platform!

Leave A Comment