Let's take a look at the code to see what everything is doing, and so that you can calibrate your artwork to work in your location.

There are several important changes to make, so don't skip this section! 

First, we import all the necessary libraries.

import time
import board
import neopixel
from adafruit_clue import clue
import adafruit_fancyled.adafruit_fancyled as fancy
import displayio
from adafruit_display_text import label
from adafruit_bitmap_font import bitmap_font

The next few sections are where we'll do most of our customization. 

num_leds = 79 # number of LEDs in your strip
timeToCheck = 100 # set the amount of time between sensor checks. 7800 is approx. 1 hour

Change num_leds to reflect the number of LEDs in your strip. Mine has 79 lights.

If you have fewer than 79 lights this will break the code, temporarily!

Don't worry! We'll fix it in the Mapping Colors to Pixels section below.

The timeToCheck variable allows you to set the length of time between sensor checks. This isn't counting seconds, it's counting up each time the code runs. I used a stopwatch to determine that my CLUE board counts to around 7800 every hour.

In general, barometers should be checked every hour to three hours to read a meaningful change in pressure. For testing purposes you can set it to around 20 - 30 to read the sensor and update the strip every few seconds. For actual usage, I am only checking data every 3 hours so I've set this to 23400 (7800 x 3).

Barometer or Thermometer? 

The CLUE's onboard barometric pressure sensor also has a temperature sensor built in. That means it's easy peezy to change this project into a thermometer instead of a barometer just by changing a couple lines of code.

I've included sample code for both types of project. Comment out the Barometer section and uncomment the Thermometer section if you'd prefer to build a temperature visualizer.

Setting Threshold Numbers

This section is also where you customize the thresholds for color visualization. Since barometers vary so much by where they're located, the best thing to do is to take a few readings at your location to find low and high pressure, and adjust these numbers as needed. My house is at around 1300 feet of elevation in a moderate weather zone in Northern California. I took a reading on a few nice sunny days to get a high threshold (around 972), and on a day when a thunderstorm was predicted to get a low threshold (around 945), then divided up my numbers into an even spread.

A low desert in Arizona or an island in the middle of the ocean would have pretty different thresholds. Barometers get less reliable as elevation goes up, and above 5000 feet they're pretty useless, so if you live in the mountains, make a thermometer instead.

The units for pressure are in hectopascals (hPa) or for temperature are in C. 

# Barometer or Thermometer? Uncomment the section you want to use

# BAROMETER RANGES (hPa)
# set desired reading range -- the NeoPixel palette choice will be determined by these thresholds
deviceType = 0  
min_reading = 965
med_reading = 965
high_reading= 970
max_reading = 975

"""
# THERMOMETER RANGES (C)
# set desired temperature range -- the NeoPixel palette choice will be determined by these thresholds
deviceType = 1
min_reading = 25
med_reading = 27
high_reading= 31
max_reading = 33
"""

#get an initial sensor reading
if deviceType ==0:
    reading = clue.pressure
else:   
    reading = clue.temperature

Screen Images

Next we set up and draw the .bmp images. We've included a submarine image for when the pressure is sinking, and a hot air balloon image for when the pressure is rising. You can add your own custom images if you'd like. Make the images 240x240 pixels, and save as .bmp with 16 bits. It's probably easiest to name them rising.bmp and sinking.bmp -- that way you don't need to update the code with different filenames, you can just replace our files with yours and it should work fine.

clue.display.brightness = 0.8
clue_display = displayio.Group(max_size=4)

# draw the rising image
rising_file = open("rising.bmp", "rb")
rising_bmp = displayio.OnDiskBitmap(rising_file)
rising_sprite = displayio.TileGrid(rising_bmp, pixel_shader=displayio.ColorConverter())
clue_display.append(rising_sprite)

# draw the sinking image
sinking_file = open("sinking.bmp", "rb")
sinking_bmp = displayio.OnDiskBitmap(sinking_file)
sinking_sprite = displayio.TileGrid(sinking_bmp, pixel_shader=displayio.ColorConverter())
clue_display.append(sinking_sprite)

Further down, we set up the images and the text using the display.io library. This is where we can customize the color and position of the text readouts that tell us the current data.

Change the position of the text by changing reading_label.x and reading_label.yin each text section.

