Adafruit CLUE Sensor Plotter in CircuitPython Instruction manual

CLUE Sensor Plotter in CircuitPython
Created by Kevin Walters
Last updated on 2021-02-08 06:58:19 PM EST
2 4
4
6 7
7
9
9
11
11 12 14
17
17
19
19
19
20
20 20
45
47 47
49 52
52 53 53 53 53 54 54
56
56 57 58 58
59
59 62
63
63
65
66
Guide Contents
Guide Contents Overview
Parts
Design Main Program
User Interface with Two Buttons
PlotSource Class
Data Sources for Plotting
Plotter Class
Accessibility Auto-scaling Algorithm Efficient Use of displayio
Scrolling Resolution and Scaling
CircuitPython on CLUE
Set up CircuitPython Quick Start!
CircuitPython
Libraries
Libraries for Sensor Plotter
Development Testing
Sensor Plotter
Example Video Code
code.py plot_source.py plotter.py
Code Discussion
IlluminatedColorPlotSource class
Units and Interfaces Setting Properties and Pass by ...
Signal/Colour Generator Sensors
Temperature Pressure Humidity Volume Colour Sensitivity Proximity Magnetometer
Testing
TemperaturePlotSource class Code Review Unit Testing Mocking
Bug 1
Finding the Bug Fixing the Bug
Bug 2
More Than One Bug
Number Representation
Python 3
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 2 of 77
66
67 67 67
69
69
71 71 72
74
74 74 74
CircuitPython
Small Boards Large Boards
C/Arduino Zuse Z1 Decimal Precision
Time in CircuitPython
CircuitPython time functions
time.monotonic() time.monotonic_ns()
Making Large Numbers Readable Performance Variability Demonstration of time.monotonic() Granularity
Ten Minutes Two Days
Going Further
Ideas for Areas to Explore Related Projects Further Reading
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 3 of 77
Overview
This project demonstrates plotting the CLUE's wide variety of onboard sensors (https://adafru.it/K7b) and the analogue input pads (https://adafru.it/K7b) in CircuitPython 5 and above using the displayio
library (https://adafru.it/EGh).
A similar plotting program in C/Arduino from the Adafruit examples is shown alongside the CircuitPython one.
Note: the Design (https://adafru.it/Kbj), Testing (https://adafru.it/Kbk) and Number
Representation (https://adafru.it/Kbl) sections are rather detailed and primarily intended for students.
Parts
Your browser does not support the video tag. Adafruit CLUE - nRF52840 Express with Bluetooth LE
Do you feel like you just don't have a CLUE? Well, we can help with that - get a CLUE here at Adafruit by picking up this sensor-packed development board. We wanted to build some... $39.95 In Stock
Add to Cart
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 4 of 77
USB cable - USB A to Micro-B
This here is your standard A to micro-B USB cable, for USB 1.1 or 2.0. Perfect for connecting a PC to your Metro, Feather, Raspberry Pi or other dev-board or... $2.95 In Stock
Add to Cart
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 5 of 77
Design
The design of the CircuitPython version of the CLUE sensor plotter separates the program into three components using object-oriented programming (OOP) (https://adafru.it/K7f):
A PlotSource class with derived classes for each sensor to represent the 10 data sources. A Plotter class to take the data and draw the graph on the LCD screen. The main program which manages the interactions with the user and uses the aforementioned
classes to read the data from the sensors and plot it to the screen.
A class (https://adafru.it/K7A) brings together related code and data to create a new
type (https://adafru.it/K7B). Variables created based on the class type are referred to as objects (https://adafru.it/K7C). Procedures are called methods (https://adafru.it/IkD) when they are part of a
class. Python and C++, the basis for CircuitPython and Arduino programming respectively, both have OO features.
The following pages in this section look at a few aspects of the design.
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 6 of 77
Main Program
The flow diagram above shows a simplified view of the main program. There are multiple Sources representing the different sensors and the pads and one Plotter object. One omitted action is the
invocation of the start() and stop() methods on the source when the source is first used or changed.
The flow diagram is unusual in not having a Stop symbol. In this case the program has an intentional
infinite loop (https://adafru.it/K7D) and runs perpetually. Turning the power off is the only way to terminate
the program. This is fairly common for embedded systems (https://adafru.it/K7E). Regulatory
changes (https://adafru.it/K7F) have affected some designs in the last decade initiating and improving
power saving when idle.
The diagram doesn't show the detail of exactly how the button inputs are processed. This is described in detail in the next section.
User Interface with Two Buttons
The CLUE follows the design of the BBC micro:bit and only has two buttons for user input. The three large pads on the edge connector can be used as touch pads (https://adafru.it/K7b) but in this program they are used as analogue inputs which prevents touch pad use, at least when the analogue inputs are being plotted.
Any button can be used for more than just simple clicks. The action can be varied based on waiting for a double-click or measuring the duration of the press. The latter approach is used with the following actions:
Left (A) button:
0-2 seconds: change to the next plotting source. 2-4s: toggle between palette from the PlotSource and a default palette.
4-6s: toggle data output on/off to serial console in a format suitable for plotting in
Mu (https://adafru.it/ANO).
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 7 of 77
>6s: toggle range lock on the y axis scale to inhibit auto-scaling.
Right (B) button: change to a different plot style/mode.
Another option, not used here, would be for the two buttons to have a different action when both are pressed at the same time.
The first design timed the button press duration and then displayed the action on screen to the user. This meant the user had to mentally time the presses and could select the wrong option. A simple redesign changed this to cycle through the actions on screen as time passes. This allows the user to release the button reliably on the desired option.
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 8 of 77
PlotSource Class
PlotSource is an abstract class (https://adafru.it/K8a). This means that it is not intended for direct use but
exists as a parent for sub-classes. It is also defining the interface (https://adafru.it/K8b) for children with the
start() , stop() and data() operations (https://adafru.it/K8c) (a UML term encompassing methods).
Data Sources for Plotting
The CLUE board has many sensors and inputs which can be used as data sources for plotting. The data output from all of these sensors and analogue input(s) are ultimately represented by either one number or a list of numbers per sample read. This commonality suggests they can be represented by a single class providing the interface to the rest of the program. The per-sensor code can be implemented in a sub-class derived from a base class using inheritance (https://adafru.it/K8d). UML (https://adafru.it/K8e) diagrams depict inheritance with an unfilled, triangular arrow head.
The sub-classes are listed below with their name together with with any processing performed, the number of values returned by data() and the units for values:
1. AccelerometerPlotSource - none - 3 values - ms .
2. ColorPlotSource - discard data from clear sensor leaving red, green and blue values - 3 values ­integer.
3. GyroPlotSource - none - 3 values - degrees per second (dps).
4. HumidityPlotSource - none - 1 value - percentage.
5. IlluminatedColorPlotSource - only pass a single value from sensor selected at instantiation - 1 value ­integer.
6. MagnetometerPlotSource - none - 3 values - uT.
7. PinPlotSource - converted to voltage - 1 value per pin, maximum of 3 - V.
8. PressurePlotSource - optionally converted to inches of mercury (https://adafru.it/K8f) - 1 value - hPa or
inHg.
-2
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 9 of 77
9. ProximityPlotSource - none - 1 value - integer.
10. TemperaturePlotSource - optionally converted to Fahrenheit - 1 value - degrees Celsius or Fahrenheit.
11. VolumePlotSource - conversion to decibel (https://adafru.it/K8A) scale - 1 value - dB.
The colour sensor here is actually represented by
two
classes. There are different ways to design the
illuminated vs the non-illuminated colour plot source.
1. The variation in behaviour can be achieved with conditional logic inside a
single
class. The selection
of the desired behaviour can be achieved in many ways:
1. creation of two objects with the variety passed as a parameter as the object is constructed;
2. a single object with additional methods extending the interface;
3. a single object with an optional parameter to the existing data() method.
2. Increased use of inheritance with two or three classes, e.g. IlluminatedColorPlotSource could be derived from ColorPlotSource and optionally a NonIlluminatedColorPlotSource could be created.
3. Two classes derived from PlotSource .
The final option was the one chosen with the IlluminatedColorPlotSource using the start() and stop() methods to turn on the CLUE's pair of bright, white, forward-facing LEDs for the duration of the illuminated
plotting. The sensor's gain is also set a little higher.
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 10 of 77
Plotter Class
The Plotter class takes the data and plots it on the screen with optional output to the serial console for plotting by Mu or general data collection.
The full class diagram would reveal a lot of attributes and operations suggesting it's a bulky, complex class. Sometimes this is indicative that the design could benefit from some further
decomposition (https://adafru.it/K8B) and refinement.
The expected usage after instantiation of the object is:
1. y_range() - set the initial range on the plot.
2. y_min_range - optional but limits the degree of zoom in.
3. y_full_range() - set the absolute range for data from sensor.
4. channels - set number of elements of data per sample.
5. channel_colidx - set colours to use in for of sequence of palette numbers.
6. display_on() - initialize display - once only.
7. data_add() - use repeatedly to draw new points or lines based on the settings - this will also eventually scroll or wrap.
8. Goto 7.
Accessibility
From The Role of Accessibility in a Universal Web (https://adafru.it/K8C):
"Universal design" is the process of creating products that are usable by people with the widest possible range of abilities, operating within the widest possible range of situations; whereas "accessibility" primarily refers to design
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 11 of 77
for people with disabilities. While the focus of accessibility is disabilities, research and development in accessibility brings benefits to everyone, particularly users with situational limitations, including device limitations and environmental limitations.
Small devices like the CLUE with its 1.3" (33mm) screen are, by their nature, limited for the visual aspects of accessibility but we can still consider:
contrast (https://adafru.it/K8D) for foreground/background colours of text, thoughtful colour selection (https://adafru.it/K8E).
It's common and very tempting to represent (x,y,z) values using the three primary colours: red, green and blue. Unfortunately this combination clashes with common forms of
colour blindess (https://adafru.it/K8F).
The RGB palette is used in the sensor plotter for many of the sensors but the user can also
override
this
with a "default palette". This is loosely based on common digital storage oscilloscopes:
1. Yellow ( 0xffff00 ),
2. Cyan ( 0x00ffff ),
3. "Almost Pink" ( 0xff0080 ).
These can be tested on a colour simulator but it's best to test with some real people.
Auto-scaling Algorithm
The PlotSource object has methods to provide the absolute minimum and maximum values for the data from that source. A typical feature would be to set the y axis scale based on the observed data values to
get a more detailed view.
The current algorithm is shown below in two flow charts, the second is a sub routine used in the first one.
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 12 of 77
The data_mins and data_maxs are lists of the recent minimum and maximums for approximate 1 second periods retained for a configurable number of seconds.
The change_range() sub routine (implemented as a method) implements the optional y_min_range feature. This prevents zooming in excessively showing uninteresting, random noise from some sensors.
The zoom out will always occur if the data is off the current range, i.e. off screen. The zoom in is a little more cautious.
There's one extra feature to reduce the frequency of zooming in not shown in the diagrams. A timestamp is recorded whenever a zoom in takes place and this is used to prevent zooming in again until N seconds
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 13 of 77
has passed.
Based on acceptance testing, the zooming still occurs when it looks unnecessary. This algorithm needs further improvement perhaps using a hysterisis (https://adafru.it/K9a)-based approach.
Efficient Use of displayio
The displayio (https://adafru.it/EFr) library for CircuitPython (or Adafruit_GFX (https://adafru.it/K9b) for Arduino) provides a single library which can be used with a variety of different size LED, LCD and ePaper screens. This abstraction (https://adafru.it/K9c) is very useful and removes the need to directly program the CLUE's ST7789 LCD display (https://adafru.it/K9d). The only details the programmer needs to know for low update rates are:
the resolution of the screen (240x240) and whether it has enough colour depth to render the desired colours sufficiently accurately (16bit).
These small LCD screens are not designed for high frame rates. If the screen needs to be updated frequently then the performance needs to be explored. The displayio library is implemented in
compiled (https://adafru.it/K9e) code to improve the performance but it needs to be thoughtfully used from
CircuitPython since this is slower due to being executed on an interpreter (https://adafru.it/K9f).
Scrolling
A plotter needs to do something when the points/lines reach the edge of the screen. It can either
wrap like an oscilloscope or scroll the existing data to the left.
The Bitmap (https://adafru.it/EFs) class does not currently provide clear, fill or scroll methods. Some early
exploratory programming (https://adafru.it/K9A)revealed that slice assignment isn't supported and
clearing a
large
Bitmap pixel by pixel is a
very slow
process. Some simple code to time clearing a Bitmap
is shown below.
# Quick benchmark of clearing a displayio Bitmap using for loops
# See https://github.com/adafruit/circuitpython/issues/2688
import time import board, displayio
WIDTH = 201 HEIGHT = 200
display = board.DISPLAY
# eight colours is 3 bits per pixel when packed bitmap = displayio.Bitmap(WIDTH, HEIGHT, 8)
palette = displayio.Palette(8) palette[0] = 0x000000 palette[1] = 0xff0000 palette[2] = 0x00ff00 palette[3] = 0x0000ff
tile_grid = displayio.TileGrid(bitmap, pixel_shader=palette) group = displayio.Group() group.append(tile_grid)
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 14 of 77
display.auto_refresh=False display.show(group)
def refresh_screen(disp): while True: refreshed = False try: refreshed = disp.refresh(minimum_frames_per_second=0) except Exception: pass if refreshed: break
def fillscreen1(bmp, col_idx): for x in range(WIDTH): for y in range(HEIGHT): bmp[x, y] = col_idx
def fillscreen2(bmp, col_idx): for y in range(HEIGHT): for x in range(WIDTH): bmp[x, y] = col_idx
def fillscreen3(bmp, col_idx): for idx in range(WIDTH * HEIGHT): bmp[idx] = col_idx
# "Big" Python has a timeit library but not present on CircuitPython # so it's time for some for loops for func in (fillscreen1, fillscreen2, fillscreen3): for _ in range(2): for colour_idx in (0, 0, 0, 1, 2, 3): t1 = time.monotonic_ns() func(bitmap, colour_idx) refresh_screen(display) t2 = time.monotonic_ns() func_name = str(func).split(" ")[1] print(func_name, colour_idx, "{:.3f}s".format((t2 - t1) / 1e9)) time.sleep(0.5)
This simple benchmark (https://adafru.it/K9B) could be improved as it both updates the Bitmap data and performs a single refresh of the screen. It would be informative to observe the performance of the two actions individually.
The output is shown below. fillscreen1 takes 1.25 seconds, fillscreen3 is faster at 0.75 seconds, fillscreen2 isn't shown as it was same as fillscreen1 .
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 15 of 77
fillscreen1 0 1.252s fillscreen1 0 1.250s fillscreen1 0 1.250s fillscreen1 1 1.251s fillscreen1 2 1.251s fillscreen1 3 1.249s fillscreen1 0 1.250s fillscreen1 0 1.251s fillscreen1 0 1.251s fillscreen1 1 1.249s fillscreen1 2 1.250s fillscreen1 3 1.251s
fillscreen3 0 0.753s fillscreen3 0 0.754s fillscreen3 0 0.755s fillscreen3 1 0.755s fillscreen3 2 0.753s fillscreen3 3 0.754s fillscreen3 0 0.755s fillscreen3 0 0.753s fillscreen3 0 0.754s fillscreen3 1 0.754s fillscreen3 2 0.755s fillscreen3 3 0.753s
These numbers would mean the screen would barely be able to update once per second. It's also slower if two bitmaps are overlaid which is a tempting solution to providing a static background.
This benchmarking lead to a change in design to use a more complex "un-drawing" technique. This reduces the number of pixel changes dramatically decreasing the time to clear the screen. The downside is the added complexity in storing the data and in the procedure to draw over the existing plot with background colour pixels.
Further testing revealed this undraw was still fairly slow. This lead to another iteration of the design. Reducing the frequency of scrolling was required and this could be achieved with a "jump" scroll - scrolling the data by more than one pixel at a time.
Resolution and Scaling
The final implementation of the Plotter class uses a Bitmap with a resolution of 192x201 pixels for the plot. The width was reduced to allow an extra character on the y axis tick labels.
Group has a feature to scale objects by an integer amount. This is implemented in C and is likely to be
efficient. Another potential option to speed up the code would be to lower the resolution and use scale=2 to display it - a trade-off between resolution and performance. This could be implemented as a user-
selected option.
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 16 of 77
CircuitPython on CLUE
CircuitPython (https://adafru.it/tB7) is a derivative of MicroPython (https://adafru.it/BeZ) designed to simplify
experimentation and education on low-cost microcontrollers. It makes it easier than ever to get prototyping by requiring no upfront desktop software downloads. Simply copy and edit files on the CIRCUITPY flash drive to iterate.
The following instructions will show you how to install CircuitPython. If you've already installed CircuitPython but are looking to update it or reinstall it, the same steps work for that as well!
Set up CircuitPython Quick Start!
Follow this quick step-by-step for super-fast Python power :)
https://adafru.it/IHF
Click the link above to download the latest version of
CircuitPython for the CLUE.
Download and save it to your desktop (or wherever is handy).
Plug your CLUE into your computer using a known-good USB cable.
A lot of people end up using charge-only USB cables and it is
very frustrating! So make sure you have a USB cable you
know is good for data sync.
Double-click the Reset button on the top (magenta arrow) on your board, and you will see the NeoPixel RGB LED (green arrow) turn green. If it turns red, check the USB cable, try another USB port, etc. Note: The little red LED next to the USB connector will pulse red. That's ok!
If double-clicking doesn't work the first time, try again. Sometimes it can take a few tries to get the rhythm right!
https://adafru.it/IHF
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 17 of 77
You will see a new disk drive appear called CLUEBOOT.
Drag the adafruit-circuitpython-clue-etc.uf2 file to
CLUEBOOT.
The LED will flash. Then, the CLUEBOOT drive will disappear and a new disk drive called CIRCUITPY will appear.
If this is the first time you're installing CircuitPython or you're doing a completely fresh install after erasing the filesystem, you will have two files - boot_out.txt, and code.py, and one folder - lib on your CIRCUITPY drive.
If CircuitPython was already installed, the files present before reloading CircuitPython should still be present on your CIRCUITPY drive. Loading CircuitPython will not create new files if there was already a CircuitPython filesystem present.
That's it, you're done! :)
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 18 of 77
CircuitPython
Libraries
Once you've gotten CircuitPython onto your Circuit Playground Bluefruit boards, it's time to add some libraries. You can follow this guide page (https://adafru.it/GdM) for the basics of downloading and transferring libraries to the board.
https://adafru.it/ENC
Libraries for Sensor Plotter
From the library bundle you downloaded in that guide page, transfer the following libraries onto the CLUE board's /lib directory:
adafruit_register
adafruit_apds9960
adafruit_bus_device
adafruit_display_notification
adafruit_display_shapes
adafruit_display_text
adafruit_bmp280.mpy
adafruit_clue.mpy
adafruit_lis3mdl.mpy
adafruit_sht31d.mpy
adafruit_slideshow.mpy
neopixel.mpy
adafruit_lsm6ds.mpy
Take care with selecting the correct library, there are many that have similar names.
Development Testing
During development, the application was tested on a CLUE using CircuitPython 5.0.0 with libraries from the adafruit-circuitpython-bundle-5.x-mpy-20200327.zip bundle. It should work on subsequent versions,
the latest version is recommended (https://adafru.it/FNK).
https://adafru.it/ENC
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 19 of 77
Sensor Plotter
Download the three CircuitPython files below renaming the clue-plotter.py to code.py by clicking on the links and then using Save as... / Save link as... in the browser. The files are hosted on Adafruit's GitHub
repo for this project (https://adafru.it/Kaw).
https://adafru.it/KaW
https://adafru.it/KaX
https://adafru.it/KaY
Plug your CLUE board into your computer via a known-good USB data cable. A flash drive named CIRCUITPY should appear in your file explorer/finder program. Copy the three files t o the CIRCUITPY drive ensuring the clue-plotter.py is renamed to code.py. (https://adafru.it/EL3)
Example Video
The video below demonstrates all of the sensors and at the end shows the analogue inputs on the three large pads marked #0, #1 and #2. A Feather M4 Express provides colour and two signals for #0 and #1 for the demonstration.
The following sections show the lengthy code in all three files. There is also a Code
Discussion (https://adafru.it/Kbm) section at the bottom of the page.
Code
code.py
The file must be renamed to code.py to run on the CLUE. If you select Download: Project Zip, you will get all three files in one zip archive file.
https://adafru.it/KaW
https://adafru.it/KaX
https://adafru.it/KaY
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 20 of 77
# clue-plotter v1.14 # Sensor and input plotter for Adafruit CLUE in CircuitPython # This plots the sensors and three of the analogue inputs on # the LCD display either with scrolling or wrap mode which # approximates a slow timebase oscilloscope, left button selects # next source or with long press changes palette or longer press # turns on output for Mu plotting, right button changes plot style
# Tested with an Adafruit CLUE (Alpha) and CircuitPython and 5.0.0
# copy this file to CLUE board as code.py # needs companion plot_sensor.py and plotter.py files
# MIT License
# Copyright (c) 2020 Kevin J. Walters
# Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE.
import time
import gc import board
from plotter import Plotter from plot_source import PlotSource, TemperaturePlotSource, PressurePlotSource, \ HumidityPlotSource, ColorPlotSource, ProximityPlotSource, \ IlluminatedColorPlotSource, VolumePlotSource, \ AccelerometerPlotSource, GyroPlotSource, \ MagnetometerPlotSource, PinPlotSource from adafruit_clue import clue
debug = 1
# A list of all the data sources for plotting sources = [TemperaturePlotSource(clue, mode="Celsius"), TemperaturePlotSource(clue, mode="Fahrenheit"), PressurePlotSource(clue, mode="Metric"), PressurePlotSource(clue, mode="Imperial"), HumidityPlotSource(clue), ColorPlotSource(clue), ProximityPlotSource(clue), IlluminatedColorPlotSource(clue, mode="Red"), IlluminatedColorPlotSource(clue, mode="Green"), IlluminatedColorPlotSource(clue, mode="Blue"),
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 21 of 77
IlluminatedColorPlotSource(clue, mode="Blue"), IlluminatedColorPlotSource(clue, mode="Clear"), VolumePlotSource(clue), AccelerometerPlotSource(clue), GyroPlotSource(clue), MagnetometerPlotSource(clue), PinPlotSource([board.P0, board.P1, board.P2]) ] # The first source to select when plotting starts current_source_idx = 0
# The various plotting styles - scroll is currently a jump scroll stylemodes = (("lines", "scroll"), # draws lines between points ("lines", "wrap"), ("dots", "scroll"), # just points - slightly quicker ("dots", "wrap") ) current_sm_idx = 0
def d_print(level, *args, **kwargs): """A simple conditional print for debugging based on global debug level.""" if not isinstance(level, int): print(level, *args, **kwargs) elif debug >= level: print(*args, **kwargs)
def select_colors(plttr, src, def_palette): """Choose the colours based on the particular PlotSource or forcing use of default palette.""" # otherwise use defaults channel_colidx = [] palette = plttr.get_colors() colors = PlotSource.DEFAULT_COLORS if def_palette else src.colors() for col in colors: try: channel_colidx.append(palette.index(col)) except ValueError: channel_colidx.append(PlotSource.DEFAULT_COLORS.index(col)) return channel_colidx
def ready_plot_source(plttr, srcs, def_palette, index=0): """Select the plot source by index from srcs list and then setup the plot parameters by retrieving meta-data from the PlotSource object.""" src = srcs[index] # Put the description of the source on screen at the top source_name = str(src) d_print(1, "Selecting source:", source_name) plttr.clear_all() plttr.title = source_name plttr.y_axis_lab = src.units() # The range on graph will start at this value plttr.y_range = (src.initial_min(), src.initial_max()) plttr.y_min_range = src.range_min() # Sensor/data source is expected to produce data between these values plttr.y_full_range = (src.min(), src.max()) channels_from_src = src.values() plttr.channels = channels_from_src # Can be between 1 and 3 plttr.channel_colidx = select_colors(plttr, src, def_palette)
src.start() return (src, channels_from_src)
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 22 of 77
def wait_release(func, menu): """Calls func repeatedly waiting for it to return a false value and goes through menu list as time passes.
The menu is a list of menu entries where each entry is a two element list of time passed in seconds and text to display for that period. The entries must be in ascending time order."""
start_t_ns = time.monotonic_ns() menu_option = None selected = False
for menu_option, menu_entry in enumerate(menu): menu_time_ns = start_t_ns + int(menu_entry[0] * 1e9) menu_text = menu_entry[1] if menu_text: plotter.info = menu_text while time.monotonic_ns() < menu_time_ns: if not func(): selected = True break if menu_text: plotter.info = "" if selected: break
return (menu_option, (time.monotonic_ns() - start_t_ns) * 1e-9)
def popup_text(plttr, text, duration=1.0): """Place some text on the screen using info property of Plotter object for duration seconds.""" plttr.info = text time.sleep(duration) plttr.info = None
mu_plotter_output = False range_lock = False
initial_title = "CLUE Plotter" # displayio has some static limits on text - pre-calculate the maximum # length of all of the different PlotSource objects max_title_len = max(len(initial_title), max([len(str(so)) for so in sources])) plotter = Plotter(board.DISPLAY, style=stylemodes[current_sm_idx][0], mode=stylemodes[current_sm_idx][1], title=initial_title, max_title_len=max_title_len, mu_output=mu_plotter_output, debug=debug)
# If set to true this forces use of colour blindness friendly colours use_def_pal = False
clue.pixel[0] = clue.BLACK # turn off the NeoPixel on the back of CLUE board
plotter.display_on() # Using left and right here in case the CLUE is cased hiding A/B labels popup_text(plotter,
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 23 of 77
"\n".join(["Button Guide", "Left: next source", " 2secs: palette", " 4s: Mu plot", " 6s: range lock", "Right: style change"]), duration=10)
count = 0
while True: # Set the source and start items (source, channels) = ready_plot_source(plotter, sources, use_def_pal, current_source_idx)
while True: # Read data from sensor or voltage from pad all_data = source.data()
# Check for left (A) and right (B) buttons if clue.button_a: # Wait for button release with time-based menu opt, _ = wait_release(lambda: clue.button_a, [(2, "Next\nsource"), (4, ("Source" if use_def_pal else "Default") + "\npalette"), (6, "Mu output " + ("off" if mu_plotter_output else "on")), (8, "Range lock\n" + ("off" if range_lock else "on")) ]) if opt == 0: # change plot source current_source_idx = (current_source_idx + 1) % len(sources) break # to leave inner while and select the new source
elif opt == 1: # toggle palette use_def_pal = not use_def_pal plotter.channel_colidx = select_colors(plotter, source, use_def_pal)
elif opt == 2: # toggle Mu output mu_plotter_output = not mu_plotter_output plotter.mu_output = mu_plotter_output
else: # toggle range lock range_lock = not range_lock plotter.y_range_lock = range_lock
if clue.button_b: # change plot style and mode current_sm_idx = (current_sm_idx + 1) % len(stylemodes) (new_style, new_mode) = stylemodes[current_sm_idx] wait_release(lambda: clue.button_b, [(2, new_style + "\n" + new_mode)]) d_print(1, "Graph change", new_style, new_mode) plotter.change_stylemode(new_style, new_mode)
# Display it if channels == 1: plotter.data_add((all_data,)) else: plotter.data_add(all_data)
© Adafruit Industries https://learn.adafruit.com/clue-sensor-plotter-circuitpython Page 24 of 77
Loading...
+ 53 hidden pages