Raspberry PI and Django Channels

At the end of 2015 Django was awarded $150,000 from the Mozilla Open Source Support program to help fund the development of Channels, the initiative to rewrite the core of Django to support (among other things) WebSockets and background tasks. Channels would appear to offer a nice way to to provide a rich web interface on the Raspberry PI that can display real time info from attached sensors, etc that are being processed by dedicated background processes. To learn about Channels I’ve used the info from the Heroku guide and the Channels getting started docs to complete the following:

  1. Install and set-up Django Channels on a RaspberryPI Model B+.
  2. Run python background process that will continuously send a value over a Channel. (This will be represent a background process which could be reading data from a sensor.)
  3. Serve a web page that will display the value received from the background process via WebSocket.

Why Channels?

Traditional web pages were static and browsing was based around the simple concept of HTTP requests and responses: a client (web browser or any other client) connects to a server and sends an HTTP request the server returns an HTTP response that is usually rendered by a browser. While this allows relatively quick and simple development to see real time information we’d have to refresh the page or set-up something like AJAX. Modern web technologies such as WebSockets enable us to create interactive and engaging functionality within our applications by allowing the client interface of a web application to communicate continuously with the corresponding real-time server with either able to send data at any time. New frameworks such as Meteor have been designed to provide this functionality but at its core, Django is built around requests and responses. Django Channels is a project that amongst other things allows Django to handle WebSockets. Channels also allow for background tasks that run on the same servers as the rest of Django. Together, these projects will help considerably improve Django’s role of backing rich web experiences as well as native applications. The following diagram from the Heroku, Get Started with Django Channels guide is a nice overview of how it works:

Django Channels Overview

Get it all running

I’m using a RaspberryPI Model B+ with the latest Raspbian image. I’m assuming some knowledge of Python, virtual environment, Django, etc but if not the Django Girls Tutorial is good. The following details the steps I followed.

Install Django

After logging in to your PI over ssh, from your virtual env install Django:

pip install Django

Create a new project (mine is called sensor):

django-admin startproject sensor .

As a test to confirm everything is set-up and running you can start a Django development server:

python manage.py run server <Raspberry PI IP>:8000

And then from a PC connected to same network as Raspberry PI browse to

http://<Raspberry PI IP>:8000/ 

You should see the “Congratulations on your first Django-powered page.”

Install Channels

Now install Channels:

pip install channels

In your project settings file (i.e. /sensor/settings.py) add ‘channels’ to the INSTALLED_APPS section. This adds the main Channels functionality to the app.

Channel layers

Next we need to add a channel layer. This is the transport mechanism that Channels uses to pass messages from producers (message senders) to consumers. Redis is the preferred production option. You also have to use Redis to enable to use Channels with a background worker so we will just go ahead and use it. To read about the other options and about the channel layer in more detail you can see the documentation.

Download, build and start Redis:

First we need to install Redis on the Raspberry PI. Download and compile the latest Redis tar ball by following these steps:

wget http://download.redis.io/redis-stable.tar.gz
tar xvzf redis-stable.tar.gz
cd redis-stable
make
sudo cp src/redis-server /usr/local/bin/

The Redis quick start documentation is here for more details.

Then start the Redis server from the command line using:

redis-server

Install Redis Channel layer:

The Redis channel layer is part of a separate package (still written by the Channels developer) and we install it with:

pip install asgi_redis

Next we add some config to make Django use the new Channels request handle. Add another new file, /sensor/asgi.py:

ASGI Config

Then in the /sensor/settings.py file we add the following at the bottom:

CHANNEL_LAYERS = {
“default”: {
“BACKEND”: “asgi_redis.RedisChannelLayer”,
“CONFIG”: {
“hosts”:[os.environ.get(‘REDIS_URL’,‘redis://localhost:6379’)],
},
“ROUTING”: “sensor.routing.channel_routing”,
},
}

Channel routing and consumers:

We have configured Channels to use Redis and given it details about the server. The “ROUTING” section points to a file where we map channel events to our “consumer” functions. In this case we map the WebSocket connect, receive and disconnect events. Add a new file /sensor/routing.py:

Channel Routing

Then we create our new /sensor/consumers.py file and add a function for each event. First we deal with ws_connect. In our application we want any client that browses to a “sensor” page to receive sensor WebSocket data from the background worker. To do this we will use a Channel Group. Groups are discussed in more detail in the concept section of the docs but basically they allow the server to broadcast messages to anyone who is in the group. Here we simply add the the new connection to the “sensor” group and send them a direct message to let them know they have connected:

Connect Consumer

Next we handle ws_message. This is called whenever the server receives a WebSocket message from a client. In our case we don’t use this functionality but the code shows how you could read the message text:

Received Message Consumer

Finally we handle ws_disconnect. This is called when the client disconnects and in this case we simply remove their reply channel from the sensor group:

Disconnect Consumer

Views and URL routing:

We’ve taken care of Channel routing and functionality, the next step is to add the view for the sensor page and the associated url routing.

Add new /sensor/templates/ and /sensor/static/ directories.

In the settings.py file change the TEMPLATES/DIRS to:

TEMPLATES = [
{
...
‘DIRS’: [os.path.join(BASE_DIR, ‘sensor/templates’)],
...
},
]

Also add a new setting:

STATICFILES_DIRS = ( os.path.join(BASE_DIR, ‘sensor/static/’),)

In url.py we add a new route in urlpatterns for the sensor page:

urlpatterns = [
url(r’^admin/’, admin.site.urls),
url(r’^sensor/’, views.sensor, name=’sensor’)
]

And add a new view in views.py:

Sensor View

And now we add sensor.html in the templates directory:

Sensor HTML

The static javascript files are added in the static directory. jquery-1.12.1.min.js is the jQuery library and reconnecting-websocket.min.js is a library that automatically reconnects the WebSocket if connection is dropped.

sensor.js contains our code that updates the page with WebSockets info:

Sensor Javascript

Here we are connecting a WebSocket when the page opens. When the connection is open we update our page with “Connected!”. Whenever a WebSocket message is received the chatsock.onmessage event will be called and we’ll update the page with the received message text. Right now there is nothing sending any WebSocket messages but we’ll set up a background worker to do this next. Before we do that make sure the Redis server is running:

redis-server

Then start the development Django server:

python manage.py runserver <Raspberry PI IP>:8000

If you browse to <Raspberry PI IP>:8000/sensor/ you should see the Sensor Reading page with a “You’re connected to sensor :)” message. You can open Developer tools to view the Javascript console messages and also see the connection on the Django server console.

Background Worker:

Initially I was unsure how I would implement a background worker so I emailed Andrew Godwin who is working on Channels to ask his advice. He was very responsive and helpful and recommended the following:

“You’ll have to handle running the background process yourself — Channels only reacts to events (in the same way celery only reacts to tasks) so it won’t be able to poll a sensor or other external thing. You can write a pretty simple one as a Django management command and just use a `while True:` loop inside `handle()` with a `time.sleep(10)` or something in the loop to run at 10 second intervals, and then inject into Channels from there.”

Followed this guide on how to write a custom management command (more details in the Django docs):

In the same dir as manage.py:

python manage.py startapp sensorWorker

This creates a new sensorWorker application with associated directory and files.

In the sensorWorker directory, create management and management/commands subdirectories. Create blank __init__.py files in both new directories.

Now we create a file in the commands dir that has same name as the custom command, in this case it will be readSensor.py:

Background worker code

This code is a simple loop that simulates the action of continuosly reading a sensor and sending the value over a group channel.

Finally add app, ’sensorWorker’, to INSTALLED in settings.py.

To start the worker, in a separate console:

python manage.py readSensor

You should see the “Sensor reading…” output in the console.

Bringing it all together

As a final check we need to make sure each of the following are running:

Redis server:

redis-server

Django development server:

python manage.py runserver <Raspberry PI IP>:8000

Background worker:

python manage.py readSensor

Now when you open a browser to the sensor page you should see the sensor reading update — via WebSockets!