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