Pimoroni Explorer Kit Tutorial

The following code demonstrates the use of the Pimoroni Explorer Kit as a desktop auto clock/calendar/weather station. I hope someone finds it useful.

Triangles used to be difficult but the latest version of picographics makes them easy to use. I really like the larger screen. It shows off the 320x240 pixels much better. The multi-sensor-stick is really easy to use.

# Pimoroni Explorer Demo
# Analog/ Digital clock - Tony Goodhew, Leicester UK - 6 Nov 2024
# Uses the Multi-Sensor Stick (BME280 + LTR559 + LSM6DS3)
from explorer import display, i2c, button_z, button_x, button_y, BLACK, WHITE, GREEN, RED, BLUE, YELLOW, CYAN
from breakout_bme280 import BreakoutBME280
from breakout_ltr559 import BreakoutLTR559
import math
import time

# Check sensor stick connected
try:
    bme = BreakoutBME280(i2c, address=0x76)
    ltr = BreakoutLTR559(i2c)
except RuntimeError:
    display.set_layer(0)
    display.set_pen(RED)
    display.clear()
    display.set_pen(WHITE)
    display.text("Multi-Sensor Stick missing", 10, 95, 320, 3)
    display.update()

WIDTH, HEIGHT = display.get_bounds()

# Get current time
year, month, day, h, m, s, wd, _ = time.localtime()

days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]

# Used for minute and hour hands
def hand(ang, long): 
    ang = ang-90
    ls = long/10
    x0 = int(round(long * math.cos(math.radians(ang))))+cx
    y0 = int(round(long * math.sin(math.radians(ang))))+cy
    x1 = int(round(ls * math.cos(math.radians(ang+90))))+cx
    y1 = int(round(ls * math.sin(math.radians(ang+90))))+cy
    x2 = int(round(ls * math.cos(math.radians(ang-90))))+cx
    y2 = int(round(ls * math.sin(math.radians(ang-90))))+cy
    display.triangle(x0,y0,x1,y1,x2,y2)
    display.circle(cx,cy,int(ls))
    
display.set_backlight(1.0)
display.set_layer(0)
display.set_pen(BLACK)
display.clear()
display.set_layer(1)
display.set_pen(BLACK)
display.clear()

cx = 199 # centre of clock
cy = 119
l = 110

# Draw clock face
display.set_pen(BLUE)
display.circle(cx,cy,110)
display.update()

ls = 94
display.set_pen(WHITE)
for angle in range(0,360,6):
    xx = int(round(l * math.cos(math.radians(angle))))
    yy = int(round(l * math.sin(math.radians(angle))))    
    display.line(cx,cy,cx+xx,cy+yy)
display.set_pen(BLACK)
display.circle(cx,cy,100)
display.set_pen(WHITE)

for angle in range(0,360,30):
    xx = int(round(l * math.cos(math.radians(angle))))
    yy = int(round(l * math.sin(math.radians(angle))))    
    display.line(cx,cy,cx+xx,cy+yy)
    
# Sensor titles
display.set_pen(WHITE)
display.text("Temperature",5,40,310,1)
display.text("Lux",5,80,300,1)
display.text("Rel Humidity",5,120,300,1)
display.text("Air Pressure",5,200,300,1)

