mirror of
https://github.com/KevinMidboe/python-gpiozero.git
synced 2025-10-29 17:50:37 +00:00
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:
6
debian/control
vendored
6
debian/control
vendored
@@ -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
|
||||
|
||||
@@ -99,3 +99,10 @@ CamJam #3 Kit Robot
|
||||
:inherited-members:
|
||||
:members:
|
||||
|
||||
Energenie
|
||||
=========
|
||||
|
||||
.. autoclass:: Energenie
|
||||
:inherited-members:
|
||||
:members:
|
||||
|
||||
|
||||
@@ -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
|
||||
==========
|
||||
|
||||
@@ -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)
|
||||
==========================
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)])
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user