mirror of
https://github.com/KevinMidboe/python-gpiozero.git
synced 2025-10-29 17:50:37 +00:00
This finishes off implementing values and source for all (current) classes in gpiozero. I'm afraid things get rather complex in this commit. For starters, we've now got quite a few "aggregate" classes which necessarily don't descend from GPIODevice. To implement values and source on these I could either repeat a helluva lot of code or ... turn to mixin classes. Yeah, it's multiple inheritance time, baby! Unfortunately multiple inheritance doesn't work with __slots__ but we really ought to keep functionality that they provide us (raise AttributeError when an unknown attribute is set). So I've implemented this with ... erm ... metaclasses. Sorry!
This commit is contained in:
@@ -1,6 +1,12 @@
|
|||||||
from __future__ import absolute_import
|
from __future__ import (
|
||||||
|
unicode_literals,
|
||||||
|
print_function,
|
||||||
|
absolute_import,
|
||||||
|
division,
|
||||||
|
)
|
||||||
|
|
||||||
from .devices import (
|
from .devices import (
|
||||||
|
GPIODeviceClosed,
|
||||||
GPIODeviceError,
|
GPIODeviceError,
|
||||||
GPIODevice,
|
GPIODevice,
|
||||||
)
|
)
|
||||||
@@ -16,6 +22,7 @@ from .input_devices import (
|
|||||||
MCP3004,
|
MCP3004,
|
||||||
)
|
)
|
||||||
from .output_devices import (
|
from .output_devices import (
|
||||||
|
OutputDeviceError,
|
||||||
OutputDevice,
|
OutputDevice,
|
||||||
PWMOutputDevice,
|
PWMOutputDevice,
|
||||||
PWMLED,
|
PWMLED,
|
||||||
|
|||||||
@@ -1,19 +1,48 @@
|
|||||||
from .input_devices import Button
|
from __future__ import (
|
||||||
from .output_devices import LED, Buzzer, Motor
|
unicode_literals,
|
||||||
from .devices import GPIODeviceError
|
print_function,
|
||||||
|
absolute_import,
|
||||||
|
division,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
from itertools import izip as zip
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from .input_devices import InputDeviceError, Button
|
||||||
|
from .output_devices import OutputDeviceError, LED, Buzzer, Motor
|
||||||
|
from .devices import CompositeDevice, SourceMixin
|
||||||
|
|
||||||
|
|
||||||
class LEDBoard(object):
|
class LEDBoard(SourceMixin, CompositeDevice):
|
||||||
"""
|
"""
|
||||||
A Generic LED Board or collection of LEDs.
|
A Generic LED Board or collection of LEDs.
|
||||||
"""
|
"""
|
||||||
def __init__(self, leds):
|
def __init__(self, *pins):
|
||||||
self._leds = tuple(LED(led) for led in leds)
|
super(LEDBoard, self).__init__()
|
||||||
|
self._leds = tuple(LED(pin) for pin in pins)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
"""
|
||||||
|
A tuple containing a boolean value for each LED on the board. This
|
||||||
|
property can also be set to update the state of all LEDs on the board.
|
||||||
|
"""
|
||||||
|
return tuple(led.value for led in self._leds)
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value):
|
||||||
|
for l, v in zip(self._leds, value):
|
||||||
|
l.value = v
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def leds(self):
|
def leds(self):
|
||||||
|
"""
|
||||||
|
A tuple of all the `LED` objects contained by the instance.
|
||||||
|
"""
|
||||||
return self._leds
|
return self._leds
|
||||||
|
|
||||||
def on(self):
|
def on(self):
|
||||||
@@ -66,10 +95,11 @@ class PiLiter(LEDBoard):
|
|||||||
Ciseco Pi-LITEr: strip of 8 very bright LEDs.
|
Ciseco Pi-LITEr: strip of 8 very bright LEDs.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
leds = (4, 17, 27, 18, 22, 23, 24, 25)
|
super(PiLiter, self).__init__(4, 17, 27, 18, 22, 23, 24, 25)
|
||||||
super(PiLiter, self).__init__(leds)
|
|
||||||
|
|
||||||
|
|
||||||
|
TrafficLightTuple = namedtuple('TrafficLightTuple', ('red', 'amber', 'green'))
|
||||||
|
|
||||||
class TrafficLights(LEDBoard):
|
class TrafficLights(LEDBoard):
|
||||||
"""
|
"""
|
||||||
Generic Traffic Lights set.
|
Generic Traffic Lights set.
|
||||||
@@ -85,12 +115,37 @@ class TrafficLights(LEDBoard):
|
|||||||
"""
|
"""
|
||||||
def __init__(self, red=None, amber=None, green=None):
|
def __init__(self, red=None, amber=None, green=None):
|
||||||
if not all([red, amber, green]):
|
if not all([red, amber, green]):
|
||||||
raise GPIODeviceError('Red, Amber and Green pins must be provided')
|
raise OutputDeviceError('red, amber and green pins must be provided')
|
||||||
|
super(TrafficLights, self).__init__(red, amber, green)
|
||||||
|
|
||||||
self.red = LED(red)
|
@property
|
||||||
self.amber = LED(amber)
|
def value(self):
|
||||||
self.green = LED(green)
|
return TrafficLightTuple(*super(TrafficLights, self).value)
|
||||||
self._leds = (self.red, self.amber, self.green)
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value):
|
||||||
|
super(TrafficLights, self).value = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def red(self):
|
||||||
|
"""
|
||||||
|
The `LED` object representing the red LED.
|
||||||
|
"""
|
||||||
|
return self.leds[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amber(self):
|
||||||
|
"""
|
||||||
|
The `LED` object representing the red LED.
|
||||||
|
"""
|
||||||
|
return self.leds[1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def green(self):
|
||||||
|
"""
|
||||||
|
The `LED` object representing the green LED.
|
||||||
|
"""
|
||||||
|
return self.leds[2]
|
||||||
|
|
||||||
|
|
||||||
class PiTraffic(TrafficLights):
|
class PiTraffic(TrafficLights):
|
||||||
@@ -99,25 +154,47 @@ class PiTraffic(TrafficLights):
|
|||||||
and 11.
|
and 11.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
red, amber, green = (9, 10, 11)
|
super(PiTraffic, self).__init__(9, 10, 11)
|
||||||
super(PiTraffic, self).__init__(red, amber, green)
|
|
||||||
|
|
||||||
|
|
||||||
|
FishDishTuple = namedtuple('FishDishTuple', ('red', 'amber', 'green', 'buzzer'))
|
||||||
|
|
||||||
class FishDish(TrafficLights):
|
class FishDish(TrafficLights):
|
||||||
"""
|
"""
|
||||||
Pi Supply FishDish: traffic light LEDs, a button and a buzzer.
|
Pi Supply FishDish: traffic light LEDs, a button and a buzzer.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
red, amber, green = (9, 22, 4)
|
super(FishDish, self).__init__(9, 22, 4)
|
||||||
super(FishDish, self).__init__(red, amber, green)
|
|
||||||
self.buzzer = Buzzer(8)
|
self.buzzer = Buzzer(8)
|
||||||
self.button = Button(pin=7, pull_up=False)
|
self.button = Button(7, pull_up=False)
|
||||||
self._all = self._leds + (self.buzzer,)
|
self._all = self.leds + (self.buzzer,)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def all(self):
|
def all(self):
|
||||||
|
"""
|
||||||
|
A tuple containing objects for all the items on the board (several
|
||||||
|
`LED` objects, a `Buzzer`, and a `Button`).
|
||||||
|
"""
|
||||||
return self._all
|
return self._all
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
"""
|
||||||
|
Returns a named-tuple containing values representing the states of
|
||||||
|
the LEDs, and the buzzer. This property can also be set to a 4-tuple
|
||||||
|
to update the state of all the board's components.
|
||||||
|
"""
|
||||||
|
return FishDishTuple(
|
||||||
|
self.red.value,
|
||||||
|
self.amber.value,
|
||||||
|
self.green.value,
|
||||||
|
self.buzzer.value)
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value):
|
||||||
|
for i, v in zip(self._all, value):
|
||||||
|
i.value = v
|
||||||
|
|
||||||
def on(self):
|
def on(self):
|
||||||
"""
|
"""
|
||||||
Turn all the board's components on.
|
Turn all the board's components on.
|
||||||
@@ -208,97 +285,79 @@ class TrafficHat(FishDish):
|
|||||||
Ryanteck Traffic HAT: traffic light LEDs, a button and a buzzer.
|
Ryanteck Traffic HAT: traffic light LEDs, a button and a buzzer.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
green, amber, red = (22, 23, 24)
|
super(FishDish, self).__init__(22, 23, 24)
|
||||||
super(FishDish, self).__init__(red, amber, green)
|
|
||||||
self.buzzer = Buzzer(5)
|
self.buzzer = Buzzer(5)
|
||||||
self.button = Button(25)
|
self.button = Button(25)
|
||||||
self._all = self._leds + (self.buzzer,)
|
self._all = self._leds + (self.buzzer,)
|
||||||
|
|
||||||
|
|
||||||
class Robot(object):
|
RobotTuple = namedtuple('RobotTuple', ('left', 'right'))
|
||||||
|
|
||||||
|
class Robot(SourceMixin, CompositeDevice):
|
||||||
"""
|
"""
|
||||||
Generic dual-motor Robot.
|
Generic dual-motor Robot.
|
||||||
"""
|
"""
|
||||||
def __init__(self, left=None, right=None):
|
def __init__(self, left=None, right=None):
|
||||||
if not all([left, right]):
|
if not all([left, right]):
|
||||||
raise GPIODeviceError('left and right motor pins must be provided')
|
raise OutputDeviceError('left and right motor pins must be provided')
|
||||||
|
super(Robot, self).__init__()
|
||||||
|
self._left = Motor(*left)
|
||||||
|
self._right = Motor(*right)
|
||||||
|
|
||||||
left_forward, left_back = left
|
@property
|
||||||
right_forward, right_back = right
|
def value(self):
|
||||||
|
"""
|
||||||
|
Returns a tuple of two floating point values (-1 to 1) representing the
|
||||||
|
speeds of the robot's two motors (left and right). This property can
|
||||||
|
also be set to alter the speed of both motors.
|
||||||
|
"""
|
||||||
|
return RobotTuple(self._left.value, self._right.value)
|
||||||
|
|
||||||
self._left = Motor(forward=left_forward, back=left_back)
|
@value.setter
|
||||||
self._right = Motor(forward=right_forward, back=right_back)
|
def value(self, value):
|
||||||
|
self._left.value, self._right.value = value
|
||||||
self._min_pwm = self._left._min_pwm
|
|
||||||
self._max_pwm = self._left._max_pwm
|
|
||||||
|
|
||||||
def forward(self, speed=1):
|
def forward(self, speed=1):
|
||||||
"""
|
"""
|
||||||
Drive the robot forward.
|
Drive the robot forward by running both motors forward.
|
||||||
|
|
||||||
speed: `1`
|
speed: `1`
|
||||||
Speed at which to drive the motors, 0 to 1.
|
Speed at which to drive the motors, 0 to 1.
|
||||||
"""
|
"""
|
||||||
self._left._backward.off()
|
self._left.forward(speed)
|
||||||
self._right._backward.off()
|
self._right.forward(speed)
|
||||||
|
|
||||||
self._left._forward.on()
|
|
||||||
self._right._forward.on()
|
|
||||||
if speed < 1:
|
|
||||||
sleep(0.1) # warm up the motors
|
|
||||||
self._left._forward.value = speed
|
|
||||||
self._right._forward.value = speed
|
|
||||||
|
|
||||||
def backward(self, speed=1):
|
def backward(self, speed=1):
|
||||||
"""
|
"""
|
||||||
Drive the robot backward.
|
Drive the robot backward by running both motors backward.
|
||||||
|
|
||||||
speed: `1`
|
speed: `1`
|
||||||
Speed at which to drive the motors, 0 to 1.
|
Speed at which to drive the motors, 0 to 1.
|
||||||
"""
|
"""
|
||||||
self._left._forward.off()
|
self._left.backward(speed)
|
||||||
self._right._forward.off()
|
self._right.backward(speed)
|
||||||
|
|
||||||
self._left._backward.on()
|
|
||||||
self._right._backward.on()
|
|
||||||
if speed < 1:
|
|
||||||
sleep(0.1) # warm up the motors
|
|
||||||
self._left._backward.value = speed
|
|
||||||
self._right._backward.value = speed
|
|
||||||
|
|
||||||
def left(self, speed=1):
|
def left(self, speed=1):
|
||||||
"""
|
"""
|
||||||
Make the robot turn left.
|
Make the robot turn left by running the right motor forward and left
|
||||||
|
motor backward.
|
||||||
|
|
||||||
speed: `1`
|
speed: `1`
|
||||||
Speed at which to drive the motors, 0 to 1.
|
Speed at which to drive the motors, 0 to 1.
|
||||||
"""
|
"""
|
||||||
self._right._backward.off()
|
self._right.forward(speed)
|
||||||
self._left._forward.off()
|
self._left.backward(speed)
|
||||||
|
|
||||||
self._right._forward.on()
|
|
||||||
self._left._backward.on()
|
|
||||||
if speed < 1:
|
|
||||||
sleep(0.1) # warm up the motors
|
|
||||||
self._right._forward.value = speed
|
|
||||||
self._left._backward.value = speed
|
|
||||||
|
|
||||||
def right(self, speed=1):
|
def right(self, speed=1):
|
||||||
"""
|
"""
|
||||||
Make the robot turn right.
|
Make the robot turn right by running the left motor forward and right
|
||||||
|
motor backward.
|
||||||
|
|
||||||
speed: `1`
|
speed: `1`
|
||||||
Speed at which to drive the motors, 0 to 1.
|
Speed at which to drive the motors, 0 to 1.
|
||||||
"""
|
"""
|
||||||
self._left._backward.off()
|
self._left.forward(speed)
|
||||||
self._right._forward.off()
|
self._right.backward(speed)
|
||||||
|
|
||||||
self._left._forward.on()
|
|
||||||
self._right._backward.on()
|
|
||||||
if speed < 1:
|
|
||||||
sleep(0.1) # warm up the motors
|
|
||||||
self._left._forward.value = speed
|
|
||||||
self._right._backward.value = speed
|
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""
|
"""
|
||||||
@@ -313,6 +372,4 @@ class RyanteckRobot(Robot):
|
|||||||
RTK MCB Robot. Generic robot controller with pre-configured pin numbers.
|
RTK MCB Robot. Generic robot controller with pre-configured pin numbers.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
left = (17, 18)
|
super(RyanteckRobot, self).__init__((17, 18), (22, 23))
|
||||||
right = (22, 23)
|
|
||||||
super(RyanteckRobot, self).__init__(left=left, right=right)
|
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
from __future__ import (
|
||||||
|
unicode_literals,
|
||||||
|
print_function,
|
||||||
|
absolute_import,
|
||||||
|
division,
|
||||||
|
)
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
import weakref
|
import weakref
|
||||||
from threading import Thread, Event, RLock
|
from threading import Thread, Event, RLock
|
||||||
@@ -30,11 +37,123 @@ GPIO.setwarnings(False)
|
|||||||
class GPIODeviceError(Exception):
|
class GPIODeviceError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class GPIODeviceClosed(GPIODeviceError):
|
class GPIODeviceClosed(GPIODeviceError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class GPIODevice(object):
|
class GPIOFixedAttrs(type):
|
||||||
|
# NOTE Yes, this is a metaclass. Don't be scared - it's a simple one.
|
||||||
|
|
||||||
|
def __call__(cls, *args, **kwargs):
|
||||||
|
# Construct the class as normal and ensure it's a subclass of GPIOBase
|
||||||
|
# (defined below with a custom __setattrs__)
|
||||||
|
result = super(GPIOFixedAttrs, cls).__call__(*args, **kwargs)
|
||||||
|
assert isinstance(result, GPIOBase)
|
||||||
|
# At this point __new__ and __init__ have all been run. We now fix the
|
||||||
|
# set of attributes on the class by dir'ing the instance and creating a
|
||||||
|
# frozenset of the result called __attrs__ (which is queried by
|
||||||
|
# GPIOBase.__setattr__)
|
||||||
|
result.__attrs__ = frozenset(dir(result))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class GPIOBase(object):
|
||||||
|
__metaclass__ = GPIOFixedAttrs
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
# This overridden __setattr__ simply ensures that additional attributes
|
||||||
|
# cannot be set on the class after construction (it manages this in
|
||||||
|
# conjunction with the meta-class above). Traditionally, this is
|
||||||
|
# managed with __slots__; however, this doesn't work with Python's
|
||||||
|
# multiple inheritance system which we need to use in order to avoid
|
||||||
|
# repeating the "source" and "values" property code in myriad places
|
||||||
|
if hasattr(self, '__attrs__') and name not in self.__attrs__:
|
||||||
|
raise AttributeError(
|
||||||
|
"'%s' object has no attribute '%s'" % (
|
||||||
|
self.__class__.__name__, name))
|
||||||
|
return super(GPIOBase, self).__setattr__(name, value)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
# This is a placeholder which is simply here to ensure close() can be
|
||||||
|
# safely called from subclasses without worrying whether super-class'
|
||||||
|
# have it (which in turn is useful in conjunction with the SourceMixin
|
||||||
|
# class).
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, exc_tb):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
|
class ValuesMixin(object):
|
||||||
|
# NOTE Use this mixin *first* in the parent list
|
||||||
|
|
||||||
|
@property
|
||||||
|
def values(self):
|
||||||
|
"""
|
||||||
|
An infinite iterator of values read from `value`.
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
yield self.value
|
||||||
|
except GPIODeviceClosed:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
class SourceMixin(object):
|
||||||
|
# NOTE Use this mixin *first* in the parent list
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self._source = None
|
||||||
|
self._source_thread = None
|
||||||
|
super(SourceMixin, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
try:
|
||||||
|
super(SourceMixin, self).close()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
self.source = None
|
||||||
|
|
||||||
|
def _copy_values(self, source):
|
||||||
|
for v in source:
|
||||||
|
self.value = v
|
||||||
|
if self._source_thread.stopping.wait(0):
|
||||||
|
break
|
||||||
|
|
||||||
|
@property
|
||||||
|
def source(self):
|
||||||
|
"""
|
||||||
|
The iterable to use as a source of values for `value`.
|
||||||
|
"""
|
||||||
|
return self._source
|
||||||
|
|
||||||
|
@source.setter
|
||||||
|
def source(self, value):
|
||||||
|
if self._source_thread is not None:
|
||||||
|
self._source_thread.stop()
|
||||||
|
self._source_thread = None
|
||||||
|
self._source = value
|
||||||
|
if value is not None:
|
||||||
|
self._source_thread = GPIOThread(target=self._copy_values, args=(value,))
|
||||||
|
self._source_thread.start()
|
||||||
|
|
||||||
|
|
||||||
|
class CompositeDevice(ValuesMixin, GPIOBase):
|
||||||
|
"""
|
||||||
|
Represents a device composed of multiple GPIO devices like simple HATs,
|
||||||
|
H-bridge motor controllers, robots composed of multiple motors, etc.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GPIODevice(ValuesMixin, GPIOBase):
|
||||||
"""
|
"""
|
||||||
Represents a generic GPIO device.
|
Represents a generic GPIO device.
|
||||||
|
|
||||||
@@ -47,9 +166,6 @@ class GPIODevice(object):
|
|||||||
The GPIO pin (in BCM numbering) that the device is connected to. If
|
The GPIO pin (in BCM numbering) that the device is connected to. If
|
||||||
this is `None` a `GPIODeviceError` will be raised.
|
this is `None` a `GPIODeviceError` will be raised.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('_pin', '_active_state', '_inactive_state')
|
|
||||||
|
|
||||||
def __init__(self, pin=None):
|
def __init__(self, pin=None):
|
||||||
super(GPIODevice, self).__init__()
|
super(GPIODevice, self).__init__()
|
||||||
# self._pin must be set before any possible exceptions can be raised
|
# self._pin must be set before any possible exceptions can be raised
|
||||||
@@ -68,9 +184,6 @@ class GPIODevice(object):
|
|||||||
self._active_state = GPIO.HIGH
|
self._active_state = GPIO.HIGH
|
||||||
self._inactive_state = GPIO.LOW
|
self._inactive_state = GPIO.LOW
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def _read(self):
|
def _read(self):
|
||||||
try:
|
try:
|
||||||
return GPIO.input(self.pin) == self._active_state
|
return GPIO.input(self.pin) == self._active_state
|
||||||
@@ -132,6 +245,7 @@ class GPIODevice(object):
|
|||||||
... led.on()
|
... led.on()
|
||||||
...
|
...
|
||||||
"""
|
"""
|
||||||
|
super(GPIODevice, self).close()
|
||||||
with _GPIO_PINS_LOCK:
|
with _GPIO_PINS_LOCK:
|
||||||
pin = self._pin
|
pin = self._pin
|
||||||
self._pin = None
|
self._pin = None
|
||||||
@@ -140,12 +254,6 @@ class GPIODevice(object):
|
|||||||
GPIO.remove_event_detect(pin)
|
GPIO.remove_event_detect(pin)
|
||||||
GPIO.cleanup(pin)
|
GPIO.cleanup(pin)
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, exc_tb):
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pin(self):
|
def pin(self):
|
||||||
"""
|
"""
|
||||||
@@ -163,17 +271,6 @@ class GPIODevice(object):
|
|||||||
|
|
||||||
is_active = value
|
is_active = value
|
||||||
|
|
||||||
@property
|
|
||||||
def values(self):
|
|
||||||
"""
|
|
||||||
An infinite iterator of values read from `value`.
|
|
||||||
"""
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
yield self.value
|
|
||||||
except GPIODeviceClosed:
|
|
||||||
break
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
try:
|
try:
|
||||||
return "<gpiozero.%s object on pin=%d, is_active=%s>" % (
|
return "<gpiozero.%s object on pin=%d, is_active=%s>" % (
|
||||||
@@ -238,3 +335,4 @@ class GPIOQueue(GPIOThread):
|
|||||||
except ReferenceError:
|
except ReferenceError:
|
||||||
# Parent is dead; time to die!
|
# Parent is dead; time to die!
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
from __future__ import division
|
from __future__ import (
|
||||||
|
unicode_literals,
|
||||||
|
print_function,
|
||||||
|
absolute_import,
|
||||||
|
division,
|
||||||
|
)
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
import warnings
|
import warnings
|
||||||
@@ -10,7 +15,13 @@ from RPi import GPIO
|
|||||||
from w1thermsensor import W1ThermSensor
|
from w1thermsensor import W1ThermSensor
|
||||||
from spidev import SpiDev
|
from spidev import SpiDev
|
||||||
|
|
||||||
from .devices import GPIODeviceError, GPIODeviceClosed, GPIODevice, GPIOQueue
|
from .devices import (
|
||||||
|
GPIODeviceError,
|
||||||
|
GPIODeviceClosed,
|
||||||
|
GPIODevice,
|
||||||
|
CompositeDevice,
|
||||||
|
GPIOQueue,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InputDeviceError(GPIODeviceError):
|
class InputDeviceError(GPIODeviceError):
|
||||||
@@ -35,13 +46,6 @@ class InputDevice(GPIODevice):
|
|||||||
If `True`, the pin will be pulled high with an internal resistor. If
|
If `True`, the pin will be pulled high with an internal resistor. If
|
||||||
`False` (the default), the pin will be pulled low.
|
`False` (the default), the pin will be pulled low.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = (
|
|
||||||
'_pull_up',
|
|
||||||
'_active_edge',
|
|
||||||
'_inactive_edge',
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, pin=None, pull_up=False):
|
def __init__(self, pin=None, pull_up=False):
|
||||||
if pin in (2, 3) and not pull_up:
|
if pin in (2, 3) and not pull_up:
|
||||||
raise InputDeviceError(
|
raise InputDeviceError(
|
||||||
@@ -79,6 +83,10 @@ class InputDevice(GPIODevice):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def pull_up(self):
|
def pull_up(self):
|
||||||
|
"""
|
||||||
|
If `True`, the device uses a pull-up resistor to set the GPIO pin
|
||||||
|
"high" by default. Defaults to `False`.
|
||||||
|
"""
|
||||||
return self._pull_up
|
return self._pull_up
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@@ -102,15 +110,6 @@ class WaitableInputDevice(InputDevice):
|
|||||||
Note that this class provides no means of actually firing its events; it's
|
Note that this class provides no means of actually firing its events; it's
|
||||||
effectively an abstract base class.
|
effectively an abstract base class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = (
|
|
||||||
'_active_event',
|
|
||||||
'_inactive_event',
|
|
||||||
'_when_activated',
|
|
||||||
'_when_deactivated',
|
|
||||||
'_last_state',
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, pin=None, pull_up=False):
|
def __init__(self, pin=None, pull_up=False):
|
||||||
super(WaitableInputDevice, self).__init__(pin, pull_up)
|
super(WaitableInputDevice, self).__init__(pin, pull_up)
|
||||||
self._active_event = Event()
|
self._active_event = Event()
|
||||||
@@ -121,7 +120,7 @@ class WaitableInputDevice(InputDevice):
|
|||||||
|
|
||||||
def wait_for_active(self, timeout=None):
|
def wait_for_active(self, timeout=None):
|
||||||
"""
|
"""
|
||||||
Halt the program until the device is activated, or the timeout is
|
Pause the script until the device is activated, or the timeout is
|
||||||
reached.
|
reached.
|
||||||
|
|
||||||
timeout: `None`
|
timeout: `None`
|
||||||
@@ -132,7 +131,7 @@ class WaitableInputDevice(InputDevice):
|
|||||||
|
|
||||||
def wait_for_inactive(self, timeout=None):
|
def wait_for_inactive(self, timeout=None):
|
||||||
"""
|
"""
|
||||||
Halt the program until the device is deactivated, or the timeout is
|
Pause the script until the device is deactivated, or the timeout is
|
||||||
reached.
|
reached.
|
||||||
|
|
||||||
timeout: `None`
|
timeout: `None`
|
||||||
@@ -248,9 +247,6 @@ class DigitalInputDevice(WaitableInputDevice):
|
|||||||
ignore changes in state after an initial change. This defaults to
|
ignore changes in state after an initial change. This defaults to
|
||||||
`None` which indicates that no bounce compensation will be performed.
|
`None` which indicates that no bounce compensation will be performed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def __init__(self, pin=None, pull_up=False, bounce_time=None):
|
def __init__(self, pin=None, pull_up=False, bounce_time=None):
|
||||||
super(DigitalInputDevice, self).__init__(pin, pull_up)
|
super(DigitalInputDevice, self).__init__(pin, pull_up)
|
||||||
try:
|
try:
|
||||||
@@ -301,9 +297,6 @@ class SmoothedInputDevice(WaitableInputDevice):
|
|||||||
If `True`, a value will be returned immediately, but be aware that this
|
If `True`, a value will be returned immediately, but be aware that this
|
||||||
value is likely to fluctuate excessively.
|
value is likely to fluctuate excessively.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('_queue', '_threshold', '__weakref__')
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, pin=None, pull_up=False, threshold=0.5,
|
self, pin=None, pull_up=False, threshold=0.5,
|
||||||
queue_len=5, sample_wait=0.0, partial=False):
|
queue_len=5, sample_wait=0.0, partial=False):
|
||||||
@@ -388,6 +381,9 @@ class SmoothedInputDevice(WaitableInputDevice):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
|
"""
|
||||||
|
Returns `True` if the device is currently active and `False` otherwise.
|
||||||
|
"""
|
||||||
return self.value > self.threshold
|
return self.value > self.threshold
|
||||||
|
|
||||||
|
|
||||||
@@ -399,9 +395,6 @@ class Button(DigitalInputDevice):
|
|||||||
side of the switch, and ground to the other (the default `pull_up` value
|
side of the switch, and ground to the other (the default `pull_up` value
|
||||||
is `True`).
|
is `True`).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def __init__(self, pin=None, pull_up=True, bouncetime=None):
|
def __init__(self, pin=None, pull_up=True, bouncetime=None):
|
||||||
super(Button, self).__init__(pin, pull_up, bouncetime)
|
super(Button, self).__init__(pin, pull_up, bouncetime)
|
||||||
|
|
||||||
@@ -426,9 +419,6 @@ class MotionSensor(SmoothedInputDevice):
|
|||||||
particularly "jittery" you may wish to set this to a higher value (e.g. 5)
|
particularly "jittery" you may wish to set this to a higher value (e.g. 5)
|
||||||
to mitigate this.
|
to mitigate this.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, pin=None, queue_len=1, sample_rate=10, threshold=0.5,
|
self, pin=None, queue_len=1, sample_rate=10, threshold=0.5,
|
||||||
partial=False):
|
partial=False):
|
||||||
@@ -458,12 +448,6 @@ class LightSensor(SmoothedInputDevice):
|
|||||||
class repeatedly discharges the capacitor, then times the duration it takes
|
class repeatedly discharges the capacitor, then times the duration it takes
|
||||||
to charge (which will vary according to the light falling on the LDR).
|
to charge (which will vary according to the light falling on the LDR).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = (
|
|
||||||
'_charge_time_limit',
|
|
||||||
'_charged',
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, pin=None, queue_len=5, charge_time_limit=0.01,
|
self, pin=None, queue_len=5, charge_time_limit=0.01,
|
||||||
threshold=0.1, partial=False):
|
threshold=0.1, partial=False):
|
||||||
@@ -517,17 +501,11 @@ class TemperatureSensor(W1ThermSensor):
|
|||||||
return self.get_temperature()
|
return self.get_temperature()
|
||||||
|
|
||||||
|
|
||||||
class AnalogInputDevice(object):
|
class AnalogInputDevice(CompositeDevice):
|
||||||
"""
|
"""
|
||||||
Represents an analog input device connected to SPI (serial interface).
|
Represents an analog input device connected to SPI (serial interface).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = (
|
|
||||||
'_device',
|
|
||||||
'_bits',
|
|
||||||
'_spi',
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, device=0, bits=None):
|
def __init__(self, device=0, bits=None):
|
||||||
if bits is None:
|
if bits is None:
|
||||||
raise InputDeviceError('you must specify the bit resolution of the device')
|
raise InputDeviceError('you must specify the bit resolution of the device')
|
||||||
@@ -537,25 +515,32 @@ class AnalogInputDevice(object):
|
|||||||
self._bits = bits
|
self._bits = bits
|
||||||
self._spi = SpiDev()
|
self._spi = SpiDev()
|
||||||
self._spi.open(0, self.device)
|
self._spi.open(0, self.device)
|
||||||
|
super(AnalogInputDevice, self).__init__()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
"""
|
||||||
|
Shut down the device and release all associated resources.
|
||||||
|
"""
|
||||||
if self._spi:
|
if self._spi:
|
||||||
s = self._spi
|
s = self._spi
|
||||||
self._spi = None
|
self._spi = None
|
||||||
s.close()
|
s.close()
|
||||||
|
super(AnalogInputDevice, self).close()
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, exc_tb):
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bus(self):
|
def bus(self):
|
||||||
|
"""
|
||||||
|
The SPI bus that the device is connected to. As the Pi only has a
|
||||||
|
single (user accessible) SPI bus, this always returns 0.
|
||||||
|
"""
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device(self):
|
def device(self):
|
||||||
|
"""
|
||||||
|
The select pin that the device is connected to. The Pi has two select
|
||||||
|
pins so this will be 0 or 1.
|
||||||
|
"""
|
||||||
return self._device
|
return self._device
|
||||||
|
|
||||||
def _read(self):
|
def _read(self):
|
||||||
@@ -563,13 +548,15 @@ class AnalogInputDevice(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
|
"""
|
||||||
|
A value read from the device. This will be a floating point value
|
||||||
|
between 0 and 1 (scaled according to the number of bits supported by
|
||||||
|
the device).
|
||||||
|
"""
|
||||||
return self._read() / (2**self._bits - 1)
|
return self._read() / (2**self._bits - 1)
|
||||||
|
|
||||||
|
|
||||||
class MCP3008(AnalogInputDevice):
|
class MCP3008(AnalogInputDevice):
|
||||||
|
|
||||||
__slots__ = ('_channel')
|
|
||||||
|
|
||||||
def __init__(self, device=0, channel=0):
|
def __init__(self, device=0, channel=0):
|
||||||
if not 0 <= channel < 8:
|
if not 0 <= channel < 8:
|
||||||
raise InputDeviceError('channel must be between 0 and 7')
|
raise InputDeviceError('channel must be between 0 and 7')
|
||||||
@@ -578,6 +565,10 @@ class MCP3008(AnalogInputDevice):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def channel(self):
|
def channel(self):
|
||||||
|
"""
|
||||||
|
The channel to read data from. The MCP3008 has 8 channels (so this will
|
||||||
|
be between 0 and 7) while the MCP3004 has 4 channels (range 0 to 3).
|
||||||
|
"""
|
||||||
return self._channel
|
return self._channel
|
||||||
|
|
||||||
def _read(self):
|
def _read(self):
|
||||||
@@ -602,11 +593,10 @@ class MCP3008(AnalogInputDevice):
|
|||||||
|
|
||||||
|
|
||||||
class MCP3004(MCP3008):
|
class MCP3004(MCP3008):
|
||||||
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def __init__(self, device=0, channel=0):
|
def __init__(self, device=0, channel=0):
|
||||||
# MCP3004 protocol is identical to MCP3008 but the top bit of the
|
# MCP3004 protocol is identical to MCP3008 but the top bit of the
|
||||||
# channel number must be 0 (effectively restricting it to 4 channels)
|
# channel number must be 0 (effectively restricting it to 4 channels)
|
||||||
if not 0 <= channel < 4:
|
if not 0 <= channel < 4:
|
||||||
raise InputDeviceError('channel must be between 0 and 3')
|
raise InputDeviceError('channel must be between 0 and 3')
|
||||||
|
super(MCP3004, self).__init__(device, channel)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
from __future__ import (
|
||||||
|
unicode_literals,
|
||||||
|
print_function,
|
||||||
|
absolute_import,
|
||||||
|
division,
|
||||||
|
)
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
@@ -5,14 +12,21 @@ from itertools import repeat
|
|||||||
|
|
||||||
from RPi import GPIO
|
from RPi import GPIO
|
||||||
|
|
||||||
from .devices import GPIODeviceError, GPIODevice, GPIOThread
|
from .devices import (
|
||||||
|
GPIODeviceError,
|
||||||
|
GPIODeviceClosed,
|
||||||
|
GPIODevice,
|
||||||
|
GPIOThread,
|
||||||
|
CompositeDevice,
|
||||||
|
SourceMixin,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OutputDeviceError(GPIODeviceError):
|
class OutputDeviceError(GPIODeviceError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class OutputDevice(GPIODevice):
|
class OutputDevice(SourceMixin, GPIODevice):
|
||||||
"""
|
"""
|
||||||
Represents a generic GPIO output device.
|
Represents a generic GPIO output device.
|
||||||
|
|
||||||
@@ -25,13 +39,8 @@ class OutputDevice(GPIODevice):
|
|||||||
`False`, the `on` method will set the GPIO to LOW (the `off` method
|
`False`, the `on` method will set the GPIO to LOW (the `off` method
|
||||||
always does the opposite).
|
always does the opposite).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('_active_high', '_source', '_source_thread')
|
|
||||||
|
|
||||||
def __init__(self, pin=None, active_high=True):
|
def __init__(self, pin=None, active_high=True):
|
||||||
self._active_high = active_high
|
self._active_high = active_high
|
||||||
self._source = None
|
|
||||||
self._source_thread = None
|
|
||||||
super(OutputDevice, self).__init__(pin)
|
super(OutputDevice, self).__init__(pin)
|
||||||
self._active_state = GPIO.HIGH if active_high else GPIO.LOW
|
self._active_state = GPIO.HIGH if active_high else GPIO.LOW
|
||||||
self._inactive_state = GPIO.LOW if active_high else GPIO.HIGH
|
self._inactive_state = GPIO.LOW if active_high else GPIO.HIGH
|
||||||
@@ -52,10 +61,6 @@ class OutputDevice(GPIODevice):
|
|||||||
self.close()
|
self.close()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.source = None
|
|
||||||
super(OutputDevice, self).close()
|
|
||||||
|
|
||||||
def _write(self, value):
|
def _write(self, value):
|
||||||
GPIO.output(self.pin, bool(value))
|
GPIO.output(self.pin, bool(value))
|
||||||
|
|
||||||
@@ -79,26 +84,6 @@ class OutputDevice(GPIODevice):
|
|||||||
def value(self, value):
|
def value(self, value):
|
||||||
self._write(value)
|
self._write(value)
|
||||||
|
|
||||||
def _copy_values(self, source):
|
|
||||||
for v in source:
|
|
||||||
self.value = v
|
|
||||||
if self._source_thread.stopping.wait(0):
|
|
||||||
break
|
|
||||||
|
|
||||||
@property
|
|
||||||
def source(self):
|
|
||||||
return self._source
|
|
||||||
|
|
||||||
@source.setter
|
|
||||||
def source(self, value):
|
|
||||||
if self._source_thread is not None:
|
|
||||||
self._source_thread.stop()
|
|
||||||
self._source_thread = None
|
|
||||||
self._source = value
|
|
||||||
if value is not None:
|
|
||||||
self._source_thread = GPIOThread(target=self._copy_values, args=(value,))
|
|
||||||
self._source_thread.start()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def active_high(self):
|
def active_high(self):
|
||||||
return self._active_high
|
return self._active_high
|
||||||
@@ -120,12 +105,9 @@ class DigitalOutputDevice(OutputDevice):
|
|||||||
optional background thread to handle toggling the device state without
|
optional background thread to handle toggling the device state without
|
||||||
further interaction.
|
further interaction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('_blink_thread', '_lock')
|
|
||||||
|
|
||||||
def __init__(self, pin=None, active_high=True):
|
def __init__(self, pin=None, active_high=True):
|
||||||
super(DigitalOutputDevice, self).__init__(pin, active_high)
|
|
||||||
self._blink_thread = None
|
self._blink_thread = None
|
||||||
|
super(DigitalOutputDevice, self).__init__(pin, active_high)
|
||||||
self._lock = Lock()
|
self._lock = Lock()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
@@ -148,8 +130,8 @@ class DigitalOutputDevice(OutputDevice):
|
|||||||
|
|
||||||
def toggle(self):
|
def toggle(self):
|
||||||
"""
|
"""
|
||||||
Reverse the state of the device.
|
Reverse the state of the device. If it's on, turn it off; if it's off,
|
||||||
If it's on, turn it off; if it's off, turn it on.
|
turn it on.
|
||||||
"""
|
"""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
if self.is_active:
|
if self.is_active:
|
||||||
@@ -161,20 +143,20 @@ class DigitalOutputDevice(OutputDevice):
|
|||||||
"""
|
"""
|
||||||
Make the device turn on and off repeatedly.
|
Make the device turn on and off repeatedly.
|
||||||
|
|
||||||
on_time: 1
|
on_time: `1`
|
||||||
Number of seconds on
|
Number of seconds on
|
||||||
|
|
||||||
off_time: 1
|
off_time: `1`
|
||||||
Number of seconds off
|
Number of seconds off
|
||||||
|
|
||||||
n: None
|
n: `None`
|
||||||
Number of times to blink; None means forever
|
Number of times to blink; `None` means forever
|
||||||
|
|
||||||
background: True
|
background: `True`
|
||||||
If True, start a background thread to continue blinking and return
|
If `True`, start a background thread to continue blinking and
|
||||||
immediately. If False, only return when the blink is finished
|
return immediately. If `False`, only return when the blink is
|
||||||
(warning: the default value of n will result in this method never
|
finished (warning: the default value of n will result in this
|
||||||
returning).
|
method never returning).
|
||||||
"""
|
"""
|
||||||
self._stop_blink()
|
self._stop_blink()
|
||||||
self._blink_thread = GPIOThread(
|
self._blink_thread = GPIOThread(
|
||||||
@@ -209,7 +191,7 @@ class LED(DigitalOutputDevice):
|
|||||||
anode (long leg) of the LED, and the cathode (short leg) to ground, with
|
anode (long leg) of the LED, and the cathode (short leg) to ground, with
|
||||||
an optional resistor to prevent the LED from burning out.
|
an optional resistor to prevent the LED from burning out.
|
||||||
"""
|
"""
|
||||||
__slots__ = ()
|
pass
|
||||||
|
|
||||||
LED.is_lit = LED.is_active
|
LED.is_lit = LED.is_active
|
||||||
|
|
||||||
@@ -221,16 +203,13 @@ class Buzzer(DigitalOutputDevice):
|
|||||||
A typical configuration of such a device is to connect a GPIO pin to the
|
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.
|
anode (long leg) of the buzzer, and the cathode (short leg) to ground.
|
||||||
"""
|
"""
|
||||||
__slots__ = ()
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PWMOutputDevice(DigitalOutputDevice):
|
class PWMOutputDevice(DigitalOutputDevice):
|
||||||
"""
|
"""
|
||||||
Generic Output device configured for PWM (Pulse-Width Modulation).
|
Generic Output device configured for PWM (Pulse-Width Modulation).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('_pwm', '_frequency', '_value')
|
|
||||||
|
|
||||||
def __init__(self, pin=None, frequency=100):
|
def __init__(self, pin=None, frequency=100):
|
||||||
self._pwm = None
|
self._pwm = None
|
||||||
super(PWMOutputDevice, self).__init__(pin)
|
super(PWMOutputDevice, self).__init__(pin)
|
||||||
@@ -278,6 +257,9 @@ class PWMOutputDevice(DigitalOutputDevice):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
|
"""
|
||||||
|
Returns `True` if the device is currently active and `False` otherwise.
|
||||||
|
"""
|
||||||
return self.value > 0.0
|
return self.value > 0.0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -295,8 +277,17 @@ class PWMOutputDevice(DigitalOutputDevice):
|
|||||||
|
|
||||||
|
|
||||||
class PWMLED(PWMOutputDevice):
|
class PWMLED(PWMOutputDevice):
|
||||||
|
"""
|
||||||
|
An LED (Light Emmitting Diode) component with variable brightness.
|
||||||
|
|
||||||
|
A typical configuration of such a device is to connect a GPIO pin to the
|
||||||
|
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
|
pass
|
||||||
|
|
||||||
|
PWMLED.is_lit = PWMLED.is_active
|
||||||
|
|
||||||
|
|
||||||
def _led_property(index, doc=None):
|
def _led_property(index, doc=None):
|
||||||
return property(
|
return property(
|
||||||
@@ -306,7 +297,7 @@ def _led_property(index, doc=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RGBLED(object):
|
class RGBLED(SourceMixin, CompositeDevice):
|
||||||
"""
|
"""
|
||||||
Single LED with individually controllable red, green and blue components.
|
Single LED with individually controllable red, green and blue components.
|
||||||
|
|
||||||
@@ -319,10 +310,10 @@ class RGBLED(object):
|
|||||||
blue: `None`
|
blue: `None`
|
||||||
The GPIO pin that controls the blue component of the RGB LED.
|
The GPIO pin that controls the blue component of the RGB LED.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('_leds')
|
|
||||||
|
|
||||||
def __init__(self, red=None, green=None, blue=None):
|
def __init__(self, red=None, green=None, blue=None):
|
||||||
|
if not all([red, green, blue]):
|
||||||
|
raise OutputDeviceError('red, green, and blue pins must be provided')
|
||||||
|
super(RGBLED, self).__init__()
|
||||||
self._leds = tuple(PWMOutputDevice(pin) for pin in (red, green, blue))
|
self._leds = tuple(PWMOutputDevice(pin) for pin in (red, green, blue))
|
||||||
|
|
||||||
red = _led_property(0)
|
red = _led_property(0)
|
||||||
@@ -330,78 +321,98 @@ class RGBLED(object):
|
|||||||
blue = _led_property(2)
|
blue = _led_property(2)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def color(self):
|
def value(self):
|
||||||
"""
|
"""
|
||||||
Set the color of the LED from an RGB 3-tuple of `(red, green, blue)`
|
Represents the color of the LED as an RGB 3-tuple of `(red, green,
|
||||||
where each value between 0 and 1.
|
blue)` where each value is between 0 and 1.
|
||||||
|
|
||||||
For example, purple would be `(1, 0, 1)` and yellow would be `(1, 1,
|
For example, purple would be `(1, 0, 1)` and yellow would be `(1, 1,
|
||||||
0)`, while orange would be `(1, 0.5, 0)`.
|
0)`, while orange would be `(1, 0.5, 0)`.
|
||||||
"""
|
"""
|
||||||
return (self.red, self.green, self.blue)
|
return (self.red, self.green, self.blue)
|
||||||
|
|
||||||
@color.setter
|
@value.setter
|
||||||
def color(self, value):
|
def value(self, value):
|
||||||
self.red, self.green, self.blue = value
|
self.red, self.green, self.blue = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_active(self):
|
||||||
|
"""
|
||||||
|
Returns `True` if the LED is currently active and `False` otherwise.
|
||||||
|
"""
|
||||||
|
return self.value != (0, 0, 0)
|
||||||
|
|
||||||
|
color = value
|
||||||
|
|
||||||
def on(self):
|
def on(self):
|
||||||
"""
|
"""
|
||||||
Turn the device on. This equivalent to setting the device color to
|
Turn the device on. This equivalent to setting the device color to
|
||||||
white `(1, 1, 1)`.
|
white `(1, 1, 1)`.
|
||||||
"""
|
"""
|
||||||
self.color = (1, 1, 1)
|
self.value = (1, 1, 1)
|
||||||
|
|
||||||
def off(self):
|
def off(self):
|
||||||
"""
|
"""
|
||||||
Turn the device off. This is equivalent to setting the device color
|
Turn the device off. This is equivalent to setting the device color
|
||||||
to black `(0, 0, 0)`.
|
to black `(0, 0, 0)`.
|
||||||
"""
|
"""
|
||||||
self.color = (0, 0, 0)
|
self.value = (0, 0, 0)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
for led in self._leds:
|
for led in self._leds:
|
||||||
led.close()
|
led.close()
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, exc_tb):
|
class Motor(SourceMixin, CompositeDevice):
|
||||||
self.close()
|
|
||||||
|
|
||||||
|
|
||||||
class Motor(object):
|
|
||||||
"""
|
"""
|
||||||
Generic bi-directional motor.
|
Generic bi-directional motor.
|
||||||
"""
|
"""
|
||||||
|
def __init__(self, forward=None, backward=None):
|
||||||
__slots__ = ('_forward', '_backward')
|
|
||||||
|
|
||||||
def __init__(self, forward=None, back=None):
|
|
||||||
if not all([forward, back]):
|
if not all([forward, back]):
|
||||||
raise GPIODeviceError('forward and back pins must be provided')
|
raise OutputDeviceError('forward and back pins must be provided')
|
||||||
|
super(Motor, self).__init__()
|
||||||
self._forward = PWMOutputDevice(forward)
|
self._forward = PWMOutputDevice(forward)
|
||||||
self._backward = PWMOutputDevice(back)
|
self._backward = PWMOutputDevice(backward)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
"""
|
||||||
|
Represents the speed of the motor as a floating point value between -1
|
||||||
|
(full speed backward) and 1 (full speed forward).
|
||||||
|
"""
|
||||||
|
return self._forward.value - self._backward.value
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value):
|
||||||
|
if not -1 <= value <= 1:
|
||||||
|
raise OutputDeviceError("Motor value must be between -1 and 1")
|
||||||
|
if value > 0:
|
||||||
|
self.forward(value)
|
||||||
|
elif value < 0:
|
||||||
|
self.backward(-value)
|
||||||
|
else:
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_active(self):
|
||||||
|
"""
|
||||||
|
Returns `True` if the motor is currently active and `False` otherwise.
|
||||||
|
"""
|
||||||
|
return self.value != 0
|
||||||
|
|
||||||
def forward(self, speed=1):
|
def forward(self, speed=1):
|
||||||
"""
|
"""
|
||||||
Drive the motor forwards
|
Drive the motor forwards
|
||||||
"""
|
"""
|
||||||
self._backward.off()
|
self._backward.off()
|
||||||
self._forward.on()
|
self._forward.value = speed
|
||||||
if speed < 1:
|
|
||||||
sleep(0.1) # warm up the motor
|
|
||||||
self._forward.value = speed
|
|
||||||
|
|
||||||
def backward(self, speed=1):
|
def backward(self, speed=1):
|
||||||
"""
|
"""
|
||||||
Drive the motor backwards
|
Drive the motor backwards
|
||||||
"""
|
"""
|
||||||
self._forward.off()
|
self._forward.off()
|
||||||
self._backward.on()
|
self._backward.value = speed
|
||||||
if speed < 1:
|
|
||||||
sleep(0.1) # warm up the motor
|
|
||||||
self._backward.value = speed
|
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""
|
"""
|
||||||
@@ -409,3 +420,4 @@ class Motor(object):
|
|||||||
"""
|
"""
|
||||||
self._forward.off()
|
self._forward.off()
|
||||||
self._backward.off()
|
self._backward.off()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user