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:
		| @@ -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 | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user