Fix #204 and start readying the release

Also re-numbers energenie sockets 1-4 (as noted by @bennuttall in
comments to #239), and adds several "real pins" tests and board tests.
The bad-PWM stuff is currently disabled as it causes segfaults when
running the tests and I can't seem to trace the cause at the moment.
Finally, I've tweaked the deb config to suggest gpiozero, removed spidev
as a mandatory dep (which'll fix installs on wheezy for py3), and
there's some more miscellaneous last-minute stuff here that I can't
recall...
This commit is contained in:
Dave Jones
2016-04-05 16:06:18 +01:00
parent 09ceed6fb3
commit ee3bd6b532
8 changed files with 585 additions and 91 deletions

6
debian/control vendored
View File

@@ -11,7 +11,8 @@ X-Python3-Version: >= 3.2
Package: python-gpiozero
Architecture: all
Section: python
Depends: ${misc:Depends}, ${python:Depends}, python-rpi.gpio, python-spidev
Depends: ${misc:Depends}, ${python:Depends}, python-rpi.gpio
Suggests: python-spidev, 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
@@ -23,7 +24,8 @@ 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, python3-spidev
Depends: ${misc:Depends}, ${python3:Depends}, python3-rpi.gpio
Suggests: python3-spidev, 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

@@ -99,3 +99,10 @@ CamJam #3 Kit Robot
:inherited-members:
:members:
Energenie
=========
.. autoclass:: Energenie
:inherited-members:
:members:

View File

@@ -70,6 +70,14 @@ to utilize pins that are part of IO extender chips. For example::
It is potentially subject to change in future versions. We welcome any
comments from testers!
.. warning::
The astute and mischievious reader may note that it is possible to mix pin
implementations, e.g. using ``RPiGPIOPin`` for one pin, and ``NativePin``
for another. This is unsupported, and if it results in your script
crashing, your components failing, or your Raspberry Pi turning into an
actual raspberry pie, you have only yourself to blame.
RPiGPIOPin
==========

View File

@@ -5,6 +5,50 @@ Changelog
.. currentmodule:: gpiozero
Release 1.2.0 (2016-04-??)
==========================
* Added :class:`Energenie` class for controlling Energenie plugs (`#69`_)
* Added :class:`LineSensor` class for single line-sensors (`#109`_)
* Added :class:`DistanceSensor` class for HC-SR04 ultra-sonic sensors (`#114`_)
* Added :class:`SnowPi` class for the Ryanteck Snow-pi board (`#130`_)
* Fixed issues with installing GPIO Zero for python 3 on Raspbian Wheezy
releases (`#140`_)
* Added support for lots of ADC chips (MCP3xxx family) (`#162`_) - many thanks
to pcopa and lurch!
* Added support for pigpiod as a pin implementation with
:class:`~gpiozero.pins.pigpiod.PiGPIOPin` (`#180`_)
* Many refinements to the base classes mean more consistency in composite
devices and several bugs squashed (`#164`_, `#175`_, `#182`_, `#189`_,
`#193`_, `#229`_)
* GPIO Zero is now aware of what sort of Pi it's running on via :func:`pi_info`
and has a fairly extensive database of Pi information which it uses to
determine when users request impossible things (like pull-down on a pin with
a physical pull-up resistor) (`#222`_)
* The source/values system was enhanced to ensure normal usage doesn't stress
the CPU and lots of utilities were added (`#181`_, `#251`_)
And I'll just add a note of thanks to the many people in the community who
contributed to this release: we've had some great PRs, suggestions, and bug
reports in this version - keep 'em coming!
.. _#69: https://github.com/RPi-Distro/python-gpiozero/issues/69
.. _#109: https://github.com/RPi-Distro/python-gpiozero/issues/109
.. _#114: https://github.com/RPi-Distro/python-gpiozero/issues/114
.. _#130: https://github.com/RPi-Distro/python-gpiozero/issues/130
.. _#140: https://github.com/RPi-Distro/python-gpiozero/issues/140
.. _#162: https://github.com/RPi-Distro/python-gpiozero/issues/162
.. _#164: https://github.com/RPi-Distro/python-gpiozero/issues/164
.. _#175: https://github.com/RPi-Distro/python-gpiozero/issues/175
.. _#180: https://github.com/RPi-Distro/python-gpiozero/issues/180
.. _#181: https://github.com/RPi-Distro/python-gpiozero/issues/181
.. _#182: https://github.com/RPi-Distro/python-gpiozero/issues/182
.. _#189: https://github.com/RPi-Distro/python-gpiozero/issues/189
.. _#193: https://github.com/RPi-Distro/python-gpiozero/issues/193
.. _#222: https://github.com/RPi-Distro/python-gpiozero/issues/222
.. _#229: https://github.com/RPi-Distro/python-gpiozero/issues/229
.. _#251: https://github.com/RPi-Distro/python-gpiozero/issues/251
Release 1.1.0 (2016-02-08)
==========================

View File

@@ -539,13 +539,15 @@ class SnowPi(LEDBoard):
_order=('top', 'middle', 'bottom')),
right=LEDBoard(
top=7, middle=8, bottom=9, pwm=pwm,
_order=('top', 'middle', 'bottom'))
_order=('top', 'middle', 'bottom')),
_order=('left', 'right')
),
eyes=LEDBoard(
left=23, right=24, pwm=pwm,
_order=('left', 'right')
),
nose=25, pwm=pwm
nose=25, pwm=pwm,
_order=('eyes', 'nose', 'arms')
)
@@ -629,9 +631,6 @@ class TrafficHat(TrafficLightsBuzzer):
)
RobotTuple = namedtuple('RobotTuple', ('left', 'right'))
class Robot(SourceMixin, CompositeDevice):
"""
Extends :class:`CompositeDevice` to represent a generic dual-motor robot.
@@ -657,48 +656,10 @@ class Robot(SourceMixin, CompositeDevice):
"""
def __init__(self, left=None, right=None):
if not all([left, right]):
raise GPIOPinMissing(
'left and right motor pins must be provided'
)
super(Robot, self).__init__()
self._left = Motor(*left)
self._right = Motor(*right)
def close(self):
self._left.close()
self._right.close()
@property
def closed(self):
return self._left.closed and self._right.closed
@property
def left_motor(self):
"""
Returns the `Motor` device representing the robot's left motor.
"""
return self._left
@property
def right_motor(self):
"""
Returns the `Motor` device representing the robot's right motor.
"""
return self._right
@property
def value(self):
"""
Returns a tuple of two floating point values (-1 to 1) representing the
speeds of the robot's two motors (left and right). This property can
also be set to alter the speed of both motors.
"""
return RobotTuple(self._left.value, self._right.value)
@value.setter
def value(self, value):
self._left.value, self._right.value = value
super(Robot, self).__init__(
left_motor=Motor(*left),
right_motor=Motor(*right),
_order=('left_motor', 'right_motor'))
def forward(self, speed=1):
"""
@@ -708,8 +669,8 @@ class Robot(SourceMixin, CompositeDevice):
Speed at which to drive the motors, as a value between 0 (stopped)
and 1 (full speed). The default is 1.
"""
self._left.forward(speed)
self._right.forward(speed)
self.left_motor.forward(speed)
self.right_motor.forward(speed)
def backward(self, speed=1):
"""
@@ -719,8 +680,8 @@ class Robot(SourceMixin, CompositeDevice):
Speed at which to drive the motors, as a value between 0 (stopped)
and 1 (full speed). The default is 1.
"""
self._left.backward(speed)
self._right.backward(speed)
self.left_motor.backward(speed)
self.right_motor.backward(speed)
def left(self, speed=1):
"""
@@ -731,8 +692,8 @@ class Robot(SourceMixin, CompositeDevice):
Speed at which to drive the motors, as a value between 0 (stopped)
and 1 (full speed). The default is 1.
"""
self._right.forward(speed)
self._left.backward(speed)
self.right_motor.forward(speed)
self.left_motor.backward(speed)
def right(self, speed=1):
"""
@@ -743,8 +704,8 @@ class Robot(SourceMixin, CompositeDevice):
Speed at which to drive the motors, as a value between 0 (stopped)
and 1 (full speed). The default is 1.
"""
self._left.forward(speed)
self._right.backward(speed)
self.left_motor.forward(speed)
self.right_motor.backward(speed)
def reverse(self):
"""
@@ -753,15 +714,15 @@ class Robot(SourceMixin, CompositeDevice):
robot is turning left at half-speed, it will turn right at half-speed.
If the robot is currently stopped it will remain stopped.
"""
self._left.value = -self._left.value
self._right.value = -self._right.value
self.left_motor.reverse()
self.right_motor.reverse()
def stop(self):
"""
Stop the robot.
"""
self._left.stop()
self._right.stop()
self.left_motor.stop()
self.right_motor.stop()
class RyanteckRobot(Robot):
@@ -779,7 +740,7 @@ class RyanteckRobot(Robot):
"""
def __init__(self):
super(RyanteckRobot, self).__init__(left=(17, 18), right=(22, 23))
super(RyanteckRobot, self).__init__((17, 18), (22, 23))
class CamJamKitRobot(Robot):
@@ -799,7 +760,7 @@ class CamJamKitRobot(Robot):
"""
def __init__(self):
super(CamJamKitRobot, self).__init__(left=(9, 10), right=(7, 8))
super(CamJamKitRobot, self).__init__((9, 10), (7, 8))
class _EnergenieMaster(SharedMixin, CompositeOutputDevice):
@@ -807,7 +768,8 @@ class _EnergenieMaster(SharedMixin, CompositeOutputDevice):
self._lock = Lock()
super(_EnergenieMaster, self).__init__(
*(OutputDevice(pin) for pin in (17, 22, 23, 27)),
mode=OutputDevice(24), enable=OutputDevice(25))
mode=OutputDevice(24), enable=OutputDevice(25),
_order=('mode', 'enable'))
def close(self):
if self._lock:
@@ -823,7 +785,7 @@ class _EnergenieMaster(SharedMixin, CompositeOutputDevice):
def transmit(self, socket, enable):
with self._lock:
try:
code = (8 * bool(enable)) + (7 - socket)
code = (8 * bool(enable)) + (8 - socket)
for bit in self.all[:4]:
bit.value = (code & 1)
code >>= 1
@@ -844,12 +806,12 @@ class Energenie(SourceMixin, Device):
from gpiozero import Energenie
lamp = Energenie(0)
lamp = Energenie(1)
lamp.on()
:param int socket:
Which socket this instance should control. This is an integer number
between 0 and 3.
between 1 and 4.
:param bool initial_value:
The initial state of the socket. As Energenie sockets provide no
@@ -863,8 +825,8 @@ class Energenie(SourceMixin, Device):
def __init__(self, socket=None, initial_value=False):
if socket is None:
raise EnergenieSocketMissing('socket number must be provided')
if not (0 <= socket < 4):
raise EnergenieBadSocket('socket number must be between 0 and 3')
if not (1 <= socket <= 4):
raise EnergenieBadSocket('socket number must be between 1 and 4')
super(Energenie, self).__init__()
self._socket = socket
self._master = _EnergenieMaster()

View File

@@ -41,6 +41,7 @@ class OutputDevice(SourceMixin, GPIODevice):
"""
def __init__(self, pin=None, active_high=True, initial_value=False):
super(OutputDevice, self).__init__(pin)
self._lock = Lock()
self.active_high = active_high
if initial_value is None:
self.pin.function = 'output'
@@ -70,6 +71,17 @@ class OutputDevice(SourceMixin, GPIODevice):
"""
self._write(False)
def toggle(self):
"""
Reverse the state of the device. If it's on, turn it off; if it's off,
turn it on.
"""
with self._lock:
if self.is_active:
self.off()
else:
self.on()
@property
def value(self):
"""
@@ -121,7 +133,6 @@ class DigitalOutputDevice(OutputDevice):
def __init__(self, pin=None, active_high=True, initial_value=False):
self._blink_thread = None
super(DigitalOutputDevice, self).__init__(pin, active_high, initial_value)
self._lock = Lock()
self._controller = None
@property
@@ -145,17 +156,6 @@ class DigitalOutputDevice(OutputDevice):
self._stop_blink()
self._write(False)
def toggle(self):
"""
Reverse the state of the device. If it's on, turn it off; if it's off,
turn it on.
"""
with self._lock:
if self.is_active:
self.off()
else:
self.on()
def blink(self, on_time=1, off_time=1, n=None, background=True):
"""
Make the device turn on and off repeatedly.
@@ -757,7 +757,8 @@ class Motor(SourceMixin, CompositeDevice):
)
super(Motor, self).__init__(
forward_device=PWMOutputDevice(forward),
backward_device=PWMOutputDevice(backward))
backward_device=PWMOutputDevice(backward),
_order=('forward_device', 'backward_device'))
@property
def value(self):

