Pico and PMS5003 sensor changing graph colors with particle sizes

Hello,

I have connected a PicoW with a PMS5003 sensor and a Pico Display Pack 2. I am using the Pimoroni library for the PMS5003 and modified the particle.py for my display. All works well but I would like to be able to change the graph color depending on the particle range reading.
Ex: PM10 is less than 10 I keep the color. Gets higher than that I switch to red.

I’ve tried a few ideas but it seems that it works when the script starts ( if I have something higher than my threshold I start with a red color) but it does not update with the live reading.
Can the values from the PMS5003 can be captured in real time somehow ?

Thank you.

Posting the code your using will likely help. For people like me anyway, that have average python skills.
I’m big on color coding readings. I have several weather info based Pi and Pico setups. I like having the text reflect what’s going on. Green OK, Red not OK etc.

You’re right,I should’ve posted it. Here it is :

Particle Sensor Example

This example requires seperate MicroPython drivers for the PMS5003 particulate sensor.
(You can find it at GitHub - pimoroni/pms5003-micropython )
or install from PyPi by searching for ‘pms5003-micropython’ in Thonny’s ‘Tools > Manage Packages’

‘’’

This is Pimoroni default script for Pico Enviro board.

mod for Pico Display Pack2.

from picographics import PicoGraphics, DISPLAY_PICO_DISPLAY_2
from pimoroni import RGBLED
from pms5003 import PMS5003
import machine
import time
import LED # turns ON onboard LED

print(“”“particle.py - Continuously print all data values.
and draw a pretty histogram on display
“””)

Configure the PMS5003 for Enviro+

pms5003 = PMS5003(
uart=machine.UART(1, tx=machine.Pin(8), rx=machine.Pin(9), baudrate=9600),
pin_enable=machine.Pin(3),
pin_reset=machine.Pin(2),
mode=“active”
)

set up the display

display = PicoGraphics(DISPLAY_PICO_DISPLAY_2, rotate=0)
display.set_backlight(1.0)
display.set_font(“bitmap8”) # or put “serif” , “sans” “bitmap4”, “bitmap8” changes default square font

WIDTH, HEIGHT = display.get_bounds()

Setup RGB Led

led = RGBLED(6, 7, 10, invert=True) # moves B LED to pin 14 (10) to avoid interference with TX from PMS5003
led.set_rgb(0, 0, 0)

To quickly switch between colors and be able to pass arguments.

Blue = display.create_pen(0, 128, 255) # color used for normal values 1.O PM
Yellow = display.create_pen(255, 255, 0) # color used for 2.5 PM
Green = display.create_pen(0, 255, 0) # color used for 10.0PM
Red = display.create_pen(255, 0, 0) # Red for high particle numbers. Use this for all values when they go high

Setup background

BG = display.create_pen(0, 0, 0)
TEXT = display.create_pen(255, 255, 255)
PM10 = display.create_pen(0,128,255) # was red 255,0,0 # 0,128,255 bluish # 255,0,255 aqua-pinkish
PM25 = display.create_pen(255, 255, 0)
PM100 = display.create_pen(0, 255, 0)
PM125 = display.create_pen(255, 255, 0)
PM1000 = display.create_pen(255, 255, 0)
display.set_pen(BG)
display.clear()

array for storing

results =

Drawing routines

def draw_background():
display.set_pen(BG)
display.clear()
display.set_pen(TEXT)
display.text(“PMS5003 Sensor”, 5, 2, scale=1)

def draw_txt_overlay(sensor_data):

display.set_pen(PM10)
display.text("PM1.0:    {0}  ".format(sensor_data.pm_ug_per_m3(1.0)), 1, 20, scale=4) # was scale=3 it was 5,40. 5 is 5 pixels from frame i think
display.set_pen(PM25)
display.text("PM2.5:    {0} ".format(sensor_data.pm_ug_per_m3(2.5)), 1, 60, scale=4)
display.set_pen(PM100)
display.text("PM10:     {0} ".format(sensor_data.pm_ug_per_m3(10)), 1, 100, scale=4)

