Shutdown Switch for a Raspberry Pi – or: too many Legs

A Raspberry Pi lacks a shutdown button. To keep the price down I heard.
However, simply cutting off the power of a running Pi might damage the system. This is avoidable with a simple button.

In projects that require a Pi running headless (without display) it certainly helps to have a button which triggers a safe shutdown procedure. It might not always be possible to log on to the Pi, run the custom shutdown procedure for the specific project and manually type the shutdown command.

A script triggered by a simple button can do this! The script can trigger the custom shutdown procedure for the project and can turn off the Pi afterwards.

Here’s the description of how it works for me:

Hardware

Raspberry Pi (Zero) with running OS etc
Momentary switch
Some cables

Wiring

Wiring
Wiring

The switch is connected to GND and a free pin next to the GND pin. In this case it is BCM pin 21. When the switch is pressed, an edge is detected. This signal can be used to trigger the desired actions.

Software

Short vs. Long Press

A short and a long press can be easily distuingished using a simple counter in a callback function. The callback function is triggered when an edge is detected on pin 21.

# This script will wait for a button to be pressed and then shutdown or reboot the Raspberry Pi.
# A long press initiates a reboot, a very long press initiates a shutdown.

import time
from time import sleep
import RPi.GPIO as GPIO
import os

##############CONFIG##########################
demo_mode = False
debug = False
GPIN=21
##############END CONFIG######################

GPIO.setmode(GPIO.BCM)

# Pin 21 will be input and will have its pull-up resistor (to 3V3) activated
# so we only need to connect a push button with a current limiting resistor to ground
GPIO.setup(GPIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
int_active = 0

print "Shutdown / Reboot script started."

# ISR: if our button is pressed, we will have a falling edge on pin 21
# this will trigger the interrupt:
def shutdown_reboot_callback(channel):

# button is pressed
# possibly shutdown our Raspberry Pi

global int_active

# only react when there is no other shutdown process running

if (int_active == 0):
int_active = 1
pressed = 1
shutdown = 0

# count how long the button is pressed
counter = 0

while ( pressed == 1 ):
if ( GPIO.input(GPIN) == False ):
# button is still pressed
counter = counter + 1
if debug:
print "pressed: " + str(counter)
# break if we count beyond 20 (long-press is a shutdown)
if (counter >= 20):
pressed = 0
else:
sleep(0.2)
else:
# button has been released
pressed = 0

# button has been released, count cycles and determine action

# count how long the button was pressed
if (counter < 2):
# short press, do nothing
pressed = 0
int_active = 0
if debug:
print str("short press, do nothing")

else:
if debug:
print "else " + str(counter)
# longer press
if (counter < 10):
# medium length press, initiate system reboot
if debug:
print("rebooting..")

# run the reboot command as a background process
if demo_mode:
print("sudo reboot now")
exit(0)
else:
os.system("sudo reboot now")

elif (counter>=10 and counter<20):
# long press, initiate system shutdown

if debug:
print("shutting down..")

# run the shutdown command as a background process
if demo_mode:
print("sudo shutdown now")
exit(0)
else:
os.system("sudo shutdown now")


# Program pin 21 as an interrupt input:
# it will react on a falling edge and call our interrupt routine "shutdown_reboot_callback"
GPIO.add_event_detect(GPIN, GPIO.FALLING, callback = shutdown_reboot_callback, bouncetime = 500)

while True:
if debug:
print "."
time.sleep(1)

Setup

To launch the script at startup of the Raspberry Pi place it on the pi (e.g. in /usr/bin) and  add this line to /etc/rc.local just before exit 0:

sudo python /usr/bin/shutdown_reboot.py&
Shutdown / Reboot switch on a Raspberry Pi Zero
Shutdown / Reboot switch on a Raspberry Pi Zero

And here is the solution for the attentive reader on the remark on too many legs: a common momentary switch has four legs and is squared. Actually it is a matter of orientation whether the switch works as expected…

A smart Raspberry Pi Zero DIY Text Clock

So this is the project I had in mind when I was experimenting with a NeoPixel strip on a Raspberry Pi Zero. The original text clock was invented a couple of years ago. With its elegant and timeless (yes, literally) design the QLOCKTWO is simultaneously a beautiful and useful piece of art.
It is not that I exactly needed yet another clock – but I got intrigued and wanted to create my own, smart version of a text clock.

Numerous examples of DIY versions and manuals on how to build a text clock are available on the internet. Some manuals involve soldering a lot of LEDs. I wanted to skip this step and went for a NeoPixel strip. In total I calculated 92 NeoPixels: one for each letter that can be alighted.

My version of the text clock should not only display the time in a unique way but should also indicate something more, it should be smart! This is why I chose a Raspberry Pi Zero instead of a microcontroller as a base. This way I’m able to easily get more information using a Python script along with some ready-made libraries.

My smart text clock indicates whether I have unread emails in my inbox by changing the colour of the LEDs. If desired the smart text clock is also able to indicate the weather developments depending on the outside temperature or any other criteria. The weather data is taken from openweathermap.org as in former projects.

One could also try to indicate whether a train one needs to catch regularly is on time. Or the smart text clock could be used as a traffic monitor for commuters (similar to this project idea).

Updating the smart text clock every 5 minutes should be precise enough for me. It is definitely more precise than a fuzzy clock which indicates bright and dark only.

Hardware

The hardware list of the last blog entry can be extended by the picture frame which is often used for DIY text clocks. A suitable one is sold by a well-known swedish furniture store.
Additionally some paper is useful for dispersing the light from the LEDs behind the letters.

Adhesive foil with precisely cut letters can be put on the glass to match the LEDs from the strip. Here I had professional help by friends owning a cutting plotter.

The LED strip is cut and soldered together appropriately to match the letters positions. The strip is glued with its adhesive back to the picture frame’s back plate.

The LEDs are separated by a grid behind the glass. It is printed with a 3D printer. This grid helps to avoid interferences between the different letters.

A piece of transparent paper between the glass and the grid is the possibility to make the letters look smooth. If it was missing the LEDs were directly visible. A bit of diffusion makes it look better…

Software

A straightforward python script is run automatically every five minutes. First the current time is determined. The time is translated into words with a five minute precision.

The words are mapped to the LED indices from the NeoPixel strip. These are the ones to alight to display the time.

Colour Definition

To determine which colour to use for the alighted LEDs some (optional) checks are built-in:

  • Approximately every hour the weather data is fetched from openweathermap.org using the python owm library. The temperature is extracted along with the weather code. The results are used for defining the colour of the LEDs. Other parameters can be taken into account as well.
  • The number of unread emails is checked using the Python imap library. If the number is greater than zero the LED color is changed.

During night time the brightness of the LEDs is lowered. That way the smart text clock serves as a convenient night light as well.

Source Code


#!/usr/bin/python
# -*- coding: cp1252 -*-

import time

import imaplib

import pyowm
import json
import pprint

from neopixel import *

########################CONFIG############################

OWM_APYKEY='get one from openweathermap.org'
OWM_ID = an ID number

# file to store weather state
fileName="/home/pi/textClockWeatherState.txt"

EMAIL_NAME = "user@googlemail.com"
EMAIL_PASS = "password"

# LED strip configuration:
LED_COUNT = 92 # Number of LED pixels.
LED_PIN = 18 # GPIO pin connected to the pixels (must support PWM!).
LED_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz)
LED_DMA = 5 # DMA channel to use for generating signal (try 5)
LED_BRIGHTNESS = 128 # Set to 0 for darkest and 255 for brightest
LED_INVERT = False # True to invert the signal (when using NPN transistor level shift)