View File

@@ -7,14 +7,450 @@ from __future__ import (
str = type('')
import sys
import pytest
from time import sleep
from gpiozero.pins.mock import MockPin
from gpiozero.pins.mock import MockPin, MockPWMPin
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
else:
gpiozero.devices.DefaultPin = MockPin
def teardown_function(function):
MockPin.clear_pins()
# TODO boards tests!
def test_composite_output_on_off():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
device = CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3))
device.on()
assert all((pin1.state, pin2.state, pin3.state))
device.off()
assert not any((pin1.state, pin2.state, pin3.state))
def test_composite_output_toggle():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
device = CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3))
device.toggle()
assert all((pin1.state, pin2.state, pin3.state))
device[0].off()
device.toggle()
assert pin1.state
assert not pin2.state
assert not pin3.state
def test_composite_output_value():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
device = CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3))
assert device.value == (0, 0, 0)
device.toggle()
assert device.value == (1, 1, 1)
device.value = (1, 0, 1)
assert device[0].is_active
assert not device[1].is_active
assert device[2].is_active
def test_led_board_on_off():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
board = LEDBoard(pin1, pin2, foo=pin3)
assert isinstance(board[0], LED)
assert isinstance(board[1], LED)
assert isinstance(board[2], LED)
board.on()
assert all((pin1.state, pin2.state, pin3.state))
board.off()
assert not any((pin1.state, pin2.state, pin3.state))
board[0].on()
assert board.value == (1, 0, 0)
assert pin1.state
assert not pin2.state
assert not pin3.state
board.toggle()
assert board.value == (0, 1, 1)
assert not pin1.state
assert pin2.state
assert pin3.state
def test_led_board_nested():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
board = LEDBoard(pin1, LEDBoard(pin2, pin3))
assert list(led.pin for led in board.leds) == [pin1, pin2, pin3]
assert board.value == (0, (0, 0))
board.value = (1, (0, 1))
assert pin1.state
assert not pin2.state
assert pin3.state
def test_led_board_bad_blink():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
board = LEDBoard(pin1, LEDBoard(pin2, pin3))
with pytest.raises(ValueError):
board.blink(fade_in_time=1, fade_out_time=1)
with pytest.raises(ValueError):
board.blink(fade_out_time=1)
with pytest.raises(ValueError):
board.pulse()
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_led_board_blink_background():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
board = LEDBoard(pin1, LEDBoard(pin2, pin3))
board.blink(0.1, 0.1, n=2)
board._blink_thread.join() # naughty, but ensures no arbitrary waits in the test
test = [
(0.0, False),
(0.0, True),
(0.1, False),
(0.1, True),
(0.1, False)
]
pin1.assert_states_and_times(test)
pin2.assert_states_and_times(test)
pin3.assert_states_and_times(test)
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_led_board_blink_foreground():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
board = LEDBoard(pin1, LEDBoard(pin2, pin3))
board.blink(0.1, 0.1, n=2, background=False)
test = [
(0.0, False),
(0.0, True),
(0.1, False),
(0.1, True),
(0.1, False)
]
pin1.assert_states_and_times(test)
pin2.assert_states_and_times(test)
pin3.assert_states_and_times(test)
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_led_board_blink_control():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
board = LEDBoard(pin1, LEDBoard(pin2, pin3))
board.blink(0.1, 0.1, n=2)
# make sure the blink thread's started
while not board._blink_leds:
sleep(0.00001)
board[1][0].off() # immediately take over the second LED
board._blink_thread.join() # naughty, but ensures no arbitrary waits in the test
test = [
(0.0, False),
(0.0, True),
(0.1, False),
(0.1, True),
(0.1, False)
]
pin1.assert_states_and_times(test)
pin3.assert_states_and_times(test)
print(pin2.states)
pin2.assert_states_and_times([(0.0, False), (0.0, True), (0.0, False)])
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_led_board_blink_take_over():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
board = LEDBoard(pin1, LEDBoard(pin2, pin3))
board[1].blink(0.1, 0.1, n=2)
board.blink(0.1, 0.1, n=2) # immediately take over blinking
board[1]._blink_thread.join()
board._blink_thread.join()
test = [
(0.0, False),
(0.0, True),
(0.1, False),
(0.1, True),
(0.1, False)
]
pin1.assert_states_and_times(test)
pin2.assert_states_and_times(test)
pin3.assert_states_and_times(test)
def test_led_board_blink_control_all():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
board = LEDBoard(pin1, LEDBoard(pin2, pin3))
board.blink(0.1, 0.1, n=2)
# make sure the blink thread's started
while not board._blink_leds:
sleep(0.00001)
board[0].off() # immediately take over all LEDs
board[1][0].off()
board[1][1].off()
board._blink_thread.join() # blink should terminate here anyway
test = [
(0.0, False),
(0.0, True),
(0.0, False),
]
pin1.assert_states_and_times(test)
pin2.assert_states_and_times(test)
pin3.assert_states_and_times(test)
def test_led_board_blink_interrupt_on():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
board = LEDBoard(pin1, LEDBoard(pin2, pin3))
board.blink(1, 0.1)
sleep(0.2)
board.off() # should interrupt while on
pin1.assert_states([False, True, False])
pin2.assert_states([False, True, False])
pin3.assert_states([False, True, False])
def test_led_board_blink_interrupt_off():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
board = LEDBoard(pin1, LEDBoard(pin2, pin3))
board.blink(0.1, 1)
sleep(0.2)
board.off() # should interrupt while off
pin1.assert_states([False, True, False])
pin2.assert_states([False, True, False])
pin3.assert_states([False, True, False])
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_led_board_fade_background():
pin1 = MockPWMPin(2)
pin2 = MockPWMPin(3)
pin3 = MockPWMPin(4)
board = LEDBoard(pin1, LEDBoard(pin2, pin3, pwm=True), pwm=True)
board.blink(0, 0, 0.1, 0.1, n=2)
board._blink_thread.join()
test = [
(0.0, 0),
(0.02, 0.2),
(0.02, 0.4),
(0.02, 0.6),
(0.02, 0.8),
(0.02, 1),
(0.02, 0.8),
(0.02, 0.6),
(0.02, 0.4),
(0.02, 0.2),
(0.02, 0),
(0.02, 0.2),
(0.02, 0.4),
(0.02, 0.6),
(0.02, 0.8),
(0.02, 1),
(0.02, 0.8),
(0.02, 0.6),
(0.02, 0.4),
(0.02, 0.2),
(0.02, 0),
]
pin1.assert_states_and_times(test)
pin2.assert_states_and_times(test)
pin3.assert_states_and_times(test)
def test_led_bar_graph_value():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
graph = LEDBarGraph(pin1, pin2, pin3)
graph.value = 0
assert not any((pin1.state, pin2.state, pin3.state))
graph.value = 1
assert all((pin1.state, pin2.state, pin3.state))
graph.value = 1/3
assert pin1.state and not (pin2.state or pin3.state)
graph.value = -1/3
assert pin3.state and not (pin1.state or pin2.state)
pin1.state = True
pin2.state = True
assert graph.value == 1
pin3.state = False
assert graph.value == 2/3
pin3.state = True
pin1.state = False
assert graph.value == -2/3
def test_led_bar_graph_pwm_value():
pin1 = MockPWMPin(2)
pin2 = MockPWMPin(3)
pin3 = MockPWMPin(4)
graph = LEDBarGraph(pin1, pin2, pin3, pwm=True)
graph.value = 0
assert not any((pin1.state, pin2.state, pin3.state))
graph.value = 1
assert all((pin1.state, pin2.state, pin3.state))
graph.value = 1/3
assert pin1.state and not (pin2.state or pin3.state)
graph.value = -1/3
assert pin3.state and not (pin1.state or pin2.state)
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_init():
pin1 = MockPin(2)
pin2 = MockPin(3)
pin3 = MockPin(4)
with pytest.raises(TypeError):
LEDBarGraph(pin1, pin2, foo=pin3)
def test_pi_liter():
pins = [MockPin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)]
board = PiLiter()
assert [device.pin for device in board] == pins
def test_pi_liter_graph():
pins = [MockPin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)]
board = PiLiterBarGraph()
board.value = 0.5
assert [pin.state for pin in pins] == [1, 1, 1, 1, 0, 0, 0, 0]
pins[4].state = 1
assert board.value == 5/8
def test_traffic_lights():
red_pin = MockPin(2)
amber_pin = MockPin(3)
green_pin = MockPin(4)
board = TrafficLights(red_pin, amber_pin, green_pin)
board.red.on()
assert red_pin.state
assert not amber_pin.state
assert not green_pin.state
def test_traffic_lights_bad_init():
with pytest.raises(ValueError):
TrafficLights()
def test_pi_traffic():
pins = [MockPin(n) for n in (9, 10, 11)]
board = PiTraffic()
assert [device.pin for device in board] == pins
def test_snow_pi():
pins = [MockPin(n) for n in (23, 24, 25, 17, 18, 22, 7, 8, 9)]
board = SnowPi()
assert [device.pin for device in board.leds] == pins
def test_traffic_lights_buzzer():
red_pin = MockPin(2)
amber_pin = MockPin(3)
green_pin = MockPin(4)
buzzer_pin = MockPin(5)
button_pin = MockPin(6)
board = TrafficLightsBuzzer(
TrafficLights(red_pin, amber_pin, green_pin),
Buzzer(buzzer_pin),
Button(button_pin))
board.lights.red.on()
board.buzzer.on()
assert red_pin.state
assert not amber_pin.state
assert not green_pin.state
assert buzzer_pin.state
button_pin.drive_low()
assert board.button.is_active
def test_fish_dish():
pins = [MockPin(n) for n in (9, 22, 4, 8, 7)]
board = FishDish()
assert [led.pin for led in board.lights] + [board.buzzer.pin, board.button.pin] == pins
def test_traffic_hat():
pins = [MockPin(n) for n in (24, 23, 22, 5, 25)]
board = TrafficHat()
assert [led.pin for led in board.lights] + [board.buzzer.pin, board.button.pin] == pins
def test_robot():
pins = [MockPWMPin(n) for n in (2, 3, 4, 5)]
robot = Robot((2, 3), (4, 5))
assert (
[device.pin for device in robot.left_motor] +
[device.pin for device in robot.right_motor]) == pins
robot.forward()
assert [pin.state for pin in pins] == [1, 0, 1, 0]
robot.backward()
assert [pin.state for pin in pins] == [0, 1, 0, 1]
robot.forward(0.5)
assert [pin.state for pin in pins] == [0.5, 0, 0.5, 0]
robot.left()
assert [pin.state for pin in pins] == [0, 1, 1, 0]
robot.right()
assert [pin.state for pin in pins] == [1, 0, 0, 1]
robot.reverse()
assert [pin.state for pin in pins] == [0, 1, 1, 0]
robot.stop()
assert [pin.state for pin in pins] == [0, 0, 0, 0]
def test_ryanteck_robot():
pins = [MockPWMPin(n) for n in (17, 18, 22, 23)]
board = RyanteckRobot()
assert [device.pin for motor in board for device in motor] == pins
def test_camjam_kit_robot():
pins = [MockPWMPin(n) for n in (9, 10, 7, 8)]
board = CamJamKitRobot()
assert [device.pin for motor in board for device in motor] == pins
def test_energenie_bad_init():
with pytest.raises(ValueError):
Energenie()
with pytest.raises(ValueError):
Energenie(0)
def test_energenie():
pins = [MockPin(n) for n in (17, 22, 23, 27, 24, 25)]
device1 = Energenie(1, initial_value=True)
device2 = Energenie(2, initial_value=False)
assert device1.value
assert not device2.value
[pin.clear_states() for pin in pins]
device1.on()
assert device1.value
pins[0].assert_states_and_times([(0.0, False), (0.0, True)])
pins[1].assert_states_and_times([(0.0, True), (0.0, True)])
pins[2].assert_states_and_times([(0.0, True), (0.0, True)])
pins[3].assert_states_and_times([(0.0, False), (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)])
[pin.clear_states() for pin in pins]
device2.on()
assert device2.value
pins[0].assert_states_and_times([(0.0, True), (0.0, False)])
pins[1].assert_states_and_times([(0.0, True), (0.0, True)])
pins[2].assert_states_and_times([(0.0, True), (0.0, True)])
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)])

