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:
- Client – Server separation; server and client may be implemented independently, even in varying programming languages, when the interface between them is well-matched.
- 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.
- 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)
- 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.
- 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.pyFirst 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 BrickletIn 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 mimerenderTo 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.0As 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: rtnThe 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()
Hinterlasse einen Kommentar