added completly new version for haslach 2025
This commit is contained in:
777
.venv/lib/python3.7/site-packages/gpiozero/pins/__init__.py
Normal file
777
.venv/lib/python3.7/site-packages/gpiozero/pins/__init__.py
Normal file
@@ -0,0 +1,777 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2015-2021 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
|
||||
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
absolute_import,
|
||||
print_function,
|
||||
division,
|
||||
)
|
||||
str = type('')
|
||||
|
||||
from weakref import ref
|
||||
from collections import defaultdict
|
||||
from threading import Lock
|
||||
|
||||
from ..devices import Device
|
||||
from ..exc import (
|
||||
PinInvalidFunction,
|
||||
PinSetInput,
|
||||
PinFixedPull,
|
||||
PinUnsupported,
|
||||
PinSPIUnsupported,
|
||||
PinPWMUnsupported,
|
||||
PinEdgeDetectUnsupported,
|
||||
SPIFixedClockMode,
|
||||
SPIFixedBitOrder,
|
||||
SPIFixedSelect,
|
||||
SPIFixedWordSize,
|
||||
SPIFixedRate,
|
||||
GPIOPinInUse,
|
||||
)
|
||||
|
||||
|
||||
class Factory(object):
|
||||
"""
|
||||
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`
|
||||
|
||||
Descendents *may* override the following methods, if applicable:
|
||||
|
||||
* :meth:`close`
|
||||
* :meth:`reserve_pins`
|
||||
* :meth:`release_pins`
|
||||
* :meth:`release_all`
|
||||
* :meth:`pin`
|
||||
* :meth:`spi`
|
||||
* :meth:`_get_pi_info`
|
||||
"""
|
||||
def __init__(self):
|
||||
self._reservations = defaultdict(list)
|
||||
self._res_lock = Lock()
|
||||
|
||||
def reserve_pins(self, requester, *pins):
|
||||
"""
|
||||
Called to indicate that the device reserves the right to use the
|
||||
specified *pins*. 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:
|
||||
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('pin %s is already in use by %r' %
|
||||
(pin, reserver))
|
||||
self._reservations[pin].append(ref(requester))
|
||||
|
||||
def release_pins(self, reserver, *pins):
|
||||
"""
|
||||
Releases the reservation of *reserver* against *pins*. 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).
|
||||
"""
|
||||
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, *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, 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 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_pi_info(self):
|
||||
return None # pragma: no cover
|
||||
|
||||
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 :data:`None`.
|
||||
""")
|
||||
|
||||
|
||||
class Pin(object):
|
||||
"""
|
||||
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_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 __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_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( # pragma: no cover
|
||||
"Cannot set the state of pin %r" % self)
|
||||
|
||||
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
|
||||
"Cannot change pull-up on pin %r" % self)
|
||||
|
||||
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
|
||||
"PWM is not supported on pin %r" % self)
|
||||
|
||||
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(
|
||||
"Edge detection is not supported on pin %r" % self)
|
||||
|
||||
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
|
||||
"Edge detection is not supported on pin %r" % self)
|
||||
|
||||
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
|
||||
"Edge detection is not supported on pin %r" % self)
|
||||
|
||||
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
|
||||
"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 # pragma: no cover
|
||||
|
||||
def _set_lsb_first(self, value):
|
||||
raise SPIFixedBitOrder( # pragma: no cover
|
||||
"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 :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
|
||||
"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 :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
|
||||
"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.
|
||||
""")
|
||||
|
||||
def _get_rate(self):
|
||||
return 100000 # pragma: no cover
|
||||
|
||||
def _set_rate(self, value):
|
||||
raise SPIFixedRate( # pragma: no cover
|
||||
"rate cannot be changed on %r" % self)
|
||||
|
||||
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).
|
||||
""")
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1479
.venv/lib/python3.7/site-packages/gpiozero/pins/data.py
Normal file
1479
.venv/lib/python3.7/site-packages/gpiozero/pins/data.py
Normal file
File diff suppressed because it is too large
Load Diff
359
.venv/lib/python3.7/site-packages/gpiozero/pins/lgpio.py
Normal file
359
.venv/lib/python3.7/site-packages/gpiozero/pins/lgpio.py
Normal file
@@ -0,0 +1,359 @@
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2021 Dave Jones <dave@waveform.org.uk>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
absolute_import,
|
||||
print_function,
|
||||
division,
|
||||
)
|
||||
str = type('')
|
||||
|
||||
import os
|
||||
|
||||
import lgpio
|
||||
|
||||
from . import SPI
|
||||
from .pi import spi_port_device
|
||||
from .local import LocalPiFactory, LocalPiPin
|
||||
from ..mixins import SharedMixin
|
||||
from ..exc import (
|
||||
PinInvalidFunction,
|
||||
PinSetInput,
|
||||
PinFixedPull,
|
||||
PinInvalidPull,
|
||||
PinInvalidBounce,
|
||||
PinInvalidState,
|
||||
SPIBadArgs,
|
||||
SPIInvalidClockMode,
|
||||
PinPWMFixedValue,
|
||||
DeviceClosed
|
||||
)
|
||||
|
||||
|
||||
class LGPIOFactory(LocalPiFactory):
|
||||
"""
|
||||
Extends :class:`~gpiozero.pins.local.LocalPiFactory`. Uses the `lgpio`_
|
||||
library to interface to the local computer's GPIO pins. The lgpio library
|
||||
simply talks to Linux gpiochip devices; it is not specific to the Raspberry
|
||||
Pi although this class is currently constructed under the assumption that
|
||||
it is running on a Raspberry Pi.
|
||||
|
||||
You can construct lgpio pins manually like so::
|
||||
|
||||
from gpiozero.pins.lgpio import LGPIOFactory
|
||||
from gpiozero import LED
|
||||
|
||||
factory = LGPIOFactory(chip=0)
|
||||
led = LED(12, pin_factory=factory)
|
||||
|
||||
The *chip* parameter to the factory constructor specifies which gpiochip
|
||||
device to attempt to open. It defaults to 0 and thus doesn't normally need
|
||||
to be specified (the example above only includes it for completeness).
|
||||
|
||||
The lgpio library relies on access to the :file:`/dev/gpiochip*` devices.
|
||||
If you run into issues, please check that your user has read/write access
|
||||
to the specific gpiochip device you are attempting to open (0 by default).
|
||||
|
||||
.. _lgpio: http://abyz.me.uk/lg/py_lgpio.html
|
||||
"""
|
||||
def __init__(self, chip=0):
|
||||
super(LGPIOFactory, self).__init__()
|
||||
self._handle = lgpio.gpiochip_open(chip)
|
||||
self._chip = chip
|
||||
self.pin_class = LGPIOPin
|
||||
|
||||
def close(self):
|
||||
super(LGPIOFactory, self).close()
|
||||
if self._handle is not None:
|
||||
lgpio.gpiochip_close(self._handle)
|
||||
self._handle = None
|
||||
|
||||
@property
|
||||
def chip(self):
|
||||
return self._chip
|
||||
|
||||
def _get_spi_class(self, shared, hardware):
|
||||
# support via lgpio instead of spidev
|
||||
if hardware:
|
||||
return [LGPIOHardwareSPI, LGPIOHardwareSPIShared][shared]
|
||||
return super(LGPIOFactory, self)._get_spi_class(shared, hardware=False)
|
||||
|
||||
|
||||
class LGPIOPin(LocalPiPin):
|
||||
"""
|
||||
Extends :class:`~gpiozero.pins.local.LocalPiPin`. Pin implementation for
|
||||
the `lgpio`_ library. See :class:`LGPIOFactory` for more information.
|
||||
|
||||
.. _lgpio: http://abyz.me.uk/lg/py_lgpio.html
|
||||
"""
|
||||
GPIO_IS_KERNEL = 1 << 0
|
||||
GPIO_IS_OUT = 1 << 1
|
||||
GPIO_IS_ACTIVE_LOW = 1 << 2
|
||||
GPIO_IS_OPEN_DRAIN = 1 << 3
|
||||
GPIO_IS_OPEN_SOURCE = 1 << 4
|
||||
GPIO_IS_BIAS_PULL_UP = 1 << 5
|
||||
GPIO_IS_BIAS_PULL_DOWN = 1 << 6
|
||||
GPIO_IS_BIAS_DISABLE = 1 << 7
|
||||
GPIO_IS_LG_INPUT = 1 << 8
|
||||
GPIO_IS_LG_OUTPUT = 1 << 9
|
||||
GPIO_IS_LG_ALERT = 1 << 10
|
||||
GPIO_IS_LG_GROUP = 1 << 11
|
||||
|
||||
GPIO_LINE_FLAGS_MASK = (
|
||||
GPIO_IS_ACTIVE_LOW | GPIO_IS_OPEN_DRAIN | GPIO_IS_OPEN_SOURCE |
|
||||
GPIO_IS_BIAS_PULL_UP | GPIO_IS_BIAS_PULL_DOWN | GPIO_IS_BIAS_DISABLE)
|
||||
|
||||
GPIO_EDGES = {
|
||||
'both': lgpio.BOTH_EDGES,
|
||||
'rising': lgpio.RISING_EDGE,
|
||||
'falling': lgpio.FALLING_EDGE,
|
||||
}
|
||||
|
||||
GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()}
|
||||
|
||||
def __init__(self, factory, number):
|
||||
super(LGPIOPin, self).__init__(factory, number)
|
||||
self._pwm = None
|
||||
self._bounce = None
|
||||
self._callback = None
|
||||
self._edges = lgpio.BOTH_EDGES
|
||||
lgpio.gpio_claim_input(
|
||||
self.factory._handle, self.number, lgpio.SET_BIAS_DISABLE)
|
||||
|
||||
def close(self):
|
||||
if self.factory._handle is not None:
|
||||
# Closing is really just "resetting" the function of the pin;
|
||||
# we let the factory close deal with actually freeing stuff
|
||||
lgpio.gpio_claim_input(
|
||||
self.factory._handle, self.number, lgpio.SET_BIAS_DISABLE)
|
||||
|
||||
def _get_function(self):
|
||||
mode = lgpio.gpio_get_mode(self.factory._handle, self.number)
|
||||
return ['input', 'output'][bool(mode & self.GPIO_IS_OUT)]
|
||||
|
||||
def _set_function(self, value):
|
||||
if self._callback is not None:
|
||||
self._callback.cancel()
|
||||
self._callback = None
|
||||
try:
|
||||
{
|
||||
'input': lgpio.gpio_claim_input,
|
||||
'output': lgpio.gpio_claim_output,
|
||||
}[value](self.factory._handle, self.number)
|
||||
except KeyError:
|
||||
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
|
||||
|
||||
def _get_state(self):
|
||||
if self._pwm:
|
||||
return self._pwm[1] / 100
|
||||
else:
|
||||
return bool(lgpio.gpio_read(self.factory._handle, self.number))
|
||||
|
||||
def _set_state(self, value):
|
||||
if self._pwm:
|
||||
freq, duty = self._pwm
|
||||
self._pwm = (freq, int(value * 100))
|
||||
try:
|
||||
lgpio.tx_pwm(self.factory._handle, self.number, *self._pwm)
|
||||
except lgpio.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:
|
||||
lgpio.gpio_write(self.factory._handle, self.number, bool(value))
|
||||
|
||||
def _get_pull(self):
|
||||
mode = lgpio.gpio_get_mode(self.factory._handle, self.number)
|
||||
if mode & self.GPIO_IS_BIAS_PULL_UP:
|
||||
return 'up'
|
||||
elif mode & self.GPIO_IS_BIAS_PULL_DOWN:
|
||||
return 'down'
|
||||
else:
|
||||
return 'floating'
|
||||
|
||||
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.factory.pi_info.pulled_up(repr(self)):
|
||||
raise PinFixedPull('%r has a physical pull-up resistor' % self)
|
||||
try:
|
||||
flags = {
|
||||
'up': lgpio.SET_BIAS_PULL_UP,
|
||||
'down': lgpio.SET_BIAS_PULL_DOWN,
|
||||
'floating': lgpio.SET_BIAS_DISABLE,
|
||||
}[value]
|
||||
except KeyError:
|
||||
raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self))
|
||||
else:
|
||||
# Simply calling gpio_claim_input is insufficient to change the
|
||||
# line flags on a pin; it needs to be freed and re-claimed
|
||||
lgpio.gpio_free(self.factory._handle, self.number)
|
||||
lgpio.gpio_claim_input(self.factory._handle, self.number, flags)
|
||||
|
||||
def _get_frequency(self):
|
||||
if self._pwm:
|
||||
freq, duty = self._pwm
|
||||
return freq
|
||||
else:
|
||||
return None
|
||||
|
||||
def _set_frequency(self, value):
|
||||
if not self._pwm and value is not None and value > 0:
|
||||
if self.function != 'output':
|
||||
raise PinPWMFixedValue('cannot start PWM on pin %r' % self)
|
||||
lgpio.tx_pwm(self.factory._handle, self.number, value, 0)
|
||||
self._pwm = (value, 0)
|
||||
elif self._pwm and value is not None and value > 0:
|
||||
freq, duty = self._pwm
|
||||
lgpio.tx_pwm(self.factory._handle, self.number, value, duty)
|
||||
self._pwm = (value, duty)
|
||||
elif self._pwm and (value is None or value == 0):
|
||||
lgpio.tx_pwm(self.factory._handle, self.number, 0, 0)
|
||||
self._pwm = None
|
||||
|
||||
def _get_bounce(self):
|
||||
return None if not self._bounce else self._bounce / 1000000
|
||||
|
||||
def _set_bounce(self, value):
|
||||
if value is None:
|
||||
value = 0
|
||||
elif value < 0:
|
||||
raise PinInvalidBounce('bounce must be 0 or greater')
|
||||
value = int(value * 1000000)
|
||||
lgpio.gpio_set_debounce_micros(self.factory._handle, self.number, value)
|
||||
self._bounce = value
|
||||
|
||||
def _get_edges(self):
|
||||
return self.GPIO_EDGES_NAMES[self._edges]
|
||||
|
||||
def _set_edges(self, value):
|
||||
f = self.when_changed
|
||||
self.when_changed = None
|
||||
try:
|
||||
self._edges = self.GPIO_EDGES[value]
|
||||
finally:
|
||||
self.when_changed = f
|
||||
|
||||
def _call_when_changed(self, chip, gpio, level, ticks):
|
||||
super(LGPIOPin, self)._call_when_changed(ticks / 1000000000, level)
|
||||
|
||||
def _enable_event_detect(self):
|
||||
lgpio.gpio_claim_alert(
|
||||
self.factory._handle, self.number, self._edges,
|
||||
lgpio.gpio_get_mode(self.factory._handle, self.number) &
|
||||
self.GPIO_LINE_FLAGS_MASK)
|
||||
self._callback = lgpio.callback(
|
||||
self.factory._handle, self.number, self._edges,
|
||||
self._call_when_changed)
|
||||
|
||||
def _disable_event_detect(self):
|
||||
if self._callback is not None:
|
||||
self._callback.cancel()
|
||||
self._callback = None
|
||||
lgpio.gpio_claim_input(
|
||||
self.factory._handle, self.number,
|
||||
lgpio.gpio_get_mode(self.factory._handle, self.number) &
|
||||
self.GPIO_LINE_FLAGS_MASK)
|
||||
|
||||
|
||||
class LGPIOHardwareSPI(SPI):
|
||||
"""
|
||||
Hardware SPI implementation for the `lgpio`_ library. Uses the ``spi_*``
|
||||
functions from the lgpio API.
|
||||
|
||||
.. _lgpio: http://abyz.me.uk/lg/py_lgpio.html
|
||||
"""
|
||||
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin, pin_factory):
|
||||
port, device = spi_port_device(
|
||||
clock_pin, mosi_pin, miso_pin, select_pin)
|
||||
self._port = port
|
||||
self._device = device
|
||||
self._baud = 500000
|
||||
self._spi_flags = 0
|
||||
self._handle = None
|
||||
super(LGPIOHardwareSPI, self).__init__(pin_factory=pin_factory)
|
||||
to_reserve = {clock_pin, select_pin}
|
||||
if mosi_pin is not None:
|
||||
to_reserve.add(mosi_pin)
|
||||
if miso_pin is not None:
|
||||
to_reserve.add(miso_pin)
|
||||
self.pin_factory.reserve_pins(self, *to_reserve)
|
||||
self._handle = lgpio.spi_open(port, device, self._baud, self._spi_flags)
|
||||
|
||||
def _conflicts_with(self, other):
|
||||
return not (
|
||||
isinstance(other, LGPIOHardwareSPI) and
|
||||
(self._port, self._device) != (other._port, other._device)
|
||||
)
|
||||
|
||||
def close(self):
|
||||
if not self.closed:
|
||||
lgpio.spi_close(self._handle)
|
||||
self._handle = None
|
||||
self.pin_factory.release_all(self)
|
||||
super(LGPIOHardwareSPI, self).close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self._handle 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 _get_clock_mode(self):
|
||||
return self._spi_flags
|
||||
|
||||
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)
|
||||
lgpio.spi_close(self._handle)
|
||||
self._spi_flags = value
|
||||
self._handle = lgpio.spi_open(
|
||||
self._port, self._device, self._baud, self._spi_flags)
|
||||
|
||||
def _get_rate(self):
|
||||
return self._baud
|
||||
|
||||
def _set_rate(self, value):
|
||||
self._check_open()
|
||||
value = int(value)
|
||||
lgpio.spi_close(self._handle)
|
||||
self._baud = value
|
||||
self._handle = lgpio.spi_open(
|
||||
self._port, self._device, self._baud, self._spi_flags)
|
||||
|
||||
def read(self, n):
|
||||
self._check_open()
|
||||
count, data = lgpio.spi_read(self._handle, n)
|
||||
if count < 0:
|
||||
raise IOError('SPI transfer error %d' % count)
|
||||
return [int(b) for b in data]
|
||||
|
||||
def write(self, data):
|
||||
self._check_open()
|
||||
count = lgpio.spi_write(self._handle, data)
|
||||
if count < 0:
|
||||
raise IOError('SPI transfer error %d' % count)
|
||||
return len(data)
|
||||
|
||||
def transfer(self, data):
|
||||
self._check_open()
|
||||
count, data = lgpio.spi_xfer(self._handle, data)
|
||||
if count < 0:
|
||||
raise IOError('SPI transfer error %d' % count)
|
||||
return [int(b) for b in data]
|
||||
|
||||
|
||||
class LGPIOHardwareSPIShared(SharedMixin, LGPIOHardwareSPI):
|
||||
@classmethod
|
||||
def _shared_key(cls, clock_pin, mosi_pin, miso_pin, select_pin, pin_factory):
|
||||
return (clock_pin, select_pin)
|
308
.venv/lib/python3.7/site-packages/gpiozero/pins/local.py
Normal file
308
.venv/lib/python3.7/site-packages/gpiozero/pins/local.py
Normal file
@@ -0,0 +1,308 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2021 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2020 Ben Nuttall <ben@bennuttall.com>
|
||||
# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
absolute_import,
|
||||
print_function,
|
||||
division,
|
||||
)
|
||||
nstr = str
|
||||
str = type('')
|
||||
|
||||
import io
|
||||
import errno
|
||||
import struct
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
from threading import Lock
|
||||
try:
|
||||
from time import monotonic
|
||||
except ImportError:
|
||||
from time import time as monotonic
|
||||
|
||||
try:
|
||||
from spidev import SpiDev
|
||||
except ImportError:
|
||||
SpiDev = None
|
||||
|
||||
from . import SPI
|
||||
from .pi import PiFactory, PiPin, SPI_HARDWARE_PINS, spi_port_device
|
||||
from .spi import SPISoftwareBus
|
||||
from ..devices import Device, SharedMixin
|
||||
from ..output_devices import OutputDevice
|
||||
from ..exc import DeviceClosed, PinUnknownPi, SPIInvalidClockMode
|
||||
|
||||
|
||||
def get_pi_revision():
|
||||
revision = None
|
||||
try:
|
||||
with io.open('/proc/device-tree/system/linux,revision', 'rb') as f:
|
||||
revision = hex(struct.unpack(nstr('>L'), f.read(4))[0])[2:]
|
||||
except IOError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
raise e
|
||||
with io.open('/proc/cpuinfo', 'r') as f:
|
||||
for line in f:
|
||||
if line.startswith('Revision'):
|
||||
revision = line.split(':')[1].strip().lower()
|
||||
if revision is not None:
|
||||
overvolted = revision.startswith('100')
|
||||
if overvolted:
|
||||
revision = revision[-4:]
|
||||
return int(revision, base=16)
|
||||
raise PinUnknownPi(
|
||||
'unable to locate Pi revision in /proc/device-tree or /proc/cpuinfo')
|
||||
|
||||
|
||||
class LocalPiFactory(PiFactory):
|
||||
"""
|
||||
Extends :class:`~gpiozero.pins.pi.PiFactory`. Abstract base class
|
||||
representing pins attached locally to a Pi. This forms the base class for
|
||||
local-only pin interfaces (:class:`~gpiozero.pins.rpigpio.RPiGPIOPin`,
|
||||
:class:`~gpiozero.pins.rpio.RPIOPin`, and
|
||||
:class:`~gpiozero.pins.native.NativePin`).
|
||||
"""
|
||||
pins = {}
|
||||
_reservations = defaultdict(list)
|
||||
_res_lock = Lock()
|
||||
|
||||
def __init__(self):
|
||||
super(LocalPiFactory, self).__init__()
|
||||
# Override the reservations and pins dict to be this class' attributes.
|
||||
# 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
|
||||
self._reservations = LocalPiFactory._reservations
|
||||
self._res_lock = LocalPiFactory._res_lock
|
||||
|
||||
def _get_revision(self):
|
||||
return get_pi_revision()
|
||||
|
||||
def _get_spi_class(self, shared, hardware):
|
||||
return {
|
||||
(False, True): LocalPiHardwareSPI,
|
||||
(True, True): LocalPiHardwareSPIShared,
|
||||
(False, False): LocalPiSoftwareSPI,
|
||||
(True, False): LocalPiSoftwareSPIShared,
|
||||
}[shared, hardware]
|
||||
|
||||
@staticmethod
|
||||
def ticks():
|
||||
return monotonic()
|
||||
|
||||
@staticmethod
|
||||
def ticks_diff(later, earlier):
|
||||
# NOTE: technically the guarantee to always return a positive result
|
||||
# cannot be maintained in versions where monotonic() is not available
|
||||
# and we fall back to time(). However, in that situation we've no
|
||||
# access to a true monotonic source, and no idea how far the clock has
|
||||
# skipped back so this is the best we can do anyway.
|
||||
return max(0, later - earlier)
|
||||
|
||||
|
||||
class LocalPiPin(PiPin):
|
||||
"""
|
||||
Extends :class:`~gpiozero.pins.pi.PiPin`. Abstract base class representing
|
||||
a multi-function GPIO pin attached to the local Raspberry Pi.
|
||||
"""
|
||||
def _call_when_changed(self, ticks=None, state=None):
|
||||
"""
|
||||
Overridden to provide default ticks from the local Pi factory.
|
||||
|
||||
.. warning::
|
||||
|
||||
The local pin factory uses a seconds-based monotonic value for
|
||||
its ticks but you *must not* rely upon this behaviour. Ticks are
|
||||
an opaque value that should only be compared with the associated
|
||||
:meth:`Factory.ticks_diff` method.
|
||||
"""
|
||||
super(LocalPiPin, self)._call_when_changed(
|
||||
self._factory.ticks() if ticks is None else ticks,
|
||||
self.state if state is None else state)
|
||||
|
||||
|
||||
class LocalPiHardwareSPI(SPI):
|
||||
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin, pin_factory):
|
||||
self._port, self._device = spi_port_device(
|
||||
clock_pin, mosi_pin, miso_pin, select_pin)
|
||||
self._interface = None
|
||||
if SpiDev is None:
|
||||
raise ImportError('failed to import spidev')
|
||||
super(LocalPiHardwareSPI, self).__init__(pin_factory=pin_factory)
|
||||
to_reserve = {clock_pin, select_pin}
|
||||
if mosi_pin is not None:
|
||||
to_reserve.add(mosi_pin)
|
||||
if miso_pin is not None:
|
||||
to_reserve.add(miso_pin)
|
||||
self.pin_factory.reserve_pins(self, *to_reserve)
|
||||
self._interface = SpiDev()
|
||||
self._interface.open(self._port, self._device)
|
||||
self._interface.max_speed_hz = 500000
|
||||
|
||||
def close(self):
|
||||
if self._interface is not None:
|
||||
self._interface.close()
|
||||
self._interface = None
|
||||
self.pin_factory.release_all(self)
|
||||
super(LocalPiHardwareSPI, self).close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self._interface 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._interface.xfer2(data)
|
||||
|
||||
def _get_clock_mode(self):
|
||||
return self._interface.mode
|
||||
|
||||
def _set_clock_mode(self, value):
|
||||
self._interface.mode = value
|
||||
|
||||
def _get_lsb_first(self):
|
||||
return self._interface.lsbfirst
|
||||
|
||||
def _set_lsb_first(self, value):
|
||||
self._interface.lsbfirst = bool(value)
|
||||
|
||||
def _get_select_high(self):
|
||||
return self._interface.cshigh
|
||||
|
||||
def _set_select_high(self, value):
|
||||
self._interface.cshigh = bool(value)
|
||||
|
||||
def _get_bits_per_word(self):
|
||||
return self._interface.bits_per_word
|
||||
|
||||
def _set_bits_per_word(self, value):
|
||||
self._interface.bits_per_word = value
|
||||
|
||||
def _get_rate(self):
|
||||
return self._interface.max_speed_hz
|
||||
|
||||
def _set_rate(self, value):
|
||||
self._interface.max_speed_hz = int(value)
|
||||
|
||||
|
||||
class LocalPiSoftwareSPI(SPI):
|
||||
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin, pin_factory):
|
||||
self._bus = None
|
||||
self._select = None
|
||||
super(LocalPiSoftwareSPI, self).__init__(pin_factory=pin_factory)
|
||||
try:
|
||||
self._clock_phase = False
|
||||
self._lsb_first = False
|
||||
self._bits_per_word = 8
|
||||
self._bus = SPISoftwareBus(clock_pin, mosi_pin, miso_pin)
|
||||
self._select = OutputDevice(
|
||||
select_pin, active_high=False, pin_factory=pin_factory)
|
||||
except:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
def _conflicts_with(self, other):
|
||||
return not (
|
||||
isinstance(other, LocalPiSoftwareSPI) and
|
||||
(self._select.pin.number != other._select.pin.number)
|
||||
)
|
||||
|
||||
def close(self):
|
||||
if self._select:
|
||||
self._select.close()
|
||||
self._select = None
|
||||
if self._bus is not None:
|
||||
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._select.pin.number)
|
||||
except DeviceClosed:
|
||||
return 'SPI(closed)'
|
||||
|
||||
def transfer(self, data):
|
||||
with self._bus.lock:
|
||||
self._select.on()
|
||||
try:
|
||||
return self._bus.transfer(
|
||||
data, self._clock_phase, self._lsb_first, self._bits_per_word)
|
||||
finally:
|
||||
self._select.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._select.active_high
|
||||
|
||||
def _set_select_high(self, value):
|
||||
with self._bus.lock:
|
||||
self._select.active_high = value
|
||||
self._select.off()
|
||||
|
||||
|
||||
class LocalPiHardwareSPIShared(SharedMixin, LocalPiHardwareSPI):
|
||||
@classmethod
|
||||
def _shared_key(cls, clock_pin, mosi_pin, miso_pin, select_pin,
|
||||
pin_factory):
|
||||
return (clock_pin, select_pin)
|
||||
|
||||
|
||||
class LocalPiSoftwareSPIShared(SharedMixin, LocalPiSoftwareSPI):
|
||||
@classmethod
|
||||
def _shared_key(cls, clock_pin, mosi_pin, miso_pin, select_pin,
|
||||
pin_factory):
|
||||
return (clock_pin, select_pin)
|
492
.venv/lib/python3.7/site-packages/gpiozero/pins/mock.py
Normal file
492
.venv/lib/python3.7/site-packages/gpiozero/pins/mock.py
Normal file
@@ -0,0 +1,492 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2021 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
absolute_import,
|
||||
print_function,
|
||||
division,
|
||||
)
|
||||
str = type('')
|
||||
|
||||
|
||||
import os
|
||||
from collections import namedtuple
|
||||
from time import time, sleep
|
||||
from threading import Thread, Event
|
||||
try:
|
||||
from math import isclose
|
||||
except ImportError:
|
||||
from ..compat import isclose
|
||||
|
||||
import pkg_resources
|
||||
|
||||
from ..exc import (
|
||||
PinPWMUnsupported,
|
||||
PinSetInput,
|
||||
PinFixedPull,
|
||||
PinInvalidFunction,
|
||||
PinInvalidPull,
|
||||
PinInvalidBounce,
|
||||
)
|
||||
from ..devices import Device
|
||||
from .local import LocalPiPin, LocalPiFactory
|
||||
|
||||
|
||||
PinState = namedtuple('PinState', ('timestamp', 'state'))
|
||||
|
||||
class MockPin(LocalPiPin):
|
||||
"""
|
||||
A mock pin used primarily for testing. This class does *not* support PWM.
|
||||
"""
|
||||
|
||||
def __init__(self, factory, number):
|
||||
super(MockPin, self).__init__(factory, number)
|
||||
self._function = 'input'
|
||||
self._pull = 'up' if self.factory.pi_info.pulled_up(repr(self)) else 'floating'
|
||||
self._state = self._pull == 'up'
|
||||
self._bounce = None
|
||||
self._edges = 'both'
|
||||
self._when_changed = None
|
||||
self.clear_states()
|
||||
|
||||
def close(self):
|
||||
self.when_changed = None
|
||||
self.function = 'input'
|
||||
|
||||
def _get_function(self):
|
||||
return self._function
|
||||
|
||||
def _set_function(self, value):
|
||||
if value not in ('input', 'output'):
|
||||
raise PinInvalidFunction('function must be input or output')
|
||||
self._function = value
|
||||
if value == 'input':
|
||||
# Drive the input to the pull
|
||||
self._set_pull(self._get_pull())
|
||||
|
||||
def _get_state(self):
|
||||
return self._state
|
||||
|
||||
def _set_state(self, value):
|
||||
if self._function == 'input':
|
||||
raise PinSetInput('cannot set state of pin %r' % self)
|
||||
assert self._function == 'output'
|
||||
assert 0 <= value <= 1
|
||||
self._change_state(bool(value))
|
||||
|
||||
def _change_state(self, value):
|
||||
if self._state != value:
|
||||
t = time()
|
||||
self._state = value
|
||||
self.states.append(PinState(t - self._last_change, value))
|
||||
self._last_change = t
|
||||
return True
|
||||
return False
|
||||
|
||||
def _get_frequency(self):
|
||||
return None
|
||||
|
||||
def _set_frequency(self, value):
|
||||
if value is not None:
|
||||
raise PinPWMUnsupported()
|
||||
|
||||
def _get_pull(self):
|
||||
return self._pull
|
||||
|
||||
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.factory.pi_info.pulled_up(repr(self)):
|
||||
raise PinFixedPull('%r has a physical pull-up resistor' % self)
|
||||
if value not in ('floating', 'up', 'down'):
|
||||
raise PinInvalidPull('pull must be floating, up, or down')
|
||||
self._pull = value
|
||||
if value == 'up':
|
||||
self.drive_high()
|
||||
elif value == 'down':
|
||||
self.drive_low()
|
||||
|
||||
def _get_bounce(self):
|
||||
return self._bounce
|
||||
|
||||
def _set_bounce(self, value):
|
||||
# XXX Need to implement this
|
||||
if value is not None:
|
||||
try:
|
||||
value = float(value)
|
||||
except ValueError:
|
||||
raise PinInvalidBounce('bounce must be None or a float')
|
||||
self._bounce = value
|
||||
|
||||
def _get_edges(self):
|
||||
return self._edges
|
||||
|
||||
def _set_edges(self, value):
|
||||
assert value in ('none', 'falling', 'rising', 'both')
|
||||
self._edges = value
|
||||
|
||||
def _disable_event_detect(self):
|
||||
pass
|
||||
|
||||
def _enable_event_detect(self):
|
||||
pass
|
||||
|
||||
def drive_high(self):
|
||||
assert self._function == 'input'
|
||||
if self._change_state(True):
|
||||
if self._edges in ('both', 'rising') and self._when_changed is not None:
|
||||
self._call_when_changed()
|
||||
|
||||
def drive_low(self):
|
||||
assert self._function == 'input'
|
||||
if self._change_state(False):
|
||||
if self._edges in ('both', 'falling') and self._when_changed is not None:
|
||||
self._call_when_changed()
|
||||
|
||||
def clear_states(self):
|
||||
self._last_change = time()
|
||||
self.states = [PinState(0.0, self._state)]
|
||||
|
||||
def assert_states(self, expected_states):
|
||||
# Tests that the pin went through the expected states (a list of values)
|
||||
for actual, expected in zip(self.states, expected_states):
|
||||
assert actual.state == expected
|
||||
|
||||
def assert_states_and_times(self, expected_states):
|
||||
# Tests that the pin went through the expected states at the expected
|
||||
# times (times are compared with a tolerance of tens-of-milliseconds as
|
||||
# that's about all we can reasonably expect in a non-realtime
|
||||
# environment on a Pi 1)
|
||||
for actual, expected in zip(self.states, expected_states):
|
||||
assert isclose(actual.timestamp, expected[0], rel_tol=0.05, abs_tol=0.05)
|
||||
assert isclose(actual.state, expected[1])
|
||||
|
||||
|
||||
class MockConnectedPin(MockPin):
|
||||
"""
|
||||
This derivative of :class:`MockPin` emulates a pin connected to another
|
||||
mock pin. This is used in the "real pins" portion of the test suite to
|
||||
check that one pin can influence another.
|
||||
"""
|
||||
def __init__(self, factory, number, input_pin=None):
|
||||
super(MockConnectedPin, self).__init__(factory, number)
|
||||
self.input_pin = input_pin
|
||||
|
||||
def _change_state(self, value):
|
||||
if self.input_pin:
|
||||
if value:
|
||||
self.input_pin.drive_high()
|
||||
else:
|
||||
self.input_pin.drive_low()
|
||||
return super(MockConnectedPin, self)._change_state(value)
|
||||
|
||||
|
||||
class MockChargingPin(MockPin):
|
||||
"""
|
||||
This derivative of :class:`MockPin` emulates a pin which, when set to
|
||||
input, waits a predetermined length of time and then drives itself high
|
||||
(as if attached to, e.g. a typical circuit using an LDR and a capacitor
|
||||
to time the charging rate).
|
||||
"""
|
||||
def __init__(self, factory, number, charge_time=0.01):
|
||||
super(MockChargingPin, self).__init__(factory, number)
|
||||
self.charge_time = charge_time # dark charging time
|
||||
self._charge_stop = Event()
|
||||
self._charge_thread = None
|
||||
|
||||
def _set_function(self, value):
|
||||
super(MockChargingPin, self)._set_function(value)
|
||||
if value == 'input':
|
||||
if self._charge_thread:
|
||||
self._charge_stop.set()
|
||||
self._charge_thread.join()
|
||||
self._charge_stop.clear()
|
||||
self._charge_thread = Thread(target=self._charge)
|
||||
self._charge_thread.start()
|
||||
elif value == 'output':
|
||||
if self._charge_thread:
|
||||
self._charge_stop.set()
|
||||
self._charge_thread.join()
|
||||
else:
|
||||
assert False
|
||||
|
||||
def _charge(self):
|
||||
if not self._charge_stop.wait(self.charge_time):
|
||||
try:
|
||||
self.drive_high()
|
||||
except AssertionError: # pragma: no cover
|
||||
# Charging pins are typically flipped between input and output
|
||||
# repeatedly; if another thread has already flipped us to
|
||||
# output ignore the assertion-error resulting from attempting
|
||||
# to drive the pin high
|
||||
pass
|
||||
|
||||
|
||||
class MockTriggerPin(MockPin):
|
||||
"""
|
||||
This derivative of :class:`MockPin` is intended to be used with another
|
||||
:class:`MockPin` to emulate a distance sensor. Set *echo_pin* to the
|
||||
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, factory, number, echo_pin=None, echo_time=0.04):
|
||||
super(MockTriggerPin, self).__init__(factory, number)
|
||||
self.echo_pin = echo_pin
|
||||
self.echo_time = echo_time # longest echo time
|
||||
self._echo_thread = None
|
||||
|
||||
def _set_state(self, value):
|
||||
super(MockTriggerPin, self)._set_state(value)
|
||||
if value:
|
||||
if self._echo_thread:
|
||||
self._echo_thread.join()
|
||||
self._echo_thread = Thread(target=self._echo)
|
||||
self._echo_thread.start()
|
||||
|
||||
def _echo(self):
|
||||
sleep(0.001)
|
||||
self.echo_pin.drive_high()
|
||||
sleep(self.echo_time)
|
||||
self.echo_pin.drive_low()
|
||||
|
||||
|
||||
class MockPWMPin(MockPin):
|
||||
"""
|
||||
This derivative of :class:`MockPin` adds PWM support.
|
||||
"""
|
||||
def __init__(self, factory, number):
|
||||
super(MockPWMPin, self).__init__(factory, number)
|
||||
self._frequency = None
|
||||
|
||||
def close(self):
|
||||
self.frequency = None
|
||||
super(MockPWMPin, self).close()
|
||||
|
||||
def _set_state(self, value):
|
||||
if self._function == 'input':
|
||||
raise PinSetInput('cannot set state of pin %r' % self)
|
||||
assert self._function == 'output'
|
||||
assert 0 <= value <= 1
|
||||
self._change_state(float(value))
|
||||
|
||||
def _get_frequency(self):
|
||||
return self._frequency
|
||||
|
||||
def _set_frequency(self, value):
|
||||
if value is not None:
|
||||
assert self._function == 'output'
|
||||
self._frequency = value
|
||||
if value is None:
|
||||
self._change_state(0.0)
|
||||
|
||||
|
||||
class MockSPIClockPin(MockPin):
|
||||
"""
|
||||
This derivative of :class:`MockPin` is intended to be used as the clock pin
|
||||
of a mock SPI device. It is not intended for direct construction in tests;
|
||||
rather, construct a :class:`MockSPIDevice` with various pin numbers, and
|
||||
this class will be used for the clock pin.
|
||||
"""
|
||||
def __init__(self, factory, number):
|
||||
super(MockSPIClockPin, self).__init__(factory, number)
|
||||
self.spi_devices = getattr(self, 'spi_devices', [])
|
||||
|
||||
def _set_state(self, value):
|
||||
super(MockSPIClockPin, self)._set_state(value)
|
||||
for dev in self.spi_devices:
|
||||
dev.on_clock()
|
||||
|
||||
|
||||
class MockSPISelectPin(MockPin):
|
||||
"""
|
||||
This derivative of :class:`MockPin` is intended to be used as the select
|
||||
pin of a mock SPI device. It is not intended for direct construction in
|
||||
tests; rather, construct a :class:`MockSPIDevice` with various pin numbers,
|
||||
and this class will be used for the select pin.
|
||||
"""
|
||||
def __init__(self, factory, number):
|
||||
super(MockSPISelectPin, self).__init__(factory, number)
|
||||
self.spi_device = getattr(self, 'spi_device', None)
|
||||
|
||||
def _set_state(self, value):
|
||||
super(MockSPISelectPin, self)._set_state(value)
|
||||
if self.spi_device:
|
||||
self.spi_device.on_select()
|
||||
|
||||
|
||||
class MockSPIDevice(object):
|
||||
def __init__(
|
||||
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 = 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
|
||||
self.bits_per_word = bits_per_word
|
||||
self.select_high = select_high
|
||||
self.rx_bit = 0
|
||||
self.rx_buf = []
|
||||
self.tx_buf = []
|
||||
self.clock_pin.spi_devices.append(self)
|
||||
self.select_pin.spi_device = self
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_tb):
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
if self in self.clock_pin.spi_devices:
|
||||
self.clock_pin.spi_devices.remove(self)
|
||||
if self.select_pin is not None:
|
||||
self.select_pin.spi_device = None
|
||||
|
||||
def on_select(self):
|
||||
if self.select_pin.state == self.select_high:
|
||||
self.on_start()
|
||||
|
||||
def on_clock(self):
|
||||
# Don't do anything if this SPI device isn't currently selected
|
||||
if self.select_pin is None or self.select_pin.state == self.select_high:
|
||||
# The XOR of the clock pin's values, polarity and phase indicates
|
||||
# whether we're meant to be acting on this edge
|
||||
if self.clock_pin.state ^ self.clock_polarity ^ self.clock_phase:
|
||||
self.rx_bit += 1
|
||||
if self.mosi_pin is not None:
|
||||
self.rx_buf.append(self.mosi_pin.state)
|
||||
if self.miso_pin is not None:
|
||||
try:
|
||||
tx_value = self.tx_buf.pop(0)
|
||||
except IndexError:
|
||||
tx_value = 0
|
||||
if tx_value:
|
||||
self.miso_pin.drive_high()
|
||||
else:
|
||||
self.miso_pin.drive_low()
|
||||
self.on_bit()
|
||||
|
||||
def on_start(self):
|
||||
"""
|
||||
Override this in descendents to detect when the mock SPI device's
|
||||
select line is activated.
|
||||
"""
|
||||
self.rx_bit = 0
|
||||
self.rx_buf = []
|
||||
self.tx_buf = []
|
||||
|
||||
def on_bit(self):
|
||||
"""
|
||||
Override this in descendents to react to receiving a bit.
|
||||
|
||||
The :attr:`rx_bit` attribute gives the index of the bit received (this
|
||||
is reset to 0 by default by :meth:`on_select`). The :attr:`rx_buf`
|
||||
sequence gives the sequence of 1s and 0s that have been recevied so
|
||||
far. The :attr:`tx_buf` sequence gives the sequence of 1s and 0s to
|
||||
transmit on the next clock pulses. All these attributes can be modified
|
||||
within this method.
|
||||
|
||||
The :meth:`rx_word` and :meth:`tx_word` methods can also be used to
|
||||
read and append to the buffers using integers instead of bool bits.
|
||||
"""
|
||||
pass
|
||||
|
||||
def rx_word(self):
|
||||
result = 0
|
||||
bits = reversed(self.rx_buf) if self.lsb_first else self.rx_buf
|
||||
for bit in bits:
|
||||
result <<= 1
|
||||
result |= bit
|
||||
return result
|
||||
|
||||
def tx_word(self, value, bits_per_word=None):
|
||||
if bits_per_word is None:
|
||||
bits_per_word = self.bits_per_word
|
||||
bits = [0] * bits_per_word
|
||||
for bit in range(bits_per_word):
|
||||
bits[bit] = value & 1
|
||||
value >>= 1
|
||||
assert not value
|
||||
if not self.lsb_first:
|
||||
bits = reversed(bits)
|
||||
self.tx_buf.extend(bits)
|
||||
|
||||
|
||||
class MockFactory(LocalPiFactory):
|
||||
"""
|
||||
Factory for generating mock pins. The *revision* parameter specifies what
|
||||
revision of Pi the mock factory pretends to be (this affects the result of
|
||||
the :attr:`~gpiozero.Factory.pi_info` attribute as well as where pull-ups
|
||||
are assumed to be). The *pin_class* attribute specifies which mock pin
|
||||
class will be generated by the :meth:`pin` method by default. This can be
|
||||
changed after construction by modifying the :attr:`pin_class` attribute.
|
||||
|
||||
.. attribute:: pin_class
|
||||
|
||||
This attribute stores the :class:`MockPin` class (or descendent) that
|
||||
will be used when constructing pins with the :meth:`pin` method (if
|
||||
no *pin_class* parameter is used to override it). It defaults on
|
||||
construction to the value of the *pin_class* parameter in the
|
||||
constructor, or :class:`MockPin` if that is unspecified.
|
||||
"""
|
||||
def __init__(self, revision=None, pin_class=None):
|
||||
super(MockFactory, self).__init__()
|
||||
if revision is None:
|
||||
revision = os.environ.get('GPIOZERO_MOCK_REVISION', 'a02082')
|
||||
if pin_class is None:
|
||||
pin_class = os.environ.get('GPIOZERO_MOCK_PIN_CLASS', MockPin)
|
||||
self._revision = int(revision, base=16)
|
||||
if isinstance(pin_class, bytes):
|
||||
pin_class = pin_class.decode('ascii')
|
||||
if isinstance(pin_class, str):
|
||||
dist = pkg_resources.get_distribution('gpiozero')
|
||||
group = 'gpiozero_mock_pin_classes'
|
||||
pin_class = pkg_resources.load_entry_point(dist, group, pin_class.lower())
|
||||
if not issubclass(pin_class, MockPin):
|
||||
raise ValueError('invalid mock pin_class: %r' % pin_class)
|
||||
self.pin_class = pin_class
|
||||
|
||||
def _get_revision(self):
|
||||
return self._revision
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Clears the pins and reservations sets. This is primarily useful in
|
||||
test suites to ensure the pin factory is back in a "clean" state before
|
||||
the next set of tests are run.
|
||||
"""
|
||||
self.pins.clear()
|
||||
self._reservations.clear()
|
||||
|
||||
def pin(self, spec, pin_class=None, **kwargs):
|
||||
"""
|
||||
The pin method for :class:`MockFactory` additionally takes a *pin_class*
|
||||
attribute which can be used to override the class' :attr:`pin_class`
|
||||
attribute. Any additional keyword arguments will be passed along to the
|
||||
pin constructor (useful with things like :class:`MockConnectedPin` which
|
||||
expect to be constructed with another pin).
|
||||
"""
|
||||
if pin_class is None:
|
||||
pin_class = self.pin_class
|
||||
n = self.pi_info.to_gpio(spec)
|
||||
try:
|
||||
pin = self.pins[n]
|
||||
except KeyError:
|
||||
pin = pin_class(self, n, **kwargs)
|
||||
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
|
633
.venv/lib/python3.7/site-packages/gpiozero/pins/native.py
Normal file
633
.venv/lib/python3.7/site-packages/gpiozero/pins/native.py
Normal file
@@ -0,0 +1,633 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2015-2021 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2016-2020 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
absolute_import,
|
||||
print_function,
|
||||
division,
|
||||
)
|
||||
try:
|
||||
range = xrange
|
||||
except NameError:
|
||||
pass
|
||||
nstr = str
|
||||
str = type('')
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import mmap
|
||||
import errno
|
||||
import struct
|
||||
import select
|
||||
import warnings
|
||||
from time import sleep
|
||||
from threading import Thread, Event, RLock
|
||||
from collections import Counter
|
||||
try:
|
||||
from queue import Queue, Empty
|
||||
except ImportError:
|
||||
from Queue import Queue, Empty
|
||||
|
||||
from .local import LocalPiPin, LocalPiFactory
|
||||
from ..exc import (
|
||||
PinInvalidPull,
|
||||
PinInvalidEdges,
|
||||
PinInvalidFunction,
|
||||
PinFixedPull,
|
||||
PinSetInput,
|
||||
)
|
||||
|
||||
|
||||
def dt_resolve_alias(alias, root='/proc/device-tree'):
|
||||
"""
|
||||
Returns the full path of a device-tree alias. For example:
|
||||
|
||||
>>> dt_resolve_alias('gpio')
|
||||
'/proc/device-tree/soc/gpio@7e200000'
|
||||
>>> dt_resolve_alias('ethernet0', root='/proc/device-tree')
|
||||
'/proc/device-tree/scb/ethernet@7d580000'
|
||||
"""
|
||||
# XXX Change this return a pathlib.Path when we drop 2.x
|
||||
filename = os.path.join(root, 'aliases', alias)
|
||||
with io.open(filename, 'rb') as f:
|
||||
node, tail = f.read().split(b'\0', 1)
|
||||
fs_encoding = sys.getfilesystemencoding()
|
||||
return os.path.join(root, node.decode(fs_encoding).lstrip('/'))
|
||||
|
||||
def dt_peripheral_reg(node, root='/proc/device-tree'):
|
||||
"""
|
||||
Returns the :class:`range` covering the registers of the specified *node*
|
||||
of the device-tree, mapped to the CPU's address space. For example:
|
||||
|
||||
>>> reg = dt_peripheral_reg(dt_resolve_alias('gpio'))
|
||||
>>> '%#x..%#x' % (reg.start, reg.stop)
|
||||
'0xfe200000..0xfe2000b4'
|
||||
>>> hex(dt_peripheral_reg(dt_resolve_alias('ethernet0')).start)
|
||||
'0xfd580000'
|
||||
"""
|
||||
# Returns a tuple of (address-cells, size-cells) for *node*
|
||||
def _cells(node):
|
||||
with io.open(os.path.join(node, '#address-cells'), 'rb') as f:
|
||||
address_cells = struct.unpack(nstr('>L'), f.read())[0]
|
||||
with io.open(os.path.join(node, '#size-cells'), 'rb') as f:
|
||||
size_cells = struct.unpack(nstr('>L'), f.read())[0]
|
||||
return (address_cells, size_cells)
|
||||
|
||||
# Returns a generator function which, given a file-like object *source*
|
||||
# iteratively decodes it, yielding a tuple of values from it. Each tuple
|
||||
# contains one integer for each specified *length*, which is the number of
|
||||
# 32-bit device-tree cells that make up that value.
|
||||
def _reader(*lengths):
|
||||
structs = [struct.Struct(nstr('>{cells}L'.format(cells=cells)))
|
||||
for cells in lengths]
|
||||
offsets = [sum(s.size for s in structs[:i])
|
||||
for i in range(len(structs))]
|
||||
buf_len = sum(s.size for s in structs)
|
||||
|
||||
def fn(source):
|
||||
while True:
|
||||
buf = source.read(buf_len)
|
||||
if not buf:
|
||||
break
|
||||
elif len(buf) < buf_len:
|
||||
raise IOError('failed to read {buf_len} bytes'.format(
|
||||
buf_len=buf_len))
|
||||
row = ()
|
||||
for offset, s in zip(offsets, structs):
|
||||
cells = s.unpack_from(buf, offset)
|
||||
value = 0
|
||||
for cell in cells:
|
||||
value = (value << 32) | cell
|
||||
row += (value,)
|
||||
yield row
|
||||
return fn
|
||||
|
||||
# Returns a list of (child-range, parent-range) tuples for *node*
|
||||
def _ranges(node):
|
||||
child_cells, size_cells = _cells(node)
|
||||
parent = os.path.dirname(node)
|
||||
parent_cells, _ = _cells(parent)
|
||||
ranges_reader = _reader(child_cells, parent_cells, size_cells)
|
||||
with io.open(os.path.join(node, 'ranges'), 'rb') as f:
|
||||
return [
|
||||
(range(child_base, child_base + size),
|
||||
range(parent_base, parent_base + size))
|
||||
for child_base, parent_base, size in ranges_reader(f)
|
||||
]
|
||||
|
||||
# XXX Replace all this gubbins with pathlib.Path stuff once we drop 2.x
|
||||
node = os.path.join(root, node)
|
||||
parent = os.path.dirname(node)
|
||||
child_cells, size_cells = _cells(parent)
|
||||
reg_reader = _reader(child_cells, size_cells)
|
||||
with io.open(os.path.join(node, 'reg'), 'rb') as f:
|
||||
base, size = list(reg_reader(f))[0]
|
||||
while parent != root:
|
||||
# Iterate up the hierarchy, resolving the base address as we go
|
||||
if os.path.exists(os.path.join(parent, 'ranges')):
|
||||
for child_range, parent_range in _ranges(parent):
|
||||
if base in child_range:
|
||||
# XXX Can't use .start here as python2's crappy xrange
|
||||
# lacks it; change this when we drop 2.x!
|
||||
base += parent_range[0] - child_range[0]
|
||||
break
|
||||
parent = os.path.dirname(parent)
|
||||
return range(base, base + size)
|
||||
|
||||
|
||||
class GPIOMemory(object):
|
||||
GPIO_BASE_OFFSET = 0x200000
|
||||
PERI_BASE_OFFSET = {
|
||||
'BCM2835': 0x20000000,
|
||||
'BCM2836': 0x3f000000,
|
||||
'BCM2837': 0x3f000000,
|
||||
'BCM2711': 0xfe000000,
|
||||
}
|
||||
|
||||
# From BCM2835 data-sheet, p.91
|
||||
GPFSEL_OFFSET = 0x00 >> 2
|
||||
GPSET_OFFSET = 0x1c >> 2
|
||||
GPCLR_OFFSET = 0x28 >> 2
|
||||
GPLEV_OFFSET = 0x34 >> 2
|
||||
GPEDS_OFFSET = 0x40 >> 2
|
||||
GPREN_OFFSET = 0x4c >> 2
|
||||
GPFEN_OFFSET = 0x58 >> 2
|
||||
GPHEN_OFFSET = 0x64 >> 2
|
||||
GPLEN_OFFSET = 0x70 >> 2
|
||||
GPAREN_OFFSET = 0x7c >> 2
|
||||
GPAFEN_OFFSET = 0x88 >> 2
|
||||
GPPUD_OFFSET = 0x94 >> 2
|
||||
GPPUDCLK_OFFSET = 0x98 >> 2
|
||||
# pull-control registers for BCM2711
|
||||
GPPUPPDN_OFFSET = 0xe4 >> 2
|
||||
|
||||
def __init__(self, soc):
|
||||
try:
|
||||
self.fd = os.open('/dev/gpiomem', os.O_RDWR | os.O_SYNC)
|
||||
except OSError:
|
||||
try:
|
||||
self.fd = os.open('/dev/mem', os.O_RDWR | os.O_SYNC)
|
||||
except OSError:
|
||||
raise IOError(
|
||||
'unable to open /dev/gpiomem or /dev/mem; '
|
||||
'upgrade your kernel or run as root')
|
||||
else:
|
||||
offset = self.gpio_base(soc)
|
||||
else:
|
||||
offset = 0
|
||||
self.mem = mmap.mmap(self.fd, 4096, offset=offset)
|
||||
# Register reads and writes must be in native format (otherwise
|
||||
# struct resorts to individual byte reads/writes and you can't hit
|
||||
# half a register :). For arm64 compat we have to figure out what the
|
||||
# native unsigned 32-bit type is...
|
||||
try:
|
||||
self.reg_fmt = {
|
||||
struct.calcsize(fmt): fmt
|
||||
for fmt in (nstr('@I'), nstr('@L'))
|
||||
}[4]
|
||||
except KeyError:
|
||||
raise RuntimeError('unable to find native unsigned 32-bit type')
|
||||
|
||||
def close(self):
|
||||
self.mem.close()
|
||||
os.close(self.fd)
|
||||
|
||||
def gpio_base(self, soc):
|
||||
try:
|
||||
# XXX Replace this with .start when 2.x is dropped
|
||||
return dt_peripheral_reg(dt_resolve_alias('gpio'))[0]
|
||||
except IOError:
|
||||
try:
|
||||
return self.PERI_BASE_OFFSET[soc] + self.GPIO_BASE_OFFSET
|
||||
except KeyError:
|
||||
pass
|
||||
raise IOError('unable to determine gpio base')
|
||||
|
||||
def __getitem__(self, index):
|
||||
return struct.unpack_from(self.reg_fmt, self.mem, index * 4)[0]
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
struct.pack_into(self.reg_fmt, self.mem, index * 4, value)
|
||||
|
||||
|
||||
class GPIOFS(object):
|
||||
GPIO_PATH = '/sys/class/gpio'
|
||||
|
||||
def __init__(self, factory, queue):
|
||||
self._lock = RLock()
|
||||
self._exports = {}
|
||||
self._thread = NativeWatchThread(factory, queue)
|
||||
|
||||
def close(self):
|
||||
# We *could* track the stuff we've exported and unexport it here, but
|
||||
# exports are a system global resource. We can't guarantee that some
|
||||
# other process isn't relying on something we've exported. In other
|
||||
# words, once exported it's *never* safe to unexport something. The
|
||||
# unexport method below is largely provided for debugging and testing.
|
||||
if self._thread is not None:
|
||||
self._thread.close()
|
||||
self._thread = None
|
||||
|
||||
def path(self, name):
|
||||
return os.path.join(self.GPIO_PATH, name)
|
||||
|
||||
def path_value(self, pin):
|
||||
return self.path('gpio%d/value' % pin)
|
||||
|
||||
def path_dir(self, pin):
|
||||
return self.path('gpio%d/direction' % pin)
|
||||
|
||||
def path_edge(self, pin):
|
||||
return self.path('gpio%d/edge' % pin)
|
||||
|
||||
def exported(self, pin):
|
||||
return pin in self._exports
|
||||
|
||||
def export(self, pin):
|
||||
with self._lock:
|
||||
try:
|
||||
result = self._exports[pin]
|
||||
except KeyError:
|
||||
result = None
|
||||
# Dirty hack to wait for udev to set permissions on
|
||||
# gpioN/value; there's no other way around this as there's no
|
||||
# synchronous mechanism for setting permissions on sysfs
|
||||
for i in range(10):
|
||||
try:
|
||||
# Must be O_NONBLOCK for use with epoll in edge
|
||||
# triggered mode
|
||||
result = os.open(self.path_value(pin),
|
||||
os.O_RDONLY | os.O_NONBLOCK)
|
||||
except IOError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
with io.open(self.path('export'), 'wb') as f:
|
||||
f.write(str(pin).encode('ascii'))
|
||||
elif e.errno == errno.EACCES:
|
||||
sleep(i / 100)
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
self._exports[pin] = result
|
||||
break
|
||||
# Same for gpioN/edge. It must exist by this point but the
|
||||
# chmod -R may not have reached it yet...
|
||||
for i in range(10):
|
||||
try:
|
||||
with io.open(self.path_edge(pin), 'w+b'):
|
||||
pass
|
||||
except IOError as e:
|
||||
if e.errno == errno.EACCES:
|
||||
sleep(i / 100)
|
||||
else:
|
||||
raise
|
||||
if result is None:
|
||||
raise RuntimeError('failed to export pin %d' % pin)
|
||||
return result
|
||||
|
||||
def unexport(self, pin):
|
||||
with self._lock:
|
||||
try:
|
||||
os.close(self._exports.pop(pin))
|
||||
except KeyError:
|
||||
# unexport should be idempotent
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
with io.open(self.path('unexport'), 'wb') as f:
|
||||
f.write(str(pin).encode('ascii'))
|
||||
except IOError as e:
|
||||
if e.errno == errno.EINVAL:
|
||||
# Someone already unexported it; ignore the error
|
||||
pass
|
||||
|
||||
def watch(self, pin):
|
||||
with self._lock:
|
||||
self._thread.watch(self.export(pin), pin)
|
||||
|
||||
def unwatch(self, pin):
|
||||
with self._lock:
|
||||
try:
|
||||
self._thread.unwatch(self._exports[pin])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
class NativeWatchThread(Thread):
|
||||
def __init__(self, factory, queue):
|
||||
super(NativeWatchThread, self).__init__(
|
||||
target=self._run, args=(factory, queue))
|
||||
self.daemon = True
|
||||
self._stop_evt = Event()
|
||||
# XXX Make this compatible with BSDs with poll() option?
|
||||
self._epoll = select.epoll()
|
||||
self._watches = {}
|
||||
self.start()
|
||||
|
||||
def close(self):
|
||||
self._stop_evt.set()
|
||||
self.join()
|
||||
self._epoll.close()
|
||||
|
||||
def watch(self, fd, pin):
|
||||
self._watches[fd] = pin
|
||||
flags = select.EPOLLIN | select.EPOLLPRI | select.EPOLLET
|
||||
self._epoll.register(fd, flags)
|
||||
|
||||
def unwatch(self, fd):
|
||||
self._epoll.unregister(fd)
|
||||
fd = self._watches.pop(fd, None)
|
||||
|
||||
def _run(self, factory, queue):
|
||||
ticks = factory.ticks
|
||||
while not self._stop_evt.wait(0):
|
||||
for fd, event in self._epoll.poll(0.01):
|
||||
when = ticks()
|
||||
state = os.read(fd, 1) == b'1'
|
||||
os.lseek(fd, 0, 0)
|
||||
try:
|
||||
queue.put((self._watches[fd], when, state))
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
class NativeDispatchThread(Thread):
|
||||
def __init__(self, factory, queue):
|
||||
super(NativeDispatchThread, self).__init__(
|
||||
target=self._run, args=(factory, queue))
|
||||
self.daemon = True
|
||||
self._stop_evt = Event()
|
||||
self.start()
|
||||
|
||||
def close(self):
|
||||
self._stop_evt.set()
|
||||
self.join()
|
||||
|
||||
def _run(self, factory, queue):
|
||||
pins = factory.pins
|
||||
while not self._stop_evt.wait(0):
|
||||
try:
|
||||
num, ticks, state = queue.get(timeout=0.1)
|
||||
except Empty:
|
||||
continue
|
||||
try:
|
||||
pin = pins[num]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if (
|
||||
pin._bounce is None or pin._last_call is None or
|
||||
factory.ticks_diff(ticks, pin._last_call) > pin._bounce
|
||||
):
|
||||
pin._call_when_changed(ticks, state)
|
||||
pin._last_call = ticks
|
||||
|
||||
|
||||
class NativeFactory(LocalPiFactory):
|
||||
"""
|
||||
Extends :class:`~gpiozero.pins.local.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 are discovered.
|
||||
|
||||
.. warning::
|
||||
|
||||
This implementation does *not* currently support PWM. Attempting to
|
||||
use any class which requests PWM will raise an exception.
|
||||
|
||||
You can construct native pin instances manually like so::
|
||||
|
||||
from gpiozero.pins.native import NativeFactory
|
||||
from gpiozero import LED
|
||||
|
||||
factory = NativeFactory()
|
||||
led = LED(12, pin_factory=factory)
|
||||
"""
|
||||
def __init__(self):
|
||||
super(NativeFactory, self).__init__()
|
||||
queue = Queue()
|
||||
self.mem = GPIOMemory(self.pi_info.soc)
|
||||
self.fs = GPIOFS(self, queue)
|
||||
self.dispatch = NativeDispatchThread(self, queue)
|
||||
if self.pi_info.soc == 'BCM2711':
|
||||
self.pin_class = Native2711Pin
|
||||
else:
|
||||
self.pin_class = Native2835Pin
|
||||
|
||||
def close(self):
|
||||
if self.dispatch is not None:
|
||||
self.dispatch.close()
|
||||
self.dispatch = None
|
||||
super(NativeFactory, self).close()
|
||||
if self.fs is not None:
|
||||
self.fs.close()
|
||||
self.fs = None
|
||||
if self.mem is not None:
|
||||
self.mem.close()
|
||||
self.mem = None
|
||||
|
||||
|
||||
class NativePin(LocalPiPin):
|
||||
"""
|
||||
Extends :class:`~gpiozero.pins.local.LocalPiPin`. Native pin
|
||||
implementation. See :class:`NativeFactory` for more information.
|
||||
"""
|
||||
GPIO_FUNCTIONS = {
|
||||
'input': 0b000,
|
||||
'output': 0b001,
|
||||
'alt0': 0b100,
|
||||
'alt1': 0b101,
|
||||
'alt2': 0b110,
|
||||
'alt3': 0b111,
|
||||
'alt4': 0b011,
|
||||
'alt5': 0b010,
|
||||
}
|
||||
|
||||
GPIO_FUNCTION_NAMES = {v: k for (k, v) in GPIO_FUNCTIONS.items()}
|
||||
|
||||
def __init__(self, factory, number):
|
||||
super(NativePin, self).__init__(factory, number)
|
||||
self._reg_init(factory, number)
|
||||
self._last_call = None
|
||||
self._when_changed = None
|
||||
self._change_thread = None
|
||||
self._change_event = Event()
|
||||
self.function = 'input'
|
||||
self.pull = 'up' if self.factory.pi_info.pulled_up(repr(self)) else 'floating'
|
||||
self.bounce = None
|
||||
self.edges = 'none'
|
||||
|
||||
def _reg_init(self, 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._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
|
||||
|
||||
def close(self):
|
||||
self.edges = 'none'
|
||||
self.frequency = None
|
||||
self.when_changed = None
|
||||
self.function = 'input'
|
||||
self.pull = 'up' if self.factory.pi_info.pulled_up(repr(self)) else 'floating'
|
||||
|
||||
def _get_function(self):
|
||||
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.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.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.factory.mem[self._set_offset] = 1 << self._set_shift
|
||||
else:
|
||||
self.factory.mem[self._clear_offset] = 1 << self._clear_shift
|
||||
|
||||
def _get_pull(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def _set_pull(self, value):
|
||||
raise NotImplementedError
|
||||
|
||||
def _get_bounce(self):
|
||||
return self._bounce
|
||||
|
||||
def _set_bounce(self, value):
|
||||
self._bounce = None if value is None else float(value)
|
||||
|
||||
def _get_edges(self):
|
||||
try:
|
||||
with io.open(self.factory.fs.path_edge(self.number), 'r') as f:
|
||||
return f.read().strip()
|
||||
except IOError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
return 'none'
|
||||
else:
|
||||
raise
|
||||
|
||||
def _set_edges(self, value):
|
||||
if value != 'none':
|
||||
self.factory.fs.export(self.number)
|
||||
try:
|
||||
with io.open(self.factory.fs.path_edge(self.number), 'w') as f:
|
||||
f.write(value)
|
||||
except IOError as e:
|
||||
if e.errno == errno.ENOENT and value == 'none':
|
||||
pass
|
||||
elif e.errno == errno.EINVAL:
|
||||
raise PinInvalidEdges('invalid edge specification "%s" for pin %r' % self)
|
||||
else:
|
||||
raise
|
||||
|
||||
def _enable_event_detect(self):
|
||||
self.factory.fs.watch(self.number)
|
||||
self._last_call = None
|
||||
|
||||
def _disable_event_detect(self):
|
||||
self.factory.fs.unwatch(self.number)
|
||||
|
||||
|
||||
class Native2835Pin(NativePin):
|
||||
"""
|
||||
Extends :class:`NativePin` for Pi hardware prior to the Pi 4 (Pi 0, 1, 2,
|
||||
3, and 3+).
|
||||
"""
|
||||
GPIO_PULL_UPS = {
|
||||
'up': 0b10,
|
||||
'down': 0b01,
|
||||
'floating': 0b00,
|
||||
}
|
||||
|
||||
GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()}
|
||||
|
||||
def _reg_init(self, factory, number):
|
||||
super(Native2835Pin, self)._reg_init(factory, number)
|
||||
self._pull_offset = self.factory.mem.GPPUDCLK_OFFSET + (number // 32)
|
||||
self._pull_shift = number % 32
|
||||
self._pull = 'floating'
|
||||
|
||||
def _get_pull(self):
|
||||
return self.GPIO_PULL_UP_NAMES[self._pull]
|
||||
|
||||
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.factory.pi_info.pulled_up(repr(self)):
|
||||
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.factory.mem[self.factory.mem.GPPUD_OFFSET] = value
|
||||
sleep(0.000000214)
|
||||
self.factory.mem[self._pull_offset] = 1 << self._pull_shift
|
||||
sleep(0.000000214)
|
||||
self.factory.mem[self.factory.mem.GPPUD_OFFSET] = 0
|
||||
self.factory.mem[self._pull_offset] = 0
|
||||
self._pull = value
|
||||
|
||||
|
||||
class Native2711Pin(NativePin):
|
||||
"""
|
||||
Extends :class:`NativePin` for Pi 4 hardware (Pi 4, CM4, Pi 400 at the time
|
||||
of writing).
|
||||
"""
|
||||
GPIO_PULL_UPS = {
|
||||
'up': 0b01,
|
||||
'down': 0b10,
|
||||
'floating': 0b00,
|
||||
}
|
||||
|
||||
GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()}
|
||||
|
||||
def _reg_init(self, factory, number):
|
||||
super(Native2711Pin, self)._reg_init(factory, number)
|
||||
self._pull_offset = self.factory.mem.GPPUPPDN_OFFSET + (number // 16)
|
||||
self._pull_shift = (number % 16) * 2
|
||||
|
||||
def _get_pull(self):
|
||||
pull = (self.factory.mem[self._pull_offset] >> self._pull_shift) & 3
|
||||
return self.GPIO_PULL_UP_NAMES[pull]
|
||||
|
||||
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.factory.pi_info.pulled_up(repr(self)):
|
||||
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.factory.mem[self._pull_offset] = (
|
||||
self.factory.mem[self._pull_offset]
|
||||
& ~(3 << self._pull_shift)
|
||||
| (value << self._pull_shift)
|
||||
)
|
334
.venv/lib/python3.7/site-packages/gpiozero/pins/pi.py
Normal file
334
.venv/lib/python3.7/site-packages/gpiozero/pins/pi.py
Normal file
@@ -0,0 +1,334 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2021 Dave Jones <dave@waveform.org.uk>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
absolute_import,
|
||||
print_function,
|
||||
division,
|
||||
)
|
||||
str = type('')
|
||||
|
||||
import io
|
||||
from threading import RLock, Lock
|
||||
from types import MethodType
|
||||
from collections import defaultdict
|
||||
try:
|
||||
from weakref import ref, WeakMethod
|
||||
except ImportError:
|
||||
|
||||
from ..compat import WeakMethod
|
||||
import warnings
|
||||
|
||||
try:
|
||||
from spidev import SpiDev
|
||||
except ImportError:
|
||||
SpiDev = None
|
||||
|
||||
from . import Factory, Pin
|
||||
from .data import PiBoardInfo
|
||||
from ..exc import (
|
||||
PinNoPins,
|
||||
PinNonPhysical,
|
||||
PinInvalidPin,
|
||||
SPIBadArgs,
|
||||
SPISoftwareFallback,
|
||||
)
|
||||
|
||||
|
||||
SPI_HARDWARE_PINS = {
|
||||
0: {
|
||||
'clock': 11,
|
||||
'mosi': 10,
|
||||
'miso': 9,
|
||||
'select': (8, 7),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def spi_port_device(clock_pin, mosi_pin, miso_pin, select_pin):
|
||||
"""
|
||||
Convert a mapping of pin definitions, which must contain 'clock_pin', and
|
||||
'select_pin' at a minimum, to a hardware SPI port, device tuple. Raises
|
||||
:exc:`~gpiozero.SPIBadArgs` if the pins do not represent a valid hardware
|
||||
SPI device.
|
||||
"""
|
||||
for port, pins in SPI_HARDWARE_PINS.items():
|
||||
if all((
|
||||
clock_pin == pins['clock'],
|
||||
mosi_pin in (None, pins['mosi']),
|
||||
miso_pin in (None, pins['miso']),
|
||||
select_pin in pins['select'],
|
||||
)):
|
||||
device = pins['select'].index(select_pin)
|
||||
return (port, device)
|
||||
raise SPIBadArgs('invalid pin selection for hardware SPI')
|
||||
|
||||
|
||||
class PiFactory(Factory):
|
||||
"""
|
||||
Extends :class:`~gpiozero.Factory`. Abstract base class representing
|
||||
hardware attached to a Raspberry Pi. This forms the base of
|
||||
:class:`~gpiozero.pins.local.LocalPiFactory`.
|
||||
"""
|
||||
def __init__(self):
|
||||
super(PiFactory, self).__init__()
|
||||
self._info = None
|
||||
self.pins = {}
|
||||
self.pin_class = None
|
||||
|
||||
def close(self):
|
||||
for pin in self.pins.values():
|
||||
pin.close()
|
||||
self.pins.clear()
|
||||
|
||||
def reserve_pins(self, requester, *pins):
|
||||
super(PiFactory, self).reserve_pins(
|
||||
requester, *(self.pi_info.to_gpio(pin) for pin in pins))
|
||||
|
||||
def release_pins(self, reserver, *pins):
|
||||
super(PiFactory, self).release_pins(
|
||||
reserver, *(self.pi_info.to_gpio(pin) for pin in pins))
|
||||
|
||||
def pin(self, spec):
|
||||
n = self.pi_info.to_gpio(spec)
|
||||
try:
|
||||
pin = self.pins[n]
|
||||
except KeyError:
|
||||
pin = self.pin_class(self, n)
|
||||
self.pins[n] = pin
|
||||
return pin
|
||||
|
||||
def _get_revision(self):
|
||||
"""
|
||||
This method must be overridden by descendents to return the Pi's
|
||||
revision code as an :class:`int`. The default is unimplemented.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _get_pi_info(self):
|
||||
if self._info is None:
|
||||
self._info = PiBoardInfo.from_revision(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:`~gpiozero.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 hardware based interface (using
|
||||
spidev) will be returned. Otherwise, a software based interface 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 simpler
|
||||
devices this doesn't matter).
|
||||
"""
|
||||
spi_args, kwargs = self._extract_spi_args(**spi_args)
|
||||
shared = bool(kwargs.pop('shared', False))
|
||||
if kwargs:
|
||||
raise SPIBadArgs(
|
||||
'unrecognized keyword argument %s' % kwargs.popitem()[0])
|
||||
try:
|
||||
port, device = spi_port_device(**spi_args)
|
||||
except SPIBadArgs:
|
||||
# Assume request is for a software SPI implementation
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
return self._get_spi_class(shared, hardware=True)(
|
||||
pin_factory=self, **spi_args)
|
||||
except Exception as e:
|
||||
warnings.warn(
|
||||
SPISoftwareFallback(
|
||||
'failed to initialize hardware SPI, falling back to '
|
||||
'software (error was: %s)' % str(e)))
|
||||
return self._get_spi_class(shared, hardware=False)(
|
||||
pin_factory=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)``.
|
||||
"""
|
||||
dev_defaults = {
|
||||
'port': 0,
|
||||
'device': 0,
|
||||
}
|
||||
default_hw = SPI_HARDWARE_PINS[dev_defaults['port']]
|
||||
pin_defaults = {
|
||||
'clock_pin': default_hw['clock'],
|
||||
'mosi_pin': default_hw['mosi'],
|
||||
'miso_pin': default_hw['miso'],
|
||||
'select_pin': default_hw['select'][dev_defaults['device']],
|
||||
}
|
||||
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: None if spi_args.get(key, default) is None else
|
||||
self.pi_info.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()
|
||||
}
|
||||
try:
|
||||
selected_hw = SPI_HARDWARE_PINS[spi_args['port']]
|
||||
except KeyError:
|
||||
raise SPIBadArgs(
|
||||
'port %d is not a valid SPI port' % spi_args['port'])
|
||||
try:
|
||||
selected_hw['select'][spi_args['device']]
|
||||
except IndexError:
|
||||
raise SPIBadArgs(
|
||||
'device must be in the range 0..%d' %
|
||||
len(selected_hw['select']))
|
||||
spi_args = {
|
||||
key: value if key != 'select_pin' else selected_hw['select'][spi_args['device']]
|
||||
for key, value in pin_defaults.items()
|
||||
}
|
||||
else:
|
||||
raise SPIBadArgs(
|
||||
'you must either specify port and device, or clock_pin, '
|
||||
'mosi_pin, miso_pin, and select_pin; combinations of the two '
|
||||
'schemes (e.g. port and clock_pin) are not permitted')
|
||||
return spi_args, kwargs
|
||||
|
||||
def _get_spi_class(self, shared, hardware):
|
||||
"""
|
||||
Returns a sub-class of the :class:`SPI` which can be constructed with
|
||||
*clock_pin*, *mosi_pin*, *miso_pin*, and *select_pin* arguments. The
|
||||
*shared* argument dictates whether the returned class uses the
|
||||
:class:`SharedMixin` to permit sharing instances between components,
|
||||
while *hardware* indicates whether the returned class uses the kernel's
|
||||
SPI device(s) rather than a bit-banged software implementation.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class PiPin(Pin):
|
||||
"""
|
||||
Extends :class:`~gpiozero.Pin`. Abstract base class representing a
|
||||
multi-function GPIO pin attached to a Raspberry Pi. Descendents *must*
|
||||
override the following methods:
|
||||
|
||||
* :meth:`_get_function`
|
||||
* :meth:`_set_function`
|
||||
* :meth:`_get_state`
|
||||
* :meth:`_call_when_changed`
|
||||
* :meth:`_enable_event_detect`
|
||||
* :meth:`_disable_event_detect`
|
||||
|
||||
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`
|
||||
"""
|
||||
def __init__(self, factory, number):
|
||||
super(PiPin, self).__init__()
|
||||
self._factory = factory
|
||||
self._when_changed_lock = RLock()
|
||||
self._when_changed = None
|
||||
self._number = number
|
||||
try:
|
||||
factory.pi_info.physical_pin(repr(self))
|
||||
except PinNoPins:
|
||||
warnings.warn(
|
||||
PinNonPhysical(
|
||||
'no physical pins exist for %s' % repr(self)))
|
||||
|
||||
@property
|
||||
def number(self):
|
||||
return self._number
|
||||
|
||||
def __repr__(self):
|
||||
return 'GPIO%d' % self._number
|
||||
|
||||
@property
|
||||
def factory(self):
|
||||
return self._factory
|
||||
|
||||
def _call_when_changed(self, ticks, state):
|
||||
"""
|
||||
Called to fire the :attr:`when_changed` event handler; override this
|
||||
in descendents if additional (currently redundant) parameters need
|
||||
to be passed.
|
||||
"""
|
||||
method = self._when_changed()
|
||||
if method is None:
|
||||
self.when_changed = None
|
||||
else:
|
||||
method(ticks, state)
|
||||
|
||||
def _get_when_changed(self):
|
||||
return None if self._when_changed is None else self._when_changed()
|
||||
|
||||
def _set_when_changed(self, value):
|
||||
with self._when_changed_lock:
|
||||
if value is None:
|
||||
if self._when_changed is not None:
|
||||
self._disable_event_detect()
|
||||
self._when_changed = None
|
||||
else:
|
||||
enabled = self._when_changed is not None
|
||||
# Have to take care, if value is either a closure or a bound
|
||||
# method, not to keep a strong reference to the containing
|
||||
# object
|
||||
if isinstance(value, MethodType):
|
||||
self._when_changed = WeakMethod(value)
|
||||
else:
|
||||
self._when_changed = ref(value)
|
||||
if not enabled:
|
||||
self._enable_event_detect()
|
||||
|
||||
def _enable_event_detect(self):
|
||||
"""
|
||||
Enables event detection. This is called to activate event detection on
|
||||
pin :attr:`number`, watching for the specified :attr:`edges`. In
|
||||
response, :meth:`_call_when_changed` should be executed.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _disable_event_detect(self):
|
||||
"""
|
||||
Disables event detection. This is called to deactivate event detection
|
||||
on pin :attr:`number`.
|
||||
"""
|
||||
raise NotImplementedError
|
601
.venv/lib/python3.7/site-packages/gpiozero/pins/pigpio.py
Normal file
601
.venv/lib/python3.7/site-packages/gpiozero/pins/pigpio.py
Normal file
@@ -0,0 +1,601 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2021 Kyle Morgan <kyle@knmorgan.net>
|
||||
# Copyright (c) 2016-2021 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2020 Ben Nuttall <ben@bennuttall.com>
|
||||
# Copyright (c) 2019 Maksim Levental <maksim.levental@gmail.com>
|
||||
# Copyright (c) 2019 Aaron Rogers <aaron.kyle.rogers@gmail.com>
|
||||
# Copyright (c) 2016 BuildTools <david.glaude@gmail.com>
|
||||
# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
absolute_import,
|
||||
print_function,
|
||||
division,
|
||||
)
|
||||
str = type('')
|
||||
|
||||
import os
|
||||
|
||||
import pigpio
|
||||
|
||||
from . import SPI
|
||||
from .pi import PiPin, PiFactory, spi_port_device
|
||||
from ..mixins import SharedMixin
|
||||
from ..exc import (
|
||||
PinInvalidFunction,
|
||||
PinSetInput,
|
||||
PinFixedPull,
|
||||
PinInvalidPull,
|
||||
PinInvalidBounce,
|
||||
PinInvalidState,
|
||||
SPIBadArgs,
|
||||
SPIInvalidClockMode,
|
||||
PinPWMFixedValue,
|
||||
DeviceClosed
|
||||
)
|
||||
|
||||
|
||||
class PiGPIOFactory(PiFactory):
|
||||
"""
|
||||
Extends :class:`~gpiozero.pins.pi.PiFactory`. Uses the `pigpio`_ library to
|
||||
interface to the Pi's GPIO pins. The pigpio library relies on a daemon
|
||||
(:command:`pigpiod`) to be running as root to provide access to the GPIO
|
||||
pins, and communicates with this daemon over a network socket.
|
||||
|
||||
While this does mean only the daemon itself should control the pins, the
|
||||
architecture does have several advantages:
|
||||
|
||||
* Pins can be remote controlled from another machine (the other
|
||||
machine doesn't even have to be a Raspberry Pi; it simply needs the
|
||||
`pigpio`_ client library installed on it)
|
||||
* The daemon supports hardware PWM via the DMA controller
|
||||
* Your script itself doesn't require root privileges; it just needs to
|
||||
be able to communicate with the daemon
|
||||
|
||||
You can construct pigpio pins manually like so::
|
||||
|
||||
from gpiozero.pins.pigpio import PiGPIOFactory
|
||||
from gpiozero import LED
|
||||
|
||||
factory = PiGPIOFactory()
|
||||
led = LED(12, pin_factory=factory)
|
||||
|
||||
This is particularly useful for controlling pins on a remote machine. To
|
||||
accomplish this simply specify the host (and optionally port) when
|
||||
constructing the pin::
|
||||
|
||||
from gpiozero.pins.pigpio import PiGPIOFactory
|
||||
from gpiozero import LED
|
||||
|
||||
factory = PiGPIOFactory(host='192.168.0.2')
|
||||
led = LED(12, pin_factory=factory)
|
||||
|
||||
.. note::
|
||||
|
||||
In some circumstances, especially when playing with PWM, it does appear
|
||||
to be possible to get the daemon into "unusual" states. We would be
|
||||
most interested to hear any bug reports relating to this (it may be a
|
||||
bug in our pin implementation). A workaround for now is simply to
|
||||
restart the :command:`pigpiod` daemon.
|
||||
|
||||
.. _pigpio: http://abyz.me.uk/rpi/pigpio/
|
||||
"""
|
||||
def __init__(self, host=None, port=None):
|
||||
super(PiGPIOFactory, self).__init__()
|
||||
if host is None:
|
||||
host = os.environ.get('PIGPIO_ADDR', 'localhost')
|
||||
if port is None:
|
||||
# XXX Use getservbyname
|
||||
port = int(os.environ.get('PIGPIO_PORT', 8888))
|
||||
self.pin_class = PiGPIOPin
|
||||
self._connection = pigpio.pi(host, port)
|
||||
# Annoyingly, pigpio doesn't raise an exception when it fails to make a
|
||||
# connection; it returns a valid (but disconnected) pi object
|
||||
if self.connection is None:
|
||||
raise IOError('failed to connect to %s:%s' % (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_spi_class(self, shared, hardware):
|
||||
return {
|
||||
(False, True): PiGPIOHardwareSPI,
|
||||
(True, True): PiGPIOHardwareSPIShared,
|
||||
(False, False): PiGPIOSoftwareSPI,
|
||||
(True, False): PiGPIOSoftwareSPIShared,
|
||||
}[shared, hardware]
|
||||
|
||||
def spi(self, **spi_args):
|
||||
intf = super(PiGPIOFactory, self).spi(**spi_args)
|
||||
self._spis.append(intf)
|
||||
return intf
|
||||
|
||||
def ticks(self):
|
||||
return self._connection.get_current_tick()
|
||||
|
||||
@staticmethod
|
||||
def ticks_diff(later, earlier):
|
||||
# NOTE: pigpio ticks are unsigned 32-bit quantities that wrap every
|
||||
# 71.6 minutes. The modulo below (oh the joys of having an *actual*
|
||||
# modulo operator, unlike C's remainder) ensures the result is valid
|
||||
# even when later < earlier due to wrap-around (assuming the duration
|
||||
# measured is not longer than the period)
|
||||
return ((later - earlier) % 0x100000000) / 1000000
|
||||
|
||||
|
||||
class PiGPIOPin(PiPin):
|
||||
"""
|
||||
Extends :class:`~gpiozero.pins.pi.PiPin`. Pin implementation for the
|
||||
`pigpio`_ library. See :class:`PiGPIOFactory` for more information.
|
||||
|
||||
.. _pigpio: http://abyz.me.uk/rpi/pigpio/
|
||||
"""
|
||||
GPIO_FUNCTIONS = {
|
||||
'input': pigpio.INPUT,
|
||||
'output': pigpio.OUTPUT,
|
||||
'alt0': pigpio.ALT0,
|
||||
'alt1': pigpio.ALT1,
|
||||
'alt2': pigpio.ALT2,
|
||||
'alt3': pigpio.ALT3,
|
||||
'alt4': pigpio.ALT4,
|
||||
'alt5': pigpio.ALT5,
|
||||
}
|
||||
|
||||
GPIO_PULL_UPS = {
|
||||
'up': pigpio.PUD_UP,
|
||||
'down': pigpio.PUD_DOWN,
|
||||
'floating': pigpio.PUD_OFF,
|
||||
}
|
||||
|
||||
GPIO_EDGES = {
|
||||
'both': pigpio.EITHER_EDGE,
|
||||
'rising': pigpio.RISING_EDGE,
|
||||
'falling': pigpio.FALLING_EDGE,
|
||||
}
|
||||
|
||||
GPIO_FUNCTION_NAMES = {v: k for (k, v) in GPIO_FUNCTIONS.items()}
|
||||
GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()}
|
||||
GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()}
|
||||
|
||||
def __init__(self, factory, number):
|
||||
super(PiGPIOPin, self).__init__(factory, number)
|
||||
self._pull = 'up' if self.factory.pi_info.pulled_up(repr(self)) else 'floating'
|
||||
self._pwm = False
|
||||
self._bounce = None
|
||||
self._callback = None
|
||||
self._edges = pigpio.EITHER_EDGE
|
||||
try:
|
||||
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 self.factory.connection:
|
||||
self.frequency = None
|
||||
self.when_changed = None
|
||||
self.function = 'input'
|
||||
self.pull = 'up' if self.factory.pi_info.pulled_up(repr(self)) else 'floating'
|
||||
|
||||
def _get_function(self):
|
||||
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.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.factory.connection.get_PWM_dutycycle(self.number) /
|
||||
self.factory.connection.get_PWM_range(self.number)
|
||||
)
|
||||
else:
|
||||
return bool(self.factory.connection.read(self.number))
|
||||
|
||||
def _set_state(self, value):
|
||||
if self._pwm:
|
||||
try:
|
||||
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.factory.connection.write(self.number, bool(value))
|
||||
|
||||
def _get_pull(self):
|
||||
return self._pull
|
||||
|
||||
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.factory.pi_info.pulled_up(repr(self)):
|
||||
raise PinFixedPull('%r has a physical pull-up resistor' % self)
|
||||
try:
|
||||
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.factory.connection.get_PWM_frequency(self.number)
|
||||
return None
|
||||
|
||||
def _set_frequency(self, value):
|
||||
if not self._pwm and value is not None:
|
||||
if self.function != 'output':
|
||||
raise PinPWMFixedValue('cannot start PWM on pin %r' % self)
|
||||
# NOTE: the pin's state *must* be set to zero; if it's currently
|
||||
# high, starting PWM and setting a 0 duty-cycle *doesn't* bring
|
||||
# the pin low; it stays high!
|
||||
self.factory.connection.write(self.number, 0)
|
||||
self.factory.connection.set_PWM_frequency(self.number, int(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.factory.connection.get_PWM_frequency(self.number):
|
||||
self.factory.connection.set_PWM_frequency(self.number, int(value))
|
||||
self.factory.connection.set_PWM_range(self.number, 10000)
|
||||
elif self._pwm and value is None:
|
||||
self.factory.connection.write(self.number, 0)
|
||||
self._pwm = False
|
||||
|
||||
def _get_bounce(self):
|
||||
return None if not self._bounce else self._bounce / 1000000
|
||||
|
||||
def _set_bounce(self, value):
|
||||
if value is None:
|
||||
value = 0
|
||||
elif not 0 <= value <= 0.3:
|
||||
raise PinInvalidBounce('bounce must be between 0 and 0.3')
|
||||
self.factory.connection.set_glitch_filter(self.number, int(value * 1000000))
|
||||
|
||||
def _get_edges(self):
|
||||
return self.GPIO_EDGES_NAMES[self._edges]
|
||||
|
||||
def _set_edges(self, value):
|
||||
f = self.when_changed
|
||||
self.when_changed = None
|
||||
try:
|
||||
self._edges = self.GPIO_EDGES[value]
|
||||
finally:
|
||||
self.when_changed = f
|
||||
|
||||
def _call_when_changed(self, gpio, level, ticks):
|
||||
super(PiGPIOPin, self)._call_when_changed(ticks, level)
|
||||
|
||||
def _enable_event_detect(self):
|
||||
self._callback = self.factory.connection.callback(
|
||||
self.number, self._edges, self._call_when_changed)
|
||||
|
||||
def _disable_event_detect(self):
|
||||
if self._callback is not None:
|
||||
self._callback.cancel()
|
||||
self._callback = None
|
||||
|
||||
|
||||
class PiGPIOHardwareSPI(SPI):
|
||||
"""
|
||||
Hardware SPI implementation for the `pigpio`_ library. Uses the ``spi_*``
|
||||
functions from the pigpio API.
|
||||
|
||||
.. _pigpio: http://abyz.me.uk/rpi/pigpio/
|
||||
"""
|
||||
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin, pin_factory):
|
||||
port, device = spi_port_device(
|
||||
clock_pin, mosi_pin, miso_pin, select_pin)
|
||||
self._port = port
|
||||
self._device = device
|
||||
self._handle = None
|
||||
super(PiGPIOHardwareSPI, self).__init__(pin_factory=pin_factory)
|
||||
to_reserve = {clock_pin, select_pin}
|
||||
if mosi_pin is not None:
|
||||
to_reserve.add(mosi_pin)
|
||||
if miso_pin is not None:
|
||||
to_reserve.add(miso_pin)
|
||||
self.pin_factory.reserve_pins(self, *to_reserve)
|
||||
self._spi_flags = (8 << 16) | (port << 8)
|
||||
self._baud = 500000
|
||||
self._handle = self.pin_factory.connection.spi_open(
|
||||
device, self._baud, self._spi_flags)
|
||||
|
||||
def _conflicts_with(self, other):
|
||||
return not (
|
||||
isinstance(other, PiGPIOHardwareSPI) and
|
||||
(self.pin_factory.host, self._port, self._device) !=
|
||||
(other.pin_factory.host, other._port, other._device)
|
||||
)
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
self.pin_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.pin_factory.connection.spi_close(self._handle)
|
||||
self._handle = None
|
||||
self.pin_factory.release_all(self)
|
||||
super(PiGPIOHardwareSPI, self).close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self._handle is None or self.pin_factory.connection 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 _get_clock_mode(self):
|
||||
return self._spi_flags & 0x3
|
||||
|
||||
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.pin_factory.connection.spi_close(self._handle)
|
||||
self._spi_flags = (self._spi_flags & ~0x3) | value
|
||||
self._handle = self.pin_factory.connection.spi_open(
|
||||
self._device, self._baud, self._spi_flags)
|
||||
|
||||
def _get_select_high(self):
|
||||
return bool((self._spi_flags >> (2 + self._device)) & 0x1)
|
||||
|
||||
def _set_select_high(self, value):
|
||||
self._check_open()
|
||||
self.pin_factory.connection.spi_close(self._handle)
|
||||
self._spi_flags = (self._spi_flags & ~0x1c) | (bool(value) << (2 + self._device))
|
||||
self._handle = self.pin_factory.connection.spi_open(
|
||||
self._device, self._baud, self._spi_flags)
|
||||
|
||||
def _get_bits_per_word(self):
|
||||
return (self._spi_flags >> 16) & 0x3f
|
||||
|
||||
def _set_bits_per_word(self, value):
|
||||
self._check_open()
|
||||
self.pin_factory.connection.spi_close(self._handle)
|
||||
self._spi_flags = (self._spi_flags & ~0x3f0000) | ((value & 0x3f) << 16)
|
||||
self._handle = self.pin_factory.connection.spi_open(
|
||||
self._device, self._baud, self._spi_flags)
|
||||
|
||||
def _get_rate(self):
|
||||
return self._baud
|
||||
|
||||
def _set_rate(self, value):
|
||||
self._check_open()
|
||||
value = int(value)
|
||||
self.pin_factory.connection.spi_close(self._handle)
|
||||
self._baud = value
|
||||
self._handle = self.pin_factory.connection.spi_open(
|
||||
self._device, self._baud, self._spi_flags)
|
||||
|
||||
def _get_lsb_first(self):
|
||||
return bool((self._spi_flags >> 14) & 0x1) if self._port else False
|
||||
|
||||
def _set_lsb_first(self, value):
|
||||
if self._port:
|
||||
self._check_open()
|
||||
self.pin_factory.connection.spi_close(self._handle)
|
||||
self._spi_flags = (
|
||||
(self._spi_flags & ~0xc000)
|
||||
| (bool(value) << 14)
|
||||
| (bool(value) << 15)
|
||||
)
|
||||
self._handle = self.pin_factory.connection.spi_open(
|
||||
self._device, self._baud, self._spi_flags)
|
||||
else:
|
||||
super(PiGPIOHardwareSPI, self)._set_lsb_first(value)
|
||||
|
||||
def transfer(self, data):
|
||||
self._check_open()
|
||||
count, data = self.pin_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):
|
||||
"""
|
||||
Software SPI implementation for the `pigpio`_ library. Uses the ``bb_spi_*``
|
||||
functions from the pigpio API.
|
||||
|
||||
.. _pigpio: http://abyz.me.uk/rpi/pigpio/
|
||||
"""
|
||||
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin, pin_factory):
|
||||
self._closed = True
|
||||
self._select_pin = select_pin
|
||||
self._clock_pin = clock_pin
|
||||
self._mosi_pin = mosi_pin
|
||||
self._miso_pin = miso_pin
|
||||
super(PiGPIOSoftwareSPI, self).__init__(pin_factory=pin_factory)
|
||||
# Can't "unreserve" MOSI/MISO on this implementation
|
||||
self.pin_factory.reserve_pins(
|
||||
self,
|
||||
clock_pin,
|
||||
mosi_pin,
|
||||
miso_pin,
|
||||
select_pin,
|
||||
)
|
||||
self._spi_flags = 0
|
||||
self._baud = 100000
|
||||
try:
|
||||
self.pin_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._closed = False
|
||||
except:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
def _conflicts_with(self, other):
|
||||
return not (
|
||||
isinstance(other, PiGPIOSoftwareSPI) and
|
||||
(self._select_pin) != (other._select_pin)
|
||||
)
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
self.pin_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 and self.pin_factory.connection:
|
||||
self._closed = True
|
||||
self.pin_factory.connection.bb_spi_close(self._select_pin)
|
||||
self.pin_factory.release_all(self)
|
||||
super(PiGPIOSoftwareSPI, self).close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self._closed
|
||||
|
||||
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._spi_flags & 0x3
|
||||
|
||||
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.pin_factory.connection.bb_spi_close(self._select_pin)
|
||||
self._spi_flags = (self._spi_flags & ~0x3) | value
|
||||
self.pin_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 bool(self._spi_flags & 0x4)
|
||||
|
||||
def _set_select_high(self, value):
|
||||
self._check_open()
|
||||
self.pin_factory.connection.bb_spi_close(self._select_pin)
|
||||
self._spi_flags = (self._spi_flags & ~0x4) | (bool(value) << 2)
|
||||
self.pin_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 bool(self._spi_flags & 0xc000)
|
||||
|
||||
def _set_lsb_first(self, value):
|
||||
self._check_open()
|
||||
self.pin_factory.connection.bb_spi_close(self._select_pin)
|
||||
self._spi_flags = (
|
||||
(self._spi_flags & ~0xc000)
|
||||
| (bool(value) << 14)
|
||||
| (bool(value) << 15)
|
||||
)
|
||||
self.pin_factory.connection.bb_spi_open(
|
||||
self._select_pin, self._miso_pin, self._mosi_pin, self._clock_pin,
|
||||
self._baud, self._spi_flags)
|
||||
|
||||
def _get_rate(self):
|
||||
return self._baud
|
||||
|
||||
def _set_rate(self, value):
|
||||
self._check_open()
|
||||
value = int(value)
|
||||
self.pin_factory.connection.bb_spi_close(self._select_pin)
|
||||
self._baud = value
|
||||
self.pin_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.pin_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, clock_pin, mosi_pin, miso_pin, select_pin, pin_factory):
|
||||
return (pin_factory.host, clock_pin, select_pin)
|
||||
|
||||
|
||||
class PiGPIOSoftwareSPIShared(SharedMixin, PiGPIOSoftwareSPI):
|
||||
@classmethod
|
||||
def _shared_key(cls, clock_pin, mosi_pin, miso_pin, select_pin, pin_factory):
|
||||
return (pin_factory.host, clock_pin, select_pin)
|
232
.venv/lib/python3.7/site-packages/gpiozero/pins/rpigpio.py
Normal file
232
.venv/lib/python3.7/site-packages/gpiozero/pins/rpigpio.py
Normal file
@@ -0,0 +1,232 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2015-2021 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
absolute_import,
|
||||
print_function,
|
||||
division,
|
||||
)
|
||||
str = type('')
|
||||
|
||||
import warnings
|
||||
|
||||
from RPi import GPIO
|
||||
|
||||
from .local import LocalPiFactory, LocalPiPin
|
||||
from ..exc import (
|
||||
PinInvalidFunction,
|
||||
PinSetInput,
|
||||
PinFixedPull,
|
||||
PinInvalidPull,
|
||||
PinInvalidState,
|
||||
PinInvalidBounce,
|
||||
PinPWMFixedValue,
|
||||
)
|
||||
|
||||
|
||||
class RPiGPIOFactory(LocalPiFactory):
|
||||
"""
|
||||
Extends :class:`~gpiozero.pins.local.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. Supports all features
|
||||
including PWM (via software).
|
||||
|
||||
Because this is the default pin implementation you can use it simply by
|
||||
specifying an integer number for the pin in most operations, e.g.::
|
||||
|
||||
from gpiozero import LED
|
||||
|
||||
led = LED(12)
|
||||
|
||||
However, you can also construct RPi.GPIO pins manually if you wish::
|
||||
|
||||
from gpiozero.pins.rpigpio import RPiGPIOFactory
|
||||
from gpiozero import LED
|
||||
|
||||
factory = RPiGPIOFactory()
|
||||
led = LED(12, pin_factory=factory)
|
||||
|
||||
.. _RPi.GPIO: https://pypi.python.org/pypi/RPi.GPIO
|
||||
"""
|
||||
|
||||
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):
|
||||
"""
|
||||
Extends :class:`~gpiozero.pins.local.LocalPiPin`. Pin implementation for
|
||||
the `RPi.GPIO`_ library. See :class:`RPiGPIOFactory` for more information.
|
||||
|
||||
.. _RPi.GPIO: https://pypi.python.org/pypi/RPi.GPIO
|
||||
"""
|
||||
GPIO_FUNCTIONS = {
|
||||
'input': GPIO.IN,
|
||||
'output': GPIO.OUT,
|
||||
'i2c': GPIO.I2C,
|
||||
'spi': GPIO.SPI,
|
||||
'pwm': GPIO.HARD_PWM,
|
||||
'serial': GPIO.SERIAL,
|
||||
'unknown': GPIO.UNKNOWN,
|
||||
}
|
||||
|
||||
GPIO_PULL_UPS = {
|
||||
'up': GPIO.PUD_UP,
|
||||
'down': GPIO.PUD_DOWN,
|
||||
'floating': GPIO.PUD_OFF,
|
||||
}
|
||||
|
||||
GPIO_EDGES = {
|
||||
'both': GPIO.BOTH,
|
||||
'rising': GPIO.RISING,
|
||||
'falling': GPIO.FALLING,
|
||||
}
|
||||
|
||||
GPIO_FUNCTION_NAMES = {v: k for (k, v) in GPIO_FUNCTIONS.items()}
|
||||
GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()}
|
||||
GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()}
|
||||
|
||||
def __init__(self, factory, number):
|
||||
super(RPiGPIOPin, self).__init__(factory, number)
|
||||
self._pull = 'up' if self.factory.pi_info.pulled_up(repr(self)) else 'floating'
|
||||
self._pwm = None
|
||||
self._frequency = None
|
||||
self._duty_cycle = None
|
||||
self._bounce = -666
|
||||
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)
|
||||
|
||||
def output_with_state(self, state):
|
||||
self._pull = 'floating'
|
||||
GPIO.setup(self.number, GPIO.OUT, initial=state)
|
||||
|
||||
def input_with_pull(self, pull):
|
||||
if pull != 'up' and self.factory.pi_info.pulled_up(repr(self)):
|
||||
raise PinFixedPull('%r has a physical pull-up resistor' % self)
|
||||
try:
|
||||
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)]
|
||||
|
||||
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])
|
||||
else:
|
||||
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
|
||||
|
||||
def _get_state(self):
|
||||
if self._pwm:
|
||||
return self._duty_cycle
|
||||
else:
|
||||
return GPIO.input(self.number)
|
||||
|
||||
def _set_state(self, value):
|
||||
if self._pwm:
|
||||
try:
|
||||
self._pwm.ChangeDutyCycle(value * 100)
|
||||
except ValueError:
|
||||
raise PinInvalidState('invalid state "%s" for pin %r' % (value, self))
|
||||
self._duty_cycle = value
|
||||
else:
|
||||
try:
|
||||
GPIO.output(self.number, value)
|
||||
except ValueError:
|
||||
raise PinInvalidState('invalid state "%s" for pin %r' % (value, self))
|
||||
except RuntimeError:
|
||||
raise PinSetInput('cannot set state of pin %r' % self)
|
||||
|
||||
def _get_pull(self):
|
||||
return self._pull
|
||||
|
||||
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.factory.pi_info.pulled_up(repr(self)):
|
||||
raise PinFixedPull('%r has a physical pull-up resistor' % self)
|
||||
try:
|
||||
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))
|
||||
|
||||
def _get_frequency(self):
|
||||
return self._frequency
|
||||
|
||||
def _set_frequency(self, value):
|
||||
if self._frequency is None and value is not None:
|
||||
try:
|
||||
self._pwm = GPIO.PWM(self.number, value)
|
||||
except RuntimeError:
|
||||
raise PinPWMFixedValue('cannot start PWM on pin %r' % self)
|
||||
self._pwm.start(0)
|
||||
self._duty_cycle = 0
|
||||
self._frequency = value
|
||||
elif self._frequency is not None and value is not None:
|
||||
self._pwm.ChangeFrequency(value)
|
||||
self._frequency = value
|
||||
elif self._frequency is not None and value is None:
|
||||
self._pwm.stop()
|
||||
self._pwm = None
|
||||
self._duty_cycle = None
|
||||
self._frequency = None
|
||||
|
||||
def _get_bounce(self):
|
||||
return None if self._bounce == -666 else (self._bounce / 1000)
|
||||
|
||||
def _set_bounce(self, value):
|
||||
if value is not None and value < 0:
|
||||
raise PinInvalidBounce('bounce must be 0 or greater')
|
||||
f = self.when_changed
|
||||
self.when_changed = None
|
||||
try:
|
||||
self._bounce = -666 if value is None else int(value * 1000)
|
||||
finally:
|
||||
self.when_changed = f
|
||||
|
||||
def _get_edges(self):
|
||||
return self.GPIO_EDGES_NAMES[self._edges]
|
||||
|
||||
def _set_edges(self, value):
|
||||
f = self.when_changed
|
||||
self.when_changed = None
|
||||
try:
|
||||
self._edges = self.GPIO_EDGES[value]
|
||||
finally:
|
||||
self.when_changed = f
|
||||
|
||||
def _call_when_changed(self, channel):
|
||||
super(RPiGPIOPin, self)._call_when_changed()
|
||||
|
||||
def _enable_event_detect(self):
|
||||
GPIO.add_event_detect(
|
||||
self.number, self._edges,
|
||||
callback=self._call_when_changed,
|
||||
bouncetime=self._bounce)
|
||||
|
||||
def _disable_event_detect(self):
|
||||
GPIO.remove_event_detect(self.number)
|
226
.venv/lib/python3.7/site-packages/gpiozero/pins/rpio.py
Normal file
226
.venv/lib/python3.7/site-packages/gpiozero/pins/rpio.py
Normal file
@@ -0,0 +1,226 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2015-2021 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
absolute_import,
|
||||
print_function,
|
||||
division,
|
||||
)
|
||||
str = type('')
|
||||
|
||||
|
||||
import warnings
|
||||
|
||||
import RPIO
|
||||
import RPIO.PWM
|
||||
from RPIO.Exceptions import InvalidChannelException
|
||||
|
||||
from .local import LocalPiPin, LocalPiFactory
|
||||
from .data import pi_info
|
||||
from ..exc import (
|
||||
PinInvalidFunction,
|
||||
PinSetInput,
|
||||
PinFixedPull,
|
||||
PinInvalidPull,
|
||||
PinInvalidBounce,
|
||||
PinInvalidState,
|
||||
PinPWMError,
|
||||
)
|
||||
|
||||
|
||||
class RPIOFactory(LocalPiFactory):
|
||||
"""
|
||||
Extends :class:`~gpiozero.pins.local.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, but RPIO is.
|
||||
Supports all features including PWM (hardware via DMA).
|
||||
|
||||
.. note::
|
||||
|
||||
Please note that at the time of writing, RPIO is only compatible with
|
||||
Pi 1's; the Raspberry Pi 2 Model B is *not* supported. Also note that
|
||||
root access is required so scripts must typically be run with ``sudo``.
|
||||
|
||||
You can construct RPIO pins manually like so::
|
||||
|
||||
from gpiozero.pins.rpio import RPIOFactory
|
||||
from gpiozero import LED
|
||||
|
||||
factory = RPIOFactory()
|
||||
led = LED(12, pin_factory=factory)
|
||||
|
||||
.. _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
|
||||
|
||||
def close(self):
|
||||
RPIO.PWM.cleanup()
|
||||
RPIO.stop_waiting_for_interrupts()
|
||||
RPIO.cleanup()
|
||||
|
||||
|
||||
class RPIOPin(LocalPiPin):
|
||||
"""
|
||||
Extends :class:`~gpiozero.pins.local.LocalPiPin`. Pin implementation for
|
||||
the `RPIO`_ library. See :class:`RPIOFactory` for more information.
|
||||
|
||||
.. _RPIO: https://pythonhosted.org/RPIO/
|
||||
"""
|
||||
GPIO_FUNCTIONS = {
|
||||
'input': RPIO.IN,
|
||||
'output': RPIO.OUT,
|
||||
'alt0': RPIO.ALT0,
|
||||
}
|
||||
|
||||
GPIO_PULL_UPS = {
|
||||
'up': RPIO.PUD_UP,
|
||||
'down': RPIO.PUD_DOWN,
|
||||
'floating': RPIO.PUD_OFF,
|
||||
}
|
||||
|
||||
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()}
|
||||
|
||||
def __init__(self, factory, number):
|
||||
super(RPIOPin, self).__init__(factory, number)
|
||||
self._pull = 'up' if self.factory.pi_info.pulled_up(repr(self)) else 'floating'
|
||||
self._pwm = False
|
||||
self._duty_cycle = None
|
||||
self._bounce = None
|
||||
self._edges = 'both'
|
||||
try:
|
||||
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)
|
||||
|
||||
def _get_function(self):
|
||||
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])
|
||||
except KeyError:
|
||||
raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
|
||||
|
||||
def _get_state(self):
|
||||
if self._pwm:
|
||||
return self._duty_cycle
|
||||
else:
|
||||
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)
|
||||
if value == 0:
|
||||
RPIO.output(self.number, False)
|
||||
elif value == 1:
|
||||
RPIO.output(self.number, True)
|
||||
else:
|
||||
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)
|
||||
except ValueError:
|
||||
raise PinInvalidState('invalid state "%s" for pin %r' % (value, self))
|
||||
except RuntimeError:
|
||||
raise PinSetInput('cannot set state of pin %r' % self)
|
||||
|
||||
def _get_pull(self):
|
||||
return self._pull
|
||||
|
||||
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.factory.pi_info.pulled_up(repr(self)):
|
||||
raise PinFixedPull('%r has a physical pull-up resistor' % self)
|
||||
try:
|
||||
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))
|
||||
|
||||
def _get_frequency(self):
|
||||
if self._pwm:
|
||||
return 100
|
||||
else:
|
||||
return None
|
||||
|
||||
def _set_frequency(self, value):
|
||||
if value is not None and value != 100:
|
||||
raise PinPWMError(
|
||||
'RPIOPin implementation is currently limited to '
|
||||
'100Hz sub-cycles')
|
||||
if not self._pwm and value is not None:
|
||||
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)
|
||||
elif self._pwm and value is None:
|
||||
RPIO.PWM.clear_channel_gpio(0, self.number)
|
||||
self._pwm = False
|
||||
|
||||
def _get_bounce(self):
|
||||
return None if self._bounce is None else (self._bounce / 1000)
|
||||
|
||||
def _set_bounce(self, value):
|
||||
if value is not None and value < 0:
|
||||
raise PinInvalidBounce('bounce must be 0 or greater')
|
||||
f = self.when_changed
|
||||
self.when_changed = None
|
||||
try:
|
||||
self._bounce = None if value is None else int(value * 1000)
|
||||
finally:
|
||||
self.when_changed = f
|
||||
|
||||
def _get_edges(self):
|
||||
return self._edges
|
||||
|
||||
def _set_edges(self, value):
|
||||
f = self.when_changed
|
||||
self.when_changed = None
|
||||
try:
|
||||
self._edges = value
|
||||
finally:
|
||||
self.when_changed = f
|
||||
|
||||
def _call_when_changed(self, channel, value):
|
||||
super(RPIOPin, self)._call_when_changed()
|
||||
|
||||
def _enable_event_detect(self):
|
||||
RPIO.add_interrupt_callback(
|
||||
self.number, self._call_when_changed, self._edges,
|
||||
self.GPIO_PULL_UPS[self._pull], self._bounce)
|
||||
|
||||
def _disable_event_detect(self):
|
||||
try:
|
||||
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
|
||||
# removed the handler
|
||||
pass
|
104
.venv/lib/python3.7/site-packages/gpiozero/pins/spi.py
Normal file
104
.venv/lib/python3.7/site-packages/gpiozero/pins/spi.py
Normal file
@@ -0,0 +1,104 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2021 Dave Jones <dave@waveform.org.uk>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
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 getattr(self, 'lock', None):
|
||||
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:
|
||||
# See https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus
|
||||
# (specifically the section "Example of bit-banging the master
|
||||
# protocol") for a simpler C implementation of this which ignores
|
||||
# clock polarity, phase, variable word-size, and multiple input
|
||||
# words
|
||||
if lsb_first:
|
||||
shift = operator.lshift
|
||||
init_mask = 1
|
||||
else:
|
||||
shift = operator.rshift
|
||||
init_mask = 1 << (bits_per_word - 1)
|
||||
for write_word in data:
|
||||
mask = init_mask
|
||||
read_word = 0
|
||||
for _ in range(bits_per_word):
|
||||
if self.mosi is not None:
|
||||
self.mosi.value = bool(write_word & mask)
|
||||
# read bit on clock activation
|
||||
self.clock.on()
|
||||
if not clock_phase:
|
||||
if self.miso is not None and self.miso.value:
|
||||
read_word |= mask
|
||||
# read bit on clock deactivation
|
||||
self.clock.off()
|
||||
if clock_phase:
|
||||
if self.miso is not None and self.miso.value:
|
||||
read_word |= mask
|
||||
mask = shift(mask, 1)
|
||||
result.append(read_word)
|
||||
return result
|
Reference in New Issue
Block a user