View File

@@ -5,7 +5,10 @@ from __future__ import (
division,
)
str = type('')
try:
range = xrange
except NameError:
pass
import io
import subprocess
@@ -13,6 +16,10 @@ import subprocess
import pytest
from gpiozero import PinFixedPull, PinInvalidPull, PinInvalidFunction
try:
from math import isclose
except ImportError:
from gpiozero.compat import isclose
# This module assumes you've wired the following GPIO pins together
@@ -56,7 +63,6 @@ try:
except ImportError:
NativePin = None
@pytest.fixture(scope='module', params=PIN_CLASSES)
def pin_class(request):
# pigpiod needs to be running for PiGPIOPin
@@ -70,13 +76,13 @@ def pin_class(request):
request.addfinalizer(kill_pigpiod)
return request.param
@pytest.fixture
@pytest.fixture(scope='function')
def pins(request, pin_class):
# Why return both pins in a single fixture? If we defined one fixture for
# each pin then pytest will (correctly) test RPiGPIOPin(22) against
# NativePin(27) and so on. This isn't supported, so we don't test it
test_pin = pin_class(22)
input_pin = pin_class(27)
test_pin = pin_class(TEST_PIN)
input_pin = pin_class(INPUT_PIN)
input_pin.function = 'input'
input_pin.pull = 'down'
def fin():
@@ -88,8 +94,8 @@ def pins(request, pin_class):
def test_pin_numbers(pins):
test_pin, input_pin = pins
assert test_pin.number == 22
assert input_pin.number == 27
assert test_pin.number == TEST_PIN
assert input_pin.number == INPUT_PIN
def test_function_bad(pins):
test_pin, input_pin = pins
@@ -125,12 +131,18 @@ def test_pull_bad(pins):
test_pin.function = 'input'
with pytest.raises(PinInvalidPull):
test_pin.pull = 'foo'
with pytest.raises(PinInvalidPull):
test_pin.input_with_pull('foo')
def test_pull_down_warning(pin_class):
# XXX This assumes we're on a vaguely modern Pi and not a compute module
# Might want to refine this with the pi-info database
pin = pin_class(2)
try:
with pytest.raises(PinFixedPull):
pin.pull = 'down'
with pytest.raises(PinFixedPull):
pin.input_with_pull('down')
finally:
pin.close()
@@ -141,3 +153,25 @@ def test_input_with_pull(pins):
test_pin.input_with_pull('down')
assert input_pin.state == 0
@pytest.mark.skipif(True, reason='causes segfaults')
def test_bad_duty_cycle(pins):
test_pin, input_pin = pins
if test_pin.__class__.__name__ == 'NativePin':
pytest.skip("native pin doesn't support PWM")
test_pin.function = 'output'
test_pin.frequency = 100
with pytest.raises(ValueError):
test_pin.state = 1.1
def test_duty_cycles(pins):
test_pin, input_pin = pins
if test_pin.__class__.__name__ == 'NativePin':
pytest.skip("native pin doesn't support PWM")
test_pin.function = 'output'
test_pin.frequency = 100
for duty_cycle in (0.0, 0.1, 0.5, 1.0):
test_pin.state = duty_cycle
assert test_pin.state == duty_cycle
total = sum(input_pin.state for i in range(20000))
assert isclose(total / 20000, duty_cycle, rel_tol=0.1, abs_tol=0.1)