Merge branch 'master' of github.com:RPi-Distro/python-gpiozero

Damn, forgot to push the debian/changelog changes after the last release.
This commit is contained in:
Dave Jones
2016-08-30 23:57:26 +01:00
103 changed files with 3738 additions and 1053 deletions

3
.gitignore vendored
View File

@@ -27,3 +27,6 @@ coverage
.coverage
.tox
.cache
# Generated documentation
docs/_build

View File

@@ -1,44 +0,0 @@
# Contributing
This module was designed for use in education; particularly for young children.
It is intended to provide a simple interface to everyday components.
If a proposed change added an advanced feature but made basic usage more
complex, it is unlikely to be added.
## Suggestions
Please make suggestions for additional components or enhancements to the
codebase by opening an
[issue](https://github.com/RPi-Distro/python-gpiozero/issues) explaining your
reasoning clearly.
## Bugs
Please submit bug reports by opening an
[issue](https://github.com/RPi-Distro/python-gpiozero/issues) explaining the
problem clearly using code examples.
## Documentation
The documentation source lives in the
[docs](https://github.com/RPi-Distro/python-gpiozero/tree/master/docs) folder.
Contributions to the documentation are welcome but should be easy to read and
understand.
## Commit messages and pull requests
Commit messages should be concise but descriptive, and in the form of a patch
description, i.e. instructional not past tense ("Add LED example" not "Added
LED example"). Commits that close (or intend to close) an issue should use the
phrase "fix #123" where `#123` is the issue number.
## Backwards compatibility
Since this library reached v1.0 we aim to maintain backwards-compatability
thereafter. Changes which break backwards-compatability will not be accepted.
## Python
- Python 2/3 compatibility
- PEP8-compliance (with exceptions)

View File

@@ -1 +1,2 @@
include README.rst
recursive-include tests *.py

View File

@@ -2,13 +2,19 @@
gpiozero
========
.. image:: https://badge.fury.io/py/gpiozero.svg
:target: https://badge.fury.io/py/gpiozero
:alt: Latest Version
.. ifconfig:: html_theme == 'sphinx_rtd_theme'
.. image:: https://travis-ci.org/RPi-Distro/python-gpiozero.svg?branch=master
:target: https://travis-ci.org/RPi-Distro/python-gpiozero
:alt: Build Tests
.. image:: https://badge.fury.io/py/gpiozero.svg
:target: https://badge.fury.io/py/gpiozero
:alt: Latest Version
.. image:: https://travis-ci.org/RPi-Distro/python-gpiozero.svg?branch=master
:target: https://travis-ci.org/RPi-Distro/python-gpiozero
:alt: Build Tests
.. image:: https://img.shields.io/codecov/c/github/RPi-Distro/python-gpiozero/master.svg?maxAge=2592000
:target: https://codecov.io/github/RPi-Distro/python-gpiozero
:alt: Code Coverage
A simple interface to everyday GPIO components used with Raspberry Pi.
@@ -69,7 +75,7 @@ or::
Documentation
=============
Comprehensive documentation is available at https://gpiozero.readthedocs.org/.
Comprehensive documentation is available at https://gpiozero.readthedocs.io/.
Development
===========
@@ -95,8 +101,8 @@ Contributors
.. _Raspberry Pi Foundation: https://www.raspberrypi.org/
.. _GitHub: https://github.com/RPi-Distro/python-gpiozero
.. _issues: https://github.com/RPi-Distro/python-gpiozero/issues
.. _recipes: http://gpiozero.readthedocs.org/en/latest/recipes.html
.. _Contribute: CONTRIBUTING.md
.. _recipes: http://gpiozero.readthedocs.io/en/latest/recipes.html
.. _contribute: http://gpiozero.readthedocs.io/en/latest/contributing.html
.. _Ben Nuttall: https://github.com/bennuttall
.. _Dave Jones: https://github.com/waveform80
.. _Martin O'Hanlon: https://github.com/martinohanlon

10
debian/control vendored
View File

@@ -11,8 +11,9 @@ X-Python3-Version: >= 3.2
Package: python-gpiozero
Architecture: all
Section: python
Depends: ${misc:Depends}, ${python:Depends}, python-rpi.gpio
Suggests: python-spidev, python-gpiozero-docs
Depends: ${misc:Depends}, ${python:Depends}
Recommends: python-rpi.gpio, python-spidev
Suggests: python-gpiozero-docs
Description: Simple API for controlling devices attached to the GPIO pins.
gpiozero builds on RPi.GPIO to provide a set of classes designed to simplify
interaction with devices connected to the GPIO pins, from simple buttons and
@@ -24,8 +25,9 @@ Description: Simple API for controlling devices attached to the GPIO pins.
Package: python3-gpiozero
Architecture: all
Section: python
Depends: ${misc:Depends}, ${python3:Depends}, python3-rpi.gpio
Suggests: python3-spidev, python-gpiozero-docs
Depends: ${misc:Depends}, ${python3:Depends}
Recommends: python3-rpi.gpio, python3-spidev
Suggests: python-gpiozero-docs
Description: Simple API for controlling devices attached to the GPIO pins.
gpiozero builds on RPi.GPIO to provide a set of classes designed to simplify
interaction with devices connected to the GPIO pins, from simple buttons and

View File

@@ -29,6 +29,13 @@ LEDBarGraph
:inherited-members:
:members:
ButtonBoard
===========
.. autoclass:: ButtonBoard(\*pins, pull_up=True, bounce_time=None, hold_time=1, hold_repeat=False, \*\*named_pins)
:inherited-members:
:members:
TrafficLights
=============
@@ -36,6 +43,13 @@ TrafficLights
:inherited-members:
:members:
LedBorg
=======
.. autoclass:: LedBorg
:inherited-members:
:members:
PiLITEr
=======
@@ -106,6 +120,13 @@ Energenie
:inherited-members:
:members:
SnowPi
======
.. autoclass:: SnowPi
:inherited-members:
:members:
Base Classes
============

View File

@@ -17,7 +17,7 @@ Button
======
.. autoclass:: Button(pin, pull_up=True, bounce_time=None)
: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, is_held, hold_time, held_time, hold_repeat, pull_up, when_pressed, when_released, when_held
Line Sensor (TRCT5000)

View File

@@ -25,6 +25,11 @@ PingServer
.. autoclass:: PingServer
CPUTemperature
==============
.. autoclass:: CPUTemperature
Base Classes
============

View File

@@ -23,13 +23,13 @@ PWMLED
======
.. autoclass:: PWMLED(pin, active_high=True, initial_value=0, frequency=100)
:members: on, off, toggle, blink, pin, is_lit, value
:members: on, off, toggle, blink, pulse, pin, is_lit, value
RGBLED
======
.. autoclass:: RGBLED(red, green, blue, active_high=True, initial_value=(0, 0, 0))
:members: on, off, toggle, blink, red, green, blue, is_lit, color
.. autoclass:: RGBLED(red, green, blue, active_high=True, initial_value=(0, 0, 0), pwm=True)
:members: on, off, toggle, blink, pulse, red, green, blue, is_lit, color
Buzzer
======
@@ -40,9 +40,23 @@ Buzzer
Motor
=====
.. autoclass:: Motor(forward, backward)
.. autoclass:: Motor(forward, backward, pwm=True)
:members: forward, backward, stop
Servo
=====
.. autoclass:: Servo(pin, initial_value=0, min_pulse_width=1/1000, max_pulse_width=2/1000, frame_width=20/1000)
:inherited-members:
:members:
AngularServo
============
.. autoclass:: AngularServo(pin, initial_angle=0, min_angle=-90, max_angle=90, min_pulse_width=1/1000, max_pulse_width=2/1000, frame_width=20/1000)
:inherited-members:
:members:
Base Classes
============

View File

@@ -25,35 +25,39 @@ integer number instead, it uses one of the following classes to provide the
4. :class:`gpiozero.pins.native.NativePin`
You can change the default pin implementation by over-writing the
``DefaultPin`` global in the ``devices`` module like so::
``pin_factory`` global in the ``devices`` module like so::
from gpiozero.pins.native import NativePin
import gpiozero.devices
# Force the default pin implementation to be NativePin
gpiozero.devices.DefaultPin = NativePin
gpiozero.devices.pin_factory = NativePin
from gpiozero import LED
# This will now use NativePin instead of RPiGPIOPin
led = LED(16)
Alternatively, instead of passing an integer to the device constructor, you
can pass a :class:`Pin` object itself::
``pin_factory`` is a concrete descendent of the abstract :class:`Pin` class.
The descendent may take additional parameters in its constructor provided they
are optional; GPIO Zero will expect to be able to construct instances with
nothing more than an integer pin number.
However, the descendent may take default information from additional sources.
For example, to default to creating pins with
:class:`gpiozero.pins.pigpiod.PiGPIOPin` on a remote pi called ``remote-pi``
you can set the :envvar:`PIGPIO_ADDR` environment variable when running your
script::
$ PIGPIO_ADDR=remote-pi python my_script.py
It is worth noting that instead of passing an integer to device constructors,
you can pass an object derived from :class:`Pin` 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:`~gpiozero.pins.pigpiod.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::
@@ -110,6 +114,13 @@ Abstract Pin
:members:
Local Pin
=========
.. autoclass:: LocalPin
:members:
Utilities
=========

View File

@@ -10,7 +10,7 @@ the :attr:`~gpiozero.SourceMixin.source` and
library. These utility routines are in the ``tools`` module of GPIO Zero and
are typically imported as follows::
from gpiozero.tools import scaled, negated, conjunction
from gpiozero.tools import scaled, negated, all_values
Given that :attr:`~gpiozero.SourceMixin.source` and
:attr:`~gpiozero.ValuesMixin.values` deal with infinite iterators, another
@@ -29,6 +29,8 @@ Single source conversions
.. autofunction:: absoluted
.. autofunction:: booleanized
.. autofunction:: clamped
.. autofunction:: inverted
@@ -37,12 +39,18 @@ Single source conversions
.. autofunction:: post_delayed
.. autofunction:: post_periodic_filtered
.. autofunction:: pre_delayed
.. autofunction:: pre_periodic_filtered
.. autofunction:: quantized
.. autofunction:: queued
.. autofunction:: smoothed
.. autofunction:: scaled
Combining sources
@@ -54,8 +62,12 @@ Combining sources
.. autofunction:: averaged
Artifical sources
=================
.. autofunction:: multiplied
.. autofunction:: summed
Artificial sources
==================
.. autofunction:: cos_values

View File

@@ -46,7 +46,12 @@ sys.modules['spidev'] = Mock()
# -- General configuration ------------------------------------------------
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx']
extensions = [
'sphinx.ext.autodoc', # support for automethod, autoclass, etc.
'sphinx.ext.viewcode', # support for "Source" links in output
'sphinx.ext.intersphinx', # support links to Python library docs etc.
'sphinx.ext.ifconfig', # support for ifconfig conditional includes
]
templates_path = ['_templates']
source_suffix = '.rst'
#source_encoding = 'utf-8-sig'
@@ -74,6 +79,7 @@ autodoc_member_order = 'groupwise'
intersphinx_mapping = {
'python': ('http://docs.python.org/3.4', None),
'picamera': ('http://picamera.readthedocs.io/en/latest', None),
}
# -- Options for HTML output ----------------------------------------------

52
docs/contributing.rst Normal file
View File

@@ -0,0 +1,52 @@
.. _contributing:
============
Contributing
============
This module was designed for use in education; particularly for young children.
It is intended to provide a simple interface to everyday components.
If a proposed change added an advanced feature but made basic usage more
complex, it is unlikely to be added.
Suggestions
===========
Please make suggestions for additional components or enhancements to the
codebase by opening an `issue`_ explaining your reasoning clearly.
Bugs
====
Please submit bug reports by opening an `issue`_ explaining the problem clearly
using code examples.
Documentation
=============
The documentation source lives in the `docs`_ folder. Contributions to the
documentation are welcome but should be easy to read and understand.
Commit messages and pull requests
=================================
Commit messages should be concise but descriptive, and in the form of a patch
description, i.e. instructional not past tense ("Add LED example" not "Added
LED example"). Commits that close (or intend to close) an issue should use the
phrase "fix #123" where ``#123`` is the issue number.
Backwards compatibility
=======================
Since this library reached v1.0 we aim to maintain backwards-compatibility
thereafter. Changes which break backwards-compatibility will not be accepted.
Python
======
* Python 2/3 compatibility
* PEP8-compliance (with exceptions)
.. _docs: https://github.com/RPi-Distro/python-gpiozero/tree/master/docs
.. _issue: https://github.com/RPi-Distro/python-gpiozero/issues

View File

@@ -0,0 +1,9 @@
from gpiozero import FishDish
from signal import pause
fish = FishDish()
fish.button.when_pressed = fish.on
fish.button.when_released = fish.off
pause()

View File

@@ -0,0 +1,9 @@
from gpiozero import TrafficHat
from signal import pause
th = TrafficHat()
th.button.when_pressed = th.on
th.button.when_released = th.off
pause()

23
docs/examples/all_on_3.py Normal file
View File

@@ -0,0 +1,23 @@
from gpiozero import LED, Buzzer, Button
from signal import pause
button = Button(2)
buzzer = Buzzer(3)
red = LED(4)
amber = LED(5)
green = LED(6)
things = [red, amber, green, buzzer]
def things_on():
for thing in things:
thing.on()
def things_off():
for thing in things:
thing.off()
button.when_pressed = things_on
button.when_released = things_off
pause()

View File

@@ -0,0 +1,9 @@
from gpiozero import Button
button = Button(2)
while True:
if button.is_pressed:
print("Button is pressed")
else:
print("Button is not pressed")

View File

@@ -0,0 +1,6 @@
from gpiozero import Button
button = Button(2)
button.wait_for_press()
print("Button was pressed")

11
docs/examples/button_3.py Normal file
View File

@@ -0,0 +1,11 @@
from gpiozero import Button
from signal import pause
def say_hello():
print("Hello!")
button = Button(2)
button.when_pressed = say_hello
pause()

15
docs/examples/button_4.py Normal file
View File

@@ -0,0 +1,15 @@
from gpiozero import Button
from signal import pause
def say_hello():
print("Hello!")
def say_goodbye():
print("Goodbye!")
button = Button(2)
button.when_pressed = say_hello
button.when_released = say_goodbye
pause()

View File

@@ -0,0 +1,15 @@
from gpiozero import Button
from picamera import PiCamera
from datetime import datetime
from signal import pause
button = Button(2)
camera = PiCamera()
def capture():
datetime = datetime.now().isoformat()
camera.capture('/home/pi/%s.jpg' % datetime)
button.when_pressed = capture
pause()

View File

@@ -0,0 +1,18 @@
from gpiozero import Button
from picamera import PiCamera
from datetime import datetime
from signal import pause
left_button = Button(2)
right_button = Button(3)
camera = PiCamera()
def capture():
datetime = datetime.now().isoformat()
camera.capture('/home/pi/%s.jpg' % datetime)
left_button.when_pressed = camera.start_preview
left_button.when_released = camera.stop_preview
right_button.when_pressed = capture
pause()

View File

@@ -0,0 +1,10 @@
from gpiozero import LED, Button
from signal import pause
led = LED(17)
button = Button(2)
button.when_pressed = led.on
button.when_released = led.off
pause()

View File

@@ -0,0 +1,9 @@
from gpiozero import LED, Button
from signal import pause
led = LED(17)
button = Button(2)
led.source = button.values
pause()

View File

@@ -0,0 +1,11 @@
from gpiozero import Button
from subprocess import check_call
from signal import pause
def shutdown():
check_call(['sudo', 'poweroff'])
shutdown_btn = Button(17, hold_time=2)
shutdown_btn.when_held = shutdown
pause()

View File

@@ -0,0 +1,12 @@
from gpiozero import Button
from picamera import PiCamera
button = Button(2)
camera = PiCamera()
camera.start_preview()
frame = 1
while True:
button.wait_for_press()
camera.capture('/home/pi/frame%03d.jpg' % frame)
frame += 1

View File

@@ -0,0 +1,8 @@
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)

View File

@@ -0,0 +1,10 @@
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()

10
docs/examples/led_1.py Normal file
View File

@@ -0,0 +1,10 @@
from gpiozero import LED
from time import sleep
red = LED(17)
while True:
red.on()
sleep(1)
red.off()
sleep(1)

8
docs/examples/led_2.py Normal file
View File

@@ -0,0 +1,8 @@
from gpiozero import LED
from signal import pause
red = LED(17)
red.blink()
pause()

View File

@@ -0,0 +1,15 @@
from gpiozero import LEDBarGraph
from time import sleep
graph = LEDBarGraph(5, 6, 13, 19, 26, 20)
graph.value = 1 # (1, 1, 1, 1, 1, 1)
sleep(1)
graph.value = 1/2 # (1, 1, 1, 0, 0, 0)
sleep(1)
graph.value = -1/2 # (0, 0, 0, 1, 1, 1)
sleep(1)
graph.value = 1/4 # (1, 0, 0, 0, 0, 0)
sleep(1)
graph.value = -1 # (1, 1, 1, 1, 1, 1)
sleep(1)

View File

@@ -0,0 +1,15 @@
from gpiozero import LEDBarGraph
from time import sleep
graph = LEDBarGraph(5, 6, 13, 19, 26, pwm=True)
graph.value = 1/10 # (0.5, 0, 0, 0, 0)
sleep(1)
graph.value = 3/10 # (1, 0.5, 0, 0, 0)
sleep(1)
graph.value = -3/10 # (0, 0, 0, 0.5, 1)
sleep(1)
graph.value = 9/10 # (1, 1, 1, 1, 0.5)
sleep(1)
graph.value = 95/100 # (1, 1, 1, 1, 0.75)
sleep(1)

View File

@@ -0,0 +1,15 @@
from gpiozero import LEDBoard
from time import sleep
from signal import pause
leds = LEDBoard(5, 6, 13, 19, 26)
leds.on()
sleep(1)
leds.off()
sleep(1)
leds.value = (1, 0, 1, 0, 1)
sleep(1)
leds.blink()
pause()

View File

@@ -0,0 +1,5 @@
from gpiozero import LEDBoard
leds = LEDBoard(5, 6, 13, 19, 26, pwm=True)
leds.value = (0.2, 0.4, 0.6, 0.8, 1.0)

View File

@@ -0,0 +1,9 @@
from gpiozero import LED
from signal import pause
power = LED(35) # /sys/class/leds/led1
activity = LED(47) # /sys/class/leds/led0
activity.blink()
power.blink()
pause()

View File

@@ -0,0 +1,8 @@
from gpiozero import PWMLED
from signal import pause
led = PWMLED(17)
led.pulse()
pause()

View File

@@ -0,0 +1,19 @@
from travispy import TravisPy
from gpiozero import LED
from gpiozero.tools import negated
from time import sleep
from signal import pause
def build_passed(repo='RPi-Distro/python-gpiozero', delay=3600):
t = TravisPy()
r = t.repo(repo)
while True:
yield r.last_build_state == 'passed'
sleep(delay) # Sleep an hour before hitting travis again
red = LED(12)
green = LED(16)
red.source = negated(green.values)
green.source = build_passed()
pause()

View File

@@ -0,0 +1,12 @@
from gpiozero import PWMLED
from time import sleep
led = PWMLED(17)
while True:
led.value = 0 # off
sleep(1)
led.value = 0.5 # half brightness
sleep(1)
led.value = 1 # full brightness
sleep(1)

View File

@@ -0,0 +1,9 @@
from gpiozero import LightSensor
sensor = LightSensor(18)
while True:
sensor.wait_for_light()
print("It's light! :)")
sensor.wait_for_dark()
print("It's dark :(")

View File

@@ -0,0 +1,10 @@
from gpiozero import LightSensor, LED
from signal import pause
sensor = LightSensor(18)
led = LED(16)
sensor.when_dark = led.on
sensor.when_light = led.off
pause()

View File

@@ -0,0 +1,9 @@
from gpiozero import LightSensor, PWMLED
from signal import pause
sensor = LightSensor(18)
led = PWMLED(16)
led.source = sensor.values
pause()

View File

@@ -0,0 +1,10 @@
from gpiozero import MotionSensor, LED
from signal import pause
pir = MotionSensor(4)
led = LED(16)
pir.when_motion = led.on
pir.when_no_motion = led.off
pause()

10
docs/examples/motor.py Normal file
View File

@@ -0,0 +1,10 @@
from gpiozero import Motor
from time import sleep
motor = Motor(forward=4, backward=14)
while True:
motor.forward()
sleep(5)
motor.backward()
sleep(5)

View File

@@ -0,0 +1,18 @@
from gpiozero import Button
import pygame.mixer
from pygame.mixer import Sound
from signal import pause
pygame.mixer.init()
sound_pins = {
2: Sound("samples/drum_tom_mid_hard.wav"),
3: Sound("samples/drum_cymbal_open.wav"),
}
buttons = [Button(pin) for pin in sound_pins]
for button in buttons:
sound = sound_pins[button.pin.number]
button.when_pressed = sound.play
pause()

6
docs/examples/pot_1.py Normal file
View File

@@ -0,0 +1,6 @@
from gpiozero import MCP3008
pot = MCP3008(channel=0)
while True:
print(pot.value)

7
docs/examples/pot_2.py Normal file
View File

@@ -0,0 +1,7 @@
from gpiozero import LEDBarGraph, MCP3008
from signal import pause
graph = LEDBarGraph(5, 6, 13, 19, 26, pwm=True)
pot = MCP3008(channel=0)
graph.source = pot.values
pause()

View File

@@ -0,0 +1,22 @@
from gpiozero import Button, LED
from time import sleep
import random
led = LED(17)
player_1 = Button(2)
player_2 = Button(3)
time = random.uniform(5, 10)
sleep(time)
led.on()
while True:
if player_1.is_pressed:
print("Player 1 wins!")
break
if player_2.is_pressed:
print("Player 2 wins!")
break
led.off()

28
docs/examples/rgbled.py Normal file
View File

@@ -0,0 +1,28 @@
from gpiozero import RGBLED
from time import sleep
led = RGBLED(red=9, green=10, blue=11)
led.red = 1 # full red
sleep(1)
led.red = 0.5 # half red
sleep(1)
led.color = (0, 1, 0) # full green
sleep(1)
led.color = (1, 0, 1) # magenta
sleep(1)
led.color = (1, 1, 0) # yellow
sleep(1)
led.color = (0, 1, 1) # cyan
sleep(1)
led.color = (1, 1, 1) # white
sleep(1)
led.color = (0, 0, 0) # off
sleep(1)
# slowly increase intensity of blue
for n in range(100):
led.blue = n/100
sleep(0.1)

View File

@@ -0,0 +1,11 @@
from gpiozero import RGBLED, MCP3008
led = RGBLED(red=2, green=3, blue=4)
red_pot = MCP3008(channel=0)
green_pot = MCP3008(channel=1)
blue_pot = MCP3008(channel=2)
while True:
led.red = red_pot.value
led.green = green_pot.value
led.blue = blue_pot.value

View File

@@ -0,0 +1,11 @@
from gpiozero import RGBLED, MCP3008
from signal import pause
led = RGBLED(2, 3, 4)
red_pot = MCP3008(0)
green_pot = MCP3008(1)
blue_pot = MCP3008(2)
led.source = zip(red_pot.values, green_pot.values, blue_pot.values)
pause()

10
docs/examples/robot_1.py Normal file
View File

@@ -0,0 +1,10 @@
from gpiozero import Robot
from time import sleep
robot = Robot(left=(4, 14), right=(17, 18))
for i in range(4):
robot.forward()
sleep(10)
robot.right()
sleep(1)

9
docs/examples/robot_2.py Normal file
View File

@@ -0,0 +1,9 @@
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()

View File

@@ -0,0 +1,23 @@
from gpiozero import Robot, Button
from signal import pause
robot = Robot(left=(4, 14), right=(17, 18))
left = Button(26)
right = Button(16)
fw = Button(21)
bw = Button(20)
fw.when_pressed = robot.forward
fw.when_released = robot.stop
left.when_pressed = robot.left
left.when_released = robot.stop
right.when_pressed = robot.right
right.when_released = robot.stop
bw.when_pressed = robot.backward
bw.when_released = robot.stop
pause()

View File

@@ -0,0 +1,34 @@
import curses
from gpiozero import Robot
robot = Robot(left=(4, 14), right=(17, 18))
actions = {
curses.KEY_UP: robot.forward,
curses.KEY_DOWN: robot.backward,
curses.KEY_LEFT: robot.left,
curses.KEY_RIGHT: robot.right,
}
def main(window):
next_key = None
while True:
curses.halfdelay(1)
if next_key is None:
key = window.getch()
else:
key = next_key
next_key = None
if key != -1:
# KEY DOWN
curses.halfdelay(3)
action = actions.get(key)
if action is not None:
action()
next_key = key
while next_key == key:
next_key = window.getch()
# KEY UP
robot.stop()
curses.wrapper(main)

View File

@@ -0,0 +1,36 @@
from gpiozero import Robot
from evdev import InputDevice, list_devices, ecodes
robot = Robot(left=(4, 14), right=(17, 18))
# Get the list of available input devices
devices = [InputDevice(device) for device in list_devices()]
# Filter out everything that's not a keyboard. Keyboards are defined as any
# device which has keys, and which specifically has keys 1..31 (roughly Esc,
# the numeric keys, the first row of QWERTY plus a few more) and which does
# *not* have key 0 (reserved)
must_have = {i for i in range(1, 32)}
must_not_have = {0}
devices = [
dev
for dev in devices
for keys in (set(dev.capabilities().get(ecodes.EV_KEY, [])),)
if must_have.issubset(keys)
and must_not_have.isdisjoint(keys)
]
# Pick the first keyboard
keyboard = devices[0]
keypress_actions = {
ecodes.KEY_UP: robot.forward,
ecodes.KEY_DOWN: robot.backward,
ecodes.KEY_LEFT: robot.left,
ecodes.KEY_RIGHT: robot.right,
}
for event in keyboard.read_loop():
if event.type == ecodes.EV_KEY and event.code in keypress_actions:
if event.value == 1: # key down
keypress_actions[event.code]()
if event.value == 0: # key up
robot.stop()

View File

@@ -0,0 +1,10 @@
from gpiozero import Robot, MotionSensor
from signal import pause
robot = Robot(left=(4, 14), right=(17, 18))
pir = MotionSensor(5)
pir.when_motion = robot.forward
pir.when_no_motion = robot.stop
pause()

View File

@@ -0,0 +1,9 @@
from gpiozero import Robot, MotionSensor
from signal import pause
robot = Robot(left=(4, 14), right=(17, 18))
pir = MotionSensor(5)
robot.source = zip(pir.values, pir.values)
pause()

View File

@@ -0,0 +1,12 @@
from gpiozero import MCP3008
from time import sleep
def convert_temp(gen):
for value in gen:
yield (value * 3.3 - 0.5) * 100
adc = MCP3008(channel=0)
for temp in convert_temp(adc.values):
print('The temperature is', temp, 'C')
sleep(1)

View File

@@ -0,0 +1,20 @@
from gpiozero import TrafficLights
from time import sleep
lights = TrafficLights(2, 3, 4)
lights.green.on()
while True:
sleep(10)
lights.green.off()
lights.amber.on()
sleep(1)
lights.amber.off()
lights.red.on()
sleep(10)
lights.amber.on()
sleep(1)
lights.green.on()
lights.amber.off()
lights.red.off()

View File

@@ -0,0 +1,20 @@
from gpiozero import TrafficLights
from time import sleep
from signal import pause
lights = TrafficLights(2, 3, 4)
def traffic_light_sequence():
while True:
yield (0, 0, 1) # green
sleep(10)
yield (0, 1, 0) # amber
sleep(1)
yield (1, 0, 0) # red
sleep(10)
yield (1, 1, 0) # red+amber
sleep(1)
lights.source = traffic_light_sequence()
pause()

View File

@@ -0,0 +1,24 @@
from gpiozero import LED
from time import sleep
red = LED(2)
amber = LED(3)
green = LED(4)
green.on()
amber.off()
red.off()
while True:
sleep(10)
green.off()
amber.on()
sleep(1)
amber.off()
red.on()
sleep(10)
amber.on()
sleep(1)
green.on()
amber.off()
red.off()

View File

@@ -5,15 +5,20 @@ digraph classes {
node [shape=rect, style=filled, color="#298029", fontname=Sans, fontcolor="#ffffff", fontsize=10];
edge [arrowhead=onormal, style=dashed];
RGBLED->LED;
RGBLED->PWMLED;
LEDBoard->LED;
LEDBoard->PWMLED;
LEDBarGraph->LED;
LEDBarGraph->PWMLED;
ButtonBoard->Button;
TrafficLightsBuzzer->TrafficLights;
TrafficLightsBuzzer->Buzzer;
TrafficLightsBuzzer->Button;
Robot->Motor;
Motor->DigitalOutputDevice;
Motor->PWMOutputDevice;
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -1,113 +1,148 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.36.0 (20140111.2315)
<!-- Generated by graphviz version 2.38.0 (20140413.2041)
-->
<!-- Title: classes Pages: 1 -->
<svg width="538pt" height="116pt"
viewBox="0.00 0.00 538.00 116.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 112)">
<svg width="672pt" height="188pt"
viewBox="0.00 0.00 672.00 188.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 184)">
<title>classes</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-112 534,-112 534,4 -4,4"/>
<polygon fill="white" stroke="none" points="-4,4 -4,-184 668,-184 668,4 -4,4"/>
<!-- RGBLED -->
<g id="node1" class="node"><title>RGBLED</title>
<polygon fill="#298029" stroke="#298029" points="55.5,-108 0.5,-108 0.5,-72 55.5,-72 55.5,-108"/>
<text text-anchor="middle" x="28" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">RGBLED</text>
</g>
<!-- PWMLED -->
<g id="node2" class="node"><title>PWMLED</title>
<polygon fill="#298029" stroke="#298029" points="143.5,-36 86.5,-36 86.5,-0 143.5,-0 143.5,-36"/>
<text text-anchor="middle" x="115" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMLED</text>
</g>
<!-- RGBLED&#45;&gt;PWMLED -->
<g id="edge1" class="edge"><title>RGBLED&#45;&gt;PWMLED</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M49.5056,-71.6966C60.4625,-62.8807 73.9459,-52.0321 85.8249,-42.4742"/>
<polygon fill="none" stroke="black" points="88.1446,-45.1001 93.7418,-36.1043 83.7565,-39.6462 88.1446,-45.1001"/>
</g>
<!-- LEDBoard -->
<g id="node3" class="node"><title>LEDBoard</title>
<polygon fill="#298029" stroke="#298029" points="238,-108 174,-108 174,-72 238,-72 238,-108"/>
<text text-anchor="middle" x="206" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">LEDBoard</text>
</g>
<!-- LEDBoard&#45;&gt;PWMLED -->
<g id="edge3" class="edge"><title>LEDBoard&#45;&gt;PWMLED</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M183.506,-71.6966C172.045,-62.8807 157.942,-52.0321 145.516,-42.4742"/>
<polygon fill="none" stroke="black" points="147.296,-39.4273 137.236,-36.1043 143.028,-44.9757 147.296,-39.4273"/>
<polygon fill="#298029" stroke="#298029" points="56,-180 0,-180 0,-144 56,-144 56,-180"/>
<text text-anchor="middle" x="28" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">RGBLED</text>
</g>
<!-- LED -->
<g id="node4" class="node"><title>LED</title>
<polygon fill="#298029" stroke="#298029" points="224,-36 170,-36 170,-0 224,-0 224,-36"/>
<text text-anchor="middle" x="197" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">LED</text>
<g id="node2" class="node"><title>LED</title>
<polygon fill="#298029" stroke="#298029" points="96,-108 42,-108 42,-72 96,-72 96,-108"/>
<text text-anchor="middle" x="69" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">LED</text>
</g>
<!-- RGBLED&#45;&gt;LED -->
<g id="edge1" class="edge"><title>RGBLED&#45;&gt;LED</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M38.1348,-143.697C42.8516,-135.644 48.5618,-125.894 53.7817,-116.982"/>
<polygon fill="none" stroke="black" points="56.9478,-118.502 58.9817,-108.104 50.9076,-114.964 56.9478,-118.502"/>
</g>
<!-- PWMLED -->
<g id="node3" class="node"><title>PWMLED</title>
<polygon fill="#298029" stroke="#298029" points="172,-108 114,-108 114,-72 172,-72 172,-108"/>
<text text-anchor="middle" x="143" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMLED</text>
</g>
<!-- RGBLED&#45;&gt;PWMLED -->
<g id="edge2" class="edge"><title>RGBLED&#45;&gt;PWMLED</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M56.1322,-143.876C71.1684,-134.724 89.8671,-123.342 106.06,-113.485"/>
<polygon fill="none" stroke="black" points="108.038,-116.379 114.76,-108.19 104.398,-110.4 108.038,-116.379"/>
</g>
<!-- LEDBoard -->
<g id="node4" class="node"><title>LEDBoard</title>
<polygon fill="#298029" stroke="#298029" points="138,-180 74,-180 74,-144 138,-144 138,-180"/>
<text text-anchor="middle" x="106" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">LEDBoard</text>
</g>
<!-- LEDBoard&#45;&gt;LED -->
<g id="edge2" class="edge"><title>LEDBoard&#45;&gt;LED</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M203.775,-71.6966C202.783,-63.9827 201.592,-54.7125 200.486,-46.1124"/>
<polygon fill="none" stroke="black" points="203.946,-45.5763 199.199,-36.1043 197.003,-46.469 203.946,-45.5763"/>
<g id="edge3" class="edge"><title>LEDBoard&#45;&gt;LED</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M96.8539,-143.697C92.5974,-135.644 87.4442,-125.894 82.7336,-116.982"/>
<polygon fill="none" stroke="black" points="85.8083,-115.31 78.0409,-108.104 79.6197,-118.581 85.8083,-115.31"/>
</g>
<!-- LEDBoard&#45;&gt;PWMLED -->
<g id="edge4" class="edge"><title>LEDBoard&#45;&gt;PWMLED</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M115.146,-143.697C119.403,-135.644 124.556,-125.894 129.266,-116.982"/>
<polygon fill="none" stroke="black" points="132.38,-118.581 133.959,-108.104 126.192,-115.31 132.38,-118.581"/>
</g>
<!-- LEDBarGraph -->
<g id="node5" class="node"><title>LEDBarGraph</title>
<polygon fill="#298029" stroke="#298029" points="155.25,-108 74.75,-108 74.75,-72 155.25,-72 155.25,-108"/>
<text text-anchor="middle" x="115" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">LEDBarGraph</text>
</g>
<!-- LEDBarGraph&#45;&gt;PWMLED -->
<g id="edge5" class="edge"><title>LEDBarGraph&#45;&gt;PWMLED</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M115,-71.6966C115,-63.9827 115,-54.7125 115,-46.1124"/>
<polygon fill="none" stroke="black" points="118.5,-46.1043 115,-36.1043 111.5,-46.1044 118.5,-46.1043"/>
<polygon fill="#298029" stroke="#298029" points="237.5,-180 156.5,-180 156.5,-144 237.5,-144 237.5,-180"/>
<text text-anchor="middle" x="197" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">LEDBarGraph</text>
</g>
<!-- LEDBarGraph&#45;&gt;LED -->
<g id="edge4" class="edge"><title>LEDBarGraph&#45;&gt;LED</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M135.27,-71.6966C145.498,-62.9655 158.061,-52.2405 169.178,-42.7503"/>
<polygon fill="none" stroke="black" points="171.63,-45.259 176.963,-36.1043 167.085,-39.935 171.63,-45.259"/>
<g id="edge5" class="edge"><title>LEDBarGraph&#45;&gt;LED</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M165.688,-143.876C147.37,-133.858 124.168,-121.17 105.085,-110.734"/>
<polygon fill="none" stroke="black" points="106.54,-107.54 96.0868,-105.813 103.181,-113.682 106.54,-107.54"/>
</g>
<!-- TrafficLightsBuzzer -->
<g id="node6" class="node"><title>TrafficLightsBuzzer</title>
<polygon fill="#298029" stroke="#298029" points="411.25,-108 306.75,-108 306.75,-72 411.25,-72 411.25,-108"/>
<text text-anchor="middle" x="359" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficLightsBuzzer</text>
<!-- LEDBarGraph&#45;&gt;PWMLED -->
<g id="edge6" class="edge"><title>LEDBarGraph&#45;&gt;PWMLED</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M183.652,-143.697C177.243,-135.389 169.442,-125.277 162.394,-116.141"/>
<polygon fill="none" stroke="black" points="165.074,-113.884 156.195,-108.104 159.532,-118.16 165.074,-113.884"/>
</g>
<!-- TrafficLights -->
<g id="node7" class="node"><title>TrafficLights</title>
<polygon fill="#298029" stroke="#298029" points="314,-36 242,-36 242,-0 314,-0 314,-36"/>
<text text-anchor="middle" x="278" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficLights</text>
</g>
<!-- TrafficLightsBuzzer&#45;&gt;TrafficLights -->
<g id="edge6" class="edge"><title>TrafficLightsBuzzer&#45;&gt;TrafficLights</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M338.978,-71.6966C328.874,-62.9655 316.464,-52.2405 305.482,-42.7503"/>
<polygon fill="none" stroke="black" points="307.647,-39.9948 297.792,-36.1043 303.07,-45.2912 307.647,-39.9948"/>
</g>
<!-- Buzzer -->
<g id="node8" class="node"><title>Buzzer</title>
<polygon fill="#298029" stroke="#298029" points="386,-36 332,-36 332,-0 386,-0 386,-36"/>
<text text-anchor="middle" x="359" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">Buzzer</text>
</g>
<!-- TrafficLightsBuzzer&#45;&gt;Buzzer -->
<g id="edge7" class="edge"><title>TrafficLightsBuzzer&#45;&gt;Buzzer</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M359,-71.6966C359,-63.9827 359,-54.7125 359,-46.1124"/>
<polygon fill="none" stroke="black" points="362.5,-46.1043 359,-36.1043 355.5,-46.1044 362.5,-46.1043"/>
<!-- ButtonBoard -->
<g id="node6" class="node"><title>ButtonBoard</title>
<polygon fill="#298029" stroke="#298029" points="332.5,-180 255.5,-180 255.5,-144 332.5,-144 332.5,-180"/>
<text text-anchor="middle" x="294" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">ButtonBoard</text>
</g>
<!-- Button -->
<g id="node9" class="node"><title>Button</title>
<polygon fill="#298029" stroke="#298029" points="458,-36 404,-36 404,-0 458,-0 458,-36"/>
<text text-anchor="middle" x="431" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">Button</text>
<g id="node7" class="node"><title>Button</title>
<polygon fill="#298029" stroke="#298029" points="328,-108 274,-108 274,-72 328,-72 328,-108"/>
<text text-anchor="middle" x="301" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">Button</text>
</g>
<!-- ButtonBoard&#45;&gt;Button -->
<g id="edge7" class="edge"><title>ButtonBoard&#45;&gt;Button</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M295.73,-143.697C296.502,-135.983 297.429,-126.712 298.289,-118.112"/>
<polygon fill="none" stroke="black" points="301.777,-118.403 299.29,-108.104 294.812,-117.706 301.777,-118.403"/>
</g>
<!-- TrafficLightsBuzzer -->
<g id="node8" class="node"><title>TrafficLightsBuzzer</title>
<polygon fill="#298029" stroke="#298029" points="455.5,-180 350.5,-180 350.5,-144 455.5,-144 455.5,-180"/>
<text text-anchor="middle" x="403" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficLightsBuzzer</text>
</g>
<!-- TrafficLightsBuzzer&#45;&gt;Button -->
<g id="edge8" class="edge"><title>TrafficLightsBuzzer&#45;&gt;Button</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M376.798,-71.6966C385.604,-63.135 396.382,-52.6562 406.001,-43.3045"/>
<polygon fill="none" stroke="black" points="408.677,-45.5846 413.407,-36.1043 403.797,-40.5657 408.677,-45.5846"/>
<g id="edge10" class="edge"><title>TrafficLightsBuzzer&#45;&gt;Button</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M378.048,-143.876C364.958,-134.893 348.74,-123.763 334.564,-114.034"/>
<polygon fill="none" stroke="black" points="336.274,-110.962 326.048,-108.19 332.313,-116.734 336.274,-110.962"/>
</g>
<!-- TrafficLights -->
<g id="node9" class="node"><title>TrafficLights</title>
<polygon fill="#298029" stroke="#298029" points="432,-108 360,-108 360,-72 432,-72 432,-108"/>
<text text-anchor="middle" x="396" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">TrafficLights</text>
</g>
<!-- TrafficLightsBuzzer&#45;&gt;TrafficLights -->
<g id="edge8" class="edge"><title>TrafficLightsBuzzer&#45;&gt;TrafficLights</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M401.27,-143.697C400.498,-135.983 399.571,-126.712 398.711,-118.112"/>
<polygon fill="none" stroke="black" points="402.188,-117.706 397.71,-108.104 395.223,-118.403 402.188,-117.706"/>
</g>
<!-- Buzzer -->
<g id="node10" class="node"><title>Buzzer</title>
<polygon fill="#298029" stroke="#298029" points="504,-108 450,-108 450,-72 504,-72 504,-108"/>
<text text-anchor="middle" x="477" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">Buzzer</text>
</g>
<!-- TrafficLightsBuzzer&#45;&gt;Buzzer -->
<g id="edge9" class="edge"><title>TrafficLightsBuzzer&#45;&gt;Buzzer</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M421.292,-143.697C430.433,-135.05 441.64,-124.449 451.6,-115.027"/>
<polygon fill="none" stroke="black" points="454.059,-117.519 458.918,-108.104 449.248,-112.434 454.059,-117.519"/>
</g>
<!-- Robot -->
<g id="node10" class="node"><title>Robot</title>
<polygon fill="#298029" stroke="#298029" points="530,-108 476,-108 476,-72 530,-72 530,-108"/>
<text text-anchor="middle" x="503" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">Robot</text>
<g id="node11" class="node"><title>Robot</title>
<polygon fill="#298029" stroke="#298029" points="576,-180 522,-180 522,-144 576,-144 576,-180"/>
<text text-anchor="middle" x="549" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">Robot</text>
</g>
<!-- Motor -->
<g id="node11" class="node"><title>Motor</title>
<polygon fill="#298029" stroke="#298029" points="530,-36 476,-36 476,-0 530,-0 530,-36"/>
<text text-anchor="middle" x="503" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">Motor</text>
<g id="node12" class="node"><title>Motor</title>
<polygon fill="#298029" stroke="#298029" points="576,-108 522,-108 522,-72 576,-72 576,-108"/>
<text text-anchor="middle" x="549" y="-87.5" font-family="Sans" font-size="10.00" fill="#ffffff">Motor</text>
</g>
<!-- Robot&#45;&gt;Motor -->
<g id="edge9" class="edge"><title>Robot&#45;&gt;Motor</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M503,-71.6966C503,-63.9827 503,-54.7125 503,-46.1124"/>
<polygon fill="none" stroke="black" points="506.5,-46.1043 503,-36.1043 499.5,-46.1044 506.5,-46.1043"/>
<g id="edge11" class="edge"><title>Robot&#45;&gt;Motor</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M549,-143.697C549,-135.983 549,-126.712 549,-118.112"/>
<polygon fill="none" stroke="black" points="552.5,-118.104 549,-108.104 545.5,-118.104 552.5,-118.104"/>
</g>
<!-- DigitalOutputDevice -->
<g id="node13" class="node"><title>DigitalOutputDevice</title>
<polygon fill="#298029" stroke="#298029" points="542,-36 430,-36 430,-0 542,-0 542,-36"/>
<text text-anchor="middle" x="486" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">DigitalOutputDevice</text>
</g>
<!-- Motor&#45;&gt;DigitalOutputDevice -->
<g id="edge12" class="edge"><title>Motor&#45;&gt;DigitalOutputDevice</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M533.427,-71.6966C525.798,-63.2198 516.477,-52.8636 508.124,-43.5826"/>
<polygon fill="none" stroke="black" points="510.685,-41.1959 501.394,-36.1043 505.482,-45.8787 510.685,-41.1959"/>
</g>
<!-- PWMOutputDevice -->
<g id="node14" class="node"><title>PWMOutputDevice</title>
<polygon fill="#298029" stroke="#298029" points="664,-36 560,-36 560,-0 664,-0 664,-36"/>
<text text-anchor="middle" x="612" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">PWMOutputDevice</text>
</g>
<!-- Motor&#45;&gt;PWMOutputDevice -->
<g id="edge13" class="edge"><title>Motor&#45;&gt;PWMOutputDevice</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M564.573,-71.6966C572.202,-63.2198 581.523,-52.8636 589.876,-43.5826"/>
<polygon fill="none" stroke="black" points="592.518,-45.8787 596.606,-36.1043 587.315,-41.1959 592.518,-45.8787"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -32,5 +32,8 @@ digraph classes {
RyanteckRobot->Robot;
CamJamKitRobot->Robot;
Motor->CompositeDevice;
Servo->CompositeDevice;
AngularServo->Servo;
Energenie->Device;
ButtonBoard->CompositeDevice;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -4,25 +4,25 @@
<!-- Generated by graphviz version 2.38.0 (20140413.2041)
-->
<!-- Title: classes Pages: 1 -->
<svg width="587pt" height="476pt"
viewBox="0.00 0.00 587.00 476.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg width="733pt" height="476pt"
viewBox="0.00 0.00 732.50 476.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 472)">
<title>classes</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-472 583,-472 583,4 -4,4"/>
<polygon fill="white" stroke="none" points="-4,4 -4,-472 728.5,-472 728.5,4 -4,4"/>
<!-- Device -->
<g id="node1" class="node"><title>Device</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="499,-468 445,-468 445,-432 499,-432 499,-468"/>
<text text-anchor="middle" x="472" y="-447.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="607,-468 553,-468 553,-432 607,-432 607,-468"/>
<text text-anchor="middle" x="580" y="-447.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
</g>
<!-- CompositeDevice -->
<g id="node2" class="node"><title>CompositeDevice</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="472.5,-396 371.5,-396 371.5,-360 472.5,-360 472.5,-396"/>
<text text-anchor="middle" x="422" y="-375.5" font-family="Sans" font-size="10.00" fill="#000000">CompositeDevice</text>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="580.5,-396 479.5,-396 479.5,-360 580.5,-360 580.5,-396"/>
<text text-anchor="middle" x="530" y="-375.5" font-family="Sans" font-size="10.00" fill="#000000">CompositeDevice</text>
</g>
<!-- CompositeDevice&#45;&gt;Device -->
<g id="edge1" class="edge"><title>CompositeDevice&#45;&gt;Device</title>
<path fill="none" stroke="black" d="M434.36,-396.303C440.233,-404.526 447.369,-414.517 453.842,-423.579"/>
<polygon fill="black" stroke="black" points="451.122,-425.793 459.783,-431.896 456.818,-421.724 451.122,-425.793"/>
<path fill="none" stroke="black" d="M542.36,-396.303C548.233,-404.526 555.369,-414.517 561.842,-423.579"/>
<polygon fill="black" stroke="black" points="559.122,-425.793 567.783,-431.896 564.818,-421.724 559.122,-425.793"/>
</g>
<!-- CompositeOutputDevice -->
<g id="node3" class="node"><title>CompositeOutputDevice</title>
@@ -31,8 +31,8 @@
</g>
<!-- CompositeOutputDevice&#45;&gt;CompositeDevice -->
<g id="edge2" class="edge"><title>CompositeOutputDevice&#45;&gt;CompositeDevice</title>
<path fill="none" stroke="black" d="M335.888,-324.124C350.793,-333.276 369.329,-344.658 385.382,-354.515"/>
<polygon fill="black" stroke="black" points="383.652,-357.56 394.005,-359.81 387.315,-351.595 383.652,-357.56"/>
<path fill="none" stroke="black" d="M362.023,-324.034C394.498,-334.274 435.899,-347.328 469.472,-357.915"/>
<polygon fill="black" stroke="black" points="468.58,-361.303 479.17,-360.972 470.685,-354.627 468.58,-361.303"/>
</g>
<!-- LEDCollection -->
<g id="node4" class="node"><title>LEDCollection</title>
@@ -136,13 +136,13 @@
</g>
<!-- Robot -->
<g id="node14" class="node"><title>Robot</title>
<polygon fill="#2980b9" stroke="#2980b9" points="449,-324 395,-324 395,-288 449,-288 449,-324"/>
<text text-anchor="middle" x="422" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">Robot</text>
<polygon fill="#2980b9" stroke="#2980b9" points="485,-324 431,-324 431,-288 485,-288 485,-324"/>
<text text-anchor="middle" x="458" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">Robot</text>
</g>
<!-- Robot&#45;&gt;CompositeDevice -->
<g id="edge13" class="edge"><title>Robot&#45;&gt;CompositeDevice</title>
<path fill="none" stroke="black" d="M422,-324.303C422,-332.017 422,-341.288 422,-349.888"/>
<polygon fill="black" stroke="black" points="418.5,-349.896 422,-359.896 425.5,-349.896 418.5,-349.896"/>
<path fill="none" stroke="black" d="M475.798,-324.303C484.604,-332.865 495.382,-343.344 505.001,-352.696"/>
<polygon fill="black" stroke="black" points="502.797,-355.434 512.407,-359.896 507.677,-350.415 502.797,-355.434"/>
</g>
<!-- RyanteckRobot -->
<g id="node15" class="node"><title>RyanteckRobot</title>
@@ -151,8 +151,8 @@
</g>
<!-- RyanteckRobot&#45;&gt;Robot -->
<g id="edge14" class="edge"><title>RyanteckRobot&#45;&gt;Robot</title>
<path fill="none" stroke="black" d="M422,-252.303C422,-260.017 422,-269.288 422,-277.888"/>
<polygon fill="black" stroke="black" points="418.5,-277.896 422,-287.896 425.5,-277.896 418.5,-277.896"/>
<path fill="none" stroke="black" d="M430.899,-252.303C434.997,-260.272 439.949,-269.9 444.493,-278.736"/>
<polygon fill="black" stroke="black" points="441.517,-280.604 449.203,-287.896 447.742,-277.402 441.517,-280.604"/>
</g>
<!-- CamJamKitRobot -->
<g id="node16" class="node"><title>CamJamKitRobot</title>
@@ -161,28 +161,58 @@
</g>
<!-- CamJamKitRobot&#45;&gt;Robot -->
<g id="edge15" class="edge"><title>CamJamKitRobot&#45;&gt;Robot</title>
<path fill="none" stroke="black" d="M504.336,-252.124C490.216,-261.192 472.689,-272.448 457.439,-282.241"/>
<polygon fill="black" stroke="black" points="455.29,-279.462 448.767,-287.81 459.073,-285.352 455.29,-279.462"/>
<path fill="none" stroke="black" d="M512.955,-252.303C503.938,-260.95 492.882,-271.551 483.057,-280.973"/>
<polygon fill="black" stroke="black" points="480.633,-278.448 475.837,-287.896 485.478,-283.501 480.633,-278.448"/>
</g>
<!-- Motor -->
<g id="node17" class="node"><title>Motor</title>
<polygon fill="#2980b9" stroke="#2980b9" points="521,-324 467,-324 467,-288 521,-288 521,-324"/>
<text text-anchor="middle" x="494" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">Motor</text>
<polygon fill="#2980b9" stroke="#2980b9" points="557,-324 503,-324 503,-288 557,-288 557,-324"/>
<text text-anchor="middle" x="530" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">Motor</text>
</g>
<!-- Motor&#45;&gt;CompositeDevice -->
<g id="edge16" class="edge"><title>Motor&#45;&gt;CompositeDevice</title>
<path fill="none" stroke="black" d="M476.202,-324.303C467.396,-332.865 456.618,-343.344 446.999,-352.696"/>
<polygon fill="black" stroke="black" points="444.323,-350.415 439.593,-359.896 449.203,-355.434 444.323,-350.415"/>
<path fill="none" stroke="black" d="M530,-324.303C530,-332.017 530,-341.288 530,-349.888"/>
<polygon fill="black" stroke="black" points="526.5,-349.896 530,-359.896 533.5,-349.896 526.5,-349.896"/>
</g>
<!-- Servo -->
<g id="node18" class="node"><title>Servo</title>
<polygon fill="#2980b9" stroke="#2980b9" points="629,-324 575,-324 575,-288 629,-288 629,-324"/>
<text text-anchor="middle" x="602" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">Servo</text>
</g>
<!-- Servo&#45;&gt;CompositeDevice -->
<g id="edge17" class="edge"><title>Servo&#45;&gt;CompositeDevice</title>
<path fill="none" stroke="black" d="M584.202,-324.303C575.396,-332.865 564.618,-343.344 554.999,-352.696"/>
<polygon fill="black" stroke="black" points="552.323,-350.415 547.593,-359.896 557.203,-355.434 552.323,-350.415"/>
</g>
<!-- AngularServo -->
<g id="node19" class="node"><title>AngularServo</title>
<polygon fill="#2980b9" stroke="#2980b9" points="678.5,-252 597.5,-252 597.5,-216 678.5,-216 678.5,-252"/>
<text text-anchor="middle" x="638" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">AngularServo</text>
</g>
<!-- AngularServo&#45;&gt;Servo -->
<g id="edge18" class="edge"><title>AngularServo&#45;&gt;Servo</title>
<path fill="none" stroke="black" d="M629.101,-252.303C625.003,-260.272 620.051,-269.9 615.507,-278.736"/>
<polygon fill="black" stroke="black" points="612.258,-277.402 610.797,-287.896 618.483,-280.604 612.258,-277.402"/>
</g>
<!-- Energenie -->
<g id="node18" class="node"><title>Energenie</title>
<polygon fill="#2980b9" stroke="#2980b9" points="555.5,-396 490.5,-396 490.5,-360 555.5,-360 555.5,-396"/>
<text text-anchor="middle" x="523" y="-375.5" font-family="Sans" font-size="10.00" fill="#ffffff">Energenie</text>
<g id="node20" class="node"><title>Energenie</title>
<polygon fill="#2980b9" stroke="#2980b9" points="663.5,-396 598.5,-396 598.5,-360 663.5,-360 663.5,-396"/>
<text text-anchor="middle" x="631" y="-375.5" font-family="Sans" font-size="10.00" fill="#ffffff">Energenie</text>
</g>
<!-- Energenie&#45;&gt;Device -->
<g id="edge17" class="edge"><title>Energenie&#45;&gt;Device</title>
<path fill="none" stroke="black" d="M510.393,-396.303C504.403,-404.526 497.124,-414.517 490.521,-423.579"/>
<polygon fill="black" stroke="black" points="487.522,-421.752 484.462,-431.896 493.179,-425.874 487.522,-421.752"/>
<g id="edge19" class="edge"><title>Energenie&#45;&gt;Device</title>
<path fill="none" stroke="black" d="M618.393,-396.303C612.403,-404.526 605.124,-414.517 598.521,-423.579"/>
<polygon fill="black" stroke="black" points="595.522,-421.752 592.462,-431.896 601.179,-425.874 595.522,-421.752"/>
</g>
<!-- ButtonBoard -->
<g id="node21" class="node"><title>ButtonBoard</title>
<polygon fill="#2980b9" stroke="#2980b9" points="724.5,-324 647.5,-324 647.5,-288 724.5,-288 724.5,-324"/>
<text text-anchor="middle" x="686" y="-303.5" font-family="Sans" font-size="10.00" fill="#ffffff">ButtonBoard</text>
</g>
<!-- ButtonBoard&#45;&gt;CompositeDevice -->
<g id="edge20" class="edge"><title>ButtonBoard&#45;&gt;CompositeDevice</title>
<path fill="none" stroke="black" d="M647.838,-324.124C626.607,-333.651 599.992,-345.593 577.431,-355.717"/>
<polygon fill="black" stroke="black" points="575.729,-352.644 568.038,-359.932 578.594,-359.031 575.729,-352.644"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -21,5 +21,6 @@ digraph classes {
PWMOutputDevice->OutputDevice;
PWMLED->PWMOutputDevice;
RGBLED->Device;
LedBorg->RGBLED;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -11,18 +11,18 @@
<polygon fill="white" stroke="none" points="-4,4 -4,-328 255,-328 255,4 -4,4"/>
<!-- Device -->
<g id="node1" class="node"><title>Device</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="204,-324 150,-324 150,-288 204,-288 204,-324"/>
<text text-anchor="middle" x="177" y="-303.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="206,-324 152,-324 152,-288 206,-288 206,-324"/>
<text text-anchor="middle" x="179" y="-303.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
</g>
<!-- GPIODevice -->
<g id="node2" class="node"><title>GPIODevice</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="172,-252 100,-252 100,-216 172,-216 172,-252"/>
<text text-anchor="middle" x="136" y="-231.5" font-family="Sans" font-size="10.00" fill="#000000">GPIODevice</text>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="173,-252 101,-252 101,-216 173,-216 173,-252"/>
<text text-anchor="middle" x="137" y="-231.5" font-family="Sans" font-size="10.00" fill="#000000">GPIODevice</text>
</g>
<!-- GPIODevice&#45;&gt;Device -->
<g id="edge1" class="edge"><title>GPIODevice&#45;&gt;Device</title>
<path fill="none" stroke="black" d="M146.135,-252.303C150.852,-260.356 156.562,-270.106 161.782,-279.018"/>
<polygon fill="black" stroke="black" points="158.908,-281.036 166.982,-287.896 164.948,-277.498 158.908,-281.036"/>
<path fill="none" stroke="black" d="M147.382,-252.303C152.265,-260.441 158.187,-270.311 163.579,-279.299"/>
<polygon fill="black" stroke="black" points="160.591,-281.121 168.737,-287.896 166.594,-277.52 160.591,-281.121"/>
</g>
<!-- OutputDevice -->
<g id="node3" class="node"><title>OutputDevice</title>
@@ -31,8 +31,8 @@
</g>
<!-- OutputDevice&#45;&gt;GPIODevice -->
<g id="edge2" class="edge"><title>OutputDevice&#45;&gt;GPIODevice</title>
<path fill="none" stroke="black" d="M136,-180.303C136,-188.017 136,-197.288 136,-205.888"/>
<polygon fill="black" stroke="black" points="132.5,-205.896 136,-215.896 139.5,-205.896 132.5,-205.896"/>
<path fill="none" stroke="black" d="M136.247,-180.303C136.357,-188.017 136.49,-197.288 136.613,-205.888"/>
<polygon fill="black" stroke="black" points="133.113,-205.947 136.756,-215.896 140.112,-205.847 133.113,-205.947"/>
</g>
<!-- DigitalOutputDevice -->
<g id="node4" class="node"><title>DigitalOutputDevice</title>
@@ -86,13 +86,23 @@
</g>
<!-- RGBLED -->
<g id="node9" class="node"><title>RGBLED</title>
<polygon fill="#2980b9" stroke="#2980b9" points="246,-252 190,-252 190,-216 246,-216 246,-252"/>
<text text-anchor="middle" x="218" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">RGBLED</text>
<polygon fill="#2980b9" stroke="#2980b9" points="249,-252 193,-252 193,-216 249,-216 249,-252"/>
<text text-anchor="middle" x="221" y="-231.5" font-family="Sans" font-size="10.00" fill="#ffffff">RGBLED</text>
</g>
<!-- RGBLED&#45;&gt;Device -->
<g id="edge8" class="edge"><title>RGBLED&#45;&gt;Device</title>
<path fill="none" stroke="black" d="M207.865,-252.303C203.148,-260.356 197.438,-270.106 192.218,-279.018"/>
<polygon fill="black" stroke="black" points="189.052,-277.498 187.018,-287.896 195.092,-281.036 189.052,-277.498"/>
<path fill="none" stroke="black" d="M210.618,-252.303C205.735,-260.441 199.813,-270.311 194.421,-279.299"/>
<polygon fill="black" stroke="black" points="191.406,-277.52 189.263,-287.896 197.409,-281.121 191.406,-277.52"/>
</g>
<!-- LedBorg -->
<g id="node10" class="node"><title>LedBorg</title>
<polygon fill="#2980b9" stroke="#2980b9" points="251,-180 195,-180 195,-144 251,-144 251,-180"/>
<text text-anchor="middle" x="223" y="-159.5" font-family="Sans" font-size="10.00" fill="#ffffff">LedBorg</text>
</g>
<!-- LedBorg&#45;&gt;RGBLED -->
<g id="edge9" class="edge"><title>LedBorg&#45;&gt;RGBLED</title>
<path fill="none" stroke="black" d="M222.506,-180.303C222.285,-188.017 222.02,-197.288 221.775,-205.888"/>
<polygon fill="black" stroke="black" points="218.276,-205.8 221.489,-215.896 225.273,-206 218.276,-205.8"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -8,6 +8,7 @@ Table of Contents
recipes
notes
contributing
api_input
api_output
api_spi
@@ -19,3 +20,4 @@ Table of Contents
api_exc
changelog
license

View File

@@ -70,3 +70,26 @@ In this case, all references to items within GPIO Zero must be prefixed::
button = gpiozero.Button(2)
How can I tell what version of gpiozero I have installed?
=========================================================
The gpiozero library relies on the setuptools package for installation
services. You can use the setuptools ``pkg_resources`` API to query which
version of gpiozero is available in your Python environment like so::
>>> from pkg_resources import require
>>> require('gpiozero')
[gpiozero 1.2.0 (/usr/local/lib/python2.7/dist-packages)]
>>> require('gpiozero')[0].version
'1.2.0'
If you have multiple versions installed (e.g. from ``pip`` and ``apt-get``)
they will not show up in the list returned by the ``require`` method. However,
the first entry in the list will be the version that ``import gpiozero`` will
import.
If you receive the error "No module named pkg_resources", you need to install
the ``pip`` utility. This can be done with the following command in Raspbian::
$ sudo apt-get install python-pip

View File

@@ -9,6 +9,8 @@ library. Please note that all recipes are written assuming Python 3. Recipes
*may* work under Python 2, but no guarantees!
.. _pin_numbering:
Pin Numbering
=============
@@ -18,39 +20,25 @@ configurable.
.. _RPi.GPIO: https://pypi.python.org/pypi/RPi.GPIO
Any pin marked ``GPIO`` in the diagram below can be used for generic
components:
Any pin marked "GPIO" in the diagram below can be used as a pin number. For
example, if an LED was attached to "GPIO17" you would specify the pin number as
17 rather than 11:
.. image:: images/pin_layout.*
LED
===
.. image:: images/led.*
Turn an :class:`LED` on and off repeatedly::
Turn an :class:`LED` on and off repeatedly:
from gpiozero import LED
from time import sleep
.. literalinclude:: examples/led_1.py
red = LED(17)
Alternatively:
while True:
red.on()
sleep(1)
red.off()
sleep(1)
Alternatively::
from gpiozero import LED
from signal import pause
red = LED(17)
red.blink()
pause()
.. literalinclude:: examples/led_2.py
.. note::
@@ -59,45 +47,51 @@ Alternatively::
:ref:`keep-your-script-running` for more information.
LED with variable brightness
============================
Any regular LED can have its brightness value set using PWM
(pulse-width-modulation). In GPIO Zero, this can be achieved using
:class:`PWMLED` using values between 0 and 1:
.. literalinclude:: examples/led_variable_brightness.py
Similarly to blinking on and off continuously, a PWMLED can pulse (fade in and
out continuously):
.. literalinclude:: examples/led_pulse.py
Button
======
.. image:: images/button.*
Check if a :class:`Button` is pressed::
Check if a :class:`Button` is pressed:
from gpiozero import Button
.. literalinclude:: examples/button_1.py
button = Button(2)
Wait for a button to be pressed before continuing:
while True:
if button.is_pressed:
print("Button is pressed")
else:
print("Button is not pressed")
.. literalinclude:: examples/button_2.py
Wait for a button to be pressed before continuing::
Run a function every time the button is pressed:
from gpiozero import Button
.. literalinclude:: examples/button_3.py
:emphasize-lines: 9
button = Button(2)
.. note::
button.wait_for_press()
print("Button was pressed")
Note that the line ``button.when_pressed = say_hello`` does not run the
function ``say_hello``, rather it creates a reference to the function to
be called when the button is pressed. Accidental use of
``button.when_pressed = say_hello()`` would set the ``when_pressed`` action
to ``None`` (the return value of this function) which would mean nothing
happens when the button is pressed.
Run a function every time the button is pressed::
Similarly, functions can be attached to button releases:
from gpiozero import Button
from signal import pause
def say_hello():
print("Hello!")
button = Button(2)
button.when_pressed = say_hello
pause()
.. literalinclude:: examples/button_4.py
Button controlled LED
@@ -105,30 +99,71 @@ Button controlled LED
.. image:: images/led_button_bb.*
Turn on an :class:`LED` when a :class:`Button` is pressed::
Turn on an :class:`LED` when a :class:`Button` is pressed:
from gpiozero import LED, Button
from signal import pause
.. literalinclude:: examples/button_led_1.py
led = LED(17)
button = Button(2)
Alternatively:
button.when_pressed = led.on
button.when_released = led.off
.. literalinclude:: examples/button_led_2.py
pause()
Alternatively::
Button controlled camera
========================
from gpiozero import LED, Button
from signal import pause
Using the button press to trigger :class:`~picamera.PiCamera` to take a picture
using ``button.when_pressed = camera.capture`` would not work because the
:meth:`~picamera.PiCamera.capture` method requires an ``output`` parameter.
However, this can be achieved using a custom function which requires no
parameters:
led = LED(17)
button = Button(2)
.. literalinclude:: examples/button_camera_1.py
:emphasize-lines: 9-11
led.source = button.values
Another example could use one button to start and stop the camera preview, and
another to capture:
pause()
.. literalinclude:: examples/button_camera_2.py
Shutdown button
===============
The :class:`Button` class also provides the ability to run a function when the
button has been held for a given length of time. This example will shut down
the Raspberry Pi when the button is held for 2 seconds:
.. literalinclude:: examples/button_shutdown.py
LEDBoard
========
A collection of LEDs can be accessed using :class:`LEDBoard`:
.. literalinclude:: examples/led_board_1.py
Using :class:`LEDBoard` with ``pwm=True`` allows each LED's brightness to be
controlled:
.. literalinclude:: examples/led_board_2.py
LEDBarGraph
===========
A collection of LEDs can be treated like a bar graph using
:class:`LEDBarGraph`:
.. literalinclude:: examples/led_bargraph_2.py
Note values are essentially rounded to account for the fact LEDs can only be on
or off when ``pwm=False`` (the default).
However, using :class:`LEDBarGraph` with ``pwm=True`` allows more precise
values using LED brightness:
.. literalinclude:: examples/led_bargraph_2.py
Traffic Lights
@@ -138,97 +173,37 @@ Traffic Lights
A full traffic lights system.
Using a :class:`TrafficLights` kit like Pi-Stop::
Using a :class:`TrafficLights` kit like Pi-Stop:
from gpiozero import TrafficLights
from time import sleep
.. literalinclude:: examples/traffic_lights_1.py
lights = TrafficLights(2, 3, 4)
Alternatively:
lights.green.on()
.. literalinclude:: examples/traffic_lights_2.py
while True:
sleep(10)
lights.green.off()
lights.amber.on()
sleep(1)
lights.amber.off()
lights.red.on()
sleep(10)
lights.amber.on()
sleep(1)
lights.green.on()
lights.amber.off()
lights.red.off()
Using :class:`LED` components:
Alternatively::
.. literalinclude:: examples/traffic_lights_3.py
from gpiozero import TrafficLights
from time import sleep
from signal import pause
lights = TrafficLights(2, 3, 4)
Travis build LED indicator
==========================
def traffic_light_sequence():
while True:
yield (0, 0, 1) # green
sleep(10)
yield (0, 1, 0) # amber
sleep(1)
yield (1, 0, 0) # red
sleep(10)
yield (1, 1, 0) # red+amber
sleep(1)
Use LEDs to indicate the status of a Travis build. A green light means the
tests are passing, a red light means the build is broken:
lights.source = traffic_light_sequence()
.. literalinclude:: examples/led_travis.py
pause()
Using :class:`LED` components::
from gpiozero import LED
from time import sleep
red = LED(2)
amber = LED(3)
green = LED(4)
green.on()
amber.off()
red.off()
while True:
sleep(10)
green.off()
amber.on()
sleep(1)
amber.off()
red.on()
sleep(10)
amber.on()
sleep(1)
green.on()
amber.off()
red.off()
Note this recipe requires `travispy`_. Install with ``sudo pip3 install
travispy``.
Push button stop motion
=======================
Capture a picture with the camera module every time a button is pressed::
Capture a picture with the camera module every time a button is pressed:
from gpiozero import Button
from picamera import PiCamera
button = Button(2)
with PiCamera() as camera:
camera.start_preview()
frame = 1
while True:
button.wait_for_press()
camera.capture('/home/pi/frame%03d.jpg' % frame)
frame += 1
.. literalinclude:: examples/button_stop_motion.py
See `Push Button Stop Motion`_ for a full resource.
@@ -240,30 +215,7 @@ Reaction Game
When you see the light come on, the first person to press their button wins!
::
from gpiozero import Button, LED
from time import sleep
import random
led = LED(17)
player_1 = Button(2)
player_2 = Button(3)
time = random.uniform(5, 10)
sleep(time)
led.on()
while True:
if player_1.is_pressed:
print("Player 1 wins!")
break
if player_2.is_pressed:
print("Player 2 wins!")
break
led.off()
.. literalinclude:: examples/reaction_game.py
See `Quick Reaction Game`_ for a full resource.
@@ -273,26 +225,7 @@ GPIO Music Box
Each button plays a different sound!
::
from gpiozero import Button
import pygame.mixer
from pygame.mixer import Sound
from signal import pause
pygame.mixer.init()
sound_pins = {
2: Sound("samples/drum_tom_mid_hard.wav"),
3: Sound("samples/drum_cymbal_open.wav"),
}
buttons = [Button(pin) for pin in sound_pins]
for button in buttons:
sound = sound_pins[button.pin.number]
button.when_pressed = sound.play
pause()
.. literalinclude:: examples/music_box.py
See `GPIO Music Box`_ for a full resource.
@@ -302,92 +235,27 @@ All on when pressed
While the button is pressed down, the buzzer and all the lights come on.
:class:`FishDish`::
:class:`FishDish`:
from gpiozero import FishDish
from signal import pause
.. literalinclude:: examples/all_on_1.py
fish = FishDish()
Ryanteck :class:`TrafficHat`:
fish.button.when_pressed = fish.on
fish.button.when_released = fish.off
.. literalinclude:: examples/all_on_2.py
pause()
Using :class:`LED`, :class:`Buzzer`, and :class:`Button` components:
Ryanteck :class:`TrafficHat`::
from gpiozero import TrafficHat
from signal import pause
th = TrafficHat()
th.button.when_pressed = th.on
th.button.when_released = th.off
pause()
Using :class:`LED`, :class:`Buzzer`, and :class:`Button` components::
from gpiozero import LED, Buzzer, Button
from signal import pause
button = Button(2)
buzzer = Buzzer(3)
red = LED(4)
amber = LED(5)
green = LED(6)
things = [red, amber, green, buzzer]
def things_on():
for thing in things:
thing.on()
def things_off():
for thing in things:
thing.off()
button.when_pressed = things_on
button.when_released = things_off
pause()
.. literalinclude:: examples/all_on_3.py
RGB LED
=======
Full color LED
==============
.. image:: images/rgb_led_bb.*
Making colours with an :class:`RGBLED`::
Making colours with an :class:`RGBLED`:
from gpiozero import RGBLED
from time import sleep
led = RGBLED(red=9, green=10, blue=11)
led.red = 1 # full red
sleep(1)
led.red = 0.5 # half red
sleep(1)
led.color = (0, 1, 0) # full green
sleep(1)
led.color = (1, 0, 1) # magenta
sleep(1)
led.color = (1, 1, 0) # yellow
sleep(1)
led.color = (0, 1, 1) # cyan
sleep(1)
led.color = (1, 1, 1) # white
sleep(1)
led.color = (0, 0, 0) # off
sleep(1)
# slowly increase intensity of blue
for n in range(100):
led.blue = n/100
sleep(0.1)
.. literalinclude:: examples/rgbled.py
Motion sensor
@@ -395,18 +263,9 @@ Motion sensor
.. image:: images/motion_sensor_bb.*
Light an :class:`LED` when a :class:`MotionSensor` detects motion::
Light an :class:`LED` when a :class:`MotionSensor` detects motion:
from gpiozero import MotionSensor, LED
from signal import pause
pir = MotionSensor(4)
led = LED(16)
pir.when_motion = led.on
pir.when_no_motion = led.off
pause()
.. literalinclude:: examples/motion_sensor.py
Light sensor
@@ -414,43 +273,18 @@ Light sensor
.. image:: images/light_sensor_bb.*
Have a :class:`LightSensor` detect light and dark::
Have a :class:`LightSensor` detect light and dark:
from gpiozero import LightSensor
.. literalinclude:: examples/light_sensor_1.py
sensor = LightSensor(18)
Run a function when the light changes:
while True:
sensor.wait_for_light()
print("It's light! :)")
sensor.wait_for_dark()
print("It's dark :(")
Run a function when the light changes::
from gpiozero import LightSensor, LED
from signal import pause
sensor = LightSensor(18)
led = LED(16)
sensor.when_dark = led.on
sensor.when_light = led.off
pause()
.. literalinclude:: examples/light_sensor_2.py
Or make a :class:`PWMLED` change brightness according to the detected light
level::
level:
from gpiozero import LightSensor, LED
from signal import pause
sensor = LightSensor(18)
led = PWMLED(16)
led.source = sensor.values
pause()
.. literalinclude:: examples/light_sensor_3.py
Distance sensor
@@ -458,29 +292,13 @@ Distance sensor
.. IMAGE TBD
Have a :class:`DistanceSensor` detect the distance to the nearest object::
Have a :class:`DistanceSensor` detect the distance to the nearest object:
from gpiozero import DistanceSensor
from time import sleep
.. literalinclude:: examples/distance_sensor_1.py
sensor = DistanceSensor(23, 24)
Run a function when something gets near the sensor:
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()
.. literalinclude:: examples/distance_sensor_2.py
Motors
@@ -488,18 +306,9 @@ Motors
.. image:: images/motor_bb.*
Spin a :class:`Motor` around forwards and backwards::
Spin a :class:`Motor` around forwards and backwards:
from gpiozero import Motor
from time import sleep
motor = Motor(forward=4, back=14)
while True:
motor.forward()
sleep(5)
motor.backward()
sleep(5)
.. literalinclude:: examples/motor.py
Robot
@@ -507,163 +316,59 @@ Robot
.. IMAGE TBD
Make a :class:`Robot` drive around in (roughly) a square::
Make a :class:`Robot` drive around in (roughly) a square:
from gpiozero import Robot
from time import sleep
robot = Robot(left=(4, 14), right=(17, 18))
for i in range(4):
robot.forward()
sleep(10)
robot.right()
sleep(1)
.. literalinclude:: examples/robot_1.py
Make a robot with a distance sensor that runs away when things get within
20cm of it::
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()
.. literalinclude:: examples/robot_2.py
Button controlled robot
=======================
Use four GPIO buttons as forward/back/left/right controls for a robot::
Use four GPIO buttons as forward/back/left/right controls for a robot:
from gpiozero import RyanteckRobot, Button
from signal import pause
robot = RyanteckRobot()
left = Button(26)
right = Button(16)
fw = Button(21)
bw = Button(20)
fw.when_pressed = robot.forward
fw.when_released = robot.stop
left.when_pressed = robot.left
left.when_released = robot.stop
right.when_pressed = robot.right
right.when_released = robot.stop
bw.when_pressed = robot.backward
bw.when_released = robot.stop
pause()
.. literalinclude:: examples/robot_buttons.py
Keyboard controlled robot
=========================
Use up/down/left/right keys to control a robot::
Use up/down/left/right keys to control a robot:
import curses
from gpiozero import RyanteckRobot
robot = RyanteckRobot()
actions = {
curses.KEY_UP: robot.forward,
curses.KEY_DOWN: robot.backward,
curses.KEY_LEFT: robot.left,
curses.KEY_RIGHT: robot.right,
}
def main(window):
next_key = None
while True:
curses.halfdelay(1)
if next_key is None:
key = window.getch()
else:
key = next_key
next_key = None
if key != -1:
# KEY DOWN
curses.halfdelay(3)
action = actions.get(key)
if action is not None:
action()
next_key = key
while next_key == key:
next_key = window.getch()
# KEY UP
robot.stop()
curses.wrapper(main)
.. literalinclude:: examples/robot_keyboard_1.py
.. note::
This recipe uses the ``curses`` module. This module requires that Python is
running in a terminal in order to work correctly, hence this recipe will
*not* work in environments like IDLE.
This recipe uses the standard :mod:`curses` module. This module requires
that Python is running in a terminal in order to work correctly, hence this
recipe will *not* work in environments like IDLE.
If you prefer a version that works under IDLE, the following recipe should
suffice, but will require that you install the evdev library with ``sudo pip
install evdev`` first::
suffice:
from gpiozero import RyanteckRobot
from evdev import InputDevice, list_devices, ecodes
.. literalinclude:: examples/robot_keyboard_2.py
robot = RyanteckRobot()
.. note::
devices = [InputDevice(device) for device in list_devices()]
keyboard = devices[0] # this may vary
keypress_actions = {
ecodes.KEY_UP: robot.forward,
ecodes.KEY_DOWN: robot.backward,
ecodes.KEY_LEFT: robot.left,
ecodes.KEY_RIGHT: robot.right,
}
for event in keyboard.read_loop():
if event.type == ecodes.EV_KEY:
if event.value == 1: # key down
keypress_actions[event.code]()
if event.value == 0: # key up
robot.stop()
This recipe uses the third-party ``evdev`` module. Install this library
with ``sudo pip3 install evdev`` first. Be aware that ``evdev`` will only
work with local input devices; this recipe will *not* work over SSH.
Motion sensor robot
===================
Make a robot drive forward when it detects motion::
Make a robot drive forward when it detects motion:
from gpiozero import Robot, MotionSensor
from signal import pause
.. literalinclude:: examples/robot_motion_1.py
robot = Robot(left=(4, 14), right=(17, 18))
pir = MotionSensor(5)
Alternatively:
pir.when_motion = robot.forward
pir.when_no_motion = robot.stop
pause()
Alternatively::
from gpiozero import Robot, MotionSensor
from signal import pause
robot = Robot(left=(4, 14), right=(17, 18))
pir = MotionSensor(5)
robot.source = zip(pir.values, pir.values)
pause()
.. literalinclude:: examples/robot_motion_2.py
Potentiometer
@@ -672,24 +377,14 @@ Potentiometer
.. image:: images/potentiometer_bb.*
Continually print the value of a potentiometer (values between 0 and 1)
connected to a :class:`MCP3008` analog to digital converter::
connected to a :class:`MCP3008` analog to digital converter:
from gpiozero import MCP3008
while True:
with MCP3008(channel=0) as pot:
print(pot.value)
.. literalinclude:: examples/pot_1.py
Present the value of a potentiometer on an LED bar graph using PWM to represent
states that won't "fill" an LED::
states that won't "fill" an LED:
from gpiozero import LEDBarGraph, MCP3008
from signal import pause
graph = LEDBarGraph(5, 6, 13, 19, 26, pwm=True)
pot = MCP3008(channel=0)
graph.source = pot.values
pause()
.. literalinclude:: examples/pot_2.py
Measure temperature with an ADC
@@ -698,54 +393,24 @@ Measure temperature with an ADC
.. IMAGE TBD
Wire a TMP36 temperature sensor to the first channel of an :class:`MCP3008`
analog to digital converter::
analog to digital converter:
from gpiozero import MCP3008
from time import sleep
def convert_temp(gen):
for value in gen:
yield (value * 3.3 - 0.5) * 100
adc = MCP3008(channel=0)
for temp in convert_temp(adc.values):
print('The temperature is', temp, 'C')
sleep(1)
.. literalinclude:: examples/thermometer.py
Full color LED controlled by 3 potentiometers
=============================================
Wire up three potentiometers (for red, green and blue) and use each of their
values to make up the colour of the LED::
values to make up the colour of the LED:
from gpiozero import RGBLED, MCP3008
led = RGBLED(red=2, green=3, blue=4)
red_pot = MCP3008(channel=0)
green_pot = MCP3008(channel=1)
blue_pot = MCP3008(channel=2)
while True:
led.red = red_pot.value
led.green = green_pot.value
led.blue = blue_pot.value
.. literalinclude:: examples/rgbled_pot_1.py
Alternatively, the following example is identical, but uses the
:attr:`~SourceMixin.source` property rather than a :keyword:`while` loop::
:attr:`~SourceMixin.source` property rather than a :keyword:`while` loop:
from gpiozero import RGBLED, MCP3008
from signal import pause
led = RGBLED(2, 3, 4)
red_pot = MCP3008(0)
green_pot = MCP3008(1)
blue_pot = MCP3008(2)
led.source = zip(red_pot.values, green_pot.values, blue_pot.values)
pause()
.. literalinclude:: examples/rgbled_pot_2.py
:emphasize-lines: 8
Please note the example above requires Python 3. In Python 2, :func:`zip`
doesn't support lazy evaluation so the script will simply hang.
@@ -765,17 +430,9 @@ be done from the terminal with the following commands::
$ echo none | sudo tee /sys/class/leds/led0/trigger
$ echo gpio | sudo tee /sys/class/leds/led1/trigger
Now you can control the LEDs with gpiozero like so::
Now you can control the LEDs with gpiozero like so:
from gpiozero import LED
from signal import pause
power = LED(35)
activity = LED(47)
activity.blink()
power.blink()
pause()
.. literalinclude:: examples/led_builtin.py
To revert the LEDs to their usual purpose you can either reboot your Pi or
run the following commands::
@@ -788,7 +445,7 @@ run the following commands::
On the Pi Zero you can control the activity LED with this recipe, but
there's no separate power LED to control (it's also worth noting the
activity LED is active low, so set ``active_high=False`` when constructing
your LED component.
your LED component).
On the original Pi 1 (model A or B), the activity LED can be controlled
with GPIO16 (after disabling its trigger as above) but the power LED is
@@ -798,6 +455,7 @@ run the following commands::
accessible from gpiozero (yet).
.. _travispy: https://travispy.readthedocs.io/
.. _Push Button Stop Motion: https://www.raspberrypi.org/learning/quick-reaction-game/
.. _Quick Reaction Game: https://www.raspberrypi.org/learning/quick-reaction-game/
.. _GPIO Music Box: https://www.raspberrypi.org/learning/gpio-music-box/

View File

@@ -7,6 +7,7 @@ from __future__ import (
from .pins import (
Pin,
LocalPin,
)
from .pins.data import (
PiBoardInfo,
@@ -99,13 +100,17 @@ from .output_devices import (
LED,
Buzzer,
Motor,
Servo,
AngularServo,
RGBLED,
)
from .boards import (
CompositeOutputDevice,
ButtonBoard,
LEDCollection,
LEDBoard,
LEDBarGraph,
LedBorg,
PiLiter,
PiLiterBarGraph,
TrafficLights,
@@ -122,5 +127,6 @@ from .boards import (
from .other_devices import (
InternalDevice,
PingServer,
CPUTemperature,
TimeOfDay,
)

View File

@@ -12,18 +12,27 @@ except ImportError:
from time import sleep
from itertools import repeat, cycle, chain
from threading import Lock
from collections import OrderedDict
from .exc import (
DeviceClosed,
GPIOPinMissing,
EnergenieSocketMissing,
EnergenieBadSocket,
OutputDeviceBadValue,
)
from .input_devices import Button
from .output_devices import OutputDevice, LED, PWMLED, Buzzer, Motor
from .output_devices import (
OutputDevice,
LED,
PWMLED,
RGBLED,
Buzzer,
Motor,
)
from .threads import GPIOThread
from .devices import Device, CompositeDevice
from .mixins import SharedMixin, SourceMixin
from .mixins import SharedMixin, SourceMixin, HoldMixin
class CompositeOutputDevice(SourceMixin, CompositeDevice):
@@ -44,7 +53,7 @@ class CompositeOutputDevice(SourceMixin, CompositeDevice):
"""
Turn all the output devices on.
"""
for device in self.all:
for device in self:
if isinstance(device, (OutputDevice, CompositeOutputDevice)):
device.on()
@@ -52,7 +61,7 @@ class CompositeOutputDevice(SourceMixin, CompositeDevice):
"""
Turn all the output devices off.
"""
for device in self.all:
for device in self:
if isinstance(device, (OutputDevice, CompositeOutputDevice)):
device.off()
@@ -61,7 +70,7 @@ class CompositeOutputDevice(SourceMixin, CompositeDevice):
Toggle all the output devices. For each device, if it's on, turn it
off; if it's off, turn it on.
"""
for device in self.all:
for device in self:
if isinstance(device, (OutputDevice, CompositeOutputDevice)):
device.toggle()
@@ -75,18 +84,126 @@ class CompositeOutputDevice(SourceMixin, CompositeDevice):
@value.setter
def value(self, value):
for device, v in zip(self.all, value):
for device, v in zip(self, value):
if isinstance(device, (OutputDevice, CompositeOutputDevice)):
device.value = v
# Simply ignore values for non-output devices
class ButtonBoard(HoldMixin, CompositeDevice):
"""
Extends :class:`CompositeDevice` and represents a generic button board or
collection of buttons.
:param int \*pins:
Specify the GPIO pins that the buttons of the board are attached to.
You can designate as many pins as necessary.
:param bool pull_up:
If ``True`` (the default), the GPIO pins will be pulled high by
default. In this case, connect the other side of the buttons to
ground. If ``False``, the GPIO pins will be pulled low by default. In
this case, connect the other side of the buttons to 3V3. This
parameter can only be specified as a keyword parameter.
:param float bounce_time:
If ``None`` (the default), no software bounce compensation will be
performed. Otherwise, this is the length of time (in seconds) that the
buttons will ignore changes in state after an initial change. This
parameter can only be specified as a keyword parameter.
:param float hold_time:
The length of time (in seconds) to wait after any button is pushed,
until executing the :attr:`when_held` handler. Defaults to ``1``. This
parameter can only be specified as a keyword parameter.
:param bool hold_repeat:
If ``True``, the :attr:`when_held` handler will be repeatedly executed
as long as any buttons remain held, every *hold_time* seconds. If
``False`` (the default) the :attr:`when_held` handler will be only be
executed once per hold. This parameter can only be specified as a
keyword parameter.
:param \*\*named_pins:
Specify GPIO pins that buttons of the board are attached to,
associating each button with a property name. You can designate as
many pins as necessary and use any names, provided they're not already
in use by something else.
"""
def __init__(self, *args, **kwargs):
pull_up = kwargs.pop('pull_up', True)
bounce_time = kwargs.pop('bounce_time', None)
hold_time = kwargs.pop('hold_time', 1)
hold_repeat = kwargs.pop('hold_repeat', False)
order = kwargs.pop('_order', None)
super(ButtonBoard, self).__init__(
*(
Button(pin, pull_up, bounce_time, hold_time, hold_repeat)
for pin in args
),
_order=order,
**{
name: Button(pin, pull_up, bounce_time, hold_time, hold_repeat)
for name, pin in kwargs.items()
})
def get_new_handler(device):
def fire_both_events():
device._fire_events()
self._fire_events()
return fire_both_events
for button in self:
button.pin.when_changed = get_new_handler(button)
self._when_changed = None
self._last_value = None
# Call _fire_events once to set initial state of events
self._fire_events()
self.hold_time = hold_time
self.hold_repeat = hold_repeat
@property
def pull_up(self):
"""
If ``True``, the device uses a pull-up resistor to set the GPIO pin
"high" by default.
"""
return self[0].pull_up
@property
def when_changed(self):
return self._when_changed
@when_changed.setter
def when_changed(self, value):
self._when_changed = self._wrap_callback(value)
def _fire_changed(self):
if self.when_changed:
self.when_changed()
def _fire_events(self):
super(ButtonBoard, self)._fire_events()
old_value = self._last_value
new_value = self._last_value = self.value
if old_value is None:
# Initial "indeterminate" value; don't do anything
pass
elif old_value != new_value:
self._fire_changed()
ButtonBoard.is_pressed = ButtonBoard.is_active
ButtonBoard.pressed_time = ButtonBoard.active_time
ButtonBoard.when_pressed = ButtonBoard.when_activated
ButtonBoard.when_released = ButtonBoard.when_deactivated
ButtonBoard.wait_for_press = ButtonBoard.wait_for_active
ButtonBoard.wait_for_release = ButtonBoard.wait_for_inactive
class LEDCollection(CompositeOutputDevice):
"""
Extends :class:`CompositeOutputDevice`. Abstract base class for
:class:`LEDBoard` and :class:`LEDBarGraph`.
"""
def __init__(self, *args, **kwargs):
self._blink_thread = None
pwm = kwargs.pop('pwm', False)
@@ -108,19 +225,26 @@ class LEDCollection(CompositeOutputDevice):
LEDClass(pin_or_collection, active_high, initial_value)
for name, pin_or_collection in kwargs.items()
})
leds = []
for item in self:
if isinstance(item, LEDCollection):
for subitem in item.leds:
leds.append(subitem)
else:
leds.append(item)
self._leds = tuple(leds)
@property
def leds(self):
"""
A flat iterator over all LEDs contained in this collection (and all
A flat tuple of all LEDs contained in this collection (and all
sub-collections).
"""
for item in self:
if isinstance(item, LEDCollection):
for subitem in item.leds:
yield subitem
else:
yield item
return self._leds
@property
def active_high(self):
return self[0].active_high
class LEDBoard(LEDCollection):
@@ -148,21 +272,23 @@ class LEDBoard(LEDCollection):
:param bool active_high:
If ``True`` (the default), the :meth:`on` method will set all the
associates pins to HIGH. If ``False``, the :meth:`on` method will set
all pins to LOW (the :meth:`off` method always does the opposite).
associated pins to HIGH. If ``False``, the :meth:`on` method will set
all pins to LOW (the :meth:`off` method always does the opposite). This
parameter can only be specified as a keyword parameter.
:param bool initial_value:
If ``False`` (the default), all LEDs will be off initially. If
``None``, each device will be left in whatever state the pin is found
in when configured for output (warning: this can be on). If ``True``,
the device will be switched on initially.
the device will be switched on initially. This parameter can only be
specified as a keyword parameter.
:param \*\*named_pins:
Sepcify GPIO pins that LEDs of the board are attached to, associated
Specify GPIO pins that LEDs of the board are attached to, associating
each LED with a property name. You can designate as many pins as
necessary and any name provided it's not already in use by something
else. You can also specify :class:`LEDBoard` instances to create
trees of LEDs.
necessary and use any names, provided they're not already in use by
something else. You can also specify :class:`LEDBoard` instances to
create trees of LEDs.
"""
def __init__(self, *args, **kwargs):
self._blink_leds = []
@@ -345,15 +471,21 @@ class LEDBarGraph(LEDCollection):
Specify the GPIO pins that the LEDs of the bar graph are attached to.
You can designate as many pins as necessary.
:param float initial_value:
The initial :attr:`value` of the graph given as a float between -1 and
+1. Defaults to 0.0. This parameter can only be specified as a keyword
parameter.
:param bool pwm:
If ``True``, construct :class:`PWMLED` instances for each pin. If
``False`` (the default), construct regular :class:`LED` instances. This
parameter can only be specified as a keyword parameter.
:param bool active_high:
If ``True`` (the default), the :meth:`on` method will set all the
associated pins to HIGH. If ``False``, the :meth:`on` method will set
all pins to LOW (the :meth:`off` method always does the opposite). This
parameter can only be specified as a keyword parameter.
:param float initial_value:
The initial :attr:`value` of the graph given as a float between -1 and
+1. Defaults to ``0.0``. This parameter can only be specified as a
keyword parameter.
"""
def __init__(self, *pins, **kwargs):
@@ -361,10 +493,11 @@ class LEDBarGraph(LEDCollection):
for pin in pins:
assert not isinstance(pin, LEDCollection)
pwm = kwargs.pop('pwm', False)
initial_value = kwargs.pop('initial_value', 0)
active_high = kwargs.pop('active_high', True)
initial_value = kwargs.pop('initial_value', 0.0)
if kwargs:
raise TypeError('unexpected keyword argument: %s' % kwargs.popitem()[0])
super(LEDBarGraph, self).__init__(*pins, pwm=pwm)
super(LEDBarGraph, self).__init__(*pins, pwm=pwm, active_high=active_high)
try:
self.value = initial_value
except:
@@ -402,6 +535,8 @@ class LEDBarGraph(LEDCollection):
@value.setter
def value(self, value):
if not -1 <= value <= 1:
raise OutputDeviceBadValue('LEDBarGraph value must be between -1 and 1')
count = len(self)
leds = self
if value < 0:
@@ -415,6 +550,36 @@ class LEDBarGraph(LEDCollection):
led.value = calc_value(index)
class LedBorg(RGBLED):
"""
Extends :class:`RGBLED` for the `PiBorg LedBorg`_: an add-on board
containing a very bright RGB LED.
The LedBorg pins are fixed and therefore there's no need to specify them
when constructing this class. The following example turns the LedBorg
purple::
from gpiozero import LedBorg
led = LedBorg()
led.color = (1, 0, 1)
:param tuple initial_value:
The initial color for the LedBorg. Defaults to black ``(0, 0, 0)``.
:param bool pwm:
If ``True`` (the default), construct :class:`PWMLED` instances for
each component of the LedBorg. If ``False``, construct regular
:class:`LED` instances, which prevents smooth color graduations.
.. _PiBorg LedBorg: https://www.piborg.org/ledborg
"""
def __init__(self, initial_value=(0, 0, 0), pwm=True):
super(LedBorg, self).__init__(red=17, green=27, blue=22,
pwm=pwm, initial_value=initial_value)
class PiLiter(LEDBoard):
"""
Extends :class:`LEDBoard` for the `Ciseco Pi-LITEr`_: a strip of 8 very bright
@@ -431,14 +596,20 @@ class PiLiter(LEDBoard):
:param bool pwm:
If ``True``, construct :class:`PWMLED` instances for each pin. If
``False`` (the default), construct regular :class:`LED` instances. This
parameter can only be specified as a keyword parameter.
``False`` (the default), construct regular :class:`LED` instances.
:param bool initial_value:
If ``False`` (the default), all LEDs will be off initially. If
``None``, each device will be left in whatever state the pin is found
in when configured for output (warning: this can be on). If ``True``,
the device will be switched on initially.
.. _Ciseco Pi-LITEr: http://shop.ciseco.co.uk/pi-liter-8-led-strip-for-the-raspberry-pi/
"""
def __init__(self, pwm=False):
super(PiLiter, self).__init__(4, 17, 27, 18, 22, 23, 24, 25, pwm=pwm)
def __init__(self, pwm=False, initial_value=False):
super(PiLiter, self).__init__(4, 17, 27, 18, 22, 23, 24, 25,
pwm=pwm, initial_value=initial_value)
class PiLiterBarGraph(LEDBarGraph):
@@ -455,25 +626,30 @@ class PiLiterBarGraph(LEDBarGraph):
graph = PiLiterBarGraph()
graph.value = 0.5
:param bool initial_value:
The initial value of the graph given as a float between -1 and +1.
Defaults to 0.0.
:param bool pwm:
If ``True``, construct :class:`PWMLED` instances for each pin. If
``False`` (the default), construct regular :class:`LED` instances.
:param float initial_value:
The initial :attr:`value` of the graph given as a float between -1 and
+1. Defaults to ``0.0``.
.. _Ciseco Pi-LITEr: http://shop.ciseco.co.uk/pi-liter-8-led-strip-for-the-raspberry-pi/
"""
def __init__(self, initial_value=0):
super(PiLiterBarGraph, self).__init__(
4, 17, 27, 18, 22, 23, 24, 25, initial_value=initial_value)
def __init__(self, pwm=False, initial_value=0.0):
pins = (4, 17, 27, 18, 22, 23, 24, 25)
super(PiLiterBarGraph, self).__init__(*pins,
pwm=pwm, initial_value=initial_value)
class TrafficLights(LEDBoard):
"""
Extends :class:`LEDBoard` for devices containing red, amber, and green
Extends :class:`LEDBoard` for devices containing red, yellow, and green
LEDs.
The following example initializes a device connected to GPIO pins 2, 3,
and 4, then lights the amber LED attached to GPIO 3::
and 4, then lights the amber (yellow) LED attached to GPIO 3::
from gpiozero import TrafficLights
@@ -493,15 +669,53 @@ class TrafficLights(LEDBoard):
If ``True``, construct :class:`PWMLED` instances to represent each
LED. If ``False`` (the default), construct regular :class:`LED`
instances.
:param bool initial_value:
If ``False`` (the default), all LEDs will be off initially. If
``None``, each device will be left in whatever state the pin is found
in when configured for output (warning: this can be on). If ``True``,
the device will be switched on initially.
:param int yellow:
The GPIO pin that the yellow LED is attached to. This is merely an
alias for the ``amber`` parameter - you can't specify both ``amber``
and ``yellow``.
"""
def __init__(self, red=None, amber=None, green=None, pwm=False):
if not all([red, amber, green]):
def __init__(self, red=None, amber=None, green=None,
pwm=False, initial_value=False, yellow=None):
if amber is not None and yellow is not None:
raise OutputDeviceBadValue(
'Only one of amber or yellow can be specified'
)
devices = OrderedDict((('red', red), ))
self._display_yellow = amber is None and yellow is not None
if self._display_yellow:
devices['yellow'] = yellow
else:
devices['amber'] = amber
devices['green'] = green
if not all(p is not None for p in devices.values()):
raise GPIOPinMissing(
'red, amber and green pins must be provided'
', '.join(devices.keys())+' pins must be provided'
)
super(TrafficLights, self).__init__(
red=red, amber=amber, green=green, pwm=pwm,
_order=('red', 'amber', 'green'))
pwm=pwm, initial_value=initial_value,
_order=devices.keys(),
**devices)
def __getattr__(self, name):
if name == 'amber' and self._display_yellow:
name = 'yellow'
elif name == 'yellow' and not self._display_yellow:
name = 'amber'
return super(TrafficLights, self).__getattr__(name)
def __setattr__(self, name, value):
if name == 'amber' and self._display_yellow:
name = 'yellow'
elif name == 'yellow' and not self._display_yellow:
name = 'amber'
return super(TrafficLights, self).__setattr__(name, value)
class PiTraffic(TrafficLights):
@@ -521,11 +735,22 @@ class PiTraffic(TrafficLights):
To use the PI-TRAFFIC board when attached to a non-standard set of pins,
simply use the parent class, :class:`TrafficLights`.
:param bool pwm:
If ``True``, construct :class:`PWMLED` instances to represent each
LED. If ``False`` (the default), construct regular :class:`LED`
instances.
:param bool initial_value:
If ``False`` (the default), all LEDs will be off initially. If
``None``, each device will be left in whatever state the pin is found
in when configured for output (warning: this can be on). If ``True``,
the device will be switched on initially.
.. _Low Voltage Labs PI-TRAFFIC: http://lowvoltagelabs.com/products/pi-traffic/
"""
def __init__(self):
super(PiTraffic, self).__init__(9, 10, 11)
def __init__(self, pwm=False, initial_value=False):
super(PiTraffic, self).__init__(9, 10, 11,
pwm=pwm, initial_value=initial_value)
class SnowPi(LEDBoard):
@@ -548,31 +773,41 @@ class SnowPi(LEDBoard):
LED. If ``False`` (the default), construct regular :class:`LED`
instances.
:param bool initial_value:
If ``False`` (the default), all LEDs will be off initially. If
``None``, each device will be left in whatever state the pin is found
in when configured for output (warning: this can be on). If ``True``,
the device will be switched on initially.
.. _Ryanteck SnowPi: https://ryanteck.uk/raspberry-pi/114-snowpi-the-gpio-snowman-for-raspberry-pi-0635648608303.html
"""
def __init__(self, pwm=False):
def __init__(self, pwm=False, initial_value=False):
super(SnowPi, self).__init__(
arms=LEDBoard(
left=LEDBoard(
top=17, middle=18, bottom=22, pwm=pwm,
top=17, middle=18, bottom=22,
pwm=pwm, initial_value=initial_value,
_order=('top', 'middle', 'bottom')),
right=LEDBoard(
top=7, middle=8, bottom=9, pwm=pwm,
top=7, middle=8, bottom=9,
pwm=pwm, initial_value=initial_value,
_order=('top', 'middle', 'bottom')),
_order=('left', 'right')
),
eyes=LEDBoard(
left=23, right=24, pwm=pwm,
left=23, right=24,
pwm=pwm, initial_value=initial_value,
_order=('left', 'right')
),
nose=25, pwm=pwm,
nose=25,
pwm=pwm, initial_value=initial_value,
_order=('eyes', 'nose', 'arms')
)
class TrafficLightsBuzzer(CompositeOutputDevice):
"""
Extends :class:`CompositeDevice` and is a generic class for HATs with
Extends :class:`CompositeOutputDevice` and is a generic class for HATs with
traffic lights, a button and a buzzer.
:param TrafficLights lights:
@@ -594,7 +829,7 @@ class TrafficLightsBuzzer(CompositeOutputDevice):
class FishDish(TrafficLightsBuzzer):
"""
Extends :class:`TrafficLightsBuzzer` for the Pi Supply FishDish: traffic
Extends :class:`TrafficLightsBuzzer` for the `Pi Supply FishDish`_: traffic
light LEDs, a button and a buzzer.
The FishDish pins are fixed and therefore there's no need to specify them
@@ -611,6 +846,8 @@ class FishDish(TrafficLightsBuzzer):
If ``True``, construct :class:`PWMLED` instances to represent each
LED. If ``False`` (the default), construct regular :class:`LED`
instances.
.. _Pi Supply FishDish: https://www.pi-supply.com/product/fish-dish-raspberry-pi-led-buzzer-board/
"""
def __init__(self, pwm=False):
@@ -623,7 +860,7 @@ class FishDish(TrafficLightsBuzzer):
class TrafficHat(TrafficLightsBuzzer):
"""
Extends :class:`TrafficLightsBuzzer` for the Ryanteck Traffic HAT: traffic
Extends :class:`TrafficLightsBuzzer` for the `Ryanteck Traffic HAT`_: traffic
light LEDs, a button and a buzzer.
The Traffic HAT pins are fixed and therefore there's no need to specify
@@ -640,6 +877,8 @@ class TrafficHat(TrafficLightsBuzzer):
If ``True``, construct :class:`PWMLED` instances to represent each
LED. If ``False`` (the default), construct regular :class:`LED`
instances.
.. _Ryanteck Traffic HAT: https://ryanteck.uk/hats/1-traffichat-0635648607122.html
"""
def __init__(self, pwm=False):
@@ -658,12 +897,12 @@ class Robot(SourceMixin, CompositeDevice):
backward pins of the left and right controllers respectively. For example,
if the left motor's controller is connected to GPIOs 4 and 14, while the
right motor's controller is connected to GPIOs 17 and 18 then the following
example will turn the robot left::
example will drive the robot forward::
from gpiozero import Robot
robot = Robot(left=(4, 14), right=(17, 18))
robot.left()
robot.forward()
:param tuple left:
A tuple of two GPIO pins representing the forward and backward inputs
@@ -680,6 +919,20 @@ class Robot(SourceMixin, CompositeDevice):
right_motor=Motor(*right),
_order=('left_motor', 'right_motor'))
@property
def value(self):
"""
Represents the motion of the robot as a tuple of (left_motor_speed,
right_motor_speed) with ``(-1, -1)`` representing full speed backwards,
``(1, 1)`` representing full speed forwards, and ``(0, 0)``
representing stopped.
"""
return super(Robot, self).value
@value.setter
def value(self, value):
self.left_motor.value, self.right_motor.value = value
def forward(self, speed=1):
"""
Drive the robot forward by running both motors forward.
@@ -746,16 +999,18 @@ class Robot(SourceMixin, CompositeDevice):
class RyanteckRobot(Robot):
"""
Extends :class:`Robot` for the Ryanteck MCB robot.
Extends :class:`Robot` for the `Ryanteck MCB`_ robot.
The Ryanteck MCB pins are fixed and therefore there's no need to specify
them when constructing this class. The following example turns the robot
left::
them when constructing this class. The following example drives the robot
forward::
from gpiozero import RyanteckRobot
robot = RyanteckRobot()
robot.left()
robot.forward()
.. _Ryanteck MCB: https://ryanteck.uk/add-ons/6-ryanteck-rpi-motor-controller-board-0635648607160.html
"""
def __init__(self):
@@ -767,13 +1022,13 @@ class CamJamKitRobot(Robot):
Extends :class:`Robot` for the `CamJam #3 EduKit`_ robot controller.
The CamJam robot controller pins are fixed and therefore there's no need
to specify them when constructing this class. The following example turns
the robot left::
to specify them when constructing this class. The following example drives
the robot forward::
from gpiozero import CamJamKitRobot
robot = CamJamKitRobot()
robot.left()
robot.forward()
.. _CamJam #3 EduKit: http://camjam.me/?page_id=1035
"""
@@ -805,7 +1060,7 @@ class _EnergenieMaster(SharedMixin, CompositeOutputDevice):
with self._lock:
try:
code = (8 * bool(enable)) + (8 - socket)
for bit in self.all[:4]:
for bit in self[:4]:
bit.value = (code & 1)
code >>= 1
sleep(0.1)
@@ -846,6 +1101,7 @@ class Energenie(SourceMixin, Device):
raise EnergenieSocketMissing('socket number must be provided')
if not (1 <= socket <= 4):
raise EnergenieBadSocket('socket number must be between 1 and 4')
self._value = None
super(Energenie, self).__init__()
self._socket = socket
self._master = _EnergenieMaster()
@@ -877,8 +1133,9 @@ class Energenie(SourceMixin, Device):
@value.setter
def value(self, value):
self._master.transmit(self._socket, bool(value))
self._value = bool(value)
value = bool(value)
self._master.transmit(self._socket, value)
self._value = value
def on(self):
self.value = True

View File

@@ -9,6 +9,9 @@ from __future__ import (
str = type('')
import cmath
import collections
import operator
import functools
# Back-ported from python 3.5; see
@@ -51,3 +54,30 @@ def median(data):
i = n // 2
return (data[i - 1] + data[i]) / 2
# Copied from the MIT-licensed https://github.com/slezica/python-frozendict
class frozendict(collections.Mapping):
def __init__(self, *args, **kwargs):
self.__dict = dict(*args, **kwargs)
self.__hash = None
def __getitem__(self, key):
return self.__dict[key]
def copy(self, **add_or_replace):
return frozendict(self, **add_or_replace)
def __iter__(self):
return iter(self.__dict)
def __len__(self):
return len(self.__dict)
def __repr__(self):
return '<frozendict %s>' % repr(self.__dict)
def __hash__(self):
if self.__hash is None:
hashes = map(hash, self.items())
self.__hash = functools.reduce(operator.xor, hashes, 0)
return self.__hash

View File

@@ -7,6 +7,7 @@ from __future__ import (
nstr = str
str = type('')
import os
import atexit
import weakref
from collections import namedtuple
@@ -14,12 +15,16 @@ from itertools import chain
from types import FunctionType
from threading import RLock
import pkg_resources
from .threads import _threads_shutdown
from .pins import _pins_shutdown
from .mixins import (
ValuesMixin,
SharedMixin,
)
from .exc import (
BadPinFactory,
DeviceClosed,
CompositeDeviceBadName,
CompositeDeviceBadOrder,
@@ -28,26 +33,34 @@ from .exc import (
GPIOPinInUse,
GPIODeviceClosed,
)
from .compat import frozendict
# Get a pin implementation to use as the default; we prefer RPi.GPIO's here
# as it supports PWM, and all Pi revisions. If no third-party libraries are
# available, however, we fall back to a pure Python implementation which
# supports platforms like PyPy
from .pins import _pins_shutdown
try:
from .pins.rpigpio import RPiGPIOPin
DefaultPin = RPiGPIOPin
except ImportError:
try:
from .pins.rpio import RPIOPin
DefaultPin = RPIOPin
except ImportError:
try:
from .pins.pigipod import PiGPIOPin
DefaultPin = PiGPIOPin
except ImportError:
from .pins.native import NativePin
DefaultPin = NativePin
def _default_pin_factory(name=os.getenv('GPIOZERO_PIN_FACTORY', None)):
group = 'gpiozero_pin_factories'
if name is None:
# If no factory is explicitly specified, try various names in
# "preferred" order. Note that in this case we only select from
# gpiozero distribution so without explicitly specifying a name (via
# the environment) it's impossible to auto-select a factory from
# outside the base distribution
#
# We prefer RPi.GPIO here as it supports PWM, and all Pi revisions. If
# no third-party libraries are available, however, we fall back to a
# pure Python implementation which supports platforms like PyPy
dist = pkg_resources.get_distribution('gpiozero')
for name in ('RPiGPIOPin', 'RPIOPin', 'PiGPIOPin', 'NativePin'):
try:
return pkg_resources.load_entry_point(dist, group, name)
except ImportError:
pass
raise BadPinFactory('Unable to locate any default pin factory!')
else:
for factory in pkg_resources.iter_entry_points(group, name):
return factory.load()
raise BadPinFactory('Unable to locate pin factory "%s"' % name)
pin_factory = _default_pin_factory()
_PINS = set()
@@ -263,14 +276,15 @@ class CompositeDevice(Device):
"""
def __init__(self, *args, **kwargs):
self._all = ()
self._named = {}
self._named = frozendict({})
self._namedtuple = None
self._order = kwargs.pop('_order', None)
if self._order is None:
self._order = sorted(kwargs.keys())
else:
for missing_name in set(kwargs.keys()) - set(self._order):
raise CompositeDeviceBadOrder('%s missing from _order' % missing_name)
self._order = tuple(self._order)
for missing_name in set(kwargs.keys()) - set(self._order):
raise CompositeDeviceBadOrder('%s missing from _order' % missing_name)
super(CompositeDevice, self).__init__()
for name in set(self._order) & set(dir(self)):
raise CompositeDeviceBadName('%s is a reserved name' % name)
@@ -278,7 +292,7 @@ class CompositeDevice(Device):
for dev in self._all:
if not isinstance(dev, Device):
raise CompositeDeviceBadDevice("%s doesn't inherit from Device" % dev)
self._named = kwargs
self._named = frozendict(kwargs)
self._namedtuple = namedtuple('%sValue' % self.__class__.__name__, chain(
(str(i) for i in range(len(args))), self._order),
rename=True)
@@ -286,7 +300,7 @@ class CompositeDevice(Device):
def __getattr__(self, name):
# if _named doesn't exist yet, pretend it's an empty dict
if name == '_named':
return {}
return frozendict({})
try:
return self._named[name]
except KeyError:
@@ -303,7 +317,7 @@ class CompositeDevice(Device):
self._check_open()
return "<gpiozero.%s object containing %d devices: %s and %d unnamed>" % (
self.__class__.__name__,
len(self), ','.join(self._named),
len(self), ','.join(self._order),
len(self) - len(self._named)
)
except DeviceClosed:
@@ -365,7 +379,7 @@ class GPIODevice(Device):
if pin is None:
raise GPIOPinMissing('No pin given')
if isinstance(pin, int):
pin = DefaultPin(pin)
pin = pin_factory(pin)
with _PINS_LOCK:
if pin in _PINS:
raise GPIOPinInUse(
@@ -376,9 +390,12 @@ class GPIODevice(Device):
self._active_state = True
self._inactive_state = False
def _state_to_value(self, state):
return bool(state == self._active_state)
def _read(self):
try:
return self.pin.state == self._active_state
return self._state_to_value(self.pin.state)
except (AttributeError, TypeError):
self._check_open()
raise

View File

@@ -22,6 +22,9 @@ class BadWaitTime(GPIOZeroError, ValueError):
class BadQueueLen(GPIOZeroError, ValueError):
"Error raised when non-positive queue length is specified"
class BadPinFactory(GPIOZeroError, ImportError):
"Error raised when an unknown pin factory name is specified"
class CompositeDeviceError(GPIOZeroError):
"Base class for errors specific to the CompositeDevice hierarchy"

View File

@@ -50,7 +50,7 @@ class InputDevice(GPIODevice):
def pull_up(self):
"""
If ``True``, the device uses a pull-up resistor to set the GPIO pin
"high" by default. Defaults to ``False``.
"high" by default.
"""
return self.pin.pull == 'up'
@@ -71,7 +71,7 @@ class DigitalInputDevice(EventsMixin, InputDevice):
straight forward on / off states with (reasonably) clean transitions
between the two.
:param float bouncetime:
:param float bounce_time:
Specifies the length of time (in seconds) that the component will
ignore changes in state after an initial change. This defaults to
``None`` which indicates that no bounce compensation will be performed.
@@ -162,10 +162,10 @@ class SmoothedInputDevice(EventsMixin, InputDevice):
except DeviceClosed:
return super(SmoothedInputDevice, self).__repr__()
else:
if self.partial or self._queue.full.wait(0):
if self.partial or self._queue.full.is_set():
return super(SmoothedInputDevice, self).__repr__()
else:
return "<gpiozero.%s object on pin=%d, pull_up=%s>" % (
return "<gpiozero.%s object on pin=%r, pull_up=%s>" % (
self.__class__.__name__, self.pin, self.pull_up)
@property
@@ -240,8 +240,8 @@ class Button(HoldMixin, DigitalInputDevice):
print("The button was pressed!")
:param int pin:
The GPIO pin which the button is attached to. See :doc:`notes` for
valid pin numbers.
The GPIO pin which the button is attached to. See :ref:`pin_numbering`
for valid pin numbers.
:param bool pull_up:
If ``True`` (the default), the GPIO pin will be pulled high by default.
@@ -251,16 +251,18 @@ class Button(HoldMixin, DigitalInputDevice):
:param float bounce_time:
If ``None`` (the default), no software bounce compensation will be
performed. Otherwise, this is the length in time (in seconds) that the
performed. Otherwise, this is the length of time (in seconds) that the
component will ignore changes in state after an initial change.
:param float hold_time:
The length of time (in seconds) to wait after the button is pushed,
until executing the :attr:`when_held` handler.
until executing the :attr:`when_held` handler. Defaults to ``1``.
:param bool hold_repeat:
If ``True``, the :attr:`when_held` handler will be repeatedly executed
as long as the device remains active, every *hold_time* seconds.
as long as the device remains active, every *hold_time* seconds. If
``False`` (the default) the :attr:`when_held` handler will be only be
executed once per hold.
"""
def __init__(
self, pin=None, pull_up=True, bounce_time=None,
@@ -279,7 +281,7 @@ Button.wait_for_release = Button.wait_for_inactive
class LineSensor(SmoothedInputDevice):
"""
Extends :class:`DigitalInputDevice` and represents a single pin line sensor
Extends :class:`SmoothedInputDevice` and represents a single pin line sensor
like the TCRT5000 infra-red proximity sensor found in the `CamJam #3
EduKit`_.
@@ -300,8 +302,8 @@ class LineSensor(SmoothedInputDevice):
pause()
:param int pin:
The GPIO pin which the button is attached to. See :doc:`notes` for
valid pin numbers.
The GPIO pin which the sensor is attached to. See :ref:`pin_numbering`
for valid pin numbers.
:param int queue_len:
The length of the queue used to store values read from the sensor. This
@@ -369,8 +371,8 @@ class MotionSensor(SmoothedInputDevice):
print("Motion detected!")
:param int pin:
The GPIO pin which the button is attached to. See :doc:`notes` for
valid pin numbers.
The GPIO pin which the sensor is attached to. See :ref:`pin_numbering`
for valid pin numbers.
:param int queue_len:
The length of the queue used to store values read from the sensor. This
@@ -418,7 +420,7 @@ class LightSensor(SmoothedInputDevice):
Extends :class:`SmoothedInputDevice` and represents a light dependent
resistor (LDR).
Connect one leg of the LDR to the 3V3 pin; connect one leg of a 1µf
Connect one leg of the LDR to the 3V3 pin; connect one leg of a 1µF
capacitor to a ground pin; connect the other leg of the LDR and the other
leg of the capacitor to the same GPIO pin. This class repeatedly discharges
the capacitor, then times the duration it takes to charge (which will vary
@@ -433,8 +435,8 @@ class LightSensor(SmoothedInputDevice):
print("Light detected!")
:param int pin:
The GPIO pin which the button is attached to. See :doc:`notes` for
valid pin numbers.
The GPIO pin which the sensor is attached to. See :ref:`pin_numbering`
for valid pin numbers.
:param int queue_len:
The length of the queue used to store values read from the circuit.
@@ -443,7 +445,7 @@ class LightSensor(SmoothedInputDevice):
:param float charge_time_limit:
If the capacitor in the circuit takes longer than this length of time
to charge, it is assumed to be dark. The default (0.01 seconds) is
appropriate for a 0.0f capacitor coupled with the LDR from the
appropriate for a 1µF capacitor coupled with the LDR from the
`CamJam #2 EduKit`_. You may need to adjust this value for different
valued capacitors or LDRs.
@@ -534,18 +536,18 @@ class DistanceSensor(SmoothedInputDevice):
from gpiozero import DistanceSensor
from time import sleep
sensor = DistanceSensor(18, 17)
sensor = DistanceSensor(echo=18, trigger=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.
The GPIO pin which the ECHO pin is attached to. See
:ref:`pin_numbering` for valid pin numbers.
:param int trigger:
The GPIO pin which the TRIG pin is attached to. See :doc:`notes` for
valid pin numbers.
The GPIO pin which the TRIG pin is attached to. See
:ref:`pin_numbering` for valid pin numbers.
:param int queue_len:
The length of the queue used to store values read from the sensor.
@@ -583,11 +585,19 @@ class DistanceSensor(SmoothedInputDevice):
self._max_distance = max_distance
self._trigger = GPIODevice(trigger)
self._echo = Event()
self._echo_rise = None
self._echo_fall = None
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
def callback():
if self._echo_rise is None:
self._echo_rise = time()
else:
self._echo_fall = time()
self._echo.set()
self.pin.when_changed = callback
self._queue.start()
except:
self.close()
@@ -615,7 +625,7 @@ class DistanceSensor(SmoothedInputDevice):
@max_distance.setter
def max_distance(self, value):
if not (value > 0):
if value <= 0:
raise ValueError('invalid maximum distance (must be positive)')
t = self.threshold_distance
self._max_distance = value
@@ -670,14 +680,15 @@ class DistanceSensor(SmoothedInputDevice):
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
if self._echo.wait(0.04) and self._echo_fall is not None and self._echo_rise is not None:
distance = (self._echo_fall - self._echo_rise) * self.speed_of_sound / 2.0
self._echo_fall = None
self._echo_rise = None
return min(1.0, distance / self._max_distance)
else:
# If we only saw one edge it means we missed the echo because

View File

@@ -234,7 +234,7 @@ class EventsMixin(object):
The length of time (in seconds) that the device has been active for.
When the device is inactive, this is ``None``.
"""
if self._active_event.wait(0):
if self._active_event.is_set():
return time() - self._last_changed
else:
return None
@@ -245,7 +245,7 @@ class EventsMixin(object):
The length of time (in seconds) that the device has been inactive for.
When the device is active, this is ``None``.
"""
if self._inactive_event.wait(0):
if self._inactive_event.is_set():
return time() - self._last_changed
else:
return None
@@ -434,11 +434,11 @@ class HoldThread(GPIOThread):
self.start()
def held(self, parent):
while not self.stopping.wait(0):
while not self.stopping.is_set():
if self.holding.wait(0.1):
self.holding.clear()
while not (
self.stopping.wait(0) or
self.stopping.is_set() or
parent._inactive_event.wait(parent.hold_time)
):
if parent._held_from is None:

View File

@@ -73,6 +73,81 @@ class PingServer(InternalDevice):
return True
class CPUTemperature(InternalDevice):
"""
Extends :class:`InternalDevice` to provide a device which is active when
the CPU temperature exceeds the *threshold* value.
The following example plots the CPU's temperature on an LED bar graph::
from gpiozero import LEDBarGraph, CPUTemperature
from signal import pause
# Use minimums and maximums that are closer to "normal" usage so the
# bar graph is a bit more "lively"
temp = CPUTemperature(min_temp=50, max_temp=90)
graph = LEDBarGraph(5, 6, 13, 19, 25, pwm=True)
graph.source = temp.values
pause()
:param str sensor_file:
The file from which to read the temperature. This defaults to the
sysfs file :file:`/sys/class/thermal/thermal_zone0/temp`. Whatever
file is specified is expected to contain a single line containing the
temperature in milli-degrees celsius.
:param float min_temp:
The temperature at which :attr:`value` will read 0.0. This defaults to
0.0.
:param float max_temp:
The temperature at which :attr:`value` will read 1.0. This defaults to
100.0.
:param float threshold:
The temperature above which the device will be considered "active".
This defaults to 80.0.
"""
def __init__(self, sensor_file='/sys/class/thermal/thermal_zone0/temp',
min_temp=0.0, max_temp=100.0, threshold=80.0):
self.sensor_file = sensor_file
super(CPUTemperature, self).__init__()
self.min_temp = min_temp
self.max_temp = max_temp
self.threshold = threshold
self._fire_events()
def __repr__(self):
return '<gpiozero.CPUTemperature temperature=%.2f>' % self.temperature
@property
def temperature(self):
"""
Returns the current CPU temperature in degrees celsius.
"""
with io.open(self.sensor_file, 'r') as f:
return float(f.readline().strip()) / 1000
@property
def value(self):
"""
Returns the current CPU temperature as a value between 0.0
(representing the *min_temp* value) and 1.0 (representing the
*max_temp* value). These default to 0.0 and 100.0 respectively, hence
:attr:`value` is :attr:`temperature` divided by 100 by default.
"""
temp_range = self.max_temp - self.min_temp
return (self.temperature - self.min_temp) / temp_range
@property
def is_active(self):
"""
Returns ``True`` when the CPU :attr:`temperature` exceeds the
:attr:`threshold`.
"""
return self.temperature > self.threshold
class TimeOfDay(InternalDevice):
"""
Extends :class:`InternalDevice` to provide a device which is active when

View File

@@ -43,16 +43,15 @@ class OutputDevice(SourceMixin, GPIODevice):
self.active_high = active_high
if initial_value is None:
self.pin.function = 'output'
elif initial_value:
self.pin.output_with_state(self._active_state)
else:
self.pin.output_with_state(self._inactive_state)
self.pin.output_with_state(self._value_to_state(initial_value))
def _value_to_state(self, value):
return bool(self._active_state if value else self._inactive_state)
def _write(self, value):
if not self.active_high:
value = not value
try:
self.pin.state = bool(value)
self.pin.state = self._value_to_state(value)
except AttributeError:
self._check_open()
raise
@@ -218,8 +217,8 @@ class LED(DigitalOutputDevice):
led.on()
:param int pin:
The GPIO pin which the LED is attached to. See :doc:`notes` for valid
pin numbers.
The GPIO pin which the LED is attached to. See :ref:`pin_numbering` for
valid pin numbers.
:param bool active_high:
If ``True`` (the default), the LED will operate normally with the
@@ -253,8 +252,8 @@ class Buzzer(DigitalOutputDevice):
bz.on()
:param int pin:
The GPIO pin which the buzzer is attached to. See :doc:`notes` for
valid pin numbers.
The GPIO pin which the buzzer is attached to. See :ref:`pin_numbering`
for valid pin numbers.
:param bool active_high:
If ``True`` (the default), the buzzer will operate normally with the
@@ -277,15 +276,15 @@ class PWMOutputDevice(OutputDevice):
Generic output device configured for pulse-width modulation (PWM).
:param int pin:
The GPIO pin which the device is attached to. See :doc:`notes` for
valid pin numbers.
The GPIO pin which the device is attached to. See :ref:`pin_numbering`
for valid pin numbers.
:param bool active_high:
If ``True`` (the default), the :meth:`on` method will set the GPIO to
HIGH. If ``False``, the :meth:`on` method will set the GPIO to LOW (the
:meth:`off` method always does the opposite).
:param bool initial_value:
:param float initial_value:
If ``0`` (the default), the device's duty cycle will be 0 initially.
Other values between 0 and 1 can be specified as an initial duty cycle.
Note that ``None`` cannot be specified (unlike the parent class) as
@@ -300,7 +299,7 @@ class PWMOutputDevice(OutputDevice):
self._controller = None
if not 0 <= initial_value <= 1:
raise OutputDeviceBadValue("initial_value must be between 0 and 1")
super(PWMOutputDevice, self).__init__(pin, active_high)
super(PWMOutputDevice, self).__init__(pin, active_high, initial_value=None)
try:
# XXX need a way of setting these together
self.pin.frequency = frequency
@@ -318,23 +317,16 @@ class PWMOutputDevice(OutputDevice):
pass
super(PWMOutputDevice, self).close()
def _read(self):
self._check_open()
if self.active_high:
return self.pin.state
else:
return 1 - self.pin.state
def _state_to_value(self, state):
return float(state if self.active_high else 1 - state)
def _value_to_state(self, value):
return float(value if self.active_high else 1 - value)
def _write(self, value):
if not self.active_high:
value = 1 - value
if not 0 <= value <= 1:
raise OutputDeviceBadValue("PWM value must be between 0 and 1")
try:
self.pin.state = value
except AttributeError:
self._check_open()
raise
super(PWMOutputDevice, self)._write(value)
@property
def value(self):
@@ -435,12 +427,12 @@ class PWMOutputDevice(OutputDevice):
Number of seconds to spend fading out. Defaults to 1.
:param int n:
Number of times to blink; ``None`` (the default) means forever.
Number of times to pulse; ``None`` (the default) means forever.
:param bool background:
If ``True`` (the default), 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
pulsing and return immediately. If ``False``, only return when the
pulse is finished (warning: the default value of *n* will result in
this method never returning).
"""
on_time = off_time = 0
@@ -491,7 +483,7 @@ class PWMLED(PWMOutputDevice):
an optional resistor to prevent the LED from burning out.
:param int pin:
The GPIO pin which the LED is attached to. See :doc:`notes` for
The GPIO pin which the LED is attached to. See :ref:`pin_numbering` for
valid pin numbers.
:param bool active_high:
@@ -499,7 +491,7 @@ class PWMLED(PWMOutputDevice):
HIGH. If ``False``, the :meth:`on` method will set the GPIO to LOW (the
:meth:`off` method always does the opposite).
:param bool initial_value:
:param float initial_value:
If ``0`` (the default), the LED will be off initially. Other values
between 0 and 1 can be specified as an initial brightness for the LED.
Note that ``None`` cannot be specified (unlike the parent class) as
@@ -553,18 +545,24 @@ class RGBLED(SourceMixin, Device):
Set to ``True`` (the default) for common cathode RGB LEDs. If you are
using a common anode RGB LED, set this to ``False``.
:param bool initial_value:
The initial color for the LED. Defaults to black ``(0, 0, 0)``.
:param tuple initial_value:
The initial color for the RGB LED. Defaults to black ``(0, 0, 0)``.
:param bool pwm:
If ``True`` (the default), construct :class:`PWMLED` instances for
each component of the RGBLED. If ``False``, construct regular
:class:`LED` instances, which prevents smooth color graduations.
"""
def __init__(
self, red=None, green=None, blue=None, active_high=True,
initial_value=(0, 0, 0)):
initial_value=(0, 0, 0), pwm=True):
self._leds = ()
self._blink_thread = None
if not all([red, green, blue]):
if not all(p is not None for p in [red, green, blue]):
raise GPIOPinMissing('red, green, and blue pins must be provided')
LEDClass = PWMLED if pwm else LED
super(RGBLED, self).__init__()
self._leds = tuple(PWMLED(pin, active_high) for pin in (red, green, blue))
self._leds = tuple(LEDClass(pin, active_high) for pin in (red, green, blue))
self.value = initial_value
red = _led_property(0)
@@ -581,13 +579,14 @@ class RGBLED(SourceMixin, Device):
@property
def closed(self):
return bool(self._leds)
return len(self._leds) == 0
@property
def value(self):
"""
Represents the color of the LED as an RGB 3-tuple of ``(red, green,
blue)`` where each value is between 0 and 1.
blue)`` where each value is between 0 and 1 if ``pwm`` was ``True``
when the class was constructed (and only 0 or 1 if not).
For example, purple would be ``(1, 0, 1)`` and yellow would be ``(1, 1,
0)``, while orange would be ``(1, 0.5, 0)``.
@@ -596,6 +595,12 @@ class RGBLED(SourceMixin, Device):
@value.setter
def value(self, value):
for component in value:
if not 0 <= component <= 1:
raise OutputDeviceBadValue('each RGB color component must be between 0 and 1')
if isinstance(self._leds[0], LED):
if component not in (0, 1):
raise OutputDeviceBadValue('each RGB color component must be 0 or 1 with non-PWM RGBLEDs')
self._stop_blink()
self.red, self.green, self.blue = value
@@ -647,10 +652,14 @@ class RGBLED(SourceMixin, Device):
Number of seconds off. Defaults to 1 second.
:param float fade_in_time:
Number of seconds to spend fading in. Defaults to 0.
Number of seconds to spend fading in. Defaults to 0. Must be 0 if
``pwm`` was ``False`` when the class was constructed
(:exc:`ValueError` will be raised if not).
:param float fade_out_time:
Number of seconds to spend fading out. Defaults to 0.
Number of seconds to spend fading out. Defaults to 0. Must be 0 if
``pwm`` was ``False`` when the class was constructed
(:exc:`ValueError` will be raised if not).
:param tuple on_color:
The color to use when the LED is "on". Defaults to white.
@@ -667,16 +676,57 @@ class RGBLED(SourceMixin, Device):
blink is finished (warning: the default value of *n* will result in
this method never returning).
"""
if isinstance(self._leds[0], LED):
if fade_in_time:
raise ValueError('fade_in_time must be 0 with non-PWM RGBLEDs')
if fade_out_time:
raise ValueError('fade_out_time must be 0 with non-PWM RGBLEDs')
self._stop_blink()
self._blink_thread = GPIOThread(
target=self._blink_device,
args=(on_time, off_time, fade_in_time, fade_out_time, on_color, off_color, n)
args=(
on_time, off_time, fade_in_time, fade_out_time,
on_color, off_color, n
)
)
self._blink_thread.start()
if not background:
self._blink_thread.join()
self._blink_thread = None
def pulse(
self, fade_in_time=1, fade_out_time=1,
on_color=(1, 1, 1), off_color=(0, 0, 0), n=None, background=True):
"""
Make the device fade in and out repeatedly.
:param float fade_in_time:
Number of seconds to spend fading in. Defaults to 1.
:param float fade_out_time:
Number of seconds to spend fading out. Defaults to 1.
:param tuple on_color:
The color to use when the LED is "on". Defaults to white.
:param tuple off_color:
The color to use when the LED is "off". Defaults to black.
:param int n:
Number of times to pulse; ``None`` (the default) means forever.
:param bool background:
If ``True`` (the default), start a background thread to continue
pulsing and return immediately. If ``False``, only return when the
pulse is finished (warning: the default value of *n* will result in
this method never returning).
"""
on_time = off_time = 0
self.blink(
on_time, off_time, fade_in_time, fade_out_time,
on_color, off_color, n, background
)
def _stop_blink(self, led=None):
# If this is called with a single led, we stop all blinking anyway
if self._blink_thread:
@@ -746,22 +796,31 @@ class Motor(SourceMixin, CompositeDevice):
:param int backward:
The GPIO pin that the backward input of the motor driver chip is
connected to.
:param bool pwm:
If ``True`` (the default), construct :class:`PWMOutputDevice`
instances for the motor controller pins, allowing both direction and
variable speed control. If ``False``, construct
:class:`DigitalOutputDevice` instances, allowing only direction
control.
"""
def __init__(self, forward=None, backward=None):
if not all([forward, backward]):
def __init__(self, forward=None, backward=None, pwm=True):
if not all(p is not None for p in [forward, backward]):
raise GPIOPinMissing(
'forward and backward pins must be provided'
)
PinClass = PWMOutputDevice if pwm else DigitalOutputDevice
super(Motor, self).__init__(
forward_device=PWMOutputDevice(forward),
backward_device=PWMOutputDevice(backward),
forward_device=PinClass(forward),
backward_device=PinClass(backward),
_order=('forward_device', 'backward_device'))
@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).
(full speed backward) and 1 (full speed forward), with 0 representing
stopped.
"""
return self.forward_device.value - self.backward_device.value
@@ -770,9 +829,15 @@ class Motor(SourceMixin, CompositeDevice):
if not -1 <= value <= 1:
raise OutputDeviceBadValue("Motor value must be between -1 and 1")
if value > 0:
self.forward(value)
try:
self.forward(value)
except ValueError as e:
raise OutputDeviceBadValue(e)
elif value < 0:
self.backward(-value)
try:
self.backward(-value)
except ValueError as e:
raise OutputDeviceBadValue(e)
else:
self.stop()
@@ -790,8 +855,14 @@ class Motor(SourceMixin, CompositeDevice):
:param float speed:
The speed at which the motor should turn. Can be any value between
0 (stopped) and the default 1 (maximum speed).
0 (stopped) and the default 1 (maximum speed) if ``pwm`` was
``True`` when the class was constructed (and only 0 or 1 if not).
"""
if not 0 <= speed <= 1:
raise ValueError('forward speed must be between 0 and 1')
if isinstance(self.forward_device, DigitalOutputDevice):
if speed not in (0, 1):
raise ValueError('forward speed must be 0 or 1 with non-PWM Motors')
self.backward_device.off()
self.forward_device.value = speed
@@ -801,8 +872,14 @@ class Motor(SourceMixin, CompositeDevice):
:param float speed:
The speed at which the motor should turn. Can be any value between
0 (stopped) and the default 1 (maximum speed).
0 (stopped) and the default 1 (maximum speed) if ``pwm`` was
``True`` when the class was constructed (and only 0 or 1 if not).
"""
if not 0 <= speed <= 1:
raise ValueError('backward speed must be between 0 and 1')
if isinstance(self.backward_device, DigitalOutputDevice):
if speed not in (0, 1):
raise ValueError('backward speed must be 0 or 1 with non-PWM Motors')
self.forward_device.off()
self.backward_device.value = speed
@@ -820,3 +897,314 @@ class Motor(SourceMixin, CompositeDevice):
"""
self.forward_device.off()
self.backward_device.off()
class Servo(SourceMixin, CompositeDevice):
"""
Extends :class:`CompositeDevice` and represents a PWM-controlled servo
motor connected to a GPIO pin.
Connect a power source (e.g. a battery pack or the 5V pin) to the power
cable of the servo (this is typically colored red); connect the ground
cable of the servo (typically colored black or brown) to the negative of
your battery pack, or a GND pin; connect the final cable (typically colored
white or orange) to the GPIO pin you wish to use for controlling the servo.
The following code will make the servo move between its minimum, maximum,
and mid-point positions with a pause between each::
from gpiozero import Servo
from time import sleep
servo = Servo(17)
while True:
servo.min()
sleep(1)
servo.mid()
sleep(1)
servo.max()
sleep(1)
:param int pin:
The GPIO pin which the device is attached to. See :ref:`pin_numbering`
for valid pin numbers.
:param float initial_value:
If ``0`` (the default), the device's mid-point will be set
initially. Other values between -1 and +1 can be specified as an
initial position. ``None`` means to start the servo un-controlled (see
:attr:`value`).
:param float min_pulse_width:
The pulse width corresponding to the servo's minimum position. This
defaults to 1ms.
:param float max_pulse_width:
The pulse width corresponding to the servo's maximum position. This
defaults to 2ms.
:param float frame_width:
The length of time between servo control pulses measured in seconds.
This defaults to 20ms which is a common value for servos.
"""
def __init__(
self, pin=None, initial_value=0.0,
min_pulse_width=1/1000, max_pulse_width=2/1000,
frame_width=20/1000):
if min_pulse_width >= max_pulse_width:
raise ValueError('min_pulse_width must be less than max_pulse_width')
if max_pulse_width >= frame_width:
raise ValueError('max_pulse_width must be less than frame_width')
self._frame_width = frame_width
self._min_dc = min_pulse_width / frame_width
self._dc_range = (max_pulse_width - min_pulse_width) / frame_width
self._min_value = -1
self._value_range = 2
super(Servo, self).__init__(
pwm_device=PWMOutputDevice(pin, frequency=int(1 / frame_width)))
try:
self.value = initial_value
except:
self.close()
raise
@property
def frame_width(self):
"""
The time between control pulses, measured in seconds.
"""
return self._frame_width
@property
def min_pulse_width(self):
"""
The control pulse width corresponding to the servo's minimum position,
measured in seconds.
"""
return self._min_dc * self.frame_width
@property
def max_pulse_width(self):
"""
The control pulse width corresponding to the servo's maximum position,
measured in seconds.
"""
return (self._dc_range * self.frame_width) + self.min_pulse_width
@property
def pulse_width(self):
"""
Returns the current pulse width controlling the servo.
"""
if self.pwm_device.pin.frequency is None:
return None
else:
return self.pwm_device.pin.state * self.frame_width
def min(self):
"""
Set the servo to its minimum position.
"""
self.value = -1
def mid(self):
"""
Set the servo to its mid-point position.
"""
self.value = 0
def max(self):
"""
Set the servo to its maximum position.
"""
self.value = 1
def detach(self):
"""
Temporarily disable control of the servo. This is equivalent to
setting :attr:`value` to ``None``.
"""
self.value = None
def _get_value(self):
if self.pwm_device.pin.frequency is None:
return None
else:
return (
((self.pwm_device.pin.state - self._min_dc) / self._dc_range) *
self._value_range + self._min_value)
@property
def value(self):
"""
Represents the position of the servo as a value between -1 (the minimum
position) and +1 (the maximum position). This can also be the special
value ``None`` indicating that the servo is currently "uncontrolled",
i.e. that no control signal is being sent. Typically this means the
servo's position remains unchanged, but that it can be moved by hand.
"""
result = self._get_value()
if result is None:
return result
else:
# NOTE: This round() only exists to ensure we don't confuse people
# by returning 2.220446049250313e-16 as the default initial value
# instead of 0. The reason _get_value and _set_value are split
# out is for descendents that require the un-rounded values for
# accuracy
return round(result, 14)
@value.setter
def value(self, value):
if value is None:
self.pwm_device.pin.frequency = None
elif -1 <= value <= 1:
self.pwm_device.pin.frequency = int(1 / self.frame_width)
self.pwm_device.pin.state = (
self._min_dc + self._dc_range *
((value - self._min_value) / self._value_range)
)
else:
raise OutputDeviceBadValue(
"Servo value must be between -1 and 1, or None")
@property
def is_active(self):
return self.value is not None
class AngularServo(Servo):
"""
Extends :class:`Servo` and represents a rotational PWM-controlled servo
motor which can be set to particular angles (assuming valid minimum and
maximum angles are provided to the constructor).
Connect a power source (e.g. a battery pack or the 5V pin) to the power
cable of the servo (this is typically colored red); connect the ground
cable of the servo (typically colored black or brown) to the negative of
your battery pack, or a GND pin; connect the final cable (typically colored
white or orange) to the GPIO pin you wish to use for controlling the servo.
Next, calibrate the angles that the servo can rotate to. In an interactive
Python session, construct a :class:`Servo` instance. The servo should move
to its mid-point by default. Set the servo to its minimum value, and
measure the angle from the mid-point. Set the servo to its maximum value,
and again measure the angle::
>>> from gpiozero import Servo
>>> s = Servo(17)
>>> s.min() # measure the angle
>>> s.max() # measure the angle
You should now be able to construct an :class:`AngularServo` instance
with the correct bounds::
>>> from gpiozero import AngularServo
>>> s = AngularServo(17, min_angle=-42, max_angle=44)
>>> s.angle = 0.0
>>> s.angle
0.0
>>> s.angle = 15
>>> s.angle
15.0
.. note::
You can set *min_angle* greater than *max_angle* if you wish to reverse
the sense of the angles (e.g. ``min_angle=45, max_angle=-45``). This
can be useful with servos that rotate in the opposite direction to your
expectations of minimum and maximum.
:param int pin:
The GPIO pin which the device is attached to. See :ref:`pin_numbering`
for valid pin numbers.
:param float initial_angle:
Sets the servo's initial angle to the specified value. The default is
0. The value specified must be between *min_angle* and *max_angle*
inclusive. ``None`` means to start the servo un-controlled (see
:attr:`value`).
:param float min_angle:
Sets the minimum angle that the servo can rotate to. This defaults to
-90, but should be set to whatever you measure from your servo during
calibration.
:param float max_angle:
Sets the maximum angle that the servo can rotate to. This defaults to
90, but should be set to whatever you measure from your servo during
calibration.
:param float min_pulse_width:
The pulse width corresponding to the servo's minimum position. This
defaults to 1ms.
:param float max_pulse_width:
The pulse width corresponding to the servo's maximum position. This
defaults to 2ms.
:param float frame_width:
The length of time between servo control pulses measured in seconds.
This defaults to 20ms which is a common value for servos.
"""
def __init__(
self, pin=None, initial_angle=0.0,
min_angle=-90, max_angle=90,
min_pulse_width=1/1000, max_pulse_width=2/1000,
frame_width=20/1000):
self._min_angle = min_angle
self._angular_range = max_angle - min_angle
initial_value = 2 * ((initial_angle - min_angle) / self._angular_range) - 1
super(AngularServo, self).__init__(
pin, initial_value, min_pulse_width, max_pulse_width, frame_width)
@property
def min_angle(self):
"""
The minimum angle that the servo will rotate to when :meth:`min` is
called.
"""
return self._min_angle
@property
def max_angle(self):
"""
The maximum angle that the servo will rotate to when :meth:`max` is
called.
"""
return self._min_angle + self._angular_range
@property
def angle(self):
"""
The position of the servo as an angle measured in degrees. This will
only be accurate if *min_angle* and *max_angle* have been set
appropriately in the constructor.
This can also be the special value ``None`` indicating that the servo
is currently "uncontrolled", i.e. that no control signal is being sent.
Typically this means the servo's position remains unchanged, but that
it can be moved by hand.
"""
result = self._get_value()
if result is None:
return None
else:
# NOTE: Why round(n, 12) here instead of 14? Angle ranges can be
# much larger than -1..1 so we need a little more rounding to
# smooth off the rough corners!
return round(
self._angular_range *
((result - self._min_value) / self._value_range) +
self._min_angle, 12)
@angle.setter
def angle(self, value):
if value is None:
self.value = None
else:
self.value = (
self._value_range *
((value - self._min_angle) / self._angular_range) +
self._min_value)

View File

@@ -6,6 +6,9 @@ from __future__ import (
)
str = type('')
import io
from .data import pi_info
from ..exc import (
PinInvalidFunction,
PinSetInput,
@@ -47,6 +50,7 @@ class Pin(object):
* :meth:`_set_edges`
* :meth:`_get_when_changed`
* :meth:`_set_when_changed`
* :meth:`pi_info`
* :meth:`output_with_state`
* :meth:`input_with_pull`
@@ -243,3 +247,48 @@ class Pin(object):
property will raise :exc:`PinEdgeDetectUnsupported`.
""")
@classmethod
def pi_info(cls):
"""
Returns a :class:`PiBoardInfo` instance representing the Pi that
instances of this pin class will be attached to.
If the pins represented by this class are not *directly* attached to a
Pi (e.g. the pin is attached to a board attached to the Pi, or the pins
are not on a Pi at all), this may return ``None``.
"""
return None
class LocalPin(Pin):
"""
Abstract base class representing pins attached locally to a Pi. This forms
the base class for local-only pin interfaces (:class:`RPiGPIOPin`,
:class:`RPIOPin`, and :class:`NativePin`).
"""
_PI_REVISION = None
@classmethod
def pi_info(cls):
"""
Returns a :class:`PiBoardInfo` instance representing the local Pi.
The Pi's revision is determined by reading :file:`/proc/cpuinfo`. If
no valid revision is found, returns ``None``.
"""
# Cache the result as we can reasonably assume it won't change during
# runtime (this is LocalPin after all; descendents that deal with
# remote Pis should inherit from Pin instead)
if cls._PI_REVISION is None:
with io.open('/proc/cpuinfo', 'r') as f:
for line in f:
if line.startswith('Revision'):
revision = line.split(':')[1].strip().lower()
overvolted = revision.startswith('100')
if overvolted:
revision = revision[-4:]
cls._PI_REVISION = revision
break
if cls._PI_REVISION is None:
return None # something weird going on
return pi_info(cls._PI_REVISION)

View File

@@ -243,29 +243,29 @@ CM_SODIMM = {
PI_REVISIONS = {
# rev model pcb_rev released soc manufacturer ram storage usb eth wifi bt csi dsi headers
'beta': ('B', '?', '2012Q1', 'BCM2835', '?', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ),
'0002': ('B', '1.0', '2012Q1', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ),
'0003': ('B', '1.0', '2012Q3', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ),
'0004': ('B', '2.0', '2012Q3', 'BCM2835', 'Sony', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
'0005': ('B', '2.0', '2012Q4', 'BCM2835', 'Qisda', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
'0006': ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
'0007': ('A', '2.0', '2013Q1', 'BCM2835', 'Egoman', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
'0008': ('A', '2.0', '2013Q1', 'BCM2835', 'Sony', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
'0009': ('A', '2.0', '2013Q1', 'BCM2835', 'Qisda', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
'000d': ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
'000e': ('B', '2.0', '2012Q4', 'BCM2835', 'Sony', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
'000f': ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
'0010': ('B+', '1.2', '2014Q3', 'BCM2835', 'Sony', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ),
'0011': ('CM', '1.2', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 0, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ),
'0012': ('A+', '1.2', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ),
'0013': ('B+', '1.2', '2015Q1', 'BCM2835', 'Egoman', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ),
'0014': ('CM', '1.1', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 0, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ),
'0015': ('A+', '1.1', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ),
'a01041': ('2B', '1.1', '2015Q1', 'BCM2836', 'Sony', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ),
'a21041': ('2B', '1.1', '2015Q1', 'BCM2836', 'Embest', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ),
'900092': ('Zero', '1.2', '2015Q4', 'BCM2835', 'Sony', 512, 'MicroSD', 1, 0, False, False, 0, 0, {'P1': PLUS_P1}, ),
'a02082': ('3B', '1.2', '2016Q1', 'BCM2837', 'Sony', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ),
'a22082': ('3B', '1.2', '2016Q1', 'BCM2837', 'Embest', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ),
0x2: ('B', '1.0', '2012Q1', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ),
0x3: ('B', '1.0', '2012Q3', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ),
0x4: ('B', '2.0', '2012Q3', 'BCM2835', 'Sony', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
0x5: ('B', '2.0', '2012Q4', 'BCM2835', 'Qisda', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
0x6: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
0x7: ('A', '2.0', '2013Q1', 'BCM2835', 'Egoman', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
0x8: ('A', '2.0', '2013Q1', 'BCM2835', 'Sony', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
0x9: ('A', '2.0', '2013Q1', 'BCM2835', 'Qisda', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
0xd: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
0xe: ('B', '2.0', '2012Q4', 'BCM2835', 'Sony', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
0xf: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
0x10: ('B+', '1.2', '2014Q3', 'BCM2835', 'Sony', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ),
0x11: ('CM', '1.2', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 1, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ),
0x12: ('A+', '1.2', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ),
0x13: ('B+', '1.2', '2015Q1', 'BCM2835', 'Egoman', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ),
0x14: ('CM', '1.1', '2014Q2', 'BCM2835', 'Embest', 512, 'eMMC', 1, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ),
0x15: ('A+', '1.1', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ),
0xa01041: ('2B', '1.1', '2015Q1', 'BCM2836', 'Sony', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ),
0xa21041: ('2B', '1.1', '2015Q1', 'BCM2836', 'Embest', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ),
0x900092: ('Zero', '1.2', '2015Q4', 'BCM2835', 'Sony', 512, 'MicroSD', 1, 0, False, False, 0, 0, {'P1': PLUS_P1}, ),
0xa02082: ('3B', '1.2', '2016Q1', 'BCM2837', 'Sony', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ),
0xa22082: ('3B', '1.2', '2016Q1', 'BCM2837', 'Embest', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ),
0x900093: ('Zero', '1.3', '2016Q2', 'BCM2835', 'Sony', 512, 'MicroSD', 1, 0, False, False, 1, 0, {'P1': PLUS_P1}, ),
}
@@ -324,6 +324,12 @@ class PiBoardInfo(namedtuple('PiBoardInfo', (
a tuple, it is strongly recommended that you use the following named
attributes to access the data contained within.
.. automethod:: physical_pin
.. automethod:: physical_pins
.. automethod:: pulled_up
.. attribute:: revision
A string indicating the revision of the Pi. This is unique to each
@@ -389,10 +395,6 @@ class PiBoardInfo(namedtuple('PiBoardInfo', (
.. note::
This does *not* include the micro-USB port used to power the Pi.
On the Compute Module this is listed as 0 as the compute module
itself doesn't have any physical USB headers, despite providing one
on the I/O development board and having the pins for one on the
module itself.
.. attribute:: ethernet
@@ -430,7 +432,7 @@ class PiBoardInfo(namedtuple('PiBoardInfo', (
def physical_pins(self, function):
"""
Return the physical pins supporting the specified *function* as a tuple
Return the physical pins supporting the specified *function* as tuples
of ``(header, pin_number)`` where *header* is a string specifying the
header containing the *pin_number*. Note that the return value is a
:class:`set` which is not indexable. Use :func:`physical_pin` if you
@@ -487,19 +489,6 @@ class PiBoardInfo(namedtuple('PiBoardInfo', (
return self.headers[header][number].pull_up
_PI_REVISION = None
def _get_pi_revision():
with io.open('/proc/cpuinfo', 'r') as f:
for line in f:
if line.startswith('Revision'):
revision = line.split(':')[1].strip().lower()
overvolted = revision.startswith('1000')
if overvolted:
revision = revision[4:]
return revision
raise IOError('unable to locate Pi revision in /proc/cpuinfo')
def _parse_pi_revision(revision):
# For new-style revisions the value's bit pattern is as follows:
#
@@ -512,10 +501,9 @@ def _parse_pi_revision(revision):
# CCCC - Manufacturer (0=Sony, 1=Egoman, 2=Embest)
# PPPP - Processor (0=2835, 1=2836, 2=2837)
# TTTTTTTT - Type (0=A, 1=B, 2=A+, 3=B+, 4=2B, 5=Alpha (??), 6=CM, 8=3B, 9=Zero)
# RRR - Revision (0, 1, or 2)
i = int(revision, base=16)
if not (i & 0x800000):
raise ValueError('cannot parse "%s"; this is not a new-style revision' % revision)
# RRRR - Revision (0, 1, or 2)
if not (revision & 0x800000):
raise PinUnknownPi('cannot parse "%x"; this is not a new-style revision' % revision)
try:
model = {
0: 'A',
@@ -526,15 +514,15 @@ def _parse_pi_revision(revision):
6: 'CM',
8: '3B',
9: 'Zero',
}[(i & 0xff0) >> 4]
}[(revision & 0xff0) >> 4]
if model in ('A', 'B'):
pcb_revision = {
0: '1.0', # is this right?
1: '1.0',
2: '2.0',
}[i & 0x0f]
}[revision & 0x0f]
else:
pcb_revision = '1.%d' % (i & 0x0f)
pcb_revision = '1.%d' % (revision & 0x0f)
released = {
'A': '2013Q1',
'B': '2012Q1' if pcb_revision == '1.0' else '2012Q4',
@@ -543,23 +531,23 @@ def _parse_pi_revision(revision):
'2B': '2015Q1',
'CM': '2014Q2',
'3B': '2016Q1',
'Zero': '2015Q4',
'Zero': '2015Q4' if pcb_revision == '1.0' else '2016Q2',
}[model]
soc = {
0: 'BCM2835',
1: 'BCM2836',
2: 'BCM2837',
}[(i & 0xf000) >> 12]
}[(revision & 0xf000) >> 12]
manufacturer = {
0: 'Sony',
1: 'Egoman',
2: 'Embest',
}[(i & 0xf0000) >> 16]
}[(revision & 0xf0000) >> 16]
memory = {
0: 256,
1: 512,
2: 1024,
}[(i & 0x700000) >> 20]
}[(revision & 0x700000) >> 20]
storage = {
'A': 'SD',
'B': 'SD',
@@ -585,17 +573,19 @@ def _parse_pi_revision(revision):
'3B': True,
}.get(model, False)
csi = {
'Zero': 0,
'Zero': 0 if pcb_revision == '1.0' else 1,
'CM': 2,
}.get(model, 1)
dsi = csi
dsi = {
'Zero': 0,
}.get(model, csi)
headers = {
'A': {'P1': REV2_P1, 'P5': REV2_P5},
'B': {'P1': REV2_P1, 'P5': REV2_P5} if pcb_revision == '2.0' else {'P1': REV1_P1},
'CM': {'SODIMM': CM_SODIMM},
}.get(model, {'P1': PLUS_P1})
except KeyError:
raise ValueError('unable to parse new-style revision "%s"' % revision)
raise PinUnknownPi('unable to parse new-style revision "%x"' % revision)
else:
return (
model,
@@ -625,16 +615,26 @@ def pi_info(revision=None):
or ``None`` (the default), then the library will attempt to determine
the model of Pi it is running on and return information about that.
"""
# cache the result as we can reasonably assume the revision of the Pi isn't
# going to change at runtime...
if revision is None:
global _PI_REVISION
if _PI_REVISION is None:
try:
_PI_REVISION = _get_pi_revision()
except IOError:
_PI_REVISION = 'unknown'
revision = _PI_REVISION
# NOTE: This import is declared locally for two reasons. Firstly it
# avoids a circular dependency (devices->pins->pins.data->devices).
# Secondly, pin_factory is one global which might potentially be
# re-written by a user's script at runtime hence we should re-import
# here in case it's changed since initialization
from ..devices import pin_factory
result = pin_factory.pi_info()
if result is None:
raise PinUnknownPi('The default pin_factory is not attached to a Pi')
else:
return result
else:
if isinstance(revision, bytes):
revision = revision.decode('ascii')
if isinstance(revision, str):
revision = int(revision, base=16)
else:
# be nice to people passing an int (or something numeric anyway)
revision = int(revision)
try:
(
model,
@@ -653,25 +653,22 @@ def pi_info(revision=None):
headers,
) = PI_REVISIONS[revision]
except KeyError:
try:
(
model,
pcb_revision,
released,
soc,
manufacturer,
memory,
storage,
usb,
ethernet,
wifi,
bluetooth,
csi,
dsi,
headers,
) = _parse_pi_revision(revision)
except ValueError:
raise PinUnknownPi('unknown RPi revision "%s"' % revision)
(
model,
pcb_revision,
released,
soc,
manufacturer,
memory,
storage,
usb,
ethernet,
wifi,
bluetooth,
csi,
dsi,
headers,
) = _parse_pi_revision(revision)
headers = {
header: {
number: PinInfo(number, function, pull_up)
@@ -680,7 +677,7 @@ def pi_info(revision=None):
for header, header_data in headers.items()
}
return PiBoardInfo(
revision,
'%04x' % revision,
model,
pcb_revision,
released,

View File

@@ -16,6 +16,7 @@ except ImportError:
from ..compat import isclose
from . import Pin
from .data import pi_info
from ..exc import PinSetInput, PinPWMUnsupported, PinFixedPull
@@ -32,6 +33,10 @@ class MockPin(Pin):
def clear_pins(cls):
cls._PINS.clear()
@classmethod
def pi_info(cls):
return pi_info('a21041') # Pretend we're a Pi 2B
def __new__(cls, number):
if not (0 <= number < 54):
raise ValueError('invalid pin %d specified (must be 0..53)' % number)

View File

@@ -17,7 +17,7 @@ from time import sleep
from threading import Thread, Event, Lock
from collections import Counter
from . import Pin, PINS_CLEANUP
from . import LocalPin, PINS_CLEANUP
from .data import pi_info
from ..exc import (
PinInvalidPull,
@@ -149,7 +149,7 @@ class GPIOFS(object):
f.write(str(pin).encode('ascii'))
class NativePin(Pin):
class NativePin(LocalPin):
"""
Uses a built-in pure Python implementation to interface to the Pi's GPIO
pins. This is the default pin implementation if no third-party libraries

View File

@@ -8,6 +8,7 @@ str = type('')
import warnings
import pigpio
import os
from . import Pin
from .data import pi_info
@@ -68,7 +69,7 @@ class PiGPIOPin(Pin):
.. _pigpio: http://abyz.co.uk/rpi/pigpio/
"""
_CONNECTIONS = {}
_CONNECTIONS = {} # maps (host, port) to (connection, pi_info)
_PINS = {}
GPIO_FUNCTIONS = {
@@ -98,25 +99,17 @@ class PiGPIOPin(Pin):
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()}
PI_INFO = None
def __new__(cls, number, host='localhost', port=8888):
# XXX What about remote pins? This should probably be instance
# specific rather than class specific for pigpio. Need to check how
# to query remote info though...
if cls.PI_INFO is None:
cls.PI_INFO = pi_info()
def __new__(
cls, number, host=os.getenv('PIGPIO_ADDR', 'localhost'),
port=int(os.getenv('PIGPIO_PORT', 8888))):
try:
return cls._PINS[(host, port, number)]
except KeyError:
self = super(PiGPIOPin, cls).__new__(cls)
cls.pi_info(host, port) # implicitly creates connection
self._connection, self._pi_info = cls._CONNECTIONS[(host, port)]
try:
self._connection = cls._CONNECTIONS[(host, port)]
except KeyError:
self._connection = pigpio.pi(host, port)
cls._CONNECTIONS[(host, port)] = self._connection
try:
cls.PI_INFO.physical_pin('GPIO%d' % number)
self._pi_info.physical_pin('GPIO%d' % number)
except PinNoPins:
warnings.warn(
PinNonPhysical(
@@ -124,7 +117,7 @@ class PiGPIOPin(Pin):
self._host = host
self._port = port
self._number = number
self._pull = 'up' if cls.PI_INFO.pulled_up('GPIO%d' % number) else 'floating'
self._pull = 'up' if self._pi_info.pulled_up('GPIO%d' % number) else 'floating'
self._pwm = False
self._bounce = None
self._when_changed = None
@@ -136,7 +129,6 @@ class PiGPIOPin(Pin):
raise ValueError(e)
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)
cls._PINS[(host, port, number)] = self
return self
@@ -167,7 +159,7 @@ class PiGPIOPin(Pin):
self.frequency = None
self.when_changed = None
self.function = 'input'
self.pull = 'up' if self.PI_INFO.pulled_up('GPIO%d' % self.number) else 'floating'
self.pull = 'up' if self._pi_info.pulled_up('GPIO%d' % self.number) else 'floating'
def _get_function(self):
return self.GPIO_FUNCTION_NAMES[self._connection.get_mode(self._number)]
@@ -182,14 +174,19 @@ class PiGPIOPin(Pin):
def _get_state(self):
if self._pwm:
return self._connection.get_PWM_dutycycle(self._number) / 255
return (
self._connection.get_PWM_dutycycle(self._number) /
self._connection.get_PWM_range(self._number)
)
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))
value = int(value * self._connection.get_PWM_range(self._number))
if value != self._connection.get_PWM_dutycycle(self._number):
self._connection.set_PWM_dutycycle(self._number, value)
except pigpio.error:
raise PinInvalidState('invalid state "%s" for pin %r' % (value, self))
elif self.function == 'input':
@@ -204,7 +201,7 @@ class PiGPIOPin(Pin):
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.PI_INFO.pulled_up('GPIO%d' % self._number):
if value != 'up' and self._pi_info.pulled_up('GPIO%d' % self._number):
raise PinFixedPull('%r has a physical pull-up resistor' % self)
try:
self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[value])
@@ -220,12 +217,15 @@ class PiGPIOPin(Pin):
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_range(self._number, 10000)
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)
if value != self._connection.get_PWM_frequency(self._number):
self._connection.set_PWM_frequency(self._number, value)
self._connection.set_PWM_range(self._number, 10000)
elif self._pwm and value is None:
self._connection.set_PWM_dutycycle(self._number, 0)
self._connection.write(self._number, 0)
self._pwm = False
def _get_bounce(self):
@@ -263,3 +263,16 @@ class PiGPIOPin(Pin):
self._number, self._edges,
lambda gpio, level, tick: value())
@classmethod
def pi_info(
cls, host=os.getenv('PIGPIO_ADDR', 'localhost'),
port=int(os.getenv('PIGPIO_PORT', 8888))):
try:
connection, info = cls._CONNECTIONS[(host, port)]
except KeyError:
connection = pigpio.pi(host, port)
revision = '%04x' % connection.get_hardware_revision()
info = pi_info(revision)
cls._CONNECTIONS[(host, port)] = (connection, info)
return info

View File

@@ -9,7 +9,7 @@ str = type('')
import warnings
from RPi import GPIO
from . import Pin
from . import LocalPin
from .data import pi_info
from ..exc import (
PinInvalidFunction,
@@ -24,7 +24,7 @@ from ..exc import (
)
class RPiGPIOPin(Pin):
class RPiGPIOPin(LocalPin):
"""
Uses the `RPi.GPIO`_ library to interface to the Pi's GPIO pins. This is
the default pin implementation if the RPi.GPIO library is installed.

View File

@@ -12,7 +12,7 @@ import RPIO
import RPIO.PWM
from RPIO.Exceptions import InvalidChannelException
from . import Pin, PINS_CLEANUP
from . import LocalPin, PINS_CLEANUP
from .data import pi_info
from ..exc import (
PinInvalidFunction,
@@ -27,7 +27,7 @@ from ..exc import (
)
class RPIOPin(Pin):
class RPIOPin(LocalPin):
"""
Uses the `RPIO`_ library to interface to the Pi's GPIO pins. This is
the default pin implementation if the RPi.GPIO library is not installed,

View File

@@ -163,7 +163,7 @@ class SPISoftwareBus(SharedMixin, Device):
return self.lock is None
@classmethod
def _shared_key(self, clock_pin, mosi_pin, miso_pin):
def _shared_key(cls, clock_pin, mosi_pin, miso_pin):
return (clock_pin, mosi_pin, miso_pin)
def read(self, n):
@@ -396,24 +396,30 @@ def SPI(**spi_args):
raise SPIBadArgs(
'unrecognized keyword argument %s' % kwargs.popitem()[0])
if all((
SpiDev is not None,
spi_args['clock_pin'] == 11,
spi_args['mosi_pin'] == 10,
spi_args['miso_pin'] == 9,
spi_args['select_pin'] in (7, 8),
)):
try:
if shared:
return SharedSPIHardwareInterface(
port=0, device={8: 0, 7: 1}[spi_args['select_pin']])
else:
return SPIHardwareInterface(
port=0, device={8: 0, 7: 1}[spi_args['select_pin']])
except Exception as e:
if SpiDev is None:
warnings.warn(
SPISoftwareFallback(
'failed to initialize hardware SPI, falling back to '
'software (error was: %s)' % str(e)))
'failed to import spidev, falling back to software SPI'))
else:
try:
hardware_spi_args = {
port: 0,
device: {8: 0, 7: 1}[spi_args['select_pin']],
}
if shared:
return SharedSPIHardwareInterface(**hardware_spi_args)
else:
return SPIHardwareInterface(**hardware_spi_args)
except Exception as e:
warnings.warn(
SPISoftwareFallback(
'failed to initialize hardware SPI, falling back to '
'software (error was: %s)' % str(e)))
if shared:
return SharedSPISoftwareInterface(**spi_args)
else:

View File

@@ -112,7 +112,6 @@ class MCP3xxx(AnalogInputDevice):
def __init__(self, channel=0, bits=10, differential=False, **spi_args):
self._channel = channel
self._bits = bits
self._differential = bool(differential)
super(MCP3xxx, self).__init__(bits, **spi_args)

View File

@@ -41,10 +41,13 @@ def negated(values):
yield not v
def inverted(values):
def inverted(values, input_min=0, input_max=1):
"""
Returns the inversion of the supplied values (1 becomes 0, 0 becomes 1,
0.1 becomes 0.9, etc.). For example::
Returns the inversion of the supplied values (*input_min* becomes
*input_max*, *input_max* becomes *input_min*, `input_min + 0.1` becomes
`input_max - 0.1`, etc.). All items in *values* are assumed to be between
*input_min* and *input_max* (which default to 0 and 1 respectively), and
the output will be in the same range. For example::
from gpiozero import MCP3008, PWMLED
from gpiozero.tools import inverted
@@ -55,8 +58,10 @@ def inverted(values):
led.source = inverted(pot.values)
pause()
"""
if input_min >= input_max:
raise ValueError('input_min must be smaller than input_max')
for v in values:
yield 1 - v
yield input_min + input_max - v
def scaled(values, output_min, output_max, input_min=0, input_max=1):
@@ -82,6 +87,8 @@ def scaled(values, output_min, output_max, input_min=0, input_max=1):
*input_max* (inclusive) then the function will not produce values that
lie within *output_min* to *output_max* (inclusive).
"""
if input_min >= input_max:
raise ValueError('input_min must be smaller than input_max')
input_size = input_max - input_min
output_size = output_max - output_min
for v in values:
@@ -104,6 +111,8 @@ def clamped(values, output_min=0, output_max=1):
led.source = clamped(pot.values, 0.5, 1.0)
pause()
"""
if output_min >= output_max:
raise ValueError('output_min must be smaller than output_max')
for v in values:
yield min(max(v, output_min), output_max)
@@ -128,11 +137,11 @@ def absoluted(values):
yield abs(v)
def quantized(values, steps, output_min=0, output_max=1):
def quantized(values, steps, input_min=0, input_max=1):
"""
Returns *values* quantized to *steps* increments. All items in *values* are
assumed to be between *output_min* and *output_max* (use :func:`scaled` to
ensure this if necessary).
assumed to be between *input_min* and *input_max* (which default to 0 and
1 respectively), and the output will be in the same range.
For example, to quantize values between 0 and 1 to 5 "steps" (0.0, 0.25,
0.5, 0.75, 1.0)::
@@ -146,9 +155,72 @@ def quantized(values, steps, output_min=0, output_max=1):
led.source = quantized(pot.values, 4)
pause()
"""
output_size = output_max - output_min
for v in scaled(values, 0, 1, output_min, output_max):
yield ((int(v * steps) / steps) * output_size) + output_min
if steps < 1:
raise ValueError("steps must be 1 or larger")
if input_min >= input_max:
raise ValueError('input_min must be smaller than input_max')
input_size = input_max - input_min
for v in scaled(values, 0, 1, input_min, input_max):
yield ((int(v * steps) / steps) * input_size) + input_min
def booleanized(values, min_value, max_value, hysteresis=0):
"""
Returns True for each item in *values* between *min_value* and
*max_value*, and False otherwise. *hysteresis* can optionally be used to
add `hysteresis`_ which prevents the output value rapidly flipping when
the input value is fluctuating near the *min_value* or *max_value*
thresholds. For example, to light an LED only when a potentiometer is
between 1/4 and 3/4 of its full range::
from gpiozero import LED, MCP3008
from gpiozero.tools import booleanized
from signal import pause
led = LED(4)
pot = MCP3008(channel=0)
led.source = booleanized(pot.values, 0.25, 0.75)
pause()
.. _hysteresis: https://en.wikipedia.org/wiki/Hysteresis
"""
if min_value >= max_value:
raise ValueError('min_value must be smaller than max_value')
min_value = float(min_value)
max_value = float(max_value)
if hysteresis < 0:
raise ValueError("hysteresis must be 0 or larger")
else:
hysteresis = float(hysteresis)
if (max_value - min_value) <= hysteresis:
raise ValueError('The gap between min_value and max_value must be larger than hysteresis')
last_state = None
for v in values:
if v < min_value:
new_state = 'below'
elif v > max_value:
new_state = 'above'
else:
new_state = 'in'
switch = False
if last_state == None or not hysteresis:
switch = True
elif new_state == last_state:
pass
else: # new_state != last_state
if last_state == 'below' and new_state == 'in':
switch = v >= min_value + hysteresis
elif last_state == 'in' and new_state == 'below':
switch = v < min_value - hysteresis
elif last_state == 'in' and new_state == 'above':
switch = v > max_value + hysteresis
elif last_state == 'above' and new_state == 'in':
switch = v <= max_value - hysteresis
else: # above->below or below->above
switch = True
if switch:
last_state = new_state
yield last_state == 'in'
def all_values(*values):
@@ -218,6 +290,54 @@ def averaged(*values):
yield mean(v)
def summed(*values):
"""
Returns the sum of all supplied values. One or more *values* can be
specified. For example, to light a :class:`PWMLED` as the (scaled) sum of
several potentiometers connected to an :class:`MCP3008` ADC::
from gpiozero import MCP3008, PWMLED
from gpiozero.tools import summed, scaled
from signal import pause
pot1 = MCP3008(channel=0)
pot2 = MCP3008(channel=1)
pot3 = MCP3008(channel=2)
led = PWMLED(4)
led.source = scaled(summed(pot1.values, pot2.values, pot3.values), 0, 1, 0, 3)
pause()
"""
for v in zip(*values):
yield sum(v)
def multiplied(*values):
"""
Returns the product of all supplied values. One or more *values* can be
specified. For example, to light a :class:`PWMLED` as the product (i.e.
multiplication) of several potentiometers connected to an :class:`MCP3008`
ADC::
from gpiozero import MCP3008, PWMLED
from gpiozero.tools import multiplied
from signal import pause
pot1 = MCP3008(channel=0)
pot2 = MCP3008(channel=1)
pot3 = MCP3008(channel=2)
led = PWMLED(4)
led.source = multiplied(pot1.values, pot2.values, pot3.values)
pause()
"""
def _product(it):
p = 1
for n in it:
p *= n
return p
for v in zip(*values):
yield _product(v)
def queued(values, qsize):
"""
Queues up readings from *values* (the number of readings queued is
@@ -236,6 +356,8 @@ def queued(values, qsize):
leds[4].source = btn.values
pause()
"""
if qsize < 1:
raise ValueError("qsize must be 1 or larger")
q = []
it = iter(values)
for i in range(qsize):
@@ -248,10 +370,41 @@ def queued(values, qsize):
break
def smoothed(values, qsize, average=mean):
"""
Queues up readings from *values* (the number of readings queued is
determined by *qsize*) and begins yielding the *average* of the last
*qsize* values when the queue is full. The larger the *qsize*, the more the
values are smoothed. For example, to smooth the analog values read from an
ADC::
from gpiozero import MCP3008
from gpiozero.tools import smoothed
with MCP3008(channel=0) as adc:
for value in smoothed(adc.values, 5):
print value
"""
if qsize < 1:
raise ValueError("qsize must be 1 or larger")
q = []
it = iter(values)
for i in range(qsize):
q.append(next(it))
for i in cycle(range(qsize)):
yield average(q)
try:
q[i] = next(it)
except StopIteration:
break
def pre_delayed(values, delay):
"""
Waits for *delay* seconds before returning each item from *values*.
"""
if delay < 0:
raise ValueError("delay must be 0 or larger")
for v in values:
sleep(delay)
yield v
@@ -261,11 +414,78 @@ def post_delayed(values, delay):
"""
Waits for *delay* seconds after returning each item from *values*.
"""
if delay < 0:
raise ValueError("delay must be 0 or larger")
for v in values:
yield v
sleep(delay)
def pre_periodic_filtered(values, block, repeat_after):
"""
Blocks the first *block* items from *values*, repeating the block after
every *repeat_after* items, if *repeat_after* is non-zero. For example, to
discard the first 50 values read from an ADC::
from gpiozero import MCP3008
from gpiozero.tools import pre_periodic_filtered
with MCP3008(channel=0) as adc:
for value in pre_periodic_filtered(adc.values, 50, 0):
print value
Or to only display every even item read from an ADC::
from gpiozero import MCP3008
from gpiozero.tools import pre_periodic_filtered
with MCP3008(channel=0) as adc:
for value in pre_periodic_filtered(adc.values, 1, 1):
print value
"""
if block < 1:
raise ValueError("block must be 1 or larger")
if repeat_after < 0:
raise ValueError("repeat_after must be 0 or larger")
it = iter(values)
if repeat_after == 0:
for _ in range(block):
next(it)
while True:
yield next(it)
else:
while True:
for _ in range(block):
next(it)
for _ in range(repeat_after):
yield next(it)
def post_periodic_filtered(values, repeat_after, block):
"""
After every *repeat_after* items, blocks the next *block* items from
*values*. Note that unlike :func:`pre_periodic_filtered`, *repeat_after*
can't be 0. For example, to block every tenth item read from an ADC::
from gpiozero import MCP3008
from gpiozero.tools import post_periodic_filtered
with MCP3008(channel=0) as adc:
for value in post_periodic_filtered(adc.values, 9, 1):
print value
"""
if repeat_after < 1:
raise ValueError("repeat_after must be 1 or larger")
if block < 1:
raise ValueError("block must be 1 or larger")
it = iter(values)
while True:
for _ in range(repeat_after):
yield next(it)
for _ in range(block):
next(it)
def random_values():
"""
Provides an infinite source of random values between 0 and 1. For example,
@@ -298,7 +518,7 @@ def sin_values(period=360):
red = PWMLED(2)
blue = PWMLED(3)
red.source_delay = 0.01
blue.source_delay = 0.01
blue.source_delay = red.source_delay
red.source = scaled(sin_values(100), 0, 1, -1, 1)
blue.source = inverted(red.values)
pause()
@@ -323,7 +543,7 @@ def cos_values(period=360):
red = PWMLED(2)
blue = PWMLED(3)
red.source_delay = 0.01
blue.source_delay = 0.01
blue.source_delay = red.source_delay
red.source = scaled(cos_values(100), 0, 1, -1, 1)
blue.source = inverted(red.values)
pause()

View File

@@ -61,6 +61,14 @@ if sys.version_info[:2] == (3, 2):
__extra_requires__['test'][1] = 'coverage<4.0dev'
__entry_points__ = {
'gpiozero_pin_factories': [
'PiGPIOPin = gpiozero.pins.pigpiod:PiGPIOPin',
'RPiGPIOPin = gpiozero.pins.rpigpio:RPiGPIOPin',
'RPIOPin = gpiozero.pins.rpio:RPIOPin',
'NativePin = gpiozero.pins.native:NativePin',
'MockPin = gpiozero.pins.mock:MockPin',
'MockPWMPin = gpiozero.pins.mock:MockPWMPin',
],
}

View File

@@ -18,10 +18,10 @@ from gpiozero import *
def setup_function(function):
import gpiozero.devices
# dirty, but it does the job
if function.__name__ in ('test_robot', 'test_ryanteck_robot', 'test_camjam_kit_robot'):
gpiozero.devices.DefaultPin = MockPWMPin
if function.__name__ in ('test_robot', 'test_ryanteck_robot', 'test_camjam_kit_robot', 'test_led_borg', 'test_snow_pi_initial_value_pwm'):
gpiozero.devices.pin_factory = MockPWMPin
else:
gpiozero.devices.DefaultPin = MockPin
gpiozero.devices.pin_factory = MockPin
def teardown_function(function):
MockPin.clear_pins()
@@ -71,6 +71,10 @@ def test_led_board_on_off():
assert isinstance(board[0], LED)
assert isinstance(board[1], LED)
assert isinstance(board[2], LED)
assert board.active_high
assert board[0].active_high
assert board[1].active_high
assert board[2].active_high
board.on()
assert all((pin1.state, pin2.state, pin3.state))
board.off()
@@ -85,6 +89,121 @@ def test_led_board_on_off():
assert not pin1.state
assert pin2.state
assert pin3.state
board.toggle(0,1)
assert board.value == (1, 0, 1)
assert pin1.state
assert not pin2.state
assert pin3.state
board.off(2)
assert board.value == (1, 0, 0)
assert pin1.state
assert not pin2.state
assert not pin3.state
board.on(1)
assert board.value == (1, 1, 0)
assert pin1.state
assert pin2.state
assert not pin3.state
board.off(0,1)
assert board.value == (0, 0, 0)
assert not pin1.state
assert not pin2.state
assert not pin3.state
board.on(1,2)
assert board.value == (0, 1, 1)
assert not pin1.state
assert pin2.state
assert pin3.state
board.toggle(0)
assert board.value == (1, 1, 1)
assert pin1.state
assert pin2.state
assert pin3.state
def test_led_board_active_low():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
with LEDBoard(pin1, pin2, foo=pin3, active_high=False) as board:
assert not board.active_high
assert not board[0].active_high
assert not board[1].active_high
assert not board[2].active_high
board.on()
assert not any ((pin1.state, pin2.state, pin3.state))
board.off()
assert all((pin1.state, pin2.state, pin3.state))
board[0].on()
assert board.value == (1, 0, 0)
assert not pin1.state
assert pin2.state
assert pin3.state
board.toggle()
assert board.value == (0, 1, 1)
assert pin1.state
assert not pin2.state
assert not pin3.state
def test_led_board_value():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
with LEDBoard(pin1, pin2, foo=pin3) as board:
assert board.value == (0, 0, 0)
board.value = (0, 1, 0)
assert board.value == (0, 1, 0)
board.value = (1, 0, 1)
assert board.value == (1, 0, 1)
def test_led_board_pwm_value():
pin1 = MockPWMPin(2)
pin2 = MockPWMPin(3)
pin3 = MockPWMPin(4)
with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board:
assert board.value == (0, 0, 0)
board.value = (0, 1, 0)
assert board.value == (0, 1, 0)
board.value = (0.5, 0, 0.75)
assert board.value == (0.5, 0, 0.75)
def test_led_board_pwm_bad_value():
pin1 = MockPWMPin(2)
pin2 = MockPWMPin(3)
pin3 = MockPWMPin(4)
with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board:
with pytest.raises(ValueError):
board.value = (-1, 0, 0)
with pytest.raises(ValueError):
board.value = (0, 2, 0)
def test_led_board_initial_value():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
with LEDBoard(pin1, pin2, foo=pin3, initial_value=0) as board:
assert board.value == (0, 0, 0)
with LEDBoard(pin1, pin2, foo=pin3, initial_value=1) as board:
assert board.value == (1, 1, 1)
def test_led_board_pwm_initial_value():
pin1 = MockPWMPin(2)
pin2 = MockPWMPin(3)
pin3 = MockPWMPin(4)
with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=0) as board:
assert board.value == (0, 0, 0)
with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=1) as board:
assert board.value == (1, 1, 1)
with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=0.5) as board:
assert board.value == (0.5, 0.5, 0.5)
def test_led_board_pwm_bad_initial_value():
pin1 = MockPWMPin(2)
pin2 = MockPWMPin(3)
pin3 = MockPWMPin(4)
with pytest.raises(ValueError):
LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=-1)
with pytest.raises(ValueError):
LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=2)
def test_led_board_nested():
pin1 = MockPin(2)
@@ -159,7 +278,7 @@ def test_led_board_blink_control():
board.blink(0.1, 0.1, n=2)
# make sure the blink thread's started
while not board._blink_leds:
sleep(0.00001)
sleep(0.00001) # pragma: no cover
board[1][0].off() # immediately take over the second LED
board._blink_thread.join() # naughty, but ensures no arbitrary waits in the test
test = [
@@ -206,7 +325,7 @@ def test_led_board_blink_control_all():
board.blink(0.1, 0.1, n=2)
# make sure the blink thread's started
while not board._blink_leds:
sleep(0.00001)
sleep(0.00001) # pragma: no cover
board[0].off() # immediately take over all LEDs
board[1][0].off()
board[1][1].off()
@@ -285,13 +404,24 @@ def test_led_bar_graph_value():
pin2 = MockPin(3)
pin3 = MockPin(4)
with LEDBarGraph(pin1, pin2, pin3) as graph:
assert isinstance(graph[0], LED)
assert isinstance(graph[1], LED)
assert isinstance(graph[2], LED)
assert graph.active_high
assert graph[0].active_high
assert graph[1].active_high
assert graph[2].active_high
graph.value = 0
assert graph.value == 0
assert not any((pin1.state, pin2.state, pin3.state))
graph.value = 1
assert graph.value == 1
assert all((pin1.state, pin2.state, pin3.state))
graph.value = 1/3
assert graph.value == 1/3
assert pin1.state and not (pin2.state or pin3.state)
graph.value = -1/3
assert graph.value == -1/3
assert pin3.state and not (pin1.state or pin2.state)
pin1.state = True
pin2.state = True
@@ -302,31 +432,102 @@ def test_led_bar_graph_value():
pin1.state = False
assert graph.value == -2/3
def test_led_bar_graph_active_low():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
with LEDBarGraph(pin1, pin2, pin3, active_high=False) as graph:
assert not graph.active_high
assert not graph[0].active_high
assert not graph[1].active_high
assert not graph[2].active_high
graph.value = 0
assert graph.value == 0
assert all((pin1.state, pin2.state, pin3.state))
graph.value = 1
assert graph.value == 1
assert not any((pin1.state, pin2.state, pin3.state))
graph.value = 1/3
assert graph.value == 1/3
assert not pin1.state and pin2.state and pin3.state
graph.value = -1/3
assert graph.value == -1/3
assert not pin3.state and pin1.state and pin2.state
def test_led_bar_graph_pwm_value():
pin1 = MockPWMPin(2)
pin2 = MockPWMPin(3)
pin3 = MockPWMPin(4)
with LEDBarGraph(pin1, pin2, pin3, pwm=True) as graph:
assert isinstance(graph[0], PWMLED)
assert isinstance(graph[1], PWMLED)
assert isinstance(graph[2], PWMLED)
graph.value = 0
assert graph.value == 0
assert not any((pin1.state, pin2.state, pin3.state))
graph.value = 1
assert graph.value == 1
assert all((pin1.state, pin2.state, pin3.state))
graph.value = 1/3
assert graph.value == 1/3
assert pin1.state and not (pin2.state or pin3.state)
graph.value = -1/3
assert graph.value == -1/3
assert pin3.state and not (pin1.state or pin2.state)
graph.value = 1/2
assert graph.value == 1/2
assert (pin1.state, pin2.state, pin3.state) == (1, 0.5, 0)
pin1.state = 0
pin3.state = 1
assert graph.value == -1/2
def test_led_bar_graph_bad_value():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
with LEDBarGraph(pin1, pin2, pin3) as graph:
with pytest.raises(ValueError):
graph.value = -2
with pytest.raises(ValueError):
graph.value = 2
def test_led_bar_graph_bad_init():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
with pytest.raises(TypeError):
LEDBarGraph(pin1, pin2, foo=pin3)
with pytest.raises(ValueError):
LEDBarGraph(pin1, pin2, pin3, initial_value=-2)
with pytest.raises(ValueError):
LEDBarGraph(pin1, pin2, pin3, initial_value=2)
def test_led_bar_graph_initial_value():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
with LEDBarGraph(pin1, pin2, pin3, initial_value=1/3) as graph:
assert graph.value == 1/3
assert pin1.state and not (pin2.state or pin3.state)
with LEDBarGraph(pin1, pin2, pin3, initial_value=-1/3) as graph:
assert graph.value == -1/3
assert pin3.state and not (pin1.state or pin2.state)
def test_led_bar_graph_pwm_initial_value():
pin1 = MockPWMPin(2)
pin2 = MockPWMPin(3)
pin3 = MockPWMPin(4)
with LEDBarGraph(pin1, pin2, pin3, pwm=True, initial_value=0.5) as graph:
assert graph.value == 0.5
assert (pin1.state, pin2.state, pin3.state) == (1, 0.5, 0)
with LEDBarGraph(pin1, pin2, pin3, pwm=True, initial_value=-0.5) as graph:
assert graph.value == -0.5
assert (pin1.state, pin2.state, pin3.state) == (0, 0.5, 1)
def test_led_borg():
pins = [MockPWMPin(n) for n in (17, 27, 22)]
with LedBorg() as board:
assert [device.pin for device in board._leds] == pins
def test_pi_liter():
pins = [MockPin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)]
@@ -347,13 +548,32 @@ def test_traffic_lights():
green_pin = MockPin(4)
with TrafficLights(red_pin, amber_pin, green_pin) as board:
board.red.on()
assert board.red.value
assert not board.amber.value
assert not board.yellow.value
assert not board.green.value
assert red_pin.state
assert not amber_pin.state
assert not green_pin.state
with TrafficLights(red=red_pin, yellow=amber_pin, green=green_pin) as board:
board.yellow.on()
assert not board.red.value
assert board.amber.value
assert board.yellow.value
assert not board.green.value
assert not red_pin.state
assert amber_pin.state
assert not green_pin.state
def test_traffic_lights_bad_init():
with pytest.raises(ValueError):
TrafficLights()
red_pin = MockPin(2)
amber_pin = MockPin(3)
green_pin = MockPin(4)
yellow_pin = MockPin(5)
with pytest.raises(ValueError):
TrafficLights(red=red_pin, amber=amber_pin, yellow=yellow_pin, green=green_pin)
def test_pi_traffic():
pins = [MockPin(n) for n in (9, 10, 11)]
@@ -365,6 +585,22 @@ def test_snow_pi():
with SnowPi() as board:
assert [device.pin for device in board.leds] == pins
def test_snow_pi_initial_value():
with SnowPi() as board:
assert all(device.pin.state == False for device in board.leds)
with SnowPi(initial_value=False) as board:
assert all(device.pin.state == False for device in board.leds)
with SnowPi(initial_value=True) as board:
assert all(device.pin.state == True for device in board.leds)
with SnowPi(initial_value=0.5) as board:
assert all(device.pin.state == True for device in board.leds)
def test_snow_pi_initial_value_pwm():
pins = [MockPWMPin(n) for n in (23, 24, 25, 17, 18, 22, 7, 8, 9)]
with SnowPi(pwm=True, initial_value=0.5) as board:
assert [device.pin for device in board.leds] == pins
assert all(device.pin.state == 0.5 for device in board.leds)
def test_traffic_lights_buzzer():
red_pin = MockPin(2)
amber_pin = MockPin(3)
@@ -400,20 +636,34 @@ def test_robot():
assert (
[device.pin for device in robot.left_motor] +
[device.pin for device in robot.right_motor]) == pins
assert robot.value == (0, 0)
robot.forward()
assert [pin.state for pin in pins] == [1, 0, 1, 0]
assert robot.value == (1, 1)
robot.backward()
assert [pin.state for pin in pins] == [0, 1, 0, 1]
assert robot.value == (-1, -1)
robot.forward(0.5)
assert [pin.state for pin in pins] == [0.5, 0, 0.5, 0]
assert robot.value == (0.5, 0.5)
robot.left()
assert [pin.state for pin in pins] == [0, 1, 1, 0]
assert robot.value == (-1, 1)
robot.right()
assert [pin.state for pin in pins] == [1, 0, 0, 1]
assert robot.value == (1, -1)
robot.reverse()
assert [pin.state for pin in pins] == [0, 1, 1, 0]
assert robot.value == (-1, 1)
robot.stop()
assert [pin.state for pin in pins] == [0, 0, 0, 0]
assert robot.value == (0, 0)
robot.value = (-1, -1)
assert robot.value == (-1, -1)
robot.value = (0.5, 1)
assert robot.value == (0.5, 1)
robot.value = (0, -0.5)
assert robot.value == (0, -0.5)
def test_ryanteck_robot():
pins = [MockPWMPin(n) for n in (17, 18, 22, 23)]
@@ -430,11 +680,15 @@ def test_energenie_bad_init():
Energenie()
with pytest.raises(ValueError):
Energenie(0)
with pytest.raises(ValueError):
Energenie(5)
def test_energenie():
pins = [MockPin(n) for n in (17, 22, 23, 27, 24, 25)]
with Energenie(1, initial_value=True) as device1, \
Energenie(2, initial_value=False) as device2:
assert repr(device1) == '<gpiozero.Energenie object on socket 1>'
assert repr(device2) == '<gpiozero.Energenie object on socket 2>'
assert device1.value
assert not device2.value
[pin.clear_states() for pin in pins]
@@ -455,4 +709,5 @@ def test_energenie():
pins[3].assert_states_and_times([(0.0, True), (0.0, True)])
pins[4].assert_states_and_times([(0.0, False)])
pins[5].assert_states_and_times([(0.0, False), (0.1, True), (0.25, False)])
device1.close()
assert repr(device1) == '<gpiozero.Energenie object closed>'

View File

@@ -120,6 +120,7 @@ def test_mean():
values = list(values)
random.shuffle(values)
assert mean(values) == result
assert mean(iter(values)) == result
def test_mean_big_data():
c = 1e9

View File

@@ -34,13 +34,25 @@ def test_input_initial_values():
assert pin.pull == 'down'
assert not device.pull_up
def test_input_is_active():
def test_input_is_active_low():
pin = MockPin(2)
with InputDevice(pin, pull_up=True) as device:
pin.drive_high()
assert not device.is_active
assert repr(device) == '<gpiozero.InputDevice object on pin MOCK2, pull_up=True, is_active=False>'
pin.drive_low()
assert device.is_active
assert repr(device) == '<gpiozero.InputDevice object on pin MOCK2, pull_up=True, is_active=True>'
def test_input_is_active_high():
pin = MockPin(2)
with InputDevice(pin, pull_up=False) as device:
pin.drive_high()
assert device.is_active
assert repr(device) == '<gpiozero.InputDevice object on pin MOCK2, pull_up=False, is_active=True>'
pin.drive_low()
assert not device.is_active
assert repr(device) == '<gpiozero.InputDevice object on pin MOCK2, pull_up=False, is_active=False>'
def test_input_pulled_up():
pin = MockPulledUpPin(2)
@@ -52,20 +64,20 @@ def test_input_event_activated():
pin = MockPin(2)
with DigitalInputDevice(pin) as device:
device.when_activated = lambda: event.set()
assert not event.wait(0)
assert not event.is_set()
pin.drive_high()
assert event.wait(0)
assert event.is_set()
def test_input_event_deactivated():
event = Event()
pin = MockPin(2)
with DigitalInputDevice(pin) as device:
device.when_deactivated = lambda: event.set()
assert not event.wait(0)
assert not event.is_set()
pin.drive_high()
assert not event.wait(0)
assert not event.is_set()
pin.drive_low()
assert event.wait(0)
assert event.is_set()
def test_input_wait_active():
pin = MockPin(2)
@@ -83,11 +95,14 @@ def test_input_wait_inactive():
def test_input_smoothed_attrib():
pin = MockPin(2)
with SmoothedInputDevice(pin, threshold=0.5, queue_len=5, partial=False) as device:
assert repr(device) == '<gpiozero.SmoothedInputDevice object on pin=MOCK2, pull_up=False>'
assert device.threshold == 0.5
assert device.queue_len == 5
assert not device.partial
device._queue.start()
assert not device.is_active
with pytest.raises(InputDeviceError):
device.threshold = 1
def test_input_smoothed_values():
pin = MockPin(2)

View File

@@ -160,15 +160,15 @@ def test_mock_pin_edges():
pin.when_changed = changed
pin.drive_high()
assert pin.state
assert fired.wait(0)
assert fired.is_set()
fired.clear()
pin.edges = 'falling'
pin.drive_low()
assert not pin.state
assert fired.wait(0)
assert fired.is_set()
fired.clear()
pin.drive_high()
assert pin.state
assert not fired.wait(0)
assert not fired.is_set()
assert pin.edges == 'falling'

Some files were not shown because too many files have changed in this diff Show More