Explorer HAT Pro PIN entry system

This beginners tutorial will teach you how to combine a couple of different output devices with the capacitive touch buttons on the Explorer HAT Pro, to create a fun little PIN entry system that will flash LEDs and buzz a buzzer when a correct/incorrect code is entered.

We'll learn how to connect up and control LEDs using the Explorer HAT's outputs and a little piezo buzzer using the PWM (pulse width modulation) channel of the Raspberry Pi that's broken out on the Explorer HAT Pro.

Here's what you'll need:

  • A Raspberry Pi
  • Explorer HAT Pro
  • A handful of 470 Ω resistors (at least three)
  • A couple of coloured 5mm LEDs (ideally green and red)
  • A piezo electric buzzer
  • A bunch of jumper leads

Note that you can get all of these in the Explorer HAT Pro parts kit.

Getting the software set up

If your Explorer HAT Pro isn't set up yet, you'll need to do the following:

curl https://get.pimoroni.com/i2c | bash
sudo apt-get install python-smbus
sudo apt-get install python-pip
sudo pip install explorerhat

Those commands will install set up I2C and install the Explorer HAT Python library.

Next, you'll want to plug your Explorer HAT Pro into the 40 pin GPIO connector on your Raspberry Pi. You can check it's working by typing the following straight in the terminal:

python -c 'import time, explorerhat; explorerhat.light.on(); time.sleep(1); explorerhat.light.off()'

That should light up all four of the LEDs on the Explorer HAT Pro board for a second and then switch them all off again. If that works, then your Explorer HAT Pro is good to go!

Connecting the components up

The wiring diagram is shown below.

Make sure that you connect the LEDs up the right way round, with the long leg - the anode - connected via the resistor to the 5V supply and the shorter leg connected to the output channel. The resistors connected to the LEDs are voltage-limiting resistors and reduce the 5V supply to a safe voltage for the LEDs. The piezo buzzer and the resistors can be connected up any which way you'd like.

Explorer HAT Pro pin entry

The code

Whenever I'm writing some code to solve a problem, I like to write out a rough plan of what I need to do - they call it pseudo-code in the trade. Here's what we need to do:

  • Set a four digit PIN code
  • Keep track of the button numbers pressed
  • Give some visual feedback when the buttons are pressed
  • When four numbers have been entered, check whether the PIN is correct
  • If the PIN is correct, blink the green LED and play a high tone on the buzzer
  • If the PIN is incorrect, blink the red LED and play a low tone on the buzzer
  • Whenever four digits have been entered, wipe the list of entered digits clean
  • Print out some feedback to the terminal

You can clone my fork of the Pimoroni Explorer HAT Python library which includes the code below for the PIN entry system, but I'll also walk you through the code bit-by-bit so that you can understand what's going on.

Here's how to clone and run it:

git clone https://github.com/sandyjmacdonald/explorer-hat.git
cd explorer-hat/examples
python pin_entry.py

And here's the code in full:

## Imports, for time delays, controlling Explorer HAT Pro and GPIO pin 18
## for the piezo buzzer.

import time
import explorerhat as eh
import RPi.GPIO as GPIO

## Sets up GPIO pin 18 as a PWM output with freq. of 400 Hz.

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)
buzzer = GPIO.PWM(18, 400)

## Lists for the correct PIN and an empty one to add digits to.

correct_pin = [1,2,3,4]
pin = []

## Function to add a digit pressed on EHP cap. touch to our pin list.

def add_to_pin(channel, event):
        if channel > 4:  ## Only use channels 1-4
                return
        if event == 'press':
                global pin
                pin.append(channel)
                eh.light[channel-1].on()  ## Blink the corresponding LED
                time.sleep(0.05)
                eh.light[channel-1].off()

## We use a try, except, finally to run our code and catch exceptions.

try:

        ## The while True loop keeps the program running until control-c breaks out.

        while True:
                while len(pin) < 4:  ## Keeping adding until 4 digits added
                        eh.touch.pressed(add_to_pin)
                        time.sleep(0.05)
                if pin == correct_pin:  ## Runs with correct PIN
                        print('PIN correct!')
                        for i in range(5):  ## Blinks LEDs and buzzes 5 times
                                buzzer.ChangeFrequency(400)  ## High frequency
                                buzzer.start(50)
                                eh.output.one.on()  ## Turns green LED on
                                time.sleep(0.1)
                                buzzer.stop()
                                eh.output.one.off()  ## Turns green LED off
                                time.sleep(0.1)
                else:  ## Runs with incorrect PIN
                        print('PIN incorrect! Try again.')
                        for i in range(5):  ## Similar to previous block
                                buzzer.ChangeFrequency(50)  ## Low frequency
                                buzzer.start(50)
                                eh.output.two.on()  ## Turns red LED on
                                time.sleep(0.1)
                                buzzer.stop()
                                eh.output.two.off()  ## Turns red LED off
                                time.sleep(0.1)
                pin = []  ## Resets the list after 4 digits have been entered

## Catches control-c.

except KeyboardInterrupt:
        pass

## Catches any other exceptions.

except Exception:
        pass

## Clean up GPIO before exit.

finally:
        GPIO.cleanup()

It's really not much code at all to set up such a nice little system, and no doubt it could be written a tad more efficiently with a couple more functions.

Here's a walk-through of what it all does.

Imports

