How to display graph colors based on temperature? (Enviro - Python)

This is the display part of the default all-in-one-enviro-mini.py.

This changes the colour of the graph on the screen based on the temperature (from red to green to blue, with gradients), but the default/base colour seems to be red. So if nothing happens and the temperature is stable, the graph seems to always be red.

Would it be possible to make it so that if the temperature is below, say 15°C, the graph is blue, from 15°C to 25°C the graph is green, and only above 25°C the graph is red? (With of course the gradient colours when the temperature nears one of these thresholds.)

# Displays data and text on the 0.96" LCD
def display_text(variable, data, unit):
    # Maintain length of list
    values[variable] = values[variable][1:] + [data]
    # Scale the values for the variable between 0 and 1
    vmin = min(values[variable])
    vmax = max(values[variable])
    colours = [(v - vmin + 1) / (vmax - vmin + 1) for v in values[variable]]
    # Format the variable name and value
    message = "{}: {:.1f} {}".format(variable[:4], data, unit)
    logging.info(message)
    draw.rectangle((0, 0, WIDTH, HEIGHT), (255, 255, 255))
    for i in range(len(colours)):
        # Convert the values to colours from red to blue
        colour = (1.0 - colours[i]) * 0.6
        r, g, b = [int(x * 255.0) for x in colorsys.hsv_to_rgb(colour, 1.0, 1.0)]
        # Draw a 1-pixel wide rectangle of colour
        draw.rectangle((i, top_pos, i + 1, HEIGHT), (r, g, b))
        # Draw a line graph in black
        line_y = HEIGHT - (top_pos + (colours[i] * (HEIGHT - top_pos))) + top_pos
        draw.rectangle((i, line_y, i + 1, line_y + 1), (0, 0, 0))
    # Write the text at the top in black
    draw.text((0, 0), message, font=font, fill=(0, 0, 0))
    st7735.display(img)

This would greatly improve the temperature readability from, say, across the room. The colour would instantly show the range the temperature is in.
The thing is, my Python skills are near to non existent, so any help would be greatly appreciated.

Thanks!

1 Like

Ditto to the above. I’d much rather have it the way you describe. I don’t have the python skills to do it though.
I do believe the way it works now is the highest reading (regardless of what it is) is Red. And then it shifts color to Green then Blue for readings that are lower in value.
I swapped the colors around for Pressure so its Green on the highest reading and Red for lower readings. I changed the r, g, b to g, r, b. I have mine setup with 3 displays though, no having to cycle through them.
g, r, b = [int(x * 255.0) for x in colorsys.hsv_to_rgb(colour, 1.0, 1.0)

1 Like

Old topic,butI was looking for the same issue. I have mine setup on multiple screens,but basically thesame behaviour happens regardless how I set up my limits.
Another thing I experience is that if I touch the BME280 sensor graphs seem to work,colors change as they should, at least for a little while, then everything goes red again.
Just for fun I have deleted red from my limits colors,yet is still there :)
I will keep trying to find a solution but it is a shame that the graphs are not working as they should.

I have my sampling slowed down. My temperature and humidity graphs show about 13 minutes of data and pressure shows the last 2 hours. This lets me see trends, if say the pressure is rising of falling. And I get a color shift on my graphs.

@Equinoxe might want to post the code your using? I’m far from any kind of python expert, but it would make suggestions for possible changes easier. ;)

Here is my attempt at solving your problem. The range is for temperatures from 0 to 40 - we may get another HOT summer - rather that all this rain in the UK.

# Display2 - PicoGraphics Temperatures to colours 
# For 240x320 pixel display 
# Tony Goodhew 21 March 2024
import time

from picographics import PicoGraphics, DISPLAY_PICO_DISPLAY_2, get_buffer_size, PEN_RGB332
from pimoroni import RGBLED
# Reduced colours to save RAM
display = PicoGraphics(display=DISPLAY_PICO_DISPLAY_2, pen_type=PEN_RGB332, rotate=0)
display.set_backlight(0.8)
display.set_font("bitmap8") # Lower case included

led = RGBLED(6, 7, 8)
led.set_rgb(0,0,0)     # Turn RGBLED OFF

# ==== Board now setup ====
BLACK = display.create_pen(0, 0, 0)         #0

for t in range(0,41,1):    
    display.set_pen(BLACK)
    display.clear()
    if t < 21:
        rr = 0
        t2 = 20 - t
        gg = int(t / 20 * 255)
        bb = 255-gg      
    else:
        bb = 0
        t3 = t - 20
        rr = int(t3/20*255)
        gg = 255 -rr
    colour = display.create_pen(rr,gg,bb)
    display.set_pen(colour)
    display.text(str(t), 10, 10, scale=8)
        
    display.update()
    print(t,rr,gg,bb)
    time.sleep(0.7)

It appears to suffer a bit from the RGB332 system only providing 255 colours rather than the 65K of RGB565. Some of the mid shades, like yellow, are a bit muddy.

In the first half as the green increases the blue drops and in the second half as the red increases the green falls away. This gives full GREEN at 20 degrees. It should be easy to reduce the range and the mid point as necessary and convert to F.

I hope this helps.

@Tonygo2 I do believe the OP is talking about the Enviro+ for a Pi.

Thank you Tonygo2, I appreciate the code, but alphanumeric is right. I am investigating the graphs on the Enviro+ (python scripts).

My code is pretty much what you have alphanumeric,we had the discussion about displaying the info on multiple screens before, and I have implemented that successfully. Since then I did notice the graphs behavior and I wasn’t happy,but always busy with something else.
I read your idea to slow down the readings for pressure and I will probably try to do that as well,but I don’t think that helps with the color issue.

My script for two 2.2" displays showing Temperature and Humidity :

import time
import colorsys
import sys
import os
import ST7789

from bme280 import BME280
from subprocess import PIPE, Popen
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
from fonts.ttf import RobotoMedium as UserFont
import logging

logging.basicConfig(
    format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s',
    level=logging.INFO,
    datefmt='%Y-%m-%d %H:%M:%S')

logging.info("""2Left_Center_Temp_Hum.py - Displays readings temperature and humidity sensors

Press Ctrl+C to exit!

""")
# BME280 temperature/pressure/humidity sensor
bme280 = BME280()

#Left screen . Displays temperature
disp0 = ST7789.ST7789(
    port=1,
    cs=0, 
    dc=25,
    backlight=13,
    rotation=180,
    spi_speed_hz=50000000
)

#Center screen. Displays humidity
disp1 = ST7789.ST7789(
    port=1,
    cs=2, 
    dc=25,
    backlight=13,
    rotation=180,
    spi_speed_hz=50000000
)

WIDTH = 320
HEIGHT = 240

disp0.begin()
WIDTH0 = disp0.width
HEIGHT0 = disp0.height

disp1.begin()
WIDTH1 = disp1.width
HEIGHT1 = disp1.height

# Set up canvas and font
img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0))
draw = ImageDraw.Draw(img)
font_size_small = 20 # was 10
font_size_large = 30 # was 20
font = ImageFont.truetype(UserFont, font_size_large)
smallfont = ImageFont.truetype(UserFont, font_size_small)
x_offset = 2
y_offset = 2

message = ""


# The position of the top bar
top_pos = 30 # was 25

# Create a values dict to store the data
variables = ["Temperature",
             "Humidity"]

units = ["C" ,
         "%"]

# Define your own warning limits
# The limits definition follows the order of the variables array
# Example limits explanation for temperature:
# [4,18,28,35] means
# [-273.15 .. 4] -> Dangerously Low
# (4 .. 18]      -> Low
# (18 .. 28]     -> Normal
# (28 .. 35]     -> High
# (35 .. MAX]    -> Dangerously High
# DISCLAIMER: The limits provided here are just examples and come
# with NO WARRANTY. The authors of this example code claim
# NO RESPONSIBILITY if reliance on the following values or this
# code in general leads to ANY DAMAGES or DEATH.
limits = [[4, 18, 28, 35],
          [20, 40, 60, 70]]

# RGB palette for values on the combined screen
palette = [(0, 0, 255),           # Dangerously Low
           (0, 255, 255),         # Low
           (0, 255, 0),           # Normal
           (255, 255, 0),         # High
           (255, 0, 0)]           # Dangerously High

