Saturday, February 29, 2020

Clock with Weather - Pi Zero, ILI9341 & DS18B20

Clock and Temperature display


Using an ILI9341 SPI display with a DS18B20 Temperature sensor connected to a Raspberry PI Zero.

I did a search on Youtube and found a starting point.  The script I found had me running in just a few minutes.  I made some modifications and posted that below.  Hopefully this helps someone.

I'm using a SunFounder DS18B20 to get the temperature in the room.  I only want an accurate time source with the temperature.  Using a Pi Zero with NTP and the temperature sensor is all I need.  To display the data, I found a 2.2 inch ILI9341.  I hope to get this into a case more suitable for a desktop.

To get the DS18B20 connected, I followed the instructions from the vendor:  https://www.sunfounder.com/learn/sensor-kit-v2-0-for-raspberry-pi-b-plus/lesson-26-ds18b20-temperature-sensor-sensor-kit-v2-0-for-b-plus.html

To get the display connected and setup the modules, I did a couple of searches on the web and used this one: https://pi0cket.com/ili9341-raspberry-pi-guide/

My abbreviated config for the display is at the bottom of this post.  I was running Stretch Lite when I originally wrote this article.   For Buster Lite on a Pi display, I have those steps abbreviated at the bottom of this post.


Clock based on first script below

The three connections on the left are the temperature sensor.  From the grey wire to the right are the 9 connectors for the display

The other side of the Pi Zero to show those connections

The ILI9341 connections

SunFounder DS18B20

Raspberry PI Pinout

<---- ILI9341 Pin to Raspberry PI Zero Pin ---->

SDO/MISO ---- 21 (GPIO9)

LED      ---- 12 (GPIO18)

SCK      ---- 23 (GPIO11)

SDI/MOSI ---- 19 (GPIO10)

DC/RS    ---- 18 (GPIO24)

RESET    ---- 22 (GPIO25)

CS       ---- 24 (GPIO8)

GND      ---- 20 (GND)

VCC      ---- 17 (3v3)


<---- Sunfounder DS18B20 Sensor Pin to Raspberry PI Zero Pin ---->

SIG (1) ---- 7 (GPIO4)

VCC (2) ---- 2 (5v)

GND (3) ---- 6 (GND)


<---- Begin Code Section for myclock.py ---->

#!/usr/bin/python
#----------------------------------------------------------------
#       Note:
#               ds18b20's data pin must be connected to pin7.
#               replace the 28-XXXXXXXXX as yours.
#----------------------------------------------------------------

import pygame, sys, os, time, datetime, signal
from pygame.locals import *
os.environ["SDL_FBDEV"] = "/dev/fb1"

## Globals

pygame.init()

## Set up the screen

display_width = 320
display_height = 240
#display_width = 800
#display_height = 480

DISPLAYSURF = pygame.display.set_mode((display_width, display_height), 0, 16)
pygame.mouse.set_visible(0)
pygame.display.set_caption('Room Temp')

# set up the colors
BLACK = (  0,   0,   0)
WHITE = (255, 255, 255)
RED   = (255,   0,   0)
GREEN = (  0, 255,   0)
COBALTGREEN = ( 61, 145,  64)
BLUE  = (  0,   0, 255)
CYAN  = (  0, 255, 255)
YELLOW  = (255, 255,  0)
BANANA = (227,207,87)
GOLD1 = (255,215,0)
EMERALDGREEN = (0, 201, 87)
ALICEBLUE = (240,248,255)

ds18b20 = ''

def setup():
        global ds18b20
        for i in os.listdir('/sys/bus/w1/devices'):
                if i != 'w1_bus_master1':
                        ds18b20 = i

def readTemp():
#       location = '/sys/bus/w1/devices/28-00000a423922/w1_slave'
        location = '/sys/bus/w1/devices/' + ds18b20 + '/w1_slave'
        tfile = open(location)
        text = tfile.read()
        tfile.close()
        secondline = text.split("\n")[1]
        temperaturedata = secondline.split(" ")[9]
        temperature = float(temperaturedata[2:])
        temperature = temperature / 1000
        return temperature

def signal_handler (signal, frame):
        pygame.quit()
        sys.exit(0)

def DrawLine(color, startX,startY,stopX,stopY):
        pygame.draw.line(DISPLAYSURF, color, [startX,startY], [stopX,stopY], 1)

## Start
signal.signal(signal.SIGINT, signal_handler)
setup()

## Main loop

while True:

        currenttime = datetime.datetime.now()

        if readTemp() != None:
                currenttemp = readTemp()
                temp_c = str("{0:.1f}".format(currenttemp))
                temp_f = str("{0:.1f}".format((currenttemp*9/5)+32))