The graph is reversed to paint the PM10 first(background), PM2.5 after and PM1.0 on foreground

def draw_hist(results_array):

result_index = 0
for result in results_array:
    display.set_pen(PM100)
    display.rectangle(3 * result_index, 240 - result.pm_ug_per_m3(10), 3, 240)
    display.set_pen(PM25)
    display.rectangle(3 * result_index, 240 - result.pm_ug_per_m3(2.5), 3, 240)

  # This is where I try to change graph color depending on number of particles
  # it starts ok but it doesn't change color when particles change.   
  #  if (PM10 < 10):
         # display.set_pen(Blue) # this is blue
  #  else:
         # display.set_pen(Red) # this is red          
    display.set_pen(PM10) 
    display.rectangle(3 * result_index, 240 - result.pm_ug_per_m3(1.0), 3, 240)
    result_index += 1

while True:

draw_background()
data = pms5003.read()
print(data)
results.append(data)

if (len(results) > 120):  # Scroll the result list by removing the first value
    results.pop(0)
            
draw_hist(results)
draw_txt_overlay(data)
display.update()
time.sleep(0.5)

You’ll notice the commented out lines I’ve tried for PM10. I’ve tried the same lines after the
while: statement with same results.
When the script starts,if the particles are higher than 10 the graph is red (instead of blue) but as the readings progress (and drop below 10) the graph stays red.

I’ve also tried something for the RGB led on the display pack with the same result. Trying to set the led works at startup but doesn’t update with the live readings.

Not sure what else to try.

Thanks for your help.

Just a FYI, clicking the </> will let you wrap your code (preformatted text) in code tags.

type or paste code here

I’m thinking the
PM25 = display.create_pen(255, 255, 0)
is what is trying to lock in the color.
I’d try normal colors,

black = graphics.create_pen(0,0,0)
red = graphics.create_pen(255,0,0)
green = graphics.create_pen(0,255,0)
blue = graphics.create_pen(0,0,255)
yellow = graphics.create_pen(255,255,0)
orange = graphics.create_pen(255,140,0)
white = graphics.create_pen(255,255,255)

then do a display.set_pen(red) if you want that graph or text red.

I do it something like this for my text display.

    if temperature < -10:
        t_color = white
    elif -10 <= temperature <= 0:
        t_color = blue
    elif 0 < temperature <= 12:
        t_color = yellow
    elif 12 < temperature <= 16:
        t_color = green
    elif 16 < temperature <= 24:
        t_color = green      
    elif 24 < temperature <= 27:
        t_color = orange
    elif temperature > 27:
        t_color = red

    graphics.set_pen(t_color)
    graphics.text("{:0.0f}°C" .format(temperature), 15, 17, scale=2)

Thank you very much. I’ll try the idea and report back.

What I do is declare them in the main section where I set all the other pen colors. I could set them to any color but I chose black. They get changed anyway in the while True section.
t_color = graphics.create_pen(0,0,0)
h_color = graphics.create_pen(0,0,0)
p_color = graphics.create_pen(0,0,0)

black = graphics.create_pen(0,0,0)
red = graphics.create_pen(255,0,0)
green = graphics.create_pen(0,255,0)
etc

This what I did on my Tufty that has a BME280 attached.

import utime
import machine
import picographics
from picographics import PicoGraphics, DISPLAY_TUFTY_2040, PEN_RGB332
display = PicoGraphics(display=DISPLAY_TUFTY_2040, rotate=180, pen_type=PEN_RGB332)
display.set_backlight(0.5)
display.set_font("bitmap8")

from breakout_bme280 import BreakoutBME280
from pimoroni_i2c import PimoroniI2C
i2c = PimoroniI2C(sda=(4), scl=(5))
bme = BreakoutBME280(i2c)

from breakout_rtc import BreakoutRTC
from machine import RTC
RV3028 = BreakoutRTC(i2c)