import time
import explorerhat as eh
import RPi.GPIO as GPIO

Here, the time module is used to introduce some delays into our code. It's always a good idea to have some very short delays when running this kind of code to stop things from getting crazy. I tend to use a 0.05 second delay.

We import explorerhat as eh simply to saving us typing explorerhat in full every time we need to use one of it's functions or classes.

Last, we need RPi.GPIO to control our piezo buzzer with PWM.

Setting up RPi.GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)
buzzer = GPIO.PWM(18, 400)

These first two lines, first, set the GPO pin numbers to the Broadcom numbering and, second, setup GPIO pin 18 as an output. The PWM channel on the Explorer HAT Pro is connected to GPIO pin 18 on the Raspberry Pi, to which we'll connect our buzzer.

The last line creates a PWM object called buzzer using pin 18 and sets the frequency to 400 Hz. We can use this buzzer object to control out buzzer later.

Setting our PIN

correct_pin = [1,2,3,4]
pin = []

We create list with the correct PIN that we want to match, and an empty list to which we can add the entered numbers. Whenver four digits have been added, we'll empty this list.

Entering digits

def add_to_pin(channel, event):
        if channel > 4:
                return
        if event == 'press':
                global pin
                pin.append(channel)
                eh.light[channel-1].on()
                time.sleep(0.05)
                eh.light[channel-1].off()

We create a short function to do three things: detect which button has been pressed, add that number to out pin list, and blink the curresponding light briefly to give some visual feedback when the button has been pressed.

We'll pass this function to the eh.touch.pressed() method later and in turn it will pass in the channel number that has been pressed and what type of event it was to our add_to_pin function.

The if channel > 4: return means that only the buttons 1-4 along the bottom edge can be used, although you could easily remove this if if you wanted to use all eight digits.

We have to specify that the pin list is a global so that we can access it everywhere, then we append the number entered to the list. The eh.light[channel-1].on() turns on the LED above the corresponding button pressed, the time.sleep(0.05) keeps the LED on briefly, and then the eh.light[channel-1].off() turns it off again. We have to subtract 1 from the channel, since the channels are indexed from 1 and the LEDs from 0.

Try, except, finally

We use try, except and finally since this allows us to try to run our main code, catch any exceptions with except which keeps things a little cleaner, and then use the finally to run the GPIO.cleanup() which is essential to avoid problems when using the RPi.GPIO library.

Detecting touches and adding numbers to the pin list

while True:
        while len(pin) < 4:
                eh.touch.pressed(add_to_pin)
                time.sleep(0.05)

while True ensures that this block of code will keep running until we break out of the program with control-c. We want to add digits to the pin list as long as the list has less than 4 digits in it, and the eh.touch.pressed(add_to_pin) runs our add_to_pin function whenever a touch is detected, adding the digit to the pin list and giving the visual feedback blink. As before, the time.sleep(0.05) avoids any strange behaviour.

Your PIN is correct! :-)

The previous while len(pin) < 4 will break out as soon as 4 digits have been entered and the next thing we want to do is to check whether the PIN is correct or incorrect and react accordingly.

if pin == correct_pin:
        print('PIN correct!')
        for i in range(5):
                buzzer.ChangeFrequency(400)
                buzzer.start(50)
                eh.output.one.on()
                time.sleep(0.1)
                buzzer.stop()
                eh.output.one.off()
                time.sleep(0.1)

This block does quite a bit.

First, we check whether the pin entered matches the correct_pin. If it does, then we print a message telling the user so.

Then we have a for loop that runs five times. Each time, it sets the buzzer frequency to a high tone with buzzer.ChangeFrequency(400) and then starts the buzzer with buzzer.start(50). At the same time, we switch the green LED connected to output 1 on with eh.output.one.on(). The time.sleep(0.1) means that the buzzer will sound and the LED will light for 0.1 seconds.

Lastly, we turn the buzzer off with buzzer.stop(), turn the LED off with eh.output.one.off(), and have another time.sleep(0.1) delay where nothing happens.

This loop runs five times, so the light will flash and buzzer buzz intermittently five times for a total of 1 second.

Your PIN is incorrect. :-(

This else block is essentially the same as the previous block, except that it blinks the other red LED and buzzes at a lower frequency to signal an incorrect PIN.

else:
        print('PIN incorrect! Try again.')
        for i in range(5):
                buzzer.ChangeFrequency(50)
                buzzer.start(50)
                eh.output.two.on()
                time.sleep(0.1)
                buzzer.stop()
                eh.output.two.off()
                time.sleep(0.1)

Lastly, we have to reset our pin list to an empty list, ready for the next four digits to be entered, pin = [], at the end of this iteration of the while True loop.

And that's it! Play with it, and then I encourage you to hack it to make it better.

Going further

I can think of loads of ways to make this better.

You could make it so that you can enter a PIN at the terminal when the program first runs, rather than having to set it within the code.

You could limit the user to a certain number of tries before they get locked out.

You could use this as a way to trigger something else, e.g. a tweet, a status update on Facebook, or send an email. Each user could have their own PIN that would trigger an event specific to that user.

GO FORTH AND HACK, INTREPID EXPLORER!

That's all folks!

Search above to find more great tutorials and guides.

Plasma 2040

Swathe everything in rainbows with this all-in-one, USB-C powered controller for WS2812/Neopixel and APA102/Dotstar addressable LED strip.