mirror of
https://github.com/KevinMidboe/python-gpiozero.git
synced 2025-12-08 20:39:01 +00:00
@@ -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