######################END#CONFIG##########################

_start = "IT IS "
_end = " O\'CLOCK"
_numbers = ('ONE', 'TWO', 'THREE', 'FOUR', 'FIVE', 'SIX', 'SEVEN', 'EIGHT', 'NINE', 'TEN', 'ELEVEN', 'TWELVE')
_past = ' PAST '
_to = ' TO '
_fivepast = 'FIVE PAST '
_tenpast = 'TEN PAST '
_aquarter = 'A QUARTER '
_twenty = ' TWENTY'
_twentyfive = ' TWENTYFIVE'
_half = ' HALF'
_fiveto = 'FIVE TO '
_tento = 'TEN TO '

'''
I T L I S A S T I M E 0,1, 2,3
A C Q U A R T E R D C 4, 5,6,7,8,9,10,11
T W E N T Y F I V E X 12,13,14,15,16,17, 18,19,20,21
H A L F B T E N F T O 22,23,24,25, 26,27,28, 29,30
P A S T E R U N I N E 31,32,33,34, 35,36,37,38
O N E S I X T H R E E 39,40,41, 42,43,44, 45,46,47,48,49
F O U R F I V E T W O 50,51,52,53, 54,55,56,57, 58,59,60
E I G H T E L E V E N 61,62,63,64,65, 66,67,68,69,70,71
S E V E N T W E L V E 72,73,74,75,76, 77,78,79,80,81,82
T E N S E O C L O C K 83,84,85
'''
# map time to precise LED indices
_timeLightMap = {
'IT IS ' : (0,1,2,3),
' HALF' : (22,23,24,25),
' PAST ' : (31,32,33,34),
' TO ' : (29,30),
'FIVE PAST ' : (18,19,20,21, 31,32,33,34),
'TEN PAST ' : (26,27,28, 31,32,33,34),
'A QUARTER ' : (4, 5,6,7,8,9,10,11),
' TWENTY' : (12,13,14,15,16,17),
' TWENTYFIVE' : (12,13,14,15,16,17, 18,19,20,21),
' HALF PAST ' : (22,23,24,25, 31,32,33,34),
' TWENTYFIVE TO ' : (12,13,14,15,16,17, 18,19,20,21, 29,30),
' TWENTY TO ' : (12,13,14,15,16,17, 29,30),
'TEN TO ' : (26,27,28, 29,30),
'FIVE TO ' : (18,19,20,21, 29,30),
'ONE' : (39,40,41),
'TWO' : (58,59,60),
'THREE' : (45,46,47,48,49),
'FOUR' : (50,51,52,53),
'FIVE' : (54,55,56,57),
'SIX' : (42,43,44),
'SEVEN' : (72,73,74,75,76),
'EIGHT' : (61,62,63,64,65),
'NINE' : (35,36,37,38),
'TEN' : (83,84,85),
'ELEVEN' : (66,67,68,69,70,71),
'TWELVE' : (77,78,79,80,81,82),
' O\'CLOCK' : (0,1,2,3)
}

class SmartTextClock():

def run(self):
print "A SMART TEXT CLOCK"

def check_googlemail(self, login, password):
# if new mail return # emails
obj = imaplib.IMAP4_SSL('imap.gmail.com','993')
obj.login(login, password)
obj.select()
nofUnreadMessages = len(obj.search(None, 'UnSeen')[1][0].split())
print "Unread emails: " + str(nofUnreadMessages)
return nofUnreadMessages

def clock(self):
t = time.strftime("%H:%M")
print t
return t