# === Main Loop ======
while True:
    # Draw face numbers
    display.set_pen(BLACK)
    display.circle(cx,cy,93) # Clear centre of clock face
    display.set_pen(YELLOW)
    display.text("9",118,108,320,3)
    display.text("3",268,108,320,3)
    display.text("10",128,70,320,3)
    display.text("2",258,70,320,3)
    display.text("1",230,45,320,3)
    display.text("11",160,45,320,3)
    display.text("12",190,37,320,3)
    display.text("6",193,186,320,3)
    display.text("7",156,176,320,3)
    display.text("5",230,176,320,3)
    display.text("4",258,150,320,3)
    display.text("8",130,150,320,3)
    
    # Draw hands
    mang = int((m + s/60)* 6)    # angle of minute hand
    hang = int((h + m/60 )* 30 ) # angle of hour hand
    display.set_pen(BLUE)
    hand(mang,90)
    display.set_pen(RED)      
    hand(hang,70)
    sang = 6 * s - 90
    xs = int(round(ls * math.cos(math.radians(sang))))
    ys = int(round(ls * math.sin(math.radians(sang))))
    display.set_pen(WHITE)
    display.line(cx,cy,cx+xs,cy+ys)
    display.circle(cx,cy,3)
    
    # Assemble Digital time
    ss = "0" + str(s)
    lgs =len(ss)
    ss = ss[lgs-2:lgs]
    ms = "0" + str(m)
    lgs = len(ms)
    ms = ms[lgs-2:lgs]
    hs = "0" + str(h)
    lgs = len(hs)
    hs = hs[lgs-2:lgs]
    dt = hs+":"+ms+":"+ss
    
    # Clear sensor text areas
    display.set_pen(BLACK)
    display.rectangle(5,10,127,18)
    display.rectangle(5,50,90,18)
    display.rectangle(5,90,80,18)
    display.rectangle(5,130,80,18)
    display.rectangle(5,210,115,18)
    display.rectangle(5,163,90,28)
    
    # Write digital time & sensor readings 
    display.set_pen(YELLOW)
    display.text(dt,5,10,320,3)
    # read the sensors
    temperature, pressure, humidity = bme.read()
    temp = round(temperature,1)
    display.set_pen(GREEN)
    display.text(str(temp)+" C",5,50,320,3)
    # Convert pressure to hPa
    pressurehpa = int(pressure / 100)
    display.text(str(int(humidity)) +" %", 5,130,320,3)
    display.text(str(pressurehpa)+" hPa",5,210,320,3)
    prox, a, b, c, d, e, lux = ltr.get_reading()
    lux = int(lux)
    display.set_pen(CYAN)
    display.text(str(lux),5,90,320,3)
    
    # Update date/time
    year, month, day, h, m, s, wd, _ = time.localtime()
    display.text(days[wd],5,163,320,1)
    display.text(str(day)+" "+months[month-1],5,173,320,2)
    display.update()
    time.sleep(0.1) 

Video here: https://youtu.be/p8yvwJqr3u4

If you would like a tutorial on using the tilt aspects of the IMU, please leave a comment.

2 Likes

Hi Tony, that looks amazing! I don’t have a Pimoroni Explorer Kit, but I tried your clock on a Pico Plus 2, Display Pack 2.0 and Multi-Sensor Stick, with minor initialisation changes, and it works really well.

Curiously, the picographics module provided in the Pico Plus 2 firmware (v0.0.9) doesn’t seem to include the set_layer() method, but it still works fine without those lines.

I would like to offer an alternative way to create the digital time string, using the % format operator:

dt = "%02d:%02d:%02d" % (h, m, s)

This printf-style method betrays my pre-retirement job as a sysadmin, programming mainly in awk(!). Python has newer, more sophisticated string formatting methods, but this old one still works.

Yes, I would be interested in a tutorial on using the inertial measurement unit, thanks.

Thank you for the comments and the formatting hint.

I will have a look at the accelerator values of the imu and see if I can work something out now that I know someone is interested.

1 Like

I’ve looked on the web but there appears to be very little about using the accelerators in an IMU to easily measure tilt. It did find a couple of formulae which I used as a starting point.
rad_to_deg = 180/math.pi # = 57.29578
roll = math.atan(ay / az) * rad_to_deg
pitch = math.atan((- ax) / math.sqrt(ay * ay + az * az)) * rad_to_deg

I decided to fix the sensor stick above the display screen wit a couble of small blobs of Blu Tack. (You need a longer cable than the short version supplied with the kit - same price but more useful.)

I found that with the IMU in this position the X and Y accelerator readings did not match with the display screen directions. This is easily swapped over in the program.

My first program reads the full tilt in both the X and Y planes and displays them on the screen. Ranges were -90 degrees to + 90 degrees.

# Display tilt readings from the multi-sensor stick on the Explorer screen
from explorer import display, i2c, BLACK, WHITE, RED, GREEN, BLUE, YELLOW, CYAN, MAGENTA
from breakout_ltr559 import BreakoutLTR559
from lsm6ds3 import LSM6DS3
from breakout_bme280 import BreakoutBME280
import time
import math

