Update There’s now a new post containing revised, working instructions for this project.
Note This article was written some time ago, and the libraries used do not work with recent versions of macOS.
I regularly back-up my Raspberry Pi storage card because it’s so easy to damage the card with an improper shutdown or some such. I back up to a Mac, and you can read how I do it here. This wasn’t much of a chore in the early days when I was working with 4GB cards, but now I use 16GB Micro SDs and I know of folks who have much, much larger storage capacities thanks to never-cheaper cards. All this means the back-up takes a long time. So I wondered if I could create a gadget to tell me the task was done, allowing me to get on with other jobs in the meantime.

Coincidentally, I dug out an Adafruit FT232H which I’d bought some months previously but never really played with. The FT232H is a small breakout board containing an FTDI FT232H chip. FTDI specialises in USB-to-serial converters. I use a cable containing an FTDI chip to connect Raspberry Pi UART pins to host computers, for example. But the Adafruit board goes further: it uses the FTDI tech to provide not only a serial line, but also access to GPIO pins and I²C and SPI buses.
Every one of these feeds can be controlled and interacted with by code running on your computer. All you need is the right driver software, some Python code and a USB micro cable.

I had intended to explore the FT232H, but had been held up by the need to find an application. Now I had one: my aforementioned Raspberry Pi back-up task notifier. The key was the FT232H’s SPI support — could I use it to drive a NeoPixel?
It turns out I can. I’ve some experience of driving NeoPixels — WS2812 RGB LEDs, to be accurate — via SPI, thanks to an Electric Imp Internet of Things project I did. NeoPixels have their own bus protocol, but one that’s easy to emulate using SPI. NeoPixels also run on 5V power, so they can be driven by USB.
So here’s what I built and how.
First, the circuit. I put the FT232H and a single NeoPixel on a small solderless breadboard. The latter I hooked up to the breakout’s GND and 5V pins; I wired its Data In pin to the FT232H’s D1 pin, its SPI MOSI (Master Out, Slave In) port.