values = {}


# Displays data on Left Display.Temperature

def display_text0(variable, data, unit):
    # Maintain length of list
    values[variable] = values[variable][1:] + [data]
    # Scale the values for the variable between 0 and 1
    vmin = min(values[variable])
    vmax = max(values[variable])
    colours = [(v - vmin + 1) / (vmax - vmin + 1) for v in values[variable]]
    # Format the variable name and value
    message = "{}:{:.1f} {}".format(variable[:11], data, unit)
    logging.info(message)
    draw.rectangle((0, 0, WIDTH, HEIGHT), (255, 255, 255))
    for i in range(len(colours)):
        # Convert the values to colours from red to blue
        colour = (1.0 - colours[i]) * 0.6
        r, g, b = [int(x * 255.0) for x in colorsys.hsv_to_rgb(colour, 1.0, 1.0)]
        # Draw a 1-pixel wide rectangle of colour
        draw.rectangle((i, top_pos, i + 1, HEIGHT), (r, g, b))
        # Draw a line graph in black
        line_y = HEIGHT - (top_pos + (colours[i] * (HEIGHT - top_pos))) + top_pos
        draw.rectangle((i, line_y, i + 1, line_y + 1), (0, 0, 0))
    # Write the text at the top in black
    draw.text((10, 0), message, font=font, fill=(0, 0, 0))
    disp0.display(img)


#Display data on Center Display.Humidity

def display_text1(variable, data, unit):
    values[variable] = values[variable][1:] + [data]
    vmin = min(values[variable])
    vmax = max(values[variable])
    colours = [(v - vmin + 1) / (vmax - vmin + 1) for v in values[variable]]
    message = "{}:{:.0f}{}".format(variable[:11],data,unit)
    logging.info(message)
    draw.rectangle((0, 0, WIDTH1, HEIGHT1), (255, 255, 255))
    for i in range(len(colours)):
        # Convert the values to colours from red to blue
        colour = (1.0 - colours[i]) * 0.6
        r, g, b = [int(x * 255.0) for x in colorsys.hsv_to_rgb(colour, 1.0, 1.0)]
        draw.rectangle((i, top_pos, i + 1, HEIGHT1), (r, g, b))
        line_y = HEIGHT1 - (top_pos + (colours[i] * (HEIGHT1 - top_pos))) + top_pos
        draw.rectangle((i, line_y, i + 1, line_y + 1), (0, 0, 0))

    draw.text((10, 0), message, font=font, fill=(0, 0, 0))
    disp1.display(img)
    

# Saves the data to be used in the graphs later and prints to the log
def save_data(idx, data):
    variable = variables[idx]
    # Maintain length of list
    values[variable] = values[variable][1:] + [data]
    unit = units[idx]
    message = "{}: {:.1f} {}".format(variable[:11], data, unit)
    logging.info(message)


# Get the temperature of the CPU for compensation
def get_cpu_temperature():
    process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE, universal_newlines=True)
    output, _error = process.communicate()
    return float(output[output.index('=') + 1:output.rindex("'")])


def main():
    # Tuning factor for compensation. Decrease this number to adjust the
    # temperature down, and increase to adjust up
    # Measured with external thermometer and adjusted
    
    factor = 22.0 # was 2.25

    cpu_temps = [get_cpu_temperature()] * 5

# keep mode to display different parameters on different displays
# mode is colled below for each display
    mode = 0    # The starting mode
    last_page = 0

    for v in variables:
        values[v] = [1] * WIDTH

    # The main loop
    try:
        while True:

            # One mode for each variable
            if mode == 0:
                # variable = "temperature"
                unit = "C"
                cpu_temp = get_cpu_temperature()
                # Smooth out with some averaging to decrease jitter
                cpu_temps = cpu_temps[1:] + [cpu_temp]
                avg_cpu_temp = sum(cpu_temps) / float(len(cpu_temps))
                raw_temp = bme280.get_temperature()
                data = raw_temp - ((avg_cpu_temp - raw_temp) / factor)
                display_text0(variables [mode], data, unit)
                mode = 1

            if mode == 1:
                # variable = "humidity"
                unit = "%"
                data = bme280.get_humidity()
                display_text1(variables[mode], data, unit)
                mode = 0
 
    # Exit cleanly
    except KeyboardInterrupt:
        sys.exit(0)