# Clear all layers first
display.set_layer(0)
display.set_pen(BLACK)
display.clear()
display.set_layer(1)
display.set_pen(BLACK)
display.clear()

try:
    ltr = BreakoutLTR559(i2c)
    lsm = LSM6DS3(i2c)
    bme = BreakoutBME280(i2c)
except OSError:
    # Clear the screen
    display.set_pen(RED)
    display.clear()
    display.set_pen(WHITE)
    display.text("Multi-Sensor stick not detected! :(", 10, 95, 320, 3)
    display.update()

rad_to_deg = 180/math.pi
print(rad_to_deg)

while True:

    # Set the layer we're going to be drawing to.
    display.set_layer(0)
    
#    lux, _, _, _, _, _, prox = ltr.get_reading()
    ax, ay, az, gx, gy, gz = lsm.get_readings() # Read the LSM6DS3 imu
    

    if az == 0: az = 0.000000001 # Avoid division by zero!
    
    yt = -math.atan(ax / az) * rad_to_deg;
    xt = -math.atan(ay / az) * rad_to_deg;
    print(int(round(xt,1)),int(round(yt,1)))
    # Clear all layers first
    display.set_layer(0)
    display.set_pen(BLACK)
    display.clear()
    display.set_layer(1)
    display.set_pen(BLACK)
    display.clear()
    display.set_pen(RED)
    display.text("X-Tilt: " + str(int(round(xt,0))), 0, 0, 320, 2)
    display.line(160+int(xt),20,160+int(xt),239)
    display.set_pen(GREEN)
    display.text("Y-Tilt: " + str(int(round(yt,0))), 160, 0, 320, 2)
    display.line(0,135+int(yt),319,135+int(yt))
    
    display.update()
    time.sleep(0.1)

I found it awkward to fully tilt the screen so decided to limit the movement in the creation of a simple game. Here the player has to tilt the board to position the blue circle directly under the target, a yellow circle. Exact positioning of the centres is required.

# Pimoroni Explorer and Sensor Stick Tilt Demo Game
# Tony Goodhew - 22nd November 2024

'''
    Install sensor stick above the display screen with BluTacK or similar
  
    Tilt the Pimoroni Explorer until the blue circle exactly fits under the yellow target circle.
   
'''
from explorer import display, i2c, BLACK, WHITE, RED, GREEN, BLUE, YELLOW, CYAN, MAGENTA
from breakout_ltr559 import BreakoutLTR559
from lsm6ds3 import LSM6DS3
from breakout_bme280 import BreakoutBME280
import time
import math
import random

# Clear all layers first
display.set_layer(0)
display.set_pen(BLACK)
display.clear()
display.set_layer(1)
display.set_pen(BLACK)
display.clear()

try:
    ltr = BreakoutLTR559(i2c)
    lsm = LSM6DS3(i2c)
    bme = BreakoutBME280(i2c)
except OSError:
    # Clear the screen
    display.set_pen(RED)
    display.clear()
    display.set_pen(WHITE)
    display.text("Multi-Sensor stick not detected! :(", 10, 95, 320, 3)
    display.update()

#rad_to_deg = 180/math.pi
x_tar = random.randint(75,245)
y_tar = random.randint(50,220)

while True:
    # Set the layer we're going to be drawing to.
    display.set_layer(0)
    
    ax, ay, az, gx, gy, gz = lsm.get_readings() # Read the LSM6DS3 imu
    
    if az == 0: az = 0.000000001
    yt = int(-math.atan(ax / az) * 120)
    if yt < -90: yt = -90
    if yt > 90: yt = 90
    xt = int(-math.atan(ay / az) * 120)
    if xt < -90: xt = -90
    if xt > 90: xt = 90
    