rtc = BreakoutRTC(i2c)
if rtc.is_12_hour:
    rtc.set_24_hour()

from machine import ADC, Pin

# set up the ADCs for measuring battery voltage
vbat_adc = ADC(29)
vref_adc = ADC(28)
vref_en = Pin(27)
vref_en.init(Pin.OUT)
vref_en.value(0)
usb_power = Pin(24, Pin.IN)

full_battery = 4.5
empty_battery = 2.5

# Setup light sensor
lux_pwr = Pin(27, Pin.OUT)
lux_pwr.value(1) # Turn on power!
lux = ADC(26)

from pimoroni import Button
button_a = Button(7, invert=False)
button_b = Button(8, invert=False)
button_c = Button(9, invert=False)
button_up = Button(22, invert=False)
button_down = Button(6, invert=False)
button_boot = Button(23, invert=True)

t_color = display.create_pen(0,255,0)
h_color = display.create_pen(0,255,0)
p_color = display.create_pen(0,255,0)
white = display.create_pen(255,255,255)
black = display.create_pen(0,0,0)
red = display.create_pen(255,0,0)
green = display.create_pen(0,255,0)
blue = display.create_pen(0,0,255)
yellow = display.create_pen(255,255,0)
orange = display.create_pen(255,140,0)
#grey = display.create_pen(120,120,120)
#violet = display.create_pen(255,0,255)
#cyan = display.create_pen(0,255,255)

display.set_pen(black)
display.clear()

# converts the temperature into a description and pen colour
def describe_temperature(temperature):
    global t_color
    if temperature < -10:
        description = "Very Cold"
        t_color = white
    elif -10 <= temperature <= 0:
        description = "Cold"
        t_color = blue
    elif 0 < temperature <= 12:
        description = "Cool"
        t_color = yellow
    elif 12 < temperature <= 16:
        description = "Warm"
        tempcolor = green
    elif 16 < temperature <= 24:
        description = "OK"
        t_color = green      
    elif 24 < temperature <= 27:
        description = "Hot"
        t_color = orange
    elif temperature > 27:
        description = "Very Hot"
        t_color = red
    return description

# converts humidity into good/bad description and pen color
def describe_humidity(humidity):
    global h_color
    if humidity < 30:
        description = "Low"
        h_color = red
    elif 30 <= humidity <= 60:
        description = "OK"
        h_color = green
    elif 60 < humidity < 80:
        description = "High"
        h_color = yellow
    elif humidity >= 80:
        description = "Very High"
        h_color = orange        
    return description

# converts pressure into barometer-type description and pen color
def describe_pressure(pressure):
    global p_color
    if pressure < 982:
        description = "Very Low"
        p_color = red
    elif 982 <= pressure < 1004:
        description = "Low"
        p_color = yellow
    elif 1004 <= pressure < 1026:
        description = "OK"
        p_color = green
    elif 1026 <= pressure < 1048:
        description = "High"
        p_color = blue
    elif pressure >= 1048:
        description = "Very High"
        p_color = orange
    return description

# graphs current values fot Temperature, Humididty and Pressure
def draw_graph(temp_value, press_value, humid_value):
    
    
    scaled_temp = int(temperature * 10)
    if scaled_temp <= 9:
        scaled_temp =9
    if scaled_temp >= 310:
        scaled_temp =310        
    
    describe_temperature(temperature)
    display.set_pen(yellow)
    display.circle(9,98,9)
    display.rectangle(9,89,116,19)
    display.set_pen(green)
    display.rectangle(127,89,38,19)
    display.set_pen(green)
    display.rectangle(165,89,73,19)
    display.set_pen(orange)
    display.rectangle(240,89,28,19)
    display.set_pen(red)
    display.rectangle(270,89,44,19)
    display.circle(310,98,9)
    display.set_pen(black)
    display.circle((scaled_temp),98,9)
    display.set_pen(white)
    display.circle((scaled_temp),98,5)    
    display.set_pen(t_color)
    display.text(describe_temperature(temperature),150,52,scale=4)
    '''

    scaled_temp = int((temperature * 5) + 160)
    if scaled_temp <= 9:
        scaled_temp =9
    if scaled_temp >= 310:
        scaled_temp =310        
    
    describe_temperature(temperature)
    display.set_pen(white)
    display.circle(9, 98, 9)
    display.rectangle(12, 89, 92, 19)
    display.set_pen(blue)
    display.rectangle(106, 89, 53, 19)
    display.set_pen(yellow)
    display.rectangle(161,89,58,19)
    display.set_pen(green)
    display.rectangle(221,89,20,19)
    display.set_pen(green)
    display.rectangle(241,89,38,19)
    display.set_pen(orange)
    display.rectangle(281,89,13,19)
    display.set_pen(red)
    display.rectangle(296,89,15,19)
    display.circle(310,98,9)
    display.set_pen(black)
    display.circle((scaled_temp),98,9)
    display.set_pen(white)
    display.circle((scaled_temp),98,5)    
    display.set_pen(t_color)
    display.text(describe_temperature(temperature),150,52,scale=4)
    '''
    scaled_humid = int(humidity * (320 / 100))
    if scaled_humid <= 9:
        scaled_humid =9
    if scaled_humid >= 310:
        scaled_humid =310        
    
    describe_humidity(humidity)
    display.set_pen(red)
    display.circle(9, 164, 9)
    display.rectangle(12,155,82,19)
    display.set_pen(green)
    display.rectangle(96,155,94,19)
    display.set_pen(yellow)
    display.rectangle(192,155,62,19)
    display.set_pen(orange)
    display.rectangle(256,155,55,19)
    display.circle(310,164,9)
    display.set_pen(black)
    display.circle((scaled_humid),164,9)
    display.set_pen(white)
    display.circle((scaled_humid),164,5)
    display.set_pen(h_color)
    display.text(describe_humidity(humidity),150,117,scale=4)
    
    scaled_press = int((pressuremb - 960) * 3)
    if scaled_press <= 9:
        scaled_press =9
    if scaled_press >= 310:
        scaled_press =310        
    
    describe_pressure(pressuremb)
    display.set_pen(red)
    display.circle(9, 230, 9)
    display.rectangle(12,221,50,19)
    display.set_pen(yellow)
    display.rectangle(64,221,62,19)
    display.set_pen(green)
    display.rectangle(128,221,62,19)
    display.set_pen(blue)
    display.rectangle(192,221,62,19)
    display.set_pen(orange)
    display.rectangle(256,221,52,19)
    display.circle(310, 230, 9)
    display.set_pen(black)
    display.line(127,221,127,240)    
    display.circle((scaled_press),230,9)
    display.set_pen(white)
    display.circle((scaled_press),230,5)
    display.set_pen(p_color)
    display.text(describe_pressure(pressuremb),150,184,scale=4) 

'''
def set_pico_time():
    # set the Pico's RTC from the RTC breakout
    RV3028.update_time()
    RTC().datetime([RV3028.get_year(), RV3028.get_month(), RV3028.get_date(),
                    RV3028.get_weekday(), RV3028.get_hours(), RV3028.get_minutes(),
                    RV3028.get_seconds(), 0])
    print(f"Pico RTC set to breakout time: {RV3028.string_date()} {RV3028.string_time()}")
 
#set_pico_time()
'''

def describe_month(month):
    
    month = rtc.get_month()
    if month == 1:
        description = "January"
    elif month == 2:
        description = "February"  
    elif month == 3:
        description = "March"
    elif month == 4:
        description = "April"              
    elif month == 5:
        description = "May"              
    elif month == 6:
        description = "June"              
    elif month == 7:
        description = "July"              
    elif month == 8:
        description = "August"              
    elif month == 9:
        description = "September"
    elif month == 10:
        description = "October"             
    elif month == 11:
        description = "November"              
    elif month == 12:
        description = "December"            
    return description