## Draw the title

        black_square_that_is_the_size_of_the_screen = pygame.Surface(DISPLAYSURF.get_size())
        black_square_that_is_the_size_of_the_screen.fill((0, 0, 0))
        DISPLAYSURF.blit(black_square_that_is_the_size_of_the_screen, (0, 0))

        font = pygame.font.Font(None, 30)
        text = font.render("Room Temp", 1, EMERALDGREEN)
        textpos = text.get_rect(center=(display_width*.5,int(round(.08*display_height))))
        DISPLAYSURF.blit(text, textpos)

## Draw temperatures

        font = pygame.font.Font(None, 70)
        text = font.render(temp_f, 1, GOLD1)
        textpos = text.get_rect(center=(display_width*.25,int(round(.30*display_height))))
        DISPLAYSURF.blit(text, textpos)

        font = pygame.font.Font(None, 20)
        textF = font.render(u'\u00b0' + "F", 1, GOLD1)
        textposF = textpos[0] + textpos[2], textpos[1] + 10
        DISPLAYSURF.blit(textF, textposF)

        font = pygame.font.Font(None, 70)
        text = font.render(temp_c, 1, GOLD1)
        textpos = text.get_rect(center=(display_width*.75,int(round(.30*display_height))))
        DISPLAYSURF.blit(text, textpos)

        font = pygame.font.Font(None, 20)
        textF = font.render(u'\u00b0' + "C", 1, GOLD1)
        textposF = textpos[0] + textpos[2], textpos[1] + 10
        DISPLAYSURF.blit(textF, textposF)

## Draw date

        font = pygame.font.Font(None, 40)
        text = font.render(currenttime.strftime("%A, %b %d"), 1, ALICEBLUE)
        textpos = text.get_rect(center=(display_width/2,int(round(.58*display_height))))
        DISPLAYSURF.blit(text, textpos)

## Draw time

        font = pygame.font.Font(None, 85)
        text = font.render(currenttime.strftime("%I:%M %p"), 1, ALICEBLUE)
        textpos = text.get_rect(center=(display_width/2,int(round(.79*display_height))))
        DISPLAYSURF.blit(text, textpos)

## Draw Lines

        DrawLine(EMERALDGREEN, 5, int(round(.16*display_height)), display_width-5, int(round(.16*display_height)))
        DrawLine(EMERALDGREEN, 5, int(round(.45*display_height)), display_width-5, int(round(.45*display_height)))
        DrawLine(EMERALDGREEN, display_width*.5, int(round(.16*display_height)), display_width*.5, int(round(.45*display_height)))

## Update the LCD

        pygame.display.update()

## Sleep time!

        time.sleep(15)

<---- End Code Section ---->


Abbreviated display configuration:

Assuming you have updated your install (apt update and apt upgrade) and have the DS18B20 connected and working, here are the steps I used based on the Pi0cket blog mentioned above.  These steps will get the display running your script automatically on a reboot:

1. I use python3 so make sure that is ready:
sudo rm /usr/bin/python
sudo ln -s /usr/bin/python3 /usr/bin/python
sudo apt install python3-pip
sudo pip3 install pygame==1.9.6

2. SDL 1.2 gets installed using the following:

sudo apt-get install libsdl1.2-dev

sudo apt-get install libsdl-image1.2-dev

sudo apt-get install libsdl-ttf2.0-dev

 
one line to get all 3:
sudo apt-get install -y libsdl1.2-dev libsdl-image1.2-dev libsdl-ttf2.0-dev

3. Execute:  sudo raspi-config

-Enable SPI (in raspi-config -> Interfacing Options -> P4 SPI -> Yes)
-Disable Overscan (in raspi-config -> Advanced Options -> A2 Overscan -> No) 
-Exit raspi-config


4. Execute:  sudo nano /etc/modules
at the bottom of file add:

spi-bcm2835
fbtft_device  

my file looks like this:

# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.

spi-bcm2835
fbtft_device

5. Execute:  sudo nano /etc/modprobe.d/fbtft.conf

options fbtft_device name=fb_ili9341 gpios=reset:25,dc:24,led:18 speed=16000000 bgr=1 rotate=90 custom=1

my file has only one line and looks like this:
options fbtft_device name=fb_ili9341 gpios=reset:25,dc:24,led:18 speed=16000000 bgr=1 rotate=90 custom=1

 6.  Make the ILI9341 display show your content:

con2fbmap 1 1

7.  If the display shows up, reboot.

sudo reboot

8. I added the python script to run my display script by modifying rc.local
Execute:  sudo nano /etc/rc.local
add:  /home/pi/bin/myclock.ph &