if __name__ == "__main__":
    main()

I have added back on the above script the default color values for graphs,but even without them I was getting the same result.

I will look at Tonygo2’s code and see if I can come up with some idea for python. I will also try your idea to switch red and green around.
The thing is, I was expecting to see green values when parameters are within limits established and changing colors when drifting one way or the other. In my case, it never happened. I never had any blue displayed (for lower than normal values) in normal operation. Again, when I touch the bme 280 sensor, briefly graphs work as they should ( going blue or green,etc) but very soon after,everything kinda defaults to red and the follow up like stays always high on the screen.

I don’t see any blue on mine. Basically red, orange, yellow, green.
What’s the difference in time between the last reading on the left, versus the first reading displayed on the right?

I don’t know. They both start graphing at the same time. I did not modify anything timing wise.

OK, the stock timing / sampling is very quick. You’ll see that if you touch the BME280. You get a nice quick response and it looks cool. Thing is, after a second or two the readings will stabilize, and the graph will be a straight line. When that happens you get just the one color. All the plots are almost the same reading. Normal temperature change won’t be fast enough to be seen as a color change. It wasn’t for me anyway, that’s why I played around with the sampling rate. That got me this.

Three display weather graphing setup - Discussion / Projects - Pimoroni Buccaneers

This the code I used for the above.

#!/usr/bin/env python3

import os
import sys
import time
import colorsys
import logging
import ST7735
import RPi.GPIO as GPIO

from sys import exit
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
from subprocess import PIPE, Popen
from fonts.ttf import RobotoMedium as UserFont
try:
    from smbus2 import SMBus
except ImportError:
    from smbus import SMBus
from bme280 import BME280

def Shutdown(channel):  
    os.system("sudo shutdown now -P")
    time.sleep(30)

GPIO.setmode(GPIO.BCM)  
GPIO.setwarnings(False)
GPIO.setup(24, GPIO.IN, pull_up_down = GPIO.PUD_UP)
GPIO.add_event_detect(24, GPIO.FALLING, callback = Shutdown, bouncetime = 2000)

logging.basicConfig(
    format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s',
    level=logging.INFO,
    datefmt='%Y-%m-%d %H:%M:%S')

logging.info("""Displays readings from BME280 sensor
Press Ctrl+C to exit!
""")

bme280 = BME280()

disp0 = ST7735.ST7735(
    port=1,
    cs=0, 
    dc=19,
#    backlight=5, 
    rotation=90,
    spi_speed_hz=10000000
)

disp0.begin()

disp1 = ST7735.ST7735(
    port=1,
    cs=1, 
    dc=19,
 #   backlight=6, 
    rotation=90,
    spi_speed_hz=10000000
)

disp1.begin()

disp2 = ST7735.ST7735(
    port=1,
    cs=2, 
    dc=19,
  #  backlight=13, 
    rotation=90,
    spi_speed_hz=10000000
)

disp2.begin()

WIDTH = 160
HEIGHT = 80

img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0))
draw = ImageDraw.Draw(img)
path = os.path.dirname(os.path.realpath(__file__))
font_size = 20
font = ImageFont.truetype(UserFont, font_size)

message = ""

top_pos = 25

def display_text0(variable, data, unit):
    values[variable] = values[variable][1:] + [data]
    vmin = min(values[variable])
    vmax = max(values[variable])
    colours = [(v - vmin + 1) / (vmax - vmin + 1) for v in values[variable]]
    message = "{}:{:.0f}{}".format(variable[:11],data,unit)
    logging.info(message)
    draw.rectangle((0, 0, WIDTH, HEIGHT), (255, 255, 255))
    for i in range(len(colours)):
        colour = (1.0 - colours[i]) * 0.6
        r, g, b = [int(x * 255.0) for x in colorsys.hsv_to_rgb(colour, 1.0, 1.0)]
        draw.rectangle((i, top_pos, i + 1, HEIGHT), (r, g, b))
        line_y = HEIGHT - (top_pos + (colours[i] * (HEIGHT - top_pos))) + top_pos
        draw.rectangle((i, line_y, i + 1, line_y + 1), (0, 0, 0))

    draw.text((0, 0), message, font=font, fill=(0, 0, 0))
    disp0.display(img)

