Getting data into Django

Following straight on from last time, where we built our database to take temperature readings from SmartThings, the next step is to actually get the data in so that we can display it.

We’ll do that via an API in Django itself.

There is an entire REST framework for Django but let’s start with a quick and dirty solution using a normal view in our app’s views.py that takes a JSON message and processes it. The incoming fields will be the sensor name and the value – for simplicity at this stage we can set the timestamp to the time the message was received by Django:

from django.shortcuts import render
from django.http import HttpResponse,
        HttpResponseForbidden, HttpResponseBadRequest
from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import csrf_exempt
from django.utils import timezone

import json

from .models import Reading, Sensor

@csrf_exempt
def record_temperature(request):

  incoming = json.loads(request.body)

  update_sensor, created = 
      Sensor.objects.get_or_create(name=incoming['sensor'])

  reading = Reading(sensor = update_sensor, 
                    time = timezone.now(),
                   value = incoming['value'])
    reading.save()

  return HttpResponse('OK')

Remember, this is a quick solution. The @csrf_exempt line tells Django to bypass the validation to prevent cross site request forgeries and is needed to allow us to POST from another application. This is important to prevent requests that attempt fake user interactions with sites, but an API needs to be able to take data from external sources.

We’re also going to automatically create the sensor object if one doesn’t exist with the incoming sensor name so that if we rename the device in ST we automatically create a new sensor in Django.

We could take a unique sensor ID from ST instead, but I think it makes sense to use the name. I name the sensors according to where they’re located, so if I moved a sensor from one room to another it would make sense to associate the readings with a new name as the temperature profile of the room will be different and the older readings would not be relevant.

Finally, there’s next to no validation and no authentication. This is very much a prototype just to see if we can get the data flowing. We also need to set up a URL to expose this view in the urls.py for our temperature app:

url(r'^temperature/api/record/$', 
    views.record_temperature, 
    name='temperature_record')

We can test this by sending a direct message with a test sensor name and value:

curl -v http://myserver/temperature/api/record/ -d '{"sensor": "Test", "value": 1  }'

This should put the data into the database, and we can check directly on the database:

MariaDB [website]> select * from temperature_reading, temperature_sensor where temperature_reading.sensor_id = temperature_sensor.id and temperature_sensor.name = 'Test'      -> ; 
+-----+---------------------+-------+-----------+----+------+ 
| id  | time                | value | sensor_id | id | name | 
+-----+---------------------+-------+-----------+----+------+ 
|  72 | 2016-07-19 21:14:05 |     1 |         7 |  7 | Test | 
|  74 | 2016-07-19 21:38:27 |     1 |         7 |  7 | Test | 
| 219 | 2016-07-24 19:22:16 |     1 |         7 |  7 | Test | 
| 220 | 2016-07-24 19:39:46 |     1 |         7 |  7 | Test | 
+-----+---------------------+-------+-----------+----+------+

As you can see, I’ve done a few tests…

If we’re sending our ST things via Zato we need our Zato service to send the incoming ST message to the Django API.

We could bypass that by modifying our Django code to accept the parameters being pushed out from the ST SmartApp. If you recall from previously we defined the data being sent by the SmartApp like this:

 
def map = [ eventid: "${evt.id}",
            date: "${evt.isoDate}",
            eventname: "${evt.name}",
            devicename: "${evt.displayName}",
            value: "${evt.stringValue}"
          ] 

So it would just mean changing the Django code to use “devicename” instead of “sensor” and to check that the eventname parameter is “temperature”.

But let’s stick with Zato, because this shows the power of an ESB. If we couldn’t control the output of the SmartApp or the input of the Django app we’d use Zato to manage the translation of fieldnames.

First of all, let’s set up a service:

from zato.server.service import Service
from zato.common import DATA_FORMAT

from anyjson import dumps

class ProcessEvent(Service):

  def handle(self):

    self.logger.info(self.request.payload['eventname'] 
      + ": " + self.request.payload['devicename'] 
      + ": " + self.request.payload['value'])

    if self.request.payload['eventname'] == 'temperature':
      request = { 
              'sensor': self.request.payload['devicename'],
              'eventname': self.request.payload['eventname'],
              'value': self.request.payload['value']
                }
      
      logtemperature = 
               self.outgoing.plain_http.get('logtemperature')
 
      response = logtemperature.conn.send(self.cid, 
                                              dumps(request))

I’m going to expose this service as /smartthings/processevent:

Zato process smartthings event

And change the SmartApp configuration accordingly:

Smartthings log event

And finally set up the logtemperature outbound channel in Zato:

zato log temperature

Again, we could configure this to send the message straight to our Django API at temperature/api/record/ on port 80 but the benefit of using the ESB in the middle is that we can send all of our events to the ESB and use the ESB code to decide which services to call to handle them based on the event name.

That should all hang together and send the ST temperature data via Zato to our Django database. The next step will be to write the views to display it.

in Home Automation

Related Posts