def translateHour(self, hour, offset):
if hour == '00' or hour == '12':
if offset == True:
return _numbers[0]
else:
return _numbers[11]
if hour == '1' or hour == '13':
if offset == True:
return _numbers[1]
else:
return _numbers[0]
if hour == '2' or hour == '14':
if offset == True:
return _numbers[2]
else:
return _numbers[1]
if hour == '3' or hour == '15':
if offset == True:
return _numbers[3]
else:
return _numbers[2]
if hour == '4' or hour == '16':
if offset == True:
return _numbers[4]
else:
return _numbers[3]
if hour == '5' or hour == '17':
if offset == True:
return _numbers[5]
else:
return _numbers[4]
if hour == '6' or hour == '18':
if offset == True:
return _numbers[6]
else:
return _numbers[5]
if hour == '7' or hour == '19':
if offset == True:
return _numbers[7]
else:
return _numbers[6]
if hour == '8' or hour == '20':
if offset == True:
return _numbers[8]
else:
return _numbers[7]
if hour == '9' or hour == '21':
if offset == True:
return _numbers[9]
else:
return _numbers[8]
if hour == '10' or hour == '22':
if offset == True:
return _numbers[10]
else:
return _numbers[9]
if hour == '11' or hour == '23':
if offset == True:
return _numbers[11]
else:
return _numbers[10]
return ''

# time format: HH:mm
def translateTime(self, time):
t = time.split(':', 1)
print t
h = str(t[0])
m = str(t[1])
print h + ":" + m

indices = (1,2)

if float(m) >= 0.0 and float(m) <= 2.5:
#IT IS X O'CLOCK
indices = _timeLightMap[_start] + _timeLightMap[self.translateHour(h, False)] + _timeLightMap[_end]
if float(m) > 2.5 and float(m) <= 7.5:
#IT IS FIVE PAST X O'CLOCK
indices = _timeLightMap[_start] + _timeLightMap[_fivepast] + _timeLightMap[self.translateHour(h, False)] + _timeLightMap[_end]
if float(m) > 7.5 and float(m) <= 12.5:
#IT IS TEN PAST X O'CLOCK
indices = _timeLightMap[_start] + _timeLightMap[_tenpast] + _timeLightMap[self.translateHour(h, False)] + _timeLightMap[_end]
if float(m) > 12.5 and float(m) <= 17.5:
#IT IS A QUARTER PAST X O'CLOCK
indices = _timeLightMap[_start] + _timeLightMap[_aquarter] + _timeLightMap[_past] + _timeLightMap[self.translateHour(h, False)] + _timeLightMap[_end]
if float(m) > 17.5 and float(m) <= 22.5:
#IT IS TWENTY PAST X O'CLOCK
indices = _timeLightMap[_start] + _timeLightMap[_twenty] + _timeLightMap[_past] + _timeLightMap[self.translateHour(h, False)] + _timeLightMap[_end]
if float(m) > 22.5 and float(m) <= 27.5:
#IT IS TWENTYFIVE PAST X O'CLOCK
indices = _timeLightMap[_start] + _timeLightMap[_twentyfive] + _timeLightMap[_past] + _timeLightMap[self.translateHour(h, False)] + _timeLightMap[_end]
if float(m) > 27.5 and float(m) <= 32.5:
#IT IS HALF PAST X O'CLOCK
indices = _timeLightMap[_start] + _timeLightMap[_half] + _timeLightMap[_past] + _timeLightMap[self.translateHour(h, False)] + _timeLightMap[_end]
if float(m) > 32.5 and float(m) <= 37.5:
#IT IS TWENTYFIVE TO X O'CLOCK
indices = _timeLightMap[_start] + _timeLightMap[_twentyfive] + _timeLightMap[_to] + _timeLightMap[self.translateHour(h, True)] + _timeLightMap[_end]
if float(m) > 37.5 and float(m) <= 42.5:
#IT IS TWENTY TO X O'CLOCK
indices = _timeLightMap[_start] + _timeLightMap[_twenty] + _timeLightMap[_to] + _timeLightMap[self.translateHour(h, True)] +_timeLightMap[_end]
if float(m) > 42.5 and float(m) <= 47.5:
#IT IS A QUARTER TO X O'CLOCK
indices = _timeLightMap[_start] + _timeLightMap[_aquarter] + _timeLightMap[_to] + _timeLightMap[self.translateHour(h, True)] + _timeLightMap[_end]
if float(m) > 47.5 and float(m) <= 52.5:
#IT IS TEN TO X O'CLOCK
indices = _timeLightMap[_start] + _timeLightMap[_tento] + _timeLightMap[self.translateHour(h, True)] + _timeLightMap[_end]
if float(m) > 52.5 and float(m) <= 57.5:
#IT IS FIVE TO X O'CLOCK
indices = _timeLightMap[_start] + _timeLightMap[_fiveto] + _timeLightMap[self.translateHour(h, True)] + _timeLightMap[_end]
if float(m) > 57.5 and float(m) <= 59.9:
#IT IS TO X O'CLOCK
indices = _timeLightMap[_start] + _timeLightMap[self.translateHour(h, True)] + _timeLightMap[_end]
return indices

def selectColor(self, weatherCondition):
# Email: Lime Green 50-205-50
# http://www.tayloredmktg.com/rgb/
color = Color(255, 222, 173)
# weatherCondition = 'fair' # 'good', 'fair, 'bad'
if weatherCondition == 'fair':
color = Color(255, 222, 173) # Navajo White 255-222-173 # Lemon Chiffon 255-250-205
if weatherCondition == 'good':
color = Color(255, 127, 80) # Coral 255-127-80 # Light Salmon 255-160-122
if weatherCondition == 'bad':
color = Color(70, 130, 180) # Steel Blue 70-130-180
return color