my rc.local looks like this:

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.


# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi

sudo python /home/pi/myclock.py &

exit 0

9. If you're feeling comfortable, reboot your pi.
sudo reboot

You should see the results of your python script after the reboot is completed.

Utilize OpenWeatherMap data

I cleaned up some of my earlier script above.  I also added an API call to get some additional weather information using openweathermap.org data.  You'll need to register with openweathermap.  The basic weather information is free to access.  I wrote a separate blog that discusses openweathermap a little more.  If you only need that information, it's here.   Below is the python script with the additional data.

Shown with the added weather data

#!/usr/bin/python
#----------------------------------------------------------------
#       Note:
#               ds18b20's data pin must be connected to pin7.
#               replace the 28-XXXXXXXXX as yours.
#----------------------------------------------------------------

import pygame, sys, os, time, datetime, signal, requests
from pygame.locals import *
os.environ["SDL_FBDEV"] = "/dev/fb1"

## Globals

pygame.init()

## Set up the screen

display_width = 320
display_height = 240
display_center = int(display_width*.5)
display_rcenter = int(display_width*.75)
display_lcenter = int(display_width*.25)

DISPLAYSURF = pygame.display.set_mode((display_width, display_height), 0, 16)
pygame.mouse.set_visible(0)
pygame.display.set_caption('Room Temp')

# set up the colors
BLACK = (  0,   0,   0)
WHITE = (255, 255, 255)
RED   = (255,   0,   0)
GREEN = (  0, 255,   0)
COBALTGREEN = ( 61, 145,  64)
BLUE  = (  0,   0, 255)
CYAN  = (  0, 255, 255)
YELLOW  = (255, 255,  0)
BANANA = (227,207,87)
GOLD1 = (255,215,0)
EMERALDGREEN = (0, 201, 87)
ALICEBLUE = (240,248,255)
ROSYBROWN1 = (255,193,193)

ds18b20 = ''

def setup():
        global ds18b20
        for i in os.listdir('/sys/bus/w1/devices'):
                if i != 'w1_bus_master1':
                        ds18b20 = i

def readTemp():
#       location = '/sys/bus/w1/devices/28-00000a423922/w1_slave'
        location = '/sys/bus/w1/devices/' + ds18b20 + '/w1_slave'
        tfile = open(location)
        text = tfile.read()
        tfile.close()
        secondline = text.split("\n")[1]
        temperaturedata = secondline.split(" ")[9]
        temperature = float(temperaturedata[2:])
        temperature = temperature / 1000
        return temperature

def signal_handler (signal, frame):
        pygame.quit()
        sys.exit(0)

def DrawLine(color, startX,startY,stopX,stopY):
        pygame.draw.line(DISPLAYSURF, color, [startX,startY], [stopX,stopY], 1)

def openweather():
        api_key = "get your api key at openweathermap.org/api"
        base_url = "https://api.openweathermap.org/data/2.5/weather?"
        complete_url = base_url + 'id=5391959&appid=' + api_key
        response = requests.get(complete_url)
        if response.status_code != 200:
                exit
        return response.json()


## Start
signal.signal(signal.SIGINT, signal_handler)
setup()

## Main loop

while True:

        currenttemp = readTemp()
        temp_c = str("{0:.1f}".format(currenttemp))
        temp_f = str("{0:.1f}".format((currenttemp*9/5)+32))

        response = openweather()
        outtemp = float((response['main']['temp']-273.15)*9/5+32)
        feeltemp = float((response['main']['feels_like']-273.15)*9/5+32)
        outtemp = str("{0:.1f}".format(outtemp))
        feeltemp = str("{0:.1f}".format(feeltemp))
        weather_desc = response['weather'][0]['description']

## Draw the title

        blank_screen = pygame.Surface(DISPLAYSURF.get_size())
        blank_screen.fill((0, 0, 0))
        DISPLAYSURF.blit(blank_screen, (0, 0))

        font = pygame.font.Font(None, 25)
        text = font.render("Room", 1, EMERALDGREEN)
        textpos = text.get_rect(center=(display_lcenter,16))
        DISPLAYSURF.blit(text, textpos)

        font = pygame.font.Font(None, 25)
        text = font.render("Outside/Feel", 1, EMERALDGREEN)
        textpos = text.get_rect(center=(display_rcenter,16))
        DISPLAYSURF.blit(text, textpos)

## Draw Lines

        DrawLine(COBALTGREEN, 5, 28, display_width-5, 28)
        DrawLine(COBALTGREEN, 5, 114, display_width-5, 114)
        DrawLine(COBALTGREEN, display_center, 28, display_center, 114)

