mirror of
https://github.com/KevinMidboe/python-gpiozero.git
synced 2025-10-29 09:40:36 +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:: SPIBadChannel
|
||||
|
||||
.. autoexception:: SPIFixedClockMode
|
||||
|
||||
.. autoexception:: SPIInvalidClockMode
|
||||
|
||||
.. autoexception:: SPIFixedBitOrder
|
||||
|
||||
.. autoexception:: SPIFixedSelect
|
||||
|
||||
.. autoexception:: SPIFixedWordSize
|
||||
|
||||
.. autoexception:: SPIInvalidWordSize
|
||||
|
||||
.. autoexception:: GPIODeviceError
|
||||
|
||||
.. autoexception:: GPIODeviceClosed
|
||||
@@ -83,23 +97,31 @@ Errors
|
||||
|
||||
.. autoexception:: PinInvalidEdges
|
||||
|
||||
.. autoexception:: PinInvalidBounce
|
||||
|
||||
.. autoexception:: PinSetInput
|
||||
|
||||
.. autoexception:: PinFixedPull
|
||||
|
||||
.. autoexception:: PinEdgeDetectUnsupported
|
||||
|
||||
.. autoexception:: PinGPIOUnsupported
|
||||
|
||||
.. autoexception:: PinSPIUnsupported
|
||||
|
||||
.. autoexception:: PinPWMError
|
||||
|
||||
.. autoexception:: PinPWMUnsupported
|
||||
|
||||
.. autoexception:: PinPWMFixedValue
|
||||
|
||||
.. autoexception:: PinUnknownPi
|
||||
|
||||
.. autoexception:: PinMultiplePins
|
||||
|
||||
.. autoexception:: PinNoPins
|
||||
|
||||
.. autoexception:: PinUnknownPi
|
||||
.. autoexception:: PinInvalidPin
|
||||
|
||||
Warnings
|
||||
========
|
||||
@@ -110,3 +132,7 @@ Warnings
|
||||
|
||||
.. 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
|
||||
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
|
||||
library actually expects is a :class:`Pin` implementation. If it finds a simple
|
||||
integer number instead, it uses one of the following classes to provide the
|
||||
:class:`Pin` implementation (classes are listed in favoured order):
|
||||
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 anything
|
||||
else, it uses the existing ``Device._pin_factory`` to construct a :class:`Pin`
|
||||
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
|
||||
``pin_factory`` global in the ``devices`` module like so::
|
||||
The following values, and the corresponding :class:`Factory` and :class:`Pin`
|
||||
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
|
||||
# Force the default pin implementation to be NativePin
|
||||
gpiozero.devices.pin_factory = NativePin
|
||||
+---------+-----------------------------------------------+-------------------------------------------+
|
||||
| Name | Factory class | Pin class |
|
||||
+=========+===============================================+===========================================+
|
||||
| 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
|
||||
|
||||
# This will now use NativePin instead of RPiGPIOPin
|
||||
led = LED(16)
|
||||
|
||||
``pin_factory`` is a concrete descendent of the abstract :class:`Pin` class.
|
||||
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.
|
||||
Certain factories may take default information from additional sources.
|
||||
For example, to default to creating pins with
|
||||
:class:`gpiozero.pins.pigpiod.PiGPIOPin` on a remote pi called ``remote-pi``
|
||||
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,
|
||||
you can pass an object derived from :class:`Pin` itself::
|
||||
|
||||
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!
|
||||
$ export GPIOZERO_PIN_FACTORY=pigpio
|
||||
$ PIGPIO_ADDR=remote-pi python3 my_script.py
|
||||
|
||||
.. 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.
|
||||
|
||||
|
||||
RPiGPIOPin
|
||||
==========
|
||||
RPi.GPIO
|
||||
========
|
||||
|
||||
.. autoclass:: gpiozero.pins.rpigpio.RPiGPIOFactory
|
||||
|
||||
.. autoclass:: gpiozero.pins.rpigpio.RPiGPIOPin
|
||||
|
||||
|
||||
RPIOPin
|
||||
=======
|
||||
RPIO
|
||||
====
|
||||
|
||||
.. autoclass:: gpiozero.pins.rpio.RPIOFactory
|
||||
|
||||
.. autoclass:: gpiozero.pins.rpio.RPIOPin
|
||||
|
||||
|
||||
PiGPIOPin
|
||||
=========
|
||||
PiGPIO
|
||||
======
|
||||
|
||||
.. autoclass:: gpiozero.pins.pigpiod.PiGPIOFactory
|
||||
|
||||
.. autoclass:: gpiozero.pins.pigpiod.PiGPIOPin
|
||||
|
||||
|
||||
NativePin
|
||||
=========
|
||||
Native
|
||||
======
|
||||
|
||||
.. autoclass:: gpiozero.pins.native.NativeFactory
|
||||
|
||||
.. autoclass:: gpiozero.pins.native.NativePin
|
||||
|
||||
|
||||
Abstract Pin
|
||||
Base classes
|
||||
============
|
||||
|
||||
.. autoclass:: Factory
|
||||
:members:
|
||||
|
||||
.. autoclass:: Pin
|
||||
:members:
|
||||
|
||||
|
||||
Local Pin
|
||||
=========
|
||||
|
||||
.. autoclass:: LocalPin
|
||||
.. autoclass:: SPI
|
||||
: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
|
||||
|
||||
|
||||
.. _keep-your-script-running:
|
||||
|
||||
Keep your script running
|
||||
@@ -46,6 +47,7 @@ events to be detected::
|
||||
button.when_pressed = hello
|
||||
pause()
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
How can I tell what version of gpiozero I have installed?
|
||||
=========================================================
|
||||
|
||||
The gpiozero library relies on the setuptools package for installation
|
||||
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
|
||||
>>> require('gpiozero')
|
||||
@@ -89,7 +94,9 @@ the first entry in the list will be the version that ``import gpiozero`` will
|
||||
import.
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ library. Please note that all recipes are written assuming Python 3. Recipes
|
||||
*may* work under Python 2, but no guarantees!
|
||||
|
||||
|
||||
.. _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").
|
||||
|
||||
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 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
|
||||
|
||||
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 input | sudo tee /sys/class/leds/led1/trigger
|
||||
|
||||
@@ -6,8 +6,9 @@ from __future__ import (
|
||||
)
|
||||
|
||||
from .pins import (
|
||||
Factory,
|
||||
Pin,
|
||||
LocalPin,
|
||||
SPI,
|
||||
)
|
||||
from .pins.data import (
|
||||
PiBoardInfo,
|
||||
@@ -15,47 +16,9 @@ from .pins.data import (
|
||||
PinInfo,
|
||||
pi_info,
|
||||
)
|
||||
from .exc import (
|
||||
GPIOZeroError,
|
||||
DeviceClosed,
|
||||
BadEventHandler,
|
||||
BadWaitTime,
|
||||
BadQueueLen,
|
||||
CompositeDeviceError,
|
||||
CompositeDeviceBadName,
|
||||
CompositeDeviceBadOrder,
|
||||
CompositeDeviceBadDevice,
|
||||
SPIError,
|
||||
SPIBadArgs,
|
||||
EnergenieSocketMissing,
|
||||
EnergenieBadSocket,
|
||||
GPIODeviceError,
|
||||
GPIODeviceClosed,
|
||||
GPIOPinInUse,
|
||||
GPIOPinMissing,
|
||||
InputDeviceError,
|
||||
OutputDeviceError,
|
||||
OutputDeviceBadValue,
|
||||
PinError,
|
||||
PinInvalidFunction,
|
||||
PinInvalidState,
|
||||
PinInvalidPull,
|
||||
PinInvalidEdges,
|
||||
PinSetInput,
|
||||
PinFixedPull,
|
||||
PinEdgeDetectUnsupported,
|
||||
PinPWMError,
|
||||
PinPWMUnsupported,
|
||||
PinPWMFixedValue,
|
||||
PinUnknownPi,
|
||||
PinMultiplePins,
|
||||
PinNoPins,
|
||||
GPIOZeroWarning,
|
||||
SPIWarning,
|
||||
SPISoftwareFallback,
|
||||
PinWarning,
|
||||
PinNonPhysical,
|
||||
)
|
||||
# Yes, import * is naughty, but exc imports nothing else so there's no cross
|
||||
# contamination here ... and besides, have you *seen* the list lately?!
|
||||
from .exc import *
|
||||
from .devices import (
|
||||
Device,
|
||||
GPIODevice,
|
||||
|
||||
@@ -10,15 +10,16 @@ str = type('')
|
||||
import os
|
||||
import atexit
|
||||
import weakref
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
from itertools import chain
|
||||
from types import FunctionType
|
||||
from threading import RLock
|
||||
from threading import Lock
|
||||
|
||||
import pkg_resources
|
||||
|
||||
from .pins import Pin
|
||||
from .threads import _threads_shutdown
|
||||
from .pins import _pins_shutdown
|
||||
from .mixins import (
|
||||
ValuesMixin,
|
||||
SharedMixin,
|
||||
@@ -32,52 +33,11 @@ from .exc import (
|
||||
GPIOPinMissing,
|
||||
GPIOPinInUse,
|
||||
GPIODeviceClosed,
|
||||
PinFactoryFallback,
|
||||
)
|
||||
from .compat import frozendict
|
||||
|
||||
|
||||
def _default_pin_factory(name=os.getenv('GPIOZERO_PIN_FACTORY', None)):
|
||||
group = 'gpiozero_pin_factories'
|
||||
if name is None:
|
||||
# If no factory is explicitly specified, try various names in
|
||||
# "preferred" order. Note that in this case we only select from
|
||||
# gpiozero distribution so without explicitly specifying a name (via
|
||||
# the environment) it's impossible to auto-select a factory from
|
||||
# outside the base distribution
|
||||
#
|
||||
# We prefer RPi.GPIO here as it supports PWM, and all Pi revisions. If
|
||||
# no third-party libraries are available, however, we fall back to a
|
||||
# pure Python implementation which supports platforms like PyPy
|
||||
dist = pkg_resources.get_distribution('gpiozero')
|
||||
for name in ('RPiGPIOPin', 'RPIOPin', 'PiGPIOPin', 'NativePin'):
|
||||
try:
|
||||
return pkg_resources.load_entry_point(dist, group, name)
|
||||
except ImportError:
|
||||
pass
|
||||
raise BadPinFactory('Unable to locate any default pin factory!')
|
||||
else:
|
||||
for factory in pkg_resources.iter_entry_points(group, name):
|
||||
return factory.load()
|
||||
raise BadPinFactory('Unable to locate pin factory "%s"' % name)
|
||||
|
||||
pin_factory = _default_pin_factory()
|
||||
|
||||
|
||||
_PINS = set()
|
||||
_PINS_LOCK = RLock() # Yes, this needs to be re-entrant
|
||||
|
||||
def _shutdown():
|
||||
_threads_shutdown()
|
||||
with _PINS_LOCK:
|
||||
while _PINS:
|
||||
_PINS.pop().close()
|
||||
# Any cleanup routines registered by pins libraries must be called *after*
|
||||
# cleanup of pin objects used by devices
|
||||
_pins_shutdown()
|
||||
|
||||
atexit.register(_shutdown)
|
||||
|
||||
|
||||
class GPIOMeta(type):
|
||||
# NOTE Yes, this is a metaclass. Don't be scared - it's a simple one.
|
||||
|
||||
@@ -106,7 +66,7 @@ class GPIOMeta(type):
|
||||
# already exists. Only construct the instance if the key's new.
|
||||
key = cls._shared_key(*args, **kwargs)
|
||||
try:
|
||||
self = cls._INSTANCES[key]
|
||||
self = cls._instances[key]
|
||||
self._refs += 1
|
||||
except (KeyError, ReferenceError) as e:
|
||||
self = super(GPIOMeta, cls).__call__(*args, **kwargs)
|
||||
@@ -122,14 +82,14 @@ class GPIOMeta(type):
|
||||
old_close()
|
||||
finally:
|
||||
try:
|
||||
del cls._INSTANCES[key]
|
||||
del cls._instances[key]
|
||||
except KeyError:
|
||||
# If the _refs go negative (too many closes)
|
||||
# just ignore the resulting KeyError here -
|
||||
# it's already gone
|
||||
pass
|
||||
self.close = close
|
||||
cls._INSTANCES[key] = weakref.proxy(self)
|
||||
cls._instances[key] = weakref.proxy(self)
|
||||
else:
|
||||
# Construct the instance as normal
|
||||
self = super(GPIOMeta, cls).__call__(*args, **kwargs)
|
||||
@@ -229,13 +189,100 @@ class GPIOBase(GPIOMeta(nstr('GPIOBase'), (), {})):
|
||||
class Device(ValuesMixin, GPIOBase):
|
||||
"""
|
||||
Represents a single device of any type; GPIO-based, SPI-based, I2C-based,
|
||||
etc. This is the base class of the device hierarchy. It defines the
|
||||
basic services applicable to all devices (specifically the :attr:`is_active`
|
||||
etc. This is the base class of the device hierarchy. It defines the basic
|
||||
services applicable to all devices (specifically the :attr:`is_active`
|
||||
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):
|
||||
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
|
||||
def value(self):
|
||||
"""
|
||||
@@ -378,14 +425,12 @@ class GPIODevice(Device):
|
||||
self._pin = None
|
||||
if pin is None:
|
||||
raise GPIOPinMissing('No pin given')
|
||||
if isinstance(pin, int):
|
||||
pin = pin_factory(pin)
|
||||
with _PINS_LOCK:
|
||||
if pin in _PINS:
|
||||
raise GPIOPinInUse(
|
||||
'pin %r is already in use by another gpiozero object' % pin
|
||||
)
|
||||
_PINS.add(pin)
|
||||
if isinstance(pin, Pin):
|
||||
self._reserve_pins(pin)
|
||||
else:
|
||||
# Check you can reserve *before* constructing the pin
|
||||
self._reserve_pins(self._pin_factory.pin_address(pin))
|
||||
pin = self._pin_factory.pin(pin)
|
||||
self._pin = pin
|
||||
self._active_state = True
|
||||
self._inactive_state = False
|
||||
@@ -402,12 +447,10 @@ class GPIODevice(Device):
|
||||
|
||||
def close(self):
|
||||
super(GPIODevice, self).close()
|
||||
with _PINS_LOCK:
|
||||
pin = self._pin
|
||||
if self._pin is not None:
|
||||
self._release_pins(self._pin)
|
||||
self._pin.close()
|
||||
self._pin = None
|
||||
if pin in _PINS:
|
||||
_PINS.remove(pin)
|
||||
pin.close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
@@ -441,3 +484,41 @@ class GPIODevice(Device):
|
||||
except DeviceClosed:
|
||||
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):
|
||||
"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):
|
||||
"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"
|
||||
|
||||
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):
|
||||
"Base class for errors specific to the InputDevice hierarchy"
|
||||
@@ -100,6 +118,12 @@ class PinFixedPull(PinError, AttributeError):
|
||||
class PinEdgeDetectUnsupported(PinError, AttributeError):
|
||||
"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):
|
||||
"Base class for errors related to PWM implementations"
|
||||
|
||||
@@ -118,6 +142,9 @@ class PinMultiplePins(PinError, RuntimeError):
|
||||
class PinNoPins(PinError, RuntimeError):
|
||||
"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):
|
||||
"Base class for all warnings in GPIO Zero"
|
||||
|
||||
@@ -130,6 +157,9 @@ class SPISoftwareFallback(SPIWarning):
|
||||
class PinWarning(GPIOZeroWarning):
|
||||
"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):
|
||||
"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():
|
||||
return super(SmoothedInputDevice, self).__repr__()
|
||||
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)
|
||||
|
||||
@property
|
||||
@@ -240,7 +240,7 @@ class Button(HoldMixin, DigitalInputDevice):
|
||||
print("The button was pressed!")
|
||||
|
||||
: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.
|
||||
|
||||
:param bool pull_up:
|
||||
@@ -302,7 +302,7 @@ class LineSensor(SmoothedInputDevice):
|
||||
pause()
|
||||
|
||||
: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.
|
||||
|
||||
:param int queue_len:
|
||||
@@ -371,7 +371,7 @@ class MotionSensor(SmoothedInputDevice):
|
||||
print("Motion detected!")
|
||||
|
||||
: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.
|
||||
|
||||
:param int queue_len:
|
||||
@@ -435,7 +435,7 @@ class LightSensor(SmoothedInputDevice):
|
||||
print("Light detected!")
|
||||
|
||||
: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.
|
||||
|
||||
:param int queue_len:
|
||||
@@ -543,11 +543,11 @@ class DistanceSensor(SmoothedInputDevice):
|
||||
|
||||
:param int echo:
|
||||
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:
|
||||
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:
|
||||
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
|
||||
decremented and the instance will only close when it reaches zero.
|
||||
"""
|
||||
_INSTANCES = {}
|
||||
_instances = {}
|
||||
|
||||
def __del__(self):
|
||||
self._refs = 0
|
||||
|
||||
@@ -128,8 +128,8 @@ class DigitalOutputDevice(OutputDevice):
|
||||
"""
|
||||
def __init__(self, pin=None, active_high=True, initial_value=False):
|
||||
self._blink_thread = None
|
||||
super(DigitalOutputDevice, self).__init__(pin, active_high, initial_value)
|
||||
self._controller = None
|
||||
super(DigitalOutputDevice, self).__init__(pin, active_high, initial_value)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
@@ -217,7 +217,7 @@ class LED(DigitalOutputDevice):
|
||||
led.on()
|
||||
|
||||
: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.
|
||||
|
||||
:param bool active_high:
|
||||
@@ -252,7 +252,7 @@ class Buzzer(DigitalOutputDevice):
|
||||
bz.on()
|
||||
|
||||
: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.
|
||||
|
||||
:param bool active_high:
|
||||
@@ -276,7 +276,7 @@ class PWMOutputDevice(OutputDevice):
|
||||
Generic output device configured for pulse-width modulation (PWM).
|
||||
|
||||
:param int pin:
|
||||
The GPIO pin which the device is attached to. See :ref:`pin_numbering`
|
||||
The GPIO pin which the device is attached to. See :ref:`pin-numbering`
|
||||
for valid pin numbers.
|
||||
|
||||
:param bool active_high:
|
||||
@@ -483,7 +483,7 @@ class PWMLED(PWMOutputDevice):
|
||||
an optional resistor to prevent the LED from burning out.
|
||||
|
||||
:param int pin:
|
||||
The GPIO pin which the LED is attached to. See :ref:`pin_numbering` for
|
||||
The GPIO pin which the LED is attached to. See :ref:`pin-numbering` for
|
||||
valid pin numbers.
|
||||
|
||||
:param bool active_high:
|
||||
@@ -562,8 +562,12 @@ class RGBLED(SourceMixin, Device):
|
||||
raise GPIOPinMissing('red, green, and blue pins must be provided')
|
||||
LEDClass = PWMLED if pwm else LED
|
||||
super(RGBLED, self).__init__()
|
||||
self._leds = tuple(LEDClass(pin, active_high) for pin in (red, green, blue))
|
||||
self.value = initial_value
|
||||
try:
|
||||
self._leds = tuple(LEDClass(pin, active_high) for pin in (red, green, blue))
|
||||
self.value = initial_value
|
||||
except:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
red = _led_property(0)
|
||||
green = _led_property(1)
|
||||
@@ -926,7 +930,7 @@ class Servo(SourceMixin, CompositeDevice):
|
||||
sleep(1)
|
||||
|
||||
: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.
|
||||
|
||||
:param float initial_value:
|
||||
@@ -1116,7 +1120,7 @@ class AngularServo(Servo):
|
||||
expectations of minimum and maximum.
|
||||
|
||||
: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.
|
||||
|
||||
:param float initial_angle:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
absolute_import,
|
||||
@@ -6,32 +8,124 @@ from __future__ import (
|
||||
)
|
||||
str = type('')
|
||||
|
||||
import io
|
||||
|
||||
from .data import pi_info
|
||||
from ..exc import (
|
||||
PinInvalidFunction,
|
||||
PinSetInput,
|
||||
PinFixedPull,
|
||||
PinSPIUnsupported,
|
||||
PinPWMUnsupported,
|
||||
PinEdgeDetectUnsupported,
|
||||
SPIFixedClockMode,
|
||||
SPIFixedBitOrder,
|
||||
SPIFixedSelect,
|
||||
SPIFixedWordSize,
|
||||
)
|
||||
|
||||
|
||||
PINS_CLEANUP = []
|
||||
def _pins_shutdown():
|
||||
for routine in PINS_CLEANUP:
|
||||
routine()
|
||||
class Factory(object):
|
||||
"""
|
||||
Generates pins, SPI, and I2C interfaces for devices. This is an abstract
|
||||
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):
|
||||
"""
|
||||
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
|
||||
represent the capabilities of pins. The following functions *must* be
|
||||
overridden:
|
||||
|
||||
* :meth:`_get_address`
|
||||
* :meth:`_get_function`
|
||||
* :meth:`_set_function`
|
||||
* :meth:`_get_state`
|
||||
@@ -39,6 +133,8 @@ class Pin(object):
|
||||
The following functions *may* be overridden if applicable:
|
||||
|
||||
* :meth:`close`
|
||||
* :meth:`output_with_state`
|
||||
* :meth:`input_with_pull`
|
||||
* :meth:`_set_state`
|
||||
* :meth:`_get_frequency`
|
||||
* :meth:`_set_frequency`
|
||||
@@ -50,20 +146,10 @@ class Pin(object):
|
||||
* :meth:`_set_edges`
|
||||
* :meth:`_get_when_changed`
|
||||
* :meth:`_set_when_changed`
|
||||
* :meth:`pi_info`
|
||||
* :meth:`output_with_state`
|
||||
* :meth:`input_with_pull`
|
||||
|
||||
.. warning::
|
||||
|
||||
Descendents must ensure that pin instances representing the same
|
||||
physical hardware are identical, right down to object identity. The
|
||||
framework relies on this to correctly clean up resources at interpreter
|
||||
shutdown.
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return "Abstract pin"
|
||||
return self.address[-1]
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
@@ -105,6 +191,18 @@ class Pin(object):
|
||||
self.function = 'input'
|
||||
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):
|
||||
return "input"
|
||||
|
||||
@@ -140,10 +238,19 @@ class Pin(object):
|
||||
doc="""\
|
||||
The state of the pin. This is 0 for low, and 1 for high. As a low level
|
||||
view of the pin, no swapping is performed in the case of pull ups (see
|
||||
:attr:`pull` for more information).
|
||||
:attr:`pull` for more information):
|
||||
|
||||
If PWM is currently active (when :attr:`frequency` is not ``None``),
|
||||
this represents the PWM duty cycle as a value between 0.0 and 1.0.
|
||||
.. code-block:: text
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
property will raise :exc:`PinEdgeDetectUnsupported`. If the pin
|
||||
supports edge detection, the class must implement bounce detection,
|
||||
@@ -223,7 +350,18 @@ class Pin(object):
|
||||
doc="""\
|
||||
The edge that will trigger execution of the function or bound method
|
||||
assigned to :attr:`when_changed`. This can be one of the strings
|
||||
"both" (the default), "rising", "falling", or "none".
|
||||
"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
|
||||
property will raise :exc:`PinEdgeDetectUnsupported`.
|
||||
@@ -247,48 +385,300 @@ class Pin(object):
|
||||
property will raise :exc:`PinEdgeDetectUnsupported`.
|
||||
""")
|
||||
|
||||
@classmethod
|
||||
def pi_info(cls):
|
||||
"""
|
||||
Returns a :class:`PiBoardInfo` instance representing the Pi that
|
||||
instances of this pin class will be attached to.
|
||||
|
||||
If the pins represented by this class are not *directly* attached to a
|
||||
Pi (e.g. the pin is attached to a board attached to the Pi, or the pins
|
||||
are not on a Pi at all), this may return ``None``.
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
class LocalPin(Pin):
|
||||
class SPI(object):
|
||||
"""
|
||||
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`).
|
||||
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
|
||||
"""
|
||||
_PI_REVISION = None
|
||||
|
||||
@classmethod
|
||||
def pi_info(cls):
|
||||
def read(self, n):
|
||||
"""
|
||||
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)
|
||||
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.
|
||||
""")
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from itertools import cycle
|
||||
from operator import attrgetter
|
||||
from collections import namedtuple
|
||||
|
||||
from ..exc import PinUnknownPi, PinMultiplePins, PinNoPins
|
||||
from ..exc import PinUnknownPi, PinMultiplePins, PinNoPins, PinInvalidPin
|
||||
|
||||
|
||||
# Some useful constants for describing pins
|
||||
@@ -119,8 +119,8 @@ A_BOARD = """\
|
||||
|
||||
BPLUS_BOARD = """\
|
||||
{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}| {P1:{style} col1}{style:white on green} {style:black on white}| USB{style:reset}
|
||||
{style:white on green}| {J8:{style} col2}{style:white on green} J8 {style:black on white}+===={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: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}
|
||||
@@ -134,8 +134,8 @@ BPLUS_BOARD = """\
|
||||
|
||||
APLUS_BOARD = """\
|
||||
{style:white on green},--------------------------.{style:reset}
|
||||
{style:white on green}| {P1:{style} col2}{style:white on green} P1 |{style:reset}
|
||||
{style:white on green}| {P1:{style} col1}{style:white on green} |{style:reset}
|
||||
{style:white on green}| {J8:{style} col2}{style:white on green} J8 |{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: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}
|
||||
@@ -149,8 +149,8 @@ APLUS_BOARD = """\
|
||||
|
||||
ZERO12_BOARD = """\
|
||||
{style:white on green},-------------------------.{style:reset}
|
||||
{style:white on green}| {P1:{style} col2}{style:white on green} P1 |{style:reset}
|
||||
{style:white on green}| {P1:{style} col1}{style:white on green} |{style:reset}
|
||||
{style:white on green}| {J8:{style} col2}{style:white on green} J8 |{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} 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}
|
||||
@@ -158,8 +158,8 @@ ZERO12_BOARD = """\
|
||||
|
||||
ZERO13_BOARD = """\
|
||||
{style:white on green}.-------------------------.{style:reset}
|
||||
{style:white on green}| {P1:{style} col2}{style:white on green} P1 |{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} col2}{style:white on green} J8 |{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} 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}
|
||||
@@ -216,7 +216,7 @@ REV2_P5 = {
|
||||
7: (GND, False), 8: (GND, False),
|
||||
}
|
||||
|
||||
PLUS_P1 = {
|
||||
PLUS_J8 = {
|
||||
1: (V3_3, False), 2: (V5, False),
|
||||
3: (GPIO2, True), 4: (V5, 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, ),
|
||||
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, ),
|
||||
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, ),
|
||||
0x12: ('A+', '1.1', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, APLUS_BOARD, ),
|
||||
0x13: ('B+', '1.2', '2015Q1', 'BCM2835', 'Egoman', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, BPLUS_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, {'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, ),
|
||||
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 *
|
||||
|
||||
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: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
|
||||
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
|
||||
|
||||
.. _ANSI color codes: https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
The following attributes are defined:
|
||||
|
||||
.. automethod:: pprint
|
||||
@@ -548,7 +548,7 @@ class HeaderInfo(namedtuple('HeaderInfo', (
|
||||
.. attribute:: name
|
||||
|
||||
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
|
||||
|
||||
@@ -561,6 +561,8 @@ class HeaderInfo(namedtuple('HeaderInfo', (
|
||||
.. attribute:: pins
|
||||
|
||||
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
|
||||
|
||||
@@ -685,6 +687,7 @@ class PiBoardInfo(namedtuple('PiBoardInfo', (
|
||||
|
||||
from gpiozero import *
|
||||
|
||||
print('{0}'.format(pi_info()))
|
||||
print('{0:full}'.format(pi_info()))
|
||||
print('{0:board}'.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.
|
||||
For example, to obtain information about header P1 you would query
|
||||
``headers['P1']``. To obtain information about pin 12 on header P1 you
|
||||
would query ``headers['P1'].pins[12]``.
|
||||
``headers['P1']``. To obtain information about pin 12 on header J8 you
|
||||
would query ``headers['J8'].pins[12]``.
|
||||
|
||||
A rendered version of this data can be obtained by using the
|
||||
:class:`PiBoardInfo` object in a format string::
|
||||
@@ -937,10 +940,10 @@ class PiBoardInfo(namedtuple('PiBoardInfo', (
|
||||
}.get(model, csi)
|
||||
headers = {
|
||||
'A': {'P1': REV2_P1, 'P5': REV2_P5},
|
||||
'B': {'P1': REV2_P1, 'P5': REV2_P5} if pcb_revision == '2.0' else {'P1': REV1_P1},
|
||||
'B': {'P1': REV1_P1} if pcb_revision == '1.0' else {'P1': REV2_P1, 'P5': REV2_P5},
|
||||
'CM': {'SODIMM': CM_SODIMM},
|
||||
'CM3': {'SODIMM': CM3_SODIMM},
|
||||
}.get(model, {'P1': PLUS_P1})
|
||||
}.get(model, {'J8': PLUS_J8})
|
||||
board = {
|
||||
'A': A_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.
|
||||
|
||||
If *color* is ``None`` (the default, the diagram will include ANSI
|
||||
color codes if stdout is a color-capable terminal). Otherwise *color*
|
||||
If *color* is ``None`` (the default), the diagram will include ANSI
|
||||
color codes if stdout is a color-capable terminal. Otherwise *color*
|
||||
can be set to ``True`` or ``False`` to force color or monochrome
|
||||
output.
|
||||
"""
|
||||
@@ -1134,13 +1137,10 @@ def pi_info(revision=None):
|
||||
the model of Pi it is running on and return information about that.
|
||||
"""
|
||||
if revision is None:
|
||||
# NOTE: This import is declared locally for two reasons. Firstly it
|
||||
# avoids a circular dependency (devices->pins->pins.data->devices).
|
||||
# Secondly, pin_factory is one global which might potentially be
|
||||
# re-written by a user's script at runtime hence we should re-import
|
||||
# here in case it's changed since initialization
|
||||
from ..devices import pin_factory
|
||||
result = pin_factory.pi_info()
|
||||
# The reason this import is located here is to avoid a circular
|
||||
# dependency; devices->pins.local->pins.data->devices
|
||||
from ..devices import Device
|
||||
result = Device._pin_factory.pi_info
|
||||
if result is None:
|
||||
raise PinUnknownPi('The default pin_factory is not attached to a Pi')
|
||||
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,56 +15,28 @@ try:
|
||||
except ImportError:
|
||||
from ..compat import isclose
|
||||
|
||||
from . import Pin
|
||||
from .data import pi_info
|
||||
from ..exc import PinSetInput, PinPWMUnsupported, PinFixedPull
|
||||
from ..exc import PinPWMUnsupported, PinSetInput, PinFixedPull
|
||||
from ..devices import Device
|
||||
from .pi import PiPin
|
||||
from .local import LocalPiFactory
|
||||
|
||||
|
||||
PinState = namedtuple('PinState', ('timestamp', 'state'))
|
||||
|
||||
class MockPin(Pin):
|
||||
class MockPin(PiPin):
|
||||
"""
|
||||
A mock pin used primarily for testing. This class does *not* support PWM.
|
||||
"""
|
||||
|
||||
_PINS = {}
|
||||
|
||||
@classmethod
|
||||
def clear_pins(cls):
|
||||
cls._PINS.clear()
|
||||
|
||||
@classmethod
|
||||
def pi_info(cls):
|
||||
return pi_info('a21041') # Pretend we're a Pi 2B
|
||||
|
||||
def __new__(cls, number):
|
||||
if not (0 <= number < 54):
|
||||
raise ValueError('invalid pin %d specified (must be 0..53)' % number)
|
||||
try:
|
||||
old_pin = cls._PINS[number]
|
||||
except KeyError:
|
||||
self = super(MockPin, cls).__new__(cls)
|
||||
cls._PINS[number] = self
|
||||
self._number = number
|
||||
self._function = 'input'
|
||||
self._state = False
|
||||
self._pull = 'floating'
|
||||
self._bounce = None
|
||||
self._edges = 'both'
|
||||
self._when_changed = None
|
||||
self.clear_states()
|
||||
return self
|
||||
# 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 __init__(self, factory, number):
|
||||
super(MockPin, self).__init__(factory, number)
|
||||
self._function = 'input'
|
||||
self._state = False
|
||||
self._pull = 'floating'
|
||||
self._bounce = None
|
||||
self._edges = 'both'
|
||||
self._when_changed = None
|
||||
self.clear_states()
|
||||
|
||||
def close(self):
|
||||
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
|
||||
to time the charging rate).
|
||||
"""
|
||||
def __init__(self, number):
|
||||
super(MockChargingPin, self).__init__()
|
||||
def __init__(self, factory, number):
|
||||
super(MockChargingPin, self).__init__(factory, number)
|
||||
self.charge_time = 0.01 # dark charging time
|
||||
self._charge_stop = Event()
|
||||
self._charge_thread = None
|
||||
@@ -225,8 +197,8 @@ class MockTriggerPin(MockPin):
|
||||
corresponding pin instance. When this pin is driven high it will trigger
|
||||
the echo pin to drive high for the echo time.
|
||||
"""
|
||||
def __init__(self, number):
|
||||
super(MockTriggerPin, self).__init__()
|
||||
def __init__(self, factory, number):
|
||||
super(MockTriggerPin, self).__init__(factory, number)
|
||||
self.echo_pin = None
|
||||
self.echo_time = 0.04 # longest echo time
|
||||
self._echo_thread = None
|
||||
@@ -250,8 +222,8 @@ class MockPWMPin(MockPin):
|
||||
"""
|
||||
This derivative of :class:`MockPin` adds PWM support.
|
||||
"""
|
||||
def __init__(self, number):
|
||||
super(MockPWMPin, self).__init__()
|
||||
def __init__(self, factory, number):
|
||||
super(MockPWMPin, self).__init__(factory, number)
|
||||
self._frequency = None
|
||||
|
||||
def close(self):
|
||||
@@ -283,8 +255,8 @@ class MockSPIClockPin(MockPin):
|
||||
rather, construct a :class:`MockSPIDevice` with various pin numbers, and
|
||||
this class will be used for the clock pin.
|
||||
"""
|
||||
def __init__(self, number):
|
||||
super(MockSPIClockPin, self).__init__()
|
||||
def __init__(self, factory, number):
|
||||
super(MockSPIClockPin, self).__init__(factory, number)
|
||||
if not hasattr(self, 'spi_devices'):
|
||||
self.spi_devices = []
|
||||
|
||||
@@ -301,8 +273,8 @@ class MockSPISelectPin(MockPin):
|
||||
tests; rather, construct a :class:`MockSPIDevice` with various pin numbers,
|
||||
and this class will be used for the select pin.
|
||||
"""
|
||||
def __init__(self, number):
|
||||
super(MockSPISelectPin, self).__init__()
|
||||
def __init__(self, factory, number):
|
||||
super(MockSPISelectPin, self).__init__(factory, number)
|
||||
if not hasattr(self, 'spi_device'):
|
||||
self.spi_device = None
|
||||
|
||||
@@ -314,13 +286,13 @@ class MockSPISelectPin(MockPin):
|
||||
|
||||
class MockSPIDevice(object):
|
||||
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,
|
||||
bits_per_word=8, select_high=False):
|
||||
self.clock_pin = MockSPIClockPin(clock_pin)
|
||||
self.mosi_pin = None if mosi_pin is None else MockPin(mosi_pin)
|
||||
self.miso_pin = None if miso_pin is None else MockPin(miso_pin)
|
||||
self.select_pin = None if select_pin is None else MockSPISelectPin(select_pin)
|
||||
self.clock_pin = Device._pin_factory.pin(clock_pin, pin_class=MockSPIClockPin)
|
||||
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 Device._pin_factory.pin(miso_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_phase = clock_phase
|
||||
self.lsb_first = lsb_first
|
||||
@@ -413,3 +385,34 @@ class MockSPIDevice(object):
|
||||
bits = reversed(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 struct
|
||||
import warnings
|
||||
import weakref
|
||||
from time import sleep
|
||||
from threading import Thread, Event, Lock
|
||||
from collections import Counter
|
||||
|
||||
from . import LocalPin, PINS_CLEANUP
|
||||
from .data import pi_info
|
||||
from .local import LocalPiPin, LocalPiFactory
|
||||
from ..exc import (
|
||||
PinInvalidPull,
|
||||
PinInvalidEdges,
|
||||
PinInvalidFunction,
|
||||
PinFixedPull,
|
||||
PinSetInput,
|
||||
PinNonPhysical,
|
||||
PinNoPins,
|
||||
)
|
||||
|
||||
|
||||
@@ -149,7 +147,7 @@ class GPIOFS(object):
|
||||
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
|
||||
pins. This is the default pin implementation if no third-party libraries
|
||||
@@ -169,10 +167,17 @@ class NativePin(LocalPin):
|
||||
|
||||
led = LED(NativePin(12))
|
||||
"""
|
||||
def __init__(self):
|
||||
super(NativeFactory, self).__init__()
|
||||
self.mem = GPIOMemory()
|
||||
self.pin_class = NativePin
|
||||
|
||||
_MEM = None
|
||||
_PINS = {}
|
||||
def close(self):
|
||||
super(NativeFactory, self).close()
|
||||
self.mem.close()
|
||||
|
||||
|
||||
class NativePin(LocalPiPin):
|
||||
GPIO_FUNCTIONS = {
|
||||
'input': 0b000,
|
||||
'output': 0b001,
|
||||
@@ -202,89 +207,62 @@ class NativePin(LocalPin):
|
||||
GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()}
|
||||
GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()}
|
||||
|
||||
PI_INFO = None
|
||||
|
||||
def __new__(cls, number):
|
||||
if not cls._PINS:
|
||||
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._set_offset = self._MEM.GPSET_OFFSET + (number // 32)
|
||||
self._set_shift = number % 32
|
||||
self._clear_offset = self._MEM.GPCLR_OFFSET + (number // 32)
|
||||
self._clear_shift = number % 32
|
||||
self._level_offset = self._MEM.GPLEV_OFFSET + (number // 32)
|
||||
self._level_shift = number % 32
|
||||
self._pull_offset = self._MEM.GPPUDCLK_OFFSET + (number // 32)
|
||||
self._pull_shift = number % 32
|
||||
self._edge_offset = self._MEM.GPEDS_OFFSET + (number // 32)
|
||||
self._edge_shift = number % 32
|
||||
self._rising_offset = self._MEM.GPREN_OFFSET + (number // 32)
|
||||
self._rising_shift = number % 32
|
||||
self._falling_offset = self._MEM.GPFEN_OFFSET + (number // 32)
|
||||
self._falling_shift = number % 32
|
||||
self._when_changed = None
|
||||
self._change_thread = None
|
||||
self._change_event = Event()
|
||||
self.function = 'input'
|
||||
self.pull = 'up' if cls.PI_INFO.pulled_up('GPIO%d' % number) else 'floating'
|
||||
self.bounce = None
|
||||
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 __init__(self, factory, number):
|
||||
super(NativePin, self).__init__(factory, number)
|
||||
self._func_offset = self.factory.mem.GPFSEL_OFFSET + (number // 10)
|
||||
self._func_shift = (number % 10) * 3
|
||||
self._set_offset = self.factory.mem.GPSET_OFFSET + (number // 32)
|
||||
self._set_shift = number % 32
|
||||
self._clear_offset = self.factory.mem.GPCLR_OFFSET + (number // 32)
|
||||
self._clear_shift = number % 32
|
||||
self._level_offset = self.factory.mem.GPLEV_OFFSET + (number // 32)
|
||||
self._level_shift = number % 32
|
||||
self._pull_offset = self.factory.mem.GPPUDCLK_OFFSET + (number // 32)
|
||||
self._pull_shift = number % 32
|
||||
self._edge_offset = self.factory.mem.GPEDS_OFFSET + (number // 32)
|
||||
self._edge_shift = number % 32
|
||||
self._rising_offset = self.factory.mem.GPREN_OFFSET + (number // 32)
|
||||
self._rising_shift = number % 32
|
||||
self._falling_offset = self.factory.mem.GPFEN_OFFSET + (number // 32)
|
||||
self._falling_shift = number % 32
|
||||
self._when_changed = None
|
||||
self._change_thread = None
|
||||
self._change_event = Event()
|
||||
self.function = 'input'
|
||||
self.pull = 'up' if factory.pi_info.pulled_up('GPIO%d' % number) else 'floating'
|
||||
self.bounce = None
|
||||
self.edges = 'both'
|
||||
|
||||
def close(self):
|
||||
self.frequency = None
|
||||
self.when_changed = None
|
||||
self.function = 'input'
|
||||
self.pull = 'up' if self.PI_INFO.pulled_up('GPIO%d' % self.number) else 'floating'
|
||||
self.pull = 'up' if self.factory.pi_info.pulled_up('GPIO%d' % self.number) else 'floating'
|
||||
|
||||
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):
|
||||
try:
|
||||
value = self.GPIO_FUNCTIONS[value]
|
||||
except KeyError:
|
||||
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
|
||||
self._MEM[self._func_offset] = (
|
||||
self._MEM[self._func_offset]
|
||||
self.factory.mem[self._func_offset] = (
|
||||
self.factory.mem[self._func_offset]
|
||||
& ~(7 << self._func_shift)
|
||||
| (value << self._func_shift)
|
||||
)
|
||||
|
||||
def _get_state(self):
|
||||
return bool(self._MEM[self._level_offset] & (1 << self._level_shift))
|
||||
return bool(self.factory.mem[self._level_offset] & (1 << self._level_shift))
|
||||
|
||||
def _set_state(self, value):
|
||||
if self.function == 'input':
|
||||
raise PinSetInput('cannot set state of pin %r' % self)
|
||||
if value:
|
||||
self._MEM[self._set_offset] = 1 << self._set_shift
|
||||
self.factory.mem[self._set_offset] = 1 << self._set_shift
|
||||
else:
|
||||
self._MEM[self._clear_offset] = 1 << self._clear_shift
|
||||
self.factory.mem[self._clear_offset] = 1 << self._clear_shift
|
||||
|
||||
def _get_pull(self):
|
||||
return self.GPIO_PULL_UP_NAMES[self._pull]
|
||||
@@ -292,23 +270,23 @@ class NativePin(LocalPin):
|
||||
def _set_pull(self, value):
|
||||
if self.function != 'input':
|
||||
raise PinFixedPull('cannot set pull on non-input pin %r' % self)
|
||||
if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self.number):
|
||||
if value != 'up' and self.factory.pi_info.pulled_up('GPIO%d' % self.number):
|
||||
raise PinFixedPull('%r has a physical pull-up resistor' % self)
|
||||
try:
|
||||
value = self.GPIO_PULL_UPS[value]
|
||||
except KeyError:
|
||||
raise PinInvalidPull('invalid pull direction "%s" for pin %r' % (value, self))
|
||||
self._MEM[self._MEM.GPPUD_OFFSET] = value
|
||||
self.factory.mem[self.factory.mem.GPPUD_OFFSET] = value
|
||||
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)
|
||||
self._MEM[self._MEM.GPPUD_OFFSET] = 0
|
||||
self._MEM[self._pull_offset] = 0
|
||||
self.factory.mem[self.factory.mem.GPPUD_OFFSET] = 0
|
||||
self.factory.mem[self._pull_offset] = 0
|
||||
self._pull = value
|
||||
|
||||
def _get_edges(self):
|
||||
rising = bool(self._MEM[self._rising_offset] & (1 << self._rising_shift))
|
||||
falling = bool(self._MEM[self._falling_offset] & (1 << self._falling_shift))
|
||||
rising = bool(self.factory.mem[self._rising_offset] & (1 << self._rising_shift))
|
||||
falling = bool(self.factory.mem[self._falling_offset] & (1 << self._falling_shift))
|
||||
return self.GPIO_EDGES_NAMES[(rising, falling)]
|
||||
|
||||
def _set_edges(self, value):
|
||||
@@ -319,13 +297,13 @@ class NativePin(LocalPin):
|
||||
f = self.when_changed
|
||||
self.when_changed = None
|
||||
try:
|
||||
self._MEM[self._rising_offset] = (
|
||||
self._MEM[self._rising_offset]
|
||||
self.factory.mem[self._rising_offset] = (
|
||||
self.factory.mem[self._rising_offset]
|
||||
& ~(1 << self._rising_shift)
|
||||
| (rising << self._rising_shift)
|
||||
)
|
||||
self._MEM[self._falling_offset] = (
|
||||
self._MEM[self._falling_offset]
|
||||
self.factory.mem[self._falling_offset] = (
|
||||
self.factory.mem[self._falling_offset]
|
||||
& ~(1 << self._falling_shift)
|
||||
| (falling << self._falling_shift)
|
||||
)
|
||||
@@ -353,9 +331,9 @@ class NativePin(LocalPin):
|
||||
def _change_watch(self):
|
||||
offset = self._edge_offset
|
||||
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):
|
||||
if self._MEM[offset] & mask:
|
||||
self._MEM[offset] = mask
|
||||
if self.factory.mem[offset] & mask:
|
||||
self.factory.mem[offset] = mask
|
||||
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('')
|
||||
|
||||
import warnings
|
||||
import weakref
|
||||
import pigpio
|
||||
import os
|
||||
|
||||
from . import Pin
|
||||
from . import SPI
|
||||
from .pi import PiPin, PiFactory
|
||||
from .data import pi_info
|
||||
from ..devices import Device
|
||||
from ..mixins import SharedMixin
|
||||
from ..exc import (
|
||||
PinInvalidFunction,
|
||||
PinSetInput,
|
||||
@@ -19,12 +22,12 @@ from ..exc import (
|
||||
PinInvalidPull,
|
||||
PinInvalidBounce,
|
||||
PinInvalidState,
|
||||
PinNonPhysical,
|
||||
PinNoPins,
|
||||
SPIBadArgs,
|
||||
SPIInvalidClockMode,
|
||||
)
|
||||
|
||||
|
||||
class PiGPIOPin(Pin):
|
||||
class PiGPIOFactory(PiFactory):
|
||||
"""
|
||||
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
|
||||
@@ -68,10 +71,65 @@ class PiGPIOPin(Pin):
|
||||
|
||||
.. _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)
|
||||
_PINS = {}
|
||||
|
||||
GPIO_FUNCTIONS = {
|
||||
'input': pigpio.INPUT,
|
||||
'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_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()}
|
||||
|
||||
def __new__(
|
||||
cls, number, host=os.getenv('PIGPIO_ADDR', 'localhost'),
|
||||
port=int(os.getenv('PIGPIO_PORT', 8888))):
|
||||
def __init__(self, factory, number):
|
||||
super(PiGPIOPin, self).__init__(factory, number)
|
||||
self._pull = 'up' if factory.pi_info.pulled_up('GPIO%d' % number) else 'floating'
|
||||
self._pwm = False
|
||||
self._bounce = None
|
||||
self._when_changed = None
|
||||
self._callback = None
|
||||
self._edges = pigpio.EITHER_EDGE
|
||||
try:
|
||||
return cls._PINS[(host, port, number)]
|
||||
except KeyError:
|
||||
self = super(PiGPIOPin, cls).__new__(cls)
|
||||
cls.pi_info(host, port) # implicitly creates connection
|
||||
self._connection, self._pi_info = cls._CONNECTIONS[(host, port)]
|
||||
try:
|
||||
self._pi_info.physical_pin('GPIO%d' % number)
|
||||
except PinNoPins:
|
||||
warnings.warn(
|
||||
PinNonPhysical(
|
||||
'no physical pins exist for GPIO%d' % number))
|
||||
self._host = host
|
||||
self._port = port
|
||||
self._number = number
|
||||
self._pull = 'up' if self._pi_info.pulled_up('GPIO%d' % number) else 'floating'
|
||||
self._pwm = False
|
||||
self._bounce = None
|
||||
self._when_changed = None
|
||||
self._callback = None
|
||||
self._edges = pigpio.EITHER_EDGE
|
||||
try:
|
||||
self._connection.set_mode(self._number, pigpio.INPUT)
|
||||
except pigpio.error as e:
|
||||
raise ValueError(e)
|
||||
self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[self._pull])
|
||||
self._connection.set_glitch_filter(self._number, 0)
|
||||
cls._PINS[(host, port, number)] = self
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
if self._host == 'localhost':
|
||||
return "GPIO%d" % self._number
|
||||
else:
|
||||
return "GPIO%d on %s:%d" % (self._number, self._host, self._port)
|
||||
|
||||
@property
|
||||
def host(self):
|
||||
return self._host
|
||||
|
||||
@property
|
||||
def port(self):
|
||||
return self._port
|
||||
|
||||
@property
|
||||
def number(self):
|
||||
return self._number
|
||||
self.factory.connection.set_mode(self.number, pigpio.INPUT)
|
||||
except pigpio.error as e:
|
||||
raise ValueError(e)
|
||||
self.factory.connection.set_pull_up_down(self.number, self.GPIO_PULL_UPS[self._pull])
|
||||
self.factory.connection.set_glitch_filter(self.number, 0)
|
||||
|
||||
def close(self):
|
||||
# If we're shutting down, the connection may have disconnected itself
|
||||
# already. Unfortunately, the connection's "connected" property is
|
||||
# rather buggy - disconnecting doesn't set it to False! So we're
|
||||
# naughty and check an internal variable instead...
|
||||
if self._connection.sl.s is not None:
|
||||
if self.factory.connection:
|
||||
self.frequency = None
|
||||
self.when_changed = None
|
||||
self.function = 'input'
|
||||
self.pull = 'up' if self._pi_info.pulled_up('GPIO%d' % self.number) else 'floating'
|
||||
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):
|
||||
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):
|
||||
if value != 'input':
|
||||
self._pull = 'floating'
|
||||
try:
|
||||
self._connection.set_mode(self._number, self.GPIO_FUNCTIONS[value])
|
||||
self.factory.connection.set_mode(self.number, self.GPIO_FUNCTIONS[value])
|
||||
except KeyError:
|
||||
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
|
||||
|
||||
def _get_state(self):
|
||||
if self._pwm:
|
||||
return (
|
||||
self._connection.get_PWM_dutycycle(self._number) /
|
||||
self._connection.get_PWM_range(self._number)
|
||||
self.factory.connection.get_PWM_dutycycle(self.number) /
|
||||
self.factory.connection.get_PWM_range(self.number)
|
||||
)
|
||||
else:
|
||||
return bool(self._connection.read(self._number))
|
||||
return bool(self.factory.connection.read(self.number))
|
||||
|
||||
def _set_state(self, value):
|
||||
if self._pwm:
|
||||
try:
|
||||
value = int(value * self._connection.get_PWM_range(self._number))
|
||||
if value != self._connection.get_PWM_dutycycle(self._number):
|
||||
self._connection.set_PWM_dutycycle(self._number, value)
|
||||
value = int(value * self.factory.connection.get_PWM_range(self.number))
|
||||
if value != self.factory.connection.get_PWM_dutycycle(self.number):
|
||||
self.factory.connection.set_PWM_dutycycle(self.number, value)
|
||||
except pigpio.error:
|
||||
raise PinInvalidState('invalid state "%s" for pin %r' % (value, self))
|
||||
elif self.function == 'input':
|
||||
raise PinSetInput('cannot set state of pin %r' % self)
|
||||
else:
|
||||
# write forces pin to OUTPUT, hence the check above
|
||||
self._connection.write(self._number, bool(value))
|
||||
self.factory.connection.write(self.number, bool(value))
|
||||
|
||||
def _get_pull(self):
|
||||
return self._pull
|
||||
@@ -201,31 +222,31 @@ class PiGPIOPin(Pin):
|
||||
def _set_pull(self, value):
|
||||
if self.function != 'input':
|
||||
raise PinFixedPull('cannot set pull on non-input pin %r' % self)
|
||||
if value != 'up' and self._pi_info.pulled_up('GPIO%d' % self._number):
|
||||
if value != 'up' and self.factory.pi_info.pulled_up('GPIO%d' % self.number):
|
||||
raise PinFixedPull('%r has a physical pull-up resistor' % self)
|
||||
try:
|
||||
self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[value])
|
||||
self.factory.connection.set_pull_up_down(self.number, self.GPIO_PULL_UPS[value])
|
||||
self._pull = value
|
||||
except KeyError:
|
||||
raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self))
|
||||
|
||||
def _get_frequency(self):
|
||||
if self._pwm:
|
||||
return self._connection.get_PWM_frequency(self._number)
|
||||
return self.factory.connection.get_PWM_frequency(self.number)
|
||||
return None
|
||||
|
||||
def _set_frequency(self, value):
|
||||
if not self._pwm and value is not None:
|
||||
self._connection.set_PWM_frequency(self._number, value)
|
||||
self._connection.set_PWM_range(self._number, 10000)
|
||||
self._connection.set_PWM_dutycycle(self._number, 0)
|
||||
self.factory.connection.set_PWM_frequency(self.number, value)
|
||||
self.factory.connection.set_PWM_range(self.number, 10000)
|
||||
self.factory.connection.set_PWM_dutycycle(self.number, 0)
|
||||
self._pwm = True
|
||||
elif self._pwm and value is not None:
|
||||
if value != self._connection.get_PWM_frequency(self._number):
|
||||
self._connection.set_PWM_frequency(self._number, value)
|
||||
self._connection.set_PWM_range(self._number, 10000)
|
||||
if value != self.factory.connection.get_PWM_frequency(self.number):
|
||||
self.factory.connection.set_PWM_frequency(self.number, value)
|
||||
self.factory.connection.set_PWM_range(self.number, 10000)
|
||||
elif self._pwm and value is None:
|
||||
self._connection.write(self._number, 0)
|
||||
self.factory.connection.write(self.number, 0)
|
||||
self._pwm = False
|
||||
|
||||
def _get_bounce(self):
|
||||
@@ -236,7 +257,7 @@ class PiGPIOPin(Pin):
|
||||
value = 0
|
||||
elif value < 0:
|
||||
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):
|
||||
return self.GPIO_EDGES_NAMES[self._edges]
|
||||
@@ -259,20 +280,224 @@ class PiGPIOPin(Pin):
|
||||
self._callback.cancel()
|
||||
self._callback = None
|
||||
if value is not None:
|
||||
self._callback = self._connection.callback(
|
||||
self._number, self._edges,
|
||||
self._callback = self.factory.connection.callback(
|
||||
self.number, self._edges,
|
||||
lambda gpio, level, tick: value())
|
||||
|
||||
@classmethod
|
||||
def pi_info(
|
||||
cls, host=os.getenv('PIGPIO_ADDR', 'localhost'),
|
||||
port=int(os.getenv('PIGPIO_PORT', 8888))):
|
||||
try:
|
||||
connection, info = cls._CONNECTIONS[(host, port)]
|
||||
except KeyError:
|
||||
connection = pigpio.pi(host, port)
|
||||
revision = '%04x' % connection.get_hardware_revision()
|
||||
info = pi_info(revision)
|
||||
cls._CONNECTIONS[(host, port)] = (connection, info)
|
||||
return info
|
||||
|
||||
class PiGPIOHardwareSPI(SPI, Device):
|
||||
def __init__(self, factory, port, device):
|
||||
self._port = port
|
||||
self._device = device
|
||||
self._factory = weakref.proxy(factory)
|
||||
super(PiGPIOHardwareSPI, self).__init__()
|
||||
self._reserve_pins(*(
|
||||
factory.address + ('GPIO%d' % pin,)
|
||||
for pin in (11, 10, 9, (8, 7)[device])
|
||||
))
|
||||
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('')
|
||||
|
||||
import warnings
|
||||
import weakref
|
||||
from RPi import GPIO
|
||||
|
||||
from . import LocalPin
|
||||
from .data import pi_info
|
||||
from .local import LocalPiFactory, LocalPiPin
|
||||
from ..exc import (
|
||||
PinInvalidFunction,
|
||||
PinSetInput,
|
||||
@@ -19,12 +19,10 @@ from ..exc import (
|
||||
PinInvalidState,
|
||||
PinInvalidBounce,
|
||||
PinPWMFixedValue,
|
||||
PinNonPhysical,
|
||||
PinNoPins,
|
||||
)
|
||||
|
||||
|
||||
class RPiGPIOPin(LocalPin):
|
||||
class RPiGPIOFactory(LocalPiFactory):
|
||||
"""
|
||||
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.
|
||||
@@ -39,7 +37,7 @@ class RPiGPIOPin(LocalPin):
|
||||
|
||||
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
|
||||
|
||||
led = LED(RPiGPIOPin(12))
|
||||
@@ -47,8 +45,18 @@ class RPiGPIOPin(LocalPin):
|
||||
.. _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 = {
|
||||
'input': GPIO.IN,
|
||||
'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_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()}
|
||||
|
||||
PI_INFO = None
|
||||
|
||||
def __new__(cls, number):
|
||||
if not cls._PINS:
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setwarnings(False)
|
||||
if cls.PI_INFO is None:
|
||||
cls.PI_INFO = pi_info()
|
||||
try:
|
||||
return cls._PINS[number]
|
||||
except KeyError:
|
||||
self = super(RPiGPIOPin, cls).__new__(cls)
|
||||
try:
|
||||
cls.PI_INFO.physical_pin('GPIO%d' % number)
|
||||
except PinNoPins:
|
||||
warnings.warn(
|
||||
PinNonPhysical(
|
||||
'no physical pins exist for GPIO%d' % number))
|
||||
self._number = number
|
||||
self._pull = 'up' if cls.PI_INFO.pulled_up('GPIO%d' % number) else 'floating'
|
||||
self._pwm = None
|
||||
self._frequency = None
|
||||
self._duty_cycle = None
|
||||
self._bounce = -666
|
||||
self._when_changed = None
|
||||
self._edges = GPIO.BOTH
|
||||
GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[self._pull])
|
||||
cls._PINS[number] = self
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return "GPIO%d" % self._number
|
||||
|
||||
@property
|
||||
def number(self):
|
||||
return self._number
|
||||
def __init__(self, factory, number):
|
||||
super(RPiGPIOPin, self).__init__(factory, number)
|
||||
self._pull = 'up' if factory.pi_info.pulled_up('GPIO%d' % number) else 'floating'
|
||||
self._pwm = None
|
||||
self._frequency = None
|
||||
self._duty_cycle = None
|
||||
self._bounce = -666
|
||||
self._when_changed = None
|
||||
self._edges = GPIO.BOTH
|
||||
GPIO.setup(self.number, GPIO.IN, self.GPIO_PULL_UPS[self._pull])
|
||||
|
||||
def close(self):
|
||||
self.frequency = None
|
||||
self.when_changed = None
|
||||
GPIO.cleanup(self._number)
|
||||
GPIO.cleanup(self.number)
|
||||
|
||||
def output_with_state(self, state):
|
||||
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):
|
||||
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)
|
||||
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
|
||||
except KeyError:
|
||||
raise PinInvalidPull('invalid pull "%s" for pin %r' % (pull, self))
|
||||
|
||||
def _get_function(self):
|
||||
return self.GPIO_FUNCTION_NAMES[GPIO.gpio_function(self._number)]
|
||||
return self.GPIO_FUNCTION_NAMES[GPIO.gpio_function(self.number)]
|
||||
|
||||
def _set_function(self, value):
|
||||
if value != 'input':
|
||||
self._pull = 'floating'
|
||||
if value in ('input', 'output') and value in self.GPIO_FUNCTIONS:
|
||||
GPIO.setup(self._number, self.GPIO_FUNCTIONS[value], self.GPIO_PULL_UPS[self._pull])
|
||||
GPIO.setup(self.number, self.GPIO_FUNCTIONS[value], self.GPIO_PULL_UPS[self._pull])
|
||||
else:
|
||||
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
|
||||
|
||||
@@ -145,7 +127,7 @@ class RPiGPIOPin(LocalPin):
|
||||
if self._pwm:
|
||||
return self._duty_cycle
|
||||
else:
|
||||
return GPIO.input(self._number)
|
||||
return GPIO.input(self.number)
|
||||
|
||||
def _set_state(self, value):
|
||||
if self._pwm:
|
||||
@@ -156,7 +138,7 @@ class RPiGPIOPin(LocalPin):
|
||||
self._duty_cycle = value
|
||||
else:
|
||||
try:
|
||||
GPIO.output(self._number, value)
|
||||
GPIO.output(self.number, value)
|
||||
except ValueError:
|
||||
raise PinInvalidState('invalid state "%s" for pin %r' % (value, self))
|
||||
except RuntimeError:
|
||||
@@ -168,10 +150,10 @@ class RPiGPIOPin(LocalPin):
|
||||
def _set_pull(self, value):
|
||||
if self.function != 'input':
|
||||
raise PinFixedPull('cannot set pull on non-input pin %r' % self)
|
||||
if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self._number):
|
||||
if value != 'up' and self.factory.pi_info.pulled_up('GPIO%d' % self.number):
|
||||
raise PinFixedPull('%r has a physical pull-up resistor' % self)
|
||||
try:
|
||||
GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[value])
|
||||
GPIO.setup(self.number, GPIO.IN, self.GPIO_PULL_UPS[value])
|
||||
self._pull = value
|
||||
except KeyError:
|
||||
raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self))
|
||||
@@ -182,7 +164,7 @@ class RPiGPIOPin(LocalPin):
|
||||
def _set_frequency(self, value):
|
||||
if self._frequency is None and value is not None:
|
||||
try:
|
||||
self._pwm = GPIO.PWM(self._number, value)
|
||||
self._pwm = GPIO.PWM(self.number, value)
|
||||
except RuntimeError:
|
||||
raise PinPWMFixedValue('cannot start PWM on pin %r' % self)
|
||||
self._pwm.start(0)
|
||||
@@ -228,11 +210,11 @@ class RPiGPIOPin(LocalPin):
|
||||
if self._when_changed is None and value is not None:
|
||||
self._when_changed = value
|
||||
GPIO.add_event_detect(
|
||||
self._number, self._edges,
|
||||
self.number, self._edges,
|
||||
callback=lambda channel: self._when_changed(),
|
||||
bouncetime=self._bounce)
|
||||
elif self._when_changed is not None and value is None:
|
||||
GPIO.remove_event_detect(self._number)
|
||||
GPIO.remove_event_detect(self.number)
|
||||
self._when_changed = None
|
||||
else:
|
||||
self._when_changed = value
|
||||
|
||||
@@ -8,11 +8,12 @@ str = type('')
|
||||
|
||||
|
||||
import warnings
|
||||
import weakref
|
||||
import RPIO
|
||||
import RPIO.PWM
|
||||
from RPIO.Exceptions import InvalidChannelException
|
||||
|
||||
from . import LocalPin, PINS_CLEANUP
|
||||
from .local import LocalPiPin, LocalPiFactory
|
||||
from .data import pi_info
|
||||
from ..exc import (
|
||||
PinInvalidFunction,
|
||||
@@ -22,12 +23,10 @@ from ..exc import (
|
||||
PinInvalidBounce,
|
||||
PinInvalidState,
|
||||
PinPWMError,
|
||||
PinNonPhysical,
|
||||
PinNoPins,
|
||||
)
|
||||
|
||||
|
||||
class RPIOPin(LocalPin):
|
||||
class RPIOFactory(LocalPiFactory):
|
||||
"""
|
||||
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,
|
||||
@@ -48,9 +47,22 @@ class RPIOPin(LocalPin):
|
||||
|
||||
.. _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 = {
|
||||
'input': RPIO.IN,
|
||||
'output': RPIO.OUT,
|
||||
@@ -66,64 +78,32 @@ class RPIOPin(LocalPin):
|
||||
GPIO_FUNCTION_NAMES = {v: k for (k, v) in GPIO_FUNCTIONS.items()}
|
||||
GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()}
|
||||
|
||||
PI_INFO = None
|
||||
|
||||
def __new__(cls, number):
|
||||
if not cls._PINS:
|
||||
RPIO.setmode(RPIO.BCM)
|
||||
RPIO.setwarnings(False)
|
||||
RPIO.wait_for_interrupts(threaded=True)
|
||||
RPIO.PWM.setup()
|
||||
RPIO.PWM.init_channel(0, 10000)
|
||||
PINS_CLEANUP.append(RPIO.PWM.cleanup)
|
||||
PINS_CLEANUP.append(RPIO.stop_waiting_for_interrupts)
|
||||
PINS_CLEANUP.append(RPIO.cleanup)
|
||||
if cls.PI_INFO is None:
|
||||
cls.PI_INFO = pi_info()
|
||||
def __init__(self, factory, number):
|
||||
super(RPIOPin, self).__init__(factory, number)
|
||||
self._pull = 'up' if factory.pi_info.pulled_up('GPIO%d' % number) else 'floating'
|
||||
self._pwm = False
|
||||
self._duty_cycle = None
|
||||
self._bounce = None
|
||||
self._when_changed = None
|
||||
self._edges = 'both'
|
||||
try:
|
||||
return cls._PINS[number]
|
||||
except KeyError:
|
||||
self = super(RPIOPin, cls).__new__(cls)
|
||||
try:
|
||||
cls.PI_INFO.physical_pin('GPIO%d' % number)
|
||||
except PinNoPins:
|
||||
warnings.warn(
|
||||
PinNonPhysical(
|
||||
'no physical pins exist for GPIO%d' % number))
|
||||
self._number = number
|
||||
self._pull = 'up' if cls.PI_INFO.pulled_up('GPIO%d' % number) else 'floating'
|
||||
self._pwm = False
|
||||
self._duty_cycle = None
|
||||
self._bounce = None
|
||||
self._when_changed = None
|
||||
self._edges = 'both'
|
||||
try:
|
||||
RPIO.setup(self._number, RPIO.IN, self.GPIO_PULL_UPS[self._pull])
|
||||
except InvalidChannelException as e:
|
||||
raise ValueError(e)
|
||||
cls._PINS[number] = self
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return "GPIO%d" % self._number
|
||||
|
||||
@property
|
||||
def number(self):
|
||||
return self._number
|
||||
RPIO.setup(self.number, RPIO.IN, self.GPIO_PULL_UPS[self._pull])
|
||||
except InvalidChannelException as e:
|
||||
raise ValueError(e)
|
||||
|
||||
def close(self):
|
||||
self.frequency = 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):
|
||||
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):
|
||||
if value != 'input':
|
||||
self._pull = 'floating'
|
||||
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:
|
||||
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
|
||||
|
||||
@@ -131,23 +111,23 @@ class RPIOPin(LocalPin):
|
||||
if self._pwm:
|
||||
return self._duty_cycle
|
||||
else:
|
||||
return RPIO.input(self._number)
|
||||
return RPIO.input(self.number)
|
||||
|
||||
def _set_state(self, value):
|
||||
if not 0 <= value <= 1:
|
||||
raise PinInvalidState('invalid state "%s" for pin %r' % (value, self))
|
||||
if self._pwm:
|
||||
RPIO.PWM.clear_channel_gpio(0, self._number)
|
||||
RPIO.PWM.clear_channel_gpio(0, self.number)
|
||||
if value == 0:
|
||||
RPIO.output(self._number, False)
|
||||
RPIO.output(self.number, False)
|
||||
elif value == 1:
|
||||
RPIO.output(self._number, True)
|
||||
RPIO.output(self.number, True)
|
||||
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
|
||||
else:
|
||||
try:
|
||||
RPIO.output(self._number, value)
|
||||
RPIO.output(self.number, value)
|
||||
except ValueError:
|
||||
raise PinInvalidState('invalid state "%s" for pin %r' % (value, self))
|
||||
except RuntimeError:
|
||||
@@ -159,10 +139,10 @@ class RPIOPin(LocalPin):
|
||||
def _set_pull(self, value):
|
||||
if self.function != 'input':
|
||||
raise PinFixedPull('cannot set pull on non-input pin %r' % self)
|
||||
if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self._number):
|
||||
if value != 'up' and self.factory.pi_info.pulled_up('GPIO%d' % self.number):
|
||||
raise PinFixedPull('%r has a physical pull-up resistor' % self)
|
||||
try:
|
||||
RPIO.setup(self._number, RPIO.IN, self.GPIO_PULL_UPS[value])
|
||||
RPIO.setup(self.number, RPIO.IN, self.GPIO_PULL_UPS[value])
|
||||
self._pull = value
|
||||
except KeyError:
|
||||
raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self))
|
||||
@@ -182,10 +162,10 @@ class RPIOPin(LocalPin):
|
||||
self._pwm = True
|
||||
# Dirty hack to get RPIO's PWM support to setup, but do nothing,
|
||||
# for a given GPIO pin
|
||||
RPIO.PWM.add_channel_pulse(0, self._number, start=0, width=0)
|
||||
RPIO.PWM.clear_channel_gpio(0, self._number)
|
||||
RPIO.PWM.add_channel_pulse(0, self.number, start=0, width=0)
|
||||
RPIO.PWM.clear_channel_gpio(0, self.number)
|
||||
elif self._pwm and value is None:
|
||||
RPIO.PWM.clear_channel_gpio(0, self._number)
|
||||
RPIO.PWM.clear_channel_gpio(0, self.number)
|
||||
self._pwm = False
|
||||
|
||||
def _get_bounce(self):
|
||||
@@ -219,12 +199,12 @@ class RPIOPin(LocalPin):
|
||||
if self._when_changed is None and value is not None:
|
||||
self._when_changed = value
|
||||
RPIO.add_interrupt_callback(
|
||||
self._number,
|
||||
self.number,
|
||||
lambda channel, value: self._when_changed(),
|
||||
self._edges, self.GPIO_PULL_UPS[self._pull], self._bounce)
|
||||
elif self._when_changed is not None and value is None:
|
||||
try:
|
||||
RPIO.del_interrupt_callback(self._number)
|
||||
RPIO.del_interrupt_callback(self.number)
|
||||
except KeyError:
|
||||
# Ignore this exception which occurs during shutdown; this
|
||||
# simply means RPIO's built-in cleanup has already run and
|
||||
|
||||
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 .devices import Device
|
||||
from .spi import SPI
|
||||
|
||||
|
||||
class SPIDevice(Device):
|
||||
@@ -28,13 +27,12 @@ class SPIDevice(Device):
|
||||
specified with the constructor.
|
||||
"""
|
||||
def __init__(self, **spi_args):
|
||||
self._spi = SPI(**spi_args)
|
||||
self._spi = self._pin_factory.spi(**spi_args)
|
||||
|
||||
def close(self):
|
||||
if self._spi:
|
||||
s = self._spi
|
||||
self._spi.close()
|
||||
self._spi = None
|
||||
s.close()
|
||||
super(SPIDevice, self).close()
|
||||
|
||||
@property
|
||||
|
||||
12
setup.py
12
setup.py
@@ -68,12 +68,12 @@ if sys.version_info[:2] == (3, 2):
|
||||
|
||||
__entry_points__ = {
|
||||
'gpiozero_pin_factories': [
|
||||
'PiGPIOPin = gpiozero.pins.pigpiod:PiGPIOPin',
|
||||
'RPiGPIOPin = gpiozero.pins.rpigpio:RPiGPIOPin',
|
||||
'RPIOPin = gpiozero.pins.rpio:RPIOPin',
|
||||
'NativePin = gpiozero.pins.native:NativePin',
|
||||
'MockPin = gpiozero.pins.mock:MockPin',
|
||||
'MockPWMPin = gpiozero.pins.mock:MockPWMPin',
|
||||
'pigpio = gpiozero.pins.pigpiod:PiGPIOFactory',
|
||||
'rpigpio = gpiozero.pins.rpigpio:RPiGPIOFactory',
|
||||
'rpio = gpiozero.pins.rpio:RPIOFactory',
|
||||
'native = gpiozero.pins.native:NativeFactory',
|
||||
'mock = gpiozero.pins.mock:MockFactory',
|
||||
'mockpwm = gpiozero.pins.mock:MockPWMFactory',
|
||||
],
|
||||
'console_scripts': [
|
||||
'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
|
||||
from time import sleep
|
||||
|
||||
from gpiozero.pins.mock import MockPin, MockPWMPin
|
||||
from gpiozero import *
|
||||
from gpiozero.pins.mock import MockPWMPin, MockPin
|
||||
|
||||
|
||||
def setup_function(function):
|
||||
import gpiozero.devices
|
||||
# dirty, but it does the job
|
||||
if function.__name__ in ('test_robot', 'test_ryanteck_robot', 'test_camjam_kit_robot', 'test_led_borg', 'test_snow_pi_initial_value_pwm'):
|
||||
gpiozero.devices.pin_factory = MockPWMPin
|
||||
else:
|
||||
gpiozero.devices.pin_factory = MockPin
|
||||
Device._pin_factory.pin_class = MockPWMPin if function.__name__ in (
|
||||
'test_robot',
|
||||
'test_ryanteck_robot',
|
||||
'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):
|
||||
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():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device:
|
||||
device.on()
|
||||
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))
|
||||
|
||||
def test_composite_output_toggle():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device:
|
||||
device.toggle()
|
||||
assert all((pin1.state, pin2.state, pin3.state))
|
||||
@@ -51,9 +64,9 @@ def test_composite_output_toggle():
|
||||
assert not pin3.state
|
||||
|
||||
def test_composite_output_value():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with CompositeOutputDevice(OutputDevice(pin1), OutputDevice(pin2), foo=OutputDevice(pin3)) as device:
|
||||
assert device.value == (0, 0, 0)
|
||||
device.toggle()
|
||||
@@ -64,9 +77,9 @@ def test_composite_output_value():
|
||||
assert device[2].is_active
|
||||
|
||||
def test_led_board_on_off():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBoard(pin1, pin2, foo=pin3) as board:
|
||||
assert isinstance(board[0], LED)
|
||||
assert isinstance(board[1], LED)
|
||||
@@ -121,9 +134,9 @@ def test_led_board_on_off():
|
||||
assert pin3.state
|
||||
|
||||
def test_led_board_active_low():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBoard(pin1, pin2, foo=pin3, active_high=False) as board:
|
||||
assert not board.active_high
|
||||
assert not board[0].active_high
|
||||
@@ -145,9 +158,9 @@ def test_led_board_active_low():
|
||||
assert not pin3.state
|
||||
|
||||
def test_led_board_value():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBoard(pin1, pin2, foo=pin3) as board:
|
||||
assert board.value == (0, 0, 0)
|
||||
board.value = (0, 1, 0)
|
||||
@@ -156,9 +169,9 @@ def test_led_board_value():
|
||||
assert board.value == (1, 0, 1)
|
||||
|
||||
def test_led_board_pwm_value():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(3)
|
||||
pin3 = MockPWMPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board:
|
||||
assert board.value == (0, 0, 0)
|
||||
board.value = (0, 1, 0)
|
||||
@@ -167,9 +180,9 @@ def test_led_board_pwm_value():
|
||||
assert board.value == (0.5, 0, 0.75)
|
||||
|
||||
def test_led_board_pwm_bad_value():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(3)
|
||||
pin3 = MockPWMPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board:
|
||||
with pytest.raises(ValueError):
|
||||
board.value = (-1, 0, 0)
|
||||
@@ -177,18 +190,18 @@ def test_led_board_pwm_bad_value():
|
||||
board.value = (0, 2, 0)
|
||||
|
||||
def test_led_board_initial_value():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBoard(pin1, pin2, foo=pin3, initial_value=0) as board:
|
||||
assert board.value == (0, 0, 0)
|
||||
with LEDBoard(pin1, pin2, foo=pin3, initial_value=1) as board:
|
||||
assert board.value == (1, 1, 1)
|
||||
|
||||
def test_led_board_pwm_initial_value():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(3)
|
||||
pin3 = MockPWMPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=0) as board:
|
||||
assert board.value == (0, 0, 0)
|
||||
with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=1) as board:
|
||||
@@ -197,18 +210,18 @@ def test_led_board_pwm_initial_value():
|
||||
assert board.value == (0.5, 0.5, 0.5)
|
||||
|
||||
def test_led_board_pwm_bad_initial_value():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(3)
|
||||
pin3 = MockPWMPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with pytest.raises(ValueError):
|
||||
LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=-1)
|
||||
with pytest.raises(ValueError):
|
||||
LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=2)
|
||||
|
||||
def test_led_board_nested():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
|
||||
assert list(led.pin for led in board.leds) == [pin1, pin2, pin3]
|
||||
assert board.value == (0, (0, 0))
|
||||
@@ -218,9 +231,9 @@ def test_led_board_nested():
|
||||
assert pin3.state
|
||||
|
||||
def test_led_board_bad_blink():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
|
||||
with pytest.raises(ValueError):
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_led_board_blink_background():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
|
||||
board.blink(0.1, 0.1, n=2)
|
||||
board._blink_thread.join() # naughty, but ensures no arbitrary waits in the test
|
||||
@@ -252,9 +265,9 @@ def test_led_board_blink_background():
|
||||
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_led_board_blink_foreground():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
|
||||
board.blink(0.1, 0.1, n=2, background=False)
|
||||
test = [
|
||||
@@ -271,9 +284,9 @@ def test_led_board_blink_foreground():
|
||||
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_led_board_blink_control():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
|
||||
board.blink(0.1, 0.1, n=2)
|
||||
# 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'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_led_board_blink_take_over():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
|
||||
board[1].blink(0.1, 0.1, n=2)
|
||||
board.blink(0.1, 0.1, n=2) # immediately take over blinking
|
||||
@@ -318,9 +331,9 @@ def test_led_board_blink_take_over():
|
||||
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_led_board_blink_control_all():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
|
||||
board.blink(0.1, 0.1, n=2)
|
||||
# make sure the blink thread's started
|
||||
@@ -340,9 +353,9 @@ def test_led_board_blink_control_all():
|
||||
pin3.assert_states_and_times(test)
|
||||
|
||||
def test_led_board_blink_interrupt_on():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
|
||||
board.blink(1, 0.1)
|
||||
sleep(0.2)
|
||||
@@ -352,9 +365,9 @@ def test_led_board_blink_interrupt_on():
|
||||
pin3.assert_states([False, True, False])
|
||||
|
||||
def test_led_board_blink_interrupt_off():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
|
||||
board.blink(0.1, 1)
|
||||
sleep(0.2)
|
||||
@@ -366,9 +379,9 @@ def test_led_board_blink_interrupt_off():
|
||||
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_led_board_fade_background():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(3)
|
||||
pin3 = MockPWMPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBoard(pin1, LEDBoard(pin2, pin3, pwm=True), pwm=True) as board:
|
||||
board.blink(0, 0, 0.2, 0.2, n=2)
|
||||
board._blink_thread.join()
|
||||
@@ -400,9 +413,9 @@ def test_led_board_fade_background():
|
||||
pin3.assert_states_and_times(test)
|
||||
|
||||
def test_led_bar_graph_value():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBarGraph(pin1, pin2, pin3) as graph:
|
||||
assert isinstance(graph[0], LED)
|
||||
assert isinstance(graph[1], LED)
|
||||
@@ -433,9 +446,9 @@ def test_led_bar_graph_value():
|
||||
assert graph.value == -2/3
|
||||
|
||||
def test_led_bar_graph_active_low():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBarGraph(pin1, pin2, pin3, active_high=False) as graph:
|
||||
assert not graph.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
|
||||
|
||||
def test_led_bar_graph_pwm_value():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(3)
|
||||
pin3 = MockPWMPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBarGraph(pin1, pin2, pin3, pwm=True) as graph:
|
||||
assert isinstance(graph[0], PWMLED)
|
||||
assert isinstance(graph[1], PWMLED)
|
||||
@@ -482,9 +495,9 @@ def test_led_bar_graph_pwm_value():
|
||||
assert graph.value == -1/2
|
||||
|
||||
def test_led_bar_graph_bad_value():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBarGraph(pin1, pin2, pin3) as graph:
|
||||
with pytest.raises(ValueError):
|
||||
graph.value = -2
|
||||
@@ -492,9 +505,9 @@ def test_led_bar_graph_bad_value():
|
||||
graph.value = 2
|
||||
|
||||
def test_led_bar_graph_bad_init():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with pytest.raises(TypeError):
|
||||
LEDBarGraph(pin1, pin2, foo=pin3)
|
||||
with pytest.raises(ValueError):
|
||||
@@ -503,9 +516,9 @@ def test_led_bar_graph_bad_init():
|
||||
LEDBarGraph(pin1, pin2, pin3, initial_value=2)
|
||||
|
||||
def test_led_bar_graph_initial_value():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
pin3 = MockPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBarGraph(pin1, pin2, pin3, initial_value=1/3) as graph:
|
||||
assert graph.value == 1/3
|
||||
assert pin1.state and not (pin2.state or pin3.state)
|
||||
@@ -514,9 +527,9 @@ def test_led_bar_graph_initial_value():
|
||||
assert pin3.state and not (pin1.state or pin2.state)
|
||||
|
||||
def test_led_bar_graph_pwm_initial_value():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(3)
|
||||
pin3 = MockPWMPin(4)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(3)
|
||||
pin3 = Device._pin_factory.pin(4)
|
||||
with LEDBarGraph(pin1, pin2, pin3, pwm=True, initial_value=0.5) as graph:
|
||||
assert graph.value == 0.5
|
||||
assert (pin1.state, pin2.state, pin3.state) == (1, 0.5, 0)
|
||||
@@ -525,17 +538,17 @@ def test_led_bar_graph_pwm_initial_value():
|
||||
assert (pin1.state, pin2.state, pin3.state) == (0, 0.5, 1)
|
||||
|
||||
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:
|
||||
assert [device.pin for device in board._leds] == pins
|
||||
|
||||
def test_pi_liter():
|
||||
pins = [MockPin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)]
|
||||
pins = [Device._pin_factory.pin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)]
|
||||
with PiLiter() as board:
|
||||
assert [device.pin for device in board] == pins
|
||||
|
||||
def test_pi_liter_graph():
|
||||
pins = [MockPin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)]
|
||||
pins = [Device._pin_factory.pin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)]
|
||||
with PiLiterBarGraph() as board:
|
||||
board.value = 0.5
|
||||
assert [pin.state for pin in pins] == [1, 1, 1, 1, 0, 0, 0, 0]
|
||||
@@ -543,9 +556,9 @@ def test_pi_liter_graph():
|
||||
assert board.value == 5/8
|
||||
|
||||
def test_traffic_lights():
|
||||
red_pin = MockPin(2)
|
||||
amber_pin = MockPin(3)
|
||||
green_pin = MockPin(4)
|
||||
red_pin = Device._pin_factory.pin(2)
|
||||
amber_pin = Device._pin_factory.pin(3)
|
||||
green_pin = Device._pin_factory.pin(4)
|
||||
with TrafficLights(red_pin, amber_pin, green_pin) as board:
|
||||
board.red.on()
|
||||
assert board.red.value
|
||||
@@ -574,15 +587,15 @@ def test_traffic_lights():
|
||||
def test_traffic_lights_bad_init():
|
||||
with pytest.raises(ValueError):
|
||||
TrafficLights()
|
||||
red_pin = MockPin(2)
|
||||
amber_pin = MockPin(3)
|
||||
green_pin = MockPin(4)
|
||||
yellow_pin = MockPin(5)
|
||||
red_pin = Device._pin_factory.pin(2)
|
||||
amber_pin = Device._pin_factory.pin(3)
|
||||
green_pin = Device._pin_factory.pin(4)
|
||||
yellow_pin = Device._pin_factory.pin(5)
|
||||
with pytest.raises(ValueError):
|
||||
TrafficLights(red=red_pin, amber=amber_pin, yellow=yellow_pin, green=green_pin)
|
||||
|
||||
def test_pi_traffic():
|
||||
pins = [MockPin(n) for n in (9, 10, 11)]
|
||||
pins = [Device._pin_factory.pin(n) for n in (9, 10, 11)]
|
||||
with PiTraffic() as board:
|
||||
assert [device.pin for device in board] == pins
|
||||
|
||||
@@ -591,27 +604,27 @@ def test_pi_stop():
|
||||
PiStop()
|
||||
with pytest.raises(ValueError):
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
assert [device.pin for device in board] == pins_d
|
||||
|
||||
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:
|
||||
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)
|
||||
|
||||
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:
|
||||
assert [device.pin for device in board.leds] == pins
|
||||
assert all(device.pin.state == 0.5 for device in board.leds)
|
||||
|
||||
def test_traffic_lights_buzzer():
|
||||
red_pin = MockPin(2)
|
||||
amber_pin = MockPin(3)
|
||||
green_pin = MockPin(4)
|
||||
buzzer_pin = MockPin(5)
|
||||
button_pin = MockPin(6)
|
||||
red_pin = Device._pin_factory.pin(2)
|
||||
amber_pin = Device._pin_factory.pin(3)
|
||||
green_pin = Device._pin_factory.pin(4)
|
||||
buzzer_pin = Device._pin_factory.pin(5)
|
||||
button_pin = Device._pin_factory.pin(6)
|
||||
with TrafficLightsBuzzer(
|
||||
TrafficLights(red_pin, amber_pin, green_pin),
|
||||
Buzzer(buzzer_pin),
|
||||
@@ -651,17 +664,17 @@ def test_traffic_lights_buzzer():
|
||||
assert board.button.is_active
|
||||
|
||||
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:
|
||||
assert [led.pin for led in board.lights] + [board.buzzer.pin, board.button.pin] == pins
|
||||
|
||||
def test_traffic_hat():
|
||||
pins = [MockPin(n) for n in (24, 23, 22, 5, 25)]
|
||||
pins = [Device._pin_factory.pin(n) for n in (24, 23, 22, 5, 25)]
|
||||
with TrafficHat() as board:
|
||||
assert [led.pin for led in board.lights] + [board.buzzer.pin, board.button.pin] == pins
|
||||
|
||||
def test_robot():
|
||||
pins = [MockPWMPin(n) for n in (2, 3, 4, 5)]
|
||||
pins = [Device._pin_factory.pin(n) for n in (2, 3, 4, 5)]
|
||||
with Robot((2, 3), (4, 5)) as robot:
|
||||
assert (
|
||||
[device.pin for device in robot.left_motor] +
|
||||
@@ -696,12 +709,12 @@ def test_robot():
|
||||
assert robot.value == (0, -0.5)
|
||||
|
||||
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:
|
||||
assert [device.pin for motor in board for device in motor] == pins
|
||||
|
||||
def test_camjam_kit_robot():
|
||||
pins = [MockPWMPin(n) for n in (9, 10, 7, 8)]
|
||||
pins = [Device._pin_factory.pin(n) for n in (9, 10, 7, 8)]
|
||||
with CamJamKitRobot() as board:
|
||||
assert [device.pin for motor in board for device in motor] == pins
|
||||
|
||||
@@ -714,7 +727,7 @@ def test_energenie_bad_init():
|
||||
Energenie(5)
|
||||
|
||||
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, \
|
||||
Energenie(2, initial_value=False) as device2:
|
||||
assert repr(device1) == '<gpiozero.Energenie object on socket 1>'
|
||||
|
||||
@@ -6,86 +6,87 @@ from __future__ import (
|
||||
)
|
||||
str = type('')
|
||||
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from gpiozero.pins.mock import MockPin
|
||||
from gpiozero import *
|
||||
|
||||
|
||||
def teardown_function(function):
|
||||
MockPin.clear_pins()
|
||||
Device._pin_factory.reset()
|
||||
|
||||
|
||||
# TODO add more devices tests!
|
||||
|
||||
def test_device_no_pin():
|
||||
def test_device_bad_pin():
|
||||
with pytest.raises(GPIOPinMissing):
|
||||
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():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with GPIODevice(pin) as device:
|
||||
assert not device.closed
|
||||
assert device.pin == pin
|
||||
|
||||
def test_device_init_twice_same_pin():
|
||||
pin = MockPin(2)
|
||||
with GPIODevice(pin) as device:
|
||||
with GPIODevice(2) as device:
|
||||
with pytest.raises(GPIOPinInUse):
|
||||
device2 = GPIODevice(pin)
|
||||
GPIODevice(2)
|
||||
|
||||
def test_device_init_twice_different_pin():
|
||||
pin = MockPin(2)
|
||||
pin2 = MockPin(3)
|
||||
with GPIODevice(pin) as device:
|
||||
with GPIODevice(pin2) as device2:
|
||||
with GPIODevice(2) as device:
|
||||
with GPIODevice(3) as device2:
|
||||
pass
|
||||
|
||||
def test_device_close():
|
||||
pin = MockPin(2)
|
||||
device = GPIODevice(pin)
|
||||
device = GPIODevice(2)
|
||||
device.close()
|
||||
assert device.closed
|
||||
assert device.pin is None
|
||||
|
||||
def test_device_reopen_same_pin():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
device = GPIODevice(pin)
|
||||
device.close()
|
||||
device2 = GPIODevice(pin)
|
||||
assert not device2.closed
|
||||
assert device2.pin == pin
|
||||
assert device2.pin is pin
|
||||
assert device.closed
|
||||
assert device.pin is None
|
||||
device2.close()
|
||||
|
||||
def test_device_repr():
|
||||
pin = MockPin(2)
|
||||
with GPIODevice(pin) as device:
|
||||
assert repr(device) == '<gpiozero.GPIODevice object on pin %s, is_active=False>' % pin
|
||||
with GPIODevice(2) as device:
|
||||
assert repr(device) == '<gpiozero.GPIODevice object on pin %s, is_active=False>' % device.pin
|
||||
|
||||
def test_device_repr_after_close():
|
||||
pin = MockPin(2)
|
||||
device = GPIODevice(pin)
|
||||
device = GPIODevice(2)
|
||||
device.close()
|
||||
assert repr(device) == '<gpiozero.GPIODevice object closed>'
|
||||
|
||||
def test_device_unknown_attr():
|
||||
pin = MockPin(2)
|
||||
with GPIODevice(pin) as device:
|
||||
with GPIODevice(2) as device:
|
||||
with pytest.raises(AttributeError):
|
||||
device.foo = 1
|
||||
|
||||
def test_device_context_manager():
|
||||
pin = MockPin(2)
|
||||
with GPIODevice(pin) as device:
|
||||
with GPIODevice(2) as device:
|
||||
assert not device.closed
|
||||
assert device.closed
|
||||
|
||||
def test_composite_device_sequence():
|
||||
with CompositeDevice(
|
||||
InputDevice(MockPin(2)),
|
||||
InputDevice(MockPin(3))
|
||||
InputDevice(2),
|
||||
InputDevice(3)
|
||||
) as device:
|
||||
assert len(device) == 2
|
||||
assert device[0].pin.number == 2
|
||||
@@ -94,8 +95,8 @@ def test_composite_device_sequence():
|
||||
|
||||
def test_composite_device_values():
|
||||
with CompositeDevice(
|
||||
InputDevice(MockPin(2)),
|
||||
InputDevice(MockPin(3))
|
||||
InputDevice(2),
|
||||
InputDevice(3)
|
||||
) as device:
|
||||
assert device.value == (0, 0)
|
||||
assert not device.is_active
|
||||
@@ -105,8 +106,8 @@ def test_composite_device_values():
|
||||
|
||||
def test_composite_device_named():
|
||||
with CompositeDevice(
|
||||
foo=InputDevice(MockPin(2)),
|
||||
bar=InputDevice(MockPin(3)),
|
||||
foo=InputDevice(2),
|
||||
bar=InputDevice(3),
|
||||
_order=('foo', 'bar')
|
||||
) as device:
|
||||
assert device.namedtuple._fields == ('foo', 'bar')
|
||||
@@ -121,13 +122,13 @@ def test_composite_device_bad_init():
|
||||
with pytest.raises(ValueError):
|
||||
CompositeDevice(2)
|
||||
with pytest.raises(ValueError):
|
||||
CompositeDevice(MockPin(2))
|
||||
CompositeDevice(Device._pin_factory.pin(2))
|
||||
|
||||
def test_composite_device_read_only():
|
||||
device = CompositeDevice(
|
||||
foo=InputDevice(MockPin(2)),
|
||||
bar=InputDevice(MockPin(3))
|
||||
)
|
||||
with pytest.raises(AttributeError):
|
||||
device.foo = 1
|
||||
with CompositeDevice(
|
||||
foo=InputDevice(2),
|
||||
bar=InputDevice(3)
|
||||
) as device:
|
||||
with pytest.raises(AttributeError):
|
||||
device.foo = 1
|
||||
|
||||
|
||||
@@ -12,57 +12,52 @@ import pytest
|
||||
from threading import Event
|
||||
from functools import partial
|
||||
|
||||
from gpiozero.pins.mock import (
|
||||
MockPin,
|
||||
MockPulledUpPin,
|
||||
MockChargingPin,
|
||||
MockTriggerPin,
|
||||
)
|
||||
from gpiozero.pins.mock import MockPulledUpPin, MockChargingPin, MockTriggerPin
|
||||
from gpiozero import *
|
||||
|
||||
|
||||
def teardown_function(function):
|
||||
MockPin.clear_pins()
|
||||
Device._pin_factory.reset()
|
||||
|
||||
|
||||
def test_input_initial_values():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with InputDevice(pin, pull_up=True) as device:
|
||||
assert pin.function == 'input'
|
||||
assert pin.pull == 'up'
|
||||
assert device.pull_up
|
||||
device.close()
|
||||
device = InputDevice(pin, pull_up=False)
|
||||
with InputDevice(pin, pull_up=False) as device:
|
||||
assert pin.pull == 'down'
|
||||
assert not device.pull_up
|
||||
|
||||
def test_input_is_active_low():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with InputDevice(pin, pull_up=True) as device:
|
||||
pin.drive_high()
|
||||
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()
|
||||
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():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with InputDevice(pin, pull_up=False) as device:
|
||||
pin.drive_high()
|
||||
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()
|
||||
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():
|
||||
pin = MockPulledUpPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockPulledUpPin)
|
||||
with pytest.raises(PinFixedPull):
|
||||
InputDevice(pin, pull_up=False)
|
||||
|
||||
def test_input_event_activated():
|
||||
event = Event()
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with DigitalInputDevice(pin) as device:
|
||||
device.when_activated = lambda: event.set()
|
||||
assert not event.is_set()
|
||||
@@ -71,7 +66,7 @@ def test_input_event_activated():
|
||||
|
||||
def test_input_event_deactivated():
|
||||
event = Event()
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with DigitalInputDevice(pin) as device:
|
||||
device.when_deactivated = lambda: event.set()
|
||||
assert not event.is_set()
|
||||
@@ -82,7 +77,7 @@ def test_input_event_deactivated():
|
||||
|
||||
def test_input_partial_callback():
|
||||
event = Event()
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
def foo(a, b):
|
||||
event.set()
|
||||
return a + b
|
||||
@@ -95,22 +90,22 @@ def test_input_partial_callback():
|
||||
assert event.is_set()
|
||||
|
||||
def test_input_wait_active():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with DigitalInputDevice(pin) as device:
|
||||
pin.drive_high()
|
||||
assert device.wait_for_active(1)
|
||||
assert not device.wait_for_inactive(0)
|
||||
|
||||
def test_input_wait_inactive():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with DigitalInputDevice(pin) as device:
|
||||
assert device.wait_for_inactive(1)
|
||||
assert not device.wait_for_active(0)
|
||||
|
||||
def test_input_smoothed_attrib():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
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.queue_len == 5
|
||||
assert not device.partial
|
||||
@@ -120,7 +115,7 @@ def test_input_smoothed_attrib():
|
||||
device.threshold = 1
|
||||
|
||||
def test_input_smoothed_values():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with SmoothedInputDevice(pin) as device:
|
||||
device._queue.start()
|
||||
assert not device.is_active
|
||||
@@ -130,7 +125,7 @@ def test_input_smoothed_values():
|
||||
assert device.wait_for_inactive(1)
|
||||
|
||||
def test_input_button():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with Button(pin) as button:
|
||||
assert pin.pull == 'up'
|
||||
assert not button.is_pressed
|
||||
@@ -142,7 +137,7 @@ def test_input_button():
|
||||
assert button.wait_for_release(1)
|
||||
|
||||
def test_input_line_sensor():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with LineSensor(pin) as sensor:
|
||||
pin.drive_low() # logic is inverted for line sensor
|
||||
assert sensor.wait_for_line(1)
|
||||
@@ -152,7 +147,7 @@ def test_input_line_sensor():
|
||||
assert not sensor.line_detected
|
||||
|
||||
def test_input_motion_sensor():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with MotionSensor(pin) as sensor:
|
||||
pin.drive_high()
|
||||
assert sensor.wait_for_motion(1)
|
||||
@@ -164,7 +159,7 @@ def test_input_motion_sensor():
|
||||
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_input_light_sensor():
|
||||
pin = MockChargingPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockChargingPin)
|
||||
with LightSensor(pin) as sensor:
|
||||
pin.charge_time = 0.1
|
||||
assert sensor.wait_for_dark(1)
|
||||
@@ -174,8 +169,8 @@ def test_input_light_sensor():
|
||||
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_input_distance_sensor():
|
||||
echo_pin = MockPin(2)
|
||||
trig_pin = MockTriggerPin(3)
|
||||
echo_pin = Device._pin_factory.pin(2)
|
||||
trig_pin = Device._pin_factory.pin(3, pin_class=MockTriggerPin)
|
||||
trig_pin.echo_pin = echo_pin
|
||||
trig_pin.echo_time = 0.02
|
||||
with pytest.raises(ValueError):
|
||||
|
||||
@@ -11,25 +11,24 @@ from threading import Event
|
||||
|
||||
import pytest
|
||||
|
||||
from gpiozero.pins.mock import MockPin, MockPWMPin
|
||||
from gpiozero.pins.mock import MockPWMPin, MockPin
|
||||
from gpiozero import *
|
||||
|
||||
|
||||
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
|
||||
# enough to get reasonable coverage but it's by no means comprehensive...
|
||||
|
||||
def test_mock_pin_init():
|
||||
with pytest.raises(TypeError):
|
||||
MockPin()
|
||||
with pytest.raises(ValueError):
|
||||
MockPin(60)
|
||||
assert MockPin(2).number == 2
|
||||
Device._pin_factory.pin(60)
|
||||
assert Device._pin_factory.pin(2).number == 2
|
||||
|
||||
def test_mock_pin_defaults():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
assert pin.bounce == None
|
||||
assert pin.edges == 'both'
|
||||
assert pin.frequency == None
|
||||
@@ -39,30 +38,23 @@ def test_mock_pin_defaults():
|
||||
assert pin.when_changed == None
|
||||
|
||||
def test_mock_pin_open_close():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
pin.close()
|
||||
|
||||
def test_mock_pin_init_twice_same_pin():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(pin1.number)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(pin1.number)
|
||||
assert pin1 is pin2
|
||||
|
||||
def test_mock_pin_init_twice_different_pin():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPin(pin1.number+1)
|
||||
pin1 = Device._pin_factory.pin(2)
|
||||
pin2 = Device._pin_factory.pin(pin1.number+1)
|
||||
assert pin1 != pin2
|
||||
assert pin1.number == 2
|
||||
assert pin2.number == pin1.number+1
|
||||
|
||||
def test_mock_pwm_pin_init():
|
||||
with pytest.raises(TypeError):
|
||||
MockPWMPin()
|
||||
with pytest.raises(ValueError):
|
||||
MockPWMPin(60)
|
||||
assert MockPWMPin(2).number == 2
|
||||
|
||||
def test_mock_pwm_pin_defaults():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
assert pin.bounce == None
|
||||
assert pin.edges == 'both'
|
||||
assert pin.frequency == None
|
||||
@@ -72,38 +64,38 @@ def test_mock_pwm_pin_defaults():
|
||||
assert pin.when_changed == None
|
||||
|
||||
def test_mock_pwm_pin_open_close():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
pin.close()
|
||||
|
||||
def test_mock_pwm_pin_init_twice_same_pin():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(pin1.number)
|
||||
pin1 = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
pin2 = Device._pin_factory.pin(pin1.number, pin_class=MockPWMPin)
|
||||
assert pin1 is pin2
|
||||
|
||||
def test_mock_pwm_pin_init_twice_different_pin():
|
||||
pin1 = MockPWMPin(2)
|
||||
pin2 = MockPWMPin(pin1.number+1)
|
||||
pin1 = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
pin2 = Device._pin_factory.pin(pin1.number + 1, pin_class=MockPWMPin)
|
||||
assert pin1 != pin2
|
||||
assert pin1.number == 2
|
||||
assert pin2.number == pin1.number+1
|
||||
|
||||
def test_mock_pin_init_twice_different_modes():
|
||||
pin1 = MockPin(2)
|
||||
pin2 = MockPWMPin(pin1.number+1)
|
||||
pin1 = Device._pin_factory.pin(2, pin_class=MockPin)
|
||||
pin2 = Device._pin_factory.pin(pin1.number + 1, pin_class=MockPWMPin)
|
||||
assert pin1 != pin2
|
||||
with pytest.raises(ValueError):
|
||||
pin3 = MockPWMPin(pin1.number)
|
||||
Device._pin_factory.pin(pin1.number, pin_class=MockPWMPin)
|
||||
with pytest.raises(ValueError):
|
||||
pin4 = MockPin(pin2.number)
|
||||
Device._pin_factory.pin(pin2.number, pin_class=MockPin)
|
||||
|
||||
def test_mock_pin_frequency_unsupported():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
pin.frequency = None
|
||||
with pytest.raises(PinPWMUnsupported):
|
||||
pin.frequency = 100
|
||||
|
||||
def test_mock_pin_frequency_supported():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
pin.function = 'output'
|
||||
assert pin.frequency is None
|
||||
pin.frequency = 100
|
||||
@@ -112,7 +104,7 @@ def test_mock_pin_frequency_supported():
|
||||
assert not pin.state
|
||||
|
||||
def test_mock_pin_pull():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
pin.function = 'input'
|
||||
assert pin.pull == 'floating'
|
||||
pin.pull = 'up'
|
||||
@@ -121,7 +113,7 @@ def test_mock_pin_pull():
|
||||
assert not pin.state
|
||||
|
||||
def test_mock_pin_state():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with pytest.raises(PinSetInput):
|
||||
pin.state = 1
|
||||
pin.function = 'output'
|
||||
@@ -134,7 +126,7 @@ def test_mock_pin_state():
|
||||
assert pin.state == 1
|
||||
|
||||
def test_mock_pwm_pin_state():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with pytest.raises(PinSetInput):
|
||||
pin.state = 1
|
||||
pin.function = 'output'
|
||||
@@ -147,7 +139,7 @@ def test_mock_pwm_pin_state():
|
||||
assert pin.state == 0.5
|
||||
|
||||
def test_mock_pin_edges():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
assert pin.when_changed is None
|
||||
fired = Event()
|
||||
pin.function = 'input'
|
||||
|
||||
@@ -16,15 +16,16 @@ except ImportError:
|
||||
|
||||
import pytest
|
||||
|
||||
from gpiozero.pins.mock import MockPin, MockPWMPin
|
||||
from gpiozero.pins.mock import MockPWMPin
|
||||
from gpiozero import *
|
||||
|
||||
|
||||
def teardown_function(function):
|
||||
MockPin.clear_pins()
|
||||
Device._pin_factory.reset()
|
||||
|
||||
|
||||
def test_output_initial_values():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with OutputDevice(pin, initial_value=False) as device:
|
||||
assert pin.function == 'output'
|
||||
assert not pin.state
|
||||
@@ -35,7 +36,7 @@ def test_output_initial_values():
|
||||
assert state == pin.state
|
||||
|
||||
def test_output_write_active_high():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with OutputDevice(pin) as device:
|
||||
device.on()
|
||||
assert pin.state
|
||||
@@ -43,7 +44,7 @@ def test_output_write_active_high():
|
||||
assert not pin.state
|
||||
|
||||
def test_output_write_active_low():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with OutputDevice(pin, active_high=False) as device:
|
||||
device.on()
|
||||
assert not pin.state
|
||||
@@ -51,7 +52,7 @@ def test_output_write_active_low():
|
||||
assert pin.state
|
||||
|
||||
def test_output_write_closed():
|
||||
with OutputDevice(MockPin(2)) as device:
|
||||
with OutputDevice(Device._pin_factory.pin(2)) as device:
|
||||
device.close()
|
||||
assert device.closed
|
||||
device.close()
|
||||
@@ -60,14 +61,14 @@ def test_output_write_closed():
|
||||
device.on()
|
||||
|
||||
def test_output_write_silly():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with OutputDevice(pin) as device:
|
||||
pin.function = 'input'
|
||||
with pytest.raises(AttributeError):
|
||||
device.on()
|
||||
|
||||
def test_output_value():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with OutputDevice(pin) as device:
|
||||
assert not device.value
|
||||
assert not pin.state
|
||||
@@ -79,7 +80,7 @@ def test_output_value():
|
||||
assert not pin.state
|
||||
|
||||
def test_output_digital_toggle():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with DigitalOutputDevice(pin) as device:
|
||||
assert not device.value
|
||||
assert not pin.state
|
||||
@@ -93,7 +94,7 @@ def test_output_digital_toggle():
|
||||
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_output_blink_background():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with DigitalOutputDevice(pin) as device:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_output_blink_foreground():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with DigitalOutputDevice(pin) as device:
|
||||
start = time()
|
||||
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():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with DigitalOutputDevice(pin) as device:
|
||||
device.blink(1, 0.1)
|
||||
sleep(0.2)
|
||||
@@ -133,7 +134,7 @@ def test_output_blink_interrupt_on():
|
||||
pin.assert_states([False, True, False])
|
||||
|
||||
def test_output_blink_interrupt_off():
|
||||
pin = MockPin(2)
|
||||
pin = Device._pin_factory.pin(2)
|
||||
with DigitalOutputDevice(pin) as device:
|
||||
device.blink(0.1, 1)
|
||||
sleep(0.2)
|
||||
@@ -142,14 +143,14 @@ def test_output_blink_interrupt_off():
|
||||
|
||||
def test_output_pwm_bad_initial_value():
|
||||
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():
|
||||
with pytest.raises(AttributeError):
|
||||
PWMOutputDevice(MockPin(2))
|
||||
PWMOutputDevice(Device._pin_factory.pin(2))
|
||||
|
||||
def test_output_pwm_states():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
device.value = 0.1
|
||||
device.value = 0.2
|
||||
@@ -157,7 +158,7 @@ def test_output_pwm_states():
|
||||
pin.assert_states([0.0, 0.1, 0.2, 0.0])
|
||||
|
||||
def test_output_pwm_read():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin, frequency=100) as device:
|
||||
assert device.frequency == 100
|
||||
device.value = 0.1
|
||||
@@ -170,14 +171,14 @@ def test_output_pwm_read():
|
||||
assert device.frequency is None
|
||||
|
||||
def test_output_pwm_write():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
device.on()
|
||||
device.off()
|
||||
pin.assert_states([False, True, False])
|
||||
|
||||
def test_output_pwm_toggle():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
device.toggle()
|
||||
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])
|
||||
|
||||
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:
|
||||
device.value = 0.1
|
||||
assert isclose(device.value, 0.1)
|
||||
@@ -196,17 +197,18 @@ def test_output_pwm_active_high_read():
|
||||
assert device.value
|
||||
|
||||
def test_output_pwm_bad_value():
|
||||
with pytest.raises(ValueError):
|
||||
PWMOutputDevice(MockPWMPin(2)).value = 2
|
||||
with PWMOutputDevice(Device._pin_factory.pin(2, pin_class=MockPWMPin)) as device:
|
||||
with pytest.raises(ValueError):
|
||||
device.value = 2
|
||||
|
||||
def test_output_pwm_write_closed():
|
||||
device = PWMOutputDevice(MockPWMPin(2))
|
||||
device.close()
|
||||
with pytest.raises(GPIODeviceClosed):
|
||||
device.on()
|
||||
with PWMOutputDevice(Device._pin_factory.pin(2, pin_class=MockPWMPin)) as device:
|
||||
device.close()
|
||||
with pytest.raises(GPIODeviceClosed):
|
||||
device.on()
|
||||
|
||||
def test_output_pwm_write_silly():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
pin.function = 'input'
|
||||
with pytest.raises(AttributeError):
|
||||
@@ -215,7 +217,7 @@ def test_output_pwm_write_silly():
|
||||
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_output_pwm_blink_background():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_output_pwm_blink_foreground():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_output_pwm_fade_background():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_output_pwm_fade_foreground():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_output_pwm_pulse_background():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
def test_output_pwm_pulse_foreground():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
start = time()
|
||||
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():
|
||||
pin = MockPWMPin(2)
|
||||
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with PWMOutputDevice(pin) as device:
|
||||
device.blink(1, 0.1)
|
||||
sleep(0.2)
|
||||
@@ -391,7 +393,7 @@ def test_rgbled_missing_pins():
|
||||
RGBLED()
|
||||
|
||||
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:
|
||||
assert r.frequency
|
||||
assert g.frequency
|
||||
@@ -401,24 +403,24 @@ def test_rgbled_initial_value():
|
||||
assert isclose(b.state, 0.0)
|
||||
|
||||
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:
|
||||
assert r.state == 0
|
||||
assert g.state == 1
|
||||
assert b.state == 1
|
||||
|
||||
def test_rgbled_initial_bad_value():
|
||||
r, g, b = (MockPWMPin(i) for i in (1, 2, 3))
|
||||
r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3))
|
||||
with pytest.raises(ValueError):
|
||||
RGBLED(r, g, b, initial_value=(0.1, 0.2, 1.2))
|
||||
|
||||
def test_rgbled_initial_bad_value_nonpwm():
|
||||
r, g, b = (MockPin(i) for i in (1, 2, 3))
|
||||
r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3))
|
||||
with pytest.raises(ValueError):
|
||||
RGBLED(r, g, b, pwm=False, initial_value=(0.1, 0.2, 0))
|
||||
|
||||
def test_rgbled_value():
|
||||
r, g, b = (MockPWMPin(i) for i in (1, 2, 3))
|
||||
r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3))
|
||||
with RGBLED(r, g, b) as device:
|
||||
assert isinstance(device._leds[0], PWMLED)
|
||||
assert isinstance(device._leds[1], PWMLED)
|
||||
@@ -436,7 +438,7 @@ def test_rgbled_value():
|
||||
assert device.value == (0.5, 0.5, 0.5)
|
||||
|
||||
def test_rgbled_value_nonpwm():
|
||||
r, g, b = (MockPin(i) for i in (1, 2, 3))
|
||||
r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3))
|
||||
with RGBLED(r, g, b, pwm=False) as device:
|
||||
assert isinstance(device._leds[0], LED)
|
||||
assert isinstance(device._leds[1], LED)
|
||||
@@ -451,7 +453,7 @@ def test_rgbled_value_nonpwm():
|
||||
assert device.value == (0, 0, 0)
|
||||
|
||||
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 pytest.raises(ValueError):
|
||||
device.value = (2, 0, 0)
|
||||
@@ -460,7 +462,7 @@ def test_rgbled_bad_value():
|
||||
device.value = (0, -1, 0)
|
||||
|
||||
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 pytest.raises(ValueError):
|
||||
device.value = (2, 0, 0)
|
||||
@@ -478,7 +480,7 @@ def test_rgbled_bad_value_nonpwm():
|
||||
device.value = (0, 0, 0.5)
|
||||
|
||||
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:
|
||||
assert not device.is_active
|
||||
assert device.value == (0, 0, 0)
|
||||
@@ -490,7 +492,7 @@ def test_rgbled_toggle():
|
||||
assert device.value == (0, 0, 0)
|
||||
|
||||
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:
|
||||
assert not device.is_active
|
||||
assert device.value == (0, 0, 0)
|
||||
@@ -501,10 +503,18 @@ def test_rgbled_toggle_nonpwm():
|
||||
assert not device.is_active
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
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:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
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:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
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:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
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:
|
||||
start = time()
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
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:
|
||||
start = time()
|
||||
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)
|
||||
|
||||
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 pytest.raises(ValueError):
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
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:
|
||||
start = time()
|
||||
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)
|
||||
|
||||
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 pytest.raises(ValueError):
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
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:
|
||||
start = time()
|
||||
device.pulse(0.2, 0.2, n=2)
|
||||
@@ -703,7 +713,7 @@ def test_rgbled_pulse_background():
|
||||
b.assert_states_and_times(expected)
|
||||
|
||||
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 pytest.raises(ValueError):
|
||||
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'),
|
||||
reason='timing is too random on pypy')
|
||||
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:
|
||||
start = time()
|
||||
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)
|
||||
|
||||
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 pytest.raises(ValueError):
|
||||
device.pulse(0.2, 0.2, n=2, background=False)
|
||||
|
||||
def test_rgbled_blink_interrupt():
|
||||
r, g, b = (MockPWMPin(i) for i in (1, 2, 3))
|
||||
r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3))
|
||||
with RGBLED(r, g, b) as device:
|
||||
device.blink(1, 0.1)
|
||||
sleep(0.2)
|
||||
@@ -760,7 +770,7 @@ def test_rgbled_blink_interrupt():
|
||||
b.assert_states([0, 1, 0])
|
||||
|
||||
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:
|
||||
device.blink(1, 0.1)
|
||||
sleep(0.2)
|
||||
@@ -770,7 +780,7 @@ def test_rgbled_blink_interrupt_nonpwm():
|
||||
b.assert_states([0, 1, 0])
|
||||
|
||||
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:
|
||||
assert not device.closed
|
||||
device.close()
|
||||
@@ -779,7 +789,7 @@ def test_rgbled_close():
|
||||
assert device.closed
|
||||
|
||||
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:
|
||||
assert not device.closed
|
||||
device.close()
|
||||
@@ -792,8 +802,8 @@ def test_motor_missing_pins():
|
||||
Motor()
|
||||
|
||||
def test_motor_pins():
|
||||
f = MockPWMPin(1)
|
||||
b = MockPWMPin(2)
|
||||
f = Device._pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
b = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with Motor(f, b) as device:
|
||||
assert device.forward_device.pin is f
|
||||
assert isinstance(device.forward_device, PWMOutputDevice)
|
||||
@@ -801,8 +811,8 @@ def test_motor_pins():
|
||||
assert isinstance(device.backward_device, PWMOutputDevice)
|
||||
|
||||
def test_motor_pins_nonpwm():
|
||||
f = MockPin(1)
|
||||
b = MockPin(2)
|
||||
f = Device._pin_factory.pin(1)
|
||||
b = Device._pin_factory.pin(2)
|
||||
with Motor(f, b, pwm=False) as device:
|
||||
assert device.forward_device.pin is f
|
||||
assert isinstance(device.forward_device, DigitalOutputDevice)
|
||||
@@ -810,8 +820,8 @@ def test_motor_pins_nonpwm():
|
||||
assert isinstance(device.backward_device, DigitalOutputDevice)
|
||||
|
||||
def test_motor_close():
|
||||
f = MockPWMPin(1)
|
||||
b = MockPWMPin(2)
|
||||
f = Device._pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
b = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with Motor(f, b) as device:
|
||||
device.close()
|
||||
assert device.closed
|
||||
@@ -821,8 +831,8 @@ def test_motor_close():
|
||||
assert device.closed
|
||||
|
||||
def test_motor_close_nonpwm():
|
||||
f = MockPin(1)
|
||||
b = MockPin(2)
|
||||
f = Device._pin_factory.pin(1)
|
||||
b = Device._pin_factory.pin(2)
|
||||
with Motor(f, b, pwm=False) as device:
|
||||
device.close()
|
||||
assert device.closed
|
||||
@@ -830,8 +840,8 @@ def test_motor_close_nonpwm():
|
||||
assert device.backward_device.pin is None
|
||||
|
||||
def test_motor_value():
|
||||
f = MockPWMPin(1)
|
||||
b = MockPWMPin(2)
|
||||
f = Device._pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
b = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with Motor(f, b) as device:
|
||||
device.value = -1
|
||||
assert device.is_active
|
||||
@@ -855,8 +865,8 @@ def test_motor_value():
|
||||
assert b.state == 0 and f.state == 0
|
||||
|
||||
def test_motor_value_nonpwm():
|
||||
f = MockPin(1)
|
||||
b = MockPin(2)
|
||||
f = Device._pin_factory.pin(1)
|
||||
b = Device._pin_factory.pin(2)
|
||||
with Motor(f, b, pwm=False) as device:
|
||||
device.value = -1
|
||||
assert device.is_active
|
||||
@@ -872,17 +882,21 @@ def test_motor_value_nonpwm():
|
||||
assert b.state == 0 and f.state == 0
|
||||
|
||||
def test_motor_bad_value():
|
||||
f = MockPWMPin(1)
|
||||
b = MockPWMPin(2)
|
||||
f = Device._pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
b = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with Motor(f, b) as device:
|
||||
with pytest.raises(ValueError):
|
||||
device.value = -2
|
||||
with pytest.raises(ValueError):
|
||||
device.value = 2
|
||||
with pytest.raises(ValueError):
|
||||
device.forward(2)
|
||||
with pytest.raises(ValueError):
|
||||
device.backward(2)
|
||||
|
||||
def test_motor_bad_value_nonpwm():
|
||||
f = MockPin(1)
|
||||
b = MockPin(2)
|
||||
f = Device._pin_factory.pin(1)
|
||||
b = Device._pin_factory.pin(2)
|
||||
with Motor(f, b, pwm=False) as device:
|
||||
with pytest.raises(ValueError):
|
||||
device.value = -2
|
||||
@@ -894,8 +908,8 @@ def test_motor_bad_value_nonpwm():
|
||||
device.value = -0.5
|
||||
|
||||
def test_motor_reverse():
|
||||
f = MockPWMPin(1)
|
||||
b = MockPWMPin(2)
|
||||
f = Device._pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
b = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with Motor(f, b) as device:
|
||||
device.forward()
|
||||
assert device.value == 1
|
||||
@@ -911,8 +925,8 @@ def test_motor_reverse():
|
||||
assert b.state == 0 and f.state == 0.5
|
||||
|
||||
def test_motor_reverse_nonpwm():
|
||||
f = MockPin(1)
|
||||
b = MockPin(2)
|
||||
f = Device._pin_factory.pin(1)
|
||||
b = Device._pin_factory.pin(2)
|
||||
with Motor(f, b, pwm=False) as device:
|
||||
device.forward()
|
||||
assert device.value == 1
|
||||
@@ -922,13 +936,13 @@ def test_motor_reverse_nonpwm():
|
||||
assert b.state == 1 and f.state == 0
|
||||
|
||||
def test_servo_pins():
|
||||
p = MockPWMPin(1)
|
||||
p = Device._pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
with Servo(p) as device:
|
||||
assert device.pwm_device.pin is p
|
||||
assert isinstance(device.pwm_device, PWMOutputDevice)
|
||||
|
||||
def test_servo_bad_value():
|
||||
p = MockPWMPin(1)
|
||||
p = Device._pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
with pytest.raises(ValueError):
|
||||
Servo(p, initial_value=2)
|
||||
with pytest.raises(ValueError):
|
||||
@@ -937,12 +951,12 @@ def test_servo_bad_value():
|
||||
Servo(p, max_pulse_width=30/1000)
|
||||
|
||||
def test_servo_pins_nonpwm():
|
||||
p = MockPin(2)
|
||||
p = Device._pin_factory.pin(2)
|
||||
with pytest.raises(PinPWMUnsupported):
|
||||
Servo(p)
|
||||
|
||||
def test_servo_close():
|
||||
p = MockPWMPin(2)
|
||||
p = Device._pin_factory.pin(2, pin_class=MockPWMPin)
|
||||
with Servo(p) as device:
|
||||
device.close()
|
||||
assert device.closed
|
||||
@@ -951,7 +965,7 @@ def test_servo_close():
|
||||
assert device.closed
|
||||
|
||||
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:
|
||||
assert isclose(device.min_pulse_width, 5/10000)
|
||||
assert isclose(device.max_pulse_width, 25/10000)
|
||||
@@ -965,7 +979,7 @@ def test_servo_pulse_width():
|
||||
assert device.pulse_width is None
|
||||
|
||||
def test_servo_values():
|
||||
p = MockPWMPin(1)
|
||||
p = Device._pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
with Servo(p) as device:
|
||||
device.min()
|
||||
assert device.is_active
|
||||
@@ -992,13 +1006,13 @@ def test_servo_values():
|
||||
assert device.value is None
|
||||
|
||||
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:
|
||||
assert device.min_angle == 0
|
||||
assert device.max_angle == 90
|
||||
|
||||
def test_angular_servo_angles():
|
||||
p = MockPWMPin(1)
|
||||
p = Device._pin_factory.pin(1, pin_class=MockPWMPin)
|
||||
with AngularServo(p) as device:
|
||||
device.angle = 0
|
||||
assert device.angle == 0
|
||||
|
||||
@@ -11,44 +11,44 @@ import re
|
||||
import pytest
|
||||
from mock import patch, MagicMock
|
||||
|
||||
import gpiozero.devices
|
||||
import gpiozero.pins.data
|
||||
import gpiozero.pins.native
|
||||
from gpiozero.pins.data import pi_info, Style, HeaderInfo, PinInfo
|
||||
from gpiozero import PinMultiplePins, PinNoPins, PinUnknownPi
|
||||
import gpiozero.pins.local
|
||||
from gpiozero.pins.local import LocalPiFactory
|
||||
from gpiozero.pins.data import Style, HeaderInfo, PinInfo
|
||||
from gpiozero import *
|
||||
|
||||
|
||||
def test_pi_revision():
|
||||
save_factory = gpiozero.devices.pin_factory
|
||||
try:
|
||||
# We're not using _set_pin_factory here because we don't want to implicitly
|
||||
# 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
|
||||
# and read /proc/cpuinfo (MockPin simply parrots the 2B's data);
|
||||
# NativePin is used as we're guaranteed to be able to import it
|
||||
gpiozero.devices.pin_factory = gpiozero.pins.native.NativePin
|
||||
# LocalPiFactory is used as we can definitely instantiate it (strictly
|
||||
# speaking it's abstract but we're only interested in the pi_info
|
||||
# stuff)
|
||||
with patch('io.open') as m:
|
||||
m.return_value.__enter__.return_value = ['lots of irrelevant', 'lines', 'followed by', 'Revision: 0002', 'Serial: xxxxxxxxxxx']
|
||||
assert pi_info().revision == '0002'
|
||||
# LocalPin caches the revision (because realistically it isn't going to
|
||||
# change at runtime); we need to wipe it here though
|
||||
gpiozero.pins.native.NativePin._PI_REVISION = None
|
||||
# LocalPiFactory caches the revision (because realistically it
|
||||
# isn't going to change at runtime); we need to wipe it here though
|
||||
Device._pin_factory._info = None
|
||||
m.return_value.__enter__.return_value = ['Revision: a21042']
|
||||
assert pi_info().revision == 'a21042'
|
||||
# Check over-volting result (some argument over whether this is 7 or
|
||||
# 8 character result; make sure both work)
|
||||
gpiozero.pins.native.NativePin._PI_REVISION = None
|
||||
Device._pin_factory._info = None
|
||||
m.return_value.__enter__.return_value = ['Revision: 1000003']
|
||||
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']
|
||||
assert pi_info().revision == '0003'
|
||||
with pytest.raises(PinUnknownPi):
|
||||
m.return_value.__enter__.return_value = ['nothing', 'relevant', 'at all']
|
||||
gpiozero.pins.native.NativePin._PI_REVISION = None
|
||||
Device._pin_factory._info = None
|
||||
pi_info()
|
||||
with pytest.raises(PinUnknownPi):
|
||||
pi_info('0fff')
|
||||
finally:
|
||||
gpiozero.devices.pin_factory = save_factory
|
||||
|
||||
def test_pi_info():
|
||||
r = pi_info('900011')
|
||||
@@ -73,14 +73,14 @@ def test_pi_info_other_types():
|
||||
|
||||
def test_physical_pins():
|
||||
# Assert physical pins for some well-known Pi's; a21041 is a Pi2B
|
||||
assert pi_info('a21041').physical_pins('3V3') == {('P1', 1), ('P1', 17)}
|
||||
assert pi_info('a21041').physical_pins('GPIO2') == {('P1', 3)}
|
||||
assert pi_info('a21041').physical_pins('3V3') == {('J8', 1), ('J8', 17)}
|
||||
assert pi_info('a21041').physical_pins('GPIO2') == {('J8', 3)}
|
||||
assert pi_info('a21041').physical_pins('GPIO47') == set()
|
||||
|
||||
def test_physical_pin():
|
||||
with pytest.raises(PinMultiplePins):
|
||||
assert pi_info('a21041').physical_pin('GND')
|
||||
assert pi_info('a21041').physical_pin('GPIO3') == ('P1', 5)
|
||||
assert pi_info('a21041').physical_pin('GPIO3') == ('J8', 5)
|
||||
with pytest.raises(PinNoPins):
|
||||
assert pi_info('a21041').physical_pin('GPIO47')
|
||||
|
||||
@@ -114,6 +114,18 @@ def test_pprint_content():
|
||||
pi_info('0014').headers['SODIMM'].pprint(color=False)
|
||||
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():
|
||||
assert len(pi_info('0002').headers) == 1
|
||||
assert len(pi_info('000e').headers) == 2
|
||||
@@ -133,7 +145,8 @@ def test_pprint_headers():
|
||||
stdout.output = []
|
||||
pi_info('900092').pprint()
|
||||
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
|
||||
|
||||
def test_pprint_color():
|
||||
@@ -194,11 +207,12 @@ def test_pprint_missing_pin():
|
||||
assert ('(%d)' % i)
|
||||
|
||||
def test_pprint_rows_cols():
|
||||
assert '{0:row1}'.format(pi_info('900092').headers['P1']) == '1o'
|
||||
assert '{0:row2}'.format(pi_info('900092').headers['P1']) == 'oo'
|
||||
assert '{0:row1}'.format(pi_info('900092').headers['J8']) == '1o'
|
||||
assert '{0:row2}'.format(pi_info('900092').headers['J8']) == 'oo'
|
||||
assert '{0:col1}'.format(pi_info('0002').headers['P1']) == '1oooooooooooo'
|
||||
assert '{0:col2}'.format(pi_info('0002').headers['P1']) == 'ooooooooooooo'
|
||||
with pytest.raises(ValueError):
|
||||
'{0:row16}'.format(pi_info('0002').headers['P1'])
|
||||
with pytest.raises(ValueError):
|
||||
'{0:col3}'.format(pi_info('0002').headers['P1'])
|
||||
|
||||
|
||||
@@ -4,93 +4,113 @@ from __future__ import (
|
||||
print_function,
|
||||
division,
|
||||
)
|
||||
nstr = str
|
||||
str = type('')
|
||||
|
||||
|
||||
import sys
|
||||
import mock
|
||||
import pytest
|
||||
from array import array
|
||||
from mock import patch
|
||||
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.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):
|
||||
MockPin.clear_pins()
|
||||
Device._pin_factory.reset()
|
||||
|
||||
|
||||
def test_spi_hardware_params():
|
||||
with mock.patch('gpiozero.spi.SpiDev') as spidev:
|
||||
with SPI() as device:
|
||||
assert isinstance(device, SPIHardwareInterface)
|
||||
with SPI(port=0, device=0) as device:
|
||||
assert isinstance(device, SPIHardwareInterface)
|
||||
with SPI(port=0, device=1) as device:
|
||||
assert isinstance(device, SPIHardwareInterface)
|
||||
with SPI(clock_pin=11) as device:
|
||||
assert isinstance(device, SPIHardwareInterface)
|
||||
with SPI(clock_pin=11, mosi_pin=10, select_pin=8) as device:
|
||||
assert isinstance(device, SPIHardwareInterface)
|
||||
with SPI(clock_pin=11, mosi_pin=10, select_pin=7) as device:
|
||||
assert isinstance(device, SPIHardwareInterface)
|
||||
with SPI(shared=True) as device:
|
||||
assert isinstance(device, SharedSPIHardwareInterface)
|
||||
with pytest.raises(ValueError):
|
||||
SPI(port=1)
|
||||
with pytest.raises(ValueError):
|
||||
SPI(device=2)
|
||||
with pytest.raises(ValueError):
|
||||
SPI(port=0, clock_pin=12)
|
||||
with pytest.raises(ValueError):
|
||||
SPI(foo='bar')
|
||||
with patch('os.open'), patch('mmap.mmap') as mmap_mmap, patch('io.open') as io_open:
|
||||
mmap_mmap.return_value = array(nstr('B'), (0,) * 4096)
|
||||
io_open.return_value.__enter__.return_value = ['Revision: a21042']
|
||||
with patch('gpiozero.devices.Device._pin_factory', NativeFactory()), \
|
||||
patch('gpiozero.pins.local.SpiDev'):
|
||||
with Device._pin_factory.spi() as device:
|
||||
assert isinstance(device, LocalPiHardwareSPI)
|
||||
with Device._pin_factory.spi(port=0, device=0) as device:
|
||||
assert isinstance(device, LocalPiHardwareSPI)
|
||||
with Device._pin_factory.spi(port=0, device=1) as device:
|
||||
assert isinstance(device, LocalPiHardwareSPI)
|
||||
with Device._pin_factory.spi(clock_pin=11) as device:
|
||||
assert isinstance(device, LocalPiHardwareSPI)
|
||||
with Device._pin_factory.spi(clock_pin=11, mosi_pin=10, select_pin=8) as device:
|
||||
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):
|
||||
Device._pin_factory.spi(port=1)
|
||||
with pytest.raises(ValueError):
|
||||
Device._pin_factory.spi(device=2)
|
||||
with pytest.raises(ValueError):
|
||||
Device._pin_factory.spi(port=0, clock_pin=12)
|
||||
with pytest.raises(ValueError):
|
||||
Device._pin_factory.spi(foo='bar')
|
||||
|
||||
def test_spi_software_params():
|
||||
with mock.patch('gpiozero.spi.SpiDev') as spidev:
|
||||
with SPI(select_pin=6) as device:
|
||||
assert isinstance(device, SPISoftwareInterface)
|
||||
with SPI(clock_pin=11, mosi_pin=9, miso_pin=10) as device:
|
||||
assert isinstance(device, SPISoftwareInterface)
|
||||
with SPI(select_pin=6, shared=True) as device:
|
||||
assert isinstance(device, SharedSPISoftwareInterface)
|
||||
# Ensure software fallback works when SpiDev isn't present
|
||||
with SPI() as device:
|
||||
assert isinstance(device, SPISoftwareInterface)
|
||||
with patch('os.open'), patch('mmap.mmap') as mmap_mmap, patch('io.open') as io_open:
|
||||
mmap_mmap.return_value = array(nstr('B'), (0,) * 4096)
|
||||
io_open.return_value.__enter__.return_value = ['Revision: a21042']
|
||||
with patch('gpiozero.devices.Device._pin_factory', NativeFactory()), \
|
||||
patch('gpiozero.pins.local.SpiDev'):
|
||||
with Device._pin_factory.spi(select_pin=6) as device:
|
||||
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
|
||||
with Device._pin_factory.spi() as device:
|
||||
assert isinstance(device, LocalPiSoftwareSPI)
|
||||
|
||||
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 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():
|
||||
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)]
|
||||
with SPI() as device:
|
||||
with Device._pin_factory.spi() as device:
|
||||
assert device.read(3) == [0, 1, 2]
|
||||
assert device.read(6) == list(range(6))
|
||||
|
||||
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)]
|
||||
with SPI() as device:
|
||||
with Device._pin_factory.spi() as device:
|
||||
assert device.write([0, 1, 2]) == 3
|
||||
assert spidev.return_value.xfer2.called_with([0, 1, 2])
|
||||
assert device.write(list(range(6))) == 6
|
||||
assert spidev.return_value.xfer2.called_with(list(range(6)))
|
||||
|
||||
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.lsbfirst = False
|
||||
spidev.return_value.cshigh = True
|
||||
spidev.return_value.bits_per_word = 8
|
||||
with SPI() as device:
|
||||
with Device._pin_factory.spi() as device:
|
||||
assert device.clock_mode == 0
|
||||
assert not device.clock_polarity
|
||||
assert not device.clock_phase
|
||||
@@ -116,7 +136,7 @@ def test_spi_software_read():
|
||||
super(SPISlave, self).on_start()
|
||||
for i in range(10):
|
||||
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(6) == [0, 1, 2, 3, 4, 5]
|
||||
slave.clock_phase = True
|
||||
@@ -125,7 +145,7 @@ def test_spi_software_read():
|
||||
assert master.read(6) == [0, 1, 2, 3, 4, 5]
|
||||
|
||||
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])
|
||||
assert test_device.rx_word() == 0
|
||||
master.write([2, 0])
|
||||
@@ -134,7 +154,7 @@ def test_spi_software_write():
|
||||
assert test_device.rx_word() == 257
|
||||
|
||||
def test_spi_software_clock_mode():
|
||||
with SPI() as master:
|
||||
with Device._pin_factory.spi() as master:
|
||||
assert master.clock_mode == 0
|
||||
assert not master.clock_polarity
|
||||
assert not master.clock_phase
|
||||
@@ -151,7 +171,7 @@ def test_spi_software_clock_mode():
|
||||
master.clock_mode = 5
|
||||
|
||||
def test_spi_software_attr():
|
||||
with SPI() as master:
|
||||
with Device._pin_factory.spi() as master:
|
||||
assert not master.lsb_first
|
||||
assert not master.select_high
|
||||
assert master.bits_per_word == 8
|
||||
|
||||
@@ -15,16 +15,12 @@ try:
|
||||
except ImportError:
|
||||
from gpiozero.compat import isclose
|
||||
|
||||
from gpiozero import *
|
||||
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):
|
||||
MockPin.clear_pins()
|
||||
Device._pin_factory.reset()
|
||||
|
||||
def clamp(v, min_value, max_value):
|
||||
return min(max_value, max(min_value, v))
|
||||
|
||||
Reference in New Issue
Block a user