# CLUE Sensor Plotter in CircuitPython

## Overview

![](https://cdn-learn.adafruit.com/assets/assets/000/089/671/medium800/sensors_clue-sensor-plotter-pads-logo-2400x1800.jpg?1584923457 Adafruit CLUE running sensor plotter showing analogue voltages on the three pads.)

This project demonstrates plotting the CLUE's wide variety of onboard [sensors](https://learn.adafruit.com/adafruit-clue/pinouts#sensors-2-9 "Adafruit Learn: Introducing Adafruit CLUE: Pinouts") and the [analogue input pads](https://learn.adafruit.com/adafruit-clue/pinouts "Adafruit Learn: Introducing Adafruit CLUE: Pinouts") in CircuitPython 5 and above using the [displayio library](https://learn.adafruit.com/circuitpython-display-support-using-displayio "Adafruit Learn: CircuitPython Display Support Using displayio").

A similar plotting program in C/Arduino from the Adafruit examples is shown alongside the CircuitPython one.

Note: the [Design](https://learn.adafruit.com/clue-sensor-plotter-circuitpython/design "Adafruit Learn: CLUE Sensor Plotter: Design"), [Testing](https://learn.adafruit.com/clue-sensor-plotter-circuitpython/testing "Adafruit Learn: CLUE Sensor Plotter: Testing") and [Number Representation](https://learn.adafruit.com/clue-sensor-plotter-circuitpython/number-representation "Adafruit Learn: CLUE Sensor Plotter: Number Representation") sections are rather detailed and primarily intended for students.

## Parts
Featured
### Adafruit CLUE - nRF52840 Express with Bluetooth® LE

[Adafruit CLUE - nRF52840 Express with Bluetooth® LE](https://www.adafruit.com/product/4500)
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 projects that have a small screen and a lot of sensors. To make it compatible with existing projects, we made...

In Stock
[Buy Now](https://www.adafruit.com/product/4500)
[Related Guides to the Product](https://learn.adafruit.com/products/4500/guides)
![Animated GIF showing CLUE board  displaying data from the many on-board sensors.](https://cdn-shop.adafruit.com/product-videos/640x480/4500-04.jpg)

Featured
### USB cable - USB A to Micro-B

[USB cable - USB A to Micro-B](https://www.adafruit.com/product/592)
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 microcontroller

Approximately 3 feet / 1 meter long

In Stock
[Buy Now](https://www.adafruit.com/product/592)
[Related Guides to the Product](https://learn.adafruit.com/products/592/guides)
![USB cable - USB A to Micro-B - 3 foot long](https://cdn-shop.adafruit.com/640x480/592-01.jpg)

# CLUE Sensor Plotter in CircuitPython

## Design

![](https://cdn-learn.adafruit.com/assets/assets/000/089/802/medium800thumb/sensors_royce-waterfall-myth-zoom-programdesign-video-nolooplongframe-1.jpg?1585087082 Iteration in the software development process from Managing the Development of Large Software Systems by Dr Winston W. Royce (1970).)

The design of the CircuitPython version of the CLUE sensor plotter separates the program into three components using [object-oriented programming (OOP)](https://en.wikipedia.org/wiki/Object-oriented_programming "Wikipedia: object-oriented programming"):

- 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://en.wikipedia.org/wiki/Class_(computer_programming) "Wikipedia: class (computer programming)") brings together related code and data to create a new [type](https://en.wikipedia.org/wiki/Data_type "Wikipedia: data type"). Variables created based on the class type are referred to as [objects](https://en.wikipedia.org/wiki/Object_(computer_science) "Wikipedia: object (computer science)"). Procedures are called [methods](https://en.wikipedia.org/wiki/Method_(computer_programming) "Wikipedia: method") 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.

# CLUE Sensor Plotter in CircuitPython

## Main Program

![](https://cdn-learn.adafruit.com/assets/assets/000/089/840/medium800/sensors_clue-sensor-plotter-main-flowchart-v3.png?1585153823 Simplified flowchart for sensor plotter program using Plotter and PlotSource objects.)

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://en.wikipedia.org/wiki/Infinite_loop "Wikipedia: infinite loop") and runs perpetually. Turning the power off is the only way to terminate the program. This is fairly common for [embedded systems](https://en.wikipedia.org/wiki/Embedded_system "Wikipedia: embedded system"). [Regulatory changes](https://ec.europa.eu/info/energy-climate-change-environment/standards-tools-and-labels/products-labelling-rules-and-requirements/energy-label-and-ecodesign/energy-efficient-products/mode-standby-and-networked-standby_en "European Commission: Off mode, standby and networked standby") 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://learn.adafruit.com/adafruit-clue/pinouts#step-3054948 "Adafruit Learn: Introducing Adafruit CLUE: pinouts") 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://learn.adafruit.com/welcome-to-circuitpython/installing-mu-editor "Adafruit Learn:Welcome to CircuitPython: Installing Mu Editor").
  - \>6s: toggle range lock on the y axis scale to inhibit auto-scaling.

- Right&nbsp;( **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.

![](https://cdn-learn.adafruit.com/assets/assets/000/089/858/medium800thumb/sensors_clue-plotter-leftbutton-cycling-video-1.jpg?1585160200 Sensor plotter button usage - holding left button shows each action.)

# CLUE Sensor Plotter in CircuitPython

## PlotSource Class

![](https://cdn-learn.adafruit.com/assets/assets/000/089/791/medium800/sensors_clue-sensor-plotter-class-plotsource-trio-v4.png?1585064680 UML class diagram for plotting sources. The abstract class is shown with three derived classes, two for sensors on the CLUE board and one for input pad(s).)

`PlotSource` is an [abstract class](https://en.wikipedia.org/wiki/Abstract_type "Wikipedia: abstract type"). 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://en.wikipedia.org/wiki/Protocol_(object-oriented_programming) "Wikipedia: Protocol (object-oriented programming)") for children with the `start()`, `stop()` and `data()` [operations](https://www.uml-diagrams.org/operation.html "UML: operation definition") (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://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming) "Wikipedia: inheritance"). [UML](https://en.wikipedia.org/wiki/Unified_Modeling_Language "Wikipedia: Unified Modeling Language") 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<sup>-2</sup>**.
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://en.wikipedia.org/wiki/Inch_of_mercury "Wikipedia: inch of mercury") - 1 value - **hPa** or **inHg.**
9. `ProximityPlotSource` - none - 1 value - **integer**.
10. `TemperaturePlotSource` - optionally converted to Fahrenheit - 1 value - **degrees Celsius** or **Fahrenheit**.
11. `VolumePlotSource` - conversion to [decibel](https://en.wikipedia.org/wiki/Decibel "Wikipedia: decibel") 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.

# CLUE Sensor Plotter in CircuitPython

## Plotter Class

![](https://cdn-learn.adafruit.com/assets/assets/000/089/794/medium800/sensors_clue-sensor-plotter-class-plotter-v3.png?1585065345 UML class diagram for Plotter which writes to the screen using displayio library. Private operations and some attributes are omitted for brevity.)

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://en.wikipedia.org/wiki/Decomposition_(computer_science) "Wikipedia: decomposition (computer science)") 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://dspace.mit.edu/handle/1721.1/88013 "Henry, Shawn Lawton, Shadi Abou-Zahra, and Judy Brewer. “The Role of Accessibility in a Universal Web.”"):

> "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 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://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html "W3C: Web Content Accessibility Guidelines (WCAG) 2.1: Understanding Success Criterion 1.4.3: Contrast (Minimum)") for foreground/background colours of text,
- [thoughtful colour selection](https://www.w3.org/WAI/WCAG21/Understanding/use-of-color.html "W3C: Web Content Accessibility Guidelines (WCAG) 2.1: Understanding Success Criterion 1.4.1: Use of Color").

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://www.nei.nih.gov/learn-about-eye-health/eye-conditions-and-diseases/color-blindness/types-color-blindness "US National Eye Institute: Types of Color Blindness").

![sensors_xyz-rgb-typicalaxisclouring-robloxstudio.jpg](https://cdn-learn.adafruit.com/assets/assets/000/089/854/medium640/sensors_xyz-rgb-typicalaxisclouring-robloxstudio.jpg?1585157831)

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.

![](https://cdn-learn.adafruit.com/assets/assets/000/090/026/medium800/sensors_clue-sensor-plotter-range-algorithm-part1-v2.png?1585680221 Flowchart showing the first half of the Plotter's range (scale) algorithm.)

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.

![](https://cdn-learn.adafruit.com/assets/assets/000/090/028/medium800/sensors_clue-sensor-plotter-range-algorithm-part2-v2.png?1585680249 Flowchart showing the second half of the Plotter's range (scale) algorithm.)

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 has passed.

Based on acceptance testing, the zooming still occurs when it looks unnecessary. This algorithm needs further improvement perhaps using a [hysterisis](https://en.wikipedia.org/wiki/Hysteresis#In_engineering "Wikipedia: hysteresis")-based approach.

## Efficient Use of displayio

The [displayio](https://circuitpython.readthedocs.io/en/latest/shared-bindings/displayio/ __init__.html "CircuitPython Docs: Core Modules: displayio") library for CircuitPython (or [Adafruit\_GFX](https://adafruit.github.io/Adafruit-GFX-Library/html/index.html "Adafruit\_GFX Library") for Arduino) provides a single library which can be used with a variety of different size LED, LCD and ePaper screens. This [abstraction](https://en.wikipedia.org/wiki/Abstraction_(computer_science) "Wikipedia: abstraction (computer science)") is very useful and removes the need to directly program the CLUE's [ST7789 LCD display](https://www.adafruit.com/product/4520 "Adafruit: Adafruit 1.3"). 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://en.wikipedia.org/wiki/Compiler "Wikipedia: compiler") 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://en.wikipedia.org/wiki/Interpreter_(computing) "Wikipedia: interpreter (computing)").

### 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://circuitpython.readthedocs.io/en/latest/shared-bindings/displayio/Bitmap.html "CircuitPython docs: displayio Bitmap") class does not currently provide clear, fill or scroll methods. Some early [exploratory programming](https://en.wikipedia.org/wiki/Exploratory_programming "Wikipedia: exploratory programming")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.

```python
# 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)

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://en.wikipedia.org/wiki/Benchmark_(computing) "Wikipedia: benchmark (computing)") 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`.

```none
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.

# CLUE Sensor Plotter in CircuitPython

## CircuitPython on CLUE

[CircuitPython](https://github.com/adafruit/circuitpython) is a derivative of [MicroPython](https://micropython.org) 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** &nbsp;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 :)

[Download the latest version of CircuitPython for CLUE from circuitpython.org](https://circuitpython.org/board/clue_nrf52840_express/)
 **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).

![adafruit_products_CLUE_UF2_Downloaded.png](https://cdn-learn.adafruit.com/assets/assets/000/088/037/medium640/adafruit_products_CLUE_UF2_Downloaded.png?1580840077)

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!

![adafruit_products_Clue_Reset_NeoPixel_bootloader.png](https://cdn-learn.adafruit.com/assets/assets/000/087/919/medium640/adafruit_products_Clue_Reset_NeoPixel_bootloader.png?1580496467)

You will see a new disk drive appear called **CLUEBOOT**.

Drag the **adafruit-circuitpython-clue-etc.uf2** file to **CLUE**** BOOT.**

![adafruit_products_CLUE_CLUEBOOT.png](https://cdn-learn.adafruit.com/assets/assets/000/088/042/medium640/adafruit_products_CLUE_CLUEBOOT.png?1580841287)

![adafruit_products_CLUE_drag_UF2.png](https://cdn-learn.adafruit.com/assets/assets/000/088/043/medium640/adafruit_products_CLUE_drag_UF2.png?1580841295)

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_products_CLUE_CIRCUITPY.png](https://cdn-learn.adafruit.com/assets/assets/000/088/044/medium640/adafruit_products_CLUE_CIRCUITPY.png?1580841453)

# CLUE Sensor Plotter in CircuitPython

## CircuitPython

## Libraries

Once you've gotten CircuitPython onto your Circuit Playground Bluefruit boards, it's time to add some libraries. You can&nbsp;[follow this guide page](https://learn.adafruit.com/adafruit-circuit-playground-bluefruit/circuitpython-libraries)&nbsp;for the basics of downloading and transferring libraries to the board.

[Download the latest library bundle from circuitpython.org](https://circuitpython.org/libraries)
### Libraries for Sensor Plotter

From the library bundle you downloaded in that guide page, transfer the following libraries onto the CLUE board's **/lib** &nbsp;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**

![sensors_clue-sensor-plotter-lib-notepadplusplus-view-expandedlib-43cropandc.png](https://cdn-learn.adafruit.com/assets/assets/000/090/077/medium640/sensors_clue-sensor-plotter-lib-notepadplusplus-view-expandedlib-43cropandc.png?1585764062)

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://circuitpython.org/board/circuitplayground_bluefruit/).

# CLUE Sensor Plotter in CircuitPython

## Sensor Plotter

![](https://cdn-learn.adafruit.com/assets/assets/000/089/979/medium800/sensors_clue-sensor-plotter-buttonguide-mmb-1600x1200.jpg?1585351116 Sensor plotter start-up screen showing button guide.)

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.

## 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.

https://www.youtube.com/watch?v=SgHA3Jvw8VU

The following sections show the lengthy code in all three files. There is also a [Code Discussion](https://learn.adafruit.com/clue-sensor-plotter-circuitpython/circuitpython-sensor-plotter#code-discussion-8-16 "Adafruit Learn: CLUE Sensor Plotter: Sensor Plotter: Code Discussion") section at the bottom of the page.

## Installing Project Code

To use with CircuitPython, you need to first install a few libraries, into the lib folder on your **CIRCUITPY** drive. Then you need to update **code.py** with the example script.

Thankfully, we can do this in one go. In the example below, click the **Download Project Bundle** button below to download the necessary libraries and the **code.py** file in a zip file. Extract the contents of the zip file, open the directory **CLUE\_Sensor\_Plotter/** and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your **CIRCUITPY** drive.

Your **CIRCUITPY** drive should now look similar to the following image:

![CIRCUITPY](https://adafruit.github.io/Adafruit_Learning_System_Guides/CLUE_Sensor_Plotter.png )

### code.py
https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/CLUE/CLUE_Sensor_Plotter/code.py

### plot\_source.py
https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/CLUE/CLUE_Sensor_Plotter/test_PlotSource.py

### plotter.py
https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/CLUE/CLUE_Sensor_Plotter/plotter.py

## Code Discussion

The [Design](https://learn.adafruit.com/clue-sensor-plotter/designr "Adafruit Learn: CLUE Sensor Plotter: Design") section covers a lot of aspects of the program. A few interesting parts of the implementation are discussed here.

### IlluminatedColorPlotSource class

This has the two required methods, the constructor and `data()`. It also implements two optional methods:

- ` start()` to set the gain and duration parameters on the colour sensor and turn on the white LEDs.
- `stop()` to turn off the white LEDs.

```python
class IlluminatedColorPlotSource(PlotSource):
    def __init__(self, my_clue, mode="Clear"):
        self._clue = my_clue
        col_fl_lc = mode[0].lower()
        if col_fl_lc == "r":
            plot_colour = self.RGB_COLORS[0]
        elif col_fl_lc == "g":
            plot_colour = self.RGB_COLORS[1]
        elif col_fl_lc == "b":
            plot_colour = self.RGB_COLORS[2]
        elif col_fl_lc == "c":
            plot_colour = self.DEFAULT_COLORS[0]
        else:
            raise ValueError("Colour must be Red, Green, Blue or Clear")

        self._channel = col_fl_lc
        super().__init__(1, "Illum. color: " + self._channel.upper(),
                         abs_min=0, abs_max=8000,
                         initial_min=0, initial_max=2000,
                         colors=(plot_colour,),
                         rate=50)

    def data(self):
        (r, g, b, c) = self._clue.color
        if self._channel == "r":
            return r
        elif self._channel == "g":
            return g
        elif self._channel == "b":
            return b
        elif self._channel == "c":
            return c
        else:
            return None  # This should never happen

    def start(self):
        # Set APDS9960 to sample every (256 - 249 ) * 2.78 = 19.46ms
        # pylint: disable=protected-access
        self._clue._sensor.integration_time = 249  # 19.46ms, ~ 50Hz
        self._clue._sensor.color_gain = 0x03  # 64x (library default is 4x)

        self._clue.white_leds = True

    def stop(self):
        self._clue.white_leds = False
```

The constructor configures the object to select the appropriate value from the colour sensor. It is intended to accept `Red`, `Green`, `Blue` or `Clear` but will accept `red` and `rouge` as it is only checking the lowercase first character. This could be viewed as an inappropriate application of the [robustness principle](https://en.wikipedia.org/wiki/Robustness_principle "Wikipedia: Robustness principle").

The full range of the sensor data is hard-coded in the constructor as `0` and `8000`. This is based on _observations_ of the data reaching `7169`, `8000` gives nicer values on the y axis tick labels. It's better to retrieve this value programmatically, if it's available, and verify that value against the manufacturer's data sheet.

The clue object does not have a property, public method or public variable to access the sensor to set `integration_time` and `color_gain`. The single underscore prefix on the `_sensor` instance variable indicates that it is intended only for the class itself to access but Python does not enforce this. This allows static code analyzers like **pylint** to detect misuse. In this case, the minor misuse is acknowledged with a pylint disable [pragma](https://en.wikipedia.org/wiki/Directive_(programming) "Wikipedia: directive (programming)") which inhibits the warning. For critical applications this would be inappropriate as it is breaking the object's [encapsulation](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming) "Wikipedia: encapsulation (computer\_programming)") and makes the application vulnerable in possibly disastrous ways to future changes in the class.

## Units and Interfaces

The setting of `integration_time` in `ColorPlotSource` and `IlluminatedColorPlotSource` uses an obscure, manufacturer-specific formula for the actual time not described in the underlying [library documentation](https://circuitpython.readthedocs.io/projects/apds9960/en/latest/api.html#adafruit_apds9960.apds9960.APDS9960.integration_time "CircuitPython Docs: apds9960 library"). This is confusing for a programmer who has previously used the [Arduino library which uses milliseconds](https://github.com/adafruit/Adafruit_APDS9960/blob/master/Adafruit_APDS9960.cpp#L130-L149 "GitHub: adafruit/Adafruit\_APDS9960: Adafruit\_APDS9960 setADCIntegrationTime") as units. Changing the CircuitPython library `integration_time` would require a coordinated and synchronised change with every application which uses the library. This is generally impractical.

The `gyro` value retrieved in `GyroPlotSource` is [documented](https://circuitpython.readthedocs.io/projects/clue/en/latest/api.html "CircuitPython Docs: adafruit\_clue library") as being measured in degrees per second and tests confirm this. The [design guide for CircuitPython's libraries lists gyro](https://circuitpython.readthedocs.io/en/latest/docs/design_guide.html#sensor-properties-and-units "CircuitPython: Design Guide: Sensor properties and units") with a type of `(float, float, float)` and units of "x, y, z `radians` per second". This presents the same problem as the previous case as a change will have impact to many existing users.

These two examples highlight the importance of thoughtful design and review of interfaces.

## Setting Properties and Pass by ...

Python has a feature called [properties](https://www.programiz.com/python-programming/property "Programiz.com: Python Programming: property") which is often used in classes to create things which appear to be instance variables but actually execute code when they are read (get) or assigned to (set). The classic example below features a pair of getter and [setter](https://en.wikipedia.org/wiki/Mutator_method "Wikipedia: mutator method") methods with [decorators](https://docs.python.org/3/glossary.html#term-decorator "Python 3 Docs: Glossary: decorator").

```python
@property
    def channel_colidx(self):
        return self._channel_colidx

    @channel_colidx.setter
    def channel_colidx(self, value):
        # tuple() ensures object has a local / read-only copy of data
        self._channel_colidx = tuple(value)
```

The comment is already explaining a subtlety here of using the passed `value`. The value is intended to be a [sequence](https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range "Python 3 Docs: Built-in Types: Sequence Types — list, tuple, range"). If a list is passed the the caller part of the program can _modify_ the list stored in the class as it is [passed by reference](https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_reference "Wikipedia.org: evaluation strategy"). For comparison, if the value had been an `int` this would not be possible as it is passed by value. This is shown on REPL below with a similar example.

```python
>>> class class_storing_sequence():
...     def __init__(self, a_sequence):
...         self._a_sequence = a_sequence
...         self._a_sequence_copy = tuple(a_sequence)
...
>>> words = ["pass", "by"]
>>> obj = class_storing_sequence(words)
>>> words.extend(["object", "reference"])

>>> obj._a_sequence
['pass', 'by', 'object', 'reference']
>>> obj._a_sequence_copy
('pass', 'by')
```

An even more confusing permutation would be another part of the program retrieving the `channel_colidx` list and intentionally or unintentionally modifying it. This could cause tricky-to-find bugs. The cautious use of `tuple()` in `channel_colidx(self, value)`&nbsp;creates an independent copy of the sequence. The use of `tuple()` rather than `list()` is very intentional as this makes it _read-only_ due to Python tuples being [immutable](https://en.wikipedia.org/wiki/Immutable_object "Wikipedia: immutable object").

The general issue is described and discussed in detail in [Robert Heaton's blog post: Is Python pass-by-reference or pass-by-value?](https://robertheaton.com/2014/02/09/pythons-pass-by-object-reference-as-explained-by-philip-k-dick/ "Robert Heaton's blog post: Is Python pass-by-reference or pass-by-value?")

# CLUE Sensor Plotter in CircuitPython

## Signal/Colour Generator

![](https://cdn-learn.adafruit.com/assets/assets/000/089/866/medium800/sensors_feather-m4-express-colour-and-signal-generator.jpg?1585163003 Feather M4 Express used as signal generator and colour source for demonstrating sensor plotter on CLUE.)

A [Feather M4 Express](https://learn.adafruit.com/adafruit-feather-m4-express-atsamd51 "Adafruit Learn: Feather M4 Express") board was enlisted to aid the demonstration of the sensor plotter in the video on the previous page. It provided:

- alternating red, green blue light from the onboard NeoPixel for the CLUE's APDS-9960 colour sensor;
- two analogue signals from **A0** and **A1** which were connected to the **#0** and **#1** pads on the CLUE using some test hooks.

Its SAMD51 (M4) processor is useful here as it has 2 DACs.

The code running on the board was very short and simply typed in over the serial console on CircuitPython's REPL.

```python
import audioio, audiocore, board, neopixel, time

dacpair = audioio.AudioOut(board.A0,
                           right_channel=board.A1)
filename = "adafruit-spinning-logo-plot-2chan.wav"
wav_file = open(filename, "rb")
samples = audiocore.WaveFile(wav_file)
samples.sample_rate = 500
dacpair.play(samples, loop=True)
pixel = neopixel.NeoPixel(board.NEOPIXEL, 1)

# Loop forever showing r, g, b while the
# DACs continue to loop the samples from wav file
while True:
    pixel[0] = (255, 0, 0)  # red
    time.sleep(0.5)
    pixel[0] = (0, 255, 0)  # green
    time.sleep(0.5)
    pixel[0] = (0, 0, 255)  # blue
    time.sleep(0.5)
```

Wav files are typically used for audio but they can be used for any signal. In this case the `adafruit-spinning-logo-plot-2chan.wav` has two signals following the top and bottom outline of the Adafruit logo, spinning 1/5th of a revolution in total across five frames.

Featured
### Adafruit Feather M4 Express - Featuring ATSAMD51

[Adafruit Feather M4 Express - Featuring ATSAMD51](https://www.adafruit.com/product/3857)
It's what you've been waiting for, the Feather M4 Express featuring ATSAMD51. This Feather is fast like a swift, smart like an owl, strong like a ox-bird (it's half ox, half bird, OK?) This feather is powered by our new favorite chip, the **ATSAMD51J19** -&nbsp; with...

In Stock
[Buy Now](https://www.adafruit.com/product/3857)
[Related Guides to the Product](https://learn.adafruit.com/products/3857/guides)
![Angled shot of a Adafruit Feather M4 Express. ](https://cdn-shop.adafruit.com/640x480/3857-10.jpg)

Featured
### Basic Multi-Color Micro SMT Test Hooks (6-pack)

[Basic Multi-Color Micro SMT Test Hooks (6-pack)](https://www.adafruit.com/product/4322)
Testing and debugging is a challenging step in board manufacturing. It can be super tricky on a fully populated PCB. So how can you test connections&nbsp;when you have an assortment of components blocking your&nbsp;[probe](https://www.adafruit.com/product/1528)?

These...

In Stock
[Buy Now](https://www.adafruit.com/product/4322)
[Related Guides to the Product](https://learn.adafruit.com/products/4322/guides)
![Angled shot of Basic Multi-Color Micro SMT Test Hooks (6-pack)](https://cdn-shop.adafruit.com/640x480/4322-01.jpg)

# CLUE Sensor Plotter in CircuitPython

## Sensors

![](https://cdn-learn.adafruit.com/assets/assets/000/089/756/medium800/sensors_spitfire-i-compass-swinging-721083-transparent43letterbox.png?1585012975 Spitfire compass swing to calibrate the onboard compass from The Royal Air Force in Pictures (1941).)

The sensor plotter shows some of the characteristics of the sensors. A few are highlighted here.

## Temperature

The temperature on the CLUE board appears to over-read. This is likely to be from the electronics on the board heating the sensor. The graph below shows a value around 30.8C for a room at 23C.

![](https://cdn-learn.adafruit.com/assets/assets/000/089/795/medium800/sensors_clue-sensor-plotter-temperature-zoomedin-droponscroll.jpg?1585072942 Plotting temperature showing drop of 0.25C when sensor is not read for a short period while the progam jump scrolls the plot.)

The graph clearly shows the temperature readings are not constant and have a distinctive pattern not representative of (random) noise. This screenshot was taken with the program in scrolling mode where it scrolls in jumps of one division. That scroll takes a few hundred milliseconds. It's possible that when the sensor is not being read it cools down a tiny amount but more research is required here.

## Pressure

The pressure is surprisingly accurate in matching local meteorological observations (adjusted for height difference). This may vary per device. The relative pressure change for changes in altitude are capable of measuring changes of a few feet. There is some noise on the sensor visible in the video below. If the vertical movements are known to be slow then filtering could be used to improve the accuracy.

https://www.youtube.com/watch?v=2R0WfttFqOs

## Humidity

Unlike a traditional [wet-bulb thermometer](https://en.wikipedia.org/wiki/Wet-bulb_temperature "Wikipedia: Wet-bulb temperature") the humidity sensor responds very rapidly to changes in humidity. It responds sub-second and can easily detect human breath nearby. In some circumstances this might be a disadvantage - careful sampling and filtering might be required to ensure a value is achieved that truly represents the local atmosphere.

## Volume

The volume is calculated from samples taken from a microphone on the board. This means it will pick up physical shocks to the board like button presses. These spikes can cause the auto-ranging to expand too much.

## Colour Sensitivity

The colour sensitivity is a bit surprising. The NeoPixel on a Feather M4 Express was used to test the red, green and blue plotting. The blue is clearly the highest of the three despite the green appearing to be the brightest to the human eye. Some ambient light was present but this plus the cross-sensitivity can be seen on the plot - these are not factors here.

![](https://cdn-learn.adafruit.com/assets/assets/000/089/968/medium800/sensors_clue-plotter-color-sensor-rgblevelmystery-1.jpeg?1585340451 Plotting light from NeoPixel illumination from Feather M4 plus a little ambient light.)

The APDS-9960 datasheet has a graph showing the sensitivity of the different colour sensors but this seems to run counter to what is observed.

![](https://cdn-learn.adafruit.com/assets/assets/000/089/676/medium800/sensors_avago-apds-9960-datasheet-photodiode-spectralresponse-1200x900.png?1584982776 Spectral response of the photodiodes used for colour detection and proximity/gesture sensing from the Avago APDS-9960 datasheet.)

## Proximity

The proximity sensor is based on a simple value derived from measuring the infrared light reflected from the object illuminated by its 950nm IR LED. This will vary depending on the infrared reflectance of the object so cannot be considered to be an absolute measure of inverse distance. For example, a matte black, plastic USB connector only registers a maximum proximity of 40. The absolute maximum is 255.

## Magnetometer

The magnetometer plots on the two CLUE boards used during development of the sensor plotter showed very different values. In the absence of any strong magnetic fields the sensor picks up the Earth's magentic field, therefore the boards must be in the same _position_ and _orientation_ for a valid comparison. A pair of representative samples were:

- CLUE 1: `(-11.1371, -7.04472, 40.2952)`,
- CLUE 2: `(-46.5653, 20.8272, 21.2803)`.

Magnetometer sensors are well-known for their need to be calibrated. This non-trivial process is described in [Adafruit SensorLab - Magnetometer Calibration](https://learn.adafruit.com/adafruit-sensorlab-magnetometer-calibration "Adafruit Learn: Adafruit SensorLab - Magnetometer Calibration").

A set of sample readings for two (uncalibrated) CLUE boards are shown below, comparing the values from the Earth's field and that from a small [neodymium magnet](https://en.wikipedia.org/wiki/Neodymium_magnet "Wikipedia: neodymium magnet") placed 8cm away.

![](https://cdn-learn.adafruit.com/assets/assets/000/090/286/medium800thumb/sensors_clue-magnetometer-comparison.jpg?1586726213)

# CLUE Sensor Plotter in CircuitPython

## Testing

![](https://cdn-learn.adafruit.com/assets/assets/000/089/984/medium800/sensors_sommerville-software-engineering-stages-of-testing.png?1585405793 Stages in software testing from old edition of Software Engineering by Ian Sommerville (redrawn).)

The essence of the diagram is testing small parts of the code in isolation and gradually assembling them into increasingly larger components until the application runs in a environment representative of how it will be used for real for the integration tests.

Testing can also be segmented into:

- tests which check whether the application meets the requirements of the users and
- tests which look for defects.

The latter often use unusual, [boundary](https://en.wikipedia.org/wiki/Boundary-value_analysis "Wikipedia: boundary-value analysis") or erroneous inputs to check the behaviour is still acceptable.

Acceptance testing differs in that the participants are either the final users or a proxy for them. There may also be other groups of users like [alpha](https://en.wikipedia.org/wiki/Software_testing#Alpha_testing "Wikipedia: software testing") and beta testers. Microsoft's [Windows Insider](https://en.wikipedia.org/wiki/Windows_Insider "Wikipedia: Windows Insider") program and ["early access"](https://en.wikipedia.org/wiki/Early_access "Wikipedia: early access") in certain parts of the video games industry are variants on this.

## TemperaturePlotSource class

The code below is the`TemperaturePlotSource` class. Objects are created in Python with the ` __init__ (self)` method. The current program only creates one object for Celsius and another for Fahrenheit leaving the conversion to Kelvin _unused._ The code looks reasonable at a quick glance.

```python
class TemperaturePlotSource(PlotSource):
    def _convert(self, value):
        return value * self._scale + self._offset

    def __init__(self, my_clue, mode="Celsius"):
        self._clue = my_clue
        range_min = 0.8
        if mode[0].lower() == "f":
            mode_name = "Fahrenheit"
            self._scale = 1.8
            self._offset = 32.0
            range_min = 1.6
        elif mode[0].lower == "k":
            mode_name = "Kelvin"
            self._scale = 1.0
            self._offset = -273.15
        else:
            mode_name = "Celsius"
            self._scale = 1.0
            self._offset = 0.0
        super().__init__(1, "Temperature",
                         units=mode_name[0],
                         abs_min=self._convert(-40),
                         abs_max=self._convert(85),
                         initial_min=self._convert(10),
                         initial_max=self._convert(40),
                         range_min=range_min,
                         rate=24)

    def data(self):
        return self._convert(self._clue.temperature)
```

## Code Review

In this case, the code below is simple enough to mentally check the output from `_convert()` and `data()`. A close inspection reveals a difference in the way `lower` is being used. A [code review](https://en.wikipedia.org/wiki/Code_review "Wikipedia: code review") is one way to discover bugs where another person checks the code. This is a very effective technique if the reviewer has sufficient review time, authority to reject the code and joint liability for the code.

The current program only creates one object for Celsius and another for Fahrenheit leaving the conversion to Kelvin unused. The CGP [GCSE OCR Computer Science Revision Guide Book](https://www.cgpbooks.co.uk/secondary-books/gcse/computer-science/cor41-gcse-computer-science-ocr-revision-guide "CGP: GCSE OCR Computer Science Revision Guide Book") covers test plans and states:

> A test plan will outline exactly what you're going to test and how you're going to test it. It should cover all the possible paths through a program.

A human tester cannot test the Kelvin code with the program as it stands. A programmatic approach to this is to write some code which tests this small piece of code in isolation. This is referred to as [unit testing](https://en.wikipedia.org/wiki/Unit_testing "Wikipedia: unit testing"). The percentage of paths tested is known as [code coverage](https://en.wikipedia.org/wiki/Code_coverage "Wikipedia: code coverage") and can be reported by software tools.

![](https://cdn-learn.adafruit.com/assets/assets/000/089/872/medium800/sensors_Le_Garcon_au_gilet_rouge-Paul_Cezanne-NG-1200x900.png?1585179325 “New employee learns that his team’s codebase has no unit tests” from Classic Programmer Paintings. Also known as Boy in a Red Waistcoat (1888-1890) by Paul Cezanne.)

## Unit Testing

The unit test framework is not supported by CircuitPython but the code has excellent compatibility with Python allowing the tests to be executed on another computer and making that testing highly equivalent.

The same physical temperature sensor is unlikely to be present on another computer but this is not really a disadvantage as the real sensor gives an unpredictable temperature and one that probably lies in a very limited range most of the time.

## Mocking

The sensor can be _emulated_ in code for testing - this technique is referred to as [mocking](https://en.wikipedia.org/wiki/Mock_object "Wikipedia: mock object"). Python has a [mock object library](https://docs.python.org/3/library/unittest.mock.html "Python 3 Docs: unittest.mock — mock object library") which helps to do this.

The following pages explore using these techniques to find bugs in the code above.

# CLUE Sensor Plotter in CircuitPython

## Bug 1

![](https://cdn-learn.adafruit.com/assets/assets/000/089/873/medium800/sensors_harvard-computer-mkii-bug-closeup-cropped.jpg?1585229734 Log Book With Computer Bug from Mark II computer at Harvard University (1947). Smithsonian Collection.)

Tests using the `unittest.mock` [framework](https://en.wikipedia.org/wiki/Software_framework "Wikipedia.org: software framework") have a lot of **test\_** prefixes: methods are prefixed with `test_`, classes are prefixed with `Test_` and they are stored in files with filenames prefixed with `test_`.

## Finding the Bug

The unit test below shows a simple test of a `TemperaturePlotSource` object created in Kelvin `mode` and tested with values starting at `20` degrees Celsius which would equate to `293.15`K.

```python
# an excerpt from Test_TemperaturePlotSource class
    SENSOR_DATA = (20, 21.3, 22.0, 0.0, -40, 85)

    def test_kelvin(self):
        """Create the source in Kelvin mode and test with some values."""
        # Emulate the clue's temperature sensor by
        # returning a temperature from a small tuple
        # of test data
        mocked_clue = Mock()
        expected_data = (293.15, 294.45, 295.15,
                         273.15, 233.15, 358.15)
        type(mocked_clue).temperature = PropertyMock(side_effect=self.SENSOR_DATA)

        source = TemperaturePlotSource(mocked_clue,
                                       mode="Kelvin")

        for expected_value in expected_data:
            data = source.data()
            # self.assertEqual(data,
            #                 expected_value,
            #                 msg="An inappropriate check for floating-point")
            self.assertAlmostEqual(data,
                                   expected_value,
                                   msg="Checking converted temperature is correct")
```

There are three tests in a `Test_TemperaturePlotSource` class. Python on a desktop computer (or Raspberry Pi) can execute these. One run is shown below.

```none
$ python tests/test_PlotSource.py
test_celsius (__main__.Test_TemperaturePlotSource)
Create the source in Celsius mode and test with some values. ... ok
test_fahrenheit (__main__.Test_TemperaturePlotSource)
Create the source in Fahrenheit mode and test with some values. ... ok
test_kelvin (__main__.Test_TemperaturePlotSource)
Create the source in Kelvin mode and test with some values. ... FAIL

======================================================================
FAIL: test_kelvin (__main__.Test_TemperaturePlotSource)
Create the source in Kelvin mode and test with some values.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests/test_PlotSource.py", line 101, in test_kelvin
    msg="Checking converted temperature is correct")
AssertionError: 20.0 != 293.15 within 7 places : Checking converted temperature is correct

----------------------------------------------------------------------
Ran 3 tests in 0.002s

FAILED (failures=1)
```

The Celsius and Fahrenheit tests are fine (`ok` output) but the Kelvin one is failing. The failure is reported with the value from the first comparison. For some reason, the value `20.0` is being returned when we expect `293.15`, _no conversion is taking place_ and the value has been left in Celsius for some reason.

![](https://cdn-learn.adafruit.com/assets/assets/000/089/992/medium800/sensors_jolni-thermometer.jpg?1585438771 Dual scale Jolni thermometer. Photograph by Kevin Walters.)

A previous code inspection showed an anomaly with the use of `lower()` vs `lower`. CircuitPython's REPL allows us to explore this interactively on the command line, using the CLUE board.

The _absence_ of the round brackets on the end of the `lower` method call introduces a critical flaw into the code. CircuitPython is somehow comparing a method call with a string. This "chalk and cheese" comparison is very unfortunate here!

```python
Adafruit CircuitPython 5.0.0 on 2020-03-02; Adafruit CLUE nRF52840 Express with nRF52840
>>> mode="Kelvin"
>>> mode[0].lower == "k"
False
>>> mode[0].lower == "K"
False
>>> mode[0]
'K'
>>> mode[0].lower
<bound_method>
>>> mode[0].lower() == "k"
True
```

Python is often referred to as [strongly](https://en.wikipedia.org/wiki/Strong_and_weak_typing "Wikipedia: strong and weak typing") and [dynamically](https://en.wikipedia.org/wiki/Type_system#DYNAMIC "Wikipedia: type system")-typed but an equality test using `==` will compare anything against anything - if they are not equivalent in some way then it will evaluate as `False`.

For comparison, this is the approximate equivalent in C++ using a different [member function](https://en.wikipedia.org/wiki/Method_(computer_programming)#Member_functions_in_C++ "Wikipedia: method (computer programming)") (C++ terminology for a method) called `front`.

```cpp
std::string Family("Coronaviridae");

if (Family.front() == 'N') {
  std::cout << "Starts with N" << std::endl;
} else if (Family.front == 'C') {
  std::cout << "Starts with C" << std::endl;
}
```

The compilation aborts with an error because the types cannot be legitimately compared using the C++ type system. The relative strength of type systems is a subjective topic and can vary in practical terms based on how a language is used.

```none
$ g++ -std=c++11 -o type-demonstration type-demonstration.cpp     type-demonstration.cpp: In function ‘int main(int, char**)’:
type-demonstration.cpp:15:30: error: invalid operands of types ‘<unresolved overloaded function type>’ and ‘char’ to binary ‘operator==’
   } else if (Family.front == 'C') {
                              ^
```

A language which checks types at compile-time is referred to as [statically](https://en.wikipedia.org/wiki/Type_system#STATIC "Wikipedia: type system")-typed.

Sometimes other software tools can help find likely bugs. In this case [pylint](https://www.pylint.org/ "Pylint") has some ability to find dubious comparisons but it needs to be able to determine the type accurately and this prevents it from finding this particular bug. The simpler case is shown below where **pylint** presents a warning (`W` in `W0143`) about a possible mistake by the programmer.

```python
STRING = "HELLO"
if STRING[0].lower == "h":  # not what programmer intended!
    print("Found an h or an H")  # won't even be executed
```

```none
$ pylint check_pylint_comparison_with_callable.py
************* Module check_pylint_comparison_with_callable
check_pylint_comparison_with_callable.py:4:3: W0143: Comparing against a callable, did you omit the parenthesis? (comparison-with-callable)
```

## Fixing the Bug

Adding the missing `()` is all that's required but the test still fails. The error message has changed.

```none
AssertionError: -253.14999999999998 != 293.15 within 7 places : Checking converted temperature is correct
```

# CLUE Sensor Plotter in CircuitPython

## Bug 2

![](https://cdn-learn.adafruit.com/assets/assets/000/089/875/medium800/sensors_software-762486_1920-43.jpg?1585230238)

## More Than One Bug

The second bug is easier to find and understand. The error message shows the number is being converted but is wildy wrong. The Kelvin scale starts at [absolute zero](https://en.wikipedia.org/wiki/Absolute_zero "Wikipedia: absolute zero") so cannot be negative!

```none
$ python tests/test_PlotSource.py Test_TemperaturePlotSource.test_kelvin
test_kelvin (__main__.Test_TemperaturePlotSource)
Create the source in Kelvin mode and test with some values. ... FAIL

======================================================================
FAIL: test_kelvin (__main__.Test_TemperaturePlotSource)
Create the source in Kelvin mode and test with some values.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests/test_PlotSource.py", line 101, in test_kelvin
    msg="Checking converted temperature is correct")
AssertionError: -253.14999999999998 != 293.15 within 7 places : Checking converted temperature is correct

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)
```

In this case the programmer's mistake can be seen by looking at the:

- calculation (`value * self._scale + self._offset`) and
- the values used in it (`self._scale = 1.0` and `self._offset = -273.15`).

The offset has the wrong sign, it should be a positive value. Once fixed, the tests can all be re-executed to check this fix.

```none
$ python tests/test_PlotSource.py
test_celsius (__main__.Test_TemperaturePlotSource)
Create the source in Celsius mode and test with some values. ... ok
test_fahrenheit (__main__.Test_TemperaturePlotSource)
Create the source in Fahrenheit mode and test with some values. ... ok
test_kelvin (__main__.Test_TemperaturePlotSource)
Create the source in Kelvin mode and test with some values. ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.002s

OK
```

These tests were written after the code was created to test some code which had not been exercised by the sensor plotter program. Some other more complex tests were also written to help understand some bugs in the more complex `Plotter` class and prevent [regressions](https://en.wikipedia.org/wiki/Software_regression "Wikipedia: software regression"). A slightly different [software engineering](https://en.wikipedia.org/wiki/Software_engineering "Wikipedia: software engineering") approach is to write the tests _before_ the code - this is known as [test-driven development (TDD)](https://en.wikipedia.org/wiki/Test-driven%20development "Wikipedia: test-driven development").

Once tests have been created they can also be integrated into the development and deployment processes. [Continuous integration](https://en.wikipedia.org/wiki/Continuous_integration "Wikipedia: continuous integration") is one way to execute them, often as a quality gate.

The full set of tests can be viewed on [GitHub](https://github.com/adafruit/Adafruit_Learning_System_Guides/tree/master/CLUE_Sensor_Plotter "GitHub: adafruit/Adafruit\_Learning\_System\_Guides CLUE\_Sensor\_Plotter").

# CLUE Sensor Plotter in CircuitPython

## Number Representation

![](https://cdn-learn.adafruit.com/assets/assets/000/089/958/medium800/sensors_z1-43ratio.png?1585269091 Zuse Z1 electro-mechanical computer with 28bit floating-point arithmetic (1937).)

The previous pages feature two interesting examples of the care that's required with numbers in a computer language.

1. The unit test uses the peculiar sounding `assertAlmostEqual` method.
2. One of the interim test failures reported a number as `-253.14999999999998` where the buggy code would be expected to produce `-253.15`.

It's very common for languages to use data types to represent numbers which have a _fixed size_. For an integer this limits the range of numbers. For a real number which are typically represented in [floating-point](https://en.wikipedia.org/wiki/Floating-point_arithmetic "Wikipedia.org: floating-point") form this limits the precision and range. The example below shows how 6.25 is represented exactly in floating-point - the relatively uncommon [half precision (FP16)](https://en.wikipedia.org/wiki/Half-precision_floating-point_format "Wikipedia: half-precision floating-point format") format is used for brevity.

![](https://cdn-learn.adafruit.com/assets/assets/000/090/136/medium800/sensors_csp-fp16-example-v2-zoom290-1600x1200-imeroded.png?1585913320 Example of steps to convert decimal 6.25 to half precision (FP16) floating-point representation. At step 7 an offset (bias) is introduced to allow for negative exponents. Step 9 swaps the order to match the final bit order. Step 10 highlights the implicit bit with a box - this is always 1 and not stored. Step 11 is the final value with sign shown as 0 for positive, negative would be 1.)

Binary computers using the ubiquitous [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754 "Wikipedia: IEEE 754") standard cannot represent 253.15 exactly in the same way that 1/3 cannot be represented exactly as a decimal number. These miniscule errors are often compounded and enlarged by repeated or large calculations.

The [assertAlmostEqual()](https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertAlmostEqual "Python 3 Docs: unittest.TestCase.assertAlmostEqual") method is specifically for floating-point comparisons where the equivalence test needs to take into account the precision limitations of floating-point arithmetic. The [assertEqual()](assertEqual "Python 3 Docs: unittest.TestCase.assertEqual") method could be used with the correct, exact number produced by the computer's arithmetic but this is too fragile as a change in

- calculation order,
- rounding mode or
- intermediate precision

may alter the resulting number.

## Python 3

Python hides the detail of the number representation from the programmer most of the time. The `type()` function will reveal whether a number is an `int` or a `float:`

- `int` - [arbritrary precision](https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic "Wikipedia: arbitrary-precision arithmetic") (length) integers;
- `float` - 64bit floating-point.

Care is still needed with very large integers to avoid loss of precision from unintentional conversion to a `float`. It's common to accidentally use `/` for (floating-point) division when `//` (floor division) was intended - the latter retains the `int` type if _both_ operands are `int`s.

## CircuitPython

CircuitPython is a variant of MicroPython and both maintain Python's approach of largely hiding the number representation from the programmer.

### Small Boards

This category, sometimes referred to as the "non-Express boards", comprises the Gemma M0, Trinket M0 and Feather M0 non-Express boards.

- `int` - 31bit, -1073741824 to 1073741823. An `OverflowError`&nbsp;exception is raised beyond this range.
- `float` - 30bit storage but calculations are performed with 32bit precision.  
&nbsp;

### Large Boards

Everything not in the above small category which runs CircuitPython.

- `int` - arbritrary precision integers.
- `float` - 30bit storage but calculations are performed with 32bit precision.

## C/Arduino

For comparison:

- [int](https://www.arduino.cc/reference/en/language/variables/data-types/int/ "Arudino Docs: Reference: Language: Variables: Data types: Int") 16bit on Arduino Uno (and other ATmega based boards) and 32bit on Arduino Due and SAMD based boards.
- [float](https://www.arduino.cc/reference/en/language/variables/data-types/float/ "Arduino Docs: Reference: Language: Variables: Data types: Float") - 32bit.

## Zuse Z1

An older comparison:

- float - 24bit storage but calculations are performed with 28bit equivalent precison.

![](https://cdn-learn.adafruit.com/assets/assets/000/089/999/medium800/sensors_zuse-z1-rebuild-architecture.png?1585579002 Block diagram of the Zuse Z1 (1936-38) according to the 1989 reconstruction. From The Z1: Architecture and Algorithms of Konrad Zuse’s First Computer by Raul Rojas.)

## Decimal Precision

The equivalent decimal precision in digits ([significant figures](https://en.wikipedia.org/wiki/Significant_figures "Wikipedia: significant figures")) for a range of floating-point sizes is shown in the table below.

![](https://cdn-learn.adafruit.com/assets/assets/000/102/171/medium800/sensors_fpsizes-summary-cleaned-v2-43.png?1621257162)

The [Schmidt JavaScript Visualisation of 32bit IEEE-754 floating-point](https://www.h-schmidt.net/FloatConverter/IEEE754.html "Schmidt JavaScript Visualisation of 32bit IEEE-754 floating-point") is a useful online tool to understand floating-point. Stored values in CircuitPython will always have the lower two bits not set. The animated example below shows how the value 3.14 is represented and how it would be represented in CircuitPython with two fewer bits.

![](https://cdn-learn.adafruit.com/assets/assets/000/090/000/medium800/sensors_schmidt-iee754-demo-clear2lsb-3.14.gif?1585590382 Schmidt IEEE-754 floating-point visualisation of 3.14 and lower 2 bits being cleared simulating how CircuitPython stores values.)

The next page discusses the practical issues of representing time in CircuitPython due to the underlying number representation.

# CLUE Sensor Plotter in CircuitPython

## Time in CircuitPython

![](https://cdn-learn.adafruit.com/assets/assets/000/089/964/medium800thumb/sensors_horology-house-rolex-submariner-secondhand-macro-video-2.jpg?1585335765 Second hand stepping on a Rolex Submariner wrist watch at eight times a second, shown at 0.05x speed, courtesy of Horology House.)

The time on a computer passes in small steps. If these values are stored or processed using a fixed size representation then care is needed to prevent issues. For example:

- A clock that ticks 100 times a second (100Hz), represented by a signed 32bit integer can only reach `(2^31 - 1) / 24 / 3600 / 100 = 248.55` days before overflowing. E.g. [FAA Airworthiness Directive 2018-20-15 for Boeing 787](https://rgl.faa.gov/Regulatory_and_Guidance_Library/rgad.nsf/0/7332a6ac61f5cb30862583280049940e/%24FILE/2018-20-15.pdf "FAA Airworthiness Directive 2018-20-15 for Boeing 787") (pdf)
- A 4000Hz clock using the same representation will overflow at `(2^31 - 1) / 3600 / 4000 = 149.13` hours. E.g. [EASA Airworthiness Directive 2017-0129R1 for Airbus A350](https://ad.easa.europa.eu/blob/EASA_AD_2017_0129_R1.pdf/AD_2017-0129R1_1 "EASA Airworthiness Directive 2017-0129R1 for Airbus A350") (pdf).

## CircuitPython time functions

CircuitPython provides a [subset of functions from the time library](https://circuitpython.readthedocs.io/en/latest/shared-bindings/time/ __init__.html "CircuitPython Docs: Docs: Core Modules: time - time and timing related functions"). The [monotonic](https://en.wikipedia.org/wiki/Monotonic_function "Wikipedia: monotonic function") in their name refers to a guarantee that time will not go backwards. For comparison, [gettimeofday on Linux](https://stackoverflow.com/questions/211055/does-linux-provide-a-monotonically-increasing-clock-to-applications "StackOverflow: Does Linux provide a monotonically increasing clock to applications") does not provide this guarantee and naive use for timing durations can result in _negative_ values!

### time.monotonic()

Python and CircuitPython's `time.monotonic()` returns time in seconds as a `float` variable. This return value increases over time reducing the resolution available to represent the fractional part of the value. The effect is far more significant for the [30bit storage representation used by CircuitPython](https://github.com/adafruit/circuitpython/issues/342#issuecomment-337228427 "GitHub: adafruit/circuitpython: Avoid double precision floats in time.monotonic() computation to further increase flash space #342") (based on [single precision floating point](https://en.wikipedia.org/wiki/IEEE_754 "Wikipedia: IEEE 754")) in combination with the [epoch](https://en.wikipedia.org/wiki/Epoch_(computing) "Wikipedia: epoch (computing)") time of 0.0 at power-up. This lowers the precision of the millisecond portion as the program continues to run and increases the granularity. This effect can be very significant if the program runs for days or weeks.

### time.monotonic\_ns()

Python (3.5+) and CircuitPython's `time.monotonic_ns()` (available on all boards bar Gemma M0, Trinket M0 and Feather M0 non-Express) is the equivalent of `time.monotonic()` with an&nbsp; `int` return value in nanoseconds (one billionth of a second). This value is not subject to reduced precision over time as long as the value is not accidentally converted to a `float`. Examples of correct and incorrect usage are shown below.

```python
import time

# Good - int value preserved
start = time.monotonic_ns()
time.sleep(0.005)
ms_duration = (((time.monotonic_ns() - start) + 500000)
               // 1000000)
print(ms_duration)


# Good - int subtraction then the small value
# duration value is ok to convert to a float
start = time.monotonic_ns()
time.sleep(0.005)
ms_duration = round((time.monotonic_ns() - start) / 1e6, 1)
print(ms_duration)


# BAD - accidental/premature conversion to
# float reducing accuracy of calculation
start_ms = time.monotonic_ns() / 1e6  # BAD
time.sleep(0.005)
ms_duration = round(time.monotonic_ns() / 1e6  # BAD
                    - start_ms, 1)
print(ms_duration)
```

The first example will print `5`. The second example prints values like `4.6` or `4.7`. The third example will print `0.0` or infrequently `32.0` for a board that's been running for one day!

Care is needed when using the exponential notation like `1e6` for `1000000`. In Python, `1e6` is always a `float`. The example below from REPL shows how unintended [type promotion](https://en.wikipedia.org/wiki/Type_conversion#Type_promotion "Wikipedia: type conversion") to `float` spoils the accuracy of the addition.

```python
>>> t1 = time.monotonic_ns()
>>> t1
167333909366000
>>> t1 + 1e9
1.67335e+14
>>> int(t1 + 1e9)
167334878642176

>>> t1 + 1000000000
167334909366000
```

Using the correct division operator for floor division (`//`) and ensuring it's used with `int` types is also critical to avoid trouble from `float`s.

```python
>>> smaller = 1034567890
>>> larger = smaller + 10

>>> if larger > smaller: print("all ok - integer calculation/comparison")
...
all ok - integer calculation/comparison
>>> if larger // 10 > smaller // 10: print("all ok - integer calculation/comparison")
...
all ok - integer calculation/comparison

>>> if larger / 10 > smaller // 10: print("not ok - float division")
...
>>> if larger // 1e1 > smaller // 10: print("not ok - integer division used with float")
...
```

## Making Large Numbers Readable

The `1e9` representation is useful for one billion because  
the number 9 is easier to read and validate compared to visually counting the number of zeroes in `1000000000`. The previous section mentions the type of `1e9` is a `float`.

The language [Ada](https://en.wikipedia.org/wiki/Ada_(programming_language) "Wikipedia: Ada (programming language)") introduced a representation of numeric [literals](https://en.wikipedia.org/wiki/Literal_(computer_programming) "Wikipedia: literal (computer programming)") (numbers) with optional underscores to allow a number to visually broken up. From Honeywell Bull's successful "GREEN" proposal for Ada, "Rationale for the Design of the GREEN Programming Language (1979)":

> The underscore is permitted within a number to break up long sequences of digits, a requirement that has long been recognized by printers.

[Java introduced this with V7 in 2011](https://docs.oracle.com/javase/8/docs/technotes/guides/language/underscores-literals.html "Java: Underscores in Numeric Literals"), Python introduced this in [3.6 in 2016](https://docs.python.org/3/whatsnew/3.6.html "Python Docs: What's new in 3.6?"). CircuitPython implements this, an example on REPL can be seen below showing how large `int` values can be more clearly represented.

```python
Adafruit CircuitPython 5.0.0 on 2020-03-02; Adafruit CLUE nRF52840 Express with nRF52840
>>>
>>> an_int = 1_000_000_000
>>> print(an_int,
...       an_int // 1000_000, an_int // 1_00_00_00,
...       int("-1_234_567"))
1000000000 1000 1000 -1234567
```

## Performance Variability

In general, interpreted languages tend to offer less predictable performance than compiled languages so there may be some variation in this timing. CircuitPython performance will be occasionally affected by:

- garbage collection (a few ms),
- various other background tasks like USB activity including **CIRCUITPY** file system operations,
- automatic `displayio` screen refreshes (can be 100+ ms).

Info: 

## Demonstration of time.monotonic() Granularity

This is a short program which demonstrates the degradation in sub-second `time.monotonic()` resolution. The output will differ on boards which have been powered up for a long time showing how time becomes more granular.

```python
import board, time

# Disable updates to LCD screen which 
# will occasionally introduce very large
# delays between statement execution
board.DISPLAY.auto_refresh = False


print(*[("{:" + str(hdr[0])
         + "s}").format(hdr[1])[:hdr[0]]
        for hdr in ((3, "num"),
                    (15, "monotonic_ns()"),
                    (12, "monotonic()"),
                    (8, "fraction"),
                    (9, "offset"))
       ],
       sep=" | ")

count = 1
previous = time.monotonic()

# Print the time when it changes according
# to time.monotonic() to inspect the
# sub-second precision
while True:
    now = time.monotonic()
    now_ns = time.monotonic_ns()
    if now != previous:
        now_ns_str = str(now_ns)
        now_frac = now - int(now)
        print("{:3d}".format(count),
              now_ns_str[:-9] + "." + now_ns_str[-9:],
              "{:.6f}".format(now),
              "{:.6f}".format(now_frac),
              "{:.6f}".format(int(now_ns_str[-9:-3])
                              / 1e6 - now_frac),
              sep=" | ")
        previous = now
        count = 1
    else:
        count += 1
```

### Ten Minutes

A CLUE board which is approaching 10 minutes of uptime prints the time on every iteration of the loop and has sub-millisecond precision from `time.monotonic()`.

```none
num | monotonic_ns()  | monotonic()  | fraction | offset
  1 | 563.305638000 | 563.304901 | 0.304932 | 0.000706
  1 | 563.310520000 | 563.309813 | 0.309814 | 0.000705
  1 | 563.315276000 | 563.314915 | 0.314941 | 0.000335
  1 | 563.319893000 | 563.318825 | 0.318848 | 0.001045
  1 | 563.324549000 | 563.323975 | 0.323975 | 0.000574
  1 | 563.329326000 | 563.328838 | 0.328857 | 0.000468
  1 | 563.333935000 | 563.332987 | 0.333008 | 0.000927
  1 | 563.338627000 | 563.337898 | 0.337891 | 0.000736
  1 | 563.343376000 | 563.343000 | 0.343018 | 0.000358
  1 | 563.348121000 | 563.347864 | 0.347900 | 0.000221
```

### Two Days

After almost two days the sub-second precision has dramatically dropped - the granularity is now 1/16th of a second (62.5ms).

```none
num | monotonic_ns()  | monotonic()  | fraction | offset
  1 | 149621.908317000 | 149621.868134 | 0.875000 | 0.033317
179 | 149621.929066000 | 149621.939659 | 0.937500 | -0.008434

584 | 149621.993094000 | 149622.001648 | 0.000000 | 0.993094
663 | 149622.057075000 | 149622.058868 | 0.062500 | -0.005425
663 | 149622.121106000 | 149622.130394 | 0.125000 | -0.003894
662 | 149622.185034000 | 149622.192383 | 0.187500 | -0.002466
663 | 149622.249074000 | 149622.249603 | 0.250000 | -0.000926
663 | 149622.313108000 | 149622.306824 | 0.312500 | 0.000608
472 | 149622.360060000 | 149622.383118 | 0.375000 | -0.014940
581 | 149622.424027000 | 149622.430801 | 0.437500 | -0.013473
663 | 149622.488075000 | 149622.497559 | 0.500000 | -0.011925
663 | 149622.552114000 | 149622.559547 | 0.562500 | -0.010386
662 | 149622.616032000 | 149622.621536 | 0.625000 | -0.008968
663 | 149622.680073000 | 149622.688293 | 0.687500 | -0.007427
663 | 149622.744107000 | 149622.750282 | 0.750000 | -0.005893
660 | 149622.808095000 | 149622.812271 | 0.812500 | -0.004405
582 | 149622.872057000 | 149622.869492 | 0.875000 | -0.002943
663 | 149622.936033000 | 149622.941017 | 0.937500 | -0.001467

663 | 149623.000109000 | 149623.003006 | 0.000000 | 0.000109
495 | 149623.049088000 | 149623.060226 | 0.062500 | -0.013412
```

The loop is typically running over 600 times before `time.monotonic()` changes. This is because only 4 bits are left for the fractional component of the time.

# CLUE Sensor Plotter in CircuitPython

## Going Further

## Ideas for Areas to Explore

- Add a proper finger sensor for plotting pulses: [Adafruit Forums: Get a CLUE about your Pulse :-)](https://forums.adafruit.com/viewtopic.php?f=19&t=162765 "Adafruit Forums: Get a CLUE about your Pulse :-)")
- Add more plotting modes:
  - min/avg/max per second/minute/hour would be useful for long-term monitoring.
  - show multiple plots on two y axis or two graphs, e.g. temperature and humidity.

- Add some supression/pause between button press and volume source to prevent the physical impact being plotted.
- Add x axis tick labels with time values and investigate the feasibility of a true oscilloscope-style output with a timebase set by the user.
- Make an altimeter and vertical speed indicator with a plot mode and an instantaneous large-font display. cf. [CLUE Altimeter](https://learn.adafruit.com/clue-altimeter "Adafruit Learn: CLUE Altimeter"), published after this guide was created.
- Look at the new high performance [ulab library](https://learn.adafruit.com/ulab-crunch-numbers-fast-with-circuitpython "Adafruit Learn: ulab: Crunch Numbers fast in CircuitPython") for number crunching which could be used to post-process the sensor data.
- Enhance the code to also work on the [Circuit Playground Bluefruit](https://www.adafruit.com/product/4333 "Adafruit: Circuit Playground Bluefruit - Bluetooth Low Energy") with the [Circuit Playground TFT Gizmo](https://www.adafruit.com/product/4367 "Adafruit: Circuit Playground TFT Gizmo - Bolt-on Display + Audio Amplifier") LCD screen.
- Test the output to Mu particularly on the three channel plots and look at how to tailor the data sent to Mu.
- Improve the performance of plotting by replacing the slow undrawing technique with a clear screen implemented using the new, fast [displayio Bitmap fill()](https://circuitpython.readthedocs.io/en/latest/shared-bindings/displayio/#displayio.Bitmap.fill "CircuitPython Docs: displayio.Bitmap.fill") method.

## Related Projects

- [Adafruit Learn: EZ Make Oven](https://learn.adafruit.com/ez-make-oven/overview "Adafruit Learn: EZ Make Oven") - a PyPortal oven controller with graphing usign `displayio` in CircuitPython.
- [Adafruit Learn: Make It Plot](https://learn.adafruit.com/make-it-graph-plot "Adafruit Learn: Make It Plot") - plotting in Mu editor.
- [Adafruit Learn: Your Pulse Displayed with NeoPixels](https://learn.adafruit.com/pulse-sensor-displayed-with-neopixels "Adafruit Learn: Your Pulse Displayed with NeoPixels") - another take on pulse representation.
- [Adafruit Blog: APP UPDATE: Bluefruit Playground v1.3.0 with CLUE support](https://blog.adafruit.com/2020/03/20/app-update-bluefruit-playground-v1-3-0-with-clue-support/ "Adafruit Blog: APP UPDATE: Bluefruit Playground v1.3.0 with CLUE support") (for iOS) - this has plotting capabilities.

## Further Reading

- [Arduino to CircuitPython: Working with Numbers](https://learn.adafruit.com/arduino-to-circuitpython/working-with-numbers "Adafruit Learn: Arduino to CircuitPython: Working with Numbers")
- [Computerphile: Floating Point Numbers](https://www.youtube.com/watch?v=PZRI1IfStY0 "YouTube: Computerphile: Floating Point Numbers") (YouTube)
- [What Every Computer Scientist Should Know About Floating-Point Arithmetic by David Goldberg](https://dl.acm.org/doi/pdf/10.1145/103162.103163 "What Every Computer Scientist Should Know About Floating-Point Arithmetic by David Goldberg")
- [Nvidia blog: What’s the Difference Between Single-, Double-, Multi- and Mixed-Precision Computing?](https://blogs.nvidia.com/blog/2019/11/15/whats-the-difference-between-single-double-multi-and-mixed-precision-computing/ "Nvidia blog: What’s the Difference Between Single-, Double-, Multi- and Mixed-Precision Computing?")
- [Python 3 Docs: Floating Point Arithmetic: Issues and Limitations](https://docs.python.org/3/tutorial/floatingpoint.html "Python 3 Docs: Floating Point Arithmetic: Issues and Limitations")
- [Philips Medical Systems: Understanding Pulse Oximetry SpO2 Concepts](http://incenter.medical.philips.com/doclib/enc/fetch/586262/586457/Understanding_Pulse_Oximetry.pdf%3Fnodeid%3D586458%26vernum%3D2 "Philips Medical Systems: Understanding Pulse Oximetry SpO2 Concepts")
- [Medical Electronics: Pulse Oximetry by Dr Neil Townsend](https://www.robots.ox.ac.uk/~neil/teaching/lectures/med_elec/notes6.pdf "Medical Electronics: Pulse Oximetry by Dr Neil Townsend, 2001, Oxford University")
- [Slides to accompany Chapter 8: Software Testing](https://www.slideshare.net/software-engineering-book/ch8testing "Slides to accompany Chapter 8: Software Testing of Software Engineering by Ian Sommerville") of [Software Engineering (10th edition) by Ian Sommerville](http://iansommerville.com/software-engineering-book/videos/software-engineering/ "Software Engineering (10th edition) by Ian Sommerville")
- [Visual Paradigm's UML Tutorial: Class diagrams](https://www.visual-paradigm.com/guide/uml-unified-modeling-language/uml-class-diagram-tutorial/ "Visual Paradigm's UML Tutorial: Class diagrams")
- **pyreverse** , part of **pylint** distribution, can generate simple class diagrams from Python code. The diagrams in this guide were created with [https://app.diagrams.net/](https://app.diagrams.net/), formerly known as **draw.io.**

Featured
### Pulse Sensor Amped

[Pulse Sensor Amped](https://www.adafruit.com/product/1093)
Pulse Sensor Amped is a greatly improved version of the original Pulse Sensor, a plug-and-play heart-rate sensor for Arduino and Arduino compatibles. It can be used by students, artists, athletes, makers, and game & mobile developers who want to easily incorporate live heart-rate data into...

In Stock
[Buy Now](https://www.adafruit.com/product/1093)
[Related Guides to the Product](https://learn.adafruit.com/products/1093/guides)
![Kit Shot of the Pulse Sensor Amped with Included Components.](https://cdn-shop.adafruit.com/640x480/1093-09.jpg)

Featured
### Circuit Playground Bluefruit - Bluetooth® Low Energy

[Circuit Playground Bluefruit - Bluetooth® Low Energy](https://www.adafruit.com/product/4333)
 **Circuit Playground Bluefruit** is our third board in the Circuit Playground series, another step towards a perfect introduction to electronics and programming. We've taken the popular Circuit Playground Express and made it even better! Now the main chip is an nRF52840...

In Stock
[Buy Now](https://www.adafruit.com/product/4333)
[Related Guides to the Product](https://learn.adafruit.com/products/4333/guides)
![shot of a Black woman's neon-green manicured hand holding up a Circuit Playground Bluefruit glowing rainbow LEDs.](https://cdn-shop.adafruit.com/640x480/4333-11.jpg)

Featured
### Circuit Playground TFT Gizmo - Bolt-on Display + Audio Amplifier

[Circuit Playground TFT Gizmo - Bolt-on Display + Audio Amplifier](https://www.adafruit.com/product/4367)
Extend and expand your Circuit Playground projects with a bolt on TFT Gizmo that lets you add a lovely color display in a sturdy and reliable fashion. This PCB looks just like a round TFT breakout but has permanently affixed M3 standoffs that act as mechanical and electrical...

In Stock
[Buy Now](https://www.adafruit.com/product/4367)
[Related Guides to the Product](https://learn.adafruit.com/products/4367/guides)
![Hand pressing buttons on circuit playground, then turning over to show TFT gizmo display an image of a friendly robot or snake](https://cdn-shop.adafruit.com/product-videos/640x480/4367-05.jpg)


## Guide Products

### Adafruit CLUE - nRF52840 Express with Bluetooth® LE

[Adafruit CLUE - nRF52840 Express with Bluetooth® LE](https://www.adafruit.com/product/4500)
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 projects that have a small screen and a lot of sensors. To make it compatible with existing projects, we made...

In Stock
[Buy Now](https://www.adafruit.com/product/4500)
[Related Guides to the Product](https://learn.adafruit.com/products/4500/guides)
### USB cable - USB A to Micro-B

[USB cable - USB A to Micro-B](https://www.adafruit.com/product/592)
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 microcontroller

Approximately 3 feet / 1 meter long

In Stock
[Buy Now](https://www.adafruit.com/product/592)
[Related Guides to the Product](https://learn.adafruit.com/products/592/guides)
### Adafruit Feather M4 Express - Featuring ATSAMD51

[Adafruit Feather M4 Express - Featuring ATSAMD51](https://www.adafruit.com/product/3857)
It's what you've been waiting for, the Feather M4 Express featuring ATSAMD51. This Feather is fast like a swift, smart like an owl, strong like a ox-bird (it's half ox, half bird, OK?) This feather is powered by our new favorite chip, the **ATSAMD51J19** -&nbsp; with...

In Stock
[Buy Now](https://www.adafruit.com/product/3857)
[Related Guides to the Product](https://learn.adafruit.com/products/3857/guides)
### Basic Multi-Color Micro SMT Test Hooks (6-pack)

[Basic Multi-Color Micro SMT Test Hooks (6-pack)](https://www.adafruit.com/product/4322)
Testing and debugging is a challenging step in board manufacturing. It can be super tricky on a fully populated PCB. So how can you test connections&nbsp;when you have an assortment of components blocking your&nbsp;[probe](https://www.adafruit.com/product/1528)?

These...

In Stock
[Buy Now](https://www.adafruit.com/product/4322)
[Related Guides to the Product](https://learn.adafruit.com/products/4322/guides)
### Pulse Sensor Amped

[Pulse Sensor Amped](https://www.adafruit.com/product/1093)
Pulse Sensor Amped is a greatly improved version of the original Pulse Sensor, a plug-and-play heart-rate sensor for Arduino and Arduino compatibles. It can be used by students, artists, athletes, makers, and game & mobile developers who want to easily incorporate live heart-rate data into...

In Stock
[Buy Now](https://www.adafruit.com/product/1093)
[Related Guides to the Product](https://learn.adafruit.com/products/1093/guides)
### Circuit Playground Bluefruit - Bluetooth® Low Energy

[Circuit Playground Bluefruit - Bluetooth® Low Energy](https://www.adafruit.com/product/4333)
 **Circuit Playground Bluefruit** is our third board in the Circuit Playground series, another step towards a perfect introduction to electronics and programming. We've taken the popular Circuit Playground Express and made it even better! Now the main chip is an nRF52840...

In Stock
[Buy Now](https://www.adafruit.com/product/4333)
[Related Guides to the Product](https://learn.adafruit.com/products/4333/guides)
### Circuit Playground TFT Gizmo - Bolt-on Display + Audio Amplifier

[Circuit Playground TFT Gizmo - Bolt-on Display + Audio Amplifier](https://www.adafruit.com/product/4367)
Extend and expand your Circuit Playground projects with a bolt on TFT Gizmo that lets you add a lovely color display in a sturdy and reliable fashion. This PCB looks just like a round TFT breakout but has permanently affixed M3 standoffs that act as mechanical and electrical...

In Stock
[Buy Now](https://www.adafruit.com/product/4367)
[Related Guides to the Product](https://learn.adafruit.com/products/4367/guides)

## Related Guides

- [Adafruit Feather M4 Express](https://learn.adafruit.com/adafruit-feather-m4-express-atsamd51.md)
- [Adafruit Circuit Playground Bluefruit](https://learn.adafruit.com/adafruit-circuit-playground-bluefruit.md)
- [Adafruit Circuit Playground TFT Gizmo](https://learn.adafruit.com/adafruit-tft-gizmo.md)
- [Introducing Adafruit CLUE](https://learn.adafruit.com/adafruit-clue.md)
- [Crawling Animatronic Hand](https://learn.adafruit.com/crawling-hand-with-cpx-and-makecode.md)
- [Circuit Playground Bluefruit BLE Heart Rate Pendant with CircuitPython](https://learn.adafruit.com/ble-heart-rate-display-pendant.md)
- [Crawling Baby Sea Turtle Robot](https://learn.adafruit.com/baby-turtle-makecode.md)
- [FlappyBird Game for NeoTrellis M4 in CircuitPython](https://learn.adafruit.com/circuitpython-neotrellism4-flappybird.md)
- [Robotic Xylophone with Adafruit Grand Central](https://learn.adafruit.com/robotic-xylophone-with-circuit-python.md)
- [Visual Studio Code for Education... and CircuitPython](https://learn.adafruit.com/visual-studio-code-for-education-and-circuitpython.md)
- [CircuitPython Powered AT Hand-Raiser](https://learn.adafruit.com/at-hand-raiser.md)
- [Circuit Playground-O-Phonor](https://learn.adafruit.com/circuit-playground-o-phonor.md)
- [Introduction to the Mastodon API using CircuitPython](https://learn.adafruit.com/intro-to-mastodon-api-circuitpython.md)
- [Professor Bubbleton’s Breathing Head in a Jar](https://learn.adafruit.com/professor-bubbleton-s-breathing-head-in-a-jar.md)
- [Crickit Powered Mini Chair Swing Ride!](https://learn.adafruit.com/mini-chair-swing-ride.md)
- [Program CircuitPython USB Devices with iPhone & iPad](https://learn.adafruit.com/use-circuitpython-devices-with-iphone-ipad.md)
- [CircuitPython BLE Multi-Temperature Monitoring](https://learn.adafruit.com/circuitpython-multi-temperature-ble-monitoring.md)
- [Getting Started with CircuitPython and Bluetooth Low Energy](https://learn.adafruit.com/circuitpython-nrf52840.md)
- [Make It Shake, Rattle, and Roll: Accelerometer Use](https://learn.adafruit.com/make-it-shake-rattle-and-roll.md)
