import atexit
import time
from threading import Thread
from sys import exit
try:
import RPi.GPIO as GPIO
except ImportError:
exit("This library requires the RPi.GPIO module\nInstall with: sudo pip install RPi.GPIO")
__version__ = '0.0.1'
DAT = 23
CLK = 24
NUM_PIXELS = 16
CHANNEL_PIXELS = 8
BRIGHTNESS = 7
BTN_FASTFWD = 5
BTN_REWIND = 13
BTN_PLAYPAUSE = 6
BTN_VOLUP = 16
BTN_VOLDN = 26
BTN_ONOFF = 12
BUTTONS = [BTN_REWIND, BTN_FASTFWD, BTN_PLAYPAUSE, BTN_VOLUP, BTN_VOLDN, BTN_ONOFF]
pixels = [[0,0,0,BRIGHTNESS]] * NUM_PIXELS
_button_handlers = {}
_button_repeat = {}
_button_hold_time = {}
_button_hold_handlers = {}
_button_hold_repeat = {}
_use_threading = False
_clear_on_exit = True
def _exit():
if _clear_on_exit:
clear()
show()
GPIO.cleanup()
def use_threading(value=True):
global _use_threading
_use_threading = value
[docs]def hold(buttons, handler=None, repeat=True, hold_time=2):
"""Attach a handler function to holding or more buttons.
Can be used as a decorator, or optionally supplied a handler param.
:param buttons: Individual button pin or list of pins to watch
:param handler: Optional handler function
:param repeat: Whether the handler should be repeated if the button is held
:param hold_time: How long (in seconds) the button should be held before triggering
"""
buttons = buttons if isinstance(buttons, list) else [buttons]
for button in buttons:
_button_hold_repeat[button] = repeat
_button_hold_time[button] = hold_time
if handler is not None:
for button in buttons:
_button_hold_handlers[button] = handler
else:
def attach_handler(handler):
for button in buttons:
_button_hold_handlers[button] = handler
return attach_handler
[docs]def on(buttons, handler=None, repeat=True):
"""Attach a handler function to one or more buttons.
Can be used as a decorator, or optionally supplied a handler param.
:param buttons: Individual button pin or list of pins to watch
:param handler: Optional handler function
:param repeat: Whether the handler should be repeated if the button is held
"""
buttons = buttons if isinstance(buttons, list) else [buttons]
for button in buttons:
_button_repeat[button] = repeat
if handler is not None:
for button in buttons:
_button_handlers[button] = handler
else:
def attach_handler(handler):
for button in buttons:
_button_handlers[button] = handler
return attach_handler
[docs]def set_brightness(brightness, channel = None):
"""Set the brightness of all pixels
:param brightness: Brightness: 0.0 to 1.0
"""
if brightness < 0 or brightness > 1:
raise ValueError("Brightness should be between 0.0 and 1.0")
if channel is None or channel == 0:
for x in range(CHANNEL_PIXELS):
pixels[x][3] = int(31.0 * brightness) & 0b11111
if channel is None or channel == 1:
for x in range(CHANNEL_PIXELS):
pixels[x + (CHANNEL_PIXELS)][3] = int(31.0 * brightness) & 0b11111
[docs]def clear(channel = None):
"""Clear the pixel buffer"""
if channel is None or channel == 0:
for x in range(CHANNEL_PIXELS):
pixels[x][0:3] = [0,0,0]
if channel is None or channel == 1:
for x in range(CHANNEL_PIXELS):
pixels[x + (CHANNEL_PIXELS)][0:3] = [0,0,0]
def _write_byte(byte):
for x in range(8):
GPIO.output(DAT, byte & 0b10000000)
GPIO.output(CLK, 1)
byte <<= 1
GPIO.output(CLK, 0)
# Emit exactly enough clock pulses to latch the small dark die APA102s which are weird
# for some reason it takes 36 clocks, the other IC takes just 4 (number of pixels/2)
def _eof():
GPIO.output(DAT, 0)
for x in range(36):
GPIO.output(CLK, 1)
GPIO.output(CLK, 0)
def _sof():
GPIO.output(DAT,0)
for x in range(32):
GPIO.output(CLK, 1)
GPIO.output(CLK, 0)
def _handle_button(pin):
if _use_threading:
Thread(target=_do_handle_button, args=(pin,)).start()
else:
_do_handle_button(pin)
def _handle_hold(pin):
if pin in _button_hold_handlers.keys() and callable(_button_hold_handlers[pin]):
if _button_hold_time[pin] > 0:
t_hold = _button_hold_time[pin]
t_end = time.time() + t_hold
while time.time() < t_end:
if GPIO.input(pin):
return False
time.sleep(0.001)
_button_hold_handlers[pin](pin)
return True
def _do_handle_button(pin):
if _handle_hold(pin):
return
if pin in _button_handlers.keys() and callable(_button_handlers[pin]):
_button_handlers[pin](pin)
if not _button_repeat[pin]:
return
delay = 0.25
ramp_rate = 0.90
t_delay = 0.5
t_end = time.time() + t_delay
while time.time() < t_end:
if GPIO.input(pin):
return
time.sleep(0.001)
while not GPIO.input(pin):
_button_handlers[pin](pin)
time.sleep(delay)
delay *= ramp_rate
delay = max(0.01, delay)
[docs]def show():
"""Output the buffer to the displays"""
_sof()
for pixel in pixels:
r, g, b, brightness = pixel
_write_byte(0b11100000 | brightness)
_write_byte(b)
_write_byte(g)
_write_byte(r)
_eof()
[docs]def set_all(r, g, b, brightness=None, channel=None):
"""Set the RGB value and optionally brightness of all pixels
If you don't supply a brightness value, the last value set for each pixel be kept.
:param r: Amount of red: 0 to 255
:param g: Amount of green: 0 to 255
:param b: Amount of blue: 0 to 255
:param brightness: Brightness: 0.0 to 1.0 (default around 0.2)
:param channel: Optionally specify which bar to set: 0 or 1
"""
if channel is None or channel == 0:
for x in range(CHANNEL_PIXELS):
set_pixel(x, r, g, b, brightness)
if channel is None or channel == 1:
for x in range(CHANNEL_PIXELS):
set_pixel(x + (CHANNEL_PIXELS), r, g, b, brightness)
[docs]def set_pixel(x, r, g, b, brightness=None, channel=None):
"""Set the RGB value, and optionally brightness, of a single pixel
If you don't supply a brightness value, the last value will be kept.
:param x: The horizontal position of the pixel: 0 to 7
:param r: Amount of red: 0 to 255
:param g: Amount of green: 0 to 255
:param b: Amount of blue: 0 to 255
:param brightness: Brightness: 0.0 to 1.0 (default around 0.2)
:param channel: Optionally specify which bar to set: 0 or 1
"""
if brightness is None:
brightness = pixels[x][3]
else:
brightness = int(31.0 * brightness) & 0b11111
if channel in [0, 1]:
if x >= CHANNEL_PIXELS:
raise ValueError("Index should be < {} when displaying on a specific channel".format(CHANNEL_PIXELS))
x += channel * (CHANNEL_PIXELS)
if x >= CHANNEL_PIXELS:
x = NUM_PIXELS - 1 - (x - CHANNEL_PIXELS)
pixels[x] = [int(r) & 0xff,int(g) & 0xff,int(b) & 0xff,brightness]
[docs]def set_clear_on_exit(value=True):
"""Set whether the displays should be cleared upon exit
By default the displays will turn off on exit, but calling::
phatbeat.set_clear_on_exit(False)
Will ensure that it does not.
:param value: True or False (default True)
"""
global _clear_on_exit
_clear_on_exit = value
atexit.register(_exit)
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup([DAT,CLK],GPIO.OUT)
GPIO.setup(BUTTONS, GPIO.IN, pull_up_down=GPIO.PUD_UP)
for button in BUTTONS:
GPIO.add_event_detect(button, GPIO.FALLING, callback=_handle_button, bouncetime=200)