Adafruit has a great tutorial on setting up its board for use with all the major operating systems — I’ll paraphrase the Mac OS X instructions. Note that you’ll have to install Homebrew if you haven’t done so already. Run the following from the Terminal:
brew install cmake brew install libusb brew install swig brew install --build-from-source libftdi mkdir -p ~/Library/Python/2.7/lib/python/site-packages echo '/usr/local/lib/python2.7/site-packages' > ~/Library/Python/2.7/lib/python/site-packages/homebrew.pth
If you plug in the FT232H now and try to run code that does anything other than communicate with the breakout by serial, it won’t work. You’ll get an “FTDI USB open error: unable to claim usb device. Make sure the default FTDI driver is not in use (-t)” error. Adafruit’s tutorial was written a couple of years ago, and Apple’s FTDI driver has changed in the interim. What you need to do is temporarily unload it, which you can do by entering this line at the Terminal prompt:
sudo kextunload -bundle-id com.apple.driver.AppleUSBFTDI
You can check it has worked by entering:
kextstat | grep -i ftdi
which should come up blank. If it doesn’t the driver is still loaded. You can re-load the driver if you need to by restarting your Mac or using:
sudo kextload -bundle-id com.apple.driver.AppleUSBFTDI
Now use your preferred text editor to enter the following Python code:
#!/usr/bin/env python import time import sys import os import Adafruit_GPIO as GPIO import Adafruit_GPIO.FT232H as FT232H # This class code derivaes from Adafruit's tutorial # at https://learn.adafruit.com/adafruit-ft232h-breakout # I had to up the SPI speed to 75MHz to work # cu.usbserial-141 class NeoPixel_FT232H(object): def __init__(self, n): self.ft232h = FT232H.FT232H() self.spi = FT232H.SPI(self.ft232h, max_speed_hz=7500000, mode=0, bitorder=FT232H.MSBFIRST) self.buffer = bytearray(n * 24) self.lookup = self.build_byte_lookup() def build_byte_lookup(self): lookup = {} for i in range(256): value = bytearray() for j in range(7, -1, -1): if ((i >> j) & 1) == 0: value.append(0b11100000) else: value.append(0b11111000) lookup[i] = value return lookup def set_pixel_color(self, n, r, g, b): # Set the pixel RGB color for the pixel at position n. index = n * 24 self.buffer[index :index+8 ] = self.lookup[int(g)] self.buffer[index+8 :index+16] = self.lookup[int(r)] self.buffer[index+16 :index+24] = self.lookup[int(b)] def show(self): # Send the pixel buffer out the SPI data output pin (D1) self.spi.write(self.buffer) # START if __name__ == '__main__': FT232H.use_FT232H() pixel_count = 1 pixels = NeoPixel_FT232H(pixel_count) filename = '/Users/smittytone/.status' # Enter your own path here brightness = 30 # Brightness control as a percentage delay = 0.1 redVal = 252 greenVal = 0 blueVal = 0 redDel = 2 greenDel = 2 blueDel = 2 redOn = True greenOn = False blueOn = False saveRed = 252 saveGreen = 0 saveBlue = 0 noteOne = False print 'Ctrl-C to quit' while True: try: pixels.set_pixel_color(0, (redVal * brightness / 100), (greenVal * brightness / 100), (blueVal * brightness / 100)) pixels.show() if noteOne: # Notification flag set if redVal != 0: redVal = 0 else: redVal = 255 else: # Notification flag unset; run a colour sweep effect if redOn: redVal = redVal + redDel if redVal > 254: redVal = 254 redDel = -2 greenOn = True if redVal < 1: redDel = 2 redOn = False redVal = 0 if greenOn: greenVal = greenVal + greenDel if greenVal > 254: greenDel = -2 blueOn = True greenVal = 254 if greenVal < 1: greenDel = 2 greenOn = False greenVal = 0 if blueOn: blueVal = blueVal + blueDel if blueVal > 254: blueDel = -1 redOn = True blueVal = 254 if blueVal < 1: blueDel = 2 blueOn = False blueVal = 0 # Check file if os.path.exists(filename): file = open(filename) text = file.read() file.close() # File contains three values, eg. 0.0.0 # First is a marker for the red light (1 = on; 0 = off) items = text.split('.') if items[0] != '0': # Status file indicates notification light should be on if noteOne == False: # So turn it on only if it's currently off noteOne = True saveGreen = greenVal greenVal = 0 saveRed = redVal redVal = 255 saveBlue = blueVal blueVal = 0 delay = 0.5 else: # Disable the notification... if noteOne: # ...but only if it's actually on noteOne = False redVal = saveRed greenVal = saveGreen blueVal = saveBlue delay = 0.1 else: # No file error - warn the user print 'No status file' noteOne = False redVal = saveRed greenVal = saveGreen blueVal = saveBlue delay = 0.1 time.sleep(delay) except KeyboardInterrupt: for i in range(pixel_count): pixels.set_pixel_color(0, 0, 0, 0) pixels.show() sys.exit(-1)
Next, create a hidden file called .status
on your machine. Save it where you like, but make sure you enter the path correctly in the code above (line 51). Put the characters 0.0.0 in the first and only line.
Finally, edit your .bash_profile
file — it’s in your home folder but hidden, so you may need to access it through the Terminal nano
— and add the following lines:
alias taskdone='echo "1.0.0" > ~/.status' alias taskclear='echo "0.0.0" > ~/.status' alias taskset='sudo kextunload -bundle-id com.apple.driver.AppleUSBFTDI; python ~/Dropbox/Programming/Python/statuslight.py'
Save the file and you’re ready to go. Plug in the hardware and enter taskset
at the command line. Your light should start running a sweep through the colours of the spectrum. You can test out the notification by entering:
sleep 4;taskdone
After four seconds, the light will flash red. Enter:
taskclear
to clear the notification.
Now when you come to do a backup of your Raspberry Pi’s storage card — or run any other Terminal task that takes a long time to execute — add ;taskdone
to the end of the command line. For example:
sudo dd if=/dev/rdisk2 of=pi.img bs=1m ; taskdone
When the task completes, the light will flash red to let you know.
Once I’d got it working, I mounted the breadboard inside a small project box and cut a piece of translucent plastic to fit on top. The are many other ways you could mount the light on or by your computer. And the code above is easily customised for other roles and modes. You might simply want it off when there’s no notification to signal.

I didn’t used two of the three digits in the source file in this project, but they’re there to be used, and the “0.0.0” sequence can be extended even further if you wish. You code just needs to check the appropriate entry in the item[] array and cue up a suitable notification colour. You’ll want to adapt your code to be able to signal multiple notifications, of course, so that one doesn’t ‘overwrite’ another. For example, it’s not hard to get red and blue to flash alternately, signalling two notifications.
A couple of things to note: the Apple FTDI is always loaded at a restart — hence the unload code in the taskset
alias. And quitting the Python code and restarting it will generate an error unless you unplug and replug the FT232H from USB. If you forget to run taskclear
before starting a new task, just open another Terminal tab and enter taskclear
there.

I bought my Adafruit FT232H from Pimoroni. Ditto the NeoPixels