Simple way to display a variable on Unicorn Hat HD

Hi,

I’ve got my BME680 set up on a PHAT STACK with my Unicorn HAT HD.

I want to combine the two and display the air quality reading on my Unicorn Hat HD. Typically the number will be a percentage say 95%.

Is there a simple way to achieve this as I’ve searched through various scripts and can only see text being displayed.

Thanks in advance

Scott

I’ll be tackling something like that down the road, so it will be interesting to see how you do this. Presently I show a scrolling message on my sense hat led matrix. Day, Date, Time, temperature, humidity, pressure, etc. I do it al in python. Here is a bit of my code for the temperature.

    t = sense.get_temperature()
    t = round(t)
          
    if t <= 0: 
        tc = [w, w, 255]  # blue
    elif t > 0 and t < 13:
        tc = [255, 255, w]  # yellow
    elif t >= 13 and t <= 27:
        tc = [w, 255, w]  # green
    elif t > 27:
        tc = [255, w, w]  # red                 
    msg = "and %sc" % (t)
    sense.show_message(msg, scroll_speed=s, text_colour=tc)

Basically I make the temperature a variable “t”, then display t in my message. I also change the color of the text based on the temperature reading. Blue for cold Red for Hot etc. I had a quick look at the Unicorn Hat HD code examples but didn’t see anything similar. At some point I want to replace my sense hat with a BME680 and Unicorn Hat HD. I’m not sure this will help you or not?

I could use similar but the Unicorn Hat HD doesn’t have any simple command to do this. Perhaps the answer lies in the buffer, I can see reference to this but no actual instructions?

I see print commands in a lot of the examples
print("""Simple
Does that actually show the text on the Unicorn Hat?

Hi Thanks…

Alas, I don’t think there is an option to print strings etc. I kind of understand this given that the hardware is basically an array of 16 x 16 LED’s so I think the buffer (which isn’t mentioned in the documentation) might be the answer. I’m still learning to code Python (by copy and pasting other bits of scripts too see what works) so i’m limited in my skills.

If anybody knows a simple way to print a variable onto the HAT HD then let me know. I’d be grateful.

Thanks alpha

Scott

That’s how I started when I first used Python. A lot of clip and past, then modify by trial and error. I don’t have a Unicorn Hat to play with. Not yet anyway. It’s pretty easy on my Sense Hat, lots of documentation. I still had to look at a lot of examples though. The Pi foundation wanted kids to program for it so they went to great lengths to get the documentation correct and easy to follow.

From the looks of this it can be done, https://www.youtube.com/watch?v=V3gpfbWk168
Getting a link to the code used may be the tricky part though.

There are a bunch of examples here, https://www.raspberrypi.org/forums/viewtopic.php?t=98666
In one I see,

print (str1 + str(x1) + ' ' + str(y1))

Not exactly sure what it does, x1 and y1 look like variables though?

(badboybubby) Alas, I don’t think there is an option to print strings etc

Isn’t that what the text.py example essentially does?

(alphanumeric) Not exactly sure what it does, x1 and y1 look like variables though?

x1 and y1 are integers in their code. print() only takes strings, so they wrap x1 and y1 in str() to convert them to strings before sending them to print(). This would be how you’d convert integer or float values from a sensor into a string for either printing or sending to some sort of text-display function which uses strings.

I wrote a script to scroll text across a home made 7x5 matrix of standard LEDs a while ago and it’s a bit mind-bending. That said, the UHHD example looks far more simple than what I was doing so maybe I was doing it wrong.

I had a feeling it was something like that. I’ve come a long way in python but I still have a lot to learn. And I have to admit to only taking a very brief look at that code. Had some other things (distractions) going on here at home lol.

Generally you should use str.format() as per this documentation and examples: https://docs.python.org/3.4/library/string.html#formatspec

It’s extremely powerful/complex but if you learn just the basics you’ll find it much easier to do things like padding strings, displaying to a number of decimal places, outputting numbers with leading zeros and lots of other common data display tasks.

Like displaying an 8bit binary number:

>>> "{:08b}".format(121)
'01111001'

Or displaying a decimal number with leading zeros, always showing 3 digits before and 2digits after the decimal place:

>>> "{:06.2f}".format(21.231)
'021.23'
>>> "{:06.2f}".format(121.231)
'121.23'

In this case the 0 in 06 means “add leading zeros”, the 6 is the total length of the result, and the 2 is the number of digits that appear after the decimal point.

Then you can refer to stuff by name:

>>> "{a} {c} {b}".format(a="Ayyy", b="Beee", c="See!")
'Ayyy See! Beee'

And so on and so forth!

Good morning Phil, once again you are the purveyor of assume information. Now I just have to clip and past that info somewhere where I can find it again latter lol. =)

I think I might just display an image to show the air quality as looking at that code you showed us I’ll never figure out how to display the BME680 air quality as a decimal on my unicorn hat hd.😕

I’ve included the script I’m using which is one of yours. I modified this adding Blinkt instructions from line 103 down.

ideally I wanted to do away with the traffic light system and replace it with the actual air quality reading on the Unicorn HAT HD.

Perhaps I’m missing something or maybe the display isn’t really designed for displaying variables (easily)


import bme680
import random
import blinkt
import time

blinkt.set_clear_on_exit()

print("""Estimate indoor air quality

Runs the sensor for a burn-in period, then uses a 
combination of relative humidity and gas resistance
to estimate indoor air quality as a percentage.

Press Ctrl+C to exit

""")

sensor = bme680.BME680()

# These oversampling settings can be tweaked to 
# change the balance between accuracy and noise in
# the data.

sensor.set_humidity_oversample(bme680.OS_2X)
sensor.set_pressure_oversample(bme680.OS_4X)
sensor.set_temperature_oversample(bme680.OS_8X)
sensor.set_filter(bme680.FILTER_SIZE_3)
sensor.set_gas_status(bme680.ENABLE_GAS_MEAS)

sensor.set_gas_heater_temperature(320)
sensor.set_gas_heater_duration(150)
sensor.select_gas_heater_profile(0)

# start_time and curr_time ensure that the 
# burn_in_time (in seconds) is kept track of.

start_time = time.time()
curr_time = time.time()
burn_in_time = 300 #should be 300 which is 300/60=5 minutes

burn_in_data = []

try:
    # Collect gas resistance burn-in values, then use the average
    # of the last 50 values to set the upper limit for calculating
    # gas_baseline.
    print("Collecting gas resistance burn-in data for 5 mins\n")
    

    while curr_time - start_time < burn_in_time:
        curr_time = time.time()
        if sensor.get_sensor_data() and sensor.data.heat_stable:
            gas = sensor.data.gas_resistance
            burn_in_data.append(gas)
            print("Gas: {0} Ohms".format(gas))
            blinkt.clear()
            blinkt.set_pixel(3, 255, 0, 1.0)
            blinkt.set_pixel(4, 255, 0, 1.0)
            blinkt.show()
            time.sleep(1)

    gas_baseline = sum(burn_in_data[-50:]) / 50.0
    

    # Set the humidity baseline to 40%, an optimal indoor humidity.
    hum_baseline = 40.0

    # This sets the balance between humidity and gas reading in the 
    # calculation of air_quality_score (25:75, humidity:gas)
    hum_weighting = 0.25

    print("Gas baseline: {0} Ohms, humidity baseline: {1:.2f} %RH\n".format(gas_baseline, hum_baseline))

    while True:
        if sensor.get_sensor_data() and sensor.data.heat_stable:
            gas = sensor.data.gas_resistance
            gas_offset = gas_baseline - gas

            hum = sensor.data.humidity
            hum_offset = hum - hum_baseline

            # Calculate hum_score as the distance from the hum_baseline.
            if hum_offset > 0:
                hum_score = (100 - hum_baseline - hum_offset) / (100 - hum_baseline) * (hum_weighting * 100)

            else:
                hum_score = (hum_baseline + hum_offset) / hum_baseline * (hum_weighting * 100)

            # Calculate gas_score as the distance from the gas_baseline.
            if gas_offset > 0:
                gas_score = (gas / gas_baseline) * (100 - (hum_weighting * 100))

            else:
                gas_score = 100 - (hum_weighting * 100)

            # Calculate air_quality_score. 
            air_quality_score = hum_score + gas_score

            print("Gas: {0:.2f} Ohms,humidity: {1:.2f} %RH,air quality: {2:.2f}".format(gas, hum, air_quality_score))
            if air_quality_score >= 85:
		blinkt.clear()
		blinkt.set_all(0, 255, 0, 0.1)
		blinkt.show()
	    elif air_quality_score > 70 and air_quality_score < 85:
		blinkt.clear()
		blinkt.set_all(255, 255, 0, 0.1)
		blinkt.show()
	    else:
		blinkt.clear()
		blinkt.set_all(255, 0, 0, 0.1)
		blinkt.show()

            time.sleep(0.02)

except KeyboardInterrupt:
    pass```

Try this: I’ve taken the text.py example and cut it down a bit and turned it into a function called show_text. It takes a list called lines and scrolls the contents of each element in lines across the HAT in a different colour. I’ve got it working with a BME280, so if you hit any bumps let me know (I’ve spotted a few formatting errors, it wouldn’t surprise me if a few more are in there somewhere). I’m sure there’s more which should technically be done differently but baby steps.

  1. In a terminal run sudo pip install pillow to install some font stuff which is needed.

  2. Include a few more import statements at the top of your script:

     import colorsys
     import signal
     from sys import exit
     import unicornhathd
    
  3. Create a list called lines:

     lines = []
    
  4. Paste this function into your script on its own (not inside your try block):

     def show_text(lines):
         colours = [tuple([int(n * 255) for n in colorsys.hsv_to_rgb(x/float(len(lines)), 1.0, 1.0)]) for x in range(len(lines))]
         FONT = ("/usr/share/fonts/truetype/freefont/FreeSansBold.ttf", 12)
         width, height = unicornhathd.get_shape()
         text_x = width
         text_y = 2
         font_file, font_size = FONT
         font = ImageFont.truetype(font_file, font_size)
         text_width, text_height = width, 0
         for line in lines:
             w, h = font.getsize(line)
             text_width += w + width
             text_height = max(text_height,h)
         text_width += width + text_x + 1
         image = Image.new("RGB", (text_width,max(16, text_height)), (0,0,0))
         draw = ImageDraw.Draw(image)
         offset_left = 0
         for index, line in enumerate(lines):
             draw.text((text_x + offset_left, text_y), line, colours[index], font=font)
             offset_left += font.getsize(line)[0] + width
         for scroll in range(text_width - width):
             for x in range(width):
                     for y in range(height):
                             pixel = image.getpixel((x+scroll, y))
                             r, g, b = [int(n) for n in pixel]
                             unicornhathd.set_pixel(width-1-x, y, r, g, b)
             unicornhathd.show()
             time.sleep(0.01)
    
  5. Now, inside your while true loop wherever you want to show air_quality_score add it to element 0 of lines:

     lines[0] = ["{:.2f}".format(air_quality_score)]
    
  6. And tell the script to show this on the HAT by calling the show_text() function, giving it lines as the stuff it is meant to show:

     show_text(lines)
    

If you have more than one element in the lines list it’ll show each element one after the other but in different colours.

1 Like

brilliant, I’ll take a look tomorrow and see if I can add to my code to get what I want.

thanks again

Hi Shoe,

Nearly there, i used the code as described but get this error

lines[0] = ["{:.2f}".format(air_quality_score)]

IndexError: list assignment index out of range


(program exited with code: 1)
Press return to continue


import bme680
import random
import blinkt
import time
import colorsys
from sys import exit
import unicornhathd

blinkt.set_clear_on_exit()

print("""Estimate indoor air quality

Runs the sensor for a burn-in period, then uses a 
combination of relative humidity and gas resistance
to estimate indoor air quality as a percentage.

Press Ctrl+C to exit

""")

sensor = bme680.BME680()

lines = []

def show_text(lines):
    colours = [tuple([int(n * 255) for n in colorsys.hsv_to_rgb(x/float(len(lines)), 1.0, 1.0)]) for x in range(len(lines))]
    FONT = ("/usr/share/fonts/truetype/freefont/FreeSansBold.ttf", 12)
    width, height = unicornhathd.get_shape()
    text_x = width
    text_y = 2
    font_file, font_size = FONT
    font = ImageFont.truetype(font_file, font_size)
    text_width, text_height = width, 0
    for line in lines:
        w, h = font.getsize(line)
        text_width += w + width
        text_height = max(text_height,h)
    text_width += width + text_x + 1
    image = Image.new("RGB", (text_width,max(16, text_height)), (0,0,0))
    draw = ImageDraw.Draw(image)
    offset_left = 0
    for index, line in enumerate(lines):
        draw.text((text_x + offset_left, text_y), line, colours[index], font=font)
        offset_left += font.getsize(line)[0] + width
    for scroll in range(text_width - width):
        for x in range(width):
                for y in range(height):
                        pixel = image.getpixel((x+scroll, y))
                        r, g, b = [int(n) for n in pixel]
                        unicornhathd.set_pixel(width-1-x, y, r, g, b)
        unicornhathd.show()
        time.sleep(0.01)

# These oversampling settings can be tweaked to 
# change the balance between accuracy and noise in
# the data.

sensor.set_humidity_oversample(bme680.OS_2X)
sensor.set_pressure_oversample(bme680.OS_4X)
sensor.set_temperature_oversample(bme680.OS_8X)
sensor.set_filter(bme680.FILTER_SIZE_3)
sensor.set_gas_status(bme680.ENABLE_GAS_MEAS)

sensor.set_gas_heater_temperature(320)
sensor.set_gas_heater_duration(150)
sensor.select_gas_heater_profile(0)

# start_time and curr_time ensure that the 
# burn_in_time (in seconds) is kept track of.

start_time = time.time()
curr_time = time.time()
burn_in_time = 3 #should be 300 which is 300/60=5 minutes

burn_in_data = []

try:
    # Collect gas resistance burn-in values, then use the average
    # of the last 50 values to set the upper limit for calculating
    # gas_baseline.
    print("Collecting gas resistance burn-in data for 5 mins\n")
    

    while curr_time - start_time < burn_in_time:
        curr_time = time.time()
        if sensor.get_sensor_data() and sensor.data.heat_stable:
            gas = sensor.data.gas_resistance
            burn_in_data.append(gas)
            print("Gas: {0} Ohms".format(gas))
            blinkt.clear()
            blinkt.set_pixel(3, 255, 0, 1.0)
            blinkt.set_pixel(4, 255, 0, 1.0)
            blinkt.show()
            time.sleep(1)

    gas_baseline = sum(burn_in_data[-50:]) / 50.0
    

    # Set the humidity baseline to 40%, an optimal indoor humidity.
    hum_baseline = 40.0

    # This sets the balance between humidity and gas reading in the 
    # calculation of air_quality_score (25:75, humidity:gas)
    hum_weighting = 0.25

    print("Gas baseline: {0} Ohms, humidity baseline: {1:.2f} %RH\n".format(gas_baseline, hum_baseline))

    while True:
        if sensor.get_sensor_data() and sensor.data.heat_stable:
            gas = sensor.data.gas_resistance
            gas_offset = gas_baseline - gas

            hum = sensor.data.humidity
            hum_offset = hum - hum_baseline

            # Calculate hum_score as the distance from the hum_baseline.
            if hum_offset > 0:
                hum_score = (100 - hum_baseline - hum_offset) / (100 - hum_baseline) * (hum_weighting * 100)

            else:
                hum_score = (hum_baseline + hum_offset) / hum_baseline * (hum_weighting * 100)

            # Calculate gas_score as the distance from the gas_baseline.
            if gas_offset > 0:
                gas_score = (gas / gas_baseline) * (100 - (hum_weighting * 100))

            else:
                gas_score = 100 - (hum_weighting * 100)

            # Calculate air_quality_score. 
            air_quality_score = hum_score + gas_score

            print("Gas: {0:.2f} Ohms,humidity: {1:.2f} %RH,air quality: {2:.2f}".format(gas, hum, air_quality_score))
            
            lines[0] = ["{:.2f}".format(air_quality_score)]
            
            show_text(lines)
            
            
            
            if air_quality_score >= 85:
		blinkt.clear()
		blinkt.set_all(0, 255, 0, 0.1)
		blinkt.show()
	    elif air_quality_score > 70 and air_quality_score < 85:
		blinkt.clear()
		blinkt.set_all(255, 255, 0, 0.1)
		blinkt.show()
	    else:
		blinkt.clear()
		blinkt.set_all(255, 0, 0, 0.1)
		blinkt.show()

            time.sleep(0.02)

except KeyboardInterrupt:
    pass

Do you see where you declare lines = []?

I completely forgot that if there’s nothing in the list you can’t change element 0, because element 0 doesn’t exist yet. You could either use lines.append the very first time you use it but not the subsequent times, or just stick some sort of placeholder in there as element 0: lines = ["placeholder string"]

Looking at it again there’s a bunch of other stuff which could probably be fixed in that function but we’ll get it working first and then worry about that.

1 Like

I have to be honest and admit it’s getting above my head, I don’t really know what constitutes a placeholder string at this point. I mean can it be anything?

Thanks by the way

Yeah, anything at all, like I suggested you could just use "placeholder string" so it’s obvious in future that the value is just a placeholder. As soon as you put the air quality value into it it’ll get wiped over anyway.

1 Like

Hi, I still can’t get it to work, I think the code is beyond me. I’ll need to learn what placeholders are within strings before I can proceed.