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)