My Fretlight Guitar Binary Clock: Raspberry Pi Edition

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.

https://www.exploringbinary.com/wp-content/uploads/Fretclock.RaspberryPi.png
My Fretlight BCD Clock, Run by a Raspberry Pi (Time shown: 7:09:26)

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.)

fretclock.py

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.

For USB communication, I use PyUSB, a USB library for Python. PyUSB itself requires a backend USB library, for which I use libusb.

Here is the code:

# fretclock.py: Rick Regan, https://www.exploringbinary.com/, 1/4/16
#   BCD mode (standard time only) binary clock represented on the Fretlight Guitar
#
#   For details see https://www.exploringbinary.com/my-fretlight-guitar-as-a-binary-clock/
#    and https://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 https://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([0] * FretlightBinaryClock.PACKET_LEN)
        self.packet[6] = 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] = 0
        self.packet[1] = 0
        self.packet[2] = 0
        self.packet[3] = 0
        self.packet[4] = 0
        self.packet[5] = 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[1] |= 0x10 # Turn on 
        else:
            self.packet[1] &= ~0x10 # Turn off

        # h2 (range: 0-9)
        if (h2 & 8): # LED at fret 18, string 5
            self.packet[3] |= 0x80 # Turn on 
        else:
            self.packet[3] &= ~0x80 # Turn off

        if (h2 & 4): # LED at fret 19, string 5
            self.packet[3] |= 0x02 # Turn on 
        else:
            self.packet[3] &= ~0x02 # Turn off

        if (h2 & 2): # LED at fret 20, string 5
            self.packet[2] |= 0x08 # Turn on 
        else:
            self.packet[2] &= ~0x08 # Turn off

        if (h2 & 1): # LED at fret 21, string 5
            self.packet[1] |= 0x20 # Turn on 
        else:
            self.packet[1] &= ~0x20 # Turn off

        # m1 (range: 0-5)
        if (m1 & 4): # LED at fret 19, string 4
            self.packet[3] |= 0x04 # Turn on 
        else:
            self.packet[3] &= ~0x04 # Turn off

        if (m1 & 2): # LED at fret 20, string 4
            self.packet[2] |= 0x10 # Turn on 
        else:
            self.packet[2] &= ~0x10 # Turn off

        if (m1 & 1): # LED at fret 21, string 4
            self.packet[1] |= 0x40 # Turn on 
        else:
            self.packet[1] &= ~0x40 # Turn off

        # m2 (range: 0-9)
        if (m2 & 8): # LED at fret 18, string 3
            self.packet[4] |= 0x02 # Turn on 
        else:
            self.packet[4] &= ~0x02 # Turn off

        if (m2 & 4): # LED at fret 19, string 3
            self.packet[3] |= 0x08 # Turn on 
        else:
            self.packet[3] &= ~0x08 # Turn off

        if (m2 & 2): # LED at fret 20, string 3
            self.packet[2] |= 0x20 # Turn on 
        else:
            self.packet[2] &= ~0x20 # Turn off

        if (m2 & 1): # LED at fret 21, string 3
            self.packet[1] |= 0x80 # Turn on 
        else:
            self.packet[1] &= ~0x80 # Turn off

        # s1 (range: 0-5)
        if (s1 & 4): # LED at fret 19, string 2
            self.packet[3] |= 0x10 # Turn on 
        else:
            self.packet[3] &= ~0x10 # Turn off

        if (s1 & 2): # LED at fret 20, string 2
            self.packet[2] |= 0x40 # Turn on 
        else:
            self.packet[2] &= ~0x40 # Turn off

        if (s1 & 1): # LED at fret 21, string 2
            self.packet[2] |= 0x01 # Turn on 
        else:
            self.packet[2] &= ~0x01 # Turn off
 
        # s2 (range: 0-9)
        if (s2 & 8): # LED at fret 18, string 1
            self.packet[4] |= 0x08 # Turn on 
        else:
            self.packet[4] &= ~0x08 # Turn off

        if (s2 & 4): # LED at fret 19, string 1
            self.packet[3] |= 0x20 # Turn on 
        else:
            self.packet[3] &= ~0x20 # Turn off

        if (s2 & 2): # LED at fret 20, string 1
            self.packet[2] |= 0x80 # Turn on 
        else:
            self.packet[2] &= ~0x80 # Turn off

        if (s2 & 1): # LED at fret 21, string 1
            self.packet[2] |= 0x02 # Turn on 
        else:
            self.packet[2] &= ~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()

Comments

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:

ctrl-c

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.)

Comments

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).

Dingbat

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

(Cookies must be enabled to leave a comment...it reduces spam.)

Copyright © 2008-2024 Exploring Binary

Privacy policy

Powered by WordPress

css.php