Skip to content

Instantly share code, notes, and snippets.

@ssokolow
Created June 10, 2012 17:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ssokolow/2906766 to your computer and use it in GitHub Desktop.
Save ssokolow/2906766 to your computer and use it in GitHub Desktop.
Helper class and demo in Python for OpenPandora LEDs
#!/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