#    print(int(round(xt,1)),int(round(yt,1)))
    # Clear all layers first
    display.set_layer(0)
    display.set_pen(BLACK)
    display.clear()
    display.set_layer(1)
    display.set_pen(BLACK)
    display.clear()
    display.set_pen(RED)
    display.text("X-Tilt: " + str(int(round(xt,0))), 0, 0, 320, 2)
    display.line(160+int(xt),45,160+int(xt),225)
    display.set_pen(GREEN)
    display.text("Y-Tilt: " + str(int(round(yt,0))), 160, 0, 320, 2)
    display.line(71,135+int(yt),250,135+int(yt))
    display.set_pen(BLUE)
    display.line(69,44,251,44) # Draw blue box
    display.line(69,226,251,226)
    display.line(69,44,69,226)
    display.line(251,44,251,226)
    display.circle(160+int(xt),135+int(yt),5) # Draw current position
    display.set_pen(YELLOW)
    display.circle(x_tar, y_tar,5) # Draw target
    display.update()
    
    # Check if circles exactly overlap
    if (x_tar == 160+int(xt)) and (y_tar == 135+int(yt)): # HIT?
        display.set_pen(BLACK)
        display.clear()
        display.set_pen(RED)
        display.text("BANG",50,120,320,7)
        display.update()
        time.sleep(1.5)
        # Get new target position
        x_tar = random.randint(75,245)
        y_tar = random.randint(50,220)
    time.sleep(0.1)

I hope someone finds this useful.

1 Like

Hello Tony,

This analog clock and digital station example is a big help and it looks great too! I was curious if you had a method of setting the clock that I missed in the code?

Also thank you for your post on the IMU, also greatly helpful for me. The combination of a large screen and sensor stick really is a great platform for experimenting and learning!

Best regards, and happy holidays,
David

It’s funny you should ask that. I was working on the code to do it this morning. I based the method on way I set the time on my digital camera.

# Set time with qwstpad
# Tony Goodhew - 23 Dec 2024
# U increase value
# D decrease number
# R move column right
# L move column left
# - finish

import time
from qwstpad import QwSTPad
from explorer import display, i2c, BLACK,RED,GREEN,BLUE,YELLOW,CYAN,MAGENTA,WHITE

# Connect single default I2C address QwSTPad
try:
    qwstpad = QwSTPad(i2c, 0x21)
except OSError:
    print("QwSTPad: Not Connected ... Exiting")
    raise SystemExit

WIDTH, HEIGHT = display.get_bounds()
maxx = [12,59,59]
minn = [1,0,0]
val = [1,30,30]

display.set_backlight(1.0)
display.set_layer(0)
display.set_pen(BLACK)
display.clear()
display.set_layer(1)
display.set_pen(BLACK)
display.clear()

def clean():    
    display.set_layer(0)
    display.set_pen(BLACK)
    display.clear()
    display.set_layer(1)
    display.set_pen(BLACK)
    display.clear()
    
def show_vals(cur,val):
    for i in range(3):
        xx = 20 + i * 60
        display.set_pen(RED)
        if cur == i:
            display.set_pen(YELLOW)
        display.text(str(val[i]),xx,yy,200,3)

p = 0
yy = 200
show_vals(p,val)
display.update()

buttons = qwstpad.read_buttons()
while buttons["-"] == False: # HALT loop with button'-'
    clean()
    # Read all the buttons from the qwstpad 
    buttons = qwstpad.read_buttons()
    if buttons["U"] == True:
        val[p] = val[p] +1
        if val[p] > maxx[p]:
            val[p] = maxx[p]

    elif buttons["D"] == True:
        val[p] = val[p] - 1
        if val[p] < minn[p]:
            val[p] = minn[p]
    elif buttons["R"] == True:
        p = p + 1
        if p > 2:
            p = 2
    
    elif buttons["L"] == True:
        p = p - 1
        if p < 0:
            p = 0
    show_vals(p,val)
    display.update()
    time.sleep(0.1)
    
clean()
display.update()
h = val[0]
m = val[1]
s = val[2]

print (h,m,s)

I’ll leave you to join it up to the previous code. I’ve not really tested it yet. Let me know if there is a problem.

Have fun and a very Merry Christmas. I’m off for a piece of cake and looking forward to my Presto board arriving!

I’ve just realised that the qwstpad is not part of the KIT. I will convert to Explorer buttons and provide the code shortly.

(The qwstpad is a great addition to the Explorer kit. I’ve connected it with the longer cable and fixed it to the desktop with a few small bits of BluTack. I find it much easier to use than the buttons on the Explorer when the legs are fitted to hold the Explorer up for easy viewing. The layout and number of buttons are great.)

1 Like

Here it is working with Explorer buttons:

# Set time with Explorer buttons
# Tony Goodhew - 23 Dec 2024
# X increase value
# Y decrease number
# Z move column right
# C move column left
# A finish

import time

from explorer import display, i2c, BLACK,RED,GREEN,BLUE,YELLOW,CYAN,MAGENTA,WHITE,button_a, button_b, button_c, button_x, button_y, button_z

WIDTH, HEIGHT = display.get_bounds()
maxx = [12,59,59]
minn = [1,0,0]
val = [1,30,30]

display.set_backlight(1.0)
display.set_layer(0)
display.set_pen(BLACK)
display.clear()
display.set_layer(1)
display.set_pen(BLACK)
display.clear()

def clean():    
    display.set_layer(0)
    display.set_pen(BLACK)
    display.clear()
    display.set_layer(1)
    display.set_pen(BLACK)
    display.clear()
    
def show_vals(cur,val):
    for i in range(3):
        xx = 20 + i * 60
        display.set_pen(RED)
        if cur == i:
            display.set_pen(YELLOW)
        display.text(str(val[i]),xx,yy,200,3)

p = 0
yy = 200
show_vals(p,val)
display.update()

while button_a.value() == 1: # HALT loop with button'A'
    clean()
    if button_x.value() == 0:
        val[p] = val[p] +1
        if val[p] > maxx[p]:
            val[p] = maxx[p]

    elif button_y.value() == 0:
        val[p] = val[p] - 1
        if val[p] < minn[p]:
            val[p] = minn[p]
    elif button_z.value() == 0:
        p = p + 1
        if p > 2:
            p = 2
    
    elif button_c.value() == 0:
        p = p - 1
        if p < 0:
            p = 0
    show_vals(p,val)
    display.update()
    time.sleep(0.1)
    
clean()
display.update()
h = val[0]
m = val[1]
s = val[2]

print (h,m,s)

Please, let me know how you get on.

The clock code at the top picks up the date, day and time from these lines.

# Get current time
year, month, day, h, m, s, wd, _ = time.localtime()

days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]


It works if you are running the code while connected to a computer. If you are running on a battery you have to set the start time by hand

This looks great Tony!

Thank you for taking the time to respond with such a thorough answer. My apologies for the delayed reply as I was out of town over the holidays. I plan to spend some time with this asap!

Best regards and thank you again for your time and knowledge,
David

Hi Tony,

I wanted to share that, after some time and effort, I used your clock and time set example to create a fully functional, adjustable clock! It was quite a journey, but thanks to your guidance, I learned much along the way. I spent plenty of time diving into documentation, and asking CoPilot and Claude questions as I worked on it.

Your code provided a goal and framework for me to work towards, and I truly appreciate it. I’ve attached my file here for you and anyone else who might find it interesting.

Best regards,
David

# Pimoroni Explorer Demo
# Analog/ Digital clock - Tony Goodhew, Leicester UK - 6 Nov 2024
# Time Adjust - Tony Goodhew, Leicester UK - 23 Dec 2024
# Modified - David Warner, Detroit MI, US - 17 Mar 2025
# Uses the Multi-Sensor Stick (BME280 + LTR559 + LSM6DS3)

from explorer import display, i2c, button_a, button_b, button_c, button_z, button_x, button_y, YELLOW
from breakout_bme280 import BreakoutBME280
from breakout_ltr559 import BreakoutLTR559
import math, time, sys

# get display dimensions in pixels
WIDTH, HEIGHT = display.get_bounds()		# explorer display is 320*240
back_lt = 1.0								# backlight variable for cl_set_disp function (range is 0.0 to 1.0)

# set pen rgb (red, green, blue) colors
BLACK = display.create_pen(0, 0, 0)			
WHITE = display.create_pen(253, 240, 213)	# pure white is (255,255,255)
RED = display.create_pen(193, 18, 31)		# pure red is (255,0,0)
BLUE = display.create_pen(37, 87, 115)		# pure blue is (0,0,255)
  