# Create text
# first create the group
text_group = displayio.Group(max_size=5, scale=1)
# Make a label
reading_font = bitmap_font.load_font("/font/RacingSansOne-Regular-29.bdf")
reading_font.load_glyphs("0123456789ADSWabcdefghijklmnopqrstuvwxyz:!".encode('utf-8'))
reading_label = label.Label(reading_font, color=0xffffff, max_glyphs=15)
reading_label.x = 10
reading_label.y = 24
text_group.append(reading_label)

reading2_label = label.Label(reading_font, color=0xdaf5f4, max_glyphs=15)
reading2_label.x = 10
reading2_label.y = 54
text_group.append(reading2_label)

reading3_label = label.Label(reading_font, color=0x4f3ab1, max_glyphs=15)
reading3_label.x = 10
reading3_label.y = 84
text_group.append(reading3_label)

timer_label = label.Label(reading_font, color=0x072170, max_glyphs=15)
timer_label.x = 10
timer_label.y = 114
text_group.append(timer_label)

Customizing Color Palettes

The next section is where we set up our color palettes. I'm using hex codes for each color, but you can also use CRGB values or CHSV, or a variety of other options. Check out the FancyLED guide for more info about how this works.

If you don't care how it works and just want to change the colors, that's okay too! Use an online hex code picker like this one. You can put as few or as many different colors in each palette as you'd like. Just select your color, copy the 6-digit hex code at the top of the screen, and place it into the code, copying the current format: i.e. for pure green, the hex code is #00FF00, so you'd use 0x00ff00to express green.

These palettes are set up to give a few different shades of each color in the rainbow. The ice palette is mainly deep blues and purples -- I was trying to get the strip to express a color as close to blacklight / uv as possible to make the uv pigments in the moss really pop.

# Define color Palettes
waterPalette = [
            0x00d9ff,
            0x006f82,
            0x43bfb9,
            0x0066ff]
icePalette = [
            0x8080FF,
            0x8080FF,
            0x8080FF,
            0x0000FF,
            0xC88AFF]
sunPalette = [
            0xffaa00,
            0xffdd00,
            0x7d5b06,
            0xfffca8]
firePalette = [
            0xff0000,
            0xff5500,
            0x8a3104,
            0xffaa00 ]
forestPalette = [
            0xccffa8,
            0x69f505,
            0x05f551,
            0x2c8247]

#set up default initial palettes, just for startup
palette = forestPalette
palette2 = waterPalette
palette3 = icePalette

The next section sets up the LED strip on pin A4, which corresponds to #2 on the CLUE board. We set brightness to 1.0 which is fully bright, since we will be controlling the brightness through FancyLED.

If you want to make the whole thing dimmer overall, you can lower the brightness here. Otherwise, just choose darker colors for your palettes (closer to the black side of the color layout) to make each individual color darker.

# Declare a NeoPixel object on pin A4 with num_leds pixels, no auto-write.
# Set brightness to max because we'll be using FancyLED's brightness control.
pixels = neopixel.NeoPixel(board.A4, num_leds, brightness=1.0,
                           auto_write=False)

Main Code Loop

That's it for setup. The main code loop starts next, with while true:

Buttons

The first thing I've done is set up toggles for the two buttons. Button A will turn the NeoPixels and the CLUE screen on and off each time it's pressed. Button B turns only the screen on and off, leaving the pixels on.

I've done this by setting the brightness of the pixels and / or the screen to 0, so they don't "forget" what they were showing when you turn them back up again.

Assigning Color Palettes

The next section correlates the palettes we set up with the pressure or temperature thresholds we made way back at the beginning of the code. Choose which palette you'd like to associate with which pressure reading in this section. 

#assign color palette to NeoPixel section 1 based on the current reading reading
    if reading1 < min_reading:
        palette = firePalette
    elif reading1 > min_reading and reading1 < med_reading:
        palette = sunPalette
    elif reading1 > med_reading and reading1 < high_reading:
        palette = forestPalette
    elif reading1 > high_reading and reading1 < max_reading:
        palette = waterPalette
    else:
        palette = icePalette

Mapping Colors to Pixels

Here is where we assign specific colors to show in the area of the artwork we want. I've set up 9 different "zones" around my frame, so that I can create a reflected gradient layout to visualize the colors. This is maybe a leeeetle bit unnecessarily complex, but, that's how you can tell it's Art. 

Go find the numbers you wrote down while you were reading the How it Works page, and plug them into the range() numbers in the code.

