Merge pull request #77 from waveform80/moar-aliases

Fix #74
This commit is contained in:
Ben Nuttall
2015-10-17 22:06:41 +01:00
3 changed files with 138 additions and 74 deletions

View File

@@ -47,7 +47,11 @@ class GPIODevice(object):
The GPIO pin (in BCM numbering) that the device is connected to. If
this is `None` a `GPIODeviceError` will be raised.
"""
__slots__ = ('_pin', '_active_state', '_inactive_state')
def __init__(self, pin=None):
super(GPIODevice, self).__init__()
# self._pin must be set before any possible exceptions can be raised
# because it's accessed in __del__. However, it mustn't be given the
# value of pin until we've verified that it isn't already allocated

View File

@@ -13,16 +13,6 @@ from spidev import SpiDev
from .devices import GPIODeviceError, GPIODeviceClosed, GPIODevice, GPIOQueue
def _alias(key, doc=None):
if doc is None:
doc = 'Alias for %s' % key
return property(
lambda self: getattr(self, key),
lambda self, val: setattr(self, key, val),
doc=doc
)
class InputDeviceError(GPIODeviceError):
pass
@@ -45,6 +35,13 @@ class InputDevice(GPIODevice):
If `True`, the pin will be pulled high with an internal resistor. If
`False` (the default), the pin will be pulled low.
"""
__slots__ = (
'_pull_up',
'_active_edge',
'_inactive_edge',
)
def __init__(self, pin=None, pull_up=False):
if pin in (2, 3) and not pull_up:
raise InputDeviceError(
@@ -105,6 +102,15 @@ class WaitableInputDevice(InputDevice):
Note that this class provides no means of actually firing its events; it's
effectively an abstract base class.
"""
__slots__ = (
'_active_event',
'_inactive_event',
'_when_activated',
'_when_deactivated',
'_last_state',
)
def __init__(self, pin=None, pull_up=False):
super(WaitableInputDevice, self).__init__(pin, pull_up)
self._active_event = Event()
@@ -135,13 +141,9 @@ class WaitableInputDevice(InputDevice):
"""
return self._inactive_event.wait(timeout)
def _get_when_activated(self):
return self._when_activated
def _set_when_activated(self, value):
self._when_activated = self._wrap_callback(value)
when_activated = property(_get_when_activated, _set_when_activated, doc="""\
@property
def when_activated(self):
"""
The function to run when the device changes state from inactive to
active.
@@ -154,15 +156,16 @@ class WaitableInputDevice(InputDevice):
Set this property to `None` (the default) to disable the event.
See also: when_deactivated.
""")
"""
return self._when_activated
def _get_when_deactivated(self):
return self._when_deactivated
@when_activated.setter
def when_activated(self, value):
self._when_activated = self._wrap_callback(value)
def _set_when_deactivated(self, value):
self._when_deactivated = self._wrap_callback(value)
when_deactivated = property(_get_when_deactivated, _set_when_deactivated, doc="""\
@property
def when_deactivated(self):
"""
The function to run when the device changes state from active to
inactive.
@@ -175,7 +178,12 @@ class WaitableInputDevice(InputDevice):
Set this property to `None` (the default) to disable the event.
See also: when_activated.
""")
"""
return self._when_deactivated
@when_deactivated.setter
def when_deactivated(self, value):
self._when_deactivated = self._wrap_callback(value)
def _wrap_callback(self, fn):
if fn is None:
@@ -240,6 +248,9 @@ class DigitalInputDevice(WaitableInputDevice):
ignore changes in state after an initial change. This defaults to
`None` which indicates that no bounce compensation will be performed.
"""
__slots__ = ()
def __init__(self, pin=None, pull_up=False, bounce_time=None):
super(DigitalInputDevice, self).__init__(pin, pull_up)
try:
@@ -290,6 +301,9 @@ class SmoothedInputDevice(WaitableInputDevice):
If `True`, a value will be returned immediately, but be aware that this
value is likely to fluctuate excessively.
"""
__slots__ = ('_queue', '_threshold', '__weakref__')
def __init__(
self, pin=None, pull_up=False, threshold=0.5,
queue_len=5, sample_wait=0.0, partial=False):
@@ -357,20 +371,21 @@ class SmoothedInputDevice(WaitableInputDevice):
self._check_open()
return self._queue.value
def _get_threshold(self):
@property
def threshold(self):
"""
If `value` exceeds this amount, then `is_active` will return `True`.
"""
return self._threshold
def _set_threshold(self, value):
@threshold.setter
def threshold(self, value):
if not (0.0 < value < 1.0):
raise InputDeviceError(
'threshold must be between zero and one exclusive'
)
self._threshold = float(value)
threshold = property(_get_threshold, _set_threshold, doc="""\
If `value` exceeds this amount, then `is_active` will return `True`.
""")
@property
def is_active(self):
return self.value > self.threshold
@@ -384,16 +399,17 @@ class Button(DigitalInputDevice):
side of the switch, and ground to the other (the default `pull_up` value
is `True`).
"""
__slots__ = ()
def __init__(self, pin=None, pull_up=True, bouncetime=None):
super(Button, self).__init__(pin, pull_up, bouncetime)
is_pressed = _alias('is_active')
when_pressed = _alias('when_activated')
when_released = _alias('when_deactivated')
wait_for_press = _alias('wait_for_active')
wait_for_release = _alias('wait_for_inactive')
Button.is_pressed = Button.is_active
Button.when_pressed = Button.when_activated
Button.when_released = Button.when_deactivated
Button.wait_for_press = Button.wait_for_active
Button.wait_for_release = Button.wait_for_inactive
class MotionSensor(SmoothedInputDevice):
@@ -410,6 +426,9 @@ class MotionSensor(SmoothedInputDevice):
particularly "jittery" you may wish to set this to a higher value (e.g. 5)
to mitigate this.
"""
__slots__ = ()
def __init__(
self, pin=None, queue_len=1, sample_rate=10, threshold=0.5,
partial=False):
@@ -423,13 +442,11 @@ class MotionSensor(SmoothedInputDevice):
self.close()
raise
motion_detected = _alias('is_active')
when_motion = _alias('when_activated')
when_no_motion = _alias('when_deactivated')
wait_for_motion = _alias('wait_for_active')
wait_for_no_motion = _alias('wait_for_inactive')
MotionSensor.motion_detected = MotionSensor.is_active
MotionSensor.when_motion = MotionSensor.when_activated
MotionSensor.when_no_motion = MotionSensor.when_deactivated
MotionSensor.wait_for_motion = MotionSensor.wait_for_active
MotionSensor.wait_for_no_motion = MotionSensor.wait_for_inactive
class LightSensor(SmoothedInputDevice):
@@ -441,6 +458,12 @@ class LightSensor(SmoothedInputDevice):
class repeatedly discharges the capacitor, then times the duration it takes
to charge (which will vary according to the light falling on the LDR).
"""
__slots__ = (
'_charge_time_limit',
'_charged',
)
def __init__(
self, pin=None, queue_len=5, charge_time_limit=0.01,
threshold=0.1, partial=False):
@@ -478,13 +501,11 @@ class LightSensor(SmoothedInputDevice):
self.charge_time_limit
)
light_detected = _alias('is_active')
when_light = _alias('when_activated')
when_dark = _alias('when_deactivated')
wait_for_light = _alias('wait_for_active')
wait_for_dark = _alias('wait_for_inactive')
LightSensor.light_detected = LightSensor.is_active
LightSensor.when_light = LightSensor.when_activated
LightSensor.when_dark = LightSensor.when_deactivated
LightSensor.wait_for_light = LightSensor.wait_for_active
LightSensor.wait_for_dark = LightSensor.wait_for_inactive
class TemperatureSensor(W1ThermSensor):
@@ -500,6 +521,13 @@ class AnalogInputDevice(object):
"""
Represents an analog input device connected to SPI (serial interface).
"""
__slots__ = (
'_device',
'_bits',
'_spi',
)
def __init__(self, device=0, bits=None):
if bits is None:
raise InputDeviceError('you must specify the bit resolution of the device')
@@ -539,6 +567,9 @@ class AnalogInputDevice(object):
class MCP3008(AnalogInputDevice):
__slots__ = ('_channel')
def __init__(self, device=0, channel=0):
if not 0 <= channel < 8:
raise InputDeviceError('channel must be between 0 and 7')
@@ -571,6 +602,9 @@ class MCP3008(AnalogInputDevice):
class MCP3004(MCP3008):
__slots__ = ()
def __init__(self, device=0, channel=0):
# MCP3004 protocol is identical to MCP3008 but the top bit of the
# channel number must be 0 (effectively restricting it to 4 channels)

View File

@@ -25,6 +25,9 @@ class OutputDevice(GPIODevice):
`False`, the `on` method will set the GPIO to LOW (the `off` method
always does the opposite).
"""
__slots__ = ('_active_high')
def __init__(self, pin=None, active_high=True):
self._active_high = active_high
super(OutputDevice, self).__init__(pin)
@@ -83,6 +86,9 @@ class DigitalOutputDevice(OutputDevice):
optional background thread to handle toggling the device state without
further interaction.
"""
__slots__ = ('_blink_thread', '_lock')
def __init__(self, pin=None, active_high=True):
super(DigitalOutputDevice, self).__init__(pin, active_high)
self._blink_thread = None
@@ -165,7 +171,9 @@ class LED(DigitalOutputDevice):
anode (long leg) of the LED, and the cathode (short leg) to ground, with
an optional resistor to prevent the LED from burning out.
"""
pass
__slots__ = ()
LED.is_lit = LED.is_active
class Buzzer(DigitalOutputDevice):
@@ -175,13 +183,16 @@ class Buzzer(DigitalOutputDevice):
A typical configuration of such a device is to connect a GPIO pin to the
anode (long leg) of the buzzer, and the cathode (short leg) to ground.
"""
pass
__slots__ = ()
class PWMOutputDevice(DigitalOutputDevice):
"""
Generic Output device configured for PWM (Pulse-Width Modulation).
"""
__slots__ = ('_pwm', '_frequency', '_value')
def __init__(self, pin=None, frequency=100):
self._pwm = None
super(PWMOutputDevice, self).__init__(pin)
@@ -214,35 +225,35 @@ class PWMOutputDevice(DigitalOutputDevice):
self._pwm.ChangeDutyCycle(value * 100)
self._value = value
def _get_value(self):
return self._read()
def _set_value(self, value):
self._stop_blink()
self._write(value)
value = property(_get_value, _set_value, doc="""\
@property
def value(self):
"""
The duty cycle of the PWM device. 0.0 is off, 1.0 is fully on. Values
in between may be specified for varying levels of power in the device.
"""
)
return self._read()
@value.setter
def value(self, value):
self._stop_blink()
self._write(value)
@property
def is_active(self):
return self.value > 0.0
def _get_frequency(self):
return self._frequency
def _set_frequency(self, value):
self._pwm.ChangeFrequency(value)
self._frequency = value
frequency = property(_get_frequency, _set_frequency, doc="""\
@property
def frequency(self):
"""
The frequency of the pulses used with the PWM device, in Hz. The
default is 100.
"""
)
return self._frequency
@frequency.setter
def frequency(self, value):
self._pwm.ChangeFrequency(value)
self._frequency = value
def _led_property(index, doc=None):
@@ -265,6 +276,9 @@ class RGBLED(object):
blue: `None`
The GPIO pin that controls the blue component of the RGB LED.
"""
__slots__ = ('_leds')
def __init__(self, red=None, green=None, blue=None):
self._leds = tuple(PWMOutputDevice(pin) for pin in (red, green, blue))
@@ -274,6 +288,13 @@ class RGBLED(object):
@property
def color(self):
"""
Set the color of the LED from an RGB 3-tuple of `(red, green, blue)`
where each value between 0 and 1.
For example, purple would be `(1, 0, 1)` and yellow would be `(1, 1,
0)`, while orange would be `(1, 0.5, 0)`.
"""
return (self.red, self.green, self.blue)
@color.setter
@@ -282,13 +303,15 @@ class RGBLED(object):
def on(self):
"""
Turn the device on
Turn the device on. This equivalent to setting the device color to
white `(1, 1, 1)`.
"""
self.color = (1, 1, 1)
def off(self):
"""
Turn the device off
Turn the device off. This is equivalent to setting the device color
to black `(0, 0, 0)`.
"""
self.color = (0, 0, 0)
@@ -307,6 +330,9 @@ class Motor(object):
"""
Generic bi-directional motor.
"""
__slots__ = ('_forward', '_backward')
def __init__(self, forward=None, back=None):
if not all([forward, back]):
raise GPIODeviceError('forward and back pins must be provided')