def getWeatherFromOWM(self):
owm = pyowm.OWM(OWM_APYKEY, version='2.5')
# Search for current weather
print "Weather @ID"
obs = owm.weather_at_id(OWM_ID)
w1 = obs.get_weather()
print(w1)
print w1.get_status()

weatherCondition = 'fair' # 'good', 'fair, 'bad'

# get general meaning for weather codes https://openweathermap.org/weather-conditions
weatherCode = w1.get_weather_code()
print weatherCode
print w1.get_sunset_time('iso')
temperature = w1.get_temperature('celsius')['temp']
print str(temperature) + " C"

# simple: judge weather on temperature
if temperature<=10.0:
weatherCondition = 'bad'
if temperature>10.0 and temperature<20.0:
weatherCondition = 'fair'
if temperature>=20.0 and temperature<35.0:
weatherCondition = 'good'
if temperature>=35.0:
weatherCondition = 'bad'

return weatherCondition

def readSavedWeatherCondition(self):
weatherCondition = 'fair' # default
try:
print 'Read file ' + fileName
target = open(fileName, 'r')
weatherCondition = target.read()
print weatherCondition
target.close()
except IOError:
print fileName + " does not exist yet. Creating a default file."
self.saveWeatherConditionToFile(weatherCondition)
pass
return weatherCondition

def saveWeatherConditionToFile(self, weatherCondition):
try:
print 'Write file ' + fileName
target = open(fileName, 'w')
target.write(weatherCondition)
target.close()
except:
print 'File ' + fileName + ' could not be written.'

# NEOPIXEL: GRB
#strip.setPixelColor(i, Color(0,0,120)) # B
#strip.setPixelColor(i, Color(0,120,0)) # R
#strip.setPixelColor(i, Color(120,0,0)) # G
def alight(self, LEDindices, color):
print 'Alight indices ' + str(LEDindices)
for i in LEDindices:
strip.setPixelColor(i, color)
strip.show()

if __name__ == "__main__":
app = SmartTextClock()
app.run()

# check for unread emails
unreadEmails = app.check_googlemail(EMAIL_NAME, EMAIL_PASS)

# get time and determine LED indices
time = app.clock()
indices = app.translateTime(time)
print "Indices " + str(indices)

# investigate weather data
weatherCondition = app.readSavedWeatherCondition()

# update weather data every hour
theTime = time.split(":")
# this range should be met from time to time
if int(theTime[1])>=0 and int(theTime[1])<=7:
weatherCondition = app.getWeatherFromOWM()
app.saveWeatherConditionToFile(weatherCondition)

# create NeoPixel object with appropriate configuration
strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
# intialize the library (must be called once before other functions)
strip.begin()

# low light during 19-8 o'clock
if(8 < int(theTime[0]) > 19):
strip.setBrightness(200)
else:
strip.setBrightness(50)

stripColor = Color(120,120,120)

# select color depending on weather condition
if weatherCondition == 'bad':
stripColor = Color(0, 120, 0)
if weatherCondition == 'fair':
stripColor = Color(120, 120, 120)
if weatherCondition == 'good':
stripColor = Color(0,0,120)

if unreadEmails > 0:
stripColor = Color(205,50,50)

app.alight(indices, stripColor)

 

Audio Book: Raspberry Pi Zero Internet Radio with pHAT BEAT

A couple of days ago I laid my hands on a pHAT BEAT and two small speakers. Together with a Raspberry Pi Zero (and an Internet connection of course) this makes building an internet radio easily possible. And yes, inspiration for this project was also the Pirate Radio Kit.
The pHAT BEAT comes along with stereo output, an amplifier, a couple of buttons for adjusting the volume, playing/pause, forward/backward and powering off and a number of bright and shiny LEDs. Just the perfect audio hardware component for an internet radio.

Hardware

Raspberry Pi Zero with Micro SD card and up-to-date OS
USB WiFi stick (not needed if a Raspberry Pi Zero W is used)
pHAT BEAT
2
small speakers
some cables
USB power supply

Assemble the hardware as required. This implies some soldering for the headers of the Raspberry Pi Zero and the pHAT BEAT as well as the connections to the speakers. This tutorial is a good guideline to see what to do.

Software

Once the Raspberry Pi Zero is accessible headless in the local WLAN network (see this blog post for setup instructions) install the pHAT BEAT Python library.

Luckily the software for an internet radio project already exists. The setup is really made simple by running the setup script only. The setup script installs the required software and adjusts the whole configuration on the Raspberry Pi Zero. See https://github.com/pimoroni/phat-beat/tree/master/projects/vlc-radio for further reference.

Once the installation is complete, reboot. After reboot the internet radio will be automatically started and will play some example music.

The pHAT BEAT’s buttons directly work with the example projects software. Adjusting the volume or switching between different items on a configurable playlist (see configuration below) is directly possible. Even the off button immediately works: it turns off the radio and fully shuts down the Raspberry Pi Zero.

Configuration

Configure Internet Radio Streams

Collect the URLs of your favourite internet radio streams. Create the file /home/pi/.config/vlc/playlist.m3u . Insert the URLs into the playlist as in this example:

Example playlist.m3u
#EXTM3U
#EXTINF:0,station1
#EXTVLCOPT:network-caching=1000
http://station1.net/.../...
#EXTINF:0,station2
http://station2.com/.../.../mp3/...
#EXTINF:0,station3
http://station3.something/...

Alternatively create a playlist containing the radio stream URLs of your choice in VLC and save the playlist to a file. This file can be copied to the Raspberry Pi Zero to /home/pi/.config/vlc/playlist.m3u.

After reboot the forward/backward buttons of the pHAT BEAT can be used to switch between the different internet radio streams.

Wrapping: The Result

