server.py
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 threading 32 import time 33 34 import board 35 import busio 36 import flask 37 38 import adafruit_bno055 39 40 i2c = busio.I2C(board.SCL, board.SDA) 41 42 # Create the BNO sensor connection. 43 bno = adafruit_bno055.BNO055_I2C(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.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 101 def bno_sse(): 102 """Function to handle sending BNO055 sensor data to the client web browser 103 using HTML5 server sent events (aka server push). This is a generator function 104 that flask will run in a thread and call to get new data that is pushed to 105 the client web page. 106 """ 107 # Loop forever waiting for a new BNO055 sensor reading and sending it to 108 # the client. Since this is a generator function the yield statement is 109 # used to return a new result. 110 while True: 111 # Capture the bno_changed condition lock and then wait for it to notify 112 # a new reading is available. 113 with bno_changed: 114 bno_changed.wait() 115 # A new reading is available! Grab the reading value and then give 116 # up the lock. 117 heading, roll, pitch = bno_data["euler"] 118 temp = bno_data["temp"] 119 x, y, z, w = bno_data["quaternion"] 120 sys, gyro, accel, mag = bno_data["calibration"] 121 # Send the data to the connected client in HTML5 server sent event format. 122 data = { 123 "heading": heading, 124 "roll": roll, 125 "pitch": pitch, 126 "temp": temp, 127 "quatX": x, 128 "quatY": y, 129 "quatZ": z, 130 "quatW": w, 131 "calSys": sys, 132 "calGyro": gyro, 133 "calAccel": accel, 134 "calMag": mag, 135 } 136 yield "data: {0}\n\n".format(json.dumps(data)) 137 138 139 @app.before_first_request 140 def start_bno_thread(): 141 # Start the BNO thread right before the first request is served. This is 142 # necessary because in debug mode flask will start multiple main threads so 143 # this is the only spot to put code that can only run once after starting. 144 # See this SO question for more context: 145 # http://stackoverflow.com/questions/24617795/starting-thread-while-running-flask-with-debug 146 global bno_thread # pylint: disable=global-statement 147 # Kick off BNO055 reading thread. 148 bno_thread = threading.Thread(target=read_bno) 149 bno_thread.daemon = True # Don't let the BNO reading thread block exiting. 150 bno_thread.start() 151 152 153 @app.route("/bno") 154 def bno_path(): 155 # Return SSE response and call bno_sse function to stream sensor data to 156 # the webpage. 157 return flask.Response(bno_sse(), mimetype="text/event-stream") 158 159 160 @app.route("/save_calibration", methods=["POST"]) 161 def save_calibration(): 162 # Save calibration data to disk. 163 # 164 # TODO: implement this 165 # 166 return "OK" 167 168 169 @app.route("/load_calibration", methods=["POST"]) 170 def load_calibration(): 171 # Load calibration from disk. 172 # 173 # TODO: implement this 174 # 175 return "OK" 176 177 178 @app.route("/") 179 def root(): 180 return flask.render_template("index.html") 181 182 183 if __name__ == "__main__": 184 # Create a server listening for external connections on the default 185 # port 5000. Enable debug mode for better error messages and live 186 # reloading of the server on changes. Also make the server threaded 187 # so multiple connections can be processed at once (very important 188 # for using server sent events). 189 app.run(host="0.0.0.0", debug=True, threaded=True)