# create lists for days and months 
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]

# create clock center, radius, and number placement delta values 
cx, cy, l = 212, 132, 105 	# clock face center x, y, and radius 
dx, dy = 12, 12				# clock face numbers x and y delta position value

# get current time and date: default epoch = (1970, 1, 1, 0, 0, 0, 3, 1) 
year, month, day, h, m, s, wd, _ = time.localtime()	

# is multi-sensor stick present? if not exit with message 
try:
    bme = BreakoutBME280(i2c, address=0x76)
    ltr = BreakoutLTR559(i2c)
except RuntimeError:
    display.set_layer(0)
    display.set_pen(RED)
    display.clear()
    display.set_pen(WHITE)
    display.text("Multi-Sensor", 60, 95, 320, 3)
    display.text("Stick missing", 60, 125, 320, 3)
    display.update()
    sys.exit()

# function to clear layers, set backlighting, and update display 
def cl_set_disp(bl):	# backlight value is passed to function when called
    display.set_backlight(bl)	
    display.set_layer(0)
    display.set_pen(BLACK)
    display.clear()
    display.set_layer(1)
    display.set_pen(BLACK)
    display.clear()
    display.update()

# function to clear layers, but not update display
def clean():    
    display.set_layer(0)
    display.set_pen(BLACK)
    display.clear()
    display.set_layer(1)
    display.set_pen(BLACK)
    display.clear()

# function for hour and minute hands
def hand(ang, long): 
    ang = ang-90
    ls = long/13	# was originally "long/10" for wider hands at pivot
    x0 = int(round(long * math.cos(math.radians(ang))))+cx
    y0 = int(round(long * math.sin(math.radians(ang))))+cy
    x1 = int(round(ls * math.cos(math.radians(ang+90))))+cx	# + x width coordinate perpindicular to hand length
    y1 = int(round(ls * math.sin(math.radians(ang+90))))+cy	# + y width coordinate perpindicular to hand length
    x2 = int(round(ls * math.cos(math.radians(ang-90))))+cx	# - x width coordinate perpindicular to hand length
    y2 = int(round(ls * math.sin(math.radians(ang-90))))+cy	# - y width coordinate perpindicular to hand length
    display.triangle(x0,y0,x1,y1,x2,y2)
    display.circle(cx,cy,int(ls))