def describe_date(date):
 
    date = rtc.get_date()
    if date == 1:
        description = "st"
    elif date == 2:
        description = "nd"     
    elif date == 3:
        description = "rd" 
    elif date == 21:
        description = "st"      
    elif date == 22:
        description = "nd"  
    elif date == 23:
        description = "rd"
    elif date == 31:
        description = "st"
    else:
        description = "th"
    return description

start_time = utime.time()
    
while True:
    
    time_elapsed = utime.time() - start_time
    hours = rtc.get_hours()
    minutes = rtc.get_minutes()
    month = rtc.get_month()
    date = rtc.get_date()    
     
    if rtc.read_periodic_update_interrupt_flag():
        rtc.clear_periodic_update_interrupt_flag()

        if rtc.update_time():
            rtc_date = rtc.string_date()
            rtc_time = rtc.string_time()
    
   
    if button_a.is_pressed:
        print ("Button A pressed")
        utime.sleep(0.2)
        
    if button_b.is_pressed:        
        if percentage >= 50:
            display.set_pen(green)
            display.text("Battery",0,9,scale=3)
            display.text("{:0.2f}v".format(vbat),150,9,scale=3)
            display.text("{:0.0f}%".format(percentage),265,9,scale=3)
        elif 50 < percentage > 20:   
            display.set_pen(yellow)
            display.text("Battery",0,9, scale=3)
            display.text("{:0.2f}v".format(vbat),150,9,scale=3)
            display.text("{:0.0f}%".format(percentage),265,9,scale=3)
        elif percentage <= 20:    
            display.set_pen(red)
            display.text("Battery",0,9,scale=3)
            display.text("{:0.2f}v".format(vbat),150,9,scale=3)
            display.text("{:0.0f}%".format(percentage),265,9,scale=3)
    else:
        display.set_pen(white)       
        if hours == 0:
            display.text(f"{12}:{minutes:02} AM",5,9,scale=3)
        elif 0 < hours < 10:
            display.text(f"{hours:1}:{minutes:02} AM",5,9,scale=3)            
        elif 10 <= hours < 12:
            display.text(f"{hours:2}:{minutes:02} AM",0,9,scale=3)
        elif hours == 12:
            display.text(f"{hours:2}:{minutes:02} PM",0,9,scale=3)    
        elif hours >12:
            hours = hours - 12
            if hours <10:
                display.text(f"{hours:1}:{minutes:02} PM",5,9,scale=3)
            elif 10 <= hours < 12:
                display.text(f"{hours:2}:{minutes:02} PM",0,9,scale=3)
            elif hours == 12:
                display.text(f"{hours:2}:{minutes:02} AM",0,9,scale=3)
                
        display.text(f"{describe_month(month)} {date}{describe_date(date)}",130,9,scale=3)         
        
    if button_c.is_pressed:
        print ("Button C pressed")
        utime.sleep(0.2)
        
    if button_up.is_pressed:
        display.set_backlight(1.0)
        utime.sleep(0.2)

    if button_down.is_pressed:
        display.set_backlight(0.4)
        utime.sleep(0.2)

    if button_boot.is_pressed:
        print ("Button BOOT/USR pressed")
        utime.sleep(0.2)
        
    #reading = lux.read_u16()
    #print (reading)
         
    temperature, pressure, humidity = bme.read()
    pressuremb = pressure / 100      
     
    temperature = round(temperature)
    humidity = round(humidity)
    pressure = round(pressure)
    
    display.set_pen(white)
    display.text("{:0.0f}°C" .format(temperature), 5, 52, scale=4)
    display.text("{:0.0f} %".format(humidity), 5, 118, scale=4)
    display.text("{:0.0f} mb".format(pressuremb), 5, 184, scale=4)
    
    draw_graph(temperature, pressure, humidity)
    
    vref_en.value(1)
    
    vdd = 1.24 * (65535 / vref_adc.read_u16())
    vbat = (
        (vbat_adc.read_u16() / 65535) * 3 * vdd
    )

    vref_en.value(0)

    # convert the raw ADC read into a voltage, and then a percentage
    percentage = 100 * ((vbat - empty_battery) / (full_battery - empty_battery))
    if percentage > 100:
        percentage = 100
    if percentage < 0:
        percentage = 0
        
    '''
    if usb_power.value() == 1:
        display.set_pen(blue)
        display.text("Powered Via USB Port", 20, 0, scale=3)
    else:
        if percentage >= 50:
            display.set_pen(green)
            display.text("Battery", 0, 0, scale=3)
            display.text("{:0.2f}v".format(vbat), 150, 0, scale=3)
            display.text("{:0.0f}%".format(percentage), 255, 0, scale=3)
        elif 50 < percentage > 20:   
            display.set_pen(yellow)
            display.text("Battery", 0, 0, scale=3)
            display.text("{:0.2f}v".format(vbat), 150, 0, scale=3)
            display.text("{:0.0f}%".format(percentage), 255, 0, scale=3)
        elif percentage <= 20:    
            display.set_pen(red)
            display.text("Battery", 0, 0, scale=3)
            display.text("{:0.2f}v".format(vbat), 150, 0, scale=3)
            display.text("{:0.0f}%".format(percentage), 255, 0, scale=3)
        '''
    if time_elapsed < 0.5:
        temperature, pressure, humidity = bme.read()
        display.set_pen(black)
        display.clear()
        display.update()
        utime.sleep(1)
    else:
        display.update()
        utime.sleep(1)
        display.set_pen(black)
        display.clear()    
        utime.sleep(1)


