Hello and welcome to my sixth update on the Universal LED Animator. This time I'm going to describe the basic structure of the Animator's web interface. As a quick refresher, my Hero boards (Redbear Blends) are connecting via BLE to the Beagle Bone Black Wireless which serves as the central unit for the system and provides users with a web interface to configure the LED patterns on the Animators. Naturally we can do more than only setting up patterns - we also want to be able to change animation speeds, brightness, and colours. At the moment I have a basic, no-style webpage providing pattern selection buttons and a brightness slider for a single Animator device:
Let's see how I got there...
What I had already
In my last week's quickie update I gave an explanation of the Bluefruit library and why I preferred it over the GATT SDK. With a basic script connecting to the Blend and pushing my super-simple Animator protocol over BLE I could start adding features and connecting them to the web interface. For now I've only been working with a single device and more will be added after the interface is full-featured.
Flask
In order to serve a website, I needed a Python Web framework. A web framework is a scaffolding that will allow me to build interactive websites and script around their features, allowing me to connect interface widgets like buttons to physial actions, like changing the LED strip brightness. I decided to pick up Flask, an April-Fools-joke-turned-microframework, for its extreme simplicity, ease of use and flat learning curve. As is often the case with Python web features, Flask's minimal application is very concise, consisting of five actual lines of code:
from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!'
At the same time, it's very easy to expand it and create a decent web interface for an embedded application - Flask is commonly used to create robot or smart building interfaces. As I have virtually no experience in web development, those were all very important characteristics for me. The main Flask-related code was developed over a few days and for now consists of only two functions, one for a slider and another for buttons:
#Create the command queue for inter-process communication and add the first command. queue = multiprocessing.Queue() queue.put('Q') #Serve the pattern buttons. @app.route("/") @app.route("/<state>") def update_mode(state=None): global queue print "Mode changed." queue.put('m'+chr(ord(state))) template_data = { 'title' : state, } html = open("index.html") response = html.read().replace('\n', '') html.close() return response #Serve the brightness slider. @app.route("/set_brightness") def set_brightness(): brightness = request.args.get("brightness") print "Brightness: " + str(brightness) queue.put('b'+str(brightness))
First, I create a Queue object that will be used to exchange commands between the Flask app and a Bluefruit loop running in the background - more on that later. Then I serve the buttons for changing the pattern. Each of the 10 buttons is a link taking us to the same main website, but with a URL extended by the pattern number. This URL change is intercepted by Flask and triggers the update_mode method which reads the URL route and puts it in a variable that is added to the queue. The other function deals with the brightness slider, which sends GET requests on every value change. Those requests are then read by the URL processor in Flask and a method is executed, once again passing the value to the command queue. The command queue is read in an endless loop inside the Bluefruit subprocess, and each item is dequeued and sent over the BLE link to be parsed inside the Arduino firmware:
print("Waiting for commands.") # Wait forever for commands from the queue. while 1: if not (q.empty()): print("TXed.") msg = q.get() uart.write(msg)
The complete code is below:
import Adafruit_BluefruitLE from Adafruit_BluefruitLE.services import UART from itertools import cycle from flask import Flask, render_template, request import multiprocessing from distutils.log import debug import sys app = Flask(__name__) def loop_a(q): # Get the BLE provider for the current platform. ble = Adafruit_BluefruitLE.get_provider() #Main function to run all BLE-related logic def main(): #Get a clean start ble.clear_cached_data() adapter = ble.get_default_adapter() adapter.power_on() print('Using adapter: {0}'.format(adapter.name)) print('Disconnecting any connected UART devices...') UART.disconnect_devices() # Scan and connect to the first UART device you can find: print('Searching for UART device...') try: adapter.start_scan() device = UART.find_device() if device is None: raise RuntimeError('Failed to find an LED Animator') sys.exit() finally: adapter.stop_scan() print('Connecting to device...') device.connect() try: # Discover services print('Discovering services...') UART.discover(device) uart = UART(device) print("Waiting for commands.") # Wait forever for commands from the queue. while 1: if not (q.empty()): print("TXed.") msg = q.get() uart.write(msg) finally: device.disconnect() # Initialize BLE ble.initialize() # Run BLE loop ble.run_mainloop_with(main) #Create the command queue for inter-process communication and add the first command. queue = multiprocessing.Queue() queue.put('Q') #Serve the pattern buttons. @app.route("/") @app.route("/<state>") def update_mode(state=None): global queue print "Mode changed." queue.put('m'+chr(ord(state))) template_data = { 'title' : state, } html = open("index.html") response = html.read().replace('\n', '') html.close() return response #Serve the brightness slider. @app.route("/set_brightness") def set_brightness(): brightness = request.args.get("brightness") print "Brightness: " + str(brightness) queue.put('b'+str(brightness)) #Start a background Bluefruit process and launch the Flask app. def main(): multiprocessing.Process(target=loop_a, args=(queue,)).start() app.run(host= '0.0.0.0', debug=False, port=81) main() print "Loop ends."
As you can see, I used Python's very straightforward Multiprocessing capability to integrate Bluefruit's features with the Flask web app. Before Flask is run (line #95), a Multiprocessing process is created, running the Bluefruit's loop in the background, parallel to the main Flask app. The queue object is passe to the subprocess, allowing information exchange by adding items to the queue to be interpreted by either process. I only use one-way communication at the moment, but will expand it to two-way at some point, as some info needs to be returned by the Animators to the main app.
Code was created based on the book Beaglebone by Example and this Instructable.
JavaScript
My website's source looks like this:
<!DOCTYPE html> <html> <body> <h1>Universal LED Animator</h1> <div id="slidecontainer"> <input type="range" min="0" max="255" value="0" id="brightness"> <p>Brightness: <span id="brightness_value"></span></p> </div> <script> var slider = document.getElementById("brightness"); var output = document.getElementById("brightness_value"); output.innerHTML = slider.value; slider.oninput = function() { output.innerHTML = slider.value; var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { } }; xhttp.open("GET", "/set_brightness?brightness=" + slider.value, true); xhttp.send(); } </script> <p>Pattern:</p> <a href="/1" id="off"> <button type="button">1</button> </a> <a href="/2" id="off"> <button type="button">2</button> </a> <a href="/3" id="off"> <button type="button">3</button> </a> <a href="/4" id="off"> <button type="button">4</button> </a> <a href="/5" id="off"> <button type="button">5</button> </a> <a href="/6" id="off"> <button type="button">6</button> </a> <a href="/7" id="off"> <button type="button">7</button> </a> <a href="/8" id="off"> <button type="button">8</button> </a> <a href="/9" id="off"> <button type="button">9</button> </a> </body> </html>
Slider code was taken from this W3C example page. Line #20 is where the actual action happens, giving our app realtime responsiveness - as soon as the user changes the slider's position, an HTTP GET is immediately sent out so that Flask can act on it and send commands to the device. Actually, there is too many requests to process sometimes and the BLE link gets congested - I'll work on this problem before the final release. The slider code is also responsible for the plain text readout of the brightness value below the slider, which is extremely useful for debugging and also informative for the end user. Below the slider script there are the LED pattern buttons, created using plain HTML.
Video proof
Up next
Having achieved this milestone and completed the BLE part of the script, I will now focus on adding pattern-editing features to the web interface, putting some proper style on it, and creating multi-device functionality. OpenCV development is shelved at the moment as I encountered plenty of obstacles and I want to focus on posting updates more often.