# function to set time manually 
def set_time():
    # button assignment: x = ^, y = v, z = >, c = <, a = set
            
    maxx = [12,31,2100,"",24,59]	# maximum values [mo, day, year, "space", hrs, min]
    minn = [1,1,2025,"",1,0]		# minimum values [mo, day, year, "space", hrs, min]
    val = [1,1,2025,"",12,30]		# initial selection [mo, day, year, "space", hrs, min]
      
    def show_vals(cur,val):
        for i in range(6):
            display.set_pen(WHITE)
            # display labels: (string, x, y, wordwrap, scale, angle, spacing)
            display.text("Month", 30, yt, 140, 2, 90)
            display.text("Day", 75, yt, 140, 2, 90)
            display.text("Year", 138, yt, 140, 2, 90)
            display.text("Hour 1 - 24", 213, yt, 140, 2, 90)
            display.text("Minute", 262, yt, 140, 2, 90)
            
            xx = 21 + i * 45 	# x-axis position for val display
            display.set_pen(RED)
            if cur == i:
                display.set_pen(YELLOW)
            display.text(str(val[i]),xx,yy,200,2)

    p = 0
    yt = 106	# yt is for label display hight on display
    yy = 76		# yy is for val display hight on display
    show_vals(p,val)
    display.update()  
    
    while button_a.value() == 1: # halt loop with "set" button'A'
        
        # draw icons on screen for buttons
        clean()											# clear layers but do not update display
        display.set_pen(WHITE)
        display.circle(18, 44, 5)   					# "set" dot, display.rectangle(x, y, r)
        display.text("Set", 35, 38, 100, 2)				# "set" text, display.text(text, x, y, wordwrap, scale)
        display.triangle(303, 38, 293, 51, 313, 51)		# "up" arrow, display.triangle(x1, y1, x2, y2, x3, y3)
        display.triangle(303, 127, 293, 114, 313, 114)	# "down" arrow, display.triangle(x1, y1, x2, y2, x3, y3)
        display.triangle(12, 195, 28, 205, 28, 185)		# "previous" arrow, display.triangle(x1, y1, x2, y2, x3, y3)
        display.triangle(311, 195, 295, 205, 295, 185)	# "next" arrow, display.triangle(x1, y1, x2, y2, x3, y3)
        
        if button_x.value() == 0:
            time.sleep(0.2)		# delay for pseudo button debounce
            val[p] = val[p] +1
            if val[p] > maxx[p]:
                val[p] = maxx[p]

        elif button_y.value() == 0:
            time.sleep(0.2)		# delay for pseudo button debounce
            val[p] = val[p] - 1
            if val[p] < minn[p]:
                val[p] = minn[p]
        elif button_z.value() == 0:
            time.sleep(0.2)		# delay for pseudo button debounce
            p = p + 1
            if p > 5:
                p = 0
        
        elif button_c.value() == 0:	# Note: eliminate 'c' button for 4 button displays
            time.sleep(0.2)		# delay for pseudo button debounce
            p = p - 1
            if p < 0:
                p = 5
        show_vals(p,val)
        display.update()
        time.sleep(0.1)
        
    cl_set_disp(back_lt)	# clear display and set backlight
    
    mo_set = val[0]
    day_set = val[1]
    yr_set = val[2]
    # val[3] is a space which is a string and will not work with machine.RTC()
    h_set = val[4]
    m_set = val[5]
        
    # update real time clock (rtc) with user entered values 
    rtc = machine.RTC()				# create real time clock object
    # set rtc time (yr, mo, day, weekday, h, m, s, sub-seconds)
    rtc.datetime((yr_set, mo_set, day_set, 0, h_set, m_set, 0, 0))	
    current_time = rtc.datetime()	# read the updated current time

# function to draw clock face and titles
def dr_clk_face():
    display.set_pen(BLUE)	# create large blue circle
    display.circle(cx,cy,l)	
    display.update()
    display.set_pen(WHITE)	# create white marks on ring every 6 degrees
    for angle in range(0,360,6):
        xx = int(round(l * math.cos(math.radians(angle))))
        yy = int(round(l * math.sin(math.radians(angle))))    
        display.line(cx,cy,cx+xx,cy+yy)
    display.set_pen(BLACK)	# overlay black circle on blue circle for 8 pixel thick blue ring effect
    display.circle(cx,cy,l-8)	
    display.set_pen(WHITE)	# overlay long white lines every 30 deg to correspond with clock facenumbers
    for angle in range(0,360,30):
        xx = int(round(l * math.cos(math.radians(angle))))
        yy = int(round(l * math.sin(math.radians(angle))))    
        display.line(cx,cy,cx+xx,cy+yy)
    
    display.set_pen(WHITE)	# sensor titles in white
    display.text("Ambient Light",5,80,300,1)
    display.text("Rel Humidity",5,120,300,1)
    display.text("Temperature",5,160,310,1)
    display.text("Air Pressure",5,200,300,1)

# test if year is current, if not call set_time() 
if year <= 2024:	# test if year is less than 2024
    set_time()

# call function to clear display layers, set backlighting, update display
cl_set_disp(back_lt)	# backlight set to 1.0 

#  call function to draw clock face and sensor titles
dr_clk_face()