The wrapping was simple in this case: an old book became a nice „audio book“! Similar to my ‚book book shelves‘ an old book is hollowed inside with a sharp knife so the hardware fits in.
Surprisingly well is the sound of the speakers inside the book!
All I need now is to find a way to operate the small buttons of the pHAT BEAT…

Info & Links

https://github.com/pimoroni/phat-beat

 

NeoPixels Strip on Raspberry Pi Zero

Looking into my desk’s drawer I found the remainder of an Adafruit NeoPixel strip I used in another project. And an unused, last years Raspberry Pi Zero. Does that work together? Well, yes, it does! At least after fiddling a bit with hard- and software and circumventing some common traps.

Searching the web I found a tutorial for steering a NeoPixel strip with a first generation Raspberry Pi. Technically it should work with an exemplary of a more recent version, but it did not initially.
Here is the description of how it all worked out in the end:

Hardware

Raspberry Pi Zero with up-to-date Raspbian Jessie Pixel
Mini USB WiFi Adapter (if the brand new Raspberry Pi Zero W is not used)
Raspberry Pi Zero adapter cables + power supply
Adafruit NeoPixel strip
1000 μF capacitor
330 Ω resistor
1N4001 diode
5 V breadboard power supply
breadbord, cables

Connections

  • 5V power supply GND : 1000 μF capacitor (short leg)
  • 5V power supply 5V : 1000 μF capacitor
  • 5V power supply GND : NeoPixel strip GND
  • 5V power supply 5V : NeoPixel strip 5V via 1N4001 diode (side with stripe goes to 5V input of the strip)
  • 5V power supply GND : Raspberry Pi Zero GND (physical pin 6)
  • Raspberry Pi Zero (physical pin 12) : NeoPixel strip data line via 330 Ω resistor

The available pins of the Raspberry Pi Zero are listed here. GPIO #1 correlates to physical pin 12 which is BCM #18. The latter is used in the Python software.

Don’t!

It is not recommended to use the 5V output of the Raspberry Pi Zero directly to power the NeoPixel strip. The pixels might draw too much current and might therefore damage the pin. It would have been way too convenient…so: an additional 5V power supply is strongly recommended.

Software

Running Headless: Setting up WiFi

To run the Raspberry Pi Zero headless (without display), set up the WiFi connection. For this step an HDMI display and a keyboard is required. Open the file

sudo nano /etc/wpa_supplicant/wpa_supplicant.conf

Put the network configuration at the end of the file:

network={
ssid="WiFi network name"
psk="password"
}

Use raspi-config to allow SSH connections and to adjust the Pi’s hostname, the password, the time settings etc. .

SSH to Zero

When attaching the Mini USB WiFi Adapter instead of the keyboard and rebooting the Raspberry Pi Zero the desired WiFi network is used and it is possible to SSH to the Pi Zero. To find the IP adress in the local network check which devices are logged into the network at your routers access point. Or kindly ask your network admin to check. 😉

GPIO Checks

To see the available GPIO pins on the Raspberry Pi Zero run

gpio readall

NeoPixel Python Library

To set up the Python library for driving NeoPixels on a Raspberry follow this tutorial. Jeremy Garff’s Python library for NeoPixels is working like a charm.

Disabling Audio

To be able to use the PWM pins as data pins for the NeoPixel strip I disabled audio by commenting the line

# Enable audio (loads snd_bcm2835)
#dtparam=audio=on  # disable audio for PWM pin usage

in the file /boot/config.txt.

Whether audio is disabled can be checked using

aplay -l

. If audio is disabled properly the result is an error message („aplay: device_list:268: no soundcards found…“).

Examples

Once the NeoPixel library is set up and the hardware is connected properly run strandtest.py or any other example code from the rpi_ws281x/python/examples section.

Result

That’s it! The NeoPixel strip finally can be driven by a Raspberry Pi Zero.

neopixeltest

While this example is working I definitively have a new project in mind…

Links

https://learn.adafruit.com/neopixels-on-raspberry-pi/overview

https://github.com/jgarff/rpi_ws281x

Raspberry Pi 3 Gesture controlled digital Picture Frame

The first time I realised the possibilities of gesture controlled devices I was dazzled. It was the kick-started project of a smart bike assistant, the Haiku. The Haiku’s idea is to use flick gestures to switch between functions or to handle notifications, independent of possibly covered fingertips. No direct touching is required!

How simple is it to steer something without touching? Without taking off gloves or anything that hampers control as it might with common touch sensitive devices such as smartphones or tablets? Without leaving fingerprints or accidentally painting the unlock pattern on a smudgy touchscreen (take a look at a smartphone in grazing light!)?
Gesture control sounds highly convenient and probably more safe than touch control to me. Although, one could accidentally do something by moving a bit too close on a gesture control input. That is truly a side effect…

During a hacking night with fellow workers I learnt about Pimoroni’s skywriter. Attachable to either an Arduino controller or the Raspberry Pi the skywriter allows to recognise gestures such as flicks and taps as well as X/Y/Z 3D position sensing within a range of 15 cm. I ordered one to test it for another project… so why not use a skywriter to see through photographs in a directory? Displaying images in a digital picture frame with a convenient input method.

Components used

Raspberry Pi 3

Raspberry Pi 7″ Touch Screen (any other monitor for a Pi will do)

Skywriter breakout or HAT

Wiring

The wiring is described in pimoroni’s github repository. The Raspberry Pi 3 pinouts are described here.

Skywriter Raspberry Pi
GND GND
TRFR GPIO 27
RESET GPIO 17
SCL GPIO 3 / SCL
SDA GPIO 2 / SDA
VCC 3 V

Software

