My Fretlight Guitar Binary Clock: Raspberry Pi Edition
Copyright © 2008-2016 Exploring Binary
I’ve previously written a Windows program (in C++) and an Android app (in Java) to turn my Fretlight guitar into a binary clock. I’ve now written a Python program to do the same, running under Raspbian Linux on a Raspberry Pi computer. I will show you the code and tell you how to run it.
Turning the Fretlight Guitar into a Binary Clock
The Fretlight guitar is a teaching aid for guitarists. It connects to a computer through a USB cable, and acts as a USB HID. Software on the computer sends it data that tells it which of its 132 LEDs to light, indicating proper placement of your fingers.
I co-opted the interface and turned my Fretlight into a BCD mode binary clock. I had done this with Windows and Android programs, and now I have done it with a Python program — running on the Raspberry Pi.
(See “My Fretlight Guitar As a Binary Clock” for more background and for the USB communication details.)
I wrote a program, fretclock.py, to implement my Fretlight clock in Python. I created a class called FretlightBinaryClock that has two “public” methods, start() and stop(). start() activates the clock, which entails opening the Fretlight USB device, getting the current time from the system, and displaying the updated time every second. stop() deactivates the clock, turning off all LEDs and releasing the device.
Here is the code:
# fretclock.py: Rick Regan, http://www.exploringbinary.com/, 1/4/16 # BCD mode (standard time only) binary clock represented on the Fretlight Guitar # # For details see http://www.exploringbinary.com/my-fretlight-guitar-as-a-binary-clock/ # and http://www.exploringbinary.com/my-fretlight-guitar-binary-clock-raspberry-pi-edition/ # # To install PyUSB (and required backend, e.g., libusb): # sudo apt-get install libusb-1.0-0-dev # sudo pip install --pre pyusb # # To run: # sudo date -s "4 Jan 2016 10:32:00" (Set current date and time) # sudo python fretclock.py # # To stop: ctrl-c # # Credits: # PyUSB calls taken from https://github.com/walac/pyusb/blob/master/docs/tutorial.rst # and http://stackoverflow.com/questions/12542799/communication-with-the-usb-device-in-python import usb.core import usb.util import time import datetime import signal import sys def _signal_handler(signal, frame): fretclock.stop() sys.exit(0) class FretlightBinaryClock: PACKET_LEN = 7 #Number of bytes in a Fretlight USB HID packet def __init__(self): self.fretlight = 0 self.cfg = 0 self.intf = 0 self.ep = 0 self.hour = 0 self.minute = 0 self.second = 0 self.packet = bytearray( * FretlightBinaryClock.PACKET_LEN) self.packet = 0x03 # All LEDS are in packet 3 def _open_fretlight(self): self.fretlight = usb.core.find(idVendor=0x0925, idProduct=0x2000) # Open the Fretlight Guitar device (Vendor ID, product ID) if self.fretlight is None: raise ValueError('Fretlight Guitar not found') if self.fretlight.is_kernel_driver_active(0): # This will happen once after each time the Fretlight is attached -- it must be detached so we can control it ourselves self.fretlight.detach_kernel_driver(0) self.fretlight.set_configuration() # Set active configuration (no arguments means the first configuration is actived) self.cfg = self.fretlight.get_active_configuration() # Get active configuration self.intf = self.cfg[(0,0)] # Get interface self.ep = usb.util.find_descriptor( self.intf, # match the first OUT endpoint custom_match = \ lambda e: \ usb.util.endpoint_direction(e.bEndpointAddress) == \ usb.util.ENDPOINT_OUT) # Get endpoint (interrupt OUT endpoint) if self.ep is None: raise ValueError('Endpoint not found') def _leds_off(self): self.packet = 0 self.packet = 0 self.packet = 0 self.packet = 0 self.packet = 0 self.packet = 0 self._send_report() def _init_clock(self): #Initialize from system clock self.hour = datetime.datetime.now().time().hour if (self.hour > 12): self.hour -= 12 # Convert from military to standard self.minute = datetime.datetime.now().time().minute self.second = datetime.datetime.now().time().second def _increment_clock(self): self.second += 1 if (self.second == 60): self.second = 0 self.minute += 1 if (self.minute == 60): self.minute = 0 self.hour += 1 if (self.hour == 13): self.hour = 1 def _send_report(self): # Report should begin with a 0 byte followed by the packet, but for some reason with PyUSB, it won't work if you send the 0 report = self.packet self.ep.write(report) # Write the data to the Fretlight def _create_packet(self): # Break time into BCD digits: h1,h2 / m1, m2 / s1,s2 # Get the two digits of the hour h1 = self.hour / 10 h2 = self.hour % 10 # Get the two digits of the minute m1 = self.minute / 10 m2 = self.minute % 10 # Get the two digits of the second s1 = self.second / 10 s2 = self.second % 10 # Set the appropriate LEDs # h1 (range: 0-1) if (h1 & 1): # LED at fret 21, string 6 self.packet |= 0x10 # Turn on else: self.packet &= ~0x10 # Turn off # h2 (range: 0-9) if (h2 & 8): # LED at fret 18, string 5 self.packet |= 0x80 # Turn on else: self.packet &= ~0x80 # Turn off if (h2 & 4): # LED at fret 19, string 5 self.packet |= 0x02 # Turn on else: self.packet &= ~0x02 # Turn off if (h2 & 2): # LED at fret 20, string 5 self.packet |= 0x08 # Turn on else: self.packet &= ~0x08 # Turn off if (h2 & 1): # LED at fret 21, string 5 self.packet |= 0x20 # Turn on else: self.packet &= ~0x20 # Turn off # m1 (range: 0-5) if (m1 & 4): # LED at fret 19, string 4 self.packet |= 0x04 # Turn on else: self.packet &= ~0x04 # Turn off if (m1 & 2): # LED at fret 20, string 4 self.packet |= 0x10 # Turn on else: self.packet &= ~0x10 # Turn off if (m1 & 1): # LED at fret 21, string 4 self.packet |= 0x40 # Turn on else: self.packet &= ~0x40 # Turn off # m2 (range: 0-9) if (m2 & 8): # LED at fret 18, string 3 self.packet |= 0x02 # Turn on else: self.packet &= ~0x02 # Turn off if (m2 & 4): # LED at fret 19, string 3 self.packet |= 0x08 # Turn on else: self.packet &= ~0x08 # Turn off if (m2 & 2): # LED at fret 20, string 3 self.packet |= 0x20 # Turn on else: self.packet &= ~0x20 # Turn off if (m2 & 1): # LED at fret 21, string 3 self.packet |= 0x80 # Turn on else: self.packet &= ~0x80 # Turn off # s1 (range: 0-5) if (s1 & 4): # LED at fret 19, string 2 self.packet |= 0x10 # Turn on else: self.packet &= ~0x10 # Turn off if (s1 & 2): # LED at fret 20, string 2 self.packet |= 0x40 # Turn on else: self.packet &= ~0x40 # Turn off if (s1 & 1): # LED at fret 21, string 2 self.packet |= 0x01 # Turn on else: self.packet &= ~0x01 # Turn off # s2 (range: 0-9) if (s2 & 8): # LED at fret 18, string 1 self.packet |= 0x08 # Turn on else: self.packet &= ~0x08 # Turn off if (s2 & 4): # LED at fret 19, string 1 self.packet |= 0x20 # Turn on else: self.packet &= ~0x20 # Turn off if (s2 & 2): # LED at fret 20, string 1 self.packet |= 0x80 # Turn on else: self.packet &= ~0x80 # Turn off if (s2 & 1): # LED at fret 21, string 1 self.packet |= 0x02 # Turn on else: self.packet &= ~0x02 # Turn off def _update(self): self._increment_clock() self._create_packet() self._send_report() def _timer_loop(self): while True: time_begin = time.time() self._update() time_end = time.time() time_elapsed = time_end - time_begin time.sleep(1.0-time_elapsed) # Delay for 1 second, less overhead to this point def _set_signal_handler(self): signal.signal(signal.SIGINT, _signal_handler) def start(self): print ("Starting fretclock") self._set_signal_handler() self._open_fretlight() self._leds_off() self._init_clock() self._timer_loop() def stop(self): print ("Stopping fretclock") self._leds_off() usb.util.dispose_resources(self.fretlight) fretclock = FretlightBinaryClock() fretclock.start()
This is the first real Python program I have written. In the time I allotted to this project, I did my best to research and follow Python’s coding conventions (please comment if you have suggestions for improvement). My goal was to explore my new Raspberry Pi, using the recommended language. (I kind of cheated though, so as not to take on too much new stuff at once: I did an initial implementation in C, which was a straightforward port of my Windows C++ code.)
As for the PyUSB API, I could not find a guide detailing all its functions. I just copied the code from this tutorial, and added a few lines of code from this Stack Overflow answer. The code works inasmuch as I tested it, but I don’t know how robust it is. I also don’t understand it well enough to explain, for example, why I had to write the packet directly to the endpoint instead of wrapping it in a USB report (like I did in my C, C++, and Java versions).
Running It On the Raspberry Pi
You must first put an image of Raspbian on the micro SD card; I used Raspbian Jessie. Insert the card in the Pi and power it on — it will boot right up to the graphical desktop. From there, open a terminal window and do the following (as necessary):
Install the USB libraries (the Pi needs to be connected to the Internet for this step):
sudo apt-get install libusb-1.0-0-dev sudo pip install --pre pyusb
Set the system time (the Pi has no built-in real-time clock); for example:
sudo date -s "4 Jan 2016 10:32:00"
Start the clock:
sudo python fretclock.py
(‘sudo’ is required; otherwise, access to the Fretlight is denied with this message: usb.core.USBError: [Errno 13] Access denied (insufficient permissions))
Stop the clock:
On my Pi, I observed that the clock loses about 3-4 seconds per hour. I don’t know if this is my code, Python’s sleep command, or the lack of “real-timeness” of the Pi. For my purposes, this is acceptable.
This code should run on any Linux, not just Raspbian. (I also ran it on Ubuntu.)
When I started this project I wasn’t sure if the Pi would have enough power for itself and the Fretlight. Apparently it does.
Ideally, you would run this code without the Pi hooked up to a monitor, so that you could just plug in the Fretlight and power up the Pi. (You would have the program set to start up at boot.) However, since there is no real-time clock on the Pi, you need to interact with it first to set the time.
To see the Fretlight binary clock in action, check out my video (the video is of the clock running under Windows, but it looks the same when running the Python code on the Pi).