# =============== main loop ================
while True:
    # draw time set button for manual time set
    display.circle(312, 200, 3)			# "set" dot, display.rectangle(x, y, r)
    display.text("Set",305,210,320,1)	# "set" text
    
    # goto time setting routine upon button 'Z' input
    while button_z.value() == 0:
        time.sleep(0.3) 	# delay for pseudo button debounce
        set_time()			# call set_time function
        dr_clk_face()		# call draw clock face upon return from set time function
    
    # draw face numbers
    display.set_pen(BLACK)
    display.circle(cx,cy,93) 	# clear centre of clock face
    display.set_pen(WHITE)
    display.text("9",118 +dx,108 +dy,320,3)	# dx = 12 pixel x delta 
    display.text("3",268 +dx,108 +dy,320,3)	# dy = 10 pixel y delta
    display.text("10",128 +dx,70 +dy,320,3)	# TODO, low priority: delta was for quick clock face move during tests, remove when done
    display.text("2",258 +dx,70 +dy,320,3)
    display.text("1",230 +dx,45 +dy,320,3)
    display.text("11",160 +dx,45 +dy,320,3)
    display.text("12",193 +dx,37 +dy,320,3)
    display.text("6",193 +dx,186 +dy,320,3)	
    display.text("7",156 +dx,176 +dy,320,3)
    display.text("5",230 +dx,176 +dy,320,3)
    display.text("4",258 +dx,150 +dy,320,3)
    display.text("8",130 +dx,150 +dy,320,3)
    
    # draw hands
    mang = int((m + s/60)* 6)   # angle of minute hand
    hang = int((h + m/60 )* 30)	# angle of hour hand
    display.set_pen(BLUE)
    hand(mang,90)
    display.set_pen(RED)      
    hand(hang,65)
    lens = 93					# length of second hand
    sang = 6 * s - 90			# angle of second hand
    xs = int(round(lens * math.cos(math.radians(sang))))
    ys = int(round(lens * math.sin(math.radians(sang))))
    display.set_pen(WHITE)
    display.line(cx,cy,cx+xs,cy+ys)
    display.circle(cx,cy,3)
    
    # convert 24 hours to 12 hours with am/pm "time of day" (tod) labels
    tod = "AM"			# start with am
    if h >= 12:			# check if hours are greater than 12
        tod = "PM"		# if true time of day is pm
        h -= 12			# if true subtract 12 hours
    if h == 0:			# check for zero, ie noon or midnight
        h = 12			# if true add 12 hours
    
    # assemble digital time
    ss = f"{s:02}"			# f-string formats value to two digits and, 
    ms = f"{m:02}"			# adding a leading zero if needed
    hs = f"{h:02}"
    dt = f"{hs}:{ms}:{ss}"	# concatenate hr, min, and sec with colons(:) in-between
    
    # clear text areas on display
    display.set_pen(BLACK)
    display.rectangle(5,10,170,18)	# time area
    display.rectangle(5,50,90,18)	# date area
    display.rectangle(5,90,101,18)	# lux area
    display.rectangle(5,130,80,18)	# humidity
    display.rectangle(5,170,105,28)	# temperature
    display.rectangle(5,210,115,18)	# pressure
    
    # write digital time and tod
    display.set_pen(BLUE)
    display.text(dt,5,10,320,3)
    display.text(tod,125,10,320,3)
        
    # read the sensors and convert to us measures
    prox, a, b, c, d, e, lux = ltr.get_reading()
    temperature, pressure, humidity = bme.read()
    temp_f = round(((temperature*1.8)+32),1)			# convert temp to °F, round to 1 place
    pressure_inhg = round((pressure*0.0002952998),2) 	# convert press to inHg, round to 2 places
    
    # display sensor readings
    display.set_pen(RED)
    display.text(str(int(lux))+" lx",5,90,320,3)		# display lux, integers only
    display.text(str(int(humidity)) +" %", 5,130,320,3)	# display humidity, integers only
    display.text(str(temp_f)+" °F",5,170,320,3) 		# display temperature
    display.text(str(pressure_inhg)+" in",5,210,320,3)	# display pressure
        
    # update time and date values
    year, month, day, h, m, s, wd, _ = time.localtime()
    display.set_pen(WHITE)
    display.text(days[wd],5,40,320,1)						# wd is weekday (ie., monday, tuesday, ...)
    display.set_pen(BLUE)
    display.text(str(day)+" "+months[month-1],5,50,320,3)	# [month-1] make time month value equal list[0] 
    display.update()
    time.sleep(0.1) 

Thanks for posting your code. You have been busy. It looks great! Nice layout with the settings page.
I’m glad someone has found my posts useful. It is useful to have feedback.

1 Like

Good hear back from you Tony, I wanted you to know that you are making a difference! I’m just sorry that it took so long to reply back to you with the clock code. My job gets in the way of my hobbies, I’m looking forward to retirement this July!