gpiozero-1.3.1.post1/0000755000175000017500000000000013046622014013747 5ustar benben00000000000000gpiozero-1.3.1.post1/tests/0000755000175000017500000000000013046622014015111 5ustar benben00000000000000gpiozero-1.3.1.post1/tests/test_devices.py0000644000175000017500000000672412714337163020166 0ustar benben00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import pytest from gpiozero.pins.mock import MockPin from gpiozero import * def teardown_function(function): MockPin.clear_pins() # TODO add more devices tests! def test_device_no_pin(): with pytest.raises(GPIOPinMissing): device = GPIODevice() def test_device_init(): pin = MockPin(2) with GPIODevice(pin) as device: assert not device.closed assert device.pin == pin def test_device_init_twice_same_pin(): pin = MockPin(2) with GPIODevice(pin) as device: with pytest.raises(GPIOPinInUse): device2 = GPIODevice(pin) def test_device_init_twice_different_pin(): pin = MockPin(2) pin2 = MockPin(3) with GPIODevice(pin) as device: with GPIODevice(pin2) as device2: pass def test_device_close(): pin = MockPin(2) device = GPIODevice(pin) device.close() assert device.closed assert device.pin is None def test_device_reopen_same_pin(): pin = MockPin(2) device = GPIODevice(pin) device.close() device2 = GPIODevice(pin) assert not device2.closed assert device2.pin == pin assert device.closed assert device.pin is None device2.close() def test_device_repr(): pin = MockPin(2) with GPIODevice(pin) as device: assert repr(device) == '' % pin def test_device_repr_after_close(): pin = MockPin(2) device = GPIODevice(pin) device.close() assert repr(device) == '' def test_device_unknown_attr(): pin = MockPin(2) with GPIODevice(pin) as device: with pytest.raises(AttributeError): device.foo = 1 def test_device_context_manager(): pin = MockPin(2) with GPIODevice(pin) as device: assert not device.closed assert device.closed def test_composite_device_sequence(): with CompositeDevice( InputDevice(MockPin(2)), InputDevice(MockPin(3)) ) as device: assert len(device) == 2 assert device[0].pin.number == 2 assert device[1].pin.number == 3 assert device.namedtuple._fields == ('_0', '_1') def test_composite_device_values(): with CompositeDevice( InputDevice(MockPin(2)), InputDevice(MockPin(3)) ) as device: assert device.value == (0, 0) assert not device.is_active device[0].pin.drive_high() assert device.value == (1, 0) assert device.is_active def test_composite_device_named(): with CompositeDevice( foo=InputDevice(MockPin(2)), bar=InputDevice(MockPin(3)), _order=('foo', 'bar') ) as device: assert device.namedtuple._fields == ('foo', 'bar') assert device.value == (0, 0) assert not device.is_active def test_composite_device_bad_init(): with pytest.raises(ValueError): CompositeDevice(foo=1, bar=2, _order=('foo',)) with pytest.raises(ValueError): CompositeDevice(close=1) with pytest.raises(ValueError): CompositeDevice(2) with pytest.raises(ValueError): CompositeDevice(MockPin(2)) def test_composite_device_read_only(): device = CompositeDevice( foo=InputDevice(MockPin(2)), bar=InputDevice(MockPin(3)) ) with pytest.raises(AttributeError): device.foo = 1 gpiozero-1.3.1.post1/tests/test_tools.py0000644000175000017500000002511113046621340017663 0ustar benben00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import pytest from math import sin, cos, radians from time import time from gpiozero.tools import * try: from math import isclose except ImportError: from gpiozero.compat import isclose try: from statistics import mean except ImportError: from gpiozero.compat import mean try: from statistics import median except ImportError: from gpiozero.compat import median def test_negated(): assert list(negated(())) == [] assert list(negated((True, True, False, False))) == [False, False, True, True] def test_inverted(): with pytest.raises(ValueError): list(inverted((), 0, 0)) with pytest.raises(ValueError): list(inverted((), 1, 1)) with pytest.raises(ValueError): list(inverted((), 1, 0)) assert list(inverted(())) == [] assert list(inverted((1, 0, 0.1, 0.5))) == [0, 1, 0.9, 0.5] assert list(inverted((1, 0, 0.1, 0.5), 0, 1)) == [0, 1, 0.9, 0.5] assert list(inverted((-1, 0, -0.1, -0.5), -1, 0)) == [0, -1, -0.9, -0.5] assert list(inverted((1, 0, 0.1, 0.5, -1, -0.1, -0.5), -1, 1)) == [-1, 0, -0.1, -0.5, 1, 0.1, 0.5] assert list(inverted((2, 1, 1.1, 1.5), 1, 2)) == [1, 2, 1.9, 1.5] def test_scaled(): with pytest.raises(ValueError): list(scaled((), 0, 1, 0, 0)) with pytest.raises(ValueError): list(scaled((), 0, 1, 1, 1)) with pytest.raises(ValueError): list(scaled((), 0, 1, 1, 0)) assert list(scaled((), 0, 1)) == [] # no scale assert list(scaled((0, 1, 0.5, 0.1), 0, 1)) == [0, 1, 0.5, 0.1] assert list(scaled((0, 1, 0.5, 0.1), 0, 1, 0, 1)) == [0, 1, 0.5, 0.1] # multiply by 2 assert list(scaled((0, 1, 0.5, 0.1), 0, 2, 0, 1)) == [0, 2, 1, 0.2] # add 1 assert list(scaled((0, 1, 0.5, 0.1), 1, 2, 0, 1)) == [1, 2, 1.5, 1.1] # multiply by 2 then add 1 assert list(scaled((0, 1, 0.5, 0.1), 1, 3, 0, 1)) == [1, 3, 2, 1.2] # add 1 then multiply by 2 assert list(scaled((0, 1, 0.5, 0.1), 2, 4, 0, 1)) == [2, 4, 3, 2.2] # invert assert list(scaled((0, 1, 0.5, 0.1), 1, 0, 0, 1)) == [1, 0, 0.5, 0.9] # multiply by -1 then subtract 1 assert list(scaled((0, 1, 0.5, 0.1), -1, -2, 0, 1)) == [-1, -2, -1.5, -1.1] # scale 0->1 to -1->+1 assert list(scaled((0, 1, 0.5, 0.1), -1, 1)) == [-1, 1, 0.0, -0.8] assert list(scaled((0, 1, 0.5, 0.1), -1, 1, 0, 1)) == [-1, 1, 0.0, -0.8] # scale -1->+1 to 0->1 assert list(scaled((-1, 1, 0.0, -0.5), 0, 1, -1, 1)) == [0, 1, 0.5, 0.25] def test_clamped(): with pytest.raises(ValueError): list(clamped((), 0, 0)) with pytest.raises(ValueError): list(clamped((), 1, 1)) with pytest.raises(ValueError): list(clamped((), 1, 0)) assert list(clamped(())) == [] assert list(clamped((-2, -1, -0.5, 0, 0.5, 1, 2))) == [0, 0, 0, 0, 0.5, 1, 1] assert list(clamped((-2, -1, -0.5, 0, 0.5, 1, 2), 0, 1)) == [0, 0, 0, 0, 0.5, 1, 1] assert list(clamped((-2, -1, -0.5, 0, 0.5, 1, 2), -1, 1)) == [-1, -1, -0.5, 0, 0.5, 1, 1] assert list(clamped((-2, -1, -0.5, 0, 0.5, 1, 2), -2, 2)) == [-2, -1, -0.5, 0, 0.5, 1, 2] def test_absoluted(): assert list(absoluted(())) == [] assert list(absoluted((-2, -1, 0, 1, 2))) == [2, 1, 0, 1, 2] def test_quantized(): with pytest.raises(ValueError): list(quantized((), 0)) with pytest.raises(ValueError): list(quantized((), 4, 0, 0)) with pytest.raises(ValueError): list(quantized((), 4, 1, 1)) with pytest.raises(ValueError): list(quantized((), 4, 1, 0)) assert list(quantized((), 4)) == [] assert list(quantized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1), 4)) == [ 0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.5, 0.75, 0.75, 1.0] assert list(quantized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1), 4, 0, 1)) == [ 0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.5, 0.75, 0.75, 1.0] assert list(quantized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1), 5)) == [ 0.0, 0.0, 0.2, 0.2, 0.4, 0.4, 0.6, 0.6, 0.8, 0.8, 1.0] assert list(quantized((0, 0.25, 0.5, 0.75, 1.0, 1.5, 1.75, 2.0), 2, 0, 2)) == [ 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0] assert list(quantized((1, 1.25, 1.5, 1.75, 2.0, 2.5, 2.75, 3.0), 2, 1, 3)) == [ 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 3.0] def test_booleanized(): with pytest.raises(ValueError): list(booleanized((), 0, 0)) with pytest.raises(ValueError): list(booleanized((), 1, 1)) with pytest.raises(ValueError): list(booleanized((), 1, 0)) with pytest.raises(ValueError): list(booleanized((), 0, 0.5, -0.2)) with pytest.raises(ValueError): list(booleanized((), 0, 0.5, 0.5)) assert list(booleanized((), 0, 0.5)) == [] assert list(booleanized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1), 0, 0.5)) == [ True, True, True, True, True, True, False, False, False, False, False] assert list(booleanized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 0), 0.25, 0.75)) == [ False, False, False, True, True, True, True, True, False, False, False, False] assert list(booleanized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 0), 0.25, 0.75, 0.2)) == [ False, False, False, False, False, True, True, True, True, True, False, False] assert list(booleanized((1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0, 1), 0.25, 0.75)) == [ False, False, False, True, True, True, True, True, False, False, False, False] assert list(booleanized((1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0, 1), 0.25, 0.75, 0.2)) == [ False, False, False, False, False, True, True, True, True, True, False, False] def test_all_values(): assert list(all_values(())) == [] assert list(all_values((False, True))) == [False, True] assert list(all_values((0, 1, 0, 1), (0, 0, 0, 1))) == [0, 0, 0, 1] def test_any_values(): assert list(any_values(())) == [] assert list(any_values((False, True))) == [False, True] assert list(any_values((0, 1, 0, 1), (0, 0, 0, 1))) == [0, 1, 0, 1] def test_averaged(): assert list(averaged(())) == [] assert list(averaged((0, 0.5, 1))) == [0, 0.5, 1] assert list(averaged((0, 0.5, 1), (1, 1, 1))) == [0.5, 0.75, 1] def test_summed(): assert list(summed(())) == [] assert list(summed((0, 0.5, 0.5, 1))) == [0, 0.5, 0.5, 1] assert list(summed((0, 0.5, 0.5, 1), (1, 0.5, 1, 1))) == [1, 1, 1.5, 2] def test_multiplied(): assert list(multiplied(())) == [] assert list(multiplied((0, 0.5, 0.5, 1))) == [0, 0.5, 0.5, 1] assert list(multiplied((0, 0.5, 0.5, 1), (1, 0.5, 1, 1))) == [0, 0.25, 0.5, 1] def test_queued(): with pytest.raises(ValueError): list(queued((), 0)) assert list(queued((), 5)) == [] assert list(queued((1, 2, 3, 4, 5), 5)) == [1] assert list(queued((1, 2, 3, 4, 5, 6), 5)) == [1, 2] def test_smoothed(): with pytest.raises(ValueError): list(smoothed((), 0)) assert list(smoothed((), 5)) == [] assert list(smoothed((1, 2, 3, 4, 5), 5)) == [3.0] assert list(smoothed((1, 2, 3, 4, 5, 6), 5)) == [3.0, 4.0] assert list(smoothed((1, 2, 3, 4, 5, 6), 5, average=mean)) == [3.0, 4.0] assert list(smoothed((1, 1, 1, 4, 5, 5), 5, average=mean)) == [2.4, 3.2] assert list(smoothed((1, 1, 1, 4, 5, 5), 5, average=median)) == [1, 4] def test_pre_delayed(): with pytest.raises(ValueError): list(pre_delayed((), -1)) assert list(pre_delayed((), 0.01)) == [] count = 0 start = time() for v in pre_delayed((0, 0, 0), 0.01): count += 1 assert v == 0 assert time() - start >= 0.01 start = time() assert count == 3 def test_post_delayed(): with pytest.raises(ValueError): list(post_delayed((), -1)) assert list(post_delayed((), 0.01)) == [] count = 0 start = time() for v in post_delayed((1, 2, 2), 0.01): count += 1 if v == 1: assert time() - start < 0.01 else: assert v == 2 assert time() - start >= 0.01 start = time() assert time() - start >= 0.01 assert count == 3 def test_pre_periodic_filtered(): with pytest.raises(ValueError): list(pre_periodic_filtered((), 2, -1)) with pytest.raises(ValueError): list(pre_periodic_filtered((), 0, 0)) assert list(pre_periodic_filtered((), 2, 0)) == [] assert list(pre_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 2, 0)) == [3, 4, 5, 6, 7, 8, 9, 10] assert list(pre_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 1, 1)) == [2, 4, 6, 8, 10] assert list(pre_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 1, 2)) == [2, 3, 5, 6, 8, 9] assert list(pre_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 2, 1)) == [3, 6, 9] def test_post_periodic_filtered(): with pytest.raises(ValueError): list(post_periodic_filtered((), 1, 0)) with pytest.raises(ValueError): list(post_periodic_filtered((), 0, 1)) assert list(pre_periodic_filtered((), 1, 1)) == [] assert list(post_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 1, 1)) == [1, 3, 5, 7, 9] assert list(post_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 1, 2)) == [1, 4, 7, 10] assert list(post_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 2, 1)) == [1, 2, 4, 5, 7, 8, 10] def test_random_values(): for _, v in zip(range(1000), random_values()): assert 0 <= v <= 1 def test_sin_values(): firstval = None for i, v in zip(range(1000), sin_values()): assert -1 <= v <= 1 assert isclose(v, sin(radians(i)), abs_tol=1e-9) if i == 0: firstval = v else: if i % 360 == 0: assert v == firstval for period in (360, 100): firstval = None for i, v in zip(range(1000), sin_values(period)): assert -1 <= v <= 1 if i == 0: firstval = v else: if i % period == 0: assert v == firstval def test_cos_values(): firstval = None for i, v in zip(range(1000), cos_values()): assert -1 <= v <= 1 assert isclose(v, cos(radians(i)), abs_tol=1e-9) if i == 0: firstval = v else: if i % 360 == 0: assert v == firstval for period in (360, 100): firstval = None for i, v in zip(range(1000), cos_values(period)): assert -1 <= v <= 1 if i == 0: firstval = v else: if i % period == 0: assert v == firstval gpiozero-1.3.1.post1/tests/test_real_pins.py0000644000175000017500000001212612714337163020511 0ustar benben00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') try: range = xrange except NameError: pass import io 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 TEST_PIN = 22 INPUT_PIN = 27 # Skip the entire module if we're not on a Pi def is_a_pi(): with io.open('/proc/cpuinfo', 'r') as cpuinfo: for line in cpuinfo: if line.startswith('Hardware'): hardware, colon, soc = line.strip().split(None, 2) return soc in ('BCM2708', 'BCM2709', 'BCM2835', 'BCM2836') else: return False pytestmark = pytest.mark.skipif(not is_a_pi(), reason='tests cannot run on non-Pi platforms') del is_a_pi # Try and import as many pin libraries as possible PIN_CLASSES = [] try: from gpiozero.pins.rpigpio import RPiGPIOPin PIN_CLASSES.append(RPiGPIOPin) except ImportError: RPiGPIOPin = None try: from gpiozero.pins.rpio import RPIOPin PIN_CLASSES.append(RPIOPin) except ImportError: RPIOPin = None try: from gpiozero.pins.pigpiod import PiGPIOPin PIN_CLASSES.append(PiGPIOPin) except ImportError: PiGPIOPin = None try: from gpiozero.pins.native import NativePin PIN_CLASSES.append(NativePin) except ImportError: NativePin = None @pytest.fixture(scope='module', params=PIN_CLASSES) def pin_class(request): # pigpiod needs to be running for PiGPIOPin if request.param.__name__ == 'PiGPIOPin': subprocess.check_call(['sudo', 'pigpiod']) # Unfortunately, pigpiod provides no option for running in the # foreground, so we have to use the sledgehammer of killall to get shot # of it def kill_pigpiod(): subprocess.check_call(['sudo', 'killall', 'pigpiod']) request.addfinalizer(kill_pigpiod) return request.param @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(TEST_PIN) input_pin = pin_class(INPUT_PIN) input_pin.function = 'input' input_pin.pull = 'down' def fin(): test_pin.close() input_pin.close() request.addfinalizer(fin) return test_pin, input_pin def test_pin_numbers(pins): test_pin, input_pin = pins assert test_pin.number == TEST_PIN assert input_pin.number == INPUT_PIN def test_function_bad(pins): test_pin, input_pin = pins with pytest.raises(PinInvalidFunction): test_pin.function = 'foo' def test_output(pins): test_pin, input_pin = pins test_pin.function = 'output' test_pin.state = 0 assert input_pin.state == 0 test_pin.state = 1 assert input_pin.state == 1 def test_output_with_state(pins): test_pin, input_pin = pins test_pin.output_with_state(0) assert input_pin.state == 0 test_pin.output_with_state(1) assert input_pin.state == 1 def test_pull(pins): test_pin, input_pin = pins test_pin.function = 'input' test_pin.pull = 'up' assert input_pin.state == 1 test_pin.pull = 'down' test_pin, input_pin = pins assert input_pin.state == 0 def test_pull_bad(pins): test_pin, input_pin = 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() def test_input_with_pull(pins): test_pin, input_pin = pins test_pin.input_with_pull('up') assert input_pin.state == 1 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) gpiozero-1.3.1.post1/tests/test_boards.py0000644000175000017500000006033013046621340017777 0ustar benben00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import sys import pytest from time import sleep 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', 'test_led_borg', 'test_snow_pi_initial_value_pwm'): gpiozero.devices.pin_factory = MockPWMPin else: gpiozero.devices.pin_factory = MockPin def teardown_function(function): MockPin.clear_pins() def test_composite_output_on_off(): pin1 = MockPin(2) pin2 = MockPin(3) pin3 = MockPin(4) with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device: 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) with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device: 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) with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device: 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) with LEDBoard(pin1, pin2, foo=pin3) as board: assert isinstance(board[0], LED) assert isinstance(board[1], LED) assert isinstance(board[2], LED) assert board.active_high assert board[0].active_high assert board[1].active_high assert board[2].active_high board.on() assert all((pin1.state, pin2.state, pin3.state)) board.off() 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 board.toggle(0,1) assert board.value == (1, 0, 1) assert pin1.state assert not pin2.state assert pin3.state board.off(2) assert board.value == (1, 0, 0) assert pin1.state assert not pin2.state assert not pin3.state board.on(1) assert board.value == (1, 1, 0) assert pin1.state assert pin2.state assert not pin3.state board.off(0,1) assert board.value == (0, 0, 0) assert not pin1.state assert not pin2.state assert not pin3.state board.on(1,2) assert board.value == (0, 1, 1) assert not pin1.state assert pin2.state assert pin3.state board.toggle(0) assert board.value == (1, 1, 1) assert pin1.state assert pin2.state assert pin3.state def test_led_board_active_low(): pin1 = MockPin(2) pin2 = MockPin(3) pin3 = MockPin(4) with LEDBoard(pin1, pin2, foo=pin3, active_high=False) as board: assert not board.active_high assert not board[0].active_high assert not board[1].active_high assert not board[2].active_high board.on() assert not any ((pin1.state, pin2.state, pin3.state)) board.off() assert all((pin1.state, pin2.state, pin3.state)) board[0].on() assert board.value == (1, 0, 0) assert not pin1.state assert pin2.state assert pin3.state board.toggle() assert board.value == (0, 1, 1) assert pin1.state assert not pin2.state assert not pin3.state def test_led_board_value(): pin1 = MockPin(2) pin2 = MockPin(3) pin3 = MockPin(4) with LEDBoard(pin1, pin2, foo=pin3) as board: assert board.value == (0, 0, 0) board.value = (0, 1, 0) assert board.value == (0, 1, 0) board.value = (1, 0, 1) assert board.value == (1, 0, 1) def test_led_board_pwm_value(): pin1 = MockPWMPin(2) pin2 = MockPWMPin(3) pin3 = MockPWMPin(4) with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board: assert board.value == (0, 0, 0) board.value = (0, 1, 0) assert board.value == (0, 1, 0) board.value = (0.5, 0, 0.75) assert board.value == (0.5, 0, 0.75) def test_led_board_pwm_bad_value(): pin1 = MockPWMPin(2) pin2 = MockPWMPin(3) pin3 = MockPWMPin(4) with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board: with pytest.raises(ValueError): board.value = (-1, 0, 0) with pytest.raises(ValueError): board.value = (0, 2, 0) def test_led_board_initial_value(): pin1 = MockPin(2) pin2 = MockPin(3) pin3 = MockPin(4) with LEDBoard(pin1, pin2, foo=pin3, initial_value=0) as board: assert board.value == (0, 0, 0) with LEDBoard(pin1, pin2, foo=pin3, initial_value=1) as board: assert board.value == (1, 1, 1) def test_led_board_pwm_initial_value(): pin1 = MockPWMPin(2) pin2 = MockPWMPin(3) pin3 = MockPWMPin(4) with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=0) as board: assert board.value == (0, 0, 0) with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=1) as board: assert board.value == (1, 1, 1) with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=0.5) as board: assert board.value == (0.5, 0.5, 0.5) def test_led_board_pwm_bad_initial_value(): pin1 = MockPWMPin(2) pin2 = MockPWMPin(3) pin3 = MockPWMPin(4) with pytest.raises(ValueError): LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=-1) with pytest.raises(ValueError): LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=2) def test_led_board_nested(): pin1 = MockPin(2) pin2 = MockPin(3) pin3 = MockPin(4) with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: 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) with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: 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) with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: 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) with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: 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) with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: board.blink(0.1, 0.1, n=2) # make sure the blink thread's started while not board._blink_leds: sleep(0.00001) # pragma: no cover board[1][0].off() # immediately take over the second LED board._blink_thread.join() # naughty, but ensures no arbitrary waits in the test test = [ (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) with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: 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) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_led_board_blink_control_all(): pin1 = MockPin(2) pin2 = MockPin(3) pin3 = MockPin(4) with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: board.blink(0.1, 0.1, n=2) # make sure the blink thread's started while not board._blink_leds: sleep(0.00001) # pragma: no cover 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) with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: 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) with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: 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) with LEDBoard(pin1, LEDBoard(pin2, pin3, pwm=True), pwm=True) as board: board.blink(0, 0, 0.2, 0.2, n=2) board._blink_thread.join() test = [ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 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) with LEDBarGraph(pin1, pin2, pin3) as graph: assert isinstance(graph[0], LED) assert isinstance(graph[1], LED) assert isinstance(graph[2], LED) assert graph.active_high assert graph[0].active_high assert graph[1].active_high assert graph[2].active_high graph.value = 0 assert graph.value == 0 assert not any((pin1.state, pin2.state, pin3.state)) graph.value = 1 assert graph.value == 1 assert all((pin1.state, pin2.state, pin3.state)) graph.value = 1/3 assert graph.value == 1/3 assert pin1.state and not (pin2.state or pin3.state) graph.value = -1/3 assert graph.value == -1/3 assert pin3.state and not (pin1.state or pin2.state) pin1.state = True pin2.state = True 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_active_low(): pin1 = MockPin(2) pin2 = MockPin(3) pin3 = MockPin(4) with LEDBarGraph(pin1, pin2, pin3, active_high=False) as graph: assert not graph.active_high assert not graph[0].active_high assert not graph[1].active_high assert not graph[2].active_high graph.value = 0 assert graph.value == 0 assert all((pin1.state, pin2.state, pin3.state)) graph.value = 1 assert graph.value == 1 assert not any((pin1.state, pin2.state, pin3.state)) graph.value = 1/3 assert graph.value == 1/3 assert not pin1.state and pin2.state and pin3.state graph.value = -1/3 assert graph.value == -1/3 assert not pin3.state and pin1.state and pin2.state def test_led_bar_graph_pwm_value(): pin1 = MockPWMPin(2) pin2 = MockPWMPin(3) pin3 = MockPWMPin(4) with LEDBarGraph(pin1, pin2, pin3, pwm=True) as graph: assert isinstance(graph[0], PWMLED) assert isinstance(graph[1], PWMLED) assert isinstance(graph[2], PWMLED) graph.value = 0 assert graph.value == 0 assert not any((pin1.state, pin2.state, pin3.state)) graph.value = 1 assert graph.value == 1 assert all((pin1.state, pin2.state, pin3.state)) graph.value = 1/3 assert graph.value == 1/3 assert pin1.state and not (pin2.state or pin3.state) graph.value = -1/3 assert graph.value == -1/3 assert pin3.state and not (pin1.state or pin2.state) graph.value = 1/2 assert graph.value == 1/2 assert (pin1.state, pin2.state, pin3.state) == (1, 0.5, 0) pin1.state = 0 pin3.state = 1 assert graph.value == -1/2 def test_led_bar_graph_bad_value(): pin1 = MockPin(2) pin2 = MockPin(3) pin3 = MockPin(4) with LEDBarGraph(pin1, pin2, pin3) as graph: with pytest.raises(ValueError): graph.value = -2 with pytest.raises(ValueError): graph.value = 2 def test_led_bar_graph_bad_init(): pin1 = MockPin(2) pin2 = MockPin(3) pin3 = MockPin(4) with pytest.raises(TypeError): LEDBarGraph(pin1, pin2, foo=pin3) with pytest.raises(ValueError): LEDBarGraph(pin1, pin2, pin3, initial_value=-2) with pytest.raises(ValueError): LEDBarGraph(pin1, pin2, pin3, initial_value=2) def test_led_bar_graph_initial_value(): pin1 = MockPin(2) pin2 = MockPin(3) pin3 = MockPin(4) with LEDBarGraph(pin1, pin2, pin3, initial_value=1/3) as graph: assert graph.value == 1/3 assert pin1.state and not (pin2.state or pin3.state) with LEDBarGraph(pin1, pin2, pin3, initial_value=-1/3) as graph: assert graph.value == -1/3 assert pin3.state and not (pin1.state or pin2.state) def test_led_bar_graph_pwm_initial_value(): pin1 = MockPWMPin(2) pin2 = MockPWMPin(3) pin3 = MockPWMPin(4) with LEDBarGraph(pin1, pin2, pin3, pwm=True, initial_value=0.5) as graph: assert graph.value == 0.5 assert (pin1.state, pin2.state, pin3.state) == (1, 0.5, 0) with LEDBarGraph(pin1, pin2, pin3, pwm=True, initial_value=-0.5) as graph: assert graph.value == -0.5 assert (pin1.state, pin2.state, pin3.state) == (0, 0.5, 1) def test_led_borg(): pins = [MockPWMPin(n) for n in (17, 27, 22)] with LedBorg() as board: assert [device.pin for device in board._leds] == pins def test_pi_liter(): pins = [MockPin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)] with PiLiter() as board: 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)] with PiLiterBarGraph() as board: 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) with TrafficLights(red_pin, amber_pin, green_pin) as board: board.red.on() assert board.red.value assert not board.amber.value assert not board.yellow.value assert not board.green.value assert red_pin.state assert not amber_pin.state assert not green_pin.state with TrafficLights(red=red_pin, yellow=amber_pin, green=green_pin) as board: board.yellow.on() assert not board.red.value assert board.amber.value assert board.yellow.value assert not board.green.value assert not red_pin.state assert amber_pin.state assert not green_pin.state def test_traffic_lights_bad_init(): with pytest.raises(ValueError): TrafficLights() red_pin = MockPin(2) amber_pin = MockPin(3) green_pin = MockPin(4) yellow_pin = MockPin(5) with pytest.raises(ValueError): TrafficLights(red=red_pin, amber=amber_pin, yellow=yellow_pin, green=green_pin) def test_pi_traffic(): pins = [MockPin(n) for n in (9, 10, 11)] with PiTraffic() as board: 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)] with SnowPi() as board: assert [device.pin for device in board.leds] == pins def test_snow_pi_initial_value(): with SnowPi() as board: assert all(device.pin.state == False for device in board.leds) with SnowPi(initial_value=False) as board: assert all(device.pin.state == False for device in board.leds) with SnowPi(initial_value=True) as board: assert all(device.pin.state == True for device in board.leds) with SnowPi(initial_value=0.5) as board: assert all(device.pin.state == True for device in board.leds) def test_snow_pi_initial_value_pwm(): pins = [MockPWMPin(n) for n in (23, 24, 25, 17, 18, 22, 7, 8, 9)] with SnowPi(pwm=True, initial_value=0.5) as board: assert [device.pin for device in board.leds] == pins assert all(device.pin.state == 0.5 for device in board.leds) def test_traffic_lights_buzzer(): red_pin = MockPin(2) amber_pin = MockPin(3) green_pin = MockPin(4) buzzer_pin = MockPin(5) button_pin = MockPin(6) with TrafficLightsBuzzer( TrafficLights(red_pin, amber_pin, green_pin), Buzzer(buzzer_pin), Button(button_pin)) as board: 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)] with FishDish() as board: 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)] with TrafficHat() as board: 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)] with Robot((2, 3), (4, 5)) as robot: assert ( [device.pin for device in robot.left_motor] + [device.pin for device in robot.right_motor]) == pins assert robot.value == (0, 0) robot.forward() assert [pin.state for pin in pins] == [1, 0, 1, 0] assert robot.value == (1, 1) robot.backward() assert [pin.state for pin in pins] == [0, 1, 0, 1] assert robot.value == (-1, -1) robot.forward(0.5) assert [pin.state for pin in pins] == [0.5, 0, 0.5, 0] assert robot.value == (0.5, 0.5) robot.left() assert [pin.state for pin in pins] == [0, 1, 1, 0] assert robot.value == (-1, 1) robot.right() assert [pin.state for pin in pins] == [1, 0, 0, 1] assert robot.value == (1, -1) robot.reverse() assert [pin.state for pin in pins] == [0, 1, 1, 0] assert robot.value == (-1, 1) robot.stop() assert [pin.state for pin in pins] == [0, 0, 0, 0] assert robot.value == (0, 0) robot.value = (-1, -1) assert robot.value == (-1, -1) robot.value = (0.5, 1) assert robot.value == (0.5, 1) robot.value = (0, -0.5) assert robot.value == (0, -0.5) def test_ryanteck_robot(): pins = [MockPWMPin(n) for n in (17, 18, 22, 23)] with RyanteckRobot() as board: 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)] with CamJamKitRobot() as board: 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) with pytest.raises(ValueError): Energenie(5) def test_energenie(): pins = [MockPin(n) for n in (17, 22, 23, 27, 24, 25)] with Energenie(1, initial_value=True) as device1, \ Energenie(2, initial_value=False) as device2: assert repr(device1) == '' assert repr(device2) == '' 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)]) device1.close() assert repr(device1) == '' gpiozero-1.3.1.post1/tests/test_inputs.py0000644000175000017500000001415113046621340020047 0ustar benben00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import sys import pytest from threading import Event from gpiozero.pins.mock import ( MockPin, MockPulledUpPin, MockChargingPin, MockTriggerPin, ) from gpiozero import * def teardown_function(function): MockPin.clear_pins() def test_input_initial_values(): pin = MockPin(2) with InputDevice(pin, pull_up=True) as device: assert pin.function == 'input' assert pin.pull == 'up' assert device.pull_up device.close() device = InputDevice(pin, pull_up=False) assert pin.pull == 'down' assert not device.pull_up def test_input_is_active_low(): pin = MockPin(2) with InputDevice(pin, pull_up=True) as device: pin.drive_high() assert not device.is_active assert repr(device) == '' pin.drive_low() assert device.is_active assert repr(device) == '' def test_input_is_active_high(): pin = MockPin(2) with InputDevice(pin, pull_up=False) as device: pin.drive_high() assert device.is_active assert repr(device) == '' pin.drive_low() assert not device.is_active assert repr(device) == '' def test_input_pulled_up(): pin = MockPulledUpPin(2) with pytest.raises(PinFixedPull): InputDevice(pin, pull_up=False) def test_input_event_activated(): event = Event() pin = MockPin(2) with DigitalInputDevice(pin) as device: device.when_activated = lambda: event.set() assert not event.is_set() pin.drive_high() assert event.is_set() def test_input_event_deactivated(): event = Event() pin = MockPin(2) with DigitalInputDevice(pin) as device: device.when_deactivated = lambda: event.set() assert not event.is_set() pin.drive_high() assert not event.is_set() pin.drive_low() assert event.is_set() def test_input_wait_active(): pin = MockPin(2) with DigitalInputDevice(pin) as device: pin.drive_high() assert device.wait_for_active(1) assert not device.wait_for_inactive(0) def test_input_wait_inactive(): pin = MockPin(2) with DigitalInputDevice(pin) as device: assert device.wait_for_inactive(1) assert not device.wait_for_active(0) def test_input_smoothed_attrib(): pin = MockPin(2) with SmoothedInputDevice(pin, threshold=0.5, queue_len=5, partial=False) as device: assert repr(device) == '' assert device.threshold == 0.5 assert device.queue_len == 5 assert not device.partial device._queue.start() assert not device.is_active with pytest.raises(InputDeviceError): device.threshold = 1 def test_input_smoothed_values(): pin = MockPin(2) with SmoothedInputDevice(pin) as device: device._queue.start() assert not device.is_active pin.drive_high() assert device.wait_for_active(1) pin.drive_low() assert device.wait_for_inactive(1) def test_input_button(): pin = MockPin(2) with Button(pin) as button: assert pin.pull == 'up' assert not button.is_pressed pin.drive_low() assert button.is_pressed assert button.wait_for_press(1) pin.drive_high() assert not button.is_pressed assert button.wait_for_release(1) def test_input_line_sensor(): pin = MockPin(2) with LineSensor(pin) as sensor: pin.drive_low() # logic is inverted for line sensor assert sensor.wait_for_line(1) assert sensor.line_detected pin.drive_high() assert sensor.wait_for_no_line(1) assert not sensor.line_detected def test_input_motion_sensor(): pin = MockPin(2) with MotionSensor(pin) as sensor: pin.drive_high() assert sensor.wait_for_motion(1) assert sensor.motion_detected pin.drive_low() assert sensor.wait_for_no_motion(1) assert not sensor.motion_detected @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_input_light_sensor(): pin = MockChargingPin(2) with LightSensor(pin) as sensor: pin.charge_time = 0.1 assert sensor.wait_for_dark(1) pin.charge_time = 0.0 assert sensor.wait_for_light(1) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_input_distance_sensor(): echo_pin = MockPin(2) trig_pin = MockTriggerPin(3) trig_pin.echo_pin = echo_pin trig_pin.echo_time = 0.02 with pytest.raises(ValueError): DistanceSensor(echo_pin, trig_pin, max_distance=-1) # normal queue len is large (because the sensor is *really* jittery) but # we want quick tests and we've got precisely controlled pins :) with DistanceSensor(echo_pin, trig_pin, queue_len=5, max_distance=1) as sensor: assert sensor.max_distance == 1 assert sensor.trigger is trig_pin assert sensor.echo is echo_pin assert sensor.wait_for_out_of_range(1) assert not sensor.in_range assert sensor.distance == 1.0 # should be waay before max-distance so this should work trig_pin.echo_time = 0.0 assert sensor.wait_for_in_range(1) assert sensor.in_range assert sensor.distance < sensor.threshold_distance # depending on speed of machine, may not reach 0 here sensor.threshold_distance = 0.1 assert sensor.threshold_distance == 0.1 with pytest.raises(ValueError): sensor.max_distance = -1 sensor.max_distance = 20 assert sensor.max_distance == 20 assert sensor.threshold_distance == 0.1 gpiozero-1.3.1.post1/tests/test_pins_data.py0000644000175000017500000000660713046621340020476 0ustar benben00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import pytest from mock import patch, MagicMock import gpiozero.devices import gpiozero.pins.data import gpiozero.pins.native from gpiozero.pins.data import pi_info from gpiozero import PinMultiplePins, PinNoPins, PinUnknownPi def test_pi_revision(): save_factory = gpiozero.devices.pin_factory try: # Can't use MockPin for this as we want something that'll actually try # and read /proc/cpuinfo (MockPin simply parrots the 2B's data); # NativePin is used as we're guaranteed to be able to import it gpiozero.devices.pin_factory = gpiozero.pins.native.NativePin with patch('io.open') as m: m.return_value.__enter__.return_value = ['lots of irrelevant', 'lines', 'followed by', 'Revision: 0002', 'Serial: xxxxxxxxxxx'] assert pi_info().revision == '0002' # LocalPin caches the revision (because realistically it isn't going to # change at runtime); we need to wipe it here though gpiozero.pins.native.NativePin._PI_REVISION = None m.return_value.__enter__.return_value = ['Revision: a21042'] assert pi_info().revision == 'a21042' # Check over-volting result (some argument over whether this is 7 or # 8 character result; make sure both work) gpiozero.pins.native.NativePin._PI_REVISION = None m.return_value.__enter__.return_value = ['Revision: 1000003'] assert pi_info().revision == '0003' gpiozero.pins.native.NativePin._PI_REVISION = None m.return_value.__enter__.return_value = ['Revision: 100003'] assert pi_info().revision == '0003' with pytest.raises(PinUnknownPi): m.return_value.__enter__.return_value = ['nothing', 'relevant', 'at all'] gpiozero.pins.native.NativePin._PI_REVISION = None pi_info() with pytest.raises(PinUnknownPi): pi_info('0fff') finally: gpiozero.devices.pin_factory = save_factory def test_pi_info(): r = pi_info('900011') assert r.model == 'B' assert r.pcb_revision == '1.0' assert r.memory == 512 assert r.manufacturer == 'Sony' assert r.storage == 'SD' assert r.usb == 2 assert not r.wifi assert not r.bluetooth assert r.csi == 1 assert r.dsi == 1 with pytest.raises(PinUnknownPi): pi_info('9000f1') def test_pi_info_other_types(): with pytest.raises(PinUnknownPi): pi_info(b'9000f1') with pytest.raises(PinUnknownPi): pi_info(0x9000f1) def test_physical_pins(): # Assert physical pins for some well-known Pi's; a21041 is a Pi2B assert pi_info('a21041').physical_pins('3V3') == {('P1', 1), ('P1', 17)} assert pi_info('a21041').physical_pins('GPIO2') == {('P1', 3)} assert pi_info('a21041').physical_pins('GPIO47') == set() def test_physical_pin(): with pytest.raises(PinMultiplePins): assert pi_info('a21041').physical_pin('GND') assert pi_info('a21041').physical_pin('GPIO3') == ('P1', 5) with pytest.raises(PinNoPins): assert pi_info('a21041').physical_pin('GPIO47') def test_pulled_up(): assert pi_info('a21041').pulled_up('GPIO2') assert not pi_info('a21041').pulled_up('GPIO4') assert not pi_info('a21041').pulled_up('GPIO47') gpiozero-1.3.1.post1/tests/test_outputs.py0000644000175000017500000007702112767742026020273 0ustar benben00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import sys from time import sleep, time try: from math import isclose except ImportError: from gpiozero.compat import isclose import pytest from gpiozero.pins.mock import MockPin, MockPWMPin from gpiozero import * def teardown_function(function): MockPin.clear_pins() def test_output_initial_values(): pin = MockPin(2) with OutputDevice(pin, initial_value=False) as device: assert pin.function == 'output' assert not pin.state with OutputDevice(pin, initial_value=True) as device: assert pin.state state = pin.state with OutputDevice(pin, initial_value=None) as device: assert state == pin.state def test_output_write_active_high(): pin = MockPin(2) with OutputDevice(pin) as device: device.on() assert pin.state device.off() assert not pin.state def test_output_write_active_low(): pin = MockPin(2) with OutputDevice(pin, active_high=False) as device: device.on() assert not pin.state device.off() assert pin.state def test_output_write_closed(): with OutputDevice(MockPin(2)) as device: device.close() assert device.closed device.close() assert device.closed with pytest.raises(GPIODeviceClosed): device.on() def test_output_write_silly(): pin = MockPin(2) with OutputDevice(pin) as device: pin.function = 'input' with pytest.raises(AttributeError): device.on() def test_output_value(): pin = MockPin(2) with OutputDevice(pin) as device: assert not device.value assert not pin.state device.on() assert device.value assert pin.state device.value = False assert not device.value assert not pin.state def test_output_digital_toggle(): pin = MockPin(2) with DigitalOutputDevice(pin) as device: assert not device.value assert not pin.state device.toggle() assert device.value assert pin.state device.toggle() assert not device.value assert not pin.state @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_output_blink_background(): pin = MockPin(2) with DigitalOutputDevice(pin) as device: start = time() device.blink(0.1, 0.1, n=2) assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() # naughty, but ensures no arbitrary waits in the test assert isclose(time() - start, 0.4, abs_tol=0.05) pin.assert_states_and_times([ (0.0, False), (0.0, True), (0.1, False), (0.1, True), (0.1, False) ]) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_output_blink_foreground(): pin = MockPin(2) with DigitalOutputDevice(pin) as device: start = time() device.blink(0.1, 0.1, n=2, background=False) assert isclose(time() - start, 0.4, abs_tol=0.05) pin.assert_states_and_times([ (0.0, False), (0.0, True), (0.1, False), (0.1, True), (0.1, False) ]) def test_output_blink_interrupt_on(): pin = MockPin(2) with DigitalOutputDevice(pin) as device: device.blink(1, 0.1) sleep(0.2) device.off() # should interrupt while on pin.assert_states([False, True, False]) def test_output_blink_interrupt_off(): pin = MockPin(2) with DigitalOutputDevice(pin) as device: device.blink(0.1, 1) sleep(0.2) device.off() # should interrupt while off pin.assert_states([False, True, False]) def test_output_pwm_bad_initial_value(): with pytest.raises(ValueError): PWMOutputDevice(MockPin(2), initial_value=2) def test_output_pwm_not_supported(): with pytest.raises(AttributeError): PWMOutputDevice(MockPin(2)) def test_output_pwm_states(): pin = MockPWMPin(2) with PWMOutputDevice(pin) as device: device.value = 0.1 device.value = 0.2 device.value = 0.0 pin.assert_states([0.0, 0.1, 0.2, 0.0]) def test_output_pwm_read(): pin = MockPWMPin(2) with PWMOutputDevice(pin, frequency=100) as device: assert device.frequency == 100 device.value = 0.1 assert isclose(device.value, 0.1) assert isclose(pin.state, 0.1) assert device.is_active device.frequency = None assert not device.value assert not device.is_active assert device.frequency is None def test_output_pwm_write(): pin = MockPWMPin(2) with PWMOutputDevice(pin) as device: device.on() device.off() pin.assert_states([False, True, False]) def test_output_pwm_toggle(): pin = MockPWMPin(2) with PWMOutputDevice(pin) as device: device.toggle() device.value = 0.5 device.value = 0.1 device.toggle() device.off() pin.assert_states([False, True, 0.5, 0.1, 0.9, False]) def test_output_pwm_active_high_read(): pin = MockPWMPin(2) with PWMOutputDevice(pin, active_high=False) as device: device.value = 0.1 assert isclose(device.value, 0.1) assert isclose(pin.state, 0.9) device.frequency = None assert device.value def test_output_pwm_bad_value(): with pytest.raises(ValueError): PWMOutputDevice(MockPWMPin(2)).value = 2 def test_output_pwm_write_closed(): device = PWMOutputDevice(MockPWMPin(2)) device.close() with pytest.raises(GPIODeviceClosed): device.on() def test_output_pwm_write_silly(): pin = MockPWMPin(2) with PWMOutputDevice(pin) as device: pin.function = 'input' with pytest.raises(AttributeError): device.off() @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_output_pwm_blink_background(): pin = MockPWMPin(2) with PWMOutputDevice(pin) as device: start = time() device.blink(0.1, 0.1, n=2) assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() assert isclose(time() - start, 0.4, abs_tol=0.05) pin.assert_states_and_times([ (0.0, 0), (0.0, 1), (0.1, 0), (0.1, 1), (0.1, 0) ]) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_output_pwm_blink_foreground(): pin = MockPWMPin(2) with PWMOutputDevice(pin) as device: start = time() device.blink(0.1, 0.1, n=2, background=False) assert isclose(time() - start, 0.4, abs_tol=0.05) pin.assert_states_and_times([ (0.0, 0), (0.0, 1), (0.1, 0), (0.1, 1), (0.1, 0) ]) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_output_pwm_fade_background(): pin = MockPWMPin(2) with PWMOutputDevice(pin) as device: start = time() device.blink(0, 0, 0.2, 0.2, n=2) assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() assert isclose(time() - start, 0.8, abs_tol=0.05) pin.assert_states_and_times([ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), ]) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_output_pwm_fade_foreground(): pin = MockPWMPin(2) with PWMOutputDevice(pin) as device: start = time() device.blink(0, 0, 0.2, 0.2, n=2, background=False) assert isclose(time() - start, 0.8, abs_tol=0.05) pin.assert_states_and_times([ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), ]) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_output_pwm_pulse_background(): pin = MockPWMPin(2) with PWMOutputDevice(pin) as device: start = time() device.pulse(0.2, 0.2, n=2) assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() assert isclose(time() - start, 0.8, abs_tol=0.05) pin.assert_states_and_times([ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), ]) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_output_pwm_pulse_foreground(): pin = MockPWMPin(2) with PWMOutputDevice(pin) as device: start = time() device.pulse(0.2, 0.2, n=2, background=False) assert isclose(time() - start, 0.8, abs_tol=0.05) pin.assert_states_and_times([ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), ]) def test_output_pwm_blink_interrupt(): pin = MockPWMPin(2) with PWMOutputDevice(pin) as device: device.blink(1, 0.1) sleep(0.2) device.off() # should interrupt while on pin.assert_states([0, 1, 0]) def test_rgbled_missing_pins(): with pytest.raises(ValueError): RGBLED() def test_rgbled_initial_value(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, initial_value=(0.1, 0.2, 0)) as device: assert r.frequency assert g.frequency assert b.frequency assert isclose(r.state, 0.1) assert isclose(g.state, 0.2) assert isclose(b.state, 0.0) def test_rgbled_initial_value_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False, initial_value=(0, 1, 1)) as device: assert r.state == 0 assert g.state == 1 assert b.state == 1 def test_rgbled_initial_bad_value(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with pytest.raises(ValueError): RGBLED(r, g, b, initial_value=(0.1, 0.2, 1.2)) def test_rgbled_initial_bad_value_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with pytest.raises(ValueError): RGBLED(r, g, b, pwm=False, initial_value=(0.1, 0.2, 0)) def test_rgbled_value(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: assert isinstance(device._leds[0], PWMLED) assert isinstance(device._leds[1], PWMLED) assert isinstance(device._leds[2], PWMLED) assert not device.is_active assert device.value == (0, 0, 0) device.on() assert device.is_active assert device.value == (1, 1, 1) device.off() assert not device.is_active assert device.value == (0, 0, 0) device.value = (0.5, 0.5, 0.5) assert device.is_active assert device.value == (0.5, 0.5, 0.5) def test_rgbled_value_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: assert isinstance(device._leds[0], LED) assert isinstance(device._leds[1], LED) assert isinstance(device._leds[2], LED) assert not device.is_active assert device.value == (0, 0, 0) device.on() assert device.is_active assert device.value == (1, 1, 1) device.off() assert not device.is_active assert device.value == (0, 0, 0) def test_rgbled_bad_value(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: with pytest.raises(ValueError): device.value = (2, 0, 0) with RGBLED(r, g, b) as device: with pytest.raises(ValueError): device.value = (0, -1, 0) def test_rgbled_bad_value_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: with pytest.raises(ValueError): device.value = (2, 0, 0) with RGBLED(r, g, b, pwm=False) as device: with pytest.raises(ValueError): device.value = (0, -1, 0) with RGBLED(r, g, b, pwm=False) as device: with pytest.raises(ValueError): device.value = (0.5, 0, 0) with RGBLED(r, g, b, pwm=False) as device: with pytest.raises(ValueError): device.value = (0, 0.5, 0) with RGBLED(r, g, b, pwm=False) as device: with pytest.raises(ValueError): device.value = (0, 0, 0.5) def test_rgbled_toggle(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: assert not device.is_active assert device.value == (0, 0, 0) device.toggle() assert device.is_active assert device.value == (1, 1, 1) device.toggle() assert not device.is_active assert device.value == (0, 0, 0) def test_rgbled_toggle_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: assert not device.is_active assert device.value == (0, 0, 0) device.toggle() assert device.is_active assert device.value == (1, 1, 1) device.toggle() assert not device.is_active assert device.value == (0, 0, 0) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_blink_background(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: start = time() device.blink(0.1, 0.1, n=2) assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() assert isclose(time() - start, 0.4, abs_tol=0.05) expected = [ (0.0, 0), (0.0, 1), (0.1, 0), (0.1, 1), (0.1, 0) ] r.assert_states_and_times(expected) g.assert_states_and_times(expected) b.assert_states_and_times(expected) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_blink_background_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: start = time() device.blink(0.1, 0.1, n=2) assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() assert isclose(time() - start, 0.4, abs_tol=0.05) expected = [ (0.0, 0), (0.0, 1), (0.1, 0), (0.1, 1), (0.1, 0) ] r.assert_states_and_times(expected) g.assert_states_and_times(expected) b.assert_states_and_times(expected) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_blink_foreground(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: start = time() device.blink(0.1, 0.1, n=2, background=False) assert isclose(time() - start, 0.4, abs_tol=0.05) expected = [ (0.0, 0), (0.0, 1), (0.1, 0), (0.1, 1), (0.1, 0) ] r.assert_states_and_times(expected) g.assert_states_and_times(expected) b.assert_states_and_times(expected) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_blink_foreground_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: start = time() device.blink(0.1, 0.1, n=2, background=False) assert isclose(time() - start, 0.4, abs_tol=0.05) expected = [ (0.0, 0), (0.0, 1), (0.1, 0), (0.1, 1), (0.1, 0) ] r.assert_states_and_times(expected) g.assert_states_and_times(expected) b.assert_states_and_times(expected) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_fade_background(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: start = time() device.blink(0, 0, 0.2, 0.2, n=2) assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() assert isclose(time() - start, 0.8, abs_tol=0.05) expected = [ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), ] r.assert_states_and_times(expected) g.assert_states_and_times(expected) b.assert_states_and_times(expected) def test_rgbled_fade_background_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: with pytest.raises(ValueError): device.blink(0, 0, 0.2, 0.2, n=2) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_fade_foreground(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: start = time() device.blink(0, 0, 0.2, 0.2, n=2, background=False) assert isclose(time() - start, 0.8, abs_tol=0.05) expected = [ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), ] r.assert_states_and_times(expected) g.assert_states_and_times(expected) b.assert_states_and_times(expected) def test_rgbled_fade_foreground_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: with pytest.raises(ValueError): device.blink(0, 0, 0.2, 0.2, n=2, background=False) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_pulse_background(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: start = time() device.pulse(0.2, 0.2, n=2) assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() assert isclose(time() - start, 0.8, abs_tol=0.05) expected = [ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), ] r.assert_states_and_times(expected) g.assert_states_and_times(expected) b.assert_states_and_times(expected) def test_rgbled_pulse_background_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: with pytest.raises(ValueError): device.pulse(0.2, 0.2, n=2) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_pulse_foreground(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: start = time() device.pulse(0.2, 0.2, n=2, background=False) assert isclose(time() - start, 0.8, abs_tol=0.05) expected = [ (0.0, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), (0.04, 0.2), (0.04, 0.4), (0.04, 0.6), (0.04, 0.8), (0.04, 1), (0.04, 0.8), (0.04, 0.6), (0.04, 0.4), (0.04, 0.2), (0.04, 0), ] r.assert_states_and_times(expected) g.assert_states_and_times(expected) b.assert_states_and_times(expected) def test_rgbled_pulse_foreground_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: with pytest.raises(ValueError): device.pulse(0.2, 0.2, n=2, background=False) def test_rgbled_blink_interrupt(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: device.blink(1, 0.1) sleep(0.2) device.off() # should interrupt while on r.assert_states([0, 1, 0]) g.assert_states([0, 1, 0]) b.assert_states([0, 1, 0]) def test_rgbled_blink_interrupt_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: device.blink(1, 0.1) sleep(0.2) device.off() # should interrupt while on r.assert_states([0, 1, 0]) g.assert_states([0, 1, 0]) b.assert_states([0, 1, 0]) def test_rgbled_close(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: assert not device.closed device.close() assert device.closed device.close() assert device.closed def test_rgbled_close_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: assert not device.closed device.close() assert device.closed device.close() assert device.closed def test_motor_missing_pins(): with pytest.raises(ValueError): Motor() def test_motor_pins(): f = MockPWMPin(1) b = MockPWMPin(2) with Motor(f, b) as device: assert device.forward_device.pin is f assert isinstance(device.forward_device, PWMOutputDevice) assert device.backward_device.pin is b assert isinstance(device.backward_device, PWMOutputDevice) def test_motor_pins_nonpwm(): f = MockPin(1) b = MockPin(2) with Motor(f, b, pwm=False) as device: assert device.forward_device.pin is f assert isinstance(device.forward_device, DigitalOutputDevice) assert device.backward_device.pin is b assert isinstance(device.backward_device, DigitalOutputDevice) def test_motor_close(): f = MockPWMPin(1) b = MockPWMPin(2) with Motor(f, b) as device: device.close() assert device.closed assert device.forward_device.pin is None assert device.backward_device.pin is None device.close() assert device.closed def test_motor_close_nonpwm(): f = MockPin(1) b = MockPin(2) with Motor(f, b, pwm=False) as device: device.close() assert device.closed assert device.forward_device.pin is None assert device.backward_device.pin is None def test_motor_value(): f = MockPWMPin(1) b = MockPWMPin(2) with Motor(f, b) as device: device.value = -1 assert device.is_active assert device.value == -1 assert b.state == 1 and f.state == 0 device.value = 1 assert device.is_active assert device.value == 1 assert b.state == 0 and f.state == 1 device.value = 0.5 assert device.is_active assert device.value == 0.5 assert b.state == 0 and f.state == 0.5 device.value = -0.5 assert device.is_active assert device.value == -0.5 assert b.state == 0.5 and f.state == 0 device.value = 0 assert not device.is_active assert not device.value assert b.state == 0 and f.state == 0 def test_motor_value_nonpwm(): f = MockPin(1) b = MockPin(2) with Motor(f, b, pwm=False) as device: device.value = -1 assert device.is_active assert device.value == -1 assert b.state == 1 and f.state == 0 device.value = 1 assert device.is_active assert device.value == 1 assert b.state == 0 and f.state == 1 device.value = 0 assert not device.is_active assert not device.value assert b.state == 0 and f.state == 0 def test_motor_bad_value(): f = MockPWMPin(1) b = MockPWMPin(2) with Motor(f, b) as device: with pytest.raises(ValueError): device.value = -2 with pytest.raises(ValueError): device.value = 2 def test_motor_bad_value_nonpwm(): f = MockPin(1) b = MockPin(2) with Motor(f, b, pwm=False) as device: with pytest.raises(ValueError): device.value = -2 with pytest.raises(ValueError): device.value = 2 with pytest.raises(ValueError): device.value = 0.5 with pytest.raises(ValueError): device.value = -0.5 def test_motor_reverse(): f = MockPWMPin(1) b = MockPWMPin(2) with Motor(f, b) as device: device.forward() assert device.value == 1 assert b.state == 0 and f.state == 1 device.reverse() assert device.value == -1 assert b.state == 1 and f.state == 0 device.backward(0.5) assert device.value == -0.5 assert b.state == 0.5 and f.state == 0 device.reverse() assert device.value == 0.5 assert b.state == 0 and f.state == 0.5 def test_motor_reverse_nonpwm(): f = MockPin(1) b = MockPin(2) with Motor(f, b, pwm=False) as device: device.forward() assert device.value == 1 assert b.state == 0 and f.state == 1 device.reverse() assert device.value == -1 assert b.state == 1 and f.state == 0 def test_servo_pins(): p = MockPWMPin(1) with Servo(p) as device: assert device.pwm_device.pin is p assert isinstance(device.pwm_device, PWMOutputDevice) def test_servo_bad_value(): p = MockPWMPin(1) with pytest.raises(ValueError): Servo(p, initial_value=2) with pytest.raises(ValueError): Servo(p, min_pulse_width=30/1000) with pytest.raises(ValueError): Servo(p, max_pulse_width=30/1000) def test_servo_pins_nonpwm(): p = MockPin(2) with pytest.raises(PinPWMUnsupported): Servo(p) def test_servo_close(): p = MockPWMPin(2) with Servo(p) as device: device.close() assert device.closed assert device.pwm_device.pin is None device.close() assert device.closed def test_servo_pulse_width(): p = MockPWMPin(2) with Servo(p, min_pulse_width=5/10000, max_pulse_width=25/10000) as device: assert isclose(device.min_pulse_width, 5/10000) assert isclose(device.max_pulse_width, 25/10000) assert isclose(device.frame_width, 20/1000) assert isclose(device.pulse_width, 15/10000) device.value = -1 assert isclose(device.pulse_width, 5/10000) device.value = 1 assert isclose(device.pulse_width, 25/10000) device.value = None assert device.pulse_width is None def test_servo_values(): p = MockPWMPin(1) with Servo(p) as device: device.min() assert device.is_active assert device.value == -1 assert isclose(p.state, 0.05) device.max() assert device.is_active assert device.value == 1 assert isclose(p.state, 0.1) device.mid() assert device.is_active assert device.value == 0.0 assert isclose(p.state, 0.075) device.value = 0.5 assert device.is_active assert device.value == 0.5 assert isclose(p.state, 0.0875) device.detach() assert not device.is_active assert device.value is None device.value = 0 assert device.value == 0 device.value = None assert device.value is None def test_angular_servo_range(): p = MockPWMPin(1) with AngularServo(p, initial_angle=15, min_angle=0, max_angle=90) as device: assert device.min_angle == 0 assert device.max_angle == 90 def test_angular_servo_angles(): p = MockPWMPin(1) with AngularServo(p) as device: device.angle = 0 assert device.angle == 0 assert isclose(device.value, 0) device.max() assert device.angle == 90 assert isclose(device.value, 1) device.min() assert device.angle == -90 assert isclose(device.value, -1) device.detach() assert device.angle is None with AngularServo(p, initial_angle=15, min_angle=0, max_angle=90) as device: assert device.angle == 15 assert isclose(device.value, -2/3) device.angle = 0 assert device.angle == 0 assert isclose(device.value, -1) device.angle = 90 assert device.angle == 90 assert isclose(device.value, 1) device.angle = None assert device.angle is None with AngularServo(p, min_angle=45, max_angle=-45) as device: assert device.angle == 0 assert isclose(device.value, 0) device.angle = -45 assert device.angle == -45 assert isclose(device.value, 1) device.angle = -15 assert device.angle == -15 assert isclose(device.value, 1/3) gpiozero-1.3.1.post1/tests/test_mock_pin.py0000644000175000017500000001020612721617004020322 0ustar benben00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') from threading import Event import pytest from gpiozero.pins.mock import MockPin, MockPWMPin from gpiozero import * def teardown_function(function): MockPin.clear_pins() # Some rough tests to make sure our MockPin is up to snuff. This is just # enough to get reasonable coverage but it's by no means comprehensive... def test_mock_pin_init(): with pytest.raises(TypeError): MockPin() with pytest.raises(ValueError): MockPin(60) assert MockPin(2).number == 2 def test_mock_pin_defaults(): pin = MockPin(2) assert pin.bounce == None assert pin.edges == 'both' assert pin.frequency == None assert pin.function == 'input' assert pin.pull == 'floating' assert pin.state == 0 assert pin.when_changed == None def test_mock_pin_open_close(): pin = MockPin(2) pin.close() def test_mock_pin_init_twice_same_pin(): pin1 = MockPin(2) pin2 = MockPin(pin1.number) assert pin1 is pin2 def test_mock_pin_init_twice_different_pin(): pin1 = MockPin(2) pin2 = MockPin(pin1.number+1) assert pin1 != pin2 assert pin1.number == 2 assert pin2.number == pin1.number+1 def test_mock_pwm_pin_init(): with pytest.raises(TypeError): MockPWMPin() with pytest.raises(ValueError): MockPWMPin(60) assert MockPWMPin(2).number == 2 def test_mock_pwm_pin_defaults(): pin = MockPWMPin(2) assert pin.bounce == None assert pin.edges == 'both' assert pin.frequency == None assert pin.function == 'input' assert pin.pull == 'floating' assert pin.state == 0 assert pin.when_changed == None def test_mock_pwm_pin_open_close(): pin = MockPWMPin(2) pin.close() def test_mock_pwm_pin_init_twice_same_pin(): pin1 = MockPWMPin(2) pin2 = MockPWMPin(pin1.number) assert pin1 is pin2 def test_mock_pwm_pin_init_twice_different_pin(): pin1 = MockPWMPin(2) pin2 = MockPWMPin(pin1.number+1) assert pin1 != pin2 assert pin1.number == 2 assert pin2.number == pin1.number+1 def test_mock_pin_init_twice_different_modes(): pin1 = MockPin(2) pin2 = MockPWMPin(pin1.number+1) assert pin1 != pin2 with pytest.raises(ValueError): pin3 = MockPWMPin(pin1.number) with pytest.raises(ValueError): pin4 = MockPin(pin2.number) def test_mock_pin_frequency_unsupported(): pin = MockPin(2) pin.frequency = None with pytest.raises(PinPWMUnsupported): pin.frequency = 100 def test_mock_pin_frequency_supported(): pin = MockPWMPin(2) pin.function = 'output' assert pin.frequency is None pin.frequency = 100 pin.state = 0.5 pin.frequency = None assert not pin.state def test_mock_pin_pull(): pin = MockPin(2) pin.function = 'input' assert pin.pull == 'floating' pin.pull = 'up' assert pin.state pin.pull = 'down' assert not pin.state def test_mock_pin_state(): pin = MockPin(2) with pytest.raises(PinSetInput): pin.state = 1 pin.function = 'output' assert pin.state == 0 pin.state = 1 assert pin.state == 1 pin.state = 0 assert pin.state == 0 pin.state = 0.5 assert pin.state == 1 def test_mock_pwm_pin_state(): pin = MockPWMPin(2) with pytest.raises(PinSetInput): pin.state = 1 pin.function = 'output' assert pin.state == 0 pin.state = 1 assert pin.state == 1 pin.state = 0 assert pin.state == 0 pin.state = 0.5 assert pin.state == 0.5 def test_mock_pin_edges(): pin = MockPin(2) assert pin.when_changed is None fired = Event() pin.function = 'input' pin.edges = 'both' assert pin.edges == 'both' pin.drive_low() assert not pin.state def changed(): fired.set() pin.when_changed = changed pin.drive_high() assert pin.state assert fired.is_set() fired.clear() pin.edges = 'falling' pin.drive_low() assert not pin.state assert fired.is_set() fired.clear() pin.drive_high() assert pin.state assert not fired.is_set() assert pin.edges == 'falling' gpiozero-1.3.1.post1/tests/test_compat.py0000644000175000017500000000740012714337163020017 0ustar benben00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import pytest import random from gpiozero.compat import * # ported from the official test cases; see # https://github.com/python/cpython/blob/master/Lib/test/test_math.py for original NAN = float('nan') INF = float('inf') NINF = float('-inf') def test_isclose_negative_tolerances(): with pytest.raises(ValueError): isclose(1, 1, rel_tol=-1e-100) with pytest.raises(ValueError): isclose(1, 1, rel_tol=1e-100, abs_tol=-1e10) def test_isclose_identical(): examples = [ (2.0, 2.0), (0.1e200, 0.1e200), (1.123e-300, 1.123e-300), (12345, 12345.0), (0.0, -0.0), (345678, 345678), ] for a, b in examples: assert isclose(a, b, rel_tol=0.0, abs_tol=0.0) def test_isclose_eight_decimals(): examples = [ (1e8, 1e8 + 1), (-1e-8, -1.000000009e-8), (1.12345678, 1.12345679), ] for a, b in examples: assert isclose(a, b, rel_tol=1e-8) assert not isclose(a, b, rel_tol=1e-9) def test_isclose_near_zero(): examples = [1e-9, -1e-9, -1e-150] for a in examples: assert not isclose(a, 0.0, rel_tol=0.9) assert isclose(a, 0.0, abs_tol=1e-8) def test_isclose_inf(): assert isclose(INF, INF) assert isclose(INF, INF, abs_tol=0.0) assert isclose(NINF, NINF) assert isclose(NINF, NINF, abs_tol=0.0) def test_isclose_inf_ninf_nan(): examples = [ (NAN, NAN), (NAN, 1e-100), (1e-100, NAN), (INF, NAN), (NAN, INF), (INF, NINF), (INF, 1.0), (1.0, INF), (INF, 1e308), (1e308, INF), ] for a, b in examples: assert not isclose(a, b, abs_tol=0.999999999999999) def test_isclose_zero_tolerance(): examples = [ (1.0, 1.0), (-3.4, -3.4), (-1e-300, -1e-300), ] for a, b in examples: assert isclose(a, b, rel_tol=0.0) examples = [ (1.0, 1.000000000000001), (0.99999999999999, 1.0), (1.0e200, .999999999999999e200), ] for a, b in examples: assert not isclose(a, b, rel_tol=0.0) def test_isclose_assymetry(): assert isclose(9, 10, rel_tol=0.1) assert isclose(10, 9, rel_tol=0.1) def test_isclose_integers(): examples = [ (100000001, 100000000), (123456789, 123456788), ] for a, b in examples: assert isclose(a, b, rel_tol=1e-8) assert not isclose(a, b, rel_tol=1e-9) # ported from the official test cases; see # https://github.com/python/cpython/blob/master/Lib/test/test_statistics.py for # original def test_mean(): examples = [ (4.8125, (0, 1, 2, 3, 3, 3, 4, 5, 5, 6, 7, 7, 7, 7, 8, 9)), (22.015625, (17.25, 19.75, 20.0, 21.5, 21.75, 23.25, 25.125, 27.5)), (INF, (1, 3, 5, 7, 9, INF)), (NINF, (1, 3, 5, 7, 9, NINF)), ] for result, values in examples: values = list(values) random.shuffle(values) assert mean(values) == result assert mean(iter(values)) == result def test_mean_big_data(): c = 1e9 data = [3.4, 4.5, 4.9, 6.7, 6.8, 7.2, 8.0, 8.1, 9.4] expected = mean(data) + c assert expected != c assert mean([x + c for x in data]) == expected def test_mean_doubled_data(): data = [random.uniform(-3, 5) for _ in range(1000)] expected = mean(data) actual = mean(data * 2) assert isclose(expected, actual) def test_mean_empty(): with pytest.raises(ValueError): mean(()) def test_median(): assert median([1, 2, 3, 4, 5, 6]) == 3.5 assert median([1, 2, 3, 4, 5, 6, 9]) == 4 def test_median_empty(): with pytest.raises(ValueError): median(()) gpiozero-1.3.1.post1/gpiozero.egg-info/0000755000175000017500000000000013046622014017277 5ustar benben00000000000000gpiozero-1.3.1.post1/gpiozero.egg-info/entry_points.txt0000644000175000017500000000042413046622014022575 0ustar benben00000000000000[gpiozero_pin_factories] MockPWMPin = gpiozero.pins.mock:MockPWMPin MockPin = gpiozero.pins.mock:MockPin NativePin = gpiozero.pins.native:NativePin PiGPIOPin = gpiozero.pins.pigpiod:PiGPIOPin RPIOPin = gpiozero.pins.rpio:RPIOPin RPiGPIOPin = gpiozero.pins.rpigpio:RPiGPIOPin gpiozero-1.3.1.post1/gpiozero.egg-info/dependency_links.txt0000644000175000017500000000000113046622014023345 0ustar benben00000000000000 gpiozero-1.3.1.post1/gpiozero.egg-info/SOURCES.txt0000644000175000017500000000155013046622014021164 0ustar benben00000000000000LICENCE.txt MANIFEST.in README.rst setup.py gpiozero/__init__.py gpiozero/boards.py gpiozero/compat.py gpiozero/devices.py gpiozero/exc.py gpiozero/input_devices.py gpiozero/mixins.py gpiozero/other_devices.py gpiozero/output_devices.py gpiozero/spi.py gpiozero/spi_devices.py gpiozero/threads.py gpiozero/tools.py gpiozero.egg-info/PKG-INFO gpiozero.egg-info/SOURCES.txt gpiozero.egg-info/dependency_links.txt gpiozero.egg-info/entry_points.txt gpiozero.egg-info/requires.txt gpiozero.egg-info/top_level.txt gpiozero/pins/__init__.py gpiozero/pins/data.py gpiozero/pins/mock.py gpiozero/pins/native.py gpiozero/pins/pigpiod.py gpiozero/pins/rpigpio.py gpiozero/pins/rpio.py tests/test_boards.py tests/test_compat.py tests/test_devices.py tests/test_inputs.py tests/test_mock_pin.py tests/test_outputs.py tests/test_pins_data.py tests/test_real_pins.py tests/test_tools.pygpiozero-1.3.1.post1/gpiozero.egg-info/requires.txt0000644000175000017500000000005313046622014021675 0ustar benben00000000000000 [doc] sphinx [test] pytest coverage mock gpiozero-1.3.1.post1/gpiozero.egg-info/PKG-INFO0000644000175000017500000001033513046622014020376 0ustar benben00000000000000Metadata-Version: 1.1 Name: gpiozero Version: 1.3.1.post1 Summary: UNKNOWN Home-page: https://github.com/RPi-Distro/python-gpiozero Author: Ben Nuttall Author-email: ben@raspberrypi.org License: BSD License Description: ======== gpiozero ======== .. image:: https://badge.fury.io/py/gpiozero.svg :target: https://badge.fury.io/py/gpiozero :alt: Latest Version .. image:: https://travis-ci.org/RPi-Distro/python-gpiozero.svg?branch=master :target: https://travis-ci.org/RPi-Distro/python-gpiozero :alt: Build Tests .. image:: https://img.shields.io/codecov/c/github/RPi-Distro/python-gpiozero/master.svg?maxAge=2592000 :target: https://codecov.io/github/RPi-Distro/python-gpiozero :alt: Code Coverage A simple interface to everyday GPIO components used with Raspberry Pi. Created by `Ben Nuttall`_ of the `Raspberry Pi Foundation`_, `Dave Jones`_, and other contributors. About ===== Component interfaces are provided to allow a frictionless way to get started with physical computing:: from gpiozero import LED from time import sleep led = LED(17) while True: led.on() sleep(1) led.off() sleep(1) With very little code, you can quickly get going connecting your components together:: from gpiozero import LED, Button from signal import pause led = LED(17) button = Button(3) button.when_pressed = led.on button.when_released = led.off pause() The library includes interfaces to many simple everyday components, as well as some more complex things like sensors, analogue-to-digital converters, full colour LEDs, robotics kits and more. Install ======= First, update your repositories list:: sudo apt-get update Then install the package of your choice. Both Python 3 and Python 2 are supported. Python 3 is recommended:: sudo apt-get install python3-gpiozero or:: sudo apt-get install python-gpiozero Documentation ============= Comprehensive documentation is available at https://gpiozero.readthedocs.io/. Development =========== This project is being developed on `GitHub`_. Join in: * Provide suggestions, report bugs and ask questions as `issues`_ * Provide examples we can use as `recipes`_ * `Contribute`_ to the code Alternatively, email suggestions and feedback to mailto:ben@raspberrypi.org Contributors ============ - `Ben Nuttall`_ (project maintainer) - `Dave Jones`_ - `Martin O'Hanlon`_ - `Andrew Scheller`_ - `Schelto vanDoorn`_ .. _Raspberry Pi Foundation: https://www.raspberrypi.org/ .. _GitHub: https://github.com/RPi-Distro/python-gpiozero .. _issues: https://github.com/RPi-Distro/python-gpiozero/issues .. _recipes: http://gpiozero.readthedocs.io/en/latest/recipes.html .. _contribute: http://gpiozero.readthedocs.io/en/latest/contributing.html .. _Ben Nuttall: https://github.com/bennuttall .. _Dave Jones: https://github.com/waveform80 .. _Martin O'Hanlon: https://github.com/martinohanlon .. _Andrew Scheller: https://github.com/lurch .. _Schelto vanDoorn: https://github.com/pcopa Keywords: raspberrypi,gpio Platform: ALL Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Education Classifier: Intended Audience :: Developers Classifier: Topic :: Education Classifier: Topic :: System :: Hardware Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 gpiozero-1.3.1.post1/gpiozero.egg-info/top_level.txt0000644000175000017500000000001113046622014022021 0ustar benben00000000000000gpiozero gpiozero-1.3.1.post1/setup.cfg0000644000175000017500000000007313046622014015570 0ustar benben00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 gpiozero-1.3.1.post1/LICENCE.txt0000644000175000017500000000274412577753432015602 0ustar benben00000000000000Copyright 2015- Raspberry Pi Foundation Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. gpiozero-1.3.1.post1/setup.py0000644000175000017500000000610413046621410015461 0ustar benben00000000000000import os import sys from setuptools import setup, find_packages "A simple interface to everyday GPIO components used with Raspberry Pi" if sys.version_info[0] == 2: if not sys.version_info >= (2, 7): raise ValueError('This package requires Python 2.7 or above') elif sys.version_info[0] == 3: if not sys.version_info >= (3, 2): raise ValueError('This package requires Python 3.2 or above') else: raise ValueError('Unrecognized major version of Python') HERE = os.path.abspath(os.path.dirname(__file__)) # Workaround try: import multiprocessing except ImportError: pass __project__ = 'gpiozero' __version__ = '1.3.1.post1' __author__ = 'Ben Nuttall' __author_email__ = 'ben@raspberrypi.org' __url__ = 'https://github.com/RPi-Distro/python-gpiozero' __platforms__ = 'ALL' __classifiers__ = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Education", "Intended Audience :: Developers", "Topic :: Education", "Topic :: System :: Hardware", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", ] __keywords__ = [ 'raspberrypi', 'gpio', ] __requires__ = [ ] __extra_requires__ = { 'doc': ['sphinx'], 'test': ['pytest', 'coverage', 'mock'], } if sys.version_info[:2] == (3, 2): # Particular versions are required for Python 3.2 compatibility __extra_requires__['doc'].extend([ 'Jinja2<2.7', 'MarkupSafe<0.16', ]) __extra_requires__['test'][1] = 'coverage<4.0dev' __entry_points__ = { 'gpiozero_pin_factories': [ 'PiGPIOPin = gpiozero.pins.pigpiod:PiGPIOPin', 'RPiGPIOPin = gpiozero.pins.rpigpio:RPiGPIOPin', 'RPIOPin = gpiozero.pins.rpio:RPIOPin', 'NativePin = gpiozero.pins.native:NativePin', 'MockPin = gpiozero.pins.mock:MockPin', 'MockPWMPin = gpiozero.pins.mock:MockPWMPin', ], } def main(): import io with io.open(os.path.join(HERE, 'README.rst'), 'r') as readme: setup( name = __project__, version = __version__, description = __doc__, long_description = readme.read(), classifiers = __classifiers__, author = __author__, author_email = __author_email__, url = __url__, license = [ c.rsplit('::', 1)[1].strip() for c in __classifiers__ if c.startswith('License ::') ][0], keywords = __keywords__, packages = find_packages(), include_package_data = True, platforms = __platforms__, install_requires = __requires__, extras_require = __extra_requires__, entry_points = __entry_points__, ) if __name__ == '__main__': main() gpiozero-1.3.1.post1/README.rst0000644000175000017500000000541613046621340015445 0ustar benben00000000000000======== gpiozero ======== .. image:: https://badge.fury.io/py/gpiozero.svg :target: https://badge.fury.io/py/gpiozero :alt: Latest Version .. image:: https://travis-ci.org/RPi-Distro/python-gpiozero.svg?branch=master :target: https://travis-ci.org/RPi-Distro/python-gpiozero :alt: Build Tests .. image:: https://img.shields.io/codecov/c/github/RPi-Distro/python-gpiozero/master.svg?maxAge=2592000 :target: https://codecov.io/github/RPi-Distro/python-gpiozero :alt: Code Coverage A simple interface to everyday GPIO components used with Raspberry Pi. Created by `Ben Nuttall`_ of the `Raspberry Pi Foundation`_, `Dave Jones`_, and other contributors. About ===== Component interfaces are provided to allow a frictionless way to get started with physical computing:: from gpiozero import LED from time import sleep led = LED(17) while True: led.on() sleep(1) led.off() sleep(1) With very little code, you can quickly get going connecting your components together:: from gpiozero import LED, Button from signal import pause led = LED(17) button = Button(3) button.when_pressed = led.on button.when_released = led.off pause() The library includes interfaces to many simple everyday components, as well as some more complex things like sensors, analogue-to-digital converters, full colour LEDs, robotics kits and more. Install ======= First, update your repositories list:: sudo apt-get update Then install the package of your choice. Both Python 3 and Python 2 are supported. Python 3 is recommended:: sudo apt-get install python3-gpiozero or:: sudo apt-get install python-gpiozero Documentation ============= Comprehensive documentation is available at https://gpiozero.readthedocs.io/. Development =========== This project is being developed on `GitHub`_. Join in: * Provide suggestions, report bugs and ask questions as `issues`_ * Provide examples we can use as `recipes`_ * `Contribute`_ to the code Alternatively, email suggestions and feedback to mailto:ben@raspberrypi.org Contributors ============ - `Ben Nuttall`_ (project maintainer) - `Dave Jones`_ - `Martin O'Hanlon`_ - `Andrew Scheller`_ - `Schelto vanDoorn`_ .. _Raspberry Pi Foundation: https://www.raspberrypi.org/ .. _GitHub: https://github.com/RPi-Distro/python-gpiozero .. _issues: https://github.com/RPi-Distro/python-gpiozero/issues .. _recipes: http://gpiozero.readthedocs.io/en/latest/recipes.html .. _contribute: http://gpiozero.readthedocs.io/en/latest/contributing.html .. _Ben Nuttall: https://github.com/bennuttall .. _Dave Jones: https://github.com/waveform80 .. _Martin O'Hanlon: https://github.com/martinohanlon .. _Andrew Scheller: https://github.com/lurch .. _Schelto vanDoorn: https://github.com/pcopa gpiozero-1.3.1.post1/PKG-INFO0000644000175000017500000001033513046622014015046 0ustar benben00000000000000Metadata-Version: 1.1 Name: gpiozero Version: 1.3.1.post1 Summary: UNKNOWN Home-page: https://github.com/RPi-Distro/python-gpiozero Author: Ben Nuttall Author-email: ben@raspberrypi.org License: BSD License Description: ======== gpiozero ======== .. image:: https://badge.fury.io/py/gpiozero.svg :target: https://badge.fury.io/py/gpiozero :alt: Latest Version .. image:: https://travis-ci.org/RPi-Distro/python-gpiozero.svg?branch=master :target: https://travis-ci.org/RPi-Distro/python-gpiozero :alt: Build Tests .. image:: https://img.shields.io/codecov/c/github/RPi-Distro/python-gpiozero/master.svg?maxAge=2592000 :target: https://codecov.io/github/RPi-Distro/python-gpiozero :alt: Code Coverage A simple interface to everyday GPIO components used with Raspberry Pi. Created by `Ben Nuttall`_ of the `Raspberry Pi Foundation`_, `Dave Jones`_, and other contributors. About ===== Component interfaces are provided to allow a frictionless way to get started with physical computing:: from gpiozero import LED from time import sleep led = LED(17) while True: led.on() sleep(1) led.off() sleep(1) With very little code, you can quickly get going connecting your components together:: from gpiozero import LED, Button from signal import pause led = LED(17) button = Button(3) button.when_pressed = led.on button.when_released = led.off pause() The library includes interfaces to many simple everyday components, as well as some more complex things like sensors, analogue-to-digital converters, full colour LEDs, robotics kits and more. Install ======= First, update your repositories list:: sudo apt-get update Then install the package of your choice. Both Python 3 and Python 2 are supported. Python 3 is recommended:: sudo apt-get install python3-gpiozero or:: sudo apt-get install python-gpiozero Documentation ============= Comprehensive documentation is available at https://gpiozero.readthedocs.io/. Development =========== This project is being developed on `GitHub`_. Join in: * Provide suggestions, report bugs and ask questions as `issues`_ * Provide examples we can use as `recipes`_ * `Contribute`_ to the code Alternatively, email suggestions and feedback to mailto:ben@raspberrypi.org Contributors ============ - `Ben Nuttall`_ (project maintainer) - `Dave Jones`_ - `Martin O'Hanlon`_ - `Andrew Scheller`_ - `Schelto vanDoorn`_ .. _Raspberry Pi Foundation: https://www.raspberrypi.org/ .. _GitHub: https://github.com/RPi-Distro/python-gpiozero .. _issues: https://github.com/RPi-Distro/python-gpiozero/issues .. _recipes: http://gpiozero.readthedocs.io/en/latest/recipes.html .. _contribute: http://gpiozero.readthedocs.io/en/latest/contributing.html .. _Ben Nuttall: https://github.com/bennuttall .. _Dave Jones: https://github.com/waveform80 .. _Martin O'Hanlon: https://github.com/martinohanlon .. _Andrew Scheller: https://github.com/lurch .. _Schelto vanDoorn: https://github.com/pcopa Keywords: raspberrypi,gpio Platform: ALL Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Education Classifier: Intended Audience :: Developers Classifier: Topic :: Education Classifier: Topic :: System :: Hardware Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 gpiozero-1.3.1.post1/MANIFEST.in0000644000175000017500000000010413046621372015506 0ustar benben00000000000000include README.rst recursive-include tests *.py include LICENCE.txt gpiozero-1.3.1.post1/gpiozero/0000755000175000017500000000000013046622014015605 5ustar benben00000000000000gpiozero-1.3.1.post1/gpiozero/__init__.py0000644000175000017500000000430413046621340017720 0ustar benben00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) from .pins import ( Pin, LocalPin, ) from .pins.data import ( PiBoardInfo, PinInfo, pi_info, ) from .exc import ( GPIOZeroError, DeviceClosed, BadEventHandler, BadWaitTime, BadQueueLen, CompositeDeviceError, CompositeDeviceBadName, CompositeDeviceBadOrder, CompositeDeviceBadDevice, SPIError, SPIBadArgs, EnergenieSocketMissing, EnergenieBadSocket, GPIODeviceError, GPIODeviceClosed, GPIOPinInUse, GPIOPinMissing, InputDeviceError, OutputDeviceError, OutputDeviceBadValue, PinError, PinInvalidFunction, PinInvalidState, PinInvalidPull, PinInvalidEdges, PinSetInput, PinFixedPull, PinEdgeDetectUnsupported, PinPWMError, PinPWMUnsupported, PinPWMFixedValue, PinUnknownPi, PinMultiplePins, PinNoPins, GPIOZeroWarning, SPIWarning, SPISoftwareFallback, PinWarning, PinNonPhysical, ) from .devices import ( Device, GPIODevice, CompositeDevice, ) from .mixins import ( SharedMixin, SourceMixin, ValuesMixin, EventsMixin, HoldMixin, ) from .input_devices import ( InputDevice, DigitalInputDevice, SmoothedInputDevice, Button, LineSensor, MotionSensor, LightSensor, DistanceSensor, ) from .spi_devices import ( SPIDevice, AnalogInputDevice, MCP3001, MCP3002, MCP3004, MCP3008, MCP3201, MCP3202, MCP3204, MCP3208, MCP3301, MCP3302, MCP3304, ) from .output_devices import ( OutputDevice, DigitalOutputDevice, PWMOutputDevice, PWMLED, LED, Buzzer, Motor, Servo, AngularServo, RGBLED, ) from .boards import ( CompositeOutputDevice, ButtonBoard, LEDCollection, LEDBoard, LEDBarGraph, LedBorg, PiLiter, PiLiterBarGraph, TrafficLights, PiTraffic, SnowPi, TrafficLightsBuzzer, FishDish, TrafficHat, Robot, RyanteckRobot, CamJamKitRobot, Energenie, ) from .other_devices import ( InternalDevice, PingServer, CPUTemperature, TimeOfDay, ) gpiozero-1.3.1.post1/gpiozero/compat.py0000644000175000017500000000377112761305734017464 0ustar benben00000000000000# vim: set fileencoding=utf-8: from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import cmath import collections import operator import functools # Back-ported from python 3.5; see # github.com/PythonCHB/close_pep/blob/master/is_close.py for original # implementation def isclose(a, b, rel_tol=1e-9, abs_tol=0.0): if rel_tol < 0.0 or abs_tol < 0.0: raise ValueError('error tolerances must be non-negative') if a == b: # fast-path for exact equality return True if cmath.isinf(a) or cmath.isinf(b): return False diff = abs(b - a) return ( (diff <= abs(rel_tol * b)) or (diff <= abs(rel_tol * a)) or (diff <= abs_tol) ) # Backported from py3.4 def mean(data): if iter(data) is data: data = list(data) n = len(data) if not n: raise ValueError('cannot calculate mean of empty data') return sum(data) / n # Backported from py3.4 def median(data): data = sorted(data) n = len(data) if not n: raise ValueError('cannot calculate median of empty data') elif n % 2: return data[n // 2] else: i = n // 2 return (data[i - 1] + data[i]) / 2 # Copied from the MIT-licensed https://github.com/slezica/python-frozendict class frozendict(collections.Mapping): def __init__(self, *args, **kwargs): self.__dict = dict(*args, **kwargs) self.__hash = None def __getitem__(self, key): return self.__dict[key] def copy(self, **add_or_replace): return frozendict(self, **add_or_replace) def __iter__(self): return iter(self.__dict) def __len__(self): return len(self.__dict) def __repr__(self): return '' % repr(self.__dict) def __hash__(self): if self.__hash is None: hashes = map(hash, self.items()) self.__hash = functools.reduce(operator.xor, hashes, 0) return self.__hash gpiozero-1.3.1.post1/gpiozero/spi_devices.py0000644000175000017500000003063313046621340020462 0ustar benben00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) str = type('') from .exc import DeviceClosed, InputDeviceError from .devices import Device from .spi import SPI class SPIDevice(Device): """ Extends :class:`Device`. Represents a device that communicates via the SPI protocol. See :ref:`spi_args` for information on the keyword arguments that can be specified with the constructor. """ def __init__(self, **spi_args): self._spi = SPI(**spi_args) def close(self): if self._spi: s = self._spi self._spi = None s.close() super(SPIDevice, self).close() @property def closed(self): return self._spi is None def __repr__(self): try: self._check_open() return "" % (self.__class__.__name__, self._spi) except DeviceClosed: return "" % self.__class__.__name__ class AnalogInputDevice(SPIDevice): """ Represents an analog input device connected to SPI (serial interface). Typical analog input devices are `analog to digital converters`_ (ADCs). Several classes are provided for specific ADC chips, including :class:`MCP3004`, :class:`MCP3008`, :class:`MCP3204`, and :class:`MCP3208`. The following code demonstrates reading the first channel of an MCP3008 chip attached to the Pi's SPI pins:: from gpiozero import MCP3008 pot = MCP3008(0) print(pot.value) The :attr:`value` attribute is normalized such that its value is always between 0.0 and 1.0 (or in special cases, such as differential sampling, -1 to +1). Hence, you can use an analog input to control the brightness of a :class:`PWMLED` like so:: from gpiozero import MCP3008, PWMLED pot = MCP3008(0) led = PWMLED(17) led.source = pot.values .. _analog to digital converters: https://en.wikipedia.org/wiki/Analog-to-digital_converter """ def __init__(self, bits=None, **spi_args): if bits is None: raise InputDeviceError('you must specify the bit resolution of the device') self._bits = bits super(AnalogInputDevice, self).__init__(shared=True, **spi_args) @property def bits(self): """ The bit-resolution of the device/channel. """ return self._bits def _read(self): raise NotImplementedError @property def value(self): """ The current value read from the device, scaled to a value between 0 and 1 (or -1 to +1 for devices operating in differential mode). """ return self._read() / (2**self.bits - 1) @property def raw_value(self): """ The raw value as read from the device. """ return self._read() class MCP3xxx(AnalogInputDevice): """ Extends :class:`AnalogInputDevice` to implement an interface for all ADC chips with a protocol similar to the Microchip MCP3xxx series of devices. """ def __init__(self, channel=0, bits=10, differential=False, **spi_args): self._channel = channel self._differential = bool(differential) super(MCP3xxx, self).__init__(bits, **spi_args) @property def channel(self): """ The channel to read data from. The MCP3008/3208/3304 have 8 channels (0-7), while the MCP3004/3204/3302 have 4 channels (0-3), and the MCP3301 only has 1 channel. """ return self._channel @property def differential(self): """ If ``True``, the device is operated in pseudo-differential mode. In this mode one channel (specified by the channel attribute) is read relative to the value of a second channel (implied by the chip's design). Please refer to the device data-sheet to determine which channel is used as the relative base value (for example, when using an :class:`MCP3008` in differential mode, channel 0 is read relative to channel 1). """ return self._differential def _read(self): # MCP3008/04 or MCP3208/04 protocol looks like the following: # # Byte 0 1 2 # ==== ======== ======== ======== # Tx 0001MCCC xxxxxxxx xxxxxxxx # Rx xxxxxxxx x0RRRRRR RRRRxxxx for the 3004/08 # Rx xxxxxxxx x0RRRRRR RRRRRRxx for the 3204/08 # # The transmit bits start with 3 preamble bits "000" (to warm up), a # start bit "1" followed by the single/differential bit (M) which is 1 # for single-ended read, and 0 for differential read, followed by # 3-bits for the channel (C). The remainder of the transmission are # "don't care" bits (x). # # The first byte received and the top 1 bit of the second byte are # don't care bits (x). These are followed by a null bit (0), and then # the result bits (R). 10 bits for the MCP300x, 12 bits for the # MCP320x. # # XXX Differential mode still requires testing data = self._spi.transfer([16 + [8, 0][self.differential] + self.channel, 0, 0]) return ((data[1] & 63) << (self.bits - 6)) | (data[2] >> (14 - self.bits)) class MCP33xx(MCP3xxx): """ Extends :class:`MCP3xxx` with functionality specific to the MCP33xx family of ADCs; specifically this handles the full differential capability of these chips supporting the full 13-bit signed range of output values. """ def __init__(self, channel=0, differential=False, **spi_args): super(MCP33xx, self).__init__(channel, 12, differential, **spi_args) def _read(self): # MCP3304/02 protocol looks like the following: # # Byte 0 1 2 # ==== ======== ======== ======== # Tx 0001MCCC xxxxxxxx xxxxxxxx # Rx xxxxxxxx x0SRRRRR RRRRRRRx # # The transmit bits start with 3 preamble bits "000" (to warm up), a # start bit "1" followed by the single/differential bit (M) which is 1 # for single-ended read, and 0 for differential read, followed by # 3-bits for the channel (C). The remainder of the transmission are # "don't care" bits (x). # # The first byte received and the top 1 bit of the second byte are # don't care bits (x). These are followed by a null bit (0), then the # sign bit (S), and then the 12 result bits (R). # # In single read mode (the default) the sign bit is always zero and the # result is effectively 12-bits. In differential mode, the sign bit is # significant and the result is a two's-complement 13-bit value. # # The MCP3301 variant of the chip always operates in differential # mode and effectively only has one channel (composed of an IN+ and # IN-). As such it requires no input, just output. This is the reason # we split out _send() below; so that MCP3301 can override it. data = self._spi.transfer(self._send()) # Extract the last two bytes (again, for MCP3301) data = data[-2:] result = ((data[0] & 63) << 7) | (data[1] >> 1) # Account for the sign bit if self.differential and result > 4095: result = -(8192 - result) assert -4096 <= result < 4096 return result def _send(self): return [16 + [8, 0][self.differential] + self.channel, 0, 0] class MCP3001(MCP3xxx): """ The `MCP3001`_ is a 10-bit analog to digital converter with 1 channel .. _MCP3001: http://www.farnell.com/datasheets/630400.pdf """ def __init__(self, **spi_args): super(MCP3001, self).__init__(0, 10, differential=True, **spi_args) class MCP3002(MCP3xxx): """ The `MCP3002`_ is a 10-bit analog to digital converter with 2 channels (0-1). .. _MCP3002: http://www.farnell.com/datasheets/1599363.pdf """ def __init__(self, channel=0, differential=False, **spi_args): if not 0 <= channel < 2: raise InputDeviceError('channel must be 0 or 1') super(MCP3002, self).__init__(channel, 10, differential, **spi_args) class MCP3004(MCP3xxx): """ The `MCP3004`_ is a 10-bit analog to digital converter with 4 channels (0-3). .. _MCP3004: http://www.farnell.com/datasheets/808965.pdf """ def __init__(self, channel=0, differential=False, **spi_args): if not 0 <= channel < 4: raise InputDeviceError('channel must be between 0 and 3') super(MCP3004, self).__init__(channel, 10, differential, **spi_args) class MCP3008(MCP3xxx): """ The `MCP3008`_ is a 10-bit analog to digital converter with 8 channels (0-7). .. _MCP3008: http://www.farnell.com/datasheets/808965.pdf """ def __init__(self, channel=0, differential=False, **spi_args): if not 0 <= channel < 8: raise InputDeviceError('channel must be between 0 and 7') super(MCP3008, self).__init__(channel, 10, differential, **spi_args) class MCP3201(MCP3xxx): """ The `MCP3201`_ is a 12-bit analog to digital converter with 1 channel .. _MCP3201: http://www.farnell.com/datasheets/1669366.pdf """ def __init__(self, **spi_args): super(MCP3201, self).__init__(0, 12, differential=True, **spi_args) class MCP3202(MCP3xxx): """ The `MCP3202`_ is a 12-bit analog to digital converter with 2 channels (0-1). .. _MCP3202: http://www.farnell.com/datasheets/1669376.pdf """ def __init__(self, channel=0, differential=False, **spi_args): if not 0 <= channel < 2: raise InputDeviceError('channel must be 0 or 1') super(MCP3202, self).__init__(channel, 12, differential, **spi_args) class MCP3204(MCP3xxx): """ The `MCP3204`_ is a 12-bit analog to digital converter with 4 channels (0-3). .. _MCP3204: http://www.farnell.com/datasheets/808967.pdf """ def __init__(self, channel=0, differential=False, **spi_args): if not 0 <= channel < 4: raise InputDeviceError('channel must be between 0 and 3') super(MCP3204, self).__init__(channel, 12, differential, **spi_args) class MCP3208(MCP3xxx): """ The `MCP3208`_ is a 12-bit analog to digital converter with 8 channels (0-7). .. _MCP3208: http://www.farnell.com/datasheets/808967.pdf """ def __init__(self, channel=0, differential=False, **spi_args): if not 0 <= channel < 8: raise InputDeviceError('channel must be between 0 and 7') super(MCP3208, self).__init__(channel, 12, differential, **spi_args) class MCP3301(MCP33xx): """ The `MCP3301`_ is a signed 13-bit analog to digital converter. Please note that the MCP3301 always operates in differential mode between its two channels and the output value is scaled from -1 to +1. .. _MCP3301: http://www.farnell.com/datasheets/1669397.pdf """ def __init__(self, **spi_args): super(MCP3301, self).__init__(0, differential=True, **spi_args) def _send(self): return [0, 0] class MCP3302(MCP33xx): """ The `MCP3302`_ is a 12/13-bit analog to digital converter with 4 channels (0-3). When operated in differential mode, the device outputs a signed 13-bit value which is scaled from -1 to +1. When operated in single-ended mode (the default), the device outputs an unsigned 12-bit value scaled from 0 to 1. .. _MCP3302: http://www.farnell.com/datasheets/1486116.pdf """ def __init__(self, channel=0, differential=False, **spi_args): if not 0 <= channel < 4: raise InputDeviceError('channel must be between 0 and 4') super(MCP3302, self).__init__(channel, differential, **spi_args) class MCP3304(MCP33xx): """ The `MCP3304`_ is a 12/13-bit analog to digital converter with 8 channels (0-7). When operated in differential mode, the device outputs a signed 13-bit value which is scaled from -1 to +1. When operated in single-ended mode (the default), the device outputs an unsigned 12-bit value scaled from 0 to 1. .. _MCP3304: http://www.farnell.com/datasheets/1486116.pdf """ def __init__(self, channel=0, differential=False, **spi_args): if not 0 <= channel < 8: raise InputDeviceError('channel must be between 0 and 7') super(MCP3304, self).__init__(channel, differential, **spi_args) gpiozero-1.3.1.post1/gpiozero/spi.py0000644000175000017500000003371313046621340016762 0ustar benben00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) str = type('') import warnings import operator from threading import RLock try: from spidev import SpiDev except ImportError: SpiDev = None from .devices import Device, SharedMixin, _PINS, _PINS_LOCK from .input_devices import InputDevice from .output_devices import OutputDevice from .exc import SPIBadArgs, SPISoftwareFallback, GPIOPinInUse, DeviceClosed class SPIHardwareInterface(Device): def __init__(self, port, device): self._device = None super(SPIHardwareInterface, self).__init__() # XXX How can we detect conflicts with existing GPIO instances? This # isn't ideal ... in fact, it's downright crap and doesn't guard # against conflicts created *after* this instance, but it's all I can # come up with right now ... conflicts = (11, 10, 9, (8, 7)[device]) with _PINS_LOCK: for pin in _PINS: if pin.number in conflicts: raise GPIOPinInUse( 'pin %r is already in use by another gpiozero object' % pin ) self._device_num = device self._device = SpiDev() self._device.open(port, device) self._device.max_speed_hz = 500000 def close(self): if self._device: try: self._device.close() finally: self._device = None super(SPIHardwareInterface, self).close() @property def closed(self): return self._device is None def __repr__(self): try: self._check_open() return ( "hardware SPI on clock_pin=11, mosi_pin=10, miso_pin=9, " "select_pin=%d" % ( 8 if self._device_num == 0 else 7)) except DeviceClosed: return "hardware SPI closed" def read(self, n): return self.transfer((0,) * n) def write(self, data): return len(self.transfer(data)) def transfer(self, data): """ Writes data (a list of integer words where each word is assumed to have :attr:`bits_per_word` bits or less) to the SPI interface, and reads an equivalent number of words, returning them as a list of integers. """ return self._device.xfer2(data) def _get_clock_mode(self): return self._device.mode def _set_clock_mode(self, value): self._device.mode = value def _get_clock_polarity(self): return bool(self.mode & 2) def _set_clock_polarity(self, value): self.mode = self.mode & (~2) | (bool(value) << 1) def _get_clock_phase(self): return bool(self.mode & 1) def _set_clock_phase(self, value): self.mode = self.mode & (~1) | bool(value) def _get_lsb_first(self): return self._device.lsbfirst def _set_lsb_first(self, value): self._device.lsbfirst = bool(value) def _get_select_high(self): return self._device.cshigh def _set_select_high(self, value): self._device.cshigh = bool(value) def _get_bits_per_word(self): return self._device.bits_per_word def _set_bits_per_word(self, value): self._device.bits_per_word = value clock_polarity = property(_get_clock_polarity, _set_clock_polarity) clock_phase = property(_get_clock_phase, _set_clock_phase) clock_mode = property(_get_clock_mode, _set_clock_mode) lsb_first = property(_get_lsb_first, _set_lsb_first) select_high = property(_get_select_high, _set_select_high) bits_per_word = property(_get_bits_per_word, _set_bits_per_word) class SPISoftwareBus(SharedMixin, Device): def __init__(self, clock_pin, mosi_pin, miso_pin): self.lock = None self.clock = None self.mosi = None self.miso = None super(SPISoftwareBus, self).__init__() self.lock = RLock() self.clock_phase = False self.lsb_first = False self.bits_per_word = 8 try: self.clock = OutputDevice(clock_pin, active_high=True) if mosi_pin is not None: self.mosi = OutputDevice(mosi_pin) if miso_pin is not None: self.miso = InputDevice(miso_pin) except: self.close() raise def close(self): super(SPISoftwareBus, self).close() if self.lock: with self.lock: if self.miso is not None: self.miso.close() self.miso = None if self.mosi is not None: self.mosi.close() self.mosi = None if self.clock is not None: self.clock.close() self.clock = None self.lock = None @property def closed(self): return self.lock is None @classmethod def _shared_key(cls, clock_pin, mosi_pin, miso_pin): return (clock_pin, mosi_pin, miso_pin) def read(self, n): return self.transfer((0,) * n) def write(self, data): return len(self.transfer(data)) def transfer(self, data): """ Writes data (a list of integer words where each word is assumed to have :attr:`bits_per_word` bits or less) to the SPI interface, and reads an equivalent number of words, returning them as a list of integers. """ result = [] with self.lock: shift = operator.lshift if self.lsb_first else operator.rshift for write_word in data: mask = 1 if self.lsb_first else 1 << (self.bits_per_word - 1) read_word = 0 for _ in range(self.bits_per_word): if self.mosi is not None: self.mosi.value = bool(write_word & mask) self.clock.on() if self.miso is not None and not self.clock_phase: if self.miso.value: read_word |= mask self.clock.off() if self.miso is not None and self.clock_phase: if self.miso.value: read_word |= mask mask = shift(mask, 1) result.append(read_word) return result class SPISoftwareInterface(OutputDevice): def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin): self._bus = None super(SPISoftwareInterface, self).__init__(select_pin, active_high=False) try: self._bus = SPISoftwareBus(clock_pin, mosi_pin, miso_pin) except: self.close() raise def close(self): if self._bus: self._bus.close() self._bus = None super(SPISoftwareInterface, self).close() def __repr__(self): try: self._check_open() return ( "software SPI on clock_pin=%d, mosi_pin=%d, miso_pin=%d, " "select_pin=%d" % ( self._bus.clock.pin.number, self._bus.mosi.pin.number, self._bus.miso.pin.number, self.pin.number)) except DeviceClosed: return "software SPI closed" def read(self, n): return self._bus.read(n) def write(self, data): return self._bus.write(data) def transfer(self, data): with self._bus.lock: self.on() try: return self._bus.transfer(data) finally: self.off() def _get_clock_mode(self): return (self.clock_polarity << 1) | self.clock_phase def _set_clock_mode(self, value): value = int(value) if not 0 <= value <= 3: raise ValueError('clock_mode must be a value between 0 and 3 inclusive') with self._bus.lock: self._bus.clock.active_high = not (value & 2) self._bus.clock.off() self._bus.clock_phase = bool(value & 1) def _get_clock_polarity(self): return not self._bus.clock.active_high def _set_clock_polarity(self, value): with self._bus.lock: self._bus.clock.active_high = not value def _get_clock_phase(self): return self._bus.clock_phase def _set_clock_phase(self, value): with self._bus.lock: self._bus.clock_phase = bool(value) def _get_lsb_first(self): return self._bus.lsb_first def _set_lsb_first(self, value): with self._bus.lock: self._bus.lsb_first = bool(value) def _get_bits_per_word(self): return self._bus.bits_per_word def _set_bits_per_word(self, value): if value < 1: raise ValueError('bits_per_word must be positive') with self._bus.lock: self._bus.bits_per_word = int(value) def _get_select_high(self): return self.active_high def _set_select_high(self, value): with self._bus.lock: self.active_high = value self.off() clock_polarity = property(_get_clock_polarity, _set_clock_polarity) clock_phase = property(_get_clock_phase, _set_clock_phase) clock_mode = property(_get_clock_mode, _set_clock_mode) lsb_first = property(_get_lsb_first, _set_lsb_first) bits_per_word = property(_get_bits_per_word, _set_bits_per_word) select_high = property(_get_select_high, _set_select_high) class SharedSPIHardwareInterface(SharedMixin, SPIHardwareInterface): @classmethod def _shared_key(cls, port, device): return (port, device) class SharedSPISoftwareInterface(SharedMixin, SPISoftwareInterface): @classmethod def _shared_key(cls, clock_pin, mosi_pin, miso_pin, select_pin): return (clock_pin, mosi_pin, miso_pin, select_pin) def extract_spi_args(**kwargs): """ Given a set of keyword arguments, splits it into those relevant to SPI implementations and all the rest. SPI arguments are augmented with defaults and converted into the pin format (from the port/device format) if necessary. Returns a tuple of ``(spi_args, other_args)``. """ pin_defaults = { 'clock_pin': 11, 'mosi_pin': 10, 'miso_pin': 9, 'select_pin': 8, } dev_defaults = { 'port': 0, 'device': 0, } spi_args = { key: value for (key, value) in kwargs.items() if key in pin_defaults or key in dev_defaults } kwargs = { key: value for (key, value) in kwargs.items() if key not in spi_args } if not spi_args: spi_args = pin_defaults elif set(spi_args) <= set(pin_defaults): spi_args = { key: spi_args.get(key, default) for key, default in pin_defaults.items() } elif set(spi_args) <= set(dev_defaults): spi_args = { key: spi_args.get(key, default) for key, default in dev_defaults.items() } if spi_args['port'] != 0: raise SPIBadArgs('port 0 is the only valid SPI port') if spi_args['device'] not in (0, 1): raise SPIBadArgs('device must be 0 or 1') spi_args = { key: value if key != 'select_pin' else (8, 7)[spi_args['device']] for key, value in pin_defaults.items() } else: raise SPIBadArgs( 'you must either specify port and device, or clock_pin, mosi_pin, ' 'miso_pin, and select_pin; combinations of the two schemes (e.g. ' 'port and clock_pin) are not permitted') return spi_args, kwargs def SPI(**spi_args): """ Returns an SPI interface, for the specified SPI *port* and *device*, or for the specified pins (*clock_pin*, *mosi_pin*, *miso_pin*, and *select_pin*). Only one of the schemes can be used; attempting to mix *port* and *device* with pin numbers will raise :exc:`SPIBadArgs`. If the pins specified match the hardware SPI pins (clock on GPIO11, MOSI on GPIO10, MISO on GPIO9, and chip select on GPIO8 or GPIO7), and the spidev module can be imported, a :class:`SPIHardwareInterface` instance will be returned. Otherwise, a :class:`SPISoftwareInterface` will be returned which will use simple bit-banging to communicate. Both interfaces have the same API, support clock polarity and phase attributes, and can handle half and full duplex communications, but the hardware interface is significantly faster (though for many things this doesn't matter). Finally, the *shared* keyword argument specifies whether the resulting SPI interface can be repeatedly created and used by multiple devices (useful with multi-channel devices like numerous ADCs). """ spi_args, kwargs = extract_spi_args(**spi_args) shared = kwargs.pop('shared', False) if kwargs: raise SPIBadArgs( 'unrecognized keyword argument %s' % kwargs.popitem()[0]) if all(( spi_args['clock_pin'] == 11, spi_args['mosi_pin'] == 10, spi_args['miso_pin'] == 9, spi_args['select_pin'] in (7, 8), )): if SpiDev is None: warnings.warn( SPISoftwareFallback( 'failed to import spidev, falling back to software SPI')) else: try: hardware_spi_args = { 'port': 0, 'device': {8: 0, 7: 1}[spi_args['select_pin']], } if shared: return SharedSPIHardwareInterface(**hardware_spi_args) else: return SPIHardwareInterface(**hardware_spi_args) except Exception as e: warnings.warn( SPISoftwareFallback( 'failed to initialize hardware SPI, falling back to ' 'software (error was: %s)' % str(e))) if shared: return SharedSPISoftwareInterface(**spi_args) else: return SPISoftwareInterface(**spi_args) gpiozero-1.3.1.post1/gpiozero/other_devices.py0000644000175000017500000001702113046621154021007 0ustar benben00000000000000# vim: set fileencoding=utf-8: from __future__ import ( unicode_literals, print_function, absolute_import, division, ) str = type('') import os import io import subprocess from datetime import datetime, time from .devices import Device from .mixins import EventsMixin class InternalDevice(EventsMixin, Device): """ Extends :class:`Device` to provide a basis for devices which have no specific hardware representation. These are effectively pseudo-devices and usually represent operating system services like the internal clock, file systems or network facilities. """ class PingServer(InternalDevice): """ Extends :class:`InternalDevice` to provide a device which is active when a *host* on the network can be pinged. The following example lights an LED while a server is reachable (note the use of :attr:`~SourceMixin.source_delay` to ensure the server is not flooded with pings):: from gpiozero import PingServer, LED from signal import pause server = PingServer('my-server') led = LED(4) led.source_delay = 1 led.source = server.values pause() :param str host: The hostname or IP address to attempt to ping. """ def __init__(self, host): self.host = host super(PingServer, self).__init__() self._fire_events() def __repr__(self): return '' % self.host @property def value(self): # XXX This is doing a DNS lookup every time it's queried; should we # call gethostbyname in the constructor and ping that instead (good # for consistency, but what if the user *expects* the host to change # address?) with io.open(os.devnull, 'wb') as devnull: try: subprocess.check_call( ['ping', '-c1', self.host], stdout=devnull, stderr=devnull) except subprocess.CalledProcessError: return False else: return True class CPUTemperature(InternalDevice): """ Extends :class:`InternalDevice` to provide a device which is active when the CPU temperature exceeds the *threshold* value. The following example plots the CPU's temperature on an LED bar graph:: from gpiozero import LEDBarGraph, CPUTemperature from signal import pause # Use minimums and maximums that are closer to "normal" usage so the # bar graph is a bit more "lively" temp = CPUTemperature(min_temp=50, max_temp=90) graph = LEDBarGraph(5, 6, 13, 19, 25, pwm=True) graph.source = temp.values pause() :param str sensor_file: The file from which to read the temperature. This defaults to the sysfs file :file:`/sys/class/thermal/thermal_zone0/temp`. Whatever file is specified is expected to contain a single line containing the temperature in milli-degrees celsius. :param float min_temp: The temperature at which :attr:`value` will read 0.0. This defaults to 0.0. :param float max_temp: The temperature at which :attr:`value` will read 1.0. This defaults to 100.0. :param float threshold: The temperature above which the device will be considered "active". This defaults to 80.0. """ def __init__(self, sensor_file='/sys/class/thermal/thermal_zone0/temp', min_temp=0.0, max_temp=100.0, threshold=80.0): self.sensor_file = sensor_file super(CPUTemperature, self).__init__() self.min_temp = min_temp self.max_temp = max_temp self.threshold = threshold self._fire_events() def __repr__(self): return '' % self.temperature @property def temperature(self): """ Returns the current CPU temperature in degrees celsius. """ with io.open(self.sensor_file, 'r') as f: return float(f.readline().strip()) / 1000 @property def value(self): """ Returns the current CPU temperature as a value between 0.0 (representing the *min_temp* value) and 1.0 (representing the *max_temp* value). These default to 0.0 and 100.0 respectively, hence :attr:`value` is :attr:`temperature` divided by 100 by default. """ temp_range = self.max_temp - self.min_temp return (self.temperature - self.min_temp) / temp_range @property def is_active(self): """ Returns ``True`` when the CPU :attr:`temperature` exceeds the :attr:`threshold`. """ return self.temperature > self.threshold class TimeOfDay(InternalDevice): """ Extends :class:`InternalDevice` to provide a device which is active when the computer's clock indicates that the current time is between *start_time* and *end_time* (inclusive) which are :class:`~datetime.time` instances. The following example turns on a lamp attached to an :class:`Energenie` plug between 7 and 8 AM:: from datetime import time from gpiozero import TimeOfDay, Energenie from signal import pause lamp = Energenie(0) morning = TimeOfDay(time(7), time(8)) morning.when_activated = lamp.on morning.when_deactivated = lamp.off pause() :param ~datetime.time start_time: The time from which the device will be considered active. :param ~datetime.time end_time: The time after which the device will be considered inactive. :param bool utc: If ``True`` (the default), a naive UTC time will be used for the comparison rather than a local time-zone reading. """ def __init__(self, start_time, end_time, utc=True): self._start_time = None self._end_time = None self._utc = True super(TimeOfDay, self).__init__() self.start_time = start_time self.end_time = end_time self.utc = utc self._fire_events() def __repr__(self): return '' % ( self.start_time, self.end_time, ('local', 'UTC')[self.utc]) @property def start_time(self): """ The time of day after which the device will be considered active. """ return self._start_time @start_time.setter def start_time(self, value): if isinstance(value, datetime): value = value.time() if not isinstance(value, time): raise ValueError('start_time must be a datetime, or time instance') self._start_time = value @property def end_time(self): """ The time of day after which the device will be considered inactive. """ return self._end_time @end_time.setter def end_time(self, value): if isinstance(value, datetime): value = value.time() if not isinstance(value, time): raise ValueError('end_time must be a datetime, or time instance') self._end_time = value @property def utc(self): """ If ``True``, use a naive UTC time reading for comparison instead of a local timezone reading. """ return self._utc @utc.setter def utc(self, value): self._utc = bool(value) @property def value(self): if self.utc: return self.start_time <= datetime.utcnow().time() <= self.end_time else: return self.start_time <= datetime.now().time() <= self.end_time gpiozero-1.3.1.post1/gpiozero/pins/0000755000175000017500000000000013046622014016556 5ustar benben00000000000000gpiozero-1.3.1.post1/gpiozero/pins/__init__.py0000644000175000017500000002410012761305734020676 0ustar benben00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import io from .data import pi_info from ..exc import ( PinInvalidFunction, PinSetInput, PinFixedPull, PinPWMUnsupported, PinEdgeDetectUnsupported, ) PINS_CLEANUP = [] def _pins_shutdown(): for routine in PINS_CLEANUP: routine() class Pin(object): """ Abstract base class representing a GPIO pin or a pin from an IO extender. Descendents should override property getters and setters to accurately represent the capabilities of pins. The following functions *must* be overridden: * :meth:`_get_function` * :meth:`_set_function` * :meth:`_get_state` The following functions *may* be overridden if applicable: * :meth:`close` * :meth:`_set_state` * :meth:`_get_frequency` * :meth:`_set_frequency` * :meth:`_get_pull` * :meth:`_set_pull` * :meth:`_get_bounce` * :meth:`_set_bounce` * :meth:`_get_edges` * :meth:`_set_edges` * :meth:`_get_when_changed` * :meth:`_set_when_changed` * :meth:`pi_info` * :meth:`output_with_state` * :meth:`input_with_pull` .. warning:: Descendents must ensure that pin instances representing the same physical hardware are identical, right down to object identity. The framework relies on this to correctly clean up resources at interpreter shutdown. """ def __repr__(self): return "Abstract pin" def close(self): """ Cleans up the resources allocated to the pin. After this method is called, this :class:`Pin` instance may no longer be used to query or control the pin's state. """ pass def output_with_state(self, state): """ Sets the pin's function to "output" and specifies an initial state for the pin. By default this is equivalent to performing:: pin.function = 'output' pin.state = state However, descendents may override this in order to provide the smallest possible delay between configuring the pin for output and specifying an initial value (which can be important for avoiding "blips" in active-low configurations). """ self.function = 'output' self.state = state def input_with_pull(self, pull): """ Sets the pin's function to "input" and specifies an initial pull-up for the pin. By default this is equivalent to performing:: pin.function = 'input' pin.pull = pull However, descendents may override this order to provide the smallest possible delay between configuring the pin for input and pulling the pin up/down (which can be important for avoiding "blips" in some configurations). """ self.function = 'input' self.pull = pull def _get_function(self): return "input" def _set_function(self, value): if value != "input": raise PinInvalidFunction( "Cannot set the function of pin %r to %s" % (self, value)) function = property( lambda self: self._get_function(), lambda self, value: self._set_function(value), doc="""\ The function of the pin. This property is a string indicating the current function or purpose of the pin. Typically this is the string "input" or "output". However, in some circumstances it can be other strings indicating non-GPIO related functionality. With certain pin types (e.g. GPIO pins), this attribute can be changed to configure the function of a pin. If an invalid function is specified, for this attribute, :exc:`PinInvalidFunction` will be raised. """) def _get_state(self): return 0 def _set_state(self, value): raise PinSetInput("Cannot set the state of input pin %r" % self) state = property( lambda self: self._get_state(), lambda self, value: self._set_state(value), doc="""\ The state of the pin. This is 0 for low, and 1 for high. As a low level view of the pin, no swapping is performed in the case of pull ups (see :attr:`pull` for more information). If PWM is currently active (when :attr:`frequency` is not ``None``), this represents the PWM duty cycle as a value between 0.0 and 1.0. If a pin is currently configured for input, and an attempt is made to set this attribute, :exc:`PinSetInput` will be raised. If an invalid value is specified for this attribute, :exc:`PinInvalidState` will be raised. """) def _get_pull(self): return 'floating' def _set_pull(self, value): raise PinFixedPull("Cannot change pull-up on pin %r" % self) pull = property( lambda self: self._get_pull(), lambda self, value: self._set_pull(value), doc="""\ The pull-up state of the pin represented as a string. This is typically one of the strings "up", "down", or "floating" but additional values may be supported by the underlying hardware. If the pin does not support changing pull-up state (for example because of a fixed pull-up resistor), attempts to set this property will raise :exc:`PinFixedPull`. If the specified value is not supported by the underlying hardware, :exc:`PinInvalidPull` is raised. """) def _get_frequency(self): return None def _set_frequency(self, value): if value is not None: raise PinPWMUnsupported("PWM is not supported on pin %r" % self) frequency = property( lambda self: self._get_frequency(), lambda self, value: self._set_frequency(value), doc="""\ The frequency (in Hz) for the pin's PWM implementation, or ``None`` if PWM is not currently in use. This value always defaults to ``None`` and may be changed with certain pin types to activate or deactivate PWM. If the pin does not support PWM, :exc:`PinPWMUnsupported` will be raised when attempting to set this to a value other than ``None``. """) def _get_bounce(self): return None def _set_bounce(self, value): if value is not None: raise PinEdgeDetectUnsupported("Edge detection is not supported on pin %r" % self) bounce = property( lambda self: self._get_bounce(), lambda self, value: self._set_bounce(value), doc="""\ The amount of bounce detection (elimination) currently in use by edge detection, measured in seconds. If bounce detection is not currently in use, this is ``None``. If the pin does not support edge detection, attempts to set this property will raise :exc:`PinEdgeDetectUnsupported`. If the pin supports edge detection, the class must implement bounce detection, even if only in software. """) def _get_edges(self): return 'none' def _set_edges(self, value): raise PinEdgeDetectUnsupported("Edge detection is not supported on pin %r" % self) edges = property( lambda self: self._get_edges(), lambda self, value: self._set_edges(value), doc="""\ The edge that will trigger execution of the function or bound method assigned to :attr:`when_changed`. This can be one of the strings "both" (the default), "rising", "falling", or "none". If the pin does not support edge detection, attempts to set this property will raise :exc:`PinEdgeDetectUnsupported`. """) def _get_when_changed(self): return None def _set_when_changed(self, value): raise PinEdgeDetectUnsupported("Edge detection is not supported on pin %r" % self) when_changed = property( lambda self: self._get_when_changed(), lambda self, value: self._set_when_changed(value), doc="""\ A function or bound method to be called when the pin's state changes (more specifically when the edge specified by :attr:`edges` is detected on the pin). The function or bound method must take no parameters. If the pin does not support edge detection, attempts to set this property will raise :exc:`PinEdgeDetectUnsupported`. """) @classmethod def pi_info(cls): """ Returns a :class:`PiBoardInfo` instance representing the Pi that instances of this pin class will be attached to. If the pins represented by this class are not *directly* attached to a Pi (e.g. the pin is attached to a board attached to the Pi, or the pins are not on a Pi at all), this may return ``None``. """ return None class LocalPin(Pin): """ Abstract base class representing pins attached locally to a Pi. This forms the base class for local-only pin interfaces (:class:`RPiGPIOPin`, :class:`RPIOPin`, and :class:`NativePin`). """ _PI_REVISION = None @classmethod def pi_info(cls): """ Returns a :class:`PiBoardInfo` instance representing the local Pi. The Pi's revision is determined by reading :file:`/proc/cpuinfo`. If no valid revision is found, returns ``None``. """ # Cache the result as we can reasonably assume it won't change during # runtime (this is LocalPin after all; descendents that deal with # remote Pis should inherit from Pin instead) if cls._PI_REVISION is None: with io.open('/proc/cpuinfo', 'r') as f: for line in f: if line.startswith('Revision'): revision = line.split(':')[1].strip().lower() overvolted = revision.startswith('100') if overvolted: revision = revision[-4:] cls._PI_REVISION = revision break if cls._PI_REVISION is None: return None # something weird going on return pi_info(cls._PI_REVISION) gpiozero-1.3.1.post1/gpiozero/pins/rpio.py0000644000175000017500000001673412761305734020126 0ustar benben00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import warnings import RPIO import RPIO.PWM from RPIO.Exceptions import InvalidChannelException from . import LocalPin, PINS_CLEANUP from .data import pi_info from ..exc import ( PinInvalidFunction, PinSetInput, PinFixedPull, PinInvalidPull, PinInvalidBounce, PinInvalidState, PinPWMError, PinNonPhysical, PinNoPins, ) class RPIOPin(LocalPin): """ Uses the `RPIO`_ library to interface to the Pi's GPIO pins. This is the default pin implementation if the RPi.GPIO library is not installed, but RPIO is. Supports all features including PWM (hardware via DMA). .. note:: Please note that at the time of writing, RPIO is only compatible with Pi 1's; the Raspberry Pi 2 Model B is *not* supported. Also note that root access is required so scripts must typically be run with ``sudo``. You can construct RPIO pins manually like so:: from gpiozero.pins.rpio import RPIOPin from gpiozero import LED led = LED(RPIOPin(12)) .. _RPIO: https://pythonhosted.org/RPIO/ """ _PINS = {} GPIO_FUNCTIONS = { 'input': RPIO.IN, 'output': RPIO.OUT, 'alt0': RPIO.ALT0, } GPIO_PULL_UPS = { 'up': RPIO.PUD_UP, 'down': RPIO.PUD_DOWN, 'floating': RPIO.PUD_OFF, } GPIO_FUNCTION_NAMES = {v: k for (k, v) in GPIO_FUNCTIONS.items()} GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()} PI_INFO = None def __new__(cls, number): if not cls._PINS: RPIO.setmode(RPIO.BCM) RPIO.setwarnings(False) RPIO.wait_for_interrupts(threaded=True) RPIO.PWM.setup() RPIO.PWM.init_channel(0, 10000) PINS_CLEANUP.append(RPIO.PWM.cleanup) PINS_CLEANUP.append(RPIO.stop_waiting_for_interrupts) PINS_CLEANUP.append(RPIO.cleanup) if cls.PI_INFO is None: cls.PI_INFO = pi_info() try: return cls._PINS[number] except KeyError: self = super(RPIOPin, cls).__new__(cls) try: cls.PI_INFO.physical_pin('GPIO%d' % number) except PinNoPins: warnings.warn( PinNonPhysical( 'no physical pins exist for GPIO%d' % number)) self._number = number self._pull = 'up' if cls.PI_INFO.pulled_up('GPIO%d' % number) else 'floating' self._pwm = False self._duty_cycle = None self._bounce = None self._when_changed = None self._edges = 'both' try: RPIO.setup(self._number, RPIO.IN, self.GPIO_PULL_UPS[self._pull]) except InvalidChannelException as e: raise ValueError(e) cls._PINS[number] = self return self def __repr__(self): return "GPIO%d" % self._number @property def number(self): return self._number def close(self): self.frequency = None self.when_changed = None RPIO.setup(self._number, RPIO.IN, RPIO.PUD_OFF) def _get_function(self): return self.GPIO_FUNCTION_NAMES[RPIO.gpio_function(self._number)] def _set_function(self, value): if value != 'input': self._pull = 'floating' try: RPIO.setup(self._number, self.GPIO_FUNCTIONS[value], self.GPIO_PULL_UPS[self._pull]) except KeyError: raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self)) def _get_state(self): if self._pwm: return self._duty_cycle else: return RPIO.input(self._number) def _set_state(self, value): if not 0 <= value <= 1: raise PinInvalidState('invalid state "%s" for pin %r' % (value, self)) if self._pwm: RPIO.PWM.clear_channel_gpio(0, self._number) if value == 0: RPIO.output(self._number, False) elif value == 1: RPIO.output(self._number, True) else: RPIO.PWM.add_channel_pulse(0, self._number, start=0, width=int(1000 * value)) self._duty_cycle = value else: try: RPIO.output(self._number, value) except ValueError: raise PinInvalidState('invalid state "%s" for pin %r' % (value, self)) except RuntimeError: raise PinSetInput('cannot set state of pin %r' % self) def _get_pull(self): return self._pull def _set_pull(self, value): if self.function != 'input': raise PinFixedPull('cannot set pull on non-input pin %r' % self) if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self._number): raise PinFixedPull('%r has a physical pull-up resistor' % self) try: RPIO.setup(self._number, RPIO.IN, self.GPIO_PULL_UPS[value]) self._pull = value except KeyError: raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self)) def _get_frequency(self): if self._pwm: return 100 else: return None def _set_frequency(self, value): if value is not None and value != 100: raise PinPWMError( 'RPIOPin implementation is currently limited to ' '100Hz sub-cycles') if not self._pwm and value is not None: self._pwm = True # Dirty hack to get RPIO's PWM support to setup, but do nothing, # for a given GPIO pin RPIO.PWM.add_channel_pulse(0, self._number, start=0, width=0) RPIO.PWM.clear_channel_gpio(0, self._number) elif self._pwm and value is None: RPIO.PWM.clear_channel_gpio(0, self._number) self._pwm = False def _get_bounce(self): return None if self._bounce is None else (self._bounce / 1000) def _set_bounce(self, value): if value is not None and value < 0: raise PinInvalidBounce('bounce must be 0 or greater') f = self.when_changed self.when_changed = None try: self._bounce = None if value is None else int(value * 1000) finally: self.when_changed = f def _get_edges(self): return self._edges def _set_edges(self, value): f = self.when_changed self.when_changed = None try: self._edges = value finally: self.when_changed = f def _get_when_changed(self): return self._when_changed def _set_when_changed(self, value): if self._when_changed is None and value is not None: self._when_changed = value RPIO.add_interrupt_callback( self._number, lambda channel, value: self._when_changed(), self._edges, self.GPIO_PULL_UPS[self._pull], self._bounce) elif self._when_changed is not None and value is None: try: RPIO.del_interrupt_callback(self._number) except KeyError: # Ignore this exception which occurs during shutdown; this # simply means RPIO's built-in cleanup has already run and # removed the handler pass self._when_changed = None else: self._when_changed = value gpiozero-1.3.1.post1/gpiozero/pins/mock.py0000644000175000017500000002042713046621340020067 0ustar benben00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') from collections import namedtuple from time import time, sleep from threading import Thread, Event try: from math import isclose except ImportError: from ..compat import isclose from . import Pin from .data import pi_info from ..exc import PinSetInput, PinPWMUnsupported, PinFixedPull PinState = namedtuple('PinState', ('timestamp', 'state')) class MockPin(Pin): """ A mock pin used primarily for testing. This class does *not* support PWM. """ _PINS = {} @classmethod def clear_pins(cls): cls._PINS.clear() @classmethod def pi_info(cls): return pi_info('a21041') # Pretend we're a Pi 2B def __new__(cls, number): if not (0 <= number < 54): raise ValueError('invalid pin %d specified (must be 0..53)' % number) try: old_pin = cls._PINS[number] except KeyError: self = super(MockPin, cls).__new__(cls) cls._PINS[number] = self self._number = number self._function = 'input' self._state = False self._pull = 'floating' self._bounce = None self._edges = 'both' self._when_changed = None self.clear_states() return self if old_pin.__class__ != cls: raise ValueError('pin %d is already in use as a %s' % (number, old_pin.__class__.__name__)) return old_pin def __repr__(self): return 'MOCK%d' % self._number @property def number(self): return self._number def close(self): self.when_changed = None self.function = 'input' def _get_function(self): return self._function def _set_function(self, value): assert value in ('input', 'output') self._function = value if value == 'input': # Drive the input to the pull self._set_pull(self._get_pull()) def _get_state(self): return self._state def _set_state(self, value): if self._function == 'input': raise PinSetInput('cannot set state of pin %r' % self) assert self._function == 'output' assert 0 <= value <= 1 self._change_state(bool(value)) def _change_state(self, value): if self._state != value: t = time() self._state = value self.states.append(PinState(t - self._last_change, value)) self._last_change = t return True return False def _get_frequency(self): return None def _set_frequency(self, value): if value is not None: raise PinPWMUnsupported() def _get_pull(self): return self._pull def _set_pull(self, value): assert self._function == 'input' assert value in ('floating', 'up', 'down') self._pull = value if value == 'up': self.drive_high() elif value == 'down': self.drive_low() def _get_bounce(self): return self._bounce def _set_bounce(self, value): # XXX Need to implement this self._bounce = value def _get_edges(self): return self._edges def _set_edges(self, value): assert value in ('none', 'falling', 'rising', 'both') self._edges = value def _get_when_changed(self): return self._when_changed def _set_when_changed(self, value): self._when_changed = value def drive_high(self): assert self._function == 'input' if self._change_state(True): if self._edges in ('both', 'rising') and self._when_changed is not None: self._when_changed() def drive_low(self): assert self._function == 'input' if self._change_state(False): if self._edges in ('both', 'falling') and self._when_changed is not None: self._when_changed() def clear_states(self): self._last_change = time() self.states = [PinState(0.0, self._state)] def assert_states(self, expected_states): # Tests that the pin went through the expected states (a list of values) for actual, expected in zip(self.states, expected_states): assert actual.state == expected def assert_states_and_times(self, expected_states): # Tests that the pin went through the expected states at the expected # times (times are compared with a tolerance of tens-of-milliseconds as # that's about all we can reasonably expect in a non-realtime # environment on a Pi 1) for actual, expected in zip(self.states, expected_states): assert isclose(actual.timestamp, expected[0], rel_tol=0.05, abs_tol=0.05) assert isclose(actual.state, expected[1]) class MockPulledUpPin(MockPin): """ This derivative of :class:`MockPin` emulates a pin with a physical pull-up resistor. """ def _set_pull(self, value): if value != 'up': raise PinFixedPull('pin has a physical pull-up resistor') class MockChargingPin(MockPin): """ This derivative of :class:`MockPin` emulates a pin which, when set to input, waits a predetermined length of time and then drives itself high (as if attached to, e.g. a typical circuit using an LDR and a capacitor to time the charging rate). """ def __init__(self, number): super(MockChargingPin, self).__init__() self.charge_time = 0.01 # dark charging time self._charge_stop = Event() self._charge_thread = None def _set_function(self, value): super(MockChargingPin, self)._set_function(value) if value == 'input': if self._charge_thread: self._charge_stop.set() self._charge_thread.join() self._charge_stop.clear() self._charge_thread = Thread(target=self._charge) self._charge_thread.start() elif value == 'output': if self._charge_thread: self._charge_stop.set() self._charge_thread.join() def _charge(self): if not self._charge_stop.wait(self.charge_time): try: self.drive_high() except AssertionError: # Charging pins are typically flipped between input and output # repeatedly; if another thread has already flipped us to # output ignore the assertion-error resulting from attempting # to drive the pin high pass class MockTriggerPin(MockPin): """ This derivative of :class:`MockPin` is intended to be used with another :class:`MockPin` to emulate a distance sensor. Set :attr:`echo_pin` to the corresponding pin instance. When this pin is driven high it will trigger the echo pin to drive high for the echo time. """ def __init__(self, number): super(MockTriggerPin, self).__init__() self.echo_pin = None self.echo_time = 0.04 # longest echo time self._echo_thread = None def _set_state(self, value): super(MockTriggerPin, self)._set_state(value) if value: if self._echo_thread: self._echo_thread.join() self._echo_thread = Thread(target=self._echo) self._echo_thread.start() def _echo(self): sleep(0.001) self.echo_pin.drive_high() sleep(self.echo_time) self.echo_pin.drive_low() class MockPWMPin(MockPin): """ This derivative of :class:`MockPin` adds PWM support. """ def __init__(self, number): super(MockPWMPin, self).__init__() self._frequency = None def close(self): self.frequency = None super(MockPWMPin, self).close() def _set_state(self, value): if self._function == 'input': raise PinSetInput('cannot set state of pin %r' % self) assert self._function == 'output' assert 0 <= value <= 1 self._change_state(float(value)) def _get_frequency(self): return self._frequency def _set_frequency(self, value): if value is not None: assert self._function == 'output' self._frequency = value if value is None: self._change_state(0.0) gpiozero-1.3.1.post1/gpiozero/pins/data.py0000644000175000017500000006546013046621340020055 0ustar benben00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import io from collections import namedtuple from ..exc import PinUnknownPi, PinMultiplePins, PinNoPins # Some useful constants for describing pins V1_8 = '1V8' V3_3 = '3V3' V5 = '5V' GND = 'GND' NC = 'NC' # not connected GPIO0 = 'GPIO0' GPIO1 = 'GPIO1' GPIO2 = 'GPIO2' GPIO3 = 'GPIO3' GPIO4 = 'GPIO4' GPIO5 = 'GPIO5' GPIO6 = 'GPIO6' GPIO7 = 'GPIO7' GPIO8 = 'GPIO8' GPIO9 = 'GPIO9' GPIO10 = 'GPIO10' GPIO11 = 'GPIO11' GPIO12 = 'GPIO12' GPIO13 = 'GPIO13' GPIO14 = 'GPIO14' GPIO15 = 'GPIO15' GPIO16 = 'GPIO16' GPIO17 = 'GPIO17' GPIO18 = 'GPIO18' GPIO19 = 'GPIO19' GPIO20 = 'GPIO20' GPIO21 = 'GPIO21' GPIO22 = 'GPIO22' GPIO23 = 'GPIO23' GPIO24 = 'GPIO24' GPIO25 = 'GPIO25' GPIO26 = 'GPIO26' GPIO27 = 'GPIO27' GPIO28 = 'GPIO28' GPIO29 = 'GPIO29' GPIO30 = 'GPIO30' GPIO31 = 'GPIO31' GPIO32 = 'GPIO32' GPIO33 = 'GPIO33' GPIO34 = 'GPIO34' GPIO35 = 'GPIO35' GPIO36 = 'GPIO36' GPIO37 = 'GPIO37' GPIO38 = 'GPIO38' GPIO39 = 'GPIO39' GPIO40 = 'GPIO40' GPIO41 = 'GPIO41' GPIO42 = 'GPIO42' GPIO43 = 'GPIO43' GPIO44 = 'GPIO44' GPIO45 = 'GPIO45' # Pin maps for various board revisions and headers REV1_P1 = { # pin func pullup pin func pullup 1: (V3_3, False), 2: (V5, False), 3: (GPIO0, True), 4: (V5, False), 5: (GPIO1, True), 6: (GND, False), 7: (GPIO4, False), 8: (GPIO14, False), 9: (GND, False), 10: (GPIO15, False), 11: (GPIO17, False), 12: (GPIO18, False), 13: (GPIO21, False), 14: (GND, False), 15: (GPIO22, False), 16: (GPIO23, False), 17: (V3_3, False), 18: (GPIO24, False), 19: (GPIO10, False), 20: (GND, False), 21: (GPIO9, False), 22: (GPIO25, False), 23: (GPIO11, False), 24: (GPIO8, False), 25: (GND, False), 26: (GPIO7, False), } REV2_P1 = { 1: (V3_3, False), 2: (V5, False), 3: (GPIO2, True), 4: (V5, False), 5: (GPIO3, True), 6: (GND, False), 7: (GPIO4, False), 8: (GPIO14, False), 9: (GND, False), 10: (GPIO15, False), 11: (GPIO17, False), 12: (GPIO18, False), 13: (GPIO27, False), 14: (GND, False), 15: (GPIO22, False), 16: (GPIO23, False), 17: (V3_3, False), 18: (GPIO24, False), 19: (GPIO10, False), 20: (GND, False), 21: (GPIO9, False), 22: (GPIO25, False), 23: (GPIO11, False), 24: (GPIO8, False), 25: (GND, False), 26: (GPIO7, False), } REV2_P5 = { 1: (V5, False), 2: (V3_3, False), 3: (GPIO28, False), 4: (GPIO29, False), 5: (GPIO30, False), 6: (GPIO31, False), 7: (GND, False), 8: (GND, False), } PLUS_P1 = { 1: (V3_3, False), 2: (V5, False), 3: (GPIO2, True), 4: (V5, False), 5: (GPIO3, True), 6: (GND, False), 7: (GPIO4, False), 8: (GPIO14, False), 9: (GND, False), 10: (GPIO15, False), 11: (GPIO17, False), 12: (GPIO18, False), 13: (GPIO27, False), 14: (GND, False), 15: (GPIO22, False), 16: (GPIO23, False), 17: (V3_3, False), 18: (GPIO24, False), 19: (GPIO10, False), 20: (GND, False), 21: (GPIO9, False), 22: (GPIO25, False), 23: (GPIO11, False), 24: (GPIO8, False), 25: (GND, False), 26: (GPIO7, False), 27: (GPIO0, False), 28: (GPIO1, False), 29: (GPIO5, False), 30: (GND, False), 31: (GPIO6, False), 32: (GPIO12, False), 33: (GPIO13, False), 34: (GND, False), 35: (GPIO19, False), 36: (GPIO16, False), 37: (GPIO26, False), 38: (GPIO20, False), 39: (GND, False), 40: (GPIO21, False), } CM_SODIMM = { 1: (GND, False), 2: ('EMMC DISABLE N', False), 3: (GPIO0, False), 4: (NC, False), 5: (GPIO1, False), 6: (NC, False), 7: (GND, False), 8: (NC, False), 9: (GPIO2, False), 10: (NC, False), 11: (GPIO3, False), 12: (NC, False), 13: (GND, False), 14: (NC, False), 15: (GPIO4, False), 16: (NC, False), 17: (GPIO5, False), 18: (NC, False), 19: (GND, False), 20: (NC, False), 21: (GPIO6, False), 22: (NC, False), 23: (GPIO7, False), 24: (NC, False), 25: (GND, False), 26: (GND, False), 27: (GPIO8, False), 28: (GPIO28, False), 29: (GPIO9, False), 30: (GPIO29, False), 31: (GND, False), 32: (GND, False), 33: (GPIO10, False), 34: (GPIO30, False), 35: (GPIO11, False), 36: (GPIO31, False), 37: (GND, False), 38: (GND, False), 39: ('GPIO0-27 VREF', False), 40: ('GPIO0-27 VREF', False), # Gap in SODIMM pins 41: ('GPIO28-45 VREF', False), 42: ('GPIO28-45 VREF', False), 43: (GND, False), 44: (GND, False), 45: (GPIO12, False), 46: (GPIO32, False), 47: (GPIO13, False), 48: (GPIO33, False), 49: (GND, False), 50: (GND, False), 51: (GPIO14, False), 52: (GPIO34, False), 53: (GPIO15, False), 54: (GPIO35, False), 55: (GND, False), 56: (GND, False), 57: (GPIO16, False), 58: (GPIO36, False), 59: (GPIO17, False), 60: (GPIO37, False), 61: (GND, False), 62: (GND, False), 63: (GPIO18, False), 64: (GPIO38, False), 65: (GPIO19, False), 66: (GPIO39, False), 67: (GND, False), 68: (GND, False), 69: (GPIO20, False), 70: (GPIO40, False), 71: (GPIO21, False), 72: (GPIO41, False), 73: (GND, False), 74: (GND, False), 75: (GPIO22, False), 76: (GPIO42, False), 77: (GPIO23, False), 78: (GPIO43, False), 79: (GND, False), 80: (GND, False), 81: (GPIO24, False), 82: (GPIO44, False), 83: (GPIO25, False), 84: (GPIO45, False), 85: (GND, False), 86: (GND, False), 87: (GPIO26, False), 88: ('GPIO46 1V8', False), 89: (GPIO27, False), 90: ('GPIO47 1V8', False), 91: (GND, False), 92: (GND, False), 93: ('DSI0 DN1', False), 94: ('DSI1 DP0', False), 95: ('DSI0 DP1', False), 96: ('DSI1 DN0', False), 97: (GND, False), 98: (GND, False), 99: ('DSI0 DN0', False), 100: ('DSI1 CP', False), 101: ('DSI0 DP0', False), 102: ('DSI1 CN', False), 103: (GND, False), 104: (GND, False), 105: ('DSI0 CN', False), 106: ('DSI1 DP3', False), 107: ('DSI0 CP', False), 108: ('DSI1 DN3', False), 109: (GND, False), 110: (GND, False), 111: ('HDMI CK N', False), 112: ('DSI1 DP2', False), 113: ('HDMI CK P', False), 114: ('DSI1 DN2', False), 115: (GND, False), 116: (GND, False), 117: ('HDMI D0 N', False), 118: ('DSI1 DP1', False), 119: ('HDMI D0 P', False), 120: ('DSI1 DN1', False), 121: (GND, False), 122: (GND, False), 123: ('HDMI D1 N', False), 124: (NC, False), 125: ('HDMI D1 P', False), 126: (NC, False), 127: (GND, False), 128: (NC, False), 129: ('HDMI D2 N', False), 130: (NC, False), 131: ('HDMI D2 P', False), 132: (NC, False), 133: (GND, False), 134: (GND, False), 135: ('CAM1 DP3', False), 136: ('CAM0 DP0', False), 137: ('CAM1 DN3', False), 138: ('CAM0 DN0', False), 139: (GND, False), 140: (GND, False), 141: ('CAM1 DP2', False), 142: ('CAM0 CP', False), 143: ('CAM1 DN2', False), 144: ('CAM0 CN', False), 145: (GND, False), 146: (GND, False), 147: ('CAM1 CP', False), 148: ('CAM0 DP1', False), 149: ('CAM1 CN', False), 150: ('CAM0 DN1', False), 151: (GND, False), 152: (GND, False), 153: ('CAM1 DP1', False), 154: (NC, False), 155: ('CAM1 DN1', False), 156: (NC, False), 157: (GND, False), 158: (NC, False), 159: ('CAM1 DP0', False), 160: (NC, False), 161: ('CAM1 DN0', False), 162: (NC, False), 163: (GND, False), 164: (GND, False), 165: ('USB DP', False), 166: ('TVDAC', False), 167: ('USB DM', False), 168: ('USB OTGID', False), 169: (GND, False), 170: (GND, False), 171: ('HDMI CEC', False), 172: ('VC TRST N', False), 173: ('HDMI SDA', False), 174: ('VC TDI', False), 175: ('HDMI SCL', False), 176: ('VC TMS', False), 177: ('RUN', False), 178: ('VC TDO', False), 179: ('VDD CORE', False), 180: ('VC TCK', False), 181: (GND, False), 182: (GND, False), 183: (V1_8, False), 184: (V1_8, False), 185: (V1_8, False), 186: (V1_8, False), 187: (GND, False), 188: (GND, False), 189: ('VDAC', False), 190: ('VDAC', False), 191: (V3_3, False), 192: (V3_3, False), 193: (V3_3, False), 194: (V3_3, False), 195: (GND, False), 196: (GND, False), 197: ('VBAT', False), 198: ('VBAT', False), 199: ('VBAT', False), 200: ('VBAT', False), } # The following data is sourced from a combination of the following locations: # # http://elinux.org/RPi_HardwareHistory # http://elinux.org/RPi_Low-level_peripherals # https://git.drogon.net/?p=wiringPi;a=blob;f=wiringPi/wiringPi.c#l807 PI_REVISIONS = { # rev model pcb_rev released soc manufacturer ram storage usb eth wifi bt csi dsi headers 0x2: ('B', '1.0', '2012Q1', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ), 0x3: ('B', '1.0', '2012Q3', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ), 0x4: ('B', '2.0', '2012Q3', 'BCM2835', 'Sony', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), 0x5: ('B', '2.0', '2012Q4', 'BCM2835', 'Qisda', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), 0x6: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), 0x7: ('A', '2.0', '2013Q1', 'BCM2835', 'Egoman', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), 0x8: ('A', '2.0', '2013Q1', 'BCM2835', 'Sony', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), 0x9: ('A', '2.0', '2013Q1', 'BCM2835', 'Qisda', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), 0xd: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), 0xe: ('B', '2.0', '2012Q4', 'BCM2835', 'Sony', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), 0xf: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), 0x10: ('B+', '1.2', '2014Q3', 'BCM2835', 'Sony', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), 0x11: ('CM', '1.2', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 1, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), 0x12: ('A+', '1.2', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ), 0x13: ('B+', '1.2', '2015Q1', 'BCM2835', 'Egoman', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), 0x14: ('CM', '1.1', '2014Q2', 'BCM2835', 'Embest', 512, 'eMMC', 1, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), 0x15: ('A+', '1.1', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ), 0xa01041: ('2B', '1.1', '2015Q1', 'BCM2836', 'Sony', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), 0xa21041: ('2B', '1.1', '2015Q1', 'BCM2836', 'Embest', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), 0x900092: ('Zero', '1.2', '2015Q4', 'BCM2835', 'Sony', 512, 'MicroSD', 1, 0, False, False, 0, 0, {'P1': PLUS_P1}, ), 0xa02082: ('3B', '1.2', '2016Q1', 'BCM2837', 'Sony', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ), 0xa22082: ('3B', '1.2', '2016Q1', 'BCM2837', 'Embest', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ), 0x900093: ('Zero', '1.3', '2016Q2', 'BCM2835', 'Sony', 512, 'MicroSD', 1, 0, False, False, 1, 0, {'P1': PLUS_P1}, ), } class PinInfo(namedtuple('PinInfo', ( 'number', 'function', 'pull_up', ))): """ This class is a :func:`~collections.namedtuple` derivative used to represent information about a pin present on a GPIO header. The following attributes are defined: .. attribute:: number An integer containing the physical pin number on the header (starting from 1 in accordance with convention). .. attribute:: function A string describing the function of the pin. Some common examples include "GND" (for pins connecting to ground), "3V3" (for pins which output 3.3 volts), "GPIO9" (for GPIO9 in the Broadcom numbering scheme), etc. .. attribute:: pull_up A bool indicating whether the pin has a physical pull-up resistor permanently attached (this is usually ``False`` but GPIO2 and GPIO3 are *usually* ``True``). This is used internally by gpiozero to raise errors when pull-down is requested on a pin with a physical pull-up resistor. """ class PiBoardInfo(namedtuple('PiBoardInfo', ( 'revision', 'model', 'pcb_revision', 'released', 'soc', 'manufacturer', 'memory', 'storage', 'usb', 'ethernet', 'wifi', 'bluetooth', 'csi', 'dsi', 'headers', ))): """ This class is a :func:`~collections.namedtuple` derivative used to represent information about a particular model of Raspberry Pi. While it is a tuple, it is strongly recommended that you use the following named attributes to access the data contained within. .. automethod:: physical_pin .. automethod:: physical_pins .. automethod:: pulled_up .. attribute:: revision A string indicating the revision of the Pi. This is unique to each revision and can be considered the "key" from which all other attributes are derived. However, in itself the string is fairly meaningless. .. attribute:: model A string containing the model of the Pi (for example, "B", "B+", "A+", "2B", "CM" (for the Compute Module), or "Zero"). .. attribute:: pcb_revision A string containing the PCB revision number which is silk-screened onto the Pi (on some models). .. note:: This is primarily useful to distinguish between the model B revision 1.0 and 2.0 (not to be confused with the model 2B) which had slightly different pinouts on their 26-pin GPIO headers. .. attribute:: released A string containing an approximate release date for this revision of the Pi (formatted as yyyyQq, e.g. 2012Q1 means the first quarter of 2012). .. attribute:: soc A string indicating the SoC (`system on a chip`_) that this revision of the Pi is based upon. .. attribute:: manufacturer A string indicating the name of the manufacturer (usually "Sony" but a few others exist). .. attribute:: memory An integer indicating the amount of memory (in Mb) connected to the SoC. .. note:: This can differ substantially from the amount of RAM available to the operating system as the GPU's memory is shared with the CPU. When the camera module is activated, at least 128Mb of RAM is typically reserved for the GPU. .. attribute:: storage A string indicating the type of bootable storage used with this revision of Pi, e.g. "SD", "MicroSD", or "eMMC" (for the Compute Module). .. attribute:: usb An integer indicating how many USB ports are physically present on this revision of the Pi. .. note:: This does *not* include the micro-USB port used to power the Pi. .. attribute:: ethernet An integer indicating how many Ethernet ports are physically present on this revision of the Pi. .. attribute:: wifi A bool indicating whether this revision of the Pi has wifi built-in. .. attribute:: bluetooth A bool indicating whether this revision of the Pi has bluetooth built-in. .. attribute:: csi An integer indicating the number of CSI (camera) ports available on this revision of the Pi. .. attribute:: dsi An integer indicating the number of DSI (display) ports available on this revision of the Pi. .. attribute:: headers A dictionary which maps header labels to dictionaries which map physical pin numbers to :class:`PinInfo` tuples. For example, to obtain information about pin 12 on header P1 you would query ``headers['P1'][12]``. .. _system on a chip: https://en.wikipedia.org/wiki/System_on_a_chip """ def physical_pins(self, function): """ Return the physical pins supporting the specified *function* as tuples of ``(header, pin_number)`` where *header* is a string specifying the header containing the *pin_number*. Note that the return value is a :class:`set` which is not indexable. Use :func:`physical_pin` if you are expecting a single return value. :param str function: The pin function you wish to search for. Usually this is something like "GPIO9" for Broadcom GPIO pin 9, or "GND" for all the pins connecting to electrical ground. """ return { (header, pin.number) for (header, pins) in self.headers.items() for pin in pins.values() if pin.function == function } def physical_pin(self, function): """ Return the physical pin supporting the specified *function*. If no pins support the desired *function*, this function raises :exc:`PinNoPins`. If multiple pins support the desired *function*, :exc:`PinMultiplePins` will be raised (use :func:`physical_pins` if you expect multiple pins in the result, such as for electrical ground). :param str function: The pin function you wish to search for. Usually this is something like "GPIO9" for Broadcom GPIO pin 9. """ result = self.physical_pins(function) if len(result) > 1: raise PinMultiplePins('multiple pins can be used for %s' % function) elif result: return result.pop() else: raise PinNoPins('no pins can be used for %s' % function) def pulled_up(self, function): """ Returns a bool indicating whether a physical pull-up is attached to the pin supporting the specified *function*. Either :exc:`PinNoPins` or :exc:`PinMultiplePins` may be raised if the function is not associated with a single pin. :param str function: The pin function you wish to determine pull-up for. Usually this is something like "GPIO9" for Broadcom GPIO pin 9. """ try: header, number = self.physical_pin(function) except PinNoPins: return False else: return self.headers[header][number].pull_up def _parse_pi_revision(revision): # For new-style revisions the value's bit pattern is as follows: # # MSB -----------------------> LSB # uuuuuuuuFMMMCCCCPPPPTTTTTTTTRRRR # # uuuuuuuu - Unused # F - New flag (1=valid new-style revision, 0=old-style) # MMM - Memory size (0=256, 1=512, 2=1024) # CCCC - Manufacturer (0=Sony, 1=Egoman, 2=Embest) # PPPP - Processor (0=2835, 1=2836, 2=2837) # TTTTTTTT - Type (0=A, 1=B, 2=A+, 3=B+, 4=2B, 5=Alpha (??), 6=CM, 8=3B, 9=Zero) # RRRR - Revision (0, 1, or 2) if not (revision & 0x800000): raise PinUnknownPi('cannot parse "%x"; this is not a new-style revision' % revision) try: model = { 0: 'A', 1: 'B', 2: 'A+', 3: 'B+', 4: '2B', 6: 'CM', 8: '3B', 9: 'Zero', }[(revision & 0xff0) >> 4] if model in ('A', 'B'): pcb_revision = { 0: '1.0', # is this right? 1: '1.0', 2: '2.0', }[revision & 0x0f] else: pcb_revision = '1.%d' % (revision & 0x0f) released = { 'A': '2013Q1', 'B': '2012Q1' if pcb_revision == '1.0' else '2012Q4', 'A+': '2014Q4', 'B+': '2014Q3', '2B': '2015Q1', 'CM': '2014Q2', '3B': '2016Q1', 'Zero': '2015Q4' if pcb_revision == '1.0' else '2016Q2', }[model] soc = { 0: 'BCM2835', 1: 'BCM2836', 2: 'BCM2837', }[(revision & 0xf000) >> 12] manufacturer = { 0: 'Sony', 1: 'Egoman', 2: 'Embest', }[(revision & 0xf0000) >> 16] memory = { 0: 256, 1: 512, 2: 1024, }[(revision & 0x700000) >> 20] storage = { 'A': 'SD', 'B': 'SD', 'CM': 'eMMC', }.get(model, 'MicroSD') usb = { 'A': 1, 'A+': 1, 'Zero': 1, 'B': 2, 'CM': 0, }.get(model, 4) ethernet = { 'A': 0, 'A+': 0, 'Zero': 0, 'CM': 0, }.get(model, 1) wifi = { '3B': True, }.get(model, False) bluetooth = { '3B': True, }.get(model, False) csi = { 'Zero': 0 if pcb_revision == '1.0' else 1, 'CM': 2, }.get(model, 1) dsi = { 'Zero': 0, }.get(model, csi) headers = { 'A': {'P1': REV2_P1, 'P5': REV2_P5}, 'B': {'P1': REV2_P1, 'P5': REV2_P5} if pcb_revision == '2.0' else {'P1': REV1_P1}, 'CM': {'SODIMM': CM_SODIMM}, }.get(model, {'P1': PLUS_P1}) except KeyError: raise PinUnknownPi('unable to parse new-style revision "%x"' % revision) else: return ( model, pcb_revision, released, soc, manufacturer, memory, storage, usb, ethernet, wifi, bluetooth, csi, dsi, headers, ) def pi_info(revision=None): """ Returns a :class:`PiBoardInfo` instance containing information about a *revision* of the Raspberry Pi. :param str revision: The revision of the Pi to return information about. If this is omitted or ``None`` (the default), then the library will attempt to determine the model of Pi it is running on and return information about that. """ if revision is None: # NOTE: This import is declared locally for two reasons. Firstly it # avoids a circular dependency (devices->pins->pins.data->devices). # Secondly, pin_factory is one global which might potentially be # re-written by a user's script at runtime hence we should re-import # here in case it's changed since initialization from ..devices import pin_factory result = pin_factory.pi_info() if result is None: raise PinUnknownPi('The default pin_factory is not attached to a Pi') else: return result else: if isinstance(revision, bytes): revision = revision.decode('ascii') if isinstance(revision, str): revision = int(revision, base=16) else: # be nice to people passing an int (or something numeric anyway) revision = int(revision) try: ( model, pcb_revision, released, soc, manufacturer, memory, storage, usb, ethernet, wifi, bluetooth, csi, dsi, headers, ) = PI_REVISIONS[revision] except KeyError: ( model, pcb_revision, released, soc, manufacturer, memory, storage, usb, ethernet, wifi, bluetooth, csi, dsi, headers, ) = _parse_pi_revision(revision) headers = { header: { number: PinInfo(number, function, pull_up) for number, (function, pull_up) in header_data.items() } for header, header_data in headers.items() } return PiBoardInfo( '%04x' % revision, model, pcb_revision, released, soc, manufacturer, memory, storage, usb, ethernet, wifi, bluetooth, csi, dsi, headers, ) gpiozero-1.3.1.post1/gpiozero/pins/native.py0000644000175000017500000003020212761305734020425 0ustar benben00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) nstr = str str = type('') import io import os import mmap import errno import struct import warnings from time import sleep from threading import Thread, Event, Lock from collections import Counter from . import LocalPin, PINS_CLEANUP from .data import pi_info from ..exc import ( PinInvalidPull, PinInvalidEdges, PinInvalidFunction, PinFixedPull, PinSetInput, PinNonPhysical, PinNoPins, ) class GPIOMemory(object): GPIO_BASE_OFFSET = 0x200000 PERI_BASE_OFFSET = { 'BCM2708': 0x20000000, 'BCM2835': 0x20000000, 'BCM2709': 0x3f000000, 'BCM2836': 0x3f000000, } # From BCM2835 data-sheet, p.91 GPFSEL_OFFSET = 0x00 >> 2 GPSET_OFFSET = 0x1c >> 2 GPCLR_OFFSET = 0x28 >> 2 GPLEV_OFFSET = 0x34 >> 2 GPEDS_OFFSET = 0x40 >> 2 GPREN_OFFSET = 0x4c >> 2 GPFEN_OFFSET = 0x58 >> 2 GPHEN_OFFSET = 0x64 >> 2 GPLEN_OFFSET = 0x70 >> 2 GPAREN_OFFSET = 0x7c >> 2 GPAFEN_OFFSET = 0x88 >> 2 GPPUD_OFFSET = 0x94 >> 2 GPPUDCLK_OFFSET = 0x98 >> 2 def __init__(self): try: self.fd = os.open('/dev/gpiomem', os.O_RDWR | os.O_SYNC) except OSError: try: self.fd = os.open('/dev/mem', os.O_RDWR | os.O_SYNC) except OSError: raise IOError( 'unable to open /dev/gpiomem or /dev/mem; ' 'upgrade your kernel or run as root') else: offset = self.peripheral_base() + self.GPIO_BASE_OFFSET else: offset = 0 self.mem = mmap.mmap(self.fd, 4096, offset=offset) def close(self): self.mem.close() os.close(self.fd) def peripheral_base(self): try: with io.open('/proc/device-tree/soc/ranges', 'rb') as f: f.seek(4) return struct.unpack(nstr('>L'), f.read(4))[0] except IOError: with io.open('/proc/cpuinfo', 'r') as f: for line in f: if line.startswith('Hardware'): try: return self.PERI_BASE_OFFSET[line.split(':')[1].strip()] except KeyError: raise IOError('unable to determine RPi revision') raise IOError('unable to determine peripheral base') def __getitem__(self, index): return struct.unpack_from(nstr('> self._func_shift) & 7] def _set_function(self, value): try: value = self.GPIO_FUNCTIONS[value] except KeyError: raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self)) self._MEM[self._func_offset] = ( self._MEM[self._func_offset] & ~(7 << self._func_shift) | (value << self._func_shift) ) def _get_state(self): return bool(self._MEM[self._level_offset] & (1 << self._level_shift)) def _set_state(self, value): if self.function == 'input': raise PinSetInput('cannot set state of pin %r' % self) if value: self._MEM[self._set_offset] = 1 << self._set_shift else: self._MEM[self._clear_offset] = 1 << self._clear_shift def _get_pull(self): return self.GPIO_PULL_UP_NAMES[self._pull] def _set_pull(self, value): if self.function != 'input': raise PinFixedPull('cannot set pull on non-input pin %r' % self) if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self.number): raise PinFixedPull('%r has a physical pull-up resistor' % self) try: value = self.GPIO_PULL_UPS[value] except KeyError: raise PinInvalidPull('invalid pull direction "%s" for pin %r' % (value, self)) self._MEM[self._MEM.GPPUD_OFFSET] = value sleep(0.000000214) self._MEM[self._pull_offset] = 1 << self._pull_shift sleep(0.000000214) self._MEM[self._MEM.GPPUD_OFFSET] = 0 self._MEM[self._pull_offset] = 0 self._pull = value def _get_edges(self): rising = bool(self._MEM[self._rising_offset] & (1 << self._rising_shift)) falling = bool(self._MEM[self._falling_offset] & (1 << self._falling_shift)) return self.GPIO_EDGES_NAMES[(rising, falling)] def _set_edges(self, value): try: rising, falling = self.GPIO_EDGES[value] except KeyError: raise PinInvalidEdges('invalid edge specification "%s" for pin %r' % self) f = self.when_changed self.when_changed = None try: self._MEM[self._rising_offset] = ( self._MEM[self._rising_offset] & ~(1 << self._rising_shift) | (rising << self._rising_shift) ) self._MEM[self._falling_offset] = ( self._MEM[self._falling_offset] & ~(1 << self._falling_shift) | (falling << self._falling_shift) ) finally: self.when_changed = f def _get_when_changed(self): return self._when_changed def _set_when_changed(self, value): if self._when_changed is None and value is not None: self._when_changed = value self._change_thread = Thread(target=self._change_watch) self._change_thread.daemon = True self._change_event.clear() self._change_thread.start() elif self._when_changed is not None and value is None: self._change_event.set() self._change_thread.join() self._change_thread = None self._when_changed = None else: self._when_changed = value def _change_watch(self): offset = self._edge_offset mask = 1 << self._edge_shift self._MEM[offset] = mask # clear any existing detection bit while not self._change_event.wait(0.001): if self._MEM[offset] & mask: self._MEM[offset] = mask self._when_changed() gpiozero-1.3.1.post1/gpiozero/pins/rpigpio.py0000644000175000017500000001664712761305734020631 0ustar benben00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import warnings from RPi import GPIO from . import LocalPin from .data import pi_info from ..exc import ( PinInvalidFunction, PinSetInput, PinFixedPull, PinInvalidPull, PinInvalidState, PinInvalidBounce, PinPWMFixedValue, PinNonPhysical, PinNoPins, ) class RPiGPIOPin(LocalPin): """ Uses the `RPi.GPIO`_ library to interface to the Pi's GPIO pins. This is the default pin implementation if the RPi.GPIO library is installed. Supports all features including PWM (via software). Because this is the default pin implementation you can use it simply by specifying an integer number for the pin in most operations, e.g.:: from gpiozero import LED led = LED(12) However, you can also construct RPi.GPIO pins manually if you wish:: from gpiozero.pins.rpigpio import RPiGPIOPin from gpiozero import LED led = LED(RPiGPIOPin(12)) .. _RPi.GPIO: https://pypi.python.org/pypi/RPi.GPIO """ _PINS = {} GPIO_FUNCTIONS = { 'input': GPIO.IN, 'output': GPIO.OUT, 'i2c': GPIO.I2C, 'spi': GPIO.SPI, 'pwm': GPIO.HARD_PWM, 'serial': GPIO.SERIAL, 'unknown': GPIO.UNKNOWN, } GPIO_PULL_UPS = { 'up': GPIO.PUD_UP, 'down': GPIO.PUD_DOWN, 'floating': GPIO.PUD_OFF, } GPIO_EDGES = { 'both': GPIO.BOTH, 'rising': GPIO.RISING, 'falling': GPIO.FALLING, } GPIO_FUNCTION_NAMES = {v: k for (k, v) in GPIO_FUNCTIONS.items()} GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()} GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()} PI_INFO = None def __new__(cls, number): if not cls._PINS: GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) if cls.PI_INFO is None: cls.PI_INFO = pi_info() try: return cls._PINS[number] except KeyError: self = super(RPiGPIOPin, cls).__new__(cls) try: cls.PI_INFO.physical_pin('GPIO%d' % number) except PinNoPins: warnings.warn( PinNonPhysical( 'no physical pins exist for GPIO%d' % number)) self._number = number self._pull = 'up' if cls.PI_INFO.pulled_up('GPIO%d' % number) else 'floating' self._pwm = None self._frequency = None self._duty_cycle = None self._bounce = -666 self._when_changed = None self._edges = GPIO.BOTH GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[self._pull]) cls._PINS[number] = self return self def __repr__(self): return "GPIO%d" % self._number @property def number(self): return self._number def close(self): self.frequency = None self.when_changed = None GPIO.cleanup(self._number) def output_with_state(self, state): self._pull = 'floating' GPIO.setup(self._number, GPIO.OUT, initial=state) def input_with_pull(self, pull): if pull != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self._number): raise PinFixedPull('%r has a physical pull-up resistor' % self) try: GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[pull]) self._pull = pull except KeyError: raise PinInvalidPull('invalid pull "%s" for pin %r' % (pull, self)) def _get_function(self): return self.GPIO_FUNCTION_NAMES[GPIO.gpio_function(self._number)] def _set_function(self, value): if value != 'input': self._pull = 'floating' if value in ('input', 'output') and value in self.GPIO_FUNCTIONS: GPIO.setup(self._number, self.GPIO_FUNCTIONS[value], self.GPIO_PULL_UPS[self._pull]) else: raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self)) def _get_state(self): if self._pwm: return self._duty_cycle else: return GPIO.input(self._number) def _set_state(self, value): if self._pwm: try: self._pwm.ChangeDutyCycle(value * 100) except ValueError: raise PinInvalidState('invalid state "%s" for pin %r' % (value, self)) self._duty_cycle = value else: try: GPIO.output(self._number, value) except ValueError: raise PinInvalidState('invalid state "%s" for pin %r' % (value, self)) except RuntimeError: raise PinSetInput('cannot set state of pin %r' % self) def _get_pull(self): return self._pull def _set_pull(self, value): if self.function != 'input': raise PinFixedPull('cannot set pull on non-input pin %r' % self) if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self._number): raise PinFixedPull('%r has a physical pull-up resistor' % self) try: GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[value]) self._pull = value except KeyError: raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self)) def _get_frequency(self): return self._frequency def _set_frequency(self, value): if self._frequency is None and value is not None: try: self._pwm = GPIO.PWM(self._number, value) except RuntimeError: raise PinPWMFixedValue('cannot start PWM on pin %r' % self) self._pwm.start(0) self._duty_cycle = 0 self._frequency = value elif self._frequency is not None and value is not None: self._pwm.ChangeFrequency(value) self._frequency = value elif self._frequency is not None and value is None: self._pwm.stop() self._pwm = None self._duty_cycle = None self._frequency = None def _get_bounce(self): return None if self._bounce == -666 else (self._bounce / 1000) def _set_bounce(self, value): if value is not None and value < 0: raise PinInvalidBounce('bounce must be 0 or greater') f = self.when_changed self.when_changed = None try: self._bounce = -666 if value is None else int(value * 1000) finally: self.when_changed = f def _get_edges(self): return self.GPIO_EDGES_NAMES[self._edges] def _set_edges(self, value): f = self.when_changed self.when_changed = None try: self._edges = self.GPIO_EDGES[value] finally: self.when_changed = f def _get_when_changed(self): return self._when_changed def _set_when_changed(self, value): if self._when_changed is None and value is not None: self._when_changed = value GPIO.add_event_detect( self._number, self._edges, callback=lambda channel: self._when_changed(), bouncetime=self._bounce) elif self._when_changed is not None and value is None: GPIO.remove_event_detect(self._number) self._when_changed = None else: self._when_changed = value gpiozero-1.3.1.post1/gpiozero/pins/pigpiod.py0000644000175000017500000002304112767742026020602 0ustar benben00000000000000from __future__ import ( unicode_literals, absolute_import, print_function, division, ) str = type('') import warnings import pigpio import os from . import Pin from .data import pi_info from ..exc import ( PinInvalidFunction, PinSetInput, PinFixedPull, PinInvalidPull, PinInvalidBounce, PinInvalidState, PinNonPhysical, PinNoPins, ) class PiGPIOPin(Pin): """ Uses the `pigpio`_ library to interface to the Pi's GPIO pins. The pigpio library relies on a daemon (``pigpiod``) to be running as root to provide access to the GPIO pins, and communicates with this daemon over a network socket. While this does mean only the daemon itself should control the pins, the architecture does have several advantages: * Pins can be remote controlled from another machine (the other machine doesn't even have to be a Raspberry Pi; it simply needs the `pigpio`_ client library installed on it) * The daemon supports hardware PWM via the DMA controller * Your script itself doesn't require root privileges; it just needs to be able to communicate with the daemon You can construct pigpiod pins manually like so:: from gpiozero.pins.pigpiod import PiGPIOPin from gpiozero import LED led = LED(PiGPIOPin(12)) This is particularly useful for controlling pins on a remote machine. To accomplish this simply specify the host (and optionally port) when constructing the pin:: from gpiozero.pins.pigpiod import PiGPIOPin from gpiozero import LED from signal import pause led = LED(PiGPIOPin(12, host='192.168.0.2')) .. note:: In some circumstances, especially when playing with PWM, it does appear to be possible to get the daemon into "unusual" states. We would be most interested to hear any bug reports relating to this (it may be a bug in our pin implementation). A workaround for now is simply to restart the ``pigpiod`` daemon. .. _pigpio: http://abyz.co.uk/rpi/pigpio/ """ _CONNECTIONS = {} # maps (host, port) to (connection, pi_info) _PINS = {} GPIO_FUNCTIONS = { 'input': pigpio.INPUT, 'output': pigpio.OUTPUT, 'alt0': pigpio.ALT0, 'alt1': pigpio.ALT1, 'alt2': pigpio.ALT2, 'alt3': pigpio.ALT3, 'alt4': pigpio.ALT4, 'alt5': pigpio.ALT5, } GPIO_PULL_UPS = { 'up': pigpio.PUD_UP, 'down': pigpio.PUD_DOWN, 'floating': pigpio.PUD_OFF, } GPIO_EDGES = { 'both': pigpio.EITHER_EDGE, 'rising': pigpio.RISING_EDGE, 'falling': pigpio.FALLING_EDGE, } GPIO_FUNCTION_NAMES = {v: k for (k, v) in GPIO_FUNCTIONS.items()} GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()} GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()} def __new__( cls, number, host=os.getenv('PIGPIO_ADDR', 'localhost'), port=int(os.getenv('PIGPIO_PORT', 8888))): try: return cls._PINS[(host, port, number)] except KeyError: self = super(PiGPIOPin, cls).__new__(cls) cls.pi_info(host, port) # implicitly creates connection self._connection, self._pi_info = cls._CONNECTIONS[(host, port)] try: self._pi_info.physical_pin('GPIO%d' % number) except PinNoPins: warnings.warn( PinNonPhysical( 'no physical pins exist for GPIO%d' % number)) self._host = host self._port = port self._number = number self._pull = 'up' if self._pi_info.pulled_up('GPIO%d' % number) else 'floating' self._pwm = False self._bounce = None self._when_changed = None self._callback = None self._edges = pigpio.EITHER_EDGE try: self._connection.set_mode(self._number, pigpio.INPUT) except pigpio.error as e: raise ValueError(e) self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[self._pull]) self._connection.set_glitch_filter(self._number, 0) cls._PINS[(host, port, number)] = self return self def __repr__(self): if self._host == 'localhost': return "GPIO%d" % self._number else: return "GPIO%d on %s:%d" % (self._number, self._host, self._port) @property def host(self): return self._host @property def port(self): return self._port @property def number(self): return self._number def close(self): # If we're shutting down, the connection may have disconnected itself # already. Unfortunately, the connection's "connected" property is # rather buggy - disconnecting doesn't set it to False! So we're # naughty and check an internal variable instead... if self._connection.sl.s is not None: self.frequency = None self.when_changed = None self.function = 'input' self.pull = 'up' if self._pi_info.pulled_up('GPIO%d' % self.number) else 'floating' def _get_function(self): return self.GPIO_FUNCTION_NAMES[self._connection.get_mode(self._number)] def _set_function(self, value): if value != 'input': self._pull = 'floating' try: self._connection.set_mode(self._number, self.GPIO_FUNCTIONS[value]) except KeyError: raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self)) def _get_state(self): if self._pwm: return ( self._connection.get_PWM_dutycycle(self._number) / self._connection.get_PWM_range(self._number) ) else: return bool(self._connection.read(self._number)) def _set_state(self, value): if self._pwm: try: value = int(value * self._connection.get_PWM_range(self._number)) if value != self._connection.get_PWM_dutycycle(self._number): self._connection.set_PWM_dutycycle(self._number, value) except pigpio.error: raise PinInvalidState('invalid state "%s" for pin %r' % (value, self)) elif self.function == 'input': raise PinSetInput('cannot set state of pin %r' % self) else: # write forces pin to OUTPUT, hence the check above self._connection.write(self._number, bool(value)) def _get_pull(self): return self._pull def _set_pull(self, value): if self.function != 'input': raise PinFixedPull('cannot set pull on non-input pin %r' % self) if value != 'up' and self._pi_info.pulled_up('GPIO%d' % self._number): raise PinFixedPull('%r has a physical pull-up resistor' % self) try: self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[value]) self._pull = value except KeyError: raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self)) def _get_frequency(self): if self._pwm: return self._connection.get_PWM_frequency(self._number) return None def _set_frequency(self, value): if not self._pwm and value is not None: self._connection.set_PWM_frequency(self._number, value) self._connection.set_PWM_range(self._number, 10000) self._connection.set_PWM_dutycycle(self._number, 0) self._pwm = True elif self._pwm and value is not None: if value != self._connection.get_PWM_frequency(self._number): self._connection.set_PWM_frequency(self._number, value) self._connection.set_PWM_range(self._number, 10000) elif self._pwm and value is None: self._connection.write(self._number, 0) self._pwm = False def _get_bounce(self): return None if not self._bounce else self._bounce / 1000000 def _set_bounce(self, value): if value is None: value = 0 elif value < 0: raise PinInvalidBounce('bounce must be 0 or greater') self._connection.set_glitch_filter(self._number, int(value * 1000000)) def _get_edges(self): return self.GPIO_EDGES_NAMES[self._edges] def _set_edges(self, value): f = self.when_changed self.when_changed = None try: self._edges = self.GPIO_EDGES[value] finally: self.when_changed = f def _get_when_changed(self): if self._callback is None: return None return self._callback.callb.func def _set_when_changed(self, value): if self._callback is not None: self._callback.cancel() self._callback = None if value is not None: self._callback = self._connection.callback( self._number, self._edges, lambda gpio, level, tick: value()) @classmethod def pi_info( cls, host=os.getenv('PIGPIO_ADDR', 'localhost'), port=int(os.getenv('PIGPIO_PORT', 8888))): try: connection, info = cls._CONNECTIONS[(host, port)] except KeyError: connection = pigpio.pi(host, port) revision = '%04x' % connection.get_hardware_revision() info = pi_info(revision) cls._CONNECTIONS[(host, port)] = (connection, info) return info gpiozero-1.3.1.post1/gpiozero/output_devices.py0000644000175000017500000012202512767762545021252 0ustar benben00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) from threading import Lock from itertools import repeat, cycle, chain from .exc import OutputDeviceBadValue, GPIOPinMissing from .devices import GPIODevice, Device, CompositeDevice from .mixins import SourceMixin from .threads import GPIOThread class OutputDevice(SourceMixin, GPIODevice): """ Represents a generic GPIO output device. This class extends :class:`GPIODevice` to add facilities common to GPIO output devices: an :meth:`on` method to switch the device on, a corresponding :meth:`off` method, and a :meth:`toggle` method. :param int pin: The GPIO pin (in BCM numbering) that the device is connected to. If this is ``None`` a :exc:`GPIOPinMissing` will be raised. :param bool active_high: If ``True`` (the default), the :meth:`on` method will set the GPIO to HIGH. If ``False``, the :meth:`on` method will set the GPIO to LOW (the :meth:`off` method always does the opposite). :param bool initial_value: If ``False`` (the default), the device will be off initially. If ``None``, the device will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the device will be switched on initially. """ 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' else: self.pin.output_with_state(self._value_to_state(initial_value)) def _value_to_state(self, value): return bool(self._active_state if value else self._inactive_state) def _write(self, value): try: self.pin.state = self._value_to_state(value) except AttributeError: self._check_open() raise def on(self): """ Turns the device on. """ self._write(True) def off(self): """ Turns the device off. """ 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): """ Returns ``True`` if the device is currently active and ``False`` otherwise. Setting this property changes the state of the device. """ return super(OutputDevice, self).value @value.setter def value(self, value): self._write(value) @property def active_high(self): """ When ``True``, the :attr:`value` property is ``True`` when the device's :attr:`pin` is high. When ``False`` the :attr:`value` property is ``True`` when the device's pin is low (i.e. the value is inverted). This property can be set after construction; be warned that changing it will invert :attr:`value` (i.e. changing this property doesn't change the device's pin state - it just changes how that state is interpreted). """ return self._active_state @active_high.setter def active_high(self, value): self._active_state = True if value else False self._inactive_state = False if value else True def __repr__(self): try: return '' % ( self.__class__.__name__, self.pin, self.active_high, self.is_active) except: return super(OutputDevice, self).__repr__() class DigitalOutputDevice(OutputDevice): """ Represents a generic output device with typical on/off behaviour. This class extends :class:`OutputDevice` with a :meth:`blink` method which uses an optional background thread to handle toggling the device state without further interaction. """ 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._controller = None @property def value(self): return self._read() @value.setter def value(self, value): self._stop_blink() self._write(value) def close(self): self._stop_blink() super(DigitalOutputDevice, self).close() def on(self): self._stop_blink() self._write(True) def off(self): self._stop_blink() self._write(False) def blink(self, on_time=1, off_time=1, n=None, background=True): """ Make the device turn on and off repeatedly. :param float on_time: Number of seconds on. Defaults to 1 second. :param float off_time: Number of seconds off. Defaults to 1 second. :param int n: Number of times to blink; ``None`` (the default) means forever. :param bool background: If ``True`` (the default), start a background thread to continue blinking and return immediately. If ``False``, only return when the blink is finished (warning: the default value of *n* will result in this method never returning). """ self._stop_blink() self._blink_thread = GPIOThread( target=self._blink_device, args=(on_time, off_time, n) ) self._blink_thread.start() if not background: self._blink_thread.join() self._blink_thread = None def _stop_blink(self): if self._controller: self._controller._stop_blink(self) self._controller = None if self._blink_thread: self._blink_thread.stop() self._blink_thread = None def _blink_device(self, on_time, off_time, n): iterable = repeat(0) if n is None else repeat(0, n) for _ in iterable: self._write(True) if self._blink_thread.stopping.wait(on_time): break self._write(False) if self._blink_thread.stopping.wait(off_time): break class LED(DigitalOutputDevice): """ Extends :class:`DigitalOutputDevice` and represents a light emitting diode (LED). Connect the cathode (short leg, flat side) of the LED to a ground pin; connect the anode (longer leg) to a limiting resistor; connect the other side of the limiting resistor to a GPIO pin (the limiting resistor can be placed either side of the LED). The following example will light the LED:: from gpiozero import LED led = LED(17) led.on() :param int pin: The GPIO pin which the LED is attached to. See :ref:`pin_numbering` for valid pin numbers. :param bool active_high: If ``True`` (the default), the LED will operate normally with the circuit described above. If ``False`` you should wire the cathode to the GPIO pin, and the anode to a 3V3 pin (via a limiting resistor). :param bool initial_value: If ``False`` (the default), the LED will be off initially. If ``None``, the LED will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the LED will be switched on initially. """ pass LED.is_lit = LED.is_active class Buzzer(DigitalOutputDevice): """ Extends :class:`DigitalOutputDevice` and represents a digital buzzer component. Connect the cathode (negative pin) of the buzzer to a ground pin; connect the other side to any GPIO pin. The following example will sound the buzzer:: from gpiozero import Buzzer bz = Buzzer(3) bz.on() :param int pin: The GPIO pin which the buzzer is attached to. See :ref:`pin_numbering` for valid pin numbers. :param bool active_high: If ``True`` (the default), the buzzer will operate normally with the circuit described above. If ``False`` you should wire the cathode to the GPIO pin, and the anode to a 3V3 pin. :param bool initial_value: If ``False`` (the default), the buzzer will be silent initially. If ``None``, the buzzer will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the buzzer will be switched on initially. """ pass Buzzer.beep = Buzzer.blink class PWMOutputDevice(OutputDevice): """ Generic output device configured for pulse-width modulation (PWM). :param int pin: The GPIO pin which the device is attached to. See :ref:`pin_numbering` for valid pin numbers. :param bool active_high: If ``True`` (the default), the :meth:`on` method will set the GPIO to HIGH. If ``False``, the :meth:`on` method will set the GPIO to LOW (the :meth:`off` method always does the opposite). :param float initial_value: If ``0`` (the default), the device's duty cycle will be 0 initially. Other values between 0 and 1 can be specified as an initial duty cycle. Note that ``None`` cannot be specified (unlike the parent class) as there is no way to tell PWM not to alter the state of the pin. :param int frequency: The frequency (in Hz) of pulses emitted to drive the device. Defaults to 100Hz. """ def __init__(self, pin=None, active_high=True, initial_value=0, frequency=100): self._blink_thread = None self._controller = None if not 0 <= initial_value <= 1: raise OutputDeviceBadValue("initial_value must be between 0 and 1") super(PWMOutputDevice, self).__init__(pin, active_high, initial_value=None) try: # XXX need a way of setting these together self.pin.frequency = frequency self.value = initial_value except: self.close() raise def close(self): self._stop_blink() try: self.pin.frequency = None except AttributeError: # If the pin's already None, ignore the exception pass super(PWMOutputDevice, self).close() def _state_to_value(self, state): return float(state if self.active_high else 1 - state) def _value_to_state(self, value): return float(value if self.active_high else 1 - value) def _write(self, value): if not 0 <= value <= 1: raise OutputDeviceBadValue("PWM value must be between 0 and 1") super(PWMOutputDevice, self)._write(value) @property def value(self): """ The duty cycle of the PWM device. 0.0 is off, 1.0 is fully on. Values in between may be specified for varying levels of power in the device. """ return self._read() @value.setter def value(self, value): self._stop_blink() self._write(value) def on(self): self._stop_blink() self._write(1) def off(self): self._stop_blink() self._write(0) def toggle(self): """ Toggle the state of the device. If the device is currently off (:attr:`value` is 0.0), this changes it to "fully" on (:attr:`value` is 1.0). If the device has a duty cycle (:attr:`value`) of 0.1, this will toggle it to 0.9, and so on. """ self._stop_blink() self.value = 1 - self.value @property def is_active(self): """ Returns ``True`` if the device is currently active (:attr:`value` is non-zero) and ``False`` otherwise. """ return self.value != 0 @property def frequency(self): """ The frequency of the pulses used with the PWM device, in Hz. The default is 100Hz. """ return self.pin.frequency @frequency.setter def frequency(self, value): self.pin.frequency = value def blink( self, on_time=1, off_time=1, fade_in_time=0, fade_out_time=0, n=None, background=True): """ Make the device turn on and off repeatedly. :param float on_time: Number of seconds on. Defaults to 1 second. :param float off_time: Number of seconds off. Defaults to 1 second. :param float fade_in_time: Number of seconds to spend fading in. Defaults to 0. :param float fade_out_time: Number of seconds to spend fading out. Defaults to 0. :param int n: Number of times to blink; ``None`` (the default) means forever. :param bool background: If ``True`` (the default), start a background thread to continue blinking and return immediately. If ``False``, only return when the blink is finished (warning: the default value of *n* will result in this method never returning). """ self._stop_blink() self._blink_thread = GPIOThread( target=self._blink_device, args=(on_time, off_time, fade_in_time, fade_out_time, n) ) self._blink_thread.start() if not background: self._blink_thread.join() self._blink_thread = None def pulse(self, fade_in_time=1, fade_out_time=1, n=None, background=True): """ Make the device fade in and out repeatedly. :param float fade_in_time: Number of seconds to spend fading in. Defaults to 1. :param float fade_out_time: Number of seconds to spend fading out. Defaults to 1. :param int n: Number of times to pulse; ``None`` (the default) means forever. :param bool background: If ``True`` (the default), start a background thread to continue pulsing and return immediately. If ``False``, only return when the pulse is finished (warning: the default value of *n* will result in this method never returning). """ on_time = off_time = 0 self.blink( on_time, off_time, fade_in_time, fade_out_time, n, background ) def _stop_blink(self): if self._controller: self._controller._stop_blink(self) self._controller = None if self._blink_thread: self._blink_thread.stop() self._blink_thread = None def _blink_device( self, on_time, off_time, fade_in_time, fade_out_time, n, fps=25): sequence = [] if fade_in_time > 0: sequence += [ (i * (1 / fps) / fade_in_time, 1 / fps) for i in range(int(fps * fade_in_time)) ] sequence.append((1, on_time)) if fade_out_time > 0: sequence += [ (1 - (i * (1 / fps) / fade_out_time), 1 / fps) for i in range(int(fps * fade_out_time)) ] sequence.append((0, off_time)) sequence = ( cycle(sequence) if n is None else chain.from_iterable(repeat(sequence, n)) ) for value, delay in sequence: self._write(value) if self._blink_thread.stopping.wait(delay): break class PWMLED(PWMOutputDevice): """ Extends :class:`PWMOutputDevice` and represents a light emitting diode (LED) with variable brightness. A typical configuration of such a device is to connect a GPIO pin to the anode (long leg) of the LED, and the cathode (short leg) to ground, with an optional resistor to prevent the LED from burning out. :param int pin: The GPIO pin which the LED is attached to. See :ref:`pin_numbering` for valid pin numbers. :param bool active_high: If ``True`` (the default), the :meth:`on` method will set the GPIO to HIGH. If ``False``, the :meth:`on` method will set the GPIO to LOW (the :meth:`off` method always does the opposite). :param float initial_value: If ``0`` (the default), the LED will be off initially. Other values between 0 and 1 can be specified as an initial brightness for the LED. Note that ``None`` cannot be specified (unlike the parent class) as there is no way to tell PWM not to alter the state of the pin. :param int frequency: The frequency (in Hz) of pulses emitted to drive the LED. Defaults to 100Hz. """ pass PWMLED.is_lit = PWMLED.is_active def _led_property(index, doc=None): def getter(self): return self._leds[index].value def setter(self, value): self._stop_blink() self._leds[index].value = value return property(getter, setter, doc=doc) class RGBLED(SourceMixin, Device): """ Extends :class:`Device` and represents a full color LED component (composed of red, green, and blue LEDs). Connect the common cathode (longest leg) to a ground pin; connect each of the other legs (representing the red, green, and blue anodes) to any GPIO pins. You can either use three limiting resistors (one per anode) or a single limiting resistor on the cathode. The following code will make the LED purple:: from gpiozero import RGBLED led = RGBLED(2, 3, 4) led.color = (1, 0, 1) :param int red: The GPIO pin that controls the red component of the RGB LED. :param int green: The GPIO pin that controls the green component of the RGB LED. :param int blue: The GPIO pin that controls the blue component of the RGB LED. :param bool active_high: Set to ``True`` (the default) for common cathode RGB LEDs. If you are using a common anode RGB LED, set this to ``False``. :param tuple initial_value: The initial color for the RGB LED. Defaults to black ``(0, 0, 0)``. :param bool pwm: If ``True`` (the default), construct :class:`PWMLED` instances for each component of the RGBLED. If ``False``, construct regular :class:`LED` instances, which prevents smooth color graduations. """ def __init__( self, red=None, green=None, blue=None, active_high=True, initial_value=(0, 0, 0), pwm=True): self._leds = () self._blink_thread = None if not all(p is not None for p in [red, green, blue]): raise GPIOPinMissing('red, green, and blue pins must be provided') LEDClass = PWMLED if pwm else LED super(RGBLED, self).__init__() self._leds = tuple(LEDClass(pin, active_high) for pin in (red, green, blue)) self.value = initial_value red = _led_property(0) green = _led_property(1) blue = _led_property(2) def close(self): if self._leds: self._stop_blink() for led in self._leds: led.close() self._leds = () super(RGBLED, self).close() @property def closed(self): return len(self._leds) == 0 @property def value(self): """ Represents the color of the LED as an RGB 3-tuple of ``(red, green, blue)`` where each value is between 0 and 1 if ``pwm`` was ``True`` when the class was constructed (and only 0 or 1 if not). For example, purple would be ``(1, 0, 1)`` and yellow would be ``(1, 1, 0)``, while orange would be ``(1, 0.5, 0)``. """ return (self.red, self.green, self.blue) @value.setter def value(self, value): for component in value: if not 0 <= component <= 1: raise OutputDeviceBadValue('each RGB color component must be between 0 and 1') if isinstance(self._leds[0], LED): if component not in (0, 1): raise OutputDeviceBadValue('each RGB color component must be 0 or 1 with non-PWM RGBLEDs') self._stop_blink() self.red, self.green, self.blue = value @property def is_active(self): """ Returns ``True`` if the LED is currently active (not black) and ``False`` otherwise. """ return self.value != (0, 0, 0) is_lit = is_active color = value def on(self): """ Turn the LED on. This equivalent to setting the LED color to white ``(1, 1, 1)``. """ self.value = (1, 1, 1) def off(self): """ Turn the LED off. This is equivalent to setting the LED color to black ``(0, 0, 0)``. """ self.value = (0, 0, 0) def toggle(self): """ Toggle the state of the device. If the device is currently off (:attr:`value` is ``(0, 0, 0)``), this changes it to "fully" on (:attr:`value` is ``(1, 1, 1)``). If the device has a specific color, this method inverts the color. """ r, g, b = self.value self.value = (1 - r, 1 - g, 1 - b) def blink( self, on_time=1, off_time=1, fade_in_time=0, fade_out_time=0, on_color=(1, 1, 1), off_color=(0, 0, 0), n=None, background=True): """ Make the device turn on and off repeatedly. :param float on_time: Number of seconds on. Defaults to 1 second. :param float off_time: Number of seconds off. Defaults to 1 second. :param float fade_in_time: Number of seconds to spend fading in. Defaults to 0. Must be 0 if ``pwm`` was ``False`` when the class was constructed (:exc:`ValueError` will be raised if not). :param float fade_out_time: Number of seconds to spend fading out. Defaults to 0. Must be 0 if ``pwm`` was ``False`` when the class was constructed (:exc:`ValueError` will be raised if not). :param tuple on_color: The color to use when the LED is "on". Defaults to white. :param tuple off_color: The color to use when the LED is "off". Defaults to black. :param int n: Number of times to blink; ``None`` (the default) means forever. :param bool background: If ``True`` (the default), start a background thread to continue blinking and return immediately. If ``False``, only return when the blink is finished (warning: the default value of *n* will result in this method never returning). """ if isinstance(self._leds[0], LED): if fade_in_time: raise ValueError('fade_in_time must be 0 with non-PWM RGBLEDs') if fade_out_time: raise ValueError('fade_out_time must be 0 with non-PWM RGBLEDs') self._stop_blink() self._blink_thread = GPIOThread( target=self._blink_device, args=( on_time, off_time, fade_in_time, fade_out_time, on_color, off_color, n ) ) self._blink_thread.start() if not background: self._blink_thread.join() self._blink_thread = None def pulse( self, fade_in_time=1, fade_out_time=1, on_color=(1, 1, 1), off_color=(0, 0, 0), n=None, background=True): """ Make the device fade in and out repeatedly. :param float fade_in_time: Number of seconds to spend fading in. Defaults to 1. :param float fade_out_time: Number of seconds to spend fading out. Defaults to 1. :param tuple on_color: The color to use when the LED is "on". Defaults to white. :param tuple off_color: The color to use when the LED is "off". Defaults to black. :param int n: Number of times to pulse; ``None`` (the default) means forever. :param bool background: If ``True`` (the default), start a background thread to continue pulsing and return immediately. If ``False``, only return when the pulse is finished (warning: the default value of *n* will result in this method never returning). """ on_time = off_time = 0 self.blink( on_time, off_time, fade_in_time, fade_out_time, on_color, off_color, n, background ) def _stop_blink(self, led=None): # If this is called with a single led, we stop all blinking anyway if self._blink_thread: self._blink_thread.stop() self._blink_thread = None def _blink_device( self, on_time, off_time, fade_in_time, fade_out_time, on_color, off_color, n, fps=25): # Define some simple lambdas to perform linear interpolation between # off_color and on_color lerp = lambda t, fade_in: tuple( (1 - t) * off + t * on if fade_in else (1 - t) * on + t * off for off, on in zip(off_color, on_color) ) sequence = [] if fade_in_time > 0: sequence += [ (lerp(i * (1 / fps) / fade_in_time, True), 1 / fps) for i in range(int(fps * fade_in_time)) ] sequence.append((on_color, on_time)) if fade_out_time > 0: sequence += [ (lerp(i * (1 / fps) / fade_out_time, False), 1 / fps) for i in range(int(fps * fade_out_time)) ] sequence.append((off_color, off_time)) sequence = ( cycle(sequence) if n is None else chain.from_iterable(repeat(sequence, n)) ) for l in self._leds: l._controller = self for value, delay in sequence: for l, v in zip(self._leds, value): l._write(v) if self._blink_thread.stopping.wait(delay): break class Motor(SourceMixin, CompositeDevice): """ Extends :class:`CompositeDevice` and represents a generic motor connected to a bi-directional motor driver circuit (i.e. an `H-bridge`_). Attach an `H-bridge`_ motor controller to your Pi; connect a power source (e.g. a battery pack or the 5V pin) to the controller; connect the outputs of the controller board to the two terminals of the motor; connect the inputs of the controller board to two GPIO pins. .. _H-bridge: https://en.wikipedia.org/wiki/H_bridge The following code will make the motor turn "forwards":: from gpiozero import Motor motor = Motor(17, 18) motor.forward() :param int forward: The GPIO pin that the forward input of the motor driver chip is connected to. :param int backward: The GPIO pin that the backward input of the motor driver chip is connected to. :param bool pwm: If ``True`` (the default), construct :class:`PWMOutputDevice` instances for the motor controller pins, allowing both direction and variable speed control. If ``False``, construct :class:`DigitalOutputDevice` instances, allowing only direction control. """ def __init__(self, forward=None, backward=None, pwm=True): if not all(p is not None for p in [forward, backward]): raise GPIOPinMissing( 'forward and backward pins must be provided' ) PinClass = PWMOutputDevice if pwm else DigitalOutputDevice super(Motor, self).__init__( forward_device=PinClass(forward), backward_device=PinClass(backward), _order=('forward_device', 'backward_device')) @property def value(self): """ Represents the speed of the motor as a floating point value between -1 (full speed backward) and 1 (full speed forward), with 0 representing stopped. """ return self.forward_device.value - self.backward_device.value @value.setter def value(self, value): if not -1 <= value <= 1: raise OutputDeviceBadValue("Motor value must be between -1 and 1") if value > 0: try: self.forward(value) except ValueError as e: raise OutputDeviceBadValue(e) elif value < 0: try: self.backward(-value) except ValueError as e: raise OutputDeviceBadValue(e) else: self.stop() @property def is_active(self): """ Returns ``True`` if the motor is currently running and ``False`` otherwise. """ return self.value != 0 def forward(self, speed=1): """ Drive the motor forwards. :param float speed: The speed at which the motor should turn. Can be any value between 0 (stopped) and the default 1 (maximum speed) if ``pwm`` was ``True`` when the class was constructed (and only 0 or 1 if not). """ if not 0 <= speed <= 1: raise ValueError('forward speed must be between 0 and 1') if isinstance(self.forward_device, DigitalOutputDevice): if speed not in (0, 1): raise ValueError('forward speed must be 0 or 1 with non-PWM Motors') self.backward_device.off() self.forward_device.value = speed def backward(self, speed=1): """ Drive the motor backwards. :param float speed: The speed at which the motor should turn. Can be any value between 0 (stopped) and the default 1 (maximum speed) if ``pwm`` was ``True`` when the class was constructed (and only 0 or 1 if not). """ if not 0 <= speed <= 1: raise ValueError('backward speed must be between 0 and 1') if isinstance(self.backward_device, DigitalOutputDevice): if speed not in (0, 1): raise ValueError('backward speed must be 0 or 1 with non-PWM Motors') self.forward_device.off() self.backward_device.value = speed def reverse(self): """ Reverse the current direction of the motor. If the motor is currently idle this does nothing. Otherwise, the motor's direction will be reversed at the current speed. """ self.value = -self.value def stop(self): """ Stop the motor. """ self.forward_device.off() self.backward_device.off() class Servo(SourceMixin, CompositeDevice): """ Extends :class:`CompositeDevice` and represents a PWM-controlled servo motor connected to a GPIO pin. Connect a power source (e.g. a battery pack or the 5V pin) to the power cable of the servo (this is typically colored red); connect the ground cable of the servo (typically colored black or brown) to the negative of your battery pack, or a GND pin; connect the final cable (typically colored white or orange) to the GPIO pin you wish to use for controlling the servo. The following code will make the servo move between its minimum, maximum, and mid-point positions with a pause between each:: from gpiozero import Servo from time import sleep servo = Servo(17) while True: servo.min() sleep(1) servo.mid() sleep(1) servo.max() sleep(1) :param int pin: The GPIO pin which the device is attached to. See :ref:`pin_numbering` for valid pin numbers. :param float initial_value: If ``0`` (the default), the device's mid-point will be set initially. Other values between -1 and +1 can be specified as an initial position. ``None`` means to start the servo un-controlled (see :attr:`value`). :param float min_pulse_width: The pulse width corresponding to the servo's minimum position. This defaults to 1ms. :param float max_pulse_width: The pulse width corresponding to the servo's maximum position. This defaults to 2ms. :param float frame_width: The length of time between servo control pulses measured in seconds. This defaults to 20ms which is a common value for servos. """ def __init__( self, pin=None, initial_value=0.0, min_pulse_width=1/1000, max_pulse_width=2/1000, frame_width=20/1000): if min_pulse_width >= max_pulse_width: raise ValueError('min_pulse_width must be less than max_pulse_width') if max_pulse_width >= frame_width: raise ValueError('max_pulse_width must be less than frame_width') self._frame_width = frame_width self._min_dc = min_pulse_width / frame_width self._dc_range = (max_pulse_width - min_pulse_width) / frame_width self._min_value = -1 self._value_range = 2 super(Servo, self).__init__( pwm_device=PWMOutputDevice(pin, frequency=int(1 / frame_width))) try: self.value = initial_value except: self.close() raise @property def frame_width(self): """ The time between control pulses, measured in seconds. """ return self._frame_width @property def min_pulse_width(self): """ The control pulse width corresponding to the servo's minimum position, measured in seconds. """ return self._min_dc * self.frame_width @property def max_pulse_width(self): """ The control pulse width corresponding to the servo's maximum position, measured in seconds. """ return (self._dc_range * self.frame_width) + self.min_pulse_width @property def pulse_width(self): """ Returns the current pulse width controlling the servo. """ if self.pwm_device.pin.frequency is None: return None else: return self.pwm_device.pin.state * self.frame_width def min(self): """ Set the servo to its minimum position. """ self.value = -1 def mid(self): """ Set the servo to its mid-point position. """ self.value = 0 def max(self): """ Set the servo to its maximum position. """ self.value = 1 def detach(self): """ Temporarily disable control of the servo. This is equivalent to setting :attr:`value` to ``None``. """ self.value = None def _get_value(self): if self.pwm_device.pin.frequency is None: return None else: return ( ((self.pwm_device.pin.state - self._min_dc) / self._dc_range) * self._value_range + self._min_value) @property def value(self): """ Represents the position of the servo as a value between -1 (the minimum position) and +1 (the maximum position). This can also be the special value ``None`` indicating that the servo is currently "uncontrolled", i.e. that no control signal is being sent. Typically this means the servo's position remains unchanged, but that it can be moved by hand. """ result = self._get_value() if result is None: return result else: # NOTE: This round() only exists to ensure we don't confuse people # by returning 2.220446049250313e-16 as the default initial value # instead of 0. The reason _get_value and _set_value are split # out is for descendents that require the un-rounded values for # accuracy return round(result, 14) @value.setter def value(self, value): if value is None: self.pwm_device.pin.frequency = None elif -1 <= value <= 1: self.pwm_device.pin.frequency = int(1 / self.frame_width) self.pwm_device.pin.state = ( self._min_dc + self._dc_range * ((value - self._min_value) / self._value_range) ) else: raise OutputDeviceBadValue( "Servo value must be between -1 and 1, or None") @property def is_active(self): return self.value is not None class AngularServo(Servo): """ Extends :class:`Servo` and represents a rotational PWM-controlled servo motor which can be set to particular angles (assuming valid minimum and maximum angles are provided to the constructor). Connect a power source (e.g. a battery pack or the 5V pin) to the power cable of the servo (this is typically colored red); connect the ground cable of the servo (typically colored black or brown) to the negative of your battery pack, or a GND pin; connect the final cable (typically colored white or orange) to the GPIO pin you wish to use for controlling the servo. Next, calibrate the angles that the servo can rotate to. In an interactive Python session, construct a :class:`Servo` instance. The servo should move to its mid-point by default. Set the servo to its minimum value, and measure the angle from the mid-point. Set the servo to its maximum value, and again measure the angle:: >>> from gpiozero import Servo >>> s = Servo(17) >>> s.min() # measure the angle >>> s.max() # measure the angle You should now be able to construct an :class:`AngularServo` instance with the correct bounds:: >>> from gpiozero import AngularServo >>> s = AngularServo(17, min_angle=-42, max_angle=44) >>> s.angle = 0.0 >>> s.angle 0.0 >>> s.angle = 15 >>> s.angle 15.0 .. note:: You can set *min_angle* greater than *max_angle* if you wish to reverse the sense of the angles (e.g. ``min_angle=45, max_angle=-45``). This can be useful with servos that rotate in the opposite direction to your expectations of minimum and maximum. :param int pin: The GPIO pin which the device is attached to. See :ref:`pin_numbering` for valid pin numbers. :param float initial_angle: Sets the servo's initial angle to the specified value. The default is 0. The value specified must be between *min_angle* and *max_angle* inclusive. ``None`` means to start the servo un-controlled (see :attr:`value`). :param float min_angle: Sets the minimum angle that the servo can rotate to. This defaults to -90, but should be set to whatever you measure from your servo during calibration. :param float max_angle: Sets the maximum angle that the servo can rotate to. This defaults to 90, but should be set to whatever you measure from your servo during calibration. :param float min_pulse_width: The pulse width corresponding to the servo's minimum position. This defaults to 1ms. :param float max_pulse_width: The pulse width corresponding to the servo's maximum position. This defaults to 2ms. :param float frame_width: The length of time between servo control pulses measured in seconds. This defaults to 20ms which is a common value for servos. """ def __init__( self, pin=None, initial_angle=0.0, min_angle=-90, max_angle=90, min_pulse_width=1/1000, max_pulse_width=2/1000, frame_width=20/1000): self._min_angle = min_angle self._angular_range = max_angle - min_angle initial_value = 2 * ((initial_angle - min_angle) / self._angular_range) - 1 super(AngularServo, self).__init__( pin, initial_value, min_pulse_width, max_pulse_width, frame_width) @property def min_angle(self): """ The minimum angle that the servo will rotate to when :meth:`min` is called. """ return self._min_angle @property def max_angle(self): """ The maximum angle that the servo will rotate to when :meth:`max` is called. """ return self._min_angle + self._angular_range @property def angle(self): """ The position of the servo as an angle measured in degrees. This will only be accurate if *min_angle* and *max_angle* have been set appropriately in the constructor. This can also be the special value ``None`` indicating that the servo is currently "uncontrolled", i.e. that no control signal is being sent. Typically this means the servo's position remains unchanged, but that it can be moved by hand. """ result = self._get_value() if result is None: return None else: # NOTE: Why round(n, 12) here instead of 14? Angle ranges can be # much larger than -1..1 so we need a little more rounding to # smooth off the rough corners! return round( self._angular_range * ((result - self._min_value) / self._value_range) + self._min_angle, 12) @angle.setter def angle(self, value): if value is None: self.value = None else: self.value = ( self._value_range * ((value - self._min_angle) / self._angular_range) + self._min_value) gpiozero-1.3.1.post1/gpiozero/boards.py0000644000175000017500000011552413046621340017442 0ustar benben00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) try: from itertools import izip as zip except ImportError: pass from time import sleep from itertools import repeat, cycle, chain from threading import Lock from collections import OrderedDict from .exc import ( DeviceClosed, GPIOPinMissing, EnergenieSocketMissing, EnergenieBadSocket, OutputDeviceBadValue, ) from .input_devices import Button from .output_devices import ( OutputDevice, LED, PWMLED, RGBLED, Buzzer, Motor, ) from .threads import GPIOThread from .devices import Device, CompositeDevice from .mixins import SharedMixin, SourceMixin, HoldMixin class CompositeOutputDevice(SourceMixin, CompositeDevice): """ Extends :class:`CompositeDevice` with :meth:`on`, :meth:`off`, and :meth:`toggle` methods for controlling subordinate output devices. Also extends :attr:`value` to be writeable. :param list _order: If specified, this is the order of named items specified by keyword arguments (to ensure that the :attr:`value` tuple is constructed with a specific order). All keyword arguments *must* be included in the collection. If omitted, an alphabetically sorted order will be selected for keyword arguments. """ def on(self): """ Turn all the output devices on. """ for device in self: if isinstance(device, (OutputDevice, CompositeOutputDevice)): device.on() def off(self): """ Turn all the output devices off. """ for device in self: if isinstance(device, (OutputDevice, CompositeOutputDevice)): device.off() def toggle(self): """ Toggle all the output devices. For each device, if it's on, turn it off; if it's off, turn it on. """ for device in self: if isinstance(device, (OutputDevice, CompositeOutputDevice)): device.toggle() @property def value(self): """ A tuple containing a value for each subordinate device. This property can also be set to update the state of all subordinate output devices. """ return super(CompositeOutputDevice, self).value @value.setter def value(self, value): for device, v in zip(self, value): if isinstance(device, (OutputDevice, CompositeOutputDevice)): device.value = v # Simply ignore values for non-output devices class ButtonBoard(HoldMixin, CompositeDevice): """ Extends :class:`CompositeDevice` and represents a generic button board or collection of buttons. :param int \*pins: Specify the GPIO pins that the buttons of the board are attached to. You can designate as many pins as necessary. :param bool pull_up: If ``True`` (the default), the GPIO pins will be pulled high by default. In this case, connect the other side of the buttons to ground. If ``False``, the GPIO pins will be pulled low by default. In this case, connect the other side of the buttons to 3V3. This parameter can only be specified as a keyword parameter. :param float bounce_time: If ``None`` (the default), no software bounce compensation will be performed. Otherwise, this is the length of time (in seconds) that the buttons will ignore changes in state after an initial change. This parameter can only be specified as a keyword parameter. :param float hold_time: The length of time (in seconds) to wait after any button is pushed, until executing the :attr:`when_held` handler. Defaults to ``1``. This parameter can only be specified as a keyword parameter. :param bool hold_repeat: If ``True``, the :attr:`when_held` handler will be repeatedly executed as long as any buttons remain held, every *hold_time* seconds. If ``False`` (the default) the :attr:`when_held` handler will be only be executed once per hold. This parameter can only be specified as a keyword parameter. :param \*\*named_pins: Specify GPIO pins that buttons of the board are attached to, associating each button with a property name. You can designate as many pins as necessary and use any names, provided they're not already in use by something else. """ def __init__(self, *args, **kwargs): pull_up = kwargs.pop('pull_up', True) bounce_time = kwargs.pop('bounce_time', None) hold_time = kwargs.pop('hold_time', 1) hold_repeat = kwargs.pop('hold_repeat', False) order = kwargs.pop('_order', None) super(ButtonBoard, self).__init__( *( Button(pin, pull_up, bounce_time, hold_time, hold_repeat) for pin in args ), _order=order, **{ name: Button(pin, pull_up, bounce_time, hold_time, hold_repeat) for name, pin in kwargs.items() }) def get_new_handler(device): def fire_both_events(): device._fire_events() self._fire_events() return fire_both_events for button in self: button.pin.when_changed = get_new_handler(button) self._when_changed = None self._last_value = None # Call _fire_events once to set initial state of events self._fire_events() self.hold_time = hold_time self.hold_repeat = hold_repeat @property def pull_up(self): """ If ``True``, the device uses a pull-up resistor to set the GPIO pin "high" by default. """ return self[0].pull_up @property def when_changed(self): return self._when_changed @when_changed.setter def when_changed(self, value): self._when_changed = self._wrap_callback(value) def _fire_changed(self): if self.when_changed: self.when_changed() def _fire_events(self): super(ButtonBoard, self)._fire_events() old_value = self._last_value new_value = self._last_value = self.value if old_value is None: # Initial "indeterminate" value; don't do anything pass elif old_value != new_value: self._fire_changed() ButtonBoard.is_pressed = ButtonBoard.is_active ButtonBoard.pressed_time = ButtonBoard.active_time ButtonBoard.when_pressed = ButtonBoard.when_activated ButtonBoard.when_released = ButtonBoard.when_deactivated ButtonBoard.wait_for_press = ButtonBoard.wait_for_active ButtonBoard.wait_for_release = ButtonBoard.wait_for_inactive class LEDCollection(CompositeOutputDevice): """ Extends :class:`CompositeOutputDevice`. Abstract base class for :class:`LEDBoard` and :class:`LEDBarGraph`. """ def __init__(self, *args, **kwargs): self._blink_thread = None pwm = kwargs.pop('pwm', False) active_high = kwargs.pop('active_high', True) initial_value = kwargs.pop('initial_value', False) order = kwargs.pop('_order', None) LEDClass = PWMLED if pwm else LED super(LEDCollection, self).__init__( *( pin_or_collection if isinstance(pin_or_collection, LEDCollection) else LEDClass(pin_or_collection, active_high, initial_value) for pin_or_collection in args ), _order=order, **{ name: pin_or_collection if isinstance(pin_or_collection, LEDCollection) else LEDClass(pin_or_collection, active_high, initial_value) for name, pin_or_collection in kwargs.items() }) leds = [] for item in self: if isinstance(item, LEDCollection): for subitem in item.leds: leds.append(subitem) else: leds.append(item) self._leds = tuple(leds) @property def leds(self): """ A flat tuple of all LEDs contained in this collection (and all sub-collections). """ return self._leds @property def active_high(self): return self[0].active_high class LEDBoard(LEDCollection): """ Extends :class:`LEDCollection` and represents a generic LED board or collection of LEDs. The following example turns on all the LEDs on a board containing 5 LEDs attached to GPIO pins 2 through 6:: from gpiozero import LEDBoard leds = LEDBoard(2, 3, 4, 5, 6) leds.on() :param int \*pins: Specify the GPIO pins that the LEDs of the board are attached to. You can designate as many pins as necessary. You can also specify :class:`LEDBoard` instances to create trees of LEDs. :param bool pwm: If ``True``, construct :class:`PWMLED` instances for each pin. If ``False`` (the default), construct regular :class:`LED` instances. This parameter can only be specified as a keyword parameter. :param bool active_high: If ``True`` (the default), the :meth:`on` method will set all the associated pins to HIGH. If ``False``, the :meth:`on` method will set all pins to LOW (the :meth:`off` method always does the opposite). This parameter can only be specified as a keyword parameter. :param bool initial_value: If ``False`` (the default), all LEDs will be off initially. If ``None``, each device will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the device will be switched on initially. This parameter can only be specified as a keyword parameter. :param \*\*named_pins: Specify GPIO pins that LEDs of the board are attached to, associating each LED with a property name. You can designate as many pins as necessary and use any names, provided they're not already in use by something else. You can also specify :class:`LEDBoard` instances to create trees of LEDs. """ def __init__(self, *args, **kwargs): self._blink_leds = [] self._blink_lock = Lock() super(LEDBoard, self).__init__(*args, **kwargs) def close(self): self._stop_blink() super(LEDBoard, self).close() def on(self, *args): self._stop_blink() if args: for index in args: self[index].on() else: super(LEDBoard, self).on() def off(self, *args): self._stop_blink() if args: for index in args: self[index].off() else: super(LEDBoard, self).off() def toggle(self, *args): self._stop_blink() if args: for index in args: self[index].toggle() else: super(LEDBoard, self).toggle() def blink( self, on_time=1, off_time=1, fade_in_time=0, fade_out_time=0, n=None, background=True): """ Make all the LEDs turn on and off repeatedly. :param float on_time: Number of seconds on. Defaults to 1 second. :param float off_time: Number of seconds off. Defaults to 1 second. :param float fade_in_time: Number of seconds to spend fading in. Defaults to 0. Must be 0 if ``pwm`` was ``False`` when the class was constructed (:exc:`ValueError` will be raised if not). :param float fade_out_time: Number of seconds to spend fading out. Defaults to 0. Must be 0 if ``pwm`` was ``False`` when the class was constructed (:exc:`ValueError` will be raised if not). :param int n: Number of times to blink; ``None`` (the default) means forever. :param bool background: If ``True``, start a background thread to continue blinking and return immediately. If ``False``, only return when the blink is finished (warning: the default value of *n* will result in this method never returning). """ for led in self.leds: if isinstance(led, LED): if fade_in_time: raise ValueError('fade_in_time must be 0 with non-PWM LEDs') if fade_out_time: raise ValueError('fade_out_time must be 0 with non-PWM LEDs') self._stop_blink() self._blink_thread = GPIOThread( target=self._blink_device, args=(on_time, off_time, fade_in_time, fade_out_time, n) ) self._blink_thread.start() if not background: self._blink_thread.join() self._blink_thread = None def _stop_blink(self, led=None): if led is None: if self._blink_thread: self._blink_thread.stop() self._blink_thread = None else: with self._blink_lock: self._blink_leds.remove(led) def pulse(self, fade_in_time=1, fade_out_time=1, n=None, background=True): """ Make the device fade in and out repeatedly. :param float fade_in_time: Number of seconds to spend fading in. Defaults to 1. :param float fade_out_time: Number of seconds to spend fading out. Defaults to 1. :param int n: Number of times to blink; ``None`` (the default) means forever. :param bool background: If ``True`` (the default), start a background thread to continue blinking and return immediately. If ``False``, only return when the blink is finished (warning: the default value of *n* will result in this method never returning). """ on_time = off_time = 0 self.blink( on_time, off_time, fade_in_time, fade_out_time, n, background ) def _blink_device(self, on_time, off_time, fade_in_time, fade_out_time, n, fps=25): sequence = [] if fade_in_time > 0: sequence += [ (i * (1 / fps) / fade_in_time, 1 / fps) for i in range(int(fps * fade_in_time)) ] sequence.append((1, on_time)) if fade_out_time > 0: sequence += [ (1 - (i * (1 / fps) / fade_out_time), 1 / fps) for i in range(int(fps * fade_out_time)) ] sequence.append((0, off_time)) sequence = ( cycle(sequence) if n is None else chain.from_iterable(repeat(sequence, n)) ) with self._blink_lock: self._blink_leds = list(self.leds) for led in self._blink_leds: if led._controller not in (None, self): led._controller._stop_blink(led) led._controller = self for value, delay in sequence: with self._blink_lock: if not self._blink_leds: break for led in self._blink_leds: led._write(value) if self._blink_thread.stopping.wait(delay): break class LEDBarGraph(LEDCollection): """ Extends :class:`LEDCollection` to control a line of LEDs representing a bar graph. Positive values (0 to 1) light the LEDs from first to last. Negative values (-1 to 0) light the LEDs from last to first. The following example demonstrates turning on the first two and last two LEDs in a board containing five LEDs attached to GPIOs 2 through 6:: from gpiozero import LEDBarGraph from time import sleep graph = LEDBarGraph(2, 3, 4, 5, 6) graph.value = 2/5 # Light the first two LEDs only sleep(1) graph.value = -2/5 # Light the last two LEDs only sleep(1) graph.off() As with other output devices, :attr:`source` and :attr:`values` are supported:: from gpiozero import LEDBarGraph, MCP3008 from signal import pause graph = LEDBarGraph(2, 3, 4, 5, 6, pwm=True) pot = MCP3008(channel=0) graph.source = pot.values pause() :param int \*pins: Specify the GPIO pins that the LEDs of the bar graph are attached to. You can designate as many pins as necessary. :param bool pwm: If ``True``, construct :class:`PWMLED` instances for each pin. If ``False`` (the default), construct regular :class:`LED` instances. This parameter can only be specified as a keyword parameter. :param bool active_high: If ``True`` (the default), the :meth:`on` method will set all the associated pins to HIGH. If ``False``, the :meth:`on` method will set all pins to LOW (the :meth:`off` method always does the opposite). This parameter can only be specified as a keyword parameter. :param float initial_value: The initial :attr:`value` of the graph given as a float between -1 and +1. Defaults to ``0.0``. This parameter can only be specified as a keyword parameter. """ def __init__(self, *pins, **kwargs): # Don't allow graphs to contain collections for pin in pins: assert not isinstance(pin, LEDCollection) pwm = kwargs.pop('pwm', False) active_high = kwargs.pop('active_high', True) initial_value = kwargs.pop('initial_value', 0.0) if kwargs: raise TypeError('unexpected keyword argument: %s' % kwargs.popitem()[0]) super(LEDBarGraph, self).__init__(*pins, pwm=pwm, active_high=active_high) try: self.value = initial_value except: self.close() raise @property def value(self): """ The value of the LED bar graph. When no LEDs are lit, the value is 0. When all LEDs are lit, the value is 1. Values between 0 and 1 light LEDs linearly from first to last. Values between 0 and -1 light LEDs linearly from last to first. To light a particular number of LEDs, simply divide that number by the number of LEDs. For example, if your graph contains 3 LEDs, the following will light the first:: from gpiozero import LEDBarGraph graph = LEDBarGraph(12, 16, 19) graph.value = 1/3 .. note:: Setting value to -1 will light all LEDs. However, querying it subsequently will return 1 as both representations are the same in hardware. The readable range of :attr:`value` is effectively -1 < value <= 1. """ result = sum(led.value for led in self) if self[0].value < self[-1].value: result = -result return result / len(self) @value.setter def value(self, value): if not -1 <= value <= 1: raise OutputDeviceBadValue('LEDBarGraph value must be between -1 and 1') count = len(self) leds = self if value < 0: leds = reversed(leds) value = -value if isinstance(self[0], PWMLED): calc_value = lambda index: min(1, max(0, count * value - index)) else: calc_value = lambda index: value >= ((index + 1) / count) for index, led in enumerate(leds): led.value = calc_value(index) class LedBorg(RGBLED): """ Extends :class:`RGBLED` for the `PiBorg LedBorg`_: an add-on board containing a very bright RGB LED. The LedBorg pins are fixed and therefore there's no need to specify them when constructing this class. The following example turns the LedBorg purple:: from gpiozero import LedBorg led = LedBorg() led.color = (1, 0, 1) :param tuple initial_value: The initial color for the LedBorg. Defaults to black ``(0, 0, 0)``. :param bool pwm: If ``True`` (the default), construct :class:`PWMLED` instances for each component of the LedBorg. If ``False``, construct regular :class:`LED` instances, which prevents smooth color graduations. .. _PiBorg LedBorg: https://www.piborg.org/ledborg """ def __init__(self, initial_value=(0, 0, 0), pwm=True): super(LedBorg, self).__init__(red=17, green=27, blue=22, pwm=pwm, initial_value=initial_value) class PiLiter(LEDBoard): """ Extends :class:`LEDBoard` for the `Ciseco Pi-LITEr`_: a strip of 8 very bright LEDs. The Pi-LITEr pins are fixed and therefore there's no need to specify them when constructing this class. The following example turns on all the LEDs of the Pi-LITEr:: from gpiozero import PiLiter lite = PiLiter() lite.on() :param bool pwm: If ``True``, construct :class:`PWMLED` instances for each pin. If ``False`` (the default), construct regular :class:`LED` instances. :param bool initial_value: If ``False`` (the default), all LEDs will be off initially. If ``None``, each device will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the device will be switched on initially. .. _Ciseco Pi-LITEr: http://shop.ciseco.co.uk/pi-liter-8-led-strip-for-the-raspberry-pi/ """ def __init__(self, pwm=False, initial_value=False): super(PiLiter, self).__init__(4, 17, 27, 18, 22, 23, 24, 25, pwm=pwm, initial_value=initial_value) class PiLiterBarGraph(LEDBarGraph): """ Extends :class:`LEDBarGraph` to treat the `Ciseco Pi-LITEr`_ as an 8-segment bar graph. The Pi-LITEr pins are fixed and therefore there's no need to specify them when constructing this class. The following example sets the graph value to 0.5:: from gpiozero import PiLiterBarGraph graph = PiLiterBarGraph() graph.value = 0.5 :param bool pwm: If ``True``, construct :class:`PWMLED` instances for each pin. If ``False`` (the default), construct regular :class:`LED` instances. :param float initial_value: The initial :attr:`value` of the graph given as a float between -1 and +1. Defaults to ``0.0``. .. _Ciseco Pi-LITEr: http://shop.ciseco.co.uk/pi-liter-8-led-strip-for-the-raspberry-pi/ """ def __init__(self, pwm=False, initial_value=0.0): pins = (4, 17, 27, 18, 22, 23, 24, 25) super(PiLiterBarGraph, self).__init__(*pins, pwm=pwm, initial_value=initial_value) class TrafficLights(LEDBoard): """ Extends :class:`LEDBoard` for devices containing red, yellow, and green LEDs. The following example initializes a device connected to GPIO pins 2, 3, and 4, then lights the amber (yellow) LED attached to GPIO 3:: from gpiozero import TrafficLights traffic = TrafficLights(2, 3, 4) traffic.amber.on() :param int red: The GPIO pin that the red LED is attached to. :param int amber: The GPIO pin that the amber LED is attached to. :param int green: The GPIO pin that the green LED is attached to. :param bool pwm: If ``True``, construct :class:`PWMLED` instances to represent each LED. If ``False`` (the default), construct regular :class:`LED` instances. :param bool initial_value: If ``False`` (the default), all LEDs will be off initially. If ``None``, each device will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the device will be switched on initially. :param int yellow: The GPIO pin that the yellow LED is attached to. This is merely an alias for the ``amber`` parameter - you can't specify both ``amber`` and ``yellow``. """ def __init__(self, red=None, amber=None, green=None, pwm=False, initial_value=False, yellow=None): if amber is not None and yellow is not None: raise OutputDeviceBadValue( 'Only one of amber or yellow can be specified' ) devices = OrderedDict((('red', red), )) self._display_yellow = amber is None and yellow is not None if self._display_yellow: devices['yellow'] = yellow else: devices['amber'] = amber devices['green'] = green if not all(p is not None for p in devices.values()): raise GPIOPinMissing( ', '.join(devices.keys())+' pins must be provided' ) super(TrafficLights, self).__init__( pwm=pwm, initial_value=initial_value, _order=devices.keys(), **devices) def __getattr__(self, name): if name == 'amber' and self._display_yellow: name = 'yellow' elif name == 'yellow' and not self._display_yellow: name = 'amber' return super(TrafficLights, self).__getattr__(name) def __setattr__(self, name, value): if name == 'amber' and self._display_yellow: name = 'yellow' elif name == 'yellow' and not self._display_yellow: name = 'amber' return super(TrafficLights, self).__setattr__(name, value) class PiTraffic(TrafficLights): """ Extends :class:`TrafficLights` for the `Low Voltage Labs PI-TRAFFIC`_: vertical traffic lights board when attached to GPIO pins 9, 10, and 11. There's no need to specify the pins if the PI-TRAFFIC is connected to the default pins (9, 10, 11). The following example turns on the amber LED on the PI-TRAFFIC:: from gpiozero import PiTraffic traffic = PiTraffic() traffic.amber.on() To use the PI-TRAFFIC board when attached to a non-standard set of pins, simply use the parent class, :class:`TrafficLights`. :param bool pwm: If ``True``, construct :class:`PWMLED` instances to represent each LED. If ``False`` (the default), construct regular :class:`LED` instances. :param bool initial_value: If ``False`` (the default), all LEDs will be off initially. If ``None``, each device will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the device will be switched on initially. .. _Low Voltage Labs PI-TRAFFIC: http://lowvoltagelabs.com/products/pi-traffic/ """ def __init__(self, pwm=False, initial_value=False): super(PiTraffic, self).__init__(9, 10, 11, pwm=pwm, initial_value=initial_value) class SnowPi(LEDBoard): """ Extends :class:`LEDBoard` for the `Ryanteck SnowPi`_ board. The SnowPi pins are fixed and therefore there's no need to specify them when constructing this class. The following example turns on the eyes, sets the nose pulsing, and the arms blinking:: from gpiozero import SnowPi snowman = SnowPi(pwm=True) snowman.eyes.on() snowman.nose.pulse() snowman.arms.blink() :param bool pwm: If ``True``, construct :class:`PWMLED` instances to represent each LED. If ``False`` (the default), construct regular :class:`LED` instances. :param bool initial_value: If ``False`` (the default), all LEDs will be off initially. If ``None``, each device will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the device will be switched on initially. .. _Ryanteck SnowPi: https://ryanteck.uk/raspberry-pi/114-snowpi-the-gpio-snowman-for-raspberry-pi-0635648608303.html """ def __init__(self, pwm=False, initial_value=False): super(SnowPi, self).__init__( arms=LEDBoard( left=LEDBoard( top=17, middle=18, bottom=22, pwm=pwm, initial_value=initial_value, _order=('top', 'middle', 'bottom')), right=LEDBoard( top=7, middle=8, bottom=9, pwm=pwm, initial_value=initial_value, _order=('top', 'middle', 'bottom')), _order=('left', 'right') ), eyes=LEDBoard( left=23, right=24, pwm=pwm, initial_value=initial_value, _order=('left', 'right') ), nose=25, pwm=pwm, initial_value=initial_value, _order=('eyes', 'nose', 'arms') ) class TrafficLightsBuzzer(CompositeOutputDevice): """ Extends :class:`CompositeOutputDevice` and is a generic class for HATs with traffic lights, a button and a buzzer. :param TrafficLights lights: An instance of :class:`TrafficLights` representing the traffic lights of the HAT. :param Buzzer buzzer: An instance of :class:`Buzzer` representing the buzzer on the HAT. :param Button button: An instance of :class:`Button` representing the button on the HAT. """ def __init__(self, lights, buzzer, button): super(TrafficLightsBuzzer, self).__init__( lights=lights, buzzer=buzzer, button=button, _order=('lights', 'buzzer', 'button')) class FishDish(TrafficLightsBuzzer): """ Extends :class:`TrafficLightsBuzzer` for the `Pi Supply FishDish`_: traffic light LEDs, a button and a buzzer. The FishDish pins are fixed and therefore there's no need to specify them when constructing this class. The following example waits for the button to be pressed on the FishDish, then turns on all the LEDs:: from gpiozero import FishDish fish = FishDish() fish.button.wait_for_press() fish.lights.on() :param bool pwm: If ``True``, construct :class:`PWMLED` instances to represent each LED. If ``False`` (the default), construct regular :class:`LED` instances. .. _Pi Supply FishDish: https://www.pi-supply.com/product/fish-dish-raspberry-pi-led-buzzer-board/ """ def __init__(self, pwm=False): super(FishDish, self).__init__( TrafficLights(9, 22, 4, pwm=pwm), Buzzer(8), Button(7, pull_up=False), ) class TrafficHat(TrafficLightsBuzzer): """ Extends :class:`TrafficLightsBuzzer` for the `Ryanteck Traffic HAT`_: traffic light LEDs, a button and a buzzer. The Traffic HAT pins are fixed and therefore there's no need to specify them when constructing this class. The following example waits for the button to be pressed on the Traffic HAT, then turns on all the LEDs:: from gpiozero import TrafficHat hat = TrafficHat() hat.button.wait_for_press() hat.lights.on() :param bool pwm: If ``True``, construct :class:`PWMLED` instances to represent each LED. If ``False`` (the default), construct regular :class:`LED` instances. .. _Ryanteck Traffic HAT: https://ryanteck.uk/hats/1-traffichat-0635648607122.html """ def __init__(self, pwm=False): super(TrafficHat, self).__init__( TrafficLights(24, 23, 22, pwm=pwm), Buzzer(5), Button(25), ) class Robot(SourceMixin, CompositeDevice): """ Extends :class:`CompositeDevice` to represent a generic dual-motor robot. This class is constructed with two tuples representing the forward and backward pins of the left and right controllers respectively. For example, if the left motor's controller is connected to GPIOs 4 and 14, while the right motor's controller is connected to GPIOs 17 and 18 then the following example will drive the robot forward:: from gpiozero import Robot robot = Robot(left=(4, 14), right=(17, 18)) robot.forward() :param tuple left: A tuple of two GPIO pins representing the forward and backward inputs of the left motor's controller. :param tuple right: A tuple of two GPIO pins representing the forward and backward inputs of the right motor's controller. """ def __init__(self, left=None, right=None): super(Robot, self).__init__( left_motor=Motor(*left), right_motor=Motor(*right), _order=('left_motor', 'right_motor')) @property def value(self): """ Represents the motion of the robot as a tuple of (left_motor_speed, right_motor_speed) with ``(-1, -1)`` representing full speed backwards, ``(1, 1)`` representing full speed forwards, and ``(0, 0)`` representing stopped. """ return super(Robot, self).value @value.setter def value(self, value): self.left_motor.value, self.right_motor.value = value def forward(self, speed=1): """ Drive the robot forward by running both motors forward. :param float speed: Speed at which to drive the motors, as a value between 0 (stopped) and 1 (full speed). The default is 1. """ self.left_motor.forward(speed) self.right_motor.forward(speed) def backward(self, speed=1): """ Drive the robot backward by running both motors backward. :param float speed: Speed at which to drive the motors, as a value between 0 (stopped) and 1 (full speed). The default is 1. """ self.left_motor.backward(speed) self.right_motor.backward(speed) def left(self, speed=1): """ Make the robot turn left by running the right motor forward and left motor backward. :param float speed: Speed at which to drive the motors, as a value between 0 (stopped) and 1 (full speed). The default is 1. """ self.right_motor.forward(speed) self.left_motor.backward(speed) def right(self, speed=1): """ Make the robot turn right by running the left motor forward and right motor backward. :param float speed: Speed at which to drive the motors, as a value between 0 (stopped) and 1 (full speed). The default is 1. """ self.left_motor.forward(speed) self.right_motor.backward(speed) def reverse(self): """ Reverse the robot's current motor directions. If the robot is currently running full speed forward, it will run full speed backward. If the 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_motor.reverse() self.right_motor.reverse() def stop(self): """ Stop the robot. """ self.left_motor.stop() self.right_motor.stop() class RyanteckRobot(Robot): """ Extends :class:`Robot` for the `Ryanteck MCB`_ robot. The Ryanteck MCB pins are fixed and therefore there's no need to specify them when constructing this class. The following example drives the robot forward:: from gpiozero import RyanteckRobot robot = RyanteckRobot() robot.forward() .. _Ryanteck MCB: https://ryanteck.uk/add-ons/6-ryanteck-rpi-motor-controller-board-0635648607160.html """ def __init__(self): super(RyanteckRobot, self).__init__((17, 18), (22, 23)) class CamJamKitRobot(Robot): """ Extends :class:`Robot` for the `CamJam #3 EduKit`_ robot controller. The CamJam robot controller pins are fixed and therefore there's no need to specify them when constructing this class. The following example drives the robot forward:: from gpiozero import CamJamKitRobot robot = CamJamKitRobot() robot.forward() .. _CamJam #3 EduKit: http://camjam.me/?page_id=1035 """ def __init__(self): super(CamJamKitRobot, self).__init__((9, 10), (7, 8)) class _EnergenieMaster(SharedMixin, CompositeOutputDevice): def __init__(self): self._lock = Lock() super(_EnergenieMaster, self).__init__( *(OutputDevice(pin) for pin in (17, 22, 23, 27)), mode=OutputDevice(24), enable=OutputDevice(25), _order=('mode', 'enable')) def close(self): if self._lock: with self._lock: super(_EnergenieMaster, self).close() self._lock = None @classmethod def _shared_key(cls): # There's only one Energenie master return None def transmit(self, socket, enable): with self._lock: try: code = (8 * bool(enable)) + (8 - socket) for bit in self[:4]: bit.value = (code & 1) code >>= 1 sleep(0.1) self.enable.on() sleep(0.25) finally: self.enable.off() class Energenie(SourceMixin, Device): """ Extends :class:`Device` to represent an `Energenie socket`_ controller. This class is constructed with a socket number and an optional initial state (defaults to ``False``, meaning off). Instances of this class can be used to switch peripherals on and off. For example:: from gpiozero import Energenie lamp = Energenie(1) lamp.on() :param int socket: Which socket this instance should control. This is an integer number between 1 and 4. :param bool initial_value: The initial state of the socket. As Energenie sockets provide no means of reading their state, you must provide an initial state for the socket, which will be set upon construction. This defaults to ``False`` which will switch the socket off. .. _Energenie socket: https://energenie4u.co.uk/index.php/catalogue/product/ENER002-2PI """ def __init__(self, socket=None, initial_value=False): if socket is None: raise EnergenieSocketMissing('socket number must be provided') if not (1 <= socket <= 4): raise EnergenieBadSocket('socket number must be between 1 and 4') self._value = None super(Energenie, self).__init__() self._socket = socket self._master = _EnergenieMaster() if initial_value: self.on() else: self.off() def close(self): if self._master: m = self._master self._master = None m.close() @property def closed(self): return self._master is None def __repr__(self): try: self._check_open() return "" % self._socket except DeviceClosed: return "" @property def value(self): return self._value @value.setter def value(self, value): value = bool(value) self._master.transmit(self._socket, value) self._value = value def on(self): self.value = True def off(self): self.value = False gpiozero-1.3.1.post1/gpiozero/tools.py0000644000175000017500000004227413046621340017331 0ustar benben00000000000000# vim: set fileencoding=utf-8: from __future__ import ( unicode_literals, print_function, absolute_import, division, ) str = type('') from random import random from time import sleep try: from itertools import izip as zip except ImportError: pass from itertools import cycle from math import sin, cos, pi try: from statistics import mean except ImportError: from .compat import mean def negated(values): """ Returns the negation of the supplied values (``True`` becomes ``False``, and ``False`` becomes ``True``). For example:: from gpiozero import Button, LED from gpiozero.tools import negated from signal import pause led = LED(4) btn = Button(17) led.source = negated(btn.values) pause() """ for v in values: yield not v def inverted(values, input_min=0, input_max=1): """ Returns the inversion of the supplied values (*input_min* becomes *input_max*, *input_max* becomes *input_min*, `input_min + 0.1` becomes `input_max - 0.1`, etc.). All items in *values* are assumed to be between *input_min* and *input_max* (which default to 0 and 1 respectively), and the output will be in the same range. For example:: from gpiozero import MCP3008, PWMLED from gpiozero.tools import inverted from signal import pause led = PWMLED(4) pot = MCP3008(channel=0) led.source = inverted(pot.values) pause() """ if input_min >= input_max: raise ValueError('input_min must be smaller than input_max') for v in values: yield input_min + input_max - v def scaled(values, output_min, output_max, input_min=0, input_max=1): """ Returns *values* scaled from *output_min* to *output_max*, assuming that all items in *values* lie between *input_min* and *input_max* (which default to 0 and 1 respectively). For example, to control the direction of a motor (which is represented as a value between -1 and 1) using a potentiometer (which typically provides values between 0 and 1):: from gpiozero import Motor, MCP3008 from gpiozero.tools import scaled from signal import pause motor = Motor(20, 21) pot = MCP3008(channel=0) motor.source = scaled(pot.values, -1, 1) pause() .. warning:: If *values* contains elements that lie outside *input_min* to *input_max* (inclusive) then the function will not produce values that lie within *output_min* to *output_max* (inclusive). """ if input_min >= input_max: raise ValueError('input_min must be smaller than input_max') input_size = input_max - input_min output_size = output_max - output_min for v in values: yield (((v - input_min) / input_size) * output_size) + output_min def clamped(values, output_min=0, output_max=1): """ Returns *values* clamped from *output_min* to *output_max*, i.e. any items less than *output_min* will be returned as *output_min* and any items larger than *output_max* will be returned as *output_max* (these default to 0 and 1 respectively). For example:: from gpiozero import PWMLED, MCP3008 from gpiozero.tools import clamped from signal import pause led = PWMLED(4) pot = MCP3008(channel=0) led.source = clamped(pot.values, 0.5, 1.0) pause() """ if output_min >= output_max: raise ValueError('output_min must be smaller than output_max') for v in values: yield min(max(v, output_min), output_max) def absoluted(values): """ Returns *values* with all negative elements negated (so that they're positive). For example:: from gpiozero import PWMLED, Motor, MCP3008 from gpiozero.tools import absoluted, scaled from signal import pause led = PWMLED(4) motor = Motor(22, 27) pot = MCP3008(channel=0) motor.source = scaled(pot.values, -1, 1) led.source = absoluted(motor.values) pause() """ for v in values: yield abs(v) def quantized(values, steps, input_min=0, input_max=1): """ Returns *values* quantized to *steps* increments. All items in *values* are assumed to be between *input_min* and *input_max* (which default to 0 and 1 respectively), and the output will be in the same range. For example, to quantize values between 0 and 1 to 5 "steps" (0.0, 0.25, 0.5, 0.75, 1.0):: from gpiozero import PWMLED, MCP3008 from gpiozero.tools import quantized from signal import pause led = PWMLED(4) pot = MCP3008(channel=0) led.source = quantized(pot.values, 4) pause() """ if steps < 1: raise ValueError("steps must be 1 or larger") if input_min >= input_max: raise ValueError('input_min must be smaller than input_max') input_size = input_max - input_min for v in scaled(values, 0, 1, input_min, input_max): yield ((int(v * steps) / steps) * input_size) + input_min def booleanized(values, min_value, max_value, hysteresis=0): """ Returns True for each item in *values* between *min_value* and *max_value*, and False otherwise. *hysteresis* can optionally be used to add `hysteresis`_ which prevents the output value rapidly flipping when the input value is fluctuating near the *min_value* or *max_value* thresholds. For example, to light an LED only when a potentiometer is between 1/4 and 3/4 of its full range:: from gpiozero import LED, MCP3008 from gpiozero.tools import booleanized from signal import pause led = LED(4) pot = MCP3008(channel=0) led.source = booleanized(pot.values, 0.25, 0.75) pause() .. _hysteresis: https://en.wikipedia.org/wiki/Hysteresis """ if min_value >= max_value: raise ValueError('min_value must be smaller than max_value') min_value = float(min_value) max_value = float(max_value) if hysteresis < 0: raise ValueError("hysteresis must be 0 or larger") else: hysteresis = float(hysteresis) if (max_value - min_value) <= hysteresis: raise ValueError('The gap between min_value and max_value must be larger than hysteresis') last_state = None for v in values: if v < min_value: new_state = 'below' elif v > max_value: new_state = 'above' else: new_state = 'in' switch = False if last_state == None or not hysteresis: switch = True elif new_state == last_state: pass else: # new_state != last_state if last_state == 'below' and new_state == 'in': switch = v >= min_value + hysteresis elif last_state == 'in' and new_state == 'below': switch = v < min_value - hysteresis elif last_state == 'in' and new_state == 'above': switch = v > max_value + hysteresis elif last_state == 'above' and new_state == 'in': switch = v <= max_value - hysteresis else: # above->below or below->above switch = True if switch: last_state = new_state yield last_state == 'in' def all_values(*values): """ Returns the `logical conjunction`_ of all supplied values (the result is only ``True`` if and only if all input values are simultaneously ``True``). One or more *values* can be specified. For example, to light an :class:`LED` only when *both* buttons are pressed:: from gpiozero import LED, Button from gpiozero.tools import all_values from signal import pause led = LED(4) btn1 = Button(20) btn2 = Button(21) led.source = all_values(btn1.values, btn2.values) pause() .. _logical conjunction: https://en.wikipedia.org/wiki/Logical_conjunction """ for v in zip(*values): yield all(v) def any_values(*values): """ Returns the `logical disjunction`_ of all supplied values (the result is ``True`` if any of the input values are currently ``True``). One or more *values* can be specified. For example, to light an :class:`LED` when *any* button is pressed:: from gpiozero import LED, Button from gpiozero.tools import any_values from signal import pause led = LED(4) btn1 = Button(20) btn2 = Button(21) led.source = any_values(btn1.values, btn2.values) pause() .. _logical disjunction: https://en.wikipedia.org/wiki/Logical_disjunction """ for v in zip(*values): yield any(v) def averaged(*values): """ Returns the mean of all supplied values. One or more *values* can be specified. For example, to light a :class:`PWMLED` as the average of several potentiometers connected to an :class:`MCP3008` ADC:: from gpiozero import MCP3008, PWMLED from gpiozero.tools import averaged from signal import pause pot1 = MCP3008(channel=0) pot2 = MCP3008(channel=1) pot3 = MCP3008(channel=2) led = PWMLED(4) led.source = averaged(pot1.values, pot2.values, pot3.values) pause() """ for v in zip(*values): yield mean(v) def summed(*values): """ Returns the sum of all supplied values. One or more *values* can be specified. For example, to light a :class:`PWMLED` as the (scaled) sum of several potentiometers connected to an :class:`MCP3008` ADC:: from gpiozero import MCP3008, PWMLED from gpiozero.tools import summed, scaled from signal import pause pot1 = MCP3008(channel=0) pot2 = MCP3008(channel=1) pot3 = MCP3008(channel=2) led = PWMLED(4) led.source = scaled(summed(pot1.values, pot2.values, pot3.values), 0, 1, 0, 3) pause() """ for v in zip(*values): yield sum(v) def multiplied(*values): """ Returns the product of all supplied values. One or more *values* can be specified. For example, to light a :class:`PWMLED` as the product (i.e. multiplication) of several potentiometers connected to an :class:`MCP3008` ADC:: from gpiozero import MCP3008, PWMLED from gpiozero.tools import multiplied from signal import pause pot1 = MCP3008(channel=0) pot2 = MCP3008(channel=1) pot3 = MCP3008(channel=2) led = PWMLED(4) led.source = multiplied(pot1.values, pot2.values, pot3.values) pause() """ def _product(it): p = 1 for n in it: p *= n return p for v in zip(*values): yield _product(v) def queued(values, qsize): """ Queues up readings from *values* (the number of readings queued is determined by *qsize*) and begins yielding values only when the queue is full. For example, to "cascade" values along a sequence of LEDs:: from gpiozero import LEDBoard, Button from gpiozero.tools import queued from signal import pause leds = LEDBoard(5, 6, 13, 19, 26) btn = Button(17) for i in range(4): leds[i].source = queued(leds[i + 1].values, 5) leds[i].source_delay = 0.01 leds[4].source = btn.values pause() """ if qsize < 1: raise ValueError("qsize must be 1 or larger") q = [] it = iter(values) for i in range(qsize): q.append(next(it)) for i in cycle(range(qsize)): yield q[i] try: q[i] = next(it) except StopIteration: break def smoothed(values, qsize, average=mean): """ Queues up readings from *values* (the number of readings queued is determined by *qsize*) and begins yielding the *average* of the last *qsize* values when the queue is full. The larger the *qsize*, the more the values are smoothed. For example, to smooth the analog values read from an ADC:: from gpiozero import MCP3008 from gpiozero.tools import smoothed adc = MCP3008(channel=0) for value in smoothed(adc.values, 5): print(value) """ if qsize < 1: raise ValueError("qsize must be 1 or larger") q = [] it = iter(values) for i in range(qsize): q.append(next(it)) for i in cycle(range(qsize)): yield average(q) try: q[i] = next(it) except StopIteration: break def pre_delayed(values, delay): """ Waits for *delay* seconds before returning each item from *values*. """ if delay < 0: raise ValueError("delay must be 0 or larger") for v in values: sleep(delay) yield v def post_delayed(values, delay): """ Waits for *delay* seconds after returning each item from *values*. """ if delay < 0: raise ValueError("delay must be 0 or larger") for v in values: yield v sleep(delay) def pre_periodic_filtered(values, block, repeat_after): """ Blocks the first *block* items from *values*, repeating the block after every *repeat_after* items, if *repeat_after* is non-zero. For example, to discard the first 50 values read from an ADC:: from gpiozero import MCP3008 from gpiozero.tools import pre_periodic_filtered adc = MCP3008(channel=0) for value in pre_periodic_filtered(adc.values, 50, 0): print(value) Or to only display every even item read from an ADC:: from gpiozero import MCP3008 from gpiozero.tools import pre_periodic_filtered adc = MCP3008(channel=0) for value in pre_periodic_filtered(adc.values, 1, 1): print(value) """ if block < 1: raise ValueError("block must be 1 or larger") if repeat_after < 0: raise ValueError("repeat_after must be 0 or larger") it = iter(values) if repeat_after == 0: for _ in range(block): next(it) while True: yield next(it) else: while True: for _ in range(block): next(it) for _ in range(repeat_after): yield next(it) def post_periodic_filtered(values, repeat_after, block): """ After every *repeat_after* items, blocks the next *block* items from *values*. Note that unlike :func:`pre_periodic_filtered`, *repeat_after* can't be 0. For example, to block every tenth item read from an ADC:: from gpiozero import MCP3008 from gpiozero.tools import post_periodic_filtered adc = MCP3008(channel=0) for value in post_periodic_filtered(adc.values, 9, 1): print(value) """ if repeat_after < 1: raise ValueError("repeat_after must be 1 or larger") if block < 1: raise ValueError("block must be 1 or larger") it = iter(values) while True: for _ in range(repeat_after): yield next(it) for _ in range(block): next(it) def random_values(): """ Provides an infinite source of random values between 0 and 1. For example, to produce a "flickering candle" effect with an LED:: from gpiozero import PWMLED from gpiozero.tools import random_values from signal import pause led = PWMLED(4) led.source = random_values() pause() If you require a wider range than 0 to 1, see :func:`scaled`. """ while True: yield random() def sin_values(period=360): """ Provides an infinite source of values representing a sine wave (from -1 to +1) which repeats every *period* values. For example, to produce a "siren" effect with a couple of LEDs that repeats once a second:: from gpiozero import PWMLED from gpiozero.tools import sin_values, scaled, inverted from signal import pause red = PWMLED(2) blue = PWMLED(3) red.source_delay = 0.01 blue.source_delay = red.source_delay red.source = scaled(sin_values(100), 0, 1, -1, 1) blue.source = inverted(red.values) pause() If you require a different range than -1 to +1, see :func:`scaled`. """ angles = (2 * pi * i / period for i in range(period)) for a in cycle(angles): yield sin(a) def cos_values(period=360): """ Provides an infinite source of values representing a cosine wave (from -1 to +1) which repeats every *period* values. For example, to produce a "siren" effect with a couple of LEDs that repeats once a second:: from gpiozero import PWMLED from gpiozero.tools import cos_values, scaled, inverted from signal import pause red = PWMLED(2) blue = PWMLED(3) red.source_delay = 0.01 blue.source_delay = red.source_delay red.source = scaled(cos_values(100), 0, 1, -1, 1) blue.source = inverted(red.values) pause() If you require a different range than -1 to +1, see :func:`scaled`. """ angles = (2 * pi * i / period for i in range(period)) for a in cycle(angles): yield cos(a) gpiozero-1.3.1.post1/gpiozero/input_devices.py0000644000175000017500000006325113020121661021021 0ustar benben00000000000000# vim: set fileencoding=utf-8: from __future__ import ( unicode_literals, print_function, absolute_import, division, ) from time import sleep, time from threading import Event from .exc import InputDeviceError, DeviceClosed from .devices import GPIODevice from .mixins import GPIOQueue, EventsMixin, HoldMixin class InputDevice(GPIODevice): """ Represents a generic GPIO input device. This class extends :class:`GPIODevice` to add facilities common to GPIO input devices. The constructor adds the optional *pull_up* parameter to specify how the pin should be pulled by the internal resistors. The :attr:`~GPIODevice.is_active` property is adjusted accordingly so that ``True`` still means active regardless of the :attr:`pull_up` setting. :param int pin: The GPIO pin (in Broadcom numbering) that the device is connected to. If this is ``None`` a :exc:`GPIODeviceError` will be raised. :param bool pull_up: If ``True``, the pin will be pulled high with an internal resistor. If ``False`` (the default), the pin will be pulled low. """ def __init__(self, pin=None, pull_up=False): super(InputDevice, self).__init__(pin) try: self.pin.function = 'input' pull = 'up' if pull_up else 'down' if self.pin.pull != pull: self.pin.pull = pull except: self.close() raise self._active_state = False if pull_up else True self._inactive_state = True if pull_up else False @property def pull_up(self): """ If ``True``, the device uses a pull-up resistor to set the GPIO pin "high" by default. """ return self.pin.pull == 'up' def __repr__(self): try: return "" % ( self.__class__.__name__, self.pin, self.pull_up, self.is_active) except: return super(InputDevice, self).__repr__() class DigitalInputDevice(EventsMixin, InputDevice): """ Represents a generic input device with typical on/off behaviour. This class extends :class:`InputDevice` with machinery to fire the active and inactive events for devices that operate in a typical digital manner: straight forward on / off states with (reasonably) clean transitions between the two. :param float bounce_time: Specifies the length of time (in seconds) that the component will ignore changes in state after an initial change. This defaults to ``None`` which indicates that no bounce compensation will be performed. """ def __init__(self, pin=None, pull_up=False, bounce_time=None): super(DigitalInputDevice, self).__init__(pin, pull_up) try: self.pin.bounce = bounce_time self.pin.edges = 'both' self.pin.when_changed = self._fire_events # Call _fire_events once to set initial state of events self._fire_events() except: self.close() raise class SmoothedInputDevice(EventsMixin, InputDevice): """ Represents a generic input device which takes its value from the mean of a queue of historical values. This class extends :class:`InputDevice` with a queue which is filled by a background thread which continually polls the state of the underlying device. The mean of the values in the queue is compared to a threshold which is used to determine the state of the :attr:`is_active` property. .. note:: The background queue is not automatically started upon construction. This is to allow descendents to set up additional components before the queue starts reading values. Effectively this is an abstract base class. This class is intended for use with devices which either exhibit analog behaviour (such as the charging time of a capacitor with an LDR), or those which exhibit "twitchy" behaviour (such as certain motion sensors). :param float threshold: The value above which the device will be considered "on". :param int queue_len: The length of the internal queue which is filled by the background thread. :param float sample_wait: The length of time to wait between retrieving the state of the underlying device. Defaults to 0.0 indicating that values are retrieved as fast as possible. :param bool partial: If ``False`` (the default), attempts to read the state of the device (from the :attr:`is_active` property) will block until the queue has filled. If ``True``, a value will be returned immediately, but be aware that this value is likely to fluctuate excessively. """ def __init__( self, pin=None, pull_up=False, threshold=0.5, queue_len=5, sample_wait=0.0, partial=False): self._queue = None super(SmoothedInputDevice, self).__init__(pin, pull_up) try: self._queue = GPIOQueue(self, queue_len, sample_wait, partial) self.threshold = float(threshold) except: self.close() raise def close(self): try: self._queue.stop() except AttributeError: # If the queue isn't initialized (it's None) ignore the error # because we're trying to close anyway if self._queue is not None: raise except RuntimeError: # Cannot join thread before it starts; we don't care about this # because we're trying to close the thread anyway pass else: self._queue = None super(SmoothedInputDevice, self).close() def __repr__(self): try: self._check_open() except DeviceClosed: return super(SmoothedInputDevice, self).__repr__() else: if self.partial or self._queue.full.is_set(): return super(SmoothedInputDevice, self).__repr__() else: return "" % ( self.__class__.__name__, self.pin, self.pull_up) @property def queue_len(self): """ The length of the internal queue of values which is averaged to determine the overall state of the device. This defaults to 5. """ self._check_open() return self._queue.queue.maxlen @property def partial(self): """ If ``False`` (the default), attempts to read the :attr:`value` or :attr:`is_active` properties will block until the queue has filled. """ self._check_open() return self._queue.partial @property def value(self): """ Returns the mean of the values in the internal queue. This is compared to :attr:`threshold` to determine whether :attr:`is_active` is ``True``. """ self._check_open() return self._queue.value @property def threshold(self): """ If :attr:`value` exceeds this amount, then :attr:`is_active` will return ``True``. """ return self._threshold @threshold.setter def threshold(self, value): if not (0.0 < value < 1.0): raise InputDeviceError( 'threshold must be between zero and one exclusive' ) self._threshold = float(value) @property def is_active(self): """ Returns ``True`` if the device is currently active and ``False`` otherwise. """ return self.value > self.threshold class Button(HoldMixin, DigitalInputDevice): """ Extends :class:`DigitalInputDevice` and represents a simple push button or switch. Connect one side of the button to a ground pin, and the other to any GPIO pin. Alternatively, connect one side of the button to the 3V3 pin, and the other to any GPIO pin, then set *pull_up* to ``False`` in the :class:`Button` constructor. The following example will print a line of text when the button is pushed:: from gpiozero import Button button = Button(4) button.wait_for_press() print("The button was pressed!") :param int pin: The GPIO pin which the button is attached to. See :ref:`pin_numbering` for valid pin numbers. :param bool pull_up: If ``True`` (the default), the GPIO pin will be pulled high by default. In this case, connect the other side of the button to ground. If ``False``, the GPIO pin will be pulled low by default. In this case, connect the other side of the button to 3V3. :param float bounce_time: If ``None`` (the default), no software bounce compensation will be performed. Otherwise, this is the length of time (in seconds) that the component will ignore changes in state after an initial change. :param float hold_time: The length of time (in seconds) to wait after the button is pushed, until executing the :attr:`when_held` handler. Defaults to ``1``. :param bool hold_repeat: If ``True``, the :attr:`when_held` handler will be repeatedly executed as long as the device remains active, every *hold_time* seconds. If ``False`` (the default) the :attr:`when_held` handler will be only be executed once per hold. """ def __init__( self, pin=None, pull_up=True, bounce_time=None, hold_time=1, hold_repeat=False): super(Button, self).__init__(pin, pull_up, bounce_time) self.hold_time = hold_time self.hold_repeat = hold_repeat Button.is_pressed = Button.is_active Button.pressed_time = Button.active_time Button.when_pressed = Button.when_activated Button.when_released = Button.when_deactivated Button.wait_for_press = Button.wait_for_active Button.wait_for_release = Button.wait_for_inactive class LineSensor(SmoothedInputDevice): """ Extends :class:`SmoothedInputDevice` and represents a single pin line sensor like the TCRT5000 infra-red proximity sensor found in the `CamJam #3 EduKit`_. A typical line sensor has a small circuit board with three pins: VCC, GND, and OUT. VCC should be connected to a 3V3 pin, GND to one of the ground pins, and finally OUT to the GPIO specified as the value of the *pin* parameter in the constructor. The following code will print a line of text indicating when the sensor detects a line, or stops detecting a line:: from gpiozero import LineSensor from signal import pause sensor = LineSensor(4) sensor.when_line = lambda: print('Line detected') sensor.when_no_line = lambda: print('No line detected') pause() :param int pin: The GPIO pin which the sensor is attached to. See :ref:`pin_numbering` for valid pin numbers. :param int queue_len: The length of the queue used to store values read from the sensor. This defaults to 5. :param float sample_rate: The number of values to read from the device (and append to the internal queue) per second. Defaults to 100. :param float threshold: Defaults to 0.5. When the mean of all values in the internal queue rises above this value, the sensor will be considered "active" by the :attr:`~SmoothedInputDevice.is_active` property, and all appropriate events will be fired. :param bool partial: When ``False`` (the default), the object will not return a value for :attr:`~SmoothedInputDevice.is_active` until the internal queue has filled with values. Only set this to ``True`` if you require values immediately after object construction. .. _CamJam #3 EduKit: http://camjam.me/?page_id=1035 """ def __init__( self, pin=None, queue_len=5, sample_rate=100, threshold=0.5, partial=False): super(LineSensor, self).__init__( pin, pull_up=False, threshold=threshold, queue_len=queue_len, sample_wait=1 / sample_rate, partial=partial ) try: self._queue.start() except: self.close() raise @property def line_detected(self): return not self.is_active LineSensor.when_line = LineSensor.when_deactivated LineSensor.when_no_line = LineSensor.when_activated LineSensor.wait_for_line = LineSensor.wait_for_inactive LineSensor.wait_for_no_line = LineSensor.wait_for_active class MotionSensor(SmoothedInputDevice): """ Extends :class:`SmoothedInputDevice` and represents a passive infra-red (PIR) motion sensor like the sort found in the `CamJam #2 EduKit`_. .. _CamJam #2 EduKit: http://camjam.me/?page_id=623 A typical PIR device has a small circuit board with three pins: VCC, OUT, and GND. VCC should be connected to a 5V pin, GND to one of the ground pins, and finally OUT to the GPIO specified as the value of the *pin* parameter in the constructor. The following code will print a line of text when motion is detected:: from gpiozero import MotionSensor pir = MotionSensor(4) pir.wait_for_motion() print("Motion detected!") :param int pin: The GPIO pin which the sensor is attached to. See :ref:`pin_numbering` for valid pin numbers. :param int queue_len: The length of the queue used to store values read from the sensor. This defaults to 1 which effectively disables the queue. If your motion sensor is particularly "twitchy" you may wish to increase this value. :param float sample_rate: The number of values to read from the device (and append to the internal queue) per second. Defaults to 100. :param float threshold: Defaults to 0.5. When the mean of all values in the internal queue rises above this value, the sensor will be considered "active" by the :attr:`~SmoothedInputDevice.is_active` property, and all appropriate events will be fired. :param bool partial: When ``False`` (the default), the object will not return a value for :attr:`~SmoothedInputDevice.is_active` until the internal queue has filled with values. Only set this to ``True`` if you require values immediately after object construction. """ def __init__( self, pin=None, queue_len=1, sample_rate=10, threshold=0.5, partial=False): super(MotionSensor, self).__init__( pin, pull_up=False, threshold=threshold, queue_len=queue_len, sample_wait=1 / sample_rate, partial=partial ) try: self._queue.start() except: self.close() raise MotionSensor.motion_detected = MotionSensor.is_active MotionSensor.when_motion = MotionSensor.when_activated MotionSensor.when_no_motion = MotionSensor.when_deactivated MotionSensor.wait_for_motion = MotionSensor.wait_for_active MotionSensor.wait_for_no_motion = MotionSensor.wait_for_inactive class LightSensor(SmoothedInputDevice): """ Extends :class:`SmoothedInputDevice` and represents a light dependent resistor (LDR). Connect one leg of the LDR to the 3V3 pin; connect one leg of a 1µF capacitor to a ground pin; connect the other leg of the LDR and the other leg of the capacitor to the same GPIO pin. This class repeatedly discharges the capacitor, then times the duration it takes to charge (which will vary according to the light falling on the LDR). The following code will print a line of text when light is detected:: from gpiozero import LightSensor ldr = LightSensor(18) ldr.wait_for_light() print("Light detected!") :param int pin: The GPIO pin which the sensor is attached to. See :ref:`pin_numbering` for valid pin numbers. :param int queue_len: The length of the queue used to store values read from the circuit. This defaults to 5. :param float charge_time_limit: If the capacitor in the circuit takes longer than this length of time to charge, it is assumed to be dark. The default (0.01 seconds) is appropriate for a 1µF capacitor coupled with the LDR from the `CamJam #2 EduKit`_. You may need to adjust this value for different valued capacitors or LDRs. :param float threshold: Defaults to 0.1. When the mean of all values in the internal queue rises above this value, the area will be considered "light", and all appropriate events will be fired. :param bool partial: When ``False`` (the default), the object will not return a value for :attr:`~SmoothedInputDevice.is_active` until the internal queue has filled with values. Only set this to ``True`` if you require values immediately after object construction. .. _CamJam #2 EduKit: http://camjam.me/?page_id=623 """ def __init__( self, pin=None, queue_len=5, charge_time_limit=0.01, threshold=0.1, partial=False): super(LightSensor, self).__init__( pin, pull_up=False, threshold=threshold, queue_len=queue_len, sample_wait=0.0, partial=partial ) try: self._charge_time_limit = charge_time_limit self._charged = Event() self.pin.edges = 'rising' self.pin.bounce = None self.pin.when_changed = self._charged.set self._queue.start() except: self.close() raise @property def charge_time_limit(self): return self._charge_time_limit def _read(self): # Drain charge from the capacitor self.pin.function = 'output' self.pin.state = False sleep(0.1) # Time the charging of the capacitor start = time() self._charged.clear() self.pin.function = 'input' self._charged.wait(self.charge_time_limit) return ( 1.0 - min(self.charge_time_limit, time() - start) / self.charge_time_limit ) LightSensor.light_detected = LightSensor.is_active LightSensor.when_light = LightSensor.when_activated LightSensor.when_dark = LightSensor.when_deactivated LightSensor.wait_for_light = LightSensor.wait_for_active LightSensor.wait_for_dark = LightSensor.wait_for_inactive class DistanceSensor(SmoothedInputDevice): """ Extends :class:`SmoothedInputDevice` and represents an HC-SR04 ultrasonic distance sensor, as found in the `CamJam #3 EduKit`_. The distance sensor requires two GPIO pins: one for the *trigger* (marked TRIG on the sensor) and another for the *echo* (marked ECHO on the sensor). However, a voltage divider is required to ensure the 5V from the ECHO pin doesn't damage the Pi. Wire your sensor according to the following instructions: 1. Connect the GND pin of the sensor to a ground pin on the Pi. 2. Connect the TRIG pin of the sensor a GPIO pin. 3. Connect a 330Ω resistor from the ECHO pin of the sensor to a different GPIO pin. 4. Connect a 470Ω resistor from ground to the ECHO GPIO pin. This forms the required voltage divider. 5. Finally, connect the VCC pin of the sensor to a 5V pin on the Pi. The following code will periodically report the distance measured by the sensor in cm assuming the TRIG pin is connected to GPIO17, and the ECHO pin to GPIO18:: from gpiozero import DistanceSensor from time import sleep sensor = DistanceSensor(echo=18, trigger=17) while True: print('Distance: ', sensor.distance * 100) sleep(1) :param int echo: The GPIO pin which the ECHO pin is attached to. See :ref:`pin_numbering` for valid pin numbers. :param int trigger: The GPIO pin which the TRIG pin is attached to. See :ref:`pin_numbering` for valid pin numbers. :param int queue_len: The length of the queue used to store values read from the sensor. This defaults to 30. :param float max_distance: The :attr:`value` attribute reports a normalized value between 0 (too close to measure) and 1 (maximum distance). This parameter specifies the maximum distance expected in meters. This defaults to 1. :param float threshold_distance: Defaults to 0.3. This is the distance (in meters) that will trigger the ``in_range`` and ``out_of_range`` events when crossed. :param bool partial: When ``False`` (the default), the object will not return a value for :attr:`~SmoothedInputDevice.is_active` until the internal queue has filled with values. Only set this to ``True`` if you require values immediately after object construction. .. _CamJam #3 EduKit: http://camjam.me/?page_id=1035 """ def __init__( self, echo=None, trigger=None, queue_len=30, max_distance=1, threshold_distance=0.3, partial=False): if max_distance <= 0: raise ValueError('invalid maximum distance (must be positive)') self._trigger = None super(DistanceSensor, self).__init__( echo, pull_up=False, threshold=threshold_distance / max_distance, queue_len=queue_len, sample_wait=0.0, partial=partial ) try: self.speed_of_sound = 343.26 # m/s self._max_distance = max_distance self._trigger = GPIODevice(trigger) self._echo = Event() self._echo_rise = None self._echo_fall = None self._trigger.pin.function = 'output' self._trigger.pin.state = False self.pin.edges = 'both' self.pin.bounce = None def callback(): if self._echo_rise is None: self._echo_rise = time() else: self._echo_fall = time() self._echo.set() self.pin.when_changed = callback self._queue.start() except: self.close() raise def close(self): try: self._trigger.close() except AttributeError: if self._trigger is not None: raise else: self._trigger = None super(DistanceSensor, self).close() @property def max_distance(self): """ The maximum distance that the sensor will measure in meters. This value is specified in the constructor and is used to provide the scaling for the :attr:`value` attribute. When :attr:`distance` is equal to :attr:`max_distance`, :attr:`value` will be 1. """ return self._max_distance @max_distance.setter def max_distance(self, value): if value <= 0: raise ValueError('invalid maximum distance (must be positive)') t = self.threshold_distance self._max_distance = value self.threshold_distance = t @property def threshold_distance(self): """ The distance, measured in meters, that will trigger the :attr:`when_in_range` and :attr:`when_out_of_range` events when crossed. This is simply a meter-scaled variant of the usual :attr:`threshold` attribute. """ return self.threshold * self.max_distance @threshold_distance.setter def threshold_distance(self, value): self.threshold = value / self.max_distance @property def distance(self): """ Returns the current distance measured by the sensor in meters. Note that this property will have a value between 0 and :attr:`max_distance`. """ return self.value * self._max_distance @property def trigger(self): """ Returns the :class:`Pin` that the sensor's trigger is connected to. """ return self._trigger.pin @property def echo(self): """ Returns the :class:`Pin` that the sensor's echo is connected to. This is simply an alias for the usual :attr:`pin` attribute. """ return self.pin def _read(self): # Make sure the echo pin is low then ensure the echo event is clear while self.pin.state: sleep(0.00001) self._echo.clear() # Fire the trigger self._trigger.pin.state = True sleep(0.00001) self._trigger.pin.state = False # Wait up to 1 second for the echo pin to rise if self._echo.wait(1): self._echo.clear() # Wait up to 40ms for the echo pin to fall (35ms is maximum pulse # time so any longer means something's gone wrong). Calculate # distance as time for echo multiplied by speed of sound divided by # two to compensate for travel to and from the reflector if self._echo.wait(0.04) and self._echo_fall is not None and self._echo_rise is not None: distance = (self._echo_fall - self._echo_rise) * self.speed_of_sound / 2.0 self._echo_fall = None self._echo_rise = None return min(1.0, distance / self._max_distance) else: # If we only saw one edge it means we missed the echo because # it was too fast; report minimum distance return 0.0 else: # The echo pin never rose or fell; something's gone horribly # wrong (XXX raise a warning?) return 1.0 @property def in_range(self): return not self.is_active DistanceSensor.when_out_of_range = DistanceSensor.when_activated DistanceSensor.when_in_range = DistanceSensor.when_deactivated DistanceSensor.wait_for_out_of_range = DistanceSensor.wait_for_active DistanceSensor.wait_for_in_range = DistanceSensor.wait_for_inactive gpiozero-1.3.1.post1/gpiozero/devices.py0000644000175000017500000003773613046621340017622 0ustar benben00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) nstr = str str = type('') import os import atexit import weakref from collections import namedtuple from itertools import chain from types import FunctionType from threading import RLock import pkg_resources from .threads import _threads_shutdown from .pins import _pins_shutdown from .mixins import ( ValuesMixin, SharedMixin, ) from .exc import ( BadPinFactory, DeviceClosed, CompositeDeviceBadName, CompositeDeviceBadOrder, CompositeDeviceBadDevice, GPIOPinMissing, GPIOPinInUse, GPIODeviceClosed, ) from .compat import frozendict def _default_pin_factory(name=os.getenv('GPIOZERO_PIN_FACTORY', None)): group = 'gpiozero_pin_factories' if name is None: # If no factory is explicitly specified, try various names in # "preferred" order. Note that in this case we only select from # gpiozero distribution so without explicitly specifying a name (via # the environment) it's impossible to auto-select a factory from # outside the base distribution # # We prefer RPi.GPIO here as it supports PWM, and all Pi revisions. If # no third-party libraries are available, however, we fall back to a # pure Python implementation which supports platforms like PyPy dist = pkg_resources.get_distribution('gpiozero') for name in ('RPiGPIOPin', 'RPIOPin', 'PiGPIOPin', 'NativePin'): try: return pkg_resources.load_entry_point(dist, group, name) except ImportError: pass raise BadPinFactory('Unable to locate any default pin factory!') else: for factory in pkg_resources.iter_entry_points(group, name): return factory.load() raise BadPinFactory('Unable to locate pin factory "%s"' % name) pin_factory = _default_pin_factory() _PINS = set() _PINS_LOCK = RLock() # Yes, this needs to be re-entrant def _shutdown(): _threads_shutdown() with _PINS_LOCK: while _PINS: _PINS.pop().close() # Any cleanup routines registered by pins libraries must be called *after* # cleanup of pin objects used by devices _pins_shutdown() atexit.register(_shutdown) class GPIOMeta(type): # NOTE Yes, this is a metaclass. Don't be scared - it's a simple one. def __new__(mcls, name, bases, cls_dict): # Construct the class as normal cls = super(GPIOMeta, mcls).__new__(mcls, name, bases, cls_dict) # If there's a method in the class which has no docstring, search # the base classes recursively for a docstring to copy for attr_name, attr in cls_dict.items(): if isinstance(attr, FunctionType) and not attr.__doc__: for base_cls in cls.__mro__: if hasattr(base_cls, attr_name): base_fn = getattr(base_cls, attr_name) if base_fn.__doc__: attr.__doc__ = base_fn.__doc__ break return cls def __call__(cls, *args, **kwargs): # Make sure cls has GPIOBase somewhere in its ancestry (otherwise # setting __attrs__ below will be rather pointless) assert issubclass(cls, GPIOBase) if issubclass(cls, SharedMixin): # If SharedMixin appears in the class' ancestry, convert the # constructor arguments to a key and check whether an instance # already exists. Only construct the instance if the key's new. key = cls._shared_key(*args, **kwargs) try: self = cls._INSTANCES[key] self._refs += 1 except (KeyError, ReferenceError) as e: self = super(GPIOMeta, cls).__call__(*args, **kwargs) self._refs = 1 # Replace the close method with one that merely decrements # the refs counter and calls the original close method when # it reaches zero old_close = self.close def close(): self._refs = max(0, self._refs - 1) if not self._refs: try: old_close() finally: try: del cls._INSTANCES[key] except KeyError: # If the _refs go negative (too many closes) # just ignore the resulting KeyError here - # it's already gone pass self.close = close cls._INSTANCES[key] = weakref.proxy(self) else: # Construct the instance as normal self = super(GPIOMeta, cls).__call__(*args, **kwargs) # At this point __new__ and __init__ have all been run. We now fix the # set of attributes on the class by dir'ing the instance and creating a # frozenset of the result called __attrs__ (which is queried by # GPIOBase.__setattr__). An exception is made for SharedMixin devices # which can be constructed multiple times, returning the same instance if not issubclass(cls, SharedMixin) or self._refs == 1: self.__attrs__ = frozenset(dir(self)) return self # Cross-version compatible method of using a metaclass class GPIOBase(GPIOMeta(nstr('GPIOBase'), (), {})): def __setattr__(self, name, value): # This overridden __setattr__ simply ensures that additional attributes # cannot be set on the class after construction (it manages this in # conjunction with the meta-class above). Traditionally, this is # managed with __slots__; however, this doesn't work with Python's # multiple inheritance system which we need to use in order to avoid # repeating the "source" and "values" property code in myriad places if hasattr(self, '__attrs__') and name not in self.__attrs__: raise AttributeError( "'%s' object has no attribute '%s'" % ( self.__class__.__name__, name)) return super(GPIOBase, self).__setattr__(name, value) def __del__(self): self.close() def close(self): """ Shut down the device and release all associated resources. This method can be called on an already closed device without raising an exception. This method is primarily intended for interactive use at the command line. It disables the device and releases its pin(s) for use by another device. You can attempt to do this simply by deleting an object, but unless you've cleaned up all references to the object this may not work (even if you've cleaned up all references, there's still no guarantee the garbage collector will actually delete the object at that point). By contrast, the close method provides a means of ensuring that the object is shut down. For example, if you have a breadboard with a buzzer connected to pin 16, but then wish to attach an LED instead: >>> from gpiozero import * >>> bz = Buzzer(16) >>> bz.on() >>> bz.off() >>> bz.close() >>> led = LED(16) >>> led.blink() :class:`Device` descendents can also be used as context managers using the :keyword:`with` statement. For example: >>> from gpiozero import * >>> with Buzzer(16) as bz: ... bz.on() ... >>> with LED(16) as led: ... led.on() ... """ # This is a placeholder which is simply here to ensure close() can be # safely called from subclasses without worrying whether super-class' # have it (which in turn is useful in conjunction with the SourceMixin # class). pass @property def closed(self): """ Returns ``True`` if the device is closed (see the :meth:`close` method). Once a device is closed you can no longer use any other methods or properties to control or query the device. """ raise NotImplementedError def _check_open(self): if self.closed: raise DeviceClosed( '%s is closed or uninitialized' % self.__class__.__name__) def __enter__(self): return self def __exit__(self, exc_type, exc_value, exc_tb): self.close() class Device(ValuesMixin, GPIOBase): """ Represents a single device of any type; GPIO-based, SPI-based, I2C-based, etc. This is the base class of the device hierarchy. It defines the basic services applicable to all devices (specifically thhe :attr:`is_active` property, the :attr:`value` property, and the :meth:`close` method). """ def __repr__(self): return "" % (self.__class__.__name__) @property def value(self): """ Returns a value representing the device's state. Frequently, this is a boolean value, or a number between 0 and 1 but some devices use larger ranges (e.g. -1 to +1) and composite devices usually use tuples to return the states of all their subordinate components. """ raise NotImplementedError @property def is_active(self): """ Returns ``True`` if the device is currently active and ``False`` otherwise. This property is usually derived from :attr:`value`. Unlike :attr:`value`, this is *always* a boolean. """ return bool(self.value) class CompositeDevice(Device): """ Extends :class:`Device`. Represents a device composed of multiple devices like simple HATs, H-bridge motor controllers, robots composed of multiple motors, etc. The constructor accepts subordinate devices as positional or keyword arguments. Positional arguments form unnamed devices accessed via the :attr:`all` attribute, while keyword arguments are added to the device as named (read-only) attributes. :param list _order: If specified, this is the order of named items specified by keyword arguments (to ensure that the :attr:`value` tuple is constructed with a specific order). All keyword arguments *must* be included in the collection. If omitted, an alphabetically sorted order will be selected for keyword arguments. """ def __init__(self, *args, **kwargs): self._all = () self._named = frozendict({}) self._namedtuple = None self._order = kwargs.pop('_order', None) if self._order is None: self._order = sorted(kwargs.keys()) else: for missing_name in set(kwargs.keys()) - set(self._order): raise CompositeDeviceBadOrder('%s missing from _order' % missing_name) self._order = tuple(self._order) super(CompositeDevice, self).__init__() for name in set(self._order) & set(dir(self)): raise CompositeDeviceBadName('%s is a reserved name' % name) self._all = args + tuple(kwargs[v] for v in self._order) for dev in self._all: if not isinstance(dev, Device): raise CompositeDeviceBadDevice("%s doesn't inherit from Device" % dev) self._named = frozendict(kwargs) self._namedtuple = namedtuple('%sValue' % self.__class__.__name__, chain( (str(i) for i in range(len(args))), self._order), rename=True) def __getattr__(self, name): # if _named doesn't exist yet, pretend it's an empty dict if name == '_named': return frozendict({}) try: return self._named[name] except KeyError: raise AttributeError("no such attribute %s" % name) def __setattr__(self, name, value): # make named components read-only properties if name in self._named: raise AttributeError("can't set attribute %s" % name) return super(CompositeDevice, self).__setattr__(name, value) def __repr__(self): try: self._check_open() return "" % ( self.__class__.__name__, len(self), ','.join(self._order), len(self) - len(self._named) ) except DeviceClosed: return "" def __len__(self): return len(self._all) def __getitem__(self, index): return self._all[index] def __iter__(self): return iter(self._all) @property def all(self): # XXX Deprecate this in favour of using the instance as a container return self._all def close(self): if self._all: for device in self._all: device.close() @property def closed(self): return all(device.closed for device in self) @property def namedtuple(self): return self._namedtuple @property def value(self): return self.namedtuple(*(device.value for device in self)) @property def is_active(self): return any(self.value) class GPIODevice(Device): """ Extends :class:`Device`. Represents a generic GPIO device and provides the services common to all single-pin GPIO devices (like ensuring two GPIO devices do no share a :attr:`pin`). :param int pin: The GPIO pin (in BCM numbering) that the device is connected to. If this is ``None``, :exc:`GPIOPinMissing` will be raised. If the pin is already in use by another device, :exc:`GPIOPinInUse` will be raised. """ def __init__(self, pin=None): super(GPIODevice, self).__init__() # self._pin must be set before any possible exceptions can be raised # because it's accessed in __del__. However, it mustn't be given the # value of pin until we've verified that it isn't already allocated self._pin = None if pin is None: raise GPIOPinMissing('No pin given') if isinstance(pin, int): pin = pin_factory(pin) with _PINS_LOCK: if pin in _PINS: raise GPIOPinInUse( 'pin %r is already in use by another gpiozero object' % pin ) _PINS.add(pin) self._pin = pin self._active_state = True self._inactive_state = False def _state_to_value(self, state): return bool(state == self._active_state) def _read(self): try: return self._state_to_value(self.pin.state) except (AttributeError, TypeError): self._check_open() raise def close(self): super(GPIODevice, self).close() with _PINS_LOCK: pin = self._pin self._pin = None if pin in _PINS: _PINS.remove(pin) pin.close() @property def closed(self): return self._pin is None def _check_open(self): try: super(GPIODevice, self)._check_open() except DeviceClosed as e: # For backwards compatibility; GPIODeviceClosed is deprecated raise GPIODeviceClosed(str(e)) @property def pin(self): """ The :class:`Pin` that the device is connected to. This will be ``None`` if the device has been closed (see the :meth:`close` method). When dealing with GPIO pins, query ``pin.number`` to discover the GPIO pin (in BCM numbering) that the device is connected to. """ return self._pin @property def value(self): return self._read() def __repr__(self): try: return "" % ( self.__class__.__name__, self.pin, self.is_active) except DeviceClosed: return "" % self.__class__.__name__ gpiozero-1.3.1.post1/gpiozero/exc.py0000644000175000017500000001141613046621340016742 0ustar benben00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) str = type('') class GPIOZeroError(Exception): "Base class for all exceptions in GPIO Zero" class DeviceClosed(GPIOZeroError): "Error raised when an operation is attempted on a closed device" class BadEventHandler(GPIOZeroError, ValueError): "Error raised when an event handler with an incompatible prototype is specified" class BadWaitTime(GPIOZeroError, ValueError): "Error raised when an invalid wait time is specified" class BadQueueLen(GPIOZeroError, ValueError): "Error raised when non-positive queue length is specified" class BadPinFactory(GPIOZeroError, ImportError): "Error raised when an unknown pin factory name is specified" class CompositeDeviceError(GPIOZeroError): "Base class for errors specific to the CompositeDevice hierarchy" class CompositeDeviceBadName(CompositeDeviceError, ValueError): "Error raised when a composite device is constructed with a reserved name" class CompositeDeviceBadOrder(CompositeDeviceError, ValueError): "Error raised when a composite device is constructed with an incomplete order" class CompositeDeviceBadDevice(CompositeDeviceError, ValueError): "Error raised when a composite device is constructed with an object that doesn't inherit from :class:`Device`" class EnergenieSocketMissing(CompositeDeviceError, ValueError): "Error raised when socket number is not specified" class EnergenieBadSocket(CompositeDeviceError, ValueError): "Error raised when an invalid socket number is passed to :class:`Energenie`" class SPIError(GPIOZeroError): "Base class for errors related to the SPI implementation" class SPIBadArgs(SPIError, ValueError): "Error raised when invalid arguments are given while constructing :class:`SPIDevice`" class GPIODeviceError(GPIOZeroError): "Base class for errors specific to the GPIODevice hierarchy" class GPIODeviceClosed(GPIODeviceError, DeviceClosed): "Deprecated descendent of :exc:`DeviceClosed`" class GPIOPinInUse(GPIODeviceError): "Error raised when attempting to use a pin already in use by another device" class GPIOPinMissing(GPIODeviceError, ValueError): "Error raised when a pin number is not specified" class InputDeviceError(GPIODeviceError): "Base class for errors specific to the InputDevice hierarchy" class OutputDeviceError(GPIODeviceError): "Base class for errors specified to the OutputDevice hierarchy" class OutputDeviceBadValue(OutputDeviceError, ValueError): "Error raised when ``value`` is set to an invalid value" class PinError(GPIOZeroError): "Base class for errors related to pin implementations" class PinInvalidFunction(PinError, ValueError): "Error raised when attempting to change the function of a pin to an invalid value" class PinInvalidState(PinError, ValueError): "Error raised when attempting to assign an invalid state to a pin" class PinInvalidPull(PinError, ValueError): "Error raised when attempting to assign an invalid pull-up to a pin" class PinInvalidEdges(PinError, ValueError): "Error raised when attempting to assign an invalid edge detection to a pin" class PinInvalidBounce(PinError, ValueError): "Error raised when attempting to assign an invalid bounce time to a pin" class PinSetInput(PinError, AttributeError): "Error raised when attempting to set a read-only pin" class PinFixedPull(PinError, AttributeError): "Error raised when attempting to set the pull of a pin with fixed pull-up" class PinEdgeDetectUnsupported(PinError, AttributeError): "Error raised when attempting to use edge detection on unsupported pins" class PinPWMError(PinError): "Base class for errors related to PWM implementations" class PinPWMUnsupported(PinPWMError, AttributeError): "Error raised when attempting to activate PWM on unsupported pins" class PinPWMFixedValue(PinPWMError, AttributeError): "Error raised when attempting to initialize PWM on an input pin" class PinUnknownPi(PinError, RuntimeError): "Error raised when gpiozero doesn't recognize a revision of the Pi" class PinMultiplePins(PinError, RuntimeError): "Error raised when multiple pins support the requested function" class PinNoPins(PinError, RuntimeError): "Error raised when no pins support the requested function" class GPIOZeroWarning(Warning): "Base class for all warnings in GPIO Zero" class SPIWarning(GPIOZeroWarning): "Base class for warnings related to the SPI implementation" class SPISoftwareFallback(SPIWarning): "Warning raised when falling back to the software implementation" class PinWarning(GPIOZeroWarning): "Base class for warnings related to pin implementations" class PinNonPhysical(PinWarning): "Warning raised when a non-physical pin is specified in a constructor" gpiozero-1.3.1.post1/gpiozero/threads.py0000644000175000017500000000152712714337163017627 0ustar benben00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) str = type('') from threading import Thread, Event _THREADS = set() def _threads_shutdown(): while _THREADS: for t in _THREADS.copy(): t.stop() class GPIOThread(Thread): def __init__(self, group=None, target=None, name=None, args=(), kwargs=None): if kwargs is None: kwargs = {} self.stopping = Event() super(GPIOThread, self).__init__(group, target, name, args, kwargs) self.daemon = True def start(self): self.stopping.clear() _THREADS.add(self) super(GPIOThread, self).start() def stop(self): self.stopping.set() self.join() def join(self): super(GPIOThread, self).join() _THREADS.discard(self) gpiozero-1.3.1.post1/gpiozero/mixins.py0000644000175000017500000004057713046621340017504 0ustar benben00000000000000from __future__ import ( unicode_literals, print_function, absolute_import, division, ) nstr = str str = type('') import inspect import weakref from functools import wraps from threading import Event from collections import deque from time import time try: from statistics import median except ImportError: from .compat import median from .threads import GPIOThread from .exc import ( BadEventHandler, BadWaitTime, BadQueueLen, DeviceClosed, ) class ValuesMixin(object): """ Adds a :attr:`values` property to the class which returns an infinite generator of readings from the :attr:`value` property. There is rarely a need to use this mixin directly as all base classes in GPIO Zero include it. .. note:: Use this mixin *first* in the parent class list. """ @property def values(self): """ An infinite iterator of values read from `value`. """ while True: try: yield self.value except DeviceClosed: break class SourceMixin(object): """ Adds a :attr:`source` property to the class which, given an iterable, sets :attr:`value` to each member of that iterable until it is exhausted. This mixin is generally included in novel output devices to allow their state to be driven from another device. .. note:: Use this mixin *first* in the parent class list. """ def __init__(self, *args, **kwargs): self._source = None self._source_thread = None self._source_delay = 0.01 super(SourceMixin, self).__init__(*args, **kwargs) def close(self): try: super(SourceMixin, self).close() except AttributeError: pass self.source = None def _copy_values(self, source): for v in source: self.value = v if self._source_thread.stopping.wait(self._source_delay): break @property def source_delay(self): """ The delay (measured in seconds) in the loop used to read values from :attr:`source`. Defaults to 0.01 seconds which is generally sufficient to keep CPU usage to a minimum while providing adequate responsiveness. """ return self._source_delay @source_delay.setter def source_delay(self, value): if value < 0: raise BadWaitTime('source_delay must be 0 or greater') self._source_delay = float(value) @property def source(self): """ The iterable to use as a source of values for :attr:`value`. """ return self._source @source.setter def source(self, value): if self._source_thread is not None: self._source_thread.stop() self._source_thread = None self._source = value if value is not None: self._source_thread = GPIOThread(target=self._copy_values, args=(value,)) self._source_thread.start() class SharedMixin(object): """ This mixin marks a class as "shared". In this case, the meta-class (GPIOMeta) will use :meth:`_shared_key` to convert the constructor arguments to an immutable key, and will check whether any existing instances match that key. If they do, they will be returned by the constructor instead of a new instance. An internal reference counter is used to determine how many times an instance has been "constructed" in this way. When :meth:`close` is called, an internal reference counter will be decremented and the instance will only close when it reaches zero. """ _INSTANCES = {} def __del__(self): self._refs = 0 super(SharedMixin, self).__del__() @classmethod def _shared_key(cls, *args, **kwargs): """ Given the constructor arguments, returns an immutable key representing the instance. The default simply assumes all positional arguments are immutable. """ return args class EventsMixin(object): """ Adds edge-detected :meth:`when_activated` and :meth:`when_deactivated` events to a device based on changes to the :attr:`~Device.is_active` property common to all devices. Also adds :meth:`wait_for_active` and :meth:`wait_for_inactive` methods for level-waiting. .. note:: Note that this mixin provides no means of actually firing its events; call :meth:`_fire_events` in sub-classes when device state changes to trigger the events. This should also be called once at the end of initialization to set initial states. """ def __init__(self, *args, **kwargs): super(EventsMixin, self).__init__(*args, **kwargs) self._active_event = Event() self._inactive_event = Event() self._when_activated = None self._when_deactivated = None self._last_state = None self._last_changed = time() def wait_for_active(self, timeout=None): """ Pause the script until the device is activated, or the timeout is reached. :param float timeout: Number of seconds to wait before proceeding. If this is ``None`` (the default), then wait indefinitely until the device is active. """ return self._active_event.wait(timeout) def wait_for_inactive(self, timeout=None): """ Pause the script until the device is deactivated, or the timeout is reached. :param float timeout: Number of seconds to wait before proceeding. If this is ``None`` (the default), then wait indefinitely until the device is inactive. """ return self._inactive_event.wait(timeout) @property def when_activated(self): """ The function to run when the device changes state from inactive to active. This can be set to a function which accepts no (mandatory) parameters, or a Python function which accepts a single mandatory parameter (with as many optional parameters as you like). If the function accepts a single mandatory parameter, the device that activated will be passed as that parameter. Set this property to ``None`` (the default) to disable the event. """ return self._when_activated @when_activated.setter def when_activated(self, value): self._when_activated = self._wrap_callback(value) @property def when_deactivated(self): """ The function to run when the device changes state from active to inactive. This can be set to a function which accepts no (mandatory) parameters, or a Python function which accepts a single mandatory parameter (with as many optional parameters as you like). If the function accepts a single mandatory parameter, the device that deactivated will be passed as that parameter. Set this property to ``None`` (the default) to disable the event. """ return self._when_deactivated @when_deactivated.setter def when_deactivated(self, value): self._when_deactivated = self._wrap_callback(value) @property def active_time(self): """ The length of time (in seconds) that the device has been active for. When the device is inactive, this is ``None``. """ if self._active_event.is_set(): return time() - self._last_changed else: return None @property def inactive_time(self): """ The length of time (in seconds) that the device has been inactive for. When the device is active, this is ``None``. """ if self._inactive_event.is_set(): return time() - self._last_changed else: return None def _wrap_callback(self, fn): if fn is None: return None elif not callable(fn): raise BadEventHandler('value must be None or a callable') elif inspect.isbuiltin(fn): # We can't introspect the prototype of builtins. In this case we # assume that the builtin has no (mandatory) parameters; this is # the most reasonable assumption on the basis that pre-existing # builtins have no knowledge of gpiozero, and the sole parameter # we would pass is a gpiozero object return fn else: # Try binding ourselves to the argspec of the provided callable. # If this works, assume the function is capable of accepting no # parameters try: inspect.getcallargs(fn) return fn except TypeError: try: # If the above fails, try binding with a single parameter # (ourselves). If this works, wrap the specified callback inspect.getcallargs(fn, self) @wraps(fn) def wrapper(): return fn(self) return wrapper except TypeError: raise BadEventHandler( 'value must be a callable which accepts up to one ' 'mandatory parameter') def _fire_activated(self): # These methods are largely here to be overridden by descendents if self.when_activated: self.when_activated() def _fire_deactivated(self): # These methods are largely here to be overridden by descendents if self.when_deactivated: self.when_deactivated() def _fire_events(self): old_state = self._last_state new_state = self._last_state = self.is_active if old_state is None: # Initial "indeterminate" state; set events but don't fire # callbacks as there's not necessarily an edge if new_state: self._active_event.set() else: self._inactive_event.set() elif old_state != new_state: self._last_changed = time() if new_state: self._inactive_event.clear() self._active_event.set() self._fire_activated() else: self._active_event.clear() self._inactive_event.set() self._fire_deactivated() class HoldMixin(EventsMixin): """ Extends :class:`EventsMixin` to add the :attr:`when_held` event and the machinery to fire that event repeatedly (when :attr:`hold_repeat` is ``True``) at internals defined by :attr:`hold_time`. """ def __init__(self, *args, **kwargs): self._hold_thread = None super(HoldMixin, self).__init__(*args, **kwargs) self._when_held = None self._held_from = None self._hold_time = 1 self._hold_repeat = False self._hold_thread = HoldThread(self) def close(self): if self._hold_thread: self._hold_thread.stop() self._hold_thread = None try: super(HoldMixin, self).close() except AttributeError: pass def _fire_activated(self): super(HoldMixin, self)._fire_activated() self._hold_thread.holding.set() def _fire_deactivated(self): self._held_from = None super(HoldMixin, self)._fire_deactivated() def _fire_held(self): if self.when_held: self.when_held() @property def when_held(self): """ The function to run when the device has remained active for :attr:`hold_time` seconds. This can be set to a function which accepts no (mandatory) parameters, or a Python function which accepts a single mandatory parameter (with as many optional parameters as you like). If the function accepts a single mandatory parameter, the device that activated will be passed as that parameter. Set this property to ``None`` (the default) to disable the event. """ return self._when_held @when_held.setter def when_held(self, value): self._when_held = self._wrap_callback(value) @property def hold_time(self): """ The length of time (in seconds) to wait after the device is activated, until executing the :attr:`when_held` handler. If :attr:`hold_repeat` is True, this is also the length of time between invocations of :attr:`when_held`. """ return self._hold_time @hold_time.setter def hold_time(self, value): if value < 0: raise BadWaitTime('hold_time must be 0 or greater') self._hold_time = float(value) @property def hold_repeat(self): """ If ``True``, :attr:`when_held` will be executed repeatedly with :attr:`hold_time` seconds between each invocation. """ return self._hold_repeat @hold_repeat.setter def hold_repeat(self, value): self._hold_repeat = bool(value) @property def is_held(self): """ When ``True``, the device has been active for at least :attr:`hold_time` seconds. """ return self._held_from is not None @property def held_time(self): """ The length of time (in seconds) that the device has been held for. This is counted from the first execution of the :attr:`when_held` event rather than when the device activated, in contrast to :attr:`~EventsMixin.active_time`. If the device is not currently held, this is ``None``. """ if self._held_from is not None: return time() - self._held_from else: return None class HoldThread(GPIOThread): """ Extends :class:`GPIOThread`. Provides a background thread that repeatedly fires the :attr:`HoldMixin.when_held` event as long as the owning device is active. """ def __init__(self, parent): super(HoldThread, self).__init__(target=self.held, args=(parent,)) self.holding = Event() self.start() def held(self, parent): while not self.stopping.is_set(): if self.holding.wait(0.1): self.holding.clear() while not ( self.stopping.is_set() or parent._inactive_event.wait(parent.hold_time) ): if parent._held_from is None: parent._held_from = time() parent._fire_held() if not parent.hold_repeat: break class GPIOQueue(GPIOThread): """ Extends :class:`GPIOThread`. Provides a background thread that monitors a device's values and provides a running *average* (defaults to median) of those values. If the *parent* device includes the :class:`EventsMixin` in its ancestry, the thread automatically calls :meth:`~EventsMixin._fire_events`. """ def __init__( self, parent, queue_len=5, sample_wait=0.0, partial=False, average=median): assert callable(average) super(GPIOQueue, self).__init__(target=self.fill) if queue_len < 1: raise BadQueueLen('queue_len must be at least one') if sample_wait < 0: raise BadWaitTime('sample_wait must be 0 or greater') self.queue = deque(maxlen=queue_len) self.partial = bool(partial) self.sample_wait = float(sample_wait) self.full = Event() self.parent = weakref.proxy(parent) self.average = average @property def value(self): if not self.partial: self.full.wait() try: return self.average(self.queue) except ZeroDivisionError: # No data == inactive value return 0.0 def fill(self): try: while (not self.stopping.wait(self.sample_wait) and len(self.queue) < self.queue.maxlen): self.queue.append(self.parent._read()) if self.partial and isinstance(self.parent, EventsMixin): self.parent._fire_events() self.full.set() while not self.stopping.wait(self.sample_wait): self.queue.append(self.parent._read()) if isinstance(self.parent, EventsMixin): self.parent._fire_events() except ReferenceError: # Parent is dead; time to die! pass