def display_text1(variable, data, unit):
    values[variable] = values[variable][1:] + [data]
    vmin = min(values[variable])
    vmax = max(values[variable])
    colours = [(v - vmin + 1) / (vmax - vmin + 1) for v in values[variable]]
    message = "{}:{:.0f}{}".format(variable[:11],data,unit)
    logging.info(message)
    draw.rectangle((0, 0, WIDTH, HEIGHT), (255, 255, 255))
    for i in range(len(colours)):
        colour = (1.0 - colours[i]) * 0.6
        g, r, b = [int(x * 255.0) for x in colorsys.hsv_to_rgb(colour, 1.0, 1.0)]
        draw.rectangle((i, top_pos, i + 1, HEIGHT), (r, g, b))
        line_y = HEIGHT - (top_pos + (colours[i] * (HEIGHT - top_pos))) + top_pos
        draw.rectangle((i, line_y, i + 1, line_y + 1), (0, 0, 0))

    draw.text((0, 0), message, font=font, fill=(0, 0, 0))
    disp1.display(img)

def display_text2(variable, data, unit):
    values[variable] = values[variable][1:] + [data]
    vmin = min(values[variable])
    vmax = max(values[variable])
    colours = [(v - vmin + 1) / (vmax - vmin + 1) for v in values[variable]]
    message = "{}:{:.0f}{}".format(variable[:11],data,unit)
    logging.info(message)
    draw.rectangle((0, 0, WIDTH, HEIGHT), (255, 255, 255))
    for i in range(len(colours)):
        colour = (1.0 - colours[i]) * 0.6
        g, r, b = [int(x * 255.0) for x in colorsys.hsv_to_rgb(colour, 1.0, 1.0)]
        draw.rectangle((i, top_pos, i + 1, HEIGHT), (r, g, b))
        line_y = HEIGHT - (top_pos + (colours[i] * (HEIGHT - top_pos))) + top_pos
        draw.rectangle((i, line_y, i + 1, line_y + 1), (0, 0, 0))

    draw.text((0, 0), message, font=font, fill=(0, 0, 0))
    disp2.display(img)    

last_page = 0

variables = ["Temperature",
             "Humidity",
             "Pressure"]

values = {}

for v in variables:
    values[v] = [1] * WIDTH

mode = 0
F = 0
D = 0

while True:

    
    if F < 161:
        F = F + 1 
    elif F >= 161:
        time.sleep(5)
        D = D + 1 
            
    if mode == 0:
        mode %= len(variables)
        last_page = time.time()
        unit = "C"
        data = bme280.get_temperature()
        display_text0(variables[mode], data, unit)
        mode = 1

    if mode == 1:
        mode %= len(variables)
        last_page = time.time()
        unit = "%"
        data = bme280.get_humidity()
        display_text1(variables[mode], data, unit)
        
    if D == 0:        
        mode = 2
    elif D > 0 and D < 9:
        mode = 0
    elif D == 9:
        mode = 2
        D = 0
    
    if mode == 2:
        mode %= len(variables)
        last_page = time.time()
        unit = "mb"
        data = bme280.get_pressure()
        display_text2(variables[mode], data, unit)
        mode = 0

Thank you, appreciate the code. I’ll have a look. You might be right, I do see more or less a straight line,but I would expect that line to plot on a mostly green area ( with the ranges I established for “normal” conditions).

I’d describe myself as average at best skill wise with coding. I’ll just have to take your word for it as what your code does. A 5 second time sleep between readings should get you about 13 minutes of data across the screen. That’s my delay for temp and humidity.
My if F stuff is just a fast fill to get some graphs on the screen right after a boot up. The if D is my extra delay for pressure. It plots once every 45 seconds. (9x5) That gets me 2 hours of plots across the screen. I’m pretty sure thats how I set it up, it’s been a while since I’ve looked at this code. That Pi setup has been sitting on my desk just doing its thing for a while now.