Created
June 10, 2012 17:57
-
-
Save ssokolow/2906766 to your computer and use it in GitHub Desktop.
Helper class and demo in Python for OpenPandora LEDs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
"""Simple OpenPandora LED wrapper with demo | |
By: Stephan Sokolow (deitarion/SSokolow) | |
LEDs on the left are (in order): | |
- sd1 | |
- sd2 | |
- wifi | |
- bluetooth | |
LEDs on the right are (in order): | |
- (unpopulated) | |
- (unpopulated) | |
- charger | |
- power | |
LEDs on the left are on or off. | |
LEDs on the right have variable brightness. | |
SD card LEDs will only light if the corresponding SD card is present. | |
Changelog: | |
- 0.2: | |
- LED.read() | |
- LED.from_index(int) | |
- Add epytext-format docstrings to the LED class. | |
- Clean up module behaviour for easy import into your project. | |
- Support the "with" statement and cleanup=True for easy LED resets. | |
- Convert sudo wrapper into a properly reusable function. | |
- Call "gksudo" if run outside a terminal or with redirected stdin. | |
- Demonstrate using the signal module to exit cleanly on kill/killall/pkill | |
- 0.1: | |
- Basic demo functionality and write-capable wrapper class | |
@note: Here are some ideas for more skilled users: | |
- Write a program which opens LED file handles, then does all its | |
work in an unprivileged child process. | |
- Listen to L1 and R1 and use them to change the LEDs and play a | |
sound so you can pretend your Pandora is a dollar-store raygun. | |
- Display WiFi signal strength or battery charge on the LEDs | |
- Listen on the microphone and make the LEDs a VU meter. | |
@todo: Support for manipulating triggers and restoring them on exit. | |
@todo: Make the demo even more CPU efficient by implementing an analogue to | |
Javascript's setInterval rather than using a static sleep() interval. | |
""" | |
__author__ = "Stephan Sokolow" | |
__license__ = "MIT" | |
__version__ = "0.2" | |
import contextlib, os, signal, sys | |
def elevate(msg="Attempting to gain root privileges... "): | |
"""If the program isn't running as root, re-exec() via sudo. | |
Otherwise, do nothing. | |
@note: gksudo will be used if stdin has been redirected | |
or the program is running in a terminal and X.org | |
wasn't started via "startx". | |
""" | |
if os.geteuid() != 0: | |
if sys.stdin.isatty(): | |
print msg | |
os.execvp('sudo', ['sudo'] + sys.argv) | |
else: | |
os.execvp('gksudo', ['gksudo', '-m', msg] + sys.argv) | |
class LED(object): | |
"""Simple API wrapper for OpenPandora LEDs""" | |
AVAILABLE_LEDS = ['sd1', 'sd2', 'wifi', 'bluetooth', | |
'charger', 'power'] | |
state = None | |
def __init__(self, name, cleanup=False): | |
""" | |
@param cleanup: Restore old LED values on C{close()} | |
@type cleanup: C{bool} | |
""" | |
self.name = name | |
self.fh = open('/sys/class/leds/pandora::%s/brightness' % name, 'wb+') | |
self.cleanup = cleanup | |
self.was = self.read() | |
if not isinstance(self.was, int): | |
raise Exception('%s: %s' % (self.name, self.was)) | |
# Context manager support | |
def __enter__(self): | |
return self | |
def __exit__(self, exc_type, exc_val, exc_tb): | |
self.close() | |
return False | |
def close(self): | |
"""Close the file handle and optionally restore old LED values | |
See L{__init__} for details. | |
""" | |
if self.cleanup and not self.fh.closed: | |
#TODO: Make sure triggers are restored too | |
self.write(self.was) | |
self.fh.close() | |
@classmethod | |
def from_index(cls, idx, *args, **kwargs): | |
"""Instantiate a new LED by position on the device.""" | |
return cls(cls.AVAILABLE_LEDS[idx], *args, **kwargs) | |
def on(self): | |
"""Convenience wrapper to set full brightness.""" | |
self.write(True) | |
def off(self): | |
"""Convenience wrapper to set no brightness.""" | |
self.write(False) | |
def toggle(self): | |
"""Convenience wrapper to toggle 0/255 brightness.""" | |
(self.off if self.state else self.on)() | |
def read(self): | |
"""Read the current brightness | |
@returns: A value in the range of 0 through 255 | |
@rtype: C{int} | |
""" | |
self.fh.seek(0) | |
return int(self.fh.read().strip('\n\0')) | |
def write(self, val): | |
"""Set a new brightness | |
@param val: C{True}, C{False}, or a number from 0 through 255. | |
@type val: C{int()}-compatible | |
""" | |
if val is True: | |
val = 255 | |
else: | |
# bool(False) == 0 so no need for a special case | |
# Also, this ensures we error out on invalid input | |
val = int(val) | |
if not 0 <= val <= 255: | |
raise ValueError("Need a boolean or integer from 0 through 255") | |
self.state = bool(val) | |
self.fh.write(str(val)) | |
self.fh.flush() | |
if __name__ == '__main__': | |
elevate("Manipulating LEDs to requires root access.") | |
import time | |
# Easy-to-customize pseudo-constants | |
SCANNER, BLINKER = range(0,4), 4 | |
SCANNER_DELAY, BLINKER_DELAY = 0.1, 0.5 | |
scanner = [LED.from_index(x, cleanup=True) for x in SCANNER] | |
blinker = LED.from_index(BLINKER, cleanup=True) | |
all = scanner + [blinker] | |
def quit(a, b): | |
# Remember, close() may reset LED states. | |
[x.close() for x in all] | |
sys.exit() | |
# Clean up LEDs when exited via kill/killall/pkill | |
for sig in ['SIGTERM', 'SIGHUP']: | |
signal.signal(getattr(signal, sig), quit) | |
pos, incr, max = -1, 1, len(scanner) - 1 | |
now, last_move, last_toggle = 0, 0, 0 | |
with contextlib.nested(*all): | |
while True: | |
now, then = time.time(), now | |
if now - last_move > SCANNER_DELAY: | |
last_move, pos = now, pos + incr | |
[y.write(x == pos) for x, y in enumerate(scanner)] | |
if (pos == max and incr > 0) or (pos == 0 and incr < 0): | |
incr *= -1 | |
if now - last_toggle > BLINKER_DELAY: | |
blinker.toggle() | |
last_toggle = now | |
# Don't spin the CPU unnecessarily | |
time.sleep(0.01) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment