mirror of
				https://github.com/KevinMidboe/python-gpiozero.git
				synced 2025-10-29 17:50:37 +00:00 
			
		
		
		
	Fix #459 - properly support remote SPI with pigpio
Sorry! Dave's messing around with the pin implementations again. Hopefully the last time. The pin_factory is now really a factory object which can be asked to produce individual pins or pin-based interfaces like SPI (which can be supported properly via pigpio).
This commit is contained in:
		| @@ -59,6 +59,20 @@ Errors | |||||||
|  |  | ||||||
| .. autoexception:: SPIBadArgs | .. autoexception:: SPIBadArgs | ||||||
|  |  | ||||||
|  | .. autoexception:: SPIBadChannel | ||||||
|  |  | ||||||
|  | .. autoexception:: SPIFixedClockMode | ||||||
|  |  | ||||||
|  | .. autoexception:: SPIInvalidClockMode | ||||||
|  |  | ||||||
|  | .. autoexception:: SPIFixedBitOrder | ||||||
|  |  | ||||||
|  | .. autoexception:: SPIFixedSelect | ||||||
|  |  | ||||||
|  | .. autoexception:: SPIFixedWordSize | ||||||
|  |  | ||||||
|  | .. autoexception:: SPIInvalidWordSize | ||||||
|  |  | ||||||
| .. autoexception:: GPIODeviceError | .. autoexception:: GPIODeviceError | ||||||
|  |  | ||||||
| .. autoexception:: GPIODeviceClosed | .. autoexception:: GPIODeviceClosed | ||||||
| @@ -83,23 +97,31 @@ Errors | |||||||
|  |  | ||||||
| .. autoexception:: PinInvalidEdges | .. autoexception:: PinInvalidEdges | ||||||
|  |  | ||||||
|  | .. autoexception:: PinInvalidBounce | ||||||
|  |  | ||||||
| .. autoexception:: PinSetInput | .. autoexception:: PinSetInput | ||||||
|  |  | ||||||
| .. autoexception:: PinFixedPull | .. autoexception:: PinFixedPull | ||||||
|  |  | ||||||
| .. autoexception:: PinEdgeDetectUnsupported | .. autoexception:: PinEdgeDetectUnsupported | ||||||
|  |  | ||||||
|  | .. autoexception:: PinGPIOUnsupported | ||||||
|  |  | ||||||
|  | .. autoexception:: PinSPIUnsupported | ||||||
|  |  | ||||||
| .. autoexception:: PinPWMError | .. autoexception:: PinPWMError | ||||||
|  |  | ||||||
| .. autoexception:: PinPWMUnsupported | .. autoexception:: PinPWMUnsupported | ||||||
|  |  | ||||||
| .. autoexception:: PinPWMFixedValue | .. autoexception:: PinPWMFixedValue | ||||||
|  |  | ||||||
|  | .. autoexception:: PinUnknownPi | ||||||
|  |  | ||||||
| .. autoexception:: PinMultiplePins | .. autoexception:: PinMultiplePins | ||||||
|  |  | ||||||
| .. autoexception:: PinNoPins | .. autoexception:: PinNoPins | ||||||
|  |  | ||||||
| .. autoexception:: PinUnknownPi | .. autoexception:: PinInvalidPin | ||||||
|  |  | ||||||
| Warnings | Warnings | ||||||
| ======== | ======== | ||||||
| @@ -110,3 +132,7 @@ Warnings | |||||||
|  |  | ||||||
| .. autoexception:: SPISoftwareFallback | .. autoexception:: SPISoftwareFallback | ||||||
|  |  | ||||||
|  | .. autoexception:: PinFactoryFallback | ||||||
|  |  | ||||||
|  | .. autoexception:: PinNonPhysical | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,68 +11,68 @@ are concerned with. However, some users may wish to take advantage of the | |||||||
| capabilities of alternative GPIO implementations or (in future) use GPIO | capabilities of alternative GPIO implementations or (in future) use GPIO | ||||||
| extender chips. This is the purpose of the pins portion of the library. | extender chips. This is the purpose of the pins portion of the library. | ||||||
|  |  | ||||||
| When you construct a device, you pass in a GPIO pin number. However, what the | When you construct a device, you pass in a pin specification. However, what the | ||||||
| library actually expects is a :class:`Pin` implementation. If it finds a simple | library actually expects is a :class:`Pin` implementation. If it finds anything | ||||||
| integer number instead, it uses one of the following classes to provide the | else, it uses the existing ``Device._pin_factory`` to construct a :class:`Pin` | ||||||
| :class:`Pin` implementation (classes are listed in favoured order): | implementation based on the specification. | ||||||
|  |  | ||||||
| 1. :class:`gpiozero.pins.rpigpio.RPiGPIOPin` | Changing the pin factory | ||||||
|  | ======================== | ||||||
|  |  | ||||||
| 2. :class:`gpiozero.pins.rpio.RPIOPin` | The default pin factory can be replaced by specifying a value for the | ||||||
|  | ``GPIOZERO_PIN_FACTORY`` environment variable. For example: | ||||||
|  |  | ||||||
| 3. :class:`gpiozero.pins.pigpiod.PiGPIOPin` | .. code-block:: console | ||||||
|  |  | ||||||
| 4. :class:`gpiozero.pins.native.NativePin` |     pi@raspberrypi $ GPIOZERO_PIN_FACTORY=native python | ||||||
|  |     Python 3.4.2 (default, Oct 19 2014, 13:31:11) | ||||||
|  |     [GCC 4.9.1] on linux | ||||||
|  |     Type "help", "copyright", "credits" or "license" for more information. | ||||||
|  |     >>> import gpiozero | ||||||
|  |     >>> gpiozero.Device._pin_factory | ||||||
|  |     <gpiozero.pins.native.NativeFactory object at 0x762c26b0> | ||||||
|  |  | ||||||
| You can change the default pin implementation by over-writing the | The following values, and the corresponding :class:`Factory` and :class:`Pin` | ||||||
| ``pin_factory`` global in the ``devices`` module like so:: | classes are listed in the table below. Factories are listed in the order that | ||||||
|  | they are tried by default. | ||||||
|  |  | ||||||
|     from gpiozero.pins.native import NativePin | +---------+-----------------------------------------------+-------------------------------------------+ | ||||||
|     import gpiozero.devices | | Name    | Factory class                                 | Pin class                                 | | ||||||
|     # Force the default pin implementation to be NativePin | +=========+===============================================+===========================================+ | ||||||
|     gpiozero.devices.pin_factory = NativePin | | rpigpio | :class:`gpiozero.pins.rpigpio.RPiGPIOFactory` | :class:`gpiozero.pins.rpigpio.RPiGPIOPin` | | ||||||
|  | +---------+-----------------------------------------------+-------------------------------------------+ | ||||||
|  | | rpio    | :class:`gpiozero.pins.rpio.RPIOFactory`       | :class:`gpiozero.pins.rpio.RPIOPin`       | | ||||||
|  | +---------+-----------------------------------------------+-------------------------------------------+ | ||||||
|  | | pigpio  | :class:`gpiozero.pins.pigpiod.PiGPIOFactory`  | :class:`gpiozero.pins.pigpiod.PiGPIOPin`  | | ||||||
|  | +---------+-----------------------------------------------+-------------------------------------------+ | ||||||
|  | | native  | :class:`gpiozero.pins.native.NativeFactory`   | :class:`gpiozero.pins.native.NativePin`   | | ||||||
|  | +---------+-----------------------------------------------+-------------------------------------------+ | ||||||
|  |  | ||||||
|  | If you need to change the default pin factory from within a script, use the | ||||||
|  | ``Device._set_pin_factory`` class method, passing in the instance of the new | ||||||
|  | factory to use. This is only supported at script startup (replacing the factory | ||||||
|  | closes all existing pin instances which can have interesting consequences for | ||||||
|  | any devices using them):: | ||||||
|  |  | ||||||
|  |     from gpiozero.pins.native import NativeFactory | ||||||
|  |     from gpiozero import * | ||||||
|  |     Device._set_pin_factory(NativeFactory()) | ||||||
|  |  | ||||||
|     from gpiozero import LED |     from gpiozero import LED | ||||||
|  |  | ||||||
|     # This will now use NativePin instead of RPiGPIOPin |     # This will now use NativePin instead of RPiGPIOPin | ||||||
|     led = LED(16) |     led = LED(16) | ||||||
|  |  | ||||||
| ``pin_factory`` is a concrete descendent of the abstract :class:`Pin` class. | Certain factories may take default information from additional sources. | ||||||
| The descendent may take additional parameters in its constructor provided they |  | ||||||
| are optional; GPIO Zero will expect to be able to construct instances with |  | ||||||
| nothing more than an integer pin number. |  | ||||||
|  |  | ||||||
| However, the descendent may take default information from additional sources. |  | ||||||
| For example, to default to creating pins with | For example, to default to creating pins with | ||||||
| :class:`gpiozero.pins.pigpiod.PiGPIOPin` on a remote pi called ``remote-pi`` | :class:`gpiozero.pins.pigpiod.PiGPIOPin` on a remote pi called ``remote-pi`` | ||||||
| you can set the :envvar:`PIGPIO_ADDR` environment variable when running your | you can set the :envvar:`PIGPIO_ADDR` environment variable when running your | ||||||
| script:: | script: | ||||||
|  |  | ||||||
|     $ PIGPIO_ADDR=remote-pi python my_script.py | .. code-block:: console | ||||||
|  |  | ||||||
| It is worth noting that instead of passing an integer to device constructors, |     $ export GPIOZERO_PIN_FACTORY=pigpio | ||||||
| you can pass an object derived from :class:`Pin` itself:: |     $ PIGPIO_ADDR=remote-pi python3 my_script.py | ||||||
|  |  | ||||||
|     from gpiozero.pins.native import NativePin |  | ||||||
|     from gpiozero import LED |  | ||||||
|  |  | ||||||
|     led = LED(NativePin(16)) |  | ||||||
|  |  | ||||||
| In future, this separation of pins and devices should also permit the library |  | ||||||
| to utilize pins that are part of IO extender chips. For example:: |  | ||||||
|  |  | ||||||
|     from gpiozero import IOExtender, LED |  | ||||||
|  |  | ||||||
|     ext = IOExtender() |  | ||||||
|     led = LED(ext.pins[0]) |  | ||||||
|     led.on() |  | ||||||
|  |  | ||||||
| .. warning:: |  | ||||||
|  |  | ||||||
|     While the devices API is now considered stable and won't change in |  | ||||||
|     backwards incompatible ways, the pins API is *not* yet considered stable. |  | ||||||
|     It is potentially subject to change in future versions. We welcome any |  | ||||||
|     comments from testers! |  | ||||||
|  |  | ||||||
| .. warning:: | .. warning:: | ||||||
|  |  | ||||||
| @@ -83,41 +83,48 @@ to utilize pins that are part of IO extender chips. For example:: | |||||||
|     actual raspberry pie, you have only yourself to blame. |     actual raspberry pie, you have only yourself to blame. | ||||||
|  |  | ||||||
|  |  | ||||||
| RPiGPIOPin | RPi.GPIO | ||||||
| ========== | ======== | ||||||
|  |  | ||||||
|  | .. autoclass:: gpiozero.pins.rpigpio.RPiGPIOFactory | ||||||
|  |  | ||||||
| .. autoclass:: gpiozero.pins.rpigpio.RPiGPIOPin | .. autoclass:: gpiozero.pins.rpigpio.RPiGPIOPin | ||||||
|  |  | ||||||
|  |  | ||||||
| RPIOPin | RPIO | ||||||
| ======= | ==== | ||||||
|  |  | ||||||
|  | .. autoclass:: gpiozero.pins.rpio.RPIOFactory | ||||||
|  |  | ||||||
| .. autoclass:: gpiozero.pins.rpio.RPIOPin | .. autoclass:: gpiozero.pins.rpio.RPIOPin | ||||||
|  |  | ||||||
|  |  | ||||||
| PiGPIOPin | PiGPIO | ||||||
| ========= | ====== | ||||||
|  |  | ||||||
|  | .. autoclass:: gpiozero.pins.pigpiod.PiGPIOFactory | ||||||
|  |  | ||||||
| .. autoclass:: gpiozero.pins.pigpiod.PiGPIOPin | .. autoclass:: gpiozero.pins.pigpiod.PiGPIOPin | ||||||
|  |  | ||||||
|  |  | ||||||
| NativePin | Native | ||||||
| ========= | ====== | ||||||
|  |  | ||||||
|  | .. autoclass:: gpiozero.pins.native.NativeFactory | ||||||
|  |  | ||||||
| .. autoclass:: gpiozero.pins.native.NativePin | .. autoclass:: gpiozero.pins.native.NativePin | ||||||
|  |  | ||||||
|  |  | ||||||
| Abstract Pin | Base classes | ||||||
| ============ | ============ | ||||||
|  |  | ||||||
|  | .. autoclass:: Factory | ||||||
|  |     :members: | ||||||
|  |  | ||||||
| .. autoclass:: Pin | .. autoclass:: Pin | ||||||
|     :members: |     :members: | ||||||
|  |  | ||||||
|  | .. autoclass:: SPI | ||||||
| Local Pin |  | ||||||
| ========= |  | ||||||
|  |  | ||||||
| .. autoclass:: LocalPin |  | ||||||
|     :members: |     :members: | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB | 
| @@ -4,6 +4,7 @@ Notes | |||||||
|  |  | ||||||
| .. currentmodule:: gpiozero | .. currentmodule:: gpiozero | ||||||
|  |  | ||||||
|  |  | ||||||
| .. _keep-your-script-running: | .. _keep-your-script-running: | ||||||
|  |  | ||||||
| Keep your script running | Keep your script running | ||||||
| @@ -46,6 +47,7 @@ events to be detected:: | |||||||
|     button.when_pressed = hello |     button.when_pressed = hello | ||||||
|     pause() |     pause() | ||||||
|  |  | ||||||
|  |  | ||||||
| Importing from GPIO Zero | Importing from GPIO Zero | ||||||
| ======================== | ======================== | ||||||
|  |  | ||||||
| @@ -70,12 +72,15 @@ In this case, all references to items within GPIO Zero must be prefixed:: | |||||||
|  |  | ||||||
|     button = gpiozero.Button(2) |     button = gpiozero.Button(2) | ||||||
|  |  | ||||||
|  |  | ||||||
| How can I tell what version of gpiozero I have installed? | How can I tell what version of gpiozero I have installed? | ||||||
| ========================================================= | ========================================================= | ||||||
|  |  | ||||||
| The gpiozero library relies on the setuptools package for installation | The gpiozero library relies on the setuptools package for installation | ||||||
| services.  You can use the setuptools ``pkg_resources`` API to query which | services.  You can use the setuptools ``pkg_resources`` API to query which | ||||||
| version of gpiozero is available in your Python environment like so:: | version of gpiozero is available in your Python environment like so: | ||||||
|  |  | ||||||
|  | .. code-block:: pycon | ||||||
|  |  | ||||||
|     >>> from pkg_resources import require |     >>> from pkg_resources import require | ||||||
|     >>> require('gpiozero') |     >>> require('gpiozero') | ||||||
| @@ -89,7 +94,9 @@ the first entry in the list will be the version that ``import gpiozero`` will | |||||||
| import. | import. | ||||||
|  |  | ||||||
| If you receive the error "No module named pkg_resources", you need to install | If you receive the error "No module named pkg_resources", you need to install | ||||||
| the ``pip`` utility. This can be done with the following command in Raspbian:: | the ``pip`` utility. This can be done with the following command in Raspbian: | ||||||
|  |  | ||||||
|  | .. code-block:: console | ||||||
|  |  | ||||||
|     $ sudo apt-get install python-pip |     $ sudo apt-get install python-pip | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ library. Please note that all recipes are written assuming Python 3. Recipes | |||||||
| *may* work under Python 2, but no guarantees! | *may* work under Python 2, but no guarantees! | ||||||
|  |  | ||||||
|  |  | ||||||
| .. _pin_numbering: | .. _pin-numbering: | ||||||
|  |  | ||||||
| Pin Numbering | Pin Numbering | ||||||
| ============= | ============= | ||||||
| @@ -429,7 +429,9 @@ functionality without the need to wire up your own LEDs (also useful because | |||||||
| the power and activity LEDs are "known good"). | the power and activity LEDs are "known good"). | ||||||
|  |  | ||||||
| Firstly you need to disable the usual triggers for the built-in LEDs. This can | Firstly you need to disable the usual triggers for the built-in LEDs. This can | ||||||
| be done from the terminal with the following commands:: | be done from the terminal with the following commands: | ||||||
|  |  | ||||||
|  | .. code-block:: console | ||||||
|  |  | ||||||
|     $ echo none | sudo tee /sys/class/leds/led0/trigger |     $ echo none | sudo tee /sys/class/leds/led0/trigger | ||||||
|     $ echo gpio | sudo tee /sys/class/leds/led1/trigger |     $ echo gpio | sudo tee /sys/class/leds/led1/trigger | ||||||
| @@ -439,7 +441,9 @@ Now you can control the LEDs with gpiozero like so: | |||||||
| .. literalinclude:: examples/led_builtin.py | .. literalinclude:: examples/led_builtin.py | ||||||
|  |  | ||||||
| To revert the LEDs to their usual purpose you can either reboot your Pi or | To revert the LEDs to their usual purpose you can either reboot your Pi or | ||||||
| run the following commands:: | run the following commands: | ||||||
|  |  | ||||||
|  | .. code-block:: console | ||||||
|  |  | ||||||
|     $ echo mmc0 | sudo tee /sys/class/leds/led0/trigger |     $ echo mmc0 | sudo tee /sys/class/leds/led0/trigger | ||||||
|     $ echo input | sudo tee /sys/class/leds/led1/trigger |     $ echo input | sudo tee /sys/class/leds/led1/trigger | ||||||
|   | |||||||
| @@ -6,8 +6,9 @@ from __future__ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| from .pins import ( | from .pins import ( | ||||||
|  |     Factory, | ||||||
|     Pin, |     Pin, | ||||||
|     LocalPin, |     SPI, | ||||||
| ) | ) | ||||||
| from .pins.data import ( | from .pins.data import ( | ||||||
|     PiBoardInfo, |     PiBoardInfo, | ||||||
| @@ -15,47 +16,9 @@ from .pins.data import ( | |||||||
|     PinInfo, |     PinInfo, | ||||||
|     pi_info, |     pi_info, | ||||||
| ) | ) | ||||||
| from .exc import ( | # Yes, import * is naughty, but exc imports nothing else so there's no cross | ||||||
|     GPIOZeroError, | # contamination here ... and besides, have you *seen* the list lately?! | ||||||
|     DeviceClosed, | from .exc import * | ||||||
|     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 ( | from .devices import ( | ||||||
|     Device, |     Device, | ||||||
|     GPIODevice, |     GPIODevice, | ||||||
|   | |||||||
| @@ -10,15 +10,16 @@ str = type('') | |||||||
| import os | import os | ||||||
| import atexit | import atexit | ||||||
| import weakref | import weakref | ||||||
|  | import warnings | ||||||
| from collections import namedtuple | from collections import namedtuple | ||||||
| from itertools import chain | from itertools import chain | ||||||
| from types import FunctionType | from types import FunctionType | ||||||
| from threading import RLock | from threading import Lock | ||||||
|  |  | ||||||
| import pkg_resources | import pkg_resources | ||||||
|  |  | ||||||
|  | from .pins import Pin | ||||||
| from .threads import _threads_shutdown | from .threads import _threads_shutdown | ||||||
| from .pins import _pins_shutdown |  | ||||||
| from .mixins import ( | from .mixins import ( | ||||||
|     ValuesMixin, |     ValuesMixin, | ||||||
|     SharedMixin, |     SharedMixin, | ||||||
| @@ -32,52 +33,11 @@ from .exc import ( | |||||||
|     GPIOPinMissing, |     GPIOPinMissing, | ||||||
|     GPIOPinInUse, |     GPIOPinInUse, | ||||||
|     GPIODeviceClosed, |     GPIODeviceClosed, | ||||||
|  |     PinFactoryFallback, | ||||||
|     ) |     ) | ||||||
| from .compat import frozendict | 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): | class GPIOMeta(type): | ||||||
|     # NOTE Yes, this is a metaclass. Don't be scared - it's a simple one. |     # NOTE Yes, this is a metaclass. Don't be scared - it's a simple one. | ||||||
|  |  | ||||||
| @@ -106,7 +66,7 @@ class GPIOMeta(type): | |||||||
|             # already exists. Only construct the instance if the key's new. |             # already exists. Only construct the instance if the key's new. | ||||||
|             key = cls._shared_key(*args, **kwargs) |             key = cls._shared_key(*args, **kwargs) | ||||||
|             try: |             try: | ||||||
|                 self = cls._INSTANCES[key] |                 self = cls._instances[key] | ||||||
|                 self._refs += 1 |                 self._refs += 1 | ||||||
|             except (KeyError, ReferenceError) as e: |             except (KeyError, ReferenceError) as e: | ||||||
|                 self = super(GPIOMeta, cls).__call__(*args, **kwargs) |                 self = super(GPIOMeta, cls).__call__(*args, **kwargs) | ||||||
| @@ -122,14 +82,14 @@ class GPIOMeta(type): | |||||||
|                             old_close() |                             old_close() | ||||||
|                         finally: |                         finally: | ||||||
|                             try: |                             try: | ||||||
|                                 del cls._INSTANCES[key] |                                 del cls._instances[key] | ||||||
|                             except KeyError: |                             except KeyError: | ||||||
|                                 # If the _refs go negative (too many closes) |                                 # If the _refs go negative (too many closes) | ||||||
|                                 # just ignore the resulting KeyError here - |                                 # just ignore the resulting KeyError here - | ||||||
|                                 # it's already gone |                                 # it's already gone | ||||||
|                                 pass |                                 pass | ||||||
|                 self.close = close |                 self.close = close | ||||||
|                 cls._INSTANCES[key] = weakref.proxy(self) |                 cls._instances[key] = weakref.proxy(self) | ||||||
|         else: |         else: | ||||||
|             # Construct the instance as normal |             # Construct the instance as normal | ||||||
|             self = super(GPIOMeta, cls).__call__(*args, **kwargs) |             self = super(GPIOMeta, cls).__call__(*args, **kwargs) | ||||||
| @@ -229,13 +189,100 @@ class GPIOBase(GPIOMeta(nstr('GPIOBase'), (), {})): | |||||||
| class Device(ValuesMixin, GPIOBase): | class Device(ValuesMixin, GPIOBase): | ||||||
|     """ |     """ | ||||||
|     Represents a single device of any type; GPIO-based, SPI-based, I2C-based, |     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 |     etc. This is the base class of the device hierarchy. It defines the basic | ||||||
|     basic services applicable to all devices (specifically the :attr:`is_active` |     services applicable to all devices (specifically the :attr:`is_active` | ||||||
|     property, the :attr:`value` property, and the :meth:`close` method). |     property, the :attr:`value` property, and the :meth:`close` method). | ||||||
|     """ |     """ | ||||||
|  |     _pin_factory = None # instance of a Factory sub-class | ||||||
|  |     _reservations = {} # maps pin addresses to lists of devices | ||||||
|  |     _res_lock = Lock() | ||||||
|  |  | ||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|         return "<gpiozero.%s object>" % (self.__class__.__name__) |         return "<gpiozero.%s object>" % (self.__class__.__name__) | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def _set_pin_factory(cls, new_factory): | ||||||
|  |         if cls._pin_factory is not None: | ||||||
|  |             cls._pin_factory.close() | ||||||
|  |         cls._pin_factory = new_factory | ||||||
|  |  | ||||||
|  |     def _reserve_pins(self, *pins_or_addresses): | ||||||
|  |         """ | ||||||
|  |         Called to indicate that the device reserves the right to use the | ||||||
|  |         specified *pins_or_addresses*. This should be done during device | ||||||
|  |         construction.  If pins are reserved, you must ensure that the | ||||||
|  |         reservation is released by eventually called :meth:`_release_pins`. | ||||||
|  |  | ||||||
|  |         The *pins_or_addresses* can be actual :class:`Pin` instances or the | ||||||
|  |         addresses of pin instances (each address is a tuple of strings). The | ||||||
|  |         latter form is permitted to ensure that devices do not have to | ||||||
|  |         construct :class:`Pin` objects to reserve pins. This is important as | ||||||
|  |         constructing a pin often configures it (e.g. as an input) which | ||||||
|  |         conflicts with alternate pin functions like SPI. | ||||||
|  |         """ | ||||||
|  |         addresses = ( | ||||||
|  |             p.address if isinstance(p, Pin) else p | ||||||
|  |             for p in pins_or_addresses | ||||||
|  |             ) | ||||||
|  |         with self._res_lock: | ||||||
|  |             for address in addresses: | ||||||
|  |                 try: | ||||||
|  |                     conflictors = self._reservations[address] | ||||||
|  |                 except KeyError: | ||||||
|  |                     conflictors = [] | ||||||
|  |                     self._reservations[address] = conflictors | ||||||
|  |                 for device_ref in conflictors: | ||||||
|  |                     device = device_ref() | ||||||
|  |                     if device is not None and self._conflicts_with(device): | ||||||
|  |                         raise GPIOPinInUse( | ||||||
|  |                             'pin %s is already in use by %r' % ( | ||||||
|  |                                 '/'.join(address), device) | ||||||
|  |                         ) | ||||||
|  |                 conflictors.append(weakref.ref(self)) | ||||||
|  |  | ||||||
|  |     def _release_pins(self, *pins_or_addresses): | ||||||
|  |         """ | ||||||
|  |         Releases the reservation of this device against *pins_or_addresses*. | ||||||
|  |         This is typically called during :meth:`close` to clean up reservations | ||||||
|  |         taken during construction. Releasing a reservation that is not | ||||||
|  |         currently held will be silently ignored (to permit clean-up after | ||||||
|  |         failed / partial construction). | ||||||
|  |         """ | ||||||
|  |         addresses = ( | ||||||
|  |             p.address if isinstance(p, Pin) else p | ||||||
|  |             for p in pins_or_addresses | ||||||
|  |             ) | ||||||
|  |         with self._res_lock: | ||||||
|  |             for address in addresses: | ||||||
|  |                 self._reservations[address] = [ | ||||||
|  |                     ref for ref in self._reservations[address] | ||||||
|  |                     if ref() not in (self, None) # may as well clean up dead refs | ||||||
|  |                     ] | ||||||
|  |  | ||||||
|  |     def _release_all(self): | ||||||
|  |         """ | ||||||
|  |         Releases all pin reservations taken out by this device. See | ||||||
|  |         :meth:`_release_pins` for further information). | ||||||
|  |         """ | ||||||
|  |         with self._res_lock: | ||||||
|  |             Device._reservations = { | ||||||
|  |                 address: [ | ||||||
|  |                     ref for ref in conflictors | ||||||
|  |                     if ref() not in (self, None) | ||||||
|  |                     ] | ||||||
|  |                 for address, conflictors in self._reservations.items() | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |     def _conflicts_with(self, other): | ||||||
|  |         """ | ||||||
|  |         Called by :meth:`_reserve_pin` to test whether the *other* | ||||||
|  |         :class:`Device` using a common pin conflicts with this device's intent | ||||||
|  |         to use it. The default is ``True`` indicating that all devices conflict | ||||||
|  |         with common pins.  Sub-classes may override this to permit more nuanced | ||||||
|  |         replies. | ||||||
|  |         """ | ||||||
|  |         return True | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def value(self): |     def value(self): | ||||||
|         """ |         """ | ||||||
| @@ -378,14 +425,12 @@ class GPIODevice(Device): | |||||||
|         self._pin = None |         self._pin = None | ||||||
|         if pin is None: |         if pin is None: | ||||||
|             raise GPIOPinMissing('No pin given') |             raise GPIOPinMissing('No pin given') | ||||||
|         if isinstance(pin, int): |         if isinstance(pin, Pin): | ||||||
|             pin = pin_factory(pin) |             self._reserve_pins(pin) | ||||||
|         with _PINS_LOCK: |         else: | ||||||
|             if pin in _PINS: |             # Check you can reserve *before* constructing the pin | ||||||
|                 raise GPIOPinInUse( |             self._reserve_pins(self._pin_factory.pin_address(pin)) | ||||||
|                     'pin %r is already in use by another gpiozero object' % pin |             pin = self._pin_factory.pin(pin) | ||||||
|                 ) |  | ||||||
|             _PINS.add(pin) |  | ||||||
|         self._pin = pin |         self._pin = pin | ||||||
|         self._active_state = True |         self._active_state = True | ||||||
|         self._inactive_state = False |         self._inactive_state = False | ||||||
| @@ -402,12 +447,10 @@ class GPIODevice(Device): | |||||||
|  |  | ||||||
|     def close(self): |     def close(self): | ||||||
|         super(GPIODevice, self).close() |         super(GPIODevice, self).close() | ||||||
|         with _PINS_LOCK: |         if self._pin is not None: | ||||||
|             pin = self._pin |             self._release_pins(self._pin) | ||||||
|  |             self._pin.close() | ||||||
|             self._pin = None |             self._pin = None | ||||||
|             if pin in _PINS: |  | ||||||
|                 _PINS.remove(pin) |  | ||||||
|                 pin.close() |  | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def closed(self): |     def closed(self): | ||||||
| @@ -441,3 +484,41 @@ class GPIODevice(Device): | |||||||
|         except DeviceClosed: |         except DeviceClosed: | ||||||
|             return "<gpiozero.%s object closed>" % self.__class__.__name__ |             return "<gpiozero.%s object closed>" % self.__class__.__name__ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Defined last to ensure Device is defined before attempting to load any pin | ||||||
|  | # factory; pin factories want to load spi which in turn relies on devices (for | ||||||
|  | # the soft-SPI implementation) | ||||||
|  | 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 ('rpigpio', 'rpio', 'pigpio', 'native'): | ||||||
|  |             try: | ||||||
|  |                 return pkg_resources.load_entry_point(dist, group, name)() | ||||||
|  |             except Exception as e: | ||||||
|  |                 warnings.warn( | ||||||
|  |                     PinFactoryFallback( | ||||||
|  |                         'Failed to load factory %s: %s' % (name, str(e)))) | ||||||
|  |         raise BadPinFactory('Unable to load any default pin factory!') | ||||||
|  |     else: | ||||||
|  |         for factory in pkg_resources.iter_entry_points(group, name.lower()): | ||||||
|  |             return factory.load()() | ||||||
|  |         raise BadPinFactory('Unable to find pin factory "%s"' % name) | ||||||
|  |  | ||||||
|  | Device._set_pin_factory(_default_pin_factory()) | ||||||
|  |  | ||||||
|  | def _shutdown(): | ||||||
|  |     _threads_shutdown() | ||||||
|  |     Device._set_pin_factory(None) | ||||||
|  |  | ||||||
|  | atexit.register(_shutdown) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -52,6 +52,24 @@ class SPIBadArgs(SPIError, ValueError): | |||||||
| class SPIBadChannel(SPIError, ValueError): | class SPIBadChannel(SPIError, ValueError): | ||||||
|     "Error raised when an invalid channel is given to an :class:`AnalogInputDevice`" |     "Error raised when an invalid channel is given to an :class:`AnalogInputDevice`" | ||||||
|  |  | ||||||
|  | class SPIFixedClockMode(SPIError, AttributeError): | ||||||
|  |     "Error raised when the SPI clock mode cannot be changed" | ||||||
|  |  | ||||||
|  | class SPIInvalidClockMode(SPIError, ValueError): | ||||||
|  |     "Error raised when an invalid clock mode is given to an SPI implementation" | ||||||
|  |  | ||||||
|  | class SPIFixedBitOrder(SPIError, AttributeError): | ||||||
|  |     "Error raised when the SPI bit-endianness cannot be changed" | ||||||
|  |  | ||||||
|  | class SPIFixedSelect(SPIError, AttributeError): | ||||||
|  |     "Error raised when the SPI select polarity cannot be changed" | ||||||
|  |  | ||||||
|  | class SPIFixedWordSize(SPIError, AttributeError): | ||||||
|  |     "Error raised when the number of bits per word cannot be changed" | ||||||
|  |  | ||||||
|  | class SPIInvalidWordSize(SPIError, ValueError): | ||||||
|  |     "Error raised when an invalid (out of range) number of bits per word is specified" | ||||||
|  |  | ||||||
| class GPIODeviceError(GPIOZeroError): | class GPIODeviceError(GPIOZeroError): | ||||||
|     "Base class for errors specific to the GPIODevice hierarchy" |     "Base class for errors specific to the GPIODevice hierarchy" | ||||||
|  |  | ||||||
| @@ -62,7 +80,7 @@ class GPIOPinInUse(GPIODeviceError): | |||||||
|     "Error raised when attempting to use a pin already in use by another device" |     "Error raised when attempting to use a pin already in use by another device" | ||||||
|  |  | ||||||
| class GPIOPinMissing(GPIODeviceError, ValueError): | class GPIOPinMissing(GPIODeviceError, ValueError): | ||||||
|     "Error raised when a pin number is not specified" |     "Error raised when a pin specification is not given" | ||||||
|  |  | ||||||
| class InputDeviceError(GPIODeviceError): | class InputDeviceError(GPIODeviceError): | ||||||
|     "Base class for errors specific to the InputDevice hierarchy" |     "Base class for errors specific to the InputDevice hierarchy" | ||||||
| @@ -100,6 +118,12 @@ class PinFixedPull(PinError, AttributeError): | |||||||
| class PinEdgeDetectUnsupported(PinError, AttributeError): | class PinEdgeDetectUnsupported(PinError, AttributeError): | ||||||
|     "Error raised when attempting to use edge detection on unsupported pins" |     "Error raised when attempting to use edge detection on unsupported pins" | ||||||
|  |  | ||||||
|  | class PinGPIOUnsupported(PinError, NotImplementedError): | ||||||
|  |     "Error raised when attempting to obtain a GPIO interface on unsupported pins" | ||||||
|  |  | ||||||
|  | class PinSPIUnsupported(PinError, NotImplementedError): | ||||||
|  |     "Error raised when attempting to obtain an SPI interface on unsupported pins" | ||||||
|  |  | ||||||
| class PinPWMError(PinError): | class PinPWMError(PinError): | ||||||
|     "Base class for errors related to PWM implementations" |     "Base class for errors related to PWM implementations" | ||||||
|  |  | ||||||
| @@ -118,6 +142,9 @@ class PinMultiplePins(PinError, RuntimeError): | |||||||
| class PinNoPins(PinError, RuntimeError): | class PinNoPins(PinError, RuntimeError): | ||||||
|     "Error raised when no pins support the requested function" |     "Error raised when no pins support the requested function" | ||||||
|  |  | ||||||
|  | class PinInvalidPin(PinError, ValueError): | ||||||
|  |     "Error raised when an invalid pin specification is provided" | ||||||
|  |  | ||||||
| class GPIOZeroWarning(Warning): | class GPIOZeroWarning(Warning): | ||||||
|     "Base class for all warnings in GPIO Zero" |     "Base class for all warnings in GPIO Zero" | ||||||
|  |  | ||||||
| @@ -130,6 +157,9 @@ class SPISoftwareFallback(SPIWarning): | |||||||
| class PinWarning(GPIOZeroWarning): | class PinWarning(GPIOZeroWarning): | ||||||
|     "Base class for warnings related to pin implementations" |     "Base class for warnings related to pin implementations" | ||||||
|  |  | ||||||
|  | class PinFactoryFallback(PinWarning): | ||||||
|  |     "Warning raised when a default pin factory fails to load and a fallback is tried" | ||||||
|  |  | ||||||
| class PinNonPhysical(PinWarning): | class PinNonPhysical(PinWarning): | ||||||
|     "Warning raised when a non-physical pin is specified in a constructor" |     "Warning raised when a non-physical pin is specified in a constructor" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -165,7 +165,7 @@ class SmoothedInputDevice(EventsMixin, InputDevice): | |||||||
|             if self.partial or self._queue.full.is_set(): |             if self.partial or self._queue.full.is_set(): | ||||||
|                 return super(SmoothedInputDevice, self).__repr__() |                 return super(SmoothedInputDevice, self).__repr__() | ||||||
|             else: |             else: | ||||||
|                 return "<gpiozero.%s object on pin=%r, pull_up=%s>" % ( |                 return "<gpiozero.%s object on pin %r, pull_up=%s>" % ( | ||||||
|                     self.__class__.__name__, self.pin, self.pull_up) |                     self.__class__.__name__, self.pin, self.pull_up) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
| @@ -240,7 +240,7 @@ class Button(HoldMixin, DigitalInputDevice): | |||||||
|         print("The button was pressed!") |         print("The button was pressed!") | ||||||
|  |  | ||||||
|     :param int pin: |     :param int pin: | ||||||
|         The GPIO pin which the button is attached to. See :ref:`pin_numbering` |         The GPIO pin which the button is attached to. See :ref:`pin-numbering` | ||||||
|         for valid pin numbers. |         for valid pin numbers. | ||||||
|  |  | ||||||
|     :param bool pull_up: |     :param bool pull_up: | ||||||
| @@ -302,7 +302,7 @@ class LineSensor(SmoothedInputDevice): | |||||||
|         pause() |         pause() | ||||||
|  |  | ||||||
|     :param int pin: |     :param int pin: | ||||||
|         The GPIO pin which the sensor is attached to. See :ref:`pin_numbering` |         The GPIO pin which the sensor is attached to. See :ref:`pin-numbering` | ||||||
|         for valid pin numbers. |         for valid pin numbers. | ||||||
|  |  | ||||||
|     :param int queue_len: |     :param int queue_len: | ||||||
| @@ -371,7 +371,7 @@ class MotionSensor(SmoothedInputDevice): | |||||||
|         print("Motion detected!") |         print("Motion detected!") | ||||||
|  |  | ||||||
|     :param int pin: |     :param int pin: | ||||||
|         The GPIO pin which the sensor is attached to. See :ref:`pin_numbering` |         The GPIO pin which the sensor is attached to. See :ref:`pin-numbering` | ||||||
|         for valid pin numbers. |         for valid pin numbers. | ||||||
|  |  | ||||||
|     :param int queue_len: |     :param int queue_len: | ||||||
| @@ -435,7 +435,7 @@ class LightSensor(SmoothedInputDevice): | |||||||
|         print("Light detected!") |         print("Light detected!") | ||||||
|  |  | ||||||
|     :param int pin: |     :param int pin: | ||||||
|         The GPIO pin which the sensor is attached to. See :ref:`pin_numbering` |         The GPIO pin which the sensor is attached to. See :ref:`pin-numbering` | ||||||
|         for valid pin numbers. |         for valid pin numbers. | ||||||
|  |  | ||||||
|     :param int queue_len: |     :param int queue_len: | ||||||
| @@ -543,11 +543,11 @@ class DistanceSensor(SmoothedInputDevice): | |||||||
|  |  | ||||||
|     :param int echo: |     :param int echo: | ||||||
|         The GPIO pin which the ECHO pin is attached to. See |         The GPIO pin which the ECHO pin is attached to. See | ||||||
|         :ref:`pin_numbering` for valid pin numbers. |         :ref:`pin-numbering` for valid pin numbers. | ||||||
|  |  | ||||||
|     :param int trigger: |     :param int trigger: | ||||||
|         The GPIO pin which the TRIG pin is attached to. See |         The GPIO pin which the TRIG pin is attached to. See | ||||||
|         :ref:`pin_numbering` for valid pin numbers. |         :ref:`pin-numbering` for valid pin numbers. | ||||||
|  |  | ||||||
|     :param int queue_len: |     :param int queue_len: | ||||||
|         The length of the queue used to store values read from the sensor. |         The length of the queue used to store values read from the sensor. | ||||||
|   | |||||||
| @@ -127,7 +127,7 @@ class SharedMixin(object): | |||||||
|     When :meth:`close` is called, an internal reference counter will be |     When :meth:`close` is called, an internal reference counter will be | ||||||
|     decremented and the instance will only close when it reaches zero. |     decremented and the instance will only close when it reaches zero. | ||||||
|     """ |     """ | ||||||
|     _INSTANCES = {} |     _instances = {} | ||||||
|  |  | ||||||
|     def __del__(self): |     def __del__(self): | ||||||
|         self._refs = 0 |         self._refs = 0 | ||||||
|   | |||||||
| @@ -128,8 +128,8 @@ class DigitalOutputDevice(OutputDevice): | |||||||
|     """ |     """ | ||||||
|     def __init__(self, pin=None, active_high=True, initial_value=False): |     def __init__(self, pin=None, active_high=True, initial_value=False): | ||||||
|         self._blink_thread = None |         self._blink_thread = None | ||||||
|         super(DigitalOutputDevice, self).__init__(pin, active_high, initial_value) |  | ||||||
|         self._controller = None |         self._controller = None | ||||||
|  |         super(DigitalOutputDevice, self).__init__(pin, active_high, initial_value) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def value(self): |     def value(self): | ||||||
| @@ -217,7 +217,7 @@ class LED(DigitalOutputDevice): | |||||||
|         led.on() |         led.on() | ||||||
|  |  | ||||||
|     :param int pin: |     :param int pin: | ||||||
|         The GPIO pin which the LED is attached to. See :ref:`pin_numbering` for |         The GPIO pin which the LED is attached to. See :ref:`pin-numbering` for | ||||||
|         valid pin numbers. |         valid pin numbers. | ||||||
|  |  | ||||||
|     :param bool active_high: |     :param bool active_high: | ||||||
| @@ -252,7 +252,7 @@ class Buzzer(DigitalOutputDevice): | |||||||
|         bz.on() |         bz.on() | ||||||
|  |  | ||||||
|     :param int pin: |     :param int pin: | ||||||
|         The GPIO pin which the buzzer is attached to. See :ref:`pin_numbering` |         The GPIO pin which the buzzer is attached to. See :ref:`pin-numbering` | ||||||
|         for valid pin numbers. |         for valid pin numbers. | ||||||
|  |  | ||||||
|     :param bool active_high: |     :param bool active_high: | ||||||
| @@ -276,7 +276,7 @@ class PWMOutputDevice(OutputDevice): | |||||||
|     Generic output device configured for pulse-width modulation (PWM). |     Generic output device configured for pulse-width modulation (PWM). | ||||||
|  |  | ||||||
|     :param int pin: |     :param int pin: | ||||||
|         The GPIO pin which the device is attached to. See :ref:`pin_numbering` |         The GPIO pin which the device is attached to. See :ref:`pin-numbering` | ||||||
|         for valid pin numbers. |         for valid pin numbers. | ||||||
|  |  | ||||||
|     :param bool active_high: |     :param bool active_high: | ||||||
| @@ -483,7 +483,7 @@ class PWMLED(PWMOutputDevice): | |||||||
|     an optional resistor to prevent the LED from burning out. |     an optional resistor to prevent the LED from burning out. | ||||||
|  |  | ||||||
|     :param int pin: |     :param int pin: | ||||||
|         The GPIO pin which the LED is attached to. See :ref:`pin_numbering` for |         The GPIO pin which the LED is attached to. See :ref:`pin-numbering` for | ||||||
|         valid pin numbers. |         valid pin numbers. | ||||||
|  |  | ||||||
|     :param bool active_high: |     :param bool active_high: | ||||||
| @@ -562,8 +562,12 @@ class RGBLED(SourceMixin, Device): | |||||||
|             raise GPIOPinMissing('red, green, and blue pins must be provided') |             raise GPIOPinMissing('red, green, and blue pins must be provided') | ||||||
|         LEDClass = PWMLED if pwm else LED |         LEDClass = PWMLED if pwm else LED | ||||||
|         super(RGBLED, self).__init__() |         super(RGBLED, self).__init__() | ||||||
|  |         try: | ||||||
|             self._leds = tuple(LEDClass(pin, active_high) for pin in (red, green, blue)) |             self._leds = tuple(LEDClass(pin, active_high) for pin in (red, green, blue)) | ||||||
|             self.value = initial_value |             self.value = initial_value | ||||||
|  |         except: | ||||||
|  |             self.close() | ||||||
|  |             raise | ||||||
|  |  | ||||||
|     red = _led_property(0) |     red = _led_property(0) | ||||||
|     green = _led_property(1) |     green = _led_property(1) | ||||||
| @@ -926,7 +930,7 @@ class Servo(SourceMixin, CompositeDevice): | |||||||
|             sleep(1) |             sleep(1) | ||||||
|  |  | ||||||
|     :param int pin: |     :param int pin: | ||||||
|         The GPIO pin which the device is attached to. See :ref:`pin_numbering` |         The GPIO pin which the device is attached to. See :ref:`pin-numbering` | ||||||
|         for valid pin numbers. |         for valid pin numbers. | ||||||
|  |  | ||||||
|     :param float initial_value: |     :param float initial_value: | ||||||
| @@ -1116,7 +1120,7 @@ class AngularServo(Servo): | |||||||
|         expectations of minimum and maximum. |         expectations of minimum and maximum. | ||||||
|  |  | ||||||
|     :param int pin: |     :param int pin: | ||||||
|         The GPIO pin which the device is attached to. See :ref:`pin_numbering` |         The GPIO pin which the device is attached to. See :ref:`pin-numbering` | ||||||
|         for valid pin numbers. |         for valid pin numbers. | ||||||
|  |  | ||||||
|     :param float initial_angle: |     :param float initial_angle: | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | # vim: set fileencoding=utf-8: | ||||||
|  |  | ||||||
| from __future__ import ( | from __future__ import ( | ||||||
|     unicode_literals, |     unicode_literals, | ||||||
|     absolute_import, |     absolute_import, | ||||||
| @@ -6,32 +8,124 @@ from __future__ import ( | |||||||
|     ) |     ) | ||||||
| str = type('') | str = type('') | ||||||
|  |  | ||||||
| import io |  | ||||||
|  |  | ||||||
| from .data import pi_info |  | ||||||
| from ..exc import ( | from ..exc import ( | ||||||
|     PinInvalidFunction, |     PinInvalidFunction, | ||||||
|     PinSetInput, |     PinSetInput, | ||||||
|     PinFixedPull, |     PinFixedPull, | ||||||
|  |     PinSPIUnsupported, | ||||||
|     PinPWMUnsupported, |     PinPWMUnsupported, | ||||||
|     PinEdgeDetectUnsupported, |     PinEdgeDetectUnsupported, | ||||||
|  |     SPIFixedClockMode, | ||||||
|  |     SPIFixedBitOrder, | ||||||
|  |     SPIFixedSelect, | ||||||
|  |     SPIFixedWordSize, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| PINS_CLEANUP = [] | class Factory(object): | ||||||
| def _pins_shutdown(): |     """ | ||||||
|     for routine in PINS_CLEANUP: |     Generates pins, SPI, and I2C interfaces for devices. This is an abstract | ||||||
|         routine() |     base class for pin factories. Descendents must override: | ||||||
|  |  | ||||||
|  |     * :meth:`_get_address` | ||||||
|  |     * :meth:`pin_address` | ||||||
|  |  | ||||||
|  |     Descendents may override: | ||||||
|  |  | ||||||
|  |     * :meth:`close` | ||||||
|  |     * :meth:`pin` | ||||||
|  |     * :meth:`spi` | ||||||
|  |     * :meth:`_get_pi_info` | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def close(self): | ||||||
|  |         """ | ||||||
|  |         Closes the pin factory. This is expected to clean up all resources | ||||||
|  |         manipulated by the factory. It it typically called at script | ||||||
|  |         termination. | ||||||
|  |         """ | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     def pin(self, spec): | ||||||
|  |         """ | ||||||
|  |         Creates an instance of a :class:`Pin` descendent representing the | ||||||
|  |         specified pin. | ||||||
|  |  | ||||||
|  |         .. warning:: | ||||||
|  |  | ||||||
|  |             Descendents must ensure that pin instances representing the same | ||||||
|  |             hardware are identical; i.e. two separate invocations of | ||||||
|  |             :meth:`pin` for the same pin specification must return the same | ||||||
|  |             object. | ||||||
|  |         """ | ||||||
|  |         raise PinGPIOUnsupported("GPIO not supported by this pin factory") | ||||||
|  |  | ||||||
|  |     def pin_address(self, spec): | ||||||
|  |         """ | ||||||
|  |         Returns the address that a pin *would* have if constructed from the | ||||||
|  |         given *spec*. | ||||||
|  |  | ||||||
|  |         This unusual method is used by the pin reservation system to check | ||||||
|  |         for conflicts *prior* to pin construction; with most implementations, | ||||||
|  |         pin construction implicitly alters the state of the pin (e.g. setting | ||||||
|  |         it to an input). This allows pin reservation to take place without | ||||||
|  |         affecting the state of other components. | ||||||
|  |         """ | ||||||
|  |         raise NotImplementedError | ||||||
|  |  | ||||||
|  |     def spi(self, **spi_args): | ||||||
|  |         """ | ||||||
|  |         Returns an instance of an :class:`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`. | ||||||
|  |         """ | ||||||
|  |         raise PinSPIUnsupported('SPI not supported by this pin factory') | ||||||
|  |  | ||||||
|  |     def _get_address(self): | ||||||
|  |         raise NotImplementedError | ||||||
|  |  | ||||||
|  |     address = property( | ||||||
|  |         lambda self: self._get_address(), | ||||||
|  |         doc="""\ | ||||||
|  |         Returns a tuple of strings representing the address of the factory. | ||||||
|  |         For the Pi itself this is a tuple of one string representing the Pi's | ||||||
|  |         address (e.g. "localhost"). Expander chips can return a tuple appending | ||||||
|  |         whatever string they require to uniquely identify the expander chip | ||||||
|  |         amongst all factories in the system. | ||||||
|  |  | ||||||
|  |         .. note:: | ||||||
|  |  | ||||||
|  |             This property *must* return an immutable object capable of being | ||||||
|  |             used as a dictionary key. | ||||||
|  |         """) | ||||||
|  |  | ||||||
|  |     def _get_pi_info(self): | ||||||
|  |         return None | ||||||
|  |  | ||||||
|  |     pi_info = property( | ||||||
|  |         lambda self: self._get_pi_info(), | ||||||
|  |         doc="""\ | ||||||
|  |         Returns a :class:`PiBoardInfo` instance representing the Pi that | ||||||
|  |         instances generated by this factory 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``. | ||||||
|  |         """) | ||||||
|  |  | ||||||
|  |  | ||||||
| class Pin(object): | class Pin(object): | ||||||
|     """ |     """ | ||||||
|     Abstract base class representing a GPIO pin or a pin from an IO extender. |     Abstract base class representing a pin attached to some form of controller, | ||||||
|  |     be it GPIO, SPI, ADC, etc. | ||||||
|  |  | ||||||
|     Descendents should override property getters and setters to accurately |     Descendents should override property getters and setters to accurately | ||||||
|     represent the capabilities of pins. The following functions *must* be |     represent the capabilities of pins. The following functions *must* be | ||||||
|     overridden: |     overridden: | ||||||
|  |  | ||||||
|  |     * :meth:`_get_address` | ||||||
|     * :meth:`_get_function` |     * :meth:`_get_function` | ||||||
|     * :meth:`_set_function` |     * :meth:`_set_function` | ||||||
|     * :meth:`_get_state` |     * :meth:`_get_state` | ||||||
| @@ -39,6 +133,8 @@ class Pin(object): | |||||||
|     The following functions *may* be overridden if applicable: |     The following functions *may* be overridden if applicable: | ||||||
|  |  | ||||||
|     * :meth:`close` |     * :meth:`close` | ||||||
|  |     * :meth:`output_with_state` | ||||||
|  |     * :meth:`input_with_pull` | ||||||
|     * :meth:`_set_state` |     * :meth:`_set_state` | ||||||
|     * :meth:`_get_frequency` |     * :meth:`_get_frequency` | ||||||
|     * :meth:`_set_frequency` |     * :meth:`_set_frequency` | ||||||
| @@ -50,20 +146,10 @@ class Pin(object): | |||||||
|     * :meth:`_set_edges` |     * :meth:`_set_edges` | ||||||
|     * :meth:`_get_when_changed` |     * :meth:`_get_when_changed` | ||||||
|     * :meth:`_set_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): |     def __repr__(self): | ||||||
|         return "Abstract pin" |         return self.address[-1] | ||||||
|  |  | ||||||
|     def close(self): |     def close(self): | ||||||
|         """ |         """ | ||||||
| @@ -105,6 +191,18 @@ class Pin(object): | |||||||
|         self.function = 'input' |         self.function = 'input' | ||||||
|         self.pull = pull |         self.pull = pull | ||||||
|  |  | ||||||
|  |     def _get_address(self): | ||||||
|  |         raise NotImplementedError | ||||||
|  |  | ||||||
|  |     address = property( | ||||||
|  |         lambda self: self._get_address(), | ||||||
|  |         doc="""\ | ||||||
|  |         The address of the pin. This property is a tuple of strings constructed | ||||||
|  |         from the owning factory's address with the unique address of the pin | ||||||
|  |         appended to it. The tuple as a whole uniquely identifies the pin | ||||||
|  |         amongst all pins attached to the system. | ||||||
|  |         """) | ||||||
|  |  | ||||||
|     def _get_function(self): |     def _get_function(self): | ||||||
|         return "input" |         return "input" | ||||||
|  |  | ||||||
| @@ -140,10 +238,19 @@ class Pin(object): | |||||||
|         doc="""\ |         doc="""\ | ||||||
|         The state of the pin. This is 0 for low, and 1 for high. As a low level |         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 |         view of the pin, no swapping is performed in the case of pull ups (see | ||||||
|         :attr:`pull` for more information). |         :attr:`pull` for more information): | ||||||
|  |  | ||||||
|         If PWM is currently active (when :attr:`frequency` is not ``None``), |         .. code-block:: text | ||||||
|         this represents the PWM duty cycle as a value between 0.0 and 1.0. |  | ||||||
|  |             HIGH - - - - >       ,---------------------- | ||||||
|  |                                  | | ||||||
|  |                                  | | ||||||
|  |             LOW  ----------------' | ||||||
|  |  | ||||||
|  |         Descendents which implement analog, or analog-like capabilities can | ||||||
|  |         return values between 0 and 1. For example, pins implementing PWM | ||||||
|  |         (where :attr:`frequency` is not ``None``) return a value between 0.0 | ||||||
|  |         and 1.0 representing the current PWM duty cycle. | ||||||
|  |  | ||||||
|         If a pin is currently configured for input, and an attempt is made to |         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 |         set this attribute, :exc:`PinSetInput` will be raised. If an invalid | ||||||
| @@ -205,6 +312,26 @@ class Pin(object): | |||||||
|         detection, measured in seconds. If bounce detection is not currently in |         detection, measured in seconds. If bounce detection is not currently in | ||||||
|         use, this is ``None``. |         use, this is ``None``. | ||||||
|  |  | ||||||
|  |         For example, if :attr:`edge` is currently "rising", :attr:`bounce` is | ||||||
|  |         currently 5/1000 (5ms), then the waveform below will only fire | ||||||
|  |         :attr:`when_changed` on two occasions despite there being three rising | ||||||
|  |         edges: | ||||||
|  |  | ||||||
|  |         .. code-block:: text | ||||||
|  |  | ||||||
|  |             TIME 0...1...2...3...4...5...6...7...8...9...10..11..12 ms | ||||||
|  |  | ||||||
|  |             bounce elimination   |===================| |============== | ||||||
|  |  | ||||||
|  |             HIGH - - - - >       ,--. ,--------------. ,--. | ||||||
|  |                                  |  | |              | |  | | ||||||
|  |                                  |  | |              | |  | | ||||||
|  |             LOW  ----------------'  `-'              `-'  `----------- | ||||||
|  |                                  :                     : | ||||||
|  |                                  :                     : | ||||||
|  |                            when_changed          when_changed | ||||||
|  |                                fires                 fires | ||||||
|  |  | ||||||
|         If the pin does not support edge detection, attempts to set this |         If the pin does not support edge detection, attempts to set this | ||||||
|         property will raise :exc:`PinEdgeDetectUnsupported`. If the pin |         property will raise :exc:`PinEdgeDetectUnsupported`. If the pin | ||||||
|         supports edge detection, the class must implement bounce detection, |         supports edge detection, the class must implement bounce detection, | ||||||
| @@ -223,7 +350,18 @@ class Pin(object): | |||||||
|         doc="""\ |         doc="""\ | ||||||
|         The edge that will trigger execution of the function or bound method |         The edge that will trigger execution of the function or bound method | ||||||
|         assigned to :attr:`when_changed`. This can be one of the strings |         assigned to :attr:`when_changed`. This can be one of the strings | ||||||
|         "both" (the default), "rising", "falling", or "none". |         "both" (the default), "rising", "falling", or "none": | ||||||
|  |  | ||||||
|  |         .. code-block:: text | ||||||
|  |  | ||||||
|  |             HIGH - - - - >           ,--------------. | ||||||
|  |                                      |              | | ||||||
|  |                                      |              | | ||||||
|  |             LOW  --------------------'              `-------------- | ||||||
|  |                                      :              : | ||||||
|  |                                      :              : | ||||||
|  |             Fires when_changed     "both"         "both" | ||||||
|  |             when edges is ...     "rising"       "falling" | ||||||
|  |  | ||||||
|         If the pin does not support edge detection, attempts to set this |         If the pin does not support edge detection, attempts to set this | ||||||
|         property will raise :exc:`PinEdgeDetectUnsupported`. |         property will raise :exc:`PinEdgeDetectUnsupported`. | ||||||
| @@ -247,48 +385,300 @@ class Pin(object): | |||||||
|         property will raise :exc:`PinEdgeDetectUnsupported`. |         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 | class SPI(object): | ||||||
|         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 |     Abstract interface for `Serial Peripheral Interface`_ (SPI) implementations. | ||||||
|  |     Descendents *must* override the following: | ||||||
|  |  | ||||||
|  |     * :meth:`transfer` | ||||||
|  |     * :meth:`_get_clock_mode` | ||||||
|  |  | ||||||
|  |     Descendents *may* override the following methods: | ||||||
|  |  | ||||||
|  |     * :meth:`read` | ||||||
|  |     * :meth:`write` | ||||||
|  |     * :meth:`_set_clock_mode` | ||||||
|  |     * :meth:`_get_lsb_first` | ||||||
|  |     * :meth:`_set_lsb_first` | ||||||
|  |     * :meth:`_get_select_high` | ||||||
|  |     * :meth:`_set_select_high` | ||||||
|  |     * :meth:`_get_bits_per_word` | ||||||
|  |     * :meth:`_set_bits_per_word` | ||||||
|  |  | ||||||
|  |     .. _Serial Peripheral Interface: https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def read(self, n): | ||||||
|  |         """ | ||||||
|  |         Read *n* words of data from the SPI interface, returning them as a | ||||||
|  |         sequence of unsigned ints, each no larger than the configured | ||||||
|  |         :attr:`bits_per_word` of the interface. | ||||||
|  |  | ||||||
|  |         This method is typically used with read-only devices that feature | ||||||
|  |         half-duplex communication. See :meth:`transfer` for full duplex | ||||||
|  |         communication. | ||||||
|  |         """ | ||||||
|  |         return self.transfer((0,) * n) | ||||||
|  |  | ||||||
|  |     def write(self, data): | ||||||
|  |         """ | ||||||
|  |         Write *data* to the SPI interface. *data* must be a sequence of | ||||||
|  |         unsigned integer words each of which will fit within the configured | ||||||
|  |         :attr:`bits_per_word` of the interface. The method returns the number | ||||||
|  |         of words written to the interface (which may be less than or equal to | ||||||
|  |         the length of *data*). | ||||||
|  |  | ||||||
|  |         This method is typically used with write-only devices that feature | ||||||
|  |         half-duplex communication. See :meth:`transfer` for full duplex | ||||||
|  |         communication. | ||||||
|  |         """ | ||||||
|  |         return len(self.transfer(data)) | ||||||
|  |  | ||||||
|  |     def transfer(self, data): | ||||||
|  |         """ | ||||||
|  |         Write *data* to the SPI interface. *data* must be a sequence of | ||||||
|  |         unsigned integer words each of which will fit within the configured | ||||||
|  |         :attr:`bits_per_word` of the interface. The method returns the sequence | ||||||
|  |         of words read from the interface while writing occurred (full duplex | ||||||
|  |         communication). | ||||||
|  |  | ||||||
|  |         The length of the sequence returned dictates the number of words of | ||||||
|  |         *data* written to the interface. Each word in the returned sequence | ||||||
|  |         will be an unsigned integer no larger than the configured | ||||||
|  |         :attr:`bits_per_word` of the interface. | ||||||
|  |         """ | ||||||
|  |         raise NotImplementedError | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def clock_polarity(self): | ||||||
|  |         """ | ||||||
|  |         The polarity of the SPI clock pin. If this is ``False`` (the default), | ||||||
|  |         the clock pin will idle low, and pulse high. Setting this to ``True`` | ||||||
|  |         will cause the clock pin to idle high, and pulse low. On many data | ||||||
|  |         sheets this is documented as the CPOL value. | ||||||
|  |  | ||||||
|  |         The following diagram illustrates the waveform when | ||||||
|  |         :attr:`clock_polarity` is ``False`` (the default), equivalent to CPOL | ||||||
|  |         0: | ||||||
|  |  | ||||||
|  |         .. code-block:: text | ||||||
|  |  | ||||||
|  |                    on      on      on      on      on      on      on | ||||||
|  |                   ,---.   ,---.   ,---.   ,---.   ,---.   ,---.   ,---. | ||||||
|  |             CLK   |   |   |   |   |   |   |   |   |   |   |   |   |   | | ||||||
|  |                   |   |   |   |   |   |   |   |   |   |   |   |   |   | | ||||||
|  |             ------'   `---'   `---'   `---'   `---'   `---'   `---'   `------ | ||||||
|  |             idle       off     off     off     off     off     off       idle | ||||||
|  |  | ||||||
|  |         The following diagram illustrates the waveform when | ||||||
|  |         :attr:`clock_polarity` is ``True``, equivalent to CPOL 1: | ||||||
|  |  | ||||||
|  |         .. code-block:: text | ||||||
|  |  | ||||||
|  |             idle       off     off     off     off     off     off       idle | ||||||
|  |             ------.   ,---.   ,---.   ,---.   ,---.   ,---.   ,---.   ,------ | ||||||
|  |                   |   |   |   |   |   |   |   |   |   |   |   |   |   | | ||||||
|  |             CLK   |   |   |   |   |   |   |   |   |   |   |   |   |   | | ||||||
|  |                   `---'   `---'   `---'   `---'   `---'   `---'   `---' | ||||||
|  |                    on      on      on      on      on      on      on | ||||||
|  |         """ | ||||||
|  |         return bool(self.clock_mode & 2) | ||||||
|  |  | ||||||
|  |     @clock_polarity.setter | ||||||
|  |     def clock_polarity(self, value): | ||||||
|  |         self.clock_mode = self.clock_mode & (~2) | (bool(value) << 1) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def clock_phase(self): | ||||||
|  |         """ | ||||||
|  |         The phase of the SPI clock pin. If this is ``False`` (the default), | ||||||
|  |         data will be read from the MISO pin when the clock pin activates. | ||||||
|  |         Setting this to ``True`` will cause data to be read from the MISO pin | ||||||
|  |         when the clock pin deactivates. On many data sheets this is documented | ||||||
|  |         as the CPHA value. Whether the clock edge is rising or falling when the | ||||||
|  |         clock is considered activated is controlled by the | ||||||
|  |         :attr:`clock_polarity` attribute (corresponding to CPOL). | ||||||
|  |  | ||||||
|  |         The following diagram indicates when data is read when | ||||||
|  |         :attr:`clock_polarity` is ``False``, and :attr:`clock_phase` is | ||||||
|  |         ``False`` (the default), equivalent to CPHA 0: | ||||||
|  |  | ||||||
|  |         .. code-block:: text | ||||||
|  |  | ||||||
|  |                 ,---.   ,---.   ,---.   ,---.   ,---.   ,---.   ,---. | ||||||
|  |             CLK |   |   |   |   |   |   |   |   |   |   |   |   |   | | ||||||
|  |                 |   |   |   |   |   |   |   |   |   |   |   |   |   | | ||||||
|  |             ----'   `---'   `---'   `---'   `---'   `---'   `---'   `------- | ||||||
|  |                 :       :       :       :       :       :       : | ||||||
|  |             MISO---.   ,---.   ,---.   ,---.   ,---.   ,---.   ,---. | ||||||
|  |               /     \ /     \ /     \ /     \ /     \ /     \ /     \\ | ||||||
|  |             -{  Bit  X  Bit  X  Bit  X  Bit  X  Bit  X  Bit  X  Bit  }------ | ||||||
|  |               \     / \     / \     / \     / \     / \     / \     / | ||||||
|  |                `---'   `---'   `---'   `---'   `---'   `---'   `---' | ||||||
|  |  | ||||||
|  |         The following diagram indicates when data is read when | ||||||
|  |         :attr:`clock_polarity` is ``False``, but :attr:`clock_phase` is | ||||||
|  |         ``True``, equivalent to CPHA 1: | ||||||
|  |  | ||||||
|  |         .. code-block:: text | ||||||
|  |  | ||||||
|  |                 ,---.   ,---.   ,---.   ,---.   ,---.   ,---.   ,---. | ||||||
|  |             CLK |   |   |   |   |   |   |   |   |   |   |   |   |   | | ||||||
|  |                 |   |   |   |   |   |   |   |   |   |   |   |   |   | | ||||||
|  |             ----'   `---'   `---'   `---'   `---'   `---'   `---'   `------- | ||||||
|  |                     :       :       :       :       :       :       : | ||||||
|  |             MISO   ,---.   ,---.   ,---.   ,---.   ,---.   ,---.   ,---. | ||||||
|  |                   /     \ /     \ /     \ /     \ /     \ /     \ /     \\ | ||||||
|  |             -----{  Bit  X  Bit  X  Bit  X  Bit  X  Bit  X  Bit  X  Bit  }-- | ||||||
|  |                   \     / \     / \     / \     / \     / \     / \     / | ||||||
|  |                    `---'   `---'   `---'   `---'   `---'   `---'   `---' | ||||||
|  |         """ | ||||||
|  |         return bool(self.clock_mode & 1) | ||||||
|  |  | ||||||
|  |     @clock_phase.setter | ||||||
|  |     def clock_phase(self, value): | ||||||
|  |         self.clock_mode = self.clock_mode & (~1) | bool(value) | ||||||
|  |  | ||||||
|  |     def _get_clock_mode(self): | ||||||
|  |         raise NotImplementedError | ||||||
|  |  | ||||||
|  |     def _set_clock_mode(self, value): | ||||||
|  |         raise SPIFixedClockMode("clock_mode cannot be changed on %r" % self) | ||||||
|  |  | ||||||
|  |     clock_mode = property( | ||||||
|  |         lambda self: self._get_clock_mode(), | ||||||
|  |         lambda self, value: self._set_clock_mode(value), | ||||||
|  |         doc="""\ | ||||||
|  |         Presents a value representing the :attr:`clock_polarity` and | ||||||
|  |         :attr:`clock_phase` attributes combined according to the following | ||||||
|  |         table: | ||||||
|  |  | ||||||
|  |         +------+-----------------+--------------+ | ||||||
|  |         | mode | polarity (CPOL) | phase (CPHA) | | ||||||
|  |         +======+=================+==============+ | ||||||
|  |         | 0    | False           | False        | | ||||||
|  |         | 1    | False           | True         | | ||||||
|  |         | 2    | True            | False        | | ||||||
|  |         | 3    | True            | True         | | ||||||
|  |         +------+-----------------+--------------+ | ||||||
|  |  | ||||||
|  |         Adjusting this value adjusts both the :attr:`clock_polarity` and | ||||||
|  |         :attr:`clock_phase` attributes simultaneously. | ||||||
|  |         """) | ||||||
|  |  | ||||||
|  |     def _get_lsb_first(self): | ||||||
|  |         return False | ||||||
|  |  | ||||||
|  |     def _set_lsb_first(self, value): | ||||||
|  |         raise SPIFixedBitOrder("lsb_first cannot be changed on %r" % self) | ||||||
|  |  | ||||||
|  |     lsb_first = property( | ||||||
|  |         lambda self: self._get_lsb_first(), | ||||||
|  |         lambda self, value: self._set_lsb_first(value), | ||||||
|  |         doc="""\ | ||||||
|  |         Controls whether words are read and written LSB in (Least Significant | ||||||
|  |         Bit first) order. The default is ``False`` indicating that words are | ||||||
|  |         read and written in MSB (Most Significant Bit first) order. | ||||||
|  |         Effectively, this controls the `Bit endianness`_ of the connection. | ||||||
|  |  | ||||||
|  |         The following diagram shows the a word containing the number 5 (binary | ||||||
|  |         0101) transmitted on MISO with :attr:`bits_per_word` set to 4, and | ||||||
|  |         :attr:`clock_mode` set to 0, when :attr:`lsb_first` is ``False`` (the | ||||||
|  |         default): | ||||||
|  |  | ||||||
|  |         .. code-block:: text | ||||||
|  |  | ||||||
|  |                 ,---.   ,---.   ,---.   ,---. | ||||||
|  |             CLK |   |   |   |   |   |   |   | | ||||||
|  |                 |   |   |   |   |   |   |   | | ||||||
|  |             ----'   `---'   `---'   `---'   `----- | ||||||
|  |                 :     ,-------. :     ,-------. | ||||||
|  |             MISO:     | :     | :     | :     | | ||||||
|  |                 :     | :     | :     | :     | | ||||||
|  |             ----------' :     `-------' :     `---- | ||||||
|  |                 :       :       :       : | ||||||
|  |                MSB                     LSB | ||||||
|  |  | ||||||
|  |         And now with :attr:`lsb_first` set to ``True`` (and all other | ||||||
|  |         parameters the same): | ||||||
|  |  | ||||||
|  |         .. code-block:: text | ||||||
|  |  | ||||||
|  |                 ,---.   ,---.   ,---.   ,---. | ||||||
|  |             CLK |   |   |   |   |   |   |   | | ||||||
|  |                 |   |   |   |   |   |   |   | | ||||||
|  |             ----'   `---'   `---'   `---'   `----- | ||||||
|  |               ,-------. :     ,-------. : | ||||||
|  |             MISO:     | :     | :     | : | ||||||
|  |               | :     | :     | :     | : | ||||||
|  |             --' :     `-------' :     `----------- | ||||||
|  |                 :       :       :       : | ||||||
|  |                LSB                     MSB | ||||||
|  |  | ||||||
|  |         .. _Bit endianness: https://en.wikipedia.org/wiki/Endianness#Bit_endianness | ||||||
|  |         """) | ||||||
|  |  | ||||||
|  |     def _get_select_high(self): | ||||||
|  |         return False | ||||||
|  |  | ||||||
|  |     def _set_select_high(self, value): | ||||||
|  |         raise SPIFixedSelect("select_high cannot be changed on %r" % self) | ||||||
|  |  | ||||||
|  |     select_high = property( | ||||||
|  |         lambda self: self._get_select_high(), | ||||||
|  |         lambda self, value: self._set_select_high(value), | ||||||
|  |         doc="""\ | ||||||
|  |         If ``False`` (the default), the chip select line is considered active | ||||||
|  |         when it is pulled low. When set to ``True``, the chip select line is | ||||||
|  |         considered active when it is driven high. | ||||||
|  |  | ||||||
|  |         The following diagram shows the waveform of the chip select line, and | ||||||
|  |         the clock when :attr:`clock_polarity` is ``False``, and | ||||||
|  |         :attr:`select_high` is ``False`` (the default): | ||||||
|  |  | ||||||
|  |         .. code-block:: text | ||||||
|  |  | ||||||
|  |             ---.                                                     ,------ | ||||||
|  |             __ |                                                     | | ||||||
|  |             CS |      chip is selected, and will react to clock      |  idle | ||||||
|  |                `-----------------------------------------------------' | ||||||
|  |  | ||||||
|  |                 ,---.   ,---.   ,---.   ,---.   ,---.   ,---.   ,---. | ||||||
|  |             CLK |   |   |   |   |   |   |   |   |   |   |   |   |   | | ||||||
|  |                 |   |   |   |   |   |   |   |   |   |   |   |   |   | | ||||||
|  |             ----'   `---'   `---'   `---'   `---'   `---'   `---'   `------- | ||||||
|  |  | ||||||
|  |         And when :attr:`select_high` is ``True``: | ||||||
|  |  | ||||||
|  |         .. code-block:: text | ||||||
|  |  | ||||||
|  |                ,-----------------------------------------------------. | ||||||
|  |             CS |      chip is selected, and will react to clock      |  idle | ||||||
|  |                |                                                     | | ||||||
|  |             ---'                                                     `------ | ||||||
|  |  | ||||||
|  |                 ,---.   ,---.   ,---.   ,---.   ,---.   ,---.   ,---. | ||||||
|  |             CLK |   |   |   |   |   |   |   |   |   |   |   |   |   | | ||||||
|  |                 |   |   |   |   |   |   |   |   |   |   |   |   |   | | ||||||
|  |             ----'   `---'   `---'   `---'   `---'   `---'   `---'   `------- | ||||||
|  |         """) | ||||||
|  |  | ||||||
|  |     def _get_bits_per_word(self): | ||||||
|  |         return 8 | ||||||
|  |  | ||||||
|  |     def _set_bits_per_word(self, value): | ||||||
|  |         raise SPIFixedWordSize("bits_per_word cannot be changed on %r" % self) | ||||||
|  |  | ||||||
|  |     bits_per_word = property( | ||||||
|  |         lambda self: self._get_bits_per_word(), | ||||||
|  |         lambda self, value: self._set_bits_per_word(value), | ||||||
|  |         doc="""\ | ||||||
|  |         Controls the number of bits that make up a word, and thus where the | ||||||
|  |         word boundaries appear in the data stream, and the maximum value of a | ||||||
|  |         word. Defaults to 8 meaning that words are effectively bytes. | ||||||
|  |  | ||||||
|  |         Several implementations do not support non-byte-sized words. | ||||||
|  |         """) | ||||||
|  |  | ||||||
|  |  | ||||||
| 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) |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ from itertools import cycle | |||||||
| from operator import attrgetter | from operator import attrgetter | ||||||
| from collections import namedtuple | from collections import namedtuple | ||||||
|  |  | ||||||
| from ..exc import PinUnknownPi, PinMultiplePins, PinNoPins | from ..exc import PinUnknownPi, PinMultiplePins, PinNoPins, PinInvalidPin | ||||||
|  |  | ||||||
|  |  | ||||||
| # Some useful constants for describing pins | # Some useful constants for describing pins | ||||||
| @@ -119,8 +119,8 @@ A_BOARD = """\ | |||||||
|  |  | ||||||
| BPLUS_BOARD = """\ | BPLUS_BOARD = """\ | ||||||
| {style:white on green},--------------------------------.{style:reset} | {style:white on green},--------------------------------.{style:reset} | ||||||
| {style:white on green}| {P1:{style} col2}{style:white on green} P1     {style:black on white}+===={style:reset} | {style:white on green}| {J8:{style} col2}{style:white on green} J8     {style:black on white}+===={style:reset} | ||||||
| {style:white on green}| {P1:{style} col1}{style:white on green}        {style:black on white}| USB{style:reset} | {style:white on green}| {J8:{style} col1}{style:white on green}        {style:black on white}| USB{style:reset} | ||||||
| {style:white on green}|                             {style:black on white}+===={style:reset} | {style:white on green}|                             {style:black on white}+===={style:reset} | ||||||
| {style:white on green}|      {style:bold}Pi Model {model:2s} V{pcb_revision:3s}{style:normal}          |{style:reset} | {style:white on green}|      {style:bold}Pi Model {model:2s} V{pcb_revision:3s}{style:normal}          |{style:reset} | ||||||
| {style:white on green}|      {style:on black}+----+{style:on green}                 {style:black on white}+===={style:reset} | {style:white on green}|      {style:on black}+----+{style:on green}                 {style:black on white}+===={style:reset} | ||||||
| @@ -134,8 +134,8 @@ BPLUS_BOARD = """\ | |||||||
|  |  | ||||||
| APLUS_BOARD = """\ | APLUS_BOARD = """\ | ||||||
| {style:white on green},--------------------------.{style:reset} | {style:white on green},--------------------------.{style:reset} | ||||||
| {style:white on green}| {P1:{style} col2}{style:white on green} P1  |{style:reset} | {style:white on green}| {J8:{style} col2}{style:white on green} J8  |{style:reset} | ||||||
| {style:white on green}| {P1:{style} col1}{style:white on green}     |{style:reset} | {style:white on green}| {J8:{style} col1}{style:white on green}     |{style:reset} | ||||||
| {style:white on green}|                          |{style:reset} | {style:white on green}|                          |{style:reset} | ||||||
| {style:white on green}|      {style:bold}Pi Model {model:2s} V{pcb_revision:3s}{style:normal}    |{style:reset} | {style:white on green}|      {style:bold}Pi Model {model:2s} V{pcb_revision:3s}{style:normal}    |{style:reset} | ||||||
| {style:white on green}|      {style:on black}+----+{style:on green}           {style:black on white}+===={style:reset} | {style:white on green}|      {style:on black}+----+{style:on green}           {style:black on white}+===={style:reset} | ||||||
| @@ -149,8 +149,8 @@ APLUS_BOARD = """\ | |||||||
|  |  | ||||||
| ZERO12_BOARD = """\ | ZERO12_BOARD = """\ | ||||||
| {style:white on green},-------------------------.{style:reset} | {style:white on green},-------------------------.{style:reset} | ||||||
| {style:white on green}| {P1:{style} col2}{style:white on green} P1 |{style:reset} | {style:white on green}| {J8:{style} col2}{style:white on green} J8 |{style:reset} | ||||||
| {style:white on green}| {P1:{style} col1}{style:white on green}    |{style:reset} | {style:white on green}| {J8:{style} col1}{style:white on green}    |{style:reset} | ||||||
| {style:black on white}---+{style:white on green}       {style:on black}+---+{style:on green}  {style:bold}PiZero{style:normal}  |{style:reset} | {style:black on white}---+{style:white on green}       {style:on black}+---+{style:on green}  {style:bold}PiZero{style:normal}  |{style:reset} | ||||||
| {style:black on white} sd|{style:white on green}       {style:on black}|SoC|{style:on green}   {style:bold}V{pcb_revision:3s}{style:normal}   |{style:reset} | {style:black on white} sd|{style:white on green}       {style:on black}|SoC|{style:on green}   {style:bold}V{pcb_revision:3s}{style:normal}   |{style:reset} | ||||||
| {style:black on white}---+|hdmi|{style:white on green} {style:on black}+---+{style:on green}  {style:black on white}usb{style:on green} {style:black on white}pwr{style:white on green} |{style:reset} | {style:black on white}---+|hdmi|{style:white on green} {style:on black}+---+{style:on green}  {style:black on white}usb{style:on green} {style:black on white}pwr{style:white on green} |{style:reset} | ||||||
| @@ -158,8 +158,8 @@ ZERO12_BOARD = """\ | |||||||
|  |  | ||||||
| ZERO13_BOARD = """\ | ZERO13_BOARD = """\ | ||||||
| {style:white on green}.-------------------------.{style:reset} | {style:white on green}.-------------------------.{style:reset} | ||||||
| {style:white on green}| {P1:{style} col2}{style:white on green} P1 |{style:reset} | {style:white on green}| {J8:{style} col2}{style:white on green} J8 |{style:reset} | ||||||
| {style:white on green}| {P1:{style} col1}{style:white on green}   {style:black on white}|c{style:reset} | {style:white on green}| {J8:{style} col1}{style:white on green}   {style:black on white}|c{style:reset} | ||||||
| {style:black on white}---+{style:white on green}       {style:on black}+---+{style:on green} {style:bold}Pi{model:6s}{style:normal}{style:black on white}|s{style:reset} | {style:black on white}---+{style:white on green}       {style:on black}+---+{style:on green} {style:bold}Pi{model:6s}{style:normal}{style:black on white}|s{style:reset} | ||||||
| {style:black on white} sd|{style:white on green}       {style:on black}|SoC|{style:on green}   {style:bold}V{pcb_revision:3s}{style:normal}  {style:black on white}|i{style:reset} | {style:black on white} sd|{style:white on green}       {style:on black}|SoC|{style:on green}   {style:bold}V{pcb_revision:3s}{style:normal}  {style:black on white}|i{style:reset} | ||||||
| {style:black on white}---+|hdmi|{style:white on green} {style:on black}+---+{style:on green}  {style:black on white}usb{style:on green} {style:on white}pwr{style:white on green} |{style:reset} | {style:black on white}---+|hdmi|{style:white on green} {style:on black}+---+{style:on green}  {style:black on white}usb{style:on green} {style:on white}pwr{style:white on green} |{style:reset} | ||||||
| @@ -216,7 +216,7 @@ REV2_P5 = { | |||||||
|     7:  (GND,    False), 8:  (GND,    False), |     7:  (GND,    False), 8:  (GND,    False), | ||||||
|     } |     } | ||||||
|  |  | ||||||
| PLUS_P1 = { | PLUS_J8 = { | ||||||
|     1:  (V3_3,   False), 2:  (V5,     False), |     1:  (V3_3,   False), 2:  (V5,     False), | ||||||
|     3:  (GPIO2,  True),  4:  (V5,     False), |     3:  (GPIO2,  True),  4:  (V5,     False), | ||||||
|     5:  (GPIO3,  True),  6:  (GND,    False), |     5:  (GPIO3,  True),  6:  (GND,    False), | ||||||
| @@ -379,12 +379,12 @@ PI_REVISIONS = { | |||||||
|     0xd:      ('B',    '2.0', '2012Q4', 'BCM2835', 'Egoman',    512,  'SD',      2,  1,  False, False, 1,  1,  {'P1': REV2_P1, 'P5': REV2_P5}, REV2_BOARD,   ), |     0xd:      ('B',    '2.0', '2012Q4', 'BCM2835', 'Egoman',    512,  'SD',      2,  1,  False, False, 1,  1,  {'P1': REV2_P1, 'P5': REV2_P5}, REV2_BOARD,   ), | ||||||
|     0xe:      ('B',    '2.0', '2012Q4', 'BCM2835', 'Sony',      512,  'SD',      2,  1,  False, False, 1,  1,  {'P1': REV2_P1, 'P5': REV2_P5}, REV2_BOARD,   ), |     0xe:      ('B',    '2.0', '2012Q4', 'BCM2835', 'Sony',      512,  'SD',      2,  1,  False, False, 1,  1,  {'P1': REV2_P1, 'P5': REV2_P5}, REV2_BOARD,   ), | ||||||
|     0xf:      ('B',    '2.0', '2012Q4', 'BCM2835', 'Qisda',     512,  'SD',      2,  1,  False, False, 1,  1,  {'P1': REV2_P1, 'P5': REV2_P5}, REV2_BOARD,   ), |     0xf:      ('B',    '2.0', '2012Q4', 'BCM2835', 'Qisda',     512,  'SD',      2,  1,  False, False, 1,  1,  {'P1': REV2_P1, 'P5': REV2_P5}, REV2_BOARD,   ), | ||||||
|     0x10:     ('B+',   '1.2', '2014Q3', 'BCM2835', 'Sony',      512,  'MicroSD', 4,  1,  False, False, 1,  1,  {'P1': PLUS_P1},                BPLUS_BOARD,  ), |     0x10:     ('B+',   '1.2', '2014Q3', 'BCM2835', 'Sony',      512,  'MicroSD', 4,  1,  False, False, 1,  1,  {'J8': PLUS_J8},                BPLUS_BOARD,  ), | ||||||
|     0x11:     ('CM',   '1.1', '2014Q2', 'BCM2835', 'Sony',      512,  'eMMC',    1,  0,  False, False, 2,  2,  {'SODIMM': CM_SODIMM},          CM_BOARD,     ), |     0x11:     ('CM',   '1.1', '2014Q2', 'BCM2835', 'Sony',      512,  'eMMC',    1,  0,  False, False, 2,  2,  {'SODIMM': CM_SODIMM},          CM_BOARD,     ), | ||||||
|     0x12:     ('A+',   '1.1', '2014Q4', 'BCM2835', 'Sony',      256,  'MicroSD', 1,  0,  False, False, 1,  1,  {'P1': PLUS_P1},                APLUS_BOARD,  ), |     0x12:     ('A+',   '1.1', '2014Q4', 'BCM2835', 'Sony',      256,  'MicroSD', 1,  0,  False, False, 1,  1,  {'J8': PLUS_J8},                APLUS_BOARD,  ), | ||||||
|     0x13:     ('B+',   '1.2', '2015Q1', 'BCM2835', 'Egoman',    512,  'MicroSD', 4,  1,  False, False, 1,  1,  {'P1': PLUS_P1},                BPLUS_BOARD,  ), |     0x13:     ('B+',   '1.2', '2015Q1', 'BCM2835', 'Egoman',    512,  'MicroSD', 4,  1,  False, False, 1,  1,  {'J8': PLUS_J8},                BPLUS_BOARD,  ), | ||||||
|     0x14:     ('CM',   '1.1', '2014Q2', 'BCM2835', 'Embest',    512,  'eMMC',    1,  0,  False, False, 2,  2,  {'SODIMM': CM_SODIMM},          CM_BOARD,     ), |     0x14:     ('CM',   '1.1', '2014Q2', 'BCM2835', 'Embest',    512,  'eMMC',    1,  0,  False, False, 2,  2,  {'SODIMM': CM_SODIMM},          CM_BOARD,     ), | ||||||
|     0x15:     ('A+',   '1.1', '2014Q4', 'BCM2835', 'Embest',    256,  'MicroSD', 1,  0,  False, False, 1,  1,  {'P1': PLUS_P1},                APLUS_BOARD,  ), |     0x15:     ('A+',   '1.1', '2014Q4', 'BCM2835', 'Embest',    256,  'MicroSD', 1,  0,  False, False, 1,  1,  {'J8': PLUS_J8},                APLUS_BOARD,  ), | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -529,7 +529,8 @@ class HeaderInfo(namedtuple('HeaderInfo', ( | |||||||
|  |  | ||||||
|         from gpiozero import * |         from gpiozero import * | ||||||
|  |  | ||||||
|         print('{0:full}'.format(pi_info().headers['P1'])) |         print('{0}'.format(pi_info().headers['J8'])) | ||||||
|  |         print('{0:full}'.format(pi_info().headers['J8'])) | ||||||
|         print('{0:col2}'.format(pi_info().headers['P1'])) |         print('{0:col2}'.format(pi_info().headers['P1'])) | ||||||
|         print('{0:row1}'.format(pi_info().headers['P1'])) |         print('{0:row1}'.format(pi_info().headers['P1'])) | ||||||
|  |  | ||||||
| @@ -537,10 +538,9 @@ class HeaderInfo(namedtuple('HeaderInfo', ( | |||||||
|     the use of `ANSI color codes`_. If neither is specified, ANSI codes will |     the use of `ANSI color codes`_. If neither is specified, ANSI codes will | ||||||
|     only be used if stdout is detected to be a tty:: |     only be used if stdout is detected to be a tty:: | ||||||
|  |  | ||||||
|         print('{0:color row2}'.format(pi_info().headers['P1'])) # force use of ANSI codes |         print('{0:color row2}'.format(pi_info().headers['J8'])) # force use of ANSI codes | ||||||
|         print('{0:mono row2}'.format(pi_info().headers['P1'])) # force plain ASCII |         print('{0:mono row2}'.format(pi_info().headers['P1'])) # force plain ASCII | ||||||
|  |  | ||||||
|     .. _ANSI color codes: https://en.wikipedia.org/wiki/ANSI_escape_code |  | ||||||
|     The following attributes are defined: |     The following attributes are defined: | ||||||
|  |  | ||||||
|     .. automethod:: pprint |     .. automethod:: pprint | ||||||
| @@ -548,7 +548,7 @@ class HeaderInfo(namedtuple('HeaderInfo', ( | |||||||
|     .. attribute:: name |     .. attribute:: name | ||||||
|  |  | ||||||
|         The name of the header, typically as it appears silk-screened on the |         The name of the header, typically as it appears silk-screened on the | ||||||
|         board (e.g. "P1"). |         board (e.g. "P1" or "J8"). | ||||||
|  |  | ||||||
|     .. attribute:: rows |     .. attribute:: rows | ||||||
|  |  | ||||||
| @@ -561,6 +561,8 @@ class HeaderInfo(namedtuple('HeaderInfo', ( | |||||||
|     .. attribute:: pins |     .. attribute:: pins | ||||||
|  |  | ||||||
|         A dictionary mapping physical pin numbers to :class:`PinInfo` tuples. |         A dictionary mapping physical pin numbers to :class:`PinInfo` tuples. | ||||||
|  |  | ||||||
|  |     .. _ANSI color codes: https://en.wikipedia.org/wiki/ANSI_escape_code | ||||||
|     """ |     """ | ||||||
|     __slots__ = () # workaround python issue #24931 |     __slots__ = () # workaround python issue #24931 | ||||||
|  |  | ||||||
| @@ -685,6 +687,7 @@ class PiBoardInfo(namedtuple('PiBoardInfo', ( | |||||||
|  |  | ||||||
|         from gpiozero import * |         from gpiozero import * | ||||||
|  |  | ||||||
|  |         print('{0}'.format(pi_info())) | ||||||
|         print('{0:full}'.format(pi_info())) |         print('{0:full}'.format(pi_info())) | ||||||
|         print('{0:board}'.format(pi_info())) |         print('{0:board}'.format(pi_info())) | ||||||
|         print('{0:specs}'.format(pi_info())) |         print('{0:specs}'.format(pi_info())) | ||||||
| @@ -801,8 +804,8 @@ class PiBoardInfo(namedtuple('PiBoardInfo', ( | |||||||
|  |  | ||||||
|         A dictionary which maps header labels to :class:`HeaderInfo` tuples. |         A dictionary which maps header labels to :class:`HeaderInfo` tuples. | ||||||
|         For example, to obtain information about header P1 you would query |         For example, to obtain information about header P1 you would query | ||||||
|         ``headers['P1']``. To obtain information about pin 12 on header P1 you |         ``headers['P1']``. To obtain information about pin 12 on header J8 you | ||||||
|         would query ``headers['P1'].pins[12]``. |         would query ``headers['J8'].pins[12]``. | ||||||
|  |  | ||||||
|         A rendered version of this data can be obtained by using the |         A rendered version of this data can be obtained by using the | ||||||
|         :class:`PiBoardInfo` object in a format string:: |         :class:`PiBoardInfo` object in a format string:: | ||||||
| @@ -937,10 +940,10 @@ class PiBoardInfo(namedtuple('PiBoardInfo', ( | |||||||
|                     }.get(model, csi) |                     }.get(model, csi) | ||||||
|                 headers = { |                 headers = { | ||||||
|                     'A':   {'P1': REV2_P1, 'P5': REV2_P5}, |                     'A':   {'P1': REV2_P1, 'P5': REV2_P5}, | ||||||
|                     'B':   {'P1': REV2_P1, 'P5': REV2_P5} if pcb_revision == '2.0' else {'P1': REV1_P1}, |                     'B':   {'P1': REV1_P1} if pcb_revision == '1.0' else {'P1': REV2_P1, 'P5': REV2_P5}, | ||||||
|                     'CM':  {'SODIMM': CM_SODIMM}, |                     'CM':  {'SODIMM': CM_SODIMM}, | ||||||
|                     'CM3': {'SODIMM': CM3_SODIMM}, |                     'CM3': {'SODIMM': CM3_SODIMM}, | ||||||
|                     }.get(model, {'P1': PLUS_P1}) |                     }.get(model, {'J8': PLUS_J8}) | ||||||
|                 board = { |                 board = { | ||||||
|                     'A':      A_BOARD, |                     'A':      A_BOARD, | ||||||
|                     'B':      REV1_BOARD if pcb_revision == '1.0' else REV2_BOARD, |                     'B':      REV1_BOARD if pcb_revision == '1.0' else REV2_BOARD, | ||||||
| @@ -1115,8 +1118,8 @@ class PiBoardInfo(namedtuple('PiBoardInfo', ( | |||||||
|         """ |         """ | ||||||
|         Pretty-print a representation of the board along with header diagrams. |         Pretty-print a representation of the board along with header diagrams. | ||||||
|  |  | ||||||
|         If *color* is ``None`` (the default, the diagram will include ANSI |         If *color* is ``None`` (the default), the diagram will include ANSI | ||||||
|         color codes if stdout is a color-capable terminal). Otherwise *color* |         color codes if stdout is a color-capable terminal. Otherwise *color* | ||||||
|         can be set to ``True`` or ``False`` to force color or monochrome |         can be set to ``True`` or ``False`` to force color or monochrome | ||||||
|         output. |         output. | ||||||
|         """ |         """ | ||||||
| @@ -1134,13 +1137,10 @@ def pi_info(revision=None): | |||||||
|         the model of Pi it is running on and return information about that. |         the model of Pi it is running on and return information about that. | ||||||
|     """ |     """ | ||||||
|     if revision is None: |     if revision is None: | ||||||
|         # NOTE: This import is declared locally for two reasons. Firstly it |         # The reason this import is located here is to avoid a circular | ||||||
|         # avoids a circular dependency (devices->pins->pins.data->devices). |         # dependency; devices->pins.local->pins.data->devices | ||||||
|         # Secondly, pin_factory is one global which might potentially be |         from ..devices import Device | ||||||
|         # re-written by a user's script at runtime hence we should re-import |         result = Device._pin_factory.pi_info | ||||||
|         # here in case it's changed since initialization |  | ||||||
|         from ..devices import pin_factory |  | ||||||
|         result = pin_factory.pi_info() |  | ||||||
|         if result is None: |         if result is None: | ||||||
|             raise PinUnknownPi('The default pin_factory is not attached to a Pi') |             raise PinUnknownPi('The default pin_factory is not attached to a Pi') | ||||||
|         else: |         else: | ||||||
|   | |||||||
							
								
								
									
										241
									
								
								gpiozero/pins/local.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								gpiozero/pins/local.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,241 @@ | |||||||
|  | from __future__ import ( | ||||||
|  |     unicode_literals, | ||||||
|  |     absolute_import, | ||||||
|  |     print_function, | ||||||
|  |     division, | ||||||
|  |     ) | ||||||
|  | str = type('') | ||||||
|  |  | ||||||
|  | import io | ||||||
|  | import weakref | ||||||
|  | import warnings | ||||||
|  |  | ||||||
|  | try: | ||||||
|  |     from spidev import SpiDev | ||||||
|  | except ImportError: | ||||||
|  |     SpiDev = None | ||||||
|  |  | ||||||
|  | from . import SPI | ||||||
|  | from .pi import PiFactory, PiPin | ||||||
|  | from .spi import SPISoftwareBus | ||||||
|  | from ..devices import Device, SharedMixin | ||||||
|  | from ..output_devices import OutputDevice | ||||||
|  | from ..exc import DeviceClosed, PinUnknownPi, SPIInvalidClockMode | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LocalPiFactory(PiFactory): | ||||||
|  |     """ | ||||||
|  |     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`). | ||||||
|  |     """ | ||||||
|  |     pins = {} | ||||||
|  |  | ||||||
|  |     def __init__(self): | ||||||
|  |         super(LocalPiFactory, self).__init__() | ||||||
|  |         self.spi_hardware_class = LocalPiHardwareSPI | ||||||
|  |         self.spi_software_class = LocalPiSoftwareSPI | ||||||
|  |         self.shared_spi_hardware_class = LocalPiHardwareSPIShared | ||||||
|  |         self.shared_spi_software_class = LocalPiSoftwareSPIShared | ||||||
|  |         # Override the pins dict to be this class' pins dict. This is a bit of | ||||||
|  |         # a dirty hack, but ensures that anyone evil enough to mix pin | ||||||
|  |         # implementations doesn't try and control the same pin with different | ||||||
|  |         # backends | ||||||
|  |         self.pins = LocalPiFactory.pins | ||||||
|  |  | ||||||
|  |     def _get_address(self): | ||||||
|  |         return ('localhost',) | ||||||
|  |  | ||||||
|  |     def _get_revision(self): | ||||||
|  |         # 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) | ||||||
|  |         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:] | ||||||
|  |                     return revision | ||||||
|  |         raise PinUnknownPi('unable to locate Pi revision in /proc/cpuinfo') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LocalPiPin(PiPin): | ||||||
|  |     """ | ||||||
|  |     Abstract base class representing a multi-function GPIO pin attached to the | ||||||
|  |     local Raspberry Pi. | ||||||
|  |     """ | ||||||
|  |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LocalPiHardwareSPI(SPI, Device): | ||||||
|  |     def __init__(self, factory, port, device): | ||||||
|  |         if SpiDev is None: | ||||||
|  |             raise ImportError('failed to import spidev') | ||||||
|  |         self._port = port | ||||||
|  |         self._device = device | ||||||
|  |         self._intf = None | ||||||
|  |         self._address = factory.address + ('SPI(port=%d, device=%d)' % (port, device),) | ||||||
|  |         super(LocalPiHardwareSPI, self).__init__() | ||||||
|  |         self._reserve_pins( | ||||||
|  |             factory.pin_address(11), | ||||||
|  |             factory.pin_address(10), | ||||||
|  |             factory.pin_address(9), | ||||||
|  |             factory.pin_address((8, 7)[device]) | ||||||
|  |             ) | ||||||
|  |         self._intf = SpiDev() | ||||||
|  |         self._intf.open(port, device) | ||||||
|  |         self._intf.max_speed_hz = 500000 | ||||||
|  |  | ||||||
|  |     def _conflicts_with(self, other): | ||||||
|  |         return not ( | ||||||
|  |             isinstance(other, LocalPiHardwareSPI) and | ||||||
|  |             (self._port, self._device) != (other._port, other._device) | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |     def close(self): | ||||||
|  |         if self._intf: | ||||||
|  |             try: | ||||||
|  |                 self._intf.close() | ||||||
|  |             finally: | ||||||
|  |                 self._intf = None | ||||||
|  |         self._release_all() | ||||||
|  |         super(LocalPiHardwareSPI, self).close() | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def closed(self): | ||||||
|  |         return self._intf is None | ||||||
|  |  | ||||||
|  |     def __repr__(self): | ||||||
|  |         try: | ||||||
|  |             self._check_open() | ||||||
|  |             return 'SPI(port=%d, device=%d)' % (self._port, self._device) | ||||||
|  |         except DeviceClosed: | ||||||
|  |             return 'SPI(closed)' | ||||||
|  |  | ||||||
|  |     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._intf.xfer2(data) | ||||||
|  |  | ||||||
|  |     def _get_clock_mode(self): | ||||||
|  |         return self._intf.mode | ||||||
|  |  | ||||||
|  |     def _set_clock_mode(self, value): | ||||||
|  |         self._intf.mode = value | ||||||
|  |  | ||||||
|  |     def _get_lsb_first(self): | ||||||
|  |         return self._intf.lsbfirst | ||||||
|  |  | ||||||
|  |     def _set_lsb_first(self, value): | ||||||
|  |         self._intf.lsbfirst = bool(value) | ||||||
|  |  | ||||||
|  |     def _get_select_high(self): | ||||||
|  |         return self._intf.cshigh | ||||||
|  |  | ||||||
|  |     def _set_select_high(self, value): | ||||||
|  |         self._intf.cshigh = bool(value) | ||||||
|  |  | ||||||
|  |     def _get_bits_per_word(self): | ||||||
|  |         return self._intf.bits_per_word | ||||||
|  |  | ||||||
|  |     def _set_bits_per_word(self, value): | ||||||
|  |         self._intf.bits_per_word = value | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LocalPiSoftwareSPI(SPI, OutputDevice): | ||||||
|  |     def __init__(self, factory, clock_pin, mosi_pin, miso_pin, select_pin): | ||||||
|  |         self._bus = None | ||||||
|  |         self._address = factory.address + ( | ||||||
|  |             'SPI(clock_pin=%d, mosi_pin=%d, miso_pin=%d, select_pin=%d)' % ( | ||||||
|  |             clock_pin, mosi_pin, miso_pin, select_pin), | ||||||
|  |             ) | ||||||
|  |         super(LocalPiSoftwareSPI, self).__init__(select_pin, active_high=False) | ||||||
|  |         try: | ||||||
|  |             self._clock_phase = False | ||||||
|  |             self._lsb_first = False | ||||||
|  |             self._bits_per_word = 8 | ||||||
|  |             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(LocalPiSoftwareSPI, self).close() | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def closed(self): | ||||||
|  |         return self._bus is None | ||||||
|  |  | ||||||
|  |     def __repr__(self): | ||||||
|  |         try: | ||||||
|  |             self._check_open() | ||||||
|  |             return 'SPI(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 'SPI(closed)' | ||||||
|  |  | ||||||
|  |     def transfer(self, data): | ||||||
|  |         with self._bus.lock: | ||||||
|  |             self.on() | ||||||
|  |             try: | ||||||
|  |                 return self._bus.transfer( | ||||||
|  |                     data, self._clock_phase, self._lsb_first, self._bits_per_word) | ||||||
|  |             finally: | ||||||
|  |                 self.off() | ||||||
|  |  | ||||||
|  |     def _get_clock_mode(self): | ||||||
|  |         with self._bus.lock: | ||||||
|  |             return (not self._bus.clock.active_high) << 1 | self._clock_phase | ||||||
|  |  | ||||||
|  |     def _set_clock_mode(self, value): | ||||||
|  |         if not (0 <= value < 4): | ||||||
|  |             raise SPIInvalidClockMode("%d is not a valid clock mode" % value) | ||||||
|  |         with self._bus.lock: | ||||||
|  |             self._bus.clock.active_high = not (value & 2) | ||||||
|  |             self._clock_phase = bool(value & 1) | ||||||
|  |  | ||||||
|  |     def _get_lsb_first(self): | ||||||
|  |         return self._lsb_first | ||||||
|  |  | ||||||
|  |     def _set_lsb_first(self, value): | ||||||
|  |         self._lsb_first = bool(value) | ||||||
|  |  | ||||||
|  |     def _get_bits_per_word(self): | ||||||
|  |         return self._bits_per_word | ||||||
|  |  | ||||||
|  |     def _set_bits_per_word(self, value): | ||||||
|  |         if value < 1: | ||||||
|  |             raise ValueError('bits_per_word must be positive') | ||||||
|  |         self._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() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LocalPiHardwareSPIShared(SharedMixin, LocalPiHardwareSPI): | ||||||
|  |     @classmethod | ||||||
|  |     def _shared_key(cls, factory, port, device): | ||||||
|  |         return (port, device) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LocalPiSoftwareSPIShared(SharedMixin, LocalPiSoftwareSPI): | ||||||
|  |     @classmethod | ||||||
|  |     def _shared_key(cls, factory, clock_pin, mosi_pin, miso_pin, select_pin): | ||||||
|  |         return (select_pin,) | ||||||
|  |  | ||||||
| @@ -15,37 +15,21 @@ try: | |||||||
| except ImportError: | except ImportError: | ||||||
|     from ..compat import isclose |     from ..compat import isclose | ||||||
|  |  | ||||||
| from . import Pin | from ..exc import PinPWMUnsupported, PinSetInput, PinFixedPull | ||||||
| from .data import pi_info | from ..devices import Device | ||||||
| from ..exc import PinSetInput, PinPWMUnsupported, PinFixedPull | from .pi import PiPin | ||||||
|  | from .local import LocalPiFactory | ||||||
|  |  | ||||||
|  |  | ||||||
| PinState = namedtuple('PinState', ('timestamp', 'state')) | PinState = namedtuple('PinState', ('timestamp', 'state')) | ||||||
|  |  | ||||||
| class MockPin(Pin): | class MockPin(PiPin): | ||||||
|     """ |     """ | ||||||
|     A mock pin used primarily for testing. This class does *not* support PWM. |     A mock pin used primarily for testing. This class does *not* support PWM. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     _PINS = {} |     def __init__(self, factory, number): | ||||||
|  |         super(MockPin, self).__init__(factory, number) | ||||||
|     @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._function = 'input' | ||||||
|         self._state = False |         self._state = False | ||||||
|         self._pull = 'floating' |         self._pull = 'floating' | ||||||
| @@ -53,18 +37,6 @@ class MockPin(Pin): | |||||||
|         self._edges = 'both' |         self._edges = 'both' | ||||||
|         self._when_changed = None |         self._when_changed = None | ||||||
|         self.clear_states() |         self.clear_states() | ||||||
|             return self |  | ||||||
|         # Ensure the pin class expected supports PWM (or not) |  | ||||||
|         if issubclass(cls, MockPWMPin) != isinstance(old_pin, MockPWMPin): |  | ||||||
|             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): |     def close(self): | ||||||
|         self.when_changed = None |         self.when_changed = None | ||||||
| @@ -186,8 +158,8 @@ class MockChargingPin(MockPin): | |||||||
|     (as if attached to, e.g. a typical circuit using an LDR and a capacitor |     (as if attached to, e.g. a typical circuit using an LDR and a capacitor | ||||||
|     to time the charging rate). |     to time the charging rate). | ||||||
|     """ |     """ | ||||||
|     def __init__(self, number): |     def __init__(self, factory, number): | ||||||
|         super(MockChargingPin, self).__init__() |         super(MockChargingPin, self).__init__(factory, number) | ||||||
|         self.charge_time = 0.01 # dark charging time |         self.charge_time = 0.01 # dark charging time | ||||||
|         self._charge_stop = Event() |         self._charge_stop = Event() | ||||||
|         self._charge_thread = None |         self._charge_thread = None | ||||||
| @@ -225,8 +197,8 @@ class MockTriggerPin(MockPin): | |||||||
|     corresponding pin instance. When this pin is driven high it will trigger |     corresponding pin instance. When this pin is driven high it will trigger | ||||||
|     the echo pin to drive high for the echo time. |     the echo pin to drive high for the echo time. | ||||||
|     """ |     """ | ||||||
|     def __init__(self, number): |     def __init__(self, factory, number): | ||||||
|         super(MockTriggerPin, self).__init__() |         super(MockTriggerPin, self).__init__(factory, number) | ||||||
|         self.echo_pin = None |         self.echo_pin = None | ||||||
|         self.echo_time = 0.04 # longest echo time |         self.echo_time = 0.04 # longest echo time | ||||||
|         self._echo_thread = None |         self._echo_thread = None | ||||||
| @@ -250,8 +222,8 @@ class MockPWMPin(MockPin): | |||||||
|     """ |     """ | ||||||
|     This derivative of :class:`MockPin` adds PWM support. |     This derivative of :class:`MockPin` adds PWM support. | ||||||
|     """ |     """ | ||||||
|     def __init__(self, number): |     def __init__(self, factory, number): | ||||||
|         super(MockPWMPin, self).__init__() |         super(MockPWMPin, self).__init__(factory, number) | ||||||
|         self._frequency = None |         self._frequency = None | ||||||
|  |  | ||||||
|     def close(self): |     def close(self): | ||||||
| @@ -283,8 +255,8 @@ class MockSPIClockPin(MockPin): | |||||||
|     rather, construct a :class:`MockSPIDevice` with various pin numbers, and |     rather, construct a :class:`MockSPIDevice` with various pin numbers, and | ||||||
|     this class will be used for the clock pin. |     this class will be used for the clock pin. | ||||||
|     """ |     """ | ||||||
|     def __init__(self, number): |     def __init__(self, factory, number): | ||||||
|         super(MockSPIClockPin, self).__init__() |         super(MockSPIClockPin, self).__init__(factory, number) | ||||||
|         if not hasattr(self, 'spi_devices'): |         if not hasattr(self, 'spi_devices'): | ||||||
|             self.spi_devices = [] |             self.spi_devices = [] | ||||||
|  |  | ||||||
| @@ -301,8 +273,8 @@ class MockSPISelectPin(MockPin): | |||||||
|     tests; rather, construct a :class:`MockSPIDevice` with various pin numbers, |     tests; rather, construct a :class:`MockSPIDevice` with various pin numbers, | ||||||
|     and this class will be used for the select pin. |     and this class will be used for the select pin. | ||||||
|     """ |     """ | ||||||
|     def __init__(self, number): |     def __init__(self, factory, number): | ||||||
|         super(MockSPISelectPin, self).__init__() |         super(MockSPISelectPin, self).__init__(factory, number) | ||||||
|         if not hasattr(self, 'spi_device'): |         if not hasattr(self, 'spi_device'): | ||||||
|             self.spi_device = None |             self.spi_device = None | ||||||
|  |  | ||||||
| @@ -314,13 +286,13 @@ class MockSPISelectPin(MockPin): | |||||||
|  |  | ||||||
| class MockSPIDevice(object): | class MockSPIDevice(object): | ||||||
|     def __init__( |     def __init__( | ||||||
|             self, clock_pin, mosi_pin, miso_pin, select_pin=None, |             self, clock_pin, mosi_pin=None, miso_pin=None, select_pin=None, | ||||||
|             clock_polarity=False, clock_phase=False, lsb_first=False, |             clock_polarity=False, clock_phase=False, lsb_first=False, | ||||||
|             bits_per_word=8, select_high=False): |             bits_per_word=8, select_high=False): | ||||||
|         self.clock_pin = MockSPIClockPin(clock_pin) |         self.clock_pin = Device._pin_factory.pin(clock_pin, pin_class=MockSPIClockPin) | ||||||
|         self.mosi_pin = None if mosi_pin is None else MockPin(mosi_pin) |         self.mosi_pin = None if mosi_pin is None else Device._pin_factory.pin(mosi_pin) | ||||||
|         self.miso_pin = None if miso_pin is None else MockPin(miso_pin) |         self.miso_pin = None if miso_pin is None else Device._pin_factory.pin(miso_pin) | ||||||
|         self.select_pin = None if select_pin is None else MockSPISelectPin(select_pin) |         self.select_pin = None if select_pin is None else Device._pin_factory.pin(select_pin, pin_class=MockSPISelectPin) | ||||||
|         self.clock_polarity = clock_polarity |         self.clock_polarity = clock_polarity | ||||||
|         self.clock_phase = clock_phase |         self.clock_phase = clock_phase | ||||||
|         self.lsb_first = lsb_first |         self.lsb_first = lsb_first | ||||||
| @@ -413,3 +385,34 @@ class MockSPIDevice(object): | |||||||
|             bits = reversed(bits) |             bits = reversed(bits) | ||||||
|         self.tx_buf.extend(bits) |         self.tx_buf.extend(bits) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class MockFactory(LocalPiFactory): | ||||||
|  |     def __init__(self, revision='a21041', pin_class=MockPin): | ||||||
|  |         super(MockFactory, self).__init__() | ||||||
|  |         self._revision = revision | ||||||
|  |         self.pin_class = pin_class | ||||||
|  |  | ||||||
|  |     def _get_address(self): | ||||||
|  |         return ('mock',) | ||||||
|  |  | ||||||
|  |     def _get_revision(self): | ||||||
|  |         return self._revision | ||||||
|  |  | ||||||
|  |     def reset(self): | ||||||
|  |         self.pins.clear() | ||||||
|  |  | ||||||
|  |     def pin(self, spec, pin_class=None): | ||||||
|  |         if pin_class is None: | ||||||
|  |             pin_class = self.pin_class | ||||||
|  |         n = self._to_gpio(spec) | ||||||
|  |         try: | ||||||
|  |             pin = self.pins[n] | ||||||
|  |         except KeyError: | ||||||
|  |             pin = pin_class(self, n) | ||||||
|  |             self.pins[n] = pin | ||||||
|  |         else: | ||||||
|  |             # Ensure the pin class expected supports PWM (or not) | ||||||
|  |             if issubclass(pin_class, MockPWMPin) != isinstance(pin, MockPWMPin): | ||||||
|  |                 raise ValueError('pin %d is already in use as a %s' % (n, pin.__class__.__name__)) | ||||||
|  |         return pin | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,20 +13,18 @@ import mmap | |||||||
| import errno | import errno | ||||||
| import struct | import struct | ||||||
| import warnings | import warnings | ||||||
|  | import weakref | ||||||
| from time import sleep | from time import sleep | ||||||
| from threading import Thread, Event, Lock | from threading import Thread, Event, Lock | ||||||
| from collections import Counter | from collections import Counter | ||||||
|  |  | ||||||
| from . import LocalPin, PINS_CLEANUP | from .local import LocalPiPin, LocalPiFactory | ||||||
| from .data import pi_info |  | ||||||
| from ..exc import ( | from ..exc import ( | ||||||
|     PinInvalidPull, |     PinInvalidPull, | ||||||
|     PinInvalidEdges, |     PinInvalidEdges, | ||||||
|     PinInvalidFunction, |     PinInvalidFunction, | ||||||
|     PinFixedPull, |     PinFixedPull, | ||||||
|     PinSetInput, |     PinSetInput, | ||||||
|     PinNonPhysical, |  | ||||||
|     PinNoPins, |  | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -149,7 +147,7 @@ class GPIOFS(object): | |||||||
|                     f.write(str(pin).encode('ascii')) |                     f.write(str(pin).encode('ascii')) | ||||||
|  |  | ||||||
|  |  | ||||||
| class NativePin(LocalPin): | class NativeFactory(LocalPiFactory): | ||||||
|     """ |     """ | ||||||
|     Uses a built-in pure Python implementation to interface to the Pi's GPIO |     Uses a built-in pure Python implementation to interface to the Pi's GPIO | ||||||
|     pins. This is the default pin implementation if no third-party libraries |     pins. This is the default pin implementation if no third-party libraries | ||||||
| @@ -169,10 +167,17 @@ class NativePin(LocalPin): | |||||||
|  |  | ||||||
|         led = LED(NativePin(12)) |         led = LED(NativePin(12)) | ||||||
|     """ |     """ | ||||||
|  |     def __init__(self): | ||||||
|  |         super(NativeFactory, self).__init__() | ||||||
|  |         self.mem = GPIOMemory() | ||||||
|  |         self.pin_class = NativePin | ||||||
|  |  | ||||||
|     _MEM = None |     def close(self): | ||||||
|     _PINS = {} |         super(NativeFactory, self).close() | ||||||
|  |         self.mem.close() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class NativePin(LocalPiPin): | ||||||
|     GPIO_FUNCTIONS = { |     GPIO_FUNCTIONS = { | ||||||
|         'input':   0b000, |         'input':   0b000, | ||||||
|         'output':  0b001, |         'output':  0b001, | ||||||
| @@ -202,89 +207,62 @@ class NativePin(LocalPin): | |||||||
|     GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.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()} |     GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()} | ||||||
|  |  | ||||||
|     PI_INFO = None |     def __init__(self, factory, number): | ||||||
|  |         super(NativePin, self).__init__(factory, number) | ||||||
|     def __new__(cls, number): |         self._func_offset = self.factory.mem.GPFSEL_OFFSET + (number // 10) | ||||||
|         if not cls._PINS: |  | ||||||
|             cls._MEM = GPIOMemory() |  | ||||||
|             PINS_CLEANUP.append(cls._MEM.close) |  | ||||||
|         if cls.PI_INFO is None: |  | ||||||
|             cls.PI_INFO = pi_info() |  | ||||||
|         if not (0 <= number < 54): |  | ||||||
|             raise ValueError('invalid pin %d specified (must be 0..53)' % number) |  | ||||||
|         try: |  | ||||||
|             return cls._PINS[number] |  | ||||||
|         except KeyError: |  | ||||||
|             self = super(NativePin, 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._func_offset = self._MEM.GPFSEL_OFFSET + (number // 10) |  | ||||||
|         self._func_shift = (number % 10) * 3 |         self._func_shift = (number % 10) * 3 | ||||||
|             self._set_offset = self._MEM.GPSET_OFFSET + (number // 32) |         self._set_offset = self.factory.mem.GPSET_OFFSET + (number // 32) | ||||||
|         self._set_shift = number % 32 |         self._set_shift = number % 32 | ||||||
|             self._clear_offset = self._MEM.GPCLR_OFFSET + (number // 32) |         self._clear_offset = self.factory.mem.GPCLR_OFFSET + (number // 32) | ||||||
|         self._clear_shift = number % 32 |         self._clear_shift = number % 32 | ||||||
|             self._level_offset = self._MEM.GPLEV_OFFSET + (number // 32) |         self._level_offset = self.factory.mem.GPLEV_OFFSET + (number // 32) | ||||||
|         self._level_shift = number % 32 |         self._level_shift = number % 32 | ||||||
|             self._pull_offset = self._MEM.GPPUDCLK_OFFSET + (number // 32) |         self._pull_offset = self.factory.mem.GPPUDCLK_OFFSET + (number // 32) | ||||||
|         self._pull_shift = number % 32 |         self._pull_shift = number % 32 | ||||||
|             self._edge_offset = self._MEM.GPEDS_OFFSET + (number // 32) |         self._edge_offset = self.factory.mem.GPEDS_OFFSET + (number // 32) | ||||||
|         self._edge_shift = number % 32 |         self._edge_shift = number % 32 | ||||||
|             self._rising_offset = self._MEM.GPREN_OFFSET + (number // 32) |         self._rising_offset = self.factory.mem.GPREN_OFFSET + (number // 32) | ||||||
|         self._rising_shift = number % 32 |         self._rising_shift = number % 32 | ||||||
|             self._falling_offset = self._MEM.GPFEN_OFFSET + (number // 32) |         self._falling_offset = self.factory.mem.GPFEN_OFFSET + (number // 32) | ||||||
|         self._falling_shift = number % 32 |         self._falling_shift = number % 32 | ||||||
|         self._when_changed = None |         self._when_changed = None | ||||||
|         self._change_thread = None |         self._change_thread = None | ||||||
|         self._change_event = Event() |         self._change_event = Event() | ||||||
|         self.function = 'input' |         self.function = 'input' | ||||||
|             self.pull = 'up' if cls.PI_INFO.pulled_up('GPIO%d' % number) else 'floating' |         self.pull = 'up' if factory.pi_info.pulled_up('GPIO%d' % number) else 'floating' | ||||||
|         self.bounce = None |         self.bounce = None | ||||||
|         self.edges = 'both' |         self.edges = 'both' | ||||||
|             cls._PINS[number] = self |  | ||||||
|             return self |  | ||||||
|  |  | ||||||
|     def __repr__(self): |  | ||||||
|         return "GPIO%d" % self._number |  | ||||||
|  |  | ||||||
|     @property |  | ||||||
|     def number(self): |  | ||||||
|         return self._number |  | ||||||
|  |  | ||||||
|     def close(self): |     def close(self): | ||||||
|  |         self.frequency = None | ||||||
|         self.when_changed = None |         self.when_changed = None | ||||||
|         self.function = 'input' |         self.function = 'input' | ||||||
|         self.pull = 'up' if self.PI_INFO.pulled_up('GPIO%d' % self.number) else 'floating' |         self.pull = 'up' if self.factory.pi_info.pulled_up('GPIO%d' % self.number) else 'floating' | ||||||
|  |  | ||||||
|     def _get_function(self): |     def _get_function(self): | ||||||
|         return self.GPIO_FUNCTION_NAMES[(self._MEM[self._func_offset] >> self._func_shift) & 7] |         return self.GPIO_FUNCTION_NAMES[(self.factory.mem[self._func_offset] >> self._func_shift) & 7] | ||||||
|  |  | ||||||
|     def _set_function(self, value): |     def _set_function(self, value): | ||||||
|         try: |         try: | ||||||
|             value = self.GPIO_FUNCTIONS[value] |             value = self.GPIO_FUNCTIONS[value] | ||||||
|         except KeyError: |         except KeyError: | ||||||
|             raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self)) |             raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self)) | ||||||
|         self._MEM[self._func_offset] = ( |         self.factory.mem[self._func_offset] = ( | ||||||
|             self._MEM[self._func_offset] |             self.factory.mem[self._func_offset] | ||||||
|             & ~(7 << self._func_shift) |             & ~(7 << self._func_shift) | ||||||
|             | (value << self._func_shift) |             | (value << self._func_shift) | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|     def _get_state(self): |     def _get_state(self): | ||||||
|         return bool(self._MEM[self._level_offset] & (1 << self._level_shift)) |         return bool(self.factory.mem[self._level_offset] & (1 << self._level_shift)) | ||||||
|  |  | ||||||
|     def _set_state(self, value): |     def _set_state(self, value): | ||||||
|         if self.function == 'input': |         if self.function == 'input': | ||||||
|             raise PinSetInput('cannot set state of pin %r' % self) |             raise PinSetInput('cannot set state of pin %r' % self) | ||||||
|         if value: |         if value: | ||||||
|             self._MEM[self._set_offset] = 1 << self._set_shift |             self.factory.mem[self._set_offset] = 1 << self._set_shift | ||||||
|         else: |         else: | ||||||
|             self._MEM[self._clear_offset] = 1 << self._clear_shift |             self.factory.mem[self._clear_offset] = 1 << self._clear_shift | ||||||
|  |  | ||||||
|     def _get_pull(self): |     def _get_pull(self): | ||||||
|         return self.GPIO_PULL_UP_NAMES[self._pull] |         return self.GPIO_PULL_UP_NAMES[self._pull] | ||||||
| @@ -292,23 +270,23 @@ class NativePin(LocalPin): | |||||||
|     def _set_pull(self, value): |     def _set_pull(self, value): | ||||||
|         if self.function != 'input': |         if self.function != 'input': | ||||||
|             raise PinFixedPull('cannot set pull on non-input pin %r' % self) |             raise PinFixedPull('cannot set pull on non-input pin %r' % self) | ||||||
|         if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self.number): |         if value != 'up' and self.factory.pi_info.pulled_up('GPIO%d' % self.number): | ||||||
|             raise PinFixedPull('%r has a physical pull-up resistor' % self) |             raise PinFixedPull('%r has a physical pull-up resistor' % self) | ||||||
|         try: |         try: | ||||||
|             value = self.GPIO_PULL_UPS[value] |             value = self.GPIO_PULL_UPS[value] | ||||||
|         except KeyError: |         except KeyError: | ||||||
|             raise PinInvalidPull('invalid pull direction "%s" for pin %r' % (value, self)) |             raise PinInvalidPull('invalid pull direction "%s" for pin %r' % (value, self)) | ||||||
|         self._MEM[self._MEM.GPPUD_OFFSET] = value |         self.factory.mem[self.factory.mem.GPPUD_OFFSET] = value | ||||||
|         sleep(0.000000214) |         sleep(0.000000214) | ||||||
|         self._MEM[self._pull_offset] = 1 << self._pull_shift |         self.factory.mem[self._pull_offset] = 1 << self._pull_shift | ||||||
|         sleep(0.000000214) |         sleep(0.000000214) | ||||||
|         self._MEM[self._MEM.GPPUD_OFFSET] = 0 |         self.factory.mem[self.factory.mem.GPPUD_OFFSET] = 0 | ||||||
|         self._MEM[self._pull_offset] = 0 |         self.factory.mem[self._pull_offset] = 0 | ||||||
|         self._pull = value |         self._pull = value | ||||||
|  |  | ||||||
|     def _get_edges(self): |     def _get_edges(self): | ||||||
|         rising = bool(self._MEM[self._rising_offset] & (1 << self._rising_shift)) |         rising = bool(self.factory.mem[self._rising_offset] & (1 << self._rising_shift)) | ||||||
|         falling = bool(self._MEM[self._falling_offset] & (1 << self._falling_shift)) |         falling = bool(self.factory.mem[self._falling_offset] & (1 << self._falling_shift)) | ||||||
|         return self.GPIO_EDGES_NAMES[(rising, falling)] |         return self.GPIO_EDGES_NAMES[(rising, falling)] | ||||||
|  |  | ||||||
|     def _set_edges(self, value): |     def _set_edges(self, value): | ||||||
| @@ -319,13 +297,13 @@ class NativePin(LocalPin): | |||||||
|         f = self.when_changed |         f = self.when_changed | ||||||
|         self.when_changed = None |         self.when_changed = None | ||||||
|         try: |         try: | ||||||
|             self._MEM[self._rising_offset] = ( |             self.factory.mem[self._rising_offset] = ( | ||||||
|                 self._MEM[self._rising_offset] |                 self.factory.mem[self._rising_offset] | ||||||
|                 & ~(1 << self._rising_shift) |                 & ~(1 << self._rising_shift) | ||||||
|                 | (rising << self._rising_shift) |                 | (rising << self._rising_shift) | ||||||
|                 ) |                 ) | ||||||
|             self._MEM[self._falling_offset] = ( |             self.factory.mem[self._falling_offset] = ( | ||||||
|                 self._MEM[self._falling_offset] |                 self.factory.mem[self._falling_offset] | ||||||
|                 & ~(1 << self._falling_shift) |                 & ~(1 << self._falling_shift) | ||||||
|                 | (falling << self._falling_shift) |                 | (falling << self._falling_shift) | ||||||
|                 ) |                 ) | ||||||
| @@ -353,9 +331,9 @@ class NativePin(LocalPin): | |||||||
|     def _change_watch(self): |     def _change_watch(self): | ||||||
|         offset = self._edge_offset |         offset = self._edge_offset | ||||||
|         mask = 1 << self._edge_shift |         mask = 1 << self._edge_shift | ||||||
|         self._MEM[offset] = mask # clear any existing detection bit |         self.factory.mem[offset] = mask # clear any existing detection bit | ||||||
|         while not self._change_event.wait(0.001): |         while not self._change_event.wait(0.001): | ||||||
|             if self._MEM[offset] & mask: |             if self.factory.mem[offset] & mask: | ||||||
|                 self._MEM[offset] = mask |                 self.factory.mem[offset] = mask | ||||||
|                 self._when_changed() |                 self._when_changed() | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										214
									
								
								gpiozero/pins/pi.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								gpiozero/pins/pi.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,214 @@ | |||||||
|  | from __future__ import ( | ||||||
|  |     unicode_literals, | ||||||
|  |     absolute_import, | ||||||
|  |     print_function, | ||||||
|  |     division, | ||||||
|  |     ) | ||||||
|  | str = type('') | ||||||
|  |  | ||||||
|  | import io | ||||||
|  | import weakref | ||||||
|  | import warnings | ||||||
|  |  | ||||||
|  | try: | ||||||
|  |     from spidev import SpiDev | ||||||
|  | except ImportError: | ||||||
|  |     SpiDev = None | ||||||
|  |  | ||||||
|  | from . import Factory, Pin | ||||||
|  | from .data import pi_info | ||||||
|  | from ..exc import ( | ||||||
|  |     PinNoPins, | ||||||
|  |     PinNonPhysical, | ||||||
|  |     PinInvalidPin, | ||||||
|  |     SPIBadArgs, | ||||||
|  |     SPISoftwareFallback, | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PiFactory(Factory): | ||||||
|  |     """ | ||||||
|  |     Abstract base class representing hardware attached to a Raspberry Pi. This | ||||||
|  |     forms the base of :class:`LocalPiFactory`. | ||||||
|  |     """ | ||||||
|  |     def __init__(self): | ||||||
|  |         self._info = None | ||||||
|  |         self.pins = {} | ||||||
|  |         self.pin_class = None | ||||||
|  |         self.spi_hardware_class = None | ||||||
|  |         self.spi_software_class = None | ||||||
|  |         self.shared_spi_hardware_class = None | ||||||
|  |         self.shared_spi_software_class = None | ||||||
|  |  | ||||||
|  |     def close(self): | ||||||
|  |         for pin in self.pins.values(): | ||||||
|  |             pin.close() | ||||||
|  |         self.pins.clear() | ||||||
|  |  | ||||||
|  |     def pin(self, spec): | ||||||
|  |         n = self._to_gpio(spec) | ||||||
|  |         try: | ||||||
|  |             pin = self.pins[n] | ||||||
|  |         except KeyError: | ||||||
|  |             pin = self.pin_class(self, n) | ||||||
|  |             self.pins[n] = pin | ||||||
|  |         return pin | ||||||
|  |  | ||||||
|  |     def pin_address(self, spec): | ||||||
|  |         n = self._to_gpio(spec) | ||||||
|  |         return self.address + ('GPIO%d' % n,) | ||||||
|  |  | ||||||
|  |     def _to_gpio(self, spec): | ||||||
|  |         """ | ||||||
|  |         Converts the pin *spec* to a GPIO port number. | ||||||
|  |         """ | ||||||
|  |         if not 0 <= spec < 54: | ||||||
|  |             raise PinInvalidPin('invalid GPIO port %d specified (range 0..53) ' % spec) | ||||||
|  |         return spec | ||||||
|  |  | ||||||
|  |     def _get_revision(self): | ||||||
|  |         raise NotImplementedError | ||||||
|  |  | ||||||
|  |     def _get_pi_info(self): | ||||||
|  |         if self._info is None: | ||||||
|  |             self._info = pi_info(self._get_revision()) | ||||||
|  |         return self._info | ||||||
|  |  | ||||||
|  |     def spi(self, **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). | ||||||
|  |         """ | ||||||
|  |         spi_args, kwargs = self._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), | ||||||
|  |                 )): | ||||||
|  |             try: | ||||||
|  |                 hardware_spi_args = { | ||||||
|  |                     'port': 0, | ||||||
|  |                     'device': {8: 0, 7: 1}[spi_args['select_pin']], | ||||||
|  |                     } | ||||||
|  |                 if shared: | ||||||
|  |                     return self.shared_spi_hardware_class(self, **hardware_spi_args) | ||||||
|  |                 else: | ||||||
|  |                     return self.spi_hardware_class(self, **hardware_spi_args) | ||||||
|  |             except Exception as e: | ||||||
|  |                 warnings.warn( | ||||||
|  |                     SPISoftwareFallback( | ||||||
|  |                         'failed to initialize hardware SPI, falling back to ' | ||||||
|  |                         'software (error was: %s)' % str(e))) | ||||||
|  |         # Convert all pin arguments to integer GPIO numbers. This is necessary | ||||||
|  |         # to ensure the shared-key for shared implementations get matched | ||||||
|  |         # correctly, and is a bit of a hack for the pigpio bit-bang | ||||||
|  |         # implementation which just wants the pin numbers too. | ||||||
|  |         spi_args = { | ||||||
|  |             key: pin.number if isinstance(pin, Pin) else pin | ||||||
|  |             for key, pin in spi_args.items() | ||||||
|  |             } | ||||||
|  |         if shared: | ||||||
|  |             return self.shared_spi_software_class(self, **spi_args) | ||||||
|  |         else: | ||||||
|  |             return self.spi_software_class(self, **spi_args) | ||||||
|  |  | ||||||
|  |     def _extract_spi_args(self, **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: self._to_gpio(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 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PiPin(Pin): | ||||||
|  |     """ | ||||||
|  |     Abstract base class representing a multi-function GPIO pin attached to a | ||||||
|  |     Raspberry Pi. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, factory, number): | ||||||
|  |         super(PiPin, self).__init__() | ||||||
|  |         try: | ||||||
|  |             factory.pi_info.physical_pin('GPIO%d' % number) | ||||||
|  |         except PinNoPins: | ||||||
|  |             warnings.warn( | ||||||
|  |                 PinNonPhysical( | ||||||
|  |                     'no physical pins exist for GPIO%d' % number)) | ||||||
|  |         self._factory = weakref.proxy(factory) | ||||||
|  |         self._number = number | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def number(self): | ||||||
|  |         return self._number | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def factory(self): | ||||||
|  |         return self._factory | ||||||
|  |  | ||||||
|  |     def _get_address(self): | ||||||
|  |         return self.factory.address + ('GPIO%d' % self.number,) | ||||||
|  |  | ||||||
| @@ -6,12 +6,15 @@ from __future__ import ( | |||||||
|     ) |     ) | ||||||
| str = type('') | str = type('') | ||||||
|  |  | ||||||
| import warnings | import weakref | ||||||
| import pigpio | import pigpio | ||||||
| import os | import os | ||||||
|  |  | ||||||
| from . import Pin | from . import SPI | ||||||
|  | from .pi import PiPin, PiFactory | ||||||
| from .data import pi_info | from .data import pi_info | ||||||
|  | from ..devices import Device | ||||||
|  | from ..mixins import SharedMixin | ||||||
| from ..exc import ( | from ..exc import ( | ||||||
|     PinInvalidFunction, |     PinInvalidFunction, | ||||||
|     PinSetInput, |     PinSetInput, | ||||||
| @@ -19,12 +22,12 @@ from ..exc import ( | |||||||
|     PinInvalidPull, |     PinInvalidPull, | ||||||
|     PinInvalidBounce, |     PinInvalidBounce, | ||||||
|     PinInvalidState, |     PinInvalidState, | ||||||
|     PinNonPhysical, |     SPIBadArgs, | ||||||
|     PinNoPins, |     SPIInvalidClockMode, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class PiGPIOPin(Pin): | class PiGPIOFactory(PiFactory): | ||||||
|     """ |     """ | ||||||
|     Uses the `pigpio`_ library to interface to the Pi's GPIO pins. The pigpio |     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 |     library relies on a daemon (``pigpiod``) to be running as root to provide | ||||||
| @@ -68,10 +71,65 @@ class PiGPIOPin(Pin): | |||||||
|  |  | ||||||
|     .. _pigpio: http://abyz.co.uk/rpi/pigpio/ |     .. _pigpio: http://abyz.co.uk/rpi/pigpio/ | ||||||
|     """ |     """ | ||||||
|  |     def __init__( | ||||||
|  |             self, host=os.getenv('PIGPIO_ADDR', 'localhost'), | ||||||
|  |             port=int(os.getenv('PIGPIO_PORT', 8888))): | ||||||
|  |         super(PiGPIOFactory, self).__init__() | ||||||
|  |         self.pin_class = PiGPIOPin | ||||||
|  |         self.spi_hardware_class = PiGPIOHardwareSPI | ||||||
|  |         self.spi_software_class = PiGPIOSoftwareSPI | ||||||
|  |         self.shared_spi_hardware_class = PiGPIOHardwareSPIShared | ||||||
|  |         self.shared_spi_software_class = PiGPIOSoftwareSPIShared | ||||||
|  |         self._connection = pigpio.pi(host, port) | ||||||
|  |         self._host = host | ||||||
|  |         self._port = port | ||||||
|  |         self._spis = [] | ||||||
|  |  | ||||||
|  |     def close(self): | ||||||
|  |         super(PiGPIOFactory, self).close() | ||||||
|  |         # We *have* to keep track of SPI interfaces constructed with pigpio; | ||||||
|  |         # if we fail to close them they prevent future interfaces from using | ||||||
|  |         # the same pins | ||||||
|  |         if self.connection: | ||||||
|  |             while self._spis: | ||||||
|  |                 self._spis[0].close() | ||||||
|  |             self.connection.stop() | ||||||
|  |             self._connection = None | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def connection(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... | ||||||
|  |         try: | ||||||
|  |             if self._connection.sl.s is not None: | ||||||
|  |                 return self._connection | ||||||
|  |         except AttributeError: | ||||||
|  |             pass | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def host(self): | ||||||
|  |         return self._host | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def port(self): | ||||||
|  |         return self._port | ||||||
|  |  | ||||||
|  |     def _get_revision(self): | ||||||
|  |         return self.connection.get_hardware_revision() | ||||||
|  |  | ||||||
|  |     def _get_address(self): | ||||||
|  |         return ("%s:%d" % (self.host, self.port),) | ||||||
|  |  | ||||||
|  |     def spi(self, **spi_args): | ||||||
|  |         intf = super(PiGPIOFactory, self).spi(**spi_args) | ||||||
|  |         self._spis.append(intf) | ||||||
|  |         return intf | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PiGPIOPin(PiPin): | ||||||
|     _CONNECTIONS = {} # maps (host, port) to (connection, pi_info) |     _CONNECTIONS = {} # maps (host, port) to (connection, pi_info) | ||||||
|     _PINS = {} |  | ||||||
|  |  | ||||||
|     GPIO_FUNCTIONS = { |     GPIO_FUNCTIONS = { | ||||||
|         'input':   pigpio.INPUT, |         'input':   pigpio.INPUT, | ||||||
|         'output':  pigpio.OUTPUT, |         'output':  pigpio.OUTPUT, | ||||||
| @@ -99,101 +157,64 @@ class PiGPIOPin(Pin): | |||||||
|     GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.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()} |     GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()} | ||||||
|  |  | ||||||
|     def __new__( |     def __init__(self, factory, number): | ||||||
|             cls, number, host=os.getenv('PIGPIO_ADDR', 'localhost'), |         super(PiGPIOPin, self).__init__(factory, number) | ||||||
|             port=int(os.getenv('PIGPIO_PORT', 8888))): |         self._pull = 'up' if factory.pi_info.pulled_up('GPIO%d' % number) else 'floating' | ||||||
|         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._pwm = False | ||||||
|         self._bounce = None |         self._bounce = None | ||||||
|         self._when_changed = None |         self._when_changed = None | ||||||
|         self._callback = None |         self._callback = None | ||||||
|         self._edges = pigpio.EITHER_EDGE |         self._edges = pigpio.EITHER_EDGE | ||||||
|         try: |         try: | ||||||
|                 self._connection.set_mode(self._number, pigpio.INPUT) |             self.factory.connection.set_mode(self.number, pigpio.INPUT) | ||||||
|         except pigpio.error as e: |         except pigpio.error as e: | ||||||
|             raise ValueError(e) |             raise ValueError(e) | ||||||
|             self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[self._pull]) |         self.factory.connection.set_pull_up_down(self.number, self.GPIO_PULL_UPS[self._pull]) | ||||||
|             self._connection.set_glitch_filter(self._number, 0) |         self.factory.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): |     def close(self): | ||||||
|         # If we're shutting down, the connection may have disconnected itself |         if self.factory.connection: | ||||||
|         # 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.frequency = None | ||||||
|             self.when_changed = None |             self.when_changed = None | ||||||
|             self.function = 'input' |             self.function = 'input' | ||||||
|             self.pull = 'up' if self._pi_info.pulled_up('GPIO%d' % self.number) else 'floating' |             self.pull = 'up' if self.factory.pi_info.pulled_up('GPIO%d' % self.number) else 'floating' | ||||||
|  |  | ||||||
|  |     def _get_address(self): | ||||||
|  |         return self.factory.address + ('GPIO%d' % self.number,) | ||||||
|  |  | ||||||
|     def _get_function(self): |     def _get_function(self): | ||||||
|         return self.GPIO_FUNCTION_NAMES[self._connection.get_mode(self._number)] |         return self.GPIO_FUNCTION_NAMES[self.factory.connection.get_mode(self.number)] | ||||||
|  |  | ||||||
|     def _set_function(self, value): |     def _set_function(self, value): | ||||||
|         if value != 'input': |         if value != 'input': | ||||||
|             self._pull = 'floating' |             self._pull = 'floating' | ||||||
|         try: |         try: | ||||||
|             self._connection.set_mode(self._number, self.GPIO_FUNCTIONS[value]) |             self.factory.connection.set_mode(self.number, self.GPIO_FUNCTIONS[value]) | ||||||
|         except KeyError: |         except KeyError: | ||||||
|             raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self)) |             raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self)) | ||||||
|  |  | ||||||
|     def _get_state(self): |     def _get_state(self): | ||||||
|         if self._pwm: |         if self._pwm: | ||||||
|             return ( |             return ( | ||||||
|                 self._connection.get_PWM_dutycycle(self._number) / |                 self.factory.connection.get_PWM_dutycycle(self.number) / | ||||||
|                 self._connection.get_PWM_range(self._number) |                 self.factory.connection.get_PWM_range(self.number) | ||||||
|                 ) |                 ) | ||||||
|         else: |         else: | ||||||
|             return bool(self._connection.read(self._number)) |             return bool(self.factory.connection.read(self.number)) | ||||||
|  |  | ||||||
|     def _set_state(self, value): |     def _set_state(self, value): | ||||||
|         if self._pwm: |         if self._pwm: | ||||||
|             try: |             try: | ||||||
|                 value = int(value * self._connection.get_PWM_range(self._number)) |                 value = int(value * self.factory.connection.get_PWM_range(self.number)) | ||||||
|                 if value != self._connection.get_PWM_dutycycle(self._number): |                 if value != self.factory.connection.get_PWM_dutycycle(self.number): | ||||||
|                     self._connection.set_PWM_dutycycle(self._number, value) |                     self.factory.connection.set_PWM_dutycycle(self.number, value) | ||||||
|             except pigpio.error: |             except pigpio.error: | ||||||
|                 raise PinInvalidState('invalid state "%s" for pin %r' % (value, self)) |                 raise PinInvalidState('invalid state "%s" for pin %r' % (value, self)) | ||||||
|         elif self.function == 'input': |         elif self.function == 'input': | ||||||
|             raise PinSetInput('cannot set state of pin %r' % self) |             raise PinSetInput('cannot set state of pin %r' % self) | ||||||
|         else: |         else: | ||||||
|             # write forces pin to OUTPUT, hence the check above |             # write forces pin to OUTPUT, hence the check above | ||||||
|             self._connection.write(self._number, bool(value)) |             self.factory.connection.write(self.number, bool(value)) | ||||||
|  |  | ||||||
|     def _get_pull(self): |     def _get_pull(self): | ||||||
|         return self._pull |         return self._pull | ||||||
| @@ -201,31 +222,31 @@ class PiGPIOPin(Pin): | |||||||
|     def _set_pull(self, value): |     def _set_pull(self, value): | ||||||
|         if self.function != 'input': |         if self.function != 'input': | ||||||
|             raise PinFixedPull('cannot set pull on non-input pin %r' % self) |             raise PinFixedPull('cannot set pull on non-input pin %r' % self) | ||||||
|         if value != 'up' and self._pi_info.pulled_up('GPIO%d' % self._number): |         if value != 'up' and self.factory.pi_info.pulled_up('GPIO%d' % self.number): | ||||||
|             raise PinFixedPull('%r has a physical pull-up resistor' % self) |             raise PinFixedPull('%r has a physical pull-up resistor' % self) | ||||||
|         try: |         try: | ||||||
|             self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[value]) |             self.factory.connection.set_pull_up_down(self.number, self.GPIO_PULL_UPS[value]) | ||||||
|             self._pull = value |             self._pull = value | ||||||
|         except KeyError: |         except KeyError: | ||||||
|             raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self)) |             raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self)) | ||||||
|  |  | ||||||
|     def _get_frequency(self): |     def _get_frequency(self): | ||||||
|         if self._pwm: |         if self._pwm: | ||||||
|             return self._connection.get_PWM_frequency(self._number) |             return self.factory.connection.get_PWM_frequency(self.number) | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|     def _set_frequency(self, value): |     def _set_frequency(self, value): | ||||||
|         if not self._pwm and value is not None: |         if not self._pwm and value is not None: | ||||||
|             self._connection.set_PWM_frequency(self._number, value) |             self.factory.connection.set_PWM_frequency(self.number, value) | ||||||
|             self._connection.set_PWM_range(self._number, 10000) |             self.factory.connection.set_PWM_range(self.number, 10000) | ||||||
|             self._connection.set_PWM_dutycycle(self._number, 0) |             self.factory.connection.set_PWM_dutycycle(self.number, 0) | ||||||
|             self._pwm = True |             self._pwm = True | ||||||
|         elif self._pwm and value is not None: |         elif self._pwm and value is not None: | ||||||
|             if value != self._connection.get_PWM_frequency(self._number): |             if value != self.factory.connection.get_PWM_frequency(self.number): | ||||||
|                 self._connection.set_PWM_frequency(self._number, value) |                 self.factory.connection.set_PWM_frequency(self.number, value) | ||||||
|                 self._connection.set_PWM_range(self._number, 10000) |                 self.factory.connection.set_PWM_range(self.number, 10000) | ||||||
|         elif self._pwm and value is None: |         elif self._pwm and value is None: | ||||||
|             self._connection.write(self._number, 0) |             self.factory.connection.write(self.number, 0) | ||||||
|             self._pwm = False |             self._pwm = False | ||||||
|  |  | ||||||
|     def _get_bounce(self): |     def _get_bounce(self): | ||||||
| @@ -236,7 +257,7 @@ class PiGPIOPin(Pin): | |||||||
|             value = 0 |             value = 0 | ||||||
|         elif value < 0: |         elif value < 0: | ||||||
|             raise PinInvalidBounce('bounce must be 0 or greater') |             raise PinInvalidBounce('bounce must be 0 or greater') | ||||||
|         self._connection.set_glitch_filter(self._number, int(value * 1000000)) |         self.factory.connection.set_glitch_filter(self.number, int(value * 1000000)) | ||||||
|  |  | ||||||
|     def _get_edges(self): |     def _get_edges(self): | ||||||
|         return self.GPIO_EDGES_NAMES[self._edges] |         return self.GPIO_EDGES_NAMES[self._edges] | ||||||
| @@ -259,20 +280,224 @@ class PiGPIOPin(Pin): | |||||||
|             self._callback.cancel() |             self._callback.cancel() | ||||||
|             self._callback = None |             self._callback = None | ||||||
|         if value is not None: |         if value is not None: | ||||||
|             self._callback = self._connection.callback( |             self._callback = self.factory.connection.callback( | ||||||
|                     self._number, self._edges, |                     self.number, self._edges, | ||||||
|                     lambda gpio, level, tick: value()) |                     lambda gpio, level, tick: value()) | ||||||
|  |  | ||||||
|     @classmethod |  | ||||||
|     def pi_info( | class PiGPIOHardwareSPI(SPI, Device): | ||||||
|             cls, host=os.getenv('PIGPIO_ADDR', 'localhost'), |     def __init__(self, factory, port, device): | ||||||
|             port=int(os.getenv('PIGPIO_PORT', 8888))): |         self._port = port | ||||||
|         try: |         self._device = device | ||||||
|             connection, info = cls._CONNECTIONS[(host, port)] |         self._factory = weakref.proxy(factory) | ||||||
|         except KeyError: |         super(PiGPIOHardwareSPI, self).__init__() | ||||||
|             connection = pigpio.pi(host, port) |         self._reserve_pins(*( | ||||||
|             revision = '%04x' % connection.get_hardware_revision() |             factory.address + ('GPIO%d' % pin,) | ||||||
|             info = pi_info(revision) |             for pin in (11, 10, 9, (8, 7)[device]) | ||||||
|             cls._CONNECTIONS[(host, port)] = (connection, info) |             )) | ||||||
|         return info |         self._mode = 0 | ||||||
|  |         self._select_high = False | ||||||
|  |         self._bits_per_word = 8 | ||||||
|  |         self._baud = 500000 | ||||||
|  |         self._handle = self._factory.connection.spi_open( | ||||||
|  |             device, self._baud, self._spi_flags()) | ||||||
|  |  | ||||||
|  |     def close(self): | ||||||
|  |         try: | ||||||
|  |             self._factory._spis.remove(self) | ||||||
|  |         except (ReferenceError, ValueError): | ||||||
|  |             # If the factory has died already or we're not present in its | ||||||
|  |             # internal list, ignore the error | ||||||
|  |             pass | ||||||
|  |         if not self.closed: | ||||||
|  |             self._factory.connection.spi_close(self._handle) | ||||||
|  |         self._handle = None | ||||||
|  |         self._release_all() | ||||||
|  |         super(PiGPIOHardwareSPI, self).close() | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def closed(self): | ||||||
|  |         return self._handle is None or self._factory.connection is None | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def factory(self): | ||||||
|  |         return self._factory | ||||||
|  |  | ||||||
|  |     def __repr__(self): | ||||||
|  |         try: | ||||||
|  |             self._check_open() | ||||||
|  |             return 'SPI(port=%d, device=%d)' % (self._port, self._device) | ||||||
|  |         except DeviceClosed: | ||||||
|  |             return 'SPI(closed)' | ||||||
|  |  | ||||||
|  |     def _spi_flags(self): | ||||||
|  |         return ( | ||||||
|  |             self._mode          << 0                  | | ||||||
|  |             self._select_high   << (2 + self._device) | | ||||||
|  |             self._bits_per_word << 16 | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |     def _get_clock_mode(self): | ||||||
|  |         return self._clock_mode | ||||||
|  |  | ||||||
|  |     def _set_clock_mode(self, value): | ||||||
|  |         self._check_open() | ||||||
|  |         if not 0 <= value < 4: | ||||||
|  |             raise SPIInvalidClockmode("%d is not a valid SPI clock mode" % value) | ||||||
|  |         self._factory.connection.spi_close(self._handle) | ||||||
|  |         self._clock_mode = value | ||||||
|  |         self._handle = self._factory.connection.spi_open( | ||||||
|  |             self._device, self._baud, self._spi_flags()) | ||||||
|  |  | ||||||
|  |     def _get_select_high(self): | ||||||
|  |         return self._select_high | ||||||
|  |  | ||||||
|  |     def _set_select_high(self, value): | ||||||
|  |         self._check_open() | ||||||
|  |         self._factory.connection.spi_close(self._handle) | ||||||
|  |         self._select_high = bool(value) | ||||||
|  |         self._handle = self._factory.connection.spi_open( | ||||||
|  |             self._device, self._baud, self._spi_flags()) | ||||||
|  |  | ||||||
|  |     def _get_bits_per_word(self): | ||||||
|  |         return self._bits_per_word | ||||||
|  |  | ||||||
|  |     def _set_bits_per_word(self, value): | ||||||
|  |         self._check_open() | ||||||
|  |         self._factory.connection.spi_close(self._handle) | ||||||
|  |         self._bits_per_word = value | ||||||
|  |         self._handle = self._factory.connection.spi_open( | ||||||
|  |             self._device, self._baud, self._spi_flags()) | ||||||
|  |  | ||||||
|  |     def transfer(self, data): | ||||||
|  |         self._check_open() | ||||||
|  |         count, data = self._factory.connection.spi_xfer(self._handle, data) | ||||||
|  |         if count < 0: | ||||||
|  |             raise IOError('SPI transfer error %d' % count) | ||||||
|  |         # Convert returned bytearray to list of ints. XXX Not sure how non-byte | ||||||
|  |         # sized words (aux intf only) are returned ... padded to 16/32-bits? | ||||||
|  |         return [int(b) for b in data] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PiGPIOSoftwareSPI(SPI, Device): | ||||||
|  |     def __init__(self, factory, clock_pin, mosi_pin, miso_pin, select_pin): | ||||||
|  |         self._select_pin = None | ||||||
|  |         self._factory = weakref.proxy(factory) | ||||||
|  |         self._address = factory.address + ( | ||||||
|  |             ) | ||||||
|  |         super(PiGPIOSoftwareSPI, self).__init__() | ||||||
|  |         self._reserve_pins( | ||||||
|  |             factory.pin_address(clock_pin), | ||||||
|  |             factory.pin_address(mosi_pin), | ||||||
|  |             factory.pin_address(miso_pin), | ||||||
|  |             factory.pin_address(select_pin), | ||||||
|  |             ) | ||||||
|  |         self._mode = 0 | ||||||
|  |         self._select_high = False | ||||||
|  |         self._lsb_first = False | ||||||
|  |         self._baud = 100000 | ||||||
|  |         try: | ||||||
|  |             self._factory.connection.bb_spi_open( | ||||||
|  |                 select_pin, miso_pin, mosi_pin, clock_pin, | ||||||
|  |                 self._baud, self._spi_flags()) | ||||||
|  |             # Only set after opening bb_spi; if that fails then close() will | ||||||
|  |             # also fail if bb_spi_close is attempted on an un-open interface | ||||||
|  |             self._select_pin = select_pin | ||||||
|  |             self._clock_pin = clock_pin | ||||||
|  |             self._mosi_pin = mosi_pin | ||||||
|  |             self._miso_pin = miso_pin | ||||||
|  |         except: | ||||||
|  |             self.close() | ||||||
|  |             raise | ||||||
|  |  | ||||||
|  |     def close(self): | ||||||
|  |         try: | ||||||
|  |             self._factory._spis.remove(self) | ||||||
|  |         except (ReferenceError, ValueError): | ||||||
|  |             # If the factory has died already or we're not present in its | ||||||
|  |             # internal list, ignore the error | ||||||
|  |             pass | ||||||
|  |         if not self.closed: | ||||||
|  |             self._factory.connection.bb_spi_close(self._select_pin) | ||||||
|  |         self._select_pin = None | ||||||
|  |         self._release_all() | ||||||
|  |         super(PiGPIOSoftwareSPI, self).close() | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def closed(self): | ||||||
|  |         return self._select_pin is None or self._factory.connection is None | ||||||
|  |  | ||||||
|  |     def __repr__(self): | ||||||
|  |         try: | ||||||
|  |             self._check_open() | ||||||
|  |             return ( | ||||||
|  |                 'SPI(clock_pin=%d, mosi_pin=%d, miso_pin=%d, select_pin=%d)' % ( | ||||||
|  |                 self._clock_pin, self._mosi_pin, self._miso_pin, self._select_pin | ||||||
|  |                 )) | ||||||
|  |         except DeviceClosed: | ||||||
|  |             return 'SPI(closed)' | ||||||
|  |  | ||||||
|  |     def _spi_flags(self): | ||||||
|  |         return ( | ||||||
|  |             self._mode          << 0  | | ||||||
|  |             self._select_high   << 2  | | ||||||
|  |             self._lsb_first     << 14 | | ||||||
|  |             self._lsb_first     << 15 | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |     def _get_clock_mode(self): | ||||||
|  |         return self._clock_mode | ||||||
|  |  | ||||||
|  |     def _set_clock_mode(self, value): | ||||||
|  |         self._check_open() | ||||||
|  |         if not 0 <= value < 4: | ||||||
|  |             raise SPIInvalidClockmode("%d is not a valid SPI clock mode" % value) | ||||||
|  |         self._factory.connection.bb_spi_close(self._select_pin) | ||||||
|  |         self._clock_mode = value | ||||||
|  |         self._factory.connection.bb_spi_open( | ||||||
|  |             self._select_pin, self._miso_pin, self._mosi_pin, self._clock_pin, | ||||||
|  |             self._baud, self._spi_flags()) | ||||||
|  |  | ||||||
|  |     def _get_select_high(self): | ||||||
|  |         return self._select_high | ||||||
|  |  | ||||||
|  |     def _set_select_high(self, value): | ||||||
|  |         self._check_open() | ||||||
|  |         self._factory.connection.bb_spi_close(self._select_pin) | ||||||
|  |         self._select_high = bool(value) | ||||||
|  |         self._factory.connection.bb_spi_open( | ||||||
|  |             self._select_pin, self._miso_pin, self._mosi_pin, self._clock_pin, | ||||||
|  |             self._baud, self._spi_flags()) | ||||||
|  |  | ||||||
|  |     def _get_lsb_first(self): | ||||||
|  |         return self._lsb_first | ||||||
|  |  | ||||||
|  |     def _set_lsb_first(self, value): | ||||||
|  |         self._check_open() | ||||||
|  |         self._factory.connection.bb_spi_close(self._select_pin) | ||||||
|  |         self._lsb_first = bool(value) | ||||||
|  |         self._factory.connection.bb_spi_open( | ||||||
|  |             self._select_pin, self._miso_pin, self._mosi_pin, self._clock_pin, | ||||||
|  |             self._baud, self._spi_flags()) | ||||||
|  |  | ||||||
|  |     def transfer(self, data): | ||||||
|  |         self._check_open() | ||||||
|  |         count, data = self._factory.connection.bb_spi_xfer(self._select_pin, data) | ||||||
|  |         if count < 0: | ||||||
|  |             raise IOError('SPI transfer error %d' % count) | ||||||
|  |         # Convert returned bytearray to list of ints. bb_spi only supports | ||||||
|  |         # byte-sized words so no issues here | ||||||
|  |         return [int(b) for b in data] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PiGPIOHardwareSPIShared(SharedMixin, PiGPIOHardwareSPI): | ||||||
|  |     @classmethod | ||||||
|  |     def _shared_key(cls, factory, port, device): | ||||||
|  |         return (factory, port, device) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PiGPIOSoftwareSPIShared(SharedMixin, PiGPIOSoftwareSPI): | ||||||
|  |     @classmethod | ||||||
|  |     def _shared_key(cls, factory, clock_pin, mosi_pin, miso_pin, select_pin): | ||||||
|  |         return (factory, select_pin) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,10 +7,10 @@ from __future__ import ( | |||||||
| str = type('') | str = type('') | ||||||
|  |  | ||||||
| import warnings | import warnings | ||||||
|  | import weakref | ||||||
| from RPi import GPIO | from RPi import GPIO | ||||||
|  |  | ||||||
| from . import LocalPin | from .local import LocalPiFactory, LocalPiPin | ||||||
| from .data import pi_info |  | ||||||
| from ..exc import ( | from ..exc import ( | ||||||
|     PinInvalidFunction, |     PinInvalidFunction, | ||||||
|     PinSetInput, |     PinSetInput, | ||||||
| @@ -19,12 +19,10 @@ from ..exc import ( | |||||||
|     PinInvalidState, |     PinInvalidState, | ||||||
|     PinInvalidBounce, |     PinInvalidBounce, | ||||||
|     PinPWMFixedValue, |     PinPWMFixedValue, | ||||||
|     PinNonPhysical, |  | ||||||
|     PinNoPins, |  | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class RPiGPIOPin(LocalPin): | class RPiGPIOFactory(LocalPiFactory): | ||||||
|     """ |     """ | ||||||
|     Uses the `RPi.GPIO`_ library to interface to the Pi's GPIO pins. This is |     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. |     the default pin implementation if the RPi.GPIO library is installed. | ||||||
| @@ -39,7 +37,7 @@ class RPiGPIOPin(LocalPin): | |||||||
|  |  | ||||||
|     However, you can also construct RPi.GPIO pins manually if you wish:: |     However, you can also construct RPi.GPIO pins manually if you wish:: | ||||||
|  |  | ||||||
|         from gpiozero.pins.rpigpio import RPiGPIOPin |         from gpiozero.pins.rpigpio import RPiGPIOFactory | ||||||
|         from gpiozero import LED |         from gpiozero import LED | ||||||
|  |  | ||||||
|         led = LED(RPiGPIOPin(12)) |         led = LED(RPiGPIOPin(12)) | ||||||
| @@ -47,8 +45,18 @@ class RPiGPIOPin(LocalPin): | |||||||
|     .. _RPi.GPIO: https://pypi.python.org/pypi/RPi.GPIO |     .. _RPi.GPIO: https://pypi.python.org/pypi/RPi.GPIO | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     _PINS = {} |     def __init__(self): | ||||||
|  |         super(RPiGPIOFactory, self).__init__() | ||||||
|  |         GPIO.setmode(GPIO.BCM) | ||||||
|  |         GPIO.setwarnings(False) | ||||||
|  |         self.pin_class = RPiGPIOPin | ||||||
|  |  | ||||||
|  |     def close(self): | ||||||
|  |         super(RPiGPIOFactory, self).close() | ||||||
|  |         GPIO.cleanup() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class RPiGPIOPin(LocalPiPin): | ||||||
|     GPIO_FUNCTIONS = { |     GPIO_FUNCTIONS = { | ||||||
|         'input':   GPIO.IN, |         'input':   GPIO.IN, | ||||||
|         'output':  GPIO.OUT, |         'output':  GPIO.OUT, | ||||||
| @@ -75,69 +83,43 @@ class RPiGPIOPin(LocalPin): | |||||||
|     GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.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()} |     GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()} | ||||||
|  |  | ||||||
|     PI_INFO = None |     def __init__(self, factory, number): | ||||||
|  |         super(RPiGPIOPin, self).__init__(factory, number) | ||||||
|     def __new__(cls, number): |         self._pull = 'up' if factory.pi_info.pulled_up('GPIO%d' % number) else 'floating' | ||||||
|         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._pwm = None | ||||||
|         self._frequency = None |         self._frequency = None | ||||||
|         self._duty_cycle = None |         self._duty_cycle = None | ||||||
|         self._bounce = -666 |         self._bounce = -666 | ||||||
|         self._when_changed = None |         self._when_changed = None | ||||||
|         self._edges = GPIO.BOTH |         self._edges = GPIO.BOTH | ||||||
|             GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[self._pull]) |         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): |     def close(self): | ||||||
|         self.frequency = None |         self.frequency = None | ||||||
|         self.when_changed = None |         self.when_changed = None | ||||||
|         GPIO.cleanup(self._number) |         GPIO.cleanup(self.number) | ||||||
|  |  | ||||||
|     def output_with_state(self, state): |     def output_with_state(self, state): | ||||||
|         self._pull = 'floating' |         self._pull = 'floating' | ||||||
|         GPIO.setup(self._number, GPIO.OUT, initial=state) |         GPIO.setup(self.number, GPIO.OUT, initial=state) | ||||||
|  |  | ||||||
|     def input_with_pull(self, pull): |     def input_with_pull(self, pull): | ||||||
|         if pull != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self._number): |         if pull != 'up' and self.factory.pi_info.pulled_up('GPIO%d' % self.number): | ||||||
|             raise PinFixedPull('%r has a physical pull-up resistor' % self) |             raise PinFixedPull('%r has a physical pull-up resistor' % self) | ||||||
|         try: |         try: | ||||||
|             GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[pull]) |             GPIO.setup(self.number, GPIO.IN, self.GPIO_PULL_UPS[pull]) | ||||||
|             self._pull = pull |             self._pull = pull | ||||||
|         except KeyError: |         except KeyError: | ||||||
|             raise PinInvalidPull('invalid pull "%s" for pin %r' % (pull, self)) |             raise PinInvalidPull('invalid pull "%s" for pin %r' % (pull, self)) | ||||||
|  |  | ||||||
|     def _get_function(self): |     def _get_function(self): | ||||||
|         return self.GPIO_FUNCTION_NAMES[GPIO.gpio_function(self._number)] |         return self.GPIO_FUNCTION_NAMES[GPIO.gpio_function(self.number)] | ||||||
|  |  | ||||||
|     def _set_function(self, value): |     def _set_function(self, value): | ||||||
|         if value != 'input': |         if value != 'input': | ||||||
|             self._pull = 'floating' |             self._pull = 'floating' | ||||||
|         if value in ('input', 'output') and value in self.GPIO_FUNCTIONS: |         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]) |             GPIO.setup(self.number, self.GPIO_FUNCTIONS[value], self.GPIO_PULL_UPS[self._pull]) | ||||||
|         else: |         else: | ||||||
|             raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self)) |             raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self)) | ||||||
|  |  | ||||||
| @@ -145,7 +127,7 @@ class RPiGPIOPin(LocalPin): | |||||||
|         if self._pwm: |         if self._pwm: | ||||||
|             return self._duty_cycle |             return self._duty_cycle | ||||||
|         else: |         else: | ||||||
|             return GPIO.input(self._number) |             return GPIO.input(self.number) | ||||||
|  |  | ||||||
|     def _set_state(self, value): |     def _set_state(self, value): | ||||||
|         if self._pwm: |         if self._pwm: | ||||||
| @@ -156,7 +138,7 @@ class RPiGPIOPin(LocalPin): | |||||||
|             self._duty_cycle = value |             self._duty_cycle = value | ||||||
|         else: |         else: | ||||||
|             try: |             try: | ||||||
|                 GPIO.output(self._number, value) |                 GPIO.output(self.number, value) | ||||||
|             except ValueError: |             except ValueError: | ||||||
|                 raise PinInvalidState('invalid state "%s" for pin %r' % (value, self)) |                 raise PinInvalidState('invalid state "%s" for pin %r' % (value, self)) | ||||||
|             except RuntimeError: |             except RuntimeError: | ||||||
| @@ -168,10 +150,10 @@ class RPiGPIOPin(LocalPin): | |||||||
|     def _set_pull(self, value): |     def _set_pull(self, value): | ||||||
|         if self.function != 'input': |         if self.function != 'input': | ||||||
|             raise PinFixedPull('cannot set pull on non-input pin %r' % self) |             raise PinFixedPull('cannot set pull on non-input pin %r' % self) | ||||||
|         if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self._number): |         if value != 'up' and self.factory.pi_info.pulled_up('GPIO%d' % self.number): | ||||||
|             raise PinFixedPull('%r has a physical pull-up resistor' % self) |             raise PinFixedPull('%r has a physical pull-up resistor' % self) | ||||||
|         try: |         try: | ||||||
|             GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[value]) |             GPIO.setup(self.number, GPIO.IN, self.GPIO_PULL_UPS[value]) | ||||||
|             self._pull = value |             self._pull = value | ||||||
|         except KeyError: |         except KeyError: | ||||||
|             raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self)) |             raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self)) | ||||||
| @@ -182,7 +164,7 @@ class RPiGPIOPin(LocalPin): | |||||||
|     def _set_frequency(self, value): |     def _set_frequency(self, value): | ||||||
|         if self._frequency is None and value is not None: |         if self._frequency is None and value is not None: | ||||||
|             try: |             try: | ||||||
|                 self._pwm = GPIO.PWM(self._number, value) |                 self._pwm = GPIO.PWM(self.number, value) | ||||||
|             except RuntimeError: |             except RuntimeError: | ||||||
|                 raise PinPWMFixedValue('cannot start PWM on pin %r' % self) |                 raise PinPWMFixedValue('cannot start PWM on pin %r' % self) | ||||||
|             self._pwm.start(0) |             self._pwm.start(0) | ||||||
| @@ -228,11 +210,11 @@ class RPiGPIOPin(LocalPin): | |||||||
|         if self._when_changed is None and value is not None: |         if self._when_changed is None and value is not None: | ||||||
|             self._when_changed = value |             self._when_changed = value | ||||||
|             GPIO.add_event_detect( |             GPIO.add_event_detect( | ||||||
|                 self._number, self._edges, |                 self.number, self._edges, | ||||||
|                 callback=lambda channel: self._when_changed(), |                 callback=lambda channel: self._when_changed(), | ||||||
|                 bouncetime=self._bounce) |                 bouncetime=self._bounce) | ||||||
|         elif self._when_changed is not None and value is None: |         elif self._when_changed is not None and value is None: | ||||||
|             GPIO.remove_event_detect(self._number) |             GPIO.remove_event_detect(self.number) | ||||||
|             self._when_changed = None |             self._when_changed = None | ||||||
|         else: |         else: | ||||||
|             self._when_changed = value |             self._when_changed = value | ||||||
|   | |||||||
| @@ -8,11 +8,12 @@ str = type('') | |||||||
|  |  | ||||||
|  |  | ||||||
| import warnings | import warnings | ||||||
|  | import weakref | ||||||
| import RPIO | import RPIO | ||||||
| import RPIO.PWM | import RPIO.PWM | ||||||
| from RPIO.Exceptions import InvalidChannelException | from RPIO.Exceptions import InvalidChannelException | ||||||
|  |  | ||||||
| from . import LocalPin, PINS_CLEANUP | from .local import LocalPiPin, LocalPiFactory | ||||||
| from .data import pi_info | from .data import pi_info | ||||||
| from ..exc import ( | from ..exc import ( | ||||||
|     PinInvalidFunction, |     PinInvalidFunction, | ||||||
| @@ -22,12 +23,10 @@ from ..exc import ( | |||||||
|     PinInvalidBounce, |     PinInvalidBounce, | ||||||
|     PinInvalidState, |     PinInvalidState, | ||||||
|     PinPWMError, |     PinPWMError, | ||||||
|     PinNonPhysical, |  | ||||||
|     PinNoPins, |  | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class RPIOPin(LocalPin): | class RPIOFactory(LocalPiFactory): | ||||||
|     """ |     """ | ||||||
|     Uses the `RPIO`_ library to interface to the Pi's GPIO pins. This is |     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, |     the default pin implementation if the RPi.GPIO library is not installed, | ||||||
| @@ -48,9 +47,22 @@ class RPIOPin(LocalPin): | |||||||
|  |  | ||||||
|     .. _RPIO: https://pythonhosted.org/RPIO/ |     .. _RPIO: https://pythonhosted.org/RPIO/ | ||||||
|     """ |     """ | ||||||
|  |     def __init__(self): | ||||||
|  |         super(RPIOFactory, self).__init__() | ||||||
|  |         RPIO.setmode(RPIO.BCM) | ||||||
|  |         RPIO.setwarnings(False) | ||||||
|  |         RPIO.wait_for_interrupts(threaded=True) | ||||||
|  |         RPIO.PWM.setup() | ||||||
|  |         RPIO.PWM.init_channel(0, 10000) | ||||||
|  |         self.pin_class = RPIOPin | ||||||
|  |  | ||||||
|     _PINS = {} |     def close(self): | ||||||
|  |         RPIO.PWM.cleanup() | ||||||
|  |         RPIO.stop_waiting_for_interrupts() | ||||||
|  |         RPIO.cleanup() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class RPIOPin(LocalPiPin): | ||||||
|     GPIO_FUNCTIONS = { |     GPIO_FUNCTIONS = { | ||||||
|         'input':   RPIO.IN, |         'input':   RPIO.IN, | ||||||
|         'output':  RPIO.OUT, |         'output':  RPIO.OUT, | ||||||
| @@ -66,64 +78,32 @@ class RPIOPin(LocalPin): | |||||||
|     GPIO_FUNCTION_NAMES = {v: k for (k, v) in GPIO_FUNCTIONS.items()} |     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_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()} | ||||||
|  |  | ||||||
|     PI_INFO = None |     def __init__(self, factory, number): | ||||||
|  |         super(RPIOPin, self).__init__(factory, number) | ||||||
|     def __new__(cls, number): |         self._pull = 'up' if factory.pi_info.pulled_up('GPIO%d' % number) else 'floating' | ||||||
|         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._pwm = False | ||||||
|         self._duty_cycle = None |         self._duty_cycle = None | ||||||
|         self._bounce = None |         self._bounce = None | ||||||
|         self._when_changed = None |         self._when_changed = None | ||||||
|         self._edges = 'both' |         self._edges = 'both' | ||||||
|         try: |         try: | ||||||
|                 RPIO.setup(self._number, RPIO.IN, self.GPIO_PULL_UPS[self._pull]) |             RPIO.setup(self.number, RPIO.IN, self.GPIO_PULL_UPS[self._pull]) | ||||||
|         except InvalidChannelException as e: |         except InvalidChannelException as e: | ||||||
|             raise ValueError(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): |     def close(self): | ||||||
|         self.frequency = None |         self.frequency = None | ||||||
|         self.when_changed = None |         self.when_changed = None | ||||||
|         RPIO.setup(self._number, RPIO.IN, RPIO.PUD_OFF) |         RPIO.setup(self.number, RPIO.IN, RPIO.PUD_OFF) | ||||||
|  |  | ||||||
|     def _get_function(self): |     def _get_function(self): | ||||||
|         return self.GPIO_FUNCTION_NAMES[RPIO.gpio_function(self._number)] |         return self.GPIO_FUNCTION_NAMES[RPIO.gpio_function(self.number)] | ||||||
|  |  | ||||||
|     def _set_function(self, value): |     def _set_function(self, value): | ||||||
|         if value != 'input': |         if value != 'input': | ||||||
|             self._pull = 'floating' |             self._pull = 'floating' | ||||||
|         try: |         try: | ||||||
|             RPIO.setup(self._number, self.GPIO_FUNCTIONS[value], self.GPIO_PULL_UPS[self._pull]) |             RPIO.setup(self.number, self.GPIO_FUNCTIONS[value], self.GPIO_PULL_UPS[self._pull]) | ||||||
|         except KeyError: |         except KeyError: | ||||||
|             raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self)) |             raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self)) | ||||||
|  |  | ||||||
| @@ -131,23 +111,23 @@ class RPIOPin(LocalPin): | |||||||
|         if self._pwm: |         if self._pwm: | ||||||
|             return self._duty_cycle |             return self._duty_cycle | ||||||
|         else: |         else: | ||||||
|             return RPIO.input(self._number) |             return RPIO.input(self.number) | ||||||
|  |  | ||||||
|     def _set_state(self, value): |     def _set_state(self, value): | ||||||
|         if not 0 <= value <= 1: |         if not 0 <= value <= 1: | ||||||
|             raise PinInvalidState('invalid state "%s" for pin %r' % (value, self)) |             raise PinInvalidState('invalid state "%s" for pin %r' % (value, self)) | ||||||
|         if self._pwm: |         if self._pwm: | ||||||
|             RPIO.PWM.clear_channel_gpio(0, self._number) |             RPIO.PWM.clear_channel_gpio(0, self.number) | ||||||
|             if value == 0: |             if value == 0: | ||||||
|                 RPIO.output(self._number, False) |                 RPIO.output(self.number, False) | ||||||
|             elif value == 1: |             elif value == 1: | ||||||
|                 RPIO.output(self._number, True) |                 RPIO.output(self.number, True) | ||||||
|             else: |             else: | ||||||
|                 RPIO.PWM.add_channel_pulse(0, self._number, start=0, width=int(1000 * value)) |                 RPIO.PWM.add_channel_pulse(0, self.number, start=0, width=int(1000 * value)) | ||||||
|             self._duty_cycle = value |             self._duty_cycle = value | ||||||
|         else: |         else: | ||||||
|             try: |             try: | ||||||
|                 RPIO.output(self._number, value) |                 RPIO.output(self.number, value) | ||||||
|             except ValueError: |             except ValueError: | ||||||
|                 raise PinInvalidState('invalid state "%s" for pin %r' % (value, self)) |                 raise PinInvalidState('invalid state "%s" for pin %r' % (value, self)) | ||||||
|             except RuntimeError: |             except RuntimeError: | ||||||
| @@ -159,10 +139,10 @@ class RPIOPin(LocalPin): | |||||||
|     def _set_pull(self, value): |     def _set_pull(self, value): | ||||||
|         if self.function != 'input': |         if self.function != 'input': | ||||||
|             raise PinFixedPull('cannot set pull on non-input pin %r' % self) |             raise PinFixedPull('cannot set pull on non-input pin %r' % self) | ||||||
|         if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self._number): |         if value != 'up' and self.factory.pi_info.pulled_up('GPIO%d' % self.number): | ||||||
|             raise PinFixedPull('%r has a physical pull-up resistor' % self) |             raise PinFixedPull('%r has a physical pull-up resistor' % self) | ||||||
|         try: |         try: | ||||||
|             RPIO.setup(self._number, RPIO.IN, self.GPIO_PULL_UPS[value]) |             RPIO.setup(self.number, RPIO.IN, self.GPIO_PULL_UPS[value]) | ||||||
|             self._pull = value |             self._pull = value | ||||||
|         except KeyError: |         except KeyError: | ||||||
|             raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self)) |             raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self)) | ||||||
| @@ -182,10 +162,10 @@ class RPIOPin(LocalPin): | |||||||
|             self._pwm = True |             self._pwm = True | ||||||
|             # Dirty hack to get RPIO's PWM support to setup, but do nothing, |             # Dirty hack to get RPIO's PWM support to setup, but do nothing, | ||||||
|             # for a given GPIO pin |             # for a given GPIO pin | ||||||
|             RPIO.PWM.add_channel_pulse(0, self._number, start=0, width=0) |             RPIO.PWM.add_channel_pulse(0, self.number, start=0, width=0) | ||||||
|             RPIO.PWM.clear_channel_gpio(0, self._number) |             RPIO.PWM.clear_channel_gpio(0, self.number) | ||||||
|         elif self._pwm and value is None: |         elif self._pwm and value is None: | ||||||
|             RPIO.PWM.clear_channel_gpio(0, self._number) |             RPIO.PWM.clear_channel_gpio(0, self.number) | ||||||
|             self._pwm = False |             self._pwm = False | ||||||
|  |  | ||||||
|     def _get_bounce(self): |     def _get_bounce(self): | ||||||
| @@ -219,12 +199,12 @@ class RPIOPin(LocalPin): | |||||||
|         if self._when_changed is None and value is not None: |         if self._when_changed is None and value is not None: | ||||||
|             self._when_changed = value |             self._when_changed = value | ||||||
|             RPIO.add_interrupt_callback( |             RPIO.add_interrupt_callback( | ||||||
|                 self._number, |                 self.number, | ||||||
|                 lambda channel, value: self._when_changed(), |                 lambda channel, value: self._when_changed(), | ||||||
|                 self._edges, self.GPIO_PULL_UPS[self._pull], self._bounce) |                 self._edges, self.GPIO_PULL_UPS[self._pull], self._bounce) | ||||||
|         elif self._when_changed is not None and value is None: |         elif self._when_changed is not None and value is None: | ||||||
|             try: |             try: | ||||||
|                 RPIO.del_interrupt_callback(self._number) |                 RPIO.del_interrupt_callback(self.number) | ||||||
|             except KeyError: |             except KeyError: | ||||||
|                 # Ignore this exception which occurs during shutdown; this |                 # Ignore this exception which occurs during shutdown; this | ||||||
|                 # simply means RPIO's built-in cleanup has already run and |                 # simply means RPIO's built-in cleanup has already run and | ||||||
|   | |||||||
							
								
								
									
										86
									
								
								gpiozero/pins/spi.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								gpiozero/pins/spi.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | from __future__ import ( | ||||||
|  |     unicode_literals, | ||||||
|  |     print_function, | ||||||
|  |     absolute_import, | ||||||
|  |     division, | ||||||
|  |     ) | ||||||
|  | str = type('') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | import operator | ||||||
|  | from threading import RLock | ||||||
|  |  | ||||||
|  | from ..devices import Device, SharedMixin | ||||||
|  | from ..input_devices import InputDevice | ||||||
|  | from ..output_devices import OutputDevice | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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() | ||||||
|  |         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 transfer(self, data, clock_phase=False, lsb_first=False, bits_per_word=8): | ||||||
|  |         """ | ||||||
|  |         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 lsb_first else operator.rshift | ||||||
|  |             for write_word in data: | ||||||
|  |                 mask = 1 if lsb_first else 1 << (bits_per_word - 1) | ||||||
|  |                 read_word = 0 | ||||||
|  |                 for _ in range(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 clock_phase: | ||||||
|  |                         if self.miso.value: | ||||||
|  |                             read_word |= mask | ||||||
|  |                     self.clock.off() | ||||||
|  |                     if self.miso is not None and clock_phase: | ||||||
|  |                         if self.miso.value: | ||||||
|  |                             read_word |= mask | ||||||
|  |                     mask = shift(mask, 1) | ||||||
|  |                 result.append(read_word) | ||||||
|  |         return result | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										419
									
								
								gpiozero/spi.py
									
									
									
									
									
								
							
							
						
						
									
										419
									
								
								gpiozero/spi.py
									
									
									
									
									
								
							| @@ -1,419 +0,0 @@ | |||||||
| from __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.clock_mode & 2) |  | ||||||
|  |  | ||||||
|     def _set_clock_polarity(self, value): |  | ||||||
|         self.clock_mode = self.clock_mode & (~2) | (bool(value) << 1) |  | ||||||
|  |  | ||||||
|     def _get_clock_phase(self): |  | ||||||
|         return bool(self.clock_mode & 1) |  | ||||||
|  |  | ||||||
|     def _set_clock_phase(self, value): |  | ||||||
|         self.clock_mode = self.clock_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() |  | ||||||
|         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 transfer(self, data, clock_phase=False, lsb_first=False, bits_per_word=8): |  | ||||||
|         """ |  | ||||||
|         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 lsb_first else operator.rshift |  | ||||||
|             for write_word in data: |  | ||||||
|                 mask = 1 if lsb_first else 1 << (bits_per_word - 1) |  | ||||||
|                 read_word = 0 |  | ||||||
|                 for _ in range(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 clock_phase: |  | ||||||
|                         if self.miso.value: |  | ||||||
|                             read_word |= mask |  | ||||||
|                     self.clock.off() |  | ||||||
|                     if self.miso is not None and 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._clock_phase = False |  | ||||||
|             self._lsb_first = False |  | ||||||
|             self._bits_per_word = 8 |  | ||||||
|             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.transfer((0,) * n) |  | ||||||
|  |  | ||||||
|     def write(self, data): |  | ||||||
|         return len(self.transfer(data)) |  | ||||||
|  |  | ||||||
|     def transfer(self, data): |  | ||||||
|         with self._bus.lock: |  | ||||||
|             self.on() |  | ||||||
|             try: |  | ||||||
|                 return self._bus.transfer( |  | ||||||
|                     data, self._clock_phase, self._lsb_first, self._bits_per_word) |  | ||||||
|             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') |  | ||||||
|         self.clock_polarity = bool(value & 2) |  | ||||||
|         self.clock_phase = bool(value & 1) |  | ||||||
|  |  | ||||||
|     def _get_clock_polarity(self): |  | ||||||
|         with self._bus.lock: |  | ||||||
|             return not self._bus.clock.active_high |  | ||||||
|  |  | ||||||
|     def _set_clock_polarity(self, value): |  | ||||||
|         with self._bus.lock: |  | ||||||
|             self._bus.clock.active_high = not value |  | ||||||
|             self._bus.clock.off() |  | ||||||
|  |  | ||||||
|     def _get_clock_phase(self): |  | ||||||
|         return self._clock_phase |  | ||||||
|  |  | ||||||
|     def _set_clock_phase(self, value): |  | ||||||
|         self._clock_phase = bool(value) |  | ||||||
|  |  | ||||||
|     def _get_lsb_first(self): |  | ||||||
|         return self._lsb_first |  | ||||||
|  |  | ||||||
|     def _set_lsb_first(self, value): |  | ||||||
|         self._lsb_first = bool(value) |  | ||||||
|  |  | ||||||
|     def _get_bits_per_word(self): |  | ||||||
|         return self._bits_per_word |  | ||||||
|  |  | ||||||
|     def _set_bits_per_word(self, value): |  | ||||||
|         if value < 1: |  | ||||||
|             raise ValueError('bits_per_word must be positive') |  | ||||||
|         self._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) |  | ||||||
|  |  | ||||||
| @@ -16,7 +16,6 @@ except ImportError: | |||||||
|  |  | ||||||
| from .exc import DeviceClosed, SPIBadChannel | from .exc import DeviceClosed, SPIBadChannel | ||||||
| from .devices import Device | from .devices import Device | ||||||
| from .spi import SPI |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SPIDevice(Device): | class SPIDevice(Device): | ||||||
| @@ -28,13 +27,12 @@ class SPIDevice(Device): | |||||||
|     specified with the constructor. |     specified with the constructor. | ||||||
|     """ |     """ | ||||||
|     def __init__(self, **spi_args): |     def __init__(self, **spi_args): | ||||||
|         self._spi = SPI(**spi_args) |         self._spi = self._pin_factory.spi(**spi_args) | ||||||
|  |  | ||||||
|     def close(self): |     def close(self): | ||||||
|         if self._spi: |         if self._spi: | ||||||
|             s = self._spi |             self._spi.close() | ||||||
|             self._spi = None |             self._spi = None | ||||||
|             s.close() |  | ||||||
|         super(SPIDevice, self).close() |         super(SPIDevice, self).close() | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								setup.py
									
									
									
									
									
								
							| @@ -68,12 +68,12 @@ if sys.version_info[:2] == (3, 2): | |||||||
|  |  | ||||||
| __entry_points__ = { | __entry_points__ = { | ||||||
|     'gpiozero_pin_factories': [ |     'gpiozero_pin_factories': [ | ||||||
|         'PiGPIOPin  = gpiozero.pins.pigpiod:PiGPIOPin', |         'pigpio  = gpiozero.pins.pigpiod:PiGPIOFactory', | ||||||
|         'RPiGPIOPin = gpiozero.pins.rpigpio:RPiGPIOPin', |         'rpigpio = gpiozero.pins.rpigpio:RPiGPIOFactory', | ||||||
|         'RPIOPin    = gpiozero.pins.rpio:RPIOPin', |         'rpio    = gpiozero.pins.rpio:RPIOFactory', | ||||||
|         'NativePin  = gpiozero.pins.native:NativePin', |         'native  = gpiozero.pins.native:NativeFactory', | ||||||
|         'MockPin    = gpiozero.pins.mock:MockPin', |         'mock    = gpiozero.pins.mock:MockFactory', | ||||||
|         'MockPWMPin = gpiozero.pins.mock:MockPWMPin', |         'mockpwm = gpiozero.pins.mock:MockPWMFactory', | ||||||
|     ], |     ], | ||||||
|     'console_scripts': [ |     'console_scripts': [ | ||||||
|         'pinout = gpiozero.cli.pinout:main', |         'pinout = gpiozero.cli.pinout:main', | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								tests/conftest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								tests/conftest.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | from __future__ import ( | ||||||
|  |     unicode_literals, | ||||||
|  |     print_function, | ||||||
|  |     absolute_import, | ||||||
|  |     division, | ||||||
|  |     ) | ||||||
|  | str = type('') | ||||||
|  |  | ||||||
|  | import os | ||||||
|  | os.environ['GPIOZERO_PIN_FACTORY'] = 'mock' | ||||||
| @@ -11,26 +11,39 @@ import sys | |||||||
| import pytest | import pytest | ||||||
| from time import sleep | from time import sleep | ||||||
|  |  | ||||||
| from gpiozero.pins.mock import MockPin, MockPWMPin |  | ||||||
| from gpiozero import * | from gpiozero import * | ||||||
|  | from gpiozero.pins.mock import MockPWMPin, MockPin | ||||||
|  |  | ||||||
|  |  | ||||||
| def setup_function(function): | def setup_function(function): | ||||||
|     import gpiozero.devices |  | ||||||
|     # dirty, but it does the job |     # 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'): |     Device._pin_factory.pin_class = MockPWMPin if function.__name__ in ( | ||||||
|         gpiozero.devices.pin_factory = MockPWMPin |         'test_robot', | ||||||
|     else: |         'test_ryanteck_robot', | ||||||
|         gpiozero.devices.pin_factory = MockPin |         'test_camjam_kit_robot', | ||||||
|  |         'test_led_borg', | ||||||
|  |         'test_led_board_pwm_value', | ||||||
|  |         'test_led_board_pwm_bad_value', | ||||||
|  |         'test_snow_pi_initial_value_pwm', | ||||||
|  |         'test_led_board_pwm_initial_value', | ||||||
|  |         'test_led_board_pwm_bad_initial_value', | ||||||
|  |         'test_led_board_fade_background', | ||||||
|  |         'test_led_bar_graph_pwm_value', | ||||||
|  |         'test_led_bar_graph_pwm_initial_value', | ||||||
|  |         ) else MockPin | ||||||
|  |  | ||||||
| def teardown_function(function): | def teardown_function(function): | ||||||
|     MockPin.clear_pins() |     Device._pin_factory.reset() | ||||||
|  |  | ||||||
|  | def teardown_module(module): | ||||||
|  |     # make sure we reset the default | ||||||
|  |     Device._pin_factory.pwm = False | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_composite_output_on_off(): | def test_composite_output_on_off(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device: |     with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device: | ||||||
|         device.on() |         device.on() | ||||||
|         assert all((pin1.state, pin2.state, pin3.state)) |         assert all((pin1.state, pin2.state, pin3.state)) | ||||||
| @@ -38,9 +51,9 @@ def test_composite_output_on_off(): | |||||||
|         assert not any((pin1.state, pin2.state, pin3.state)) |         assert not any((pin1.state, pin2.state, pin3.state)) | ||||||
|  |  | ||||||
| def test_composite_output_toggle(): | def test_composite_output_toggle(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device: |     with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device: | ||||||
|         device.toggle() |         device.toggle() | ||||||
|         assert all((pin1.state, pin2.state, pin3.state)) |         assert all((pin1.state, pin2.state, pin3.state)) | ||||||
| @@ -51,9 +64,9 @@ def test_composite_output_toggle(): | |||||||
|         assert not pin3.state |         assert not pin3.state | ||||||
|  |  | ||||||
| def test_composite_output_value(): | def test_composite_output_value(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device: |     with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device: | ||||||
|         assert device.value == (0, 0, 0) |         assert device.value == (0, 0, 0) | ||||||
|         device.toggle() |         device.toggle() | ||||||
| @@ -64,9 +77,9 @@ def test_composite_output_value(): | |||||||
|         assert device[2].is_active |         assert device[2].is_active | ||||||
|  |  | ||||||
| def test_led_board_on_off(): | def test_led_board_on_off(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBoard(pin1, pin2, foo=pin3) as board: |     with LEDBoard(pin1, pin2, foo=pin3) as board: | ||||||
|         assert isinstance(board[0], LED) |         assert isinstance(board[0], LED) | ||||||
|         assert isinstance(board[1], LED) |         assert isinstance(board[1], LED) | ||||||
| @@ -121,9 +134,9 @@ def test_led_board_on_off(): | |||||||
|         assert pin3.state |         assert pin3.state | ||||||
|  |  | ||||||
| def test_led_board_active_low(): | def test_led_board_active_low(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBoard(pin1, pin2, foo=pin3, active_high=False) as board: |     with LEDBoard(pin1, pin2, foo=pin3, active_high=False) as board: | ||||||
|         assert not board.active_high |         assert not board.active_high | ||||||
|         assert not board[0].active_high |         assert not board[0].active_high | ||||||
| @@ -145,9 +158,9 @@ def test_led_board_active_low(): | |||||||
|         assert not pin3.state |         assert not pin3.state | ||||||
|  |  | ||||||
| def test_led_board_value(): | def test_led_board_value(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBoard(pin1, pin2, foo=pin3) as board: |     with LEDBoard(pin1, pin2, foo=pin3) as board: | ||||||
|         assert board.value == (0, 0, 0) |         assert board.value == (0, 0, 0) | ||||||
|         board.value = (0, 1, 0) |         board.value = (0, 1, 0) | ||||||
| @@ -156,9 +169,9 @@ def test_led_board_value(): | |||||||
|         assert board.value == (1, 0, 1) |         assert board.value == (1, 0, 1) | ||||||
|  |  | ||||||
| def test_led_board_pwm_value(): | def test_led_board_pwm_value(): | ||||||
|     pin1 = MockPWMPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPWMPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPWMPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board: |     with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board: | ||||||
|         assert board.value == (0, 0, 0) |         assert board.value == (0, 0, 0) | ||||||
|         board.value = (0, 1, 0) |         board.value = (0, 1, 0) | ||||||
| @@ -167,9 +180,9 @@ def test_led_board_pwm_value(): | |||||||
|         assert board.value == (0.5, 0, 0.75) |         assert board.value == (0.5, 0, 0.75) | ||||||
|  |  | ||||||
| def test_led_board_pwm_bad_value(): | def test_led_board_pwm_bad_value(): | ||||||
|     pin1 = MockPWMPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPWMPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPWMPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board: |     with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board: | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|             board.value = (-1, 0, 0) |             board.value = (-1, 0, 0) | ||||||
| @@ -177,18 +190,18 @@ def test_led_board_pwm_bad_value(): | |||||||
|             board.value = (0, 2, 0) |             board.value = (0, 2, 0) | ||||||
|  |  | ||||||
| def test_led_board_initial_value(): | def test_led_board_initial_value(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBoard(pin1, pin2, foo=pin3, initial_value=0) as board: |     with LEDBoard(pin1, pin2, foo=pin3, initial_value=0) as board: | ||||||
|         assert board.value == (0, 0, 0) |         assert board.value == (0, 0, 0) | ||||||
|     with LEDBoard(pin1, pin2, foo=pin3, initial_value=1) as board: |     with LEDBoard(pin1, pin2, foo=pin3, initial_value=1) as board: | ||||||
|         assert board.value == (1, 1, 1) |         assert board.value == (1, 1, 1) | ||||||
|  |  | ||||||
| def test_led_board_pwm_initial_value(): | def test_led_board_pwm_initial_value(): | ||||||
|     pin1 = MockPWMPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPWMPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPWMPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=0) as board: |     with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=0) as board: | ||||||
|         assert board.value == (0, 0, 0) |         assert board.value == (0, 0, 0) | ||||||
|     with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=1) as board: |     with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=1) as board: | ||||||
| @@ -197,18 +210,18 @@ def test_led_board_pwm_initial_value(): | |||||||
|         assert board.value == (0.5, 0.5, 0.5) |         assert board.value == (0.5, 0.5, 0.5) | ||||||
|  |  | ||||||
| def test_led_board_pwm_bad_initial_value(): | def test_led_board_pwm_bad_initial_value(): | ||||||
|     pin1 = MockPWMPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPWMPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPWMPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=-1) |         LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=-1) | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=2) |         LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=2) | ||||||
|  |  | ||||||
| def test_led_board_nested(): | def test_led_board_nested(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: |     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: | ||||||
|         assert list(led.pin for led in board.leds) == [pin1, pin2, pin3] |         assert list(led.pin for led in board.leds) == [pin1, pin2, pin3] | ||||||
|         assert board.value == (0, (0, 0)) |         assert board.value == (0, (0, 0)) | ||||||
| @@ -218,9 +231,9 @@ def test_led_board_nested(): | |||||||
|         assert pin3.state |         assert pin3.state | ||||||
|  |  | ||||||
| def test_led_board_bad_blink(): | def test_led_board_bad_blink(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: |     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|             board.blink(fade_in_time=1, fade_out_time=1) |             board.blink(fade_in_time=1, fade_out_time=1) | ||||||
| @@ -232,9 +245,9 @@ def test_led_board_bad_blink(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_led_board_blink_background(): | def test_led_board_blink_background(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: |     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: | ||||||
|         board.blink(0.1, 0.1, n=2) |         board.blink(0.1, 0.1, n=2) | ||||||
|         board._blink_thread.join() # naughty, but ensures no arbitrary waits in the test |         board._blink_thread.join() # naughty, but ensures no arbitrary waits in the test | ||||||
| @@ -252,9 +265,9 @@ def test_led_board_blink_background(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_led_board_blink_foreground(): | def test_led_board_blink_foreground(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: |     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: | ||||||
|         board.blink(0.1, 0.1, n=2, background=False) |         board.blink(0.1, 0.1, n=2, background=False) | ||||||
|         test = [ |         test = [ | ||||||
| @@ -271,9 +284,9 @@ def test_led_board_blink_foreground(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_led_board_blink_control(): | def test_led_board_blink_control(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: |     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: | ||||||
|         board.blink(0.1, 0.1, n=2) |         board.blink(0.1, 0.1, n=2) | ||||||
|         # make sure the blink thread's started |         # make sure the blink thread's started | ||||||
| @@ -296,9 +309,9 @@ def test_led_board_blink_control(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_led_board_blink_take_over(): | def test_led_board_blink_take_over(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: |     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: | ||||||
|         board[1].blink(0.1, 0.1, n=2) |         board[1].blink(0.1, 0.1, n=2) | ||||||
|         board.blink(0.1, 0.1, n=2) # immediately take over blinking |         board.blink(0.1, 0.1, n=2) # immediately take over blinking | ||||||
| @@ -318,9 +331,9 @@ def test_led_board_blink_take_over(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_led_board_blink_control_all(): | def test_led_board_blink_control_all(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: |     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: | ||||||
|         board.blink(0.1, 0.1, n=2) |         board.blink(0.1, 0.1, n=2) | ||||||
|         # make sure the blink thread's started |         # make sure the blink thread's started | ||||||
| @@ -340,9 +353,9 @@ def test_led_board_blink_control_all(): | |||||||
|         pin3.assert_states_and_times(test) |         pin3.assert_states_and_times(test) | ||||||
|  |  | ||||||
| def test_led_board_blink_interrupt_on(): | def test_led_board_blink_interrupt_on(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: |     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: | ||||||
|         board.blink(1, 0.1) |         board.blink(1, 0.1) | ||||||
|         sleep(0.2) |         sleep(0.2) | ||||||
| @@ -352,9 +365,9 @@ def test_led_board_blink_interrupt_on(): | |||||||
|         pin3.assert_states([False, True, False]) |         pin3.assert_states([False, True, False]) | ||||||
|  |  | ||||||
| def test_led_board_blink_interrupt_off(): | def test_led_board_blink_interrupt_off(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: |     with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board: | ||||||
|         board.blink(0.1, 1) |         board.blink(0.1, 1) | ||||||
|         sleep(0.2) |         sleep(0.2) | ||||||
| @@ -366,9 +379,9 @@ def test_led_board_blink_interrupt_off(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_led_board_fade_background(): | def test_led_board_fade_background(): | ||||||
|     pin1 = MockPWMPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPWMPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPWMPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBoard(pin1, LEDBoard(pin2, pin3, pwm=True), pwm=True) as board: |     with LEDBoard(pin1, LEDBoard(pin2, pin3, pwm=True), pwm=True) as board: | ||||||
|         board.blink(0, 0, 0.2, 0.2, n=2) |         board.blink(0, 0, 0.2, 0.2, n=2) | ||||||
|         board._blink_thread.join() |         board._blink_thread.join() | ||||||
| @@ -400,9 +413,9 @@ def test_led_board_fade_background(): | |||||||
|         pin3.assert_states_and_times(test) |         pin3.assert_states_and_times(test) | ||||||
|  |  | ||||||
| def test_led_bar_graph_value(): | def test_led_bar_graph_value(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBarGraph(pin1, pin2, pin3) as graph: |     with LEDBarGraph(pin1, pin2, pin3) as graph: | ||||||
|         assert isinstance(graph[0], LED) |         assert isinstance(graph[0], LED) | ||||||
|         assert isinstance(graph[1], LED) |         assert isinstance(graph[1], LED) | ||||||
| @@ -433,9 +446,9 @@ def test_led_bar_graph_value(): | |||||||
|         assert graph.value == -2/3 |         assert graph.value == -2/3 | ||||||
|  |  | ||||||
| def test_led_bar_graph_active_low(): | def test_led_bar_graph_active_low(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBarGraph(pin1, pin2, pin3, active_high=False) as graph: |     with LEDBarGraph(pin1, pin2, pin3, active_high=False) as graph: | ||||||
|         assert not graph.active_high |         assert not graph.active_high | ||||||
|         assert not graph[0].active_high |         assert not graph[0].active_high | ||||||
| @@ -455,9 +468,9 @@ def test_led_bar_graph_active_low(): | |||||||
|         assert not pin3.state and pin1.state and pin2.state |         assert not pin3.state and pin1.state and pin2.state | ||||||
|  |  | ||||||
| def test_led_bar_graph_pwm_value(): | def test_led_bar_graph_pwm_value(): | ||||||
|     pin1 = MockPWMPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPWMPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPWMPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBarGraph(pin1, pin2, pin3, pwm=True) as graph: |     with LEDBarGraph(pin1, pin2, pin3, pwm=True) as graph: | ||||||
|         assert isinstance(graph[0], PWMLED) |         assert isinstance(graph[0], PWMLED) | ||||||
|         assert isinstance(graph[1], PWMLED) |         assert isinstance(graph[1], PWMLED) | ||||||
| @@ -482,9 +495,9 @@ def test_led_bar_graph_pwm_value(): | |||||||
|         assert graph.value == -1/2 |         assert graph.value == -1/2 | ||||||
|  |  | ||||||
| def test_led_bar_graph_bad_value(): | def test_led_bar_graph_bad_value(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBarGraph(pin1, pin2, pin3) as graph: |     with LEDBarGraph(pin1, pin2, pin3) as graph: | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|             graph.value = -2 |             graph.value = -2 | ||||||
| @@ -492,9 +505,9 @@ def test_led_bar_graph_bad_value(): | |||||||
|             graph.value = 2 |             graph.value = 2 | ||||||
|  |  | ||||||
| def test_led_bar_graph_bad_init(): | def test_led_bar_graph_bad_init(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with pytest.raises(TypeError): |     with pytest.raises(TypeError): | ||||||
|         LEDBarGraph(pin1, pin2, foo=pin3) |         LEDBarGraph(pin1, pin2, foo=pin3) | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
| @@ -503,9 +516,9 @@ def test_led_bar_graph_bad_init(): | |||||||
|         LEDBarGraph(pin1, pin2, pin3, initial_value=2) |         LEDBarGraph(pin1, pin2, pin3, initial_value=2) | ||||||
|  |  | ||||||
| def test_led_bar_graph_initial_value(): | def test_led_bar_graph_initial_value(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBarGraph(pin1, pin2, pin3, initial_value=1/3) as graph: |     with LEDBarGraph(pin1, pin2, pin3, initial_value=1/3) as graph: | ||||||
|         assert graph.value == 1/3 |         assert graph.value == 1/3 | ||||||
|         assert pin1.state and not (pin2.state or pin3.state) |         assert pin1.state and not (pin2.state or pin3.state) | ||||||
| @@ -514,9 +527,9 @@ def test_led_bar_graph_initial_value(): | |||||||
|         assert pin3.state and not (pin1.state or pin2.state) |         assert pin3.state and not (pin1.state or pin2.state) | ||||||
|  |  | ||||||
| def test_led_bar_graph_pwm_initial_value(): | def test_led_bar_graph_pwm_initial_value(): | ||||||
|     pin1 = MockPWMPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPWMPin(3) |     pin2 = Device._pin_factory.pin(3) | ||||||
|     pin3 = MockPWMPin(4) |     pin3 = Device._pin_factory.pin(4) | ||||||
|     with LEDBarGraph(pin1, pin2, pin3, pwm=True, initial_value=0.5) as graph: |     with LEDBarGraph(pin1, pin2, pin3, pwm=True, initial_value=0.5) as graph: | ||||||
|         assert graph.value == 0.5 |         assert graph.value == 0.5 | ||||||
|         assert (pin1.state, pin2.state, pin3.state) == (1, 0.5, 0) |         assert (pin1.state, pin2.state, pin3.state) == (1, 0.5, 0) | ||||||
| @@ -525,17 +538,17 @@ def test_led_bar_graph_pwm_initial_value(): | |||||||
|         assert (pin1.state, pin2.state, pin3.state) == (0, 0.5, 1) |         assert (pin1.state, pin2.state, pin3.state) == (0, 0.5, 1) | ||||||
|  |  | ||||||
| def test_led_borg(): | def test_led_borg(): | ||||||
|     pins = [MockPWMPin(n) for n in (17, 27, 22)] |     pins = [Device._pin_factory.pin(n) for n in (17, 27, 22)] | ||||||
|     with LedBorg() as board: |     with LedBorg() as board: | ||||||
|         assert [device.pin for device in board._leds] == pins |         assert [device.pin for device in board._leds] == pins | ||||||
|  |  | ||||||
| def test_pi_liter(): | def test_pi_liter(): | ||||||
|     pins = [MockPin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)] |     pins = [Device._pin_factory.pin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)] | ||||||
|     with PiLiter() as board: |     with PiLiter() as board: | ||||||
|         assert [device.pin for device in board] == pins |         assert [device.pin for device in board] == pins | ||||||
|  |  | ||||||
| def test_pi_liter_graph(): | def test_pi_liter_graph(): | ||||||
|     pins = [MockPin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)] |     pins = [Device._pin_factory.pin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)] | ||||||
|     with PiLiterBarGraph() as board: |     with PiLiterBarGraph() as board: | ||||||
|         board.value = 0.5 |         board.value = 0.5 | ||||||
|         assert [pin.state for pin in pins] == [1, 1, 1, 1, 0, 0, 0, 0] |         assert [pin.state for pin in pins] == [1, 1, 1, 1, 0, 0, 0, 0] | ||||||
| @@ -543,9 +556,9 @@ def test_pi_liter_graph(): | |||||||
|         assert board.value == 5/8 |         assert board.value == 5/8 | ||||||
|  |  | ||||||
| def test_traffic_lights(): | def test_traffic_lights(): | ||||||
|     red_pin = MockPin(2) |     red_pin = Device._pin_factory.pin(2) | ||||||
|     amber_pin = MockPin(3) |     amber_pin = Device._pin_factory.pin(3) | ||||||
|     green_pin = MockPin(4) |     green_pin = Device._pin_factory.pin(4) | ||||||
|     with TrafficLights(red_pin, amber_pin, green_pin) as board: |     with TrafficLights(red_pin, amber_pin, green_pin) as board: | ||||||
|         board.red.on() |         board.red.on() | ||||||
|         assert board.red.value |         assert board.red.value | ||||||
| @@ -574,15 +587,15 @@ def test_traffic_lights(): | |||||||
| def test_traffic_lights_bad_init(): | def test_traffic_lights_bad_init(): | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         TrafficLights() |         TrafficLights() | ||||||
|     red_pin = MockPin(2) |     red_pin = Device._pin_factory.pin(2) | ||||||
|     amber_pin = MockPin(3) |     amber_pin = Device._pin_factory.pin(3) | ||||||
|     green_pin = MockPin(4) |     green_pin = Device._pin_factory.pin(4) | ||||||
|     yellow_pin = MockPin(5) |     yellow_pin = Device._pin_factory.pin(5) | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         TrafficLights(red=red_pin, amber=amber_pin, yellow=yellow_pin, green=green_pin) |         TrafficLights(red=red_pin, amber=amber_pin, yellow=yellow_pin, green=green_pin) | ||||||
|  |  | ||||||
| def test_pi_traffic(): | def test_pi_traffic(): | ||||||
|     pins = [MockPin(n) for n in (9, 10, 11)] |     pins = [Device._pin_factory.pin(n) for n in (9, 10, 11)] | ||||||
|     with PiTraffic() as board: |     with PiTraffic() as board: | ||||||
|         assert [device.pin for device in board] == pins |         assert [device.pin for device in board] == pins | ||||||
|  |  | ||||||
| @@ -591,27 +604,27 @@ def test_pi_stop(): | |||||||
|         PiStop() |         PiStop() | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         PiStop('E') |         PiStop('E') | ||||||
|     pins_a = [MockPin(n) for n in (7, 8, 25)] |     pins_a = [Device._pin_factory.pin(n) for n in (7, 8, 25)] | ||||||
|     with PiStop('A') as board: |     with PiStop('A') as board: | ||||||
|         assert [device.pin for device in board] == pins_a |         assert [device.pin for device in board] == pins_a | ||||||
|     pins_aplus = [MockPin(n) for n in (21, 20, 16)] |     pins_aplus = [Device._pin_factory.pin(n) for n in (21, 20, 16)] | ||||||
|     with PiStop('A+') as board: |     with PiStop('A+') as board: | ||||||
|         assert [device.pin for device in board] == pins_aplus |         assert [device.pin for device in board] == pins_aplus | ||||||
|     pins_b = [MockPin(n) for n in (10, 9, 11)] |     pins_b = [Device._pin_factory.pin(n) for n in (10, 9, 11)] | ||||||
|     with PiStop('B') as board: |     with PiStop('B') as board: | ||||||
|         assert [device.pin for device in board] == pins_b |         assert [device.pin for device in board] == pins_b | ||||||
|     pins_bplus = [MockPin(n) for n in (13, 19, 26)] |     pins_bplus = [Device._pin_factory.pin(n) for n in (13, 19, 26)] | ||||||
|     with PiStop('B+') as board: |     with PiStop('B+') as board: | ||||||
|         assert [device.pin for device in board] == pins_bplus |         assert [device.pin for device in board] == pins_bplus | ||||||
|     pins_c = [MockPin(n) for n in (18, 15, 14)] |     pins_c = [Device._pin_factory.pin(n) for n in (18, 15, 14)] | ||||||
|     with PiStop('C') as board: |     with PiStop('C') as board: | ||||||
|         assert [device.pin for device in board] == pins_c |         assert [device.pin for device in board] == pins_c | ||||||
|     pins_d = [MockPin(n) for n in (2, 3, 4)] |     pins_d = [Device._pin_factory.pin(n) for n in (2, 3, 4)] | ||||||
|     with PiStop('D') as board: |     with PiStop('D') as board: | ||||||
|         assert [device.pin for device in board] == pins_d |         assert [device.pin for device in board] == pins_d | ||||||
|  |  | ||||||
| def test_snow_pi(): | def test_snow_pi(): | ||||||
|     pins = [MockPin(n) for n in (23, 24, 25, 17, 18, 22, 7, 8, 9)] |     pins = [Device._pin_factory.pin(n) for n in (23, 24, 25, 17, 18, 22, 7, 8, 9)] | ||||||
|     with SnowPi() as board: |     with SnowPi() as board: | ||||||
|         assert [device.pin for device in board.leds] == pins |         assert [device.pin for device in board.leds] == pins | ||||||
|  |  | ||||||
| @@ -626,17 +639,17 @@ def test_snow_pi_initial_value(): | |||||||
|         assert all(device.pin.state == True for device in board.leds) |         assert all(device.pin.state == True for device in board.leds) | ||||||
|  |  | ||||||
| def test_snow_pi_initial_value_pwm(): | def test_snow_pi_initial_value_pwm(): | ||||||
|     pins = [MockPWMPin(n) for n in (23, 24, 25, 17, 18, 22, 7, 8, 9)] |     pins = [Device._pin_factory.pin(n) for n in (23, 24, 25, 17, 18, 22, 7, 8, 9)] | ||||||
|     with SnowPi(pwm=True, initial_value=0.5) as board: |     with SnowPi(pwm=True, initial_value=0.5) as board: | ||||||
|         assert [device.pin for device in board.leds] == pins |         assert [device.pin for device in board.leds] == pins | ||||||
|         assert all(device.pin.state == 0.5 for device in board.leds) |         assert all(device.pin.state == 0.5 for device in board.leds) | ||||||
|  |  | ||||||
| def test_traffic_lights_buzzer(): | def test_traffic_lights_buzzer(): | ||||||
|     red_pin = MockPin(2) |     red_pin = Device._pin_factory.pin(2) | ||||||
|     amber_pin = MockPin(3) |     amber_pin = Device._pin_factory.pin(3) | ||||||
|     green_pin = MockPin(4) |     green_pin = Device._pin_factory.pin(4) | ||||||
|     buzzer_pin = MockPin(5) |     buzzer_pin = Device._pin_factory.pin(5) | ||||||
|     button_pin = MockPin(6) |     button_pin = Device._pin_factory.pin(6) | ||||||
|     with TrafficLightsBuzzer( |     with TrafficLightsBuzzer( | ||||||
|             TrafficLights(red_pin, amber_pin, green_pin), |             TrafficLights(red_pin, amber_pin, green_pin), | ||||||
|             Buzzer(buzzer_pin), |             Buzzer(buzzer_pin), | ||||||
| @@ -651,17 +664,17 @@ def test_traffic_lights_buzzer(): | |||||||
|         assert board.button.is_active |         assert board.button.is_active | ||||||
|  |  | ||||||
| def test_fish_dish(): | def test_fish_dish(): | ||||||
|     pins = [MockPin(n) for n in (9, 22, 4, 8, 7)] |     pins = [Device._pin_factory.pin(n) for n in (9, 22, 4, 8, 7)] | ||||||
|     with FishDish() as board: |     with FishDish() as board: | ||||||
|         assert [led.pin for led in board.lights] + [board.buzzer.pin, board.button.pin] == pins |         assert [led.pin for led in board.lights] + [board.buzzer.pin, board.button.pin] == pins | ||||||
|  |  | ||||||
| def test_traffic_hat(): | def test_traffic_hat(): | ||||||
|     pins = [MockPin(n) for n in (24, 23, 22, 5, 25)] |     pins = [Device._pin_factory.pin(n) for n in (24, 23, 22, 5, 25)] | ||||||
|     with TrafficHat() as board: |     with TrafficHat() as board: | ||||||
|         assert [led.pin for led in board.lights] + [board.buzzer.pin, board.button.pin] == pins |         assert [led.pin for led in board.lights] + [board.buzzer.pin, board.button.pin] == pins | ||||||
|  |  | ||||||
| def test_robot(): | def test_robot(): | ||||||
|     pins = [MockPWMPin(n) for n in (2, 3, 4, 5)] |     pins = [Device._pin_factory.pin(n) for n in (2, 3, 4, 5)] | ||||||
|     with Robot((2, 3), (4, 5)) as robot: |     with Robot((2, 3), (4, 5)) as robot: | ||||||
|         assert ( |         assert ( | ||||||
|             [device.pin for device in robot.left_motor] + |             [device.pin for device in robot.left_motor] + | ||||||
| @@ -696,12 +709,12 @@ def test_robot(): | |||||||
|         assert robot.value == (0, -0.5) |         assert robot.value == (0, -0.5) | ||||||
|  |  | ||||||
| def test_ryanteck_robot(): | def test_ryanteck_robot(): | ||||||
|     pins = [MockPWMPin(n) for n in (17, 18, 22, 23)] |     pins = [Device._pin_factory.pin(n) for n in (17, 18, 22, 23)] | ||||||
|     with RyanteckRobot() as board: |     with RyanteckRobot() as board: | ||||||
|         assert [device.pin for motor in board for device in motor] == pins |         assert [device.pin for motor in board for device in motor] == pins | ||||||
|  |  | ||||||
| def test_camjam_kit_robot(): | def test_camjam_kit_robot(): | ||||||
|     pins = [MockPWMPin(n) for n in (9, 10, 7, 8)] |     pins = [Device._pin_factory.pin(n) for n in (9, 10, 7, 8)] | ||||||
|     with CamJamKitRobot() as board: |     with CamJamKitRobot() as board: | ||||||
|         assert [device.pin for motor in board for device in motor] == pins |         assert [device.pin for motor in board for device in motor] == pins | ||||||
|  |  | ||||||
| @@ -714,7 +727,7 @@ def test_energenie_bad_init(): | |||||||
|         Energenie(5) |         Energenie(5) | ||||||
|  |  | ||||||
| def test_energenie(): | def test_energenie(): | ||||||
|     pins = [MockPin(n) for n in (17, 22, 23, 27, 24, 25)] |     pins = [Device._pin_factory.pin(n) for n in (17, 22, 23, 27, 24, 25)] | ||||||
|     with Energenie(1, initial_value=True) as device1, \ |     with Energenie(1, initial_value=True) as device1, \ | ||||||
|             Energenie(2, initial_value=False) as device2: |             Energenie(2, initial_value=False) as device2: | ||||||
|         assert repr(device1) == '<gpiozero.Energenie object on socket 1>' |         assert repr(device1) == '<gpiozero.Energenie object on socket 1>' | ||||||
|   | |||||||
| @@ -6,86 +6,87 @@ from __future__ import ( | |||||||
|     ) |     ) | ||||||
| str = type('') | str = type('') | ||||||
|  |  | ||||||
|  | import warnings | ||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
| from gpiozero.pins.mock import MockPin |  | ||||||
| from gpiozero import * | from gpiozero import * | ||||||
|  |  | ||||||
|  |  | ||||||
| def teardown_function(function): | def teardown_function(function): | ||||||
|     MockPin.clear_pins() |     Device._pin_factory.reset() | ||||||
|  |  | ||||||
|  |  | ||||||
| # TODO add more devices tests! | # TODO add more devices tests! | ||||||
|  |  | ||||||
| def test_device_no_pin(): | def test_device_bad_pin(): | ||||||
|     with pytest.raises(GPIOPinMissing): |     with pytest.raises(GPIOPinMissing): | ||||||
|         device = GPIODevice() |         device = GPIODevice() | ||||||
|  |     with pytest.raises(PinInvalidPin): | ||||||
|  |         device = GPIODevice(60) | ||||||
|  |  | ||||||
|  | def test_device_non_physical(): | ||||||
|  |     with warnings.catch_warnings(record=True) as w: | ||||||
|  |         device = GPIODevice(37) | ||||||
|  |         assert len(w) == 1 | ||||||
|  |         assert w[0].category == PinNonPhysical | ||||||
|  |  | ||||||
| def test_device_init(): | def test_device_init(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with GPIODevice(pin) as device: |     with GPIODevice(pin) as device: | ||||||
|         assert not device.closed |         assert not device.closed | ||||||
|         assert device.pin == pin |         assert device.pin == pin | ||||||
|  |  | ||||||
| def test_device_init_twice_same_pin(): | def test_device_init_twice_same_pin(): | ||||||
|     pin = MockPin(2) |     with GPIODevice(2) as device: | ||||||
|     with GPIODevice(pin) as device: |  | ||||||
|         with pytest.raises(GPIOPinInUse): |         with pytest.raises(GPIOPinInUse): | ||||||
|             device2 = GPIODevice(pin) |             GPIODevice(2) | ||||||
|  |  | ||||||
| def test_device_init_twice_different_pin(): | def test_device_init_twice_different_pin(): | ||||||
|     pin = MockPin(2) |     with GPIODevice(2) as device: | ||||||
|     pin2 = MockPin(3) |         with GPIODevice(3) as device2: | ||||||
|     with GPIODevice(pin) as device: |  | ||||||
|         with GPIODevice(pin2) as device2: |  | ||||||
|             pass |             pass | ||||||
|  |  | ||||||
| def test_device_close(): | def test_device_close(): | ||||||
|     pin = MockPin(2) |     device = GPIODevice(2) | ||||||
|     device = GPIODevice(pin) |  | ||||||
|     device.close() |     device.close() | ||||||
|     assert device.closed |     assert device.closed | ||||||
|     assert device.pin is None |     assert device.pin is None | ||||||
|  |  | ||||||
| def test_device_reopen_same_pin(): | def test_device_reopen_same_pin(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     device = GPIODevice(pin) |     device = GPIODevice(pin) | ||||||
|     device.close() |     device.close() | ||||||
|     device2 = GPIODevice(pin) |     device2 = GPIODevice(pin) | ||||||
|     assert not device2.closed |     assert not device2.closed | ||||||
|     assert device2.pin == pin |     assert device2.pin is pin | ||||||
|     assert device.closed |     assert device.closed | ||||||
|     assert device.pin is None |     assert device.pin is None | ||||||
|     device2.close() |     device2.close() | ||||||
|  |  | ||||||
| def test_device_repr(): | def test_device_repr(): | ||||||
|     pin = MockPin(2) |     with GPIODevice(2) as device: | ||||||
|     with GPIODevice(pin) as device: |         assert repr(device) == '<gpiozero.GPIODevice object on pin %s, is_active=False>' % device.pin | ||||||
|         assert repr(device) == '<gpiozero.GPIODevice object on pin %s, is_active=False>' % pin |  | ||||||
|  |  | ||||||
| def test_device_repr_after_close(): | def test_device_repr_after_close(): | ||||||
|     pin = MockPin(2) |     device = GPIODevice(2) | ||||||
|     device = GPIODevice(pin) |  | ||||||
|     device.close() |     device.close() | ||||||
|     assert repr(device) == '<gpiozero.GPIODevice object closed>' |     assert repr(device) == '<gpiozero.GPIODevice object closed>' | ||||||
|  |  | ||||||
| def test_device_unknown_attr(): | def test_device_unknown_attr(): | ||||||
|     pin = MockPin(2) |     with GPIODevice(2) as device: | ||||||
|     with GPIODevice(pin) as device: |  | ||||||
|         with pytest.raises(AttributeError): |         with pytest.raises(AttributeError): | ||||||
|             device.foo = 1 |             device.foo = 1 | ||||||
|  |  | ||||||
| def test_device_context_manager(): | def test_device_context_manager(): | ||||||
|     pin = MockPin(2) |     with GPIODevice(2) as device: | ||||||
|     with GPIODevice(pin) as device: |  | ||||||
|         assert not device.closed |         assert not device.closed | ||||||
|     assert device.closed |     assert device.closed | ||||||
|  |  | ||||||
| def test_composite_device_sequence(): | def test_composite_device_sequence(): | ||||||
|     with CompositeDevice( |     with CompositeDevice( | ||||||
|             InputDevice(MockPin(2)), |             InputDevice(2), | ||||||
|             InputDevice(MockPin(3)) |             InputDevice(3) | ||||||
|             ) as device: |             ) as device: | ||||||
|         assert len(device) == 2 |         assert len(device) == 2 | ||||||
|         assert device[0].pin.number == 2 |         assert device[0].pin.number == 2 | ||||||
| @@ -94,8 +95,8 @@ def test_composite_device_sequence(): | |||||||
|  |  | ||||||
| def test_composite_device_values(): | def test_composite_device_values(): | ||||||
|     with CompositeDevice( |     with CompositeDevice( | ||||||
|             InputDevice(MockPin(2)), |             InputDevice(2), | ||||||
|             InputDevice(MockPin(3)) |             InputDevice(3) | ||||||
|             ) as device: |             ) as device: | ||||||
|         assert device.value == (0, 0) |         assert device.value == (0, 0) | ||||||
|         assert not device.is_active |         assert not device.is_active | ||||||
| @@ -105,8 +106,8 @@ def test_composite_device_values(): | |||||||
|  |  | ||||||
| def test_composite_device_named(): | def test_composite_device_named(): | ||||||
|     with CompositeDevice( |     with CompositeDevice( | ||||||
|             foo=InputDevice(MockPin(2)), |             foo=InputDevice(2), | ||||||
|             bar=InputDevice(MockPin(3)), |             bar=InputDevice(3), | ||||||
|             _order=('foo', 'bar') |             _order=('foo', 'bar') | ||||||
|             ) as device: |             ) as device: | ||||||
|         assert device.namedtuple._fields == ('foo', 'bar') |         assert device.namedtuple._fields == ('foo', 'bar') | ||||||
| @@ -121,13 +122,13 @@ def test_composite_device_bad_init(): | |||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         CompositeDevice(2) |         CompositeDevice(2) | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         CompositeDevice(MockPin(2)) |         CompositeDevice(Device._pin_factory.pin(2)) | ||||||
|  |  | ||||||
| def test_composite_device_read_only(): | def test_composite_device_read_only(): | ||||||
|     device = CompositeDevice( |     with CompositeDevice( | ||||||
|         foo=InputDevice(MockPin(2)), |         foo=InputDevice(2), | ||||||
|         bar=InputDevice(MockPin(3)) |         bar=InputDevice(3) | ||||||
|         ) |         ) as device: | ||||||
|         with pytest.raises(AttributeError): |         with pytest.raises(AttributeError): | ||||||
|             device.foo = 1 |             device.foo = 1 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,57 +12,52 @@ import pytest | |||||||
| from threading import Event | from threading import Event | ||||||
| from functools import partial | from functools import partial | ||||||
|  |  | ||||||
| from gpiozero.pins.mock import ( | from gpiozero.pins.mock import MockPulledUpPin, MockChargingPin, MockTriggerPin | ||||||
|     MockPin, |  | ||||||
|     MockPulledUpPin, |  | ||||||
|     MockChargingPin, |  | ||||||
|     MockTriggerPin, |  | ||||||
|     ) |  | ||||||
| from gpiozero import * | from gpiozero import * | ||||||
|  |  | ||||||
|  |  | ||||||
| def teardown_function(function): | def teardown_function(function): | ||||||
|     MockPin.clear_pins() |     Device._pin_factory.reset() | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_input_initial_values(): | def test_input_initial_values(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with InputDevice(pin, pull_up=True) as device: |     with InputDevice(pin, pull_up=True) as device: | ||||||
|         assert pin.function == 'input' |         assert pin.function == 'input' | ||||||
|         assert pin.pull == 'up' |         assert pin.pull == 'up' | ||||||
|         assert device.pull_up |         assert device.pull_up | ||||||
|         device.close() |     with InputDevice(pin, pull_up=False) as device: | ||||||
|         device = InputDevice(pin, pull_up=False) |  | ||||||
|         assert pin.pull == 'down' |         assert pin.pull == 'down' | ||||||
|         assert not device.pull_up |         assert not device.pull_up | ||||||
|  |  | ||||||
| def test_input_is_active_low(): | def test_input_is_active_low(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with InputDevice(pin, pull_up=True) as device: |     with InputDevice(pin, pull_up=True) as device: | ||||||
|         pin.drive_high() |         pin.drive_high() | ||||||
|         assert not device.is_active |         assert not device.is_active | ||||||
|         assert repr(device) == '<gpiozero.InputDevice object on pin MOCK2, pull_up=True, is_active=False>' |         assert repr(device) == '<gpiozero.InputDevice object on pin GPIO2, pull_up=True, is_active=False>' | ||||||
|         pin.drive_low() |         pin.drive_low() | ||||||
|         assert device.is_active |         assert device.is_active | ||||||
|         assert repr(device) == '<gpiozero.InputDevice object on pin MOCK2, pull_up=True, is_active=True>' |         assert repr(device) == '<gpiozero.InputDevice object on pin GPIO2, pull_up=True, is_active=True>' | ||||||
|  |  | ||||||
| def test_input_is_active_high(): | def test_input_is_active_high(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with InputDevice(pin, pull_up=False) as device: |     with InputDevice(pin, pull_up=False) as device: | ||||||
|         pin.drive_high() |         pin.drive_high() | ||||||
|         assert device.is_active |         assert device.is_active | ||||||
|         assert repr(device) == '<gpiozero.InputDevice object on pin MOCK2, pull_up=False, is_active=True>' |         assert repr(device) == '<gpiozero.InputDevice object on pin GPIO2, pull_up=False, is_active=True>' | ||||||
|         pin.drive_low() |         pin.drive_low() | ||||||
|         assert not device.is_active |         assert not device.is_active | ||||||
|         assert repr(device) == '<gpiozero.InputDevice object on pin MOCK2, pull_up=False, is_active=False>' |         assert repr(device) == '<gpiozero.InputDevice object on pin GPIO2, pull_up=False, is_active=False>' | ||||||
|  |  | ||||||
| def test_input_pulled_up(): | def test_input_pulled_up(): | ||||||
|     pin = MockPulledUpPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPulledUpPin) | ||||||
|     with pytest.raises(PinFixedPull): |     with pytest.raises(PinFixedPull): | ||||||
|         InputDevice(pin, pull_up=False) |         InputDevice(pin, pull_up=False) | ||||||
|  |  | ||||||
| def test_input_event_activated(): | def test_input_event_activated(): | ||||||
|     event = Event() |     event = Event() | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with DigitalInputDevice(pin) as device: |     with DigitalInputDevice(pin) as device: | ||||||
|         device.when_activated = lambda: event.set() |         device.when_activated = lambda: event.set() | ||||||
|         assert not event.is_set() |         assert not event.is_set() | ||||||
| @@ -71,7 +66,7 @@ def test_input_event_activated(): | |||||||
|  |  | ||||||
| def test_input_event_deactivated(): | def test_input_event_deactivated(): | ||||||
|     event = Event() |     event = Event() | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with DigitalInputDevice(pin) as device: |     with DigitalInputDevice(pin) as device: | ||||||
|         device.when_deactivated = lambda: event.set() |         device.when_deactivated = lambda: event.set() | ||||||
|         assert not event.is_set() |         assert not event.is_set() | ||||||
| @@ -82,7 +77,7 @@ def test_input_event_deactivated(): | |||||||
|  |  | ||||||
| def test_input_partial_callback(): | def test_input_partial_callback(): | ||||||
|     event = Event() |     event = Event() | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     def foo(a, b): |     def foo(a, b): | ||||||
|         event.set() |         event.set() | ||||||
|         return a + b |         return a + b | ||||||
| @@ -95,22 +90,22 @@ def test_input_partial_callback(): | |||||||
|         assert event.is_set() |         assert event.is_set() | ||||||
|  |  | ||||||
| def test_input_wait_active(): | def test_input_wait_active(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with DigitalInputDevice(pin) as device: |     with DigitalInputDevice(pin) as device: | ||||||
|         pin.drive_high() |         pin.drive_high() | ||||||
|         assert device.wait_for_active(1) |         assert device.wait_for_active(1) | ||||||
|         assert not device.wait_for_inactive(0) |         assert not device.wait_for_inactive(0) | ||||||
|  |  | ||||||
| def test_input_wait_inactive(): | def test_input_wait_inactive(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with DigitalInputDevice(pin) as device: |     with DigitalInputDevice(pin) as device: | ||||||
|         assert device.wait_for_inactive(1) |         assert device.wait_for_inactive(1) | ||||||
|         assert not device.wait_for_active(0) |         assert not device.wait_for_active(0) | ||||||
|  |  | ||||||
| def test_input_smoothed_attrib(): | def test_input_smoothed_attrib(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with SmoothedInputDevice(pin, threshold=0.5, queue_len=5, partial=False) as device: |     with SmoothedInputDevice(pin, threshold=0.5, queue_len=5, partial=False) as device: | ||||||
|         assert repr(device) == '<gpiozero.SmoothedInputDevice object on pin=MOCK2, pull_up=False>' |         assert repr(device) == '<gpiozero.SmoothedInputDevice object on pin GPIO2, pull_up=False>' | ||||||
|         assert device.threshold == 0.5 |         assert device.threshold == 0.5 | ||||||
|         assert device.queue_len == 5 |         assert device.queue_len == 5 | ||||||
|         assert not device.partial |         assert not device.partial | ||||||
| @@ -120,7 +115,7 @@ def test_input_smoothed_attrib(): | |||||||
|             device.threshold = 1 |             device.threshold = 1 | ||||||
|  |  | ||||||
| def test_input_smoothed_values(): | def test_input_smoothed_values(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with SmoothedInputDevice(pin) as device: |     with SmoothedInputDevice(pin) as device: | ||||||
|         device._queue.start() |         device._queue.start() | ||||||
|         assert not device.is_active |         assert not device.is_active | ||||||
| @@ -130,7 +125,7 @@ def test_input_smoothed_values(): | |||||||
|         assert device.wait_for_inactive(1) |         assert device.wait_for_inactive(1) | ||||||
|  |  | ||||||
| def test_input_button(): | def test_input_button(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with Button(pin) as button: |     with Button(pin) as button: | ||||||
|         assert pin.pull == 'up' |         assert pin.pull == 'up' | ||||||
|         assert not button.is_pressed |         assert not button.is_pressed | ||||||
| @@ -142,7 +137,7 @@ def test_input_button(): | |||||||
|         assert button.wait_for_release(1) |         assert button.wait_for_release(1) | ||||||
|  |  | ||||||
| def test_input_line_sensor(): | def test_input_line_sensor(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with LineSensor(pin) as sensor: |     with LineSensor(pin) as sensor: | ||||||
|         pin.drive_low() # logic is inverted for line sensor |         pin.drive_low() # logic is inverted for line sensor | ||||||
|         assert sensor.wait_for_line(1) |         assert sensor.wait_for_line(1) | ||||||
| @@ -152,7 +147,7 @@ def test_input_line_sensor(): | |||||||
|         assert not sensor.line_detected |         assert not sensor.line_detected | ||||||
|  |  | ||||||
| def test_input_motion_sensor(): | def test_input_motion_sensor(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with MotionSensor(pin) as sensor: |     with MotionSensor(pin) as sensor: | ||||||
|         pin.drive_high() |         pin.drive_high() | ||||||
|         assert sensor.wait_for_motion(1) |         assert sensor.wait_for_motion(1) | ||||||
| @@ -164,7 +159,7 @@ def test_input_motion_sensor(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_input_light_sensor(): | def test_input_light_sensor(): | ||||||
|     pin = MockChargingPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockChargingPin) | ||||||
|     with LightSensor(pin) as sensor: |     with LightSensor(pin) as sensor: | ||||||
|         pin.charge_time = 0.1 |         pin.charge_time = 0.1 | ||||||
|         assert sensor.wait_for_dark(1) |         assert sensor.wait_for_dark(1) | ||||||
| @@ -174,8 +169,8 @@ def test_input_light_sensor(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_input_distance_sensor(): | def test_input_distance_sensor(): | ||||||
|     echo_pin = MockPin(2) |     echo_pin = Device._pin_factory.pin(2) | ||||||
|     trig_pin = MockTriggerPin(3) |     trig_pin = Device._pin_factory.pin(3, pin_class=MockTriggerPin) | ||||||
|     trig_pin.echo_pin = echo_pin |     trig_pin.echo_pin = echo_pin | ||||||
|     trig_pin.echo_time = 0.02 |     trig_pin.echo_time = 0.02 | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|   | |||||||
| @@ -11,25 +11,24 @@ from threading import Event | |||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
| from gpiozero.pins.mock import MockPin, MockPWMPin | from gpiozero.pins.mock import MockPWMPin, MockPin | ||||||
| from gpiozero import * | from gpiozero import * | ||||||
|  |  | ||||||
|  |  | ||||||
| def teardown_function(function): | def teardown_function(function): | ||||||
|     MockPin.clear_pins() |     Device._pin_factory.reset() | ||||||
|  |  | ||||||
|  |  | ||||||
| # Some rough tests to make sure our MockPin is up to snuff. This is just | # 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... | # enough to get reasonable coverage but it's by no means comprehensive... | ||||||
|  |  | ||||||
| def test_mock_pin_init(): | def test_mock_pin_init(): | ||||||
|     with pytest.raises(TypeError): |  | ||||||
|         MockPin() |  | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         MockPin(60) |         Device._pin_factory.pin(60) | ||||||
|     assert MockPin(2).number == 2 |     assert Device._pin_factory.pin(2).number == 2 | ||||||
|  |  | ||||||
| def test_mock_pin_defaults(): | def test_mock_pin_defaults(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     assert pin.bounce == None |     assert pin.bounce == None | ||||||
|     assert pin.edges == 'both' |     assert pin.edges == 'both' | ||||||
|     assert pin.frequency == None |     assert pin.frequency == None | ||||||
| @@ -39,30 +38,23 @@ def test_mock_pin_defaults(): | |||||||
|     assert pin.when_changed == None |     assert pin.when_changed == None | ||||||
|  |  | ||||||
| def test_mock_pin_open_close(): | def test_mock_pin_open_close(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     pin.close() |     pin.close() | ||||||
|  |  | ||||||
| def test_mock_pin_init_twice_same_pin(): | def test_mock_pin_init_twice_same_pin(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(pin1.number) |     pin2 = Device._pin_factory.pin(pin1.number) | ||||||
|     assert pin1 is pin2 |     assert pin1 is pin2 | ||||||
|  |  | ||||||
| def test_mock_pin_init_twice_different_pin(): | def test_mock_pin_init_twice_different_pin(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2) | ||||||
|     pin2 = MockPin(pin1.number+1) |     pin2 = Device._pin_factory.pin(pin1.number+1) | ||||||
|     assert pin1 != pin2 |     assert pin1 != pin2 | ||||||
|     assert pin1.number == 2 |     assert pin1.number == 2 | ||||||
|     assert pin2.number == pin1.number+1 |     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(): | def test_mock_pwm_pin_defaults(): | ||||||
|     pin = MockPWMPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     assert pin.bounce == None |     assert pin.bounce == None | ||||||
|     assert pin.edges == 'both' |     assert pin.edges == 'both' | ||||||
|     assert pin.frequency == None |     assert pin.frequency == None | ||||||
| @@ -72,38 +64,38 @@ def test_mock_pwm_pin_defaults(): | |||||||
|     assert pin.when_changed == None |     assert pin.when_changed == None | ||||||
|  |  | ||||||
| def test_mock_pwm_pin_open_close(): | def test_mock_pwm_pin_open_close(): | ||||||
|     pin = MockPWMPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     pin.close() |     pin.close() | ||||||
|  |  | ||||||
| def test_mock_pwm_pin_init_twice_same_pin(): | def test_mock_pwm_pin_init_twice_same_pin(): | ||||||
|     pin1 = MockPWMPin(2) |     pin1 = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     pin2 = MockPWMPin(pin1.number) |     pin2 = Device._pin_factory.pin(pin1.number, pin_class=MockPWMPin) | ||||||
|     assert pin1 is pin2 |     assert pin1 is pin2 | ||||||
|  |  | ||||||
| def test_mock_pwm_pin_init_twice_different_pin(): | def test_mock_pwm_pin_init_twice_different_pin(): | ||||||
|     pin1 = MockPWMPin(2) |     pin1 = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     pin2 = MockPWMPin(pin1.number+1) |     pin2 = Device._pin_factory.pin(pin1.number + 1, pin_class=MockPWMPin) | ||||||
|     assert pin1 != pin2 |     assert pin1 != pin2 | ||||||
|     assert pin1.number == 2 |     assert pin1.number == 2 | ||||||
|     assert pin2.number == pin1.number+1 |     assert pin2.number == pin1.number+1 | ||||||
|  |  | ||||||
| def test_mock_pin_init_twice_different_modes(): | def test_mock_pin_init_twice_different_modes(): | ||||||
|     pin1 = MockPin(2) |     pin1 = Device._pin_factory.pin(2, pin_class=MockPin) | ||||||
|     pin2 = MockPWMPin(pin1.number+1) |     pin2 = Device._pin_factory.pin(pin1.number + 1, pin_class=MockPWMPin) | ||||||
|     assert pin1 != pin2 |     assert pin1 != pin2 | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         pin3 = MockPWMPin(pin1.number) |         Device._pin_factory.pin(pin1.number, pin_class=MockPWMPin) | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         pin4 = MockPin(pin2.number) |         Device._pin_factory.pin(pin2.number, pin_class=MockPin) | ||||||
|  |  | ||||||
| def test_mock_pin_frequency_unsupported(): | def test_mock_pin_frequency_unsupported(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     pin.frequency = None |     pin.frequency = None | ||||||
|     with pytest.raises(PinPWMUnsupported): |     with pytest.raises(PinPWMUnsupported): | ||||||
|         pin.frequency = 100 |         pin.frequency = 100 | ||||||
|  |  | ||||||
| def test_mock_pin_frequency_supported(): | def test_mock_pin_frequency_supported(): | ||||||
|     pin = MockPWMPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     pin.function = 'output' |     pin.function = 'output' | ||||||
|     assert pin.frequency is None |     assert pin.frequency is None | ||||||
|     pin.frequency = 100 |     pin.frequency = 100 | ||||||
| @@ -112,7 +104,7 @@ def test_mock_pin_frequency_supported(): | |||||||
|     assert not pin.state |     assert not pin.state | ||||||
|  |  | ||||||
| def test_mock_pin_pull(): | def test_mock_pin_pull(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     pin.function = 'input' |     pin.function = 'input' | ||||||
|     assert pin.pull == 'floating' |     assert pin.pull == 'floating' | ||||||
|     pin.pull = 'up' |     pin.pull = 'up' | ||||||
| @@ -121,7 +113,7 @@ def test_mock_pin_pull(): | |||||||
|     assert not pin.state |     assert not pin.state | ||||||
|  |  | ||||||
| def test_mock_pin_state(): | def test_mock_pin_state(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with pytest.raises(PinSetInput): |     with pytest.raises(PinSetInput): | ||||||
|         pin.state = 1 |         pin.state = 1 | ||||||
|     pin.function = 'output' |     pin.function = 'output' | ||||||
| @@ -134,7 +126,7 @@ def test_mock_pin_state(): | |||||||
|     assert pin.state == 1 |     assert pin.state == 1 | ||||||
|  |  | ||||||
| def test_mock_pwm_pin_state(): | def test_mock_pwm_pin_state(): | ||||||
|     pin = MockPWMPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with pytest.raises(PinSetInput): |     with pytest.raises(PinSetInput): | ||||||
|         pin.state = 1 |         pin.state = 1 | ||||||
|     pin.function = 'output' |     pin.function = 'output' | ||||||
| @@ -147,7 +139,7 @@ def test_mock_pwm_pin_state(): | |||||||
|     assert pin.state == 0.5 |     assert pin.state == 0.5 | ||||||
|  |  | ||||||
| def test_mock_pin_edges(): | def test_mock_pin_edges(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     assert pin.when_changed is None |     assert pin.when_changed is None | ||||||
|     fired = Event() |     fired = Event() | ||||||
|     pin.function = 'input' |     pin.function = 'input' | ||||||
|   | |||||||
| @@ -16,15 +16,16 @@ except ImportError: | |||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
| from gpiozero.pins.mock import MockPin, MockPWMPin | from gpiozero.pins.mock import MockPWMPin | ||||||
| from gpiozero import * | from gpiozero import * | ||||||
|  |  | ||||||
|  |  | ||||||
| def teardown_function(function): | def teardown_function(function): | ||||||
|     MockPin.clear_pins() |     Device._pin_factory.reset() | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_output_initial_values(): | def test_output_initial_values(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with OutputDevice(pin, initial_value=False) as device: |     with OutputDevice(pin, initial_value=False) as device: | ||||||
|         assert pin.function == 'output' |         assert pin.function == 'output' | ||||||
|         assert not pin.state |         assert not pin.state | ||||||
| @@ -35,7 +36,7 @@ def test_output_initial_values(): | |||||||
|         assert state == pin.state |         assert state == pin.state | ||||||
|  |  | ||||||
| def test_output_write_active_high(): | def test_output_write_active_high(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with OutputDevice(pin) as device: |     with OutputDevice(pin) as device: | ||||||
|         device.on() |         device.on() | ||||||
|         assert pin.state |         assert pin.state | ||||||
| @@ -43,7 +44,7 @@ def test_output_write_active_high(): | |||||||
|         assert not pin.state |         assert not pin.state | ||||||
|  |  | ||||||
| def test_output_write_active_low(): | def test_output_write_active_low(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with OutputDevice(pin, active_high=False) as device: |     with OutputDevice(pin, active_high=False) as device: | ||||||
|         device.on() |         device.on() | ||||||
|         assert not pin.state |         assert not pin.state | ||||||
| @@ -51,7 +52,7 @@ def test_output_write_active_low(): | |||||||
|         assert pin.state |         assert pin.state | ||||||
|  |  | ||||||
| def test_output_write_closed(): | def test_output_write_closed(): | ||||||
|     with OutputDevice(MockPin(2)) as device: |     with OutputDevice(Device._pin_factory.pin(2)) as device: | ||||||
|         device.close() |         device.close() | ||||||
|         assert device.closed |         assert device.closed | ||||||
|         device.close() |         device.close() | ||||||
| @@ -60,14 +61,14 @@ def test_output_write_closed(): | |||||||
|             device.on() |             device.on() | ||||||
|  |  | ||||||
| def test_output_write_silly(): | def test_output_write_silly(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with OutputDevice(pin) as device: |     with OutputDevice(pin) as device: | ||||||
|         pin.function = 'input' |         pin.function = 'input' | ||||||
|         with pytest.raises(AttributeError): |         with pytest.raises(AttributeError): | ||||||
|             device.on() |             device.on() | ||||||
|  |  | ||||||
| def test_output_value(): | def test_output_value(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with OutputDevice(pin) as device: |     with OutputDevice(pin) as device: | ||||||
|         assert not device.value |         assert not device.value | ||||||
|         assert not pin.state |         assert not pin.state | ||||||
| @@ -79,7 +80,7 @@ def test_output_value(): | |||||||
|         assert not pin.state |         assert not pin.state | ||||||
|  |  | ||||||
| def test_output_digital_toggle(): | def test_output_digital_toggle(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with DigitalOutputDevice(pin) as device: |     with DigitalOutputDevice(pin) as device: | ||||||
|         assert not device.value |         assert not device.value | ||||||
|         assert not pin.state |         assert not pin.state | ||||||
| @@ -93,7 +94,7 @@ def test_output_digital_toggle(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_output_blink_background(): | def test_output_blink_background(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with DigitalOutputDevice(pin) as device: |     with DigitalOutputDevice(pin) as device: | ||||||
|         start = time() |         start = time() | ||||||
|         device.blink(0.1, 0.1, n=2) |         device.blink(0.1, 0.1, n=2) | ||||||
| @@ -111,7 +112,7 @@ def test_output_blink_background(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_output_blink_foreground(): | def test_output_blink_foreground(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with DigitalOutputDevice(pin) as device: |     with DigitalOutputDevice(pin) as device: | ||||||
|         start = time() |         start = time() | ||||||
|         device.blink(0.1, 0.1, n=2, background=False) |         device.blink(0.1, 0.1, n=2, background=False) | ||||||
| @@ -125,7 +126,7 @@ def test_output_blink_foreground(): | |||||||
|             ]) |             ]) | ||||||
|  |  | ||||||
| def test_output_blink_interrupt_on(): | def test_output_blink_interrupt_on(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with DigitalOutputDevice(pin) as device: |     with DigitalOutputDevice(pin) as device: | ||||||
|         device.blink(1, 0.1) |         device.blink(1, 0.1) | ||||||
|         sleep(0.2) |         sleep(0.2) | ||||||
| @@ -133,7 +134,7 @@ def test_output_blink_interrupt_on(): | |||||||
|         pin.assert_states([False, True, False]) |         pin.assert_states([False, True, False]) | ||||||
|  |  | ||||||
| def test_output_blink_interrupt_off(): | def test_output_blink_interrupt_off(): | ||||||
|     pin = MockPin(2) |     pin = Device._pin_factory.pin(2) | ||||||
|     with DigitalOutputDevice(pin) as device: |     with DigitalOutputDevice(pin) as device: | ||||||
|         device.blink(0.1, 1) |         device.blink(0.1, 1) | ||||||
|         sleep(0.2) |         sleep(0.2) | ||||||
| @@ -142,14 +143,14 @@ def test_output_blink_interrupt_off(): | |||||||
|  |  | ||||||
| def test_output_pwm_bad_initial_value(): | def test_output_pwm_bad_initial_value(): | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         PWMOutputDevice(MockPin(2), initial_value=2) |         PWMOutputDevice(Device._pin_factory.pin(2), initial_value=2) | ||||||
|  |  | ||||||
| def test_output_pwm_not_supported(): | def test_output_pwm_not_supported(): | ||||||
|     with pytest.raises(AttributeError): |     with pytest.raises(AttributeError): | ||||||
|         PWMOutputDevice(MockPin(2)) |         PWMOutputDevice(Device._pin_factory.pin(2)) | ||||||
|  |  | ||||||
| def test_output_pwm_states(): | def test_output_pwm_states(): | ||||||
|     pin = MockPWMPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with PWMOutputDevice(pin) as device: |     with PWMOutputDevice(pin) as device: | ||||||
|         device.value = 0.1 |         device.value = 0.1 | ||||||
|         device.value = 0.2 |         device.value = 0.2 | ||||||
| @@ -157,7 +158,7 @@ def test_output_pwm_states(): | |||||||
|         pin.assert_states([0.0, 0.1, 0.2, 0.0]) |         pin.assert_states([0.0, 0.1, 0.2, 0.0]) | ||||||
|  |  | ||||||
| def test_output_pwm_read(): | def test_output_pwm_read(): | ||||||
|     pin = MockPWMPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with PWMOutputDevice(pin, frequency=100) as device: |     with PWMOutputDevice(pin, frequency=100) as device: | ||||||
|         assert device.frequency == 100 |         assert device.frequency == 100 | ||||||
|         device.value = 0.1 |         device.value = 0.1 | ||||||
| @@ -170,14 +171,14 @@ def test_output_pwm_read(): | |||||||
|         assert device.frequency is None |         assert device.frequency is None | ||||||
|  |  | ||||||
| def test_output_pwm_write(): | def test_output_pwm_write(): | ||||||
|     pin = MockPWMPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with PWMOutputDevice(pin) as device: |     with PWMOutputDevice(pin) as device: | ||||||
|         device.on() |         device.on() | ||||||
|         device.off() |         device.off() | ||||||
|         pin.assert_states([False, True, False]) |         pin.assert_states([False, True, False]) | ||||||
|  |  | ||||||
| def test_output_pwm_toggle(): | def test_output_pwm_toggle(): | ||||||
|     pin = MockPWMPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with PWMOutputDevice(pin) as device: |     with PWMOutputDevice(pin) as device: | ||||||
|         device.toggle() |         device.toggle() | ||||||
|         device.value = 0.5 |         device.value = 0.5 | ||||||
| @@ -187,7 +188,7 @@ def test_output_pwm_toggle(): | |||||||
|         pin.assert_states([False, True, 0.5, 0.1, 0.9, False]) |         pin.assert_states([False, True, 0.5, 0.1, 0.9, False]) | ||||||
|  |  | ||||||
| def test_output_pwm_active_high_read(): | def test_output_pwm_active_high_read(): | ||||||
|     pin = MockPWMPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with PWMOutputDevice(pin, active_high=False) as device: |     with PWMOutputDevice(pin, active_high=False) as device: | ||||||
|         device.value = 0.1 |         device.value = 0.1 | ||||||
|         assert isclose(device.value, 0.1) |         assert isclose(device.value, 0.1) | ||||||
| @@ -196,17 +197,18 @@ def test_output_pwm_active_high_read(): | |||||||
|         assert device.value |         assert device.value | ||||||
|  |  | ||||||
| def test_output_pwm_bad_value(): | def test_output_pwm_bad_value(): | ||||||
|  |     with PWMOutputDevice(Device._pin_factory.pin(2, pin_class=MockPWMPin)) as device: | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|         PWMOutputDevice(MockPWMPin(2)).value = 2 |             device.value = 2 | ||||||
|  |  | ||||||
| def test_output_pwm_write_closed(): | def test_output_pwm_write_closed(): | ||||||
|     device = PWMOutputDevice(MockPWMPin(2)) |     with PWMOutputDevice(Device._pin_factory.pin(2, pin_class=MockPWMPin)) as device: | ||||||
|         device.close() |         device.close() | ||||||
|         with pytest.raises(GPIODeviceClosed): |         with pytest.raises(GPIODeviceClosed): | ||||||
|             device.on() |             device.on() | ||||||
|  |  | ||||||
| def test_output_pwm_write_silly(): | def test_output_pwm_write_silly(): | ||||||
|     pin = MockPWMPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with PWMOutputDevice(pin) as device: |     with PWMOutputDevice(pin) as device: | ||||||
|         pin.function = 'input' |         pin.function = 'input' | ||||||
|         with pytest.raises(AttributeError): |         with pytest.raises(AttributeError): | ||||||
| @@ -215,7 +217,7 @@ def test_output_pwm_write_silly(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_output_pwm_blink_background(): | def test_output_pwm_blink_background(): | ||||||
|     pin = MockPWMPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with PWMOutputDevice(pin) as device: |     with PWMOutputDevice(pin) as device: | ||||||
|         start = time() |         start = time() | ||||||
|         device.blink(0.1, 0.1, n=2) |         device.blink(0.1, 0.1, n=2) | ||||||
| @@ -233,7 +235,7 @@ def test_output_pwm_blink_background(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_output_pwm_blink_foreground(): | def test_output_pwm_blink_foreground(): | ||||||
|     pin = MockPWMPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with PWMOutputDevice(pin) as device: |     with PWMOutputDevice(pin) as device: | ||||||
|         start = time() |         start = time() | ||||||
|         device.blink(0.1, 0.1, n=2, background=False) |         device.blink(0.1, 0.1, n=2, background=False) | ||||||
| @@ -249,7 +251,7 @@ def test_output_pwm_blink_foreground(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_output_pwm_fade_background(): | def test_output_pwm_fade_background(): | ||||||
|     pin = MockPWMPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with PWMOutputDevice(pin) as device: |     with PWMOutputDevice(pin) as device: | ||||||
|         start = time() |         start = time() | ||||||
|         device.blink(0, 0, 0.2, 0.2, n=2) |         device.blink(0, 0, 0.2, 0.2, n=2) | ||||||
| @@ -283,7 +285,7 @@ def test_output_pwm_fade_background(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_output_pwm_fade_foreground(): | def test_output_pwm_fade_foreground(): | ||||||
|     pin = MockPWMPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with PWMOutputDevice(pin) as device: |     with PWMOutputDevice(pin) as device: | ||||||
|         start = time() |         start = time() | ||||||
|         device.blink(0, 0, 0.2, 0.2, n=2, background=False) |         device.blink(0, 0, 0.2, 0.2, n=2, background=False) | ||||||
| @@ -315,7 +317,7 @@ def test_output_pwm_fade_foreground(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_output_pwm_pulse_background(): | def test_output_pwm_pulse_background(): | ||||||
|     pin = MockPWMPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with PWMOutputDevice(pin) as device: |     with PWMOutputDevice(pin) as device: | ||||||
|         start = time() |         start = time() | ||||||
|         device.pulse(0.2, 0.2, n=2) |         device.pulse(0.2, 0.2, n=2) | ||||||
| @@ -349,7 +351,7 @@ def test_output_pwm_pulse_background(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_output_pwm_pulse_foreground(): | def test_output_pwm_pulse_foreground(): | ||||||
|     pin = MockPWMPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with PWMOutputDevice(pin) as device: |     with PWMOutputDevice(pin) as device: | ||||||
|         start = time() |         start = time() | ||||||
|         device.pulse(0.2, 0.2, n=2, background=False) |         device.pulse(0.2, 0.2, n=2, background=False) | ||||||
| @@ -379,7 +381,7 @@ def test_output_pwm_pulse_foreground(): | |||||||
|             ]) |             ]) | ||||||
|  |  | ||||||
| def test_output_pwm_blink_interrupt(): | def test_output_pwm_blink_interrupt(): | ||||||
|     pin = MockPWMPin(2) |     pin = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with PWMOutputDevice(pin) as device: |     with PWMOutputDevice(pin) as device: | ||||||
|         device.blink(1, 0.1) |         device.blink(1, 0.1) | ||||||
|         sleep(0.2) |         sleep(0.2) | ||||||
| @@ -391,7 +393,7 @@ def test_rgbled_missing_pins(): | |||||||
|         RGBLED() |         RGBLED() | ||||||
|  |  | ||||||
| def test_rgbled_initial_value(): | def test_rgbled_initial_value(): | ||||||
|     r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b, initial_value=(0.1, 0.2, 0)) as device: |     with RGBLED(r, g, b, initial_value=(0.1, 0.2, 0)) as device: | ||||||
|         assert r.frequency |         assert r.frequency | ||||||
|         assert g.frequency |         assert g.frequency | ||||||
| @@ -401,24 +403,24 @@ def test_rgbled_initial_value(): | |||||||
|         assert isclose(b.state, 0.0) |         assert isclose(b.state, 0.0) | ||||||
|  |  | ||||||
| def test_rgbled_initial_value_nonpwm(): | def test_rgbled_initial_value_nonpwm(): | ||||||
|     r, g, b = (MockPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b, pwm=False, initial_value=(0, 1, 1)) as device: |     with RGBLED(r, g, b, pwm=False, initial_value=(0, 1, 1)) as device: | ||||||
|         assert r.state == 0 |         assert r.state == 0 | ||||||
|         assert g.state == 1 |         assert g.state == 1 | ||||||
|         assert b.state == 1 |         assert b.state == 1 | ||||||
|  |  | ||||||
| def test_rgbled_initial_bad_value(): | def test_rgbled_initial_bad_value(): | ||||||
|     r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3)) | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         RGBLED(r, g, b, initial_value=(0.1, 0.2, 1.2)) |         RGBLED(r, g, b, initial_value=(0.1, 0.2, 1.2)) | ||||||
|  |  | ||||||
| def test_rgbled_initial_bad_value_nonpwm(): | def test_rgbled_initial_bad_value_nonpwm(): | ||||||
|     r, g, b = (MockPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3)) | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         RGBLED(r, g, b, pwm=False, initial_value=(0.1, 0.2, 0)) |         RGBLED(r, g, b, pwm=False, initial_value=(0.1, 0.2, 0)) | ||||||
|  |  | ||||||
| def test_rgbled_value(): | def test_rgbled_value(): | ||||||
|     r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b) as device: |     with RGBLED(r, g, b) as device: | ||||||
|         assert isinstance(device._leds[0], PWMLED) |         assert isinstance(device._leds[0], PWMLED) | ||||||
|         assert isinstance(device._leds[1], PWMLED) |         assert isinstance(device._leds[1], PWMLED) | ||||||
| @@ -436,7 +438,7 @@ def test_rgbled_value(): | |||||||
|         assert device.value == (0.5, 0.5, 0.5) |         assert device.value == (0.5, 0.5, 0.5) | ||||||
|  |  | ||||||
| def test_rgbled_value_nonpwm(): | def test_rgbled_value_nonpwm(): | ||||||
|     r, g, b = (MockPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b, pwm=False) as device: |     with RGBLED(r, g, b, pwm=False) as device: | ||||||
|         assert isinstance(device._leds[0], LED) |         assert isinstance(device._leds[0], LED) | ||||||
|         assert isinstance(device._leds[1], LED) |         assert isinstance(device._leds[1], LED) | ||||||
| @@ -451,7 +453,7 @@ def test_rgbled_value_nonpwm(): | |||||||
|         assert device.value == (0, 0, 0) |         assert device.value == (0, 0, 0) | ||||||
|  |  | ||||||
| def test_rgbled_bad_value(): | def test_rgbled_bad_value(): | ||||||
|     r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b) as device: |     with RGBLED(r, g, b) as device: | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|             device.value = (2, 0, 0) |             device.value = (2, 0, 0) | ||||||
| @@ -460,7 +462,7 @@ def test_rgbled_bad_value(): | |||||||
|             device.value = (0, -1, 0) |             device.value = (0, -1, 0) | ||||||
|  |  | ||||||
| def test_rgbled_bad_value_nonpwm(): | def test_rgbled_bad_value_nonpwm(): | ||||||
|     r, g, b = (MockPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b, pwm=False) as device: |     with RGBLED(r, g, b, pwm=False) as device: | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|             device.value = (2, 0, 0) |             device.value = (2, 0, 0) | ||||||
| @@ -478,7 +480,7 @@ def test_rgbled_bad_value_nonpwm(): | |||||||
|             device.value = (0, 0, 0.5) |             device.value = (0, 0, 0.5) | ||||||
|  |  | ||||||
| def test_rgbled_toggle(): | def test_rgbled_toggle(): | ||||||
|     r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b) as device: |     with RGBLED(r, g, b) as device: | ||||||
|         assert not device.is_active |         assert not device.is_active | ||||||
|         assert device.value == (0, 0, 0) |         assert device.value == (0, 0, 0) | ||||||
| @@ -490,7 +492,7 @@ def test_rgbled_toggle(): | |||||||
|         assert device.value == (0, 0, 0) |         assert device.value == (0, 0, 0) | ||||||
|  |  | ||||||
| def test_rgbled_toggle_nonpwm(): | def test_rgbled_toggle_nonpwm(): | ||||||
|     r, g, b = (MockPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b, pwm=False) as device: |     with RGBLED(r, g, b, pwm=False) as device: | ||||||
|         assert not device.is_active |         assert not device.is_active | ||||||
|         assert device.value == (0, 0, 0) |         assert device.value == (0, 0, 0) | ||||||
| @@ -501,10 +503,18 @@ def test_rgbled_toggle_nonpwm(): | |||||||
|         assert not device.is_active |         assert not device.is_active | ||||||
|         assert device.value == (0, 0, 0) |         assert device.value == (0, 0, 0) | ||||||
|  |  | ||||||
|  | def test_rgbled_blink_nonpwm(): | ||||||
|  |     r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3)) | ||||||
|  |     with RGBLED(r, g, b, pwm=False) as device: | ||||||
|  |         with pytest.raises(ValueError): | ||||||
|  |             device.blink(fade_in_time=1) | ||||||
|  |         with pytest.raises(ValueError): | ||||||
|  |             device.blink(fade_out_time=1) | ||||||
|  |  | ||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_rgbled_blink_background(): | def test_rgbled_blink_background(): | ||||||
|     r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b) as device: |     with RGBLED(r, g, b) as device: | ||||||
|         start = time() |         start = time() | ||||||
|         device.blink(0.1, 0.1, n=2) |         device.blink(0.1, 0.1, n=2) | ||||||
| @@ -525,7 +535,7 @@ def test_rgbled_blink_background(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_rgbled_blink_background_nonpwm(): | def test_rgbled_blink_background_nonpwm(): | ||||||
|     r, g, b = (MockPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b, pwm=False) as device: |     with RGBLED(r, g, b, pwm=False) as device: | ||||||
|         start = time() |         start = time() | ||||||
|         device.blink(0.1, 0.1, n=2) |         device.blink(0.1, 0.1, n=2) | ||||||
| @@ -546,7 +556,7 @@ def test_rgbled_blink_background_nonpwm(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_rgbled_blink_foreground(): | def test_rgbled_blink_foreground(): | ||||||
|     r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b) as device: |     with RGBLED(r, g, b) as device: | ||||||
|         start = time() |         start = time() | ||||||
|         device.blink(0.1, 0.1, n=2, background=False) |         device.blink(0.1, 0.1, n=2, background=False) | ||||||
| @@ -565,7 +575,7 @@ def test_rgbled_blink_foreground(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_rgbled_blink_foreground_nonpwm(): | def test_rgbled_blink_foreground_nonpwm(): | ||||||
|     r, g, b = (MockPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b, pwm=False) as device: |     with RGBLED(r, g, b, pwm=False) as device: | ||||||
|         start = time() |         start = time() | ||||||
|         device.blink(0.1, 0.1, n=2, background=False) |         device.blink(0.1, 0.1, n=2, background=False) | ||||||
| @@ -584,7 +594,7 @@ def test_rgbled_blink_foreground_nonpwm(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_rgbled_fade_background(): | def test_rgbled_fade_background(): | ||||||
|     r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b) as device: |     with RGBLED(r, g, b) as device: | ||||||
|         start = time() |         start = time() | ||||||
|         device.blink(0, 0, 0.2, 0.2, n=2) |         device.blink(0, 0, 0.2, 0.2, n=2) | ||||||
| @@ -619,7 +629,7 @@ def test_rgbled_fade_background(): | |||||||
|         b.assert_states_and_times(expected) |         b.assert_states_and_times(expected) | ||||||
|  |  | ||||||
| def test_rgbled_fade_background_nonpwm(): | def test_rgbled_fade_background_nonpwm(): | ||||||
|     r, g, b = (MockPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b, pwm=False) as device: |     with RGBLED(r, g, b, pwm=False) as device: | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|             device.blink(0, 0, 0.2, 0.2, n=2) |             device.blink(0, 0, 0.2, 0.2, n=2) | ||||||
| @@ -627,7 +637,7 @@ def test_rgbled_fade_background_nonpwm(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_rgbled_fade_foreground(): | def test_rgbled_fade_foreground(): | ||||||
|     r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b) as device: |     with RGBLED(r, g, b) as device: | ||||||
|         start = time() |         start = time() | ||||||
|         device.blink(0, 0, 0.2, 0.2, n=2, background=False) |         device.blink(0, 0, 0.2, 0.2, n=2, background=False) | ||||||
| @@ -660,7 +670,7 @@ def test_rgbled_fade_foreground(): | |||||||
|         b.assert_states_and_times(expected) |         b.assert_states_and_times(expected) | ||||||
|  |  | ||||||
| def test_rgbled_fade_foreground_nonpwm(): | def test_rgbled_fade_foreground_nonpwm(): | ||||||
|     r, g, b = (MockPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b, pwm=False) as device: |     with RGBLED(r, g, b, pwm=False) as device: | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|             device.blink(0, 0, 0.2, 0.2, n=2, background=False) |             device.blink(0, 0, 0.2, 0.2, n=2, background=False) | ||||||
| @@ -668,7 +678,7 @@ def test_rgbled_fade_foreground_nonpwm(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_rgbled_pulse_background(): | def test_rgbled_pulse_background(): | ||||||
|     r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b) as device: |     with RGBLED(r, g, b) as device: | ||||||
|         start = time() |         start = time() | ||||||
|         device.pulse(0.2, 0.2, n=2) |         device.pulse(0.2, 0.2, n=2) | ||||||
| @@ -703,7 +713,7 @@ def test_rgbled_pulse_background(): | |||||||
|         b.assert_states_and_times(expected) |         b.assert_states_and_times(expected) | ||||||
|  |  | ||||||
| def test_rgbled_pulse_background_nonpwm(): | def test_rgbled_pulse_background_nonpwm(): | ||||||
|     r, g, b = (MockPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b, pwm=False) as device: |     with RGBLED(r, g, b, pwm=False) as device: | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|             device.pulse(0.2, 0.2, n=2) |             device.pulse(0.2, 0.2, n=2) | ||||||
| @@ -711,7 +721,7 @@ def test_rgbled_pulse_background_nonpwm(): | |||||||
| @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), | ||||||
|                     reason='timing is too random on pypy') |                     reason='timing is too random on pypy') | ||||||
| def test_rgbled_pulse_foreground(): | def test_rgbled_pulse_foreground(): | ||||||
|     r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b) as device: |     with RGBLED(r, g, b) as device: | ||||||
|         start = time() |         start = time() | ||||||
|         device.pulse(0.2, 0.2, n=2, background=False) |         device.pulse(0.2, 0.2, n=2, background=False) | ||||||
| @@ -744,13 +754,13 @@ def test_rgbled_pulse_foreground(): | |||||||
|         b.assert_states_and_times(expected) |         b.assert_states_and_times(expected) | ||||||
|  |  | ||||||
| def test_rgbled_pulse_foreground_nonpwm(): | def test_rgbled_pulse_foreground_nonpwm(): | ||||||
|     r, g, b = (MockPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b, pwm=False) as device: |     with RGBLED(r, g, b, pwm=False) as device: | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|             device.pulse(0.2, 0.2, n=2, background=False) |             device.pulse(0.2, 0.2, n=2, background=False) | ||||||
|  |  | ||||||
| def test_rgbled_blink_interrupt(): | def test_rgbled_blink_interrupt(): | ||||||
|     r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b) as device: |     with RGBLED(r, g, b) as device: | ||||||
|         device.blink(1, 0.1) |         device.blink(1, 0.1) | ||||||
|         sleep(0.2) |         sleep(0.2) | ||||||
| @@ -760,7 +770,7 @@ def test_rgbled_blink_interrupt(): | |||||||
|         b.assert_states([0, 1, 0]) |         b.assert_states([0, 1, 0]) | ||||||
|  |  | ||||||
| def test_rgbled_blink_interrupt_nonpwm(): | def test_rgbled_blink_interrupt_nonpwm(): | ||||||
|     r, g, b = (MockPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b, pwm=False) as device: |     with RGBLED(r, g, b, pwm=False) as device: | ||||||
|         device.blink(1, 0.1) |         device.blink(1, 0.1) | ||||||
|         sleep(0.2) |         sleep(0.2) | ||||||
| @@ -770,7 +780,7 @@ def test_rgbled_blink_interrupt_nonpwm(): | |||||||
|         b.assert_states([0, 1, 0]) |         b.assert_states([0, 1, 0]) | ||||||
|  |  | ||||||
| def test_rgbled_close(): | def test_rgbled_close(): | ||||||
|     r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b) as device: |     with RGBLED(r, g, b) as device: | ||||||
|         assert not device.closed |         assert not device.closed | ||||||
|         device.close() |         device.close() | ||||||
| @@ -779,7 +789,7 @@ def test_rgbled_close(): | |||||||
|         assert device.closed |         assert device.closed | ||||||
|  |  | ||||||
| def test_rgbled_close_nonpwm(): | def test_rgbled_close_nonpwm(): | ||||||
|     r, g, b = (MockPin(i) for i in (1, 2, 3)) |     r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3)) | ||||||
|     with RGBLED(r, g, b, pwm=False) as device: |     with RGBLED(r, g, b, pwm=False) as device: | ||||||
|         assert not device.closed |         assert not device.closed | ||||||
|         device.close() |         device.close() | ||||||
| @@ -792,8 +802,8 @@ def test_motor_missing_pins(): | |||||||
|         Motor() |         Motor() | ||||||
|  |  | ||||||
| def test_motor_pins(): | def test_motor_pins(): | ||||||
|     f = MockPWMPin(1) |     f = Device._pin_factory.pin(1, pin_class=MockPWMPin) | ||||||
|     b = MockPWMPin(2) |     b = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with Motor(f, b) as device: |     with Motor(f, b) as device: | ||||||
|         assert device.forward_device.pin is f |         assert device.forward_device.pin is f | ||||||
|         assert isinstance(device.forward_device, PWMOutputDevice) |         assert isinstance(device.forward_device, PWMOutputDevice) | ||||||
| @@ -801,8 +811,8 @@ def test_motor_pins(): | |||||||
|         assert isinstance(device.backward_device, PWMOutputDevice) |         assert isinstance(device.backward_device, PWMOutputDevice) | ||||||
|  |  | ||||||
| def test_motor_pins_nonpwm(): | def test_motor_pins_nonpwm(): | ||||||
|     f = MockPin(1) |     f = Device._pin_factory.pin(1) | ||||||
|     b = MockPin(2) |     b = Device._pin_factory.pin(2) | ||||||
|     with Motor(f, b, pwm=False) as device: |     with Motor(f, b, pwm=False) as device: | ||||||
|         assert device.forward_device.pin is f |         assert device.forward_device.pin is f | ||||||
|         assert isinstance(device.forward_device, DigitalOutputDevice) |         assert isinstance(device.forward_device, DigitalOutputDevice) | ||||||
| @@ -810,8 +820,8 @@ def test_motor_pins_nonpwm(): | |||||||
|         assert isinstance(device.backward_device, DigitalOutputDevice) |         assert isinstance(device.backward_device, DigitalOutputDevice) | ||||||
|  |  | ||||||
| def test_motor_close(): | def test_motor_close(): | ||||||
|     f = MockPWMPin(1) |     f = Device._pin_factory.pin(1, pin_class=MockPWMPin) | ||||||
|     b = MockPWMPin(2) |     b = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with Motor(f, b) as device: |     with Motor(f, b) as device: | ||||||
|         device.close() |         device.close() | ||||||
|         assert device.closed |         assert device.closed | ||||||
| @@ -821,8 +831,8 @@ def test_motor_close(): | |||||||
|         assert device.closed |         assert device.closed | ||||||
|  |  | ||||||
| def test_motor_close_nonpwm(): | def test_motor_close_nonpwm(): | ||||||
|     f = MockPin(1) |     f = Device._pin_factory.pin(1) | ||||||
|     b = MockPin(2) |     b = Device._pin_factory.pin(2) | ||||||
|     with Motor(f, b, pwm=False) as device: |     with Motor(f, b, pwm=False) as device: | ||||||
|         device.close() |         device.close() | ||||||
|         assert device.closed |         assert device.closed | ||||||
| @@ -830,8 +840,8 @@ def test_motor_close_nonpwm(): | |||||||
|         assert device.backward_device.pin is None |         assert device.backward_device.pin is None | ||||||
|  |  | ||||||
| def test_motor_value(): | def test_motor_value(): | ||||||
|     f = MockPWMPin(1) |     f = Device._pin_factory.pin(1, pin_class=MockPWMPin) | ||||||
|     b = MockPWMPin(2) |     b = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with Motor(f, b) as device: |     with Motor(f, b) as device: | ||||||
|         device.value = -1 |         device.value = -1 | ||||||
|         assert device.is_active |         assert device.is_active | ||||||
| @@ -855,8 +865,8 @@ def test_motor_value(): | |||||||
|         assert b.state == 0 and f.state == 0 |         assert b.state == 0 and f.state == 0 | ||||||
|  |  | ||||||
| def test_motor_value_nonpwm(): | def test_motor_value_nonpwm(): | ||||||
|     f = MockPin(1) |     f = Device._pin_factory.pin(1) | ||||||
|     b = MockPin(2) |     b = Device._pin_factory.pin(2) | ||||||
|     with Motor(f, b, pwm=False) as device: |     with Motor(f, b, pwm=False) as device: | ||||||
|         device.value = -1 |         device.value = -1 | ||||||
|         assert device.is_active |         assert device.is_active | ||||||
| @@ -872,17 +882,21 @@ def test_motor_value_nonpwm(): | |||||||
|         assert b.state == 0 and f.state == 0 |         assert b.state == 0 and f.state == 0 | ||||||
|  |  | ||||||
| def test_motor_bad_value(): | def test_motor_bad_value(): | ||||||
|     f = MockPWMPin(1) |     f = Device._pin_factory.pin(1, pin_class=MockPWMPin) | ||||||
|     b = MockPWMPin(2) |     b = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with Motor(f, b) as device: |     with Motor(f, b) as device: | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|             device.value = -2 |             device.value = -2 | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|             device.value = 2 |             device.value = 2 | ||||||
|  |         with pytest.raises(ValueError): | ||||||
|  |             device.forward(2) | ||||||
|  |         with pytest.raises(ValueError): | ||||||
|  |             device.backward(2) | ||||||
|  |  | ||||||
| def test_motor_bad_value_nonpwm(): | def test_motor_bad_value_nonpwm(): | ||||||
|     f = MockPin(1) |     f = Device._pin_factory.pin(1) | ||||||
|     b = MockPin(2) |     b = Device._pin_factory.pin(2) | ||||||
|     with Motor(f, b, pwm=False) as device: |     with Motor(f, b, pwm=False) as device: | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|             device.value = -2 |             device.value = -2 | ||||||
| @@ -894,8 +908,8 @@ def test_motor_bad_value_nonpwm(): | |||||||
|             device.value = -0.5 |             device.value = -0.5 | ||||||
|  |  | ||||||
| def test_motor_reverse(): | def test_motor_reverse(): | ||||||
|     f = MockPWMPin(1) |     f = Device._pin_factory.pin(1, pin_class=MockPWMPin) | ||||||
|     b = MockPWMPin(2) |     b = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with Motor(f, b) as device: |     with Motor(f, b) as device: | ||||||
|         device.forward() |         device.forward() | ||||||
|         assert device.value == 1 |         assert device.value == 1 | ||||||
| @@ -911,8 +925,8 @@ def test_motor_reverse(): | |||||||
|         assert b.state == 0 and f.state == 0.5 |         assert b.state == 0 and f.state == 0.5 | ||||||
|  |  | ||||||
| def test_motor_reverse_nonpwm(): | def test_motor_reverse_nonpwm(): | ||||||
|     f = MockPin(1) |     f = Device._pin_factory.pin(1) | ||||||
|     b = MockPin(2) |     b = Device._pin_factory.pin(2) | ||||||
|     with Motor(f, b, pwm=False) as device: |     with Motor(f, b, pwm=False) as device: | ||||||
|         device.forward() |         device.forward() | ||||||
|         assert device.value == 1 |         assert device.value == 1 | ||||||
| @@ -922,13 +936,13 @@ def test_motor_reverse_nonpwm(): | |||||||
|         assert b.state == 1 and f.state == 0 |         assert b.state == 1 and f.state == 0 | ||||||
|  |  | ||||||
| def test_servo_pins(): | def test_servo_pins(): | ||||||
|     p = MockPWMPin(1) |     p = Device._pin_factory.pin(1, pin_class=MockPWMPin) | ||||||
|     with Servo(p) as device: |     with Servo(p) as device: | ||||||
|         assert device.pwm_device.pin is p |         assert device.pwm_device.pin is p | ||||||
|         assert isinstance(device.pwm_device, PWMOutputDevice) |         assert isinstance(device.pwm_device, PWMOutputDevice) | ||||||
|  |  | ||||||
| def test_servo_bad_value(): | def test_servo_bad_value(): | ||||||
|     p = MockPWMPin(1) |     p = Device._pin_factory.pin(1, pin_class=MockPWMPin) | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         Servo(p, initial_value=2) |         Servo(p, initial_value=2) | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
| @@ -937,12 +951,12 @@ def test_servo_bad_value(): | |||||||
|         Servo(p, max_pulse_width=30/1000) |         Servo(p, max_pulse_width=30/1000) | ||||||
|  |  | ||||||
| def test_servo_pins_nonpwm(): | def test_servo_pins_nonpwm(): | ||||||
|     p = MockPin(2) |     p = Device._pin_factory.pin(2) | ||||||
|     with pytest.raises(PinPWMUnsupported): |     with pytest.raises(PinPWMUnsupported): | ||||||
|         Servo(p) |         Servo(p) | ||||||
|  |  | ||||||
| def test_servo_close(): | def test_servo_close(): | ||||||
|     p = MockPWMPin(2) |     p = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with Servo(p) as device: |     with Servo(p) as device: | ||||||
|         device.close() |         device.close() | ||||||
|         assert device.closed |         assert device.closed | ||||||
| @@ -951,7 +965,7 @@ def test_servo_close(): | |||||||
|         assert device.closed |         assert device.closed | ||||||
|  |  | ||||||
| def test_servo_pulse_width(): | def test_servo_pulse_width(): | ||||||
|     p = MockPWMPin(2) |     p = Device._pin_factory.pin(2, pin_class=MockPWMPin) | ||||||
|     with Servo(p, min_pulse_width=5/10000, max_pulse_width=25/10000) as device: |     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.min_pulse_width, 5/10000) | ||||||
|         assert isclose(device.max_pulse_width, 25/10000) |         assert isclose(device.max_pulse_width, 25/10000) | ||||||
| @@ -965,7 +979,7 @@ def test_servo_pulse_width(): | |||||||
|         assert device.pulse_width is None |         assert device.pulse_width is None | ||||||
|  |  | ||||||
| def test_servo_values(): | def test_servo_values(): | ||||||
|     p = MockPWMPin(1) |     p = Device._pin_factory.pin(1, pin_class=MockPWMPin) | ||||||
|     with Servo(p) as device: |     with Servo(p) as device: | ||||||
|         device.min() |         device.min() | ||||||
|         assert device.is_active |         assert device.is_active | ||||||
| @@ -992,13 +1006,13 @@ def test_servo_values(): | |||||||
|         assert device.value is None |         assert device.value is None | ||||||
|  |  | ||||||
| def test_angular_servo_range(): | def test_angular_servo_range(): | ||||||
|     p = MockPWMPin(1) |     p = Device._pin_factory.pin(1, pin_class=MockPWMPin) | ||||||
|     with AngularServo(p, initial_angle=15, min_angle=0, max_angle=90) as device: |     with AngularServo(p, initial_angle=15, min_angle=0, max_angle=90) as device: | ||||||
|         assert device.min_angle == 0 |         assert device.min_angle == 0 | ||||||
|         assert device.max_angle == 90 |         assert device.max_angle == 90 | ||||||
|  |  | ||||||
| def test_angular_servo_angles(): | def test_angular_servo_angles(): | ||||||
|     p = MockPWMPin(1) |     p = Device._pin_factory.pin(1, pin_class=MockPWMPin) | ||||||
|     with AngularServo(p) as device: |     with AngularServo(p) as device: | ||||||
|         device.angle = 0 |         device.angle = 0 | ||||||
|         assert device.angle == 0 |         assert device.angle == 0 | ||||||
|   | |||||||
| @@ -11,44 +11,44 @@ import re | |||||||
| import pytest | import pytest | ||||||
| from mock import patch, MagicMock | from mock import patch, MagicMock | ||||||
|  |  | ||||||
| import gpiozero.devices |  | ||||||
| import gpiozero.pins.data | import gpiozero.pins.data | ||||||
| import gpiozero.pins.native | import gpiozero.pins.local | ||||||
| from gpiozero.pins.data import pi_info, Style, HeaderInfo, PinInfo | from gpiozero.pins.local import LocalPiFactory | ||||||
| from gpiozero import PinMultiplePins, PinNoPins, PinUnknownPi | from gpiozero.pins.data import Style, HeaderInfo, PinInfo | ||||||
|  | from gpiozero import * | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_pi_revision(): | def test_pi_revision(): | ||||||
|     save_factory = gpiozero.devices.pin_factory |     # We're not using _set_pin_factory here because we don't want to implicitly | ||||||
|     try: |     # close the old instance, just replace it while we test stuff | ||||||
|  |     with patch('gpiozero.devices.Device._pin_factory', LocalPiFactory()): | ||||||
|         # Can't use MockPin for this as we want something that'll actually 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); |         # and read /proc/cpuinfo (MockPin simply parrots the 2B's data); | ||||||
|         # NativePin is used as we're guaranteed to be able to import it |         # LocalPiFactory is used as we can definitely instantiate it (strictly | ||||||
|         gpiozero.devices.pin_factory = gpiozero.pins.native.NativePin |         # speaking it's abstract but we're only interested in the pi_info | ||||||
|  |         # stuff) | ||||||
|         with patch('io.open') as m: |         with patch('io.open') as m: | ||||||
|             m.return_value.__enter__.return_value = ['lots of irrelevant', 'lines', 'followed by', 'Revision: 0002', 'Serial:  xxxxxxxxxxx'] |             m.return_value.__enter__.return_value = ['lots of irrelevant', 'lines', 'followed by', 'Revision: 0002', 'Serial:  xxxxxxxxxxx'] | ||||||
|             assert pi_info().revision == '0002' |             assert pi_info().revision == '0002' | ||||||
|             # LocalPin caches the revision (because realistically it isn't going to |             # LocalPiFactory caches the revision (because realistically it | ||||||
|             # change at runtime); we need to wipe it here though |             # isn't going to change at runtime); we need to wipe it here though | ||||||
|             gpiozero.pins.native.NativePin._PI_REVISION = None |             Device._pin_factory._info = None | ||||||
|             m.return_value.__enter__.return_value = ['Revision: a21042'] |             m.return_value.__enter__.return_value = ['Revision: a21042'] | ||||||
|             assert pi_info().revision == 'a21042' |             assert pi_info().revision == 'a21042' | ||||||
|             # Check over-volting result (some argument over whether this is 7 or |             # Check over-volting result (some argument over whether this is 7 or | ||||||
|             # 8 character result; make sure both work) |             # 8 character result; make sure both work) | ||||||
|             gpiozero.pins.native.NativePin._PI_REVISION = None |             Device._pin_factory._info = None | ||||||
|             m.return_value.__enter__.return_value = ['Revision: 1000003'] |             m.return_value.__enter__.return_value = ['Revision: 1000003'] | ||||||
|             assert pi_info().revision == '0003' |             assert pi_info().revision == '0003' | ||||||
|             gpiozero.pins.native.NativePin._PI_REVISION = None |             Device._pin_factory._info = None | ||||||
|             m.return_value.__enter__.return_value = ['Revision: 100003'] |             m.return_value.__enter__.return_value = ['Revision: 100003'] | ||||||
|             assert pi_info().revision == '0003' |             assert pi_info().revision == '0003' | ||||||
|             with pytest.raises(PinUnknownPi): |             with pytest.raises(PinUnknownPi): | ||||||
|                 m.return_value.__enter__.return_value = ['nothing', 'relevant', 'at all'] |                 m.return_value.__enter__.return_value = ['nothing', 'relevant', 'at all'] | ||||||
|                 gpiozero.pins.native.NativePin._PI_REVISION = None |                 Device._pin_factory._info = None | ||||||
|                 pi_info() |                 pi_info() | ||||||
|             with pytest.raises(PinUnknownPi): |             with pytest.raises(PinUnknownPi): | ||||||
|                 pi_info('0fff') |                 pi_info('0fff') | ||||||
|     finally: |  | ||||||
|         gpiozero.devices.pin_factory = save_factory |  | ||||||
|  |  | ||||||
| def test_pi_info(): | def test_pi_info(): | ||||||
|     r = pi_info('900011') |     r = pi_info('900011') | ||||||
| @@ -73,14 +73,14 @@ def test_pi_info_other_types(): | |||||||
|  |  | ||||||
| def test_physical_pins(): | def test_physical_pins(): | ||||||
|     # Assert physical pins for some well-known Pi's; a21041 is a Pi2B |     # 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('3V3') == {('J8', 1), ('J8', 17)} | ||||||
|     assert pi_info('a21041').physical_pins('GPIO2') == {('P1', 3)} |     assert pi_info('a21041').physical_pins('GPIO2') == {('J8', 3)} | ||||||
|     assert pi_info('a21041').physical_pins('GPIO47') == set() |     assert pi_info('a21041').physical_pins('GPIO47') == set() | ||||||
|  |  | ||||||
| def test_physical_pin(): | def test_physical_pin(): | ||||||
|     with pytest.raises(PinMultiplePins): |     with pytest.raises(PinMultiplePins): | ||||||
|         assert pi_info('a21041').physical_pin('GND') |         assert pi_info('a21041').physical_pin('GND') | ||||||
|     assert pi_info('a21041').physical_pin('GPIO3') == ('P1', 5) |     assert pi_info('a21041').physical_pin('GPIO3') == ('J8', 5) | ||||||
|     with pytest.raises(PinNoPins): |     with pytest.raises(PinNoPins): | ||||||
|         assert pi_info('a21041').physical_pin('GPIO47') |         assert pi_info('a21041').physical_pin('GPIO47') | ||||||
|  |  | ||||||
| @@ -114,6 +114,18 @@ def test_pprint_content(): | |||||||
|         pi_info('0014').headers['SODIMM'].pprint(color=False) |         pi_info('0014').headers['SODIMM'].pprint(color=False) | ||||||
|         assert len(''.join(stdout.output).splitlines()) == 100 |         assert len(''.join(stdout.output).splitlines()) == 100 | ||||||
|  |  | ||||||
|  | def test_format_content(): | ||||||
|  |     with patch('sys.stdout') as stdout: | ||||||
|  |         stdout.output = [] | ||||||
|  |         stdout.write = lambda buf: stdout.output.append(buf) | ||||||
|  |         pi_info('900092').pprint(color=False) | ||||||
|  |         s = ''.join(stdout.output) | ||||||
|  |         assert '{0:mono}\n'.format(pi_info('900092')) == s | ||||||
|  |         stdout.output = [] | ||||||
|  |         pi_info('900092').pprint(color=True) | ||||||
|  |         s = ''.join(stdout.output) | ||||||
|  |         assert '{0:color full}\n'.format(pi_info('900092')) == s | ||||||
|  |  | ||||||
| def test_pprint_headers(): | def test_pprint_headers(): | ||||||
|     assert len(pi_info('0002').headers) == 1 |     assert len(pi_info('0002').headers) == 1 | ||||||
|     assert len(pi_info('000e').headers) == 2 |     assert len(pi_info('000e').headers) == 2 | ||||||
| @@ -133,7 +145,8 @@ def test_pprint_headers(): | |||||||
|         stdout.output = [] |         stdout.output = [] | ||||||
|         pi_info('900092').pprint() |         pi_info('900092').pprint() | ||||||
|         s = ''.join(stdout.output) |         s = ''.join(stdout.output) | ||||||
|         assert 'P1:\n' in s |         assert 'J8:\n' in s | ||||||
|  |         assert 'P1:\n' not in s | ||||||
|         assert 'P5:\n' not in s |         assert 'P5:\n' not in s | ||||||
|  |  | ||||||
| def test_pprint_color(): | def test_pprint_color(): | ||||||
| @@ -194,11 +207,12 @@ def test_pprint_missing_pin(): | |||||||
|                 assert ('(%d)' % i) |                 assert ('(%d)' % i) | ||||||
|  |  | ||||||
| def test_pprint_rows_cols(): | def test_pprint_rows_cols(): | ||||||
|     assert '{0:row1}'.format(pi_info('900092').headers['P1']) == '1o' |     assert '{0:row1}'.format(pi_info('900092').headers['J8']) == '1o' | ||||||
|     assert '{0:row2}'.format(pi_info('900092').headers['P1']) == 'oo' |     assert '{0:row2}'.format(pi_info('900092').headers['J8']) == 'oo' | ||||||
|     assert '{0:col1}'.format(pi_info('0002').headers['P1']) == '1oooooooooooo' |     assert '{0:col1}'.format(pi_info('0002').headers['P1']) == '1oooooooooooo' | ||||||
|     assert '{0:col2}'.format(pi_info('0002').headers['P1']) == 'ooooooooooooo' |     assert '{0:col2}'.format(pi_info('0002').headers['P1']) == 'ooooooooooooo' | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         '{0:row16}'.format(pi_info('0002').headers['P1']) |         '{0:row16}'.format(pi_info('0002').headers['P1']) | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         '{0:col3}'.format(pi_info('0002').headers['P1']) |         '{0:col3}'.format(pi_info('0002').headers['P1']) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,93 +4,113 @@ from __future__ import ( | |||||||
|     print_function, |     print_function, | ||||||
|     division, |     division, | ||||||
|     ) |     ) | ||||||
|  | nstr = str | ||||||
| str = type('') | str = type('') | ||||||
|  |  | ||||||
|  |  | ||||||
| import sys | import sys | ||||||
| import mock |  | ||||||
| import pytest | import pytest | ||||||
|  | from array import array | ||||||
|  | from mock import patch | ||||||
| from collections import namedtuple | from collections import namedtuple | ||||||
|  |  | ||||||
|  | from gpiozero.pins.native import NativeFactory | ||||||
|  | from gpiozero.pins.local import ( | ||||||
|  |     LocalPiHardwareSPI, | ||||||
|  |     LocalPiSoftwareSPI, | ||||||
|  |     LocalPiHardwareSPIShared, | ||||||
|  |     LocalPiSoftwareSPIShared, | ||||||
|  |     ) | ||||||
|  | from gpiozero.pins.mock import MockSPIDevice | ||||||
| from gpiozero import * | from gpiozero import * | ||||||
| from gpiozero.pins.mock import MockPin, MockSPIDevice |  | ||||||
| from gpiozero.spi import * |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def setup_function(function): |  | ||||||
|     import gpiozero.devices |  | ||||||
|     gpiozero.devices.pin_factory = MockPin |  | ||||||
|  |  | ||||||
| def teardown_function(function): | def teardown_function(function): | ||||||
|     MockPin.clear_pins() |     Device._pin_factory.reset() | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_spi_hardware_params(): | def test_spi_hardware_params(): | ||||||
|     with mock.patch('gpiozero.spi.SpiDev') as spidev: |     with patch('os.open'), patch('mmap.mmap') as mmap_mmap, patch('io.open') as io_open: | ||||||
|         with SPI() as device: |         mmap_mmap.return_value = array(nstr('B'), (0,) * 4096) | ||||||
|             assert isinstance(device, SPIHardwareInterface) |         io_open.return_value.__enter__.return_value = ['Revision: a21042'] | ||||||
|         with SPI(port=0, device=0) as device: |         with patch('gpiozero.devices.Device._pin_factory', NativeFactory()), \ | ||||||
|             assert isinstance(device, SPIHardwareInterface) |                 patch('gpiozero.pins.local.SpiDev'): | ||||||
|         with SPI(port=0, device=1) as device: |             with Device._pin_factory.spi() as device: | ||||||
|             assert isinstance(device, SPIHardwareInterface) |                 assert isinstance(device, LocalPiHardwareSPI) | ||||||
|         with SPI(clock_pin=11) as device: |             with Device._pin_factory.spi(port=0, device=0) as device: | ||||||
|             assert isinstance(device, SPIHardwareInterface) |                 assert isinstance(device, LocalPiHardwareSPI) | ||||||
|         with SPI(clock_pin=11, mosi_pin=10, select_pin=8) as device: |             with Device._pin_factory.spi(port=0, device=1) as device: | ||||||
|             assert isinstance(device, SPIHardwareInterface) |                 assert isinstance(device, LocalPiHardwareSPI) | ||||||
|         with SPI(clock_pin=11, mosi_pin=10, select_pin=7) as device: |             with Device._pin_factory.spi(clock_pin=11) as device: | ||||||
|             assert isinstance(device, SPIHardwareInterface) |                 assert isinstance(device, LocalPiHardwareSPI) | ||||||
|         with SPI(shared=True) as device: |             with Device._pin_factory.spi(clock_pin=11, mosi_pin=10, select_pin=8) as device: | ||||||
|             assert isinstance(device, SharedSPIHardwareInterface) |                 assert isinstance(device, LocalPiHardwareSPI) | ||||||
|  |             with Device._pin_factory.spi(clock_pin=11, mosi_pin=10, select_pin=7) as device: | ||||||
|  |                 assert isinstance(device, LocalPiHardwareSPI) | ||||||
|  |             with Device._pin_factory.spi(shared=True) as device: | ||||||
|  |                 assert isinstance(device, LocalPiHardwareSPIShared) | ||||||
|             with pytest.raises(ValueError): |             with pytest.raises(ValueError): | ||||||
|             SPI(port=1) |                 Device._pin_factory.spi(port=1) | ||||||
|             with pytest.raises(ValueError): |             with pytest.raises(ValueError): | ||||||
|             SPI(device=2) |                 Device._pin_factory.spi(device=2) | ||||||
|             with pytest.raises(ValueError): |             with pytest.raises(ValueError): | ||||||
|             SPI(port=0, clock_pin=12) |                 Device._pin_factory.spi(port=0, clock_pin=12) | ||||||
|             with pytest.raises(ValueError): |             with pytest.raises(ValueError): | ||||||
|             SPI(foo='bar') |                 Device._pin_factory.spi(foo='bar') | ||||||
|  |  | ||||||
| def test_spi_software_params(): | def test_spi_software_params(): | ||||||
|     with mock.patch('gpiozero.spi.SpiDev') as spidev: |     with patch('os.open'), patch('mmap.mmap') as mmap_mmap, patch('io.open') as io_open: | ||||||
|         with SPI(select_pin=6) as device: |         mmap_mmap.return_value = array(nstr('B'), (0,) * 4096) | ||||||
|             assert isinstance(device, SPISoftwareInterface) |         io_open.return_value.__enter__.return_value = ['Revision: a21042'] | ||||||
|         with SPI(clock_pin=11, mosi_pin=9, miso_pin=10) as device: |         with patch('gpiozero.devices.Device._pin_factory', NativeFactory()), \ | ||||||
|             assert isinstance(device, SPISoftwareInterface) |                 patch('gpiozero.pins.local.SpiDev'): | ||||||
|         with SPI(select_pin=6, shared=True) as device: |             with Device._pin_factory.spi(select_pin=6) as device: | ||||||
|             assert isinstance(device, SharedSPISoftwareInterface) |                 assert isinstance(device, LocalPiSoftwareSPI) | ||||||
|  |             with Device._pin_factory.spi(clock_pin=11, mosi_pin=9, miso_pin=10) as device: | ||||||
|  |                 assert isinstance(device, LocalPiSoftwareSPI) | ||||||
|  |             with Device._pin_factory.spi(select_pin=6, shared=True) as device: | ||||||
|  |                 assert isinstance(device, LocalPiSoftwareSPIShared) | ||||||
|  |         with patch('gpiozero.devices.Device._pin_factory', NativeFactory()): | ||||||
|  |             # Clear out the old factory's pins cache (this is only necessary | ||||||
|  |             # because we're being very naughty switching out pin factories) | ||||||
|  |             Device._pin_factory.pins.clear() | ||||||
|             # Ensure software fallback works when SpiDev isn't present |             # Ensure software fallback works when SpiDev isn't present | ||||||
|     with SPI() as device: |             with Device._pin_factory.spi() as device: | ||||||
|         assert isinstance(device, SPISoftwareInterface) |                 assert isinstance(device, LocalPiSoftwareSPI) | ||||||
|  |  | ||||||
| def test_spi_hardware_conflict(): | def test_spi_hardware_conflict(): | ||||||
|     with mock.patch('gpiozero.spi.SpiDev') as spidev: |     with patch('gpiozero.pins.local.SpiDev') as spidev: | ||||||
|         with LED(11) as led: |         with LED(11) as led: | ||||||
|             with pytest.raises(GPIOPinInUse): |             with pytest.raises(GPIOPinInUse): | ||||||
|                 SPI(port=0, device=0) |                 Device._pin_factory.spi(port=0, device=0) | ||||||
|  |     with patch('gpiozero.pins.local.SpiDev') as spidev: | ||||||
|  |         with Device._pin_factory.spi(port=0, device=0) as spi: | ||||||
|  |             with pytest.raises(GPIOPinInUse): | ||||||
|  |                 LED(11) | ||||||
|  |  | ||||||
| def test_spi_hardware_read(): | def test_spi_hardware_read(): | ||||||
|     with mock.patch('gpiozero.spi.SpiDev') as spidev: |     with patch('gpiozero.pins.local.SpiDev') as spidev: | ||||||
|         spidev.return_value.xfer2.side_effect = lambda data: list(range(10))[:len(data)] |         spidev.return_value.xfer2.side_effect = lambda data: list(range(10))[:len(data)] | ||||||
|         with SPI() as device: |         with Device._pin_factory.spi() as device: | ||||||
|             assert device.read(3) == [0, 1, 2] |             assert device.read(3) == [0, 1, 2] | ||||||
|             assert device.read(6) == list(range(6)) |             assert device.read(6) == list(range(6)) | ||||||
|  |  | ||||||
| def test_spi_hardware_write(): | def test_spi_hardware_write(): | ||||||
|     with mock.patch('gpiozero.spi.SpiDev') as spidev: |     with patch('gpiozero.pins.local.SpiDev') as spidev: | ||||||
|         spidev.return_value.xfer2.side_effect = lambda data: list(range(10))[:len(data)] |         spidev.return_value.xfer2.side_effect = lambda data: list(range(10))[:len(data)] | ||||||
|         with SPI() as device: |         with Device._pin_factory.spi() as device: | ||||||
|             assert device.write([0, 1, 2]) == 3 |             assert device.write([0, 1, 2]) == 3 | ||||||
|             assert spidev.return_value.xfer2.called_with([0, 1, 2]) |             assert spidev.return_value.xfer2.called_with([0, 1, 2]) | ||||||
|             assert device.write(list(range(6))) == 6 |             assert device.write(list(range(6))) == 6 | ||||||
|             assert spidev.return_value.xfer2.called_with(list(range(6))) |             assert spidev.return_value.xfer2.called_with(list(range(6))) | ||||||
|  |  | ||||||
| def test_spi_hardware_modes(): | def test_spi_hardware_modes(): | ||||||
|     with mock.patch('gpiozero.spi.SpiDev') as spidev: |     with patch('gpiozero.pins.local.SpiDev') as spidev: | ||||||
|         spidev.return_value.mode = 0 |         spidev.return_value.mode = 0 | ||||||
|         spidev.return_value.lsbfirst = False |         spidev.return_value.lsbfirst = False | ||||||
|         spidev.return_value.cshigh = True |         spidev.return_value.cshigh = True | ||||||
|         spidev.return_value.bits_per_word = 8 |         spidev.return_value.bits_per_word = 8 | ||||||
|         with SPI() as device: |         with Device._pin_factory.spi() as device: | ||||||
|             assert device.clock_mode == 0 |             assert device.clock_mode == 0 | ||||||
|             assert not device.clock_polarity |             assert not device.clock_polarity | ||||||
|             assert not device.clock_phase |             assert not device.clock_phase | ||||||
| @@ -116,7 +136,7 @@ def test_spi_software_read(): | |||||||
|             super(SPISlave, self).on_start() |             super(SPISlave, self).on_start() | ||||||
|             for i in range(10): |             for i in range(10): | ||||||
|                 self.tx_word(i) |                 self.tx_word(i) | ||||||
|     with SPISlave(11, 10, 9, 8) as slave, SPI() as master: |     with SPISlave(11, 10, 9, 8) as slave, Device._pin_factory.spi() as master: | ||||||
|         assert master.read(3) == [0, 1, 2] |         assert master.read(3) == [0, 1, 2] | ||||||
|         assert master.read(6) == [0, 1, 2, 3, 4, 5] |         assert master.read(6) == [0, 1, 2, 3, 4, 5] | ||||||
|         slave.clock_phase = True |         slave.clock_phase = True | ||||||
| @@ -125,7 +145,7 @@ def test_spi_software_read(): | |||||||
|         assert master.read(6) == [0, 1, 2, 3, 4, 5] |         assert master.read(6) == [0, 1, 2, 3, 4, 5] | ||||||
|  |  | ||||||
| def test_spi_software_write(): | def test_spi_software_write(): | ||||||
|     with MockSPIDevice(11, 10, 9, 8) as test_device, SPI() as master: |     with MockSPIDevice(11, 10, 9, 8) as test_device, Device._pin_factory.spi() as master: | ||||||
|         master.write([0]) |         master.write([0]) | ||||||
|         assert test_device.rx_word() == 0 |         assert test_device.rx_word() == 0 | ||||||
|         master.write([2, 0]) |         master.write([2, 0]) | ||||||
| @@ -134,7 +154,7 @@ def test_spi_software_write(): | |||||||
|         assert test_device.rx_word() == 257 |         assert test_device.rx_word() == 257 | ||||||
|  |  | ||||||
| def test_spi_software_clock_mode(): | def test_spi_software_clock_mode(): | ||||||
|     with SPI() as master: |     with Device._pin_factory.spi() as master: | ||||||
|         assert master.clock_mode == 0 |         assert master.clock_mode == 0 | ||||||
|         assert not master.clock_polarity |         assert not master.clock_polarity | ||||||
|         assert not master.clock_phase |         assert not master.clock_phase | ||||||
| @@ -151,7 +171,7 @@ def test_spi_software_clock_mode(): | |||||||
|             master.clock_mode = 5 |             master.clock_mode = 5 | ||||||
|  |  | ||||||
| def test_spi_software_attr(): | def test_spi_software_attr(): | ||||||
|     with SPI() as master: |     with Device._pin_factory.spi() as master: | ||||||
|         assert not master.lsb_first |         assert not master.lsb_first | ||||||
|         assert not master.select_high |         assert not master.select_high | ||||||
|         assert master.bits_per_word == 8 |         assert master.bits_per_word == 8 | ||||||
|   | |||||||
| @@ -15,16 +15,12 @@ try: | |||||||
| except ImportError: | except ImportError: | ||||||
|     from gpiozero.compat import isclose |     from gpiozero.compat import isclose | ||||||
|  |  | ||||||
| from gpiozero import * |  | ||||||
| from gpiozero.pins.mock import MockSPIDevice, MockPin | from gpiozero.pins.mock import MockSPIDevice, MockPin | ||||||
|  | from gpiozero import * | ||||||
|  |  | ||||||
|  |  | ||||||
| def setup_function(function): |  | ||||||
|     import gpiozero.devices |  | ||||||
|     gpiozero.devices.pin_factory = MockPin |  | ||||||
|  |  | ||||||
| def teardown_function(function): | def teardown_function(function): | ||||||
|     MockPin.clear_pins() |     Device._pin_factory.reset() | ||||||
|  |  | ||||||
| def clamp(v, min_value, max_value): | def clamp(v, min_value, max_value): | ||||||
|     return min(max_value, max(min_value, v)) |     return min(max_value, max(min_value, v)) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user