1.0
This commit is contained in:
128
venv/lib/python3.11/site-packages/gpiozero/__init__.py
Normal file
128
venv/lib/python3.11/site-packages/gpiozero/__init__.py
Normal file
@@ -0,0 +1,128 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2015-2023 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2015-2021 Ben Nuttall <ben@bennuttall.com>
|
||||
# Copyright (c) 2019 tuftii <3215045+tuftii@users.noreply.github.com>
|
||||
# Copyright (c) 2019 Jeevan M R <14.jeevan@gmail.com>
|
||||
# Copyright (c) 2019 ForToffee <ForToffee@users.noreply.github.com>
|
||||
# Copyright (c) 2018 Claire Pollard <claire.r.pollard@gmail.com>
|
||||
# Copyright (c) 2016 pcopa <scheltovandoorn@gmail.com>
|
||||
# Copyright (c) 2016 Ian Harcombe <ian.harcombe@gmail.com>
|
||||
# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
|
||||
# Copyright (c) 2016 Andrew Scheller <lurch@durge.org>
|
||||
# Copyright (c) 2015 Philip Howard <phil@gadgetoid.com>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from .pins import (
|
||||
Factory,
|
||||
Pin,
|
||||
SPI,
|
||||
BoardInfo,
|
||||
HeaderInfo,
|
||||
PinInfo,
|
||||
)
|
||||
from .pins.pi import (
|
||||
PiBoardInfo,
|
||||
pi_info,
|
||||
)
|
||||
# Yes, import * is naughty, but exc imports nothing else so there's no cross
|
||||
# contamination here ... and besides, have you *seen* the list lately?!
|
||||
from .exc import *
|
||||
from .devices import (
|
||||
Device,
|
||||
GPIODevice,
|
||||
CompositeDevice,
|
||||
)
|
||||
from .mixins import (
|
||||
SharedMixin,
|
||||
SourceMixin,
|
||||
ValuesMixin,
|
||||
EventsMixin,
|
||||
event,
|
||||
HoldMixin,
|
||||
)
|
||||
from .input_devices import (
|
||||
InputDevice,
|
||||
DigitalInputDevice,
|
||||
SmoothedInputDevice,
|
||||
Button,
|
||||
LineSensor,
|
||||
MotionSensor,
|
||||
LightSensor,
|
||||
DistanceSensor,
|
||||
RotaryEncoder,
|
||||
)
|
||||
from .spi_devices import (
|
||||
SPIDevice,
|
||||
AnalogInputDevice,
|
||||
MCP3001,
|
||||
MCP3002,
|
||||
MCP3004,
|
||||
MCP3008,
|
||||
MCP3201,
|
||||
MCP3202,
|
||||
MCP3204,
|
||||
MCP3208,
|
||||
MCP3301,
|
||||
MCP3302,
|
||||
MCP3304,
|
||||
)
|
||||
from .output_devices import (
|
||||
OutputDevice,
|
||||
DigitalOutputDevice,
|
||||
PWMOutputDevice,
|
||||
PWMLED,
|
||||
LED,
|
||||
Buzzer,
|
||||
Motor,
|
||||
PhaseEnableMotor,
|
||||
Servo,
|
||||
AngularServo,
|
||||
RGBLED,
|
||||
TonalBuzzer,
|
||||
)
|
||||
from .boards import (
|
||||
CompositeOutputDevice,
|
||||
ButtonBoard,
|
||||
LEDCollection,
|
||||
LEDBoard,
|
||||
LEDBarGraph,
|
||||
LEDCharDisplay,
|
||||
LEDMultiCharDisplay,
|
||||
LEDCharFont,
|
||||
LedBorg,
|
||||
PiHutXmasTree,
|
||||
PiLiter,
|
||||
PiLiterBarGraph,
|
||||
TrafficLights,
|
||||
PiTraffic,
|
||||
PiStop,
|
||||
StatusZero,
|
||||
StatusBoard,
|
||||
SnowPi,
|
||||
TrafficLightsBuzzer,
|
||||
FishDish,
|
||||
TrafficHat,
|
||||
TrafficpHat,
|
||||
Robot,
|
||||
RyanteckRobot,
|
||||
CamJamKitRobot,
|
||||
PololuDRV8835Robot,
|
||||
PhaseEnableRobot,
|
||||
Energenie,
|
||||
PumpkinPi,
|
||||
JamHat,
|
||||
Pibrella,
|
||||
)
|
||||
from .internal_devices import (
|
||||
InternalDevice,
|
||||
PolledInternalDevice,
|
||||
PingServer,
|
||||
CPUTemperature,
|
||||
LoadAverage,
|
||||
TimeOfDay,
|
||||
DiskUsage,
|
||||
)
|
||||
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.
Binary file not shown.
Binary file not shown.
2794
venv/lib/python3.11/site-packages/gpiozero/boards.py
Normal file
2794
venv/lib/python3.11/site-packages/gpiozero/boards.py
Normal file
File diff suppressed because it is too large
Load Diff
44
venv/lib/python3.11/site-packages/gpiozero/compat.py
Normal file
44
venv/lib/python3.11/site-packages/gpiozero/compat.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2023 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2020 Fangchen Li <fangchen.li@outlook.com>
|
||||
# Copyright (c) 2018 Rick Ansell <rick@nbinvincible.org.uk>
|
||||
# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import operator
|
||||
from functools import reduce
|
||||
from collections.abc import Mapping
|
||||
|
||||
|
||||
# Derived from the MIT-licensed https://github.com/slezica/python-frozendict
|
||||
class frozendict(Mapping):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._dict = dict(*args, **kwargs)
|
||||
self._hash = None
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._dict[key]
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._dict
|
||||
|
||||
def copy(self, **add_or_replace):
|
||||
return frozendict(self, **add_or_replace)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._dict)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._dict)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<{self.__class__.__name__} {self._dict!r}>'
|
||||
|
||||
def __hash__(self):
|
||||
if self._hash is None:
|
||||
self._hash = reduce(operator.xor, map(hash, self.items()), 0)
|
||||
return self._hash
|
||||
634
venv/lib/python3.11/site-packages/gpiozero/devices.py
Normal file
634
venv/lib/python3.11/site-packages/gpiozero/devices.py
Normal file
@@ -0,0 +1,634 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2015-2024 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2020 Fangchen Li <fangchen.li@outlook.com>
|
||||
# Copyright (c) 2015-2019 Ben Nuttall <ben@bennuttall.com>
|
||||
# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import os
|
||||
import atexit
|
||||
import weakref
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
from itertools import chain
|
||||
from types import FunctionType
|
||||
|
||||
# NOTE: Remove try when compatibility moves beyond Python 3.10
|
||||
try:
|
||||
from importlib_metadata import entry_points
|
||||
except ImportError:
|
||||
from importlib.metadata import entry_points
|
||||
|
||||
from .threads import _threads_shutdown
|
||||
from .mixins import (
|
||||
ValuesMixin,
|
||||
SharedMixin,
|
||||
)
|
||||
from .exc import (
|
||||
BadPinFactory,
|
||||
DeviceClosed,
|
||||
CompositeDeviceBadName,
|
||||
CompositeDeviceBadOrder,
|
||||
CompositeDeviceBadDevice,
|
||||
GPIOPinMissing,
|
||||
GPIODeviceClosed,
|
||||
NativePinFactoryFallback,
|
||||
PinFactoryFallback,
|
||||
)
|
||||
|
||||
from .compat import frozendict
|
||||
|
||||
native_fallback_message = (
|
||||
'Falling back to the experimental pin factory NativeFactory because no other '
|
||||
'pin factory could be loaded. For best results, install RPi.GPIO or pigpio. '
|
||||
'See https://gpiozero.readthedocs.io/en/stable/api_pins.html for more information.'
|
||||
)
|
||||
|
||||
|
||||
class GPIOMeta(type):
|
||||
# NOTE Yes, this is a metaclass. Don't be scared - it's a simple one.
|
||||
|
||||
def __new__(mcls, name, bases, cls_dict):
|
||||
# Construct the class as normal
|
||||
cls = super().__new__(mcls, name, bases, cls_dict)
|
||||
# If there's a method in the class which has no docstring, search
|
||||
# the base classes recursively for a docstring to copy
|
||||
for attr_name, attr in cls_dict.items():
|
||||
if isinstance(attr, FunctionType) and not attr.__doc__:
|
||||
for base_cls in cls.__mro__:
|
||||
if hasattr(base_cls, attr_name):
|
||||
base_fn = getattr(base_cls, attr_name)
|
||||
if base_fn.__doc__:
|
||||
attr.__doc__ = base_fn.__doc__
|
||||
break
|
||||
return cls
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
# Make sure cls has GPIOBase somewhere in its ancestry (otherwise
|
||||
# setting __attrs__ below will be rather pointless)
|
||||
assert issubclass(cls, GPIOBase)
|
||||
if issubclass(cls, SharedMixin):
|
||||
# If SharedMixin appears in the class' ancestry, convert the
|
||||
# constructor arguments to a key and check whether an instance
|
||||
# already exists. Only construct the instance if the key's new.
|
||||
key = cls._shared_key(*args, **kwargs)
|
||||
try:
|
||||
self = cls._instances[key]()
|
||||
self._refs += 1
|
||||
except (KeyError, AttributeError) as e:
|
||||
self = super().__call__(*args, **kwargs)
|
||||
self._refs = 1
|
||||
# Replace the close method with one that merely decrements
|
||||
# the refs counter and calls the original close method when
|
||||
# it reaches zero
|
||||
old_close = self.close
|
||||
|
||||
def close():
|
||||
self._refs = max(0, self._refs - 1)
|
||||
if not self._refs:
|
||||
try:
|
||||
old_close()
|
||||
finally:
|
||||
try:
|
||||
del cls._instances[key]
|
||||
except KeyError:
|
||||
# If the _refs go negative (too many closes)
|
||||
# just ignore the resulting KeyError here -
|
||||
# it's already gone
|
||||
pass
|
||||
|
||||
self.close = close
|
||||
cls._instances[key] = weakref.ref(self)
|
||||
else:
|
||||
# Construct the instance as normal
|
||||
self = super().__call__(*args, **kwargs)
|
||||
# At this point __new__ and __init__ have all been run. We now fix the
|
||||
# set of attributes on the class by dir'ing the instance and creating a
|
||||
# frozenset of the result called __attrs__ (which is queried by
|
||||
# GPIOBase.__setattr__). An exception is made for SharedMixin devices
|
||||
# which can be constructed multiple times, returning the same instance
|
||||
if not issubclass(cls, SharedMixin) or self._refs == 1:
|
||||
self.__attrs__ = frozenset(dir(self))
|
||||
return self
|
||||
|
||||
|
||||
class GPIOBase(metaclass=GPIOMeta):
|
||||
def __setattr__(self, name, value):
|
||||
# This overridden __setattr__ simply ensures that additional attributes
|
||||
# cannot be set on the class after construction (it manages this in
|
||||
# conjunction with the meta-class above). Traditionally, this is
|
||||
# managed with __slots__; however, this doesn't work with Python's
|
||||
# multiple inheritance system which we need to use in order to avoid
|
||||
# repeating the "source" and "values" property code in myriad places
|
||||
if hasattr(self, '__attrs__') and name not in self.__attrs__:
|
||||
raise AttributeError(
|
||||
f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
||||
return super().__setattr__(name, value)
|
||||
|
||||
def __del__(self):
|
||||
# NOTE: Yes, we implicitly call close() on __del__(), and yes for you
|
||||
# dear hacker-on-this-library, this means pain!
|
||||
#
|
||||
# It's entirely for the convenience of command line experimenters and
|
||||
# newbies who want to re-gain those pins when stuff falls out of scope
|
||||
# without managing their object lifetimes "properly" with "with" (but,
|
||||
# hey, this is an educational library at heart so that's the way we
|
||||
# roll).
|
||||
#
|
||||
# What does this mean for you? It means that in close() you cannot
|
||||
# assume *anything*. If someone calls a constructor with a fundamental
|
||||
# mistake like the wrong number of params, then your close() method is
|
||||
# going to be called before __init__ ever ran so all those attributes
|
||||
# you *think* exist, erm, don't. Basically if you refer to anything in
|
||||
# "self" within your close method, be preprared to catch AttributeError
|
||||
# on its access to avoid spurious warnings for the end user.
|
||||
#
|
||||
# "But we're exiting anyway; surely exceptions in __del__ get
|
||||
# squashed?" Yes, but they still cause verbose warnings and remember
|
||||
# that this is an educational library; keep it friendly!
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Shut down the device and release all associated resources (such as GPIO
|
||||
pins).
|
||||
|
||||
This method is idempotent (can be called on an already closed device
|
||||
without any side-effects). It is primarily intended for interactive use
|
||||
at the command line. It disables the device and releases its pin(s) for
|
||||
use by another device.
|
||||
|
||||
You can attempt to do this simply by deleting an object, but unless
|
||||
you've cleaned up all references to the object this may not work (even
|
||||
if you've cleaned up all references, there's still no guarantee the
|
||||
garbage collector will actually delete the object at that point). By
|
||||
contrast, the close method provides a means of ensuring that the object
|
||||
is shut down.
|
||||
|
||||
For example, if you have a breadboard with a buzzer connected to pin
|
||||
16, but then wish to attach an LED instead:
|
||||
|
||||
>>> from gpiozero import *
|
||||
>>> bz = Buzzer(16)
|
||||
>>> bz.on()
|
||||
>>> bz.off()
|
||||
>>> bz.close()
|
||||
>>> led = LED(16)
|
||||
>>> led.blink()
|
||||
|
||||
:class:`Device` descendents can also be used as context managers using
|
||||
the :keyword:`with` statement. For example:
|
||||
|
||||
>>> from gpiozero import *
|
||||
>>> with Buzzer(16) as bz:
|
||||
... bz.on()
|
||||
...
|
||||
>>> with LED(16) as led:
|
||||
... led.on()
|
||||
...
|
||||
"""
|
||||
# This is a placeholder which is simply here to ensure close() can be
|
||||
# safely called from subclasses without worrying whether super-classes
|
||||
# have it (which in turn is useful in conjunction with the mixin
|
||||
# classes).
|
||||
#
|
||||
# P.S. See note in __del__ above.
|
||||
pass
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
"""
|
||||
Returns :data:`True` if the device is closed (see the :meth:`close`
|
||||
method). Once a device is closed you can no longer use any other
|
||||
methods or properties to control or query the device.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _check_open(self):
|
||||
if self.closed:
|
||||
raise DeviceClosed(
|
||||
f"{self.__class__.__name__} is closed or uninitialized")
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_tb):
|
||||
self.close()
|
||||
|
||||
|
||||
class Device(ValuesMixin, GPIOBase):
|
||||
"""
|
||||
Represents a single device of any type; GPIO-based, SPI-based, I2C-based,
|
||||
etc. This is the base class of the device hierarchy. It defines the basic
|
||||
services applicable to all devices (specifically the :attr:`is_active`
|
||||
property, the :attr:`value` property, and the :meth:`close` method).
|
||||
|
||||
.. attribute:: pin_factory
|
||||
|
||||
This attribute exists at both a class level (representing the default
|
||||
pin factory used to construct devices when no *pin_factory* parameter
|
||||
is specified), and at an instance level (representing the pin factory
|
||||
that the device was constructed with).
|
||||
|
||||
The pin factory provides various facilities to the device including
|
||||
allocating pins, providing low level interfaces (e.g. SPI), and clock
|
||||
facilities (querying and calculating elapsed times).
|
||||
"""
|
||||
pin_factory = None # instance of a Factory sub-class
|
||||
|
||||
def __init__(self, *, pin_factory=None):
|
||||
if pin_factory is None:
|
||||
Device.ensure_pin_factory()
|
||||
self.pin_factory = Device.pin_factory
|
||||
else:
|
||||
self.pin_factory = pin_factory
|
||||
super().__init__()
|
||||
|
||||
@staticmethod
|
||||
def ensure_pin_factory():
|
||||
"""
|
||||
Ensures that :attr:`Device.pin_factory` is set appropriately.
|
||||
|
||||
This is called implicitly upon construction of any device, but there
|
||||
are some circumstances where you may need to call it manually.
|
||||
Specifically, when you wish to retrieve board information without
|
||||
constructing any devices, e.g.::
|
||||
|
||||
Device.ensure_pin_factory()
|
||||
info = Device.pin_factory.board_info
|
||||
|
||||
If :attr:`Device.pin_factory` is not :data:`None`, this function does
|
||||
nothing. Otherwise it will attempt to locate and initialize a default
|
||||
pin factory. This may raise a number of different exceptions including
|
||||
:exc:`ImportError` if no valid pin driver can be imported.
|
||||
"""
|
||||
if Device.pin_factory is None:
|
||||
Device.pin_factory = Device._default_pin_factory()
|
||||
|
||||
@staticmethod
|
||||
def _default_pin_factory():
|
||||
# We prefer lgpio here as it supports PWM, and all Pi revisions without
|
||||
# banging on registers directly. If no third-party libraries are
|
||||
# available, however, we fall back to a pure Python implementation
|
||||
# which supports platforms like PyPy
|
||||
#
|
||||
# NOTE: If the built-in pin factories are expanded, the dict must be
|
||||
# updated along with the entry-points in setup.py.
|
||||
default_factories = {
|
||||
'lgpio': 'gpiozero.pins.lgpio:LGPIOFactory',
|
||||
'rpigpio': 'gpiozero.pins.rpigpio:RPiGPIOFactory',
|
||||
'pigpio': 'gpiozero.pins.pigpio:PiGPIOFactory',
|
||||
'native': 'gpiozero.pins.native:NativeFactory',
|
||||
}
|
||||
name = os.environ.get('GPIOZERO_PIN_FACTORY')
|
||||
if name is None:
|
||||
# If no factory is explicitly specified, try various names in
|
||||
# "preferred" order
|
||||
for name, entry_point in default_factories.items():
|
||||
try:
|
||||
mod_name, cls_name = entry_point.split(':', 1)
|
||||
module = __import__(mod_name, fromlist=(cls_name,))
|
||||
pin_factory = getattr(module, cls_name)()
|
||||
if name == 'native':
|
||||
warnings.warn(NativePinFactoryFallback(native_fallback_message))
|
||||
return pin_factory
|
||||
except Exception as e:
|
||||
warnings.warn(
|
||||
PinFactoryFallback(f'Falling back from {name}: {e!s}'))
|
||||
raise BadPinFactory('Unable to load any default pin factory!')
|
||||
else:
|
||||
# Use importlib's entry_points to try and find the specified
|
||||
# entry-point. Try with name verbatim first. If that fails, attempt
|
||||
# with the lower-cased name (this ensures compatibility names work
|
||||
# but we're still case insensitive for all factories)
|
||||
with warnings.catch_warnings():
|
||||
# The dict interface of entry_points is deprecated ... already
|
||||
# and this deprecation is for us to worry about, not our users
|
||||
group = entry_points(group='gpiozero_pin_factories')
|
||||
for ep in group:
|
||||
if ep.name == name:
|
||||
return ep.load()()
|
||||
for ep in group:
|
||||
if ep.name == name.lower():
|
||||
return ep.load()()
|
||||
raise BadPinFactory(f'Unable to find pin factory {name!r}')
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
self._check_open()
|
||||
return f"<gpiozero.{self.__class__.__name__} object>"
|
||||
except DeviceClosed:
|
||||
return f"<gpiozero.{self.__class__.__name__} object closed>"
|
||||
|
||||
def _conflicts_with(self, other):
|
||||
"""
|
||||
Called by :meth:`Factory.reserve_pins` to test whether the *other*
|
||||
:class:`Device` using a common pin conflicts with this device's intent
|
||||
to use it. The default is :data:`True` indicating that all devices
|
||||
conflict with common pins. Sub-classes may override this to permit
|
||||
more nuanced replies.
|
||||
"""
|
||||
return True
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
Returns a value representing the device's state. Frequently, this is a
|
||||
boolean value, or a number between 0 and 1 but some devices use larger
|
||||
ranges (e.g. -1 to +1) and composite devices usually use tuples to
|
||||
return the states of all their subordinate components.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
"""
|
||||
Returns :data:`True` if the device is currently active and
|
||||
:data:`False` otherwise. This property is usually derived from
|
||||
:attr:`value`. Unlike :attr:`value`, this is *always* a boolean.
|
||||
"""
|
||||
return bool(self.value)
|
||||
|
||||
|
||||
class CompositeDevice(Device):
|
||||
"""
|
||||
Extends :class:`Device`. Represents a device composed of multiple devices
|
||||
like simple HATs, H-bridge motor controllers, robots composed of multiple
|
||||
motors, etc.
|
||||
|
||||
The constructor accepts subordinate devices as positional or keyword
|
||||
arguments. Positional arguments form unnamed devices accessed by treating
|
||||
the composite device as a container, while keyword arguments are added to
|
||||
the device as named (read-only) attributes.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from gpiozero import *
|
||||
>>> d = CompositeDevice(LED(2), LED(3), LED(4), btn=Button(17))
|
||||
>>> d[0]
|
||||
<gpiozero.LED object on pin GPIO2, active_high=True, is_active=False>
|
||||
>>> d[1]
|
||||
<gpiozero.LED object on pin GPIO3, active_high=True, is_active=False>
|
||||
>>> d[2]
|
||||
<gpiozero.LED object on pin GPIO4, active_high=True, is_active=False>
|
||||
>>> d.btn
|
||||
<gpiozero.Button object on pin GPIO17, pull_up=True, is_active=False>
|
||||
>>> d.value
|
||||
CompositeDeviceValue(device_0=False, device_1=False, device_2=False, btn=False)
|
||||
|
||||
:param Device \\*args:
|
||||
The un-named devices that belong to the composite device. The
|
||||
:attr:`value` attributes of these devices will be represented within
|
||||
the composite device's tuple :attr:`value` in the order specified here.
|
||||
|
||||
:type _order: list or None
|
||||
:param _order:
|
||||
If specified, this is the order of named items specified by keyword
|
||||
arguments (to ensure that the :attr:`value` tuple is constructed with a
|
||||
specific order). All keyword arguments *must* be included in the
|
||||
collection. If omitted, an alphabetically sorted order will be selected
|
||||
for keyword arguments.
|
||||
|
||||
:type pin_factory: Factory or None
|
||||
:param pin_factory:
|
||||
See :doc:`api_pins` for more information (this is an advanced feature
|
||||
which most users can ignore).
|
||||
|
||||
:param Device \\*\\*kwargs:
|
||||
The named devices that belong to the composite device. These devices
|
||||
will be accessible as named attributes on the resulting device, and
|
||||
their :attr:`value` attributes will be accessible as named elements of
|
||||
the composite device's tuple :attr:`value`.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, _order=None, pin_factory=None, **kwargs):
|
||||
self._all = ()
|
||||
self._named = frozendict({})
|
||||
self._namedtuple = None
|
||||
self._order = _order
|
||||
try:
|
||||
if self._order is None:
|
||||
self._order = sorted(kwargs.keys())
|
||||
else:
|
||||
for missing_name in set(kwargs.keys()) - set(self._order):
|
||||
raise CompositeDeviceBadOrder(
|
||||
f'{missing_name} missing from _order')
|
||||
self._order = tuple(self._order)
|
||||
for name in set(self._order) & set(dir(self)):
|
||||
raise CompositeDeviceBadName(f'{name} is a reserved name')
|
||||
for dev in chain(args, kwargs.values()):
|
||||
if not isinstance(dev, Device):
|
||||
raise CompositeDeviceBadDevice(
|
||||
f"{dev} doesn't inherit from Device")
|
||||
self._named = frozendict(kwargs)
|
||||
self._namedtuple = namedtuple(
|
||||
f'{self.__class__.__name__}Value',
|
||||
chain((f'device_{i}' for i in range(len(args))), self._order))
|
||||
except:
|
||||
for dev in chain(args, kwargs.values()):
|
||||
if isinstance(dev, Device):
|
||||
dev.close()
|
||||
raise
|
||||
self._all = args + tuple(kwargs[v] for v in self._order)
|
||||
super().__init__(pin_factory=pin_factory)
|
||||
|
||||
def __getattr__(self, name):
|
||||
# if _named doesn't exist yet, pretend it's an empty dict
|
||||
if name == '_named':
|
||||
return frozendict({})
|
||||
try:
|
||||
return self._named[name]
|
||||
except KeyError:
|
||||
raise AttributeError(f"no such attribute {name}")
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
# make named components read-only properties
|
||||
if name in self._named:
|
||||
raise AttributeError(f"can't set attribute {name}")
|
||||
return super().__setattr__(name, value)
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
self._check_open()
|
||||
named = len(self._named)
|
||||
names = ', '.join(self._order)
|
||||
unnamed = len(self) - len(self._named)
|
||||
if named > 0 and unnamed > 0:
|
||||
return (
|
||||
f"<gpiozero.{self.__class__.__name__} object containing "
|
||||
f"{len(self)} devices: {names} and {unnamed} unnamed>")
|
||||
elif named > 0:
|
||||
return (
|
||||
f"<gpiozero.{self.__class__.__name__} object containing "
|
||||
f"{len(self)} devices: {names}>")
|
||||
else:
|
||||
return (
|
||||
f"<gpiozero.{self.__class__.__name__} object containing "
|
||||
f"{len(self)} unnamed devices>")
|
||||
except DeviceClosed:
|
||||
return super().__repr__()
|
||||
|
||||
def __len__(self):
|
||||
return len(self._all)
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self._all[index]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._all)
|
||||
|
||||
@property
|
||||
def all(self):
|
||||
# XXX Deprecate this in favour of using the instance as a container
|
||||
return self._all
|
||||
|
||||
def close(self):
|
||||
if getattr(self, '_all', None):
|
||||
for device in self._all:
|
||||
device.close()
|
||||
self._all = ()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return all(device.closed for device in self)
|
||||
|
||||
@property
|
||||
def namedtuple(self):
|
||||
"""
|
||||
The :func:`~collections.namedtuple` type constructed to represent the
|
||||
value of the composite device. The :attr:`value` attribute returns
|
||||
values of this type.
|
||||
"""
|
||||
return self._namedtuple
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
A :func:`~collections.namedtuple` containing a value for each
|
||||
subordinate device. Devices with names will be represented as named
|
||||
elements. Unnamed devices will have a unique name generated for them,
|
||||
and they will appear in the position they appeared in the constructor.
|
||||
"""
|
||||
return self.namedtuple(*(device.value for device in self))
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
"""
|
||||
Composite devices are considered "active" if any of their constituent
|
||||
devices have a "truthy" value.
|
||||
"""
|
||||
return any(self.value)
|
||||
|
||||
|
||||
class GPIODevice(Device):
|
||||
"""
|
||||
Extends :class:`Device`. Represents a generic GPIO device and provides
|
||||
the services common to all single-pin GPIO devices (like ensuring two
|
||||
GPIO devices do no share a :attr:`pin`).
|
||||
|
||||
:type pin: int or str
|
||||
:param pin:
|
||||
The GPIO pin that the device is connected to. See :ref:`pin-numbering`
|
||||
for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
|
||||
will be raised. If the pin is already in use by another device,
|
||||
:exc:`GPIOPinInUse` will be raised.
|
||||
"""
|
||||
|
||||
def __init__(self, pin=None, *, pin_factory=None):
|
||||
super().__init__(pin_factory=pin_factory)
|
||||
# self._pin must be set before any possible exceptions can be raised
|
||||
# because it's accessed in __del__. However, it mustn't be given the
|
||||
# value of pin until we've verified that it isn't already allocated
|
||||
self._pin = None
|
||||
if pin is None:
|
||||
raise GPIOPinMissing('No pin given')
|
||||
# Check you can reserve *before* constructing the pin
|
||||
self.pin_factory.reserve_pins(self, pin)
|
||||
pin = self.pin_factory.pin(pin)
|
||||
self._pin = pin
|
||||
self._active_state = True
|
||||
self._inactive_state = False
|
||||
|
||||
def _state_to_value(self, state):
|
||||
return int(state == self._active_state)
|
||||
|
||||
def _read(self):
|
||||
try:
|
||||
return self._state_to_value(self.pin.state)
|
||||
except (AttributeError, TypeError):
|
||||
self._check_open()
|
||||
raise
|
||||
|
||||
def close(self):
|
||||
super().close()
|
||||
if getattr(self, '_pin', None) is not None:
|
||||
self.pin_factory.release_pins(self, self._pin.info.name)
|
||||
self._pin.close()
|
||||
self._pin = None
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
try:
|
||||
return self._pin is None
|
||||
except AttributeError:
|
||||
return True
|
||||
|
||||
def _check_open(self):
|
||||
try:
|
||||
super()._check_open()
|
||||
except DeviceClosed as e:
|
||||
# For backwards compatibility; GPIODeviceClosed is deprecated
|
||||
raise GPIODeviceClosed(str(e))
|
||||
|
||||
@property
|
||||
def pin(self):
|
||||
"""
|
||||
The :class:`Pin` that the device is connected to. This will be
|
||||
:data:`None` if the device has been closed (see the
|
||||
:meth:`~Device.close` method). When dealing with GPIO pins, query
|
||||
``pin.number`` to discover the GPIO pin (in BCM numbering) that the
|
||||
device is connected to.
|
||||
"""
|
||||
return self._pin
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self._read()
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
return (
|
||||
f"<gpiozero.{self.__class__.__name__} object on pin "
|
||||
f"{self.pin!r}, is_active={self.is_active}>")
|
||||
except DeviceClosed:
|
||||
return f"<gpiozero.{self.__class__.__name__} object closed>"
|
||||
|
||||
|
||||
def _devices_shutdown():
|
||||
if Device.pin_factory is not None:
|
||||
with Device.pin_factory._res_lock:
|
||||
reserved_devices = {
|
||||
dev
|
||||
for ref_list in Device.pin_factory._reservations.values()
|
||||
for ref in ref_list
|
||||
for dev in (ref(),)
|
||||
if dev is not None
|
||||
}
|
||||
for dev in reserved_devices:
|
||||
dev.close()
|
||||
Device.pin_factory.close()
|
||||
Device.pin_factory = None
|
||||
|
||||
|
||||
def _shutdown():
|
||||
_threads_shutdown()
|
||||
_devices_shutdown()
|
||||
|
||||
|
||||
atexit.register(_shutdown)
|
||||
194
venv/lib/python3.11/site-packages/gpiozero/exc.py
Normal file
194
venv/lib/python3.11/site-packages/gpiozero/exc.py
Normal file
@@ -0,0 +1,194 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2023 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2019 Kosovan Sofiia <sofiia.kosovan@gmail.com>
|
||||
# Copyright (c) 2019 Ben Nuttall <ben@bennuttall.com>
|
||||
# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
|
||||
class GPIOZeroError(Exception):
|
||||
"Base class for all exceptions in GPIO Zero"
|
||||
|
||||
class DeviceClosed(GPIOZeroError):
|
||||
"Error raised when an operation is attempted on a closed device"
|
||||
|
||||
class BadEventHandler(GPIOZeroError, ValueError):
|
||||
"Error raised when an event handler with an incompatible prototype is specified"
|
||||
|
||||
class BadWaitTime(GPIOZeroError, ValueError):
|
||||
"Error raised when an invalid wait time is specified"
|
||||
|
||||
class BadQueueLen(GPIOZeroError, ValueError):
|
||||
"Error raised when non-positive queue length is specified"
|
||||
|
||||
class BadPinFactory(GPIOZeroError, ImportError):
|
||||
"Error raised when an unknown pin factory name is specified"
|
||||
|
||||
class ZombieThread(GPIOZeroError, RuntimeError):
|
||||
"Error raised when a thread fails to die within a given timeout"
|
||||
|
||||
class CompositeDeviceError(GPIOZeroError):
|
||||
"Base class for errors specific to the CompositeDevice hierarchy"
|
||||
|
||||
class CompositeDeviceBadName(CompositeDeviceError, ValueError):
|
||||
"Error raised when a composite device is constructed with a reserved name"
|
||||
|
||||
class CompositeDeviceBadOrder(CompositeDeviceError, ValueError):
|
||||
"Error raised when a composite device is constructed with an incomplete order"
|
||||
|
||||
class CompositeDeviceBadDevice(CompositeDeviceError, ValueError):
|
||||
"Error raised when a composite device is constructed with an object that doesn't inherit from :class:`Device`"
|
||||
|
||||
class EnergenieSocketMissing(CompositeDeviceError, ValueError):
|
||||
"Error raised when socket number is not specified"
|
||||
|
||||
class EnergenieBadSocket(CompositeDeviceError, ValueError):
|
||||
"Error raised when an invalid socket number is passed to :class:`Energenie`"
|
||||
|
||||
class SPIError(GPIOZeroError):
|
||||
"Base class for errors related to the SPI implementation"
|
||||
|
||||
class SPIBadArgs(SPIError, ValueError):
|
||||
"Error raised when invalid arguments are given while constructing :class:`SPIDevice`"
|
||||
|
||||
class SPIBadChannel(SPIError, ValueError):
|
||||
"Error raised when an invalid channel is given to an :class:`AnalogInputDevice`"
|
||||
|
||||
class SPIFixedClockMode(SPIError, AttributeError):
|
||||
"Error raised when the SPI clock mode cannot be changed"
|
||||
|
||||
class SPIInvalidClockMode(SPIError, ValueError):
|
||||
"Error raised when an invalid clock mode is given to an SPI implementation"
|
||||
|
||||
class SPIFixedBitOrder(SPIError, AttributeError):
|
||||
"Error raised when the SPI bit-endianness cannot be changed"
|
||||
|
||||
class SPIFixedSelect(SPIError, AttributeError):
|
||||
"Error raised when the SPI select polarity cannot be changed"
|
||||
|
||||
class SPIFixedWordSize(SPIError, AttributeError):
|
||||
"Error raised when the number of bits per word cannot be changed"
|
||||
|
||||
class SPIFixedRate(SPIError, AttributeError):
|
||||
"Error raised when the baud-rate of the interface cannot be changed"
|
||||
|
||||
class SPIInvalidWordSize(SPIError, ValueError):
|
||||
"Error raised when an invalid (out of range) number of bits per word is specified"
|
||||
|
||||
class GPIODeviceError(GPIOZeroError):
|
||||
"Base class for errors specific to the GPIODevice hierarchy"
|
||||
|
||||
class GPIODeviceClosed(GPIODeviceError, DeviceClosed):
|
||||
"Deprecated descendent of :exc:`DeviceClosed`"
|
||||
|
||||
class GPIOPinInUse(GPIODeviceError):
|
||||
"Error raised when attempting to use a pin already in use by another device"
|
||||
|
||||
class GPIOPinMissing(GPIODeviceError, ValueError):
|
||||
"Error raised when a pin specification is not given"
|
||||
|
||||
class InputDeviceError(GPIODeviceError):
|
||||
"Base class for errors specific to the InputDevice hierarchy"
|
||||
|
||||
class OutputDeviceError(GPIODeviceError):
|
||||
"Base class for errors specified to the OutputDevice hierarchy"
|
||||
|
||||
class OutputDeviceBadValue(OutputDeviceError, ValueError):
|
||||
"Error raised when ``value`` is set to an invalid value"
|
||||
|
||||
class PinError(GPIOZeroError):
|
||||
"Base class for errors related to pin implementations"
|
||||
|
||||
class PinInvalidFunction(PinError, ValueError):
|
||||
"Error raised when attempting to change the function of a pin to an invalid value"
|
||||
|
||||
class PinInvalidState(PinError, ValueError):
|
||||
"Error raised when attempting to assign an invalid state to a pin"
|
||||
|
||||
class PinInvalidPull(PinError, ValueError):
|
||||
"Error raised when attempting to assign an invalid pull-up to a pin"
|
||||
|
||||
class PinInvalidEdges(PinError, ValueError):
|
||||
"Error raised when attempting to assign an invalid edge detection to a pin"
|
||||
|
||||
class PinInvalidBounce(PinError, ValueError):
|
||||
"Error raised when attempting to assign an invalid bounce time to a pin"
|
||||
|
||||
class PinSetInput(PinError, AttributeError):
|
||||
"Error raised when attempting to set a read-only pin"
|
||||
|
||||
class PinFixedPull(PinError, AttributeError):
|
||||
"Error raised when attempting to set the pull of a pin with fixed pull-up"
|
||||
|
||||
class PinEdgeDetectUnsupported(PinError, AttributeError):
|
||||
"Error raised when attempting to use edge detection on unsupported pins"
|
||||
|
||||
class PinUnsupported(PinError, NotImplementedError):
|
||||
"Error raised when attempting to obtain a pin interface on unsupported pins"
|
||||
|
||||
class PinSPIUnsupported(PinError, NotImplementedError):
|
||||
"Error raised when attempting to obtain an SPI interface on unsupported pins"
|
||||
|
||||
class PinPWMError(PinError):
|
||||
"Base class for errors related to PWM implementations"
|
||||
|
||||
class PinPWMUnsupported(PinPWMError, AttributeError):
|
||||
"Error raised when attempting to activate PWM on unsupported pins"
|
||||
|
||||
class PinPWMFixedValue(PinPWMError, AttributeError):
|
||||
"Error raised when attempting to initialize PWM on an input pin"
|
||||
|
||||
class PinUnknownPi(PinError, RuntimeError):
|
||||
"Error raised when gpiozero doesn't recognize a revision of the Pi"
|
||||
|
||||
class PinMultiplePins(PinError, RuntimeError):
|
||||
"Error raised when multiple pins support the requested function"
|
||||
|
||||
class PinNoPins(PinError, RuntimeError):
|
||||
"Error raised when no pins support the requested function"
|
||||
|
||||
class PinInvalidPin(PinError, ValueError):
|
||||
"Error raised when an invalid pin specification is provided"
|
||||
|
||||
class GPIOZeroWarning(Warning):
|
||||
"Base class for all warnings in GPIO Zero"
|
||||
|
||||
class DistanceSensorNoEcho(GPIOZeroWarning):
|
||||
"Warning raised when the distance sensor sees no echo at all"
|
||||
|
||||
class SPIWarning(GPIOZeroWarning):
|
||||
"Base class for warnings related to the SPI implementation"
|
||||
|
||||
class SPISoftwareFallback(SPIWarning):
|
||||
"Warning raised when falling back to the SPI software implementation"
|
||||
|
||||
class PWMWarning(GPIOZeroWarning):
|
||||
"Base class for PWM warnings"
|
||||
|
||||
class PWMSoftwareFallback(PWMWarning):
|
||||
"Warning raised when falling back to the PWM software implementation"
|
||||
|
||||
class PinWarning(GPIOZeroWarning):
|
||||
"Base class for warnings related to pin implementations"
|
||||
|
||||
class PinFactoryFallback(PinWarning):
|
||||
"Warning raised when a default pin factory fails to load and a fallback is tried"
|
||||
|
||||
class NativePinFactoryFallback(PinWarning):
|
||||
"Warning raised when all other default pin factories fail to load and NativeFactory is used"
|
||||
|
||||
class PinNonPhysical(PinWarning):
|
||||
"Warning raised when a non-physical pin is specified in a constructor"
|
||||
|
||||
class ThresholdOutOfRange(GPIOZeroWarning):
|
||||
"Warning raised when a threshold is out of range specified by min and max values"
|
||||
|
||||
class CallbackSetToNone(GPIOZeroWarning):
|
||||
"Warning raised when a callback is set to None when its previous value was None"
|
||||
|
||||
class AmbiguousTone(GPIOZeroWarning):
|
||||
"Warning raised when a Tone is constructed with an ambiguous number"
|
||||
54
venv/lib/python3.11/site-packages/gpiozero/fonts/14seg.txt
Normal file
54
venv/lib/python3.11/site-packages/gpiozero/fonts/14seg.txt
Normal file
@@ -0,0 +1,54 @@
|
||||
# This file defines the default alphabet for 14-segment displays. The format is
|
||||
# fairly simple: the file is loaded as a whole and all blank and #-prefixed
|
||||
# lines are stripped out. Then all blank columns are stripped out. Finally the
|
||||
# remaining rows and columns are divided into 5x5 cells with the following
|
||||
# format:
|
||||
#
|
||||
# X.a..
|
||||
# fijkb
|
||||
# .g.h.
|
||||
# elmnc
|
||||
# ..d..
|
||||
#
|
||||
# Where X is the character being defined, and a..n are the segments that are
|
||||
# active. a, d, g, and h are considered active if they are "-". b, c, e, f, j,
|
||||
# and m are considered active if they are "|". i and n are active when they
|
||||
# are "\". Finally, k and l are active when they are "/". All other characters
|
||||
# marked "." are ignored but may be set to anything for the purposes of making
|
||||
# the character's shape more obvious.
|
||||
#
|
||||
# Note that blank columns are stripped, so when defining the space (" ")
|
||||
# character you will need to use place-holder characters for unused positions.
|
||||
# Furthermore, the parser checks that definitions are multiples of 5 wide and
|
||||
# 5 high. If a character's definition has entirely empty rows or columns you
|
||||
# may need more place-holder characters to satisfy this limitation.
|
||||
|
||||
.... 0--- 1.. 2--- 3--- 4 5--- 6--- 7---. 8--- 9---
|
||||
..... | /| /| | | | | | | / | | | |
|
||||
..... | / | | --- -- ---| --- |--- | --- ---|
|
||||
..... |/ | | | | | | | | | | | |
|
||||
..... --- --- --- --- --- ---
|
||||
|
||||
A--- B--- C---. D--- E---. F---. G--- H I---. J K . L M
|
||||
| | | | | | | | | | | | | | | / | |\ /|
|
||||
|---| -| | | | |--- |--- | -- |---| | | |-- | | ' |
|
||||
| | | | | | | | | | | | | | | | | \ | | |
|
||||
' ' --- --- --- --- ' --- ' ' --- --- ' ' ---. ' '
|
||||
|
||||
N O--- P--- Q--- R--- S--- T---. U V . W X . Y . Z---.
|
||||
|\ | | | | | | | | | | | | | | / | | \ / \ / /
|
||||
| \ | | | |--- | | |-- --- | | | | / | . | X | /
|
||||
| \| | | | | \| | \ | | | | |/ |/ \| / \ | /
|
||||
' ' --- --- ' ' --- --- ' ' ' ' ' ---
|
||||
|
||||
&---. $--- (---. )--- [---. ]--- %.... *.... +.... -.... /.... =.... \....
|
||||
\ / | | | | | | | / .\|/ . | . . / . .\
|
||||
-- --- | | | | . .--- .--- .--- . / .--- . \
|
||||
| \ | | | | | | ./ | ./|\ . | . ./ . . \
|
||||
--- --- --- --- --- --- . . . . . .--- .
|
||||
|
||||
_.... '....
|
||||
. . |
|
||||
. .
|
||||
. .
|
||||
.--- .
|
||||
23
venv/lib/python3.11/site-packages/gpiozero/fonts/7seg.txt
Normal file
23
venv/lib/python3.11/site-packages/gpiozero/fonts/7seg.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
# This file defines the default alphabet for 7-segment displays. The format is
|
||||
# fairly simple: the file is loaded as a whole and all blank and #-prefixed
|
||||
# lines are stripped out. Then all blank columns are stripped out. Finally the
|
||||
# remaining rows and columns are divided into 3x3 cells with the following
|
||||
# format:
|
||||
#
|
||||
# Xa.
|
||||
# fgb
|
||||
# edc
|
||||
#
|
||||
# Where X is the character being defined, and a..g are the segments that are
|
||||
# active. a, d, and g are considered active if they are "_" and inactive if
|
||||
# they are anything else. b, c, e, and f are considered active if they are "|"
|
||||
# and inactive if they are anything else. The top-right character (marked "."
|
||||
# in the diagram above) is ignored. The result is fairly visually obvious :)
|
||||
|
||||
. 0_ 1. 2_ 3_ 4. 5_ 6_ 7_ 8_ 9_
|
||||
... |.| ..| ._| ._| |_| |_. |_. ..| |_| |_|
|
||||
... |_| ..| |_. ._| ..| ._| |_| ..| |_| ._|
|
||||
|
||||
A_ B. C_ D. E_ F_ G_ H. I. J. L. N_ O. P_ Q_ R. S_ T. U. Y.
|
||||
|_| |_. |.. ._| |_. |_. |.. |_| |.. ..| |.. |.| ._. |_| |_| ._. |_. |_. |.| |_|
|
||||
|.| |_| |_. |_| |_. |.. |_| |.| |.. ._| |__ |.| |_| |.. ..| |.. ._| |_. |_| ._|
|
||||
224
venv/lib/python3.11/site-packages/gpiozero/fonts/__init__.py
Normal file
224
venv/lib/python3.11/site-packages/gpiozero/fonts/__init__.py
Normal file
@@ -0,0 +1,224 @@
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2021-2023 Dave Jones <dave@waveform.org.uk>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import io
|
||||
from collections import Counter
|
||||
from itertools import zip_longest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def load_segment_font(filename_or_obj, width, height, pins):
|
||||
"""
|
||||
A generic function for parsing segment font definition files.
|
||||
|
||||
If you're working with "standard" `7-segment`_ or `14-segment`_ displays
|
||||
you *don't* want this function; see :func:`load_font_7seg` or
|
||||
:func:`load_font_14seg` instead. However, if you are working with another
|
||||
style of segmented display and wish to construct a parser for a custom
|
||||
format, this is the function you want.
|
||||
|
||||
The *filename_or_obj* parameter is simply the file-like object or filename
|
||||
to load. This is typically passed in from the calling function.
|
||||
|
||||
The *width* and *height* parameters give the width and height in characters
|
||||
of each character definition. For example, these are 3 and 3 for 7-segment
|
||||
displays. Finally, *pins* is a list of tuples that defines the position of
|
||||
each pin definition in the character array, and the character that marks
|
||||
that position "active".
|
||||
|
||||
For example, for 7-segment displays this function is called as follows::
|
||||
|
||||
load_segment_font(filename_or_obj, width=3, height=3, pins=[
|
||||
(1, '_'), (5, '|'), (8, '|'), (7, '_'),
|
||||
(6, '|'), (3, '|'), (4, '_')])
|
||||
|
||||
This dictates that each character will be defined by a 3x3 character grid
|
||||
which will be converted into a nine-character string like so:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
012
|
||||
345 ==> '012345678'
|
||||
678
|
||||
|
||||
Position 0 is always assumed to be the character being defined. The *pins*
|
||||
list then specifies: the first pin is the character at position 1 which
|
||||
will be "on" when that character is "_". The second pin is the character
|
||||
at position 5 which will be "on" when that character is "|", and so on.
|
||||
|
||||
.. _7-segment: https://en.wikipedia.org/wiki/Seven-segment_display
|
||||
.. _14-segment: https://en.wikipedia.org/wiki/Fourteen-segment_display
|
||||
"""
|
||||
assert 0 < len(pins) <= (width * height) - 1
|
||||
if isinstance(filename_or_obj, bytes):
|
||||
filename_or_obj = filename_or_obj.decode('utf-8')
|
||||
opened = isinstance(filename_or_obj, (str, Path))
|
||||
if opened:
|
||||
filename_or_obj = io.open(filename_or_obj, 'r')
|
||||
try:
|
||||
lines = filename_or_obj.read()
|
||||
if isinstance(lines, bytes):
|
||||
lines = lines.decode('utf-8')
|
||||
lines = lines.splitlines()
|
||||
finally:
|
||||
if opened:
|
||||
filename_or_obj.close()
|
||||
|
||||
# Strip out comments and blank lines, but remember the original line
|
||||
# numbers of each row for error reporting purposes
|
||||
rows = [
|
||||
(index, line) for index, line in enumerate(lines, start=1)
|
||||
# Strip comments and blank (or whitespace) lines
|
||||
if line.strip() and not line.startswith('#')
|
||||
]
|
||||
line_numbers = {
|
||||
row_index: line_index
|
||||
for row_index, (line_index, row) in enumerate(rows)
|
||||
}
|
||||
rows = [row for index, row in rows]
|
||||
if len(rows) % height:
|
||||
raise ValueError(
|
||||
f'number of definition lines is not divisible by {height}')
|
||||
|
||||
# Strip out blank columns then transpose back to rows, and make sure
|
||||
# everything is the right "shape"
|
||||
for n in range(0, len(rows), height):
|
||||
cols = [
|
||||
col for col in zip_longest(*rows[n:n + height], fillvalue=' ')
|
||||
# Strip blank (or whitespace) columns
|
||||
if ''.join(col).strip()
|
||||
]
|
||||
rows[n:n + height] = list(zip(*cols))
|
||||
for row_index, row in enumerate(rows):
|
||||
if len(row) % width:
|
||||
raise ValueError(
|
||||
f'length of definitions starting on line '
|
||||
f'{line_numbers[row_index]} is not divisible by {width}')
|
||||
|
||||
# Split rows up into character definitions. After this, chars will be a
|
||||
# list of strings each with width x height characters. The first character
|
||||
# in each string will be the character being defined
|
||||
chars = [
|
||||
''.join(
|
||||
char
|
||||
for row in rows[y::height]
|
||||
for char in row
|
||||
)[x::width]
|
||||
for y in range(height)
|
||||
for x in range(width)
|
||||
]
|
||||
chars = [''.join(char) for char in zip(*chars)]
|
||||
|
||||
# Strip out blank entries (a consequence of zip_longest above) and check
|
||||
# there're no repeat definitions
|
||||
chars = [char for char in chars if char.strip()]
|
||||
counts = Counter(char[0] for char in chars)
|
||||
for char, count in counts.most_common():
|
||||
if count > 1:
|
||||
raise ValueError(f'multiple definitions for {char!r}')
|
||||
|
||||
return {
|
||||
char[0]: tuple(int(char[pos] == on) for pos, on in pins)
|
||||
for char in chars
|
||||
}
|
||||
|
||||
|
||||
def load_font_7seg(filename_or_obj):
|
||||
"""
|
||||
Given a filename or a file-like object, parse it as an font definition for
|
||||
a `7-segment display`_, returning a :class:`dict` suitable for use with
|
||||
:class:`~gpiozero.LEDCharDisplay`.
|
||||
|
||||
The file-format is a simple text-based format in which blank and #-prefixed
|
||||
lines are ignored. All other lines are assumed to be groups of character
|
||||
definitions which are cells of 3x3 characters laid out as follows:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
Ca
|
||||
fgb
|
||||
edc
|
||||
|
||||
Where C is the character being defined, and a-g define the states of the
|
||||
LEDs for that position. a, d, and g are on if they are "_". b, c, e, and
|
||||
f are on if they are "|". Any other character in these positions is
|
||||
considered off. For example, you might define the following characters:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
. 0_ 1. 2_ 3_ 4. 5_ 6_ 7_ 8_ 9_
|
||||
... |.| ..| ._| ._| |_| |_. |_. ..| |_| |_|
|
||||
... |_| ..| |_. ._| ..| ._| |_| ..| |_| ._|
|
||||
|
||||
In the example above, empty locations are marked with "." but could mostly
|
||||
be left as spaces. However, the first item defines the space (" ")
|
||||
character and needs *some* non-space characters in its definition as the
|
||||
parser also strips empty columns (as typically occur between character
|
||||
definitions). This is also why the definition for "1" must include
|
||||
something to fill the middle column.
|
||||
|
||||
.. _7-segment display: https://en.wikipedia.org/wiki/Seven-segment_display
|
||||
"""
|
||||
return load_segment_font(filename_or_obj, width=3, height=3, pins=[
|
||||
(1, '_'), (5, '|'), (8, '|'), (7, '_'),
|
||||
(6, '|'), (3, '|'), (4, '_')])
|
||||
|
||||
|
||||
def load_font_14seg(filename_or_obj):
|
||||
"""
|
||||
Given a filename or a file-like object, parse it as a font definition for a
|
||||
`14-segment display`_, returning a :class:`dict` suitable for use with
|
||||
:class:`~gpiozero.LEDCharDisplay`.
|
||||
|
||||
The file-format is a simple text-based format in which blank and #-prefixed
|
||||
lines are ignored. All other lines are assumed to be groups of character
|
||||
definitions which are cells of 5x5 characters laid out as follows:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
X.a..
|
||||
fijkb
|
||||
.g.h.
|
||||
elmnc
|
||||
..d..
|
||||
|
||||
Where X is the character being defined, and a-n define the states of the
|
||||
LEDs for that position. a, d, g, and h are on if they are "-". b, c, e, f,
|
||||
j, and m are on if they are "|". i and n are on if they are "\\". Finally,
|
||||
k and l are on if they are "/". Any other character in these positions is
|
||||
considered off. For example, you might define the following characters:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
.... 0--- 1.. 2--- 3--- 4 5--- 6--- 7---. 8--- 9---
|
||||
..... | /| /| | | | | | | / | | | |
|
||||
..... | / | | --- -- ---| --- |--- | --- ---|
|
||||
..... |/ | | | | | | | | | | | |
|
||||
..... --- --- --- --- --- ---
|
||||
|
||||
In the example above, several locations have extraneous characters. For
|
||||
example, the "/" in the center of the "0" definition, or the "-" in the
|
||||
middle of the "8". These locations are ignored, but filled in nonetheless
|
||||
to make the shape more obvious.
|
||||
|
||||
These extraneous locations could equally well be left as spaces. However,
|
||||
the first item defines the space (" ") character and needs *some* non-space
|
||||
characters in its definition as the parser also strips empty columns (as
|
||||
typically occur between character definitions) and verifies that
|
||||
definitions are 5 columns wide and 5 rows high.
|
||||
|
||||
This also explains why place-holder characters (".") have been inserted at
|
||||
the top of the definition of the "1" character. Otherwise the parser will
|
||||
strip these empty columns and decide the definition is invalid (as the
|
||||
result is only 3 columns wide).
|
||||
|
||||
.. _14-segment display: https://en.wikipedia.org/wiki/Fourteen-segment_display
|
||||
"""
|
||||
return load_segment_font(filename_or_obj, width=5, height=5, pins=[
|
||||
(2, '-'), (9, '|'), (19, '|'), (22, '-'),
|
||||
(15, '|'), (5, '|'), (11, '-'), (13, '-'),
|
||||
(6, '\\'), (7, '|'), (8, '/'), (16, '/'),
|
||||
(17, '|'), (18, '\\')])
|
||||
Binary file not shown.
1371
venv/lib/python3.11/site-packages/gpiozero/input_devices.py
Normal file
1371
venv/lib/python3.11/site-packages/gpiozero/input_devices.py
Normal file
File diff suppressed because it is too large
Load Diff
744
venv/lib/python3.11/site-packages/gpiozero/internal_devices.py
Normal file
744
venv/lib/python3.11/site-packages/gpiozero/internal_devices.py
Normal file
@@ -0,0 +1,744 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2023 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2017-2021 Ben Nuttall <ben@bennuttall.com>
|
||||
# Copyright (c) 2019 Jeevan M R <14.jeevan@gmail.com>
|
||||
# Copyright (c) 2019 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import os
|
||||
import io
|
||||
import warnings
|
||||
import subprocess
|
||||
from datetime import datetime, time
|
||||
|
||||
from .devices import Device
|
||||
from .mixins import EventsMixin, event
|
||||
from .threads import GPIOThread
|
||||
from .exc import ThresholdOutOfRange, DeviceClosed
|
||||
|
||||
|
||||
class InternalDevice(EventsMixin, Device):
|
||||
"""
|
||||
Extends :class:`Device` to provide a basis for devices which have no
|
||||
specific hardware representation. These are effectively pseudo-devices and
|
||||
usually represent operating system services like the internal clock, file
|
||||
systems or network facilities.
|
||||
"""
|
||||
def __init__(self, *, pin_factory=None):
|
||||
self._closed = False
|
||||
super().__init__(pin_factory=pin_factory)
|
||||
|
||||
def close(self):
|
||||
self._closed = True
|
||||
super().close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self._closed
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
self._check_open()
|
||||
return f"<gpiozero.{self.__class__.__name__} object>"
|
||||
except DeviceClosed:
|
||||
return f"<gpiozero.{self.__class__.__name__} object closed>"
|
||||
|
||||
|
||||
class PolledInternalDevice(InternalDevice):
|
||||
"""
|
||||
Extends :class:`InternalDevice` to provide a background thread to poll
|
||||
internal devices that lack any other mechanism to inform the instance of
|
||||
changes.
|
||||
"""
|
||||
def __init__(self, *, event_delay=1.0, pin_factory=None):
|
||||
self._event_thread = None
|
||||
self._event_delay = event_delay
|
||||
super().__init__(pin_factory=pin_factory)
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
self._start_stop_events(False)
|
||||
except AttributeError:
|
||||
pass # pragma: no cover
|
||||
super().close()
|
||||
|
||||
@property
|
||||
def event_delay(self):
|
||||
"""
|
||||
The delay between sampling the device's value for the purposes of
|
||||
firing events.
|
||||
|
||||
Note that this only applies to events assigned to attributes like
|
||||
:attr:`~EventsMixin.when_activated` and
|
||||
:attr:`~EventsMixin.when_deactivated`. When using the
|
||||
:attr:`~SourceMixin.source` and :attr:`~ValuesMixin.values` properties,
|
||||
the sampling rate is controlled by the
|
||||
:attr:`~SourceMixin.source_delay` property.
|
||||
"""
|
||||
return self._event_delay
|
||||
|
||||
@event_delay.setter
|
||||
def event_delay(self, value):
|
||||
self._event_delay = float(value)
|
||||
|
||||
def wait_for_active(self, timeout=None):
|
||||
self._start_stop_events(True)
|
||||
try:
|
||||
return super().wait_for_active(timeout)
|
||||
finally:
|
||||
self._start_stop_events(
|
||||
self.when_activated or self.when_deactivated)
|
||||
|
||||
def wait_for_inactive(self, timeout=None):
|
||||
self._start_stop_events(True)
|
||||
try:
|
||||
return super().wait_for_inactive(timeout)
|
||||
finally:
|
||||
self._start_stop_events(
|
||||
self.when_activated or self.when_deactivated)
|
||||
|
||||
def _watch_value(self):
|
||||
while not self._event_thread.stopping.wait(self._event_delay):
|
||||
self._fire_events(self.pin_factory.ticks(), self.is_active)
|
||||
|
||||
def _start_stop_events(self, enabled):
|
||||
if self._event_thread and not enabled:
|
||||
self._event_thread.stop()
|
||||
self._event_thread = None
|
||||
elif not self._event_thread and enabled:
|
||||
self._event_thread = GPIOThread(self._watch_value)
|
||||
self._event_thread.start()
|
||||
|
||||
|
||||
class PingServer(PolledInternalDevice):
|
||||
"""
|
||||
Extends :class:`PolledInternalDevice` to provide a device which is active
|
||||
when a *host* (domain name or IP address) can be pinged.
|
||||
|
||||
The following example lights an LED while ``google.com`` is reachable::
|
||||
|
||||
from gpiozero import PingServer, LED
|
||||
from signal import pause
|
||||
|
||||
google = PingServer('google.com')
|
||||
led = LED(4)
|
||||
|
||||
google.when_activated = led.on
|
||||
google.when_deactivated = led.off
|
||||
|
||||
pause()
|
||||
|
||||
:param str host:
|
||||
The hostname or IP address to attempt to ping.
|
||||
|
||||
:type event_delay: float
|
||||
:param event_delay:
|
||||
The number of seconds between pings (defaults to 10 seconds).
|
||||
|
||||
:type pin_factory: Factory or None
|
||||
:param pin_factory:
|
||||
See :doc:`api_pins` for more information (this is an advanced feature
|
||||
which most users can ignore).
|
||||
"""
|
||||
def __init__(self, host, *, event_delay=10.0, pin_factory=None):
|
||||
self._host = host
|
||||
super().__init__(event_delay=event_delay, pin_factory=pin_factory)
|
||||
self._fire_events(self.pin_factory.ticks(), self.is_active)
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
self._check_open()
|
||||
return f'<gpiozero.PingServer object host="{self.host}">'
|
||||
except DeviceClosed:
|
||||
return super().__repr__()
|
||||
|
||||
@property
|
||||
def host(self):
|
||||
"""
|
||||
The hostname or IP address to test whenever :attr:`value` is queried.
|
||||
"""
|
||||
return self._host
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
Returns :data:`1` if the host returned a single ping, and :data:`0`
|
||||
otherwise.
|
||||
"""
|
||||
# XXX This is doing a DNS lookup every time it's queried; should we
|
||||
# call gethostbyname in the constructor and ping that instead (good
|
||||
# for consistency, but what if the user *expects* the host to change
|
||||
# address?)
|
||||
with io.open(os.devnull, 'wb') as devnull:
|
||||
try:
|
||||
subprocess.check_call(
|
||||
['ping', '-c1', self.host],
|
||||
stdout=devnull, stderr=devnull)
|
||||
except subprocess.CalledProcessError:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
when_activated = event(
|
||||
"""
|
||||
The function to run when the device changes state from inactive
|
||||
(host unresponsive) to active (host responsive).
|
||||
|
||||
This can be set to a function which accepts no (mandatory)
|
||||
parameters, or a Python function which accepts a single mandatory
|
||||
parameter (with as many optional parameters as you like). If the
|
||||
function accepts a single mandatory parameter, the device that
|
||||
activated it will be passed as that parameter.
|
||||
|
||||
Set this property to ``None`` (the default) to disable the event.
|
||||
""")
|
||||
|
||||
when_deactivated = event(
|
||||
"""
|
||||
The function to run when the device changes state from inactive
|
||||
(host responsive) to active (host unresponsive).
|
||||
|
||||
This can be set to a function which accepts no (mandatory)
|
||||
parameters, or a Python function which accepts a single mandatory
|
||||
parameter (with as many optional parameters as you like). If the
|
||||
function accepts a single mandatory parameter, the device that
|
||||
activated it will be passed as that parameter.
|
||||
|
||||
Set this property to ``None`` (the default) to disable the event.
|
||||
""")
|
||||
|
||||
|
||||
class CPUTemperature(PolledInternalDevice):
|
||||
"""
|
||||
Extends :class:`PolledInternalDevice` to provide a device which is active
|
||||
when the CPU temperature exceeds the *threshold* value.
|
||||
|
||||
The following example plots the CPU's temperature on an LED bar graph::
|
||||
|
||||
from gpiozero import LEDBarGraph, CPUTemperature
|
||||
from signal import pause
|
||||
|
||||
# Use minimums and maximums that are closer to "normal" usage so the
|
||||
# bar graph is a bit more "lively"
|
||||
cpu = CPUTemperature(min_temp=50, max_temp=90)
|
||||
|
||||
print(f'Initial temperature: {cpu.temperature}C')
|
||||
|
||||
graph = LEDBarGraph(5, 6, 13, 19, 25, pwm=True)
|
||||
graph.source = cpu
|
||||
|
||||
pause()
|
||||
|
||||
:param str sensor_file:
|
||||
The file from which to read the temperature. This defaults to the
|
||||
sysfs file :file:`/sys/class/thermal/thermal_zone0/temp`. Whatever
|
||||
file is specified is expected to contain a single line containing the
|
||||
temperature in milli-degrees celsius.
|
||||
|
||||
:param float min_temp:
|
||||
The temperature at which :attr:`value` will read 0.0. This defaults to
|
||||
0.0.
|
||||
|
||||
:param float max_temp:
|
||||
The temperature at which :attr:`value` will read 1.0. This defaults to
|
||||
100.0.
|
||||
|
||||
:param float threshold:
|
||||
The temperature above which the device will be considered "active".
|
||||
(see :attr:`is_active`). This defaults to 80.0.
|
||||
|
||||
:type event_delay: float
|
||||
:param event_delay:
|
||||
The number of seconds between file reads (defaults to 5 seconds).
|
||||
|
||||
:type pin_factory: Factory or None
|
||||
:param pin_factory:
|
||||
See :doc:`api_pins` for more information (this is an advanced feature
|
||||
which most users can ignore).
|
||||
"""
|
||||
def __init__(self, sensor_file='/sys/class/thermal/thermal_zone0/temp', *,
|
||||
min_temp=0.0, max_temp=100.0, threshold=80.0, event_delay=5.0,
|
||||
pin_factory=None):
|
||||
self.sensor_file = sensor_file
|
||||
super().__init__(event_delay=event_delay, pin_factory=pin_factory)
|
||||
try:
|
||||
if min_temp >= max_temp:
|
||||
raise ValueError('max_temp must be greater than min_temp')
|
||||
self.min_temp = min_temp
|
||||
self.max_temp = max_temp
|
||||
if not min_temp <= threshold <= max_temp:
|
||||
warnings.warn(ThresholdOutOfRange(
|
||||
'threshold is outside of the range (min_temp, max_temp)'))
|
||||
self.threshold = threshold
|
||||
self._fire_events(self.pin_factory.ticks(), self.is_active)
|
||||
except:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
self._check_open()
|
||||
return (
|
||||
f'<gpiozero.{self.__class__.__name__} object '
|
||||
f'temperature={self.temperature:.2f}>')
|
||||
except DeviceClosed:
|
||||
return super().__repr__()
|
||||
|
||||
@property
|
||||
def temperature(self):
|
||||
"""
|
||||
Returns the current CPU temperature in degrees celsius.
|
||||
"""
|
||||
with io.open(self.sensor_file, 'r') as f:
|
||||
return float(f.read().strip()) / 1000
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
Returns the current CPU temperature as a value between 0.0
|
||||
(representing the *min_temp* value) and 1.0 (representing the
|
||||
*max_temp* value). These default to 0.0 and 100.0 respectively, hence
|
||||
:attr:`value` is :attr:`temperature` divided by 100 by default.
|
||||
"""
|
||||
temp_range = self.max_temp - self.min_temp
|
||||
return (self.temperature - self.min_temp) / temp_range
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
"""
|
||||
Returns :data:`True` when the CPU :attr:`temperature` exceeds the
|
||||
*threshold*.
|
||||
"""
|
||||
return self.temperature > self.threshold
|
||||
|
||||
when_activated = event(
|
||||
"""
|
||||
The function to run when the device changes state from inactive to
|
||||
active (temperature reaches *threshold*).
|
||||
|
||||
This can be set to a function which accepts no (mandatory)
|
||||
parameters, or a Python function which accepts a single mandatory
|
||||
parameter (with as many optional parameters as you like). If the
|
||||
function accepts a single mandatory parameter, the device that
|
||||
activated it will be passed as that parameter.
|
||||
|
||||
Set this property to ``None`` (the default) to disable the event.
|
||||
""")
|
||||
|
||||
when_deactivated = event(
|
||||
"""
|
||||
The function to run when the device changes state from active to
|
||||
inactive (temperature drops below *threshold*).
|
||||
|
||||
This can be set to a function which accepts no (mandatory)
|
||||
parameters, or a Python function which accepts a single mandatory
|
||||
parameter (with as many optional parameters as you like). If the
|
||||
function accepts a single mandatory parameter, the device that
|
||||
activated it will be passed as that parameter.
|
||||
|
||||
Set this property to ``None`` (the default) to disable the event.
|
||||
""")
|
||||
|
||||
|
||||
class LoadAverage(PolledInternalDevice):
|
||||
"""
|
||||
Extends :class:`PolledInternalDevice` to provide a device which is active
|
||||
when the CPU load average exceeds the *threshold* value.
|
||||
|
||||
The following example plots the load average on an LED bar graph::
|
||||
|
||||
from gpiozero import LEDBarGraph, LoadAverage
|
||||
from signal import pause
|
||||
|
||||
la = LoadAverage(min_load_average=0, max_load_average=2)
|
||||
graph = LEDBarGraph(5, 6, 13, 19, 25, pwm=True)
|
||||
|
||||
graph.source = la
|
||||
|
||||
pause()
|
||||
|
||||
:param str load_average_file:
|
||||
The file from which to read the load average. This defaults to the
|
||||
proc file :file:`/proc/loadavg`. Whatever file is specified is expected
|
||||
to contain three space-separated load averages at the beginning of the
|
||||
file, representing 1 minute, 5 minute and 15 minute averages
|
||||
respectively.
|
||||
|
||||
:param float min_load_average:
|
||||
The load average at which :attr:`value` will read 0.0. This defaults to
|
||||
0.0.
|
||||
|
||||
:param float max_load_average:
|
||||
The load average at which :attr:`value` will read 1.0. This defaults to
|
||||
1.0.
|
||||
|
||||
:param float threshold:
|
||||
The load average above which the device will be considered "active".
|
||||
(see :attr:`is_active`). This defaults to 0.8.
|
||||
|
||||
:param int minutes:
|
||||
The number of minutes over which to average the load. Must be 1, 5 or
|
||||
15. This defaults to 5.
|
||||
|
||||
:type event_delay: float
|
||||
:param event_delay:
|
||||
The number of seconds between file reads (defaults to 10 seconds).
|
||||
|
||||
:type pin_factory: Factory or None
|
||||
:param pin_factory:
|
||||
See :doc:`api_pins` for more information (this is an advanced feature
|
||||
which most users can ignore).
|
||||
"""
|
||||
def __init__(self, load_average_file='/proc/loadavg', *,
|
||||
min_load_average=0.0, max_load_average=1.0, threshold=0.8,
|
||||
minutes=5, event_delay=10.0, pin_factory=None):
|
||||
if min_load_average >= max_load_average:
|
||||
raise ValueError(
|
||||
'max_load_average must be greater than min_load_average')
|
||||
self.load_average_file = load_average_file
|
||||
self.min_load_average = min_load_average
|
||||
self.max_load_average = max_load_average
|
||||
if not min_load_average <= threshold <= max_load_average:
|
||||
warnings.warn(ThresholdOutOfRange(
|
||||
'threshold is outside of the range (min_load_average, '
|
||||
'max_load_average)'))
|
||||
self.threshold = threshold
|
||||
if minutes not in (1, 5, 15):
|
||||
raise ValueError('minutes must be 1, 5 or 15')
|
||||
self._load_average_file_column = {
|
||||
1: 0,
|
||||
5: 1,
|
||||
15: 2,
|
||||
}[minutes]
|
||||
super().__init__(event_delay=event_delay, pin_factory=pin_factory)
|
||||
self._fire_events(self.pin_factory.ticks(), None)
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
self._check_open()
|
||||
return (
|
||||
f'<gpiozero.{self.__class__.__name__} object '
|
||||
f'load average={self.load_average:.2f}>')
|
||||
except DeviceClosed:
|
||||
return super().__repr__()
|
||||
|
||||
@property
|
||||
def load_average(self):
|
||||
"""
|
||||
Returns the current load average.
|
||||
"""
|
||||
with io.open(self.load_average_file, 'r') as f:
|
||||
file_columns = f.read().strip().split()
|
||||
return float(file_columns[self._load_average_file_column])
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
Returns the current load average as a value between 0.0 (representing
|
||||
the *min_load_average* value) and 1.0 (representing the
|
||||
*max_load_average* value). These default to 0.0 and 1.0 respectively.
|
||||
"""
|
||||
load_average_range = self.max_load_average - self.min_load_average
|
||||
return (self.load_average - self.min_load_average) / load_average_range
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
"""
|
||||
Returns :data:`True` when the :attr:`load_average` exceeds the
|
||||
*threshold*.
|
||||
"""
|
||||
return self.load_average > self.threshold
|
||||
|
||||
when_activated = event(
|
||||
"""
|
||||
The function to run when the device changes state from inactive to
|
||||
active (load average reaches *threshold*).
|
||||
|
||||
This can be set to a function which accepts no (mandatory)
|
||||
parameters, or a Python function which accepts a single mandatory
|
||||
parameter (with as many optional parameters as you like). If the
|
||||
function accepts a single mandatory parameter, the device that
|
||||
activated it will be passed as that parameter.
|
||||
|
||||
Set this property to ``None`` (the default) to disable the event.
|
||||
""")
|
||||
|
||||
when_deactivated = event(
|
||||
"""
|
||||
The function to run when the device changes state from active to
|
||||
inactive (load average drops below *threshold*).
|
||||
|
||||
This can be set to a function which accepts no (mandatory)
|
||||
parameters, or a Python function which accepts a single mandatory
|
||||
parameter (with as many optional parameters as you like). If the
|
||||
function accepts a single mandatory parameter, the device that
|
||||
activated it will be passed as that parameter.
|
||||
|
||||
Set this property to ``None`` (the default) to disable the event.
|
||||
""")
|
||||
|
||||
|
||||
class TimeOfDay(PolledInternalDevice):
|
||||
"""
|
||||
Extends :class:`PolledInternalDevice` to provide a device which is active
|
||||
when the computer's clock indicates that the current time is between
|
||||
*start_time* and *end_time* (inclusive) which are :class:`~datetime.time`
|
||||
instances.
|
||||
|
||||
The following example turns on a lamp attached to an :class:`Energenie`
|
||||
plug between 07:00AM and 08:00AM::
|
||||
|
||||
from gpiozero import TimeOfDay, Energenie
|
||||
from datetime import time
|
||||
from signal import pause
|
||||
|
||||
lamp = Energenie(1)
|
||||
morning = TimeOfDay(time(7), time(8))
|
||||
|
||||
morning.when_activated = lamp.on
|
||||
morning.when_deactivated = lamp.off
|
||||
|
||||
pause()
|
||||
|
||||
Note that *start_time* may be greater than *end_time*, indicating a time
|
||||
period which crosses midnight.
|
||||
|
||||
:param ~datetime.time start_time:
|
||||
The time from which the device will be considered active.
|
||||
|
||||
:param ~datetime.time end_time:
|
||||
The time after which the device will be considered inactive.
|
||||
|
||||
:param bool utc:
|
||||
If :data:`True` (the default), a naive UTC time will be used for the
|
||||
comparison rather than a local time-zone reading.
|
||||
|
||||
:type event_delay: float
|
||||
:param event_delay:
|
||||
The number of seconds between file reads (defaults to 10 seconds).
|
||||
|
||||
:type pin_factory: Factory or None
|
||||
:param pin_factory:
|
||||
See :doc:`api_pins` for more information (this is an advanced feature
|
||||
which most users can ignore).
|
||||
"""
|
||||
def __init__(self, start_time, end_time, *, utc=True, event_delay=5.0,
|
||||
pin_factory=None):
|
||||
self._start_time = None
|
||||
self._end_time = None
|
||||
self._utc = True
|
||||
super().__init__(event_delay=event_delay, pin_factory=pin_factory)
|
||||
try:
|
||||
self._start_time = self._validate_time(start_time)
|
||||
self._end_time = self._validate_time(end_time)
|
||||
if self.start_time == self.end_time:
|
||||
raise ValueError('end_time cannot equal start_time')
|
||||
self._utc = utc
|
||||
self._fire_events(self.pin_factory.ticks(), self.is_active)
|
||||
except:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
self._check_open()
|
||||
return (
|
||||
f'<gpiozero.{self.__class__.__name__} object active between '
|
||||
f'{self.start_time} and {self.end_time} '
|
||||
f'{("local", "UTC")[self.utc]}>')
|
||||
except DeviceClosed:
|
||||
return super().__repr__()
|
||||
|
||||
def _validate_time(self, value):
|
||||
if isinstance(value, datetime):
|
||||
value = value.time()
|
||||
if not isinstance(value, time):
|
||||
raise ValueError(
|
||||
'start_time and end_time must be a datetime, or time instance')
|
||||
return value
|
||||
|
||||
@property
|
||||
def start_time(self):
|
||||
"""
|
||||
The time of day after which the device will be considered active.
|
||||
"""
|
||||
return self._start_time
|
||||
|
||||
@property
|
||||
def end_time(self):
|
||||
"""
|
||||
The time of day after which the device will be considered inactive.
|
||||
"""
|
||||
return self._end_time
|
||||
|
||||
@property
|
||||
def utc(self):
|
||||
"""
|
||||
If :data:`True`, use a naive UTC time reading for comparison instead of
|
||||
a local timezone reading.
|
||||
"""
|
||||
return self._utc
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
Returns :data:`1` when the system clock reads between :attr:`start_time`
|
||||
and :attr:`end_time`, and :data:`0` otherwise. If :attr:`start_time` is
|
||||
greater than :attr:`end_time` (indicating a period that crosses
|
||||
midnight), then this returns :data:`1` when the current time is
|
||||
greater than :attr:`start_time` or less than :attr:`end_time`.
|
||||
"""
|
||||
now = datetime.utcnow().time() if self.utc else datetime.now().time()
|
||||
if self.start_time < self.end_time:
|
||||
return int(self.start_time <= now <= self.end_time)
|
||||
else:
|
||||
return int(not self.end_time < now < self.start_time)
|
||||
|
||||
when_activated = event(
|
||||
"""
|
||||
The function to run when the device changes state from inactive to
|
||||
active (time reaches *start_time*).
|
||||
|
||||
This can be set to a function which accepts no (mandatory)
|
||||
parameters, or a Python function which accepts a single mandatory
|
||||
parameter (with as many optional parameters as you like). If the
|
||||
function accepts a single mandatory parameter, the device that
|
||||
activated it will be passed as that parameter.
|
||||
|
||||
Set this property to ``None`` (the default) to disable the event.
|
||||
""")
|
||||
|
||||
when_deactivated = event(
|
||||
"""
|
||||
The function to run when the device changes state from active to
|
||||
inactive (time reaches *end_time*).
|
||||
|
||||
This can be set to a function which accepts no (mandatory)
|
||||
parameters, or a Python function which accepts a single mandatory
|
||||
parameter (with as many optional parameters as you like). If the
|
||||
function accepts a single mandatory parameter, the device that
|
||||
activated it will be passed as that parameter.
|
||||
|
||||
Set this property to ``None`` (the default) to disable the event.
|
||||
""")
|
||||
|
||||
|
||||
class DiskUsage(PolledInternalDevice):
|
||||
"""
|
||||
Extends :class:`PolledInternalDevice` to provide a device which is active
|
||||
when the disk space used exceeds the *threshold* value.
|
||||
|
||||
The following example plots the disk usage on an LED bar graph::
|
||||
|
||||
from gpiozero import LEDBarGraph, DiskUsage
|
||||
from signal import pause
|
||||
|
||||
disk = DiskUsage()
|
||||
|
||||
print(f'Current disk usage: {disk.usage}%')
|
||||
|
||||
graph = LEDBarGraph(5, 6, 13, 19, 25, pwm=True)
|
||||
graph.source = disk
|
||||
|
||||
pause()
|
||||
|
||||
:param str filesystem:
|
||||
A path within the filesystem for which the disk usage needs to be
|
||||
computed. This defaults to :file:`/`, which is the root filesystem.
|
||||
|
||||
:param float threshold:
|
||||
The disk usage percentage above which the device will be considered
|
||||
"active" (see :attr:`is_active`). This defaults to 90.0.
|
||||
|
||||
:type event_delay: float
|
||||
:param event_delay:
|
||||
The number of seconds between file reads (defaults to 30 seconds).
|
||||
|
||||
:type pin_factory: Factory or None
|
||||
:param pin_factory:
|
||||
See :doc:`api_pins` for more information (this is an advanced feature
|
||||
which most users can ignore).
|
||||
"""
|
||||
def __init__(self, filesystem='/', *, threshold=90.0, event_delay=30.0,
|
||||
pin_factory=None):
|
||||
super().__init__(
|
||||
event_delay=event_delay, pin_factory=pin_factory)
|
||||
os.statvfs(filesystem)
|
||||
if not 0 <= threshold <= 100:
|
||||
warnings.warn(ThresholdOutOfRange(
|
||||
'threshold is outside of the range (0, 100)'))
|
||||
self.filesystem = filesystem
|
||||
self.threshold = threshold
|
||||
self._fire_events(self.pin_factory.ticks(), None)
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
self._check_open()
|
||||
return (
|
||||
f'<gpiozero.{self.__class__.__name__} object '
|
||||
f'usage={self.usage:.2f}>')
|
||||
except DeviceClosed:
|
||||
return super().__repr__()
|
||||
|
||||
@property
|
||||
def usage(self):
|
||||
"""
|
||||
Returns the current disk usage in percentage.
|
||||
"""
|
||||
return self.value * 100
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
Returns the current disk usage as a value between 0.0 and 1.0 by
|
||||
dividing :attr:`usage` by 100.
|
||||
"""
|
||||
# This slightly convoluted calculation is equivalent to df's "Use%";
|
||||
# it calculates the percentage of FS usage as a proportion of the
|
||||
# space available to *non-root users*. Technically this means it can
|
||||
# exceed 100% (when FS is filled to the point that only root can write
|
||||
# to it), hence the clamp.
|
||||
vfs = os.statvfs(self.filesystem)
|
||||
used = vfs.f_blocks - vfs.f_bfree
|
||||
total = used + vfs.f_bavail
|
||||
return min(1.0, used / total)
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
"""
|
||||
Returns :data:`True` when the disk :attr:`usage` exceeds the
|
||||
*threshold*.
|
||||
"""
|
||||
return self.usage > self.threshold
|
||||
|
||||
when_activated = event(
|
||||
"""
|
||||
The function to run when the device changes state from inactive to
|
||||
active (disk usage reaches *threshold*).
|
||||
|
||||
This can be set to a function which accepts no (mandatory)
|
||||
parameters, or a Python function which accepts a single mandatory
|
||||
parameter (with as many optional parameters as you like). If the
|
||||
function accepts a single mandatory parameter, the device that
|
||||
activated it will be passed as that parameter.
|
||||
|
||||
Set this property to ``None`` (the default) to disable the event.
|
||||
""")
|
||||
|
||||
when_deactivated = event(
|
||||
"""
|
||||
The function to run when the device changes state from active to
|
||||
inactive (disk usage drops below *threshold*).
|
||||
|
||||
This can be set to a function which accepts no (mandatory)
|
||||
parameters, or a Python function which accepts a single mandatory
|
||||
parameter (with as many optional parameters as you like). If the
|
||||
function accepts a single mandatory parameter, the device that
|
||||
activated it will be passed as that parameter.
|
||||
|
||||
Set this property to ``None`` (the default) to disable the event.
|
||||
""")
|
||||
588
venv/lib/python3.11/site-packages/gpiozero/mixins.py
Normal file
588
venv/lib/python3.11/site-packages/gpiozero/mixins.py
Normal file
@@ -0,0 +1,588 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2023 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2018-2021 Ben Nuttall <ben@bennuttall.com>
|
||||
# Copyright (c) 2020 Fangchen Li <fangchen.li@outlook.com>
|
||||
# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import inspect
|
||||
import weakref
|
||||
import warnings
|
||||
from functools import wraps, partial
|
||||
from threading import Event
|
||||
from collections import deque
|
||||
from statistics import median
|
||||
|
||||
from .threads import GPIOThread
|
||||
from .exc import (
|
||||
BadEventHandler,
|
||||
BadWaitTime,
|
||||
BadQueueLen,
|
||||
DeviceClosed,
|
||||
CallbackSetToNone,
|
||||
)
|
||||
|
||||
callback_warning = (
|
||||
'The callback was set to None. This may have been unintentional '
|
||||
'e.g. btn.when_pressed = pressed() instead of btn.when_pressed = pressed'
|
||||
)
|
||||
|
||||
|
||||
class ValuesMixin:
|
||||
"""
|
||||
Adds a :attr:`values` property to the class which returns an infinite
|
||||
generator of readings from the :attr:`~Device.value` property. There is
|
||||
rarely a need to use this mixin directly as all base classes in GPIO Zero
|
||||
include it.
|
||||
|
||||
.. note::
|
||||
|
||||
Use this mixin *first* in the parent class list.
|
||||
"""
|
||||
|
||||
@property
|
||||
def values(self):
|
||||
"""
|
||||
An infinite iterator of values read from :attr:`value`.
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
yield self.value
|
||||
except DeviceClosed:
|
||||
break
|
||||
|
||||
|
||||
class SourceMixin:
|
||||
"""
|
||||
Adds a :attr:`source` property to the class which, given an iterable or a
|
||||
:class:`ValuesMixin` descendent, sets :attr:`~Device.value` to each member
|
||||
of that iterable until it is exhausted. This mixin is generally included in
|
||||
novel output devices to allow their state to be driven from another device.
|
||||
|
||||
.. note::
|
||||
|
||||
Use this mixin *first* in the parent class list.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._source = None
|
||||
self._source_thread = None
|
||||
self._source_delay = 0.01
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def close(self):
|
||||
self.source = None
|
||||
super().close()
|
||||
|
||||
def _copy_values(self, source):
|
||||
for v in source:
|
||||
self.value = v
|
||||
if self._source_thread.stopping.wait(self._source_delay):
|
||||
break
|
||||
|
||||
@property
|
||||
def source_delay(self):
|
||||
"""
|
||||
The delay (measured in seconds) in the loop used to read values from
|
||||
:attr:`source`. Defaults to 0.01 seconds which is generally sufficient
|
||||
to keep CPU usage to a minimum while providing adequate responsiveness.
|
||||
"""
|
||||
return self._source_delay
|
||||
|
||||
@source_delay.setter
|
||||
def source_delay(self, value):
|
||||
if value < 0:
|
||||
raise BadWaitTime('source_delay must be 0 or greater')
|
||||
self._source_delay = float(value)
|
||||
|
||||
@property
|
||||
def source(self):
|
||||
"""
|
||||
The iterable to use as a source of values for :attr:`value`.
|
||||
"""
|
||||
return self._source
|
||||
|
||||
@source.setter
|
||||
def source(self, value):
|
||||
if getattr(self, '_source_thread', None):
|
||||
self._source_thread.stop()
|
||||
self._source_thread = None
|
||||
if isinstance(value, ValuesMixin):
|
||||
value = value.values
|
||||
self._source = value
|
||||
if value is not None:
|
||||
self._source_thread = GPIOThread(self._copy_values, (value,))
|
||||
self._source_thread.start()
|
||||
|
||||
|
||||
class SharedMixin:
|
||||
"""
|
||||
This mixin marks a class as "shared". In this case, the meta-class
|
||||
(GPIOMeta) will use :meth:`_shared_key` to convert the constructor
|
||||
arguments to an immutable key, and will check whether any existing
|
||||
instances match that key. If they do, they will be returned by the
|
||||
constructor instead of a new instance. An internal reference counter is
|
||||
used to determine how many times an instance has been "constructed" in this
|
||||
way.
|
||||
|
||||
When :meth:`~Device.close` is called, an internal reference counter will be
|
||||
decremented and the instance will only close when it reaches zero.
|
||||
"""
|
||||
_instances = {}
|
||||
|
||||
def __del__(self):
|
||||
self._refs = 0
|
||||
super().__del__()
|
||||
|
||||
@classmethod
|
||||
def _shared_key(cls, *args, **kwargs):
|
||||
"""
|
||||
This is called with the constructor arguments to generate a unique
|
||||
key (which must be storable in a :class:`dict` and, thus, immutable
|
||||
and hashable) representing the instance that can be shared. This must
|
||||
be overridden by descendents.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class event:
|
||||
"""
|
||||
A descriptor representing a callable event on a class descending from
|
||||
:class:`EventsMixin`.
|
||||
|
||||
Instances of this class are very similar to a :class:`property` but also
|
||||
deal with notifying the owning class when events are assigned (or
|
||||
unassigned) and wrapping callbacks implicitly as appropriate.
|
||||
"""
|
||||
def __init__(self, doc=None):
|
||||
self.handlers = {}
|
||||
self.__doc__ = doc
|
||||
|
||||
def _wrap_callback(self, instance, fn):
|
||||
if not callable(fn):
|
||||
raise BadEventHandler('value must be None or a callable')
|
||||
# If fn is wrapped with partial (i.e. partial, partialmethod, or wraps
|
||||
# has been used to produce it) we need to dig out the "real" function
|
||||
# that's been wrapped along with all the mandatory positional args
|
||||
# used in the wrapper so we can test the binding
|
||||
args = ()
|
||||
wrapped_fn = fn
|
||||
while isinstance(wrapped_fn, partial):
|
||||
args = wrapped_fn.args + args
|
||||
wrapped_fn = wrapped_fn.func
|
||||
if inspect.isbuiltin(wrapped_fn):
|
||||
# We can't introspect the prototype of builtins. In this case we
|
||||
# assume that the builtin has no (mandatory) parameters; this is
|
||||
# the most reasonable assumption on the basis that pre-existing
|
||||
# builtins have no knowledge of gpiozero, and the sole parameter
|
||||
# we would pass is a gpiozero object
|
||||
return fn
|
||||
else:
|
||||
# Try binding ourselves to the argspec of the provided callable.
|
||||
# If this works, assume the function is capable of accepting no
|
||||
# parameters
|
||||
try:
|
||||
inspect.getcallargs(wrapped_fn, *args)
|
||||
return fn
|
||||
except TypeError:
|
||||
try:
|
||||
# If the above fails, try binding with a single parameter
|
||||
# (ourselves). If this works, wrap the specified callback
|
||||
inspect.getcallargs(wrapped_fn, *(args + (instance,)))
|
||||
@wraps(fn)
|
||||
def wrapper():
|
||||
return fn(instance)
|
||||
return wrapper
|
||||
except TypeError:
|
||||
raise BadEventHandler(
|
||||
'value must be a callable which accepts up to one '
|
||||
'mandatory parameter')
|
||||
|
||||
def __get__(self, instance, owner=None):
|
||||
if instance is None:
|
||||
return self
|
||||
else:
|
||||
return self.handlers.get(id(instance))
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if value is None:
|
||||
try:
|
||||
del self.handlers[id(instance)]
|
||||
except KeyError:
|
||||
warnings.warn(CallbackSetToNone(callback_warning))
|
||||
else:
|
||||
self.handlers[id(instance)] = self._wrap_callback(instance, value)
|
||||
enabled = any(
|
||||
obj.handlers.get(id(instance))
|
||||
for name in dir(type(instance))
|
||||
for obj in (getattr(type(instance), name),)
|
||||
if isinstance(obj, event)
|
||||
)
|
||||
instance._start_stop_events(enabled)
|
||||
|
||||
|
||||
class EventsMixin:
|
||||
"""
|
||||
Adds edge-detected :meth:`when_activated` and :meth:`when_deactivated`
|
||||
events to a device based on changes to the :attr:`~Device.is_active`
|
||||
property common to all devices. Also adds :meth:`wait_for_active` and
|
||||
:meth:`wait_for_inactive` methods for level-waiting.
|
||||
|
||||
.. note::
|
||||
|
||||
Note that this mixin provides no means of actually firing its events;
|
||||
call :meth:`_fire_events` in sub-classes when device state changes to
|
||||
trigger the events. This should also be called once at the end of
|
||||
initialization to set initial states.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._active_event = Event()
|
||||
self._inactive_event = Event()
|
||||
self._last_active = None
|
||||
self._last_changed = self.pin_factory.ticks()
|
||||
|
||||
def _all_events(self):
|
||||
"""
|
||||
Generator function which yields all :class:`event` instances defined
|
||||
against this class.
|
||||
"""
|
||||
for name in dir(type(self)):
|
||||
obj = getattr(type(self), name)
|
||||
if isinstance(obj, event):
|
||||
yield obj
|
||||
|
||||
def close(self):
|
||||
for ev in self._all_events():
|
||||
try:
|
||||
del ev.handlers[id(self)]
|
||||
except KeyError:
|
||||
pass
|
||||
super().close()
|
||||
|
||||
def wait_for_active(self, timeout=None):
|
||||
"""
|
||||
Pause the script until the device is activated, or the timeout is
|
||||
reached.
|
||||
|
||||
:type timeout: float or None
|
||||
:param timeout:
|
||||
Number of seconds to wait before proceeding. If this is
|
||||
:data:`None` (the default), then wait indefinitely until the device
|
||||
is active.
|
||||
"""
|
||||
return self._active_event.wait(timeout)
|
||||
|
||||
def wait_for_inactive(self, timeout=None):
|
||||
"""
|
||||
Pause the script until the device is deactivated, or the timeout is
|
||||
reached.
|
||||
|
||||
:type timeout: float or None
|
||||
:param timeout:
|
||||
Number of seconds to wait before proceeding. If this is
|
||||
:data:`None` (the default), then wait indefinitely until the device
|
||||
is inactive.
|
||||
"""
|
||||
return self._inactive_event.wait(timeout)
|
||||
|
||||
when_activated = event(
|
||||
"""
|
||||
The function to run when the device changes state from inactive to
|
||||
active.
|
||||
|
||||
This can be set to a function which accepts no (mandatory) parameters,
|
||||
or a Python function which accepts a single mandatory parameter (with
|
||||
as many optional parameters as you like). If the function accepts a
|
||||
single mandatory parameter, the device that activated it will be passed
|
||||
as that parameter.
|
||||
|
||||
Set this property to :data:`None` (the default) to disable the event.
|
||||
""")
|
||||
|
||||
when_deactivated = event(
|
||||
"""
|
||||
The function to run when the device changes state from active to
|
||||
inactive.
|
||||
|
||||
This can be set to a function which accepts no (mandatory) parameters,
|
||||
or a Python function which accepts a single mandatory parameter (with
|
||||
as many optional parameters as you like). If the function accepts a
|
||||
single mandatory parameter, the device that deactivated it will be
|
||||
passed as that parameter.
|
||||
|
||||
Set this property to :data:`None` (the default) to disable the event.
|
||||
""")
|
||||
|
||||
@property
|
||||
def active_time(self):
|
||||
"""
|
||||
The length of time (in seconds) that the device has been active for.
|
||||
When the device is inactive, this is :data:`None`.
|
||||
"""
|
||||
if self._active_event.is_set():
|
||||
return self.pin_factory.ticks_diff(self.pin_factory.ticks(),
|
||||
self._last_changed)
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def inactive_time(self):
|
||||
"""
|
||||
The length of time (in seconds) that the device has been inactive for.
|
||||
When the device is active, this is :data:`None`.
|
||||
"""
|
||||
if self._inactive_event.is_set():
|
||||
return self.pin_factory.ticks_diff(self.pin_factory.ticks(),
|
||||
self._last_changed)
|
||||
else:
|
||||
return None
|
||||
|
||||
def _fire_activated(self):
|
||||
# These methods are largely here to be overridden by descendents
|
||||
if self.when_activated:
|
||||
self.when_activated()
|
||||
|
||||
def _fire_deactivated(self):
|
||||
# These methods are largely here to be overridden by descendents
|
||||
if self.when_deactivated:
|
||||
self.when_deactivated()
|
||||
|
||||
def _fire_events(self, ticks, new_active):
|
||||
"""
|
||||
This method should be called by descendents whenever the
|
||||
:attr:`~Device.is_active` property is likely to have changed (for
|
||||
example, in response to a pin's :attr:`~gpiozero.Pin.state` changing).
|
||||
|
||||
The *ticks* parameter must be set to the time when the change occurred;
|
||||
this can usually be obtained from the pin factory's
|
||||
:meth:`gpiozero.Factory.ticks` method but some pin implementations will
|
||||
implicitly provide the ticks when an event occurs as part of their
|
||||
reporting mechanism.
|
||||
|
||||
The *new_active* parameter must be set to the device's
|
||||
:attr:`~Device.is_active` value at the time indicated by *ticks* (which
|
||||
is not necessarily the value of :attr:`~Device.is_active` right now, if
|
||||
the pin factory provides means of reporting a pin's historical state).
|
||||
"""
|
||||
old_active, self._last_active = self._last_active, new_active
|
||||
if old_active is None:
|
||||
# Initial "indeterminate" state; set events but don't fire
|
||||
# callbacks as there's not necessarily an edge
|
||||
if new_active:
|
||||
self._active_event.set()
|
||||
else:
|
||||
self._inactive_event.set()
|
||||
elif old_active != new_active:
|
||||
self._last_changed = ticks
|
||||
if new_active:
|
||||
self._inactive_event.clear()
|
||||
self._active_event.set()
|
||||
self._fire_activated()
|
||||
else:
|
||||
self._active_event.clear()
|
||||
self._inactive_event.set()
|
||||
self._fire_deactivated()
|
||||
|
||||
def _start_stop_events(self, enabled):
|
||||
"""
|
||||
This is a stub method that only exists to be overridden by descendents.
|
||||
It is called when :class:`event` properties are assigned (including
|
||||
when set to :data:`None) to permit the owning instance to activate or
|
||||
deactivate monitoring facilities.
|
||||
|
||||
For example, if a descendent requires a background thread to monitor a
|
||||
device, it would be preferable to only run the thread if event handlers
|
||||
are present to respond to it.
|
||||
|
||||
The *enabled* parameter is :data:`False` when all :class:`event`
|
||||
properties on the owning class are :data:`None`, and :data:`True`
|
||||
otherwise.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class HoldMixin(EventsMixin):
|
||||
"""
|
||||
Extends :class:`EventsMixin` to add the :attr:`when_held` event and the
|
||||
machinery to fire that event repeatedly (when :attr:`hold_repeat` is
|
||||
:data:`True`) at internals defined by :attr:`hold_time`.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._hold_thread = None
|
||||
super().__init__(*args, **kwargs)
|
||||
self._when_held = None
|
||||
self._held_from = None
|
||||
self._hold_time = 1
|
||||
self._hold_repeat = False
|
||||
self._hold_thread = HoldThread(self)
|
||||
|
||||
def close(self):
|
||||
if self._hold_thread is not None:
|
||||
self._hold_thread.stop()
|
||||
self._hold_thread = None
|
||||
super().close()
|
||||
|
||||
def _fire_activated(self):
|
||||
super()._fire_activated()
|
||||
self._hold_thread.holding.set()
|
||||
|
||||
def _fire_deactivated(self):
|
||||
self._held_from = None
|
||||
super()._fire_deactivated()
|
||||
|
||||
def _fire_held(self):
|
||||
if self.when_held:
|
||||
self.when_held()
|
||||
|
||||
when_held = event(
|
||||
"""
|
||||
The function to run when the device has remained active for
|
||||
:attr:`hold_time` seconds.
|
||||
|
||||
This can be set to a function which accepts no (mandatory) parameters,
|
||||
or a Python function which accepts a single mandatory parameter (with
|
||||
as many optional parameters as you like). If the function accepts a
|
||||
single mandatory parameter, the device that activated will be passed
|
||||
as that parameter.
|
||||
|
||||
Set this property to :data:`None` (the default) to disable the event.
|
||||
""")
|
||||
|
||||
@property
|
||||
def hold_time(self):
|
||||
"""
|
||||
The length of time (in seconds) to wait after the device is activated,
|
||||
until executing the :attr:`when_held` handler. If :attr:`hold_repeat`
|
||||
is True, this is also the length of time between invocations of
|
||||
:attr:`when_held`.
|
||||
"""
|
||||
return self._hold_time
|
||||
|
||||
@hold_time.setter
|
||||
def hold_time(self, value):
|
||||
if value < 0:
|
||||
raise BadWaitTime('hold_time must be 0 or greater')
|
||||
self._hold_time = float(value)
|
||||
|
||||
@property
|
||||
def hold_repeat(self):
|
||||
"""
|
||||
If :data:`True`, :attr:`when_held` will be executed repeatedly with
|
||||
:attr:`hold_time` seconds between each invocation.
|
||||
"""
|
||||
return self._hold_repeat
|
||||
|
||||
@hold_repeat.setter
|
||||
def hold_repeat(self, value):
|
||||
self._hold_repeat = bool(value)
|
||||
|
||||
@property
|
||||
def is_held(self):
|
||||
"""
|
||||
When :data:`True`, the device has been active for at least
|
||||
:attr:`hold_time` seconds.
|
||||
"""
|
||||
return self._held_from is not None
|
||||
|
||||
@property
|
||||
def held_time(self):
|
||||
"""
|
||||
The length of time (in seconds) that the device has been held for.
|
||||
This is counted from the first execution of the :attr:`when_held` event
|
||||
rather than when the device activated, in contrast to
|
||||
:attr:`~EventsMixin.active_time`. If the device is not currently held,
|
||||
this is :data:`None`.
|
||||
"""
|
||||
if self._held_from is not None:
|
||||
return self.pin_factory.ticks_diff(self.pin_factory.ticks(),
|
||||
self._held_from)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class HoldThread(GPIOThread):
|
||||
"""
|
||||
Extends :class:`GPIOThread`. Provides a background thread that repeatedly
|
||||
fires the :attr:`HoldMixin.when_held` event as long as the owning
|
||||
device is active.
|
||||
"""
|
||||
def __init__(self, parent):
|
||||
super().__init__(
|
||||
target=self.held, args=(weakref.proxy(parent),))
|
||||
self.holding = Event()
|
||||
self.start()
|
||||
|
||||
def held(self, parent):
|
||||
try:
|
||||
while not self.stopping.is_set():
|
||||
if self.holding.wait(0.1):
|
||||
self.holding.clear()
|
||||
while not (
|
||||
self.stopping.is_set() or
|
||||
parent._inactive_event.wait(parent.hold_time)
|
||||
):
|
||||
if parent._held_from is None:
|
||||
parent._held_from = parent.pin_factory.ticks()
|
||||
parent._fire_held()
|
||||
if not parent.hold_repeat:
|
||||
break
|
||||
except ReferenceError:
|
||||
# Parent is dead; time to die!
|
||||
pass
|
||||
|
||||
|
||||
class GPIOQueue(GPIOThread):
|
||||
"""
|
||||
Extends :class:`GPIOThread`. Provides a background thread that monitors a
|
||||
device's values and provides a running *average* (defaults to median) of
|
||||
those values. If the *parent* device includes the :class:`EventsMixin` in
|
||||
its ancestry, the thread automatically calls
|
||||
:meth:`~EventsMixin._fire_events`.
|
||||
"""
|
||||
def __init__(
|
||||
self, parent, queue_len=5, sample_wait=0.0, partial=False,
|
||||
average=median, ignore=None):
|
||||
assert callable(average)
|
||||
if queue_len < 1:
|
||||
raise BadQueueLen('queue_len must be at least one')
|
||||
if sample_wait < 0:
|
||||
raise BadWaitTime('sample_wait must be 0 or greater')
|
||||
if ignore is None:
|
||||
ignore = set()
|
||||
super().__init__(target=self.fill)
|
||||
self.queue = deque(maxlen=queue_len)
|
||||
self.partial = bool(partial)
|
||||
self.sample_wait = float(sample_wait)
|
||||
self.full = Event()
|
||||
self.parent = weakref.proxy(parent)
|
||||
self.average = average
|
||||
self.ignore = ignore
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
if not self.partial:
|
||||
self.full.wait()
|
||||
try:
|
||||
return self.average(self.queue)
|
||||
except (ZeroDivisionError, ValueError):
|
||||
# No data == inactive value
|
||||
return 0.0
|
||||
|
||||
def fill(self):
|
||||
try:
|
||||
while not self.stopping.wait(self.sample_wait):
|
||||
value = self.parent._read()
|
||||
if value not in self.ignore:
|
||||
self.queue.append(value)
|
||||
if not self.full.is_set() and len(self.queue) >= self.queue.maxlen:
|
||||
self.full.set()
|
||||
if (self.partial or self.full.is_set()) and isinstance(self.parent, EventsMixin):
|
||||
self.parent._fire_events(self.parent.pin_factory.ticks(), self.parent.is_active)
|
||||
except ReferenceError:
|
||||
# Parent is dead; time to die!
|
||||
pass
|
||||
1783
venv/lib/python3.11/site-packages/gpiozero/output_devices.py
Normal file
1783
venv/lib/python3.11/site-packages/gpiozero/output_devices.py
Normal file
File diff suppressed because it is too large
Load Diff
1418
venv/lib/python3.11/site-packages/gpiozero/pins/__init__.py
Normal file
1418
venv/lib/python3.11/site-packages/gpiozero/pins/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
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.
714
venv/lib/python3.11/site-packages/gpiozero/pins/data.py
Normal file
714
venv/lib/python3.11/site-packages/gpiozero/pins/data.py
Normal file
@@ -0,0 +1,714 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2023 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2019 Ben Nuttall <ben@bennuttall.com>
|
||||
# Copyright (c) 2017-2018 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# Board layout ASCII art
|
||||
|
||||
REV1_BOARD = """\
|
||||
{style:white on green}+------------------{style:black on white}| |{style:white on green}--{style:on cyan}| |{style:on green}------+{style:reset}
|
||||
{style:white on green}| {P1:{style} col2}{style:white on green} P1 {style:black on yellow}|C|{style:white on green}{P2:{style} row8}{style:white on green} {style:on cyan}|A|{style:on green} |{style:reset}
|
||||
{style:white on green}| {P1:{style} col1}{style:white on green} {style:black on yellow}+-+{style:white on green}{P2:{style} row7}{P3:{style} row7}{style:white on cyan}+-+{style:on green} |{style:reset}
|
||||
{style:white on green}| {P2:{style} row6}{P3:{style} row6}{style:white on green} |{style:reset}
|
||||
{style:white on green}| {style:on black}+---+{style:on green} {P2:{style} row5}{P3:{style} row5}{style:on green} {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:on black}|D{style:on green} {style:on black}|SoC|{style:on green} {P2:{style} row4}{P3:{style} row4}{style:on green} {style:black on white}| USB{style:reset}
|
||||
{style:white on green}| {style:on black}|S{style:on green} {style:bold}Pi Model{style:normal} {style:on black}+---+{style:on green} {P2:{style} row3}{P3:{style} row3}{style:on green} {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:on black}|I{style:on green} {style:bold}{model:3s}V{pcb_revision:3s}{style:normal} {P2:{style} row2}{P3:{style} row2}{style:white on green} P3 |{style:reset}
|
||||
{style:white on green}| {style:on black}|0{style:on green} P2 {P2:{style} row1}{P3:{style} row1}{style:white on green} {style:black on white}+======{style:reset}
|
||||
{style:white on green}| {style:on black}C|{style:on green} {style:black on white}| Net{style:reset}
|
||||
{style:white on green}| {style:on black}S|{style:on green} {style:black on white}+======{style:reset}
|
||||
{style:black on white}=pwr{style:white on green} {style:black on white}|HDMI|{style:white on green} {style:on black}I|{style:on green} |{style:reset}
|
||||
{style:white on green}+----------------{style:black on white}| |{style:white on green}--{style:on black}0|{style:on green}------+{style:reset}"""
|
||||
|
||||
REV2_BOARD = """\
|
||||
{style:white on green}+------------------{style:black on white}| |{style:white on green}--{style:on cyan}| |{style:on green}------+{style:reset}
|
||||
{style:white on green}| {P1:{style} col2}{style:white on green} P1 {style:black on yellow}|C|{style:white on green}{P2:{style} row8}{style:white on green} {style:on cyan}|A|{style:on green} |{style:reset}
|
||||
{style:white on green}| {P1:{style} col1}{style:white on green} {style:black on yellow}+-+{style:white on green}{P2:{style} row7}{P3:{style} row7}{style:white on cyan}+-+{style:on green} |{style:reset}
|
||||
{style:white on green}| {P5:{style} col1}{style:white on green} {P2:{style} row6}{P3:{style} row6}{style:white on green} |{style:reset}
|
||||
{style:white on green}| P5 {P5:{style} col2}{style:white on green} {style:on black}+---+{style:on green} {P2:{style} row5}{P3:{style} row5}{style:on green} {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:on black}|D{style:on green} {style:on black}|SoC|{style:on green} {P2:{style} row4}{P3:{style} row4}{style:on green} {style:black on white}| USB{style:reset}
|
||||
{style:white on green}| {style:on black}|S{style:on green} {style:bold}Pi Model{style:normal} {style:on black}+---+{style:on green} {P2:{style} row3}{P3:{style} row3}{style:on green} {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:on black}|I{style:on green} {style:bold}{model:3s}V{pcb_revision:3s}{style:normal} {P2:{style} row2}{P3:{style} row2}{style:white on green} P3 |{style:reset}
|
||||
{style:white on green}| {style:on black}|0{style:on green} P2 {P2:{style} row1}{P3:{style} row1}{style:white on green} {style:black on white}+======{style:reset}
|
||||
{style:white on green}| {style:on black}C|{style:on green} {style:black on white}| Net{style:reset}
|
||||
{style:white on green}| {P6:{style} row2}{style:white on green} {style:on black}S|{style:on green} {style:black on white}+======{style:reset}
|
||||
{style:black on white}=pwr{style:white on green} P6 {P6:{style} row1}{style:white on green} {style:black on white}|HDMI|{style:white on green} {style:on black}I|{style:on green} |{style:reset}
|
||||
{style:white on green}+----------------{style:black on white}| |{style:white on green}--{style:on black}0|{style:on green}------+{style:reset}"""
|
||||
|
||||
A_BOARD = """\
|
||||
{style:white on green}+------------------{style:black on white}| |{style:white on green}--{style:on cyan}| |{style:on green}------+{style:reset}
|
||||
{style:white on green}| {P1:{style} col2}{style:white on green} P1 {style:black on yellow}|C|{style:white on green}{P2:{style} row8}{style:white on green} {style:on cyan}|A|{style:on green} |{style:reset}
|
||||
{style:white on green}| {P1:{style} col1}{style:white on green} {style:black on yellow}+-+{style:white on green}{P2:{style} row7}{P3:{style} row7}{style:white on cyan}+-+{style:on green} |{style:reset}
|
||||
{style:white on green}| {P5:{style} col1}{style:white on green} {P2:{style} row6}{P3:{style} row6}{style:white on green} |{style:reset}
|
||||
{style:white on green}| P5 {P5:{style} col2}{style:white on green} {style:on black}+---+{style:on green} {P2:{style} row5}{P3:{style} row5}{style:on green} {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:on black}|D{style:on green} {style:on black}|SoC|{style:on green} {P2:{style} row4}{P3:{style} row4}{style:on green} {style:black on white}| USB{style:reset}
|
||||
{style:white on green}| {style:on black}|S{style:on green} {style:bold}Pi Model{style:normal} {style:on black}+---+{style:on green} {P2:{style} row3}{P3:{style} row3}{style:on green} {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:on black}|I{style:on green} {style:bold}{model:3s}V{pcb_revision:3s}{style:normal} {P2:{style} row2}{P3:{style} row2}{style:white on green} P3 |{style:reset}
|
||||
{style:white on green}| {style:on black}|0{style:on green} P2 {P2:{style} row1}{P3:{style} row1}{style:white on green} |{style:reset}
|
||||
{style:white on green}| {style:on black}C|{style:on green} |{style:reset}
|
||||
{style:white on green}| {P6:{style} row2}{style:white on green} {style:on black}S|{style:on green} |{style:reset}
|
||||
{style:black on white}=pwr{style:white on green} P6 {P6:{style} row1}{style:white on green} {style:black on white}|HDMI|{style:white on green} {style:on black}I|{style:on green} |{style:reset}
|
||||
{style:white on green}+----------------{style:black on white}| |{style:white on green}--{style:on black}0|{style:on green}------+{style:reset}"""
|
||||
|
||||
BPLUS_BOARD = """\
|
||||
{style:white on green},--------------------------------.{style:reset}
|
||||
{style:white on green}| {J8:{style} col2}{style:white on green} J8 {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {J8:{style} col1}{style:white on green} {style:black on white}| USB{style:reset}
|
||||
{style:white on green}| {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {RUN:{style} rev col1}{style:white on green} RUN{style:bold} Pi Model {model:4s}V{pcb_revision:3s}{style:normal} |{style:reset}
|
||||
{style:white on green}| {style:on black}|D{style:on green} {style:on black}+---+{style:on green} {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:on black}|{style:black on white}S{style:white on green} {style:on black}|SoC|{style:on green} {style:black on white}| USB{style:reset}
|
||||
{style:white on green}| {style:on black}|{style:black on white}I{style:white on green} {style:on black}+---+{style:on green} {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:on black}|0{style:on green} {style:on black}C|{style:on green} |{style:reset}
|
||||
{style:white on green}| {style:black on white}S{style:white on black}|{style:on green} {style:black on white}+======{style:reset}
|
||||
{style:white on green}| {style:black on white}I{style:white on black}|{style:on green} {style:on black}|A|{style:on green} {style:black on white}| Net{style:reset}
|
||||
{style:white on green}| {style:black on white}pwr{style:white on green} {style:black on white}|HDMI|{style:white on green} {style:on black}0|{style:on green} {style:on black}|u|{style:on green} {style:black on white}+======{style:reset}
|
||||
{style:white on green}`-{style:black on white}| |{style:white on green}------{style:black on white}| |{style:white on green}-----{style:on black}|x|{style:on green}--------'{style:reset}"""
|
||||
|
||||
B3PLUS_BOARD = """\
|
||||
{style:white on green},--------------------------------.{style:reset}
|
||||
{style:white on green}| {J8:{style} col2}{style:white on green} J8 PoE {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {J8:{style} col1}{style:white on green} {POE:{style} row1}{style:on green} {style:black on white}| USB{style:reset}
|
||||
{style:white on green}| {style:black on white} Wi {style:white on green} {POE:{style} row2}{style:on green} {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:black on white} Fi {style:white on green} {style:bold}Pi Model {model:4s}V{pcb_revision:3s}{style:normal} |{style:reset}
|
||||
{style:white on green}| {style:on black}|D{style:on green} {style:black on white},---.{style:on green} {RUN:{style} col1}{style:on green} {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:on black}|{style:black on white}S{style:white on green} {style:black on white}|SoC|{style:white on green} RUN {style:black on white}| USB{style:reset}
|
||||
{style:white on green}| {style:on black}|{style:black on white}I{style:white on green} {style:black on white}`---'{style:on green} {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:on black}|0{style:on green} {style:on black}C|{style:on green} |{style:reset}
|
||||
{style:white on green}| {style:black on white}S{style:white on black}|{style:on green} {style:black on white}+======{style:reset}
|
||||
{style:white on green}| {style:black on white}I{style:white on black}|{style:on green} {style:on black}|A|{style:on green} {style:black on white}| Net{style:reset}
|
||||
{style:white on green}| {style:black on white}pwr{style:white on green} {style:black on white}|HDMI|{style:white on green} {style:on black}0|{style:on green} {style:on black}|u|{style:on green} {style:black on white}+======{style:reset}
|
||||
{style:white on green}`-{style:black on white}| |{style:white on green}------{style:black on white}| |{style:white on green}-----{style:on black}|x|{style:on green}--------'{style:reset}"""
|
||||
|
||||
B4_BOARD = """\
|
||||
{style:white on green},--------------------------------.{style:reset}
|
||||
{style:white on green}| {J8:{style} col2}{style:white on green} J8 {style:black on white}+======{style:reset}
|
||||
{style:white on green}| {J8:{style} col1}{style:white on green} J14 {style:black on white}| Net{style:reset}
|
||||
{style:white on green}| {style:black on white} Wi {style:white on green} {J14:{style} row1}{style:on green} {style:black on white}+======{style:reset}
|
||||
{style:white on green}| {style:black on white} Fi {style:white on green} {style:bold}Pi Model {model:4s}V{pcb_revision:3s}{style:normal} {J14:{style} row2}{style:normal white on green} |{style:reset}
|
||||
{style:white on green}| {style:on black}|D{style:on green} {style:black on white},---.{style:on green} {style:white on black}+---+{style:on green} {style:blue on white}+===={style:reset}
|
||||
{style:white on green}| {style:on black}|{style:black on white}S{style:white on green} {style:black on white}|SoC|{style:on green} {style:white on black}|RAM|{style:on green} {style:blue on white}|USB3{style:reset}
|
||||
{style:white on green}| {style:on black}|{style:black on white}I{style:white on green} {style:black on white}`---'{style:on green} {style:white on black}+---+{style:on green} {style:blue on white}+===={style:reset}
|
||||
{style:white on green}| {style:on black}|0{style:on green} {style:on black}C|{style:white on green} |{style:reset}
|
||||
{style:white on green}| {J2:{style} rev col1}{style:white on green} J2 {style:black on white}S{style:white on black}|{style:on green} {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:black on white}I{style:white on black}|{style:on green} {style:white on black}|A|{style:white on green} {style:black on white}|USB2{style:reset}
|
||||
{style:white on green}| {style:black on white}pwr{style:white on green} {style:black on white}|hd|{style:white on green} {style:black on white}|hd|{style:white on green} {style:on black}0|{style:on green} {style:on black}|u|{style:on green} {style:black on white}+===={style:reset}
|
||||
{style:white on green}`-{style:black on white}| |{style:white on green}---{style:black on white}|m0|{style:white on green}---{style:black on white}|m1|{style:white on green}----{style:on black}|x|{style:on green}-------'{style:reset}"""
|
||||
|
||||
B5_BOARD = """\
|
||||
{style:white on green},--------------------------------.{style:reset}
|
||||
{style:white on green}| {J8:{style} col2}{style:white on green} J8 {style:black on white}:{style:on green} {style:on white}+===={style:reset}
|
||||
{style:white on green}| {J8:{style} col1}{style:white on green} {style:black on white}:{style:on green} {style:on white}|USB2{style:reset}
|
||||
{style:white on green}| {style:black on white} Wi {style:white on green} {style:bold}Pi Model {model:4s}V{pcb_revision:3s}{style:normal} fan {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:black on white} Fi {style:white on green} {style:on black}+---+{style:on green} {style:on black}+---+{style:on green} |{style:reset}
|
||||
{style:white on green}| {style:on black}|RAM|{style:on green} {style:on black}|RP1|{style:on green} {style:blue on white}+===={style:reset}
|
||||
{style:white on green}|{style:black on yellow}|p{style:white on green} {style:on black}+---+{style:on green} {style:on black}+---+{style:on green} {style:blue on white}|USB3{style:reset}
|
||||
{style:white on green}|{style:black on yellow}|{style:on white}c{style:white on green} {style:black on white}-------{style:white on green} {style:blue on white}+===={style:reset}
|
||||
{style:white on green}|{style:black on yellow}|i{style:white on green} {style:black on white} SoC {style:white on green} {style:black on yellow}|c|c{style:white on green} J14 |{style:reset}
|
||||
{style:bold white on green}({style:normal} {style:black on white}-------{style:white on green} J7{style:black on yellow}|{style:on white}s{style:on yellow}|{style:on white}s{style:white on green} {J14:{style} row1}{style:on green} {style:black on white}+======{style:reset}
|
||||
{style:white on green}| J2 bat uart {J7:{style} row1}{style:black on yellow}|{style:on white}i{style:on yellow}|{style:on white}i{style:white on green} {J14:{style} row2}{style:on green} {style:black on white}| Net{style:reset}
|
||||
{style:white on green}| {style:black on white}pwr{style:white on green}\\{style:black on white}..|hd|...|hd|{style:white on green}{J7:{style} row2}{style:black on yellow}|1|0{style:white on green} {style:black on white}+======{style:reset}
|
||||
{style:white on green}`-{style:black on white}| |{style:white on green}-{J2:{style} col1}{style:black on white}|m0|{style:white on green}---{style:black on white}|m1|{style:white on green}--------------'{style:reset}"""
|
||||
|
||||
APLUS_BOARD = """\
|
||||
{style:white on green},--------------------------.{style:reset}
|
||||
{style:white on green}| {J8:{style} col2}{style:white on green} J8 |{style:reset}
|
||||
{style:white on green}| {J8:{style} col1}{style:white on green} |{style:reset}
|
||||
{style:white on green}| |{style:reset}
|
||||
{style:white on green}| {style:bold}Pi Model {model:4s}V{pcb_revision:3s}{style:normal} |{style:reset}
|
||||
{style:white on green}| {style:on black}|D{style:on green} {style:on black}+---+{style:on green} {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:on black}|S{style:on green} {style:on black}|SoC|{style:on green} {style:black on white}| USB{style:reset}
|
||||
{style:white on green}| {style:on black}|I{style:on green} {style:on black}+---+{style:on green} {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:on black}|0{style:on green} {style:on black}C|{style:on green} |{style:reset}
|
||||
{style:white on green}| {style:on black}S|{style:on green} |{style:reset}
|
||||
{style:white on green}| {style:on black}I|{style:on green} {style:on black}|A|{style:on green} |{style:reset}
|
||||
{style:white on green}| {style:black on white}pwr{style:white on green} {style:black on white}|HDMI|{style:white on green} {style:on black}0|{style:on green} {style:on black}|u|{style:on green} |{style:reset}
|
||||
{style:white on green}`-{style:black on white}| |{style:white on green}------{style:black on white}| |{style:white on green}-----{style:on black}|x|{style:on green}--'{style:reset}"""
|
||||
|
||||
A3PLUS_BOARD = """\
|
||||
{style:white on green},--------------------------.{style:reset}
|
||||
{style:white on green}| {J8:{style} col2}{style:white on green} J8 |{style:reset}
|
||||
{style:white on green}| {J8:{style} col1}{style:white on green} RUN|{style:reset}
|
||||
{style:white on green}| {style:black on white} Wi {style:white on green} {RUN:{style} col1}{style:white on green}|{style:reset}
|
||||
{style:white on green}| {style:black on white} Fi {style:white on green} {style:bold}Pi Model {model:4s}V{pcb_revision:3s}{style:normal} |{style:reset}
|
||||
{style:white on green}| {style:on black}|D{style:on green} {style:black on white},---.{style:on green} {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:on black}|{style:black on white}S{style:white on green} {style:black on white}|SoC|{style:on green} {style:black on white}| USB{style:reset}
|
||||
{style:white on green}| {style:on black}|{style:black on white}I{style:white on green} {style:black on white}`---'{style:on green} {style:black on white}+===={style:reset}
|
||||
{style:white on green}| {style:on black}|0{style:on green} {style:on black}C|{style:on green} |{style:reset}
|
||||
{style:white on green}| {style:black on white}S{style:white on black}|{style:on green} |{style:reset}
|
||||
{style:white on green}| {style:black on white}I{style:white on black}|{style:on green} {style:on black}|A|{style:on green} |{style:reset}
|
||||
{style:white on green}| {style:black on white}pwr{style:white on green} {style:black on white}|HDMI|{style:white on green} {style:on black}0|{style:on green} {style:on black}|u|{style:on green} |{style:reset}
|
||||
{style:white on green}`-{style:black on white}| |{style:white on green}------{style:black on white}| |{style:white on green}-----{style:on black}|x|{style:on green}--'{style:reset}"""
|
||||
|
||||
ZERO12_BOARD = """\
|
||||
{style:white on green},--{J8:{style} col2}{style:white on green}---.{style:reset}
|
||||
{style:white on green}| {J8:{style} col1}{style:white on green} J8|{style:reset}
|
||||
{style:black on white}---+{style:white on green} {style:bold}Pi{model:6s}{style:normal} RUN {RUN:{style} rev col1}{style:white on green} |{style:reset}
|
||||
{style:black on white} sd|{style:white on green} {style:bold}V{pcb_revision:3s}{style:normal} {style:white on black}+---+{style:white on green} TV {TV:{style} col1}{style:white on green} |{style:reset}
|
||||
{style:black on white}---+{style:white on green} {style:white on black}|SoC|{style:white on green} |{style:reset}
|
||||
{style:white on green}| {style:black on white}hdmi{style:white on green} {style:white on black}+---+{style:white on green} {style:black on white}usb{style:on green} {style:black on white}pwr{style:white on green} |{style:reset}
|
||||
{style:white on green}`-{style:black on white}| |{style:white on green}------------{style:black on white}| |{style:white on green}-{style:black on white}| |{style:white on green}-'{style:reset}"""
|
||||
|
||||
ZERO13_BOARD = """\
|
||||
{style:white on green},--{J8:{style} col2}{style:white on green}---.{style:reset}
|
||||
{style:white on green}| {J8:{style} col1}{style:white on green} J8|{style:reset}
|
||||
{style:black on white}---+{style:white on green} {style:bold}Pi{model:6s}{style:normal} RUN {RUN:{style} rev col1}{style:white on green} {style:black on white}c{style:white on black}|{style:reset}
|
||||
{style:black on white} sd|{style:white on green} {style:bold}V{pcb_revision:3s}{style:normal} {style:white on black}+---+{style:white on green} TV {TV:{style} col1}{style:white on green} {style:black on white}s{style:white on black}|{style:reset}
|
||||
{style:black on white}---+{style:white on green} {style:white on black}|SoC|{style:white on green} {style:black on white}i{style:white on black}|{style:reset}
|
||||
{style:white on green}| {style:black on white}hdmi{style:white on green} {style:white on black}+---+{style:white on green} {style:black on white}usb{style:on green} {style:black on white}pwr{style:white on green} |{style:reset}
|
||||
{style:white on green}`-{style:black on white}| |{style:white on green}------------{style:black on white}| |{style:white on green}-{style:black on white}| |{style:white on green}-'{style:reset}"""
|
||||
|
||||
ZERO2_BOARD = """\
|
||||
{style:white on green},--{J8:{style} col2}{style:white on green}---.{style:reset}
|
||||
{style:white on green}| {J8:{style} col1}{style:white on green} J8|{style:reset}
|
||||
{style:black on white}---+{style:white on green} {style:normal on black}+---+{style:on green} {style:bold}Pi{model:6s} {style:normal black on white}c{style:white on black}|{style:reset}
|
||||
{style:black on white} sd|{style:white on green} {style:white on black}|SoC|{style:white on green} {style:black on white} Wi {style:bold white on green}V{pcb_revision:3s} {style:normal black on white}s{style:white on black}|{style:reset}
|
||||
{style:black on white}---+{style:white on green} {style:white on black}+---+{style:white on green} {style:black on white} Fi {style:on green} {style:on white}i{style:white on black}|{style:reset}
|
||||
{style:white on green}| {style:black on white}hdmi{style:white on green} {style:black on white}usb{style:on green} {style:black on white}pwr{style:white on green} |{style:reset}
|
||||
{style:white on green}`-{style:black on white}| |{style:white on green}------------{style:black on white}| |{style:white on green}-{style:black on white}| |{style:white on green}-'{style:reset}
|
||||
"""
|
||||
|
||||
CM_BOARD = """\
|
||||
{style:white on green}+---------------------------------------+{style:reset}
|
||||
{style:white on green}| {style:yellow on black}O{style:bold white on green} Raspberry Pi {model:4s} {style:normal yellow on black}O{style:white on green} |{style:reset}
|
||||
{style:white on green}) Version {pcb_revision:3s} {style:on black}+---+{style:on green} ({style:reset}
|
||||
{style:white on green}| {style:on black}|SoC|{style:on green} |{style:reset}
|
||||
{style:white on green}) {style:on black}+---+{style:on green} ({style:reset}
|
||||
{style:white on green}| {style:on black}O{style:on green} _ {style:on black}O{style:on green} |{style:reset}
|
||||
{style:white on green}||||||{style:reset} {style:white on green}||||||||||||||||||||||||||||||||||{style:reset}"""
|
||||
|
||||
CM3PLUS_BOARD = """\
|
||||
{style:white on green}+---------------------------------------+{style:reset}
|
||||
{style:white on green}| {style:yellow on black}O{style:bold white on green} Raspberry Pi {model:4s} {style:normal yellow on black}O{style:white on green} |{style:reset}
|
||||
{style:white on green}) Version {pcb_revision:3s} {style:black on white},---.{style:white on green} ({style:reset}
|
||||
{style:white on green}| {style:black on white}|SoC|{style:white on green} |{style:reset}
|
||||
{style:white on green}) {style:black on white}`---'{style:white on green} ({style:reset}
|
||||
{style:white on green}| {style:on black}O{style:on green} _ {style:on black}O{style:on green} |{style:reset}
|
||||
{style:white on green}||||||{style:reset} {style:white on green}||||||||||||||||||||||||||||||||||{style:reset}"""
|
||||
|
||||
CM4_BOARD = """\
|
||||
{style:white on green},--{style:black on white}csi1{style:white on green}---{style:black on white}dsi0{style:white on green}---{style:black on white}dsi1{style:white on green}-----------{style:bold},-------------.{style:normal}-----------.{style:reset}
|
||||
{style:white on green}| {style:black on white}----{style:white on green} {style:black on white}----{style:white on green} {style:black on white}----{style:white on green} J2 {J2:{style} col2}{style:bold white on green}| |{style:normal}{J3:{style} rev col1}{style:white on green} |{style:reset}
|
||||
{style:white on green}{style:black on white}c|{style:white on green} {style:bold}Pi {model:4s} Rev {pcb_revision:3s}{style:normal} {J2:{style} col1}{style:bold white on green}| {style:normal black on white} Wi {style:white on green} {style:bold}|{style:normal}J3 |{style:reset}
|
||||
{style:white on green}{style:black on white}s|{style:white on green} {style:bold}IO Board{style:normal} {style:bold}| {style:normal black on white} Fi {style:white on green} {style:bold}|{style:normal} |{style:reset}
|
||||
{style:white on green}{style:black on white}i|{style:white on green} J6 {J6:{style} col2}{style:bold white on green} | {style:normal white on black}+--+{style:on green}{style:bold}| {style:normal white on black}|P|{style:on green} |{style:reset}
|
||||
{style:white on green}| J8 {J6:{style} col1}{style:bold white on green} | {style:normal black on white},----.{style:on green} {style:white on black}|eM|{style:bold on green}| {style:normal white on black}}}-{{{style:on green} |{style:reset}
|
||||
{style:white on green}| {J8:{style} col2}{style:white on green} {style:bold}| {style:normal black on white}|SoC |{style:on green} {style:white on black}|MC|{style:bold on green}| {style:normal white on black}|C|{style:on green} |{style:reset}
|
||||
{style:white on green}| {J8:{style} col1}{style:white on green} J9 {style:bold}| {style:normal black on white}| |{style:on green} {style:white on black}+--+{style:bold on green}| {style:normal white on black}|I|{style:on green} |{style:reset}
|
||||
{style:white on green}| {style:black on white},---.{style:white on green} {J9:{style} row1}{style:bold white on green} | {style:normal black on white}`----'{style:white on green} {style:bold}| {style:normal white on black}|e|{style:on green} |{style:reset}
|
||||
{style:white on green}|{style:black on white}( ={style:on green}O{style:on white} |{style:white on green} {J9:{style} row2}{style:bold white on green} | {style:normal white on black}+----+{style:on green} {style:bold}|{style:normal} |{style:reset}
|
||||
{style:white on green}| {style:black on white}) + |{style:white on green} {style:bold}| {style:normal white on black}|RAM |{style:bold white on green} |{style:normal} |{style:reset}
|
||||
{style:white on green}|{style:black on white}( ={style:on green}O{style:on white} |{style:white on green} {style:bold}`--{style:normal white on black}+----+{style:bold on green}-----'{style:normal} |{style:reset}
|
||||
{style:white on green}| {style:black on white}`---'{style:white on green} |{style:reset}
|
||||
{style:white on green}| {J1:{style} rev col1}{style:white on green} J1 |{style:reset}
|
||||
{style:white on green}| |{style:reset}
|
||||
{style:white on green}| {style:black on white}|Net |{style:on green} {style:black on white}|USB|{style:on green} {style:black on white}|uSD|{style:white on green} {style:on black}|p|{style:on green}|{style:reset}
|
||||
{style:white on green}| {style:black on white}|HDMI|{style:on green} {style:black on white}|HDMI|{style:white on green} {style:black on white}| |{style:on green} {style:black on white}| 2 |{style:on green} {style:black on white}usb{style:white on green} {style:black on white}| |{style:white on green} {style:on black}|w|{style:on green}|{style:reset}
|
||||
{style:white on green}`----{style:black on white}| 0 |{style:white on green}---{style:black on white}| 1 |{style:white on green}-------{style:black on white}| |{style:white on green}-{style:black on white}| |{style:white on green}-{style:black on white}| |{style:white on green}------------{style:white on black}|r|{style:on green}'{style:reset}"""
|
||||
|
||||
P400_BOARD = """\
|
||||
{style:white on red},------+----+----+----+----+---+--+--+--+--------------------+---.{style:reset}
|
||||
{style:white on red},' |{style:white on black}Net {style:white on red}|{style:white on black}USB {style:white on red}|{style:cyan on black}USB {style:white on red}|{style:cyan on black}USB {style:white on red}|{style:white on black}pwr{style:white on red}|{style:white on black}hd{style:white on red}|{style:white on black}hd{style:white on red}|{style:white on black}sd{style:white on red}|{J8:{style} col2}{style:white on red}| `.{style:reset}
|
||||
{style:white on red}/ {style:black}=={style:white} |{style:white on black} {style:white on red}|{style:white on black} 2 {style:white on red}|{style:cyan on black} 3 {style:white on red}|{style:cyan on black} 3 {style:white on red}|{style:white on black} {style:white on red}|{style:white on black}m1{style:white on red}|{style:white on black}m0{style:white on red}|{style:white on black} {style:white on red}|{J8:{style} col1}{style:white on red}| \\{style:reset}
|
||||
{style:black on white},------------------------------------------------------------------------.{style:reset}
|
||||
{style:black on white}| ___ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ {style:bold on white}o o {style:green}o{style:normal black}____ |{style:reset}
|
||||
{style:black on white}| |Esc|F1{style:red}11{style:black}|F2{style:red}12{style:black}|F3 |F4 |F5 |F6 |F7 |F8 |F9 |F10{style:red}o{style:black}|NumL|Pt{style:red}Sq{style:black}|Dl{style:red}In{style:black}| |{style:reset}
|
||||
{style:black on white}| ___ ___ ____ ____ ____ ____ ____ ___ ____ ____ ____ ___ ____ _______ |{style:reset}
|
||||
{style:black on white}| |¬ |! |" |£ |$ |% |^ |& {style:red}7{style:black}|* {style:red}8{style:black}|( {style:red}9{style:black}|) {style:red}*{style:black}|_ |+ |BkSpc | |{style:reset}
|
||||
{style:black on white}| |` ||1 |2 |3 |4 |5 |6 |7 |8 |9 |0 |- |= |<-- | |{style:reset}
|
||||
{style:black on white}| _____ ___ ____ ____ ____ ____ ____ ___ ____ ____ ____ ____ __ ______ |{style:reset}
|
||||
{style:black on white}| |Tab |Q |W |E |R |T |Y |U {style:red}4{style:black}|I {style:red}5{style:black}|O {style:red}6{style:black}|P {style:red}-{style:black}|{{ |}} |Enter | |{style:reset}
|
||||
{style:black on white}| |->| | | | | | | | | | | |[ |] |<-' | |{style:reset}
|
||||
{style:black on white}| ______ ____ ____ ____ ____ ____ ____ ___ ____ ____ ____ ____ __ | |{style:reset}
|
||||
{style:black on white}| |Caps |A |S |D |F |G |H |J {style:red}1{style:black}|K {style:red}2{style:black}|L {style:red}3{style:black}|: {style:red}+{style:black}|@ |~ | | |{style:reset}
|
||||
{style:black on white}| |Lock | | | | | | | | | |; |' |# | | |{style:reset}
|
||||
{style:black on white}| _____ ___ ___ ____ ____ ____ ____ ____ ___ ____ ____ ____ __________ |{style:reset}
|
||||
{style:black on white}| |Shift|| |Z |X |C |V |B |N |M {style:red}0{style:black}|< |> {style:red}.{style:black}|? {style:red}/{style:black}|Shift | |{style:reset}
|
||||
{style:black on white}| |^ |\\ | | | | | | | |, |. |/ |^ | |{style:reset}
|
||||
{style:black on white}| ____ ___ ____ ____ _______________________ ____ ____ _____ |{style:reset}
|
||||
{style:black on white}| |Ctrl|{style:red}Fn{style:black} | {style:red}**{style:black} |Alt | |Alt |Ctrl|____|^{style:red}PgUp{style:black}|____ |{style:reset}
|
||||
{style:black on white}| | | | {style:red}{{}}{style:black} | | | | |<{style:red}Hom{style:black}|v{style:red}PgDn{style:black}|>{style:red}End{style:black}| |{style:reset}
|
||||
{style:black on white}`------------------------------------------------------------------------'{style:reset}
|
||||
Raspberry Pi {style:bold red}{model}{style:reset} Rev {pcb_revision}"""
|
||||
|
||||
# Pin maps for various board revisions and headers. Much of the information
|
||||
# below is derived from the BCM2835 ARM Peripherals datasheet, but Gadgetoid's
|
||||
# superb https://pinout.xyz site was also a great deal of help in filling in
|
||||
# the gaps!
|
||||
|
||||
import re
|
||||
def gpiof(*names):
|
||||
return {
|
||||
'gpio' if re.match(r'GPIO\d+$', name) else
|
||||
'i2c' if re.match(r'I2C\d (SDA|SCL)$', name) else
|
||||
'spi' if re.match(r'SPI\d (SCLK|MOSI|MISO|CE\d)$', name) else
|
||||
'uart' if re.match(r'UART\d (RXD|TXD|RTS|CTS)$', name) else
|
||||
'smi' if re.match(r'SMI (name[AD]\d+|SOE / SE|SWE / SRW)$', name) else
|
||||
'dpi' if re.match(r'DPI (D\d+|PCLK|DE|[HV]SYNC)$', name) else
|
||||
'pwm' if re.match(r'PWM\d+ \d+$', name) else
|
||||
'pcm' if re.match(r'PCM (CLK|FS|DIN|DOUT)$', name) else
|
||||
'sdio' if re.match(r'SD\d+ (CLK|CMD|DAT\d+)$', name) else
|
||||
'jtag' if re.match(r'JTAG (TDI|TDO|TCK|TMS|RTCK|TRST)$', name) else
|
||||
'mii' if re.match(r'(RG)?MII ', name) else
|
||||
'': name
|
||||
for name in names
|
||||
if name
|
||||
}
|
||||
|
||||
V1_8 = {'': '1V8'}
|
||||
V3_3 = {'': '3V3'}
|
||||
V5 = {'': '5V'}
|
||||
GND = {'': 'GND'}
|
||||
NC = {'': 'NC'} # not connected
|
||||
|
||||
# gpio alt0 alt1 alt2 alt3 alt4 alt5
|
||||
PI1_GPIO0 = gpiof('GPIO0', 'I2C0 SDA', 'SMI SA5', 'DPI PCLK')
|
||||
PI1_GPIO1 = gpiof('GPIO1', 'I2C0 SCL', 'SMI SA4', 'DPI DE')
|
||||
PI1_GPIO2 = gpiof('GPIO2', 'I2C1 SDA', 'SMI SA3', 'DPI VSYNC')
|
||||
PI1_GPIO3 = gpiof('GPIO3', 'I2C1 SCL', 'SMI SA2', 'DPI HSYNC')
|
||||
PI1_GPIO4 = gpiof('GPIO4', 'GPCLK0', 'SMI SA1', 'DPI D0', '', '', 'JTAG TDI')
|
||||
PI1_GPIO5 = gpiof('GPIO5', 'GPCLK1', 'SMI SA0', 'DPI D1', '', '', 'JTAG TDO')
|
||||
PI1_GPIO6 = gpiof('GPIO6', 'GPCLK2', 'SMI SOE / SE', 'DPI D2', '', '', 'JTAG RTCK')
|
||||
PI1_GPIO7 = gpiof('GPIO7', 'SPI0 CE1', 'SMI SWE / SRW', 'DPI D3')
|
||||
PI1_GPIO8 = gpiof('GPIO8', 'SPI0 CE0', 'SMI SD0', 'DPI D4')
|
||||
PI1_GPIO9 = gpiof('GPIO9', 'SPI0 MISO', 'SMI SD1', 'DPI D5')
|
||||
PI1_GPIO10 = gpiof('GPIO10', 'SPI0 MOSI', 'SMI SD2', 'DPI D6')
|
||||
PI1_GPIO11 = gpiof('GPIO11', 'SPI0 SCLK', 'SMI SD3', 'DPI D7')
|
||||
PI1_GPIO12 = gpiof('GPIO12', 'PWM0 0', 'SMI SD4', 'DPI D8', '', '', 'JTAG TMS' )
|
||||
PI1_GPIO13 = gpiof('GPIO13', 'PWM0 1', 'SMI SD5', 'DPI D9', '', '', 'JTAG TCK' )
|
||||
PI1_GPIO14 = gpiof('GPIO14', 'UART0 TXD', 'SMI SD6', 'DPI D10' '', '', 'UART1 TXD')
|
||||
PI1_GPIO15 = gpiof('GPIO15', 'UART0 RXD', 'SMI SD7', 'DPI D11' '', '', 'UART1 RXD')
|
||||
PI1_GPIO16 = gpiof('GPIO16', '', 'SMI SD8', 'DPI D11', 'UART0 CTS', 'SPI1 CE2', 'UART1 CTS')
|
||||
PI1_GPIO17 = gpiof('GPIO17', '', 'SMI SD9', 'DPI D13', 'UART0 RTS', 'SPI1 CE1', 'UART1 RTS')
|
||||
PI1_GPIO18 = gpiof('GPIO18', 'PCM CLK', 'SMI SD10', 'DPI D14', 'BSC SDA / MOSI', 'SPI1 CE0', 'PWM0 0')
|
||||
PI1_GPIO19 = gpiof('GPIO19', 'PCM FS', 'SMI SD11', 'DPI D15', 'BSC SCL / SCLK', 'SPI1 MISO', 'PWM0 1')
|
||||
PI1_GPIO20 = gpiof('GPIO20', 'PCM DIN', 'SMI SD12', 'DPI D16', 'BSC MISO', 'SPI1 MOSI', 'GPCLK0')
|
||||
PI1_GPIO21 = gpiof('GPIO21', 'PCM DOUT', 'SMI SD13', 'DPI D17', 'BSC CE', 'SPI1 SCLK', 'GPCLK1')
|
||||
PI1_GPIO22 = gpiof('GPIO22', 'SD0 CLK', 'SMI SD14', 'DPI D18', 'SD1 CLK', 'JTAG TRST')
|
||||
PI1_GPIO23 = gpiof('GPIO23', 'SD0 CMD', 'SMI SD15', 'DPI D19', 'SD1 CMD', 'JTAG RTCK')
|
||||
PI1_GPIO24 = gpiof('GPIO24', 'SD0 DAT0', 'SMI SD16', 'DPI D20', 'SD1 DAT0', 'JTAG TDO')
|
||||
PI1_GPIO25 = gpiof('GPIO25', 'SD0 DAT1', 'SMI SD17', 'DPI D21', 'SD1 DAT1', 'JTAG TCK')
|
||||
PI1_GPIO26 = gpiof('GPIO26', 'SD0 DAT2', '', 'DPI D22', 'SD1 DAT2', 'JTAG TDI')
|
||||
PI1_GPIO27 = gpiof('GPIO27', 'SD0 DAT3', '', 'DPI D23', 'SD1 DAT3', 'JTAG TMS')
|
||||
PI1_GPIO28 = gpiof('GPIO28', 'I2C0 SDA', 'SMI SA5', 'PCM CLK')
|
||||
PI1_GPIO29 = gpiof('GPIO29', 'I2C0 SCL', 'SMI SA4', 'PCM FS')
|
||||
PI1_GPIO30 = gpiof('GPIO30', '', 'SMI SA3', 'PCM DIN', 'UART0 CTS', '', 'UART1 CTS')
|
||||
PI1_GPIO31 = gpiof('GPIO31', '', 'SMI SA2', 'PCM DOUT', 'UART0 RTS', '', 'UART1 RTS')
|
||||
PI1_GPIO32 = gpiof('GPIO32', 'GPCLK0', 'SMI SA1', '', 'UART0 TXD', '', 'UART1 TXD')
|
||||
PI1_GPIO33 = gpiof('GPIO33', '', 'SMI SA0', '', 'UART0 RXD', '', 'UART1 RXD')
|
||||
PI1_GPIO34 = gpiof('GPIO34', 'GPCLK0', 'SMI SOE / SE')
|
||||
PI1_GPIO35 = gpiof('GPIO35', 'SPI0 CE1', 'SMI SWE / SRW')
|
||||
PI1_GPIO36 = gpiof('GPIO36', 'SPI0 CE0', 'SMI SD0', 'UART0 TXD')
|
||||
PI1_GPIO37 = gpiof('GPIO37', 'SPI0 MISO', 'SMI SD1', 'UART0 RXD')
|
||||
PI1_GPIO38 = gpiof('GPIO38', 'SPI0 MOSI', 'SMI SD2', 'UART0 RTS')
|
||||
PI1_GPIO39 = gpiof('GPIO39', 'SPI0 SCLK', 'SMI SD3', 'UART0 CTS')
|
||||
PI1_GPIO40 = gpiof('GPIO40', 'PWM0 0', 'SMI SD4', '', '', 'SPI2 MISO', 'UART1 TXD')
|
||||
PI1_GPIO41 = gpiof('GPIO41', 'PWM0 1', 'SMI SD5', '', '', 'SPI2 MOSI', 'UART1 RXD')
|
||||
PI1_GPIO42 = gpiof('GPIO42', 'GPCLK1', 'SMI SD6', '', '', 'SPI2 SCLK', 'UART1 RTS')
|
||||
PI1_GPIO43 = gpiof('GPIO43', 'GPCLK2', 'SMI SD7', '', '', 'SPI2 CE0', 'UART1 CTS')
|
||||
PI1_GPIO44 = gpiof('GPIO44', 'GPCLK1', 'I2C0 SDA', 'I2C1 SDA', '', 'SPI2 CE1')
|
||||
PI1_GPIO45 = gpiof('GPIO45', 'PWM0 1', 'I2C0 SCL', 'I2C1 SCL', '', 'SPI2 CE2')
|
||||
PI1_GPIO46 = gpiof('GPIO46')
|
||||
PI1_GPIO47 = gpiof('GPIO47')
|
||||
PI1_GPIO48 = gpiof('GPIO48')
|
||||
PI1_GPIO49 = gpiof('GPIO49')
|
||||
PI1_GPIO50 = gpiof('GPIO50')
|
||||
PI1_GPIO51 = gpiof('GPIO51')
|
||||
PI1_GPIO52 = gpiof('GPIO52')
|
||||
PI1_GPIO53 = gpiof('GPIO53')
|
||||
|
||||
# gpio alt0 alt1 alt2 alt3 alt4 alt5
|
||||
PI4_GPIO0 = gpiof('GPIO0', 'I2C0 SDA', 'SMI SA5', 'DPI PCLK', 'SPI3 CE0', 'UART2 TXD', 'I2C6 SDA')
|
||||
PI4_GPIO1 = gpiof('GPIO1', 'I2C0 SCL', 'SMI SA4', 'DPI DE', 'SPI3 MISO', 'UART2 RXD', 'I2C6 SCL')
|
||||
PI4_GPIO2 = gpiof('GPIO2', 'I2C1 SDA', 'SMI SA3', 'DPI VSYNC', 'SPI3 MOSI', 'UART2 CTS', 'I2C3 SDA')
|
||||
PI4_GPIO3 = gpiof('GPIO3', 'I2C1 SCL', 'SMI SA2', 'DPI HSYNC', 'SPI3 SCLK', 'UART2 RTS', 'I2C3 SCL')
|
||||
PI4_GPIO4 = gpiof('GPIO4', 'GPCLK0', 'SMI SA1', 'DPI D0', 'SPI4 CE0', 'UART3 TXD', 'I2C3 SDA')
|
||||
PI4_GPIO5 = gpiof('GPIO5', 'GPCLK1', 'SMI SA0', 'DPI D1', 'SPI4 MISO', 'UART3 RXD', 'I2C3 SCL')
|
||||
PI4_GPIO6 = gpiof('GPIO6', 'GPCLK2', 'SMI SOE / SE', 'DPI D2', 'SPI4 MOSI', 'UART3 CTS', 'I2C4 SDA')
|
||||
PI4_GPIO7 = gpiof('GPIO7', 'SPI0 CE1', 'SMI SWE / SRW', 'DPI D3', 'SPI4 SCLK', 'UART3 RTS', 'I2C4 SCL')
|
||||
PI4_GPIO8 = gpiof('GPIO8', 'SPI0 CE0', 'SMI SD0', 'DPI D4', 'BSC CE', 'UART4 TXD', 'I2C4 SDA')
|
||||
PI4_GPIO9 = gpiof('GPIO9', 'SPI0 MISO', 'SMI SD1', 'DPI D5', 'BSC MISO', 'UART4 RXD', 'I2C4 SCL')
|
||||
PI4_GPIO10 = gpiof('GPIO10', 'SPI0 MOSI', 'SMI SD2', 'DPI D6', 'BSC SDA / MOSI', 'UART4 CTS', 'I2C5 SDA')
|
||||
PI4_GPIO11 = gpiof('GPIO11', 'SPI0 SCLK', 'SMI SD3', 'DPI D7', 'BSC SCL / SCLK', 'UART4 RTS', 'I2C5 SCL')
|
||||
PI4_GPIO12 = gpiof('GPIO12', 'PWM0 0', 'SMI SD4', 'DPI D8', 'SPI5 CE0', 'UART5 TXD', 'I2C5 SDA')
|
||||
PI4_GPIO13 = gpiof('GPIO13', 'PWM0 1', 'SMI SD5', 'DPI D9', 'SPI5 MISO', 'UART5 RXD', 'I2C5 SCL')
|
||||
PI4_GPIO14 = gpiof('GPIO14', 'UART0 TXD', 'SMI SD6', 'DPI D10', 'SPI5 MOSI', 'UART5 CTS', 'UART1 TXD')
|
||||
PI4_GPIO15 = gpiof('GPIO15', 'UART0 RXD', 'SMI SD7', 'DPI D11', 'SPI5 SCLK', 'UART5 RTS', 'UART1 RXD')
|
||||
PI4_GPIO16 = gpiof('GPIO16', '', 'SMI SD8', 'DPI D12', 'UART0 CTS', 'SPI1 CE2', 'UART1 CTS')
|
||||
PI4_GPIO17 = gpiof('GPIO17', '', 'SMI SD9', 'DPI D13', 'UART0 RTS', 'SPI1 CE1', 'UART1 RTS')
|
||||
PI4_GPIO18 = gpiof('GPIO18', 'PCM CLK', 'SMI SD10', 'DPI D14', 'SPI6 CE0', 'SPI1 CE0', 'PWM0 0')
|
||||
PI4_GPIO19 = gpiof('GPIO19', 'PCM FS', 'SMI SD11', 'DPI D15', 'SPI6 MISO', 'SPI1 MISO', 'PWM0 1')
|
||||
PI4_GPIO20 = gpiof('GPIO20', 'PCM DIN', 'SMI SD12', 'DPI D16', 'SPI6 MOSI', 'SPI1 MOSI', 'GPCLK0')
|
||||
PI4_GPIO21 = gpiof('GPIO21', 'PCM DOUT', 'SMI SD13', 'DPI D17', 'SPI6 SCLK', 'SPI1 SCLK', 'GPCLK1')
|
||||
PI4_GPIO22 = gpiof('GPIO22', 'SD0 CLK', 'SMI SD14', 'DPI D18', 'SD1 CLK', 'JTAG TRST', 'I2C6 SDA')
|
||||
PI4_GPIO23 = gpiof('GPIO23', 'SD0 CMD', 'SMI SD15', 'DPI D19', 'SD1 CMD', 'JTAG RTCK', 'I2C6 SCL')
|
||||
PI4_GPIO24 = gpiof('GPIO24', 'SD0 DAT0', 'SMI SD16', 'DPI D20', 'SD1 DAT0', 'JTAG TDO', 'SPI3 CE1')
|
||||
PI4_GPIO25 = gpiof('GPIO25', 'SD0 DAT1', 'SMI SD17', 'DPI D21', 'SD1 DAT1', 'JTAG TCK', 'SPI4 CE1')
|
||||
PI4_GPIO26 = gpiof('GPIO26', 'SDA DAT2', '', 'DPI D22', 'SD1 DAT2', 'JTAG TDI', 'SPI5 CE1')
|
||||
PI4_GPIO27 = gpiof('GPIO27', 'SDA DAT3', '', 'DPI D23', 'SD1 DAT3', 'JTAG TMS', 'SPI6 CE1')
|
||||
PI4_GPIO28 = gpiof('GPIO28', 'I2C0 SDA', 'SMI SA5', 'PCM CLK', '', 'MII RX ERR', 'RGMII MDIO')
|
||||
PI4_GPIO29 = gpiof('GPIO29', 'I2C0 SCL', 'SMI SA4', 'PCM FS', '', 'MII TX ERR', 'RGMII MDC')
|
||||
PI4_GPIO30 = gpiof('GPIO30', '', 'SMI SA3', 'PCM DIN', 'UART0 CTS', 'MII CRS', 'UART1 CTS')
|
||||
PI4_GPIO31 = gpiof('GPIO31', '', 'SMI SA2', 'PCM DOUT', 'UART0 RTS', 'MII COL', 'UART1 RTS')
|
||||
PI4_GPIO32 = gpiof('GPIO32', 'GPCLK0', 'SMI SA1', '', 'UART0 TXD', 'SD CARD PRES', 'UART1 TXD')
|
||||
PI4_GPIO33 = gpiof('GPIO33', '', 'SMI SA0', '', 'UART0 RXD', 'SD CARD WRPROT', 'UART1 RXD')
|
||||
PI4_GPIO34 = gpiof('GPIO34', 'GPCLK0', 'SMI SOE / SE', '', 'SD1 CLK', 'SD CARD LED', 'RGMII IRQ')
|
||||
PI4_GPIO35 = gpiof('GPIO35', 'SPI0 CE1', 'SMI SWE / SRW', '', 'SD1 CMD', 'RGMII START STOP')
|
||||
PI4_GPIO36 = gpiof('GPIO36', 'SPI0 CE0', 'SMI SD0', 'UART0 TXD', 'SD1 DAT0', 'RGMII RX OK', 'MII RX ERR')
|
||||
PI4_GPIO37 = gpiof('GPIO37', 'SPI0 MISO', 'SMI SD1', 'UART0 RXD', 'SD1 DAT1', 'RGMII MDIO', 'MII TX ERR')
|
||||
PI4_GPIO38 = gpiof('GPIO38', 'SPI0 MOSI', 'SMI SD2', 'UART0 RTS', 'SD1 DAT2', 'RGMII MDC', 'MII CRS')
|
||||
PI4_GPIO39 = gpiof('GPIO39', 'SPI0 SCLK', 'SMI SD3', 'UART0 CTS', 'SD1 DAT3', 'RGMII IRQ', 'MII COL')
|
||||
PI4_GPIO40 = gpiof('GPIO40', 'PWM1 0', 'SMI SD4', '', 'SD1 DAT4', 'SPI0 MISO', 'UART1 TXD')
|
||||
PI4_GPIO41 = gpiof('GPIO41', 'PWM1 1', 'SMI SD5', '', 'SD1 DAT5', 'SPI0 MOSI', 'UART1 RXD')
|
||||
PI4_GPIO42 = gpiof('GPIO42', 'GPCLK1', 'SMI SD6', '', 'SD1 DAT6', 'SPI0 SCLK', 'UART1 RTS')
|
||||
PI4_GPIO43 = gpiof('GPIO43', 'GPCLK2', 'SMI SD7', '', 'SD1 DAT7', 'SPI0 CE0', 'UART1 CTS')
|
||||
PI4_GPIO44 = gpiof('GPIO44', 'GPCLK1', 'I2C0 SDA', 'I2C1 SDA', '', 'SPI0 CE1', 'SD CARD VOLT')
|
||||
PI4_GPIO45 = gpiof('GPIO45', 'PWM0 1', 'I2C0 SCL', 'I2C1 SCL', '', 'SPI0 CE2', 'SD CARD PWR0')
|
||||
PI4_GPIO46 = gpiof('GPIO46')
|
||||
PI4_GPIO47 = gpiof('GPIO47')
|
||||
PI4_GPIO48 = gpiof('GPIO48')
|
||||
PI4_GPIO49 = gpiof('GPIO49')
|
||||
PI4_GPIO50 = gpiof('GPIO50')
|
||||
PI4_GPIO51 = gpiof('GPIO51')
|
||||
PI4_GPIO52 = gpiof('GPIO52')
|
||||
PI4_GPIO53 = gpiof('GPIO53')
|
||||
PI4_GPIO54 = gpiof('GPIO54')
|
||||
PI4_GPIO55 = gpiof('GPIO55')
|
||||
PI4_GPIO56 = gpiof('GPIO56')
|
||||
PI4_GPIO57 = gpiof('GPIO57')
|
||||
|
||||
del gpiof
|
||||
del re
|
||||
|
||||
REV1_P1 = (13, 2, {
|
||||
1: V3_3, 2: V5,
|
||||
3: PI1_GPIO0, 4: V5,
|
||||
5: PI1_GPIO1, 6: GND,
|
||||
7: PI1_GPIO4, 8: PI1_GPIO14,
|
||||
9: GND, 10: PI1_GPIO15,
|
||||
11: PI1_GPIO17, 12: PI1_GPIO18,
|
||||
13: PI1_GPIO21, 14: GND,
|
||||
15: PI1_GPIO22, 16: PI1_GPIO23,
|
||||
17: V3_3, 18: PI1_GPIO24,
|
||||
19: PI1_GPIO10, 20: GND,
|
||||
21: PI1_GPIO9, 22: PI1_GPIO25,
|
||||
23: PI1_GPIO11, 24: PI1_GPIO8,
|
||||
25: GND, 26: PI1_GPIO7,
|
||||
})
|
||||
|
||||
REV2_P1 = (13, 2, {
|
||||
1: V3_3, 2: V5,
|
||||
3: PI1_GPIO2, 4: V5,
|
||||
5: PI1_GPIO3, 6: GND,
|
||||
7: PI1_GPIO4, 8: PI1_GPIO14,
|
||||
9: GND, 10: PI1_GPIO15,
|
||||
11: PI1_GPIO17, 12: PI1_GPIO18,
|
||||
13: PI1_GPIO27, 14: GND,
|
||||
15: PI1_GPIO22, 16: PI1_GPIO23,
|
||||
17: V3_3, 18: PI1_GPIO24,
|
||||
19: PI1_GPIO10, 20: GND,
|
||||
21: PI1_GPIO9, 22: PI1_GPIO25,
|
||||
23: PI1_GPIO11, 24: PI1_GPIO8,
|
||||
25: GND, 26: PI1_GPIO7,
|
||||
})
|
||||
|
||||
REV2_P5 = (4, 2, {
|
||||
1: V5, 2: V3_3,
|
||||
3: PI1_GPIO28, 4: PI1_GPIO29,
|
||||
5: PI1_GPIO30, 6: PI1_GPIO31,
|
||||
7: GND, 8: GND,
|
||||
})
|
||||
|
||||
PI1_P2 = (8, 1, {
|
||||
1: {'': 'GPU JTAG'},
|
||||
2: {'': 'GPU JTAG'},
|
||||
3: {'': 'GPU JTAG'},
|
||||
4: {'': 'GPU JTAG'},
|
||||
5: {'': 'GPU JTAG'},
|
||||
6: {'': 'GPU JTAG'},
|
||||
7: {'': 'GPU JTAG'},
|
||||
8: {'': 'GPU JTAG'},
|
||||
})
|
||||
|
||||
PI1_P3 = (7, 1, {
|
||||
1: {'': 'LAN JTAG'},
|
||||
2: {'': 'LAN JTAG'},
|
||||
3: {'': 'LAN JTAG'},
|
||||
4: {'': 'LAN JTAG'},
|
||||
5: {'': 'LAN JTAG'},
|
||||
6: {'': 'LAN JTAG'},
|
||||
7: {'': 'LAN JTAG'},
|
||||
})
|
||||
|
||||
REV2_P6 = (2, 1, {
|
||||
1: {'': 'RUN'},
|
||||
2: GND,
|
||||
})
|
||||
|
||||
PLUS_J8 = (20, 2, {
|
||||
1: V3_3, 2: V5,
|
||||
3: PI1_GPIO2, 4: V5,
|
||||
5: PI1_GPIO3, 6: GND,
|
||||
7: PI1_GPIO4, 8: PI1_GPIO14,
|
||||
9: GND, 10: PI1_GPIO15,
|
||||
11: PI1_GPIO17, 12: PI1_GPIO18,
|
||||
13: PI1_GPIO27, 14: GND,
|
||||
15: PI1_GPIO22, 16: PI1_GPIO23,
|
||||
17: V3_3, 18: PI1_GPIO24,
|
||||
19: PI1_GPIO10, 20: GND,
|
||||
21: PI1_GPIO9, 22: PI1_GPIO25,
|
||||
23: PI1_GPIO11, 24: PI1_GPIO8,
|
||||
25: GND, 26: PI1_GPIO7,
|
||||
27: PI1_GPIO0, 28: PI1_GPIO1,
|
||||
29: PI1_GPIO5, 30: GND,
|
||||
31: PI1_GPIO6, 32: PI1_GPIO12,
|
||||
33: PI1_GPIO13, 34: GND,
|
||||
35: PI1_GPIO19, 36: PI1_GPIO16,
|
||||
37: PI1_GPIO26, 38: PI1_GPIO20,
|
||||
39: GND, 40: PI1_GPIO21,
|
||||
})
|
||||
|
||||
PLUS_POE = (2, 2, {
|
||||
1: {'': 'TR01 TAP'}, 2: {'': 'TR00 TAP'},
|
||||
3: {'': 'TR03 TAP'}, 4: {'': 'TR02 TAP'},
|
||||
})
|
||||
|
||||
PLUS_RUN = (2, 1, {
|
||||
1: {'': 'POWER ENABLE'},
|
||||
2: {'': 'RUN'},
|
||||
})
|
||||
|
||||
ZERO_RUN = REV2_P6
|
||||
|
||||
ZERO_TV = (2, 1, {
|
||||
1: {'': 'COMPOSITE'},
|
||||
2: GND,
|
||||
})
|
||||
|
||||
PI4_J8 = (20, 2, {
|
||||
1: V3_3, 2: V5,
|
||||
3: PI4_GPIO2, 4: V5,
|
||||
5: PI4_GPIO3, 6: GND,
|
||||
7: PI4_GPIO4, 8: PI4_GPIO14,
|
||||
9: GND, 10: PI4_GPIO15,
|
||||
11: PI4_GPIO17, 12: PI4_GPIO18,
|
||||
13: PI4_GPIO27, 14: GND,
|
||||
15: PI4_GPIO22, 16: PI4_GPIO23,
|
||||
17: V3_3, 18: PI4_GPIO24,
|
||||
19: PI4_GPIO10, 20: GND,
|
||||
21: PI4_GPIO9, 22: PI4_GPIO25,
|
||||
23: PI4_GPIO11, 24: PI4_GPIO8,
|
||||
25: GND, 26: PI4_GPIO7,
|
||||
27: PI4_GPIO0, 28: PI4_GPIO1,
|
||||
29: PI4_GPIO5, 30: GND,
|
||||
31: PI4_GPIO6, 32: PI4_GPIO12,
|
||||
33: PI4_GPIO13, 34: GND,
|
||||
35: PI4_GPIO19, 36: PI4_GPIO16,
|
||||
37: PI4_GPIO26, 38: PI4_GPIO20,
|
||||
39: GND, 40: PI4_GPIO21,
|
||||
})
|
||||
|
||||
PI4_J2 = (3, 1, {
|
||||
1: {'': 'GLOBAL ENABLE'},
|
||||
2: GND,
|
||||
3: {'': 'RUN'},
|
||||
})
|
||||
|
||||
PI4_J14 = PLUS_POE
|
||||
|
||||
PI5_J2 = (2, 1, {
|
||||
1: {'': 'RUN'},
|
||||
2: GND,
|
||||
})
|
||||
|
||||
PI5_J7 = (2, 1, {
|
||||
1: {'': 'COMPOSITE'},
|
||||
2: GND,
|
||||
})
|
||||
|
||||
CM_SODIMM = (100, 2, {
|
||||
1: GND, 2: {'': 'EMMC DISABLE N'},
|
||||
3: PI1_GPIO0, 4: NC,
|
||||
5: PI1_GPIO1, 6: NC,
|
||||
7: GND, 8: NC,
|
||||
9: PI1_GPIO2, 10: NC,
|
||||
11: PI1_GPIO3, 12: NC,
|
||||
13: GND, 14: NC,
|
||||
15: PI1_GPIO4, 16: NC,
|
||||
17: PI1_GPIO5, 18: NC,
|
||||
19: GND, 20: NC,
|
||||
21: PI1_GPIO6, 22: NC,
|
||||
23: PI1_GPIO7, 24: NC,
|
||||
25: GND, 26: GND,
|
||||
27: PI1_GPIO8, 28: PI1_GPIO28,
|
||||
29: PI1_GPIO9, 30: PI1_GPIO29,
|
||||
31: GND, 32: GND,
|
||||
33: PI1_GPIO10, 34: PI1_GPIO30,
|
||||
35: PI1_GPIO11, 36: PI1_GPIO31,
|
||||
37: GND, 38: GND,
|
||||
39: {'': 'GPIO0-27 VREF'}, 40: {'': 'GPIO0-27 VREF'},
|
||||
# Gap in SODIMM pins
|
||||
41: {'': 'GPIO28-45 VREF'}, 42: {'': 'GPIO28-45 VREF'},
|
||||
43: GND, 44: GND,
|
||||
45: PI1_GPIO12, 46: PI1_GPIO32,
|
||||
47: PI1_GPIO13, 48: PI1_GPIO33,
|
||||
49: GND, 50: GND,
|
||||
51: PI1_GPIO14, 52: PI1_GPIO34,
|
||||
53: PI1_GPIO15, 54: PI1_GPIO35,
|
||||
55: GND, 56: GND,
|
||||
57: PI1_GPIO16, 58: PI1_GPIO36,
|
||||
59: PI1_GPIO17, 60: PI1_GPIO37,
|
||||
61: GND, 62: GND,
|
||||
63: PI1_GPIO18, 64: PI1_GPIO38,
|
||||
65: PI1_GPIO19, 66: PI1_GPIO39,
|
||||
67: GND, 68: GND,
|
||||
69: PI1_GPIO20, 70: PI1_GPIO40,
|
||||
71: PI1_GPIO21, 72: PI1_GPIO41,
|
||||
73: GND, 74: GND,
|
||||
75: PI1_GPIO22, 76: PI1_GPIO42,
|
||||
77: PI1_GPIO23, 78: PI1_GPIO43,
|
||||
79: GND, 80: GND,
|
||||
81: PI1_GPIO24, 82: PI1_GPIO44,
|
||||
83: PI1_GPIO25, 84: PI1_GPIO45,
|
||||
85: GND, 86: GND,
|
||||
87: PI1_GPIO26, 88: {'': 'GPIO46 1V8'},
|
||||
89: PI1_GPIO27, 90: {'': 'GPIO47 1V8'},
|
||||
91: GND, 92: GND,
|
||||
93: {'': 'DSI0 DN1'}, 94: {'': 'DSI1 DP0'},
|
||||
95: {'': 'DSI0 DP1'}, 96: {'': 'DSI1 DN0'},
|
||||
97: GND, 98: GND,
|
||||
99: {'': 'DSI0 DN0'}, 100: {'': 'DSI1 CP'},
|
||||
101: {'': 'DSI0 DP0'}, 102: {'': 'DSI1 CN'},
|
||||
103: GND, 104: GND,
|
||||
105: {'': 'DSI0 CN'}, 106: {'': 'DSI1 DP3'},
|
||||
107: {'': 'DSI0 CP'}, 108: {'': 'DSI1 DN3'},
|
||||
109: GND, 110: GND,
|
||||
111: {'': 'HDMI CK N'}, 112: {'': 'DSI1 DP2'},
|
||||
113: {'': 'HDMI CK P'}, 114: {'': 'DSI1 DN2'},
|
||||
115: GND, 116: GND,
|
||||
117: {'': 'HDMI D0 N'}, 118: {'': 'DSI1 DP1'},
|
||||
119: {'': 'HDMI D0 P'}, 120: {'': 'DSI1 DN1'},
|
||||
121: GND, 122: GND,
|
||||
123: {'': 'HDMI D1 N'}, 124: NC,
|
||||
125: {'': 'HDMI D1 P'}, 126: NC,
|
||||
127: GND, 128: NC,
|
||||
129: {'': 'HDMI D2 N'}, 130: NC,
|
||||
131: {'': 'HDMI D2 P'}, 132: NC,
|
||||
133: GND, 134: GND,
|
||||
135: {'': 'CAM1 DP3'}, 136: {'': 'CAM0 DP0'},
|
||||
137: {'': 'CAM1 DN3'}, 138: {'': 'CAM0 DN0'},
|
||||
139: GND, 140: GND,
|
||||
141: {'': 'CAM1 DP2'}, 142: {'': 'CAM0 CP'},
|
||||
143: {'': 'CAM1 DN2'}, 144: {'': 'CAM0 CN'},
|
||||
145: GND, 146: GND,
|
||||
147: {'': 'CAM1 CP'}, 148: {'': 'CAM0 DP1'},
|
||||
149: {'': 'CAM1 CN'}, 150: {'': 'CAM0 DN1'},
|
||||
151: GND, 152: GND,
|
||||
153: {'': 'CAM1 DP1'}, 154: NC,
|
||||
155: {'': 'CAM1 DN1'}, 156: NC,
|
||||
157: GND, 158: NC,
|
||||
159: {'': 'CAM1 DP0'}, 160: NC,
|
||||
161: {'': 'CAM1 DN0'}, 162: NC,
|
||||
163: GND, 164: GND,
|
||||
165: {'': 'USB DP'}, 166: {'': 'TVDAC'},
|
||||
167: {'': 'USB DM'}, 168: {'': 'USB OTGID'},
|
||||
169: GND, 170: GND,
|
||||
171: {'': 'HDMI CEC'}, 172: {'': 'VC TRST N'},
|
||||
173: {'': 'HDMI SDA'}, 174: {'': 'VC TDI'},
|
||||
175: {'': 'HDMI SCL'}, 176: {'': 'VC TMS'},
|
||||
177: {'': 'RUN'}, 178: {'': 'VC TDO'},
|
||||
179: {'': 'VDD CORE'}, 180: {'': 'VC TCK'},
|
||||
181: GND, 182: GND,
|
||||
183: V1_8, 184: V1_8,
|
||||
185: V1_8, 186: V1_8,
|
||||
187: GND, 188: GND,
|
||||
189: {'': 'VDAC'}, 190: {'': 'VDAC'},
|
||||
191: V3_3, 192: V3_3,
|
||||
193: V3_3, 194: V3_3,
|
||||
195: GND, 196: GND,
|
||||
197: {'': 'VBAT'}, 198: {'': 'VBAT'},
|
||||
199: {'': 'VBAT'}, 200: {'': 'VBAT'},
|
||||
})
|
||||
|
||||
CM3_SODIMM = (CM_SODIMM[0], CM_SODIMM[1], CM_SODIMM[2].copy())
|
||||
CM3_SODIMM[-1].update({
|
||||
4: {'': 'NC / SDX VREF'},
|
||||
6: {'': 'NC / SDX VREF'},
|
||||
8: GND,
|
||||
10: {'': 'NC / SDX CLK'},
|
||||
12: {'': 'NC / SDX CMD'},
|
||||
14: GND,
|
||||
16: {'': 'NC / SDX D0'},
|
||||
18: {'': 'NC / SDX D1'},
|
||||
20: GND,
|
||||
22: {'': 'NC / SDX D2'},
|
||||
24: {'': 'NC / SDX D3'},
|
||||
88: {'': 'HDMI HPD N 1V8'},
|
||||
90: {'': 'EMMC EN N 1V8'},
|
||||
})
|
||||
|
||||
CM4_J6 = (2, 2, {
|
||||
1: {'': '1-2 CAM0+DISP0'}, 2: {'': '1-2 CAM0+DISP0'},
|
||||
3: {'': '3-4 CAM0+DISP0'}, 4: {'': '3-4 CAM0+DISP0'},
|
||||
})
|
||||
|
||||
CM4_J2 = (7, 2, {
|
||||
1: {'': '1-2 DISABLE eMMC BOOT'}, 2: {'': '1-2 DISABLE eMMC BOOT'},
|
||||
3: {'': '3-4 WRITE-PROT EEPROM'}, 4: {'': '3-4 WRITE-PROT EEPROM'},
|
||||
5: {'': 'AIN0 MXL7704'}, 6: {'': 'AIN1 MXL7704'},
|
||||
7: GND, 8: {'': 'SYNC_IN'},
|
||||
9: {'': 'SYNC OUT'}, 10: GND,
|
||||
11: {'': 'TV OUT'}, 12: GND,
|
||||
13: {'': '13-14 WAKE'}, 14: {'': '13-14 WAKE'},
|
||||
})
|
||||
|
||||
CM4_J1 = PI4_J2
|
||||
CM4_J9 = PLUS_POE
|
||||
|
||||
CM4_J3 = (3, 1, {
|
||||
1: {'': 'WL DISABLE'},
|
||||
2: GND,
|
||||
3: {'': 'BT DISABLE'},
|
||||
})
|
||||
|
||||
# The following data is sourced from a combination of the following locations:
|
||||
#
|
||||
# http://elinux.org/RPi_HardwareHistory
|
||||
# http://elinux.org/RPi_Low-level_peripherals
|
||||
# https://git.drogon.net/?p=wiringPi;a=blob;f=wiringPi/wiringPi.c#l807
|
||||
# https://www.raspberrypi.org/documentation/hardware/raspberrypi/revision-codes/README.md
|
||||
|
||||
PI_REVISIONS = {
|
||||
# rev model pcb_rev released soc manufacturer ram storage usb eth wifi bt csi dsi headers board
|
||||
0x2: ('B', '1.0', '2012Q1', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1, 'P2': PI1_P2, 'P3': PI1_P3}, REV1_BOARD, ),
|
||||
0x3: ('B', '1.0', '2012Q3', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1, 'P2': PI1_P2, 'P3': PI1_P3}, REV1_BOARD, ),
|
||||
0x4: ('B', '2.0', '2012Q3', 'BCM2835', 'Sony', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P2': PI1_P2, 'P3': PI1_P3, 'P5': REV2_P5, 'P6': REV2_P6}, REV2_BOARD, ),
|
||||
0x5: ('B', '2.0', '2012Q4', 'BCM2835', 'Qisda', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P2': PI1_P2, 'P3': PI1_P3, 'P5': REV2_P5, 'P6': REV2_P6}, REV2_BOARD, ),
|
||||
0x6: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P2': PI1_P2, 'P3': PI1_P3, 'P5': REV2_P5, 'P6': REV2_P6}, REV2_BOARD, ),
|
||||
0x7: ('A', '2.0', '2013Q1', 'BCM2835', 'Egoman', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P2': PI1_P2, 'P3': PI1_P3, 'P5': REV2_P5, 'P6': REV2_P6}, A_BOARD, ),
|
||||
0x8: ('A', '2.0', '2013Q1', 'BCM2835', 'Sony', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P2': PI1_P2, 'P3': PI1_P3, 'P5': REV2_P5, 'P6': REV2_P6}, A_BOARD, ),
|
||||
0x9: ('A', '2.0', '2013Q1', 'BCM2835', 'Qisda', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P2': PI1_P2, 'P3': PI1_P3, 'P5': REV2_P5, 'P6': REV2_P6}, A_BOARD, ),
|
||||
0xd: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P2': PI1_P2, 'P3': PI1_P3, 'P5': REV2_P5, 'P6': REV2_P6}, REV2_BOARD, ),
|
||||
0xe: ('B', '2.0', '2012Q4', 'BCM2835', 'Sony', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P2': PI1_P2, 'P3': PI1_P3, 'P5': REV2_P5, 'P6': REV2_P6}, REV2_BOARD, ),
|
||||
0xf: ('B', '2.0', '2012Q4', 'BCM2835', 'Qisda', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P2': PI1_P2, 'P3': PI1_P3, 'P5': REV2_P5, 'P6': REV2_P6}, REV2_BOARD, ),
|
||||
0x10: ('B+', '1.2', '2014Q3', 'BCM2835', 'Sony', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'J8': PLUS_J8}, BPLUS_BOARD, ),
|
||||
0x11: ('CM', '1.1', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 1, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, CM_BOARD, ),
|
||||
0x12: ('A+', '1.1', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'J8': PLUS_J8}, APLUS_BOARD, ),
|
||||
0x13: ('B+', '1.2', '2015Q1', 'BCM2835', 'Egoman', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'J8': PLUS_J8}, BPLUS_BOARD, ),
|
||||
0x14: ('CM', '1.1', '2014Q2', 'BCM2835', 'Embest', 512, 'eMMC', 1, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, CM_BOARD, ),
|
||||
0x15: ('A+', '1.1', '2014Q4', 'BCM2835', 'Embest', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'J8': PLUS_J8}, APLUS_BOARD, ),
|
||||
}
|
||||
|
||||
SPI_HARDWARE_PINS = {
|
||||
0: {
|
||||
'clock': 'GPIO11',
|
||||
'mosi': 'GPIO10',
|
||||
'miso': 'GPIO9',
|
||||
'select': ('GPIO8', 'GPIO7'),
|
||||
},
|
||||
}
|
||||
365
venv/lib/python3.11/site-packages/gpiozero/pins/lgpio.py
Normal file
365
venv/lib/python3.11/site-packages/gpiozero/pins/lgpio.py
Normal file
@@ -0,0 +1,365 @@
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2021-2023 Dave Jones <dave@waveform.org.uk>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
try:
|
||||
# Patch several constants which changed incompatibly between lg 0.1.6.0
|
||||
# and 0.2.0.0
|
||||
lgpio.SET_PULL_NONE
|
||||
except AttributeError:
|
||||
lgpio.SET_PULL_NONE = lgpio.SET_BIAS_DISABLE
|
||||
lgpio.SET_PULL_UP = lgpio.SET_BIAS_PULL_UP
|
||||
lgpio.SET_PULL_DOWN = lgpio.SET_BIAS_PULL_DOWN
|
||||
|
||||
|
||||
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=None):
|
||||
super().__init__()
|
||||
chip = 4 if (self._get_revision() & 0xff0) >> 4 == 0x17 else 0
|
||||
self._handle = lgpio.gpiochip_open(chip)
|
||||
self._chip = chip
|
||||
self.pin_class = LGPIOPin
|
||||
|
||||
def close(self):
|
||||
super().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()._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, info):
|
||||
super().__init__(factory, info)
|
||||
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_PULL_NONE)
|
||||
|
||||
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_PULL_NONE)
|
||||
|
||||
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(
|
||||
f'invalid function "{value}" for pin {self!r}')
|
||||
|
||||
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(
|
||||
f'invalid state "{value}" for pin {self!r}')
|
||||
elif self.function == 'input':
|
||||
raise PinSetInput(f'cannot set state of pin {self!r}')
|
||||
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(f'cannot set pull on non-input pin {self!r}')
|
||||
if self.info.pull not in (value, ''):
|
||||
raise PinFixedPull(
|
||||
f'{self!r} has a physical pull-{self.info.pull} resistor')
|
||||
try:
|
||||
flags = {
|
||||
'up': lgpio.SET_PULL_UP,
|
||||
'down': lgpio.SET_PULL_DOWN,
|
||||
'floating': lgpio.SET_PULL_NONE,
|
||||
}[value]
|
||||
except KeyError:
|
||||
raise PinInvalidPull(f'invalid pull "{value}" for pin {self!r}')
|
||||
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(f'cannot start PWM on pin {self!r}')
|
||||
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()._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().__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().close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self._handle is None
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
self._check_open()
|
||||
return f'SPI(port={self._port:d}, device={self._device:d})'
|
||||
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(f"{value} is not a valid SPI clock mode")
|
||||
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(f'SPI transfer error {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(f'SPI transfer error {count}')
|
||||
return len(data)
|
||||
|
||||
def transfer(self, data):
|
||||
self._check_open()
|
||||
count, data = lgpio.spi_xfer(self._handle, data)
|
||||
if count < 0:
|
||||
raise IOError(f'SPI transfer error {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)
|
||||
214
venv/lib/python3.11/site-packages/gpiozero/pins/local.py
Normal file
214
venv/lib/python3.11/site-packages/gpiozero/pins/local.py
Normal file
@@ -0,0 +1,214 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2023 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2020 Fangchen Li <fangchen.li@outlook.com>
|
||||
# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import io
|
||||
import errno
|
||||
import struct
|
||||
from collections import defaultdict
|
||||
from threading import Lock
|
||||
from time import 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 SPISoftware
|
||||
from ..devices import Device
|
||||
from ..mixins import 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('>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.lgpio.LGPIOPin`, and
|
||||
:class:`~gpiozero.pins.native.NativePin`).
|
||||
"""
|
||||
pins = {}
|
||||
_reservations = defaultdict(list)
|
||||
_res_lock = Lock()
|
||||
|
||||
def __init__(self):
|
||||
super().__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):
|
||||
return 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()._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._bus = None
|
||||
if SpiDev is None:
|
||||
raise ImportError('failed to import spidev')
|
||||
super().__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._bus = SpiDev()
|
||||
self._bus.open(self._port, self._device)
|
||||
self._bus.max_speed_hz = 500000
|
||||
|
||||
def _conflicts_with(self, other):
|
||||
if isinstance(other, LocalPiHardwareSPI):
|
||||
return (
|
||||
(self._port == other._port) and
|
||||
(self._device == other._device))
|
||||
else:
|
||||
return True
|
||||
|
||||
def close(self):
|
||||
if self._bus is not None:
|
||||
self._bus.close()
|
||||
self._bus = None
|
||||
self.pin_factory.release_all(self)
|
||||
super().close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self._bus 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._bus.xfer2(data)
|
||||
|
||||
def _get_clock_mode(self):
|
||||
return self._bus.mode
|
||||
|
||||
def _set_clock_mode(self, value):
|
||||
self._bus.mode = value
|
||||
|
||||
def _get_lsb_first(self):
|
||||
return self._bus.lsbfirst
|
||||
|
||||
def _set_lsb_first(self, value):
|
||||
self._bus.lsbfirst = bool(value)
|
||||
|
||||
def _get_select_high(self):
|
||||
return self._bus.cshigh
|
||||
|
||||
def _set_select_high(self, value):
|
||||
self._bus.cshigh = bool(value)
|
||||
|
||||
def _get_bits_per_word(self):
|
||||
return self._bus.bits_per_word
|
||||
|
||||
def _set_bits_per_word(self, value):
|
||||
self._bus.bits_per_word = value
|
||||
|
||||
def _get_rate(self):
|
||||
return self._bus.max_speed_hz
|
||||
|
||||
def _set_rate(self, value):
|
||||
self._bus.max_speed_hz = int(value)
|
||||
|
||||
|
||||
class LocalPiSoftwareSPI(SPISoftware):
|
||||
pass
|
||||
|
||||
|
||||
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)
|
||||
535
venv/lib/python3.11/site-packages/gpiozero/pins/mock.py
Normal file
535
venv/lib/python3.11/site-packages/gpiozero/pins/mock.py
Normal file
@@ -0,0 +1,535 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2024 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2020 Fangchen Li <fangchen.li@outlook.com>
|
||||
# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import os
|
||||
from collections import namedtuple
|
||||
from time import time, sleep, monotonic
|
||||
from threading import Thread, Event
|
||||
from math import isclose
|
||||
|
||||
# NOTE: Remove try when compatibility moves beyond Python 3.10
|
||||
try:
|
||||
from importlib_metadata import entry_points
|
||||
except ImportError:
|
||||
from importlib.metadata import entry_points
|
||||
|
||||
from ..exc import (
|
||||
PinPWMUnsupported,
|
||||
PinSetInput,
|
||||
PinFixedPull,
|
||||
PinInvalidPin,
|
||||
PinInvalidFunction,
|
||||
PinInvalidPull,
|
||||
PinInvalidBounce,
|
||||
)
|
||||
from ..devices import Device
|
||||
from ..mixins import SharedMixin
|
||||
from . import SPI
|
||||
from .pi import PiPin, PiFactory
|
||||
from .spi import SPISoftware
|
||||
|
||||
|
||||
PinState = namedtuple('PinState', ('timestamp', 'state'))
|
||||
|
||||
|
||||
class MockPin(PiPin):
|
||||
"""
|
||||
A mock pin used primarily for testing. This class does *not* support PWM.
|
||||
"""
|
||||
def __init__(self, factory, info):
|
||||
super().__init__(factory, info)
|
||||
self._function = 'input'
|
||||
self._pull = info.pull or '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(f'cannot set state of pin {self!r}')
|
||||
assert self._function == 'output'
|
||||
assert 0 <= value <= 1
|
||||
self._change_state(bool(value))
|
||||
|
||||
def _change_state(self, value):
|
||||
if self._state != value:
|
||||
t = monotonic()
|
||||
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(f'cannot set pull on non-input pin {self!r}')
|
||||
if self.info.pull and value != self.info.pull:
|
||||
raise PinFixedPull(f'{self!r} has a fixed pull resistor')
|
||||
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 _call_when_changed(self):
|
||||
super()._call_when_changed(self._last_change, self._state)
|
||||
|
||||
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 = monotonic()
|
||||
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, info, input_pin=None):
|
||||
super().__init__(factory, info)
|
||||
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()._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, info, charge_time=0.01):
|
||||
super().__init__(factory, info)
|
||||
self.charge_time = charge_time # dark charging time
|
||||
self._charge_stop = Event()
|
||||
self._charge_thread = None
|
||||
|
||||
def _set_function(self, value):
|
||||
super()._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, info, echo_pin=None, echo_time=0.04):
|
||||
super().__init__(factory, info)
|
||||
self.echo_pin = echo_pin
|
||||
self.echo_time = echo_time # longest echo time
|
||||
self._echo_thread = None
|
||||
|
||||
def _set_state(self, value):
|
||||
super()._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, info):
|
||||
super().__init__(factory, info)
|
||||
self._frequency = None
|
||||
|
||||
def close(self):
|
||||
self.frequency = None
|
||||
super().close()
|
||||
|
||||
def _set_state(self, value):
|
||||
if self._function == 'input':
|
||||
raise PinSetInput(f'cannot set state of pin {self!r}')
|
||||
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, info):
|
||||
super().__init__(factory, info)
|
||||
self.spi_devices = getattr(self, 'spi_devices', [])
|
||||
|
||||
def _set_state(self, value):
|
||||
super()._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, info):
|
||||
super().__init__(factory, info)
|
||||
self.spi_device = getattr(self, 'spi_device', None)
|
||||
|
||||
def _set_state(self, value):
|
||||
super()._set_state(value)
|
||||
if self.spi_device:
|
||||
self.spi_device.on_select()
|
||||
|
||||
|
||||
class MockSPIDevice:
|
||||
"""
|
||||
This class is used to test :class:`SPIDevice` implementations. It can be
|
||||
used to mock up the slave side of simple SPI devices, e.g. the MCP3xxx
|
||||
series of ADCs.
|
||||
|
||||
Descendants should override the :meth:`on_start` and/or :meth:`on_bit`
|
||||
methods to respond to SPI interface events. The :meth:`rx_word` and
|
||||
:meth:`tx_word` methods can be used facilitate communications within these
|
||||
methods. Such descendents can then be passed as the *spi_class* parameter
|
||||
of the :class:`MockFactory` constructor to have instances attached to any
|
||||
SPI interface requested by an :class:`SPIDevice`.
|
||||
"""
|
||||
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,
|
||||
pin_factory=None):
|
||||
if pin_factory is None:
|
||||
pin_factory = Device.pin_factory
|
||||
assert isinstance(pin_factory, MockFactory)
|
||||
self.clock_pin = pin_factory.pin(clock_pin, pin_class=MockSPIClockPin)
|
||||
self.mosi_pin = None if mosi_pin is None else pin_factory.pin(mosi_pin)
|
||||
self.miso_pin = None if miso_pin is None else pin_factory.pin(miso_pin)
|
||||
self.select_pin = None if select_pin is None else 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(PiFactory):
|
||||
"""
|
||||
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:`Factory.board_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 descendant) 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().__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):
|
||||
group = entry_points(group='gpiozero_mock_pin_classes')
|
||||
pin_class = group[pin_class.lower()].load()
|
||||
if not issubclass(pin_class, MockPin):
|
||||
raise ValueError(f'invalid mock pin_class: {pin_class!r}')
|
||||
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, name, 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
|
||||
for header, info in self.board_info.find_pin(name):
|
||||
try:
|
||||
pin = self.pins[info]
|
||||
except KeyError:
|
||||
pin = pin_class(self, info, **kwargs)
|
||||
self.pins[info] = pin
|
||||
else:
|
||||
# Ensure the pin class expected supports PWM (or not)
|
||||
if issubclass(pin_class, MockPWMPin) != isinstance(pin, MockPWMPin):
|
||||
raise ValueError(
|
||||
f'pin {info.name} is already in use as a '
|
||||
f'{pin.__class__.__name__}')
|
||||
return pin
|
||||
raise PinInvalidPin(f'{name} is not a valid pin name')
|
||||
|
||||
def _get_spi_class(self, shared, hardware):
|
||||
return MockSPIInterfaceShared if shared else MockSPIInterface
|
||||
|
||||
@staticmethod
|
||||
def ticks():
|
||||
return monotonic()
|
||||
|
||||
@staticmethod
|
||||
def ticks_diff(later, earlier):
|
||||
return later - earlier
|
||||
|
||||
|
||||
class MockSPIInterface(SPISoftware):
|
||||
pass
|
||||
|
||||
|
||||
class MockSPIInterfaceShared(SharedMixin, MockSPIInterface):
|
||||
@classmethod
|
||||
def _shared_key(cls, clock_pin, mosi_pin, miso_pin, select_pin,
|
||||
pin_factory):
|
||||
return (clock_pin, select_pin)
|
||||
619
venv/lib/python3.11/site-packages/gpiozero/pins/native.py
Normal file
619
venv/lib/python3.11/site-packages/gpiozero/pins/native.py
Normal file
@@ -0,0 +1,619 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2015-2023 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2020 Fangchen Li <fangchen.li@outlook.com>
|
||||
# Copyright (c) 2016-2020 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import mmap
|
||||
import errno
|
||||
import struct
|
||||
import select
|
||||
from time import sleep
|
||||
from threading import Thread, Event, RLock
|
||||
from queue import Queue, Empty
|
||||
from pathlib import Path
|
||||
|
||||
from .local import LocalPiPin, LocalPiFactory
|
||||
from ..exc import (
|
||||
PinInvalidPull,
|
||||
PinInvalidEdges,
|
||||
PinInvalidFunction,
|
||||
PinFixedPull,
|
||||
PinSetInput,
|
||||
)
|
||||
|
||||
|
||||
def dt_resolve_alias(alias, root=Path('/proc/device-tree')):
|
||||
"""
|
||||
Returns the full :class:`~pathlib.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'
|
||||
"""
|
||||
if not isinstance(root, Path):
|
||||
root = Path(root)
|
||||
filename = root / 'aliases' / alias
|
||||
with filename.open('rb') as f:
|
||||
node, tail = f.read().split(b'\0', 1)
|
||||
fs_encoding = sys.getfilesystemencoding()
|
||||
return root / node.decode(fs_encoding).lstrip('/')
|
||||
|
||||
def dt_peripheral_reg(node, root=Path('/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'))
|
||||
>>> f'{reg.start:#x}..{reg.stop:#x}'
|
||||
'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 (node / '#address-cells').open('rb') as f:
|
||||
address_cells = struct.unpack('>L', f.read())[0]
|
||||
with (node / '#size-cells').open('rb') as f:
|
||||
size_cells = struct.unpack('>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(f'>{cells}L') 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(f'failed to read {buf_len} bytes')
|
||||
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_cells, _ = _cells(node.parent)
|
||||
ranges_reader = _reader(child_cells, parent_cells, size_cells)
|
||||
with (node / 'ranges').open('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)
|
||||
]
|
||||
|
||||
if not isinstance(root, Path):
|
||||
root = Path(root)
|
||||
node = root / node
|
||||
child_cells, size_cells = _cells(node.parent)
|
||||
reg_reader = _reader(child_cells, size_cells)
|
||||
with (node / 'reg').open('rb') as f:
|
||||
base, size = list(reg_reader(f))[0]
|
||||
while node.parent != root:
|
||||
# Iterate up the hierarchy, resolving the base address as we go
|
||||
if (node.parent / 'ranges').exists():
|
||||
for child_range, parent_range in _ranges(node.parent):
|
||||
if base in child_range:
|
||||
base += parent_range.start - child_range.start
|
||||
break
|
||||
node = node.parent
|
||||
return range(base, base + size)
|
||||
|
||||
|
||||
class GPIOMemory:
|
||||
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 ('@I', '@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:
|
||||
return dt_peripheral_reg(dt_resolve_alias('gpio')).start
|
||||
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:
|
||||
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(f'gpio{pin:d}/value')
|
||||
|
||||
def path_dir(self, pin):
|
||||
return self.path(f'gpio{pin:d}/direction')
|
||||
|
||||
def path_edge(self, pin):
|
||||
return self.path(f'gpio{pin:d}/edge')
|
||||
|
||||
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(f'failed to export pin {pin:d}')
|
||||
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().__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().__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().__init__()
|
||||
queue = Queue()
|
||||
self.mem = GPIOMemory(self.board_info.soc)
|
||||
self.fs = GPIOFS(self, queue)
|
||||
self.dispatch = NativeDispatchThread(self, queue)
|
||||
if self.board_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().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, info):
|
||||
super().__init__(factory, info)
|
||||
self._reg_init(factory, self._number)
|
||||
self._last_call = None
|
||||
self._when_changed = None
|
||||
self._change_thread = None
|
||||
self._change_event = Event()
|
||||
self.function = 'input'
|
||||
self.pull = info.pull or '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 = self.info.pull or '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(
|
||||
f'invalid function "{value}" for pin {self!r}')
|
||||
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(f'cannot set state of pin {self!r}')
|
||||
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(
|
||||
f'invalid edge specification "{value}" for pin {self!r}')
|
||||
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()._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(f'cannot set pull on non-input pin {self!r}')
|
||||
if self.info.pull not in (value, ''):
|
||||
raise PinFixedPull(
|
||||
f'{self!r} has a physical pull-{self.info.pull} resistor')
|
||||
try:
|
||||
value = self.GPIO_PULL_UPS[value]
|
||||
except KeyError:
|
||||
raise PinInvalidPull(
|
||||
f'invalid pull direction "{value}" for pin {self!r}')
|
||||
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()._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(f'cannot set pull on non-input pin {self!r}')
|
||||
if self.info.pull not in (value, ''):
|
||||
raise PinFixedPull(
|
||||
f'{self!r} has a physical pull-{self.info.pull} resistor')
|
||||
try:
|
||||
value = self.GPIO_PULL_UPS[value]
|
||||
except KeyError:
|
||||
raise PinInvalidPull(
|
||||
f'invalid pull direction "{value}" for pin {self!r}')
|
||||
self.factory.mem[self._pull_offset] = (
|
||||
self.factory.mem[self._pull_offset]
|
||||
& ~(3 << self._pull_shift)
|
||||
| (value << self._pull_shift)
|
||||
)
|
||||
693
venv/lib/python3.11/site-packages/gpiozero/pins/pi.py
Normal file
693
venv/lib/python3.11/site-packages/gpiozero/pins/pi.py
Normal file
@@ -0,0 +1,693 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2023 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2020 Fangchen Li <fangchen.li@outlook.com>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import re
|
||||
from threading import RLock
|
||||
from types import MethodType
|
||||
from weakref import ref, WeakMethod
|
||||
import warnings
|
||||
|
||||
try:
|
||||
from spidev import SpiDev
|
||||
except ImportError:
|
||||
SpiDev = None
|
||||
|
||||
from . import Factory, Pin, BoardInfo, HeaderInfo, PinInfo, data
|
||||
from .data import SPI_HARDWARE_PINS
|
||||
from ..compat import frozendict
|
||||
from ..devices import Device
|
||||
from ..exc import (
|
||||
GPIOPinInUse,
|
||||
PinInvalidPin,
|
||||
PinNoPins,
|
||||
PinNonPhysical,
|
||||
PinUnknownPi,
|
||||
SPIBadArgs,
|
||||
SPISoftwareFallback,
|
||||
)
|
||||
|
||||
|
||||
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 PiBoardInfo(BoardInfo):
|
||||
__slots__ = () # workaround python issue #24931
|
||||
|
||||
@classmethod
|
||||
def from_revision(cls, revision):
|
||||
"""
|
||||
Construct a :class:`PiBoardInfo` instance from the specified Raspberry
|
||||
Pi board *revision* which must be specified as an :class:`int`
|
||||
(typically in hexi-decimal format).
|
||||
|
||||
For example, from an old-style revision code for the model B+::
|
||||
|
||||
>>> from gpiozero.pins.pi import PiBoardInfo
|
||||
>>> PiBoardInfo.from_revision(0x0010)
|
||||
PiBoardInfo(revision='0010', model='B+', pcb_revision='1.2',
|
||||
released='2014Q3', soc='BCM2835', manufacturer='Sony', memory=512,
|
||||
storage='MicroSD', usb=4, usb3=0, ethernet=1, eth_speed=100,
|
||||
wifi=False, bluetooth=False, csi=1, dsi=1, headers=..., board=...)
|
||||
|
||||
Or from a new-style revision code for the Pi Zero 2W::
|
||||
|
||||
>>> PiBoardInfo.from_revision(0x902120)
|
||||
PiBoardInfo(revision='902120', model='Zero2W', pcb_revision='1.0',
|
||||
released='2021Q4', soc='BCM2837', manufacturer='Sony', memory=512,
|
||||
storage='MicroSD', usb=1, usb3=0, ethernet=0, eth_speed=0,
|
||||
wifi=True, bluetooth=True, csi=1, dsi=0, headers=..., board=...)
|
||||
"""
|
||||
if revision & 0x800000:
|
||||
# New-style revision, parse information from bit-pattern:
|
||||
#
|
||||
# MSB -----------------------> LSB
|
||||
# NOQuuuWuFMMMCCCCPPPPTTTTTTTTRRRR
|
||||
#
|
||||
# N - Overvoltage (0=allowed, 1=disallowed)
|
||||
# O - OTP programming (0=allowed, 1=disallowed)
|
||||
# Q - OTP read (0=allowed, 1=disallowed)
|
||||
# u - Unused
|
||||
# W - Warranty bit (0=intact, 1=voided by overclocking)
|
||||
# F - New flag (1=valid new-style revision, 0=old-style)
|
||||
# MMM - Memory size (see memory dict below)
|
||||
# CCCC - Manufacturer (see manufacturer dict below)
|
||||
# PPPP - Processor (see soc dict below)
|
||||
# TTTTTTTT - Type (see model dict below)
|
||||
# RRRR - Revision (0, 1, 2, etc.)
|
||||
revcode_memory = (revision & 0x700000) >> 20
|
||||
revcode_manufacturer = (revision & 0xf0000) >> 16
|
||||
revcode_processor = (revision & 0xf000) >> 12
|
||||
revcode_type = (revision & 0xff0) >> 4
|
||||
revcode_revision = (revision & 0x0f)
|
||||
model = {
|
||||
0x0: 'A',
|
||||
0x1: 'B',
|
||||
0x2: 'A+',
|
||||
0x3: 'B+',
|
||||
0x4: '2B',
|
||||
0x6: 'CM',
|
||||
0x8: '3B',
|
||||
0x9: 'Zero',
|
||||
0xa: 'CM3',
|
||||
0xc: 'Zero W',
|
||||
0xd: '3B+',
|
||||
0xe: '3A+',
|
||||
0x10: 'CM3+',
|
||||
0x11: '4B',
|
||||
0x12: 'Zero2W',
|
||||
0x13: '400',
|
||||
0x14: 'CM4',
|
||||
0x17: '5B',
|
||||
}.get(revcode_type, '???')
|
||||
if model in ('A', 'B'):
|
||||
pcb_revision = {
|
||||
0: '1.0', # is this right?
|
||||
1: '1.0',
|
||||
2: '2.0',
|
||||
}.get(revcode_revision, 'Unknown')
|
||||
else:
|
||||
pcb_revision = f'1.{revcode_revision}'
|
||||
soc = {
|
||||
0: 'BCM2835',
|
||||
1: 'BCM2836',
|
||||
2: 'BCM2837',
|
||||
3: 'BCM2711',
|
||||
4: 'BCM2712',
|
||||
}.get(revcode_processor, 'Unknown')
|
||||
manufacturer = {
|
||||
0: 'Sony',
|
||||
1: 'Egoman',
|
||||
2: 'Embest',
|
||||
3: 'Sony Japan',
|
||||
4: 'Embest',
|
||||
5: 'Stadium',
|
||||
}.get(revcode_manufacturer, 'Unknown')
|
||||
memory = {
|
||||
0: 256,
|
||||
1: 512,
|
||||
2: 1024,
|
||||
3: 2048,
|
||||
4: 4096,
|
||||
5: 8192,
|
||||
6: 16384,
|
||||
}.get(revcode_memory, None)
|
||||
released = {
|
||||
'A': '2013Q1',
|
||||
'B': '2012Q1' if pcb_revision == '1.0' else '2012Q4',
|
||||
'A+': '2014Q4' if memory == 512 else '2016Q3',
|
||||
'B+': '2014Q3',
|
||||
'2B': '2015Q1' if pcb_revision in ('1.0', '1.1') else '2016Q3',
|
||||
'CM': '2014Q2',
|
||||
'3B': '2016Q1' if manufacturer in ('Sony', 'Embest') else '2016Q4',
|
||||
'Zero': '2015Q4' if pcb_revision == '1.2' else '2016Q2',
|
||||
'CM3': '2017Q1',
|
||||
'Zero W': '2017Q1',
|
||||
'3B+': '2018Q1',
|
||||
'3A+': '2018Q4',
|
||||
'CM3+': '2019Q1',
|
||||
'4B': '2020Q2' if memory == 8192 else '2019Q2',
|
||||
'CM4': '2020Q4',
|
||||
'400': '2020Q4',
|
||||
'Zero2W': '2021Q4',
|
||||
'5B': '2023Q4',
|
||||
}.get(model, 'Unknown')
|
||||
storage = {
|
||||
'A': 'SD',
|
||||
'B': 'SD',
|
||||
'CM': 'eMMC',
|
||||
'CM3': 'eMMC / off-board',
|
||||
'CM3+': 'eMMC / off-board',
|
||||
'CM4': 'eMMC / off-board',
|
||||
}.get(model, 'MicroSD')
|
||||
usb = {
|
||||
'A': 1,
|
||||
'A+': 1,
|
||||
'Zero': 1,
|
||||
'Zero W': 1,
|
||||
'Zero2W': 1,
|
||||
'B': 2,
|
||||
'CM': 1,
|
||||
'CM3': 1,
|
||||
'3A+': 1,
|
||||
'CM3+': 1,
|
||||
'CM4': 2,
|
||||
'400': 3,
|
||||
}.get(model, 4)
|
||||
usb3 = {
|
||||
'4B': 2,
|
||||
'400': 2,
|
||||
'5B': 2,
|
||||
}.get(model, 0)
|
||||
ethernet = {
|
||||
'A': 0,
|
||||
'A+': 0,
|
||||
'Zero': 0,
|
||||
'Zero W': 0,
|
||||
'Zero2W': 0,
|
||||
'CM': 0,
|
||||
'CM3': 0,
|
||||
'3A+': 0,
|
||||
'CM3+': 0,
|
||||
}.get(model, 1)
|
||||
eth_speed = {
|
||||
'B': 100,
|
||||
'B+': 100,
|
||||
'2B': 100,
|
||||
'3B': 100,
|
||||
'3B+': 300,
|
||||
'4B': 1000,
|
||||
'400': 1000,
|
||||
'CM4': 1000,
|
||||
'5B': 1000,
|
||||
}.get(model, 0)
|
||||
bluetooth = wifi = {
|
||||
'3B': True,
|
||||
'Zero W': True,
|
||||
'Zero2W': True,
|
||||
'3B+': True,
|
||||
'3A+': True,
|
||||
'4B': True,
|
||||
'400': True,
|
||||
'CM4': True,
|
||||
'5B': True,
|
||||
}.get(model, False)
|
||||
csi = {
|
||||
'Zero': 0 if pcb_revision == '1.2' else 1,
|
||||
'CM': 2,
|
||||
'CM3': 2,
|
||||
'CM3+': 2,
|
||||
'400': 0,
|
||||
'CM4': 2,
|
||||
'5B': 2,
|
||||
}.get(model, 1)
|
||||
dsi = {
|
||||
'Zero': 0,
|
||||
'Zero W': 0,
|
||||
'Zero2W': 0,
|
||||
'5B': 2,
|
||||
}.get(model, csi)
|
||||
headers = {
|
||||
'A': {'P1': data.REV2_P1, 'P5': data.REV2_P5, 'P6': data.REV2_P6, 'P2': data.PI1_P2, 'P3': data.PI1_P3},
|
||||
'B': {'P1': data.REV1_P1, 'P2': data.PI1_P2, 'P3': data.PI1_P3} if pcb_revision == '1.0' else
|
||||
{'P1': data.REV2_P1, 'P5': data.REV2_P5, 'P6': data.REV2_P6, 'P2': data.PI1_P2, 'P3': data.PI1_P3},
|
||||
'B+': {'J8': data.PLUS_J8, 'RUN': data.ZERO_RUN},
|
||||
'CM': {'SODIMM': data.CM_SODIMM},
|
||||
'CM3': {'SODIMM': data.CM3_SODIMM},
|
||||
'CM3+': {'SODIMM': data.CM3_SODIMM},
|
||||
'Zero': {'J8': data.PLUS_J8, 'RUN': data.ZERO_RUN, 'TV': data.ZERO_TV},
|
||||
'Zero W': {'J8': data.PLUS_J8, 'RUN': data.ZERO_RUN, 'TV': data.ZERO_TV},
|
||||
'2B': {'J8': data.PLUS_J8, 'RUN': data.ZERO_RUN},
|
||||
'3B': {'J8': data.PLUS_J8, 'RUN': data.ZERO_RUN},
|
||||
'3A+': {'J8': data.PLUS_J8, 'RUN': data.PLUS_RUN},
|
||||
'3B+': {'J8': data.PLUS_J8, 'RUN': data.PLUS_RUN, 'POE': data.PLUS_POE},
|
||||
'4B': {'J8': data.PI4_J8, 'J2': data.PI4_J2, 'J14': data.PI4_J14},
|
||||
'400': {'J8': data.PI4_J8},
|
||||
'CM4': {'J8': data.PI4_J8, 'J1': data.CM4_J1, 'J2': data.CM4_J2, 'J3': data.CM4_J3, 'J6': data.CM4_J6, 'J9': data.CM4_J9},
|
||||
'5B': {'J8': data.PI4_J8, 'J2': data.PI5_J2, 'J7': data.PI5_J7, 'J14': data.PI4_J14},
|
||||
}.get(model, {'J8': data.PLUS_J8})
|
||||
board = {
|
||||
'A': data.A_BOARD,
|
||||
'B': data.REV1_BOARD if pcb_revision == '1.0' else data.REV2_BOARD,
|
||||
'A+': data.APLUS_BOARD,
|
||||
'CM': data.CM_BOARD,
|
||||
'CM3': data.CM_BOARD,
|
||||
'CM3+': data.CM3PLUS_BOARD,
|
||||
'Zero': data.ZERO12_BOARD if pcb_revision == '1.2' else data.ZERO13_BOARD,
|
||||
'Zero W': data.ZERO13_BOARD,
|
||||
'Zero2W': data.ZERO2_BOARD,
|
||||
'3A+': data.A3PLUS_BOARD,
|
||||
'3B+': data.B3PLUS_BOARD,
|
||||
'4B': data.B4_BOARD,
|
||||
'CM4': data.CM4_BOARD,
|
||||
'400': data.P400_BOARD,
|
||||
'5B': data.B5_BOARD,
|
||||
}.get(model, data.BPLUS_BOARD)
|
||||
else:
|
||||
# Old-style revision, use the lookup table
|
||||
try:
|
||||
(
|
||||
model,
|
||||
pcb_revision,
|
||||
released,
|
||||
soc,
|
||||
manufacturer,
|
||||
memory,
|
||||
storage,
|
||||
usb,
|
||||
ethernet,
|
||||
wifi,
|
||||
bluetooth,
|
||||
csi,
|
||||
dsi,
|
||||
headers,
|
||||
board,
|
||||
) = data.PI_REVISIONS[revision]
|
||||
usb3 = 0
|
||||
eth_speed = ethernet * 100
|
||||
except KeyError:
|
||||
raise PinUnknownPi(f'unknown old-style revision "{revision:x}"')
|
||||
headers = frozendict({
|
||||
header: HeaderInfo(
|
||||
name=header, rows=rows, columns=columns,
|
||||
pins=frozendict({
|
||||
number: cls._make_pin(
|
||||
header, number, row + 1, col + 1, functions)
|
||||
for number, functions in header_data.items()
|
||||
for row, col in (divmod(number - 1, 2),)
|
||||
})
|
||||
)
|
||||
for header, (rows, columns, header_data) in headers.items()
|
||||
})
|
||||
return cls(
|
||||
f'{revision:04x}',
|
||||
model,
|
||||
pcb_revision,
|
||||
released,
|
||||
soc,
|
||||
manufacturer,
|
||||
memory,
|
||||
storage,
|
||||
usb,
|
||||
usb3,
|
||||
ethernet,
|
||||
eth_speed,
|
||||
wifi,
|
||||
bluetooth,
|
||||
csi,
|
||||
dsi,
|
||||
headers,
|
||||
board,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _make_pin(header, number, row, col, interfaces):
|
||||
pull = 'up' if number in (3, 5) and header in ('P1', 'J8') else ''
|
||||
phys_name = f'{header}:{number}'
|
||||
names = {phys_name}
|
||||
if header in ('P1', 'J8', 'SODIMM'):
|
||||
names.add(f'BOARD{number}')
|
||||
try:
|
||||
name = interfaces['gpio']
|
||||
gpio = int(name[4:])
|
||||
names.add(name)
|
||||
names.add(gpio)
|
||||
names.add(str(gpio))
|
||||
names.add(f'BCM{gpio}')
|
||||
try:
|
||||
wpi_map = {
|
||||
'J8:3': 8, 'J8:5': 9, 'J8:7': 7, 'J8:8': 15,
|
||||
'J8:10': 16, 'J8:11': 0, 'J8:12': 1, 'J8:13': 2,
|
||||
'J8:15': 3, 'J8:16': 4, 'J8:18': 5, 'J8:19': 12,
|
||||
'J8:21': 13, 'J8:22': 6, 'J8:23': 14, 'J8:24': 10,
|
||||
'J8:26': 11, 'J8:27': 30, 'J8:28': 31, 'J8:29': 21,
|
||||
'J8:31': 22, 'J8:32': 26, 'J8:33': 23, 'J8:35': 24,
|
||||
'J8:36': 27, 'J8:37': 25, 'J8:38': 28, 'J8:40': 29,
|
||||
'P1:3': 8, 'P1:5': 9, 'P1:7': 7, 'P1:8': 15,
|
||||
'P1:10': 16, 'P1:11': 0, 'P1:12': 1, 'P1:13': 2,
|
||||
'P1:15': 3, 'P1:16': 4, 'P1:18': 5, 'P1:19': 12,
|
||||
'P1:21': 13, 'P1:22': 6, 'P1:23': 14, 'P1:24': 10,
|
||||
'P1:26': 11, 'P1:27': 30, 'P1:28': 31, 'P1:29': 21,
|
||||
'P1:31': 22, 'P1:32': 26, 'P1:33': 23, 'P1:35': 24,
|
||||
'P1:36': 27, 'P1:37': 25, 'P1:38': 28, 'P1:40': 29,
|
||||
'P5:3': 17, 'P5:4': 18, 'P5:5': 19, 'P5:6': 20,
|
||||
}
|
||||
names.add(f'WPI{wpi_map[phys_name]}')
|
||||
except KeyError:
|
||||
pass
|
||||
except KeyError:
|
||||
name = interfaces['']
|
||||
names.add(name)
|
||||
return PinInfo(
|
||||
number=number, name=name, names=frozenset(names), pull=pull,
|
||||
row=row, col=col, interfaces=frozenset(interfaces))
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
return f"Raspberry Pi {self.model} rev {self.pcb_revision}"
|
||||
|
||||
|
||||
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().__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 pin(self, name):
|
||||
for header, info in self.board_info.find_pin(name):
|
||||
try:
|
||||
pin = self.pins[info]
|
||||
except KeyError:
|
||||
pin = self.pin_class(self, info)
|
||||
self.pins[info] = pin
|
||||
return pin
|
||||
raise PinInvalidPin(f'{name} is not a valid pin name')
|
||||
|
||||
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_board_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(
|
||||
f'unrecognized keyword argument {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 GPIOPinInUse:
|
||||
# If a pin is already reserved, don't fallback to software SPI
|
||||
# as it'll just be reserved there too
|
||||
raise
|
||||
except Exception as e:
|
||||
warnings.warn(
|
||||
SPISoftwareFallback(
|
||||
f'failed to initialize hardware SPI, falling back to '
|
||||
f'software (error was: {e!s})'))
|
||||
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.board_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(
|
||||
f"port {spi_args['port']} is not a valid SPI port")
|
||||
try:
|
||||
selected_hw['select'][spi_args['device']]
|
||||
except IndexError:
|
||||
raise SPIBadArgs(
|
||||
f"device must be in the range 0.."
|
||||
f"{len(selected_hw['select'])}")
|
||||
# XXX: This is incorrect; assumes port == dev_defaults['port']
|
||||
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, info):
|
||||
super().__init__()
|
||||
if 'gpio' not in info.interfaces:
|
||||
raise PinInvalidPin(f'{info} is not a GPIO pin')
|
||||
self._factory = factory
|
||||
self._info = info
|
||||
self._number = int(info.name[4:])
|
||||
self._when_changed_lock = RLock()
|
||||
self._when_changed = None
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
return self._info
|
||||
|
||||
@property
|
||||
def number(self):
|
||||
warnings.warn(
|
||||
DeprecationWarning(
|
||||
"PiPin.number is deprecated; please use Pin.info.name instead"))
|
||||
return self._number
|
||||
|
||||
def __repr__(self):
|
||||
return self._info.name
|
||||
|
||||
@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
|
||||
|
||||
|
||||
def pi_info(revision=None):
|
||||
"""
|
||||
Deprecated function for retrieving information about a *revision* of the
|
||||
Raspberry Pi. If you wish to retrieve information about the board that your
|
||||
script is running on, please query the :attr:`Factory.board_info` property
|
||||
like so::
|
||||
|
||||
>>> from gpiozero import Device
|
||||
>>> Device.ensure_pin_factory()
|
||||
>>> Device.pin_factory.board_info
|
||||
PiBoardInfo(revision='a02082', model='3B', pcb_revision='1.2',
|
||||
released='2016Q1', soc='BCM2837', manufacturer='Sony', memory=1024,
|
||||
storage='MicroSD', usb=4, usb3=0, ethernet=1, eth_speed=100, wifi=True,
|
||||
bluetooth=True, csi=1, dsi=1, headers=..., board=...)
|
||||
|
||||
To obtain information for a specific Raspberry Pi board revision, use the
|
||||
:meth:`PiBoardInfo.from_revision` constructor.
|
||||
|
||||
:param str revision:
|
||||
The revision of the Pi to return information about. If this is omitted
|
||||
or :data:`None` (the default), then the library will attempt to
|
||||
determine the model of Pi it is running on and return information about
|
||||
that.
|
||||
"""
|
||||
if revision is None:
|
||||
if Device.pin_factory is None:
|
||||
Device.pin_factory = Device._default_pin_factory()
|
||||
return Device.pin_factory.board_info
|
||||
else:
|
||||
if isinstance(revision, bytes):
|
||||
revision = revision.decode('ascii')
|
||||
if isinstance(revision, str):
|
||||
revision = int(revision, base=16)
|
||||
else:
|
||||
# be nice to people passing an int (or something numeric anyway)
|
||||
revision = int(revision)
|
||||
return PiBoardInfo.from_revision(revision)
|
||||
600
venv/lib/python3.11/site-packages/gpiozero/pins/pigpio.py
Normal file
600
venv/lib/python3.11/site-packages/gpiozero/pins/pigpio.py
Normal file
@@ -0,0 +1,600 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2023 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2021 Kyle Morgan <kyle@knmorgan.net>
|
||||
# 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
|
||||
|
||||
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().__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(f'failed to connect to {host}:{port}')
|
||||
self._host = host
|
||||
self._port = port
|
||||
self._spis = []
|
||||
|
||||
def close(self):
|
||||
super().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().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, info):
|
||||
super().__init__(factory, info)
|
||||
self._pull = info.pull or '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 = self.info.pull or '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(
|
||||
f'invalid function "{value}" for pin {self!r}')
|
||||
|
||||
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(
|
||||
f'invalid state "{value}" for pin {self!r}')
|
||||
elif self.function == 'input':
|
||||
raise PinSetInput(f'cannot set state of pin {self!r}')
|
||||
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(f'cannot set pull on non-input pin {self!r}')
|
||||
if self.info.pull and value != self.info.pull:
|
||||
raise PinFixedPull(f'{self!r} has a fixed pull resistor')
|
||||
try:
|
||||
self.factory.connection.set_pull_up_down(
|
||||
self._number, self.GPIO_PULL_UPS[value])
|
||||
self._pull = value
|
||||
except KeyError:
|
||||
raise PinInvalidPull(f'invalid pull "{value}" for pin {self!r}')
|
||||
|
||||
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(f'cannot start PWM on pin {self!r}')
|
||||
# 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()._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().__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().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 f'SPI(port={self._port:d}, device={self._device:d})'
|
||||
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(f"{value} is not a valid SPI clock mode")
|
||||
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()._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(f'SPI transfer error {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().__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().close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self._closed
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
self._check_open()
|
||||
return (
|
||||
f'SPI(clock_pin={self._clock_pin}, mosi_pin={self._mosi_pin}, '
|
||||
f'miso_pin={self._miso_pin}, select_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(f"{value} is not a valid SPI clock mode")
|
||||
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(f'SPI transfer error {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)
|
||||
226
venv/lib/python3.11/site-packages/gpiozero/pins/rpigpio.py
Normal file
226
venv/lib/python3.11/site-packages/gpiozero/pins/rpigpio.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-2023 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
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().__init__()
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setwarnings(False)
|
||||
self.pin_class = RPiGPIOPin
|
||||
|
||||
def close(self):
|
||||
super().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, info):
|
||||
super().__init__(factory, info)
|
||||
self._pull = info.pull or '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 self.info.pull and pull != self.info.pull:
|
||||
raise PinFixedPull(f'{self!r} has a fixed pull resistor')
|
||||
try:
|
||||
GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[pull])
|
||||
self._pull = pull
|
||||
except KeyError:
|
||||
raise PinInvalidPull(f'invalid pull "{pull}" for pin {self!r}')
|
||||
|
||||
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(
|
||||
f'invalid function "{value}" for pin {self!r}')
|
||||
|
||||
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(
|
||||
f'invalid state "{value}" for pin {self!r}')
|
||||
self._duty_cycle = value
|
||||
else:
|
||||
try:
|
||||
GPIO.output(self._number, value)
|
||||
except ValueError:
|
||||
raise PinInvalidState(
|
||||
f'invalid state "{value}" for pin {self!r}')
|
||||
except RuntimeError:
|
||||
raise PinSetInput(f'cannot set state of pin {self!r}')
|
||||
|
||||
def _get_pull(self):
|
||||
return self._pull
|
||||
|
||||
def _set_pull(self, value):
|
||||
if self.function != 'input':
|
||||
raise PinFixedPull(f'cannot set pull on non-input pin {self!r}')
|
||||
if self.info.pull and value != self.info.pull:
|
||||
raise PinFixedPull(f'{self!r} has a fixed pull resistor')
|
||||
try:
|
||||
GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[value])
|
||||
self._pull = value
|
||||
except KeyError:
|
||||
raise PinInvalidPull(f'invalid pull "{value}" for pin {self!r}')
|
||||
|
||||
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(f'cannot start PWM on pin {self!r}')
|
||||
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()._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)
|
||||
219
venv/lib/python3.11/site-packages/gpiozero/pins/spi.py
Normal file
219
venv/lib/python3.11/site-packages/gpiozero/pins/spi.py
Normal file
@@ -0,0 +1,219 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2023 Dave Jones <dave@waveform.org.uk>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import operator
|
||||
from threading import RLock
|
||||
|
||||
from . import SPI
|
||||
from ..devices import Device, SharedMixin
|
||||
from ..input_devices import InputDevice
|
||||
from ..output_devices import OutputDevice
|
||||
from ..exc import DeviceClosed, SPIInvalidClockMode
|
||||
|
||||
|
||||
class SPISoftware(SPI):
|
||||
"""
|
||||
A software bit-banged implementation of the :class:`gpiozero.pins.SPI`
|
||||
interface.
|
||||
|
||||
This is a reasonable basis for a *local* SPI software implementation, but
|
||||
be aware that it's unlikely to be usable for remote operation (a dedicated
|
||||
daemon that locally handles SPI transactions should be used for such
|
||||
operations). Instances will happily share their clock, mosi, and miso pins
|
||||
with other instances provided each has a distinct select pin.
|
||||
|
||||
See :class:`~gpiozero.pins.spi.SPISoftwareBus` for the actual SPI
|
||||
transfer logic.
|
||||
"""
|
||||
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin, *,
|
||||
pin_factory):
|
||||
self._bus = None
|
||||
self._select = None
|
||||
super().__init__(pin_factory=pin_factory)
|
||||
try:
|
||||
# XXX We *should* be storing clock_mode locally, not clock_phase;
|
||||
# after all different users of the bus can disagree about the
|
||||
# clock's polarity and even select pin polarity
|
||||
self._clock_phase = False
|
||||
self._lsb_first = False
|
||||
self._bits_per_word = 8
|
||||
self._bus = SPISoftwareBus(
|
||||
clock_pin, mosi_pin, miso_pin, pin_factory=pin_factory)
|
||||
self._select = OutputDevice(
|
||||
select_pin, active_high=False, pin_factory=pin_factory)
|
||||
except:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
def _conflicts_with(self, other):
|
||||
if isinstance(other, SoftwareSPI):
|
||||
return self._select.pin.info.name == other._select.pin.info.name
|
||||
else:
|
||||
return True
|
||||
|
||||
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().close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self._bus is None
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
self._check_open()
|
||||
return (
|
||||
f'SPI(clock_pin={self._bus.clock.pin.info.name!r}, '
|
||||
f'mosi_pin={self._bus.mosi.pin.info.name!r}, '
|
||||
f'miso_pin={self._bus.miso.pin.info.name!r}, '
|
||||
f'select_pin={self._select.pin.info.name!r})')
|
||||
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(f"{value} is not a valid clock mode")
|
||||
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 SPISoftwareBus(SharedMixin, Device):
|
||||
"""
|
||||
A software bit-banged SPI bus implementation, used by
|
||||
:class:`~gpiozero.pins.spi.SPISoftware` to implement shared SPI interfaces.
|
||||
|
||||
.. warning::
|
||||
|
||||
This implementation has no rate control; it simply clocks out data as
|
||||
fast as it can as Python isn't terribly quick on a Pi anyway, and the
|
||||
extra logic required for rate control is liable to reduce the maximum
|
||||
achievable data rate quite substantially.
|
||||
"""
|
||||
def __init__(self, clock_pin, mosi_pin, miso_pin, *, pin_factory):
|
||||
self.lock = None
|
||||
self.clock = None
|
||||
self.mosi = None
|
||||
self.miso = None
|
||||
super().__init__()
|
||||
# XXX Should probably just use CompositeDevice for this; would make
|
||||
# close() a bit cleaner - any implications with the RLock?
|
||||
self.lock = RLock()
|
||||
try:
|
||||
self.clock = OutputDevice(
|
||||
clock_pin, active_high=True, pin_factory=pin_factory)
|
||||
if mosi_pin is not None:
|
||||
self.mosi = OutputDevice(mosi_pin, pin_factory=pin_factory)
|
||||
if miso_pin is not None:
|
||||
self.miso = InputDevice(miso_pin, pin_factory=pin_factory)
|
||||
except:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
def close(self):
|
||||
super().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, *, pin_factory=None):
|
||||
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
|
||||
92
venv/lib/python3.11/site-packages/gpiozero/pins/style.py
Normal file
92
venv/lib/python3.11/site-packages/gpiozero/pins/style.py
Normal file
@@ -0,0 +1,92 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2021-2023 Dave Jones <dave@waveform.org.uk>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
# ANSI color codes, for the pretty printers (nothing comprehensive, just enough
|
||||
# for our purposes)
|
||||
|
||||
class Style:
|
||||
def __init__(self, color=None):
|
||||
self.color = self._term_supports_color() if color is None else bool(color)
|
||||
self.effects = {
|
||||
'reset': 0,
|
||||
'bold': 1,
|
||||
'normal': 22,
|
||||
}
|
||||
self.colors = {
|
||||
'black': 0,
|
||||
'red': 1,
|
||||
'green': 2,
|
||||
'yellow': 3,
|
||||
'blue': 4,
|
||||
'magenta': 5,
|
||||
'cyan': 6,
|
||||
'white': 7,
|
||||
'default': 9,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _term_supports_color():
|
||||
try:
|
||||
stdout_fd = sys.stdout.fileno()
|
||||
except IOError:
|
||||
return False
|
||||
else:
|
||||
is_a_tty = os.isatty(stdout_fd)
|
||||
is_windows = sys.platform.startswith('win')
|
||||
return is_a_tty and not is_windows
|
||||
|
||||
@classmethod
|
||||
def from_style_content(cls, format_spec):
|
||||
specs = set(format_spec.split())
|
||||
style = specs & {'mono', 'color'}
|
||||
content = specs - style
|
||||
if len(style) > 1:
|
||||
raise ValueError('cannot specify both mono and color styles')
|
||||
try:
|
||||
style = style.pop()
|
||||
except KeyError:
|
||||
style = 'color' if cls._term_supports_color() else 'mono'
|
||||
if not content:
|
||||
content = 'full'
|
||||
else:
|
||||
content = ' '.join(content)
|
||||
return cls(style == 'color'), content
|
||||
|
||||
def __call__(self, format_spec):
|
||||
specs = format_spec.split()
|
||||
codes = []
|
||||
fore = True
|
||||
for spec in specs:
|
||||
if spec == 'on':
|
||||
fore = False
|
||||
else:
|
||||
try:
|
||||
codes.append(self.effects[spec])
|
||||
except KeyError:
|
||||
try:
|
||||
if fore:
|
||||
codes.append(30 + self.colors[spec])
|
||||
else:
|
||||
codes.append(40 + self.colors[spec])
|
||||
except KeyError:
|
||||
raise ValueError(f'invalid format specification "{spec}"')
|
||||
if self.color:
|
||||
codes = ';'.join(str(code) for code in codes)
|
||||
return f'\x1b[{codes}m'
|
||||
else:
|
||||
return ''
|
||||
|
||||
def __format__(self, format_spec):
|
||||
if format_spec == '':
|
||||
return 'color' if self.color else 'mono'
|
||||
else:
|
||||
return self(format_spec)
|
||||
544
venv/lib/python3.11/site-packages/gpiozero/spi_devices.py
Normal file
544
venv/lib/python3.11/site-packages/gpiozero/spi_devices.py
Normal file
@@ -0,0 +1,544 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2023 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2020 Grzegorz Szymaszek <gszymaszek@short.pl>
|
||||
# Copyright (c) 2020 Fangchen Li <fangchen.li@outlook.com>
|
||||
# Copyright (c) 2016-2019 Andrew Scheller <github@loowis.durge.org>
|
||||
# Copyright (c) 2016-2018 Ben Nuttall <ben@bennuttall.com>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from math import log, ceil
|
||||
from operator import or_
|
||||
from functools import reduce
|
||||
|
||||
from .exc import DeviceClosed, SPIBadChannel, InputDeviceError
|
||||
from .devices import Device
|
||||
|
||||
|
||||
class SPIDevice(Device):
|
||||
"""
|
||||
Extends :class:`Device`. Represents a device that communicates via the SPI
|
||||
protocol.
|
||||
|
||||
See :ref:`spi_args` for information on the keyword arguments that can be
|
||||
specified with the constructor.
|
||||
"""
|
||||
def __init__(self, **spi_args):
|
||||
self._spi = None
|
||||
super().__init__(pin_factory=spi_args.pop('pin_factory', None))
|
||||
self._spi = self.pin_factory.spi(**spi_args)
|
||||
|
||||
def close(self):
|
||||
if getattr(self, '_spi', None):
|
||||
self._spi.close()
|
||||
self._spi = None
|
||||
super().close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self._spi is None
|
||||
|
||||
def _int_to_words(self, pattern):
|
||||
"""
|
||||
Given a bit-pattern expressed an integer number, return a sequence of
|
||||
the individual words that make up the pattern. The number of bits per
|
||||
word will be obtained from the internal SPI interface.
|
||||
"""
|
||||
try:
|
||||
bits_required = int(ceil(log(pattern, 2))) + 1
|
||||
except ValueError:
|
||||
# pattern == 0 (technically speaking, no bits are required to
|
||||
# transmit the value zero ;)
|
||||
bits_required = 1
|
||||
shifts = range(0, bits_required, self._spi.bits_per_word)[::-1]
|
||||
mask = 2 ** self._spi.bits_per_word - 1
|
||||
return [(pattern >> shift) & mask for shift in shifts]
|
||||
|
||||
def _words_to_int(self, words, expected_bits=None):
|
||||
"""
|
||||
Given a sequence of words which each fit in the internal SPI
|
||||
interface's number of bits per word, returns the value obtained by
|
||||
concatenating each word into a single bit-string.
|
||||
|
||||
If *expected_bits* is specified, it limits the size of the output to
|
||||
the specified number of bits (by masking off bits above the expected
|
||||
number). If unspecified, no limit will be applied.
|
||||
"""
|
||||
if expected_bits is None:
|
||||
expected_bits = len(words) * self._spi.bits_per_word
|
||||
shifts = range(0, expected_bits, self._spi.bits_per_word)[::-1]
|
||||
mask = 2 ** expected_bits - 1
|
||||
return reduce(or_, (word << shift for word, shift in zip(words, shifts))) & mask
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
self._check_open()
|
||||
return (
|
||||
f"<gpiozero.{self.__class__.__name__} object using "
|
||||
f"{self._spi!r}>")
|
||||
except DeviceClosed:
|
||||
return f"<gpiozero.{self.__class__.__name__} object closed>"
|
||||
|
||||
|
||||
class AnalogInputDevice(SPIDevice):
|
||||
"""
|
||||
Represents an analog input device connected to SPI (serial interface).
|
||||
|
||||
Typical analog input devices are `analog to digital converters`_ (ADCs).
|
||||
Several classes are provided for specific ADC chips, including
|
||||
:class:`MCP3004`, :class:`MCP3008`, :class:`MCP3204`, and :class:`MCP3208`.
|
||||
|
||||
The following code demonstrates reading the first channel of an MCP3008
|
||||
chip attached to the Pi's SPI pins::
|
||||
|
||||
from gpiozero import MCP3008
|
||||
|
||||
pot = MCP3008(0)
|
||||
print(pot.value)
|
||||
|
||||
The :attr:`value` attribute is normalized such that its value is always
|
||||
between 0.0 and 1.0 (or in special cases, such as differential sampling,
|
||||
-1 to +1). Hence, you can use an analog input to control the brightness of
|
||||
a :class:`PWMLED` like so::
|
||||
|
||||
from gpiozero import MCP3008, PWMLED
|
||||
|
||||
pot = MCP3008(0)
|
||||
led = PWMLED(17)
|
||||
led.source = pot
|
||||
|
||||
The :attr:`voltage` attribute reports values between 0.0 and *max_voltage*
|
||||
(which defaults to 3.3, the logic level of the GPIO pins).
|
||||
|
||||
.. _analog to digital converters: https://en.wikipedia.org/wiki/Analog-to-digital_converter
|
||||
"""
|
||||
|
||||
def __init__(self, bits, max_voltage=3.3, **spi_args):
|
||||
if bits is None:
|
||||
raise InputDeviceError(
|
||||
'you must specify the bit resolution of the device')
|
||||
self._bits = bits
|
||||
self._min_value = -(2 ** bits)
|
||||
self._range = 2 ** (bits + 1) - 1
|
||||
if max_voltage <= 0:
|
||||
raise InputDeviceError('max_voltage must be positive')
|
||||
self._max_voltage = float(max_voltage)
|
||||
super().__init__(shared=True, **spi_args)
|
||||
|
||||
@property
|
||||
def bits(self):
|
||||
"""
|
||||
The bit-resolution of the device/channel.
|
||||
"""
|
||||
return self._bits
|
||||
|
||||
def _read(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
The current value read from the device, scaled to a value between 0 and
|
||||
1 (or -1 to +1 for certain devices operating in differential mode).
|
||||
"""
|
||||
return (2 * (self._read() - self._min_value) / self._range) - 1
|
||||
|
||||
@property
|
||||
def raw_value(self):
|
||||
"""
|
||||
The raw value as read from the device.
|
||||
"""
|
||||
return self._read()
|
||||
|
||||
@property
|
||||
def max_voltage(self):
|
||||
"""
|
||||
The voltage required to set the device's value to 1.
|
||||
"""
|
||||
return self._max_voltage
|
||||
|
||||
@property
|
||||
def voltage(self):
|
||||
"""
|
||||
The current voltage read from the device. This will be a value between
|
||||
0 and the *max_voltage* parameter specified in the constructor.
|
||||
"""
|
||||
return self.value * self._max_voltage
|
||||
|
||||
|
||||
class MCP3xxx(AnalogInputDevice):
|
||||
"""
|
||||
Extends :class:`AnalogInputDevice` to implement an interface for all ADC
|
||||
chips with a protocol similar to the Microchip MCP3xxx series of devices.
|
||||
"""
|
||||
|
||||
def __init__(self, channel=0, bits=10, differential=False, max_voltage=3.3,
|
||||
**spi_args):
|
||||
self._channel = channel
|
||||
self._differential = bool(differential)
|
||||
super().__init__(bits, max_voltage, **spi_args)
|
||||
|
||||
@property
|
||||
def channel(self):
|
||||
"""
|
||||
The channel to read data from. The MCP3008/3208/3304 have 8 channels
|
||||
(0-7), while the MCP3004/3204/3302 have 4 channels (0-3), the
|
||||
MCP3002/3202 have 2 channels (0-1), and the MCP3001/3201/3301 only
|
||||
have 1 channel.
|
||||
"""
|
||||
return self._channel
|
||||
|
||||
@property
|
||||
def differential(self):
|
||||
"""
|
||||
If ``True``, the device is operated in differential mode. In this mode
|
||||
one channel (specified by the channel attribute) is read relative to
|
||||
the value of a second channel (implied by the chip's design).
|
||||
|
||||
Please refer to the device data-sheet to determine which channel is
|
||||
used as the relative base value (for example, when using an
|
||||
:class:`MCP3008` in differential mode, channel 0 is read relative to
|
||||
channel 1).
|
||||
"""
|
||||
return self._differential
|
||||
|
||||
def _read(self):
|
||||
return self._words_to_int(
|
||||
self._spi.transfer(self._send())[-2:], self.bits
|
||||
)
|
||||
|
||||
def _send(self):
|
||||
# MCP3004/08 protocol looks like the following:
|
||||
#
|
||||
# Byte 0 1 2
|
||||
# ==== ======== ======== ========
|
||||
# Tx 00000001 MCCCxxxx xxxxxxxx
|
||||
# Rx xxxxxxxx xxxxx0RR RRRRRRRR
|
||||
#
|
||||
# MCP3204/08 protocol looks like the following:
|
||||
#
|
||||
# Byte 0 1 2
|
||||
# ==== ======== ======== ========
|
||||
# Tx 000001MC CCxxxxxx xxxxxxxx
|
||||
# Rx xxxxxxxx xxx0RRRR RRRRRRRR
|
||||
#
|
||||
# The transmit bits start with several preamble "0" bits, the number
|
||||
# of which is determined by the amount required to align the last byte
|
||||
# of the result with the final byte of output. A start "1" bit is then
|
||||
# transmitted, followed by the single/differential bit (M); 1 for
|
||||
# single-ended read, 0 for differential read. Next comes three bits for
|
||||
# channel (C).
|
||||
#
|
||||
# Read-out begins with a don't care bit (x), then a null bit (0)
|
||||
# followed by the result bits (R). All other bits are don't care (x).
|
||||
#
|
||||
# The 3x01 variant of the chips always operates in differential mode
|
||||
# and effectively only has one channel (composed of an IN+ and IN-). As
|
||||
# such it requires no input, just output.
|
||||
return self._int_to_words(
|
||||
(0b10000 | (not self.differential) << 3 | self.channel) << (self.bits + 2)
|
||||
)
|
||||
|
||||
|
||||
class MCP3xx2(MCP3xxx):
|
||||
def _send(self):
|
||||
# MCP3002 protocol looks like the following:
|
||||
#
|
||||
# Byte 0 1
|
||||
# ==== ======== ========
|
||||
# Tx 01MCLxxx xxxxxxxx
|
||||
# Rx xxxxx0RR RRRRRRRR for the 3002
|
||||
#
|
||||
# MCP3202 protocol looks like the following:
|
||||
#
|
||||
# Byte 0 1 2
|
||||
# ==== ======== ======== ========
|
||||
# Tx 00000001 MCLxxxxx xxxxxxxx
|
||||
# Rx xxxxxxxx xxx0RRRR RRRRRRRR
|
||||
#
|
||||
# The transmit bits start with several preamble "0" bits, the number of
|
||||
# which is determined by the amount required to align the last byte of
|
||||
# the result with the final byte of output. A start "1" bit is then
|
||||
# transmitted, followed by the single/differential bit (M); 1 for
|
||||
# single-ended read, 0 for differential read. Next comes a single bit
|
||||
# for channel (C) then the MSBF bit (L) which selects whether the data
|
||||
# will be read out in MSB form only (1) or whether LSB read-out will
|
||||
# occur after MSB read-out (0).
|
||||
#
|
||||
# Read-out begins with a null bit (0) followed by the result bits (R).
|
||||
# All other bits are don't care (x).
|
||||
return self._int_to_words(
|
||||
(0b1001 | (not self.differential) << 2 | self.channel << 1) << (self.bits + 1)
|
||||
)
|
||||
|
||||
|
||||
class MCP30xx(MCP3xxx):
|
||||
"""
|
||||
Extends :class:`MCP3xxx` to implement an interface for all ADC
|
||||
chips with a protocol similar to the Microchip MCP30xx series of devices.
|
||||
"""
|
||||
|
||||
def __init__(self, channel=0, differential=False, max_voltage=3.3,
|
||||
**spi_args):
|
||||
super().__init__(channel, 10, differential, max_voltage, **spi_args)
|
||||
|
||||
|
||||
class MCP32xx(MCP3xxx):
|
||||
"""
|
||||
Extends :class:`MCP3xxx` to implement an interface for all ADC
|
||||
chips with a protocol similar to the Microchip MCP32xx series of devices.
|
||||
"""
|
||||
|
||||
def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args):
|
||||
super().__init__(channel, 12, differential, max_voltage, **spi_args)
|
||||
|
||||
|
||||
class MCP33xx(MCP3xxx):
|
||||
"""
|
||||
Extends :class:`MCP3xxx` with functionality specific to the MCP33xx family
|
||||
of ADCs; specifically this handles the full differential capability of
|
||||
these chips supporting the full 13-bit signed range of output values.
|
||||
"""
|
||||
|
||||
def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args):
|
||||
super().__init__(channel, 12, differential, max_voltage, **spi_args)
|
||||
|
||||
def _read(self):
|
||||
if self.differential:
|
||||
result = self._words_to_int(
|
||||
self._spi.transfer(self._send())[-2:], self.bits + 1)
|
||||
# Account for the sign bit
|
||||
if result > 4095:
|
||||
return -(8192 - result)
|
||||
else:
|
||||
return result
|
||||
else:
|
||||
return super()._read()
|
||||
|
||||
def _send(self):
|
||||
# MCP3302/04 protocol looks like the following:
|
||||
#
|
||||
# Byte 0 1 2
|
||||
# ==== ======== ======== ========
|
||||
# Tx 00001MCC Cxxxxxxx xxxxxxxx
|
||||
# Rx xxxxxxxx xx0SRRRR RRRRRRRR
|
||||
#
|
||||
# The transmit bits start with 4 preamble bits "0000", a start bit "1"
|
||||
# followed by the single/differential bit (M) which is 1 for
|
||||
# single-ended read, and 0 for differential read, followed by 3-bits
|
||||
# for the channel (C). The remainder of the transmission are "don't
|
||||
# care" bits (x).
|
||||
#
|
||||
# The first byte received and the top 2 bits of the second byte are
|
||||
# don't care bits (x). These are followed by a null bit (0), then the
|
||||
# sign bit (S), and then the 12 result bits (R).
|
||||
#
|
||||
# In single read mode (the default) the sign bit is always zero and the
|
||||
# result is effectively 12-bits. In differential mode, the sign bit is
|
||||
# significant and the result is a two's-complement 13-bit value.
|
||||
#
|
||||
# The MCP3301 variant operates similarly to the other MCP3x01 variants;
|
||||
# no input, just output and always differential.
|
||||
return self._int_to_words(
|
||||
(0b10000 | (not self.differential) << 3 | self.channel) << (self.bits + 3)
|
||||
)
|
||||
|
||||
@property
|
||||
def differential(self):
|
||||
"""
|
||||
If ``True``, the device is operated in differential mode. In this mode
|
||||
one channel (specified by the channel attribute) is read relative to
|
||||
the value of a second channel (implied by the chip's design).
|
||||
|
||||
Please refer to the device data-sheet to determine which channel is
|
||||
used as the relative base value (for example, when using an
|
||||
:class:`MCP3304` in differential mode, channel 0 is read relative to
|
||||
channel 1).
|
||||
"""
|
||||
return super().differential
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
The current value read from the device, scaled to a value between 0 and
|
||||
1 (or -1 to +1 for devices operating in differential mode).
|
||||
"""
|
||||
return super().value
|
||||
|
||||
|
||||
class MCP3001(MCP30xx):
|
||||
"""
|
||||
The `MCP3001`_ is a 10-bit analog to digital converter with 1 channel.
|
||||
Please note that the MCP3001 always operates in differential mode,
|
||||
measuring the value of IN+ relative to IN-.
|
||||
|
||||
.. _MCP3001: http://www.farnell.com/datasheets/630400.pdf
|
||||
"""
|
||||
def __init__(self, max_voltage=3.3, **spi_args):
|
||||
super().__init__(0, True, max_voltage, **spi_args)
|
||||
|
||||
def _read(self):
|
||||
# MCP3001 protocol looks like the following:
|
||||
#
|
||||
# Byte 0 1
|
||||
# ==== ======== ========
|
||||
# Rx xx0RRRRR RRRRRxxx
|
||||
return self._words_to_int(self._spi.read(2), 13) >> 3
|
||||
|
||||
|
||||
class MCP3002(MCP30xx, MCP3xx2):
|
||||
"""
|
||||
The `MCP3002`_ is a 10-bit analog to digital converter with 2 channels
|
||||
(0-1).
|
||||
|
||||
.. _MCP3002: http://www.farnell.com/datasheets/1599363.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args):
|
||||
if not 0 <= channel < 2:
|
||||
raise SPIBadChannel('channel must be 0 or 1')
|
||||
super().__init__(channel, differential, max_voltage, **spi_args)
|
||||
|
||||
|
||||
class MCP3004(MCP30xx):
|
||||
"""
|
||||
The `MCP3004`_ is a 10-bit analog to digital converter with 4 channels
|
||||
(0-3).
|
||||
|
||||
.. _MCP3004: http://www.farnell.com/datasheets/808965.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args):
|
||||
if not 0 <= channel < 4:
|
||||
raise SPIBadChannel('channel must be between 0 and 3')
|
||||
super().__init__(channel, differential, max_voltage, **spi_args)
|
||||
|
||||
|
||||
class MCP3008(MCP30xx):
|
||||
"""
|
||||
The `MCP3008`_ is a 10-bit analog to digital converter with 8 channels
|
||||
(0-7).
|
||||
|
||||
.. _MCP3008: http://www.farnell.com/datasheets/808965.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args):
|
||||
if not 0 <= channel < 8:
|
||||
raise SPIBadChannel('channel must be between 0 and 7')
|
||||
super().__init__(channel, differential, max_voltage, **spi_args)
|
||||
|
||||
|
||||
class MCP3201(MCP32xx):
|
||||
"""
|
||||
The `MCP3201`_ is a 12-bit analog to digital converter with 1 channel.
|
||||
Please note that the MCP3201 always operates in differential mode,
|
||||
measuring the value of IN+ relative to IN-.
|
||||
|
||||
.. _MCP3201: http://www.farnell.com/datasheets/1669366.pdf
|
||||
"""
|
||||
def __init__(self, max_voltage=3.3, **spi_args):
|
||||
super().__init__(0, True, max_voltage, **spi_args)
|
||||
|
||||
def _read(self):
|
||||
# MCP3201 protocol looks like the following:
|
||||
#
|
||||
# Byte 0 1
|
||||
# ==== ======== ========
|
||||
# Rx xx0RRRRR RRRRRRRx
|
||||
return self._words_to_int(self._spi.read(2), 13) >> 1
|
||||
|
||||
|
||||
class MCP3202(MCP32xx, MCP3xx2):
|
||||
"""
|
||||
The `MCP3202`_ is a 12-bit analog to digital converter with 2 channels
|
||||
(0-1).
|
||||
|
||||
.. _MCP3202: http://www.farnell.com/datasheets/1669376.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args):
|
||||
if not 0 <= channel < 2:
|
||||
raise SPIBadChannel('channel must be 0 or 1')
|
||||
super().__init__(channel, differential, max_voltage, **spi_args)
|
||||
|
||||
|
||||
class MCP3204(MCP32xx):
|
||||
"""
|
||||
The `MCP3204`_ is a 12-bit analog to digital converter with 4 channels
|
||||
(0-3).
|
||||
|
||||
.. _MCP3204: http://www.farnell.com/datasheets/808967.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args):
|
||||
if not 0 <= channel < 4:
|
||||
raise SPIBadChannel('channel must be between 0 and 3')
|
||||
super().__init__(channel, differential, max_voltage, **spi_args)
|
||||
|
||||
|
||||
class MCP3208(MCP32xx):
|
||||
"""
|
||||
The `MCP3208`_ is a 12-bit analog to digital converter with 8 channels
|
||||
(0-7).
|
||||
|
||||
.. _MCP3208: http://www.farnell.com/datasheets/808967.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args):
|
||||
if not 0 <= channel < 8:
|
||||
raise SPIBadChannel('channel must be between 0 and 7')
|
||||
super().__init__(channel, differential, max_voltage, **spi_args)
|
||||
|
||||
|
||||
class MCP3301(MCP33xx):
|
||||
"""
|
||||
The `MCP3301`_ is a signed 13-bit analog to digital converter. Please note
|
||||
that the MCP3301 always operates in differential mode measuring the
|
||||
difference between IN+ and IN-. Its output value is scaled from -1 to +1.
|
||||
|
||||
.. _MCP3301: http://www.farnell.com/datasheets/1669397.pdf
|
||||
"""
|
||||
def __init__(self, max_voltage=3.3, **spi_args):
|
||||
super().__init__(0, True, max_voltage, **spi_args)
|
||||
|
||||
def _read(self):
|
||||
# MCP3301 protocol looks like the following:
|
||||
#
|
||||
# Byte 0 1
|
||||
# ==== ======== ========
|
||||
# Rx xx0SRRRR RRRRRRRR
|
||||
result = self._words_to_int(self._spi.read(2), 13)
|
||||
# Account for the sign bit
|
||||
if result > 4095:
|
||||
return -(8192 - result)
|
||||
else:
|
||||
return result
|
||||
|
||||
|
||||
class MCP3302(MCP33xx):
|
||||
"""
|
||||
The `MCP3302`_ is a 12/13-bit analog to digital converter with 4 channels
|
||||
(0-3). When operated in differential mode, the device outputs a signed
|
||||
13-bit value which is scaled from -1 to +1. When operated in single-ended
|
||||
mode (the default), the device outputs an unsigned 12-bit value scaled from
|
||||
0 to 1.
|
||||
|
||||
.. _MCP3302: http://www.farnell.com/datasheets/1486116.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args):
|
||||
if not 0 <= channel < 4:
|
||||
raise SPIBadChannel('channel must be between 0 and 4')
|
||||
super().__init__(channel, differential, max_voltage, **spi_args)
|
||||
|
||||
|
||||
class MCP3304(MCP33xx):
|
||||
"""
|
||||
The `MCP3304`_ is a 12/13-bit analog to digital converter with 8 channels
|
||||
(0-7). When operated in differential mode, the device outputs a signed
|
||||
13-bit value which is scaled from -1 to +1. When operated in single-ended
|
||||
mode (the default), the device outputs an unsigned 12-bit value scaled from
|
||||
0 to 1.
|
||||
|
||||
.. _MCP3304: http://www.farnell.com/datasheets/1486116.pdf
|
||||
"""
|
||||
def __init__(self, channel=0, differential=False, max_voltage=3.3, **spi_args):
|
||||
if not 0 <= channel < 8:
|
||||
raise SPIBadChannel('channel must be between 0 and 7')
|
||||
super().__init__(channel, differential, max_voltage, **spi_args)
|
||||
56
venv/lib/python3.11/site-packages/gpiozero/threads.py
Normal file
56
venv/lib/python3.11/site-packages/gpiozero/threads.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2023 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2020 Fangchen Li <fangchen.li@outlook.com>
|
||||
# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from threading import Thread, Event
|
||||
|
||||
from .exc import ZombieThread
|
||||
|
||||
|
||||
_THREADS = set()
|
||||
|
||||
|
||||
def _threads_shutdown():
|
||||
while _THREADS:
|
||||
threads = _THREADS.copy()
|
||||
# Optimization: instead of calling stop() which implicitly calls
|
||||
# join(), set all the stopping events simultaneously, *then* join
|
||||
# threads with a reasonable timeout
|
||||
for t in threads:
|
||||
t.stopping.set()
|
||||
for t in threads:
|
||||
t.join(10)
|
||||
|
||||
|
||||
class GPIOThread(Thread):
|
||||
def __init__(self, target, args=(), kwargs=None, name=None):
|
||||
if kwargs is None:
|
||||
kwargs = {}
|
||||
self.stopping = Event()
|
||||
super().__init__(None, target, name, args, kwargs)
|
||||
self.daemon = True
|
||||
|
||||
def start(self):
|
||||
self.stopping.clear()
|
||||
_THREADS.add(self)
|
||||
super().start()
|
||||
|
||||
def stop(self, timeout=10):
|
||||
self.stopping.set()
|
||||
self.join(timeout)
|
||||
|
||||
def join(self, timeout=None):
|
||||
super().join(timeout)
|
||||
if self.is_alive():
|
||||
assert timeout is not None
|
||||
# timeout can't be None here because if it was, then join()
|
||||
# wouldn't return until the thread was dead
|
||||
raise ZombieThread(f"Thread failed to die within {timeout} seconds")
|
||||
else:
|
||||
_THREADS.discard(self)
|
||||
237
venv/lib/python3.11/site-packages/gpiozero/tones.py
Normal file
237
venv/lib/python3.11/site-packages/gpiozero/tones.py
Normal file
@@ -0,0 +1,237 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2019-2023 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2020 Fangchen Li <fangchen.li@outlook.com>
|
||||
# Copyright (c) 2019 Ben Nuttall <ben@bennuttall.com>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import re
|
||||
import warnings
|
||||
from math import log2
|
||||
|
||||
from .exc import AmbiguousTone
|
||||
|
||||
|
||||
class Tone(float):
|
||||
"""
|
||||
Represents a frequency of sound in a variety of musical notations.
|
||||
|
||||
:class:`Tone` class can be used with the :class:`~gpiozero.TonalBuzzer`
|
||||
class to easily represent musical tones. The class can be constructed in a
|
||||
variety of ways. For example as a straight frequency in `Hz`_ (which is the
|
||||
internal storage format), as an integer MIDI note, or as a string
|
||||
representation of a musical note.
|
||||
|
||||
All the following constructors are equivalent ways to construct the typical
|
||||
tuning note, `concert A`_ at 440Hz, which is MIDI note #69:
|
||||
|
||||
>>> from gpiozero.tones import Tone
|
||||
>>> Tone(440.0)
|
||||
>>> Tone(69)
|
||||
>>> Tone('A4')
|
||||
|
||||
If you do not want the constructor to guess which format you are using
|
||||
(there is some ambiguity between frequencies and MIDI notes at the bottom
|
||||
end of the frequencies, from 128Hz down), you can use one of the explicit
|
||||
constructors, :meth:`from_frequency`, :meth:`from_midi`, or
|
||||
:meth:`from_note`, or you can specify a keyword argument when
|
||||
constructing::
|
||||
|
||||
>>> Tone.from_frequency(440)
|
||||
>>> Tone.from_midi(69)
|
||||
>>> Tone.from_note('A4')
|
||||
>>> Tone(frequency=440)
|
||||
>>> Tone(midi=69)
|
||||
>>> Tone(note='A4')
|
||||
|
||||
Several attributes are provided to permit conversion to any of the
|
||||
supported construction formats: :attr:`frequency`, :attr:`midi`, and
|
||||
:attr:`note`. Methods are provided to step :meth:`up` or :meth:`down` to
|
||||
adjacent MIDI notes.
|
||||
|
||||
.. warning::
|
||||
|
||||
Currently :class:`Tone` derives from :class:`float` and can be used as
|
||||
a floating point number in most circumstances (addition, subtraction,
|
||||
etc). This part of the API is not yet considered "stable"; i.e. we may
|
||||
decide to enhance / change this behaviour in future versions.
|
||||
|
||||
.. _Hz: https://en.wikipedia.org/wiki/Hertz
|
||||
.. _concert A: https://en.wikipedia.org/wiki/Concert_pitch
|
||||
"""
|
||||
|
||||
tones = 'CCDDEFFGGAAB'
|
||||
semitones = {
|
||||
'♭': -1,
|
||||
'b': -1,
|
||||
'♮': 0,
|
||||
'': 0,
|
||||
'♯': 1,
|
||||
'#': 1,
|
||||
}
|
||||
regex = re.compile(
|
||||
rf'(?P<note>[A-G])'
|
||||
rf'(?P<semi>[{"".join(semitones.keys())}]?)'
|
||||
rf'(?P<octave>[0-9])')
|
||||
|
||||
def __new__(cls, value=None, *, frequency=None, midi=None, note=None):
|
||||
n = sum(1 for arg in (value, frequency, midi, note) if arg is not None)
|
||||
if n != 1:
|
||||
raise TypeError('must specify a value, frequency, midi number, '
|
||||
'or note')
|
||||
if note is not None:
|
||||
return cls.from_note(note)
|
||||
elif midi is not None:
|
||||
return cls.from_midi(midi)
|
||||
elif frequency is not None:
|
||||
return cls.from_frequency(frequency)
|
||||
else:
|
||||
if isinstance(value, (int, float)):
|
||||
if 0 <= value < 128:
|
||||
if value > 0:
|
||||
warnings.warn(
|
||||
AmbiguousTone(
|
||||
"Ambiguous tone specification; assuming you "
|
||||
"want a MIDI note. To suppress this warning "
|
||||
"use, e.g. Tone(midi=60), or to obtain a "
|
||||
"frequency instead use, e.g. Tone(frequency="
|
||||
"60)"))
|
||||
return cls.from_midi(value)
|
||||
else:
|
||||
return cls.from_frequency(value)
|
||||
elif isinstance(value, (bytes, str)):
|
||||
return cls.from_note(value)
|
||||
else:
|
||||
return cls.from_frequency(value)
|
||||
|
||||
def __str__(self):
|
||||
return self.note
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
midi = self.midi
|
||||
except ValueError:
|
||||
midi = ''
|
||||
else:
|
||||
midi = f' midi={midi!r}'
|
||||
try:
|
||||
note = self.note
|
||||
except ValueError:
|
||||
note = ''
|
||||
else:
|
||||
note = f' note={note!r}'
|
||||
return f"<Tone{note}{midi} frequency={self.frequency:.2f}Hz>"
|
||||
|
||||
@classmethod
|
||||
def from_midi(cls, midi_note):
|
||||
"""
|
||||
Construct a :class:`Tone` from a MIDI note, which must be an integer
|
||||
in the range 0 to 127. For reference, A4 (`concert A`_ typically used
|
||||
for tuning) is MIDI note #69.
|
||||
|
||||
.. _concert A: https://en.wikipedia.org/wiki/Concert_pitch
|
||||
"""
|
||||
midi = int(midi_note)
|
||||
if 0 <= midi_note < 128:
|
||||
A4_midi = 69
|
||||
A4_freq = 440
|
||||
return cls.from_frequency(A4_freq * 2 ** ((midi - A4_midi) / 12))
|
||||
raise ValueError(f'invalid MIDI note: {midi!r}')
|
||||
|
||||
@classmethod
|
||||
def from_note(cls, note):
|
||||
"""
|
||||
Construct a :class:`Tone` from a musical note which must consist of
|
||||
a capital letter A through G, followed by an optional semi-tone
|
||||
modifier ("b" for flat, "#" for sharp, or their Unicode equivalents),
|
||||
followed by an octave number (0 through 9).
|
||||
|
||||
For example `concert A`_, the typical tuning note at 440Hz, would be
|
||||
represented as "A4". One semi-tone above this would be "A#4" or
|
||||
alternatively "Bb4". Unicode representations of sharp and flat are also
|
||||
accepted.
|
||||
|
||||
.. _concert A: https://en.wikipedia.org/wiki/Concert_pitch
|
||||
"""
|
||||
if isinstance(note, bytes):
|
||||
note = note.decode('ascii')
|
||||
if isinstance(note, str):
|
||||
match = Tone.regex.match(note)
|
||||
if match:
|
||||
octave = int(match.group('octave')) + 1
|
||||
return cls.from_midi(
|
||||
Tone.tones.index(match.group('note')) +
|
||||
Tone.semitones[match.group('semi')] +
|
||||
octave * 12)
|
||||
raise ValueError(f'invalid note specification: {note!r}')
|
||||
|
||||
@classmethod
|
||||
def from_frequency(cls, freq):
|
||||
"""
|
||||
Construct a :class:`Tone` from a frequency specified in `Hz`_ which
|
||||
must be a positive floating-point value in the range 0 < freq <= 20000.
|
||||
|
||||
.. _Hz: https://en.wikipedia.org/wiki/Hertz
|
||||
"""
|
||||
if 0 < freq <= 20000:
|
||||
return super().__new__(cls, freq)
|
||||
raise ValueError(f'invalid frequency: {freq:.2f}')
|
||||
|
||||
@property
|
||||
def frequency(self):
|
||||
"""
|
||||
Return the frequency of the tone in `Hz`_.
|
||||
|
||||
.. _Hz: https://en.wikipedia.org/wiki/Hertz
|
||||
"""
|
||||
return float(self)
|
||||
|
||||
@property
|
||||
def midi(self):
|
||||
"""
|
||||
Return the (nearest) MIDI note to the tone's frequency. This will be an
|
||||
integer number in the range 0 to 127. If the frequency is outside the
|
||||
range represented by MIDI notes (which is approximately 8Hz to 12.5KHz)
|
||||
:exc:`ValueError` exception will be raised.
|
||||
"""
|
||||
result = int(round(12 * log2(self.frequency / 440) + 69))
|
||||
if 0 <= result < 128:
|
||||
return result
|
||||
raise ValueError(f'{self.frequency:f} is outside the MIDI note range')
|
||||
|
||||
@property
|
||||
def note(self):
|
||||
"""
|
||||
Return the (nearest) note to the tone's frequency. This will be a
|
||||
string in the form accepted by :meth:`from_note`. If the frequency is
|
||||
outside the range represented by this format ("A0" is approximately
|
||||
27.5Hz, and "G9" is approximately 12.5Khz) a :exc:`ValueError`
|
||||
exception will be raised.
|
||||
"""
|
||||
offset = self.midi - 60 # self.midi - A4_midi + Tone.tones.index('A')
|
||||
index = offset % 12 # offset % len(Tone.tones)
|
||||
octave = 4 + offset // 12
|
||||
if 0 <= octave <= 9:
|
||||
return (
|
||||
Tone.tones[index] +
|
||||
('#' if Tone.tones[index] == Tone.tones[index - 1] else '') +
|
||||
str(octave)
|
||||
)
|
||||
raise ValueError(f'{self.frequency:f} is outside the notation range')
|
||||
|
||||
def up(self, n=1):
|
||||
"""
|
||||
Return the :class:`Tone` *n* semi-tones above this frequency (*n*
|
||||
defaults to 1).
|
||||
"""
|
||||
return Tone.from_midi(self.midi + n)
|
||||
|
||||
def down(self, n=1):
|
||||
"""
|
||||
Return the :class:`Tone` *n* semi-tones below this frequency (*n*
|
||||
defaults to 1).
|
||||
"""
|
||||
return Tone.from_midi(self.midi - n)
|
||||
737
venv/lib/python3.11/site-packages/gpiozero/tools.py
Normal file
737
venv/lib/python3.11/site-packages/gpiozero/tools.py
Normal file
@@ -0,0 +1,737 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2023 Dave Jones <dave@waveform.org.uk>
|
||||
# Copyright (c) 2020 Fangchen Li <fangchen.li@outlook.com>
|
||||
# Copyright (c) 2016-2019 Ben Nuttall <ben@bennuttall.com>
|
||||
# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from random import random
|
||||
from time import sleep
|
||||
from itertools import cycle
|
||||
from math import sin, cos, pi, isclose
|
||||
from statistics import mean
|
||||
|
||||
from .mixins import ValuesMixin
|
||||
|
||||
|
||||
def _normalize(values):
|
||||
"""
|
||||
If *values* is a ``ValuesMixin`` derivative, return ``values.values``,
|
||||
otherwise return `values` as provided. Intended to allow support for::
|
||||
|
||||
led.source = foo(btn)
|
||||
|
||||
and::
|
||||
|
||||
led.source = foo(btn.values)
|
||||
|
||||
and::
|
||||
|
||||
led.source = foo(some_iterator)
|
||||
"""
|
||||
if isinstance(values, ValuesMixin):
|
||||
return values.values
|
||||
return values
|
||||
|
||||
|
||||
def negated(values):
|
||||
"""
|
||||
Returns the negation of the supplied values (:data:`True` becomes
|
||||
:data:`False`, and :data:`False` becomes :data:`True`). For example::
|
||||
|
||||
from gpiozero import Button, LED
|
||||
from gpiozero.tools import negated
|
||||
from signal import pause
|
||||
|
||||
led = LED(4)
|
||||
btn = Button(17)
|
||||
|
||||
led.source = negated(btn)
|
||||
|
||||
pause()
|
||||
"""
|
||||
values = _normalize(values)
|
||||
for v in values:
|
||||
yield not v
|
||||
|
||||
|
||||
def inverted(values, input_min=0, input_max=1):
|
||||
"""
|
||||
Returns the inversion of the supplied values (*input_min* becomes
|
||||
*input_max*, *input_max* becomes *input_min*, `input_min + 0.1` becomes
|
||||
`input_max - 0.1`, etc.). All items in *values* are assumed to be between
|
||||
*input_min* and *input_max* (which default to 0 and 1 respectively), and
|
||||
the output will be in the same range. For example::
|
||||
|
||||
from gpiozero import MCP3008, PWMLED
|
||||
from gpiozero.tools import inverted
|
||||
from signal import pause
|
||||
|
||||
led = PWMLED(4)
|
||||
pot = MCP3008(channel=0)
|
||||
|
||||
led.source = inverted(pot)
|
||||
|
||||
pause()
|
||||
"""
|
||||
values = _normalize(values)
|
||||
if input_min >= input_max:
|
||||
raise ValueError('input_min must be smaller than input_max')
|
||||
for v in values:
|
||||
yield input_min + input_max - v
|
||||
|
||||
|
||||
def scaled(values, output_min, output_max, input_min=0, input_max=1):
|
||||
"""
|
||||
Returns *values* scaled from *output_min* to *output_max*, assuming that
|
||||
all items in *values* lie between *input_min* and *input_max* (which
|
||||
default to 0 and 1 respectively). For example, to control the direction of
|
||||
a motor (which is represented as a value between -1 and 1) using a
|
||||
potentiometer (which typically provides values between 0 and 1)::
|
||||
|
||||
from gpiozero import Motor, MCP3008
|
||||
from gpiozero.tools import scaled
|
||||
from signal import pause
|
||||
|
||||
motor = Motor(20, 21)
|
||||
pot = MCP3008(channel=0)
|
||||
|
||||
motor.source = scaled(pot, -1, 1)
|
||||
|
||||
pause()
|
||||
|
||||
.. warning::
|
||||
|
||||
If *values* contains elements that lie outside *input_min* to
|
||||
*input_max* (inclusive) then the function will not produce values that
|
||||
lie within *output_min* to *output_max* (inclusive).
|
||||
"""
|
||||
values = _normalize(values)
|
||||
if input_min >= input_max:
|
||||
raise ValueError('input_min must be smaller than input_max')
|
||||
input_size = input_max - input_min
|
||||
output_size = output_max - output_min
|
||||
for v in values:
|
||||
yield (((v - input_min) / input_size) * output_size) + output_min
|
||||
|
||||
|
||||
def scaled_full(values):
|
||||
"""
|
||||
A convenience function that builds on :func:`scaled`. It converts a
|
||||
"half-range" value (0..1) to a "full-range" value (-1..1). This is
|
||||
equivalent to calling::
|
||||
|
||||
scaled(values, -1, 1, 0, 1)
|
||||
"""
|
||||
return scaled(values, -1, 1, 0, 1)
|
||||
|
||||
|
||||
def scaled_half(values):
|
||||
"""
|
||||
A convenience function that builds on :func:`scaled`. It converts a
|
||||
"full-range" value (-1..1) to a "half-range" value (0..1). This is
|
||||
equivalent to calling::
|
||||
|
||||
scaled(values, 0, 1, -1, 1)
|
||||
"""
|
||||
return scaled(values, 0, 1, -1, 1)
|
||||
|
||||
|
||||
def clamped(values, output_min=0, output_max=1):
|
||||
"""
|
||||
Returns *values* clamped from *output_min* to *output_max*, i.e. any items
|
||||
less than *output_min* will be returned as *output_min* and any items
|
||||
larger than *output_max* will be returned as *output_max* (these default to
|
||||
0 and 1 respectively). For example::
|
||||
|
||||
from gpiozero import PWMLED, MCP3008
|
||||
from gpiozero.tools import clamped
|
||||
from signal import pause
|
||||
|
||||
led = PWMLED(4)
|
||||
pot = MCP3008(channel=0)
|
||||
|
||||
led.source = clamped(pot, 0.5, 1.0)
|
||||
|
||||
pause()
|
||||
"""
|
||||
values = _normalize(values)
|
||||
if output_min >= output_max:
|
||||
raise ValueError('output_min must be smaller than output_max')
|
||||
for v in values:
|
||||
yield min(max(v, output_min), output_max)
|
||||
|
||||
|
||||
def absoluted(values):
|
||||
"""
|
||||
Returns *values* with all negative elements negated (so that they're
|
||||
positive). For example::
|
||||
|
||||
from gpiozero import PWMLED, Motor, MCP3008
|
||||
from gpiozero.tools import absoluted, scaled
|
||||
from signal import pause
|
||||
|
||||
led = PWMLED(4)
|
||||
motor = Motor(22, 27)
|
||||
pot = MCP3008(channel=0)
|
||||
|
||||
motor.source = scaled(pot, -1, 1)
|
||||
led.source = absoluted(motor)
|
||||
|
||||
pause()
|
||||
"""
|
||||
values = _normalize(values)
|
||||
for v in values:
|
||||
yield abs(v)
|
||||
|
||||
|
||||
def quantized(values, steps, input_min=0, input_max=1):
|
||||
"""
|
||||
Returns *values* quantized to *steps* increments. All items in *values* are
|
||||
assumed to be between *input_min* and *input_max* (which default to 0 and
|
||||
1 respectively), and the output will be in the same range.
|
||||
|
||||
For example, to quantize values between 0 and 1 to 5 "steps" (0.0, 0.25,
|
||||
0.5, 0.75, 1.0)::
|
||||
|
||||
from gpiozero import PWMLED, MCP3008
|
||||
from gpiozero.tools import quantized
|
||||
from signal import pause
|
||||
|
||||
led = PWMLED(4)
|
||||
pot = MCP3008(channel=0)
|
||||
|
||||
led.source = quantized(pot, 4)
|
||||
|
||||
pause()
|
||||
"""
|
||||
values = _normalize(values)
|
||||
if steps < 1:
|
||||
raise ValueError("steps must be 1 or larger")
|
||||
if input_min >= input_max:
|
||||
raise ValueError('input_min must be smaller than input_max')
|
||||
input_size = input_max - input_min
|
||||
for v in scaled(values, 0, 1, input_min, input_max):
|
||||
yield ((int(v * steps) / steps) * input_size) + input_min
|
||||
|
||||
|
||||
def booleanized(values, min_value, max_value, hysteresis=0):
|
||||
"""
|
||||
Returns True for each item in *values* between *min_value* and
|
||||
*max_value*, and False otherwise. *hysteresis* can optionally be used to
|
||||
add `hysteresis`_ which prevents the output value rapidly flipping when
|
||||
the input value is fluctuating near the *min_value* or *max_value*
|
||||
thresholds. For example, to light an LED only when a potentiometer is
|
||||
between ¼ and ¾ of its full range::
|
||||
|
||||
from gpiozero import LED, MCP3008
|
||||
from gpiozero.tools import booleanized
|
||||
from signal import pause
|
||||
|
||||
led = LED(4)
|
||||
pot = MCP3008(channel=0)
|
||||
|
||||
led.source = booleanized(pot, 0.25, 0.75)
|
||||
|
||||
pause()
|
||||
|
||||
.. _hysteresis: https://en.wikipedia.org/wiki/Hysteresis
|
||||
"""
|
||||
values = _normalize(values)
|
||||
if min_value >= max_value:
|
||||
raise ValueError('min_value must be smaller than max_value')
|
||||
min_value = float(min_value)
|
||||
max_value = float(max_value)
|
||||
if hysteresis < 0:
|
||||
raise ValueError("hysteresis must be 0 or larger")
|
||||
else:
|
||||
hysteresis = float(hysteresis)
|
||||
if (max_value - min_value) <= hysteresis:
|
||||
raise ValueError('The gap between min_value and max_value must be '
|
||||
'larger than hysteresis')
|
||||
last_state = None
|
||||
for v in values:
|
||||
if v < min_value:
|
||||
new_state = 'below'
|
||||
elif v > max_value:
|
||||
new_state = 'above'
|
||||
else:
|
||||
new_state = 'in'
|
||||
switch = False
|
||||
if last_state == None or not hysteresis:
|
||||
switch = True
|
||||
elif new_state == last_state:
|
||||
pass
|
||||
else: # new_state != last_state
|
||||
if last_state == 'below' and new_state == 'in':
|
||||
switch = v >= min_value + hysteresis
|
||||
elif last_state == 'in' and new_state == 'below':
|
||||
switch = v < min_value - hysteresis
|
||||
elif last_state == 'in' and new_state == 'above':
|
||||
switch = v > max_value + hysteresis
|
||||
elif last_state == 'above' and new_state == 'in':
|
||||
switch = v <= max_value - hysteresis
|
||||
else: # above->below or below->above
|
||||
switch = True
|
||||
if switch:
|
||||
last_state = new_state
|
||||
yield last_state == 'in'
|
||||
|
||||
|
||||
def all_values(*values):
|
||||
"""
|
||||
Returns the `logical conjunction`_ of all supplied values (the result is
|
||||
only :data:`True` if and only if all input values are simultaneously
|
||||
:data:`True`). One or more *values* can be specified. For example, to light
|
||||
an :class:`~gpiozero.LED` only when *both* buttons are pressed::
|
||||
|
||||
from gpiozero import LED, Button
|
||||
from gpiozero.tools import all_values
|
||||
from signal import pause
|
||||
|
||||
led = LED(4)
|
||||
btn1 = Button(20)
|
||||
btn2 = Button(21)
|
||||
|
||||
led.source = all_values(btn1, btn2)
|
||||
|
||||
pause()
|
||||
|
||||
.. _logical conjunction: https://en.wikipedia.org/wiki/Logical_conjunction
|
||||
"""
|
||||
values = [_normalize(v) for v in values]
|
||||
for v in zip(*values):
|
||||
yield all(v)
|
||||
|
||||
|
||||
def any_values(*values):
|
||||
"""
|
||||
Returns the `logical disjunction`_ of all supplied values (the result is
|
||||
:data:`True` if any of the input values are currently :data:`True`). One or
|
||||
more *values* can be specified. For example, to light an
|
||||
:class:`~gpiozero.LED` when *any* button is pressed::
|
||||
|
||||
from gpiozero import LED, Button
|
||||
from gpiozero.tools import any_values
|
||||
from signal import pause
|
||||
|
||||
led = LED(4)
|
||||
btn1 = Button(20)
|
||||
btn2 = Button(21)
|
||||
|
||||
led.source = any_values(btn1, btn2)
|
||||
|
||||
pause()
|
||||
|
||||
.. _logical disjunction: https://en.wikipedia.org/wiki/Logical_disjunction
|
||||
"""
|
||||
values = [_normalize(v) for v in values]
|
||||
for v in zip(*values):
|
||||
yield any(v)
|
||||
|
||||
|
||||
def averaged(*values):
|
||||
"""
|
||||
Returns the mean of all supplied values. One or more *values* can be
|
||||
specified. For example, to light a :class:`~gpiozero.PWMLED` as the average
|
||||
of several potentiometers connected to an :class:`~gpiozero.MCP3008` ADC::
|
||||
|
||||
from gpiozero import MCP3008, PWMLED
|
||||
from gpiozero.tools import averaged
|
||||
from signal import pause
|
||||
|
||||
pot1 = MCP3008(channel=0)
|
||||
pot2 = MCP3008(channel=1)
|
||||
pot3 = MCP3008(channel=2)
|
||||
led = PWMLED(4)
|
||||
|
||||
led.source = averaged(pot1, pot2, pot3)
|
||||
|
||||
pause()
|
||||
"""
|
||||
values = [_normalize(v) for v in values]
|
||||
for v in zip(*values):
|
||||
yield mean(v)
|
||||
|
||||
|
||||
def summed(*values):
|
||||
"""
|
||||
Returns the sum of all supplied values. One or more *values* can be
|
||||
specified. For example, to light a :class:`~gpiozero.PWMLED` as the
|
||||
(scaled) sum of several potentiometers connected to an
|
||||
:class:`~gpiozero.MCP3008` ADC::
|
||||
|
||||
from gpiozero import MCP3008, PWMLED
|
||||
from gpiozero.tools import summed, scaled
|
||||
from signal import pause
|
||||
|
||||
pot1 = MCP3008(channel=0)
|
||||
pot2 = MCP3008(channel=1)
|
||||
pot3 = MCP3008(channel=2)
|
||||
led = PWMLED(4)
|
||||
|
||||
led.source = scaled(summed(pot1, pot2, pot3), 0, 1, 0, 3)
|
||||
|
||||
pause()
|
||||
"""
|
||||
values = [_normalize(v) for v in values]
|
||||
for v in zip(*values):
|
||||
yield sum(v)
|
||||
|
||||
|
||||
def multiplied(*values):
|
||||
"""
|
||||
Returns the product of all supplied values. One or more *values* can be
|
||||
specified. For example, to light a :class:`~gpiozero.PWMLED` as the product
|
||||
(i.e. multiplication) of several potentiometers connected to an
|
||||
:class:`~gpiozero.MCP3008`
|
||||
ADC::
|
||||
|
||||
from gpiozero import MCP3008, PWMLED
|
||||
from gpiozero.tools import multiplied
|
||||
from signal import pause
|
||||
|
||||
pot1 = MCP3008(channel=0)
|
||||
pot2 = MCP3008(channel=1)
|
||||
pot3 = MCP3008(channel=2)
|
||||
led = PWMLED(4)
|
||||
|
||||
led.source = multiplied(pot1, pot2, pot3)
|
||||
|
||||
pause()
|
||||
"""
|
||||
values = [_normalize(v) for v in values]
|
||||
def _product(it):
|
||||
p = 1
|
||||
for n in it:
|
||||
p *= n
|
||||
return p
|
||||
for v in zip(*values):
|
||||
yield _product(v)
|
||||
|
||||
|
||||
def queued(values, qsize):
|
||||
"""
|
||||
Queues up readings from *values* (the number of readings queued is
|
||||
determined by *qsize*) and begins yielding values only when the queue is
|
||||
full. For example, to "cascade" values along a sequence of LEDs::
|
||||
|
||||
from gpiozero import LEDBoard, Button
|
||||
from gpiozero.tools import queued
|
||||
from signal import pause
|
||||
|
||||
leds = LEDBoard(5, 6, 13, 19, 26)
|
||||
btn = Button(17)
|
||||
|
||||
for i in range(4):
|
||||
leds[i].source = queued(leds[i + 1], 5)
|
||||
leds[i].source_delay = 0.01
|
||||
|
||||
leds[4].source = btn
|
||||
|
||||
pause()
|
||||
"""
|
||||
values = [_normalize(v) for v in values]
|
||||
if qsize < 1:
|
||||
raise ValueError("qsize must be 1 or larger")
|
||||
q = []
|
||||
it = iter(values)
|
||||
try:
|
||||
for i in range(qsize):
|
||||
q.append(next(it))
|
||||
for i in cycle(range(qsize)):
|
||||
yield q[i]
|
||||
q[i] = next(it)
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
|
||||
def smoothed(values, qsize, average=mean):
|
||||
"""
|
||||
Queues up readings from *values* (the number of readings queued is
|
||||
determined by *qsize*) and begins yielding the *average* of the last
|
||||
*qsize* values when the queue is full. The larger the *qsize*, the more the
|
||||
values are smoothed. For example, to smooth the analog values read from an
|
||||
ADC::
|
||||
|
||||
from gpiozero import MCP3008
|
||||
from gpiozero.tools import smoothed
|
||||
|
||||
adc = MCP3008(channel=0)
|
||||
|
||||
for value in smoothed(adc, 5):
|
||||
print(value)
|
||||
"""
|
||||
values = _normalize(values)
|
||||
if qsize < 1:
|
||||
raise ValueError("qsize must be 1 or larger")
|
||||
q = []
|
||||
it = iter(values)
|
||||
try:
|
||||
for i in range(qsize):
|
||||
q.append(next(it))
|
||||
for i in cycle(range(qsize)):
|
||||
yield average(q)
|
||||
q[i] = next(it)
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
|
||||
def pre_delayed(values, delay):
|
||||
"""
|
||||
Waits for *delay* seconds before returning each item from *values*.
|
||||
"""
|
||||
values = _normalize(values)
|
||||
if delay < 0:
|
||||
raise ValueError("delay must be 0 or larger")
|
||||
for v in values:
|
||||
sleep(delay)
|
||||
yield v
|
||||
|
||||
|
||||
def post_delayed(values, delay):
|
||||
"""
|
||||
Waits for *delay* seconds after returning each item from *values*.
|
||||
"""
|
||||
values = _normalize(values)
|
||||
if delay < 0:
|
||||
raise ValueError("delay must be 0 or larger")
|
||||
for v in values:
|
||||
yield v
|
||||
sleep(delay)
|
||||
|
||||
|
||||
def pre_periodic_filtered(values, block, repeat_after):
|
||||
"""
|
||||
Blocks the first *block* items from *values*, repeating the block after
|
||||
every *repeat_after* items, if *repeat_after* is non-zero. For example, to
|
||||
discard the first 50 values read from an ADC::
|
||||
|
||||
from gpiozero import MCP3008
|
||||
from gpiozero.tools import pre_periodic_filtered
|
||||
|
||||
adc = MCP3008(channel=0)
|
||||
|
||||
for value in pre_periodic_filtered(adc, 50, 0):
|
||||
print(value)
|
||||
|
||||
Or to only display every even item read from an ADC::
|
||||
|
||||
from gpiozero import MCP3008
|
||||
from gpiozero.tools import pre_periodic_filtered
|
||||
|
||||
adc = MCP3008(channel=0)
|
||||
|
||||
for value in pre_periodic_filtered(adc, 1, 1):
|
||||
print(value)
|
||||
"""
|
||||
values = _normalize(values)
|
||||
if block < 1:
|
||||
raise ValueError("block must be 1 or larger")
|
||||
if repeat_after < 0:
|
||||
raise ValueError("repeat_after must be 0 or larger")
|
||||
it = iter(values)
|
||||
try:
|
||||
if repeat_after == 0:
|
||||
for _ in range(block):
|
||||
next(it)
|
||||
while True:
|
||||
yield next(it)
|
||||
else:
|
||||
while True:
|
||||
for _ in range(block):
|
||||
next(it)
|
||||
for _ in range(repeat_after):
|
||||
yield next(it)
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
|
||||
def post_periodic_filtered(values, repeat_after, block):
|
||||
"""
|
||||
After every *repeat_after* items, blocks the next *block* items from
|
||||
*values*. Note that unlike :func:`pre_periodic_filtered`, *repeat_after*
|
||||
can't be 0. For example, to block every tenth item read from an ADC::
|
||||
|
||||
from gpiozero import MCP3008
|
||||
from gpiozero.tools import post_periodic_filtered
|
||||
|
||||
adc = MCP3008(channel=0)
|
||||
|
||||
for value in post_periodic_filtered(adc, 9, 1):
|
||||
print(value)
|
||||
"""
|
||||
values = _normalize(values)
|
||||
if repeat_after < 1:
|
||||
raise ValueError("repeat_after must be 1 or larger")
|
||||
if block < 1:
|
||||
raise ValueError("block must be 1 or larger")
|
||||
it = iter(values)
|
||||
try:
|
||||
while True:
|
||||
for _ in range(repeat_after):
|
||||
yield next(it)
|
||||
for _ in range(block):
|
||||
next(it)
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
|
||||
def random_values():
|
||||
"""
|
||||
Provides an infinite source of random values between 0 and 1. For example,
|
||||
to produce a "flickering candle" effect with an LED::
|
||||
|
||||
from gpiozero import PWMLED
|
||||
from gpiozero.tools import random_values
|
||||
from signal import pause
|
||||
|
||||
led = PWMLED(4)
|
||||
|
||||
led.source = random_values()
|
||||
|
||||
pause()
|
||||
|
||||
If you require a wider range than 0 to 1, see :func:`scaled`.
|
||||
"""
|
||||
while True:
|
||||
yield random()
|
||||
|
||||
|
||||
def sin_values(period=360):
|
||||
"""
|
||||
Provides an infinite source of values representing a sine wave (from -1 to
|
||||
+1) which repeats every *period* values. For example, to produce a "siren"
|
||||
effect with a couple of LEDs that repeats once a second::
|
||||
|
||||
from gpiozero import PWMLED
|
||||
from gpiozero.tools import sin_values, scaled_half, inverted
|
||||
from signal import pause
|
||||
|
||||
red = PWMLED(2)
|
||||
blue = PWMLED(3)
|
||||
|
||||
red.source_delay = 0.01
|
||||
blue.source_delay = red.source_delay
|
||||
red.source = scaled_half(sin_values(100))
|
||||
blue.source = inverted(red)
|
||||
|
||||
pause()
|
||||
|
||||
If you require a different range than -1 to +1, see :func:`scaled`.
|
||||
"""
|
||||
angles = (2 * pi * i / period for i in range(period))
|
||||
for a in cycle(angles):
|
||||
yield sin(a)
|
||||
|
||||
|
||||
def cos_values(period=360):
|
||||
"""
|
||||
Provides an infinite source of values representing a cosine wave (from -1
|
||||
to +1) which repeats every *period* values. For example, to produce a
|
||||
"siren" effect with a couple of LEDs that repeats once a second::
|
||||
|
||||
from gpiozero import PWMLED
|
||||
from gpiozero.tools import cos_values, scaled_half, inverted
|
||||
from signal import pause
|
||||
|
||||
red = PWMLED(2)
|
||||
blue = PWMLED(3)
|
||||
|
||||
red.source_delay = 0.01
|
||||
blue.source_delay = red.source_delay
|
||||
red.source = scaled_half(cos_values(100))
|
||||
blue.source = inverted(red)
|
||||
|
||||
pause()
|
||||
|
||||
If you require a different range than -1 to +1, see :func:`scaled`.
|
||||
"""
|
||||
angles = (2 * pi * i / period for i in range(period))
|
||||
for a in cycle(angles):
|
||||
yield cos(a)
|
||||
|
||||
|
||||
def alternating_values(initial_value=False):
|
||||
"""
|
||||
Provides an infinite source of values alternating between :data:`True` and
|
||||
:data:`False`, starting wth *initial_value* (which defaults to
|
||||
:data:`False`). For example, to produce a flashing LED::
|
||||
|
||||
from gpiozero import LED
|
||||
from gpiozero.tools import alternating_values
|
||||
from signal import pause
|
||||
|
||||
red = LED(2)
|
||||
|
||||
red.source_delay = 0.5
|
||||
red.source = alternating_values()
|
||||
|
||||
pause()
|
||||
"""
|
||||
value = initial_value
|
||||
while True:
|
||||
yield value
|
||||
value = not value
|
||||
|
||||
|
||||
def ramping_values(period=360):
|
||||
"""
|
||||
Provides an infinite source of values representing a triangle wave (from 0
|
||||
to 1 and back again) which repeats every *period* values. For example, to
|
||||
pulse an LED once a second::
|
||||
|
||||
from gpiozero import PWMLED
|
||||
from gpiozero.tools import ramping_values
|
||||
from signal import pause
|
||||
|
||||
red = PWMLED(2)
|
||||
|
||||
red.source_delay = 0.01
|
||||
red.source = ramping_values(100)
|
||||
|
||||
pause()
|
||||
|
||||
If you require a wider range than 0 to 1, see :func:`scaled`.
|
||||
"""
|
||||
step = 2 / period
|
||||
value = 0
|
||||
while True:
|
||||
yield value
|
||||
value += step
|
||||
if isclose(value, 1, abs_tol=1e-9):
|
||||
value = 1
|
||||
step *= -1
|
||||
elif isclose(value, 0, abs_tol=1e-9):
|
||||
value = 0
|
||||
step *= -1
|
||||
elif value > 1 or value < 0:
|
||||
step *= -1
|
||||
value += step
|
||||
|
||||
|
||||
def zip_values(*devices):
|
||||
"""
|
||||
Provides a source constructed from the values of each item, for example::
|
||||
|
||||
from gpiozero import MCP3008, Robot
|
||||
from gpiozero.tools import zip_values
|
||||
from signal import pause
|
||||
|
||||
robot = Robot(left=(4, 14), right=(17, 18))
|
||||
|
||||
left = MCP3008(0)
|
||||
right = MCP3008(1)
|
||||
|
||||
robot.source = zip_values(left, right)
|
||||
|
||||
pause()
|
||||
|
||||
``zip_values(left, right)`` is equivalent to ``zip(left.values,
|
||||
right.values)``.
|
||||
"""
|
||||
return zip(*[d.values for d in devices])
|
||||
Reference in New Issue
Block a user