Halloween candy vending machine

Software

The preconfigured Python image from the PiXtend website [6] lets you start programming as quickly as possible. Make sure the image matches the PiXtend model you use. To do so, search for the PiXtend Python Library heading on the page for the respective device.

Once you have downloaded the image and written it to an SD card, the PiXtend Python Library (PPL) is available directly. You only need to install the library to control the WS2812 LEDs. These steps are summarized in the first eight lines of Listing 1. To test whether the library installation was successful, run the strandtest.py program (lines 9 and 10).

Listing 1

Installing PPL

01 $ cd ~
02 $ sudo apt update
03 $ sudo apt upgrade
04 $ sudo apt install build-essential python-dev python-pip unzip wget scons swig
05 $ wget https://github.com/jgarff/rpi_ws281x/archive/master.zip  unzip master.zip
06 $ cd rpi_ws281x-master
07 $ sudo scons
08 $ sudo pip install rpi_ws281x
09 $ cd ~/rpi_ws281x-master/python
10 $ sudo PYTHONPATH=".:build/lib.linux-armv7l-2.7" python examples/strandtest.py

In the source code, you can configure different settings for the LEDs, including the LED_BRIGHTNESS parameter for the brightness of the diodes. You will want to choose a value that is as low as possible, because the LEDs consume a lot of current at full light intensity, and they also heat up.

Control

The ws.py program (Listing 2) [7] controls the eyes and mouth. At the start of the program you will find the TRANSLATE table that enables linear access to the individual LEDs. As mentioned earlier, the way the LEDs are installed on the panels is a little strange.

Listing 2

ws.py

01 import time
02 from neopixel import *
03 import argparse
04
05 LED_COUNT      = 384
06 LED_PIN        = 12
07 LED_FREQ_HZ    = 800000
08 LED_DMA        = 10
09 LED_BRIGHTNESS = 16
10 LED_INVERT     = False
11 LED_CHANNEL    = 0
12
13 TRANSLATE = [0, 15, 16, 31, 32, 47, 48, 63, 64, 79, 80, 95, 96, 111, 112, 127, 128, 143, 144, 159, 160, 175, 176, 191, 192, 207, 208, 223, 224, 239, 240, 255, 256, 271, 272, 287, 288, 303, 304, 319, 320, 335, 336, 351, 352, 367, 368, 383, 1, 14, 17, 30, 33, 46, 49, 62, 65, 78, 81, 94, 97, 110, 113, 126, 129, 142, 145, 158, 161, 174, 177, 190, 193, 206, 209, 222, 225, 238, 241, 254, 257, 270, 273, 286, 289, 302, 305, 318, 321, 334, 337, 350, 353, 366, 369, 382, 2, 13, 18, 29, 34, 45, 50, 61, 66, 77, 82, 93, 98, 109, 114, 125, 130, 141, 146, 157, 162, 173, 178, 189, 194, 205, 210, 221, 226, 237, 242, 253, 258, 269, 274, 285, 290, 301, 306, 317, 322, 333, 338, 349, 354, 365, 370, 381, 3, 12, 19, 28, 35, 44, 51, 60, 67, 76, 83, 92, 99, 108, 115, 124, 131, 140, 147, 156, 163, 172, 179, 188, 195, 204, 211, 220, 227, 236, 243, 252, 259, 268, 275, 284, 291, 300, 307, 316, 323, 332, 339, 348, 355, 364, 371, 380, 4, 11, 20, 27, 36, 43, 52, 59, 68, 75, 84, 91, 100, 107, 116, 123, 132, 139, 148, 155, 164, 171, 180, 187, 196, 203, 212, 219, 228, 235, 244, 251, 260, 267, 276, 283, 292, 299, 308, 315, 324, 331, 340, 347, 356, 363, 372, 379, 5, 10, 21, 26, 37, 42, 53, 58, 69, 74, 85, 90, 101, 106, 117, 122, 133, 138, 149, 154, 165, 170, 181, 186, 197, 202, 213, 218, 229, 234, 245, 250, 261, 266, 277, 282, 293, 298, 309, 314, 325, 330, 341, 346, 357, 362, 373, 378, 6, 9, 22, 25, 38, 41, 54, 57, 70, 73, 86, 89, 102, 105, 118, 121, 134, 137, 150, 153, 166, 169, 182, 185, 198, 201, 214, 217, 230, 233, 246, 249, 262, 265, 278, 281, 294, 297, 310, 313, 326, 329, 342, 345, 358, 361, 374, 377, 7, 8, 23, 24, 39, 40, 55, 56, 71, 72, 87, 88, 103, 104, 119, 120, 135, 136, 151, 152, 167, 168, 183, 184, 199, 200, 215, 216, 231, 232, 247, 248, 263, 264, 279, 280, 295, 296, 311, 312, 327, 328, 343, 344, 359, 360, 375, 376]
14
15 strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL)
16 strip.begin()
17
18 f = open("data.bin","rt")
19 data=f.readlines()
20 bitcounter=0
21 image=2
22 for line in data:
23   if len(line)<3:
24     bitcounter=0
25     image=image+1
26     strip.show()
27     time.sleep(0.1)
28   if not(line.find("#")):
29     continue
30   bits = line.split(" ")
31   for bit in bits:
32     bit=bit.strip()
33     if len(bit) > 0:
34       if bit=='1':
35         strip.setPixelColor(TRANSLATE[bitcounter], Color(0,255,0))
36       else:
37         strip.setPixelColor(TRANSLATE[bitcounter], Color(0,0,0))
38       bitcounter=bitcounter+1
39 f.close