## Draw temperatures

        font = pygame.font.Font(None, 70)
        text = font.render(temp_f, 1, GOLD1)
        textpos = text.get_rect(center=(display_lcenter,56))
        DISPLAYSURF.blit(text, textpos)

        font = pygame.font.Font(None, 20)
        textF = font.render(u'\u00b0' + "F", 1, GOLD1)
        textposF = textpos[0] + textpos[2], textpos[1] + 6
        DISPLAYSURF.blit(textF, textposF)

        font = pygame.font.Font(None, 50)
        text = font.render(temp_c, 1, GOLD1)
        textpos = text.get_rect(center=(display_lcenter,96))
        DISPLAYSURF.blit(text, textpos)

        font = pygame.font.Font(None, 20)
        textF = font.render(u'\u00b0' + "C", 1, GOLD1)
        textposF = textpos[0] + textpos[2], textpos[1] + 5
        DISPLAYSURF.blit(textF, textposF)

        font = pygame.font.Font(None, 70)
        text = font.render(outtemp, 1, GOLD1)
        textpos = text.get_rect(center=(display_rcenter,56))
        DISPLAYSURF.blit(text, textpos)

        font = pygame.font.Font(None, 20)
        textF = font.render(u'\u00b0' + "F", 1, GOLD1)
        textposF = textpos[0] + textpos[2], textpos[1] + 6
        DISPLAYSURF.blit(textF, textposF)

        font = pygame.font.Font(None, 50)
        text = font.render(feeltemp, 1, GOLD1)
        textpos = text.get_rect(center=(display_rcenter,96))
        DISPLAYSURF.blit(text, textpos)

        font = pygame.font.Font(None, 20)
        textF = font.render(u'\u00b0' + "F", 1, GOLD1)
        textposF = textpos[0] + textpos[2], textpos[1] + 5
        DISPLAYSURF.blit(textF, textposF)

        font = pygame.font.Font(None, 20)
        text = font.render(weather_desc, 1, ROSYBROWN1)
        textpos = text.get_rect(center=(display_rcenter,126))
        DISPLAYSURF.blit(text, textpos)

## Draw time

        currenttime = datetime.datetime.now()

        font = pygame.font.Font(None, 85)
        text = font.render(currenttime.strftime("%I:%M %p"), 1, WHITE)
        textpos = text.get_rect(center=(display_center,180))
        DISPLAYSURF.blit(text, textpos)

## Draw date

        font = pygame.font.Font(None, 40)
        text = font.render(currenttime.strftime("%A, %b %d"), 1, ALICEBLUE)
        textpos = text.get_rect(center=(display_center,222))
        DISPLAYSURF.blit(text, textpos)

## Update the LCD

        pygame.display.update()

## Sleep time!

        time.sleep(15) 
 
Abbreviated Buster Steps:
buster lite with pi0 and ili9341 
1. make sure you perform rpi-update to update to the latest firmware:
sudo rpi-update 
2. sudo vi /boot/config.txt
- add to the end of the file: 
dtoverlay=fbtft,ili9341,dc_pin=24,reset_pin=25
dtparam=speed=16000000
dtparam=custom=1
dtparam=bgr=1
dtparam=led_pin=18
dtparam=rotate=90

3. I use python3 so make sure that is ready:
sudo rm /usr/bin/python
sudo ln -s /usr/bin/python3 /usr/bin/python
sudo apt install python3-pip
sudo pip3 install pygame==1.9.6

4. SDL 1.2 gets installed using the following:
sudo apt-get install libsdl1.2-dev

sudo apt-get install libsdl-image1.2-dev

sudo apt-get install libsdl-ttf2.0-dev


If you read the earlier instructions for the Stretch version, you don't need the module setup that we did.  You should be able to reboot the PI and see your display.

 

buster-lite with pi3b+ and 7 inch raspberry pi display

sudo vi /boot/config.txt
- add to the end of the file:
lcd_rotate=2

reboot
sudo raspi-config
Under System Options, set the following for your needs:
S1 Wireless LAN
S4 Hostname
S5 Boot / Auto Login

Under Display Options
D2 Underscan -> Yes to overscan

Interface Options
P2 SSH enable Yes
P7 1-Wire enable Yes

Configure Localization Options for your environment
L1 Locale
L2 Timezone
L3 Keyboard

Finish and reboot
Change the pi user password
passwd
Update and Install additional components:
sudo apt update
sudo apt -y upgrade
sudo rm /usr/bin/python
sudo ln -s /usr/bin/python3 /usr/bin/python
sudo apt install -y python3-pip
sudo apt install -y vim
sudo pip3 install pygame
sudo apt install -y libsdl2-2.0
sudo apt install -y python3-sdl2

start the clock