Unfortunately I can;t seem to make it work. I have tried to describe a function as I saw on your example ( for temperature),but I can’t capture the data from the PMS sensor.
I am wondering if nobody else tried to do this ? It seems a logical way to try to change colors with the thresholds.

I haven’t tried anything like this. And I don’t have a PMS sensor on hand. I have one setup that graphs data, but the background changes color not the actual line plot. And its not my code. It’s Enviro+ code.

I’ve figured it out. The following code will change the text and value color of individual PM values as they get read from the PMS5003 sensor :

Basically it is just a slight modification of the function present on Pimoroni’s example to read PMS sensor data.

def draw_txt_overlay(sensor_data):

display.set_pen(LIGHT_BLUE)
display.text("PM1.0: {0}".format(sensor_data.pm_ug_per_m3(1.0)), 2, 155, scale=1) 

set colors thresholds for 2.5PM and set LED color according to thresholds

rgb = BLACK
if sensor_data.pm_ug_per_m3(2.5) <= 12:
    rgb = GREEN
    led.set_rgb(0,20,0) # LED is green .FULL BRIGHTNESS GREEN IS 0,255,0
elif 12 <= sensor_data.pm_ug_per_m3(2.5) < 35:
    rgb = YELLOW
    led.set_rgb(20,20,0) # LED is yellow
elif sensor_data.pm_ug_per_m3(2.5) > 35:
    rgb = RED
    led.set_rgb(20,0,0) # LED is red . FULL RED IS 255,0,0
display.set_pen(rgb)
display.text("PM2.5: {0}".format(sensor_data.pm_ug_per_m3(2.5)), 2, 190, scale=1)

set colors thresholds for 10PM

rgb = BLACK
if sensor_data.pm_ug_per_m3(10) <= 40:
    rgb = GREEN
elif 40 <= sensor_data.pm_ug_per_m3(10) < 100:
    rgb = YELLOW
elif sensor_data.pm_ug_per_m3(10) > 100:
    rgb = RED        
display.set_pen(rgb)
display.text("PM 10: {0}".format(sensor_data.pm_ug_per_m3(10)), 2, 225, scale=1)

If using a PicoW display pack with a LED will also turn the LED green,yellow or red as well.
The thresholds taken from the EPA site. Only PM2.5 and PM10 will change colors,PM1 stays blue.

I hope this will help someone else looking for the same thing.

1 Like

Thankyou for posting the solution. =)