The information as to which LED is switched is provided in the data.bin file; Listing 3 shows an excerpt. Bit patterns control the LEDs. You can store any number of patterns for ws.py to send to the LEDs in sequence. It parses the file line-by-line and converts the data to commands for the LEDs. An empty line initiates a new bit pattern; the program skips comment lines that start with a hash symbol (#).

Listing 3

data.bin (Excerpt)

# 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0
# 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
0 1 1 1 1 1 1 0 0 1 1 1 1 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0
1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0
1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
0 1 1 1 1 1 1 0 0 1 1 1 1 1 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0

Program

The control program (Listing 4) continuously queries the PiXtend's inputs and starts the conveyor belts alternately when the light barrier is triggered. In parallel to starting the belts, it calls two additional programs: One controls the LEDs, and the other plays back the matching creepy sound. If the play command doesn't work as intended, you can try connecting and configuring an external sound card to the Rasp Pi. Figure 4 shows the eyes and mouth. To see the whole setup working, check out my YouTube video [8].

Listing 4

halloween.py

01 #!/usr/bin/env python
02 # coding=utf-8
03
04 from __future__ import print_function
05 # Import Pixtend class
06 from pixtendlib import Pixtend
07 import time
08 import sys
09 import threading
10 import os, platform
11
12 p = Pixtend()
13
14 # Open SPI bus for communication
15 try:
16   p.open()
17 except IOError as io_err:
18   # Print error text and delete PiXtend instance
19   print("Error opening the SPI bus! Error is: ", io_err)
20   p.close()
21   p = None
22
23 def thread_sound():
24   os.system("play gost.wav")
25
26 def thread_animation():
27   os.system("sudo python ws.py")
28
29 # -----------------------------------------------------
30 # Main Program
31 # -----------------------------------------------------
32 if p is not None:
33   print("Running Main Program - Hit Ctrl + C to exit")
34   # Set some variables needed in the main loop
35   is_config = False
36   switch = True
37   pressed = False
38   while True:
39     try:
40       # Using Auto Mode for optimal SPI bus usage
41       if p.auto_mode() == 0:
42         if not is_config:
43           is_config = True
44           # Space for setup
45         print (p.digital_input0,":",p.digital_input1)
46         if p.digital_input2 == p.ON and pressed==False:
47           pressed=True;
48           x = threading.Thread(target=thread_sound)
49           x.start()
50           y = threading.Thread(target=thread_animation)
51           y.start()
52           if switch: p.digital_output0 = p.ON
53           else : p.digital_output1 = p.ON
54           switch=not(switch)
55         if p.digital_output0 == p.ON and di0_old == 0 and  p.digital_input0 == p.ON:
56           p.digital_output0 = p.OFF
57           pressed=False
58         if p.digital_output1 == p.ON and di1_old == 0 and  p.digital_input1 == p.ON:
59           p.digital_output1 = p.OFF
60           pressed=False
61         di0_old = p.digital_input0
62         di1_old = p.digital_input1
63       else:
64         print("Auto Mode - Communication is not yet up...Please wait...")
65       # Wait at minimum 0.1sec or 100ms before getting new values
66       time.sleep(0.1)
67     except KeyboardInterrupt:
68       # Keyboard interrupt caught, Ctrl + C, now clean up and leave program
69       p.close()
70       p = None
71       break
72 else:
73   # If there was an error when opening the SPI bus interface, leave the program.
74   print("")
75   print("There was a problem with the PiXtend communication. Quitting.")
76   print("")
Figure 4: The vending machine's eyes and mouth.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • PiXtend V2

    The PiXtend board extends the Raspberry Pi with many useful interfaces and functions for new target groups.

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More

News