|
| 1 | +# Adafruit BNO055 WebGL Example |
| 2 | +# |
| 3 | +# Requires the flask web framework to be installed. See http://flask.pocoo.org/ |
| 4 | +# for installation instructions, however on a Linux machine like the Raspberry |
| 5 | +# Pi or BeagleBone black you can likely install it by running: |
| 6 | +# sudo apt-get update |
| 7 | +# sudo apt-get install python3-flask |
| 8 | +# |
| 9 | +# Copyright (c) 2015 Adafruit Industries |
| 10 | +# Author: Tony DiCola |
| 11 | +# 2019 update: Carter Nelson |
| 12 | +# |
| 13 | +# Permission is hereby granted, free of charge, to any person obtaining a copy |
| 14 | +# of this software and associated documentation files (the "Software"), to deal |
| 15 | +# in the Software without restriction, including without limitation the rights |
| 16 | +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 17 | +# copies of the Software, and to permit persons to whom the Software is |
| 18 | +# furnished to do so, subject to the following conditions: |
| 19 | +# |
| 20 | +# The above copyright notice and this permission notice shall be included in |
| 21 | +# all copies or substantial portions of the Software. |
| 22 | +# |
| 23 | +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 24 | +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 25 | +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 26 | +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 27 | +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 28 | +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 29 | +# THE SOFTWARE. |
| 30 | +import json |
| 31 | +import logging |
| 32 | +import threading |
| 33 | +import time |
| 34 | + |
| 35 | +from flask import * |
| 36 | + |
| 37 | +import board |
| 38 | +import busio |
| 39 | +import adafruit_bno055 |
| 40 | +i2c = busio.I2C(board.SCL, board.SDA) |
| 41 | + |
| 42 | +# Create the BNO sensor connection. |
| 43 | +bno = adafruit_bno055.BNO055(i2c) |
| 44 | + |
| 45 | +# Application configuration below. You probably don't need to change these values. |
| 46 | + |
| 47 | +# How often to update the BNO sensor data (in hertz). |
| 48 | +BNO_UPDATE_FREQUENCY_HZ = 10 |
| 49 | + |
| 50 | +# Name of the file to store calibration data when the save/load calibration |
| 51 | +# button is pressed. Calibration data is stored in JSON format. |
| 52 | +CALIBRATION_FILE = 'calibration.json' |
| 53 | + |
| 54 | +# BNO sensor axes remap values. These are the parameters to the BNO.set_axis_remap |
| 55 | +# function. Don't change these without consulting section 3.4 of the datasheet. |
| 56 | +# The default axes mapping below assumes the Adafruit BNO055 breakout is flat on |
| 57 | +# a table with the row of SDA, SCL, GND, VIN, etc pins facing away from you. |
| 58 | +#BNO_AXIS_REMAP = { 'x': BNO055.AXIS_REMAP_X, |
| 59 | +# 'y': BNO055.AXIS_REMAP_Z, |
| 60 | +# 'z': BNO055.AXIS_REMAP_Y, |
| 61 | +# 'x_sign': BNO055.AXIS_REMAP_POSITIVE, |
| 62 | +# 'y_sign': BNO055.AXIS_REMAP_POSITIVE, |
| 63 | +# 'z_sign': BNO055.AXIS_REMAP_NEGATIVE } |
| 64 | + |
| 65 | + |
| 66 | +# Create flask application. |
| 67 | +app = Flask(__name__) |
| 68 | + |
| 69 | +# Global state to keep track of the latest readings from the BNO055 sensor. |
| 70 | +# This will be accessed from multiple threads so care needs to be taken to |
| 71 | +# protect access with a lock (or else inconsistent/partial results might be read). |
| 72 | +# A condition object is used both as a lock for safe access across threads, and |
| 73 | +# to notify threads that the BNO state has changed. |
| 74 | +bno_data = {} |
| 75 | +bno_changed = threading.Condition() |
| 76 | + |
| 77 | +# Background thread to read BNO sensor data. Will be created right before |
| 78 | +# the first request is served (see start_bno_thread below). |
| 79 | +bno_thread = None |
| 80 | + |
| 81 | + |
| 82 | +def read_bno(): |
| 83 | + """Function to read the BNO sensor and update the bno_data object with the |
| 84 | + latest BNO orientation, etc. state. Must be run in its own thread because |
| 85 | + it will never return! |
| 86 | + """ |
| 87 | + while True: |
| 88 | + # Capture the lock on the bno_changed condition so the bno_data shared |
| 89 | + # state can be updated. |
| 90 | + with bno_changed: |
| 91 | + bno_data['euler'] = bno.euler |
| 92 | + bno_data['temp'] = bno.temperature |
| 93 | + bno_data['quaternion'] = bno.quaternion |
| 94 | + bno_data['calibration'] = bno.calibration_status |
| 95 | + # Notify any waiting threads that the BNO state has been updated. |
| 96 | + bno_changed.notifyAll() |
| 97 | + # Sleep until the next reading. |
| 98 | + time.sleep(1.0/BNO_UPDATE_FREQUENCY_HZ) |
| 99 | + |
| 100 | +def bno_sse(): |
| 101 | + """Function to handle sending BNO055 sensor data to the client web browser |
| 102 | + using HTML5 server sent events (aka server push). This is a generator function |
| 103 | + that flask will run in a thread and call to get new data that is pushed to |
| 104 | + the client web page. |
| 105 | + """ |
| 106 | + # Loop forever waiting for a new BNO055 sensor reading and sending it to |
| 107 | + # the client. Since this is a generator function the yield statement is |
| 108 | + # used to return a new result. |
| 109 | + while True: |
| 110 | + # Capture the bno_changed condition lock and then wait for it to notify |
| 111 | + # a new reading is available. |
| 112 | + with bno_changed: |
| 113 | + bno_changed.wait() |
| 114 | + # A new reading is available! Grab the reading value and then give |
| 115 | + # up the lock. |
| 116 | + heading, roll, pitch = bno_data['euler'] |
| 117 | + temp = bno_data['temp'] |
| 118 | + x, y, z, w = bno_data['quaternion'] |
| 119 | + sys, gyro, accel, mag = bno_data['calibration'] |
| 120 | + # Send the data to the connected client in HTML5 server sent event format. |
| 121 | + data = {'heading': heading, 'roll': roll, 'pitch': pitch, 'temp': temp, |
| 122 | + 'quatX': x, 'quatY': y, 'quatZ': z, 'quatW': w, |
| 123 | + 'calSys': sys, 'calGyro': gyro, 'calAccel': accel, 'calMag': mag } |
| 124 | + yield 'data: {0}\n\n'.format(json.dumps(data)) |
| 125 | + |
| 126 | + |
| 127 | +@app.before_first_request |
| 128 | +def start_bno_thread(): |
| 129 | + # Start the BNO thread right before the first request is served. This is |
| 130 | + # necessary because in debug mode flask will start multiple main threads so |
| 131 | + # this is the only spot to put code that can only run once after starting. |
| 132 | + # See this SO question for more context: |
| 133 | + # http://stackoverflow.com/questions/24617795/starting-thread-while-running-flask-with-debug |
| 134 | + global bno_thread |
| 135 | + # Kick off BNO055 reading thread. |
| 136 | + bno_thread = threading.Thread(target=read_bno) |
| 137 | + bno_thread.daemon = True # Don't let the BNO reading thread block exiting. |
| 138 | + bno_thread.start() |
| 139 | + |
| 140 | +@app.route('/bno') |
| 141 | +def bno_path(): |
| 142 | + # Return SSE response and call bno_sse function to stream sensor data to |
| 143 | + # the webpage. |
| 144 | + return Response(bno_sse(), mimetype='text/event-stream') |
| 145 | + |
| 146 | +@app.route('/save_calibration', methods=['POST']) |
| 147 | +def save_calibration(): |
| 148 | + # Save calibration data to disk. |
| 149 | + # |
| 150 | + # TODO: implement this |
| 151 | + # |
| 152 | + return 'OK' |
| 153 | + |
| 154 | +@app.route('/load_calibration', methods=['POST']) |
| 155 | +def load_calibration(): |
| 156 | + # Load calibration from disk. |
| 157 | + # |
| 158 | + # TODO: implement this |
| 159 | + # |
| 160 | + return 'OK' |
| 161 | + |
| 162 | +@app.route('/') |
| 163 | +def root(): |
| 164 | + return render_template('index.html') |
| 165 | + |
| 166 | + |
| 167 | +if __name__ == '__main__': |
| 168 | + # Create a server listening for external connections on the default |
| 169 | + # port 5000. Enable debug mode for better error messages and live |
| 170 | + # reloading of the server on changes. Also make the server threaded |
| 171 | + # so multiple connections can be processed at once (very important |
| 172 | + # for using server sent events). |
| 173 | + app.run(host='0.0.0.0', debug=True, threaded=True) |
0 commit comments