At pimoroni’s github repository some very good examples are found for using the skywriter either on an Arduino controller or a Raspberry. On the Raspberry the same libraries have to be installed, e.g. with the given shell command as described in the readme.

For the Raspberry I started with the Python example touch.py. Each recognised gesture and a move’s coordinates are printed on the console. The print statement in the move() method obscures the results of the recognised gestures, it can be commented.

Python’s TKinter toolkit is used to display pictures in a window. The TKinter mainloop runs in a separate thread.

Usage

The usage should be natural and intuitive.

  • Flicking from left to right will display the next image in the directory.
  • Flicking from right to left will switch back to the previous image.
  • Tapping into the centre of the skywriter will close the program
  • Tapping onto the lower end will minimise the image window.
  • Guess how to maximise the image window again!

This is the whole Python script to move through (holiday) pictures using gesture control on the digital picture frame:

#!/usr/bin/python

print('''
Switch between different images in a directory using the skywriter.

Swipe left to right: display next image in directory
Swipe north to south: display next image in directory
Swipe right to left: display previous image in directory
Swipe south to north: display previous image in directory

Tap the lower left corner to minimize the image window
Tap the upper right corner to maximize the image window

Press CTRL+C, ESC or tap the skywriter's center to exit.
''')
# use a Tkinter label as a panel/frame with a background image
# note that Tkinter only reads gif and ppm images
# use the Python Image Library (PIL) for other image formats
# free from [url]http://www.pythonware.com/products/pil/index.htm[/url]
# give Tkinter a namespace to avoid conflicts with PIL
# (they both have a class named Image)

import Tkinter as tk
from PIL import Image, ImageTk
from ttk import Frame, Button, Style
import gtk, pygtk
import time

import sys
import os
import signal
import skywriter
import threading
import random

pathToPictures = "/home/pi/Desktop/images/"

class ImageDisplay(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)
        self.root = None
        self.start()

    def callback(self):
        self.root.quit()

    def run(self):
        if self.root == None:
        self.root = tk.Tk()
        self.root.title('My Photographs')

    self.root.mainloop()

    def setImageName(self, name):
        self.name = name

    def showImage(self, path):
        self.original = Image.open(path)

        # make the root window the size of the screen
        screen_size = getScreenSize()
        self.root.geometry("%dx%d+%d+%d" % (screen_size["width"],         screen_size["height"], 0, 0))
        #self.root.attributes("-fullscreen", False)
        #self.root.overrideredirect(True)
                       #self.root.geometry({0}x{1}+0+0".format(self.root.winfo_screenwidth(), self.root.winfo_screenheight()))
        ##self.root.geometry("{0}x{1}+0+0".format(screen_size["width"],       screen_size["height"]))
        self.root.focus_set()  # >-- move focus to this widget
        self.root.bind(">Escape<", lambda e: e.widget.quit())

        self.resized = self.original.resize((screen_size["width"],        screen_size["height"]), Image.ANTIALIAS)
        self.image = ImageTk.PhotoImage(self.resized) # keep a reference, prevent GC

        # root has no image argument, so use a label as a panel
        self.panel1 = tk.Label(self.root, image = self.image)
        self.display = self.image
        self.panel1.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.YES)
        print "Display image " + path

    def updateImage(self, path):
        self.original = Image.open(path)
        # resize
        screen_size = getScreenSize()
        self.root.geometry("%dx%d+%d+%d" % (screen_size["width"], screen_size["height"], 0, 0))

        self.resized = self.original.resize((screen_size["width"], screen_size["height"]), Image.ANTIALIAS)
        self.image = ImageTk.PhotoImage(self.resized) # keep a reference, prevent GC

        # root has no image argument, so use a label as a panel
        self.panel1.configure(image=self.image)
        self.display = self.image
        print "Display image " + path

    def minimize(self):
        self.root.iconify()
    def maximize(self):
        self.root.deiconify()

    def stopThread(self):
        self.root.quit()
        self.do_run = False  # stop thread

#---Utilities---#
def getScreenSize():
    window = gtk.Window()
    screen = window.get_screen()
    print "width = " + str(screen.get_width()) + ", height = " + str(screen.get_height())
    screen_size = {}
    screen_size["width"] = screen.get_width()
    screen_size["height"] = screen.get_height()
    return screen_size

def findImages(directory):
    imageList = []
    for file in os.listdir(directory):
        if file.endswith(('.jpg','.JPG','.jpeg','.JPEG')):
            print(file)
            imageList.append(file)
    return imageList

def increaseIndex():
    global index, images
    index += 1
    # start again with index 0
    if index >= len(images):
	index = 0

def decreaseIndex():
    global index, images
    index -= 1
    # start again with index max
    if index < 0:
	index = len(images) - 1

def nextImage():
    global imageDisplay, images, index, pathToPictures
    increaseIndex()
    print "image " + images[index] + ", index=" + str(index) + "(" + str(len(images)) + ")"
    imageDisplay.updateImage(pathToPictures + images[index])

def previousImage():
    global imageDisplay, images, index, pathToPictures
    decreaseIndex()
    print "image " + images[index] + ", index=" + str(index) + "(" + str(len(images)) + ")"
    imageDisplay.updateImage(pathToPictures + images[index])

#---detect gestures on skywriter---#
@skywriter.flick()
def flick(start,finish):
    print('Got a flick!', start, finish)
    if (start == "west" and finish == "east") or (start == "south" and finish == "north"):
        print "Display next image in directory"
        nextImage()
    if (start == "east" and finish == "west") or (start == "north" and finish == "south"):
        print "Display previous image in directory"
        previousImage()

