1419 lines
51 KiB
Python
1419 lines
51 KiB
Python
# vim: set fileencoding=utf-8:
|
|
#
|
|
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
|
#
|
|
# Copyright (c) 2015-2023 Dave Jones <dave@waveform.org.uk>
|
|
# Copyright (c) 2018 Rick Ansell <rick@nbinvincible.org.uk>
|
|
# Copyright (c) 2018 Mike Kazantsev <mk.fraggod@gmail.com>
|
|
# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
|
|
#
|
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
import warnings
|
|
from weakref import ref
|
|
from threading import Lock
|
|
from textwrap import dedent
|
|
from itertools import cycle
|
|
from operator import attrgetter
|
|
from collections import defaultdict, namedtuple
|
|
|
|
from .style import Style
|
|
from ..devices import Device
|
|
from ..exc import (
|
|
PinInvalidPin,
|
|
PinInvalidFunction,
|
|
PinSetInput,
|
|
PinFixedPull,
|
|
PinUnsupported,
|
|
PinSPIUnsupported,
|
|
PinPWMUnsupported,
|
|
PinEdgeDetectUnsupported,
|
|
PinMultiplePins,
|
|
PinNoPins,
|
|
SPIFixedClockMode,
|
|
SPIFixedBitOrder,
|
|
SPIFixedSelect,
|
|
SPIFixedWordSize,
|
|
SPIFixedRate,
|
|
GPIOPinInUse,
|
|
)
|
|
|
|
|
|
class Factory:
|
|
"""
|
|
Generates pins and SPI interfaces for devices. This is an abstract
|
|
base class for pin factories. Descendents *must* override the following
|
|
methods:
|
|
|
|
* :meth:`ticks`
|
|
* :meth:`ticks_diff`
|
|
* :meth:`_get_board_info`
|
|
|
|
Descendents *may* override the following methods, if applicable:
|
|
|
|
* :meth:`close`
|
|
* :meth:`reserve_pins`
|
|
* :meth:`release_pins`
|
|
* :meth:`release_all`
|
|
* :meth:`pin`
|
|
* :meth:`spi`
|
|
"""
|
|
def __init__(self):
|
|
self._reservations = defaultdict(list)
|
|
self._res_lock = Lock()
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *exc_info):
|
|
self.close()
|
|
|
|
def reserve_pins(self, requester, *names):
|
|
"""
|
|
Called to indicate that the device reserves the right to use the
|
|
specified pin *names*. This should be done during device construction.
|
|
If pins are reserved, you must ensure that the reservation is released
|
|
by eventually called :meth:`release_pins`.
|
|
"""
|
|
with self._res_lock:
|
|
pins = (
|
|
info
|
|
for name in names
|
|
for header, info in self.board_info.find_pin(name)
|
|
)
|
|
for pin in pins:
|
|
for reserver_ref in self._reservations[pin]:
|
|
reserver = reserver_ref()
|
|
if reserver is not None and requester._conflicts_with(reserver):
|
|
raise GPIOPinInUse(
|
|
f'pin {pin.name} is already in use by {reserver!r}')
|
|
self._reservations[pin].append(ref(requester))
|
|
|
|
def release_pins(self, reserver, *names):
|
|
"""
|
|
Releases the reservation of *reserver* against pin *names*. This is
|
|
typically called during :meth:`~gpiozero.Device.close` to clean up
|
|
reservations taken during construction. Releasing a reservation that is
|
|
not currently held will be silently ignored (to permit clean-up after
|
|
failed / partial construction).
|
|
"""
|
|
pins = (
|
|
info
|
|
for name in names
|
|
for header, info in self.board_info.find_pin(name)
|
|
)
|
|
with self._res_lock:
|
|
for pin in pins:
|
|
self._reservations[pin] = [
|
|
ref for ref in self._reservations[pin]
|
|
if ref() not in (reserver, None) # may as well clean up dead refs
|
|
]
|
|
|
|
def release_all(self, reserver):
|
|
"""
|
|
Releases all pin reservations taken out by *reserver*. See
|
|
:meth:`release_pins` for further information).
|
|
"""
|
|
# Yes, this would be more efficient if it simply regenerated the
|
|
# reservations list without any references to reserver instead of
|
|
# (in release_pins) looping over each pin individually. However, this
|
|
# then causes a subtle bug in LocalPiFactory which does something
|
|
# horribly naughty (with good reason) and makes its _reservations
|
|
# dictionary equivalent to a class-level one.
|
|
self.release_pins(reserver, *(
|
|
pin.name for pin in self._reservations))
|
|
|
|
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, name):
|
|
"""
|
|
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 PinUnsupported( # pragma: no cover
|
|
"Individual pins are not supported by this pin factory")
|
|
|
|
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( # pragma: no cover
|
|
'SPI not supported by this pin factory')
|
|
|
|
def ticks(self):
|
|
"""
|
|
Return the current ticks, according to the factory. The reference point
|
|
is undefined and thus the result of this method is only meaningful when
|
|
compared to another value returned by this method.
|
|
|
|
The format of the time is also arbitrary, as is whether the time wraps
|
|
after a certain duration. Ticks should only be compared using the
|
|
:meth:`ticks_diff` method.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def ticks_diff(self, later, earlier):
|
|
"""
|
|
Return the time in seconds between two :meth:`ticks` results. The
|
|
arguments are specified in the same order as they would be in the
|
|
formula *later* - *earlier* but the result is guaranteed to be in
|
|
seconds, and to be positive even if the ticks "wrapped" between calls
|
|
to :meth:`ticks`.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def _get_board_info(self):
|
|
raise NotImplementedError
|
|
|
|
board_info = property(
|
|
lambda self: self._get_board_info(),
|
|
doc="""\
|
|
Returns a :class:`BoardInfo` instance (or derivative) representing the
|
|
board that instances generated by this factory will be attached to.
|
|
""")
|
|
|
|
def _get_pi_info(self):
|
|
warnings.warn(
|
|
DeprecationWarning(
|
|
"Please use Factory.board_info instead of Factory.pi_info"))
|
|
return self._get_board_info()
|
|
|
|
pi_info = property(lambda self: self._get_pi_info())
|
|
|
|
|
|
class Pin:
|
|
"""
|
|
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. Descendents *must* override the
|
|
following methods:
|
|
|
|
* :meth:`_get_info`
|
|
* :meth:`_get_function`
|
|
* :meth:`_set_function`
|
|
* :meth:`_get_state`
|
|
|
|
Descendents *may* additionally override the following methods, if
|
|
applicable:
|
|
|
|
* :meth:`close`
|
|
* :meth:`output_with_state`
|
|
* :meth:`input_with_pull`
|
|
* :meth:`_set_state`
|
|
* :meth:`_get_frequency`
|
|
* :meth:`_set_frequency`
|
|
* :meth:`_get_pull`
|
|
* :meth:`_set_pull`
|
|
* :meth:`_get_bounce`
|
|
* :meth:`_set_bounce`
|
|
* :meth:`_get_edges`
|
|
* :meth:`_set_edges`
|
|
* :meth:`_get_when_changed`
|
|
* :meth:`_set_when_changed`
|
|
"""
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *exc_info):
|
|
self.close()
|
|
|
|
def __repr__(self):
|
|
return "<Pin>" # pragma: no cover
|
|
|
|
def close(self):
|
|
"""
|
|
Cleans up the resources allocated to the pin. After this method is
|
|
called, this :class:`Pin` instance may no longer be used to query or
|
|
control the pin's state.
|
|
"""
|
|
pass
|
|
|
|
def output_with_state(self, state):
|
|
"""
|
|
Sets the pin's function to "output" and specifies an initial state
|
|
for the pin. By default this is equivalent to performing::
|
|
|
|
pin.function = 'output'
|
|
pin.state = state
|
|
|
|
However, descendents may override this in order to provide the smallest
|
|
possible delay between configuring the pin for output and specifying an
|
|
initial value (which can be important for avoiding "blips" in
|
|
active-low configurations).
|
|
"""
|
|
self.function = 'output'
|
|
self.state = state
|
|
|
|
def input_with_pull(self, pull):
|
|
"""
|
|
Sets the pin's function to "input" and specifies an initial pull-up
|
|
for the pin. By default this is equivalent to performing::
|
|
|
|
pin.function = 'input'
|
|
pin.pull = pull
|
|
|
|
However, descendents may override this order to provide the smallest
|
|
possible delay between configuring the pin for input and pulling the
|
|
pin up/down (which can be important for avoiding "blips" in some
|
|
configurations).
|
|
"""
|
|
self.function = 'input'
|
|
self.pull = pull
|
|
|
|
def _get_info(self):
|
|
raise NotImplementedError
|
|
|
|
info = property(
|
|
lambda self: self._get_info(),
|
|
doc="""\
|
|
Returns the :class:`PinInfo` associated with the pin. This can be used
|
|
to determine physical properties of the pin, including its location on
|
|
the header, fixed pulls, and the various specs that can be used to
|
|
identify it.
|
|
""")
|
|
|
|
def _get_function(self):
|
|
raise NotImplementedError
|
|
|
|
def _set_function(self, value):
|
|
raise NotImplementedError
|
|
|
|
function = property(
|
|
lambda self: self._get_function(),
|
|
lambda self, value: self._set_function(value),
|
|
doc="""\
|
|
The function of the pin. This property is a string indicating the
|
|
current function or purpose of the pin. Typically this is the string
|
|
"input" or "output". However, in some circumstances it can be other
|
|
strings indicating non-GPIO related functionality.
|
|
|
|
With certain pin types (e.g. GPIO pins), this attribute can be changed
|
|
to configure the function of a pin. If an invalid function is
|
|
specified, for this attribute, :exc:`PinInvalidFunction` will be
|
|
raised.
|
|
""")
|
|
|
|
def _get_state(self):
|
|
raise NotImplementedError
|
|
|
|
def _set_state(self, value):
|
|
raise PinSetInput(f"Cannot set the state of pin {self!r}") # pragma: no cover
|
|
|
|
state = property(
|
|
lambda self: self._get_state(),
|
|
lambda self, value: self._set_state(value),
|
|
doc="""\
|
|
The state of the pin. This is 0 for low, and 1 for high. As a low level
|
|
view of the pin, no swapping is performed in the case of pull ups (see
|
|
:attr:`pull` for more information):
|
|
|
|
.. 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 :data:`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
|
|
value is specified for this attribute, :exc:`PinInvalidState` will be
|
|
raised.
|
|
""")
|
|
|
|
def _get_pull(self):
|
|
return 'floating' # pragma: no cover
|
|
|
|
def _set_pull(self, value):
|
|
raise PinFixedPull( # pragma: no cover
|
|
f"Cannot change pull-up on pin {self!r}")
|
|
|
|
pull = property(
|
|
lambda self: self._get_pull(),
|
|
lambda self, value: self._set_pull(value),
|
|
doc="""\
|
|
The pull-up state of the pin represented as a string. This is typically
|
|
one of the strings "up", "down", or "floating" but additional values
|
|
may be supported by the underlying hardware.
|
|
|
|
If the pin does not support changing pull-up state (for example because
|
|
of a fixed pull-up resistor), attempts to set this property will raise
|
|
:exc:`PinFixedPull`. If the specified value is not supported by the
|
|
underlying hardware, :exc:`PinInvalidPull` is raised.
|
|
""")
|
|
|
|
def _get_frequency(self):
|
|
return None # pragma: no cover
|
|
|
|
def _set_frequency(self, value):
|
|
if value is not None:
|
|
raise PinPWMUnsupported( # pragma: no cover
|
|
f"PWM is not supported on pin {self!r}")
|
|
|
|
frequency = property(
|
|
lambda self: self._get_frequency(),
|
|
lambda self, value: self._set_frequency(value),
|
|
doc="""\
|
|
The frequency (in Hz) for the pin's PWM implementation, or :data:`None`
|
|
if PWM is not currently in use. This value always defaults to
|
|
:data:`None` and may be changed with certain pin types to activate or
|
|
deactivate PWM.
|
|
|
|
If the pin does not support PWM, :exc:`PinPWMUnsupported` will be
|
|
raised when attempting to set this to a value other than :data:`None`.
|
|
""")
|
|
|
|
def _get_bounce(self):
|
|
return None # pragma: no cover
|
|
|
|
def _set_bounce(self, value):
|
|
if value is not None: # pragma: no cover
|
|
raise PinEdgeDetectUnsupported(
|
|
f"Edge detection is not supported on pin {self!r}")
|
|
|
|
bounce = property(
|
|
lambda self: self._get_bounce(),
|
|
lambda self, value: self._set_bounce(value),
|
|
doc="""\
|
|
The amount of bounce detection (elimination) currently in use by edge
|
|
detection, measured in seconds. If bounce detection is not currently in
|
|
use, this is :data:`None`.
|
|
|
|
For example, if :attr:`edges` 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,
|
|
even if only in software.
|
|
""")
|
|
|
|
def _get_edges(self):
|
|
return 'none' # pragma: no cover
|
|
|
|
def _set_edges(self, value):
|
|
raise PinEdgeDetectUnsupported( # pragma: no cover
|
|
f"Edge detection is not supported on pin {self!r}")
|
|
|
|
edges = property(
|
|
lambda self: self._get_edges(),
|
|
lambda self, value: self._set_edges(value),
|
|
doc="""\
|
|
The edge that will trigger execution of the function or bound method
|
|
assigned to :attr:`when_changed`. This can be one of the strings
|
|
"both" (the default), "rising", "falling", or "none":
|
|
|
|
.. 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`.
|
|
""")
|
|
|
|
def _get_when_changed(self):
|
|
return None # pragma: no cover
|
|
|
|
def _set_when_changed(self, value):
|
|
raise PinEdgeDetectUnsupported( # pragma: no cover
|
|
f"Edge detection is not supported on pin {self!r}")
|
|
|
|
when_changed = property(
|
|
lambda self: self._get_when_changed(),
|
|
lambda self, value: self._set_when_changed(value),
|
|
doc="""\
|
|
A function or bound method to be called when the pin's state changes
|
|
(more specifically when the edge specified by :attr:`edges` is detected
|
|
on the pin). The function or bound method must accept two parameters:
|
|
the first will report the ticks (from :meth:`Factory.ticks`) when
|
|
the pin's state changed, and the second will report the pin's current
|
|
state.
|
|
|
|
.. warning::
|
|
|
|
Depending on hardware support, the state is *not guaranteed to be
|
|
accurate*. For instance, many GPIO implementations will provide
|
|
an interrupt indicating when a pin's state changed but not what it
|
|
changed to. In this case the pin driver simply reads the pin's
|
|
current state to supply this parameter, but the pin's state may
|
|
have changed *since* the interrupt. Exercise appropriate caution
|
|
when relying upon this parameter.
|
|
|
|
If the pin does not support edge detection, attempts to set this
|
|
property will raise :exc:`PinEdgeDetectUnsupported`.
|
|
""")
|
|
|
|
|
|
class SPI(Device):
|
|
"""
|
|
Abstract interface for `Serial Peripheral Interface`_ (SPI)
|
|
implementations. Descendents *must* override the following methods:
|
|
|
|
* :meth:`transfer`
|
|
* :meth:`_get_clock_mode`
|
|
|
|
Descendents *may* override the following methods, if applicable:
|
|
|
|
* :meth:`read`
|
|
* :meth:`write`
|
|
* :meth:`_set_clock_mode`
|
|
* :meth:`_get_lsb_first`
|
|
* :meth:`_set_lsb_first`
|
|
* :meth:`_get_select_high`
|
|
* :meth:`_set_select_high`
|
|
* :meth:`_get_bits_per_word`
|
|
* :meth:`_set_bits_per_word`
|
|
|
|
.. _Serial Peripheral Interface: https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus
|
|
"""
|
|
|
|
def read(self, n):
|
|
"""
|
|
Read *n* words of data from the SPI interface, returning them as a
|
|
sequence of unsigned ints, each no larger than the configured
|
|
:attr:`bits_per_word` of the interface.
|
|
|
|
This method is typically used with read-only devices that feature
|
|
half-duplex communication. See :meth:`transfer` for full duplex
|
|
communication.
|
|
"""
|
|
return self.transfer([0] * n)
|
|
|
|
def write(self, data):
|
|
"""
|
|
Write *data* to the SPI interface. *data* must be a sequence of
|
|
unsigned integer words each of which will fit within the configured
|
|
:attr:`bits_per_word` of the interface. The method returns the number
|
|
of words written to the interface (which may be less than or equal to
|
|
the length of *data*).
|
|
|
|
This method is typically used with write-only devices that feature
|
|
half-duplex communication. See :meth:`transfer` for full duplex
|
|
communication.
|
|
"""
|
|
return len(self.transfer(data))
|
|
|
|
def transfer(self, data):
|
|
"""
|
|
Write *data* to the SPI interface. *data* must be a sequence of
|
|
unsigned integer words each of which will fit within the configured
|
|
:attr:`bits_per_word` of the interface. The method returns the sequence
|
|
of words read from the interface while writing occurred (full duplex
|
|
communication).
|
|
|
|
The length of the sequence returned dictates the number of words of
|
|
*data* written to the interface. Each word in the returned sequence
|
|
will be an unsigned integer no larger than the configured
|
|
:attr:`bits_per_word` of the interface.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
@property
|
|
def clock_polarity(self):
|
|
"""
|
|
The polarity of the SPI clock pin. If this is :data:`False` (the
|
|
default), the clock pin will idle low, and pulse high. Setting this to
|
|
:data:`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 :data:`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 :data:`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 :data:`False` (the default),
|
|
data will be read from the MISO pin when the clock pin activates.
|
|
Setting this to :data:`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 :data:`False`, and :attr:`clock_phase` is
|
|
:data:`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 :data:`False`, but :attr:`clock_phase` is
|
|
:data:`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 # pragma: no cover
|
|
|
|
def _set_clock_mode(self, value):
|
|
raise SPIFixedClockMode( # pragma: no cover
|
|
f"clock_mode cannot be changed on {self!r}")
|
|
|
|
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 # pragma: no cover
|
|
|
|
def _set_lsb_first(self, value):
|
|
raise SPIFixedBitOrder( # pragma: no cover
|
|
f"lsb_first cannot be changed on {self!r}")
|
|
|
|
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 :data:`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 :data:`False`
|
|
(the default):
|
|
|
|
.. code-block:: text
|
|
|
|
,---. ,---. ,---. ,---.
|
|
CLK | | | | | | | |
|
|
| | | | | | | |
|
|
----' `---' `---' `---' `-----
|
|
: ,-------. : ,-------.
|
|
MISO: | : | : | : |
|
|
: | : | : | : |
|
|
----------' : `-------' : `----
|
|
: : : :
|
|
MSB LSB
|
|
|
|
And now with :attr:`lsb_first` set to :data:`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 # pragma: no cover
|
|
|
|
def _set_select_high(self, value):
|
|
raise SPIFixedSelect( # pragma: no cover
|
|
f"select_high cannot be changed on {self!r}")
|
|
|
|
select_high = property(
|
|
lambda self: self._get_select_high(),
|
|
lambda self, value: self._set_select_high(value),
|
|
doc="""\
|
|
If :data:`False` (the default), the chip select line is considered
|
|
active when it is pulled low. When set to :data:`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 :data:`False`, and
|
|
:attr:`select_high` is :data:`False` (the default):
|
|
|
|
.. code-block:: text
|
|
|
|
---. ,------
|
|
__ | |
|
|
CS | chip is selected, and will react to clock | idle
|
|
`-----------------------------------------------------'
|
|
|
|
,---. ,---. ,---. ,---. ,---. ,---. ,---.
|
|
CLK | | | | | | | | | | | | | |
|
|
| | | | | | | | | | | | | |
|
|
----' `---' `---' `---' `---' `---' `---' `-------
|
|
|
|
And when :attr:`select_high` is :data:`True`:
|
|
|
|
.. code-block:: text
|
|
|
|
,-----------------------------------------------------.
|
|
CS | chip is selected, and will react to clock | idle
|
|
| |
|
|
---' `------
|
|
|
|
,---. ,---. ,---. ,---. ,---. ,---. ,---.
|
|
CLK | | | | | | | | | | | | | |
|
|
| | | | | | | | | | | | | |
|
|
----' `---' `---' `---' `---' `---' `---' `-------
|
|
""")
|
|
|
|
def _get_bits_per_word(self):
|
|
return 8 # pragma: no cover
|
|
|
|
def _set_bits_per_word(self, value):
|
|
raise SPIFixedWordSize( # pragma: no cover
|
|
f"bits_per_word cannot be changed on {self!r}")
|
|
|
|
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.
|
|
""")
|
|
|
|
def _get_rate(self):
|
|
return 100000 # pragma: no cover
|
|
|
|
def _set_rate(self, value):
|
|
raise SPIFixedRate( # pragma: no cover
|
|
f"rate cannot be changed on {self!r}")
|
|
|
|
rate = property(
|
|
lambda self: self._get_rate(),
|
|
lambda self, value: self._set_rate(value),
|
|
doc="""\
|
|
Controls the speed of the SPI interface in Hz (or baud).
|
|
|
|
Note that most software SPI implementations ignore this property, and
|
|
will raise :exc:`SPIFixedRate` if an attempt is made to set it, as they
|
|
have no rate control (they simply bit-bang as fast as possible because
|
|
typically this isn't very fast anyway, and introducing measures to
|
|
limit the rate would simply slow them down to the point of being
|
|
useless).
|
|
""")
|
|
|
|
|
|
class PinInfo(namedtuple('PinInfo', (
|
|
'number',
|
|
'name',
|
|
'names',
|
|
'pull',
|
|
'row',
|
|
'col',
|
|
'interfaces',
|
|
))):
|
|
"""
|
|
This class is a :func:`~collections.namedtuple` derivative used to
|
|
represent information about a pin present on a GPIO header. The following
|
|
attributes are defined:
|
|
|
|
.. attribute:: number
|
|
|
|
An integer containing the physical pin number on the header (starting
|
|
from 1 in accordance with convention).
|
|
|
|
.. attribute:: name
|
|
|
|
A string describing the function of the pin. Some common examples
|
|
include "GND" (for pins connecting to ground), "3V3" (for pins which
|
|
output 3.3 volts), "GPIO9" (for GPIO9 in the board's numbering scheme),
|
|
etc.
|
|
|
|
.. attribute:: names
|
|
|
|
A set of all the names that can be used to identify this pin with
|
|
:meth:`BoardInfo.find_pin`. The :attr:`name` attribute is the "typical"
|
|
name for this pin, and will be one of the values in this set.
|
|
|
|
When "gpio" is in :attr:`interfaces`, these names can be used with
|
|
:meth:`Factory.pin` to construct a :class:`Pin` instance representing
|
|
this pin.
|
|
|
|
.. attribute:: pull
|
|
|
|
A string indicating the fixed pull of the pin, if any. This is a blank
|
|
string if the pin has no fixed pull, but may be "up" in the case of
|
|
pins typically used for I2C such as GPIO2 and GPIO3 on a Raspberry Pi.
|
|
|
|
.. attribute:: row
|
|
|
|
An integer indicating on which row the pin is physically located in
|
|
the header (1-based)
|
|
|
|
.. attribute:: col
|
|
|
|
An integer indicating in which column the pin is physically located
|
|
in the header (1-based)
|
|
|
|
.. attribute:: interfaces
|
|
|
|
A :class:`dict` mapping interfaces that this pin can be a part of to
|
|
the description of that pin in that interface (e.g. "i2c" might map to
|
|
"I2C0 SDA"). Typical keys are "gpio", "spi", "i2c", "uart", "pwm",
|
|
"smi", and "dpi".
|
|
|
|
.. autoattribute:: pull_up
|
|
|
|
.. autoattribute:: function
|
|
"""
|
|
__slots__ = () # workaround python issue #24931
|
|
|
|
@property
|
|
def function(self):
|
|
"""
|
|
Deprecated alias of :attr:`name`.
|
|
"""
|
|
warnings.warn(
|
|
DeprecationWarning(
|
|
"PinInfo.function is deprecated; please use PinInfo.name"))
|
|
return self.name
|
|
|
|
@property
|
|
def pull_up(self):
|
|
"""
|
|
Deprecated variant of :attr:`pull`.
|
|
"""
|
|
warnings.warn(
|
|
DeprecationWarning(
|
|
"PinInfo.pull_up is deprecated; please use PinInfo.pull"))
|
|
return self.pull == 'up'
|
|
|
|
|
|
class HeaderInfo(namedtuple('HeaderInfo', (
|
|
'name',
|
|
'rows',
|
|
'columns',
|
|
'pins',
|
|
))):
|
|
"""
|
|
This class is a :func:`~collections.namedtuple` derivative used to
|
|
represent information about a pin header on a board. The object can be used
|
|
in a format string with various custom specifications::
|
|
|
|
from gpiozero.pins.native import NativeFactory
|
|
|
|
factory = NativeFactory()
|
|
j8 = factory.board_info.headers['J8']
|
|
print(f'{j8}')
|
|
print(f'{j8:full}')
|
|
p1 = factory.board_info.headers['P1']
|
|
print(f'{p1:col2}')
|
|
print(f'{p1:row1}')
|
|
|
|
"color" and "mono" can be prefixed to format specifications to force the
|
|
use of `ANSI color codes`_. If neither is specified, ANSI codes will only
|
|
be used if stdout is detected to be a tty. "rev" can be added to output the
|
|
row or column in reverse order::
|
|
|
|
# force use of ANSI codes
|
|
j8 = factory.board_info.headers['J8']
|
|
print(f'{j8:color row2}')
|
|
# force plain ASCII
|
|
print(f'{j8:mono row2}')
|
|
# output in reverse order
|
|
print(f'{j8:color rev row1}')
|
|
|
|
The following attributes are defined:
|
|
|
|
.. automethod:: pprint
|
|
|
|
.. attribute:: name
|
|
|
|
The name of the header, typically as it appears silk-screened on the
|
|
board (e.g. "P1" or "J8").
|
|
|
|
.. attribute:: rows
|
|
|
|
The number of rows on the header.
|
|
|
|
.. attribute:: columns
|
|
|
|
The number of columns on the header.
|
|
|
|
.. 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
|
|
|
|
def _pin_style(self, pin, style):
|
|
if 'gpio' in pin.interfaces:
|
|
return style('bold green')
|
|
elif pin.name == '5V':
|
|
return style('bold red')
|
|
elif pin.name in {'3V3', '1V8'}:
|
|
return style('bold cyan')
|
|
elif pin.name in {'GND', 'NC'}:
|
|
return style('bold black')
|
|
else:
|
|
return style('yellow')
|
|
|
|
def _format_full(self, style):
|
|
Cell = namedtuple('Cell', ('content', 'align', 'style'))
|
|
|
|
lines = []
|
|
for row in range(self.rows):
|
|
line = []
|
|
for col in range(self.columns):
|
|
pin = (row * self.columns) + col + 1
|
|
try:
|
|
pin = self.pins[pin]
|
|
cells = [
|
|
Cell(pin.name, '><'[col % 2], self._pin_style(pin, style)),
|
|
Cell(f'({pin.number})', '><'[col % 2], ''),
|
|
]
|
|
if col % 2:
|
|
cells = reversed(cells)
|
|
line.extend(cells)
|
|
except KeyError:
|
|
line.append(Cell('', '<', ''))
|
|
lines.append(line)
|
|
cols = list(zip(*lines))
|
|
col_lens = [max(len(cell.content) for cell in col) for col in cols]
|
|
lines = [
|
|
' '.join(
|
|
f'{cell.style}{cell.content:{cell.align}{width}s}{style:reset}'
|
|
for cell, width, align in zip(line, col_lens, cycle('><')))
|
|
for line in lines
|
|
]
|
|
return '\n'.join(lines)
|
|
|
|
def _format_pin(self, pin, style):
|
|
return ''.join((
|
|
style('on black'),
|
|
(
|
|
' ' if pin is None else
|
|
self._pin_style(pin, style) +
|
|
('1' if pin.number == 1 else
|
|
'2' if pin.number == 2 and self.rows == self.columns else
|
|
'o')
|
|
),
|
|
style('reset')
|
|
))
|
|
|
|
def _format_row(self, row, style, rev=False):
|
|
step = -1 if rev else 1
|
|
if row > self.rows:
|
|
raise ValueError(f'invalid row {row} for header {self.name}')
|
|
start_pin = (row - 1) * self.columns + 1
|
|
return ''.join(
|
|
self._format_pin(pin, style)
|
|
for n in range(start_pin, start_pin + self.columns)[::step]
|
|
for pin in (self.pins.get(n),)
|
|
)
|
|
|
|
def _format_col(self, col, style, rev=False):
|
|
step = -1 if rev else 1
|
|
if col > self.columns:
|
|
raise ValueError(f'invalid col {col} for header {self.name}')
|
|
return ''.join(
|
|
self._format_pin(pin, style)
|
|
for n in range(col, self.rows * self.columns + 1, self.columns)[::step]
|
|
for pin in (self.pins.get(n),)
|
|
)
|
|
|
|
def __format__(self, format_spec):
|
|
style, content = Style.from_style_content(format_spec)
|
|
content = set(content.split())
|
|
try:
|
|
content.remove('rev')
|
|
rev = True
|
|
except KeyError:
|
|
rev = False
|
|
if len(content) != 1:
|
|
raise ValueError('Invalid format specifier')
|
|
content = content.pop()
|
|
if content == 'full':
|
|
return self._format_full(style)
|
|
elif content.startswith('row') and content[3:].isdigit():
|
|
return self._format_row(int(content[3:]), style, rev=rev)
|
|
elif content.startswith('col') and content[3:].isdigit():
|
|
return self._format_col(int(content[3:]), style, rev=rev)
|
|
else:
|
|
raise ValueError('Invalid format specifier')
|
|
|
|
def pprint(self, color=None):
|
|
"""
|
|
Pretty-print a diagram of the header pins.
|
|
|
|
If *color* is :data:`None` (the default, the diagram will include ANSI
|
|
color codes if stdout is a color-capable terminal). Otherwise *color*
|
|
can be set to :data:`True` or :data:`False` to force color or
|
|
monochrome output.
|
|
"""
|
|
style = Style(color)
|
|
print(f'{self:{style} full}')
|
|
|
|
|
|
class BoardInfo(namedtuple('BoardInfo', (
|
|
'revision',
|
|
'model',
|
|
'pcb_revision',
|
|
'released',
|
|
'soc',
|
|
'manufacturer',
|
|
'memory',
|
|
'storage',
|
|
'usb',
|
|
'usb3',
|
|
'ethernet',
|
|
'eth_speed',
|
|
'wifi',
|
|
'bluetooth',
|
|
'csi',
|
|
'dsi',
|
|
'headers',
|
|
'board',
|
|
))):
|
|
"""
|
|
This class is a :func:`~collections.namedtuple` derivative used to
|
|
represent information about a particular board. While it is a tuple, it is
|
|
strongly recommended that you use the following named attributes to access
|
|
the data contained within. The object can be used in format strings with
|
|
various custom format specifications::
|
|
|
|
from gpiozero.pins.native import NativeFactory
|
|
|
|
factory = NativeFactory()
|
|
print(f'{factory.board_info}'
|
|
print(f'{factory.board_info:full}'
|
|
print(f'{factory.board_info:board}'
|
|
print(f'{factory.board_info:specs}'
|
|
print(f'{factory.board_info:headers}'
|
|
|
|
"color" and "mono" can be prefixed to format specifications to force the
|
|
use of `ANSI color codes`_. If neither is specified, ANSI codes will only
|
|
be used if stdout is detected to be a tty::
|
|
|
|
# force use of ANSI codes
|
|
print(f'{factory.board_info:color board}')
|
|
# force plain ASCII
|
|
print(f'{factory.board_info:mono board}')
|
|
|
|
.. _ANSI color codes: https://en.wikipedia.org/wiki/ANSI_escape_code
|
|
|
|
.. automethod:: physical_pin
|
|
|
|
.. automethod:: physical_pins
|
|
|
|
.. automethod:: pprint
|
|
|
|
.. automethod:: pulled_up
|
|
|
|
.. automethod:: to_gpio
|
|
|
|
.. attribute:: revision
|
|
|
|
A string indicating the revision of the board. This is unique to each
|
|
revision and can be considered the "key" from which all other
|
|
attributes are derived. However, in itself the string is fairly
|
|
meaningless.
|
|
|
|
.. attribute:: model
|
|
|
|
A string containing the model of the board (for example, "B", "B+",
|
|
"A+", "2B", "CM", "Zero", etc.)
|
|
|
|
.. attribute:: pcb_revision
|
|
|
|
A string containing the PCB revision number which is silk-screened onto
|
|
the board.
|
|
|
|
.. attribute:: released
|
|
|
|
A string containing an approximate release date for this board
|
|
(formatted as yyyyQq, e.g. 2012Q1 means the first quarter of 2012).
|
|
|
|
.. attribute:: soc
|
|
|
|
A string indicating the SoC (`system on a chip`_) that powers this
|
|
board.
|
|
|
|
.. attribute:: manufacturer
|
|
|
|
A string indicating the name of the manufacturer (e.g. "Sony").
|
|
|
|
.. attribute:: memory
|
|
|
|
An integer indicating the amount of memory (in Mb) connected to the
|
|
SoC.
|
|
|
|
.. note::
|
|
|
|
This can differ substantially from the amount of RAM available to
|
|
the operating system as the GPU's memory is typically shared with
|
|
the CPU.
|
|
|
|
.. attribute:: storage
|
|
|
|
A string indicating the typical bootable storage used with this board,
|
|
e.g. "SD", "MicroSD", or "eMMC".
|
|
|
|
.. attribute:: usb
|
|
|
|
An integer indicating how many USB ports are physically present on
|
|
this board, of any type.
|
|
|
|
.. note::
|
|
|
|
This does *not* include any (typically micro-USB) port used to
|
|
power the board.
|
|
|
|
.. attribute:: usb3
|
|
|
|
An integer indicating how many of the USB ports are USB3 ports on this
|
|
board.
|
|
|
|
.. attribute:: ethernet
|
|
|
|
An integer indicating how many Ethernet ports are physically present
|
|
on this board.
|
|
|
|
.. attribute:: eth_speed
|
|
|
|
An integer indicating the maximum speed (in Mbps) of the Ethernet ports
|
|
(if any). If no Ethernet ports are present, this is 0.
|
|
|
|
.. attribute:: wifi
|
|
|
|
A bool indicating whether this board has wifi built-in.
|
|
|
|
.. attribute:: bluetooth
|
|
|
|
A bool indicating whether this board has bluetooth built-in.
|
|
|
|
.. attribute:: csi
|
|
|
|
An integer indicating the number of CSI (camera) ports available on
|
|
this board.
|
|
|
|
.. attribute:: dsi
|
|
|
|
An integer indicating the number of DSI (display) ports available on
|
|
this board.
|
|
|
|
.. attribute:: headers
|
|
|
|
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 J8 you
|
|
would query ``headers['J8'].pins[12]``.
|
|
|
|
A rendered version of this data can be obtained by using the
|
|
:class:`BoardInfo` object in a format string::
|
|
|
|
from gpiozero.pins.native import NativeFactory
|
|
|
|
factory = NativeFactory()
|
|
print(f'{factory.board_info:headers}')
|
|
|
|
.. attribute:: board
|
|
|
|
An ASCII art rendition of the board, primarily intended for console
|
|
pretty-print usage. A more usefully rendered version of this data can
|
|
be obtained by using the :class:`BoardInfo` object in a format string.
|
|
For example::
|
|
|
|
from gpiozero.pins.native import NativeFactory
|
|
|
|
factory = NativeFactory()
|
|
print(f'{factory.board_info:board}')
|
|
|
|
.. autoattribute:: description
|
|
|
|
.. _system on a chip: https://en.wikipedia.org/wiki/System_on_a_chip
|
|
"""
|
|
__slots__ = () # workaround python issue #24931
|
|
|
|
def find_pin(self, name):
|
|
"""
|
|
A generator function which, given a pin *name*, yields tuples of
|
|
:class:`HeaderInfo` and :class:`PinInfo` instances for which *name*
|
|
equals :attr:`PinInfo.name`.
|
|
"""
|
|
for header in self.headers.values():
|
|
for pin in header.pins.values():
|
|
if name in pin.names:
|
|
yield header, pin
|
|
|
|
def physical_pins(self, function):
|
|
"""
|
|
Return the physical pins supporting the specified *function* as tuples
|
|
of ``(header, pin_number)`` where *header* is a string specifying the
|
|
header containing the *pin_number*. Note that the return value is a
|
|
:class:`set` which is not indexable. Use :func:`physical_pin` if you
|
|
are expecting a single return value.
|
|
|
|
:param str function:
|
|
The pin function you wish to search for. Usually this is something
|
|
like "GPIO9" for GPIO pin 9, or "GND" for all the pins connecting
|
|
to electrical ground.
|
|
"""
|
|
warnings.warn(
|
|
DeprecationWarning(
|
|
"PiBoardInfo.physical_pins is deprecated; please use "
|
|
"BoardInfo.find_pin instead"))
|
|
return {
|
|
(header.name, pin.number)
|
|
for header, pin in self.find_pin(function)
|
|
}
|
|
|
|
def physical_pin(self, function):
|
|
"""
|
|
Return the physical pin supporting the specified *function* as a tuple
|
|
of ``(header, pin_number)`` where *header* is a string specifying the
|
|
header containing the *pin_number*. If no pins support the desired
|
|
*function*, this function raises :exc:`PinNoPins`. If multiple pins
|
|
support the desired *function*, :exc:`PinMultiplePins` will be raised
|
|
(use :func:`physical_pins` if you expect multiple pins in the result,
|
|
such as for electrical ground).
|
|
|
|
:param str function:
|
|
The pin function you wish to search for. Usually this is something
|
|
like "GPIO9" for GPIO pin 9.
|
|
"""
|
|
warnings.warn(
|
|
DeprecationWarning(
|
|
"PiBoardInfo.physical_pin is deprecated; please use "
|
|
"BoardInfo.find_pin instead"))
|
|
result = self.physical_pins(function)
|
|
if len(result) > 1:
|
|
raise PinMultiplePins(f'multiple pins can be used for {function}')
|
|
elif result:
|
|
return result.pop()
|
|
else:
|
|
raise PinNoPins(f'no pins can be used for {function}')
|
|
|
|
def pulled_up(self, function):
|
|
"""
|
|
Returns a bool indicating whether a physical pull-up is attached to
|
|
the pin supporting the specified *function*. Either :exc:`PinNoPins`
|
|
or :exc:`PinMultiplePins` may be raised if the function is not
|
|
associated with a single pin.
|
|
|
|
:param str function:
|
|
The pin function you wish to determine pull-up for. Usually this is
|
|
something like "GPIO9" for GPIO pin 9.
|
|
"""
|
|
warnings.warn(
|
|
DeprecationWarning(
|
|
"PiBoardInfo.pulled_up is deprecated; please use "
|
|
"BoardInfo.find_pin and PinInfo.pull instead"))
|
|
for header, pin in self.find_pin(function):
|
|
return pin.pull == 'up'
|
|
return False
|
|
|
|
def to_gpio(self, name):
|
|
"""
|
|
Parses a pin *name*, returning the primary name of the pin (which can
|
|
be used to construct it), or raising a :exc:`ValueError` exception if
|
|
the name does not represent a GPIO pin.
|
|
|
|
The *name* may be given in any of the following forms:
|
|
|
|
* An integer, which will be accepted as a GPIO number
|
|
* 'GPIOn' where n is the GPIO number
|
|
* 'h:n' where h is the header name and n is the physical pin number
|
|
"""
|
|
for header, pin in self.find_pin(name):
|
|
if 'gpio' in pin.interfaces:
|
|
return pin.name
|
|
else:
|
|
raise PinInvalidPin(f'{name} is not a GPIO pin')
|
|
raise PinInvalidPin(f'{name} is not a valid pin name')
|
|
|
|
def __repr__(self):
|
|
fields=', '.join(
|
|
f'{name}=...' if name in ('headers', 'board') else
|
|
f'{name}={value!r}'
|
|
for name, value in zip(self._fields, self)
|
|
)
|
|
return f'{self.__class__.__name__}({fields})'
|
|
|
|
def __format__(self, format_spec):
|
|
style, content = Style.from_style_content(format_spec)
|
|
if content == 'full':
|
|
return '\n\n'.join((
|
|
f'{self:{style} specs}',
|
|
f'{self:{style} board}',
|
|
f'{self:{style} headers}',
|
|
))
|
|
elif content == 'board':
|
|
kw = self._asdict()
|
|
kw.update({
|
|
name: header
|
|
for name, header in self.headers.items()
|
|
})
|
|
return self.board.format(style=style, **kw)
|
|
elif content == 'specs':
|
|
if self.memory < 1024:
|
|
memory = f'{self.memory}MB'
|
|
else:
|
|
memory = f'{int(self.memory / 1024)}GB'
|
|
return dedent(f"""\
|
|
{style:bold}Description {style:reset}: {self.description}
|
|
{style:bold}Revision {style:reset}: {self.revision}
|
|
{style:bold}SoC {style:reset}: {self.soc}
|
|
{style:bold}RAM {style:reset}: {memory}
|
|
{style:bold}Storage {style:reset}: {self.storage}
|
|
{style:bold}USB ports {style:reset}: {self.usb} (of which {self.usb3} USB3)
|
|
{style:bold}Ethernet ports {style:reset}: {self.ethernet} ({self.eth_speed}Mbps max. speed)
|
|
{style:bold}Wi-fi {style:reset}: {self.wifi}
|
|
{style:bold}Bluetooth {style:reset}: {self.bluetooth}
|
|
{style:bold}Camera ports (CSI) {style:reset}: {self.csi}
|
|
{style:bold}Display ports (DSI){style:reset}: {self.dsi}"""
|
|
)
|
|
elif content == 'headers':
|
|
return '\n\n'.join(
|
|
f'{style:bold}{header.name}{style:reset}:\n'
|
|
f'{header:{style} full}'
|
|
for header in self.headers.values()
|
|
)
|
|
else:
|
|
raise ValueError('Invalid format specifier')
|
|
|
|
def pprint(self, color=None):
|
|
"""
|
|
Pretty-print a representation of the board along with header diagrams.
|
|
|
|
If *color* is :data:`None` (the default), the diagram will include ANSI
|
|
color codes if stdout is a color-capable terminal. Otherwise *color*
|
|
can be set to :data:`True` or :data:`False` to force color or monochrome
|
|
output.
|
|
"""
|
|
style = Style(color)
|
|
print(f'{self:{style} full}')
|
|
|
|
@property
|
|
def description(self):
|
|
"""
|
|
A string containing a textual description of the board typically
|
|
containing the :attr:`model`, for example "Raspberry Pi 3B"
|
|
"""
|
|
return f'{self.model} rev {self.pcb_revision}'
|