Use the ending number from one zone as the starting number for the next zone, i.e.:

for i in range (0, 16): ...

for i in range (16, 23): ...

for i in range (23, 31): ...

# Map colors to pixels. Adjust range numbers to light up specific pixels. This configuration
#  maps to a reflected gradient, with pixel 0 in the upper left corner
#  Load each pixel's color from the palette using an offset, run it
#  through the gamma function, pack RGB value and assign to pixel.
    for i in range(23, 31):  #center right -- present moment
        color = fancy.palette_lookup(palette, offset + i / num_leds)
        color = fancy.gamma_adjust(color, brightness=0.25)
        pixels[i] = color.pack()

    for i in range(63, 71): #center left -- present moment
        color = fancy.palette_lookup(palette, offset + i / num_leds)
        color = fancy.gamma_adjust(color, brightness=0.25)
        pixels[i] = color.pack()

    for i in range(16, 23): #top mid right  -- 1 cycle ago
        color = fancy.palette_lookup(palette2, offset + i / num_leds)
        color = fancy.gamma_adjust(color, brightness=0.25)
        pixels[i] = color.pack()

    for i in range(71, 78): #top mid left  -- 1 cycle ago
        color = fancy.palette_lookup(palette2, offset + i / num_leds)
        color = fancy.gamma_adjust(color, brightness=0.25)
        pixels[i] = color.pack()

    for i in range(31, 38): #bottom mid right  -- 1 cycle ago
        color = fancy.palette_lookup(palette2, offset + i / num_leds)
        color = fancy.gamma_adjust(color, brightness=0.25)
        pixels[i] = color.pack()

    for i in range(56, 63): #bottom mid left  -- 1 cycle ago
        color = fancy.palette_lookup(palette2, offset + i / num_leds)
        color = fancy.gamma_adjust(color, brightness=0.25)
        pixels[i] = color.pack()

    for i in range(0, 16): #top right  -- 2 cycles ago
        color = fancy.palette_lookup(palette3, offset + i / num_leds)
        color = fancy.gamma_adjust(color, brightness=0.25)
        pixels[i] = color.pack()

    for i in range(77, 79): #top left  -- 2 cycles ago
        color = fancy.palette_lookup(palette3, offset + i / num_leds)
        color = fancy.gamma_adjust(color, brightness=0.25)
        pixels[i] = color.pack()

    for i in range(38, 56): #bottom  -- 2 cycles ago
        color = fancy.palette_lookup(palette3, offset + i / num_leds)
        color = fancy.gamma_adjust(color, brightness=0.25)
        pixels[i] = color.pack()

Reading the Sensor & Showing the Data

Finally, we write the sensor readings to the screen, then check to see if it's time to read the sensor again. If it is, we do so, and update all the reading variables: reading2 (data from the last check) becomes reading3 (data from two checks ago), reading (current data) becomes reading2, and a new sensor reading is taken to replace the current data.

reading_label.text = "Now  {:.1f}".format(reading1)
    reading2_label.text = "Last  {:.1f}".format(reading2)
    reading3_label.text = "Prev  {:.1f}".format(reading3)
    timer_label.text = "{}".format(counter)
    clue.display.show(clue_display)

    #Is it time to update?
    if counter > timeToCheck:
        #This moves the current data to the "1 hour old" section of pixels and the "1 hour old" data
        #to the "2 hours old" section of pixels
        palette3 = palette2
        palette2 = palette
        reading3 = reading2
        reading2 = reading1
        reading1 = reading
        #take a new sensor reading and reset the counter
        if deviceType == 0:
            reading = clue.pressure
        else:
            reading = clue.temperature
        counter = 0
        #if reading is rising, show rising image and position text at the bottom
        if reading1 > reading2:
            sinking_sprite.x = 300
            reading_label.y = 134
            reading2_label.y = 164
            reading3_label.y = 194
            timer_label.y = 224
        #if reading is falling, show sinking image and position text at the top
        elif reading2 < reading3:  #reading is falling
            sinking_sprite.x = 0
            reading_label.y = 24
            reading2_label.y = 54
            reading3_label.y = 84
            timer_label.y = 114
    #otherwise keep counting up
    else:
        counter = counter + 1

This guide was first published on May 06, 2020. It was last updated on May 06, 2020.

This page (Customizing Your Code) was last updated on May 10, 2021.

Text editor powered by tinymce.