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 (
|
||||
GPIODeviceClosed,
|
||||
GPIODeviceError,
|
||||
GPIODevice,
|
||||
)
|
||||
@@ -16,6 +22,7 @@ from .input_devices import (
|
||||
MCP3004,
|
||||
)
|
||||
from .output_devices import (
|
||||
OutputDeviceError,
|
||||
OutputDevice,
|
||||
PWMOutputDevice,
|
||||
PWMLED,
|
||||
|
||||
@@ -1,19 +1,48 @@
|
||||
from .input_devices import Button
|
||||
from .output_devices import LED, Buzzer, Motor
|
||||
from .devices import GPIODeviceError
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
print_function,
|
||||
absolute_import,
|
||||
division,
|
||||
)
|
||||
try:
|
||||
from itertools import izip as zip
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
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.
|
||||
"""
|
||||
def __init__(self, leds):
|
||||
self._leds = tuple(LED(led) for led in leds)
|
||||
def __init__(self, *pins):
|
||||
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
|
||||
def leds(self):
|
||||
"""
|
||||
A tuple of all the `LED` objects contained by the instance.
|
||||
"""
|
||||
return self._leds
|
||||
|
||||
def on(self):
|
||||
@@ -66,10 +95,11 @@ class PiLiter(LEDBoard):
|
||||
Ciseco Pi-LITEr: strip of 8 very bright LEDs.
|
||||
"""
|
||||
def __init__(self):
|
||||
leds = (4, 17, 27, 18, 22, 23, 24, 25)
|
||||
super(PiLiter, self).__init__(leds)
|
||||
super(PiLiter, self).__init__(4, 17, 27, 18, 22, 23, 24, 25)
|
||||
|
||||
|
||||
TrafficLightTuple = namedtuple('TrafficLightTuple', ('red', 'amber', 'green'))
|
||||
|
||||
class TrafficLights(LEDBoard):
|
||||
"""
|
||||
Generic Traffic Lights set.
|
||||
@@ -85,12 +115,37 @@ class TrafficLights(LEDBoard):
|
||||
"""
|
||||
def __init__(self, red=None, amber=None, green=None):
|
||||
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)
|
||||
self.amber = LED(amber)
|
||||
self.green = LED(green)
|
||||
self._leds = (self.red, self.amber, self.green)
|
||||
@property
|
||||
def value(self):
|
||||
return TrafficLightTuple(*super(TrafficLights, self).value)
|
||||
|
||||
@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):
|
||||
@@ -99,25 +154,47 @@ class PiTraffic(TrafficLights):
|
||||
and 11.
|
||||
"""
|
||||
def __init__(self):
|
||||
red, amber, green = (9, 10, 11)
|
||||
super(PiTraffic, self).__init__(red, amber, green)
|
||||
super(PiTraffic, self).__init__(9, 10, 11)
|
||||
|
||||
|
||||
FishDishTuple = namedtuple('FishDishTuple', ('red', 'amber', 'green', 'buzzer'))
|
||||
|
||||
class FishDish(TrafficLights):
|
||||
"""
|
||||
Pi Supply FishDish: traffic light LEDs, a button and a buzzer.
|
||||
"""
|
||||
def __init__(self):
|
||||
red, amber, green = (9, 22, 4)
|
||||
super(FishDish, self).__init__(red, amber, green)
|
||||
super(FishDish, self).__init__(9, 22, 4)
|
||||
self.buzzer = Buzzer(8)
|
||||
self.button = Button(pin=7, pull_up=False)
|
||||
self._all = self._leds + (self.buzzer,)
|
||||
self.button = Button(7, pull_up=False)
|
||||
self._all = self.leds + (self.buzzer,)
|
||||
|
||||
@property
|
||||
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
|
||||
|
||||
@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):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
def __init__(self):
|
||||
green, amber, red = (22, 23, 24)
|
||||
super(FishDish, self).__init__(red, amber, green)
|
||||
super(FishDish, self).__init__(22, 23, 24)
|
||||
self.buzzer = Buzzer(5)
|
||||
self.button = Button(25)
|
||||
self._all = self._leds + (self.buzzer,)
|
||||
|
||||
|
||||
class Robot(object):
|
||||
RobotTuple = namedtuple('RobotTuple', ('left', 'right'))
|
||||
|
||||
class Robot(SourceMixin, CompositeDevice):
|
||||
"""
|
||||
Generic dual-motor Robot.
|
||||
"""
|
||||
def __init__(self, left=None, right=None):
|
||||
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
|
||||
right_forward, right_back = right
|
||||
@property
|
||||
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)
|
||||
self._right = Motor(forward=right_forward, back=right_back)
|
||||
|
||||
self._min_pwm = self._left._min_pwm
|
||||
self._max_pwm = self._left._max_pwm
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
self._left.value, self._right.value = value
|
||||
|
||||
def forward(self, speed=1):
|
||||
"""
|
||||
Drive the robot forward.
|
||||
Drive the robot forward by running both motors forward.
|
||||
|
||||
speed: `1`
|
||||
Speed at which to drive the motors, 0 to 1.
|
||||
"""
|
||||
self._left._backward.off()
|
||||
self._right._backward.off()
|
||||
|
||||
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
|
||||
self._left.forward(speed)
|
||||
self._right.forward(speed)
|
||||
|
||||
def backward(self, speed=1):
|
||||
"""
|
||||
Drive the robot backward.
|
||||
Drive the robot backward by running both motors backward.
|
||||
|
||||
speed: `1`
|
||||
Speed at which to drive the motors, 0 to 1.
|
||||
"""
|
||||
self._left._forward.off()
|
||||
self._right._forward.off()
|
||||
|
||||
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
|
||||
self._left.backward(speed)
|
||||
self._right.backward(speed)
|
||||
|
||||
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 at which to drive the motors, 0 to 1.
|
||||
"""
|
||||
self._right._backward.off()
|
||||
self._left._forward.off()
|
||||
|
||||
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
|
||||
self._right.forward(speed)
|
||||
self._left.backward(speed)
|
||||
|
||||
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 at which to drive the motors, 0 to 1.
|
||||
"""
|
||||
self._left._backward.off()
|
||||
self._right._forward.off()
|
||||
|
||||
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
|
||||
self._left.forward(speed)
|
||||
self._right.backward(speed)
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
@@ -313,6 +372,4 @@ class RyanteckRobot(Robot):
|
||||
RTK MCB Robot. Generic robot controller with pre-configured pin numbers.
|
||||
"""
|
||||
def __init__(self):
|
||||
left = (17, 18)
|
||||
right = (22, 23)
|
||||
super(RyanteckRobot, self).__init__(left=left, right=right)
|
||||
super(RyanteckRobot, self).__init__((17, 18), (22, 23))
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
print_function,
|
||||
absolute_import,
|
||||
division,
|
||||
)
|
||||
|
||||
import atexit
|
||||
import weakref
|
||||
from threading import Thread, Event, RLock
|
||||
@@ -30,11 +37,123 @@ GPIO.setwarnings(False)
|
||||
class GPIODeviceError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class GPIODeviceClosed(GPIODeviceError):
|
||||
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.
|
||||
|
||||
@@ -47,9 +166,6 @@ 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
|
||||
@@ -68,9 +184,6 @@ class GPIODevice(object):
|
||||
self._active_state = GPIO.HIGH
|
||||
self._inactive_state = GPIO.LOW
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def _read(self):
|
||||
try:
|
||||
return GPIO.input(self.pin) == self._active_state
|
||||
@@ -132,6 +245,7 @@ class GPIODevice(object):
|
||||
... led.on()
|
||||
...
|
||||
"""
|
||||
super(GPIODevice, self).close()
|
||||
with _GPIO_PINS_LOCK:
|
||||
pin = self._pin
|
||||
self._pin = None
|
||||
@@ -140,12 +254,6 @@ class GPIODevice(object):
|
||||
GPIO.remove_event_detect(pin)
|
||||
GPIO.cleanup(pin)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_tb):
|
||||
self.close()
|
||||
|
||||
@property
|
||||
def pin(self):
|
||||
"""
|
||||
@@ -163,17 +271,6 @@ class GPIODevice(object):
|
||||
|
||||
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):
|
||||
try:
|
||||
return "<gpiozero.%s object on pin=%d, is_active=%s>" % (
|
||||
@@ -238,3 +335,4 @@ class GPIOQueue(GPIOThread):
|
||||
except ReferenceError:
|
||||
# Parent is dead; time to die!
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
from __future__ import division
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
print_function,
|
||||
absolute_import,
|
||||
division,
|
||||
)
|
||||
|
||||
import inspect
|
||||
import warnings
|
||||
@@ -10,7 +15,13 @@ from RPi import GPIO
|
||||
from w1thermsensor import W1ThermSensor
|
||||
from spidev import SpiDev
|
||||
|
||||
from .devices import GPIODeviceError, GPIODeviceClosed, GPIODevice, GPIOQueue
|
||||
from .devices import (
|
||||
GPIODeviceError,
|
||||
GPIODeviceClosed,
|
||||
GPIODevice,
|
||||
CompositeDevice,
|
||||
GPIOQueue,
|
||||
)
|
||||
|
||||
|
||||
class InputDeviceError(GPIODeviceError):
|
||||
@@ -35,13 +46,6 @@ 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(
|
||||
@@ -79,6 +83,10 @@ class InputDevice(GPIODevice):
|
||||
|
||||
@property
|
||||
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
|
||||
|
||||
def __repr__(self):
|
||||
@@ -102,15 +110,6 @@ 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()
|
||||
@@ -121,7 +120,7 @@ class WaitableInputDevice(InputDevice):
|
||||
|
||||
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.
|
||||
|
||||
timeout: `None`
|
||||
@@ -132,7 +131,7 @@ class WaitableInputDevice(InputDevice):
|
||||
|
||||
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.
|
||||
|
||||
timeout: `None`
|
||||
@@ -248,9 +247,6 @@ 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:
|
||||
@@ -301,9 +297,6 @@ 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):
|
||||
@@ -388,6 +381,9 @@ class SmoothedInputDevice(WaitableInputDevice):
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
"""
|
||||
Returns `True` if the device is currently active and `False` otherwise.
|
||||
"""
|
||||
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
|
||||
is `True`).
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self, pin=None, pull_up=True, bouncetime=None):
|
||||
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)
|
||||
to mitigate this.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(
|
||||
self, pin=None, queue_len=1, sample_rate=10, threshold=0.5,
|
||||
partial=False):
|
||||
@@ -458,12 +448,6 @@ 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):
|
||||
@@ -517,17 +501,11 @@ class TemperatureSensor(W1ThermSensor):
|
||||
return self.get_temperature()
|
||||
|
||||
|
||||
class AnalogInputDevice(object):
|
||||
class AnalogInputDevice(CompositeDevice):
|
||||
"""
|
||||
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')
|
||||
@@ -537,25 +515,32 @@ class AnalogInputDevice(object):
|
||||
self._bits = bits
|
||||
self._spi = SpiDev()
|
||||
self._spi.open(0, self.device)
|
||||
super(AnalogInputDevice, self).__init__()
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Shut down the device and release all associated resources.
|
||||
"""
|
||||
if self._spi:
|
||||
s = self._spi
|
||||
self._spi = None
|
||||
s.close()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_tb):
|
||||
self.close()
|
||||
super(AnalogInputDevice, self).close()
|
||||
|
||||
@property
|
||||
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
|
||||
|
||||
@property
|
||||
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
|
||||
|
||||
def _read(self):
|
||||
@@ -563,13 +548,15 @@ class AnalogInputDevice(object):
|
||||
|
||||
@property
|
||||
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)
|
||||
|
||||
|
||||
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')
|
||||
@@ -578,6 +565,10 @@ class MCP3008(AnalogInputDevice):
|
||||
|
||||
@property
|
||||
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
|
||||
|
||||
def _read(self):
|
||||
@@ -602,11 +593,10 @@ 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)
|
||||
if not 0 <= channel < 4:
|
||||
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
|
||||
from time import sleep
|
||||
from threading import Lock
|
||||
@@ -5,14 +12,21 @@ from itertools import repeat
|
||||
|
||||
from RPi import GPIO
|
||||
|
||||
from .devices import GPIODeviceError, GPIODevice, GPIOThread
|
||||
from .devices import (
|
||||
GPIODeviceError,
|
||||
GPIODeviceClosed,
|
||||
GPIODevice,
|
||||
GPIOThread,
|
||||
CompositeDevice,
|
||||
SourceMixin,
|
||||
)
|
||||
|
||||
|
||||
class OutputDeviceError(GPIODeviceError):
|
||||
pass
|
||||
|
||||
|
||||
class OutputDevice(GPIODevice):
|
||||
class OutputDevice(SourceMixin, GPIODevice):
|
||||
"""
|
||||
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
|
||||
always does the opposite).
|
||||
"""
|
||||
|
||||
__slots__ = ('_active_high', '_source', '_source_thread')
|
||||
|
||||
def __init__(self, pin=None, active_high=True):
|
||||
self._active_high = active_high
|
||||
self._source = None
|
||||
self._source_thread = None
|
||||
super(OutputDevice, self).__init__(pin)
|
||||
self._active_state = GPIO.HIGH if active_high else GPIO.LOW
|
||||
self._inactive_state = GPIO.LOW if active_high else GPIO.HIGH
|
||||
@@ -52,10 +61,6 @@ class OutputDevice(GPIODevice):
|
||||
self.close()
|
||||
raise
|
||||
|
||||
def close(self):
|
||||
self.source = None
|
||||
super(OutputDevice, self).close()
|
||||
|
||||
def _write(self, value):
|
||||
GPIO.output(self.pin, bool(value))
|
||||
|
||||
@@ -79,26 +84,6 @@ class OutputDevice(GPIODevice):
|
||||
def value(self, 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
|
||||
def active_high(self):
|
||||
return self._active_high
|
||||
@@ -120,12 +105,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
|
||||
super(DigitalOutputDevice, self).__init__(pin, active_high)
|
||||
self._lock = Lock()
|
||||
|
||||
def close(self):
|
||||
@@ -148,8 +130,8 @@ class DigitalOutputDevice(OutputDevice):
|
||||
|
||||
def toggle(self):
|
||||
"""
|
||||
Reverse the state of the device.
|
||||
If it's on, turn it off; if it's off, turn it on.
|
||||
Reverse the state of the device. If it's on, turn it off; if it's off,
|
||||
turn it on.
|
||||
"""
|
||||
with self._lock:
|
||||
if self.is_active:
|
||||
@@ -161,20 +143,20 @@ class DigitalOutputDevice(OutputDevice):
|
||||
"""
|
||||
Make the device turn on and off repeatedly.
|
||||
|
||||
on_time: 1
|
||||
on_time: `1`
|
||||
Number of seconds on
|
||||
|
||||
off_time: 1
|
||||
off_time: `1`
|
||||
Number of seconds off
|
||||
|
||||
n: None
|
||||
Number of times to blink; None means forever
|
||||
n: `None`
|
||||
Number of times to blink; `None` means forever
|
||||
|
||||
background: True
|
||||
If True, start a background thread to continue blinking and return
|
||||
immediately. If False, only return when the blink is finished
|
||||
(warning: the default value of n will result in this method never
|
||||
returning).
|
||||
background: `True`
|
||||
If `True`, start a background thread to continue blinking and
|
||||
return immediately. If `False`, only return when the blink is
|
||||
finished (warning: the default value of n will result in this
|
||||
method never returning).
|
||||
"""
|
||||
self._stop_blink()
|
||||
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
|
||||
an optional resistor to prevent the LED from burning out.
|
||||
"""
|
||||
__slots__ = ()
|
||||
pass
|
||||
|
||||
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
|
||||
anode (long leg) of the buzzer, and the cathode (short leg) to ground.
|
||||
"""
|
||||
__slots__ = ()
|
||||
pass
|
||||
|
||||
|
||||
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)
|
||||
@@ -278,6 +257,9 @@ class PWMOutputDevice(DigitalOutputDevice):
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
"""
|
||||
Returns `True` if the device is currently active and `False` otherwise.
|
||||
"""
|
||||
return self.value > 0.0
|
||||
|
||||
@property
|
||||
@@ -295,8 +277,17 @@ class PWMOutputDevice(DigitalOutputDevice):
|
||||
|
||||
|
||||
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
|
||||
|
||||
PWMLED.is_lit = PWMLED.is_active
|
||||
|
||||
|
||||
def _led_property(index, doc=None):
|
||||
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.
|
||||
|
||||
@@ -319,10 +310,10 @@ 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):
|
||||
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))
|
||||
|
||||
red = _led_property(0)
|
||||
@@ -330,78 +321,98 @@ class RGBLED(object):
|
||||
blue = _led_property(2)
|
||||
|
||||
@property
|
||||
def color(self):
|
||||
def value(self):
|
||||
"""
|
||||
Set the color of the LED from an RGB 3-tuple of `(red, green, blue)`
|
||||
where each value between 0 and 1.
|
||||
Represents the color of the LED as an RGB 3-tuple of `(red, green,
|
||||
blue)` where each value is 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
|
||||
def color(self, value):
|
||||
@value.setter
|
||||
def value(self, 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):
|
||||
"""
|
||||
Turn the device on. This equivalent to setting the device color to
|
||||
white `(1, 1, 1)`.
|
||||
"""
|
||||
self.color = (1, 1, 1)
|
||||
self.value = (1, 1, 1)
|
||||
|
||||
def off(self):
|
||||
"""
|
||||
Turn the device off. This is equivalent to setting the device color
|
||||
to black `(0, 0, 0)`.
|
||||
"""
|
||||
self.color = (0, 0, 0)
|
||||
self.value = (0, 0, 0)
|
||||
|
||||
def close(self):
|
||||
for led in self._leds:
|
||||
led.close()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_tb):
|
||||
self.close()
|
||||
|
||||
|
||||
class Motor(object):
|
||||
class Motor(SourceMixin, CompositeDevice):
|
||||
"""
|
||||
Generic bi-directional motor.
|
||||
"""
|
||||
|
||||
__slots__ = ('_forward', '_backward')
|
||||
|
||||
def __init__(self, forward=None, back=None):
|
||||
def __init__(self, forward=None, backward=None):
|
||||
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._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):
|
||||
"""
|
||||
Drive the motor forwards
|
||||
"""
|
||||
self._backward.off()
|
||||
self._forward.on()
|
||||
if speed < 1:
|
||||
sleep(0.1) # warm up the motor
|
||||
self._forward.value = speed
|
||||
self._forward.value = speed
|
||||
|
||||
def backward(self, speed=1):
|
||||
"""
|
||||
Drive the motor backwards
|
||||
"""
|
||||
self._forward.off()
|
||||
self._backward.on()
|
||||
if speed < 1:
|
||||
sleep(0.1) # warm up the motor
|
||||
self._backward.value = speed
|
||||
self._backward.value = speed
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
@@ -409,3 +420,4 @@ class Motor(object):
|
||||
"""
|
||||
self._forward.off()
|
||||
self._backward.off()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user