@skywriter.touch()
def touch(position):
    print('Touched!', position)
    if (position == "center"):
        print "Exit image display"
        imageDisplay.stopThread()
        sys.exit()
    if (position == "south"):
        print "minimize image window"
        imageDisplay.minimize()
    if (position == "north"):
        print "maximize image window"
        imageDisplay.maximize()

# parse picture folder
images = findImages(pathToPictures)

# reset index
index = 0

# launch image window as thread
imageDisplay = ImageDisplay()

def main():
    try:
        print "Skywriter image display launched"
        getScreenSize()
        print "Images found: "
        for i in images:
            print i
        global imageDisplay, pathToPictures
        imageDisplay.showImage(pathToPictures + images[index])
    except KeyboardInterrupt:
        print "Exit"
        imageDisplay.stopThread()
        sys.exit()
    pass
if __name__ == '__main__':
    main()

Result

skywriter_photo
Gesture controlled Picture Frame

Raspberry Pi 3 wears a Display-O-Tron HAT

Two weeks ago I laid my hands on a Raspberry Pi 3. For sure I did not buy the 10.000.000th one. I ordered just a week before this milestone wich was a good excuse for discounts, but anyway, a Raspberry Pi is a nice and versatile thing to play with.

Since there are already a huge number of tutorials on how to set up and configure a Raspberry Pi I will spare the details. For this example of using the Display-O-Tron HAT on a Raspberry lets assume your Pi is already set up with a Linux distribution, knows Python and is connected to the internet.

dothat

An Idea

My idea is to use the Display-O-Tron HAT as display for several purposes. The Display-O-Tron HAT comes with a three line LCD display, a row of very bright LEDs on the right and 6 touch buttons. Each button can trigger the display of different information such as

  • system statistics
  • the number of unread emails, signaling incoming messages
  • process states (dead or alive?)
  • date and time
  • head lines of a news feed

The Sources

Based on the examples for the Display-O-Tron HAT on github I created a simple, straightforward, quick and dirty Python script.

On the push of a button the LCD display will be alighted for a couple of seconds in a different colour and the desired information will be retrieved and displayed. After a couple of seconds the backlight LEDs will be turned off. This is realized using threads that will finish after the desired time has passed.
The functionality that is implemented is a check for unread emails on googlemail’s IMAP servers, a simple date and time display, a quick internet connection and system status check and a status check of certain processes.
If unread emails were detected the LEDs on the (b)right side will blink three times in a row. Every minute the display will switch back to the default view: date and time.

#!/usr/bin/env python
print('''
Switch between different views using the Display-o-Tron HAT buttons

Main button: Display date and time
Right: Display number of unread emails
Left: Display system statistics
Down: Display process status
Up: TODO find something else to display

Press CTRL+C to exit.
''')

import dothat.touch as touch
import dothat.lcd as lcd
import dothat.backlight as backlight
import signal
import sys

import psutil
import urllib2
import subprocess
import imaplib
import time
from datetime import date
import calendar
import threading

'''

Captouch provides the @captouch.on() decorator
to make it super easy to attach handlers to each button.

The handler will receive 'channel'( corresponding to a particular
button ID ) and 'event' ( corresponding to press/release ) arguments.
'''

'''DISPLAY utilities'''
# store time of last button touch
# to be able to reset backlight LEDs
oldTime = time.time()
turnedOff = True

def resetBacklightLEDs():
    global oldTime
    oldTime = time.time()
    global turnedOff
    turnedOff = False

def clearDOT():
    lcd.clear()

def colorDOT(r, g, b):
    backlight.rgb(r, g, b)

def turnOnLEDsDOT():
    # turn on LEDs one by one in a row
    for led in range(6):
        backlight.graph_set_led_state(led, 1)
        time.sleep(0.1)
    pass

def turnOffDOT():
    colorDOT(0, 0, 0)  # backlight off
    backlight.off()
    backlight.graph_off()  # side LEDs off
    global turnedOff
    turnedOff = True

def dotClock():
    clearDOT()

    d = time.strftime("%d.%m.%Y")
    t = time.strftime("%H:%M")
    the_date = date.today()
    day = calendar.day_name[the_date.weekday()]
    print day + ", " + d + " / " + t

    lcd.set_cursor_position(3, 0)
    lcd.write(day)
    lcd.set_cursor_position(3, 1)
    lcd.write(d)
    lcd.set_cursor_position(5, 2)
    lcd.write(t)

def dotMails(login, password):
    nofUnreadEmails = check_googlemail(login, password)
    if nofUnreadEmails > 0:
        lcd.write("Unread Emails: " + str(nofUnreadEmails))
        showNewMessages(200, 0, 0)

    else:
        lcd.write("No new mail")

def dotSystemStats():
    lcd.set_cursor_position(0, 0)
    lcd.write(check_internet())

    lcd.set_cursor_position(0, 1)
    lcd.write("CPU: " + check_CPU())

    lcd.set_cursor_position(0, 2)
    lcd.write("Memory: " + check_memory())

def check_internet():
    try:
        # ping google to check whether internet connection works
        response = urllib2.urlopen('http://www.google.com', timeout=1)
        return "Internet: OK"
    except urllib2.URLError as err: pass
    return "Internet connection broken"

def check_CPU():
    cpu_usage = str(psutil.cpu_percent(interval=None)) + " %"
    print "CPU usage: " + cpu_usage
    return cpu_usage

def check_memory():
    mem = psutil.virtual_memory()
    # print "Memory: " + str(mem)
    memory_used = str(mem.percent) + " %"
    print "Memory used: " + memory_used
    THRESHOLD = 100 * 1024 * 1024  # 100MB
    if mem.available >= THRESHOLD:
        print("Warning, memory low")
        return "Warning, memory low"
    return memory_used

def get_pid(name):
    try:
        pids = subprocess.check_output(["pidof", name])
    except subprocess.CalledProcessError as pids:
        print "error code", pids.returncode, pids.output
        return ""
    return map(int, pids.split())

def get_single_pid(name):
    return int(check_output(["pidof", "-s", name]))

def check_process(name):
    PID = get_pid(name)
    if len(PID) == 1:
        print "PID " + name + ": " + str(PID[0])
        p = psutil.Process(PID[0])
        status = ""
        if p.status == psutil.STATUS_ZOMBIE:
            status = "Process " + name + " died"
            print status
        else:
            status = "Process " + name + " OK"
            print status
            return status
        return ""

def dotProcessStats():
    lcd.set_cursor_position(0, 0)     
    process1 = check_process('geany')
    if len(process1) > 1 :    
        lcd.write(process1)
    else:
        lcd.write("geany is dead.")

    lcd.set_cursor_position(0, 1)
    process2 = check_process('bash')
    if len(process2) > 1 :
        lcd.write(process2)
    else:
        lcd.write("bash is dead.")

    lcd.set_cursor_position(0, 2)
    process3 = check_process('firefox')
    if len(process3) > 1 :
        lcd.write(process3)
    else:
        lcd.write("firefox is dead.")

def check_googlemail(login, password):
    # if new mail return # emails
    obj = imaplib.IMAP4_SSL('imap.gmail.com', '993')
    obj.login(login, password)
    obj.select()
    nofUnreadMessages = len(obj.search(None, 'UnSeen')[1][0].split())
    print "Unread emails: " + str(nofUnreadMessages)
    return nofUnreadMessages

class ShowNewMessagesThread (threading.Thread):
    red, green, blue = 0, 0, 0  # static elements, it means, they belong to the class

    def run (self):
        colorDOT(self.red, self.green, self.blue)
        for i in range(0, 3):
            turnOnLEDsDOT()
            time.sleep(2)
            turnOffDOT()
            time.sleep(2)

            if i == 2:
                print "stop ShowNewMessagesThread"
                self.do_run = False  # stop thread
                break

snmt = ShowNewMessagesThread()
def showNewMessages(r, g, b):
    global snmt
    if snmt.is_alive():
        return
    snmt = ShowNewMessagesThread()
    snmt.red = r
    snmt.green = g
    snmt.blue = b
    snmt.daemon = True  # enable stop of thread along script with Ctrl+C
    snmt.start()

class AlightThread (threading.Thread):
    red, green, blue = 0, 0, 0  # static elements, it means, they belong to the class

    def run (self):
        colorDOT(self.red, self.green, self.blue)
        while True:
        if turnedOff == False:
            if time.time() - oldTime > 5:
                print "stop AlightThread"
                turnOffDOT()
                self.do_run = False  # stop thread
                break

at = AlightThread()
def alightDisplay(r, g, b):
    global at
    if at.is_alive():
        return
    at = AlightThread()
    at.red = r
    at.green = g
    at.blue = b
    at.daemon = True  # enable stop of thread along script with Ctrl+C
    at.start()

class ClockThread (threading.Thread):
    def run (self):
        print "run update clock thread " + str(self)
        dotClock()
        self.do_run = False  # stop thread

    def stopClockThread(self):
        self.do_run = False  # stop thread

ct = ClockThread()
def updateClock():
    global ct
    if ct.is_alive():
        print "Clock thread " + str(ct) + " is alive."
        ct.stopClockThread()
        return
    print "Launching clock thread " + str(ct)
    ct = ClockThread()
    ct.daemon = True  # enable stop of thread along script with Ctrl+C
    ct.start()

'''DOT touch button handler'''
@touch.on(touch.UP)
def handle_up(ch, evt):
    print("Up pressed: TODO find another useful display idea")
    clearDOT()
    alightDisplay(255, 0, 255)
    lcd.write("Up up and away: TODO")
    resetBacklightLEDs()

@touch.on(touch.DOWN)
def handle_down(ch, evt):
    print("Down pressed: display process states")
    clearDOT()
    alightDisplay(255, 0, 0)
    dotProcessStats()
    resetBacklightLEDs()

@touch.on(touch.LEFT)
def handle_left(ch, evt):
    print("Left pressed: display system statistics")
    clearDOT()
    alightDisplay(0, 100, 200)
    dotSystemStats()
    resetBacklightLEDs()

@touch.on(touch.RIGHT)
def handle_right(ch, evt):
    print("Right pressed, check for new email")
    clearDOT()
    alightDisplay(100, 200, 255)
    dotMails('email adress', 'password')
    resetBacklightLEDs()

@touch.on(touch.BUTTON)
def handle_button(ch, evt):
    print("Main button pressed: show date and time")
    clearDOT()
    alightDisplay(255, 255, 255)
    updateClock()
    resetBacklightLEDs()

@touch.on(touch.CANCEL)
def handle_cancel(ch, evt):
    print("Cancel pressed!")
    clearDOT()
    backlight.rgb(0, 0, 0)
    lcd.write("Cancel")
    resetBacklightLEDs()
    alightDisplay(20, 20, 20)

'''main'''
if __name__ == '__main__':
    try:
    while True:
        timeDiff = time.time() - oldTime
        # print "time diff: " + str(timeDiff)

        # update clock every minute
        if timeDiff > 59 or timeDiff < 0.5:
        oldTime = time.time()
        alightDisplay(255, 255, 255)
        updateClock()
        time.sleep(1)
    except KeyboardInterrupt:
        print "exit"
        clearDOT()
        turnOffDOT()
        sys.exit()

 

Links

Raspberry Pi 3 Model B

Display-O-Tron HAT

https://github.com/pimoroni/dot3k

https://www.python.org/