mirror of
				https://github.com/KevinMidboe/python-gpiozero.git
				synced 2025-10-29 17:50:37 +00:00 
			
		
		
		
	Fix #459 - properly support remote SPI with pigpio
Sorry! Dave's messing around with the pin implementations again. Hopefully the last time. The pin_factory is now really a factory object which can be asked to produce individual pins or pin-based interfaces like SPI (which can be supported properly via pigpio).
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user