mirror of
https://github.com/KevinMidboe/python-gpiozero.git
synced 2025-10-29 17:50:37 +00:00
Merge branch 'master' of github.com:rpi-distro/python-gpiozero
This commit is contained in:
@@ -48,6 +48,8 @@ so you can still do::
|
|||||||
|
|
||||||
.. autoexception:: GPIOBadQueueLen
|
.. autoexception:: GPIOBadQueueLen
|
||||||
|
|
||||||
|
.. autoexception:: GPIOBadSampleWait
|
||||||
|
|
||||||
.. autoexception:: InputDeviceError
|
.. autoexception:: InputDeviceError
|
||||||
|
|
||||||
.. autoexception:: OutputDeviceError
|
.. autoexception:: OutputDeviceError
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ Button
|
|||||||
:members: wait_for_press, wait_for_release, pin, is_pressed, pull_up, when_pressed, when_released
|
:members: wait_for_press, wait_for_release, pin, is_pressed, pull_up, when_pressed, when_released
|
||||||
|
|
||||||
|
|
||||||
Motion Sensor (PIR)
|
Motion Sensor (D-SUN PIR)
|
||||||
===================
|
=========================
|
||||||
|
|
||||||
.. autoclass:: MotionSensor(pin, queue_len=1, sample_rate=10, threshold=0.5, partial=False)
|
.. autoclass:: MotionSensor(pin, queue_len=1, sample_rate=10, threshold=0.5, partial=False)
|
||||||
:members: wait_for_motion, wait_for_no_motion, pin, motion_detected, when_motion, when_no_motion
|
:members: wait_for_motion, wait_for_no_motion, pin, motion_detected, when_motion, when_no_motion
|
||||||
@@ -33,6 +33,14 @@ Light Sensor (LDR)
|
|||||||
.. autoclass:: LightSensor(pin, queue_len=5, charge_time_limit=0.01, threshold=0.1, partial=False)
|
.. autoclass:: LightSensor(pin, queue_len=5, charge_time_limit=0.01, threshold=0.1, partial=False)
|
||||||
:members: wait_for_light, wait_for_dark, pin, light_detected, when_light, when_dark
|
:members: wait_for_light, wait_for_dark, pin, light_detected, when_light, when_dark
|
||||||
|
|
||||||
|
|
||||||
|
Distance Sensor (HC-SR04)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
.. autoclass:: DistanceSensor(echo, trigger, queue_len=30, max_distance=1, threshold_distance=0.3, partial=False)
|
||||||
|
:members: wait_for_in_range, wait_for_out_of_range, trigger, echo, when_in_range, when_out_of_range, max_distance, distance, threshold_distance
|
||||||
|
|
||||||
|
|
||||||
Analog to Digital Converters (ADC)
|
Analog to Digital Converters (ADC)
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,12 @@ integer number instead, it uses one of the following classes to provide the
|
|||||||
|
|
||||||
2. :class:`gpiozero.pins.rpio.RPIOPin`
|
2. :class:`gpiozero.pins.rpio.RPIOPin`
|
||||||
|
|
||||||
3. :class:`gpiozero.pins.native.NativePin`
|
3. :class:`gpiozero.pins.pigpiod.PiGPIOPin`
|
||||||
|
|
||||||
|
4. :class:`gpiozero.pins.native.NativePin`
|
||||||
|
|
||||||
You can change the default pin implementation by over-writing the
|
You can change the default pin implementation by over-writing the
|
||||||
``DefaultPin`` global in devices like so::
|
``DefaultPin`` global in the ``devices`` module like so::
|
||||||
|
|
||||||
from gpiozero.pins.native import NativePin
|
from gpiozero.pins.native import NativePin
|
||||||
import gpiozero.devices
|
import gpiozero.devices
|
||||||
@@ -35,8 +37,24 @@ You can change the default pin implementation by over-writing the
|
|||||||
# This will now use NativePin instead of RPiGPIOPin
|
# This will now use NativePin instead of RPiGPIOPin
|
||||||
led = LED(16)
|
led = LED(16)
|
||||||
|
|
||||||
In future, this separation should allow the library to utilize pins that are
|
Alternatively, instead of passing an integer to the device constructor, you
|
||||||
part of IO extender chips. For example::
|
can pass a :class:`Pin` object itself::
|
||||||
|
|
||||||
|
from gpiozero.pins.native import NativePin
|
||||||
|
from gpiozero import LED
|
||||||
|
|
||||||
|
led = LED(NativePin(16))
|
||||||
|
|
||||||
|
This is particularly useful with implementations that can take extra parameters
|
||||||
|
such as :class:`PiGPIOPin` which can address pins on remote machines::
|
||||||
|
|
||||||
|
from gpiozero.pins.pigpiod import PiGPIOPin
|
||||||
|
from gpiozero import LED
|
||||||
|
|
||||||
|
led = LED(PiGPIOPin(16, host='my_other_pi'))
|
||||||
|
|
||||||
|
In future, this separation of pins and devices should also permit the library
|
||||||
|
to utilize pins that are part of IO extender chips. For example::
|
||||||
|
|
||||||
from gpiozero import IOExtender, LED
|
from gpiozero import IOExtender, LED
|
||||||
|
|
||||||
@@ -52,13 +70,6 @@ part of IO extender chips. For example::
|
|||||||
comments from testers!
|
comments from testers!
|
||||||
|
|
||||||
|
|
||||||
Abstract Pin
|
|
||||||
============
|
|
||||||
|
|
||||||
.. autoclass:: Pin
|
|
||||||
:members:
|
|
||||||
|
|
||||||
|
|
||||||
RPiGPIOPin
|
RPiGPIOPin
|
||||||
==========
|
==========
|
||||||
|
|
||||||
@@ -75,6 +86,14 @@ RPIOPin
|
|||||||
.. autoclass:: RPIOPin
|
.. autoclass:: RPIOPin
|
||||||
|
|
||||||
|
|
||||||
|
PiGPIOPin
|
||||||
|
=========
|
||||||
|
|
||||||
|
.. currentmodule:: gpiozero.pins.pigpiod
|
||||||
|
|
||||||
|
.. autoclass:: PiGPIOPin
|
||||||
|
|
||||||
|
|
||||||
NativePin
|
NativePin
|
||||||
=========
|
=========
|
||||||
|
|
||||||
@@ -82,3 +101,10 @@ NativePin
|
|||||||
|
|
||||||
.. autoclass:: NativePin
|
.. autoclass:: NativePin
|
||||||
|
|
||||||
|
|
||||||
|
Abstract Pin
|
||||||
|
============
|
||||||
|
|
||||||
|
.. autoclass:: Pin
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ sys.modules['RPi'] = Mock()
|
|||||||
sys.modules['RPi.GPIO'] = sys.modules['RPi'].GPIO
|
sys.modules['RPi.GPIO'] = sys.modules['RPi'].GPIO
|
||||||
sys.modules['RPIO'] = Mock()
|
sys.modules['RPIO'] = Mock()
|
||||||
sys.modules['RPIO.PWM'] = sys.modules['RPIO'].PWM
|
sys.modules['RPIO.PWM'] = sys.modules['RPIO'].PWM
|
||||||
|
sys.modules['pigpio'] = Mock()
|
||||||
sys.modules['w1thermsensor'] = Mock()
|
sys.modules['w1thermsensor'] = Mock()
|
||||||
sys.modules['spidev'] = Mock()
|
sys.modules['spidev'] = Mock()
|
||||||
|
|
||||||
|
|||||||
@@ -443,6 +443,36 @@ level::
|
|||||||
pause()
|
pause()
|
||||||
|
|
||||||
|
|
||||||
|
Distance sensor
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. IMAGE TBD
|
||||||
|
|
||||||
|
Have a :class:`DistanceSensor` detect the distance to the nearest object::
|
||||||
|
|
||||||
|
from gpiozero import DistanceSensor
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
sensor = DistanceSensor(23, 24)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
print('Distance to nearest object is', sensor.distance, 'm')
|
||||||
|
sleep(1)
|
||||||
|
|
||||||
|
Run a function when something gets near the sensor::
|
||||||
|
|
||||||
|
from gpiozero import DistanceSensor, LED
|
||||||
|
from signal import pause
|
||||||
|
|
||||||
|
sensor = DistanceSensor(23, 24, max_distance=1, threshold_distance=0.2)
|
||||||
|
led = LED(16)
|
||||||
|
|
||||||
|
sensor.when_in_range = led.on
|
||||||
|
sensor.when_out_of_range = led.off
|
||||||
|
|
||||||
|
pause()
|
||||||
|
|
||||||
|
|
||||||
Motors
|
Motors
|
||||||
======
|
======
|
||||||
|
|
||||||
@@ -480,6 +510,19 @@ Make a :class:`Robot` drive around in (roughly) a square::
|
|||||||
robot.right()
|
robot.right()
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
|
Make a robot with a distance sensor that runs away when things get within
|
||||||
|
20cm of it::
|
||||||
|
|
||||||
|
from gpiozero import Robot, DistanceSensor
|
||||||
|
from signal import pause
|
||||||
|
|
||||||
|
sensor = DistanceSensor(23, 24, max_distance=1, threshold_distance=0.2)
|
||||||
|
robot = Robot(left=(4, 14), right=(17, 18))
|
||||||
|
|
||||||
|
sensor.when_in_range = robot.backward
|
||||||
|
sensor.when_out_of_range = robot.stop
|
||||||
|
pause()
|
||||||
|
|
||||||
|
|
||||||
Button controlled robot
|
Button controlled robot
|
||||||
=======================
|
=======================
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from .exc import (
|
|||||||
GPIOPinInUse,
|
GPIOPinInUse,
|
||||||
GPIOPinMissing,
|
GPIOPinMissing,
|
||||||
GPIOBadQueueLen,
|
GPIOBadQueueLen,
|
||||||
|
GPIOBadSampleWait,
|
||||||
InputDeviceError,
|
InputDeviceError,
|
||||||
OutputDeviceError,
|
OutputDeviceError,
|
||||||
OutputDeviceBadValue,
|
OutputDeviceBadValue,
|
||||||
@@ -48,6 +49,7 @@ from .input_devices import (
|
|||||||
LineSensor,
|
LineSensor,
|
||||||
MotionSensor,
|
MotionSensor,
|
||||||
LightSensor,
|
LightSensor,
|
||||||
|
DistanceSensor,
|
||||||
AnalogInputDevice,
|
AnalogInputDevice,
|
||||||
MCP3008,
|
MCP3008,
|
||||||
MCP3004,
|
MCP3004,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# vim: set fileencoding=utf-8:
|
||||||
|
|
||||||
from __future__ import (
|
from __future__ import (
|
||||||
unicode_literals,
|
unicode_literals,
|
||||||
absolute_import,
|
absolute_import,
|
||||||
@@ -25,3 +27,27 @@ def isclose(a, b, rel_tol=1e-9, abs_tol=0.0):
|
|||||||
(diff <= abs(rel_tol * a)) or
|
(diff <= abs(rel_tol * a)) or
|
||||||
(diff <= abs_tol)
|
(diff <= abs_tol)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Backported from py3.4
|
||||||
|
def mean(data):
|
||||||
|
if iter(data) is data:
|
||||||
|
data = list(data)
|
||||||
|
n = len(data)
|
||||||
|
if not n:
|
||||||
|
raise ValueError('cannot calculate mean of empty data')
|
||||||
|
return sum(data) / n
|
||||||
|
|
||||||
|
|
||||||
|
# Backported from py3.4
|
||||||
|
def median(data):
|
||||||
|
data = sorted(data)
|
||||||
|
n = len(data)
|
||||||
|
if not n:
|
||||||
|
raise ValueError('cannot calculate median of empty data')
|
||||||
|
elif n % 2:
|
||||||
|
return data[n // 2]
|
||||||
|
else:
|
||||||
|
i = n // 2
|
||||||
|
return (data[n - 1] + data[n]) / 2
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,17 @@ import weakref
|
|||||||
from threading import Thread, Event, RLock
|
from threading import Thread, Event, RLock
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from types import FunctionType
|
from types import FunctionType
|
||||||
|
try:
|
||||||
|
from statistics import median, mean
|
||||||
|
except ImportError:
|
||||||
|
from .compat import median, mean
|
||||||
|
|
||||||
from .exc import (
|
from .exc import (
|
||||||
GPIOPinMissing,
|
GPIOPinMissing,
|
||||||
GPIOPinInUse,
|
GPIOPinInUse,
|
||||||
GPIODeviceClosed,
|
GPIODeviceClosed,
|
||||||
GPIOBadQueueLen,
|
GPIOBadQueueLen,
|
||||||
|
GPIOBadSampleWait,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get a pin implementation to use as the default; we prefer RPi.GPIO's here
|
# Get a pin implementation to use as the default; we prefer RPi.GPIO's here
|
||||||
@@ -32,6 +37,10 @@ except ImportError:
|
|||||||
try:
|
try:
|
||||||
from .pins.rpio import RPIOPin
|
from .pins.rpio import RPIOPin
|
||||||
DefaultPin = RPIOPin
|
DefaultPin = RPIOPin
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
from .pins.pigipod import PiGPIOPin
|
||||||
|
DefaultPin = PiGPIOPin
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from .pins.native import NativePin
|
from .pins.native import NativePin
|
||||||
DefaultPin = NativePin
|
DefaultPin = NativePin
|
||||||
@@ -344,23 +353,29 @@ class GPIOThread(Thread):
|
|||||||
|
|
||||||
|
|
||||||
class GPIOQueue(GPIOThread):
|
class GPIOQueue(GPIOThread):
|
||||||
def __init__(self, parent, queue_len=5, sample_wait=0.0, partial=False):
|
def __init__(
|
||||||
|
self, parent, queue_len=5, sample_wait=0.0, partial=False,
|
||||||
|
average=median):
|
||||||
assert isinstance(parent, GPIODevice)
|
assert isinstance(parent, GPIODevice)
|
||||||
|
assert callable(average)
|
||||||
super(GPIOQueue, self).__init__(target=self.fill)
|
super(GPIOQueue, self).__init__(target=self.fill)
|
||||||
if queue_len < 1:
|
if queue_len < 1:
|
||||||
raise GPIOBadQueueLen('queue_len must be at least one')
|
raise GPIOBadQueueLen('queue_len must be at least one')
|
||||||
|
if sample_wait < 0:
|
||||||
|
raise GPIOBadSampleWait('sample_wait must be 0 or greater')
|
||||||
self.queue = deque(maxlen=queue_len)
|
self.queue = deque(maxlen=queue_len)
|
||||||
self.partial = partial
|
self.partial = partial
|
||||||
self.sample_wait = sample_wait
|
self.sample_wait = sample_wait
|
||||||
self.full = Event()
|
self.full = Event()
|
||||||
self.parent = weakref.proxy(parent)
|
self.parent = weakref.proxy(parent)
|
||||||
|
self.average = average
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
if not self.partial:
|
if not self.partial:
|
||||||
self.full.wait()
|
self.full.wait()
|
||||||
try:
|
try:
|
||||||
return sum(self.queue) / len(self.queue)
|
return self.average(self.queue)
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
# No data == inactive value
|
# No data == inactive value
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ class GPIOPinMissing(GPIODeviceError, ValueError):
|
|||||||
class GPIOBadQueueLen(GPIODeviceError, ValueError):
|
class GPIOBadQueueLen(GPIODeviceError, ValueError):
|
||||||
"Error raised when non-positive queue length is specified"
|
"Error raised when non-positive queue length is specified"
|
||||||
|
|
||||||
|
class GPIOBadSampleWait(GPIODeviceError, ValueError):
|
||||||
|
"Error raised when a negative sampling wait period is specified"
|
||||||
|
|
||||||
class InputDeviceError(GPIODeviceError):
|
class InputDeviceError(GPIODeviceError):
|
||||||
"Base class for errors specific to the InputDevice hierarchy"
|
"Base class for errors specific to the InputDevice hierarchy"
|
||||||
|
|
||||||
|
|||||||
@@ -572,6 +572,194 @@ LightSensor.wait_for_light = LightSensor.wait_for_active
|
|||||||
LightSensor.wait_for_dark = LightSensor.wait_for_inactive
|
LightSensor.wait_for_dark = LightSensor.wait_for_inactive
|
||||||
|
|
||||||
|
|
||||||
|
class DistanceSensor(SmoothedInputDevice):
|
||||||
|
"""
|
||||||
|
Extends :class:`SmoothedInputDevice` and represents an HC-SR04 ultrasonic
|
||||||
|
distance sensor, as found in the `CamJam #3 EduKit`_.
|
||||||
|
|
||||||
|
The distance sensor requires two GPIO pins: one for the *trigger* (marked
|
||||||
|
TRIG on the sensor) and another for the *echo* (marked ECHO on the sensor).
|
||||||
|
However, a voltage divider is required to ensure the 5V from the ECHO pin
|
||||||
|
doesn't damage the Pi. Wire your sensor according to the following
|
||||||
|
instructions:
|
||||||
|
|
||||||
|
1. Connect the GND pin of the sensor to a ground pin on the Pi.
|
||||||
|
|
||||||
|
2. Connect the TRIG pin of the sensor a GPIO pin.
|
||||||
|
|
||||||
|
3. Connect a 330Ω resistor from the ECHO pin of the sensor to a different
|
||||||
|
GPIO pin.
|
||||||
|
|
||||||
|
4. Connect a 470Ω resistor from ground to the ECHO GPIO pin. This forms
|
||||||
|
the required voltage divider.
|
||||||
|
|
||||||
|
5. Finally, connect the VCC pin of the sensor to a 5V pin on the Pi.
|
||||||
|
|
||||||
|
The following code will periodically report the distance measured by the
|
||||||
|
sensor in cm assuming the TRIG pin is connected to GPIO17, and the ECHO
|
||||||
|
pin to GPIO18::
|
||||||
|
|
||||||
|
from gpiozero import DistanceSensor
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
sensor = DistanceSensor(18, 17)
|
||||||
|
while True:
|
||||||
|
print('Distance: ', sensor.distance * 100)
|
||||||
|
sleep(1)
|
||||||
|
|
||||||
|
:param int echo:
|
||||||
|
The GPIO pin which the ECHO pin is attached to. See :doc:`notes` for
|
||||||
|
valid pin numbers.
|
||||||
|
|
||||||
|
:param int trigger:
|
||||||
|
The GPIO pin which the TRIG pin is attached to. See :doc:`notes` for
|
||||||
|
valid pin numbers.
|
||||||
|
|
||||||
|
:param int queue_len:
|
||||||
|
The length of the queue used to store values read from the sensor.
|
||||||
|
This defaults to 30.
|
||||||
|
|
||||||
|
:param float max_distance:
|
||||||
|
The :attr:`value` attribute reports a normalized value between 0 (too
|
||||||
|
close to measure) and 1 (maximum distance). This parameter specifies
|
||||||
|
the maximum distance expected in meters. This defaults to 1.
|
||||||
|
|
||||||
|
:param float threshold_distance:
|
||||||
|
Defaults to 0.3. This is the distance (in meters) that will trigger the
|
||||||
|
``in_range`` and ``out_of_range`` events when crossed.
|
||||||
|
|
||||||
|
:param bool partial:
|
||||||
|
When ``False`` (the default), the object will not return a value for
|
||||||
|
:attr:`~SmoothedInputDevice.is_active` until the internal queue has
|
||||||
|
filled with values. Only set this to ``True`` if you require values
|
||||||
|
immediately after object construction.
|
||||||
|
|
||||||
|
.. _CamJam #3 EduKit: http://camjam.me/?page_id=1035
|
||||||
|
"""
|
||||||
|
def __init__(
|
||||||
|
self, echo=None, trigger=None, queue_len=30, max_distance=1,
|
||||||
|
threshold_distance=0.3, partial=False):
|
||||||
|
if not (max_distance > 0):
|
||||||
|
raise ValueError('invalid maximum distance (must be positive)')
|
||||||
|
self._trigger = None
|
||||||
|
super(DistanceSensor, self).__init__(
|
||||||
|
echo, pull_up=False, threshold=threshold_distance / max_distance,
|
||||||
|
queue_len=queue_len, sample_wait=0.0, partial=partial
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
self.speed_of_sound = 343.26 # m/s
|
||||||
|
self._max_distance = max_distance
|
||||||
|
self._trigger = GPIODevice(trigger)
|
||||||
|
self._echo = Event()
|
||||||
|
self._trigger.pin.function = 'output'
|
||||||
|
self._trigger.pin.state = False
|
||||||
|
self.pin.edges = 'both'
|
||||||
|
self.pin.bounce = None
|
||||||
|
self.pin.when_changed = self._echo.set
|
||||||
|
self._queue.start()
|
||||||
|
except:
|
||||||
|
self.close()
|
||||||
|
raise
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
try:
|
||||||
|
self._trigger.close()
|
||||||
|
except AttributeError:
|
||||||
|
if self._trigger is not None:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
self._trigger = None
|
||||||
|
super(DistanceSensor, self).close()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_distance(self):
|
||||||
|
"""
|
||||||
|
The maximum distance that the sensor will measure in meters. This value
|
||||||
|
is specified in the constructor and is used to provide the scaling
|
||||||
|
for the :attr:`value` attribute. When :attr:`distance` is equal to
|
||||||
|
:attr:`max_distance`, :attr:`value` will be 1.
|
||||||
|
"""
|
||||||
|
return self._max_distance
|
||||||
|
|
||||||
|
@max_distance.setter
|
||||||
|
def max_distance(self, value):
|
||||||
|
if not (value > 0):
|
||||||
|
raise ValueError('invalid maximum distance (must be positive)')
|
||||||
|
t = self.threshold_distance
|
||||||
|
self._max_distance = value
|
||||||
|
self.threshold_distance = t
|
||||||
|
|
||||||
|
@property
|
||||||
|
def threshold_distance(self):
|
||||||
|
"""
|
||||||
|
The distance, measured in meters, that will trigger the
|
||||||
|
:attr:`when_in_range` and :attr:`when_out_of_range` events when
|
||||||
|
crossed. This is simply a meter-scaled variant of the usual
|
||||||
|
:attr:`threshold` attribute.
|
||||||
|
"""
|
||||||
|
return self.threshold * self.max_distance
|
||||||
|
|
||||||
|
@threshold_distance.setter
|
||||||
|
def threshold_distance(self, value):
|
||||||
|
self.threshold = value / self.max_distance
|
||||||
|
|
||||||
|
@property
|
||||||
|
def distance(self):
|
||||||
|
"""
|
||||||
|
Returns the current distance measured by the sensor in meters. Note
|
||||||
|
that this property will have a value between 0 and
|
||||||
|
:attr:`max_distance`.
|
||||||
|
"""
|
||||||
|
return self.value * self._max_distance
|
||||||
|
|
||||||
|
@property
|
||||||
|
def trigger(self):
|
||||||
|
"""
|
||||||
|
Returns the :class:`Pin` that the sensor's trigger is connected to.
|
||||||
|
"""
|
||||||
|
return self._trigger.pin
|
||||||
|
|
||||||
|
@property
|
||||||
|
def echo(self):
|
||||||
|
"""
|
||||||
|
Returns the :class:`Pin` that the sensor's echo is connected to. This
|
||||||
|
is simply an alias for the usual :attr:`pin` attribute.
|
||||||
|
"""
|
||||||
|
return self.pin
|
||||||
|
|
||||||
|
def _read(self):
|
||||||
|
# Make sure the echo event is clear
|
||||||
|
self._echo.clear()
|
||||||
|
# Fire the trigger
|
||||||
|
self._trigger.pin.state = True
|
||||||
|
sleep(0.00001)
|
||||||
|
self._trigger.pin.state = False
|
||||||
|
# Wait up to 1 second for the echo pin to rise
|
||||||
|
if self._echo.wait(1):
|
||||||
|
start = time()
|
||||||
|
self._echo.clear()
|
||||||
|
# Wait up to 40ms for the echo pin to fall (35ms is maximum pulse
|
||||||
|
# time so any longer means something's gone wrong). Calculate
|
||||||
|
# distance as time for echo multiplied by speed of sound divided by
|
||||||
|
# two to compensate for travel to and from the reflector
|
||||||
|
if self._echo.wait(0.04):
|
||||||
|
distance = (time() - start) * self.speed_of_sound / 2.0
|
||||||
|
return min(1.0, distance / self._max_distance)
|
||||||
|
else:
|
||||||
|
# If we only saw one edge it means we missed the echo because
|
||||||
|
# it was too fast; report minimum distance
|
||||||
|
return 0.0
|
||||||
|
else:
|
||||||
|
# The echo pin never rose or fell; something's gone horribly
|
||||||
|
# wrong (XXX raise a warning?)
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
DistanceSensor.when_out_of_range = DistanceSensor.when_activated
|
||||||
|
DistanceSensor.when_in_range = DistanceSensor.when_deactivated
|
||||||
|
DistanceSensor.wait_for_out_of_range = DistanceSensor.wait_for_active
|
||||||
|
DistanceSensor.wait_for_in_range = DistanceSensor.wait_for_inactive
|
||||||
|
|
||||||
|
|
||||||
class AnalogInputDevice(CompositeDevice):
|
class AnalogInputDevice(CompositeDevice):
|
||||||
"""
|
"""
|
||||||
Represents an analog input device connected to SPI (serial interface).
|
Represents an analog input device connected to SPI (serial interface).
|
||||||
|
|||||||
@@ -156,6 +156,13 @@ class NativePin(Pin):
|
|||||||
use any class which requests PWM will raise an exception. This
|
use any class which requests PWM will raise an exception. This
|
||||||
implementation is also experimental; we make no guarantees it will
|
implementation is also experimental; we make no guarantees it will
|
||||||
not eat your Pi for breakfast!
|
not eat your Pi for breakfast!
|
||||||
|
|
||||||
|
You can construct native pin instances manually like so::
|
||||||
|
|
||||||
|
from gpiozero.pins.native import NativePin
|
||||||
|
from gpiozero import LED
|
||||||
|
|
||||||
|
led = LED(NativePin(12))
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_MEM = None
|
_MEM = None
|
||||||
|
|||||||
240
gpiozero/pins/pigpiod.py
Normal file
240
gpiozero/pins/pigpiod.py
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
from __future__ import (
|
||||||
|
unicode_literals,
|
||||||
|
absolute_import,
|
||||||
|
print_function,
|
||||||
|
division,
|
||||||
|
)
|
||||||
|
str = type('')
|
||||||
|
|
||||||
|
import pigpio
|
||||||
|
|
||||||
|
from . import Pin
|
||||||
|
from ..exc import (
|
||||||
|
PinInvalidFunction,
|
||||||
|
PinSetInput,
|
||||||
|
PinFixedPull,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PiGPIOPin(Pin):
|
||||||
|
"""
|
||||||
|
Uses the `pigpio`_ library to interface to the Pi's GPIO pins. The pigpio
|
||||||
|
library relies on a daemon (``pigpiod``) to be running as root to provide
|
||||||
|
access to the GPIO pins, and communicates with this daemon over a network
|
||||||
|
socket.
|
||||||
|
|
||||||
|
While this does mean only the daemon itself should control the pins, the
|
||||||
|
architecture does have several advantages:
|
||||||
|
|
||||||
|
* Pins can be remote controlled from another machine (the other
|
||||||
|
machine doesn't even have to be a Raspberry Pi; it simply needs the
|
||||||
|
`pigpio`_ client library installed on it)
|
||||||
|
* The daemon supports hardware PWM via the DMA controller
|
||||||
|
* Your script itself doesn't require root privileges; it just needs to
|
||||||
|
be able to communicate with the daemon
|
||||||
|
|
||||||
|
You can construct pigpiod pins manually like so::
|
||||||
|
|
||||||
|
from gpiozero.pins.pigpiod import PiGPIOPin
|
||||||
|
from gpiozero import LED
|
||||||
|
|
||||||
|
led = LED(PiGPIOPin(12))
|
||||||
|
|
||||||
|
This is particularly useful for controlling pins on a remote machine. To
|
||||||
|
accomplish this simply specify the host (and optionally port) when
|
||||||
|
constructing the pin::
|
||||||
|
|
||||||
|
from gpiozero.pins.pigpiod import PiGPIOPin
|
||||||
|
from gpiozero import LED
|
||||||
|
from signal import pause
|
||||||
|
|
||||||
|
led = LED(PiGPIOPin(12, host='192.168.0.2'))
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
In some circumstances, especially when playing with PWM, it does appear
|
||||||
|
to be possible to get the daemon into "unusual" states. We would be
|
||||||
|
most interested to hear any bug reports relating to this (it may be a
|
||||||
|
bug in our pin implementation). A workaround for now is simply to
|
||||||
|
restart the ``pigpiod`` daemon.
|
||||||
|
|
||||||
|
.. _pigpio: http://abyz.co.uk/rpi/pigpio/
|
||||||
|
"""
|
||||||
|
|
||||||
|
_CONNECTIONS = {}
|
||||||
|
_PINS = {}
|
||||||
|
|
||||||
|
GPIO_FUNCTIONS = {
|
||||||
|
'input': pigpio.INPUT,
|
||||||
|
'output': pigpio.OUTPUT,
|
||||||
|
'alt0': pigpio.ALT0,
|
||||||
|
'alt1': pigpio.ALT1,
|
||||||
|
'alt2': pigpio.ALT2,
|
||||||
|
'alt3': pigpio.ALT3,
|
||||||
|
'alt4': pigpio.ALT4,
|
||||||
|
'alt5': pigpio.ALT5,
|
||||||
|
}
|
||||||
|
|
||||||
|
GPIO_PULL_UPS = {
|
||||||
|
'up': pigpio.PUD_UP,
|
||||||
|
'down': pigpio.PUD_DOWN,
|
||||||
|
'floating': pigpio.PUD_OFF,
|
||||||
|
}
|
||||||
|
|
||||||
|
GPIO_EDGES = {
|
||||||
|
'both': pigpio.EITHER_EDGE,
|
||||||
|
'rising': pigpio.RISING_EDGE,
|
||||||
|
'falling': pigpio.FALLING_EDGE,
|
||||||
|
}
|
||||||
|
|
||||||
|
GPIO_FUNCTION_NAMES = {v: k for (k, v) in GPIO_FUNCTIONS.items()}
|
||||||
|
GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()}
|
||||||
|
GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()}
|
||||||
|
|
||||||
|
def __new__(cls, number, host='localhost', port=8888):
|
||||||
|
try:
|
||||||
|
return cls._PINS[(host, port, number)]
|
||||||
|
except KeyError:
|
||||||
|
self = super(PiGPIOPin, cls).__new__(cls)
|
||||||
|
cls._PINS[(host, port, number)] = self
|
||||||
|
try:
|
||||||
|
self._connection = cls._CONNECTIONS[(host, port)]
|
||||||
|
except KeyError:
|
||||||
|
self._connection = pigpio.pi(host, port)
|
||||||
|
cls._CONNECTIONS[(host, port)] = self._connection
|
||||||
|
self._host = host
|
||||||
|
self._port = port
|
||||||
|
self._number = number
|
||||||
|
self._pull = 'up' if number in (2, 3) else 'floating'
|
||||||
|
self._pwm = False
|
||||||
|
self._bounce = None
|
||||||
|
self._when_changed = None
|
||||||
|
self._callback = None
|
||||||
|
self._edges = pigpio.EITHER_EDGE
|
||||||
|
self._connection.set_mode(self._number, pigpio.INPUT)
|
||||||
|
self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[self._pull])
|
||||||
|
self._connection.set_glitch_filter(self._number, 0)
|
||||||
|
self._connection.set_PWM_range(self._number, 255)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self._host == 'localhost':
|
||||||
|
return "GPIO%d" % self._number
|
||||||
|
else:
|
||||||
|
return "GPIO%d on %s:%d" % (self._host, self._port)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def host(self):
|
||||||
|
return self._host
|
||||||
|
|
||||||
|
@property
|
||||||
|
def port(self):
|
||||||
|
return self._port
|
||||||
|
|
||||||
|
@property
|
||||||
|
def number(self):
|
||||||
|
return self._number
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
# If we're shutting down, the connection may have disconnected itself
|
||||||
|
# already. Unfortunately, the connection's "connected" property is
|
||||||
|
# rather buggy - disconnecting doesn't set it to False! So we're
|
||||||
|
# naughty and check an internal variable instead...
|
||||||
|
if self._connection.sl.s is not None:
|
||||||
|
self.frequency = None
|
||||||
|
self.when_changed = None
|
||||||
|
self.function = 'input'
|
||||||
|
self.pull = 'floating'
|
||||||
|
|
||||||
|
def _get_function(self):
|
||||||
|
return self.GPIO_FUNCTION_NAMES[self._connection.get_mode(self._number)]
|
||||||
|
|
||||||
|
def _set_function(self, value):
|
||||||
|
if value != 'input':
|
||||||
|
self._pull = 'floating'
|
||||||
|
try:
|
||||||
|
self._connection.set_mode(self._number, self.GPIO_FUNCTIONS[value])
|
||||||
|
except KeyError:
|
||||||
|
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
|
||||||
|
|
||||||
|
def _get_state(self):
|
||||||
|
if self._pwm:
|
||||||
|
return self._connection.get_PWM_dutycycle(self._number) / 255
|
||||||
|
else:
|
||||||
|
return bool(self._connection.read(self._number))
|
||||||
|
|
||||||
|
def _set_state(self, value):
|
||||||
|
if self._pwm:
|
||||||
|
try:
|
||||||
|
self._connection.set_PWM_dutycycle(self._number, int(value * 255))
|
||||||
|
except pigpio.error:
|
||||||
|
raise PinInvalidValue('invalid state "%s" for pin %r' % (value, self))
|
||||||
|
elif self.function == 'input':
|
||||||
|
raise PinSetInput('cannot set state of pin %r' % self)
|
||||||
|
else:
|
||||||
|
# write forces pin to OUTPUT, hence the check above
|
||||||
|
self._connection.write(self._number, bool(value))
|
||||||
|
|
||||||
|
def _get_pull(self):
|
||||||
|
return self._pull
|
||||||
|
|
||||||
|
def _set_pull(self, value):
|
||||||
|
if self.function != 'input':
|
||||||
|
raise PinFixedPull('cannot set pull on non-input pin %r' % self)
|
||||||
|
if value != 'up' and self._number in (2, 3):
|
||||||
|
raise PinFixedPull('%r has a physical pull-up resistor' % self)
|
||||||
|
try:
|
||||||
|
self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[value])
|
||||||
|
self._pull = value
|
||||||
|
except KeyError:
|
||||||
|
raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self))
|
||||||
|
|
||||||
|
def _get_frequency(self):
|
||||||
|
if self._pwm:
|
||||||
|
return self._connection.get_PWM_frequency(self._number)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _set_frequency(self, value):
|
||||||
|
if not self._pwm and value is not None:
|
||||||
|
self._connection.set_PWM_frequency(self._number, value)
|
||||||
|
self._connection.set_PWM_dutycycle(self._number, 0)
|
||||||
|
self._pwm = True
|
||||||
|
elif self._pwm and value is not None:
|
||||||
|
self._connection.set_PWM_frequency(self._number, value)
|
||||||
|
elif self._pwm and value is None:
|
||||||
|
self._connection.set_PWM_dutycycle(self._number, 0)
|
||||||
|
self._pwm = False
|
||||||
|
|
||||||
|
def _get_bounce(self):
|
||||||
|
return None if not self._bounce else self._bounce / 1000000
|
||||||
|
|
||||||
|
def _set_bounce(self, value):
|
||||||
|
if value is None:
|
||||||
|
value = 0
|
||||||
|
self._connection.set_glitch_filter(self._number, int(value * 1000000))
|
||||||
|
|
||||||
|
def _get_edges(self):
|
||||||
|
return self.GPIO_EDGES_NAMES[self._edges]
|
||||||
|
|
||||||
|
def _set_edges(self, value):
|
||||||
|
f = self.when_changed
|
||||||
|
self.when_changed = None
|
||||||
|
try:
|
||||||
|
self._edges = self.GPIO_EDGES[value]
|
||||||
|
finally:
|
||||||
|
self.when_changed = f
|
||||||
|
|
||||||
|
def _get_when_changed(self):
|
||||||
|
if self._callback is None:
|
||||||
|
return None
|
||||||
|
return self._callback.callb.func
|
||||||
|
|
||||||
|
def _set_when_changed(self, value):
|
||||||
|
if self._callback is not None:
|
||||||
|
self._callback.cancel()
|
||||||
|
self._callback = None
|
||||||
|
if value is not None:
|
||||||
|
self._callback = self._connection.callback(
|
||||||
|
self._number, self._edges,
|
||||||
|
lambda gpio, level, tick: value())
|
||||||
|
|
||||||
@@ -22,6 +22,20 @@ class RPiGPIOPin(Pin):
|
|||||||
the default pin implementation if the RPi.GPIO library is installed.
|
the default pin implementation if the RPi.GPIO library is installed.
|
||||||
Supports all features including PWM (via software).
|
Supports all features including PWM (via software).
|
||||||
|
|
||||||
|
Because this is the default pin implementation you can use it simply by
|
||||||
|
specifying an integer number for the pin in most operations, e.g.::
|
||||||
|
|
||||||
|
from gpiozero import LED
|
||||||
|
|
||||||
|
led = LED(12)
|
||||||
|
|
||||||
|
However, you can also construct RPi.GPIO pins manually if you wish::
|
||||||
|
|
||||||
|
from gpiozero.pins.rpigpio import RPiGPIOPin
|
||||||
|
from gpiozero import LED
|
||||||
|
|
||||||
|
led = LED(RPiGPIOPin(12))
|
||||||
|
|
||||||
.. _RPi.GPIO: https://pypi.python.org/pypi/RPi.GPIO
|
.. _RPi.GPIO: https://pypi.python.org/pypi/RPi.GPIO
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,13 @@ class RPIOPin(Pin):
|
|||||||
Pi 1's; the Raspberry Pi 2 Model B is *not* supported. Also note that
|
Pi 1's; the Raspberry Pi 2 Model B is *not* supported. Also note that
|
||||||
root access is required so scripts must typically be run with ``sudo``.
|
root access is required so scripts must typically be run with ``sudo``.
|
||||||
|
|
||||||
|
You can construct RPIO pins manually like so::
|
||||||
|
|
||||||
|
from gpiozero.pins.rpio import RPIOPin
|
||||||
|
from gpiozero import LED
|
||||||
|
|
||||||
|
led = LED(RPIOPin(12))
|
||||||
|
|
||||||
.. _RPIO: https://pythonhosted.org/RPIO/
|
.. _RPIO: https://pythonhosted.org/RPIO/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user